This commit is contained in:
Tater 2024-09-18 14:22:19 +03:00 committed by GitHub
commit 21584980bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 124 additions and 113 deletions

View file

@ -333,9 +333,8 @@ public:
virtual void OnClientEnter(int ClientId) = 0;
virtual void OnClientDrop(int ClientId, const char *pReason) = 0;
virtual void OnClientPrepareInput(int ClientId, void *pInput) = 0;
virtual void OnClientDirectInput(int ClientId, void *pInput) = 0;
virtual void OnClientFreshInput(int ClientId, void *pInput) = 0;
virtual void OnClientPredictedInput(int ClientId, void *pInput) = 0;
virtual void OnClientPredictedEarlyInput(int ClientId, void *pInput) = 0;
virtual bool IsClientReady(int ClientId) const = 0;
virtual bool IsClientPlayer(int ClientId) const = 0;

View file

@ -208,8 +208,6 @@ void CServer::CClient::Reset()
// reset input
for(auto &Input : m_aInputs)
Input.m_GameTick = -1;
m_CurrentInput = 0;
mem_zero(&m_LatestInput, sizeof(m_LatestInput));
m_Snapshots.PurgeAll();
m_LastAckedSnapshot = -1;
@ -1633,12 +1631,22 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
const int LastAckedSnapshot = Unpacker.GetInt();
int IntendedTick = Unpacker.GetInt();
int Size = Unpacker.GetInt();
int BufferPosition = IntendedTick % 200;
// The client is not allowed to change inputs for a tick they have already sent to the server
if(m_aClients[ClientId].m_aInputs[BufferPosition].m_GameTick == IntendedTick)
{
return;
}
if(Unpacker.Error() || Size / 4 > MAX_INPUT_SIZE || IntendedTick < MIN_TICK || IntendedTick >= MAX_TICK)
{
return;
}
m_aClients[ClientId].m_LastAckedSnapshot = LastAckedSnapshot;
// The LastAckedSnapshot should only increase
if(LastAckedSnapshot > m_aClients[ClientId].m_LastAckedSnapshot || LastAckedSnapshot == -1)
m_aClients[ClientId].m_LastAckedSnapshot = LastAckedSnapshot;
if(m_aClients[ClientId].m_LastAckedSnapshot > 0)
m_aClients[ClientId].m_SnapRate = CClient::SNAPRATE_FULL;
@ -1660,11 +1668,18 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
m_aClients[ClientId].m_LastInputTick = IntendedTick;
CClient::CInput *pInput = &m_aClients[ClientId].m_aInputs[m_aClients[ClientId].m_CurrentInput];
// TODO: This should probably not be here, the most recent input can be found by looping over the ring buffer
// so we should not change the inputs original intended tick since we might need that information
if(IntendedTick <= Tick())
IntendedTick = Tick() + 1;
// Check once again that we are not overriding an input the client has already sent
// This is a workaround while the above code is still able to change IntendedTick
BufferPosition = IntendedTick % 200;
if(m_aClients[ClientId].m_aInputs[BufferPosition].m_GameTick == IntendedTick)
return;
CClient::CInput *pInput = &m_aClients[ClientId].m_aInputs[BufferPosition];
pInput->m_GameTick = IntendedTick;
for(int i = 0; i < Size / 4; i++)
@ -1677,14 +1692,13 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket)
}
GameServer()->OnClientPrepareInput(ClientId, pInput->m_aData);
mem_copy(m_aClients[ClientId].m_LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE * sizeof(int));
m_aClients[ClientId].m_CurrentInput++;
m_aClients[ClientId].m_CurrentInput %= 200;
CClient::CInput LatestInput;
mem_copy(LatestInput.m_aData, pInput->m_aData, MAX_INPUT_SIZE * sizeof(int));
// call the mod with the fresh input data
if(m_aClients[ClientId].m_State == CClient::STATE_INGAME)
GameServer()->OnClientDirectInput(ClientId, m_aClients[ClientId].m_LatestInput.m_aData);
GameServer()->OnClientFreshInput(ClientId, LatestInput.m_aData);
}
else if(Msg == NETMSG_RCON_CMD)
{
@ -2666,8 +2680,6 @@ void CServer::UpdateDebugDummies(bool ForceDisconnect)
Input.m_Direction = (ClientId & 1) ? -1 : 1;
m_aClients[ClientId].m_aInputs[0].m_GameTick = Tick() + 1;
mem_copy(m_aClients[ClientId].m_aInputs[0].m_aData, &Input, minimum(sizeof(Input), sizeof(m_aClients[ClientId].m_aInputs[0].m_aData)));
m_aClients[ClientId].m_LatestInput = m_aClients[ClientId].m_aInputs[0];
m_aClients[ClientId].m_CurrentInput = 0;
}
}
@ -2888,23 +2900,6 @@ int CServer::Run()
UpdateDebugDummies(false);
#endif
for(int c = 0; c < MAX_CLIENTS; c++)
{
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++;
@ -2926,7 +2921,6 @@ int CServer::Run()
if(!ClientHadInput)
GameServer()->OnClientPredictedInput(c, nullptr);
}
GameServer()->OnTick();
if(ErrorShutdown())
{

View file

@ -147,9 +147,7 @@ public:
int m_LastInputTick;
CSnapshotStorage m_Snapshots;
CInput m_LatestInput;
CInput m_aInputs[200]; // TODO: handle input better
int m_CurrentInput;
CInput m_aInputs[200];
char m_aName[MAX_NAME_LENGTH];
char m_aClan[MAX_CLAN_LENGTH];

View file

@ -403,7 +403,7 @@ void CCharacter::HandleWeaponSwitch()
DoWeaponSwitch();
}
void CCharacter::FireWeapon()
void CCharacter::FireWeapon(bool EarlyFire)
{
if(m_ReloadTimer != 0)
{
@ -530,7 +530,7 @@ void CCharacter::FireWeapon()
{
int Lifetime = (int)(Server()->TickSpeed() * GetTuning(m_TuneZone)->m_GunLifetime);
new CProjectile(
CProjectile *Projectile = new CProjectile(
GameWorld(),
WEAPON_GUN, //Type
m_pPlayer->GetCid(), //Owner
@ -542,6 +542,8 @@ void CCharacter::FireWeapon()
-1, //SoundImpact
MouseTarget //InitDir
);
if(EarlyFire)
Projectile->SetStartTick(Server()->Tick() - 1);
GameServer()->CreateSound(m_Pos, SOUND_GUN_FIRE, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
}
@ -552,7 +554,10 @@ void CCharacter::FireWeapon()
{
float LaserReach = GetTuning(m_TuneZone)->m_LaserReach;
new CLaser(&GameServer()->m_World, m_Pos, Direction, LaserReach, m_pPlayer->GetCid(), WEAPON_SHOTGUN);
CLaser *Laser = new CLaser(&GameServer()->m_World, m_Pos, Direction, LaserReach, m_pPlayer->GetCid(), WEAPON_SHOTGUN);
if(EarlyFire)
Laser->SetEvalTick(Server()->Tick() - 1);
GameServer()->CreateSound(m_Pos, SOUND_SHOTGUN_FIRE, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
}
break;
@ -561,7 +566,7 @@ void CCharacter::FireWeapon()
{
int Lifetime = (int)(Server()->TickSpeed() * GetTuning(m_TuneZone)->m_GrenadeLifetime);
new CProjectile(
CProjectile *Projectile = new CProjectile(
GameWorld(),
WEAPON_GRENADE, //Type
m_pPlayer->GetCid(), //Owner
@ -573,6 +578,8 @@ void CCharacter::FireWeapon()
SOUND_GRENADE_EXPLODE, //SoundImpact
MouseTarget // MouseTarget
);
if(EarlyFire)
Projectile->SetStartTick(Server()->Tick() - 1);
GameServer()->CreateSound(m_Pos, SOUND_GRENADE_FIRE, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
}
@ -582,7 +589,10 @@ void CCharacter::FireWeapon()
{
float LaserReach = GetTuning(m_TuneZone)->m_LaserReach;
new CLaser(GameWorld(), m_Pos, Direction, LaserReach, m_pPlayer->GetCid(), WEAPON_LASER);
CLaser *Laser = new CLaser(GameWorld(), m_Pos, Direction, LaserReach, m_pPlayer->GetCid(), WEAPON_LASER);
if(EarlyFire)
Laser->SetEvalTick(Server()->Tick() - 1);
GameServer()->CreateSound(m_Pos, SOUND_LASER_FIRE, TeamMask()); // NOLINT(clang-analyzer-unix.Malloc)
}
break;
@ -602,6 +612,8 @@ void CCharacter::FireWeapon()
}
m_AttackTick = Server()->Tick();
if(EarlyFire)
m_AttackTick--;
if(!m_ReloadTimer)
{
@ -671,6 +683,7 @@ void CCharacter::OnPredictedInput(CNetObj_PlayerInput *pNewInput)
// copy new input
mem_copy(&m_Input, pNewInput, sizeof(m_Input));
m_NumInputs++;
// it is not allowed to aim in the center
if(m_Input.m_TargetX == 0 && m_Input.m_TargetY == 0)
@ -679,28 +692,6 @@ void CCharacter::OnPredictedInput(CNetObj_PlayerInput *pNewInput)
mem_copy(&m_SavedInput, &m_Input, sizeof(m_SavedInput));
}
void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput)
{
mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
mem_copy(&m_LatestInput, pNewInput, sizeof(m_LatestInput));
m_NumInputs++;
// it is not allowed to aim in the center
if(m_LatestInput.m_TargetX == 0 && m_LatestInput.m_TargetY == 0)
m_LatestInput.m_TargetY = -1;
Antibot()->OnDirectInput(m_pPlayer->GetCid());
if(m_NumInputs > 1 && m_pPlayer->GetTeam() != TEAM_SPECTATORS)
{
HandleWeaponSwitch();
FireWeapon();
}
mem_copy(&m_LatestPrevPrevInput, &m_LatestPrevInput, sizeof(m_LatestInput));
mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
}
void CCharacter::ReleaseHook()
{
m_Core.SetHookedPlayer(-1);
@ -725,6 +716,27 @@ void CCharacter::ResetInput()
m_LatestPrevInput = m_LatestInput = m_Input;
}
void CCharacter::WeaponTick()
{
mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
mem_copy(&m_LatestInput, &m_Input, sizeof(m_LatestInput));
// it is not allowed to aim in the center
if(m_LatestInput.m_TargetX == 0 && m_LatestInput.m_TargetY == 0)
m_LatestInput.m_TargetY = -1;
Antibot()->OnDirectInput(m_pPlayer->GetCid());
if(m_NumInputs > 1 && m_pPlayer->GetTeam() != TEAM_SPECTATORS)
{
HandleWeaponSwitch();
FireWeapon(true);
}
mem_copy(&m_LatestPrevPrevInput, &m_LatestPrevInput, sizeof(m_LatestInput));
mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
}
void CCharacter::PreTick()
{
if(m_StartTime > Server()->Tick())

View file

@ -34,6 +34,7 @@ public:
void Reset() override;
void Destroy() override;
void PreTick();
void WeaponTick();
void Tick() override;
void TickDeferred() override;
void TickPaused() override;
@ -61,11 +62,11 @@ public:
void HandleJetpack();
void OnPredictedInput(CNetObj_PlayerInput *pNewInput);
void OnDirectInput(CNetObj_PlayerInput *pNewInput);
void ReleaseHook();
void ResetHook();
void ResetInput();
void FireWeapon();
void FireWeapon(bool EarlyFire = false);
void Die(int Killer, int Weapon, bool SendKillMsg = true);
bool TakeDamage(vec2 Force, int Dmg, int From, int Weapon);

View file

@ -35,6 +35,11 @@ CLaser::CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEner
DoBounce();
}
void CLaser::SetEvalTick(int Tick)
{
m_EvalTick = Tick;
}
bool CLaser::HitCharacter(vec2 From, vec2 To)
{
static const vec2 StackedLaserShotgunBugSpeed = vec2(-2147483648.0f, -2147483648.0f);
@ -98,9 +103,11 @@ bool CLaser::HitCharacter(vec2 From, vec2 To)
return true;
}
void CLaser::DoBounce()
void CLaser::DoBounce(bool EarlyTick)
{
m_EvalTick = Server()->Tick();
if(EarlyTick)
m_EvalTick--;
if(m_Energy < 0)
{

View file

@ -8,7 +8,13 @@
class CLaser : public CEntity
{
public:
CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEnergy, int Owner, int Type);
CLaser(
CGameWorld *pGameWorld,
vec2 Pos,
vec2 Direction,
float StartEnergy,
int Owner,
int Type);
virtual void Reset() override;
virtual void Tick() override;
@ -20,7 +26,7 @@ public:
protected:
bool HitCharacter(vec2 From, vec2 To);
void DoBounce();
void DoBounce(bool EarlyTick = false);
private:
vec2 m_From;
@ -42,6 +48,9 @@ private:
bool m_TeleportCancelled;
bool m_IsBlueTeleport;
bool m_BelongsToPracticeTeam;
public:
void SetEvalTick(int Tick);
};
#endif

View file

@ -49,6 +49,11 @@ CProjectile::CProjectile(
GameWorld()->InsertEntity(this);
}
void CProjectile::SetStartTick(int Tick)
{
m_StartTick = Tick;
}
void CProjectile::Reset()
{
m_MarkedForDestroy = true;

View file

@ -50,6 +50,7 @@ private:
vec2 m_InitDir;
public:
void SetStartTick(int Tick);
void SetBouncing(int Value);
bool FillExtraInfoLegacy(CNetObj_DDRaceProjectile *pProj);
void FillExtraInfo(CNetObj_DDNetProjectile *pProj);

View file

@ -1314,6 +1314,8 @@ void CGameContext::OnTick()
}
// Server hooks
// Called on all incoming NETMSG_INPUT, reformats player flags for sixup compatibility
void CGameContext::OnClientPrepareInput(int ClientId, void *pInput)
{
auto *pPlayerInput = (CNetObj_PlayerInput *)pInput;
@ -1321,10 +1323,11 @@ void CGameContext::OnClientPrepareInput(int ClientId, void *pInput)
pPlayerInput->m_PlayerFlags = PlayerFlags_SevenToSix(pPlayerInput->m_PlayerFlags);
}
void CGameContext::OnClientDirectInput(int ClientId, void *pInput)
// Called on all incoming NETMSG_INPUT, only sets player flags and tracks afk status
void CGameContext::OnClientFreshInput(int ClientId, void *pInput)
{
if(!m_World.m_Paused)
m_apPlayers[ClientId]->OnDirectInput((CNetObj_PlayerInput *)pInput);
m_apPlayers[ClientId]->OnPlayerFreshInput((CNetObj_PlayerInput *)pInput);
int Flags = ((CNetObj_PlayerInput *)pInput)->m_PlayerFlags;
if((Flags & 256) || (Flags & 512))
@ -1333,26 +1336,11 @@ void CGameContext::OnClientDirectInput(int ClientId, void *pInput)
}
}
// Called once per input that happens on this tick.
// pInput is nullptr if the client did not send any fresh input this tick.
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(pApplyInput);
}
void CGameContext::OnClientPredictedEarlyInput(int ClientId, void *pInput)
{
// early return if no input at all has been sent by a player
// early return if no input has ever been sent by the player
if(pInput == nullptr && !m_aPlayerHasInput[ClientId])
return;
@ -1364,16 +1352,12 @@ void CGameContext::OnClientPredictedEarlyInput(int ClientId, void *pInput)
}
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(pApplyInput);
m_apPlayers[ClientId]->OnPlayerInput(pApplyInput);
if(m_TeeHistorianActive)
{

View file

@ -320,9 +320,8 @@ public:
void OnClientEnter(int ClientId) override;
void OnClientDrop(int ClientId, const char *pReason) override;
void OnClientPrepareInput(int ClientId, void *pInput) override;
void OnClientDirectInput(int ClientId, void *pInput) override;
void OnClientFreshInput(int ClientId, void *pInput) override;
void OnClientPredictedInput(int ClientId, void *pInput) override;
void OnClientPredictedEarlyInput(int ClientId, void *pInput) override;
void TeehistorianRecordAntibot(const void *pData, int DataSize) override;
void TeehistorianRecordPlayerJoin(int ClientId, bool Sixup) override;

View file

@ -6,6 +6,7 @@
#include "entity.h"
#include "gamecontext.h"
#include "gamecontroller.h"
#include "player.h"
#include <engine/shared/config.h>
@ -214,6 +215,17 @@ void CGameWorld::Tick()
if(GameServer()->m_pController->IsForceBalanced())
GameServer()->SendChat(-1, TEAM_ALL, "Teams have been balanced");
// This is placed here so that certain weapon physics can happen before the regular Charecter Tick() to preserve physics accuracy.
// It is done in client order to preserve previous behavior.
for(auto &pPlayer : GameServer()->m_apPlayers)
{
if(!pPlayer)
continue;
CCharacter *pChar = pPlayer->GetCharacter();
if(pChar)
pChar->WeaponTick();
}
// update all objects
for(int i = 0; i < NUM_ENTTYPES; i++)
{

View file

@ -500,8 +500,13 @@ void CPlayer::OnDisconnect()
m_Moderating = false;
}
void CPlayer::OnPredictedInput(CNetObj_PlayerInput *pNewInput)
void CPlayer::OnPlayerInput(CNetObj_PlayerInput *pNewInput)
{
m_PlayerFlags = pNewInput->m_PlayerFlags;
if(!m_pCharacter && m_Team != TEAM_SPECTATORS && (pNewInput->m_Fire & 1))
m_Spawning = true;
// skip the input if chat is active
if((m_PlayerFlags & PLAYERFLAG_CHATTING) && (pNewInput->m_PlayerFlags & PLAYERFLAG_CHATTING))
return;
@ -520,7 +525,8 @@ void CPlayer::OnPredictedInput(CNetObj_PlayerInput *pNewInput)
GameServer()->SendBroadcast("This server uses an experimental translation from Teeworlds 0.7 to 0.6. Please report bugs on ddnet.org/discord", m_ClientId);
}
void CPlayer::OnDirectInput(CNetObj_PlayerInput *pNewInput)
// Afk player tracking
void CPlayer::OnPlayerFreshInput(CNetObj_PlayerInput *pNewInput)
{
Server()->SetClientFlags(m_ClientId, pNewInput->m_PlayerFlags);
@ -541,21 +547,6 @@ void CPlayer::OnDirectInput(CNetObj_PlayerInput *pNewInput)
}
}
void CPlayer::OnPredictedEarlyInput(CNetObj_PlayerInput *pNewInput)
{
m_PlayerFlags = pNewInput->m_PlayerFlags;
if(!m_pCharacter && m_Team != TEAM_SPECTATORS && (pNewInput->m_Fire & 1))
m_Spawning = true;
// skip the input if chat is active
if(m_PlayerFlags & PLAYERFLAG_CHATTING)
return;
if(m_pCharacter && !m_Paused)
m_pCharacter->OnDirectInput(pNewInput);
}
int CPlayer::GetClientVersion() const
{
return m_pGameServer->GetClientVersion(m_ClientId);

View file

@ -57,9 +57,8 @@ public:
void Snap(int SnappingClient);
void FakeSnap();
void OnDirectInput(CNetObj_PlayerInput *pNewInput);
void OnPredictedInput(CNetObj_PlayerInput *pNewInput);
void OnPredictedEarlyInput(CNetObj_PlayerInput *pNewInput);
void OnPlayerFreshInput(CNetObj_PlayerInput *pNewInput);
void OnPlayerInput(CNetObj_PlayerInput *pNewInput);
void OnDisconnect();
void KillCharacter(int Weapon = WEAPON_GAME, bool SendKillMsg = true);