Skip to content

Commit feb8846

Browse files
committed
WIP! test: save and load
1 parent 93eaf76 commit feb8846

File tree

3 files changed

+232
-3
lines changed

3 files changed

+232
-3
lines changed

src/main/java/org/terasology/wildAnimals/system/WildAnimalsSpawnSystem.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020 The Terasology Foundation
1+
// Copyright 2022 The Terasology Foundation
22
// SPDX-License-Identifier: Apache-2.0
33
package org.terasology.wildAnimals.system;
44

@@ -30,7 +30,7 @@
3030
import java.util.Random;
3131
import java.util.function.Function;
3232

33-
@Share(value = WildAnimalsSpawnSystem.class)
33+
@Share(WildAnimalsSpawnSystem.class)
3434
@RegisterSystem(RegisterMode.AUTHORITY)
3535
public class WildAnimalsSpawnSystem extends BaseComponentSystem {
3636
private AnimalSpawnConfig config;
@@ -123,7 +123,7 @@ public void onChunkGenerated(OnChunkGenerated event, EntityRef worldEntity) {
123123
*
124124
* @param chunkPos The chunk which the game will try to spawn deers on
125125
*/
126-
private void tryFlockAnimalSpawn(Prefab animalPrefab, Vector3ic chunkPos) {
126+
void tryFlockAnimalSpawn(Prefab animalPrefab, Vector3ic chunkPos) {
127127
List<Vector3i> foundPositions = findFlockAnimalSpawnPositions(chunkPos);
128128

129129
if (foundPositions.size() < config.minFlockSize * config.minGroundPerFlockAnimal) {
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Copyright 2022 The Terasology Foundation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package org.terasology.wildAnimals.system;
5+
6+
import com.google.common.collect.Lists;
7+
import org.joml.Vector3f;
8+
import org.joml.Vector3i;
9+
import org.joml.Vector3ic;
10+
import org.junit.jupiter.api.BeforeAll;
11+
import org.junit.jupiter.api.MethodOrderer;
12+
import org.junit.jupiter.api.Order;
13+
import org.junit.jupiter.api.Tag;
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.jupiter.api.TestInstance;
16+
import org.junit.jupiter.api.TestMethodOrder;
17+
import org.junit.jupiter.api.Timeout;
18+
import org.junit.jupiter.api.extension.ExtendWith;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
import org.terasology.engine.config.Config;
22+
import org.terasology.engine.config.SystemConfig;
23+
import org.terasology.engine.context.Context;
24+
import org.terasology.engine.core.GameEngine;
25+
import org.terasology.engine.core.PathManager;
26+
import org.terasology.engine.core.Time;
27+
import org.terasology.engine.core.bootstrap.EntitySystemSetupUtil;
28+
import org.terasology.engine.core.module.ModuleManager;
29+
import org.terasology.engine.core.subsystem.EngineSubsystem;
30+
import org.terasology.engine.entitySystem.entity.EntityManager;
31+
import org.terasology.engine.entitySystem.entity.EntityRef;
32+
import org.terasology.engine.entitySystem.entity.internal.EngineEntityManager;
33+
import org.terasology.engine.entitySystem.prefab.PrefabManager;
34+
import org.terasology.engine.integrationenvironment.MainLoop;
35+
import org.terasology.engine.integrationenvironment.jupiter.Dependencies;
36+
import org.terasology.engine.integrationenvironment.jupiter.IntegrationEnvironment;
37+
import org.terasology.engine.integrationenvironment.jupiter.MTEExtension;
38+
import org.terasology.engine.logic.location.LocationComponent;
39+
import org.terasology.engine.logic.players.LocalPlayer;
40+
import org.terasology.engine.persistence.StorageManager;
41+
import org.terasology.engine.persistence.internal.ReadWriteStorageManager;
42+
import org.terasology.engine.rendering.logic.SkeletalMeshComponent;
43+
import org.terasology.engine.world.WorldProvider;
44+
import org.terasology.engine.world.block.BlockManager;
45+
import org.terasology.engine.world.block.BlockRegion;
46+
import org.terasology.engine.world.chunks.Chunks;
47+
import org.terasology.engine.world.chunks.blockdata.ExtraBlockDataManager;
48+
import org.terasology.wildAnimals.AnimalSpawnConfig;
49+
import reactor.core.publisher.Flux;
50+
51+
import java.io.IOException;
52+
import java.nio.file.Path;
53+
import java.util.HashSet;
54+
import java.util.Set;
55+
import java.util.concurrent.atomic.AtomicInteger;
56+
import java.util.function.Supplier;
57+
import java.util.stream.Collectors;
58+
59+
import static com.google.common.truth.Truth.assertThat;
60+
import static com.google.common.truth.Truth.assertWithMessage;
61+
62+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
63+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
64+
@Tag("MteTest")
65+
@ExtendWith(MTEExtension.class)
66+
@Dependencies("WildAnimals")
67+
@IntegrationEnvironment(subsystem = WildAnimalsSpawnSystemTest.WriteSaveGames.class)
68+
@Timeout(3600)
69+
class WildAnimalsSpawnSystemTest {
70+
71+
private static final Logger logger = LoggerFactory.getLogger(WildAnimalsSpawnSystemTest.class);
72+
73+
Set<Vector3ic> inhabitedChunks = new HashSet<>();
74+
75+
@BeforeAll
76+
void configSpawn(WildAnimalsSpawnSystem spawnSystem, WorldProvider worldProvider) {
77+
var config = new AnimalSpawnConfig();
78+
config.spawnChanceInPercent = 100;
79+
config.minFlockSize = 1;
80+
config.minGroundPerFlockAnimal = 1;
81+
spawnSystem.setConfig(config);
82+
83+
// Spawn on any solid ground.
84+
spawnSystem.setSpawnCondition(pos -> {
85+
Vector3i below = new Vector3i(pos.x, pos.y - 1, pos.z);
86+
return worldProvider.getBlock(pos).isPenetrable()
87+
&& !worldProvider.getBlock(below).isPenetrable();
88+
});
89+
}
90+
91+
@Test
92+
@Order(1)
93+
void testCanSave(EntityManager entities, StorageManager storage, LocalPlayer player, MainLoop main,
94+
WildAnimalsSpawnSystem spawnSystem, PrefabManager prefabs) {
95+
var character = player.getCharacterEntity();
96+
var loc = character.getComponent(LocationComponent.class).getWorldPosition(new Vector3f());
97+
main.runUntil(main.makeBlocksRelevant(new BlockRegion((int) loc.x, (int) loc.y, (int) loc.z)
98+
.expand(100, 0, 100)));
99+
100+
// var sheepPrefab = prefabs.getPrefab("WildAnimals:sheep");
101+
// spawnSystem.tryFlockAnimalSpawn(sheepPrefab, Chunks.toChunkPos(loc, new Vector3i()));
102+
//
103+
// main.runUntil(new Ticks(1));
104+
105+
assertThat(entities.getEntitiesWith(SkeletalMeshComponent.class)).isNotEmpty();
106+
107+
var animalPosition = Flux.fromIterable(entities.getEntitiesWith(SkeletalMeshComponent.class, LocationComponent.class))
108+
.map(e -> e.getComponent(LocationComponent.class).getWorldPosition(new Vector3f()))
109+
.map(v -> (Vector3ic) Chunks.toChunkPos(v, new Vector3i()))
110+
.collect(Collectors.toUnmodifiableSet())
111+
.block();
112+
inhabitedChunks.addAll(animalPosition);
113+
114+
storage.waitForCompletionOfPreviousSaveAndStartSaving();
115+
storage.finishSavingAndShutdown();
116+
}
117+
118+
@Test
119+
@Order(2)
120+
void testCanLoad(ModuleManager modules,
121+
BlockManager blockManager, ExtraBlockDataManager extraDataManager,
122+
Context context, Config config, MainLoop main, Time time) throws IOException {
123+
// MTE does not include helpers for loading from a save file.
124+
// This is kludged together from StorageManagerTest internals.
125+
EntitySystemSetupUtil.addReflectionBasedLibraries(context);
126+
EntitySystemSetupUtil.addEntityManagementRelatedClasses(context);
127+
EngineEntityManager newEntityManager = context.getValue(EngineEntityManager.class);
128+
StorageManager newSM = new ReadWriteStorageManager(getSavePath(config), modules.getEnvironment(), newEntityManager, blockManager,
129+
extraDataManager, null, null, null);
130+
context.put(StorageManager.class, newSM);
131+
132+
newSM.loadGlobalStore();
133+
134+
for (Vector3ic chunk : inhabitedChunks) {
135+
var restored = newSM.loadChunkStore(chunk);
136+
var dudes = restored.restoreEntities();
137+
logger.warn("restored dudes in {} '{}'", chunk, dudes);
138+
}
139+
140+
// var later = time.getGameTime() + 2;
141+
// main.runUntil(() -> time.getGameTime() > later);
142+
143+
var animals = Lists.newArrayList(newEntityManager.getEntitiesWith(SkeletalMeshComponent.class));
144+
assertThat(animals).isNotNull();
145+
assertThat(animals).isNotEmpty();
146+
for (EntityRef animal : animals) {
147+
var skeleton = animal.getComponent(SkeletalMeshComponent.class);
148+
assertThat(skeleton.boneEntities).isNotNull();
149+
assertThat(skeleton.boneEntities).isNotEmpty();
150+
skeleton.boneEntities.forEach((name, boneEnt) ->
151+
assertWithMessage("Bone \"%s\"", name).that(boneEnt).isNotEqualTo(EntityRef.NULL));
152+
}
153+
}
154+
155+
// copied in from StorageManagerTest
156+
Path getSavePath(Config config) {
157+
// TODO: add a more direct way of inspecting StorageManager's path.
158+
// This way of getting the game title (and thus the path) is a bit brittle,
159+
// because world title and game title are not _required_ to be identical.
160+
String gameTitle = config.getWorldGeneration().getWorldTitle();
161+
return PathManager.getInstance().getSavePath(gameTitle);
162+
}
163+
164+
static class WriteSaveGames implements EngineSubsystem {
165+
@Override
166+
public String getName() {
167+
return getClass().getCanonicalName();
168+
}
169+
170+
@Override
171+
public void initialise(GameEngine engine, Context rootContext) {
172+
rootContext.getValue(SystemConfig.class).writeSaveGamesEnabled.set(true);
173+
}
174+
}
175+
176+
static class Ticks implements Supplier<Boolean> {
177+
private final AtomicInteger count;
178+
Ticks(int initial) {
179+
count = new AtomicInteger(initial);
180+
}
181+
182+
@Override
183+
public Boolean get() {
184+
return count.getAndDecrement() > 0;
185+
}
186+
}
187+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!-- This file, when present as src/test/resources/logback-test.xml,
2+
configures log filters during test execution.
3+
4+
To configure the logs generated while in-game, see
5+
facades/PC/src/main/resources/logback.xml
6+
-->
7+
<configuration debug="false">
8+
<!-- debug: reports all changes to this configuration on System.out -->
9+
10+
<include resource="org/terasology/engine/logback/setup.xml" />
11+
12+
<!-- JUnit collects console output during test execution
13+
and include it in the report for the test. -->
14+
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
15+
<encoder>
16+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
17+
</encoder>
18+
19+
<include resource="org/terasology/engine/logback/filter-reflections.xml" />
20+
</appender>
21+
22+
<root level="warn">
23+
<appender-ref ref="CONSOLE" />
24+
</root>
25+
26+
<!-- NOTE You can add fine granular custom log-level overrides here.
27+
This works in both ways; you can increase or decrease the log level
28+
for specific packages or classes.
29+
30+
For example:
31+
32+
<logger name="org.terasology.engine.world" level="debug" />
33+
<logger name="org.terasology.module.health.systems.HealthAuthoritySystem" level="error" />
34+
-->
35+
<logger name="org.terasology" level="warn" />
36+
<logger name="org.terasology.engine.i18n" level="warn" />
37+
<logger name="org.reflections.Reflections" level="warn" />
38+
<logger name="org.terasology.engine.integrationenvironment" level="info" />
39+
<logger name="org.terasology.persistence" level="debug" />
40+
<logger name="org.terasology.engine.persistence" level="debug" />
41+
<logger name="org.terasology.engine.core.module" level="info" />
42+
</configuration>

0 commit comments

Comments
 (0)