2512: Fix prediction when chatting while shooting/walking (fixes #2506) r=def- a=trml

Also fixed a case when switching to the dummy and back while holding fire (the reload timer should now stay in sync afterwards).

Co-authored-by: trml <trml@users.noreply.github.com>
This commit is contained in:
bors[bot] 2020-07-15 05:39:19 +00:00 committed by GitHub
commit c6ec6c5e50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 66 additions and 6 deletions

View file

@ -17,10 +17,10 @@ void CCharacter::SetWeapon(int W)
m_LastWeapon = m_Core.m_ActiveWeapon;
m_QueuedWeapon = -1;
m_Core.m_ActiveWeapon = W;
SetActiveWeapon(W);
if(m_Core.m_ActiveWeapon < 0 || m_Core.m_ActiveWeapon >= NUM_WEAPONS)
m_Core.m_ActiveWeapon = 0;
SetActiveWeapon(0);
}
void CCharacter::SetSolo(bool Solo)
@ -497,6 +497,10 @@ void CCharacter::GiveNinja()
void CCharacter::OnPredictedInput(CNetObj_PlayerInput *pNewInput)
{
// skip the input if chat is active
if(pNewInput->m_PlayerFlags&PLAYERFLAG_CHATTING)
return;
// copy new input
mem_copy(&m_Input, pNewInput, sizeof(m_Input));
//m_NumInputs++;
@ -510,6 +514,15 @@ void CCharacter::OnPredictedInput(CNetObj_PlayerInput *pNewInput)
void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput)
{
// skip the input if chat is active
if(pNewInput->m_PlayerFlags&PLAYERFLAG_CHATTING)
{
// reset input
ResetInput();
return;
}
m_NumInputs++;
mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
mem_copy(&m_LatestInput, pNewInput, sizeof(m_LatestInput));
@ -527,6 +540,18 @@ void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput)
mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
}
void CCharacter::ResetInput()
{
m_Input.m_Direction = 0;
//m_Input.m_Hook = 0;
// simulate releasing the fire button
if((m_Input.m_Fire&1) != 0)
m_Input.m_Fire++;
m_Input.m_Fire &= INPUT_STATE_MASK;
m_Input.m_Jump = 0;
m_LatestPrevInput = m_LatestInput = m_Input;
}
void CCharacter::Tick()
{
DDRaceTick();
@ -833,7 +858,7 @@ void CCharacter::HandleTiles(int Index)
void CCharacter::HandleTuneLayer()
{
int CurrentIndex = Collision()->GetMapIndex(m_Pos);
m_TuneZone = GameWorld()->m_WorldConfig.m_PredictTiles ? Collision()->IsTune(CurrentIndex) : 0;
SetTuneZone(GameWorld()->m_WorldConfig.m_PredictTiles ? Collision()->IsTune(CurrentIndex) : 0);
m_Core.m_pWorld->m_Tuning[g_Config.m_ClDummy] = *GetTuning(m_TuneZone); // throw tunings (from specific zone if in a tunezone) into gamecore
}
@ -1028,6 +1053,8 @@ void CCharacter::ResetPrediction()
m_Core.m_HookedPlayer = -1;
m_Core.m_HookState = HOOK_IDLE;
}
m_LastWeaponSwitchTick = 0;
m_LastTuneZoneTick = 0;
}
void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, bool IsLocal)
@ -1174,13 +1201,14 @@ void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtende
m_LastSnapWeapon = pChar->m_Weapon;
m_Alive = true;
m_TuneZone = GameWorld()->m_WorldConfig.m_PredictTiles ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0;
SetTuneZone(GameWorld()->m_WorldConfig.m_PredictTiles ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0);
// set the current weapon
if(pChar->m_Weapon != WEAPON_NINJA)
{
m_aWeapons[pChar->m_Weapon].m_Ammo = (GameWorld()->m_WorldConfig.m_InfiniteAmmo || GameWorld()->m_WorldConfig.m_IsDDRace || pChar->m_Weapon == WEAPON_HAMMER) ? -1 : pChar->m_AmmoCount;
SetActiveWeapon(pChar->m_Weapon);
if(pChar->m_Weapon != m_Core.m_ActiveWeapon)
SetActiveWeapon(pChar->m_Weapon);
}
// reset all input except direction and hook for non-local players (as in vanilla prediction)
@ -1193,6 +1221,19 @@ void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtende
m_Input.m_TargetX = cosf(pChar->m_Angle/256.0f);
m_Input.m_TargetY = sinf(pChar->m_Angle/256.0f);
}
// in most cases the reload timer can be determined from the last attack tick
// (this is only needed for autofire weapons to prevent the predicted reload timer from desyncing)
if(IsLocal && m_Core.m_ActiveWeapon != WEAPON_HAMMER)
{
if(maximum(m_LastTuneZoneTick, m_LastWeaponSwitchTick) + GameWorld()->GameTickSpeed() < GameWorld()->GameTick())
{
float FireDelay;
GetTuning(m_TuneZone)->Get(38 + m_Core.m_ActiveWeapon, &FireDelay);
const int FireDelayTicks = FireDelay * GameWorld()->GameTickSpeed() / 1000;
m_ReloadTimer = maximum(0, m_AttackTick + FireDelayTicks - GameWorld()->GameTick());
}
}
}
void CCharacter::SetCoreWorld(CGameWorld *pGameWorld)
@ -1208,3 +1249,17 @@ bool CCharacter::Match(CCharacter *pChar)
return false;
return true;
}
void CCharacter::SetActiveWeapon(int ActiveWeap)
{
m_Core.m_ActiveWeapon = ActiveWeap;
m_LastWeaponSwitchTick = GameWorld()->GameTick();
}
void CCharacter::SetTuneZone(int Zone)
{
if(Zone == m_TuneZone)
return;
m_TuneZone = Zone;
m_LastTuneZoneTick = GameWorld()->GameTick();
}

View file

@ -51,6 +51,7 @@ public:
void OnPredictedInput(CNetObj_PlayerInput *pNewInput);
void OnDirectInput(CNetObj_PlayerInput *pNewInput);
void ResetInput();
void FireWeapon();
bool TakeDamage(vec2 Force, int Dmg, int From, int Weapon);
@ -104,7 +105,7 @@ public:
int GetLastWeapon() { return m_LastWeapon; };
void SetLastWeapon(int LastWeap) { m_LastWeapon = LastWeap; };
int GetActiveWeapon() { return m_Core.m_ActiveWeapon; };
void SetActiveWeapon(int ActiveWeap) { m_Core.m_ActiveWeapon = ActiveWeap; };
void SetActiveWeapon(int ActiveWeap);
CCharacterCore GetCore() { return m_Core; };
void SetCore(CCharacterCore Core) { m_Core = Core; };
CCharacterCore* Core() { return &m_Core; };
@ -134,6 +135,7 @@ public:
bool Match(CCharacter *pChar);
void ResetPrediction();
CCharacter() { m_Alive = false; }
void SetTuneZone(int Zone);
private:
// weapon info
@ -187,6 +189,9 @@ private:
void HandleTuneLayer();
int m_StrongWeakID;
int m_LastWeaponSwitchTick;
int m_LastTuneZoneTick;
};
enum