refactor: Extract creature animation handling into a separate class

This commit is contained in:
Vsevolod Kremianskii 2020-12-10 13:48:40 +07:00
parent 798eb3f809
commit 0fcb37f45f
12 changed files with 377 additions and 196 deletions

View file

@ -404,6 +404,8 @@ set(GAME_HEADERS
src/game/object/area.h
src/game/object/camera.h
src/game/object/creature.h
src/game/object/creatureanimresolver.h
src/game/object/creaturemodelbuilder.h
src/game/object/door.h
src/game/object/item.h
src/game/object/module.h
@ -492,6 +494,8 @@ set(GAME_SOURCES
src/game/object/area.cpp
src/game/object/camera.cpp
src/game/object/creature.cpp
src/game/object/creatureanimresolver.cpp
src/game/object/creaturemodelbuilder.cpp
src/game/object/door.cpp
src/game/object/item.cpp
src/game/object/module.cpp

View file

@ -105,7 +105,7 @@ void Combat::updateCombatantAI(Combatant &combatant) {
ActionQueue &actions = creature->actionQueue();
actions.clear();
actions.add(make_unique<AttackAction>(enemy, creature->attackRange()));
actions.add(make_unique<AttackAction>(enemy, creature->getAttackRange()));
debug(boost::format("Combat: attack action added: '%s' -> '%s'") % creature->tag() % enemy->tag(), 2);
}

View file

@ -126,7 +126,7 @@ bool SelectionOverlay::handleMouseButtonDown(const SDL_MouseButtonEvent &event)
shared_ptr<Creature> partyLeader(_game->party().leader());
ActionQueue &actions = partyLeader->actionQueue();
actions.add(make_unique<AttackAction>(static_pointer_cast<Creature>(object),
partyLeader->attackRange()));
partyLeader->getAttackRange()));
break;
}

View file

@ -648,7 +648,7 @@ void Area::fill(SceneGraph &sceneGraph) {
glm::vec3 Area::getSelectableScreenCoords(const shared_ptr<SpatialObject> &object, const glm::mat4 &projection, const glm::mat4 &view) const {
static glm::vec4 viewport(0.0f, 0.0f, 1.0f, 1.0f);
glm::vec3 position(object->selectablePosition());
glm::vec3 position(object->getSelectablePosition());
return glm::project(position, view, projection, viewport);
}

View file

@ -49,30 +49,24 @@ namespace reone {
namespace game {
static string g_animPauseCreature("cpause1");
static string g_animPauseCharacter("pause1");
static string g_animWalkCreature("cwalk");
static string g_animWalkCharacter("walk");
static string g_animRunCreature("crun");
static string g_animRunCharacter("run");
static string g_animTalkHead("talk");
static string g_animTalkBody("tlknorm");
static string g_animGreeting("greeting");
static string g_animUnlockDoor("unlockdr");
static string g_headHookNode("headhook");
static string g_talkDummyNode("talkdummy");
Creature::Creature(uint32_t id, ObjectFactory *objectFactory, SceneGraph *sceneGraph) :
SpatialObject(id, ObjectType::Creature, sceneGraph), _objectFactory(objectFactory) {
SpatialObject(id, ObjectType::Creature, sceneGraph),
_objectFactory(objectFactory),
_animResolver(this) {
_drawDistance = 2048.0f;
_selectable = true;
}
void Creature::load(const GffStruct &gffs) {
loadTransform(gffs);
loadBlueprint(gffs);
}
void Creature::loadTransform(const GffStruct &gffs) {
_position[0] = gffs.getFloat("XPosition");
_position[1] = gffs.getFloat("YPosition");
_position[2] = gffs.getFloat("ZPosition");
@ -310,19 +304,19 @@ void Creature::updateModelAnimation() {
switch (_movementType) {
case MovementType::Run:
_model->playAnimation(getRunAnimation(), kAnimationLoop | kAnimationPropagate | kAnimationBlend);
_model->playAnimation(_animResolver.getRunAnimation(), kAnimationLoop | kAnimationPropagate | kAnimationBlend);
break;
case MovementType::Walk:
_model->playAnimation(getWalkAnimation(), kAnimationLoop | kAnimationPropagate | kAnimationBlend);
_model->playAnimation(_animResolver.getWalkAnimation(), kAnimationLoop | kAnimationPropagate | kAnimationBlend);
break;
default:
if (_talking) {
_model->playAnimation(g_animTalkBody, kAnimationLoop | kAnimationPropagate);
_model->playAnimation(_animResolver.getTalkAnimation(), kAnimationLoop | kAnimationPropagate);
if (_headModel) {
_headModel->playAnimation(g_animTalkHead, kAnimationLoop | kAnimationOverlay, 0.25f);
_headModel->playAnimation(_animResolver.getHeadTalkAnimation(), kAnimationLoop | kAnimationOverlay, 0.25f);
}
} else {
_model->playAnimation(getPauseAnimation(), kAnimationLoop | kAnimationPropagate | kAnimationBlend);
_model->playAnimation(_animResolver.getPauseAnimation(), kAnimationLoop | kAnimationPropagate | kAnimationBlend);
}
break;
}
@ -341,19 +335,19 @@ void Creature::playAnimation(Animation anim) {
string animName;
switch (anim) {
case Animation::UnlockDoor:
animName = g_animUnlockDoor;
animName = _animResolver.getUnlockDoorAnimation();
break;
case Animation::DuelAttack:
animName = getDuelAttackAnimation();
animName = _animResolver.getDuelAttackAnimation();
break;
case Animation::BashAttack:
animName = getBashAttackAnimation();
animName = _animResolver.getBashAttackAnimation();
break;
case Animation::Dodge:
animName = getDodgeAnimation();
animName = _animResolver.getDodgeAnimation();
break;
case Animation::Knockdown:
animName = getKnockdownAnimation();
animName = _animResolver.getKnockdownAnimation();
break;
default:
break;
@ -424,146 +418,6 @@ void Creature::setTalking(bool talking) {
_animDirty = true;
}
string Creature::getPauseAnimation() const {
if (_modelType == ModelType::Creature) {
return g_animPauseCreature;
}
// TODO: if (_lowHP) return "pauseinj"
if (_inCombat) {
WeaponType type = WeaponType::None;
WeaponWield wield = WeaponWield::None;
getWeaponInfo(type, wield);
int wieldNumber = getWeaponWieldNumber(wield);
return str(boost::format("g%dr1") % wieldNumber);
}
return g_animPauseCharacter;
}
const string &Creature::getWalkAnimation() const {
switch (_modelType) {
case ModelType::Creature:
return g_animWalkCreature;
default:
return g_animWalkCharacter;
}
}
string Creature::getRunAnimation() const {
if (_modelType == ModelType::Creature) {
return g_animRunCreature;
}
// TODO: if (_lowHP) return "runinj"
if (_inCombat) {
WeaponType type = WeaponType::None;
WeaponWield wield = WeaponWield::None;
getWeaponInfo(type, wield);
switch (wield) {
case WeaponWield::SingleSaber:
return isSlotEquipped(kInventorySlotLeftWeapon) ? "runds" : "runss";
case WeaponWield::TwoHandedSaber:
return "runst";
case WeaponWield::Rifle:
case WeaponWield::HeavyCarbine:
return "runrf";
default:
break;
}
}
return g_animRunCharacter;
}
string Creature::getDuelAttackAnimation() const {
if (_modelType == ModelType::Creature) return "g0a1";
WeaponType type = WeaponType::None;
WeaponWield wield = WeaponWield::None;
getWeaponInfo(type, wield);
int wieldNumber = getWeaponWieldNumber(wield);
switch (type) {
case WeaponType::Melee:
return str(boost::format("c%da1") % wieldNumber);
case WeaponType::Ranged:
return str(boost::format("b%da1") % wieldNumber);
default:
return str(boost::format("g%da1") % wieldNumber);
}
}
bool Creature::getWeaponInfo(WeaponType &type, WeaponWield &wield) const {
shared_ptr<Item> item(getEquippedItem(kInventorySlotRightWeapon));
if (item) {
type = item->weaponType();
wield = item->weaponWield();
return true;
}
return false;
}
int Creature::getWeaponWieldNumber(WeaponWield wield) const {
switch (wield) {
case WeaponWield::StunBaton:
return 1;
case WeaponWield::SingleSaber:
return isSlotEquipped(kInventorySlotLeftWeapon) ? 4 : 2;
case WeaponWield::TwoHandedSaber:
return 3;
case WeaponWield::SingleBlaster:
return isSlotEquipped(kInventorySlotLeftWeapon) ? 6 : 5;
case WeaponWield::Rifle:
return 7;
case WeaponWield::HeavyCarbine:
return 9;
default:
return 8;
}
}
string Creature::getBashAttackAnimation() const {
if (_modelType == ModelType::Creature) return "g0a2";
WeaponType type = WeaponType::None;
WeaponWield wield = WeaponWield::None;
getWeaponInfo(type, wield);
int wieldNumber = getWeaponWieldNumber(wield);
switch (type) {
case WeaponType::Melee:
return str(boost::format("c%da2") % wieldNumber);
case WeaponType::Ranged:
return str(boost::format("b%da2") % wieldNumber);
default:
return str(boost::format("g%da2") % wieldNumber);
}
}
string Creature::getDodgeAnimation() const {
if (_modelType == ModelType::Creature) return "cdodgeg";
WeaponType type = WeaponType::None;
WeaponWield wield = WeaponWield::None;
getWeaponInfo(type, wield);
int wieldNumber = getWeaponWieldNumber(wield);
return str(boost::format("g%dg1") % wieldNumber);
}
string Creature::getKnockdownAnimation() const {
return _modelType == ModelType::Creature ? "ckdbck" : "g1y1";
}
void Creature::setPath(const glm::vec3 &dest, vector<glm::vec3> &&points, uint32_t timeFound) {
int pointIdx = 0;
if (_path) {
@ -612,6 +466,10 @@ Gender Creature::gender() const {
return _config.gender;
}
Creature::ModelType Creature::modelType() const {
return _modelType;
}
int Creature::appearance() const {
return _config.appearance;
}
@ -640,7 +498,7 @@ CreatureAttributes &Creature::attributes() {
return _attributes;
}
glm::vec3 Creature::selectablePosition() const {
glm::vec3 Creature::getSelectablePosition() const {
glm::vec3 position;
if (_model->getNodeAbsolutePosition(g_talkDummyNode, position)) {
@ -654,7 +512,7 @@ Faction Creature::faction() const {
return _faction;
}
float Creature::attackRange() const {
float Creature::getAttackRange() const {
float result = kDefaultAttackRange;
shared_ptr<Item> item(getEquippedItem(kInventorySlotRightWeapon));
@ -673,10 +531,18 @@ bool Creature::isMovementRestricted() const {
return _movementRestricted;
}
bool Creature::isInCombat() const {
return _inCombat;
}
void Creature::setMovementRestricted(bool restricted) {
_movementRestricted = restricted;
}
void Creature::setInCombat(bool active) {
_inCombat = active;
}
void Creature::setOnSpawn(const string &onSpawn) {
_onSpawn = onSpawn;
}

View file

@ -27,6 +27,7 @@
#include "../enginetype/effect.h"
#include "../rp/attributes.h"
#include "creatureanimresolver.h"
#include "item.h"
#include "spatial.h"
@ -51,6 +52,12 @@ enum class CombatState {
class Creature : public SpatialObject {
public:
enum class ModelType {
Creature,
Droid,
Character
};
enum class MovementType {
None,
Walk,
@ -77,7 +84,7 @@ public:
void update(float dt) override;
void clearAllActions() override;
glm::vec3 selectablePosition() const override;
glm::vec3 getSelectablePosition() const override;
void load(const resource::GffStruct &gffs);
void load(const std::shared_ptr<CreatureBlueprint> &blueprint);
@ -85,26 +92,28 @@ public:
void playAnimation(Animation anim);
void updateModelAnimation();
void applyEffect(std::unique_ptr<Effect> &&eff);
bool isMovementRestricted() const;
bool isInCombat() const;
float getAttackRange() const;
const std::string &blueprintResRef() const;
Gender gender() const;
ModelType modelType() const;
int appearance() const;
std::shared_ptr<render::Texture> portrait() const;
float walkSpeed() const;
float runSpeed() const;
CreatureAttributes &attributes();
Faction faction() const;
float attackRange() const;
void setMovementType(MovementType type);
void setTalking(bool talking);
void setFaction(Faction faction);
void setMovementRestricted(bool restricted);
void setInCombat(bool active) { _inCombat = active; }
void setInCombat(bool active);
void setOnSpawn(const std::string &onSpawn);
void setOnUserDefined(const std::string &onUserDefined);
@ -114,29 +123,24 @@ public:
void equip(InventorySlot slot, const std::shared_ptr<Item> &item);
void unequip(const std::shared_ptr<Item> &item);
std::shared_ptr<Item> getEquippedItem(InventorySlot slot) const;
bool isSlotEquipped(InventorySlot slot) const;
std::shared_ptr<Item> getEquippedItem(InventorySlot slot) const;
const std::map<InventorySlot, std::shared_ptr<Item>> &equipment() const;
// END Equipment
// Pathfinding
std::shared_ptr<Path> &path();
void setPath(const glm::vec3 &dest, std::vector<glm::vec3> &&points, uint32_t timeFound);
void clearPath();
std::shared_ptr<Path> &path();
// END Pathfinding
private:
enum class ModelType {
Creature,
Droid,
Character
};
ObjectFactory *_objectFactory { nullptr };
CreatureConfiguration _config;
std::string _blueprintResRef;
@ -158,6 +162,7 @@ private:
bool _movementRestricted { false };
bool _inCombat { false };
int _portraitId { 0 };
CreatureAnimationResolver _animResolver;
// Scripts
@ -165,29 +170,19 @@ private:
// END Scripts
void loadTransform(const resource::GffStruct &gffs);
void loadBlueprint(const resource::GffStruct &gffs);
void loadAppearance(const resource::TwoDaTable &table, int row);
void loadPortrait(int appearance);
void updateModel();
ModelType parseModelType(const std::string &s) const;
bool getWeaponInfo(WeaponType &type, WeaponWield &wield) const;
int getWeaponWieldNumber(WeaponWield wield) const;
std::string getBodyModelName() const;
std::string getBodyTextureName() const;
std::string getHeadModelName() const;
std::string getWeaponModelName(InventorySlot slot) const;
std::string getPauseAnimation() const;
std::string getRunAnimation() const;
const std::string &getWalkAnimation() const;
std::string getDuelAttackAnimation() const;
std::string getBashAttackAnimation() const;
std::string getDodgeAnimation() const;
std::string getKnockdownAnimation() const;
friend class CreatureBlueprint;
};

View file

@ -0,0 +1,203 @@
/*
* Copyright (c) 2020 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 "creatureanimresolver.h"
#include <stdexcept>
#include <boost/format.hpp>
#include "creature.h"
using namespace std;
namespace reone {
namespace game {
static string g_animPauseCreature("cpause1");
static string g_animPauseCharacter("pause1");
static string g_animWalkCreature("cwalk");
static string g_animWalkCharacter("walk");
static string g_animRunCreature("crun");
static string g_animRunCharacter("run");
static string g_animTalkHead("talk");
static string g_animTalkBody("tlknorm");
static string g_animGreeting("greeting");
static string g_animUnlockDoor("unlockdr");
CreatureAnimationResolver::CreatureAnimationResolver(const Creature *creature) : _creature(creature) {
if (!creature) {
throw invalid_argument("creature must not be null");
}
}
string CreatureAnimationResolver::getPauseAnimation() const {
if (_creature->modelType() == Creature::ModelType::Creature) {
return g_animPauseCreature;
}
// TODO: if (_lowHP) return "pauseinj"
if (_creature->isInCombat()) {
WeaponType type = WeaponType::None;
WeaponWield wield = WeaponWield::None;
getWeaponInfo(type, wield);
int wieldNumber = getWeaponWieldNumber(wield);
return str(boost::format("g%dr1") % wieldNumber);
}
return g_animPauseCharacter;
}
bool CreatureAnimationResolver::getWeaponInfo(WeaponType &type, WeaponWield &wield) const {
shared_ptr<Item> item(_creature->getEquippedItem(kInventorySlotRightWeapon));
if (item) {
type = item->weaponType();
wield = item->weaponWield();
return true;
}
return false;
}
int CreatureAnimationResolver::getWeaponWieldNumber(WeaponWield wield) const {
switch (wield) {
case WeaponWield::StunBaton:
return 1;
case WeaponWield::SingleSaber:
return _creature->isSlotEquipped(kInventorySlotLeftWeapon) ? 4 : 2;
case WeaponWield::TwoHandedSaber:
return 3;
case WeaponWield::SingleBlaster:
return _creature->isSlotEquipped(kInventorySlotLeftWeapon) ? 6 : 5;
case WeaponWield::Rifle:
return 7;
case WeaponWield::HeavyCarbine:
return 9;
default:
return 8;
}
}
string CreatureAnimationResolver::getWalkAnimation() const {
switch (_creature->modelType()) {
case Creature::ModelType::Creature:
return g_animWalkCreature;
default:
return g_animWalkCharacter;
}
}
string CreatureAnimationResolver::getRunAnimation() const {
if (_creature->modelType() == Creature::ModelType::Creature) {
return g_animRunCreature;
}
// TODO: if (_lowHP) return "runinj"
if (_creature->isInCombat()) {
WeaponType type = WeaponType::None;
WeaponWield wield = WeaponWield::None;
getWeaponInfo(type, wield);
switch (wield) {
case WeaponWield::SingleSaber:
return _creature->isSlotEquipped(kInventorySlotLeftWeapon) ? "runds" : "runss";
case WeaponWield::TwoHandedSaber:
return "runst";
case WeaponWield::Rifle:
case WeaponWield::HeavyCarbine:
return "runrf";
default:
break;
}
}
return g_animRunCharacter;
}
string CreatureAnimationResolver::getUnlockDoorAnimation() const {
return g_animUnlockDoor;
}
string CreatureAnimationResolver::getTalkAnimation() const {
return g_animTalkBody;
}
string CreatureAnimationResolver::getHeadTalkAnimation() const {
return g_animTalkHead;
}
string CreatureAnimationResolver::getDuelAttackAnimation() const {
if (_creature->modelType() == Creature::ModelType::Creature) return "g0a1";
WeaponType type = WeaponType::None;
WeaponWield wield = WeaponWield::None;
getWeaponInfo(type, wield);
int wieldNumber = getWeaponWieldNumber(wield);
switch (type) {
case WeaponType::Melee:
return str(boost::format("c%da1") % wieldNumber);
case WeaponType::Ranged:
return str(boost::format("b%da1") % wieldNumber);
default:
return str(boost::format("g%da1") % wieldNumber);
}
}
string CreatureAnimationResolver::getBashAttackAnimation() const {
if (_creature->modelType() == Creature::ModelType::Creature) return "g0a2";
WeaponType type = WeaponType::None;
WeaponWield wield = WeaponWield::None;
getWeaponInfo(type, wield);
int wieldNumber = getWeaponWieldNumber(wield);
switch (type) {
case WeaponType::Melee:
return str(boost::format("c%da2") % wieldNumber);
case WeaponType::Ranged:
return str(boost::format("b%da2") % wieldNumber);
default:
return str(boost::format("g%da2") % wieldNumber);
}
}
string CreatureAnimationResolver::getDodgeAnimation() const {
if (_creature->modelType() == Creature::ModelType::Creature) return "cdodgeg";
WeaponType type = WeaponType::None;
WeaponWield wield = WeaponWield::None;
getWeaponInfo(type, wield);
int wieldNumber = getWeaponWieldNumber(wield);
return str(boost::format("g%dg1") % wieldNumber);
}
string CreatureAnimationResolver::getKnockdownAnimation() const {
return _creature->modelType() == Creature::ModelType::Creature ? "ckdbck" : "g1y1";
}
} // namespace game
} // namespace reone

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020 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 <string>
#include "../types.h"
namespace reone {
namespace game {
class Creature;
class CreatureAnimationResolver {
public:
CreatureAnimationResolver(const Creature *creature);
std::string getPauseAnimation() const;
std::string getRunAnimation() const;
std::string getWalkAnimation() const;
std::string getUnlockDoorAnimation() const;
std::string getTalkAnimation() const;
std::string getHeadTalkAnimation() const;
// Attack animations
std::string getDuelAttackAnimation() const;
std::string getBashAttackAnimation() const;
std::string getDodgeAnimation() const;
std::string getKnockdownAnimation() const;
// END Attack animations
private:
const Creature *_creature { nullptr };
bool getWeaponInfo(WeaponType &type, WeaponWield &wield) const;
int getWeaponWieldNumber(WeaponWield wield) const;
};
} // namespace game
} // namespace reone

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2020 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 "creaturemodelbuilder.h"
namespace reone {
namespace game {
} // namespace game
} // namespace reone

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2020 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
namespace reone {
namespace game {
} // namespace game
} // namespace reone

View file

@ -118,7 +118,7 @@ const vector<shared_ptr<Item>> &SpatialObject::items() const {
return _items;
}
glm::vec3 SpatialObject::selectablePosition() const {
glm::vec3 SpatialObject::getSelectablePosition() const {
return _model->getCenterOfAABB();
}

View file

@ -41,7 +41,10 @@ class SpatialObject : public Object {
public:
void update(float dt) override;
void face(const SpatialObject &other);
void addItem(const std::shared_ptr<Item> &item);
void moveItemsTo(SpatialObject &other);
float distanceTo(const glm::vec2 &point) const;
float distanceTo(const glm::vec3 &point) const;
@ -49,11 +52,10 @@ public:
bool contains(const glm::vec3 &point) const;
void face(const SpatialObject &other);
void moveItemsTo(SpatialObject &other);
bool isSelectable() const;
virtual glm::vec3 getSelectablePosition() const;
Room *room() const;
const glm::vec3 &position() const;
float heading() const;
@ -62,7 +64,6 @@ public:
std::shared_ptr<scene::ModelSceneNode> model() const;
std::shared_ptr<render::Walkmesh> walkmesh() const;
const std::vector<std::shared_ptr<Item>> &items() const;
virtual glm::vec3 selectablePosition() const;
float drawDistance() const;
void setRoom(Room *room);