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"
# include <boost/crc.hpp>
2014-01-30 21:56:31 +03:00
# include "MapFormatH3M.h"
2013-02-04 13:29:59 +03:00
# include "CMap.h"
2014-01-30 21:56:31 +03:00
# include "../CStopWatch.h"
# include "../filesystem/Filesystem.h"
2015-02-02 10:25:26 +02:00
# include "../spells/CSpellHandler.h"
2018-03-31 07:56:40 +02:00
# include "../CSkillHandler.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"
2014-06-05 14:19:47 +03:00
# include "../mapObjects/CObjectClassesHandler.h"
2014-06-05 19:52:14 +03:00
# include "../mapObjects/MapObjects.h"
2013-02-04 13:29:59 +03:00
# include "../VCMI_Lib.h"
2023-01-09 01:17:37 +02:00
# include "../TerrainHandler.h"
2014-01-30 21:56:31 +03:00
# include "../NetPacksBase.h"
2013-02-04 13:29:59 +03:00
2022-07-26 15:07:42 +02:00
VCMI_LIB_NAMESPACE_BEGIN
2013-02-04 13:29:59 +03:00
const bool CMapLoaderH3M : : IS_PROFILING_ENABLED = false ;
2013-02-07 20:02:15 +03:00
CMapLoaderH3M : : CMapLoaderH3M ( CInputStream * stream ) : map ( nullptr ) , reader ( stream ) , inputStream ( stream )
2013-02-04 13:29:59 +03:00
{
}
CMapLoaderH3M : : ~ CMapLoaderH3M ( )
{
}
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 ( )
{
2013-02-07 20:02:15 +03:00
//FIXME: get rid of double input process
si64 temp_size = inputStream - > getSize ( ) ;
inputStream - > seek ( 0 ) ;
2013-06-29 16:05:48 +03:00
auto temp_buffer = new ui8 [ temp_size ] ;
2013-02-07 20:02:15 +03:00
inputStream - > read ( temp_buffer , temp_size ) ;
2013-02-04 13:29:59 +03:00
// Compute checksum
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 ( ) ;
2013-02-17 21:25:42 +03:00
delete [ ] temp_buffer ;
2013-02-07 20:02:15 +03:00
inputStream - > seek ( 0 ) ;
2013-02-04 13:29:59 +03:00
CStopWatch sw ;
struct MapLoadingTime
{
std : : string name ;
si64 time ;
MapLoadingTime ( std : : string name , si64 time ) : name ( name ) ,
time ( time )
{
}
} ;
std : : vector < MapLoadingTime > times ;
readHeader ( ) ;
times . push_back ( MapLoadingTime ( " header " , sw . getDiff ( ) ) ) ;
2013-05-19 01:30:48 +03:00
map - > allHeroes . resize ( map - > allowedHeroes . size ( ) ) ;
2013-02-04 13:29:59 +03:00
readDisposedHeroes ( ) ;
times . push_back ( MapLoadingTime ( " disposed heroes " , sw . getDiff ( ) ) ) ;
readAllowedArtifacts ( ) ;
times . push_back ( MapLoadingTime ( " allowed artifacts " , sw . getDiff ( ) ) ) ;
readAllowedSpellsAbilities ( ) ;
times . push_back ( MapLoadingTime ( " allowed spells and abilities " , sw . getDiff ( ) ) ) ;
readRumors ( ) ;
times . push_back ( MapLoadingTime ( " rumors " , sw . getDiff ( ) ) ) ;
readPredefinedHeroes ( ) ;
times . push_back ( MapLoadingTime ( " predefined heroes " , sw . getDiff ( ) ) ) ;
readTerrain ( ) ;
times . push_back ( MapLoadingTime ( " terrain " , sw . getDiff ( ) ) ) ;
readDefInfo ( ) ;
times . push_back ( MapLoadingTime ( " def info " , sw . getDiff ( ) ) ) ;
readObjects ( ) ;
times . push_back ( MapLoadingTime ( " objects " , sw . getDiff ( ) ) ) ;
readEvents ( ) ;
times . push_back ( MapLoadingTime ( " events " , sw . getDiff ( ) ) ) ;
times . push_back ( MapLoadingTime ( " blocked/visitable tiles " , sw . getDiff ( ) ) ) ;
// Print profiling times
if ( IS_PROFILING_ENABLED )
{
2013-06-29 16:05:48 +03:00
for ( MapLoadingTime & mlt : times )
2013-02-04 13:29:59 +03:00
{
2017-08-11 19:03:05 +02:00
logGlobal - > debug ( " \t Reading %s took %d ms " , mlt . name , mlt . time ) ;
2013-02-04 13:29:59 +03:00
}
}
2014-04-01 14:53:28 +03:00
map - > calculateGuardingGreaturePositions ( ) ;
2016-11-13 12:38:42 +02:00
afterRead ( ) ;
2013-02-04 13:29:59 +03:00
}
void CMapLoaderH3M : : readHeader ( )
{
// Check map for validity
2013-12-06 12:13:55 +03:00
// Note: disabled, causes decompression of the entire file ( = SLOW)
//if(inputStream->getSize() < 50)
//{
// throw std::runtime_error("Corrupted map file.");
//}
2013-02-04 13:29:59 +03:00
// Map version
2013-02-07 20:02:15 +03:00
mapHeader - > version = ( EMapFormat : : EMapFormat ) ( reader . readUInt32 ( ) ) ;
2013-02-04 13:29:59 +03:00
if ( mapHeader - > version ! = EMapFormat : : ROE & & mapHeader - > version ! = EMapFormat : : AB & & mapHeader - > version ! = EMapFormat : : SOD
& & mapHeader - > version ! = EMapFormat : : WOG )
{
throw std : : runtime_error ( " Invalid map format! " ) ;
}
// Read map name, description, dimensions,...
2013-02-07 20:02:15 +03:00
mapHeader - > areAnyPlayers = reader . readBool ( ) ;
mapHeader - > height = mapHeader - > width = reader . readUInt32 ( ) ;
mapHeader - > twoLevel = reader . readBool ( ) ;
mapHeader - > name = reader . readString ( ) ;
mapHeader - > description = reader . readString ( ) ;
mapHeader - > difficulty = reader . readInt8 ( ) ;
2013-02-04 13:29:59 +03:00
if ( mapHeader - > version ! = EMapFormat : : ROE )
{
2013-02-07 20:02:15 +03: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 )
{
2013-02-07 20:02:15 +03:00
mapHeader - > players [ i ] . canHumanPlay = reader . readBool ( ) ;
mapHeader - > players [ i ] . canComputerPlay = reader . readBool ( ) ;
2013-02-04 13:29:59 +03:00
// If nobody can play with this player
if ( ( ! ( mapHeader - > players [ i ] . canHumanPlay | | mapHeader - > players [ i ] . canComputerPlay ) ) )
{
switch ( mapHeader - > version )
{
case EMapFormat : : SOD :
case EMapFormat : : WOG :
2013-02-07 20:02:15 +03:00
reader . skip ( 13 ) ;
2013-02-04 13:29:59 +03:00
break ;
case EMapFormat : : AB :
2013-02-07 20:02:15 +03:00
reader . skip ( 12 ) ;
2013-02-04 13:29:59 +03:00
break ;
case EMapFormat : : ROE :
2013-02-07 20:02:15 +03:00
reader . skip ( 6 ) ;
2013-02-04 13:29:59 +03:00
break ;
}
continue ;
}
2013-02-07 20:02:15 +03:00
mapHeader - > players [ i ] . aiTactic = static_cast < EAiTactic : : EAiTactic > ( reader . readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
if ( mapHeader - > version = = EMapFormat : : SOD | | mapHeader - > version = = EMapFormat : : WOG )
{
2013-02-07 20:02:15 +03:00
mapHeader - > players [ i ] . p7 = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
mapHeader - > players [ i ] . p7 = - 1 ;
}
// Factions this player can choose
2013-02-07 20:02:15 +03:00
ui16 allowedFactions = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
// How many factions will be read from map
ui16 totalFactions = GameConstants : : F_NUMBER ;
if ( mapHeader - > version ! = EMapFormat : : ROE )
2021-12-20 01:34:35 +02:00
allowedFactions + = reader . readUInt8 ( ) * 256 ; // 256 = 2^8 = 0b100000000
2013-02-04 13:29:59 +03:00
else
totalFactions - - ; //exclude conflux for ROE
2021-11-08 18:49:04 +02:00
const bool isFactionRandom = mapHeader - > players [ i ] . isFactionRandom = reader . readBool ( ) ;
const ui16 allFactionsMask = ( mapHeader - > version = = EMapFormat : : ROE )
2021-12-20 01:34:35 +02:00
? 0b11111111 // 8 towns for ROE
: 0b111111111 ; // 8 towns + Conflux
2021-11-08 18:49:04 +02:00
const bool allFactionsAllowed = mapHeader - > version = = EMapFormat : : VCMI
| | ( isFactionRandom & & ( ( allowedFactions & allFactionsMask ) = = allFactionsMask ) ) ;
if ( ! allFactionsAllowed )
2013-02-04 13:29:59 +03:00
{
2021-11-08 18:49:04 +02:00
mapHeader - > players [ i ] . allowedFactions . clear ( ) ;
for ( int fact = 0 ; fact < totalFactions ; + + fact )
2013-02-04 13:29:59 +03:00
{
2021-11-08 18:49:04 +02:00
if ( allowedFactions & ( 1 < < fact ) )
mapHeader - > players [ i ] . allowedFactions . insert ( fact ) ;
2013-02-04 13:29:59 +03:00
}
}
2013-02-07 20:02:15 +03:00
mapHeader - > players [ i ] . hasMainTown = reader . readBool ( ) ;
2013-02-04 13:29:59 +03:00
if ( mapHeader - > players [ i ] . hasMainTown )
{
if ( mapHeader - > version ! = EMapFormat : : ROE )
{
2013-02-07 20:02:15 +03:00
mapHeader - > players [ i ] . generateHeroAtMainTown = reader . readBool ( ) ;
mapHeader - > players [ i ] . generateHero = reader . readBool ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
mapHeader - > players [ i ] . generateHeroAtMainTown = true ;
mapHeader - > players [ i ] . generateHero = false ;
}
mapHeader - > players [ i ] . posOfMainTown = readInt3 ( ) ;
}
2013-12-23 18:59:37 +03:00
mapHeader - > players [ i ] . hasRandomHero = reader . readBool ( ) ;
mapHeader - > players [ i ] . mainCustomHeroId = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2013-12-23 18:59:37 +03:00
if ( mapHeader - > players [ i ] . mainCustomHeroId ! = 0xff )
2013-02-04 13:29:59 +03:00
{
2013-12-23 18:59:37 +03:00
mapHeader - > players [ i ] . mainCustomHeroPortrait = reader . readUInt8 ( ) ;
if ( mapHeader - > players [ i ] . mainCustomHeroPortrait = = 0xff )
mapHeader - > players [ i ] . mainCustomHeroPortrait = - 1 ; //correct 1-byte -1 (0xff) into 4-byte -1
2013-02-04 13:29:59 +03:00
2013-12-23 18:59:37 +03:00
mapHeader - > players [ i ] . mainCustomHeroName = reader . readString ( ) ;
2013-02-04 13:29:59 +03:00
}
else
2013-12-23 18:59:37 +03:00
mapHeader - > players [ i ] . mainCustomHeroId = - 1 ; //correct 1-byte -1 (0xff) into 4-byte -1
2013-02-04 13:29:59 +03:00
if ( mapHeader - > version ! = EMapFormat : : ROE )
{
2013-02-07 20:02:15 +03:00
mapHeader - > players [ i ] . powerPlaceholders = reader . readUInt8 ( ) ; //unknown byte
int heroCount = reader . readUInt8 ( ) ;
reader . skip ( 3 ) ;
2013-02-04 13:29:59 +03:00
for ( int pp = 0 ; pp < heroCount ; + + pp )
{
SHeroName vv ;
2013-02-07 20:02:15 +03:00
vv . heroId = reader . readUInt8 ( ) ;
vv . heroName = reader . readString ( ) ;
2013-02-04 13:29:59 +03:00
mapHeader - > players [ i ] . heroesNames . push_back ( vv ) ;
}
}
}
}
2013-12-29 14:27:38 +03:00
namespace EVictoryConditionType
{
enum EVictoryConditionType { ARTIFACT , GATHERTROOP , GATHERRESOURCE , BUILDCITY , BUILDGRAIL , BEATHERO ,
CAPTURECITY , BEATMONSTER , TAKEDWELLINGS , TAKEMINES , TRANSPORTITEM , WINSTANDARD = 255 } ;
}
namespace ELossConditionType
{
enum ELossConditionType { LOSSCASTLE , LOSSHERO , TIMEEXPIRES , LOSSSTANDARD = 255 } ;
}
2013-02-04 13:29:59 +03:00
void CMapLoaderH3M : : readVictoryLossConditions ( )
{
2013-12-29 14:27:38 +03:00
mapHeader - > triggeredEvents . clear ( ) ;
auto vicCondition = ( EVictoryConditionType : : EVictoryConditionType ) reader . readUInt8 ( ) ;
EventCondition victoryCondition ( EventCondition : : STANDARD_WIN ) ;
EventCondition defeatCondition ( EventCondition : : DAYS_WITHOUT_TOWN ) ;
defeatCondition . value = 7 ;
TriggeredEvent standardVictory ;
standardVictory . effect . type = EventEffect : : VICTORY ;
standardVictory . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 5 ] ;
standardVictory . identifier = " standardVictory " ;
2022-11-15 02:20:55 +02:00
standardVictory . description . clear ( ) ; // TODO: display in quest window
2013-12-29 14:27:38 +03:00
standardVictory . onFulfill = VLC - > generaltexth - > allTexts [ 659 ] ;
standardVictory . trigger = EventExpression ( victoryCondition ) ;
TriggeredEvent standardDefeat ;
standardDefeat . effect . type = EventEffect : : DEFEAT ;
standardDefeat . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 8 ] ;
standardDefeat . identifier = " standardDefeat " ;
2022-11-15 02:20:55 +02:00
standardDefeat . description . clear ( ) ; // TODO: display in quest window
2013-12-29 14:27:38 +03:00
standardDefeat . onFulfill = VLC - > generaltexth - > allTexts [ 7 ] ;
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 ;
mapHeader - > victoryMessage = VLC - > generaltexth - > victoryConditions [ 0 ] ;
}
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
mapHeader - > victoryIconIndex = ui16 ( vicCondition ) ;
mapHeader - > victoryMessage = VLC - > generaltexth - > victoryConditions [ size_t ( vicCondition ) + 1 ] ;
bool allowNormalVictory = reader . readBool ( ) ;
bool appliesToAI = reader . readBool ( ) ;
2013-02-04 13:29:59 +03:00
2014-02-09 15:10:02 +03:00
if ( allowNormalVictory )
{
size_t playersOnMap = boost : : range : : count_if ( mapHeader - > players , [ ] ( const PlayerInfo & info ) { return info . canAnyonePlay ( ) ; } ) ;
if ( playersOnMap = = 1 )
{
2017-08-11 19:03:05 +02:00
logGlobal - > warn ( " Map %s has only one player but allows normal victory? " , mapHeader - > name ) ;
2014-02-09 15:10:02 +03:00
allowNormalVictory = false ; // makes sense? Not much. Works as H3? Yes!
}
}
2013-12-29 14:27:38 +03:00
switch ( vicCondition )
2013-02-04 13:29:59 +03:00
{
case EVictoryConditionType : : ARTIFACT :
{
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : HAVE_ARTIFACT ) ;
cond . objectType = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
if ( mapHeader - > version ! = EMapFormat : : ROE )
2013-02-07 20:02:15 +03:00
reader . skip ( 1 ) ;
2013-12-29 14:27:38 +03:00
specialVictory . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 281 ] ;
specialVictory . onFulfill = VLC - > generaltexth - > allTexts [ 280 ] ;
specialVictory . trigger = EventExpression ( cond ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case EVictoryConditionType : : GATHERTROOP :
{
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : HAVE_CREATURES ) ;
cond . objectType = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
if ( mapHeader - > version ! = EMapFormat : : ROE )
2013-02-07 20:02:15 +03:00
reader . skip ( 1 ) ;
2013-12-29 14:27:38 +03:00
cond . value = reader . readUInt32 ( ) ;
specialVictory . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 277 ] ;
specialVictory . onFulfill = VLC - > generaltexth - > allTexts [ 276 ] ;
specialVictory . trigger = EventExpression ( cond ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case EVictoryConditionType : : GATHERRESOURCE :
{
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : HAVE_RESOURCES ) ;
cond . objectType = reader . readUInt8 ( ) ;
cond . value = reader . readUInt32 ( ) ;
specialVictory . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 279 ] ;
specialVictory . onFulfill = VLC - > generaltexth - > allTexts [ 278 ] ;
specialVictory . trigger = EventExpression ( cond ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case EVictoryConditionType : : BUILDCITY :
{
2013-12-29 14:27:38 +03:00
EventExpression : : OperatorAll oper ;
EventCondition cond ( EventCondition : : HAVE_BUILDING ) ;
cond . position = readInt3 ( ) ;
2022-10-23 15:38:12 +02:00
cond . objectType = BuildingID : : TOWN_HALL + reader . readUInt8 ( ) ;
2013-12-29 14:27:38 +03:00
oper . expressions . push_back ( cond ) ;
cond . objectType = BuildingID : : FORT + reader . readUInt8 ( ) ;
oper . expressions . push_back ( cond ) ;
specialVictory . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 283 ] ;
specialVictory . onFulfill = VLC - > generaltexth - > allTexts [ 282 ] ;
specialVictory . trigger = EventExpression ( oper ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case EVictoryConditionType : : BUILDGRAIL :
{
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : HAVE_BUILDING ) ;
cond . objectType = BuildingID : : GRAIL ;
cond . position = readInt3 ( ) ;
if ( cond . position . z > 2 )
cond . position = int3 ( - 1 , - 1 , - 1 ) ;
specialVictory . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 285 ] ;
specialVictory . onFulfill = VLC - > generaltexth - > allTexts [ 284 ] ;
specialVictory . trigger = EventExpression ( cond ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case EVictoryConditionType : : BEATHERO :
2013-12-29 14:27:38 +03:00
{
EventCondition cond ( EventCondition : : DESTROY ) ;
cond . objectType = Obj : : HERO ;
cond . position = readInt3 ( ) ;
specialVictory . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 253 ] ;
specialVictory . onFulfill = VLC - > generaltexth - > allTexts [ 252 ] ;
specialVictory . trigger = EventExpression ( cond ) ;
break ;
}
2013-02-04 13:29:59 +03:00
case EVictoryConditionType : : CAPTURECITY :
2013-12-29 14:27:38 +03:00
{
EventCondition cond ( EventCondition : : CONTROL ) ;
cond . objectType = Obj : : TOWN ;
cond . position = readInt3 ( ) ;
specialVictory . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 250 ] ;
specialVictory . onFulfill = VLC - > generaltexth - > allTexts [ 249 ] ;
specialVictory . trigger = EventExpression ( cond ) ;
break ;
}
2013-02-04 13:29:59 +03:00
case EVictoryConditionType : : BEATMONSTER :
{
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : DESTROY ) ;
cond . objectType = Obj : : MONSTER ;
cond . position = readInt3 ( ) ;
specialVictory . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 287 ] ;
specialVictory . onFulfill = VLC - > generaltexth - > allTexts [ 286 ] ;
specialVictory . trigger = EventExpression ( cond ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case EVictoryConditionType : : TAKEDWELLINGS :
2013-12-29 14:27:38 +03:00
{
2014-02-09 22:47:23 +03:00
EventExpression : : OperatorAll oper ;
oper . expressions . push_back ( EventCondition ( EventCondition : : CONTROL , 0 , Obj : : CREATURE_GENERATOR1 ) ) ;
oper . expressions . push_back ( EventCondition ( EventCondition : : CONTROL , 0 , Obj : : CREATURE_GENERATOR4 ) ) ;
2013-12-29 14:27:38 +03:00
specialVictory . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 289 ] ;
specialVictory . onFulfill = VLC - > generaltexth - > allTexts [ 288 ] ;
2014-02-09 22:47:23 +03:00
specialVictory . trigger = EventExpression ( oper ) ;
2013-12-29 14:27:38 +03:00
break ;
}
2013-02-04 13:29:59 +03:00
case EVictoryConditionType : : TAKEMINES :
{
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : CONTROL ) ;
cond . objectType = Obj : : MINE ;
specialVictory . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 291 ] ;
specialVictory . onFulfill = VLC - > generaltexth - > allTexts [ 290 ] ;
specialVictory . trigger = EventExpression ( cond ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case EVictoryConditionType : : TRANSPORTITEM :
{
2013-12-29 14:27:38 +03:00
EventCondition cond ( EventCondition : : TRANSPORT ) ;
cond . objectType = reader . readUInt8 ( ) ;
cond . position = readInt3 ( ) ;
specialVictory . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 293 ] ;
specialVictory . onFulfill = VLC - > generaltexth - > allTexts [ 292 ] ;
specialVictory . trigger = EventExpression ( cond ) ;
2013-02-04 13:29:59 +03:00
break ;
}
default :
assert ( 0 ) ;
}
2014-01-30 21:56:31 +03:00
2013-12-29 14:27:38 +03:00
// if condition is human-only turn it into following construction: AllOf(human, condition)
if ( ! appliesToAI )
{
EventExpression : : OperatorAll oper ;
EventCondition notAI ( EventCondition : : IS_HUMAN ) ;
notAI . value = 1 ;
oper . expressions . push_back ( notAI ) ;
oper . expressions . push_back ( specialVictory . trigger . get ( ) ) ;
specialVictory . trigger = EventExpression ( oper ) ;
}
// if normal victory allowed - add one more quest
if ( allowNormalVictory )
{
mapHeader - > victoryMessage + = " / " ;
mapHeader - > victoryMessage + = VLC - > generaltexth - > victoryConditions [ 0 ] ;
mapHeader - > triggeredEvents . push_back ( standardVictory ) ;
}
mapHeader - > triggeredEvents . push_back ( specialVictory ) ;
2013-02-04 13:29:59 +03:00
}
// Read loss conditions
2013-12-29 14:27:38 +03:00
auto lossCond = ( ELossConditionType : : ELossConditionType ) reader . readUInt8 ( ) ;
if ( lossCond = = ELossConditionType : : LOSSSTANDARD )
2013-02-04 13:29:59 +03:00
{
2013-12-29 14:27:38 +03:00
mapHeader - > defeatIconIndex = 3 ;
mapHeader - > defeatMessage = VLC - > generaltexth - > lossCondtions [ 0 ] ;
}
else
{
TriggeredEvent specialDefeat ;
specialDefeat . effect . type = EventEffect : : DEFEAT ;
specialDefeat . effect . toOtherMessage = VLC - > generaltexth - > allTexts [ 5 ] ;
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
mapHeader - > defeatIconIndex = ui16 ( lossCond ) ;
mapHeader - > defeatMessage = VLC - > generaltexth - > lossCondtions [ size_t ( lossCond ) + 1 ] ;
switch ( lossCond )
2013-02-04 13:29:59 +03:00
{
2013-12-29 14:27:38 +03:00
case ELossConditionType : : LOSSCASTLE :
{
EventExpression : : OperatorNone noneOf ;
EventCondition cond ( EventCondition : : CONTROL ) ;
cond . objectType = Obj : : TOWN ;
cond . position = readInt3 ( ) ;
noneOf . expressions . push_back ( cond ) ;
specialDefeat . onFulfill = VLC - > generaltexth - > allTexts [ 251 ] ;
specialDefeat . trigger = EventExpression ( noneOf ) ;
break ;
}
case ELossConditionType : : LOSSHERO :
{
EventExpression : : OperatorNone noneOf ;
EventCondition cond ( EventCondition : : CONTROL ) ;
cond . objectType = Obj : : HERO ;
cond . position = readInt3 ( ) ;
noneOf . expressions . push_back ( cond ) ;
specialDefeat . onFulfill = VLC - > generaltexth - > allTexts [ 253 ] ;
specialDefeat . trigger = EventExpression ( noneOf ) ;
break ;
}
case ELossConditionType : : TIMEEXPIRES :
{
EventCondition cond ( EventCondition : : DAYS_PASSED ) ;
cond . value = reader . readUInt16 ( ) ;
specialDefeat . onFulfill = VLC - > generaltexth - > allTexts [ 254 ] ;
specialDefeat . trigger = EventExpression ( cond ) ;
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 ;
allOf . expressions . push_back ( isHuman ) ;
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 ( )
{
2013-02-07 20:02:15 +03: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 )
2013-02-04 13:29:59 +03:00
{
2013-03-03 20:06:03 +03: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 ( )
{
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
mapHeader - > allowedHeroes . resize ( VLC - > heroh - > size ( ) , true ) ;
2013-02-04 13:29:59 +03:00
2013-02-05 00:58:42 +03:00
const int bytes = mapHeader - > version = = EMapFormat : : ROE ? 16 : 20 ;
readBitmask ( mapHeader - > allowedHeroes , bytes , GameConstants : : HEROES_QUANTITY , false ) ;
2013-02-04 13:29:59 +03:00
// Probably reserved for further heroes
if ( mapHeader - > version > EMapFormat : : ROE )
{
2013-02-07 20:02:15 +03:00
int placeholdersQty = reader . readUInt32 ( ) ;
2016-02-21 21:12:58 +02:00
reader . skip ( placeholdersQty * 1 ) ;
// std::vector<ui16> placeholdedHeroes;
//
// for(int p = 0; p < placeholdersQty; ++p)
// {
// placeholdedHeroes.push_back(reader.readUInt8());
// }
2013-02-04 13:29:59 +03:00
}
}
void CMapLoaderH3M : : readDisposedHeroes ( )
{
// Reading disposed heroes (20 bytes)
if ( map - > version > = EMapFormat : : SOD )
{
2013-02-07 20:02:15 +03:00
ui8 disp = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
map - > disposedHeroes . resize ( disp ) ;
for ( int g = 0 ; g < disp ; + + g )
{
2013-02-07 20:02:15 +03:00
map - > disposedHeroes [ g ] . heroId = reader . readUInt8 ( ) ;
map - > disposedHeroes [ g ] . portrait = reader . readUInt8 ( ) ;
map - > disposedHeroes [ g ] . name = reader . readString ( ) ;
map - > disposedHeroes [ g ] . players = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
}
//omitting NULLS
2013-02-07 20:02:15 +03:00
reader . skip ( 31 ) ;
2013-02-04 13:29:59 +03:00
}
void CMapLoaderH3M : : readAllowedArtifacts ( )
{
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
map - > allowedArtifact . resize ( VLC - > arth - > objects . size ( ) , true ) ; //handle new artifacts, make them allowed by default
2013-02-04 13:29:59 +03:00
// Reading allowed artifacts: 17 or 18 bytes
if ( map - > version ! = EMapFormat : : ROE )
{
2013-02-05 00:58:42 +03:00
const int bytes = map - > version = = EMapFormat : : AB ? 17 : 18 ;
readBitmask ( map - > allowedArtifact , bytes , GameConstants : : ARTIFACTS_QUANTITY ) ;
2013-02-04 13:29:59 +03:00
}
// ban combo artifacts
if ( map - > version = = EMapFormat : : ROE | | map - > version = = EMapFormat : : AB )
{
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 )
2013-02-04 13:29:59 +03:00
{
// combo
if ( artifact - > constituents )
{
map - > allowedArtifact [ artifact - > id ] = false ;
}
}
if ( map - > version = = EMapFormat : : ROE )
{
2015-11-07 10:46:58 +02:00
map - > allowedArtifact [ ArtifactID : : ARMAGEDDONS_BLADE ] = false ;
2013-02-04 13:29:59 +03:00
}
}
// Messy, but needed
2013-12-29 14:27:38 +03: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
{
if ( cond . condition = = EventCondition : : HAVE_ARTIFACT | |
cond . condition = = EventCondition : : TRANSPORT )
{
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 ( )
{
2014-03-09 15:33:26 +03:00
// Read allowed spells, including new ones
map - > allowedSpell . resize ( VLC - > spellh - > objects . size ( ) , true ) ;
2013-02-04 13:29:59 +03:00
// Read allowed abilities
2013-02-05 00:58:42 +03:00
map - > allowedAbilities . resize ( GameConstants : : SKILL_QUANTITY , true ) ;
2013-02-04 13:29:59 +03:00
if ( map - > version > = EMapFormat : : SOD )
{
// Reading allowed spells (9 bytes)
2013-02-05 00:58:42 +03:00
const int spell_bytes = 9 ;
readBitmask ( map - > allowedSpell , spell_bytes , GameConstants : : SPELLS_QUANTITY ) ;
2013-02-04 13:29:59 +03:00
// Allowed hero's abilities (4 bytes)
2013-02-05 00:58:42 +03:00
const int abil_bytes = 4 ;
readBitmask ( map - > allowedAbilities , abil_bytes , GameConstants : : SKILL_QUANTITY ) ;
2013-02-04 13:29:59 +03:00
}
2014-03-09 15:33:26 +03:00
//do not generate special abilities and spells
for ( auto spell : VLC - > spellh - > objects )
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
if ( spell - > isSpecial ( ) | | spell - > isCreatureAbility ( ) )
2014-03-09 15:33:26 +03:00
map - > allowedSpell [ spell - > id ] = false ;
2013-02-04 13:29:59 +03:00
}
void CMapLoaderH3M : : readRumors ( )
{
2013-02-07 20:02:15 +03:00
int rumNr = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
for ( int it = 0 ; it < rumNr ; it + + )
{
Rumor ourRumor ;
2013-02-07 20:02:15 +03:00
ourRumor . name = reader . readString ( ) ;
ourRumor . text = reader . readString ( ) ;
2013-02-04 13:29:59 +03:00
map - > rumors . push_back ( ourRumor ) ;
}
}
void CMapLoaderH3M : : readPredefinedHeroes ( )
{
switch ( map - > version )
{
case EMapFormat : : WOG :
case EMapFormat : : SOD :
{
// Disposed heroes
for ( int z = 0 ; z < GameConstants : : HEROES_QUANTITY ; z + + )
{
2013-02-07 20:02:15 +03:00
int custom = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
if ( ! custom ) continue ;
2013-06-29 16:05:48 +03:00
auto hero = new CGHeroInstance ( ) ;
2013-02-04 13:29:59 +03:00
hero - > ID = Obj : : HERO ;
hero - > subID = z ;
2013-02-07 20:02:15 +03:00
bool hasExp = reader . readBool ( ) ;
if ( hasExp )
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
hero - > exp = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
hero - > exp = 0 ;
}
2013-02-07 20:02:15 +03:00
bool hasSecSkills = reader . readBool ( ) ;
if ( hasSecSkills )
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
int howMany = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
hero - > secSkills . resize ( howMany ) ;
for ( int yy = 0 ; yy < howMany ; + + yy )
{
2013-02-12 22:49:40 +03:00
hero - > secSkills [ yy ] . first = SecondarySkill ( reader . readUInt8 ( ) ) ;
2013-02-07 20:02:15 +03:00
hero - > secSkills [ yy ] . second = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
}
loadArtifactsOfHero ( hero ) ;
2013-02-07 20:02:15 +03:00
bool hasCustomBio = reader . readBool ( ) ;
if ( hasCustomBio )
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
hero - > biography = reader . readString ( ) ;
2013-02-04 13:29:59 +03:00
}
// 0xFF is default, 00 male, 01 female
2013-02-07 20:02:15 +03:00
hero - > sex = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2013-02-07 20:02:15 +03:00
bool hasCustomSpells = reader . readBool ( ) ;
if ( hasCustomSpells )
2013-02-04 13:29:59 +03:00
{
2013-02-05 00:58:42 +03:00
readSpells ( hero - > spells ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
bool hasCustomPrimSkills = reader . readBool ( ) ;
if ( hasCustomPrimSkills )
2013-02-04 13:29:59 +03:00
{
for ( int xx = 0 ; xx < GameConstants : : PRIMARY_SKILLS ; xx + + )
{
2013-02-07 20:02:15 +03:00
hero - > pushPrimSkill ( static_cast < PrimarySkill : : PrimarySkill > ( xx ) , reader . readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
}
map - > predefinedHeroes . push_back ( hero ) ;
}
break ;
}
case EMapFormat : : ROE :
break ;
}
}
void CMapLoaderH3M : : loadArtifactsOfHero ( CGHeroInstance * hero )
{
2013-02-07 20:02:15 +03:00
bool artSet = reader . readBool ( ) ;
2013-02-04 13:29:59 +03:00
// True if artifact set is not default (hero has some artifacts)
if ( artSet )
{
2013-09-03 16:18:09 +03:00
if ( hero - > artifactsWorn . size ( ) | | hero - > artifactsInBackpack . size ( ) )
{
2017-08-11 19:03:05 +02:00
logGlobal - > warn ( " Hero %s at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set... " , hero - > name , hero - > pos . toString ( ) ) ;
2013-09-03 16:18:09 +03:00
hero - > artifactsInBackpack . clear ( ) ;
while ( hero - > artifactsWorn . size ( ) )
hero - > eraseArtSlot ( hero - > artifactsWorn . begin ( ) - > first ) ;
}
2013-02-04 13:29:59 +03:00
for ( int pom = 0 ; pom < 16 ; pom + + )
{
loadArtifactToSlot ( hero , pom ) ;
}
// misc5 art //17
if ( map - > version > = EMapFormat : : SOD )
{
2013-09-03 16:18:09 +03:00
assert ( ! hero - > getArt ( ArtifactPosition : : MACH4 ) ) ;
2013-02-04 13:29:59 +03:00
if ( ! loadArtifactToSlot ( hero , ArtifactPosition : : MACH4 ) )
{
// catapult by default
2013-09-03 16:18:09 +03:00
assert ( ! hero - > getArt ( ArtifactPosition : : MACH4 ) ) ;
2016-02-13 18:43:05 +02:00
hero - > putArtifact ( ArtifactPosition : : MACH4 , CArtifactInstance : : createArtifact ( map , ArtifactID : : CATAPULT ) ) ;
2013-02-04 13:29:59 +03:00
}
}
loadArtifactToSlot ( hero , ArtifactPosition : : SPELLBOOK ) ;
// 19 //???what is that? gap in file or what? - it's probably fifth slot..
if ( map - > version > EMapFormat : : ROE )
{
loadArtifactToSlot ( hero , ArtifactPosition : : MISC5 ) ;
}
else
{
2013-02-07 20:02:15 +03:00
reader . skip ( 1 ) ;
2013-02-04 13:29:59 +03:00
}
// bag artifacts //20
// number of artifacts in hero's bag
2013-02-07 20:02:15 +03:00
int amount = reader . readUInt16 ( ) ;
2013-02-04 13:29:59 +03:00
for ( int ss = 0 ; ss < amount ; + + ss )
{
2020-10-01 10:38:06 +02:00
loadArtifactToSlot ( hero , GameConstants : : BACKPACK_START + ( int ) hero - > artifactsInBackpack . size ( ) ) ;
2013-02-04 13:29:59 +03:00
}
}
}
bool CMapLoaderH3M : : loadArtifactToSlot ( CGHeroInstance * hero , int slot )
{
const int artmask = map - > version = = EMapFormat : : ROE ? 0xff : 0xffff ;
2022-11-29 00:11:46 +02:00
ArtifactID aid ;
2013-02-04 13:29:59 +03:00
if ( map - > version = = EMapFormat : : ROE )
{
2022-11-29 00:11:46 +02:00
aid = ArtifactID ( reader . readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2022-11-29 00:11:46 +02:00
aid = ArtifactID ( reader . readUInt16 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
bool isArt = aid ! = artmask ;
if ( isArt )
{
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
const Artifact * art = ArtifactID ( aid ) . toArtifact ( VLC - > artifacts ( ) ) ;
2017-05-26 18:51:45 +02:00
if ( nullptr = = art )
{
2017-08-10 18:39:27 +02:00
logGlobal - > warn ( " Invalid artifact in hero's backpack, ignoring... " ) ;
2017-05-26 18:51:45 +02:00
return false ;
}
if ( art - > isBig ( ) & & slot > = GameConstants : : BACKPACK_START )
2013-02-04 13:29:59 +03:00
{
2017-08-10 18:39:27 +02:00
logGlobal - > warn ( " A big artifact (war machine) in hero's backpack, ignoring... " ) ;
2013-02-04 13:29:59 +03:00
return false ;
}
if ( aid = = 0 & & slot = = ArtifactPosition : : MISC5 )
{
//TODO: check how H3 handles it -> art 0 in slot 18 in AB map
2017-08-11 19:03:05 +02:00
logGlobal - > warn ( " Spellbook to MISC5 slot? Putting it spellbook place. AB format peculiarity? (format %d) " , static_cast < int > ( map - > version ) ) ;
2013-02-04 13:29:59 +03:00
slot = ArtifactPosition : : SPELLBOOK ;
}
2014-01-30 21:56:31 +03:00
// this is needed, because some H3M maps (last scenario of ROE map) contain invalid data like misplaced artifacts
2016-02-13 18:43:05 +02:00
auto artifact = CArtifactInstance : : createArtifact ( map , aid ) ;
2014-01-30 21:56:31 +03:00
auto artifactPos = ArtifactPosition ( slot ) ;
if ( artifact - > canBePutAt ( ArtifactLocation ( hero , artifactPos ) ) )
{
hero - > putArtifact ( artifactPos , artifact ) ;
}
else
{
2017-08-10 18:39:27 +02:00
logGlobal - > debug ( " Artifact can't be put at the specified location. " ) ; //TODO add more debugging information
2014-01-30 21:56:31 +03:00
}
2013-02-04 13:29:59 +03:00
}
return isArt ;
}
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 ) ;
2022-12-20 16:14:06 +02:00
tile . terType = const_cast < TerrainType * > ( VLC - > terrainTypeHandler - > getByIndex ( reader . readUInt8 ( ) ) ) ;
2013-04-19 14:43:11 +03:00
tile . terView = reader . readUInt8 ( ) ;
2022-12-20 16:14:06 +02:00
tile . riverType = const_cast < RiverType * > ( VLC - > riverTypeHandler - > getByIndex ( reader . readUInt8 ( ) ) ) ;
2013-04-19 14:43:11 +03:00
tile . riverDir = reader . readUInt8 ( ) ;
2022-12-20 16:14:06 +02:00
tile . roadType = const_cast < RoadType * > ( VLC - > roadTypeHandler - > getByIndex ( reader . readUInt8 ( ) ) ) ;
2013-04-19 14:43:11 +03:00
tile . roadDir = reader . readUInt8 ( ) ;
tile . extTileFlags = reader . readUInt8 ( ) ;
2023-01-01 17:10:47 +02:00
tile . blocked = ( ( ! tile . terType - > isPassable ( ) | | tile . terType - > getId ( ) = = ETerrainId : : BORDER ) ? true : false ) ; //underground tiles are always blocked
2013-04-19 14:43:11 +03:00
tile . visitable = 0 ;
2013-02-04 13:29:59 +03:00
}
}
}
}
void CMapLoaderH3M : : readDefInfo ( )
{
2013-02-07 20:02:15 +03:00
int 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
for ( int idd = 0 ; idd < defAmount ; + + idd )
{
2022-09-11 15:12:35 +02:00
auto tmpl = new ObjectTemplate ;
tmpl - > readMap ( reader ) ;
templates . push_back ( std : : shared_ptr < const ObjectTemplate > ( tmpl ) ) ;
2013-02-04 13:29:59 +03:00
}
}
void CMapLoaderH3M : : readObjects ( )
{
2013-02-07 20:02:15 +03:00
int howManyObjs = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
for ( int ww = 0 ; ww < howManyObjs ; + + ww )
{
2013-06-29 16:05:48 +03:00
CGObjectInstance * nobj = nullptr ;
2013-02-04 13:29:59 +03:00
int3 objPos = readInt3 ( ) ;
2013-02-07 20:02:15 +03:00
int defnum = reader . readUInt32 ( ) ;
2020-10-01 10:38:06 +02:00
ObjectInstanceID idToBeGiven = ObjectInstanceID ( ( si32 ) map - > objects . size ( ) ) ;
2013-02-04 13:29:59 +03:00
2022-09-11 15:12:35 +02:00
std : : shared_ptr < const ObjectTemplate > objTempl = templates . at ( defnum ) ;
2013-02-07 20:02:15 +03:00
reader . skip ( 5 ) ;
2013-02-04 13:29:59 +03:00
2022-09-11 15:12:35 +02:00
switch ( objTempl - > id )
2013-02-04 13:29:59 +03:00
{
case Obj : : EVENT :
{
2013-06-29 16:05:48 +03:00
auto evnt = new CGEvent ( ) ;
2013-02-04 13:29:59 +03:00
nobj = evnt ;
2013-02-07 20:02:15 +03:00
readMessageAndGuards ( evnt - > message , evnt ) ;
2013-02-04 13:29:59 +03:00
2013-02-07 20:02:15 +03:00
evnt - > gainedExp = reader . readUInt32 ( ) ;
evnt - > manaDiff = reader . readUInt32 ( ) ;
evnt - > moraleDiff = reader . readInt8 ( ) ;
evnt - > luckDiff = reader . readInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2013-02-05 00:58:42 +03:00
readResourses ( evnt - > resources ) ;
2013-02-04 13:29:59 +03:00
evnt - > primskills . resize ( GameConstants : : PRIMARY_SKILLS ) ;
for ( int x = 0 ; x < 4 ; + + x )
{
2013-02-07 20:02:15 +03:00
evnt - > primskills [ x ] = static_cast < PrimarySkill : : PrimarySkill > ( reader . readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
int gabn = reader . readUInt8 ( ) ; // Number of gained abilities
2013-02-04 13:29:59 +03:00
for ( int oo = 0 ; oo < gabn ; + + oo )
{
2013-02-12 22:49:40 +03:00
evnt - > abilities . push_back ( SecondarySkill ( reader . readUInt8 ( ) ) ) ;
2013-02-07 20:02:15 +03:00
evnt - > abilityLevels . push_back ( reader . readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
int gart = reader . readUInt8 ( ) ; // Number of gained artifacts
2013-02-04 13:29:59 +03:00
for ( int oo = 0 ; oo < gart ; + + oo )
{
if ( map - > version = = EMapFormat : : ROE )
{
2013-02-11 02:24:57 +03:00
evnt - > artifacts . push_back ( ArtifactID ( reader . readUInt8 ( ) ) ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2013-02-11 02:24:57 +03:00
evnt - > artifacts . push_back ( ArtifactID ( reader . readUInt16 ( ) ) ) ;
2013-02-04 13:29:59 +03:00
}
}
2013-02-07 20:02:15 +03:00
int gspel = reader . readUInt8 ( ) ; // Number of gained spells
2013-02-04 13:29:59 +03:00
for ( int oo = 0 ; oo < gspel ; + + oo )
{
2013-02-11 02:24:57 +03:00
evnt - > spells . push_back ( SpellID ( reader . readUInt8 ( ) ) ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
int gcre = reader . readUInt8 ( ) ; //number of gained creatures
2013-02-05 00:58:42 +03:00
readCreatureSet ( & evnt - > creatures , gcre ) ;
2013-02-04 13:29:59 +03:00
2013-02-07 20:02:15 +03:00
reader . skip ( 8 ) ;
evnt - > availableFor = reader . readUInt8 ( ) ;
evnt - > computerActivate = reader . readUInt8 ( ) ;
evnt - > removeAfterVisit = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
evnt - > humanActivate = true ;
2013-02-07 20:02:15 +03:00
reader . skip ( 4 ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : HERO :
case Obj : : RANDOM_HERO :
case Obj : : PRISON :
{
2015-11-27 10:34:03 +02:00
nobj = readHero ( idToBeGiven , objPos ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : MONSTER : //Monster
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 :
{
2013-06-29 16:05:48 +03:00
auto cre = new CGCreature ( ) ;
2013-02-04 13:29:59 +03:00
nobj = cre ;
if ( map - > version > EMapFormat : : ROE )
{
2013-02-07 20:02:15 +03:00
cre - > identifier = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
map - > questIdentifierToId [ cre - > identifier ] = idToBeGiven ;
}
2013-06-29 16:05:48 +03:00
auto hlp = new CStackInstance ( ) ;
2013-02-07 20:02:15 +03:00
hlp - > count = reader . readUInt16 ( ) ;
2013-02-04 13:29:59 +03:00
//type will be set during initialization
2013-02-16 17:03:47 +03:00
cre - > putStack ( SlotID ( 0 ) , hlp ) ;
2013-02-04 13:29:59 +03:00
2013-02-07 20:02:15 +03:00
cre - > character = reader . readUInt8 ( ) ;
bool hasMessage = reader . readBool ( ) ;
if ( hasMessage )
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
cre - > message = reader . readString ( ) ;
2013-02-05 00:58:42 +03:00
readResourses ( cre - > resources ) ;
2013-02-04 13:29:59 +03:00
int artID ;
if ( map - > version = = EMapFormat : : ROE )
{
2013-02-07 20:02:15 +03:00
artID = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2013-02-07 20:02:15 +03:00
artID = reader . readUInt16 ( ) ;
2013-02-04 13:29:59 +03:00
}
if ( map - > version = = EMapFormat : : ROE )
{
if ( artID ! = 0xff )
{
2013-02-11 02:24:57 +03:00
cre - > gainedArtifact = ArtifactID ( artID ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2013-02-07 20:34:50 +03:00
cre - > gainedArtifact = ArtifactID : : NONE ;
2013-02-04 13:29:59 +03:00
}
}
else
{
if ( artID ! = 0xffff )
{
2013-02-11 02:24:57 +03:00
cre - > gainedArtifact = ArtifactID ( artID ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2013-02-07 20:34:50 +03:00
cre - > gainedArtifact = ArtifactID : : NONE ;
2013-02-04 13:29:59 +03:00
}
}
}
2013-02-07 20:02:15 +03:00
cre - > neverFlees = reader . readUInt8 ( ) ;
cre - > notGrowingTeam = reader . readUInt8 ( ) ;
reader . skip ( 2 ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : OCEAN_BOTTLE :
case Obj : : SIGN :
{
2013-06-29 16:05:48 +03:00
auto sb = new CGSignBottle ( ) ;
2013-02-04 13:29:59 +03:00
nobj = sb ;
2013-02-07 20:02:15 +03:00
sb - > message = reader . readString ( ) ;
reader . skip ( 4 ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : SEER_HUT :
{
nobj = readSeerHut ( ) ;
break ;
}
case Obj : : WITCH_HUT :
{
2013-06-29 16:05:48 +03:00
auto wh = new CGWitchHut ( ) ;
2013-02-04 13:29:59 +03:00
nobj = wh ;
// in RoE we cannot specify it - all are allowed (I hope)
if ( map - > version > EMapFormat : : ROE )
{
for ( int i = 0 ; i < 4 ; + + i )
{
2013-02-07 20:02:15 +03:00
ui8 c = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
for ( int yy = 0 ; yy < 8 ; + + yy )
{
if ( i * 8 + yy < GameConstants : : SKILL_QUANTITY )
{
if ( c = = ( c | static_cast < ui8 > ( std : : pow ( 2. , yy ) ) ) )
{
wh - > allowedAbilities . push_back ( i * 8 + yy ) ;
}
}
}
}
2018-03-31 07:56:40 +02:00
// enable new (modded) skills
if ( wh - > allowedAbilities . size ( ) ! = 1 )
{
for ( int skillID = GameConstants : : SKILL_QUANTITY ; skillID < VLC - > skillh - > size ( ) ; + + skillID )
wh - > allowedAbilities . push_back ( skillID ) ;
}
2013-02-04 13:29:59 +03:00
}
else
{
// RoE map
2018-03-31 07:56:40 +02:00
for ( int skillID = 0 ; skillID < VLC - > skillh - > size ( ) ; + + skillID )
wh - > allowedAbilities . push_back ( skillID ) ;
2013-02-04 13:29:59 +03:00
}
break ;
}
case Obj : : SCHOLAR :
{
2013-06-29 16:05:48 +03:00
auto sch = new CGScholar ( ) ;
2013-02-04 13:29:59 +03:00
nobj = sch ;
2013-02-07 20:02:15 +03:00
sch - > bonusType = static_cast < CGScholar : : EBonusType > ( reader . readUInt8 ( ) ) ;
sch - > bonusID = reader . readUInt8 ( ) ;
reader . skip ( 6 ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : GARRISON :
case Obj : : GARRISON2 :
{
2013-06-29 16:05:48 +03:00
auto gar = new CGGarrison ( ) ;
2013-02-04 13:29:59 +03:00
nobj = gar ;
2013-03-03 20:06:03 +03:00
nobj - > setOwner ( PlayerColor ( reader . readUInt8 ( ) ) ) ;
2013-02-07 20:02:15 +03:00
reader . skip ( 3 ) ;
2013-02-05 00:58:42 +03:00
readCreatureSet ( gar , 7 ) ;
2013-02-04 13:29:59 +03:00
if ( map - > version > EMapFormat : : ROE )
{
2013-02-07 20:02:15 +03:00
gar - > removableUnits = reader . readBool ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
gar - > removableUnits = true ;
}
2013-02-07 20:02:15 +03:00
reader . skip ( 8 ) ;
2013-02-04 13:29:59 +03:00
break ;
}
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 :
{
2022-11-29 00:11:46 +02:00
auto artID = ArtifactID : : NONE ; //random, set later
2013-02-04 13:29:59 +03:00
int spellID = - 1 ;
2013-06-29 16:05:48 +03:00
auto art = new CGArtifact ( ) ;
2013-02-04 13:29:59 +03:00
nobj = art ;
2013-02-07 20:02:15 +03:00
readMessageAndGuards ( art - > message , art ) ;
2013-02-04 13:29:59 +03:00
2022-09-11 15:12:35 +02:00
if ( objTempl - > id = = Obj : : SPELL_SCROLL )
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
spellID = reader . readUInt32 ( ) ;
2014-12-24 17:49:12 +02:00
artID = ArtifactID : : SPELL_SCROLL ;
2013-02-04 13:29:59 +03:00
}
2022-09-11 15:12:35 +02:00
else if ( objTempl - > id = = Obj : : ARTIFACT )
2013-02-04 13:29:59 +03:00
{
//specific artifact
2022-11-29 00:11:46 +02:00
artID = ArtifactID ( objTempl - > subid ) ;
2013-02-04 13:29:59 +03:00
}
2016-02-13 18:43:05 +02:00
art - > storedArtifact = CArtifactInstance : : createArtifact ( map , artID , spellID ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : RANDOM_RESOURCE :
case Obj : : RESOURCE :
{
2013-06-29 16:05:48 +03:00
auto res = new CGResource ( ) ;
2013-02-04 13:29:59 +03:00
nobj = res ;
2013-02-07 20:02:15 +03:00
readMessageAndGuards ( res - > message , res ) ;
res - > amount = reader . readUInt32 ( ) ;
2022-09-11 15:12:35 +02:00
if ( objTempl - > subid = = Res : : GOLD )
2013-02-04 13:29:59 +03:00
{
// Gold is multiplied by 100.
res - > amount * = 100 ;
}
2013-02-07 20:02:15 +03:00
reader . skip ( 4 ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : RANDOM_TOWN :
case Obj : : TOWN :
{
2022-09-11 15:12:35 +02:00
nobj = readTown ( objTempl - > subid ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : MINE :
case Obj : : ABANDONED_MINE :
{
nobj = new CGMine ( ) ;
2013-03-03 20:06:03 +03:00
nobj - > setOwner ( PlayerColor ( reader . readUInt8 ( ) ) ) ;
2013-02-07 20:02:15 +03:00
reader . skip ( 3 ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : CREATURE_GENERATOR1 :
case Obj : : CREATURE_GENERATOR2 :
case Obj : : CREATURE_GENERATOR3 :
case Obj : : CREATURE_GENERATOR4 :
{
nobj = new CGDwelling ( ) ;
2013-03-03 20:06:03 +03:00
nobj - > setOwner ( PlayerColor ( reader . readUInt8 ( ) ) ) ;
2013-02-07 20:02:15 +03:00
reader . skip ( 3 ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : SHRINE_OF_MAGIC_INCANTATION :
case Obj : : SHRINE_OF_MAGIC_GESTURE :
case Obj : : SHRINE_OF_MAGIC_THOUGHT :
{
2013-06-29 16:05:48 +03:00
auto shr = new CGShrine ( ) ;
2013-02-04 13:29:59 +03:00
nobj = shr ;
2013-02-13 22:35:43 +03:00
ui8 raw_id = reader . readUInt8 ( ) ;
if ( 255 = = raw_id )
{
shr - > spell = SpellID ( SpellID : : NONE ) ;
}
else
{
shr - > spell = SpellID ( raw_id ) ;
}
2013-02-07 20:02:15 +03:00
reader . skip ( 3 ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : PANDORAS_BOX :
{
2013-06-29 16:05:48 +03:00
auto box = new CGPandoraBox ( ) ;
2013-02-04 13:29:59 +03:00
nobj = box ;
2013-02-07 20:02:15 +03:00
readMessageAndGuards ( box - > message , box ) ;
2013-02-04 13:29:59 +03:00
2013-02-07 20:02:15 +03:00
box - > gainedExp = reader . readUInt32 ( ) ;
box - > manaDiff = reader . readUInt32 ( ) ;
box - > moraleDiff = reader . readInt8 ( ) ;
box - > luckDiff = reader . readInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2013-02-05 00:58:42 +03:00
readResourses ( box - > resources ) ;
2013-02-04 13:29:59 +03:00
box - > primskills . resize ( GameConstants : : PRIMARY_SKILLS ) ;
for ( int x = 0 ; x < 4 ; + + x )
{
2013-02-07 20:02:15 +03:00
box - > primskills [ x ] = static_cast < PrimarySkill : : PrimarySkill > ( reader . readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
int gabn = reader . readUInt8 ( ) ; //number of gained abilities
2013-02-04 13:29:59 +03:00
for ( int oo = 0 ; oo < gabn ; + + oo )
{
2013-02-12 22:49:40 +03:00
box - > abilities . push_back ( SecondarySkill ( reader . readUInt8 ( ) ) ) ;
2013-02-07 20:02:15 +03:00
box - > abilityLevels . push_back ( reader . readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
int gart = reader . readUInt8 ( ) ; //number of gained artifacts
2013-02-04 13:29:59 +03:00
for ( int oo = 0 ; oo < gart ; + + oo )
{
if ( map - > version > EMapFormat : : ROE )
{
2013-02-11 02:24:57 +03:00
box - > artifacts . push_back ( ArtifactID ( reader . readUInt16 ( ) ) ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2013-02-11 02:24:57 +03:00
box - > artifacts . push_back ( ArtifactID ( reader . readUInt8 ( ) ) ) ;
2013-02-04 13:29:59 +03:00
}
}
2013-02-07 20:02:15 +03:00
int gspel = reader . readUInt8 ( ) ; //number of gained spells
2013-02-04 13:29:59 +03:00
for ( int oo = 0 ; oo < gspel ; + + oo )
{
2013-02-11 02:24:57 +03:00
box - > spells . push_back ( SpellID ( reader . readUInt8 ( ) ) ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
int gcre = reader . readUInt8 ( ) ; //number of gained creatures
2013-02-05 00:58:42 +03:00
readCreatureSet ( & box - > creatures , gcre ) ;
2013-02-07 20:02:15 +03:00
reader . skip ( 8 ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : GRAIL :
{
map - > grailPos = objPos ;
2016-01-31 17:01:58 +02:00
map - > grailRadius = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
continue ;
}
case Obj : : RANDOM_DWELLING : //same as castle + level range
case Obj : : RANDOM_DWELLING_LVL : //same as castle, fixed level
case Obj : : RANDOM_DWELLING_FACTION : //level range, fixed faction
{
2016-11-13 12:38:42 +02:00
auto dwelling = new CGDwelling ( ) ;
nobj = dwelling ;
2013-02-04 13:29:59 +03:00
CSpecObjInfo * spec = nullptr ;
2022-09-11 15:12:35 +02:00
switch ( objTempl - > id )
2013-02-04 13:29:59 +03:00
{
2017-05-28 14:16:40 +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 " ) ;
2013-02-04 13:29:59 +03:00
}
2016-11-13 12:38:42 +02:00
spec - > owner = dwelling ;
2013-02-04 13:29:59 +03:00
2016-11-13 12:38:42 +02:00
nobj - > setOwner ( PlayerColor ( reader . readUInt32 ( ) ) ) ;
2013-02-04 13:29:59 +03:00
//216 and 217
if ( auto castleSpec = dynamic_cast < CCreGenAsCastleInfo * > ( spec ) )
{
2016-11-13 12:38:42 +02:00
castleSpec - > instanceId = " " ;
castleSpec - > identifier = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
if ( ! castleSpec - > identifier )
{
castleSpec - > asCastle = false ;
2016-11-13 12:38:42 +02:00
const int MASK_SIZE = 8 ;
ui8 mask [ 2 ] ;
mask [ 0 ] = reader . readUInt8 ( ) ;
mask [ 1 ] = reader . readUInt8 ( ) ;
castleSpec - > allowedFactions . clear ( ) ;
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
castleSpec - > allowedFactions . resize ( VLC - > townh - > size ( ) , false ) ;
2016-11-13 12:38:42 +02:00
for ( int i = 0 ; i < MASK_SIZE ; i + + )
castleSpec - > allowedFactions [ i ] = ( ( mask [ 0 ] & ( 1 < < i ) ) > 0 ) ;
for ( int i = 0 ; i < ( GameConstants : : F_NUMBER - MASK_SIZE ) ; i + + )
castleSpec - > allowedFactions [ i + MASK_SIZE ] = ( ( mask [ 1 ] & ( 1 < < i ) ) > 0 ) ;
2013-02-04 13:29:59 +03:00
}
else
{
castleSpec - > asCastle = true ;
}
}
//216 and 218
if ( auto lvlSpec = dynamic_cast < CCreGenLeveledInfo * > ( spec ) )
{
2013-02-07 20:02:15 +03:00
lvlSpec - > minLevel = std : : max ( reader . readUInt8 ( ) , ui8 ( 1 ) ) ;
lvlSpec - > maxLevel = std : : min ( reader . readUInt8 ( ) , ui8 ( 7 ) ) ;
2013-02-04 13:29:59 +03:00
}
2016-11-13 12:38:42 +02:00
dwelling - > info = spec ;
2013-02-04 13:29:59 +03:00
break ;
}
2013-02-07 20:02:15 +03:00
case Obj : : QUEST_GUARD :
2013-02-04 13:29:59 +03:00
{
2013-06-29 16:05:48 +03:00
auto guard = new CGQuestGuard ( ) ;
2013-02-04 13:29:59 +03:00
readQuest ( guard ) ;
nobj = guard ;
break ;
}
case Obj : : SHIPYARD :
{
nobj = new CGShipyard ( ) ;
2013-03-03 20:06:03 +03:00
nobj - > setOwner ( PlayerColor ( reader . readUInt32 ( ) ) ) ;
2013-02-04 13:29:59 +03:00
break ;
}
case Obj : : HERO_PLACEHOLDER : //hero placeholder
{
2013-06-29 16:05:48 +03:00
auto hp = new CGHeroPlaceholder ( ) ;
2013-02-04 13:29:59 +03:00
nobj = hp ;
2013-03-03 20:06:03 +03:00
hp - > setOwner ( PlayerColor ( reader . readUInt8 ( ) ) ) ;
2013-02-04 13:29:59 +03:00
2017-07-12 21:01:10 +02:00
int htid = reader . readUInt8 ( ) ; //hero type id
2013-02-04 13:29:59 +03:00
nobj - > subID = htid ;
if ( htid = = 0xff )
{
2013-02-07 20:02:15 +03:00
hp - > power = reader . readUInt8 ( ) ;
2017-08-11 19:03:05 +02:00
logGlobal - > info ( " Hero placeholder: by power at %s " , objPos . toString ( ) ) ;
2013-02-04 13:29:59 +03:00
}
else
{
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
logGlobal - > info ( " Hero placeholder: %s at %s " , VLC - > heroh - > objects [ htid ] - > name , objPos . toString ( ) ) ;
2013-02-04 13:29:59 +03:00
hp - > power = 0 ;
}
break ;
}
case Obj : : BORDERGUARD :
{
nobj = new CGBorderGuard ( ) ;
break ;
}
case Obj : : BORDER_GATE :
{
nobj = new CGBorderGate ( ) ;
break ;
}
case Obj : : PYRAMID : //Pyramid of WoG object
{
2022-09-11 15:12:35 +02:00
if ( objTempl - > subid = = 0 )
2013-02-04 13:29:59 +03:00
{
2014-06-22 13:39:40 +03:00
nobj = new CBank ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
//WoG object
//TODO: possible special handling
nobj = new CGObjectInstance ( ) ;
}
break ;
}
case Obj : : LIGHTHOUSE : //Lighthouse
{
nobj = new CGLighthouse ( ) ;
2013-03-03 20:06:03 +03:00
nobj - > tempOwner = PlayerColor ( reader . readUInt32 ( ) ) ;
2013-02-04 13:29:59 +03:00
break ;
}
default : //any other object
{
2022-09-11 15:12:35 +02:00
if ( VLC - > objtypeh - > knownSubObjects ( objTempl - > id ) . count ( objTempl - > subid ) )
2014-06-25 20:26:47 +03:00
{
2022-09-11 15:12:35 +02:00
nobj = VLC - > objtypeh - > getHandlerFor ( objTempl - > id , objTempl - > subid ) - > create ( objTempl ) ;
2014-06-25 20:26:47 +03:00
}
else
{
2022-09-11 15:12:35 +02:00
logGlobal - > warn ( " Unrecognized object: %d:%d at %s on map %s " , objTempl - > id . toEnum ( ) , objTempl - > subid , objPos . toString ( ) , map - > name ) ;
2014-06-25 20:26:47 +03:00
nobj = new CGObjectInstance ( ) ;
}
2013-02-04 13:29:59 +03:00
break ;
}
}
nobj - > pos = objPos ;
2022-09-11 15:12:35 +02:00
nobj - > ID = objTempl - > id ;
2013-02-04 13:29:59 +03:00
nobj - > id = idToBeGiven ;
if ( nobj - > ID ! = Obj : : HERO & & nobj - > ID ! = Obj : : HERO_PLACEHOLDER & & nobj - > ID ! = Obj : : PRISON )
{
2022-09-11 15:12:35 +02:00
nobj - > subID = objTempl - > subid ;
2013-02-04 13:29:59 +03:00
}
2014-01-03 02:48:38 +03:00
nobj - > appearance = objTempl ;
2020-10-01 10:38:06 +02:00
assert ( idToBeGiven = = ObjectInstanceID ( ( si32 ) map - > objects . size ( ) ) ) ;
2016-02-22 18:26:42 +02:00
2013-02-04 13:29:59 +03:00
{
2016-02-22 18:26:42 +02:00
//TODO: define valid typeName and subtypeName fro H3M maps
//boost::format fmt("%s_%d");
//fmt % nobj->typeName % nobj->id.getNum();
boost : : format fmt ( " obj_%d " ) ;
fmt % nobj - > id . getNum ( ) ;
nobj - > instanceName = fmt . str ( ) ;
2013-02-04 13:29:59 +03:00
}
2016-02-22 18:26:42 +02:00
map - > addNewObject ( nobj ) ;
2013-02-04 13:29:59 +03:00
}
2013-05-19 01:30:48 +03:00
std : : sort ( map - > heroesOnMap . begin ( ) , map - > heroesOnMap . end ( ) , [ ] ( const ConstTransitivePtr < CGHeroInstance > & a , const ConstTransitivePtr < CGHeroInstance > & b )
2013-02-04 13:29:59 +03:00
{
return a - > subID < b - > subID ;
} ) ;
}
2013-02-05 00:58:42 +03:00
void CMapLoaderH3M : : readCreatureSet ( CCreatureSet * out , int number )
2013-02-04 13:29:59 +03:00
{
2013-02-05 00:58:42 +03:00
const bool version = ( map - > version > EMapFormat : : ROE ) ;
2013-02-04 13:29:59 +03:00
const int maxID = version ? 0xffff : 0xff ;
for ( int ir = 0 ; ir < number ; + + ir )
{
2013-02-11 02:24:57 +03:00
CreatureID creID ;
2013-02-04 13:29:59 +03:00
int count ;
if ( version )
{
2013-02-11 02:24:57 +03:00
creID = CreatureID ( reader . readUInt16 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2013-02-11 02:24:57 +03:00
creID = CreatureID ( reader . readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
count = reader . readUInt16 ( ) ;
2013-02-04 13:29:59 +03:00
// Empty slot
2014-02-09 15:10:02 +03:00
if ( creID = = maxID )
continue ;
2013-02-04 13:29:59 +03:00
2013-06-29 16:05:48 +03:00
auto hlp = new CStackInstance ( ) ;
2013-02-04 13:29:59 +03:00
hlp - > count = count ;
if ( creID > maxID - 0xf )
{
//this will happen when random object has random army
2014-02-09 15:10:02 +03:00
hlp - > idRand = maxID - creID - 1 ;
2013-02-04 13:29:59 +03:00
}
else
{
hlp - > setType ( creID ) ;
}
2013-02-16 17:03:47 +03:00
out - > putStack ( SlotID ( ir ) , hlp ) ;
2013-02-04 13:29:59 +03:00
}
out - > validTypes ( true ) ;
}
2015-11-27 10:34:03 +02:00
CGObjectInstance * CMapLoaderH3M : : readHero ( ObjectInstanceID idToBeGiven , const int3 & initialPos )
2013-02-04 13:29:59 +03:00
{
2013-11-07 15:48:41 +03:00
auto nhi = new CGHeroInstance ( ) ;
2013-02-04 13:29:59 +03:00
if ( map - > version > EMapFormat : : ROE )
{
2013-11-07 15:48:41 +03:00
unsigned int identifier = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
map - > questIdentifierToId [ identifier ] = idToBeGiven ;
}
2013-03-03 20:06:03 +03:00
PlayerColor owner = PlayerColor ( reader . readUInt8 ( ) ) ;
2013-02-07 20:02:15 +03:00
nhi - > subID = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2013-09-03 16:18:09 +03:00
assert ( ! nhi - > getArt ( ArtifactPosition : : MACH4 ) ) ;
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
{
2013-06-29 16:05:48 +03:00
if ( elem - > subID = = nhi - > subID )
2013-02-04 13:29:59 +03:00
{
2017-08-11 19:03:05 +02:00
logGlobal - > debug ( " Hero %d will be taken from the predefined heroes list. " , nhi - > subID ) ;
2013-02-04 13:29:59 +03:00
delete nhi ;
2013-06-29 16:05:48 +03:00
nhi = elem ;
2013-02-04 13:29:59 +03:00
break ;
}
}
nhi - > setOwner ( owner ) ;
nhi - > portrait = nhi - > subID ;
2013-06-29 16:05:48 +03:00
for ( auto & elem : map - > disposedHeroes )
2013-02-04 13:29:59 +03:00
{
2013-06-29 16:05:48 +03:00
if ( elem . heroId = = nhi - > subID )
2013-02-04 13:29:59 +03:00
{
2013-06-29 16:05:48 +03:00
nhi - > name = elem . name ;
nhi - > portrait = elem . portrait ;
2013-02-04 13:29:59 +03:00
break ;
}
}
2013-02-07 20:02:15 +03:00
bool hasName = reader . readBool ( ) ;
if ( hasName )
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
nhi - > name = reader . readString ( ) ;
2013-02-04 13:29:59 +03:00
}
if ( map - > version > EMapFormat : : AB )
{
2013-02-07 20:02:15 +03:00
bool hasExp = reader . readBool ( ) ;
if ( hasExp )
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
nhi - > exp = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
nhi - > exp = 0xffffffff ;
}
}
else
{
2013-02-07 20:02:15 +03:00
nhi - > exp = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
//0 means "not set" in <=AB maps
if ( ! nhi - > exp )
{
nhi - > exp = 0xffffffff ;
}
}
2013-02-07 20:02:15 +03:00
bool hasPortrait = reader . readBool ( ) ;
if ( hasPortrait )
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
nhi - > portrait = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
bool hasSecSkills = reader . readBool ( ) ;
if ( hasSecSkills )
2013-02-04 13:29:59 +03:00
{
2013-09-03 16:18:09 +03:00
if ( nhi - > secSkills . size ( ) )
{
nhi - > secSkills . clear ( ) ;
2017-08-10 19:17:10 +02:00
//logGlobal->warn("Hero %s subID=%d has set secondary skills twice (in map properties and on adventure map instance). Using the latter set...", nhi->name, nhi->subID);
2013-09-03 16:18:09 +03:00
}
2013-02-07 20:02:15 +03:00
int howMany = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
nhi - > secSkills . resize ( howMany ) ;
for ( int yy = 0 ; yy < howMany ; + + yy )
{
2013-02-12 22:49:40 +03:00
nhi - > secSkills [ yy ] . first = SecondarySkill ( reader . readUInt8 ( ) ) ;
2013-02-07 20:02:15 +03:00
nhi - > secSkills [ yy ] . second = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
}
2013-02-07 20:02:15 +03:00
bool hasGarison = reader . readBool ( ) ;
if ( hasGarison )
2013-02-04 13:29:59 +03:00
{
2013-02-05 00:58:42 +03:00
readCreatureSet ( nhi , 7 ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
nhi - > formation = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
loadArtifactsOfHero ( nhi ) ;
2016-01-31 17:01:58 +02:00
nhi - > patrol . patrolRadius = reader . readUInt8 ( ) ;
2023-01-06 23:11:53 +02:00
nhi - > patrol . patrolling = ( nhi - > patrol . patrolRadius ! = 0xff ) ;
2013-02-04 13:29:59 +03:00
if ( map - > version > EMapFormat : : ROE )
{
2013-02-07 20:02:15 +03:00
bool hasCustomBiography = reader . readBool ( ) ;
if ( hasCustomBiography )
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
nhi - > biography = reader . readString ( ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
nhi - > sex = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
// Remove trash
if ( nhi - > sex ! = 0xFF )
{
nhi - > sex & = 1 ;
}
}
else
{
nhi - > sex = 0xFF ;
}
// Spells
if ( map - > version > EMapFormat : : AB )
{
2013-02-07 20:02:15 +03:00
bool hasCustomSpells = reader . readBool ( ) ;
2013-09-03 16:18:09 +03:00
if ( nhi - > spells . size ( ) )
{
nhi - > clear ( ) ;
2017-08-10 19:17:10 +02:00
logGlobal - > warn ( " Hero %s subID=%d has spells set twice (in map properties and on adventure map instance). Using the latter set... " , nhi - > name , nhi - > subID ) ;
2013-09-03 16:18:09 +03:00
}
2013-02-07 20:02:15 +03:00
if ( hasCustomSpells )
2013-02-04 13:29:59 +03:00
{
2013-02-11 02:24:57 +03:00
nhi - > spells . insert ( SpellID : : PRESET ) ; //placeholder "preset spells"
2013-02-05 00:58:42 +03:00
readSpells ( nhi - > spells ) ;
2013-02-04 13:29:59 +03:00
}
}
else if ( map - > version = = EMapFormat : : AB )
{
//we can read one spell
2013-02-07 20:02:15 +03:00
ui8 buff = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
if ( buff ! = 254 )
{
2013-02-11 02:24:57 +03:00
nhi - > spells . insert ( SpellID : : PRESET ) ; //placeholder "preset spells"
2013-02-04 13:29:59 +03:00
if ( buff < 254 ) //255 means no spells
{
2013-02-11 02:24:57 +03:00
nhi - > spells . insert ( SpellID ( buff ) ) ;
2013-02-04 13:29:59 +03:00
}
}
}
if ( map - > version > EMapFormat : : AB )
{
2013-02-07 20:02:15 +03:00
bool hasCustomPrimSkills = reader . readBool ( ) ;
if ( hasCustomPrimSkills )
2013-02-04 13:29:59 +03:00
{
2020-11-11 21:43:40 +02:00
auto ps = nhi - > getAllBonuses ( Selector : : type ( ) ( Bonus : : PRIMARY_SKILL )
. And ( Selector : : sourceType ( ) ( Bonus : : HERO_BASE_SKILL ) ) , nullptr ) ;
2013-09-03 16:18:09 +03:00
if ( ps - > size ( ) )
{
2017-08-10 19:17:10 +02:00
logGlobal - > warn ( " Hero %s subID=%d has set primary skills twice (in map properties and on adventure map instance). Using the latter set... " , nhi - > name , nhi - > subID ) ;
2013-09-03 16:18:09 +03:00
for ( auto b : * ps )
nhi - > removeBonus ( b ) ;
}
2013-02-04 13:29:59 +03:00
for ( int xx = 0 ; xx < GameConstants : : PRIMARY_SKILLS ; + + xx )
{
2013-02-07 20:02:15 +03:00
nhi - > pushPrimSkill ( static_cast < PrimarySkill : : PrimarySkill > ( xx ) , reader . readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
}
}
}
2013-02-07 20:02:15 +03:00
reader . skip ( 16 ) ;
2013-02-04 13:29:59 +03:00
return nhi ;
}
CGSeerHut * CMapLoaderH3M : : readSeerHut ( )
{
2013-06-29 16:05:48 +03:00
auto hut = new CGSeerHut ( ) ;
2013-02-04 13:29:59 +03:00
if ( map - > version > EMapFormat : : ROE )
{
readQuest ( hut ) ;
}
else
{
//RoE
2022-11-29 00:11:46 +02:00
auto artID = ArtifactID ( reader . readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
if ( artID ! = 255 )
{
//not none quest
2022-10-01 08:28:32 +02:00
hut - > quest - > addArtifactID ( artID ) ;
2013-02-04 13:29:59 +03:00
hut - > quest - > missionType = CQuest : : MISSION_ART ;
}
else
{
hut - > quest - > missionType = CQuest : : MISSION_NONE ;
}
hut - > quest - > lastDay = - 1 ; //no timeout
hut - > quest - > isCustomFirst = hut - > quest - > isCustomNext = hut - > quest - > isCustomComplete = false ;
}
if ( hut - > quest - > missionType )
{
2013-02-07 20:02:15 +03:00
auto rewardType = static_cast < CGSeerHut : : ERewardType > ( reader . readUInt8 ( ) ) ;
hut - > rewardType = rewardType ;
2013-02-04 13:29:59 +03:00
switch ( rewardType )
{
2013-02-07 20:02:15 +03:00
case CGSeerHut : : EXPERIENCE :
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
hut - > rVal = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2013-02-07 20:02:15 +03:00
case CGSeerHut : : MANA_POINTS :
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
hut - > rVal = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2013-02-07 20:02:15 +03:00
case CGSeerHut : : MORALE_BONUS :
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
hut - > rVal = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2013-02-07 20:02:15 +03:00
case CGSeerHut : : LUCK_BONUS :
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
hut - > rVal = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2013-02-07 20:02:15 +03:00
case CGSeerHut : : RESOURCES :
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
hut - > rID = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
// Only the first 3 bytes are used. Skip the 4th.
2013-02-07 20:02:15 +03:00
hut - > rVal = reader . readUInt32 ( ) & 0x00ffffff ;
2013-02-04 13:29:59 +03:00
break ;
}
2013-02-07 20:02:15 +03:00
case CGSeerHut : : PRIMARY_SKILL :
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
hut - > rID = reader . readUInt8 ( ) ;
hut - > rVal = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2013-02-07 20:02:15 +03:00
case CGSeerHut : : SECONDARY_SKILL :
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
hut - > rID = reader . readUInt8 ( ) ;
hut - > rVal = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2013-02-07 20:02:15 +03:00
case CGSeerHut : : ARTIFACT :
2013-02-04 13:29:59 +03:00
{
if ( map - > version = = EMapFormat : : ROE )
{
2013-02-07 20:02:15 +03:00
hut - > rID = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2013-02-07 20:02:15 +03:00
hut - > rID = reader . readUInt16 ( ) ;
2013-02-04 13:29:59 +03:00
}
break ;
}
2013-02-07 20:02:15 +03:00
case CGSeerHut : : SPELL :
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
hut - > rID = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2013-02-07 20:02:15 +03:00
case CGSeerHut : : CREATURE :
2013-02-04 13:29:59 +03:00
{
if ( map - > version > EMapFormat : : ROE )
{
2013-02-07 20:02:15 +03:00
hut - > rID = reader . readUInt16 ( ) ;
hut - > rVal = reader . readUInt16 ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2013-02-07 20:02:15 +03:00
hut - > rID = reader . readUInt8 ( ) ;
hut - > rVal = reader . readUInt16 ( ) ;
2013-02-04 13:29:59 +03:00
}
break ;
}
}
2013-02-07 20:02:15 +03:00
reader . skip ( 2 ) ;
2013-02-04 13:29:59 +03:00
}
else
{
// missionType==255
2013-02-07 20:02:15 +03:00
reader . skip ( 3 ) ;
2013-02-04 13:29:59 +03:00
}
return hut ;
}
void CMapLoaderH3M : : readQuest ( IQuestObject * guard )
{
2013-02-07 20:02:15 +03:00
guard - > quest - > missionType = static_cast < CQuest : : Emission > ( reader . readUInt8 ( ) ) ;
2013-02-04 13:29:59 +03:00
switch ( guard - > quest - > missionType )
{
2016-11-13 12:38:42 +02:00
case CQuest : : MISSION_NONE :
2013-02-04 13:29:59 +03:00
return ;
2016-11-13 12:38:42 +02:00
case CQuest : : MISSION_PRIMARY_STAT :
2013-02-04 13:29:59 +03:00
{
guard - > quest - > m2stats . resize ( 4 ) ;
for ( int x = 0 ; x < 4 ; + + x )
{
2013-02-07 20:02:15 +03:00
guard - > quest - > m2stats [ x ] = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
}
break ;
2016-11-13 12:38:42 +02:00
case CQuest : : MISSION_LEVEL :
case CQuest : : MISSION_KILL_HERO :
case CQuest : : MISSION_KILL_CREATURE :
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
guard - > quest - > m13489val = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
break ;
}
2016-11-13 12:38:42 +02:00
case CQuest : : MISSION_ART :
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
int artNumber = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
for ( int yy = 0 ; yy < artNumber ; + + yy )
{
2022-11-29 00:11:46 +02:00
auto artid = ArtifactID ( reader . readUInt16 ( ) ) ;
2022-10-01 08:28:32 +02:00
guard - > quest - > addArtifactID ( artid ) ;
2013-02-04 13:29:59 +03:00
map - > allowedArtifact [ artid ] = false ; //these are unavailable for random generation
}
break ;
}
2016-11-13 12:38:42 +02:00
case CQuest : : MISSION_ARMY :
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
int typeNumber = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
guard - > quest - > m6creatures . resize ( typeNumber ) ;
for ( int hh = 0 ; hh < typeNumber ; + + hh )
{
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
guard - > quest - > m6creatures [ hh ] . type = VLC - > creh - > objects [ reader . readUInt16 ( ) ] ;
2013-02-07 20:02:15 +03:00
guard - > quest - > m6creatures [ hh ] . count = reader . readUInt16 ( ) ;
2013-02-04 13:29:59 +03:00
}
break ;
}
2016-11-13 12:38:42 +02:00
case CQuest : : MISSION_RESOURCES :
2013-02-04 13:29:59 +03:00
{
guard - > quest - > m7resources . resize ( 7 ) ;
for ( int x = 0 ; x < 7 ; + + x )
{
2013-02-07 20:02:15 +03:00
guard - > quest - > m7resources [ x ] = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
}
break ;
}
2016-11-13 12:38:42 +02:00
case CQuest : : MISSION_HERO :
case CQuest : : MISSION_PLAYER :
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
guard - > quest - > m13489val = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
break ;
}
}
2013-02-07 20:02:15 +03:00
int limit = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
if ( limit = = ( static_cast < int > ( 0xffffffff ) ) )
{
guard - > quest - > lastDay = - 1 ;
}
else
{
guard - > quest - > lastDay = limit ;
}
2013-02-07 20:02:15 +03:00
guard - > quest - > firstVisitText = reader . readString ( ) ;
guard - > quest - > nextVisitText = reader . readString ( ) ;
guard - > quest - > completedText = reader . readString ( ) ;
2013-02-04 13:29:59 +03:00
guard - > quest - > isCustomFirst = guard - > quest - > firstVisitText . size ( ) > 0 ;
guard - > quest - > isCustomNext = guard - > quest - > nextVisitText . size ( ) > 0 ;
guard - > quest - > isCustomComplete = guard - > quest - > completedText . size ( ) > 0 ;
}
CGTownInstance * CMapLoaderH3M : : readTown ( int castleID )
{
2013-06-29 16:05:48 +03:00
auto nt = new CGTownInstance ( ) ;
2013-02-04 13:29:59 +03:00
if ( map - > version > EMapFormat : : ROE )
{
2013-02-07 20:02:15 +03:00
nt - > identifier = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
}
2013-03-03 20:06:03 +03:00
nt - > tempOwner = PlayerColor ( reader . readUInt8 ( ) ) ;
2013-02-07 20:02:15 +03:00
bool hasName = reader . readBool ( ) ;
if ( hasName )
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
nt - > name = reader . readString ( ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
bool hasGarrison = reader . readBool ( ) ;
if ( hasGarrison )
2013-02-04 13:29:59 +03:00
{
2013-02-05 00:58:42 +03:00
readCreatureSet ( nt , 7 ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
nt - > formation = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2013-02-07 20:02:15 +03:00
bool hasCustomBuildings = reader . readBool ( ) ;
if ( hasCustomBuildings )
2013-02-04 13:29:59 +03:00
{
2013-02-07 20:02:15 +03:00
readBitmask ( nt - > builtBuildings , 6 , 48 , false ) ;
readBitmask ( nt - > forbiddenBuildings , 6 , 48 , false ) ;
2013-02-04 13:29:59 +03:00
nt - > builtBuildings = convertBuildings ( nt - > builtBuildings , castleID ) ;
nt - > forbiddenBuildings = convertBuildings ( nt - > forbiddenBuildings , castleID ) ;
}
// Standard buildings
else
{
2013-02-07 20:02:15 +03:00
bool hasFort = reader . readBool ( ) ;
if ( hasFort )
2013-02-04 13:29:59 +03:00
{
2013-02-11 02:24:57 +03:00
nt - > builtBuildings . insert ( BuildingID : : FORT ) ;
2013-02-04 13:29:59 +03:00
}
//means that set of standard building should be included
2013-02-11 22:11:34 +03:00
nt - > builtBuildings . insert ( BuildingID : : DEFAULT ) ;
2013-02-04 13:29:59 +03:00
}
if ( map - > version > EMapFormat : : ROE )
{
for ( int i = 0 ; i < 9 ; + + i )
{
2013-02-07 20:02:15 +03:00
ui8 c = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
for ( int yy = 0 ; yy < 8 ; + + yy )
{
if ( i * 8 + yy < GameConstants : : SPELLS_QUANTITY )
{
2014-05-16 13:15:35 +03:00
if ( c = = ( c | static_cast < ui8 > ( std : : pow ( 2. , yy ) ) ) ) //add obligatory spell even if it's banned on a map (?)
2013-02-04 13:29:59 +03:00
{
2013-02-11 22:11:34 +03:00
nt - > obligatorySpells . push_back ( SpellID ( i * 8 + yy ) ) ;
2013-02-04 13:29:59 +03:00
}
}
}
}
}
for ( int i = 0 ; i < 9 ; + + i )
{
2013-02-07 20:02:15 +03:00
ui8 c = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
for ( int yy = 0 ; yy < 8 ; + + yy )
{
2016-02-13 18:43:05 +02:00
int spellid = i * 8 + yy ;
2014-05-16 13:15:35 +03:00
if ( spellid < GameConstants : : SPELLS_QUANTITY )
2013-02-04 13:29:59 +03:00
{
2014-05-16 13:15:35 +03:00
if ( c ! = ( c | static_cast < ui8 > ( std : : pow ( 2. , yy ) ) ) & & map - > allowedSpell [ spellid ] ) //add random spell only if it's allowed on entire map
2013-02-04 13:29:59 +03:00
{
2014-05-16 13:15:35 +03:00
nt - > possibleSpells . push_back ( SpellID ( spellid ) ) ;
2013-02-04 13:29:59 +03:00
}
}
}
}
2014-03-09 12:37:20 +03:00
//add all spells from mods
2014-05-16 13:15:35 +03:00
//TODO: allow customize new spells in towns
2014-03-09 12:37:20 +03:00
for ( int i = SpellID : : AFTER_LAST ; i < VLC - > spellh - > objects . size ( ) ; + + i )
{
nt - > possibleSpells . push_back ( SpellID ( i ) ) ;
}
2013-02-04 13:29:59 +03:00
// Read castle events
2013-02-07 20:02:15 +03:00
int numberOfEvent = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
for ( int gh = 0 ; gh < numberOfEvent ; + + gh )
{
2013-02-19 01:37:22 +03:00
CCastleEvent nce ;
nce . town = nt ;
nce . name = reader . readString ( ) ;
nce . message = reader . readString ( ) ;
2013-02-05 00:58:42 +03:00
2013-02-19 01:37:22 +03:00
readResourses ( nce . resources ) ;
2013-02-05 00:58:42 +03:00
2013-02-19 01:37:22 +03:00
nce . players = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
if ( map - > version > EMapFormat : : AB )
{
2013-02-19 01:37:22 +03:00
nce . humanAffected = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2013-02-19 01:37:22 +03:00
nce . humanAffected = true ;
2013-02-04 13:29:59 +03:00
}
2013-02-19 01:37:22 +03:00
nce . computerAffected = reader . readUInt8 ( ) ;
nce . firstOccurence = reader . readUInt16 ( ) ;
nce . nextOccurence = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2013-02-07 20:02:15 +03:00
reader . skip ( 17 ) ;
2013-02-04 13:29:59 +03:00
// New buildings
2013-02-07 20:02:15 +03:00
2013-02-19 01:37:22 +03:00
readBitmask ( nce . buildings , 6 , 48 , false ) ;
2013-02-07 20:02:15 +03:00
2013-02-19 01:37:22 +03:00
nce . buildings = convertBuildings ( nce . buildings , castleID , false ) ;
2013-02-04 13:29:59 +03:00
2013-02-19 01:37:22 +03:00
nce . creatures . resize ( 7 ) ;
2013-02-04 13:29:59 +03:00
for ( int vv = 0 ; vv < 7 ; + + vv )
{
2013-02-19 01:37:22 +03:00
nce . creatures [ vv ] = reader . readUInt16 ( ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
reader . skip ( 4 ) ;
2013-02-04 13:29:59 +03:00
nt - > events . push_back ( nce ) ;
}
if ( map - > version > EMapFormat : : AB )
{
2013-02-07 20:02:15 +03:00
nt - > alignment = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
2013-02-07 20:02:15 +03:00
reader . skip ( 3 ) ;
2013-02-04 13:29:59 +03:00
return nt ;
}
2017-07-15 13:08:20 +02:00
std : : set < BuildingID > CMapLoaderH3M : : convertBuildings ( const std : : set < BuildingID > h3m , int castleID , bool addAuxiliary )
2013-02-04 13:29:59 +03:00
{
2013-02-11 22:11:34 +03:00
std : : map < int , BuildingID > mapa ;
std : : set < BuildingID > ret ;
2013-02-04 13:29:59 +03:00
// Note: this file is parsed many times.
const JsonNode config ( ResourceID ( " config/buildings5.json " ) ) ;
2013-06-29 16:05:48 +03:00
for ( const JsonNode & entry : config [ " table " ] . Vector ( ) )
2013-02-04 13:29:59 +03:00
{
2020-10-01 10:38:06 +02:00
int town = static_cast < int > ( entry [ " town " ] . Float ( ) ) ;
2013-02-04 13:29:59 +03:00
if ( town = = castleID | | town = = - 1 )
{
2020-10-01 10:38:06 +02:00
mapa [ ( int ) entry [ " h3 " ] . Float ( ) ] = BuildingID ( ( si32 ) entry [ " vcmi " ] . Float ( ) ) ;
2013-02-04 13:29:59 +03:00
}
}
2013-06-29 16:05:48 +03:00
for ( auto & elem : h3m )
2013-02-04 13:29:59 +03:00
{
2013-06-29 16:05:48 +03:00
if ( mapa [ elem ] > = 0 )
2013-02-04 13:29:59 +03:00
{
2013-06-29 16:05:48 +03:00
ret . insert ( mapa [ elem ] ) ;
2013-02-04 13:29:59 +03:00
}
// horde buildings
2013-06-29 16:05:48 +03:00
else if ( mapa [ elem ] > = ( - GameConstants : : CREATURES_PER_TOWN ) )
2013-02-04 13:29:59 +03:00
{
2013-06-29 16:05:48 +03:00
int level = ( mapa [ elem ] ) ;
2013-02-04 13:29:59 +03:00
//(-30)..(-36) - horde buildings (for game loading only), don't see other way to handle hordes in random towns
2013-02-11 22:11:34 +03:00
ret . insert ( BuildingID ( level - 30 ) ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2017-08-11 19:03:05 +02:00
logGlobal - > warn ( " Conversion warning: unknown building %d in castle %d " , elem . num , castleID ) ;
2013-02-04 13:29:59 +03:00
}
}
if ( addAuxiliary )
{
//village hall is always present
2013-02-11 02:24:57 +03:00
ret . insert ( BuildingID : : VILLAGE_HALL ) ;
2013-02-04 13:29:59 +03:00
2019-06-04 22:29:07 +02:00
if ( ret . find ( BuildingID : : CITY_HALL ) ! = ret . end ( ) )
{
ret . insert ( BuildingID : : EXTRA_CITY_HALL ) ;
}
if ( ret . find ( BuildingID : : TOWN_HALL ) ! = ret . end ( ) )
{
ret . insert ( BuildingID : : EXTRA_TOWN_HALL ) ;
}
if ( ret . find ( BuildingID : : CAPITOL ) ! = ret . end ( ) )
{
ret . insert ( BuildingID : : EXTRA_CAPITOL ) ;
}
2013-02-04 13:29:59 +03:00
}
return ret ;
}
void CMapLoaderH3M : : readEvents ( )
{
2013-02-07 20:02:15 +03:00
int numberOfEvents = reader . readUInt32 ( ) ;
2013-02-04 13:29:59 +03:00
for ( int yyoo = 0 ; yyoo < numberOfEvents ; + + yyoo )
{
2013-02-19 01:37:22 +03:00
CMapEvent ne ;
ne . name = reader . readString ( ) ;
ne . message = reader . readString ( ) ;
2013-02-04 13:29:59 +03:00
2013-02-19 01:37:22 +03:00
readResourses ( ne . resources ) ;
ne . players = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
if ( map - > version > EMapFormat : : AB )
{
2013-02-19 01:37:22 +03:00
ne . humanAffected = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
}
else
{
2013-02-19 01:37:22 +03:00
ne . humanAffected = true ;
2013-02-04 13:29:59 +03:00
}
2013-02-19 01:37:22 +03:00
ne . computerAffected = reader . readUInt8 ( ) ;
ne . firstOccurence = reader . readUInt16 ( ) ;
ne . nextOccurence = reader . readUInt8 ( ) ;
2013-02-04 13:29:59 +03:00
2013-02-07 20:02:15 +03:00
reader . skip ( 17 ) ;
2013-02-04 13:29:59 +03:00
map - > events . push_back ( ne ) ;
}
}
2013-02-07 20:02:15 +03:00
void CMapLoaderH3M : : readMessageAndGuards ( std : : string & message , CCreatureSet * guards )
2013-02-05 00:58:42 +03:00
{
2013-02-07 20:02:15 +03:00
bool hasMessage = reader . readBool ( ) ;
if ( hasMessage )
2013-02-05 00:58:42 +03:00
{
2013-02-07 20:02:15 +03:00
message = reader . readString ( ) ;
bool hasGuards = reader . readBool ( ) ;
if ( hasGuards )
2013-02-05 00:58:42 +03:00
{
2013-02-07 20:02:15 +03:00
readCreatureSet ( guards , 7 ) ;
2013-02-05 00:58:42 +03:00
}
2013-02-07 20:02:15 +03:00
reader . skip ( 4 ) ;
2013-02-05 00:58:42 +03:00
}
}
2013-02-11 02:24:57 +03:00
void CMapLoaderH3M : : readSpells ( std : : set < SpellID > & dest )
2013-02-07 20:02:15 +03:00
{
readBitmask ( dest , 9 , GameConstants : : SPELLS_QUANTITY , false ) ;
}
2013-02-05 00:58:42 +03:00
void CMapLoaderH3M : : readResourses ( TResources & resources )
{
resources . resize ( GameConstants : : RESOURCE_QUANTITY ) ; //needed?
for ( int x = 0 ; x < 7 ; + + x )
{
2013-02-07 20:02:15 +03:00
resources [ x ] = reader . readUInt32 ( ) ;
2013-02-05 00:58:42 +03:00
}
}
2016-11-13 12:38:42 +02:00
template < class Indentifier >
void CMapLoaderH3M : : readBitmask ( std : : set < Indentifier > & dest , const int byteCount , const int limit , bool negate )
2013-02-07 20:02:15 +03:00
{
std : : vector < bool > temp ;
temp . resize ( limit , true ) ;
readBitmask ( temp , byteCount , limit , negate ) ;
for ( int i = 0 ; i < std : : min ( temp . size ( ) , static_cast < size_t > ( limit ) ) ; i + + )
{
if ( temp [ i ] )
{
2016-11-13 12:38:42 +02:00
dest . insert ( static_cast < Indentifier > ( i ) ) ;
2013-02-07 20:02:15 +03:00
}
}
}
2013-02-05 00:58:42 +03:00
void CMapLoaderH3M : : readBitmask ( std : : vector < bool > & dest , const int byteCount , const int limit , bool negate )
{
for ( int byte = 0 ; byte < byteCount ; + + byte )
{
2013-02-07 20:02:15 +03:00
const ui8 mask = reader . readUInt8 ( ) ;
2013-02-05 00:58:42 +03:00
for ( int bit = 0 ; bit < 8 ; + + bit )
{
if ( byte * 8 + bit < limit )
{
const bool flag = mask & ( 1 < < bit ) ;
2017-10-29 17:23:30 +02:00
if ( ( negate & & flag ) | | ( ! negate & & ! flag ) ) // FIXME: check PR388
2013-02-05 00:58:42 +03:00
dest [ byte * 8 + bit ] = false ;
}
}
}
}
2013-02-04 13:29:59 +03:00
ui8 CMapLoaderH3M : : reverse ( ui8 arg )
{
ui8 ret = 0 ;
for ( int i = 0 ; i < 8 ; + + i )
{
if ( ( arg & ( 1 < < i ) ) > > i )
{
ret | = ( 128 > > i ) ;
}
}
return ret ;
}
2016-11-13 12:38:42 +02:00
void CMapLoaderH3M : : afterRead ( )
{
//convert main town positions for all players to actual object position, in H3M it is position of active tile
for ( auto & p : map - > players )
{
int3 posOfMainTown = p . posOfMainTown ;
if ( posOfMainTown . valid ( ) & & map - > isInTheMap ( posOfMainTown ) )
{
const TerrainTile & t = map - > getTile ( posOfMainTown ) ;
const CGObjectInstance * mainTown = nullptr ;
for ( auto obj : t . visitableObjects )
{
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