5 #include "command/common.qh"
6 #include "../warpzonelib/common.qh"
8 void SUB_NullThink(void) { }
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");
67 void SUB_Remove (void)
76 Applies some friction to self
79 void SUB_Friction (void)
81 self.nextthink = time;
82 if(self.flags & FL_ONGROUND)
83 self.velocity = self.velocity * (1 - frametime * self.friction);
90 Makes client invisible or removes non-client
93 void SUB_VanishOrRemove (entity ent)
110 void SUB_SetFade_Think (void)
114 self.think = SUB_SetFade_Think;
115 self.nextthink = time;
116 self.alpha -= frametime * self.fade_rate;
117 if (self.alpha < 0.01)
118 SUB_VanishOrRemove(self);
120 self.nextthink = time;
127 Fade 'ent' out when time >= 'when'
130 void SUB_SetFade (entity ent, float when, float fadetime)
132 ent.fade_rate = 1/fadetime;
133 ent.think = SUB_SetFade_Think;
134 ent.nextthink = when;
141 calculate self.velocity and self.nextthink to reach dest from
142 self.origin traveling at speed
145 void SUB_CalcMoveDone (void)
147 // After moving, set origin to exact final destination
149 setorigin (self, self.finaldest);
150 self.velocity = '0 0 0';
156 void SUB_CalcMove_controller_think (void)
167 delta = self.destvec;
168 delta2 = self.destvec2;
169 if(time < self.animstate_endtime) {
170 nexttick = time + sys_frametime;
172 traveltime = self.animstate_endtime - self.animstate_starttime;
173 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
174 phasepos = cubic_speedfunc(self.platmovetype_start, self.platmovetype_end, phasepos);
175 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
176 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
178 if(self.owner.platmovetype_turn)
181 destangle = delta + 2 * delta2 * phasepos;
182 destangle = vectoangles(destangle);
183 destangle.x = -destangle.x; // flip up / down orientation
185 // take the shortest distance for the angles
186 self.owner.angles_x -= 360 * floor((self.owner.angles.x - destangle.x) / 360 + 0.5);
187 self.owner.angles_y -= 360 * floor((self.owner.angles.y - destangle.y) / 360 + 0.5);
188 self.owner.angles_z -= 360 * floor((self.owner.angles.z - destangle.z) / 360 + 0.5);
189 angloc = destangle - self.owner.angles;
190 angloc = angloc * (1 / sys_frametime); // so it arrives for the next frame
191 self.owner.avelocity = angloc;
193 if(nexttick < self.animstate_endtime)
194 veloc = nextpos - self.owner.origin;
196 veloc = self.finaldest - self.owner.origin;
197 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
199 self.owner.velocity = veloc;
200 self.nextthink = nexttick;
202 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
204 self.owner.think = self.think1;
211 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
213 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
214 // 2 * control * t - 2 * control * t * t + dest * t * t
215 // 2 * control * t + (dest - 2 * control) * t * t
217 controller.origin = org; // starting point
221 controller.destvec = 2 * control; // control point
222 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
223 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
226 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
228 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
229 // 2 * control * t - 2 * control * t * t + dest * t * t
230 // 2 * control * t + (dest - 2 * control) * t * t
232 controller.origin = org; // starting point
235 controller.destvec = dest; // end point
236 controller.destvec2 = '0 0 0';
239 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
245 objerror ("No speed is defined!");
248 self.finaldest = tdest;
249 self.think = SUB_CalcMoveDone;
255 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
258 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
261 traveltime = vlen(tdest - self.origin) / 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 controller.platmovetype_start = self.platmovetype_start;
280 controller.platmovetype_end = self.platmovetype_end;
281 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
282 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
283 controller.animstate_starttime = time;
284 controller.animstate_endtime = time + traveltime;
285 controller.think = SUB_CalcMove_controller_think;
286 controller.think1 = self.think;
288 // the thinking is now done by the controller
289 self.think = SUB_NullThink; // for PushMove
290 self.nextthink = self.ltime + traveltime;
298 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
304 objerror ("No speed is defined!");
307 self.finaldest = tdest;
308 self.think = SUB_CalcMoveDone;
310 if (tdest == self.origin)
312 self.velocity = '0 0 0';
313 self.nextthink = self.ltime + 0.1;
317 delta = tdest - self.origin;
325 traveltime = vlen (delta) / tspeed;
332 // Very short animations don't really show off the effect
333 // of controlled animation, so let's just use linear movement.
334 // Alternatively entities can choose to specify non-controlled movement.
335 // The only currently implemented alternative movement is linear (value 1)
336 if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
338 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
339 self.nextthink = self.ltime + traveltime;
343 // now just run like a bezier curve...
344 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
347 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
354 SUB_CalcMove (tdest, tspeedtype, tspeed, func);
363 calculate self.avelocity and self.nextthink to reach destangle from
366 The calling function should make sure self.think is valid
369 void SUB_CalcAngleMoveDone (void)
371 // After rotating, set angle to exact final angle
372 self.angles = self.finalangle;
373 self.avelocity = '0 0 0';
379 // FIXME: I fixed this function only for rotation around the main axes
380 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
386 objerror ("No speed is defined!");
388 // take the shortest distance for the angles
389 self.angles_x -= 360 * floor((self.angles.x - destangle.x) / 360 + 0.5);
390 self.angles_y -= 360 * floor((self.angles.y - destangle.y) / 360 + 0.5);
391 self.angles_z -= 360 * floor((self.angles.z - destangle.z) / 360 + 0.5);
392 delta = destangle - self.angles;
400 traveltime = vlen (delta) / tspeed;
408 self.finalangle = destangle;
409 self.think = SUB_CalcAngleMoveDone;
411 if (traveltime < 0.1)
413 self.avelocity = '0 0 0';
414 self.nextthink = self.ltime + 0.1;
418 self.avelocity = delta * (1 / traveltime);
419 self.nextthink = self.ltime + traveltime;
422 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
429 SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
438 unused but required by the engine
452 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
453 Additionally it moves players back into the past before the trace and restores them afterward.
456 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
461 // check whether antilagged traces are enabled
464 if (!IS_REAL_CLIENT(forent))
465 lag = 0; // only antilag for clients
467 // change shooter to SOLID_BBOX so the shot can hit corpses
468 oldsolid = source.dphitcontentsmask;
470 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
474 // take players back into the past
475 FOR_EACH_PLAYER(player)
477 antilag_takeback(player, time - lag);
478 FOR_EACH_MONSTER(player)
479 antilag_takeback(player, time - lag);
484 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
486 tracebox (v1, mi, ma, v2, nomonst, forent);
488 // restore players to current positions
491 FOR_EACH_PLAYER(player)
493 antilag_restore(player);
494 FOR_EACH_MONSTER(player)
495 antilag_restore(player);
498 // restore shooter solid type
500 source.dphitcontentsmask = oldsolid;
502 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
504 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, false);
506 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
508 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
510 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
512 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
514 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
516 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, false);
518 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
520 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, true);
522 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
524 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
526 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
528 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
530 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
532 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, true);
535 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
541 //nudge = 2 * cvar("collision_impactnudge"); // why not?
544 dir = normalize(v2 - v1);
546 pos = v1 + dir * nudge;
553 if(pos * dir >= v2 * dir)
561 tracebox(pos, mi, ma, v2, nomonsters, forent);
566 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
567 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
568 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
569 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
572 stopentity = trace_ent;
576 // we started inside solid.
577 // then trace from endpos to pos
579 tracebox(t, mi, ma, pos, nomonsters, forent);
583 // t is still inside solid? bad
584 // force advance, then, and retry
585 pos = t + dir * nudge;
587 // but if we hit an entity, stop RIGHT before it
588 if(stopatentity && stopentity && stopentity != ignorestopatentity)
590 trace_ent = stopentity;
592 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
598 // we actually LEFT solid!
599 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
605 // pos is outside solid?!? but why?!? never mind, just return it.
607 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
613 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
615 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
622 Returns a point at least 12 units away from walls
623 (useful for explosion animations, although the blast is performed where it really happened)
627 vector findbetterlocation (vector org, float mindist)
633 vec = mindist * '1 0 0';
637 traceline (org, org + vec, true, world);
639 if (trace_fraction < 1)
642 traceline (loc, loc + vec, true, world);
643 if (trace_fraction >= 1)
663 Returns a random number between -1.0 and 1.0
668 return 2 * (random () - 0.5);
673 Angc used for animations
678 float angc (float a1, float a2)
701 float LOD_customize()
705 if(autocvar_loddebug)
707 d = autocvar_loddebug;
709 self.modelindex = self.lodmodelindex0;
710 else if(d == 2 || !self.lodmodelindex2)
711 self.modelindex = self.lodmodelindex1;
713 self.modelindex = self.lodmodelindex2;
717 // TODO csqc network this so it only gets sent once
718 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
719 if(d < self.loddistance1)
720 self.modelindex = self.lodmodelindex0;
721 else if(!self.lodmodelindex2 || d < self.loddistance2)
722 self.modelindex = self.lodmodelindex1;
724 self.modelindex = self.lodmodelindex2;
729 void LOD_uncustomize()
731 self.modelindex = self.lodmodelindex0;
734 void LODmodel_attach()
738 if(!self.loddistance1)
739 self.loddistance1 = 1000;
740 if(!self.loddistance2)
741 self.loddistance2 = 2000;
742 self.lodmodelindex0 = self.modelindex;
744 if(self.lodtarget1 != "")
746 e = find(world, targetname, self.lodtarget1);
749 self.lodmodel1 = e.model;
753 if(self.lodtarget2 != "")
755 e = find(world, targetname, self.lodtarget2);
758 self.lodmodel2 = e.model;
763 if(autocvar_loddebug < 0)
765 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
768 if(self.lodmodel1 != "")
774 precache_model(self.lodmodel1);
775 setmodel(self, self.lodmodel1);
776 self.lodmodelindex1 = self.modelindex;
778 if(self.lodmodel2 != "")
780 precache_model(self.lodmodel2);
781 setmodel(self, self.lodmodel2);
782 self.lodmodelindex2 = self.modelindex;
785 self.modelindex = self.lodmodelindex0;
786 setsize(self, mi, ma);
789 if(self.lodmodelindex1)
790 if (!self.SendEntity)
791 SetCustomizer(self, LOD_customize, LOD_uncustomize);
794 void ApplyMinMaxScaleAngles(entity e)
796 if(e.angles.x != 0 || e.angles.z != 0 || self.avelocity.x != 0 || self.avelocity.z != 0) // "weird" rotation
798 e.maxs = '1 1 1' * vlen(
799 '1 0 0' * max(-e.mins.x, e.maxs.x) +
800 '0 1 0' * max(-e.mins.y, e.maxs.y) +
801 '0 0 1' * max(-e.mins.z, e.maxs.z)
805 else if(e.angles.y != 0 || self.avelocity.y != 0) // yaw only is a bit better
808 '1 0 0' * max(-e.mins.x, e.maxs.x) +
809 '0 1 0' * max(-e.mins.y, e.maxs.y)
812 e.mins_x = -e.maxs.x;
813 e.mins_y = -e.maxs.x;
816 setsize(e, e.mins * e.scale, e.maxs * e.scale);
818 setsize(e, e.mins, e.maxs);
821 void SetBrushEntityModel()
825 precache_model(self.model);
826 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
828 vector mi = self.mins;
829 vector ma = self.maxs;
830 setmodel(self, self.model); // no precision needed
831 setsize(self, mi, ma);
834 setmodel(self, self.model); // no precision needed
835 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
837 setorigin(self, self.origin);
838 ApplyMinMaxScaleAngles(self);
841 void SetBrushEntityModelNoLOD()
845 precache_model(self.model);
846 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
848 vector mi = self.mins;
849 vector ma = self.maxs;
850 setmodel(self, self.model); // no precision needed
851 setsize(self, mi, ma);
854 setmodel(self, self.model); // no precision needed
856 setorigin(self, self.origin);
857 ApplyMinMaxScaleAngles(self);
868 if (self.movedir != '0 0 0')
869 self.movedir = normalize(self.movedir);
872 makevectors (self.angles);
873 self.movedir = v_forward;
876 self.angles = '0 0 0';
881 // trigger angles are used for one-way touches. An angle of 0 is assumed
882 // to mean no restrictions, so use a yaw of 360 instead.
884 self.solid = SOLID_TRIGGER;
885 SetBrushEntityModel();
886 self.movetype = MOVETYPE_NONE;
891 void InitSolidBSPTrigger()
893 // trigger angles are used for one-way touches. An angle of 0 is assumed
894 // to mean no restrictions, so use a yaw of 360 instead.
896 self.solid = SOLID_BSP;
897 SetBrushEntityModel();
898 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
899 // self.modelindex = 0;
903 float InitMovingBrushTrigger()
905 // trigger angles are used for one-way touches. An angle of 0 is assumed
906 // to mean no restrictions, so use a yaw of 360 instead.
907 self.solid = SOLID_BSP;
908 SetBrushEntityModel();
909 self.movetype = MOVETYPE_PUSH;
910 if(self.modelindex == 0)
912 objerror("InitMovingBrushTrigger: no brushes found!");