2 float SUB_True() { return 1; }
3 float SUB_False() { return 0; }
5 void() SUB_CalcMoveDone;
6 void() SUB_CalcAngleMoveDone;
7 //void() SUB_UseTargets;
10 void spawnfunc_info_null (void)
13 // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
16 void setanim(entity e, vector anim, float looping, float override, float restart)
19 return; // no animation was given to us! We can't use this.
21 if (anim_x == e.animstate_startframe)
22 if (anim_y == e.animstate_numframes)
23 if (anim_z == e.animstate_framerate)
28 if(anim_y == 1) // ZYM animation
29 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
34 e.animstate_startframe = anim_x;
35 e.animstate_numframes = anim_y;
36 e.animstate_framerate = anim_z;
37 e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
38 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
39 e.animstate_looping = looping;
40 e.animstate_override = override;
41 e.frame = e.animstate_startframe;
42 e.frame1time = servertime;
45 void updateanim(entity e)
47 if (time >= e.animstate_endtime)
49 if (e.animstate_looping)
51 e.animstate_starttime = e.animstate_endtime;
52 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
54 e.animstate_override = FALSE;
56 e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
57 //print(ftos(time), " -> ", ftos(e.frame), "\n");
60 vector animfixfps(entity e, vector a)
62 // multi-frame anim: keep as-is
66 dur = frameduration(e.modelindex, a_x);
80 void SUB_Remove (void)
89 Applies some friction to self
93 void SUB_Friction (void)
95 self.nextthink = time;
96 if(self.flags & FL_ONGROUND)
97 self.velocity = self.velocity * (1 - frametime * self.friction);
104 Makes client invisible or removes non-client
107 void SUB_VanishOrRemove (entity ent)
109 if (ent.flags & FL_CLIENT)
124 void SUB_SetFade_Think (void)
128 self.think = SUB_SetFade_Think;
129 self.nextthink = time;
130 self.alpha -= frametime * self.fade_rate;
131 if (self.alpha < 0.01)
132 SUB_VanishOrRemove(self);
134 self.nextthink = time;
141 Fade 'ent' out when time >= 'when'
144 void SUB_SetFade (entity ent, float when, float fadetime)
146 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
149 ent.fade_rate = 1/fadetime;
150 ent.think = SUB_SetFade_Think;
151 ent.nextthink = when;
158 calculate self.velocity and self.nextthink to reach dest from
159 self.origin traveling at speed
162 void SUB_CalcMoveDone (void)
164 // After moving, set origin to exact final destination
166 setorigin (self, self.finaldest);
167 self.velocity = '0 0 0';
173 .float platmovetype_turn;
174 void SUB_CalcMove_controller_think (void)
185 delta = self.destvec;
186 delta2 = self.destvec2;
187 if(time < self.animstate_endtime) {
188 nexttick = time + sys_frametime;
190 traveltime = self.animstate_endtime - self.animstate_starttime;
191 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
192 phasepos = cubic_speedfunc(self.platmovetype_start, self.platmovetype_end, phasepos);
193 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
194 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
196 if(self.owner.platmovetype_turn)
199 destangle = delta + 2 * delta2 * phasepos;
200 destangle = vectoangles(destangle);
201 destangle_x = -destangle_x;
203 // take the shortest distance for the angles
204 self.owner.angles_x -= 360 * floor((self.owner.angles_x - destangle_x) / 360 + 0.5);
205 self.owner.angles_y -= 360 * floor((self.owner.angles_y - destangle_y) / 360 + 0.5);
206 self.owner.angles_z -= 360 * floor((self.owner.angles_z - destangle_z) / 360 + 0.5);
207 adelta = destangle - self.owner.angles; // flip up / down orientation
209 if(nexttick < self.animstate_endtime) {
210 veloc = nextpos - self.owner.origin;
211 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
213 veloc = self.finaldest - self.owner.origin;
214 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
216 self.owner.velocity = veloc;
217 self.owner.avelocity = adelta;
218 self.nextthink = nexttick;
220 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
222 self.owner.think = self.think1;
229 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
231 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
232 // 2 * control * t - 2 * control * t * t + dest * t * t
233 // 2 * control * t + (dest - 2 * control) * t * t
235 controller.origin = org; // starting point
239 controller.destvec = 2 * control; // control point
240 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
241 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
244 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
246 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
247 // 2 * control * t - 2 * control * t * t + dest * t * t
248 // 2 * control * t + (dest - 2 * control) * t * t
250 controller.origin = org; // starting point
253 controller.destvec = dest; // end point
254 controller.destvec2 = '0 0 0';
257 float TSPEED_TIME = -1;
258 float TSPEED_LINEAR = 0;
259 float TSPEED_START = 1;
260 float TSPEED_END = 2;
263 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
269 objerror ("No speed is defined!");
272 self.finaldest = tdest;
273 self.think = SUB_CalcMoveDone;
279 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
282 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
285 traveltime = vlen(tdest - self.origin) / tspeed;
292 if (traveltime < 0.1) // useless anim
294 self.velocity = '0 0 0';
295 self.nextthink = self.ltime + 0.1;
299 controller = spawn();
300 controller.classname = "SUB_CalcMove_controller";
301 controller.owner = self;
302 controller.platmovetype = self.platmovetype;
303 controller.platmovetype_start = self.platmovetype_start;
304 controller.platmovetype_end = self.platmovetype_end;
305 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
306 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
307 controller.animstate_starttime = time;
308 controller.animstate_endtime = time + traveltime;
309 controller.think = SUB_CalcMove_controller_think;
310 controller.think1 = self.think;
312 // the thinking is now done by the controller
313 self.think = SUB_Null;
314 self.nextthink = self.ltime + traveltime;
322 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
328 objerror ("No speed is defined!");
331 self.finaldest = tdest;
332 self.think = SUB_CalcMoveDone;
334 if (tdest == self.origin)
336 self.velocity = '0 0 0';
337 self.nextthink = self.ltime + 0.1;
341 delta = tdest - self.origin;
349 traveltime = vlen (delta) / tspeed;
356 // Very short animations don't really show off the effect
357 // of controlled animation, so let's just use linear movement.
358 // Alternatively entities can choose to specify non-controlled movement.
359 // The only currently implemented alternative movement is linear (value 1)
360 if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
362 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
363 self.nextthink = self.ltime + traveltime;
367 // now just run like a bezier curve...
368 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
371 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
378 SUB_CalcMove (tdest, tspeedtype, tspeed, func);
387 calculate self.avelocity and self.nextthink to reach destangle from
390 The calling function should make sure self.think is valid
393 void SUB_CalcAngleMoveDone (void)
395 // After rotating, set angle to exact final angle
396 self.angles = self.finalangle;
397 self.avelocity = '0 0 0';
403 // FIXME: I fixed this function only for rotation around the main axes
404 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
410 objerror ("No speed is defined!");
412 // take the shortest distance for the angles
413 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
414 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
415 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
416 delta = destangle - self.angles;
424 traveltime = vlen (delta) / tspeed;
432 self.finalangle = destangle;
433 self.think = SUB_CalcAngleMoveDone;
435 if (traveltime < 0.1)
437 self.avelocity = '0 0 0';
438 self.nextthink = self.ltime + 0.1;
442 self.avelocity = delta * (1 / traveltime);
443 self.nextthink = self.ltime + traveltime;
446 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
453 SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
462 unused but required by the engine
476 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
477 Additionally it moves players back into the past before the trace and restores them afterward.
480 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
485 // check whether antilagged traces are enabled
488 if (clienttype(forent) != CLIENTTYPE_REAL)
489 lag = 0; // only antilag for clients
491 // change shooter to SOLID_BBOX so the shot can hit corpses
492 oldsolid = source.dphitcontentsmask;
494 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
498 // take players back into the past
499 FOR_EACH_PLAYER(player)
501 antilag_takeback(player, time - lag);
506 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
508 tracebox (v1, mi, ma, v2, nomonst, forent);
510 // restore players to current positions
513 FOR_EACH_PLAYER(player)
515 antilag_restore(player);
518 // restore shooter solid type
520 source.dphitcontentsmask = oldsolid;
522 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
524 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
526 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
528 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
530 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
532 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
534 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
536 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
538 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
540 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
542 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
544 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
546 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
548 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
550 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
552 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
555 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
561 //nudge = 2 * cvar("collision_impactnudge"); // why not?
564 dir = normalize(v2 - v1);
566 pos = v1 + dir * nudge;
573 if((pos - v1) * dir >= (v2 - v1) * dir)
581 tracebox(pos, mi, ma, v2, nomonsters, forent);
586 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
587 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
588 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
589 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
592 stopentity = trace_ent;
596 // we started inside solid.
597 // then trace from endpos to pos
599 tracebox(t, mi, ma, pos, nomonsters, forent);
603 // t is still inside solid? bad
604 // force advance, then, and retry
605 pos = t + dir * nudge;
607 // but if we hit an entity, stop RIGHT before it
608 if(stopatentity && stopentity)
610 trace_ent = stopentity;
612 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
618 // we actually LEFT solid!
619 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
625 // pos is outside solid?!? but why?!? never mind, just return it.
627 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
633 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity)
635 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity);
642 Returns a point at least 12 units away from walls
643 (useful for explosion animations, although the blast is performed where it really happened)
647 vector findbetterlocation (vector org, float mindist)
653 vec = mindist * '1 0 0';
657 traceline (org, org + vec, TRUE, world);
659 if (trace_fraction < 1)
662 traceline (loc, loc + vec, TRUE, world);
663 if (trace_fraction >= 1)
683 Returns a random number between -1.0 and 1.0
688 return 2 * (random () - 0.5);
693 Angc used for animations
698 float angc (float a1, float a2)
725 .float lodmodelindex0;
726 .float lodmodelindex1;
727 .float lodmodelindex2;
731 float LOD_customize()
735 if(autocvar_loddebug)
737 d = autocvar_loddebug;
739 self.modelindex = self.lodmodelindex0;
740 else if(d == 2 || !self.lodmodelindex2)
741 self.modelindex = self.lodmodelindex1;
743 self.modelindex = self.lodmodelindex2;
747 // TODO csqc network this so it only gets sent once
748 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
749 if(d < self.loddistance1)
750 self.modelindex = self.lodmodelindex0;
751 else if(!self.lodmodelindex2 || d < self.loddistance2)
752 self.modelindex = self.lodmodelindex1;
754 self.modelindex = self.lodmodelindex2;
759 void LOD_uncustomize()
761 self.modelindex = self.lodmodelindex0;
764 void LODmodel_attach()
768 if(!self.loddistance1)
769 self.loddistance1 = 1000;
770 if(!self.loddistance2)
771 self.loddistance2 = 2000;
772 self.lodmodelindex0 = self.modelindex;
774 if(self.lodtarget1 != "")
776 e = find(world, targetname, self.lodtarget1);
779 self.lodmodel1 = e.model;
783 if(self.lodtarget2 != "")
785 e = find(world, targetname, self.lodtarget2);
788 self.lodmodel2 = e.model;
793 if(autocvar_loddebug < 0)
795 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
798 if(self.lodmodel1 != "")
804 precache_model(self.lodmodel1);
805 setmodel(self, self.lodmodel1);
806 self.lodmodelindex1 = self.modelindex;
808 if(self.lodmodel2 != "")
810 precache_model(self.lodmodel2);
811 setmodel(self, self.lodmodel2);
812 self.lodmodelindex2 = self.modelindex;
815 self.modelindex = self.lodmodelindex0;
816 setsize(self, mi, ma);
819 if(self.lodmodelindex1)
820 if not(self.SendEntity)
821 SetCustomizer(self, LOD_customize, LOD_uncustomize);
824 void ApplyMinMaxScaleAngles(entity e)
826 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
828 e.maxs = '1 1 1' * vlen(
829 '1 0 0' * max(-e.mins_x, e.maxs_x) +
830 '0 1 0' * max(-e.mins_y, e.maxs_y) +
831 '0 0 1' * max(-e.mins_z, e.maxs_z)
835 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
838 '1 0 0' * max(-e.mins_x, e.maxs_x) +
839 '0 1 0' * max(-e.mins_y, e.maxs_y)
842 e.mins_x = -e.maxs_x;
843 e.mins_y = -e.maxs_x;
846 setsize(e, e.mins * e.scale, e.maxs * e.scale);
848 setsize(e, e.mins, e.maxs);
851 void SetBrushEntityModel()
855 precache_model(self.model);
856 setmodel(self, self.model); // no precision needed
857 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
859 setorigin(self, self.origin);
860 ApplyMinMaxScaleAngles(self);
863 void SetBrushEntityModelNoLOD()
867 precache_model(self.model);
868 setmodel(self, self.model); // no precision needed
870 setorigin(self, self.origin);
871 ApplyMinMaxScaleAngles(self);
882 if (self.movedir != '0 0 0')
883 self.movedir = normalize(self.movedir);
886 makevectors (self.angles);
887 self.movedir = v_forward;
890 self.angles = '0 0 0';
895 // trigger angles are used for one-way touches. An angle of 0 is assumed
896 // to mean no restrictions, so use a yaw of 360 instead.
898 self.solid = SOLID_TRIGGER;
899 SetBrushEntityModel();
900 self.movetype = MOVETYPE_NONE;
905 void InitSolidBSPTrigger()
907 // trigger angles are used for one-way touches. An angle of 0 is assumed
908 // to mean no restrictions, so use a yaw of 360 instead.
910 self.solid = SOLID_BSP;
911 SetBrushEntityModel();
912 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
913 // self.modelindex = 0;
917 float InitMovingBrushTrigger()
919 // trigger angles are used for one-way touches. An angle of 0 is assumed
920 // to mean no restrictions, so use a yaw of 360 instead.
921 self.solid = SOLID_BSP;
922 SetBrushEntityModel();
923 self.movetype = MOVETYPE_PUSH;
924 if(self.modelindex == 0)
926 objerror("InitMovingBrushTrigger: no brushes found!");