1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-16 10:19:47 +02:00
vcmi/lib/mapObjects/CGTownInstance.cpp
Ivan Savenko 20d5b33ea6 Remove marketModes as member
marketModes are now generated in runtime and are not a member of
IMarket. Was not a bad change, but towns load buildings before town type
is randomized, leading to case where market modes are not actually known
when building is added to town (like random towns with market built)

Since altar requires CArtifactSet for work, IMarket will now always
contain it, but it will only be accessible if market supports altar
mode.
2024-08-27 14:07:00 +00:00

1267 lines
33 KiB
C++

/*
* CGTownInstance.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 "CGTownInstance.h"
#include "TownBuildingInstance.h"
#include "../spells/CSpellHandler.h"
#include "../bonuses/Bonus.h"
#include "../battle/IBattleInfoCallback.h"
#include "../CConfigHandler.h"
#include "../texts/CGeneralTextHandler.h"
#include "../IGameCallback.h"
#include "../gameState/CGameState.h"
#include "../mapping/CMap.h"
#include "../CPlayerState.h"
#include "../StartInfo.h"
#include "../TerrainHandler.h"
#include "../entities/building/CBuilding.h"
#include "../entities/faction/CTownHandler.h"
#include "../mapObjectConstructors/AObjectTypeHandler.h"
#include "../mapObjectConstructors/CObjectClassesHandler.h"
#include "../mapObjects/CGHeroInstance.h"
#include "../modding/ModScope.h"
#include "../networkPacks/StackLocation.h"
#include "../networkPacks/PacksForClient.h"
#include "../networkPacks/PacksForClientBattle.h"
#include "../serializer/JsonSerializeFormat.h"
#include <vstd/RNG.h>
VCMI_LIB_NAMESPACE_BEGIN
int CGTownInstance::getSightRadius() const //returns sight distance
{
auto ret = CBuilding::HEIGHT_NO_TOWER;
for(const auto & bid : builtBuildings)
{
if(bid.IsSpecialOrGrail())
{
auto height = town->buildings.at(bid)->height;
if(ret < height)
ret = height;
}
}
return ret;
}
void CGTownInstance::setPropertyDer(ObjProperty what, ObjPropertyID identifier)
{
///this is freakin' overcomplicated solution
switch (what)
{
case ObjProperty::STRUCTURE_ADD_VISITING_HERO:
rewardableBuildings.at(identifier.getNum())->setProperty(ObjProperty::VISITORS, visitingHero->id);
break;
case ObjProperty::STRUCTURE_CLEAR_VISITORS:
rewardableBuildings.at(identifier.getNum())->setProperty(ObjProperty::STRUCTURE_CLEAR_VISITORS, NumericID(0));
break;
case ObjProperty::STRUCTURE_ADD_GARRISONED_HERO: //add garrisoned hero to visitors
rewardableBuildings.at(identifier.getNum())->setProperty(ObjProperty::VISITORS, garrisonHero->id);
break;
case ObjProperty::BONUS_VALUE_FIRST:
bonusValue.first = identifier.getNum();
break;
case ObjProperty::BONUS_VALUE_SECOND:
bonusValue.second = identifier.getNum();
break;
}
}
CGTownInstance::EFortLevel CGTownInstance::fortLevel() const //0 - none, 1 - fort, 2 - citadel, 3 - castle
{
if (hasBuilt(BuildingID::CASTLE))
return CASTLE;
if (hasBuilt(BuildingID::CITADEL))
return CITADEL;
if (hasBuilt(BuildingID::FORT))
return FORT;
return NONE;
}
int CGTownInstance::hallLevel() const // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol
{
if (hasBuilt(BuildingID::CAPITOL))
return 3;
if (hasBuilt(BuildingID::CITY_HALL))
return 2;
if (hasBuilt(BuildingID::TOWN_HALL))
return 1;
if (hasBuilt(BuildingID::VILLAGE_HALL))
return 0;
return -1;
}
int CGTownInstance::mageGuildLevel() const
{
if (hasBuilt(BuildingID::MAGES_GUILD_5))
return 5;
if (hasBuilt(BuildingID::MAGES_GUILD_4))
return 4;
if (hasBuilt(BuildingID::MAGES_GUILD_3))
return 3;
if (hasBuilt(BuildingID::MAGES_GUILD_2))
return 2;
if (hasBuilt(BuildingID::MAGES_GUILD_1))
return 1;
return 0;
}
int CGTownInstance::getHordeLevel(const int & HID) const//HID - 0 or 1; returns creature level or -1 if that horde structure is not present
{
return town->hordeLvl.at(HID);
}
int CGTownInstance::creatureGrowth(const int & level) const
{
return getGrowthInfo(level).totalGrowth();
}
GrowthInfo CGTownInstance::getGrowthInfo(int level) const
{
GrowthInfo ret;
if (level<0 || level >=town->creatures.size())
return ret;
if (creatures[level].second.empty())
return ret; //no dwelling
const Creature *creature = creatures[level].second.back().toEntity(VLC);
const int base = creature->getGrowth();
int castleBonus = 0;
if(tempOwner.isValidPlayer())
{
auto * playerSettings = cb->getPlayerSettings(tempOwner);
ret.handicapPercentage = playerSettings->handicap.percentGrowth;
}
else
ret.handicapPercentage = 100;
ret.entries.emplace_back(VLC->generaltexth->allTexts[590], base); // \n\nBasic growth %d"
if (hasBuilt(BuildingID::CASTLE))
ret.entries.emplace_back(subID, BuildingID::CASTLE, castleBonus = base);
else if (hasBuilt(BuildingID::CITADEL))
ret.entries.emplace_back(subID, BuildingID::CITADEL, castleBonus = base / 2);
if(town->hordeLvl.at(0) == level)//horde 1
if(hasBuilt(BuildingID::HORDE_1))
ret.entries.emplace_back(subID, BuildingID::HORDE_1, creature->getHorde());
if(town->hordeLvl.at(1) == level)//horde 2
if(hasBuilt(BuildingID::HORDE_2))
ret.entries.emplace_back(subID, BuildingID::HORDE_2, creature->getHorde());
//statue-of-legion-like bonus: % to base+castle
TConstBonusListPtr bonuses2 = getBonuses(Selector::type()(BonusType::CREATURE_GROWTH_PERCENT));
for(const auto & b : *bonuses2)
{
const auto growth = b->val * (base + castleBonus) / 100;
if (growth)
{
ret.entries.emplace_back(growth, b->Description(growth));
}
}
//other *-of-legion-like bonuses (%d to growth cumulative with grail)
// Note: bonus uses 1-based levels (Pikeman is level 1), town list uses 0-based (Pikeman in 0-th creatures entry)
TConstBonusListPtr bonuses = getBonuses(Selector::typeSubtype(BonusType::CREATURE_GROWTH, BonusCustomSubtype::creatureLevel(level+1)));
for(const auto & b : *bonuses)
ret.entries.emplace_back(b->val, b->Description());
int dwellingBonus = 0;
if(const PlayerState *p = cb->getPlayerState(tempOwner, false))
{
dwellingBonus = getDwellingBonus(creatures[level].second, p->dwellings);
}
if(dwellingBonus)
ret.entries.emplace_back(VLC->generaltexth->allTexts[591], dwellingBonus); // \nExternal dwellings %+d
if(hasBuilt(BuildingID::GRAIL)) //grail - +50% to ALL (so far added) growth
ret.entries.emplace_back(subID, BuildingID::GRAIL, ret.totalGrowth() / 2);
return ret;
}
int CGTownInstance::getDwellingBonus(const std::vector<CreatureID>& creatureIds, const std::vector<ConstTransitivePtr<CGDwelling> >& dwellings) const
{
int totalBonus = 0;
for (const auto& dwelling : dwellings)
{
for (const auto& creature : dwelling->creatures)
{
totalBonus += vstd::contains(creatureIds, creature.second[0]) ? 1 : 0;
}
}
return totalBonus;
}
TResources CGTownInstance::dailyIncome() const
{
TResources ret;
for(const auto & p : town->buildings)
{
BuildingID buildingUpgrade;
for(const auto & p2 : town->buildings)
{
if (p2.second->upgrade == p.first)
{
buildingUpgrade = p2.first;
}
}
if (!hasBuilt(buildingUpgrade)&&(hasBuilt(p.first)))
{
ret += p.second->produce;
}
}
auto playerSettings = cb->gameState()->scenarioOps->getIthPlayersSettings(getOwner());
for(TResources::nziterator it(ret); it.valid(); it++)
// always round up income - we don't want to always produce zero if handicap in use
ret[it->resType] = vstd::divideAndCeil(ret[it->resType] * playerSettings.handicap.percentIncome, 100);
return ret;
}
bool CGTownInstance::hasFort() const
{
return hasBuilt(BuildingID::FORT);
}
bool CGTownInstance::hasCapitol() const
{
return hasBuilt(BuildingID::CAPITOL);
}
CGTownInstance::CGTownInstance(IGameCallback *cb):
CGDwelling(cb),
town(nullptr),
built(0),
destroyed(0),
identifier(0),
alignmentToPlayer(PlayerColor::NEUTRAL)
{
this->setNodeType(CBonusSystemNode::TOWN);
}
CGTownInstance::~CGTownInstance()
{
for (auto & elem : rewardableBuildings)
delete elem.second;
}
int CGTownInstance::spellsAtLevel(int level, bool checkGuild) const
{
if(checkGuild && mageGuildLevel() < level)
return 0;
int ret = 6 - level; //how many spells are available at this level
if (hasBuilt(BuildingSubID::LIBRARY))
ret++;
return ret;
}
bool CGTownInstance::needsLastStack() const
{
return garrisonHero != nullptr;
}
void CGTownInstance::setOwner(const PlayerColor & player) const
{
removeCapitols(player);
cb->setOwner(this, player);
}
void CGTownInstance::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const
{
for (auto building : rewardableBuildings)
building.second->blockingDialogAnswered(hero, answer); // FIXME: why call for every building?
}
void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const
{
if(cb->gameState()->getPlayerRelations( getOwner(), h->getOwner() ) == PlayerRelations::ENEMIES)
{
if(armedGarrison() || visitingHero)
{
const CGHeroInstance * defendingHero = visitingHero ? visitingHero : garrisonHero;
const CArmedInstance * defendingArmy = defendingHero ? (CArmedInstance *)defendingHero : this;
const bool isBattleOutside = isBattleOutsideTown(defendingHero);
if(!isBattleOutside && visitingHero && defendingHero == visitingHero)
{
//we have two approaches to merge armies: mergeGarrisonOnSiege() and used in the CGameHandler::garrisonSwap(ObjectInstanceID tid)
auto * nodeSiege = defendingHero->whereShouldBeAttachedOnSiege(isBattleOutside);
if(nodeSiege == (CBonusSystemNode *)this)
cb->swapGarrisonOnSiege(this->id);
const_cast<CGHeroInstance *>(defendingHero)->inTownGarrison = false; //hack to return visitor from garrison after battle
}
cb->startBattlePrimary(h, defendingArmy, getSightCenter(), h, defendingHero, false, (isBattleOutside ? nullptr : this));
}
else
{
auto heroColor = h->getOwner();
onTownCaptured(heroColor);
if(cb->gameState()->getPlayerStatus(heroColor) == EPlayerStatus::WINNER)
{
return; //we just won game, we do not need to perform any extra actions
//TODO: check how does H3 behave, visiting town on victory can affect campaigns (spells learned, +1 stat building visited)
}
cb->heroVisitCastle(this, h);
}
}
else
{
assert(h->visitablePos() == this->visitablePos());
bool commander_recover = h->commander && !h->commander->alive;
if (commander_recover) // rise commander from dead
{
SetCommanderProperty scp;
scp.heroid = h->id;
scp.which = SetCommanderProperty::ALIVE;
scp.amount = 1;
cb->sendAndApply(&scp);
}
cb->heroVisitCastle(this, h);
// TODO(vmarkovtsev): implement payment for rising the commander
if (commander_recover) // info window about commander
{
InfoWindow iw;
iw.player = h->tempOwner;
iw.text.appendRawString(h->commander->getName());
iw.components.emplace_back(ComponentType::CREATURE, h->commander->getId(), h->commander->getCount());
cb->showInfoDialog(&iw);
}
}
}
void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const
{
//FIXME: find out why this issue appears on random maps
if(visitingHero == h)
{
cb->stopHeroVisitCastle(this, h);
logGlobal->trace("%s correctly left town %s", h->getNameTranslated(), getNameTranslated());
}
else
logGlobal->warn("Warning, %s tries to leave the town %s but hero is not inside.", h->getNameTranslated(), getNameTranslated());
}
std::string CGTownInstance::getObjectName() const
{
return getNameTranslated() + ", " + town->faction->getNameTranslated();
}
bool CGTownInstance::townEnvisagesBuilding(BuildingSubID::EBuildingSubID subId) const
{
return town->getBuildingType(subId) != BuildingID::NONE;
}
void CGTownInstance::initializeConfigurableBuildings(vstd::RNG & rand)
{
for(const auto & kvp : town->buildings)
{
if(!kvp.second->rewardableObjectInfo.getParameters().isNull())
rewardableBuildings[kvp.first] = new TownRewardableBuildingInstance(this, kvp.second->bid, rand);
}
}
DamageRange CGTownInstance::getTowerDamageRange() const
{
assert(hasBuilt(BuildingID::CASTLE));
// http://heroes.thelazy.net/wiki/Arrow_tower
// base damage, irregardless of town level
static constexpr int baseDamage = 6;
// extra damage, for each building in town
static constexpr int extraDamage = 1;
const int minDamage = baseDamage + extraDamage * getTownLevel();
return {
minDamage,
minDamage * 2
};
}
DamageRange CGTownInstance::getKeepDamageRange() const
{
assert(hasBuilt(BuildingID::CITADEL));
// http://heroes.thelazy.net/wiki/Arrow_tower
// base damage, irregardless of town level
static constexpr int baseDamage = 10;
// extra damage, for each building in town
static constexpr int extraDamage = 2;
const int minDamage = baseDamage + extraDamage * getTownLevel();
return {
minDamage,
minDamage * 2
};
}
FactionID CGTownInstance::randomizeFaction(vstd::RNG & rand)
{
if(getOwner().isValidPlayer())
return cb->gameState()->scenarioOps->getIthPlayersSettings(getOwner()).castle;
if(alignmentToPlayer.isValidPlayer())
return cb->gameState()->scenarioOps->getIthPlayersSettings(alignmentToPlayer).castle;
std::vector<FactionID> potentialPicks;
for (FactionID faction(0); faction < FactionID(VLC->townh->size()); ++faction)
if (VLC->factions()->getById(faction)->hasTown())
potentialPicks.push_back(faction);
assert(!potentialPicks.empty());
return *RandomGeneratorUtil::nextItem(potentialPicks, rand);
}
void CGTownInstance::pickRandomObject(vstd::RNG & rand)
{
assert(ID == MapObjectID::TOWN || ID == MapObjectID::RANDOM_TOWN);
if (ID == MapObjectID::RANDOM_TOWN)
{
ID = MapObjectID::TOWN;
subID = randomizeFaction(rand);
}
assert(ID == Obj::TOWN); // just in case
setType(ID, subID);
town = (*VLC->townh)[getFaction()]->town;
randomizeArmy(getFaction());
updateAppearance();
}
void CGTownInstance::initObj(vstd::RNG & rand) ///initialize town structures
{
blockVisit = true;
if(townEnvisagesBuilding(BuildingSubID::PORTAL_OF_SUMMONING)) //Dungeon for example
creatures.resize(town->creatures.size() + 1);
else
creatures.resize(town->creatures.size());
for (int level = 0; level < town->creatures.size(); level++)
{
BuildingID buildID = BuildingID(BuildingID::getDwellingFromLevel(level, 0));
int upgradeNum = 0;
for (; town->buildings.count(buildID); upgradeNum++, buildID.advance(town->creatures.size()))
{
if (hasBuilt(buildID) && town->creatures.at(level).size() > upgradeNum)
creatures[level].second.push_back(town->creatures[level][upgradeNum]);
}
}
initializeConfigurableBuildings(rand);
recreateBuildingsBonuses();
updateAppearance();
}
void CGTownInstance::newTurn(vstd::RNG & rand) const
{
if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week
{
//give resources if there's a Mystic Pond
if (hasBuilt(BuildingSubID::MYSTIC_POND)
&& cb->getDate(Date::DAY) != 1
&& (tempOwner.isValidPlayer())
)
{
int resID = rand.nextInt(2, 5); //bonus to random rare resource
resID = (resID==2)?1:resID;
int resVal = rand.nextInt(1, 4);//with size 1..4
cb->giveResource(tempOwner, static_cast<EGameResID>(resID), resVal);
cb->setObjPropertyValue(id, ObjProperty::BONUS_VALUE_FIRST, resID);
cb->setObjPropertyValue(id, ObjProperty::BONUS_VALUE_SECOND, resVal);
}
if (tempOwner == PlayerColor::NEUTRAL) //garrison growth for neutral towns
{
std::vector<SlotID> nativeCrits; //slots
for(const auto & elem : Slots())
{
if (elem.second->type->getFaction() == getFaction()) //native
{
nativeCrits.push_back(elem.first); //collect matching slots
}
}
if(!nativeCrits.empty())
{
SlotID pos = *RandomGeneratorUtil::nextItem(nativeCrits, rand);
StackLocation sl(this, pos);
const CCreature *c = getCreature(pos);
if (rand.nextInt(99) < 90 || c->upgrades.empty()) //increase number if no upgrade available
{
cb->changeStackCount(sl, c->getGrowth());
}
else //upgrade
{
cb->changeStackType(sl, c->upgrades.begin()->toCreature());
}
}
if ((stacksCount() < GameConstants::ARMY_SIZE && rand.nextInt(99) < 25) || Slots().empty()) //add new stack
{
int i = rand.nextInt(std::min((int)town->creatures.size(), cb->getDate(Date::MONTH) << 1) - 1);
if (!town->creatures[i].empty())
{
CreatureID c = town->creatures[i][0];
SlotID n;
TQuantity count = creatureGrowth(i);
if (!count) // no dwelling
count = VLC->creatures()->getById(c)->getGrowth();
{//no lower tiers or above current month
if ((n = getSlotFor(c)).validSlot())
{
StackLocation sl(this, n);
if (slotEmpty(n))
cb->insertNewStack(sl, c.toCreature(), count);
else //add to existing
cb->changeStackCount(sl, count);
}
}
}
}
}
}
for(const auto & building : rewardableBuildings)
building.second->newTurn(rand);
if(hasBuilt(BuildingSubID::BANK) && bonusValue.second > 0)
{
TResources res;
res[EGameResID::GOLD] = -500;
cb->giveResources(getOwner(), res);
cb->setObjPropertyValue(id, ObjProperty::BONUS_VALUE_SECOND, bonusValue.second - 500);
}
}
/*
int3 CGTownInstance::getSightCenter() const
{
return pos - int3(2,0,0);
}
*/
bool CGTownInstance::passableFor(PlayerColor color) const
{
if (!armedGarrison())//empty castle - anyone can visit
return true;
if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit
return false;
return cb->getPlayerRelations(tempOwner, color) != PlayerRelations::ENEMIES;
}
void CGTownInstance::getOutOffsets( std::vector<int3> &offsets ) const
{
offsets = {int3(-1,2,0), int3(+1,2,0)};
}
CGTownInstance::EGeneratorState CGTownInstance::shipyardStatus() const
{
if (!hasBuilt(BuildingID::SHIPYARD))
return EGeneratorState::UNKNOWN;
return IShipyard::shipyardStatus();
}
const IObjectInterface * CGTownInstance::getObject() const
{
return this;
}
void CGTownInstance::mergeGarrisonOnSiege() const
{
auto getWeakestStackSlot = [&](ui64 powerLimit)
{
std::vector<SlotID> weakSlots;
auto stacksList = visitingHero->stacks;
std::pair<SlotID, CStackInstance *> pair;
while(!stacksList.empty())
{
pair = *vstd::minElementByFun(stacksList, [&](const std::pair<SlotID, CStackInstance *> & elem) { return elem.second->getPower(); });
if(powerLimit > pair.second->getPower() &&
(weakSlots.empty() || pair.second->getPower() == visitingHero->getStack(weakSlots.front()).getPower()))
{
weakSlots.push_back(pair.first);
stacksList.erase(pair.first);
}
else
break;
}
if(!weakSlots.empty())
return *std::max_element(weakSlots.begin(), weakSlots.end());
return SlotID();
};
auto count = static_cast<int>(stacks.size());
for(int i = 0; i < count; i++)
{
auto pair = *vstd::maxElementByFun(stacks, [&](const std::pair<SlotID, CStackInstance *> & elem)
{
ui64 power = elem.second->getPower();
auto dst = visitingHero->getSlotFor(elem.second->getCreatureID());
if(dst.validSlot() && visitingHero->hasStackAtSlot(dst))
power += visitingHero->getStack(dst).getPower();
return power;
});
auto dst = visitingHero->getSlotFor(pair.second->getCreatureID());
if(dst.validSlot())
cb->moveStack(StackLocation(this, pair.first), StackLocation(visitingHero, dst), -1);
else
{
dst = getWeakestStackSlot(static_cast<int>(pair.second->getPower()));
if(dst.validSlot())
cb->swapStacks(StackLocation(this, pair.first), StackLocation(visitingHero, dst));
}
}
}
void CGTownInstance::removeCapitols(const PlayerColor & owner) const
{
if (hasCapitol()) // search if there's an older capitol
{
PlayerState* state = cb->gameState()->getPlayerState(owner); //get all towns owned by player
for (auto i = state->towns.cbegin(); i < state->towns.cend(); ++i)
{
if (*i != this && (*i)->hasCapitol())
{
RazeStructures rs;
rs.tid = id;
rs.bid.insert(BuildingID::CAPITOL);
rs.destroyed = destroyed;
cb->sendAndApply(&rs);
return;
}
}
}
}
void CGTownInstance::clearArmy() const
{
while(!stacks.empty())
{
cb->eraseStack(StackLocation(this, stacks.begin()->first));
}
}
BoatId CGTownInstance::getBoatType() const
{
return town->faction->boatType;
}
int CGTownInstance::getMarketEfficiency() const
{
if(!hasBuiltSomeTradeBuilding())
return 0;
const PlayerState *p = cb->getPlayerState(tempOwner);
assert(p);
int marketCount = 0;
for(const CGTownInstance *t : p->towns)
if(t->hasBuiltSomeTradeBuilding())
marketCount++;
return marketCount;
}
std::vector<TradeItemBuy> CGTownInstance::availableItemsIds(EMarketMode mode) const
{
if(mode == EMarketMode::RESOURCE_ARTIFACT)
{
std::vector<TradeItemBuy> ret;
for(const CArtifact *a : cb->gameState()->map->townMerchantArtifacts)
if(a)
ret.push_back(a->getId());
else
ret.push_back(ArtifactID{});
return ret;
}
else if ( mode == EMarketMode::RESOURCE_SKILL )
{
return cb->gameState()->map->townUniversitySkills;
}
else
return IMarket::availableItemsIds(mode);
}
ObjectInstanceID CGTownInstance::getObjInstanceID() const
{
return id;
}
void CGTownInstance::updateAppearance()
{
auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId();
//FIXME: not the best way to do this
auto app = getObjectHandler()->getOverride(terrain, this);
if (app)
appearance = app;
}
std::string CGTownInstance::nodeName() const
{
return "Town (" + (town ? town->faction->getNameTranslated() : "unknown") + ") of " + getNameTranslated();
}
void CGTownInstance::deserializationFix()
{
attachTo(townAndVis);
//Hero is already handled by CGameState::attachArmedObjects
// if(visitingHero)
// visitingHero->attachTo(&townAndVis);
// if(garrisonHero)
// garrisonHero->attachTo(this);
}
void CGTownInstance::updateMoraleBonusFromArmy()
{
auto b = getExportedBonusList().getFirst(Selector::sourceType()(BonusSource::ARMY).And(Selector::type()(BonusType::MORALE)));
if(!b)
{
b = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::ARMY, 0, BonusSourceID());
addNewBonus(b);
}
if (garrisonHero)
{
b->val = 0;
CBonusSystemNode::treeHasChanged();
}
else
CArmedInstance::updateMoraleBonusFromArmy();
}
void CGTownInstance::recreateBuildingsBonuses()
{
BonusList bl;
getExportedBonusList().getBonuses(bl, Selector::sourceType()(BonusSource::TOWN_STRUCTURE));
for(const auto & b : bl)
removeBonus(b);
for(const auto & bid : builtBuildings)
{
bool bonusesReplacedByUpgrade = false;
for(const auto & upgradeID : builtBuildings)
{
const auto & upgrade = town->buildings.at(upgradeID);
if (upgrade->getBase() == bid && upgrade->upgradeReplacesBonuses)
bonusesReplacedByUpgrade = true;
}
// bonuses from this building are disabled and replaced by bonuses from an upgrade
if (bonusesReplacedByUpgrade)
continue;
auto building = town->buildings.at(bid);
if(building->buildingBonuses.empty())
continue;
for(auto & bonus : building->buildingBonuses)
addNewBonus(bonus);
}
}
void CGTownInstance::setVisitingHero(CGHeroInstance *h)
{
if(visitingHero.get() == h)
return;
if(h)
{
PlayerState *p = cb->gameState()->getPlayerState(h->tempOwner);
assert(p);
h->detachFrom(*p);
h->attachTo(townAndVis);
visitingHero = h;
h->visitedTown = this;
h->inTownGarrison = false;
}
else
{
PlayerState *p = cb->gameState()->getPlayerState(visitingHero->tempOwner);
visitingHero->visitedTown = nullptr;
visitingHero->detachFrom(townAndVis);
visitingHero->attachTo(*p);
visitingHero = nullptr;
}
}
void CGTownInstance::setGarrisonedHero(CGHeroInstance *h)
{
if(garrisonHero.get() == h)
return;
if(h)
{
PlayerState *p = cb->gameState()->getPlayerState(h->tempOwner);
assert(p);
h->detachFrom(*p);
h->attachTo(*this);
garrisonHero = h;
h->visitedTown = this;
h->inTownGarrison = true;
}
else
{
PlayerState *p = cb->gameState()->getPlayerState(garrisonHero->tempOwner);
garrisonHero->visitedTown = nullptr;
garrisonHero->inTownGarrison = false;
garrisonHero->detachFrom(*this);
garrisonHero->attachTo(*p);
garrisonHero = nullptr;
}
updateMoraleBonusFromArmy(); //avoid giving morale bonus for same army twice
}
bool CGTownInstance::armedGarrison() const
{
return !stacks.empty() || garrisonHero;
}
const CTown * CGTownInstance::getTown() const
{
if(ID == Obj::RANDOM_TOWN)
return VLC->townh->randomTown;
else
{
if(nullptr == town)
{
return (*VLC->townh)[getFaction()]->town;
}
else
return town;
}
}
int CGTownInstance::getTownLevel() const
{
// count all buildings that are not upgrades
int level = 0;
for(const auto & bid : builtBuildings)
{
if(town->buildings.at(bid)->upgrade == BuildingID::NONE)
level++;
}
return level;
}
CBonusSystemNode & CGTownInstance::whatShouldBeAttached()
{
return townAndVis;
}
std::string CGTownInstance::getNameTranslated() const
{
return VLC->generaltexth->translate(nameTextId);
}
std::string CGTownInstance::getNameTextID() const
{
return nameTextId;
}
void CGTownInstance::setNameTextId( const std::string & newName )
{
nameTextId = newName;
}
const CArmedInstance * CGTownInstance::getUpperArmy() const
{
if(garrisonHero)
return garrisonHero;
return this;
}
bool CGTownInstance::hasBuiltSomeTradeBuilding() const
{
return availableModes().empty() ? false : true;
}
bool CGTownInstance::hasBuilt(BuildingSubID::EBuildingSubID buildingID) const
{
for(const auto & bid : builtBuildings)
{
if(town->buildings.at(bid)->subId == buildingID)
return true;
}
return false;
}
bool CGTownInstance::hasBuilt(const BuildingID & buildingID) const
{
return vstd::contains(builtBuildings, buildingID);
}
bool CGTownInstance::hasBuilt(const BuildingID & buildingID, FactionID townID) const
{
if (townID == town->faction->getId() || townID == FactionID::ANY)
return hasBuilt(buildingID);
return false;
}
void CGTownInstance::addBuilding(const BuildingID & buildingID)
{
if(buildingID == BuildingID::NONE)
return;
builtBuildings.insert(buildingID);
}
std::set<EMarketMode> CGTownInstance::availableModes() const
{
std::set<EMarketMode> result;
for (const auto & buildingID : builtBuildings)
{
const auto * buildingPtr = town->buildings.at(buildingID).get();
result.insert(buildingPtr->marketModes.begin(), buildingPtr->marketModes.end());
}
return result;
}
void CGTownInstance::removeBuilding(const BuildingID & buildingID)
{
if(!vstd::contains(builtBuildings, buildingID))
return;
builtBuildings.erase(buildingID);
}
void CGTownInstance::removeAllBuildings()
{
builtBuildings.clear();
}
std::set<BuildingID> CGTownInstance::getBuildings() const
{
return builtBuildings;
}
TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const
{
if (vstd::contains(town->buildings, buildingID))
return town->buildings.at(buildingID)->resources;
else
{
logGlobal->error("Town %s at %s has no possible building %d!", getNameTranslated(), pos.toString(), buildingID.toEnum());
return TResources();
}
}
CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID & buildID, bool deep) const
{
const CBuilding * building = town->buildings.at(buildID);
//TODO: find better solution to prevent infinite loops
std::set<BuildingID> processed;
std::function<CBuilding::TRequired::Variant(const BuildingID &)> dependTest =
[&](const BuildingID & id) -> CBuilding::TRequired::Variant
{
if (town->buildings.count(id) == 0)
{
logMod->error("Invalid building ID %d in building dependencies!", id.getNum());
return CBuilding::TRequired::OperatorAll();
}
const CBuilding * build = town->buildings.at(id);
CBuilding::TRequired::OperatorAll requirements;
if (!hasBuilt(id))
{
if (deep)
requirements.expressions.emplace_back(id);
else
return id;
}
if(!vstd::contains(processed, id))
{
processed.insert(id);
if (build->upgrade != BuildingID::NONE)
requirements.expressions.push_back(dependTest(build->upgrade));
requirements.expressions.push_back(build->requirements.morph(dependTest));
}
return requirements;
};
CBuilding::TRequired::OperatorAll requirements;
if (building->upgrade != BuildingID::NONE)
{
const CBuilding * upgr = town->buildings.at(building->upgrade);
requirements.expressions.push_back(dependTest(upgr->bid));
processed.clear();
}
requirements.expressions.push_back(building->requirements.morph(dependTest));
CBuilding::TRequired::Variant variant(requirements);
CBuilding::TRequired ret(variant);
ret.minimize();
return ret;
}
void CGTownInstance::addHeroToStructureVisitors(const CGHeroInstance *h, si64 structureInstanceID ) const
{
if(visitingHero == h)
cb->setObjPropertyValue(id, ObjProperty::STRUCTURE_ADD_VISITING_HERO, structureInstanceID); //add to visitors
else if(garrisonHero == h)
cb->setObjPropertyValue(id, ObjProperty::STRUCTURE_ADD_GARRISONED_HERO, structureInstanceID); //then it must be garrisoned hero
else
{
//should never ever happen
logGlobal->error("Cannot add hero %s to visitors of structure # %d", h->getNameTranslated(), structureInstanceID);
throw std::runtime_error("unexpected hero in CGTownInstance::addHeroToStructureVisitors");
}
}
void CGTownInstance::battleFinished(const CGHeroInstance * hero, const BattleResult & result) const
{
if(result.winner == BattleSide::ATTACKER)
{
clearArmy();
onTownCaptured(hero->getOwner());
}
}
void CGTownInstance::onTownCaptured(const PlayerColor & winner) const
{
setOwner(winner);
cb->changeFogOfWar(getSightCenter(), getSightRadius(), winner, ETileVisibility::REVEALED);
}
void CGTownInstance::afterAddToMap(CMap * map)
{
map->towns.emplace_back(this);
}
void CGTownInstance::afterRemoveFromMap(CMap * map)
{
vstd::erase_if_present(map->towns, this);
}
void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
{
CGObjectInstance::serializeJsonOwner(handler);
if(!handler.saving)
handler.serializeEnum("tightFormation", formation, NArmyFormation::names); //for old format
CArmedInstance::serializeJsonOptions(handler);
handler.serializeString("name", nameTextId);
{
auto decodeBuilding = [this](const std::string & identifier) -> si32
{
auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), getTown()->getBuildingScope(), identifier);
if(rawId)
return rawId.value();
else
return -1;
};
auto encodeBuilding = [this](si32 index) -> std::string
{
return getTown()->buildings.at(BuildingID(index))->getJsonKey();
};
const std::set<si32> standard = getTown()->getAllBuildings();//by default all buildings are allowed
JsonSerializeFormat::LICSet buildingsLIC(standard, decodeBuilding, encodeBuilding);
if(handler.saving)
{
bool customBuildings = false;
boost::logic::tribool hasFort(false);
for(const BuildingID & id : forbiddenBuildings)
{
buildingsLIC.none.insert(id.getNum());
customBuildings = true;
}
for(const BuildingID & id : builtBuildings)
{
if(id == BuildingID::DEFAULT)
continue;
const CBuilding * building = getTown()->buildings.at(id);
if(building->mode == CBuilding::BUILD_AUTO)
continue;
if(id == BuildingID::FORT)
hasFort = true;
buildingsLIC.all.insert(id.getNum());
customBuildings = true;
}
if(customBuildings)
handler.serializeLIC("buildings", buildingsLIC);
else
handler.serializeBool("hasFort",hasFort);
}
else
{
handler.serializeLIC("buildings", buildingsLIC);
addBuilding(BuildingID::VILLAGE_HALL);
if(buildingsLIC.none.empty() && buildingsLIC.all.empty())
{
addBuilding(BuildingID::DEFAULT);
bool hasFort = false;
handler.serializeBool("hasFort",hasFort);
if(hasFort)
addBuilding(BuildingID::FORT);
}
else
{
for(const si32 item : buildingsLIC.none)
forbiddenBuildings.insert(BuildingID(item));
for(const si32 item : buildingsLIC.all)
addBuilding(BuildingID(item));
}
}
}
{
handler.serializeIdArray( "possibleSpells", possibleSpells);
handler.serializeIdArray( "obligatorySpells", obligatorySpells);
}
{
auto eventsHandler = handler.enterArray("events");
eventsHandler.syncSize(events, JsonNode::JsonType::DATA_VECTOR);
eventsHandler.serializeStruct(events);
}
}
FactionID CGTownInstance::getFaction() const
{
return FactionID(subID.getNum());
}
TerrainId CGTownInstance::getNativeTerrain() const
{
return town->faction->getNativeTerrain();
}
GrowthInfo::Entry::Entry(const std::string &format, int _count)
: count(_count)
{
MetaString formatter;
formatter.appendRawString(format);
formatter.replacePositiveNumber(count);
description = formatter.toString();
}
GrowthInfo::Entry::Entry(int subID, const BuildingID & building, int _count): count(_count)
{
MetaString formatter;
formatter.appendRawString("%s %+d");
formatter.replaceRawString((*VLC->townh)[subID]->town->buildings.at(building)->getNameTranslated());
formatter.replacePositiveNumber(count);
description = formatter.toString();
}
GrowthInfo::Entry::Entry(int _count, std::string fullDescription):
count(_count),
description(std::move(fullDescription))
{
}
CTownAndVisitingHero::CTownAndVisitingHero()
{
setNodeType(TOWN_AND_VISITOR);
}
int GrowthInfo::totalGrowth() const
{
int ret = 0;
for(const Entry &entry : entries)
ret += entry.count;
// always round up income - we don't want buildings to always produce zero if handicap in use
return vstd::divideAndCeil(ret * handicapPercentage, 100);
}
void CGTownInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) const
{
for(const CGTownInstance::TCreaturesSet::value_type & dwelling : creatures)
{
if (vstd::contains(dwelling.second, stack.type->getId())) //Dwelling with our creature
{
for(const auto & upgrID : dwelling.second)
{
if(vstd::contains(stack.type->upgrades, upgrID)) //possible upgrade
{
info.newID.push_back(upgrID);
info.cost.push_back(upgrID.toCreature()->getFullRecruitCost() - stack.type->getFullRecruitCost());
}
}
}
}
}
void CGTownInstance::postDeserialize()
{
setNodeType(CBonusSystemNode::TOWN);
for(auto & building : rewardableBuildings)
building.second->town = this;
}
std::map<BuildingID, TownRewardableBuildingInstance*> CGTownInstance::convertOldBuildings(std::vector<TownRewardableBuildingInstance*> oldVector)
{
std::map<BuildingID, TownRewardableBuildingInstance*> result;
for(auto & building : oldVector)
{
result[building->getBuildingType()] = new TownRewardableBuildingInstance(*building);
delete building;
}
return result;
}
VCMI_LIB_NAMESPACE_END