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, float stopatentity) // returns the number of traces done, for benchmarking
507 //nudge = 2 * cvar("collision_impactnudge"); // why not?
510 dir = normalize(v2 - v1);
512 pos = v1 + dir * nudge;
519 if((pos - v1) * dir >= (v2 - v1) * dir)
527 tracebox(pos, mi, ma, v2, nomonsters, forent);
532 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
533 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
534 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
535 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
538 stopentity = trace_ent;
542 // we started inside solid.
543 // then trace from endpos to pos
545 tracebox(t, mi, ma, pos, nomonsters, forent);
549 // t is still inside solid? bad
550 // force advance, then, and retry
551 pos = t + dir * nudge;
553 // but if we hit an entity, stop RIGHT before it
554 if(stopatentity && stopentity)
556 trace_ent = stopentity;
558 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
564 // we actually LEFT solid!
565 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
571 // pos is outside solid?!? but why?!? never mind, just return it.
573 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
579 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity)
581 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity);
588 Returns a point at least 12 units away from walls
589 (useful for explosion animations, although the blast is performed where it really happened)
593 vector findbetterlocation (vector org, float mindist)
599 vec = mindist * '1 0 0';
603 traceline (org, org + vec, TRUE, world);
605 if (trace_fraction < 1)
608 traceline (loc, loc + vec, TRUE, world);
609 if (trace_fraction >= 1)
629 Returns a random number between -1.0 and 1.0
634 return 2 * (random () - 0.5);
639 Angc used for animations
644 float angc (float a1, float a2)
671 .float lodmodelindex0;
672 .float lodmodelindex1;
673 .float lodmodelindex2;
677 float LOD_customize()
681 if(autocvar_loddebug)
683 d = autocvar_loddebug;
685 self.modelindex = self.lodmodelindex0;
686 else if(d == 2 || !self.lodmodelindex2)
687 self.modelindex = self.lodmodelindex1;
689 self.modelindex = self.lodmodelindex2;
693 // TODO csqc network this so it only gets sent once
694 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
695 if(d < self.loddistance1)
696 self.modelindex = self.lodmodelindex0;
697 else if(!self.lodmodelindex2 || d < self.loddistance2)
698 self.modelindex = self.lodmodelindex1;
700 self.modelindex = self.lodmodelindex2;
705 void LOD_uncustomize()
707 self.modelindex = self.lodmodelindex0;
710 void LODmodel_attach()
714 if(!self.loddistance1)
715 self.loddistance1 = 1000;
716 if(!self.loddistance2)
717 self.loddistance2 = 2000;
718 self.lodmodelindex0 = self.modelindex;
720 if(self.lodtarget1 != "")
722 e = find(world, targetname, self.lodtarget1);
725 self.lodmodel1 = e.model;
729 if(self.lodtarget2 != "")
731 e = find(world, targetname, self.lodtarget2);
734 self.lodmodel2 = e.model;
739 if(autocvar_loddebug < 0)
741 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
744 if(self.lodmodel1 != "")
750 precache_model(self.lodmodel1);
751 setmodel(self, self.lodmodel1);
752 self.lodmodelindex1 = self.modelindex;
754 if(self.lodmodel2 != "")
756 precache_model(self.lodmodel2);
757 setmodel(self, self.lodmodel2);
758 self.lodmodelindex2 = self.modelindex;
761 self.modelindex = self.lodmodelindex0;
762 setsize(self, mi, ma);
765 if(self.lodmodelindex1)
766 if not(self.SendEntity)
767 SetCustomizer(self, LOD_customize, LOD_uncustomize);
770 void ApplyMinMaxScaleAngles(entity e)
772 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
774 e.maxs = '1 1 1' * vlen(
775 '1 0 0' * max(-e.mins_x, e.maxs_x) +
776 '0 1 0' * max(-e.mins_y, e.maxs_y) +
777 '0 0 1' * max(-e.mins_z, e.maxs_z)
781 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
784 '1 0 0' * max(-e.mins_x, e.maxs_x) +
785 '0 1 0' * max(-e.mins_y, e.maxs_y)
788 e.mins_x = -e.maxs_x;
789 e.mins_y = -e.maxs_x;
792 setsize(e, e.mins * e.scale, e.maxs * e.scale);
794 setsize(e, e.mins, e.maxs);
797 void SetBrushEntityModel()
801 precache_model(self.model);
802 setmodel(self, self.model); // no precision needed
803 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
805 setorigin(self, self.origin);
806 ApplyMinMaxScaleAngles(self);
809 void SetBrushEntityModelNoLOD()
813 precache_model(self.model);
814 setmodel(self, self.model); // no precision needed
816 setorigin(self, self.origin);
817 ApplyMinMaxScaleAngles(self);
828 if (self.movedir != '0 0 0')
829 self.movedir = normalize(self.movedir);
832 makevectors (self.angles);
833 self.movedir = v_forward;
836 self.angles = '0 0 0';
841 // trigger angles are used for one-way touches. An angle of 0 is assumed
842 // to mean no restrictions, so use a yaw of 360 instead.
844 self.solid = SOLID_TRIGGER;
845 SetBrushEntityModel();
846 self.movetype = MOVETYPE_NONE;
851 void InitSolidBSPTrigger()
853 // trigger angles are used for one-way touches. An angle of 0 is assumed
854 // to mean no restrictions, so use a yaw of 360 instead.
856 self.solid = SOLID_BSP;
857 SetBrushEntityModel();
858 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
859 // self.modelindex = 0;
863 float InitMovingBrushTrigger()
865 // trigger angles are used for one-way touches. An angle of 0 is assumed
866 // to mean no restrictions, so use a yaw of 360 instead.
867 self.solid = SOLID_BSP;
868 SetBrushEntityModel();
869 self.movetype = MOVETYPE_PUSH;
870 if(self.modelindex == 0)
872 objerror("InitMovingBrushTrigger: no brushes found!");