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 != 1)
193 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
194 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
195 phasepos = phasepos + 1; // correct range to [0, 2]
196 phasepos = phasepos / 2; // correct range to [0, 1]
198 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
199 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
201 if(nexttick < self.animstate_endtime) {
202 veloc = nextpos - self.owner.origin;
203 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
205 veloc = self.finaldest - self.owner.origin;
206 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
208 self.owner.velocity = veloc;
209 if(self.owner.bezier_turn)
212 vel = delta + 2 * delta2 * phasepos;
213 vel_z = -vel_z; // invert z velocity
214 vel = vectoangles(vel);
215 self.owner.angles = vel;
217 self.nextthink = nexttick;
219 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
221 self.owner.think = self.think1;
228 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
230 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
231 // 2 * control * t - 2 * control * t * t + dest * t * t
232 // 2 * control * t + (dest - 2 * control) * t * t
234 controller.origin = org; // starting point
238 controller.destvec = 2 * control; // control point
239 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
240 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
243 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
245 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
246 // 2 * control * t - 2 * control * t * t + dest * t * t
247 // 2 * control * t + (dest - 2 * control) * t * t
249 controller.origin = org; // starting point
252 controller.destvec = dest; // end point
253 controller.destvec2 = '0 0 0';
256 float TSPEED_TIME = -1;
257 float TSPEED_LINEAR = 0;
258 float TSPEED_START = 1;
259 float TSPEED_END = 2;
262 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
268 objerror ("No speed is defined!");
271 self.finaldest = tdest;
272 self.think = SUB_CalcMoveDone;
278 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
281 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
284 traveltime = vlen(tdest - self.origin) / tspeed;
291 if (traveltime < 0.1) // useless anim
293 self.velocity = '0 0 0';
294 self.nextthink = self.ltime + 0.1;
298 controller = spawn();
299 controller.classname = "SUB_CalcMove_controller";
300 controller.owner = self;
301 controller.platmovetype = self.platmovetype;
302 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
303 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
304 controller.animstate_starttime = time;
305 controller.animstate_endtime = time + traveltime;
306 controller.think = SUB_CalcMove_controller_think;
307 controller.think1 = self.think;
309 // the thinking is now done by the controller
310 self.think = SUB_Null;
311 self.nextthink = self.ltime + traveltime;
319 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
325 objerror ("No speed is defined!");
328 self.finaldest = tdest;
329 self.think = SUB_CalcMoveDone;
331 if (tdest == self.origin)
333 self.velocity = '0 0 0';
334 self.nextthink = self.ltime + 0.1;
338 delta = tdest - self.origin;
346 traveltime = vlen (delta) / tspeed;
353 // Very short animations don't really show off the effect
354 // of controlled animation, so let's just use linear movement.
355 // Alternatively entities can choose to specify non-controlled movement.
356 // The only currently implemented alternative movement is linear (value 1)
357 if (traveltime < 0.15 || self.platmovetype == 1)
359 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
360 self.nextthink = self.ltime + traveltime;
364 // now just run like a bezier curve...
365 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
368 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
375 SUB_CalcMove (tdest, tspeedtype, tspeed, func);
384 calculate self.avelocity and self.nextthink to reach destangle from
387 The calling function should make sure self.think is valid
390 void SUB_CalcAngleMoveDone (void)
392 // After rotating, set angle to exact final angle
393 self.angles = self.finalangle;
394 self.avelocity = '0 0 0';
400 // FIXME: I fixed this function only for rotation around the main axes
401 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
407 objerror ("No speed is defined!");
409 // take the shortest distance for the angles
410 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
411 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
412 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
413 delta = destangle - self.angles;
421 traveltime = vlen (delta) / tspeed;
429 self.finalangle = destangle;
430 self.think = SUB_CalcAngleMoveDone;
432 if (traveltime < 0.1)
434 self.avelocity = '0 0 0';
435 self.nextthink = self.ltime + 0.1;
439 self.avelocity = delta * (1 / traveltime);
440 self.nextthink = self.ltime + traveltime;
443 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
450 SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
459 unused but required by the engine
473 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
474 Additionally it moves players back into the past before the trace and restores them afterward.
477 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
482 // check whether antilagged traces are enabled
485 if (clienttype(forent) != CLIENTTYPE_REAL)
486 lag = 0; // only antilag for clients
488 // change shooter to SOLID_BBOX so the shot can hit corpses
489 oldsolid = source.dphitcontentsmask;
491 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
495 // take players back into the past
496 FOR_EACH_PLAYER(player)
498 antilag_takeback(player, time - lag);
503 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
505 tracebox (v1, mi, ma, v2, nomonst, forent);
507 // restore players to current positions
510 FOR_EACH_PLAYER(player)
512 antilag_restore(player);
515 // restore shooter solid type
517 source.dphitcontentsmask = oldsolid;
519 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
521 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
523 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
525 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
527 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
529 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
531 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
533 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
535 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
537 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
539 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
541 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
543 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
545 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
547 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
549 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
552 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
557 //nudge = 2 * cvar("collision_impactnudge"); // why not?
560 dir = normalize(v2 - v1);
562 pos = v1 + dir * nudge;
569 if((pos - v1) * dir >= (v2 - v1) * dir)
577 tracebox(pos, mi, ma, v2, nomonsters, forent);
582 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
583 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
584 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
585 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
590 // we started inside solid.
591 // then trace from endpos to pos
593 tracebox(t, mi, ma, pos, nomonsters, forent);
597 // t is still inside solid? bad
598 // force advance, then, and retry
599 pos = t + dir * nudge;
603 // we actually LEFT solid!
604 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
610 // pos is outside solid?!? but why?!? never mind, just return it.
612 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
618 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
624 //nudge = 2 * cvar("collision_impactnudge"); // why not?
627 dir = normalize(v2 - v1);
629 pos = v1 + dir * nudge;
633 if((pos - v1) * dir >= (v2 - v1) * dir)
640 traceline(pos, v2, nomonsters, forent);
644 // we started inside solid.
645 // then trace from endpos to pos
647 traceline(t, pos, nomonsters, forent);
650 // t is inside solid? bad
651 // force advance, then
652 pos = pos + dir * nudge;
656 // we actually LEFT solid!
657 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
663 // pos is outside solid?!? but why?!? never mind, just return it.
665 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
670 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
677 Returns a point at least 12 units away from walls
678 (useful for explosion animations, although the blast is performed where it really happened)
682 vector findbetterlocation (vector org, float mindist)
688 vec = mindist * '1 0 0';
692 traceline (org, org + vec, TRUE, world);
694 if (trace_fraction < 1)
697 traceline (loc, loc + vec, TRUE, world);
698 if (trace_fraction >= 1)
718 Returns a random number between -1.0 and 1.0
723 return 2 * (random () - 0.5);
728 Angc used for animations
733 float angc (float a1, float a2)
760 .float lodmodelindex0;
761 .float lodmodelindex1;
762 .float lodmodelindex2;
766 float LOD_customize()
770 if(autocvar_loddebug)
772 d = autocvar_loddebug;
774 self.modelindex = self.lodmodelindex0;
775 else if(d == 2 || !self.lodmodelindex2)
776 self.modelindex = self.lodmodelindex1;
778 self.modelindex = self.lodmodelindex2;
782 // TODO csqc network this so it only gets sent once
783 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
784 if(d < self.loddistance1)
785 self.modelindex = self.lodmodelindex0;
786 else if(!self.lodmodelindex2 || d < self.loddistance2)
787 self.modelindex = self.lodmodelindex1;
789 self.modelindex = self.lodmodelindex2;
794 void LOD_uncustomize()
796 self.modelindex = self.lodmodelindex0;
799 void LODmodel_attach()
803 if(!self.loddistance1)
804 self.loddistance1 = 1000;
805 if(!self.loddistance2)
806 self.loddistance2 = 2000;
807 self.lodmodelindex0 = self.modelindex;
809 if(self.lodtarget1 != "")
811 e = find(world, targetname, self.lodtarget1);
814 self.lodmodel1 = e.model;
818 if(self.lodtarget2 != "")
820 e = find(world, targetname, self.lodtarget2);
823 self.lodmodel2 = e.model;
828 if(autocvar_loddebug < 0)
830 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
833 if(self.lodmodel1 != "")
839 precache_model(self.lodmodel1);
840 setmodel(self, self.lodmodel1);
841 self.lodmodelindex1 = self.modelindex;
843 if(self.lodmodel2 != "")
845 precache_model(self.lodmodel2);
846 setmodel(self, self.lodmodel2);
847 self.lodmodelindex2 = self.modelindex;
850 self.modelindex = self.lodmodelindex0;
851 setsize(self, mi, ma);
854 if(self.lodmodelindex1)
855 if not(self.SendEntity)
856 SetCustomizer(self, LOD_customize, LOD_uncustomize);
859 void ApplyMinMaxScaleAngles(entity e)
861 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
863 e.maxs = '1 1 1' * vlen(
864 '1 0 0' * max(-e.mins_x, e.maxs_x) +
865 '0 1 0' * max(-e.mins_y, e.maxs_y) +
866 '0 0 1' * max(-e.mins_z, e.maxs_z)
870 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
873 '1 0 0' * max(-e.mins_x, e.maxs_x) +
874 '0 1 0' * max(-e.mins_y, e.maxs_y)
877 e.mins_x = -e.maxs_x;
878 e.mins_y = -e.maxs_x;
881 setsize(e, e.mins * e.scale, e.maxs * e.scale);
883 setsize(e, e.mins, e.maxs);
886 void SetBrushEntityModel()
890 precache_model(self.model);
891 setmodel(self, self.model); // no precision needed
892 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
894 setorigin(self, self.origin);
895 ApplyMinMaxScaleAngles(self);
898 void SetBrushEntityModelNoLOD()
902 precache_model(self.model);
903 setmodel(self, self.model); // no precision needed
905 setorigin(self, self.origin);
906 ApplyMinMaxScaleAngles(self);
917 if (self.movedir != '0 0 0')
918 self.movedir = normalize(self.movedir);
921 makevectors (self.angles);
922 self.movedir = v_forward;
925 self.angles = '0 0 0';
930 // trigger angles are used for one-way touches. An angle of 0 is assumed
931 // to mean no restrictions, so use a yaw of 360 instead.
933 self.solid = SOLID_TRIGGER;
934 SetBrushEntityModel();
935 self.movetype = MOVETYPE_NONE;
940 void InitSolidBSPTrigger()
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_BSP;
946 SetBrushEntityModel();
947 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
948 // self.modelindex = 0;
952 float InitMovingBrushTrigger()
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.
956 self.solid = SOLID_BSP;
957 SetBrushEntityModel();
958 self.movetype = MOVETYPE_PUSH;
959 if(self.modelindex == 0)
961 objerror("InitMovingBrushTrigger: no brushes found!");