2 float SUB_True() { return 1; }
3 float SUB_False() { return 0; }
5 void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove;
6 void() SUB_CalcMoveDone;
7 void() SUB_CalcAngleMoveDone;
8 //void() SUB_UseTargets;
11 void spawnfunc_info_null (void)
14 // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
17 void setanim(entity e, vector anim, float looping, float override, float restart)
20 return; // no animation was given to us! We can't use this.
22 if (anim_x == e.animstate_startframe)
23 if (anim_y == e.animstate_numframes)
24 if (anim_z == e.animstate_framerate)
29 if(anim_y == 1) // ZYM animation
30 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
35 e.animstate_startframe = anim_x;
36 e.animstate_numframes = anim_y;
37 e.animstate_framerate = anim_z;
38 e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
39 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
40 e.animstate_looping = looping;
41 e.animstate_override = override;
42 e.frame = e.animstate_startframe;
43 e.frame1time = servertime;
46 void updateanim(entity e)
48 if (time >= e.animstate_endtime)
50 if (e.animstate_looping)
52 e.animstate_starttime = e.animstate_endtime;
53 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
55 e.animstate_override = FALSE;
57 e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
58 //print(ftos(time), " -> ", ftos(e.frame), "\n");
62 vector animparseline(float animfile)
69 line = fgets(animfile);
70 c = tokenize_console(line);
73 animparseerror = TRUE;
76 anim_x = stof(argv(0));
77 anim_y = stof(argv(1));
78 anim_z = stof(argv(2));
79 // don't allow completely bogus values
80 if (anim_x < 0 || anim_y < 1 || anim_z < 0.001)
92 void SUB_Remove (void)
101 Applies some friction to self
105 void SUB_Friction (void)
107 self.nextthink = time;
108 if(self.flags & FL_ONGROUND)
109 self.velocity = self.velocity * (1 - frametime * self.friction);
116 Makes client invisible or removes non-client
119 void SUB_VanishOrRemove (entity ent)
121 if (ent.flags & FL_CLIENT)
136 void SUB_SetFade_Think (void)
138 self.think = SUB_SetFade_Think;
139 self.nextthink = self.fade_time;
140 self.alpha = 1 - (time - self.fade_time) * self.fade_rate;
141 if (self.alpha < 0.01)
142 SUB_VanishOrRemove(self);
143 self.alpha = bound(0.01, self.alpha, 1);
150 Fade 'ent' out when time >= 'when'
153 void SUB_SetFade (entity ent, float when, float fadetime)
155 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
158 ent.fade_rate = 1/fadetime;
159 ent.fade_time = when;
160 ent.think = SUB_SetFade_Think;
161 ent.nextthink = when;
168 calculate self.velocity and self.nextthink to reach dest from
169 self.origin traveling at speed
172 void SUB_CalcMoveDone (void)
174 // After moving, set origin to exact final destination
176 setorigin (self, self.finaldest);
177 self.velocity = '0 0 0';
183 void SUB_CalcMove_controller_think (void)
192 if(time < self.animstate_endtime) {
193 delta = self.destvec;
194 nexttick = time + sys_frametime;
196 if(nexttick < self.animstate_endtime) {
197 traveltime = self.animstate_endtime - self.animstate_starttime;
198 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
199 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
200 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
201 phasepos = phasepos + 1; // correct range to [0, 2]
202 phasepos = phasepos / 2; // correct range to [0, 1]
203 nextpos = self.origin + (delta * phasepos);
205 veloc = nextpos - self.owner.origin;
206 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
209 veloc = self.finaldest - self.owner.origin;
210 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
212 self.owner.velocity = veloc;
213 self.nextthink = nexttick;
216 self.owner.think = self.think1;
223 void SUB_CalcMove (vector tdest, float tspeed, void() func)
230 objerror ("No speed is defined!");
233 self.finaldest = tdest;
234 self.think = SUB_CalcMoveDone;
236 if (tdest == self.origin)
238 self.velocity = '0 0 0';
239 self.nextthink = self.ltime + 0.1;
243 delta = tdest - self.origin;
244 traveltime = vlen (delta) / tspeed;
246 if (traveltime < 0.1)
248 self.velocity = '0 0 0';
249 self.nextthink = self.ltime + 0.1;
253 // Very short animations don't really show off the effect
254 // of controlled animation, so let's just use linear movement.
255 // Alternatively entities can choose to specify non-controlled movement.
256 // The only currently implemented alternative movement is linear (value 1)
257 if (traveltime < 0.15 || self.platmovetype == 1)
259 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
260 self.nextthink = self.ltime + traveltime;
264 controller = spawn();
265 controller.classname = "SUB_CalcMove_controller";
266 controller.owner = self;
267 controller.origin = self.origin; // starting point
268 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
269 controller.destvec = delta;
270 controller.animstate_starttime = time;
271 controller.animstate_endtime = time + traveltime;
272 controller.think = SUB_CalcMove_controller_think;
273 controller.think1 = self.think;
275 // the thinking is now done by the controller
276 self.think = SUB_Null;
277 self.nextthink = self.ltime + traveltime;
285 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
292 SUB_CalcMove (tdest, tspeed, func);
301 calculate self.avelocity and self.nextthink to reach destangle from
304 The calling function should make sure self.think is valid
307 void SUB_CalcAngleMoveDone (void)
309 // After rotating, set angle to exact final angle
310 self.angles = self.finalangle;
311 self.avelocity = '0 0 0';
317 // FIXME: I fixed this function only for rotation around the main axes
318 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
324 objerror ("No speed is defined!");
326 // take the shortest distance for the angles
327 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
328 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
329 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
330 delta = destangle - self.angles;
331 traveltime = vlen (delta) / tspeed;
334 self.finalangle = destangle;
335 self.think = SUB_CalcAngleMoveDone;
337 if (traveltime < 0.1)
339 self.avelocity = '0 0 0';
340 self.nextthink = self.ltime + 0.1;
344 self.avelocity = delta * (1 / traveltime);
345 self.nextthink = self.ltime + traveltime;
348 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
355 SUB_CalcAngleMove (destangle, tspeed, func);
364 unused but required by the engine
378 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
379 Additionally it moves players back into the past before the trace and restores them afterward.
382 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
385 local float oldsolid;
387 // check whether antilagged traces are enabled
390 if (clienttype(forent) != CLIENTTYPE_REAL)
391 lag = 0; // only antilag for clients
393 // change shooter to SOLID_BBOX so the shot can hit corpses
396 oldsolid = source.dphitcontentsmask;
397 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
402 // take players back into the past
403 player = player_list;
406 antilag_takeback(player, time - lag);
407 player = player.nextplayer;
413 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
415 tracebox (v1, mi, ma, v2, nomonst, forent);
417 // restore players to current positions
420 player = player_list;
423 antilag_restore(player);
424 player = player.nextplayer;
428 // restore shooter solid type
430 source.dphitcontentsmask = oldsolid;
432 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
434 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
436 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
438 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
440 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
442 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
444 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
446 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
448 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
450 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
452 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
454 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
456 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
458 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
460 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
462 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
465 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
470 //nudge = 2 * cvar("collision_impactnudge"); // why not?
473 dir = normalize(v2 - v1);
475 pos = v1 + dir * nudge;
482 if((pos - v1) * dir >= (v2 - v1) * dir)
490 tracebox(pos, mi, ma, v2, nomonsters, forent);
495 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
496 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
497 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
498 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
503 // we started inside solid.
504 // then trace from endpos to pos
506 tracebox(t, mi, ma, pos, nomonsters, forent);
510 // t is still inside solid? bad
511 // force advance, then, and retry
512 pos = t + dir * nudge;
516 // we actually LEFT solid!
517 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
523 // pos is outside solid?!? but why?!? never mind, just return it.
525 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
531 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
537 //nudge = 2 * cvar("collision_impactnudge"); // why not?
540 dir = normalize(v2 - v1);
542 pos = v1 + dir * nudge;
546 if((pos - v1) * dir >= (v2 - v1) * dir)
553 traceline(pos, v2, nomonsters, forent);
557 // we started inside solid.
558 // then trace from endpos to pos
560 traceline(t, pos, nomonsters, forent);
563 // t is inside solid? bad
564 // force advance, then
565 pos = pos + dir * nudge;
569 // we actually LEFT solid!
570 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
576 // pos is outside solid?!? but why?!? never mind, just return it.
578 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
583 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
590 Returns a point at least 12 units away from walls
591 (useful for explosion animations, although the blast is performed where it really happened)
595 vector findbetterlocation (vector org, float mindist)
601 vec = mindist * '1 0 0';
605 traceline (org, org + vec, TRUE, world);
607 if (trace_fraction < 1)
610 traceline (loc, loc + vec, TRUE, world);
611 if (trace_fraction >= 1)
631 Returns a random number between -1.0 and 1.0
636 return 2 * (random () - 0.5);
641 Angc used for animations
646 float angc (float a1, float a2)
673 .float lodmodelindex0;
674 .float lodmodelindex1;
675 .float lodmodelindex2;
679 float LOD_customize()
683 if(autocvar_loddebug)
685 d = autocvar_loddebug;
687 self.modelindex = self.lodmodelindex0;
688 else if(d == 2 || !self.lodmodelindex2)
689 self.modelindex = self.lodmodelindex1;
691 self.modelindex = self.lodmodelindex2;
695 // TODO csqc network this so it only gets sent once
696 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
697 if(d < self.loddistance1)
698 self.modelindex = self.lodmodelindex0;
699 else if(!self.lodmodelindex2 || d < self.loddistance2)
700 self.modelindex = self.lodmodelindex1;
702 self.modelindex = self.lodmodelindex2;
707 void LOD_uncustomize()
709 self.modelindex = self.lodmodelindex0;
712 void LODmodel_attach()
716 if(!self.loddistance1)
717 self.loddistance1 = 1000;
718 if(!self.loddistance2)
719 self.loddistance2 = 2000;
720 self.lodmodelindex0 = self.modelindex;
722 if(self.lodtarget1 != "")
724 e = find(world, targetname, self.lodtarget1);
727 self.lodmodel1 = e.model;
731 if(self.lodtarget2 != "")
733 e = find(world, targetname, self.lodtarget2);
736 self.lodmodel2 = e.model;
741 if(autocvar_loddebug < 0)
743 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
746 if(self.lodmodel1 != "")
752 precache_model(self.lodmodel1);
753 setmodel(self, self.lodmodel1);
754 self.lodmodelindex1 = self.modelindex;
756 if(self.lodmodel2 != "")
758 precache_model(self.lodmodel2);
759 setmodel(self, self.lodmodel2);
760 self.lodmodelindex2 = self.modelindex;
763 self.modelindex = self.lodmodelindex0;
764 setsize(self, mi, ma);
767 if(self.lodmodelindex1)
768 if not(self.SendEntity)
769 SetCustomizer(self, LOD_customize, LOD_uncustomize);
772 void ApplyMinMaxScaleAngles(entity e)
774 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
776 e.maxs = '1 1 1' * vlen(
777 '1 0 0' * max(-e.mins_x, e.maxs_x) +
778 '0 1 0' * max(-e.mins_y, e.maxs_y) +
779 '0 0 1' * max(-e.mins_z, e.maxs_z)
783 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
786 '1 0 0' * max(-e.mins_x, e.maxs_x) +
787 '0 1 0' * max(-e.mins_y, e.maxs_y)
790 e.mins_x = -e.maxs_x;
791 e.mins_y = -e.maxs_x;
794 setsize(e, e.mins * e.scale, e.maxs * e.scale);
796 setsize(e, e.mins, e.maxs);
799 void SetBrushEntityModel()
803 precache_model(self.model);
804 setmodel(self, self.model); // no precision needed
805 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
807 setorigin(self, self.origin);
808 ApplyMinMaxScaleAngles(self);
811 void SetBrushEntityModelNoLOD()
815 precache_model(self.model);
816 setmodel(self, self.model); // no precision needed
818 setorigin(self, self.origin);
819 ApplyMinMaxScaleAngles(self);
830 if (self.movedir != '0 0 0')
831 self.movedir = normalize(self.movedir);
834 makevectors (self.angles);
835 self.movedir = v_forward;
838 self.angles = '0 0 0';
843 // trigger angles are used for one-way touches. An angle of 0 is assumed
844 // to mean no restrictions, so use a yaw of 360 instead.
846 self.solid = SOLID_TRIGGER;
847 SetBrushEntityModel();
848 self.movetype = MOVETYPE_NONE;
853 void InitSolidBSPTrigger()
855 // trigger angles are used for one-way touches. An angle of 0 is assumed
856 // to mean no restrictions, so use a yaw of 360 instead.
858 self.solid = SOLID_BSP;
859 SetBrushEntityModel();
860 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
861 // self.modelindex = 0;
865 float InitMovingBrushTrigger()
867 // trigger angles are used for one-way touches. An angle of 0 is assumed
868 // to mean no restrictions, so use a yaw of 360 instead.
869 self.solid = SOLID_BSP;
870 SetBrushEntityModel();
871 self.movetype = MOVETYPE_PUSH;
872 if(self.modelindex == 0)
874 objerror("InitMovingBrushTrigger: no brushes found!");