mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
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:
parent
5e4722e8ba
commit
b99d8dc259
|
@ -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)
|
||||
for(auto &Input : m_aClients[c].m_aInputs)
|
||||
if(Input.m_GameTick == Tick() + 1)
|
||||
GameServer()->OnClientPredictedEarlyInput(c, Input.m_aData);
|
||||
{
|
||||
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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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];
|
||||
};
|
||||
|
||||
|
|
|
@ -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[] = {
|
||||
|
|
Loading…
Reference in a new issue