1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-22 22:13:35 +02:00

Implemented serialization of local player state in json form

This commit is contained in:
Ivan Savenko 2024-10-08 19:55:51 +00:00
parent 9492eab7c5
commit 679181c103
16 changed files with 215 additions and 32 deletions

View File

@ -25,6 +25,7 @@
#include "lib/UnlockGuard.h"
#include "lib/battle/BattleInfo.h"
#include "lib/networkPacks/PacksForServer.h"
#include "lib/networkPacks/SaveLocalState.h"
bool CCallback::teleportHero(const CGHeroInstance *who, const CGTownInstance *where)
{
@ -323,6 +324,15 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn
sendRequest(pack);
}
void CCallback::saveLocalState(const JsonNode & data)
{
SaveLocalState state;
state.data = data;
state.player = *player;
sendRequest(state);
}
void CCallback::save( const std::string &fname )
{
cl->save(fname);

View File

@ -100,6 +100,7 @@ public:
virtual void assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
virtual void saveLocalState(const JsonNode & data)=0;
virtual void endTurn()=0;
virtual void buyArtifact(const CGHeroInstance *hero, ArtifactID aid)=0; //used to buy artifacts in towns (including spell book in the guild and war machines in blacksmith)
virtual void setFormation(const CGHeroInstance * hero, EArmyFormation mode)=0;
@ -193,6 +194,7 @@ public:
void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override;
bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override;
bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override;
void saveLocalState(const JsonNode & data) override;
void endTurn() override;
void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted) override;
void swapGarrisonHero(const CGTownInstance *town) override;

View File

@ -1333,6 +1333,8 @@ void CPlayerInterface::initializeHeroTownList()
localState->addOwnedTown(town);
}
localState->deserialize(*cb->getPlayerState(playerID)->playerLocalSettings);
if(adventureInt)
adventureInt->onHeroChanged(nullptr);
}

View File

@ -11,6 +11,7 @@
#include "PlayerLocalState.h"
#include "../CCallback.h"
#include "../lib/json/JsonNode.h"
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/pathfinder/CGPathNode.h"
@ -33,34 +34,10 @@ void PlayerLocalState::setSpellbookSettings(const PlayerSpellbookSetting & newSe
spellbookSettings = newSettings;
}
void PlayerLocalState::saveHeroPaths(std::map<const CGHeroInstance *, int3> & pathsMap)
{
for(auto & p : paths)
{
if(p.second.nodes.size())
pathsMap[p.first] = p.second.endPos();
else
logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->getNameTranslated());
}
}
void PlayerLocalState::loadHeroPaths(std::map<const CGHeroInstance *, int3> & pathsMap)
{
if(owner.cb)
{
for(auto & p : pathsMap)
{
CGPath path;
owner.cb->getPathsInfo(p.first)->getPath(path, p.second);
paths[p.first] = path;
logGlobal->trace("Restored path for hero %s leading to %s with %d nodes", p.first->nodeName(), p.second.toString(), path.nodes.size());
}
}
}
void PlayerLocalState::setPath(const CGHeroInstance * h, const CGPath & path)
{
paths[h] = path;
syncronizeState();
}
const CGPath & PlayerLocalState::getPath(const CGHeroInstance * h) const
@ -80,6 +57,7 @@ bool PlayerLocalState::setPath(const CGHeroInstance * h, const int3 & destinatio
if(!owner.cb->getPathsInfo(h)->getPath(path, destination))
{
paths.erase(h); //invalidate previously possible path if selected (before other hero blocked only path / fly spell expired)
syncronizeState();
return false;
}
@ -103,6 +81,7 @@ void PlayerLocalState::erasePath(const CGHeroInstance * h)
{
paths.erase(h);
adventureInt->onHeroChanged(h);
syncronizeState();
}
void PlayerLocalState::verifyPath(const CGHeroInstance * h)
@ -180,6 +159,7 @@ void PlayerLocalState::setSelection(const CArmedInstance * selection)
if (adventureInt && selection)
adventureInt->onSelectionChanged(selection);
syncronizeState();
}
bool PlayerLocalState::isHeroSleeping(const CGHeroInstance * hero) const
@ -194,6 +174,7 @@ void PlayerLocalState::setHeroAsleep(const CGHeroInstance * hero)
assert(!vstd::contains(sleepingHeroes, hero));
sleepingHeroes.push_back(hero);
syncronizeState();
}
void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero)
@ -203,6 +184,7 @@ void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero)
assert(vstd::contains(sleepingHeroes, hero));
vstd::erase(sleepingHeroes, hero);
syncronizeState();
}
const std::vector<const CGHeroInstance *> & PlayerLocalState::getWanderingHeroes()
@ -225,6 +207,8 @@ void PlayerLocalState::addWanderingHero(const CGHeroInstance * hero)
if (currentSelection == nullptr)
setSelection(hero);
syncronizeState();
}
void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero)
@ -246,6 +230,8 @@ void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero)
if (currentSelection == nullptr && !ownedTowns.empty())
setSelection(ownedTowns.front());
syncronizeState();
}
void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2)
@ -254,6 +240,8 @@ void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2)
std::swap(wanderingHeroes.at(pos1), wanderingHeroes.at(pos2));
adventureInt->onHeroOrderChanged();
syncronizeState();
}
const std::vector<const CGTownInstance *> & PlayerLocalState::getOwnedTowns()
@ -276,6 +264,8 @@ void PlayerLocalState::addOwnedTown(const CGTownInstance * town)
if (currentSelection == nullptr)
setSelection(town);
syncronizeState();
}
void PlayerLocalState::removeOwnedTown(const CGTownInstance * town)
@ -292,6 +282,8 @@ void PlayerLocalState::removeOwnedTown(const CGTownInstance * town)
if (currentSelection == nullptr && !ownedTowns.empty())
setSelection(ownedTowns.front());
syncronizeState();
}
void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2)
@ -299,5 +291,119 @@ void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2)
assert(ownedTowns[pos1] && ownedTowns[pos2]);
std::swap(ownedTowns.at(pos1), ownedTowns.at(pos2));
syncronizeState();
adventureInt->onTownOrderChanged();
}
void PlayerLocalState::syncronizeState()
{
JsonNode data;
serialize(data);
owner.cb->saveLocalState(data);
}
void PlayerLocalState::serialize(JsonNode & dest) const
{
dest.clear();
for (auto const * town : ownedTowns)
{
JsonNode record;
record["id"].Integer() = town->id;
dest["towns"].Vector().push_back(record);
}
for (auto const * hero : wanderingHeroes)
{
JsonNode record;
record["id"].Integer() = hero->id;
if (vstd::contains(sleepingHeroes, hero))
record["sleeping"].Bool() = true;
if (paths.count(hero))
{
record["path"]["x"].Integer() = paths.at(hero).lastNode().coord.x;
record["path"]["y"].Integer() = paths.at(hero).lastNode().coord.y;
record["path"]["z"].Integer() = paths.at(hero).lastNode().coord.z;
}
dest["heroes"].Vector().push_back(record);
}
dest["spellbook"]["pageBattle"].Integer() = spellbookSettings.spellbookLastPageBattle;
dest["spellbook"]["pageAdvmap"].Integer() = spellbookSettings.spellbookLastPageAdvmap;
dest["spellbook"]["tabBattle"].Integer() = spellbookSettings.spellbookLastTabBattle;
dest["spellbook"]["tabAdvmap"].Integer() = spellbookSettings.spellbookLastTabAdvmap;
dest["currentSelection"].Integer() = currentSelection->id;
}
void PlayerLocalState::deserialize(const JsonNode & source)
{
// this method must be called after player state has been initialized
assert(currentSelection != nullptr);
assert(!ownedTowns.empty() || wanderingHeroes.empty());
auto oldHeroes = wanderingHeroes;
auto oldTowns = ownedTowns;
paths.clear();
sleepingHeroes.clear();
wanderingHeroes.clear();
ownedTowns.clear();
for (auto const & town : source["towns"].Vector())
{
ObjectInstanceID objID(town["id"].Integer());
const CGTownInstance * townPtr = owner.cb->getTown(objID);
if (!townPtr)
continue;
if (!vstd::contains(oldTowns, townPtr))
continue;
ownedTowns.push_back(townPtr);
vstd::erase(oldTowns, townPtr);
}
for (auto const & hero : source["heroes"].Vector())
{
ObjectInstanceID objID(hero["id"].Integer());
const CGHeroInstance * heroPtr = owner.cb->getHero(objID);
if (!heroPtr)
continue;
if (!vstd::contains(oldHeroes, heroPtr))
continue;
wanderingHeroes.push_back(heroPtr);
vstd::erase(oldHeroes, heroPtr);
if (hero["sleeping"].Bool())
sleepingHeroes.push_back(heroPtr);
if (hero["path"]["x"].isNumber() && hero["path"]["y"].isNumber() && hero["path"]["z"].isNumber())
{
int3 pathTarget(hero["path"]["x"].Integer(), hero["path"]["y"].Integer(), hero["path"]["z"].Integer());
setPath(heroPtr, pathTarget);
}
}
spellbookSettings.spellbookLastPageBattle = source["spellbook"]["pageBattle"].Integer();
spellbookSettings.spellbookLastPageAdvmap = source["spellbook"]["pageAdvmap"].Integer();
spellbookSettings.spellbookLastTabBattle = source["spellbook"]["tabBattle"].Integer();
spellbookSettings.spellbookLastTabAdvmap = source["spellbook"]["tabAdvmap"].Integer();
// append any owned heroes / towns that were not present in loaded state
wanderingHeroes.insert(wanderingHeroes.end(), oldHeroes.begin(), oldHeroes.end());
ownedTowns.insert(ownedTowns.end(), oldTowns.begin(), oldTowns.end());
//FIXME: broken, anything that is selected in here will be overwritten on NewTurn pack
// ObjectInstanceID selectedObjectID(source["currentSelection"].Integer());
// const CGObjectInstance * objectPtr = owner.cb->getObjInstance(selectedObjectID);
// const CArmedInstance * armyPtr = dynamic_cast<const CArmedInstance*>(objectPtr);
//
// if (armyPtr)
// setSelection(armyPtr);
}

View File

@ -14,6 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN
class CGHeroInstance;
class CGTownInstance;
class CArmedInstance;
class JsonNode;
struct CGPath;
class int3;
@ -45,9 +46,7 @@ class PlayerLocalState
PlayerSpellbookSetting spellbookSettings;
void saveHeroPaths(std::map<const CGHeroInstance *, int3> & paths);
void loadHeroPaths(std::map<const CGHeroInstance *, int3> & paths);
void syncronizeState();
public:
explicit PlayerLocalState(CPlayerInterface & owner);
@ -87,6 +86,9 @@ public:
const CGTownInstance * getCurrentTown() const;
const CArmedInstance * getCurrentArmy() const;
void serialize(JsonNode & dest) const;
void deserialize(const JsonNode & source);
/// Changes currently selected object
void setSelection(const CArmedInstance *sel);
};

View File

@ -559,6 +559,7 @@ set(lib_MAIN_HEADERS
networkPacks/PacksForServer.h
networkPacks/SetRewardableConfiguration.h
networkPacks/SetStackEffect.h
networkPacks/SaveLocalState.h
networkPacks/StackLocation.h
networkPacks/TradeItem.h

View File

@ -10,6 +10,7 @@
#include "StdInc.h"
#include "CPlayerState.h"
#include "json/JsonNode.h"
#include "mapObjects/CGDwelling.h"
#include "mapObjects/CGTownInstance.h"
#include "mapObjects/CGHeroInstance.h"
@ -20,8 +21,13 @@
VCMI_LIB_NAMESPACE_BEGIN
PlayerState::PlayerState()
: color(-1), human(false), cheated(false), enteredWinningCheatCode(false),
enteredLosingCheatCode(false), status(EPlayerStatus::INGAME)
: color(-1)
, playerLocalSettings(std::make_unique<JsonNode>())
, human(false)
, cheated(false)
, enteredWinningCheatCode(false)
, enteredLosingCheatCode(false)
, status(EPlayerStatus::INGAME)
{
setNodeType(PLAYER);
}

View File

@ -16,7 +16,6 @@
#include "bonuses/CBonusSystemNode.h"
#include "ResourceSet.h"
#include "TurnTimerInfo.h"
#include "ConstTransitivePtr.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -66,6 +65,7 @@ public:
std::vector<QuestInfo> quests; //store info about all received quests
std::vector<Bonus> battleBonuses; //additional bonuses to be added during battle with neutrals
std::map<uint32_t, std::map<ArtifactPosition, ArtifactID>> costumesArtifacts;
std::unique_ptr<JsonNode> playerLocalSettings; // Json with client-defined data, such as order of heroes or current hero paths. Not used by client/lib
bool cheated;
bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory
@ -116,6 +116,9 @@ public:
h & status;
h & turnTimer;
if (h.version >= Handler::Version::LOCAL_PLAYER_STATE_DATA)
h & *playerLocalSettings;
if (h.version >= Handler::Version::PLAYER_STATE_OWNED_OBJECTS)
{
h & ownedObjects;

View File

@ -13,6 +13,7 @@
#include "PacksForClientBattle.h"
#include "PacksForServer.h"
#include "PacksForLobby.h"
#include "SaveLocalState.h"
#include "SetRewardableConfiguration.h"
#include "SetStackEffect.h"
@ -177,6 +178,7 @@ public:
virtual void visitLobbyForceSetPlayer(LobbyForceSetPlayer & pack) {}
virtual void visitLobbyShowMessage(LobbyShowMessage & pack) {}
virtual void visitLobbyPvPAction(LobbyPvPAction & pack) {}
virtual void visitSaveLocalState(SaveLocalState & pack) {}
};
VCMI_LIB_NAMESPACE_END

View File

@ -12,6 +12,7 @@
#include "PacksForClient.h"
#include "PacksForClientBattle.h"
#include "PacksForServer.h"
#include "SaveLocalState.h"
#include "SetRewardableConfiguration.h"
#include "StackLocation.h"
#include "PacksForLobby.h"
@ -92,6 +93,12 @@ bool CLobbyPackToServer::isForServer() const
return true;
}
void SaveLocalState::visitTyped(ICPackVisitor & visitor)
{
visitor.visitSaveLocalState(*this);
}
void PackageApplied::visitTyped(ICPackVisitor & visitor)
{
visitor.visitPackageApplied(*this);

View File

@ -0,0 +1,27 @@
/*
* SaveLocalState.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "NetPacksBase.h"
#include "../json/JsonNode.h"
struct DLL_LINKAGE SaveLocalState : public CPackForServer
{
JsonNode data;
void visitTyped(ICPackVisitor & visitor) override;
template <typename Handler> void serialize(Handler & h)
{
h & static_cast<CPackForServer &>(*this);
h & data;
}
};

View File

@ -62,6 +62,7 @@ enum class ESerializationVersion : int32_t
REWARDABLE_BANKS, // 863 - team state contains list of scouted objects, coast visitable rewardable objects
REGION_LABEL, // 864 - labels for campaign regions
SPELL_RESEARCH, // 865 - spell research
LOCAL_PLAYER_STATE_DATA, // 866 - player state contains arbitrary client-side data
CURRENT = SPELL_RESEARCH
CURRENT = LOCAL_PLAYER_STATE_DATA
};

View File

@ -34,6 +34,7 @@
#include "../networkPacks/PacksForClientBattle.h"
#include "../networkPacks/PacksForLobby.h"
#include "../networkPacks/PacksForServer.h"
#include "../networkPacks/SaveLocalState.h"
#include "../networkPacks/SetRewardableConfiguration.h"
#include "../networkPacks/SetStackEffect.h"
@ -290,6 +291,7 @@ void registerTypes(Serializer &s)
s.template registerType<LobbySetExtraOptions>(240);
s.template registerType<SpellResearch>(241);
s.template registerType<SetResearchedSpells>(242);
s.template registerType<SaveLocalState>(243);
}
VCMI_LIB_NAMESPACE_END

View File

@ -4012,6 +4012,9 @@ bool CGameHandler::isBlockedByQueries(const CPackForServer *pack, PlayerColor pl
if (dynamic_cast<const PlayerMessage *>(pack) != nullptr)
return false;
if (dynamic_cast<const SaveLocalState *>(pack) != nullptr)
return false;
auto query = queries->topQuery(player);
if (query && query->blocksPack(pack))
{

View File

@ -19,6 +19,7 @@
#include "queries/MapQueries.h"
#include "../lib/IGameCallback.h"
#include "../lib/CPlayerState.h"
#include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/gameState/CGameState.h"
@ -389,6 +390,13 @@ void ApplyGhNetPackVisitor::visitQueryReply(QueryReply & pack)
result = gh.queryReply(pack.qid, pack.reply, pack.player);
}
void ApplyGhNetPackVisitor::visitSaveLocalState(SaveLocalState & pack)
{
gh.throwIfWrongPlayer(&pack);
*gh.gameState()->getPlayerState(pack.player)->playerLocalSettings = pack.data;
result = true;
}
void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack)
{
gh.throwIfWrongPlayer(&pack);

View File

@ -62,4 +62,5 @@ public:
void visitDigWithHero(DigWithHero & pack) override;
void visitCastAdvSpell(CastAdvSpell & pack) override;
void visitPlayerMessage(PlayerMessage & pack) override;
void visitSaveLocalState(SaveLocalState & pack) override;
};