1
0
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:
Ivan Savenko
2025-11-04 15:55:15 +02:00
committed by GitHub
26 changed files with 178 additions and 62 deletions

View File

@@ -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",

View File

@@ -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();

View File

@@ -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)
{ {

View File

@@ -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;

View File

@@ -196,7 +196,7 @@
"earthMagic" "earthMagic"
] ]
}, },
"grail": { }, "grail": { "type" : "auroraBorealis" },
"extraTownHall": { }, "extraTownHall": { },
"extraCityHall": { }, "extraCityHall": { },
"extraCapitol": { }, "extraCapitol": { },

View File

@@ -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" ] },

View File

@@ -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" : {

View File

@@ -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)

View File

@@ -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

View File

@@ -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()
}; };
} }

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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 */

View File

@@ -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
}; };
} }

View File

@@ -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 =

View File

@@ -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;

View File

@@ -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;
} }
} }
}; };

View File

@@ -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)

View File

@@ -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)

View File

@@ -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();

View File

@@ -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)
{ {

View File

@@ -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());

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 };
} }