mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-23 22:37:55 +02:00
Merge pull request #6276 from IvanSavenko/misc_fixes
Fixes for multiple recently reported issues
This commit is contained in:
@@ -1016,6 +1016,7 @@
|
|||||||
"core.bonus.MANA_DRAIN.description" : "{Drains enemy mana}\nDrains ${val} mana every turn from enemy hero",
|
"core.bonus.MANA_DRAIN.description" : "{Drains enemy mana}\nDrains ${val} mana every turn from enemy hero",
|
||||||
"core.bonus.MECHANICAL.description" : "{Mechanical}\nThis unit is immune to effects that only affect living and can be repaired",
|
"core.bonus.MECHANICAL.description" : "{Mechanical}\nThis unit is immune to effects that only affect living and can be repaired",
|
||||||
"core.bonus.MIND_IMMUNITY.description" : "{Mind Spell Immunity}\nThis unit cannot be targeted by spells that affect its mind",
|
"core.bonus.MIND_IMMUNITY.description" : "{Mind Spell Immunity}\nThis unit cannot be targeted by spells that affect its mind",
|
||||||
|
"core.bonus.MORE_DAMAGE_FROM_SPELL.description" : "{Vulnerable to ${subtype.spell}}\nThe damage taken by this unit when hit by a ${subtype.spell} is increased by ${val}%",
|
||||||
"core.bonus.NO_DISTANCE_PENALTY.description" : "{No distance penalty}\nRanged attacks deal full damage at any distance",
|
"core.bonus.NO_DISTANCE_PENALTY.description" : "{No distance penalty}\nRanged attacks deal full damage at any distance",
|
||||||
"core.bonus.NO_MELEE_PENALTY.description" : "{No melee penalty}\nThis ranged unit deals full damage with melee attacks",
|
"core.bonus.NO_MELEE_PENALTY.description" : "{No melee penalty}\nThis ranged unit deals full damage with melee attacks",
|
||||||
"core.bonus.NO_MORALE.description" : "{Neutral Morale}\nCreature is immune to morale effects",
|
"core.bonus.NO_MORALE.description" : "{Neutral Morale}\nCreature is immune to morale effects",
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ const CCreature & BattleProjectileController::getShooter(const CStack * stack) c
|
|||||||
if(creature->getId() == CreatureID::ARROW_TOWERS)
|
if(creature->getId() == CreatureID::ARROW_TOWERS)
|
||||||
creature = owner.siegeController->getTurretCreature(stack->initialPosition);
|
creature = owner.siegeController->getTurretCreature(stack->initialPosition);
|
||||||
|
|
||||||
if(creature->animation.missileFrameAngles.empty())
|
if(creature->animation.missileFrameAngles.empty() && creature->animation.projectileRay.empty())
|
||||||
{
|
{
|
||||||
logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->getNameSingularTranslated());
|
logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->getNameSingularTranslated());
|
||||||
creature = CreatureID(CreatureID::ARCHER).toCreature();
|
creature = CreatureID(CreatureID::ARCHER).toCreature();
|
||||||
|
|||||||
@@ -2184,8 +2184,16 @@ void CMageGuildScreen::updateSpells(ObjectInstanceID tID)
|
|||||||
uint32_t spellCount = town->spellsAtLevel(i+1,false); //spell at level with -1 hmmm?
|
uint32_t spellCount = town->spellsAtLevel(i+1,false); //spell at level with -1 hmmm?
|
||||||
for(uint32_t j=0; j<spellCount; j++)
|
for(uint32_t j=0; j<spellCount; j++)
|
||||||
{
|
{
|
||||||
if(i<town->mageGuildLevel() && town->spells[i].size()>j)
|
if (town->hasBuilt(BuildingSubID::AURORA_BOREALIS))
|
||||||
|
{
|
||||||
|
std::string auroraBorealisName = town->getTown()->getSpecialBuilding(BuildingSubID::AURORA_BOREALIS)->getNameTranslated();
|
||||||
|
|
||||||
|
auroraBorealisScrolls.push_back(std::make_shared<ScrollAllSpells>(positions[i][j], auroraBorealisName));
|
||||||
|
}
|
||||||
|
else if(i<town->mageGuildLevel() && town->spells[i].size()>j)
|
||||||
|
{
|
||||||
spells.push_back(std::make_shared<Scroll>(positions[i][j], town->spells[i][j].toSpell(), townId));
|
spells.push_back(std::make_shared<Scroll>(positions[i][j], town->spells[i][j].toSpell(), townId));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
emptyScrolls.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("TPMAGES.DEF"), 1, 0, positions[i][j].x, positions[i][j].y));
|
emptyScrolls.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("TPMAGES.DEF"), 1, 0, positions[i][j].x, positions[i][j].y));
|
||||||
}
|
}
|
||||||
@@ -2194,6 +2202,22 @@ void CMageGuildScreen::updateSpells(ObjectInstanceID tID)
|
|||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CMageGuildScreen::ScrollAllSpells::ScrollAllSpells(Point position, const std::string & buildingName)
|
||||||
|
{
|
||||||
|
constexpr int auroraBorealisImageIndex = 70;
|
||||||
|
|
||||||
|
OBJECT_CONSTRUCTION;
|
||||||
|
pos += position;
|
||||||
|
image = std::make_shared<CAnimImage>(AnimationPath::builtin("SPELLSCR"), auroraBorealisImageIndex);
|
||||||
|
pos = image->pos;
|
||||||
|
|
||||||
|
MetaString description;
|
||||||
|
description.appendTextID("core.genrltxt.714");
|
||||||
|
description.replaceRawString(buildingName);
|
||||||
|
|
||||||
|
text = std::make_shared<LRClickableAreaWText>(Rect(Point(), pos.dimensions()), description.toString(), description.toString() );
|
||||||
|
}
|
||||||
|
|
||||||
CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, ObjectInstanceID townId)
|
CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, ObjectInstanceID townId)
|
||||||
: spell(Spell), townId(townId)
|
: spell(Spell), townId(townId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class CGarrisonInt;
|
|||||||
class CComponent;
|
class CComponent;
|
||||||
class CComponentBox;
|
class CComponentBox;
|
||||||
class LRClickableArea;
|
class LRClickableArea;
|
||||||
|
class LRClickableAreaWText;
|
||||||
class CTextInputWithConfirm;
|
class CTextInputWithConfirm;
|
||||||
|
|
||||||
/// Building "button"
|
/// Building "button"
|
||||||
@@ -394,10 +395,21 @@ class CMageGuildScreen : public CStatusbarWindow
|
|||||||
void showPopupWindow(const Point & cursorPosition) override;
|
void showPopupWindow(const Point & cursorPosition) override;
|
||||||
void hover(bool on) override;
|
void hover(bool on) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ScrollAllSpells : public CIntObject
|
||||||
|
{
|
||||||
|
std::shared_ptr<CAnimImage> image;
|
||||||
|
std::shared_ptr<LRClickableAreaWText> text;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ScrollAllSpells(Point position, const std::string & buildingName);
|
||||||
|
};
|
||||||
|
|
||||||
std::shared_ptr<CPicture> window;
|
std::shared_ptr<CPicture> window;
|
||||||
std::shared_ptr<CButton> exit;
|
std::shared_ptr<CButton> exit;
|
||||||
std::vector<std::shared_ptr<Scroll>> spells;
|
std::vector<std::shared_ptr<Scroll>> spells;
|
||||||
std::vector<std::shared_ptr<CAnimImage>> emptyScrolls;
|
std::vector<std::shared_ptr<CAnimImage>> emptyScrolls;
|
||||||
|
std::vector<std::shared_ptr<ScrollAllSpells>> auroraBorealisScrolls;
|
||||||
|
|
||||||
std::shared_ptr<CMinorResDataBar> resdatabar;
|
std::shared_ptr<CMinorResDataBar> resdatabar;
|
||||||
|
|
||||||
|
|||||||
@@ -196,7 +196,7 @@
|
|||||||
"earthMagic"
|
"earthMagic"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"grail": { },
|
"grail": { "type" : "auroraBorealis" },
|
||||||
"extraTownHall": { },
|
"extraTownHall": { },
|
||||||
"extraCityHall": { },
|
"extraCityHall": { },
|
||||||
"extraCapitol": { },
|
"extraCapitol": { },
|
||||||
|
|||||||
@@ -205,7 +205,7 @@
|
|||||||
},
|
},
|
||||||
"horde2": { "upgrades" : "dwellingLvl3" },
|
"horde2": { "upgrades" : "dwellingLvl3" },
|
||||||
"horde2Upgr": { "upgrades" : "dwellingUpLvl3", "requires" : [ "horde2" ] },
|
"horde2Upgr": { "upgrades" : "dwellingUpLvl3", "requires" : [ "horde2" ] },
|
||||||
"grail": { },
|
"grail": { "type" : "deityOfFire" },
|
||||||
|
|
||||||
"dwellingLvl1": { "requires" : [ "fort" ] },
|
"dwellingLvl1": { "requires" : [ "fort" ] },
|
||||||
"dwellingLvl2": { "requires" : [ "dwellingLvl1" ] },
|
"dwellingLvl2": { "requires" : [ "dwellingLvl1" ] },
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
},
|
},
|
||||||
"type" : {
|
"type" : {
|
||||||
"type" : "string",
|
"type" : "string",
|
||||||
"enum" : [ "mysticPond", "castleGate", "portalOfSummoning", "library", "escapeTunnel", "treasury", "bank" ],
|
"enum" : [ "mysticPond", "castleGate", "portalOfSummoning", "library", "escapeTunnel", "treasury", "bank", "auroraBorealis", "deityOfFire" ],
|
||||||
"description" : "Subtype for some special buildings"
|
"description" : "Subtype for some special buildings"
|
||||||
},
|
},
|
||||||
"mode" : {
|
"mode" : {
|
||||||
|
|||||||
@@ -543,9 +543,26 @@ When affected unit is attacked from behind, it will receive more damage when att
|
|||||||
|
|
||||||
Affected unit will deal more damage when attacking specific creature
|
Affected unit will deal more damage when attacking specific creature
|
||||||
|
|
||||||
- subtype - identifier of hated creature, ie. "creature.genie"
|
- subtype - identifier of hated creature, ie. `genie`
|
||||||
- val - additional damage, percentage
|
- val - additional damage, percentage
|
||||||
|
|
||||||
|
### HATES_TRAIT
|
||||||
|
|
||||||
|
Affected unit will deal more damage when attacking unit that has specific bonus. Note that this bonus has no assigned description. To make it visible in creature window UI, make sure to provide custom description for such bonus.
|
||||||
|
|
||||||
|
- subtype - identifier of hated bonus, ie. `UNDEAD`
|
||||||
|
- val - additional damage, percentage
|
||||||
|
|
||||||
|
Example: Unit deals 50% more damage to any target that has UNDEAD bonus
|
||||||
|
|
||||||
|
```json
|
||||||
|
"hatesUndead" : {
|
||||||
|
"type" : "HATES_TRAIT",
|
||||||
|
"subtype" : "UNDEAD",
|
||||||
|
"val" : 50
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### SPELL_LIKE_ATTACK
|
### SPELL_LIKE_ATTACK
|
||||||
|
|
||||||
Affected unit ranged attack will use animation and range of specified spell (Magog, Lich)
|
Affected unit ranged attack will use animation and range of specified spell (Magog, Lich)
|
||||||
|
|||||||
@@ -238,15 +238,13 @@ Building requirements can be described using logical expressions:
|
|||||||
Following Heroes III buildings can be used as unique buildings for a town. Their functionality should be identical to a corresponding H3 building. H3 buildings that are not present in this list contain no hardcoded functionality. See vcmi json configuration to see how such buildings can be implemented in a mod.
|
Following Heroes III buildings can be used as unique buildings for a town. Their functionality should be identical to a corresponding H3 building. H3 buildings that are not present in this list contain no hardcoded functionality. See vcmi json configuration to see how such buildings can be implemented in a mod.
|
||||||
|
|
||||||
- `mysticPond`
|
- `mysticPond`
|
||||||
- `artifactMerchant`
|
|
||||||
- `freelancersGuild`
|
|
||||||
- `magicUniversity`
|
|
||||||
- `castleGate`
|
- `castleGate`
|
||||||
- `creatureTransformer`
|
|
||||||
- `portalOfSummoning`
|
- `portalOfSummoning`
|
||||||
- `library`
|
- `library`
|
||||||
- `escapeTunnel`
|
- `escapeTunnel`
|
||||||
- `treasury`
|
- `treasury`
|
||||||
|
- `auroraBorealis`
|
||||||
|
- `deityOfFire`
|
||||||
|
|
||||||
#### Buildings from other Heroes III mods
|
#### Buildings from other Heroes III mods
|
||||||
|
|
||||||
|
|||||||
@@ -285,13 +285,26 @@ double DamageCalculator::getAttackFromBackFactor() const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
double DamageCalculator::getAttackHateFactor() const
|
double DamageCalculator::getAttackHateCreatureFactor() const
|
||||||
{
|
{
|
||||||
//assume that unit have only few HATE features and cache them all
|
//assume that unit have only few HATE features and cache them all
|
||||||
auto allHateEffects = info.attacker->getBonusesOfType(BonusType::HATE);
|
auto allHateEffects = info.attacker->getBonusesOfType(BonusType::HATE);
|
||||||
return allHateEffects->valOfBonuses(Selector::subtype()(BonusSubtypeID(info.defender->creatureId()))) / 100.0;
|
return allHateEffects->valOfBonuses(Selector::subtype()(BonusSubtypeID(info.defender->creatureId()))) / 100.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double DamageCalculator::getAttackHateTraitFactor() const
|
||||||
|
{
|
||||||
|
//assume that unit have only few HATE features and cache them all
|
||||||
|
auto allHateEffects = info.attacker->getBonusesOfType(BonusType::HATES_TRAIT);
|
||||||
|
|
||||||
|
auto selector = [this](const Bonus* hateBonus) -> bool
|
||||||
|
{
|
||||||
|
return info.defender->hasBonusOfType(hateBonus->subtype.as<BonusTypeID>().toEnum());
|
||||||
|
};
|
||||||
|
|
||||||
|
return allHateEffects->valOfBonuses(selector) / 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
double DamageCalculator::getAttackRevengeFactor() const
|
double DamageCalculator::getAttackRevengeFactor() const
|
||||||
{
|
{
|
||||||
if(info.attacker->hasBonusOfType(BonusType::REVENGE)) //HotA Haspid ability
|
if(info.attacker->hasBonusOfType(BonusType::REVENGE)) //HotA Haspid ability
|
||||||
@@ -475,7 +488,8 @@ std::vector<double> DamageCalculator::getAttackFactors() const
|
|||||||
getAttackFromBackFactor(),
|
getAttackFromBackFactor(),
|
||||||
getAttackDeathBlowFactor(),
|
getAttackDeathBlowFactor(),
|
||||||
getAttackDoubleDamageFactor(),
|
getAttackDoubleDamageFactor(),
|
||||||
getAttackHateFactor(),
|
getAttackHateCreatureFactor(),
|
||||||
|
getAttackHateTraitFactor(),
|
||||||
getAttackRevengeFactor()
|
getAttackRevengeFactor()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ class DLL_LINKAGE DamageCalculator
|
|||||||
double getAttackJoustingFactor() const;
|
double getAttackJoustingFactor() const;
|
||||||
double getAttackDeathBlowFactor() const;
|
double getAttackDeathBlowFactor() const;
|
||||||
double getAttackDoubleDamageFactor() const;
|
double getAttackDoubleDamageFactor() const;
|
||||||
double getAttackHateFactor() const;
|
double getAttackHateCreatureFactor() const;
|
||||||
|
double getAttackHateTraitFactor() const;
|
||||||
double getAttackRevengeFactor() const;
|
double getAttackRevengeFactor() const;
|
||||||
double getAttackFromBackFactor() const;
|
double getAttackFromBackFactor() const;
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "BonusCustomTypes.h"
|
#include "BonusCustomTypes.h"
|
||||||
|
#include "CBonusTypeHandler.h"
|
||||||
|
#include "GameLibrary.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
@@ -75,4 +77,19 @@ std::string BonusCustomSource::encode(const si32 index)
|
|||||||
return std::to_string(index);
|
return std::to_string(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string BonusTypeID::encode(int32_t index)
|
||||||
|
{
|
||||||
|
if (index == static_cast<int32_t>(BonusType::NONE))
|
||||||
|
return "";
|
||||||
|
return LIBRARY->bth->bonusToString(static_cast<BonusType>(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
si32 BonusTypeID::decode(const std::string & identifier)
|
||||||
|
{
|
||||||
|
if (identifier.empty())
|
||||||
|
return RiverId::NO_RIVER.getNum();
|
||||||
|
|
||||||
|
return resolveIdentifier("bonus", identifier);
|
||||||
|
}
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include "../constants/EntityIdentifiers.h"
|
#include "../constants/EntityIdentifiers.h"
|
||||||
#include "../constants/VariantIdentifier.h"
|
#include "../constants/VariantIdentifier.h"
|
||||||
|
#include "BonusEnum.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
@@ -77,7 +78,27 @@ public:
|
|||||||
static BonusCustomSubtype creatureLevel(int level);
|
static BonusCustomSubtype creatureLevel(int level);
|
||||||
};
|
};
|
||||||
|
|
||||||
using BonusSubtypeID = VariantIdentifier<BonusCustomSubtype, SpellID, CreatureID, PrimarySkill, TerrainId, GameResID, SpellSchool>;
|
class DLL_LINKAGE BonusTypeID : public EntityIdentifier<BonusTypeID>
|
||||||
using BonusSourceID = VariantIdentifier<BonusCustomSource, SpellID, CreatureID, ArtifactID, ArtifactInstanceID, CampaignScenarioID, SecondarySkill, HeroTypeID, Obj, ObjectInstanceID, BuildingTypeUniqueID, BattleField>;
|
{
|
||||||
|
public:
|
||||||
|
using EntityIdentifier<BonusTypeID>::EntityIdentifier;
|
||||||
|
using EnumType = BonusType;
|
||||||
|
|
||||||
|
static std::string encode(int32_t index);
|
||||||
|
static si32 decode(const std::string & identifier);
|
||||||
|
|
||||||
|
constexpr EnumType toEnum() const
|
||||||
|
{
|
||||||
|
return static_cast<EnumType>(EntityIdentifier::num);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr BonusTypeID(const EnumType & enumValue)
|
||||||
|
{
|
||||||
|
EntityIdentifier::num = static_cast<int32_t>(enumValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using BonusSubtypeID = VariantIdentifier<BonusCustomSubtype, SpellID, CreatureID, PrimarySkill, TerrainId, GameResID, SpellSchool, BonusTypeID>;
|
||||||
|
using BonusSourceID = VariantIdentifier<BonusCustomSource, SpellID, CreatureID, ArtifactID, CampaignScenarioID, SecondarySkill, HeroTypeID, Obj, ObjectInstanceID, BuildingTypeUniqueID, BattleField, ArtifactInstanceID>;
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ class JsonNode;
|
|||||||
BONUS_NAME(TRANSMUTATION_IMMUNITY) /*blocks TRANSMUTATION bonus*/\
|
BONUS_NAME(TRANSMUTATION_IMMUNITY) /*blocks TRANSMUTATION bonus*/\
|
||||||
BONUS_NAME(COMBAT_MANA_BONUS) /* Additional mana per combat */ \
|
BONUS_NAME(COMBAT_MANA_BONUS) /* Additional mana per combat */ \
|
||||||
BONUS_NAME(SPECIFIC_SPELL_RANGE) /* value used for allowed spell range, subtype - spell id */\
|
BONUS_NAME(SPECIFIC_SPELL_RANGE) /* value used for allowed spell range, subtype - spell id */\
|
||||||
|
BONUS_NAME(HATES_TRAIT) /* affected unit deals additional damage to units with specific bonus. subtype - bonus, val - damage bonus percent */ \
|
||||||
/* end of list */
|
/* end of list */
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ namespace BuildingSubID
|
|||||||
PORTAL_OF_SUMMONING,
|
PORTAL_OF_SUMMONING,
|
||||||
ESCAPE_TUNNEL,
|
ESCAPE_TUNNEL,
|
||||||
TREASURY,
|
TREASURY,
|
||||||
BANK
|
BANK,
|
||||||
|
AURORA_BOREALIS,
|
||||||
|
DEITY_OF_FIRE
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,9 @@ namespace MappedKeys
|
|||||||
{ "library", BuildingSubID::LIBRARY },
|
{ "library", BuildingSubID::LIBRARY },
|
||||||
{ "escapeTunnel", BuildingSubID::ESCAPE_TUNNEL },
|
{ "escapeTunnel", BuildingSubID::ESCAPE_TUNNEL },
|
||||||
{ "treasury", BuildingSubID::TREASURY },
|
{ "treasury", BuildingSubID::TREASURY },
|
||||||
{ "bank", BuildingSubID::BANK }
|
{ "bank", BuildingSubID::BANK },
|
||||||
|
{ "auroraBorealis", BuildingSubID::AURORA_BOREALIS },
|
||||||
|
{ "deityOfFire", BuildingSubID::DEITY_OF_FIRE }
|
||||||
};
|
};
|
||||||
|
|
||||||
static const std::map<std::string, EMarketMode> MARKET_NAMES_TO_TYPES =
|
static const std::map<std::string, EMarketMode> MARKET_NAMES_TO_TYPES =
|
||||||
|
|||||||
@@ -1242,17 +1242,20 @@ bool CGameState::checkForVictory(const PlayerColor & player, const EventConditio
|
|||||||
case EventCondition::HAVE_CREATURES:
|
case EventCondition::HAVE_CREATURES:
|
||||||
{
|
{
|
||||||
//check if in players armies there is enough creatures
|
//check if in players armies there is enough creatures
|
||||||
int total = 0; //creature counter
|
// NOTE: only heroes & towns are checked, in line with H3.
|
||||||
for(auto ai : map->getObjects<CArmedInstance>())
|
// Garrisons, mines, and guards of owned dwellings(!) are excluded
|
||||||
{
|
int totalCreatures = 0;
|
||||||
if(ai->getOwner() == player)
|
for (const auto & hero : p->getHeroes())
|
||||||
{
|
for(const auto & elem : hero->Slots()) //iterate through army
|
||||||
for(const auto & elem : ai->Slots()) //iterate through army
|
if(elem.second->getId() == condition.objectType.as<CreatureID>()) //it's searched creature
|
||||||
if(elem.second->getId() == condition.objectType.as<CreatureID>()) //it's searched creature
|
totalCreatures += elem.second->getCount();
|
||||||
total += elem.second->getCount();
|
|
||||||
}
|
for (const auto & town : p->getTowns())
|
||||||
}
|
for(const auto & elem : town->Slots()) //iterate through army
|
||||||
return total >= condition.value;
|
if(elem.second->getId() == condition.objectType.as<CreatureID>()) //it's searched creature
|
||||||
|
totalCreatures += elem.second->getCount();
|
||||||
|
|
||||||
|
return totalCreatures >= condition.value;
|
||||||
}
|
}
|
||||||
case EventCondition::HAVE_RESOURCES:
|
case EventCondition::HAVE_RESOURCES:
|
||||||
{
|
{
|
||||||
@@ -1626,18 +1629,19 @@ void CGameState::loadGame(CLoadFile & file)
|
|||||||
logGlobal->info("Loading game state...");
|
logGlobal->info("Loading game state...");
|
||||||
|
|
||||||
CMapHeader dummyHeader;
|
CMapHeader dummyHeader;
|
||||||
StartInfo dummyStartInfo;
|
|
||||||
ActiveModsInSaveList dummyActiveMods;
|
ActiveModsInSaveList dummyActiveMods;
|
||||||
|
|
||||||
file.load(dummyHeader);
|
file.load(dummyHeader);
|
||||||
if (file.hasFeature(ESerializationVersion::NO_RAW_POINTERS_IN_SERIALIZER))
|
if (file.hasFeature(ESerializationVersion::NO_RAW_POINTERS_IN_SERIALIZER))
|
||||||
{
|
{
|
||||||
|
StartInfo dummyStartInfo;
|
||||||
file.load(dummyStartInfo);
|
file.load(dummyStartInfo);
|
||||||
file.load(dummyActiveMods);
|
file.load(dummyActiveMods);
|
||||||
file.load(*this);
|
file.load(*this);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
auto dummyStartInfo = std::make_shared<StartInfo>();
|
||||||
bool dummyA = false;
|
bool dummyA = false;
|
||||||
uint32_t dummyB = 0;
|
uint32_t dummyB = 0;
|
||||||
uint16_t dummyC = 0;
|
uint16_t dummyC = 0;
|
||||||
|
|||||||
@@ -81,11 +81,9 @@ public:
|
|||||||
{
|
{
|
||||||
bool dummyA = false;
|
bool dummyA = false;
|
||||||
uint32_t dummyB = 0;
|
uint32_t dummyB = 0;
|
||||||
uint16_t dummyC = 0;
|
|
||||||
|
|
||||||
h & dummyA;
|
h & dummyA;
|
||||||
h & dummyB;
|
h & dummyB;
|
||||||
h & dummyC;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -91,6 +91,14 @@ static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const Jso
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case BonusType::HATES_TRAIT:
|
||||||
|
{
|
||||||
|
LIBRARY->identifiers()->requestIdentifier( "bonus", node, [&subtype](int32_t identifier)
|
||||||
|
{
|
||||||
|
subtype = BonusType(identifier);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case BonusType::NO_TERRAIN_PENALTY:
|
case BonusType::NO_TERRAIN_PENALTY:
|
||||||
{
|
{
|
||||||
LIBRARY->identifiers()->requestIdentifier( "terrain", node, [&subtype](int32_t identifier)
|
LIBRARY->identifiers()->requestIdentifier( "terrain", node, [&subtype](int32_t identifier)
|
||||||
|
|||||||
@@ -905,13 +905,6 @@ bool CGTownInstance::hasBuilt(const BuildingID & buildingID) const
|
|||||||
return vstd::contains(builtBuildings, buildingID);
|
return vstd::contains(builtBuildings, buildingID);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CGTownInstance::hasBuilt(const BuildingID & buildingID, FactionID townID) const
|
|
||||||
{
|
|
||||||
if (townID == getTown()->faction->getId() || townID == FactionID::ANY)
|
|
||||||
return hasBuilt(buildingID);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CGTownInstance::addBuilding(const BuildingID & buildingID)
|
void CGTownInstance::addBuilding(const BuildingID & buildingID)
|
||||||
{
|
{
|
||||||
if(buildingID == BuildingID::NONE)
|
if(buildingID == BuildingID::NONE)
|
||||||
|
|||||||
@@ -165,7 +165,6 @@ public:
|
|||||||
bool hasBuilt(BuildingSubID::EBuildingSubID buildingID) const;
|
bool hasBuilt(BuildingSubID::EBuildingSubID buildingID) const;
|
||||||
//checks if building is constructed and town has same subID
|
//checks if building is constructed and town has same subID
|
||||||
bool hasBuilt(const BuildingID & buildingID) const;
|
bool hasBuilt(const BuildingID & buildingID) const;
|
||||||
bool hasBuilt(const BuildingID & buildingID, FactionID townID) const;
|
|
||||||
void addBuilding(const BuildingID & buildingID);
|
void addBuilding(const BuildingID & buildingID);
|
||||||
void removeBuilding(const BuildingID & buildingID);
|
void removeBuilding(const BuildingID & buildingID);
|
||||||
void removeAllBuildings();
|
void removeAllBuildings();
|
||||||
|
|||||||
@@ -143,12 +143,20 @@ struct DLL_LINKAGE Reward final
|
|||||||
h & movePoints;
|
h & movePoints;
|
||||||
h & primary;
|
h & primary;
|
||||||
h & secondary;
|
h & secondary;
|
||||||
h & heroBonuses;
|
|
||||||
if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
|
if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
|
||||||
{
|
{
|
||||||
|
h & heroBonuses;
|
||||||
h & playerBonuses;
|
h & playerBonuses;
|
||||||
h & commanderBonuses;
|
h & commanderBonuses;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::vector<Bonus> bonuses;
|
||||||
|
h & bonuses;
|
||||||
|
for (const auto & bonus : bonuses)
|
||||||
|
heroBonuses.push_back(std::make_shared<Bonus>(bonus));
|
||||||
|
}
|
||||||
|
|
||||||
h & grantedArtifacts;
|
h & grantedArtifacts;
|
||||||
if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
|
if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -953,7 +953,7 @@ std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, c
|
|||||||
|
|
||||||
const si32 levelPower = levelObject.power = static_cast<si32>(levelNode["power"].Integer());
|
const si32 levelPower = levelObject.power = static_cast<si32>(levelNode["power"].Integer());
|
||||||
|
|
||||||
if (!spell->isCreatureAbility())
|
if (!levelNode["description"].String().empty())
|
||||||
LIBRARY->generaltexth->registerString(scope, spell->getDescriptionTextID(levelIndex), levelNode["description"]);
|
LIBRARY->generaltexth->registerString(scope, spell->getDescriptionTextID(levelIndex), levelNode["description"]);
|
||||||
|
|
||||||
levelObject.cost = static_cast<si32>(levelNode["cost"].Integer());
|
levelObject.cost = static_cast<si32>(levelNode["cost"].Integer());
|
||||||
|
|||||||
@@ -773,7 +773,7 @@ void CGameHandler::giveSpells(const CGTownInstance *t, const CGHeroInstance *h)
|
|||||||
ChangeSpells cs;
|
ChangeSpells cs;
|
||||||
cs.hid = h->id;
|
cs.hid = h->id;
|
||||||
cs.learn = true;
|
cs.learn = true;
|
||||||
if (t->hasBuilt(BuildingID::GRAIL, ETownType::CONFLUX) && t->hasBuilt(BuildingID::MAGES_GUILD_1))
|
if (t->hasBuilt(BuildingSubID::AURORA_BOREALIS) && t->hasBuilt(BuildingID::MAGES_GUILD_1))
|
||||||
{
|
{
|
||||||
// Aurora Borealis give spells of all levels even if only level 1 mages guild built
|
// Aurora Borealis give spells of all levels even if only level 1 mages guild built
|
||||||
for (int i = 0; i < h->maxSpellLevel(); i++)
|
for (int i = 0; i < h->maxSpellLevel(); i++)
|
||||||
@@ -2115,22 +2115,6 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//Performs stuff that has to be done after new building is built
|
|
||||||
auto processAfterBuiltStructure = [t, this](const BuildingID buildingID)
|
|
||||||
{
|
|
||||||
auto isMageGuild = (buildingID <= BuildingID::MAGES_GUILD_5 && buildingID >= BuildingID::MAGES_GUILD_1);
|
|
||||||
auto isLibrary = isMageGuild ? false
|
|
||||||
: t->getTown()->buildings.at(buildingID)->subId == BuildingSubID::EBuildingSubID::LIBRARY;
|
|
||||||
|
|
||||||
if(isMageGuild || isLibrary || (t->getFactionID() == ETownType::CONFLUX && buildingID == BuildingID::GRAIL))
|
|
||||||
{
|
|
||||||
if(t->getVisitingHero())
|
|
||||||
giveSpells(t,t->getVisitingHero());
|
|
||||||
if(t->getGarrisonHero())
|
|
||||||
giveSpells(t,t->getGarrisonHero());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Checks if all requirements will be met with expected building list "buildingsThatWillBe"
|
//Checks if all requirements will be met with expected building list "buildingsThatWillBe"
|
||||||
auto areRequirementsFulfilled = [&buildingsThatWillBe](const BuildingID & buildID)
|
auto areRequirementsFulfilled = [&buildingsThatWillBe](const BuildingID & buildID)
|
||||||
{
|
{
|
||||||
@@ -2192,8 +2176,20 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
|
|||||||
sendAndApply(ns);
|
sendAndApply(ns);
|
||||||
|
|
||||||
//Other post-built events. To some logic like giving spells to work gamestate changes for new building must be already in place!
|
//Other post-built events. To some logic like giving spells to work gamestate changes for new building must be already in place!
|
||||||
for(auto builtID : ns.bid)
|
for(auto buildingID : ns.bid)
|
||||||
processAfterBuiltStructure(builtID);
|
{
|
||||||
|
bool isMageGuild = buildingID <= BuildingID::MAGES_GUILD_5 && buildingID >= BuildingID::MAGES_GUILD_1;
|
||||||
|
bool isLibrary = t->getTown()->buildings.at(buildingID)->subId == BuildingSubID::LIBRARY;
|
||||||
|
bool isAurora = t->getTown()->buildings.at(buildingID)->subId == BuildingSubID::AURORA_BOREALIS;
|
||||||
|
|
||||||
|
if(isMageGuild || isLibrary || isAurora)
|
||||||
|
{
|
||||||
|
if(t->getVisitingHero())
|
||||||
|
giveSpells(t,t->getVisitingHero());
|
||||||
|
if(t->getGarrisonHero())
|
||||||
|
giveSpells(t,t->getGarrisonHero());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// now when everything is built - reveal tiles for lookout tower
|
// now when everything is built - reveal tiles for lookout tower
|
||||||
changeFogOfWar(t->getSightCenter(), t->getSightRadius(), t->getOwner(), ETileVisibility::REVEALED);
|
changeFogOfWar(t->getSightCenter(), t->getSightRadius(), t->getOwner(), ETileVisibility::REVEALED);
|
||||||
|
|||||||
@@ -1448,7 +1448,7 @@ void BattleActionProcessor::applyBattleEffects(const CBattleInfoCallback & battl
|
|||||||
}
|
}
|
||||||
|
|
||||||
//life drain handling
|
//life drain handling
|
||||||
if(attackerState->hasBonusOfType(BonusType::LIFE_DRAIN) && def->isLiving())
|
if(attackerState->hasBonusOfType(BonusType::LIFE_DRAIN) && def->isLiving() && attackerState->getTotalHealth() != attackerState->getAvailableHealth())
|
||||||
{
|
{
|
||||||
int64_t toHeal = bsa.damageAmount * attackerState->valOfBonuses(BonusType::LIFE_DRAIN) / 100;
|
int64_t toHeal = bsa.damageAmount * attackerState->valOfBonuses(BonusType::LIFE_DRAIN) / 100;
|
||||||
healInfo += attackerState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
|
healInfo += attackerState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);
|
||||||
|
|||||||
@@ -512,7 +512,7 @@ std::tuple<EWeekType, CreatureID> NewTurnProcessor::pickWeekType(bool newMonth)
|
|||||||
for (const auto & townID : gameHandler->gameState().getMap().getAllTowns())
|
for (const auto & townID : gameHandler->gameState().getMap().getAllTowns())
|
||||||
{
|
{
|
||||||
const auto * t = gameHandler->gameState().getTown(townID);
|
const auto * t = gameHandler->gameState().getTown(townID);
|
||||||
if (t->hasBuilt(BuildingID::GRAIL, ETownType::INFERNO))
|
if (t->hasBuilt(BuildingSubID::DEITY_OF_FIRE))
|
||||||
return { EWeekType::DEITYOFFIRE, CreatureID::IMP };
|
return { EWeekType::DEITYOFFIRE, CreatureID::IMP };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user