mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-08-20 09:57:09 +00:00
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:
parent
c998346312
commit
aabacfa432
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
1642
Minecraft.Client/ServerCommands.cpp
Normal file
1642
Minecraft.Client/ServerCommands.cpp
Normal file
File diff suppressed because it is too large
Load diff
144
Minecraft.Client/ServerCommands.h
Normal file
144
Minecraft.Client/ServerCommands.h
Normal 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;
|
||||||
|
};
|
||||||
766
Minecraft.Client/ServerConsole.cpp
Normal file
766
Minecraft.Client/ServerConsole.cpp
Normal 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
|
||||||
92
Minecraft.Client/ServerConsole.h
Normal file
92
Minecraft.Client/ServerConsole.h
Normal 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
|
||||||
|
|
@ -125,3 +125,9 @@ 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();
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue