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 player = player_list;
395 antilag_takeback(player, time - lag);
396 player = player.nextplayer;
402 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
404 tracebox (v1, mi, ma, v2, nomonst, forent);
406 // restore players to current positions
409 player = player_list;
412 antilag_restore(player);
413 player = player.nextplayer;
417 // restore shooter solid type
419 source.dphitcontentsmask = oldsolid;
421 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
423 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
425 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
427 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
429 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
431 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
433 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
435 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
437 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
439 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
441 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
443 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
445 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
447 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
449 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
451 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
454 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
459 //nudge = 2 * cvar("collision_impactnudge"); // why not?
462 dir = normalize(v2 - v1);
464 pos = v1 + dir * nudge;
471 if((pos - v1) * dir >= (v2 - v1) * dir)
479 tracebox(pos, mi, ma, v2, nomonsters, forent);
484 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
485 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
486 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
487 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
492 // we started inside solid.
493 // then trace from endpos to pos
495 tracebox(t, mi, ma, pos, nomonsters, forent);
499 // t is still inside solid? bad
500 // force advance, then, and retry
501 pos = t + dir * nudge;
505 // we actually LEFT solid!
506 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
512 // pos is outside solid?!? but why?!? never mind, just return it.
514 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
520 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
526 //nudge = 2 * cvar("collision_impactnudge"); // why not?
529 dir = normalize(v2 - v1);
531 pos = v1 + dir * nudge;
535 if((pos - v1) * dir >= (v2 - v1) * dir)
542 traceline(pos, v2, nomonsters, forent);
546 // we started inside solid.
547 // then trace from endpos to pos
549 traceline(t, pos, nomonsters, forent);
552 // t is inside solid? bad
553 // force advance, then
554 pos = pos + dir * nudge;
558 // we actually LEFT solid!
559 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
565 // pos is outside solid?!? but why?!? never mind, just return it.
567 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
572 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
579 Returns a point at least 12 units away from walls
580 (useful for explosion animations, although the blast is performed where it really happened)
584 vector findbetterlocation (vector org, float mindist)
590 vec = mindist * '1 0 0';
594 traceline (org, org + vec, TRUE, world);
596 if (trace_fraction < 1)
599 traceline (loc, loc + vec, TRUE, world);
600 if (trace_fraction >= 1)
620 Returns a random number between -1.0 and 1.0
625 return 2 * (random () - 0.5);
630 Angc used for animations
635 float angc (float a1, float a2)
662 .float lodmodelindex0;
663 .float lodmodelindex1;
664 .float lodmodelindex2;
668 float LOD_customize()
672 if(autocvar_loddebug)
674 d = autocvar_loddebug;
676 self.modelindex = self.lodmodelindex0;
677 else if(d == 2 || !self.lodmodelindex2)
678 self.modelindex = self.lodmodelindex1;
680 self.modelindex = self.lodmodelindex2;
684 // TODO csqc network this so it only gets sent once
685 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
686 if(d < self.loddistance1)
687 self.modelindex = self.lodmodelindex0;
688 else if(!self.lodmodelindex2 || d < self.loddistance2)
689 self.modelindex = self.lodmodelindex1;
691 self.modelindex = self.lodmodelindex2;
696 void LOD_uncustomize()
698 self.modelindex = self.lodmodelindex0;
701 void LODmodel_attach()
705 if(!self.loddistance1)
706 self.loddistance1 = 1000;
707 if(!self.loddistance2)
708 self.loddistance2 = 2000;
709 self.lodmodelindex0 = self.modelindex;
711 if(self.lodtarget1 != "")
713 e = find(world, targetname, self.lodtarget1);
716 self.lodmodel1 = e.model;
720 if(self.lodtarget2 != "")
722 e = find(world, targetname, self.lodtarget2);
725 self.lodmodel2 = e.model;
730 if(autocvar_loddebug < 0)
732 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
735 if(self.lodmodel1 != "")
741 precache_model(self.lodmodel1);
742 setmodel(self, self.lodmodel1);
743 self.lodmodelindex1 = self.modelindex;
745 if(self.lodmodel2 != "")
747 precache_model(self.lodmodel2);
748 setmodel(self, self.lodmodel2);
749 self.lodmodelindex2 = self.modelindex;
752 self.modelindex = self.lodmodelindex0;
753 setsize(self, mi, ma);
756 if(self.lodmodelindex1)
757 if not(self.SendEntity)
758 SetCustomizer(self, LOD_customize, LOD_uncustomize);
761 void ApplyMinMaxScaleAngles(entity e)
763 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
765 e.maxs = '1 1 1' * vlen(
766 '1 0 0' * max(-e.mins_x, e.maxs_x) +
767 '0 1 0' * max(-e.mins_y, e.maxs_y) +
768 '0 0 1' * max(-e.mins_z, e.maxs_z)
772 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
775 '1 0 0' * max(-e.mins_x, e.maxs_x) +
776 '0 1 0' * max(-e.mins_y, e.maxs_y)
779 e.mins_x = -e.maxs_x;
780 e.mins_y = -e.maxs_x;
783 setsize(e, e.mins * e.scale, e.maxs * e.scale);
785 setsize(e, e.mins, e.maxs);
788 void SetBrushEntityModel()
792 precache_model(self.model);
793 setmodel(self, self.model); // no precision needed
794 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
796 setorigin(self, self.origin);
797 ApplyMinMaxScaleAngles(self);
800 void SetBrushEntityModelNoLOD()
804 precache_model(self.model);
805 setmodel(self, self.model); // no precision needed
807 setorigin(self, self.origin);
808 ApplyMinMaxScaleAngles(self);
819 if (self.movedir != '0 0 0')
820 self.movedir = normalize(self.movedir);
823 makevectors (self.angles);
824 self.movedir = v_forward;
827 self.angles = '0 0 0';
832 // trigger angles are used for one-way touches. An angle of 0 is assumed
833 // to mean no restrictions, so use a yaw of 360 instead.
835 self.solid = SOLID_TRIGGER;
836 SetBrushEntityModel();
837 self.movetype = MOVETYPE_NONE;
842 void InitSolidBSPTrigger()
844 // trigger angles are used for one-way touches. An angle of 0 is assumed
845 // to mean no restrictions, so use a yaw of 360 instead.
847 self.solid = SOLID_BSP;
848 SetBrushEntityModel();
849 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
850 // self.modelindex = 0;
854 float InitMovingBrushTrigger()
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.
858 self.solid = SOLID_BSP;
859 SetBrushEntityModel();
860 self.movetype = MOVETYPE_PUSH;
861 if(self.modelindex == 0)
863 objerror("InitMovingBrushTrigger: no brushes found!");