1 void SUB_NullThink(void) { }
3 void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove;
4 void() SUB_CalcMoveDone;
5 void() SUB_CalcAngleMoveDone;
6 //void() SUB_UseTargets;
9 void spawnfunc_info_null (void)
12 // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
15 void setanim(entity e, vector anim, float looping, float override, float restart)
18 return; // no animation was given to us! We can't use this.
20 if (anim_x == e.animstate_startframe)
21 if (anim_y == e.animstate_numframes)
22 if (anim_z == e.animstate_framerate)
27 if(anim_y == 1) // ZYM animation
28 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
33 e.animstate_startframe = anim_x;
34 e.animstate_numframes = anim_y;
35 e.animstate_framerate = anim_z;
36 e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
37 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
38 e.animstate_looping = looping;
39 e.animstate_override = override;
40 e.frame = e.animstate_startframe;
41 e.frame1time = servertime;
44 void updateanim(entity e)
46 if (time >= e.animstate_endtime)
48 if (e.animstate_looping)
50 e.animstate_starttime = e.animstate_endtime;
51 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
53 e.animstate_override = FALSE;
55 e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
56 //print(ftos(time), " -> ", ftos(e.frame), "\n");
66 void SUB_Remove (void)
75 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)
166 delta = self.destvec;
167 delta2 = self.destvec2;
168 if(time < self.animstate_endtime) {
169 nexttick = time + sys_frametime;
171 traveltime = self.animstate_endtime - self.animstate_starttime;
172 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
173 if(self.platmovetype != 1)
175 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
176 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
177 phasepos = phasepos + 1; // correct range to [0, 2]
178 phasepos = phasepos / 2; // correct range to [0, 1]
180 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
181 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
183 if(nexttick < self.animstate_endtime) {
184 veloc = nextpos - self.owner.origin;
185 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
187 veloc = self.finaldest - self.owner.origin;
188 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
190 self.owner.velocity = veloc;
191 self.nextthink = nexttick;
193 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
195 self.owner.think = self.think1;
202 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
204 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
205 // 2 * control * t - 2 * control * t * t + dest * t * t
206 // 2 * control * t + (dest - 2 * control) * t * t
208 controller.origin = org; // starting point
212 controller.destvec = 2 * control; // control point
213 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
216 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
218 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
219 // 2 * control * t - 2 * control * t * t + dest * t * t
220 // 2 * control * t + (dest - 2 * control) * t * t
222 controller.origin = org; // starting point
225 controller.destvec = dest; // end point
226 controller.destvec2 = '0 0 0';
229 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeed, void() func)
235 objerror ("No speed is defined!");
238 self.finaldest = tdest;
239 self.think = SUB_CalcMoveDone;
241 if(tspeed > 0) // positive: start speed
242 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
243 else // negative: end speed
244 traveltime = 2 * vlen(tcontrol - tdest) / -tspeed;
246 if (traveltime < 0.1) // useless anim
248 self.velocity = '0 0 0';
249 self.nextthink = self.ltime + 0.1;
253 controller = spawn();
254 controller.classname = "SUB_CalcMove_controller";
255 controller.owner = self;
256 controller.platmovetype = self.platmovetype;
257 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
258 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
259 controller.animstate_starttime = time;
260 controller.animstate_endtime = time + traveltime;
261 controller.think = SUB_CalcMove_controller_think;
262 controller.think1 = self.think;
264 // the thinking is now done by the controller
265 self.think = SUB_NullThink; // for PushMove
266 self.nextthink = self.ltime + traveltime;
274 void SUB_CalcMove (vector tdest, float tspeed, void() func)
280 objerror ("No speed is defined!");
283 self.finaldest = tdest;
284 self.think = SUB_CalcMoveDone;
286 if (tdest == self.origin)
288 self.velocity = '0 0 0';
289 self.nextthink = self.ltime + 0.1;
293 delta = tdest - self.origin;
294 traveltime = vlen (delta) / tspeed;
296 // Very short animations don't really show off the effect
297 // of controlled animation, so let's just use linear movement.
298 // Alternatively entities can choose to specify non-controlled movement.
299 // The only currently implemented alternative movement is linear (value 1)
300 if (traveltime < 0.15 || self.platmovetype == 1)
302 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
303 self.nextthink = self.ltime + traveltime;
307 // now just run like a bezier curve...
308 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeed, func);
311 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
318 SUB_CalcMove (tdest, tspeed, func);
327 calculate self.avelocity and self.nextthink to reach destangle from
330 The calling function should make sure self.think is valid
333 void SUB_CalcAngleMoveDone (void)
335 // After rotating, set angle to exact final angle
336 self.angles = self.finalangle;
337 self.avelocity = '0 0 0';
343 // FIXME: I fixed this function only for rotation around the main axes
344 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
350 objerror ("No speed is defined!");
352 // take the shortest distance for the angles
353 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
354 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
355 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
356 delta = destangle - self.angles;
357 traveltime = vlen (delta) / tspeed;
360 self.finalangle = destangle;
361 self.think = SUB_CalcAngleMoveDone;
363 if (traveltime < 0.1)
365 self.avelocity = '0 0 0';
366 self.nextthink = self.ltime + 0.1;
370 self.avelocity = delta * (1 / traveltime);
371 self.nextthink = self.ltime + traveltime;
374 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
381 SUB_CalcAngleMove (destangle, tspeed, func);
390 unused but required by the engine
404 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
405 Additionally it moves players back into the past before the trace and restores them afterward.
408 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
413 // check whether antilagged traces are enabled
416 if not(IS_REAL_CLIENT(forent))
417 lag = 0; // only antilag for clients
419 // change shooter to SOLID_BBOX so the shot can hit corpses
420 oldsolid = source.dphitcontentsmask;
422 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
426 // take players back into the past
427 FOR_EACH_PLAYER(player)
429 antilag_takeback(player, time - lag);
434 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
436 tracebox (v1, mi, ma, v2, nomonst, forent);
438 // restore players to current positions
441 FOR_EACH_PLAYER(player)
443 antilag_restore(player);
446 // restore shooter solid type
448 source.dphitcontentsmask = oldsolid;
450 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
452 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
454 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
456 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
458 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
460 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
462 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
464 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
466 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
468 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
470 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
472 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
474 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
476 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
478 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
480 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
483 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
489 //nudge = 2 * cvar("collision_impactnudge"); // why not?
492 dir = normalize(v2 - v1);
494 pos = v1 + dir * nudge;
501 if((pos - v1) * dir >= (v2 - v1) * dir)
509 tracebox(pos, mi, ma, v2, nomonsters, forent);
514 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
515 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
516 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
517 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
520 stopentity = trace_ent;
524 // we started inside solid.
525 // then trace from endpos to pos
527 tracebox(t, mi, ma, pos, nomonsters, forent);
531 // t is still inside solid? bad
532 // force advance, then, and retry
533 pos = t + dir * nudge;
535 // but if we hit an entity, stop RIGHT before it
536 if(stopatentity && stopentity)
538 trace_ent = stopentity;
540 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
546 // we actually LEFT solid!
547 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
553 // pos is outside solid?!? but why?!? never mind, just return it.
555 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
561 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity)
563 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity);
570 Returns a point at least 12 units away from walls
571 (useful for explosion animations, although the blast is performed where it really happened)
575 vector findbetterlocation (vector org, float mindist)
581 vec = mindist * '1 0 0';
585 traceline (org, org + vec, TRUE, world);
587 if (trace_fraction < 1)
590 traceline (loc, loc + vec, TRUE, world);
591 if (trace_fraction >= 1)
611 Returns a random number between -1.0 and 1.0
616 return 2 * (random () - 0.5);
621 Angc used for animations
626 float angc (float a1, float a2)
653 .float lodmodelindex0;
654 .float lodmodelindex1;
655 .float lodmodelindex2;
659 float LOD_customize()
663 if(autocvar_loddebug)
665 d = autocvar_loddebug;
667 self.modelindex = self.lodmodelindex0;
668 else if(d == 2 || !self.lodmodelindex2)
669 self.modelindex = self.lodmodelindex1;
671 self.modelindex = self.lodmodelindex2;
675 // TODO csqc network this so it only gets sent once
676 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
677 if(d < self.loddistance1)
678 self.modelindex = self.lodmodelindex0;
679 else if(!self.lodmodelindex2 || d < self.loddistance2)
680 self.modelindex = self.lodmodelindex1;
682 self.modelindex = self.lodmodelindex2;
687 void LOD_uncustomize()
689 self.modelindex = self.lodmodelindex0;
692 void LODmodel_attach()
696 if(!self.loddistance1)
697 self.loddistance1 = 1000;
698 if(!self.loddistance2)
699 self.loddistance2 = 2000;
700 self.lodmodelindex0 = self.modelindex;
702 if(self.lodtarget1 != "")
704 e = find(world, targetname, self.lodtarget1);
707 self.lodmodel1 = e.model;
711 if(self.lodtarget2 != "")
713 e = find(world, targetname, self.lodtarget2);
716 self.lodmodel2 = e.model;
721 if(autocvar_loddebug < 0)
723 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
726 if(self.lodmodel1 != "")
732 precache_model(self.lodmodel1);
733 setmodel(self, self.lodmodel1);
734 self.lodmodelindex1 = self.modelindex;
736 if(self.lodmodel2 != "")
738 precache_model(self.lodmodel2);
739 setmodel(self, self.lodmodel2);
740 self.lodmodelindex2 = self.modelindex;
743 self.modelindex = self.lodmodelindex0;
744 setsize(self, mi, ma);
747 if(self.lodmodelindex1)
748 if not(self.SendEntity)
749 SetCustomizer(self, LOD_customize, LOD_uncustomize);
752 void ApplyMinMaxScaleAngles(entity e)
754 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
756 e.maxs = '1 1 1' * vlen(
757 '1 0 0' * max(-e.mins_x, e.maxs_x) +
758 '0 1 0' * max(-e.mins_y, e.maxs_y) +
759 '0 0 1' * max(-e.mins_z, e.maxs_z)
763 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
766 '1 0 0' * max(-e.mins_x, e.maxs_x) +
767 '0 1 0' * max(-e.mins_y, e.maxs_y)
770 e.mins_x = -e.maxs_x;
771 e.mins_y = -e.maxs_x;
774 setsize(e, e.mins * e.scale, e.maxs * e.scale);
776 setsize(e, e.mins, e.maxs);
779 void SetBrushEntityModel()
783 precache_model(self.model);
784 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
786 vector mi = self.mins;
787 vector ma = self.maxs;
788 setmodel(self, self.model); // no precision needed
789 setsize(self, mi, ma);
792 setmodel(self, self.model); // no precision needed
793 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
795 setorigin(self, self.origin);
796 ApplyMinMaxScaleAngles(self);
799 void SetBrushEntityModelNoLOD()
803 precache_model(self.model);
804 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
806 vector mi = self.mins;
807 vector ma = self.maxs;
808 setmodel(self, self.model); // no precision needed
809 setsize(self, mi, ma);
812 setmodel(self, self.model); // no precision needed
814 setorigin(self, self.origin);
815 ApplyMinMaxScaleAngles(self);
826 if (self.movedir != '0 0 0')
827 self.movedir = normalize(self.movedir);
830 makevectors (self.angles);
831 self.movedir = v_forward;
834 self.angles = '0 0 0';
839 // trigger angles are used for one-way touches. An angle of 0 is assumed
840 // to mean no restrictions, so use a yaw of 360 instead.
842 self.solid = SOLID_TRIGGER;
843 SetBrushEntityModel();
844 self.movetype = MOVETYPE_NONE;
849 void InitSolidBSPTrigger()
851 // trigger angles are used for one-way touches. An angle of 0 is assumed
852 // to mean no restrictions, so use a yaw of 360 instead.
854 self.solid = SOLID_BSP;
855 SetBrushEntityModel();
856 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
857 // self.modelindex = 0;
861 float InitMovingBrushTrigger()
863 // trigger angles are used for one-way touches. An angle of 0 is assumed
864 // to mean no restrictions, so use a yaw of 360 instead.
865 self.solid = SOLID_BSP;
866 SetBrushEntityModel();
867 self.movetype = MOVETYPE_PUSH;
868 if(self.modelindex == 0)
870 objerror("InitMovingBrushTrigger: no brushes found!");