diff --git a/GameMods.hpp b/GameMods.hpp index 9c08afc..6f60124 100644 --- a/GameMods.hpp +++ b/GameMods.hpp @@ -41,4 +41,7 @@ // Toggle Demo Mode //#define DEMO +// Enable Debug Mode +#define MC_DEBUG + #endif diff --git a/source/Base/Utils.cpp b/source/Base/Utils.cpp index f7956c5..9abc732 100644 --- a/source/Base/Utils.cpp +++ b/source/Base/Utils.cpp @@ -11,17 +11,40 @@ #include "Utils.hpp" #ifdef _WIN32 + #include +#include +#include + +// XPL means "Cross PLatform" +#define XPL_ACCESS _access +#define XPL_MKDIR(path, mode) _mkdir(path) // Why are we not using GetTickCount64()? It's simple -- getTimeMs has the exact same problem as using regular old GetTickCount. #pragma warning(disable : 28159) + #else + #include +#include +#include + +#define XPL_ACCESS access +#define XPL_MKDIR(path, mode) mkdir(path, mode) #endif + #include "compat/GL.hpp" int g_TimeSecondsOnInit = 0; +bool createFolderIfNotExists(const char* pDir) +{ + if (!XPL_ACCESS(pDir, 0)) + return true; + + return XPL_MKDIR(pDir, 0755) == 0; +} + const char* GetTerrainName() { return "terrain.png"; diff --git a/source/Base/Utils.hpp b/source/Base/Utils.hpp index dff6359..6b33a5c 100644 --- a/source/Base/Utils.hpp +++ b/source/Base/Utils.hpp @@ -504,6 +504,7 @@ constexpr float Lerp(float a, float b, float progress) return a + progress * (b - a); } +bool createFolderIfNotExists(const char* pDir); // things that we added: #ifndef ORIGINAL_CODE @@ -537,3 +538,23 @@ void SetHWND(HWND hwnd); #define LogMsgNoCR(...) #endif + +#ifdef MC_DEBUG + +#ifdef PLATFORM_ANDROID +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MinecraftPE", __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "MinecraftPE", __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "MinecraftPE", __VA_ARGS__) +#else +#define LOGI(...) printf("Info: " __VA_ARGS__) +#define LOGW(...) printf("WARN: " __VA_ARGS__) +#define LOGE(...) printf("ERROR: " __VA_ARGS__) +#endif + +#else + +#define LOGI(...) printf(__VA_ARGS__) +#define LOGW(...) printf(__VA_ARGS__) +#define LOGE(...) printf(__VA_ARGS__) + +#endif diff --git a/source/World/LevelData.cpp b/source/World/LevelData.cpp index 1ce5f87..e23aa18 100644 --- a/source/World/LevelData.cpp +++ b/source/World/LevelData.cpp @@ -19,6 +19,36 @@ LevelData::LevelData(TLong seed, const std::string& name, int x) field_78 = name; } +void LevelData::read(RakNet::BitStream& bs, int version) +{ + field_20 = version; + bs.Read(m_seed); + bs.Read(m_spawnPos.x); + bs.Read(m_spawnPos.y); + bs.Read(m_spawnPos.z); + bs.Read(field_10); + bs.Read(field_18); + bs.Read(field_14); + + RakNet::RakString rs; + bs.Read(rs); + field_78 = std::string(rs.C_String()); +} + +void LevelData::write(RakNet::BitStream& bs, int d) +{ + bs.Write(m_seed); + bs.Write(m_spawnPos.x); + bs.Write(m_spawnPos.y); + bs.Write(m_spawnPos.z); + bs.Write(field_10); + bs.Write(field_18); + bs.Write(int(getEpochTimeS())); + + RakNet::RakString rs(field_78.c_str()); + bs.Write(rs); +} + void PlayerData::loadPlayer(Player* player) { player->setPos(0.0f, 0.0f, 0.0f); @@ -43,3 +73,18 @@ void PlayerData::loadPlayer(Player* player) for (int i = 0; i < C_MAX_HOTBAR_ITEMS; i++) player->m_pInventory->setSelectionSlotItemId(i, m_hotbar[i]); } + +void PlayerData::savePlayer(Player* player) +{ + m_pos = player->m_pos; + m_vel = player->m_vel; + m_pitch = player->m_pitch; + m_yaw = player->m_yaw; + m_distanceFallen = player->m_distanceFallen; + field_24 = player->field_C0; + field_26 = player->field_BC; + field_28 = player->field_7C; + + for (int i = 0; i < C_MAX_HOTBAR_ITEMS; i++) + m_hotbar[i] = player->m_pInventory->getSelectionSlotItemId(i); +} diff --git a/source/World/LevelData.hpp b/source/World/LevelData.hpp index f6d0f63..5e6d3d2 100644 --- a/source/World/LevelData.hpp +++ b/source/World/LevelData.hpp @@ -13,6 +13,7 @@ #include "Utils.hpp" #include "Vec3.hpp" #include "Inventory.hpp" +#include "BitStream.h" struct PlayerData { @@ -27,6 +28,7 @@ struct PlayerData int m_hotbar[C_MAX_HOTBAR_ITEMS]; void loadPlayer(Player* player); + void savePlayer(Player* player); }; struct LevelData @@ -34,15 +36,24 @@ struct LevelData LevelData(); LevelData(TLong seed, const std::string&, int); + void read(RakNet::BitStream& bs, int d); + void write(RakNet::BitStream& bs, int d); + TLong m_seed = 0; Pos m_spawnPos; TLong field_10 = 0; int field_14 = 0; - int field_18 = 0; + TLong field_18 = 0; int field_1C = 0; int field_20 = 0; PlayerData m_LocalPlayerData; int m_nPlayers = -1; std::string field_78; + + // inlined in 0.1.0 demo + int getVersion() const + { + return field_20; + } }; diff --git a/source/World/Storage/ExternalFileLevelStorage.cpp b/source/World/Storage/ExternalFileLevelStorage.cpp new file mode 100644 index 0000000..838df4d --- /dev/null +++ b/source/World/Storage/ExternalFileLevelStorage.cpp @@ -0,0 +1,225 @@ +#include "ExternalFileLevelStorage.hpp" +#include "LevelChunk.hpp" + +ExternalFileLevelStorage::ExternalFileLevelStorage(const std::string& a, const std::string& path) : + field_8(a), + m_levelDirPath(path) +{ + createFolderIfNotExists(m_levelDirPath.c_str()); + + std::string datLevel = m_levelDirPath + "/" + "level.dat"; + std::string datPlayer = m_levelDirPath + "/" + "player.dat"; + + m_pLevelData = new LevelData; + if (!readLevelData(datLevel, m_pLevelData)) + { + delete m_pLevelData; + m_pLevelData = nullptr; + return; + } + + readPlayerData(datPlayer, m_pLevelData); +} + +LevelData* ExternalFileLevelStorage::prepareLevel(Level* level) +{ + m_pLevel = level; + return m_pLevelData; +} + +ChunkStorage* ExternalFileLevelStorage::createChunkStorage(Dimension* pDim) +{ + return this; +} + +void ExternalFileLevelStorage::saveLevelData(LevelData* levelData, std::vector& players) +{ + writeLevelData(m_levelDirPath + "/" + "level.dat", levelData); + savePlayerData(levelData, players); + + SAFE_DELETE(m_pLevelData); + + m_pLevelData = new LevelData(*levelData); +} + +void ExternalFileLevelStorage::savePlayerData(LevelData* levelData, std::vector& players) +{ + if (players.empty()) + return; + + FILE* pFile = fopen((m_levelDirPath + "/" + "player.dat").c_str(), "wb"); + if (!pFile) + { + LogMsg("Not saving player data"); + return; + } + + levelData->m_LocalPlayerData.savePlayer(players[0]); + + int nPlayers = 1; + fwrite(&nPlayers, sizeof nPlayers, 1, pFile); + + int nSizePD = 80; + fwrite(&nSizePD, sizeof nSizePD, 1, pFile); + + // @NOTE: No reason to swap elementCount and elementSize here. I understood it the + // last time - to check whether the data loaded all the way. However, no checks are + // done here. + fwrite(&levelData->m_LocalPlayerData, 1, nSizePD, pFile); + + fclose(pFile); +} + +void ExternalFileLevelStorage::closeAll() +{ +} + +void ExternalFileLevelStorage::tick() +{ +} + +void ExternalFileLevelStorage::flush() +{ +} + +LevelChunk* ExternalFileLevelStorage::load(Level* level, int x, int z) +{ + if (!m_pRegionFile) + { + m_pRegionFile = new RegionFile(m_levelDirPath); + + if (!m_pRegionFile->open()) + { + SAFE_DELETE(m_pRegionFile); + m_pRegionFile = nullptr; + + return nullptr; + } + } + + RakNet::BitStream* pBitStream = nullptr; + if (!m_pRegionFile->readChunk(x, z, &pBitStream)) + return nullptr; + + pBitStream->ResetReadPointer(); + + TileID* pData = new TileID[16 * 16 * 128]; + pBitStream->Read((char*)pData, 16 * 16 * 128 * sizeof(TileID)); + + LevelChunk* pChunk = new LevelChunk(level, pData, x, z); + pBitStream->Read((char*)pChunk->m_tileData, 16 * 16 * 128 / 2); + + if (m_pLevelData->getVersion() == 1) + { + pBitStream->Read((char*)pChunk->m_lightSky, 16 * 16 * 128 / 2); + pBitStream->Read((char*)pChunk->m_lightBlk, 16 * 16 * 128 / 2); + } + + pBitStream->Read((char*)pChunk->m_updateMap, sizeof pChunk->m_updateMap); + + delete pBitStream->GetData(); + delete pBitStream; + + pChunk->recalcHeightmap(); + pChunk->m_bUnsaved = false; + pChunk->field_234 = true; + pChunk->field_237 = true; + + return pChunk; +} + +void ExternalFileLevelStorage::save(Level* level, LevelChunk* chunk) +{ + if (!m_pRegionFile) + m_pRegionFile = new RegionFile(m_levelDirPath); + + if (!m_pRegionFile->open()) + { + SAFE_DELETE(m_pRegionFile); + m_pRegionFile = nullptr; + + LogMsg("Not saving :( (x: %d z: %d)", chunk->m_chunkX, chunk->m_chunkZ); + return; + } + + RakNet::BitStream bs; + bs.Write((const char*)chunk->m_pBlockData, 16 * 16 * 128 * sizeof(TileID)); + bs.Write((const char*)chunk->m_tileData, 16 * 16 * 128 / 2); + + if (m_pLevelData->field_20 == 1) + { + bs.Write((const char*)chunk->m_lightSky, 16 * 16 * 128 / 2); + bs.Write((const char*)chunk->m_lightBlk, 16 * 16 * 128 / 2); + } + + bs.Write((const char*)chunk->m_updateMap, sizeof chunk->m_updateMap); + + m_pRegionFile->writeChunk(chunk->m_chunkX, chunk->m_chunkZ, bs); +} + +void ExternalFileLevelStorage::saveEntities(Level* level, LevelChunk* chunk) +{ + // no op +} + +bool ExternalFileLevelStorage::readLevelData(const std::string& path, LevelData* pLevelData) +{ + FILE* pFile = fopen(path.c_str(), "rb"); + if (!pFile) + return false; + + int version = 0, length = 0; + if (fread(&version, sizeof(int), 1, pFile) != 1) + { + _cleanup: + fclose(pFile); + return false; + } + + if (fread(&length, sizeof(int), 1, pFile) != 1) + goto _cleanup; + + uint8_t* data = new uint8_t[length]; + + if (fread(data, sizeof(uint8_t), length, pFile) != length) + { + SAFE_DELETE(data); + goto _cleanup; + } + + RakNet::BitStream bs(data, length, false); + pLevelData->read(bs, version); + + SAFE_DELETE_ARRAY(data); + fclose(pFile); + + return true; +} + +bool ExternalFileLevelStorage::readPlayerData(const std::string& path, LevelData* pLevelData) +{ + FILE* pFile = fopen(path.c_str(), "rb"); + if (!pFile) + return false; + + // don't know if it's actually nPlayers or version + int nPlayers = 0, size = 0; + if (fread(&nPlayers, sizeof(int), 1, pFile) != 1) + goto _cleanup; + + if (fread(&size, sizeof(int), 1, pFile) != 1) + goto _cleanup; + + if (nPlayers != 1) + goto _cleanup; + + if (fread(&pLevelData->m_LocalPlayerData, 1, sizeof pLevelData->m_LocalPlayerData, pFile) == size) + pLevelData->m_nPlayers = nPlayers; + + fclose(pFile); + return true; + +_cleanup: + fclose(pFile); + return false; +} diff --git a/source/World/Storage/ExternalFileLevelStorage.hpp b/source/World/Storage/ExternalFileLevelStorage.hpp new file mode 100644 index 0000000..88736b7 --- /dev/null +++ b/source/World/Storage/ExternalFileLevelStorage.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include "LevelStorage.hpp" +#include "ChunkStorage.hpp" +#include "RegionFile.hpp" + +#ifndef DEMO + +class ExternalFileLevelStorage : public LevelStorage, ChunkStorage +{ +public: + ExternalFileLevelStorage(const std::string& a, const std::string& path); + + // LevelStorage + LevelData* prepareLevel(Level* level) override; + ChunkStorage* createChunkStorage(Dimension*) override; + void saveLevelData(LevelData* levelData, std::vector& players) override; + void savePlayerData(LevelData* levelData, std::vector& players) override; + void closeAll() override; + void tick() override; + void flush() override; + + // ChunkStorage + LevelChunk* load(Level* level, int x, int z) override; + void save(Level* level, LevelChunk* chunk) override; + void saveEntities(Level* level, LevelChunk* chunk) override; + + static bool readLevelData(const std::string& path, LevelData* pLevelData); + static bool readPlayerData(const std::string& path, LevelData* pLevelData); + +public: + std::string field_8; + std::string m_levelDirPath; + LevelData* m_pLevelData = nullptr; + RegionFile* m_pRegionFile = nullptr; + Level* m_pLevel = nullptr; + std::list m_unsavedLevelChunks; +}; + +#endif diff --git a/windows_vs/minecraftcpp.vcxproj b/windows_vs/minecraftcpp.vcxproj index 8acb945..48ee41e 100644 --- a/windows_vs/minecraftcpp.vcxproj +++ b/windows_vs/minecraftcpp.vcxproj @@ -154,6 +154,7 @@ + @@ -472,6 +473,7 @@ + diff --git a/windows_vs/minecraftcpp.vcxproj.filters b/windows_vs/minecraftcpp.vcxproj.filters index 94636be..5ad96c3 100644 --- a/windows_vs/minecraftcpp.vcxproj.filters +++ b/windows_vs/minecraftcpp.vcxproj.filters @@ -1062,6 +1062,9 @@ Source Files\World + + Source Files\World\Storage + @@ -1931,6 +1934,9 @@ Header Files\World + + Header Files +