Skip to content

Commit

Permalink
Use initial state index instead of state name.
Browse files Browse the repository at this point in the history
  • Loading branch information
afritz1 committed Jan 6, 2025
1 parent 07b5260 commit 1b4bca7
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 43 deletions.
7 changes: 7 additions & 0 deletions OpenTESArena/src/Assets/ArenaAnimUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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(),
Expand All @@ -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<TextureFileMetadataID> metadataID = textureManager.tryGetMetadataID(animFilename.c_str());
if (!metadataID.has_value())
Expand Down
12 changes: 12 additions & 0 deletions OpenTESArena/src/Entities/EntityAnimationDefinition.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <algorithm>
#include <cstdio>
#include <cstring>

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -166,6 +173,11 @@ bool EntityAnimationDefinition::operator==(const EntityAnimationDefinition &othe
}
}

if (!StringView::equals(this->initialStateName, other.initialStateName))
{
return false;
}

return true;
}

Expand Down
4 changes: 4 additions & 0 deletions OpenTESArena/src/Entities/EntityAnimationDefinition.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
33 changes: 21 additions & 12 deletions OpenTESArena/src/Entities/EntityChunkManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Double2> direction;
std::optional<int8_t> citizenDirectionIndex;
Expand All @@ -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;
Expand Down Expand Up @@ -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<int> 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))
{
Expand Down Expand Up @@ -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<int> initialAnimStateIndex = animDef.tryGetStateIndex(initialAnimStateName);
DebugAssert(initialAnimStateIndex.has_value());

std::optional<EntityDefID> entityDefID; // Global entity def ID (shared across all active chunks).
for (const WorldDouble3 &position : placementDef.positions)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<int> initialCitizenAnimStateIndex = citizenAnimDef.tryGetStateIndex(citizenAnimDef.initialStateName);
DebugAssert(initialCitizenAnimStateIndex.has_value());

for (int i = 0; i < citizensToSpawn; i++)
{
const std::optional<VoxelInt2> citizenSpawnVoxel = tryMakeCitizenSpawnVoxel();
Expand All @@ -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);
Expand Down
12 changes: 0 additions & 12 deletions OpenTESArena/src/Entities/EntityGeneration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
2 changes: 0 additions & 2 deletions OpenTESArena/src/Entities/EntityGeneration.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ namespace EntityGeneration

void init(bool nightLightsAreActive);
};

const std::string &getDefaultAnimationStateName(const EntityDefinition &entityDef, const EntityGenInfo &genInfo);
}

#endif
19 changes: 2 additions & 17 deletions OpenTESArena/src/World/MapGeneration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> 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<bool> 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<int> 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)
Expand Down

0 comments on commit 1b4bca7

Please sign in to comment.