1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-27 00:41:08 +02:00

Merge pull request #4741 from IvanSavenko/serialize_local_state

Serialize local state of player interface
This commit is contained in:
Ivan Savenko
2024-10-10 15:03:14 +03:00
committed by GitHub
17 changed files with 250 additions and 39 deletions

View File

@ -25,6 +25,7 @@
#include "lib/UnlockGuard.h" #include "lib/UnlockGuard.h"
#include "lib/battle/BattleInfo.h" #include "lib/battle/BattleInfo.h"
#include "lib/networkPacks/PacksForServer.h" #include "lib/networkPacks/PacksForServer.h"
#include "lib/networkPacks/SaveLocalState.h"
bool CCallback::teleportHero(const CGHeroInstance *who, const CGTownInstance *where) bool CCallback::teleportHero(const CGHeroInstance *who, const CGTownInstance *where)
{ {
@ -323,6 +324,15 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn
sendRequest(pack); 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 ) void CCallback::save( const std::string &fname )
{ {
cl->save(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 assembleArtifacts(const ObjectInstanceID & heroID, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
virtual void eraseArtifactByClient(const ArtifactLocation & al)=0; virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0; virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
virtual void saveLocalState(const JsonNode & data)=0;
virtual void endTurn()=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 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; 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; 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 dismissCreature(const CArmedInstance *obj, SlotID stackPos) override;
bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override; bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override;
void saveLocalState(const JsonNode & data) override;
void endTurn() override; void endTurn() override;
void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted) override; void spellResearch(const CGTownInstance *town, SpellID spellAtSlot, bool accepted) override;
void swapGarrisonHero(const CGTownInstance *town) override; void swapGarrisonHero(const CGTownInstance *town) override;

View File

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

View File

@ -11,6 +11,7 @@
#include "PlayerLocalState.h" #include "PlayerLocalState.h"
#include "../CCallback.h" #include "../CCallback.h"
#include "../lib/json/JsonNode.h"
#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/mapObjects/CGTownInstance.h" #include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/pathfinder/CGPathNode.h" #include "../lib/pathfinder/CGPathNode.h"
@ -23,34 +24,20 @@ PlayerLocalState::PlayerLocalState(CPlayerInterface & owner)
{ {
} }
void PlayerLocalState::saveHeroPaths(std::map<const CGHeroInstance *, int3> & pathsMap) const PlayerSpellbookSetting & PlayerLocalState::getSpellbookSettings()
{ {
for(auto & p : paths) return spellbookSettings;
{
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) void PlayerLocalState::setSpellbookSettings(const PlayerSpellbookSetting & newSettings)
{ {
if(owner.cb) spellbookSettings = newSettings;
{
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) void PlayerLocalState::setPath(const CGHeroInstance * h, const CGPath & path)
{ {
paths[h] = path; paths[h] = path;
syncronizeState();
} }
const CGPath & PlayerLocalState::getPath(const CGHeroInstance * h) const const CGPath & PlayerLocalState::getPath(const CGHeroInstance * h) const
@ -70,6 +57,7 @@ bool PlayerLocalState::setPath(const CGHeroInstance * h, const int3 & destinatio
if(!owner.cb->getPathsInfo(h)->getPath(path, destination)) 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) paths.erase(h); //invalidate previously possible path if selected (before other hero blocked only path / fly spell expired)
syncronizeState();
return false; return false;
} }
@ -93,6 +81,7 @@ void PlayerLocalState::erasePath(const CGHeroInstance * h)
{ {
paths.erase(h); paths.erase(h);
adventureInt->onHeroChanged(h); adventureInt->onHeroChanged(h);
syncronizeState();
} }
void PlayerLocalState::verifyPath(const CGHeroInstance * h) void PlayerLocalState::verifyPath(const CGHeroInstance * h)
@ -170,6 +159,7 @@ void PlayerLocalState::setSelection(const CArmedInstance * selection)
if (adventureInt && selection) if (adventureInt && selection)
adventureInt->onSelectionChanged(selection); adventureInt->onSelectionChanged(selection);
syncronizeState();
} }
bool PlayerLocalState::isHeroSleeping(const CGHeroInstance * hero) const bool PlayerLocalState::isHeroSleeping(const CGHeroInstance * hero) const
@ -184,6 +174,7 @@ void PlayerLocalState::setHeroAsleep(const CGHeroInstance * hero)
assert(!vstd::contains(sleepingHeroes, hero)); assert(!vstd::contains(sleepingHeroes, hero));
sleepingHeroes.push_back(hero); sleepingHeroes.push_back(hero);
syncronizeState();
} }
void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero) void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero)
@ -193,6 +184,7 @@ void PlayerLocalState::setHeroAwaken(const CGHeroInstance * hero)
assert(vstd::contains(sleepingHeroes, hero)); assert(vstd::contains(sleepingHeroes, hero));
vstd::erase(sleepingHeroes, hero); vstd::erase(sleepingHeroes, hero);
syncronizeState();
} }
const std::vector<const CGHeroInstance *> & PlayerLocalState::getWanderingHeroes() const std::vector<const CGHeroInstance *> & PlayerLocalState::getWanderingHeroes()
@ -215,6 +207,8 @@ void PlayerLocalState::addWanderingHero(const CGHeroInstance * hero)
if (currentSelection == nullptr) if (currentSelection == nullptr)
setSelection(hero); setSelection(hero);
syncronizeState();
} }
void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero) void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero)
@ -236,6 +230,8 @@ void PlayerLocalState::removeWanderingHero(const CGHeroInstance * hero)
if (currentSelection == nullptr && !ownedTowns.empty()) if (currentSelection == nullptr && !ownedTowns.empty())
setSelection(ownedTowns.front()); setSelection(ownedTowns.front());
syncronizeState();
} }
void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2) void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2)
@ -244,6 +240,8 @@ void PlayerLocalState::swapWanderingHero(size_t pos1, size_t pos2)
std::swap(wanderingHeroes.at(pos1), wanderingHeroes.at(pos2)); std::swap(wanderingHeroes.at(pos1), wanderingHeroes.at(pos2));
adventureInt->onHeroOrderChanged(); adventureInt->onHeroOrderChanged();
syncronizeState();
} }
const std::vector<const CGTownInstance *> & PlayerLocalState::getOwnedTowns() const std::vector<const CGTownInstance *> & PlayerLocalState::getOwnedTowns()
@ -266,6 +264,8 @@ void PlayerLocalState::addOwnedTown(const CGTownInstance * town)
if (currentSelection == nullptr) if (currentSelection == nullptr)
setSelection(town); setSelection(town);
syncronizeState();
} }
void PlayerLocalState::removeOwnedTown(const CGTownInstance * town) void PlayerLocalState::removeOwnedTown(const CGTownInstance * town)
@ -282,6 +282,8 @@ void PlayerLocalState::removeOwnedTown(const CGTownInstance * town)
if (currentSelection == nullptr && !ownedTowns.empty()) if (currentSelection == nullptr && !ownedTowns.empty())
setSelection(ownedTowns.front()); setSelection(ownedTowns.front());
syncronizeState();
} }
void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2) void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2)
@ -289,5 +291,119 @@ void PlayerLocalState::swapOwnedTowns(size_t pos1, size_t pos2)
assert(ownedTowns[pos1] && ownedTowns[pos2]); assert(ownedTowns[pos1] && ownedTowns[pos2]);
std::swap(ownedTowns.at(pos1), ownedTowns.at(pos2)); std::swap(ownedTowns.at(pos1), ownedTowns.at(pos2));
syncronizeState();
adventureInt->onTownOrderChanged(); 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 CGHeroInstance;
class CGTownInstance; class CGTownInstance;
class CArmedInstance; class CArmedInstance;
class JsonNode;
struct CGPath; struct CGPath;
class int3; class int3;
@ -21,6 +22,15 @@ VCMI_LIB_NAMESPACE_END
class CPlayerInterface; class CPlayerInterface;
struct PlayerSpellbookSetting
{
//on which page we left spellbook
int spellbookLastPageBattle = 0;
int spellbookLastPageAdvmap = 0;
int spellbookLastTabBattle = 4;
int spellbookLastTabAdvmap = 4;
};
/// Class that contains potentially serializeable state of a local player /// Class that contains potentially serializeable state of a local player
class PlayerLocalState class PlayerLocalState
{ {
@ -34,18 +44,10 @@ class PlayerLocalState
std::vector<const CGHeroInstance *> wanderingHeroes; //our heroes on the adventure map (not the garrisoned ones) std::vector<const CGHeroInstance *> wanderingHeroes; //our heroes on the adventure map (not the garrisoned ones)
std::vector<const CGTownInstance *> ownedTowns; //our towns on the adventure map std::vector<const CGTownInstance *> ownedTowns; //our towns on the adventure map
void saveHeroPaths(std::map<const CGHeroInstance *, int3> & paths); PlayerSpellbookSetting spellbookSettings;
void loadHeroPaths(std::map<const CGHeroInstance *, int3> & paths);
void syncronizeState();
public: public:
struct SpellbookLastSetting
{
//on which page we left spellbook
int spellbookLastPageBattle = 0;
int spellbookLastPageAdvmap = 0;
int spellbookLastTabBattle = 4;
int spellbookLastTabAdvmap = 4;
} spellbookSettings;
explicit PlayerLocalState(CPlayerInterface & owner); explicit PlayerLocalState(CPlayerInterface & owner);
@ -53,6 +55,9 @@ public:
void setHeroAsleep(const CGHeroInstance * hero); void setHeroAsleep(const CGHeroInstance * hero);
void setHeroAwaken(const CGHeroInstance * hero); void setHeroAwaken(const CGHeroInstance * hero);
const PlayerSpellbookSetting & getSpellbookSettings();
void setSpellbookSettings(const PlayerSpellbookSetting & newSettings);
const std::vector<const CGTownInstance *> & getOwnedTowns(); const std::vector<const CGTownInstance *> & getOwnedTowns();
const CGTownInstance * getOwnedTown(size_t index); const CGTownInstance * getOwnedTown(size_t index);
void addOwnedTown(const CGTownInstance * hero); void addOwnedTown(const CGTownInstance * hero);
@ -81,6 +86,9 @@ public:
const CGTownInstance * getCurrentTown() const; const CGTownInstance * getCurrentTown() const;
const CArmedInstance * getCurrentArmy() const; const CArmedInstance * getCurrentArmy() const;
void serialize(JsonNode & dest) const;
void deserialize(const JsonNode & source);
/// Changes currently selected object /// Changes currently selected object
void setSelection(const CArmedInstance *sel); void setSelection(const CArmedInstance *sel);
}; };

View File

@ -205,9 +205,9 @@ CSpellWindow::CSpellWindow(const CGHeroInstance * _myHero, CPlayerInterface * _m
} }
} }
selectedTab = battleSpellsOnly ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap; selectedTab = battleSpellsOnly ? myInt->localState->getSpellbookSettings().spellbookLastTabBattle : myInt->localState->getSpellbookSettings().spellbookLastTabAdvmap;
schoolTab->setFrame(selectedTab, 0); schoolTab->setFrame(selectedTab, 0);
int cp = battleSpellsOnly ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap; int cp = battleSpellsOnly ? myInt->localState->getSpellbookSettings().spellbookLastPageBattle : myInt->localState->getSpellbookSettings().spellbookLastPageAdvmap;
// spellbook last page battle index is not reset after battle, so this needs to stay here // spellbook last page battle index is not reset after battle, so this needs to stay here
vstd::abetween(cp, 0, std::max(0, pagesWithinCurrentTab() - 1)); vstd::abetween(cp, 0, std::max(0, pagesWithinCurrentTab() - 1));
setCurrentPage(cp); setCurrentPage(cp);
@ -313,8 +313,18 @@ void CSpellWindow::processSpells()
void CSpellWindow::fexitb() void CSpellWindow::fexitb()
{ {
(myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastTabBattle : myInt->localState->spellbookSettings.spellbookLastTabAdvmap) = selectedTab; auto spellBookState = myInt->localState->getSpellbookSettings();
(myInt->battleInt ? myInt->localState->spellbookSettings.spellbookLastPageBattle : myInt->localState->spellbookSettings.spellbookLastPageAdvmap) = currentPage; if(myInt->battleInt)
{
spellBookState.spellbookLastTabBattle = selectedTab;
spellBookState.spellbookLastPageBattle = currentPage;
}
else
{
spellBookState.spellbookLastTabAdvmap = selectedTab;
spellBookState.spellbookLastPageAdvmap = currentPage;
}
myInt->localState->setSpellbookSettings(spellBookState);
if(onSpellSelect) if(onSpellSelect)
onSpellSelect(SpellID::NONE); onSpellSelect(SpellID::NONE);
@ -619,8 +629,10 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
auto guard = vstd::makeScopeGuard([this]() auto guard = vstd::makeScopeGuard([this]()
{ {
owner->myInt->localState->spellbookSettings.spellbookLastTabAdvmap = owner->selectedTab; auto spellBookState = owner->myInt->localState->getSpellbookSettings();
owner->myInt->localState->spellbookSettings.spellbookLastPageAdvmap = owner->currentPage; spellBookState.spellbookLastTabAdvmap = owner->selectedTab;
spellBookState.spellbookLastPageAdvmap = owner->currentPage;
owner->myInt->localState->setSpellbookSettings(spellBookState);
}); });
spells::detail::ProblemImpl problem; spells::detail::ProblemImpl problem;

View File

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

View File

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

View File

@ -16,7 +16,6 @@
#include "bonuses/CBonusSystemNode.h" #include "bonuses/CBonusSystemNode.h"
#include "ResourceSet.h" #include "ResourceSet.h"
#include "TurnTimerInfo.h" #include "TurnTimerInfo.h"
#include "ConstTransitivePtr.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -66,6 +65,7 @@ public:
std::vector<QuestInfo> quests; //store info about all received quests std::vector<QuestInfo> quests; //store info about all received quests
std::vector<Bonus> battleBonuses; //additional bonuses to be added during battle with neutrals std::vector<Bonus> battleBonuses; //additional bonuses to be added during battle with neutrals
std::map<uint32_t, std::map<ArtifactPosition, ArtifactID>> costumesArtifacts; 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 cheated;
bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory
@ -116,6 +116,9 @@ public:
h & status; h & status;
h & turnTimer; h & turnTimer;
if (h.version >= Handler::Version::LOCAL_PLAYER_STATE_DATA)
h & *playerLocalSettings;
if (h.version >= Handler::Version::PLAYER_STATE_OWNED_OBJECTS) if (h.version >= Handler::Version::PLAYER_STATE_OWNED_OBJECTS)
{ {
h & ownedObjects; h & ownedObjects;

View File

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

View File

@ -12,6 +12,7 @@
#include "PacksForClient.h" #include "PacksForClient.h"
#include "PacksForClientBattle.h" #include "PacksForClientBattle.h"
#include "PacksForServer.h" #include "PacksForServer.h"
#include "SaveLocalState.h"
#include "SetRewardableConfiguration.h" #include "SetRewardableConfiguration.h"
#include "StackLocation.h" #include "StackLocation.h"
#include "PacksForLobby.h" #include "PacksForLobby.h"
@ -92,6 +93,12 @@ bool CLobbyPackToServer::isForServer() const
return true; return true;
} }
void SaveLocalState::visitTyped(ICPackVisitor & visitor)
{
visitor.visitSaveLocalState(*this);
}
void PackageApplied::visitTyped(ICPackVisitor & visitor) void PackageApplied::visitTyped(ICPackVisitor & visitor)
{ {
visitor.visitPackageApplied(*this); 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 REWARDABLE_BANKS, // 863 - team state contains list of scouted objects, coast visitable rewardable objects
REGION_LABEL, // 864 - labels for campaign regions REGION_LABEL, // 864 - labels for campaign regions
SPELL_RESEARCH, // 865 - spell research 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/PacksForClientBattle.h"
#include "../networkPacks/PacksForLobby.h" #include "../networkPacks/PacksForLobby.h"
#include "../networkPacks/PacksForServer.h" #include "../networkPacks/PacksForServer.h"
#include "../networkPacks/SaveLocalState.h"
#include "../networkPacks/SetRewardableConfiguration.h" #include "../networkPacks/SetRewardableConfiguration.h"
#include "../networkPacks/SetStackEffect.h" #include "../networkPacks/SetStackEffect.h"
@ -290,6 +291,7 @@ void registerTypes(Serializer &s)
s.template registerType<LobbySetExtraOptions>(240); s.template registerType<LobbySetExtraOptions>(240);
s.template registerType<SpellResearch>(241); s.template registerType<SpellResearch>(241);
s.template registerType<SetResearchedSpells>(242); s.template registerType<SetResearchedSpells>(242);
s.template registerType<SaveLocalState>(243);
} }
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

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

View File

@ -19,6 +19,7 @@
#include "queries/MapQueries.h" #include "queries/MapQueries.h"
#include "../lib/IGameCallback.h" #include "../lib/IGameCallback.h"
#include "../lib/CPlayerState.h"
#include "../lib/mapObjects/CGTownInstance.h" #include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/gameState/CGameState.h" #include "../lib/gameState/CGameState.h"
@ -389,6 +390,13 @@ void ApplyGhNetPackVisitor::visitQueryReply(QueryReply & pack)
result = gh.queryReply(pack.qid, pack.reply, pack.player); 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) void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack)
{ {
gh.throwIfWrongPlayer(&pack); gh.throwIfWrongPlayer(&pack);

View File

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