diff --git a/OpenTESArena/src/Entities/CitizenUtils.cpp b/OpenTESArena/src/Entities/CitizenUtils.cpp index 2d34c7297..071c68207 100644 --- a/OpenTESArena/src/Entities/CitizenUtils.cpp +++ b/OpenTESArena/src/Entities/CitizenUtils.cpp @@ -32,15 +32,12 @@ namespace } void CitizenUtils::CitizenGenInfo::init(EntityDefID maleEntityDefID, EntityDefID femaleEntityDefID, - const EntityDefinition *maleEntityDef, const EntityDefinition *femaleEntityDef, - EntityAnimationInstance &&maleAnimInst, EntityAnimationInstance &&femaleAnimInst, int raceID) + const EntityDefinition *maleEntityDef, const EntityDefinition *femaleEntityDef, int raceID) { this->maleEntityDefID = maleEntityDefID; this->femaleEntityDefID = femaleEntityDefID; this->maleEntityDef = maleEntityDef; this->femaleEntityDef = femaleEntityDef; - this->maleAnimInst = std::move(maleAnimInst); - this->femaleAnimInst = std::move(femaleAnimInst); this->raceID = raceID; } @@ -66,36 +63,12 @@ CitizenUtils::CitizenGenInfo CitizenUtils::makeCitizenGenInfo(int raceID, ArenaT DebugCrash("Couldn't get citizen entity def ID from library."); } - // Only two citizen entity definitions for a given climate, based on the gender. + // Two citizen entity definitions per climate. const EntityDefinition &maleEntityDef = entityDefLibrary.getDefinition(maleEntityDefID); const EntityDefinition &femaleEntityDef = entityDefLibrary.getDefinition(femaleEntityDefID); - auto initAnimInst = [](EntityAnimationInstance &animInst, const EntityAnimationDefinition &animDef) - { - for (int i = 0; i < animDef.stateCount; i++) - { - const EntityAnimationDefinitionState &animDefState = animDef.states[i]; - animInst.addState(animDefState.seconds, animDefState.isLooping); - } - - // Idle animation by default. - const std::optional stateIndex = animDef.tryGetStateIndex(EntityAnimationUtils::STATE_IDLE.c_str()); - if (!stateIndex.has_value()) - { - DebugLogError("Couldn't get idle state index for citizen."); - return; - } - - animInst.setStateIndex(*stateIndex); - }; - - EntityAnimationInstance maleAnimInst, femaleAnimInst; - initAnimInst(maleAnimInst, maleEntityDef.animDef); - initAnimInst(femaleAnimInst, femaleEntityDef.animDef); - CitizenGenInfo citizenGenInfo; - citizenGenInfo.init(maleEntityDefID, femaleEntityDefID, &maleEntityDef, &femaleEntityDef, - std::move(maleAnimInst), std::move(femaleAnimInst), raceID); + citizenGenInfo.init(maleEntityDefID, femaleEntityDefID, &maleEntityDef, &femaleEntityDef, raceID); return citizenGenInfo; } diff --git a/OpenTESArena/src/Entities/CitizenUtils.h b/OpenTESArena/src/Entities/CitizenUtils.h index db7eb6b18..0b7768491 100644 --- a/OpenTESArena/src/Entities/CitizenUtils.h +++ b/OpenTESArena/src/Entities/CitizenUtils.h @@ -30,13 +30,10 @@ namespace CitizenUtils EntityDefID femaleEntityDefID; const EntityDefinition *maleEntityDef; const EntityDefinition *femaleEntityDef; - EntityAnimationInstance maleAnimInst; - EntityAnimationInstance femaleAnimInst; int raceID; void init(EntityDefID maleEntityDefID, EntityDefID femaleEntityDefID, const EntityDefinition *maleEntityDef, - const EntityDefinition *femaleEntityDef, EntityAnimationInstance &&maleAnimInst, - EntityAnimationInstance &&femaleAnimInst, int raceID); + const EntityDefinition *femaleEntityDef, int raceID); }; bool canMapTypeSpawnCitizens(MapType mapType); diff --git a/OpenTESArena/src/Entities/EntityChunkManager.cpp b/OpenTESArena/src/Entities/EntityChunkManager.cpp index 91e12975c..bf360d26a 100644 --- a/OpenTESArena/src/Entities/EntityChunkManager.cpp +++ b/OpenTESArena/src/Entities/EntityChunkManager.cpp @@ -31,6 +31,32 @@ namespace { + struct EntityInitInfo + { + EntityDefID defID; + VoxelDouble2 point; + WorldDouble3 bboxMin, bboxMax; // Centered on the entity in model space + double animMaxHeight; + char defaultAnimStateName[EntityAnimationUtils::NAME_LENGTH]; + bool isSensorCollider; + std::optional direction; + std::optional citizenDirectionIndex; + std::optional citizenColorSeed; + std::optional raceID; + bool hasInventory; + bool hasCreatureSound; + + EntityInitInfo() + { + this->defID = -1; + this->animMaxHeight = 0.0; + std::fill(std::begin(this->defaultAnimStateName), std::end(this->defaultAnimStateName), '\0'); + this->isSensorCollider = false; + this->hasInventory = false; + this->hasCreatureSound = false; + } + }; + bool TryCreatePhysicsCollider(const CoordDouble2 &coord, double colliderHeight, double ceilingScale, bool isSensor, JPH::PhysicsSystem &physicsSystem, JPH::BodyID *outBodyID) { JPH::BodyInterface &bodyInterface = physicsSystem.GetBodyInterface(); @@ -162,212 +188,175 @@ void EntityChunkManager::populateChunkEntities(EntityChunk &entityChunk, const V Random &random, const EntityDefinitionLibrary &entityDefLibrary, const BinaryAssetLibrary &binaryAssetLibrary, JPH::PhysicsSystem &physicsSystem, TextureManager &textureManager, Renderer &renderer) { - SNInt startX, endX; - int startY, endY; - WEInt startZ, endZ; - ChunkUtils::GetWritingRanges(levelOffset, levelDefinition.getWidth(), levelDefinition.getHeight(), - levelDefinition.getDepth(), &startX, &startY, &startZ, &endX, &endY, &endZ); - + const ChunkInt2 chunkPos = voxelChunk.getPosition(); const double ceilingScale = levelInfoDefinition.getCeilingScale(); - for (int i = 0; i < levelDefinition.getEntityPlacementDefCount(); i++) + auto initializeEntity = [this, &random, &binaryAssetLibrary, &physicsSystem, &chunkPos, ceilingScale]( + EntityInstance &entityInst, EntityInstanceID instID, const EntityDefinition &entityDef, const EntityInitInfo &initInfo) { - const LevelEntityPlacementDefinition &placementDef = levelDefinition.getEntityPlacementDef(i); - const LevelVoxelEntityDefID levelEntityDefID = placementDef.id; - const EntityDefinition &entityDef = levelInfoDefinition.getEntityDef(levelEntityDefID); - 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 std::optional defaultAnimStateIndex = animDef.tryGetStateIndex(defaultAnimStateName.c_str()); - if (!defaultAnimStateIndex.has_value()) + EntityPositionID positionID; + if (!this->positions.tryAlloc(&positionID)) { - DebugLogWarning("Couldn't get default anim state index for entity."); - continue; + DebugLogError("Couldn't allocate EntityPositionID."); } - std::optional entityDefID; // Global entity def ID (shared across all active chunks). - for (const WorldDouble3 &position : placementDef.positions) + EntityBoundingBoxID bboxID; + if (!this->boundingBoxes.tryAlloc(&bboxID)) { - const WorldInt3 voxelPosition = VoxelUtils::pointToVoxel(position, ceilingScale); - if (ChunkUtils::IsInWritingRange(voxelPosition, startX, endX, startY, endY, startZ, endZ)) - { - if (!entityDefID.has_value()) - { - entityDefID = this->getOrAddEntityDefID(entityDef, entityDefLibrary); - } - - const VoxelDouble3 point = ChunkUtils::MakeChunkPointFromLevel(position, startX, startY, startZ); - EntityInstanceID entityInstID = this->spawnEntity(); - EntityInstance &entityInst = this->entities.get(entityInstID); - - EntityPositionID positionID; - if (!this->positions.tryAlloc(&positionID)) - { - DebugCrash("Couldn't allocate EntityPositionID."); - } - - EntityBoundingBoxID bboxID; - if (!this->boundingBoxes.tryAlloc(&bboxID)) - { - DebugCrash("Couldn't allocate EntityBoundingBoxID."); - } - - entityInst.init(entityInstID, *entityDefID, positionID, bboxID); - - CoordDouble2 &entityCoord = this->positions.get(positionID); - entityCoord.chunk = voxelChunk.getPosition(); - entityCoord.point = VoxelDouble2(point.x, point.z); - - double animMaxWidth, animMaxHeight; - EntityUtils::getAnimationMaxDims(animDef, &animMaxWidth, &animMaxHeight); - const double halfAnimMaxWidth = animMaxWidth * 0.50; + DebugLogError("Couldn't allocate EntityBoundingBoxID."); + } - // Bounding box is centered on the entity in model space. - const WorldDouble3 entityBBoxMin(-halfAnimMaxWidth, 0.0, -halfAnimMaxWidth); - const WorldDouble3 entityBBoxMax(halfAnimMaxWidth, animMaxHeight, halfAnimMaxWidth); - BoundingBox3D &entityBBox = this->boundingBoxes.get(bboxID); - entityBBox.init(entityBBoxMin, entityBBoxMax); + const EntityDefID defID = initInfo.defID; + entityInst.init(instID, defID, positionID, bboxID); - if (isDynamicEntity) // Dynamic entities have a direction. - { - if (!this->directions.tryAlloc(&entityInst.directionID)) - { - DebugCrash("Couldn't allocate EntityDirectionID."); - } + CoordDouble2 &entityCoord = this->positions.get(positionID); + entityCoord = CoordDouble2(chunkPos, initInfo.point); - VoxelDouble2 &entityDir = this->directions.get(entityInst.directionID); - entityDir = CardinalDirection::North; + BoundingBox3D &entityBBox = this->boundingBoxes.get(bboxID); + entityBBox.init(initInfo.bboxMin, initInfo.bboxMax); - if (entityDefType == EntityDefinitionType::Enemy) - { - if (!this->creatureSoundInsts.tryAlloc(&entityInst.creatureSoundInstID)) - { - DebugCrash("Couldn't allocate EntityCreatureSoundInstanceID."); - } + if (!this->animInsts.tryAlloc(&entityInst.animInstID)) + { + DebugLogError("Couldn't allocate EntityAnimationInstanceID."); + } - double &secondsTillCreatureSound = this->creatureSoundInsts.get(entityInst.creatureSoundInstID); - secondsTillCreatureSound = EntityUtils::nextCreatureSoundWaitTime(random); - } - } + const EntityAnimationDefinition &animDef = entityDef.animDef; + EntityAnimationInstance &animInst = this->animInsts.get(entityInst.animInstID); + for (int animDefStateIndex = 0; animDefStateIndex < animDef.stateCount; animDefStateIndex++) + { + const EntityAnimationDefinitionState &animDefState = animDef.states[animDefStateIndex]; + animInst.addState(animDefState.seconds, animDefState.isLooping); + } - if (!this->animInsts.tryAlloc(&entityInst.animInstID)) - { - DebugCrash("Couldn't allocate EntityAnimationInstanceID."); - } + const std::optional defaultAnimStateIndex = animDef.tryGetStateIndex(initInfo.defaultAnimStateName); + DebugAssert(defaultAnimStateIndex.has_value()); + animInst.setStateIndex(*defaultAnimStateIndex); - EntityAnimationInstance &animInst = this->animInsts.get(entityInst.animInstID); + if (!TryCreatePhysicsCollider(entityCoord, initInfo.animMaxHeight, ceilingScale, initInfo.isSensorCollider, physicsSystem, &entityInst.physicsBodyID)) + { + DebugLogError("Couldn't allocate entity Jolt physics body."); + } - // Populate anim inst states now so the def doesn't need to be provided later. - for (int animDefStateIndex = 0; animDefStateIndex < animDef.stateCount; animDefStateIndex++) - { - const EntityAnimationDefinitionState &animDefState = animDef.states[animDefStateIndex]; - animInst.addState(animDefState.seconds, animDefState.isLooping); - } + if (initInfo.direction.has_value()) + { + if (!this->directions.tryAlloc(&entityInst.directionID)) + { + DebugLogError("Couldn't allocate EntityDirectionID."); + } - const EntityAnimationDefinitionState &defaultAnimDefState = animDef.states[*defaultAnimStateIndex]; - animInst.setStateIndex(*defaultAnimStateIndex); + const Double2 &direction = *initInfo.direction; + this->directions.get(entityInst.directionID) = direction; + } - const bool isSensor = !EntityUtils::hasCollision(entityDef); - if (!TryCreatePhysicsCollider(entityCoord, animMaxHeight, ceilingScale, isSensor, physicsSystem, &entityInst.physicsBodyID)) - { - DebugLogError("Couldn't allocate Jolt physics body for entity."); - } + if (initInfo.citizenDirectionIndex.has_value()) + { + if (!this->citizenDirectionIndices.tryAlloc(&entityInst.citizenDirectionIndexID)) + { + DebugLogError("Couldn't allocate EntityCitizenDirectionIndexID."); + } - bool hasInventory = (entityDefType == EntityDefinitionType::Enemy) || (entityDefType == EntityDefinitionType::Container); - if (hasInventory) - { - if (!this->itemInventories.tryAlloc(&entityInst.itemInventoryInstID)) - { - DebugCrash("Couldn't allocate EntityItemInventoryInstanceID."); - } - } + const uint8_t citizenDirectionIndex = *initInfo.citizenDirectionIndex; + this->citizenDirectionIndices.get(entityInst.citizenDirectionIndexID) = citizenDirectionIndex; + } - entityChunk.entityIDs.emplace_back(entityInstID); + if (initInfo.citizenColorSeed.has_value()) + { + if (!this->paletteIndices.tryAlloc(&entityInst.paletteIndicesInstID)) + { + DebugLogError("Couldn't allocate EntityPaletteIndicesInstanceID."); } + + DebugAssert(initInfo.raceID.has_value()); + const uint16_t citizenColorSeed = *initInfo.citizenColorSeed; + PaletteIndices &paletteIndices = this->paletteIndices.get(entityInst.paletteIndicesInstID); + paletteIndices = ArenaAnimUtils::transformCitizenColors(*initInfo.raceID, citizenColorSeed, binaryAssetLibrary.getExeData()); } - } - if (citizenGenInfo.has_value()) - { - const ChunkInt2 chunkPos = voxelChunk.getPosition(); - auto spawnCitizenAtVoxel = [this, &entityChunk, &random, &binaryAssetLibrary, &physicsSystem, &textureManager, - &chunkPos, ceilingScale](const VoxelInt2 &voxelPos, EntityDefID entityDefID, const EntityDefinition &entityDef, - const EntityAnimationDefinition &entityAnimDef, const EntityAnimationInstance &animInst, int raceID) + if (initInfo.hasInventory) { - EntityInstanceID entityInstID = this->spawnEntity(); - EntityInstance &entityInst = this->entities.get(entityInstID); - - EntityPositionID positionID; - if (!this->positions.tryAlloc(&positionID)) + if (!this->itemInventories.tryAlloc(&entityInst.itemInventoryInstID)) { - DebugLogError("Couldn't allocate citizen EntityPositionID."); + DebugCrash("Couldn't allocate EntityItemInventoryInstanceID."); } + } - EntityBoundingBoxID bboxID; - if (!this->boundingBoxes.tryAlloc(&bboxID)) + if (initInfo.hasCreatureSound) + { + if (!this->creatureSoundInsts.tryAlloc(&entityInst.creatureSoundInstID)) { - DebugLogError("Couldn't allocate citizen EntityBoundingBoxID."); + DebugCrash("Couldn't allocate EntityCreatureSoundInstanceID."); } - entityInst.init(entityInstID, entityDefID, positionID, bboxID); - - CoordDouble2 &entityCoord = this->positions.get(positionID); - entityCoord = CoordDouble2(chunkPos, VoxelUtils::getVoxelCenter(voxelPos)); + double &secondsTillNextCreatureSound = this->creatureSoundInsts.get(entityInst.creatureSoundInstID); + secondsTillNextCreatureSound = EntityUtils::nextCreatureSoundWaitSeconds(random); + } + }; - double animMaxWidth, animMaxHeight; - EntityUtils::getAnimationMaxDims(entityAnimDef, &animMaxWidth, &animMaxHeight); - const double halfAnimMaxWidth = animMaxWidth * 0.50; + SNInt startX, endX; + int startY, endY; + WEInt startZ, endZ; + ChunkUtils::GetWritingRanges(levelOffset, levelDefinition.getWidth(), levelDefinition.getHeight(), + levelDefinition.getDepth(), &startX, &startY, &startZ, &endX, &endY, &endZ); - // Bounding box is centered on the entity in model space. - const WorldDouble3 entityBBoxMin(-halfAnimMaxWidth, 0.0, -halfAnimMaxWidth); - const WorldDouble3 entityBBoxMax(halfAnimMaxWidth, animMaxHeight, halfAnimMaxWidth); - BoundingBox3D &entityBBox = this->boundingBoxes.get(bboxID); - entityBBox.init(entityBBoxMin, entityBBoxMax); + for (int i = 0; i < levelDefinition.getEntityPlacementDefCount(); i++) + { + const LevelEntityPlacementDefinition &placementDef = levelDefinition.getEntityPlacementDef(i); + const LevelVoxelEntityDefID levelEntityDefID = placementDef.id; + const EntityDefinition &entityDef = levelInfoDefinition.getEntityDef(levelEntityDefID); + const EntityDefinitionType entityDefType = entityDef.type; + const bool isDynamicEntity = EntityUtils::isDynamicEntity(entityDefType); + const EntityAnimationDefinition &animDef = entityDef.animDef; + const std::string &defaultAnimStateName = EntityGeneration::getDefaultAnimationStateName(entityDef, entityGenInfo); - if (!this->citizenDirectionIndices.tryAlloc(&entityInst.citizenDirectionIndexID)) + std::optional entityDefID; // Global entity def ID (shared across all active chunks). + for (const WorldDouble3 &position : placementDef.positions) + { + const WorldInt3 voxelPosition = VoxelUtils::pointToVoxel(position, ceilingScale); + if (!ChunkUtils::IsInWritingRange(voxelPosition, startX, endX, startY, endY, startZ, endZ)) { - DebugLogError("Couldn't allocate citizen EntityCitizenDirectionIndexID."); + continue; } - int8_t &citizenDirIndex = this->citizenDirectionIndices.get(entityInst.citizenDirectionIndexID); - citizenDirIndex = CitizenUtils::getRandomCitizenDirectionIndex(random); - - if (!this->directions.tryAlloc(&entityInst.directionID)) + if (!entityDefID.has_value()) { - DebugLogError("Couldn't allocate citizen EntityDirectionID."); + entityDefID = this->getOrAddEntityDefID(entityDef, entityDefLibrary); } - VoxelDouble2 &entityDir = this->directions.get(entityInst.directionID); - entityDir = CitizenUtils::getCitizenDirectionByIndex(citizenDirIndex); + const VoxelDouble3 point = ChunkUtils::MakeChunkPointFromLevel(position, startX, startY, startZ); - if (!this->animInsts.tryAlloc(&entityInst.animInstID)) - { - DebugLogError("Couldn't allocate citizen EntityAnimationInstanceID."); - } + double animMaxWidth, animMaxHeight; + EntityUtils::getAnimationMaxDims(animDef, &animMaxWidth, &animMaxHeight); + const double halfAnimMaxWidth = animMaxWidth * 0.50; - this->animInsts.get(entityInst.animInstID) = animInst; + EntityInitInfo initInfo; + initInfo.defID = *entityDefID; + initInfo.point = VoxelDouble2(point.x, point.z); - if (!this->paletteIndices.tryAlloc(&entityInst.paletteIndicesInstID)) - { - DebugLogError("Couldn't allocate citizen EntityPaletteIndicesInstanceID."); - } + // Bounding box is centered on the entity in model space. + initInfo.bboxMin = WorldDouble3(-halfAnimMaxWidth, 0.0, -halfAnimMaxWidth); + initInfo.bboxMax = WorldDouble3(halfAnimMaxWidth, animMaxHeight, halfAnimMaxWidth); - const uint16_t colorSeed = static_cast(random.next() % std::numeric_limits::max()); - PaletteIndices &paletteIndices = this->paletteIndices.get(entityInst.paletteIndicesInstID); - paletteIndices = ArenaAnimUtils::transformCitizenColors(raceID, colorSeed, binaryAssetLibrary.getExeData()); + initInfo.animMaxHeight = animMaxHeight; + std::snprintf(initInfo.defaultAnimStateName, std::size(initInfo.defaultAnimStateName), "%s", defaultAnimStateName.c_str()); + + initInfo.isSensorCollider = !EntityUtils::hasCollision(entityDef); - // Citizens don't collide with player. - constexpr bool isSensor = true; - if (!TryCreatePhysicsCollider(entityCoord, animMaxHeight, ceilingScale, isSensor, physicsSystem, &entityInst.physicsBodyID)) + if (isDynamicEntity) { - DebugLogError("Couldn't allocate Jolt physics body for citizen entity."); + initInfo.direction = CardinalDirection::North; + initInfo.hasInventory = (entityDefType == EntityDefinitionType::Enemy) || (entityDefType == EntityDefinitionType::Container); + initInfo.hasCreatureSound = (entityDefType == EntityDefinitionType::Enemy) && (entityDef.enemy.type == EnemyEntityDefinitionType::Creature); } + EntityInstanceID entityInstID = this->spawnEntity(); + EntityInstance &entityInst = this->entities.get(entityInstID); + initializeEntity(entityInst, entityInstID, entityDef, initInfo); entityChunk.entityIDs.emplace_back(entityInstID); - }; + } + } + if (citizenGenInfo.has_value()) + { auto tryMakeCitizenSpawnVoxel = [&voxelChunk, &random]() -> std::optional { constexpr int maxSpawnAttemptsCount = 30; @@ -394,34 +383,51 @@ void EntityChunkManager::populateChunkEntities(EntityChunk &entityChunk, const V const int remainingFemaleCitizensToSpawn = targetCitizensToSpawn - remainingMaleCitizensToSpawn; const int citizenRaceID = citizenGenInfo->raceID; - for (int i = 0; i < remainingMaleCitizensToSpawn; i++) + const int citizenCountsToSpawn[] = { remainingMaleCitizensToSpawn, remainingFemaleCitizensToSpawn }; + const EntityDefID citizenDefIDs[] = { citizenGenInfo->maleEntityDefID, citizenGenInfo->femaleEntityDefID }; + const EntityDefinition *citizenDefs[] = { citizenGenInfo->maleEntityDef, citizenGenInfo->femaleEntityDef }; + for (int citizenGenderIndex = 0; citizenGenderIndex < 2; citizenGenderIndex++) { - const std::optional maleSpawnVoxel = tryMakeCitizenSpawnVoxel(); - if (!maleSpawnVoxel.has_value()) + DebugAssertIndex(citizenCountsToSpawn, citizenGenderIndex); + const int citizensToSpawn = citizenCountsToSpawn[citizenGenderIndex]; + const EntityDefID citizenEntityDefID = citizenDefIDs[citizenGenderIndex]; + const EntityDefinition &citizenDef = *citizenDefs[citizenGenderIndex]; + for (int i = 0; i < citizensToSpawn; i++) { - continue; - } + const std::optional citizenSpawnVoxel = tryMakeCitizenSpawnVoxel(); + if (!citizenSpawnVoxel.has_value()) + { + continue; + } - const EntityDefID maleEntityDefID = citizenGenInfo->maleEntityDefID; - const EntityDefinition &maleEntityDef = *citizenGenInfo->maleEntityDef; - const EntityAnimationDefinition &maleAnimDef = maleEntityDef.animDef; - const EntityAnimationInstance &maleAnimInst = citizenGenInfo->maleAnimInst; - spawnCitizenAtVoxel(*maleSpawnVoxel, maleEntityDefID, maleEntityDef, maleAnimDef, maleAnimInst, citizenRaceID); - } + EntityInitInfo citizenInitInfo; + citizenInitInfo.defID = citizenEntityDefID; + citizenInitInfo.point = VoxelUtils::getVoxelCenter(*citizenSpawnVoxel); - for (int i = 0; i < remainingFemaleCitizensToSpawn; i++) - { - const std::optional femaleSpawnVoxel = tryMakeCitizenSpawnVoxel(); - if (!femaleSpawnVoxel.has_value()) - { - continue; - } + double animMaxWidth, animMaxHeight; + EntityUtils::getAnimationMaxDims(citizenDef.animDef, &animMaxWidth, &animMaxHeight); + const double halfAnimMaxWidth = animMaxWidth * 0.50; + + // Bounding box is centered on the entity in model space. + citizenInitInfo.bboxMin = WorldDouble3(-halfAnimMaxWidth, 0.0, -halfAnimMaxWidth); + citizenInitInfo.bboxMax = WorldDouble3(halfAnimMaxWidth, animMaxHeight, halfAnimMaxWidth); + + citizenInitInfo.animMaxHeight = animMaxHeight; + std::snprintf(citizenInitInfo.defaultAnimStateName, std::size(citizenInitInfo.defaultAnimStateName), "%s", EntityAnimationUtils::STATE_IDLE.c_str()); + + citizenInitInfo.isSensorCollider = true; + citizenInitInfo.citizenDirectionIndex = CitizenUtils::getRandomCitizenDirectionIndex(random); + citizenInitInfo.direction = CitizenUtils::getCitizenDirectionByIndex(*citizenInitInfo.citizenDirectionIndex); + citizenInitInfo.citizenColorSeed = static_cast(random.next() % std::numeric_limits::max()); + citizenInitInfo.raceID = citizenRaceID; + citizenInitInfo.hasInventory = false; + citizenInitInfo.hasCreatureSound = false; - const EntityDefID femaleEntityDefID = citizenGenInfo->femaleEntityDefID; - const EntityDefinition &femaleEntityDef = *citizenGenInfo->femaleEntityDef; - const EntityAnimationDefinition &femaleAnimDef = femaleEntityDef.animDef; - const EntityAnimationInstance &femaleAnimInst = citizenGenInfo->maleAnimInst; - spawnCitizenAtVoxel(*femaleSpawnVoxel, femaleEntityDefID, femaleEntityDef, femaleAnimDef, femaleAnimInst, citizenRaceID); + EntityInstanceID entityInstID = this->spawnEntity(); + EntityInstance &entityInst = this->entities.get(entityInstID); + initializeEntity(entityInst, entityInstID, citizenDef, citizenInitInfo); + entityChunk.entityIDs.emplace_back(entityInstID); + } } } } @@ -938,7 +944,7 @@ void EntityChunkManager::updateCreatureSounds(double dt, EntityChunk &entityChun const WorldDouble3 absoluteSoundPosition = VoxelUtils::coordToWorldPoint(soundCoord); audioManager.playSound(creatureSoundFilename.c_str(), absoluteSoundPosition); - secondsTillCreatureSound = EntityUtils::nextCreatureSoundWaitTime(random); + secondsTillCreatureSound = EntityUtils::nextCreatureSoundWaitSeconds(random); } } } diff --git a/OpenTESArena/src/Entities/EntityUtils.cpp b/OpenTESArena/src/Entities/EntityUtils.cpp index af70f6b43..6c65f311b 100644 --- a/OpenTESArena/src/Entities/EntityUtils.cpp +++ b/OpenTESArena/src/Entities/EntityUtils.cpp @@ -236,7 +236,7 @@ bool EntityUtils::withinHearingDistance(const CoordDouble3 &listenerCoord, const return diff.lengthSquared() < hearingDistanceSqr; } -double EntityUtils::nextCreatureSoundWaitTime(Random &random) +double EntityUtils::nextCreatureSoundWaitSeconds(Random &random) { // Arbitrary amount of time. return 2.75 + (random.nextReal() * 4.50); diff --git a/OpenTESArena/src/Entities/EntityUtils.h b/OpenTESArena/src/Entities/EntityUtils.h index 62c363582..d3cb3fb85 100644 --- a/OpenTESArena/src/Entities/EntityUtils.h +++ b/OpenTESArena/src/Entities/EntityUtils.h @@ -55,7 +55,7 @@ namespace EntityUtils bool withinHearingDistance(const CoordDouble3 &listenerCoord, const CoordDouble2 &soundCoord, double ceilingScale); - double nextCreatureSoundWaitTime(Random &random); + double nextCreatureSoundWaitSeconds(Random &random); } #endif