From 61a2349b8bbd0cd67813d8d8ca39dfa90cd268b6 Mon Sep 17 00:00:00 2001 From: Kolyah35 Date: Fri, 27 Mar 2026 21:26:25 +0300 Subject: [PATCH] FEAT: Player data saving/loading --- src/client/gamemode/SurvivalMode.cpp | 1 + src/network/ClientSideNetworkHandler.cpp | 5 ++ src/network/ServerSideNetworkHandler.cpp | 33 +++++++++ src/network/packet/SendInventoryPacket.h | 26 ++++++- .../storage/ExternalFileLevelStorage.cpp | 68 +++++++++++++++++++ .../level/storage/ExternalFileLevelStorage.h | 13 ++++ src/world/level/storage/LevelStorage.h | 3 + 7 files changed, 146 insertions(+), 3 deletions(-) diff --git a/src/client/gamemode/SurvivalMode.cpp b/src/client/gamemode/SurvivalMode.cpp index ac49fda..c53c198 100755 --- a/src/client/gamemode/SurvivalMode.cpp +++ b/src/client/gamemode/SurvivalMode.cpp @@ -68,6 +68,7 @@ bool SurvivalMode::destroyBlock( int x, int y, int z, int face ) { minecraft->player->inventory->clearSlot(minecraft->player->inventory->selected); } } + if (changed && couldDestroy) { ItemInstance instance(t, 1, data); Tile::tiles[t]->playerDestroy(minecraft->level, minecraft->player, x, y, z, data); diff --git a/src/network/ClientSideNetworkHandler.cpp b/src/network/ClientSideNetworkHandler.cpp index 696e918..aa15ed3 100755 --- a/src/network/ClientSideNetworkHandler.cpp +++ b/src/network/ClientSideNetworkHandler.cpp @@ -395,6 +395,11 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& source, SendInve auto items = packet->items; minecraft->player->inventory->replace(items); + + for (int i = 0; i < packet->linkedSlot.size(); i++) { + minecraft->player->inventory->linkSlot(i, packet->linkedSlot[i].inventorySlot, true); + LOGI("%i -> %i\n", packet->linkedSlot[i].inventorySlot, i); + } } } diff --git a/src/network/ServerSideNetworkHandler.cpp b/src/network/ServerSideNetworkHandler.cpp index c897591..6032c0e 100755 --- a/src/network/ServerSideNetworkHandler.cpp +++ b/src/network/ServerSideNetworkHandler.cpp @@ -22,6 +22,7 @@ #include "../raknet/PacketPriority.h" #include "platform/log.h" #include "world/item/ItemInstance.h" +#include "world/level/storage/LevelStorage.h" #include "world/phys/Vec3.h" #include "world/item/crafting/Recipe.h" #include "world/item/crafting/Recipes.h" @@ -169,6 +170,8 @@ void ServerSideNetworkHandler::onDisconnect(const RakNet::RakNetGUID& guid) if (player->owner == guid) { + minecraft->level->getLevelStorage()->savePlayer(*player); + std::string message = player->name; message += " disconnected from the game"; displayGameMessage(message); @@ -319,6 +322,36 @@ void ServerSideNetworkHandler::onReady_ClientGeneration(const RakNet::RakNetGUID } } + if (!minecraft->level->getLevelStorage()->loadPlayer(*newPlayer)) { + LOGW("Failed to load %s data\n", newPlayer->name.c_str()); + } + + // Credits to EpikIzCool + bitStream.Reset(); + MovePlayerPacket mv(newPlayer->entityId, newPlayer->x, newPlayer->y - newPlayer->heightOffset, + newPlayer->z, newPlayer->xRot, newPlayer->yRot); + mv.write(&bitStream); + + rakPeer->Send(&bitStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, source, false); + + bitStream.Reset(); + SetHealthPacket hp(newPlayer->health); + hp.write(&bitStream); + + rakPeer->Send(&bitStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, source, false); + + if (newPlayer->hasRespawnPosition()) { + bitStream.Reset(); + SetSpawnPositionPacket sp(newPlayer->getRespawnPosition()); + sp.write(&bitStream); + rakPeer->Send(&bitStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, source, false); + } + + bitStream.Reset(); + SendInventoryPacket(newPlayer, false).write(&bitStream); + rakPeer->Send(&bitStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, source, false); + + // Additional packets // * set spawn /* diff --git a/src/network/packet/SendInventoryPacket.h b/src/network/packet/SendInventoryPacket.h index 482ef51..b744b58 100755 --- a/src/network/packet/SendInventoryPacket.h +++ b/src/network/packet/SendInventoryPacket.h @@ -3,13 +3,13 @@ #include "../Packet.h" #include "world/entity/player/Inventory.h" +#include "world/inventory/FillingContainer.h" +#include class SendInventoryPacket: public Packet { public: - SendInventoryPacket() - { - } + SendInventoryPacket() {} SendInventoryPacket(Player* player, bool dropItems) : entityId(player->entityId), @@ -22,10 +22,15 @@ public: ItemInstance* item = inv->getItem(i); items.push_back(item? *item : ItemInstance()); } + for (int i = 0; i < NumArmorItems; ++i) { ItemInstance* item = player->getArmor(i); items.push_back(item? *item : ItemInstance()); } + + for (int i = 0; i < inv->numLinkedSlots; ++i) { + linkedSlot[i] = inv->linkedSlots[i]; + } } void write(RakNet::BitStream* bitStream) @@ -40,6 +45,13 @@ public: // Armor for (int i = 0; i < NumArmorItems; ++i) PacketUtil::writeItemInstance(items[i + numItems], bitStream); + + int lSlots = Inventory::MAX_SELECTION_SIZE; + + // Linked slots + bitStream->Write(lSlots); + for (int i = 0; i < lSlots; ++i) + bitStream->Write(linkedSlot[i]); } void read(RakNet::BitStream* bitStream) @@ -51,6 +63,12 @@ public: // Inventory, Armor for (int i = 0; i < numItems + NumArmorItems; ++i) items.push_back(PacketUtil::readItemInstance(bitStream)); + + // Linked slots + int lSlots = 0; + bitStream->Read(lSlots); + for (int i = 0; i < lSlots; ++i) + bitStream->Read(linkedSlot[i]); } void handle(const RakNet::RakNetGUID& source, NetEventCallback* callback) @@ -63,6 +81,8 @@ public: short numItems; unsigned char extra; + std::array linkedSlot; + static const int ExtraDrop = 1; static const int NumArmorItems = 4; }; diff --git a/src/world/level/storage/ExternalFileLevelStorage.cpp b/src/world/level/storage/ExternalFileLevelStorage.cpp index 91bcf45..03cca08 100755 --- a/src/world/level/storage/ExternalFileLevelStorage.cpp +++ b/src/world/level/storage/ExternalFileLevelStorage.cpp @@ -1,3 +1,6 @@ +#include +#include +#include #if !defined(DEMO_MODE) && !defined(APPLE_DEMO_PROMOTION) #include "LevelData.h" @@ -88,6 +91,9 @@ ExternalFileLevelStorage::ExternalFileLevelStorage(const std::string& levelId, c { createFolderIfNotExists(levelPath.c_str()); + std::string playerFolder = levelPath + "/players"; + createFolderIfNotExists(playerFolder.c_str()); + std::string datFileName = levelPath + "/" + fnLevelDat; std::string levelFileName = levelPath + "/" + fnPlayerDat; loadedLevelData = new LevelData(); @@ -113,6 +119,7 @@ void ExternalFileLevelStorage::saveLevelData(LevelData& levelData, std::vector

* players ) { + // @todo: completely rewrite std::string directory = levelPath + "/"; std::string tmpFile = directory + fnLevelDatNew; std::string datFile = directory + fnLevelDat; @@ -141,6 +148,67 @@ void ExternalFileLevelStorage::saveLevelData( const std::string& levelPath, Leve // Remove the temporary save, if the rename didn't do it remove(tmpFile.c_str()); + + // Save players + // fuck mojang for that + if (!players || players->empty()) { + return; + } + + for (auto& player : *players) { + if (player != NULL) { + savePlayer(*player, directory); + } + } +} + +void ExternalFileLevelStorage::savePlayer(Player& player, const std::string& worldDir) { + std::string playerPath = worldDir + "/players/" + player.name + ".dat"; + + LOGI("Saving player %s to %s...\n", player.name.c_str(), playerPath.c_str()); + + RakNet::BitStream data; + RakDataOutput buf(data); + CompoundTag playerTag; + player.saveWithoutId(&playerTag); + + NbtIo::write(&playerTag, &buf); + + std::ofstream file(playerPath, std::ios::out | std::ios::binary); + file.write((const char*)data.GetData(), (size_t)data.GetNumberOfBytesUsed()); +} + +bool ExternalFileLevelStorage::loadPlayer(Player& player, const std::string& worldDir) { + std::string playerPath = worldDir + "/players/" + player.name + ".dat"; + + LOGI("Loading player %s from %s...\n", player.name.c_str(), playerPath.c_str()); + + std::ifstream file(playerPath, std::ios::in | std::ios::binary); + if (!file.is_open()) { + return false; + } + + std::vector data((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + RakNet::BitStream bitStream(data.data(), data.size(), false); + RakDataInput stream(bitStream); + + CompoundTag* tag = NbtIo::read(&stream); + if (tag) { + player.load(tag); + tag->deleteChildren(); + delete tag; + } + + return true; +} + +void ExternalFileLevelStorage::savePlayer(Player& player) { + ExternalFileLevelStorage::savePlayer(player, levelPath); +} + +bool ExternalFileLevelStorage::loadPlayer(Player& player) { + return ExternalFileLevelStorage::loadPlayer(player, levelPath); } LevelData* ExternalFileLevelStorage::prepareLevel(Level* _level) diff --git a/src/world/level/storage/ExternalFileLevelStorage.h b/src/world/level/storage/ExternalFileLevelStorage.h index b7dd960..9aad448 100755 --- a/src/world/level/storage/ExternalFileLevelStorage.h +++ b/src/world/level/storage/ExternalFileLevelStorage.h @@ -67,6 +67,19 @@ public: void saveGame(Level* level); void saveAll(Level* level, std::vector& levelChunks); + /** + * @brief Save player to /player/.dat file + */ + static void savePlayer(Player& player, const std::string& worldDir); + + /** + * @brief Load player from /player/.dat file + */ + static bool loadPlayer(Player& player, const std::string& worldDir); + + virtual void savePlayer(Player& player); + virtual bool loadPlayer(Player& player); + virtual void tick(); virtual void flush() {} private: diff --git a/src/world/level/storage/LevelStorage.h b/src/world/level/storage/LevelStorage.h index 84c0f62..d3098d2 100755 --- a/src/world/level/storage/LevelStorage.h +++ b/src/world/level/storage/LevelStorage.h @@ -32,6 +32,9 @@ public: virtual void saveGame(Level* level) {} virtual void loadEntities(Level* level, LevelChunk* levelChunk) {} + virtual void savePlayer(Player& player) = 0; + virtual bool loadPlayer(Player& player) = 0; + //void checkSession() throws LevelConflictException; //PlayerIO getPlayerIO(); };