feat: headless server
This commit is contained in:
@@ -17,6 +17,7 @@ function(configure_msvc_target target)
|
||||
$<$<AND:$<NOT:$<CONFIG:Release>>,$<COMPILE_LANGUAGE:C,CXX>>:/W3>
|
||||
$<$<AND:$<CONFIG:Release>,$<COMPILE_LANGUAGE:C,CXX>>:/W0>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:/MP>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:/FS>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:/EHsc>
|
||||
$<$<AND:$<CONFIG:Release>,$<COMPILE_LANGUAGE:C,CXX>>:/GL /O2 /Oi /GT /GF>
|
||||
)
|
||||
|
||||
@@ -362,12 +362,23 @@ void CPlatformNetworkManagerStub::HostGame(int localUsersMask, bool bOnlineGame,
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
int port = WIN64_NET_DEFAULT_PORT;
|
||||
const char* bindIp = NULL;
|
||||
if (g_Win64DedicatedServer)
|
||||
{
|
||||
if (g_Win64DedicatedServerPort > 0)
|
||||
port = g_Win64DedicatedServerPort;
|
||||
if (g_Win64DedicatedServerBindIP[0] != 0)
|
||||
bindIp = g_Win64DedicatedServerBindIP;
|
||||
}
|
||||
if (!WinsockNetLayer::IsActive())
|
||||
WinsockNetLayer::HostGame(port);
|
||||
WinsockNetLayer::HostGame(port, bindIp);
|
||||
|
||||
const wchar_t* hostName = IQNet::m_player[0].m_gamertag;
|
||||
unsigned int settings = app.GetGameHostOption(eGameHostOption_All);
|
||||
WinsockNetLayer::StartAdvertising(port, hostName, settings, 0, 0, MINECRAFT_NET_VERSION);
|
||||
if (WinsockNetLayer::IsActive())
|
||||
{
|
||||
const wchar_t* hostName = IQNet::m_player[0].m_gamertag;
|
||||
unsigned int settings = app.GetGameHostOption(eGameHostOption_All);
|
||||
WinsockNetLayer::StartAdvertising(port, hostName, settings, 0, 0, MINECRAFT_NET_VERSION);
|
||||
}
|
||||
#endif
|
||||
//#endif
|
||||
}
|
||||
|
||||
@@ -1545,6 +1545,7 @@ if not exist "$(TargetDir)\savedata" mkdir "$(TargetDir)\savedata"</Command>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<ShowIncludes>false</ShowIncludes>
|
||||
<AdditionalOptions>/FS %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
@@ -1773,7 +1774,7 @@ xcopy /q /y /i /s /e $(ProjectDir)DurangoMedia\CU $(LayoutDir)Image\Loose\CU</C
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
|
||||
<StringPooling>true</StringPooling>
|
||||
<AdditionalOptions>/Ob3</AdditionalOptions>
|
||||
<AdditionalOptions>/FS /Ob3 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
@@ -48677,4 +48678,4 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU</Comman
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
#include "..\Minecraft.World\Pos.h"
|
||||
#include "..\Minecraft.World\System.h"
|
||||
#include "..\Minecraft.World\StringHelpers.h"
|
||||
#include "..\Minecraft.World\net.minecraft.world.entity.item.h"
|
||||
#include "..\Minecraft.World\net.minecraft.world.item.h"
|
||||
#include "..\Minecraft.World\net.minecraft.world.item.enchantment.h"
|
||||
#include "..\Minecraft.World\net.minecraft.world.damagesource.h"
|
||||
#include <sstream>
|
||||
#ifdef SPLIT_SAVES
|
||||
#include "..\Minecraft.World\ConsoleSaveFileSplit.h"
|
||||
#endif
|
||||
@@ -78,6 +83,438 @@ bool MinecraftServer::s_slowQueuePacketSent = false;
|
||||
|
||||
unordered_map<wstring, int> MinecraftServer::ironTimers;
|
||||
|
||||
static void PrintConsoleLine(const wchar_t *prefix, const wstring &message)
|
||||
{
|
||||
wprintf(L"%ls%ls\n", prefix, message.c_str());
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static bool TryParseIntValue(const wstring &text, int &value)
|
||||
{
|
||||
std::wistringstream stream(text);
|
||||
stream >> value;
|
||||
return !stream.fail() && stream.eof();
|
||||
}
|
||||
|
||||
static vector<wstring> SplitConsoleCommand(const wstring &command)
|
||||
{
|
||||
vector<wstring> tokens;
|
||||
std::wistringstream stream(command);
|
||||
wstring token;
|
||||
while (stream >> token)
|
||||
{
|
||||
tokens.push_back(token);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
static wstring JoinConsoleCommandTokens(const vector<wstring> &tokens, size_t startIndex)
|
||||
{
|
||||
wstring joined;
|
||||
for (size_t i = startIndex; i < tokens.size(); ++i)
|
||||
{
|
||||
if (!joined.empty()) joined += L" ";
|
||||
joined += tokens[i];
|
||||
}
|
||||
return joined;
|
||||
}
|
||||
|
||||
static shared_ptr<ServerPlayer> FindPlayerByName(PlayerList *playerList, const wstring &name)
|
||||
{
|
||||
if (playerList == NULL) return nullptr;
|
||||
|
||||
for (size_t i = 0; i < playerList->players.size(); ++i)
|
||||
{
|
||||
shared_ptr<ServerPlayer> player = playerList->players[i];
|
||||
if (player != NULL && equalsIgnoreCase(player->getName(), name))
|
||||
{
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void SetAllLevelTimes(MinecraftServer *server, int value)
|
||||
{
|
||||
for (unsigned int i = 0; i < server->levels.length; ++i)
|
||||
{
|
||||
if (server->levels[i] != NULL)
|
||||
{
|
||||
server->levels[i]->setDayTime(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ExecuteConsoleCommand(MinecraftServer *server, const wstring &rawCommand)
|
||||
{
|
||||
if (server == NULL)
|
||||
return false;
|
||||
|
||||
wstring command = trimString(rawCommand);
|
||||
if (command.empty())
|
||||
return true;
|
||||
|
||||
if (command[0] == L'/')
|
||||
{
|
||||
command = trimString(command.substr(1));
|
||||
}
|
||||
|
||||
vector<wstring> tokens = SplitConsoleCommand(command);
|
||||
if (tokens.empty())
|
||||
return true;
|
||||
|
||||
const wstring action = toLower(tokens[0]);
|
||||
PlayerList *playerList = server->getPlayers();
|
||||
|
||||
if (action == L"help" || action == L"?")
|
||||
{
|
||||
server->info(L"Commands: help, stop, list, say <message>, save-all, time <set day|night|ticks|add ticks>, weather <clear|rain|thunder> [seconds], tp <player> <target>, give <player> <itemId> [amount] [aux], enchant <player> <enchantId> [level], kill <player>");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"stop")
|
||||
{
|
||||
server->info(L"Stopping server...");
|
||||
MinecraftServer::HaltServer();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"list")
|
||||
{
|
||||
wstring playerNames = (playerList != NULL) ? playerList->getPlayerNames() : L"";
|
||||
if (playerNames.empty()) playerNames = L"(none)";
|
||||
server->info(L"Players (" + _toString((playerList != NULL) ? playerList->getPlayerCount() : 0) + L"): " + playerNames);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"say")
|
||||
{
|
||||
if (tokens.size() < 2)
|
||||
{
|
||||
server->warn(L"Usage: say <message>");
|
||||
return false;
|
||||
}
|
||||
|
||||
wstring message = L"[Server] " + JoinConsoleCommandTokens(tokens, 1);
|
||||
if (playerList != NULL)
|
||||
{
|
||||
playerList->broadcastAll(shared_ptr<ChatPacket>(new ChatPacket(message)));
|
||||
}
|
||||
server->info(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"save-all")
|
||||
{
|
||||
if (playerList != NULL)
|
||||
{
|
||||
playerList->saveAll(NULL, false);
|
||||
}
|
||||
server->info(L"World saved.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"time")
|
||||
{
|
||||
if (tokens.size() < 2)
|
||||
{
|
||||
server->warn(L"Usage: time set <day|night|ticks> | time add <ticks>");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (toLower(tokens[1]) == L"add")
|
||||
{
|
||||
if (tokens.size() < 3)
|
||||
{
|
||||
server->warn(L"Usage: time add <ticks>");
|
||||
return false;
|
||||
}
|
||||
|
||||
int delta = 0;
|
||||
if (!TryParseIntValue(tokens[2], delta))
|
||||
{
|
||||
server->warn(L"Invalid tick value: " + tokens[2]);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < server->levels.length; ++i)
|
||||
{
|
||||
if (server->levels[i] != NULL)
|
||||
{
|
||||
server->levels[i]->setDayTime(server->levels[i]->getDayTime() + delta);
|
||||
}
|
||||
}
|
||||
|
||||
server->info(L"Added " + _toString(delta) + L" ticks.");
|
||||
return true;
|
||||
}
|
||||
|
||||
wstring timeValue = toLower(tokens[1]);
|
||||
if (timeValue == L"set")
|
||||
{
|
||||
if (tokens.size() < 3)
|
||||
{
|
||||
server->warn(L"Usage: time set <day|night|ticks>");
|
||||
return false;
|
||||
}
|
||||
timeValue = toLower(tokens[2]);
|
||||
}
|
||||
|
||||
int targetTime = 0;
|
||||
if (timeValue == L"day")
|
||||
{
|
||||
targetTime = 0;
|
||||
}
|
||||
else if (timeValue == L"night")
|
||||
{
|
||||
targetTime = 12500;
|
||||
}
|
||||
else if (!TryParseIntValue(timeValue, targetTime))
|
||||
{
|
||||
server->warn(L"Invalid time value: " + timeValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
SetAllLevelTimes(server, targetTime);
|
||||
server->info(L"Time set to " + _toString(targetTime) + L".");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"weather")
|
||||
{
|
||||
if (tokens.size() < 2)
|
||||
{
|
||||
server->warn(L"Usage: weather <clear|rain|thunder> [seconds]");
|
||||
return false;
|
||||
}
|
||||
|
||||
int durationSeconds = 600;
|
||||
if (tokens.size() >= 3 && !TryParseIntValue(tokens[2], durationSeconds))
|
||||
{
|
||||
server->warn(L"Invalid duration: " + tokens[2]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (server->levels[0] == NULL)
|
||||
{
|
||||
server->warn(L"The overworld is not loaded.");
|
||||
return false;
|
||||
}
|
||||
|
||||
LevelData *levelData = server->levels[0]->getLevelData();
|
||||
int duration = durationSeconds * SharedConstants::TICKS_PER_SECOND;
|
||||
levelData->setRainTime(duration);
|
||||
levelData->setThunderTime(duration);
|
||||
|
||||
wstring weather = toLower(tokens[1]);
|
||||
if (weather == L"clear")
|
||||
{
|
||||
levelData->setRaining(false);
|
||||
levelData->setThundering(false);
|
||||
}
|
||||
else if (weather == L"rain")
|
||||
{
|
||||
levelData->setRaining(true);
|
||||
levelData->setThundering(false);
|
||||
}
|
||||
else if (weather == L"thunder")
|
||||
{
|
||||
levelData->setRaining(true);
|
||||
levelData->setThundering(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
server->warn(L"Usage: weather <clear|rain|thunder> [seconds]");
|
||||
return false;
|
||||
}
|
||||
|
||||
server->info(L"Weather set to " + weather + L".");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"tp" || action == L"teleport")
|
||||
{
|
||||
if (tokens.size() < 3)
|
||||
{
|
||||
server->warn(L"Usage: tp <player> <target>");
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<ServerPlayer> subject = FindPlayerByName(playerList, tokens[1]);
|
||||
shared_ptr<ServerPlayer> destination = FindPlayerByName(playerList, tokens[2]);
|
||||
if (subject == NULL)
|
||||
{
|
||||
server->warn(L"Unknown player: " + tokens[1]);
|
||||
return false;
|
||||
}
|
||||
if (destination == NULL)
|
||||
{
|
||||
server->warn(L"Unknown player: " + tokens[2]);
|
||||
return false;
|
||||
}
|
||||
if (subject->level->dimension->id != destination->level->dimension->id || !subject->isAlive())
|
||||
{
|
||||
server->warn(L"Teleport failed because the players are not in the same dimension or the source player is dead.");
|
||||
return false;
|
||||
}
|
||||
|
||||
subject->ride(nullptr);
|
||||
subject->connection->teleport(destination->x, destination->y, destination->z, destination->yRot, destination->xRot);
|
||||
server->info(L"Teleported " + subject->getName() + L" to " + destination->getName() + L".");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"give")
|
||||
{
|
||||
if (tokens.size() < 3)
|
||||
{
|
||||
server->warn(L"Usage: give <player> <itemId> [amount] [aux]");
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
|
||||
if (player == NULL)
|
||||
{
|
||||
server->warn(L"Unknown player: " + tokens[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
int itemId = 0;
|
||||
int amount = 1;
|
||||
int aux = 0;
|
||||
if (!TryParseIntValue(tokens[2], itemId))
|
||||
{
|
||||
server->warn(L"Invalid item id: " + tokens[2]);
|
||||
return false;
|
||||
}
|
||||
if (tokens.size() >= 4 && !TryParseIntValue(tokens[3], amount))
|
||||
{
|
||||
server->warn(L"Invalid amount: " + tokens[3]);
|
||||
return false;
|
||||
}
|
||||
if (tokens.size() >= 5 && !TryParseIntValue(tokens[4], aux))
|
||||
{
|
||||
server->warn(L"Invalid aux value: " + tokens[4]);
|
||||
return false;
|
||||
}
|
||||
if (itemId <= 0 || Item::items[itemId] == NULL)
|
||||
{
|
||||
server->warn(L"Unknown item id: " + _toString(itemId));
|
||||
return false;
|
||||
}
|
||||
if (amount <= 0)
|
||||
{
|
||||
server->warn(L"Amount must be positive.");
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<ItemInstance> itemInstance(new ItemInstance(itemId, amount, aux));
|
||||
shared_ptr<ItemEntity> drop = player->drop(itemInstance);
|
||||
if (drop != NULL)
|
||||
{
|
||||
drop->throwTime = 0;
|
||||
}
|
||||
server->info(L"Gave item " + _toString(itemId) + L" x" + _toString(amount) + L" to " + player->getName() + L".");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"enchant")
|
||||
{
|
||||
if (tokens.size() < 3)
|
||||
{
|
||||
server->warn(L"Usage: enchant <player> <enchantId> [level]");
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
|
||||
if (player == NULL)
|
||||
{
|
||||
server->warn(L"Unknown player: " + tokens[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
int enchantmentId = 0;
|
||||
int enchantmentLevel = 1;
|
||||
if (!TryParseIntValue(tokens[2], enchantmentId))
|
||||
{
|
||||
server->warn(L"Invalid enchantment id: " + tokens[2]);
|
||||
return false;
|
||||
}
|
||||
if (tokens.size() >= 4 && !TryParseIntValue(tokens[3], enchantmentLevel))
|
||||
{
|
||||
server->warn(L"Invalid enchantment level: " + tokens[3]);
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<ItemInstance> selectedItem = player->getSelectedItem();
|
||||
if (selectedItem == NULL)
|
||||
{
|
||||
server->warn(L"The player is not holding an item.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Enchantment *enchantment = Enchantment::enchantments[enchantmentId];
|
||||
if (enchantment == NULL)
|
||||
{
|
||||
server->warn(L"Unknown enchantment id: " + _toString(enchantmentId));
|
||||
return false;
|
||||
}
|
||||
if (!enchantment->canEnchant(selectedItem))
|
||||
{
|
||||
server->warn(L"That enchantment cannot be applied to the selected item.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enchantmentLevel < enchantment->getMinLevel()) enchantmentLevel = enchantment->getMinLevel();
|
||||
if (enchantmentLevel > enchantment->getMaxLevel()) enchantmentLevel = enchantment->getMaxLevel();
|
||||
|
||||
if (selectedItem->hasTag())
|
||||
{
|
||||
ListTag<CompoundTag> *enchantmentTags = selectedItem->getEnchantmentTags();
|
||||
if (enchantmentTags != NULL)
|
||||
{
|
||||
for (int i = 0; i < enchantmentTags->size(); i++)
|
||||
{
|
||||
int type = enchantmentTags->get(i)->getShort((wchar_t *)ItemInstance::TAG_ENCH_ID);
|
||||
if (Enchantment::enchantments[type] != NULL && !Enchantment::enchantments[type]->isCompatibleWith(enchantment))
|
||||
{
|
||||
server->warn(L"That enchantment conflicts with an existing enchantment on the selected item.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedItem->enchant(enchantment, enchantmentLevel);
|
||||
server->info(L"Enchanted " + player->getName() + L"'s held item with " + _toString(enchantmentId) + L" " + _toString(enchantmentLevel) + L".");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == L"kill")
|
||||
{
|
||||
if (tokens.size() < 2)
|
||||
{
|
||||
server->warn(L"Usage: kill <player>");
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
|
||||
if (player == NULL)
|
||||
{
|
||||
server->warn(L"Unknown player: " + tokens[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
player->hurt(DamageSource::outOfWorld, 3.4e38f);
|
||||
server->info(L"Killed " + player->getName() + L".");
|
||||
return true;
|
||||
}
|
||||
|
||||
server->warn(L"Unknown command: " + command);
|
||||
return false;
|
||||
}
|
||||
|
||||
MinecraftServer::MinecraftServer()
|
||||
{
|
||||
// 4J - added initialisers
|
||||
@@ -107,12 +544,14 @@ MinecraftServer::MinecraftServer()
|
||||
forceGameType = false;
|
||||
|
||||
commandDispatcher = new ServerCommandDispatcher();
|
||||
InitializeCriticalSection(&m_consoleInputCS);
|
||||
|
||||
DispenserBootstrap::bootStrap();
|
||||
}
|
||||
|
||||
MinecraftServer::~MinecraftServer()
|
||||
{
|
||||
DeleteCriticalSection(&m_consoleInputCS);
|
||||
}
|
||||
|
||||
bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DWORD initSettings, bool findSeed)
|
||||
@@ -150,6 +589,15 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW
|
||||
#endif
|
||||
settings = new Settings(new File(L"server.properties"));
|
||||
|
||||
app.SetGameHostOption(eGameHostOption_Difficulty, settings->getInt(L"difficulty", app.GetGameHostOption(eGameHostOption_Difficulty)));
|
||||
app.SetGameHostOption(eGameHostOption_GameType, settings->getInt(L"gamemode", app.GetGameHostOption(eGameHostOption_GameType)));
|
||||
app.SetGameHostOption(eGameHostOption_Structures, settings->getBoolean(L"generate-structures", app.GetGameHostOption(eGameHostOption_Structures) > 0) ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_BonusChest, settings->getBoolean(L"bonus-chest", app.GetGameHostOption(eGameHostOption_BonusChest) > 0) ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_PvP, settings->getBoolean(L"pvp", app.GetGameHostOption(eGameHostOption_PvP) > 0) ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_TrustPlayers, settings->getBoolean(L"trust-players", app.GetGameHostOption(eGameHostOption_TrustPlayers) > 0) ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_FireSpreads, settings->getBoolean(L"fire-spreads", app.GetGameHostOption(eGameHostOption_FireSpreads) > 0) ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_TNT, settings->getBoolean(L"tnt", app.GetGameHostOption(eGameHostOption_TNT) > 0) ? 1 : 0);
|
||||
|
||||
app.DebugPrintf("\n*** SERVER SETTINGS ***\n");
|
||||
app.DebugPrintf("ServerSettings: host-friends-only is %s\n",(app.GetGameHostOption(eGameHostOption_FriendsOfFriends)>0)?"on":"off");
|
||||
app.DebugPrintf("ServerSettings: game-type is %s\n",(app.GetGameHostOption(eGameHostOption_GameType)==0)?"Survival Mode":"Creative Mode");
|
||||
@@ -169,11 +617,11 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW
|
||||
|
||||
setAnimals(settings->getBoolean(L"spawn-animals", true));
|
||||
setNpcsEnabled(settings->getBoolean(L"spawn-npcs", true));
|
||||
setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false); // settings->getBoolean(L"pvp", true);
|
||||
setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false);
|
||||
|
||||
// 4J Stu - We should never have hacked clients flying when they shouldn't be like the PC version, so enable flying always
|
||||
// Fix for #46612 - TU5: Code: Multiplayer: A client can be banned for flying when accidentaly being blown by dynamite
|
||||
setFlightAllowed(true); //settings->getBoolean(L"allow-flight", false);
|
||||
setFlightAllowed(settings->getBoolean(L"allow-flight", true));
|
||||
|
||||
// 4J Stu - Enabling flight to stop it kicking us when we use it
|
||||
#ifdef _DEBUG_MENUS_ENABLED
|
||||
@@ -1707,17 +2155,23 @@ void MinecraftServer::tick()
|
||||
|
||||
void MinecraftServer::handleConsoleInput(const wstring& msg, ConsoleInputSource *source)
|
||||
{
|
||||
EnterCriticalSection(&m_consoleInputCS);
|
||||
consoleInput.push_back(new ConsoleInput(msg, source));
|
||||
LeaveCriticalSection(&m_consoleInputCS);
|
||||
}
|
||||
|
||||
void MinecraftServer::handleConsoleInputs()
|
||||
{
|
||||
while (consoleInput.size() > 0)
|
||||
vector<ConsoleInput *> pendingInputs;
|
||||
EnterCriticalSection(&m_consoleInputCS);
|
||||
pendingInputs.swap(consoleInput);
|
||||
LeaveCriticalSection(&m_consoleInputCS);
|
||||
|
||||
for (size_t i = 0; i < pendingInputs.size(); ++i)
|
||||
{
|
||||
AUTO_VAR(it, consoleInput.begin());
|
||||
ConsoleInput *input = *it;
|
||||
consoleInput.erase(it);
|
||||
// commands->handleCommand(input); // 4J - removed - TODO - do we want equivalent of console commands?
|
||||
ConsoleInput *input = pendingInputs[i];
|
||||
ExecuteConsoleCommand(this, input->msg);
|
||||
delete input;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1750,10 +2204,12 @@ File *MinecraftServer::getFile(const wstring& name)
|
||||
|
||||
void MinecraftServer::info(const wstring& string)
|
||||
{
|
||||
PrintConsoleLine(L"[INFO] ", string);
|
||||
}
|
||||
|
||||
void MinecraftServer::warn(const wstring& string)
|
||||
{
|
||||
PrintConsoleLine(L"[WARN] ", string);
|
||||
}
|
||||
|
||||
wstring MinecraftServer::getConsoleName()
|
||||
|
||||
@@ -103,6 +103,7 @@ private:
|
||||
// vector<Tickable *> tickables = new ArrayList<Tickable>(); // 4J - removed
|
||||
CommandDispatcher *commandDispatcher;
|
||||
vector<ConsoleInput *> consoleInput; // 4J - was synchronizedList - TODO - investigate
|
||||
CRITICAL_SECTION m_consoleInputCS;
|
||||
public:
|
||||
bool onlineMode;
|
||||
bool animals;
|
||||
|
||||
@@ -1,18 +1,90 @@
|
||||
#include "stdafx.h"
|
||||
#include "Settings.h"
|
||||
#include "..\Minecraft.World\File.h"
|
||||
#include "..\Minecraft.World\StringHelpers.h"
|
||||
#include <fstream>
|
||||
|
||||
static wstring ParsePropertyText(const string &text)
|
||||
{
|
||||
return trimString(convStringToWstring(text));
|
||||
}
|
||||
|
||||
static bool TryParseBoolean(const wstring &text, bool defaultValue)
|
||||
{
|
||||
wstring lowered = toLower(trimString(text));
|
||||
if (lowered == L"true" || lowered == L"1" || lowered == L"yes" || lowered == L"on")
|
||||
return true;
|
||||
if (lowered == L"false" || lowered == L"0" || lowered == L"no" || lowered == L"off")
|
||||
return false;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// 4J - TODO - serialise/deserialise from file
|
||||
Settings::Settings(File *file)
|
||||
{
|
||||
if (file != NULL)
|
||||
{
|
||||
filePath = file->getPath();
|
||||
}
|
||||
|
||||
if (filePath.empty())
|
||||
return;
|
||||
|
||||
std::ifstream stream(wstringtofilename(filePath), std::ios::in | std::ios::binary);
|
||||
if (!stream.is_open())
|
||||
return;
|
||||
|
||||
string line;
|
||||
while (std::getline(stream, line))
|
||||
{
|
||||
if (!line.empty() && line[line.size() - 1] == '\r')
|
||||
line.erase(line.size() - 1);
|
||||
|
||||
if (line.size() >= 3 &&
|
||||
(unsigned char)line[0] == 0xEF &&
|
||||
(unsigned char)line[1] == 0xBB &&
|
||||
(unsigned char)line[2] == 0xBF)
|
||||
{
|
||||
line.erase(0, 3);
|
||||
}
|
||||
|
||||
size_t commentPos = line.find_first_of("#;");
|
||||
if (commentPos != string::npos && line.find_first_not_of(" \t") == commentPos)
|
||||
continue;
|
||||
|
||||
size_t separatorPos = line.find('=');
|
||||
if (separatorPos == string::npos)
|
||||
continue;
|
||||
|
||||
wstring key = ParsePropertyText(line.substr(0, separatorPos));
|
||||
if (key.empty())
|
||||
continue;
|
||||
|
||||
wstring value = ParsePropertyText(line.substr(separatorPos + 1));
|
||||
properties[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::generateNewProperties()
|
||||
{
|
||||
saveProperties();
|
||||
}
|
||||
|
||||
void Settings::saveProperties()
|
||||
{
|
||||
if (filePath.empty())
|
||||
return;
|
||||
|
||||
std::ofstream stream(wstringtofilename(filePath), std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
if (!stream.is_open())
|
||||
return;
|
||||
|
||||
stream << "# MinecraftConsoles dedicated server properties\r\n";
|
||||
for (unordered_map<wstring, wstring>::const_iterator it = properties.begin(); it != properties.end(); ++it)
|
||||
{
|
||||
string key = string(wstringtochararray(it->first));
|
||||
string value = string(wstringtochararray(it->second));
|
||||
stream << key << "=" << value << "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
wstring Settings::getString(const wstring& key, const wstring& defaultValue)
|
||||
@@ -39,17 +111,17 @@ bool Settings::getBoolean(const wstring& key, bool defaultValue)
|
||||
{
|
||||
if(properties.find(key) == properties.end())
|
||||
{
|
||||
properties[key] = _toString<bool>(defaultValue);
|
||||
properties[key] = defaultValue ? L"true" : L"false";
|
||||
saveProperties();
|
||||
}
|
||||
MemSect(35);
|
||||
bool retval = _fromString<bool>(properties[key]);
|
||||
bool retval = TryParseBoolean(properties[key], defaultValue);
|
||||
MemSect(0);
|
||||
return retval;
|
||||
}
|
||||
|
||||
void Settings::setBooleanAndSave(const wstring& key, bool value)
|
||||
{
|
||||
properties[key] = _toString<bool>(value);
|
||||
properties[key] = value ? L"true" : L"false";
|
||||
saveProperties();
|
||||
}
|
||||
@@ -7,8 +7,8 @@ class Settings
|
||||
// public static Logger logger = Logger.getLogger("Minecraft");
|
||||
// private Properties properties = new Properties();
|
||||
private:
|
||||
unordered_map<wstring,wstring> properties; // 4J - TODO was Properties type, will need to implement something we can serialise/deserialise too
|
||||
//File *file;
|
||||
unordered_map<wstring,wstring> properties;
|
||||
wstring filePath;
|
||||
|
||||
public:
|
||||
Settings(File *file);
|
||||
@@ -18,4 +18,4 @@ public:
|
||||
int getInt(const wstring& key, int defaultValue);
|
||||
bool getBoolean(const wstring& key, bool defaultValue);
|
||||
void setBooleanAndSave(const wstring& key, bool value);
|
||||
};
|
||||
};
|
||||
@@ -51,6 +51,9 @@ bool g_Win64MultiplayerHost = false;
|
||||
bool g_Win64MultiplayerJoin = false;
|
||||
int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
|
||||
char g_Win64MultiplayerIP[256] = "127.0.0.1";
|
||||
bool g_Win64DedicatedServer = false;
|
||||
int g_Win64DedicatedServerPort = WIN64_NET_DEFAULT_PORT;
|
||||
char g_Win64DedicatedServerBindIP[256] = "";
|
||||
|
||||
bool WinsockNetLayer::Initialize()
|
||||
{
|
||||
@@ -139,7 +142,7 @@ void WinsockNetLayer::Shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
bool WinsockNetLayer::HostGame(int port)
|
||||
bool WinsockNetLayer::HostGame(int port, const char* bindIp)
|
||||
{
|
||||
if (!s_initialized && !Initialize()) return false;
|
||||
|
||||
@@ -159,15 +162,19 @@ bool WinsockNetLayer::HostGame(int port)
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
hints.ai_flags = (bindIp == NULL || bindIp[0] == 0) ? AI_PASSIVE : 0;
|
||||
|
||||
char portStr[16];
|
||||
sprintf_s(portStr, "%d", port);
|
||||
|
||||
int iResult = getaddrinfo(NULL, portStr, &hints, &result);
|
||||
const char* resolvedBindIp = (bindIp != NULL && bindIp[0] != 0) ? bindIp : NULL;
|
||||
int iResult = getaddrinfo(resolvedBindIp, portStr, &hints, &result);
|
||||
if (iResult != 0)
|
||||
{
|
||||
app.DebugPrintf("getaddrinfo failed: %d\n", iResult);
|
||||
app.DebugPrintf("getaddrinfo failed for %s:%d - %d\n",
|
||||
resolvedBindIp != NULL ? resolvedBindIp : "*",
|
||||
port,
|
||||
iResult);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -206,7 +213,9 @@ bool WinsockNetLayer::HostGame(int port)
|
||||
|
||||
s_acceptThread = CreateThread(NULL, 0, AcceptThreadProc, NULL, 0, NULL);
|
||||
|
||||
app.DebugPrintf("Win64 LAN: Hosting on port %d\n", port);
|
||||
app.DebugPrintf("Win64 LAN: Hosting on %s:%d\n",
|
||||
resolvedBindIp != NULL ? resolvedBindIp : "*",
|
||||
port);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -908,4 +917,4 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
static bool Initialize();
|
||||
static void Shutdown();
|
||||
|
||||
static bool HostGame(int port);
|
||||
static bool HostGame(int port, const char* bindIp = NULL);
|
||||
static bool JoinGame(const char* ip, int port);
|
||||
|
||||
static bool SendToSmallId(BYTE targetSmallId, const void* data, int dataSize);
|
||||
@@ -147,5 +147,8 @@ extern bool g_Win64MultiplayerHost;
|
||||
extern bool g_Win64MultiplayerJoin;
|
||||
extern int g_Win64MultiplayerPort;
|
||||
extern char g_Win64MultiplayerIP[256];
|
||||
extern bool g_Win64DedicatedServer;
|
||||
extern int g_Win64DedicatedServerPort;
|
||||
extern char g_Win64DedicatedServerBindIP[256];
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <iostream>
|
||||
#include <ShellScalingApi.h>
|
||||
#include <shellapi.h>
|
||||
#include "GameConfig\Minecraft.spa.h"
|
||||
#include "..\MinecraftServer.h"
|
||||
#include "..\LocalPlayer.h"
|
||||
@@ -34,6 +36,7 @@
|
||||
#include "Sentient\SentientManager.h"
|
||||
#include "..\..\Minecraft.World\IntCache.h"
|
||||
#include "..\Textures.h"
|
||||
#include "..\Settings.h"
|
||||
#include "Resource.h"
|
||||
#include "..\..\Minecraft.World\compression.h"
|
||||
#include "..\..\Minecraft.World\OldChunkStorage.h"
|
||||
@@ -95,6 +98,148 @@ wchar_t g_Win64UsernameW[17] = { 0 };
|
||||
static bool g_isFullscreen = false;
|
||||
static WINDOWPLACEMENT g_wpPrev = { sizeof(g_wpPrev) };
|
||||
|
||||
struct Win64LaunchOptions
|
||||
{
|
||||
int screenMode;
|
||||
bool serverMode;
|
||||
};
|
||||
|
||||
static void CopyWideArgToAnsi(LPCWSTR source, char* dest, size_t destSize)
|
||||
{
|
||||
if (destSize == 0)
|
||||
return;
|
||||
|
||||
dest[0] = 0;
|
||||
if (source == NULL)
|
||||
return;
|
||||
|
||||
WideCharToMultiByte(CP_ACP, 0, source, -1, dest, (int)destSize, NULL, NULL);
|
||||
dest[destSize - 1] = 0;
|
||||
}
|
||||
|
||||
static void ApplyScreenMode(int screenMode)
|
||||
{
|
||||
switch (screenMode)
|
||||
{
|
||||
case 1:
|
||||
g_iScreenWidth = 1280;
|
||||
g_iScreenHeight = 720;
|
||||
break;
|
||||
case 2:
|
||||
g_iScreenWidth = 640;
|
||||
g_iScreenHeight = 480;
|
||||
break;
|
||||
case 3:
|
||||
g_iScreenWidth = 720;
|
||||
g_iScreenHeight = 408;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static Win64LaunchOptions ParseLaunchOptions()
|
||||
{
|
||||
Win64LaunchOptions options = {};
|
||||
options.screenMode = 0;
|
||||
options.serverMode = false;
|
||||
|
||||
g_Win64MultiplayerJoin = false;
|
||||
g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
|
||||
g_Win64DedicatedServer = false;
|
||||
g_Win64DedicatedServerPort = WIN64_NET_DEFAULT_PORT;
|
||||
g_Win64DedicatedServerBindIP[0] = 0;
|
||||
|
||||
int argc = 0;
|
||||
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
if (argv == NULL)
|
||||
return options;
|
||||
|
||||
if (argc > 1 && lstrlenW(argv[1]) == 1)
|
||||
{
|
||||
if (argv[1][0] >= L'1' && argv[1][0] <= L'3')
|
||||
options.screenMode = argv[1][0] - L'0';
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
if (_wcsicmp(argv[i], L"-server") == 0)
|
||||
{
|
||||
options.serverMode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_Win64DedicatedServer = options.serverMode;
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
if (_wcsicmp(argv[i], L"-name") == 0 && (i + 1) < argc)
|
||||
{
|
||||
CopyWideArgToAnsi(argv[++i], g_Win64Username, sizeof(g_Win64Username));
|
||||
}
|
||||
else if (_wcsicmp(argv[i], L"-ip") == 0 && (i + 1) < argc)
|
||||
{
|
||||
char ipBuf[256];
|
||||
CopyWideArgToAnsi(argv[++i], ipBuf, sizeof(ipBuf));
|
||||
if (options.serverMode)
|
||||
{
|
||||
strncpy_s(g_Win64DedicatedServerBindIP, sizeof(g_Win64DedicatedServerBindIP), ipBuf, _TRUNCATE);
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy_s(g_Win64MultiplayerIP, sizeof(g_Win64MultiplayerIP), ipBuf, _TRUNCATE);
|
||||
g_Win64MultiplayerJoin = true;
|
||||
}
|
||||
}
|
||||
else if (_wcsicmp(argv[i], L"-port") == 0 && (i + 1) < argc)
|
||||
{
|
||||
wchar_t* endPtr = NULL;
|
||||
long port = wcstol(argv[++i], &endPtr, 10);
|
||||
if (endPtr != argv[i] && *endPtr == 0 && port > 0 && port <= 65535)
|
||||
{
|
||||
if (options.serverMode)
|
||||
g_Win64DedicatedServerPort = (int)port;
|
||||
else
|
||||
g_Win64MultiplayerPort = (int)port;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LocalFree(argv);
|
||||
return options;
|
||||
}
|
||||
|
||||
static BOOL WINAPI HeadlessServerCtrlHandler(DWORD ctrlType)
|
||||
{
|
||||
switch (ctrlType)
|
||||
{
|
||||
case CTRL_C_EVENT:
|
||||
case CTRL_BREAK_EVENT:
|
||||
case CTRL_CLOSE_EVENT:
|
||||
case CTRL_SHUTDOWN_EVENT:
|
||||
app.m_bShutdown = true;
|
||||
MinecraftServer::HaltServer();
|
||||
return TRUE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static void SetupHeadlessServerConsole()
|
||||
{
|
||||
if (AllocConsole())
|
||||
{
|
||||
FILE* stream = NULL;
|
||||
freopen_s(&stream, "CONIN$", "r", stdin);
|
||||
freopen_s(&stream, "CONOUT$", "w", stdout);
|
||||
freopen_s(&stream, "CONOUT$", "w", stderr);
|
||||
SetConsoleTitleA("Minecraft Server");
|
||||
}
|
||||
|
||||
SetConsoleCtrlHandler(HeadlessServerCtrlHandler, TRUE);
|
||||
}
|
||||
|
||||
void DefineActions(void)
|
||||
{
|
||||
// The app needs to define the actions required, and the possible mappings for these
|
||||
@@ -722,6 +867,225 @@ void CleanupDevice()
|
||||
if( g_pd3dDevice ) g_pd3dDevice->Release();
|
||||
}
|
||||
|
||||
static Minecraft* InitialiseMinecraftRuntime()
|
||||
{
|
||||
app.loadMediaArchive();
|
||||
|
||||
RenderManager.Initialise(g_pd3dDevice, g_pSwapChain);
|
||||
|
||||
app.loadStringTable();
|
||||
ui.init(g_pd3dDevice, g_pImmediateContext, g_pRenderTargetView, g_pDepthStencilView, g_iScreenWidth, g_iScreenHeight);
|
||||
|
||||
InputManager.Initialise(1, 3, MINECRAFT_ACTION_MAX, ACTION_MAX_MENU);
|
||||
KMInput.Init(g_hWnd);
|
||||
DefineActions();
|
||||
InputManager.SetJoypadMapVal(0, 0);
|
||||
InputManager.SetKeyRepeatRate(0.3f, 0.2f);
|
||||
|
||||
ProfileManager.Initialise(TITLEID_MINECRAFT,
|
||||
app.m_dwOfferID,
|
||||
PROFILE_VERSION_10,
|
||||
NUM_PROFILE_VALUES,
|
||||
NUM_PROFILE_SETTINGS,
|
||||
dwProfileSettingsA,
|
||||
app.GAME_DEFINED_PROFILE_DATA_BYTES * XUSER_MAX_COUNT,
|
||||
&app.uiGameDefinedDataChangedBitmask
|
||||
);
|
||||
ProfileManager.SetDefaultOptionsCallback(&CConsoleMinecraftApp::DefaultOptionsCallback, (LPVOID)&app);
|
||||
|
||||
g_NetworkManager.Initialise();
|
||||
|
||||
for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++)
|
||||
{
|
||||
IQNet::m_player[i].m_smallId = (BYTE)i;
|
||||
IQNet::m_player[i].m_isRemote = false;
|
||||
IQNet::m_player[i].m_isHostPlayer = (i == 0);
|
||||
swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i);
|
||||
}
|
||||
wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW);
|
||||
|
||||
WinsockNetLayer::Initialize();
|
||||
|
||||
ProfileManager.SetDebugFullOverride(true);
|
||||
|
||||
Tesselator::CreateNewThreadStorage(1024 * 1024);
|
||||
AABB::CreateNewThreadStorage();
|
||||
Vec3::CreateNewThreadStorage();
|
||||
IntCache::CreateNewThreadStorage();
|
||||
Compression::CreateNewThreadStorage();
|
||||
OldChunkStorage::CreateNewThreadStorage();
|
||||
Level::enableLightingCache();
|
||||
Tile::CreateNewThreadStorage();
|
||||
|
||||
Minecraft::main();
|
||||
Minecraft* pMinecraft = Minecraft::GetInstance();
|
||||
if (pMinecraft == NULL)
|
||||
return NULL;
|
||||
|
||||
app.InitGameSettings();
|
||||
app.InitialiseTips();
|
||||
|
||||
pMinecraft->options->set(Options::Option::MUSIC, 1.0f);
|
||||
pMinecraft->options->set(Options::Option::SOUND, 1.0f);
|
||||
|
||||
return pMinecraft;
|
||||
}
|
||||
|
||||
static int HeadlessServerConsoleThreadProc(void* lpParameter)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(lpParameter);
|
||||
|
||||
std::string line;
|
||||
while (!app.m_bShutdown)
|
||||
{
|
||||
if (!std::getline(std::cin, line))
|
||||
{
|
||||
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 != NULL)
|
||||
{
|
||||
server->handleConsoleInput(command, server);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int RunHeadlessServer()
|
||||
{
|
||||
SetupHeadlessServerConsole();
|
||||
|
||||
Settings serverSettings(new File(L"server.properties"));
|
||||
wstring configuredBindIp = serverSettings.getString(L"server-ip", L"");
|
||||
|
||||
const char* bindIp = "*";
|
||||
if (g_Win64DedicatedServerBindIP[0] != 0)
|
||||
{
|
||||
bindIp = g_Win64DedicatedServerBindIP;
|
||||
}
|
||||
else if (!configuredBindIp.empty())
|
||||
{
|
||||
bindIp = wstringtochararray(configuredBindIp);
|
||||
}
|
||||
|
||||
const int port = g_Win64DedicatedServerPort > 0 ? g_Win64DedicatedServerPort : serverSettings.getInt(L"server-port", WIN64_NET_DEFAULT_PORT);
|
||||
|
||||
printf("Starting headless server on %s:%d\n", bindIp, port);
|
||||
fflush(stdout);
|
||||
|
||||
Minecraft* pMinecraft = InitialiseMinecraftRuntime();
|
||||
if (pMinecraft == NULL)
|
||||
{
|
||||
fprintf(stderr, "Failed to initialise the Minecraft runtime.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
app.SetGameHostOption(eGameHostOption_Difficulty, serverSettings.getInt(L"difficulty", 1));
|
||||
app.SetGameHostOption(eGameHostOption_Gamertags, 1);
|
||||
app.SetGameHostOption(eGameHostOption_GameType, serverSettings.getInt(L"gamemode", 0));
|
||||
app.SetGameHostOption(eGameHostOption_LevelType, 0);
|
||||
app.SetGameHostOption(eGameHostOption_Structures, serverSettings.getBoolean(L"generate-structures", true) ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_BonusChest, serverSettings.getBoolean(L"bonus-chest", false) ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_PvP, serverSettings.getBoolean(L"pvp", true) ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_TrustPlayers, serverSettings.getBoolean(L"trust-players", true) ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_FireSpreads, serverSettings.getBoolean(L"fire-spreads", true) ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_TNT, serverSettings.getBoolean(L"tnt", true) ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_HostCanFly, 1);
|
||||
app.SetGameHostOption(eGameHostOption_HostCanChangeHunger, 1);
|
||||
app.SetGameHostOption(eGameHostOption_HostCanBeInvisible, 1);
|
||||
app.SetGameHostOption(eGameHostOption_MobGriefing, 1);
|
||||
app.SetGameHostOption(eGameHostOption_KeepInventory, 0);
|
||||
app.SetGameHostOption(eGameHostOption_DoMobSpawning, 1);
|
||||
app.SetGameHostOption(eGameHostOption_DoMobLoot, 1);
|
||||
app.SetGameHostOption(eGameHostOption_DoTileDrops, 1);
|
||||
app.SetGameHostOption(eGameHostOption_NaturalRegeneration, 1);
|
||||
app.SetGameHostOption(eGameHostOption_DoDaylightCycle, 1);
|
||||
|
||||
MinecraftServer::resetFlags();
|
||||
g_NetworkManager.HostGame(0, false, true, MINECRAFT_NET_MAX_PLAYERS, 0);
|
||||
|
||||
if (!WinsockNetLayer::IsActive())
|
||||
{
|
||||
fprintf(stderr, "Failed to bind the server socket on %s:%d.\n", bindIp, port);
|
||||
return 1;
|
||||
}
|
||||
|
||||
g_NetworkManager.FakeLocalPlayerJoined();
|
||||
|
||||
NetworkGameInitData* param = new NetworkGameInitData();
|
||||
param->seed = 0;
|
||||
param->settings = app.GetGameHostOption(eGameHostOption_All);
|
||||
|
||||
g_NetworkManager.ServerStoppedCreate(true);
|
||||
g_NetworkManager.ServerReadyCreate(true);
|
||||
|
||||
C4JThread* thread = new C4JThread(&CGameNetworkManager::ServerThreadProc, param, "Server", 256 * 1024);
|
||||
thread->SetProcessor(CPU_CORE_SERVER);
|
||||
thread->Run();
|
||||
|
||||
g_NetworkManager.ServerReadyWait();
|
||||
g_NetworkManager.ServerReadyDestroy();
|
||||
|
||||
if (MinecraftServer::serverHalted())
|
||||
{
|
||||
fprintf(stderr, "The server halted during startup.\n");
|
||||
g_NetworkManager.LeaveGame(false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
app.SetGameStarted(true);
|
||||
g_NetworkManager.DoWork();
|
||||
|
||||
printf("Server ready on %s:%d\n", bindIp, port);
|
||||
printf("Type 'help' for server commands.\n");
|
||||
fflush(stdout);
|
||||
|
||||
C4JThread* consoleThread = new C4JThread(&HeadlessServerConsoleThreadProc, NULL, "Server console", 128 * 1024);
|
||||
consoleThread->Run();
|
||||
|
||||
MSG msg = { 0 };
|
||||
while (WM_QUIT != msg.message && !app.m_bShutdown && !MinecraftServer::serverHalted())
|
||||
{
|
||||
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
continue;
|
||||
}
|
||||
|
||||
app.UpdateTime();
|
||||
ProfileManager.Tick();
|
||||
StorageManager.Tick();
|
||||
RenderManager.Tick();
|
||||
ui.tick();
|
||||
g_NetworkManager.DoWork();
|
||||
app.HandleXuiActions();
|
||||
|
||||
Sleep(10);
|
||||
}
|
||||
|
||||
printf("Stopping server...\n");
|
||||
fflush(stdout);
|
||||
|
||||
app.m_bShutdown = true;
|
||||
MinecraftServer::HaltServer();
|
||||
g_NetworkManager.LeaveGame(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
_In_opt_ HINSTANCE hPrevInstance,
|
||||
_In_ LPTSTR lpCmdLine,
|
||||
@@ -742,70 +1106,8 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
SetProcessDPIAware();
|
||||
g_iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
|
||||
g_iScreenHeight = GetSystemMetrics(SM_CYSCREEN);
|
||||
|
||||
if(lpCmdLine)
|
||||
{
|
||||
if(lpCmdLine[0] == '1')
|
||||
{
|
||||
g_iScreenWidth = 1280;
|
||||
g_iScreenHeight = 720;
|
||||
}
|
||||
else if(lpCmdLine[0] == '2')
|
||||
{
|
||||
g_iScreenWidth = 640;
|
||||
g_iScreenHeight = 480;
|
||||
}
|
||||
else if(lpCmdLine[0] == '3')
|
||||
{
|
||||
// Vita
|
||||
g_iScreenWidth = 720;
|
||||
g_iScreenHeight = 408;
|
||||
|
||||
// Vita native
|
||||
//g_iScreenWidth = 960;
|
||||
//g_iScreenHeight = 544;
|
||||
}
|
||||
|
||||
char cmdLineA[1024];
|
||||
strncpy_s(cmdLineA, sizeof(cmdLineA), lpCmdLine, _TRUNCATE);
|
||||
|
||||
char *nameArg = strstr(cmdLineA, "-name ");
|
||||
if (nameArg)
|
||||
{
|
||||
nameArg += 6;
|
||||
while (*nameArg == ' ') nameArg++;
|
||||
char nameBuf[17];
|
||||
int n = 0;
|
||||
while (nameArg[n] && nameArg[n] != ' ' && n < 16) { nameBuf[n] = nameArg[n]; n++; }
|
||||
nameBuf[n] = 0;
|
||||
strncpy_s(g_Win64Username, 17, nameBuf, _TRUNCATE);
|
||||
}
|
||||
|
||||
char *ipArg = strstr(cmdLineA, "-ip ");
|
||||
if (ipArg)
|
||||
{
|
||||
ipArg += 4;
|
||||
while (*ipArg == ' ') ipArg++;
|
||||
char ipBuf[256];
|
||||
int n = 0;
|
||||
while (ipArg[n] && ipArg[n] != ' ' && n < 255) { ipBuf[n] = ipArg[n]; n++; }
|
||||
ipBuf[n] = 0;
|
||||
strncpy_s(g_Win64MultiplayerIP, 256, ipBuf, _TRUNCATE);
|
||||
g_Win64MultiplayerJoin = true;
|
||||
}
|
||||
|
||||
char *portArg = strstr(cmdLineA, "-port ");
|
||||
if (portArg)
|
||||
{
|
||||
portArg += 6;
|
||||
while (*portArg == ' ') portArg++;
|
||||
char portBuf[16];
|
||||
int n = 0;
|
||||
while (portArg[n] && portArg[n] != ' ' && n < 15) { portBuf[n] = portArg[n]; n++; }
|
||||
portBuf[n] = 0;
|
||||
g_Win64MultiplayerPort = atoi(portBuf);
|
||||
}
|
||||
}
|
||||
Win64LaunchOptions launchOptions = ParseLaunchOptions();
|
||||
ApplyScreenMode(launchOptions.screenMode);
|
||||
|
||||
if (g_Win64Username[0] == 0)
|
||||
{
|
||||
@@ -821,7 +1123,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
MyRegisterClass(hInstance);
|
||||
|
||||
// Perform application initialization:
|
||||
if (!InitInstance (hInstance, nCmdShow))
|
||||
if (!InitInstance (hInstance, launchOptions.serverMode ? SW_HIDE : nCmdShow))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
@@ -834,6 +1136,13 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (launchOptions.serverMode)
|
||||
{
|
||||
int serverResult = RunHeadlessServer();
|
||||
CleanupDevice();
|
||||
return serverResult;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Main message loop
|
||||
MSG msg = {0};
|
||||
@@ -886,202 +1195,12 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
}
|
||||
|
||||
#endif
|
||||
app.loadMediaArchive();
|
||||
|
||||
RenderManager.Initialise(g_pd3dDevice, g_pSwapChain);
|
||||
|
||||
app.loadStringTable();
|
||||
ui.init(g_pd3dDevice,g_pImmediateContext,g_pRenderTargetView,g_pDepthStencilView,g_iScreenWidth,g_iScreenHeight);
|
||||
|
||||
////////////////
|
||||
// Initialise //
|
||||
////////////////
|
||||
|
||||
// Set the number of possible joypad layouts that the user can switch between, and the number of actions
|
||||
InputManager.Initialise(1,3,MINECRAFT_ACTION_MAX, ACTION_MAX_MENU);
|
||||
|
||||
// Initialize keyboard/mouse input
|
||||
KMInput.Init(g_hWnd);
|
||||
|
||||
// Set the default joypad action mappings for Minecraft
|
||||
DefineActions();
|
||||
InputManager.SetJoypadMapVal(0,0);
|
||||
InputManager.SetKeyRepeatRate(0.3f,0.2f);
|
||||
|
||||
// Initialise the profile manager with the game Title ID, Offer ID, a profile version number, and the number of profile values and settings
|
||||
ProfileManager.Initialise(TITLEID_MINECRAFT,
|
||||
app.m_dwOfferID,
|
||||
PROFILE_VERSION_10,
|
||||
NUM_PROFILE_VALUES,
|
||||
NUM_PROFILE_SETTINGS,
|
||||
dwProfileSettingsA,
|
||||
app.GAME_DEFINED_PROFILE_DATA_BYTES*XUSER_MAX_COUNT,
|
||||
&app.uiGameDefinedDataChangedBitmask
|
||||
);
|
||||
#if 0
|
||||
// register the awards
|
||||
ProfileManager.RegisterAward(eAward_TakingInventory, ACHIEVEMENT_01, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_GettingWood, ACHIEVEMENT_02, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_Benchmarking, ACHIEVEMENT_03, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_TimeToMine, ACHIEVEMENT_04, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_HotTopic, ACHIEVEMENT_05, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_AquireHardware, ACHIEVEMENT_06, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_TimeToFarm, ACHIEVEMENT_07, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_BakeBread, ACHIEVEMENT_08, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_TheLie, ACHIEVEMENT_09, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_GettingAnUpgrade, ACHIEVEMENT_10, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_DeliciousFish, ACHIEVEMENT_11, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_OnARail, ACHIEVEMENT_12, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_TimeToStrike, ACHIEVEMENT_13, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_MonsterHunter, ACHIEVEMENT_14, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_CowTipper, ACHIEVEMENT_15, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_WhenPigsFly, ACHIEVEMENT_16, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_LeaderOfThePack, ACHIEVEMENT_17, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_MOARTools, ACHIEVEMENT_18, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_DispenseWithThis, ACHIEVEMENT_19, eAwardType_Achievement);
|
||||
ProfileManager.RegisterAward(eAward_InToTheNether, ACHIEVEMENT_20, eAwardType_Achievement);
|
||||
|
||||
ProfileManager.RegisterAward(eAward_mine100Blocks, GAMER_PICTURE_GAMERPIC1, eAwardType_GamerPic,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_GAMERPIC1,IDS_CONFIRM_OK);
|
||||
ProfileManager.RegisterAward(eAward_kill10Creepers, GAMER_PICTURE_GAMERPIC2, eAwardType_GamerPic,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_GAMERPIC2,IDS_CONFIRM_OK);
|
||||
|
||||
ProfileManager.RegisterAward(eAward_eatPorkChop, AVATARASSETAWARD_PORKCHOP_TSHIRT, eAwardType_AvatarItem,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_AVATAR1,IDS_CONFIRM_OK);
|
||||
ProfileManager.RegisterAward(eAward_play100Days, AVATARASSETAWARD_WATCH, eAwardType_AvatarItem,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_AVATAR2,IDS_CONFIRM_OK);
|
||||
ProfileManager.RegisterAward(eAward_arrowKillCreeper, AVATARASSETAWARD_CAP, eAwardType_AvatarItem,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_AVATAR3,IDS_CONFIRM_OK);
|
||||
|
||||
ProfileManager.RegisterAward(eAward_socialPost, 0, eAwardType_Theme,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_THEME,IDS_CONFIRM_OK,THEME_NAME,THEME_FILESIZE);
|
||||
|
||||
// Rich Presence init - number of presences, number of contexts
|
||||
ProfileManager.RichPresenceInit(4,1);
|
||||
ProfileManager.RegisterRichPresenceContext(CONTEXT_GAME_STATE);
|
||||
|
||||
// initialise the storage manager with a default save display name, a Minimum save size, and a callback for displaying the saving message
|
||||
StorageManager.Init(app.GetString(IDS_DEFAULT_SAVENAME),"savegame.dat",FIFTY_ONE_MB,&CConsoleMinecraftApp::DisplaySavingMessage,(LPVOID)&app);
|
||||
// Set up the global title storage path
|
||||
StorageManager.StoreTMSPathName();
|
||||
|
||||
// set a function to be called when there's a sign in change, so we can exit a level if the primary player signs out
|
||||
ProfileManager.SetSignInChangeCallback(&CConsoleMinecraftApp::SignInChangeCallback,(LPVOID)&app);
|
||||
|
||||
// set a function to be called when the ethernet is disconnected, so we can back out if required
|
||||
ProfileManager.SetNotificationsCallback(&CConsoleMinecraftApp::NotificationsCallback,(LPVOID)&app);
|
||||
|
||||
#endif
|
||||
// Set a callback for the default player options to be set - when there is no profile data for the player
|
||||
ProfileManager.SetDefaultOptionsCallback(&CConsoleMinecraftApp::DefaultOptionsCallback,(LPVOID)&app);
|
||||
#if 0
|
||||
// Set a callback to deal with old profile versions needing updated to new versions
|
||||
ProfileManager.SetOldProfileVersionCallback(&CConsoleMinecraftApp::OldProfileVersionCallback,(LPVOID)&app);
|
||||
|
||||
// Set a callback for when there is a read error on profile data
|
||||
ProfileManager.SetProfileReadErrorCallback(&CConsoleMinecraftApp::ProfileReadErrorCallback,(LPVOID)&app);
|
||||
|
||||
#endif
|
||||
// QNet needs to be setup after profile manager, as we do not want its Notify listener to handle
|
||||
// XN_SYS_SIGNINCHANGED notifications. This does mean that we need to have a callback in the
|
||||
// ProfileManager for XN_LIVE_INVITE_ACCEPTED for QNet.
|
||||
g_NetworkManager.Initialise();
|
||||
|
||||
for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++)
|
||||
Minecraft *pMinecraft = InitialiseMinecraftRuntime();
|
||||
if (pMinecraft == NULL)
|
||||
{
|
||||
IQNet::m_player[i].m_smallId = (BYTE)i;
|
||||
IQNet::m_player[i].m_isRemote = false;
|
||||
IQNet::m_player[i].m_isHostPlayer = (i == 0);
|
||||
swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i);
|
||||
CleanupDevice();
|
||||
return 1;
|
||||
}
|
||||
extern wchar_t g_Win64UsernameW[17];
|
||||
wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW);
|
||||
|
||||
WinsockNetLayer::Initialize();
|
||||
|
||||
// 4J-PB moved further down
|
||||
//app.InitGameSettings();
|
||||
|
||||
// debug switch to trial version
|
||||
ProfileManager.SetDebugFullOverride(true);
|
||||
|
||||
#if 0
|
||||
//ProfileManager.AddDLC(2);
|
||||
StorageManager.SetDLCPackageRoot("DLCDrive");
|
||||
StorageManager.RegisterMarketplaceCountsCallback(&CConsoleMinecraftApp::MarketplaceCountsCallback,(LPVOID)&app);
|
||||
// Kinect !
|
||||
|
||||
if(XNuiGetHardwareStatus()!=0)
|
||||
{
|
||||
// If the Kinect Sensor is not physically connected, this function returns 0.
|
||||
NuiInitialize(NUI_INITIALIZE_FLAG_USES_HIGH_QUALITY_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH |
|
||||
NUI_INITIALIZE_FLAG_EXTRAPOLATE_FLOOR_PLANE | NUI_INITIALIZE_FLAG_USES_FITNESS | NUI_INITIALIZE_FLAG_NUI_GUIDE_DISABLED | NUI_INITIALIZE_FLAG_SUPPRESS_AUTOMATIC_UI,NUI_INITIALIZE_DEFAULT_HARDWARE_THREAD );
|
||||
}
|
||||
|
||||
// Sentient !
|
||||
hr = TelemetryManager->Init();
|
||||
|
||||
#endif
|
||||
// Initialise TLS for tesselator, for this main thread
|
||||
Tesselator::CreateNewThreadStorage(1024*1024);
|
||||
// Initialise TLS for AABB and Vec3 pools, for this main thread
|
||||
AABB::CreateNewThreadStorage();
|
||||
Vec3::CreateNewThreadStorage();
|
||||
IntCache::CreateNewThreadStorage();
|
||||
Compression::CreateNewThreadStorage();
|
||||
OldChunkStorage::CreateNewThreadStorage();
|
||||
Level::enableLightingCache();
|
||||
Tile::CreateNewThreadStorage();
|
||||
|
||||
Minecraft::main();
|
||||
Minecraft *pMinecraft=Minecraft::GetInstance();
|
||||
|
||||
app.InitGameSettings();
|
||||
|
||||
#if 0
|
||||
//bool bDisplayPauseMenu=false;
|
||||
|
||||
// set the default gamma level
|
||||
float fVal=50.0f*327.68f;
|
||||
RenderManager.UpdateGamma((unsigned short)fVal);
|
||||
|
||||
// load any skins
|
||||
//app.AddSkinsToMemoryTextureFiles();
|
||||
|
||||
// set the achievement text for a trial achievement, now we have the string table loaded
|
||||
ProfileManager.SetTrialTextStringTable(app.GetStringTable(),IDS_CONFIRM_OK, IDS_CONFIRM_CANCEL);
|
||||
ProfileManager.SetTrialAwardText(eAwardType_Achievement,IDS_UNLOCK_TITLE,IDS_UNLOCK_ACHIEVEMENT_TEXT);
|
||||
ProfileManager.SetTrialAwardText(eAwardType_GamerPic,IDS_UNLOCK_TITLE,IDS_UNLOCK_GAMERPIC_TEXT);
|
||||
ProfileManager.SetTrialAwardText(eAwardType_AvatarItem,IDS_UNLOCK_TITLE,IDS_UNLOCK_AVATAR_TEXT);
|
||||
ProfileManager.SetTrialAwardText(eAwardType_Theme,IDS_UNLOCK_TITLE,IDS_UNLOCK_THEME_TEXT);
|
||||
ProfileManager.SetUpsellCallback(&app.UpsellReturnedCallback,&app);
|
||||
|
||||
// Set up a debug character press sequence
|
||||
#ifndef _FINAL_BUILD
|
||||
app.SetDebugSequence("LRLRYYY");
|
||||
#endif
|
||||
|
||||
// Initialise the social networking manager.
|
||||
CSocialManager::Instance()->Initialise();
|
||||
|
||||
// Update the base scene quick selects now that the minecraft class exists
|
||||
//CXuiSceneBase::UpdateScreenSettings(0);
|
||||
#endif
|
||||
app.InitialiseTips();
|
||||
#if 0
|
||||
|
||||
DWORD initData=0;
|
||||
|
||||
#ifndef _FINAL_BUILD
|
||||
#ifndef _DEBUG
|
||||
#pragma message(__LOC__"Need to define the _FINAL_BUILD before submission")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Set the default sound levels
|
||||
pMinecraft->options->set(Options::Option::MUSIC,1.0f);
|
||||
pMinecraft->options->set(Options::Option::SOUND,1.0f);
|
||||
|
||||
app.NavigateToScene(XUSER_INDEX_ANY,eUIScene_Intro,&initData);
|
||||
#endif
|
||||
|
||||
// Set the default sound levels
|
||||
pMinecraft->options->set(Options::Option::MUSIC,1.0f);
|
||||
pMinecraft->options->set(Options::Option::SOUND,1.0f);
|
||||
|
||||
//app.TemporaryCreateGameStart();
|
||||
|
||||
@@ -1637,4 +1756,4 @@ void MemPixStuff()
|
||||
PIXAddNamedCounter(((float)allSectsTotal)/(4096.0f),"MemSect total pages");
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -1257,6 +1257,7 @@
|
||||
<UseFullPaths>false</UseFullPaths>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<AdditionalOptions>/FS %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
@@ -1355,7 +1356,7 @@
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
|
||||
<StringPooling>true</StringPooling>
|
||||
<AdditionalOptions>/Ob3</AdditionalOptions>
|
||||
<AdditionalOptions>/FS /Ob3 %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
@@ -4957,4 +4958,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
12
README.md
12
README.md
@@ -41,14 +41,22 @@ This feature is based on [LCEMP](https://github.com/LCEMP/LCEMP/)
|
||||
| Argument | Description |
|
||||
|--------------------|----------------------------------------------------------------------------------------------------------------|
|
||||
| `-name <username>` | Sets your in-game username |
|
||||
| `-ip <address>` | Manually connect to an IP if LAN advertising does not work or if the server cannot be discovered automatically |
|
||||
| `-port <port>` | Override the default port if it was changed in the source |
|
||||
| `-server` | Launches a headless server instead of the client |
|
||||
| `-ip <address>` | Client mode: manually connect to an IP. Server mode: override the bind IP from `server.properties` |
|
||||
| `-port <port>` | Client mode: override the join port. Server mode: override the listen port from `server.properties` |
|
||||
|
||||
Example:
|
||||
```
|
||||
Minecraft.Client.exe -name Steve -ip 192.168.0.25 -port 25565
|
||||
```
|
||||
|
||||
Headless server example:
|
||||
```
|
||||
Minecraft.Client.exe -server -ip 0.0.0.0 -port 25565
|
||||
```
|
||||
|
||||
The headless server also reads and writes `server.properties` in the working directory. If `-ip` / `-port` are omitted in `-server` mode, it falls back to `server-ip` / `server-port` from that file. Dedicated-server host options such as `trust-players`, `pvp`, `fire-spreads`, `tnt`, `difficulty`, `gamemode`, `spawn-animals`, and `spawn-npcs` are persisted there as well.
|
||||
|
||||
## Controls (Keyboard & Mouse)
|
||||
|
||||
- **Movement**: `W` `A` `S` `D`
|
||||
|
||||
Reference in New Issue
Block a user