Implement critical hits in combat

This commit is contained in:
Vsevolod Kremianskii 2021-06-05 00:25:26 +07:00
parent 706d408483
commit 93b389fcd4
3 changed files with 63 additions and 27 deletions

View file

@ -120,7 +120,7 @@ private:
// Damage
std::vector<std::shared_ptr<DamageEffect>> getDamageEffects(std::shared_ptr<Creature> damager) const;
std::vector<std::shared_ptr<DamageEffect>> getDamageEffects(std::shared_ptr<Creature> damager, float multiplier = 1.0f) const;
// END Damage

View file

@ -30,28 +30,6 @@ namespace reone {
namespace game {
AttackResultType Combat::determineAttackResult(const Attack &attack) const {
auto result = AttackResultType::Invalid;
int roll = random(1, 20);
int defense;
if (attack.target->type() == ObjectType::Creature) {
defense = static_pointer_cast<Creature>(attack.target)->getDefense();
} else {
defense = 10;
}
if (roll == 20) {
result = AttackResultType::AutomaticHit;
} else if (roll + attack.attacker->getAttackBonus() >= defense) {
result = AttackResultType::HitSuccessful;
} else {
result = AttackResultType::Miss;
}
return result;
}
static bool isAttackSuccessful(AttackResultType result) {
switch (result) {
case AttackResultType::HitSuccessful:
@ -63,6 +41,46 @@ static bool isAttackSuccessful(AttackResultType result) {
}
}
AttackResultType Combat::determineAttackResult(const Attack &attack) const {
auto result = AttackResultType::Miss;
// Determine defense of a target
int defense;
if (attack.target->type() == ObjectType::Creature) {
defense = static_pointer_cast<Creature>(attack.target)->getDefense();
} else {
defense = 10;
}
// Attack roll
int roll = random(1, 20);
if (roll == 20) {
result = AttackResultType::AutomaticHit;
} else if (roll > 1 && roll + attack.attacker->getAttackBonus() >= defense) { // 1 is automatic miss
result = AttackResultType::HitSuccessful;
}
// Critical threat
if (isAttackSuccessful(result)) {
int criticalThreat;
shared_ptr<Item> rightWeapon(attack.attacker->getEquippedItem(InventorySlot::rightWeapon));
if (rightWeapon) {
criticalThreat = rightWeapon->criticalThreat();
} else {
criticalThreat = 1;
}
if (roll > 20 - criticalThreat) {
// Critical hit roll
int criticalRoll = random(1, 20);
if (criticalRoll + attack.attacker->getAttackBonus() >= defense) {
result = AttackResultType::CriticalHit;
}
}
}
return result;
}
static bool isMeleeWieldType(CreatureWieldType type) {
switch (type) {
case CreatureWieldType::SingleSword:
@ -133,6 +151,13 @@ Combat::AttackAnimation Combat::determineAttackAnimation(const Attack &attack, b
}
void Combat::applyAttackResult(const Attack &attack) {
// Determine critical hit multiplier
int criticalHitMultiplier = 2;
shared_ptr<Item> rightWeapon(attack.attacker->getEquippedItem(InventorySlot::rightWeapon));
if (rightWeapon) {
criticalHitMultiplier = rightWeapon->criticalHitMultiplier();
}
switch (attack.resultType) {
case AttackResultType::Miss:
case AttackResultType::AttackResisted:
@ -142,7 +167,6 @@ void Combat::applyAttackResult(const Attack &attack) {
debug(boost::format("Combat: attack missed: %s -> %s") % attack.attacker->tag() % attack.target->tag(), 2, DebugChannels::combat);
break;
case AttackResultType::HitSuccessful:
case AttackResultType::CriticalHit:
case AttackResultType::AutomaticHit: {
debug(boost::format("Combat: attack hit: %s -> %s") % attack.attacker->tag() % attack.target->tag(), 2, DebugChannels::combat);
if (attack.damage == -1) {
@ -155,6 +179,18 @@ void Combat::applyAttackResult(const Attack &attack) {
}
break;
}
case AttackResultType::CriticalHit: {
debug(boost::format("Combat: attack critical hit: %s -> %s") % attack.attacker->tag() % attack.target->tag(), 2, DebugChannels::combat);
if (attack.damage == -1) {
auto effects = getDamageEffects(attack.attacker, criticalHitMultiplier);
for (auto &effect : effects) {
attack.target->applyEffect(effect, DurationType::Instant);
}
} else {
attack.target->applyEffect(make_shared<DamageEffect>(criticalHitMultiplier * attack.damage, DamageType::Universal, attack.attacker), DurationType::Instant);
}
break;
}
default:
throw logic_error("Unsupported attack result");
}

View file

@ -29,10 +29,10 @@ namespace reone {
namespace game {
vector<shared_ptr<DamageEffect>> Combat::getDamageEffects(shared_ptr<Creature> damager) const {
vector<shared_ptr<DamageEffect>> Combat::getDamageEffects(shared_ptr<Creature> damager, float multiplier) const {
shared_ptr<Item> item(damager->getEquippedItem(InventorySlot::rightWeapon));
int amount = 0;
DamageType type = DamageType::Bludgeoning;
auto type = DamageType::Bludgeoning;
if (item) {
for (int i = 0; i < item->numDice(); ++i) {
@ -41,7 +41,7 @@ vector<shared_ptr<DamageEffect>> Combat::getDamageEffects(shared_ptr<Creature> d
type = static_cast<DamageType>(item->damageFlags());
}
amount = glm::max(1, amount);
shared_ptr<DamageEffect> effect(make_shared<DamageEffect>(amount, type, move(damager)));
auto effect = make_shared<DamageEffect>(multiplier * amount, type, move(damager));
return vector<shared_ptr<DamageEffect>> { effect };
}