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

Initial version of ACCURATE_SHOT implementation

This commit is contained in:
Dydzio 2024-01-04 22:27:51 +01:00
parent fbd988df42
commit 7283a4861e
7 changed files with 87 additions and 2 deletions

View File

@ -184,6 +184,8 @@
"vcmi.battleWindow.damageEstimation.kills" : "%d will perish",
"vcmi.battleWindow.damageEstimation.kills.1" : "%d will perish",
"vcmi.battleWindow.killed" : "Killed",
"vcmi.battleWindow.accurateShot.resultDescription" : "%d %s were killed by accurate shots!",
"vcmi.battleWindow.accurateShot.resultDescription.1" : "1 %s was killed with an accurate shot!",
"vcmi.battleResultsWindow.applyResultsLabel" : "Apply battle result",
@ -328,7 +330,9 @@
"vcmi.stackExperience.rank.8" : "Elite",
"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",

View File

@ -3,6 +3,13 @@
// LEVEL_SPELL_IMMUNITY
{
"ACCURATE_SHOT":
{
"graphics":
{
"icon": "zvs/Lib1.res/E_DIST"
}
},
"ADDITIONAL_ATTACK":
{
"graphics":

View File

@ -732,6 +732,15 @@ 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

View File

@ -515,6 +515,7 @@ 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)

View File

@ -174,6 +174,7 @@ 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*/ \
/* end of list */

View File

@ -152,6 +152,22 @@ void Damage::describeEffect(std::vector<MetaString> & log, const Mechanics * m,
m->caster->getCasterName(line);
log.push_back(line);
}
else if(m->getSpell()->getJsonKey().find("accurateShot") != std::string::npos && !multiple)
{
MetaString line;
if(kills > 1)
{
line.appendTextID("vcmi.battleWindow.accurateShot.resultDescription"); //(number) (unit type) was killed with an accurate shot!
line.replaceNumber(kills);
firstTarget->addNameReplacement(line, true);
}
else
{
line.appendTextID("vcmi.battleWindow.accurateShot.resultDescription.1"); //1 (unit type) were killed by accurate shots!
firstTarget->addNameReplacement(line, false);
}
log.push_back(line);
}
else if(m->getSpellIndex() == SpellID::THUNDERBOLT && !multiple)
{
{

View File

@ -1214,7 +1214,7 @@ void BattleActionProcessor::handleAfterAttackCasting(const CBattleInfoCallback &
// each gorgon have 10% chance to kill (counted separately in H3) -> binomial distribution
//original formula x = min(x, (gorgons_count + 9)/10);
double chanceToKill = attacker->valOfBonuses(BonusType::DEATH_STARE, BonusCustomSubtype::deathStareGorgon) / 100.0f;
double chanceToKill = attacker->valOfBonuses(BonusType::DEATH_STARE, BonusCustomSubtype::deathStareGorgon) / 100.0;
vstd::amin(chanceToKill, 1); //cap at 100%
std::binomial_distribution<> distribution(attacker->getCount(), chanceToKill);
@ -1241,6 +1241,53 @@ void BattleActionProcessor::handleAfterAttackCasting(const CBattleInfoCallback &
}
}
if(attacker->hasBonusOfType(BonusType::ACCURATE_SHOT))
{
/* Intended to match HotA Sea Dogs
* The Sea Dog's Accurate Shot is triggered after a shot:
* each creature in an attacking stack has a X% chance of killing a creature in the attacked squad,
* but the total number of killed creatures cannot be more than (number of creatures in an attacking squad) * X/100 (rounded up).
* X = 3 multiplier for shooting without penalty and X = 2 if shooting with penalty. Ability doesn't work if shooting at creatures behind walls.
*/
if(!ranged || battle.battleHasWallPenalty(attacker, attacker->getPosition(), defender->getPosition()))
return;
int singleCreatureKillChancePercent = attacker->valOfBonuses(BonusType::ACCURATE_SHOT);
if(battle.battleHasDistancePenalty(attacker, attacker->getPosition(), defender->getPosition()))
singleCreatureKillChancePercent = (singleCreatureKillChancePercent * 2) / 3;
double chanceToKill = singleCreatureKillChancePercent / 100.0;
vstd::amin(chanceToKill, 1); //cap at 100%
std::binomial_distribution<> distribution(attacker->getCount(), chanceToKill);
int killedCreatures = distribution(gameHandler->getRandomGenerator().getStdGenerator());
bool isMaxToKillRounded = attacker->getCount() * singleCreatureKillChancePercent % 100 == 0;
int maxToKill = attacker->getCount() * singleCreatureKillChancePercent / 100 + (isMaxToKillRounded ? 0 : 1);
vstd::amin(killedCreatures, maxToKill);
if(killedCreatures)
{
//TODO: accurate shot was not originally available for multiple-hex attacks, but...
const auto bonus = attacker->getBonus(Selector::type()(BonusType::ACCURATE_SHOT));
auto spellID = bonus->subtype.as<SpellID>();
if(spellID == SpellID::NONE)
spellID = SpellID(SpellID::DEATH_STARE); //fallback for spell not specified
const CSpell * spell = spellID.toSpell();
spells::AbilityCaster caster(attacker, 0);
spells::BattleCast parameters(&battle, &caster, spells::Mode::PASSIVE, spell);
spells::Target target;
target.emplace_back(defender);
parameters.setEffectValue(killedCreatures);
parameters.cast(gameHandler->spellEnv, target);
}
}
if(!defender->alive())
return;