Implement two-weapon fighting: penalties, off-hand attacks
This commit is contained in:
parent
93b389fcd4
commit
842a8fe3b0
6 changed files with 62 additions and 29 deletions
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue