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';
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);
193 /* switch(self.platmovetype)
197 // phasepos = cubic_speedfunc(1, 1, phasepos); // identity
199 // phasepos = (1 - cos(phasepos * 3.14159265)) / 2;
200 phasepos = cubic_speedfunc(0, 0, phasepos);
202 case 3: // inverted cosine
203 // phasepos = acos(1 - phasepos * 2) / 3.14159265;
204 phasepos = cubic_speedfunc(2, 2, phasepos);
206 case 4: // half cosine
207 // phasepos = (1 - cos(phasepos * (3.14159265 / 2)));
208 phasepos = cubic_speedfunc(0, 1.5, phasepos);
210 case 5: // inverted half cosine
211 // phasepos = sin(phasepos * (3.14159265 / 2));
212 phasepos = cubic_speedfunc(1.5, 0, phasepos);
215 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
216 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
218 if(nexttick < self.animstate_endtime) {
219 veloc = nextpos - self.owner.origin;
220 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
222 veloc = self.finaldest - self.owner.origin;
223 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
225 self.owner.velocity = veloc;
226 if(self.owner.bezier_turn)
229 vel = delta + 2 * delta2 * phasepos;
230 vel_z = -vel_z; // invert z velocity
231 vel = vectoangles(vel);
232 self.owner.angles = vel;
234 self.nextthink = nexttick;
236 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
238 self.owner.think = self.think1;
245 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
247 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
248 // 2 * control * t - 2 * control * t * t + dest * t * t
249 // 2 * control * t + (dest - 2 * control) * t * t
251 controller.origin = org; // starting point
255 controller.destvec = 2 * control; // control point
256 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
257 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
260 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
262 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
263 // 2 * control * t - 2 * control * t * t + dest * t * t
264 // 2 * control * t + (dest - 2 * control) * t * t
266 controller.origin = org; // starting point
269 controller.destvec = dest; // end point
270 controller.destvec2 = '0 0 0';
273 float TSPEED_TIME = -1;
274 float TSPEED_LINEAR = 0;
275 float TSPEED_START = 1;
276 float TSPEED_END = 2;
279 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
285 objerror ("No speed is defined!");
288 self.finaldest = tdest;
289 self.think = SUB_CalcMoveDone;
295 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
298 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
301 traveltime = vlen(tdest - self.origin) / tspeed;
308 if (traveltime < 0.1) // useless anim
310 self.velocity = '0 0 0';
311 self.nextthink = self.ltime + 0.1;
315 controller = spawn();
316 controller.classname = "SUB_CalcMove_controller";
317 controller.owner = self;
318 controller.platmovetype = self.platmovetype;
319 controller.platmovetype_start = self.platmovetype_start;
320 controller.platmovetype_end = self.platmovetype_end;
321 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
322 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
323 controller.animstate_starttime = time;
324 controller.animstate_endtime = time + traveltime;
325 controller.think = SUB_CalcMove_controller_think;
326 controller.think1 = self.think;
328 // the thinking is now done by the controller
329 self.think = SUB_Null;
330 self.nextthink = self.ltime + traveltime;
338 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
344 objerror ("No speed is defined!");
347 self.finaldest = tdest;
348 self.think = SUB_CalcMoveDone;
350 if (tdest == self.origin)
352 self.velocity = '0 0 0';
353 self.nextthink = self.ltime + 0.1;
357 delta = tdest - self.origin;
365 traveltime = vlen (delta) / tspeed;
372 // Very short animations don't really show off the effect
373 // of controlled animation, so let's just use linear movement.
374 // Alternatively entities can choose to specify non-controlled movement.
375 // The only currently implemented alternative movement is linear (value 1)
376 if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
378 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
379 self.nextthink = self.ltime + traveltime;
383 // now just run like a bezier curve...
384 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
387 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
394 SUB_CalcMove (tdest, tspeedtype, tspeed, func);
403 calculate self.avelocity and self.nextthink to reach destangle from
406 The calling function should make sure self.think is valid
409 void SUB_CalcAngleMoveDone (void)
411 // After rotating, set angle to exact final angle
412 self.angles = self.finalangle;
413 self.avelocity = '0 0 0';
419 // FIXME: I fixed this function only for rotation around the main axes
420 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
426 objerror ("No speed is defined!");
428 // take the shortest distance for the angles
429 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
430 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
431 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
432 delta = destangle - self.angles;
440 traveltime = vlen (delta) / tspeed;
448 self.finalangle = destangle;
449 self.think = SUB_CalcAngleMoveDone;
451 if (traveltime < 0.1)
453 self.avelocity = '0 0 0';
454 self.nextthink = self.ltime + 0.1;
458 self.avelocity = delta * (1 / traveltime);
459 self.nextthink = self.ltime + traveltime;
462 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
469 SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
478 unused but required by the engine
492 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
493 Additionally it moves players back into the past before the trace and restores them afterward.
496 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
501 // check whether antilagged traces are enabled
504 if (clienttype(forent) != CLIENTTYPE_REAL)
505 lag = 0; // only antilag for clients
507 // change shooter to SOLID_BBOX so the shot can hit corpses
508 oldsolid = source.dphitcontentsmask;
510 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
514 // take players back into the past
515 FOR_EACH_PLAYER(player)
517 antilag_takeback(player, time - lag);
522 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
524 tracebox (v1, mi, ma, v2, nomonst, forent);
526 // restore players to current positions
529 FOR_EACH_PLAYER(player)
531 antilag_restore(player);
534 // restore shooter solid type
536 source.dphitcontentsmask = oldsolid;
538 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
540 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
542 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
544 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
546 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
548 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
550 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
552 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
554 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
556 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
558 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
560 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
562 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
564 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
566 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
568 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
571 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
577 //nudge = 2 * cvar("collision_impactnudge"); // why not?
580 dir = normalize(v2 - v1);
582 pos = v1 + dir * nudge;
589 if((pos - v1) * dir >= (v2 - v1) * dir)
597 tracebox(pos, mi, ma, v2, nomonsters, forent);
602 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
603 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
604 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
605 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
608 stopentity = trace_ent;
612 // we started inside solid.
613 // then trace from endpos to pos
615 tracebox(t, mi, ma, pos, nomonsters, forent);
619 // t is still inside solid? bad
620 // force advance, then, and retry
621 pos = t + dir * nudge;
623 // but if we hit an entity, stop RIGHT before it
624 if(stopatentity && stopentity)
626 trace_ent = stopentity;
628 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
634 // we actually LEFT solid!
635 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
641 // pos is outside solid?!? but why?!? never mind, just return it.
643 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
649 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity)
651 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity);
658 Returns a point at least 12 units away from walls
659 (useful for explosion animations, although the blast is performed where it really happened)
663 vector findbetterlocation (vector org, float mindist)
669 vec = mindist * '1 0 0';
673 traceline (org, org + vec, TRUE, world);
675 if (trace_fraction < 1)
678 traceline (loc, loc + vec, TRUE, world);
679 if (trace_fraction >= 1)
699 Returns a random number between -1.0 and 1.0
704 return 2 * (random () - 0.5);
709 Angc used for animations
714 float angc (float a1, float a2)
741 .float lodmodelindex0;
742 .float lodmodelindex1;
743 .float lodmodelindex2;
747 float LOD_customize()
751 if(autocvar_loddebug)
753 d = autocvar_loddebug;
755 self.modelindex = self.lodmodelindex0;
756 else if(d == 2 || !self.lodmodelindex2)
757 self.modelindex = self.lodmodelindex1;
759 self.modelindex = self.lodmodelindex2;
763 // TODO csqc network this so it only gets sent once
764 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
765 if(d < self.loddistance1)
766 self.modelindex = self.lodmodelindex0;
767 else if(!self.lodmodelindex2 || d < self.loddistance2)
768 self.modelindex = self.lodmodelindex1;
770 self.modelindex = self.lodmodelindex2;
775 void LOD_uncustomize()
777 self.modelindex = self.lodmodelindex0;
780 void LODmodel_attach()
784 if(!self.loddistance1)
785 self.loddistance1 = 1000;
786 if(!self.loddistance2)
787 self.loddistance2 = 2000;
788 self.lodmodelindex0 = self.modelindex;
790 if(self.lodtarget1 != "")
792 e = find(world, targetname, self.lodtarget1);
795 self.lodmodel1 = e.model;
799 if(self.lodtarget2 != "")
801 e = find(world, targetname, self.lodtarget2);
804 self.lodmodel2 = e.model;
809 if(autocvar_loddebug < 0)
811 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
814 if(self.lodmodel1 != "")
820 precache_model(self.lodmodel1);
821 setmodel(self, self.lodmodel1);
822 self.lodmodelindex1 = self.modelindex;
824 if(self.lodmodel2 != "")
826 precache_model(self.lodmodel2);
827 setmodel(self, self.lodmodel2);
828 self.lodmodelindex2 = self.modelindex;
831 self.modelindex = self.lodmodelindex0;
832 setsize(self, mi, ma);
835 if(self.lodmodelindex1)
836 if not(self.SendEntity)
837 SetCustomizer(self, LOD_customize, LOD_uncustomize);
840 void ApplyMinMaxScaleAngles(entity e)
842 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
844 e.maxs = '1 1 1' * vlen(
845 '1 0 0' * max(-e.mins_x, e.maxs_x) +
846 '0 1 0' * max(-e.mins_y, e.maxs_y) +
847 '0 0 1' * max(-e.mins_z, e.maxs_z)
851 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
854 '1 0 0' * max(-e.mins_x, e.maxs_x) +
855 '0 1 0' * max(-e.mins_y, e.maxs_y)
858 e.mins_x = -e.maxs_x;
859 e.mins_y = -e.maxs_x;
862 setsize(e, e.mins * e.scale, e.maxs * e.scale);
864 setsize(e, e.mins, e.maxs);
867 void SetBrushEntityModel()
871 precache_model(self.model);
872 setmodel(self, self.model); // no precision needed
873 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
875 setorigin(self, self.origin);
876 ApplyMinMaxScaleAngles(self);
879 void SetBrushEntityModelNoLOD()
883 precache_model(self.model);
884 setmodel(self, self.model); // no precision needed
886 setorigin(self, self.origin);
887 ApplyMinMaxScaleAngles(self);
898 if (self.movedir != '0 0 0')
899 self.movedir = normalize(self.movedir);
902 makevectors (self.angles);
903 self.movedir = v_forward;
906 self.angles = '0 0 0';
911 // trigger angles are used for one-way touches. An angle of 0 is assumed
912 // to mean no restrictions, so use a yaw of 360 instead.
914 self.solid = SOLID_TRIGGER;
915 SetBrushEntityModel();
916 self.movetype = MOVETYPE_NONE;
921 void InitSolidBSPTrigger()
923 // trigger angles are used for one-way touches. An angle of 0 is assumed
924 // to mean no restrictions, so use a yaw of 360 instead.
926 self.solid = SOLID_BSP;
927 SetBrushEntityModel();
928 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
929 // self.modelindex = 0;
933 float InitMovingBrushTrigger()
935 // trigger angles are used for one-way touches. An angle of 0 is assumed
936 // to mean no restrictions, so use a yaw of 360 instead.
937 self.solid = SOLID_BSP;
938 SetBrushEntityModel();
939 self.movetype = MOVETYPE_PUSH;
940 if(self.modelindex == 0)
942 objerror("InitMovingBrushTrigger: no brushes found!");