Przeglądaj źródła

Heroes placed on water in map will be automatically given boat

Ivan Savenko 2 lat temu
rodzic
commit
3913b8e98c

+ 1 - 1
config/factions/castle.json

@@ -9,6 +9,7 @@
 			"120px" : "TPCASCAS",
 			"130px" : "CRBKGCAS"
 		},
+		"boat" : "boatCastle",
 		"puzzleMap" :
 		{
 			"prefix" : "PUZCAS",
@@ -148,7 +149,6 @@
 			"mageGuild" : 4,
 			"warMachine" : "ballista",
 			"moatAbility" : "castleMoat",
-			"boat" : "boatCastle",
 			// primaryResource not specified so town get both Wood and Ore for resource bonus
 
 			"buildings" :

+ 1 - 1
config/factions/conflux.json

@@ -9,6 +9,7 @@
 			"120px" : "TPCASELE",
 			"130px" : "CRBKGELE"
 		},
+		"boat" : "boatNecropolis",
 		"puzzleMap" :
 		{
 			"prefix" : "PUZELE",
@@ -153,7 +154,6 @@
 			"primaryResource" : "mercury",
 			"warMachine" : "ballista",
 			"moatAbility" : "castleMoat",
-			"boat" : "boatNecropolis",
 
 			"buildings" :
 			{

+ 1 - 0
config/factions/dungeon.json

@@ -10,6 +10,7 @@
 			"120px" : "TPCASDUN",
 			"130px" : "CRBKGDUN"
 		},
+		"boat" : "boatCastle",
 		"puzzleMap" :
 		{
 			"prefix" : "PUZDUN",

+ 1 - 1
config/factions/fortress.json

@@ -9,6 +9,7 @@
 			"120px" : "TPCASFOR",
 			"130px" : "CRBKGFOR"
 		},
+		"boat" : "boatFortress",
 		"puzzleMap" :
 		{
 			"prefix" : "PUZFOR",
@@ -148,7 +149,6 @@
 			"mageGuild" : 3,
 			"warMachine" : "firstAidTent",
 			"moatAbility" : "fortressMoat",
-			"boat" : "boatFortress",
 			// primaryResource not specified so town get both Wood and Ore for resource bonus
 
 			"buildings" :

+ 1 - 0
config/factions/inferno.json

@@ -10,6 +10,7 @@
 			"120px" : "TPCASINF",
 			"130px" : "CRBKGINF"
 		},
+		"boat" : "boatCastle",
 		"puzzleMap" :
 		{
 			"prefix" : "PUZINF",

+ 1 - 1
config/factions/necropolis.json

@@ -10,6 +10,7 @@
 			"120px" : "TPCASNEC",
 			"130px" : "CRBKGNEC"
 		},
+		"boat" : "boatNecropolis",
 		"puzzleMap" :
 		{
 			"prefix" : "PUZNEC",
@@ -153,7 +154,6 @@
 			"mageGuild" : 5,
 			"warMachine" : "firstAidTent",
 			"moatAbility" : "necropolisMoat",
-			"boat" : "boatNecropolis",
 			// primaryResource not specified so town get both Wood and Ore for resource bonus
 
 			"buildings" :

+ 1 - 0
config/factions/rampart.json

@@ -9,6 +9,7 @@
 			"120px" : "TPCASRAM",
 			"130px" : "CRBKGRAM"
 		},
+		"boat" : "boatCastle",
 		"puzzleMap" :
 		{
 			"prefix" : "PUZRAM",

+ 1 - 0
config/factions/stronghold.json

@@ -9,6 +9,7 @@
 			"120px" : "TPCASSTR",
 			"130px" : "CRBKGSTR"
 		},
+		"boat" : "boatCastle",
 		"puzzleMap" :
 		{
 			"prefix" : "PUZSTR",

+ 1 - 0
config/factions/tower.json

@@ -9,6 +9,7 @@
 			"120px" : "TPCASTOW",
 			"130px" : "CRBKGTOW"
 		},
+		"boat" : "boatCastle",
 		"puzzleMap" :
 		{
 			"prefix" : "PUZTOW",

+ 2 - 0
include/vcmi/Faction.h

@@ -16,12 +16,14 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 class FactionID;
 enum class EAlignment : uint8_t;
+enum class EBoatId : int32_t;
 
 class DLL_LINKAGE Faction : public EntityT<FactionID>, public INativeTerrainProvider
 {
 public:
 	virtual bool hasTown() const = 0;
 	virtual EAlignment getAlignment() const = 0;
+	virtual EBoatId getBoatType() const = 0;
 };
 
 VCMI_LIB_NAMESPACE_END

+ 29 - 0
lib/CGameState.cpp

@@ -1250,6 +1250,31 @@ void CGameState::initHeroes()
 		map->allHeroes[hero->type->getIndex()] = hero;
 	}
 
+	// generate boats for all heroes on water
+	for(auto hero : map->heroesOnMap)
+	{
+		assert(map->isInTheMap(hero->visitablePos()));
+		const auto & tile = map->getTile(hero->visitablePos());
+		if (tile.terType->isWater())
+		{
+			auto handler = VLC->objtypeh->getHandlerFor(Obj::BOAT, hero->getBoatType().getNum());
+			CGBoat * boat = dynamic_cast<CGBoat*>(handler->create());
+			handler->configureObject(boat, gs->getRandomGenerator());
+
+			boat->ID = Obj::BOAT;
+			boat->subID = hero->getBoatType().getNum();
+			boat->pos = hero->pos;
+			boat->appearance = handler->getTemplates().front();
+			boat->id = ObjectInstanceID(static_cast<si32>(gs->map->objects.size()));
+
+			map->objects.emplace_back(boat);
+			map->addBlockVisTiles(boat);
+
+			boat->hero = hero;
+			hero->boat = boat;
+		}
+	}
+
 	for(auto obj : map->objects) //prisons
 	{
 		if(obj && obj->ID == Obj::PRISON)
@@ -2535,6 +2560,10 @@ void CGameState::buildBonusSystemTree()
 	}
 	// CStackInstance <-> CCreature, CStackInstance <-> CArmedInstance, CArtifactInstance <-> CArtifact
 	// are provided on initializing / deserializing
+
+	// NOTE: calling deserializationFix() might be more correct option, but might lead to side effects
+	for (auto hero : map->heroesOnMap)
+		hero->boatDeserializationFix();
 }
 
 void CGameState::deserializationFix()

+ 14 - 29
lib/CTownHandler.cpp

@@ -174,6 +174,11 @@ EAlignment CFaction::getAlignment() const
 	return alignment;
 }
 
+EBoatId CFaction::getBoatType() const
+{
+	return boatType.toEnum();
+}
+
 TerrainId CFaction::getNativeTerrain() const
 {
 	return nativeTerrain;
@@ -893,16 +898,6 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
 
 	warMachinesToLoad[town] = source["warMachine"];
 
-
-	town->shipyardBoat = EBoatId::NONE;
-	if (!source["boat"].isNull())
-	{
-		VLC->modh->identifiers.requestIdentifier("core:boat", source["boat"], [=](int32_t boatTypeID)
-		{
-			town->shipyardBoat = BoatId(boatTypeID);
-		});
-	}
-
 	town->mageLevel = static_cast<ui32>(source["mageGuild"].Float());
 
 	town->namesCount = 0;
@@ -1028,6 +1023,15 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode
 	faction->creatureBg120 = source["creatureBackground"]["120px"].String();
 	faction->creatureBg130 = source["creatureBackground"]["130px"].String();
 
+	faction->boatType = EBoatId::NONE;
+	if (!source["boat"].isNull())
+	{
+		VLC->modh->identifiers.requestIdentifier("core:boat", source["boat"], [=](int32_t boatTypeID)
+		{
+			faction->boatType = BoatId(boatTypeID);
+		});
+	}
+
 	int alignment = vstd::find_pos(GameConstants::ALIGNMENT_NAMES, source["alignment"].String());
 	if (alignment == -1)
 		faction->alignment = EAlignment::NEUTRAL;
@@ -1158,25 +1162,6 @@ void CTownHandler::afterLoadFinalization()
 	initializeRequirements();
 	initializeOverridden();
 	initializeWarMachines();
-
-	for(auto & faction : objects)
-	{
-		if (!faction->town)
-			continue;
-
-		bool hasBoat = faction->town->shipyardBoat != EBoatId::NONE;
-		bool hasShipyard = faction->town->buildings.count(BuildingID::SHIPYARD);
-
-		if ( hasBoat && !hasShipyard )
-			logMod->warn("Town %s has boat but has no shipyard!", faction->getJsonKey());
-
-		if ( !hasBoat && hasShipyard )
-		{
-			logMod->warn("Town %s has shipyard but has no boat set!", faction->getJsonKey());
-			// Mod compatibility for 1.3
-			faction->town->shipyardBoat = EBoatId::CASTLE;
-		}
-	}
 }
 
 void CTownHandler::initializeRequirements()

+ 7 - 3
lib/CTownHandler.h

@@ -204,6 +204,11 @@ public:
 	EAlignment alignment = EAlignment::NEUTRAL;
 	bool preferUndergroundPlacement = false;
 
+	/// Boat that will be used by town shipyard (if any)
+	/// and for placing heroes directly on boat (in map editor, water prisons & taverns)
+	BoatId boatType;
+
+
 	CTown * town = nullptr; //NOTE: can be null
 
 	std::string creatureBg120;
@@ -226,6 +231,7 @@ public:
 	bool hasTown() const override;
 	TerrainId getNativeTerrain() const override;
 	EAlignment getAlignment() const override;
+	EBoatId getBoatType() const override;
 
 	void updateFrom(const JsonNode & data);
 	void serializeJson(JsonSerializeFormat & handler);
@@ -236,6 +242,7 @@ public:
 		h & identifier;
 		h & index;
 		h & nativeTerrain;
+		h & boatType;
 		h & alignment;
 		h & town;
 		h & creatureBg120;
@@ -282,8 +289,6 @@ public:
 	ArtifactID warMachine;
 	SpellID moatAbility;
 
-	/// boat that will be built by town shipyard, if exists
-	BoatId shipyardBoat;
 	// default chance for hero of specific class to appear in tavern, if field "tavern" was not set
 	// resulting chance = sqrt(town.chance * heroClass.chance)
 	ui32 defaultTavernChance;
@@ -349,7 +354,6 @@ public:
 		h & mageLevel;
 		h & primaryRes;
 		h & warMachine;
-		h & shipyardBoat;
 		h & clientInfo;
 		h & moatAbility;
 		h & defaultTavernChance;

+ 1 - 1
lib/GameConstants.h

@@ -1282,7 +1282,7 @@ class BattleField : public BaseForID<BattleField, si32>
 	DLL_LINKAGE static BattleField fromString(const std::string & identifier);
 };
 
-enum class EBoatId
+enum class EBoatId : int32_t
 {
 	NONE = -1,
 	NECROPOLIS = 0,

+ 2 - 2
lib/MetaString.cpp

@@ -323,7 +323,7 @@ void MetaString::jsonSerialize(JsonNode & dest) const
 	for (const auto & entry : localStrings )
 	{
 		JsonNode value;
-		value.Float() = static_cast<int>(entry.first) * 10000 + entry.second;
+		value.Integer() = static_cast<int>(entry.first) * 10000 + entry.second;
 		jsonLocalStrings.Vector().push_back(value);
 	}
 
@@ -344,7 +344,7 @@ void MetaString::jsonSerialize(JsonNode & dest) const
 	for (const auto & entry : numbers )
 	{
 		JsonNode value;
-		value.Float() = entry;
+		value.Integer() = entry;
 		jsonNumbers.Vector().push_back(value);
 	}
 

+ 10 - 5
lib/mapObjects/CGHeroInstance.cpp

@@ -452,9 +452,8 @@ void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const
 				//Create a new boat for hero
 				NewObject no;
 				no.ID = Obj::BOAT;
-				no.subID = BoatId(EBoatId::CASTLE);
-				no.pos = CGBoat::translatePos(boatPos);
-				
+				no.subID = getBoatType().getNum();
+
 				cb->sendAndApply(&no);
 
 				boatId = cb->getTopObj(boatPos)->id;
@@ -953,8 +952,7 @@ si32 CGHeroInstance::getManaNewTurn() const
 
 BoatId CGHeroInstance::getBoatType() const
 {
-	// hero can only generate boat via "Summon Boat" spell which always create same boat as in Necropolis shipyard
-	return EBoatId::NECROPOLIS;
+	return BoatId(VLC->townh->getById(type->heroClass->faction)->getBoatType());
 }
 
 void CGHeroInstance::getOutOffsets(std::vector<int3> &offsets) const
@@ -1105,6 +1103,13 @@ int CGHeroInstance::maxSpellLevel() const
 void CGHeroInstance::deserializationFix()
 {
 	artDeserializationFix(this);
+	boatDeserializationFix();
+}
+
+void CGHeroInstance::boatDeserializationFix()
+{
+	if (boat)
+		attachTo(const_cast<CGBoat&>(*boat));
 }
 
 CBonusSystemNode * CGHeroInstance::whereShouldBeAttachedOnSiege(const bool isBattleOutsideTown) const

+ 1 - 0
lib/mapObjects/CGHeroInstance.h

@@ -274,6 +274,7 @@ public:
 	void getCastDescription(const spells::Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const override;
 	void spendMana(ServerCallback * server, const int spellCost) const override;
 
+	void boatDeserializationFix();
 	void deserializationFix();
 
 	void initObj(CRandomGenerator & rand) override;

+ 1 - 1
lib/mapObjects/CGTownInstance.cpp

@@ -682,7 +682,7 @@ void CGTownInstance::clearArmy() const
 
 BoatId CGTownInstance::getBoatType() const
 {
-	return town->shipyardBoat;
+	return town->faction->boatType;
 }
 
 int CGTownInstance::getMarketEfficiency() const

+ 0 - 5
lib/mapObjects/MiscObjects.cpp

@@ -1294,11 +1294,6 @@ CGBoat::CGBoat()
 	layer = EPathfindingLayer::EEPathfindingLayer::SAIL;
 }
 
-void CGBoat::initObj(CRandomGenerator & rand)
-{
-	hero = nullptr;
-}
-
 int3 CGBoat::translatePos(const int3& pos, bool reverse /* = false */)
 {
 	//pos - offset we want to place the boat at the map

+ 0 - 1
lib/mapObjects/MiscObjects.h

@@ -359,7 +359,6 @@ public:
 	std::array<std::string, PlayerColor::PLAYER_LIMIT_I> flagAnimations;
 
 	CGBoat();
-	void initObj(CRandomGenerator & rand) override;
 	static int3 translatePos(const int3 &pos, bool reverse = false);
 
 	template <typename Handler> void serialize(Handler &h, const int version)

+ 1 - 1
server/CGameHandler.cpp

@@ -4428,7 +4428,7 @@ bool CGameHandler::hireHero(const CGObjectInstance *obj, ui8 hid, PlayerColor pl
 		//Create a new boat for hero
 		NewObject no;
 		no.ID = Obj::BOAT;
-		no.subID = BoatId(EBoatId::CASTLE);
+		no.subID = nh->getBoatType().getNum();
 		no.pos = hr.tile + int3(1,0,0);
 		sendAndApply(&no);