1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-28 23:06:24 +02:00
vcmi/lib/CTownHandler.cpp
Konstantin 95503d0623 vcmi: unify movement
1. Now there is only one bonus: MOVEMENT, with 2 subtypes: 0 is sea, 1 is land
   For movement value on land depends on creature speed we use a new
   ARMY_MOVEMENT updater with global bonus. If we does not like such
   dependency, we can just remove this updater from json.
2. All specialities and secondary skills for movement moved to new
   system AFAIK
2023-03-16 16:46:41 +03:00

1215 lines
36 KiB
C++

/*
* CTownHandler.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 "CTownHandler.h"
#include "VCMI_Lib.h"
#include "CGeneralTextHandler.h"
#include "JsonNode.h"
#include "StringConstants.h"
#include "CCreatureHandler.h"
#include "CModHandler.h"
#include "CHeroHandler.h"
#include "CArtHandler.h"
#include "spells/CSpellHandler.h"
#include "filesystem/Filesystem.h"
#include "mapObjects/CObjectClassesHandler.h"
#include "mapObjects/CObjectHandler.h"
#include "HeroBonus.h"
VCMI_LIB_NAMESPACE_BEGIN
const int NAMES_PER_TOWN=16; // number of town names per faction in H3 files. Json can define any number
const std::map<std::string, CBuilding::EBuildMode> CBuilding::MODES =
{
{ "normal", CBuilding::BUILD_NORMAL },
{ "auto", CBuilding::BUILD_AUTO },
{ "special", CBuilding::BUILD_SPECIAL },
{ "grail", CBuilding::BUILD_GRAIL }
};
const std::map<std::string, CBuilding::ETowerHeight> CBuilding::TOWER_TYPES =
{
{ "low", CBuilding::HEIGHT_LOW },
{ "average", CBuilding::HEIGHT_AVERAGE },
{ "high", CBuilding::HEIGHT_HIGH },
{ "skyship", CBuilding::HEIGHT_SKYSHIP }
};
std::string CBuilding::getJsonKey() const
{
return modScope + ':' + identifier;;
}
std::string CBuilding::getNameTranslated() const
{
return VLC->generaltexth->translate(getNameTextID());
}
std::string CBuilding::getDescriptionTranslated() const
{
return VLC->generaltexth->translate(getDescriptionTextID());
}
std::string CBuilding::getNameTextID() const
{
return TextIdentifier("building", modScope, town->faction->identifier, identifier, "name").get();
}
std::string CBuilding::getDescriptionTextID() const
{
return TextIdentifier("building", modScope, town->faction->identifier, identifier, "description").get();
}
BuildingID CBuilding::getBase() const
{
const CBuilding * build = this;
while (build->upgrade >= 0)
{
build = build->town->buildings.at(build->upgrade);
}
return build->bid;
}
si32 CBuilding::getDistance(BuildingID buildID) const
{
const CBuilding * build = town->buildings.at(buildID);
int distance = 0;
while (build->upgrade >= 0 && build != this)
{
build = build->town->buildings.at(build->upgrade);
distance++;
}
if (build == this)
return distance;
return -1;
}
void CBuilding::addNewBonus(std::shared_ptr<Bonus> b, BonusList & bonusList)
{
bonusList.push_back(b);
}
CFaction::CFaction()
{
town = nullptr;
index = 0;
alignment = EAlignment::NEUTRAL;
preferUndergroundPlacement = false;
}
CFaction::~CFaction()
{
delete town;
}
int32_t CFaction::getIndex() const
{
return index;
}
int32_t CFaction::getIconIndex() const
{
return index; //???
}
std::string CFaction::getJsonKey() const
{
return modScope + ':' + identifier;;
}
void CFaction::registerIcons(const IconRegistar & cb) const
{
if(town)
{
auto & info = town->clientInfo;
cb(info.icons[0][0], 0, "ITPT", info.iconLarge[0][0]);
cb(info.icons[0][1], 0, "ITPT", info.iconLarge[0][1]);
cb(info.icons[1][0], 0, "ITPT", info.iconLarge[1][0]);
cb(info.icons[1][1], 0, "ITPT", info.iconLarge[1][1]);
cb(info.icons[0][0] + 2, 0, "ITPA", info.iconSmall[0][0]);
cb(info.icons[0][1] + 2, 0, "ITPA", info.iconSmall[0][1]);
cb(info.icons[1][0] + 2, 0, "ITPA", info.iconSmall[1][0]);
cb(info.icons[1][1] + 2, 0, "ITPA", info.iconSmall[1][1]);
cb(index, 1, "CPRSMALL", info.towerIconSmall);
cb(index, 1, "TWCRPORT", info.towerIconLarge);
}
}
std::string CFaction::getNameTranslated() const
{
return VLC->generaltexth->translate(getNameTextID());
}
std::string CFaction::getNameTextID() const
{
return TextIdentifier("faction", modScope, identifier, "name").get();
}
FactionID CFaction::getId() const
{
return FactionID(index);
}
bool CFaction::hasTown() const
{
return town != nullptr;
}
void CFaction::updateFrom(const JsonNode & data)
{
}
void CFaction::serializeJson(JsonSerializeFormat & handler)
{
}
CTown::CTown()
: faction(nullptr), mageLevel(0), primaryRes(0), moatDamage(0), defaultTavernChance(0)
{
}
CTown::~CTown()
{
for(auto & build : buildings)
build.second.dellNull();
for(auto & str : clientInfo.structures)
str.dellNull();
}
std::string CTown::getRandomNameTranslated(size_t index) const
{
return VLC->generaltexth->translate(getRandomNameTextID(index));
}
std::string CTown::getRandomNameTextID(size_t index) const
{
return TextIdentifier("faction", faction->modScope, faction->identifier, "randomName", index).get();
}
size_t CTown::getRandomNamesCount() const
{
return namesCount;
}
std::string CTown::getBuildingScope() const
{
if(faction == nullptr)
//no faction == random faction
return "building";
else
return "building." + faction->getJsonKey();
}
std::set<si32> CTown::getAllBuildings() const
{
std::set<si32> res;
for(const auto & b : buildings)
{
res.insert(b.first.num);
}
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;
}
const std::string CTown::getGreeting(BuildingSubID::EBuildingSubID subID) const
{
return VLC->townh->getMappedValue<const std::string, BuildingSubID::EBuildingSubID>(subID, std::string(), specialMessages, false);
}
void CTown::setGreeting(BuildingSubID::EBuildingSubID subID, const std::string message) const
{
specialMessages.insert(std::pair<BuildingSubID::EBuildingSubID, const std::string>(subID, message));
}
CTownHandler::CTownHandler()
{
randomTown = new CTown();
randomFaction = new CFaction();
randomFaction->town = randomTown;
randomTown->faction = randomFaction;
randomFaction->identifier = "random";
randomFaction->modScope = "core";
}
CTownHandler::~CTownHandler()
{
delete randomTown;
}
JsonNode readBuilding(CLegacyConfigParser & parser)
{
JsonNode ret;
JsonNode & cost = ret["cost"];
//note: this code will try to parse mithril as well but wil always return 0 for it
for(const std::string & resID : GameConstants::RESOURCE_NAMES)
cost[resID].Float() = parser.readNumber();
cost.Struct().erase("mithril"); // erase mithril to avoid confusing validator
parser.endLine();
return ret;
}
TPropagatorPtr & CTownHandler::emptyPropagator()
{
static TPropagatorPtr emptyProp(nullptr);
return emptyProp;
}
std::vector<JsonNode> CTownHandler::loadLegacyData(size_t dataSize)
{
std::vector<JsonNode> dest(dataSize);
objects.resize(dataSize);
auto getBuild = [&](size_t town, size_t building) -> JsonNode &
{
return dest[town]["town"]["buildings"][EBuildingType::names[building]];
};
CLegacyConfigParser parser("DATA/BUILDING.TXT");
parser.endLine(); // header
parser.endLine();
//Unique buildings
for (size_t town=0; town<dataSize; town++)
{
parser.endLine(); //header
parser.endLine();
int buildID = 17;
do
{
getBuild(town, buildID) = readBuilding(parser);
buildID++;
}
while (!parser.isNextEntryEmpty());
}
// Common buildings
parser.endLine(); // header
parser.endLine();
parser.endLine();
int buildID = 0;
do
{
JsonNode building = readBuilding(parser);
for (size_t town=0; town<dataSize; town++)
getBuild(town, buildID) = building;
buildID++;
}
while (!parser.isNextEntryEmpty());
parser.endLine(); //header
parser.endLine();
//Dwellings
for (size_t town=0; town<dataSize; town++)
{
parser.endLine(); //header
parser.endLine();
for (size_t i=0; i<14; i++)
{
getBuild(town, 30+i) = readBuilding(parser);
}
}
{
CLegacyConfigParser parser("DATA/BLDGNEUT.TXT");
for(int building=0; building<15; building++)
{
std::string name = parser.readString();
std::string descr = parser.readString();
parser.endLine();
for(int j=0; j<dataSize; j++)
{
getBuild(j, building)["name"].String() = name;
getBuild(j, building)["description"].String() = descr;
}
}
parser.endLine(); // silo
parser.endLine(); // blacksmith //unused entries
parser.endLine(); // moat
//shipyard with the ship
std::string name = parser.readString();
std::string descr = parser.readString();
parser.endLine();
for(int town=0; town<dataSize; town++)
{
getBuild(town, 20)["name"].String() = name;
getBuild(town, 20)["description"].String() = descr;
}
//blacksmith
for(int town=0; town<dataSize; town++)
{
getBuild(town, 16)["name"].String() = parser.readString();
getBuild(town, 16)["description"].String() = parser.readString();
parser.endLine();
}
}
{
CLegacyConfigParser parser("DATA/BLDGSPEC.TXT");
for(int town=0; town<dataSize; town++)
{
for(int build=0; build<9; build++)
{
getBuild(town, 17 + build)["name"].String() = parser.readString();
getBuild(town, 17 + build)["description"].String() = parser.readString();
parser.endLine();
}
getBuild(town, 26)["name"].String() = parser.readString(); // Grail
getBuild(town, 26)["description"].String() = parser.readString();
parser.endLine();
getBuild(town, 15)["name"].String() = parser.readString(); // Resource silo
getBuild(town, 15)["description"].String() = parser.readString();
parser.endLine();
}
}
{
CLegacyConfigParser parser("DATA/DWELLING.TXT");
for(int town=0; town<dataSize; town++)
{
for(int build=0; build<14; build++)
{
getBuild(town, 30 + build)["name"].String() = parser.readString();
getBuild(town, 30 + build)["description"].String() = parser.readString();
parser.endLine();
}
}
}
{
CLegacyConfigParser typeParser("DATA/TOWNTYPE.TXT");
CLegacyConfigParser nameParser("DATA/TOWNNAME.TXT");
size_t townID=0;
do
{
dest[townID]["name"].String() = typeParser.readString();
for (int i=0; i<NAMES_PER_TOWN; i++)
{
JsonNode name;
name.String() = nameParser.readString();
dest[townID]["town"]["names"].Vector().push_back(name);
nameParser.endLine();
}
townID++;
}
while (typeParser.endLine());
}
return dest;
}
void CTownHandler::loadBuildingRequirements(CBuilding * building, const JsonNode & source, std::vector<BuildingRequirementsHelper> & bidsToLoad)
{
if (source.isNull())
return;
BuildingRequirementsHelper hlp;
hlp.building = building;
hlp.town = building->town;
hlp.json = source;
bidsToLoad.push_back(hlp);
}
template<typename R, typename K>
R CTownHandler::getMappedValue(const K key, const R defval, const std::map<K, R> & map, bool required)
{
auto it = map.find(key);
if(it != map.end())
return it->second;
if(required)
logMod->warn("Warning: Property: '%s' is unknown. Correct the typo or update VCMI.", key);
return defval;
}
template<typename R>
R CTownHandler::getMappedValue(const JsonNode & node, const R defval, const std::map<std::string, R> & map, bool required)
{
if(!node.isNull() && node.getType() == JsonNode::JsonType::DATA_STRING)
return getMappedValue<R, std::string>(node.String(), defval, map, required);
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->getJsonKey(), ":cove"))
{
static TPropagatorPtr allCreaturesPropagator(new CPropagatorNodeType(CBonusSystemNode::ENodeTypes::ALL_CREATURES));
static auto factionLimiter = std::make_shared<CreatureFactionLimiter>(building->town->faction->getIndex());
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::MOVEMENT, +500, playerPropagator, 0);
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->getNameTranslated();
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->getNameTranslated());
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->getIndex();
}
//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)
{
assert(stringID.find(':') == std::string::npos);
assert(!source.meta.empty());
auto ret = new CBuilding();
ret->bid = getMappedValue<BuildingID, std::string>(stringID, BuildingID::NONE, MappedKeys::BUILDING_NAMES_TO_TYPES, false);
if(ret->bid == BuildingID::NONE)
ret->bid = source["id"].isNull() ? BuildingID(BuildingID::NONE) : BuildingID(source["id"].Float());
if (ret->bid == BuildingID::NONE)
logMod->error("Error: Building '%s' has not internal ID and won't work properly. Correct the typo or update VCMI.", stringID);
ret->mode = ret->bid == BuildingID::GRAIL
? CBuilding::BUILD_GRAIL
: getMappedValue<CBuilding::EBuildMode>(source["mode"], CBuilding::BUILD_NORMAL, CBuilding::MODES);
ret->subId = getMappedValue<BuildingSubID::EBuildingSubID>(source["type"], BuildingSubID::NONE, MappedKeys::SPECIAL_BUILDINGS);
ret->height = CBuilding::HEIGHT_NO_TOWER;
if(ret->subId == BuildingSubID::LOOKOUT_TOWER
|| ret->bid == BuildingID::GRAIL)
ret->height = getMappedValue<CBuilding::ETowerHeight>(source["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES);
ret->identifier = stringID;
ret->modScope = source.meta;
ret->town = town;
VLC->generaltexth->registerString(source.meta, ret->getNameTextID(), source["name"].String());
VLC->generaltexth->registerString(source.meta, ret->getDescriptionTextID(), source["description"].String());
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->getIndex(), ret->bid);
}
}
//MODS COMPATIBILITY FOR 0.96
if(!ret->produce.nonZero())
{
switch (ret->bid) {
break; case BuildingID::VILLAGE_HALL: ret->produce[Res::GOLD] = 500;
break; case BuildingID::TOWN_HALL : ret->produce[Res::GOLD] = 1000;
break; case BuildingID::CITY_HALL : ret->produce[Res::GOLD] = 2000;
break; case BuildingID::CAPITOL : ret->produce[Res::GOLD] = 4000;
break; case BuildingID::GRAIL : ret->produce[Res::GOLD] = 5000;
break; case BuildingID::RESOURCE_SILO :
{
switch (ret->town->primaryRes)
{
case Res::GOLD:
ret->produce[ret->town->primaryRes] = 500;
break;
case Res::WOOD_AND_ORE:
ret->produce[Res::WOOD] = 1;
ret->produce[Res::ORE] = 1;
break;
default:
ret->produce[ret->town->primaryRes] = 1;
break;
}
}
}
}
loadBuildingRequirements(ret, source["requires"], requirementsToLoad);
if(ret->bid.IsSpecialOrGrail())
loadBuildingRequirements(ret, source["overrides"], overriddenBidsToLoad);
if (!source["upgrades"].isNull())
{
// building id and upgrades can't be the same
if(stringID == source["upgrades"].String())
{
throw std::runtime_error(boost::str(boost::format("Building with ID '%s' of town '%s' can't be an upgrade of the same building.") %
stringID % ret->town->faction->getNameTranslated()));
}
VLC->modh->identifiers.requestIdentifier(ret->town->getBuildingScope(), source["upgrades"], [=](si32 identifier)
{
ret->upgrade = BuildingID(identifier);
});
}
else
ret->upgrade = BuildingID::NONE;
ret->town->buildings[ret->bid] = ret;
registerObject(source.meta, ret->town->getBuildingScope(), ret->identifier, ret->bid);
}
void CTownHandler::loadBuildings(CTown * town, const JsonNode & source)
{
for(auto & node : source.Struct())
{
if (!node.second.isNull())
{
loadBuilding(town, node.first, node.second);
}
}
}
void CTownHandler::loadStructure(CTown &town, const std::string & stringID, const JsonNode & source)
{
auto ret = new CStructure();
ret->building = nullptr;
ret->buildable = nullptr;
VLC->modh->identifiers.tryRequestIdentifier( source.meta, "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable
{
ret->building = town.buildings[BuildingID(identifier)];
});
if (source["builds"].isNull())
{
VLC->modh->identifiers.tryRequestIdentifier( source.meta, "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable
{
ret->building = town.buildings[BuildingID(identifier)];
});
}
else
{
VLC->modh->identifiers.requestIdentifier("building." + town.faction->getJsonKey(), source["builds"], [=, &town](si32 identifier) mutable
{
ret->buildable = town.buildings[BuildingID(identifier)];
});
}
ret->identifier = stringID;
ret->pos.x = static_cast<si32>(source["x"].Float());
ret->pos.y = static_cast<si32>(source["y"].Float());
ret->pos.z = static_cast<si32>(source["z"].Float());
ret->hiddenUpgrade = source["hidden"].Bool();
ret->defName = source["animation"].String();
ret->borderName = source["border"].String();
ret->areaName = source["area"].String();
town.clientInfo.structures.push_back(ret);
}
void CTownHandler::loadStructures(CTown &town, const JsonNode & source)
{
for(auto &node : source.Struct())
{
if (!node.second.isNull())
loadStructure(town, node.first, node.second);
}
}
void CTownHandler::loadTownHall(CTown &town, const JsonNode & source)
{
auto & dstSlots = town.clientInfo.hallSlots;
auto & srcSlots = source.Vector();
dstSlots.resize(srcSlots.size());
for(size_t i=0; i<dstSlots.size(); i++)
{
auto & dstRow = dstSlots[i];
auto & srcRow = srcSlots[i].Vector();
dstRow.resize(srcRow.size());
for(size_t j=0; j < dstRow.size(); j++)
{
auto & dstBox = dstRow[j];
auto & srcBox = srcRow[j].Vector();
dstBox.resize(srcBox.size());
for(size_t k=0; k<dstBox.size(); k++)
{
auto & dst = dstBox[k];
auto & src = srcBox[k];
VLC->modh->identifiers.requestIdentifier("building." + town.faction->getJsonKey(), src, [&](si32 identifier)
{
dst = BuildingID(identifier);
});
}
}
}
}
Point JsonToPoint(const JsonNode & node)
{
Point ret;
ret.x = static_cast<si32>(node["x"].Float());
ret.y = static_cast<si32>(node["y"].Float());
return ret;
}
void CTownHandler::loadSiegeScreen(CTown &town, const JsonNode & source)
{
town.clientInfo.siegePrefix = source["imagePrefix"].String();
town.clientInfo.towerIconSmall = source["towerIconSmall"].String();
town.clientInfo.towerIconLarge = source["towerIconLarge"].String();
VLC->modh->identifiers.requestIdentifier("creature", source["shooter"], [&town](si32 creature)
{
auto crId = CreatureID(creature);
if(!(*VLC->creh)[crId]->animation.missleFrameAngles.size())
logMod->error("Mod '%s' error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Siege will not work properly!"
, town.faction->getNameTranslated()
, (*VLC->creh)[crId]->getNameSingularTranslated());
town.clientInfo.siegeShooter = crId;
});
auto & pos = town.clientInfo.siegePositions;
pos.resize(21);
pos[8] = JsonToPoint(source["towers"]["top"]["tower"]);
pos[17] = JsonToPoint(source["towers"]["top"]["battlement"]);
pos[20] = JsonToPoint(source["towers"]["top"]["creature"]);
pos[2] = JsonToPoint(source["towers"]["keep"]["tower"]);
pos[15] = JsonToPoint(source["towers"]["keep"]["battlement"]);
pos[18] = JsonToPoint(source["towers"]["keep"]["creature"]);
pos[3] = JsonToPoint(source["towers"]["bottom"]["tower"]);
pos[16] = JsonToPoint(source["towers"]["bottom"]["battlement"]);
pos[19] = JsonToPoint(source["towers"]["bottom"]["creature"]);
pos[9] = JsonToPoint(source["gate"]["gate"]);
pos[10] = JsonToPoint(source["gate"]["arch"]);
pos[7] = JsonToPoint(source["walls"]["upper"]);
pos[6] = JsonToPoint(source["walls"]["upperMid"]);
pos[5] = JsonToPoint(source["walls"]["bottomMid"]);
pos[4] = JsonToPoint(source["walls"]["bottom"]);
pos[13] = JsonToPoint(source["moat"]["moat"]);
pos[14] = JsonToPoint(source["moat"]["bank"]);
pos[11] = JsonToPoint(source["static"]["bottom"]);
pos[12] = JsonToPoint(source["static"]["top"]);
pos[1] = JsonToPoint(source["static"]["background"]);
}
static void readIcon(JsonNode source, std::string & small, std::string & large)
{
if (source.getType() == JsonNode::JsonType::DATA_STRUCT) // don't crash on old format
{
small = source["small"].String();
large = source["large"].String();
}
}
void CTownHandler::loadClientData(CTown &town, const JsonNode & source)
{
CTown::ClientInfo & info = town.clientInfo;
readIcon(source["icons"]["village"]["normal"], info.iconSmall[0][0], info.iconLarge[0][0]);
readIcon(source["icons"]["village"]["built"], info.iconSmall[0][1], info.iconLarge[0][1]);
readIcon(source["icons"]["fort"]["normal"], info.iconSmall[1][0], info.iconLarge[1][0]);
readIcon(source["icons"]["fort"]["built"], info.iconSmall[1][1], info.iconLarge[1][1]);
info.hallBackground = source["hallBackground"].String();
info.musicTheme = source["musicTheme"].String();
info.townBackground = source["townBackground"].String();
info.guildWindow = source["guildWindow"].String();
info.buildingsIcons = source["buildingsIcons"].String();
//left for back compatibility - will be removed later
if(!source["guildBackground"].String().empty())
info.guildBackground = source["guildBackground"].String();
else
info.guildBackground = "TPMAGE.bmp";
if(!source["tavernVideo"].String().empty())
info.tavernVideo = source["tavernVideo"].String();
else
info.tavernVideo = "TAVERN.BIK";
//end of legacy assignment
loadTownHall(town, source["hallSlots"]);
loadStructures(town, source["structures"]);
loadSiegeScreen(town, source["siege"]);
}
void CTownHandler::loadTown(CTown * town, const JsonNode & source)
{
auto resIter = boost::find(GameConstants::RESOURCE_NAMES, source["primaryResource"].String());
if(resIter == std::end(GameConstants::RESOURCE_NAMES))
town->primaryRes = Res::WOOD_AND_ORE; //Wood + Ore
else
town->primaryRes = static_cast<ui16>(resIter - std::begin(GameConstants::RESOURCE_NAMES));
warMachinesToLoad[town] = source["warMachine"];
town->moatDamage = static_cast<si32>(source["moatDamage"].Float());
town->moatHexes = source["moatHexes"].convertTo<std::vector<BattleHex> >();
town->mageLevel = static_cast<ui32>(source["mageGuild"].Float());
town->namesCount = 0;
for (auto const & name : source["names"].Vector())
{
VLC->generaltexth->registerString(town->faction->modScope, town->getRandomNameTextID(town->namesCount), name.String());
town->namesCount += 1;
}
// Horde building creature level
for(const JsonNode &node : source["horde"].Vector())
town->hordeLvl[(int)town->hordeLvl.size()] = static_cast<int>(node.Float());
// town needs to have exactly 2 horde entries. Validation will take care of 2+ entries
// but anything below 2 must be handled here
for (size_t i=source["horde"].Vector().size(); i<2; i++)
town->hordeLvl[(int)i] = -1;
const JsonVector & creatures = source["creatures"].Vector();
town->creatures.resize(creatures.size());
for (size_t i=0; i< creatures.size(); i++)
{
const JsonVector & level = creatures[i].Vector();
town->creatures[i].resize(level.size());
for (size_t j=0; j<level.size(); j++)
{
VLC->modh->identifiers.requestIdentifier("creature", level[j], [=](si32 creature)
{
town->creatures[i][j] = CreatureID(creature);
});
}
}
town->defaultTavernChance = static_cast<ui32>(source["defaultTavern"].Float());
/// set chance of specific hero class to appear in this town
for(auto &node : source["tavern"].Struct())
{
int chance = static_cast<int>(node.second.Float());
VLC->modh->identifiers.requestIdentifier(node.second.meta, "heroClass",node.first, [=](si32 classID)
{
VLC->heroh->classes[HeroClassID(classID)]->selectionProbability[town->faction->getIndex()] = chance;
});
}
for(auto &node : source["guildSpells"].Struct())
{
int chance = static_cast<int>(node.second.Float());
VLC->modh->identifiers.requestIdentifier(node.second.meta, "spell", node.first, [=](si32 spellID)
{
VLC->spellh->objects.at(spellID)->probabilities[town->faction->getIndex()] = chance;
});
}
for(const JsonNode & d : source["adventureMap"]["dwellings"].Vector())
{
town->dwellings.push_back(d["graphics"].String());
town->dwellingNames.push_back(d["name"].String());
}
loadBuildings(town, source["buildings"]);
loadClientData(*town, source);
}
void CTownHandler::loadPuzzle(CFaction &faction, const JsonNode &source)
{
faction.puzzleMap.reserve(GameConstants::PUZZLE_MAP_PIECES);
std::string prefix = source["prefix"].String();
for(const JsonNode &piece : source["pieces"].Vector())
{
size_t index = faction.puzzleMap.size();
SPuzzleInfo spi;
spi.x = static_cast<si16>(piece["x"].Float());
spi.y = static_cast<si16>(piece["y"].Float());
spi.whenUncovered = static_cast<ui16>(piece["index"].Float());
spi.number = static_cast<ui16>(index);
// filename calculation
std::ostringstream suffix;
suffix << std::setfill('0') << std::setw(2) << index;
spi.filename = prefix + suffix.str();
faction.puzzleMap.push_back(spi);
}
assert(faction.puzzleMap.size() == GameConstants::PUZZLE_MAP_PIECES);
}
CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode & source, const std::string & identifier, size_t index)
{
assert(identifier.find(':') == std::string::npos);
auto faction = new CFaction();
faction->index = static_cast<TFaction>(index);
faction->modScope = scope;
faction->identifier = identifier;
VLC->generaltexth->registerString(scope, faction->getNameTextID(), source["name"].String());
faction->creatureBg120 = source["creatureBackground"]["120px"].String();
faction->creatureBg130 = source["creatureBackground"]["130px"].String();
int alignment = vstd::find_pos(EAlignment::names, source["alignment"].String());
if (alignment == -1)
faction->alignment = EAlignment::NEUTRAL;
else
faction->alignment = static_cast<EAlignment::EAlignment>(alignment);
auto preferUndergound = source["preferUndergroundPlacement"];
faction->preferUndergroundPlacement = preferUndergound.isNull() ? false : preferUndergound.Bool();
// NOTE: semi-workaround - normally, towns are supposed to have native terrains.
// Towns without one are exceptions. So, vcmi requires nativeTerrain to be defined
// But allows it to be defined with explicit value of "none" if town should not have native terrain
// This is better than allowing such terrain-less towns silently, leading to issues with RMG
faction->nativeTerrain = ETerrainId::NONE;
if ( !source["nativeTerrain"].isNull() && source["nativeTerrain"].String() != "none")
{
VLC->modh->identifiers.requestIdentifier("terrain", source["nativeTerrain"], [=](int32_t index){
faction->nativeTerrain = TerrainId(index);
});
}
if (!source["town"].isNull())
{
faction->town = new CTown();
faction->town->faction = faction;
loadTown(faction->town, source["town"]);
}
else
faction->town = nullptr;
if (!source["puzzleMap"].isNull())
loadPuzzle(*faction, source["puzzleMap"]);
return faction;
}
void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
{
auto object = loadFromJson(scope, data, name, objects.size());
objects.push_back(object);
if (object->town)
{
auto & info = object->town->clientInfo;
info.icons[0][0] = 8 + object->index * 4 + 0;
info.icons[0][1] = 8 + object->index * 4 + 1;
info.icons[1][0] = 8 + object->index * 4 + 2;
info.icons[1][1] = 8 + object->index * 4 + 3;
VLC->modh->identifiers.requestIdentifier(scope, "object", "town", [=](si32 index)
{
// register town once objects are loaded
JsonNode config = data["town"]["mapObject"];
config["faction"].String() = name;
config["faction"].meta = scope;
if (config.meta.empty())// MODS COMPATIBILITY FOR 0.96
config.meta = scope;
VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index);
// MODS COMPATIBILITY FOR 0.96
auto & advMap = data["town"]["adventureMap"];
if (!advMap.isNull())
{
logMod->warn("Outdated town mod. Will try to generate valid templates out of fort");
JsonNode config;
config["animation"] = advMap["castle"];
VLC->objtypeh->getHandlerFor(index, object->index)->addTemplate(config);
}
});
}
registerObject(scope, "faction", name, object->index);
}
void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
{
auto object = loadFromJson(scope, data, name, index);
if (objects.size() > index)
assert(objects[index] == nullptr); // ensure that this id was not loaded before
else
objects.resize(index + 1);
objects[index] = object;
if (object->town)
{
auto & info = object->town->clientInfo;
info.icons[0][0] = (GameConstants::F_NUMBER + object->index) * 2 + 0;
info.icons[0][1] = (GameConstants::F_NUMBER + object->index) * 2 + 1;
info.icons[1][0] = object->index * 2 + 0;
info.icons[1][1] = object->index * 2 + 1;
VLC->modh->identifiers.requestIdentifier(scope, "object", "town", [=](si32 index)
{
// register town once objects are loaded
JsonNode config = data["town"]["mapObject"];
config["faction"].String() = name;
config["faction"].meta = scope;
VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index);
});
}
registerObject(scope, "faction", name, object->index);
}
void CTownHandler::loadRandomFaction()
{
static const ResourceID randomFactionPath("config/factions/random.json");
JsonNode randomFactionJson(randomFactionPath);
randomFactionJson.setMeta(CModHandler::scopeBuiltin(), true);
loadBuildings(randomTown, randomFactionJson["random"]["town"]["buildings"]);
}
void CTownHandler::loadCustom()
{
loadRandomFaction();
}
void CTownHandler::afterLoadFinalization()
{
initializeRequirements();
initializeOverridden();
initializeWarMachines();
}
void CTownHandler::initializeRequirements()
{
// must be done separately after all ID's are known
for (auto & requirement : requirementsToLoad)
{
requirement.building->requirements = CBuilding::TRequired(requirement.json, [&](const JsonNode & node) -> BuildingID
{
if (node.Vector().size() > 1)
{
logMod->warn("Unexpected length of town buildings requirements: %d", node.Vector().size());
logMod->warn("Entry contains: ");
logMod->warn(node.toJson());
}
return BuildingID(VLC->modh->identifiers.getIdentifier(requirement.town->getBuildingScope(), node.Vector()[0]).get());
});
}
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
for(auto & p : warMachinesToLoad)
{
CTown * t = p.first;
JsonNode creatureKey = p.second;
auto ret = VLC->modh->identifiers.getIdentifier("creature", creatureKey, false);
if(ret)
{
const CCreature * creature = CreatureID(*ret).toCreature();
t->warMachine = creature->warMachine;
}
}
warMachinesToLoad.clear();
}
std::vector<bool> CTownHandler::getDefaultAllowed() const
{
std::vector<bool> allowedFactions;
for(auto town : objects)
{
allowedFactions.push_back(town->town != nullptr);
}
return allowedFactions;
}
std::set<TFaction> CTownHandler::getAllowedFactions(bool withTown) const
{
std::set<TFaction> allowedFactions;
std::vector<bool> allowed;
if (withTown)
allowed = getDefaultAllowed();
else
allowed.resize( objects.size(), true);
for (size_t i=0; i<allowed.size(); i++)
if (allowed[i])
allowedFactions.insert((TFaction)i);
return allowedFactions;
}
const std::vector<std::string> & CTownHandler::getTypeNames() const
{
static const std::vector<std::string> typeNames = { "faction", "town" };
return typeNames;
}
VCMI_LIB_NAMESPACE_END