From 1b4bca7b4bc6c41158346531d2975a931ae92af5 Mon Sep 17 00:00:00 2001 From: Aaron Date: Sun, 5 Jan 2025 17:18:38 -0800 Subject: [PATCH] Use initial state index instead of state name. --- OpenTESArena/src/Assets/ArenaAnimUtils.cpp | 7 ++++ .../Entities/EntityAnimationDefinition.cpp | 12 +++++++ .../src/Entities/EntityAnimationDefinition.h | 4 +++ .../src/Entities/EntityChunkManager.cpp | 33 ++++++++++++------- .../src/Entities/EntityGeneration.cpp | 12 ------- OpenTESArena/src/Entities/EntityGeneration.h | 2 -- OpenTESArena/src/World/MapGeneration.cpp | 19 ++--------- 7 files changed, 46 insertions(+), 43 deletions(-) diff --git a/OpenTESArena/src/Assets/ArenaAnimUtils.cpp b/OpenTESArena/src/Assets/ArenaAnimUtils.cpp index a4b552d1b..a519b5c81 100644 --- a/OpenTESArena/src/Assets/ArenaAnimUtils.cpp +++ b/OpenTESArena/src/Assets/ArenaAnimUtils.cpp @@ -892,6 +892,7 @@ bool ArenaAnimUtils::tryMakeStaticEntityAnims(ArenaTypes::FlatIndex flatIndex, M const INFFile &inf, TextureManager &textureManager, EntityAnimationDefinition *outAnimDef) { DebugAssert(outAnimDef != nullptr); + outAnimDef->init(EntityAnimationUtils::STATE_IDLE.c_str()); // Generate animation states based on what the entity needs. The animations to load depend on // the flat index. The wilderness does not have any streetlights (there is no ID for them). @@ -944,6 +945,8 @@ bool ArenaAnimUtils::tryMakeStaticEntityAnims(ArenaTypes::FlatIndex flatIndex, M bool ArenaAnimUtils::tryMakeDynamicEntityCreatureAnims(int creatureID, const ExeData &exeData, TextureManager &textureManager, EntityAnimationDefinition *outAnimDef) { + outAnimDef->init(EntityAnimationUtils::STATE_IDLE.c_str()); + // Basic states are idle/look/walk. if (!ArenaAnimUtils::tryAddDynamicEntityCreatureBasicAnimState(creatureID, EntityAnimationUtils::STATE_IDLE.c_str(), CreatureIdleSecondsPerFrame, CreatureIdleLoop, CreatureIdleIndices, exeData, textureManager, outAnimDef)) @@ -986,6 +989,8 @@ bool ArenaAnimUtils::tryMakeDynamicEntityCreatureAnims(int creatureID, const Exe bool ArenaAnimUtils::tryMakeDynamicEntityHumanAnims(int charClassIndex, bool isMale, const CharacterClassLibrary &charClassLibrary, const BinaryAssetLibrary &binaryAssetLibrary, TextureManager &textureManager, EntityAnimationDefinition *outAnimDef) { + outAnimDef->init(EntityAnimationUtils::STATE_IDLE.c_str()); + // Basic states are idle and walk. Human enemies don't have look animations. if (!ArenaAnimUtils::tryAddDynamicEntityHumanBasicAnimState(charClassIndex, isMale, EntityAnimationUtils::STATE_IDLE.c_str(), HumanIdleSecondsPerFrame, HumanIdleLoop, HumanIdleIndices, charClassLibrary, binaryAssetLibrary, textureManager, outAnimDef)) @@ -1061,6 +1066,7 @@ bool ArenaAnimUtils::tryMakeDynamicEntityAnims(ArenaTypes::FlatIndex flatIndex, bool ArenaAnimUtils::tryMakeCitizenAnims(ArenaTypes::ClimateType climateType, bool isMale, const ExeData &exeData, TextureManager &textureManager, EntityAnimationDefinition *outAnimDef) { + outAnimDef->init(EntityAnimationUtils::STATE_IDLE.c_str()); const int animFilenameIndex = GetCitizenAnimationFilenameIndex(isMale, climateType); if (!ArenaAnimUtils::tryAddDynamicEntityCitizenBasicAnimState(EntityAnimationUtils::STATE_IDLE.c_str(), @@ -1084,6 +1090,7 @@ bool ArenaAnimUtils::tryMakeCitizenAnims(ArenaTypes::ClimateType climateType, bo bool ArenaAnimUtils::tryMakeVfxAnim(const std::string &animFilename, bool isLooping, TextureManager &textureManager, EntityAnimationDefinition *outAnimDef) { DebugAssert(outAnimDef->stateCount == 0); + outAnimDef->init(EntityAnimationUtils::STATE_IDLE.c_str()); const std::optional metadataID = textureManager.tryGetMetadataID(animFilename.c_str()); if (!metadataID.has_value()) diff --git a/OpenTESArena/src/Entities/EntityAnimationDefinition.cpp b/OpenTESArena/src/Entities/EntityAnimationDefinition.cpp index 61e3ed501..6346295c8 100644 --- a/OpenTESArena/src/Entities/EntityAnimationDefinition.cpp +++ b/OpenTESArena/src/Entities/EntityAnimationDefinition.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -112,6 +113,12 @@ EntityAnimationDefinition::EntityAnimationDefinition() this->stateCount = 0; this->keyframeListCount = 0; this->keyframeCount = 0; + std::fill(std::begin(this->initialStateName), std::end(this->initialStateName), '\0'); +} + +void EntityAnimationDefinition::init(const char *initialStateName) +{ + std::snprintf(this->initialStateName, std::size(this->initialStateName), "%s", initialStateName); } bool EntityAnimationDefinition::operator==(const EntityAnimationDefinition &other) const @@ -166,6 +173,11 @@ bool EntityAnimationDefinition::operator==(const EntityAnimationDefinition &othe } } + if (!StringView::equals(this->initialStateName, other.initialStateName)) + { + return false; + } + return true; } diff --git a/OpenTESArena/src/Entities/EntityAnimationDefinition.h b/OpenTESArena/src/Entities/EntityAnimationDefinition.h index 3b50338e3..bd6897834 100644 --- a/OpenTESArena/src/Entities/EntityAnimationDefinition.h +++ b/OpenTESArena/src/Entities/EntityAnimationDefinition.h @@ -52,8 +52,12 @@ struct EntityAnimationDefinition EntityAnimationDefinitionKeyframe keyframes[MAX_KEYFRAMES]; int keyframeCount; + char initialStateName[EntityAnimationUtils::NAME_LENGTH]; + EntityAnimationDefinition(); + void init(const char *initialStateName); + bool operator==(const EntityAnimationDefinition &other) const; bool operator!=(const EntityAnimationDefinition &other) const; diff --git a/OpenTESArena/src/Entities/EntityChunkManager.cpp b/OpenTESArena/src/Entities/EntityChunkManager.cpp index bf360d26a..6a9822361 100644 --- a/OpenTESArena/src/Entities/EntityChunkManager.cpp +++ b/OpenTESArena/src/Entities/EntityChunkManager.cpp @@ -37,7 +37,7 @@ namespace VoxelDouble2 point; WorldDouble3 bboxMin, bboxMax; // Centered on the entity in model space double animMaxHeight; - char defaultAnimStateName[EntityAnimationUtils::NAME_LENGTH]; + char initialAnimStateIndex; bool isSensorCollider; std::optional direction; std::optional citizenDirectionIndex; @@ -50,7 +50,7 @@ namespace { this->defID = -1; this->animMaxHeight = 0.0; - std::fill(std::begin(this->defaultAnimStateName), std::end(this->defaultAnimStateName), '\0'); + this->initialAnimStateIndex = -1; this->isSensorCollider = false; this->hasInventory = false; this->hasCreatureSound = false; @@ -227,10 +227,8 @@ void EntityChunkManager::populateChunkEntities(EntityChunk &entityChunk, const V const EntityAnimationDefinitionState &animDefState = animDef.states[animDefStateIndex]; animInst.addState(animDefState.seconds, animDefState.isLooping); } - - const std::optional defaultAnimStateIndex = animDef.tryGetStateIndex(initInfo.defaultAnimStateName); - DebugAssert(defaultAnimStateIndex.has_value()); - animInst.setStateIndex(*defaultAnimStateIndex); + + animInst.setStateIndex(initInfo.initialAnimStateIndex); if (!TryCreatePhysicsCollider(entityCoord, initInfo.animMaxHeight, ceilingScale, initInfo.isSensorCollider, physicsSystem, &entityInst.physicsBodyID)) { @@ -306,7 +304,15 @@ void EntityChunkManager::populateChunkEntities(EntityChunk &entityChunk, const V const EntityDefinitionType entityDefType = entityDef.type; const bool isDynamicEntity = EntityUtils::isDynamicEntity(entityDefType); const EntityAnimationDefinition &animDef = entityDef.animDef; - const std::string &defaultAnimStateName = EntityGeneration::getDefaultAnimationStateName(entityDef, entityGenInfo); + + const char *initialAnimStateName = animDef.initialStateName; + if (EntityUtils::isStreetlight(entityDef) && entityGenInfo.nightLightsAreActive) + { + initialAnimStateName = EntityAnimationUtils::STATE_ACTIVATED.c_str(); + } + + const std::optional initialAnimStateIndex = animDef.tryGetStateIndex(initialAnimStateName); + DebugAssert(initialAnimStateIndex.has_value()); std::optional entityDefID; // Global entity def ID (shared across all active chunks). for (const WorldDouble3 &position : placementDef.positions) @@ -337,8 +343,7 @@ void EntityChunkManager::populateChunkEntities(EntityChunk &entityChunk, const V initInfo.bboxMax = WorldDouble3(halfAnimMaxWidth, animMaxHeight, halfAnimMaxWidth); initInfo.animMaxHeight = animMaxHeight; - std::snprintf(initInfo.defaultAnimStateName, std::size(initInfo.defaultAnimStateName), "%s", defaultAnimStateName.c_str()); - + initInfo.initialAnimStateIndex = *initialAnimStateIndex; initInfo.isSensorCollider = !EntityUtils::hasCollision(entityDef); if (isDynamicEntity) @@ -391,7 +396,12 @@ void EntityChunkManager::populateChunkEntities(EntityChunk &entityChunk, const V DebugAssertIndex(citizenCountsToSpawn, citizenGenderIndex); const int citizensToSpawn = citizenCountsToSpawn[citizenGenderIndex]; const EntityDefID citizenEntityDefID = citizenDefIDs[citizenGenderIndex]; - const EntityDefinition &citizenDef = *citizenDefs[citizenGenderIndex]; + const EntityDefinition &citizenDef = *citizenDefs[citizenGenderIndex]; + const EntityAnimationDefinition &citizenAnimDef = citizenDef.animDef; + + const std::optional initialCitizenAnimStateIndex = citizenAnimDef.tryGetStateIndex(citizenAnimDef.initialStateName); + DebugAssert(initialCitizenAnimStateIndex.has_value()); + for (int i = 0; i < citizensToSpawn; i++) { const std::optional citizenSpawnVoxel = tryMakeCitizenSpawnVoxel(); @@ -413,8 +423,7 @@ void EntityChunkManager::populateChunkEntities(EntityChunk &entityChunk, const V citizenInitInfo.bboxMax = WorldDouble3(halfAnimMaxWidth, animMaxHeight, halfAnimMaxWidth); citizenInitInfo.animMaxHeight = animMaxHeight; - std::snprintf(citizenInitInfo.defaultAnimStateName, std::size(citizenInitInfo.defaultAnimStateName), "%s", EntityAnimationUtils::STATE_IDLE.c_str()); - + citizenInitInfo.initialAnimStateIndex = *initialCitizenAnimStateIndex; citizenInitInfo.isSensorCollider = true; citizenInitInfo.citizenDirectionIndex = CitizenUtils::getRandomCitizenDirectionIndex(random); citizenInitInfo.direction = CitizenUtils::getCitizenDirectionByIndex(*citizenInitInfo.citizenDirectionIndex); diff --git a/OpenTESArena/src/Entities/EntityGeneration.cpp b/OpenTESArena/src/Entities/EntityGeneration.cpp index c3531e82e..e4caab4a1 100644 --- a/OpenTESArena/src/Entities/EntityGeneration.cpp +++ b/OpenTESArena/src/Entities/EntityGeneration.cpp @@ -4,15 +4,3 @@ void EntityGeneration::EntityGenInfo::init(bool nightLightsAreActive) { this->nightLightsAreActive = nightLightsAreActive; } - -const std::string &EntityGeneration::getDefaultAnimationStateName(const EntityDefinition &entityDef, const EntityGenInfo &genInfo) -{ - if (!EntityUtils::isStreetlight(entityDef)) - { - return EntityAnimationUtils::STATE_IDLE; - } - else - { - return genInfo.nightLightsAreActive ? EntityAnimationUtils::STATE_ACTIVATED : EntityAnimationUtils::STATE_IDLE; - } -} diff --git a/OpenTESArena/src/Entities/EntityGeneration.h b/OpenTESArena/src/Entities/EntityGeneration.h index a4a954d4b..dea137806 100644 --- a/OpenTESArena/src/Entities/EntityGeneration.h +++ b/OpenTESArena/src/Entities/EntityGeneration.h @@ -20,8 +20,6 @@ namespace EntityGeneration void init(bool nightLightsAreActive); }; - - const std::string &getDefaultAnimationStateName(const EntityDefinition &entityDef, const EntityGenInfo &genInfo); } #endif diff --git a/OpenTESArena/src/World/MapGeneration.cpp b/OpenTESArena/src/World/MapGeneration.cpp index 4eac7de58..9c0f78ffe 100644 --- a/OpenTESArena/src/World/MapGeneration.cpp +++ b/OpenTESArena/src/World/MapGeneration.cpp @@ -201,35 +201,20 @@ namespace MapGeneration DebugLogWarning("Couldn't make static entity anims for flat \"" + std::to_string(flatIndex) + "\"."); return false; } - - // The entity can only be instantiated if there is at least an idle animation. - const std::optional idleStateIndex = entityAnimDef.tryGetStateIndex(EntityAnimationUtils::STATE_IDLE.c_str()); - if (!idleStateIndex.has_value()) - { - DebugLogWarning("Missing static entity idle anim state for flat \"" + std::to_string(flatIndex) + "\"."); - return false; - } } else { // Assume that human enemies in level data are male. const std::optional isMale = true; - if (!ArenaAnimUtils::tryMakeDynamicEntityAnims(flatIndex, isMale, inf, charClassLibrary, binaryAssetLibrary, textureManager, &entityAnimDef)) { DebugLogWarning("Couldn't make dynamic entity anims for flat \"" + std::to_string(flatIndex) + "\"."); return false; } - - // Must have at least an idle animation. - const std::optional idleStateIndex = entityAnimDef.tryGetStateIndex(EntityAnimationUtils::STATE_IDLE.c_str()); - if (!idleStateIndex.has_value()) - { - DebugLogWarning("Missing dynamic entity idle anim state for flat \"" + std::to_string(flatIndex) + "\"."); - return false; - } } + DebugAssert(!String::isNullOrEmpty(entityAnimDef.initialStateName)); + // @todo: replace isCreature/etc. with some flatIndex -> EntityDefinition::Type function. // - Most likely also need location/interior type, etc. because flatIndex is level-dependent. if (isCreature)