3 void SUB_NullThink(void) { }
5 void spawnfunc_info_null (void)
8 // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
11 void setanim(entity e, vector anim, float looping, float override, float restart)
14 return; // no animation was given to us! We can't use this.
16 if (anim.x == e.animstate_startframe)
17 if (anim.y == e.animstate_numframes)
18 if (anim.z == e.animstate_framerate)
23 if(anim.y == 1) // ZYM animation
24 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
29 e.animstate_startframe = anim.x;
30 e.animstate_numframes = anim.y;
31 e.animstate_framerate = anim.z;
32 e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
33 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
34 e.animstate_looping = looping;
35 e.animstate_override = override;
36 e.frame = e.animstate_startframe;
37 e.frame1time = servertime;
40 void updateanim(entity e)
42 if (time >= e.animstate_endtime)
44 if (e.animstate_looping)
46 e.animstate_starttime = e.animstate_endtime;
47 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
49 e.animstate_override = false;
51 e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
52 //print(ftos(time), " -> ", ftos(e.frame), "\n");
62 void SUB_Remove (void)
71 Applies some friction to self
74 void SUB_Friction (void)
76 self.nextthink = time;
77 if(self.flags & FL_ONGROUND)
78 self.velocity = self.velocity * (1 - frametime * self.friction);
85 Makes client invisible or removes non-client
88 void SUB_VanishOrRemove (entity ent)
105 void SUB_SetFade_Think (void)
109 self.think = SUB_SetFade_Think;
110 self.nextthink = time;
111 self.alpha -= frametime * self.fade_rate;
112 if (self.alpha < 0.01)
113 SUB_VanishOrRemove(self);
115 self.nextthink = time;
122 Fade 'ent' out when time >= 'when'
125 void SUB_SetFade (entity ent, float when, float fadetime)
127 ent.fade_rate = 1/fadetime;
128 ent.think = SUB_SetFade_Think;
129 ent.nextthink = when;
136 calculate self.velocity and self.nextthink to reach dest from
137 self.origin traveling at speed
140 void SUB_CalcMoveDone (void)
142 // After moving, set origin to exact final destination
144 setorigin (self, self.finaldest);
145 self.velocity = '0 0 0';
151 void SUB_CalcMove_controller_think (void)
162 delta = self.destvec;
163 delta2 = self.destvec2;
164 if(time < self.animstate_endtime) {
165 nexttick = time + sys_frametime;
167 traveltime = self.animstate_endtime - self.animstate_starttime;
168 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
169 phasepos = cubic_speedfunc(self.platmovetype_start, self.platmovetype_end, phasepos);
170 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
171 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
173 if(self.owner.platmovetype_turn)
176 destangle = delta + 2 * delta2 * phasepos;
177 destangle = vectoangles(destangle);
178 destangle.x = -destangle.x; // flip up / down orientation
180 // take the shortest distance for the angles
181 self.owner.angles_x -= 360 * floor((self.owner.angles.x - destangle.x) / 360 + 0.5);
182 self.owner.angles_y -= 360 * floor((self.owner.angles.y - destangle.y) / 360 + 0.5);
183 self.owner.angles_z -= 360 * floor((self.owner.angles.z - destangle.z) / 360 + 0.5);
184 angloc = destangle - self.owner.angles;
185 angloc = angloc * (1 / sys_frametime); // so it arrives for the next frame
186 self.owner.avelocity = angloc;
188 if(nexttick < self.animstate_endtime)
189 veloc = nextpos - self.owner.origin;
191 veloc = self.finaldest - self.owner.origin;
192 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
194 self.owner.velocity = veloc;
195 self.nextthink = nexttick;
197 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
199 self.owner.think = self.think1;
206 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
208 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
209 // 2 * control * t - 2 * control * t * t + dest * t * t
210 // 2 * control * t + (dest - 2 * control) * t * t
212 controller.origin = org; // starting point
216 controller.destvec = 2 * control; // control point
217 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
218 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
221 void SUB_CalcMove_controller_setlinear (entity controller, vector org, 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
230 controller.destvec = dest; // end point
231 controller.destvec2 = '0 0 0';
234 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
240 objerror ("No speed is defined!");
243 self.finaldest = tdest;
244 self.think = SUB_CalcMoveDone;
250 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
253 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
256 traveltime = vlen(tdest - self.origin) / tspeed;
263 if (traveltime < 0.1) // useless anim
265 self.velocity = '0 0 0';
266 self.nextthink = self.ltime + 0.1;
270 controller = spawn();
271 controller.classname = "SUB_CalcMove_controller";
272 controller.owner = self;
273 controller.platmovetype = self.platmovetype;
274 controller.platmovetype_start = self.platmovetype_start;
275 controller.platmovetype_end = self.platmovetype_end;
276 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
277 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
278 controller.animstate_starttime = time;
279 controller.animstate_endtime = time + traveltime;
280 controller.think = SUB_CalcMove_controller_think;
281 controller.think1 = self.think;
283 // the thinking is now done by the controller
284 self.think = SUB_NullThink; // for PushMove
285 self.nextthink = self.ltime + traveltime;
293 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
299 objerror ("No speed is defined!");
302 self.finaldest = tdest;
303 self.think = SUB_CalcMoveDone;
305 if (tdest == self.origin)
307 self.velocity = '0 0 0';
308 self.nextthink = self.ltime + 0.1;
312 delta = tdest - self.origin;
320 traveltime = vlen (delta) / tspeed;
327 // Very short animations don't really show off the effect
328 // of controlled animation, so let's just use linear movement.
329 // Alternatively entities can choose to specify non-controlled movement.
330 // The only currently implemented alternative movement is linear (value 1)
331 if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
333 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
334 self.nextthink = self.ltime + traveltime;
338 // now just run like a bezier curve...
339 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
342 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
349 SUB_CalcMove (tdest, tspeedtype, tspeed, func);
358 calculate self.avelocity and self.nextthink to reach destangle from
361 The calling function should make sure self.think is valid
364 void SUB_CalcAngleMoveDone (void)
366 // After rotating, set angle to exact final angle
367 self.angles = self.finalangle;
368 self.avelocity = '0 0 0';
374 // FIXME: I fixed this function only for rotation around the main axes
375 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
381 objerror ("No speed is defined!");
383 // take the shortest distance for the angles
384 self.angles_x -= 360 * floor((self.angles.x - destangle.x) / 360 + 0.5);
385 self.angles_y -= 360 * floor((self.angles.y - destangle.y) / 360 + 0.5);
386 self.angles_z -= 360 * floor((self.angles.z - destangle.z) / 360 + 0.5);
387 delta = destangle - self.angles;
395 traveltime = vlen (delta) / tspeed;
403 self.finalangle = destangle;
404 self.think = SUB_CalcAngleMoveDone;
406 if (traveltime < 0.1)
408 self.avelocity = '0 0 0';
409 self.nextthink = self.ltime + 0.1;
413 self.avelocity = delta * (1 / traveltime);
414 self.nextthink = self.ltime + traveltime;
417 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
424 SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
433 unused but required by the engine
447 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
448 Additionally it moves players back into the past before the trace and restores them afterward.
451 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
456 // check whether antilagged traces are enabled
459 if (!IS_REAL_CLIENT(forent))
460 lag = 0; // only antilag for clients
462 // change shooter to SOLID_BBOX so the shot can hit corpses
463 oldsolid = source.dphitcontentsmask;
465 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
469 // take players back into the past
470 FOR_EACH_PLAYER(player)
472 antilag_takeback(player, time - lag);
473 FOR_EACH_MONSTER(player)
474 antilag_takeback(player, time - lag);
479 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
481 tracebox (v1, mi, ma, v2, nomonst, forent);
483 // restore players to current positions
486 FOR_EACH_PLAYER(player)
488 antilag_restore(player);
489 FOR_EACH_MONSTER(player)
490 antilag_restore(player);
493 // restore shooter solid type
495 source.dphitcontentsmask = oldsolid;
497 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
499 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, false);
501 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
503 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
505 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
507 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
509 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
511 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, false);
513 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
515 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, true);
517 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
519 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
521 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
523 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
525 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
527 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, true);
530 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
536 //nudge = 2 * cvar("collision_impactnudge"); // why not?
539 dir = normalize(v2 - v1);
541 pos = v1 + dir * nudge;
548 if(pos * dir >= v2 * dir)
556 tracebox(pos, mi, ma, v2, nomonsters, forent);
561 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
562 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
563 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
564 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
567 stopentity = trace_ent;
571 // we started inside solid.
572 // then trace from endpos to pos
574 tracebox(t, mi, ma, pos, nomonsters, forent);
578 // t is still inside solid? bad
579 // force advance, then, and retry
580 pos = t + dir * nudge;
582 // but if we hit an entity, stop RIGHT before it
583 if(stopatentity && stopentity && stopentity != ignorestopatentity)
585 trace_ent = stopentity;
587 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
593 // we actually LEFT solid!
594 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
600 // pos is outside solid?!? but why?!? never mind, just return it.
602 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
608 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
610 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
617 Returns a point at least 12 units away from walls
618 (useful for explosion animations, although the blast is performed where it really happened)
622 vector findbetterlocation (vector org, float mindist)
628 vec = mindist * '1 0 0';
632 traceline (org, org + vec, true, world);
634 if (trace_fraction < 1)
637 traceline (loc, loc + vec, true, world);
638 if (trace_fraction >= 1)
658 Returns a random number between -1.0 and 1.0
663 return 2 * (random () - 0.5);
668 Angc used for animations
673 float angc (float a1, float a2)
696 float LOD_customize()
700 if(autocvar_loddebug)
702 d = autocvar_loddebug;
704 self.modelindex = self.lodmodelindex0;
705 else if(d == 2 || !self.lodmodelindex2)
706 self.modelindex = self.lodmodelindex1;
708 self.modelindex = self.lodmodelindex2;
712 // TODO csqc network this so it only gets sent once
713 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
714 if(d < self.loddistance1)
715 self.modelindex = self.lodmodelindex0;
716 else if(!self.lodmodelindex2 || d < self.loddistance2)
717 self.modelindex = self.lodmodelindex1;
719 self.modelindex = self.lodmodelindex2;
724 void LOD_uncustomize()
726 self.modelindex = self.lodmodelindex0;
729 void LODmodel_attach()
733 if(!self.loddistance1)
734 self.loddistance1 = 1000;
735 if(!self.loddistance2)
736 self.loddistance2 = 2000;
737 self.lodmodelindex0 = self.modelindex;
739 if(self.lodtarget1 != "")
741 e = find(world, targetname, self.lodtarget1);
744 self.lodmodel1 = e.model;
748 if(self.lodtarget2 != "")
750 e = find(world, targetname, self.lodtarget2);
753 self.lodmodel2 = e.model;
758 if(autocvar_loddebug < 0)
760 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
763 if(self.lodmodel1 != "")
769 precache_model(self.lodmodel1);
770 setmodel(self, self.lodmodel1);
771 self.lodmodelindex1 = self.modelindex;
773 if(self.lodmodel2 != "")
775 precache_model(self.lodmodel2);
776 setmodel(self, self.lodmodel2);
777 self.lodmodelindex2 = self.modelindex;
780 self.modelindex = self.lodmodelindex0;
781 setsize(self, mi, ma);
784 if(self.lodmodelindex1)
785 if (!self.SendEntity)
786 SetCustomizer(self, LOD_customize, LOD_uncustomize);
789 void ApplyMinMaxScaleAngles(entity e)
791 if(e.angles.x != 0 || e.angles.z != 0 || self.avelocity.x != 0 || self.avelocity.z != 0) // "weird" rotation
793 e.maxs = '1 1 1' * vlen(
794 '1 0 0' * max(-e.mins.x, e.maxs.x) +
795 '0 1 0' * max(-e.mins.y, e.maxs.y) +
796 '0 0 1' * max(-e.mins.z, e.maxs.z)
800 else if(e.angles.y != 0 || self.avelocity.y != 0) // yaw only is a bit better
803 '1 0 0' * max(-e.mins.x, e.maxs.x) +
804 '0 1 0' * max(-e.mins.y, e.maxs.y)
807 e.mins_x = -e.maxs.x;
808 e.mins_y = -e.maxs.x;
811 setsize(e, e.mins * e.scale, e.maxs * e.scale);
813 setsize(e, e.mins, e.maxs);
816 void SetBrushEntityModel()
820 precache_model(self.model);
821 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
823 vector mi = self.mins;
824 vector ma = self.maxs;
825 setmodel(self, self.model); // no precision needed
826 setsize(self, mi, ma);
829 setmodel(self, self.model); // no precision needed
830 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
832 setorigin(self, self.origin);
833 ApplyMinMaxScaleAngles(self);
836 void SetBrushEntityModelNoLOD()
840 precache_model(self.model);
841 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
843 vector mi = self.mins;
844 vector ma = self.maxs;
845 setmodel(self, self.model); // no precision needed
846 setsize(self, mi, ma);
849 setmodel(self, self.model); // no precision needed
851 setorigin(self, self.origin);
852 ApplyMinMaxScaleAngles(self);
863 if (self.movedir != '0 0 0')
864 self.movedir = normalize(self.movedir);
867 makevectors (self.angles);
868 self.movedir = v_forward;
871 self.angles = '0 0 0';
876 // trigger angles are used for one-way touches. An angle of 0 is assumed
877 // to mean no restrictions, so use a yaw of 360 instead.
879 self.solid = SOLID_TRIGGER;
880 SetBrushEntityModel();
881 self.movetype = MOVETYPE_NONE;
886 void InitSolidBSPTrigger()
888 // trigger angles are used for one-way touches. An angle of 0 is assumed
889 // to mean no restrictions, so use a yaw of 360 instead.
891 self.solid = SOLID_BSP;
892 SetBrushEntityModel();
893 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
894 // self.modelindex = 0;
898 float InitMovingBrushTrigger()
900 // trigger angles are used for one-way touches. An angle of 0 is assumed
901 // to mean no restrictions, so use a yaw of 360 instead.
902 self.solid = SOLID_BSP;
903 SetBrushEntityModel();
904 self.movetype = MOVETYPE_PUSH;
905 if(self.modelindex == 0)
907 objerror("InitMovingBrushTrigger: no brushes found!");