From 38efbb345e61dbc6a855d290c205fe0a9ef1dc55 Mon Sep 17 00:00:00 2001 From: MikeLodz Date: Mon, 8 Feb 2021 23:39:52 +0100 Subject: [PATCH 1/2] Fixes mantis tickets 2899 and 2984 (bugged hero spell specialties) - stoneskin/haste/prayer/weakness: didnt work because there were 2 bonus objects in buffer and they were filtered out as a possible duplicate (BattleInfo::addOrUpdateUnitBonus). it was fixed by making them a single bonus. - disrupting-ray and weakness: had opposite effect, because of missing negation - added a new specialty types: SPECIAL_ADD_VALUE_ENCHANT and SPECIAL_FIXED_VALUE_ENCHANT. this is to make possible specialties like Aenin (fixed specialty value added to spell value) and Melody (fixed value for spell regardless of anything). These specialties can be used in mods with any heroes. - slayer spell effect calculations was fixed to include hero Coronius-style specialty. - finally fixed description for Labetha Conflux hero, this is a OH3 bug described here https://heroes.thelazy.net/index.php/Labetha Changes were tested and work as intended. commit was made in cooperation with modder Misiokles --- config/heroes/conflux.json | 9 ++++- config/heroes/rampart.json | 3 +- lib/HeroBonus.cpp | 4 ++ lib/HeroBonus.h | 2 + lib/battle/CBattleInfoCallback.cpp | 8 ++++ lib/spells/effects/Timed.cpp | 59 ++++++++++++++++++++---------- 6 files changed, 62 insertions(+), 23 deletions(-) diff --git a/config/heroes/conflux.json b/config/heroes/conflux.json index 6985fefc8..74b97a968 100644 --- a/config/heroes/conflux.json +++ b/config/heroes/conflux.json @@ -325,6 +325,11 @@ "class" : "elementalist", "female": true, "spellbook": [ "stoneSkin" ], + "texts" : { + "specialty" : { + "description" : "{Stone Skin}\r\n\r\nCasts Stone Skin with increased effect, based on the level of the target unit. (The bonus is greater when used on weaker units)" + } + }, "skills": [ { "skill" : "wisdom", "level": "basic" }, @@ -375,9 +380,9 @@ "specialty" : { "bonuses" : { "disruptingRay" : { - "addInfo" : 0, + "addInfo" : -2, "subtype" : "spell.disruptingRay", - "type" : "SPECIAL_PECULIAR_ENCHANT" + "type" : "SPECIAL_ADD_VALUE_ENCHANT" } } } diff --git a/config/heroes/rampart.json b/config/heroes/rampart.json index 80a8bbde6..7055cc798 100644 --- a/config/heroes/rampart.json +++ b/config/heroes/rampart.json @@ -265,8 +265,9 @@ "specialty" : { "bonuses" : { "fortune" : { + "addInfo" : 3, "subtype" : "spell.fortune", - "type" : "MAXED_SPELL" + "type" : "SPECIAL_FIXED_VALUE_ENCHANT" } } } diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index 19c56c4db..998f691f8 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -1472,6 +1472,8 @@ JsonNode subtypeToJson(Bonus::BonusType type, int subtype) case Bonus::SPECIAL_BLESS_DAMAGE: case Bonus::MAXED_SPELL: case Bonus::SPECIAL_PECULIAR_ENCHANT: + case Bonus::SPECIAL_ADD_VALUE_ENCHANT: + case Bonus::SPECIAL_FIXED_VALUE_ENCHANT: return JsonUtils::stringNode("spell." + (*VLC->spellh)[SpellID::ESpellID(subtype)]->identifier); case Bonus::IMPROVED_NECROMANCY: case Bonus::SPECIAL_UPGRADE: @@ -1560,6 +1562,8 @@ std::string Bonus::nameForBonus() const case Bonus::SPECIAL_BLESS_DAMAGE: case Bonus::MAXED_SPELL: case Bonus::SPECIAL_PECULIAR_ENCHANT: + case Bonus::SPECIAL_ADD_VALUE_ENCHANT: + case Bonus::SPECIAL_FIXED_VALUE_ENCHANT: return (*VLC->spellh)[SpellID::ESpellID(subtype)]->identifier; case Bonus::SPECIAL_UPGRADE: return CreatureID::encode(subtype) + "2" + CreatureID::encode(additionalInfo[0]); diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 501b3d939..9efde8da7 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -281,6 +281,8 @@ public: BONUS_NAME(SPECIAL_BLESS_DAMAGE) /*val = spell (bless), additionalInfo = value per level in percent*/\ BONUS_NAME(MAXED_SPELL) /*val = id*/\ BONUS_NAME(SPECIAL_PECULIAR_ENCHANT) /*blesses and curses with id = val dependent on unit's level, subtype = 0 or 1 for Coronius*/\ + BONUS_NAME(SPECIAL_ADD_VALUE_ENCHANT) /*specialty like Aenin, additionalInfo = value to add*/\ + BONUS_NAME(SPECIAL_FIXED_VALUE_ENCHANT) /*specialty like Melody constant 3 luck, additionalInfo = value to fix.*/\ BONUS_NAME(SPECIAL_UPGRADE) /*subtype = base, additionalInfo = target */\ BONUS_NAME(DRAGON_NATURE) \ BONUS_NAME(CREATURE_DAMAGE)/*subtype 0 = both, 1 = min, 2 = max*/\ diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 1f2ca9f61..b12b993d9 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -791,7 +791,15 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo & info) } if(isAffected) + { attackDefenceDifference += SpellID(SpellID::SLAYER).toSpell()->getPower(spLevel); + if(info.attacker->hasBonusOfType(Bonus::SPECIAL_PECULIAR_ENCHANT, SpellID::SLAYER)) + { + ui8 attackerTier = info.attacker->unitType()->level; + ui8 specialtyBonus = std::max(5 - attackerTier, 0); + attackDefenceDifference += specialtyBonus; + } + } } //bonus from attack/defense skills diff --git a/lib/spells/effects/Timed.cpp b/lib/spells/effects/Timed.cpp index 8dd5a03d2..ccf767503 100644 --- a/lib/spells/effects/Timed.cpp +++ b/lib/spells/effects/Timed.cpp @@ -151,10 +151,16 @@ void Timed::prepareEffects(SetStackEffect & sse, const Mechanics * m, const Effe std::vector converted; convertBonus(m, duration, converted); - std::shared_ptr bonus = nullptr; + std::shared_ptr peculiarBonus = nullptr; + std::shared_ptr addedValueBonus = nullptr; + std::shared_ptr fixedValueBonus = nullptr; auto casterHero = dynamic_cast(m->caster); if(casterHero) - bonus = casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, m->getSpellIndex())); + { + peculiarBonus = casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, m->getSpellIndex())); + addedValueBonus = casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_ADD_VALUE_ENCHANT, m->getSpellIndex())); + fixedValueBonus = casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_FIXED_VALUE_ENCHANT, m->getSpellIndex())); + } //TODO: does hero specialty should affects his stack casting spells? for(auto & t : target) @@ -175,16 +181,17 @@ void Timed::prepareEffects(SetStackEffect & sse, const Mechanics * m, const Effe if(describe) describeEffect(sse.battleLog, m, converted, affected); - si32 power = 0; + const auto tier = std::max(affected->creatureLevel(), 1); //don't divide by 0 for certain creatures (commanders, war machines) //Apply hero specials - peculiar enchants - const auto tier = std::max(affected->creatureLevel(), 1); //don't divide by 0 for certain creatures (commanders, war machines) - if(bonus) + if(peculiarBonus) { - switch(bonus->additionalInfo[0]) + + si32 power = 0; + switch (peculiarBonus->additionalInfo[0]) { case 0: //normal - switch(tier) + switch (tier) { case 1: case 2: @@ -199,23 +206,35 @@ void Timed::prepareEffects(SetStackEffect & sse, const Mechanics * m, const Effe power = 1; break; } - for(const Bonus & b : converted) - { - Bonus specialBonus(b); - specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely - specialBonus.turnsRemain = duration; - - //additional premy to given effect - buffer.push_back(specialBonus); - } break; - case 1: //only Coronius as yet + case 1: + //Coronius style specialty bonus. + //Please note that actual Coronius isnt here, because Slayer is a spell that doesnt affect monster stats and is used only in calculateDmgRange power = std::max(5 - tier, 0); - Bonus specialBonus(Bonus::N_TURNS, Bonus::PRIMARY_SKILL, Bonus::SPELL_EFFECT, power, m->getSpellIndex(), PrimarySkill::ATTACK); - specialBonus.turnsRemain = duration; - buffer.push_back(specialBonus); break; } + if(m->isNegativeSpell()) { + //negative spells like weakness are defined in json with negative numbers, so we need do same here + power = -1 * power; + } + for(Bonus& b : buffer) + { + b.val += power; + } + } + else if(addedValueBonus) + { + for(Bonus& b : buffer) + { + b.val += addedValueBonus->additionalInfo[0]; + } + } + else if(fixedValueBonus) + { + for(Bonus& b : buffer) + { + b.val = fixedValueBonus->additionalInfo[0]; + } } if(casterHero && casterHero->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, m->getSpellIndex())) //TODO: better handling of bonus percentages { From fe9412f845cf6e63c6fa30feb83bd9474c1cd8b4 Mon Sep 17 00:00:00 2001 From: MikeLodz Date: Sat, 13 Feb 2021 16:02:42 +0100 Subject: [PATCH 2/2] hero specialty fixes continuation: changes requested during code review --- lib/HeroBonus.h | 4 ++-- lib/spells/effects/Timed.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 9efde8da7..6f0c37aaa 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -281,8 +281,6 @@ public: BONUS_NAME(SPECIAL_BLESS_DAMAGE) /*val = spell (bless), additionalInfo = value per level in percent*/\ BONUS_NAME(MAXED_SPELL) /*val = id*/\ BONUS_NAME(SPECIAL_PECULIAR_ENCHANT) /*blesses and curses with id = val dependent on unit's level, subtype = 0 or 1 for Coronius*/\ - BONUS_NAME(SPECIAL_ADD_VALUE_ENCHANT) /*specialty like Aenin, additionalInfo = value to add*/\ - BONUS_NAME(SPECIAL_FIXED_VALUE_ENCHANT) /*specialty like Melody constant 3 luck, additionalInfo = value to fix.*/\ BONUS_NAME(SPECIAL_UPGRADE) /*subtype = base, additionalInfo = target */\ BONUS_NAME(DRAGON_NATURE) \ BONUS_NAME(CREATURE_DAMAGE)/*subtype 0 = both, 1 = min, 2 = max*/\ @@ -323,6 +321,8 @@ public: BONUS_NAME(SPECIAL_CRYSTAL_GENERATION) /*crystal dragon crystal generation*/ \ BONUS_NAME(NO_SPELLCAST_BY_DEFAULT) /*spellcast will not be default attack option for this creature*/ \ BONUS_NAME(GARGOYLE) /* gargoyle is special than NON_LIVING, cannot be rised or healed */ \ + BONUS_NAME(SPECIAL_ADD_VALUE_ENCHANT) /*specialty spell like Aenin has, increased effect of spell, additionalInfo = value to add*/\ + BONUS_NAME(SPECIAL_FIXED_VALUE_ENCHANT) /*specialty spell like Melody has, constant spell effect (i.e. 3 luck), additionalInfo = value to fix.*/\ /* end of list */ diff --git a/lib/spells/effects/Timed.cpp b/lib/spells/effects/Timed.cpp index ccf767503..a82186de7 100644 --- a/lib/spells/effects/Timed.cpp +++ b/lib/spells/effects/Timed.cpp @@ -213,7 +213,8 @@ void Timed::prepareEffects(SetStackEffect & sse, const Mechanics * m, const Effe power = std::max(5 - tier, 0); break; } - if(m->isNegativeSpell()) { + if(m->isNegativeSpell()) + { //negative spells like weakness are defined in json with negative numbers, so we need do same here power = -1 * power; } @@ -222,14 +223,14 @@ void Timed::prepareEffects(SetStackEffect & sse, const Mechanics * m, const Effe b.val += power; } } - else if(addedValueBonus) + if(addedValueBonus) { for(Bonus& b : buffer) { b.val += addedValueBonus->additionalInfo[0]; } } - else if(fixedValueBonus) + if(fixedValueBonus) { for(Bonus& b : buffer) {