Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Minecraft.Client/ClientConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,8 @@ void ClientConnection::handleLogin(shared_ptr<LoginPacket> packet)

if( m_userIndex == ProfileManager.GetPrimaryPad() )
{
if( app.GetTutorialMode() )
// Clients joining a server should never use tutorial mode - it blocks jump and other input
if( app.GetTutorialMode() && g_NetworkManager.IsLocalGame() )
{
minecraft->gameMode = new FullTutorialMode(ProfileManager.GetPrimaryPad(), minecraft, this);
}
Expand Down
6 changes: 6 additions & 0 deletions Minecraft.Client/Common/Tutorial/Tutorial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2109,6 +2109,12 @@ bool Tutorial::isInputAllowed(int mapping)
// If the player is under water then allow all keypresses so they can jump out
if( Minecraft::GetInstance()->localplayers[m_iPad]->isUnderLiquid(Material::water) ) return true;

// If we're in a riding state but the player has dismounted, allow all input (covers race where
// ride(nullptr) hasn't fired changeTutorialState yet, or alternate dismount paths).
if( (m_CurrentState == e_Tutorial_State_Riding_Minecart || m_CurrentState == e_Tutorial_State_Riding_Boat) &&
Minecraft::GetInstance()->localplayers[m_iPad]->riding == nullptr )
return true;

bool allowed = true;
for(auto& constraint : constraints[m_CurrentState])
{
Expand Down
3 changes: 2 additions & 1 deletion Minecraft.Client/Minecraft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1021,7 +1021,8 @@ shared_ptr<MultiplayerLocalPlayer> Minecraft::createExtraLocalPlayer(int idx, co
mpLevel->addClientConnection( clientConnection );
}

if( app.GetTutorialMode() )
// Clients joining a server should never use tutorial mode - it blocks jump and other input
if( app.GetTutorialMode() && g_NetworkManager.IsLocalGame() )
{
localgameModes[idx] = new FullTutorialMode(idx, this, clientConnection);
}
Expand Down
21 changes: 21 additions & 0 deletions Minecraft.Client/MinecraftServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ static bool ExecuteConsoleCommand(MinecraftServer *server, const wstring &rawCom

if (action == L"stop")
{
server->info(L"Saving before stopping...");
if (playerList != NULL)
playerList->saveAll(NULL, false);
server->saveWorldToDisk();
server->info(L"Stopping server...");
MinecraftServer::HaltServer();
return true;
Expand Down Expand Up @@ -238,6 +242,7 @@ static bool ExecuteConsoleCommand(MinecraftServer *server, const wstring &rawCom
{
playerList->saveAll(nullptr, false);
}
server->saveWorldToDisk();
server->info(L"World saved.");
return true;
}
Expand Down Expand Up @@ -542,6 +547,16 @@ static bool ExecuteConsoleCommand(MinecraftServer *server, const wstring &rawCom
return false;
}

void MinecraftServer::saveWorldToDisk()
{
saveAllChunks();
saveGameRules();
if (levels[0] != NULL)
{
levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, false);
}
}

MinecraftServer::MinecraftServer()
{
// 4J - added initialisers
Expand Down Expand Up @@ -733,6 +748,12 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW
#else
seed = BiomeSource::findSeed(pLevelType);
#endif
// only save seed when creating a new world; when loading existing save, level.dat has the correct seed
if (seed != 0 && initData->saveData == nullptr)
{
settings->setStringAndSave(L"seed", std::to_wstring(seed));
printf("Saved generated seed to server.properties: %lld\n", (long long)seed);
}
}

setMaxBuildHeight(GetDedicatedServerInt(settings, L"max-build-height", Level::maxBuildHeight));
Expand Down
2 changes: 1 addition & 1 deletion Minecraft.Client/MinecraftServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class MinecraftServer : public ConsoleInputSource
static int64_t getCurrentTimeMillis();
int getPlayerIdleTimeout();
void setPlayerIdleTimeout(int playerIdleTimeout);

void saveWorldToDisk();
public:
void halt();
void run(int64_t seed, void *lpParameter);
Expand Down
6 changes: 6 additions & 0 deletions Minecraft.Client/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,10 @@ void Settings::setBooleanAndSave(const wstring& key, bool value)
{
properties[key] = value ? L"true" : L"false";
saveProperties();
}

void Settings::setStringAndSave(const wstring& key, const wstring& value)
{
properties[key] = value;
saveProperties();
}
1 change: 1 addition & 0 deletions Minecraft.Client/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ class Settings
int getInt(const wstring& key, int defaultValue);
bool getBoolean(const wstring& key, bool defaultValue);
void setBooleanAndSave(const wstring& key, bool value);
void setStringAndSave(const wstring& key, const wstring& value);
};
103 changes: 96 additions & 7 deletions Minecraft.Client/Windows64/Windows64_Minecraft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <shellapi.h>
#include "GameConfig\Minecraft.spa.h"
#include "..\MinecraftServer.h"
#include "..\PlayerList.h"
#include "..\LocalPlayer.h"
#include "..\..\Minecraft.World\ItemInstance.h"
#include "..\..\Minecraft.World\MapItem.h"
Expand Down Expand Up @@ -1427,12 +1428,29 @@ static int RunHeadlessServer()
app.SetGameHostOption(eGameHostOption_HostCanBeInvisible, 1);
app.SetGameHostOption(eGameHostOption_MobGriefing, 1);
app.SetGameHostOption(eGameHostOption_KeepInventory, 0);
app.SetGameHostOption(eGameHostOption_DoMobSpawning, 1);
app.SetGameHostOption(eGameHostOption_DoMobSpawning, serverSettings.getBoolean(L"spawn-monsters", true) ? 1 : 0);
app.SetGameHostOption(eGameHostOption_DoMobLoot, 1);
app.SetGameHostOption(eGameHostOption_DoTileDrops, 1);
app.SetGameHostOption(eGameHostOption_NaturalRegeneration, 1);
app.SetGameHostOption(eGameHostOption_DoDaylightCycle, 1);

// World size: 0=classic, 1=small, 2=medium, 3=large
int worldSizeSetting = serverSettings.getInt(L"world-size", 3);
if (worldSizeSetting < 0) worldSizeSetting = 0;
if (worldSizeSetting > 3) worldSizeSetting = 3;
app.SetGameHostOption(eGameHostOption_WorldSize, worldSizeSetting + 1);

// Save name — defaults to "server", configurable via level-name in server.properties
wstring levelNameW = serverSettings.getString(L"level-name", L"server");
if (levelNameW.empty()) levelNameW = L"server";
char levelNameA[256] = {};
WideCharToMultiByte(CP_ACP, 0, levelNameW.c_str(), -1, levelNameA, sizeof(levelNameA), NULL, NULL);

// Tell StorageManager where to save — creates GameHDD/{name}/saveData.ms
StorageManager.ResetSaveData();
StorageManager.SetSaveTitle(levelNameW.c_str());
StorageManager.SetSaveUniqueFilename(levelNameA);

MinecraftServer::resetFlags();
g_NetworkManager.HostGame(0, false, true, MINECRAFT_NET_MAX_PLAYERS, 0);

Expand All @@ -1445,8 +1463,66 @@ static int RunHeadlessServer()
g_NetworkManager.FakeLocalPlayerJoined();

NetworkGameInitData* param = new NetworkGameInitData();
param->seed = 0;
param->findSeed = true; // set this only and just set seed 0 (fallback to seed generation) only if there isn't an existing seed to load

wstring seedStr = serverSettings.getString(L"seed", L"");
if (!seedStr.empty())
{
__int64 configSeed = _wtoi64(seedStr.c_str());
if (configSeed != 0)
{
param->seed = configSeed;
param->findSeed = false;
printf("Using configured seed: %lld\n", (long long)configSeed);
}
}

if (param->findSeed)
{
param->seed = 0;
printf("No seed configured, generating random world.\n");
}

param->settings = app.GetGameHostOption(eGameHostOption_All);
param->levelName = levelNameW;

switch (worldSizeSetting)
{
case 0: param->xzSize = LEVEL_WIDTH_CLASSIC; param->hellScale = HELL_LEVEL_SCALE_CLASSIC; break;
case 1: param->xzSize = LEVEL_WIDTH_SMALL; param->hellScale = HELL_LEVEL_SCALE_SMALL; break;
case 2: param->xzSize = LEVEL_WIDTH_MEDIUM; param->hellScale = HELL_LEVEL_SCALE_MEDIUM; break;
case 3: param->xzSize = LEVEL_WIDTH_LARGE; param->hellScale = HELL_LEVEL_SCALE_LARGE; break;
}

// this will load an existing save for the server instead of creating a new one every launch
char savePath[MAX_PATH];
sprintf_s(savePath, "Windows64\\GameHDD\\%s\\saveData.ms", levelNameA);
HANDLE hFile = CreateFileA(savePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
LARGE_INTEGER fileSize;
GetFileSizeEx(hFile, &fileSize);
if (fileSize.QuadPart > 0 && fileSize.QuadPart < 256 * 1024 * 1024)
{
LPVOID saveBuffer = new unsigned char[(size_t)fileSize.QuadPart];
DWORD bytesRead = 0;
if (ReadFile(hFile, saveBuffer, (DWORD)fileSize.QuadPart, &bytesRead, NULL) && bytesRead == fileSize.QuadPart)
{
param->saveData = new LoadSaveDataThreadParam(saveBuffer, fileSize.QuadPart, levelNameW);
printf("Loading save '%s' (%lld bytes)\n", levelNameA, fileSize.QuadPart);
}
else
{
delete[] (unsigned char*)saveBuffer;
printf("Failed to read save file, creating new world.\n");
}
}
CloseHandle(hFile);
}
else
{
printf("No existing save found at %s, creating new world.\n", savePath);
}

g_NetworkManager.ServerStoppedCreate(true);
g_NetworkManager.ServerReadyCreate(true);
Expand Down Expand Up @@ -1499,6 +1575,20 @@ static int RunHeadlessServer()
printf("Stopping server...\n");
fflush(stdout);

// force everything to save before shutdown
MinecraftServer* shutdownServer = MinecraftServer::getInstance();
if (shutdownServer != NULL)
{
printf("Saving world...\n");
fflush(stdout);
PlayerList* pl = shutdownServer->getPlayers();
if (pl != NULL)
pl->saveAll(NULL, false);
shutdownServer->saveWorldToDisk();
printf("World saved.\n");
fflush(stdout);
}

app.m_bShutdown = true;
MinecraftServer::HaltServer();
g_NetworkManager.LeaveGame(false);
Expand Down Expand Up @@ -1566,11 +1656,10 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
const Win64LaunchOptions launchOptions = ParseLaunchOptions();
ApplyScreenMode(launchOptions.screenMode);

// Ensure uid.dat exists from startup in client mode (before any multiplayer/login path).
if (!launchOptions.serverMode)
{
Win64Xuid::ResolvePersistentXuid();
}
// Ensure uid.dat exists from startup (client and headless server).
// Clients need it for their identity; headless server needs it so any XUID-dependent
// code (e.g. fake host, profile stubs) has a valid persistent ID.
Win64Xuid::ResolvePersistentXuid();

// If no username, let's fall back
if (g_Win64Username[0] == 0)
Expand Down
6 changes: 3 additions & 3 deletions Minecraft.World/Ocelot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ bool Ocelot::mobInteract(shared_ptr<Player> player)
shared_ptr<ItemInstance> item = player->inventory->getSelected();
if (isTame())
{
if (equalsIgnoreCase(player->getUUID(), getOwnerUUID()))
// Windows64: owner may be stored as name (legacy) or xuid string; check both for compatibility
if (equalsIgnoreCase(player->getUUID(), getOwnerUUID()) || equalsIgnoreCase(player->getName(), getOwnerUUID()))
{
if (!level->isClientSide && !isFood(item))
{
Expand Down Expand Up @@ -219,13 +220,12 @@ bool Ocelot::mobInteract(shared_ptr<Player> player)

setCatType(1 + level->random->nextInt(3));
setOwnerUUID(player->getUUID());
spawnTamingParticles(true);
sitGoal->wantToSit(true);
// broadcast triggers handleEntityEvent which spawns particles
level->broadcastEntityEvent(shared_from_this(), EntityEvent::TAMING_SUCCEEDED);
}
else
{
spawnTamingParticles(false);
level->broadcastEntityEvent(shared_from_this(), EntityEvent::TAMING_FAILED);
}
}
Expand Down
6 changes: 3 additions & 3 deletions Minecraft.World/Ozelot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ bool Ozelot::interact(shared_ptr<Player> player)
shared_ptr<ItemInstance> item = player->inventory->getSelected();
if (isTame())
{
if (equalsIgnoreCase(player->getUUID(), getOwnerUUID()))
// Windows64: owner may be stored as name (legacy) or xuid string; check both for compatibility
if (equalsIgnoreCase(player->getUUID(), getOwnerUUID()) || equalsIgnoreCase(player->getName(), getOwnerUUID()))
{
if (!level->isClientSide && !isFood(item))
{
Expand Down Expand Up @@ -234,13 +235,12 @@ bool Ozelot::interact(shared_ptr<Player> player)

setCatType(1 + level->random->nextInt(3));
setOwnerUUID(player->getUUID());
spawnTamingParticles(true);
sitGoal->wantToSit(true);
// broadcast triggers handleEntityEvent which spawns particles
level->broadcastEntityEvent(shared_from_this(), EntityEvent::TAMING_SUCCEEDED);
}
else
{
spawnTamingParticles(false);
level->broadcastEntityEvent(shared_from_this(), EntityEvent::TAMING_FAILED);
}
}
Expand Down
5 changes: 5 additions & 0 deletions Minecraft.World/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,11 @@ void Player::setXuid(PlayerUID xuid)
// This should just be a string version of the xuid

setUUID( xuid.toString() );
#elif defined _WINDOWS64
// Windows64: PlayerUID is ULONGLONG - convert to hex string for wolf/ocelot ownership
wchar_t buf[24];
swprintf_s(buf, 24, L"%016llX", (unsigned long long)xuid);
setUUID(wstring(buf));
#endif
}

Expand Down
9 changes: 5 additions & 4 deletions Minecraft.World/Wolf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,8 @@ void Wolf::tame(const wstring &wsOwnerUUID, bool bDisplayTamingParticles, bool b

setOwnerUUID(wsOwnerUUID);

// We'll not show the taming particles if this is a baby wolf
spawnTamingParticles(bDisplayTamingParticles);
// Don't spawn particles here - TAMING_SUCCEEDED/FAILED broadcast triggers handleEntityEvent which spawns them.
// That avoids double-spawning. Baby wolf taming (bDisplayTamingParticles=false) has no broadcast, so no particles - correct.
}

bool Wolf::mobInteract(shared_ptr<Player> player)
Expand Down Expand Up @@ -381,7 +381,8 @@ bool Wolf::mobInteract(shared_ptr<Player> player)
}
}
}
if (equalsIgnoreCase(player->getUUID(), getOwnerUUID()))
// Windows64: owner may be stored as name (legacy) or xuid string; check both for compatibility
if (equalsIgnoreCase(player->getUUID(), getOwnerUUID()) || equalsIgnoreCase(player->getName(), getOwnerUUID()))
{
if (!level->isClientSide && !isFood(item))
{
Expand Down Expand Up @@ -421,7 +422,7 @@ bool Wolf::mobInteract(shared_ptr<Player> player)
}
else
{
spawnTamingParticles(false);
// broadcastEntityEvent triggers handleEntityEvent which spawns smoke
level->broadcastEntityEvent(shared_from_this(), EntityEvent::TAMING_FAILED);
}
}
Expand Down
Loading