Skip to content

Commit

Permalink
Start loading VFX animations.
Browse files Browse the repository at this point in the history
  • Loading branch information
afritz1 committed Dec 31, 2024
1 parent b83f583 commit 2f8b593
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 1 deletion.
35 changes: 35 additions & 0 deletions OpenTESArena/src/Assets/ArenaAnimUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ namespace ArenaAnimUtils
MakeHumanKeyframeDimensions(width, height, outWidth, outHeight);
}

void MakeVfxKeyframeDimensions(int width, int height, double *outWidth, double *outHeight)
{
MakeHumanKeyframeDimensions(width, height, outWidth, outHeight);
}

int GetCitizenAnimationFilenameIndex(bool isMale, ArenaTypes::ClimateType climateType)
{
if (isMale)
Expand Down Expand Up @@ -1076,6 +1081,36 @@ bool ArenaAnimUtils::tryMakeCitizenAnims(ArenaTypes::ClimateType climateType, bo
return true;
}

bool ArenaAnimUtils::tryMakeVfxAnim(const std::string &animFilename, bool isLooping, TextureManager &textureManager, EntityAnimationDefinition *outAnimDef)
{
DebugAssert(outAnimDef->stateCount == 0);

const std::optional<TextureFileMetadataID> metadataID = textureManager.tryGetMetadataID(animFilename.c_str());
if (!metadataID.has_value())
{
DebugLogWarning("Couldn't get VFX anim texture file metadata for \"" + animFilename + "\".");
return false;
}

const TextureFileMetadata &textureFileMetadata = textureManager.getMetadataHandle(*metadataID);
const int keyframeCount = textureFileMetadata.getTextureCount();
const double stateSeconds = static_cast<double>(keyframeCount) * VfxIdleSecondsPerFrame;
const int stateIndex = outAnimDef->addState(EntityAnimationUtils::STATE_IDLE.c_str(), stateSeconds, isLooping);

constexpr bool isMirrored = false;
const int keyframeListIndex = outAnimDef->addKeyframeList(stateIndex, isMirrored);
for (int i = 0; i < keyframeCount; i++)
{
double width, height;
MakeVfxKeyframeDimensions(textureFileMetadata.getWidth(i), textureFileMetadata.getHeight(i), &width, &height);

TextureAsset textureAsset(std::string(textureFileMetadata.getFilename()), i);
outAnimDef->addKeyframe(keyframeListIndex, std::move(textureAsset), width, height);
}

return true;
}

PaletteIndices ArenaAnimUtils::transformCitizenColors(int raceIndex, uint16_t seed, const ExeData &exeData)
{
PaletteIndices newPaletteIndices;
Expand Down
6 changes: 6 additions & 0 deletions OpenTESArena/src/Assets/ArenaAnimUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ namespace ArenaAnimUtils
constexpr int CitizenIdleIndices[] = { 6, 7, 8 };
constexpr int CitizenWalkIndices[] = { 0, 1, 2, 3, 4, 5 };

// Animation values for VFX like spells and melee strikes.
constexpr double VfxIdleSecondsPerFrame = 1.0 / 12.0;

// The final boss is sort of a special case. Their *ITEM index is at the very end of
// human enemies, but they are treated like a creature.
bool isFinalBossIndex(ArenaTypes::ItemIndex itemIndex);
Expand Down Expand Up @@ -170,6 +173,9 @@ namespace ArenaAnimUtils
bool tryMakeCitizenAnims(ArenaTypes::ClimateType climateType, bool isMale, const ExeData &exeData,
TextureManager &textureManager, EntityAnimationDefinition *outAnimDef);

// Writes out animation for spell projectile, explosion, or melee VFX.
bool tryMakeVfxAnim(const std::string &animFilename, bool isLooping, TextureManager &textureManager, EntityAnimationDefinition *outAnimDef);

// Transforms the palette indices used for a citizen's clothes and skin. The given seed value is
// "pure random" and can essentially be anything.
PaletteIndices transformCitizenColors(int raceIndex, uint16_t seed, const ExeData &exeData);
Expand Down
113 changes: 112 additions & 1 deletion OpenTESArena/src/Entities/EntityAnimationLibrary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "../World/ArenaClimateUtils.h"

#include "components/debug/Debug.h"
#include "components/utilities/String.h"

CreatureEntityAnimationKey::CreatureEntityAnimationKey()
{
Expand Down Expand Up @@ -43,6 +44,29 @@ void CitizenEntityAnimationKey::init(bool male, ArenaTypes::ClimateType climateT
this->climateType = climateType;
}

VfxEntityAnimationKey::VfxEntityAnimationKey()
{
this->type = static_cast<VfxEntityAnimationType>(-1);
}

void VfxEntityAnimationKey::initSpellProjectile(int spellIndex)
{
this->type = VfxEntityAnimationType::SpellProjectile;
this->spellIndex = spellIndex;
}

void VfxEntityAnimationKey::initSpellExplosion(int spellIndex)
{
this->type = VfxEntityAnimationType::SpellExplosion;
this->spellIndex = spellIndex;
}

void VfxEntityAnimationKey::initMeleeStrike(int bloodIndex)
{
this->type = VfxEntityAnimationType::MeleeStrike;
this->bloodIndex = bloodIndex;
}

void EntityAnimationLibrary::init(const BinaryAssetLibrary &binaryAssetLibrary, const CharacterClassLibrary &charClassLibrary, TextureManager &textureManager)
{
const ExeData &exeData = binaryAssetLibrary.getExeData();
Expand Down Expand Up @@ -127,8 +151,68 @@ void EntityAnimationLibrary::init(const BinaryAssetLibrary &binaryAssetLibrary,
this->citizenDefIDs.emplace_back(std::move(femaleAnimKey), femaleAnimDefID);
}

// @todo: explosion animations
// VFX
const int spellTypeCount = 12;
const int meleeVfxCount = 3;
const int spellProjectileStartIndex = spellTypeCount;
const int spellExplosionStartIndex = 0;
const int meleeVfxStartIndex = spellProjectileStartIndex + spellTypeCount;
const BufferView<const std::string> spellProjectileAnimFilenames(exeData.entities.effectAnimations.data() + spellProjectileStartIndex, spellTypeCount);
const BufferView<const std::string> spellExplosionAnimFilenames(exeData.entities.effectAnimations.data() + spellExplosionStartIndex, spellTypeCount);
const BufferView<const std::string> meleeVfxAnimFilenames(exeData.entities.effectAnimations.data() + meleeVfxStartIndex, meleeVfxCount); // Blood, demon, undead
for (int i = 0; i < spellProjectileAnimFilenames.getCount(); i++)
{
const std::string animFilename = String::toUppercase(spellProjectileAnimFilenames[i]);
EntityAnimationDefinition animDef;
if (!ArenaAnimUtils::tryMakeVfxAnim(animFilename, true, textureManager, &animDef))
{
DebugLogError("Couldn't create VFX animation definition for spell projectile \"" + animFilename + "\".");
continue;
}

const EntityAnimationDefinitionID animDefID = static_cast<EntityAnimationDefinitionID>(this->defs.size());
this->defs.emplace_back(std::move(animDef));

VfxEntityAnimationKey animKey;
animKey.initSpellProjectile(i);
this->vfxDefIDs.emplace_back(std::move(animKey), animDefID);
}

for (int i = 0; i < spellExplosionAnimFilenames.getCount(); i++)
{
const std::string animFilename = String::toUppercase(spellExplosionAnimFilenames[i]);
EntityAnimationDefinition animDef;
if (!ArenaAnimUtils::tryMakeVfxAnim(animFilename, false, textureManager, &animDef))
{
DebugLogError("Couldn't create VFX animation definition for spell explosion \"" + animFilename + "\".");
continue;
}

const EntityAnimationDefinitionID animDefID = static_cast<EntityAnimationDefinitionID>(this->defs.size());
this->defs.emplace_back(std::move(animDef));

VfxEntityAnimationKey animKey;
animKey.initSpellExplosion(i);
this->vfxDefIDs.emplace_back(std::move(animKey), animDefID);
}

for (int i = 0; i < meleeVfxAnimFilenames.getCount(); i++)
{
const std::string animFilename = String::toUppercase(meleeVfxAnimFilenames[i]);
EntityAnimationDefinition animDef;
if (!ArenaAnimUtils::tryMakeVfxAnim(animFilename, false, textureManager, &animDef))
{
DebugLogError("Couldn't create VFX animation definition for melee strike \"" + animFilename + "\".");
continue;
}

const EntityAnimationDefinitionID animDefID = static_cast<EntityAnimationDefinitionID>(this->defs.size());
this->defs.emplace_back(std::move(animDef));

VfxEntityAnimationKey animKey;
animKey.initMeleeStrike(i);
this->vfxDefIDs.emplace_back(std::move(animKey), animDefID);
}
}

int EntityAnimationLibrary::getDefinitionCount() const
Expand Down Expand Up @@ -175,6 +259,33 @@ EntityAnimationDefinitionID EntityAnimationLibrary::getCitizenAnimDefID(const Ci
return iter->second;
}

EntityAnimationDefinitionID EntityAnimationLibrary::getVfxAnimDefID(const VfxEntityAnimationKey &key) const
{
const auto iter = std::find_if(this->vfxDefIDs.begin(), this->vfxDefIDs.end(),
[&key](const auto &pair)
{
const VfxEntityAnimationKey &animKey = pair.first;
if (animKey.type != key.type)
{
return false;
}

switch (animKey.type)
{
case VfxEntityAnimationType::SpellProjectile:
case VfxEntityAnimationType::SpellExplosion:
return animKey.spellIndex == key.spellIndex;
case VfxEntityAnimationType::MeleeStrike:
return animKey.bloodIndex == key.bloodIndex;
default:
DebugUnhandledReturnMsg(bool, std::to_string(static_cast<int>(animKey.type)));
}
});

DebugAssert(iter != this->vfxDefIDs.end());
return iter->second;
}

const EntityAnimationDefinition &EntityAnimationLibrary::getDefinition(EntityAnimationDefinitionID id) const
{
DebugAssertIndex(this->defs, id);
Expand Down
26 changes: 26 additions & 0 deletions OpenTESArena/src/Entities/EntityAnimationLibrary.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,30 @@ struct CitizenEntityAnimationKey
void init(bool male, ArenaTypes::ClimateType climateType);
};

enum class VfxEntityAnimationType
{
SpellProjectile,
SpellExplosion,
MeleeStrike
};

struct VfxEntityAnimationKey
{
VfxEntityAnimationType type;

union
{
int spellIndex; // 0-11
int bloodIndex; // 0-2
};

VfxEntityAnimationKey();

void initSpellProjectile(int spellIndex);
void initSpellExplosion(int spellIndex);
void initMeleeStrike(int bloodIndex);
};

using EntityAnimationDefinitionID = int;

class EntityAnimationLibrary : public Singleton<EntityAnimationLibrary>
Expand All @@ -52,13 +76,15 @@ class EntityAnimationLibrary : public Singleton<EntityAnimationLibrary>
std::vector<std::pair<CreatureEntityAnimationKey, EntityAnimationDefinitionID>> creatureDefIDs;
std::vector<std::pair<HumanEnemyEntityAnimationKey, EntityAnimationDefinitionID>> humanEnemyDefIDs;
std::vector<std::pair<CitizenEntityAnimationKey, EntityAnimationDefinitionID>> citizenDefIDs;
std::vector<std::pair<VfxEntityAnimationKey, EntityAnimationDefinitionID>> vfxDefIDs;
public:
void init(const BinaryAssetLibrary &binaryAssetLibrary, const CharacterClassLibrary &charClassLibrary, TextureManager &textureManager);

int getDefinitionCount() const;
EntityAnimationDefinitionID getCreatureAnimDefID(const CreatureEntityAnimationKey &key) const;
EntityAnimationDefinitionID getHumanEnemyAnimDefID(const HumanEnemyEntityAnimationKey &key) const;
EntityAnimationDefinitionID getCitizenAnimDefID(const CitizenEntityAnimationKey &key) const;
EntityAnimationDefinitionID getVfxAnimDefID(const VfxEntityAnimationKey &key) const;
const EntityAnimationDefinition &getDefinition(EntityAnimationDefinitionID id) const;
};

Expand Down

0 comments on commit 2f8b593

Please sign in to comment.