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 not(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);
488 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
490 tracebox (v1, mi, ma, v2, nomonst, forent);
492 // restore players to current positions
495 FOR_EACH_PLAYER(player)
497 antilag_restore(player);
500 // restore shooter solid type
502 source.dphitcontentsmask = oldsolid;
504 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
506 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
508 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
510 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
512 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
514 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
516 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
518 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
520 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
522 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
524 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
526 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
528 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
530 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
532 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
534 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
537 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
543 //nudge = 2 * cvar("collision_impactnudge"); // why not?
546 dir = normalize(v2 - v1);
548 pos = v1 + dir * nudge;
555 if((pos - v1) * dir >= (v2 - v1) * dir)
563 tracebox(pos, mi, ma, v2, nomonsters, forent);
568 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
569 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
570 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
571 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
574 stopentity = trace_ent;
578 // we started inside solid.
579 // then trace from endpos to pos
581 tracebox(t, mi, ma, pos, nomonsters, forent);
585 // t is still inside solid? bad
586 // force advance, then, and retry
587 pos = t + dir * nudge;
589 // but if we hit an entity, stop RIGHT before it
590 if(stopatentity && stopentity)
592 trace_ent = stopentity;
594 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
600 // we actually LEFT solid!
601 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
607 // pos is outside solid?!? but why?!? never mind, just return it.
609 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
615 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity)
617 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity);
624 Returns a point at least 12 units away from walls
625 (useful for explosion animations, although the blast is performed where it really happened)
629 vector findbetterlocation (vector org, float mindist)
635 vec = mindist * '1 0 0';
639 traceline (org, org + vec, TRUE, world);
641 if (trace_fraction < 1)
644 traceline (loc, loc + vec, TRUE, world);
645 if (trace_fraction >= 1)
665 Returns a random number between -1.0 and 1.0
670 return 2 * (random () - 0.5);
675 Angc used for animations
680 float angc (float a1, float a2)
707 .float lodmodelindex0;
708 .float lodmodelindex1;
709 .float lodmodelindex2;
713 float LOD_customize()
717 if(autocvar_loddebug)
719 d = autocvar_loddebug;
721 self.modelindex = self.lodmodelindex0;
722 else if(d == 2 || !self.lodmodelindex2)
723 self.modelindex = self.lodmodelindex1;
725 self.modelindex = self.lodmodelindex2;
729 // TODO csqc network this so it only gets sent once
730 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
731 if(d < self.loddistance1)
732 self.modelindex = self.lodmodelindex0;
733 else if(!self.lodmodelindex2 || d < self.loddistance2)
734 self.modelindex = self.lodmodelindex1;
736 self.modelindex = self.lodmodelindex2;
741 void LOD_uncustomize()
743 self.modelindex = self.lodmodelindex0;
746 void LODmodel_attach()
750 if(!self.loddistance1)
751 self.loddistance1 = 1000;
752 if(!self.loddistance2)
753 self.loddistance2 = 2000;
754 self.lodmodelindex0 = self.modelindex;
756 if(self.lodtarget1 != "")
758 e = find(world, targetname, self.lodtarget1);
761 self.lodmodel1 = e.model;
765 if(self.lodtarget2 != "")
767 e = find(world, targetname, self.lodtarget2);
770 self.lodmodel2 = e.model;
775 if(autocvar_loddebug < 0)
777 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
780 if(self.lodmodel1 != "")
786 precache_model(self.lodmodel1);
787 setmodel(self, self.lodmodel1);
788 self.lodmodelindex1 = self.modelindex;
790 if(self.lodmodel2 != "")
792 precache_model(self.lodmodel2);
793 setmodel(self, self.lodmodel2);
794 self.lodmodelindex2 = self.modelindex;
797 self.modelindex = self.lodmodelindex0;
798 setsize(self, mi, ma);
801 if(self.lodmodelindex1)
802 if not(self.SendEntity)
803 SetCustomizer(self, LOD_customize, LOD_uncustomize);
806 void ApplyMinMaxScaleAngles(entity e)
808 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
810 e.maxs = '1 1 1' * vlen(
811 '1 0 0' * max(-e.mins_x, e.maxs_x) +
812 '0 1 0' * max(-e.mins_y, e.maxs_y) +
813 '0 0 1' * max(-e.mins_z, e.maxs_z)
817 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
820 '1 0 0' * max(-e.mins_x, e.maxs_x) +
821 '0 1 0' * max(-e.mins_y, e.maxs_y)
824 e.mins_x = -e.maxs_x;
825 e.mins_y = -e.maxs_x;
828 setsize(e, e.mins * e.scale, e.maxs * e.scale);
830 setsize(e, e.mins, e.maxs);
833 void SetBrushEntityModel()
837 precache_model(self.model);
838 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
840 vector mi = self.mins;
841 vector ma = self.maxs;
842 setmodel(self, self.model); // no precision needed
843 setsize(self, mi, ma);
846 setmodel(self, self.model); // no precision needed
847 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
849 setorigin(self, self.origin);
850 ApplyMinMaxScaleAngles(self);
853 void SetBrushEntityModelNoLOD()
857 precache_model(self.model);
858 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
860 vector mi = self.mins;
861 vector ma = self.maxs;
862 setmodel(self, self.model); // no precision needed
863 setsize(self, mi, ma);
866 setmodel(self, self.model); // no precision needed
868 setorigin(self, self.origin);
869 ApplyMinMaxScaleAngles(self);
880 if (self.movedir != '0 0 0')
881 self.movedir = normalize(self.movedir);
884 makevectors (self.angles);
885 self.movedir = v_forward;
888 self.angles = '0 0 0';
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_TRIGGER;
897 SetBrushEntityModel();
898 self.movetype = MOVETYPE_NONE;
903 void InitSolidBSPTrigger()
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.
908 self.solid = SOLID_BSP;
909 SetBrushEntityModel();
910 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
911 // self.modelindex = 0;
915 float InitMovingBrushTrigger()
917 // trigger angles are used for one-way touches. An angle of 0 is assumed
918 // to mean no restrictions, so use a yaw of 360 instead.
919 self.solid = SOLID_BSP;
920 SetBrushEntityModel();
921 self.movetype = MOVETYPE_PUSH;
922 if(self.modelindex == 0)
924 objerror("InitMovingBrushTrigger: no brushes found!");