Sfoglia il codice sorgente

Implemented serialization of local player state in json form

Ivan Savenko 1 anno fa
parent
commit
679181c103

+ 10 - 0
CCallback.cpp

@@ -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);

+ 2 - 0
CCallback.h

@@ -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;

+ 2 - 0
client/CPlayerInterface.cpp

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

+ 131 - 25
client/PlayerLocalState.cpp

@@ -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);
+}

+ 5 - 3
client/PlayerLocalState.h

@@ -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);
 };

+ 1 - 0
lib/CMakeLists.txt

@@ -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
 

+ 8 - 2
lib/CPlayerState.cpp

@@ -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);
 }

+ 4 - 1
lib/CPlayerState.h

@@ -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;

+ 2 - 0
lib/networkPacks/NetPackVisitor.h

@@ -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

+ 7 - 0
lib/networkPacks/NetPacksLib.cpp

@@ -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);

+ 27 - 0
lib/networkPacks/SaveLocalState.h

@@ -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;
+	}
+};

+ 2 - 1
lib/serializer/ESerializationVersion.h

@@ -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
 };

+ 2 - 0
lib/serializer/RegisterTypes.h

@@ -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

+ 3 - 0
server/CGameHandler.cpp

@@ -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))
 	{

+ 8 - 0
server/NetPacksServer.cpp

@@ -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);

+ 1 - 0
server/ServerNetPackVisitors.h

@@ -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;
 };