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)
19 if (anim_x == e.animstate_startframe)
20 if (anim_y == e.animstate_numframes)
21 if (anim_z == e.animstate_framerate)
26 if(anim_y == 1) // ZYM animation
27 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
32 e.animstate_startframe = anim_x;
33 e.animstate_numframes = anim_y;
34 e.animstate_framerate = anim_z;
35 e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
36 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
37 e.animstate_looping = looping;
38 e.animstate_override = override;
39 e.frame = e.animstate_startframe;
40 e.frame1time = servertime;
43 void updateanim(entity e)
45 if (time >= e.animstate_endtime)
47 if (e.animstate_looping)
49 e.animstate_starttime = e.animstate_endtime;
50 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
52 e.animstate_override = FALSE;
54 e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
55 //print(ftos(time), " -> ", ftos(e.frame), "\n");
59 vector animparseline(float animfile)
66 line = fgets(animfile);
67 c = tokenize_console(line);
70 animparseerror = TRUE;
73 anim_x = stof(argv(0));
74 anim_y = stof(argv(1));
75 anim_z = stof(argv(2));
76 // don't allow completely bogus values
77 if (anim_x < 0 || anim_y < 1 || anim_z < 0.001)
89 void SUB_Remove (void)
98 Applies some friction to self
102 void SUB_Friction (void)
104 self.nextthink = time;
105 if(self.flags & FL_ONGROUND)
106 self.velocity = self.velocity * (1 - frametime * self.friction);
113 Makes client invisible or removes non-client
116 void SUB_VanishOrRemove (entity ent)
118 if (ent.flags & FL_CLIENT)
133 void SUB_SetFade_Think (void)
135 self.think = SUB_SetFade_Think;
136 self.nextthink = self.fade_time;
137 self.alpha = 1 - (time - self.fade_time) * self.fade_rate;
138 if (self.alpha < 0.01)
139 SUB_VanishOrRemove(self);
140 self.alpha = bound(0.01, self.alpha, 1);
147 Fade 'ent' out when time >= 'when'
150 void SUB_SetFade (entity ent, float when, float fadetime)
152 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
155 ent.fade_rate = 1/fadetime;
156 ent.fade_time = when;
157 ent.think = SUB_SetFade_Think;
158 ent.nextthink = when;
165 calculate self.velocity and self.nextthink to reach dest from
166 self.origin traveling at speed
169 void SUB_CalcMoveDone (void)
171 // After moving, set origin to exact final destination
173 setorigin (self, self.finaldest);
174 self.velocity = '0 0 0';
180 void SUB_CalcMove_controller_think (void)
189 if(time < self.animstate_endtime) {
190 delta = self.destvec;
191 nexttick = time + sys_frametime;
193 if(nexttick < self.animstate_endtime) {
194 traveltime = self.animstate_endtime - self.animstate_starttime;
195 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
196 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
197 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
198 phasepos = phasepos + 1; // correct range to [0, 2]
199 phasepos = phasepos / 2; // correct range to [0, 1]
200 nextpos = self.origin + (delta * phasepos);
202 veloc = nextpos - self.owner.origin;
203 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
206 veloc = self.finaldest - self.owner.origin;
207 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
209 self.owner.velocity = veloc;
210 self.nextthink = nexttick;
213 self.owner.think = self.think1;
220 void SUB_CalcMove (vector tdest, float tspeed, void() func)
227 objerror ("No speed is defined!");
230 self.finaldest = tdest;
231 self.think = SUB_CalcMoveDone;
233 if (tdest == self.origin)
235 self.velocity = '0 0 0';
236 self.nextthink = self.ltime + 0.1;
240 delta = tdest - self.origin;
241 traveltime = vlen (delta) / tspeed;
243 if (traveltime < 0.1)
245 self.velocity = '0 0 0';
246 self.nextthink = self.ltime + 0.1;
250 // Very short animations don't really show off the effect
251 // of controlled animation, so let's just use linear movement.
252 // Alternatively entities can choose to specify non-controlled movement.
253 // The default, controlled movement, is value 0, the only currently
254 // implemented alternative movement is linear, which is any other value.
255 if (traveltime < 0.15 || self.platmovetype)
257 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
258 self.nextthink = self.ltime + traveltime;
262 controller = spawn();
263 controller.classname = "SUB_CalcMove_controller";
264 controller.owner = self;
265 controller.origin = self.origin; // starting point
266 controller.finaldest = (tdest + '0 0 1'); // where do we want to end? Offset to overshoot a bit.
267 controller.destvec = delta;
268 controller.animstate_starttime = time;
269 controller.animstate_endtime = time + traveltime;
270 controller.think = SUB_CalcMove_controller_think;
271 controller.think1 = self.think;
273 // the thinking is now done by the controller
274 self.think = SUB_Null;
275 self.nextthink = self.ltime + traveltime;
283 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
290 SUB_CalcMove (tdest, tspeed, func);
299 calculate self.avelocity and self.nextthink to reach destangle from
302 The calling function should make sure self.think is valid
305 void SUB_CalcAngleMoveDone (void)
307 // After rotating, set angle to exact final angle
308 self.angles = self.finalangle;
309 self.avelocity = '0 0 0';
315 // FIXME: I fixed this function only for rotation around the main axes
316 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
322 objerror ("No speed is defined!");
324 // take the shortest distance for the angles
325 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
326 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
327 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
328 delta = destangle - self.angles;
329 traveltime = vlen (delta) / tspeed;
332 self.finalangle = destangle;
333 self.think = SUB_CalcAngleMoveDone;
335 if (traveltime < 0.1)
337 self.avelocity = '0 0 0';
338 self.nextthink = self.ltime + 0.1;
342 self.avelocity = delta * (1 / traveltime);
343 self.nextthink = self.ltime + traveltime;
346 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
353 SUB_CalcAngleMove (destangle, tspeed, func);
362 unused but required by the engine
376 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
377 Additionally it moves players back into the past before the trace and restores them afterward.
380 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
383 local float oldsolid;
385 // check whether antilagged traces are enabled
388 if (clienttype(forent) != CLIENTTYPE_REAL)
389 lag = 0; // only antilag for clients
391 // change shooter to SOLID_BBOX so the shot can hit corpses
394 oldsolid = source.dphitcontentsmask;
395 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
400 // take players back into the past
401 player = player_list;
404 antilag_takeback(player, time - lag);
405 player = player.nextplayer;
411 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
413 tracebox (v1, mi, ma, v2, nomonst, forent);
415 // restore players to current positions
418 player = player_list;
421 antilag_restore(player);
422 player = player.nextplayer;
426 // restore shooter solid type
428 source.dphitcontentsmask = oldsolid;
430 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
432 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
434 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
436 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
438 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
440 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
442 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
444 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
446 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
448 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
450 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
452 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
454 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
456 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
458 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
460 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
463 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
468 //nudge = 2 * cvar("collision_impactnudge"); // why not?
471 dir = normalize(v2 - v1);
473 pos = v1 + dir * nudge;
480 if((pos - v1) * dir >= (v2 - v1) * dir)
488 tracebox(pos, mi, ma, v2, nomonsters, forent);
493 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
494 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
495 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
496 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
501 // we started inside solid.
502 // then trace from endpos to pos
504 tracebox(t, mi, ma, pos, nomonsters, forent);
508 // t is still inside solid? bad
509 // force advance, then, and retry
510 pos = t + dir * nudge;
514 // we actually LEFT solid!
515 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
521 // pos is outside solid?!? but why?!? never mind, just return it.
523 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
529 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
535 //nudge = 2 * cvar("collision_impactnudge"); // why not?
538 dir = normalize(v2 - v1);
540 pos = v1 + dir * nudge;
544 if((pos - v1) * dir >= (v2 - v1) * dir)
551 traceline(pos, v2, nomonsters, forent);
555 // we started inside solid.
556 // then trace from endpos to pos
558 traceline(t, pos, nomonsters, forent);
561 // t is inside solid? bad
562 // force advance, then
563 pos = pos + dir * nudge;
567 // we actually LEFT solid!
568 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
574 // pos is outside solid?!? but why?!? never mind, just return it.
576 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
581 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
588 Returns a point at least 12 units away from walls
589 (useful for explosion animations, although the blast is performed where it really happened)
593 vector findbetterlocation (vector org, float mindist)
599 vec = mindist * '1 0 0';
603 traceline (org, org + vec, TRUE, world);
605 if (trace_fraction < 1)
608 traceline (loc, loc + vec, TRUE, world);
609 if (trace_fraction >= 1)
629 Returns a random number between -1.0 and 1.0
634 return 2 * (random () - 0.5);
639 Angc used for animations
644 float angc (float a1, float a2)
671 .float lodmodelindex0;
672 .float lodmodelindex1;
673 .float lodmodelindex2;
677 float LOD_customize()
681 if(autocvar_loddebug)
683 d = autocvar_loddebug;
685 self.modelindex = self.lodmodelindex0;
686 else if(d == 2 || !self.lodmodelindex2)
687 self.modelindex = self.lodmodelindex1;
689 self.modelindex = self.lodmodelindex2;
693 // TODO csqc network this so it only gets sent once
694 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
695 if(d < self.loddistance1)
696 self.modelindex = self.lodmodelindex0;
697 else if(!self.lodmodelindex2 || d < self.loddistance2)
698 self.modelindex = self.lodmodelindex1;
700 self.modelindex = self.lodmodelindex2;
705 void LOD_uncustomize()
707 self.modelindex = self.lodmodelindex0;
710 void LODmodel_attach()
714 if(!self.loddistance1)
715 self.loddistance1 = 1000;
716 if(!self.loddistance2)
717 self.loddistance2 = 2000;
718 self.lodmodelindex0 = self.modelindex;
720 if(self.lodtarget1 != "")
722 e = find(world, targetname, self.lodtarget1);
725 self.lodmodel1 = e.model;
729 if(self.lodtarget2 != "")
731 e = find(world, targetname, self.lodtarget2);
734 self.lodmodel2 = e.model;
739 if(autocvar_loddebug < 0)
741 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
744 if(self.lodmodel1 != "")
750 precache_model(self.lodmodel1);
751 setmodel(self, self.lodmodel1);
752 self.lodmodelindex1 = self.modelindex;
754 if(self.lodmodel2 != "")
756 precache_model(self.lodmodel2);
757 setmodel(self, self.lodmodel2);
758 self.lodmodelindex2 = self.modelindex;
761 self.modelindex = self.lodmodelindex0;
762 setsize(self, mi, ma);
765 if(self.lodmodelindex1)
766 if not(self.SendEntity)
767 SetCustomizer(self, LOD_customize, LOD_uncustomize);
770 void ApplyMinMaxScaleAngles(entity e)
772 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
774 e.maxs = '1 1 1' * vlen(
775 '1 0 0' * max(-e.mins_x, e.maxs_x) +
776 '0 1 0' * max(-e.mins_y, e.maxs_y) +
777 '0 0 1' * max(-e.mins_z, e.maxs_z)
781 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
784 '1 0 0' * max(-e.mins_x, e.maxs_x) +
785 '0 1 0' * max(-e.mins_y, e.maxs_y)
788 e.mins_x = -e.maxs_x;
789 e.mins_y = -e.maxs_x;
792 setsize(e, e.mins * e.scale, e.maxs * e.scale);
794 setsize(e, e.mins, e.maxs);
797 void SetBrushEntityModel()
801 precache_model(self.model);
802 setmodel(self, self.model); // no precision needed
803 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
805 setorigin(self, self.origin);
806 ApplyMinMaxScaleAngles(self);
809 void SetBrushEntityModelNoLOD()
813 precache_model(self.model);
814 setmodel(self, self.model); // no precision needed
816 setorigin(self, self.origin);
817 ApplyMinMaxScaleAngles(self);
828 if (self.movedir != '0 0 0')
829 self.movedir = normalize(self.movedir);
832 makevectors (self.angles);
833 self.movedir = v_forward;
836 self.angles = '0 0 0';
841 // trigger angles are used for one-way touches. An angle of 0 is assumed
842 // to mean no restrictions, so use a yaw of 360 instead.
844 self.solid = SOLID_TRIGGER;
845 SetBrushEntityModel();
846 self.movetype = MOVETYPE_NONE;
851 void InitSolidBSPTrigger()
853 // trigger angles are used for one-way touches. An angle of 0 is assumed
854 // to mean no restrictions, so use a yaw of 360 instead.
856 self.solid = SOLID_BSP;
857 SetBrushEntityModel();
858 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
859 // self.modelindex = 0;
863 float InitMovingBrushTrigger()
865 // trigger angles are used for one-way touches. An angle of 0 is assumed
866 // to mean no restrictions, so use a yaw of 360 instead.
867 self.solid = SOLID_BSP;
868 SetBrushEntityModel();
869 self.movetype = MOVETYPE_PUSH;
870 if(self.modelindex == 0)
872 objerror("InitMovingBrushTrigger: no brushes found!");