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)
198 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
199 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
200 phasepos = phasepos + 1; // correct range to [0, 2]
201 phasepos = phasepos / 2; // correct range to [0, 1]
204 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
205 phasepos = sin(phasepos); // sin [pi, 2pi] is in [-1, 1]
206 phasepos = phasepos + 1; // correct range to [0, 2]
207 phasepos = phasepos / 2; // correct range to [0, 1]
210 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
211 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
213 if(nexttick < self.animstate_endtime) {
214 veloc = nextpos - self.owner.origin;
215 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
217 veloc = self.finaldest - self.owner.origin;
218 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
220 self.owner.velocity = veloc;
221 if(self.owner.bezier_turn)
224 vel = delta + 2 * delta2 * phasepos;
225 vel_z = -vel_z; // invert z velocity
226 vel = vectoangles(vel);
227 self.owner.angles = vel;
229 self.nextthink = nexttick;
231 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
233 self.owner.think = self.think1;
240 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
242 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
243 // 2 * control * t - 2 * control * t * t + dest * t * t
244 // 2 * control * t + (dest - 2 * control) * t * t
246 controller.origin = org; // starting point
250 controller.destvec = 2 * control; // control point
251 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
252 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
255 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
257 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
258 // 2 * control * t - 2 * control * t * t + dest * t * t
259 // 2 * control * t + (dest - 2 * control) * t * t
261 controller.origin = org; // starting point
264 controller.destvec = dest; // end point
265 controller.destvec2 = '0 0 0';
268 float TSPEED_TIME = -1;
269 float TSPEED_LINEAR = 0;
270 float TSPEED_START = 1;
271 float TSPEED_END = 2;
274 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
280 objerror ("No speed is defined!");
283 self.finaldest = tdest;
284 self.think = SUB_CalcMoveDone;
290 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
293 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
296 traveltime = vlen(tdest - self.origin) / tspeed;
303 if (traveltime < 0.1) // useless anim
305 self.velocity = '0 0 0';
306 self.nextthink = self.ltime + 0.1;
310 controller = spawn();
311 controller.classname = "SUB_CalcMove_controller";
312 controller.owner = self;
313 controller.platmovetype = self.platmovetype;
314 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
315 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
316 controller.animstate_starttime = time;
317 controller.animstate_endtime = time + traveltime;
318 controller.think = SUB_CalcMove_controller_think;
319 controller.think1 = self.think;
321 // the thinking is now done by the controller
322 self.think = SUB_Null;
323 self.nextthink = self.ltime + traveltime;
331 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
337 objerror ("No speed is defined!");
340 self.finaldest = tdest;
341 self.think = SUB_CalcMoveDone;
343 if (tdest == self.origin)
345 self.velocity = '0 0 0';
346 self.nextthink = self.ltime + 0.1;
350 delta = tdest - self.origin;
358 traveltime = vlen (delta) / tspeed;
365 // Very short animations don't really show off the effect
366 // of controlled animation, so let's just use linear movement.
367 // Alternatively entities can choose to specify non-controlled movement.
368 // The only currently implemented alternative movement is linear (value 1)
369 if (traveltime < 0.15 || self.platmovetype < 2)
371 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
372 self.nextthink = self.ltime + traveltime;
376 // now just run like a bezier curve...
377 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
380 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
387 SUB_CalcMove (tdest, tspeedtype, tspeed, func);
396 calculate self.avelocity and self.nextthink to reach destangle from
399 The calling function should make sure self.think is valid
402 void SUB_CalcAngleMoveDone (void)
404 // After rotating, set angle to exact final angle
405 self.angles = self.finalangle;
406 self.avelocity = '0 0 0';
412 // FIXME: I fixed this function only for rotation around the main axes
413 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
419 objerror ("No speed is defined!");
421 // take the shortest distance for the angles
422 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
423 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
424 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
425 delta = destangle - self.angles;
433 traveltime = vlen (delta) / tspeed;
441 self.finalangle = destangle;
442 self.think = SUB_CalcAngleMoveDone;
444 if (traveltime < 0.1)
446 self.avelocity = '0 0 0';
447 self.nextthink = self.ltime + 0.1;
451 self.avelocity = delta * (1 / traveltime);
452 self.nextthink = self.ltime + traveltime;
455 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
462 SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
471 unused but required by the engine
485 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
486 Additionally it moves players back into the past before the trace and restores them afterward.
489 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
494 // check whether antilagged traces are enabled
497 if (clienttype(forent) != CLIENTTYPE_REAL)
498 lag = 0; // only antilag for clients
500 // change shooter to SOLID_BBOX so the shot can hit corpses
501 oldsolid = source.dphitcontentsmask;
503 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
507 // take players back into the past
508 FOR_EACH_PLAYER(player)
510 antilag_takeback(player, time - lag);
515 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
517 tracebox (v1, mi, ma, v2, nomonst, forent);
519 // restore players to current positions
522 FOR_EACH_PLAYER(player)
524 antilag_restore(player);
527 // restore shooter solid type
529 source.dphitcontentsmask = oldsolid;
531 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
533 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
535 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
537 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
539 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
541 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
543 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
545 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
547 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
549 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
551 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
553 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
555 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
557 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
559 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
561 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
564 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
569 //nudge = 2 * cvar("collision_impactnudge"); // why not?
572 dir = normalize(v2 - v1);
574 pos = v1 + dir * nudge;
581 if((pos - v1) * dir >= (v2 - v1) * dir)
589 tracebox(pos, mi, ma, v2, nomonsters, forent);
594 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
595 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
596 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
597 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
602 // we started inside solid.
603 // then trace from endpos to pos
605 tracebox(t, mi, ma, pos, nomonsters, forent);
609 // t is still inside solid? bad
610 // force advance, then, and retry
611 pos = t + dir * nudge;
615 // we actually LEFT solid!
616 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
622 // pos is outside solid?!? but why?!? never mind, just return it.
624 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
630 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
636 //nudge = 2 * cvar("collision_impactnudge"); // why not?
639 dir = normalize(v2 - v1);
641 pos = v1 + dir * nudge;
645 if((pos - v1) * dir >= (v2 - v1) * dir)
652 traceline(pos, v2, nomonsters, forent);
656 // we started inside solid.
657 // then trace from endpos to pos
659 traceline(t, pos, nomonsters, forent);
662 // t is inside solid? bad
663 // force advance, then
664 pos = pos + dir * nudge;
668 // we actually LEFT solid!
669 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
675 // pos is outside solid?!? but why?!? never mind, just return it.
677 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
682 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
689 Returns a point at least 12 units away from walls
690 (useful for explosion animations, although the blast is performed where it really happened)
694 vector findbetterlocation (vector org, float mindist)
700 vec = mindist * '1 0 0';
704 traceline (org, org + vec, TRUE, world);
706 if (trace_fraction < 1)
709 traceline (loc, loc + vec, TRUE, world);
710 if (trace_fraction >= 1)
730 Returns a random number between -1.0 and 1.0
735 return 2 * (random () - 0.5);
740 Angc used for animations
745 float angc (float a1, float a2)
772 .float lodmodelindex0;
773 .float lodmodelindex1;
774 .float lodmodelindex2;
778 float LOD_customize()
782 if(autocvar_loddebug)
784 d = autocvar_loddebug;
786 self.modelindex = self.lodmodelindex0;
787 else if(d == 2 || !self.lodmodelindex2)
788 self.modelindex = self.lodmodelindex1;
790 self.modelindex = self.lodmodelindex2;
794 // TODO csqc network this so it only gets sent once
795 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
796 if(d < self.loddistance1)
797 self.modelindex = self.lodmodelindex0;
798 else if(!self.lodmodelindex2 || d < self.loddistance2)
799 self.modelindex = self.lodmodelindex1;
801 self.modelindex = self.lodmodelindex2;
806 void LOD_uncustomize()
808 self.modelindex = self.lodmodelindex0;
811 void LODmodel_attach()
815 if(!self.loddistance1)
816 self.loddistance1 = 1000;
817 if(!self.loddistance2)
818 self.loddistance2 = 2000;
819 self.lodmodelindex0 = self.modelindex;
821 if(self.lodtarget1 != "")
823 e = find(world, targetname, self.lodtarget1);
826 self.lodmodel1 = e.model;
830 if(self.lodtarget2 != "")
832 e = find(world, targetname, self.lodtarget2);
835 self.lodmodel2 = e.model;
840 if(autocvar_loddebug < 0)
842 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
845 if(self.lodmodel1 != "")
851 precache_model(self.lodmodel1);
852 setmodel(self, self.lodmodel1);
853 self.lodmodelindex1 = self.modelindex;
855 if(self.lodmodel2 != "")
857 precache_model(self.lodmodel2);
858 setmodel(self, self.lodmodel2);
859 self.lodmodelindex2 = self.modelindex;
862 self.modelindex = self.lodmodelindex0;
863 setsize(self, mi, ma);
866 if(self.lodmodelindex1)
867 if not(self.SendEntity)
868 SetCustomizer(self, LOD_customize, LOD_uncustomize);
871 void ApplyMinMaxScaleAngles(entity e)
873 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
875 e.maxs = '1 1 1' * vlen(
876 '1 0 0' * max(-e.mins_x, e.maxs_x) +
877 '0 1 0' * max(-e.mins_y, e.maxs_y) +
878 '0 0 1' * max(-e.mins_z, e.maxs_z)
882 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
885 '1 0 0' * max(-e.mins_x, e.maxs_x) +
886 '0 1 0' * max(-e.mins_y, e.maxs_y)
889 e.mins_x = -e.maxs_x;
890 e.mins_y = -e.maxs_x;
893 setsize(e, e.mins * e.scale, e.maxs * e.scale);
895 setsize(e, e.mins, e.maxs);
898 void SetBrushEntityModel()
902 precache_model(self.model);
903 setmodel(self, self.model); // no precision needed
904 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
906 setorigin(self, self.origin);
907 ApplyMinMaxScaleAngles(self);
910 void SetBrushEntityModelNoLOD()
914 precache_model(self.model);
915 setmodel(self, self.model); // no precision needed
917 setorigin(self, self.origin);
918 ApplyMinMaxScaleAngles(self);
929 if (self.movedir != '0 0 0')
930 self.movedir = normalize(self.movedir);
933 makevectors (self.angles);
934 self.movedir = v_forward;
937 self.angles = '0 0 0';
942 // trigger angles are used for one-way touches. An angle of 0 is assumed
943 // to mean no restrictions, so use a yaw of 360 instead.
945 self.solid = SOLID_TRIGGER;
946 SetBrushEntityModel();
947 self.movetype = MOVETYPE_NONE;
952 void InitSolidBSPTrigger()
954 // trigger angles are used for one-way touches. An angle of 0 is assumed
955 // to mean no restrictions, so use a yaw of 360 instead.
957 self.solid = SOLID_BSP;
958 SetBrushEntityModel();
959 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
960 // self.modelindex = 0;
964 float InitMovingBrushTrigger()
966 // trigger angles are used for one-way touches. An angle of 0 is assumed
967 // to mean no restrictions, so use a yaw of 360 instead.
968 self.solid = SOLID_BSP;
969 SetBrushEntityModel();
970 self.movetype = MOVETYPE_PUSH;
971 if(self.modelindex == 0)
973 objerror("InitMovingBrushTrigger: no brushes found!");