diff --git a/src/engine/game/combat/combat.h b/src/engine/game/combat/combat.h index aed03697..d2bc09d3 100644 --- a/src/engine/game/combat/combat.h +++ b/src/engine/game/combat/combat.h @@ -120,7 +120,7 @@ private: // Damage - std::vector> getDamageEffects(std::shared_ptr damager) const; + std::vector> getDamageEffects(std::shared_ptr damager, float multiplier = 1.0f) const; // END Damage diff --git a/src/engine/game/combat/combat_attack.cpp b/src/engine/game/combat/combat_attack.cpp index d84c8faf..ff3d3f54 100644 --- a/src/engine/game/combat/combat_attack.cpp +++ b/src/engine/game/combat/combat_attack.cpp @@ -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(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(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 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 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(criticalHitMultiplier * attack.damage, DamageType::Universal, attack.attacker), DurationType::Instant); + } + break; + } default: throw logic_error("Unsupported attack result"); } diff --git a/src/engine/game/combat/combat_damage.cpp b/src/engine/game/combat/combat_damage.cpp index 52a615ee..009e0f9c 100644 --- a/src/engine/game/combat/combat_damage.cpp +++ b/src/engine/game/combat/combat_damage.cpp @@ -29,10 +29,10 @@ namespace reone { namespace game { -vector> Combat::getDamageEffects(shared_ptr damager) const { +vector> Combat::getDamageEffects(shared_ptr damager, float multiplier) const { shared_ptr 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> Combat::getDamageEffects(shared_ptr d type = static_cast(item->damageFlags()); } amount = glm::max(1, amount); - shared_ptr effect(make_shared(amount, type, move(damager))); + auto effect = make_shared(multiplier * amount, type, move(damager)); return vector> { effect }; }