mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
Merge pull request #3482 from IvanSavenko/extend_hota_bonuses
Extend hota bonuses
This commit is contained in:
commit
a32ef673f7
@ -333,8 +333,6 @@
|
||||
"vcmi.stackExperience.rank.9" : "Master",
|
||||
"vcmi.stackExperience.rank.10" : "Ace",
|
||||
|
||||
"core.bonus.ACCURATE_SHOT.name": "Accurate Shot",
|
||||
"core.bonus.ACCURATE_SHOT.description": "Has (${val}% - penalties) extra kills chance",
|
||||
"core.bonus.ADDITIONAL_ATTACK.name": "Double Strike",
|
||||
"core.bonus.ADDITIONAL_ATTACK.description": "Attacks twice",
|
||||
"core.bonus.ADDITIONAL_RETALIATION.name": "Additional retaliations",
|
||||
|
@ -3,13 +3,6 @@
|
||||
// LEVEL_SPELL_IMMUNITY
|
||||
|
||||
{
|
||||
"ACCURATE_SHOT":
|
||||
{
|
||||
"graphics":
|
||||
{
|
||||
"icon": "zvs/Lib1.res/E_DIST"
|
||||
}
|
||||
},
|
||||
"ADDITIONAL_ATTACK":
|
||||
{
|
||||
"graphics":
|
||||
|
@ -587,6 +587,11 @@ Affected unit will attack units on all hexes that surround attacked hex
|
||||
|
||||
Affected unit will retaliate before enemy attacks, if able
|
||||
|
||||
- subtype:
|
||||
- damageTypeMelee: only melee attacks affected
|
||||
- damageTypeRanged: only ranged attacks affected. Note that unit also requires ability to retaliate in ranged, such as RANGED_RETALIATION bonus
|
||||
- damageTypeAll: any attacks are affected
|
||||
|
||||
### SHOOTS_ALL_ADJACENT
|
||||
|
||||
Affected unit will attack units on all hexes that surround attacked hex in ranged attacks
|
||||
@ -727,14 +732,20 @@ Affected unit will deal additional damage after attack
|
||||
|
||||
### DEATH_STARE
|
||||
|
||||
Affected unit will kill additional units after attack
|
||||
Affected unit will kill additional units after attack. Used for Death stare (Mighty Gorgon) ability and for Accurate Shot (Pirates, HotA)
|
||||
|
||||
- subtype:
|
||||
- deathStareGorgon: random amount
|
||||
- deathStareCommander: fixed amount
|
||||
- deathStareGorgon: only melee attack, random amount of killed units
|
||||
- deathStareNoRangePenalty: only ranged attacks without obstacle (walls) or range penalty
|
||||
- deathStareRangePenalty: only ranged attacks with range penalty
|
||||
- deathStareObstaclePenalty: only ranged attacks with obstacle (walls) penalty
|
||||
- deathStareRangeObstaclePenalty: only ranged attacks with both range and obstacle penalty
|
||||
- deathStareCommander: fixed amount, both melee and ranged attacks
|
||||
- val:
|
||||
- for deathStareGorgon: chance to kill, counted separately for each unit in attacking stack, percentage. At most (stack size \* chance) units can be killed at once. TODO: recheck formula
|
||||
- for deathStareCommander: number of creatures to kill, total amount of killed creatures is (attacker level / defender level) \* val
|
||||
- for all other subtypes: chance to kill, counted separately for each unit in attacking stack, percentage. At most (stack size \* chance) units can be killed at once, rounded up
|
||||
- addInfo:
|
||||
- SpellID to be used as hit effect. If not set - 'deathStare' spell will be used. If set to "accurateShot" battle log messages will use alternative description
|
||||
|
||||
### SPECIAL_CRYSTAL_GENERATION
|
||||
|
||||
@ -744,15 +755,6 @@ If player has affected unit under his control in any army, he will receive addit
|
||||
|
||||
Affected unit will not use spellcast as default attack option
|
||||
|
||||
### ACCURATE_SHOT
|
||||
|
||||
Affected unit will kill additional units after attack, similar to death stare - works only for ranged attack
|
||||
|
||||
- subtype:
|
||||
spell identifier for spell that receives value that should be killed on input, spell.deathStare is used by default, use 'accurateShot' as part of spell name to allow detection for proper battle log description
|
||||
- val:
|
||||
chance to kill, counted separately for each unit in attacking stack, percentage. Chance gets lessened by 2/3 with range penalty and effect won't trigger with wall penalty. At most (stack size \* chance / 100 **[rounded up]**) units can be killed at once. TODO: recheck formula
|
||||
|
||||
## Creature spellcasting and activated abilities
|
||||
|
||||
### SPELLCASTER
|
||||
|
@ -515,7 +515,6 @@ static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const Jso
|
||||
case BonusType::SPECIFIC_SPELL_POWER:
|
||||
case BonusType::ENCHANTED:
|
||||
case BonusType::MORE_DAMAGE_FROM_SPELL:
|
||||
case BonusType::ACCURATE_SHOT:
|
||||
case BonusType::NOT_ACTIVE:
|
||||
{
|
||||
VLC->identifiers()->requestIdentifier( "spell", node, [&subtype](int32_t identifier)
|
||||
@ -538,6 +537,7 @@ static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const Jso
|
||||
case BonusType::NEGATE_ALL_NATURAL_IMMUNITIES:
|
||||
case BonusType::CREATURE_DAMAGE:
|
||||
case BonusType::FLYING:
|
||||
case BonusType::FIRST_STRIKE:
|
||||
case BonusType::GENERAL_DAMAGE_REDUCTION:
|
||||
case BonusType::PERCENTAGE_DAMAGE_BOOST:
|
||||
case BonusType::SOUL_STEAL:
|
||||
|
@ -23,6 +23,10 @@ const BonusCustomSubtype BonusCustomSubtype::heroMovementLand(1);
|
||||
const BonusCustomSubtype BonusCustomSubtype::heroMovementSea(0);
|
||||
const BonusCustomSubtype BonusCustomSubtype::deathStareGorgon(0);
|
||||
const BonusCustomSubtype BonusCustomSubtype::deathStareCommander(1);
|
||||
const BonusCustomSubtype BonusCustomSubtype::deathStareNoRangePenalty(2);
|
||||
const BonusCustomSubtype BonusCustomSubtype::deathStareRangePenalty(3);
|
||||
const BonusCustomSubtype BonusCustomSubtype::deathStareObstaclePenalty(4);
|
||||
const BonusCustomSubtype BonusCustomSubtype::deathStareRangeObstaclePenalty(5);
|
||||
const BonusCustomSubtype BonusCustomSubtype::rebirthRegular(0);
|
||||
const BonusCustomSubtype BonusCustomSubtype::rebirthSpecial(1);
|
||||
const BonusCustomSubtype BonusCustomSubtype::visionsMonsters(0);
|
||||
|
@ -45,6 +45,10 @@ public:
|
||||
|
||||
static const BonusCustomSubtype deathStareGorgon; // 0
|
||||
static const BonusCustomSubtype deathStareCommander;
|
||||
static const BonusCustomSubtype deathStareNoRangePenalty;
|
||||
static const BonusCustomSubtype deathStareRangePenalty;
|
||||
static const BonusCustomSubtype deathStareObstaclePenalty;
|
||||
static const BonusCustomSubtype deathStareRangeObstaclePenalty;
|
||||
|
||||
static const BonusCustomSubtype rebirthRegular; // 0
|
||||
static const BonusCustomSubtype rebirthSpecial; // 1
|
||||
|
@ -174,7 +174,6 @@ class JsonNode;
|
||||
BONUS_NAME(MAX_MORALE) /*cheat bonus*/ \
|
||||
BONUS_NAME(MAX_LUCK) /*cheat bonus*/ \
|
||||
BONUS_NAME(FEROCITY) /*extra attacks, only if at least some creatures killed while attacking target unit, val = amount of additional attacks, additional info = amount of creatures killed to trigger (default 1)*/ \
|
||||
BONUS_NAME(ACCURATE_SHOT) /*HotA Sea Dog-like ability - ranged only, val = full arrow trigger percent, subtype = spell identifier that killed value goes through (death stare by default) - use 'accurateShot' as part of spell name for proper battle log description*/ \
|
||||
BONUS_NAME(ENEMY_ATTACK_REDUCTION) /*in % (value) eg. Nix (HotA)*/ \
|
||||
BONUS_NAME(REVENGE) /*additional damage based on how many units in stack died - formula: sqrt((number of creatures at battle start + 1) * creature health) / (total health now + 1 creature health) - 1) * 100% */ \
|
||||
/* end of list */
|
||||
|
@ -53,6 +53,10 @@ CIdentifierStorage::CIdentifierStorage()
|
||||
registerObject(ModScope::scopeBuiltin(), "bonusSubtype", "heroMovementSea", 0);
|
||||
registerObject(ModScope::scopeBuiltin(), "bonusSubtype", "deathStareGorgon", 0);
|
||||
registerObject(ModScope::scopeBuiltin(), "bonusSubtype", "deathStareCommander", 1);
|
||||
registerObject(ModScope::scopeBuiltin(), "bonusSubtype", "deathStareNoRangePenalty", 2);
|
||||
registerObject(ModScope::scopeBuiltin(), "bonusSubtype", "deathStareRangePenalty", 3);
|
||||
registerObject(ModScope::scopeBuiltin(), "bonusSubtype", "deathStareObstaclePenalty", 4);
|
||||
registerObject(ModScope::scopeBuiltin(), "bonusSubtype", "deathStareRangeObstaclePenalty", 5);
|
||||
registerObject(ModScope::scopeBuiltin(), "bonusSubtype", "rebirthRegular", 0);
|
||||
registerObject(ModScope::scopeBuiltin(), "bonusSubtype", "rebirthSpecial", 1);
|
||||
registerObject(ModScope::scopeBuiltin(), "bonusSubtype", "visionsMonsters", 0);
|
||||
|
@ -268,7 +268,9 @@ bool BattleActionProcessor::doAttackAction(const CBattleInfoCallback & battle, c
|
||||
totalAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, BonusSubtypeID(stack->creatureId()));
|
||||
}
|
||||
|
||||
const bool firstStrike = destinationStack->hasBonusOfType(BonusType::FIRST_STRIKE);
|
||||
static const auto firstStrikeSelector = Selector::typeSubtype(BonusType::FIRST_STRIKE, BonusCustomSubtype::damageTypeAll).Or(Selector::typeSubtype(BonusType::FIRST_STRIKE, BonusCustomSubtype::damageTypeMelee));
|
||||
const bool firstStrike = destinationStack->hasBonus(firstStrikeSelector);
|
||||
|
||||
const bool retaliation = destinationStack->ableToRetaliate();
|
||||
bool ferocityApplied = false;
|
||||
int32_t defenderInitialQuantity = destinationStack->getCount();
|
||||
@ -276,7 +278,7 @@ bool BattleActionProcessor::doAttackAction(const CBattleInfoCallback & battle, c
|
||||
for (int i = 0; i < totalAttacks; ++i)
|
||||
{
|
||||
//first strike
|
||||
if(i == 0 && firstStrike && retaliation)
|
||||
if(i == 0 && firstStrike && retaliation && !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION))
|
||||
{
|
||||
makeAttack(battle, destinationStack, stack, 0, stack->getPosition(), true, false, true);
|
||||
}
|
||||
@ -353,7 +355,11 @@ bool BattleActionProcessor::doShootAction(const CBattleInfoCallback & battle, co
|
||||
return false;
|
||||
}
|
||||
|
||||
makeAttack(battle, stack, destinationStack, 0, destination, true, true, false);
|
||||
static const auto firstStrikeSelector = Selector::typeSubtype(BonusType::FIRST_STRIKE, BonusCustomSubtype::damageTypeAll).Or(Selector::typeSubtype(BonusType::FIRST_STRIKE, BonusCustomSubtype::damageTypeRanged));
|
||||
const bool firstStrike = destinationStack->hasBonus(firstStrikeSelector);
|
||||
|
||||
if (!firstStrike)
|
||||
makeAttack(battle, stack, destinationStack, 0, destination, true, true, false);
|
||||
|
||||
//ranged counterattack
|
||||
if (destinationStack->hasBonusOfType(BonusType::RANGED_RETALIATION)
|
||||
@ -375,7 +381,7 @@ bool BattleActionProcessor::doShootAction(const CBattleInfoCallback & battle, co
|
||||
totalRangedAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, BonusSubtypeID(stack->creatureId()));
|
||||
}
|
||||
|
||||
for(int i = 1; i < totalRangedAttacks; ++i)
|
||||
for(int i = firstStrike ? 0:1; i < totalRangedAttacks; ++i)
|
||||
{
|
||||
if(
|
||||
stack->alive()
|
||||
@ -1234,7 +1240,7 @@ void BattleActionProcessor::handleAttackBeforeCasting(const CBattleInfoCallback
|
||||
attackCasting(battle, ranged, BonusType::SPELL_BEFORE_ATTACK, attacker, defender); //no death stare / acid breath needed?
|
||||
}
|
||||
|
||||
void BattleActionProcessor::HandleDeathStareAndPirateShot(const CBattleInfoCallback & battle, bool ranged, const CStack * attacker, const CStack * defender)
|
||||
void BattleActionProcessor::handleDeathStare(const CBattleInfoCallback & battle, bool ranged, const CStack * attacker, const CStack * defender)
|
||||
{
|
||||
// mechanics of Death Stare as in H3:
|
||||
// each gorgon have 10% chance to kill (counted separately in H3) -> binomial distribution
|
||||
@ -1246,28 +1252,30 @@ void BattleActionProcessor::HandleDeathStareAndPirateShot(const CBattleInfoCallb
|
||||
* X = 3 multiplier for shooting without penalty and X = 2 if shooting with penalty. Ability doesn't work if shooting at creatures behind walls.
|
||||
*/
|
||||
|
||||
auto bonus = attacker->getBonus(Selector::type()(BonusType::DEATH_STARE));
|
||||
if(bonus == nullptr)
|
||||
bonus = attacker->getBonus(Selector::type()(BonusType::ACCURATE_SHOT));
|
||||
auto subtype = BonusCustomSubtype::deathStareGorgon;
|
||||
|
||||
if(bonus->type == BonusType::ACCURATE_SHOT) //should not work from behind walls, except when being defender or under effect of golden bow etc.
|
||||
if (ranged)
|
||||
{
|
||||
if(!ranged)
|
||||
return;
|
||||
if(battle.battleHasWallPenalty(attacker, attacker->getPosition(), defender->getPosition()))
|
||||
return;
|
||||
bool rangePenalty = battle.battleHasDistancePenalty(attacker, attacker->getPosition(), defender->getPosition());
|
||||
bool obstaclePenalty = battle.battleHasWallPenalty(attacker, attacker->getPosition(), defender->getPosition());
|
||||
|
||||
if(rangePenalty)
|
||||
{
|
||||
if(obstaclePenalty)
|
||||
subtype = BonusCustomSubtype::deathStareRangeObstaclePenalty;
|
||||
else
|
||||
subtype = BonusCustomSubtype::deathStareRangePenalty;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(obstaclePenalty)
|
||||
subtype = BonusCustomSubtype::deathStareObstaclePenalty;
|
||||
else
|
||||
subtype = BonusCustomSubtype::deathStareNoRangePenalty;
|
||||
}
|
||||
}
|
||||
|
||||
int singleCreatureKillChancePercent;
|
||||
if(bonus->type == BonusType::ACCURATE_SHOT)
|
||||
{
|
||||
singleCreatureKillChancePercent = attacker->valOfBonuses(BonusType::ACCURATE_SHOT);
|
||||
if(battle.battleHasDistancePenalty(attacker, attacker->getPosition(), defender->getPosition()))
|
||||
singleCreatureKillChancePercent = (singleCreatureKillChancePercent * 2) / 3;
|
||||
}
|
||||
else //DEATH_STARE
|
||||
singleCreatureKillChancePercent = attacker->valOfBonuses(BonusType::DEATH_STARE, BonusCustomSubtype::deathStareGorgon);
|
||||
|
||||
int singleCreatureKillChancePercent = attacker->valOfBonuses(BonusType::DEATH_STARE, subtype);
|
||||
double chanceToKill = singleCreatureKillChancePercent / 100.0;
|
||||
vstd::amin(chanceToKill, 1); //cap at 100%
|
||||
std::binomial_distribution<> distribution(attacker->getCount(), chanceToKill);
|
||||
@ -1276,16 +1284,16 @@ void BattleActionProcessor::HandleDeathStareAndPirateShot(const CBattleInfoCallb
|
||||
int maxToKill = (attacker->getCount() * singleCreatureKillChancePercent + 99) / 100;
|
||||
vstd::amin(killedCreatures, maxToKill);
|
||||
|
||||
if(bonus->type == BonusType::DEATH_STARE)
|
||||
killedCreatures += (attacker->level() * attacker->valOfBonuses(BonusType::DEATH_STARE, BonusCustomSubtype::deathStareCommander)) / defender->level();
|
||||
killedCreatures += (attacker->level() * attacker->valOfBonuses(BonusType::DEATH_STARE, BonusCustomSubtype::deathStareCommander)) / defender->level();
|
||||
|
||||
if(killedCreatures)
|
||||
{
|
||||
//TODO: death stare or accurate shot was not originally available for multiple-hex attacks, but...
|
||||
|
||||
SpellID spellID = SpellID(SpellID::DEATH_STARE); //also used as fallback spell for ACCURATE_SHOT
|
||||
if(bonus->type == BonusType::ACCURATE_SHOT && bonus->subtype.as<SpellID>() != SpellID::NONE)
|
||||
spellID = bonus->subtype.as<SpellID>();
|
||||
auto bonus = attacker->getBonus(Selector::typeSubtype(BonusType::DEATH_STARE, subtype));
|
||||
if(bonus && bonus->additionalInfo[0] != SpellID::NONE)
|
||||
spellID = SpellID(bonus->additionalInfo[0]);
|
||||
|
||||
const CSpell * spell = spellID.toSpell();
|
||||
spells::AbilityCaster caster(attacker, 0);
|
||||
@ -1311,10 +1319,8 @@ void BattleActionProcessor::handleAfterAttackCasting(const CBattleInfoCallback &
|
||||
return;
|
||||
}
|
||||
|
||||
if(attacker->hasBonusOfType(BonusType::DEATH_STARE) || attacker->hasBonusOfType(BonusType::ACCURATE_SHOT))
|
||||
{
|
||||
HandleDeathStareAndPirateShot(battle, ranged, attacker, defender);
|
||||
}
|
||||
if(attacker->hasBonusOfType(BonusType::DEATH_STARE))
|
||||
handleDeathStare(battle, ranged, attacker, defender);
|
||||
|
||||
if(!defender->alive())
|
||||
return;
|
||||
|
@ -45,7 +45,7 @@ class BattleActionProcessor : boost::noncopyable
|
||||
|
||||
void handleAttackBeforeCasting(const CBattleInfoCallback & battle, bool ranged, const CStack * attacker, const CStack * defender);
|
||||
|
||||
void HandleDeathStareAndPirateShot(const CBattleInfoCallback &battle, bool ranged, const CStack *attacker, const CStack *defender);
|
||||
void handleDeathStare(const CBattleInfoCallback &battle, bool ranged, const CStack *attacker, const CStack *defender);
|
||||
|
||||
void handleAfterAttackCasting(const CBattleInfoCallback & battle, bool ranged, const CStack * attacker, const CStack * defender);
|
||||
void attackCasting(const CBattleInfoCallback & battle, bool ranged, BonusType attackMode, const battle::Unit * attacker, const CStack * defender);
|
||||
|
Loading…
Reference in New Issue
Block a user