Skip to content

Commit ce02db2

Browse files
committed
Add support for reading structure files from both mod jars and the game directory
1 parent 10d4c81 commit ce02db2

File tree

5 files changed

+115
-38
lines changed

5 files changed

+115
-38
lines changed

src/main/java/aztech/modern_industrialization/MI.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import aztech.modern_industrialization.machines.init.MultiblockMachines;
4343
import aztech.modern_industrialization.machines.init.SingleBlockCraftingMachines;
4444
import aztech.modern_industrialization.machines.init.SingleBlockSpecialMachines;
45+
import aztech.modern_industrialization.machines.multiblocks.structure.MIStructureTemplateManager;
4546
import aztech.modern_industrialization.machines.multiblocks.world.ChunkEventListeners;
4647
import aztech.modern_industrialization.materials.MIMaterials;
4748
import aztech.modern_industrialization.misc.autotest.MIAutoTesting;
@@ -73,6 +74,7 @@
7374
import net.neoforged.bus.api.IEventBus;
7475
import net.neoforged.fml.common.Mod;
7576
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
77+
import net.neoforged.fml.javafmlmod.FMLModContainer;
7678
import net.neoforged.fml.loading.FMLPaths;
7779
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
7880
import net.neoforged.neoforge.common.NeoForge;
@@ -96,9 +98,11 @@ public static ResourceLocation id(String path) {
9698
return ResourceLocation.fromNamespaceAndPath(ID, path);
9799
}
98100

99-
public MI(IEventBus modBus, Dist dist) {
101+
public MI(FMLModContainer container, IEventBus modBus, Dist dist) {
100102
KubeJSProxy.checkThatKubeJsIsLoaded();
101103

104+
MIStructureTemplateManager.init();
105+
102106
MIAdvancementTriggers.init(modBus);
103107
MIComponents.init(modBus);
104108
MIFluids.init(modBus);

src/main/java/aztech/modern_industrialization/debug/DebugCommands.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
import net.minecraft.commands.CommandSourceStack;
4747
import net.minecraft.commands.SharedSuggestionProvider;
4848
import net.minecraft.core.BlockPos;
49-
import net.minecraft.nbt.CompoundTag;
5049
import net.minecraft.network.chat.Component;
5150
import net.minecraft.resources.ResourceLocation;
5251
import net.minecraft.server.level.ServerPlayer;
@@ -205,16 +204,11 @@ private static int buildMultiblock(CommandSourceStack src, BlockPos controllerPo
205204
private static int structures(CommandSourceStack src, ResourceLocation id, BlockPos controllerPos, Consumer<ShapeMatcher> action) {
206205
BlockState controllerState = src.getLevel().getBlockState(controllerPos);
207206
if (controllerState.is(MIBlock.STRUCTURE_MULTIBLOCK_CONTROLLER.get())) {
208-
CompoundTag tag = MIStructureTemplateManager.load(id);
209-
if (tag == null) {
207+
if (!MIStructureTemplateManager.exists(id)) {
210208
src.sendFailure(Component.literal("Could not find structure with the id %s".formatted(id)));
211209
return Command.SINGLE_SUCCESS;
212210
}
213-
ShapeTemplate shape = MIStructureTemplateManager.deserialize(tag);
214-
if (shape == null) {
215-
src.sendFailure(Component.literal("Failed to read structure file for structure %s".formatted(id)));
216-
return Command.SINGLE_SUCCESS;
217-
}
211+
ShapeTemplate shape = MIStructureTemplateManager.get(id);
218212
ShapeMatcher matcher = new ShapeMatcher(src.getLevel(), controllerPos, controllerState.getValue(BlockStateProperties.HORIZONTAL_FACING),
219213
shape);
220214
action.accept(matcher);

src/main/java/aztech/modern_industrialization/machines/multiblocks/ShapeTemplate.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -211,15 +211,7 @@ public Structure(ResourceLocation id) {
211211
}
212212

213213
public ShapeTemplate build() {
214-
var tag = MIStructureTemplateManager.load(id);
215-
if (tag == null) {
216-
throw new IllegalStateException("Failed to load structure with id %s".formatted(id));
217-
}
218-
ShapeTemplate template = MIStructureTemplateManager.deserialize(tag);
219-
if (template == null) {
220-
throw new IllegalStateException("Failed to parse structure with id %s".formatted(id));
221-
}
222-
return template;
214+
return MIStructureTemplateManager.get(id);
223215
}
224216
}
225217
}

src/main/java/aztech/modern_industrialization/machines/multiblocks/structure/MIStructureTemplateManager.java

Lines changed: 104 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,18 @@
3232
import aztech.modern_industrialization.machines.models.MachineCasings;
3333
import aztech.modern_industrialization.machines.multiblocks.ShapeTemplate;
3434
import aztech.modern_industrialization.machines.multiblocks.structure.member.StructureMember;
35-
import java.io.FileInputStream;
36-
import java.io.FileOutputStream;
3735
import java.io.IOException;
3836
import java.io.InputStream;
3937
import java.io.OutputStream;
38+
import java.nio.file.DirectoryStream;
4039
import java.nio.file.Files;
4140
import java.nio.file.Path;
4241
import java.util.ArrayList;
42+
import java.util.HashMap;
4343
import java.util.List;
44+
import java.util.Map;
4445
import java.util.Objects;
46+
import java.util.function.BiConsumer;
4547
import net.minecraft.FileUtil;
4648
import net.minecraft.core.BlockPos;
4749
import net.minecraft.core.Direction;
@@ -58,10 +60,35 @@
5860
import net.minecraft.world.level.block.entity.BlockEntity;
5961
import net.minecraft.world.level.block.state.BlockState;
6062
import net.minecraft.world.level.levelgen.structure.BoundingBox;
63+
import net.neoforged.fml.ModList;
6164
import net.neoforged.fml.loading.FMLPaths;
65+
import net.neoforged.neoforgespi.language.IModFileInfo;
6266
import org.jetbrains.annotations.Nullable;
6367

6468
public final class MIStructureTemplateManager {
69+
private static Map<ResourceLocation, ShapeTemplate> STRUCTURE_TEMPLATES;
70+
71+
private static void assertLoaded() {
72+
if (STRUCTURE_TEMPLATES == null) {
73+
throw new IllegalStateException("Structure templates have not yet been loaded");
74+
}
75+
}
76+
77+
public static boolean exists(ResourceLocation id) {
78+
assertLoaded();
79+
return STRUCTURE_TEMPLATES.containsKey(id);
80+
}
81+
82+
public static ShapeTemplate get(ResourceLocation id) {
83+
assertLoaded();
84+
ShapeTemplate template = STRUCTURE_TEMPLATES.get(id);
85+
if (template != null) {
86+
return template;
87+
} else {
88+
throw new IllegalArgumentException("Structure shape template \"" + id.toString() + "\" does not exist.");
89+
}
90+
}
91+
6592
public static StructureResult fromWorld(ResourceLocation id, Level level,
6693
BlockPos controllerPos, Direction controllerDirection,
6794
MachineCasing hatchCasing, StructureControllerBounds bounds) {
@@ -160,7 +187,7 @@ public static StructureResult fromWorld(ResourceLocation id, Level level,
160187
}
161188

162189
@Nullable
163-
public static ShapeTemplate deserialize(CompoundTag tag) {
190+
private static ShapeTemplate deserialize(CompoundTag tag) {
164191
Objects.requireNonNull(tag);
165192

166193
ResourceLocation hatchCasingId = ResourceLocation.tryParse(tag.getString("hatch_casing"));
@@ -189,12 +216,15 @@ public static ShapeTemplate deserialize(CompoundTag tag) {
189216
return builder.build();
190217
}
191218

219+
private static Path structuresPath() {
220+
return FMLPaths.GAMEDIR.get()
221+
.resolve(MI.ID)
222+
.resolve("structures");
223+
}
224+
192225
private static Path path(ResourceLocation id) throws IOException {
193226
Objects.requireNonNull(id);
194-
var miFolder = FMLPaths.GAMEDIR.get().resolve(MI.ID);
195-
var structuresFolder = miFolder
196-
.resolve("structures")
197-
.resolve(id.getNamespace());
227+
var structuresFolder = structuresPath().resolve(id.getNamespace());
198228
Files.createDirectories(structuresFolder);
199229
return FileUtil.createPathToResource(structuresFolder, id.getPath(), ".nbt");
200230
}
@@ -203,39 +233,97 @@ public static boolean save(ResourceLocation id, CompoundTag tag) {
203233
Objects.requireNonNull(id);
204234
Objects.requireNonNull(tag);
205235
try {
206-
try (OutputStream output = new FileOutputStream(path(id).toFile())) {
236+
try (OutputStream output = Files.newOutputStream(path(id))) {
207237
NbtIo.writeCompressed(tag, output);
208238
return true;
209239
} catch (Exception ex) {
210-
MI.LOGGER.error("Failed to save structure '{}'", id, ex);
240+
MI.LOGGER.error("Failed to save structure \"{}\"", id, ex);
211241
}
212242
} catch (Exception ex) {
213-
MI.LOGGER.error("Failed to save structure '{}'", id, ex);
243+
MI.LOGGER.error("Failed to save structure \"{}\"", id, ex);
214244
}
215245
return false;
216246
}
217247

218248
@Nullable
219-
public static CompoundTag load(ResourceLocation id) {
220-
Objects.requireNonNull(id);
249+
private static CompoundTag load(Path path) {
250+
Objects.requireNonNull(path);
221251
try {
222-
Path path = path(id);
223252
if (Files.exists(path)) {
224-
try (InputStream input = new FileInputStream(path.toFile());
253+
try (InputStream input = Files.newInputStream(path);
225254
InputStream fastInput = new FastBufferedInputStream(input)) {
226255
return NbtIo.readCompressed(fastInput, NbtAccounter.unlimitedHeap());
227256
} catch (Exception ex) {
228-
MI.LOGGER.error("Failed to load structure '{}'", id, ex);
257+
MI.LOGGER.error("Failed to load structure at \"{}\"", path, ex);
229258
return null;
230259
}
231260
}
261+
MI.LOGGER.error("Could not find structure at \"{}\"", path);
232262
return null;
233263
} catch (Exception ex) {
234-
MI.LOGGER.error("Failed to load structure '{}'", id, ex);
264+
MI.LOGGER.error("Failed to load structure at \"{}\"", path, ex);
235265
return null;
236266
}
237267
}
238268

269+
private static void iterateStructureFiles(Path origin, BiConsumer<ResourceLocation, Path> action) throws IOException {
270+
try (DirectoryStream<Path> subdirectories = Files.newDirectoryStream(origin, Files::isDirectory)) {
271+
for (Path subdirectory : subdirectories) {
272+
String namespace = subdirectory.getFileName().toString();
273+
try (DirectoryStream<Path> files = Files.newDirectoryStream(subdirectory)) {
274+
for (Path file : files) {
275+
if (file.toString().endsWith(".nbt")) {
276+
String rawFileName = file.getFileName().toString();
277+
String path = rawFileName.substring(0, rawFileName.lastIndexOf('.'));
278+
ResourceLocation id = ResourceLocation.fromNamespaceAndPath(namespace, path);
279+
action.accept(id, file);
280+
}
281+
}
282+
}
283+
}
284+
}
285+
}
286+
287+
private static void register(ResourceLocation id, Path path) {
288+
CompoundTag structureTag = load(path);
289+
if (structureTag != null) {
290+
ShapeTemplate structure = deserialize(structureTag);
291+
if (structure != null) {
292+
STRUCTURE_TEMPLATES.put(id, structure);
293+
} else {
294+
MI.LOGGER.error("Failed to load structure with id \"{}\"", id);
295+
}
296+
}
297+
}
298+
299+
public static void init() {
300+
if (STRUCTURE_TEMPLATES != null) {
301+
throw new IllegalStateException("Structures have already been loaded");
302+
}
303+
304+
STRUCTURE_TEMPLATES = new HashMap<>();
305+
306+
try {
307+
var structuresPath = structuresPath();
308+
Files.createDirectories(structuresPath);
309+
iterateStructureFiles(structuresPath, MIStructureTemplateManager::register);
310+
311+
for (IModFileInfo modFile : ModList.get().getModFiles()) {
312+
Path modStructuresPath = modFile.getFile().findResource("mi_structures");
313+
iterateStructureFiles(modStructuresPath, (id, path) -> {
314+
if (STRUCTURE_TEMPLATES.containsKey(id)) {
315+
return;
316+
}
317+
register(id, path);
318+
});
319+
}
320+
321+
MI.LOGGER.info("Loaded {} structures.", STRUCTURE_TEMPLATES.size());
322+
} catch (IOException ex) {
323+
throw new RuntimeException(ex);
324+
}
325+
}
326+
239327
private MIStructureTemplateManager() {
240328
}
241329
}

src/main/java/aztech/modern_industrialization/network/structure/StructureSaveControllerPacket.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,9 @@ public void handle(Context ctx) {
6262
var result = MIStructureTemplateManager.fromWorld(id,
6363
level, pos, state.getValue(StructureMultiblockControllerBlock.FACING),
6464
controller.getCasing(), controller.getBounds());
65-
if (result instanceof StructureResult.Success success) {
66-
if (!MIStructureTemplateManager.save(id, success.tag())) {
67-
result = new StructureResult.Unknown();
68-
}
65+
if (result instanceof StructureResult.Success success &&
66+
!MIStructureTemplateManager.save(id, success.tag())) {
67+
result = new StructureResult.Unknown();
6968
}
7069
result.send(player);
7170
}

0 commit comments

Comments
 (0)