Ver Fonte

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.
Konstantin há 2 anos atrás
pai
commit
84f53485e2

+ 0 - 9
client/battle/BattleEffectsController.cpp

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

+ 13 - 1
config/creatures/special.json

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

+ 4 - 3
config/heroes/castle.json

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

+ 4 - 3
config/heroes/fortress.json

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

+ 4 - 3
config/heroes/rampart.json

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

+ 5 - 5
config/skills.json

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

+ 57 - 1
config/spells/vcmiAbility.json

@@ -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"
+			}
+		}
+	}
 }

+ 12 - 6
lib/spells/TargetCondition.cpp

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

+ 11 - 0
lib/spells/effects/Heal.cpp

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

+ 6 - 32
server/CGameHandler.cpp

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