Implement persistent hardcore death bans (XUID + IP) for dedicated server

On the dedicated server, hardcore death now persists XUID and IP bans to
banned-players.json and banned-ips.json via the Access system, and
disconnects the player. Bans survive server restarts. Client-hosted games
retain the existing in-memory XUID ban with force-save behavior.

- Add hardcore property to server.properties (forces Hard difficulty)
- Add LevelData::setHardcore() so loaded worlds respect the server config
- Add PlayerList::banPlayerForHardcoreDeath() with persistent XUID + IP bans
- Reject respawn requests server-side in hardcore mode
- Ensure server-side player ticks run without move packets (fixes
  environmental damage not applying for some clients)
- Restore 0x8 hardcore bit on LoginPacket/RespawnPacket wire format so
  the client-side death screen detects hardcore mode correctly
This commit is contained in:
Revela
2026-03-16 02:52:16 -05:00
parent 110cec3dab
commit d2e9808a26
14 changed files with 115 additions and 22 deletions

View File

@@ -39,6 +39,9 @@
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\Access\Access.h"
#include "..\Minecraft.Server\Common\StringUtils.h"
#include "..\Minecraft.Server\ServerLogger.h"
#include "..\Minecraft.Server\ServerLogManager.h"
extern bool g_Win64DedicatedServer;
#endif
@@ -1729,6 +1732,71 @@ void PlayerList::banXuid(PlayerUID xuid)
LeaveCriticalSection(&m_banCS);
}
void PlayerList::banPlayerForHardcoreDeath(ServerPlayer *player)
{
if (player == nullptr) return;
// Always apply the in-memory XUID ban (works for both client-hosted and dedicated)
banXuid(player->getOnlineXuid());
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (g_Win64DedicatedServer)
{
const std::string playerName = ServerRuntime::StringUtils::WideToUtf8(player->getName());
ServerRuntime::Access::BanMetadata metadata = ServerRuntime::Access::BanManager::BuildDefaultMetadata("Hardcore Death");
metadata.reason = "Died in hardcore mode";
// Ban online XUID
ServerRuntime::Access::AddPlayerBan(player->getOnlineXuid(), playerName, metadata);
// Also ban offline XUID if it differs (follows CliCommandBan pattern)
PlayerUID offlineXuid = player->getXuid();
if (offlineXuid != INVALID_XUID && offlineXuid != player->getOnlineXuid())
{
ServerRuntime::Access::AddPlayerBan(offlineXuid, playerName, metadata);
}
// Ban the player's IP address (uses same access path as CliCommandBanIp)
if (player->connection != nullptr && player->connection->connection != nullptr && player->connection->connection->getSocket() != nullptr)
{
const unsigned char smallId = player->connection->connection->getSocket()->getSmallId();
std::string ip;
if (smallId != 0 && ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(smallId, &ip))
{
ServerRuntime::Access::AddIpBan(ip, metadata);
ServerRuntime::LogInfof("Hardcore", "Player %s banned (XUID + IP %s) for dying in hardcore mode.", playerName.c_str(), ip.c_str());
}
else
{
ServerRuntime::LogInfof("Hardcore", "Player %s banned (XUID only, IP not available) for dying in hardcore mode.", playerName.c_str());
}
}
else
{
ServerRuntime::LogInfof("Hardcore", "Player %s banned (XUID only, no connection) for dying in hardcore mode.", playerName.c_str());
}
// Send ban reason then defer the actual close to the next tick, because this
// method runs mid-tick inside ServerPlayer::die(). A synchronous disconnect
// can invalidate the player/connection while the tick is still executing.
if (player->connection != nullptr)
{
player->connection->send(std::make_shared<DisconnectPacket>(DisconnectPacket::eDisconnect_Banned));
player->connection->closeOnTick();
}
}
else
#endif
{
// Client-hosted: force-save so the host cannot circumvent death by quitting without saving.
// On dedicated server the autosave handles persistence, so skip the forced save to avoid
// the client getting stuck on a "host is saving" screen during disconnect.
app.SetXuiServerAction(ProfileManager.GetPrimaryPad(), eXuiServerAction_SaveGame);
}
}
// AP added for Vita so the range can be increased once the level starts
void PlayerList::setViewDistance(int newViewDistance)
{