forked from cafeberry/cafeberry
feat: add smartcmd dedicated server
major bug: when connecting to the server a second time, it gets softlocked and keeps repeating ticking connection A\n in debug logs until it times out.
This commit is contained in:
@@ -27,9 +27,19 @@
|
||||
#include "..\Minecraft.World\Pos.h"
|
||||
#include "..\Minecraft.World\System.h"
|
||||
#include "..\Minecraft.World\StringHelpers.h"
|
||||
#include "../Minecraft.World/net.minecraft.world.entity.item.h"
|
||||
#include "../Minecraft.World/net.minecraft.world.item.h"
|
||||
#include "../Minecraft.World/net.minecraft.world.item.enchantment.h"
|
||||
#include "../Minecraft.World/net.minecraft.world.damagesource.h"
|
||||
#ifdef SPLIT_SAVES
|
||||
#include "..\Minecraft.World\ConsoleSaveFileSplit.h"
|
||||
#endif
|
||||
#if defined(_WINDOWS64) || defined(DISABLE_PSN) || defined(_DISABLE_XBLIVE)
|
||||
#include "Common/Network/NetworkSocketLayer.h"
|
||||
#endif
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
#include "../Minecraft.Server/ServerLogger.h"
|
||||
#endif
|
||||
#include "..\Minecraft.World\ConsoleSaveFileOriginal.h"
|
||||
#include "..\Minecraft.World\Socket.h"
|
||||
#include "..\Minecraft.World\net.minecraft.world.entity.h"
|
||||
@@ -78,6 +88,462 @@ bool MinecraftServer::s_slowQueuePacketSent = false;
|
||||
|
||||
unordered_map<wstring, int> MinecraftServer::ironTimers;
|
||||
|
||||
static bool ShouldUseDedicatedServerProperties()
|
||||
{
|
||||
#if defined(MINECRAFT_SERVER_BUILD)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int GetDedicatedServerInt(Settings *settings, const wchar_t *key, int defaultValue)
|
||||
{
|
||||
return (ShouldUseDedicatedServerProperties() && settings != nullptr) ? settings->getInt(key, defaultValue) : defaultValue;
|
||||
}
|
||||
|
||||
static bool GetDedicatedServerBool(Settings *settings, const wchar_t *key, bool defaultValue)
|
||||
{
|
||||
return (ShouldUseDedicatedServerProperties() && settings != nullptr) ? settings->getBoolean(key, defaultValue) : defaultValue;
|
||||
}
|
||||
|
||||
static wstring GetDedicatedServerString(Settings *settings, const wchar_t *key, const wstring &defaultValue)
|
||||
{
|
||||
return (ShouldUseDedicatedServerProperties() && settings != nullptr) ? settings->getString(key, defaultValue) : defaultValue;
|
||||
}
|
||||
|
||||
static void PrintConsoleLine(const wchar_t *prefix, const wstring &message)
|
||||
{
|
||||
wprintf(L"%ls%ls\n", prefix, message.c_str());
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static bool TryParseIntValue(const wstring &text, int &value)
|
||||
{
|
||||
std::wistringstream stream(text);
|
||||
stream >> value;
|
||||
return !stream.fail() && stream.eof();
|
||||
}
|
||||
|
||||
static vector<wstring> SplitConsoleCommand(const wstring &command)
|
||||
{
|
||||
vector<wstring> tokens;
|
||||
std::wistringstream stream(command);
|
||||
wstring token;
|
||||
while (stream >> token)
|
||||
{
|
||||
tokens.push_back(token);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
static wstring JoinConsoleCommandTokens(const vector<wstring> &tokens, size_t startIndex)
|
||||
{
|
||||
wstring joined;
|
||||
for (size_t i = startIndex; i < tokens.size(); ++i)
|
||||
{
|
||||
if (!joined.empty()) joined += L" ";
|
||||
joined += tokens[i];
|
||||
}
|
||||
return joined;
|
||||
}
|
||||
|
||||
static shared_ptr<ServerPlayer> FindPlayerByName(PlayerList *playerList, const wstring &name)
|
||||
{
|
||||
if (playerList == nullptr) return nullptr;
|
||||
|
||||
for (size_t i = 0; i < playerList->players.size(); ++i)
|
||||
{
|
||||
shared_ptr<ServerPlayer> player = playerList->players[i];
|
||||
if (player != nullptr && equalsIgnoreCase(player->getName(), name))
|
||||
{
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void SetAllLevelTimes(MinecraftServer *server, int value)
|
||||
{
|
||||
for (unsigned int i = 0; i < server->levels.length; ++i)
|
||||
{
|
||||
if (server->levels[i] != nullptr)
|
||||
{
|
||||
server->levels[i]->setDayTime(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ExecuteConsoleCommand(MinecraftServer *server, const wstring &rawCommand)
|
||||
{
|
||||
if (server == nullptr)
|
||||
return false;
|
||||
|
||||
wstring command = trimString(rawCommand);
|
||||
if (command.empty())
|
||||
return true;
|
||||
|
||||
if (command[0] == L'/')
|
||||
{
|
||||
command = trimString(command.substr(1));
|
||||
}
|
||||
|
||||
vector<wstring> tokens = SplitConsoleCommand(command);
|
||||
if (tokens.empty())
|
||||
return true;
|
||||
|
||||
const wstring action = toLower(tokens[0]);
|
||||
PlayerList *playerList = server->getPlayers();
|
||||
|
||||
if (action == L"help" || action == L"?")
|
||||
{
|
||||
server->info(L"Commands: help, stop, list, say <message>, save-all, time <set day|night|ticks|add ticks>, weather <clear|rain|thunder> [seconds], tp <player> <target>, give <player> <itemId> [amount] [aux], enchant <player> <enchantId> [level], kill <player>");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"stop")
|
||||
{
|
||||
server->info(L"Stopping server...");
|
||||
MinecraftServer::HaltServer();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"list")
|
||||
{
|
||||
wstring playerNames = (playerList != nullptr) ? playerList->getPlayerNames() : L"";
|
||||
if (playerNames.empty()) playerNames = L"(none)";
|
||||
server->info(L"Players (" + std::to_wstring((playerList != nullptr) ? playerList->getPlayerCount() : 0) + L"): " + playerNames);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"say")
|
||||
{
|
||||
if (tokens.size() < 2)
|
||||
{
|
||||
server->warn(L"Usage: say <message>");
|
||||
return false;
|
||||
}
|
||||
|
||||
wstring message = L"[Server] " + JoinConsoleCommandTokens(tokens, 1);
|
||||
if (playerList != nullptr)
|
||||
{
|
||||
playerList->broadcastAll(std::make_shared<ChatPacket>(message));
|
||||
}
|
||||
server->info(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"save-all")
|
||||
{
|
||||
if (playerList != nullptr)
|
||||
{
|
||||
playerList->saveAll(nullptr, false);
|
||||
}
|
||||
server->info(L"World saved.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"time")
|
||||
{
|
||||
if (tokens.size() < 2)
|
||||
{
|
||||
server->warn(L"Usage: time set <day|night|ticks> | time add <ticks>");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (toLower(tokens[1]) == L"add")
|
||||
{
|
||||
if (tokens.size() < 3)
|
||||
{
|
||||
server->warn(L"Usage: time add <ticks>");
|
||||
return false;
|
||||
}
|
||||
|
||||
int delta = 0;
|
||||
if (!TryParseIntValue(tokens[2], delta))
|
||||
{
|
||||
server->warn(L"Invalid tick value: " + tokens[2]);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < server->levels.length; ++i)
|
||||
{
|
||||
if (server->levels[i] != nullptr)
|
||||
{
|
||||
server->levels[i]->setDayTime(server->levels[i]->getDayTime() + delta);
|
||||
}
|
||||
}
|
||||
|
||||
server->info(L"Added " + std::to_wstring(delta) + L" ticks.");
|
||||
return true;
|
||||
}
|
||||
|
||||
wstring timeValue = toLower(tokens[1]);
|
||||
if (timeValue == L"set")
|
||||
{
|
||||
if (tokens.size() < 3)
|
||||
{
|
||||
server->warn(L"Usage: time set <day|night|ticks>");
|
||||
return false;
|
||||
}
|
||||
timeValue = toLower(tokens[2]);
|
||||
}
|
||||
|
||||
int targetTime = 0;
|
||||
if (timeValue == L"day")
|
||||
{
|
||||
targetTime = 0;
|
||||
}
|
||||
else if (timeValue == L"night")
|
||||
{
|
||||
targetTime = 12500;
|
||||
}
|
||||
else if (!TryParseIntValue(timeValue, targetTime))
|
||||
{
|
||||
server->warn(L"Invalid time value: " + timeValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
SetAllLevelTimes(server, targetTime);
|
||||
server->info(L"Time set to " + std::to_wstring(targetTime) + L".");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"weather")
|
||||
{
|
||||
if (tokens.size() < 2)
|
||||
{
|
||||
server->warn(L"Usage: weather <clear|rain|thunder> [seconds]");
|
||||
return false;
|
||||
}
|
||||
|
||||
int durationSeconds = 600;
|
||||
if (tokens.size() >= 3 && !TryParseIntValue(tokens[2], durationSeconds))
|
||||
{
|
||||
server->warn(L"Invalid duration: " + tokens[2]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (server->levels[0] == nullptr)
|
||||
{
|
||||
server->warn(L"The overworld is not loaded.");
|
||||
return false;
|
||||
}
|
||||
|
||||
LevelData *levelData = server->levels[0]->getLevelData();
|
||||
int duration = durationSeconds * SharedConstants::TICKS_PER_SECOND;
|
||||
levelData->setRainTime(duration);
|
||||
levelData->setThunderTime(duration);
|
||||
|
||||
wstring weather = toLower(tokens[1]);
|
||||
if (weather == L"clear")
|
||||
{
|
||||
levelData->setRaining(false);
|
||||
levelData->setThundering(false);
|
||||
}
|
||||
else if (weather == L"rain")
|
||||
{
|
||||
levelData->setRaining(true);
|
||||
levelData->setThundering(false);
|
||||
}
|
||||
else if (weather == L"thunder")
|
||||
{
|
||||
levelData->setRaining(true);
|
||||
levelData->setThundering(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
server->warn(L"Usage: weather <clear|rain|thunder> [seconds]");
|
||||
return false;
|
||||
}
|
||||
|
||||
server->info(L"Weather set to " + weather + L".");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"tp" || action == L"teleport")
|
||||
{
|
||||
if (tokens.size() < 3)
|
||||
{
|
||||
server->warn(L"Usage: tp <player> <target>");
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<ServerPlayer> subject = FindPlayerByName(playerList, tokens[1]);
|
||||
shared_ptr<ServerPlayer> destination = FindPlayerByName(playerList, tokens[2]);
|
||||
if (subject == nullptr)
|
||||
{
|
||||
server->warn(L"Unknown player: " + tokens[1]);
|
||||
return false;
|
||||
}
|
||||
if (destination == nullptr)
|
||||
{
|
||||
server->warn(L"Unknown player: " + tokens[2]);
|
||||
return false;
|
||||
}
|
||||
if (subject->level->dimension->id != destination->level->dimension->id || !subject->isAlive())
|
||||
{
|
||||
server->warn(L"Teleport failed because the players are not in the same dimension or the source player is dead.");
|
||||
return false;
|
||||
}
|
||||
|
||||
subject->ride(nullptr);
|
||||
subject->connection->teleport(destination->x, destination->y, destination->z, destination->yRot, destination->xRot);
|
||||
server->info(L"Teleported " + subject->getName() + L" to " + destination->getName() + L".");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"give")
|
||||
{
|
||||
if (tokens.size() < 3)
|
||||
{
|
||||
server->warn(L"Usage: give <player> <itemId> [amount] [aux]");
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
|
||||
if (player == nullptr)
|
||||
{
|
||||
server->warn(L"Unknown player: " + tokens[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
int itemId = 0;
|
||||
int amount = 1;
|
||||
int aux = 0;
|
||||
if (!TryParseIntValue(tokens[2], itemId))
|
||||
{
|
||||
server->warn(L"Invalid item id: " + tokens[2]);
|
||||
return false;
|
||||
}
|
||||
if (tokens.size() >= 4 && !TryParseIntValue(tokens[3], amount))
|
||||
{
|
||||
server->warn(L"Invalid amount: " + tokens[3]);
|
||||
return false;
|
||||
}
|
||||
if (tokens.size() >= 5 && !TryParseIntValue(tokens[4], aux))
|
||||
{
|
||||
server->warn(L"Invalid aux value: " + tokens[4]);
|
||||
return false;
|
||||
}
|
||||
if (itemId <= 0 || Item::items[itemId] == nullptr)
|
||||
{
|
||||
server->warn(L"Unknown item id: " + std::to_wstring(itemId));
|
||||
return false;
|
||||
}
|
||||
if (amount <= 0)
|
||||
{
|
||||
server->warn(L"Amount must be positive.");
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<ItemInstance> itemInstance(new ItemInstance(itemId, amount, aux));
|
||||
shared_ptr<ItemEntity> drop = player->drop(itemInstance);
|
||||
if (drop != nullptr)
|
||||
{
|
||||
drop->throwTime = 0;
|
||||
}
|
||||
server->info(L"Gave item " + std::to_wstring(itemId) + L" x" + std::to_wstring(amount) + L" to " + player->getName() + L".");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"enchant")
|
||||
{
|
||||
if (tokens.size() < 3)
|
||||
{
|
||||
server->warn(L"Usage: enchant <player> <enchantId> [level]");
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
|
||||
if (player == nullptr)
|
||||
{
|
||||
server->warn(L"Unknown player: " + tokens[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
int enchantmentId = 0;
|
||||
int enchantmentLevel = 1;
|
||||
if (!TryParseIntValue(tokens[2], enchantmentId))
|
||||
{
|
||||
server->warn(L"Invalid enchantment id: " + tokens[2]);
|
||||
return false;
|
||||
}
|
||||
if (tokens.size() >= 4 && !TryParseIntValue(tokens[3], enchantmentLevel))
|
||||
{
|
||||
server->warn(L"Invalid enchantment level: " + tokens[3]);
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<ItemInstance> selectedItem = player->getSelectedItem();
|
||||
if (selectedItem == nullptr)
|
||||
{
|
||||
server->warn(L"The player is not holding an item.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Enchantment *enchantment = Enchantment::enchantments[enchantmentId];
|
||||
if (enchantment == nullptr)
|
||||
{
|
||||
server->warn(L"Unknown enchantment id: " + std::to_wstring(enchantmentId));
|
||||
return false;
|
||||
}
|
||||
if (!enchantment->canEnchant(selectedItem))
|
||||
{
|
||||
server->warn(L"That enchantment cannot be applied to the selected item.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enchantmentLevel < enchantment->getMinLevel()) enchantmentLevel = enchantment->getMinLevel();
|
||||
if (enchantmentLevel > enchantment->getMaxLevel()) enchantmentLevel = enchantment->getMaxLevel();
|
||||
|
||||
if (selectedItem->hasTag())
|
||||
{
|
||||
ListTag<CompoundTag> *enchantmentTags = selectedItem->getEnchantmentTags();
|
||||
if (enchantmentTags != nullptr)
|
||||
{
|
||||
for (int i = 0; i < enchantmentTags->size(); i++)
|
||||
{
|
||||
int type = enchantmentTags->get(i)->getShort((wchar_t *)ItemInstance::TAG_ENCH_ID);
|
||||
if (Enchantment::enchantments[type] != nullptr && !Enchantment::enchantments[type]->isCompatibleWith(enchantment))
|
||||
{
|
||||
server->warn(L"That enchantment conflicts with an existing enchantment on the selected item.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedItem->enchant(enchantment, enchantmentLevel);
|
||||
server->info(L"Enchanted " + player->getName() + L"'s held item with " + std::to_wstring(enchantmentId) + L" " + std::to_wstring(enchantmentLevel) + L".");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"kill")
|
||||
{
|
||||
if (tokens.size() < 2)
|
||||
{
|
||||
server->warn(L"Usage: kill <player>");
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
|
||||
if (player == nullptr)
|
||||
{
|
||||
server->warn(L"Unknown player: " + tokens[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
player->hurt(DamageSource::outOfWorld, 3.4e38f);
|
||||
server->info(L"Killed " + player->getName() + L".");
|
||||
return true;
|
||||
}
|
||||
|
||||
server->warn(L"Unknown command: " + command);
|
||||
return false;
|
||||
}
|
||||
|
||||
MinecraftServer::MinecraftServer()
|
||||
{
|
||||
// 4J - added initialisers
|
||||
@@ -105,6 +571,7 @@ MinecraftServer::MinecraftServer()
|
||||
playerIdleTimeout = 0;
|
||||
m_postUpdateThread = NULL;
|
||||
forceGameType = false;
|
||||
m_spawnProtectionRadius = 0;
|
||||
|
||||
commandDispatcher = new ServerCommandDispatcher();
|
||||
|
||||
@@ -149,6 +616,10 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW
|
||||
logger.info("Loading properties");
|
||||
#endif
|
||||
settings = new Settings(new File(L"server.properties"));
|
||||
// Dedicated-only: spawn-protection radius in blocks; 0 disables protection.
|
||||
m_spawnProtectionRadius = GetDedicatedServerInt(settings, L"spawn-protection", 0);
|
||||
if (m_spawnProtectionRadius < 0) m_spawnProtectionRadius = 0;
|
||||
if (m_spawnProtectionRadius > 256) m_spawnProtectionRadius = 256;
|
||||
|
||||
app.DebugPrintf("\n*** SERVER SETTINGS ***\n");
|
||||
app.DebugPrintf("ServerSettings: host-friends-only is %s\n",(app.GetGameHostOption(eGameHostOption_FriendsOfFriends)>0)?"on":"off");
|
||||
@@ -156,6 +627,7 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW
|
||||
app.DebugPrintf("ServerSettings: pvp is %s\n",(app.GetGameHostOption(eGameHostOption_PvP)>0)?"on":"off");
|
||||
app.DebugPrintf("ServerSettings: fire spreads is %s\n",(app.GetGameHostOption(eGameHostOption_FireSpreads)>0)?"on":"off");
|
||||
app.DebugPrintf("ServerSettings: tnt explodes is %s\n",(app.GetGameHostOption(eGameHostOption_TNT)>0)?"on":"off");
|
||||
app.DebugPrintf("ServerSettings: spawn protection radius is %d\n", m_spawnProtectionRadius);
|
||||
app.DebugPrintf("\n");
|
||||
|
||||
// TODO 4J Stu - Init a load of settings based on data passed as params
|
||||
@@ -1172,7 +1644,9 @@ Level *MinecraftServer::getCommandSenderWorld()
|
||||
|
||||
int MinecraftServer::getSpawnProtectionRadius()
|
||||
{
|
||||
return 16;
|
||||
// Client-host mode must never apply dedicated-server spawn protection settings.
|
||||
if (!ShouldUseDedicatedServerProperties()) return 0;
|
||||
return m_spawnProtectionRadius;
|
||||
}
|
||||
|
||||
bool MinecraftServer::isUnderSpawnProtection(Level *level, int x, int y, int z, shared_ptr<Player> player)
|
||||
|
||||
Reference in New Issue
Block a user