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';
175 void SUB_CalcMove_controller_think (void)
185 delta = self.destvec;
186 delta2 = self.destvec2;
187 if(time < self.animstate_endtime) {
188 nexttick = time + sys_frametime;
190 traveltime = self.animstate_endtime - self.animstate_starttime;
191 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
192 if(self.platmovetype != 1)
194 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
195 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
196 phasepos = phasepos + 1; // correct range to [0, 2]
197 phasepos = phasepos / 2; // correct range to [0, 1]
199 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
200 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
202 if(nexttick < self.animstate_endtime) {
203 veloc = nextpos - self.owner.origin;
204 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
206 veloc = self.finaldest - self.owner.origin;
207 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
209 self.owner.velocity = veloc;
210 if(self.owner.bezier_turn)
213 vel = delta + 2 * delta2 * phasepos;
214 vel_z = -vel_z; // invert z velocity
215 vel = vectoangles(vel);
216 self.owner.angles = vel;
218 self.nextthink = nexttick;
220 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
222 self.owner.think = self.think1;
229 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
231 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
232 // 2 * control * t - 2 * control * t * t + dest * t * t
233 // 2 * control * t + (dest - 2 * control) * t * t
235 controller.origin = org; // starting point
239 controller.destvec = 2 * control; // control point
240 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
241 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
244 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
246 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
247 // 2 * control * t - 2 * control * t * t + dest * t * t
248 // 2 * control * t + (dest - 2 * control) * t * t
250 controller.origin = org; // starting point
253 controller.destvec = dest; // end point
254 controller.destvec2 = '0 0 0';
257 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeed, void() func)
263 objerror ("No speed is defined!");
266 self.finaldest = tdest;
267 self.think = SUB_CalcMoveDone;
269 if(tspeed > 0) // positive: start speed
270 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
271 else // negative: end speed
272 traveltime = 2 * vlen(tcontrol - tdest) / -tspeed;
274 if (traveltime < 0.1) // useless anim
276 self.velocity = '0 0 0';
277 self.nextthink = self.ltime + 0.1;
281 controller = spawn();
282 controller.classname = "SUB_CalcMove_controller";
283 controller.owner = self;
284 controller.platmovetype = self.platmovetype;
285 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
286 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
287 controller.animstate_starttime = time;
288 controller.animstate_endtime = time + traveltime;
289 controller.think = SUB_CalcMove_controller_think;
290 controller.think1 = self.think;
292 // the thinking is now done by the controller
293 self.think = SUB_Null;
294 self.nextthink = self.ltime + traveltime;
302 void SUB_CalcMove (vector tdest, float tspeed, void() func)
308 objerror ("No speed is defined!");
311 self.finaldest = tdest;
312 self.think = SUB_CalcMoveDone;
314 if (tdest == self.origin)
316 self.velocity = '0 0 0';
317 self.nextthink = self.ltime + 0.1;
321 delta = tdest - self.origin;
322 traveltime = vlen (delta) / tspeed;
324 // Very short animations don't really show off the effect
325 // of controlled animation, so let's just use linear movement.
326 // Alternatively entities can choose to specify non-controlled movement.
327 // The only currently implemented alternative movement is linear (value 1)
328 if (traveltime < 0.15 || self.platmovetype == 1)
330 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
331 self.nextthink = self.ltime + traveltime;
335 // now just run like a bezier curve...
336 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeed, func);
339 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
346 SUB_CalcMove (tdest, tspeed, func);
355 calculate self.avelocity and self.nextthink to reach destangle from
358 The calling function should make sure self.think is valid
361 void SUB_CalcAngleMoveDone (void)
363 // After rotating, set angle to exact final angle
364 self.angles = self.finalangle;
365 self.avelocity = '0 0 0';
371 // FIXME: I fixed this function only for rotation around the main axes
372 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
378 objerror ("No speed is defined!");
380 // take the shortest distance for the angles
381 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
382 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
383 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
384 delta = destangle - self.angles;
385 traveltime = vlen (delta) / tspeed;
388 self.finalangle = destangle;
389 self.think = SUB_CalcAngleMoveDone;
391 if (traveltime < 0.1)
393 self.avelocity = '0 0 0';
394 self.nextthink = self.ltime + 0.1;
398 self.avelocity = delta * (1 / traveltime);
399 self.nextthink = self.ltime + traveltime;
402 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
409 SUB_CalcAngleMove (destangle, tspeed, func);
418 unused but required by the engine
432 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
433 Additionally it moves players back into the past before the trace and restores them afterward.
436 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
441 // check whether antilagged traces are enabled
444 if (clienttype(forent) != CLIENTTYPE_REAL)
445 lag = 0; // only antilag for clients
447 // change shooter to SOLID_BBOX so the shot can hit corpses
448 oldsolid = source.dphitcontentsmask;
450 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
454 // take players back into the past
455 FOR_EACH_PLAYER(player)
457 antilag_takeback(player, time - lag);
462 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
464 tracebox (v1, mi, ma, v2, nomonst, forent);
466 // restore players to current positions
469 FOR_EACH_PLAYER(player)
471 antilag_restore(player);
474 // restore shooter solid type
476 source.dphitcontentsmask = oldsolid;
478 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
480 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
482 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
484 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
486 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
488 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
490 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
492 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
494 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
496 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
498 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
500 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
502 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
504 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
506 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
508 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
511 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
516 //nudge = 2 * cvar("collision_impactnudge"); // why not?
519 dir = normalize(v2 - v1);
521 pos = v1 + dir * nudge;
528 if((pos - v1) * dir >= (v2 - v1) * dir)
536 tracebox(pos, mi, ma, v2, nomonsters, forent);
541 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
542 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
543 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
544 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
549 // we started inside solid.
550 // then trace from endpos to pos
552 tracebox(t, mi, ma, pos, nomonsters, forent);
556 // t is still inside solid? bad
557 // force advance, then, and retry
558 pos = t + dir * nudge;
562 // we actually LEFT solid!
563 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
569 // pos is outside solid?!? but why?!? never mind, just return it.
571 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
577 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
583 //nudge = 2 * cvar("collision_impactnudge"); // why not?
586 dir = normalize(v2 - v1);
588 pos = v1 + dir * nudge;
592 if((pos - v1) * dir >= (v2 - v1) * dir)
599 traceline(pos, v2, nomonsters, forent);
603 // we started inside solid.
604 // then trace from endpos to pos
606 traceline(t, pos, nomonsters, forent);
609 // t is inside solid? bad
610 // force advance, then
611 pos = pos + 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);
629 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
636 Returns a point at least 12 units away from walls
637 (useful for explosion animations, although the blast is performed where it really happened)
641 vector findbetterlocation (vector org, float mindist)
647 vec = mindist * '1 0 0';
651 traceline (org, org + vec, TRUE, world);
653 if (trace_fraction < 1)
656 traceline (loc, loc + vec, TRUE, world);
657 if (trace_fraction >= 1)
677 Returns a random number between -1.0 and 1.0
682 return 2 * (random () - 0.5);
687 Angc used for animations
692 float angc (float a1, float a2)
719 .float lodmodelindex0;
720 .float lodmodelindex1;
721 .float lodmodelindex2;
725 float LOD_customize()
729 if(autocvar_loddebug)
731 d = autocvar_loddebug;
733 self.modelindex = self.lodmodelindex0;
734 else if(d == 2 || !self.lodmodelindex2)
735 self.modelindex = self.lodmodelindex1;
737 self.modelindex = self.lodmodelindex2;
741 // TODO csqc network this so it only gets sent once
742 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
743 if(d < self.loddistance1)
744 self.modelindex = self.lodmodelindex0;
745 else if(!self.lodmodelindex2 || d < self.loddistance2)
746 self.modelindex = self.lodmodelindex1;
748 self.modelindex = self.lodmodelindex2;
753 void LOD_uncustomize()
755 self.modelindex = self.lodmodelindex0;
758 void LODmodel_attach()
762 if(!self.loddistance1)
763 self.loddistance1 = 1000;
764 if(!self.loddistance2)
765 self.loddistance2 = 2000;
766 self.lodmodelindex0 = self.modelindex;
768 if(self.lodtarget1 != "")
770 e = find(world, targetname, self.lodtarget1);
773 self.lodmodel1 = e.model;
777 if(self.lodtarget2 != "")
779 e = find(world, targetname, self.lodtarget2);
782 self.lodmodel2 = e.model;
787 if(autocvar_loddebug < 0)
789 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
792 if(self.lodmodel1 != "")
798 precache_model(self.lodmodel1);
799 setmodel(self, self.lodmodel1);
800 self.lodmodelindex1 = self.modelindex;
802 if(self.lodmodel2 != "")
804 precache_model(self.lodmodel2);
805 setmodel(self, self.lodmodel2);
806 self.lodmodelindex2 = self.modelindex;
809 self.modelindex = self.lodmodelindex0;
810 setsize(self, mi, ma);
813 if(self.lodmodelindex1)
814 if not(self.SendEntity)
815 SetCustomizer(self, LOD_customize, LOD_uncustomize);
818 void ApplyMinMaxScaleAngles(entity e)
820 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
822 e.maxs = '1 1 1' * vlen(
823 '1 0 0' * max(-e.mins_x, e.maxs_x) +
824 '0 1 0' * max(-e.mins_y, e.maxs_y) +
825 '0 0 1' * max(-e.mins_z, e.maxs_z)
829 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
832 '1 0 0' * max(-e.mins_x, e.maxs_x) +
833 '0 1 0' * max(-e.mins_y, e.maxs_y)
836 e.mins_x = -e.maxs_x;
837 e.mins_y = -e.maxs_x;
840 setsize(e, e.mins * e.scale, e.maxs * e.scale);
842 setsize(e, e.mins, e.maxs);
845 void SetBrushEntityModel()
849 precache_model(self.model);
850 setmodel(self, self.model); // no precision needed
851 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
853 setorigin(self, self.origin);
854 ApplyMinMaxScaleAngles(self);
857 void SetBrushEntityModelNoLOD()
861 precache_model(self.model);
862 setmodel(self, self.model); // no precision needed
864 setorigin(self, self.origin);
865 ApplyMinMaxScaleAngles(self);
876 if (self.movedir != '0 0 0')
877 self.movedir = normalize(self.movedir);
880 makevectors (self.angles);
881 self.movedir = v_forward;
884 self.angles = '0 0 0';
889 // trigger angles are used for one-way touches. An angle of 0 is assumed
890 // to mean no restrictions, so use a yaw of 360 instead.
892 self.solid = SOLID_TRIGGER;
893 SetBrushEntityModel();
894 self.movetype = MOVETYPE_NONE;
899 void InitSolidBSPTrigger()
901 // trigger angles are used for one-way touches. An angle of 0 is assumed
902 // to mean no restrictions, so use a yaw of 360 instead.
904 self.solid = SOLID_BSP;
905 SetBrushEntityModel();
906 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
907 // self.modelindex = 0;
911 float InitMovingBrushTrigger()
913 // trigger angles are used for one-way touches. An angle of 0 is assumed
914 // to mean no restrictions, so use a yaw of 360 instead.
915 self.solid = SOLID_BSP;
916 SetBrushEntityModel();
917 self.movetype = MOVETYPE_PUSH;
918 if(self.modelindex == 0)
920 objerror("InitMovingBrushTrigger: no brushes found!");