Browse Source

max range for spellcaster

Laserlicht 3 months ago
parent
commit
9bfe3a8195

+ 6 - 1
docs/modders/Bonus/Bonus_Types.md

@@ -882,7 +882,7 @@ Affected unit will not use spellcast as default attack option
 
 
 ### SPELLCASTER
 ### SPELLCASTER
 
 
-Affected units can cast a spell as targeted action (Archangel, Faerie Dragon). Use CASTS bonus to specify how many times per combat creature can use spellcasting. Use SPECIFIC_SPELL_POWER, CREATURE_SPELL_POWER or CREATURE_ENCHANT_POWER bonuses to set spell power.
+Affected units can cast a spell as targeted action (Archangel, Faerie Dragon). Use CASTS bonus to specify how many times per combat creature can use spellcasting. Use SPECIFIC_SPELL_POWER, CREATURE_SPELL_POWER or CREATURE_ENCHANT_POWER bonuses to set spell power. SPECIFIC_SPELL_RANGE bonus can be used to limit range of spell.
 
 
 - subtype: spell identifier
 - subtype: spell identifier
 - val: spell mastery level
 - val: spell mastery level
@@ -933,6 +933,11 @@ Determines how many times per combat affected creature can cast its targeted spe
 - value: Used for Thunderbolt and Resurrection cast by units (multiplied by stack size). Also used for Healing secondary skill (for core:spell.firstAid used by First Aid tent)
 - value: Used for Thunderbolt and Resurrection cast by units (multiplied by stack size). Also used for Healing secondary skill (for core:spell.firstAid used by First Aid tent)
 - subtype - spell id
 - subtype - spell id
 
 
+### SPECIFIC_SPELL_RANGE
+
+- value: Can be used to limit range of spells casted by creatures.
+- subtype - spell id
+
 ### CREATURE_SPELL_POWER
 ### CREATURE_SPELL_POWER
 
 
 - value: Spell Power of offensive spell cast unit, multiplied by 100. ie. Faerie Dragons have value fo 500, which gives them 5 Spell Power for each unit in the stack.
 - value: Spell Power of offensive spell cast unit, multiplied by 100. ie. Faerie Dragons have value fo 500, which gives them 5 Spell Power for each unit in the stack.

+ 6 - 0
include/vcmi/spells/Caster.h

@@ -10,6 +10,8 @@
 
 
 #pragma once
 #pragma once
 
 
+#include "../../../lib/battle/BattleHex.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
 class PlayerColor;
 class PlayerColor;
@@ -36,6 +38,7 @@ public:
 	virtual ~Caster() = default;
 	virtual ~Caster() = default;
 
 
 	virtual int32_t getCasterUnitId() const = 0;
 	virtual int32_t getCasterUnitId() const = 0;
+	virtual BattleHex getCasterPosition() const = 0;
 
 
 	/// returns level on which given spell would be cast by this(0 - none, 1 - basic etc);
 	/// returns level on which given spell would be cast by this(0 - none, 1 - basic etc);
 	/// caster may not know this spell at all
 	/// caster may not know this spell at all
@@ -60,6 +63,9 @@ public:
 	///damage/heal override(ignores spell configuration, effect level and effect power)
 	///damage/heal override(ignores spell configuration, effect level and effect power)
 	virtual int64_t getEffectValue(const Spell * spell) const = 0;
 	virtual int64_t getEffectValue(const Spell * spell) const = 0;
 
 
+	///maximal range of effect
+	virtual int64_t getEffectRange(const Spell * spell) const = 0;
+
 	virtual PlayerColor getCasterOwner() const = 0;
 	virtual PlayerColor getCasterOwner() const = 0;
 
 
 	///only name substitution
 	///only name substitution

+ 10 - 0
lib/battle/CUnitState.cpp

@@ -407,6 +407,11 @@ int32_t CUnitState::getCasterUnitId() const
 	return static_cast<int32_t>(unitId());
 	return static_cast<int32_t>(unitId());
 }
 }
 
 
+BattleHex CUnitState::getCasterPosition() const
+{
+	return getPosition();
+}
+
 const CGHeroInstance * CUnitState::getHeroCaster() const
 const CGHeroInstance * CUnitState::getHeroCaster() const
 {
 {
 	return nullptr;
 	return nullptr;
@@ -453,6 +458,11 @@ int64_t CUnitState::getEffectValue(const spells::Spell * spell) const
 	return static_cast<int64_t>(getCount()) * valOfBonuses(BonusType::SPECIFIC_SPELL_POWER, BonusSubtypeID(spell->getId()));
 	return static_cast<int64_t>(getCount()) * valOfBonuses(BonusType::SPECIFIC_SPELL_POWER, BonusSubtypeID(spell->getId()));
 }
 }
 
 
+int64_t CUnitState::getEffectRange(const spells::Spell * spell) const
+{
+	return valOfBonuses(BonusType::SPECIFIC_SPELL_RANGE, BonusSubtypeID(spell->getId()));
+}
+
 PlayerColor CUnitState::getCasterOwner() const
 PlayerColor CUnitState::getCasterOwner() const
 {
 {
 	return env->unitEffectiveOwner(this);
 	return env->unitEffectiveOwner(this);

+ 2 - 0
lib/battle/CUnitState.h

@@ -170,6 +170,7 @@ public:
 	int32_t creatureIconIndex() const override;
 	int32_t creatureIconIndex() const override;
 
 
 	int32_t getCasterUnitId() const override;
 	int32_t getCasterUnitId() const override;
+	BattleHex getCasterPosition() const override;
 
 
 	int32_t getSpellSchoolLevel(const spells::Spell * spell, SpellSchool * outSelectedSchool = nullptr) const override;
 	int32_t getSpellSchoolLevel(const spells::Spell * spell, SpellSchool * outSelectedSchool = nullptr) const override;
 	int32_t getEffectLevel(const spells::Spell * spell) const override;
 	int32_t getEffectLevel(const spells::Spell * spell) const override;
@@ -180,6 +181,7 @@ public:
 	int32_t getEffectPower(const spells::Spell * spell) const override;
 	int32_t getEffectPower(const spells::Spell * spell) const override;
 	int32_t getEnchantPower(const spells::Spell * spell) const override;
 	int32_t getEnchantPower(const spells::Spell * spell) const override;
 	int64_t getEffectValue(const spells::Spell * spell) const override;
 	int64_t getEffectValue(const spells::Spell * spell) const override;
+	int64_t getEffectRange(const spells::Spell * spell) const override;
 
 
 	PlayerColor getCasterOwner() const override;
 	PlayerColor getCasterOwner() const override;
 	const CGHeroInstance * getHeroCaster() const override;
 	const CGHeroInstance * getHeroCaster() const override;

+ 1 - 0
lib/bonuses/BonusEnum.h

@@ -192,6 +192,7 @@ class JsonNode;
 	BONUS_NAME(FULL_MAP_SCOUTING) /*Skyship*/\
 	BONUS_NAME(FULL_MAP_SCOUTING) /*Skyship*/\
 	BONUS_NAME(FULL_MAP_DARKNESS) /*opposite to Skyship*/\
 	BONUS_NAME(FULL_MAP_DARKNESS) /*opposite to Skyship*/\
 	BONUS_NAME(TRANSMUTATION_IMMUNITY) /*blocks TRANSMUTATION bonus*/\
 	BONUS_NAME(TRANSMUTATION_IMMUNITY) /*blocks TRANSMUTATION bonus*/\
+	BONUS_NAME(SPECIFIC_SPELL_RANGE) /* value used for allowed spell range, subtype - spell id */\
 	/* end of list */
 	/* end of list */
 
 
 
 

+ 1 - 0
lib/json/JsonBonus.cpp

@@ -130,6 +130,7 @@ static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const Jso
 		case BonusType::SPECIAL_PECULIAR_ENCHANT:
 		case BonusType::SPECIAL_PECULIAR_ENCHANT:
 		case BonusType::SPECIAL_SPELL_LEV:
 		case BonusType::SPECIAL_SPELL_LEV:
 		case BonusType::SPECIFIC_SPELL_DAMAGE:
 		case BonusType::SPECIFIC_SPELL_DAMAGE:
+		case BonusType::SPECIFIC_SPELL_RANGE:
 		case BonusType::SPELL:
 		case BonusType::SPELL:
 		case BonusType::OPENING_BATTLE_SPELL:
 		case BonusType::OPENING_BATTLE_SPELL:
 		case BonusType::SPELL_LIKE_ATTACK:
 		case BonusType::SPELL_LIKE_ATTACK:

+ 10 - 0
lib/mapObjects/CGHeroInstance.cpp

@@ -783,6 +783,11 @@ int32_t CGHeroInstance::getCasterUnitId() const
 	return id.getNum();
 	return id.getNum();
 }
 }
 
 
+BattleHex CGHeroInstance::getCasterPosition() const
+{
+	return BattleHex::INVALID;
+}
+
 int32_t CGHeroInstance::getSpellSchoolLevel(const spells::Spell * spell, SpellSchool * outSelectedSchool) const
 int32_t CGHeroInstance::getSpellSchoolLevel(const spells::Spell * spell, SpellSchool * outSelectedSchool) const
 {
 {
 	int32_t skill = -1; //skill level
 	int32_t skill = -1; //skill level
@@ -860,6 +865,11 @@ int64_t CGHeroInstance::getEffectValue(const spells::Spell * spell) const
 	return 0;
 	return 0;
 }
 }
 
 
+int64_t CGHeroInstance::getEffectRange(const spells::Spell * spell) const
+{
+	return 0;
+}
+
 PlayerColor CGHeroInstance::getCasterOwner() const
 PlayerColor CGHeroInstance::getCasterOwner() const
 {
 {
 	return tempOwner;
 	return tempOwner;

+ 2 - 0
lib/mapObjects/CGHeroInstance.h

@@ -278,6 +278,7 @@ public:
 
 
 	///spells::Caster
 	///spells::Caster
 	int32_t getCasterUnitId() const override;
 	int32_t getCasterUnitId() const override;
+	BattleHex getCasterPosition() const override;
 	int32_t getSpellSchoolLevel(const spells::Spell * spell, SpellSchool * outSelectedSchool = nullptr) const override;
 	int32_t getSpellSchoolLevel(const spells::Spell * spell, SpellSchool * outSelectedSchool = nullptr) const override;
 	int64_t getSpellBonus(const spells::Spell * spell, int64_t base, const battle::Unit * affectedStack) const override;
 	int64_t getSpellBonus(const spells::Spell * spell, int64_t base, const battle::Unit * affectedStack) const override;
 	int64_t getSpecificSpellBonus(const spells::Spell * spell, int64_t base) const override;
 	int64_t getSpecificSpellBonus(const spells::Spell * spell, int64_t base) const override;
@@ -286,6 +287,7 @@ public:
 	int32_t getEffectPower(const spells::Spell * spell) const override;
 	int32_t getEffectPower(const spells::Spell * spell) const override;
 	int32_t getEnchantPower(const spells::Spell * spell) const override;
 	int32_t getEnchantPower(const spells::Spell * spell) const override;
 	int64_t getEffectValue(const spells::Spell * spell) const override;
 	int64_t getEffectValue(const spells::Spell * spell) const override;
+	int64_t getEffectRange(const spells::Spell * spell) const override;
 
 
 	PlayerColor getCasterOwner() const override;
 	PlayerColor getCasterOwner() const override;
 	const CGHeroInstance * getHeroCaster() const override;
 	const CGHeroInstance * getHeroCaster() const override;

+ 8 - 0
lib/spells/BattleSpellMechanics.cpp

@@ -226,6 +226,14 @@ bool BattleSpellMechanics::canBeCastAt(const Target & target, Problem & problem)
 		mainTarget = battle()->battleGetUnitByPos(target.front().hexValue, true);
 		mainTarget = battle()->battleGetUnitByPos(target.front().hexValue, true);
 	}
 	}
 
 
+	int range = caster->getEffectRange(getSpell());
+	if(mode != Mode::HERO && range > 0 )
+	{
+		int distance = BattleHex::getDistance(spellTarget.front().hexValue, caster->getCasterPosition());
+		if(distance > range)
+			return false;
+	}
+
 	if (!getSpell()->canCastOnSelf() && !getSpell()->canCastOnlyOnSelf())
 	if (!getSpell()->canCastOnSelf() && !getSpell()->canCastOnlyOnSelf())
 	{
 	{
 		if(mainTarget && mainTarget == caster)
 		if(mainTarget && mainTarget == caster)

+ 8 - 0
lib/spells/ObstacleCasterProxy.cpp

@@ -58,6 +58,14 @@ int64_t ObstacleCasterProxy::getEffectValue(const Spell * spell) const
 		return obs.minimalDamage;
 		return obs.minimalDamage;
 }
 }
 
 
+int64_t ObstacleCasterProxy::getEffectRange(const Spell * spell) const
+{
+	if(actualCaster)
+		actualCaster->getEffectRange(spell);
+
+	return 0;
+}
+
 int32_t SilentCaster::manaLimit() const
 int32_t SilentCaster::manaLimit() const
 {
 {
 	return 0;
 	return 0;

+ 1 - 0
lib/spells/ObstacleCasterProxy.h

@@ -42,6 +42,7 @@ public:
 	int32_t getEffectPower(const Spell * spell) const override;
 	int32_t getEffectPower(const Spell * spell) const override;
 	int32_t getEnchantPower(const Spell * spell) const override;
 	int32_t getEnchantPower(const Spell * spell) const override;
 	int64_t getEffectValue(const Spell * spell) const override;
 	int64_t getEffectValue(const Spell * spell) const override;
+	int64_t getEffectRange(const Spell * spell) const override;
 
 
 private:
 private:
 	const SpellCreatedObstacle & obs;
 	const SpellCreatedObstacle & obs;

+ 16 - 0
lib/spells/ProxyCaster.cpp

@@ -36,6 +36,14 @@ int32_t ProxyCaster::getCasterUnitId() const
 	return -1;
 	return -1;
 }
 }
 
 
+BattleHex ProxyCaster::getCasterPosition() const
+{
+	if(actualCaster)
+		return actualCaster->getCasterPosition();
+
+	return BattleHex::INVALID;
+}
+
 int32_t ProxyCaster::getSpellSchoolLevel(const Spell * spell, SpellSchool * outSelectedSchool) const
 int32_t ProxyCaster::getSpellSchoolLevel(const Spell * spell, SpellSchool * outSelectedSchool) const
 {
 {
 	if(actualCaster)
 	if(actualCaster)
@@ -92,6 +100,14 @@ int64_t ProxyCaster::getEffectValue(const Spell * spell) const
 	return 0;
 	return 0;
 }
 }
 
 
+int64_t ProxyCaster::getEffectRange(const Spell * spell) const
+{
+	if(actualCaster)
+		return actualCaster->getEffectRange(spell);
+
+	return 0;
+}
+
 PlayerColor ProxyCaster::getCasterOwner() const
 PlayerColor ProxyCaster::getCasterOwner() const
 {
 {
 	if(actualCaster)
 	if(actualCaster)

+ 2 - 0
lib/spells/ProxyCaster.h

@@ -24,6 +24,7 @@ public:
 	virtual ~ProxyCaster();
 	virtual ~ProxyCaster();
 
 
 	int32_t getCasterUnitId() const override;
 	int32_t getCasterUnitId() const override;
+	BattleHex getCasterPosition() const override;
 	int32_t getSpellSchoolLevel(const Spell * spell, SpellSchool * outSelectedSchool = nullptr) const override;
 	int32_t getSpellSchoolLevel(const Spell * spell, SpellSchool * outSelectedSchool = nullptr) const override;
 	int32_t getEffectLevel(const Spell * spell) const override;
 	int32_t getEffectLevel(const Spell * spell) const override;
 	int64_t getSpellBonus(const Spell * spell, int64_t base, const battle::Unit * affectedStack) const override;
 	int64_t getSpellBonus(const Spell * spell, int64_t base, const battle::Unit * affectedStack) const override;
@@ -31,6 +32,7 @@ public:
 	int32_t getEffectPower(const Spell * spell) const override;
 	int32_t getEffectPower(const Spell * spell) const override;
 	int32_t getEnchantPower(const Spell * spell) const override;
 	int32_t getEnchantPower(const Spell * spell) const override;
 	int64_t getEffectValue(const Spell * spell) const override;
 	int64_t getEffectValue(const Spell * spell) const override;
+	int64_t getEffectRange(const Spell * spell) const override;
 	PlayerColor getCasterOwner() const override;
 	PlayerColor getCasterOwner() const override;
 	void getCasterName(MetaString & text) const override;
 	void getCasterName(MetaString & text) const override;
 	void getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const override;
 	void getCastDescription(const Spell * spell, const battle::Units & attacked, MetaString & text) const override;

+ 2 - 0
test/mock/mock_battle_Unit.h

@@ -19,6 +19,7 @@ public:
 	MOCK_CONST_METHOD0(getTreeVersion, int32_t());
 	MOCK_CONST_METHOD0(getTreeVersion, int32_t());
 
 
 	MOCK_CONST_METHOD0(getCasterUnitId, int32_t());
 	MOCK_CONST_METHOD0(getCasterUnitId, int32_t());
+	MOCK_CONST_METHOD0(getCasterPosition, BattleHex());
 	MOCK_CONST_METHOD2(getSpellSchoolLevel, int32_t(const spells::Spell *, SpellSchool *));
 	MOCK_CONST_METHOD2(getSpellSchoolLevel, int32_t(const spells::Spell *, SpellSchool *));
 	MOCK_CONST_METHOD1(getEffectLevel, int32_t(const spells::Spell *));
 	MOCK_CONST_METHOD1(getEffectLevel, int32_t(const spells::Spell *));
 	MOCK_CONST_METHOD3(getSpellBonus, int64_t(const spells::Spell *, int64_t, const battle::Unit *));
 	MOCK_CONST_METHOD3(getSpellBonus, int64_t(const spells::Spell *, int64_t, const battle::Unit *));
@@ -26,6 +27,7 @@ public:
 	MOCK_CONST_METHOD1(getEffectPower, int32_t(const spells::Spell *));
 	MOCK_CONST_METHOD1(getEffectPower, int32_t(const spells::Spell *));
 	MOCK_CONST_METHOD1(getEnchantPower, int32_t(const spells::Spell *));
 	MOCK_CONST_METHOD1(getEnchantPower, int32_t(const spells::Spell *));
 	MOCK_CONST_METHOD1(getEffectValue, int64_t(const spells::Spell *));
 	MOCK_CONST_METHOD1(getEffectValue, int64_t(const spells::Spell *));
+	MOCK_CONST_METHOD1(getEffectRange, int64_t(const spells::Spell *));
 	MOCK_CONST_METHOD0(getCasterOwner, PlayerColor());
 	MOCK_CONST_METHOD0(getCasterOwner, PlayerColor());
 	MOCK_CONST_METHOD1(getCasterName, void(MetaString &));
 	MOCK_CONST_METHOD1(getCasterName, void(MetaString &));
 	MOCK_CONST_METHOD3(getCastDescription, void(const spells::Spell *, const battle::Units &, MetaString &));
 	MOCK_CONST_METHOD3(getCastDescription, void(const spells::Spell *, const battle::Units &, MetaString &));