1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-20 20:23:03 +02:00
vcmi/lib/mapping/MapFormatH3M.cpp
Arseniy Shestakov 10dbbead2d Fix indentation of logging code and around it
That wouldn't be as big issue if problem affected few files, but it everywhere in codebase.
Fixed it everywhere since in most files that is the only code with wrong indentation.
2016-03-12 04:46:21 +03:00

2209 lines
56 KiB
C++

/*
* 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>
#include "MapFormatH3M.h"
#include "CMap.h"
#include "../CStopWatch.h"
#include "../filesystem/Filesystem.h"
#include "../spells/CSpellHandler.h"
#include "../CCreatureHandler.h"
#include "../CGeneralTextHandler.h"
#include "../CHeroHandler.h"
#include "../mapObjects/CObjectClassesHandler.h"
#include "../mapObjects/MapObjects.h"
#include "../VCMI_Lib.h"
#include "../NetPacksBase.h"
const bool CMapLoaderH3M::IS_PROFILING_ENABLED = false;
CMapLoaderH3M::CMapLoaderH3M(CInputStream * stream) : map(nullptr), reader(stream),inputStream(stream)
{
}
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();
return std::unique_ptr<CMap>(dynamic_cast<CMap *>(mapHeader.release()));;
}
std::unique_ptr<CMapHeader> CMapLoaderH3M::loadMapHeader()
{
// Read header
mapHeader = make_unique<CMapHeader>();
readHeader();
return std::move(mapHeader);
}
void CMapLoaderH3M::init()
{
//FIXME: get rid of double input process
si64 temp_size = inputStream->getSize();
inputStream->seek(0);
auto temp_buffer = new ui8[temp_size];
inputStream->read(temp_buffer,temp_size);
// Compute checksum
boost::crc_32_type result;
result.process_bytes(temp_buffer, temp_size);
map->checksum = result.checksum();
delete [] temp_buffer;
inputStream->seek(0);
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()));
map->allHeroes.resize(map->allowedHeroes.size());
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)
{
for(MapLoadingTime & mlt : times)
{
logGlobal->debugStream() << "\tReading " << mlt.name << " took " << mlt.time << " ms.";
}
}
map->calculateGuardingGreaturePositions();
}
void CMapLoaderH3M::readHeader()
{
// Check map for validity
// Note: disabled, causes decompression of the entire file ( = SLOW)
//if(inputStream->getSize() < 50)
//{
// throw std::runtime_error("Corrupted map file.");
//}
// Map version
mapHeader->version = (EMapFormat::EMapFormat)(reader.readUInt32());
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,...
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();
if(mapHeader->version != EMapFormat::ROE)
{
mapHeader->levelLimit = reader.readUInt8();
}
else
{
mapHeader->levelLimit = 0;
}
readPlayerInfo();
readVictoryLossConditions();
readTeamInfo();
readAllowedHeroes();
}
void CMapLoaderH3M::readPlayerInfo()
{
for(int i = 0; i < mapHeader->players.size(); ++i)
{
mapHeader->players[i].canHumanPlay = reader.readBool();
mapHeader->players[i].canComputerPlay = reader.readBool();
// 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:
reader.skip(13);
break;
case EMapFormat::AB:
reader.skip(12);
break;
case EMapFormat::ROE:
reader.skip(6);
break;
}
continue;
}
mapHeader->players[i].aiTactic = static_cast<EAiTactic::EAiTactic>(reader.readUInt8());
if(mapHeader->version == EMapFormat::SOD || mapHeader->version == EMapFormat::WOG)
{
mapHeader->players[i].p7 = reader.readUInt8();
}
else
{
mapHeader->players[i].p7 = -1;
}
// Factions this player can choose
ui16 allowedFactions = reader.readUInt8();
// How many factions will be read from map
ui16 totalFactions = GameConstants::F_NUMBER;
if(mapHeader->version != EMapFormat::ROE)
allowedFactions += reader.readUInt8() * 256;
else
totalFactions--; //exclude conflux for ROE
for(int fact = 0; fact < totalFactions; ++fact)
{
if(!(allowedFactions & (1 << fact)))
{
mapHeader->players[i].allowedFactions.erase(fact);
}
}
mapHeader->players[i].isFactionRandom = reader.readBool();
mapHeader->players[i].hasMainTown = reader.readBool();
if(mapHeader->players[i].hasMainTown)
{
if(mapHeader->version != EMapFormat::ROE)
{
mapHeader->players[i].generateHeroAtMainTown = reader.readBool();
mapHeader->players[i].generateHero = reader.readBool();
}
else
{
mapHeader->players[i].generateHeroAtMainTown = true;
mapHeader->players[i].generateHero = false;
}
mapHeader->players[i].posOfMainTown = readInt3();
}
mapHeader->players[i].hasRandomHero = reader.readBool();
mapHeader->players[i].mainCustomHeroId = reader.readUInt8();
if(mapHeader->players[i].mainCustomHeroId != 0xff)
{
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
mapHeader->players[i].mainCustomHeroName = reader.readString();
}
else
mapHeader->players[i].mainCustomHeroId = -1; //correct 1-byte -1 (0xff) into 4-byte -1
if(mapHeader->version != EMapFormat::ROE)
{
mapHeader->players[i].powerPlaceholders = reader.readUInt8(); //unknown byte
int heroCount = reader.readUInt8();
reader.skip(3);
for(int pp = 0; pp < heroCount; ++pp)
{
SHeroName vv;
vv.heroId = reader.readUInt8();
vv.heroName = reader.readString();
mapHeader->players[i].heroesNames.push_back(vv);
}
}
}
}
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 };
}
void CMapLoaderH3M::readVictoryLossConditions()
{
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";
standardVictory.description = ""; // TODO: display in quest window
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";
standardDefeat.description = ""; // TODO: display in quest window
standardDefeat.onFulfill = VLC->generaltexth->allTexts[7];
standardDefeat.trigger = EventExpression(defeatCondition);
// Specific victory conditions
if(vicCondition == EVictoryConditionType::WINSTANDARD)
{
// create normal condition
mapHeader->triggeredEvents.push_back(standardVictory);
mapHeader->victoryIconIndex = 11;
mapHeader->victoryMessage = VLC->generaltexth->victoryConditions[0];
}
else
{
TriggeredEvent specialVictory;
specialVictory.effect.type = EventEffect::VICTORY;
specialVictory.identifier = "specialVictory";
specialVictory.description = ""; // TODO: display in quest window
mapHeader->victoryIconIndex = ui16(vicCondition);
mapHeader->victoryMessage = VLC->generaltexth->victoryConditions[size_t(vicCondition) + 1];
bool allowNormalVictory = reader.readBool();
bool appliesToAI = reader.readBool();
if (allowNormalVictory)
{
size_t playersOnMap = boost::range::count_if(mapHeader->players,[](const PlayerInfo & info) { return info.canAnyonePlay();});
if (playersOnMap == 1)
{
logGlobal->warnStream() << "Map " << mapHeader->name << " has only one player but allows normal victory?";
allowNormalVictory = false; // makes sense? Not much. Works as H3? Yes!
}
}
switch(vicCondition)
{
case EVictoryConditionType::ARTIFACT:
{
EventCondition cond(EventCondition::HAVE_ARTIFACT);
cond.objectType = reader.readUInt8();
if (mapHeader->version != EMapFormat::ROE)
reader.skip(1);
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[281];
specialVictory.onFulfill = VLC->generaltexth->allTexts[280];
specialVictory.trigger = EventExpression(cond);
break;
}
case EVictoryConditionType::GATHERTROOP:
{
EventCondition cond(EventCondition::HAVE_CREATURES);
cond.objectType = reader.readUInt8();
if (mapHeader->version != EMapFormat::ROE)
reader.skip(1);
cond.value = reader.readUInt32();
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[277];
specialVictory.onFulfill = VLC->generaltexth->allTexts[276];
specialVictory.trigger = EventExpression(cond);
break;
}
case EVictoryConditionType::GATHERRESOURCE:
{
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);
break;
}
case EVictoryConditionType::BUILDCITY:
{
EventExpression::OperatorAll oper;
EventCondition cond(EventCondition::HAVE_BUILDING);
cond.position = readInt3();
cond.objectType = BuildingID::VILLAGE_HALL + reader.readUInt8();
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);
break;
}
case EVictoryConditionType::BUILDGRAIL:
{
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);
break;
}
case EVictoryConditionType::BEATHERO:
{
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;
}
case EVictoryConditionType::CAPTURECITY:
{
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;
}
case EVictoryConditionType::BEATMONSTER:
{
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);
break;
}
case EVictoryConditionType::TAKEDWELLINGS:
{
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));
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[289];
specialVictory.onFulfill = VLC->generaltexth->allTexts[288];
specialVictory.trigger = EventExpression(oper);
break;
}
case EVictoryConditionType::TAKEMINES:
{
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);
break;
}
case EVictoryConditionType::TRANSPORTITEM:
{
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);
break;
}
default:
assert(0);
}
// 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);
}
// Read loss conditions
auto lossCond = (ELossConditionType::ELossConditionType)reader.readUInt8();
if (lossCond == ELossConditionType::LOSSSTANDARD)
{
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";
specialDefeat.description = ""; // TODO: display in quest window
mapHeader->defeatIconIndex = ui16(lossCond);
mapHeader->defeatMessage = VLC->generaltexth->lossCondtions[size_t(lossCond) + 1];
switch(lossCond)
{
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;
}
}
// 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);
}
mapHeader->triggeredEvents.push_back(standardDefeat);
}
void CMapLoaderH3M::readTeamInfo()
{
mapHeader->howManyTeams = reader.readUInt8();
if(mapHeader->howManyTeams > 0)
{
// Teams
for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; ++i)
{
mapHeader->players[i].team = TeamID(reader.readUInt8());
}
}
else
{
// No alliances
for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
{
if(mapHeader->players[i].canComputerPlay || mapHeader->players[i].canHumanPlay)
{
mapHeader->players[i].team = TeamID(mapHeader->howManyTeams++);
}
}
}
}
void CMapLoaderH3M::readAllowedHeroes()
{
mapHeader->allowedHeroes.resize(VLC->heroh->heroes.size(), true);
const int bytes = mapHeader->version == EMapFormat::ROE ? 16 : 20;
readBitmask(mapHeader->allowedHeroes,bytes,GameConstants::HEROES_QUANTITY, false);
// Probably reserved for further heroes
if(mapHeader->version > EMapFormat::ROE)
{
int placeholdersQty = reader.readUInt32();
reader.skip(placeholdersQty * 1);
// std::vector<ui16> placeholdedHeroes;
//
// for(int p = 0; p < placeholdersQty; ++p)
// {
// placeholdedHeroes.push_back(reader.readUInt8());
// }
}
}
void CMapLoaderH3M::readDisposedHeroes()
{
// Reading disposed heroes (20 bytes)
if(map->version >= EMapFormat::SOD)
{
ui8 disp = reader.readUInt8();
map->disposedHeroes.resize(disp);
for(int g = 0; g < disp; ++g)
{
map->disposedHeroes[g].heroId = reader.readUInt8();
map->disposedHeroes[g].portrait = reader.readUInt8();
map->disposedHeroes[g].name = reader.readString();
map->disposedHeroes[g].players = reader.readUInt8();
}
}
//omitting NULLS
reader.skip(31);
}
void CMapLoaderH3M::readAllowedArtifacts()
{
map->allowedArtifact.resize (VLC->arth->artifacts.size(),true); //handle new artifacts, make them allowed by default
// Reading allowed artifacts: 17 or 18 bytes
if(map->version != EMapFormat::ROE)
{
const int bytes = map->version == EMapFormat::AB ? 17 : 18;
readBitmask(map->allowedArtifact,bytes,GameConstants::ARTIFACTS_QUANTITY);
}
// ban combo artifacts
if (map->version == EMapFormat::ROE || map->version == EMapFormat::AB)
{
for(CArtifact * artifact : VLC->arth->artifacts)
{
// combo
if (artifact->constituents)
{
map->allowedArtifact[artifact->id] = false;
}
}
if (map->version == EMapFormat::ROE)
{
map->allowedArtifact[ArtifactID::ARMAGEDDONS_BLADE] = false;
}
}
// Messy, but needed
for (TriggeredEvent & event : map->triggeredEvents)
{
auto patcher = [&](EventCondition cond) -> EventExpression::Variant
{
if (cond.condition == EventCondition::HAVE_ARTIFACT ||
cond.condition == EventCondition::TRANSPORT)
{
map->allowedArtifact[cond.objectType] = false;
}
return cond;
};
event.trigger = event.trigger.morph(patcher);
}
}
void CMapLoaderH3M::readAllowedSpellsAbilities()
{
// Read allowed spells, including new ones
map->allowedSpell.resize(VLC->spellh->objects.size(), true);
// Read allowed abilities
map->allowedAbilities.resize(GameConstants::SKILL_QUANTITY, true);
if(map->version >= EMapFormat::SOD)
{
// Reading allowed spells (9 bytes)
const int spell_bytes = 9;
readBitmask(map->allowedSpell, spell_bytes, GameConstants::SPELLS_QUANTITY);
// Allowed hero's abilities (4 bytes)
const int abil_bytes = 4;
readBitmask(map->allowedAbilities, abil_bytes, GameConstants::SKILL_QUANTITY);
}
//do not generate special abilities and spells
for (auto spell : VLC->spellh->objects)
if (spell->isSpecialSpell() || spell->isCreatureAbility())
map->allowedSpell[spell->id] = false;
}
void CMapLoaderH3M::readRumors()
{
int rumNr = reader.readUInt32();
for(int it = 0; it < rumNr; it++)
{
Rumor ourRumor;
ourRumor.name = reader.readString();
ourRumor.text = reader.readString();
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++)
{
int custom = reader.readUInt8();
if(!custom) continue;
auto hero = new CGHeroInstance();
hero->ID = Obj::HERO;
hero->subID = z;
bool hasExp = reader.readBool();
if(hasExp)
{
hero->exp = reader.readUInt32();
}
else
{
hero->exp = 0;
}
bool hasSecSkills = reader.readBool();
if(hasSecSkills)
{
int howMany = reader.readUInt32();
hero->secSkills.resize(howMany);
for(int yy = 0; yy < howMany; ++yy)
{
hero->secSkills[yy].first = SecondarySkill(reader.readUInt8());
hero->secSkills[yy].second = reader.readUInt8();
}
}
loadArtifactsOfHero(hero);
bool hasCustomBio = reader.readBool();
if(hasCustomBio)
{
hero->biography = reader.readString();
}
// 0xFF is default, 00 male, 01 female
hero->sex = reader.readUInt8();
bool hasCustomSpells = reader.readBool();
if(hasCustomSpells)
{
readSpells(hero->spells);
}
bool hasCustomPrimSkills = reader.readBool();
if(hasCustomPrimSkills)
{
for(int xx = 0; xx < GameConstants::PRIMARY_SKILLS; xx++)
{
hero->pushPrimSkill(static_cast<PrimarySkill::PrimarySkill>(xx), reader.readUInt8());
}
}
map->predefinedHeroes.push_back(hero);
}
break;
}
case EMapFormat::ROE:
break;
}
}
void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero)
{
bool artSet = reader.readBool();
// True if artifact set is not default (hero has some artifacts)
if(artSet)
{
if(hero->artifactsWorn.size() || hero->artifactsInBackpack.size())
{
logGlobal->warnStream() << boost::format("Hero %s at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...") % hero->name % hero->pos;
hero->artifactsInBackpack.clear();
while(hero->artifactsWorn.size())
hero->eraseArtSlot(hero->artifactsWorn.begin()->first);
}
for(int pom = 0; pom < 16; pom++)
{
loadArtifactToSlot(hero, pom);
}
// misc5 art //17
if(map->version >= EMapFormat::SOD)
{
assert(!hero->getArt(ArtifactPosition::MACH4));
if(!loadArtifactToSlot(hero, ArtifactPosition::MACH4))
{
// catapult by default
assert(!hero->getArt(ArtifactPosition::MACH4));
hero->putArtifact(ArtifactPosition::MACH4, CArtifactInstance::createArtifact(map, ArtifactID::CATAPULT));
}
}
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
{
reader.skip(1);
}
// bag artifacts //20
// number of artifacts in hero's bag
int amount = reader.readUInt16();
for(int ss = 0; ss < amount; ++ss)
{
loadArtifactToSlot(hero, GameConstants::BACKPACK_START + hero->artifactsInBackpack.size());
}
}
}
bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot)
{
const int artmask = map->version == EMapFormat::ROE ? 0xff : 0xffff;
int aid;
if(map->version == EMapFormat::ROE)
{
aid = reader.readUInt8();
}
else
{
aid = reader.readUInt16();
}
bool isArt = aid != artmask;
if(isArt)
{
if(vstd::contains(VLC->arth->bigArtifacts, aid) && slot >= GameConstants::BACKPACK_START)
{
logGlobal->warnStream() << "Warning: A big artifact (war machine) in hero's backpack, ignoring...";
return false;
}
if(aid == 0 && slot == ArtifactPosition::MISC5)
{
//TODO: check how H3 handles it -> art 0 in slot 18 in AB map
logGlobal->warnStream() << "Spellbook to MISC5 slot? Putting it spellbook place. AB format peculiarity ? (format "
<< static_cast<int>(map->version) << ")";
slot = ArtifactPosition::SPELLBOOK;
}
// this is needed, because some H3M maps (last scenario of ROE map) contain invalid data like misplaced artifacts
auto artifact = CArtifactInstance::createArtifact(map, aid);
auto artifactPos = ArtifactPosition(slot);
if (artifact->canBePutAt(ArtifactLocation(hero, artifactPos)))
{
hero->putArtifact(artifactPos, artifact);
}
else
{
logGlobal->debugStream() << "Artifact can't be put at the specified location."; //TODO add more debugging information
}
}
return isArt;
}
void CMapLoaderH3M::readTerrain()
{
map->initTerrain();
// Read terrain
for(int a = 0; a < 2; ++a)
{
if(a == 1 && !map->twoLevel)
{
break;
}
for(int c = 0; c < map->width; c++)
{
for(int z = 0; z < map->height; z++)
{
auto & tile = map->getTile(int3(z, c, a));
tile.terType = ETerrainType(reader.readUInt8());
tile.terView = reader.readUInt8();
tile.riverType = static_cast<ERiverType::ERiverType>(reader.readUInt8());
tile.riverDir = reader.readUInt8();
tile.roadType = static_cast<ERoadType::ERoadType>(reader.readUInt8());
tile.roadDir = reader.readUInt8();
tile.extTileFlags = reader.readUInt8();
tile.blocked = ((tile.terType == ETerrainType::ROCK || tile.terType == ETerrainType::BORDER ) ? true : false); //underground tiles are always blocked
tile.visitable = 0;
}
}
}
}
void CMapLoaderH3M::readDefInfo()
{
int defAmount = reader.readUInt32();
templates.reserve(defAmount);
// Read custom defs
for(int idd = 0; idd < defAmount; ++idd)
{
ObjectTemplate tmpl;
tmpl.readMap(reader);
templates.push_back(tmpl);
}
}
void CMapLoaderH3M::readObjects()
{
int howManyObjs = reader.readUInt32();
for(int ww = 0; ww < howManyObjs; ++ww)
{
CGObjectInstance * nobj = nullptr;
int3 objPos = readInt3();
int defnum = reader.readUInt32();
ObjectInstanceID idToBeGiven = ObjectInstanceID(map->objects.size());
ObjectTemplate & objTempl = templates.at(defnum);
reader.skip(5);
switch(objTempl.id)
{
case Obj::EVENT:
{
auto evnt = new CGEvent();
nobj = evnt;
readMessageAndGuards(evnt->message, evnt);
evnt->gainedExp = reader.readUInt32();
evnt->manaDiff = reader.readUInt32();
evnt->moraleDiff = reader.readInt8();
evnt->luckDiff = reader.readInt8();
readResourses(evnt->resources);
evnt->primskills.resize(GameConstants::PRIMARY_SKILLS);
for(int x = 0; x < 4; ++x)
{
evnt->primskills[x] = static_cast<PrimarySkill::PrimarySkill>(reader.readUInt8());
}
int gabn = reader.readUInt8(); // Number of gained abilities
for(int oo = 0; oo < gabn; ++oo)
{
evnt->abilities.push_back(SecondarySkill(reader.readUInt8()));
evnt->abilityLevels.push_back(reader.readUInt8());
}
int gart = reader.readUInt8(); // Number of gained artifacts
for(int oo = 0; oo < gart; ++oo)
{
if(map->version == EMapFormat::ROE)
{
evnt->artifacts.push_back(ArtifactID(reader.readUInt8()));
}
else
{
evnt->artifacts.push_back(ArtifactID(reader.readUInt16()));
}
}
int gspel = reader.readUInt8(); // Number of gained spells
for(int oo = 0; oo < gspel; ++oo)
{
evnt->spells.push_back(SpellID(reader.readUInt8()));
}
int gcre = reader.readUInt8(); //number of gained creatures
readCreatureSet(&evnt->creatures, gcre);
reader.skip(8);
evnt->availableFor = reader.readUInt8();
evnt->computerActivate = reader.readUInt8();
evnt->removeAfterVisit = reader.readUInt8();
evnt->humanActivate = true;
reader.skip(4);
break;
}
case Obj::HERO:
case Obj::RANDOM_HERO:
case Obj::PRISON:
{
nobj = readHero(idToBeGiven, objPos);
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:
{
auto cre = new CGCreature();
nobj = cre;
if(map->version > EMapFormat::ROE)
{
cre->identifier = reader.readUInt32();
map->questIdentifierToId[cre->identifier] = idToBeGiven;
}
auto hlp = new CStackInstance();
hlp->count = reader.readUInt16();
//type will be set during initialization
cre->putStack(SlotID(0), hlp);
cre->character = reader.readUInt8();
bool hasMessage = reader.readBool();
if(hasMessage)
{
cre->message = reader.readString();
readResourses(cre->resources);
int artID;
if (map->version == EMapFormat::ROE)
{
artID = reader.readUInt8();
}
else
{
artID = reader.readUInt16();
}
if(map->version == EMapFormat::ROE)
{
if(artID != 0xff)
{
cre->gainedArtifact = ArtifactID(artID);
}
else
{
cre->gainedArtifact = ArtifactID::NONE;
}
}
else
{
if(artID != 0xffff)
{
cre->gainedArtifact = ArtifactID(artID);
}
else
{
cre->gainedArtifact = ArtifactID::NONE;
}
}
}
cre->neverFlees = reader.readUInt8();
cre->notGrowingTeam =reader.readUInt8();
reader.skip(2);
break;
}
case Obj::OCEAN_BOTTLE:
case Obj::SIGN:
{
auto sb = new CGSignBottle();
nobj = sb;
sb->message = reader.readString();
reader.skip(4);
break;
}
case Obj::SEER_HUT:
{
nobj = readSeerHut();
map->addQuest(nobj);
break;
}
case Obj::WITCH_HUT:
{
auto wh = new CGWitchHut();
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)
{
ui8 c = reader.readUInt8();
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);
}
}
}
}
}
else
{
// RoE map
for(int gg = 0; gg < GameConstants::SKILL_QUANTITY; ++gg)
{
wh->allowedAbilities.push_back(gg);
}
}
break;
}
case Obj::SCHOLAR:
{
auto sch = new CGScholar();
nobj = sch;
sch->bonusType = static_cast<CGScholar::EBonusType>(reader.readUInt8());
sch->bonusID = reader.readUInt8();
reader.skip(6);
break;
}
case Obj::GARRISON:
case Obj::GARRISON2:
{
auto gar = new CGGarrison();
nobj = gar;
nobj->setOwner(PlayerColor(reader.readUInt8()));
reader.skip(3);
readCreatureSet(gar, 7);
if(map->version > EMapFormat::ROE)
{
gar->removableUnits = reader.readBool();
}
else
{
gar->removableUnits = true;
}
reader.skip(8);
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:
{
int artID = ArtifactID::NONE; //random, set later
int spellID = -1;
auto art = new CGArtifact();
nobj = art;
readMessageAndGuards(art->message, art);
if(objTempl.id == Obj::SPELL_SCROLL)
{
spellID = reader.readUInt32();
artID = ArtifactID::SPELL_SCROLL;
}
else if(objTempl.id == Obj::ARTIFACT)
{
//specific artifact
artID = objTempl.subid;
}
art->storedArtifact = CArtifactInstance::createArtifact(map, artID, spellID);
break;
}
case Obj::RANDOM_RESOURCE:
case Obj::RESOURCE:
{
auto res = new CGResource();
nobj = res;
readMessageAndGuards(res->message, res);
res->amount = reader.readUInt32();
if(objTempl.subid == Res::GOLD)
{
// Gold is multiplied by 100.
res->amount *= 100;
}
reader.skip(4);
break;
}
case Obj::RANDOM_TOWN:
case Obj::TOWN:
{
nobj = readTown(objTempl.subid);
break;
}
case Obj::MINE:
case Obj::ABANDONED_MINE:
{
nobj = new CGMine();
nobj->setOwner(PlayerColor(reader.readUInt8()));
reader.skip(3);
break;
}
case Obj::CREATURE_GENERATOR1:
case Obj::CREATURE_GENERATOR2:
case Obj::CREATURE_GENERATOR3:
case Obj::CREATURE_GENERATOR4:
{
nobj = new CGDwelling();
nobj->setOwner(PlayerColor(reader.readUInt8()));
reader.skip(3);
break;
}
case Obj::SHRINE_OF_MAGIC_INCANTATION:
case Obj::SHRINE_OF_MAGIC_GESTURE:
case Obj::SHRINE_OF_MAGIC_THOUGHT:
{
auto shr = new CGShrine();
nobj = shr;
ui8 raw_id = reader.readUInt8();
if (255 == raw_id)
{
shr->spell = SpellID(SpellID::NONE);
}
else
{
shr->spell = SpellID(raw_id);
}
reader.skip(3);
break;
}
case Obj::PANDORAS_BOX:
{
auto box = new CGPandoraBox();
nobj = box;
readMessageAndGuards(box->message, box);
box->gainedExp = reader.readUInt32();
box->manaDiff = reader.readUInt32();
box->moraleDiff = reader.readInt8();
box->luckDiff = reader.readInt8();
readResourses(box->resources);
box->primskills.resize(GameConstants::PRIMARY_SKILLS);
for(int x = 0; x < 4; ++x)
{
box->primskills[x] = static_cast<PrimarySkill::PrimarySkill>(reader.readUInt8());
}
int gabn = reader.readUInt8();//number of gained abilities
for(int oo = 0; oo < gabn; ++oo)
{
box->abilities.push_back(SecondarySkill(reader.readUInt8()));
box->abilityLevels.push_back(reader.readUInt8());
}
int gart = reader.readUInt8(); //number of gained artifacts
for(int oo = 0; oo < gart; ++oo)
{
if(map->version > EMapFormat::ROE)
{
box->artifacts.push_back(ArtifactID(reader.readUInt16()));
}
else
{
box->artifacts.push_back(ArtifactID(reader.readUInt8()));
}
}
int gspel = reader.readUInt8(); //number of gained spells
for(int oo = 0; oo < gspel; ++oo)
{
box->spells.push_back(SpellID(reader.readUInt8()));
}
int gcre = reader.readUInt8(); //number of gained creatures
readCreatureSet(&box->creatures, gcre);
reader.skip(8);
break;
}
case Obj::GRAIL:
{
map->grailPos = objPos;
map->grailRadius = reader.readUInt32();
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
{
nobj = new CGDwelling();
CSpecObjInfo * spec = nullptr;
switch(objTempl.id)
{
break; 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();
}
spec->player = PlayerColor(reader.readUInt32());
//216 and 217
if (auto castleSpec = dynamic_cast<CCreGenAsCastleInfo *>(spec))
{
castleSpec->identifier = reader.readUInt32();
if(!castleSpec->identifier)
{
castleSpec->asCastle = false;
castleSpec->castles[0] = reader.readUInt8();
castleSpec->castles[1] = reader.readUInt8();
}
else
{
castleSpec->asCastle = true;
}
}
//216 and 218
if (auto lvlSpec = dynamic_cast<CCreGenLeveledInfo *>(spec))
{
lvlSpec->minLevel = std::max(reader.readUInt8(), ui8(1));
lvlSpec->maxLevel = std::min(reader.readUInt8(), ui8(7));
}
nobj->setOwner(spec->player);
static_cast<CGDwelling *>(nobj)->info = spec;
break;
}
case Obj::QUEST_GUARD:
{
auto guard = new CGQuestGuard();
map->addQuest(guard);
readQuest(guard);
nobj = guard;
break;
}
case Obj::SHIPYARD:
{
nobj = new CGShipyard();
nobj->setOwner(PlayerColor(reader.readUInt32()));
break;
}
case Obj::HERO_PLACEHOLDER: //hero placeholder
{
auto hp = new CGHeroPlaceholder();
nobj = hp;
hp->setOwner(PlayerColor(reader.readUInt8()));
int htid = reader.readUInt8();; //hero type id
nobj->subID = htid;
if(htid == 0xff)
{
hp->power = reader.readUInt8();
logGlobal->infoStream() << "Hero placeholder: by power at " << objPos;
}
else
{
logGlobal->infoStream() << "Hero placeholder: " << VLC->heroh->heroes[htid]->name << " at " << objPos;
hp->power = 0;
}
break;
}
case Obj::BORDERGUARD:
{
nobj = new CGBorderGuard();
map->addQuest(nobj);
break;
}
case Obj::BORDER_GATE:
{
nobj = new CGBorderGate();
map->addQuest (nobj);
break;
}
case Obj::PYRAMID: //Pyramid of WoG object
{
if(objTempl.subid == 0)
{
nobj = new CBank();
}
else
{
//WoG object
//TODO: possible special handling
nobj = new CGObjectInstance();
}
break;
}
case Obj::LIGHTHOUSE: //Lighthouse
{
nobj = new CGLighthouse();
nobj->tempOwner = PlayerColor(reader.readUInt32());
break;
}
default: //any other object
{
if (VLC->objtypeh->knownSubObjects(objTempl.id).count(objTempl.subid))
{
nobj = VLC->objtypeh->getHandlerFor(objTempl.id, objTempl.subid)->create(objTempl);
}
else
{
logGlobal->warnStream() << "Unrecognized object: " << objTempl.id << ":" << objTempl.subid << " at " << objPos
<< " on map " << map->name;
nobj = new CGObjectInstance();
}
break;
}
}
nobj->pos = objPos;
nobj->ID = objTempl.id;
nobj->id = idToBeGiven;
if(nobj->ID != Obj::HERO && nobj->ID != Obj::HERO_PLACEHOLDER && nobj->ID != Obj::PRISON)
{
nobj->subID = objTempl.subid;
}
nobj->appearance = objTempl;
assert(idToBeGiven == ObjectInstanceID(map->objects.size()));
{
//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();
}
map->addNewObject(nobj);
}
std::sort(map->heroesOnMap.begin(), map->heroesOnMap.end(), [](const ConstTransitivePtr<CGHeroInstance> & a, const ConstTransitivePtr<CGHeroInstance> & b)
{
return a->subID < b->subID;
});
}
void CMapLoaderH3M::readCreatureSet(CCreatureSet * out, int number)
{
const bool version = (map->version > EMapFormat::ROE);
const int maxID = version ? 0xffff : 0xff;
for(int ir = 0; ir < number; ++ir)
{
CreatureID creID;
int count;
if (version)
{
creID = CreatureID(reader.readUInt16());
}
else
{
creID = CreatureID(reader.readUInt8());
}
count = reader.readUInt16();
// Empty slot
if(creID == maxID)
continue;
auto hlp = new CStackInstance();
hlp->count = count;
if(creID > maxID - 0xf)
{
//this will happen when random object has random army
hlp->idRand = maxID - creID - 1;
}
else
{
hlp->setType(creID);
}
out->putStack(SlotID(ir), hlp);
}
out->validTypes(true);
}
CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const int3 & initialPos)
{
auto nhi = new CGHeroInstance();
if(map->version > EMapFormat::ROE)
{
unsigned int identifier = reader.readUInt32();
map->questIdentifierToId[identifier] = idToBeGiven;
}
PlayerColor owner = PlayerColor(reader.readUInt8());
nhi->subID = reader.readUInt8();
assert(!nhi->getArt(ArtifactPosition::MACH4));
//If hero of this type has been predefined, use that as a base.
//Instance data will overwrite the predefined values where appropriate.
for(auto & elem : map->predefinedHeroes)
{
if(elem->subID == nhi->subID)
{
logGlobal->debugStream() << "Hero " << nhi->subID << " will be taken from the predefined heroes list.";
delete nhi;
nhi = elem;
break;
}
}
nhi->setOwner(owner);
nhi->portrait = nhi->subID;
for(auto & elem : map->disposedHeroes)
{
if(elem.heroId == nhi->subID)
{
nhi->name = elem.name;
nhi->portrait = elem.portrait;
break;
}
}
bool hasName = reader.readBool();
if(hasName)
{
nhi->name = reader.readString();
}
if(map->version > EMapFormat::AB)
{
bool hasExp = reader.readBool();
if(hasExp)
{
nhi->exp = reader.readUInt32();
}
else
{
nhi->exp = 0xffffffff;
}
}
else
{
nhi->exp = reader.readUInt32();
//0 means "not set" in <=AB maps
if(!nhi->exp)
{
nhi->exp = 0xffffffff;
}
}
bool hasPortrait = reader.readBool();
if(hasPortrait)
{
nhi->portrait = reader.readUInt8();
}
bool hasSecSkills = reader.readBool();
if(hasSecSkills)
{
if(nhi->secSkills.size())
{
nhi->secSkills.clear();
//logGlobal->warnStream() << boost::format("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;
}
int howMany = reader.readUInt32();
nhi->secSkills.resize(howMany);
for(int yy = 0; yy < howMany; ++yy)
{
nhi->secSkills[yy].first = SecondarySkill(reader.readUInt8());
nhi->secSkills[yy].second = reader.readUInt8();
}
}
bool hasGarison = reader.readBool();
if(hasGarison)
{
readCreatureSet(nhi, 7);
}
nhi->formation = reader.readUInt8();
loadArtifactsOfHero(nhi);
nhi->patrol.patrolRadius = reader.readUInt8();
if(nhi->patrol.patrolRadius == 0xff)
{
nhi->patrol.patrolling = false;
}
else
{
nhi->patrol.patrolling = true;
nhi->patrol.initialPos = CGHeroInstance::convertPosition(initialPos, false);
}
if(map->version > EMapFormat::ROE)
{
bool hasCustomBiography = reader.readBool();
if(hasCustomBiography)
{
nhi->biography = reader.readString();
}
nhi->sex = reader.readUInt8();
// Remove trash
if (nhi->sex != 0xFF)
{
nhi->sex &= 1;
}
}
else
{
nhi->sex = 0xFF;
}
// Spells
if(map->version > EMapFormat::AB)
{
bool hasCustomSpells = reader.readBool();
if(nhi->spells.size())
{
nhi->clear();
logGlobal->warnStream() << boost::format("Hero %s subID=%d has spells set twice (in map properties and on adventure map instance). Using the latter set...") % nhi->name % nhi->subID;
}
if(hasCustomSpells)
{
nhi->spells.insert(SpellID::PRESET); //placeholder "preset spells"
readSpells(nhi->spells);
}
}
else if(map->version == EMapFormat::AB)
{
//we can read one spell
ui8 buff = reader.readUInt8();
if(buff != 254)
{
nhi->spells.insert(SpellID::PRESET); //placeholder "preset spells"
if(buff < 254) //255 means no spells
{
nhi->spells.insert(SpellID(buff));
}
}
}
if(map->version > EMapFormat::AB)
{
bool hasCustomPrimSkills = reader.readBool();
if(hasCustomPrimSkills)
{
auto ps = nhi->getAllBonuses(Selector::type(Bonus::PRIMARY_SKILL)
.And(Selector::sourceType(Bonus::HERO_BASE_SKILL)), nullptr);
if(ps->size())
{
logGlobal->warnStream() << boost::format("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;
for(auto b : *ps)
nhi->removeBonus(b);
}
for(int xx = 0; xx < GameConstants::PRIMARY_SKILLS; ++xx)
{
nhi->pushPrimSkill(static_cast<PrimarySkill::PrimarySkill>(xx), reader.readUInt8());
}
}
}
reader.skip(16);
return nhi;
}
CGSeerHut * CMapLoaderH3M::readSeerHut()
{
auto hut = new CGSeerHut();
if(map->version > EMapFormat::ROE)
{
readQuest(hut);
}
else
{
//RoE
int artID = reader.readUInt8();
if (artID != 255)
{
//not none quest
hut->quest->m5arts.push_back (artID);
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)
{
auto rewardType = static_cast<CGSeerHut::ERewardType>(reader.readUInt8());
hut->rewardType = rewardType;
switch(rewardType)
{
case CGSeerHut::EXPERIENCE:
{
hut->rVal = reader.readUInt32();
break;
}
case CGSeerHut::MANA_POINTS:
{
hut->rVal = reader.readUInt32();
break;
}
case CGSeerHut::MORALE_BONUS:
{
hut->rVal = reader.readUInt8();
break;
}
case CGSeerHut::LUCK_BONUS:
{
hut->rVal = reader.readUInt8();
break;
}
case CGSeerHut::RESOURCES:
{
hut->rID = reader.readUInt8();
// Only the first 3 bytes are used. Skip the 4th.
hut->rVal = reader.readUInt32() & 0x00ffffff;
break;
}
case CGSeerHut::PRIMARY_SKILL:
{
hut->rID = reader.readUInt8();
hut->rVal = reader.readUInt8();
break;
}
case CGSeerHut::SECONDARY_SKILL:
{
hut->rID = reader.readUInt8();
hut->rVal = reader.readUInt8();
break;
}
case CGSeerHut::ARTIFACT:
{
if (map->version == EMapFormat::ROE)
{
hut->rID = reader.readUInt8();
}
else
{
hut->rID = reader.readUInt16();
}
break;
}
case CGSeerHut::SPELL:
{
hut->rID = reader.readUInt8();
break;
}
case CGSeerHut::CREATURE:
{
if(map->version > EMapFormat::ROE)
{
hut->rID = reader.readUInt16();
hut->rVal = reader.readUInt16();
}
else
{
hut->rID = reader.readUInt8();
hut->rVal = reader.readUInt16();
}
break;
}
}
reader.skip(2);
}
else
{
// missionType==255
reader.skip(3);
}
return hut;
}
void CMapLoaderH3M::readQuest(IQuestObject * guard)
{
guard->quest->missionType = static_cast<CQuest::Emission>(reader.readUInt8());
switch(guard->quest->missionType)
{
case 0:
return;
case 2:
{
guard->quest->m2stats.resize(4);
for(int x = 0; x < 4; ++x)
{
guard->quest->m2stats[x] = reader.readUInt8();
}
}
break;
case 1:
case 3:
case 4:
{
guard->quest->m13489val = reader.readUInt32();
break;
}
case 5:
{
int artNumber = reader.readUInt8();
for(int yy = 0; yy < artNumber; ++yy)
{
int artid = reader.readUInt16();
guard->quest->m5arts.push_back(artid);
map->allowedArtifact[artid] = false; //these are unavailable for random generation
}
break;
}
case 6:
{
int typeNumber = reader.readUInt8();
guard->quest->m6creatures.resize(typeNumber);
for(int hh = 0; hh < typeNumber; ++hh)
{
guard->quest->m6creatures[hh].type = VLC->creh->creatures[reader.readUInt16()];
guard->quest->m6creatures[hh].count = reader.readUInt16();
}
break;
}
case 7:
{
guard->quest->m7resources.resize(7);
for(int x = 0; x < 7; ++x)
{
guard->quest->m7resources[x] = reader.readUInt32();
}
break;
}
case 8:
case 9:
{
guard->quest->m13489val = reader.readUInt8();
break;
}
}
int limit = reader.readUInt32();
if(limit == (static_cast<int>(0xffffffff)))
{
guard->quest->lastDay = -1;
}
else
{
guard->quest->lastDay = limit;
}
guard->quest->firstVisitText = reader.readString();
guard->quest->nextVisitText = reader.readString();
guard->quest->completedText = reader.readString();
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)
{
auto nt = new CGTownInstance();
if(map->version > EMapFormat::ROE)
{
nt->identifier = reader.readUInt32();
}
nt->tempOwner = PlayerColor(reader.readUInt8());
bool hasName = reader.readBool();
if(hasName)
{
nt->name = reader.readString();
}
bool hasGarrison = reader.readBool();
if(hasGarrison)
{
readCreatureSet(nt, 7);
}
nt->formation = reader.readUInt8();
bool hasCustomBuildings = reader.readBool();
if(hasCustomBuildings)
{
readBitmask(nt->builtBuildings,6,48,false);
readBitmask(nt->forbiddenBuildings,6,48,false);
nt->builtBuildings = convertBuildings(nt->builtBuildings, castleID);
nt->forbiddenBuildings = convertBuildings(nt->forbiddenBuildings, castleID);
}
// Standard buildings
else
{
bool hasFort = reader.readBool();
if(hasFort)
{
nt->builtBuildings.insert(BuildingID::FORT);
}
//means that set of standard building should be included
nt->builtBuildings.insert(BuildingID::DEFAULT);
}
if(map->version > EMapFormat::ROE)
{
for(int i = 0; i < 9; ++i)
{
ui8 c = reader.readUInt8();
for(int yy = 0; yy < 8; ++yy)
{
if(i * 8 + yy < GameConstants::SPELLS_QUANTITY)
{
if(c == (c | static_cast<ui8>(std::pow(2., yy)))) //add obligatory spell even if it's banned on a map (?)
{
nt->obligatorySpells.push_back(SpellID(i * 8 + yy));
}
}
}
}
}
for(int i = 0; i < 9; ++i)
{
ui8 c = reader.readUInt8();
for(int yy = 0; yy < 8; ++yy)
{
int spellid = i * 8 + yy;
if(spellid < GameConstants::SPELLS_QUANTITY)
{
if(c != (c | static_cast<ui8>(std::pow(2., yy))) && map->allowedSpell[spellid]) //add random spell only if it's allowed on entire map
{
nt->possibleSpells.push_back(SpellID(spellid));
}
}
}
}
//add all spells from mods
//TODO: allow customize new spells in towns
for (int i = SpellID::AFTER_LAST; i < VLC->spellh->objects.size(); ++i)
{
nt->possibleSpells.push_back(SpellID(i));
}
// Read castle events
int numberOfEvent = reader.readUInt32();
for(int gh = 0; gh < numberOfEvent; ++gh)
{
CCastleEvent nce;
nce.town = nt;
nce.name = reader.readString();
nce.message = reader.readString();
readResourses(nce.resources);
nce.players = reader.readUInt8();
if(map->version > EMapFormat::AB)
{
nce.humanAffected = reader.readUInt8();
}
else
{
nce.humanAffected = true;
}
nce.computerAffected = reader.readUInt8();
nce.firstOccurence = reader.readUInt16();
nce.nextOccurence = reader.readUInt8();
reader.skip(17);
// New buildings
readBitmask(nce.buildings,6,48,false);
nce.buildings = convertBuildings(nce.buildings, castleID, false);
nce.creatures.resize(7);
for(int vv = 0; vv < 7; ++vv)
{
nce.creatures[vv] = reader.readUInt16();
}
reader.skip(4);
nt->events.push_back(nce);
}
if(map->version > EMapFormat::AB)
{
nt->alignment = reader.readUInt8();
}
reader.skip(3);
return nt;
}
std::set<BuildingID> CMapLoaderH3M::convertBuildings(const std::set<BuildingID> h3m, int castleID, bool addAuxiliary /*= true*/)
{
std::map<int, BuildingID> mapa;
std::set<BuildingID> ret;
// Note: this file is parsed many times.
const JsonNode config(ResourceID("config/buildings5.json"));
for(const JsonNode & entry : config["table"].Vector())
{
int town = entry["town"].Float();
if (town == castleID || town == -1)
{
mapa[entry["h3"].Float()] = BuildingID((si32)entry["vcmi"].Float());
}
}
for(auto & elem : h3m)
{
if(mapa[elem] >= 0)
{
ret.insert(mapa[elem]);
}
// horde buildings
else if(mapa[elem] >= (-GameConstants::CREATURES_PER_TOWN))
{
int level = (mapa[elem]);
//(-30)..(-36) - horde buildings (for game loading only), don't see other way to handle hordes in random towns
ret.insert(BuildingID(level - 30));
}
else
{
logGlobal->warnStream() << "Conversion warning: unknown building " << elem << " in castle "
<< castleID;
}
}
if(addAuxiliary)
{
//village hall is always present
ret.insert(BuildingID::VILLAGE_HALL);
}
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);
}
return ret;
}
void CMapLoaderH3M::readEvents()
{
int numberOfEvents = reader.readUInt32();
for(int yyoo = 0; yyoo < numberOfEvents; ++yyoo)
{
CMapEvent ne;
ne.name = reader.readString();
ne.message = reader.readString();
readResourses(ne.resources);
ne.players = reader.readUInt8();
if(map->version > EMapFormat::AB)
{
ne.humanAffected = reader.readUInt8();
}
else
{
ne.humanAffected = true;
}
ne.computerAffected = reader.readUInt8();
ne.firstOccurence = reader.readUInt16();
ne.nextOccurence = reader.readUInt8();
reader.skip(17);
map->events.push_back(ne);
}
}
void CMapLoaderH3M::readMessageAndGuards(std::string& message, CCreatureSet* guards)
{
bool hasMessage = reader.readBool();
if(hasMessage)
{
message = reader.readString();
bool hasGuards = reader.readBool();
if(hasGuards)
{
readCreatureSet(guards, 7);
}
reader.skip(4);
}
}
void CMapLoaderH3M::readSpells(std::set<SpellID>& dest)
{
readBitmask(dest,9,GameConstants::SPELLS_QUANTITY,false);
}
void CMapLoaderH3M::readResourses(TResources& resources)
{
resources.resize(GameConstants::RESOURCE_QUANTITY); //needed?
for(int x = 0; x < 7; ++x)
{
resources[x] = reader.readUInt32();
}
}
template <class Indenifier>
void CMapLoaderH3M::readBitmask(std::set<Indenifier>& dest, const int byteCount, const int limit, bool negate)
{
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])
{
dest.insert(static_cast<Indenifier>(i));
}
// else
// {
// dest.erase(static_cast<Indenifier>(i));
// }
}
}
void CMapLoaderH3M::readBitmask(std::vector<bool>& dest, const int byteCount, const int limit, bool negate)
{
for(int byte = 0; byte < byteCount; ++byte)
{
const ui8 mask = reader.readUInt8();
for(int bit = 0; bit < 8; ++bit)
{
if(byte * 8 + bit < limit)
{
const bool flag = mask & (1 << bit);
if((negate && flag) || (!negate && !flag))
dest[byte * 8 + bit] = false;
}
}
}
}
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;
}