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");
59 vector animfixfps(entity e, vector a)
61 // multi-frame anim: keep as-is
65 dur = frameduration(e.modelindex, a_x);
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)
108 if (ent.flags & FL_CLIENT)
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 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
148 ent.fade_rate = 1/fadetime;
149 ent.think = SUB_SetFade_Think;
150 ent.nextthink = when;
157 calculate self.velocity and self.nextthink to reach dest from
158 self.origin traveling at speed
161 void SUB_CalcMoveDone (void)
163 // After moving, set origin to exact final destination
165 setorigin (self, self.finaldest);
166 self.velocity = '0 0 0';
172 void SUB_CalcMove_controller_think (void)
182 delta = self.destvec;
183 delta2 = self.destvec2;
184 if(time < self.animstate_endtime) {
185 nexttick = time + sys_frametime;
187 traveltime = self.animstate_endtime - self.animstate_starttime;
188 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
189 if(self.platmovetype != 1)
191 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
192 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
193 phasepos = phasepos + 1; // correct range to [0, 2]
194 phasepos = phasepos / 2; // correct range to [0, 1]
196 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
197 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
199 if(nexttick < self.animstate_endtime) {
200 veloc = nextpos - self.owner.origin;
201 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
203 veloc = self.finaldest - self.owner.origin;
204 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
206 self.owner.velocity = veloc;
207 self.nextthink = nexttick;
209 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
211 self.owner.think = self.think1;
218 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
220 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
221 // 2 * control * t - 2 * control * t * t + dest * t * t
222 // 2 * control * t + (dest - 2 * control) * t * t
224 controller.origin = org; // starting point
228 controller.destvec = 2 * control; // control point
229 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
232 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
234 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
235 // 2 * control * t - 2 * control * t * t + dest * t * t
236 // 2 * control * t + (dest - 2 * control) * t * t
238 controller.origin = org; // starting point
241 controller.destvec = dest; // end point
242 controller.destvec2 = '0 0 0';
245 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeed, void() func)
251 objerror ("No speed is defined!");
254 self.finaldest = tdest;
255 self.think = SUB_CalcMoveDone;
257 if(tspeed > 0) // positive: start speed
258 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
259 else // negative: end speed
260 traveltime = 2 * vlen(tcontrol - tdest) / -tspeed;
262 if (traveltime < 0.1) // useless anim
264 self.velocity = '0 0 0';
265 self.nextthink = self.ltime + 0.1;
269 controller = spawn();
270 controller.classname = "SUB_CalcMove_controller";
271 controller.owner = self;
272 controller.platmovetype = self.platmovetype;
273 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
274 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
275 controller.animstate_starttime = time;
276 controller.animstate_endtime = time + traveltime;
277 controller.think = SUB_CalcMove_controller_think;
278 controller.think1 = self.think;
280 // the thinking is now done by the controller
281 self.think = SUB_NullThink; // for PushMove
282 self.nextthink = self.ltime + traveltime;
290 void SUB_CalcMove (vector tdest, float tspeed, void() func)
296 objerror ("No speed is defined!");
299 self.finaldest = tdest;
300 self.think = SUB_CalcMoveDone;
302 if (tdest == self.origin)
304 self.velocity = '0 0 0';
305 self.nextthink = self.ltime + 0.1;
309 delta = tdest - self.origin;
310 traveltime = vlen (delta) / tspeed;
312 // Very short animations don't really show off the effect
313 // of controlled animation, so let's just use linear movement.
314 // Alternatively entities can choose to specify non-controlled movement.
315 // The only currently implemented alternative movement is linear (value 1)
316 if (traveltime < 0.15 || self.platmovetype == 1)
318 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
319 self.nextthink = self.ltime + traveltime;
323 // now just run like a bezier curve...
324 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeed, func);
327 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
334 SUB_CalcMove (tdest, tspeed, func);
343 calculate self.avelocity and self.nextthink to reach destangle from
346 The calling function should make sure self.think is valid
349 void SUB_CalcAngleMoveDone (void)
351 // After rotating, set angle to exact final angle
352 self.angles = self.finalangle;
353 self.avelocity = '0 0 0';
359 // FIXME: I fixed this function only for rotation around the main axes
360 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
366 objerror ("No speed is defined!");
368 // take the shortest distance for the angles
369 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
370 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
371 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
372 delta = destangle - self.angles;
373 traveltime = vlen (delta) / tspeed;
376 self.finalangle = destangle;
377 self.think = SUB_CalcAngleMoveDone;
379 if (traveltime < 0.1)
381 self.avelocity = '0 0 0';
382 self.nextthink = self.ltime + 0.1;
386 self.avelocity = delta * (1 / traveltime);
387 self.nextthink = self.ltime + traveltime;
390 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
397 SUB_CalcAngleMove (destangle, tspeed, func);
406 unused but required by the engine
420 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
421 Additionally it moves players back into the past before the trace and restores them afterward.
424 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
429 // check whether antilagged traces are enabled
432 if (clienttype(forent) != CLIENTTYPE_REAL)
433 lag = 0; // only antilag for clients
435 // change shooter to SOLID_BBOX so the shot can hit corpses
436 oldsolid = source.dphitcontentsmask;
438 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
442 // take players back into the past
443 FOR_EACH_PLAYER(player)
445 antilag_takeback(player, time - lag);
450 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
452 tracebox (v1, mi, ma, v2, nomonst, forent);
454 // restore players to current positions
457 FOR_EACH_PLAYER(player)
459 antilag_restore(player);
462 // restore shooter solid type
464 source.dphitcontentsmask = oldsolid;
466 void 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, FALSE);
470 void 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 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
476 void 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, FALSE);
482 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
484 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
486 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
488 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
490 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
492 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
494 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
496 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
499 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
505 //nudge = 2 * cvar("collision_impactnudge"); // why not?
508 dir = normalize(v2 - v1);
510 pos = v1 + dir * nudge;
517 if((pos - v1) * dir >= (v2 - v1) * dir)
525 tracebox(pos, mi, ma, v2, nomonsters, forent);
530 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
531 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
532 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
533 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
536 stopentity = trace_ent;
540 // we started inside solid.
541 // then trace from endpos to pos
543 tracebox(t, mi, ma, pos, nomonsters, forent);
547 // t is still inside solid? bad
548 // force advance, then, and retry
549 pos = t + dir * nudge;
551 // but if we hit an entity, stop RIGHT before it
552 if(stopatentity && stopentity)
554 trace_ent = stopentity;
556 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
562 // we actually LEFT solid!
563 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
569 // pos is outside solid?!? but why?!? never mind, just return it.
571 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
577 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity)
579 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity);
586 Returns a point at least 12 units away from walls
587 (useful for explosion animations, although the blast is performed where it really happened)
591 vector findbetterlocation (vector org, float mindist)
597 vec = mindist * '1 0 0';
601 traceline (org, org + vec, TRUE, world);
603 if (trace_fraction < 1)
606 traceline (loc, loc + vec, TRUE, world);
607 if (trace_fraction >= 1)
627 Returns a random number between -1.0 and 1.0
632 return 2 * (random () - 0.5);
637 Angc used for animations
642 float angc (float a1, float a2)
669 .float lodmodelindex0;
670 .float lodmodelindex1;
671 .float lodmodelindex2;
675 float LOD_customize()
679 if(autocvar_loddebug)
681 d = autocvar_loddebug;
683 self.modelindex = self.lodmodelindex0;
684 else if(d == 2 || !self.lodmodelindex2)
685 self.modelindex = self.lodmodelindex1;
687 self.modelindex = self.lodmodelindex2;
691 // TODO csqc network this so it only gets sent once
692 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
693 if(d < self.loddistance1)
694 self.modelindex = self.lodmodelindex0;
695 else if(!self.lodmodelindex2 || d < self.loddistance2)
696 self.modelindex = self.lodmodelindex1;
698 self.modelindex = self.lodmodelindex2;
703 void LOD_uncustomize()
705 self.modelindex = self.lodmodelindex0;
708 void LODmodel_attach()
712 if(!self.loddistance1)
713 self.loddistance1 = 1000;
714 if(!self.loddistance2)
715 self.loddistance2 = 2000;
716 self.lodmodelindex0 = self.modelindex;
718 if(self.lodtarget1 != "")
720 e = find(world, targetname, self.lodtarget1);
723 self.lodmodel1 = e.model;
727 if(self.lodtarget2 != "")
729 e = find(world, targetname, self.lodtarget2);
732 self.lodmodel2 = e.model;
737 if(autocvar_loddebug < 0)
739 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
742 if(self.lodmodel1 != "")
748 precache_model(self.lodmodel1);
749 setmodel(self, self.lodmodel1);
750 self.lodmodelindex1 = self.modelindex;
752 if(self.lodmodel2 != "")
754 precache_model(self.lodmodel2);
755 setmodel(self, self.lodmodel2);
756 self.lodmodelindex2 = self.modelindex;
759 self.modelindex = self.lodmodelindex0;
760 setsize(self, mi, ma);
763 if(self.lodmodelindex1)
764 if not(self.SendEntity)
765 SetCustomizer(self, LOD_customize, LOD_uncustomize);
768 void ApplyMinMaxScaleAngles(entity e)
770 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
772 e.maxs = '1 1 1' * vlen(
773 '1 0 0' * max(-e.mins_x, e.maxs_x) +
774 '0 1 0' * max(-e.mins_y, e.maxs_y) +
775 '0 0 1' * max(-e.mins_z, e.maxs_z)
779 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
782 '1 0 0' * max(-e.mins_x, e.maxs_x) +
783 '0 1 0' * max(-e.mins_y, e.maxs_y)
786 e.mins_x = -e.maxs_x;
787 e.mins_y = -e.maxs_x;
790 setsize(e, e.mins * e.scale, e.maxs * e.scale);
792 setsize(e, e.mins, e.maxs);
795 void SetBrushEntityModel()
799 precache_model(self.model);
800 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
802 vector mi = self.mins;
803 vector ma = self.maxs;
804 setmodel(self, self.model); // no precision needed
805 setsize(self, mi, ma);
808 setmodel(self, self.model); // no precision needed
809 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
811 setorigin(self, self.origin);
812 ApplyMinMaxScaleAngles(self);
815 void SetBrushEntityModelNoLOD()
819 precache_model(self.model);
820 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
822 vector mi = self.mins;
823 vector ma = self.maxs;
824 setmodel(self, self.model); // no precision needed
825 setsize(self, mi, ma);
828 setmodel(self, self.model); // no precision needed
830 setorigin(self, self.origin);
831 ApplyMinMaxScaleAngles(self);
842 if (self.movedir != '0 0 0')
843 self.movedir = normalize(self.movedir);
846 makevectors (self.angles);
847 self.movedir = v_forward;
850 self.angles = '0 0 0';
855 // trigger angles are used for one-way touches. An angle of 0 is assumed
856 // to mean no restrictions, so use a yaw of 360 instead.
858 self.solid = SOLID_TRIGGER;
859 SetBrushEntityModel();
860 self.movetype = MOVETYPE_NONE;
865 void InitSolidBSPTrigger()
867 // trigger angles are used for one-way touches. An angle of 0 is assumed
868 // to mean no restrictions, so use a yaw of 360 instead.
870 self.solid = SOLID_BSP;
871 SetBrushEntityModel();
872 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
873 // self.modelindex = 0;
877 float InitMovingBrushTrigger()
879 // trigger angles are used for one-way touches. An angle of 0 is assumed
880 // to mean no restrictions, so use a yaw of 360 instead.
881 self.solid = SOLID_BSP;
882 SetBrushEntityModel();
883 self.movetype = MOVETYPE_PUSH;
884 if(self.modelindex == 0)
886 objerror("InitMovingBrushTrigger: no brushes found!");