2 float SUB_True() { return 1; }
3 float SUB_False() { return 0; }
5 void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove;
6 void() SUB_CalcMoveDone;
7 void() SUB_CalcAngleMoveDone;
8 //void() SUB_UseTargets;
11 void spawnfunc_info_null (void)
14 // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
17 void setanim(entity e, vector anim, float looping, float override, float restart)
20 return; // no animation was given to us! We can't use this.
22 if (anim_x == e.animstate_startframe)
23 if (anim_y == e.animstate_numframes)
24 if (anim_z == e.animstate_framerate)
29 if(anim_y == 1) // ZYM animation
30 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
35 e.animstate_startframe = anim_x;
36 e.animstate_numframes = anim_y;
37 e.animstate_framerate = anim_z;
38 e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
39 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
40 e.animstate_looping = looping;
41 e.animstate_override = override;
42 e.frame = e.animstate_startframe;
43 e.frame1time = servertime;
46 void updateanim(entity e)
48 if (time >= e.animstate_endtime)
50 if (e.animstate_looping)
52 e.animstate_starttime = e.animstate_endtime;
53 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
55 e.animstate_override = FALSE;
57 e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
58 //print(ftos(time), " -> ", ftos(e.frame), "\n");
61 vector animfixfps(entity e, vector a)
63 // multi-frame anim: keep as-is
67 dur = frameduration(e.modelindex, a_x);
81 void SUB_Remove (void)
90 Applies some friction to self
94 void SUB_Friction (void)
96 self.nextthink = time;
97 if(self.flags & FL_ONGROUND)
98 self.velocity = self.velocity * (1 - frametime * self.friction);
105 Makes client invisible or removes non-client
108 void SUB_VanishOrRemove (entity ent)
110 if (ent.flags & FL_CLIENT)
125 void SUB_SetFade_Think (void)
127 self.think = SUB_SetFade_Think;
128 self.nextthink = self.fade_time;
129 self.alpha = 1 - (time - self.fade_time) * self.fade_rate;
130 if (self.alpha < 0.01)
131 SUB_VanishOrRemove(self);
132 self.alpha = bound(0.01, self.alpha, 1);
139 Fade 'ent' out when time >= 'when'
142 void SUB_SetFade (entity ent, float when, float fadetime)
144 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
147 ent.fade_rate = 1/fadetime;
148 ent.fade_time = when;
149 ent.think = SUB_SetFade_Think;
150 ent.nextthink = when;
157 calculate self.velocity and self.nextthink to reach dest from
158 self.origin traveling at speed
161 void SUB_CalcMoveDone (void)
163 // After moving, set origin to exact final destination
165 setorigin (self, self.finaldest);
166 self.velocity = '0 0 0';
172 void SUB_CalcMove_controller_think (void)
181 if(time < self.animstate_endtime) {
182 delta = self.destvec;
183 nexttick = time + sys_frametime;
185 if(nexttick < self.animstate_endtime) {
186 traveltime = self.animstate_endtime - self.animstate_starttime;
187 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
188 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
189 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
190 phasepos = phasepos + 1; // correct range to [0, 2]
191 phasepos = phasepos / 2; // correct range to [0, 1]
192 nextpos = self.origin + (delta * phasepos);
194 veloc = nextpos - self.owner.origin;
195 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
198 veloc = self.finaldest - self.owner.origin;
199 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
201 self.owner.velocity = veloc;
202 self.nextthink = nexttick;
205 self.owner.think = self.think1;
212 void SUB_CalcMove (vector tdest, float tspeed, void() func)
219 objerror ("No speed is defined!");
222 self.finaldest = tdest;
223 self.think = SUB_CalcMoveDone;
225 if (tdest == self.origin)
227 self.velocity = '0 0 0';
228 self.nextthink = self.ltime + 0.1;
232 delta = tdest - self.origin;
233 traveltime = vlen (delta) / tspeed;
235 if (traveltime < 0.1)
237 self.velocity = '0 0 0';
238 self.nextthink = self.ltime + 0.1;
242 // Very short animations don't really show off the effect
243 // of controlled animation, so let's just use linear movement.
244 // Alternatively entities can choose to specify non-controlled movement.
245 // The only currently implemented alternative movement is linear (value 1)
246 if (traveltime < 0.15 || self.platmovetype == 1)
248 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
249 self.nextthink = self.ltime + traveltime;
253 controller = spawn();
254 controller.classname = "SUB_CalcMove_controller";
255 controller.owner = self;
256 controller.origin = self.origin; // starting point
257 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
258 controller.destvec = delta;
259 controller.animstate_starttime = time;
260 controller.animstate_endtime = time + traveltime;
261 controller.think = SUB_CalcMove_controller_think;
262 controller.think1 = self.think;
264 // the thinking is now done by the controller
265 self.think = SUB_Null;
266 self.nextthink = self.ltime + traveltime;
274 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
281 SUB_CalcMove (tdest, tspeed, func);
290 calculate self.avelocity and self.nextthink to reach destangle from
293 The calling function should make sure self.think is valid
296 void SUB_CalcAngleMoveDone (void)
298 // After rotating, set angle to exact final angle
299 self.angles = self.finalangle;
300 self.avelocity = '0 0 0';
306 // FIXME: I fixed this function only for rotation around the main axes
307 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
313 objerror ("No speed is defined!");
315 // take the shortest distance for the angles
316 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
317 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
318 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
319 delta = destangle - self.angles;
320 traveltime = vlen (delta) / tspeed;
323 self.finalangle = destangle;
324 self.think = SUB_CalcAngleMoveDone;
326 if (traveltime < 0.1)
328 self.avelocity = '0 0 0';
329 self.nextthink = self.ltime + 0.1;
333 self.avelocity = delta * (1 / traveltime);
334 self.nextthink = self.ltime + traveltime;
337 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
344 SUB_CalcAngleMove (destangle, tspeed, func);
353 unused but required by the engine
367 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
368 Additionally it moves players back into the past before the trace and restores them afterward.
371 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
376 // check whether antilagged traces are enabled
379 if (clienttype(forent) != CLIENTTYPE_REAL)
380 lag = 0; // only antilag for clients
382 // change shooter to SOLID_BBOX so the shot can hit corpses
385 oldsolid = source.dphitcontentsmask;
386 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
391 // take players back into the past
392 FOR_EACH_PLAYER(player)
394 antilag_takeback(player, time - lag);
399 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
401 tracebox (v1, mi, ma, v2, nomonst, forent);
403 // restore players to current positions
406 FOR_EACH_PLAYER(player)
408 antilag_restore(player);
411 // restore shooter solid type
413 source.dphitcontentsmask = oldsolid;
415 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
417 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
419 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
421 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
423 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
425 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
427 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
429 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
431 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
433 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
435 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
437 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
439 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
441 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
443 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
445 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
448 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
453 //nudge = 2 * cvar("collision_impactnudge"); // why not?
456 dir = normalize(v2 - v1);
458 pos = v1 + dir * nudge;
465 if((pos - v1) * dir >= (v2 - v1) * dir)
473 tracebox(pos, mi, ma, v2, nomonsters, forent);
478 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
479 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
480 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
481 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
486 // we started inside solid.
487 // then trace from endpos to pos
489 tracebox(t, mi, ma, pos, nomonsters, forent);
493 // t is still inside solid? bad
494 // force advance, then, and retry
495 pos = t + dir * nudge;
499 // we actually LEFT solid!
500 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
506 // pos is outside solid?!? but why?!? never mind, just return it.
508 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
514 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
520 //nudge = 2 * cvar("collision_impactnudge"); // why not?
523 dir = normalize(v2 - v1);
525 pos = v1 + dir * nudge;
529 if((pos - v1) * dir >= (v2 - v1) * dir)
536 traceline(pos, v2, nomonsters, forent);
540 // we started inside solid.
541 // then trace from endpos to pos
543 traceline(t, pos, nomonsters, forent);
546 // t is inside solid? bad
547 // force advance, then
548 pos = pos + dir * nudge;
552 // we actually LEFT solid!
553 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
559 // pos is outside solid?!? but why?!? never mind, just return it.
561 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
566 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
573 Returns a point at least 12 units away from walls
574 (useful for explosion animations, although the blast is performed where it really happened)
578 vector findbetterlocation (vector org, float mindist)
584 vec = mindist * '1 0 0';
588 traceline (org, org + vec, TRUE, world);
590 if (trace_fraction < 1)
593 traceline (loc, loc + vec, TRUE, world);
594 if (trace_fraction >= 1)
614 Returns a random number between -1.0 and 1.0
619 return 2 * (random () - 0.5);
624 Angc used for animations
629 float angc (float a1, float a2)
656 .float lodmodelindex0;
657 .float lodmodelindex1;
658 .float lodmodelindex2;
662 float LOD_customize()
666 if(autocvar_loddebug)
668 d = autocvar_loddebug;
670 self.modelindex = self.lodmodelindex0;
671 else if(d == 2 || !self.lodmodelindex2)
672 self.modelindex = self.lodmodelindex1;
674 self.modelindex = self.lodmodelindex2;
678 // TODO csqc network this so it only gets sent once
679 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
680 if(d < self.loddistance1)
681 self.modelindex = self.lodmodelindex0;
682 else if(!self.lodmodelindex2 || d < self.loddistance2)
683 self.modelindex = self.lodmodelindex1;
685 self.modelindex = self.lodmodelindex2;
690 void LOD_uncustomize()
692 self.modelindex = self.lodmodelindex0;
695 void LODmodel_attach()
699 if(!self.loddistance1)
700 self.loddistance1 = 1000;
701 if(!self.loddistance2)
702 self.loddistance2 = 2000;
703 self.lodmodelindex0 = self.modelindex;
705 if(self.lodtarget1 != "")
707 e = find(world, targetname, self.lodtarget1);
710 self.lodmodel1 = e.model;
714 if(self.lodtarget2 != "")
716 e = find(world, targetname, self.lodtarget2);
719 self.lodmodel2 = e.model;
724 if(autocvar_loddebug < 0)
726 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
729 if(self.lodmodel1 != "")
735 precache_model(self.lodmodel1);
736 setmodel(self, self.lodmodel1);
737 self.lodmodelindex1 = self.modelindex;
739 if(self.lodmodel2 != "")
741 precache_model(self.lodmodel2);
742 setmodel(self, self.lodmodel2);
743 self.lodmodelindex2 = self.modelindex;
746 self.modelindex = self.lodmodelindex0;
747 setsize(self, mi, ma);
750 if(self.lodmodelindex1)
751 if not(self.SendEntity)
752 SetCustomizer(self, LOD_customize, LOD_uncustomize);
755 void ApplyMinMaxScaleAngles(entity e)
757 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
759 e.maxs = '1 1 1' * vlen(
760 '1 0 0' * max(-e.mins_x, e.maxs_x) +
761 '0 1 0' * max(-e.mins_y, e.maxs_y) +
762 '0 0 1' * max(-e.mins_z, e.maxs_z)
766 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
769 '1 0 0' * max(-e.mins_x, e.maxs_x) +
770 '0 1 0' * max(-e.mins_y, e.maxs_y)
773 e.mins_x = -e.maxs_x;
774 e.mins_y = -e.maxs_x;
777 setsize(e, e.mins * e.scale, e.maxs * e.scale);
779 setsize(e, e.mins, e.maxs);
782 void SetBrushEntityModel()
786 precache_model(self.model);
787 setmodel(self, self.model); // no precision needed
788 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
790 setorigin(self, self.origin);
791 ApplyMinMaxScaleAngles(self);
794 void SetBrushEntityModelNoLOD()
798 precache_model(self.model);
799 setmodel(self, self.model); // no precision needed
801 setorigin(self, self.origin);
802 ApplyMinMaxScaleAngles(self);
813 if (self.movedir != '0 0 0')
814 self.movedir = normalize(self.movedir);
817 makevectors (self.angles);
818 self.movedir = v_forward;
821 self.angles = '0 0 0';
826 // trigger angles are used for one-way touches. An angle of 0 is assumed
827 // to mean no restrictions, so use a yaw of 360 instead.
829 self.solid = SOLID_TRIGGER;
830 SetBrushEntityModel();
831 self.movetype = MOVETYPE_NONE;
836 void InitSolidBSPTrigger()
838 // trigger angles are used for one-way touches. An angle of 0 is assumed
839 // to mean no restrictions, so use a yaw of 360 instead.
841 self.solid = SOLID_BSP;
842 SetBrushEntityModel();
843 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
844 // self.modelindex = 0;
848 float InitMovingBrushTrigger()
850 // trigger angles are used for one-way touches. An angle of 0 is assumed
851 // to mean no restrictions, so use a yaw of 360 instead.
852 self.solid = SOLID_BSP;
853 SetBrushEntityModel();
854 self.movetype = MOVETYPE_PUSH;
855 if(self.modelindex == 0)
857 objerror("InitMovingBrushTrigger: no brushes found!");