diff --git a/src/main/java/org/terasology/wildAnimals/system/WildAnimalsSpawnSystem.java b/src/main/java/org/terasology/wildAnimals/system/WildAnimalsSpawnSystem.java index 61fcdd1..04bc43e 100644 --- a/src/main/java/org/terasology/wildAnimals/system/WildAnimalsSpawnSystem.java +++ b/src/main/java/org/terasology/wildAnimals/system/WildAnimalsSpawnSystem.java @@ -1,4 +1,4 @@ -// Copyright 2020 The Terasology Foundation +// Copyright 2022 The Terasology Foundation // SPDX-License-Identifier: Apache-2.0 package org.terasology.wildAnimals.system; @@ -30,7 +30,7 @@ import java.util.Random; import java.util.function.Function; -@Share(value = WildAnimalsSpawnSystem.class) +@Share(WildAnimalsSpawnSystem.class) @RegisterSystem(RegisterMode.AUTHORITY) public class WildAnimalsSpawnSystem extends BaseComponentSystem { private AnimalSpawnConfig config; @@ -123,7 +123,7 @@ public void onChunkGenerated(OnChunkGenerated event, EntityRef worldEntity) { * * @param chunkPos The chunk which the game will try to spawn deers on */ - private void tryFlockAnimalSpawn(Prefab animalPrefab, Vector3ic chunkPos) { + void tryFlockAnimalSpawn(Prefab animalPrefab, Vector3ic chunkPos) { List foundPositions = findFlockAnimalSpawnPositions(chunkPos); if (foundPositions.size() < config.minFlockSize * config.minGroundPerFlockAnimal) { diff --git a/src/test/java/org/terasology/wildAnimals/system/WildAnimalsSpawnSystemTest.java b/src/test/java/org/terasology/wildAnimals/system/WildAnimalsSpawnSystemTest.java new file mode 100644 index 0000000..de0026f --- /dev/null +++ b/src/test/java/org/terasology/wildAnimals/system/WildAnimalsSpawnSystemTest.java @@ -0,0 +1,152 @@ +// Copyright 2022 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.wildAnimals.system; + +import com.google.common.collect.Lists; +import org.joml.Vector3f; +import org.joml.Vector3i; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.engine.config.SystemConfig; +import org.terasology.engine.context.Context; +import org.terasology.engine.core.GameEngine; +import org.terasology.engine.core.PathManager; +import org.terasology.engine.core.subsystem.EngineSubsystem; +import org.terasology.engine.entitySystem.entity.EntityManager; +import org.terasology.engine.entitySystem.entity.EntityRef; +import org.terasology.engine.integrationenvironment.MainLoop; +import org.terasology.engine.integrationenvironment.jupiter.Dependencies; +import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment; +import org.terasology.engine.integrationenvironment.jupiter.MTEExtension; +import org.terasology.engine.logic.location.LocationComponent; +import org.terasology.engine.logic.players.LocalPlayer; +import org.terasology.engine.persistence.StorageManager; +import org.terasology.engine.rendering.logic.SkeletalMeshComponent; +import org.terasology.engine.rendering.nui.layers.mainMenu.savedGames.GameProvider; +import org.terasology.engine.world.WorldProvider; +import org.terasology.engine.world.block.BlockRegion; +import org.terasology.wildAnimals.AnimalSpawnConfig; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.util.List; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Tag("MteTest") +@ExtendWith(MTEExtension.class) +@Dependencies("WildAnimals") +@IntegrationEnvironment(subsystem = WildAnimalsSpawnSystemTest.WriteSaveGames.class) +class WildAnimalsSpawnSystemTest { + + private static final Logger logger = LoggerFactory.getLogger(WildAnimalsSpawnSystemTest.class); + + @BeforeEach + void configSpawn(WildAnimalsSpawnSystem spawnSystem, WorldProvider worldProvider) { + var config = new AnimalSpawnConfig(); + config.spawnChanceInPercent = 100; + config.minFlockSize = 1; + config.minGroundPerFlockAnimal = 1; + spawnSystem.setConfig(config); + + // Spawn on any solid ground. + spawnSystem.setSpawnCondition(pos -> { + Vector3i below = new Vector3i(pos.x, pos.y - 1, pos.z); + return worldProvider.getBlock(pos).isPenetrable() + && !worldProvider.getBlock(below).isPenetrable(); + }); + } + + @Test + @Order(1) + void testCanSave(EntityManager entities, StorageManager storage, LocalPlayer player, MainLoop main) { + assertThat(GameProvider.isSavesFolderEmpty()).isTrue(); + + loadRegionAroundPlayer(player, main); + + assertThat(entities.getEntitiesWith(SkeletalMeshComponent.class)).isNotEmpty(); + + var animals = Lists.newArrayList(entities.getEntitiesWith(SkeletalMeshComponent.class)); + assertEveryoneHasBones(animals); + + storage.waitForCompletionOfPreviousSaveAndStartSaving(); + storage.finishSavingAndShutdown(); + } + + @Test + @Order(2) + void testCanLoad(EntityManager entities, LocalPlayer player, MainLoop main) { + assertThat(GameProvider.getSavedGames()).hasSize(1); + + loadRegionAroundPlayer(player, main); + + var animals = Lists.newArrayList(entities.getEntitiesWith(SkeletalMeshComponent.class)); + assertEveryoneHasBones(animals); + } + + private void loadRegionAroundPlayer(LocalPlayer player, MainLoop main) { + var character = player.getCharacterEntity(); + var loc = character.getComponent(LocationComponent.class).getWorldPosition(new Vector3f()); + var relevantRegion = new BlockRegion((int) loc.x, (int) loc.y, (int) loc.z) + .expand(100, 0, 100); + main.runUntil(main.makeBlocksRelevant(relevantRegion)); + } + + private void assertEveryoneHasBones(List animals) { + assertThat(animals).isNotNull(); + assertThat(animals).isNotEmpty(); + for (EntityRef animal : animals) { + var skeleton = animal.getComponent(SkeletalMeshComponent.class); + var a = assertWithMessage("Entity %s animal %s", animal, skeleton); + a.that(skeleton.boneEntities).isNotNull(); + a.that(skeleton.boneEntities).isNotEmpty(); + skeleton.boneEntities.forEach((name, boneEnt) -> + assertWithMessage("Bone \"%s\"", name).that(boneEnt).isNotEqualTo(EntityRef.NULL)); + } + } + + /** @see Terasology#5050 */ + static class WriteSaveGames implements EngineSubsystem { + + private static Path homePath; + + @Override + public String getName() { + return getClass().getCanonicalName(); + } + + @Override + public void preInitialise(Context rootContext) { + if (homePath != null) { + try { + logger.debug("Resetting home path to previously seen {}", homePath); + PathManager.getInstance().useOverrideHomePath(homePath); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + + @Override + public void initialise(GameEngine engine, Context rootContext) { + if (homePath == null) { + homePath = PathManager.getInstance().getHomePath(); + logger.debug("Home path first set to {}", homePath); + } + rootContext.getValue(SystemConfig.class).writeSaveGamesEnabled.set(true); + } + } +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..9fe5ff0 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,42 @@ + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + +