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 only currently implemented alternative movement is linear (value 1)
254 if (traveltime < 0.15 || self.platmovetype == 1)
256 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
257 self.nextthink = self.ltime + traveltime;
261 controller = spawn();
262 controller.classname = "SUB_CalcMove_controller";
263 controller.owner = self;
264 controller.origin = self.origin; // starting point
265 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
266 controller.destvec = delta;
267 controller.animstate_starttime = time;
268 controller.animstate_endtime = time + traveltime;
269 controller.think = SUB_CalcMove_controller_think;
270 controller.think1 = self.think;
272 // the thinking is now done by the controller
273 self.think = SUB_Null;
274 self.nextthink = self.ltime + traveltime;
282 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
289 SUB_CalcMove (tdest, tspeed, func);
298 calculate self.avelocity and self.nextthink to reach destangle from
301 The calling function should make sure self.think is valid
304 void SUB_CalcAngleMoveDone (void)
306 // After rotating, set angle to exact final angle
307 self.angles = self.finalangle;
308 self.avelocity = '0 0 0';
314 // FIXME: I fixed this function only for rotation around the main axes
315 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
321 objerror ("No speed is defined!");
323 // take the shortest distance for the angles
324 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
325 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
326 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
327 delta = destangle - self.angles;
328 traveltime = vlen (delta) / tspeed;
331 self.finalangle = destangle;
332 self.think = SUB_CalcAngleMoveDone;
334 if (traveltime < 0.1)
336 self.avelocity = '0 0 0';
337 self.nextthink = self.ltime + 0.1;
341 self.avelocity = delta * (1 / traveltime);
342 self.nextthink = self.ltime + traveltime;
345 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
352 SUB_CalcAngleMove (destangle, tspeed, func);
361 unused but required by the engine
375 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
376 Additionally it moves players back into the past before the trace and restores them afterward.
379 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
382 local float oldsolid;
384 // check whether antilagged traces are enabled
387 if (clienttype(forent) != CLIENTTYPE_REAL)
388 lag = 0; // only antilag for clients
390 // change shooter to SOLID_BBOX so the shot can hit corpses
393 oldsolid = source.dphitcontentsmask;
394 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
399 // take players back into the past
400 player = player_list;
403 antilag_takeback(player, time - lag);
404 player = player.nextplayer;
410 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
412 tracebox (v1, mi, ma, v2, nomonst, forent);
414 // restore players to current positions
417 player = player_list;
420 antilag_restore(player);
421 player = player.nextplayer;
425 // restore shooter solid type
427 source.dphitcontentsmask = oldsolid;
429 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
431 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
433 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
435 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
437 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
439 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
441 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
443 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
445 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
447 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
449 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
451 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
453 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
455 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
457 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
459 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
462 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
467 //nudge = 2 * cvar("collision_impactnudge"); // why not?
470 dir = normalize(v2 - v1);
472 pos = v1 + dir * nudge;
479 if((pos - v1) * dir >= (v2 - v1) * dir)
487 tracebox(pos, mi, ma, v2, nomonsters, forent);
492 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
493 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
494 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
495 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
500 // we started inside solid.
501 // then trace from endpos to pos
503 tracebox(t, mi, ma, pos, nomonsters, forent);
507 // t is still inside solid? bad
508 // force advance, then, and retry
509 pos = t + dir * nudge;
513 // we actually LEFT solid!
514 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
520 // pos is outside solid?!? but why?!? never mind, just return it.
522 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
528 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
534 //nudge = 2 * cvar("collision_impactnudge"); // why not?
537 dir = normalize(v2 - v1);
539 pos = v1 + dir * nudge;
543 if((pos - v1) * dir >= (v2 - v1) * dir)
550 traceline(pos, v2, nomonsters, forent);
554 // we started inside solid.
555 // then trace from endpos to pos
557 traceline(t, pos, nomonsters, forent);
560 // t is inside solid? bad
561 // force advance, then
562 pos = pos + dir * nudge;
566 // we actually LEFT solid!
567 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
573 // pos is outside solid?!? but why?!? never mind, just return it.
575 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
580 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
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 setmodel(self, self.model); // no precision needed
802 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
804 setorigin(self, self.origin);
805 ApplyMinMaxScaleAngles(self);
808 void SetBrushEntityModelNoLOD()
812 precache_model(self.model);
813 setmodel(self, self.model); // no precision needed
815 setorigin(self, self.origin);
816 ApplyMinMaxScaleAngles(self);
827 if (self.movedir != '0 0 0')
828 self.movedir = normalize(self.movedir);
831 makevectors (self.angles);
832 self.movedir = v_forward;
835 self.angles = '0 0 0';
840 // trigger angles are used for one-way touches. An angle of 0 is assumed
841 // to mean no restrictions, so use a yaw of 360 instead.
843 self.solid = SOLID_TRIGGER;
844 SetBrushEntityModel();
845 self.movetype = MOVETYPE_NONE;
850 void InitSolidBSPTrigger()
852 // trigger angles are used for one-way touches. An angle of 0 is assumed
853 // to mean no restrictions, so use a yaw of 360 instead.
855 self.solid = SOLID_BSP;
856 SetBrushEntityModel();
857 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
858 // self.modelindex = 0;
862 float InitMovingBrushTrigger()
864 // trigger angles are used for one-way touches. An angle of 0 is assumed
865 // to mean no restrictions, so use a yaw of 360 instead.
866 self.solid = SOLID_BSP;
867 SetBrushEntityModel();
868 self.movetype = MOVETYPE_PUSH;
869 if(self.modelindex == 0)
871 objerror("InitMovingBrushTrigger: no brushes found!");