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)
395 antilag_takeback(player, time - lag);
397 antilag_takeback(player.vehicle, time - lag);
404 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
406 tracebox (v1, mi, ma, v2, nomonst, forent);
408 // restore players to current positions
411 FOR_EACH_PLAYER(player)
414 antilag_restore(player);
416 antilag_restore(player.vehicle);
421 // restore shooter solid type
423 source.dphitcontentsmask = oldsolid;
425 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
427 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
429 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
431 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
433 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
435 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
437 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
439 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
441 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
443 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
445 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
447 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
449 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
451 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
453 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
455 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
458 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
463 //nudge = 2 * cvar("collision_impactnudge"); // why not?
466 dir = normalize(v2 - v1);
468 pos = v1 + dir * nudge;
475 if((pos - v1) * dir >= (v2 - v1) * dir)
483 tracebox(pos, mi, ma, v2, nomonsters, forent);
488 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
489 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
490 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
491 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
496 // we started inside solid.
497 // then trace from endpos to pos
499 tracebox(t, mi, ma, pos, nomonsters, forent);
503 // t is still inside solid? bad
504 // force advance, then, and retry
505 pos = t + dir * nudge;
509 // we actually LEFT solid!
510 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
516 // pos is outside solid?!? but why?!? never mind, just return it.
518 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
524 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
530 //nudge = 2 * cvar("collision_impactnudge"); // why not?
533 dir = normalize(v2 - v1);
535 pos = v1 + dir * nudge;
539 if((pos - v1) * dir >= (v2 - v1) * dir)
546 traceline(pos, v2, nomonsters, forent);
550 // we started inside solid.
551 // then trace from endpos to pos
553 traceline(t, pos, nomonsters, forent);
556 // t is inside solid? bad
557 // force advance, then
558 pos = pos + dir * nudge;
562 // we actually LEFT solid!
563 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
569 // pos is outside solid?!? but why?!? never mind, just return it.
571 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
576 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
583 Returns a point at least 12 units away from walls
584 (useful for explosion animations, although the blast is performed where it really happened)
588 vector findbetterlocation (vector org, float mindist)
594 vec = mindist * '1 0 0';
598 traceline (org, org + vec, TRUE, world);
600 if (trace_fraction < 1)
603 traceline (loc, loc + vec, TRUE, world);
604 if (trace_fraction >= 1)
624 Returns a random number between -1.0 and 1.0
629 return 2 * (random () - 0.5);
634 Angc used for animations
639 float angc (float a1, float a2)
666 .float lodmodelindex0;
667 .float lodmodelindex1;
668 .float lodmodelindex2;
672 float LOD_customize()
676 if(autocvar_loddebug)
678 d = autocvar_loddebug;
680 self.modelindex = self.lodmodelindex0;
681 else if(d == 2 || !self.lodmodelindex2)
682 self.modelindex = self.lodmodelindex1;
684 self.modelindex = self.lodmodelindex2;
688 // TODO csqc network this so it only gets sent once
689 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
690 if(d < self.loddistance1)
691 self.modelindex = self.lodmodelindex0;
692 else if(!self.lodmodelindex2 || d < self.loddistance2)
693 self.modelindex = self.lodmodelindex1;
695 self.modelindex = self.lodmodelindex2;
700 void LOD_uncustomize()
702 self.modelindex = self.lodmodelindex0;
705 void LODmodel_attach()
709 if(!self.loddistance1)
710 self.loddistance1 = 1000;
711 if(!self.loddistance2)
712 self.loddistance2 = 2000;
713 self.lodmodelindex0 = self.modelindex;
715 if(self.lodtarget1 != "")
717 e = find(world, targetname, self.lodtarget1);
720 self.lodmodel1 = e.model;
724 if(self.lodtarget2 != "")
726 e = find(world, targetname, self.lodtarget2);
729 self.lodmodel2 = e.model;
734 if(autocvar_loddebug < 0)
736 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
739 if(self.lodmodel1 != "")
745 precache_model(self.lodmodel1);
746 setmodel(self, self.lodmodel1);
747 self.lodmodelindex1 = self.modelindex;
749 if(self.lodmodel2 != "")
751 precache_model(self.lodmodel2);
752 setmodel(self, self.lodmodel2);
753 self.lodmodelindex2 = self.modelindex;
756 self.modelindex = self.lodmodelindex0;
757 setsize(self, mi, ma);
760 if(self.lodmodelindex1)
761 if not(self.SendEntity)
762 SetCustomizer(self, LOD_customize, LOD_uncustomize);
765 void ApplyMinMaxScaleAngles(entity e)
767 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
769 e.maxs = '1 1 1' * vlen(
770 '1 0 0' * max(-e.mins_x, e.maxs_x) +
771 '0 1 0' * max(-e.mins_y, e.maxs_y) +
772 '0 0 1' * max(-e.mins_z, e.maxs_z)
776 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
779 '1 0 0' * max(-e.mins_x, e.maxs_x) +
780 '0 1 0' * max(-e.mins_y, e.maxs_y)
783 e.mins_x = -e.maxs_x;
784 e.mins_y = -e.maxs_x;
787 setsize(e, e.mins * e.scale, e.maxs * e.scale);
789 setsize(e, e.mins, e.maxs);
792 void SetBrushEntityModel()
796 precache_model(self.model);
797 setmodel(self, self.model); // no precision needed
798 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
800 setorigin(self, self.origin);
801 ApplyMinMaxScaleAngles(self);
804 void SetBrushEntityModelNoLOD()
808 precache_model(self.model);
809 setmodel(self, self.model); // no precision needed
811 setorigin(self, self.origin);
812 ApplyMinMaxScaleAngles(self);
823 if (self.movedir != '0 0 0')
824 self.movedir = normalize(self.movedir);
827 makevectors (self.angles);
828 self.movedir = v_forward;
831 self.angles = '0 0 0';
836 // trigger angles are used for one-way touches. An angle of 0 is assumed
837 // to mean no restrictions, so use a yaw of 360 instead.
839 self.solid = SOLID_TRIGGER;
840 SetBrushEntityModel();
841 self.movetype = MOVETYPE_NONE;
846 void InitSolidBSPTrigger()
848 // trigger angles are used for one-way touches. An angle of 0 is assumed
849 // to mean no restrictions, so use a yaw of 360 instead.
851 self.solid = SOLID_BSP;
852 SetBrushEntityModel();
853 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
854 // self.modelindex = 0;
858 float InitMovingBrushTrigger()
860 // trigger angles are used for one-way touches. An angle of 0 is assumed
861 // to mean no restrictions, so use a yaw of 360 instead.
862 self.solid = SOLID_BSP;
863 SetBrushEntityModel();
864 self.movetype = MOVETYPE_PUSH;
865 if(self.modelindex == 0)
867 objerror("InitMovingBrushTrigger: no brushes found!");