2023-10-19 16:19:09 +02:00
/*
* 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"
2024-07-21 12:49:40 +02:00
# include "CTown.h"
# include "CFaction.h"
# include "../building/CBuilding.h"
# include "../../CCreatureHandler.h"
# include "../../CHeroHandler.h"
# include "../../GameSettings.h"
# include "../../TerrainHandler.h"
# include "../../VCMI_Lib.h"
# include "../../bonuses/Propagators.h"
# include "../../constants/StringConstants.h"
# include "../../mapObjectConstructors/AObjectTypeHandler.h"
# include "../../mapObjectConstructors/CObjectClassesHandler.h"
# include "../../modding/IdentifierStorage.h"
# include "../../modding/ModScope.h"
# include "../../spells/CSpellHandler.h"
# include "../../texts/CGeneralTextHandler.h"
# include "../../texts/CLegacyConfigParser.h"
# include "../../json/JsonBonus.h"
2024-08-16 01:06:33 +02:00
# include "../../json/JsonUtils.h"
2023-10-19 16:19:09 +02:00
VCMI_LIB_NAMESPACE_BEGIN
const int NAMES_PER_TOWN = 16 ; // number of town names per faction in H3 files. Json can define any number
2024-08-16 01:06:33 +02:00
CTownHandler : : CTownHandler ( )
: buildingsLibrary ( JsonPath : : builtin ( " config/buildingsLibrary " ) )
, randomTown ( new CTown ( ) )
, randomFaction ( new CFaction ( ) )
2023-10-19 16:19:09 +02:00
{
randomFaction - > town = randomTown ;
randomTown - > faction = randomFaction ;
randomFaction - > identifier = " random " ;
randomFaction - > modScope = " core " ;
}
CTownHandler : : ~ CTownHandler ( )
{
2023-11-13 16:37:02 +02:00
delete randomFaction ; // will also delete randomTown
2023-10-19 16:19:09 +02:00
}
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 ;
}
2024-01-09 16:43:36 +02:00
const TPropagatorPtr & CTownHandler : : emptyPropagator ( )
2023-10-19 16:19:09 +02:00
{
2024-01-09 16:43:36 +02:00
static const TPropagatorPtr emptyProp ( nullptr ) ;
2023-10-19 16:19:09 +02:00
return emptyProp ;
}
std : : vector < JsonNode > CTownHandler : : loadLegacyData ( )
{
size_t dataSize = VLC - > settings ( ) - > getInteger ( EGameSettings : : TEXTS_FACTION ) ;
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 ( TextPath : : builtin ( " 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 ( TextPath : : builtin ( " 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 ( TextPath : : builtin ( " 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 ( TextPath : : builtin ( " 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 ( TextPath : : builtin ( " DATA/TOWNTYPE.TXT " ) ) ;
CLegacyConfigParser nameParser ( TextPath : : builtin ( " 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 ) const
{
if ( source . isNull ( ) )
return ;
BuildingRequirementsHelper hlp ;
hlp . building = building ;
hlp . town = building - > town ;
hlp . json = source ;
bidsToLoad . push_back ( hlp ) ;
}
void CTownHandler : : loadSpecialBuildingBonuses ( const JsonNode & source , BonusList & bonusList , CBuilding * building )
{
for ( const auto & b : source . Vector ( ) )
{
2024-04-07 18:57:49 +02:00
auto bonus = std : : make_shared < Bonus > ( BonusDuration : : PERMANENT , BonusType : : NONE , BonusSource : : TOWN_STRUCTURE , 0 , BonusSourceID ( building - > getUniqueTypeID ( ) ) ) ;
2023-10-19 16:19:09 +02:00
2024-04-07 18:57:49 +02:00
if ( ! JsonUtils : : parseBonus ( b , bonus . get ( ) ) )
2023-10-19 16:19:09 +02:00
continue ;
2024-04-07 18:57:49 +02:00
bonus - > description . appendTextID ( building - > getNameTextID ( ) ) ;
2023-10-19 16:19:09 +02:00
//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 ) ;
2024-02-13 14:34:16 +02:00
assert ( ! source . getModScope ( ) . empty ( ) ) ;
2023-10-19 16:19:09 +02:00
auto * ret = new CBuilding ( ) ;
2024-08-12 14:32:13 +02:00
ret - > bid = vstd : : find_or ( MappedKeys : : BUILDING_NAMES_TO_TYPES , stringID , BuildingID : : NONE ) ;
2023-10-19 16:19:09 +02:00
ret - > subId = BuildingSubID : : NONE ;
if ( ret - > bid = = BuildingID : : NONE & & ! source [ " id " ] . isNull ( ) )
{
// FIXME: A lot of false-positives with no clear way to handle them in mods
//logMod->warn("Building %s: id field is deprecated", stringID);
ret - > bid = source [ " id " ] . isNull ( ) ? BuildingID ( BuildingID : : NONE ) : BuildingID ( source [ " id " ] . Float ( ) ) ;
}
if ( ret - > bid = = BuildingID : : NONE )
logMod - > error ( " Building '%s' isn't recognized and won't work properly. Correct the typo or update VCMI. " , stringID ) ;
ret - > mode = ret - > bid = = BuildingID : : GRAIL
? CBuilding : : BUILD_GRAIL
2024-08-12 14:32:13 +02:00
: vstd : : find_or ( CBuilding : : MODES , source [ " mode " ] . String ( ) , CBuilding : : BUILD_NORMAL ) ;
2023-10-19 16:19:09 +02:00
2024-08-12 14:32:13 +02:00
ret - > height = vstd : : find_or ( CBuilding : : TOWER_TYPES , source [ " height " ] . String ( ) , CBuilding : : HEIGHT_NO_TOWER ) ;
2023-10-19 16:19:09 +02:00
ret - > identifier = stringID ;
2024-02-13 14:34:16 +02:00
ret - > modScope = source . getModScope ( ) ;
2023-10-19 16:19:09 +02:00
ret - > town = town ;
2024-02-13 14:34:16 +02:00
VLC - > generaltexth - > registerString ( source . getModScope ( ) , ret - > getNameTextID ( ) , source [ " name " ] . String ( ) ) ;
VLC - > generaltexth - > registerString ( source . getModScope ( ) , ret - > getDescriptionTextID ( ) , source [ " description " ] . String ( ) ) ;
2023-10-19 16:19:09 +02:00
ret - > resources = TResources ( source [ " cost " ] ) ;
ret - > produce = TResources ( source [ " produce " ] ) ;
2024-08-16 14:57:38 +02:00
if ( ret - > bid . IsSpecialOrGrail ( ) )
2023-10-19 16:19:09 +02:00
{
loadSpecialBuildingBonuses ( source [ " bonuses " ] , ret - > buildingBonuses , ret ) ;
if ( ret - > buildingBonuses . empty ( ) )
2024-08-12 14:32:13 +02:00
ret - > subId = vstd : : find_or ( MappedKeys : : SPECIAL_BUILDINGS , source [ " type " ] . String ( ) , BuildingSubID : : NONE ) ;
2023-10-19 16:19:09 +02:00
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 )
2023-10-22 17:36:41 +02:00
bonus - > sid = BonusSourceID ( ret - > getUniqueTypeID ( ) ) ;
2023-10-19 16:19:09 +02:00
}
2024-08-16 14:57:38 +02:00
if ( ! source [ " configuration " ] . isNull ( ) )
2024-08-14 13:13:00 +02:00
ret - > rewardableObjectInfo . init ( source [ " configuration " ] , ret - > getBaseTextID ( ) ) ;
2023-10-19 16:19:09 +02:00
}
//MODS COMPATIBILITY FOR 0.96
2024-08-16 14:57:38 +02:00
if ( ! ret - > produce . nonZero ( ) & & ret - > bid = = BuildingID : : RESOURCE_SILO )
2023-10-19 16:19:09 +02:00
{
2024-08-16 14:57:38 +02:00
switch ( ret - > town - > primaryRes . toEnum ( ) )
{
case EGameResID : : GOLD :
ret - > produce [ ret - > town - > primaryRes ] = 500 ;
break ;
case EGameResID : : WOOD_AND_ORE :
ret - > produce [ EGameResID : : WOOD ] = 1 ;
ret - > produce [ EGameResID : : ORE ] = 1 ;
break ;
default :
ret - > produce [ ret - > town - > primaryRes ] = 1 ;
break ;
2023-10-19 16:19:09 +02:00
}
}
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 - > identifiers ( ) - > requestIdentifier ( ret - > town - > getBuildingScope ( ) , source [ " upgrades " ] , [ = ] ( si32 identifier )
{
ret - > upgrade = BuildingID ( identifier ) ;
} ) ;
}
else
ret - > upgrade = BuildingID : : NONE ;
ret - > town - > buildings [ ret - > bid ] = ret ;
2024-02-13 14:34:16 +02:00
registerObject ( source . getModScope ( ) , ret - > town - > getBuildingScope ( ) , ret - > identifier , ret - > bid . getNum ( ) ) ;
2023-10-19 16:19:09 +02:00
}
void CTownHandler : : loadBuildings ( CTown * town , const JsonNode & source )
{
if ( source . isStruct ( ) )
{
for ( const 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 ) const
{
auto * ret = new CStructure ( ) ;
ret - > building = nullptr ;
ret - > buildable = nullptr ;
2024-02-13 14:34:16 +02:00
VLC - > identifiers ( ) - > tryRequestIdentifier ( source . getModScope ( ) , " building. " + town . faction - > getJsonKey ( ) , stringID , [ = , & town ] ( si32 identifier ) mutable
2023-10-19 16:19:09 +02:00
{
ret - > building = town . buildings [ BuildingID ( identifier ) ] ;
} ) ;
if ( source [ " builds " ] . isNull ( ) )
{
2024-02-13 14:34:16 +02:00
VLC - > identifiers ( ) - > tryRequestIdentifier ( source . getModScope ( ) , " building. " + town . faction - > getJsonKey ( ) , stringID , [ = , & town ] ( si32 identifier ) mutable
2023-10-19 16:19:09 +02:00
{
ret - > building = town . buildings [ BuildingID ( identifier ) ] ;
} ) ;
}
else
{
VLC - > 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 = AnimationPath : : fromJson ( source [ " animation " ] ) ;
ret - > borderName = ImagePath : : fromJson ( source [ " border " ] ) ;
ret - > areaName = ImagePath : : fromJson ( source [ " area " ] ) ;
town . clientInfo . structures . emplace_back ( ret ) ;
}
void CTownHandler : : loadStructures ( CTown & town , const JsonNode & source ) const
{
for ( const auto & node : source . Struct ( ) )
{
if ( ! node . second . isNull ( ) )
loadStructure ( town , node . first , node . second ) ;
}
}
void CTownHandler : : loadTownHall ( CTown & town , const JsonNode & source ) const
{
auto & dstSlots = town . clientInfo . hallSlots ;
const auto & srcSlots = source . Vector ( ) ;
dstSlots . resize ( srcSlots . size ( ) ) ;
for ( size_t i = 0 ; i < dstSlots . size ( ) ; i + + )
{
auto & dstRow = dstSlots [ i ] ;
const auto & srcRow = srcSlots [ i ] . Vector ( ) ;
dstRow . resize ( srcRow . size ( ) ) ;
for ( size_t j = 0 ; j < dstRow . size ( ) ; j + + )
{
auto & dstBox = dstRow [ j ] ;
const auto & srcBox = srcRow [ j ] . Vector ( ) ;
dstBox . resize ( srcBox . size ( ) ) ;
for ( size_t k = 0 ; k < dstBox . size ( ) ; k + + )
{
auto & dst = dstBox [ k ] ;
const auto & src = srcBox [ k ] ;
VLC - > identifiers ( ) - > requestIdentifier ( " building. " + town . faction - > getJsonKey ( ) , src , [ & ] ( si32 identifier )
{
dst = BuildingID ( identifier ) ;
} ) ;
}
}
}
}
Point JsonToPoint ( const JsonNode & node )
{
if ( ! node . isStruct ( ) )
return Point : : makeInvalid ( ) ;
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 ) const
{
town . clientInfo . siegePrefix = source [ " imagePrefix " ] . String ( ) ;
town . clientInfo . towerIconSmall = source [ " towerIconSmall " ] . String ( ) ;
town . clientInfo . towerIconLarge = source [ " towerIconLarge " ] . String ( ) ;
VLC - > identifiers ( ) - > requestIdentifier ( " creature " , source [ " shooter " ] , [ & town ] ( si32 creature )
{
auto crId = CreatureID ( creature ) ;
2024-06-24 03:23:26 +02:00
if ( ( * VLC - > creh ) [ crId ] - > animation . missileFrameAngles . empty ( ) )
2023-10-19 16:19:09 +02:00
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 ) const
{
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 ] ) ;
2024-07-15 23:45:51 +02:00
if ( source [ " musicTheme " ] . isVector ( ) )
{
for ( auto const & entry : source [ " musicTheme " ] . Vector ( ) )
info . musicTheme . push_back ( AudioPath : : fromJson ( entry ) ) ;
}
else
{
info . musicTheme . push_back ( AudioPath : : fromJson ( source [ " musicTheme " ] ) ) ;
}
2023-10-19 16:19:09 +02:00
info . hallBackground = ImagePath : : fromJson ( source [ " hallBackground " ] ) ;
info . townBackground = ImagePath : : fromJson ( source [ " townBackground " ] ) ;
info . guildWindow = ImagePath : : fromJson ( source [ " guildWindow " ] ) ;
info . buildingsIcons = AnimationPath : : fromJson ( source [ " buildingsIcons " ] ) ;
info . guildBackground = ImagePath : : fromJson ( source [ " guildBackground " ] ) ;
info . tavernVideo = VideoPath : : fromJson ( source [ " tavernVideo " ] ) ;
loadTownHall ( town , source [ " hallSlots " ] ) ;
loadStructures ( town , source [ " structures " ] ) ;
loadSiegeScreen ( town , source [ " siege " ] ) ;
}
void CTownHandler : : loadTown ( CTown * town , const JsonNode & source )
{
const auto * resIter = boost : : find ( GameConstants : : RESOURCE_NAMES , source [ " primaryResource " ] . String ( ) ) ;
if ( resIter = = std : : end ( GameConstants : : RESOURCE_NAMES ) )
town - > primaryRes = GameResID ( EGameResID : : WOOD_AND_ORE ) ; //Wood + Ore
else
town - > primaryRes = GameResID ( resIter - std : : begin ( GameConstants : : RESOURCE_NAMES ) ) ;
warMachinesToLoad [ town ] = source [ " warMachine " ] ;
town - > mageLevel = static_cast < ui32 > ( source [ " mageGuild " ] . Float ( ) ) ;
town - > namesCount = 0 ;
for ( const auto & name : source [ " names " ] . Vector ( ) )
{
VLC - > generaltexth - > registerString ( town - > faction - > modScope , town - > getRandomNameTextID ( town - > namesCount ) , name . String ( ) ) ;
town - > namesCount + = 1 ;
}
if ( ! source [ " moatAbility " ] . isNull ( ) ) // VCMI 1.2 compatibility code
{
VLC - > identifiers ( ) - > requestIdentifier ( " spell " , source [ " moatAbility " ] , [ = ] ( si32 ability )
{
town - > moatAbility = SpellID ( ability ) ;
} ) ;
}
else
{
2024-02-13 14:34:16 +02:00
VLC - > identifiers ( ) - > requestIdentifier ( source . getModScope ( ) , " spell " , " castleMoat " , [ = ] ( si32 ability )
2023-10-19 16:19:09 +02:00
{
town - > moatAbility = SpellID ( ability ) ;
} ) ;
}
// Horde building creature level
for ( const JsonNode & node : source [ " horde " ] . Vector ( ) )
town - > hordeLvl [ static_cast < 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 [ static_cast < 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 - > 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 ( const auto & node : source [ " tavern " ] . Struct ( ) )
{
int chance = static_cast < int > ( node . second . Float ( ) ) ;
2024-02-13 14:34:16 +02:00
VLC - > identifiers ( ) - > requestIdentifier ( node . second . getModScope ( ) , " heroClass " , node . first , [ = ] ( si32 classID )
2023-10-19 16:19:09 +02:00
{
2023-12-31 23:43:35 +02:00
VLC - > heroclassesh - > objects [ classID ] - > selectionProbability [ town - > faction - > getId ( ) ] = chance ;
2023-10-19 16:19:09 +02:00
} ) ;
}
for ( const auto & node : source [ " guildSpells " ] . Struct ( ) )
{
int chance = static_cast < int > ( node . second . Float ( ) ) ;
2024-02-13 14:34:16 +02:00
VLC - > identifiers ( ) - > requestIdentifier ( node . second . getModScope ( ) , " spell " , node . first , [ = ] ( si32 spellID )
2023-10-19 16:19:09 +02:00
{
VLC - > spellh - > objects . at ( spellID ) - > probabilities [ town - > faction - > getId ( ) ] = 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 ) const
{
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 ;
2024-08-12 20:26:30 +02:00
spi . position . x = static_cast < si16 > ( piece [ " x " ] . Float ( ) ) ;
spi . position . y = static_cast < si16 > ( piece [ " y " ] . Float ( ) ) ;
2023-10-19 16:19:09 +02:00
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 = ImagePath : : builtinTODO ( prefix + suffix . str ( ) ) ;
faction . puzzleMap . push_back ( spi ) ;
}
assert ( faction . puzzleMap . size ( ) = = GameConstants : : PUZZLE_MAP_PIECES ) ;
}
2024-05-17 00:05:51 +02:00
std : : shared_ptr < CFaction > CTownHandler : : loadFromJson ( const std : : string & scope , const JsonNode & source , const std : : string & identifier , size_t index )
2023-10-19 16:19:09 +02:00
{
assert ( identifier . find ( ' : ' ) = = std : : string : : npos ) ;
2024-05-17 00:05:51 +02:00
auto faction = std : : make_shared < CFaction > ( ) ;
2023-10-19 16:19:09 +02:00
faction - > index = static_cast < FactionID > ( index ) ;
faction - > modScope = scope ;
faction - > identifier = identifier ;
VLC - > generaltexth - > registerString ( scope , faction - > getNameTextID ( ) , source [ " name " ] . String ( ) ) ;
2024-04-12 23:35:39 +02:00
VLC - > generaltexth - > registerString ( scope , faction - > getDescriptionTranslated ( ) , source [ " description " ] . String ( ) ) ;
2023-10-19 16:19:09 +02:00
faction - > creatureBg120 = ImagePath : : fromJson ( source [ " creatureBackground " ] [ " 120px " ] ) ;
faction - > creatureBg130 = ImagePath : : fromJson ( source [ " creatureBackground " ] [ " 130px " ] ) ;
faction - > boatType = BoatId : : CASTLE ; //Do not crash
if ( ! source [ " boat " ] . isNull ( ) )
{
VLC - > identifiers ( ) - > requestIdentifier ( " core:boat " , source [ " boat " ] , [ = ] ( int32_t boatTypeID )
{
faction - > boatType = BoatId ( boatTypeID ) ;
} ) ;
}
int alignment = vstd : : find_pos ( GameConstants : : ALIGNMENT_NAMES , source [ " alignment " ] . String ( ) ) ;
if ( alignment = = - 1 )
faction - > alignment = EAlignment : : NEUTRAL ;
else
faction - > alignment = static_cast < EAlignment > ( alignment ) ;
auto preferUndergound = source [ " preferUndergroundPlacement " ] ;
faction - > preferUndergroundPlacement = preferUndergound . isNull ( ) ? false : preferUndergound . Bool ( ) ;
2024-04-12 10:14:35 +02:00
faction - > special = source [ " special " ] . Bool ( ) ;
2023-10-19 16:19:09 +02:00
// 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 - > identifiers ( ) - > requestIdentifier ( " terrain " , source [ " nativeTerrain " ] , [ = ] ( int32_t index ) {
faction - > nativeTerrain = TerrainId ( index ) ;
auto const & terrain = VLC - > terrainTypeHandler - > getById ( faction - > nativeTerrain ) ;
if ( ! terrain - > isSurface ( ) & & ! terrain - > isUnderground ( ) )
logMod - > warn ( " Faction %s has terrain %s as native, but terrain is not suitable for either surface or subterranean layers! " , faction - > getJsonKey ( ) , terrain - > getJsonKey ( ) ) ;
} ) ;
}
if ( ! source [ " town " ] . isNull ( ) )
{
faction - > town = new CTown ( ) ;
2024-05-17 00:05:51 +02:00
faction - > town - > faction = faction . get ( ) ;
2023-10-19 16:19:09 +02:00
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 )
{
2024-05-17 00:05:51 +02:00
auto object = loadFromJson ( scope , data , name , objects . size ( ) ) ;
2023-10-19 16:19:09 +02:00
objects . emplace_back ( object ) ;
if ( object - > town )
{
auto & info = object - > town - > clientInfo ;
2023-11-05 19:13:18 +02:00
info . icons [ 0 ] [ 0 ] = 8 + object - > index . getNum ( ) * 4 + 0 ;
info . icons [ 0 ] [ 1 ] = 8 + object - > index . getNum ( ) * 4 + 1 ;
info . icons [ 1 ] [ 0 ] = 8 + object - > index . getNum ( ) * 4 + 2 ;
info . icons [ 1 ] [ 1 ] = 8 + object - > index . getNum ( ) * 4 + 3 ;
2023-10-19 16:19:09 +02:00
VLC - > identifiers ( ) - > requestIdentifier ( scope , " object " , " town " , [ = ] ( si32 index )
{
// register town once objects are loaded
JsonNode config = data [ " town " ] [ " mapObject " ] ;
config [ " faction " ] . String ( ) = name ;
2024-02-13 14:34:16 +02:00
config [ " faction " ] . setModScope ( scope , false ) ;
if ( config . getModScope ( ) . empty ( ) ) // MODS COMPATIBILITY FOR 0.96
config . setModScope ( scope , false ) ;
2023-10-19 16:19:09 +02:00
VLC - > objtypeh - > loadSubObject ( object - > identifier , config , index , object - > index ) ;
// MODS COMPATIBILITY FOR 0.96
const 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 ) ;
}
} ) ;
}
2023-11-05 19:13:18 +02:00
registerObject ( scope , " faction " , name , object - > index . getNum ( ) ) ;
2023-10-19 16:19:09 +02:00
}
void CTownHandler : : loadObject ( std : : string scope , std : : string name , const JsonNode & data , size_t index )
{
2024-05-17 00:05:51 +02:00
auto object = loadFromJson ( scope , data , name , index ) ;
2023-10-19 16:19:09 +02:00
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 ;
2023-11-05 19:13:18 +02:00
info . icons [ 0 ] [ 0 ] = ( GameConstants : : F_NUMBER + object - > index . getNum ( ) ) * 2 + 0 ;
info . icons [ 0 ] [ 1 ] = ( GameConstants : : F_NUMBER + object - > index . getNum ( ) ) * 2 + 1 ;
info . icons [ 1 ] [ 0 ] = object - > index . getNum ( ) * 2 + 0 ;
info . icons [ 1 ] [ 1 ] = object - > index . getNum ( ) * 2 + 1 ;
2023-10-19 16:19:09 +02:00
VLC - > identifiers ( ) - > requestIdentifier ( scope , " object " , " town " , [ = ] ( si32 index )
{
// register town once objects are loaded
JsonNode config = data [ " town " ] [ " mapObject " ] ;
config [ " faction " ] . String ( ) = name ;
2024-02-13 14:34:16 +02:00
config [ " faction " ] . setModScope ( scope , false ) ;
2023-10-19 16:19:09 +02:00
VLC - > objtypeh - > loadSubObject ( object - > identifier , config , index , object - > index ) ;
} ) ;
}
2023-11-05 19:13:18 +02:00
registerObject ( scope , " faction " , name , object - > index . getNum ( ) ) ;
2023-10-19 16:19:09 +02:00
}
void CTownHandler : : loadRandomFaction ( )
{
JsonNode randomFactionJson ( JsonPath : : builtin ( " config/factions/random.json " ) ) ;
2024-02-13 14:34:16 +02:00
randomFactionJson . setModScope ( ModScope : : scopeBuiltin ( ) , true ) ;
2023-10-19 16:19:09 +02:00
loadBuildings ( randomTown , randomFactionJson [ " random " ] [ " town " ] [ " buildings " ] ) ;
}
void CTownHandler : : loadCustom ( )
{
loadRandomFaction ( ) ;
}
2024-08-16 01:06:33 +02:00
void CTownHandler : : beforeValidate ( JsonNode & object )
{
if ( object . Struct ( ) . count ( " town " ) = = 0 )
return ;
const auto & inheritBuilding = [ this ] ( const std : : string & name , JsonNode & target )
{
if ( buildingsLibrary . Struct ( ) . count ( name ) = = 0 )
return ;
JsonNode baseCopy ( buildingsLibrary [ name ] ) ;
baseCopy . setModScope ( target . getModScope ( ) ) ;
JsonUtils : : inherit ( target , baseCopy ) ;
} ;
for ( auto & building : object [ " town " ] [ " buildings " ] . Struct ( ) )
{
inheritBuilding ( building . first , building . second ) ;
if ( building . second . Struct ( ) . count ( " type " ) )
inheritBuilding ( building . second [ " type " ] . String ( ) , building . second ) ;
}
}
2023-10-19 16:19:09 +02:00
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 - > error ( " Unexpected length of town buildings requirements: %d " , node . Vector ( ) . size ( ) ) ;
logMod - > error ( " Entry contains: " ) ;
2024-02-12 01:22:16 +02:00
logMod - > error ( node . toString ( ) ) ;
2023-10-19 16:19:09 +02:00
}
auto index = VLC - > identifiers ( ) - > getIdentifier ( requirement . town - > getBuildingScope ( ) , node [ 0 ] ) ;
if ( ! index . has_value ( ) )
{
logMod - > error ( " Unknown building in town buildings: %s " , node [ 0 ] . String ( ) ) ;
return BuildingID : : NONE ;
}
return BuildingID ( index . value ( ) ) ;
} ) ;
}
requirementsToLoad . clear ( ) ;
}
void CTownHandler : : initializeOverridden ( )
{
for ( auto & bidHelper : overriddenBidsToLoad )
{
auto jsonNode = bidHelper . json ;
auto scope = bidHelper . town - > getBuildingScope ( ) ;
for ( const auto & b : jsonNode . Vector ( ) )
{
auto bid = BuildingID ( VLC - > identifiers ( ) - > getIdentifier ( scope , b ) . value ( ) ) ;
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 - > identifiers ( ) - > getIdentifier ( " creature " , creatureKey , false ) ;
if ( ret )
{
const CCreature * creature = CreatureID ( * ret ) . toCreature ( ) ;
t - > warMachine = creature - > warMachine ;
}
}
warMachinesToLoad . clear ( ) ;
}
2023-11-05 15:24:26 +02:00
std : : set < FactionID > CTownHandler : : getDefaultAllowed ( ) const
2023-10-19 16:19:09 +02:00
{
2023-11-05 15:24:26 +02:00
std : : set < FactionID > allowedFactions ;
2024-05-17 00:05:51 +02:00
for ( const auto & town : objects )
2024-04-10 21:22:29 +02:00
if ( town - > town ! = nullptr & & ! town - > special )
2023-11-05 15:24:26 +02:00
allowedFactions . insert ( town - > getId ( ) ) ;
2023-10-19 16:19:09 +02:00
return allowedFactions ;
}
std : : set < FactionID > CTownHandler : : getAllowedFactions ( bool withTown ) const
{
2023-11-17 15:57:39 +02:00
if ( withTown )
2023-11-05 15:24:26 +02:00
return getDefaultAllowed ( ) ;
2023-10-19 16:19:09 +02:00
2023-11-05 15:24:26 +02:00
std : : set < FactionID > result ;
2024-05-17 00:05:51 +02:00
for ( const auto & town : objects )
2023-11-05 15:24:26 +02:00
result . insert ( town - > getId ( ) ) ;
return result ;
2023-10-19 16:19:09 +02:00
}
const std : : vector < std : : string > & CTownHandler : : getTypeNames ( ) const
{
static const std : : vector < std : : string > typeNames = { " faction " , " town " } ;
return typeNames ;
}
VCMI_LIB_NAMESPACE_END