2 float SUB_True() { return 1; }
3 float SUB_False() { return 0; }
5 void() SUB_CalcMoveDone;
6 void() SUB_CalcAngleMoveDone;
7 //void() SUB_UseTargets;
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");
60 vector animfixfps(entity e, vector a)
62 // multi-frame anim: keep as-is
66 dur = frameduration(e.modelindex, a_x);
80 void SUB_Remove (void)
89 Applies some friction to self
93 void SUB_Friction (void)
95 self.nextthink = time;
96 if(self.flags & FL_ONGROUND)
97 self.velocity = self.velocity * (1 - frametime * self.friction);
104 Makes client invisible or removes non-client
107 void SUB_VanishOrRemove (entity ent)
109 if (ent.flags & FL_CLIENT)
124 void SUB_SetFade_Think (void)
128 self.think = SUB_SetFade_Think;
129 self.nextthink = time;
130 self.alpha -= frametime * self.fade_rate;
131 if (self.alpha < 0.01)
132 SUB_VanishOrRemove(self);
134 self.nextthink = time;
141 Fade 'ent' out when time >= 'when'
144 void SUB_SetFade (entity ent, float when, float fadetime)
146 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
149 ent.fade_rate = 1/fadetime;
150 ent.think = SUB_SetFade_Think;
151 ent.nextthink = when;
158 calculate self.velocity and self.nextthink to reach dest from
159 self.origin traveling at speed
162 void SUB_CalcMoveDone (void)
164 // After moving, set origin to exact final destination
166 setorigin (self, self.finaldest);
167 self.velocity = '0 0 0';
174 void SUB_CalcMove_controller_think (void)
184 delta = self.destvec;
185 delta2 = self.destvec2;
186 if(time < self.animstate_endtime) {
187 nexttick = time + sys_frametime;
189 traveltime = self.animstate_endtime - self.animstate_starttime;
190 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
191 if(!self.platmovetype)
192 self.platmovetype = 2; // default
193 switch(self.platmovetype)
198 /* old version, good for mathematical reference
199 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
200 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
201 phasepos = phasepos + 1; // correct range to [0, 2]
202 phasepos = phasepos / 2; // correct range to [0, 1]
205 phasepos = (1 - cos(phasepos * 3.14159265)) / 2;
207 case 3: // inverted cosine
208 phasepos = acos(1 - phasepos * 2) / 3.14159265;
210 case 4: // half cosine
211 phasepos = (1 - cos(phasepos * (3.14159265 / 2)));
213 case 5: // inverted half cosine
214 phasepos = sin(phasepos * (3.14159265 / 2));
217 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
218 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
220 if(nexttick < self.animstate_endtime) {
221 veloc = nextpos - self.owner.origin;
222 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
224 veloc = self.finaldest - self.owner.origin;
225 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
227 self.owner.velocity = veloc;
228 if(self.owner.bezier_turn)
231 vel = delta + 2 * delta2 * phasepos;
232 vel_z = -vel_z; // invert z velocity
233 vel = vectoangles(vel);
234 self.owner.angles = vel;
236 self.nextthink = nexttick;
238 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
240 self.owner.think = self.think1;
247 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
249 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
250 // 2 * control * t - 2 * control * t * t + dest * t * t
251 // 2 * control * t + (dest - 2 * control) * t * t
253 controller.origin = org; // starting point
257 controller.destvec = 2 * control; // control point
258 controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
259 // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
262 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
264 // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
265 // 2 * control * t - 2 * control * t * t + dest * t * t
266 // 2 * control * t + (dest - 2 * control) * t * t
268 controller.origin = org; // starting point
271 controller.destvec = dest; // end point
272 controller.destvec2 = '0 0 0';
275 float TSPEED_TIME = -1;
276 float TSPEED_LINEAR = 0;
277 float TSPEED_START = 1;
278 float TSPEED_END = 2;
281 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
287 objerror ("No speed is defined!");
290 self.finaldest = tdest;
291 self.think = SUB_CalcMoveDone;
297 traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
300 traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
303 traveltime = vlen(tdest - self.origin) / tspeed;
310 if (traveltime < 0.1) // useless anim
312 self.velocity = '0 0 0';
313 self.nextthink = self.ltime + 0.1;
317 controller = spawn();
318 controller.classname = "SUB_CalcMove_controller";
319 controller.owner = self;
320 controller.platmovetype = self.platmovetype;
321 SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
322 controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
323 controller.animstate_starttime = time;
324 controller.animstate_endtime = time + traveltime;
325 controller.think = SUB_CalcMove_controller_think;
326 controller.think1 = self.think;
328 // the thinking is now done by the controller
329 self.think = SUB_Null;
330 self.nextthink = self.ltime + traveltime;
338 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
344 objerror ("No speed is defined!");
347 self.finaldest = tdest;
348 self.think = SUB_CalcMoveDone;
350 if (tdest == self.origin)
352 self.velocity = '0 0 0';
353 self.nextthink = self.ltime + 0.1;
357 delta = tdest - self.origin;
365 traveltime = vlen (delta) / tspeed;
372 // Very short animations don't really show off the effect
373 // of controlled animation, so let's just use linear movement.
374 // Alternatively entities can choose to specify non-controlled movement.
375 // The only currently implemented alternative movement is linear (value 1)
376 if (traveltime < 0.15 || self.platmovetype < 2)
378 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
379 self.nextthink = self.ltime + traveltime;
383 // now just run like a bezier curve...
384 SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
387 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
394 SUB_CalcMove (tdest, tspeedtype, tspeed, func);
403 calculate self.avelocity and self.nextthink to reach destangle from
406 The calling function should make sure self.think is valid
409 void SUB_CalcAngleMoveDone (void)
411 // After rotating, set angle to exact final angle
412 self.angles = self.finalangle;
413 self.avelocity = '0 0 0';
419 // FIXME: I fixed this function only for rotation around the main axes
420 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
426 objerror ("No speed is defined!");
428 // take the shortest distance for the angles
429 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
430 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
431 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
432 delta = destangle - self.angles;
440 traveltime = vlen (delta) / tspeed;
448 self.finalangle = destangle;
449 self.think = SUB_CalcAngleMoveDone;
451 if (traveltime < 0.1)
453 self.avelocity = '0 0 0';
454 self.nextthink = self.ltime + 0.1;
458 self.avelocity = delta * (1 / traveltime);
459 self.nextthink = self.ltime + traveltime;
462 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
469 SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
478 unused but required by the engine
492 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
493 Additionally it moves players back into the past before the trace and restores them afterward.
496 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
501 // check whether antilagged traces are enabled
504 if (clienttype(forent) != CLIENTTYPE_REAL)
505 lag = 0; // only antilag for clients
507 // change shooter to SOLID_BBOX so the shot can hit corpses
508 oldsolid = source.dphitcontentsmask;
510 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
514 // take players back into the past
515 FOR_EACH_PLAYER(player)
517 antilag_takeback(player, time - lag);
522 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
524 tracebox (v1, mi, ma, v2, nomonst, forent);
526 // restore players to current positions
529 FOR_EACH_PLAYER(player)
531 antilag_restore(player);
534 // restore shooter solid type
536 source.dphitcontentsmask = oldsolid;
538 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
540 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
542 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
544 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
546 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
548 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
550 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
552 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
554 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
556 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
558 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
560 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
562 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
564 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
566 if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
568 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
571 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
576 //nudge = 2 * cvar("collision_impactnudge"); // why not?
579 dir = normalize(v2 - v1);
581 pos = v1 + dir * nudge;
588 if((pos - v1) * dir >= (v2 - v1) * dir)
596 tracebox(pos, mi, ma, v2, nomonsters, forent);
601 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
602 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
603 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
604 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
609 // we started inside solid.
610 // then trace from endpos to pos
612 tracebox(t, mi, ma, pos, nomonsters, forent);
616 // t is still inside solid? bad
617 // force advance, then, and retry
618 pos = t + dir * nudge;
622 // we actually LEFT solid!
623 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
629 // pos is outside solid?!? but why?!? never mind, just return it.
631 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
637 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
643 //nudge = 2 * cvar("collision_impactnudge"); // why not?
646 dir = normalize(v2 - v1);
648 pos = v1 + dir * nudge;
652 if((pos - v1) * dir >= (v2 - v1) * dir)
659 traceline(pos, v2, nomonsters, forent);
663 // we started inside solid.
664 // then trace from endpos to pos
666 traceline(t, pos, nomonsters, forent);
669 // t is inside solid? bad
670 // force advance, then
671 pos = pos + dir * nudge;
675 // we actually LEFT solid!
676 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
682 // pos is outside solid?!? but why?!? never mind, just return it.
684 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
689 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
696 Returns a point at least 12 units away from walls
697 (useful for explosion animations, although the blast is performed where it really happened)
701 vector findbetterlocation (vector org, float mindist)
707 vec = mindist * '1 0 0';
711 traceline (org, org + vec, TRUE, world);
713 if (trace_fraction < 1)
716 traceline (loc, loc + vec, TRUE, world);
717 if (trace_fraction >= 1)
737 Returns a random number between -1.0 and 1.0
742 return 2 * (random () - 0.5);
747 Angc used for animations
752 float angc (float a1, float a2)
779 .float lodmodelindex0;
780 .float lodmodelindex1;
781 .float lodmodelindex2;
785 float LOD_customize()
789 if(autocvar_loddebug)
791 d = autocvar_loddebug;
793 self.modelindex = self.lodmodelindex0;
794 else if(d == 2 || !self.lodmodelindex2)
795 self.modelindex = self.lodmodelindex1;
797 self.modelindex = self.lodmodelindex2;
801 // TODO csqc network this so it only gets sent once
802 d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
803 if(d < self.loddistance1)
804 self.modelindex = self.lodmodelindex0;
805 else if(!self.lodmodelindex2 || d < self.loddistance2)
806 self.modelindex = self.lodmodelindex1;
808 self.modelindex = self.lodmodelindex2;
813 void LOD_uncustomize()
815 self.modelindex = self.lodmodelindex0;
818 void LODmodel_attach()
822 if(!self.loddistance1)
823 self.loddistance1 = 1000;
824 if(!self.loddistance2)
825 self.loddistance2 = 2000;
826 self.lodmodelindex0 = self.modelindex;
828 if(self.lodtarget1 != "")
830 e = find(world, targetname, self.lodtarget1);
833 self.lodmodel1 = e.model;
837 if(self.lodtarget2 != "")
839 e = find(world, targetname, self.lodtarget2);
842 self.lodmodel2 = e.model;
847 if(autocvar_loddebug < 0)
849 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
852 if(self.lodmodel1 != "")
858 precache_model(self.lodmodel1);
859 setmodel(self, self.lodmodel1);
860 self.lodmodelindex1 = self.modelindex;
862 if(self.lodmodel2 != "")
864 precache_model(self.lodmodel2);
865 setmodel(self, self.lodmodel2);
866 self.lodmodelindex2 = self.modelindex;
869 self.modelindex = self.lodmodelindex0;
870 setsize(self, mi, ma);
873 if(self.lodmodelindex1)
874 if not(self.SendEntity)
875 SetCustomizer(self, LOD_customize, LOD_uncustomize);
878 void ApplyMinMaxScaleAngles(entity e)
880 if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
882 e.maxs = '1 1 1' * vlen(
883 '1 0 0' * max(-e.mins_x, e.maxs_x) +
884 '0 1 0' * max(-e.mins_y, e.maxs_y) +
885 '0 0 1' * max(-e.mins_z, e.maxs_z)
889 else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
892 '1 0 0' * max(-e.mins_x, e.maxs_x) +
893 '0 1 0' * max(-e.mins_y, e.maxs_y)
896 e.mins_x = -e.maxs_x;
897 e.mins_y = -e.maxs_x;
900 setsize(e, e.mins * e.scale, e.maxs * e.scale);
902 setsize(e, e.mins, e.maxs);
905 void SetBrushEntityModel()
909 precache_model(self.model);
910 setmodel(self, self.model); // no precision needed
911 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
913 setorigin(self, self.origin);
914 ApplyMinMaxScaleAngles(self);
917 void SetBrushEntityModelNoLOD()
921 precache_model(self.model);
922 setmodel(self, self.model); // no precision needed
924 setorigin(self, self.origin);
925 ApplyMinMaxScaleAngles(self);
936 if (self.movedir != '0 0 0')
937 self.movedir = normalize(self.movedir);
940 makevectors (self.angles);
941 self.movedir = v_forward;
944 self.angles = '0 0 0';
949 // trigger angles are used for one-way touches. An angle of 0 is assumed
950 // to mean no restrictions, so use a yaw of 360 instead.
952 self.solid = SOLID_TRIGGER;
953 SetBrushEntityModel();
954 self.movetype = MOVETYPE_NONE;
959 void InitSolidBSPTrigger()
961 // trigger angles are used for one-way touches. An angle of 0 is assumed
962 // to mean no restrictions, so use a yaw of 360 instead.
964 self.solid = SOLID_BSP;
965 SetBrushEntityModel();
966 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
967 // self.modelindex = 0;
971 float InitMovingBrushTrigger()
973 // trigger angles are used for one-way touches. An angle of 0 is assumed
974 // to mean no restrictions, so use a yaw of 360 instead.
975 self.solid = SOLID_BSP;
976 SetBrushEntityModel();
977 self.movetype = MOVETYPE_PUSH;
978 if(self.modelindex == 0)
980 objerror("InitMovingBrushTrigger: no brushes found!");