From 1685641357562523d589acf48725896224a6edf6 Mon Sep 17 00:00:00 2001 From: Henning Koehler Date: Sat, 17 Mar 2018 21:46:16 +1300 Subject: [PATCH] Flexible necromancy (#430) * made IMPROVED_NECROMANCY bonus configurable * updated cloak of the undead king --- config/artifacts.json | 16 ++++- lib/HeroBonus.h | 2 +- lib/ResourceSet.cpp | 10 ++++ lib/ResourceSet.h | 1 + lib/mapObjects/CGHeroInstance.cpp | 97 +++++++++++++++++++++---------- 5 files changed, 91 insertions(+), 35 deletions(-) diff --git a/config/artifacts.json b/config/artifacts.json index 869e33dc6..27d544dd5 100644 --- a/config/artifacts.json +++ b/config/artifacts.json @@ -1884,9 +1884,19 @@ { "bonuses" : [ { - "type" : "IMPROVED_NECROMANCY", //TODO: more flexible? - "val" : 0, - "valueType" : "BASE_NUMBER" + "type" : "IMPROVED_NECROMANCY", + "subtype" : "creature.walkingDead", + "addInfo" : 1 + }, + { + "type" : "IMPROVED_NECROMANCY", + "subtype" : "creature.wight", + "addInfo" : 2 + }, + { + "type" : "IMPROVED_NECROMANCY", + "subtype" : "creature.lich", + "addInfo" : 3 } ], "index" : 130, diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 6dbf89c20..6badbf6c4 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -147,7 +147,7 @@ public: BONUS_NAME(MAGIC_SCHOOL_SKILL) /* //eg. for magic plains terrain, subtype: school of magic (0 - all, 1 - fire, 2 - air, 4 - water, 8 - earth), value - level*/ \ BONUS_NAME(FREE_SHOOTING) /*stacks can shoot even if otherwise blocked (sharpshooter's bow effect)*/ \ BONUS_NAME(OPENING_BATTLE_SPELL) /*casts a spell at expert level at beginning of battle, val - spell power, subtype - spell id*/ \ - BONUS_NAME(IMPROVED_NECROMANCY) /*allows Necropolis units other than skeletons to be raised by necromancy*/ \ + BONUS_NAME(IMPROVED_NECROMANCY) /* raise more powerful creatures: subtype - creature type raised, addInfo - [required necromancy level, required stack level] */ \ BONUS_NAME(CREATURE_GROWTH_PERCENT) /*increases growth of all units in all towns, val - percentage*/ \ BONUS_NAME(FREE_SHIP_BOARDING) /*movement points preserved with ship boarding and landing*/ \ BONUS_NAME(NO_TYPE) \ diff --git a/lib/ResourceSet.cpp b/lib/ResourceSet.cpp index 320c2ebf7..06d5c62f4 100644 --- a/lib/ResourceSet.cpp +++ b/lib/ResourceSet.cpp @@ -13,6 +13,8 @@ #include "StringConstants.h" #include "JsonNode.h" #include "serializer/JsonSerializeFormat.h" +#include "VCMI_Lib.h" +#include "mapObjects/CObjectHandler.h" Res::ResourceSet::ResourceSet() { @@ -86,6 +88,14 @@ bool Res::canAfford(const ResourceSet &res, const ResourceSet &price) return true; } +TResourceCap Res::ResourceSet::marketValue() const +{ + TResourceCap total = 0; + for(int i = 0; i < GameConstants::RESOURCE_QUANTITY; i++) + total += static_cast(VLC->objh->resVals[i]) * static_cast(operator[](i)); + return total; +} + std::string Res::ResourceSet::toString() const { std::ostringstream out; diff --git a/lib/ResourceSet.h b/lib/ResourceSet.h index 9961d9914..d143e6fad 100644 --- a/lib/ResourceSet.h +++ b/lib/ResourceSet.h @@ -133,6 +133,7 @@ namespace Res DLL_LINKAGE bool nonZero() const; //returns true if at least one value is non-zero; DLL_LINKAGE bool canAfford(const ResourceSet &price) const; DLL_LINKAGE bool canBeAfforded(const ResourceSet &res) const; + DLL_LINKAGE TResourceCap marketValue() const; DLL_LINKAGE std::string toString() const; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index fac58f029..1f3d3e1f1 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -804,45 +804,80 @@ bool CGHeroInstance::canLearnSpell(const CSpell * spell) const CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &battleResult) const { const ui8 necromancyLevel = getSecSkillLevel(SecondarySkill::NECROMANCY); - - // Hero knows necromancy or has Necromancer Cloak + // need skill or cloak of undead king - lesser artifacts don't work without skill if (necromancyLevel > 0 || hasBonusOfType(Bonus::IMPROVED_NECROMANCY)) { - double necromancySkill = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::NECROMANCY)/100.0; + double necromancySkill = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::NECROMANCY) / 100.0; vstd::amin(necromancySkill, 1.0); //it's impossible to raise more creatures than all... const std::map &casualties = battleResult.casualties[!battleResult.winner]; - ui32 raisedUnits = 0; - - // Figure out what to raise and how many. - const CreatureID creatureTypes[] = {CreatureID::SKELETON, CreatureID::WALKING_DEAD, CreatureID::WIGHTS, CreatureID::LICHES}; - const bool improvedNecromancy = hasBonusOfType(Bonus::IMPROVED_NECROMANCY); - const CCreature *raisedUnitType = VLC->creh->creatures[creatureTypes[improvedNecromancy ? necromancyLevel : 0]]; - const ui32 raisedUnitHP = raisedUnitType->MaxHealth(); - - //calculate creatures raised from each defeated stack - for (auto & casualtie : casualties) + // figure out what to raise - pick strongest creature meeting requirements + CreatureID creatureTypeRaised = CreatureID::SKELETON; + int requiredCasualtyLevel = 1; + const TBonusListPtr improvedNecromancy = getBonuses(Selector::type(Bonus::IMPROVED_NECROMANCY)); + if(!improvedNecromancy->empty()) { - // Get lost enemy hit points convertible to units. - CCreature * c = VLC->creh->creatures[casualtie.first]; - - const ui32 raisedHP = c->MaxHealth() * casualtie.second * necromancySkill; - raisedUnits += std::min(raisedHP / raisedUnitHP, casualtie.second * necromancySkill); //limit to % of HP and % of original stack count + auto getCreatureID = [necromancyLevel](std::shared_ptr bonus) -> CreatureID + { + const CreatureID legacyTypes[] = {CreatureID::SKELETON, CreatureID::WALKING_DEAD, CreatureID::WIGHTS, CreatureID::LICHES}; + return CreatureID(bonus->subtype >= 0 ? bonus->subtype : legacyTypes[necromancyLevel]); + }; + int maxCasualtyLevel = 1; + for(auto & casualty : casualties) + vstd::amax(maxCasualtyLevel, VLC->creh->creatures[casualty.first]->level); + // pick best bonus available + std::shared_ptr topPick; + for(std::shared_ptr newPick : *improvedNecromancy) + { + // addInfo[0] = required necromancy skill, addInfo[1] = required casualty level + if(newPick->additionalInfo[0] > necromancyLevel || newPick->additionalInfo[1] > maxCasualtyLevel) + continue; + if(!topPick) + { + topPick = newPick; + } + else + { + auto quality = [getCreatureID](std::shared_ptr pick) -> std::vector + { + const CCreature * c = VLC->creh->creatures[getCreatureID(pick)]; + std::vector v = {c->level, static_cast(c->cost.marketValue()), -pick->additionalInfo[1]}; + return v; + }; + if(quality(topPick) < quality(newPick)) + topPick = newPick; + } + } + if(topPick) + { + creatureTypeRaised = getCreatureID(topPick); + requiredCasualtyLevel = std::max(topPick->additionalInfo[1], 1); + } } - - // Make room for new units. - SlotID slot = getSlotFor(raisedUnitType->idNumber); - if (slot == SlotID()) + // raise upgraded creature (at 2/3 rate) if no space available otherwise + if(getSlotFor(creatureTypeRaised) == SlotID()) { - // If there's no room for unit, try it's upgraded version 2/3rds the size. - raisedUnitType = VLC->creh->creatures[*raisedUnitType->upgrades.begin()]; - raisedUnits = (raisedUnits*2)/3; - - slot = getSlotFor(raisedUnitType->idNumber); + for(CreatureID upgraded : VLC->creh->creatures[creatureTypeRaised]->upgrades) + { + if(getSlotFor(upgraded) != SlotID()) + { + creatureTypeRaised = upgraded; + necromancySkill *= 2/3.0; + break; + } + } } - if (raisedUnits <= 0) - raisedUnits = 1; - - return CStackBasicDescriptor(raisedUnitType->idNumber, raisedUnits); + // calculate number of creatures raised - low level units contribute at 50% rate + const double raisedUnitHealth = VLC->creh->creatures[creatureTypeRaised]->MaxHealth(); + double raisedUnits = 0; + for(auto & casualty : casualties) + { + const CCreature * c = VLC->creh->creatures[casualty.first]; + double raisedFromCasualty = std::min(c->MaxHealth() / raisedUnitHealth, 1.0) * casualty.second * necromancySkill; + if(c->level < requiredCasualtyLevel) + raisedFromCasualty *= 0.5; + raisedUnits += raisedFromCasualty; + } + return CStackBasicDescriptor(creatureTypeRaised, std::max(static_cast(raisedUnits), 1)); } return CStackBasicDescriptor();