Pārlūkot izejas kodu

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 2 gadi atpakaļ
vecāks
revīzija
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;
 		}