1 void SUB_NullThink(void) { }
3 void() SUB_CalcMoveDone;
4 void() SUB_CalcAngleMoveDone;
5 //void() SUB_UseTargets;
8 void spawnfunc_info_null (void)
11 // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
14 void setanim(entity e, vector anim, float looping, float override, float restart)
17 return; // no animation was given to us! We can't use this.
19 if (anim_x == e.animstate_startframe)
20 if (anim_y == e.animstate_numframes)
21 if (anim_z == e.animstate_framerate)
26 if(anim_y == 1) // ZYM animation
27 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
32 e.animstate_startframe = anim.x;
33 e.animstate_numframes = anim.y;
34 e.animstate_framerate = anim.z;
35 e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
36 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
37 e.animstate_looping = looping;
38 e.animstate_override = override;
39 e.frame = e.animstate_startframe;
40 e.frame1time = servertime;
43 void updateanim(entity e)
45 if (time >= e.animstate_endtime)
47 if (e.animstate_looping)
49 e.animstate_starttime = e.animstate_endtime;
50 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
52 e.animstate_override = FALSE;
54 e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
55 //print(ftos(time), " -> ", ftos(e.frame), "\n");
65 void SUB_Remove (void)
74 Applies some friction to self
78 void SUB_Friction (void)
80 self.nextthink = time;
81 if(self.flags & FL_ONGROUND)
82 self.velocity = self.velocity * (1 - frametime * self.friction);
89 Makes client invisible or removes non-client
92 void SUB_VanishOrRemove (entity ent)
109 void SUB_SetFade_Think (void)
113 self.think = SUB_SetFade_Think;
114 self.nextthink = time;
115 self.alpha -= frametime * self.fade_rate;
116 if (self.alpha < 0.01)
117 SUB_VanishOrRemove(self);
119 self.nextthink = time;
126 Fade 'ent' out when time >= 'when'
129 void SUB_SetFade (entity ent, float when, float fadetime)
131 ent.fade_rate = 1/fadetime;
132 ent.think = SUB_SetFade_Think;
133 ent.nextthink = when;
140 calculate self.velocity and self.nextthink to reach dest from
141 self.origin traveling at speed
144 void SUB_CalcMoveDone (void)
146 // After moving, set origin to exact final destination
148 setorigin (self, self.finaldest);
149 self.velocity = '0 0 0';
155 .float platmovetype_turn;
156 void SUB_CalcMove_controller_think (void)
167 delta = self.destvec;
168 delta2 = self.destvec2;
169 if(time < self.animstate_endtime) {
170 nexttick = time + sys_frametime;
172 traveltime = self.animstate_endtime - self.animstate_starttime;
173 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
174 phasepos = cubic_speedfunc(self.platmovetype_start, self.platmovetype_end, phasepos);
175 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
176 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
178 if(self.owner.platmovetype_turn)
181 destangle = delta + 2 * delta2 * phasepos;
182 destangle = vectoangles(destangle);
183 destangle_x = -destangle.x; // flip up / down orientation
185 // take the shortest distance for the angles
186 self.owner.angles_x -= 360 * floor((self.owner.angles.x - destangle.x) / 360 + 0.5);
187 self.owner.angles_y -= 360 * floor((self.owner.angles.y - destangle.y) / 360 + 0.5);
188 self.owner.angles_z -= 360 * floor((self.owner.angles.z - destangle.z) / 360 + 0.5);
189 angloc = destangle - self.owner.angles;
190 angloc = angloc * (1 / sys_frametime); // so it arrives for the next frame
191 self.owner.avelocity = angloc;
193 if(nexttick < self.animstate_endtime)
194 veloc = nextpos - self.owner.origin;
196 veloc = self.finaldest - self.owner.origin;
197 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
199 self.owner.velocity = veloc;
200 self.nextthink = nexttick;
202 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
204 self.owner.think = self.think1;
211 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
213 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
214 // 2 * control * t - 2 * control * t * t + dest * t * t
215 // 2 * control * t + (dest - 2 * control) * t * t
217 controller.origin = org; // starting point
221 controller.destvec = 2 * control; // control point
222 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
223 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
226 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
228 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
229 // 2 * control * t - 2 * control * t * t + dest * t * t
230 // 2 * control * t + (dest - 2 * control) * t * t
232 controller.origin = org; // starting point
235 controller.destvec = dest; // end point
236 controller.destvec2 = '0 0 0';
239 float TSPEED_TIME = -1;
240 float TSPEED_LINEAR = 0;
241 float TSPEED_START = 1;
242 float TSPEED_END = 2;
245 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
251 objerror ("No speed is defined!");
254 self.finaldest = tdest;
255 self.think = SUB_CalcMoveDone;
261 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
264 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
267 traveltime = vlen(tdest - self.origin) / 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 controller.platmovetype_start = self.platmovetype_start;
286 controller.platmovetype_end = self.platmovetype_end;
287 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
288 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
289 controller.animstate_starttime = time;
290 controller.animstate_endtime = time + traveltime;
291 controller.think = SUB_CalcMove_controller_think;
292 controller.think1 = self.think;
294 // the thinking is now done by the controller
295 self.think = SUB_NullThink; // for PushMove
296 self.nextthink = self.ltime + traveltime;
304 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
310 objerror ("No speed is defined!");
313 self.finaldest = tdest;
314 self.think = SUB_CalcMoveDone;
316 if (tdest == self.origin)
318 self.velocity = '0 0 0';
319 self.nextthink = self.ltime + 0.1;
323 delta = tdest - self.origin;
331 traveltime = vlen (delta) / tspeed;
338 // Very short animations don't really show off the effect
339 // of controlled animation, so let's just use linear movement.
340 // Alternatively entities can choose to specify non-controlled movement.
341 // The only currently implemented alternative movement is linear (value 1)
342 if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
344 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
345 self.nextthink = self.ltime + traveltime;
349 // now just run like a bezier curve...
350 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
353 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
360 SUB_CalcMove (tdest, tspeedtype, tspeed, func);
369 calculate self.avelocity and self.nextthink to reach destangle from
372 The calling function should make sure self.think is valid
375 void SUB_CalcAngleMoveDone (void)
377 // After rotating, set angle to exact final angle
378 self.angles = self.finalangle;
379 self.avelocity = '0 0 0';
385 // FIXME: I fixed this function only for rotation around the main axes
386 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
392 objerror ("No speed is defined!");
394 // take the shortest distance for the angles
395 self.angles_x -= 360 * floor((self.angles.x - destangle.x) / 360 + 0.5);
396 self.angles_y -= 360 * floor((self.angles.y - destangle.y) / 360 + 0.5);
397 self.angles_z -= 360 * floor((self.angles.z - destangle.z) / 360 + 0.5);
398 delta = destangle - self.angles;
406 traveltime = vlen (delta) / tspeed;
414 self.finalangle = destangle;
415 self.think = SUB_CalcAngleMoveDone;
417 if (traveltime < 0.1)
419 self.avelocity = '0 0 0';
420 self.nextthink = self.ltime + 0.1;
424 self.avelocity = delta * (1 / traveltime);
425 self.nextthink = self.ltime + traveltime;
428 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
435 SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
444 unused but required by the engine
458 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
459 Additionally it moves players back into the past before the trace and restores them afterward.
462 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
467 // check whether antilagged traces are enabled
470 if (!IS_REAL_CLIENT(forent))
471 lag = 0; // only antilag for clients
473 // change shooter to SOLID_BBOX so the shot can hit corpses
474 oldsolid = source.dphitcontentsmask;
476 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
480 // take players back into the past
481 FOR_EACH_PLAYER(player)
483 antilag_takeback(player, time - lag);
484 FOR_EACH_MONSTER(player)
485 antilag_takeback(player, time - lag);
490 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
492 tracebox (v1, mi, ma, v2, nomonst, forent);
494 // restore players to current positions
497 FOR_EACH_PLAYER(player)
499 antilag_restore(player);
500 FOR_EACH_MONSTER(player)
501 antilag_restore(player);
504 // restore shooter solid type
506 source.dphitcontentsmask = oldsolid;
508 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
510 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
512 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
514 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
516 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
518 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
520 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
522 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
524 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
526 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
528 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
530 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
532 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
534 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
536 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
538 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
541 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) // returns the number of traces done, for benchmarking
547 //nudge = 2 * cvar("collision_impactnudge"); // why not?
550 dir = normalize(v2 - v1);
552 pos = v1 + dir * nudge;
559 if(pos * dir >= v2 * dir)
567 tracebox(pos, mi, ma, v2, nomonsters, forent);
572 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
573 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
574 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
575 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
578 stopentity = trace_ent;
582 // we started inside solid.
583 // then trace from endpos to pos
585 tracebox(t, mi, ma, pos, nomonsters, forent);
589 // t is still inside solid? bad
590 // force advance, then, and retry
591 pos = t + dir * nudge;
593 // but if we hit an entity, stop RIGHT before it
594 if(stopatentity && stopentity && stopentity != ignorestopatentity)
596 trace_ent = stopentity;
598 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
604 // we actually LEFT solid!
605 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
611 // pos is outside solid?!? but why?!? never mind, just return it.
613 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
619 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
621 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
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 (!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 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
844 vector mi = self.mins;
845 vector ma = self.maxs;
846 setmodel(self, self.model); // no precision needed
847 setsize(self, mi, ma);
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 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
864 vector mi = self.mins;
865 vector ma = self.maxs;
866 setmodel(self, self.model); // no precision needed
867 setsize(self, mi, ma);
870 setmodel(self, self.model); // no precision needed
872 setorigin(self, self.origin);
873 ApplyMinMaxScaleAngles(self);
884 if (self.movedir != '0 0 0')
885 self.movedir = normalize(self.movedir);
888 makevectors (self.angles);
889 self.movedir = v_forward;
892 self.angles = '0 0 0';
897 // trigger angles are used for one-way touches. An angle of 0 is assumed
898 // to mean no restrictions, so use a yaw of 360 instead.
900 self.solid = SOLID_TRIGGER;
901 SetBrushEntityModel();
902 self.movetype = MOVETYPE_NONE;
907 void InitSolidBSPTrigger()
909 // trigger angles are used for one-way touches. An angle of 0 is assumed
910 // to mean no restrictions, so use a yaw of 360 instead.
912 self.solid = SOLID_BSP;
913 SetBrushEntityModel();
914 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
915 // self.modelindex = 0;
919 float InitMovingBrushTrigger()
921 // trigger angles are used for one-way touches. An angle of 0 is assumed
922 // to mean no restrictions, so use a yaw of 360 instead.
923 self.solid = SOLID_BSP;
924 SetBrushEntityModel();
925 self.movetype = MOVETYPE_PUSH;
926 if(self.modelindex == 0)
928 objerror("InitMovingBrushTrigger: no brushes found!");