Files
mcpe/source/client/app/Minecraft.cpp

1235 lines
26 KiB
C++

/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#include "client/app/Minecraft.hpp"
#include "client/gui/screens/PauseScreen.hpp"
#include "client/gui/screens/StartMenuScreen.hpp"
#include "client/gui/screens/RenameMPLevelScreen.hpp"
#include "client/gui/screens/SavingWorldScreen.hpp"
#include "client/gui/screens/DeathScreen.hpp"
#include "network/ServerSideNetworkHandler.hpp"
#include "client/network/ClientSideNetworkHandler.hpp"
#include "world/gamemode/SurvivalMode.hpp"
#include "world/gamemode/CreativeMode.hpp"
#include "client/player/input/ControllerTurnInput.hpp"
#include "client/player/input/MouseTurnInput.hpp"
#include "client/player/input/KeyboardInput.hpp"
#include "client/player/input/IBuildInput.hpp"
#include "client/player/input/CustomInputHolder.hpp"
#include "client/player/input/TouchInputHolder.hpp"
#include "client/player/input/Multitouch.hpp"
#include "world/tile/SandTile.hpp"
#include "client/renderer/GrassColor.hpp"
#include "client/renderer/FoliageColor.hpp"
// custom:
#include "client/renderer/PatchManager.hpp"
int Minecraft::width = C_DEFAULT_SCREEN_WIDTH;
int Minecraft::height = C_DEFAULT_SCREEN_HEIGHT;
float Minecraft::guiScaleMultiplier = 1.0f;
bool Minecraft::useAmbientOcclusion = false;
int Minecraft::customDebugId = 0;
//@HUH: For the demo, this is defined as TRUE.
//@HUH: deadmau5 had camera cheats? That's interesting.
const bool Minecraft::DEADMAU5_CAMERA_CHEATS = true;
const char* Minecraft::progressMessages[] =
{
"Locating server",
"Building terrain",
"Preparing",
"Saving chunks",
};
Minecraft::Minecraft() :
m_gui(this)
{
m_options = nullptr;
field_18 = false;
field_288 = false;
m_pLevelRenderer = nullptr;
m_pGameRenderer = nullptr;
m_pParticleEngine = nullptr;
m_pSoundEngine = nullptr;
m_pGameMode = nullptr;
m_pTextures = nullptr;
m_pFont = nullptr;
m_pRakNetInstance = nullptr;
m_pNetEventCallback = nullptr;
field_2B0 = 0;
m_pUser = nullptr;
m_pLevel = nullptr;
m_pLocalPlayer = nullptr;
m_pMobPersp = nullptr; // why is there a duplicate?
field_D0C = 0;
m_pPrepThread = nullptr;
m_pScreen = nullptr;
field_D18 = 10;
m_pInputHolder = nullptr;
m_bGrabbedMouse = true;
m_progressPercent = 0;
m_bPreparingLevel = false;
m_pLevelStorageSource = nullptr; // TODO
field_D9C = 0;
field_DA0 = 0;
field_DA4 = 0;
field_DA8 = 0;
field_DAC = 0;
m_bUsingScreen = false;
m_bHasQueuedScreen = false;
m_pQueuedScreen = nullptr;
m_licenseID = -2;
m_fLastUpdated = 0;
m_fDeltaTime = 0;
m_Logger = new Logger();
}
int Minecraft::getLicenseId()
{
if (m_licenseID < 0)
m_licenseID = m_pPlatform->checkLicense();
return m_licenseID;
}
void Minecraft::releaseMouse()
{
if (!m_bGrabbedMouse)
return;
if (m_pLocalPlayer)
m_pLocalPlayer->m_pMoveInput->releaseAllKeys();
m_bGrabbedMouse = false;
m_mouseHandler.release();
// Note, normally the platform stuff would be located within
// the mouse handler, but we don't have access to the platform
// from there!
platform()->setMouseGrabbed(false);
}
void Minecraft::grabMouse()
{
if (m_bGrabbedMouse)
return;
m_bGrabbedMouse = true;
m_mouseHandler.grab();
setScreen(nullptr);
platform()->setMouseGrabbed(!isTouchscreen());
}
void Minecraft::setScreen(Screen* pScreen)
{
#ifndef ORIGINAL_CODE
if (pScreen == nullptr && !isLevelGenerated())
{
return;
}
#endif
if (m_bUsingScreen)
{
m_bHasQueuedScreen = true;
m_pQueuedScreen = pScreen;
return;
}
if (pScreen && pScreen->isErrorScreen())
{
// not in original
delete pScreen;
return;
}
if (m_pScreen)
{
m_pScreen->removed();
delete m_pScreen;
}
m_pScreen = pScreen;
if (pScreen)
{
releaseMouse();
pScreen->init(this, int(width * Gui::InvGuiScale), int(height * Gui::InvGuiScale));
}
else
{
grabMouse();
}
}
void Minecraft::onGraphicsReset()
{
m_pTextures->clear();
m_pFont->onGraphicsReset();
if (m_pLevelRenderer)
m_pLevelRenderer->onGraphicsReset();
if (m_pGameRenderer)
m_pGameRenderer->onGraphicsReset();
EntityRenderDispatcher::getInstance()->onGraphicsReset();
}
void Minecraft::saveOptions()
{
if (platform()->hasFileSystemAccess())
getOptions()->save();
}
bool Minecraft::isLevelGenerated()
{
if (m_pLevel)
return !m_bPreparingLevel;
return false;
}
bool Minecraft::isOnline()
{
return m_pNetEventCallback != nullptr;
}
bool Minecraft::isOnlineClient()
{
if (!m_pLevel)
return false;
return m_pLevel->m_bIsMultiplayer;
}
bool Minecraft::isTouchscreen()
{
return m_bIsTouchscreen;
}
bool Minecraft::useSplitControls()
{
return !m_bIsTouchscreen || m_options->m_bSplitControls;
}
void Minecraft::setGuiScaleMultiplier(float f)
{
guiScaleMultiplier = f;
}
void Minecraft::handleMouseDown(int type, bool b)
{
if (!m_pGameMode->field_8 && (type != 1 || this->field_DA4 <= 0))
{
if (b && type == 1 && m_hitResult.m_hitType == HitResult::AABB && !m_hitResult.m_bUnk24)
{
m_pGameMode->continueDestroyBlock(m_hitResult.m_tileX, m_hitResult.m_tileY, m_hitResult.m_tileZ, m_hitResult.m_hitSide);
m_pParticleEngine->crack(m_hitResult.m_tileX, m_hitResult.m_tileY, m_hitResult.m_tileZ, m_hitResult.m_hitSide);
}
else
{
m_pGameMode->stopDestroyBlock();
}
}
}
void Minecraft::handleBuildAction(BuildActionIntention* pAction)
{
if (pAction->isRemove())
{
if (field_DA4 > 0)
return;
m_pLocalPlayer->swing();
}
bool bInteract = true;
if (!m_hitResult.isHit())
{
if (pAction->isRemove() && !m_pGameMode->isCreativeType())
field_DA4 = 10;
}
else if (m_hitResult.m_hitType == HitResult::ENTITY)
{
if (pAction->isAttack())
{
m_pGameMode->attack(m_pLocalPlayer, m_hitResult.m_pEnt);
}
else if (pAction->isInteract())
{
if (m_hitResult.m_pEnt->interactPreventDefault())
bInteract = false;
m_pGameMode->interact(m_pLocalPlayer, m_hitResult.m_pEnt);
}
}
else if (m_hitResult.m_hitType == HitResult::AABB)
{
Tile* pTile = Tile::tiles[m_pLevel->getTile(m_hitResult.m_tileX, m_hitResult.m_tileY, m_hitResult.m_tileZ)];
if (pAction->isRemove())
{
if (!pTile)
return;
// @BUG: This is only done on the client side.
m_pLevel->extinguishFire(m_hitResult.m_tileX, m_hitResult.m_tileY, m_hitResult.m_tileZ, m_hitResult.m_hitSide);
if (pTile != Tile::unbreakable || (m_pLocalPlayer->field_B94 > 99 && m_hitResult.m_bUnk24 != 1))
{
m_pGameMode->startDestroyBlock(m_hitResult.m_tileX, m_hitResult.m_tileY, m_hitResult.m_tileZ, m_hitResult.m_hitSide);
}
}
else
{
ItemInstance* pItem = getSelectedItem();
if (m_pGameMode->useItemOn(
m_pLocalPlayer,
m_pLevel,
pItem->m_itemID <= 0 ? nullptr : pItem,
m_hitResult.m_tileX,
m_hitResult.m_tileY,
m_hitResult.m_tileZ,
m_hitResult.m_hitSide))
{
bInteract = false;
m_pLocalPlayer->swing();
if (isOnline())
{
if (pItem->m_itemID > C_MAX_TILES || pItem->m_itemID < 0)
return;
int dx = m_hitResult.m_tileX, dz = m_hitResult.m_tileZ;
uint8_t dy = uint8_t(m_hitResult.m_tileY);
uint8_t hitSide = m_hitResult.m_hitSide;
if (m_pLevel->getTile(m_hitResult.m_tileX, m_hitResult.m_tileY, m_hitResult.m_tileZ) != Tile::topSnow->m_ID)
{
switch (m_hitResult.m_hitSide)
{
case HitResult::NOHIT: break;
case HitResult::MINY: dy--; break;
case HitResult::MAXY: dy++; break;
case HitResult::MINZ: dz--; break;
case HitResult::MAXZ: dz++; break;
case HitResult::MINX: dx--; break;
case HitResult::MAXX: dx++; break;
}
}
else
{
hitSide = HitResult::MINY;
}
m_pRakNetInstance->send(new PlaceBlockPacket(m_pLocalPlayer->m_EntityID, dx, dy, dz, uint8_t(pItem->m_itemID), hitSide));
}
}
}
}
if (bInteract && pAction->isInteract())
{
ItemInstance* pItem = getSelectedItem();
if (pItem)
{
if (m_pGameMode->useItem(m_pLocalPlayer, m_pLevel, pItem))
m_pGameRenderer->m_pItemInHandRenderer->itemUsed();
}
}
}
void Minecraft::handleMouseClick(int type)
{
if (!isTouchscreen())
{
if (type == 1)
{
BuildActionIntention bai(INTENT_HELD);
handleBuildAction(&bai);
}
if (type == 2)
{
BuildActionIntention bai(INTENT_CLICKED);
handleBuildAction(&bai);
}
}
}
void Minecraft::tickInput()
{
if (m_pScreen)
{
if (!m_pScreen->field_10)
{
m_bUsingScreen = true;
m_pScreen->updateEvents();
m_bUsingScreen = false;
if (m_bHasQueuedScreen)
{
setScreen(m_pQueuedScreen);
m_pQueuedScreen = nullptr;
m_bHasQueuedScreen = false;
}
return;
}
}
if (!m_pLocalPlayer)
return;
bool bIsInGUI = m_gui.isInside(Mouse::getX(), Mouse::getY());
while (Mouse::next())
{
if (getTimeMs() - field_2B4 > 200)
continue;
if (Mouse::isButtonDown(BUTTON_LEFT))
m_gui.handleClick(1, Mouse::getX(), Mouse::getY());
if (!bIsInGUI && getOptions()->field_19)
{
if (Mouse::getEventButton() == BUTTON_LEFT && Mouse::getEventButtonState())
{
handleMouseClick(1);
field_DAC = field_DA8;
}
if (Mouse::getEventButton() == BUTTON_RIGHT && Mouse::getEventButtonState())
{
handleMouseClick(2);
field_DAC = field_DA8;
}
#ifdef ENH_ALLOW_SCROLL_WHEEL
if (Mouse::getEventButton() == BUTTON_SCROLLWHEEL)
{
int slot = m_pLocalPlayer->m_pInventory->m_SelectedHotbarSlot;
int maxItems = m_gui.getNumSlots() - 1;
if (isTouchscreen())
maxItems--;
if (Mouse::getEventButtonState() == 0) // @NOTE: Scroll up
{
if (slot-- == 0)
{
slot = maxItems;
}
}
else
{
if (slot++ == maxItems) // @NOTE: Scroll down
{
slot = 0;
}
}
m_pLocalPlayer->m_pInventory->selectSlot(slot);
}
#endif
}
}
while (Keyboard::next())
{
int keyCode = Keyboard::getEventKey();
bool bPressed = Keyboard::getEventKeyState() == 1;
m_pLocalPlayer->m_pMoveInput->setKey(keyCode, bPressed);
if (bPressed)
{
m_gui.handleKeyPressed(keyCode);
for (int i = 0; i < m_gui.getNumSlots(); i++)
{
if (getOptions()->isKey(eKeyMappingIndex(KM_SLOT_1 + i), keyCode))
m_pLocalPlayer->m_pInventory->selectSlot(i);
}
if (getOptions()->isKey(KM_TOGGLE3RD, keyCode))
{
bool thirdPerson = getOptions()->m_bThirdPerson;
if (thirdPerson && !getOptions()->field_241)
getOptions()->field_241 = 1;
else
{
getOptions()->m_bThirdPerson = !thirdPerson;
getOptions()->field_241 = 0;
}
}
else if (getOptions()->isKey(KM_MENU_CANCEL, keyCode))
{
pauseGame();
}
else if (getOptions()->isKey(KM_DROP, keyCode))
{
int itemID = m_pLocalPlayer->m_pInventory->getSelectedItemId();
if (itemID > 0)
{
ItemInstance inst(itemID, 1, 0);
m_pLocalPlayer->drop(&inst);
}
}
else if (getOptions()->isKey(KM_TOGGLEGUI, keyCode))
{
getOptions()->m_bDontRenderGui = !getOptions()->m_bDontRenderGui;
}
else if (getOptions()->isKey(KM_TOGGLEDEBUG, keyCode))
{
getOptions()->m_bDebugText = !getOptions()->m_bDebugText;
}
#ifdef ENH_ALLOW_AO
else if (getOptions()->isKey(KM_TOGGLEAO, keyCode))
{
// Toggle ambient occlusion.
getOptions()->m_bAmbientOcclusion = !getOptions()->m_bAmbientOcclusion;
Minecraft::useAmbientOcclusion = getOptions()->m_bAmbientOcclusion;
m_pLevelRenderer->allChanged();
}
#endif
}
if (getOptions()->field_19)
continue;
if (getTimeMs() - field_2B4 <= 200)
{
if (getOptions()->getKey(KM_DESTROY) == keyCode && bPressed)
{
BuildActionIntention intention(INTENT_HELD);
handleBuildAction(&intention);
}
if (getOptions()->getKey(KM_PLACE) == keyCode && bPressed)
{
BuildActionIntention intention(INTENT_CLICKED);
handleBuildAction(&intention);
}
}
}
BuildActionIntention bai;
bool b = m_pInputHolder->getBuildInput()->tickBuild(m_pLocalPlayer, &bai);
if (b && !bai.isRemoveContinue())
handleBuildAction(&bai);
bool flag =
// If we are mouse operated, the LMB is held down and it's not in the GUI
((m_options->field_19 && Mouse::isButtonDown(BUTTON_LEFT) && !bIsInGUI) ||
// We are instead keyboard operated, so check for the KM_DESTROY key being held down
(!m_options->field_19 && Keyboard::isKeyDown(m_options->m_keyMappings[KM_DESTROY].value)) ||
// The build action intention is a remove one
(b && bai.isRemove()));
if (flag && !m_pScreen && (field_DA8 - field_DAC) >= (m_timer.m_ticksPerSecond * 0.25f))
{
bai = BuildActionIntention(INTENT_HELD);
handleBuildAction(&bai); // handleMouseClick(BUTTON_LEFT)
field_DAC = field_DA8;
}
handleMouseDown(BUTTON_LEFT, flag);
field_2B4 = getTimeMs();
Keyboard::reset();
Mouse::reset();
}
void Minecraft::tickMouse()
{
if (!m_bGrabbedMouse)
return;
platform()->recenterMouse();
}
void Minecraft::handleCharInput(char chr)
{
if (m_pScreen)
m_pScreen->charInput(chr);
}
void Minecraft::sendMessage(const std::string& message)
{
if (isOnlineClient())
{
// send the server a message packet
if (m_pRakNetInstance)
m_pRakNetInstance->send(new MessagePacket(message));
else
m_gui.addMessage("You aren't actually playing multiplayer!");
}
else
{
// fake the server having received a packet
MessagePacket mp(message);
if (m_pNetEventCallback && m_pRakNetInstance)
m_pNetEventCallback->handle(m_pRakNetInstance->m_pRakPeerInterface->GetMyGUID(), &mp);
else
m_gui.addMessage("You aren't hosting a multiplayer server!");
}
}
void Minecraft::resetPlayer(Player* player)
{
m_pLevel->validateSpawn();
player->reset();
Pos pos = m_pLevel->getSharedSpawnPos();
player->setPos(float(pos.x), float(pos.y), float(pos.z));
player->resetPos();
// Of course we have to add him back into the game, if he isn't already.
EntityVector& vec = m_pLevel->m_entities;
for (int i = 0; i < int(vec.size()); i++)
{
if (vec[i] == player)
return;
}
std::vector<Player*>& vec2 = m_pLevel->m_players;
for (int i = 0; i < int(vec2.size()); i++)
{
// remove the player if he is already in the player list
if (vec2[i] == player)
{
vec2.erase(vec2.begin() + i);
i--;
}
}
// add him in!!
m_pLevel->addEntity(player);
}
void Minecraft::respawnPlayer(Player* player)
{
resetPlayer(player);
// TODO: send a RespawnPacket
}
std::string Minecraft::getVersionString()
{
return "v0.1.0 alpha";
}
void Minecraft::_reloadInput()
{
if (m_pInputHolder)
delete m_pInputHolder;
if (isTouchscreen())
{
m_pInputHolder = new TouchInputHolder(this, m_options);
}
else
{
m_pInputHolder = new CustomInputHolder(
new KeyboardInput(m_options),
#ifdef ORIGINAL_CODE
new ControllerTurnInput,
#else
new MouseTurnInput(this),
#endif
new IBuildInput
);
}
m_mouseHandler.setTurnInput(m_pInputHolder->getTurnInput());
if (m_pLevel && m_pLocalPlayer)
{
m_pLocalPlayer->m_pMoveInput = m_pInputHolder->getMoveInput();
}
m_options->field_19 = !isTouchscreen();
}
void Minecraft::_levelGenerated()
{
if (m_pNetEventCallback)
m_pNetEventCallback->levelGenerated(m_pLevel);
}
void Minecraft::tick()
{
if (field_DA4 > 0)
field_DA4--;
if (!m_pScreen)
{
if (m_pLocalPlayer && m_pLocalPlayer->m_health <= 0)
{
setScreen(new DeathScreen);
}
}
tickInput();
m_gui.tick();
// if the level has been prepared, delete the prep thread
if (!m_bPreparingLevel)
{
if (m_pPrepThread)
{
delete m_pPrepThread;
m_pPrepThread = nullptr;
_levelGenerated();
}
SandTile::instaFall = false;
if (m_pLevel && !field_288)
{
m_pGameMode->tick();
m_pGameRenderer->tick();
m_pLevelRenderer->tick();
m_pLevel->tickEntities();
m_pLevel->tick();
if (m_pLocalPlayer)
{
m_pLevel->animateTick(
Mth::floor(m_pLocalPlayer->m_pos.x),
Mth::floor(m_pLocalPlayer->m_pos.y),
Mth::floor(m_pLocalPlayer->m_pos.z));
}
}
m_pTextures->loadAndBindTexture(C_TERRAIN_NAME);
if (!field_288)
{
m_pTextures->tick();
m_pParticleEngine->tick();
#ifndef ORIGINAL_CODE
if (m_pMobPersp)
{
m_pSoundEngine->m_pSoundSystem->setListenerPos(m_pMobPersp->m_pos.x, m_pMobPersp->m_pos.y, m_pMobPersp->m_pos.z);
m_pSoundEngine->m_pSoundSystem->setListenerAngle(m_pMobPersp->m_yaw, m_pMobPersp->m_pitch);
}
#endif
}
if (m_pScreen)
m_pScreen->tick();
Multitouch::reset();
}
}
void Minecraft::update()
{
if (field_288 && m_pLevel)
{
float x = m_timer.m_renderTicks;
m_timer.advanceTime();
m_timer.m_renderTicks = x;
}
else
{
m_timer.advanceTime();
}
if (m_pRakNetInstance)
{
m_pRakNetInstance->runEvents(m_pNetEventCallback);
}
for (int i = 0; i < m_timer.m_ticks; i++)
{
tick();
field_DA8++;
}
if (m_pLevel && !m_bPreparingLevel)
{
m_pLevel->updateLights();
}
#ifndef ORIGINAL_CODE
tickMouse();
#endif
m_pGameRenderer->render(m_timer.m_renderTicks);
double time = double(getTimeS());
m_fDeltaTime = time - m_fLastUpdated;
m_fLastUpdated = time;
// Added by iProgramInCpp
m_pGameMode->render(m_timer.m_renderTicks);
}
void Minecraft::init()
{
if (platform()->hasFileSystemAccess())
m_options = new Options(m_externalStorageDir);
else
m_options = new Options();
m_bIsTouchscreen = platform()->isTouchscreen();
_reloadInput();
m_pRakNetInstance = new RakNetInstance;
m_pSoundEngine = new SoundEngine(platform()->getSoundSystem());
m_pSoundEngine->init(m_options);
m_pTextures = new Textures(m_options, platform());
m_pTextures->addDynamicTexture(new WaterTexture);
m_pTextures->addDynamicTexture(new WaterSideTexture);
m_pTextures->addDynamicTexture(new LavaTexture);
m_pTextures->addDynamicTexture(new LavaSideTexture);
m_pTextures->addDynamicTexture(new FireTexture(0));
m_pLevelRenderer = new LevelRenderer(this, m_pTextures);
m_pGameRenderer = new GameRenderer(this);
m_pParticleEngine = new ParticleEngine(m_pLevel, m_pTextures);
m_pUser = new User(getOptions()->m_playerName, "");
#ifdef TEST_SURVIVAL_MODE
m_pGameMode = new SurvivalMode(this);
#else
m_pGameMode = new CreativeMode(this);
#endif
m_pFont = new Font(m_options, "font/default.png", m_pTextures);
if (GrassColor::isAvailable())
{
GrassColor::init(m_pPlatform->loadTexture("misc/grasscolor.png", true));
}
if (FoliageColor::isAvailable())
{
FoliageColor::init(m_pPlatform->loadTexture("misc/foliagecolor.png", true));
}
// Patch Manager
GetPatchManager()->LoadPatchData(platform()->getPatchData());
m_pTextures->loadAndBindTexture(C_TERRAIN_NAME);
GetPatchManager()->PatchTextures(platform(), TYPE_TERRAIN);
m_pTextures->loadAndBindTexture(C_ITEMS_NAME);
GetPatchManager()->PatchTextures(platform(), TYPE_ITEMS);
GetPatchManager()->PatchTiles();
}
Minecraft::~Minecraft()
{
SAFE_DELETE(m_options);
SAFE_DELETE(m_pNetEventCallback);
SAFE_DELETE(m_pRakNetInstance);
SAFE_DELETE(m_pLevelRenderer);
SAFE_DELETE(m_pGameRenderer);
SAFE_DELETE(m_pParticleEngine);
SAFE_DELETE(m_pSoundEngine);
SAFE_DELETE(m_pGameMode);
SAFE_DELETE(m_pFont);
SAFE_DELETE(m_pTextures);
if (m_pLevel)
{
LevelStorage* pStor = m_pLevel->getLevelStorage();
if (pStor)
delete pStor;
if (m_pLevel)
delete m_pLevel;
}
SAFE_DELETE(m_pUser);
SAFE_DELETE(m_pLevelStorageSource);
SAFE_DELETE(m_pInputHolder);
SAFE_DELETE(m_Logger);
//@BUG: potentially leaking a CThread instance if this is destroyed early?
}
void Minecraft::prepareLevel(const std::string& unused)
{
field_DA0 = 1;
float startTime = getTimeS();
Level* pLevel = m_pLevel;
if (!pLevel->field_B0C)
{
pLevel->setUpdateLights(0);
}
for (int i = 8, i2 = 0; i != 8 + C_MAX_CHUNKS_X * 16; i += 16)
{
for (int j = 8; j != 8 + C_MAX_CHUNKS_Z * 16; j += 16, i2 += 100)
{
// this looks like some kind of progress tracking
m_progressPercent = i2 / (C_MAX_CHUNKS_X * C_MAX_CHUNKS_Z);
float time1 = getTimeS();
// generating all the chunks at once
(void) m_pLevel->getTile(i, (C_MAX_Y + C_MIN_Y) / 2, j);
if (time1 != -1.0f)
getTimeS();
float time2 = getTimeS();
if (m_pLevel->field_B0C)
{
while (m_pLevel->updateLights());
}
if (time2 != -1.0f)
getTimeS();
}
}
if (startTime != -1.0f)
getTimeS();
m_pLevel->setUpdateLights(1);
startTime = getTimeS();
for (int x = 0; x < C_MAX_CHUNKS_X; x++)
{
for (int z = 0; z < C_MAX_CHUNKS_Z; z++)
{
LevelChunk* pChunk = m_pLevel->getChunk(x, z);
if (!pChunk)
continue;
if (pChunk->field_237)
continue;
pChunk->m_bUnsaved = false;
pChunk->clearUpdateMap();
}
}
if (startTime != -1.0f)
getTimeS();
field_DA0 = 3;
if (m_pLevel->field_B0C)
{
m_pLevel->setInitialSpawn();
m_pLevel->saveLevelData();
m_pLevel->getChunkSource()->saveAll();
}
else
{
m_pLevel->saveLevelData();
}
m_progressPercent = -1;
field_DA0 = 2;
startTime = getTimeS();
m_pLevel->prepare();
if (startTime != -1.0f)
getTimeS();
// These strings are initialized and then removed quickly afterwards. Probably some debug leftover
// "Generate level:";
// " - light: ";
// " - getTl: ";
// " - clear: ";
// " - prepr: ";
}
void Minecraft::sizeUpdate(int newWidth, int newHeight)
{
// re-calculate the GUI scale.
Gui::InvGuiScale = getBestScaleForThisScreenSize(newWidth, newHeight) / guiScaleMultiplier;
if (m_pScreen)
m_pScreen->setSize(int(Minecraft::width * Gui::InvGuiScale), int(Minecraft::height * Gui::InvGuiScale));
if (m_pInputHolder)
m_pInputHolder->setScreenSize(newWidth * guiScaleMultiplier, newHeight * guiScaleMultiplier);
}
float Minecraft::getBestScaleForThisScreenSize(int width, int height)
{
if (height > 1800)
return 1.0f / 4.0f;
if (isTouchscreen())
{
if (height > 600)
return 1.0f / 4.0f;
if (height > 400)
return 1.0f / 3.0f;
if (height > 300)
return 1.0f / 2.0f;
}
else
{
if (height > 1600)
return 1.0f / 4.0f;
if (height > 800)
return 1.0f / 3.0f;
if (height > 400)
return 1.0f / 2.0f;
}
return 1.0f;
}
void Minecraft::generateLevel(const std::string& unused, Level* pLevel)
{
float time = getTimeS(); //@UNUSED
prepareLevel(unused);
if (time != -1.0f)
getTimeS(); //@QUIRK: unused return value
// std::string("Level generated: "); //@QUIRK: unused string instance
LocalPlayer* pLocalPlayer = m_pLocalPlayer;
if (!pLocalPlayer)
{
pLocalPlayer = m_pGameMode->createPlayer(pLevel);
m_pLocalPlayer = pLocalPlayer;
m_pGameMode->initPlayer(pLocalPlayer);
}
if (pLocalPlayer)
pLocalPlayer->m_pMoveInput = m_pInputHolder->getMoveInput();
if (m_pLevelRenderer)
m_pLevelRenderer->setLevel(pLevel);
if (m_pParticleEngine)
m_pParticleEngine->setLevel(pLevel);
m_pGameMode->adjustPlayer(m_pLocalPlayer);
pLevel->validateSpawn();
pLevel->loadPlayer(m_pLocalPlayer);
if (m_pLocalPlayer)
{
m_pLocalPlayer->resetPos();
}
m_pMobPersp = m_pLocalPlayer;
m_pLevel = pLevel;
m_bPreparingLevel = false;
if (m_pRakNetInstance->m_bIsHost)
m_pRakNetInstance->announceServer(m_pUser->field_0);
}
void* Minecraft::prepareLevel_tspawn(void* ptr)
{
Minecraft* pMinecraft = (Minecraft*)ptr;
pMinecraft->generateLevel("Currently not used", pMinecraft->m_pLevel);
return nullptr;
}
void Minecraft::pauseGame()
{
if (m_pScreen) return;
m_pLevel->savePlayerData();
setScreen(new PauseScreen);
}
void Minecraft::setLevel(Level* pLevel, const std::string& text, LocalPlayer* pLocalPlayer)
{
m_pMobPersp = nullptr;
if (pLevel)
{
m_pGameMode->initLevel(pLevel);
if (pLocalPlayer && m_pLocalPlayer == nullptr)
{
m_pLocalPlayer = pLocalPlayer;
pLocalPlayer->resetPos();
}
else if (m_pLocalPlayer)
{
m_pLocalPlayer->resetPos();
pLevel->addEntity(m_pLocalPlayer);
}
m_pLevel = pLevel;
m_bPreparingLevel = true;
m_pPrepThread = new CThread(&Minecraft::prepareLevel_tspawn, this);
}
else
{
m_pLocalPlayer = nullptr;
}
}
void Minecraft::selectLevel(const std::string& a, const std::string& b, int c)
{
LevelStorage* pStor = m_pLevelStorageSource->selectLevel(a, false);
Dimension* pDim = Dimension::getNew(0);
m_pLevel = new Level(pStor, b, c, 1, pDim);
setLevel(m_pLevel, "Generating level", nullptr);
field_D9C = 1;
}
const char* Minecraft::getProgressMessage()
{
return progressMessages[field_DA0];
}
LevelStorageSource* Minecraft::getLevelSource()
{
return m_pLevelStorageSource;
}
ItemInstance* Minecraft::getSelectedItem()
{
ItemInstance* pInst = m_pLocalPlayer->m_pInventory->getSelectedItem();
if (!pInst)
{
m_CurrItemInstance.m_itemID = -1;
m_CurrItemInstance.m_amount = 999;
m_CurrItemInstance.m_auxValue = 0;
return &m_CurrItemInstance;
}
if (m_pGameMode->isSurvivalType())
return pInst;
m_CurrItemInstance.m_itemID = pInst->m_itemID;
m_CurrItemInstance.m_amount = 999;
m_CurrItemInstance.m_auxValue = pInst->m_auxValue;
return &m_CurrItemInstance;
}
int Minecraft::getFpsIntlCounter()
{
return 0;
}
void Minecraft::leaveGame(bool bCopyMap)
{
m_bPreparingLevel = false;
m_pRakNetInstance->disconnect();
m_pMobPersp = nullptr;
m_pLevelRenderer->setLevel(nullptr);
m_pParticleEngine->setLevel(nullptr);
#ifndef ORIGINAL_CODE
// @BUG: Deleting ServerSideNetworkHandler too late! This causes
// access to invalid memory in the destructor seeing as we already deleted the level.
delete m_pNetEventCallback;
#endif
#ifdef ENH_IMPROVED_SAVING
field_288 = true;
setScreen(new SavingWorldScreen(bCopyMap, m_pLocalPlayer));
#else
if (m_pLevel)
{
LevelStorage* pStorage = m_pLevel->getLevelStorage();
SAFE_DELETE(pStorage);
SAFE_DELETE(m_pLevel);
m_pLevel = nullptr;
}
#endif
#ifdef ORIGINAL_CODE
delete m_pNetEventCallback;
#endif
m_pLocalPlayer = nullptr;
m_pNetEventCallback = nullptr;
field_D9C = 0;
#ifndef ENH_IMPROVED_SAVING
// this is safe to do, since on destruction, we don't actually delete it.
SAFE_DELETE(m_pLocalPlayer);
if (bCopyMap)
setScreen(new RenameMPLevelScreen("_LastJoinedServer"));
else
setScreen(new StartMenuScreen);
#endif
}
void Minecraft::hostMultiplayer()
{
#ifndef __EMSCRIPTEN__
m_pRakNetInstance->host(m_pUser->field_0, C_DEFAULT_PORT, C_MAX_CONNECTIONS);
m_pNetEventCallback = new ServerSideNetworkHandler(this, m_pRakNetInstance);
#endif
}
void Minecraft::joinMultiplayer(const PingedCompatibleServer& serverInfo)
{
#ifndef __EMSCRIPTEN__
if (field_18 && m_pNetEventCallback)
{
field_18 = false;
m_pRakNetInstance->connect(serverInfo.m_address.ToString(), serverInfo.m_address.GetPort());
}
#endif
}
void Minecraft::cancelLocateMultiplayer()
{
#ifndef __EMSCRIPTEN__
field_18 = false;
m_pRakNetInstance->stopPingForHosts();
delete m_pNetEventCallback;
m_pNetEventCallback = nullptr;
#endif
}
void Minecraft::locateMultiplayer()
{
#ifndef __EMSCRIPTEN__
field_18 = true;
m_pRakNetInstance->pingForHosts(C_DEFAULT_PORT);
m_pNetEventCallback = new ClientSideNetworkHandler(this, m_pRakNetInstance);
#endif
}