Always apply last sent input

Makes teehistorian during spawn more reproduceable.
Currently during respawn the first applied input doesn't get recorded.
Always appliying the last sent input fixes this.
This commit is contained in:
Zwelf 2022-03-27 13:31:07 +02:00
parent 5e4722e8ba
commit b99d8dc259
8 changed files with 127 additions and 21 deletions

View file

@ -2775,10 +2775,21 @@ int CServer::Run()
GameServer()->OnPreTickTeehistorian();
for(int c = 0; c < MAX_CLIENTS; c++)
if(m_aClients[c].m_State == CClient::STATE_INGAME)
{
if(m_aClients[c].m_State != CClient::STATE_INGAME)
continue;
bool ClientHadInput = false;
for(auto &Input : m_aClients[c].m_aInputs)
{
if(Input.m_GameTick == Tick() + 1)
{
GameServer()->OnClientPredictedEarlyInput(c, Input.m_aData);
ClientHadInput = true;
}
}
if(!ClientHadInput)
GameServer()->OnClientPredictedEarlyInput(c, nullptr);
}
m_CurrentGameTick++;
NewTicks++;
@ -2788,14 +2799,18 @@ int CServer::Run()
{
if(m_aClients[c].m_State != CClient::STATE_INGAME)
continue;
bool ClientHadInput = false;
for(auto &Input : m_aClients[c].m_aInputs)
{
if(Input.m_GameTick == Tick())
{
GameServer()->OnClientPredictedInput(c, Input.m_aData);
ClientHadInput = true;
break;
}
}
if(!ClientHadInput)
GameServer()->OnClientPredictedInput(c, nullptr);
}
GameServer()->OnTick();

View file

@ -3,6 +3,7 @@
#include <base/tl/sorted_array.h>
#include <iostream>
#include "base/system.h"
#include "gamecontext.h"
#include "teeinfo.h"
#include <antibot/antibot_data.h>
@ -73,6 +74,9 @@ void CGameContext::Construct(int Resetting)
for(auto &pPlayer : m_apPlayers)
pPlayer = 0;
mem_zero(&m_aLastPlayerInput, sizeof(m_aLastPlayerInput));
mem_zero(&m_aPlayerHasInput, sizeof(m_aPlayerHasInput));
m_pController = 0;
m_aVoteCommand[0] = 0;
m_VoteType = VOTE_TYPE_UNKNOWN;
@ -1153,18 +1157,49 @@ void CGameContext::OnClientDirectInput(int ClientID, void *pInput)
void CGameContext::OnClientPredictedInput(int ClientID, void *pInput)
{
// early return if no input at all has been sent by a player
if(pInput == nullptr && !m_aPlayerHasInput[ClientID])
return;
// set to last sent input when no new input has been sent
CNetObj_PlayerInput *pApplyInput = (CNetObj_PlayerInput *)pInput;
if(pApplyInput == nullptr)
{
pApplyInput = &m_aLastPlayerInput[ClientID];
}
if(!m_World.m_Paused)
m_apPlayers[ClientID]->OnPredictedInput((CNetObj_PlayerInput *)pInput);
m_apPlayers[ClientID]->OnPredictedInput(pApplyInput);
}
void CGameContext::OnClientPredictedEarlyInput(int ClientID, void *pInput)
{
// early return if no input at all has been sent by a player
if(pInput == nullptr && !m_aPlayerHasInput[ClientID])
return;
// set to last sent input when no new input has been sent
CNetObj_PlayerInput *pApplyInput = (CNetObj_PlayerInput *)pInput;
if(pApplyInput == nullptr)
{
pApplyInput = &m_aLastPlayerInput[ClientID];
}
else
{
// Store input in this function and not in `OnClientPredictedInput`,
// because this function is called on all inputs, while
// `OnClientPredictedInput` is only called on the first input of each
// tick.
mem_copy(&m_aLastPlayerInput[ClientID], pApplyInput, sizeof(m_aLastPlayerInput[ClientID]));
m_aPlayerHasInput[ClientID] = true;
}
if(!m_World.m_Paused)
m_apPlayers[ClientID]->OnPredictedEarlyInput((CNetObj_PlayerInput *)pInput);
m_apPlayers[ClientID]->OnPredictedEarlyInput(pApplyInput);
if(m_TeeHistorianActive)
{
m_TeeHistorian.RecordPlayerInput(ClientID, (CNetObj_PlayerInput *)pInput);
m_TeeHistorian.RecordPlayerInput(ClientID, m_apPlayers[ClientID]->GetUniqueCID(), pApplyInput);
}
}
@ -1348,6 +1383,8 @@ void CGameContext::OnClientEnter(int ClientID)
Server()->ExpireServerInfo();
CPlayer *pNewPlayer = m_apPlayers[ClientID];
mem_zero(&m_aLastPlayerInput[ClientID], sizeof(m_aLastPlayerInput[ClientID]));
m_aPlayerHasInput[ClientID] = false;
// new info for others
protocol7::CNetMsg_Sv_ClientInfo NewClientInfoMsg;
@ -1457,7 +1494,8 @@ void CGameContext::OnClientConnected(int ClientID, void *pData)
if(m_apPlayers[ClientID])
delete m_apPlayers[ClientID];
m_apPlayers[ClientID] = new(ClientID) CPlayer(this, ClientID, StartTeam);
m_apPlayers[ClientID] = new(ClientID) CPlayer(this, NextUniqueClientID, ClientID, StartTeam);
NextUniqueClientID += 1;
#ifdef CONF_DEBUG
if(g_Config.m_DbgDummies)

View file

@ -15,6 +15,7 @@
#include "eventhandler.h"
//#include "gamecontroller.h"
#include "game/generated/protocol.h"
#include "gameworld.h"
#include "teehistorian.h"
@ -149,6 +150,9 @@ public:
CEventHandler m_Events;
CPlayer *m_apPlayers[MAX_CLIENTS];
// keep last input to always apply when none is sent
CNetObj_PlayerInput m_aLastPlayerInput[MAX_CLIENTS];
bool m_aPlayerHasInput[MAX_CLIENTS];
IGameController *m_pController;
CGameWorld m_World;
@ -295,6 +299,8 @@ public:
std::shared_ptr<CScoreRandomMapResult> m_SqlRandomMapResult;
private:
// starting 1 to make 0 the special value "no client id"
uint32_t NextUniqueClientID = 1;
bool m_VoteWillPass;
class CScore *m_pScore;

View file

@ -3,6 +3,7 @@
#include "player.h"
#include <engine/shared/config.h>
#include "base/system.h"
#include "entities/character.h"
#include "gamecontext.h"
#include <engine/server.h>
@ -13,7 +14,8 @@ MACRO_ALLOC_POOL_ID_IMPL(CPlayer, MAX_CLIENTS)
IServer *CPlayer::Server() const { return m_pGameServer->Server(); }
CPlayer::CPlayer(CGameContext *pGameServer, int ClientID, int Team)
CPlayer::CPlayer(CGameContext *pGameServer, uint32_t UniqueClientID, int ClientID, int Team) :
m_UniqueClientID(UniqueClientID)
{
m_pGameServer = pGameServer;
m_ClientID = ClientID;

View file

@ -22,7 +22,7 @@ class CPlayer
MACRO_ALLOC_POOL_ID()
public:
CPlayer(CGameContext *pGameServer, int ClientID, int Team);
CPlayer(CGameContext *pGameServer, uint32_t UniqueClientID, int ClientID, int Team);
~CPlayer();
void Reset();
@ -33,6 +33,7 @@ public:
void SetTeam(int Team, bool DoChatMsg = true);
int GetTeam() const { return m_Team; }
int GetCID() const { return m_ClientID; }
uint32_t GetUniqueCID() const { return m_UniqueClientID; }
int GetClientVersion() const;
bool SetTimerType(int TimerType);
@ -113,6 +114,7 @@ public:
} m_Latency;
private:
const uint32_t m_UniqueClientID;
CCharacter *m_pCharacter;
int m_NumInputs;
CGameContext *m_pGameServer;

View file

@ -53,7 +53,8 @@ void CTeeHistorian::Reset(const CGameInfo *pGameInfo, WRITE_CALLBACK pfnWriteCal
for(auto &PrevPlayer : m_aPrevPlayers)
{
PrevPlayer.m_Alive = false;
PrevPlayer.m_InputExists = false;
// zero means no id
PrevPlayer.m_UniqueClientID = 0;
PrevPlayer.m_Team = 0;
}
for(auto &PrevTeam : m_aPrevTeams)
@ -256,7 +257,7 @@ void CTeeHistorian::RecordPlayer(int ClientID, const CNetObj_CharacterCore *pCha
{
dbg_assert(m_State == STATE_PLAYERS, "invalid teehistorian state");
CPlayer *pPrev = &m_aPrevPlayers[ClientID];
CTeehistorianPlayer *pPrev = &m_aPrevPlayers[ClientID];
if(!pPrev->m_Alive || pPrev->m_X != pChar->m_X || pPrev->m_Y != pChar->m_Y)
{
EnsureTickWrittenPlayerData(ClientID);
@ -299,7 +300,7 @@ void CTeeHistorian::RecordDeadPlayer(int ClientID)
{
dbg_assert(m_State == STATE_PLAYERS, "invalid teehistorian state");
CPlayer *pPrev = &m_aPrevPlayers[ClientID];
CTeehistorianPlayer *pPrev = &m_aPrevPlayers[ClientID];
if(pPrev->m_Alive)
{
EnsureTickWrittenPlayerData(ClientID);
@ -406,13 +407,13 @@ void CTeeHistorian::BeginInputs()
m_State = STATE_INPUTS;
}
void CTeeHistorian::RecordPlayerInput(int ClientID, const CNetObj_PlayerInput *pInput)
void CTeeHistorian::RecordPlayerInput(int ClientID, uint32_t UniqueClientID, const CNetObj_PlayerInput *pInput)
{
CPacker Buffer;
CPlayer *pPrev = &m_aPrevPlayers[ClientID];
CTeehistorianPlayer *pPrev = &m_aPrevPlayers[ClientID];
CNetObj_PlayerInput DiffInput;
if(pPrev->m_InputExists)
if(pPrev->m_UniqueClientID == UniqueClientID)
{
if(mem_comp(&pPrev->m_Input, pInput, sizeof(pPrev->m_Input)) == 0)
{
@ -447,7 +448,7 @@ void CTeeHistorian::RecordPlayerInput(int ClientID, const CNetObj_PlayerInput *p
{
Buffer.AddInt(((int *)&DiffInput)[i]);
}
pPrev->m_InputExists = true;
pPrev->m_UniqueClientID = UniqueClientID;
pPrev->m_Input = *pInput;
Write(Buffer.Data(), Buffer.Size());

View file

@ -12,6 +12,7 @@
class CConfig;
class CTuningParams;
class CUuidManager;
class CPlayer;
class CTeeHistorian
{
@ -62,7 +63,7 @@ public:
void EndPlayers();
void BeginInputs();
void RecordPlayerInput(int ClientID, const CNetObj_PlayerInput *pInput);
void RecordPlayerInput(int ClientID, uint32_t UniqueClientID, const CNetObj_PlayerInput *pInput);
void RecordPlayerMessage(int ClientID, const void *pMsg, int MsgSize);
void RecordPlayerJoin(int ClientID, int Protocol);
void RecordPlayerReady(int ClientID);
@ -106,14 +107,14 @@ private:
NUM_STATES,
};
struct CPlayer
struct CTeehistorianPlayer
{
bool m_Alive;
int m_X;
int m_Y;
CNetObj_PlayerInput m_Input;
bool m_InputExists;
uint32_t m_UniqueClientID;
// DDNet team
int m_Team;
@ -134,7 +135,7 @@ private:
int m_Tick;
int m_PrevMaxClientID;
int m_MaxClientID;
CPlayer m_aPrevPlayers[MAX_CLIENTS];
CTeehistorianPlayer m_aPrevPlayers[MAX_CLIENTS];
CTeam m_aPrevTeams[MAX_CLIENTS];
};

View file

@ -463,6 +463,47 @@ TEST_F(TeeHistorian, JoinLeave)
Expect(EXPECTED, sizeof(EXPECTED));
}
TEST_F(TeeHistorian, Input)
{
CNetObj_PlayerInput Input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
const unsigned char EXPECTED[] = {
// TICK_SKIP dt=0
0x41, 0x00,
// new player -> InputNew
0x45,
0x00, // ClientID 0
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
// same unique id, same input -> nothing
// same unique id, different input -> InputDiff
0x44,
0x00, // ClientID 0
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// different unique id, same input -> InputNew
0x45,
0x00, // ClientID 0
0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
// FINISH
0x40};
Tick(1);
// new player -> InputNew
m_TH.RecordPlayerInput(0, 1, &Input);
// same unique id, same input -> nothing
m_TH.RecordPlayerInput(0, 1, &Input);
Input.m_Direction = 0;
// same unique id, different input -> InputDiff
m_TH.RecordPlayerInput(0, 1, &Input);
// different unique id, same input -> InputNew
m_TH.RecordPlayerInput(0, 2, &Input);
Finish();
Expect(EXPECTED, sizeof(EXPECTED));
}
TEST_F(TeeHistorian, SaveSuccess)
{
const unsigned char EXPECTED[] = {