1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

vcmi: skill-agnostic first aid

Now first aid is passive battle spell, and skill just bumps
specific spell power for this spell. Everything about healing
is handled into Heal spell effect.
This commit is contained in:
Konstantin 2023-02-21 21:46:08 +03:00
parent a0987313ba
commit 84f53485e2
10 changed files with 116 additions and 63 deletions

View File

@ -110,15 +110,6 @@ void BattleEffectsController::startAction(const BattleAction* action)
break;
}
//displaying special abilities
auto actionTarget = action->getTarget(owner.curInt->cb.get());
switch(action->actionType)
{
case EActionType::STACK_HEAL:
displayEffect(EBattleEffect::REGENERATION, "REGENER", actionTarget.at(0).hexValue);
break;
}
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
}

View File

@ -78,7 +78,19 @@
"index": 147,
"level": 0,
"faction": "neutral",
"abilities": { "heals" : { "type" : "HEALER" } },
"abilities":
{
"heals" : {
"type" : "HEALER" ,
"subtype" : "spell.firstAid"
},
"power" : {
"type" : "SPECIFIC_SPELL_POWER",
"subtype" : "spell.firstAid",
"val" : 10,
"valueType" : "BASE_NUMBER"
}
},
"graphics" :
{
"animation": "SMTENT.DEF"

View File

@ -152,11 +152,12 @@
"specialty" : {
"bonuses" : {
"firstAid" : {
"subtype" : "skill.firstAid",
"type" : "SECONDARY_SKILL_PREMY",
"subtype" : "spell.firstAid",
"type" : "SPECIFIC_SPELL_POWER",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
}

View File

@ -214,11 +214,12 @@
"specialty" : {
"bonuses" : {
"firstAid" : {
"subtype" : "skill.firstAid",
"type" : "SECONDARY_SKILL_PREMY",
"subtype" : "spell.firstAid",
"type" : "SPECIFIC_SPELL_POWER",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
}

View File

@ -220,11 +220,12 @@
"specialty" : {
"bonuses" : {
"firstAid" : {
"subtype" : "skill.firstAid",
"type" : "SECONDARY_SKILL_PREMY",
"subtype" : "spell.firstAid",
"type" : "SPECIFIC_SPELL_POWER",
"updater" : "TIMES_HERO_LEVEL",
"val" : 5,
"valueType" : "PERCENT_TO_BASE"
"valueType" : "PERCENT_TO_TARGET_TYPE",
"targetSourceType" : "SECONDARY_SKILL"
}
}
}

View File

@ -786,8 +786,8 @@
"base" : {
"effects" : {
"main" : {
"subtype" : "skill.firstAid",
"type" : "SECONDARY_SKILL_PREMY",
"subtype" : "spell.firstAid",
"type" : "SPECIFIC_SPELL_POWER",
"valueType" : "BASE_NUMBER"
},
"ctrl" : {
@ -800,17 +800,17 @@
},
"basic" : {
"effects" : {
"main" : { "val" : 50 }
"main" : { "val" : 40 }
}
},
"advanced" : {
"effects" : {
"main" : { "val" : 75 }
"main" : { "val" : 65 }
}
},
"expert" : {
"effects" : {
"main" : { "val" : 100 }
"main" : { "val" : 90 }
}
}
}

View File

@ -45,5 +45,61 @@
"bonus.GARGOYLE" : "absolute"
}
}
}
},
"firstAid" : {
"targetType" : "CREATURE",
"type": "ability",
"name": "First Aid",
"school" : {},
"level": 1,
"power": 10,
"defaultGainChance": 0,
"gainChance": {},
"animation":{
"affect":["SP12_"]
},
"sounds": {
"cast": "REGENER"
},
"levels" : {
"base":{
"description" : "",
"aiValue" : 0,
"power" : 10,
"cost" : 0,
"targetModifier":{"smart":true},
"battleEffects":{
"heal":{
"type":"core:heal",
"healLevel":"heal",
"healPower":"permanent",
"optional":true
}
},
"range" : "0"
},
"none" :{
"power" : 10
},
"basic" :{
"power" : 50
},
"advanced" :{
"power" : 75
},
"expert" :{
"power" : 100
}
},
"flags" : {
"positive": true
},
"targetCondition" : {
"nonMagical" : true,
"noneOf" : {
"bonus.SIEGE_WEAPON" : "absolute"
}
}
}
}

View File

@ -389,6 +389,7 @@ bool TargetCondition::isReceptive(const Mechanics * m, const battle::Unit * targ
void TargetCondition::serializeJson(JsonSerializeFormat & handler, const ItemFactory * itemFactory)
{
bool isNonMagical = false;
if(handler.saving)
{
logGlobal->error("Spell target condition saving is not supported");
@ -399,13 +400,18 @@ void TargetCondition::serializeJson(JsonSerializeFormat & handler, const ItemFac
normal.clear();
negation.clear();
absolute.push_back(itemFactory->createAbsoluteLevel());
absolute.push_back(itemFactory->createAbsoluteSpell());
normal.push_back(itemFactory->createElemental());
normal.push_back(itemFactory->createNormalLevel());
normal.push_back(itemFactory->createNormalSpell());
negation.push_back(itemFactory->createReceptiveFeature());
negation.push_back(itemFactory->createImmunityNegation());
handler.serializeBool("nonMagical", isNonMagical);
if(!isNonMagical)
{
absolute.push_back(itemFactory->createAbsoluteLevel());
normal.push_back(itemFactory->createElemental());
normal.push_back(itemFactory->createNormalLevel());
normal.push_back(itemFactory->createNormalSpell());
negation.push_back(itemFactory->createReceptiveFeature());
negation.push_back(itemFactory->createImmunityNegation());
}
{
auto anyOf = handler.enterStruct("anyOf");

View File

@ -15,6 +15,7 @@
#include "../../NetPacks.h"
#include "../../battle/IBattleState.h"
#include "../../battle/CUnitState.h"
#include "../../battle/CBattleInfoCallback.h"
#include "../../battle/Unit.h"
#include "../../serializer/JsonSerializeFormat.h"
@ -128,6 +129,16 @@ void Heal::prepareHealEffect(int64_t value, BattleUnitsChanged & pack, BattleLog
resurrectText.addReplacement(resurrectedCount);
logMessage.lines.push_back(std::move(resurrectText));
}
else if (unitHPgained > 0 && m->caster->getCasterUnitId() >= 0) //Show text about healed HP if healed by unit
{
MetaString healText;
auto casterUnit = dynamic_cast<const battle::CUnitState*>(m->caster)->acquire();
healText.addTxt(MetaString::GENERAL_TXT, 414);
casterUnit->addNameReplacement(healText, false);
state->addNameReplacement(healText, false);
healText.addReplacement((int)unitHPgained);
logMessage.lines.push_back(std::move(healText));
}
if(unitHPgained > 0)
{

View File

@ -5010,7 +5010,6 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
case EActionType::STACK_HEAL: //healing with First Aid Tent
{
auto wrapper = wrapAction(ba);
const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side);
const CStack * healer = gs->curB->battleGetStackByID(ba.stackNumber);
if(target.size() < 1)
@ -5021,48 +5020,23 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
}
const battle::Unit * destStack = nullptr;
std::shared_ptr<const Bonus> healerAbility = stack->getBonusLocalFirst(Selector::type()(Bonus::HEALER));
if(target.at(0).unitValue)
destStack = target.at(0).unitValue;
else
destStack = gs->curB->battleGetStackByPos(target.at(0).hexValue);
if(healer == nullptr || destStack == nullptr || !healer->hasBonusOfType(Bonus::HEALER))
if(healer == nullptr || destStack == nullptr || !healerAbility || healerAbility->subtype < 0)
{
complain("There is either no healer, no destination, or healer cannot heal :P");
}
else
{
int64_t toHeal = healer->getCount() * std::max(10, attackingHero->valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::FIRST_AID));
//TODO: allow resurrection for mods
auto state = destStack->acquireState();
state->heal(toHeal, EHealLevel::HEAL, EHealPower::PERMANENT);
if(toHeal == 0)
{
logGlobal->warn("Nothing to heal");
}
else
{
BattleUnitsChanged pack;
BattleLogMessage message;
MetaString text;
text.addTxt(MetaString::GENERAL_TXT, 414);
healer->addNameReplacement(text, false);
destStack->addNameReplacement(text, false);
text.addReplacement((int)toHeal);
message.lines.push_back(text);
UnitChanges info(state->unitId(), UnitChanges::EOperation::RESET_STATE);
info.healthDelta = toHeal;
state->save(info.data);
pack.changedStacks.push_back(info);
sendAndApply(&pack);
sendAndApply(&message);
}
const CSpell * spell = SpellID(healerAbility->subtype).toSpell();
spells::BattleCast parameters(gs->curB, healer, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can heal infinitely by first aid tent
parameters.setSpellLevel(0);
parameters.cast(spellEnv, target);
}
break;
}