1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Special buildings support : Patch 1

This commit is contained in:
Dmitry Orlov 2020-10-15 15:03:01 +03:00
parent 12a4cee092
commit 934c4e511d
8 changed files with 116 additions and 91 deletions

View File

@ -115,7 +115,7 @@ void CBuildingRect::clickLeft(tribool down, bool previousState)
if (!CSDL_Ext::isTransparent(area, GH.current->motion.x - pos.x, GH.current->motion.y - pos.y)) //inside building image
{
auto building = getBuilding();
parent->buildingClicked(building->bid, building->subId);
parent->buildingClicked(building->bid, building->subId, building->upgrade);
}
}
@ -653,7 +653,7 @@ const CGHeroInstance * CCastleBuildings::getHero()
return town->garrisonHero;
}
void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuildingSubID subID)
void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuildingSubID subID, BuildingID::EBuildingID upgrades)
{
logGlobal->trace("You've clicked on %d", (int)building.toEnum());
const CBuilding *b = town->town->buildings.find(building)->second;
@ -719,7 +719,7 @@ void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuil
break;
case BuildingSubID::MYSTIC_POND:
enterFountain(building);
enterFountain(building, subID, upgrades);
break;
case BuildingSubID::ARTIFACT_MERCHANT:
@ -730,7 +730,7 @@ void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuil
break;
case BuildingSubID::FOUNTAIN_OF_FORTUNE:
enterFountain(building);
enterFountain(building, subID, upgrades);
break;
case BuildingSubID::FREELANCERS_GUILD:
@ -748,7 +748,10 @@ void CCastleBuildings::buildingClicked(BuildingID building, BuildingSubID::EBuil
break;
case BuildingSubID::BROTHERHOOD_OF_SWORD:
LOCPLINT->showTavernWindow(town);
if(upgrades == BuildingID::TAVERN)
LOCPLINT->showTavernWindow(town);
else
enterBuilding(building);
break;
case BuildingSubID::CASTLE_GATE:
@ -839,22 +842,28 @@ void CCastleBuildings::enterToTheQuickRecruitmentWindow()
GH.pushIntT<QuickRecruitmentWindow>(town, pos);
}
void CCastleBuildings::enterFountain(BuildingID building)
void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID::EBuildingID upgrades)
{
std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(CComponent::building,town->subID,building));
std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(CComponent::building,town->subID, building));
std::string descr = town->town->buildings.find(building)->second->Description();
if ( building == BuildingID::FOUNTAIN_OF_FORTUNE)
descr += "\n\n"+town->town->buildings.find(BuildingID::MYSTIC_POND)->second->Description();
bool isMysticPondOrItsUpgrade = subID == BuildingSubID::MYSTIC_POND
|| (upgrades != BuildingID::NONE
&& town->town->buildings.find(BuildingID(upgrades))->second->subId == BuildingSubID::MYSTIC_POND);
if (town->bonusValue.first == 0)//fountain was builded this week
descr += "\n\n"+ CGI->generaltexth->allTexts[677];
else//fountain produced something;
if(upgrades != BuildingID::NONE)
descr += "\n\n"+town->town->buildings.find(BuildingID(upgrades))->second->Description();
if(isMysticPondOrItsUpgrade) //for vanila Rampart like towns
{
descr+= "\n\n"+ CGI->generaltexth->allTexts[678];
boost::algorithm::replace_first(descr,"%s",CGI->generaltexth->restypes[town->bonusValue.first]);
boost::algorithm::replace_first(descr,"%d",boost::lexical_cast<std::string>(town->bonusValue.second));
if(town->bonusValue.first == 0) //Mystic Pond produced nothing;
descr += "\n\n" + CGI->generaltexth->allTexts[677];
else //Mystic Pond produced something;
{
descr += "\n\n" + CGI->generaltexth->allTexts[678];
boost::algorithm::replace_first(descr, "%s", CGI->generaltexth->restypes[town->bonusValue.first]);
boost::algorithm::replace_first(descr, "%d", boost::lexical_cast<std::string>(town->bonusValue.second));
}
}
LOCPLINT->showInfoDialog(descr, comps);
}
@ -1627,7 +1636,7 @@ const CBuilding * CFortScreen::RecruitArea::getMyBuilding()
BuildingID myID = BuildingID(BuildingID::DWELL_FIRST).advance(level);
if (level == GameConstants::CREATURES_PER_TOWN)
return town->town->buildings.at(BuildingID::PORTAL_OF_SUMMON);
return town->town->getSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING);
if (!town->town->buildings.count(myID))
return nullptr;

View File

@ -136,7 +136,7 @@ class CCastleBuildings : public CIntObject
void enterBlacksmith(ArtifactID artifactID);//support for blacksmith + ballista yard
void enterBuilding(BuildingID building);//for buildings with simple description + pic left-click messages
void enterCastleGate();
void enterFountain(BuildingID building);//Rampart's fountains
void enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID::EBuildingID upgrades);//Rampart's fountains
void enterMagesGuild();
void enterTownHall();
@ -153,7 +153,7 @@ public:
void enterDwelling(int level);
void enterToTheQuickRecruitmentWindow();
void buildingClicked(BuildingID building, BuildingSubID::EBuildingSubID subID = BuildingSubID::NONE);
void buildingClicked(BuildingID building, BuildingSubID::EBuildingSubID subID = BuildingSubID::NONE, BuildingID::EBuildingID upgrades = BuildingID::NONE);
void addBuilding(BuildingID building);
void removeBuilding(BuildingID building);//FIXME: not tested!!!
};

View File

@ -177,7 +177,7 @@
"special1": { "type" : "mysticPond" },
"horde1": { "id" : 18, "upgrades" : "dwellingLvl2" },
"horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl2", "requires" : [ "horde1" ], "mode" : "auto" },
"special2": { "type" : "fountainOfFortune", "requires" : [ "special1" ] },
"special2": { "type" : "fountainOfFortune", "upgrades" : "special1" },
"special3": { "requires" : [ "horde1" ] },
"horde2": { "id" : 24, "upgrades" : "dwellingLvl5" },
"horde2Upgr": { "id" : 25, "upgrades" : "dwellingUpLvl5", "requires" : [ "horde2" ], "mode" : "auto" },

View File

@ -201,6 +201,21 @@ std::set<si32> CTown::getAllBuildings() const
return res;
}
const CBuilding * CTown::getSpecialBuilding(BuildingSubID::EBuildingSubID subID) const
{
for(const auto & kvp : buildings)
{
if(kvp.second->subId == subID)
return buildings.at(kvp.first);
}
return nullptr;
}
BuildingID::EBuildingID CTown::getBuildingType(BuildingSubID::EBuildingSubID subID) const
{
auto building = getSpecialBuilding(subID);
return building == nullptr ? BuildingID::NONE : building->bid.num;
}
CTownHandler::CTownHandler()
{

View File

@ -78,6 +78,13 @@ public:
// returns how many times build has to be upgraded to become build
si32 getDistance(BuildingID build) const;
STRONG_INLINE
bool IsTradeBuilding() const
{
return bid == BuildingID::MARKETPLACE || subId == BuildingSubID::ARTIFACT_MERCHANT || subId == BuildingSubID::FREELANCERS_GUILD;
}
/// input: faction, bid; output: subId, height;
void update792(const BuildingID & bid, BuildingSubID::EBuildingSubID & subId, ETowerHeight & height);
@ -204,6 +211,8 @@ public:
std::string getFactionName() const;
std::string getBuildingScope() const;
std::set<si32> getAllBuildings() const;
const CBuilding * getSpecialBuilding(BuildingSubID::EBuildingSubID subID) const;
BuildingID::EBuildingID getBuildingType(BuildingSubID::EBuildingSubID subID) const;
CFaction * faction;

View File

@ -739,23 +739,29 @@ std::string CGTownInstance::getObjectName() const
return name + ", " + town->faction->name;
}
bool CGTownInstance::townEnvisagesSpecialBuilding(BuildingSubID::EBuildingSubID bid) const
bool CGTownInstance::townEnvisagesBuilding(BuildingSubID::EBuildingSubID subId) const
{
for(const auto & it : town->buildings)
{
if(it.second->subId == bid)
return true;
}
return false;
return town->getBuildingType(subId) != BuildingID::NONE;
}
void CGTownInstance::initObj(CRandomGenerator & rand)
///initialize town structures
//it does not check hasBuilt(...) because this check is in the OnHeroVisit handler
bool CGTownInstance::tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID)
{
auto bid = town->getBuildingType(subID);
if(bid == BuildingID::NONE)
return false;
bonusingBuildings.push_back(new COPWBonus(bid, subID, this));
return true;
}
void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structures
{
blockVisit = true;
if(townEnvisagesSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING)) //Dungeon for example
creatures.resize(GameConstants::CREATURES_PER_TOWN+1);
if(townEnvisagesBuilding(BuildingSubID::PORTAL_OF_SUMMONING)) //Dungeon for example
creatures.resize(GameConstants::CREATURES_PER_TOWN + 1);
else
creatures.resize(GameConstants::CREATURES_PER_TOWN);
@ -770,11 +776,8 @@ void CGTownInstance::initObj(CRandomGenerator & rand)
creatures[level].second.push_back(town->creatures[level][upgradeNum]);
}
}
if(townEnvisagesSpecialBuilding(BuildingSubID::STABLES))
bonusingBuildings.push_back(new COPWBonus(BuildingID::STABLES, BuildingSubID::STABLES, this));
if(townEnvisagesSpecialBuilding(BuildingSubID::MANA_VORTEX))
bonusingBuildings.push_back(new COPWBonus(BuildingID::MANA_VORTEX, BuildingSubID::MANA_VORTEX, this));
tryAddOnePerWeekBonus(BuildingSubID::STABLES);
tryAddOnePerWeekBonus(BuildingSubID::MANA_VORTEX);
switch (subID)
{
@ -824,45 +827,22 @@ void CGTownInstance::updateBonusingBuildings()
switch (building->subId)
{
case BuildingSubID::PORTAL_OF_SUMMONING:
creatures.resize(GameConstants::CREATURES_PER_TOWN + 1);
if(!hasBuiltInOldWay(ETownType::DUNGEON, BuildingID::PORTAL_OF_SUMMON))
creatures.resize(GameConstants::CREATURES_PER_TOWN + 1);
break;
///'hasBuilt' checking for COPW bonuses is in the COPWBonus::onHeroVisit
case BuildingSubID::STABLES:
if(getBonusingBuilding(building->subId) == nullptr)
bonusingBuildings.push_back(new COPWBonus(BuildingID::STABLES, BuildingSubID::STABLES, this));
tryAddOnePerWeekBonus(BuildingSubID::STABLES);
break;
case BuildingSubID::MANA_VORTEX:
if(getBonusingBuilding(building->subId) == nullptr)
bonusingBuildings.push_back(new COPWBonus(BuildingID::MANA_VORTEX, BuildingSubID::MANA_VORTEX, this));
break;
///add new bonus if bonusing building was built in the user added towns:
case BuildingSubID::BROTHERHOOD_OF_SWORD:
if(!hasBuiltInOldWay(ETownType::CASTLE, BuildingID::BROTHERHOOD))
addBonusIfBuilt(BuildingID::BROTHERHOOD, BuildingSubID::BROTHERHOOD_OF_SWORD, Bonus::MORALE, +2);
break;
case BuildingSubID::FOUNTAIN_OF_FORTUNE:
if(!hasBuiltInOldWay(ETownType::RAMPART, BuildingID::FOUNTAIN_OF_FORTUNE))
addBonusIfBuilt(BuildingID::FOUNTAIN_OF_FORTUNE, BuildingSubID::FOUNTAIN_OF_FORTUNE, Bonus::LUCK, +2);
break;
case BuildingSubID::SPELL_POWER_GARRISON_BONUS:
if(!hasBuiltInOldWay(ETownType::INFERNO, BuildingID::STORMCLOUDS))
addBonusIfBuilt(BuildingID::STORMCLOUDS, BuildingSubID::SPELL_POWER_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER);
break;
case BuildingSubID::ATTACK_GARRISON_BONUS:
if(!hasBuiltInOldWay(ETownType::FORTRESS, BuildingID::BLOOD_OBELISK))
addBonusIfBuilt(BuildingID::BLOOD_OBELISK, BuildingSubID::ATTACK_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK);
break;
case BuildingSubID::DEFENSE_GARRISON_BONUS:
if(!hasBuiltInOldWay(ETownType::FORTRESS, BuildingID::GLYPHS_OF_FEAR))
addBonusIfBuilt(BuildingID::GLYPHS_OF_FEAR, BuildingSubID::DEFENSE_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE);
tryAddOnePerWeekBonus(BuildingSubID::MANA_VORTEX);
break;
}
}
recreateBuildingsBonuses(); ///Clear all bonuses and recreate
}
bool CGTownInstance::hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const
@ -1071,7 +1051,7 @@ int CGTownInstance::getBoatType() const
int CGTownInstance::getMarketEfficiency() const
{
if (!hasBuilt(BuildingID::MARKETPLACE))
if(!hasBuiltSomeTradeBuilding())
return 0;
const PlayerState *p = cb->getPlayer(tempOwner);
@ -1079,7 +1059,7 @@ int CGTownInstance::getMarketEfficiency() const
int marketCount = 0;
for(const CGTownInstance *t : p->towns)
if(t->hasBuilt(BuildingID::MARKETPLACE))
if(t->hasBuiltSomeTradeBuilding())
marketCount++;
return marketCount;
@ -1095,18 +1075,16 @@ bool CGTownInstance::allowsTrade(EMarketMode::EMarketMode mode) const
case EMarketMode::ARTIFACT_RESOURCE:
case EMarketMode::RESOURCE_ARTIFACT:
return hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::TOWER)
|| hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::DUNGEON)
|| hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::CONFLUX);
return hasBuilt(BuildingSubID::ARTIFACT_MERCHANT);
case EMarketMode::CREATURE_RESOURCE:
return hasBuilt(BuildingID::FREELANCERS_GUILD, ETownType::STRONGHOLD);
return hasBuilt(BuildingSubID::FREELANCERS_GUILD);
case EMarketMode::CREATURE_UNDEAD:
return hasBuilt(BuildingID::SKELETON_TRANSFORMER, ETownType::NECROPOLIS);
return hasBuilt(BuildingSubID::CREATURE_TRANSFORMER);
case EMarketMode::RESOURCE_SKILL:
return hasBuilt(BuildingID::MAGIC_UNIVERSITY, ETownType::CONFLUX);
return hasBuilt(BuildingSubID::MAGIC_UNIVERSITY);
default:
assert(0);
return false;
@ -1195,13 +1173,13 @@ void CGTownInstance::recreateBuildingsBonuses()
removeBonus(b);
//tricky! -> checks tavern only if no bratherhood of sword or not a castle
if(!addBonusIfBuilt(BuildingID::BROTHERHOOD, BuildingSubID::BROTHERHOOD_OF_SWORD, Bonus::MORALE, +2))
if(!addBonusIfBuilt(BuildingSubID::BROTHERHOOD_OF_SWORD, Bonus::MORALE, +2))
addBonusIfBuilt(BuildingID::TAVERN, Bonus::MORALE, +1);
addBonusIfBuilt(BuildingID::FOUNTAIN_OF_FORTUNE, BuildingSubID::FOUNTAIN_OF_FORTUNE, Bonus::LUCK, +2); //fountain of fortune
addBonusIfBuilt(BuildingID::STORMCLOUDS, BuildingSubID::SPELL_POWER_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER);//works as Brimstone Clouds
addBonusIfBuilt(BuildingID::BLOOD_OBELISK, BuildingSubID::ATTACK_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK);//works as Blood Obelisk
addBonusIfBuilt(BuildingID::GLYPHS_OF_FEAR, BuildingSubID::DEFENSE_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE);//works as Glyphs of Fear
addBonusIfBuilt(BuildingSubID::FOUNTAIN_OF_FORTUNE, Bonus::LUCK, +2); //fountain of fortune
addBonusIfBuilt(BuildingSubID::SPELL_POWER_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER);//works as Brimstone Clouds
addBonusIfBuilt(BuildingSubID::ATTACK_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK);//works as Blood Obelisk
addBonusIfBuilt(BuildingSubID::DEFENSE_GARRISON_BONUS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE);//works as Glyphs of Fear
if(subID == ETownType::CASTLE) //castle
{
@ -1237,23 +1215,22 @@ void CGTownInstance::recreateBuildingsBonuses()
}
}
bool CGTownInstance::addBonusIfBuilt(BuildingID bid, BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, int subtype)
bool CGTownInstance::addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, int subtype)
{
bool hasBuilt = false;
BuildingID currentBid = BuildingID::NONE;
std::ostringstream descr;
for (const auto & bid : builtBuildings)
for(const auto & bid : builtBuildings)
{
if (town->buildings.at(bid)->subId == subId)
{
descr << town->buildings.at(bid)->Name();
hasBuilt = true;
currentBid = bid;
break;
}
}
if(hasBuilt)
hasBuilt = addBonusImpl(bid, type, val, emptyPropagator, descr.str(), subtype);
return hasBuilt;
return currentBid == BuildingID::NONE ? false
: addBonusImpl(currentBid, type, val, emptyPropagator, descr.str(), subtype);
}
bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype)
@ -1390,6 +1367,17 @@ const CGTownBuilding * CGTownInstance::getBonusingBuilding(BuildingSubID::EBuild
return nullptr;
}
bool CGTownInstance::hasBuiltSomeTradeBuilding() const
{
for (const auto & bid : builtBuildings)
{
if(town->buildings.at(bid)->IsTradeBuilding())
return true;
}
return false;
}
bool CGTownInstance::hasBuilt(BuildingSubID::EBuildingSubID buildingID) const
{
for(const auto & bid : builtBuildings)

View File

@ -262,7 +262,7 @@ public:
void deserializationFix();
void recreateBuildingsBonuses();
///bid: param to bind a building with a bonus, subId: param to check if already built
bool addBonusIfBuilt(BuildingID bid, BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, int subtype = -1);
bool addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, int subtype = -1);
bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr &prop, int subtype = -1); //returns true if building is built and bonus has been added
bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype = -1); //convienence version of above
void setVisitingHero(CGHeroInstance *h);
@ -295,6 +295,7 @@ public:
bool hasFort() const;
bool hasCapitol() const;
const CGTownBuilding * getBonusingBuilding(BuildingSubID::EBuildingSubID subId) const;
bool hasBuiltSomeTradeBuilding() const;
//checks if special building with type buildingID is constructed
bool hasBuilt(BuildingSubID::EBuildingSubID buildingID) const;
//checks if building is constructed and town has same subID
@ -313,7 +314,6 @@ public:
void removeCapitols (PlayerColor owner) const;
void clearArmy() const;
void addHeroToStructureVisitors(const CGHeroInstance *h, si64 structureInstanceID) const; //hero must be visiting or garrisoned in town
bool townEnvisagesSpecialBuilding(BuildingSubID::EBuildingSubID bid) const;
const CTown * getTown() const ;
@ -330,13 +330,17 @@ public:
void afterAddToMap(CMap * map) override;
static void reset();
protected:
static TPropagatorPtr emptyPropagator;
void setPropertyDer(ui8 what, ui32 val) override;
void serializeJsonOptions(JsonSerializeFormat & handler) override;
private:
int getDwellingBonus(const std::vector<CreatureID>& creatureIds, const std::vector<ConstTransitivePtr<CGDwelling> >& dwellings) const;
void updateBonusingBuildings();
bool hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const;
bool addBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype = -1);
bool townEnvisagesBuilding(BuildingSubID::EBuildingSubID bid) const;
bool tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID);
};

View File

@ -1808,7 +1808,7 @@ void CGameHandler::newTurn()
handleTownEvents(t, n);
if (newWeek) //first day of week
{
if (t->hasBuilt(BuildingID::PORTAL_OF_SUMMON, ETownType::DUNGEON))
if (t->hasBuilt(BuildingSubID::PORTAL_OF_SUMMONING))
setPortalDwelling(t, true, (n.specialWeek == NewTurn::PLAGUE ? true : false)); //set creatures for Portal of Summoning
if (!firstTurn)
@ -2391,7 +2391,7 @@ void CGameHandler::setOwner(const CGObjectInstance * obj, PlayerColor owner)
{
if (owner < PlayerColor::PLAYER_LIMIT) //new owner is real player
{
if (town->hasBuilt(BuildingID::PORTAL_OF_SUMMON, ETownType::DUNGEON))
if (town->hasBuilt(BuildingSubID::PORTAL_OF_SUMMONING))
setPortalDwelling(town, true, false);
}
@ -2414,7 +2414,7 @@ void CGameHandler::setOwner(const CGObjectInstance * obj, PlayerColor owner)
{
for (const CGTownInstance * t : getPlayer(owner)->towns)
{
if (t->hasBuilt(BuildingID::PORTAL_OF_SUMMON, ETownType::DUNGEON))
if (t->hasBuilt(BuildingSubID::PORTAL_OF_SUMMONING))
setPortalDwelling(t);//set initial creatures for all portals of summoning
}
}
@ -3076,7 +3076,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
ssi.creatures[level].second.push_back(crea->idNumber);
sendAndApply(&ssi);
}
if (t->subID == ETownType::DUNGEON && buildingID == BuildingID::PORTAL_OF_SUMMON)
if (t->town->buildings.at(buildingID)->subId == BuildingSubID::PORTAL_OF_SUMMONING)
{
setPortalDwelling(t);
}
@ -3541,7 +3541,7 @@ bool CGameHandler::buyArtifact(ObjectInstanceID hid, ArtifactID aid)
COMPLAIN_RET_FALSE_IF(getPlayer(hero->getOwner())->resources.at(Res::GOLD) < price, "Not enough gold!");
if ((town->hasBuilt(BuildingID::BLACKSMITH) && town->town->warMachine == aid)
|| ((town->hasBuilt(BuildingID::BALLISTA_YARD, ETownType::STRONGHOLD)) && aid == ArtifactID::BALLISTA))
|| (town->hasBuilt(BuildingSubID::BALLISTA_YARD) && aid == ArtifactID::BALLISTA))
{
giveResource(hero->getOwner(),Res::GOLD,-price);
return giveHeroNewArtifact(hero, art);