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)
95 if (ent.flags & FL_CLIENT)
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 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
135 ent.fade_rate = 1/fadetime;
136 ent.think = SUB_SetFade_Think;
137 ent.nextthink = when;
144 calculate self.velocity and self.nextthink to reach dest from
145 self.origin traveling at speed
148 void SUB_CalcMoveDone (void)
150 // After moving, set origin to exact final destination
152 setorigin (self, self.finaldest);
153 self.velocity = '0 0 0';
159 void SUB_CalcMove_controller_think (void)
169 delta = self.destvec;
170 delta2 = self.destvec2;
171 if(time < self.animstate_endtime) {
172 nexttick = time + sys_frametime;
174 traveltime = self.animstate_endtime - self.animstate_starttime;
175 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
176 if(self.platmovetype != 1)
178 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
179 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
180 phasepos = phasepos + 1; // correct range to [0, 2]
181 phasepos = phasepos / 2; // correct range to [0, 1]
183 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
184 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
186 if(nexttick < self.animstate_endtime) {
187 veloc = nextpos - self.owner.origin;
188 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
190 veloc = self.finaldest - self.owner.origin;
191 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
193 self.owner.velocity = veloc;
194 self.nextthink = nexttick;
196 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
198 self.owner.think = self.think1;
205 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
207 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
208 // 2 * control * t - 2 * control * t * t + dest * t * t
209 // 2 * control * t + (dest - 2 * control) * t * t
211 controller.origin = org; // starting point
215 controller.destvec = 2 * control; // control point
216 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
219 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
221 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
222 // 2 * control * t - 2 * control * t * t + dest * t * t
223 // 2 * control * t + (dest - 2 * control) * t * t
225 controller.origin = org; // starting point
228 controller.destvec = dest; // end point
229 controller.destvec2 = '0 0 0';
232 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeed, void() func)
238 objerror ("No speed is defined!");
241 self.finaldest = tdest;
242 self.think = SUB_CalcMoveDone;
244 if(tspeed > 0) // positive: start speed
245 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
246 else // negative: end speed
247 traveltime = 2 * vlen(tcontrol - tdest) / -tspeed;
249 if (traveltime < 0.1) // useless anim
251 self.velocity = '0 0 0';
252 self.nextthink = self.ltime + 0.1;
256 controller = spawn();
257 controller.classname = "SUB_CalcMove_controller";
258 controller.owner = self;
259 controller.platmovetype = self.platmovetype;
260 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
261 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
262 controller.animstate_starttime = time;
263 controller.animstate_endtime = time + traveltime;
264 controller.think = SUB_CalcMove_controller_think;
265 controller.think1 = self.think;
267 // the thinking is now done by the controller
268 self.think = SUB_NullThink; // for PushMove
269 self.nextthink = self.ltime + traveltime;
277 void SUB_CalcMove (vector tdest, float tspeed, void() func)
283 objerror ("No speed is defined!");
286 self.finaldest = tdest;
287 self.think = SUB_CalcMoveDone;
289 if (tdest == self.origin)
291 self.velocity = '0 0 0';
292 self.nextthink = self.ltime + 0.1;
296 delta = tdest - self.origin;
297 traveltime = vlen (delta) / tspeed;
299 // Very short animations don't really show off the effect
300 // of controlled animation, so let's just use linear movement.
301 // Alternatively entities can choose to specify non-controlled movement.
302 // The only currently implemented alternative movement is linear (value 1)
303 if (traveltime < 0.15 || self.platmovetype == 1)
305 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
306 self.nextthink = self.ltime + traveltime;
310 // now just run like a bezier curve...
311 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeed, func);
314 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
321 SUB_CalcMove (tdest, tspeed, func);
330 calculate self.avelocity and self.nextthink to reach destangle from
333 The calling function should make sure self.think is valid
336 void SUB_CalcAngleMoveDone (void)
338 // After rotating, set angle to exact final angle
339 self.angles = self.finalangle;
340 self.avelocity = '0 0 0';
346 // FIXME: I fixed this function only for rotation around the main axes
347 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
353 objerror ("No speed is defined!");
355 // take the shortest distance for the angles
356 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
357 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
358 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
359 delta = destangle - self.angles;
360 traveltime = vlen (delta) / tspeed;
363 self.finalangle = destangle;
364 self.think = SUB_CalcAngleMoveDone;
366 if (traveltime < 0.1)
368 self.avelocity = '0 0 0';
369 self.nextthink = self.ltime + 0.1;
373 self.avelocity = delta * (1 / traveltime);
374 self.nextthink = self.ltime + traveltime;
377 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
384 SUB_CalcAngleMove (destangle, tspeed, func);
393 unused but required by the engine
407 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
408 Additionally it moves players back into the past before the trace and restores them afterward.
411 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
416 // check whether antilagged traces are enabled
419 if (clienttype(forent) != CLIENTTYPE_REAL)
420 lag = 0; // only antilag for clients
422 // change shooter to SOLID_BBOX so the shot can hit corpses
423 oldsolid = source.dphitcontentsmask;
425 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
429 // take players back into the past
430 FOR_EACH_PLAYER(player)
432 antilag_takeback(player, time - lag);
437 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
439 tracebox (v1, mi, ma, v2, nomonst, forent);
441 // restore players to current positions
444 FOR_EACH_PLAYER(player)
446 antilag_restore(player);
449 // restore shooter solid type
451 source.dphitcontentsmask = oldsolid;
453 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
455 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
457 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
459 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
461 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
463 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
465 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
467 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
469 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
471 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
473 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
475 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
477 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
479 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
481 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
483 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
486 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
492 //nudge = 2 * cvar("collision_impactnudge"); // why not?
495 dir = normalize(v2 - v1);
497 pos = v1 + dir * nudge;
504 if((pos - v1) * dir >= (v2 - v1) * dir)
512 tracebox(pos, mi, ma, v2, nomonsters, forent);
517 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
518 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
519 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
520 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
523 stopentity = trace_ent;
527 // we started inside solid.
528 // then trace from endpos to pos
530 tracebox(t, mi, ma, pos, nomonsters, forent);
534 // t is still inside solid? bad
535 // force advance, then, and retry
536 pos = t + dir * nudge;
538 // but if we hit an entity, stop RIGHT before it
539 if(stopatentity && stopentity)
541 trace_ent = stopentity;
543 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
549 // we actually LEFT solid!
550 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
556 // pos is outside solid?!? but why?!? never mind, just return it.
558 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
564 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity)
566 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity);
573 Returns a point at least 12 units away from walls
574 (useful for explosion animations, although the blast is performed where it really happened)
578 vector findbetterlocation (vector org, float mindist)
584 vec = mindist * '1 0 0';
588 traceline (org, org + vec, TRUE, world);
590 if (trace_fraction < 1)
593 traceline (loc, loc + vec, TRUE, world);
594 if (trace_fraction >= 1)
614 Returns a random number between -1.0 and 1.0
619 return 2 * (random () - 0.5);
624 Angc used for animations
629 float angc (float a1, float a2)
656 .float lodmodelindex0;
657 .float lodmodelindex1;
658 .float lodmodelindex2;
662 float LOD_customize()
666 if(autocvar_loddebug)
668 d = autocvar_loddebug;
670 self.modelindex = self.lodmodelindex0;
671 else if(d == 2 || !self.lodmodelindex2)
672 self.modelindex = self.lodmodelindex1;
674 self.modelindex = self.lodmodelindex2;
678 // TODO csqc network this so it only gets sent once
679 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
680 if(d < self.loddistance1)
681 self.modelindex = self.lodmodelindex0;
682 else if(!self.lodmodelindex2 || d < self.loddistance2)
683 self.modelindex = self.lodmodelindex1;
685 self.modelindex = self.lodmodelindex2;
690 void LOD_uncustomize()
692 self.modelindex = self.lodmodelindex0;
695 void LODmodel_attach()
699 if(!self.loddistance1)
700 self.loddistance1 = 1000;
701 if(!self.loddistance2)
702 self.loddistance2 = 2000;
703 self.lodmodelindex0 = self.modelindex;
705 if(self.lodtarget1 != "")
707 e = find(world, targetname, self.lodtarget1);
710 self.lodmodel1 = e.model;
714 if(self.lodtarget2 != "")
716 e = find(world, targetname, self.lodtarget2);
719 self.lodmodel2 = e.model;
724 if(autocvar_loddebug < 0)
726 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
729 if(self.lodmodel1 != "")
735 precache_model(self.lodmodel1);
736 setmodel(self, self.lodmodel1);
737 self.lodmodelindex1 = self.modelindex;
739 if(self.lodmodel2 != "")
741 precache_model(self.lodmodel2);
742 setmodel(self, self.lodmodel2);
743 self.lodmodelindex2 = self.modelindex;
746 self.modelindex = self.lodmodelindex0;
747 setsize(self, mi, ma);
750 if(self.lodmodelindex1)
751 if not(self.SendEntity)
752 SetCustomizer(self, LOD_customize, LOD_uncustomize);
755 void ApplyMinMaxScaleAngles(entity e)
757 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
759 e.maxs = '1 1 1' * vlen(
760 '1 0 0' * max(-e.mins_x, e.maxs_x) +
761 '0 1 0' * max(-e.mins_y, e.maxs_y) +
762 '0 0 1' * max(-e.mins_z, e.maxs_z)
766 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
769 '1 0 0' * max(-e.mins_x, e.maxs_x) +
770 '0 1 0' * max(-e.mins_y, e.maxs_y)
773 e.mins_x = -e.maxs_x;
774 e.mins_y = -e.maxs_x;
777 setsize(e, e.mins * e.scale, e.maxs * e.scale);
779 setsize(e, e.mins, e.maxs);
782 void SetBrushEntityModel()
786 precache_model(self.model);
787 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
789 vector mi = self.mins;
790 vector ma = self.maxs;
791 setmodel(self, self.model); // no precision needed
792 setsize(self, mi, ma);
795 setmodel(self, self.model); // no precision needed
796 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
798 setorigin(self, self.origin);
799 ApplyMinMaxScaleAngles(self);
802 void SetBrushEntityModelNoLOD()
806 precache_model(self.model);
807 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
809 vector mi = self.mins;
810 vector ma = self.maxs;
811 setmodel(self, self.model); // no precision needed
812 setsize(self, mi, ma);
815 setmodel(self, self.model); // no precision needed
817 setorigin(self, self.origin);
818 ApplyMinMaxScaleAngles(self);
829 if (self.movedir != '0 0 0')
830 self.movedir = normalize(self.movedir);
833 makevectors (self.angles);
834 self.movedir = v_forward;
837 self.angles = '0 0 0';
842 // trigger angles are used for one-way touches. An angle of 0 is assumed
843 // to mean no restrictions, so use a yaw of 360 instead.
845 self.solid = SOLID_TRIGGER;
846 SetBrushEntityModel();
847 self.movetype = MOVETYPE_NONE;
852 void InitSolidBSPTrigger()
854 // trigger angles are used for one-way touches. An angle of 0 is assumed
855 // to mean no restrictions, so use a yaw of 360 instead.
857 self.solid = SOLID_BSP;
858 SetBrushEntityModel();
859 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
860 // self.modelindex = 0;
864 float InitMovingBrushTrigger()
866 // trigger angles are used for one-way touches. An angle of 0 is assumed
867 // to mean no restrictions, so use a yaw of 360 instead.
868 self.solid = SOLID_BSP;
869 SetBrushEntityModel();
870 self.movetype = MOVETYPE_PUSH;
871 if(self.modelindex == 0)
873 objerror("InitMovingBrushTrigger: no brushes found!");