2 float SUB_True() { return 1; }
3 float SUB_False() { return 0; }
5 void() SUB_CalcMoveDone;
6 void() SUB_CalcAngleMoveDone;
7 //void() SUB_UseTargets;
10 void spawnfunc_info_null (void)
13 // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
16 void setanim(entity e, vector anim, float looping, float override, float restart)
19 return; // no animation was given to us! We can't use this.
21 if (anim_x == e.animstate_startframe)
22 if (anim_y == e.animstate_numframes)
23 if (anim_z == e.animstate_framerate)
28 if(anim_y == 1) // ZYM animation
29 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
34 e.animstate_startframe = anim_x;
35 e.animstate_numframes = anim_y;
36 e.animstate_framerate = anim_z;
37 e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
38 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
39 e.animstate_looping = looping;
40 e.animstate_override = override;
41 e.frame = e.animstate_startframe;
42 e.frame1time = servertime;
45 void updateanim(entity e)
47 if (time >= e.animstate_endtime)
49 if (e.animstate_looping)
51 e.animstate_starttime = e.animstate_endtime;
52 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
54 e.animstate_override = FALSE;
56 e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
57 //print(ftos(time), " -> ", ftos(e.frame), "\n");
60 vector animfixfps(entity e, vector a)
62 // multi-frame anim: keep as-is
66 dur = frameduration(e.modelindex, a_x);
80 void SUB_Remove (void)
89 Applies some friction to self
93 void SUB_Friction (void)
95 self.nextthink = time;
96 if(self.flags & FL_ONGROUND)
97 self.velocity = self.velocity * (1 - frametime * self.friction);
104 Makes client invisible or removes non-client
107 void SUB_VanishOrRemove (entity ent)
109 if (ent.flags & FL_CLIENT)
124 void SUB_SetFade_Think (void)
128 self.think = SUB_SetFade_Think;
129 self.nextthink = time;
130 self.alpha -= frametime * self.fade_rate;
131 if (self.alpha < 0.01)
132 SUB_VanishOrRemove(self);
134 self.nextthink = time;
141 Fade 'ent' out when time >= 'when'
144 void SUB_SetFade (entity ent, float when, float fadetime)
146 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
149 ent.fade_rate = 1/fadetime;
150 ent.think = SUB_SetFade_Think;
151 ent.nextthink = when;
158 calculate self.velocity and self.nextthink to reach dest from
159 self.origin traveling at speed
162 void SUB_CalcMoveDone (void)
164 // After moving, set origin to exact final destination
166 setorigin (self, self.finaldest);
167 self.velocity = '0 0 0';
174 void SUB_CalcMove_controller_think (void)
184 delta = self.destvec;
185 delta2 = self.destvec2;
186 if(time < self.animstate_endtime) {
187 nexttick = time + sys_frametime;
189 traveltime = self.animstate_endtime - self.animstate_starttime;
190 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
191 if(!self.platmovetype)
192 self.platmovetype = 2; // default
193 switch(self.platmovetype)
197 // phasepos = cubic_speedfunc(1, 1, phasepos); // identity
199 /* old version, good for mathematical reference
200 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
201 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
202 phasepos = phasepos + 1; // correct range to [0, 2]
203 phasepos = phasepos / 2; // correct range to [0, 1]
206 // phasepos = (1 - cos(phasepos * 3.14159265)) / 2;
207 phasepos = cubic_speedfunc(0, 0, phasepos);
209 case 3: // inverted cosine
210 // phasepos = acos(1 - phasepos * 2) / 3.14159265;
211 phasepos = cubic_speedfunc(2, 2, phasepos);
213 case 4: // half cosine
214 // phasepos = (1 - cos(phasepos * (3.14159265 / 2)));
215 phasepos = cubic_speedfunc(0, 1.5, phasepos);
217 case 5: // inverted half cosine
218 // phasepos = sin(phasepos * (3.14159265 / 2));
219 phasepos = cubic_speedfunc(1.5, 0, phasepos);
222 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
223 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
225 if(nexttick < self.animstate_endtime) {
226 veloc = nextpos - self.owner.origin;
227 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
229 veloc = self.finaldest - self.owner.origin;
230 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
232 self.owner.velocity = veloc;
233 if(self.owner.bezier_turn)
236 vel = delta + 2 * delta2 * phasepos;
237 vel_z = -vel_z; // invert z velocity
238 vel = vectoangles(vel);
239 self.owner.angles = vel;
241 self.nextthink = nexttick;
243 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
245 self.owner.think = self.think1;
252 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
254 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
255 // 2 * control * t - 2 * control * t * t + dest * t * t
256 // 2 * control * t + (dest - 2 * control) * t * t
258 controller.origin = org; // starting point
262 controller.destvec = 2 * control; // control point
263 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
264 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
267 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
269 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
270 // 2 * control * t - 2 * control * t * t + dest * t * t
271 // 2 * control * t + (dest - 2 * control) * t * t
273 controller.origin = org; // starting point
276 controller.destvec = dest; // end point
277 controller.destvec2 = '0 0 0';
280 float TSPEED_TIME = -1;
281 float TSPEED_LINEAR = 0;
282 float TSPEED_START = 1;
283 float TSPEED_END = 2;
286 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
292 objerror ("No speed is defined!");
295 self.finaldest = tdest;
296 self.think = SUB_CalcMoveDone;
302 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
305 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
308 traveltime = vlen(tdest - self.origin) / tspeed;
315 if (traveltime < 0.1) // useless anim
317 self.velocity = '0 0 0';
318 self.nextthink = self.ltime + 0.1;
322 controller = spawn();
323 controller.classname = "SUB_CalcMove_controller";
324 controller.owner = self;
325 controller.platmovetype = self.platmovetype;
326 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
327 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
328 controller.animstate_starttime = time;
329 controller.animstate_endtime = time + traveltime;
330 controller.think = SUB_CalcMove_controller_think;
331 controller.think1 = self.think;
333 // the thinking is now done by the controller
334 self.think = SUB_Null;
335 self.nextthink = self.ltime + traveltime;
343 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
349 objerror ("No speed is defined!");
352 self.finaldest = tdest;
353 self.think = SUB_CalcMoveDone;
355 if (tdest == self.origin)
357 self.velocity = '0 0 0';
358 self.nextthink = self.ltime + 0.1;
362 delta = tdest - self.origin;
370 traveltime = vlen (delta) / tspeed;
377 // Very short animations don't really show off the effect
378 // of controlled animation, so let's just use linear movement.
379 // Alternatively entities can choose to specify non-controlled movement.
380 // The only currently implemented alternative movement is linear (value 1)
381 if (traveltime < 0.15 || self.platmovetype < 2)
383 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
384 self.nextthink = self.ltime + traveltime;
388 // now just run like a bezier curve...
389 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
392 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
399 SUB_CalcMove (tdest, tspeedtype, tspeed, func);
408 calculate self.avelocity and self.nextthink to reach destangle from
411 The calling function should make sure self.think is valid
414 void SUB_CalcAngleMoveDone (void)
416 // After rotating, set angle to exact final angle
417 self.angles = self.finalangle;
418 self.avelocity = '0 0 0';
424 // FIXME: I fixed this function only for rotation around the main axes
425 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
431 objerror ("No speed is defined!");
433 // take the shortest distance for the angles
434 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
435 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
436 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
437 delta = destangle - self.angles;
445 traveltime = vlen (delta) / tspeed;
453 self.finalangle = destangle;
454 self.think = SUB_CalcAngleMoveDone;
456 if (traveltime < 0.1)
458 self.avelocity = '0 0 0';
459 self.nextthink = self.ltime + 0.1;
463 self.avelocity = delta * (1 / traveltime);
464 self.nextthink = self.ltime + traveltime;
467 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
474 SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
483 unused but required by the engine
497 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
498 Additionally it moves players back into the past before the trace and restores them afterward.
501 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
506 // check whether antilagged traces are enabled
509 if (clienttype(forent) != CLIENTTYPE_REAL)
510 lag = 0; // only antilag for clients
512 // change shooter to SOLID_BBOX so the shot can hit corpses
513 oldsolid = source.dphitcontentsmask;
515 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
519 // take players back into the past
520 FOR_EACH_PLAYER(player)
522 antilag_takeback(player, time - lag);
527 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
529 tracebox (v1, mi, ma, v2, nomonst, forent);
531 // restore players to current positions
534 FOR_EACH_PLAYER(player)
536 antilag_restore(player);
539 // restore shooter solid type
541 source.dphitcontentsmask = oldsolid;
543 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
545 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
547 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
549 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
551 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
553 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
555 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
557 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
559 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
561 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
563 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
565 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
567 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
569 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
571 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
573 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
576 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity) // returns the number of traces done, for benchmarking
582 //nudge = 2 * cvar("collision_impactnudge"); // why not?
585 dir = normalize(v2 - v1);
587 pos = v1 + dir * nudge;
594 if((pos - v1) * dir >= (v2 - v1) * dir)
602 tracebox(pos, mi, ma, v2, nomonsters, forent);
607 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
608 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
609 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
610 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
613 stopentity = trace_ent;
617 // we started inside solid.
618 // then trace from endpos to pos
620 tracebox(t, mi, ma, pos, nomonsters, forent);
624 // t is still inside solid? bad
625 // force advance, then, and retry
626 pos = t + dir * nudge;
628 // but if we hit an entity, stop RIGHT before it
629 if(stopatentity && stopentity)
631 trace_ent = stopentity;
633 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
639 // we actually LEFT solid!
640 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
646 // pos is outside solid?!? but why?!? never mind, just return it.
648 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
654 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity)
656 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity);
663 Returns a point at least 12 units away from walls
664 (useful for explosion animations, although the blast is performed where it really happened)
668 vector findbetterlocation (vector org, float mindist)
674 vec = mindist * '1 0 0';
678 traceline (org, org + vec, TRUE, world);
680 if (trace_fraction < 1)
683 traceline (loc, loc + vec, TRUE, world);
684 if (trace_fraction >= 1)
704 Returns a random number between -1.0 and 1.0
709 return 2 * (random () - 0.5);
714 Angc used for animations
719 float angc (float a1, float a2)
746 .float lodmodelindex0;
747 .float lodmodelindex1;
748 .float lodmodelindex2;
752 float LOD_customize()
756 if(autocvar_loddebug)
758 d = autocvar_loddebug;
760 self.modelindex = self.lodmodelindex0;
761 else if(d == 2 || !self.lodmodelindex2)
762 self.modelindex = self.lodmodelindex1;
764 self.modelindex = self.lodmodelindex2;
768 // TODO csqc network this so it only gets sent once
769 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
770 if(d < self.loddistance1)
771 self.modelindex = self.lodmodelindex0;
772 else if(!self.lodmodelindex2 || d < self.loddistance2)
773 self.modelindex = self.lodmodelindex1;
775 self.modelindex = self.lodmodelindex2;
780 void LOD_uncustomize()
782 self.modelindex = self.lodmodelindex0;
785 void LODmodel_attach()
789 if(!self.loddistance1)
790 self.loddistance1 = 1000;
791 if(!self.loddistance2)
792 self.loddistance2 = 2000;
793 self.lodmodelindex0 = self.modelindex;
795 if(self.lodtarget1 != "")
797 e = find(world, targetname, self.lodtarget1);
800 self.lodmodel1 = e.model;
804 if(self.lodtarget2 != "")
806 e = find(world, targetname, self.lodtarget2);
809 self.lodmodel2 = e.model;
814 if(autocvar_loddebug < 0)
816 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
819 if(self.lodmodel1 != "")
825 precache_model(self.lodmodel1);
826 setmodel(self, self.lodmodel1);
827 self.lodmodelindex1 = self.modelindex;
829 if(self.lodmodel2 != "")
831 precache_model(self.lodmodel2);
832 setmodel(self, self.lodmodel2);
833 self.lodmodelindex2 = self.modelindex;
836 self.modelindex = self.lodmodelindex0;
837 setsize(self, mi, ma);
840 if(self.lodmodelindex1)
841 if not(self.SendEntity)
842 SetCustomizer(self, LOD_customize, LOD_uncustomize);
845 void ApplyMinMaxScaleAngles(entity e)
847 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
849 e.maxs = '1 1 1' * vlen(
850 '1 0 0' * max(-e.mins_x, e.maxs_x) +
851 '0 1 0' * max(-e.mins_y, e.maxs_y) +
852 '0 0 1' * max(-e.mins_z, e.maxs_z)
856 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
859 '1 0 0' * max(-e.mins_x, e.maxs_x) +
860 '0 1 0' * max(-e.mins_y, e.maxs_y)
863 e.mins_x = -e.maxs_x;
864 e.mins_y = -e.maxs_x;
867 setsize(e, e.mins * e.scale, e.maxs * e.scale);
869 setsize(e, e.mins, e.maxs);
872 void SetBrushEntityModel()
876 precache_model(self.model);
877 setmodel(self, self.model); // no precision needed
878 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
880 setorigin(self, self.origin);
881 ApplyMinMaxScaleAngles(self);
884 void SetBrushEntityModelNoLOD()
888 precache_model(self.model);
889 setmodel(self, self.model); // no precision needed
891 setorigin(self, self.origin);
892 ApplyMinMaxScaleAngles(self);
903 if (self.movedir != '0 0 0')
904 self.movedir = normalize(self.movedir);
907 makevectors (self.angles);
908 self.movedir = v_forward;
911 self.angles = '0 0 0';
916 // trigger angles are used for one-way touches. An angle of 0 is assumed
917 // to mean no restrictions, so use a yaw of 360 instead.
919 self.solid = SOLID_TRIGGER;
920 SetBrushEntityModel();
921 self.movetype = MOVETYPE_NONE;
926 void InitSolidBSPTrigger()
928 // trigger angles are used for one-way touches. An angle of 0 is assumed
929 // to mean no restrictions, so use a yaw of 360 instead.
931 self.solid = SOLID_BSP;
932 SetBrushEntityModel();
933 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
934 // self.modelindex = 0;
938 float InitMovingBrushTrigger()
940 // trigger angles are used for one-way touches. An angle of 0 is assumed
941 // to mean no restrictions, so use a yaw of 360 instead.
942 self.solid = SOLID_BSP;
943 SetBrushEntityModel();
944 self.movetype = MOVETYPE_PUSH;
945 if(self.modelindex == 0)
947 objerror("InitMovingBrushTrigger: no brushes found!");