feat: dedicated server command addition and enhancement for some parity with java/bedrock, console prettification(?), you can FINALLY have a proper server seed, tools for moderation.

This commit is contained in:
la 2026-03-10 13:30:01 +10:00 committed by Matthew Toro
parent c998346312
commit aabacfa432
17 changed files with 2738 additions and 29 deletions

View file

@ -5699,6 +5699,9 @@ bool CMinecraftApp::isXuidDeadmau5(PlayerUID xuid)
void CMinecraftApp::AddMemoryTextureFile(const wstring &wName,PBYTE pbData,DWORD dwBytes) void CMinecraftApp::AddMemoryTextureFile(const wstring &wName,PBYTE pbData,DWORD dwBytes)
{ {
#ifdef _WINDOWS64
extern bool g_Win64Verbose;
#endif
EnterCriticalSection(&csMemFilesLock); EnterCriticalSection(&csMemFilesLock);
// check it's not already in // check it's not already in
PMEMDATA pData=nullptr; PMEMDATA pData=nullptr;
@ -5706,6 +5709,9 @@ void CMinecraftApp::AddMemoryTextureFile(const wstring &wName,PBYTE pbData,DWORD
if(it != m_MEM_Files.end()) if(it != m_MEM_Files.end())
{ {
#ifndef _CONTENT_PACKAGE #ifndef _CONTENT_PACKAGE
#ifdef _WINDOWS64
if (g_Win64Verbose)
#endif
wprintf(L"Incrementing the memory texture file count for %ls\n", wName.c_str()); wprintf(L"Incrementing the memory texture file count for %ls\n", wName.c_str());
#endif #endif
pData = (*it).second; pData = (*it).second;

View file

@ -195,8 +195,8 @@ void IUIScene_HUD::renderPlayerHealth()
// Update health // Update health
bool blink = pMinecraft->localplayers[iPad]->invulnerableTime / 3 % 2 == 1; bool blink = pMinecraft->localplayers[iPad]->invulnerableTime / 3 % 2 == 1;
if (pMinecraft->localplayers[iPad]->invulnerableTime < 10) blink = false; if (pMinecraft->localplayers[iPad]->invulnerableTime < 10) blink = false;
int currentHealth = pMinecraft->localplayers[iPad]->getHealth(); int currentHealth = static_cast<int>(ceil(pMinecraft->localplayers[iPad]->getHealth()));
int oldHealth = pMinecraft->localplayers[iPad]->lastHealth; int oldHealth = static_cast<int>(ceil(pMinecraft->localplayers[iPad]->lastHealth));
bool bHasPoison = pMinecraft->localplayers[iPad]->hasEffect(MobEffect::poison); bool bHasPoison = pMinecraft->localplayers[iPad]->hasEffect(MobEffect::poison);
bool bHasWither = pMinecraft->localplayers[iPad]->hasEffect(MobEffect::wither); bool bHasWither = pMinecraft->localplayers[iPad]->hasEffect(MobEffect::wither);
AttributeInstance *maxHealthAttribute = pMinecraft->localplayers[iPad]->getAttribute(SharedMonsterAttributes::MAX_HEALTH); AttributeInstance *maxHealthAttribute = pMinecraft->localplayers[iPad]->getAttribute(SharedMonsterAttributes::MAX_HEALTH);

View file

@ -8,6 +8,9 @@
#include "DispenserBootstrap.h" #include "DispenserBootstrap.h"
#include "EntityTracker.h" #include "EntityTracker.h"
#include "MinecraftServer.h" #include "MinecraftServer.h"
#ifdef _WINDOWS64
#include "ServerConsole.h"
#endif
#include "Options.h" #include "Options.h"
#include "PlayerList.h" #include "PlayerList.h"
#include "ServerChunkCache.h" #include "ServerChunkCache.h"
@ -58,6 +61,9 @@
#include "..\Minecraft.World\BiomeSource.h" #include "..\Minecraft.World\BiomeSource.h"
#include "PlayerChunkMap.h" #include "PlayerChunkMap.h"
#include "Common\Telemetry\TelemetryManager.h" #include "Common\Telemetry\TelemetryManager.h"
#include "ServerCommands.h"
#ifdef _WINDOWS64
#endif
#include "PlayerConnection.h" #include "PlayerConnection.h"
#ifdef _XBOX_ONE #ifdef _XBOX_ONE
#include "Durango\Network\NetworkPlayerDurango.h" #include "Durango\Network\NetworkPlayerDurango.h"
@ -726,6 +732,19 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW
ProgressRenderer *mcprogress = Minecraft::GetInstance()->progressRenderer; ProgressRenderer *mcprogress = Minecraft::GetInstance()->progressRenderer;
mcprogress->progressStart(IDS_PROGRESS_INITIALISING_SERVER); mcprogress->progressStart(IDS_PROGRESS_INITIALISING_SERVER);
if (ShouldUseDedicatedServerProperties())
{
wstring seedStr = GetDedicatedServerString(settings, L"seed", L"");
if (!seedStr.empty())
{
int64_t parsedSeed = _wtoi64(seedStr.c_str());
if (parsedSeed != 0)
seed = parsedSeed;
}
if (seed == 0 && !findSeed)
findSeed = true;
}
if( findSeed ) if( findSeed )
{ {
#ifdef __PSVITA__ #ifdef __PSVITA__
@ -733,6 +752,10 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW
#else #else
seed = BiomeSource::findSeed(pLevelType); seed = BiomeSource::findSeed(pLevelType);
#endif #endif
if (ShouldUseDedicatedServerProperties() && settings != nullptr)
{
settings->setStringAndSave(L"seed", std::to_wstring(seed));
}
} }
setMaxBuildHeight(GetDedicatedServerInt(settings, L"max-build-height", Level::maxBuildHeight)); setMaxBuildHeight(GetDedicatedServerInt(settings, L"max-build-height", Level::maxBuildHeight));
@ -2208,10 +2231,13 @@ void MinecraftServer::handleConsoleInputs()
pendingInputs.swap(consoleInput); pendingInputs.swap(consoleInput);
LeaveCriticalSection(&m_consoleInputCS); LeaveCriticalSection(&m_consoleInputCS);
ServerCommands::initialize();
for (size_t i = 0; i < pendingInputs.size(); ++i) for (size_t i = 0; i < pendingInputs.size(); ++i)
{ {
ConsoleInput *input = pendingInputs[i]; ConsoleInput *input = pendingInputs[i];
ExecuteConsoleCommand(this, input->msg); CommandSource source(this, input->source, CommandSource::CONSOLE, L"CONSOLE");
ServerCommands::execute(this, input->msg, source);
delete input; delete input;
} }
} }
@ -2245,11 +2271,25 @@ File *MinecraftServer::getFile(const wstring& name)
void MinecraftServer::info(const wstring& string) void MinecraftServer::info(const wstring& string)
{ {
#ifdef _WINDOWS64
if (ServerConsole::getInstance())
{
ServerConsole::logInfo(string.c_str());
return;
}
#endif
PrintConsoleLine(L"[INFO] ", string); PrintConsoleLine(L"[INFO] ", string);
} }
void MinecraftServer::warn(const wstring& string) void MinecraftServer::warn(const wstring& string)
{ {
#ifdef _WINDOWS64
if (ServerConsole::getInstance())
{
ServerConsole::logWarn(string.c_str());
return;
}
#endif
PrintConsoleLine(L"[WARN] ", string); PrintConsoleLine(L"[WARN] ", string);
} }

View file

@ -142,7 +142,7 @@ private:
bool loadLevel(LevelStorageSource *storageSource, const wstring& name, int64_t levelSeed, LevelType *pLevelType, NetworkGameInitData *initData); bool loadLevel(LevelStorageSource *storageSource, const wstring& name, int64_t levelSeed, LevelType *pLevelType, NetworkGameInitData *initData);
void setProgress(const wstring& status, int progress); void setProgress(const wstring& status, int progress);
void endProgress(); void endProgress();
void saveAllChunks(); void saveAllChunks();
void saveGameRules(); void saveGameRules();
void stopServer(bool didInit); void stopServer(bool didInit);
#ifdef _LARGE_WORLDS #ifdef _LARGE_WORLDS

View file

@ -7,6 +7,7 @@
#include "ServerLevel.h" #include "ServerLevel.h"
#include "PlayerList.h" #include "PlayerList.h"
#include "MinecraftServer.h" #include "MinecraftServer.h"
#include "ServerCommands.h"
#include "..\Minecraft.World\net.minecraft.network.h" #include "..\Minecraft.World\net.minecraft.network.h"
#include "..\Minecraft.World\pos.h" #include "..\Minecraft.World\pos.h"
#include "..\Minecraft.World\net.minecraft.world.level.dimension.h" #include "..\Minecraft.World\net.minecraft.world.level.dimension.h"
@ -188,6 +189,10 @@ void PendingConnection::handleLogin(shared_ptr<LoginPacket> packet)
{ {
disconnect(DisconnectPacket::eDisconnect_Banned); disconnect(DisconnectPacket::eDisconnect_Banned);
} }
else if (ServerCommands::getBanList().isBanned(name))
{
disconnect(DisconnectPacket::eDisconnect_Banned);
}
else if (duplicateXuid) else if (duplicateXuid)
{ {
// Reject the incoming connection — a player with this UID is already // Reject the incoming connection — a player with this UID is already

View file

@ -34,6 +34,7 @@
// 4J Added // 4J Added
#include "..\Minecraft.World\net.minecraft.world.item.crafting.h" #include "..\Minecraft.World\net.minecraft.world.item.crafting.h"
#include "Options.h" #include "Options.h"
#include "ServerCommands.h"
Random PlayerConnection::random; Random PlayerConnection::random;
@ -633,10 +634,9 @@ void PlayerConnection::handleChat(shared_ptr<ChatPacket> packet)
void PlayerConnection::handleCommand(const wstring& message) void PlayerConnection::handleCommand(const wstring& message)
{ {
// 4J - TODO ServerCommands::initialize();
#if 0 CommandSource source(server, player);
server.getCommandDispatcher().performCommand(player, message); ServerCommands::execute(server, message, source);
#endif
} }
void PlayerConnection::handleAnimate(shared_ptr<AnimatePacket> packet) void PlayerConnection::handleAnimate(shared_ptr<AnimatePacket> packet)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,144 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <functional>
class MinecraftServer;
class ServerPlayer;
class ConsoleInputSource;
class PlayerList;
// Command sender abstraction - can be server console or a player
class CommandSource
{
public:
enum Type { CONSOLE, PLAYER };
CommandSource(MinecraftServer* server, ConsoleInputSource* source, Type type, const wstring& name);
CommandSource(MinecraftServer* server, shared_ptr<ServerPlayer> player);
void sendMessage(const wstring& message);
void sendSuccess(const wstring& message);
void sendError(const wstring& message);
bool isConsole() const { return m_type == CONSOLE; }
bool isPlayer() const { return m_type == PLAYER; }
bool isOp() const;
MinecraftServer* getServer() const { return m_server; }
shared_ptr<ServerPlayer> getPlayer() const { return m_player; }
wstring getName() const { return m_name; }
private:
MinecraftServer* m_server;
ConsoleInputSource* m_consoleSource;
shared_ptr<ServerPlayer> m_player;
Type m_type;
wstring m_name;
};
// Server-side op list management
class OpList
{
public:
void addOp(const wstring& name);
void removeOp(const wstring& name);
bool isOp(const wstring& name) const;
const unordered_set<wstring>& getOps() const { return m_ops; }
void save() const;
void load();
private:
unordered_set<wstring> m_ops;
};
// Server-side ban list management (persistent)
class BanList
{
public:
void ban(const wstring& name, const wstring& reason = L"");
void unban(const wstring& name);
bool isBanned(const wstring& name) const;
wstring getBanReason(const wstring& name) const;
const unordered_map<wstring, wstring>& getBans() const { return m_bans; }
void save() const;
void load();
private:
unordered_map<wstring, wstring> m_bans; // name -> reason
};
// Vanish manager
class VanishManager
{
public:
void setVanished(const wstring& name, bool vanished);
bool isVanished(const wstring& name) const;
const unordered_set<wstring>& getVanished() const { return m_vanished; }
private:
unordered_set<wstring> m_vanished;
};
// Main command registry and execution engine
class ServerCommands
{
public:
static void initialize();
// Execute a command string (with or without leading /)
static bool execute(MinecraftServer* server, const wstring& rawCommand, CommandSource& source);
// Access op and vanish managers
static OpList& getOpList() { return s_opList; }
static VanishManager& getVanishManager() { return s_vanishManager; }
static BanList& getBanList() { return s_banList; }
private:
// Utility functions
static vector<wstring> splitCommand(const wstring& command);
static wstring joinTokens(const vector<wstring>& tokens, size_t startIndex);
static bool tryParseInt(const wstring& text, int& value);
static bool tryParseDouble(const wstring& text, double& value);
static shared_ptr<ServerPlayer> findPlayer(PlayerList* playerList, const wstring& name);
static vector<shared_ptr<ServerPlayer>> resolvePlayerSelector(MinecraftServer* server, const wstring& selector, CommandSource& source);
// Command handlers
static bool cmdHelp(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdStop(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdList(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdSay(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdSaveAll(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdKill(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdGamemode(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdTeleport(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdGive(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdEnchant(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdTime(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdWeather(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdSummon(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdSetblock(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdFill(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdEffect(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdClear(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdVanish(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdOp(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdDeop(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdKick(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdBan(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdUnban(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
static bool cmdBanList(MinecraftServer* server, const vector<wstring>& tokens, CommandSource& source);
// Command handler map
typedef bool (*CommandHandler)(MinecraftServer*, const vector<wstring>&, CommandSource&);
static unordered_map<wstring, CommandHandler> s_commands;
static unordered_set<wstring> s_opOnlyCommands;
static OpList s_opList;
static BanList s_banList;
static VanishManager s_vanishManager;
static bool s_initialized;
};

View file

@ -0,0 +1,766 @@
#include "stdafx.h"
#ifdef _WINDOWS64
#include "ServerConsole.h"
#include "MinecraftServer.h"
#include "PlayerList.h"
#include "ServerPlayer.h"
#include "..\Minecraft.World\StringHelpers.h"
#include "..\Minecraft.World\EntityIO.h"
#include "..\Minecraft.World\Tile.h"
#include "..\Minecraft.World\MobEffect.h"
#include "..\Minecraft.World\LevelSettings.h"
#include <cstdarg>
#include <algorithm>
#include <sstream>
#include <cstdio>
ServerConsole* ServerConsole::s_instance = nullptr;
const char* ServerConsole::Color::Reset = "\033[0m";
const char* ServerConsole::Color::Red = "\033[31m";
const char* ServerConsole::Color::Green = "\033[32m";
const char* ServerConsole::Color::Yellow = "\033[33m";
const char* ServerConsole::Color::Blue = "\033[34m";
const char* ServerConsole::Color::Magenta = "\033[35m";
const char* ServerConsole::Color::Cyan = "\033[36m";
const char* ServerConsole::Color::White = "\033[37m";
const char* ServerConsole::Color::Gray = "\033[90m";
const char* ServerConsole::Color::BrightRed = "\033[91m";
const char* ServerConsole::Color::BrightGreen = "\033[92m";
const char* ServerConsole::Color::BrightYellow= "\033[93m";
const char* ServerConsole::Color::BrightBlue = "\033[94m";
const char* ServerConsole::Color::BrightMagenta="\033[95m";
const char* ServerConsole::Color::BrightCyan = "\033[96m";
const char* ServerConsole::Color::BrightWhite = "\033[97m";
// Known command names for completion and highlighting
static const char* s_commandNames[] = {
"help", "stop", "list", "say", "save-all",
"kill", "gamemode", "teleport", "tp", "give", "giveitem",
"enchant", "enchantitem", "time", "weather", "summon",
"setblock", "fill", "effect", "clear", "vanish", "op", "deop",
"kick", "ban", "unban", "pardon", "banlist",
nullptr
};
// Subcommand completions for specific commands
static const char* s_timeSubcmds[] = { "set", "add", "query", nullptr };
static const char* s_timeSetValues[] = { "day", "night", "noon", "midnight", nullptr };
static const char* s_weatherTypes[] = { "clear", "rain", "thunder", nullptr };
static const char* s_gamemodeNames[] = { "survival", "creative", "adventure", "0", "1", "2", "s", "c", "a", nullptr };
static const char* s_effectActions[] = { "give", "clear", nullptr };
ServerConsole::ServerConsole(MinecraftServer* server)
: m_server(server)
, m_cursorPos(0)
, m_historyIndex(-1)
, m_tabIndex(-1)
, m_running(true)
{
m_hConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
m_hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
s_instance = this;
}
ServerConsole::~ServerConsole()
{
if (s_instance == this)
s_instance = nullptr;
}
void ServerConsole::enableVirtualTerminal()
{
DWORD dwMode = 0;
if (GetConsoleMode(m_hConsoleOut, &dwMode))
{
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(m_hConsoleOut, dwMode);
}
if (GetConsoleMode(m_hConsoleIn, &dwMode))
{
// Disable line input and echo so we can handle raw key events
dwMode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
dwMode |= ENABLE_WINDOW_INPUT;
SetConsoleMode(m_hConsoleIn, dwMode);
}
}
void ServerConsole::logInfo(const char* fmt, ...)
{
ServerConsole* sc = getInstance();
if (sc) sc->clearInputLine();
char buf[2048];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
printf("%s[INFO]%s %s\n", Color::BrightCyan, Color::Reset, buf);
fflush(stdout);
if (sc) sc->redrawInputLine();
}
void ServerConsole::logWarn(const char* fmt, ...)
{
ServerConsole* sc = getInstance();
if (sc) sc->clearInputLine();
char buf[2048];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
printf("%s[WARN]%s %s%s%s\n", Color::BrightYellow, Color::Reset, Color::Yellow, buf, Color::Reset);
fflush(stdout);
if (sc) sc->redrawInputLine();
}
void ServerConsole::logError(const char* fmt, ...)
{
ServerConsole* sc = getInstance();
if (sc) sc->clearInputLine();
char buf[2048];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
printf("%s[ERROR]%s %s%s%s\n", Color::BrightRed, Color::Reset, Color::Red, buf, Color::Reset);
fflush(stdout);
if (sc) sc->redrawInputLine();
}
void ServerConsole::logSuccess(const char* fmt, ...)
{
ServerConsole* sc = getInstance();
if (sc) sc->clearInputLine();
char buf[2048];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
printf("%s[INFO]%s %s%s%s\n", Color::BrightGreen, Color::Reset, Color::Green, buf, Color::Reset);
fflush(stdout);
if (sc) sc->redrawInputLine();
}
void ServerConsole::logCommand(const char* label, const char* message)
{
ServerConsole* sc = getInstance();
if (sc) sc->clearInputLine();
printf("%s[%s]%s %s\n", Color::BrightMagenta, label, Color::Reset, message);
fflush(stdout);
if (sc) sc->redrawInputLine();
}
void ServerConsole::logInfo(const wchar_t* message)
{
ServerConsole* sc = getInstance();
if (sc) sc->clearInputLine();
printf("%s[INFO]%s %ls\n", Color::BrightCyan, Color::Reset, message);
fflush(stdout);
if (sc) sc->redrawInputLine();
}
void ServerConsole::logWarn(const wchar_t* message)
{
ServerConsole* sc = getInstance();
if (sc) sc->clearInputLine();
printf("%s[WARN]%s %s%ls%s\n", Color::BrightYellow, Color::Reset, Color::Yellow, message, Color::Reset);
fflush(stdout);
if (sc) sc->redrawInputLine();
}
void ServerConsole::logError(const wchar_t* message)
{
ServerConsole* sc = getInstance();
if (sc) sc->clearInputLine();
printf("%s[ERROR]%s %s%ls%s\n", Color::BrightRed, Color::Reset, Color::Red, message, Color::Reset);
fflush(stdout);
if (sc) sc->redrawInputLine();
}
void ServerConsole::printPrompt()
{
printf("%s> %s", Color::BrightGreen, Color::Reset);
fflush(stdout);
}
void ServerConsole::clearInputLine()
{
// Move to start of line, clear it
printf("\r\033[K");
fflush(stdout);
}
void ServerConsole::redrawInputLine()
{
printf("\r\033[K");
printPrompt();
if (!m_inputBuffer.empty())
{
std::string highlighted = highlightCommand(m_inputBuffer);
printf("%s", highlighted.c_str());
}
// Position cursor correctly
int promptLen = 2; // "> "
int targetCol = promptLen + m_cursorPos;
printf("\r\033[%dC", targetCol);
fflush(stdout);
}
std::string ServerConsole::highlightCommand(const std::string& input)
{
if (input.empty()) return input;
std::string result;
std::istringstream stream(input);
std::string token;
bool firstToken = true;
size_t pos = 0;
// Find the first word (command name)
size_t firstSpace = input.find(' ');
std::string cmdName = (firstSpace != std::string::npos) ? input.substr(0, firstSpace) : input;
std::string cmdLower = cmdName;
std::transform(cmdLower.begin(), cmdLower.end(), cmdLower.begin(), ::tolower);
// Check if it starts with /
size_t cmdStart = 0;
if (!cmdLower.empty() && cmdLower[0] == '/')
{
result += Color::Gray;
result += '/';
cmdLower = cmdLower.substr(1);
cmdName = cmdName.substr(1);
cmdStart = 1;
}
// Highlight command name
bool knownCmd = false;
for (int i = 0; s_commandNames[i] != nullptr; ++i)
{
if (cmdLower == s_commandNames[i])
{
knownCmd = true;
break;
}
}
if (knownCmd)
result += std::string(Color::BrightGreen) + cmdName + Color::Reset;
else
result += std::string(Color::BrightRed) + cmdName + Color::Reset;
// Highlight rest (arguments)
if (firstSpace != std::string::npos)
{
std::string rest = input.substr(firstSpace);
// Color numbers differently from strings
std::string argResult;
bool inNumber = false;
for (size_t i = 0; i < rest.size(); ++i)
{
char c = rest[i];
if (c == ' ')
{
if (inNumber) { argResult += Color::Reset; inNumber = false; }
argResult += c;
}
else if ((c >= '0' && c <= '9') || c == '-' || c == '.')
{
if (!inNumber) { argResult += Color::BrightYellow; inNumber = true; }
argResult += c;
}
else if (c == '~')
{
if (inNumber) { argResult += Color::Reset; inNumber = false; }
argResult += Color::BrightMagenta;
argResult += c;
argResult += Color::Reset;
}
else
{
if (inNumber) { argResult += Color::Reset; inNumber = false; }
argResult += Color::BrightWhite;
argResult += c;
argResult += Color::Reset;
}
}
if (inNumber) argResult += Color::Reset;
result += argResult;
}
return result;
}
static std::string toLowerStr(const std::string& s)
{
std::string r = s;
std::transform(r.begin(), r.end(), r.begin(), ::tolower);
return r;
}
std::vector<std::string> ServerConsole::getCompletions(const std::string& partial)
{
std::vector<std::string> results;
if (partial.empty()) return results;
// Parse the input to figure out what we're completing
std::string input = partial;
bool hasSlash = false;
if (!input.empty() && input[0] == '/')
{
hasSlash = true;
input = input.substr(1);
}
// Split into tokens
std::vector<std::string> tokens;
{
std::istringstream ss(input);
std::string tok;
while (ss >> tok) tokens.push_back(tok);
}
// If input ends with a space, we're completing the NEXT token
bool completingNext = (!partial.empty() && partial.back() == ' ');
if (completingNext) tokens.push_back("");
if (tokens.empty())
{
// Complete command names
for (int i = 0; s_commandNames[i] != nullptr; ++i)
{
std::string name = s_commandNames[i];
results.push_back(hasSlash ? ("/" + name) : name);
}
return results;
}
if (tokens.size() == 1)
{
// Complete command name
std::string prefix = toLowerStr(tokens[0]);
for (int i = 0; s_commandNames[i] != nullptr; ++i)
{
std::string name = s_commandNames[i];
if (name.find(prefix) == 0)
{
results.push_back(hasSlash ? ("/" + name) : name);
}
}
return results;
}
// Token 1+ : subcommand or argument completions
std::string cmdName = toLowerStr(tokens[0]);
std::string argPrefix = toLowerStr(tokens.back());
// Subcommand completions
const char** subCmds = nullptr;
if (tokens.size() == 2)
{
if (cmdName == "time") subCmds = s_timeSubcmds;
else if (cmdName == "weather") subCmds = s_weatherTypes;
else if (cmdName == "gamemode") subCmds = s_gamemodeNames;
else if (cmdName == "effect") subCmds = s_effectActions;
}
else if (tokens.size() == 3 && cmdName == "time" && toLowerStr(tokens[1]) == "set")
{
subCmds = s_timeSetValues;
}
if (subCmds != nullptr)
{
for (int i = 0; subCmds[i] != nullptr; ++i)
{
std::string sub = subCmds[i];
if (sub.find(argPrefix) == 0)
{
results.push_back(sub);
}
}
// Don't return yet - also try player names
}
// Player name completions for commands that take player names
bool wantsPlayerName = false;
if (cmdName == "kill" || cmdName == "tp" || cmdName == "teleport" ||
cmdName == "give" || cmdName == "giveitem" || cmdName == "enchant" || cmdName == "enchantitem" ||
cmdName == "gamemode" || cmdName == "effect" || cmdName == "clear" ||
cmdName == "vanish" || cmdName == "op" || cmdName == "deop" ||
cmdName == "kick" || cmdName == "ban")
{
// Most commands take a player name as the first or second arg
wantsPlayerName = true;
}
if (wantsPlayerName)
{
MinecraftServer* server = MinecraftServer::getInstance();
if (server != nullptr)
{
PlayerList* playerList = server->getPlayers();
if (playerList != nullptr)
{
for (size_t i = 0; i < playerList->players.size(); ++i)
{
if (playerList->players[i] != nullptr)
{
wstring wname = playerList->players[i]->getName();
// Convert to narrow string
std::string name(wname.begin(), wname.end());
std::string nameLower = toLowerStr(name);
if (nameLower.find(argPrefix) == 0)
{
results.push_back(name);
}
}
}
// Add @a, @p, @r, @s selectors
const char* selectors[] = { "@a", "@p", "@r", "@s", nullptr };
for (int i = 0; selectors[i] != nullptr; ++i)
{
std::string sel = selectors[i];
if (sel.find(argPrefix) == 0)
results.push_back(sel);
}
}
}
}
// Entity name completions for summon (use public idsSpawnableInCreative)
if (cmdName == "summon" && tokens.size() == 2)
{
for (auto& pair : EntityIO::idsSpawnableInCreative)
{
wstring wname = EntityIO::getEncodeId(pair.first);
if (!wname.empty())
{
std::string name(wname.begin(), wname.end());
std::string nameLower = toLowerStr(name);
if (nameLower.find(argPrefix) == 0)
{
results.push_back(name);
}
}
}
}
return results;
}
void ServerConsole::handleTabCompletion()
{
if (m_tabIndex == -1)
{
// Start new tab completion
m_tabPartial = m_inputBuffer;
m_tabCompletions = getCompletions(m_tabPartial);
if (m_tabCompletions.empty()) return;
m_tabIndex = 0;
}
else
{
m_tabIndex = (m_tabIndex + 1) % (int)m_tabCompletions.size();
}
if (m_tabCompletions.empty()) return;
// Figure out what portion to replace
// Find the last space in the partial to know what word we're completing
std::string completion = m_tabCompletions[m_tabIndex];
size_t lastSpace = m_tabPartial.rfind(' ');
if (lastSpace == std::string::npos)
{
// Replacing the whole input
m_inputBuffer = completion;
}
else
{
// Replacing the last word
m_inputBuffer = m_tabPartial.substr(0, lastSpace + 1) + completion;
}
m_cursorPos = (int)m_inputBuffer.size();
// If there are multiple completions, show them
if (m_tabCompletions.size() > 1)
{
clearInputLine();
printf("\r\n");
for (size_t i = 0; i < m_tabCompletions.size(); ++i)
{
if ((int)i == m_tabIndex)
printf("%s%s%s ", Color::BrightGreen, m_tabCompletions[i].c_str(), Color::Reset);
else
printf("%s ", m_tabCompletions[i].c_str());
}
printf("\r\n");
fflush(stdout);
}
redrawInputLine();
}
void ServerConsole::insertChar(char c)
{
if ((int)m_inputBuffer.size() >= MAX_INPUT_LENGTH) return;
m_inputBuffer.insert(m_inputBuffer.begin() + m_cursorPos, c);
m_cursorPos++;
m_tabIndex = -1; // Reset tab completion
redrawInputLine();
}
void ServerConsole::deleteCharBack()
{
if (m_cursorPos > 0)
{
m_inputBuffer.erase(m_cursorPos - 1, 1);
m_cursorPos--;
m_tabIndex = -1;
redrawInputLine();
}
}
void ServerConsole::deleteCharForward()
{
if (m_cursorPos < (int)m_inputBuffer.size())
{
m_inputBuffer.erase(m_cursorPos, 1);
m_tabIndex = -1;
redrawInputLine();
}
}
void ServerConsole::moveCursorLeft()
{
if (m_cursorPos > 0)
{
m_cursorPos--;
redrawInputLine();
}
}
void ServerConsole::moveCursorRight()
{
if (m_cursorPos < (int)m_inputBuffer.size())
{
m_cursorPos++;
redrawInputLine();
}
}
void ServerConsole::moveCursorHome()
{
m_cursorPos = 0;
redrawInputLine();
}
void ServerConsole::moveCursorEnd()
{
m_cursorPos = (int)m_inputBuffer.size();
redrawInputLine();
}
void ServerConsole::historyUp()
{
if (m_history.empty()) return;
if (m_historyIndex == -1)
{
m_savedInput = m_inputBuffer;
m_historyIndex = 0;
}
else if (m_historyIndex < (int)m_history.size() - 1)
{
m_historyIndex++;
}
else
{
return; // at oldest entry
}
m_inputBuffer = m_history[m_historyIndex];
m_cursorPos = (int)m_inputBuffer.size();
m_tabIndex = -1;
redrawInputLine();
}
void ServerConsole::historyDown()
{
if (m_historyIndex == -1) return;
m_historyIndex--;
if (m_historyIndex < 0)
{
m_historyIndex = -1;
m_inputBuffer = m_savedInput;
}
else
{
m_inputBuffer = m_history[m_historyIndex];
}
m_cursorPos = (int)m_inputBuffer.size();
m_tabIndex = -1;
redrawInputLine();
}
void ServerConsole::readInputLine(std::string& outLine)
{
m_inputBuffer.clear();
m_cursorPos = 0;
m_historyIndex = -1;
m_tabIndex = -1;
m_savedInput.clear();
printPrompt();
while (m_running)
{
INPUT_RECORD ir;
DWORD count = 0;
if (!ReadConsoleInputA(m_hConsoleIn, &ir, 1, &count) || count == 0)
{
Sleep(10);
continue;
}
if (ir.EventType != KEY_EVENT || !ir.Event.KeyEvent.bKeyDown)
continue;
KEY_EVENT_RECORD& key = ir.Event.KeyEvent;
WORD vk = key.wVirtualKeyCode;
char ch = key.uChar.AsciiChar;
if (vk == VK_RETURN)
{
printf("\r\n");
fflush(stdout);
outLine = m_inputBuffer;
return;
}
else if (vk == VK_TAB)
{
handleTabCompletion();
}
else if (vk == VK_BACK)
{
deleteCharBack();
}
else if (vk == VK_DELETE)
{
deleteCharForward();
}
else if (vk == VK_LEFT)
{
moveCursorLeft();
}
else if (vk == VK_RIGHT)
{
moveCursorRight();
}
else if (vk == VK_UP)
{
historyUp();
}
else if (vk == VK_DOWN)
{
historyDown();
}
else if (vk == VK_HOME)
{
moveCursorHome();
}
else if (vk == VK_END)
{
moveCursorEnd();
}
else if (vk == VK_ESCAPE)
{
m_inputBuffer.clear();
m_cursorPos = 0;
m_tabIndex = -1;
redrawInputLine();
}
else if (ch >= 32 && ch < 127)
{
insertChar(ch);
}
}
outLine.clear();
}
void ServerConsole::run()
{
enableVirtualTerminal();
printf("\n%s========================================%s\n", Color::BrightCyan, Color::Reset);
printf("%s Minecraft Legacy Console Server%s\n", Color::BrightWhite, Color::Reset);
printf("%s========================================%s\n", Color::BrightCyan, Color::Reset);
printf("Type %shelp%s for available commands.\n", Color::BrightGreen, Color::Reset);
printf("Use %sTab%s for auto-completion.\n\n", Color::BrightYellow, Color::Reset);
while (m_running && !MinecraftServer::serverHalted())
{
std::string line;
readInputLine(line);
if (!m_running || MinecraftServer::serverHalted()) break;
// Trim
size_t start = line.find_first_not_of(" \t");
size_t end = line.find_last_not_of(" \t");
if (start == std::string::npos) continue;
line = line.substr(start, end - start + 1);
if (line.empty()) continue;
// Add to history (avoid duplicates at front)
if (m_history.empty() || m_history.front() != line)
{
m_history.push_front(line);
if ((int)m_history.size() > MAX_HISTORY)
m_history.pop_back();
}
// Convert to wstring and send to server
wstring command = convStringToWstring(line);
if (m_server != nullptr)
{
m_server->handleConsoleInput(command, m_server);
}
}
}
#endif // _WINDOWS64

View file

@ -0,0 +1,92 @@
#pragma once
#ifdef _WINDOWS64
#include <string>
#include <vector>
#include <deque>
#include <functional>
#include <windows.h>
class MinecraftServer;
class ServerConsole
{
public:
static const int MAX_HISTORY = 500;
static const int MAX_INPUT_LENGTH = 512;
struct Color
{
static const char* Reset;
static const char* Red;
static const char* Green;
static const char* Yellow;
static const char* Blue;
static const char* Magenta;
static const char* Cyan;
static const char* White;
static const char* Gray;
static const char* BrightRed;
static const char* BrightGreen;
static const char* BrightYellow;
static const char* BrightBlue;
static const char* BrightMagenta;
static const char* BrightCyan;
static const char* BrightWhite;
};
ServerConsole(MinecraftServer* server);
~ServerConsole();
void enableVirtualTerminal();
void run();
static void logInfo(const char* fmt, ...);
static void logWarn(const char* fmt, ...);
static void logError(const char* fmt, ...);
static void logSuccess(const char* fmt, ...);
static void logCommand(const char* label, const char* message);
static void logInfo(const wchar_t* message);
static void logWarn(const wchar_t* message);
static void logError(const wchar_t* message);
std::vector<std::string> getCompletions(const std::string& partial);
static ServerConsole* getInstance() { return s_instance; }
private:
void readInputLine(std::string& outLine);
void redrawInputLine();
void clearInputLine();
void handleTabCompletion();
void insertChar(char c);
void deleteCharBack();
void deleteCharForward();
void moveCursorLeft();
void moveCursorRight();
void moveCursorHome();
void moveCursorEnd();
void historyUp();
void historyDown();
std::string highlightCommand(const std::string& input);
void printPrompt();
MinecraftServer* m_server;
std::string m_inputBuffer;
int m_cursorPos;
std::deque<std::string> m_history;
int m_historyIndex;
std::string m_savedInput;
std::vector<std::string> m_tabCompletions;
int m_tabIndex;
std::string m_tabPartial;
HANDLE m_hConsoleIn;
HANDLE m_hConsoleOut;
bool m_running;
static ServerConsole* s_instance;
};
#endif // _WINDOWS64

View file

@ -124,4 +124,10 @@ void Settings::setBooleanAndSave(const wstring& key, bool value)
{ {
properties[key] = value ? L"true" : L"false"; properties[key] = value ? L"true" : L"false";
saveProperties(); saveProperties();
}
void Settings::setStringAndSave(const wstring& key, const wstring& value)
{
properties[key] = value;
saveProperties();
} }

View file

@ -18,4 +18,5 @@ public:
int getInt(const wstring& key, int defaultValue); int getInt(const wstring& key, int defaultValue);
bool getBoolean(const wstring& key, bool defaultValue); bool getBoolean(const wstring& key, bool defaultValue);
void setBooleanAndSave(const wstring& key, bool value); void setBooleanAndSave(const wstring& key, bool value);
void setStringAndSave(const wstring& key, const wstring& value);
}; };

View file

@ -375,6 +375,11 @@ TexturePack *TexturePackRepository::addTexturePackFromDLC(DLCPack *dlcPack, DWOR
cacheById[dwParentID] = newPack; cacheById[dwParentID] = newPack;
#ifndef _CONTENT_PACKAGE #ifndef _CONTENT_PACKAGE
#ifdef _WINDOWS64
extern bool g_Win64Verbose;
if (g_Win64Verbose)
{
#endif
if(dlcPack->hasPurchasedFile(DLCManager::e_DLCType_TexturePack,L"")) if(dlcPack->hasPurchasedFile(DLCManager::e_DLCType_TexturePack,L""))
{ {
wprintf(L"Added new FULL DLCTexturePack: %ls - id=%d\n", dlcPack->getName().c_str(),dwParentID ); wprintf(L"Added new FULL DLCTexturePack: %ls - id=%d\n", dlcPack->getName().c_str(),dwParentID );
@ -383,6 +388,9 @@ TexturePack *TexturePackRepository::addTexturePackFromDLC(DLCPack *dlcPack, DWOR
{ {
wprintf(L"Added new TRIAL DLCTexturePack: %ls - id=%d\n", dlcPack->getName().c_str(),dwParentID ); wprintf(L"Added new TRIAL DLCTexturePack: %ls - id=%d\n", dlcPack->getName().c_str(),dwParentID );
} }
#ifdef _WINDOWS64
}
#endif
#endif #endif
} }
return newPack; return newPack;

View file

@ -65,6 +65,7 @@ char g_Win64MultiplayerIP[256] = "127.0.0.1";
bool g_Win64DedicatedServer = false; bool g_Win64DedicatedServer = false;
int g_Win64DedicatedServerPort = WIN64_NET_DEFAULT_PORT; int g_Win64DedicatedServerPort = WIN64_NET_DEFAULT_PORT;
char g_Win64DedicatedServerBindIP[256] = ""; char g_Win64DedicatedServerBindIP[256] = "";
bool g_Win64Verbose = false;
bool WinsockNetLayer::Initialize() bool WinsockNetLayer::Initialize()
{ {

View file

@ -170,5 +170,6 @@ extern char g_Win64MultiplayerIP[256];
extern bool g_Win64DedicatedServer; extern bool g_Win64DedicatedServer;
extern int g_Win64DedicatedServerPort; extern int g_Win64DedicatedServerPort;
extern char g_Win64DedicatedServerBindIP[256]; extern char g_Win64DedicatedServerBindIP[256];
extern bool g_Win64Verbose;
#endif #endif

View file

@ -30,6 +30,7 @@
#include "..\..\Minecraft.World\ThreadName.h" #include "..\..\Minecraft.World\ThreadName.h"
#include "..\..\Minecraft.Client\StatsCounter.h" #include "..\..\Minecraft.Client\StatsCounter.h"
#include "..\ConnectScreen.h" #include "..\ConnectScreen.h"
#include "..\ServerConsole.h"
//#include "Social\SocialManager.h" //#include "Social\SocialManager.h"
//#include "Leaderboards\LeaderboardManager.h" //#include "Leaderboards\LeaderboardManager.h"
//#include "XUI\XUI_Scene_Container.h" //#include "XUI\XUI_Scene_Container.h"
@ -123,6 +124,7 @@ struct Win64LaunchOptions
int screenMode; int screenMode;
bool serverMode; bool serverMode;
bool fullscreen; bool fullscreen;
bool verbose;
}; };
static void CopyWideArgToAnsi(LPCWSTR source, char* dest, size_t destSize) static void CopyWideArgToAnsi(LPCWSTR source, char* dest, size_t destSize)
@ -208,6 +210,7 @@ static Win64LaunchOptions ParseLaunchOptions()
Win64LaunchOptions options = {}; Win64LaunchOptions options = {};
options.screenMode = 0; options.screenMode = 0;
options.serverMode = false; options.serverMode = false;
options.verbose = false;
g_Win64MultiplayerJoin = false; g_Win64MultiplayerJoin = false;
g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT; g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
@ -271,6 +274,8 @@ static Win64LaunchOptions ParseLaunchOptions()
} }
else if (_wcsicmp(argv[i], L"-fullscreen") == 0) else if (_wcsicmp(argv[i], L"-fullscreen") == 0)
options.fullscreen = true; options.fullscreen = true;
else if (_wcsicmp(argv[i], L"-v") == 0 || _wcsicmp(argv[i], L"-verbose") == 0)
options.verbose = true;
} }
LocalFree(argv); LocalFree(argv);
@ -1354,30 +1359,19 @@ static int HeadlessServerConsoleThreadProc(void* lpParameter)
{ {
UNREFERENCED_PARAMETER(lpParameter); UNREFERENCED_PARAMETER(lpParameter);
std::string line; MinecraftServer* server = nullptr;
while (!app.m_bShutdown) while (!app.m_bShutdown)
{ {
if (!std::getline(std::cin, line)) server = MinecraftServer::getInstance();
{
if (std::cin.eof())
{
break;
}
std::cin.clear();
Sleep(50);
continue;
}
wstring command = trimString(convStringToWstring(line));
if (command.empty())
continue;
MinecraftServer* server = MinecraftServer::getInstance();
if (server != nullptr) if (server != nullptr)
{ break;
server->handleConsoleInput(command, server); Sleep(100);
} }
if (server != nullptr && !app.m_bShutdown)
{
ServerConsole console(server);
console.run();
} }
return 0; return 0;
@ -1564,6 +1558,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
// Load stuff from launch options, including username // Load stuff from launch options, including username
const Win64LaunchOptions launchOptions = ParseLaunchOptions(); const Win64LaunchOptions launchOptions = ParseLaunchOptions();
g_Win64Verbose = launchOptions.verbose;
ApplyScreenMode(launchOptions.screenMode); ApplyScreenMode(launchOptions.screenMode);
// Ensure uid.dat exists from startup in client mode (before any multiplayer/login path). // Ensure uid.dat exists from startup in client mode (before any multiplayer/login path).

View file

@ -397,7 +397,9 @@ set(MINECRAFT_CLIENT_SOURCES
"SelectWorldScreen.cpp" "SelectWorldScreen.cpp"
"ServerChunkCache.cpp" "ServerChunkCache.cpp"
"ServerCommandDispatcher.cpp" "ServerCommandDispatcher.cpp"
"ServerCommands.cpp"
"ServerConnection.cpp" "ServerConnection.cpp"
"ServerConsole.cpp"
"ServerLevel.cpp" "ServerLevel.cpp"
"ServerLevelListener.cpp" "ServerLevelListener.cpp"
"ServerPlayer.cpp" "ServerPlayer.cpp"