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");
61 vector animfixfps(entity e, vector a)
63 // multi-frame anim: keep as-is
67 dur = frameduration(e.modelindex, a_x);
81 void SUB_Remove (void)
90 Applies some friction to self
94 void SUB_Friction (void)
96 self.nextthink = time;
97 if(self.flags & FL_ONGROUND)
98 self.velocity = self.velocity * (1 - frametime * self.friction);
105 Makes client invisible or removes non-client
108 void SUB_VanishOrRemove (entity ent)
110 if (ent.flags & FL_CLIENT)
125 void SUB_SetFade_Think (void)
129 self.think = SUB_SetFade_Think;
130 self.nextthink = time;
131 self.alpha -= frametime * self.fade_rate;
132 if (self.alpha < 0.01)
133 SUB_VanishOrRemove(self);
135 self.nextthink = time;
142 Fade 'ent' out when time >= 'when'
145 void SUB_SetFade (entity ent, float when, float fadetime)
147 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
150 ent.fade_rate = 1/fadetime;
151 ent.think = SUB_SetFade_Think;
152 ent.nextthink = when;
159 calculate self.velocity and self.nextthink to reach dest from
160 self.origin traveling at speed
163 void SUB_CalcMoveDone (void)
165 // After moving, set origin to exact final destination
167 setorigin (self, self.finaldest);
168 self.velocity = '0 0 0';
174 void SUB_CalcMove_controller_think (void)
183 if(time < self.animstate_endtime) {
184 delta = self.destvec;
185 nexttick = time + sys_frametime;
187 if(nexttick < self.animstate_endtime) {
188 traveltime = self.animstate_endtime - self.animstate_starttime;
189 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
190 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
191 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
192 phasepos = phasepos + 1; // correct range to [0, 2]
193 phasepos = phasepos / 2; // correct range to [0, 1]
194 nextpos = self.origin + (delta * phasepos);
196 veloc = nextpos - self.owner.origin;
197 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
200 veloc = self.finaldest - self.owner.origin;
201 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
203 self.owner.velocity = veloc;
204 self.nextthink = nexttick;
207 self.owner.think = self.think1;
214 void SUB_CalcMove (vector tdest, float tspeed, void() func)
221 objerror ("No speed is defined!");
224 self.finaldest = tdest;
225 self.think = SUB_CalcMoveDone;
227 if (tdest == self.origin)
229 self.velocity = '0 0 0';
230 self.nextthink = self.ltime + 0.1;
234 delta = tdest - self.origin;
235 traveltime = vlen (delta) / tspeed;
237 if (traveltime < 0.1)
239 self.velocity = '0 0 0';
240 self.nextthink = self.ltime + 0.1;
244 // Very short animations don't really show off the effect
245 // of controlled animation, so let's just use linear movement.
246 // Alternatively entities can choose to specify non-controlled movement.
247 // The only currently implemented alternative movement is linear (value 1)
248 if (traveltime < 0.15 || self.platmovetype == 1)
250 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
251 self.nextthink = self.ltime + traveltime;
255 controller = spawn();
256 controller.classname = "SUB_CalcMove_controller";
257 controller.owner = self;
258 controller.origin = self.origin; // starting point
259 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
260 controller.destvec = delta;
261 controller.animstate_starttime = time;
262 controller.animstate_endtime = time + traveltime;
263 controller.think = SUB_CalcMove_controller_think;
264 controller.think1 = self.think;
266 // the thinking is now done by the controller
267 self.think = SUB_Null;
268 self.nextthink = self.ltime + traveltime;
276 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
283 SUB_CalcMove (tdest, tspeed, func);
292 calculate self.avelocity and self.nextthink to reach destangle from
295 The calling function should make sure self.think is valid
298 void SUB_CalcAngleMoveDone (void)
300 // After rotating, set angle to exact final angle
301 self.angles = self.finalangle;
302 self.avelocity = '0 0 0';
308 // FIXME: I fixed this function only for rotation around the main axes
309 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
315 objerror ("No speed is defined!");
317 // take the shortest distance for the angles
318 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
319 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
320 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
321 delta = destangle - self.angles;
322 traveltime = vlen (delta) / tspeed;
325 self.finalangle = destangle;
326 self.think = SUB_CalcAngleMoveDone;
328 if (traveltime < 0.1)
330 self.avelocity = '0 0 0';
331 self.nextthink = self.ltime + 0.1;
335 self.avelocity = delta * (1 / traveltime);
336 self.nextthink = self.ltime + traveltime;
339 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
346 SUB_CalcAngleMove (destangle, tspeed, func);
355 unused but required by the engine
369 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
370 Additionally it moves players back into the past before the trace and restores them afterward.
373 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
378 // check whether antilagged traces are enabled
381 if (clienttype(forent) != CLIENTTYPE_REAL)
382 lag = 0; // only antilag for clients
384 // change shooter to SOLID_BBOX so the shot can hit corpses
387 oldsolid = source.dphitcontentsmask;
388 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
393 // take players back into the past
394 FOR_EACH_PLAYER(player)
396 antilag_takeback(player, time - lag);
401 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
403 tracebox (v1, mi, ma, v2, nomonst, forent);
405 // restore players to current positions
408 FOR_EACH_PLAYER(player)
410 antilag_restore(player);
413 // restore shooter solid type
415 source.dphitcontentsmask = oldsolid;
417 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
419 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
421 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
423 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
425 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
427 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
429 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
431 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
433 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
435 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
437 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
439 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
441 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
443 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
445 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
447 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
450 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
455 //nudge = 2 * cvar("collision_impactnudge"); // why not?
458 dir = normalize(v2 - v1);
460 pos = v1 + dir * nudge;
467 if((pos - v1) * dir >= (v2 - v1) * dir)
475 tracebox(pos, mi, ma, v2, nomonsters, forent);
480 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
481 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
482 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
483 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
488 // we started inside solid.
489 // then trace from endpos to pos
491 tracebox(t, mi, ma, pos, nomonsters, forent);
495 // t is still inside solid? bad
496 // force advance, then, and retry
497 pos = t + dir * nudge;
501 // we actually LEFT solid!
502 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
508 // pos is outside solid?!? but why?!? never mind, just return it.
510 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
516 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
522 //nudge = 2 * cvar("collision_impactnudge"); // why not?
525 dir = normalize(v2 - v1);
527 pos = v1 + dir * nudge;
531 if((pos - v1) * dir >= (v2 - v1) * dir)
538 traceline(pos, v2, nomonsters, forent);
542 // we started inside solid.
543 // then trace from endpos to pos
545 traceline(t, pos, nomonsters, forent);
548 // t is inside solid? bad
549 // force advance, then
550 pos = pos + dir * nudge;
554 // we actually LEFT solid!
555 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
561 // pos is outside solid?!? but why?!? never mind, just return it.
563 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
568 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
575 Returns a point at least 12 units away from walls
576 (useful for explosion animations, although the blast is performed where it really happened)
580 vector findbetterlocation (vector org, float mindist)
586 vec = mindist * '1 0 0';
590 traceline (org, org + vec, TRUE, world);
592 if (trace_fraction < 1)
595 traceline (loc, loc + vec, TRUE, world);
596 if (trace_fraction >= 1)
616 Returns a random number between -1.0 and 1.0
621 return 2 * (random () - 0.5);
626 Angc used for animations
631 float angc (float a1, float a2)
658 .float lodmodelindex0;
659 .float lodmodelindex1;
660 .float lodmodelindex2;
664 float LOD_customize()
668 if(autocvar_loddebug)
670 d = autocvar_loddebug;
672 self.modelindex = self.lodmodelindex0;
673 else if(d == 2 || !self.lodmodelindex2)
674 self.modelindex = self.lodmodelindex1;
676 self.modelindex = self.lodmodelindex2;
680 // TODO csqc network this so it only gets sent once
681 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
682 if(d < self.loddistance1)
683 self.modelindex = self.lodmodelindex0;
684 else if(!self.lodmodelindex2 || d < self.loddistance2)
685 self.modelindex = self.lodmodelindex1;
687 self.modelindex = self.lodmodelindex2;
692 void LOD_uncustomize()
694 self.modelindex = self.lodmodelindex0;
697 void LODmodel_attach()
701 if(!self.loddistance1)
702 self.loddistance1 = 1000;
703 if(!self.loddistance2)
704 self.loddistance2 = 2000;
705 self.lodmodelindex0 = self.modelindex;
707 if(self.lodtarget1 != "")
709 e = find(world, targetname, self.lodtarget1);
712 self.lodmodel1 = e.model;
716 if(self.lodtarget2 != "")
718 e = find(world, targetname, self.lodtarget2);
721 self.lodmodel2 = e.model;
726 if(autocvar_loddebug < 0)
728 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
731 if(self.lodmodel1 != "")
737 precache_model(self.lodmodel1);
738 setmodel(self, self.lodmodel1);
739 self.lodmodelindex1 = self.modelindex;
741 if(self.lodmodel2 != "")
743 precache_model(self.lodmodel2);
744 setmodel(self, self.lodmodel2);
745 self.lodmodelindex2 = self.modelindex;
748 self.modelindex = self.lodmodelindex0;
749 setsize(self, mi, ma);
752 if(self.lodmodelindex1)
753 if not(self.SendEntity)
754 SetCustomizer(self, LOD_customize, LOD_uncustomize);
757 void ApplyMinMaxScaleAngles(entity e)
759 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
761 e.maxs = '1 1 1' * vlen(
762 '1 0 0' * max(-e.mins_x, e.maxs_x) +
763 '0 1 0' * max(-e.mins_y, e.maxs_y) +
764 '0 0 1' * max(-e.mins_z, e.maxs_z)
768 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
771 '1 0 0' * max(-e.mins_x, e.maxs_x) +
772 '0 1 0' * max(-e.mins_y, e.maxs_y)
775 e.mins_x = -e.maxs_x;
776 e.mins_y = -e.maxs_x;
779 setsize(e, e.mins * e.scale, e.maxs * e.scale);
781 setsize(e, e.mins, e.maxs);
784 void SetBrushEntityModel()
788 precache_model(self.model);
789 setmodel(self, self.model); // no precision needed
790 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
792 setorigin(self, self.origin);
793 ApplyMinMaxScaleAngles(self);
796 void SetBrushEntityModelNoLOD()
800 precache_model(self.model);
801 setmodel(self, self.model); // no precision needed
803 setorigin(self, self.origin);
804 ApplyMinMaxScaleAngles(self);
815 if (self.movedir != '0 0 0')
816 self.movedir = normalize(self.movedir);
819 makevectors (self.angles);
820 self.movedir = v_forward;
823 self.angles = '0 0 0';
828 // trigger angles are used for one-way touches. An angle of 0 is assumed
829 // to mean no restrictions, so use a yaw of 360 instead.
831 self.solid = SOLID_TRIGGER;
832 SetBrushEntityModel();
833 self.movetype = MOVETYPE_NONE;
838 void InitSolidBSPTrigger()
840 // trigger angles are used for one-way touches. An angle of 0 is assumed
841 // to mean no restrictions, so use a yaw of 360 instead.
843 self.solid = SOLID_BSP;
844 SetBrushEntityModel();
845 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
846 // self.modelindex = 0;
850 float InitMovingBrushTrigger()
852 // trigger angles are used for one-way touches. An angle of 0 is assumed
853 // to mean no restrictions, so use a yaw of 360 instead.
854 self.solid = SOLID_BSP;
855 SetBrushEntityModel();
856 self.movetype = MOVETYPE_PUSH;
857 if(self.modelindex == 0)
859 objerror("InitMovingBrushTrigger: no brushes found!");