From 421bbab1cc822c964fe22498d2a45b17254d8fb2 Mon Sep 17 00:00:00 2001 From: nordsoft Date: Wed, 26 Apr 2023 22:55:56 +0400 Subject: [PATCH] Split CGTownInstance file --- cmake_modules/VCMI_lib.cmake | 4 + lib/mapObjects/CGDwelling.cpp | 434 +++++++++++++++++++ lib/mapObjects/CGDwelling.h | 94 +++++ lib/mapObjects/CGTownBuilding.cpp | 288 +++++++++++++ lib/mapObjects/CGTownBuilding.h | 107 +++++ lib/mapObjects/CGTownInstance.cpp | 673 ------------------------------ lib/mapObjects/CGTownInstance.h | 166 +------- lib/mapObjects/MapObjects.h | 2 + 8 files changed, 931 insertions(+), 837 deletions(-) create mode 100644 lib/mapObjects/CGDwelling.cpp create mode 100644 lib/mapObjects/CGDwelling.h create mode 100644 lib/mapObjects/CGTownBuilding.cpp create mode 100644 lib/mapObjects/CGTownBuilding.h diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 32db2ec97..494db195d 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -55,9 +55,11 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/mapObjects/CArmedInstance.cpp ${MAIN_LIB_DIR}/mapObjects/CBank.cpp + ${MAIN_LIB_DIR}/mapObjects/CGDwelling.cpp ${MAIN_LIB_DIR}/mapObjects/CGHeroInstance.cpp ${MAIN_LIB_DIR}/mapObjects/CGMarket.cpp ${MAIN_LIB_DIR}/mapObjects/CGPandoraBox.cpp + ${MAIN_LIB_DIR}/mapObjects/CGTownBuilding.cpp ${MAIN_LIB_DIR}/mapObjects/CGTownInstance.cpp ${MAIN_LIB_DIR}/mapObjects/CObjectClassesHandler.cpp ${MAIN_LIB_DIR}/mapObjects/CObjectHandler.cpp @@ -325,9 +327,11 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/mapObjects/CArmedInstance.h ${MAIN_LIB_DIR}/mapObjects/CBank.h + ${MAIN_LIB_DIR}/mapObjects/CGDwelling.cpp ${MAIN_LIB_DIR}/mapObjects/CGHeroInstance.h ${MAIN_LIB_DIR}/mapObjects/CGMarket.h ${MAIN_LIB_DIR}/mapObjects/CGPandoraBox.h + ${MAIN_LIB_DIR}/mapObjects/CGTownBuilding.h ${MAIN_LIB_DIR}/mapObjects/CGTownInstance.h ${MAIN_LIB_DIR}/mapObjects/CObjectClassesHandler.h ${MAIN_LIB_DIR}/mapObjects/CObjectHandler.h diff --git a/lib/mapObjects/CGDwelling.cpp b/lib/mapObjects/CGDwelling.cpp new file mode 100644 index 000000000..d84c51bc4 --- /dev/null +++ b/lib/mapObjects/CGDwelling.cpp @@ -0,0 +1,434 @@ +/* + * CGDwelling.cpp, 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 + * + */ + +#include "StdInc.h" +#include "CGDwelling.h" +#include "CObjectClassesHandler.h" +#include "../serializer/JsonSerializeFormat.h" +#include "../CTownHandler.h" +#include "../IGameCallback.h" +#include "../CGameState.h" +#include "../CPlayerState.h" +#include "../NetPacks.h" +#include "../GameSettings.h" +#include "../CConfigHandler.h" + +VCMI_LIB_NAMESPACE_BEGIN + +CSpecObjInfo::CSpecObjInfo(): + owner(nullptr) +{ + +} + +void CCreGenAsCastleInfo::serializeJson(JsonSerializeFormat & handler) +{ + handler.serializeString("sameAsTown", instanceId); + + if(!handler.saving) + { + asCastle = !instanceId.empty(); + allowedFactions.clear(); + } + + if(!asCastle) + { + std::vector standard; + standard.resize(VLC->townh->size(), true); + + JsonSerializeFormat::LIC allowedLIC(standard, &FactionID::decode, &FactionID::encode); + allowedLIC.any = allowedFactions; + + handler.serializeLIC("allowedFactions", allowedLIC); + + if(!handler.saving) + { + allowedFactions = allowedLIC.any; + } + } +} + +void CCreGenLeveledInfo::serializeJson(JsonSerializeFormat & handler) +{ + handler.serializeInt("minLevel", minLevel, static_cast(1)); + handler.serializeInt("maxLevel", maxLevel, static_cast(7)); + + if(!handler.saving) + { + //todo: safely allow any level > 7 + vstd::abetween(minLevel, 1, 7); + vstd::abetween(maxLevel, minLevel, 7); + } +} + +void CCreGenLeveledCastleInfo::serializeJson(JsonSerializeFormat & handler) +{ + CCreGenAsCastleInfo::serializeJson(handler); + CCreGenLeveledInfo::serializeJson(handler); +} + +CGDwelling::CGDwelling() + : info(nullptr) +{ +} + +CGDwelling::~CGDwelling() +{ + vstd::clear_pointer(info); +} + +void CGDwelling::initObj(CRandomGenerator & rand) +{ + switch(ID) + { + case Obj::CREATURE_GENERATOR1: + case Obj::CREATURE_GENERATOR4: + { + VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand); + + if (getOwner() != PlayerColor::NEUTRAL) + cb->gameState()->players[getOwner()].dwellings.emplace_back(this); + + assert(!creatures.empty()); + assert(!creatures[0].second.empty()); + break; + } + case Obj::REFUGEE_CAMP: + //is handled within newturn func + break; + + case Obj::WAR_MACHINE_FACTORY: + creatures.resize(3); + creatures[0].second.emplace_back(CreatureID::BALLISTA); + creatures[1].second.emplace_back(CreatureID::FIRST_AID_TENT); + creatures[2].second.emplace_back(CreatureID::AMMO_CART); + break; + + default: + assert(0); + break; + } +} + +void CGDwelling::initRandomObjectInfo() +{ + vstd::clear_pointer(info); + switch(ID) + { + case Obj::RANDOM_DWELLING: info = new CCreGenLeveledCastleInfo(); + break; + case Obj::RANDOM_DWELLING_LVL: info = new CCreGenAsCastleInfo(); + break; + case Obj::RANDOM_DWELLING_FACTION: info = new CCreGenLeveledInfo(); + break; + } + + if(info) + info->owner = this; +} + +void CGDwelling::setPropertyDer(ui8 what, ui32 val) +{ + switch (what) + { + case ObjProperty::OWNER: //change owner + if (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::CREATURE_GENERATOR2 + || ID == Obj::CREATURE_GENERATOR3 || ID == Obj::CREATURE_GENERATOR4) + { + if (tempOwner != PlayerColor::NEUTRAL) + { + std::vector >* dwellings = &cb->gameState()->players[tempOwner].dwellings; + dwellings->erase (std::find(dwellings->begin(), dwellings->end(), this)); + } + if (PlayerColor(val) != PlayerColor::NEUTRAL) //can new owner be neutral? + cb->gameState()->players[PlayerColor(val)].dwellings.emplace_back(this); + } + break; + case ObjProperty::AVAILABLE_CREATURE: + creatures.resize(1); + creatures[0].second.resize(1); + creatures[0].second[0] = CreatureID(val); + break; + } +} + +void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const +{ + if(ID == Obj::REFUGEE_CAMP && !creatures[0].first) //Refugee Camp, no available cres + { + InfoWindow iw; + iw.type = EInfoWindowMode::AUTO; + iw.player = h->tempOwner; + iw.text.addTxt(MetaString::ADVOB_TXT, 44); //{%s} \n\n The camp is deserted. Perhaps you should try next week. + iw.text.addReplacement(MetaString::OBJ_NAMES, ID); + cb->sendAndApply(&iw); + return; + } + + PlayerRelations::PlayerRelations relations = cb->gameState()->getPlayerRelations( h->tempOwner, tempOwner ); + + if ( relations == PlayerRelations::ALLIES ) + return;//do not allow recruiting or capturing + + if( !relations && stacksCount() > 0) //object is guarded, owned by enemy + { + BlockingDialog bd(true,false); + bd.player = h->tempOwner; + bd.text.addTxt(MetaString::GENERAL_TXT, 421); //Much to your dismay, the %s is guarded by %s %s. Do you wish to fight the guards? + bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); + if(settings["gameTweaks"]["numericCreaturesQuantities"].Bool()) + bd.text.addReplacement(CCreature::getQuantityRangeStringForId(Slots().begin()->second->getQuantityID())); + else + bd.text.addReplacement(MetaString::ARRAY_TXT, 173 + (int)Slots().begin()->second->getQuantityID()*3); + bd.text.addReplacement(*Slots().begin()->second); + cb->showBlockingDialog(&bd); + return; + } + + // TODO this shouldn't be hardcoded + if(!relations && ID != Obj::WAR_MACHINE_FACTORY && ID != Obj::REFUGEE_CAMP) + { + cb->setOwner(this, h->tempOwner); + } + + BlockingDialog bd (true,false); + bd.player = h->tempOwner; + if(ID == Obj::CREATURE_GENERATOR1 || ID == Obj::CREATURE_GENERATOR4) + { + bd.text.addTxt(MetaString::ADVOB_TXT, ID == Obj::CREATURE_GENERATOR1 ? 35 : 36); //{%s} Would you like to recruit %s? / {%s} Would you like to recruit %s, %s, %s, or %s? + bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); + for(const auto & elem : creatures) + bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]); + } + else if(ID == Obj::REFUGEE_CAMP) + { + bd.text.addTxt(MetaString::ADVOB_TXT, 35); //{%s} Would you like to recruit %s? + bd.text.addReplacement(MetaString::OBJ_NAMES, ID); + for(const auto & elem : creatures) + bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]); + } + else if(ID == Obj::WAR_MACHINE_FACTORY) + bd.text.addTxt(MetaString::ADVOB_TXT, 157); //{War Machine Factory} Would you like to purchase War Machines? + else + throw std::runtime_error("Illegal dwelling!"); + + cb->showBlockingDialog(&bd); +} + +void CGDwelling::newTurn(CRandomGenerator & rand) const +{ + if(cb->getDate(Date::DAY_OF_WEEK) != 1) //not first day of week + return; + + //town growths and War Machines Factories are handled separately + if(ID == Obj::TOWN || ID == Obj::WAR_MACHINE_FACTORY) + return; + + if(ID == Obj::REFUGEE_CAMP) //if it's a refugee camp, we need to pick an available creature + { + cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster(rand)); + } + + bool change = false; + + SetAvailableCreatures sac; + sac.creatures = creatures; + sac.tid = id; + for (size_t i = 0; i < creatures.size(); i++) + { + if(!creatures[i].second.empty()) + { + bool creaturesAccumulate = false; + + if (tempOwner.isValidPlayer()) + creaturesAccumulate = VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED); + else + creaturesAccumulate = VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL); + + CCreature *cre = VLC->creh->objects[creatures[i].second[0]]; + TQuantity amount = cre->getGrowth() * (1 + cre->valOfBonuses(Bonus::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(Bonus::CREATURE_GROWTH); + if (creaturesAccumulate && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures + sac.creatures[i].first += amount; + else + sac.creatures[i].first = amount; + change = true; + } + } + + if(change) + cb->sendAndApply(&sac); + + updateGuards(); +} + +void CGDwelling::updateGuards() const +{ + //TODO: store custom guard config and use it + //TODO: store boolean flag for guards + + bool guarded = false; + //default condition - creatures are of level 5 or higher + for (auto creatureEntry : creatures) + { + if (VLC->creatures()->getByIndex(creatureEntry.second.at(0))->getLevel() >= 5 && ID != Obj::REFUGEE_CAMP) + { + guarded = true; + break; + } + } + + if (guarded) + { + for (auto creatureEntry : creatures) + { + const CCreature * crea = VLC->creh->objects[creatureEntry.second.at(0)]; + SlotID slot = getSlotFor(crea->getId()); + + if (hasStackAtSlot(slot)) //stack already exists, overwrite it + { + ChangeStackCount csc; + csc.army = this->id; + csc.slot = slot; + csc.count = crea->getGrowth() * 3; + csc.absoluteValue = true; + cb->sendAndApply(&csc); + } + else //slot is empty, create whole new stack + { + InsertNewStack ns; + ns.army = this->id; + ns.slot = slot; + ns.type = crea->getId(); + ns.count = crea->getGrowth() * 3; + cb->sendAndApply(&ns); + } + } + } +} + +void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const +{ + CreatureID crid = creatures[0].second[0]; + auto *crs = crid.toCreature(); + TQuantity count = creatures[0].first; + + if(crs->getLevel() == 1 && ID != Obj::REFUGEE_CAMP) //first level - creatures are for free + { + if(count) //there are available creatures + { + SlotID slot = h->getSlotFor(crid); + if(!slot.validSlot()) //no available slot + { + InfoWindow iw; + iw.type = EInfoWindowMode::AUTO; + iw.player = h->tempOwner; + iw.text.addTxt(MetaString::GENERAL_TXT, 425);//The %s would join your hero, but there aren't enough provisions to support them. + iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); + cb->showInfoDialog(&iw); + } + else //give creatures + { + SetAvailableCreatures sac; + sac.tid = id; + sac.creatures = creatures; + sac.creatures[0].first = 0; + + + InfoWindow iw; + iw.type = EInfoWindowMode::AUTO; + iw.player = h->tempOwner; + iw.text.addTxt(MetaString::GENERAL_TXT, 423); //%d %s join your army. + iw.text.addReplacement(count); + iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); + + cb->showInfoDialog(&iw); + cb->sendAndApply(&sac); + cb->addToSlot(StackLocation(h, slot), crs, count); + } + } + else //there no creatures + { + InfoWindow iw; + iw.type = EInfoWindowMode::AUTO; + iw.text.addTxt(MetaString::GENERAL_TXT, 422); //There are no %s here to recruit. + iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); + iw.player = h->tempOwner; + cb->sendAndApply(&iw); + } + } + else + { + if(ID == Obj::WAR_MACHINE_FACTORY) //pick available War Machines + { + //there is 1 war machine available to recruit if hero doesn't have one + SetAvailableCreatures sac; + sac.tid = id; + sac.creatures = creatures; + sac.creatures[0].first = !h->getArt(ArtifactPosition::MACH1); //ballista + sac.creatures[1].first = !h->getArt(ArtifactPosition::MACH3); //first aid tent + sac.creatures[2].first = !h->getArt(ArtifactPosition::MACH2); //ammo cart + cb->sendAndApply(&sac); + } + + OpenWindow ow; + ow.id1 = id.getNum(); + ow.id2 = h->id.getNum(); + ow.window = (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::REFUGEE_CAMP) + ? EOpenWindowMode::RECRUITMENT_FIRST + : EOpenWindowMode::RECRUITMENT_ALL; + cb->sendAndApply(&ow); + } +} + +void CGDwelling::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if (result.winner == 0) + { + onHeroVisit(hero); + } +} + +void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + auto relations = cb->getPlayerRelations(getOwner(), hero->getOwner()); + if(stacksCount() > 0 && relations == PlayerRelations::ENEMIES) //guards present + { + if(answer) + cb->startBattleI(hero, this); + } + else if(answer) + { + heroAcceptsCreatures(hero); + } +} + +void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler) +{ + if(!handler.saving) + initRandomObjectInfo(); + + switch (ID) + { + case Obj::WAR_MACHINE_FACTORY: + case Obj::REFUGEE_CAMP: + //do nothing + break; + case Obj::RANDOM_DWELLING: + case Obj::RANDOM_DWELLING_LVL: + case Obj::RANDOM_DWELLING_FACTION: + info->serializeJson(handler); + //fall through + default: + serializeJsonOwner(handler); + break; + } +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CGDwelling.h b/lib/mapObjects/CGDwelling.h new file mode 100644 index 000000000..a84ea88bb --- /dev/null +++ b/lib/mapObjects/CGDwelling.h @@ -0,0 +1,94 @@ +/* + * CGDwelling.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 "CArmedInstance.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class CGDwelling; + +class DLL_LINKAGE CSpecObjInfo +{ +public: + CSpecObjInfo(); + virtual ~CSpecObjInfo() = default; + + virtual void serializeJson(JsonSerializeFormat & handler) = 0; + + const CGDwelling * owner; +}; + +class DLL_LINKAGE CCreGenAsCastleInfo : public virtual CSpecObjInfo +{ +public: + bool asCastle = false; + ui32 identifier = 0;//h3m internal identifier + + std::vector allowedFactions; + + std::string instanceId;//vcmi map instance identifier + void serializeJson(JsonSerializeFormat & handler) override; +}; + +class DLL_LINKAGE CCreGenLeveledInfo : public virtual CSpecObjInfo +{ +public: + ui8 minLevel = 0; + ui8 maxLevel = 7; //minimal and maximal level of creature in dwelling: <1, 7> + + void serializeJson(JsonSerializeFormat & handler) override; +}; + +class DLL_LINKAGE CCreGenLeveledCastleInfo : public CCreGenAsCastleInfo, public CCreGenLeveledInfo +{ +public: + CCreGenLeveledCastleInfo() = default; + void serializeJson(JsonSerializeFormat & handler) override; +}; + + +class DLL_LINKAGE CGDwelling : public CArmedInstance +{ +public: + typedef std::vector > > TCreaturesSet; + + CSpecObjInfo * info; //random dwelling options; not serialized + TCreaturesSet creatures; //creatures[level] -> + + CGDwelling(); + ~CGDwelling() override; + + void initRandomObjectInfo(); +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; + +private: + void initObj(CRandomGenerator & rand) override; + void onHeroVisit(const CGHeroInstance * h) const override; + void newTurn(CRandomGenerator & rand) const override; + void setPropertyDer(ui8 what, ui32 val) override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + void updateGuards() const; + void heroAcceptsCreatures(const CGHeroInstance *h) const; + +public: + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & creatures; + } +}; + + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CGTownBuilding.cpp b/lib/mapObjects/CGTownBuilding.cpp new file mode 100644 index 000000000..da28c2660 --- /dev/null +++ b/lib/mapObjects/CGTownBuilding.cpp @@ -0,0 +1,288 @@ +/* + * CGTownBuilding.cpp, 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 + * + */ + +#include "StdInc.h" +#include "CGTownBuilding.h" +#include "CGTownInstance.h" +#include "../CGeneralTextHandler.h" +#include "../NetPacks.h" +#include "../IGameCallback.h" + +VCMI_LIB_NAMESPACE_BEGIN + +PlayerColor CGTownBuilding::getOwner() const +{ + return town->getOwner(); +} + +int32_t CGTownBuilding::getObjGroupIndex() const +{ + return -1; +} + +int32_t CGTownBuilding::getObjTypeIndex() const +{ + return 0; +} + +int3 CGTownBuilding::visitablePos() const +{ + return town->visitablePos(); +} + +int3 CGTownBuilding::getPosition() const +{ + return town->getPosition(); +} + +std::string CGTownBuilding::getVisitingBonusGreeting() const +{ + auto bonusGreeting = town->getTown()->getGreeting(bType); + + if(!bonusGreeting.empty()) + return bonusGreeting; + + switch(bType) + { + case BuildingSubID::MANA_VORTEX: + bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingManaVortex")); + break; + case BuildingSubID::KNOWLEDGE_VISITING_BONUS: + bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingKnowledge")); + break; + case BuildingSubID::SPELL_POWER_VISITING_BONUS: + bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingSpellPower")); + break; + case BuildingSubID::ATTACK_VISITING_BONUS: + bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingAttack")); + break; + case BuildingSubID::EXPERIENCE_VISITING_BONUS: + bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingExperience")); + break; + case BuildingSubID::DEFENSE_VISITING_BONUS: + bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingDefence")); + break; + } + auto buildingName = town->getTown()->getSpecialBuilding(bType)->getNameTranslated(); + + if(bonusGreeting.empty()) + { + bonusGreeting = "Error: Bonus greeting for '%s' is not localized."; + logGlobal->error("'%s' building of '%s' faction has not localized bonus greeting.", buildingName, town->getTown()->faction->getNameTranslated()); + } + boost::algorithm::replace_first(bonusGreeting, "%s", buildingName); + town->getTown()->setGreeting(bType, bonusGreeting); + return bonusGreeting; +} + +std::string CGTownBuilding::getCustomBonusGreeting(const Bonus & bonus) const +{ + if(bonus.type == Bonus::TOWN_MAGIC_WELL) + { + auto bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingInTownMagicWell")); + auto buildingName = town->getTown()->getSpecialBuilding(bType)->getNameTranslated(); + boost::algorithm::replace_first(bonusGreeting, "%s", buildingName); + return bonusGreeting; + } + auto bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingCustomBonus")); //"%s gives you +%d %s%s" + std::string param; + std::string until; + + if(bonus.type == Bonus::MORALE) + param = VLC->generaltexth->allTexts[384]; + else if(bonus.type == Bonus::LUCK) + param = VLC->generaltexth->allTexts[385]; + + until = bonus.duration == static_cast(Bonus::ONE_BATTLE) + ? VLC->generaltexth->translate("vcmi.townHall.greetingCustomUntil") + : "."; + + boost::format fmt = boost::format(bonusGreeting) % bonus.description % bonus.val % param % until; + std::string greeting = fmt.str(); + return greeting; +} + + +COPWBonus::COPWBonus(const BuildingID & bid, BuildingSubID::EBuildingSubID subId, CGTownInstance * cgTown) +{ + bID = bid; + bType = subId; + town = cgTown; + indexOnTV = static_cast(town->bonusingBuildings.size()); +} + +void COPWBonus::setProperty(ui8 what, ui32 val) +{ + switch (what) + { + case ObjProperty::VISITORS: + visitors.insert(val); + break; + case ObjProperty::STRUCTURE_CLEAR_VISITORS: + visitors.clear(); + break; + } +} + +void COPWBonus::onHeroVisit (const CGHeroInstance * h) const +{ + ObjectInstanceID heroID = h->id; + if(town->hasBuilt(bID)) + { + InfoWindow iw; + iw.player = h->tempOwner; + + switch (this->bType) + { + case BuildingSubID::STABLES: + if(!h->hasBonusFrom(Bonus::OBJECT, Obj::STABLES)) //does not stack with advMap Stables + { + GiveBonus gb; + gb.bonus = Bonus(Bonus::ONE_WEEK, Bonus::MOVEMENT, Bonus::OBJECT, 600, 94, VLC->generaltexth->arraytxt[100], 1); + gb.id = heroID.getNum(); + cb->giveHeroBonus(&gb); + + SetMovePoints mp; + mp.val = 600; + mp.absolute = false; + mp.hid = heroID; + cb->setMovePoints(&mp); + + iw.text << VLC->generaltexth->allTexts[580]; + cb->showInfoDialog(&iw); + } + break; + + case BuildingSubID::MANA_VORTEX: + if(visitors.empty()) + { + if(h->mana < h->manaLimit() * 2) + cb->setManaPoints (heroID, 2 * h->manaLimit()); + //TODO: investigate line below + //cb->setObjProperty (town->id, ObjProperty::VISITED, true); + iw.text << getVisitingBonusGreeting(); + cb->showInfoDialog(&iw); + //extra visit penalty if hero alredy had double mana points (or even more?!) + town->addHeroToStructureVisitors(h, indexOnTV); + } + break; + } + } +} + +CTownBonus::CTownBonus(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * cgTown) +{ + bID = index; + bType = subId; + town = cgTown; + indexOnTV = static_cast(town->bonusingBuildings.size()); +} + +void CTownBonus::setProperty (ui8 what, ui32 val) +{ + if(what == ObjProperty::VISITORS) + visitors.insert(ObjectInstanceID(val)); +} + +void CTownBonus::onHeroVisit (const CGHeroInstance * h) const +{ + ObjectInstanceID heroID = h->id; + if(town->hasBuilt(bID) && visitors.find(heroID) == visitors.end()) + { + si64 val = 0; + InfoWindow iw; + PrimarySkill::PrimarySkill what = PrimarySkill::NONE; + + switch(bType) + { + case BuildingSubID::KNOWLEDGE_VISITING_BONUS: //wall of knowledge + what = PrimarySkill::KNOWLEDGE; + val = 1; + iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 3, 1, 0); + break; + + case BuildingSubID::SPELL_POWER_VISITING_BONUS: //order of fire + what = PrimarySkill::SPELL_POWER; + val = 1; + iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 2, 1, 0); + break; + + case BuildingSubID::ATTACK_VISITING_BONUS: //hall of Valhalla + what = PrimarySkill::ATTACK; + val = 1; + iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 0, 1, 0); + break; + + case BuildingSubID::EXPERIENCE_VISITING_BONUS: //academy of battle scholars + what = PrimarySkill::EXPERIENCE; + val = static_cast(h->calculateXp(1000)); + iw.components.emplace_back(Component::EComponentType::EXPERIENCE, 0, val, 0); + break; + + case BuildingSubID::DEFENSE_VISITING_BONUS: //cage of warlords + what = PrimarySkill::DEFENSE; + val = 1; + iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 1, 1, 0); + break; + + case BuildingSubID::CUSTOM_VISITING_BONUS: + const auto building = town->getTown()->buildings.at(bID); + if(!h->hasBonusFrom(Bonus::TOWN_STRUCTURE, Bonus::getSid32(building->town->faction->getIndex(), building->bid))) + { + const auto & bonuses = building->onVisitBonuses; + applyBonuses(const_cast(h), bonuses); + } + break; + } + + if(what != PrimarySkill::NONE) + { + iw.player = cb->getOwner(heroID); + iw.text << getVisitingBonusGreeting(); + cb->showInfoDialog(&iw); + cb->changePrimSkill (cb->getHero(heroID), what, val); + town->addHeroToStructureVisitors(h, indexOnTV); + } + } +} + +void CTownBonus::applyBonuses(CGHeroInstance * h, const BonusList & bonuses) const +{ + auto addToVisitors = false; + + for(const auto & bonus : bonuses) + { + GiveBonus gb; + InfoWindow iw; + + if(bonus->type == Bonus::TOWN_MAGIC_WELL) + { + if(h->mana >= h->manaLimit()) + return; + cb->setManaPoints(h->id, h->manaLimit()); + bonus->duration = Bonus::ONE_DAY; + } + gb.bonus = * bonus; + gb.id = h->id.getNum(); + cb->giveHeroBonus(&gb); + + if(bonus->duration == Bonus::PERMANENT) + addToVisitors = true; + + iw.player = cb->getOwner(h->id); + iw.text << getCustomBonusGreeting(gb.bonus); + cb->showInfoDialog(&iw); + } + if(addToVisitors) + town->addHeroToStructureVisitors(h, indexOnTV); +} + + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CGTownBuilding.h b/lib/mapObjects/CGTownBuilding.h new file mode 100644 index 000000000..39233f532 --- /dev/null +++ b/lib/mapObjects/CGTownBuilding.h @@ -0,0 +1,107 @@ +/* + * CGTownBuilding.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 "CObjectHandler.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class CGTownInstance; + + +class DLL_LINKAGE CGTownBuilding : public IObjectInterface +{ +///basic class for town structures handled as map objects +public: + si32 indexOnTV = 0; //identifies its index on towns vector + + CGTownInstance * town = nullptr; + + STRONG_INLINE + BuildingSubID::EBuildingSubID getBuildingSubtype() const + { + return bType; + } + + STRONG_INLINE + const BuildingID & getBuildingType() const + { + return bID; + } + + STRONG_INLINE + void setBuildingSubtype(BuildingSubID::EBuildingSubID subId) + { + bType = subId; + } + + PlayerColor getOwner() const override; + int32_t getObjGroupIndex() const override; + int32_t getObjTypeIndex() const override; + + int3 visitablePos() const override; + int3 getPosition() const override; + + template void serialize(Handler &h, const int version) + { + h & bID; + h & indexOnTV; + h & bType; + } + +protected: + BuildingID bID; //from buildig list + BuildingSubID::EBuildingSubID bType = BuildingSubID::NONE; + + std::string getVisitingBonusGreeting() const; + std::string getCustomBonusGreeting(const Bonus & bonus) const; +}; + +class DLL_LINKAGE COPWBonus : public CGTownBuilding +{///used for OPW bonusing structures +public: + std::set visitors; + void setProperty(ui8 what, ui32 val) override; + void onHeroVisit (const CGHeroInstance * h) const override; + + COPWBonus(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * TOWN); + COPWBonus() = default; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & visitors; + } +}; + +class DLL_LINKAGE CTownBonus : public CGTownBuilding +{ +///used for one-time bonusing structures +///feel free to merge inheritance tree +public: + std::set visitors; + void setProperty(ui8 what, ui32 val) override; + void onHeroVisit (const CGHeroInstance * h) const override; + + CTownBonus(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * TOWN); + CTownBonus() = default; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & visitors; + } + +private: + void applyBonuses(CGHeroInstance * h, const BonusList & bonuses) const; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index c16fff7b2..2721d8636 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -17,7 +17,6 @@ #include "../CConfigHandler.h" #include "../CGeneralTextHandler.h" #include "../CModHandler.h" -#include "../GameSettings.h" #include "../IGameCallback.h" #include "../CGameState.h" #include "../mapping/CMap.h" @@ -31,414 +30,6 @@ VCMI_LIB_NAMESPACE_BEGIN std::vector CGTownInstance::merchantArtifacts; std::vector CGTownInstance::universitySkills; -CSpecObjInfo::CSpecObjInfo(): - owner(nullptr) -{ - -} - -void CCreGenAsCastleInfo::serializeJson(JsonSerializeFormat & handler) -{ - handler.serializeString("sameAsTown", instanceId); - - if(!handler.saving) - { - asCastle = !instanceId.empty(); - allowedFactions.clear(); - } - - if(!asCastle) - { - std::vector standard; - standard.resize(VLC->townh->size(), true); - - JsonSerializeFormat::LIC allowedLIC(standard, &FactionID::decode, &FactionID::encode); - allowedLIC.any = allowedFactions; - - handler.serializeLIC("allowedFactions", allowedLIC); - - if(!handler.saving) - { - allowedFactions = allowedLIC.any; - } - } -} - -void CCreGenLeveledInfo::serializeJson(JsonSerializeFormat & handler) -{ - handler.serializeInt("minLevel", minLevel, static_cast(1)); - handler.serializeInt("maxLevel", maxLevel, static_cast(7)); - - if(!handler.saving) - { - //todo: safely allow any level > 7 - vstd::abetween(minLevel, 1, 7); - vstd::abetween(maxLevel, minLevel, 7); - } -} - -void CCreGenLeveledCastleInfo::serializeJson(JsonSerializeFormat & handler) -{ - CCreGenAsCastleInfo::serializeJson(handler); - CCreGenLeveledInfo::serializeJson(handler); -} - -CGDwelling::CGDwelling() - : info(nullptr) -{ -} - -CGDwelling::~CGDwelling() -{ - vstd::clear_pointer(info); -} - -void CGDwelling::initObj(CRandomGenerator & rand) -{ - switch(ID) - { - case Obj::CREATURE_GENERATOR1: - case Obj::CREATURE_GENERATOR4: - { - VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand); - - if (getOwner() != PlayerColor::NEUTRAL) - cb->gameState()->players[getOwner()].dwellings.emplace_back(this); - - assert(!creatures.empty()); - assert(!creatures[0].second.empty()); - break; - } - case Obj::REFUGEE_CAMP: - //is handled within newturn func - break; - - case Obj::WAR_MACHINE_FACTORY: - creatures.resize(3); - creatures[0].second.emplace_back(CreatureID::BALLISTA); - creatures[1].second.emplace_back(CreatureID::FIRST_AID_TENT); - creatures[2].second.emplace_back(CreatureID::AMMO_CART); - break; - - default: - assert(0); - break; - } -} - -void CGDwelling::initRandomObjectInfo() -{ - vstd::clear_pointer(info); - switch(ID) - { - case Obj::RANDOM_DWELLING: info = new CCreGenLeveledCastleInfo(); - break; - case Obj::RANDOM_DWELLING_LVL: info = new CCreGenAsCastleInfo(); - break; - case Obj::RANDOM_DWELLING_FACTION: info = new CCreGenLeveledInfo(); - break; - } - - if(info) - info->owner = this; -} - -void CGDwelling::setPropertyDer(ui8 what, ui32 val) -{ - switch (what) - { - case ObjProperty::OWNER: //change owner - if (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::CREATURE_GENERATOR2 - || ID == Obj::CREATURE_GENERATOR3 || ID == Obj::CREATURE_GENERATOR4) - { - if (tempOwner != PlayerColor::NEUTRAL) - { - std::vector >* dwellings = &cb->gameState()->players[tempOwner].dwellings; - dwellings->erase (std::find(dwellings->begin(), dwellings->end(), this)); - } - if (PlayerColor(val) != PlayerColor::NEUTRAL) //can new owner be neutral? - cb->gameState()->players[PlayerColor(val)].dwellings.emplace_back(this); - } - break; - case ObjProperty::AVAILABLE_CREATURE: - creatures.resize(1); - creatures[0].second.resize(1); - creatures[0].second[0] = CreatureID(val); - break; - } -} - -void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const -{ - if(ID == Obj::REFUGEE_CAMP && !creatures[0].first) //Refugee Camp, no available cres - { - InfoWindow iw; - iw.type = EInfoWindowMode::AUTO; - iw.player = h->tempOwner; - iw.text.addTxt(MetaString::ADVOB_TXT, 44); //{%s} \n\n The camp is deserted. Perhaps you should try next week. - iw.text.addReplacement(MetaString::OBJ_NAMES, ID); - cb->sendAndApply(&iw); - return; - } - - PlayerRelations::PlayerRelations relations = cb->gameState()->getPlayerRelations( h->tempOwner, tempOwner ); - - if ( relations == PlayerRelations::ALLIES ) - return;//do not allow recruiting or capturing - - if( !relations && stacksCount() > 0) //object is guarded, owned by enemy - { - BlockingDialog bd(true,false); - bd.player = h->tempOwner; - bd.text.addTxt(MetaString::GENERAL_TXT, 421); //Much to your dismay, the %s is guarded by %s %s. Do you wish to fight the guards? - bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); - if(settings["gameTweaks"]["numericCreaturesQuantities"].Bool()) - bd.text.addReplacement(CCreature::getQuantityRangeStringForId(Slots().begin()->second->getQuantityID())); - else - bd.text.addReplacement(MetaString::ARRAY_TXT, 173 + (int)Slots().begin()->second->getQuantityID()*3); - bd.text.addReplacement(*Slots().begin()->second); - cb->showBlockingDialog(&bd); - return; - } - - // TODO this shouldn't be hardcoded - if(!relations && ID != Obj::WAR_MACHINE_FACTORY && ID != Obj::REFUGEE_CAMP) - { - cb->setOwner(this, h->tempOwner); - } - - BlockingDialog bd (true,false); - bd.player = h->tempOwner; - if(ID == Obj::CREATURE_GENERATOR1 || ID == Obj::CREATURE_GENERATOR4) - { - bd.text.addTxt(MetaString::ADVOB_TXT, ID == Obj::CREATURE_GENERATOR1 ? 35 : 36); //{%s} Would you like to recruit %s? / {%s} Would you like to recruit %s, %s, %s, or %s? - bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); - for(const auto & elem : creatures) - bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]); - } - else if(ID == Obj::REFUGEE_CAMP) - { - bd.text.addTxt(MetaString::ADVOB_TXT, 35); //{%s} Would you like to recruit %s? - bd.text.addReplacement(MetaString::OBJ_NAMES, ID); - for(const auto & elem : creatures) - bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]); - } - else if(ID == Obj::WAR_MACHINE_FACTORY) - bd.text.addTxt(MetaString::ADVOB_TXT, 157); //{War Machine Factory} Would you like to purchase War Machines? - else - throw std::runtime_error("Illegal dwelling!"); - - cb->showBlockingDialog(&bd); -} - -void CGDwelling::newTurn(CRandomGenerator & rand) const -{ - if(cb->getDate(Date::DAY_OF_WEEK) != 1) //not first day of week - return; - - //town growths and War Machines Factories are handled separately - if(ID == Obj::TOWN || ID == Obj::WAR_MACHINE_FACTORY) - return; - - if(ID == Obj::REFUGEE_CAMP) //if it's a refugee camp, we need to pick an available creature - { - cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster(rand)); - } - - bool change = false; - - SetAvailableCreatures sac; - sac.creatures = creatures; - sac.tid = id; - for (size_t i = 0; i < creatures.size(); i++) - { - if(!creatures[i].second.empty()) - { - bool creaturesAccumulate = false; - - if (tempOwner.isValidPlayer()) - creaturesAccumulate = VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED); - else - creaturesAccumulate = VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL); - - CCreature *cre = VLC->creh->objects[creatures[i].second[0]]; - TQuantity amount = cre->getGrowth() * (1 + cre->valOfBonuses(Bonus::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(Bonus::CREATURE_GROWTH); - if (creaturesAccumulate && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures - sac.creatures[i].first += amount; - else - sac.creatures[i].first = amount; - change = true; - } - } - - if(change) - cb->sendAndApply(&sac); - - updateGuards(); -} - -void CGDwelling::updateGuards() const -{ - //TODO: store custom guard config and use it - //TODO: store boolean flag for guards - - bool guarded = false; - //default condition - creatures are of level 5 or higher - for (auto creatureEntry : creatures) - { - if (VLC->creatures()->getByIndex(creatureEntry.second.at(0))->getLevel() >= 5 && ID != Obj::REFUGEE_CAMP) - { - guarded = true; - break; - } - } - - if (guarded) - { - for (auto creatureEntry : creatures) - { - const CCreature * crea = VLC->creh->objects[creatureEntry.second.at(0)]; - SlotID slot = getSlotFor(crea->getId()); - - if (hasStackAtSlot(slot)) //stack already exists, overwrite it - { - ChangeStackCount csc; - csc.army = this->id; - csc.slot = slot; - csc.count = crea->getGrowth() * 3; - csc.absoluteValue = true; - cb->sendAndApply(&csc); - } - else //slot is empty, create whole new stack - { - InsertNewStack ns; - ns.army = this->id; - ns.slot = slot; - ns.type = crea->getId(); - ns.count = crea->getGrowth() * 3; - cb->sendAndApply(&ns); - } - } - } -} - -void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const -{ - CreatureID crid = creatures[0].second[0]; - auto *crs = crid.toCreature(); - TQuantity count = creatures[0].first; - - if(crs->getLevel() == 1 && ID != Obj::REFUGEE_CAMP) //first level - creatures are for free - { - if(count) //there are available creatures - { - SlotID slot = h->getSlotFor(crid); - if(!slot.validSlot()) //no available slot - { - InfoWindow iw; - iw.type = EInfoWindowMode::AUTO; - iw.player = h->tempOwner; - iw.text.addTxt(MetaString::GENERAL_TXT, 425);//The %s would join your hero, but there aren't enough provisions to support them. - iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); - cb->showInfoDialog(&iw); - } - else //give creatures - { - SetAvailableCreatures sac; - sac.tid = id; - sac.creatures = creatures; - sac.creatures[0].first = 0; - - - InfoWindow iw; - iw.type = EInfoWindowMode::AUTO; - iw.player = h->tempOwner; - iw.text.addTxt(MetaString::GENERAL_TXT, 423); //%d %s join your army. - iw.text.addReplacement(count); - iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); - - cb->showInfoDialog(&iw); - cb->sendAndApply(&sac); - cb->addToSlot(StackLocation(h, slot), crs, count); - } - } - else //there no creatures - { - InfoWindow iw; - iw.type = EInfoWindowMode::AUTO; - iw.text.addTxt(MetaString::GENERAL_TXT, 422); //There are no %s here to recruit. - iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); - iw.player = h->tempOwner; - cb->sendAndApply(&iw); - } - } - else - { - if(ID == Obj::WAR_MACHINE_FACTORY) //pick available War Machines - { - //there is 1 war machine available to recruit if hero doesn't have one - SetAvailableCreatures sac; - sac.tid = id; - sac.creatures = creatures; - sac.creatures[0].first = !h->getArt(ArtifactPosition::MACH1); //ballista - sac.creatures[1].first = !h->getArt(ArtifactPosition::MACH3); //first aid tent - sac.creatures[2].first = !h->getArt(ArtifactPosition::MACH2); //ammo cart - cb->sendAndApply(&sac); - } - - OpenWindow ow; - ow.id1 = id.getNum(); - ow.id2 = h->id.getNum(); - ow.window = (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::REFUGEE_CAMP) - ? EOpenWindowMode::RECRUITMENT_FIRST - : EOpenWindowMode::RECRUITMENT_ALL; - cb->sendAndApply(&ow); - } -} - -void CGDwelling::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if (result.winner == 0) - { - onHeroVisit(hero); - } -} - -void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - auto relations = cb->getPlayerRelations(getOwner(), hero->getOwner()); - if(stacksCount() > 0 && relations == PlayerRelations::ENEMIES) //guards present - { - if(answer) - cb->startBattleI(hero, this); - } - else if(answer) - { - heroAcceptsCreatures(hero); - } -} - -void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler) -{ - if(!handler.saving) - initRandomObjectInfo(); - - switch (ID) - { - case Obj::WAR_MACHINE_FACTORY: - case Obj::REFUGEE_CAMP: - //do nothing - break; - case Obj::RANDOM_DWELLING: - case Obj::RANDOM_DWELLING_LVL: - case Obj::RANDOM_DWELLING_FACTION: - info->serializeJson(handler); - //fall through - default: - serializeJsonOwner(handler); - break; - } -} int CGTownInstance::getSightRadius() const //returns sight distance { @@ -1622,204 +1213,6 @@ TerrainId CGTownInstance::getNativeTerrain() const return town->faction->getNativeTerrain(); } -PlayerColor CGTownBuilding::getOwner() const -{ - return town->getOwner(); -} - -int32_t CGTownBuilding::getObjGroupIndex() const -{ - return -1; -} - -int32_t CGTownBuilding::getObjTypeIndex() const -{ - return 0; -} - -int3 CGTownBuilding::visitablePos() const -{ - return town->visitablePos(); -} - -int3 CGTownBuilding::getPosition() const -{ - return town->getPosition(); -} - -COPWBonus::COPWBonus(const BuildingID & bid, BuildingSubID::EBuildingSubID subId, CGTownInstance * cgTown) -{ - bID = bid; - bType = subId; - town = cgTown; - indexOnTV = static_cast(town->bonusingBuildings.size()); -} - -void COPWBonus::setProperty(ui8 what, ui32 val) -{ - switch (what) - { - case ObjProperty::VISITORS: - visitors.insert(val); - break; - case ObjProperty::STRUCTURE_CLEAR_VISITORS: - visitors.clear(); - break; - } -} - -void COPWBonus::onHeroVisit (const CGHeroInstance * h) const -{ - ObjectInstanceID heroID = h->id; - if(town->hasBuilt(bID)) - { - InfoWindow iw; - iw.player = h->tempOwner; - - switch (this->bType) - { - case BuildingSubID::STABLES: - if(!h->hasBonusFrom(Bonus::OBJECT, Obj::STABLES)) //does not stack with advMap Stables - { - GiveBonus gb; - gb.bonus = Bonus(Bonus::ONE_WEEK, Bonus::MOVEMENT, Bonus::OBJECT, 600, 94, VLC->generaltexth->arraytxt[100], 1); - gb.id = heroID.getNum(); - cb->giveHeroBonus(&gb); - - SetMovePoints mp; - mp.val = 600; - mp.absolute = false; - mp.hid = heroID; - cb->setMovePoints(&mp); - - iw.text << VLC->generaltexth->allTexts[580]; - cb->showInfoDialog(&iw); - } - break; - - case BuildingSubID::MANA_VORTEX: - if(visitors.empty()) - { - if(h->mana < h->manaLimit() * 2) - cb->setManaPoints (heroID, 2 * h->manaLimit()); - //TODO: investigate line below - //cb->setObjProperty (town->id, ObjProperty::VISITED, true); - iw.text << getVisitingBonusGreeting(); - cb->showInfoDialog(&iw); - //extra visit penalty if hero alredy had double mana points (or even more?!) - town->addHeroToStructureVisitors(h, indexOnTV); - } - break; - } - } -} -CTownBonus::CTownBonus(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * cgTown) -{ - bID = index; - bType = subId; - town = cgTown; - indexOnTV = static_cast(town->bonusingBuildings.size()); -} - -void CTownBonus::setProperty (ui8 what, ui32 val) -{ - if(what == ObjProperty::VISITORS) - visitors.insert(ObjectInstanceID(val)); -} - -void CTownBonus::onHeroVisit (const CGHeroInstance * h) const -{ - ObjectInstanceID heroID = h->id; - if(town->hasBuilt(bID) && visitors.find(heroID) == visitors.end()) - { - si64 val = 0; - InfoWindow iw; - PrimarySkill::PrimarySkill what = PrimarySkill::NONE; - - switch(bType) - { - case BuildingSubID::KNOWLEDGE_VISITING_BONUS: //wall of knowledge - what = PrimarySkill::KNOWLEDGE; - val = 1; - iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 3, 1, 0); - break; - - case BuildingSubID::SPELL_POWER_VISITING_BONUS: //order of fire - what = PrimarySkill::SPELL_POWER; - val = 1; - iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 2, 1, 0); - break; - - case BuildingSubID::ATTACK_VISITING_BONUS: //hall of Valhalla - what = PrimarySkill::ATTACK; - val = 1; - iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 0, 1, 0); - break; - - case BuildingSubID::EXPERIENCE_VISITING_BONUS: //academy of battle scholars - what = PrimarySkill::EXPERIENCE; - val = static_cast(h->calculateXp(1000)); - iw.components.emplace_back(Component::EComponentType::EXPERIENCE, 0, val, 0); - break; - - case BuildingSubID::DEFENSE_VISITING_BONUS: //cage of warlords - what = PrimarySkill::DEFENSE; - val = 1; - iw.components.emplace_back(Component::EComponentType::PRIM_SKILL, 1, 1, 0); - break; - - case BuildingSubID::CUSTOM_VISITING_BONUS: - const auto building = town->getTown()->buildings.at(bID); - if(!h->hasBonusFrom(Bonus::TOWN_STRUCTURE, Bonus::getSid32(building->town->faction->getIndex(), building->bid))) - { - const auto & bonuses = building->onVisitBonuses; - applyBonuses(const_cast(h), bonuses); - } - break; - } - - if(what != PrimarySkill::NONE) - { - iw.player = cb->getOwner(heroID); - iw.text << getVisitingBonusGreeting(); - cb->showInfoDialog(&iw); - cb->changePrimSkill (cb->getHero(heroID), what, val); - town->addHeroToStructureVisitors(h, indexOnTV); - } - } -} - -void CTownBonus::applyBonuses(CGHeroInstance * h, const BonusList & bonuses) const -{ - auto addToVisitors = false; - - for(const auto & bonus : bonuses) - { - GiveBonus gb; - InfoWindow iw; - - if(bonus->type == Bonus::TOWN_MAGIC_WELL) - { - if(h->mana >= h->manaLimit()) - return; - cb->setManaPoints(h->id, h->manaLimit()); - bonus->duration = Bonus::ONE_DAY; - } - gb.bonus = * bonus; - gb.id = h->id.getNum(); - cb->giveHeroBonus(&gb); - - if(bonus->duration == Bonus::PERMANENT) - addToVisitors = true; - - iw.player = cb->getOwner(h->id); - iw.text << getCustomBonusGreeting(gb.bonus); - cb->showInfoDialog(&iw); - } - if(addToVisitors) - town->addHeroToStructureVisitors(h, indexOnTV); -} - GrowthInfo::Entry::Entry(const std::string &format, int _count) : count(_count) { @@ -1851,71 +1244,5 @@ int GrowthInfo::totalGrowth() const return ret; } -std::string CGTownBuilding::getVisitingBonusGreeting() const -{ - auto bonusGreeting = town->getTown()->getGreeting(bType); - - if(!bonusGreeting.empty()) - return bonusGreeting; - - switch(bType) - { - case BuildingSubID::MANA_VORTEX: - bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingManaVortex")); - break; - case BuildingSubID::KNOWLEDGE_VISITING_BONUS: - bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingKnowledge")); - break; - case BuildingSubID::SPELL_POWER_VISITING_BONUS: - bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingSpellPower")); - break; - case BuildingSubID::ATTACK_VISITING_BONUS: - bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingAttack")); - break; - case BuildingSubID::EXPERIENCE_VISITING_BONUS: - bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingExperience")); - break; - case BuildingSubID::DEFENSE_VISITING_BONUS: - bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingDefence")); - break; - } - auto buildingName = town->getTown()->getSpecialBuilding(bType)->getNameTranslated(); - - if(bonusGreeting.empty()) - { - bonusGreeting = "Error: Bonus greeting for '%s' is not localized."; - logGlobal->error("'%s' building of '%s' faction has not localized bonus greeting.", buildingName, town->getTown()->faction->getNameTranslated()); - } - boost::algorithm::replace_first(bonusGreeting, "%s", buildingName); - town->getTown()->setGreeting(bType, bonusGreeting); - return bonusGreeting; -} - -std::string CGTownBuilding::getCustomBonusGreeting(const Bonus & bonus) const -{ - if(bonus.type == Bonus::TOWN_MAGIC_WELL) - { - auto bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingInTownMagicWell")); - auto buildingName = town->getTown()->getSpecialBuilding(bType)->getNameTranslated(); - boost::algorithm::replace_first(bonusGreeting, "%s", buildingName); - return bonusGreeting; - } - auto bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingCustomBonus")); //"%s gives you +%d %s%s" - std::string param; - std::string until; - - if(bonus.type == Bonus::MORALE) - param = VLC->generaltexth->allTexts[384]; - else if(bonus.type == Bonus::LUCK) - param = VLC->generaltexth->allTexts[385]; - - until = bonus.duration == static_cast(Bonus::ONE_BATTLE) - ? VLC->generaltexth->translate("vcmi.townHall.greetingCustomUntil") - : "."; - - boost::format fmt = boost::format(bonusGreeting) % bonus.description % bonus.val % param % until; - std::string greeting = fmt.str(); - return greeting; -} VCMI_LIB_NAMESPACE_END diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index d66ccbcf1..1a8758730 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -12,174 +12,16 @@ #include "CObjectHandler.h" #include "CGMarket.h" // For IMarket interface #include "CArmedInstance.h" +#include "CGDwelling.h" +#include "CGTownBuilding.h" #include "../CTownHandler.h" // For CTown VCMI_LIB_NAMESPACE_BEGIN class CCastleEvent; -class CGTownInstance; -class CGDwelling; struct DamageRange; -class DLL_LINKAGE CSpecObjInfo -{ -public: - CSpecObjInfo(); - virtual ~CSpecObjInfo() = default; - - virtual void serializeJson(JsonSerializeFormat & handler) = 0; - - const CGDwelling * owner; -}; - -class DLL_LINKAGE CCreGenAsCastleInfo : public virtual CSpecObjInfo -{ -public: - bool asCastle = false; - ui32 identifier = 0;//h3m internal identifier - - std::vector allowedFactions; - - std::string instanceId;//vcmi map instance identifier - void serializeJson(JsonSerializeFormat & handler) override; -}; - -class DLL_LINKAGE CCreGenLeveledInfo : public virtual CSpecObjInfo -{ -public: - ui8 minLevel = 0; - ui8 maxLevel = 7; //minimal and maximal level of creature in dwelling: <1, 7> - - void serializeJson(JsonSerializeFormat & handler) override; -}; - -class DLL_LINKAGE CCreGenLeveledCastleInfo : public CCreGenAsCastleInfo, public CCreGenLeveledInfo -{ -public: - CCreGenLeveledCastleInfo() = default; - void serializeJson(JsonSerializeFormat & handler) override; -}; - -class DLL_LINKAGE CGDwelling : public CArmedInstance -{ -public: - typedef std::vector > > TCreaturesSet; - - CSpecObjInfo * info; //random dwelling options; not serialized - TCreaturesSet creatures; //creatures[level] -> - - CGDwelling(); - ~CGDwelling() override; - - void initRandomObjectInfo(); -protected: - void serializeJsonOptions(JsonSerializeFormat & handler) override; - -private: - void initObj(CRandomGenerator & rand) override; - void onHeroVisit(const CGHeroInstance * h) const override; - void newTurn(CRandomGenerator & rand) const override; - void setPropertyDer(ui8 what, ui32 val) override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - void updateGuards() const; - void heroAcceptsCreatures(const CGHeroInstance *h) const; - -public: - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & creatures; - } -}; - -class DLL_LINKAGE CGTownBuilding : public IObjectInterface -{ -///basic class for town structures handled as map objects -public: - si32 indexOnTV = 0; //identifies its index on towns vector - CGTownInstance *town = nullptr; - - STRONG_INLINE - BuildingSubID::EBuildingSubID getBuildingSubtype() const - { - return bType; - } - - STRONG_INLINE - const BuildingID & getBuildingType() const - { - return bID; - } - - STRONG_INLINE - void setBuildingSubtype(BuildingSubID::EBuildingSubID subId) - { - bType = subId; - } - - PlayerColor getOwner() const override; - int32_t getObjGroupIndex() const override; - int32_t getObjTypeIndex() const override; - - int3 visitablePos() const override; - int3 getPosition() const override; - - template void serialize(Handler &h, const int version) - { - h & bID; - h & indexOnTV; - h & bType; - } - -protected: - BuildingID bID; //from buildig list - BuildingSubID::EBuildingSubID bType = BuildingSubID::NONE; - - std::string getVisitingBonusGreeting() const; - std::string getCustomBonusGreeting(const Bonus & bonus) const; -}; - -class DLL_LINKAGE COPWBonus : public CGTownBuilding -{///used for OPW bonusing structures -public: - std::set visitors; - void setProperty(ui8 what, ui32 val) override; - void onHeroVisit (const CGHeroInstance * h) const override; - - COPWBonus(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * TOWN); - COPWBonus() = default; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & visitors; - } -}; - -class DLL_LINKAGE CTownBonus : public CGTownBuilding -{ -///used for one-time bonusing structures -///feel free to merge inheritance tree -public: - std::set visitors; - void setProperty(ui8 what, ui32 val) override; - void onHeroVisit (const CGHeroInstance * h) const override; - - CTownBonus(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * TOWN); - CTownBonus() = default; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & visitors; - } - -private: - void applyBonuses(CGHeroInstance * h, const BonusList & bonuses) const; -}; class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode { @@ -248,10 +90,6 @@ public: h & spells; h & events; h & bonusingBuildings; - - for(auto * bonusingBuilding : bonusingBuildings) - bonusingBuilding->town = this; - h & town; h & townAndVis; BONUS_TREE_DESERIALIZATION_FIX diff --git a/lib/mapObjects/MapObjects.h b/lib/mapObjects/MapObjects.h index 63cd959f2..475ddf9b2 100644 --- a/lib/mapObjects/MapObjects.h +++ b/lib/mapObjects/MapObjects.h @@ -15,8 +15,10 @@ #include "CArmedInstance.h" #include "CBank.h" +#include "CGDwelling.h" #include "CGHeroInstance.h" #include "CGMarket.h" +#include "CGTownBuilding.h" #include "CGTownInstance.h" #include "CGPandoraBox.h" #include "CRewardableObject.h"