1 const float STAT_MOVEFLAGS = 225;
2 const float MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE = 4;
3 #define GRAVITY_UNAFFECTED_BY_TICRATE (getstati(STAT_MOVEFLAGS) & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
5 .entity move_groundentity; // FIXME add move_groundnetworkentity?
6 .float move_suspendedinair;
7 .float move_didgravity;
9 void _Movetype_CheckVelocity() // SV_CheckVelocity
14 int Mod_Q1BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents)
16 switch(nativecontents)
21 return SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE;
23 return SUPERCONTENTS_WATER;
25 return SUPERCONTENTS_SLIME;
27 return SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP;
29 return SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP | SUPERCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
33 int Mod_Q1BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents)
35 if (supercontents & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY))
36 return CONTENTS_SOLID;
37 if (supercontents & SUPERCONTENTS_SKY)
39 if (supercontents & SUPERCONTENTS_LAVA)
41 if (supercontents & SUPERCONTENTS_SLIME)
42 return CONTENTS_SLIME;
43 if (supercontents & SUPERCONTENTS_WATER)
44 return CONTENTS_WATER;
45 return CONTENTS_EMPTY;
47 static qboolean SV_CheckWater (prvm_edict_t *ent)
49 prvm_prog_t *prog = SVVM_prog;
54 point[0] = PRVM_serveredictvector(ent, origin)[0];
55 point[1] = PRVM_serveredictvector(ent, origin)[1];
56 point[2] = PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, mins)[2] + 1;
58 // DRESK - Support for Entity Contents Transition Event
59 // NOTE: Some logic needed to be slightly re-ordered
60 // to not affect performance and allow for the feature.
62 // Acquire Super Contents Prior to Resets
63 cont = SV_PointSuperContents(point);
64 // Acquire Native Contents Here
65 nNativeContents = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, cont);
67 // DRESK - Support for Entity Contents Transition Event
68 if(PRVM_serveredictfloat(ent, watertype))
69 // Entity did NOT Spawn; Check
70 SV_CheckContentsTransition(ent, nNativeContents);
73 PRVM_serveredictfloat(ent, waterlevel) = 0;
74 PRVM_serveredictfloat(ent, watertype) = CONTENTS_EMPTY;
75 cont = SV_PointSuperContents(point);
76 if (cont & (SUPERCONTENTS_LIQUIDSMASK))
78 PRVM_serveredictfloat(ent, watertype) = nNativeContents;
79 PRVM_serveredictfloat(ent, waterlevel) = 1;
80 point[2] = PRVM_serveredictvector(ent, origin)[2] + (PRVM_serveredictvector(ent, mins)[2] + PRVM_serveredictvector(ent, maxs)[2])*0.5;
81 if (SV_PointSuperContents(point) & (SUPERCONTENTS_LIQUIDSMASK))
83 PRVM_serveredictfloat(ent, waterlevel) = 2;
84 point[2] = PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, view_ofs)[2];
85 if (SV_PointSuperContents(point) & (SUPERCONTENTS_LIQUIDSMASK))
86 PRVM_serveredictfloat(ent, waterlevel) = 3;
90 return PRVM_serveredictfloat(ent, waterlevel) > 1;
92 static int SV_CheckContentsTransition(prvm_edict_t *ent, const int nContents)
94 prvm_prog_t *prog = SVVM_prog;
95 int bValidFunctionCall;
97 // Default Valid Function Call to False
98 bValidFunctionCall = false;
100 if(PRVM_serveredictfloat(ent, watertype) != nContents)
101 { // Changed Contents
102 // Acquire Contents Transition Function from QC
103 if(PRVM_serveredictfunction(ent, contentstransition))
104 { // Valid Function; Execute
105 // Assign Valid Function
106 bValidFunctionCall = true;
107 // Prepare Parameters (Original Contents, New Contents)
109 PRVM_G_FLOAT(OFS_PARM0) = PRVM_serveredictfloat(ent, watertype);
111 PRVM_G_FLOAT(OFS_PARM1) = nContents;
113 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent);
115 PRVM_serverglobalfloat(time) = sv.time;
116 // Execute VM Function
117 prog->ExecuteProgram(prog, PRVM_serveredictfunction(ent, contentstransition), "contentstransition: NULL function");
121 // Return if Function Call was Valid
122 return bValidFunctionCall;
125 static void SV_CheckWaterTransition (prvm_edict_t *ent)
128 prvm_prog_t *prog = SVVM_prog;
129 // LordHavoc: bugfixes in this function are keyed to the sv_gameplayfix_bugfixedcheckwatertransition cvar - if this cvar is 0 then all the original bugs should be reenabled for compatibility
131 VectorCopy(PRVM_serveredictvector(ent, origin), entorigin);
132 cont = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, SV_PointSuperContents(entorigin));
133 if (!PRVM_serveredictfloat(ent, watertype))
136 if (!sv_gameplayfix_fixedcheckwatertransition.integer)
138 PRVM_serveredictfloat(ent, watertype) = cont;
139 PRVM_serveredictfloat(ent, waterlevel) = 1;
143 // DRESK - Support for Entity Contents Transition Event
144 // NOTE: Call here BEFORE updating the watertype below,
145 // and suppress watersplash sound if a valid function
146 // call was made to allow for custom "splash" sounds.
147 else if( !SV_CheckContentsTransition(ent, cont) )
148 { // Contents Transition Function Invalid; Potentially Play Water Sound
149 // check if the entity crossed into or out of water
150 if (sv_sound_watersplash.string && ((PRVM_serveredictfloat(ent, watertype) == CONTENTS_WATER || PRVM_serveredictfloat(ent, watertype) == CONTENTS_SLIME) != (cont == CONTENTS_WATER || cont == CONTENTS_SLIME)))
151 SV_StartSound (ent, 0, sv_sound_watersplash.string, 255, 1, false, 1.0f);
154 if (cont <= CONTENTS_WATER)
156 PRVM_serveredictfloat(ent, watertype) = cont;
157 PRVM_serveredictfloat(ent, waterlevel) = 1;
161 PRVM_serveredictfloat(ent, watertype) = CONTENTS_EMPTY;
162 PRVM_serveredictfloat(ent, waterlevel) = sv_gameplayfix_fixedcheckwatertransition.integer ? 0 : cont;
169 float Mod_Q1BSP_SuperContentsFromNativeContents(float nativecontents)
171 switch(nativecontents)
176 return DPCONTENTS_SOLID | DPCONTENTS_OPAQUE;
178 return DPCONTENTS_WATER;
180 return DPCONTENTS_SLIME;
182 return DPCONTENTS_LAVA | DPCONTENTS_NODROP;
184 return DPCONTENTS_SKY | DPCONTENTS_NODROP | DPCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque
189 float Mod_Q1BSP_NativeContentsFromSuperContents(float supercontents)
191 if (supercontents & (DPCONTENTS_SOLID | DPCONTENTS_BODY))
192 return CONTENT_SOLID;
193 if (supercontents & DPCONTENTS_SKY)
195 if (supercontents & DPCONTENTS_LAVA)
197 if (supercontents & DPCONTENTS_SLIME)
198 return CONTENT_SLIME;
199 if (supercontents & DPCONTENTS_WATER)
200 return CONTENT_WATER;
201 return CONTENT_EMPTY;
204 float _Movetype_CheckWater(entity ent) // SV_CheckWater
207 float nativecontents;
210 point = ent.move_origin;
211 point_z += (ent.mins_z + 1);
213 contents = pointcontents(point);
214 nativecontents = Mod_Q1BSP_NativeContentsFromSuperContents(contents);
217 if(ent.watertype != nativecontents)
219 print(sprintf("_Movetype_CheckWater(): Original: '%d', New: '%d'\n", ent.watertype, nativecontents));
220 //ent.contentstransition(ent.watertype, contents);
224 ent.watertype = CONTENT_EMPTY;
226 contents = pointcontents(point);
227 if(contents & DPCONTENTS_LIQUIDSMASK)
229 ent.watertype = nativecontents;
231 point_y = (ent.origin_y + ((ent.mins_z + ent.maxs_y) * 0.5));
232 if(pointcontents(point) & DPCONTENTS_LIQUIDSMASK)
235 point_y = ent.origin_y + ent.view_ofs_y;
236 if(pointcontents(point) & DPCONTENTS_LIQUIDSMASK)
241 return (ent.waterlevel > 1);
245 void _Movetype_CheckContentsTransition(entity ent, float contents)
247 if(ent.watertype != contents)
249 print(sprintf("_Movetype_CheckWaterTransition(): Original: '%d', New: '%d'\n", ent.watertype, contents));
250 //ent.contentstransition(ent.watertype, contents);
255 float autocvar_sv_gameplayfix_fixedcheckwatertransition;
256 void _Movetype_CheckWaterTransition(entity ent) // SV_CheckWaterTransition
258 float contents = Mod_Q1BSP_NativeContentsFromSuperContents(pointcontents(ent.move_origin));
263 if(!autocvar_sv_gameplayfix_fixedcheckwatertransition)
265 ent.watertype = contents;
270 //else if(!_Movetype_CheckContentsTransition(ent, contents))
272 //if (sv_sound_watersplash.string && ((PRVM_serveredictfloat(ent, watertype) == CONTENTS_WATER || PRVM_serveredictfloat(ent, watertype) == CONTENTS_SLIME) != (cont == CONTENTS_WATER || cont == CONTENTS_SLIME)))
273 // SV_StartSound (ent, 0, sv_sound_watersplash.string, 255, 1, false, 1.0f);
275 else if(ent.watertype != contents)
277 print(sprintf("_Movetype_CheckWaterTransition(): Original: '%d', New: '%d'\n", ent.watertype, contents));
278 //ent.contentstransition(ent.watertype, contents);
281 if(contents <= CONTENT_WATER)
283 ent.watertype = contents;
288 ent.watertype = CONTENT_EMPTY;
289 ent.waterlevel = (autocvar_sv_gameplayfix_fixedcheckwatertransition ? 0 : contents);
293 void _Movetype_Impact(entity oth) // SV_Impact
295 entity oldother, oldself;
321 void _Movetype_LinkEdict_TouchAreaGrid() // SV_LinkEdict_TouchAreaGrid
323 entity e, oldself, oldother;
328 for(e = findradius(0.5 * (self.absmin + self.absmax), 0.5 * vlen(self.absmax - self.absmin)); e; e = e.chain)
331 if(boxesoverlap(e.absmin, e.absmax, oldself.absmin, oldself.absmax))
336 trace_allsolid = FALSE;
337 trace_startsolid = FALSE;
339 trace_inwater = FALSE;
341 trace_endpos = e.origin;
342 trace_plane_normal = '0 0 1';
343 trace_plane_dist = 0;
354 void _Movetype_LinkEdict(float touch_triggers) // SV_LinkEdict
357 if(self.solid == SOLID_BSP)
359 // TODO set the absolute bbox
368 mi = mi + self.origin;
369 ma = ma + self.origin;
371 if(self.move_flags & FL_ITEM)
394 _Movetype_LinkEdict_TouchAreaGrid();
397 float _Movetype_TestEntityPosition(vector ofs) // SV_TestEntityPosition
401 org = self.move_origin + ofs;
403 cont = self.dphitcontentsmask;
404 self.dphitcontentsmask = DPCONTENTS_SOLID;
405 tracebox(self.move_origin, self.mins, self.maxs, self.move_origin, MOVE_NOMONSTERS, self);
406 self.dphitcontentsmask = cont;
411 if(vlen(trace_endpos - self.move_origin) > 0.0001)
412 self.move_origin = trace_endpos;
416 float _Movetype_UnstickEntity() // SV_UnstickEntity
418 if(!_Movetype_TestEntityPosition('0 0 0'))
420 if(!_Movetype_TestEntityPosition('-1 0 0')) goto success;
421 if(!_Movetype_TestEntityPosition('1 0 0')) goto success;
422 if(!_Movetype_TestEntityPosition('0 -1 0')) goto success;
423 if(!_Movetype_TestEntityPosition('0 1 0')) goto success;
424 if(!_Movetype_TestEntityPosition('-1 -1 0')) goto success;
425 if(!_Movetype_TestEntityPosition('1 -1 0')) goto success;
426 if(!_Movetype_TestEntityPosition('-1 1 0')) goto success;
427 if(!_Movetype_TestEntityPosition('1 1 0')) goto success;
429 for(i = 1; i <= 17; ++i)
431 if(!_Movetype_TestEntityPosition('0 0 -1' * i)) goto success;
432 if(!_Movetype_TestEntityPosition('0 0 1' * i)) goto success;
434 dprint(sprintf(_("Can't unstick an entity (edict: %d, classname: %s, origin: %s)\n"), num_for_edict(self), self.classname, vtos(self.move_origin)));
437 dprint(sprintf(_("Sucessfully unstuck an entity (edict: %d, classname: %s, origin: %s)\n"), num_for_edict(self), self.classname, vtos(self.move_origin)));
438 _Movetype_LinkEdict(TRUE);
442 vector _Movetype_ClipVelocity(vector vel, vector norm, float f) // SV_ClipVelocity
444 vel = vel - ((vel * norm) * norm) * f;
446 if(vel_x > -0.1 && vel_x < 0.1) vel_x = 0;
447 if(vel_y > -0.1 && vel_y < 0.1) vel_y = 0;
448 if(vel_z > -0.1 && vel_z < 0.1) vel_z = 0;
453 void _Movetype_PushEntityTrace(vector push)
458 end = self.move_origin + push;
460 if(self.move_nomonsters)
461 type = max(0, self.move_nomonsters);
462 else if(self.move_movetype == MOVETYPE_FLYMISSILE)
464 else if(self.solid == SOLID_TRIGGER || self.solid == SOLID_NOT)
465 type = MOVE_NOMONSTERS;
469 tracebox(self.move_origin, self.mins, self.maxs, end, type, self);
472 float _Movetype_PushEntity(vector push, float failonstartsolid) // SV_PushEntity
474 _Movetype_PushEntityTrace(push);
476 if(trace_startsolid && failonstartsolid)
477 return trace_fraction;
479 self.move_origin = trace_endpos;
481 if(trace_fraction < 1)
482 if(self.solid >= SOLID_TRIGGER && (!(self.move_flags & FL_ONGROUND) || (self.move_groundentity != trace_ent)))
483 _Movetype_Impact(trace_ent);
485 return trace_fraction;
488 #define MAX_CLIP_PLANES 5
489 void _Movetype_Physics_Toss(float dt) // SV_Physics_Toss
491 if(self.move_flags & FL_ONGROUND)
493 if(self.move_velocity_z >= 1/32)
494 self.move_flags &= ~FL_ONGROUND;
495 else if(!self.move_groundentity)
497 else if(self.move_suspendedinair && wasfreed(self.move_groundentity))
499 self.move_groundentity = world;
504 self.move_suspendedinair = FALSE;
506 _Movetype_CheckVelocity();
508 if(self.move_movetype == MOVETYPE_BOUNCE || self.move_movetype == MOVETYPE_TOSS)
510 self.move_didgravity = 1;
511 if(GRAVITY_UNAFFECTED_BY_TICRATE)
514 self.move_velocity_z -= 0.5 * dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
516 self.move_velocity_z -= 0.5 * dt * getstatf(STAT_MOVEVARS_GRAVITY);
521 self.move_velocity_z -= dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
523 self.move_velocity_z -= dt * getstatf(STAT_MOVEVARS_GRAVITY);
527 self.move_angles = self.move_angles + self.move_avelocity * dt;
529 float movetime, bump;
531 for(bump = 0; bump < MAX_CLIP_PLANES && movetime > 0; ++bump)
534 move = self.move_velocity * movetime;
535 _Movetype_PushEntity(move, TRUE);
541 _Movetype_UnstickEntity();
542 _Movetype_PushEntity(move, FALSE);
547 if(trace_fraction == 1)
550 movetime *= 1 - min(1, trace_fraction);
552 if(self.move_movetype == MOVETYPE_BOUNCEMISSILE)
554 self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 2.0);
555 self.move_flags &= ~FL_ONGROUND;
557 else if(self.move_movetype == MOVETYPE_BOUNCE)
559 float d, bouncefac, bouncestop;
561 bouncefac = self.move_bounce_factor; if(!bouncefac) bouncefac = 0.5;
562 bouncestop = self.move_bounce_stopspeed; if(!bouncestop) bouncestop = 60 / 800;
564 bouncestop *= self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
566 bouncestop *= getstatf(STAT_MOVEVARS_GRAVITY);
568 self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 1 + bouncefac);
570 d = trace_plane_normal * self.move_velocity;
571 if(trace_plane_normal_z > 0.7 && d < bouncestop && d > -bouncestop)
573 self.move_flags |= FL_ONGROUND;
574 self.move_groundentity = trace_ent;
575 self.move_velocity = '0 0 0';
576 self.move_avelocity = '0 0 0';
579 self.move_flags &= ~FL_ONGROUND;
583 self.move_velocity = _Movetype_ClipVelocity(self.move_velocity, trace_plane_normal, 1.0);
584 if(trace_plane_normal_z > 0.7)
586 self.move_flags |= FL_ONGROUND;
587 self.move_groundentity = trace_ent;
588 if(trace_ent.solid == SOLID_BSP)
589 self.move_suspendedinair = TRUE;
590 self.move_velocity = '0 0 0';
591 self.move_avelocity = '0 0 0';
594 self.move_flags &= ~FL_ONGROUND;
597 // DP revision 8905 (just, WHY...)
598 if(self.move_movetype == MOVETYPE_BOUNCEMISSILE)
601 // DP revision 8918 (WHY...)
602 if(self.move_flags & FL_ONGROUND)
606 if(GRAVITY_UNAFFECTED_BY_TICRATE)
607 if(self.move_didgravity > 0)
608 if(!(self.move_flags & FL_ONGROUND))
611 self.move_velocity_z -= 0.5 * dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
613 self.move_velocity_z -= 0.5 * dt * getstatf(STAT_MOVEVARS_GRAVITY);
616 _Movetype_CheckWaterTransition(self);
619 void _Movetype_Physics_Frame(float movedt)
621 self.move_didgravity = -1;
622 switch(self.move_movetype)
625 case MOVETYPE_FAKEPUSH:
626 error("SV_Physics_Pusher not implemented");
630 case MOVETYPE_FOLLOW:
631 error("SV_Physics_Follow not implemented");
633 case MOVETYPE_NOCLIP:
634 _Movetype_CheckWater(self);
635 self.move_origin = self.move_origin + ticrate * self.move_velocity;
636 self.move_angles = self.move_angles + ticrate * self.move_avelocity;
637 _Movetype_LinkEdict(FALSE);
640 error("SV_Physics_Step not implemented");
643 error("SV_Physics_Walk not implemented");
646 case MOVETYPE_BOUNCE:
647 case MOVETYPE_BOUNCEMISSILE:
648 case MOVETYPE_FLYMISSILE:
650 _Movetype_Physics_Toss(movedt);
655 void Movetype_Physics_NoMatchServer() // optimized
659 movedt = time - self.move_time;
660 self.move_time = time;
662 _Movetype_Physics_Frame(movedt);
666 self.avelocity = self.move_avelocity;
667 self.velocity = self.move_velocity;
668 self.angles = self.move_angles;
669 setorigin(self, self.move_origin);
672 void Movetype_Physics_MatchServer(float sloppy)
674 Movetype_Physics_MatchTicrate(ticrate, sloppy);
677 void Movetype_Physics_MatchTicrate(float tr, float sloppy) // SV_Physics_Entity
679 float n, i, dt, movedt;
683 Movetype_Physics_NoMatchServer();
687 dt = time - self.move_time;
690 n = max(0, floor(dt / tr));
692 self.move_time += n * tr;
694 if(!self.move_didgravity)
695 self.move_didgravity = ((self.move_movetype == MOVETYPE_BOUNCE || self.move_movetype == MOVETYPE_TOSS) && !(self.move_flags & FL_ONGROUND));
697 for(i = 0; i < n; ++i)
699 _Movetype_Physics_Frame(movedt);
704 self.avelocity = self.move_avelocity;
706 if(dt > 0 && self.move_movetype != MOVETYPE_NONE && !(self.move_flags & FL_ONGROUND))
708 // now continue the move from move_time to time
709 self.velocity = self.move_velocity;
711 if(self.move_didgravity > 0)
713 if(GRAVITY_UNAFFECTED_BY_TICRATE)
716 self.velocity_z -= 0.5 * dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
718 self.velocity_z -= 0.5 * dt * getstatf(STAT_MOVEVARS_GRAVITY);
723 self.velocity_z -= dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
725 self.velocity_z -= dt * getstatf(STAT_MOVEVARS_GRAVITY);
729 self.angles = self.move_angles + dt * self.avelocity;
731 if(sloppy || self.movetype == MOVETYPE_NOCLIP)
733 setorigin(self, self.move_origin + dt * self.velocity);
737 _Movetype_PushEntityTrace(dt * self.velocity);
738 if(!trace_startsolid)
739 setorigin(self, trace_endpos);
742 if(self.move_didgravity > 0)
744 if(GRAVITY_UNAFFECTED_BY_TICRATE)
747 self.velocity_z -= 0.5 * dt * self.gravity * getstatf(STAT_MOVEVARS_GRAVITY);
749 self.velocity_z -= 0.5 * dt * getstatf(STAT_MOVEVARS_GRAVITY);
755 self.velocity = self.move_velocity;
756 self.angles = self.move_angles;
757 setorigin(self, self.move_origin);