1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Flexible necromancy (#430)

* made IMPROVED_NECROMANCY bonus configurable
* updated cloak of the undead king
This commit is contained in:
Henning Koehler 2018-03-17 21:46:16 +13:00 committed by ArseniyShestakov
parent e666b2740c
commit 1685641357
5 changed files with 91 additions and 35 deletions

View File

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

View File

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

View File

@ -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<TResourceCap>(VLC->objh->resVals[i]) * static_cast<TResourceCap>(operator[](i));
return total;
}
std::string Res::ResourceSet::toString() const
{
std::ostringstream out;

View File

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

View File

@ -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;
vstd::amin(necromancySkill, 1.0); //it's impossible to raise more creatures than all...
const std::map<ui32,si32> &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<ui32>(raisedHP / raisedUnitHP, casualtie.second * necromancySkill); //limit to % of HP and % of original stack count
}
// Make room for new units.
SlotID slot = getSlotFor(raisedUnitType->idNumber);
if (slot == SlotID())
auto getCreatureID = [necromancyLevel](std::shared_ptr<Bonus> bonus) -> CreatureID
{
// 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);
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<Bonus> topPick;
for(std::shared_ptr<Bonus> 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;
}
if (raisedUnits <= 0)
raisedUnits = 1;
return CStackBasicDescriptor(raisedUnitType->idNumber, raisedUnits);
else
{
auto quality = [getCreatureID](std::shared_ptr<Bonus> pick) -> std::vector<int>
{
const CCreature * c = VLC->creh->creatures[getCreatureID(pick)];
std::vector<int> v = {c->level, static_cast<int>(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);
}
}
// raise upgraded creature (at 2/3 rate) if no space available otherwise
if(getSlotFor(creatureTypeRaised) == SlotID())
{
for(CreatureID upgraded : VLC->creh->creatures[creatureTypeRaised]->upgrades)
{
if(getSlotFor(upgraded) != SlotID())
{
creatureTypeRaised = upgraded;
necromancySkill *= 2/3.0;
break;
}
}
}
// 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<int>(raisedUnits), 1));
}
return CStackBasicDescriptor();