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)
19 if (anim_x == e.animstate_startframe)
20 if (anim_y == e.animstate_numframes)
21 if (anim_z == e.animstate_framerate)
26 if(anim_y == 1) // ZYM animation
27 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
32 e.animstate_startframe = anim_x;
33 e.animstate_numframes = anim_y;
34 e.animstate_framerate = anim_z;
35 e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
36 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
37 e.animstate_looping = looping;
38 e.animstate_override = override;
39 e.frame = e.animstate_startframe;
40 e.frame1time = servertime;
43 void updateanim(entity e)
45 if (time >= e.animstate_endtime)
47 if (e.animstate_looping)
49 e.animstate_starttime = e.animstate_endtime;
50 e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
52 e.animstate_override = FALSE;
54 e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
55 //print(ftos(time), " -> ", ftos(e.frame), "\n");
59 vector animparseline(float animfile)
66 line = fgets(animfile);
67 c = tokenize_console(line);
70 animparseerror = TRUE;
73 anim_x = stof(argv(0));
74 anim_y = stof(argv(1));
75 anim_z = stof(argv(2));
76 // don't allow completely bogus values
77 if (anim_x < 0 || anim_y < 1 || anim_z < 0.001)
89 void SUB_Remove (void)
98 Applies some friction to self
102 void SUB_Friction (void)
104 self.nextthink = time;
105 if(self.flags & FL_ONGROUND)
106 self.velocity = self.velocity * (1 - frametime * self.friction);
113 Makes client invisible or removes non-client
116 void SUB_VanishOrRemove (entity ent)
118 if (ent.flags & FL_CLIENT)
133 void SUB_SetFade_Think (void)
135 self.think = SUB_SetFade_Think;
136 self.nextthink = self.fade_time;
137 self.alpha = 1 - (time - self.fade_time) * self.fade_rate;
138 if (self.alpha < 0.01)
139 SUB_VanishOrRemove(self);
140 self.alpha = bound(0.01, self.alpha, 1);
147 Fade 'ent' out when time >= 'when'
150 void SUB_SetFade (entity ent, float when, float fadetime)
152 //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
155 ent.fade_rate = 1/fadetime;
156 ent.fade_time = when;
157 ent.think = SUB_SetFade_Think;
158 ent.nextthink = when;
165 calculate self.velocity and self.nextthink to reach dest from
166 self.origin traveling at speed
169 void SUB_CalcMoveDone (void)
171 // After moving, set origin to exact final destination
173 setorigin (self, self.finaldest);
174 self.velocity = '0 0 0';
180 void SUB_CalcMove_controller_think (void)
188 if(time < self.animstate_endtime) {
189 delta = self.destvec;
190 traveltime = self.animstate_endtime - self.animstate_starttime;
191 movephase = (time - self.animstate_starttime) / traveltime;
193 //bprint(ftos(movephase));
196 // TODO: Don't mess with the velocity, instead compute where
197 // we want to be next tick and compute velocity from that
199 veloc = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
201 // scale velocity with pi/2 so integrated over time we
202 // still have the original velocity
203 veloc = veloc * 1.5708;
205 // scale velocity with sin(phase)
206 phasepos = movephase * 3.1416; // phase * pi
207 phasepos = sin(phasepos);
208 veloc = veloc * phasepos;
210 self.owner.velocity = veloc;
211 self.nextthink = time + 0.1;
214 self.owner.think = self.think1;
221 void SUB_CalcMove (vector tdest, float tspeed, void() func)
228 objerror ("No speed is defined!");
231 self.finaldest = tdest;
232 self.think = SUB_CalcMoveDone;
234 if (tdest == self.origin)
236 self.velocity = '0 0 0';
237 self.nextthink = self.ltime + 0.1;
241 delta = tdest - self.origin;
242 traveltime = vlen (delta) / tspeed;
244 if (traveltime < 0.1)
246 self.velocity = '0 0 0';
247 self.nextthink = self.ltime + 0.1;
251 // the controller only thinks every 0.1 seconds, so very short
252 // animations should just use the traditional movement
253 if (traveltime < 0.3)
255 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
256 self.nextthink = self.ltime + traveltime;
260 controller = spawn();
261 controller.classname = "SUB_CalcMove_controller";
262 controller.owner = self;
263 controller.origin = self.origin; // starting point
264 controller.finaldest = tdest; // where do we want to end?
265 controller.destvec = delta;
266 controller.animstate_starttime = time;
267 controller.animstate_endtime = time + traveltime;
268 controller.think = SUB_CalcMove_controller_think;
269 controller.think1 = self.think;
271 // the thinking is now done by the controller
272 self.think = SUB_Null;
273 self.nextthink = self.ltime + traveltime;
281 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
288 SUB_CalcMove (tdest, tspeed, func);
297 calculate self.avelocity and self.nextthink to reach destangle from
300 The calling function should make sure self.think is valid
303 void SUB_CalcAngleMoveDone (void)
305 // After rotating, set angle to exact final angle
306 self.angles = self.finalangle;
307 self.avelocity = '0 0 0';
313 // FIXME: I fixed this function only for rotation around the main axes
314 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
320 objerror ("No speed is defined!");
322 // take the shortest distance for the angles
323 self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
324 self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
325 self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
326 delta = destangle - self.angles;
327 traveltime = vlen (delta) / tspeed;
330 self.finalangle = destangle;
331 self.think = SUB_CalcAngleMoveDone;
333 if (traveltime < 0.1)
335 self.avelocity = '0 0 0';
336 self.nextthink = self.ltime + 0.1;
340 self.avelocity = delta * (1 / traveltime);
341 self.nextthink = self.ltime + traveltime;
344 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
351 SUB_CalcAngleMove (destangle, tspeed, func);
360 unused but required by the engine
374 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
375 Additionally it moves players back into the past before the trace and restores them afterward.
378 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
381 local float oldsolid;
383 // check whether antilagged traces are enabled
386 if (clienttype(forent) != CLIENTTYPE_REAL)
387 lag = 0; // only antilag for clients
389 // change shooter to SOLID_BBOX so the shot can hit corpses
392 oldsolid = source.dphitcontentsmask;
393 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
398 // take players back into the past
399 player = player_list;
402 antilag_takeback(player, time - lag);
403 player = player.nextplayer;
409 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
411 tracebox (v1, mi, ma, v2, nomonst, forent);
413 // restore players to current positions
416 player = player_list;
419 antilag_restore(player);
420 player = player.nextplayer;
424 // restore shooter solid type
426 source.dphitcontentsmask = oldsolid;
428 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
430 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
432 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
434 if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
436 traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
438 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
440 if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
442 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
444 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
446 tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
448 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
450 if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
452 WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
454 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
456 if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
458 tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
461 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
466 //nudge = 2 * cvar("collision_impactnudge"); // why not?
469 dir = normalize(v2 - v1);
471 pos = v1 + dir * nudge;
478 if((pos - v1) * dir >= (v2 - v1) * dir)
486 tracebox(pos, mi, ma, v2, nomonsters, forent);
491 dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
492 dprint(" Nudging gets us nowhere at ", vtos(pos), "\n");
493 dprint(" trace_endpos is ", vtos(trace_endpos), "\n");
494 dprint(" trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
499 // we started inside solid.
500 // then trace from endpos to pos
502 tracebox(t, mi, ma, pos, nomonsters, forent);
506 // t is still inside solid? bad
507 // force advance, then, and retry
508 pos = t + dir * nudge;
512 // we actually LEFT solid!
513 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
519 // pos is outside solid?!? but why?!? never mind, just return it.
521 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
527 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
533 //nudge = 2 * cvar("collision_impactnudge"); // why not?
536 dir = normalize(v2 - v1);
538 pos = v1 + dir * nudge;
542 if((pos - v1) * dir >= (v2 - v1) * dir)
549 traceline(pos, v2, nomonsters, forent);
553 // we started inside solid.
554 // then trace from endpos to pos
556 traceline(t, pos, nomonsters, forent);
559 // t is inside solid? bad
560 // force advance, then
561 pos = pos + dir * nudge;
565 // we actually LEFT solid!
566 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
572 // pos is outside solid?!? but why?!? never mind, just return it.
574 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
579 tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
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()
681 d = cvar("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(cvar("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 SetBrushEntityModel()
772 precache_model(self.model);
773 setmodel(self, self.model); // no precision needed
774 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
776 setorigin(self, self.origin);
778 setsize(self, self.mins * self.scale, self.maxs * self.scale);
780 setsize(self, self.mins, self.maxs);
783 void SetBrushEntityModelNoLOD()
787 precache_model(self.model);
788 setmodel(self, self.model); // no precision needed
790 setorigin(self, self.origin);
792 setsize(self, self.mins * self.scale, self.maxs * self.scale);
794 setsize(self, self.mins, self.maxs);
805 if (self.movedir != '0 0 0')
806 self.movedir = normalize(self.movedir);
809 makevectors (self.angles);
810 self.movedir = v_forward;
813 self.angles = '0 0 0';
818 // trigger angles are used for one-way touches. An angle of 0 is assumed
819 // to mean no restrictions, so use a yaw of 360 instead.
820 if (self.movedir == '0 0 0')
821 if (self.angles != '0 0 0')
823 self.solid = SOLID_TRIGGER;
824 SetBrushEntityModel();
825 self.movetype = MOVETYPE_NONE;
830 void InitSolidBSPTrigger()
832 // trigger angles are used for one-way touches. An angle of 0 is assumed
833 // to mean no restrictions, so use a yaw of 360 instead.
834 if (self.movedir == '0 0 0')
835 if (self.angles != '0 0 0')
837 self.solid = SOLID_BSP;
838 SetBrushEntityModel();
839 self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
840 // self.modelindex = 0;
844 float InitMovingBrushTrigger()
846 // trigger angles are used for one-way touches. An angle of 0 is assumed
847 // to mean no restrictions, so use a yaw of 360 instead.
848 self.solid = SOLID_BSP;
849 SetBrushEntityModel();
850 self.movetype = MOVETYPE_PUSH;
851 if(self.modelindex == 0)
853 objerror("InitMovingBrushTrigger: no brushes found!");