From: TimePath Date: Sun, 31 Jul 2016 09:49:35 +0000 (+1000) Subject: Merge branch 'master' into TimePath/physics X-Git-Tag: xonotic-v0.8.2~759 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=05ee5b1212a6537e5c5acb76dbc1ef9df40f85c6;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into TimePath/physics # Conflicts: # qcsrc/common/physics/player.qc # qcsrc/common/triggers/trigger/jumppads.qc # qcsrc/common/weapons/weapon/electro.qc # qcsrc/server/g_world.qc --- 05ee5b1212a6537e5c5acb76dbc1ef9df40f85c6 diff --cc qcsrc/common/physics/movetypes/movetypes.qh index 764b99380,6b75d81c0..35a73d333 --- a/qcsrc/common/physics/movetypes/movetypes.qh +++ b/qcsrc/common/physics/movetypes/movetypes.qh @@@ -5,10 -5,16 +5,12 @@@ #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; diff --cc qcsrc/common/physics/player.qc index 907784d35,1ef5faa59..74a44252f --- a/qcsrc/common/physics/player.qc +++ b/qcsrc/common/physics/player.qc @@@ -153,53 -154,8 +154,8 @@@ void PM_ClientMovement_UpdateStatus(ent } } - // 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 } @@@ -920,26 -964,148 +789,8 @@@ void PM_jetpack(entity this, float maxs 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) { @@@ -961,5 -1379,9 +812,9 @@@ void SV_PlayerPhysics(entity this 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 } diff --cc qcsrc/common/triggers/trigger/jumppads.qc index caa62e036,4a30ca695..5ec7b41fd --- a/qcsrc/common/triggers/trigger/jumppads.qc +++ b/qcsrc/common/triggers/trigger/jumppads.qc @@@ -235,30 -230,29 +230,30 @@@ void trigger_push_touch(entity this, en } 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) diff --cc qcsrc/common/weapons/weapon/electro.qc index 1fc3cb4b1,2853625f2..1f8cc1944 --- a/qcsrc/common/weapons/weapon/electro.qc +++ b/qcsrc/common/weapons/weapon/electro.qc @@@ -193,21 -194,17 +194,21 @@@ void W_Electro_Explode(entity this, ent 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); @@@ -256,7 -253,6 +257,7 @@@ { 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) @@@ -290,7 -286,7 +291,8 @@@ 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); @@@ -301,15 -297,50 +303,52 @@@ 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); diff --cc qcsrc/ecs/components/physics.qh index 377e1d1d8,502657e4b..f150a296c --- a/qcsrc/ecs/components/physics.qh +++ b/qcsrc/ecs/components/physics.qh @@@ -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; diff --cc qcsrc/ecs/systems/cl_physics.qc index 52e8ed0ca,000000000..206c80d96 mode 100644,000000..100644 --- a/qcsrc/ecs/systems/cl_physics.qc +++ b/qcsrc/ecs/systems/cl_physics.qc @@@ -1,30 -1,0 +1,30 @@@ +#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, 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) {} diff --cc qcsrc/ecs/systems/physics.qc index 7267a4864,8348b8785..8646b29cc --- a/qcsrc/ecs/systems/physics.qc +++ b/qcsrc/ecs/systems/physics.qc @@@ -8,486 -2,5 +8,484 @@@ void sys_phys_simulate_simple(entity th 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 --cc qcsrc/server/g_world.qc index 3ae2f27f3,5102aecdf..7d477ac57 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@@ -1983,7 -1984,47 +1984,48 @@@ 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(); @@@ -2015,7 -2059,7 +2060,8 @@@ PlayerState s = PS(it); s.ps_push(s, it); }); + systems_update(); + IL_ENDFRAME(); } diff --cc qcsrc/server/sv_main.qc index dc2019792,7469d2230..a10edf45b --- a/qcsrc/server/sv_main.qc +++ b/qcsrc/server/sv_main.qc @@@ -152,10 -153,9 +153,10 @@@ 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)