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)
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 self.nextthink = nexttick;
211 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
213 self.owner.think = self.think1;
220 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
222 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
223 // 2 * control * t - 2 * control * t * t + dest * t * t
224 // 2 * control * t + (dest - 2 * control) * t * t
226 controller.origin = org; // starting point
230 controller.destvec = 2 * control; // control point
231 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
234 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
236 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
237 // 2 * control * t - 2 * control * t * t + dest * t * t
238 // 2 * control * t + (dest - 2 * control) * t * t
240 controller.origin = org; // starting point
243 controller.destvec = dest; // end point
244 controller.destvec2 = '0 0 0';
247 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeed, void() func)
253 objerror ("No speed is defined!");
256 self.finaldest = tdest;
257 self.think = SUB_CalcMoveDone;
259 if(tspeed > 0) // positive: start speed
260 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
261 else // negative: end speed
262 traveltime = 2 * vlen(tcontrol - tdest) / -tspeed;
264 if (traveltime < 0.1) // useless anim
266 self.velocity = '0 0 0';
267 self.nextthink = self.ltime + 0.1;
271 controller = spawn();
272 controller.classname = "SUB_CalcMove_controller";
273 controller.owner = self;
274 controller.platmovetype = self.platmovetype;
275 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
276 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
277 controller.animstate_starttime = time;
278 controller.animstate_endtime = time + traveltime;
279 controller.think = SUB_CalcMove_controller_think;
280 controller.think1 = self.think;
282 // the thinking is now done by the controller
283 self.think = SUB_Null;
284 self.nextthink = self.ltime + traveltime;
292 void SUB_CalcMove (vector tdest, float tspeed, void() func)
298 objerror ("No speed is defined!");
301 self.finaldest = tdest;
302 self.think = SUB_CalcMoveDone;
304 if (tdest == self.origin)
306 self.velocity = '0 0 0';
307 self.nextthink = self.ltime + 0.1;
311 delta = tdest - self.origin;
312 traveltime = vlen (delta) / tspeed;
314 // Very short animations don't really show off the effect
315 // of controlled animation, so let's just use linear movement.
316 // Alternatively entities can choose to specify non-controlled movement.
317 // The only currently implemented alternative movement is linear (value 1)
318 if (traveltime < 0.15 || self.platmovetype == 1)
320 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
321 self.nextthink = self.ltime + traveltime;
325 // now just run like a bezier curve...
326 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeed, func);
329 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
336 SUB_CalcMove (tdest, tspeed, func);
345 calculate self.avelocity and self.nextthink to reach destangle from
348 The calling function should make sure self.think is valid
351 void SUB_CalcAngleMoveDone (void)
353 // After rotating, set angle to exact final angle
354 self.angles = self.finalangle;
355 self.avelocity = '0 0 0';
361 // FIXME: I fixed this function only for rotation around the main axes
362 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
368 objerror ("No speed is defined!");
370 // take the shortest distance for the angles
371 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
372 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
373 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
374 delta = destangle - self.angles;
375 traveltime = vlen (delta) / tspeed;
378 self.finalangle = destangle;
379 self.think = SUB_CalcAngleMoveDone;
381 if (traveltime < 0.1)
383 self.avelocity = '0 0 0';
384 self.nextthink = self.ltime + 0.1;
388 self.avelocity = delta * (1 / traveltime);
389 self.nextthink = self.ltime + traveltime;
392 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
399 SUB_CalcAngleMove (destangle, tspeed, func);
408 unused but required by the engine
422 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
423 Additionally it moves players back into the past before the trace and restores them afterward.
426 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
431 // check whether antilagged traces are enabled
434 if (clienttype(forent) != CLIENTTYPE_REAL)
435 lag = 0; // only antilag for clients
437 // change shooter to SOLID_BBOX so the shot can hit corpses
438 oldsolid = source.dphitcontentsmask;
440 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
444 // take players back into the past
445 FOR_EACH_PLAYER(player)
447 antilag_takeback(player, time - lag);
452 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
454 tracebox (v1, mi, ma, v2, nomonst, forent);
456 // restore players to current positions
459 FOR_EACH_PLAYER(player)
461 antilag_restore(player);
464 // restore shooter solid type
466 source.dphitcontentsmask = oldsolid;
468 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
470 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
472 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
474 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
476 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
478 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
480 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
482 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
484 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
486 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
488 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
490 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
492 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
494 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
496 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
498 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
501 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
506 //nudge = 2 * cvar("collision_impactnudge"); // why not?
509 dir = normalize(v2 - v1);
511 pos = v1 + dir * nudge;
518 if((pos - v1) * dir >= (v2 - v1) * dir)
526 tracebox(pos, mi, ma, v2, nomonsters, forent);
531 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
532 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
533 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
534 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
539 // we started inside solid.
540 // then trace from endpos to pos
542 tracebox(t, mi, ma, pos, nomonsters, forent);
546 // t is still inside solid? bad
547 // force advance, then, and retry
548 pos = t + dir * nudge;
552 // we actually LEFT solid!
553 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
559 // pos is outside solid?!? but why?!? never mind, just return it.
561 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
567 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
573 //nudge = 2 * cvar("collision_impactnudge"); // why not?
576 dir = normalize(v2 - v1);
578 pos = v1 + dir * nudge;
582 if((pos - v1) * dir >= (v2 - v1) * dir)
589 traceline(pos, v2, nomonsters, forent);
593 // we started inside solid.
594 // then trace from endpos to pos
596 traceline(t, pos, nomonsters, forent);
599 // t is inside solid? bad
600 // force advance, then
601 pos = pos + dir * nudge;
605 // we actually LEFT solid!
606 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
612 // pos is outside solid?!? but why?!? never mind, just return it.
614 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
619 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
626 Returns a point at least 12 units away from walls
627 (useful for explosion animations, although the blast is performed where it really happened)
631 vector findbetterlocation (vector org, float mindist)
637 vec = mindist * '1 0 0';
641 traceline (org, org + vec, TRUE, world);
643 if (trace_fraction < 1)
646 traceline (loc, loc + vec, TRUE, world);
647 if (trace_fraction >= 1)
667 Returns a random number between -1.0 and 1.0
672 return 2 * (random () - 0.5);
677 Angc used for animations
682 float angc (float a1, float a2)
709 .float lodmodelindex0;
710 .float lodmodelindex1;
711 .float lodmodelindex2;
715 float LOD_customize()
719 if(autocvar_loddebug)
721 d = autocvar_loddebug;
723 self.modelindex = self.lodmodelindex0;
724 else if(d == 2 || !self.lodmodelindex2)
725 self.modelindex = self.lodmodelindex1;
727 self.modelindex = self.lodmodelindex2;
731 // TODO csqc network this so it only gets sent once
732 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
733 if(d < self.loddistance1)
734 self.modelindex = self.lodmodelindex0;
735 else if(!self.lodmodelindex2 || d < self.loddistance2)
736 self.modelindex = self.lodmodelindex1;
738 self.modelindex = self.lodmodelindex2;
743 void LOD_uncustomize()
745 self.modelindex = self.lodmodelindex0;
748 void LODmodel_attach()
752 if(!self.loddistance1)
753 self.loddistance1 = 1000;
754 if(!self.loddistance2)
755 self.loddistance2 = 2000;
756 self.lodmodelindex0 = self.modelindex;
758 if(self.lodtarget1 != "")
760 e = find(world, targetname, self.lodtarget1);
763 self.lodmodel1 = e.model;
767 if(self.lodtarget2 != "")
769 e = find(world, targetname, self.lodtarget2);
772 self.lodmodel2 = e.model;
777 if(autocvar_loddebug < 0)
779 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
782 if(self.lodmodel1 != "")
788 precache_model(self.lodmodel1);
789 setmodel(self, self.lodmodel1);
790 self.lodmodelindex1 = self.modelindex;
792 if(self.lodmodel2 != "")
794 precache_model(self.lodmodel2);
795 setmodel(self, self.lodmodel2);
796 self.lodmodelindex2 = self.modelindex;
799 self.modelindex = self.lodmodelindex0;
800 setsize(self, mi, ma);
803 if(self.lodmodelindex1)
804 if not(self.SendEntity)
805 SetCustomizer(self, LOD_customize, LOD_uncustomize);
808 void ApplyMinMaxScaleAngles(entity e)
810 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
812 e.maxs = '1 1 1' * vlen(
813 '1 0 0' * max(-e.mins_x, e.maxs_x) +
814 '0 1 0' * max(-e.mins_y, e.maxs_y) +
815 '0 0 1' * max(-e.mins_z, e.maxs_z)
819 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
822 '1 0 0' * max(-e.mins_x, e.maxs_x) +
823 '0 1 0' * max(-e.mins_y, e.maxs_y)
826 e.mins_x = -e.maxs_x;
827 e.mins_y = -e.maxs_x;
830 setsize(e, e.mins * e.scale, e.maxs * e.scale);
832 setsize(e, e.mins, e.maxs);
835 void SetBrushEntityModel()
839 precache_model(self.model);
840 setmodel(self, self.model); // no precision needed
841 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
843 setorigin(self, self.origin);
844 ApplyMinMaxScaleAngles(self);
847 void SetBrushEntityModelNoLOD()
851 precache_model(self.model);
852 setmodel(self, self.model); // no precision needed
854 setorigin(self, self.origin);
855 ApplyMinMaxScaleAngles(self);
866 if (self.movedir != '0 0 0')
867 self.movedir = normalize(self.movedir);
870 makevectors (self.angles);
871 self.movedir = v_forward;
874 self.angles = '0 0 0';
879 // trigger angles are used for one-way touches. An angle of 0 is assumed
880 // to mean no restrictions, so use a yaw of 360 instead.
882 self.solid = SOLID_TRIGGER;
883 SetBrushEntityModel();
884 self.movetype = MOVETYPE_NONE;
889 void InitSolidBSPTrigger()
891 // trigger angles are used for one-way touches. An angle of 0 is assumed
892 // to mean no restrictions, so use a yaw of 360 instead.
894 self.solid = SOLID_BSP;
895 SetBrushEntityModel();
896 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
897 // self.modelindex = 0;
901 float InitMovingBrushTrigger()
903 // trigger angles are used for one-way touches. An angle of 0 is assumed
904 // to mean no restrictions, so use a yaw of 360 instead.
905 self.solid = SOLID_BSP;
906 SetBrushEntityModel();
907 self.movetype = MOVETYPE_PUSH;
908 if(self.modelindex == 0)
910 objerror("InitMovingBrushTrigger: no brushes found!");