Skip to content

Commit c04d863

Browse files
committed
Renamed ExportStructureScreen
Renamed wrong file es_mx.json Added support for importing nbt structures
1 parent 9e9c982 commit c04d863

19 files changed

+358
-27
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
![Icon](src/main/resources/assets/exportable-structures/icon.png?raw=true)
2+
13
# Exportable Structures
24

35
This mod exists for the cases when a custom launcher breaks the structure blocks in Minecraft.
46

57
This mod adds a button **Export** in the **Save** mode of the structure block screen that lets you chose a folder where to export the structure.
68

7-
I may add a feature to import `.nbt` structures in the future.
9+
## Version 1.1_1.20.6
10+
11+
Exportable Structures now allows `.nbt` files to be imported from anywhere in your computer. To make this happen a few elements where moved in the **Load** screen of the structure block. Now to apply the rotation to the structure there is a state button that changes between `0`, `90`, `180` and `270`, replacing the respective buttons from Vanilla.
12+
13+
![Modified Load Screen](media/modified_load_screen.png?raw=true)

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ base {
1010
archivesName = project.archives_base_name
1111
}
1212

13+
loom {
14+
accessWidenerPath = file("src/main/resources/exportable-structures.accesswidener")
15+
}
16+
1317
repositories {
1418
// Add repositories to retrieve artifacts from in here.
1519
// You should only use this when depending on other mods because

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ org.gradle.jvmargs=-Xmx1G
88
loader_version=0.15.11
99

1010
# Mod Properties
11-
mod_version = 1.0-SNAPSHOT
11+
mod_version = 1.1_1.20.6
1212
maven_group = xyz.binarydev.exportablestructures
1313
archives_base_name = exportable-structures
1414

media/modified_load_screen.png

167 KB
Loading

src/main/java/xyz/binarydev/exportablestructures/exportablestructures/FileStructureTemplateManager.java

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
11
package xyz.binarydev.exportablestructures.exportablestructures;
22

3+
import net.minecraft.datafixer.DataFixTypes;
34
import net.minecraft.nbt.NbtCompound;
5+
import net.minecraft.nbt.NbtHelper;
46
import net.minecraft.nbt.NbtIo;
7+
import net.minecraft.nbt.NbtSizeTracker;
58
import net.minecraft.structure.StructureTemplate;
69
import net.minecraft.structure.StructureTemplateManager;
10+
import net.minecraft.util.FixedBufferInputStream;
711
import net.minecraft.util.Identifier;
812

9-
import java.io.File;
10-
import java.io.FileOutputStream;
11-
import java.io.IOException;
12-
import java.io.OutputStream;
13+
import java.io.*;
14+
import java.net.MalformedURLException;
15+
import java.net.URI;
16+
import java.nio.charset.StandardCharsets;
17+
import java.security.MessageDigest;
18+
import java.security.NoSuchAlgorithmException;
19+
import java.util.LinkedHashMap;
1320
import java.util.Optional;
21+
import java.util.UUID;
1422

1523
public class FileStructureTemplateManager {
1624

1725
private final StructureTemplateManager manager;
26+
private final LinkedHashMap<Identifier, File> loadedStructures;
1827

1928
public FileStructureTemplateManager(StructureTemplateManager manager) {
2029
this.manager = manager;
30+
loadedStructures = new LinkedHashMap<>();
2131
}
2232

2333
public boolean saveToFile(File file, Identifier id) throws IOException {
@@ -39,6 +49,7 @@ public boolean saveToFile(File file, Identifier id) throws IOException {
3949
throw ex;
4050
}
4151
stream.close();
52+
loadedStructures.put(id, file);
4253
return true;
4354
} catch (IOException ex) {
4455
return false;
@@ -50,4 +61,61 @@ private File getPath(File parent, Identifier id) {
5061
String name = id.toUnderscoreSeparatedString();
5162
return new File(parent, name + ".nbt");
5263
}
64+
65+
public Optional<StructureTemplate> loadFromFile(File file) {
66+
String md5 = getPathHash(file);
67+
Identifier id = new Identifier("exportable-structures", md5);
68+
loadedStructures.putIfAbsent(id, file);
69+
return loadStructure(id);
70+
}
71+
72+
public Optional<StructureTemplate> loadFromFile(String file) {
73+
return loadFromFile(new File(file));
74+
}
75+
76+
public Optional<StructureTemplate> loadFromFile(URI file) throws MalformedURLException {
77+
return loadFromFile(file.toURL().getFile());
78+
}
79+
80+
private Optional<StructureTemplate> loadStructure(Identifier id) {
81+
if (loadedStructures.containsKey(id)) {
82+
try(FileInputStream stream = new FileInputStream(loadedStructures.get(id))) {
83+
Optional<StructureTemplate> optional;
84+
try (FixedBufferInputStream inputStream = new FixedBufferInputStream(stream)) {
85+
optional = Optional.of(this.readTemplate(inputStream));
86+
}
87+
return optional;
88+
} catch (IOException ex) {
89+
return Optional.empty();
90+
}
91+
}
92+
return Optional.empty();
93+
}
94+
95+
private StructureTemplate readTemplate(InputStream stream) throws IOException {
96+
NbtCompound compound = NbtIo.readCompressed(stream, NbtSizeTracker.ofUnlimitedBytes());
97+
return this.createTemplate(compound);
98+
}
99+
100+
private StructureTemplate createTemplate(NbtCompound nbt) {
101+
StructureTemplate template = new StructureTemplate();
102+
int ver = NbtHelper.getDataVersion(nbt, 500);
103+
template.readNbt(manager.blockLookup, DataFixTypes.STRUCTURE.update(manager.dataFixer, nbt, ver));
104+
return template;
105+
}
106+
107+
private String getPathHash(File file) {
108+
try {
109+
MessageDigest digest = MessageDigest.getInstance("MD5");
110+
digest.update(file.toString().getBytes(StandardCharsets.UTF_8));
111+
byte[] hash = digest.digest();
112+
StringBuilder builder = new StringBuilder();
113+
for (byte b : hash) {
114+
builder.append(b);
115+
}
116+
return builder.toString();
117+
} catch (NoSuchAlgorithmException ex) {
118+
return UUID.randomUUID().toString().replace('-', '_');
119+
}
120+
}
53121
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package xyz.binarydev.exportablestructures.exportablestructures.mixin;
22

33
import net.minecraft.block.entity.StructureBlockBlockEntity;
4+
import net.minecraft.structure.StructureTemplate;
45
import org.spongepowered.asm.mixin.Mixin;
56
import org.spongepowered.asm.mixin.gen.Accessor;
7+
import org.spongepowered.asm.mixin.gen.Invoker;
68

79
@Mixin(StructureBlockBlockEntity.class)
810
public interface StructureBlockBlockEntityAccessor {
911

1012
@Accessor
1113
String getAuthor();
1214

15+
@Invoker("loadStructure")
16+
void invokeLoadStructure(StructureTemplate template);
17+
1318
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package xyz.binarydev.exportablestructures.exportablestructures.mixin;
2+
3+
import net.minecraft.block.entity.StructureBlockBlockEntity;
4+
import net.minecraft.block.enums.StructureBlockMode;
5+
import net.minecraft.server.world.ServerWorld;
6+
import net.minecraft.structure.StructureTemplate;
7+
import net.minecraft.util.Identifier;
8+
import net.minecraft.util.StringHelper;
9+
import net.minecraft.util.math.Vec3i;
10+
import org.jetbrains.annotations.Nullable;
11+
import org.spongepowered.asm.mixin.Mixin;
12+
import org.spongepowered.asm.mixin.Shadow;
13+
import org.spongepowered.asm.mixin.Unique;
14+
import org.spongepowered.asm.mixin.injection.At;
15+
import org.spongepowered.asm.mixin.injection.Inject;
16+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
17+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
18+
import xyz.binarydev.exportablestructures.exportablestructures.FileStructureTemplateManager;
19+
20+
import java.io.File;
21+
import java.io.IOException;
22+
import java.net.MalformedURLException;
23+
import java.net.URI;
24+
25+
@Mixin(StructureBlockBlockEntity.class)
26+
public abstract class StructureBlockBlockEntityMixin {
27+
28+
@Shadow private StructureBlockMode mode;
29+
@Shadow private Vec3i size;
30+
31+
@Shadow protected abstract void loadAndPlaceStructure(ServerWorld world, StructureTemplate template);
32+
33+
@Unique @Nullable
34+
private String templateFile = null;
35+
36+
@Inject(method = "setTemplateName(Ljava/lang/String;)V", at = @At("HEAD"))
37+
public void setTemplateName(String templateName, CallbackInfo info) {
38+
StructureBlockBlockEntity self = (StructureBlockBlockEntity) (Object) this;
39+
if (StringHelper.isEmpty(templateName)) {
40+
self.setTemplateName((Identifier) null);
41+
} else if (templateName.startsWith("file:")) {
42+
templateFile = templateName;
43+
} else {
44+
self.setTemplateName(Identifier.tryParse(templateName));
45+
}
46+
}
47+
48+
@Inject(method = "hasStructureName()Z", at = @At("RETURN"), cancellable = true)
49+
public void hasStructureName(CallbackInfoReturnable<Boolean> info) {
50+
info.setReturnValue(info.getReturnValue() || templateFile != null);
51+
}
52+
53+
@Inject(method = "getTemplateName()Ljava/lang/String;", at = @At("RETURN"), cancellable = true)
54+
public void getTemplateName(CallbackInfoReturnable<String> info) {
55+
if (info.getReturnValue() == null || info.getReturnValue().isEmpty()) {
56+
if (templateFile != null) {
57+
info.setReturnValue(templateFile);
58+
}
59+
}
60+
}
61+
62+
@Inject(method = "isStructureAvailable()Z", at = @At("HEAD"), cancellable = true)
63+
public void isStructureAvailable(CallbackInfoReturnable<Boolean> info) {
64+
StructureBlockBlockEntity self = (StructureBlockBlockEntity) (Object) this;
65+
if (self.getMode() != StructureBlockMode.LOAD || (self.getWorld() != null && !self.getWorld().isClient)) {
66+
if (this.templateFile != null) {
67+
try {
68+
File f = new File(URI.create(templateFile).toURL().getFile());
69+
info.setReturnValue(f.exists() && f.isFile());
70+
} catch (IOException ex) {
71+
info.setReturnValue(false);
72+
}
73+
}
74+
}
75+
}
76+
77+
@Inject(method = "loadAndTryPlaceStructure(Lnet/minecraft/server/world/ServerWorld;)Z", at = @At("RETURN"), cancellable = true)
78+
public void loadAndTryPlaceStructure(ServerWorld world, CallbackInfoReturnable<Boolean> info) {
79+
if (!info.getReturnValue()) {
80+
if (this.mode == StructureBlockMode.LOAD && this.templateFile != null) {
81+
try {
82+
FileStructureTemplateManager manager = new FileStructureTemplateManager(world.getStructureTemplateManager());
83+
StructureTemplate structure = manager.loadFromFile(URI.create(this.templateFile)).orElse(null);
84+
if (structure == null) {
85+
info.setReturnValue(false);
86+
return;
87+
}
88+
if (structure.getSize().equals(size)) {
89+
loadAndPlaceStructure(world, structure);
90+
info.setReturnValue(true);
91+
return;
92+
}
93+
StructureBlockBlockEntityAccessor accessor = (StructureBlockBlockEntityAccessor) this;
94+
accessor.invokeLoadStructure(structure);
95+
} catch (MalformedURLException e) {
96+
throw new RuntimeException(e);
97+
}
98+
}
99+
}
100+
}
101+
102+
}

src/main/java/xyz/binarydev/exportablestructures/exportablestructures/mixin/StructureBlockScreenAccessor.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
import net.minecraft.block.entity.StructureBlockBlockEntity;
44
import net.minecraft.client.gui.screen.ingame.StructureBlockScreen;
5+
import net.minecraft.client.gui.widget.ButtonWidget;
6+
import net.minecraft.client.gui.widget.CyclingButtonWidget;
7+
import net.minecraft.client.gui.widget.TextFieldWidget;
8+
import net.minecraft.util.BlockMirror;
59
import org.spongepowered.asm.mixin.Mixin;
610
import org.spongepowered.asm.mixin.gen.Accessor;
711

@@ -11,4 +15,22 @@ public interface StructureBlockScreenAccessor {
1115
@Accessor
1216
StructureBlockBlockEntity getStructureBlock();
1317

18+
@Accessor
19+
CyclingButtonWidget<BlockMirror> getButtonMirror();
20+
21+
@Accessor
22+
ButtonWidget getButtonRotate0();
23+
24+
@Accessor
25+
ButtonWidget getButtonRotate90();
26+
27+
@Accessor
28+
ButtonWidget getButtonRotate180();
29+
30+
@Accessor
31+
ButtonWidget getButtonRotate270();
32+
33+
@Accessor
34+
TextFieldWidget getInputName();
35+
1436
}
Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,92 @@
11
package xyz.binarydev.exportablestructures.exportablestructures.mixin;
22

3+
import net.minecraft.block.entity.StructureBlockBlockEntity;
34
import net.minecraft.block.enums.StructureBlockMode;
45
import net.minecraft.client.MinecraftClient;
56
import net.minecraft.client.gui.screen.ingame.StructureBlockScreen;
67
import net.minecraft.client.gui.widget.ButtonWidget;
8+
import net.minecraft.client.gui.widget.CyclingButtonWidget;
79
import net.minecraft.text.Text;
10+
import net.minecraft.util.BlockRotation;
11+
import org.spongepowered.asm.mixin.Final;
812
import org.spongepowered.asm.mixin.Mixin;
13+
import org.spongepowered.asm.mixin.Shadow;
914
import org.spongepowered.asm.mixin.Unique;
1015
import org.spongepowered.asm.mixin.injection.At;
1116
import org.spongepowered.asm.mixin.injection.Inject;
1217
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
13-
import xyz.binarydev.exportablestructures.exportablestructures.screen.ExportStructureScreen;
18+
import xyz.binarydev.exportablestructures.exportablestructures.screen.ImportExportStructureScreen;
1419

1520
@Mixin(StructureBlockScreen.class)
1621
public class StructureBlockScreenMixin {
1722

23+
@Shadow @Final private StructureBlockBlockEntity structureBlock;
1824
@Unique
1925
private ButtonWidget exportButton;
26+
@Unique
27+
private ButtonWidget importButton;
28+
@Unique
29+
private CyclingButtonWidget<BlockRotation> rotationButton;
2030

2131
@Inject(method = "init()V", at = @At("HEAD"))
2232
private void init(CallbackInfo info) {
2333
StructureBlockScreen self = (StructureBlockScreen) ((Object) this);
2434
StructureBlockScreenAccessor accessor = (StructureBlockScreenAccessor) self;
2535
this.exportButton = ((ScreenInvoker) self).invokeAddDrawableChild(ButtonWidget.builder(Text.translatable("exportable_structures.button.export"), (button) -> {
2636
if (accessor.getStructureBlock().getMode() == StructureBlockMode.SAVE) {
27-
MinecraftClient.getInstance().setScreen(new ExportStructureScreen(self, accessor.getStructureBlock()));
37+
MinecraftClient.getInstance().setScreen(new ImportExportStructureScreen(self, accessor.getStructureBlock(), ImportExportStructureScreen.MODE_SAVE).hideFiles());
38+
}
39+
}).dimensions(self.width / 2 - 1 - 40 - 1 - 40 - 20, 185, 50, 20).build());
40+
this.importButton = ((ScreenInvoker) self).invokeAddDrawableChild(ButtonWidget.builder(Text.translatable("exportable_structures.button.import"), (button) -> {
41+
if (accessor.getStructureBlock().getMode() == StructureBlockMode.LOAD) {
42+
MinecraftClient.getInstance().setScreen(new ImportExportStructureScreen(self, accessor.getStructureBlock(), ImportExportStructureScreen.MODE_LOAD).showFiles("nbt").setOnFileSelected((screen, file) -> {
43+
screen.close();
44+
accessor.getInputName().setText(file.toURI().toString());
45+
}));
2846
}
2947
}).dimensions(self.width / 2 - 1 - 40 - 1 - 40 - 20, 185, 50, 20).build());
48+
this.rotationButton = ((ScreenInvoker) self).invokeAddDrawableChild(CyclingButtonWidget.<BlockRotation>builder((value) -> {
49+
switch (value) {
50+
case CLOCKWISE_90 -> {
51+
return Text.literal("90");
52+
}
53+
case CLOCKWISE_180 -> {
54+
return Text.literal("180");
55+
}
56+
case COUNTERCLOCKWISE_90 -> {
57+
return Text.literal("270");
58+
}
59+
default -> {
60+
return Text.literal("0");
61+
}
62+
}
63+
}).values(BlockRotation.values())
64+
.omitKeyText()
65+
.initially(structureBlock.getRotation())
66+
.build(self.width / 2 + 1 + 40 + 1 + 20, 185, 40, 20, Text.literal("Rotation"), (button, value) -> structureBlock.setRotation(value)));
3067
}
3168

69+
@Inject(method = "init()V", at = @At("TAIL"))
70+
private void initTail(CallbackInfo info) {
71+
StructureBlockScreen self = (StructureBlockScreen) ((Object) this);
72+
StructureBlockScreenAccessor accessor = (StructureBlockScreenAccessor) self;
73+
accessor.getButtonMirror().setDimensionsAndPosition(40, 20, self.width / 2 + 1 + 20, 185);
74+
accessor.getInputName().setMaxLength(4096);
75+
}
76+
77+
3278
@Inject(method = "updateWidgets(Lnet/minecraft/block/enums/StructureBlockMode;)V", at = @At("TAIL"))
3379
private void updateWidgets(StructureBlockMode mode, CallbackInfo info) {
80+
StructureBlockScreen self = (StructureBlockScreen) ((Object) this);
81+
StructureBlockScreenAccessor accessor = (StructureBlockScreenAccessor) self;
3482
exportButton.visible = (mode == StructureBlockMode.SAVE);
83+
importButton.visible = (mode == StructureBlockMode.LOAD);
84+
rotationButton.visible = (mode == StructureBlockMode.LOAD);
85+
accessor.getButtonRotate0().visible = false;
86+
accessor.getButtonRotate90().visible = false;
87+
accessor.getButtonRotate180().visible = false;
88+
accessor.getButtonRotate270().visible = false;
89+
3590
}
3691

3792
}

0 commit comments

Comments
 (0)