2 float SUB_True() { return 1; }
3 float SUB_False() { return 0; }
5 void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove;
6 void() SUB_CalcMoveDone;
7 void() SUB_CalcAngleMoveDone;
8 //void() SUB_UseTargets;
11 void spawnfunc_info_null (void)
14 // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
17 void setanim(entity e, vector anim, float looping, float override, float restart)
20 return; // no animation was given to us! We can't use this.
22 if (anim_x == e.animstate_startframe)
23 if (anim_y == e.animstate_numframes)
24 if (anim_z == e.animstate_framerate)
29 if(anim_y == 1) // ZYM animation
30 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
35 e.animstate_startframe = anim_x;
36 e.animstate_numframes = anim_y;
37 e.animstate_framerate = anim_z;
38 e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
39 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
40 e.animstate_looping = looping;
41 e.animstate_override = override;
42 e.frame = e.animstate_startframe;
43 e.frame1time = servertime;
46 void updateanim(entity e)
48 if (time >= e.animstate_endtime)
50 if (e.animstate_looping)
52 e.animstate_starttime = e.animstate_endtime;
53 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
55 e.animstate_override = FALSE;
57 e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
58 //print(ftos(time), " -> ", ftos(e.frame), "\n");
61 vector animfixfps(entity e, vector a)
63 // multi-frame anim: keep as-is
67 dur = frameduration(e.modelindex, a_x);
74 vector animfixfps2(entity e, vector a, vector b)
76 // multi-frame anim: keep as-is
78 dur = frameduration(e.modelindex, a_x);
82 dur = frameduration(e.modelindex, a_x);
99 void SUB_Remove (void)
108 Applies some friction to self
112 void SUB_Friction (void)
114 self.nextthink = time;
115 if(self.flags & FL_ONGROUND)
116 self.velocity = self.velocity * (1 - frametime * self.friction);
123 Makes client invisible or removes non-client
126 void SUB_VanishOrRemove (entity ent)
128 if (ent.flags & FL_CLIENT)
143 void SUB_SetFade_Think (void)
145 self.think = SUB_SetFade_Think;
146 self.nextthink = self.fade_time;
147 self.alpha = 1 - (time - self.fade_time) * self.fade_rate;
148 if (self.alpha < 0.01)
149 SUB_VanishOrRemove(self);
150 self.alpha = bound(0.01, self.alpha, 1);
157 Fade 'ent' out when time >= 'when'
160 void SUB_SetFade (entity ent, float when, float fadetime)
162 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
165 ent.fade_rate = 1/fadetime;
166 ent.fade_time = when;
167 ent.think = SUB_SetFade_Think;
168 ent.nextthink = when;
175 calculate self.velocity and self.nextthink to reach dest from
176 self.origin traveling at speed
179 void SUB_CalcMoveDone (void)
181 // After moving, set origin to exact final destination
183 setorigin (self, self.finaldest);
184 self.velocity = '0 0 0';
190 void SUB_CalcMove_controller_think (void)
199 if(time < self.animstate_endtime) {
200 delta = self.destvec;
201 nexttick = time + sys_frametime;
203 if(nexttick < self.animstate_endtime) {
204 traveltime = self.animstate_endtime - self.animstate_starttime;
205 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
206 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
207 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
208 phasepos = phasepos + 1; // correct range to [0, 2]
209 phasepos = phasepos / 2; // correct range to [0, 1]
210 nextpos = self.origin + (delta * phasepos);
212 veloc = nextpos - self.owner.origin;
213 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
216 veloc = self.finaldest - self.owner.origin;
217 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
219 self.owner.velocity = veloc;
220 self.nextthink = nexttick;
223 self.owner.think = self.think1;
230 void SUB_CalcMove (vector tdest, float tspeed, void() func)
237 objerror ("No speed is defined!");
240 self.finaldest = tdest;
241 self.think = SUB_CalcMoveDone;
243 if (tdest == self.origin)
245 self.velocity = '0 0 0';
246 self.nextthink = self.ltime + 0.1;
250 delta = tdest - self.origin;
251 traveltime = vlen (delta) / tspeed;
253 if (traveltime < 0.1)
255 self.velocity = '0 0 0';
256 self.nextthink = self.ltime + 0.1;
260 // Very short animations don't really show off the effect
261 // of controlled animation, so let's just use linear movement.
262 // Alternatively entities can choose to specify non-controlled movement.
263 // The only currently implemented alternative movement is linear (value 1)
264 if (traveltime < 0.15 || self.platmovetype == 1)
266 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
267 self.nextthink = self.ltime + traveltime;
271 controller = spawn();
272 controller.classname = "SUB_CalcMove_controller";
273 controller.owner = self;
274 controller.origin = self.origin; // starting point
275 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
276 controller.destvec = delta;
277 controller.animstate_starttime = time;
278 controller.animstate_endtime = time + traveltime;
279 controller.think = SUB_CalcMove_controller_think;
280 controller.think1 = self.think;
282 // the thinking is now done by the controller
283 self.think = SUB_Null;
284 self.nextthink = self.ltime + traveltime;
292 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
299 SUB_CalcMove (tdest, tspeed, func);
308 calculate self.avelocity and self.nextthink to reach destangle from
311 The calling function should make sure self.think is valid
314 void SUB_CalcAngleMoveDone (void)
316 // After rotating, set angle to exact final angle
317 self.angles = self.finalangle;
318 self.avelocity = '0 0 0';
324 // FIXME: I fixed this function only for rotation around the main axes
325 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
331 objerror ("No speed is defined!");
333 // take the shortest distance for the angles
334 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
335 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
336 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
337 delta = destangle - self.angles;
338 traveltime = vlen (delta) / tspeed;
341 self.finalangle = destangle;
342 self.think = SUB_CalcAngleMoveDone;
344 if (traveltime < 0.1)
346 self.avelocity = '0 0 0';
347 self.nextthink = self.ltime + 0.1;
351 self.avelocity = delta * (1 / traveltime);
352 self.nextthink = self.ltime + traveltime;
355 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
362 SUB_CalcAngleMove (destangle, tspeed, func);
371 unused but required by the engine
385 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
386 Additionally it moves players back into the past before the trace and restores them afterward.
389 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
394 // check whether antilagged traces are enabled
397 if (clienttype(forent) != CLIENTTYPE_REAL)
398 lag = 0; // only antilag for clients
400 // change shooter to SOLID_BBOX so the shot can hit corpses
403 oldsolid = source.dphitcontentsmask;
404 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
409 // take players back into the past
410 FOR_EACH_PLAYER(player)
412 antilag_takeback(player, time - lag);
417 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
419 tracebox (v1, mi, ma, v2, nomonst, forent);
421 // restore players to current positions
424 FOR_EACH_PLAYER(player)
426 antilag_restore(player);
429 // restore shooter solid type
431 source.dphitcontentsmask = oldsolid;
433 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
435 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
437 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
439 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
441 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
443 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
445 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
447 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
449 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
451 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
453 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
455 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
457 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
459 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
461 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
463 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
466 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
471 //nudge = 2 * cvar("collision_impactnudge"); // why not?
474 dir = normalize(v2 - v1);
476 pos = v1 + dir * nudge;
483 if((pos - v1) * dir >= (v2 - v1) * dir)
491 tracebox(pos, mi, ma, v2, nomonsters, forent);
496 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
497 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
498 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
499 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
504 // we started inside solid.
505 // then trace from endpos to pos
507 tracebox(t, mi, ma, pos, nomonsters, forent);
511 // t is still inside solid? bad
512 // force advance, then, and retry
513 pos = t + dir * nudge;
517 // we actually LEFT solid!
518 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
524 // pos is outside solid?!? but why?!? never mind, just return it.
526 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
532 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
538 //nudge = 2 * cvar("collision_impactnudge"); // why not?
541 dir = normalize(v2 - v1);
543 pos = v1 + dir * nudge;
547 if((pos - v1) * dir >= (v2 - v1) * dir)
554 traceline(pos, v2, nomonsters, forent);
558 // we started inside solid.
559 // then trace from endpos to pos
561 traceline(t, pos, nomonsters, forent);
564 // t is inside solid? bad
565 // force advance, then
566 pos = pos + dir * nudge;
570 // we actually LEFT solid!
571 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
577 // pos is outside solid?!? but why?!? never mind, just return it.
579 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
584 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
591 Returns a point at least 12 units away from walls
592 (useful for explosion animations, although the blast is performed where it really happened)
596 vector findbetterlocation (vector org, float mindist)
602 vec = mindist * '1 0 0';
606 traceline (org, org + vec, TRUE, world);
608 if (trace_fraction < 1)
611 traceline (loc, loc + vec, TRUE, world);
612 if (trace_fraction >= 1)
632 Returns a random number between -1.0 and 1.0
637 return 2 * (random () - 0.5);
642 Angc used for animations
647 float angc (float a1, float a2)
674 .float lodmodelindex0;
675 .float lodmodelindex1;
676 .float lodmodelindex2;
680 float LOD_customize()
684 if(autocvar_loddebug)
686 d = autocvar_loddebug;
688 self.modelindex = self.lodmodelindex0;
689 else if(d == 2 || !self.lodmodelindex2)
690 self.modelindex = self.lodmodelindex1;
692 self.modelindex = self.lodmodelindex2;
696 // TODO csqc network this so it only gets sent once
697 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
698 if(d < self.loddistance1)
699 self.modelindex = self.lodmodelindex0;
700 else if(!self.lodmodelindex2 || d < self.loddistance2)
701 self.modelindex = self.lodmodelindex1;
703 self.modelindex = self.lodmodelindex2;
708 void LOD_uncustomize()
710 self.modelindex = self.lodmodelindex0;
713 void LODmodel_attach()
717 if(!self.loddistance1)
718 self.loddistance1 = 1000;
719 if(!self.loddistance2)
720 self.loddistance2 = 2000;
721 self.lodmodelindex0 = self.modelindex;
723 if(self.lodtarget1 != "")
725 e = find(world, targetname, self.lodtarget1);
728 self.lodmodel1 = e.model;
732 if(self.lodtarget2 != "")
734 e = find(world, targetname, self.lodtarget2);
737 self.lodmodel2 = e.model;
742 if(autocvar_loddebug < 0)
744 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
747 if(self.lodmodel1 != "")
753 precache_model(self.lodmodel1);
754 setmodel(self, self.lodmodel1);
755 self.lodmodelindex1 = self.modelindex;
757 if(self.lodmodel2 != "")
759 precache_model(self.lodmodel2);
760 setmodel(self, self.lodmodel2);
761 self.lodmodelindex2 = self.modelindex;
764 self.modelindex = self.lodmodelindex0;
765 setsize(self, mi, ma);
768 if(self.lodmodelindex1)
769 if not(self.SendEntity)
770 SetCustomizer(self, LOD_customize, LOD_uncustomize);
773 void ApplyMinMaxScaleAngles(entity e)
775 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
777 e.maxs = '1 1 1' * vlen(
778 '1 0 0' * max(-e.mins_x, e.maxs_x) +
779 '0 1 0' * max(-e.mins_y, e.maxs_y) +
780 '0 0 1' * max(-e.mins_z, e.maxs_z)
784 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
787 '1 0 0' * max(-e.mins_x, e.maxs_x) +
788 '0 1 0' * max(-e.mins_y, e.maxs_y)
791 e.mins_x = -e.maxs_x;
792 e.mins_y = -e.maxs_x;
795 setsize(e, e.mins * e.scale, e.maxs * e.scale);
797 setsize(e, e.mins, e.maxs);
800 void SetBrushEntityModel()
804 precache_model(self.model);
805 setmodel(self, self.model); // no precision needed
806 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
808 setorigin(self, self.origin);
809 ApplyMinMaxScaleAngles(self);
812 void SetBrushEntityModelNoLOD()
816 precache_model(self.model);
817 setmodel(self, self.model); // no precision needed
819 setorigin(self, self.origin);
820 ApplyMinMaxScaleAngles(self);
831 if (self.movedir != '0 0 0')
832 self.movedir = normalize(self.movedir);
835 makevectors (self.angles);
836 self.movedir = v_forward;
839 self.angles = '0 0 0';
844 // trigger angles are used for one-way touches. An angle of 0 is assumed
845 // to mean no restrictions, so use a yaw of 360 instead.
847 self.solid = SOLID_TRIGGER;
848 SetBrushEntityModel();
849 self.movetype = MOVETYPE_NONE;
854 void InitSolidBSPTrigger()
856 // trigger angles are used for one-way touches. An angle of 0 is assumed
857 // to mean no restrictions, so use a yaw of 360 instead.
859 self.solid = SOLID_BSP;
860 SetBrushEntityModel();
861 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
862 // self.modelindex = 0;
866 float InitMovingBrushTrigger()
868 // trigger angles are used for one-way touches. An angle of 0 is assumed
869 // to mean no restrictions, so use a yaw of 360 instead.
870 self.solid = SOLID_BSP;
871 SetBrushEntityModel();
872 self.movetype = MOVETYPE_PUSH;
873 if(self.modelindex == 0)
875 objerror("InitMovingBrushTrigger: no brushes found!");