aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.Client/MinecraftServer.cpp
diff options
context:
space:
mode:
authordaoge_cmd <3523206925@qq.com>2026-03-04 16:18:47 +0800
committerdaoge_cmd <3523206925@qq.com>2026-03-04 17:29:43 +0800
commitd112090fde200c545a70ec5dc33fe91cca0f26ec (patch)
tree9adf5fea35a3e1bccb40d94638fdf63f45baedd5 /Minecraft.Client/MinecraftServer.cpp
parent8ecfc525471720012f36a0016d88a4f0f4cfaa1d (diff)
feat: headless server
Diffstat (limited to 'Minecraft.Client/MinecraftServer.cpp')
-rw-r--r--Minecraft.Client/MinecraftServer.cpp470
1 files changed, 463 insertions, 7 deletions
diff --git a/Minecraft.Client/MinecraftServer.cpp b/Minecraft.Client/MinecraftServer.cpp
index 4206a399..974c7405 100644
--- a/Minecraft.Client/MinecraftServer.cpp
+++ b/Minecraft.Client/MinecraftServer.cpp
@@ -27,6 +27,11 @@
#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"
+#include <sstream>
#ifdef SPLIT_SAVES
#include "..\Minecraft.World\ConsoleSaveFileSplit.h"
#endif
@@ -78,6 +83,438 @@ bool MinecraftServer::s_slowQueuePacketSent = false;
unordered_map<wstring, int> MinecraftServer::ironTimers;
+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 == NULL) return nullptr;
+
+ for (size_t i = 0; i < playerList->players.size(); ++i)
+ {
+ shared_ptr<ServerPlayer> player = playerList->players[i];
+ if (player != NULL && 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] != NULL)
+ {
+ server->levels[i]->setDayTime(value);
+ }
+ }
+}
+
+static bool ExecuteConsoleCommand(MinecraftServer *server, const wstring &rawCommand)
+{
+ if (server == NULL)
+ 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 != NULL) ? playerList->getPlayerNames() : L"";
+ if (playerNames.empty()) playerNames = L"(none)";
+ server->info(L"Players (" + _toString((playerList != NULL) ? 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 != NULL)
+ {
+ playerList->broadcastAll(shared_ptr<ChatPacket>(new ChatPacket(message)));
+ }
+ server->info(message);
+ return true;
+ }
+
+ if (action == L"save-all")
+ {
+ if (playerList != NULL)
+ {
+ playerList->saveAll(NULL, 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] != NULL)
+ {
+ server->levels[i]->setDayTime(server->levels[i]->getDayTime() + delta);
+ }
+ }
+
+ server->info(L"Added " + _toString(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 " + _toString(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] == NULL)
+ {
+ 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 == NULL)
+ {
+ server->warn(L"Unknown player: " + tokens[1]);
+ return false;
+ }
+ if (destination == NULL)
+ {
+ 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 == NULL)
+ {
+ 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] == NULL)
+ {
+ server->warn(L"Unknown item id: " + _toString(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 != NULL)
+ {
+ drop->throwTime = 0;
+ }
+ server->info(L"Gave item " + _toString(itemId) + L" x" + _toString(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 == NULL)
+ {
+ 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 == NULL)
+ {
+ server->warn(L"The player is not holding an item.");
+ return false;
+ }
+
+ Enchantment *enchantment = Enchantment::enchantments[enchantmentId];
+ if (enchantment == NULL)
+ {
+ server->warn(L"Unknown enchantment id: " + _toString(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 != NULL)
+ {
+ for (int i = 0; i < enchantmentTags->size(); i++)
+ {
+ int type = enchantmentTags->get(i)->getShort((wchar_t *)ItemInstance::TAG_ENCH_ID);
+ if (Enchantment::enchantments[type] != NULL && !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 " + _toString(enchantmentId) + L" " + _toString(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 == NULL)
+ {
+ 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
@@ -107,12 +544,14 @@ MinecraftServer::MinecraftServer()
forceGameType = false;
commandDispatcher = new ServerCommandDispatcher();
+ InitializeCriticalSection(&m_consoleInputCS);
DispenserBootstrap::bootStrap();
}
MinecraftServer::~MinecraftServer()
{
+ DeleteCriticalSection(&m_consoleInputCS);
}
bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DWORD initSettings, bool findSeed)
@@ -150,6 +589,15 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW
#endif
settings = new Settings(new File(L"server.properties"));
+ app.SetGameHostOption(eGameHostOption_Difficulty, settings->getInt(L"difficulty", app.GetGameHostOption(eGameHostOption_Difficulty)));
+ app.SetGameHostOption(eGameHostOption_GameType, settings->getInt(L"gamemode", app.GetGameHostOption(eGameHostOption_GameType)));
+ app.SetGameHostOption(eGameHostOption_Structures, settings->getBoolean(L"generate-structures", app.GetGameHostOption(eGameHostOption_Structures) > 0) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_BonusChest, settings->getBoolean(L"bonus-chest", app.GetGameHostOption(eGameHostOption_BonusChest) > 0) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_PvP, settings->getBoolean(L"pvp", app.GetGameHostOption(eGameHostOption_PvP) > 0) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_TrustPlayers, settings->getBoolean(L"trust-players", app.GetGameHostOption(eGameHostOption_TrustPlayers) > 0) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_FireSpreads, settings->getBoolean(L"fire-spreads", app.GetGameHostOption(eGameHostOption_FireSpreads) > 0) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_TNT, settings->getBoolean(L"tnt", app.GetGameHostOption(eGameHostOption_TNT) > 0) ? 1 : 0);
+
app.DebugPrintf("\n*** SERVER SETTINGS ***\n");
app.DebugPrintf("ServerSettings: host-friends-only is %s\n",(app.GetGameHostOption(eGameHostOption_FriendsOfFriends)>0)?"on":"off");
app.DebugPrintf("ServerSettings: game-type is %s\n",(app.GetGameHostOption(eGameHostOption_GameType)==0)?"Survival Mode":"Creative Mode");
@@ -169,11 +617,11 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW
setAnimals(settings->getBoolean(L"spawn-animals", true));
setNpcsEnabled(settings->getBoolean(L"spawn-npcs", true));
- setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false); // settings->getBoolean(L"pvp", true);
+ setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false);
// 4J Stu - We should never have hacked clients flying when they shouldn't be like the PC version, so enable flying always
// Fix for #46612 - TU5: Code: Multiplayer: A client can be banned for flying when accidentaly being blown by dynamite
- setFlightAllowed(true); //settings->getBoolean(L"allow-flight", false);
+ setFlightAllowed(settings->getBoolean(L"allow-flight", true));
// 4J Stu - Enabling flight to stop it kicking us when we use it
#ifdef _DEBUG_MENUS_ENABLED
@@ -1707,17 +2155,23 @@ void MinecraftServer::tick()
void MinecraftServer::handleConsoleInput(const wstring& msg, ConsoleInputSource *source)
{
+ EnterCriticalSection(&m_consoleInputCS);
consoleInput.push_back(new ConsoleInput(msg, source));
+ LeaveCriticalSection(&m_consoleInputCS);
}
void MinecraftServer::handleConsoleInputs()
{
- while (consoleInput.size() > 0)
+ vector<ConsoleInput *> pendingInputs;
+ EnterCriticalSection(&m_consoleInputCS);
+ pendingInputs.swap(consoleInput);
+ LeaveCriticalSection(&m_consoleInputCS);
+
+ for (size_t i = 0; i < pendingInputs.size(); ++i)
{
- AUTO_VAR(it, consoleInput.begin());
- ConsoleInput *input = *it;
- consoleInput.erase(it);
- // commands->handleCommand(input); // 4J - removed - TODO - do we want equivalent of console commands?
+ ConsoleInput *input = pendingInputs[i];
+ ExecuteConsoleCommand(this, input->msg);
+ delete input;
}
}
@@ -1750,10 +2204,12 @@ File *MinecraftServer::getFile(const wstring& name)
void MinecraftServer::info(const wstring& string)
{
+ PrintConsoleLine(L"[INFO] ", string);
}
void MinecraftServer::warn(const wstring& string)
{
+ PrintConsoleLine(L"[WARN] ", string);
}
wstring MinecraftServer::getConsoleName()