2013-02-04 13:29:59 +03:00
/*
* MapFormatH3M . 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"
2014-01-30 21:56:31 +03:00
# include "MapFormatH3M.h"
2023-04-02 18:56:10 +02:00
2013-02-04 13:29:59 +03:00
# include "CMap.h"
2023-04-02 18:56:10 +02:00
# include "MapReaderH3M.h"
2023-05-24 00:14:06 +02:00
# include "MapFormat.h"
2013-02-04 13:29:59 +03:00
2023-05-17 15:52:16 +02:00
# include "../ArtifactUtils.h"
2013-02-04 13:29:59 +03:00
# include "../CCreatureHandler.h"
2013-12-29 14:27:38 +03:00
# include "../CGeneralTextHandler.h"
2013-02-04 13:29:59 +03:00
# include "../CHeroHandler.h"
2023-04-05 13:08:41 +02:00
# include "../CSkillHandler.h"
# include "../CStopWatch.h"
2023-05-23 14:07:44 +02:00
# include "../GameSettings.h"
2023-04-05 13:08:41 +02:00
# include "../RiverHandler.h"
# include "../RoadHandler.h"
# include "../TerrainHandler.h"
# include "../TextOperations.h"
# include "../VCMI_Lib.h"
# include "../filesystem/CBinaryReader.h"
# include "../filesystem/Filesystem.h"
2023-06-02 20:47:37 +02:00
# include "../mapObjectConstructors/AObjectTypeHandler.h"
# include "../mapObjectConstructors/CObjectClassesHandler.h"
2023-06-17 14:21:42 +02:00
# include "../mapObjects/CGCreature.h"
2014-06-05 19:52:14 +03:00
# include "../mapObjects/MapObjects.h"
2023-06-02 20:47:37 +02:00
# include "../mapObjects/ObjectTemplate.h"
2023-04-05 13:08:41 +02:00
# include "../spells/CSpellHandler.h"
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
# include <boost/crc.hpp>
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_BEGIN
2013-02-04 13:29:59 +03:00
2023-03-14 17:54:37 +02:00
static std : : string convertMapName ( std : : string input )
{
boost : : algorithm : : to_lower ( input ) ;
boost : : algorithm : : trim ( input ) ;
2023-09-20 21:00:03 +02:00
boost : : algorithm : : erase_all ( input , " . " ) ;
2023-03-14 17:54:37 +02:00
2023-04-03 14:02:15 +02:00
size_t slashPos = input . find_last_of ( ' / ' ) ;
2023-03-14 17:54:37 +02:00
2023-04-05 13:08:41 +02:00
if ( slashPos ! = std : : string : : npos )
2023-03-14 17:54:37 +02:00
return input . substr ( slashPos + 1 ) ;
return input ;
}
2023-02-25 17:44:15 +02:00
CMapLoaderH3M : : CMapLoaderH3M ( const std : : string & mapName , const std : : string & modName , const std : : string & encodingName , CInputStream * stream )
2023-02-24 16:15:45 +02:00
: map ( nullptr )
2023-04-02 18:56:10 +02:00
, reader ( new MapReaderH3M ( stream ) )
2023-02-24 16:15:45 +02:00
, inputStream ( stream )
2023-03-14 17:54:37 +02:00
, mapName ( convertMapName ( mapName ) )
2023-02-25 17:44:15 +02:00
, modName ( modName )
2023-02-25 01:18:15 +02:00
, fileEncoding ( encodingName )
2013-02-04 13:29:59 +03:00
{
}
2023-02-11 18:30:06 +02:00
//must be instantiated in .cpp file for access to complete types of all member fields
CMapLoaderH3M : : ~ CMapLoaderH3M ( ) = default ;
2013-02-04 13:29:59 +03:00
std : : unique_ptr < CMap > CMapLoaderH3M : : loadMap ( )
{
// Init map object by parsing the input buffer
map = new CMap ( ) ;
mapHeader = std : : unique_ptr < CMapHeader > ( dynamic_cast < CMapHeader * > ( map ) ) ;
init ( ) ;
2017-07-12 21:01:10 +02:00
return std : : unique_ptr < CMap > ( dynamic_cast < CMap * > ( mapHeader . release ( ) ) ) ;
2013-02-04 13:29:59 +03:00
}
std : : unique_ptr < CMapHeader > CMapLoaderH3M : : loadMapHeader ( )
{
// Read header
2022-12-07 23:36:20 +02:00
mapHeader = std : : make_unique < CMapHeader > ( ) ;
2013-02-04 13:29:59 +03:00
readHeader ( ) ;
return std : : move ( mapHeader ) ;
}
void CMapLoaderH3M : : init ( )
{
2023-04-02 18:56:10 +02:00
//TODO: get rid of double input process
2013-02-07 20:02:15 +03:00
si64 temp_size = inputStream - > getSize ( ) ;
inputStream - > seek ( 0 ) ;
2023-02-11 18:30:06 +02:00
auto * temp_buffer = new ui8 [ temp_size ] ;
2023-04-05 13:08:41 +02:00
inputStream - > read ( temp_buffer , temp_size ) ;
2013-02-07 20:02:15 +03:00
2013-02-04 13:29:59 +03:00
// Compute checksum
2023-04-05 13:08:41 +02:00
boost : : crc_32_type result ;
2013-02-07 20:02:15 +03:00
result . process_bytes ( temp_buffer , temp_size ) ;
2013-02-04 13:29:59 +03:00
map - > checksum = result . checksum ( ) ;
2023-04-05 13:08:41 +02:00
delete [ ] temp_buffer ;
2013-02-07 20:02:15 +03:00
inputStream - > seek ( 0 ) ;
2013-02-04 13:29:59 +03:00
readHeader ( ) ;
2013-05-19 01:30:48 +03:00
map - > allHeroes . resize ( map - > allowedHeroes . size ( ) ) ;
2013-02-04 13:29:59 +03:00
readDisposedHeroes ( ) ;
2023-04-03 19:51:02 +02:00
readMapOptions ( ) ;
2013-02-04 13:29:59 +03:00
readAllowedArtifacts ( ) ;
readAllowedSpellsAbilities ( ) ;
readRumors ( ) ;
readPredefinedHeroes ( ) ;
readTerrain ( ) ;
2023-05-24 00:14:06 +02:00
readObjectTemplates ( ) ;
2013-02-04 13:29:59 +03:00
readObjects ( ) ;
readEvents ( ) ;
2014-04-01 14:53:28 +03:00
map - > calculateGuardingGreaturePositions ( ) ;
2016-11-13 12:38:42 +02:00
afterRead ( ) ;
2023-07-17 17:21:28 +02:00
//map->banWaterContent(); //Not sure if force this for custom scenarios
2013-02-04 13:29:59 +03:00
}
void CMapLoaderH3M : : readHeader ( )
{
// Map version
2023-04-02 18:56:10 +02:00
mapHeader - > version = static_cast < EMapFormat > ( reader - > readUInt32 ( ) ) ;
2023-04-10 13:01:09 +02:00
if ( mapHeader - > version = = EMapFormat : : HOTA )
2013-02-04 13:29:59 +03:00
{
2023-04-04 14:40:16 +02:00
uint32_t hotaVersion = reader - > readUInt32 ( ) ;
2023-04-03 19:51:02 +02:00
features = MapFormatFeaturesH3M : : find ( mapHeader - > version , hotaVersion ) ;
2023-05-23 14:07:44 +02:00
reader - > setFormatLevel ( features ) ;
2023-04-03 17:56:49 +02:00
2023-04-09 00:03:43 +02:00
if ( hotaVersion > 0 )
{
2023-06-16 19:21:37 +02:00
bool isMirrorMap = reader - > readBool ( ) ;
2023-04-12 00:02:01 +02:00
bool isArenaMap = reader - > readBool ( ) ;
2023-06-16 19:21:37 +02:00
//TODO: HotA
if ( isMirrorMap )
logGlobal - > warn ( " Map '%s': Mirror maps are not yet supported! " , mapName ) ;
2023-04-12 00:02:01 +02:00
if ( isArenaMap )
logGlobal - > warn ( " Map '%s': Arena maps are not supported! " , mapName ) ;
2023-04-09 00:03:43 +02:00
}
2023-04-05 13:08:41 +02:00
if ( hotaVersion > 1 )
2023-04-03 17:56:49 +02:00
{
2023-04-15 16:20:19 +02:00
[[maybe_unused]] uint8_t unknown = reader - > readUInt32 ( ) ;
2023-04-04 14:40:16 +02:00
assert ( unknown = = 12 ) ;
2023-04-03 17:56:49 +02:00
}
2013-02-04 13:29:59 +03:00
}
2023-04-03 19:51:02 +02:00
else
2013-02-04 13:29:59 +03:00
{
2023-04-03 19:51:02 +02:00
features = MapFormatFeaturesH3M : : find ( mapHeader - > version , 0 ) ;
2023-05-23 14:07:44 +02:00
reader - > setFormatLevel ( features ) ;
}
MapIdentifiersH3M identifierMapper ;
if ( features . levelROE )
identifierMapper . loadMapping ( VLC - > settings ( ) - > getValue ( EGameSettings : : MAP_FORMAT_RESTORATION_OF_ERATHIA ) ) ;
if ( features . levelAB )
identifierMapper . loadMapping ( VLC - > settings ( ) - > getValue ( EGameSettings : : MAP_FORMAT_ARMAGEDDONS_BLADE ) ) ;
if ( features . levelSOD )
identifierMapper . loadMapping ( VLC - > settings ( ) - > getValue ( EGameSettings : : MAP_FORMAT_SHADOW_OF_DEATH ) ) ;
if ( features . levelWOG )
identifierMapper . loadMapping ( VLC - > settings ( ) - > getValue ( EGameSettings : : MAP_FORMAT_IN_THE_WAKE_OF_GODS ) ) ;
if ( features . levelHOTA0 )
identifierMapper . loadMapping ( VLC - > settings ( ) - > getValue ( EGameSettings : : MAP_FORMAT_HORN_OF_THE_ABYSS ) ) ;
2023-04-12 00:59:55 +02:00
2023-05-23 14:07:44 +02:00
reader - > setIdentifierRemapper ( identifierMapper ) ;
2023-04-12 00:59:55 +02:00
// include basic mod
if ( mapHeader - > version = = EMapFormat : : WOG )
mapHeader - > mods [ " wake-of-gods " ] ;
2013-02-04 13:29:59 +03:00
// Read map name, description, dimensions,...
2023-02-24 16:15:45 +02:00
mapHeader - > areAnyPlayers = reader - > readBool ( ) ;
2023-04-03 14:02:15 +02:00
mapHeader - > height = mapHeader - > width = reader - > readInt32 ( ) ;
2023-02-24 16:15:45 +02:00
mapHeader - > twoLevel = reader - > readBool ( ) ;
2023-09-27 22:53:13 +02:00
mapHeader - > name . appendTextID ( readLocalizedString ( " header.name " ) ) ;
mapHeader - > description . appendTextID ( readLocalizedString ( " header.description " ) ) ;
2023-02-24 16:15:45 +02:00
mapHeader - > difficulty = reader - > readInt8 ( ) ;
2023-04-02 18:56:10 +02:00
if ( features . levelAB )
2023-02-24 16:15:45 +02:00
mapHeader - > levelLimit = reader - > readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
else
mapHeader - > levelLimit = 0 ;
readPlayerInfo ( ) ;
readVictoryLossConditions ( ) ;
readTeamInfo ( ) ;
readAllowedHeroes ( ) ;
}
void CMapLoaderH3M : : readPlayerInfo ( )
{
for ( int i = 0 ; i < mapHeader - > players . size ( ) ; + + i )
{
2023-04-02 18:56:10 +02:00
auto & playerInfo = mapHeader - > players [ i ] ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
playerInfo . canHumanPlay = reader - > readBool ( ) ;
playerInfo . canComputerPlay = reader - > readBool ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
// If nobody can play with this player - skip loading of these properties
if ( ( ! ( playerInfo . canHumanPlay | | playerInfo . canComputerPlay ) ) )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
if ( features . levelROE )
2023-04-02 18:56:10 +02:00
reader - > skipUnused ( 6 ) ;
2023-04-05 13:08:41 +02:00
if ( features . levelAB )
2023-04-02 18:56:10 +02:00
reader - > skipUnused ( 6 ) ;
2023-04-05 13:08:41 +02:00
if ( features . levelSOD )
2023-04-02 18:56:10 +02:00
reader - > skipUnused ( 1 ) ;
2013-02-04 13:29:59 +03:00
continue ;
}
2023-08-19 21:35:44 +02:00
playerInfo . aiTactic = static_cast < EAiTactic > ( reader - > readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
if ( features . levelSOD )
reader - > skipUnused ( 1 ) ; //TODO: check meaning?
2013-02-04 13:29:59 +03:00
2023-04-11 16:46:08 +02:00
std : : set < FactionID > allowedFactions ;
2013-02-04 13:29:59 +03:00
2023-05-23 21:41:21 +02:00
reader - > readBitmaskFactions ( allowedFactions , false ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
const bool isFactionRandom = playerInfo . isFactionRandom = reader - > readBool ( ) ;
const bool allFactionsAllowed = isFactionRandom & & allowedFactions . size ( ) = = features . factionsCount ;
2021-11-08 18:49:04 +02:00
if ( ! allFactionsAllowed )
2023-04-02 18:56:10 +02:00
playerInfo . allowedFactions = allowedFactions ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
playerInfo . hasMainTown = reader - > readBool ( ) ;
if ( playerInfo . hasMainTown )
2013-02-04 13:29:59 +03:00
{
2023-04-02 18:56:10 +02:00
if ( features . levelAB )
2013-02-04 13:29:59 +03:00
{
2023-04-02 18:56:10 +02:00
playerInfo . generateHeroAtMainTown = reader - > readBool ( ) ;
reader - > skipUnused ( 1 ) ; //TODO: check meaning?
2013-02-04 13:29:59 +03:00
}
else
{
2023-04-02 18:56:10 +02:00
playerInfo . generateHeroAtMainTown = true ;
2013-02-04 13:29:59 +03:00
}
2023-04-02 18:56:10 +02:00
playerInfo . posOfMainTown = reader - > readInt3 ( ) ;
2013-02-04 13:29:59 +03:00
}
2023-04-02 18:56:10 +02:00
playerInfo . hasRandomHero = reader - > readBool ( ) ;
2023-09-28 18:43:04 +02:00
playerInfo . mainCustomHeroId = reader - > readHero ( ) ;
2013-02-04 13:29:59 +03:00
2023-09-28 18:43:04 +02:00
if ( playerInfo . mainCustomHeroId ! = HeroTypeID : : NONE )
2013-02-04 13:29:59 +03:00
{
2023-06-20 14:09:42 +02:00
playerInfo . mainCustomHeroPortrait = reader - > readHeroPortrait ( ) ;
2023-09-28 00:04:05 +02:00
playerInfo . mainCustomHeroNameTextId = readLocalizedString ( TextIdentifier ( " header " , " player " , i , " mainHeroName " ) ) ;
2013-02-04 13:29:59 +03:00
}
2023-04-02 18:56:10 +02:00
if ( features . levelAB )
2013-02-04 13:29:59 +03:00
{
2023-04-02 18:56:10 +02:00
reader - > skipUnused ( 1 ) ; //TODO: check meaning?
2023-04-03 14:02:15 +02:00
uint32_t heroCount = reader - > readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
for ( int pp = 0 ; pp < heroCount ; + + pp )
{
SHeroName vv ;
2023-09-28 18:43:04 +02:00
vv . heroId = reader - > readHero ( ) ;
2023-04-05 13:08:41 +02:00
vv . heroName = readLocalizedString ( TextIdentifier ( " header " , " heroNames " , vv . heroId ) ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
playerInfo . heroesNames . push_back ( vv ) ;
2013-02-04 13:29:59 +03:00
}
}
}
}
2023-04-03 17:56:49 +02:00
enum class EVictoryConditionType : uint8_t
2013-12-29 14:27:38 +03:00
{
2023-04-03 17:56:49 +02:00
ARTIFACT = 0 ,
GATHERTROOP = 1 ,
GATHERRESOURCE = 2 ,
BUILDCITY = 3 ,
BUILDGRAIL = 4 ,
BEATHERO = 5 ,
CAPTURECITY = 6 ,
BEATMONSTER = 7 ,
TAKEDWELLINGS = 8 ,
TAKEMINES = 9 ,
TRANSPORTITEM = 10 ,
HOTA_ELIMINATE_ALL_MONSTERS = 11 ,
HOTA_SURVIVE_FOR_DAYS = 12 ,
WINSTANDARD = 255
} ;
enum class ELossConditionType : uint8_t
2013-12-29 14:27:38 +03:00
{
2023-04-05 13:08:41 +02:00
LOSSCASTLE = 0 ,
LOSSHERO = 1 ,
TIMEEXPIRES = 2 ,
2023-04-03 17:56:49 +02:00
LOSSSTANDARD = 255
} ;
2013-12-29 14:27:38 +03:00
2013-02-04 13:29:59 +03:00
void CMapLoaderH3M : : readVictoryLossConditions ( )
{
2013-12-29 14:27:38 +03:00
mapHeader - > triggeredEvents . clear ( ) ;
2023-06-18 12:51:11 +02:00
mapHeader - > victoryMessage . clear ( ) ;
mapHeader - > defeatMessage . clear ( ) ;
2013-12-29 14:27:38 +03:00
2023-04-03 17:56:49 +02:00
auto vicCondition = static_cast < EVictoryConditionType > ( reader - > readUInt8 ( ) ) ;
2013-12-29 14:27:38 +03:00
EventCondition victoryCondition ( EventCondition : : STANDARD_WIN ) ;
EventCondition defeatCondition ( EventCondition : : DAYS_WITHOUT_TOWN ) ;
defeatCondition . value = 7 ;
TriggeredEvent standardVictory ;
standardVictory . effect . type = EventEffect : : VICTORY ;
2023-06-18 12:51:11 +02:00
standardVictory . effect . toOtherMessage . appendTextID ( " core.genrltxt.5 " ) ;
2013-12-29 14:27:38 +03:00
standardVictory . identifier = " standardVictory " ;
2022-11-15 02:20:55 +02:00
standardVictory . description . clear ( ) ; // TODO: display in quest window
2023-06-18 12:51:11 +02:00
standardVictory . onFulfill . appendTextID ( " core.genrltxt.659 " ) ;
2013-12-29 14:27:38 +03:00
standardVictory . trigger = EventExpression ( victoryCondition ) ;
TriggeredEvent standardDefeat ;
standardDefeat . effect . type = EventEffect : : DEFEAT ;
2023-06-18 12:51:11 +02:00
standardDefeat . effect . toOtherMessage . appendTextID ( " core.genrltxt.8 " ) ;
2013-12-29 14:27:38 +03:00
standardDefeat . identifier = " standardDefeat " ;
2022-11-15 02:20:55 +02:00
standardDefeat . description . clear ( ) ; // TODO: display in quest window
2023-06-18 12:51:11 +02:00
standardDefeat . onFulfill . appendTextID ( " core.genrltxt.7 " ) ;
2013-12-29 14:27:38 +03:00
standardDefeat . trigger = EventExpression ( defeatCondition ) ;
2013-02-04 13:29:59 +03:00
// Specific victory conditions
2013-12-29 14:27:38 +03:00
if ( vicCondition = = EVictoryConditionType : : WINSTANDARD )
{
// create normal condition
mapHeader - > triggeredEvents . push_back ( standardVictory ) ;
mapHeader - > victoryIconIndex = 11 ;
2023-06-18 12:51:11 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.0 " ) ;
2013-12-29 14:27:38 +03:00
}
else
2013-02-04 13:29:59 +03:00
{
2013-12-29 14:27:38 +03:00
TriggeredEvent specialVictory ;
specialVictory . effect . type = EventEffect : : VICTORY ;
specialVictory . identifier = " specialVictory " ;
2022-11-15 02:20:55 +02:00
specialVictory . description . clear ( ) ; // TODO: display in quest window
2013-12-29 14:27:38 +03:00
2023-02-11 18:30:06 +02:00
mapHeader - > victoryIconIndex = static_cast < ui16 > ( vicCondition ) ;
2013-12-29 14:27:38 +03:00
2023-02-24 16:15:45 +02:00
bool allowNormalVictory = reader - > readBool ( ) ;
bool appliesToAI = reader - > readBool ( ) ;
2013-02-04 13:29:59 +03:00
2013-12-29 14:27:38 +03:00
switch ( vicCondition )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : ARTIFACT :
2013-02-04 13:29:59 +03:00
{
2023-09-20 21:00:03 +02:00
assert ( allowNormalVictory = = true ) ; // not selectable in editor
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : HAVE_ARTIFACT ) ;
2023-04-02 18:56:10 +02:00
cond . objectType = reader - > readArtifact ( ) ;
2013-12-29 14:27:38 +03:00
2023-06-18 12:51:11 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " core.genrltxt.281 " ) ;
specialVictory . onFulfill . appendTextID ( " core.genrltxt.280 " ) ;
2013-12-29 14:27:38 +03:00
specialVictory . trigger = EventExpression ( cond ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.1 " ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : GATHERTROOP :
2013-02-04 13:29:59 +03:00
{
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : HAVE_CREATURES ) ;
2023-04-02 18:56:10 +02:00
cond . objectType = reader - > readCreature ( ) ;
2023-04-03 14:02:15 +02:00
cond . value = reader - > readInt32 ( ) ;
2013-12-29 14:27:38 +03:00
2023-06-18 12:51:11 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " core.genrltxt.277 " ) ;
2023-09-09 14:31:44 +02:00
specialVictory . onFulfill . appendTextID ( " core.genrltxt.276 " ) ;
2013-12-29 14:27:38 +03:00
specialVictory . trigger = EventExpression ( cond ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.2 " ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : GATHERRESOURCE :
2013-02-04 13:29:59 +03:00
{
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : HAVE_RESOURCES ) ;
2023-02-24 16:15:45 +02:00
cond . objectType = reader - > readUInt8 ( ) ;
2023-04-03 14:02:15 +02:00
cond . value = reader - > readInt32 ( ) ;
2013-12-29 14:27:38 +03:00
2023-06-18 12:51:11 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " core.genrltxt.279 " ) ;
specialVictory . onFulfill . appendTextID ( " core.genrltxt.278 " ) ;
2013-12-29 14:27:38 +03:00
specialVictory . trigger = EventExpression ( cond ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.3 " ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : BUILDCITY :
2013-02-04 13:29:59 +03:00
{
2023-09-20 21:00:03 +02:00
assert ( appliesToAI = = true ) ; // not selectable in editor
2013-12-29 14:27:38 +03:00
EventExpression : : OperatorAll oper ;
EventCondition cond ( EventCondition : : HAVE_BUILDING ) ;
2023-04-02 18:56:10 +02:00
cond . position = reader - > readInt3 ( ) ;
2023-02-24 16:15:45 +02:00
cond . objectType = BuildingID : : TOWN_HALL + reader - > readUInt8 ( ) ;
2023-02-11 18:30:06 +02:00
oper . expressions . emplace_back ( cond ) ;
2023-02-24 16:15:45 +02:00
cond . objectType = BuildingID : : FORT + reader - > readUInt8 ( ) ;
2023-02-11 18:30:06 +02:00
oper . expressions . emplace_back ( cond ) ;
2013-12-29 14:27:38 +03:00
2023-06-18 12:51:11 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " core.genrltxt.283 " ) ;
specialVictory . onFulfill . appendTextID ( " core.genrltxt.282 " ) ;
2013-12-29 14:27:38 +03:00
specialVictory . trigger = EventExpression ( oper ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.4 " ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : BUILDGRAIL :
2013-02-04 13:29:59 +03:00
{
2023-09-20 21:00:03 +02:00
assert ( allowNormalVictory = = true ) ; // not selectable in editor
assert ( appliesToAI = = true ) ; // not selectable in editor
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : HAVE_BUILDING ) ;
cond . objectType = BuildingID : : GRAIL ;
2023-04-02 18:56:10 +02:00
cond . position = reader - > readInt3 ( ) ;
2013-12-29 14:27:38 +03:00
if ( cond . position . z > 2 )
2023-04-05 13:08:41 +02:00
cond . position = int3 ( - 1 , - 1 , - 1 ) ;
2013-12-29 14:27:38 +03:00
2023-06-18 12:51:11 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " core.genrltxt.285 " ) ;
specialVictory . onFulfill . appendTextID ( " core.genrltxt.284 " ) ;
2013-12-29 14:27:38 +03:00
specialVictory . trigger = EventExpression ( cond ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.5 " ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : BEATHERO :
2013-12-29 14:27:38 +03:00
{
2023-09-20 21:00:03 +02:00
if ( ! allowNormalVictory )
2023-09-27 20:24:37 +02:00
logGlobal - > debug ( " Map %s: Has 'beat hero' as victory condition, but 'allow normal victory' not set. Ignoring " , mapName ) ;
2023-09-20 21:00:03 +02:00
allowNormalVictory = true ; // H3 behavior
assert ( appliesToAI = = false ) ; // not selectable in editor
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : DESTROY ) ;
cond . objectType = Obj : : HERO ;
2023-04-02 18:56:10 +02:00
cond . position = reader - > readInt3 ( ) ;
2013-12-29 14:27:38 +03:00
2023-06-18 12:51:11 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " core.genrltxt.253 " ) ;
specialVictory . onFulfill . appendTextID ( " core.genrltxt.252 " ) ;
2013-12-29 14:27:38 +03:00
specialVictory . trigger = EventExpression ( cond ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.6 " ) ;
2013-12-29 14:27:38 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : CAPTURECITY :
2013-12-29 14:27:38 +03:00
{
EventCondition cond ( EventCondition : : CONTROL ) ;
cond . objectType = Obj : : TOWN ;
2023-04-02 18:56:10 +02:00
cond . position = reader - > readInt3 ( ) ;
2013-12-29 14:27:38 +03:00
2023-06-18 12:51:11 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " core.genrltxt.250 " ) ;
specialVictory . onFulfill . appendTextID ( " core.genrltxt.249 " ) ;
2013-12-29 14:27:38 +03:00
specialVictory . trigger = EventExpression ( cond ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.7 " ) ;
2013-12-29 14:27:38 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : BEATMONSTER :
2013-02-04 13:29:59 +03:00
{
2023-09-20 21:00:03 +02:00
assert ( appliesToAI = = true ) ; // not selectable in editor
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : DESTROY ) ;
cond . objectType = Obj : : MONSTER ;
2023-04-02 18:56:10 +02:00
cond . position = reader - > readInt3 ( ) ;
2013-12-29 14:27:38 +03:00
2023-06-18 12:51:11 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " core.genrltxt.287 " ) ;
specialVictory . onFulfill . appendTextID ( " core.genrltxt.286 " ) ;
2013-12-29 14:27:38 +03:00
specialVictory . trigger = EventExpression ( cond ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.8 " ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : TAKEDWELLINGS :
2013-12-29 14:27:38 +03:00
{
2014-02-09 22:47:23 +03:00
EventExpression : : OperatorAll oper ;
2023-02-11 18:30:06 +02:00
oper . expressions . emplace_back ( EventCondition ( EventCondition : : CONTROL , 0 , Obj : : CREATURE_GENERATOR1 ) ) ;
oper . expressions . emplace_back ( EventCondition ( EventCondition : : CONTROL , 0 , Obj : : CREATURE_GENERATOR4 ) ) ;
2013-12-29 14:27:38 +03:00
2023-06-18 12:51:11 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " core.genrltxt.289 " ) ;
specialVictory . onFulfill . appendTextID ( " core.genrltxt.288 " ) ;
2014-02-09 22:47:23 +03:00
specialVictory . trigger = EventExpression ( oper ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.9 " ) ;
2013-12-29 14:27:38 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : TAKEMINES :
2013-02-04 13:29:59 +03:00
{
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : CONTROL ) ;
cond . objectType = Obj : : MINE ;
2023-06-18 12:51:11 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " core.genrltxt.291 " ) ;
specialVictory . onFulfill . appendTextID ( " core.genrltxt.290 " ) ;
2013-12-29 14:27:38 +03:00
specialVictory . trigger = EventExpression ( cond ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.10 " ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : TRANSPORTITEM :
2013-02-04 13:29:59 +03:00
{
2023-09-20 21:00:03 +02:00
assert ( allowNormalVictory = = true ) ; // not selectable in editor
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : TRANSPORT ) ;
2023-02-24 16:15:45 +02:00
cond . objectType = reader - > readUInt8 ( ) ;
2023-04-02 18:56:10 +02:00
cond . position = reader - > readInt3 ( ) ;
2013-12-29 14:27:38 +03:00
2023-06-18 12:51:11 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " core.genrltxt.293 " ) ;
specialVictory . onFulfill . appendTextID ( " core.genrltxt.292 " ) ;
2013-12-29 14:27:38 +03:00
specialVictory . trigger = EventExpression ( cond ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.11 " ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : HOTA_ELIMINATE_ALL_MONSTERS :
2023-06-17 22:15:23 +02:00
{
2023-09-20 21:00:03 +02:00
assert ( appliesToAI = = false ) ; // not selectable in editor
2023-06-17 22:15:23 +02:00
EventCondition cond ( EventCondition : : DESTROY ) ;
cond . objectType = Obj : : MONSTER ;
2023-06-18 22:45:12 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " vcmi.map.victoryCondition.eliminateMonsters.toOthers " ) ;
specialVictory . onFulfill . appendTextID ( " vcmi.map.victoryCondition.eliminateMonsters.toSelf " ) ;
2023-06-17 22:15:23 +02:00
specialVictory . trigger = EventExpression ( cond ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.12 " ) ;
mapHeader - > victoryIconIndex = 12 ;
2023-04-03 17:56:49 +02:00
break ;
2023-06-17 22:15:23 +02:00
}
2023-04-05 13:08:41 +02:00
case EVictoryConditionType : : HOTA_SURVIVE_FOR_DAYS :
2023-04-04 14:40:16 +02:00
{
2023-09-20 21:00:03 +02:00
assert ( appliesToAI = = false ) ; // not selectable in editor
2023-06-17 22:15:23 +02:00
EventCondition cond ( EventCondition : : DAYS_PASSED ) ;
cond . value = reader - > readUInt32 ( ) ;
2023-06-18 22:45:12 +02:00
specialVictory . effect . toOtherMessage . appendTextID ( " vcmi.map.victoryCondition.daysPassed.toOthers " ) ;
specialVictory . onFulfill . appendTextID ( " vcmi.map.victoryCondition.daysPassed.toSelf " ) ;
2023-06-17 22:15:23 +02:00
specialVictory . trigger = EventExpression ( cond ) ;
2023-06-18 22:45:12 +02:00
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.13 " ) ;
mapHeader - > victoryIconIndex = 13 ;
2023-04-03 17:56:49 +02:00
break ;
2023-04-04 14:40:16 +02:00
}
2023-04-05 13:08:41 +02:00
default :
2023-04-04 14:40:16 +02:00
assert ( 0 ) ;
2013-02-04 13:29:59 +03:00
}
2014-01-30 21:56:31 +03:00
2023-09-20 21:00:03 +02:00
if ( allowNormalVictory )
{
size_t playersOnMap = boost : : range : : count_if (
mapHeader - > players ,
[ ] ( const PlayerInfo & info )
{
return info . canAnyonePlay ( ) ;
}
) ;
if ( playersOnMap = = 1 )
{
logGlobal - > warn ( " Map %s: Only one player exists, but normal victory allowed! " , mapName ) ;
allowNormalVictory = false ; // makes sense? Not much. Works as H3? Yes!
}
}
2013-12-29 14:27:38 +03:00
// if condition is human-only turn it into following construction: AllOf(human, condition)
2023-04-05 13:08:41 +02:00
if ( ! appliesToAI )
2013-12-29 14:27:38 +03:00
{
EventExpression : : OperatorAll oper ;
EventCondition notAI ( EventCondition : : IS_HUMAN ) ;
notAI . value = 1 ;
2023-02-11 18:30:06 +02:00
oper . expressions . emplace_back ( notAI ) ;
2013-12-29 14:27:38 +03:00
oper . expressions . push_back ( specialVictory . trigger . get ( ) ) ;
specialVictory . trigger = EventExpression ( oper ) ;
}
// if normal victory allowed - add one more quest
2023-04-05 13:08:41 +02:00
if ( allowNormalVictory )
2013-12-29 14:27:38 +03:00
{
2023-06-18 12:51:11 +02:00
mapHeader - > victoryMessage . appendRawString ( " / " ) ;
mapHeader - > victoryMessage . appendTextID ( " core.vcdesc.0 " ) ;
2013-12-29 14:27:38 +03:00
mapHeader - > triggeredEvents . push_back ( standardVictory ) ;
}
mapHeader - > triggeredEvents . push_back ( specialVictory ) ;
2013-02-04 13:29:59 +03:00
}
// Read loss conditions
2023-04-03 17:56:49 +02:00
auto lossCond = static_cast < ELossConditionType > ( reader - > readUInt8 ( ) ) ;
2023-04-05 13:08:41 +02:00
if ( lossCond = = ELossConditionType : : LOSSSTANDARD )
2013-02-04 13:29:59 +03:00
{
2013-12-29 14:27:38 +03:00
mapHeader - > defeatIconIndex = 3 ;
2023-06-18 12:51:11 +02:00
mapHeader - > defeatMessage . appendTextID ( " core.lcdesc.0 " ) ;
2013-12-29 14:27:38 +03:00
}
else
{
TriggeredEvent specialDefeat ;
specialDefeat . effect . type = EventEffect : : DEFEAT ;
2023-06-18 12:51:11 +02:00
specialDefeat . effect . toOtherMessage . appendTextID ( " core.genrltxt.5 " ) ;
2013-12-29 14:27:38 +03:00
specialDefeat . identifier = " specialDefeat " ;
2022-11-15 02:20:55 +02:00
specialDefeat . description . clear ( ) ; // TODO: display in quest window
2013-12-29 14:27:38 +03:00
2023-02-11 18:30:06 +02:00
mapHeader - > defeatIconIndex = static_cast < ui16 > ( lossCond ) ;
2013-12-29 14:27:38 +03:00
switch ( lossCond )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
case ELossConditionType : : LOSSCASTLE :
2013-12-29 14:27:38 +03:00
{
EventExpression : : OperatorNone noneOf ;
EventCondition cond ( EventCondition : : CONTROL ) ;
cond . objectType = Obj : : TOWN ;
2023-04-02 18:56:10 +02:00
cond . position = reader - > readInt3 ( ) ;
2013-12-29 14:27:38 +03:00
2023-02-11 18:30:06 +02:00
noneOf . expressions . emplace_back ( cond ) ;
2023-06-18 12:51:11 +02:00
specialDefeat . onFulfill . appendTextID ( " core.genrltxt.251 " ) ;
2013-12-29 14:27:38 +03:00
specialDefeat . trigger = EventExpression ( noneOf ) ;
2023-06-18 12:51:11 +02:00
mapHeader - > defeatMessage . appendTextID ( " core.lcdesc.1 " ) ;
2013-12-29 14:27:38 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case ELossConditionType : : LOSSHERO :
2013-12-29 14:27:38 +03:00
{
EventExpression : : OperatorNone noneOf ;
EventCondition cond ( EventCondition : : CONTROL ) ;
cond . objectType = Obj : : HERO ;
2023-04-02 18:56:10 +02:00
cond . position = reader - > readInt3 ( ) ;
2013-12-29 14:27:38 +03:00
2023-02-11 18:30:06 +02:00
noneOf . expressions . emplace_back ( cond ) ;
2023-06-18 12:51:11 +02:00
specialDefeat . onFulfill . appendTextID ( " core.genrltxt.253 " ) ;
2013-12-29 14:27:38 +03:00
specialDefeat . trigger = EventExpression ( noneOf ) ;
2023-06-18 12:51:11 +02:00
mapHeader - > defeatMessage . appendTextID ( " core.lcdesc.2 " ) ;
2013-12-29 14:27:38 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
case ELossConditionType : : TIMEEXPIRES :
2013-12-29 14:27:38 +03:00
{
EventCondition cond ( EventCondition : : DAYS_PASSED ) ;
2023-02-24 16:15:45 +02:00
cond . value = reader - > readUInt16 ( ) ;
2013-12-29 14:27:38 +03:00
2023-06-18 12:51:11 +02:00
specialDefeat . onFulfill . appendTextID ( " core.genrltxt.254 " ) ;
2013-12-29 14:27:38 +03:00
specialDefeat . trigger = EventExpression ( cond ) ;
2023-06-18 12:51:11 +02:00
mapHeader - > defeatMessage . appendTextID ( " core.lcdesc.3 " ) ;
2013-12-29 14:27:38 +03:00
break ;
}
2013-02-04 13:29:59 +03:00
}
2013-12-29 14:27:38 +03:00
// turn simple loss condition into complete one that can be evaluated later:
// - any of :
// - days without town: 7
// - all of:
// - is human
// - (expression)
EventExpression : : OperatorAll allOf ;
EventCondition isHuman ( EventCondition : : IS_HUMAN ) ;
isHuman . value = 1 ;
2023-02-11 18:30:06 +02:00
allOf . expressions . emplace_back ( isHuman ) ;
2013-12-29 14:27:38 +03:00
allOf . expressions . push_back ( specialDefeat . trigger . get ( ) ) ;
specialDefeat . trigger = EventExpression ( allOf ) ;
mapHeader - > triggeredEvents . push_back ( specialDefeat ) ;
2013-02-04 13:29:59 +03:00
}
2013-12-29 14:27:38 +03:00
mapHeader - > triggeredEvents . push_back ( standardDefeat ) ;
2013-02-04 13:29:59 +03:00
}
void CMapLoaderH3M : : readTeamInfo ( )
{
2023-02-24 16:15:45 +02:00
mapHeader - > howManyTeams = reader - > readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
if ( mapHeader - > howManyTeams > 0 )
{
// Teams
2013-03-03 20:06:03 +03:00
for ( int i = 0 ; i < PlayerColor : : PLAYER_LIMIT_I ; + + i )
2023-02-24 16:15:45 +02:00
mapHeader - > players [ i ] . team = TeamID ( reader - > readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
else
{
// No alliances
2013-03-03 20:06:03 +03:00
for ( int i = 0 ; i < PlayerColor : : PLAYER_LIMIT_I ; i + + )
2013-02-04 13:29:59 +03:00
if ( mapHeader - > players [ i ] . canComputerPlay | | mapHeader - > players [ i ] . canHumanPlay )
2013-03-03 20:06:03 +03:00
mapHeader - > players [ i ] . team = TeamID ( mapHeader - > howManyTeams + + ) ;
2013-02-04 13:29:59 +03:00
}
}
void CMapLoaderH3M : : readAllowedHeroes ( )
{
2023-04-03 14:02:15 +02:00
mapHeader - > allowedHeroes = VLC - > heroh - > getDefaultAllowed ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-09 00:03:43 +02:00
if ( features . levelHOTA0 )
2023-05-23 21:41:21 +02:00
reader - > readBitmaskHeroesSized ( mapHeader - > allowedHeroes , false ) ;
else
reader - > readBitmaskHeroes ( mapHeader - > allowedHeroes , false ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
if ( features . levelAB )
2013-02-04 13:29:59 +03:00
{
2023-04-03 14:02:15 +02:00
uint32_t placeholdersQty = reader - > readUInt32 ( ) ;
2023-06-25 16:47:45 +02:00
for ( uint32_t i = 0 ; i < placeholdersQty ; + + i )
{
auto heroID = reader - > readHero ( ) ;
mapHeader - > reservedCampaignHeroes . push_back ( heroID ) ;
}
2013-02-04 13:29:59 +03:00
}
}
void CMapLoaderH3M : : readDisposedHeroes ( )
{
// Reading disposed heroes (20 bytes)
2023-04-02 18:56:10 +02:00
if ( features . levelSOD )
2013-02-04 13:29:59 +03:00
{
2023-02-24 16:15:45 +02:00
ui8 disp = reader - > readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
map - > disposedHeroes . resize ( disp ) ;
for ( int g = 0 ; g < disp ; + + g )
{
2023-09-28 18:43:04 +02:00
map - > disposedHeroes [ g ] . heroId = reader - > readHero ( ) ;
map - > disposedHeroes [ g ] . portrait . setNum ( reader - > readHeroPortrait ( ) ) ;
2023-02-25 17:44:15 +02:00
map - > disposedHeroes [ g ] . name = readLocalizedString ( TextIdentifier ( " header " , " heroes " , map - > disposedHeroes [ g ] . heroId ) ) ;
2023-08-25 20:40:19 +02:00
reader - > readBitmaskPlayers ( map - > disposedHeroes [ g ] . players , false ) ;
2013-02-04 13:29:59 +03:00
}
}
2023-04-03 19:51:02 +02:00
}
2013-02-04 13:29:59 +03:00
2023-04-03 19:51:02 +02:00
void CMapLoaderH3M : : readMapOptions ( )
{
2013-02-04 13:29:59 +03:00
//omitting NULLS
2023-04-02 18:56:10 +02:00
reader - > skipZero ( 31 ) ;
2023-04-03 19:51:02 +02:00
2023-04-09 00:03:43 +02:00
if ( features . levelHOTA0 )
2023-04-03 19:51:02 +02:00
{
2023-04-04 14:40:16 +02:00
//TODO: HotA
bool allowSpecialMonths = reader - > readBool ( ) ;
2023-04-05 13:08:41 +02:00
if ( ! allowSpecialMonths )
2023-04-04 14:40:16 +02:00
logGlobal - > warn ( " Map '%s': Option 'allow special months' is not implemented! " , mapName ) ;
reader - > skipZero ( 3 ) ;
2023-04-09 00:03:43 +02:00
}
if ( features . levelHOTA1 )
{
2023-04-15 16:20:19 +02:00
// Unknown, may be another "sized bitmap", e.g
// 4 bytes - size of bitmap (16)
// 2 bytes - bitmap data (16 bits / 2 bytes)
[[maybe_unused]] uint8_t unknownConstant = reader - > readUInt8 ( ) ;
2023-04-04 14:40:16 +02:00
assert ( unknownConstant = = 16 ) ;
reader - > skipZero ( 5 ) ;
2023-04-03 19:51:02 +02:00
}
2023-04-05 13:08:41 +02:00
if ( features . levelHOTA3 )
2023-04-03 19:51:02 +02:00
{
2023-04-04 14:40:16 +02:00
//TODO: HotA
int32_t roundLimit = reader - > readInt32 ( ) ;
2023-04-05 13:08:41 +02:00
if ( roundLimit ! = - 1 )
2023-04-04 14:40:16 +02:00
logGlobal - > warn ( " Map '%s': roundLimit of %d is not implemented! " , mapName , roundLimit ) ;
2023-04-03 19:51:02 +02:00
}
2013-02-04 13:29:59 +03:00
}
void CMapLoaderH3M : : readAllowedArtifacts ( )
{
2023-04-03 14:02:15 +02:00
map - > allowedArtifact = VLC - > arth - > getDefaultAllowed ( ) ;
2013-02-05 00:58:42 +03:00
2023-05-23 21:41:21 +02:00
if ( features . levelAB )
2023-04-04 12:45:31 +02:00
{
2023-05-23 21:41:21 +02:00
if ( features . levelHOTA0 )
reader - > readBitmaskArtifactsSized ( map - > allowedArtifact , true ) ;
else
reader - > readBitmaskArtifacts ( map - > allowedArtifact , true ) ;
2013-02-04 13:29:59 +03:00
}
// ban combo artifacts
2023-04-05 13:08:41 +02:00
if ( ! features . levelSOD )
2013-02-04 13:29:59 +03:00
{
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
for ( CArtifact * artifact : VLC - > arth - > objects )
2023-07-03 18:15:40 +02:00
if ( artifact - > isCombined ( ) )
2023-01-02 15:58:56 +02:00
map - > allowedArtifact [ artifact - > getId ( ) ] = false ;
2023-04-02 18:56:10 +02:00
}
2023-04-05 13:08:41 +02:00
if ( ! features . levelAB )
2023-04-02 18:56:10 +02:00
{
map - > allowedArtifact [ ArtifactID : : VIAL_OF_DRAGON_BLOOD ] = false ;
map - > allowedArtifact [ ArtifactID : : ARMAGEDDONS_BLADE ] = false ;
2013-02-04 13:29:59 +03:00
}
// Messy, but needed
2023-04-05 13:08:41 +02:00
for ( TriggeredEvent & event : map - > triggeredEvents )
2013-02-04 13:29:59 +03:00
{
2014-08-07 19:40:22 +03:00
auto patcher = [ & ] ( EventCondition cond ) - > EventExpression : : Variant
2013-12-29 14:27:38 +03:00
{
2023-04-05 13:08:41 +02:00
if ( cond . condition = = EventCondition : : HAVE_ARTIFACT | | cond . condition = = EventCondition : : TRANSPORT )
2013-12-29 14:27:38 +03:00
{
map - > allowedArtifact [ cond . objectType ] = false ;
}
2014-08-07 19:40:22 +03:00
return cond ;
2013-12-29 14:27:38 +03:00
} ;
2014-08-07 19:40:22 +03:00
event . trigger = event . trigger . morph ( patcher ) ;
2013-02-04 13:29:59 +03:00
}
}
void CMapLoaderH3M : : readAllowedSpellsAbilities ( )
{
2023-07-17 17:21:28 +02:00
map - > allowedSpells = VLC - > spellh - > getDefaultAllowed ( ) ;
2023-04-03 14:02:15 +02:00
map - > allowedAbilities = VLC - > skillh - > getDefaultAllowed ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
if ( features . levelSOD )
2013-02-04 13:29:59 +03:00
{
2023-07-17 17:21:28 +02:00
reader - > readBitmaskSpells ( map - > allowedSpells , true ) ;
2023-05-23 21:41:21 +02:00
reader - > readBitmaskSkills ( map - > allowedAbilities , true ) ;
2013-02-04 13:29:59 +03:00
}
}
void CMapLoaderH3M : : readRumors ( )
{
2023-04-03 19:51:02 +02:00
uint32_t rumorsCount = reader - > readUInt32 ( ) ;
2023-04-05 13:08:41 +02:00
assert ( rumorsCount < 1000 ) ; // sanity check
2013-02-04 13:29:59 +03:00
2023-04-03 19:51:02 +02:00
for ( int it = 0 ; it < rumorsCount ; it + + )
2013-02-04 13:29:59 +03:00
{
Rumor ourRumor ;
2023-02-25 18:11:17 +02:00
ourRumor . name = readBasicString ( ) ;
2023-09-27 23:22:45 +02:00
ourRumor . text . appendTextID ( readLocalizedString ( TextIdentifier ( " header " , " rumor " , it , " text " ) ) ) ;
2013-02-04 13:29:59 +03:00
map - > rumors . push_back ( ourRumor ) ;
}
}
void CMapLoaderH3M : : readPredefinedHeroes ( )
{
2023-04-05 13:08:41 +02:00
if ( ! features . levelSOD )
2023-04-02 18:56:10 +02:00
return ;
2023-04-03 19:51:02 +02:00
uint32_t heroesCount = features . heroesCount ;
2023-04-09 00:03:43 +02:00
if ( features . levelHOTA0 )
2023-04-03 19:51:02 +02:00
heroesCount = reader - > readUInt32 ( ) ;
assert ( heroesCount < = features . heroesCount ) ;
2023-04-05 13:08:41 +02:00
for ( int heroID = 0 ; heroID < heroesCount ; heroID + + )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
bool custom = reader - > readBool ( ) ;
2023-04-02 18:56:10 +02:00
if ( ! custom )
continue ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
auto * hero = new CGHeroInstance ( ) ;
hero - > ID = Obj : : HERO ;
2023-04-05 13:08:41 +02:00
hero - > subID = heroID ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
bool hasExp = reader - > readBool ( ) ;
if ( hasExp )
{
hero - > exp = reader - > readUInt32 ( ) ;
}
else
{
hero - > exp = 0 ;
}
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
bool hasSecSkills = reader - > readBool ( ) ;
if ( hasSecSkills )
{
2023-04-03 14:02:15 +02:00
uint32_t howMany = reader - > readUInt32 ( ) ;
2023-04-02 18:56:10 +02:00
hero - > secSkills . resize ( howMany ) ;
for ( int yy = 0 ; yy < howMany ; + + yy )
{
hero - > secSkills [ yy ] . first = reader - > readSkill ( ) ;
hero - > secSkills [ yy ] . second = reader - > readUInt8 ( ) ;
}
}
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
loadArtifactsOfHero ( hero ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
bool hasCustomBio = reader - > readBool ( ) ;
if ( hasCustomBio )
2023-09-28 00:00:32 +02:00
hero - > biographyCustomTextId = readLocalizedString ( TextIdentifier ( " heroes " , heroID , " biography " ) ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
// 0xFF is default, 00 male, 01 female
hero - > gender = static_cast < EHeroGender > ( reader - > readUInt8 ( ) ) ;
assert ( hero - > gender = = EHeroGender : : MALE | | hero - > gender = = EHeroGender : : FEMALE | | hero - > gender = = EHeroGender : : DEFAULT ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
bool hasCustomSpells = reader - > readBool ( ) ;
if ( hasCustomSpells )
2023-05-23 21:41:21 +02:00
reader - > readBitmaskSpells ( hero - > spells , false ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
bool hasCustomPrimSkills = reader - > readBool ( ) ;
if ( hasCustomPrimSkills )
{
2023-04-05 13:08:41 +02:00
for ( int skillID = 0 ; skillID < GameConstants : : PRIMARY_SKILLS ; skillID + + )
2023-04-02 18:56:10 +02:00
{
2023-08-19 20:43:50 +02:00
hero - > pushPrimSkill ( static_cast < PrimarySkill > ( skillID ) , reader - > readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
}
2023-04-02 18:56:10 +02:00
map - > predefinedHeroes . emplace_back ( hero ) ;
2023-06-25 16:47:45 +02:00
logGlobal - > debug ( " Map '%s': Hero predefined in map: %s " , mapName , VLC - > heroh - > getByIndex ( hero - > subID ) - > getJsonKey ( ) ) ;
2013-02-04 13:29:59 +03:00
}
}
void CMapLoaderH3M : : loadArtifactsOfHero ( CGHeroInstance * hero )
{
2023-04-02 18:56:10 +02:00
bool hasArtSet = reader - > readBool ( ) ;
2013-02-04 13:29:59 +03:00
// True if artifact set is not default (hero has some artifacts)
2023-04-02 18:56:10 +02:00
if ( ! hasArtSet )
return ;
2023-06-27 12:58:45 +02:00
// Workaround - if hero has customized artifacts game should not attempt to add spellbook based on hero type
hero - > spells . insert ( SpellID : : SPELLBOOK_PRESET ) ;
2023-02-11 18:30:06 +02:00
if ( ! hero - > artifactsWorn . empty ( ) | | ! hero - > artifactsInBackpack . empty ( ) )
{
2023-09-20 21:00:03 +02:00
logGlobal - > debug ( " Hero %s at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set... " , hero - > getNameTranslated ( ) , hero - > pos . toString ( ) ) ;
2023-04-05 13:08:41 +02:00
2023-02-11 18:30:06 +02:00
hero - > artifactsInBackpack . clear ( ) ;
while ( ! hero - > artifactsWorn . empty ( ) )
2023-04-05 13:08:41 +02:00
hero - > eraseArtSlot ( hero - > artifactsWorn . begin ( ) - > first ) ;
2023-02-11 18:30:06 +02:00
}
2013-09-03 16:18:09 +03:00
2023-04-05 13:08:41 +02:00
for ( int i = 0 ; i < features . artifactSlotsCount ; i + + )
loadArtifactToSlot ( hero , i ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
// bag artifacts
// number of artifacts in hero's bag
int amount = reader - > readUInt16 ( ) ;
2023-04-05 13:08:41 +02:00
for ( int i = 0 ; i < amount ; + + i )
2023-04-02 18:56:10 +02:00
{
2023-08-19 23:22:31 +02:00
loadArtifactToSlot ( hero , ArtifactPosition : : BACKPACK_START + static_cast < int > ( hero - > artifactsInBackpack . size ( ) ) ) ;
2013-02-04 13:29:59 +03:00
}
}
bool CMapLoaderH3M : : loadArtifactToSlot ( CGHeroInstance * hero , int slot )
{
2023-04-05 13:08:41 +02:00
ArtifactID artifactID = reader - > readArtifact ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
if ( artifactID = = ArtifactID : : NONE )
2023-04-02 18:56:10 +02:00
return false ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
const Artifact * art = artifactID . toArtifact ( VLC - > artifacts ( ) ) ;
2023-04-02 18:56:10 +02:00
2023-04-05 13:08:41 +02:00
if ( ! art )
2013-02-04 13:29:59 +03:00
{
2023-04-11 16:46:08 +02:00
logGlobal - > warn ( " Map '%s': Invalid artifact in hero's backpack, ignoring... " , mapName ) ;
2023-04-02 18:56:10 +02:00
return false ;
2013-02-04 13:29:59 +03:00
}
2023-04-02 18:56:10 +02:00
2023-08-19 23:22:31 +02:00
if ( art - > isBig ( ) & & slot > = ArtifactPosition : : BACKPACK_START )
2013-02-04 13:29:59 +03:00
{
2023-04-11 16:46:08 +02:00
logGlobal - > warn ( " Map '%s': A big artifact (war machine) in hero's backpack, ignoring... " , mapName ) ;
2023-04-02 18:56:10 +02:00
return false ;
2013-02-04 13:29:59 +03:00
}
2023-04-02 18:56:10 +02:00
// H3 bug workaround - Enemy hero on 3rd scenario of Good1.h3c campaign ("Long Live The Queen")
// He has Shackles of War (normally - MISC slot artifact) in LEFT_HAND slot set in editor
// Artifact seems to be missing in game, so skip artifacts that don't fit target slot
2023-05-17 15:52:16 +02:00
auto * artifact = ArtifactUtils : : createArtifact ( map , artifactID ) ;
2023-07-24 18:09:17 +02:00
auto dstLoc = ArtifactLocation ( hero , ArtifactPosition ( slot ) ) ;
if ( artifact - > canBePutAt ( dstLoc ) )
2013-02-04 13:29:59 +03:00
{
2023-07-24 18:09:17 +02:00
artifact - > putAt ( dstLoc ) ;
2023-04-02 18:56:10 +02:00
}
else
{
2023-04-11 16:46:08 +02:00
logGlobal - > warn ( " Map '%s': Artifact '%s' can't be put at the slot %d " , mapName , artifact - > artType - > getNameTranslated ( ) , slot ) ;
2023-04-02 18:56:10 +02:00
return false ;
2013-02-04 13:29:59 +03:00
}
2023-04-02 18:56:10 +02:00
return true ;
2013-02-04 13:29:59 +03:00
}
void CMapLoaderH3M : : readTerrain ( )
{
map - > initTerrain ( ) ;
// Read terrain
2022-09-18 16:39:10 +02:00
int3 pos ;
2022-09-23 16:24:01 +02:00
for ( pos . z = 0 ; pos . z < map - > levels ( ) ; + + pos . z )
2013-02-04 13:29:59 +03:00
{
2022-09-18 16:39:10 +02:00
//OH3 format is [z][y][x]
for ( pos . y = 0 ; pos . y < map - > height ; pos . y + + )
2013-02-04 13:29:59 +03:00
{
2022-09-18 16:39:10 +02:00
for ( pos . x = 0 ; pos . x < map - > width ; pos . x + + )
2013-02-04 13:29:59 +03:00
{
2022-09-18 16:39:10 +02:00
auto & tile = map - > getTile ( pos ) ;
2023-04-02 18:56:10 +02:00
tile . terType = VLC - > terrainTypeHandler - > getById ( reader - > readTerrain ( ) ) ;
2023-02-24 16:15:45 +02:00
tile . terView = reader - > readUInt8 ( ) ;
2023-04-02 18:56:10 +02:00
tile . riverType = VLC - > riverTypeHandler - > getById ( reader - > readRiver ( ) ) ;
2023-02-24 16:15:45 +02:00
tile . riverDir = reader - > readUInt8 ( ) ;
2023-04-02 18:56:10 +02:00
tile . roadType = VLC - > roadTypeHandler - > getById ( reader - > readRoad ( ) ) ;
2023-02-24 16:15:45 +02:00
tile . roadDir = reader - > readUInt8 ( ) ;
tile . extTileFlags = reader - > readUInt8 ( ) ;
2023-01-10 20:09:09 +02:00
tile . blocked = ! tile . terType - > isPassable ( ) ;
2023-02-11 18:30:06 +02:00
tile . visitable = false ;
2023-01-10 20:09:09 +02:00
assert ( tile . terType - > getId ( ) ! = ETerrainId : : NONE ) ;
2013-02-04 13:29:59 +03:00
}
}
}
2023-07-09 11:16:36 +02:00
map - > calculateWaterContent ( ) ;
2013-02-04 13:29:59 +03:00
}
2023-05-24 00:14:06 +02:00
void CMapLoaderH3M : : readObjectTemplates ( )
2013-02-04 13:29:59 +03:00
{
2023-04-03 14:02:15 +02:00
uint32_t defAmount = reader - > readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
2014-01-03 02:48:38 +03:00
templates . reserve ( defAmount ) ;
2013-02-04 13:29:59 +03:00
// Read custom defs
2023-04-05 13:08:41 +02:00
for ( int defID = 0 ; defID < defAmount ; + + defID )
2013-02-04 13:29:59 +03:00
{
2023-05-25 18:35:21 +02:00
auto tmpl = reader - > readObjectTemplate ( ) ;
2023-05-24 21:02:27 +02:00
templates . push_back ( tmpl ) ;
2023-09-04 15:16:00 +02:00
if ( ! CResourceHandler : : get ( ) - > existsResource ( tmpl - > animationFile . addPrefix ( " SPRITES/ " ) ) )
2023-08-23 14:07:50 +02:00
logMod - > warn ( " Template animation %s of type (%d %d) is missing! " , tmpl - > animationFile . getOriginalName ( ) , tmpl - > id , tmpl - > subid ) ;
2013-02-04 13:29:59 +03:00
}
}
2023-09-17 15:06:49 +02:00
CGObjectInstance * CMapLoaderH3M : : readEvent ( const int3 & mapPosition , const ObjectInstanceID & idToBeGiven )
2013-02-04 13:29:59 +03:00
{
2023-04-03 21:41:05 +02:00
auto * object = new CGEvent ( ) ;
2013-02-04 13:29:59 +03:00
2023-09-17 15:06:49 +02:00
readBoxContent ( object , mapPosition , idToBeGiven ) ;
2013-02-04 13:29:59 +03:00
2023-08-25 20:40:19 +02:00
reader - > readBitmaskPlayers ( object - > availableFor , false ) ;
2023-04-03 21:41:05 +02:00
object - > computerActivate = reader - > readBool ( ) ;
object - > removeAfterVisit = reader - > readBool ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-03 21:41:05 +02:00
reader - > skipZero ( 4 ) ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
if ( features . levelHOTA3 )
2023-04-04 12:45:31 +02:00
object - > humanActivate = reader - > readBool ( ) ;
else
object - > humanActivate = true ;
2013-02-04 13:29:59 +03:00
2023-04-03 21:41:05 +02:00
return object ;
}
2013-02-04 13:29:59 +03:00
2023-09-17 15:06:49 +02:00
CGObjectInstance * CMapLoaderH3M : : readPandora ( const int3 & mapPosition , const ObjectInstanceID & idToBeGiven )
2023-04-03 21:41:05 +02:00
{
auto * object = new CGPandoraBox ( ) ;
2023-09-17 15:06:49 +02:00
readBoxContent ( object , mapPosition , idToBeGiven ) ;
2023-04-03 21:41:05 +02:00
return object ;
}
2013-02-04 13:29:59 +03:00
2023-09-17 15:06:49 +02:00
void CMapLoaderH3M : : readBoxContent ( CGPandoraBox * object , const int3 & mapPosition , const ObjectInstanceID & idToBeGiven )
2023-04-03 21:41:05 +02:00
{
2023-04-05 13:08:41 +02:00
readMessageAndGuards ( object - > message , object , mapPosition ) ;
2023-09-15 15:29:41 +02:00
Rewardable : : VisitInfo vinfo ;
auto & reward = vinfo . reward ;
reward . heroExperience = reader - > readUInt32 ( ) ;
reward . manaDiff = reader - > readInt32 ( ) ;
2023-09-17 16:04:34 +02:00
if ( auto val = reader - > readUInt8 ( ) )
reward . bonuses . emplace_back ( BonusDuration : : ONE_BATTLE , BonusType : : MORALE , BonusSource : : OBJECT , val , idToBeGiven ) ;
if ( auto val = reader - > readUInt8 ( ) )
reward . bonuses . emplace_back ( BonusDuration : : ONE_BATTLE , BonusType : : LUCK , BonusSource : : OBJECT , val , idToBeGiven ) ;
2013-02-04 13:29:59 +03:00
2023-09-15 15:29:41 +02:00
reader - > readResourses ( reward . resources ) ;
2023-04-03 21:41:05 +02:00
for ( int x = 0 ; x < GameConstants : : PRIMARY_SKILLS ; + + x )
2023-09-15 15:29:41 +02:00
reward . primary . at ( x ) = reader - > readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
int gabn = reader - > readUInt8 ( ) ; //number of gained abilities
2023-04-03 21:41:05 +02:00
for ( int oo = 0 ; oo < gabn ; + + oo )
2013-02-04 13:29:59 +03:00
{
2023-09-15 15:29:41 +02:00
auto rId = reader - > readSkill ( ) ;
auto rVal = reader - > readUInt8 ( ) ;
reward . secondary [ rId ] = rVal ;
2023-04-03 21:41:05 +02:00
}
int gart = reader - > readUInt8 ( ) ; //number of gained artifacts
for ( int oo = 0 ; oo < gart ; + + oo )
2023-09-15 15:29:41 +02:00
reward . artifacts . push_back ( reader - > readArtifact ( ) ) ;
2013-02-04 13:29:59 +03:00
2023-04-03 21:41:05 +02:00
int gspel = reader - > readUInt8 ( ) ; //number of gained spells
for ( int oo = 0 ; oo < gspel ; + + oo )
2023-09-15 15:29:41 +02:00
reward . spells . push_back ( reader - > readSpell ( ) ) ;
2013-02-04 13:29:59 +03:00
2023-04-03 21:41:05 +02:00
int gcre = reader - > readUInt8 ( ) ; //number of gained creatures
2023-09-15 15:29:41 +02:00
for ( int oo = 0 ; oo < gcre ; + + oo )
{
auto rId = reader - > readCreature ( ) ;
auto rVal = reader - > readUInt16 ( ) ;
reward . creatures . emplace_back ( rId , rVal ) ;
}
vinfo . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
object - > configuration . info . push_back ( vinfo ) ;
2023-04-03 21:41:05 +02:00
reader - > skipZero ( 8 ) ;
}
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readMonster ( const int3 & mapPosition , const ObjectInstanceID & objectInstanceID )
2023-04-03 21:41:05 +02:00
{
auto * object = new CGCreature ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-03 21:41:05 +02:00
if ( features . levelAB )
{
object - > identifier = reader - > readUInt32 ( ) ;
2023-04-05 13:08:41 +02:00
map - > questIdentifierToId [ object - > identifier ] = objectInstanceID ;
2023-04-03 21:41:05 +02:00
}
2013-02-04 13:29:59 +03:00
2023-04-03 21:41:05 +02:00
auto * hlp = new CStackInstance ( ) ;
hlp - > count = reader - > readUInt16 ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-03 21:41:05 +02:00
//type will be set during initialization
object - > putStack ( SlotID ( 0 ) , hlp ) ;
2013-02-04 13:29:59 +03:00
2023-04-03 21:41:05 +02:00
object - > character = reader - > readInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-03 21:41:05 +02:00
bool hasMessage = reader - > readBool ( ) ;
if ( hasMessage )
{
2023-09-27 23:25:19 +02:00
object - > message . appendTextID ( readLocalizedString ( TextIdentifier ( " monster " , mapPosition . x , mapPosition . y , mapPosition . z , " message " ) ) ) ;
2023-04-03 21:41:05 +02:00
reader - > readResourses ( object - > resources ) ;
object - > gainedArtifact = reader - > readArtifact ( ) ;
}
object - > neverFlees = reader - > readBool ( ) ;
object - > notGrowingTeam = reader - > readBool ( ) ;
reader - > skipZero ( 2 ) ;
2023-04-04 12:45:31 +02:00
2023-04-05 13:08:41 +02:00
if ( features . levelHOTA3 )
2023-04-04 12:45:31 +02:00
{
2023-04-04 14:40:16 +02:00
//TODO: HotA
2023-04-05 12:40:13 +02:00
int32_t agressionExact = reader - > readInt32 ( ) ; // -1 = default, 1-10 = possible values range
bool joinOnlyForMoney = reader - > readBool ( ) ; // if true, monsters will only join for money
int32_t joinPercent = reader - > readInt32 ( ) ; // 100 = default, percent of monsters that will join on succesfull agression check
int32_t upgradedStack = reader - > readInt32 ( ) ; // Presence of upgraded stack, -1 = random, 0 = never, 1 = always
int32_t stacksCount = reader - > readInt32 ( ) ; // TODO: check possible values. How many creature stacks will be present on battlefield, -1 = default
2023-04-05 13:08:41 +02:00
if ( agressionExact ! = - 1 | | joinOnlyForMoney | | joinPercent ! = 100 | | upgradedStack ! = - 1 | | stacksCount ! = - 1 )
logGlobal - > warn (
" Map '%s': Wandering monsters %s settings %d %d %d %d %d are not implemeted! " ,
mapName ,
mapPosition . toString ( ) ,
agressionExact ,
int ( joinOnlyForMoney ) ,
joinPercent ,
upgradedStack ,
stacksCount
) ;
2023-04-04 12:45:31 +02:00
}
2023-04-03 21:41:05 +02:00
return object ;
}
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readSign ( const int3 & mapPosition )
2023-04-03 21:41:05 +02:00
{
auto * object = new CGSignBottle ( ) ;
2023-09-27 23:11:11 +02:00
object - > message . appendTextID ( readLocalizedString ( TextIdentifier ( " sign " , mapPosition . x , mapPosition . y , mapPosition . z , " message " ) ) ) ;
2023-04-03 21:41:05 +02:00
reader - > skipZero ( 4 ) ;
return object ;
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readWitchHut ( )
2023-04-03 21:41:05 +02:00
{
auto * object = new CGWitchHut ( ) ;
// AB and later maps have allowed abilities defined in H3M
if ( features . levelAB )
{
2023-05-23 21:41:21 +02:00
reader - > readBitmaskSkills ( object - > allowedAbilities , false ) ;
2023-04-03 21:41:05 +02:00
if ( object - > allowedAbilities . size ( ) ! = 1 )
2013-02-04 13:29:59 +03:00
{
2023-04-03 21:41:05 +02:00
auto defaultAllowed = VLC - > skillh - > getDefaultAllowed ( ) ;
for ( int skillID = 0 ; skillID < VLC - > skillh - > size ( ) ; + + skillID )
2023-04-05 13:08:41 +02:00
if ( defaultAllowed [ skillID ] )
2023-05-23 21:41:21 +02:00
object - > allowedAbilities . insert ( SecondarySkill ( skillID ) ) ;
2023-04-03 21:41:05 +02:00
}
}
return object ;
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readScholar ( )
2023-04-03 21:41:05 +02:00
{
auto * object = new CGScholar ( ) ;
object - > bonusType = static_cast < CGScholar : : EBonusType > ( reader - > readUInt8 ( ) ) ;
object - > bonusID = reader - > readUInt8 ( ) ;
reader - > skipZero ( 6 ) ;
2023-04-11 16:46:08 +02:00
return object ;
}
CGObjectInstance * CMapLoaderH3M : : readGarrison ( const int3 & mapPosition )
{
auto * object = new CGGarrison ( ) ;
setOwnerAndValidate ( mapPosition , object , reader - > readPlayer32 ( ) ) ;
readCreatureSet ( object , 7 ) ;
if ( features . levelAB )
object - > removableUnits = reader - > readBool ( ) ;
2023-04-03 21:41:05 +02:00
else
object - > removableUnits = true ;
reader - > skipZero ( 8 ) ;
return object ;
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readArtifact ( const int3 & mapPosition , std : : shared_ptr < const ObjectTemplate > objectTemplate )
2023-04-03 21:41:05 +02:00
{
2023-08-19 19:48:28 +02:00
ArtifactID artID = ArtifactID : : NONE ; //random, set later
SpellID spellID = SpellID : : NONE ;
2023-04-03 21:41:05 +02:00
auto * object = new CGArtifact ( ) ;
2023-04-05 13:08:41 +02:00
readMessageAndGuards ( object - > message , object , mapPosition ) ;
2023-04-03 21:41:05 +02:00
2023-04-05 13:08:41 +02:00
if ( objectTemplate - > id = = Obj : : SPELL_SCROLL )
2023-04-03 21:41:05 +02:00
{
spellID = reader - > readSpell32 ( ) ;
artID = ArtifactID : : SPELL_SCROLL ;
}
2023-04-05 13:08:41 +02:00
else if ( objectTemplate - > id = = Obj : : ARTIFACT )
2023-04-03 21:41:05 +02:00
{
//specific artifact
2023-04-05 13:08:41 +02:00
artID = ArtifactID ( objectTemplate - > subid ) ;
2023-04-03 21:41:05 +02:00
}
2023-08-19 19:48:28 +02:00
object - > storedArtifact = ArtifactUtils : : createArtifact ( map , artID , spellID . getNum ( ) ) ;
2023-04-03 21:41:05 +02:00
return object ;
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readResource ( const int3 & mapPosition , std : : shared_ptr < const ObjectTemplate > objectTemplate )
2023-04-03 21:41:05 +02:00
{
auto * object = new CGResource ( ) ;
2023-04-05 13:08:41 +02:00
readMessageAndGuards ( object - > message , object , mapPosition ) ;
2023-04-03 21:41:05 +02:00
object - > amount = reader - > readUInt32 ( ) ;
2023-04-05 13:08:41 +02:00
if ( objectTemplate - > subid = = GameResID ( EGameResID : : GOLD ) )
2023-04-03 21:41:05 +02:00
{
// Gold is multiplied by 100.
object - > amount * = 100 ;
}
reader - > skipZero ( 4 ) ;
return object ;
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readMine ( const int3 & mapPosition , std : : shared_ptr < const ObjectTemplate > objectTemplate )
2023-04-03 21:41:05 +02:00
{
2023-04-11 16:46:08 +02:00
auto * object = new CGMine ( ) ;
if ( objectTemplate - > subid < 7 )
{
setOwnerAndValidate ( mapPosition , object , reader - > readPlayer32 ( ) ) ;
}
else
{
2023-04-03 21:41:05 +02:00
object - > setOwner ( PlayerColor : : NEUTRAL ) ;
2023-05-23 21:41:21 +02:00
reader - > readBitmaskResources ( object - > abandonedMineResources , false ) ;
2023-04-03 21:41:05 +02:00
}
return object ;
}
2023-04-11 16:46:08 +02:00
CGObjectInstance * CMapLoaderH3M : : readDwelling ( const int3 & position )
{
auto * object = new CGDwelling ( ) ;
setOwnerAndValidate ( position , object , reader - > readPlayer32 ( ) ) ;
return object ;
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readDwellingRandom ( const int3 & mapPosition , std : : shared_ptr < const ObjectTemplate > objectTemplate )
2023-04-03 21:41:05 +02:00
{
auto * object = new CGDwelling ( ) ;
CSpecObjInfo * spec = nullptr ;
2023-04-05 13:08:41 +02:00
switch ( objectTemplate - > id )
2023-04-03 21:41:05 +02:00
{
2023-04-05 13:08:41 +02:00
case Obj : : RANDOM_DWELLING :
spec = new CCreGenLeveledCastleInfo ( ) ;
break ;
case Obj : : RANDOM_DWELLING_LVL :
spec = new CCreGenAsCastleInfo ( ) ;
break ;
case Obj : : RANDOM_DWELLING_FACTION :
spec = new CCreGenLeveledInfo ( ) ;
break ;
default :
throw std : : runtime_error ( " Invalid random dwelling format " ) ;
2023-04-11 16:46:08 +02:00
}
spec - > owner = object ;
setOwnerAndValidate ( mapPosition , object , reader - > readPlayer32 ( ) ) ;
//216 and 217
if ( auto * castleSpec = dynamic_cast < CCreGenAsCastleInfo * > ( spec ) )
2023-04-03 21:41:05 +02:00
{
castleSpec - > instanceId = " " ;
castleSpec - > identifier = reader - > readUInt32 ( ) ;
if ( ! castleSpec - > identifier )
{
castleSpec - > asCastle = false ;
const int MASK_SIZE = 8 ;
ui8 mask [ 2 ] ;
mask [ 0 ] = reader - > readUInt8 ( ) ;
mask [ 1 ] = reader - > readUInt8 ( ) ;
castleSpec - > allowedFactions . clear ( ) ;
castleSpec - > allowedFactions . resize ( VLC - > townh - > size ( ) , false ) ;
for ( int i = 0 ; i < MASK_SIZE ; i + + )
2023-04-05 13:08:41 +02:00
castleSpec - > allowedFactions [ i ] = ( ( mask [ 0 ] & ( 1 < < i ) ) > 0 ) ;
2023-04-03 21:41:05 +02:00
2023-04-05 13:08:41 +02:00
for ( int i = 0 ; i < ( GameConstants : : F_NUMBER - MASK_SIZE ) ; i + + )
castleSpec - > allowedFactions [ i + MASK_SIZE ] = ( ( mask [ 1 ] & ( 1 < < i ) ) > 0 ) ;
2023-04-03 21:41:05 +02:00
}
else
{
castleSpec - > asCastle = true ;
}
}
//216 and 218
if ( auto * lvlSpec = dynamic_cast < CCreGenLeveledInfo * > ( spec ) )
{
2023-09-09 21:17:21 +02:00
lvlSpec - > minLevel = std : : max ( reader - > readUInt8 ( ) , static_cast < ui8 > ( 0 ) ) + 1 ;
lvlSpec - > maxLevel = std : : min ( reader - > readUInt8 ( ) , static_cast < ui8 > ( 6 ) ) + 1 ;
2023-04-03 21:41:05 +02:00
}
object - > info = spec ;
return object ;
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readShrine ( )
2023-04-03 21:41:05 +02:00
{
auto * object = new CGShrine ( ) ;
object - > spell = reader - > readSpell32 ( ) ;
return object ;
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readHeroPlaceholder ( const int3 & mapPosition )
2023-04-11 16:46:08 +02:00
{
auto * object = new CGHeroPlaceholder ( ) ;
setOwnerAndValidate ( mapPosition , object , reader - > readPlayer ( ) ) ;
HeroTypeID htid = reader - > readHero ( ) ; //hero type id
2023-04-03 21:41:05 +02:00
if ( htid . getNum ( ) = = - 1 )
{
2023-06-26 15:25:34 +02:00
object - > powerRank = reader - > readUInt8 ( ) ;
2023-09-04 21:21:02 +02:00
logGlobal - > debug ( " Map '%s': Hero placeholder: by power at %s, owned by %s " , mapName , mapPosition . toString ( ) , object - > getOwner ( ) . toString ( ) ) ;
2023-04-03 21:41:05 +02:00
}
else
{
2023-06-26 15:25:34 +02:00
object - > heroType = htid ;
2023-09-04 21:21:02 +02:00
logGlobal - > debug ( " Map '%s': Hero placeholder: %s at %s, owned by %s " , mapName , VLC - > heroh - > getById ( htid ) - > getJsonKey ( ) , mapPosition . toString ( ) , object - > getOwner ( ) . toString ( ) ) ;
2023-04-03 21:41:05 +02:00
}
return object ;
}
2023-04-12 00:02:01 +02:00
CGObjectInstance * CMapLoaderH3M : : readGrail ( const int3 & mapPosition , std : : shared_ptr < const ObjectTemplate > objectTemplate )
2023-04-03 21:41:05 +02:00
{
2023-04-12 00:02:01 +02:00
if ( objectTemplate - > subid < 1000 )
{
map - > grailPos = mapPosition ;
map - > grailRadius = reader - > readInt32 ( ) ;
}
else
{
// Battle location for arena mode in HotA
logGlobal - > warn ( " Map '%s': Arena mode is not supported! " , mapName ) ;
}
2023-04-03 21:41:05 +02:00
return nullptr ;
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readGeneric ( const int3 & mapPosition , std : : shared_ptr < const ObjectTemplate > objectTemplate )
2023-04-03 21:41:05 +02:00
{
2023-04-05 13:08:41 +02:00
if ( VLC - > objtypeh - > knownSubObjects ( objectTemplate - > id ) . count ( objectTemplate - > subid ) )
return VLC - > objtypeh - > getHandlerFor ( objectTemplate - > id , objectTemplate - > subid ) - > create ( objectTemplate ) ;
2023-04-03 21:41:05 +02:00
2023-08-23 14:07:50 +02:00
logGlobal - > warn ( " Map '%s': Unrecognized object %d:%d ('%s') at %s found! " , mapName , objectTemplate - > id . toEnum ( ) , objectTemplate - > subid , objectTemplate - > animationFile . getOriginalName ( ) , mapPosition . toString ( ) ) ;
2023-04-03 21:41:05 +02:00
return new CGObjectInstance ( ) ;
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readPyramid ( const int3 & mapPosition , std : : shared_ptr < const ObjectTemplate > objectTemplate )
2023-04-03 21:41:05 +02:00
{
2023-04-05 13:08:41 +02:00
if ( objectTemplate - > subid = = 0 )
2023-04-03 21:41:05 +02:00
return new CBank ( ) ;
return new CGObjectInstance ( ) ;
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readQuestGuard ( const int3 & mapPosition )
2023-04-03 21:41:05 +02:00
{
auto * guard = new CGQuestGuard ( ) ;
2023-04-05 13:08:41 +02:00
readQuest ( guard , mapPosition ) ;
2023-04-11 16:46:08 +02:00
return guard ;
}
2023-06-07 23:04:13 +02:00
CGObjectInstance * CMapLoaderH3M : : readShipyard ( const int3 & mapPosition , std : : shared_ptr < const ObjectTemplate > objectTemplate )
2023-04-11 16:46:08 +02:00
{
2023-06-07 23:04:13 +02:00
auto * object = readGeneric ( mapPosition , objectTemplate ) ;
2023-04-11 16:46:08 +02:00
setOwnerAndValidate ( mapPosition , object , reader - > readPlayer32 ( ) ) ;
return object ;
}
CGObjectInstance * CMapLoaderH3M : : readLighthouse ( const int3 & mapPosition )
{
auto * object = new CGLighthouse ( ) ;
setOwnerAndValidate ( mapPosition , object , reader - > readPlayer32 ( ) ) ;
return object ;
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readBank ( const int3 & mapPosition , std : : shared_ptr < const ObjectTemplate > objectTemplate )
2023-04-04 12:45:31 +02:00
{
2023-04-05 13:08:41 +02:00
if ( features . levelHOTA3 )
2023-04-04 12:45:31 +02:00
{
2023-04-04 14:40:16 +02:00
//TODO: HotA
2023-04-05 12:40:13 +02:00
// index of guards preset. -1 = random, 0-4 = index of possible guards settings
int32_t guardsPresetIndex = reader - > readInt32 ( ) ;
// presence of upgraded stack: -1 = random, 0 = never, 1 = always
int8_t upgradedStackPresence = reader - > readInt8 ( ) ;
2023-04-04 12:45:31 +02:00
2023-04-05 12:40:13 +02:00
assert ( vstd : : iswithin ( guardsPresetIndex , - 1 , 4 ) ) ;
assert ( vstd : : iswithin ( upgradedStackPresence , - 1 , 1 ) ) ;
// list of possible artifacts in reward
// - if list is empty, artifacts are either not present in reward or random
// - if non-empty, then list always have same number of elements as number of artifacts in bank
// - ArtifactID::NONE indictates random artifact, other values indicate artifact that should be used as reward
2023-04-04 12:45:31 +02:00
std : : vector < ArtifactID > artifacts ;
int artNumber = reader - > readUInt32 ( ) ;
for ( int yy = 0 ; yy < artNumber ; + + yy )
{
2023-04-05 12:40:13 +02:00
artifacts . push_back ( reader - > readArtifact32 ( ) ) ;
2023-04-04 12:45:31 +02:00
}
2023-04-05 12:40:13 +02:00
2023-04-05 13:08:41 +02:00
if ( guardsPresetIndex ! = - 1 | | upgradedStackPresence ! = - 1 | | ! artifacts . empty ( ) )
logGlobal - > warn (
" Map '%s: creature bank at %s settings %d %d %d are not implemented! " ,
mapName ,
mapPosition . toString ( ) ,
guardsPresetIndex ,
int ( upgradedStackPresence ) ,
artifacts . size ( )
) ;
2023-04-04 12:45:31 +02:00
}
2023-04-05 13:08:41 +02:00
return readGeneric ( mapPosition , objectTemplate ) ;
2023-04-04 12:45:31 +02:00
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readObject ( std : : shared_ptr < const ObjectTemplate > objectTemplate , const int3 & mapPosition , const ObjectInstanceID & objectInstanceID )
2023-04-03 21:41:05 +02:00
{
switch ( objectTemplate - > id )
{
2013-02-04 13:29:59 +03:00
case Obj : : EVENT :
2023-09-17 15:06:49 +02:00
return readEvent ( mapPosition , objectInstanceID ) ;
2023-04-03 21:41:05 +02:00
2013-02-04 13:29:59 +03:00
case Obj : : HERO :
case Obj : : RANDOM_HERO :
case Obj : : PRISON :
2023-04-05 13:08:41 +02:00
return readHero ( mapPosition , objectInstanceID ) ;
2023-04-03 21:41:05 +02:00
case Obj : : MONSTER :
2013-02-04 13:29:59 +03:00
case Obj : : RANDOM_MONSTER :
case Obj : : RANDOM_MONSTER_L1 :
case Obj : : RANDOM_MONSTER_L2 :
case Obj : : RANDOM_MONSTER_L3 :
case Obj : : RANDOM_MONSTER_L4 :
case Obj : : RANDOM_MONSTER_L5 :
case Obj : : RANDOM_MONSTER_L6 :
case Obj : : RANDOM_MONSTER_L7 :
2023-04-05 13:08:41 +02:00
return readMonster ( mapPosition , objectInstanceID ) ;
2013-02-07 20:02:15 +03:00
2013-02-04 13:29:59 +03:00
case Obj : : OCEAN_BOTTLE :
case Obj : : SIGN :
2023-04-05 13:08:41 +02:00
return readSign ( mapPosition ) ;
2023-04-03 21:41:05 +02:00
2013-02-04 13:29:59 +03:00
case Obj : : SEER_HUT :
2023-09-17 15:06:49 +02:00
return readSeerHut ( mapPosition , objectInstanceID ) ;
2013-02-04 13:29:59 +03:00
case Obj : : WITCH_HUT :
2023-04-05 13:08:41 +02:00
return readWitchHut ( ) ;
2013-02-04 13:29:59 +03:00
case Obj : : SCHOLAR :
2023-04-05 13:08:41 +02:00
return readScholar ( ) ;
2023-04-11 16:46:08 +02:00
2013-02-04 13:29:59 +03:00
case Obj : : GARRISON :
case Obj : : GARRISON2 :
2023-04-11 16:46:08 +02:00
return readGarrison ( mapPosition ) ;
2013-02-04 13:29:59 +03:00
case Obj : : ARTIFACT :
case Obj : : RANDOM_ART :
case Obj : : RANDOM_TREASURE_ART :
case Obj : : RANDOM_MINOR_ART :
case Obj : : RANDOM_MAJOR_ART :
case Obj : : RANDOM_RELIC_ART :
case Obj : : SPELL_SCROLL :
2023-04-05 13:08:41 +02:00
return readArtifact ( mapPosition , objectTemplate ) ;
2013-02-04 13:29:59 +03:00
case Obj : : RANDOM_RESOURCE :
case Obj : : RESOURCE :
2023-04-05 13:08:41 +02:00
return readResource ( mapPosition , objectTemplate ) ;
2013-02-04 13:29:59 +03:00
case Obj : : RANDOM_TOWN :
case Obj : : TOWN :
2023-04-05 13:08:41 +02:00
return readTown ( mapPosition , objectTemplate ) ;
2023-04-03 21:41:05 +02:00
2013-02-04 13:29:59 +03:00
case Obj : : MINE :
case Obj : : ABANDONED_MINE :
2023-04-05 13:08:41 +02:00
return readMine ( mapPosition , objectTemplate ) ;
2023-04-03 21:41:05 +02:00
2013-02-04 13:29:59 +03:00
case Obj : : CREATURE_GENERATOR1 :
case Obj : : CREATURE_GENERATOR2 :
case Obj : : CREATURE_GENERATOR3 :
case Obj : : CREATURE_GENERATOR4 :
2023-04-05 13:08:41 +02:00
return readDwelling ( mapPosition ) ;
2023-04-03 21:41:05 +02:00
2013-02-04 13:29:59 +03:00
case Obj : : SHRINE_OF_MAGIC_INCANTATION :
case Obj : : SHRINE_OF_MAGIC_GESTURE :
case Obj : : SHRINE_OF_MAGIC_THOUGHT :
2023-04-05 13:08:41 +02:00
return readShrine ( ) ;
2013-02-13 22:35:43 +03:00
2013-02-04 13:29:59 +03:00
case Obj : : PANDORAS_BOX :
2023-09-17 15:06:49 +02:00
return readPandora ( mapPosition , objectInstanceID ) ;
2013-02-04 13:29:59 +03:00
case Obj : : GRAIL :
2023-04-12 00:02:01 +02:00
return readGrail ( mapPosition , objectTemplate ) ;
2013-02-04 13:29:59 +03:00
2023-04-03 21:41:05 +02:00
case Obj : : RANDOM_DWELLING :
case Obj : : RANDOM_DWELLING_LVL :
case Obj : : RANDOM_DWELLING_FACTION :
2023-04-05 13:08:41 +02:00
return readDwellingRandom ( mapPosition , objectTemplate ) ;
2013-02-04 13:29:59 +03:00
2023-04-03 21:41:05 +02:00
case Obj : : QUEST_GUARD :
2023-04-11 16:46:08 +02:00
return readQuestGuard ( mapPosition ) ;
2013-02-04 13:29:59 +03:00
2023-04-11 16:46:08 +02:00
case Obj : : SHIPYARD :
2023-06-07 23:04:13 +02:00
return readShipyard ( mapPosition , objectTemplate ) ;
2013-02-04 13:29:59 +03:00
2023-04-11 16:46:08 +02:00
case Obj : : HERO_PLACEHOLDER :
return readHeroPlaceholder ( mapPosition ) ;
2013-02-04 13:29:59 +03:00
2023-04-03 21:41:05 +02:00
case Obj : : PYRAMID :
2023-04-11 16:46:08 +02:00
return readPyramid ( mapPosition , objectTemplate ) ;
2013-02-04 13:29:59 +03:00
2023-04-11 16:46:08 +02:00
case Obj : : LIGHTHOUSE :
return readLighthouse ( mapPosition ) ;
2013-02-04 13:29:59 +03:00
2023-04-11 16:46:08 +02:00
case Obj : : CREATURE_BANK :
case Obj : : DERELICT_SHIP :
2023-04-04 12:45:31 +02:00
case Obj : : DRAGON_UTOPIA :
case Obj : : CRYPT :
case Obj : : SHIPWRECK :
2023-04-05 13:08:41 +02:00
return readBank ( mapPosition , objectTemplate ) ;
2013-02-04 13:29:59 +03:00
default : //any other object
2023-04-05 13:08:41 +02:00
return readGeneric ( mapPosition , objectTemplate ) ;
2023-04-03 21:41:05 +02:00
}
}
void CMapLoaderH3M : : readObjects ( )
{
2023-04-05 13:08:41 +02:00
uint32_t objectsCount = reader - > readUInt32 ( ) ;
2023-04-03 21:41:05 +02:00
2023-04-05 13:08:41 +02:00
for ( uint32_t i = 0 ; i < objectsCount ; + + i )
2023-04-03 21:41:05 +02:00
{
2023-04-05 13:08:41 +02:00
int3 mapPosition = reader - > readInt3 ( ) ;
2023-04-03 21:41:05 +02:00
2023-04-05 13:08:41 +02:00
uint32_t defIndex = reader - > readUInt32 ( ) ;
ObjectInstanceID objectInstanceID = ObjectInstanceID ( static_cast < si32 > ( map - > objects . size ( ) ) ) ;
2023-04-03 21:41:05 +02:00
2023-04-05 13:08:41 +02:00
std : : shared_ptr < const ObjectTemplate > objectTemplate = templates . at ( defIndex ) ;
2023-04-03 21:41:05 +02:00
reader - > skipZero ( 5 ) ;
2023-04-05 13:08:41 +02:00
CGObjectInstance * newObject = readObject ( objectTemplate , mapPosition , objectInstanceID ) ;
2023-04-03 21:41:05 +02:00
2023-04-05 13:08:41 +02:00
if ( ! newObject )
2023-04-03 21:41:05 +02:00
continue ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
newObject - > pos = mapPosition ;
newObject - > ID = objectTemplate - > id ;
newObject - > id = objectInstanceID ;
if ( newObject - > ID ! = Obj : : HERO & & newObject - > ID ! = Obj : : HERO_PLACEHOLDER & & newObject - > ID ! = Obj : : PRISON )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
newObject - > subID = objectTemplate - > subid ;
2013-02-04 13:29:59 +03:00
}
2023-04-05 13:08:41 +02:00
newObject - > appearance = objectTemplate ;
assert ( objectInstanceID = = ObjectInstanceID ( ( si32 ) map - > objects . size ( ) ) ) ;
2016-02-22 18:26:42 +02:00
2023-09-20 21:00:03 +02:00
if ( newObject - > isVisitable ( ) & & ! map - > isInTheMap ( newObject - > visitablePos ( ) ) )
logGlobal - > error ( " Map '%s': Object at %s - outside of map borders! " , mapName , mapPosition . toString ( ) ) ;
2013-02-04 13:29:59 +03:00
{
2023-04-02 18:56:10 +02:00
//TODO: define valid typeName and subtypeName for H3M maps
2016-02-22 18:26:42 +02:00
//boost::format fmt("%s_%d");
//fmt % nobj->typeName % nobj->id.getNum();
boost : : format fmt ( " obj_%d " ) ;
2023-04-05 13:08:41 +02:00
fmt % newObject - > id . getNum ( ) ;
newObject - > instanceName = fmt . str ( ) ;
2013-02-04 13:29:59 +03:00
}
2023-04-05 13:08:41 +02:00
map - > addNewObject ( newObject ) ;
2013-02-04 13:29:59 +03:00
}
2023-04-05 13:08:41 +02:00
std : : sort (
map - > heroesOnMap . begin ( ) ,
map - > heroesOnMap . end ( ) ,
[ ] ( const ConstTransitivePtr < CGHeroInstance > & a , const ConstTransitivePtr < CGHeroInstance > & b )
{
return a - > subID < b - > subID ;
}
) ;
2013-02-04 13:29:59 +03:00
}
2013-02-05 00:58:42 +03:00
void CMapLoaderH3M : : readCreatureSet ( CCreatureSet * out , int number )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
for ( int index = 0 ; index < number ; + + index )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
CreatureID creatureID = reader - > readCreature ( ) ;
2023-04-02 18:56:10 +02:00
int count = reader - > readUInt16 ( ) ;
2013-02-04 13:29:59 +03:00
// Empty slot
2023-04-05 13:08:41 +02:00
if ( creatureID = = CreatureID : : NONE )
2014-02-09 15:10:02 +03:00
continue ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
auto * result = new CStackInstance ( ) ;
result - > count = count ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
if ( creatureID < CreatureID : : NONE )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
int value = - creatureID . getNum ( ) - 2 ;
2023-04-02 18:56:10 +02:00
assert ( value > = 0 & & value < 14 ) ;
uint8_t level = value / 2 ;
uint8_t upgrade = value % 2 ;
2013-02-04 13:29:59 +03:00
//this will happen when random object has random army
2023-04-05 13:08:41 +02:00
result - > randomStack = CStackInstance : : RandomStackInfo { level , upgrade } ;
2013-02-04 13:29:59 +03:00
}
else
{
2023-04-05 13:08:41 +02:00
result - > setType ( creatureID ) ;
2013-02-04 13:29:59 +03:00
}
2023-04-05 13:08:41 +02:00
out - > putStack ( SlotID ( index ) , result ) ;
2013-02-04 13:29:59 +03:00
}
out - > validTypes ( true ) ;
}
2023-04-11 16:46:08 +02:00
void CMapLoaderH3M : : setOwnerAndValidate ( const int3 & mapPosition , CGObjectInstance * object , const PlayerColor & owner )
2013-02-04 13:29:59 +03:00
{
2023-04-11 16:46:08 +02:00
assert ( owner . isValidPlayer ( ) | | owner = = PlayerColor : : NEUTRAL ) ;
2013-02-04 13:29:59 +03:00
2023-04-11 16:46:08 +02:00
if ( owner = = PlayerColor : : NEUTRAL )
2013-02-04 13:29:59 +03:00
{
2023-04-11 16:46:08 +02:00
object - > setOwner ( PlayerColor : : NEUTRAL ) ;
return ;
}
if ( ! owner . isValidPlayer ( ) )
{
object - > setOwner ( PlayerColor : : NEUTRAL ) ;
logGlobal - > warn ( " Map '%s': Object at %s - owned by invalid player %d! Will be set to neutral! " , mapName , mapPosition . toString ( ) , int ( owner . getNum ( ) ) ) ;
return ;
}
if ( ! mapHeader - > players [ owner . getNum ( ) ] . canAnyonePlay ( ) )
{
object - > setOwner ( PlayerColor : : NEUTRAL ) ;
logGlobal - > warn ( " Map '%s': Object at %s - owned by non-existing player %d! Will be set to neutral! " , mapName , mapPosition . toString ( ) , int ( owner . getNum ( ) )
) ;
return ;
2013-02-04 13:29:59 +03:00
}
2023-04-11 16:46:08 +02:00
object - > setOwner ( owner ) ;
}
CGObjectInstance * CMapLoaderH3M : : readHero ( const int3 & mapPosition , const ObjectInstanceID & objectInstanceID )
{
auto * object = new CGHeroInstance ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
if ( features . levelAB )
2013-02-04 13:29:59 +03:00
{
2023-02-24 16:15:45 +02:00
unsigned int identifier = reader - > readUInt32 ( ) ;
2023-04-05 13:08:41 +02:00
map - > questIdentifierToId [ identifier ] = objectInstanceID ;
2013-02-04 13:29:59 +03:00
}
2023-04-02 18:56:10 +02:00
PlayerColor owner = reader - > readPlayer ( ) ;
2023-06-20 14:09:42 +02:00
object - > subID = reader - > readHero ( ) . getNum ( ) ;
2013-09-03 16:18:09 +03:00
2013-05-19 18:14:23 +03:00
//If hero of this type has been predefined, use that as a base.
//Instance data will overwrite the predefined values where appropriate.
2013-06-29 16:05:48 +03:00
for ( auto & elem : map - > predefinedHeroes )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
if ( elem - > subID = = object - > subID )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
logGlobal - > debug ( " Hero %d will be taken from the predefined heroes list. " , object - > subID ) ;
delete object ;
object = elem ;
2013-02-04 13:29:59 +03:00
break ;
}
}
2023-04-11 16:46:08 +02:00
setOwnerAndValidate ( mapPosition , object , owner ) ;
2013-02-04 13:29:59 +03:00
2013-06-29 16:05:48 +03:00
for ( auto & elem : map - > disposedHeroes )
2013-02-04 13:29:59 +03:00
{
2023-08-19 23:22:31 +02:00
if ( elem . heroId . getNum ( ) = = object - > subID )
2013-02-04 13:29:59 +03:00
{
2023-09-28 00:00:32 +02:00
object - > nameCustomTextId = elem . name ;
2023-09-28 18:43:04 +02:00
object - > customPortraitSource = elem . portrait ;
2013-02-04 13:29:59 +03:00
break ;
}
}
2023-02-24 16:15:45 +02:00
bool hasName = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasName )
2023-09-28 00:00:32 +02:00
object - > nameCustomTextId = readLocalizedString ( TextIdentifier ( " heroes " , object - > subID , " name " ) ) ;
2023-04-02 18:56:10 +02:00
if ( features . levelSOD )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
bool hasCustomExperience = reader - > readBool ( ) ;
if ( hasCustomExperience )
object - > exp = reader - > readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2023-04-05 13:08:41 +02:00
object - > exp = reader - > readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
//0 means "not set" in <=AB maps
2023-04-05 13:08:41 +02:00
if ( ! object - > exp )
object - > exp = CGHeroInstance : : UNINITIALIZED_EXPERIENCE ;
2013-02-04 13:29:59 +03:00
}
2023-02-24 16:15:45 +02:00
bool hasPortrait = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasPortrait )
2023-09-28 18:43:04 +02:00
object - > customPortraitSource = reader - > readHeroPortrait ( ) ;
2013-02-04 13:29:59 +03:00
2023-02-24 16:15:45 +02:00
bool hasSecSkills = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasSecSkills )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
if ( ! object - > secSkills . empty ( ) )
2013-09-03 16:18:09 +03:00
{
2023-04-05 13:08:41 +02:00
if ( object - > secSkills [ 0 ] . first ! = SecondarySkill : : DEFAULT )
2023-09-20 21:00:03 +02:00
logGlobal - > debug ( " Map '%s': Hero %s subID=%d has set secondary skills twice (in map properties and on adventure map instance). Using the latter set... " , mapName , object - > getNameTextID ( ) , object - > subID ) ;
2023-04-05 13:08:41 +02:00
object - > secSkills . clear ( ) ;
2013-09-03 16:18:09 +03:00
}
2023-04-05 13:08:41 +02:00
uint32_t skillsCount = reader - > readUInt32 ( ) ;
object - > secSkills . resize ( skillsCount ) ;
for ( int i = 0 ; i < skillsCount ; + + i )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
object - > secSkills [ i ] . first = reader - > readSkill ( ) ;
object - > secSkills [ i ] . second = reader - > readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
}
2023-02-24 16:15:45 +02:00
bool hasGarison = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasGarison )
2023-04-10 13:01:09 +02:00
readCreatureSet ( object , 7 ) ;
object - > formation = static_cast < EArmyFormation > ( reader - > readUInt8 ( ) ) ;
assert ( object - > formation = = EArmyFormation : : LOOSE | | object - > formation = = EArmyFormation : : TIGHT ) ;
2013-02-04 13:29:59 +03:00
2023-04-10 13:01:09 +02:00
loadArtifactsOfHero ( object ) ;
object - > patrol . patrolRadius = reader - > readUInt8 ( ) ;
2023-04-05 13:08:41 +02:00
object - > patrol . patrolling = ( object - > patrol . patrolRadius ! = 0xff ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
if ( features . levelAB )
2013-02-04 13:29:59 +03:00
{
2023-02-24 16:15:45 +02:00
bool hasCustomBiography = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasCustomBiography )
2023-09-28 00:00:32 +02:00
object - > biographyCustomTextId = readLocalizedString ( TextIdentifier ( " heroes " , object - > subID , " biography " ) ) ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
object - > gender = static_cast < EHeroGender > ( reader - > readUInt8 ( ) ) ;
assert ( object - > gender = = EHeroGender : : MALE | | object - > gender = = EHeroGender : : FEMALE | | object - > gender = = EHeroGender : : DEFAULT ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2023-04-05 13:08:41 +02:00
object - > gender = EHeroGender : : DEFAULT ;
2013-02-04 13:29:59 +03:00
}
// Spells
2023-04-02 18:56:10 +02:00
if ( features . levelSOD )
2013-02-04 13:29:59 +03:00
{
2023-02-24 16:15:45 +02:00
bool hasCustomSpells = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasCustomSpells )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
if ( ! object - > spells . empty ( ) )
2023-04-02 18:56:10 +02:00
{
2023-08-07 18:12:04 +02:00
object - > spells . clear ( ) ;
2023-07-17 15:22:29 +02:00
logGlobal - > debug ( " Hero %s subID=%d has spells set twice (in map properties and on adventure map instance). Using the latter set... " , object - > getNameTextID ( ) , object - > subID ) ;
2023-04-02 18:56:10 +02:00
}
2023-04-05 13:08:41 +02:00
object - > spells . insert ( SpellID : : PRESET ) ; //placeholder "preset spells"
2013-02-05 00:58:42 +03:00
2023-05-23 21:41:21 +02:00
reader - > readBitmaskSpells ( object - > spells , false ) ;
2013-02-04 13:29:59 +03:00
}
}
2023-04-02 18:56:10 +02:00
else if ( features . levelAB )
2013-02-04 13:29:59 +03:00
{
//we can read one spell
2023-04-02 18:56:10 +02:00
SpellID spell = reader - > readSpell ( ) ;
if ( spell ! = SpellID : : NONE )
2023-04-05 13:08:41 +02:00
object - > spells . insert ( spell ) ;
2013-02-04 13:29:59 +03:00
}
2023-04-02 18:56:10 +02:00
if ( features . levelSOD )
2013-02-04 13:29:59 +03:00
{
2023-02-24 16:15:45 +02:00
bool hasCustomPrimSkills = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasCustomPrimSkills )
2013-02-04 13:29:59 +03:00
{
2023-05-01 00:20:01 +02:00
auto ps = object - > getAllBonuses ( Selector : : type ( ) ( BonusType : : PRIMARY_SKILL ) . And ( Selector : : sourceType ( ) ( BonusSource : : HERO_BASE_SKILL ) ) , nullptr ) ;
2013-09-03 16:18:09 +03:00
if ( ps - > size ( ) )
{
2023-09-20 21:00:03 +02:00
logGlobal - > debug ( " Hero %s subID=%d has set primary skills twice (in map properties and on adventure map instance). Using the latter set... " , object - > getNameTranslated ( ) , object - > subID ) ;
2023-02-11 18:30:06 +02:00
for ( const auto & b : * ps )
2023-04-05 13:08:41 +02:00
object - > removeBonus ( b ) ;
2013-09-03 16:18:09 +03:00
}
2013-02-04 13:29:59 +03:00
for ( int xx = 0 ; xx < GameConstants : : PRIMARY_SKILLS ; + + xx )
{
2023-08-19 20:43:50 +02:00
object - > pushPrimSkill ( static_cast < PrimarySkill > ( xx ) , reader - > readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
}
}
2023-06-25 16:47:45 +02:00
if ( object - > subID ! = - 1 )
2023-09-04 21:21:02 +02:00
logGlobal - > debug ( " Map '%s': Hero on map: %s at %s, owned by %s " , mapName , VLC - > heroh - > getByIndex ( object - > subID ) - > getJsonKey ( ) , mapPosition . toString ( ) , object - > getOwner ( ) . toString ( ) ) ;
2023-06-25 16:47:45 +02:00
else
2023-09-04 21:21:02 +02:00
logGlobal - > debug ( " Map '%s': Hero on map: (random) at %s, owned by %s " , mapName , mapPosition . toString ( ) , object - > getOwner ( ) . toString ( ) ) ;
2023-06-25 16:47:45 +02:00
2023-04-02 18:56:10 +02:00
reader - > skipZero ( 16 ) ;
2023-04-05 13:08:41 +02:00
return object ;
2013-02-04 13:29:59 +03:00
}
2023-09-17 15:06:49 +02:00
CGObjectInstance * CMapLoaderH3M : : readSeerHut ( const int3 & position , const ObjectInstanceID & idToBeGiven )
2013-02-04 13:29:59 +03:00
{
2023-02-11 18:30:06 +02:00
auto * hut = new CGSeerHut ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-05 00:08:24 +02:00
uint32_t questsCount = 1 ;
2023-04-05 13:08:41 +02:00
if ( features . levelHOTA3 )
2023-04-05 00:08:24 +02:00
questsCount = reader - > readUInt32 ( ) ;
//TODO: HotA
2023-04-05 13:08:41 +02:00
if ( questsCount > 1 )
2023-04-05 12:40:13 +02:00
logGlobal - > warn ( " Map '%s': Seer Hut at %s - %d quests are not implemented! " , mapName , position . toString ( ) , questsCount ) ;
2023-04-05 00:08:24 +02:00
2023-04-05 13:08:41 +02:00
for ( size_t i = 0 ; i < questsCount ; + + i )
2023-09-17 15:06:49 +02:00
readSeerHutQuest ( hut , position , idToBeGiven ) ;
2023-04-05 00:08:24 +02:00
2023-04-05 13:08:41 +02:00
if ( features . levelHOTA3 )
2013-02-04 13:29:59 +03:00
{
2023-04-05 00:08:24 +02:00
uint32_t repeateableQuestsCount = reader - > readUInt32 ( ) ;
2023-04-04 12:45:31 +02:00
2023-04-05 13:08:41 +02:00
if ( repeateableQuestsCount ! = 0 )
2023-04-05 00:08:24 +02:00
logGlobal - > warn ( " Map '%s': Seer Hut at %s - %d repeatable quests are not implemented! " , mapName , position . toString ( ) , repeateableQuestsCount ) ;
2023-04-05 13:08:41 +02:00
for ( size_t i = 0 ; i < repeateableQuestsCount ; + + i )
2023-09-17 15:06:49 +02:00
readSeerHutQuest ( hut , position , idToBeGiven ) ;
2023-04-05 00:08:24 +02:00
}
reader - > skipZero ( 2 ) ;
return hut ;
}
2023-09-17 15:06:49 +02:00
enum class ESeerHutRewardType : uint8_t
{
NOTHING = 0 ,
EXPERIENCE = 1 ,
MANA_POINTS = 2 ,
MORALE = 3 ,
LUCK = 4 ,
RESOURCES = 5 ,
PRIMARY_SKILL = 6 ,
SECONDARY_SKILL = 7 ,
ARTIFACT = 8 ,
SPELL = 9 ,
CREATURE = 10 ,
} ;
2023-10-09 19:15:34 +02:00
enum class EQuestMission {
NONE = 0 ,
LEVEL = 1 ,
PRIMARY_SKILL = 2 ,
KILL_HERO = 3 ,
KILL_CREATURE = 4 ,
ARTIFACT = 5 ,
ARMY = 6 ,
RESOURCES = 7 ,
HERO = 8 ,
PLAYER = 9 ,
HOTA_MULTI = 10 ,
// end of H3 missions
KEYMASTER = 100 ,
HOTA_HERO_CLASS = 101 ,
HOTA_REACH_DATE = 102
} ;
2023-09-17 15:06:49 +02:00
void CMapLoaderH3M : : readSeerHutQuest ( CGSeerHut * hut , const int3 & position , const ObjectInstanceID & idToBeGiven )
2023-04-05 00:08:24 +02:00
{
2023-10-09 19:15:34 +02:00
EQuestMission missionType = EQuestMission : : NONE ;
2023-04-05 00:08:24 +02:00
if ( features . levelAB )
2013-02-04 13:29:59 +03:00
{
2023-10-09 19:15:34 +02:00
missionType = static_cast < EQuestMission > ( readQuest ( hut , position ) ) ;
2013-02-04 13:29:59 +03:00
}
else
{
//RoE
2023-04-02 18:56:10 +02:00
auto artID = reader - > readArtifact ( ) ;
2023-04-05 13:08:41 +02:00
if ( artID ! = ArtifactID : : NONE )
2013-02-04 13:29:59 +03:00
{
//not none quest
2023-10-11 00:47:19 +02:00
hut - > quest - > mission . artifacts . push_back ( artID ) ;
2023-10-09 19:15:34 +02:00
missionType = EQuestMission : : ARTIFACT ;
2013-02-04 13:29:59 +03:00
}
hut - > quest - > lastDay = - 1 ; //no timeout
2023-04-02 18:56:10 +02:00
hut - > quest - > isCustomFirst = false ;
hut - > quest - > isCustomNext = false ;
hut - > quest - > isCustomComplete = false ;
2013-02-04 13:29:59 +03:00
}
2023-10-09 19:15:34 +02:00
if ( missionType ! = EQuestMission : : NONE )
2013-02-04 13:29:59 +03:00
{
2023-09-17 15:06:49 +02:00
auto rewardType = static_cast < ESeerHutRewardType > ( reader - > readUInt8 ( ) ) ;
2023-09-15 15:29:41 +02:00
Rewardable : : VisitInfo vinfo ;
auto & reward = vinfo . reward ;
2013-02-04 13:29:59 +03:00
switch ( rewardType )
{
2023-09-17 15:06:49 +02:00
case ESeerHutRewardType : : NOTHING :
2013-02-04 13:29:59 +03:00
{
2023-09-13 01:40:07 +02:00
// no-op
2013-02-04 13:29:59 +03:00
break ;
}
2023-09-17 15:06:49 +02:00
case ESeerHutRewardType : : EXPERIENCE :
2013-02-04 13:29:59 +03:00
{
2023-09-13 01:40:07 +02:00
reward . heroExperience = reader - > readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-09-17 15:06:49 +02:00
case ESeerHutRewardType : : MANA_POINTS :
2013-02-04 13:29:59 +03:00
{
2023-09-13 01:40:07 +02:00
reward . manaDiff = reader - > readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-09-17 15:06:49 +02:00
case ESeerHutRewardType : : MORALE :
2013-02-04 13:29:59 +03:00
{
2023-09-17 15:06:49 +02:00
reward . bonuses . emplace_back ( BonusDuration : : ONE_BATTLE , BonusType : : MORALE , BonusSource : : OBJECT , reader - > readUInt8 ( ) , idToBeGiven ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-09-17 15:06:49 +02:00
case ESeerHutRewardType : : LUCK :
2013-02-04 13:29:59 +03:00
{
2023-09-17 15:06:49 +02:00
reward . bonuses . emplace_back ( BonusDuration : : ONE_BATTLE , BonusType : : LUCK , BonusSource : : OBJECT , reader - > readUInt8 ( ) , idToBeGiven ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-09-17 15:06:49 +02:00
case ESeerHutRewardType : : RESOURCES :
2013-02-04 13:29:59 +03:00
{
2023-09-13 01:40:07 +02:00
auto rId = reader - > readUInt8 ( ) ;
auto rVal = reader - > readUInt32 ( ) ;
assert ( rId < features . resourcesCount ) ;
2023-09-20 21:00:03 +02:00
2023-09-13 01:40:07 +02:00
reward . resources [ rId ] = rVal ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-09-17 15:06:49 +02:00
case ESeerHutRewardType : : PRIMARY_SKILL :
2013-02-04 13:29:59 +03:00
{
2023-09-13 01:40:07 +02:00
auto rId = reader - > readUInt8 ( ) ;
auto rVal = reader - > readUInt8 ( ) ;
reward . primary . at ( rId ) = rVal ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-09-17 15:06:49 +02:00
case ESeerHutRewardType : : SECONDARY_SKILL :
2013-02-04 13:29:59 +03:00
{
2023-09-13 01:40:07 +02:00
auto rId = reader - > readSkill ( ) ;
auto rVal = reader - > readUInt8 ( ) ;
reward . secondary [ rId ] = rVal ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-09-17 15:06:49 +02:00
case ESeerHutRewardType : : ARTIFACT :
2013-02-04 13:29:59 +03:00
{
2023-09-13 01:40:07 +02:00
reward . artifacts . push_back ( reader - > readArtifact ( ) ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-09-17 15:06:49 +02:00
case ESeerHutRewardType : : SPELL :
2013-02-04 13:29:59 +03:00
{
2023-09-13 01:40:07 +02:00
reward . spells . push_back ( reader - > readSpell ( ) ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-09-17 15:06:49 +02:00
case ESeerHutRewardType : : CREATURE :
2023-04-02 18:56:10 +02:00
{
2023-09-13 01:40:07 +02:00
auto rId = reader - > readCreature ( ) ;
auto rVal = reader - > readUInt16 ( ) ;
reward . creatures . emplace_back ( rId , rVal ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-04-05 13:08:41 +02:00
default :
2023-04-02 18:56:10 +02:00
{
assert ( 0 ) ;
}
2013-02-04 13:29:59 +03:00
}
2023-09-13 01:40:07 +02:00
2023-09-15 15:29:41 +02:00
vinfo . visitType = Rewardable : : EEventType : : EVENT_FIRST_VISIT ;
hut - > configuration . info . push_back ( vinfo ) ;
2013-02-04 13:29:59 +03:00
}
else
{
// missionType==255
2023-04-05 00:08:24 +02:00
reader - > skipZero ( 1 ) ;
2013-02-04 13:29:59 +03:00
}
}
2023-10-09 19:15:34 +02:00
int CMapLoaderH3M : : readQuest ( IQuestObject * guard , const int3 & position )
2013-02-04 13:29:59 +03:00
{
2023-10-09 19:15:34 +02:00
auto missionId = reader - > readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2023-10-09 19:15:34 +02:00
switch ( static_cast < EQuestMission > ( missionId ) )
2013-02-04 13:29:59 +03:00
{
2023-10-09 19:15:34 +02:00
case EQuestMission : : NONE :
return missionId ;
case EQuestMission : : PRIMARY_SKILL :
2013-02-04 13:29:59 +03:00
{
for ( int x = 0 ; x < 4 ; + + x )
{
2023-10-11 00:47:19 +02:00
guard - > quest - > mission . primary [ x ] = reader - > readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
2023-10-10 02:39:10 +02:00
break ;
2013-02-04 13:29:59 +03:00
}
2023-10-09 19:15:34 +02:00
case EQuestMission : : LEVEL :
{
2023-10-11 00:47:19 +02:00
guard - > quest - > mission . heroLevel = reader - > readUInt32 ( ) ;
2023-10-10 02:39:10 +02:00
break ;
2023-10-09 19:15:34 +02:00
}
case EQuestMission : : KILL_HERO :
case EQuestMission : : KILL_CREATURE :
2013-02-04 13:29:59 +03:00
{
2023-10-11 00:47:19 +02:00
guard - > quest - > killTarget = ObjectInstanceID ( reader - > readUInt32 ( ) ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-10-09 19:15:34 +02:00
case EQuestMission : : ARTIFACT :
2013-02-04 13:29:59 +03:00
{
2023-02-24 16:15:45 +02:00
int artNumber = reader - > readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
for ( int yy = 0 ; yy < artNumber ; + + yy )
{
2023-04-02 18:56:10 +02:00
auto artid = reader - > readArtifact ( ) ;
2023-10-11 00:47:19 +02:00
guard - > quest - > mission . artifacts . push_back ( artid ) ;
2013-02-04 13:29:59 +03:00
map - > allowedArtifact [ artid ] = false ; //these are unavailable for random generation
}
break ;
}
2023-10-09 19:15:34 +02:00
case EQuestMission : : ARMY :
2013-02-04 13:29:59 +03:00
{
2023-02-24 16:15:45 +02:00
int typeNumber = reader - > readUInt8 ( ) ;
2023-10-11 00:47:19 +02:00
guard - > quest - > mission . creatures . resize ( typeNumber ) ;
2013-02-04 13:29:59 +03:00
for ( int hh = 0 ; hh < typeNumber ; + + hh )
{
2023-10-11 00:47:19 +02:00
guard - > quest - > mission . creatures [ hh ] . type = VLC - > creh - > objects [ reader - > readCreature ( ) ] ;
guard - > quest - > mission . creatures [ hh ] . count = reader - > readUInt16 ( ) ;
2013-02-04 13:29:59 +03:00
}
break ;
}
2023-10-09 19:15:34 +02:00
case EQuestMission : : RESOURCES :
2013-02-04 13:29:59 +03:00
{
for ( int x = 0 ; x < 7 ; + + x )
2023-10-11 00:47:19 +02:00
guard - > quest - > mission . resources [ x ] = reader - > readUInt32 ( ) ;
2023-04-05 02:26:29 +02:00
2013-02-04 13:29:59 +03:00
break ;
}
2023-10-09 19:15:34 +02:00
case EQuestMission : : HERO :
2013-02-04 13:29:59 +03:00
{
2023-10-11 00:47:19 +02:00
guard - > quest - > mission . heroes . push_back ( reader - > readHero ( ) ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-10-09 19:15:34 +02:00
case EQuestMission : : PLAYER :
2013-02-04 13:29:59 +03:00
{
2023-10-11 00:47:19 +02:00
guard - > quest - > mission . players . push_back ( reader - > readPlayer ( ) ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2023-10-09 19:15:34 +02:00
case EQuestMission : : HOTA_MULTI :
2023-04-05 00:08:24 +02:00
{
uint32_t missionSubID = reader - > readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
if ( missionSubID = = 0 )
2023-04-05 00:08:24 +02:00
{
2023-10-09 19:15:34 +02:00
missionId = int ( EQuestMission : : HOTA_HERO_CLASS ) ;
2023-04-05 00:08:24 +02:00
std : : set < HeroClassID > heroClasses ;
2023-05-23 21:41:21 +02:00
reader - > readBitmaskHeroClassesSized ( heroClasses , false ) ;
2023-10-09 05:24:40 +02:00
for ( auto & hc : heroClasses )
2023-10-11 00:47:19 +02:00
guard - > quest - > mission . heroClasses . push_back ( hc ) ;
2023-04-05 00:08:24 +02:00
break ;
}
2023-04-05 13:08:41 +02:00
if ( missionSubID = = 1 )
2023-04-05 00:08:24 +02:00
{
2023-10-09 19:15:34 +02:00
missionId = int ( EQuestMission : : HOTA_REACH_DATE ) ;
2023-10-11 00:47:19 +02:00
guard - > quest - > mission . daysPassed = reader - > readUInt32 ( ) ;
2023-04-05 00:08:24 +02:00
break ;
}
break ;
}
2023-04-05 13:08:41 +02:00
default :
2023-04-02 18:56:10 +02:00
{
assert ( 0 ) ;
}
2013-02-04 13:29:59 +03:00
}
2023-04-02 18:56:10 +02:00
guard - > quest - > lastDay = reader - > readInt32 ( ) ;
2023-09-27 23:49:27 +02:00
guard - > quest - > firstVisitText . appendTextID ( readLocalizedString ( TextIdentifier ( " quest " , position . x , position . y , position . z , " firstVisit " ) ) ) ;
guard - > quest - > nextVisitText . appendTextID ( readLocalizedString ( TextIdentifier ( " quest " , position . x , position . y , position . z , " nextVisit " ) ) ) ;
guard - > quest - > completedText . appendTextID ( readLocalizedString ( TextIdentifier ( " quest " , position . x , position . y , position . z , " completed " ) ) ) ;
2023-02-11 18:30:06 +02:00
guard - > quest - > isCustomFirst = ! guard - > quest - > firstVisitText . empty ( ) ;
guard - > quest - > isCustomNext = ! guard - > quest - > nextVisitText . empty ( ) ;
guard - > quest - > isCustomComplete = ! guard - > quest - > completedText . empty ( ) ;
2023-10-09 19:15:34 +02:00
return missionId ;
2013-02-04 13:29:59 +03:00
}
2023-04-05 13:08:41 +02:00
CGObjectInstance * CMapLoaderH3M : : readTown ( const int3 & position , std : : shared_ptr < const ObjectTemplate > objectTemplate )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
auto * object = new CGTownInstance ( ) ;
2023-04-11 16:46:08 +02:00
if ( features . levelAB )
object - > identifier = reader - > readUInt32 ( ) ;
setOwnerAndValidate ( position , object , reader - > readPlayer ( ) ) ;
2023-05-23 15:13:09 +02:00
std : : optional < FactionID > faction ;
if ( objectTemplate - > id = = Obj : : TOWN )
faction = FactionID ( objectTemplate - > subid ) ;
2023-02-24 16:15:45 +02:00
bool hasName = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasName )
2023-09-27 23:57:05 +02:00
object - > setNameTextId ( readLocalizedString ( TextIdentifier ( " town " , position . x , position . y , position . z , " name " ) ) ) ;
2013-02-04 13:29:59 +03:00
2023-02-24 16:15:45 +02:00
bool hasGarrison = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasGarrison )
2023-04-10 13:01:09 +02:00
readCreatureSet ( object , 7 ) ;
object - > formation = static_cast < EArmyFormation > ( reader - > readUInt8 ( ) ) ;
assert ( object - > formation = = EArmyFormation : : LOOSE | | object - > formation = = EArmyFormation : : TIGHT ) ;
2013-02-04 13:29:59 +03:00
2023-02-24 16:15:45 +02:00
bool hasCustomBuildings = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasCustomBuildings )
2013-02-04 13:29:59 +03:00
{
2023-05-23 15:13:09 +02:00
reader - > readBitmaskBuildings ( object - > builtBuildings , faction ) ;
reader - > readBitmaskBuildings ( object - > forbiddenBuildings , faction ) ;
2013-02-04 13:29:59 +03:00
}
// Standard buildings
else
{
2023-02-24 16:15:45 +02:00
bool hasFort = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasFort )
2023-04-05 13:08:41 +02:00
object - > builtBuildings . insert ( BuildingID : : FORT ) ;
2013-02-04 13:29:59 +03:00
//means that set of standard building should be included
2023-04-05 13:08:41 +02:00
object - > builtBuildings . insert ( BuildingID : : DEFAULT ) ;
2013-02-04 13:29:59 +03:00
}
2023-04-02 18:56:10 +02:00
if ( features . levelAB )
2013-02-04 13:29:59 +03:00
{
2023-04-02 18:56:10 +02:00
std : : set < SpellID > spellsMask ;
2023-05-23 21:41:21 +02:00
reader - > readBitmaskSpells ( spellsMask , false ) ;
2023-04-05 13:08:41 +02:00
std : : copy ( spellsMask . begin ( ) , spellsMask . end ( ) , std : : back_inserter ( object - > obligatorySpells ) ) ;
2013-02-04 13:29:59 +03:00
}
{
2023-04-02 18:56:10 +02:00
std : : set < SpellID > spellsMask ;
2023-05-23 21:41:21 +02:00
reader - > readBitmaskSpells ( spellsMask , true ) ;
2023-04-05 13:08:41 +02:00
std : : copy ( spellsMask . begin ( ) , spellsMask . end ( ) , std : : back_inserter ( object - > possibleSpells ) ) ;
2023-04-02 18:56:10 +02:00
2023-04-03 14:02:15 +02:00
auto defaultAllowed = VLC - > spellh - > getDefaultAllowed ( ) ;
//add all spells from mods
2023-04-05 13:08:41 +02:00
for ( int i = features . spellsCount ; i < defaultAllowed . size ( ) ; + + i )
if ( defaultAllowed [ i ] )
object - > possibleSpells . emplace_back ( i ) ;
2013-02-04 13:29:59 +03:00
}
2023-04-09 00:03:43 +02:00
if ( features . levelHOTA1 )
2014-03-09 12:37:20 +03:00
{
2023-04-04 14:40:16 +02:00
// TODO: HOTA support
2023-04-15 16:20:19 +02:00
[[maybe_unused]] bool spellResearchAvailable = reader - > readBool ( ) ;
2014-03-09 12:37:20 +03:00
}
2013-02-04 13:29:59 +03:00
// Read castle events
2023-04-05 13:08:41 +02:00
uint32_t eventsCount = reader - > readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
for ( int eventID = 0 ; eventID < eventsCount ; + + eventID )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
CCastleEvent event ;
event . name = readBasicString ( ) ;
2023-09-27 23:28:17 +02:00
event . message . appendTextID ( readLocalizedString ( TextIdentifier ( " town " , position . x , position . y , position . z , " event " , eventID , " description " ) ) ) ;
2013-02-05 00:58:42 +03:00
2023-04-05 13:08:41 +02:00
reader - > readResourses ( event . resources ) ;
2013-02-05 00:58:42 +03:00
2023-04-05 13:08:41 +02:00
event . players = reader - > readUInt8 ( ) ;
2023-04-02 18:56:10 +02:00
if ( features . levelSOD )
2023-04-05 13:08:41 +02:00
event . humanAffected = reader - > readBool ( ) ;
2013-02-04 13:29:59 +03:00
else
2023-04-05 13:08:41 +02:00
event . humanAffected = true ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
event . computerAffected = reader - > readUInt8 ( ) ;
event . firstOccurence = reader - > readUInt16 ( ) ;
event . nextOccurence = reader - > readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
reader - > skipZero ( 17 ) ;
2013-02-04 13:29:59 +03:00
// New buildings
2023-05-23 15:13:09 +02:00
reader - > readBitmaskBuildings ( event . buildings , faction ) ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
event . creatures . resize ( 7 ) ;
for ( int i = 0 ; i < 7 ; + + i )
event . creatures [ i ] = reader - > readUInt16 ( ) ;
2023-04-02 18:56:10 +02:00
reader - > skipZero ( 4 ) ;
2023-04-05 13:08:41 +02:00
object - > events . push_back ( event ) ;
2013-02-04 13:29:59 +03:00
}
2023-04-11 23:36:01 +02:00
if ( features . levelSOD )
2013-02-04 13:29:59 +03:00
{
2023-04-11 23:36:01 +02:00
object - > alignmentToPlayer = PlayerColor : : NEUTRAL ; // "same as owner or random"
uint8_t alignment = reader - > readUInt8 ( ) ;
2023-09-20 21:00:03 +02:00
if ( alignment ! = 255 )
2023-04-11 23:36:01 +02:00
{
if ( alignment < PlayerColor : : PLAYER_LIMIT . getNum ( ) )
{
if ( mapHeader - > players [ alignment ] . canAnyonePlay ( ) )
object - > alignmentToPlayer = PlayerColor ( alignment ) ;
else
logGlobal - > warn ( " %s - Aligment of town at %s is invalid! Player %d is not present on map! " , mapName , position . toString ( ) , int ( alignment ) ) ;
}
else
{
// TODO: HOTA support
uint8_t invertedAlignment = alignment - PlayerColor : : PLAYER_LIMIT . getNum ( ) ;
if ( invertedAlignment < PlayerColor : : PLAYER_LIMIT . getNum ( ) )
{
logGlobal - > warn ( " %s - Aligment of town at %s 'not as player %d' is not implemented! " , mapName , position . toString ( ) , alignment - PlayerColor : : PLAYER_LIMIT . getNum ( ) ) ;
}
else
{
logGlobal - > warn ( " %s - Aligment of town at %s is corrupted!! " , mapName , position . toString ( ) ) ;
}
}
}
2013-02-04 13:29:59 +03:00
}
2023-04-02 18:56:10 +02:00
reader - > skipZero ( 3 ) ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
return object ;
2013-02-04 13:29:59 +03:00
}
void CMapLoaderH3M : : readEvents ( )
{
2023-04-05 13:08:41 +02:00
uint32_t eventsCount = reader - > readUInt32 ( ) ;
for ( int eventID = 0 ; eventID < eventsCount ; + + eventID )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
CMapEvent event ;
event . name = readBasicString ( ) ;
2023-09-27 23:28:17 +02:00
event . message . appendTextID ( readLocalizedString ( TextIdentifier ( " event " , eventID , " description " ) ) ) ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
reader - > readResourses ( event . resources ) ;
event . players = reader - > readUInt8 ( ) ;
2023-04-02 18:56:10 +02:00
if ( features . levelSOD )
2013-02-04 13:29:59 +03:00
{
2023-04-05 13:08:41 +02:00
event . humanAffected = reader - > readBool ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2023-04-05 13:08:41 +02:00
event . humanAffected = true ;
2013-02-04 13:29:59 +03:00
}
2023-04-05 13:08:41 +02:00
event . computerAffected = reader - > readBool ( ) ;
event . firstOccurence = reader - > readUInt16 ( ) ;
event . nextOccurence = reader - > readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2023-04-02 18:56:10 +02:00
reader - > skipZero ( 17 ) ;
2013-02-04 13:29:59 +03:00
2023-04-05 13:08:41 +02:00
map - > events . push_back ( event ) ;
2013-02-04 13:29:59 +03:00
}
}
2023-09-27 23:11:11 +02:00
void CMapLoaderH3M : : readMessageAndGuards ( MetaString & message , CCreatureSet * guards , const int3 & position )
2013-02-05 00:58:42 +03:00
{
2023-02-24 16:15:45 +02:00
bool hasMessage = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasMessage )
2013-02-05 00:58:42 +03:00
{
2023-09-27 23:11:11 +02:00
message . appendTextID ( readLocalizedString ( TextIdentifier ( " guards " , position . x , position . y , position . z , " message " ) ) ) ;
2023-02-24 16:15:45 +02:00
bool hasGuards = reader - > readBool ( ) ;
2013-02-07 20:02:15 +03:00
if ( hasGuards )
readCreatureSet ( guards , 7 ) ;
2013-02-05 00:58:42 +03:00
2023-04-02 18:56:10 +02:00
reader - > skipZero ( 4 ) ;
2013-02-04 13:29:59 +03:00
}
}
2016-11-13 12:38:42 +02:00
2023-02-25 17:44:15 +02:00
std : : string CMapLoaderH3M : : readBasicString ( )
2023-02-24 16:15:45 +02:00
{
2023-02-25 01:18:15 +02:00
return TextOperations : : toUnicode ( reader - > readBaseString ( ) , fileEncoding ) ;
2023-02-24 16:15:45 +02:00
}
2023-02-25 17:44:15 +02:00
std : : string CMapLoaderH3M : : readLocalizedString ( const TextIdentifier & stringIdentifier )
{
std : : string mapString = TextOperations : : toUnicode ( reader - > readBaseString ( ) , fileEncoding ) ;
2023-03-14 17:54:37 +02:00
TextIdentifier fullIdentifier ( " map " , mapName , stringIdentifier . get ( ) ) ;
2023-02-25 17:44:15 +02:00
2023-04-05 13:08:41 +02:00
if ( mapString . empty ( ) )
2023-02-25 17:44:15 +02:00
return " " ;
2023-09-29 00:24:45 +02:00
return mapRegisterLocalizedString ( modName , * mapHeader , fullIdentifier , mapString ) ;
2023-02-25 17:44:15 +02:00
}
2016-11-13 12:38:42 +02:00
void CMapLoaderH3M : : afterRead ( )
{
2023-02-24 16:15:45 +02:00
//convert main town positions for all players to actual object position, in H3M it is position of active tile
2016-11-13 12:38:42 +02:00
2023-02-24 16:15:45 +02:00
for ( auto & p : map - > players )
2016-11-13 12:38:42 +02:00
{
int3 posOfMainTown = p . posOfMainTown ;
if ( posOfMainTown . valid ( ) & & map - > isInTheMap ( posOfMainTown ) )
{
const TerrainTile & t = map - > getTile ( posOfMainTown ) ;
const CGObjectInstance * mainTown = nullptr ;
2023-02-11 18:30:06 +02:00
for ( auto * obj : t . visitableObjects )
2016-11-13 12:38:42 +02:00
{
2017-05-29 11:39:08 +02:00
if ( obj - > ID = = Obj : : TOWN | | obj - > ID = = Obj : : RANDOM_TOWN )
2016-11-13 12:38:42 +02:00
{
mainTown = obj ;
break ;
}
}
if ( mainTown = = nullptr )
continue ;
p . posOfMainTown = posOfMainTown + mainTown - > getVisitableOffset ( ) ;
}
}
}
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_END