Implement two-weapon fighting: penalties, off-hand attacks

This commit is contained in:
Vsevolod Kremianskii 2021-06-05 00:57:51 +07:00
parent 93b389fcd4
commit 842a8fe3b0
6 changed files with 62 additions and 29 deletions

View file

@ -99,6 +99,9 @@ void Combat::updateRound(Round &round, float dt) {
switch (round.state) {
case RoundState::Started: {
if (round.attack1->resultType == AttackResultType::Invalid) {
round.attack1->resultType = determineAttackResult(*round.attack1);
}
startAttack(*round.attack1, round.duel);
round.state = RoundState::FirstAttack;
break;
@ -107,7 +110,18 @@ void Combat::updateRound(Round &round, float dt) {
if (isRoundPastFirstAttack(round.time)) {
resetProjectile(round);
applyAttackResult(*round.attack1);
// Off-hand attack
if (round.attack1->attacker->isTwoWeaponFighting()) {
round.attack1->resultType = determineAttackResult(*round.attack1, true);
applyAttackResult(*round.attack1, true);
}
// TODO: additional attacks from Feats and Force Powers
if (round.attack2) {
if (round.attack2->resultType == AttackResultType::Invalid) {
round.attack2->resultType = determineAttackResult(*round.attack2);
}
startAttack(*round.attack2, round.duel);
}
round.state = RoundState::SecondAttack;
@ -125,6 +139,13 @@ void Combat::updateRound(Round &round, float dt) {
resetProjectile(round);
if (round.attack2) {
applyAttackResult(*round.attack2);
// Off-hand attack
if (round.attack2->attacker->isTwoWeaponFighting()) {
round.attack2->resultType = determineAttackResult(*round.attack2, true);
applyAttackResult(*round.attack2, true);
}
// TODO: additional attacks from Feats and Force Powers
}
finishRound(round);
round.state = RoundState::Finished;
@ -143,9 +164,6 @@ void Combat::updateRound(Round &round, float dt) {
}
void Combat::startAttack(Attack &attack, bool duel) {
if (attack.resultType == AttackResultType::Invalid) {
attack.resultType = determineAttackResult(attack);
}
AttackAnimation animation = determineAttackAnimation(attack, duel);
attack.attacker->face(*attack.target);

View file

@ -112,15 +112,15 @@ private:
// Attack
AttackResultType determineAttackResult(const Attack &attack) const;
AttackResultType determineAttackResult(const Attack &attack, bool offHand = false) const;
AttackAnimation determineAttackAnimation(const Attack &attack, bool duel) const;
void applyAttackResult(const Attack &attack);
void applyAttackResult(const Attack &attack, bool offHand = false);
// END Attack
// Damage
std::vector<std::shared_ptr<DamageEffect>> getDamageEffects(std::shared_ptr<Creature> damager, float multiplier = 1.0f) const;
std::vector<std::shared_ptr<DamageEffect>> getDamageEffects(std::shared_ptr<Creature> damager, bool offHand = false, float multiplier = 1.0f) const;
// END Damage

View file

@ -41,7 +41,7 @@ static bool isAttackSuccessful(AttackResultType result) {
}
}
AttackResultType Combat::determineAttackResult(const Attack &attack) const {
AttackResultType Combat::determineAttackResult(const Attack &attack, bool offHand) const {
auto result = AttackResultType::Miss;
// Determine defense of a target
@ -56,16 +56,16 @@ AttackResultType Combat::determineAttackResult(const Attack &attack) const {
int roll = random(1, 20);
if (roll == 20) {
result = AttackResultType::AutomaticHit;
} else if (roll > 1 && roll + attack.attacker->getAttackBonus() >= defense) { // 1 is automatic miss
} else if (roll > 1 && roll + attack.attacker->getAttackBonus(offHand) >= 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();
shared_ptr<Item> weapon(attack.attacker->getEquippedItem(offHand ? InventorySlot::leftWeapon : InventorySlot::rightWeapon));
if (weapon) {
criticalThreat = weapon->criticalThreat();
} else {
criticalThreat = 1;
}
@ -150,12 +150,12 @@ Combat::AttackAnimation Combat::determineAttackAnimation(const Attack &attack, b
return move(result);
}
void Combat::applyAttackResult(const Attack &attack) {
void Combat::applyAttackResult(const Attack &attack, bool offHand) {
// Determine critical hit multiplier
int criticalHitMultiplier = 2;
shared_ptr<Item> rightWeapon(attack.attacker->getEquippedItem(InventorySlot::rightWeapon));
if (rightWeapon) {
criticalHitMultiplier = rightWeapon->criticalHitMultiplier();
shared_ptr<Item> weapon(attack.attacker->getEquippedItem(offHand ? InventorySlot::leftWeapon : InventorySlot::rightWeapon));
if (weapon) {
criticalHitMultiplier = weapon->criticalHitMultiplier();
}
switch (attack.resultType) {
@ -170,7 +170,7 @@ void Combat::applyAttackResult(const Attack &attack) {
case AttackResultType::AutomaticHit: {
debug(boost::format("Combat: attack hit: %s -> %s") % attack.attacker->tag() % attack.target->tag(), 2, DebugChannels::combat);
if (attack.damage == -1) {
auto effects = getDamageEffects(attack.attacker);
auto effects = getDamageEffects(attack.attacker, offHand);
for (auto &effect : effects) {
attack.target->applyEffect(effect, DurationType::Instant);
}
@ -182,7 +182,7 @@ void Combat::applyAttackResult(const Attack &attack) {
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);
auto effects = getDamageEffects(attack.attacker, offHand, criticalHitMultiplier);
for (auto &effect : effects) {
attack.target->applyEffect(effect, DurationType::Instant);
}

View file

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

View file

@ -550,6 +550,10 @@ void Creature::deactivateCombat(float delay) {
}
}
bool Creature::isTwoWeaponFighting() const {
return static_cast<bool>(getEquippedItem(InventorySlot::leftWeapon));
}
shared_ptr<SpatialObject> Creature::getAttemptedAttackTarget() const {
shared_ptr<SpatialObject> result;
@ -561,17 +565,27 @@ shared_ptr<SpatialObject> Creature::getAttemptedAttackTarget() const {
return move(result);
}
int Creature::getAttackBonus() const {
int modifier;
int Creature::getAttackBonus(bool offHand) const {
auto rightWeapon(getEquippedItem(InventorySlot::rightWeapon));
auto leftWeapon(getEquippedItem(InventorySlot::leftWeapon));
auto weapon = offHand ? leftWeapon : rightWeapon;
auto rightWeapon = getEquippedItem(InventorySlot::rightWeapon);
if (rightWeapon && rightWeapon->isRanged()) {
int modifier;
if (weapon && weapon->isRanged()) {
modifier = _attributes.getAbilityModifier(Ability::Dexterity);
} else {
modifier = _attributes.getAbilityModifier(Ability::Strength);
}
return _attributes.getAggregateAttackBonus() + modifier;
int penalty;
if (rightWeapon && leftWeapon) {
// TODO: support Dueling and Two-Weapon Fighting feats
penalty = offHand ? 10 : 6;
} else {
penalty = 0;
}
return _attributes.getAggregateAttackBonus() + modifier - penalty;
}
int Creature::getDefense() const {

View file

@ -192,10 +192,11 @@ public:
bool isInCombat() const { return _combat.active; }
bool isDebilitated() const { return _combat.debilitated; }
bool isTwoWeaponFighting() const;
std::shared_ptr<SpatialObject> getAttemptedAttackTarget() const;
std::shared_ptr<SpatialObject> getAttackTarget() const { return _combat.attackTarget; }
int getAttackBonus() const;
int getAttackBonus(bool offHand = false) const;
int getDefense() const;
void getMainHandDamage(int &min, int &max) const;
void getOffhandDamage(int &min, int &max) const;