feat: faction and activity scanner.
This commit is contained in:
parent
8d7b55e7cf
commit
269878c695
12 changed files with 311 additions and 70 deletions
|
@ -35,13 +35,9 @@ public:
|
|||
|
||||
bool isInRange() { return _inRange; }
|
||||
|
||||
void advance(float dt) {
|
||||
_timeout = glm::max(0.0f, _timeout - dt);
|
||||
}
|
||||
void advance(float dt) { _timeout = glm::max(0.0f, _timeout - dt); }
|
||||
|
||||
bool isTimedOut() const {
|
||||
return _timeout < 1E-6;
|
||||
}
|
||||
bool isTimedOut() const { return _timeout < 1E-6; }
|
||||
|
||||
float distance() const { return _distance; }
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
void update();
|
||||
|
||||
bool empty() const;
|
||||
size_t size() const { return _actions.size(); }
|
||||
|
||||
Action *currentAction();
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "../../common/log.h"
|
||||
#include "../../resource/resources.h"
|
||||
|
||||
using namespace std;
|
||||
|
@ -42,6 +43,7 @@ void CreatureBlueprint::load(const GffStruct &utc) {
|
|||
|
||||
_appearance = utc.getInt("Appearance_Type");
|
||||
_portraitId = utc.getInt("PortraitId", -1);
|
||||
_factionId = utc.getInt("FactionID", -1);
|
||||
_conversation = utc.getString("Conversation");
|
||||
|
||||
int firstNameStrRef = utc.getInt("FirstName", -1);
|
||||
|
|
|
@ -41,6 +41,7 @@ public:
|
|||
const std::vector<std::string> &equipment() const;
|
||||
int appearance() const;
|
||||
int portraitId() const;
|
||||
int factionId() const { return _factionId; }
|
||||
const std::string &conversation() const;
|
||||
const CreatureAttributes &attributes() const;
|
||||
const std::string &onSpawn() const;
|
||||
|
@ -53,6 +54,7 @@ private:
|
|||
std::vector<std::string> _equipment;
|
||||
int _appearance { 0 };
|
||||
int _portraitId { -1 };
|
||||
int _factionId { -1 };
|
||||
std::string _conversation;
|
||||
CreatureAttributes _attributes;
|
||||
|
||||
|
|
|
@ -28,22 +28,34 @@ namespace reone {
|
|||
|
||||
namespace game {
|
||||
|
||||
// void Combat::load(const shared_ptr<Area> &area)
|
||||
// {
|
||||
// _area = area;
|
||||
// helper functions
|
||||
|
||||
// _activeCombatants.clear();
|
||||
AttackAction* getAttackAction(shared_ptr<Creature>& combatant) {
|
||||
return dynamic_cast<AttackAction*>(combatant->actionQueue().currentAction());
|
||||
}
|
||||
|
||||
// // for testing purpose:
|
||||
// for (auto &creature : area->objectsByType()[ObjectType::Creature]) {
|
||||
// if (creature->tag().compare("kas22_kinrath_05") == 0 || creature->tag().compare("kas22_czguard_01") == 0
|
||||
// || creature->tag().compare("kas22_czguard_02") == 0 || creature->tag().compare("kas22_dehno_01") == 0) {
|
||||
bool isActiveTargetInRange(shared_ptr<Creature>& combatant) {
|
||||
auto* action = getAttackAction(combatant);
|
||||
return action && action->isInRange();
|
||||
}
|
||||
|
||||
// _activeCombatants.push_back(move(static_pointer_cast<Creature>(creature)));
|
||||
// debug(boost::format("creature '%s' with id '%d' added to combatant list!!") % creature->tag() % creature->id());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
void duel(shared_ptr<Creature>& attacker, shared_ptr<Creature>& target) {
|
||||
target->face(*attacker);
|
||||
attacker->face(*target);
|
||||
attacker->playAnimation("g8a1");
|
||||
target->playAnimation("g8g1");
|
||||
}
|
||||
|
||||
void bash(shared_ptr<Creature>& attacker, shared_ptr<Creature>& target) {
|
||||
attacker->face(*target);
|
||||
attacker->playAnimation("g8a2");
|
||||
}
|
||||
|
||||
void flinch(shared_ptr<Creature>& target) {
|
||||
target->playAnimation("g1y1");
|
||||
}
|
||||
|
||||
// END helper functions
|
||||
|
||||
Combat::Combat(Area *area, Party *party) : _area(area), _party(party) {
|
||||
if (!area) {
|
||||
|
@ -56,20 +68,66 @@ void Combat::update() {
|
|||
|
||||
std::shared_ptr<Creature> pc = _party->player();
|
||||
if (pc) {
|
||||
for (auto &cbt : _activeCombatants) AIMaster(cbt); // TODO: use blind cycle
|
||||
activityScanner();
|
||||
|
||||
combatStateMachine(pc);
|
||||
for (auto &cbt : _activeCombatants) {
|
||||
combatStateMachine(cbt);
|
||||
|
||||
|
||||
if (_activated) {
|
||||
for (auto& cbt : _activeCombatants) AIMaster(cbt);
|
||||
for (auto &cbt : _activeCombatants) combatStateMachine(cbt);
|
||||
|
||||
animationSync();
|
||||
}
|
||||
animationSync();
|
||||
}
|
||||
else {
|
||||
debug("no pc yet ...");
|
||||
}
|
||||
}
|
||||
|
||||
void Combat::activityScanner()
|
||||
{
|
||||
_activated = !_activeCombatants.empty();
|
||||
auto &actor = _activated ? _activeCombatants.front() : _party->player();
|
||||
|
||||
// TODO: need a better mechanism to query creatures in range
|
||||
bool stillActive = false;
|
||||
for (auto &creature : _area->objectsByType()[ObjectType::Creature]) {
|
||||
if (glm::length(creature->position() - actor->position()) > MAX_DETECT_RANGE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: add line-of-sight requirement
|
||||
|
||||
const auto &target = static_pointer_cast<Creature>(creature);
|
||||
if (getIsEnemy(actor, target)) {
|
||||
stillActive = true;
|
||||
if (registerCombatant(target)) { // will fail if already registered
|
||||
debug(boost::format("combat: registered '%s', faction '%d'")
|
||||
% target->tag() % static_cast<int>(target->getFaction()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_activated) return;
|
||||
|
||||
// remove actor from _activeCombatants if !active
|
||||
if (!stillActive) {
|
||||
actor->setCombatState(CombatState::Idle);
|
||||
|
||||
_activeCombatants.pop_front();
|
||||
_activeCombatantIds.erase(actor->id());
|
||||
|
||||
debug(boost::format("combat: deactivated '%s', combat_mode[%d]") % actor->tag() % _activated);
|
||||
}
|
||||
else { // rotate _activeCombatants
|
||||
_activeCombatants.pop_front();
|
||||
_activeCombatants.push_back(actor);
|
||||
}
|
||||
}
|
||||
|
||||
void Combat::AIMaster(shared_ptr<Creature> &combatant) {
|
||||
if (combatant->id() == _party->player()->id()) return;
|
||||
|
||||
ActionQueue &cbt_queue = combatant->actionQueue();
|
||||
|
||||
// no additional instructions if current queue is empty
|
||||
|
@ -78,23 +136,20 @@ void Combat::AIMaster(shared_ptr<Creature> &combatant) {
|
|||
// otherwise, go to nearest hostile, set meleecommand, unset meleecommand
|
||||
|
||||
auto hostile = findNearestHostile(combatant);
|
||||
if (hostile) cbt_queue.add(make_unique<AttackAction>(hostile, true));
|
||||
}
|
||||
|
||||
AttackAction* getAttackAction(shared_ptr<Creature>& combatant) {
|
||||
return dynamic_cast<AttackAction*>(combatant->actionQueue().currentAction());
|
||||
}
|
||||
|
||||
bool isActiveTargetInRange(shared_ptr<Creature>& combatant) {
|
||||
auto* action = getAttackAction(combatant);
|
||||
return action && action->isInRange();
|
||||
if (hostile) {
|
||||
cbt_queue.add(make_unique<AttackAction>(hostile, true));
|
||||
debug(boost::format("AIMaster: '%s' Queued to attack '%s'") % combatant->tag()
|
||||
% hostile->tag());
|
||||
}
|
||||
}
|
||||
|
||||
void Combat::onEnterAttackState(shared_ptr<Creature> &combatant) {
|
||||
if (!combatant) return;
|
||||
|
||||
setStateTimeout(combatant, 1500);
|
||||
debug(boost::format("'%s' enters Attack state, set_timer") % combatant->tag());
|
||||
debug(boost::format("'%s' enters Attack state, actionQueueLen[%d], attackAction[%d]")
|
||||
% combatant->tag() % combatant->actionQueue().size()
|
||||
% (getAttackAction(combatant) != nullptr)); // TODO: disable redundant info
|
||||
|
||||
auto *action = getAttackAction(combatant);
|
||||
auto &target = action->target();
|
||||
|
@ -180,22 +235,6 @@ void Combat::combatStateMachine(shared_ptr<Creature> &combatant) {
|
|||
}
|
||||
}
|
||||
|
||||
void duel(shared_ptr<Creature>& attacker, shared_ptr<Creature>& target) {
|
||||
target->face(*attacker);
|
||||
attacker->face(*target);
|
||||
attacker->playAnimation("g8a1");
|
||||
target->playAnimation("g8g1");
|
||||
}
|
||||
|
||||
void bash(shared_ptr<Creature>& attacker, shared_ptr<Creature>& target) {
|
||||
attacker->face(*target);
|
||||
attacker->playAnimation("g8a2");
|
||||
}
|
||||
|
||||
void flinch(shared_ptr<Creature>& target) {
|
||||
target->playAnimation("g1y1");
|
||||
}
|
||||
|
||||
void Combat::animationSync() {
|
||||
while (!_duelQueue.empty()) {
|
||||
auto &pr = _duelQueue.front();
|
||||
|
@ -214,10 +253,11 @@ shared_ptr<Creature> Combat::findNearestHostile(shared_ptr<Creature> &combatant)
|
|||
shared_ptr<Creature> closest_target = nullptr;
|
||||
float min_dist = 1000000;
|
||||
|
||||
for (auto &creature : _area->objectsByType()[ObjectType::Creature]) {
|
||||
for (auto &creature : _activeCombatants) {
|
||||
if (creature->id() == combatant->id()) continue;
|
||||
|
||||
// TODO: if (nonhostile) continue;
|
||||
if (!getIsEnemy(static_pointer_cast<Creature>(creature), combatant))
|
||||
continue;
|
||||
|
||||
float distance = glm::length(creature->position() - combatant->position()); // TODO: fine tune the distance
|
||||
if (distance < min_dist) {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#include <unordered_set>
|
||||
#include <map>
|
||||
|
||||
|
||||
#include "faction.h"
|
||||
#include "effect.h"
|
||||
#include "types.h"
|
||||
#include "object/creature.h"
|
||||
|
@ -71,20 +71,46 @@ private:
|
|||
uint32_t _timestamp;
|
||||
};
|
||||
|
||||
constexpr float MAX_DETECT_RANGE = 20; // TODO: adjust detection distance
|
||||
|
||||
class Combat {
|
||||
|
||||
public:
|
||||
Combat(Area *area, Party *party);
|
||||
|
||||
/*
|
||||
* AIMaster -> CombatStateMachine -> effectSync -> animationSync
|
||||
* Always:
|
||||
* 0. Update Timers
|
||||
* 1. Activity Scanner
|
||||
* 2. Sync Effect
|
||||
|
||||
* If Combat Mode Activated:
|
||||
* 3. AIMaster
|
||||
* 4. Update CombatStateMachine
|
||||
* 5. Sync Animation
|
||||
*/
|
||||
void update();
|
||||
|
||||
/*
|
||||
* Roles:
|
||||
* 1. Scan surroundings for hostiles
|
||||
* 2. Queue Commands (e.g. go to, item consumption, equipment swapping etc.)
|
||||
* 1. Scan the surrounding of one combatant per frame, remove the combatant if
|
||||
* no enemies are in visible range & no action in actionQueue
|
||||
* 2. Add creatures to _activeCombatants, if applicable
|
||||
* 3. Activate/Deactivate global combat mode for party->player
|
||||
*/
|
||||
void activityScanner();
|
||||
|
||||
/*
|
||||
* Roles:
|
||||
* 1. Evaluate damage/effects
|
||||
* 2. Animate damage statistics
|
||||
* 3. Feedback Text
|
||||
*/
|
||||
void effectSync();
|
||||
|
||||
/*
|
||||
* Roles:
|
||||
* 1. Queue Commands (e.g. go to, item consumption, equipment swapping etc.)
|
||||
*/
|
||||
void AIMaster(std::shared_ptr<Creature> &combatant);
|
||||
|
||||
|
@ -95,13 +121,6 @@ public:
|
|||
*/
|
||||
void combatStateMachine(std::shared_ptr<Creature> &combatant);
|
||||
|
||||
/*
|
||||
* 1. Evaluate damage/effects
|
||||
* 2. Animate damage statistics
|
||||
* 3. Feedback Text
|
||||
*/
|
||||
void effectSync();
|
||||
|
||||
/*
|
||||
* Roles:
|
||||
* 1. Synchronize dueling and isolated attacks
|
||||
|
@ -115,8 +134,21 @@ private:
|
|||
Area *_area;
|
||||
Party *_party;
|
||||
|
||||
std::list<std::shared_ptr<Creature>> _activeCombatants;
|
||||
|
||||
/* combat mode */
|
||||
bool _activated = false;
|
||||
|
||||
/* register to _activeCombatants */
|
||||
bool registerCombatant(const std::shared_ptr<Creature> &combatant) {
|
||||
auto res = _activeCombatantIds.insert(combatant->id());
|
||||
if (res.second) { // combatant not already in _activeCombatantIds
|
||||
_activeCombatants.push_back(combatant);
|
||||
}
|
||||
return res.second;
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<Creature>> _activeCombatants;
|
||||
std::unordered_set<uint32_t> _activeCombatantIds;
|
||||
|
||||
/* queue[ pair( attacker, victim ) ] */
|
||||
std::deque<std::pair<std::shared_ptr<Creature>,
|
||||
std::shared_ptr<Creature>>> _duelQueue;
|
||||
|
|
114
src/game/faction.cpp
Normal file
114
src/game/faction.cpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2020 uwadmin12
|
||||
*
|
||||
* 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 "faction.h"
|
||||
#include "../common/log.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace game {
|
||||
|
||||
const std::list<Faction> HOSTILE = {
|
||||
Faction::STANDARD_FACTION_HOSTILE_1, Faction::STANDARD_FACTION_HOSTILE_2 };
|
||||
|
||||
const std::list<Faction> FRIENDLY = {
|
||||
Faction::STANDARD_FACTION_FRIENDLY_1, Faction::STANDARD_FACTION_FRIENDLY_2 };
|
||||
|
||||
const std::list<Faction> SURRENDER = {
|
||||
Faction::STANDARD_FACTION_SURRENDER_1, Faction::STANDARD_FACTION_SURRENDER_2 };
|
||||
|
||||
const std::list<Faction> GIZKA = {
|
||||
Faction::STANDARD_FACTION_GIZKA_1, Faction::STANDARD_FACTION_GIZKA_2 };
|
||||
|
||||
std::list<std::pair<Faction, Faction>> groupPairs(std::list<Faction> group1, std::list<Faction> group2) {
|
||||
std::list<std::pair<Faction, Faction>> aggr;
|
||||
for (auto f1 : group1) {
|
||||
for (auto f2 : group2) {
|
||||
aggr.push_back(std::make_pair(f1, f2));
|
||||
}
|
||||
}
|
||||
return std::move(aggr);
|
||||
}
|
||||
|
||||
std::list<std::pair<Faction, Faction>> propagateHostileLinks() {
|
||||
std::list<std::pair<Faction, Faction>> aggr;
|
||||
|
||||
// aggregate factions that are hostile towards each other?
|
||||
aggr.splice(aggr.end(), groupPairs(HOSTILE, FRIENDLY));
|
||||
aggr.splice(aggr.end(), groupPairs(HOSTILE, SURRENDER));
|
||||
aggr.splice(aggr.end(), groupPairs(HOSTILE, GIZKA));
|
||||
aggr.splice(aggr.end(), groupPairs(HOSTILE, { Faction::STANDARD_FACTION_ENDAR_SPIRE }));
|
||||
aggr.splice(aggr.end(), groupPairs(HOSTILE, { Faction::STANDARD_FACTION_PREDATOR }));
|
||||
aggr.splice(aggr.end(), groupPairs(HOSTILE, { Faction::STANDARD_FACTION_PREY }));
|
||||
aggr.splice(aggr.end(), groupPairs(FRIENDLY, { Faction::STANDARD_FACTION_PREDATOR }));
|
||||
aggr.splice(aggr.end(), groupPairs(FRIENDLY, { Faction::STANDARD_FACTION_PREY }));
|
||||
aggr.splice(aggr.end(), groupPairs(FRIENDLY, { Faction::STANDARD_FACTION_RANCOR }));
|
||||
aggr.splice(aggr.end(), groupPairs(FRIENDLY, { Faction::STANDARD_FACTION_PTAT_TUSKAN }));
|
||||
|
||||
aggr.insert(aggr.end(), std::make_pair(Faction::STANDARD_FACTION_PREDATOR, Faction::STANDARD_FACTION_PREY));
|
||||
|
||||
return std::move(aggr);
|
||||
}
|
||||
|
||||
std::vector<std::vector<bool>> initialize() {
|
||||
std::vector<std::vector<bool>> arr(MAX_NUM_FACTION, std::vector<bool>(MAX_NUM_FACTION, false));
|
||||
|
||||
// set insane faction hostile to all factions
|
||||
for (size_t i = 0; i < MAX_NUM_FACTION; ++i) {
|
||||
size_t j = static_cast<size_t>(Faction::STANDARD_FACTION_INSANE);
|
||||
arr[i][j] = true;
|
||||
arr[j][i] = true;
|
||||
}
|
||||
|
||||
// propagate hostile links to map
|
||||
for (auto& pr : propagateHostileLinks()) {
|
||||
size_t i = static_cast<size_t>(pr.first);
|
||||
size_t j = static_cast<size_t>(pr.second);
|
||||
|
||||
arr[i][j] = true;
|
||||
arr[j][i] = true;
|
||||
}
|
||||
|
||||
return std::move(arr);
|
||||
}
|
||||
|
||||
const std::vector<std::vector<bool>> _hostility = initialize();
|
||||
|
||||
bool getIsEnemy(const std::shared_ptr<Creature>& oTarget, const std::shared_ptr<Creature>& oSource) {
|
||||
if (!oTarget || !oSource) {
|
||||
debug("getIsEnemy, oTarget or OSource is nullptr");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int s = static_cast<int>(oSource->getFaction());
|
||||
int t = static_cast<int>(oTarget->getFaction());
|
||||
if (s < 0 || s >= MAX_NUM_FACTION || t < 0 || t >= MAX_NUM_FACTION) {
|
||||
debug(boost::format("Source %s Faction: %d") % oSource->tag() % s);
|
||||
debug(boost::format("Target %s Faction: %d") % oTarget->tag() % t);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return _hostility[s][t];
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
|
||||
} // namespace reone
|
15
src/game/faction.h
Normal file
15
src/game/faction.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "object/creature.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace game {
|
||||
|
||||
constexpr static size_t MAX_NUM_FACTION = 25;
|
||||
|
||||
bool getIsEnemy(const std::shared_ptr<Creature>& oTarget, const std::shared_ptr<Creature>& oSource);
|
||||
|
||||
} // namespace game
|
||||
|
||||
} // namespace reone
|
|
@ -205,11 +205,13 @@ void MainMenu::onModuleSelected(const string &name) {
|
|||
shared_ptr<Creature> player(_game->objectFactory().newCreature());
|
||||
player->load(playerCfg);
|
||||
player->setTag("PLAYER");
|
||||
player->setFaction(Faction::STANDARD_FACTION_FRIENDLY_1);
|
||||
party.addMember(player);
|
||||
party.setPlayer(player);
|
||||
|
||||
shared_ptr<Creature> companion(_game->objectFactory().newCreature());
|
||||
companion->load(companionCfg);
|
||||
companion->setFaction(Faction::STANDARD_FACTION_FRIENDLY_1);
|
||||
companion->actionQueue().add(make_unique<FollowAction>(player, 1.0f));
|
||||
party.addMember(companion);
|
||||
|
||||
|
|
|
@ -111,6 +111,16 @@ void Creature::load(const shared_ptr<CreatureBlueprint> &blueprint) {
|
|||
_attributes = blueprint->attributes();
|
||||
_onSpawn = blueprint->onSpawn();
|
||||
_onUserDefined = blueprint->onUserDefined();
|
||||
|
||||
// TODO: update to match KOTOR 2
|
||||
if (blueprint->factionId() < 1 || blueprint->factionId() > 17) {
|
||||
if (blueprint->factionId() != -1)
|
||||
debug(boost::format("'%s' with id '%d' has strange factionId(): %d") %tag() % id() % blueprint->factionId());
|
||||
|
||||
_factionId = Faction::INVALID_STANDARD_FACTION;
|
||||
} else {
|
||||
_factionId = static_cast<Faction>(blueprint->factionId());
|
||||
}
|
||||
}
|
||||
|
||||
void Creature::loadAppearance(const TwoDaTable &table, int row) {
|
||||
|
|
|
@ -43,6 +43,28 @@ enum class CombatState {
|
|||
Staggered
|
||||
};
|
||||
|
||||
// TODO: Factions from KOTOR 2
|
||||
enum class Faction {
|
||||
INVALID_STANDARD_FACTION = -1,
|
||||
STANDARD_FACTION_HOSTILE_1 = 1,
|
||||
STANDARD_FACTION_FRIENDLY_1 = 2,
|
||||
STANDARD_FACTION_HOSTILE_2 = 3,
|
||||
STANDARD_FACTION_FRIENDLY_2 = 4,
|
||||
STANDARD_FACTION_NEUTRAL = 5,
|
||||
STANDARD_FACTION_INSANE = 6,
|
||||
STANDARD_FACTION_PTAT_TUSKAN = 7,
|
||||
STANDARD_FACTION_GLB_XOR = 8,
|
||||
STANDARD_FACTION_SURRENDER_1 = 9,
|
||||
STANDARD_FACTION_SURRENDER_2 = 10,
|
||||
STANDARD_FACTION_PREDATOR = 11,
|
||||
STANDARD_FACTION_PREY = 12,
|
||||
STANDARD_FACTION_TRAP = 13,
|
||||
STANDARD_FACTION_ENDAR_SPIRE = 14,
|
||||
STANDARD_FACTION_RANCOR = 15,
|
||||
STANDARD_FACTION_GIZKA_1 = 16,
|
||||
STANDARD_FACTION_GIZKA_2 = 17
|
||||
};
|
||||
|
||||
class Creature : public SpatialObject {
|
||||
public:
|
||||
enum class MovementType {
|
||||
|
@ -110,11 +132,15 @@ public:
|
|||
// Combat
|
||||
|
||||
/* combat animation interruption */
|
||||
bool isInterrupted() { return _cbtState != CombatState::Idle; }
|
||||
bool isInterrupted() { return !(_cbtState == CombatState::Idle || _cbtState == CombatState::Cooldown); }
|
||||
|
||||
CombatState getCombatState() { return _cbtState; }
|
||||
void setCombatState(CombatState state) { _cbtState = state; }
|
||||
|
||||
Faction getFaction() const { return _factionId; }
|
||||
|
||||
void setFaction(Faction faction) { _factionId = faction; }
|
||||
|
||||
// const std::deque<std::unique_ptr<Effect>> &getActiveEffects() { return _activeEffects; }
|
||||
|
||||
void applyEffect(std::unique_ptr<Effect> &&eff) {
|
||||
|
@ -154,6 +180,7 @@ private:
|
|||
|
||||
CombatState _cbtState = CombatState::Idle;
|
||||
std::deque<std::unique_ptr<Effect>> _activeEffects;
|
||||
Faction _factionId = Faction::INVALID_STANDARD_FACTION;
|
||||
|
||||
// END combat
|
||||
|
||||
|
|
|
@ -77,12 +77,12 @@ bool Player::handleKeyDown(const SDL_KeyboardEvent &event) {
|
|||
return true;
|
||||
|
||||
case SDL_SCANCODE_C:
|
||||
|
||||
_moveRight = true;
|
||||
return true;
|
||||
|
||||
case SDL_SCANCODE_F:
|
||||
_party->leader()->actionQueue().add(std::make_unique<AttackAction>(
|
||||
_area->combat().findNearestHostile(_party->leader())));
|
||||
_party->player()->actionQueue().add(std::make_unique<AttackAction>(_area->combat().findNearestHostile(_party->player())));
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
@ -121,7 +121,7 @@ void Player::update(float dt) {
|
|||
shared_ptr<Creature> partyLeader(_party->leader());
|
||||
if (!partyLeader) return;
|
||||
|
||||
if (_party->leader()->isInterrupted()) return;
|
||||
if (_party->player()->isInterrupted()) return;
|
||||
|
||||
float heading = 0.0f;
|
||||
bool movement = true;
|
||||
|
|
Loading…
Reference in a new issue