]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into TimePath/physics
authorTimePath <andrew.hardaker1995@gmail.com>
Sun, 31 Jul 2016 09:49:35 +0000 (19:49 +1000)
committerTimePath <andrew.hardaker1995@gmail.com>
Sun, 31 Jul 2016 10:26:14 +0000 (20:26 +1000)
# Conflicts:
# qcsrc/common/physics/player.qc
# qcsrc/common/triggers/trigger/jumppads.qc
# qcsrc/common/weapons/weapon/electro.qc
# qcsrc/server/g_world.qc

13 files changed:
1  2 
qcsrc/common/physics/movetypes/movetypes.qh
qcsrc/common/physics/player.qc
qcsrc/common/physics/player.qh
qcsrc/common/triggers/trigger/jumppads.qc
qcsrc/common/weapons/weapon/electro.qc
qcsrc/ecs/components/physics.qh
qcsrc/ecs/systems/cl_physics.qc
qcsrc/ecs/systems/physics.qc
qcsrc/lib/_all.inc
qcsrc/lib/iter.qh
qcsrc/server/g_world.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/sv_main.qc

index 764b993805ad55ec44a864e806e79dd98ad5529d,6b75d81c02eac80ec652a958c0903eef7e936689..35a73d3331755d5f24b04ef175ce3f3208bf7420
@@@ -5,32 -5,35 +5,31 @@@
  #define SET_ONGROUND(s)                     ((s).flags |= FL_ONGROUND)
  #define UNSET_ONGROUND(s)                   ((s).flags &= ~FL_ONGROUND)
  
- .float move_ltime;
- .void(entity this) move_think;
- .float move_nextthink;
- .void(entity this) move_blocked;
+ #ifdef CSQC
+ .float bouncestop;
+ .float bouncefactor;
+ #endif
 -#ifdef SVQC
 -.bool move_qcphysics;
 -#endif
 -
+ void set_movetype(entity this, int mt);
  
  .float move_movetype;
  .float move_time;
- .vector move_origin;
- .vector move_angles;
- .vector move_velocity;
- .vector move_avelocity;
- .int move_flags;
- .int move_watertype;
- .int move_waterlevel;
//.vector move_origin;
//.vector move_angles;
//.vector move_velocity;
//.vector move_avelocity;
//.int move_flags;
//.int move_watertype;
//.int move_waterlevel;
  .void(float, float)contentstransition;
- .float move_bounce_factor;
- .float move_bounce_stopspeed;
//.float move_bounce_factor;
//.float move_bounce_stopspeed;
  .float move_nomonsters;  // -1 for MOVE_NORMAL, otherwise a MOVE_ constant
  
  .entity aiment;
  .vector punchangle;
  
- // should match sv_gameplayfix_fixedcheckwatertransition
- float autocvar_cl_gameplayfix_fixedcheckwatertransition = 1;
- .entity move_groundentity;  // FIXME add move_groundnetworkentity?
+ .entity groundentity;  // FIXME add move_groundnetworkentity?
  .float move_suspendedinair;
  .float move_didgravity;
  
@@@ -46,6 -49,7 +45,7 @@@ void _Movetype_PushEntityTrace(entity t
  float _Movetype_PushEntity(entity this, vector push, float failonstartsolid);
  void makevectors_matrix(vector myangles);
  
+ void Movetype_Physics_NoMatchTicrate(entity this, float movedt, bool isclient);
  void Movetype_Physics_MatchTicrate(entity this, float tr, bool sloppy);
  void Movetype_Physics_MatchServer(entity this, bool sloppy);
  void Movetype_Physics_NoMatchServer(entity this);
@@@ -75,6 -79,9 +75,9 @@@ const int MOVETYPE_FLY_WORLDONLY    = 3
  
  const int FL_ITEM                   = 256;
  const int FL_ONGROUND                         = 512;
+ #elif defined(SVQC)
+ const int MOVETYPE_ANGLENOCLIP      = 1;
+ const int MOVETYPE_ANGLECLIP        = 2;
  #endif
  
  const int MOVETYPE_FAKEPUSH         = 13;
index 907784d356ad96e31abf6f61997dea106fb323ae,1ef5faa59d87554dd9bad0d251e1ce687fa0c205..74a44252fe8bd6d7fc8a11a177ca7ad9c1dfd8ee
@@@ -13,7 -13,7 +13,7 @@@ bool Physics_Valid(string thecvar
        return autocvar_g_physics_clientselect && strhasword(autocvar_g_physics_clientselect_options, thecvar);
  }
  
- float Physics_ClientOption(entity this, string option)
+ float Physics_ClientOption(entity this, string option, float defaultval)
  {
        if(Physics_Valid(this.cvar_cl_physics))
        {
                if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
                        return cvar(s);
        }
-       return cvar(strcat("sv_", option));
+       return defaultval;
  }
  
  void Physics_UpdateStats(entity this, float maxspd_mod)
  {
-       STAT(MOVEVARS_AIRACCEL_QW, this) = AdjustAirAccelQW(Physics_ClientOption(this, "airaccel_qw"), maxspd_mod);
-       STAT(MOVEVARS_AIRSTRAFEACCEL_QW, this) = (Physics_ClientOption(this, "airstrafeaccel_qw"))
-               ? AdjustAirAccelQW(Physics_ClientOption(this, "airstrafeaccel_qw"), maxspd_mod)
+       STAT(MOVEVARS_AIRACCEL_QW, this) = AdjustAirAccelQW(Physics_ClientOption(this, "airaccel_qw", autocvar_sv_airaccel_qw), maxspd_mod);
+       STAT(MOVEVARS_AIRSTRAFEACCEL_QW, this) = (Physics_ClientOption(this, "airstrafeaccel_qw", autocvar_sv_airstrafeaccel_qw))
+               ? AdjustAirAccelQW(Physics_ClientOption(this, "airstrafeaccel_qw", autocvar_sv_airstrafeaccel_qw), maxspd_mod)
                : 0;
-       STAT(MOVEVARS_AIRSPEEDLIMIT_NONQW, this) = Physics_ClientOption(this, "airspeedlimit_nonqw") * maxspd_mod;
-       STAT(MOVEVARS_MAXSPEED, this) = Physics_ClientOption(this, "maxspeed") * maxspd_mod; // also slow walking
+       STAT(MOVEVARS_AIRSPEEDLIMIT_NONQW, this) = Physics_ClientOption(this, "airspeedlimit_nonqw", autocvar_sv_airspeedlimit_nonqw) * maxspd_mod;
+       STAT(MOVEVARS_MAXSPEED, this) = Physics_ClientOption(this, "maxspeed", autocvar_sv_maxspeed) * maxspd_mod; // also slow walking
  
        // old stats
        // fix some new settings
-       STAT(MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, this) = Physics_ClientOption(this, "airaccel_qw_stretchfactor");
-       STAT(MOVEVARS_MAXAIRSTRAFESPEED, this) = Physics_ClientOption(this, "maxairstrafespeed");
-       STAT(MOVEVARS_MAXAIRSPEED, this) = Physics_ClientOption(this, "maxairspeed");
-       STAT(MOVEVARS_AIRSTRAFEACCELERATE, this) = Physics_ClientOption(this, "airstrafeaccelerate");
-       STAT(MOVEVARS_WARSOWBUNNY_TURNACCEL, this) = Physics_ClientOption(this, "warsowbunny_turnaccel");
-       STAT(MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, this) = Physics_ClientOption(this, "airaccel_sideways_friction");
-       STAT(MOVEVARS_AIRCONTROL, this) = Physics_ClientOption(this, "aircontrol");
-       STAT(MOVEVARS_AIRCONTROL_POWER, this) = Physics_ClientOption(this, "aircontrol_power");
-       STAT(MOVEVARS_AIRCONTROL_PENALTY, this) = Physics_ClientOption(this, "aircontrol_penalty");
-       STAT(MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL, this) = Physics_ClientOption(this, "warsowbunny_airforwardaccel");
-       STAT(MOVEVARS_WARSOWBUNNY_TOPSPEED, this) = Physics_ClientOption(this, "warsowbunny_topspeed");
-       STAT(MOVEVARS_WARSOWBUNNY_ACCEL, this) = Physics_ClientOption(this, "warsowbunny_accel");
-       STAT(MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO, this) = Physics_ClientOption(this, "warsowbunny_backtosideratio");
-       STAT(MOVEVARS_FRICTION, this) = Physics_ClientOption(this, "friction");
-       STAT(MOVEVARS_ACCELERATE, this) = Physics_ClientOption(this, "accelerate");
-       STAT(MOVEVARS_STOPSPEED, this) = Physics_ClientOption(this, "stopspeed");
-       STAT(MOVEVARS_AIRACCELERATE, this) = Physics_ClientOption(this, "airaccelerate");
-       STAT(MOVEVARS_AIRSTOPACCELERATE, this) = Physics_ClientOption(this, "airstopaccelerate");
-       STAT(MOVEVARS_JUMPVELOCITY, this) = Physics_ClientOption(this, "jumpvelocity");
-       STAT(MOVEVARS_TRACK_CANJUMP, this) = Physics_ClientOption(this, "track_canjump");
+       STAT(MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, this) = Physics_ClientOption(this, "airaccel_qw_stretchfactor", autocvar_sv_airaccel_qw_stretchfactor);
+       STAT(MOVEVARS_MAXAIRSTRAFESPEED, this) = Physics_ClientOption(this, "maxairstrafespeed", autocvar_sv_maxairstrafespeed);
+       STAT(MOVEVARS_MAXAIRSPEED, this) = Physics_ClientOption(this, "maxairspeed", autocvar_sv_maxairspeed);
+       STAT(MOVEVARS_AIRSTRAFEACCELERATE, this) = Physics_ClientOption(this, "airstrafeaccelerate", autocvar_sv_airstrafeaccelerate);
+       STAT(MOVEVARS_WARSOWBUNNY_TURNACCEL, this) = Physics_ClientOption(this, "warsowbunny_turnaccel", autocvar_sv_warsowbunny_turnaccel);
+       STAT(MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, this) = Physics_ClientOption(this, "airaccel_sideways_friction", autocvar_sv_airaccel_sideways_friction);
+       STAT(MOVEVARS_AIRCONTROL, this) = Physics_ClientOption(this, "aircontrol", autocvar_sv_aircontrol);
+       STAT(MOVEVARS_AIRCONTROL_POWER, this) = Physics_ClientOption(this, "aircontrol_power", autocvar_sv_aircontrol_power);
+       STAT(MOVEVARS_AIRCONTROL_PENALTY, this) = Physics_ClientOption(this, "aircontrol_penalty", autocvar_sv_aircontrol_penalty);
+       STAT(MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL, this) = Physics_ClientOption(this, "warsowbunny_airforwardaccel", autocvar_sv_warsowbunny_airforwardaccel);
+       STAT(MOVEVARS_WARSOWBUNNY_TOPSPEED, this) = Physics_ClientOption(this, "warsowbunny_topspeed", autocvar_sv_warsowbunny_topspeed);
+       STAT(MOVEVARS_WARSOWBUNNY_ACCEL, this) = Physics_ClientOption(this, "warsowbunny_accel", autocvar_sv_warsowbunny_accel);
+       STAT(MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO, this) = Physics_ClientOption(this, "warsowbunny_backtosideratio", autocvar_sv_warsowbunny_backtosideratio);
+       STAT(MOVEVARS_FRICTION, this) = Physics_ClientOption(this, "friction", autocvar_sv_friction);
+       STAT(MOVEVARS_ACCELERATE, this) = Physics_ClientOption(this, "accelerate", autocvar_sv_accelerate);
+       STAT(MOVEVARS_STOPSPEED, this) = Physics_ClientOption(this, "stopspeed", autocvar_sv_stopspeed);
+       STAT(MOVEVARS_AIRACCELERATE, this) = Physics_ClientOption(this, "airaccelerate", autocvar_sv_airaccelerate);
+       STAT(MOVEVARS_AIRSTOPACCELERATE, this) = Physics_ClientOption(this, "airstopaccelerate", autocvar_sv_airstopaccelerate);
+       STAT(MOVEVARS_JUMPVELOCITY, this) = Physics_ClientOption(this, "jumpvelocity", autocvar_sv_jumpvelocity);
+       STAT(MOVEVARS_TRACK_CANJUMP, this) = Physics_ClientOption(this, "track_canjump", autocvar_sv_track_canjump);
  }
  #endif
  
@@@ -73,11 -73,11 +73,11 @@@ float IsMoveInDirection(vector mv, floa
        return ang > 1 ? 0 : ang < -1 ? 0 : 1 - fabs(ang);
  }
  
- float GeomLerp(float a, float lerp, float b)
+ float GeomLerp(float a, float _lerp, float b)
  {
-       return a == 0 ? (lerp < 1 ? 0 : b)
-               : b == 0 ? (lerp > 0 ? 0 : a)
-               : a * pow(fabs(b / a), lerp);
+       return a == 0 ? (_lerp < 1 ? 0 : b)
+               : b == 0 ? (_lerp > 0 ? 0 : a)
+               : a * pow(fabs(b / a), _lerp);
  }
  
  #define unstick_offsets(X) \
@@@ -118,24 -118,25 +118,25 @@@ void PM_ClientMovement_Unstick(entity t
        #undef X
  }
  
- void PM_ClientMovement_UpdateStatus(entity this, bool ground)
+ void PM_ClientMovement_UpdateStatus(entity this)
  {
  #ifdef CSQC
        if(!IS_PLAYER(this))
                return;
-       // make sure player is not stuck
-       if(autocvar_cl_movement == 3)
-               PM_ClientMovement_Unstick(this);
  
        // set crouched
        bool do_crouch = PHYS_INPUT_BUTTON_CROUCH(this);
        if(this.hook && !wasfreed(this.hook))
                do_crouch = false;
+       if(this.waterlevel >= WATERLEVEL_SWIMMING)
+               do_crouch = false;
        if(hud != HUD_NORMAL)
                do_crouch = false;
        if(STAT(FROZEN, this))
                do_crouch = false;
-       if((activeweapon == WEP_SHOCKWAVE || activeweapon == WEP_SHOTGUN) && viewmodel.animstate_startframe == viewmodel.anim_fire2_x && time < viewmodel.weapon_nextthink)
+       if((activeweapon.spawnflags & WEP_TYPE_MELEE_PRI) && viewmodel.animstate_startframe == viewmodel.anim_fire1_x && time < viewmodel.weapon_nextthink)
+               do_crouch = false;
+       if((activeweapon.spawnflags & WEP_TYPE_MELEE_SEC) && viewmodel.animstate_startframe == viewmodel.anim_fire2_x && time < viewmodel.weapon_nextthink)
                do_crouch = false;
  
        if (do_crouch)
                }
        }
  
-       // set onground
-       vector origin1 = this.origin + '0 0 1';
-       vector origin2 = this.origin - '0 0 1';
-       if (ground && autocvar_cl_movement == 3)
-       {
-               tracebox(origin1, this.mins, this.maxs, origin2, MOVE_NORMAL, this);
-               if (trace_fraction < 1.0 && trace_plane_normal.z > 0.7)
-               {
-                       SET_ONGROUND(this);
-                       // this code actually "predicts" an impact; so let's clip velocity first
-                       this.velocity -= this.velocity * trace_plane_normal * trace_plane_normal;
-               }
-               else
-                       UNSET_ONGROUND(this);
-       }
-       if(autocvar_cl_movement == 3)
-       {
-               // set watertype/waterlevel
-               origin1 = this.origin;
-               origin1.z += this.mins_z + 1;
-               this.waterlevel = WATERLEVEL_NONE;
-               int thepoint = pointcontents(origin1);
-               this.watertype = (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME);
-               if (this.watertype)
-               {
-                       this.waterlevel = WATERLEVEL_WETFEET;
-                       origin1.z = this.origin.z + (this.mins.z + this.maxs.z) * 0.5;
-                       thepoint = pointcontents(origin1);
-                       if (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME)
-                       {
-                               this.waterlevel = WATERLEVEL_SWIMMING;
-                               origin1.z = this.origin.z + 22;
-                               thepoint = pointcontents(origin1);
-                               if (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME)
-                                       this.waterlevel = WATERLEVEL_SUBMERGED;
-                       }
-               }
-       }
 -      if (IS_ONGROUND(this) || this.velocity.z <= 0 || pmove_waterjumptime <= 0)
 -              pmove_waterjumptime = 0;
 +      if (IS_ONGROUND(this) || this.velocity.z <= 0 || PHYS_WATERJUMP_TIME(this) <= 0)
 +              PHYS_WATERJUMP_TIME(this) = 0;
  #endif
  }
  
- void PM_ClientMovement_Move(entity this)
- {
- #ifdef CSQC
-       PM_ClientMovement_UpdateStatus(this, false);
-       if(autocvar_cl_movement == 1)
-               return;
-       int bump;
-       float t;
-       float f;
-       vector neworigin;
-       vector currentorigin2;
-       vector neworigin2;
-       vector primalvelocity;
-       vector trace1_endpos = '0 0 0';
-       vector trace2_endpos = '0 0 0';
-       vector trace3_endpos = '0 0 0';
-       float trace1_fraction = 0;
-       float trace2_fraction = 0;
-       float trace3_fraction = 0;
-       vector trace1_plane_normal = '0 0 0';
-       vector trace2_plane_normal = '0 0 0';
-       vector trace3_plane_normal = '0 0 0';
-       primalvelocity = this.velocity;
-       for(bump = 0, t = PHYS_INPUT_TIMELENGTH; bump < 8 && (this.velocity * this.velocity) > 0; bump++)
-       {
-               neworigin = this.origin + t * this.velocity;
-               tracebox(this.origin, this.mins, this.maxs, neworigin, MOVE_NORMAL, this);
-               trace1_endpos = trace_endpos;
-               trace1_fraction = trace_fraction;
-               trace1_plane_normal = trace_plane_normal;
-               if(trace1_fraction < 1 && trace1_plane_normal_z == 0)
-               {
-                       // may be a step or wall, try stepping up
-                       // first move forward at a higher level
-                       currentorigin2 = this.origin;
-                       currentorigin2_z += PHYS_STEPHEIGHT(this);
-                       neworigin2 = neworigin;
-                       neworigin2_z += PHYS_STEPHEIGHT(this);
-                       tracebox(currentorigin2, this.mins, this.maxs, neworigin2, MOVE_NORMAL, this);
-                       trace2_endpos = trace_endpos;
-                       trace2_fraction = trace_fraction;
-                       trace2_plane_normal = trace_plane_normal;
-                       if(!trace_startsolid)
-                       {
-                               // then move down from there
-                               currentorigin2 = trace2_endpos;
-                               neworigin2 = trace2_endpos;
-                               neworigin2_z = this.origin_z;
-                               tracebox(currentorigin2, this.mins, this.maxs, neworigin2, MOVE_NORMAL, this);
-                               trace3_endpos = trace_endpos;
-                               trace3_fraction = trace_fraction;
-                               trace3_plane_normal = trace_plane_normal;
-                               // accept the new trace if it made some progress
-                               if(fabs(trace3_endpos_x - trace1_endpos_x) >= 0.03125 || fabs(trace3_endpos_y - trace1_endpos_y) >= 0.03125)
-                               {
-                                       trace1_endpos = trace2_endpos;
-                                       trace1_fraction = trace2_fraction;
-                                       trace1_plane_normal = trace2_plane_normal;
-                                       trace1_endpos = trace3_endpos;
-                               }
-                       }
-               }
-               // check if it moved at all
-               if(trace1_fraction >= 0.001)
-                       setorigin(this, trace1_endpos);
-               // check if it moved all the way
-               if(trace1_fraction == 1)
-                       break;
-               // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate
-               // <LordHavoc> I'm pretty sure I commented it out solely because it seemed redundant
-               // this got commented out in a change that supposedly makes the code match QW better
-               // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block
-               if(trace1_plane_normal_z > 0.7)
-                       SET_ONGROUND(this);
-               t -= t * trace1_fraction;
-               f = (this.velocity * trace1_plane_normal);
-               this.velocity = this.velocity + -f * trace1_plane_normal;
-       }
-       if(PHYS_TELEPORT_TIME(this) > 0)
-               this.velocity = primalvelocity;
- #endif
- }
  void CPM_PM_Aircontrol(entity this, vector wishdir, float wishspeed)
  {
        float k = 32 * (2 * IsMoveInDirection(this.movement, 0) - 1);
@@@ -541,7 -405,7 +405,7 @@@ bool PlayerJump(entity this
        animdecide_setaction(this, ANIMACTION_JUMP, true);
  
        if (autocvar_g_jump_grunt)
-               PlayerSound(this, playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
+               PlayerSound(this, playersound_jump, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
  #endif
        return true;
  }
@@@ -570,7 -434,7 +434,7 @@@ void CheckWaterJump(entity this
                #ifdef SVQC
                        PHYS_TELEPORT_TIME(this) = time + 2;    // safety net
                #elif defined(CSQC)
 -                      pmove_waterjumptime = 2;
 +                      PHYS_WATERJUMP_TIME(this) = 2;
                #endif
                }
        }
@@@ -781,7 -645,8 +645,8 @@@ void PM_check_hitground(entity this
      entity gs = (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
        ? GS_FALL_METAL
        : GS_FALL;
-     GlobalSound(this, gs, CH_PLAYER, VOICETYPE_PLAYERSOUND);
+     float vol = ((IS_DUCKED(this)) ? VOL_MUFFLED : VOL_BASE);
+     GlobalSound(this, gs, CH_PLAYER, vol, VOICETYPE_PLAYERSOUND);
  #endif
  }
  
@@@ -801,7 -666,7 +666,7 @@@ void PM_Footsteps(entity this
                entity gs = (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
                        ? GS_STEP_METAL
                        : GS_STEP;
-               GlobalSound(this, gs, CH_PLAYER, VOICETYPE_PLAYERSOUND);
+               GlobalSound(this, gs, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
        }
  #endif
  }
@@@ -816,7 -681,182 +681,7 @@@ void PM_check_blocked(entity this
  #endif
  }
  
 -void PM_fly(entity this, float maxspd_mod)
 -{
 -      // noclipping or flying
 -      UNSET_ONGROUND(this);
 -
 -      this.velocity = this.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this));
 -      makevectors(this.v_angle);
 -      //wishvel = v_forward * this.movement.x + v_right * this.movement.y + v_up * this.movement.z;
 -      vector wishvel = v_forward * this.movement.x
 -                                      + v_right * this.movement.y
 -                                      + '0 0 1' * this.movement.z;
 -      // acceleration
 -      vector wishdir = normalize(wishvel);
 -      float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(this) * maxspd_mod);
 -#ifdef SVQC
 -      if(time >= PHYS_TELEPORT_TIME(this))
 -#elif defined(CSQC)
 -      if(pmove_waterjumptime <= 0)
 -#endif
 -              PM_Accelerate(this, wishdir, wishspeed, wishspeed, PHYS_ACCELERATE(this) * maxspd_mod, 1, 0, 0, 0);
 -}
 -
 -void PM_swim(entity this, float maxspd_mod)
 -{
 -      // swimming
 -      UNSET_ONGROUND(this);
 -
 -      float jump = PHYS_INPUT_BUTTON_JUMP(this);
 -      // water jump only in certain situations
 -      // this mimics quakeworld code
 -      if (jump && this.waterlevel == WATERLEVEL_SWIMMING && this.velocity_z >= -180 && !this.viewloc)
 -      {
 -              vector yawangles = '0 1 0' * this.v_angle.y;
 -              makevectors(yawangles);
 -              vector forward = v_forward;
 -              vector spot = this.origin + 24 * forward;
 -              spot_z += 8;
 -              traceline(spot, spot, MOVE_NOMONSTERS, this);
 -              if (trace_startsolid)
 -              {
 -                      spot_z += 24;
 -                      traceline(spot, spot, MOVE_NOMONSTERS, this);
 -                      if (!trace_startsolid)
 -                      {
 -                              this.velocity = forward * 50;
 -                              this.velocity_z = 310;
 -                              UNSET_ONGROUND(this);
 -                              SET_JUMP_HELD(this);
 -                      }
 -              }
 -      }
 -      makevectors(this.v_angle);
 -      float wishdown = this.movement.z;
 -      if(PHYS_INPUT_BUTTON_CROUCH(this))
 -              wishdown = -PHYS_MAXSPEED(this);
 -      //wishvel = v_forward * this.movement.x + v_right * this.movement.y + v_up * this.movement.z;
 -      vector wishvel = v_forward * this.movement.x
 -                                      + v_right * this.movement.y
 -                                      + '0 0 1' * wishdown;
 -      if(this.viewloc)
 -              wishvel.z = -160; // drift anyway
 -      else if (wishvel == '0 0 0')
 -              wishvel = '0 0 -60'; // drift towards bottom
 -
 -      vector wishdir = normalize(wishvel);
 -      float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(this) * maxspd_mod) * 0.7;
 -
 -//    if (pmove_waterjumptime <= 0) // TODO: use
 -    {
 -              // water friction
 -              float f = 1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this);
 -              f = min(max(0, f), 1);
 -              this.velocity *= f;
 -
 -              f = wishspeed - this.velocity * wishdir;
 -              if (f > 0)
 -              {
 -                      float accelspeed = min(PHYS_ACCELERATE(this) * PHYS_INPUT_TIMELENGTH * wishspeed, f);
 -                      this.velocity += accelspeed * wishdir;
 -              }
 -
 -              // holding jump button swims upward slowly
 -              if (jump && !this.viewloc)
 -              {
 -#if 0
 -                      if (this.watertype & CONTENT_LAVA)
 -                              this.velocity_z =  50;
 -                      else if (this.watertype & CONTENT_SLIME)
 -                              this.velocity_z =  80;
 -                      else
 -                      {
 -                              if (IS_NEXUIZ_DERIVED(gamemode))
 -#endif
 -                              if(this.waterlevel >= WATERLEVEL_SUBMERGED)
 -                                      this.velocity_z = PHYS_MAXSPEED(this);
 -                              else
 -                                      this.velocity_z = 200;
 -#if 0
 -                              else
 -                                      this.velocity_z = 100;
 -                      }
 -#endif
 -              }
 -      }
 -      if(this.viewloc)
 -      {
 -              const float addspeed = wishspeed - this.velocity * wishdir;
 -              if (addspeed > 0)
 -              {
 -                      const float accelspeed = min(PHYS_ACCELERATE(this) * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
 -                      this.velocity += accelspeed * wishdir;
 -              }
 -      }
 -      else
 -      {
 -              // water acceleration
 -              PM_Accelerate(this, wishdir, wishspeed, wishspeed, PHYS_ACCELERATE(this) * maxspd_mod, 1, 0, 0, 0);
 -      }
 -}
 -
  .vector oldmovement;
 -void PM_ladder(entity this, float maxspd_mod)
 -{
 -      // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
 -      UNSET_ONGROUND(this);
 -
 -      float g;
 -      g = PHYS_GRAVITY(this) * PHYS_INPUT_TIMELENGTH;
 -      if (PHYS_ENTGRAVITY(this))
 -              g *= PHYS_ENTGRAVITY(this);
 -      if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
 -      {
 -              g *= 0.5;
 -              this.velocity_z += g;
 -      }
 -
 -      this.velocity = this.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this));
 -      makevectors(this.v_angle);
 -      //wishvel = v_forward * this.movement.x + v_right * this.movement.y + v_up * this.movement.z;
 -      vector wishvel = v_forward * this.movement_x
 -                                      + v_right * this.movement_y
 -                                      + '0 0 1' * this.movement_z;
 -      if(this.viewloc)
 -              wishvel.z = this.oldmovement.x;
 -      this.velocity_z += g;
 -      if (this.ladder_entity.classname == "func_water")
 -      {
 -              float f = vlen(wishvel);
 -              if (f > this.ladder_entity.speed)
 -                      wishvel *= (this.ladder_entity.speed / f);
 -
 -              this.watertype = this.ladder_entity.skin;
 -              f = this.ladder_entity.origin_z + this.ladder_entity.maxs_z;
 -              if ((this.origin_z + this.view_ofs_z) < f)
 -                      this.waterlevel = WATERLEVEL_SUBMERGED;
 -              else if ((this.origin_z + (this.mins_z + this.maxs_z) * 0.5) < f)
 -                      this.waterlevel = WATERLEVEL_SWIMMING;
 -              else if ((this.origin_z + this.mins_z + 1) < f)
 -                      this.waterlevel = WATERLEVEL_WETFEET;
 -              else
 -              {
 -                      this.waterlevel = WATERLEVEL_NONE;
 -                      this.watertype = CONTENT_EMPTY;
 -              }
 -      }
 -      // acceleration
 -      vector wishdir = normalize(wishvel);
 -      float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(this) * maxspd_mod);
 -#ifdef SVQC
 -      if(time >= PHYS_TELEPORT_TIME(this))
 -#elif defined(CSQC)
 -      if(pmove_waterjumptime <= 0)
 -#endif
 -              // water acceleration
 -              PM_Accelerate(this, wishdir, wishspeed, wishspeed, PHYS_ACCELERATE(this)*maxspd_mod, 1, 0, 0, 0);
 -}
  
  void PM_jetpack(entity this, float maxspd_mod)
  {
        float a_up = PHYS_JETPACK_ACCEL_UP(this);
        float a_add = PHYS_JETPACK_ANTIGRAVITY(this) * PHYS_GRAVITY(this);
  
+       if(PHYS_JETPACK_REVERSE_THRUST(this) && PHYS_INPUT_BUTTON_CROUCH(self)) { a_up = PHYS_JETPACK_REVERSE_THRUST(this); }
        wishvel_x *= a_side;
        wishvel_y *= a_side;
        wishvel_z *= a_up;
        wishvel_z += a_add;
  
+       if(PHYS_JETPACK_REVERSE_THRUST(this) && PHYS_INPUT_BUTTON_CROUCH(self)) { wishvel_z *= -1; }
        float best = 0;
        //////////////////////////////////////////////////////////////////////////////////////
        // finding the maximum over all vectors of above form
                this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
  #endif
        }
- #ifdef CSQC
-       float g = PHYS_GRAVITY(this) * PHYS_ENTGRAVITY(this) * PHYS_INPUT_TIMELENGTH;
-       if(autocvar_cl_movement == 3)
-       {
-               if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
-                       this.velocity_z -= g * 0.5;
-               else
-                       this.velocity_z -= g;
-       }
-       PM_ClientMovement_Move(this);
-       if(autocvar_cl_movement == 3)
-       {
-               if (!IS_ONGROUND(this) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
-                       if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
-                               this.velocity_z -= g * 0.5;
-       }
- #endif
  }
  
 -void PM_walk(entity this, float maxspd_mod)
 -{
 -      if (!WAS_ONGROUND(this))
 -      {
 -#ifdef SVQC
 -              if (autocvar_speedmeter)
 -                      LOG_TRACE(strcat("landing velocity: ", vtos(this.velocity), " (abs: ", ftos(vlen(this.velocity)), ")\n"));
 -#endif
 -              if (this.lastground < time - 0.3)
 -                      this.velocity *= (1 - PHYS_FRICTION_ONLAND(this));
 -#ifdef SVQC
 -              if (this.jumppadcount > 1)
 -                      LOG_TRACE(strcat(ftos(this.jumppadcount), "x jumppad combo\n"));
 -              this.jumppadcount = 0;
 -#endif
 -      }
 -
 -      // walking
 -      makevectors(this.v_angle.y * '0 1 0');
 -      const vector wishvel = v_forward * this.movement.x
 -                                              + v_right * this.movement.y;
 -      // acceleration
 -      const vector wishdir = normalize(wishvel);
 -      float wishspeed = vlen(wishvel);
 -      wishspeed = min(wishspeed, PHYS_MAXSPEED(this) * maxspd_mod);
 -      if (IS_DUCKED(this)) wishspeed *= 0.5;
 -
 -      // apply edge friction
 -      const float f2 = vlen2(vec2(this.velocity));
 -      if (f2 > 0)
 -      {
 -              trace_dphitq3surfaceflags = 0;
 -              tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
 -              // TODO: apply edge friction
 -              // apply ground friction
 -              const int realfriction = (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)
 -                      ? PHYS_FRICTION_SLICK(this)
 -                      : PHYS_FRICTION(this);
 -
 -              float f = sqrt(f2);
 -              f = 1 - PHYS_INPUT_TIMELENGTH * realfriction * ((f < PHYS_STOPSPEED(this)) ? (PHYS_STOPSPEED(this) / f) : 1);
 -              f = max(0, f);
 -              this.velocity *= f;
 -              /*
 -                 Mathematical analysis time!
 -
 -                 Our goal is to invert this mess.
 -
 -                 For the two cases we get:
 -                      v = v0 * (1 - PHYS_INPUT_TIMELENGTH * (PHYS_STOPSPEED(this) / v0) * PHYS_FRICTION(this))
 -                        = v0 - PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED(this) * PHYS_FRICTION(this)
 -                      v0 = v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED(this) * PHYS_FRICTION(this)
 -                 and
 -                      v = v0 * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this))
 -                      v0 = v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this))
 -
 -                 These cases would be chosen ONLY if:
 -                      v0 < PHYS_STOPSPEED(this)
 -                      v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED(this) * PHYS_FRICTION(this) < PHYS_STOPSPEED(this)
 -                      v < PHYS_STOPSPEED(this) * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this))
 -                 and, respectively:
 -                      v0 >= PHYS_STOPSPEED(this)
 -                      v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this)) >= PHYS_STOPSPEED(this)
 -                      v >= PHYS_STOPSPEED(this) * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this))
 -               */
 -      }
 -      const float addspeed = wishspeed - this.velocity * wishdir;
 -      if (addspeed > 0)
 -      {
 -              const float accelspeed = min(PHYS_ACCELERATE(this) * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
 -              this.velocity += accelspeed * wishdir;
 -      }
 -}
 -
 -void PM_air(entity this, float buttons_prev, float maxspd_mod)
 -{
 -      makevectors(this.v_angle.y * '0 1 0');
 -      vector wishvel = v_forward * this.movement.x
 -                                      + v_right * this.movement.y;
 -      // acceleration
 -      vector wishdir = normalize(wishvel);
 -      float wishspeed = vlen(wishvel);
 -
 -#ifdef SVQC
 -      if(time >= PHYS_TELEPORT_TIME(this))
 -#elif defined(CSQC)
 -      if(pmove_waterjumptime <= 0)
 -#endif
 -      {
 -              float maxairspd = PHYS_MAXAIRSPEED(this) * min(maxspd_mod, 1);
 -
 -              // apply air speed limit
 -              float airaccelqw = PHYS_AIRACCEL_QW(this);
 -              float wishspeed0 = wishspeed;
 -              wishspeed = min(wishspeed, maxairspd);
 -              if (IS_DUCKED(this))
 -                      wishspeed *= 0.5;
 -              float airaccel = PHYS_AIRACCELERATE(this) * min(maxspd_mod, 1);
 -
 -              float accelerating = (this.velocity * wishdir > 0);
 -              float wishspeed2 = wishspeed;
 -
 -              // CPM: air control
 -              if (PHYS_AIRSTOPACCELERATE(this))
 -              {
 -                      vector curdir = normalize(vec2(this.velocity));
 -                      airaccel += (PHYS_AIRSTOPACCELERATE(this)*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
 -              }
 -              // note that for straight forward jumping:
 -              // step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
 -              // accel  = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
 -              // -->
 -              // dv/dt = accel * maxspeed (when slow)
 -              // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
 -              // log dv/dt = logaccel + logmaxspeed (when slow)
 -              // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
 -              float strafity = IsMoveInDirection(this.movement, -90) + IsMoveInDirection(this.movement, +90); // if one is nonzero, other is always zero
 -              if (PHYS_MAXAIRSTRAFESPEED(this))
 -                      wishspeed = min(wishspeed, GeomLerp(PHYS_MAXAIRSPEED(this)*maxspd_mod, strafity, PHYS_MAXAIRSTRAFESPEED(this)*maxspd_mod));
 -              if (PHYS_AIRSTRAFEACCELERATE(this))
 -                      airaccel = GeomLerp(airaccel, strafity, PHYS_AIRSTRAFEACCELERATE(this)*maxspd_mod);
 -              if (PHYS_AIRSTRAFEACCEL_QW(this))
 -                      airaccelqw =
 -              (((strafity > 0.5 ? PHYS_AIRSTRAFEACCEL_QW(this) : PHYS_AIRACCEL_QW(this)) >= 0) ? +1 : -1)
 -              *
 -              (1 - GeomLerp(1 - fabs(PHYS_AIRACCEL_QW(this)), strafity, 1 - fabs(PHYS_AIRSTRAFEACCEL_QW(this))));
 -              // !CPM
 -
 -              if (PHYS_WARSOWBUNNY_TURNACCEL(this) && accelerating && this.movement.y == 0 && this.movement.x != 0)
 -                      PM_AirAccelerate(this, wishdir, wishspeed2);
 -              else {
 -                  float sidefric = maxairspd ? (PHYS_AIRACCEL_SIDEWAYS_FRICTION(this) / maxairspd) : 0;
 -                      PM_Accelerate(this, wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, PHYS_AIRACCEL_QW_STRETCHFACTOR(this), sidefric, PHYS_AIRSPEEDLIMIT_NONQW(this));
 -        }
 -
 -              if (PHYS_AIRCONTROL(this))
 -                      CPM_PM_Aircontrol(this, wishdir, wishspeed2);
 -      }
 -}
 -
  // used for calculating airshots
  bool IsFlying(entity this)
  {
        return true;
  }
  
 -void PM_Main(entity this)
 -{
 -      int buttons = PHYS_INPUT_BUTTON_MASK(this);
 -#ifdef CSQC
 -      this.items = STAT(ITEMS);
 -
 -      this.movement = PHYS_INPUT_MOVEVALUES(this);
 -
 -      this.spectatorspeed = STAT(SPECTATORSPEED);
 -
 -      this.team = myteam + 1; // is this correct?
 -      if (!(PHYS_INPUT_BUTTON_JUMP(this))) // !jump
 -              UNSET_JUMP_HELD(this); // canjump = true
 -      pmove_waterjumptime -= PHYS_INPUT_TIMELENGTH;
 -
 -      PM_ClientMovement_UpdateStatus(this);
 -#endif
 -
 -      this.oldmovement = this.movement;
 -
 -
 -#ifdef SVQC
 -      WarpZone_PlayerPhysics_FixVAngle(this);
 -#endif
 -      float maxspeed_mod = 1;
 -      maxspeed_mod *= PHYS_HIGHSPEED(this);
 -
 -#ifdef SVQC
 -      Physics_UpdateStats(this, maxspeed_mod);
 -
 -      if (this.PlayerPhysplug)
 -              if (this.PlayerPhysplug(this))
 -                      return;
 -#elif defined(CSQC)
 -      if(hud != HUD_NORMAL)
 -              return; // no vehicle prediction (yet)
 -#endif
 -
 -#ifdef SVQC
 -      anticheat_physics(this);
 -#endif
 -
 -      if (PM_check_specialcommand(this, buttons))
 -              return;
 -#ifdef SVQC
 -      if (sv_maxidle > 0)
 -      {
 -              if (buttons != this.buttons_old || this.movement != this.movement_old || this.v_angle != this.v_angle_old)
 -                      this.parm_idlesince = time;
 -      }
 -#endif
 -      int buttons_prev = this.buttons_old;
 -      this.buttons_old = buttons;
 -      this.movement_old = this.movement;
 -      this.v_angle_old = this.v_angle;
 -
 -      PM_check_nickspam(this);
 -
 -      PM_check_punch(this);
 -#ifdef SVQC
 -      if (IS_BOT_CLIENT(this))
 -      {
 -              if (playerdemo_read(this))
 -                      return;
 -              bot_think(this);
 -      }
 -#endif
 -
 -#ifdef SVQC
 -      if (IS_PLAYER(this))
 -      {
 -              const bool allowed_to_move = (time >= game_starttime);
 -              if (!allowed_to_move)
 -              {
 -                      this.velocity = '0 0 0';
 -                      set_movetype(this, MOVETYPE_NONE);
 -                      this.disableclientprediction = 2;
 -              }
 -              else if (this.disableclientprediction == 2)
 -              {
 -                      if (this.move_movetype == MOVETYPE_NONE)
 -                              set_movetype(this, MOVETYPE_WALK);
 -                      this.disableclientprediction = 0;
 -              }
 -      }
 -#endif
 -
 -#ifdef SVQC
 -      if (this.move_movetype == MOVETYPE_NONE)
 -              return;
 -
 -      // when we get here, disableclientprediction cannot be 2
 -      this.disableclientprediction = (this.move_qcphysics) ? -1 : 0;
 -#endif
 -
 -      viewloc_PlayerPhysics(this);
 -
 -      PM_check_frozen(this);
 -
 -      PM_check_blocked(this);
 -
 -      maxspeed_mod = 1;
 -
 -      if (this.in_swamp)
 -              maxspeed_mod *= this.swamp_slowdown; //cvar("g_balance_swamp_moverate");
 -
 -      // conveyors: first fix velocity
 -      if (this.conveyor.state)
 -              this.velocity -= this.conveyor.movedir;
 -
 -      MUTATOR_CALLHOOK(PlayerPhysics, this);
 -
 -      if (!IS_PLAYER(this))
 -      {
 -#ifdef SVQC
 -              maxspeed_mod = autocvar_sv_spectator_speed_multiplier;
 -              if (!this.spectatorspeed)
 -                      this.spectatorspeed = maxspeed_mod;
 -              if (this.impulse && this.impulse <= 19 || (this.impulse >= 200 && this.impulse <= 209) || (this.impulse >= 220 && this.impulse <= 229))
 -              {
 -                      if (this.lastclassname != STR_PLAYER)
 -                      {
 -                              if (this.impulse == 10 || this.impulse == 15 || this.impulse == 18 || (this.impulse >= 200 && this.impulse <= 209))
 -                                      this.spectatorspeed = bound(1, this.spectatorspeed + 0.5, 5);
 -                              else if (this.impulse == 11)
 -                                      this.spectatorspeed = maxspeed_mod;
 -                              else if (this.impulse == 12 || this.impulse == 16  || this.impulse == 19 || (this.impulse >= 220 && this.impulse <= 229))
 -                                      this.spectatorspeed = bound(1, this.spectatorspeed - 0.5, 5);
 -                              else if (this.impulse >= 1 && this.impulse <= 9)
 -                                      this.spectatorspeed = 1 + 0.5 * (this.impulse - 1);
 -                      } // otherwise just clear
 -                      this.impulse = 0;
 -              }
 -#endif
 -              maxspeed_mod = this.spectatorspeed;
 -      }
 -#ifdef SVQC
 -
 -      float spd = max(PHYS_MAXSPEED(this), PHYS_MAXAIRSPEED(this)) * maxspeed_mod;
 -      if(this.speed != spd)
 -      {
 -              this.speed = spd;
 -              string temps = ftos(spd);
 -              stuffcmd(this, strcat("cl_forwardspeed ", temps, "\n"));
 -              stuffcmd(this, strcat("cl_backspeed ", temps, "\n"));
 -              stuffcmd(this, strcat("cl_sidespeed ", temps, "\n"));
 -              stuffcmd(this, strcat("cl_upspeed ", temps, "\n"));
 -      }
 -
 -      if(this.jumpspeedcap_min != autocvar_sv_jumpspeedcap_min)
 -      {
 -              this.jumpspeedcap_min = autocvar_sv_jumpspeedcap_min;
 -              stuffcmd(this, sprintf("\ncl_jumpspeedcap_min \"%s\"\n", autocvar_sv_jumpspeedcap_min));
 -      }
 -      if(this.jumpspeedcap_max != autocvar_sv_jumpspeedcap_max)
 -      {
 -              this.jumpspeedcap_max = autocvar_sv_jumpspeedcap_max;
 -              stuffcmd(this, sprintf("\ncl_jumpspeedcap_max \"%s\"\n", autocvar_sv_jumpspeedcap_max));
 -      }
 -#endif
 -
 -      if(IS_DEAD(this))
 -      {
 -              // handle water here
 -              vector midpoint = ((this.absmin + this.absmax) * 0.5);
 -              if(pointcontents(midpoint) == CONTENT_WATER)
 -              {
 -                      this.velocity = this.velocity * 0.5;
 -
 -                      // do we want this?
 -                      //if(pointcontents(midpoint + '0 0 2') == CONTENT_WATER)
 -                              //{ this.velocity_z = 70; }
 -              }
 -              goto end;
 -      }
 -
 -#ifdef SVQC
 -      if (!this.fixangle)
 -              this.angles = '0 1 0' * this.v_angle.y;
 -#endif
 -
 -      if (IS_PLAYER(this) && IS_ONGROUND(this))
 -      {
 -              PM_check_hitground(this);
 -              PM_Footsteps(this);
 -      }
 -
 -#ifdef SVQC
 -      if(IsFlying(this))
 -              this.wasFlying = 1;
 -#endif
 -
 -      if (IS_PLAYER(this))
 -              CheckPlayerJump(this);
 -
 -      if (this.flags & FL_WATERJUMP)
 -      {
 -              this.velocity_x = this.movedir.x;
 -              this.velocity_y = this.movedir.y;
 -              if (this.waterlevel == WATERLEVEL_NONE
 -              #ifdef CSQC
 -                      || pmove_waterjumptime <= 0
 -              #elif defined(SVQC)
 -                      || time > PHYS_TELEPORT_TIME(this)
 -              #endif
 -                      )
 -              {
 -                      this.flags &= ~FL_WATERJUMP;
 -              #ifdef CSQC
 -                      pmove_waterjumptime = 0;
 -              #elif defined(CSQC)
 -                      PHYS_TELEPORT_TIME(this) = 0;
 -              #endif
 -              }
 -      }
 -
 -      else if (MUTATOR_CALLHOOK(PM_Physics, this, maxspeed_mod))
 -              { }
 -
 -#ifdef SVQC
 -      else if (this.move_movetype == MOVETYPE_NOCLIP || this.move_movetype == MOVETYPE_FLY || this.move_movetype == MOVETYPE_FLY_WORLDONLY || MUTATOR_CALLHOOK(IsFlying, this))
 -#elif defined(CSQC)
 -      else if (this.move_movetype == MOVETYPE_NOCLIP || this.move_movetype == MOVETYPE_FLY || this.move_movetype == MOVETYPE_FLY_WORLDONLY || MUTATOR_CALLHOOK(IsFlying, this))
 -#endif
 -              PM_fly(this, maxspeed_mod);
 -
 -      else if (this.waterlevel >= WATERLEVEL_SWIMMING)
 -              PM_swim(this, maxspeed_mod);
 -
 -      else if (time < this.ladder_time)
 -              PM_ladder(this, maxspeed_mod);
 -
 -      else if (ITEMS_STAT(this) & IT_USING_JETPACK)
 -              PM_jetpack(this, maxspeed_mod);
 -
 -      else if (IS_ONGROUND(this))
 -              PM_walk(this, maxspeed_mod);
 -
 -      else
 -              PM_air(this, buttons_prev, maxspeed_mod);
 -
 -LABEL(end)
 -      if (IS_ONGROUND(this))
 -              this.lastground = time;
 -
 -      // conveyors: then break velocity again
 -      if(this.conveyor.state)
 -              this.velocity += this.conveyor.movedir;
 -
 -      this.lastflags = this.flags;
 -
 -      this.lastclassname = this.classname;
 -}
  
 +void sys_phys_update(entity this, float dt);
  #if defined(SVQC)
  void SV_PlayerPhysics(entity this)
  #elif defined(CSQC)
  void CSQC_ClientMovement_PlayerMove_Frame(entity this)
  #endif
  {
 -      PM_Main(this);
 +      sys_phys_update(this, PHYS_INPUT_TIMELENGTH);
+ #ifdef SVQC
+       this.pm_frametime = frametime;
+ #endif
  }
index 5ce35dedceb80398e374662084de1bfe6b63f56e,c74602daba5ab22ccc529f2a54cab83e5ff404ce..f1553f2f130c7e4f2f6f07098ac2a84242b84cd4
@@@ -3,6 -3,8 +3,8 @@@
  
  // Client/server mappings
  
+ .float pm_frametime;
  .entity conveyor;
  
  .float race_penalty;
@@@ -35,6 -37,7 +37,7 @@@ bool IsFlying(entity a)
  #define GAMEPLAYFIX_STEPDOWN(s)             STAT(GAMEPLAYFIX_STEPDOWN, s)
  #define GAMEPLAYFIX_STEPMULTIPLETIMES(s)    STAT(GAMEPLAYFIX_STEPMULTIPLETIMES, s)
  #define GAMEPLAYFIX_UNSTICKPLAYERS(s)       STAT(GAMEPLAYFIX_UNSTICKPLAYERS, s)
+ #define GAMEPLAYFIX_WATERTRANSITION(s) STAT(GAMEPLAYFIX_WATERTRANSITION, s)
  
  #define PHYS_ACCELERATE(s)                  STAT(MOVEVARS_ACCELERATE, s)
  #define PHYS_AIRACCELERATE(s)               STAT(MOVEVARS_AIRACCELERATE, s)
@@@ -67,6 -70,7 +70,7 @@@
  #define PHYS_JETPACK_FUEL(s)                STAT(JETPACK_FUEL, s)
  #define PHYS_JETPACK_MAXSPEED_SIDE(s)       STAT(JETPACK_MAXSPEED_SIDE, s)
  #define PHYS_JETPACK_MAXSPEED_UP(s)         STAT(JETPACK_MAXSPEED_UP, s)
+ #define PHYS_JETPACK_REVERSE_THRUST(s)                STAT(JETPACK_REVERSE_THRUST, s)
  
  #define PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS(s) STAT(MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS, s)
  #define PHYS_JUMPSTEP(s)                    STAT(MOVEVARS_JUMPSTEP, s)
@@@ -156,19 -160,14 +160,19 @@@ STATIC_INIT(PHYS_INPUT_BUTTON_JETPACK
  #define ITEMS_STAT(s)                       ((s).items)
  
  .float teleport_time;
 +#define PHYS_TELEPORT_TIME(s)               ((s).teleport_time)
 +
 +.float waterjump_time;
 +#define PHYS_WATERJUMP_TIME(s)               ((s).waterjump_time)
  
  #ifdef CSQC
  
 +      #define PHYS_FIXANGLE(s) ('0 0 0')
 +      #define PHYS_MOVETYPE(s) ((s).move_movetype)
 +
        string autocvar_cl_jumpspeedcap_min;
        string autocvar_cl_jumpspeedcap_max;
  
 -      noref float pmove_waterjumptime;
 -
        const int FL_WATERJUMP = 2048;  // player jumping out of water
        const int FL_JUMPRELEASED = 4096;  // for jump debouncing
  
  
        #define PHYS_GRAVITY(s)                     STAT(MOVEVARS_GRAVITY, s)
  
 -      #define PHYS_TELEPORT_TIME(s)               ((s).teleport_time)
 -
        #define TICRATE                             ticrate
  
        #define PHYS_INPUT_ANGLES(s)                input_angles
  
  #elif defined(SVQC)
  
 +      #define PHYS_FIXANGLE(s) ((s).fixangle)
 +      #define PHYS_MOVETYPE(s) ((s).movetype)
 +
        bool Physics_Valid(string thecvar);
  
        .float stat_sv_airspeedlimit_nonqw = _STAT(MOVEVARS_AIRSPEEDLIMIT_NONQW);
        .string jumpspeedcap_min;
        .string jumpspeedcap_max;
  
 -      #define PHYS_TELEPORT_TIME(s)               ((s).teleport_time)
 -
        #define PHYS_GRAVITY(s)                     autocvar_sv_gravity
  
        #define TICRATE sys_frametime
index caa62e03607a408195f55151ffda88e348c46738,4a30ca69510d59a5c1aac0577f6a92fed64095d1..5ec7b41fd484c84fe3cbbe8ce7f32452038c8d9e
@@@ -129,24 -129,23 +129,23 @@@ vector trigger_push_calculatevelocity(v
        return sdir * vs + '0 0 1' * vz;
  }
  
- void trigger_push_touch(entity this)
+ void trigger_push_touch(entity this, entity toucher)
  {
        if (this.active == ACTIVE_NOT)
                return;
  
-       if (!isPushable(other))
+       if (!isPushable(toucher))
                return;
  
        if(this.team)
-               if(((this.spawnflags & 4) == 0) == (DIFF_TEAM(this, other)))
+               if(((this.spawnflags & 4) == 0) == (DIFF_TEAM(this, toucher)))
                        return;
  
-       EXACTTRIGGER_TOUCH;
+       EXACTTRIGGER_TOUCH(this, toucher);
  
        if(this.enemy)
        {
-               other.velocity = trigger_push_calculatevelocity(other.origin, this.enemy, this.height);
-               other.move_velocity = other.velocity;
+               toucher.velocity = trigger_push_calculatevelocity(toucher.origin, this.enemy, this.height);
        }
        else if(this.target && this.target != "")
        {
                        else
                                RandomSelection_Add(e, 0, string_null, 1, 1);
                }
-               other.velocity = trigger_push_calculatevelocity(other.origin, RandomSelection_chosen_ent, this.height);
-               other.move_velocity = other.velocity;
+               toucher.velocity = trigger_push_calculatevelocity(toucher.origin, RandomSelection_chosen_ent, this.height);
        }
        else
        {
-               other.velocity = this.movedir;
-               other.move_velocity = other.velocity;
+               toucher.velocity = this.movedir;
        }
  
- #ifdef SVQC
-       UNSET_ONGROUND(other);
- #elif defined(CSQC)
-       other.move_flags &= ~FL_ONGROUND;
+       UNSET_ONGROUND(toucher);
  
-       if (other.flags & FL_PROJECTILE)
+ #ifdef CSQC
+       if (toucher.flags & FL_PROJECTILE)
        {
-               other.move_angles = vectoangles (other.move_velocity);
-               switch(other.move_movetype)
+               toucher.angles = vectoangles (toucher.velocity);
+               switch(toucher.move_movetype)
                {
                        case MOVETYPE_FLY:
-                               other.move_movetype = MOVETYPE_TOSS;
-                               other.gravity = 1;
+                               toucher.move_movetype = MOVETYPE_TOSS;
+                               toucher.gravity = 1;
                                break;
                        case MOVETYPE_BOUNCEMISSILE:
-                               other.move_movetype = MOVETYPE_BOUNCE;
-                               other.gravity = 1;
+                               toucher.move_movetype = MOVETYPE_BOUNCE;
+                               toucher.gravity = 1;
                                break;
                }
        }
  #endif
  
  #ifdef SVQC
-       if (IS_PLAYER(other))
+       if (IS_PLAYER(toucher))
        {
                // reset tracking of oldvelocity for impact damage (sudden velocity changes)
-               other.oldvelocity = other.velocity;
+               toucher.oldvelocity = toucher.velocity;
  
                if(this.pushltime < time)  // prevent "snorring" sound when a player hits the jumppad more than once
                {
                        // flash when activated
-                       Send_Effect(EFFECT_JUMPPAD, other.origin, other.velocity, 1);
-                       _sound (other, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
+                       Send_Effect(EFFECT_JUMPPAD, toucher.origin, toucher.velocity, 1);
+                       _sound (toucher, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
                        this.pushltime = time + 0.2;
                }
-               if(IS_REAL_CLIENT(other) || IS_BOT_CLIENT(other))
+               if(IS_REAL_CLIENT(toucher) || IS_BOT_CLIENT(toucher))
                {
                        bool found = false;
-                       for(int i = 0; i < other.jumppadcount && i < NUM_JUMPPADSUSED; ++i)
-                               if(other.(jumppadsused[i]) == this)
+                       for(int i = 0; i < toucher.jumppadcount && i < NUM_JUMPPADSUSED; ++i)
+                               if(toucher.(jumppadsused[i]) == this)
                                        found = true;
                        if(!found)
                        {
-                               other.(jumppadsused[other.jumppadcount % NUM_JUMPPADSUSED]) = this;
-                               other.jumppadcount = other.jumppadcount + 1;
+                               toucher.(jumppadsused[toucher.jumppadcount % NUM_JUMPPADSUSED]) = this;
+                               toucher.jumppadcount = toucher.jumppadcount + 1;
                        }
  
-                       if(IS_REAL_CLIENT(other))
+                       if(IS_REAL_CLIENT(toucher))
                        {
                                if(this.message)
-                                       centerprint(other, this.message);
+                                       centerprint(toucher, this.message);
                        }
                        else
-                               other.lastteleporttime = time;
+                               toucher.lastteleporttime = time;
  
-                       if (!IS_DEAD(other))
-                               animdecide_setaction(other, ANIMACTION_JUMP, true);
+                       if (!IS_DEAD(toucher))
+                               animdecide_setaction(toucher, ANIMACTION_JUMP, true);
                }
                else
-                       other.jumppadcount = true;
+                       toucher.jumppadcount = true;
  
                // reset tracking of who pushed you into a hazard (for kill credit)
-               other.pushltime = 0;
-               other.istypefrag = 0;
+               toucher.pushltime = 0;
+               toucher.istypefrag = 0;
        }
  
        if(this.enemy.target)
-               SUB_UseTargets(this.enemy, other, other); // TODO: do we need other as trigger too?
+               SUB_UseTargets(this.enemy, toucher, toucher); // TODO: do we need toucher as trigger too?
  
-       if (other.flags & FL_PROJECTILE)
+       if (toucher.flags & FL_PROJECTILE)
        {
-               other.angles = vectoangles (other.velocity);
-               other.com_phys_gravity_factor = 1;
-               switch(other.movetype)
+               toucher.angles = vectoangles (toucher.velocity);
++              toucher.com_phys_gravity_factor = 1;
+               switch(toucher.move_movetype)
                {
                        case MOVETYPE_FLY:
-                               other.movetype = MOVETYPE_TOSS;
-                               other.gravity = 1;
+                               set_movetype(toucher, MOVETYPE_TOSS);
+                               toucher.gravity = 1;
                                break;
                        case MOVETYPE_BOUNCEMISSILE:
-                               other.movetype = MOVETYPE_BOUNCE;
-                               other.gravity = 1;
+                               set_movetype(toucher, MOVETYPE_BOUNCE);
+                               toucher.gravity = 1;
                                break;
                }
-               UpdateCSQCProjectile(other);
+               UpdateCSQCProjectile(toucher);
        }
  
-       /*if (other.flags & FL_ITEM)
+       /*if (toucher.flags & FL_ITEM)
        {
-               ItemUpdate(other);
-               other.SendFlags |= ISF_DROP;
+               ItemUpdate(toucher);
+               toucher.SendFlags |= ISF_DROP;
        }*/
  
        if (this.spawnflags & PUSH_ONCE)
@@@ -295,7 -289,7 +290,7 @@@ void trigger_push_findtarget(entity thi
                        setsize(e, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL));
                        e.velocity = trigger_push_calculatevelocity(org, t, this.height);
                        tracetoss(e, e);
-                       if(e.movetype == MOVETYPE_NONE)
+                       if(e.move_movetype == MOVETYPE_NONE)
                                waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
                        remove(e);
  #endif
index 1fc3cb4b1a71ac0ae217beeaad83106652cfe2cc,2853625f2c343ef0829ab129ab635eeddd6ad6fe..1f8cc19441db45195999fe3262d577a09e9a8b4a
@@@ -50,6 -50,7 +50,7 @@@ CLASS(Electro, Weapon
                P(class, prefix, speed_up, float, SEC) \
                P(class, prefix, speed_z, float, SEC) \
                P(class, prefix, spread, float, BOTH) \
+               P(class, prefix, stick, float, SEC) \
                P(class, prefix, switchdelay_drop, float, NONE) \
                P(class, prefix, switchdelay_raise, float, NONE) \
                P(class, prefix, touchexplode, float, SEC) \
@@@ -144,19 -145,19 +145,19 @@@ void W_Electro_ExplodeCombo(entity this
        remove(this);
  }
  
- void W_Electro_Explode(entity this)
+ void W_Electro_Explode(entity this, entity directhitentity)
  {
-       if(other.takedamage == DAMAGE_AIM)
-               if(IS_PLAYER(other))
-                       if(DIFF_TEAM(this.realowner, other))
-                               if(!IS_DEAD(other))
-                                       if(IsFlying(other))
+       if(directhitentity.takedamage == DAMAGE_AIM)
+               if(IS_PLAYER(directhitentity))
+                       if(DIFF_TEAM(this.realowner, directhitentity))
+                               if(!IS_DEAD(directhitentity))
+                                       if(IsFlying(directhitentity))
                                                Send_Notification(NOTIF_ONE, this.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH);
  
        this.event_damage = func_null;
        this.takedamage = DAMAGE_NO;
  
-       if(this.movetype == MOVETYPE_BOUNCE)
+       if(this.move_movetype == MOVETYPE_BOUNCE || this.classname == "electro_orb") // TODO: classname is more reliable anyway?
        {
                RadiusDamage(
                        this,
                        NULL,
                        WEP_CVAR_SEC(electro, force),
                        this.projectiledeathtype,
-                       other
+                       directhitentity
                );
        }
        else
                        NULL,
                        WEP_CVAR_PRI(electro, force),
                        this.projectiledeathtype,
-                       other
+                       directhitentity
                );
        }
  
  
  void W_Electro_Explode_use(entity this, entity actor, entity trigger)
  {
-       W_Electro_Explode(this);
+       W_Electro_Explode(this, trigger);
  }
  
- void W_Electro_TouchExplode(entity this)
+ void W_Electro_TouchExplode(entity this, entity toucher)
  {
-       PROJECTILE_TOUCH(this);
-       W_Electro_Explode(this);
+       PROJECTILE_TOUCH(this, toucher);
+       W_Electro_Explode(this, toucher);
  }
  
 +
 +void sys_phys_update_single(entity this);
 +
  void W_Electro_Bolt_Think(entity this)
  {
-       sys_phys_update_single(this);
++      // sys_phys_update_single(this);
        if(time >= this.ltime)
        {
                this.use(this, NULL, NULL);
                        { this.nextthink = min(time + WEP_CVAR_PRI(electro, midaircombo_interval), this.ltime); }
        }
        else { this.nextthink = this.ltime; }
-       this.nextthink = time;
++      // this.nextthink = time;
  }
  
  void W_Electro_Attack_Bolt(Weapon thiswep, entity actor)
        proj.projectiledeathtype = WEP_ELECTRO.m_id;
        setorigin(proj, w_shotorg);
  
-       if (IS_CSQC) proj.movetype = MOVETYPE_FLY;
++      // if (IS_CSQC)
+       set_movetype(proj, MOVETYPE_FLY);
        W_SetupProjVelocity_PRI(proj, electro);
        proj.angles = vectoangles(proj.velocity);
        settouch(proj, W_Electro_TouchExplode);
        CSQCProjectile(proj, true, PROJECTILE_ELECTRO_BEAM, true);
  
        MUTATOR_CALLHOOK(EditProjectile, actor, proj);
-       proj.com_phys_pos = proj.origin;
-       proj.com_phys_vel = proj.velocity;
++      // proj.com_phys_pos = proj.origin;
++      // proj.com_phys_vel = proj.velocity;
  }
  
- void W_Electro_Orb_Touch(entity this)
+ void W_Electro_Orb_Stick(entity this, entity to)
  {
-       PROJECTILE_TOUCH(this);
-       if(other.takedamage == DAMAGE_AIM)
-               { if(WEP_CVAR_SEC(electro, touchexplode)) { W_Electro_Explode(this); } }
+       entity newproj = spawn();
+       newproj.classname = this.classname;
+       newproj.bot_dodge = this.bot_dodge;
+       newproj.bot_dodgerating = this.bot_dodgerating;
+       newproj.owner = this.owner;
+       newproj.realowner = this.realowner;
+       setsize(newproj, this.mins, this.maxs);
+       setorigin(newproj, this.origin);
+       setmodel(newproj, MDL_PROJECTILE_ELECTRO);
+       newproj.angles = vectoangles(-trace_plane_normal); // face against the surface
+       newproj.takedamage = this.takedamage;
+       newproj.damageforcescale = this.damageforcescale;
+       newproj.health = this.health;
+       newproj.event_damage = this.event_damage;
+       newproj.spawnshieldtime = this.spawnshieldtime;
+       newproj.damagedbycontents = true;
+       set_movetype(newproj, MOVETYPE_NONE); // lock the orb in place
+       newproj.projectiledeathtype = this.projectiledeathtype;
+       settouch(newproj, func_null);
+       setthink(newproj, getthink(this));
+       newproj.nextthink = this.nextthink;
+       newproj.use = this.use;
+       newproj.flags = this.flags;
+       remove(this);
+       if(to)
+               SetMovetypeFollow(this, to);
+ }
+ void W_Electro_Orb_Touch(entity this, entity toucher)
+ {
+       PROJECTILE_TOUCH(this, toucher);
+       if(toucher.takedamage == DAMAGE_AIM)
+               { if(WEP_CVAR_SEC(electro, touchexplode)) { W_Electro_Explode(this, toucher); } }
        else
        {
                //UpdateCSQCProjectile(this);
                spamsound(this, CH_SHOTS, SND(ELECTRO_BOUNCE), VOL_BASE, ATTEN_NORM);
                this.projectiledeathtype |= HITTYPE_BOUNCE;
+               if(WEP_CVAR_SEC(electro, stick))
+                       W_Electro_Orb_Stick(this, toucher);
        }
  }
  
@@@ -366,8 -400,8 +408,8 @@@ void W_Electro_Attack_Orb(Weapon thiswe
  
        W_SetupShot_ProjectileSize(
                actor,
-               '0 0 -4',
-               '0 0 -4',
+               '-4 -4 -4',
+               '4 4 4',
                false,
                2,
                SND_ELECTRO_FIRE2,
  
        //proj.glow_size = 50;
        //proj.glow_color = 45;
-       proj.movetype = MOVETYPE_BOUNCE;
+       set_movetype(proj, MOVETYPE_BOUNCE);
        W_SetupProjVelocity_UP_SEC(proj, electro);
        settouch(proj, W_Electro_Orb_Touch);
-       setsize(proj, '0 0 -4', '0 0 -4');
+       setsize(proj, '-4 -4 -4', '4 4 4');
        proj.takedamage = DAMAGE_YES;
        proj.damageforcescale = WEP_CVAR_SEC(electro, damageforcescale);
        proj.health = WEP_CVAR_SEC(electro, health);
index 377e1d1d8e27ca602cca0f92d9c64ef6a44ae657,502657e4bcbeb52db0fa319c1c41161a21f13480..f150a296c299e8d46980c1ba2d4fb278665947d4
@@@ -4,22 -4,4 +4,23 @@@ COMPONENT(phys)
  .vector com_phys_pos, com_phys_pos_prev;
  .vector com_phys_ang, com_phys_ang_prev;
  .vector com_phys_vel;
 +.float com_phys_vel_max;
 +.float com_phys_vel_max_air;
 +.float com_phys_vel_max_air_strafe;
  .vector com_phys_acc;
 +.float com_phys_acc_rate;
 +.float com_phys_acc_rate_air;
 +.float com_phys_acc_rate_air_strafe;
 +.float com_phys_acc_rate_air_stop;
 +.float com_phys_friction;
 +
 +.vector com_phys_gravity;
 +.float com_phys_gravity_factor;
 +// TODO: remove
 +.bool com_phys_ground;
 +.bool com_phys_air;
 +.bool com_phys_ladder;
 +.bool com_phys_vel_2d;
 +.bool com_phys_water;
 +.bool com_phys_friction_air;
++.bool move_qcphysics;
index 52e8ed0caeb3c9800aa282449f50238de95913e3,0000000000000000000000000000000000000000..206c80d96526f4001200bd8b6d0a56d9a05e3fd6
mode 100644,000000..100644
--- /dev/null
@@@ -1,30 -1,0 +1,30 @@@
-       PM_ClientMovement_UpdateStatus(this, true);
 +#include "physics.qh"
 +
 +void sys_phys_fix(entity this, float dt)
 +{
 +      this.team = myteam + 1; // is this correct?
 +      PHYS_WATERJUMP_TIME(this) -= dt;
 +      this.oldmovement = this.movement;
 +      this.movement = PHYS_INPUT_MOVEVALUES(this);
 +      this.items = STAT(ITEMS, this);
 +      this.spectatorspeed = STAT(SPECTATORSPEED, this);
 +      if (!(PHYS_INPUT_BUTTON_JUMP(this))) // !jump
 +              UNSET_JUMP_HELD(this);           // canjump = true
++      PM_ClientMovement_UpdateStatus(this);
 +}
 +
 +bool sys_phys_override(entity this)
 +{
 +      // no vehicle prediction
 +      return hud != HUD_NORMAL;
 +}
 +
 +void sys_phys_monitor(entity this) {}
 +
 +void sys_phys_ai(entity this) {}
 +
 +void sys_phys_pregame_hold(entity this) {}
 +
 +void sys_phys_spectator_control(entity this) {}
 +
 +void sys_phys_fixspeed(entity this, float maxspeed_mod) {}
index 7267a48644303ad98e939bc0b74782d0e2528c6c,8348b87851cc82f8e3bf6999d54bd01a6246116b..8646b29cc983827a48be0387de3373d8f3295bf9
  #include "physics.qh"
 +#include "input.qh"
 +
 +.int disableclientprediction;
 +
 +void sys_phys_simulate(entity this, float dt);
 +void sys_phys_simulate_simple(entity this, float dt);
  
  void sys_phys_update(entity this, float dt)
  {
 -      PM_Main(this);
 +      if (!IS_CLIENT(this)) {
 +              sys_phys_simulate_simple(this, dt);
 +              return;
 +      }
 +      sys_in_update(this, dt);
 +
 +      sys_phys_fix(this, dt);
 +      if (sys_phys_override(this)) { return; } sys_phys_monitor(this);
 +
 +      this.buttons_old = PHYS_INPUT_BUTTON_MASK(this);
 +      this.movement_old = this.movement;
 +      this.v_angle_old = this.v_angle;
 +
 +      sys_phys_ai(this);
 +
 +      sys_phys_pregame_hold(this);
 +
 +      if (IS_SVQC) {
 +              if (PHYS_MOVETYPE(this) == MOVETYPE_NONE) { return; }
 +              // when we get here, disableclientprediction cannot be 2
-               this.disableclientprediction = 0;
++              this.disableclientprediction = (this.move_qcphysics) ? -1 : 0;
 +      }
 +
 +      viewloc_PlayerPhysics(this);
 +
 +      PM_check_frozen(this);
 +
 +      PM_check_blocked(this);
 +
 +      float maxspeed_mod = (!this.in_swamp) ? 1 : this.swamp_slowdown;  // cvar("g_balance_swamp_moverate");
 +
 +// conveyors: first fix velocity
 +      if (this.conveyor.state) { this.velocity -= this.conveyor.movedir; }
 +      MUTATOR_CALLHOOK(PlayerPhysics, this);
 +
 +      if (!IS_PLAYER(this)) {
 +              sys_phys_spectator_control(this);
 +              maxspeed_mod = this.spectatorspeed;
 +      }
 +      sys_phys_fixspeed(this, maxspeed_mod);
 +
 +      if (IS_DEAD(this)) {
 +              // handle water here
 +              vector midpoint = ((this.absmin + this.absmax) * 0.5);
 +              if (pointcontents(midpoint) == CONTENT_WATER) {
 +                      this.velocity = this.velocity * 0.5;
 +
 +                      // do we want this?
 +                      // if(pointcontents(midpoint + '0 0 2') == CONTENT_WATER)
 +                      // { this.velocity_z = 70; }
 +              }
 +              goto end;
 +      }
 +
 +      if (IS_SVQC && !PHYS_FIXANGLE(this)) { this.angles = '0 1 0' * this.v_angle.y; }
 +      if (IS_PLAYER(this)) {
 +              if (IS_ONGROUND(this)) {
 +                      PM_check_hitground(this);
 +                      PM_Footsteps(this);
 +              } else if (IsFlying(this)) {
 +                      this.wasFlying = true;
 +              }
 +              CheckPlayerJump(this);
 +      }
 +
 +      if (this.flags & FL_WATERJUMP) {
 +              this.velocity_x = this.movedir.x;
 +              this.velocity_y = this.movedir.y;
-               if (time > PHYS_TELEPORT_TIME(this)
-                   || this.waterlevel == WATERLEVEL_NONE
++              if (this.waterlevel == WATERLEVEL_NONE
++                  || time > PHYS_TELEPORT_TIME(this)
 +                  || PHYS_WATERJUMP_TIME(this) <= 0
 +                 ) {
 +                      this.flags &= ~FL_WATERJUMP;
 +                      PHYS_TELEPORT_TIME(this) = 0;
 +                      PHYS_WATERJUMP_TIME(this) = 0;
 +              }
 +      } else if (MUTATOR_CALLHOOK(PM_Physics, this, maxspeed_mod)) {
 +              // handled
 +      } else if (PHYS_MOVETYPE(this) == MOVETYPE_NOCLIP
 +          || PHYS_MOVETYPE(this) == MOVETYPE_FLY
 +          || PHYS_MOVETYPE(this) == MOVETYPE_FLY_WORLDONLY
 +          || MUTATOR_CALLHOOK(IsFlying, this)) {
 +              this.com_phys_friction = PHYS_FRICTION(this);
 +              this.com_phys_vel_max = PHYS_MAXSPEED(this) * maxspeed_mod;
 +              this.com_phys_acc_rate = PHYS_ACCELERATE(this) * maxspeed_mod;
 +              this.com_phys_friction_air = true;
 +              sys_phys_simulate(this, dt);
 +              this.com_phys_friction_air = false;
 +      } else if (this.waterlevel >= WATERLEVEL_SWIMMING) {
 +              this.com_phys_vel_max = PHYS_MAXSPEED(this) * maxspeed_mod;
 +              this.com_phys_acc_rate = PHYS_ACCELERATE(this) * maxspeed_mod;
 +              this.com_phys_water = true;
 +              sys_phys_simulate(this, dt);
 +              this.com_phys_water = false;
 +      } else if (time < this.ladder_time) {
 +              this.com_phys_friction = PHYS_FRICTION(this);
 +              this.com_phys_vel_max = PHYS_MAXSPEED(this) * maxspeed_mod;
 +              this.com_phys_acc_rate = PHYS_ACCELERATE(this) * maxspeed_mod;
 +              this.com_phys_gravity = '0 0 -1' * PHYS_GRAVITY(this) * dt;
 +              if (PHYS_ENTGRAVITY(this)) { this.com_phys_gravity *= PHYS_ENTGRAVITY(this); }
 +              this.com_phys_ladder = true;
 +              this.com_phys_friction_air = true;
 +              sys_phys_simulate(this, dt);
 +              this.com_phys_friction_air = false;
 +              this.com_phys_ladder = false;
 +              this.com_phys_gravity = '0 0 0';
 +      } else if (ITEMS_STAT(this) & IT_USING_JETPACK) {
 +              PM_jetpack(this, maxspeed_mod);
 +      } else if (IS_ONGROUND(this)) {
 +              if (!WAS_ONGROUND(this)) {
 +                      emit(phys_land, this);
 +                      if (this.lastground < time - 0.3) {
 +                              this.velocity *= (1 - PHYS_FRICTION_ONLAND(this));
 +                      }
 +              }
 +              this.com_phys_vel_max = PHYS_MAXSPEED(this) * maxspeed_mod;
 +              this.com_phys_gravity = '0 0 -1' * PHYS_GRAVITY(this) * dt;
 +              if (PHYS_ENTGRAVITY(this)) { this.com_phys_gravity *= PHYS_ENTGRAVITY(this); }
 +              this.com_phys_ground = true;
 +              this.com_phys_vel_2d = true;
 +              sys_phys_simulate(this, dt);
 +              this.com_phys_vel_2d = false;
 +              this.com_phys_ground = false;
 +              this.com_phys_gravity = '0 0 0';
 +      } else {
 +              this.com_phys_acc_rate_air = PHYS_AIRACCELERATE(this) * min(maxspeed_mod, 1);
 +              this.com_phys_acc_rate_air_stop = PHYS_AIRSTOPACCELERATE(this) * maxspeed_mod;
 +              this.com_phys_acc_rate_air_strafe = PHYS_AIRSTRAFEACCELERATE(this) * maxspeed_mod;
 +              this.com_phys_vel_max_air_strafe = PHYS_MAXAIRSTRAFESPEED(this) * maxspeed_mod;
 +              this.com_phys_vel_max_air = PHYS_MAXAIRSPEED(this) * maxspeed_mod;
 +              this.com_phys_vel_max = PHYS_MAXAIRSPEED(this) * min(maxspeed_mod, 1);
 +              this.com_phys_air = true;
 +              this.com_phys_vel_2d = true;
 +              sys_phys_simulate(this, dt);
 +              this.com_phys_vel_2d = false;
 +              this.com_phys_air = false;
 +      }
 +
 +      LABEL(end)
 +      if (IS_ONGROUND(this)) { this.lastground = time; }
 +// conveyors: then break velocity again
 +      if (this.conveyor.state) { this.velocity += this.conveyor.movedir; }
 +      this.lastflags = this.flags;
 +
 +      this.lastclassname = this.classname;
 +}
 +
 +/** for players */
 +void sys_phys_simulate(entity this, float dt)
 +{
 +      const vector g = -this.com_phys_gravity;
 +      const bool jump = this.com_in_jump;
 +
 +      if (!this.com_phys_ground && !this.com_phys_air) {
 +              // noclipping
 +              // flying
 +              // on a spawnfunc_func_ladder
 +              // swimming in spawnfunc_func_water
 +              // swimming
 +              UNSET_ONGROUND(this);
 +
 +              if (this.com_phys_friction_air) {
 +                      this.velocity_z += g.z / 2;
 +                      this.velocity = this.velocity * (1 - dt * this.com_phys_friction);
 +                      this.velocity_z += g.z / 2;
 +              }
 +      }
 +
 +      if (this.com_phys_water) {
 +              // water jump only in certain situations
 +              // this mimics quakeworld code
 +              if (jump && this.waterlevel == WATERLEVEL_SWIMMING && this.velocity_z >= -180 && !this.viewloc) {
 +                      vector yawangles = '0 1 0' * this.v_angle.y;
 +                      makevectors(yawangles);
 +                      vector forward = v_forward;
 +                      vector spot = this.origin + 24 * forward;
 +                      spot_z += 8;
 +                      traceline(spot, spot, MOVE_NOMONSTERS, this);
 +                      if (trace_startsolid) {
 +                              spot_z += 24;
 +                              traceline(spot, spot, MOVE_NOMONSTERS, this);
 +                              if (!trace_startsolid) {
 +                                      this.velocity = forward * 50;
 +                                      this.velocity_z = 310;
-                                       if (IS_CSQC) { PHYS_WATERJUMP_TIME(this) = 2; }
 +                                      UNSET_ONGROUND(this);
 +                                      SET_JUMP_HELD(this);
 +                              }
 +                      }
 +              }
 +      }
 +      makevectors(vmul(this.v_angle, (this.com_phys_vel_2d ? '0 1 0' : '1 1 1')));
 +      // wishvel = v_forward * this.movement.x + v_right * this.movement.y + v_up * this.movement.z;
 +      vector wishvel = v_forward * this.movement.x
 +          + v_right * this.movement.y
 +          + '0 0 1' * this.movement.z * (this.com_phys_vel_2d ? 0 : 1);
 +      if (this.com_phys_water) {
++              if (PHYS_INPUT_BUTTON_CROUCH(this)) {
++                      wishvel.z = -PHYS_MAXSPEED(this);
++              }
 +              if (this.viewloc) {
 +                      wishvel.z = -160;    // drift anyway
 +              } else if (wishvel == '0 0 0') {
 +                      wishvel = '0 0 -60'; // drift towards bottom
 +              }
 +      }
 +      if (this.com_phys_ladder) {
 +              if (this.viewloc) {
 +                      wishvel.z = this.oldmovement.x;
 +              }
 +              if (this.ladder_entity.classname == "func_water") {
 +                      float f = vlen(wishvel);
 +                      if (f > this.ladder_entity.speed) {
 +                              wishvel *= (this.ladder_entity.speed / f);
 +                      }
 +
 +                      this.watertype = this.ladder_entity.skin;
 +                      f = this.ladder_entity.origin_z + this.ladder_entity.maxs_z;
 +                      if ((this.origin_z + this.view_ofs_z) < f) {
 +                              this.waterlevel = WATERLEVEL_SUBMERGED;
 +                      } else if ((this.origin_z + (this.mins_z + this.maxs_z) * 0.5) < f) {
 +                              this.waterlevel = WATERLEVEL_SWIMMING;
 +                      } else if ((this.origin_z + this.mins_z + 1) < f) {
 +                              this.waterlevel = WATERLEVEL_WETFEET;
 +                      } else {
 +                              this.waterlevel = WATERLEVEL_NONE;
 +                              this.watertype = CONTENT_EMPTY;
 +                      }
 +              }
 +      }
 +      // acceleration
 +      const vector wishdir = normalize(wishvel);
 +      float wishspeed = min(vlen(wishvel), this.com_phys_vel_max);
 +
 +      if (this.com_phys_air) {
 +              if ((IS_SVQC && time >= PHYS_TELEPORT_TIME(this))
 +                  ||  (IS_CSQC && PHYS_WATERJUMP_TIME(this) <= 0)) {
 +                      // apply air speed limit
 +                      float airaccelqw = PHYS_AIRACCEL_QW(this);
 +                      float wishspeed0 = wishspeed;
 +                      const float maxairspd = this.com_phys_vel_max;
 +                      wishspeed = min(wishspeed, maxairspd);
 +                      if (IS_DUCKED(this)) {
 +                              wishspeed *= 0.5;
 +                      }
 +                      float airaccel = this.com_phys_acc_rate_air;
 +
 +                      float accelerating = (this.velocity * wishdir > 0);
 +                      float wishspeed2 = wishspeed;
 +
 +                      // CPM: air control
 +                      if (PHYS_AIRSTOPACCELERATE(this)) {
 +                              vector curdir = normalize(vec2(this.velocity));
 +                              airaccel += (this.com_phys_acc_rate_air_stop - airaccel) * max(0, -(curdir * wishdir));
 +                      }
 +                      // note that for straight forward jumping:
 +                      // step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
 +                      // accel  = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
 +                      // -->
 +                      // dv/dt = accel * maxspeed (when slow)
 +                      // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
 +                      // log dv/dt = logaccel + logmaxspeed (when slow)
 +                      // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
 +                      float strafity = IsMoveInDirection(this.movement, -90) + IsMoveInDirection(this.movement, +90);  // if one is nonzero, other is always zero
 +                      if (PHYS_MAXAIRSTRAFESPEED(this)) {
 +                              wishspeed =
 +                                  min(wishspeed,
 +                                      GeomLerp(this.com_phys_vel_max_air, strafity, this.com_phys_vel_max_air_strafe));
 +                      }
 +                      if (PHYS_AIRSTRAFEACCELERATE(this)) {
 +                              airaccel = GeomLerp(airaccel, strafity, this.com_phys_acc_rate_air_strafe);
 +                      }
 +                      if (PHYS_AIRSTRAFEACCEL_QW(this)) {
 +                              airaccelqw =
 +                                  (((strafity > 0.5 ? PHYS_AIRSTRAFEACCEL_QW(this) : PHYS_AIRACCEL_QW(this)) >= 0) ? +1 : -1)
 +                                  *
 +                                  (1 - GeomLerp(1 - fabs(PHYS_AIRACCEL_QW(this)), strafity, 1 - fabs(PHYS_AIRSTRAFEACCEL_QW(this))));
 +                      }
 +                      // !CPM
 +
 +                      if (PHYS_WARSOWBUNNY_TURNACCEL(this) && accelerating && this.movement.y == 0 && this.movement.x != 0) {
 +                              PM_AirAccelerate(this, wishdir, wishspeed2);
 +                      } else {
 +                              float sidefric = maxairspd ? (PHYS_AIRACCEL_SIDEWAYS_FRICTION(this) / maxairspd) : 0;
 +                              PM_Accelerate(this, wishdir, wishspeed, wishspeed0, airaccel, airaccelqw,
 +                                      PHYS_AIRACCEL_QW_STRETCHFACTOR(this), sidefric, PHYS_AIRSPEEDLIMIT_NONQW(this));
 +                      }
 +
 +                      if (PHYS_AIRCONTROL(this)) {
 +                              CPM_PM_Aircontrol(this, wishdir, wishspeed2);
 +                      }
 +              }
 +      } else {
-               if (this.com_phys_ground || this.com_phys_water) {
-                       if (IS_DUCKED(this)) { wishspeed *= 0.5; }
-               }
++              if (this.com_phys_ground && IS_DUCKED(this)) { wishspeed *= 0.5; }
 +              if (this.com_phys_water) {
 +                      wishspeed *= 0.7;
 +
 +                      //      if (PHYS_WATERJUMP_TIME(this) <= 0) // TODO: use
 +                      {
 +                              // water friction
 +                              float f = 1 - dt * PHYS_FRICTION(this);
 +                              f = min(max(0, f), 1);
 +                              this.velocity *= f;
 +
 +                              f = wishspeed - this.velocity * wishdir;
 +                              if (f > 0) {
 +                                      float accelspeed = min(PHYS_ACCELERATE(this) * dt * wishspeed, f);
 +                                      this.velocity += accelspeed * wishdir;
 +                              }
 +
 +                              // holding jump button swims upward slowly
 +                              if (jump && !this.viewloc) {
 +                                      // was:
 +                                      // lava: 50
 +                                      // slime: 80
 +                                      // water: 100
 +                                      // idea: double those
 +                                      this.velocity_z = 200;
++                                      if (this.waterlevel >= WATERLEVEL_SUBMERGED) {
++                                              this.velocity_z = PHYS_MAXSPEED(this) * 0.7;
++                                      }
 +                              }
 +                      }
 +                      if (this.viewloc) {
 +                              const float addspeed = wishspeed - this.velocity * wishdir;
 +                              if (addspeed > 0) {
 +                                      const float accelspeed = min(PHYS_ACCELERATE(this) * dt * wishspeed, addspeed);
 +                                      this.velocity += accelspeed * wishdir;
 +                              }
 +                      } else {
 +                              // water acceleration
 +                              PM_Accelerate(this, wishdir, wishspeed, wishspeed, this.com_phys_acc_rate, 1, 0, 0, 0);
-                               PM_ClientMovement_Move(this);
 +                      }
 +                      return;
 +              }
 +              if (this.com_phys_ground) {
 +                      // apply edge friction
 +                      const float f2 = vlen2(vec2(this.velocity));
 +                      if (f2 > 0) {
 +                              trace_dphitq3surfaceflags = 0;
 +                              tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
 +                              // TODO: apply edge friction
 +                              // apply ground friction
 +                              const int realfriction = (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)
 +                                  ? PHYS_FRICTION_SLICK(this)
 +                                      : PHYS_FRICTION(this);
 +
 +                              float f = sqrt(f2);
 +                              f = 1 - dt * realfriction
 +                                  * ((f < PHYS_STOPSPEED(this)) ? (PHYS_STOPSPEED(this) / f) : 1);
 +                              f = max(0, f);
 +                              this.velocity *= f;
 +                              /*
 +                                 Mathematical analysis time!
 +
 +                                 Our goal is to invert this mess.
 +
 +                                 For the two cases we get:
 +                                  v = v0 * (1 - dt * (PHYS_STOPSPEED(this) / v0) * PHYS_FRICTION(this))
 +                                    = v0 - dt * PHYS_STOPSPEED(this) * PHYS_FRICTION(this)
 +                                  v0 = v + dt * PHYS_STOPSPEED(this) * PHYS_FRICTION(this)
 +                                 and
 +                                  v = v0 * (1 - dt * PHYS_FRICTION(this))
 +                                  v0 = v / (1 - dt * PHYS_FRICTION(this))
 +
 +                                 These cases would be chosen ONLY if:
 +                                  v0 < PHYS_STOPSPEED(this)
 +                                  v + dt * PHYS_STOPSPEED(this) * PHYS_FRICTION(this) < PHYS_STOPSPEED(this)
 +                                  v < PHYS_STOPSPEED(this) * (1 - dt * PHYS_FRICTION(this))
 +                                 and, respectively:
 +                                  v0 >= PHYS_STOPSPEED(this)
 +                                  v / (1 - dt * PHYS_FRICTION(this)) >= PHYS_STOPSPEED(this)
 +                                  v >= PHYS_STOPSPEED(this) * (1 - dt * PHYS_FRICTION(this))
 +                               */
 +                      }
 +                      const float addspeed = wishspeed - this.velocity * wishdir;
 +                      if (addspeed > 0) {
 +                              const float accelspeed = min(PHYS_ACCELERATE(this) * dt * wishspeed, addspeed);
 +                              this.velocity += accelspeed * wishdir;
 +                      }
-                       if (IS_CSQC && vdist(this.velocity, >, 0)) {
-                               PM_ClientMovement_Move(this);
-                       }
 +                      return;
 +              }
 +
-               if (IS_CSQC || time >= PHYS_TELEPORT_TIME(this)) {
++              if (IS_CSQC ? PHYS_WATERJUMP_TIME(this) <= 0 : time >= PHYS_TELEPORT_TIME(this)) {
 +                      PM_Accelerate(this, wishdir, wishspeed, wishspeed, this.com_phys_acc_rate, 1, 0, 0, 0);
 +              }
 +      }
-       PM_ClientMovement_Move(this);
 +}
 +
 +.entity groundentity;
 +/** for other entities */
 +void sys_phys_simulate_simple(entity this, float dt)
 +{
 +      vector mn = this.mins;
 +      vector mx = this.maxs;
 +
 +      vector g = '0 0 0';
 +      if (this.com_phys_gravity_factor && !g) g = '0 0 -1' * PHYS_GRAVITY(NULL);
 +
 +      vector acc = this.com_phys_acc;
 +      vector vel = this.com_phys_vel;
 +      vector pos = this.com_phys_pos;
 +
 +      // SV_Physics_Toss
 +
 +      vel += g * dt;
 +
 +      this.angles += dt * this.avelocity;
 +      float movetime = dt;
 +      for (int i = 0; i < MAX_CLIP_PLANES && movetime > 0; i++) {
 +              vector push = vel * movetime;
 +              vector p0 = pos;
 +              vector p1 = p0 + push;
 +              // SV_PushEntity
 +              tracebox(p0, mn, mx, p1, MOVE_NORMAL, this);
 +              if (!trace_startsolid) {
 +                      bool hit = trace_fraction < 1;
 +                      pos = trace_endpos;
 +                      entity ent = trace_ent;
 +                      // SV_LinkEdict_TouchAreaGrid
 +                      if (this.solid != SOLID_NOT) {
 +                              FOREACH_ENTITY_RADIUS_ORDERED(0.5 * (this.absmin + this.absmax), 0.5 * vlen(this.absmax - this.absmin), true, {
 +                                      if (it.solid != SOLID_TRIGGER || it == this) continue;
 +                                      if (gettouch(it) && boxesoverlap(it.absmin, it.absmax, this.absmin, this.absmax)) {
 +                                          // SV_LinkEdict_TouchAreaGrid_Call
 +                                          trace_allsolid = false;
 +                                          trace_startsolid = false;
 +                                          trace_fraction = 1;
 +                                          trace_inwater = false;
 +                                          trace_inopen = true;
 +                                          trace_endpos = it.origin;
 +                                          trace_plane_normal = '0 0 1';
 +                                          trace_plane_dist = 0;
 +                                          trace_ent = this;
 +                                          trace_dpstartcontents = 0;
 +                                          trace_dphitcontents = 0;
 +                                          trace_dphitq3surfaceflags = 0;
 +                                          trace_dphittexturename = string_null;
-                                           gettouch(it)((other = this, it));
++                                          gettouch(it)(this, it);
 +                                          vel = this.velocity;
 +                                      }
 +                              });
 +                      }
 +                      if (hit && this.solid >= SOLID_TRIGGER && (!IS_ONGROUND(this) || this.groundentity != ent)) {
 +                              // SV_Impact (ent, trace);
 +                              tracebox(p0, mn, mx, p1, MOVE_NORMAL, this);
-                               void(entity) touched = gettouch(this);
++                              void(entity, entity) touched = gettouch(this);
 +                              if (touched && this.solid != SOLID_NOT) {
-                                       touched((other = ent, this));
++                                      touched(ent, this);
 +                              }
-                               void(entity) touched2 = gettouch(ent);
++                              void(entity, entity) touched2 = gettouch(ent);
 +                              if (this && ent && touched2 && ent.solid != SOLID_NOT) {
 +                                      trace_endpos = ent.origin;
 +                                      trace_plane_normal *= -1;
 +                                      trace_plane_dist *= -1;
 +                                      trace_ent = this;
 +                                      trace_dpstartcontents = 0;
 +                                      trace_dphitcontents = 0;
 +                                      trace_dphitq3surfaceflags = 0;
 +                                      trace_dphittexturename = string_null;
-                                       touched2((other = this, ent));
++                                      touched2(this, ent);
 +                              }
 +                      }
 +              }
 +              // end SV_PushEntity
 +              if (wasfreed(this)) { return; }
 +              tracebox(p0, mn, mx, p1, MOVE_NORMAL, this);
 +              if (trace_fraction == 1) { break; }
 +              movetime *= 1 - min(1, trace_fraction);
 +              ClipVelocity(vel, trace_plane_normal, vel, 1);
 +      }
 +
 +      this.com_phys_acc = acc;
 +      this.com_phys_vel = vel;
 +      this.com_phys_pos = pos;
 +      setorigin(this, this.com_phys_pos);
 +}
 +
 +void sys_phys_update_single(entity this)
 +{
 +      sys_phys_simulate_simple(this, frametime);
  }
diff --combined qcsrc/lib/_all.inc
index 1d63a28e540425d8e6b12e5fcbf0fd853f652aca,b0c9ec901c76103dae5531c9bb0136b7ace93471..f52c9a3c735437ef6195f8c1499baceba51842af
@@@ -2,18 -2,6 +2,18 @@@
        #define COMPAT_NO_MOD_IS_XONOTIC
  #endif
  
 +#ifdef CSQC
 +#define IS_CSQC 1
 +#else
 +#define IS_CSQC 0
 +#endif
 +
 +#ifdef SVQC
 +#define IS_SVQC 1
 +#else
 +#define IS_SVQC 0
 +#endif
 +
  #include "compiler.qh"
  
  #ifndef QCC_SUPPORT_INT
@@@ -89,6 -77,7 +89,7 @@@ void    isnt_bool(float this) { print(f
  #include "file.qh"
  #include "functional.qh"
  #include "i18n.qh"
+ #include "intrusivelist.qh"
  #include "iter.qh"
  #include "json.qc"
  #include "lazy.qh"
  
  #include "matrix/_mod.inc"
  
+ #ifndef SVQC
+ #define objerror_safe(e)
+ #else
+ void make_safe_for_remove(entity this);
+     #define objerror_safe(e) make_safe_for_remove(e)
+ #endif
+ #define objerror(this, msg) MACRO_BEGIN { \
+       LOG_WARNING("======OBJECT ERROR======"); \
+       entity _e = (this); \
+       eprint(_e); \
+       objerror_safe(_e); \
+       delete(_e); \
+       LOG_WARNINGF("%s OBJECT ERROR in %s:\n%s\nTip: read above for entity information", PROGNAME, __FUNC__, msg); \
+ } MACRO_END
  #ifdef MENUQC
        void _m_init();
        void m_init() { if (_m_init) _m_init(); }
        #define CSQC_Ent_Remove _CSQC_Ent_Remove
  #endif
  #undef ENGINE_EVENT
 +
 +#ifndef MENUQC
 +      #include <ecs/_lib.qh>
 +      #include <ecs/components/_mod.qh>
 +#endif
diff --combined qcsrc/lib/iter.qh
index 49c2a41e856257b76214284d9ac1ae455561b2e8,82448b931bed86c3788be433ea8fd9588b130145..7183e90feab6bad7a26ce4ba98f8e01886ea8bca
        MACRO_BEGIN \
        { \
                int _i = 0; \
-               for (entity _it = list##_first; _it; (_it = _it.next, ++_i)) \
+               for (entity _it = list##_first, _next = NULL; _it; (_it = _next, ++_i)) \
                { \
                        const noref int i = _i; \
                        ITER_CONST noref entity it = _it; \
+                       _next = _it.next; \
                        if (cond) { LAMBDA(body) } \
                } \
        } MACRO_END
              if (cond) LAMBDA(body) \
          } \
      } MACRO_END
 +#define MUTEX_LOCK(this) MACRO_BEGIN \
 +      if (this) LOG_SEVEREF("Loop mutex held by %s", this); \
 +      this = __FUNC__; \
 +MACRO_END
 +#define MUTEX_UNLOCK(this) MACRO_BEGIN \
 +      this = string_null; \
 +MACRO_END
  #define _FOREACH_ENTITY_FIND_UNORDERED(id, T, fld, match, cond, body) \
        MACRO_BEGIN { \
 -              if (_FOREACH_ENTITY_FIND_##T##_##id##mutex) LOG_SEVEREF("Loop mutex held by %s", _FOREACH_ENTITY_FIND_##T##_##id##mutex); \
 -              _FOREACH_ENTITY_FIND_##T##_##id##mutex = __FUNC__; \
 +              MUTEX_LOCK(_FOREACH_ENTITY_FIND_##T##_##id##mutex); \
                entity _foundchain_first = _findchain##T##_tofield(fld, match, _FOREACH_ENTITY_FIND_##T##_next##id); \
                FOREACH_LIST(_foundchain, _FOREACH_ENTITY_FIND_##T##_next##id, cond, body); \
 -              _FOREACH_ENTITY_FIND_##T##_##id##mutex = string_null; \
 +              MUTEX_UNLOCK(_FOREACH_ENTITY_FIND_##T##_##id##mutex); \
        } MACRO_END
  
  #define FOREACH_ENTITY(cond, body) ORDERED(FOREACH_ENTITY)(cond, body)
  
  #ifndef MENUQC
  entity(vector org, float rad, .entity tofield) _findchainradius_tofield = #22;
 -#define FOREACH_ENTITY_RADIUS(org, dist, cond, body) FOREACH_ENTITY_RADIUS_UNORDERED(org, dist, cond, body)
 +#define FOREACH_ENTITY_RADIUS(org, dist, cond, body) ORDERED(FOREACH_ENTITY_RADIUS)(org, dist, cond, body)
  .entity _FOREACH_ENTITY_FIND_radius_next; noref string _FOREACH_ENTITY_FIND_radius_mutex;
  #define FOREACH_ENTITY_RADIUS_UNORDERED(org, dist, cond, body) _FOREACH_ENTITY_FIND_UNORDERED(, radius, org, dist, cond, body)
 +.entity _FOREACH_ENTITY_FIND_radius_nexttmp; noref string _FOREACH_ENTITY_FIND_radius_tmpmutex;
 +#define FOREACH_ENTITY_RADIUS_ORDERED(org, dist, cond, body) \
 +MACRO_BEGIN \
 +      entity _rev_first = NULL; \
 +      _FOREACH_ENTITY_FIND_UNORDERED(tmp, radius, org, dist, cond, (it._FOREACH_ENTITY_FIND_radius_nexttmp = _rev_first, _rev_first = it)); \
 +      MUTEX_LOCK(_FOREACH_ENTITY_FIND_radius_tmpmutex); \
 +      FOREACH_LIST(_rev, _FOREACH_ENTITY_FIND_radius_nexttmp, true, body); \
 +      MUTEX_UNLOCK(_FOREACH_ENTITY_FIND_radius_tmpmutex); \
 +MACRO_END
  #endif
  
  #define FOREACH_ENTITY_FLOAT(fld, match, body) ORDERED(FOREACH_ENTITY_FLOAT)(fld, match, body)
diff --combined qcsrc/server/g_world.qc
index 3ae2f27f397b3509fb1919e24972834dc3deb382,5102aecdf69e6ff2dd931611274a2630db91897a..7d477ac577daf27f5074f1b4194766172b6512b3
@@@ -540,7 -540,7 +540,7 @@@ spawnfunc(__init_dedicated_server
        cvar_string = cvar_string_normal;
        cvar_set = cvar_set_normal;
  
-       remove = remove_unsafely;
+       delete_fn = remove_unsafely;
  
        entity e = spawn();
        setthink(e, GotoFirstMap);
@@@ -657,7 -657,7 +657,7 @@@ spawnfunc(worldspawn
                error("world already spawned - you may have EXACTLY ONE worldspawn!");
        world_already_spawned = true;
  
-       remove = remove_safely; // during spawning, watch what you remove!
+       delete_fn = remove_safely; // during spawning, watch what you remove!
  
        cvar_changes_init(); // do this very early now so it REALLY matches the server config
  
@@@ -1299,6 -1299,7 +1299,7 @@@ When the player presses attack or jump
  void IntermissionThink(entity this)
  {
        FixIntermissionClient(this);
+       CSQCMODEL_AUTOUPDATE(this); // PlayerPostThink returns before calling this during intermission, so run it here
  
        float server_screenshot = (autocvar_sv_autoscreenshot && this.cvar_cl_autoscreenshot);
        float client_screenshot = (this.cvar_cl_autoscreenshot == 2);
@@@ -1487,7 -1488,7 +1488,7 @@@ void FixIntermissionClient(entity e
                e.health = -2342;
                // first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not)
                e.solid = SOLID_NOT;
-               e.movetype = MOVETYPE_NONE;
+               set_movetype(e, MOVETYPE_NONE);
                e.takedamage = DAMAGE_NO;
                for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                {
@@@ -1983,11 -1984,53 +1984,54 @@@ string GotoMap(string m
                return "Map switch will happen after scoreboard.";
  }
  
 -              
+ bool autocvar_sv_freezenonclients;
+ bool autocvar_sv_gameplayfix_delayprojectiles;
+ void Physics_Frame()
+ {
+       if(autocvar_sv_freezenonclients)
+               return;
+       FOREACH_ENTITY_FLOAT(pure_data, false,
+       {
+               if(IS_CLIENT(it) || it.classname == "" || it.movetype == MOVETYPE_PUSH || it.movetype == MOVETYPE_FAKEPUSH || it.movetype == MOVETYPE_PHYSICS)
+                       continue;
+               int mt = it.move_movetype;
+               if(mt == MOVETYPE_PUSH || mt == MOVETYPE_FAKEPUSH || mt == MOVETYPE_PHYSICS)
+               {
+                       it.move_qcphysics = false;
+                       it.movetype = mt;
+                       continue;
+               }
++
+               it.movetype = ((it.move_qcphysics) ? MOVETYPE_NONE : it.move_movetype);
+               if(it.move_movetype == MOVETYPE_NONE)
+                       continue;
+               if(it.move_qcphysics)
+                       Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false);
+       });
+       if(autocvar_sv_gameplayfix_delayprojectiles >= 0)
+               return;
+       FOREACH_ENTITY_FLOAT(move_qcphysics, true,
+       {
+               if(IS_CLIENT(it) || is_pure(it) || it.classname == "" || it.move_movetype == MOVETYPE_NONE)
+                       continue;
+               Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false);
+       });
+ }
 +void systems_update();
  void EndFrame()
  {
        anticheat_endframe();
  
+       Physics_Frame();
        FOREACH_CLIENT(IS_REAL_CLIENT(it), {
                entity e = IS_SPEC(it) ? it.enemy : it;
                if (e.typehitsound) {
                it.damage_dealt = 0;
                antilag_record(it, CS(it), altime);
        });
-       FOREACH_ENTITY_FLAGS(flags, FL_MONSTER, {
+       IL_EACH(g_monsters, true,
+       {
                antilag_record(it, it, altime);
        });
        FOREACH_CLIENT(PS(it), {
                PlayerState s = PS(it);
                s.ps_push(s, it);
        });
 +      systems_update();
+       IL_ENDFRAME();
  }
  
  
index e4d9156f6653308dbf8df3ab9d45bad3323951a6,72d3250daeecb6ae71febeb1f4308d13faaebe4f..212e8cffb6dd60de37196936df8722dc9682668c
@@@ -33,20 -33,25 +33,25 @@@ void crosshair_trace(entity pl
  {
        traceline_antilag(pl, pl.cursor_trace_start, pl.cursor_trace_start + normalize(pl.cursor_trace_endpos - pl.cursor_trace_start) * MAX_SHOT_DISTANCE, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
  }
+ .bool ctrace_solidchanged;
  void crosshair_trace_plusvisibletriggers(entity pl)
  {
-       entity first;
-       entity e;
-       first = findchainfloat(solid, SOLID_TRIGGER);
-       for (e = first; e; e = e.chain)
-               if (e.model != "")
-                       e.solid = SOLID_BSP;
+       FOREACH_ENTITY_FLOAT(solid, SOLID_TRIGGER,
+       {
+               if(it.model != "")
+               {
+                       it.solid = SOLID_BSP;
+                       it.ctrace_solidchanged = true;
+               }
+       });
  
        crosshair_trace(pl);
  
-       for (e = first; e; e = e.chain)
-               e.solid = SOLID_TRIGGER;
+       FOREACH_ENTITY_FLOAT(ctrace_solidchanged, true,
+       {
+               it.solid = SOLID_TRIGGER;
+               it.ctrace_solidchanged = false;
+       });
  }
  void WarpZone_crosshair_trace(entity pl)
  {
@@@ -881,7 -886,7 +886,7 @@@ void InitializeEntitiesRun(
  {
      entity startoflist = initialize_entity_first;
      initialize_entity_first = NULL;
-     remove = remove_except_protected;
+     delete_fn = remove_except_protected;
      for (entity e = startoflist; e; e = e.initialize_entity_next)
      {
                e.remove_except_protected_forbidden = 1;
          }
          e = next;
      }
-     remove = remove_unsafely;
+     delete_fn = remove_unsafely;
  }
  
  .float(entity) isEliminated;
@@@ -1031,7 -1036,8 +1036,7 @@@ bool SUB_NoImpactCheck(entity this, ent
        // these stop the projectile from moving, so...
        if(trace_dphitcontents == 0)
        {
 -              //dprint("A hit happened with zero hit contents... DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct.\n");
 -              LOG_TRACEF("A hit from a projectile happened with no hit contents! DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct. (edict: %d, classname: %s, origin: %s)\n", etof(this), this.classname, vtos(this.origin));
 +              LOG_TRACEF("A hit from a projectile happened with no hit contents! DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct. (edict: %i, classname: %s, origin: %v)", this, this.classname, this.origin);
                checkclient(this);
        }
      if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
@@@ -1318,7 -1324,7 +1323,7 @@@ void detach_sameorigin(entity e
  
  void follow_sameorigin(entity e, entity to)
  {
-     e.movetype = MOVETYPE_FOLLOW; // make the hole follow
+     set_movetype(e, MOVETYPE_FOLLOW); // make the hole follow
      e.aiment = to; // make the hole follow bmodel
      e.punchangle = to.angles; // the original angles of bmodel
      e.view_ofs = e.origin - to.origin; // relative origin
  
  void unfollow_sameorigin(entity e)
  {
-     e.movetype = MOVETYPE_NONE;
+     set_movetype(e, MOVETYPE_NONE);
  }
  
  entity gettaginfo_relative_ent;
@@@ -1349,7 -1355,7 +1354,7 @@@ vector gettaginfo_relative(entity e, fl
  void SetMovetypeFollow(entity ent, entity e)
  {
        // FIXME this may not be warpzone aware
-       ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
+       set_movetype(ent, MOVETYPE_FOLLOW); // make the hole follow
        ent.solid = SOLID_NOT; // MOVETYPE_FOLLOW is always non-solid - this means this cannot be teleported by warpzones any more! Instead, we must notice when our owner gets teleported.
        ent.aiment = e; // make the hole follow bmodel
        ent.punchangle = e.angles; // the original angles of bmodel
  }
  void UnsetMovetypeFollow(entity ent)
  {
-       ent.movetype = MOVETYPE_FLY;
+       set_movetype(ent, MOVETYPE_FLY);
        PROJECTILE_MAKETRIGGER(ent);
        ent.aiment = NULL;
  }
  float LostMovetypeFollow(entity ent)
  {
  /*
-       if(ent.movetype != MOVETYPE_FOLLOW)
+       if(ent.move_movetype != MOVETYPE_FOLLOW)
                if(ent.aiment)
                        error("???");
  */
diff --combined qcsrc/server/sv_main.qc
index dc2019792b134bbf826f86ddf232f3402fdf70c3,7469d2230de7e1bd79f3c9746410d7d42e880b23..a10edf45b4173a8a01df3b1632aa0578430ca027
@@@ -96,6 -96,7 +96,7 @@@ void CreatureFrame_Liquids(entity this
  void CreatureFrame_FallDamage(entity this)
  {
        if(!IS_VEHICLE(this) && !(this.flags & FL_PROJECTILE)) // vehicles don't get falling damage
+       if(this.velocity || this.oldvelocity) // moving or has moved
        {
                // check for falling damage
                float velocity_len = vlen(this.velocity);
  void CreatureFrame_All()
  {
        FOREACH_ENTITY_FLOAT(damagedbycontents, true, {
-               if (it.movetype == MOVETYPE_NOCLIP) continue;
+               if (it.move_movetype == MOVETYPE_NOCLIP) continue;
                CreatureFrame_Liquids(it);
                CreatureFrame_FallDamage(it);
                it.oldvelocity = it.velocity;
@@@ -152,20 -153,19 +153,20 @@@ Called before each frame by the serve
  float game_delay;
  float game_delay_last;
  
- bool autocvar_sv_autopause = true;
+ bool autocvar_sv_autopause = false;
  float RedirectionThink();
 -void PM_Main(Client this);
 +void systems_update();
 +void sys_phys_update(entity this, float dt);
  void StartFrame()
  {
      // TODO: if move is more than 50ms, split it into two moves (this matches QWSV behavior and the client prediction)
 -    FOREACH_ENTITY_CLASS(STR_PLAYER, IS_FAKE_CLIENT(it), PM_Main(it));
 +    FOREACH_ENTITY_CLASS(STR_PLAYER, IS_FAKE_CLIENT(it), sys_phys_update(it, frametime));
      FOREACH_ENTITY_CLASS(STR_PLAYER, IS_FAKE_CLIENT(it), PlayerPreThink(it));
  
        execute_next_frame();
        if (autocvar_sv_autopause && !server_is_dedicated) Pause_TryPause(true);
  
-       remove = remove_unsafely; // not during spawning!
+       delete_fn = remove_unsafely; // not during spawning!
        serverprevtime = servertime;
        servertime = time;
        serverframetime = frametime;
@@@ -368,6 -368,8 +369,8 @@@ LABEL(cvar_fail
                return;
        }
  
+       this.move_movetype = this.movetype;
        // support special -1 and -2 angle from radiant
        if (this.angles == '0 -1 0')
                this.angles = '-90 0 0';