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.owner.angles = vectoangles(delta + 2 * delta2 * phasepos);
210 self.nextthink = nexttick;
212 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
214 self.owner.think = self.think1;
221 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
223 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
224 // 2 * control * t - 2 * control * t * t + dest * t * t
225 // 2 * control * t + (dest - 2 * control) * t * t
227 controller.origin = org; // starting point
231 controller.destvec = 2 * control; // control point
232 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
233 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
236 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
238 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
239 // 2 * control * t - 2 * control * t * t + dest * t * t
240 // 2 * control * t + (dest - 2 * control) * t * t
242 controller.origin = org; // starting point
245 controller.destvec = dest; // end point
246 controller.destvec2 = '0 0 0';
249 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeed, void() func)
255 objerror ("No speed is defined!");
258 self.finaldest = tdest;
259 self.think = SUB_CalcMoveDone;
261 if(tspeed > 0) // positive: start speed
262 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
263 else // negative: end speed
264 traveltime = 2 * vlen(tcontrol - tdest) / -tspeed;
266 if (traveltime < 0.1) // useless anim
268 self.velocity = '0 0 0';
269 self.nextthink = self.ltime + 0.1;
273 controller = spawn();
274 controller.classname = "SUB_CalcMove_controller";
275 controller.owner = self;
276 controller.platmovetype = self.platmovetype;
277 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
278 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
279 controller.animstate_starttime = time;
280 controller.animstate_endtime = time + traveltime;
281 controller.think = SUB_CalcMove_controller_think;
282 controller.think1 = self.think;
284 // the thinking is now done by the controller
285 self.think = SUB_Null;
286 self.nextthink = self.ltime + traveltime;
294 void SUB_CalcMove (vector tdest, float tspeed, void() func)
300 objerror ("No speed is defined!");
303 self.finaldest = tdest;
304 self.think = SUB_CalcMoveDone;
306 if (tdest == self.origin)
308 self.velocity = '0 0 0';
309 self.nextthink = self.ltime + 0.1;
313 delta = tdest - self.origin;
314 traveltime = vlen (delta) / tspeed;
316 // Very short animations don't really show off the effect
317 // of controlled animation, so let's just use linear movement.
318 // Alternatively entities can choose to specify non-controlled movement.
319 // The only currently implemented alternative movement is linear (value 1)
320 if (traveltime < 0.15 || self.platmovetype == 1)
322 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
323 self.nextthink = self.ltime + traveltime;
327 // now just run like a bezier curve...
328 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeed, func);
331 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
338 SUB_CalcMove (tdest, tspeed, func);
347 calculate self.avelocity and self.nextthink to reach destangle from
350 The calling function should make sure self.think is valid
353 void SUB_CalcAngleMoveDone (void)
355 // After rotating, set angle to exact final angle
356 self.angles = self.finalangle;
357 self.avelocity = '0 0 0';
363 // FIXME: I fixed this function only for rotation around the main axes
364 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
370 objerror ("No speed is defined!");
372 // take the shortest distance for the angles
373 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
374 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
375 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
376 delta = destangle - self.angles;
377 traveltime = vlen (delta) / tspeed;
380 self.finalangle = destangle;
381 self.think = SUB_CalcAngleMoveDone;
383 if (traveltime < 0.1)
385 self.avelocity = '0 0 0';
386 self.nextthink = self.ltime + 0.1;
390 self.avelocity = delta * (1 / traveltime);
391 self.nextthink = self.ltime + traveltime;
394 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
401 SUB_CalcAngleMove (destangle, tspeed, func);
410 unused but required by the engine
424 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
425 Additionally it moves players back into the past before the trace and restores them afterward.
428 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
433 // check whether antilagged traces are enabled
436 if (clienttype(forent) != CLIENTTYPE_REAL)
437 lag = 0; // only antilag for clients
439 // change shooter to SOLID_BBOX so the shot can hit corpses
440 oldsolid = source.dphitcontentsmask;
442 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
446 // take players back into the past
447 FOR_EACH_PLAYER(player)
449 antilag_takeback(player, time - lag);
454 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
456 tracebox (v1, mi, ma, v2, nomonst, forent);
458 // restore players to current positions
461 FOR_EACH_PLAYER(player)
463 antilag_restore(player);
466 // restore shooter solid type
468 source.dphitcontentsmask = oldsolid;
470 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
472 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
474 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
476 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
478 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
480 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
482 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
484 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
486 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
488 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
490 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
492 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
494 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
496 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
498 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
500 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
503 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
508 //nudge = 2 * cvar("collision_impactnudge"); // why not?
511 dir = normalize(v2 - v1);
513 pos = v1 + dir * nudge;
520 if((pos - v1) * dir >= (v2 - v1) * dir)
528 tracebox(pos, mi, ma, v2, nomonsters, forent);
533 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
534 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
535 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
536 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
541 // we started inside solid.
542 // then trace from endpos to pos
544 tracebox(t, mi, ma, pos, nomonsters, forent);
548 // t is still inside solid? bad
549 // force advance, then, and retry
550 pos = t + dir * nudge;
554 // we actually LEFT solid!
555 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
561 // pos is outside solid?!? but why?!? never mind, just return it.
563 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
569 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
575 //nudge = 2 * cvar("collision_impactnudge"); // why not?
578 dir = normalize(v2 - v1);
580 pos = v1 + dir * nudge;
584 if((pos - v1) * dir >= (v2 - v1) * dir)
591 traceline(pos, v2, nomonsters, forent);
595 // we started inside solid.
596 // then trace from endpos to pos
598 traceline(t, pos, nomonsters, forent);
601 // t is inside solid? bad
602 // force advance, then
603 pos = pos + dir * nudge;
607 // we actually LEFT solid!
608 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
614 // pos is outside solid?!? but why?!? never mind, just return it.
616 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
621 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
628 Returns a point at least 12 units away from walls
629 (useful for explosion animations, although the blast is performed where it really happened)
633 vector findbetterlocation (vector org, float mindist)
639 vec = mindist * '1 0 0';
643 traceline (org, org + vec, TRUE, world);
645 if (trace_fraction < 1)
648 traceline (loc, loc + vec, TRUE, world);
649 if (trace_fraction >= 1)
669 Returns a random number between -1.0 and 1.0
674 return 2 * (random () - 0.5);
679 Angc used for animations
684 float angc (float a1, float a2)
711 .float lodmodelindex0;
712 .float lodmodelindex1;
713 .float lodmodelindex2;
717 float LOD_customize()
721 if(autocvar_loddebug)
723 d = autocvar_loddebug;
725 self.modelindex = self.lodmodelindex0;
726 else if(d == 2 || !self.lodmodelindex2)
727 self.modelindex = self.lodmodelindex1;
729 self.modelindex = self.lodmodelindex2;
733 // TODO csqc network this so it only gets sent once
734 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
735 if(d < self.loddistance1)
736 self.modelindex = self.lodmodelindex0;
737 else if(!self.lodmodelindex2 || d < self.loddistance2)
738 self.modelindex = self.lodmodelindex1;
740 self.modelindex = self.lodmodelindex2;
745 void LOD_uncustomize()
747 self.modelindex = self.lodmodelindex0;
750 void LODmodel_attach()
754 if(!self.loddistance1)
755 self.loddistance1 = 1000;
756 if(!self.loddistance2)
757 self.loddistance2 = 2000;
758 self.lodmodelindex0 = self.modelindex;
760 if(self.lodtarget1 != "")
762 e = find(world, targetname, self.lodtarget1);
765 self.lodmodel1 = e.model;
769 if(self.lodtarget2 != "")
771 e = find(world, targetname, self.lodtarget2);
774 self.lodmodel2 = e.model;
779 if(autocvar_loddebug < 0)
781 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
784 if(self.lodmodel1 != "")
790 precache_model(self.lodmodel1);
791 setmodel(self, self.lodmodel1);
792 self.lodmodelindex1 = self.modelindex;
794 if(self.lodmodel2 != "")
796 precache_model(self.lodmodel2);
797 setmodel(self, self.lodmodel2);
798 self.lodmodelindex2 = self.modelindex;
801 self.modelindex = self.lodmodelindex0;
802 setsize(self, mi, ma);
805 if(self.lodmodelindex1)
806 if not(self.SendEntity)
807 SetCustomizer(self, LOD_customize, LOD_uncustomize);
810 void ApplyMinMaxScaleAngles(entity e)
812 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
814 e.maxs = '1 1 1' * vlen(
815 '1 0 0' * max(-e.mins_x, e.maxs_x) +
816 '0 1 0' * max(-e.mins_y, e.maxs_y) +
817 '0 0 1' * max(-e.mins_z, e.maxs_z)
821 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
824 '1 0 0' * max(-e.mins_x, e.maxs_x) +
825 '0 1 0' * max(-e.mins_y, e.maxs_y)
828 e.mins_x = -e.maxs_x;
829 e.mins_y = -e.maxs_x;
832 setsize(e, e.mins * e.scale, e.maxs * e.scale);
834 setsize(e, e.mins, e.maxs);
837 void SetBrushEntityModel()
841 precache_model(self.model);
842 setmodel(self, self.model); // no precision needed
843 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
845 setorigin(self, self.origin);
846 ApplyMinMaxScaleAngles(self);
849 void SetBrushEntityModelNoLOD()
853 precache_model(self.model);
854 setmodel(self, self.model); // no precision needed
856 setorigin(self, self.origin);
857 ApplyMinMaxScaleAngles(self);
868 if (self.movedir != '0 0 0')
869 self.movedir = normalize(self.movedir);
872 makevectors (self.angles);
873 self.movedir = v_forward;
876 self.angles = '0 0 0';
881 // trigger angles are used for one-way touches. An angle of 0 is assumed
882 // to mean no restrictions, so use a yaw of 360 instead.
884 self.solid = SOLID_TRIGGER;
885 SetBrushEntityModel();
886 self.movetype = MOVETYPE_NONE;
891 void InitSolidBSPTrigger()
893 // trigger angles are used for one-way touches. An angle of 0 is assumed
894 // to mean no restrictions, so use a yaw of 360 instead.
896 self.solid = SOLID_BSP;
897 SetBrushEntityModel();
898 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
899 // self.modelindex = 0;
903 float InitMovingBrushTrigger()
905 // trigger angles are used for one-way touches. An angle of 0 is assumed
906 // to mean no restrictions, so use a yaw of 360 instead.
907 self.solid = SOLID_BSP;
908 SetBrushEntityModel();
909 self.movetype = MOVETYPE_PUSH;
910 if(self.modelindex == 0)
912 objerror("InitMovingBrushTrigger: no brushes found!");