1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-29 21:56:54 +02:00

Merge branch 'develop' into highscore_menu

This commit is contained in:
Laserlicht 2023-09-23 21:38:17 +02:00 committed by GitHub
commit f3e1943aaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 320 additions and 97 deletions

@ -393,7 +393,7 @@ void ArmyManager::update()
}
}
for(auto army : totalArmy)
for(auto & army : totalArmy)
{
army.second.creature = army.first.toCreature();
army.second.power = evaluateStackPower(army.second.creature, army.second.count);

70
config/difficulty.json Normal file

@ -0,0 +1,70 @@
//Configured difficulty
{
"human":
{
"pawn":
{
"resources": { "wood" : 30, "mercury": 15, "ore": 30, "sulfur": 15, "crystal": 15, "gems": 15, "gold": 30000, "mithril": 0 },
"globalBonuses": [],
"battleBonuses": []
},
"knight":
{
"resources": { "wood" : 20, "mercury": 10, "ore": 20, "sulfur": 10, "crystal": 10, "gems": 10, "gold": 20000, "mithril": 0 },
"globalBonuses": [],
"battleBonuses": []
},
"rook":
{
"resources": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 15000, "mithril": 0 },
"globalBonuses": [],
"battleBonuses": []
},
"queen":
{
"resources": { "wood" : 10, "mercury": 4, "ore": 10, "sulfur": 4, "crystal": 4, "gems": 4, "gold": 10000, "mithril": 0 },
"globalBonuses": [],
"battleBonuses": []
},
"king":
{
"resources": { "wood" : 0, "mercury": 0, "ore": 0 , "sulfur": 0, "crystal": 0, "gems": 0, "gold": 0, "mithril": 0 },
"globalBonuses": [],
"battleBonuses": []
}
},
"ai":
{
"pawn":
{
"resources": { "wood" : 5, "mercury": 2, "ore": 5, "sulfur": 2, "crystal": 2, "gems": 2, "gold": 5000, "mithril": 0 },
"globalBonuses": [],
"battleBonuses": []
},
"knight":
{
"resources": { "wood" : 10, "mercury": 4, "ore": 10, "sulfur": 4, "crystal": 4, "gems": 4, "gold": 7500, "mithril": 0 },
"globalBonuses": [],
"battleBonuses": []
},
"rook":
{
"resources": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 10000, "mithril": 0 },
"globalBonuses": [],
"battleBonuses": []
},
"queen":
{
"resources": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 10000, "mithril": 0 },
"globalBonuses": [],
"battleBonuses": []
},
"king":
{
"resources": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 10000, "mithril": 0 },
"globalBonuses": [],
"battleBonuses": []
}
},
}

@ -75,6 +75,46 @@
"victoryIconIndex" : 11,
"victoryString" : "core.vcdesc.0"
},
"data/evil2:0" : { // A Gryphon's Heart
"defeatIconIndex" : 2,
"defeatString" : "core.lcdesc.3",
"triggeredEvents" : {
"specialDefeat" : {
"condition" : [
"allOf",
[ "isHuman", { "value" : 1 } ],
[ "daysPassed", { "value" : 84 } ]
],
"effect" : {
"messageToSend" : "core.genrltxt.5",
"type" : "defeat"
},
"message" : "core.genrltxt.254"
},
"specialVictory" : {
"condition" : [
"allOf",
[ "isHuman", { "value" : 1 } ],
[ "transport", { "position" : [ 16, 23, 0 ], "type" : 84 } ]
],
"effect" : {
"messageToSend" : "core.genrltxt.293",
"type" : "victory"
},
"message" : "core.genrltxt.292"
},
"standardDefeat" : {
"condition" : [ "daysWithoutTown", { "value" : 7 } ],
"effect" : {
"messageToSend" : "core.genrltxt.8",
"type" : "defeat"
},
"message" : "core.genrltxt.7"
}
},
"victoryIconIndex" : 10,
"victoryString" : "core.vcdesc.11"
},
"data/secret1:0" : { // The Grail
"defeatIconIndex" : 2,
"defeatString" : "core.lcdesc.3",

@ -1,31 +0,0 @@
// Starting resources, ordered by difficulty level (0 to 4)
{
"difficulty":
[
{
"human": { "wood" : 30, "mercury": 15, "ore": 30, "sulfur": 15, "crystal": 15, "gems": 15, "gold": 30000, "mithril": 0 },
"ai": { "wood" : 5, "mercury": 2, "ore": 5, "sulfur": 2, "crystal": 2, "gems": 2, "gold": 5000, "mithril": 0 }
},
{
"human": { "wood" : 20, "mercury": 10, "ore": 20, "sulfur": 10, "crystal": 10, "gems": 10, "gold": 20000, "mithril": 0 },
"ai": { "wood" : 10, "mercury": 4, "ore": 10, "sulfur": 4, "crystal": 4, "gems": 4, "gold": 7500, "mithril": 0 }
},
{
"human": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 15000, "mithril": 0 },
"ai": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 10000, "mithril": 0 }
},
{
"human": { "wood" : 10, "mercury": 4, "ore": 10, "sulfur": 4, "crystal": 4, "gems": 4, "gold": 10000, "mithril": 0 },
"ai": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 10000, "mithril": 0 }
},
{
"human": { "wood" : 0, "mercury": 0, "ore": 0 , "sulfur": 0, "crystal": 0, "gems": 0, "gold": 0, "mithril": 0 },
"ai": { "wood" : 15, "mercury": 7, "ore": 15, "sulfur": 7, "crystal": 7, "gems": 7, "gold": 10000, "mithril": 0 }
}
]
}

@ -0,0 +1,67 @@
< [Documentation](../Readme.md) / [Modding](Readme.md) / Difficulty
Since VCMI 1.4.0 there are more capabilities to configure difficulty parameters.
It means, that modders can give different bonuses to AI or human players depending on selected difficulty
Difficulty configuration is located in [config/difficulty.json](../config/difficulty.json) file and can be overriden by mods.
## Format summary
``` javascript
{
"human": //parameters impacting human players only
{
"pawn": //parameters for specific difficulty
{
//starting resources
"resources": { "wood" : 30, "mercury": 15, "ore": 30, "sulfur": 15, "crystal": 15, "gems": 15, "gold": 30000, "mithril": 0 },
//bonuses will be given to player globaly
"globalBonuses": [],
//bonuses will be given to player every battle
"battleBonuses": []
},
"knight": {},
"rook": {},
"queen": {},
"king": {},
},
"ai": //parameters impacting AI players only
{
"pawn": {}, //parameters for specific difficulty
"knight": {},
"rook": {},
"queen": {},
"king": {},
}
}
```
## Bonuses
It's possible to specify bonuses of two types: `globalBonuses` and `battleBonuses`.
Both are arrays containing any amount of bonuses, each can be described as usual bonus. See details in [bonus documenation](Bonus_Format.md).
`globalBonuses` are given to player on the begining and depending on bonus configuration, it can behave diffierently.
`battleBonuses` are given to player during the battles, but *only for battles with neutral forces*. So it won't be provided to player for PvP battles and battles versus AI heroes/castles/garrisons. To avoid cumulative effects or unexpected behavior it's recommended to specify bonus `duration` as `ONE_BATTLE`.
For both types of bonuses, `source` should be specified as `OTHER`.
## Example
```js
{ //will give 150% extra health to all players' creatures if specified in "battleBonuses" array
"type" : "STACK_HEALTH",
"val" : 150,
"valueType" : "PERCENT_TO_ALL",
"duration" : "ONE_BATTLE",
"sourceType" : "OTHER"
},
```
## Compatibility
Starting from VCMI 1.4 `startres.json` is not available anymore and will be ignored if present in any mod.
Thus, `Resourceful AI` mod of version 1.2 won't work anymore.

@ -40,6 +40,7 @@ PlayerState::PlayerState(PlayerState && other) noexcept:
std::swap(towns, other.towns);
std::swap(dwellings, other.dwellings);
std::swap(quests, other.quests);
std::swap(battleBonuses, other.battleBonuses);
}
PlayerState::~PlayerState() = default;

@ -37,6 +37,7 @@ public:
std::vector<ConstTransitivePtr<CGTownInstance> > towns;
std::vector<ConstTransitivePtr<CGDwelling> > dwellings; //used for town growth
std::vector<QuestInfo> quests; //store info about all received quests
std::vector<Bonus> battleBonuses; //additional bonuses to be added during battle with neutrals
bool cheated;
bool enteredWinningCheatCode, enteredLosingCheatCode; //if true, this player has entered cheat codes for loss / victory
@ -84,6 +85,7 @@ public:
h & status;
h & daysWithoutCastle;
h & cheated;
h & battleBonuses;
h & enteredLosingCheatCode;
h & enteredWinningCheatCode;
h & static_cast<CBonusSystemNode&>(*this);

@ -388,12 +388,11 @@ void MetaString::jsonDeserialize(const JsonNode & source)
void MetaString::serializeJson(JsonSerializeFormat & handler)
{
JsonNode attr;
if(handler.saving)
jsonSerialize(attr);
handler.serializeRaw("attributes", attr, std::nullopt);
jsonSerialize(const_cast<JsonNode&>(handler.getCurrent()));
if(!handler.saving)
jsonDeserialize(attr);
jsonDeserialize(handler.getCurrent());
}
VCMI_LIB_NAMESPACE_END

@ -1573,7 +1573,7 @@ int32_t CBattleInfoCallback::battleGetSpellCost(const spells::Spell * sp, const
}
}
return ret - manaReduction + manaIncrease;
return std::max(0, ret - manaReduction + manaIncrease);
}
bool CBattleInfoCallback::battleHasShootingPenalty(const battle::Unit * shooter, BattleHex destHex) const

@ -27,6 +27,8 @@ namespace GameConstants
};
const std::string ALIGNMENT_NAMES [3] = {"good", "evil", "neutral"};
const std::string DIFFICULTY_NAMES [5] = {"pawn", "knight", "rook", "queen", "king"};
}
namespace NPrimarySkill

@ -29,6 +29,7 @@
#include "../VCMI_Lib.h"
#include "../battle/BattleInfo.h"
#include "../campaign/CampaignState.h"
#include "../constants/StringConstants.h"
#include "../filesystem/ResourcePath.h"
#include "../mapObjectConstructors/AObjectTypeHandler.h"
#include "../mapObjectConstructors/CObjectClassesHandler.h"
@ -455,7 +456,7 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, Load::Prog
initRandomFactionsForPlayers();
randomizeMapObjects();
placeStartingHeroes();
initStartingResources();
initDifficulty();
initHeroes();
initStartingBonus();
initTowns();
@ -657,6 +658,41 @@ void CGameState::initGlobalBonuses()
VLC->creh->loadCrExpBon(globalEffects);
}
void CGameState::initDifficulty()
{
logGlobal->debug("\tLoading difficulty settings");
const JsonNode config = JsonUtils::assembleFromFiles("config/difficulty.json");
const JsonNode & difficultyAI(config["ai"][GameConstants::DIFFICULTY_NAMES[scenarioOps->difficulty]]);
const JsonNode & difficultyHuman(config["human"][GameConstants::DIFFICULTY_NAMES[scenarioOps->difficulty]]);
auto setDifficulty = [](PlayerState & state, const JsonNode & json)
{
//set starting resources
state.resources = TResources(json["resources"]);
//set global bonuses
for(auto & jsonBonus : json["globalBonuses"].Vector())
if(auto bonus = JsonUtils::parseBonus(jsonBonus))
state.addNewBonus(bonus);
//set battle bonuses
for(auto & jsonBonus : json["battleBonuses"].Vector())
if(auto bonus = JsonUtils::parseBonus(jsonBonus))
state.battleBonuses.push_back(*bonus);
};
for (auto & elem : players)
{
PlayerState &p = elem.second;
setDifficulty(p, p.human ? difficultyHuman : difficultyAI);
}
if (campaign)
campaign->initStartingResources();
}
void CGameState::initGrailPosition()
{
logGlobal->debug("\tPicking grail position");
@ -813,30 +849,6 @@ void CGameState::removeHeroPlaceholders()
}
}
void CGameState::initStartingResources()
{
logGlobal->debug("\tSetting up resources");
const JsonNode config(JsonPath::builtin("config/startres.json"));
const JsonVector &vector = config["difficulty"].Vector();
const JsonNode &level = vector[scenarioOps->difficulty];
TResources startresAI(level["ai"]);
TResources startresHuman(level["human"]);
for (auto & elem : players)
{
PlayerState &p = elem.second;
if (p.human)
p.resources = startresHuman;
else
p.resources = startresAI;
}
if (campaign)
campaign->initStartingResources();
}
void CGameState::initHeroes()
{
for(auto hero : map->heroesOnMap) //heroes instances initialization

@ -192,7 +192,7 @@ private:
void placeStartingHeroes();
void placeStartingHero(const PlayerColor & playerColor, const HeroTypeID & heroTypeId, int3 townPos);
void removeHeroPlaceholders();
void initStartingResources();
void initDifficulty();
void initHeroes();
void placeHeroesInTowns();
void initFogOfWar();

@ -103,6 +103,16 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
{
//show message
if(!message.empty())
{
InfoWindow iw;
iw.player = h->tempOwner;
iw.text.appendRawString(message);
iw.type = EInfoWindowMode::MODAL;
cb->showInfoDialog(&iw);
}
int action = takenAction(h);
switch( action ) //decide what we do...
{

@ -49,7 +49,7 @@ MapFormatFeaturesH3M MapFormatFeaturesH3M::getFeaturesROE()
result.factionsCount = 8;
result.heroesCount = 128;
result.heroesPortraitsCount = 128;
result.heroesPortraitsCount = 130; // +General Kendal, +Catherine (portrait-only in RoE)
result.artifactsCount = 127;
result.resourcesCount = 7;
result.creaturesCount = 118;

@ -44,6 +44,7 @@ static std::string convertMapName(std::string input)
{
boost::algorithm::to_lower(input);
boost::algorithm::trim(input);
boost::algorithm::erase_all(input, ".");
size_t slashPos = input.find_last_of('/');
@ -345,27 +346,11 @@ void CMapLoaderH3M::readVictoryLossConditions()
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->warn("Map %s: Only one player exists, but normal victory allowed!", mapName);
allowNormalVictory = false; // makes sense? Not much. Works as H3? Yes!
}
}
switch(vicCondition)
{
case EVictoryConditionType::ARTIFACT:
{
assert(allowNormalVictory == true); // not selectable in editor
EventCondition cond(EventCondition::HAVE_ARTIFACT);
cond.objectType = reader->readArtifact();
@ -404,6 +389,7 @@ void CMapLoaderH3M::readVictoryLossConditions()
}
case EVictoryConditionType::BUILDCITY:
{
assert(appliesToAI == true); // not selectable in editor
EventExpression::OperatorAll oper;
EventCondition cond(EventCondition::HAVE_BUILDING);
cond.position = reader->readInt3();
@ -421,6 +407,8 @@ void CMapLoaderH3M::readVictoryLossConditions()
}
case EVictoryConditionType::BUILDGRAIL:
{
assert(allowNormalVictory == true); // not selectable in editor
assert(appliesToAI == true); // not selectable in editor
EventCondition cond(EventCondition::HAVE_BUILDING);
cond.objectType = BuildingID::GRAIL;
cond.position = reader->readInt3();
@ -436,6 +424,10 @@ void CMapLoaderH3M::readVictoryLossConditions()
}
case EVictoryConditionType::BEATHERO:
{
if (!allowNormalVictory)
logGlobal->warn("Map %s: Has 'beat hero' as victory condition, but 'allow normal victory' not set. Ignoring", mapName);
allowNormalVictory = true; // H3 behavior
assert(appliesToAI == false); // not selectable in editor
EventCondition cond(EventCondition::DESTROY);
cond.objectType = Obj::HERO;
cond.position = reader->readInt3();
@ -462,6 +454,7 @@ void CMapLoaderH3M::readVictoryLossConditions()
}
case EVictoryConditionType::BEATMONSTER:
{
assert(appliesToAI == true); // not selectable in editor
EventCondition cond(EventCondition::DESTROY);
cond.objectType = Obj::MONSTER;
cond.position = reader->readInt3();
@ -500,6 +493,7 @@ void CMapLoaderH3M::readVictoryLossConditions()
}
case EVictoryConditionType::TRANSPORTITEM:
{
assert(allowNormalVictory == true); // not selectable in editor
EventCondition cond(EventCondition::TRANSPORT);
cond.objectType = reader->readUInt8();
cond.position = reader->readInt3();
@ -513,6 +507,7 @@ void CMapLoaderH3M::readVictoryLossConditions()
}
case EVictoryConditionType::HOTA_ELIMINATE_ALL_MONSTERS:
{
assert(appliesToAI == false); // not selectable in editor
EventCondition cond(EventCondition::DESTROY);
cond.objectType = Obj::MONSTER;
@ -526,6 +521,7 @@ void CMapLoaderH3M::readVictoryLossConditions()
}
case EVictoryConditionType::HOTA_SURVIVE_FOR_DAYS:
{
assert(appliesToAI == false); // not selectable in editor
EventCondition cond(EventCondition::DAYS_PASSED);
cond.value = reader->readUInt32();
@ -541,6 +537,24 @@ void CMapLoaderH3M::readVictoryLossConditions()
assert(0);
}
if(allowNormalVictory)
{
size_t playersOnMap = boost::range::count_if(
mapHeader->players,
[](const PlayerInfo & info)
{
return info.canAnyonePlay();
}
);
assert(playersOnMap > 1);
if(playersOnMap == 1)
{
logGlobal->warn("Map %s: Only one player exists, but normal victory allowed!", mapName);
allowNormalVictory = false; // makes sense? Not much. Works as H3? Yes!
}
}
// if condition is human-only turn it into following construction: AllOf(human, condition)
if(!appliesToAI)
{
@ -883,7 +897,7 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero)
if(!hero->artifactsWorn.empty() || !hero->artifactsInBackpack.empty())
{
logGlobal->warn("Hero %s at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getNameTranslated(), hero->pos.toString());
logGlobal->debug("Hero %s at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getNameTranslated(), hero->pos.toString());
hero->artifactsInBackpack.clear();
while(!hero->artifactsWorn.empty())
@ -1551,6 +1565,9 @@ void CMapLoaderH3M::readObjects()
newObject->appearance = objectTemplate;
assert(objectInstanceID == ObjectInstanceID((si32)map->objects.size()));
if (newObject->isVisitable() && !map->isInTheMap(newObject->visitablePos()))
logGlobal->error("Map '%s': Object at %s - outside of map borders!", mapName, mapPosition.toString());
{
//TODO: define valid typeName and subtypeName for H3M maps
//boost::format fmt("%s_%d");
@ -1703,7 +1720,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec
if(!object->secSkills.empty())
{
if(object->secSkills[0].first != SecondarySkill::DEFAULT)
logGlobal->warn("Hero %s subID=%d has set secondary skills twice (in map properties and on adventure map instance). Using the latter set...", object->getNameTextID(), object->subID);
logGlobal->debug("Map '%s': Hero %s subID=%d has set secondary skills twice (in map properties and on adventure map instance). Using the latter set...", mapName, object->getNameTextID(), object->subID);
object->secSkills.clear();
}
@ -1775,7 +1792,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec
auto ps = object->getAllBonuses(Selector::type()(BonusType::PRIMARY_SKILL).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)), nullptr);
if(ps->size())
{
logGlobal->warn("Hero %s subID=%d has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", object->getNameTranslated(), object->subID );
logGlobal->debug("Hero %s subID=%d has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", object->getNameTranslated(), object->subID );
for(const auto & b : *ps)
object->removeBonus(b);
}
@ -1907,8 +1924,7 @@ void CMapLoaderH3M::readSeerHutQuest(CGSeerHut * hut, const int3 & position, con
auto rVal = reader->readUInt32();
assert(rId < features.resourcesCount);
assert((rVal & 0x00ffffff) == rVal);
reward.resources[rId] = rVal;
break;
}
@ -2172,7 +2188,7 @@ CGObjectInstance * CMapLoaderH3M::readTown(const int3 & position, std::shared_pt
uint8_t alignment = reader->readUInt8();
if(alignment != PlayerColor::NEUTRAL.getNum())
if(alignment != 255)
{
if(alignment < PlayerColor::PLAYER_LIMIT.getNum())
{

@ -870,6 +870,11 @@ void CMapPatcher::readPatchData()
{
JsonDeserializer handler(mapObjectResolver.get(), input);
readTriggeredEvents(handler);
handler.serializeInt("defeatIconIndex", mapHeader->defeatIconIndex);
handler.serializeInt("victoryIconIndex", mapHeader->victoryIconIndex);
handler.serializeStruct("victoryString", mapHeader->victoryMessage);
handler.serializeStruct("defeatString", mapHeader->defeatMessage);
}
///CMapLoaderJson

@ -53,11 +53,11 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping)
for (auto entryTemplate : mapping["templates"].Struct())
{
std::string h3mName = boost::to_lower_copy(entryTemplate.second.String());
std::string vcmiName = boost::to_lower_copy(entryTemplate.first);
AnimationPath h3mName = AnimationPath::builtinTODO(entryTemplate.second.String());
AnimationPath vcmiName = AnimationPath::builtinTODO(entryTemplate.first);
if (!CResourceHandler::get()->existsResource(AnimationPath::builtinTODO("SPRITES/" + vcmiName)))
logMod->warn("Template animation file %s was not found!", vcmiName);
if (!CResourceHandler::get()->existsResource(vcmiName.addPrefix("SPRITES/")))
logMod->warn("Template animation file %s was not found!", vcmiName.getOriginalName());
mappingObjectTemplate[h3mName] = vcmiName;
}
@ -108,10 +108,10 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping)
void MapIdentifiersH3M::remapTemplate(ObjectTemplate & objectTemplate)
{
std::string name = boost::to_lower_copy(objectTemplate.animationFile.getName());
auto name = objectTemplate.animationFile;
if (mappingObjectTemplate.count(name))
objectTemplate.animationFile = AnimationPath::builtinTODO(mappingObjectTemplate.at(name));
objectTemplate.animationFile = mappingObjectTemplate.at(name);
ObjectTypeIdentifier objectType{ objectTemplate.id, objectTemplate.subid};

@ -11,6 +11,7 @@
#pragma once
#include "../GameConstants.h"
#include "../filesystem/ResourcePath.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -43,7 +44,7 @@ class MapIdentifiersH3M
std::map<ArtifactID, ArtifactID> mappingArtifact;
std::map<SecondarySkill, SecondarySkill> mappingSecondarySkill;
std::map<std::string, std::string> mappingObjectTemplate;
std::map<AnimationPath, AnimationPath> mappingObjectTemplate;
std::map<ObjectTypeIdentifier, ObjectTypeIdentifier> mappingObjectIndex;
template<typename IdentifierID>

@ -113,7 +113,12 @@ int32_t MapReaderH3M::readHeroPortrait()
if(result.getNum() == features.heroIdentifierInvalid)
return int32_t(-1);
assert(result.getNum() < features.heroesPortraitsCount);
if (result.getNum() >= features.heroesPortraitsCount)
{
logGlobal->warn("Map contains invalid hero portrait ID %d. Will be reset!", result.getNum() );
return int32_t(-1);
}
return remapper.remapPortrrait(result);
}
@ -199,7 +204,12 @@ PlayerColor MapReaderH3M::readPlayer()
if (value == 255)
return PlayerColor::NEUTRAL;
assert(value < PlayerColor::PLAYER_LIMIT_I);
if (value >= PlayerColor::PLAYER_LIMIT_I)
{
logGlobal->warn("Map contains invalid player ID %d. Will be reset!", value );
return PlayerColor::NEUTRAL;
}
return PlayerColor(value);
}
@ -210,7 +220,12 @@ PlayerColor MapReaderH3M::readPlayer32()
if (value == 255)
return PlayerColor::NEUTRAL;
assert(value < PlayerColor::PLAYER_LIMIT_I);
if (value >= PlayerColor::PLAYER_LIMIT_I)
{
logGlobal->warn("Map contains invalid player ID %d. Will be reset!", value );
return PlayerColor::NEUTRAL;
}
return PlayerColor(value);
}

@ -24,6 +24,7 @@
#include "../../lib/gameState/CGameState.h"
#include "../../lib/mapping/CMap.h"
#include "../../lib/modding/IdentifierStorage.h"
#include "../../lib/CPlayerState.h"
BattleProcessor::BattleProcessor(CGameHandler * gameHandler)
: gameHandler(gameHandler)
@ -113,6 +114,19 @@ void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArm
const auto * battle = gameHandler->gameState()->getBattle(battleID);
assert(battle);
//add battle bonuses based from player state only when attacks neutral creatures
const auto * attackerInfo = gameHandler->getPlayerState(army1->getOwner(), false);
if(attackerInfo && !army2->getOwner().isValidPlayer())
{
for(auto bonus : attackerInfo->battleBonuses)
{
GiveBonus giveBonus(GiveBonus::ETarget::HERO);
giveBonus.id = hero1->id.getNum();
giveBonus.bonus = bonus;
gameHandler->sendAndApply(&giveBonus);
}
}
auto lastBattleQuery = std::dynamic_pointer_cast<CBattleQuery>(gameHandler->queries->topQuery(battle->sides[0].color));