4 #include "../dpdefs/progsdefs.qh"
5 #include "../dpdefs/dpextensions.qh"
6 #include "../warpzonelib/common.qh"
7 #include "../common/util.qh"
8 #include "autocvars.qh"
10 #include "command/common.qh"
11 #include "../csqcmodellib/sv_model.qh"
15 void SUB_NullThink(void) { }
17 void() SUB_CalcMoveDone;
18 void() SUB_CalcAngleMoveDone;
19 //void() SUB_UseTargets;
22 void spawnfunc_info_null (void)
25 // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
28 void setanim(entity e, vector anim, float looping, float override, float restart)
31 return; // no animation was given to us! We can't use this.
33 if (anim_x == e.animstate_startframe)
34 if (anim_y == e.animstate_numframes)
35 if (anim_z == e.animstate_framerate)
40 if(anim_y == 1) // ZYM animation
41 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
46 e.animstate_startframe = anim.x;
47 e.animstate_numframes = anim.y;
48 e.animstate_framerate = anim.z;
49 e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
50 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
51 e.animstate_looping = looping;
52 e.animstate_override = override;
53 e.frame = e.animstate_startframe;
54 e.frame1time = servertime;
57 void updateanim(entity e)
59 if (time >= e.animstate_endtime)
61 if (e.animstate_looping)
63 e.animstate_starttime = e.animstate_endtime;
64 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
66 e.animstate_override = false;
68 e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
69 //print(ftos(time), " -> ", ftos(e.frame), "\n");
79 void SUB_Remove (void)
88 Applies some friction to self
92 void SUB_Friction (void)
94 self.nextthink = time;
95 if(self.flags & FL_ONGROUND)
96 self.velocity = self.velocity * (1 - frametime * self.friction);
103 Makes client invisible or removes non-client
106 void SUB_VanishOrRemove (entity ent)
123 void SUB_SetFade_Think (void)
127 self.think = SUB_SetFade_Think;
128 self.nextthink = time;
129 self.alpha -= frametime * self.fade_rate;
130 if (self.alpha < 0.01)
131 SUB_VanishOrRemove(self);
133 self.nextthink = time;
140 Fade 'ent' out when time >= 'when'
143 void SUB_SetFade (entity ent, float when, float fadetime)
145 ent.fade_rate = 1/fadetime;
146 ent.think = SUB_SetFade_Think;
147 ent.nextthink = when;
154 calculate self.velocity and self.nextthink to reach dest from
155 self.origin traveling at speed
158 void SUB_CalcMoveDone (void)
160 // After moving, set origin to exact final destination
162 setorigin (self, self.finaldest);
163 self.velocity = '0 0 0';
169 .float platmovetype_turn;
170 void SUB_CalcMove_controller_think (void)
181 delta = self.destvec;
182 delta2 = self.destvec2;
183 if(time < self.animstate_endtime) {
184 nexttick = time + sys_frametime;
186 traveltime = self.animstate_endtime - self.animstate_starttime;
187 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
188 phasepos = cubic_speedfunc(self.platmovetype_start, self.platmovetype_end, phasepos);
189 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
190 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
192 if(self.owner.platmovetype_turn)
195 destangle = delta + 2 * delta2 * phasepos;
196 destangle = vectoangles(destangle);
197 destangle_x = -destangle.x; // flip up / down orientation
199 // take the shortest distance for the angles
200 self.owner.angles_x -= 360 * floor((self.owner.angles.x - destangle.x) / 360 + 0.5);
201 self.owner.angles_y -= 360 * floor((self.owner.angles.y - destangle.y) / 360 + 0.5);
202 self.owner.angles_z -= 360 * floor((self.owner.angles.z - destangle.z) / 360 + 0.5);
203 angloc = destangle - self.owner.angles;
204 angloc = angloc * (1 / sys_frametime); // so it arrives for the next frame
205 self.owner.avelocity = angloc;
207 if(nexttick < self.animstate_endtime)
208 veloc = nextpos - self.owner.origin;
210 veloc = self.finaldest - self.owner.origin;
211 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
213 self.owner.velocity = veloc;
214 self.nextthink = nexttick;
216 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
218 self.owner.think = self.think1;
225 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
227 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
228 // 2 * control * t - 2 * control * t * t + dest * t * t
229 // 2 * control * t + (dest - 2 * control) * t * t
231 controller.origin = org; // starting point
235 controller.destvec = 2 * control; // control point
236 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
237 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
240 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
242 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
243 // 2 * control * t - 2 * control * t * t + dest * t * t
244 // 2 * control * t + (dest - 2 * control) * t * t
246 controller.origin = org; // starting point
249 controller.destvec = dest; // end point
250 controller.destvec2 = '0 0 0';
253 float TSPEED_TIME = -1;
254 float TSPEED_LINEAR = 0;
255 float TSPEED_START = 1;
256 float TSPEED_END = 2;
259 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
265 objerror ("No speed is defined!");
268 self.finaldest = tdest;
269 self.think = SUB_CalcMoveDone;
275 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
278 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
281 traveltime = vlen(tdest - self.origin) / tspeed;
288 if (traveltime < 0.1) // useless anim
290 self.velocity = '0 0 0';
291 self.nextthink = self.ltime + 0.1;
295 controller = spawn();
296 controller.classname = "SUB_CalcMove_controller";
297 controller.owner = self;
298 controller.platmovetype = self.platmovetype;
299 controller.platmovetype_start = self.platmovetype_start;
300 controller.platmovetype_end = self.platmovetype_end;
301 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
302 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
303 controller.animstate_starttime = time;
304 controller.animstate_endtime = time + traveltime;
305 controller.think = SUB_CalcMove_controller_think;
306 controller.think1 = self.think;
308 // the thinking is now done by the controller
309 self.think = SUB_NullThink; // for PushMove
310 self.nextthink = self.ltime + traveltime;
318 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
324 objerror ("No speed is defined!");
327 self.finaldest = tdest;
328 self.think = SUB_CalcMoveDone;
330 if (tdest == self.origin)
332 self.velocity = '0 0 0';
333 self.nextthink = self.ltime + 0.1;
337 delta = tdest - self.origin;
345 traveltime = vlen (delta) / tspeed;
352 // Very short animations don't really show off the effect
353 // of controlled animation, so let's just use linear movement.
354 // Alternatively entities can choose to specify non-controlled movement.
355 // The only currently implemented alternative movement is linear (value 1)
356 if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
358 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
359 self.nextthink = self.ltime + traveltime;
363 // now just run like a bezier curve...
364 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
367 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
374 SUB_CalcMove (tdest, tspeedtype, tspeed, func);
383 calculate self.avelocity and self.nextthink to reach destangle from
386 The calling function should make sure self.think is valid
389 void SUB_CalcAngleMoveDone (void)
391 // After rotating, set angle to exact final angle
392 self.angles = self.finalangle;
393 self.avelocity = '0 0 0';
399 // FIXME: I fixed this function only for rotation around the main axes
400 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
406 objerror ("No speed is defined!");
408 // take the shortest distance for the angles
409 self.angles_x -= 360 * floor((self.angles.x - destangle.x) / 360 + 0.5);
410 self.angles_y -= 360 * floor((self.angles.y - destangle.y) / 360 + 0.5);
411 self.angles_z -= 360 * floor((self.angles.z - destangle.z) / 360 + 0.5);
412 delta = destangle - self.angles;
420 traveltime = vlen (delta) / tspeed;
428 self.finalangle = destangle;
429 self.think = SUB_CalcAngleMoveDone;
431 if (traveltime < 0.1)
433 self.avelocity = '0 0 0';
434 self.nextthink = self.ltime + 0.1;
438 self.avelocity = delta * (1 / traveltime);
439 self.nextthink = self.ltime + traveltime;
442 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
449 SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
458 unused but required by the engine
472 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
473 Additionally it moves players back into the past before the trace and restores them afterward.
476 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
481 // check whether antilagged traces are enabled
484 if (!IS_REAL_CLIENT(forent))
485 lag = 0; // only antilag for clients
487 // change shooter to SOLID_BBOX so the shot can hit corpses
488 oldsolid = source.dphitcontentsmask;
490 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
494 // take players back into the past
495 FOR_EACH_PLAYER(player)
497 antilag_takeback(player, time - lag);
498 FOR_EACH_MONSTER(player)
499 antilag_takeback(player, time - lag);
504 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
506 tracebox (v1, mi, ma, v2, nomonst, forent);
508 // restore players to current positions
511 FOR_EACH_PLAYER(player)
513 antilag_restore(player);
514 FOR_EACH_MONSTER(player)
515 antilag_restore(player);
518 // restore shooter solid type
520 source.dphitcontentsmask = oldsolid;
522 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
524 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, false);
526 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
528 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
530 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
532 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
534 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
536 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, false);
538 void WarpZone_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, true);
542 void WarpZone_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 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
548 void WarpZone_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, true);
555 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) // returns the number of traces done, for benchmarking
561 //nudge = 2 * cvar("collision_impactnudge"); // why not?
564 dir = normalize(v2 - v1);
566 pos = v1 + dir * nudge;
573 if(pos * dir >= v2 * dir)
581 tracebox(pos, mi, ma, v2, nomonsters, forent);
586 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
587 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
588 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
589 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
592 stopentity = trace_ent;
596 // we started inside solid.
597 // then trace from endpos to pos
599 tracebox(t, mi, ma, pos, nomonsters, forent);
603 // t is still inside solid? bad
604 // force advance, then, and retry
605 pos = t + dir * nudge;
607 // but if we hit an entity, stop RIGHT before it
608 if(stopatentity && stopentity && stopentity != ignorestopatentity)
610 trace_ent = stopentity;
612 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
618 // we actually LEFT solid!
619 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
625 // pos is outside solid?!? but why?!? never mind, just return it.
627 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
633 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
635 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
642 Returns a point at least 12 units away from walls
643 (useful for explosion animations, although the blast is performed where it really happened)
647 vector findbetterlocation (vector org, float mindist)
653 vec = mindist * '1 0 0';
657 traceline (org, org + vec, true, world);
659 if (trace_fraction < 1)
662 traceline (loc, loc + vec, true, world);
663 if (trace_fraction >= 1)
683 Returns a random number between -1.0 and 1.0
688 return 2 * (random () - 0.5);
693 Angc used for animations
698 float angc (float a1, float a2)
725 .float lodmodelindex0;
726 .float lodmodelindex1;
727 .float lodmodelindex2;
731 float LOD_customize()
735 if(autocvar_loddebug)
737 d = autocvar_loddebug;
739 self.modelindex = self.lodmodelindex0;
740 else if(d == 2 || !self.lodmodelindex2)
741 self.modelindex = self.lodmodelindex1;
743 self.modelindex = self.lodmodelindex2;
747 // TODO csqc network this so it only gets sent once
748 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
749 if(d < self.loddistance1)
750 self.modelindex = self.lodmodelindex0;
751 else if(!self.lodmodelindex2 || d < self.loddistance2)
752 self.modelindex = self.lodmodelindex1;
754 self.modelindex = self.lodmodelindex2;
759 void LOD_uncustomize()
761 self.modelindex = self.lodmodelindex0;
764 void LODmodel_attach()
768 if(!self.loddistance1)
769 self.loddistance1 = 1000;
770 if(!self.loddistance2)
771 self.loddistance2 = 2000;
772 self.lodmodelindex0 = self.modelindex;
774 if(self.lodtarget1 != "")
776 e = find(world, targetname, self.lodtarget1);
779 self.lodmodel1 = e.model;
783 if(self.lodtarget2 != "")
785 e = find(world, targetname, self.lodtarget2);
788 self.lodmodel2 = e.model;
793 if(autocvar_loddebug < 0)
795 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
798 if(self.lodmodel1 != "")
804 precache_model(self.lodmodel1);
805 setmodel(self, self.lodmodel1);
806 self.lodmodelindex1 = self.modelindex;
808 if(self.lodmodel2 != "")
810 precache_model(self.lodmodel2);
811 setmodel(self, self.lodmodel2);
812 self.lodmodelindex2 = self.modelindex;
815 self.modelindex = self.lodmodelindex0;
816 setsize(self, mi, ma);
819 if(self.lodmodelindex1)
820 if (!self.SendEntity)
821 SetCustomizer(self, LOD_customize, LOD_uncustomize);
824 void ApplyMinMaxScaleAngles(entity e)
826 if(e.angles.x != 0 || e.angles.z != 0 || self.avelocity.x != 0 || self.avelocity.z != 0) // "weird" rotation
828 e.maxs = '1 1 1' * vlen(
829 '1 0 0' * max(-e.mins.x, e.maxs.x) +
830 '0 1 0' * max(-e.mins.y, e.maxs.y) +
831 '0 0 1' * max(-e.mins.z, e.maxs.z)
835 else if(e.angles.y != 0 || self.avelocity.y != 0) // yaw only is a bit better
838 '1 0 0' * max(-e.mins.x, e.maxs.x) +
839 '0 1 0' * max(-e.mins.y, e.maxs.y)
842 e.mins_x = -e.maxs.x;
843 e.mins_y = -e.maxs.x;
846 setsize(e, e.mins * e.scale, e.maxs * e.scale);
848 setsize(e, e.mins, e.maxs);
851 void SetBrushEntityModel()
855 precache_model(self.model);
856 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
858 vector mi = self.mins;
859 vector ma = self.maxs;
860 setmodel(self, self.model); // no precision needed
861 setsize(self, mi, ma);
864 setmodel(self, self.model); // no precision needed
865 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
867 setorigin(self, self.origin);
868 ApplyMinMaxScaleAngles(self);
871 void SetBrushEntityModelNoLOD()
875 precache_model(self.model);
876 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
878 vector mi = self.mins;
879 vector ma = self.maxs;
880 setmodel(self, self.model); // no precision needed
881 setsize(self, mi, ma);
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!");