refactor: Refactor combat, fix TSL crashes

This commit is contained in:
Vsevolod Kremianskii 2021-01-10 11:38:21 +07:00
parent cd0772bb06
commit 127022a0d9
10 changed files with 228 additions and 118 deletions

View file

@ -371,7 +371,10 @@ set(GAME_HEADERS
src/game/characterutil.h
src/game/collisiondetect.h
src/game/console.h
src/game/combat.h
src/game/combat/attackresolver.h
src/game/combat/attackresult.h
src/game/combat/combat.h
src/game/combat/damageresolver.h
src/game/cursors.h
src/game/dialog.h
src/game/enginetype/effect.h
@ -440,7 +443,6 @@ set(GAME_HEADERS
src/game/rp/attributes.h
src/game/rp/class.h
src/game/rp/classes.h
src/game/rp/damageresolver.h
src/game/rp/factionutil.h
src/game/rp/savingthrows.h
src/game/rp/skills.h
@ -477,7 +479,9 @@ set(GAME_SOURCES
src/game/characterutil.cpp
src/game/collisiondetect.cpp
src/game/console.cpp
src/game/combat.cpp
src/game/combat/attackresolver.cpp
src/game/combat/combat.cpp
src/game/combat/damageresolver.cpp
src/game/cursors.cpp
src/game/dialog.cpp
src/game/game.cpp
@ -539,7 +543,6 @@ set(GAME_SOURCES
src/game/rp/attributes.cpp
src/game/rp/class.cpp
src/game/rp/classes.cpp
src/game/rp/damageresolver.cpp
src/game/rp/factionutil.cpp
src/game/rp/skills.cpp
src/game/savedgame.cpp

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2020-2021 The reone project contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "attackresolver.h"
#include "../../common/random.h"
#include "../object/creature.h"
using namespace std;
namespace reone {
namespace game {
static bool isMeleeWieldType(CreatureWieldType type) {
switch (type) {
case CreatureWieldType::SingleSword:
case CreatureWieldType::DoubleBladedSword:
case CreatureWieldType::DualSwords:
return true;
default:
return false;
}
}
static bool isAttackSuccessful(AttackResultType type) {
switch (type) {
case AttackResultType::HitSuccessful:
case AttackResultType::CriticalHit:
case AttackResultType::AutomaticHit:
return true;
default:
return false;
}
}
static bool isRangedWieldType(CreatureWieldType type) {
switch (type) {
case CreatureWieldType::BlasterPistol:
case CreatureWieldType::DualPistols:
case CreatureWieldType::BlasterRifle:
case CreatureWieldType::HeavyWeapon:
return true;
default:
return false;
}
}
AttackResult AttackResolver::getAttackResult(const shared_ptr<Creature> &attacker, const shared_ptr<SpatialObject> &target, bool duel, AttackResultType fixedType) {
AttackResult result;
if (fixedType != AttackResultType::Invalid) {
result.type = fixedType;
} else {
int attack = random(1, 20);
int defense = 10; // TODO: add armor bonus and dexterity modifier
if (attack == 20) {
result.type = AttackResultType::AutomaticHit;
} else if (attack >= defense) {
result.type = AttackResultType::HitSuccessful;
} else {
result.type = AttackResultType::Miss;
}
}
CreatureWieldType attackerWield = attacker->getWieldType();
CreatureWieldType targetWield = CreatureWieldType::None;
auto targetCreature = dynamic_pointer_cast<Creature>(target);
if (targetCreature) {
targetWield = targetCreature->getWieldType();
}
if (duel) {
if (isMeleeWieldType(attackerWield) && isMeleeWieldType(targetWield)) {
result.attackerAnimation = CombatAnimation::MeleeDuelAttack;
result.animationVariant = random(1, 5);
result.targetAnimation = isAttackSuccessful(result.type) ? CombatAnimation::MeleeDuelDamage : CombatAnimation::MeleeDuelParry;
} else if (isMeleeWieldType(attackerWield)) {
result.attackerAnimation = CombatAnimation::MeleeAttack;
result.animationVariant = random(1, 2);
result.targetAnimation = isAttackSuccessful(result.type) ? CombatAnimation::MeleeDamage : CombatAnimation::MeleeDodge;
} else if (isRangedWieldType(attackerWield)) {
result.attackerAnimation = CombatAnimation::RangedAttack;
result.targetAnimation = isAttackSuccessful(result.type) ? CombatAnimation::Damage : CombatAnimation::Dodge;
} else {
result.attackerAnimation = CombatAnimation::Attack;
result.animationVariant = random(1, 2);
result.targetAnimation = isAttackSuccessful(result.type) ? CombatAnimation::Damage : CombatAnimation::Dodge;
}
} else {
if (isRangedWieldType(attackerWield)) {
result.attackerAnimation = CombatAnimation::RangedAttack;
} else {
result.attackerAnimation = CombatAnimation::Attack;
result.animationVariant = random(1, 2);
}
}
result.attackerWieldType = attackerWield;
return move(result);
}
} // namespace game
} // namespace reone

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020-2021 The reone project contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include "attackresult.h"
namespace reone {
namespace game {
class Creature;
class SpatialObject;
/**
* Encapsulates combat attack logic.
*/
class AttackResolver {
public:
AttackResult getAttackResult(const std::shared_ptr<Creature> &attacker, const std::shared_ptr<SpatialObject> &target, bool duel, AttackResultType fixedType = AttackResultType::Invalid);
};
} // namespace game
} // namespace reone

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2020-2021 The reone project contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "../object/types.h"
#include "../types.h"
namespace reone {
namespace game {
struct AttackResult {
AttackResultType type { AttackResultType::Invalid };
CreatureWieldType attackerWieldType { CreatureWieldType::None };
CombatAnimation attackerAnimation { CombatAnimation::None };
int animationVariant { 1 };
CombatAnimation targetAnimation { CombatAnimation::None };
};
} // namespace game
} // namespace reone

View file

@ -22,11 +22,11 @@
#include "glm/common.hpp"
#include "../common/log.h"
#include "../common/random.h"
#include "../../common/log.h"
#include "../../common/random.h"
#include "game.h"
#include "rp/factionutil.h"
#include "../game.h"
#include "../rp/factionutil.h"
using namespace std;
@ -271,7 +271,7 @@ void Combat::updateRound(Round &round, float dt) {
switch (round.state) {
case RoundState::Started:
round.attackResult = determineAttackResult(attacker, round.target, duel, round.cutsceneAttackResult);
round.attackResult = _attackResolver.getAttackResult(attacker, round.target, duel, round.cutsceneAttackResult);
attacker->face(*round.target);
attacker->setMovementType(Creature::MovementType::None);
@ -299,7 +299,7 @@ void Combat::updateRound(Round &round, float dt) {
}
if (duel) {
auto targetCreature = static_pointer_cast<Creature>(round.target);
round.attackResult = determineAttackResult(targetCreature, attacker, true);
round.attackResult = _attackResolver.getAttackResult(targetCreature, attacker, true);
targetCreature->face(*attacker);
targetCreature->playAnimation(round.attackResult.attackerAnimation, round.attackResult.attackerWieldType, round.attackResult.animationVariant);
@ -424,97 +424,6 @@ void Combat::cutsceneAttack(
addRound(round);
}
static bool isMeleeWieldType(CreatureWieldType type) {
switch (type) {
case CreatureWieldType::SingleSword:
case CreatureWieldType::DoubleBladedSword:
case CreatureWieldType::DualSwords:
return true;
default:
return false;
}
}
static bool isAttackSuccessful(AttackResultType type) {
switch (type) {
case AttackResultType::HitSuccessful:
case AttackResultType::CriticalHit:
case AttackResultType::AutomaticHit:
return true;
default:
return false;
}
}
static bool isRangedWieldType(CreatureWieldType type) {
switch (type) {
case CreatureWieldType::BlasterPistol:
case CreatureWieldType::DualPistols:
case CreatureWieldType::BlasterRifle:
case CreatureWieldType::HeavyWeapon:
return true;
default:
return false;
}
}
Combat::AttackResult Combat::determineAttackResult(const shared_ptr<Creature> &attacker, const shared_ptr<SpatialObject> &target, bool duel, AttackResultType type) {
AttackResult result;
if (type != AttackResultType::Invalid) {
result.type = type;
} else {
int attack = random(1, 20);
int defense = 10; // TODO: add armor bonus and dexterity modifier
if (attack == 20) {
result.type = AttackResultType::AutomaticHit;
} else if (attack >= defense) {
result.type = AttackResultType::HitSuccessful;
} else {
result.type = AttackResultType::Miss;
}
}
CreatureWieldType attackerWield = attacker->getWieldType();
CreatureWieldType targetWield = CreatureWieldType::None;
auto targetCreature = dynamic_pointer_cast<Creature>(target);
if (targetCreature) {
targetWield = targetCreature->getWieldType();
}
if (duel) {
if (isMeleeWieldType(attackerWield) && isMeleeWieldType(targetWield)) {
result.attackerAnimation = CombatAnimation::MeleeDuelAttack;
result.animationVariant = random(1, 5);
result.targetAnimation = isAttackSuccessful(result.type) ? CombatAnimation::MeleeDuelDamage : CombatAnimation::MeleeDuelParry;
} else if (isMeleeWieldType(attackerWield)) {
result.attackerAnimation = CombatAnimation::MeleeAttack;
result.animationVariant = random(1, 2);
result.targetAnimation = isAttackSuccessful(result.type) ? CombatAnimation::MeleeDamage : CombatAnimation::MeleeDodge;
} else if (isRangedWieldType(attackerWield)) {
result.attackerAnimation = CombatAnimation::RangedAttack;
result.targetAnimation = isAttackSuccessful(result.type) ? CombatAnimation::Damage : CombatAnimation::Dodge;
} else {
result.attackerAnimation = CombatAnimation::Attack;
result.animationVariant = random(1, 2);
result.targetAnimation = isAttackSuccessful(result.type) ? CombatAnimation::Damage : CombatAnimation::Dodge;
}
} else {
if (isRangedWieldType(attackerWield)) {
result.attackerAnimation = CombatAnimation::RangedAttack;
} else {
result.attackerAnimation = CombatAnimation::Attack;
result.animationVariant = random(1, 2);
}
}
result.attackerWieldType = attackerWield;
return move(result);
}
} // namespace game
} // namespace reone

View file

@ -23,12 +23,14 @@
#include <queue>
#include <set>
#include "../common/timer.h"
#include "../../common/timer.h"
#include "enginetype/effect.h"
#include "object/creature.h"
#include "rp/damageresolver.h"
#include "types.h"
#include "../enginetype/effect.h"
#include "../object/creature.h"
#include "../types.h"
#include "attackresolver.h"
#include "damageresolver.h"
namespace reone {
@ -76,14 +78,6 @@ private:
typedef std::map<uint32_t, std::shared_ptr<Combatant>> CombatantMap;
struct AttackResult {
AttackResultType type { AttackResultType::Invalid };
CreatureWieldType attackerWieldType { CreatureWieldType::None };
CombatAnimation attackerAnimation { CombatAnimation::None };
int animationVariant { 1 };
CombatAnimation targetAnimation { CombatAnimation::None };
};
struct Round {
std::shared_ptr<Combatant> attacker;
std::shared_ptr<SpatialObject> target;
@ -101,6 +95,7 @@ private:
Game *_game;
bool _active { false };
AttackResolver _attackResolver;
DamageResolver _damageResolver;
Timer _heartbeatTimer { 0.0f };
@ -129,8 +124,6 @@ private:
// Attacks
AttackResult determineAttackResult(const std::shared_ptr<Creature> &attacker, const std::shared_ptr<SpatialObject> &target, bool duel, AttackResultType type = AttackResultType::Invalid);
void applyAttackResult(const std::shared_ptr<Creature> &attacker, const std::shared_ptr<SpatialObject> &target, AttackResult result, int damage = -1);
// END Attacks

View file

@ -29,7 +29,6 @@
#include "../../resource/gfffile.h"
#include "../../resource/types.h"
#include "../combat.h"
#include "../actionexecutor.h"
#include "../camera/animatedcamera.h"
#include "../camera/dialogcamera.h"
@ -38,6 +37,7 @@
#include "../camera/thirdperson.h"
#include "../camera/types.h"
#include "../collisiondetect.h"
#include "../combat/combat.h"
#include "../map.h"
#include "../objectselect.h"
#include "../pathfinder.h"

View file

@ -487,7 +487,11 @@ Variable Routines::applyEffectToObject(const VariablesList &args, ExecutionConte
auto target = getSpatialObject(args, 2);
float duration = getFloat(args, 3, 0.0f);
target->applyEffect(effect, durationType, duration);
if (target) {
target->applyEffect(effect, durationType, duration);
} else {
warn("Routines: applyEffectToObject: target is invalid");
}
return Variable();
}