1 float SUB_True() { return 1; }
2 float SUB_False() { return 0; }
4 void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove;
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 void SUB_CalcMove_controller_think (void)
183 delta = self.destvec;
184 delta2 = self.destvec2;
185 if(time < self.animstate_endtime) {
186 nexttick = time + sys_frametime;
188 traveltime = self.animstate_endtime - self.animstate_starttime;
189 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
190 if(self.platmovetype != 1)
192 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
193 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
194 phasepos = phasepos + 1; // correct range to [0, 2]
195 phasepos = phasepos / 2; // correct range to [0, 1]
197 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
198 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
200 if(nexttick < self.animstate_endtime) {
201 veloc = nextpos - self.owner.origin;
202 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
204 veloc = self.finaldest - self.owner.origin;
205 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
207 self.owner.velocity = veloc;
208 self.nextthink = nexttick;
210 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
212 self.owner.think = self.think1;
219 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
221 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
222 // 2 * control * t - 2 * control * t * t + dest * t * t
223 // 2 * control * t + (dest - 2 * control) * t * t
225 controller.origin = org; // starting point
229 controller.destvec = 2 * control; // control point
230 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
233 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
235 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
236 // 2 * control * t - 2 * control * t * t + dest * t * t
237 // 2 * control * t + (dest - 2 * control) * t * t
239 controller.origin = org; // starting point
242 controller.destvec = dest; // end point
243 controller.destvec2 = '0 0 0';
246 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeed, void() func)
252 objerror ("No speed is defined!");
255 self.finaldest = tdest;
256 self.think = SUB_CalcMoveDone;
258 if(tspeed > 0) // positive: start speed
259 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
260 else // negative: end speed
261 traveltime = 2 * vlen(tcontrol - tdest) / -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 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
275 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
276 controller.animstate_starttime = time;
277 controller.animstate_endtime = time + traveltime;
278 controller.think = SUB_CalcMove_controller_think;
279 controller.think1 = self.think;
281 // the thinking is now done by the controller
282 self.think = func_null;
283 self.nextthink = self.ltime + traveltime;
291 void SUB_CalcMove (vector tdest, float tspeed, void() func)
297 objerror ("No speed is defined!");
300 self.finaldest = tdest;
301 self.think = SUB_CalcMoveDone;
303 if (tdest == self.origin)
305 self.velocity = '0 0 0';
306 self.nextthink = self.ltime + 0.1;
310 delta = tdest - self.origin;
311 traveltime = vlen (delta) / tspeed;
313 // Very short animations don't really show off the effect
314 // of controlled animation, so let's just use linear movement.
315 // Alternatively entities can choose to specify non-controlled movement.
316 // The only currently implemented alternative movement is linear (value 1)
317 if (traveltime < 0.15 || self.platmovetype == 1)
319 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
320 self.nextthink = self.ltime + traveltime;
324 // now just run like a bezier curve...
325 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeed, func);
328 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
335 SUB_CalcMove (tdest, tspeed, func);
344 calculate self.avelocity and self.nextthink to reach destangle from
347 The calling function should make sure self.think is valid
350 void SUB_CalcAngleMoveDone (void)
352 // After rotating, set angle to exact final angle
353 self.angles = self.finalangle;
354 self.avelocity = '0 0 0';
360 // FIXME: I fixed this function only for rotation around the main axes
361 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
367 objerror ("No speed is defined!");
369 // take the shortest distance for the angles
370 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
371 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
372 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
373 delta = destangle - self.angles;
374 traveltime = vlen (delta) / tspeed;
377 self.finalangle = destangle;
378 self.think = SUB_CalcAngleMoveDone;
380 if (traveltime < 0.1)
382 self.avelocity = '0 0 0';
383 self.nextthink = self.ltime + 0.1;
387 self.avelocity = delta * (1 / traveltime);
388 self.nextthink = self.ltime + traveltime;
391 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
398 SUB_CalcAngleMove (destangle, tspeed, func);
407 unused but required by the engine
421 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
422 Additionally it moves players back into the past before the trace and restores them afterward.
425 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
430 // check whether antilagged traces are enabled
433 if (clienttype(forent) != CLIENTTYPE_REAL)
434 lag = 0; // only antilag for clients
436 // change shooter to SOLID_BBOX so the shot can hit corpses
437 oldsolid = source.dphitcontentsmask;
439 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
443 // take players back into the past
444 FOR_EACH_PLAYER(player)
446 antilag_takeback(player, time - lag);
451 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
453 tracebox (v1, mi, ma, v2, nomonst, forent);
455 // restore players to current positions
458 FOR_EACH_PLAYER(player)
460 antilag_restore(player);
463 // restore shooter solid type
465 source.dphitcontentsmask = oldsolid;
467 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
469 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
471 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
473 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
475 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
477 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
479 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
481 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
483 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
485 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
487 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
489 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
491 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
493 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
495 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
497 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
500 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
506 //nudge = 2 * cvar("collision_impactnudge"); // why not?
509 dir = normalize(v2 - v1);
511 pos = v1 + dir * nudge;
518 if((pos - v1) * dir >= (v2 - v1) * dir)
526 tracebox(pos, mi, ma, v2, nomonsters, forent);
531 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
532 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
533 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
534 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
537 stopentity = trace_ent;
541 // we started inside solid.
542 // then trace from endpos to pos
544 tracebox(t, mi, ma, pos, nomonsters, forent);
548 // t is still inside solid? bad
549 // force advance, then, and retry
550 pos = t + dir * nudge;
552 // but if we hit an entity, stop RIGHT before it
553 if(stopatentity && stopentity)
555 trace_ent = stopentity;
557 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
563 // we actually LEFT solid!
564 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
570 // pos is outside solid?!? but why?!? never mind, just return it.
572 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
578 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity)
580 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity);
587 Returns a point at least 12 units away from walls
588 (useful for explosion animations, although the blast is performed where it really happened)
592 vector findbetterlocation (vector org, float mindist)
598 vec = mindist * '1 0 0';
602 traceline (org, org + vec, TRUE, world);
604 if (trace_fraction < 1)
607 traceline (loc, loc + vec, TRUE, world);
608 if (trace_fraction >= 1)
628 Returns a random number between -1.0 and 1.0
633 return 2 * (random () - 0.5);
638 Angc used for animations
643 float angc (float a1, float a2)
670 .float lodmodelindex0;
671 .float lodmodelindex1;
672 .float lodmodelindex2;
676 float LOD_customize()
680 if(autocvar_loddebug)
682 d = autocvar_loddebug;
684 self.modelindex = self.lodmodelindex0;
685 else if(d == 2 || !self.lodmodelindex2)
686 self.modelindex = self.lodmodelindex1;
688 self.modelindex = self.lodmodelindex2;
692 // TODO csqc network this so it only gets sent once
693 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
694 if(d < self.loddistance1)
695 self.modelindex = self.lodmodelindex0;
696 else if(!self.lodmodelindex2 || d < self.loddistance2)
697 self.modelindex = self.lodmodelindex1;
699 self.modelindex = self.lodmodelindex2;
704 void LOD_uncustomize()
706 self.modelindex = self.lodmodelindex0;
709 void LODmodel_attach()
713 if(!self.loddistance1)
714 self.loddistance1 = 1000;
715 if(!self.loddistance2)
716 self.loddistance2 = 2000;
717 self.lodmodelindex0 = self.modelindex;
719 if(self.lodtarget1 != "")
721 e = find(world, targetname, self.lodtarget1);
724 self.lodmodel1 = e.model;
728 if(self.lodtarget2 != "")
730 e = find(world, targetname, self.lodtarget2);
733 self.lodmodel2 = e.model;
738 if(autocvar_loddebug < 0)
740 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
743 if(self.lodmodel1 != "")
749 precache_model(self.lodmodel1);
750 setmodel(self, self.lodmodel1);
751 self.lodmodelindex1 = self.modelindex;
753 if(self.lodmodel2 != "")
755 precache_model(self.lodmodel2);
756 setmodel(self, self.lodmodel2);
757 self.lodmodelindex2 = self.modelindex;
760 self.modelindex = self.lodmodelindex0;
761 setsize(self, mi, ma);
764 if(self.lodmodelindex1)
765 if not(self.SendEntity)
766 SetCustomizer(self, LOD_customize, LOD_uncustomize);
769 void ApplyMinMaxScaleAngles(entity e)
771 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
773 e.maxs = '1 1 1' * vlen(
774 '1 0 0' * max(-e.mins_x, e.maxs_x) +
775 '0 1 0' * max(-e.mins_y, e.maxs_y) +
776 '0 0 1' * max(-e.mins_z, e.maxs_z)
780 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
783 '1 0 0' * max(-e.mins_x, e.maxs_x) +
784 '0 1 0' * max(-e.mins_y, e.maxs_y)
787 e.mins_x = -e.maxs_x;
788 e.mins_y = -e.maxs_x;
791 setsize(e, e.mins * e.scale, e.maxs * e.scale);
793 setsize(e, e.mins, e.maxs);
796 void SetBrushEntityModel()
800 precache_model(self.model);
801 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
803 vector mi = self.mins;
804 vector ma = self.maxs;
805 setmodel(self, self.model); // no precision needed
806 setsize(self, mi, ma);
809 setmodel(self, self.model); // no precision needed
810 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
812 setorigin(self, self.origin);
813 ApplyMinMaxScaleAngles(self);
816 void SetBrushEntityModelNoLOD()
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
831 setorigin(self, self.origin);
832 ApplyMinMaxScaleAngles(self);
843 if (self.movedir != '0 0 0')
844 self.movedir = normalize(self.movedir);
847 makevectors (self.angles);
848 self.movedir = v_forward;
851 self.angles = '0 0 0';
856 // trigger angles are used for one-way touches. An angle of 0 is assumed
857 // to mean no restrictions, so use a yaw of 360 instead.
859 self.solid = SOLID_TRIGGER;
860 SetBrushEntityModel();
861 self.movetype = MOVETYPE_NONE;
866 void InitSolidBSPTrigger()
868 // trigger angles are used for one-way touches. An angle of 0 is assumed
869 // to mean no restrictions, so use a yaw of 360 instead.
871 self.solid = SOLID_BSP;
872 SetBrushEntityModel();
873 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
874 // self.modelindex = 0;
878 float InitMovingBrushTrigger()
880 // trigger angles are used for one-way touches. An angle of 0 is assumed
881 // to mean no restrictions, so use a yaw of 360 instead.
882 self.solid = SOLID_BSP;
883 SetBrushEntityModel();
884 self.movetype = MOVETYPE_PUSH;
885 if(self.modelindex == 0)
887 objerror("InitMovingBrushTrigger: no brushes found!");