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)
129 self.think = SUB_SetFade_Think;
130 self.nextthink = time;
131 self.alpha -= frametime * self.fade_rate;
132 if (self.alpha < 0.01)
133 SUB_VanishOrRemove(self);
135 self.nextthink = time;
142 Fade 'ent' out when time >= 'when'
145 void SUB_SetFade (entity ent, float when, float fadetime)
147 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
150 ent.fade_rate = 1/fadetime;
151 ent.think = SUB_SetFade_Think;
152 ent.nextthink = when;
159 calculate self.velocity and self.nextthink to reach dest from
160 self.origin traveling at speed
163 void SUB_CalcMoveDone (void)
165 // After moving, set origin to exact final destination
167 setorigin (self, self.finaldest);
168 self.velocity = '0 0 0';
175 void SUB_CalcMove_controller_think (void)
185 delta = self.destvec;
186 delta2 = self.destvec2;
187 if(time < self.animstate_endtime) {
188 nexttick = time + sys_frametime;
190 traveltime = self.animstate_endtime - self.animstate_starttime;
191 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
192 if(self.platmovetype != 1)
194 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
195 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
196 phasepos = phasepos + 1; // correct range to [0, 2]
197 phasepos = phasepos / 2; // correct range to [0, 1]
199 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
200 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
202 if(nexttick < self.animstate_endtime) {
203 veloc = nextpos - self.owner.origin;
204 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 if(self.owner.bezier_turn)
211 self.owner.angles = vectoangles(delta + 2 * delta2 * phasepos);
212 self.nextthink = nexttick;
214 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
216 self.owner.think = self.think1;
223 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
225 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
226 // 2 * control * t - 2 * control * t * t + dest * t * t
227 // 2 * control * t + (dest - 2 * control) * t * t
229 controller.origin = org; // starting point
233 controller.destvec = 2 * control; // control point
234 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
235 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
238 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
240 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
241 // 2 * control * t - 2 * control * t * t + dest * t * t
242 // 2 * control * t + (dest - 2 * control) * t * t
244 controller.origin = org; // starting point
247 controller.destvec = dest; // end point
248 controller.destvec2 = '0 0 0';
251 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeed, void() func)
257 objerror ("No speed is defined!");
260 self.finaldest = tdest;
261 self.think = SUB_CalcMoveDone;
263 if(tspeed > 0) // positive: start speed
264 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
265 else // negative: end speed
266 traveltime = 2 * vlen(tcontrol - tdest) / -tspeed;
268 if (traveltime < 0.1) // useless anim
270 self.velocity = '0 0 0';
271 self.nextthink = self.ltime + 0.1;
275 controller = spawn();
276 controller.classname = "SUB_CalcMove_controller";
277 controller.owner = self;
278 controller.platmovetype = self.platmovetype;
279 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
280 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
281 controller.animstate_starttime = time;
282 controller.animstate_endtime = time + traveltime;
283 controller.think = SUB_CalcMove_controller_think;
284 controller.think1 = self.think;
286 // the thinking is now done by the controller
287 self.think = SUB_Null;
288 self.nextthink = self.ltime + traveltime;
296 void SUB_CalcMove (vector tdest, float tspeed, void() func)
302 objerror ("No speed is defined!");
305 self.finaldest = tdest;
306 self.think = SUB_CalcMoveDone;
308 if (tdest == self.origin)
310 self.velocity = '0 0 0';
311 self.nextthink = self.ltime + 0.1;
315 delta = tdest - self.origin;
316 traveltime = vlen (delta) / tspeed;
318 // Very short animations don't really show off the effect
319 // of controlled animation, so let's just use linear movement.
320 // Alternatively entities can choose to specify non-controlled movement.
321 // The only currently implemented alternative movement is linear (value 1)
322 if (traveltime < 0.15 || self.platmovetype == 1)
324 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
325 self.nextthink = self.ltime + traveltime;
329 // now just run like a bezier curve...
330 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeed, func);
333 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
340 SUB_CalcMove (tdest, tspeed, func);
349 calculate self.avelocity and self.nextthink to reach destangle from
352 The calling function should make sure self.think is valid
355 void SUB_CalcAngleMoveDone (void)
357 // After rotating, set angle to exact final angle
358 self.angles = self.finalangle;
359 self.avelocity = '0 0 0';
365 // FIXME: I fixed this function only for rotation around the main axes
366 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
372 objerror ("No speed is defined!");
374 // take the shortest distance for the angles
375 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
376 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
377 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
378 delta = destangle - self.angles;
379 traveltime = vlen (delta) / tspeed;
382 self.finalangle = destangle;
383 self.think = SUB_CalcAngleMoveDone;
385 if (traveltime < 0.1)
387 self.avelocity = '0 0 0';
388 self.nextthink = self.ltime + 0.1;
392 self.avelocity = delta * (1 / traveltime);
393 self.nextthink = self.ltime + traveltime;
396 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
403 SUB_CalcAngleMove (destangle, tspeed, func);
412 unused but required by the engine
426 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
427 Additionally it moves players back into the past before the trace and restores them afterward.
430 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
435 // check whether antilagged traces are enabled
438 if (clienttype(forent) != CLIENTTYPE_REAL)
439 lag = 0; // only antilag for clients
441 // change shooter to SOLID_BBOX so the shot can hit corpses
442 oldsolid = source.dphitcontentsmask;
444 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
448 // take players back into the past
449 FOR_EACH_PLAYER(player)
451 antilag_takeback(player, time - lag);
456 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
458 tracebox (v1, mi, ma, v2, nomonst, forent);
460 // restore players to current positions
463 FOR_EACH_PLAYER(player)
465 antilag_restore(player);
468 // restore shooter solid type
470 source.dphitcontentsmask = oldsolid;
472 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
474 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
476 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
478 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
480 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
482 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
484 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
486 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
488 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
490 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
492 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
494 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
496 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
498 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
500 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
502 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
505 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
510 //nudge = 2 * cvar("collision_impactnudge"); // why not?
513 dir = normalize(v2 - v1);
515 pos = v1 + dir * nudge;
522 if((pos - v1) * dir >= (v2 - v1) * dir)
530 tracebox(pos, mi, ma, v2, nomonsters, forent);
535 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
536 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
537 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
538 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
543 // we started inside solid.
544 // then trace from endpos to pos
546 tracebox(t, mi, ma, pos, nomonsters, forent);
550 // t is still inside solid? bad
551 // force advance, then, and retry
552 pos = t + dir * nudge;
556 // we actually LEFT solid!
557 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
563 // pos is outside solid?!? but why?!? never mind, just return it.
565 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
571 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
577 //nudge = 2 * cvar("collision_impactnudge"); // why not?
580 dir = normalize(v2 - v1);
582 pos = v1 + dir * nudge;
586 if((pos - v1) * dir >= (v2 - v1) * dir)
593 traceline(pos, v2, nomonsters, forent);
597 // we started inside solid.
598 // then trace from endpos to pos
600 traceline(t, pos, nomonsters, forent);
603 // t is inside solid? bad
604 // force advance, then
605 pos = pos + dir * nudge;
609 // we actually LEFT solid!
610 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
616 // pos is outside solid?!? but why?!? never mind, just return it.
618 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
623 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
630 Returns a point at least 12 units away from walls
631 (useful for explosion animations, although the blast is performed where it really happened)
635 vector findbetterlocation (vector org, float mindist)
641 vec = mindist * '1 0 0';
645 traceline (org, org + vec, TRUE, world);
647 if (trace_fraction < 1)
650 traceline (loc, loc + vec, TRUE, world);
651 if (trace_fraction >= 1)
671 Returns a random number between -1.0 and 1.0
676 return 2 * (random () - 0.5);
681 Angc used for animations
686 float angc (float a1, float a2)
713 .float lodmodelindex0;
714 .float lodmodelindex1;
715 .float lodmodelindex2;
719 float LOD_customize()
723 if(autocvar_loddebug)
725 d = autocvar_loddebug;
727 self.modelindex = self.lodmodelindex0;
728 else if(d == 2 || !self.lodmodelindex2)
729 self.modelindex = self.lodmodelindex1;
731 self.modelindex = self.lodmodelindex2;
735 // TODO csqc network this so it only gets sent once
736 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
737 if(d < self.loddistance1)
738 self.modelindex = self.lodmodelindex0;
739 else if(!self.lodmodelindex2 || d < self.loddistance2)
740 self.modelindex = self.lodmodelindex1;
742 self.modelindex = self.lodmodelindex2;
747 void LOD_uncustomize()
749 self.modelindex = self.lodmodelindex0;
752 void LODmodel_attach()
756 if(!self.loddistance1)
757 self.loddistance1 = 1000;
758 if(!self.loddistance2)
759 self.loddistance2 = 2000;
760 self.lodmodelindex0 = self.modelindex;
762 if(self.lodtarget1 != "")
764 e = find(world, targetname, self.lodtarget1);
767 self.lodmodel1 = e.model;
771 if(self.lodtarget2 != "")
773 e = find(world, targetname, self.lodtarget2);
776 self.lodmodel2 = e.model;
781 if(autocvar_loddebug < 0)
783 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
786 if(self.lodmodel1 != "")
792 precache_model(self.lodmodel1);
793 setmodel(self, self.lodmodel1);
794 self.lodmodelindex1 = self.modelindex;
796 if(self.lodmodel2 != "")
798 precache_model(self.lodmodel2);
799 setmodel(self, self.lodmodel2);
800 self.lodmodelindex2 = self.modelindex;
803 self.modelindex = self.lodmodelindex0;
804 setsize(self, mi, ma);
807 if(self.lodmodelindex1)
808 if not(self.SendEntity)
809 SetCustomizer(self, LOD_customize, LOD_uncustomize);
812 void ApplyMinMaxScaleAngles(entity e)
814 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
816 e.maxs = '1 1 1' * vlen(
817 '1 0 0' * max(-e.mins_x, e.maxs_x) +
818 '0 1 0' * max(-e.mins_y, e.maxs_y) +
819 '0 0 1' * max(-e.mins_z, e.maxs_z)
823 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
826 '1 0 0' * max(-e.mins_x, e.maxs_x) +
827 '0 1 0' * max(-e.mins_y, e.maxs_y)
830 e.mins_x = -e.maxs_x;
831 e.mins_y = -e.maxs_x;
834 setsize(e, e.mins * e.scale, e.maxs * e.scale);
836 setsize(e, e.mins, e.maxs);
839 void SetBrushEntityModel()
843 precache_model(self.model);
844 setmodel(self, self.model); // no precision needed
845 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
847 setorigin(self, self.origin);
848 ApplyMinMaxScaleAngles(self);
851 void SetBrushEntityModelNoLOD()
855 precache_model(self.model);
856 setmodel(self, self.model); // no precision needed
858 setorigin(self, self.origin);
859 ApplyMinMaxScaleAngles(self);
870 if (self.movedir != '0 0 0')
871 self.movedir = normalize(self.movedir);
874 makevectors (self.angles);
875 self.movedir = v_forward;
878 self.angles = '0 0 0';
883 // trigger angles are used for one-way touches. An angle of 0 is assumed
884 // to mean no restrictions, so use a yaw of 360 instead.
886 self.solid = SOLID_TRIGGER;
887 SetBrushEntityModel();
888 self.movetype = MOVETYPE_NONE;
893 void InitSolidBSPTrigger()
895 // trigger angles are used for one-way touches. An angle of 0 is assumed
896 // to mean no restrictions, so use a yaw of 360 instead.
898 self.solid = SOLID_BSP;
899 SetBrushEntityModel();
900 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
901 // self.modelindex = 0;
905 float InitMovingBrushTrigger()
907 // trigger angles are used for one-way touches. An angle of 0 is assumed
908 // to mean no restrictions, so use a yaw of 360 instead.
909 self.solid = SOLID_BSP;
910 SetBrushEntityModel();
911 self.movetype = MOVETYPE_PUSH;
912 if(self.modelindex == 0)
914 objerror("InitMovingBrushTrigger: no brushes found!");