Skip to content

Commit

Permalink
Redesigned weapon animation system.
Browse files Browse the repository at this point in the history
Now acts like a real state machine similar to entity animations. Also allows idle animations.
  • Loading branch information
afritz1 committed Dec 24, 2024
1 parent 7e22e17 commit 84b216a
Show file tree
Hide file tree
Showing 19 changed files with 710 additions and 454 deletions.
8 changes: 7 additions & 1 deletion OpenTESArena/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ SET(TES_INTERFACE
"${SRC_ROOT}/Interface/WorldMapUiView.h")

SET(TES_ITEMS
"${SRC_ROOT}/Items/ArenaItemUtils.cpp"
"${SRC_ROOT}/Items/ArenaItemUtils.h"
"${SRC_ROOT}/Items/ItemConditionLibrary.cpp"
"${SRC_ROOT}/Items/ItemConditionLibrary.h"
Expand Down Expand Up @@ -379,6 +380,7 @@ SET(TES_MATH
SET(TES_PLAYER
"${SRC_ROOT}/Player/ArenaPlayerUtils.cpp"
"${SRC_ROOT}/Player/ArenaPlayerUtils.h"
"${SRC_ROOT}/Player/ArenaWeaponUtils.h"
"${SRC_ROOT}/Player/CharacterClassGeneration.cpp"
"${SRC_ROOT}/Player/CharacterClassGeneration.h"
"${SRC_ROOT}/Player/CharacterCreationState.cpp"
Expand All @@ -391,7 +393,11 @@ SET(TES_PLAYER
"${SRC_ROOT}/Player/PlayerLogicController.cpp"
"${SRC_ROOT}/Player/PlayerLogicController.h"
"${SRC_ROOT}/Player/WeaponAnimation.cpp"
"${SRC_ROOT}/Player/WeaponAnimation.h")
"${SRC_ROOT}/Player/WeaponAnimation.h"
"${SRC_ROOT}/Player/WeaponAnimationLibrary.cpp"
"${SRC_ROOT}/Player/WeaponAnimationLibrary.h"
"${SRC_ROOT}/Player/WeaponAnimationUtils.cpp"
"${SRC_ROOT}/Player/WeaponAnimationUtils.h")

SET(TES_RENDERING
"${SRC_ROOT}/Rendering/ArenaRenderUtils.cpp"
Expand Down
8 changes: 7 additions & 1 deletion OpenTESArena/src/Entities/EntityChunkManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "../Math/RandomUtils.h"
#include "../Math/Random.h"
#include "../Player/Player.h"
#include "../Player/WeaponAnimationLibrary.h"
#include "../Rendering/Renderer.h"
#include "../Voxels/VoxelChunk.h"
#include "../Voxels/VoxelChunkManager.h"
Expand Down Expand Up @@ -988,7 +989,12 @@ void EntityChunkManager::update(double dt, BufferView<const ChunkInt2> activeChu
const CoordDouble3 playerCoord = player.getEyeCoord();
const CoordDouble2 playerCoordXZ(playerCoord.chunk, VoxelDouble2(playerCoord.point.x, playerCoord.point.z));
const bool isPlayerMoving = player.isMoving();
const bool isPlayerWeaponSheathed = player.weaponAnimation.isSheathed();

const WeaponAnimationLibrary &weaponAnimLibrary = WeaponAnimationLibrary::getInstance();
const WeaponAnimationDefinition &weaponAnimDef = weaponAnimLibrary.getDefinition(player.weaponAnimDefID);
const WeaponAnimationInstance &weaponAnimInst = player.weaponAnimInst;
const WeaponAnimationDefinitionState &weaponAnimDefState = weaponAnimDef.states[weaponAnimInst.currentStateIndex];
const bool isPlayerWeaponSheathed = WeaponAnimationUtils::isSheathed(weaponAnimDefState);

for (const ChunkInt2 &chunkPos : activeChunkPositions)
{
Expand Down
2 changes: 2 additions & 0 deletions OpenTESArena/src/Game/Game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "../Items/ItemMaterialLibrary.h"
#include "../Player/PlayerInterface.h"
#include "../Player/PlayerLogicController.h"
#include "../Player/WeaponAnimationLibrary.h"
#include "../Rendering/RenderCamera.h"
#include "../Rendering/Renderer.h"
#include "../Rendering/RendererUtils.h"
Expand Down Expand Up @@ -371,6 +372,7 @@ bool Game::init()
ItemConditionLibrary::getInstance().init(exeData);
ItemMaterialLibrary::getInstance().init(exeData);
ItemLibrary::getInstance().init(exeData);
WeaponAnimationLibrary::getInstance().init(exeData, this->textureManager);
CharacterClassLibrary::getInstance().init(exeData);
CharacterRaceLibrary::getInstance().init(exeData);
EntityDefinitionLibrary::getInstance().init(exeData, this->textureManager);
Expand Down
6 changes: 3 additions & 3 deletions OpenTESArena/src/Game/GameState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -841,10 +841,10 @@ void GameState::tickPlayerMovementTriggers(const CoordDouble3 &oldPlayerCoord, c

void GameState::tickPlayerAttack(double dt, Game &game)
{
auto &player = game.player;
player.weaponAnimation.tick(dt);
Player &player = game.player;
player.weaponAnimInst.update(dt);

const auto &inputManager = game.inputManager;
const InputManager &inputManager = game.inputManager;
const Int2 mouseDelta = inputManager.getMouseDelta();
PlayerLogicController::handlePlayerAttack(game, mouseDelta);
}
Expand Down
102 changes: 58 additions & 44 deletions OpenTESArena/src/Interface/GameWorldPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "../Input/InputActionMapName.h"
#include "../Input/InputActionName.h"
#include "../Player/PlayerLogicController.h"
#include "../Player/WeaponAnimationLibrary.h"
#include "../Rendering/RenderCamera.h"
#include "../Rendering/RenderCommandBuffer.h"
#include "../Rendering/RendererUtils.h"
Expand Down Expand Up @@ -286,37 +287,32 @@ void GameWorldPanel::initUiDrawCalls()
const auto &options = game.options;
const bool modernInterface = options.getGraphics_ModernInterface();

const UiTextureID gameWorldInterfaceTextureID =
GameWorldUiView::allocGameWorldInterfaceTexture(textureManager, renderer);
const UiTextureID gameWorldInterfaceTextureID = GameWorldUiView::allocGameWorldInterfaceTexture(textureManager, renderer);
this->gameWorldInterfaceTextureRef.init(gameWorldInterfaceTextureID, renderer);

constexpr GameWorldUiView::StatusGradientType gradientType = GameWorldUiView::StatusGradientType::Default;
const UiTextureID statusGradientTextureID =
GameWorldUiView::allocStatusGradientTexture(gradientType, textureManager, renderer);
const UiTextureID statusGradientTextureID = GameWorldUiView::allocStatusGradientTexture(gradientType, textureManager, renderer);
this->statusGradientTextureRef.init(statusGradientTextureID, renderer);

const auto &player = game.player;
const UiTextureID playerPortraitTextureID = GameWorldUiView::allocPlayerPortraitTexture(
player.male, player.raceID, player.portraitID, textureManager, renderer);
const UiTextureID playerPortraitTextureID = GameWorldUiView::allocPlayerPortraitTexture(player.male, player.raceID, player.portraitID, textureManager, renderer);
this->playerPortraitTextureRef.init(playerPortraitTextureID, renderer);

const UiTextureID noMagicTextureID = GameWorldUiView::allocNoMagicTexture(textureManager, renderer);
this->noMagicTextureRef.init(noMagicTextureID, renderer);

const auto &weaponAnimation = player.weaponAnimation;
const std::string &weaponFilename = weaponAnimation.getAnimationFilename();
const std::optional<TextureFileMetadataID> weaponAnimMetadataID = textureManager.tryGetMetadataID(weaponFilename.c_str());
if (!weaponAnimMetadataID.has_value())
const WeaponAnimationLibrary &weaponAnimLibrary = WeaponAnimationLibrary::getInstance();
const WeaponAnimationDefinition &weaponAnimDef = weaponAnimLibrary.getDefinition(player.weaponAnimDefID);
this->weaponAnimTextureRefs.init(weaponAnimDef.frameCount);
for (int i = 0; i < weaponAnimDef.frameCount; i++)
{
DebugCrash("Couldn't get texture file metadata ID for weapon animation \"" + weaponFilename + "\".");
}

const TextureFileMetadata &weaponAnimMetadata = textureManager.getMetadataHandle(*weaponAnimMetadataID);
this->weaponAnimTextureRefs.init(weaponAnimMetadata.getTextureCount());
for (int i = 0; i < weaponAnimMetadata.getTextureCount(); i++)
{
const UiTextureID weaponTextureID =
GameWorldUiView::allocWeaponAnimTexture(weaponFilename, i, textureManager, renderer);
const WeaponAnimationDefinitionFrame &weaponAnimDefFrame = weaponAnimDef.frames[i];
const TextureAsset &weaponAnimDefFrameTextureAsset = weaponAnimDefFrame.textureAsset;
const std::string &weaponAnimDefFrameTextureFilename = weaponAnimDefFrameTextureAsset.filename;
DebugAssert(weaponAnimDefFrameTextureAsset.index.has_value());
const int weaponAnimDefFrameTextureIndex = *weaponAnimDefFrameTextureAsset.index;
// @todo: some WeaponAnimationDefinitionFrames are duplicates, this can cause duplicate UiTextureID allocations, maybe map TextureAsset to UiTextureID to avoid it
const UiTextureID weaponTextureID = GameWorldUiView::allocWeaponAnimTexture(weaponAnimDefFrameTextureFilename, weaponAnimDefFrameTextureIndex, textureManager, renderer);
this->weaponAnimTextureRefs.set(i, ScopedUiTextureRef(weaponTextureID, renderer));
}

Expand All @@ -339,24 +335,28 @@ void GameWorldPanel::initUiDrawCalls()
{
UiDrawCall::TextureFunc weaponAnimTextureFunc = [this, &player]()
{
const auto &weaponAnimation = player.weaponAnimation;
const ScopedUiTextureRef &textureRef = this->weaponAnimTextureRefs.get(weaponAnimation.getFrameIndex());
const WeaponAnimationLibrary &weaponAnimLibrary = WeaponAnimationLibrary::getInstance();
const WeaponAnimationDefinition &weaponAnimDef = weaponAnimLibrary.getDefinition(player.weaponAnimDefID);
const WeaponAnimationInstance &weaponAnimInst = player.weaponAnimInst;
const int weaponAnimFrameIndex = WeaponAnimationUtils::getFrameIndex(weaponAnimInst, weaponAnimDef);
const ScopedUiTextureRef &textureRef = this->weaponAnimTextureRefs.get(weaponAnimFrameIndex);
return textureRef.get();
};

UiDrawCall::PositionFunc weaponAnimPositionFunc = [this, &game, &player]()
{
const int classicViewHeight = ArenaRenderUtils::SCREEN_HEIGHT - this->gameWorldInterfaceTextureRef.getHeight();

const auto &weaponAnimation = player.weaponAnimation;
const std::string &weaponFilename = weaponAnimation.getAnimationFilename();
const int weaponAnimIndex = weaponAnimation.getFrameIndex();
const WeaponAnimationLibrary &weaponAnimLibrary = WeaponAnimationLibrary::getInstance();
const WeaponAnimationDefinition &weaponAnimDef = weaponAnimLibrary.getDefinition(player.weaponAnimDefID);
const WeaponAnimationInstance &weaponAnimInst = player.weaponAnimInst;
const int weaponAnimFrameIndex = WeaponAnimationUtils::getFrameIndex(weaponAnimInst, weaponAnimDef);
const WeaponAnimationDefinitionFrame &weaponAnimFrame = weaponAnimDef.frames[weaponAnimFrameIndex];

auto &textureManager = game.textureManager;
const Int2 offset = GameWorldUiView::getWeaponAnimationOffset(weaponFilename, weaponAnimIndex, textureManager);
const Double2 offsetPercents(
static_cast<double>(offset.x) / ArenaRenderUtils::SCREEN_WIDTH_REAL,
static_cast<double>(offset.y) / static_cast<double>(classicViewHeight));
static_cast<double>(weaponAnimFrame.xOffset) / ArenaRenderUtils::SCREEN_WIDTH_REAL,
static_cast<double>(weaponAnimFrame.yOffset) / static_cast<double>(classicViewHeight));

const auto &renderer = game.renderer;
const Int2 windowDims = renderer.getWindowDimensions();
Expand All @@ -370,8 +370,11 @@ void GameWorldPanel::initUiDrawCalls()
{
const int classicViewHeight = ArenaRenderUtils::SCREEN_HEIGHT - this->gameWorldInterfaceTextureRef.getHeight();

const auto &weaponAnimation = player.weaponAnimation;
const ScopedUiTextureRef &textureRef = this->weaponAnimTextureRefs.get(weaponAnimation.getFrameIndex());
const WeaponAnimationLibrary &weaponAnimLibrary = WeaponAnimationLibrary::getInstance();
const WeaponAnimationDefinition &weaponAnimDef = weaponAnimLibrary.getDefinition(player.weaponAnimDefID);
const WeaponAnimationInstance &weaponAnimInst = player.weaponAnimInst;
const int weaponAnimFrameIndex = WeaponAnimationUtils::getFrameIndex(weaponAnimInst, weaponAnimDef);
const ScopedUiTextureRef &textureRef = this->weaponAnimTextureRefs.get(weaponAnimFrameIndex);
const Int2 textureDims(textureRef.getWidth(), textureRef.getHeight());
const Double2 texturePercents(
static_cast<double>(textureDims.x) / ArenaRenderUtils::SCREEN_WIDTH_REAL,
Expand All @@ -389,8 +392,11 @@ void GameWorldPanel::initUiDrawCalls()

UiDrawCall::ActiveFunc weaponAnimActiveFunc = [this, &player]()
{
const auto &weaponAnimation = player.weaponAnimation;
return !this->isPaused() && !weaponAnimation.isSheathed();
const WeaponAnimationLibrary &weaponAnimLibrary = WeaponAnimationLibrary::getInstance();
const WeaponAnimationDefinition &weaponAnimDef = weaponAnimLibrary.getDefinition(player.weaponAnimDefID);
const WeaponAnimationInstance &weaponAnimInst = player.weaponAnimInst;
const WeaponAnimationDefinitionState &weaponAnimDefState = weaponAnimDef.states[weaponAnimInst.currentStateIndex];
return !this->isPaused() && !WeaponAnimationUtils::isSheathed(weaponAnimDefState);
};

this->addDrawCall(
Expand Down Expand Up @@ -490,35 +496,43 @@ void GameWorldPanel::initUiDrawCalls()
{
UiDrawCall::TextureFunc weaponAnimTextureFunc = [this, &player]()
{
const auto &weaponAnimation = player.weaponAnimation;
const ScopedUiTextureRef &textureRef = this->weaponAnimTextureRefs.get(weaponAnimation.getFrameIndex());
const WeaponAnimationLibrary &weaponAnimLibrary = WeaponAnimationLibrary::getInstance();
const WeaponAnimationDefinition &weaponAnimDef = weaponAnimLibrary.getDefinition(player.weaponAnimDefID);
const WeaponAnimationInstance &weaponAnimInst = player.weaponAnimInst;
const int weaponAnimFrameIndex = WeaponAnimationUtils::getFrameIndex(weaponAnimInst, weaponAnimDef);
const ScopedUiTextureRef &textureRef = this->weaponAnimTextureRefs.get(weaponAnimFrameIndex);
return textureRef.get();
};

UiDrawCall::PositionFunc weaponAnimPositionFunc = [this, &game, &player]()
{
const auto &weaponAnimation = player.weaponAnimation;
const std::string &weaponFilename = weaponAnimation.getAnimationFilename();
const int weaponAnimIndex = weaponAnimation.getFrameIndex();

auto &textureManager = game.textureManager;
const Int2 offset = GameWorldUiView::getWeaponAnimationOffset(weaponFilename, weaponAnimIndex, textureManager);
return offset;
const WeaponAnimationLibrary &weaponAnimLibrary = WeaponAnimationLibrary::getInstance();
const WeaponAnimationDefinition &weaponAnimDef = weaponAnimLibrary.getDefinition(player.weaponAnimDefID);
const WeaponAnimationInstance &weaponAnimInst = player.weaponAnimInst;
const int weaponAnimFrameIndex = WeaponAnimationUtils::getFrameIndex(weaponAnimInst, weaponAnimDef);
const WeaponAnimationDefinitionFrame &weaponAnimFrame = weaponAnimDef.frames[weaponAnimFrameIndex];
return Int2(weaponAnimFrame.xOffset, weaponAnimFrame.yOffset);
};

UiDrawCall::SizeFunc weaponAnimSizeFunc = [this, &player]()
{
const auto &weaponAnimation = player.weaponAnimation;
const ScopedUiTextureRef &textureRef = this->weaponAnimTextureRefs.get(weaponAnimation.getFrameIndex());
const WeaponAnimationLibrary &weaponAnimLibrary = WeaponAnimationLibrary::getInstance();
const WeaponAnimationDefinition &weaponAnimDef = weaponAnimLibrary.getDefinition(player.weaponAnimDefID);
const WeaponAnimationInstance &weaponAnimInst = player.weaponAnimInst;
const int weaponAnimFrameIndex = WeaponAnimationUtils::getFrameIndex(weaponAnimInst, weaponAnimDef);
const ScopedUiTextureRef &textureRef = this->weaponAnimTextureRefs.get(weaponAnimFrameIndex);
return Int2(textureRef.getWidth(), textureRef.getHeight());
};

UiDrawCall::PivotFunc weaponAnimPivotFunc = []() { return PivotType::TopLeft; };

UiDrawCall::ActiveFunc weaponAnimActiveFunc = [this, &player]()
{
const auto &weaponAnimation = player.weaponAnimation;
return !this->isPaused() && !weaponAnimation.isSheathed();
const WeaponAnimationLibrary &weaponAnimLibrary = WeaponAnimationLibrary::getInstance();
const WeaponAnimationDefinition &weaponAnimDef = weaponAnimLibrary.getDefinition(player.weaponAnimDefID);
const WeaponAnimationInstance &weaponAnimInst = player.weaponAnimInst;
const WeaponAnimationDefinitionState &weaponAnimDefState = weaponAnimDef.states[weaponAnimInst.currentStateIndex];
return !this->isPaused() && !WeaponAnimationUtils::isSheathed(weaponAnimDefState);
};

this->addDrawCall(
Expand Down
26 changes: 19 additions & 7 deletions OpenTESArena/src/Interface/GameWorldUiController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "../Game/Game.h"
#include "../Player/Player.h"
#include "../Player/PlayerLogicController.h"
#include "../Player/WeaponAnimationLibrary.h"
#include "../Time/ArenaClockUtils.h"
#include "../Time/ArenaDateUtils.h"
#include "../UI/FontLibrary.h"
Expand Down Expand Up @@ -102,17 +103,28 @@ void GameWorldUiController::onCharacterSheetButtonSelected(Game &game)

void GameWorldUiController::onWeaponButtonSelected(Player &player)
{
WeaponAnimation &weaponAnimation = player.weaponAnimation;
WeaponAnimationInstance &weaponAnimInst = player.weaponAnimInst;
const WeaponAnimationLibrary &weaponAnimLibrary = WeaponAnimationLibrary::getInstance();
const WeaponAnimationDefinition &weaponAnimDef = weaponAnimLibrary.getDefinition(player.weaponAnimDefID);
const WeaponAnimationDefinitionState &weaponAnimDefState = weaponAnimDef.states[weaponAnimInst.currentStateIndex];

if (weaponAnimation.isSheathed())
int newStateIndex = -1;
int nextStateIndex = -1;
if (WeaponAnimationUtils::isSheathed(weaponAnimDefState))
{
// Begin unsheathing the weapon.
weaponAnimation.setState(WeaponAnimation::State::Unsheathing);
weaponAnimDef.tryGetStateIndex(WeaponAnimationUtils::STATE_UNSHEATHING.c_str(), &newStateIndex);
weaponAnimDef.tryGetStateIndex(WeaponAnimationUtils::STATE_IDLE.c_str(), &nextStateIndex);
}
else if (weaponAnimation.isIdle())
else if (WeaponAnimationUtils::isIdle(weaponAnimDefState))
{
// Begin sheathing the weapon.
weaponAnimation.setState(WeaponAnimation::State::Sheathing);
weaponAnimDef.tryGetStateIndex(WeaponAnimationUtils::STATE_SHEATHING.c_str(), &newStateIndex);
weaponAnimDef.tryGetStateIndex(WeaponAnimationUtils::STATE_SHEATHED.c_str(), &nextStateIndex);
}

if (newStateIndex >= 0)
{
weaponAnimInst.setStateIndex(newStateIndex);
weaponAnimInst.setNextStateIndex(nextStateIndex);
}
}

Expand Down
3 changes: 3 additions & 0 deletions OpenTESArena/src/Interface/GameWorldUiView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ Int2 GameWorldUiView::getCompassFramePosition()
Int2 GameWorldUiView::getWeaponAnimationOffset(const std::string &weaponFilename, int frameIndex,
TextureManager &textureManager)
{
// @todo: this is obsoleted by WeaponAnimationDefinition

const std::optional<TextureFileMetadataID> metadataID = textureManager.tryGetMetadataID(weaponFilename.c_str());
if (!metadataID.has_value())
{
Expand Down Expand Up @@ -362,6 +364,7 @@ TextureAsset GameWorldUiView::getNoMagicTextureAsset()

TextureAsset GameWorldUiView::getWeaponAnimTextureAsset(const std::string &weaponFilename, int index)
{
// @todo: this is obsoleted by WeaponAnimationDefinition
return TextureAsset(std::string(weaponFilename), index);
}

Expand Down
10 changes: 10 additions & 0 deletions OpenTESArena/src/Items/ArenaItemUtils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <algorithm>

#include "ArenaItemUtils.h"

bool ArenaItemUtils::isRangedWeapon(int weaponID)
{
const auto rangedWeaponsBegin = std::begin(ArenaItemUtils::RangedWeaponIDs);
const auto rangedWeaponsEnd = std::end(ArenaItemUtils::RangedWeaponIDs);
return std::find(rangedWeaponsBegin, rangedWeaponsEnd, weaponID) != rangedWeaponsEnd;
}
4 changes: 4 additions & 0 deletions OpenTESArena/src/Items/ArenaItemUtils.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
#ifndef ARENA_ITEM_UTILS_H
#define ARENA_ITEM_UTILS_H

#include <algorithm>

namespace ArenaItemUtils
{
// Converts Arena weight units to kilograms.
constexpr double KilogramsDivisor = 256.0;

constexpr int FistsWeaponID = -1;
constexpr int RangedWeaponIDs[] = { 16, 17 };

bool isRangedWeapon(int weaponID);
}

#endif
Loading

0 comments on commit 84b216a

Please sign in to comment.