2 float SUB_True() { return 1; }
3 float SUB_False() { return 0; }
5 void() SUB_CalcMoveDone;
6 void() SUB_CalcAngleMoveDone;
7 //void() SUB_UseTargets;
10 void spawnfunc_info_null (void)
13 // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
16 void setanim(entity e, vector anim, float looping, float override, float restart)
19 return; // no animation was given to us! We can't use this.
21 if (anim_x == e.animstate_startframe)
22 if (anim_y == e.animstate_numframes)
23 if (anim_z == e.animstate_framerate)
28 if(anim_y == 1) // ZYM animation
29 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
34 e.animstate_startframe = anim_x;
35 e.animstate_numframes = anim_y;
36 e.animstate_framerate = anim_z;
37 e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
38 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
39 e.animstate_looping = looping;
40 e.animstate_override = override;
41 e.frame = e.animstate_startframe;
42 e.frame1time = servertime;
45 void updateanim(entity e)
47 if (time >= e.animstate_endtime)
49 if (e.animstate_looping)
51 e.animstate_starttime = e.animstate_endtime;
52 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
54 e.animstate_override = FALSE;
56 e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
57 //print(ftos(time), " -> ", ftos(e.frame), "\n");
60 vector animfixfps(entity e, vector a)
62 // multi-frame anim: keep as-is
66 dur = frameduration(e.modelindex, a_x);
80 void SUB_Remove (void)
89 Applies some friction to self
93 void SUB_Friction (void)
95 self.nextthink = time;
96 if(self.flags & FL_ONGROUND)
97 self.velocity = self.velocity * (1 - frametime * self.friction);
104 Makes client invisible or removes non-client
107 void SUB_VanishOrRemove (entity ent)
109 if (ent.flags & FL_CLIENT)
124 void SUB_SetFade_Think (void)
128 self.think = SUB_SetFade_Think;
129 self.nextthink = time;
130 self.alpha -= frametime * self.fade_rate;
131 if (self.alpha < 0.01)
132 SUB_VanishOrRemove(self);
134 self.nextthink = time;
141 Fade 'ent' out when time >= 'when'
144 void SUB_SetFade (entity ent, float when, float fadetime)
146 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
149 ent.fade_rate = 1/fadetime;
150 ent.think = SUB_SetFade_Think;
151 ent.nextthink = when;
158 calculate self.velocity and self.nextthink to reach dest from
159 self.origin traveling at speed
162 void SUB_CalcMoveDone (void)
164 // After moving, set origin to exact final destination
166 setorigin (self, self.finaldest);
167 self.velocity = '0 0 0';
173 .float platmovetype_turn;
174 void SUB_CalcMove_controller_think (void)
184 delta = self.destvec;
185 delta2 = self.destvec2;
186 if(time < self.animstate_endtime) {
187 nexttick = time + sys_frametime;
189 traveltime = self.animstate_endtime - self.animstate_starttime;
190 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
191 phasepos = cubic_speedfunc(self.platmovetype_start, self.platmovetype_end, phasepos);
192 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
193 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
195 if(nexttick < self.animstate_endtime) {
196 veloc = nextpos - self.owner.origin;
197 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
199 veloc = self.finaldest - self.owner.origin;
200 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
202 self.owner.velocity = veloc;
203 if(self.owner.platmovetype_turn)
206 vel = delta + 2 * delta2 * phasepos;
207 vel_z = -vel_z; // invert z velocity
208 vel = vectoangles(vel);
209 self.owner.angles = vel;
211 self.nextthink = nexttick;
213 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
215 self.owner.think = self.think1;
222 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
224 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
225 // 2 * control * t - 2 * control * t * t + dest * t * t
226 // 2 * control * t + (dest - 2 * control) * t * t
228 controller.origin = org; // starting point
232 controller.destvec = 2 * control; // control point
233 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
234 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
237 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
239 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
240 // 2 * control * t - 2 * control * t * t + dest * t * t
241 // 2 * control * t + (dest - 2 * control) * t * t
243 controller.origin = org; // starting point
246 controller.destvec = dest; // end point
247 controller.destvec2 = '0 0 0';
250 float TSPEED_TIME = -1;
251 float TSPEED_LINEAR = 0;
252 float TSPEED_START = 1;
253 float TSPEED_END = 2;
256 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
262 objerror ("No speed is defined!");
265 self.finaldest = tdest;
266 self.think = SUB_CalcMoveDone;
272 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
275 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
278 traveltime = vlen(tdest - self.origin) / tspeed;
285 if (traveltime < 0.1) // useless anim
287 self.velocity = '0 0 0';
288 self.nextthink = self.ltime + 0.1;
292 controller = spawn();
293 controller.classname = "SUB_CalcMove_controller";
294 controller.owner = self;
295 controller.platmovetype = self.platmovetype;
296 controller.platmovetype_start = self.platmovetype_start;
297 controller.platmovetype_end = self.platmovetype_end;
298 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
299 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
300 controller.animstate_starttime = time;
301 controller.animstate_endtime = time + traveltime;
302 controller.think = SUB_CalcMove_controller_think;
303 controller.think1 = self.think;
305 // the thinking is now done by the controller
306 self.think = SUB_Null;
307 self.nextthink = self.ltime + traveltime;
315 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
321 objerror ("No speed is defined!");
324 self.finaldest = tdest;
325 self.think = SUB_CalcMoveDone;
327 if (tdest == self.origin)
329 self.velocity = '0 0 0';
330 self.nextthink = self.ltime + 0.1;
334 delta = tdest - self.origin;
342 traveltime = vlen (delta) / tspeed;
349 // Very short animations don't really show off the effect
350 // of controlled animation, so let's just use linear movement.
351 // Alternatively entities can choose to specify non-controlled movement.
352 // The only currently implemented alternative movement is linear (value 1)
353 if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
355 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
356 self.nextthink = self.ltime + traveltime;
360 // now just run like a bezier curve...
361 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
364 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
371 SUB_CalcMove (tdest, tspeedtype, tspeed, func);
380 calculate self.avelocity and self.nextthink to reach destangle from
383 The calling function should make sure self.think is valid
386 void SUB_CalcAngleMoveDone (void)
388 // After rotating, set angle to exact final angle
389 self.angles = self.finalangle;
390 self.avelocity = '0 0 0';
396 // FIXME: I fixed this function only for rotation around the main axes
397 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
403 objerror ("No speed is defined!");
405 // take the shortest distance for the angles
406 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
407 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
408 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
409 delta = destangle - self.angles;
417 traveltime = vlen (delta) / tspeed;
425 self.finalangle = destangle;
426 self.think = SUB_CalcAngleMoveDone;
428 if (traveltime < 0.1)
430 self.avelocity = '0 0 0';
431 self.nextthink = self.ltime + 0.1;
435 self.avelocity = delta * (1 / traveltime);
436 self.nextthink = self.ltime + traveltime;
439 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
446 SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
455 unused but required by the engine
469 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
470 Additionally it moves players back into the past before the trace and restores them afterward.
473 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
478 // check whether antilagged traces are enabled
481 if (clienttype(forent) != CLIENTTYPE_REAL)
482 lag = 0; // only antilag for clients
484 // change shooter to SOLID_BBOX so the shot can hit corpses
485 oldsolid = source.dphitcontentsmask;
487 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
491 // take players back into the past
492 FOR_EACH_PLAYER(player)
494 antilag_takeback(player, time - lag);
499 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
501 tracebox (v1, mi, ma, v2, nomonst, forent);
503 // restore players to current positions
506 FOR_EACH_PLAYER(player)
508 antilag_restore(player);
511 // restore shooter solid type
513 source.dphitcontentsmask = oldsolid;
515 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
517 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
519 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
521 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
523 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
525 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
527 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
529 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
531 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
533 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
535 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
537 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
539 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
541 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
543 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
545 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
548 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity) // returns the number of traces done, for benchmarking
554 //nudge = 2 * cvar("collision_impactnudge"); // why not?
557 dir = normalize(v2 - v1);
559 pos = v1 + dir * nudge;
566 if((pos - v1) * dir >= (v2 - v1) * dir)
574 tracebox(pos, mi, ma, v2, nomonsters, forent);
579 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
580 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
581 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
582 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
585 stopentity = trace_ent;
589 // we started inside solid.
590 // then trace from endpos to pos
592 tracebox(t, mi, ma, pos, nomonsters, forent);
596 // t is still inside solid? bad
597 // force advance, then, and retry
598 pos = t + dir * nudge;
600 // but if we hit an entity, stop RIGHT before it
601 if(stopatentity && stopentity)
603 trace_ent = stopentity;
605 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
611 // we actually LEFT solid!
612 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
618 // pos is outside solid?!? but why?!? never mind, just return it.
620 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
626 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity)
628 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity);
635 Returns a point at least 12 units away from walls
636 (useful for explosion animations, although the blast is performed where it really happened)
640 vector findbetterlocation (vector org, float mindist)
646 vec = mindist * '1 0 0';
650 traceline (org, org + vec, TRUE, world);
652 if (trace_fraction < 1)
655 traceline (loc, loc + vec, TRUE, world);
656 if (trace_fraction >= 1)
676 Returns a random number between -1.0 and 1.0
681 return 2 * (random () - 0.5);
686 Angc used for animations
691 float angc (float a1, float a2)
718 .float lodmodelindex0;
719 .float lodmodelindex1;
720 .float lodmodelindex2;
724 float LOD_customize()
728 if(autocvar_loddebug)
730 d = autocvar_loddebug;
732 self.modelindex = self.lodmodelindex0;
733 else if(d == 2 || !self.lodmodelindex2)
734 self.modelindex = self.lodmodelindex1;
736 self.modelindex = self.lodmodelindex2;
740 // TODO csqc network this so it only gets sent once
741 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
742 if(d < self.loddistance1)
743 self.modelindex = self.lodmodelindex0;
744 else if(!self.lodmodelindex2 || d < self.loddistance2)
745 self.modelindex = self.lodmodelindex1;
747 self.modelindex = self.lodmodelindex2;
752 void LOD_uncustomize()
754 self.modelindex = self.lodmodelindex0;
757 void LODmodel_attach()
761 if(!self.loddistance1)
762 self.loddistance1 = 1000;
763 if(!self.loddistance2)
764 self.loddistance2 = 2000;
765 self.lodmodelindex0 = self.modelindex;
767 if(self.lodtarget1 != "")
769 e = find(world, targetname, self.lodtarget1);
772 self.lodmodel1 = e.model;
776 if(self.lodtarget2 != "")
778 e = find(world, targetname, self.lodtarget2);
781 self.lodmodel2 = e.model;
786 if(autocvar_loddebug < 0)
788 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
791 if(self.lodmodel1 != "")
797 precache_model(self.lodmodel1);
798 setmodel(self, self.lodmodel1);
799 self.lodmodelindex1 = self.modelindex;
801 if(self.lodmodel2 != "")
803 precache_model(self.lodmodel2);
804 setmodel(self, self.lodmodel2);
805 self.lodmodelindex2 = self.modelindex;
808 self.modelindex = self.lodmodelindex0;
809 setsize(self, mi, ma);
812 if(self.lodmodelindex1)
813 if not(self.SendEntity)
814 SetCustomizer(self, LOD_customize, LOD_uncustomize);
817 void ApplyMinMaxScaleAngles(entity e)
819 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
821 e.maxs = '1 1 1' * vlen(
822 '1 0 0' * max(-e.mins_x, e.maxs_x) +
823 '0 1 0' * max(-e.mins_y, e.maxs_y) +
824 '0 0 1' * max(-e.mins_z, e.maxs_z)
828 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
831 '1 0 0' * max(-e.mins_x, e.maxs_x) +
832 '0 1 0' * max(-e.mins_y, e.maxs_y)
835 e.mins_x = -e.maxs_x;
836 e.mins_y = -e.maxs_x;
839 setsize(e, e.mins * e.scale, e.maxs * e.scale);
841 setsize(e, e.mins, e.maxs);
844 void SetBrushEntityModel()
848 precache_model(self.model);
849 setmodel(self, self.model); // no precision needed
850 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
852 setorigin(self, self.origin);
853 ApplyMinMaxScaleAngles(self);
856 void SetBrushEntityModelNoLOD()
860 precache_model(self.model);
861 setmodel(self, self.model); // no precision needed
863 setorigin(self, self.origin);
864 ApplyMinMaxScaleAngles(self);
875 if (self.movedir != '0 0 0')
876 self.movedir = normalize(self.movedir);
879 makevectors (self.angles);
880 self.movedir = v_forward;
883 self.angles = '0 0 0';
888 // trigger angles are used for one-way touches. An angle of 0 is assumed
889 // to mean no restrictions, so use a yaw of 360 instead.
891 self.solid = SOLID_TRIGGER;
892 SetBrushEntityModel();
893 self.movetype = MOVETYPE_NONE;
898 void InitSolidBSPTrigger()
900 // trigger angles are used for one-way touches. An angle of 0 is assumed
901 // to mean no restrictions, so use a yaw of 360 instead.
903 self.solid = SOLID_BSP;
904 SetBrushEntityModel();
905 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
906 // self.modelindex = 0;
910 float InitMovingBrushTrigger()
912 // trigger angles are used for one-way touches. An angle of 0 is assumed
913 // to mean no restrictions, so use a yaw of 360 instead.
914 self.solid = SOLID_BSP;
915 SetBrushEntityModel();
916 self.movetype = MOVETYPE_PUSH;
917 if(self.modelindex == 0)
919 objerror("InitMovingBrushTrigger: no brushes found!");