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

Feature: Mods system improvement, Part III. Bunusing buildings customization.

This commit is contained in:
Dmitry Orlov 2021-01-14 01:02:13 +03:00
parent 9cf953157a
commit 854a2e6c39
27 changed files with 596 additions and 223 deletions

View File

@ -88,7 +88,7 @@ size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
switch(type)
{
case TOWN:
switch (settings.castle)
switch(settings.castle)
{
case PlayerSettings::NONE:
return TOWN_NONE;

View File

@ -176,7 +176,7 @@
"ship": { "id" : 20, "upgrades" : "shipyard" },
"special2": { "type" : "stables", "requires" : [ "dwellingLvl4" ] },
"special3": { "type" : "brotherhoodOfSword", "upgrades" : "tavern" },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "MORALE", "val": 2, "propagator": "PLAYER_PROPAGATOR" } ] },
"dwellingLvl1": { "id" : 30, "requires" : [ "fort" ] },
"dwellingLvl2": { "id" : 31, "requires" : [ "dwellingLvl1" ] },

View File

@ -175,7 +175,8 @@
"special2": { "type" : "manaVortex", "requires" : [ "mageGuild1" ] },
"special3": { "type" : "portalOfSummoning" },
"special4": { "type" : "experienceVisitingBonus" },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
"bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primSkill.spellpower", "val": 12 } ] },
"dwellingLvl1": { "id" : 30, "requires" : [ "fort" ] },
"dwellingLvl2": { "id" : 31, "requires" : [ "dwellingLvl1" ] },

View File

@ -175,9 +175,14 @@
"ship": { "id" : 20, "upgrades" : "shipyard" },
"special2": { "type" : "defenseGarrisonBonus", "requires" : [ "fort" ] },
"special3": { "type" : "attackGarrisonBonus", "requires" : [ "special2" ] },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
"extraCapitol": { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
"bonuses": [
{ "type": "PRIMARY_SKILL", "subtype": "primSkill.attack", "val": 10 },
{ "type": "PRIMARY_SKILL", "subtype": "primSkill.defence", "val": 10 }
]
},
"extraCapitol": { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" },
"dwellingLvl1": { "id" : 30, "requires" : [ "fort" ] },
"dwellingLvl2": { "id" : 31, "requires" : [ "dwellingLvl1" ] },
"dwellingLvl3": { "id" : 32, "requires" : [ "dwellingLvl1" ] },

View File

@ -175,13 +175,16 @@
"resourceSilo": { "id" : 15, "requires" : [ "marketplace" ], "produce": { "ore": 1, "wood": 1 } },
"blacksmith": { "id" : 16 },
"special1": { "requires" : [ "fort" ] },
"special1": { "requires" : [ "fort" ], "bonuses": [ { "type": "DARKNESS", "val": 20 } ] },
"horde1": { "id" : 18, "upgrades" : "dwellingLvl1", "requires" : [ "special3" ] },
"horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
"ship": { "id" : 20, "upgrades" : "shipyard" },
"special2": { "requires" : [ "mageGuild1" ] },
"special2": { "requires" : [ "mageGuild1" ],
"bonuses": [ { "type": "SECONDARY_SKILL_PREMY", "subtype": "skill.necromancy", "val": 10, "propagator": "PLAYER_PROPAGATOR" } ] },
"special3": { "type" : "creatureTransformer", "requires" : [ "dwellingLvl1" ] },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
"bonuses": [ { "type": "SECONDARY_SKILL_PREMY", "subtype": "skill.necromancy", "val": 20, "propagator": "PLAYER_PROPAGATOR" } ] },
"extraTownHall": { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
"extraCityHall": { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },
"extraCapitol": { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" },

View File

@ -181,7 +181,8 @@
"special3": { "type" : "treasury", "requires" : [ "horde1" ] },
"horde2": { "id" : 24, "upgrades" : "dwellingLvl5" },
"horde2Upgr": { "id" : 25, "upgrades" : "dwellingUpLvl5", "requires" : [ "horde2" ], "mode" : "auto" },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }, "bonuses": [ { "type": "LUCK", "val": 2, "propagator": "PLAYER_PROPAGATOR" } ] },
"extraTownHall": { "id" : 27, "requires" : [ "townHall" ], "mode" : "auto" },
"extraCityHall": { "id" : 28, "requires" : [ "cityHall" ], "mode" : "auto" },
"extraCapitol": { "id" : 29, "requires" : [ "capitol" ], "mode" : "auto" },

View File

@ -172,7 +172,8 @@
"special2": { "type" : "freelancersGuild", "requires" : [ "marketplace" ] },
"special3": { "type" : "ballistaYard", "requires" : [ "blacksmith" ] },
"special4": { "type" : "attackVisitingBonus", "requires" : [ "fort" ] },
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 }},
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
"bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primSkill.attack", "val": 20 } ] },
"dwellingLvl1": { "id" : 30, "requires" : [ "fort" ] },
"dwellingLvl2": { "id" : 31, "requires" : [ "dwellingLvl1" ] },

View File

@ -175,7 +175,7 @@
"special2": { "type" : "lookoutTower", "height" : "high", "requires" : [ "fort" ] },
"special3": { "type" : "library", "requires" : [ "mageGuild1" ] },
"special4": { "type" : "knowledgeVisitingBonus", "requires" : [ "mageGuild1" ] },
"grail": { "height" : "skyship", "produce" : { "gold": 5000 } },
"grail": { "height" : "skyship", "produce" : { "gold": 5000 }, "bonuses": [ { "type": "PRIMARY_SKILL", "subtype": "primSkill.knowledge", "val": 15 } ] },
"dwellingLvl1": { "id" : 30, "requires" : [ "fort" ] },
"dwellingLvl2": { "id" : 31, "requires" : [ "dwellingLvl1" ] },

View File

@ -85,7 +85,25 @@
"gems": { "type":"number"},
"gold": { "type":"number"}
}
},
"overrides": {
"type" : "array",
"items" : [
{
"description" : "The buildings which bonuses should be overridden with bonuses of the current building",
"type" : "string"
}
]
},
"bonuses": {
"type":"array",
"description": "Bonuses, provided by this special building on build using bonus system",
"items": { "$ref" : "bonus.json" }
},
"onVisitBonuses": {
"type":"array",
"description": "Bonuses, provided by this special building on hero visit and applied to the visiting hero",
"items": { "$ref" : "bonus.json" }
}
}
}

View File

@ -51,7 +51,9 @@
"greetingAttack" : "Some time spent at the %s allows you to learn more effective combat skills (+1 Attack Skill).",
"greetingDefence" : "Spending time in the %s, the experienced warriors therein teach you additional defensive skills (+1 Defense).",
"hasNotProduced" : "The %s has not produced anything yet.",
"hasProduced" : "The %s produced %d %s this week."
"hasProduced" : "The %s produced %d %s this week.",
"greetingCustomBonus" : "%s gives you +%d %s%s",
"greetingCustomUntil" : " until next battle."
},
"logicalExpressions" :
{

View File

@ -1210,6 +1210,11 @@ void CCreatureHandler::removeBonusesFromAllCreatures()
allCreatures.removeBonuses(Selector::all);
}
void CCreatureHandler::restoreAllCreaturesNodeType794()
{
allCreatures.setNodeType(CBonusSystemNode::ENodeTypes::ALL_CREATURES);
}
void CCreatureHandler::buildBonusTreeForTiers()
{
for(CCreature *c : creatures)

View File

@ -249,6 +249,7 @@ public:
void addBonusForTier(int tier, std::shared_ptr<Bonus> b); //tier must be <1-7>
void addBonusForAllCreatures(std::shared_ptr<Bonus> b);
void removeBonusesFromAllCreatures();
void restoreAllCreaturesNodeType794(); //restore ALL_CREATURES node type for old saves
CCreatureHandler();
~CCreatureHandler();

View File

@ -22,6 +22,7 @@
#include "filesystem/Filesystem.h"
#include "mapObjects/CObjectClassesHandler.h"
#include "mapObjects/CObjectHandler.h"
#include "HeroBonus.h"
const int NAMES_PER_TOWN=16; // number of town names per faction in H3 files. Json can define any number
@ -93,7 +94,37 @@ void CBuilding::deserializeFix()
}
}
void CBuilding::update792(const BuildingID & bid, BuildingSubID::EBuildingSubID & subId, ETowerHeight & height)
void CBuilding::addNewBonus(std::shared_ptr<Bonus> b, BonusList & bonusList)
{
bonusList.push_back(b);
}
const JsonNode & CBuilding::getCurrentFactionForUpdateRoutine() const
{
const auto & faction = town->faction->identifier;
const auto & factionsContent = (*VLC->modh->content)["factions"];
const auto & coreData = factionsContent.modData.at("core");
const auto & coreFactions = coreData.modData;
const auto & currentFaction = coreFactions[faction];
if(currentFaction.isNull())
{
const auto index = faction.find(':');
const std::string factionDir = index == std::string::npos ? faction : faction.substr(0, index);
const auto it = factionsContent.modData.find(factionDir);
if(it == factionsContent.modData.end())
{
logMod->warn("Warning: Update old save failed: Faction: '%s' is not found.", factionDir);
return currentFaction;
}
const std::string modFaction = index == std::string::npos ? faction : faction.substr(index + 1);
return it->second.modData[modFaction];
}
return currentFaction;
}
void CBuilding::update792()
{
subId = BuildingSubID::NONE;
height = ETowerHeight::HEIGHT_NO_TOWER;
@ -106,37 +137,72 @@ void CBuilding::update792(const BuildingID & bid, BuildingSubID::EBuildingSubID
if(buildingName.empty())
return;
const auto & faction = town->faction->identifier;
auto factionsContent = (*VLC->modh->content)["factions"];
auto & coreData = factionsContent.modData.at("core");
auto & coreFactions = coreData.modData;
auto & currentFaction = coreFactions[faction];
auto & currentFaction = getCurrentFactionForUpdateRoutine();
if (currentFaction.isNull())
{
const auto index = faction.find(':');
const std::string factionDir = index == std::string::npos ? faction : faction.substr(0, index);
const auto it = factionsContent.modData.find(factionDir);
if (it == factionsContent.modData.end())
{
logMod->warn("Warning: Update old save failed: Faction: '%s' is not found.", factionDir);
return;
}
const std::string modFaction = index == std::string::npos ? faction : faction.substr(index + 1);
currentFaction = it->second.modData[modFaction];
}
if (!currentFaction.isNull() && currentFaction.getType() == JsonNode::JsonType::DATA_STRUCT)
if(!currentFaction.isNull() && currentFaction.getType() == JsonNode::JsonType::DATA_STRUCT)
{
const auto & buildings = currentFaction["town"]["buildings"];
const auto & currentBuilding = buildings[buildingName];
subId = CTownHandler::getMappedValue<BuildingSubID::EBuildingSubID>(currentBuilding["type"], BuildingSubID::NONE, MappedKeys::SPECIAL_BUILDINGS);
height = CBuilding::HEIGHT_NO_TOWER;
height = subId == BuildingSubID::LOOKOUT_TOWER || bid == BuildingID::GRAIL
? CTownHandler::getMappedValue<CBuilding::ETowerHeight>(currentBuilding["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES)
: height = CBuilding::HEIGHT_NO_TOWER;
}
}
if (subId == BuildingSubID::LOOKOUT_TOWER || bid == BuildingID::GRAIL)
height = CTownHandler::getMappedValue<CBuilding::ETowerHeight>(currentBuilding["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES);
void CBuilding::update794()
{
if(bid == BuildingID::TAVERN || subId == BuildingSubID::BROTHERHOOD_OF_SWORD)
{
VLC->townh->addBonusesForVanilaBuilding(this);
return;
}
if(!bid.IsSpecialOrGrail())
return;
VLC->townh->addBonusesForVanilaBuilding(this);
if(!buildingBonuses.empty() //addBonusesForVanilaBuilding has done all work
|| town->faction == nullptr //or faction data is not valid
|| town->faction->identifier.empty())
return;
const auto buildingName = CTownHandler::getMappedValue<std::string, BuildingID>(bid, std::string(), MappedKeys::BUILDING_TYPES_TO_NAMES, false);
if(buildingName.empty())
return;
auto & currentFaction = getCurrentFactionForUpdateRoutine();
if(currentFaction.isNull() || currentFaction.getType() != JsonNode::JsonType::DATA_STRUCT)
return;
const auto & buildings = currentFaction["town"]["buildings"];
const auto & currentBuilding = buildings[buildingName];
CTownHandler::loadSpecialBuildingBonuses(currentBuilding["bonuses"], buildingBonuses, this);
CTownHandler::loadSpecialBuildingBonuses(currentBuilding["onVisitBonuses"], onVisitBonuses, this);
if(!onVisitBonuses.empty())
{
if(subId == BuildingSubID::NONE)
subId = BuildingSubID::CUSTOM_VISITING_BONUS;
for(auto & bonus : onVisitBonuses)
bonus->sid = Bonus::getSid32(town->faction->index, bid);
}
const auto & overriddenBids = currentBuilding["overrides"];
if(overriddenBids.isNull())
return;
auto scope = town->getBuildingScope();
for(auto b : overriddenBids.Vector())
{
auto bid = BuildingID(VLC->modh->identifiers.getIdentifier(scope, b).get());
overrideBids.insert(bid);
}
}
@ -258,6 +324,8 @@ JsonNode readBuilding(CLegacyConfigParser & parser)
return ret;
}
TPropagatorPtr CTownHandler::emptyPropagator = std::make_shared<CPropagatorNodeType>();
std::vector<JsonNode> CTownHandler::loadLegacyData(size_t dataSize)
{
std::vector<JsonNode> dest(dataSize);
@ -412,7 +480,7 @@ std::vector<JsonNode> CTownHandler::loadLegacyData(size_t dataSize)
return dest;
}
void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode & source)
void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode & source, std::vector<BuildingRequirementsHelper> & bidsToLoad)
{
if (source.isNull())
return;
@ -421,7 +489,7 @@ void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode
hlp.building = building;
hlp.town = building->town;
hlp.json = source;
requirementsToLoad.push_back(hlp);
bidsToLoad.push_back(hlp);
}
template<typename R, typename K>
@ -445,6 +513,100 @@ R CTownHandler::getMappedValue(const JsonNode & node, const R defval, const std:
return defval;
}
void CTownHandler::addBonusesForVanilaBuilding(CBuilding * building)
{
std::shared_ptr<Bonus> b;
static TPropagatorPtr playerPropagator = std::make_shared<CPropagatorNodeType>(CBonusSystemNode::ENodeTypes::PLAYER);
if(building->subId == BuildingSubID::NONE)
{
if(building->bid == BuildingID::TAVERN)
b = createBonus(building, Bonus::MORALE, +1);
else if(building->bid == BuildingID::GRAIL
&& building->town->faction != nullptr
&& boost::algorithm::ends_with(building->town->faction->identifier, ":cove"))
{
static TPropagatorPtr allCreaturesPropagator(new CPropagatorNodeType(CBonusSystemNode::ENodeTypes::ALL_CREATURES));
static auto factionLimiter = std::make_shared<CreatureFactionLimiter>(building->town->faction->index);
b = createBonus(building, Bonus::NO_TERRAIN_PENALTY, 0, allCreaturesPropagator);
b->addLimiter(factionLimiter);
}
}
else
{
switch(building->subId)
{
case BuildingSubID::BROTHERHOOD_OF_SWORD:
b = createBonus(building, Bonus::MORALE, +2);
building->overrideBids.insert(BuildingID::TAVERN);
break;
case BuildingSubID::FOUNTAIN_OF_FORTUNE:
b = createBonus(building, Bonus::LUCK, +2);
break;
case BuildingSubID::SPELL_POWER_GARRISON_BONUS:
b = createBonus(building, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER);
break;
case BuildingSubID::ATTACK_GARRISON_BONUS:
b = createBonus(building, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK);
break;
case BuildingSubID::DEFENSE_GARRISON_BONUS:
b = createBonus(building, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE);
break;
case BuildingSubID::LIGHTHOUSE:
b = createBonus(building, Bonus::SEA_MOVEMENT, +500, playerPropagator);
break;
}
}
if(b)
building->addNewBonus(b, building->buildingBonuses);
}
std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, Bonus::BonusType type, int val, int subtype)
{
return createBonus(build, type, val, emptyPropagator, subtype);
}
std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype)
{
std::ostringstream descr;
descr << build->name;
return createBonusImpl(build->bid, type, val, prop, descr.str(), subtype);
}
std::shared_ptr<Bonus> CTownHandler::createBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype)
{
auto b = std::make_shared<Bonus>(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, description, subtype);
if(prop)
b->addPropagator(prop);
return b;
}
void CTownHandler::loadSpecialBuildingBonuses(const JsonNode & source, BonusList & bonusList, CBuilding * building)
{
for(auto b : source.Vector())
{
auto bonus = JsonUtils::parseBuildingBonus(b, building->bid, building->name);
if(bonus == nullptr)
continue;
if(bonus->limiter != nullptr)
{
auto limPtr = dynamic_cast<CreatureFactionLimiter*>(bonus->limiter.get());
if(limPtr != nullptr && limPtr->faction == (TFaction)-1)
limPtr->faction = building->town->faction->index;
}
//JsonUtils::parseBuildingBonus produces UNKNOWN type propagator instead of empty.
if(bonus->propagator != nullptr
&& bonus->propagator->getPropagatorType() == CBonusSystemNode::ENodeTypes::UNKNOWN)
bonus->addPropagator(emptyPropagator);
building->addNewBonus(bonus, bonusList);
}
}
void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source)
{
auto ret = new CBuilding();
@ -474,6 +636,26 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
ret->resources = TResources(source["cost"]);
ret->produce = TResources(source["produce"]);
if(ret->bid == BuildingID::TAVERN)
addBonusesForVanilaBuilding(ret);
else if(ret->bid.IsSpecialOrGrail())
{
loadSpecialBuildingBonuses(source["bonuses"], ret->buildingBonuses, ret);
if(ret->buildingBonuses.empty())
addBonusesForVanilaBuilding(ret);
loadSpecialBuildingBonuses(source["onVisitBonuses"], ret->onVisitBonuses, ret);
if(!ret->onVisitBonuses.empty())
{
if(ret->subId == BuildingSubID::NONE)
ret->subId = BuildingSubID::CUSTOM_VISITING_BONUS;
for(auto & bonus : ret->onVisitBonuses)
bonus->sid = Bonus::getSid32(ret->town->faction->index, ret->bid);
}
}
//MODS COMPATIBILITY FOR 0.96
if(!ret->produce.nonZero())
{
@ -501,8 +683,10 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
}
}
}
loadBuildingRequirements(ret, source["requires"], requirementsToLoad);
loadBuildingRequirements(ret, source["requires"]);
if(ret->bid.IsSpecialOrGrail())
loadBuildingRequirements(ret, source["overrides"], overriddenBidsToLoad);
if (!source["upgrades"].isNull())
{
@ -828,9 +1012,10 @@ ETerrainType::EETerrainType CTownHandler::getDefaultTerrainForAlignment(EAlignme
return terrain;
}
CFaction * CTownHandler::loadFromJson(const JsonNode &source, const std::string & identifier)
CFaction * CTownHandler::loadFromJson(const JsonNode &source, const std::string & identifier, TFaction index)
{
auto faction = new CFaction();
faction->index = index;
faction->name = source["name"].String();
faction->identifier = identifier;
@ -871,9 +1056,9 @@ CFaction * CTownHandler::loadFromJson(const JsonNode &source, const std::string
void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
{
auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
auto index = static_cast<TFaction>(factions.size());
auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), index);
object->index = static_cast<TFaction>(factions.size());
factions.push_back(object);
if (object->town)
@ -911,8 +1096,8 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
{
auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
object->index = static_cast<TFaction>(index);
auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name), static_cast<TFaction>(index));
if (factions.size() > index)
assert(factions[index] == nullptr); // ensure that this id was not loaded before
else
@ -957,6 +1142,7 @@ void CTownHandler::loadCustom()
void CTownHandler::afterLoadFinalization()
{
initializeRequirements();
initializeOverridden();
initializeWarMachines();
}
@ -979,6 +1165,22 @@ void CTownHandler::initializeRequirements()
requirementsToLoad.clear();
}
void CTownHandler::initializeOverridden()
{
for(auto & bidHelper : overriddenBidsToLoad)
{
auto jsonNode = bidHelper.json;
auto scope = bidHelper.town->getBuildingScope();
for(auto b : jsonNode.Vector())
{
auto bid = BuildingID(VLC->modh->identifiers.getIdentifier(scope, b).get());
bidHelper.building->overrideBids.insert(bid);
}
}
overriddenBidsToLoad.clear();
}
void CTownHandler::initializeWarMachines()
{
// must be done separately after all objects are loaded

View File

@ -16,6 +16,7 @@
#include "IHandlerBase.h"
#include "LogicalExpression.h"
#include "battle/BattleHex.h"
#include "HeroBonus.h"
class CLegacyConfigParser;
class JsonNode;
@ -47,6 +48,9 @@ public:
BuildingID bid; //structure ID
BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty
BuildingSubID::EBuildingSubID subId; /// subtype for special buildings, -1 = the building is not special
std::set<BuildingID> overrideBids; /// the building which bonuses should be overridden with bonuses of the current building
BonusList buildingBonuses;
BonusList onVisitBonuses;
enum EBuildMode
{
@ -95,14 +99,16 @@ public:
bool IsVisitingBonus() const
{
return subId == BuildingSubID::ATTACK_VISITING_BONUS ||
subId == BuildingSubID::DEFENSE_VISITING_BONUS ||
subId == BuildingSubID::DEFENSE_VISITING_BONUS ||
subId == BuildingSubID::SPELL_POWER_VISITING_BONUS ||
subId == BuildingSubID::KNOWLEDGE_VISITING_BONUS ||
subId == BuildingSubID::EXPERIENCE_VISITING_BONUS;
subId == BuildingSubID::EXPERIENCE_VISITING_BONUS ||
subId == BuildingSubID::CUSTOM_VISITING_BONUS;
}
/// input: faction, bid; output: subId, height;
void update792(const BuildingID & bid, BuildingSubID::EBuildingSubID & subId, ETowerHeight & height);
void addNewBonus(std::shared_ptr<Bonus> b, BonusList & bonusList);
void update792();
void update794();
template <typename Handler> void serialize(Handler &h, const int version)
{
@ -123,9 +129,17 @@ public:
h & height;
}
if(!h.saving && version < 793)
update792(); //adjust height, subId
if(version >= 794)
{
update792(bid, subId, height);
h & overrideBids;
h & buildingBonuses;
h & onVisitBonuses;
}
else if(!h.saving)
update794(); //populate overrideBids, buildingBonuses, onVisitBonuses
if(!h.saving)
deserializeFix();
}
@ -133,8 +147,8 @@ public:
friend class CTownHandler;
private:
void deserializeFix();
const JsonNode & getCurrentFactionForUpdateRoutine() const;
};
/// This is structure used only by client
@ -354,19 +368,27 @@ class DLL_LINKAGE CTownHandler : public IHandlerBase
std::map<CTown *, JsonNode> warMachinesToLoad;
std::vector<BuildingRequirementsHelper> requirementsToLoad;
std::vector<BuildingRequirementsHelper> overriddenBidsToLoad; //list of buildings, which bonuses should be overridden.
const static ETerrainType::EETerrainType defaultGoodTerrain = ETerrainType::EETerrainType::GRASS;
const static ETerrainType::EETerrainType defaultEvilTerrain = ETerrainType::EETerrainType::LAVA;
const static ETerrainType::EETerrainType defaultNeutralTerrain = ETerrainType::EETerrainType::ROUGH;
static TPropagatorPtr emptyPropagator;
void initializeRequirements();
void initializeOverridden();
void initializeWarMachines();
/// loads CBuilding's into town
void loadBuildingRequirements(CBuilding * building, const JsonNode & source);
void loadBuildingRequirements(CBuilding * building, const JsonNode & source, std::vector<BuildingRequirementsHelper> & bidsToLoad);
void loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source);
void loadBuildings(CTown * town, const JsonNode & source);
std::shared_ptr<Bonus> createBonus(CBuilding * build, Bonus::BonusType type, int val, int subtype = -1);
std::shared_ptr<Bonus> createBonus(CBuilding * build, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype = -1);
std::shared_ptr<Bonus> createBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype = -1);
/// loads CStructure's into town
void loadStructure(CTown &town, const std::string & stringID, const JsonNode & source);
void loadStructures(CTown &town, const JsonNode & source);
@ -383,7 +405,7 @@ class DLL_LINKAGE CTownHandler : public IHandlerBase
ETerrainType::EETerrainType getDefaultTerrainForAlignment(EAlignment::EAlignment aligment) const;
CFaction * loadFromJson(const JsonNode & data, const std::string & identifier);
CFaction * loadFromJson(const JsonNode & data, const std::string & identifier, TFaction index);
void loadRandomFaction();
@ -404,6 +426,7 @@ public:
void loadObject(std::string scope, std::string name, const JsonNode & data) override;
void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
void addBonusesForVanilaBuilding(CBuilding * building);
void loadCustom() override;
void afterLoadFinalization() override;
@ -416,6 +439,7 @@ public:
//json serialization helper
static std::string encodeFaction(const si32 index);
static void loadSpecialBuildingBonuses(const JsonNode & source, BonusList & bonusList, CBuilding * building);
template <typename Handler> void serialize(Handler &h, const int version)
{

View File

@ -297,7 +297,7 @@ class TeleportChannelID : public BaseForID<TeleportChannelID, si32>
// Enum declarations
namespace PrimarySkill
{
enum PrimarySkill { ATTACK, DEFENSE, SPELL_POWER, KNOWLEDGE,
enum PrimarySkill { NONE = -1, ATTACK, DEFENSE, SPELL_POWER, KNOWLEDGE,
EXPERIENCE = 4}; //for some reason changePrimSkill uses it
}
@ -452,7 +452,8 @@ namespace BuildingSubID
KNOWLEDGE_VISITING_BONUS,
EXPERIENCE_VISITING_BONUS,
LIGHTHOUSE,
TREASURY
TREASURY,
CUSTOM_VISITING_BONUS
};
}

View File

@ -71,7 +71,8 @@ const std::map<std::string, TLimiterPtr> bonusLimiterMap =
{"SHOOTER_ONLY", std::make_shared<HasAnotherBonusLimiter>(Bonus::SHOOTER)},
{"DRAGON_NATURE", std::make_shared<HasAnotherBonusLimiter>(Bonus::DRAGON_NATURE)},
{"IS_UNDEAD", std::make_shared<HasAnotherBonusLimiter>(Bonus::UNDEAD)},
{"CREATURE_NATIVE_TERRAIN", std::make_shared<CreatureTerrainLimiter>()}
{"CREATURE_NATIVE_TERRAIN", std::make_shared<CreatureTerrainLimiter>()},
{"CREATURE_FACTION", std::make_shared<CreatureFactionLimiter>()}
};
const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
@ -81,7 +82,8 @@ const std::map<std::string, TPropagatorPtr> bonusPropagatorMap =
{"PLAYER_PROPAGATOR", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::PLAYER)},
{"HERO", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::HERO)},
{"TEAM_PROPAGATOR", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::TEAM)}, //untested
{"GLOBAL_EFFECT", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::GLOBAL_EFFECTS)}
{"GLOBAL_EFFECT", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::GLOBAL_EFFECTS)},
{"ALL_CREATURES", std::make_shared<CPropagatorNodeType>(CBonusSystemNode::ALL_CREATURES)}
}; //untested
const std::map<std::string, TUpdaterPtr> bonusUpdaterMap =
@ -1570,8 +1572,8 @@ std::string Bonus::nameForBonus() const
}
}
Bonus::Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype)
: duration(Dur), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), description(Desc)
Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype)
: duration((ui16)Duration), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), description(Desc)
{
turnsRemain = 0;
valType = ADDITIVE_VALUE;
@ -1579,8 +1581,8 @@ Bonus::Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::
boost::algorithm::trim(description);
}
Bonus::Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype, ValueType ValType)
: duration(Dur), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), valType(ValType)
Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype, ValueType ValType)
: duration((ui16)Duration), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), valType(ValType)
{
turnsRemain = 0;
effectRange = NO_LIMIT;
@ -1922,17 +1924,27 @@ bool IPropagator::shouldBeAttached(CBonusSystemNode *dest)
return false;
}
CBonusSystemNode::ENodeTypes IPropagator::getPropagatorType() const
{
return CBonusSystemNode::ENodeTypes::NONE;
}
CPropagatorNodeType::CPropagatorNodeType()
:nodeType(0)
:nodeType(CBonusSystemNode::ENodeTypes::UNKNOWN)
{
}
CPropagatorNodeType::CPropagatorNodeType(int NodeType)
CPropagatorNodeType::CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType)
: nodeType(NodeType)
{
}
CBonusSystemNode::ENodeTypes CPropagatorNodeType::getPropagatorType() const
{
return nodeType;
}
bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest)
{
return nodeType == dest->getNodeType();

View File

@ -418,8 +418,8 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
std::string description;
Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype=-1);
Bonus(ui16 Dur, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype=-1, ValueType ValType = ADDITIVE_VALUE);
Bonus(BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype=-1);
Bonus(BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, si32 Subtype=-1, ValueType ValType = ADDITIVE_VALUE);
Bonus();
template <typename Handler> void serialize(Handler &h, const int version)
@ -508,6 +508,10 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
{
val += Val;
}
STRONG_INLINE static ui32 getSid32(ui32 high, ui32 low)
{
return (high << 16) + low;
}
std::string Description() const;
JsonNode toJsonNode() const;
@ -639,30 +643,6 @@ inline BonusList::const_iterator range_end(BonusList const &x)
DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const BonusList &bonusList);
class DLL_LINKAGE IPropagator
{
public:
virtual ~IPropagator();
virtual bool shouldBeAttached(CBonusSystemNode *dest);
template <typename Handler> void serialize(Handler &h, const int version)
{}
};
class DLL_LINKAGE CPropagatorNodeType : public IPropagator
{
int nodeType; //CBonusSystemNode::ENodeTypes
public:
CPropagatorNodeType();
CPropagatorNodeType(int NodeType);
bool shouldBeAttached(CBonusSystemNode *dest) override;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & nodeType;
}
};
struct BonusLimitationContext
{
std::shared_ptr<const Bonus> b;
@ -756,6 +736,7 @@ class DLL_LINKAGE CBonusSystemNode : public virtual IBonusBearer, public boost::
public:
enum ENodeTypes
{
NONE = -1,
UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALTY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO, PLAYER, TEAM,
TOWN_AND_VISITOR, BATTLE, COMMANDER, GLOBAL_EFFECTS, ALL_CREATURES
};
@ -857,6 +838,33 @@ public:
friend class CBonusProxy;
};
class DLL_LINKAGE IPropagator
{
public:
virtual ~IPropagator();
virtual bool shouldBeAttached(CBonusSystemNode *dest);
virtual CBonusSystemNode::ENodeTypes getPropagatorType() const;
template <typename Handler> void serialize(Handler &h, const int version)
{}
};
class DLL_LINKAGE CPropagatorNodeType : public IPropagator
{
CBonusSystemNode::ENodeTypes nodeType;
public:
CPropagatorNodeType();
CPropagatorNodeType(CBonusSystemNode::ENodeTypes NodeType);
bool shouldBeAttached(CBonusSystemNode *dest) override;
CBonusSystemNode::ENodeTypes getPropagatorType() const override;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & nodeType;
}
};
namespace NBonus
{
//set of methods that may be safely called with nullptr objs

View File

@ -697,6 +697,19 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonNode &ability)
return b;
}
std::shared_ptr<Bonus> JsonUtils::parseBuildingBonus(const JsonNode &ability, BuildingID building, std::string description)
{
/* duration = Bonus::PERMANENT
source = Bonus::TOWN_STRUCTURE
bonusType, val, subtype - get from json
*/
auto b = std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::NONE, Bonus::TOWN_STRUCTURE, 0, building, description, -1);
if(!parseBonus(ability, b.get()))
return nullptr;
return b;
}
bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
{
const JsonNode *value;
@ -726,7 +739,8 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
b->sid = static_cast<si32>(ability["sourceID"].Float());
b->description = ability["description"].String();
if(!ability["description"].isNull())
b->description = ability["description"].String();
value = &ability["effectRange"];
if (!value->isNull())
@ -738,7 +752,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
switch (value->getType())
{
case JsonNode::JsonType::DATA_STRING:
b->duration = parseByMap(bonusDurationMap, value, "duration type ");
b->duration = (Bonus::BonusDuration)parseByMap(bonusDurationMap, value, "duration type ");
break;
case JsonNode::JsonType::DATA_VECTOR:
{
@ -747,7 +761,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
{
dur |= parseByMap(bonusDurationMap, &d, "duration type ");
}
b->duration = dur;
b->duration = (Bonus::BonusDuration)dur;
}
break;
default:

View File

@ -8,6 +8,7 @@
*
*/
#pragma once
#include "GameConstants.h"
class JsonNode;
typedef std::map <std::string, JsonNode> JsonMap;
@ -168,6 +169,7 @@ namespace JsonUtils
///
DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonVector &ability_vec);
DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonNode &ability);
DLL_LINKAGE std::shared_ptr<Bonus> parseBuildingBonus(const JsonNode &ability, BuildingID building, std::string description);
DLL_LINKAGE bool parseBonus(const JsonNode &ability, Bonus *placement);
DLL_LINKAGE std::shared_ptr<ILimiter> parseLimiter(const JsonNode & limiter);
DLL_LINKAGE void resolveIdentifier(si32 &var, const JsonNode &node, std::string name);

View File

@ -245,10 +245,21 @@ DLL_LINKAGE void GiveBonus::applyGs(CGameState *gs)
std::string &descr = b->description;
if(!bdescr.message.size()
&& bonus.source == Bonus::OBJECT
&& (bonus.type == Bonus::LUCK || bonus.type == Bonus::MORALE))
{
descr = VLC->generaltexth->arraytxt[bonus.val > 0 ? 110 : 109]; //+/-%d Temporary until next battle"
if (bonus.source == Bonus::OBJECT)
{
descr = VLC->generaltexth->arraytxt[bonus.val > 0 ? 110 : 109]; //+/-%d Temporary until next battle"
}
else if(bonus.source == Bonus::TOWN_STRUCTURE)
{
descr = bonus.description;
return;
}
else
{
bdescr.toString(descr);
}
}
else
{
@ -556,16 +567,27 @@ void TryMoveHero::applyGs(CGameState *gs)
DLL_LINKAGE void NewStructures::applyGs(CGameState *gs)
{
CGTownInstance *t = gs->getTown(tid);
for(const auto & id : bid)
{
assert(t->town->buildings.at(id) != nullptr);
t->builtBuildings.insert(id);
t->updateAppearance();
auto currentBuilding = t->town->buildings.at(id);
if(currentBuilding->overrideBids.empty())
continue;
for(auto overrideBid : currentBuilding->overrideBids)
{
t->overriddenBuildings.insert(overrideBid);
t->deleteTownBonus(overrideBid);
}
}
t->builded = builded;
t->recreateBuildingsBonuses();
}
DLL_LINKAGE void RazeStructures::applyGs(CGameState *gs)
{
CGTownInstance *t = gs->getTown(tid);

View File

@ -196,3 +196,8 @@ void LibClasses::setContent(std::shared_ptr<CContentHandler> content)
{
modh->content = content;
}
void LibClasses::restoreAllCreaturesNodeType794()
{
creh->restoreAllCreaturesNodeType794();
}

View File

@ -36,6 +36,7 @@ class DLL_LINKAGE LibClasses
void makeNull(); //sets all handler pointers to null
std::shared_ptr<CContentHandler> getContent() const;
void setContent(std::shared_ptr<CContentHandler> content);
void restoreAllCreaturesNodeType794();
public:
bool IS_AI_ENABLED; //unused?
@ -69,6 +70,9 @@ public:
h & heroh;
h & arth;
h & creh;
if(!h.saving && version < 794)
restoreAllCreaturesNodeType794();
h & townh;
h & objh;
h & objtypeh;

View File

@ -21,6 +21,7 @@
#include "../mapping/CMap.h"
#include "../CPlayerState.h"
#include "../serializer/JsonSerializeFormat.h"
#include "../HeroBonus.h"
std::vector<const CArtifact *> CGTownInstance::merchantArtifacts;
std::vector<int> CGTownInstance::universitySkills;
@ -435,8 +436,6 @@ void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler)
}
}
TPropagatorPtr CGTownInstance::emptyPropagator = TPropagatorPtr();
int CGTownInstance::getSightRadius() const //returns sight distance
{
auto ret = CBuilding::HEIGHT_NO_TOWER;
@ -753,6 +752,17 @@ void CGTownInstance::tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID)
bonusingBuildings.push_back(new COPWBonus(bid, subID, this));
}
void CGTownInstance::initOverriddenBids()
{
for(const auto & bid : builtBuildings)
{
auto & overrideThem = town->buildings.at(bid)->overrideBids;
for(auto & overrideIt : overrideThem)
overriddenBuildings.insert(overrideIt);
}
}
void CGTownInstance::tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID)
{
auto bid = town->getBuildingType(subID);
@ -765,6 +775,9 @@ void CGTownInstance::addTownBonuses()
{
for(const auto & kvp : town->buildings)
{
if(vstd::contains(overriddenBuildings, kvp.first))
continue;
if(kvp.second->IsVisitingBonus())
bonusingBuildings.push_back(new CTownBonus(kvp.second->bid, kvp.second->subId, this));
@ -773,6 +786,36 @@ void CGTownInstance::addTownBonuses()
}
}
void CGTownInstance::deleteTownBonus(BuildingID::EBuildingID bid)
{
size_t i = 0;
CGTownBuilding * freeIt = nullptr;
for(i = 0; i != bonusingBuildings.size(); i++)
{
if(bonusingBuildings[i]->getBuildingType() == bid)
{
freeIt = bonusingBuildings[i];
break;
}
}
if(freeIt == nullptr)
return;
auto building = town->buildings.at(bid);
auto isVisitingBonus = building->IsVisitingBonus();
auto isWeekBonus = building->IsWeekBonus();
if(!isVisitingBonus && !isWeekBonus)
return;
bonusingBuildings.erase(bonusingBuildings.begin() + i);
if(isVisitingBonus)
delete (CTownBonus *)freeIt;
else if(isWeekBonus)
delete (COPWBonus *)freeIt;
}
void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structures
{
@ -794,17 +837,18 @@ void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structu
creatures[level].second.push_back(town->creatures[level][upgradeNum]);
}
}
initOverriddenBids();
addTownBonuses(); //add special bonuses from buildings to the bonusingBuildings vector.
recreateBuildingsBonuses();
updateAppearance();
}
void CGTownInstance::updateBonusingBuildings()
void CGTownInstance::updateBonusingBuildings() //update to version 792
{
if (this->town->faction != nullptr)
if(this->town->faction != nullptr)
{
//firstly, update subtype for the Bonusing objects, which are already stored in the bonusing list
for (auto building : bonusingBuildings) //no garrison bonuses here, only week and visiting bonuses
for(auto building : bonusingBuildings) //no garrison bonuses here, only week and visiting bonuses
{
switch (this->town->faction->index)
{
@ -838,7 +882,7 @@ void CGTownInstance::updateBonusingBuildings()
}
}
//secondly, supplement bonusing buildings list and active bonuses; subtypes for these objects are already set in update792
for (auto & kvp : town->buildings)
for(auto & kvp : town->buildings)
{
auto & building = kvp.second;
@ -864,6 +908,25 @@ void CGTownInstance::updateBonusingBuildings()
recreateBuildingsBonuses(); ///Clear all bonuses and recreate
}
void CGTownInstance::updateTown794()
{
for(auto builtBuilding : builtBuildings)
{
auto building = town->buildings.at(builtBuilding);
for(auto overriddenBid : building->overrideBids)
overriddenBuildings.insert(overriddenBid);
}
for(auto & kvp : town->buildings)
{
auto & building = kvp.second;
//The building acts as a visiting bonus and it has not been overridden.
if(building->IsVisitingBonus() && overriddenBuildings.find(kvp.first) == overriddenBuildings.end())
tryAddVisitingBonus(building->subId);
}
recreateBuildingsBonuses();
}
bool CGTownInstance::hasBuiltInOldWay(ETownType::ETownType type, BuildingID bid) const
{
return (this->town->faction != nullptr && this->town->faction->index == type && hasBuilt(bid));
@ -1184,112 +1247,30 @@ void CGTownInstance::updateMoraleBonusFromArmy()
void CGTownInstance::recreateBuildingsBonuses()
{
static TPropagatorPtr playerProp(new CPropagatorNodeType(PLAYER));
BonusList bl;
getExportedBonusList().getBonuses(bl, Selector::sourceType()(Bonus::TOWN_STRUCTURE));
for(auto b : bl)
removeBonus(b);
//tricky! -> checks tavern only if no bratherhood of sword or not a castle
if(!addBonusIfBuilt(BuildingSubID::BROTHERHOOD_OF_SWORD, Bonus::MORALE, +2))
addBonusIfBuilt(BuildingID::TAVERN, Bonus::MORALE, +1);
for(auto bid : builtBuildings)
{
if(vstd::contains(overriddenBuildings, bid)) //tricky! -> checks tavern only if no bratherhood of sword
continue;
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
addBonusIfBuilt(BuildingSubID::LIGHTHOUSE, Bonus::SEA_MOVEMENT, +500, playerProp);
auto building = town->buildings.at(bid);
if(subID == ETownType::CASTLE) //castle
{
addBonusIfBuilt(BuildingID::GRAIL, Bonus::MORALE, +2, playerProp); //colossus
}
else if(subID == ETownType::RAMPART) //rampart
{
addBonusIfBuilt(BuildingID::GRAIL, Bonus::LUCK, +2, playerProp); //guardian spirit
}
else if(subID == ETownType::TOWER) //tower
{
addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +15, PrimarySkill::KNOWLEDGE); //grail
}
else if(subID == ETownType::NECROPOLIS) //necropolis
{
addBonusIfBuilt(BuildingID::COVER_OF_DARKNESS, Bonus::DARKNESS, +20);
addBonusIfBuilt(BuildingID::NECROMANCY_AMPLIFIER, Bonus::SECONDARY_SKILL_PREMY, +10, playerProp, SecondarySkill::NECROMANCY); //necromancy amplifier
addBonusIfBuilt(BuildingID::GRAIL, Bonus::SECONDARY_SKILL_PREMY, +20, playerProp, SecondarySkill::NECROMANCY); //Soul prison
}
else if(subID == ETownType::DUNGEON) //Dungeon
{
addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +12, PrimarySkill::SPELL_POWER); //grail
}
else if(subID == ETownType::STRONGHOLD) //Stronghold
{
addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +20, PrimarySkill::ATTACK); //grail
}
else if(subID == ETownType::FORTRESS) //Fortress
{
addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::ATTACK); //grail
addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::DEFENSE); //grail
}
else if(boost::algorithm::ends_with(this->town->faction->identifier, ":cove") && hasBuilt(BuildingID::GRAIL))
{
static TPropagatorPtr lsProp(new CPropagatorNodeType(ALL_CREATURES));
static auto factionLimiter = std::make_shared<CreatureFactionLimiter>(this->town->faction->index);
auto b = std::make_shared<Bonus>(Bonus::PERMANENT, Bonus::NO_TERRAIN_PENALTY, Bonus::TOWN_STRUCTURE, 0, BuildingID::GRAIL, "", -1);
if(building->buildingBonuses.empty())
continue;
b->addLimiter(factionLimiter);
b->addPropagator(lsProp);
VLC->creh->addBonusForAllCreatures(b);
}
}
bool CGTownInstance::addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype)
{
BuildingID currentBid = BuildingID::NONE;
std::ostringstream descr;
for(const auto & bid : builtBuildings)
{
if(town->buildings.at(bid)->subId == subId)
for(auto bonus : building->buildingBonuses)
{
descr << town->buildings.at(bid)->Name();
currentBid = bid;
break;
if(bonus->propagator != nullptr && bonus->propagator->getPropagatorType() == ALL_CREATURES)
VLC->creh->addBonusForAllCreatures(bonus);
else
addNewBonus(bonus);
}
}
return currentBid == BuildingID::NONE ? false
: addBonusImpl(currentBid, type, val, prop, descr.str(), subtype);
}
bool CGTownInstance::addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, int subtype)
{
return addBonusIfBuilt(subId, type, val, emptyPropagator, subtype);
}
bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype)
{
if(!hasBuilt(building))
return false;
std::ostringstream descr;
descr << town->buildings.at(building)->Name();
return addBonusImpl(building, type, val, prop, descr.str(), subtype);
}
bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype)
{
return addBonusIfBuilt(building, type, val, emptyPropagator, subtype);
}
bool CGTownInstance::addBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype)
{
auto b = std::make_shared<Bonus>(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, description, subtype);
if(prop)
b->addPropagator(prop);
addNewBonus(b);
return true;
}
void CGTownInstance::setVisitingHero(CGHeroInstance *h)
@ -1738,11 +1719,11 @@ void CTownBonus::setProperty (ui8 what, ui32 val)
void CTownBonus::onHeroVisit(const CGHeroInstance * h) const
{
ObjectInstanceID heroID = h->id;
if (town->hasBuilt(bID) && visitors.find(heroID) == visitors.end())
if(town->hasBuilt(bID) && visitors.find(heroID) == visitors.end())
{
si64 val = 0;
InfoWindow iw;
PrimarySkill::PrimarySkill what = PrimarySkill::ATTACK;
PrimarySkill::PrimarySkill what = PrimarySkill::NONE;
switch (bType)
{
@ -1775,15 +1756,51 @@ void CTownBonus::onHeroVisit(const CGHeroInstance * h) const
val = 1;
iw.components.push_back(Component(Component::PRIM_SKILL, 1, 1, 0));
break;
case BuildingSubID::CUSTOM_VISITING_BONUS:
const auto building = town->town->buildings.at(bID);
if(!h->hasBonusFrom(Bonus::TOWN_STRUCTURE, Bonus::getSid32(building->town->faction->index, building->bid)))
{
const auto & bonuses = building->onVisitBonuses;
applyBonuses(const_cast<CGHeroInstance *>(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);
}
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(auto bonus : bonuses)
{
GiveBonus gb;
InfoWindow iw;
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,3 +1868,22 @@ const std::string CGTownBuilding::getVisitingBonusGreeting() const
town->town->setGreeting(bType, bonusGreeting);
return bonusGreeting;
}
const std::string CGTownBuilding::getCustomBonusGreeting(const Bonus & bonus)
{
auto bonusGreeting = std::string(VLC->generaltexth->localizedTexts["townHall"]["greetingCustomBonus"].String()); //"%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 == (ui16)Bonus::ONE_BATTLE
? VLC->generaltexth->localizedTexts["townHall"]["greetingCustomUntil"].String() : ".";
boost::format fmt = boost::format(bonusGreeting) % bonus.description % bonus.val % param % until;
std::string greeting = fmt.str();
return greeting;
}

View File

@ -133,6 +133,7 @@ protected:
BuildingSubID::EBuildingSubID bType;
const std::string getVisitingBonusGreeting() const;
static const std::string getCustomBonusGreeting(const Bonus & bonus);
};
class DLL_LINKAGE COPWBonus : public CGTownBuilding
@ -169,6 +170,9 @@ public:
h & static_cast<CGTownBuilding&>(*this);
h & visitors;
}
private:
void applyBonuses(CGHeroInstance * h, const BonusList & bonuses) const;
};
class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode
@ -205,7 +209,9 @@ public:
ConstTransitivePtr<CGHeroInstance> garrisonHero, visitingHero;
ui32 identifier; //special identifier from h3m (only > RoE maps)
si32 alignment;
std::set<BuildingID> forbiddenBuildings, builtBuildings;
std::set<BuildingID> forbiddenBuildings;
std::set<BuildingID> builtBuildings;
std::set<BuildingID> overriddenBuildings; ///buildings which bonuses are overridden and should not be applied
std::vector<CGTownBuilding*> bonusingBuildings;
std::vector<SpellID> possibleSpells, obligatorySpells;
std::vector<std::vector<SpellID> > spells; //spells[level] -> vector of spells, first will be available in guild
@ -237,8 +243,8 @@ public:
h & events;
h & bonusingBuildings;
for (std::vector<CGTownBuilding*>::iterator i = bonusingBuildings.begin(); i!=bonusingBuildings.end(); i++)
(*i)->town = this;
for(auto * bonusingBuilding : bonusingBuildings)
bonusingBuilding->town = this;
h & town;
h & townAndVis;
@ -256,6 +262,11 @@ public:
if(!h.saving && version < 793)
updateBonusingBuildings();
if(version >= 794)
h & overriddenBuildings;
else if(!h.saving)
updateTown794();
}
//////////////////////////////////////////////////////////////////////////
@ -264,11 +275,6 @@ public:
void updateMoraleBonusFromArmy() override;
void deserializationFix();
void recreateBuildingsBonuses();
///bid: param to bind a building with a bonus, subId: param to check if already built
bool addBonusIfBuilt(BuildingSubID::EBuildingSubID subId, Bonus::BonusType type, int val, TPropagatorPtr & prop, 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);
void setGarrisonedHero(CGHeroInstance *h);
const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself
@ -318,6 +324,7 @@ 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
void deleteTownBonus(BuildingID::EBuildingID bid);
const CTown * getTown() const ;
@ -336,7 +343,6 @@ public:
static void reset();
protected:
static TPropagatorPtr emptyPropagator;
void setPropertyDer(ui8 what, ui32 val) override;
void serializeJsonOptions(JsonSerializeFormat & handler) override;
@ -344,9 +350,10 @@ 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;
void tryAddOnePerWeekBonus(BuildingSubID::EBuildingSubID subID);
void tryAddVisitingBonus(BuildingSubID::EBuildingSubID subID);
void initOverriddenBids();
void addTownBonuses();
void updateTown794(); //populate overriddenBuildings and vanila bonuses for old saves
};

View File

@ -260,7 +260,7 @@ void CGObjectInstance::giveDummyBonus(ObjectInstanceID heroID, ui8 duration) con
GiveBonus gbonus;
gbonus.bonus.type = Bonus::NONE;
gbonus.id = heroID.getNum();
gbonus.bonus.duration = duration;
gbonus.bonus.duration = (Bonus::BonusDuration)duration;
gbonus.bonus.source = Bonus::OBJECT;
gbonus.bonus.sid = ID;
cb->giveHeroBonus(&gbonus);

View File

@ -12,7 +12,7 @@
#include "../ConstTransitivePtr.h"
#include "../GameConstants.h"
const ui32 SERIALIZATION_VERSION = 793;
const ui32 SERIALIZATION_VERSION = 794;
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
const std::string SAVEGAME_MAGIC = "VCMISVG";

View File

@ -3147,16 +3147,14 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
processBeforeBuiltStructure(builtID);
//Take cost
if (!force)
{
if(!force)
giveResources(t->tempOwner, -requestedBuilding->resources);
}
//We know what has been built, appluy changes. Do this as final step to properly update town window
//We know what has been built, apply changes. Do this as final step to properly update town window
sendAndApply(&ns);
//Other post-built events. To some logic like giving spells to work gamestate changes for new building must be already in place!
for (auto builtID : ns.bid)
for(auto builtID : ns.bid)
processAfterBuiltStructure(builtID);
// now when everything is built - reveal tiles for lookout tower
@ -3166,14 +3164,15 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
getTilesInRange(fw.tiles, t->getSightCenter(), t->getSightRadius(), t->tempOwner, 1);
sendAndApply(&fw);
if (t->visitingHero)
if(t->visitingHero)
visitCastleObjects(t, t->visitingHero);
if (t->garrisonHero)
if(t->garrisonHero)
visitCastleObjects(t, t->garrisonHero);
checkVictoryLossConditionsForPlayer(t->tempOwner);
return true;
}
bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid)
{
///incomplete, simply erases target building