From 3913b8e98c020973b9a3d192717329fd969673b4 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Jun 2023 22:01:18 +0300 Subject: [PATCH] Heroes placed on water in map will be automatically given boat --- config/factions/castle.json | 2 +- config/factions/conflux.json | 2 +- config/factions/dungeon.json | 1 + config/factions/fortress.json | 2 +- config/factions/inferno.json | 1 + config/factions/necropolis.json | 2 +- config/factions/rampart.json | 1 + config/factions/stronghold.json | 1 + config/factions/tower.json | 1 + include/vcmi/Faction.h | 2 ++ lib/CGameState.cpp | 29 +++++++++++++++++++++ lib/CTownHandler.cpp | 43 ++++++++++--------------------- lib/CTownHandler.h | 10 ++++--- lib/GameConstants.h | 2 +- lib/MetaString.cpp | 4 +-- lib/mapObjects/CGHeroInstance.cpp | 15 +++++++---- lib/mapObjects/CGHeroInstance.h | 1 + lib/mapObjects/CGTownInstance.cpp | 2 +- lib/mapObjects/MiscObjects.cpp | 5 ---- lib/mapObjects/MiscObjects.h | 1 - server/CGameHandler.cpp | 2 +- 21 files changed, 77 insertions(+), 52 deletions(-) diff --git a/config/factions/castle.json b/config/factions/castle.json index fddce6cc9..5026120a1 100644 --- a/config/factions/castle.json +++ b/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" : diff --git a/config/factions/conflux.json b/config/factions/conflux.json index 24fa448ff..fd431f6b1 100644 --- a/config/factions/conflux.json +++ b/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" : { diff --git a/config/factions/dungeon.json b/config/factions/dungeon.json index 3a6a354c3..ac347b9b1 100644 --- a/config/factions/dungeon.json +++ b/config/factions/dungeon.json @@ -10,6 +10,7 @@ "120px" : "TPCASDUN", "130px" : "CRBKGDUN" }, + "boat" : "boatCastle", "puzzleMap" : { "prefix" : "PUZDUN", diff --git a/config/factions/fortress.json b/config/factions/fortress.json index 61001da09..403d840ca 100644 --- a/config/factions/fortress.json +++ b/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" : diff --git a/config/factions/inferno.json b/config/factions/inferno.json index 03b767a79..9a8c86912 100644 --- a/config/factions/inferno.json +++ b/config/factions/inferno.json @@ -10,6 +10,7 @@ "120px" : "TPCASINF", "130px" : "CRBKGINF" }, + "boat" : "boatCastle", "puzzleMap" : { "prefix" : "PUZINF", diff --git a/config/factions/necropolis.json b/config/factions/necropolis.json index 383a04115..29951540d 100644 --- a/config/factions/necropolis.json +++ b/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" : diff --git a/config/factions/rampart.json b/config/factions/rampart.json index 40f633e53..8893fe56a 100644 --- a/config/factions/rampart.json +++ b/config/factions/rampart.json @@ -9,6 +9,7 @@ "120px" : "TPCASRAM", "130px" : "CRBKGRAM" }, + "boat" : "boatCastle", "puzzleMap" : { "prefix" : "PUZRAM", diff --git a/config/factions/stronghold.json b/config/factions/stronghold.json index 97e9e08a5..eed97bb94 100644 --- a/config/factions/stronghold.json +++ b/config/factions/stronghold.json @@ -9,6 +9,7 @@ "120px" : "TPCASSTR", "130px" : "CRBKGSTR" }, + "boat" : "boatCastle", "puzzleMap" : { "prefix" : "PUZSTR", diff --git a/config/factions/tower.json b/config/factions/tower.json index 5b1438102..572a9e156 100644 --- a/config/factions/tower.json +++ b/config/factions/tower.json @@ -9,6 +9,7 @@ "120px" : "TPCASTOW", "130px" : "CRBKGTOW" }, + "boat" : "boatCastle", "puzzleMap" : { "prefix" : "PUZTOW", diff --git a/include/vcmi/Faction.h b/include/vcmi/Faction.h index a89a82f19..8d98ae075 100644 --- a/include/vcmi/Faction.h +++ b/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, public INativeTerrainProvider { public: virtual bool hasTown() const = 0; virtual EAlignment getAlignment() const = 0; + virtual EBoatId getBoatType() const = 0; }; VCMI_LIB_NAMESPACE_END diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 51b92636a..7c6273741 100644 --- a/lib/CGameState.cpp +++ b/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(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(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() diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 018388dbc..d2a03480b 100644 --- a/lib/CTownHandler.cpp +++ b/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(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() diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 48e1f2bb5..49cdc0c97 100644 --- a/lib/CTownHandler.h +++ b/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; diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 4192ba1eb..34a96d3ce 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -1282,7 +1282,7 @@ class BattleField : public BaseForID DLL_LINKAGE static BattleField fromString(const std::string & identifier); }; -enum class EBoatId +enum class EBoatId : int32_t { NONE = -1, NECROPOLIS = 0, diff --git a/lib/MetaString.cpp b/lib/MetaString.cpp index c566ba45c..78329a899 100644 --- a/lib/MetaString.cpp +++ b/lib/MetaString.cpp @@ -323,7 +323,7 @@ void MetaString::jsonSerialize(JsonNode & dest) const for (const auto & entry : localStrings ) { JsonNode value; - value.Float() = static_cast(entry.first) * 10000 + entry.second; + value.Integer() = static_cast(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); } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 826aa5590..3bf7d5f80 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/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 &offsets) const @@ -1105,6 +1103,13 @@ int CGHeroInstance::maxSpellLevel() const void CGHeroInstance::deserializationFix() { artDeserializationFix(this); + boatDeserializationFix(); +} + +void CGHeroInstance::boatDeserializationFix() +{ + if (boat) + attachTo(const_cast(*boat)); } CBonusSystemNode * CGHeroInstance::whereShouldBeAttachedOnSiege(const bool isBattleOutsideTown) const diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 08d6f1cea..fb4438fc1 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -274,6 +274,7 @@ public: void getCastDescription(const spells::Spell * spell, const std::vector & attacked, MetaString & text) const override; void spendMana(ServerCallback * server, const int spellCost) const override; + void boatDeserializationFix(); void deserializationFix(); void initObj(CRandomGenerator & rand) override; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index dc45116d8..bab2be44e 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/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 diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 2930887b1..403c29158 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/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 diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index 5da7e464b..972f52c1f 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -359,7 +359,6 @@ public: std::array flagAnimations; CGBoat(); - void initObj(CRandomGenerator & rand) override; static int3 translatePos(const int3 &pos, bool reverse = false); template void serialize(Handler &h, const int version) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 16eb9b532..657dbc9d6 100644 --- a/server/CGameHandler.cpp +++ b/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);