├── .idea ├── .name ├── vcs.xml ├── .gitignore ├── encodings.xml ├── modules.xml ├── hotswap_agent.xml ├── misc.xml ├── inspectionProfiles │ └── Project_Default.xml ├── compiler.xml ├── jarRepositories.xml └── uiDesigner.xml ├── src ├── META-INF │ └── MANIFEST.MF └── main │ ├── resources │ ├── example.png │ ├── configexample.png │ ├── net │ │ └── ryanland │ │ │ └── dfschematics │ │ │ ├── logo.png │ │ │ └── fxml │ │ │ ├── update.fxml │ │ │ ├── main.fxml │ │ │ └── configuration.fxml │ └── project.properties │ └── java │ ├── net │ └── ryanland │ │ └── dfschematics │ │ ├── Main.java │ │ ├── schematic │ │ ├── special │ │ │ ├── Head.java │ │ │ ├── TrackedBlocks.java │ │ │ ├── TrackedBlock.java │ │ │ └── Sign.java │ │ ├── StructureContainer.java │ │ ├── StructurePart.java │ │ ├── TemplateFactory.java │ │ ├── DFSchematic.java │ │ └── LitematicConverter.java │ │ ├── df │ │ ├── code │ │ │ ├── SetVariable.java │ │ │ ├── CodeBlock.java │ │ │ ├── Tag.java │ │ │ ├── Function.java │ │ │ ├── SetVariableCreateList.java │ │ │ ├── SetVariableAppendValue.java │ │ │ ├── ChestCodeBlock.java │ │ │ └── CodeLine.java │ │ ├── value │ │ │ ├── Scope.java │ │ │ ├── Value.java │ │ │ ├── Item.java │ │ │ ├── Str.java │ │ │ ├── Num.java │ │ │ ├── Vector.java │ │ │ ├── Variable.java │ │ │ └── Location.java │ │ └── api │ │ │ ├── WSClient.java │ │ │ ├── API.java │ │ │ ├── RecodeItemAPI.java │ │ │ └── CodeClientAPI.java │ │ ├── DFSchematics.java │ │ ├── fxml │ │ ├── UpdateCheckController.java │ │ ├── ConfigController.java │ │ └── MainController.java │ │ └── UpdateChecker.java │ └── module-info.java ├── .gitignore ├── README.md └── pom.xml /.idea/.name: -------------------------------------------------------------------------------- 1 | DFSchematics -------------------------------------------------------------------------------- /src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: net.ryanland.dfschematics.DFSchematics 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | out 3 | DFSchematics.exe 4 | DFSchematics.iml 5 | dependency-reduced-pom.xml 6 | launch4j.log -------------------------------------------------------------------------------- /src/main/resources/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RyanLandDev/DFSchematics/HEAD/src/main/resources/example.png -------------------------------------------------------------------------------- /src/main/resources/configexample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RyanLandDev/DFSchematics/HEAD/src/main/resources/configexample.png -------------------------------------------------------------------------------- /src/main/resources/net/ryanland/dfschematics/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RyanLandDev/DFSchematics/HEAD/src/main/resources/net/ryanland/dfschematics/logo.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/Main.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics; 2 | 3 | public class Main { 4 | 5 | public static void main(String[] args) { 6 | DFSchematics.main(args); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/schematic/special/Head.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.schematic.special; 2 | 3 | import net.sandrohc.schematic4j.schematic.types.SchematicBlockPos; 4 | 5 | public record Head(SchematicBlockPos pos, String texture) { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/code/SetVariable.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.code; 2 | 3 | public abstract class SetVariable implements ChestCodeBlock { 4 | 5 | @Override 6 | public String getBlock() { 7 | return "set_var"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | # Other 10 | discord.xml 11 | dbnavigator.xml 12 | runConfigurations.xml -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/hotswap_agent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/value/Scope.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.value; 2 | 3 | public enum Scope { 4 | 5 | LOCAL("local"), 6 | GAME("unsaved"), 7 | SAVED("saved"), 8 | LINE("line") 9 | ; 10 | 11 | private String id; 12 | 13 | Scope(String id) { 14 | this.id = id; 15 | } 16 | 17 | public String getId() { 18 | return id; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/code/CodeBlock.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.code; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public interface CodeBlock { 6 | 7 | String getBlock(); 8 | 9 | String getAction(); 10 | 11 | int getWeight(); 12 | 13 | default JsonObject toJson() { 14 | JsonObject json = new JsonObject(); 15 | json.addProperty("id", "block"); 16 | json.addProperty("block", getBlock()); 17 | json.addProperty("action", getAction()); 18 | return json; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/value/Value.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.value; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public interface Value { 6 | 7 | String getType(); 8 | 9 | JsonObject getData(); 10 | 11 | default JsonObject toJson(int slot) { 12 | JsonObject data = new JsonObject(); 13 | data.addProperty("id", getType()); 14 | data.add("data", getData()); 15 | 16 | JsonObject json = new JsonObject(); 17 | json.add("item", data); 18 | json.addProperty("slot", slot); 19 | return json; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/value/Item.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.value; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public interface Item extends Value { 6 | 7 | @Override 8 | default String getType() { 9 | return "item"; 10 | } 11 | 12 | @Override 13 | default JsonObject getData() { 14 | JsonObject json = new JsonObject(); 15 | json.addProperty("item", getItemNBT()); 16 | return json; 17 | } 18 | 19 | /** 20 | * Returns a JSON String representing the item's NBT data. 21 | */ 22 | String getItemNBT(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/value/Str.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.value; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public class Str implements Value { 6 | 7 | private final String value; 8 | 9 | public Str(String value) { 10 | this.value = value; 11 | } 12 | 13 | public String getValue() { 14 | return value; 15 | } 16 | 17 | @Override 18 | public String getType() { 19 | return "txt"; 20 | } 21 | 22 | @Override 23 | public JsonObject getData() { 24 | JsonObject json = new JsonObject(); 25 | json.addProperty("name", value); 26 | return json; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/api/WSClient.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.api; 2 | 3 | import org.java_websocket.client.WebSocketClient; 4 | import org.java_websocket.handshake.ServerHandshake; 5 | 6 | import java.net.URI; 7 | 8 | //Todo better close/error handling? 9 | public class WSClient extends WebSocketClient { 10 | 11 | public WSClient(URI serverUri) { 12 | super(serverUri); 13 | } 14 | 15 | @Override 16 | public void onMessage(String s) {} 17 | 18 | @Override 19 | public void onOpen(ServerHandshake serverHandshake) {} 20 | 21 | @Override 22 | public void onClose(int i, String s, boolean b) {} 23 | 24 | @Override 25 | public void onError(Exception e) {} 26 | } -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module net.ryanland.dfschematics { 2 | requires javafx.controls; 3 | requires javafx.fxml; 4 | requires com.google.gson; 5 | requires org.slf4j; 6 | requires org.java_websocket; 7 | requires schematic4j; 8 | requires net.kyori.adventure; 9 | requires net.kyori.adventure.text.serializer.gson; 10 | requires net.kyori.adventure.text.serializer.legacy; 11 | requires org.jetbrains.annotations; 12 | requires java.logging; 13 | requires jo.nbt; 14 | requires static lombok; 15 | 16 | opens net.ryanland.dfschematics to javafx.fxml; 17 | exports net.ryanland.dfschematics; 18 | exports net.ryanland.dfschematics.fxml; 19 | opens net.ryanland.dfschematics.fxml to javafx.fxml; 20 | } -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/value/Num.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.value; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public class Num implements Value { 6 | 7 | private final String value; 8 | 9 | public Num(String value) { 10 | this.value = value; 11 | } 12 | 13 | public Num(float value) { 14 | this.value = String.valueOf(value).replaceAll("\\.0+$", "");// optionally remove .00 at the end 15 | } 16 | 17 | public String getValue() { 18 | return value; 19 | } 20 | 21 | @Override 22 | public String getType() { 23 | return "num"; 24 | } 25 | 26 | @Override 27 | public JsonObject getData() { 28 | JsonObject json = new JsonObject(); 29 | json.addProperty("name", value); 30 | return json; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/code/Tag.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.code; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public record Tag(String name, String option) { 6 | 7 | public JsonObject toJson(String block, String action, int slot) { 8 | if (block.equals("func")) action = "dynamic"; 9 | 10 | JsonObject data = new JsonObject(); 11 | data.addProperty("option", option); 12 | data.addProperty("tag", name); 13 | data.addProperty("action", action); 14 | data.addProperty("block", block); 15 | 16 | JsonObject item = new JsonObject(); 17 | item.addProperty("id", "bl_tag"); 18 | item.add("data", data); 19 | 20 | JsonObject json = new JsonObject(); 21 | json.add("item", item); 22 | json.addProperty("slot", slot); 23 | return json; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/schematic/special/TrackedBlocks.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.schematic.special; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class TrackedBlocks { 7 | 8 | private final List blocks = new ArrayList<>(); 9 | private double[] offset = new double[3]; 10 | 11 | public List getBlocks() { 12 | return blocks; 13 | } 14 | 15 | public void add(TrackedBlock block) { 16 | blocks.add(block); 17 | } 18 | 19 | public void remove(TrackedBlock block) { 20 | blocks.remove(block); 21 | } 22 | 23 | public void reset() { 24 | for (TrackedBlock block : blocks) block.getLocations().clear(); 25 | } 26 | 27 | public double[] getOffset() { 28 | return offset; 29 | } 30 | 31 | public void setOffset(int index, double offset) { 32 | this.offset[index] = offset; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/value/Vector.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.value; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public class Vector implements Value { 6 | 7 | private final float x; 8 | private final float y; 9 | private final float z; 10 | 11 | public Vector(float x, float y, float z) { 12 | this.x = x; 13 | this.y = y; 14 | this.z = z; 15 | } 16 | 17 | public float getX() { 18 | return x; 19 | } 20 | 21 | public float getY() { 22 | return y; 23 | } 24 | 25 | public float getZ() { 26 | return z; 27 | } 28 | 29 | @Override 30 | public String getType() { 31 | return "vec"; 32 | } 33 | 34 | @Override 35 | public JsonObject getData() { 36 | JsonObject json = new JsonObject(); 37 | json.addProperty("x", x); 38 | json.addProperty("y", y); 39 | json.addProperty("z", z); 40 | return json; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/code/Function.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.code; 2 | 3 | import com.google.gson.JsonObject; 4 | import net.ryanland.dfschematics.df.value.Value; 5 | 6 | import java.util.List; 7 | 8 | public class Function implements ChestCodeBlock { 9 | 10 | private final String name; 11 | 12 | public Function(String name) { 13 | this.name = name; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | @Override 21 | public List getParameters() { 22 | return null; 23 | } 24 | 25 | @Override 26 | public List getTags() { 27 | return List.of(new Tag("Is Hidden", "False")); 28 | } 29 | 30 | @Override 31 | public String getBlock() { 32 | return "func"; 33 | } 34 | 35 | @Override 36 | public String getAction() { 37 | return null; 38 | } 39 | 40 | @Override 41 | public int getWeight() { 42 | return 0; 43 | } 44 | 45 | @Override 46 | public JsonObject toJson() { 47 | JsonObject json = ChestCodeBlock.super.toJson(); 48 | json.remove("action"); 49 | json.addProperty("data", getName()); 50 | return json; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/code/SetVariableCreateList.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.code; 2 | 3 | import net.ryanland.dfschematics.df.value.Value; 4 | import net.ryanland.dfschematics.df.value.Variable; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class SetVariableCreateList extends SetVariable { 10 | 11 | private final Variable variable; 12 | private final List values; 13 | 14 | public SetVariableCreateList(Variable variable, List values) { 15 | this.variable = variable; 16 | this.values = (List) values; 17 | } 18 | 19 | @Override 20 | public List getParameters() { 21 | List parameters = new ArrayList<>(); 22 | parameters.add(variable); 23 | parameters.addAll(values); 24 | return parameters; 25 | } 26 | 27 | @Override 28 | public List getTags() { 29 | return null; 30 | } 31 | 32 | @Override 33 | public String getAction() { 34 | return "CreateList"; 35 | } 36 | 37 | @Override 38 | public int getWeight() { 39 | if (getParameters().get(1).getType().equals("item")) return 1;// block entities 40 | return getParameters().size(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/code/SetVariableAppendValue.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.code; 2 | 3 | import net.ryanland.dfschematics.df.value.Value; 4 | import net.ryanland.dfschematics.df.value.Variable; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class SetVariableAppendValue extends SetVariable { 10 | 11 | private final Variable variable; 12 | private final List values; 13 | 14 | public SetVariableAppendValue(Variable variable, List values) { 15 | this.variable = variable; 16 | this.values = (List) values; 17 | } 18 | 19 | @Override 20 | public List getParameters() { 21 | List parameters = new ArrayList<>(); 22 | parameters.add(variable); 23 | parameters.addAll(values); 24 | return parameters; 25 | } 26 | 27 | @Override 28 | public List getTags() { 29 | return null; 30 | } 31 | 32 | @Override 33 | public String getAction() { 34 | return "AppendValue"; 35 | } 36 | 37 | @Override 38 | public int getWeight() { 39 | if (getParameters().get(1).getType().equals("item")) return 1;// block entities 40 | return getParameters().size(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/code/ChestCodeBlock.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.code; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import net.ryanland.dfschematics.df.value.Value; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Represents a code block with a chest (can have parameters + tags) 11 | */ 12 | public interface ChestCodeBlock extends CodeBlock { 13 | 14 | List getParameters(); 15 | 16 | List getTags(); 17 | 18 | @Override 19 | default JsonObject toJson() { 20 | JsonObject json = CodeBlock.super.toJson(); 21 | JsonObject args = new JsonObject(); 22 | JsonArray items = new JsonArray(); 23 | 24 | if (getParameters() != null) { 25 | int i = 0; 26 | for (Value parameter : getParameters()) { 27 | items.add(parameter.toJson(i)); 28 | i++; 29 | } 30 | } 31 | if (getTags() != null) { 32 | int i = 26; 33 | for (Tag tag : getTags()) { 34 | items.add(tag.toJson(getBlock(), getAction(), i)); 35 | i--; 36 | } 37 | } 38 | 39 | args.add("items", items); 40 | json.add("args", args); 41 | return json; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/value/Variable.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.value; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public class Variable/**/ implements Value { 6 | 7 | private final String name; 8 | private final Scope scope; 9 | 10 | /** 11 | * Creates a variable with {@link Scope#LOCAL} 12 | * @param name The variable name 13 | */ 14 | public Variable(String name) { 15 | this(name, Scope.LOCAL); 16 | } 17 | 18 | /** 19 | * Creates a variable 20 | * @param name The variable name 21 | * @param scope The variable scope 22 | */ 23 | public Variable(String name, Scope scope) { 24 | this.name = name; 25 | this.scope = scope; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public Scope getScope() { 33 | return scope; 34 | } 35 | 36 | @Override 37 | public String getType() { 38 | return "var"; 39 | } 40 | 41 | @Override 42 | public JsonObject getData() { 43 | JsonObject json = new JsonObject(); 44 | json.addProperty("name", name); 45 | json.addProperty("scope", scope.getId()); 46 | return json; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object obj) { 51 | return getName().equals(((Variable) obj).getName()) && 52 | getScope().equals(((Variable) obj).getScope()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/resources/net/ryanland/dfschematics/fxml/update.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 | 28 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/schematic/special/TrackedBlock.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.schematic.special; 2 | 3 | import javafx.scene.image.ImageView; 4 | import net.ryanland.dfschematics.df.value.Location; 5 | import net.ryanland.dfschematics.df.value.Variable; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public final class TrackedBlock { 11 | 12 | private final Variable variable; 13 | private final String material; 14 | private final boolean remove; 15 | private final ImageView icon; 16 | private final List locations = new ArrayList<>(); 17 | 18 | public TrackedBlock(Variable variable, String material, boolean remove) { 19 | this.variable = variable; 20 | this.material = material; 21 | this.remove = remove; 22 | this.icon = null;//new ImageView("https://mcapi.marveldc.me/item/"+material+"?width=20&height=20"); 23 | } 24 | 25 | public Variable getVariable() { 26 | return variable; 27 | } 28 | 29 | public String getMaterial() { 30 | return material; 31 | } 32 | 33 | public boolean isRemoved() { 34 | return remove; 35 | } 36 | 37 | public ImageView getIcon() { 38 | return icon; 39 | } 40 | 41 | public List getLocations() { 42 | return locations; 43 | } 44 | 45 | public void addLocation(Location location) { 46 | locations.add(location); 47 | } 48 | 49 | public int getOccurrences() { 50 | return locations.size(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/schematic/StructureContainer.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.schematic; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class StructureContainer { 7 | 8 | private final List palette = new ArrayList<>(); 9 | private final List parts = new ArrayList<>(); 10 | 11 | public List getPalette() { 12 | return palette; 13 | } 14 | 15 | public void addToPalette(String material) { 16 | material = material.replace("minecraft:", ""); 17 | if (!material.equals("air") && !palette.contains(material)) palette.add(material); 18 | } 19 | 20 | public int indexOf(String material) { 21 | if (material.equals("air")) return -1; 22 | return palette.indexOf(material); 23 | } 24 | 25 | public List getParts() { 26 | return parts; 27 | } 28 | 29 | private StructurePart partInProgress = null; 30 | 31 | public void addBlock(String material) { 32 | material = material.replace("minecraft:", ""); 33 | 34 | if (partInProgress != null && partInProgress.getBlockIndex() != indexOf(material)) { 35 | finalizePart(); 36 | } 37 | if (partInProgress == null) { 38 | partInProgress = new StructurePart(indexOf(material), 0); 39 | } 40 | 41 | partInProgress.increaseAmount(); 42 | } 43 | 44 | public void finalizePart() { 45 | addPart(partInProgress); 46 | partInProgress = null; 47 | } 48 | 49 | public void addPart(StructurePart part) { 50 | parts.add(part); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/schematic/StructurePart.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.schematic; 2 | 3 | public final class StructurePart { 4 | 5 | private final int blockIndex;//-1 equals air 6 | private int amount; 7 | 8 | public StructurePart(int blockIndex, int amount) { 9 | this.blockIndex = blockIndex; 10 | this.amount = amount; 11 | } 12 | 13 | public int getBlockIndex() { 14 | return blockIndex; 15 | } 16 | 17 | public int getAmount() { 18 | return amount; 19 | } 20 | 21 | public void setAmount(int amount) { 22 | this.amount = amount; 23 | } 24 | 25 | public void increaseAmount() { 26 | amount++; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return "StructurePart[" + 32 | "blockIndex=" + blockIndex + ", " + 33 | "amount=" + amount + ']'; 34 | } 35 | 36 | private static final String[] compressChars; 37 | static { 38 | compressChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#|%&+/<>?@()-=*,:;[]^_`{}~".split(""); 39 | } 40 | 41 | public String getText() { 42 | if (blockIndex == -1) return String.valueOf(amount);//air 43 | 44 | int length = compressChars.length; // 80, index 0-79 45 | 46 | int char1Index = (int) Math.floor(blockIndex / length) - 1; 47 | String char1; 48 | if (char1Index == -1) char1 = ""; 49 | else char1 = compressChars[char1Index]; 50 | 51 | String char2 = compressChars[blockIndex % length]; 52 | 53 | return char1 + char2 + (amount == 1 ? "" : amount); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/value/Location.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.value; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public class Location implements Value { 6 | 7 | private final float x; 8 | private final float y; 9 | private final float z; 10 | private final float pitch; 11 | private final float yaw; 12 | 13 | public Location(float x, float y, float z) { 14 | this(x, y, z, 0, 0); 15 | } 16 | 17 | public Location(float x, float y, float z, float pitch, float yaw) { 18 | this.x = x; 19 | this.y = y; 20 | this.z = z; 21 | this.pitch = pitch; 22 | this.yaw = yaw; 23 | } 24 | 25 | public float getX() { 26 | return x; 27 | } 28 | 29 | public float getY() { 30 | return y; 31 | } 32 | 33 | public float getZ() { 34 | return z; 35 | } 36 | 37 | public float getPitch() { 38 | return pitch; 39 | } 40 | 41 | public float getYaw() { 42 | return yaw; 43 | } 44 | 45 | public Location add(Location location) { 46 | return new Location(x+location.x, y+location.y, z+location.z, pitch+location.pitch, yaw+location.yaw); 47 | } 48 | 49 | @Override 50 | public String getType() { 51 | return "loc"; 52 | } 53 | 54 | @Override 55 | public JsonObject getData() { 56 | JsonObject loc = new JsonObject(); 57 | loc.addProperty("x", x); 58 | loc.addProperty("y", y); 59 | loc.addProperty("z", z); 60 | loc.addProperty("pitch", pitch); 61 | loc.addProperty("yaw", yaw); 62 | 63 | JsonObject json = new JsonObject(); 64 | json.addProperty("isBlock", false);//what is this for??? 65 | json.add("loc", loc); 66 | return json; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/DFSchematics.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics; 2 | 3 | import javafx.application.Application; 4 | import javafx.application.HostServices; 5 | import javafx.fxml.FXMLLoader; 6 | import javafx.scene.Scene; 7 | import javafx.scene.image.Image; 8 | import javafx.stage.Stage; 9 | import net.ryanland.dfschematics.df.api.API; 10 | 11 | import java.io.IOException; 12 | import java.util.Properties; 13 | 14 | public class DFSchematics extends Application { 15 | 16 | public static Stage stage; 17 | public static HostServices hostServices; 18 | public static String version; 19 | public static String builderTemplate; 20 | 21 | @Override 22 | public void start(Stage stage) throws IOException { 23 | fetchProperties(); 24 | 25 | FXMLLoader fxmlLoader = new FXMLLoader(DFSchematics.class.getResource("fxml/main.fxml")); 26 | Scene scene = new Scene(fxmlLoader.load(), 600, 400); 27 | //setUserAgentStylesheet(new PrimerDark().getUserAgentStylesheet()); 28 | stage.setTitle("DFSchematics"); 29 | stage.setScene(scene); 30 | stage.getIcons().add(new Image(String.valueOf(DFSchematics.class.getResource("logo.png")))); 31 | stage.setResizable(false); 32 | stage.show(); 33 | 34 | DFSchematics.stage = stage; 35 | DFSchematics.hostServices = getHostServices(); 36 | API.attemptConnections(); 37 | } 38 | 39 | private void fetchProperties() throws IOException { 40 | Properties properties = new Properties(); 41 | properties.load(this.getClass().getClassLoader().getResourceAsStream("project.properties")); 42 | version = properties.getProperty("version"); 43 | builderTemplate = properties.getProperty("template"); 44 | } 45 | 46 | public static void main(String[] args) { 47 | launch(); 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/api/API.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.api; 2 | 3 | import net.ryanland.dfschematics.df.code.CodeLine; 4 | import net.ryanland.dfschematics.fxml.MainController; 5 | 6 | import java.util.List; 7 | 8 | public class API { 9 | 10 | public static void attemptConnections() { 11 | RecodeItemAPI.attemptConnection(); 12 | CodeClientAPI.attemptConnection(); 13 | if (RecodeItemAPI.connected && CodeClientAPI.connected) { 14 | MainController.instance.retryButton.setDisable(true); 15 | } 16 | if (RecodeItemAPI.connected || CodeClientAPI.connected) { 17 | if (MainController.selectedFile != null) MainController.instance.sendTemplates.setDisable(false); 18 | MainController.instance.sendBuilder.setDisable(false); 19 | } 20 | if (CodeClientAPI.connected && MainController.selectedFile != null) { 21 | MainController.instance.placeTemplates.setDisable(false); 22 | } 23 | } 24 | 25 | public static void sendTemplates(List codeLines, String name) { 26 | if (RecodeItemAPI.connected) { 27 | RecodeItemAPI.sendTemplates(codeLines, name); 28 | } else if (CodeClientAPI.connected) { 29 | CodeClientAPI.sendTemplates(codeLines, name); 30 | } else { 31 | throw new IllegalStateException("No connections available"); 32 | } 33 | } 34 | 35 | public static void sendRawTemplates(List codeLines, String name) { 36 | if (RecodeItemAPI.connected) { 37 | RecodeItemAPI.sendRawTemplates(codeLines, name); 38 | } else if (CodeClientAPI.connected) { 39 | CodeClientAPI.sendRawTemplates(codeLines, name); 40 | } else { 41 | throw new IllegalStateException("No connections available"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/api/RecodeItemAPI.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.api; 2 | 3 | import com.google.gson.JsonObject; 4 | import lombok.SneakyThrows; 5 | import net.ryanland.dfschematics.df.code.CodeLine; 6 | import net.ryanland.dfschematics.fxml.MainController; 7 | 8 | import java.net.URI; 9 | import java.util.List; 10 | 11 | public class RecodeItemAPI { 12 | 13 | private static WSClient socket; 14 | public static boolean connected = false; 15 | 16 | @SneakyThrows 17 | public static void attemptConnection() { 18 | if (connected) return; 19 | 20 | socket = new WSClient(new URI("ws://localhost:31371")); // recode API endpoint 21 | if (!socket.connectBlocking()) { 22 | // Failed connecting 23 | MainController.instance.setRecodeStatus("Failed connecting to recode", false); 24 | return; 25 | } 26 | // connected 27 | MainController.instance.setRecodeStatus("Connected to recode", true); 28 | connected = true; 29 | } 30 | 31 | @SneakyThrows 32 | public static void sendTemplates(List codeLines, String name) { 33 | sendRawTemplates(codeLines.stream().map(CodeLine::toCompressedJson).toList(), name); 34 | } 35 | 36 | @SneakyThrows 37 | public static void sendRawTemplates(List codeLines, String name) { 38 | int i = 0; 39 | for (String codeLine : codeLines) { 40 | if (i != 0) Thread.sleep(400);//delay necessary to send multiple items 41 | i++; 42 | JsonObject json = new JsonObject(); 43 | json.addProperty("type", "template"); 44 | JsonObject data = new JsonObject(); 45 | data.addProperty("data", codeLine); 46 | data.addProperty("name", name + (codeLines.size() > 1 ? " "+i+"/"+codeLines.size() : "")); 47 | json.addProperty("data", data.toString()); 48 | json.addProperty("source", "DFSchematics"); 49 | 50 | socket.send(json + "\n"); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/fxml/UpdateCheckController.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.fxml; 2 | 3 | import javafx.application.Platform; 4 | import javafx.fxml.FXML; 5 | import javafx.fxml.FXMLLoader; 6 | import javafx.fxml.Initializable; 7 | import javafx.scene.Parent; 8 | import javafx.scene.Scene; 9 | import javafx.scene.control.Label; 10 | import javafx.scene.image.Image; 11 | import javafx.stage.Modality; 12 | import javafx.stage.Stage; 13 | import lombok.SneakyThrows; 14 | import net.ryanland.dfschematics.DFSchematics; 15 | import net.ryanland.dfschematics.UpdateChecker; 16 | 17 | import java.net.URL; 18 | import java.util.ResourceBundle; 19 | 20 | public class UpdateCheckController implements Initializable { 21 | 22 | public static UpdateChecker checker = new UpdateChecker("RyanLandDev", "DFSchematics", DFSchematics.version); 23 | 24 | @FXML private Label desc; 25 | 26 | @Override 27 | public void initialize(URL location, ResourceBundle resources) { 28 | desc.setText("There is a new version available (v%s).\nYou are currently using v%s." 29 | .formatted(checker.getLatestVersion(), checker.getCurrentVersion())); 30 | } 31 | 32 | @FXML 33 | void viewRelease() { 34 | DFSchematics.hostServices.showDocument("https://github.com/RyanLandDev/DFSchematics/releases"); 35 | } 36 | 37 | public static void checkForUpdates() { 38 | Platform.runLater(() -> { 39 | checker.check(); 40 | if (checker.isUpdateAvailable()) { 41 | showUpdateAvailable(); 42 | } 43 | }); 44 | } 45 | 46 | @SneakyThrows 47 | private static void showUpdateAvailable() { 48 | FXMLLoader loader = new FXMLLoader(UpdateCheckController.class.getResource("update.fxml")); 49 | Parent root = loader.load(); 50 | 51 | Stage stage = new Stage(); 52 | stage.setTitle("Update Available"); 53 | Scene scene = new Scene(root); 54 | stage.setScene(scene); 55 | stage.getIcons().add(new Image(String.valueOf(DFSchematics.class.getResource("logo.png")))); 56 | stage.setResizable(false); 57 | stage.initModality(Modality.WINDOW_MODAL); 58 | stage.initOwner(DFSchematics.stage.getScene().getWindow()); 59 | stage.setOnCloseRequest(evt -> MainController.instance.disableRoot(false)); 60 | MainController.instance.disableRoot(true); 61 | stage.showAndWait(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/schematic/special/Sign.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.schematic.special; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import net.kyori.adventure.text.Component; 6 | import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; 7 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 8 | import net.ryanland.dfschematics.df.value.Item; 9 | import net.sandrohc.schematic4j.schematic.types.SchematicBlockPos; 10 | 11 | import java.util.List; 12 | 13 | public record Sign(SchematicBlockPos pos, Side front, Side back) implements Item { 14 | 15 | // example sign 16 | // {Count:1b,DF_NBT:3578,id:\"minecraft:oak_sign\",tag:{display:{Lore:['{\"extra\":[{\"text\":\"1front\"}]}','{\"extra\":[{\"text\":\"2front\"}]}','{\"extra\":[{\"text\":\"3front\"}]}','{\"extra\":[{\"text\":\"4front\"}]}','{\"extra\":[{\"text\":\"1back\"}]}','{\"extra\":[{\"text\":\"2back\"}]}','{\"extra\":[{\"text\":\"3back\"}]}','{\"extra\":[{\"text\":\"4back\"}]}']}}} 17 | 18 | @Override 19 | public String getItemNBT() { 20 | JsonObject item = new JsonObject(); 21 | item.addProperty("id", "minecraft:oak_sign"); 22 | item.addProperty("count", 1); 23 | 24 | JsonArray lore = new JsonArray(8); 25 | for (String frontLine : front.lines) lore.add(frontLine); 26 | lore.add(front.getDFColor()); 27 | lore.add(front.getDFGlowing()); 28 | for (String backLine : back.lines) lore.add(backLine); 29 | lore.add(back.getDFColor()); 30 | lore.add(back.getDFGlowing()); 31 | 32 | JsonObject components = new JsonObject(); 33 | components.add("lore", lore); 34 | components.addProperty("custom_name", extra("%s,%s,%s".formatted(pos.x, pos.y, pos.z))); 35 | item.add("components", components); 36 | 37 | return item.toString(); 38 | // Results in an oak sign with 39 | // Item name as xyz coordinates 40 | // 1-4 lore lines as sign's front lines & 5-6 lore lines as sign's front color and glow 41 | // 7-10 lore lines as sign's back lines & 11-12 lore lines as sign's back color and glow 42 | } 43 | 44 | private static String extra(String input) { 45 | return "{\"extra\":[{\"text\":\"%s\"}],\"text\":\"\"}".formatted(input); 46 | } 47 | 48 | public boolean isEmpty() { 49 | return front.lines.stream().allMatch(this::isComponentEmpty) && 50 | back.lines.stream().allMatch(this::isComponentEmpty); 51 | } 52 | 53 | private boolean isComponentEmpty(String json) { 54 | if (json.isEmpty()) return true; 55 | Component component = GsonComponentSerializer.gson().deserialize(json); 56 | return LegacyComponentSerializer.legacySection().serialize(component).isEmpty(); 57 | } 58 | 59 | public record Side(List lines, boolean glowing, String color) { 60 | 61 | private String getDFGlowing() { 62 | return extra(glowing ? "Enable" : "Disable"); 63 | } 64 | 65 | private String getDFColor() { 66 | return extra(color.substring(0, 1).toUpperCase() + color.substring(1).replaceAll("_", " ")); 67 | } 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NEW VERSION > https://ryanland.dev/schematic-importer 2 | 3 | # DFSchematics v1.5.2 4 | Tool to import schematic files into DiamondFire. 5 | 6 | ![](src/main/resources/example.png) 7 | 8 | You can use this application to convert schematic files to 9 | DiamondFire data templates, which can then be imported into 10 | a plot using a builder function. 11 | This is useful for e.g. importing maps from the internet, saving plot space, and for games with rotating 12 | (destructible) maps. This is the successor to Schem2DF. 13 | 14 | _I do not take responsibility for the ownership/copyright of any builds imported through this program._ 15 | 16 | ## Install 17 | 18 | To install the application, go to the [releases page](https://github.com/RyanLandDev/DFSchematics/releases) and download the executable file from the latest version. 19 | 20 | ## How to use 21 | 22 | 1. Launch Minecraft with either [recode](https://github.com/homchom/recode) or [CodeClient](https://github.com/DFOnline/CodeClient) installed. 23 | 2. Navigate to a plot in dev mode. 24 | 3. Open the program, click _'Pick file...'_ and choose a schematic file (`.schem` `.litematic` `.schematic`)*. 25 | 4. (Optional) [Configure the schematic.](#schematic-configuration) 26 | 5. Send the generated schematic data template(s) to Minecraft and place them in order. 27 | 6. Send the builder template to Minecraft and place it. 28 | 7. Call the builder function and configure the chest parameters. 29 | 30 | _*At the moment, only Sponge, Litematica and Schematica schematics are compatible, although additional formats may be supported in the future. 31 | If you have a different format, please convert it to a valid format beforehand._ 32 | 33 | ## Schematic configuration 34 | 35 | You can customize the output template using the Schematic Configuration tab, 36 | accessed by clicking _'Configure schematic...'_. 37 | 38 | ![](src/main/resources/configexample.png) 39 | 40 | The schematic name is used for the function name. 41 | Both the schematic name and author will be placed inside the `Metadata` list. 42 | 43 | ### Tracked blocks 44 | 45 | A tracked block is a type of block that will be looked for in the schematic. 46 | All instances of the block's locations will be put into a list variable. 47 | - This is useful for automatically generating a list of e.g. team spawnpoints, loot boxes, power-ups, etc. 48 | - The generated tracked block lists will be completely ignored by the schematic builder, it is up to you to use the lists. 49 | - The _Remove from structure_ option will replace all block instances with air when enabled. 50 | For example, if you have gold blocks representing spawn points, this feature will remove those unwanted blocks automatically. 51 | - The configured _Tracked Block Offset XYZ_ coordinates will be added to every found location. 52 | 53 | **NOTE:** All generated locations ignore the schematic origin location. 54 | To circumvent this, either add the origin location when using locations or use the _Tracked Block Offset XYZ_ feature. 55 | 56 | ## Support 57 | 58 | If you need help using the program, you can contact me on Discord at `ryandev.`. 59 | 60 | ## Suggestions/Bugs 61 | 62 | Do you have a suggestion or have you found a bug? Please [open an issue](https://github.com/RyanLandDev/DFSchematics/issues/new) 63 | or contact me on Discord at `ryandev.`. 64 | 65 | ## Dependencies 66 | 67 | The program reads schematic files using [schematic4j](https://github.com/SandroHc/schematic4j). 68 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/code/CodeLine.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.code; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import lombok.SneakyThrows; 6 | 7 | import java.io.ByteArrayOutputStream; 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.ArrayList; 10 | import java.util.Base64; 11 | import java.util.Collection; 12 | import java.util.List; 13 | import java.util.zip.GZIPOutputStream; 14 | 15 | /** 16 | * Represents a DiamondFire code line. 17 | */ 18 | public class CodeLine { 19 | 20 | private final List codeBlocks = new ArrayList<>(); 21 | 22 | public List getCodeBlocks() { 23 | return codeBlocks; 24 | } 25 | 26 | public boolean isEmpty() { 27 | return getCodeBlocks().isEmpty(); 28 | } 29 | 30 | /** 31 | * Adds a {@link CodeBlock} to this line at the end. 32 | * @param codeBlock The code block to add 33 | */ 34 | public void add(CodeBlock codeBlock) { 35 | codeBlocks.add(codeBlock); 36 | } 37 | 38 | /** 39 | * Adds {@link CodeBlock CodeBlocks} to this line at the end. 40 | * @param codeBlocks The code blocks to add 41 | */ 42 | public void add(CodeBlock... codeBlocks) { 43 | this.codeBlocks.addAll(List.of(codeBlocks)); 44 | } 45 | 46 | /** 47 | * Adds {@link CodeBlock CodeBlocks} to this line at the end. 48 | * @param codeBlocks The code blocks to add 49 | */ 50 | public , B extends CodeBlock> void addAll(C codeBlocks) { 51 | this.codeBlocks.addAll(codeBlocks); 52 | } 53 | 54 | /** 55 | * Adds a {@link CodeBlock} to this line at the end minus a provided index. 56 | * @param codeBlock The code block to add 57 | * @param insertBefore Offset index for the end, e.g. 1 would add this code block to the second-last position. 58 | */ 59 | public void add(CodeBlock codeBlock, int insertBefore) { 60 | codeBlocks.add(codeBlocks.size() - 1 - insertBefore, codeBlock); 61 | } 62 | 63 | /** 64 | * Inserts a {@link CodeBlock} in this line at the specified index. 65 | * @param index The index to add the code block at 66 | * @param codeBlock The code block to insert 67 | */ 68 | public void insert(int index, CodeBlock codeBlock) { 69 | codeBlocks.add(index, codeBlock); 70 | } 71 | 72 | public JsonObject toJson() { 73 | JsonArray blocks = new JsonArray(); 74 | codeBlocks.forEach(block -> blocks.add(block.toJson())); 75 | 76 | JsonObject json = new JsonObject(); 77 | json.add("blocks", blocks); 78 | return json; 79 | } 80 | 81 | public String toCompressedJson() { 82 | return compressGzipBase64(toJson().toString()); 83 | } 84 | 85 | @SneakyThrows 86 | private static String compressGzipBase64(String uncompressed) { 87 | ByteArrayOutputStream bos = new ByteArrayOutputStream(uncompressed.length()); 88 | GZIPOutputStream gzip = new GZIPOutputStream(bos); 89 | 90 | // Convert to gzip 91 | gzip.write(uncompressed.getBytes()); 92 | gzip.close(); 93 | byte[] compressed = bos.toByteArray(); 94 | bos.close(); 95 | 96 | // Convert to base64 and returns it 97 | //return new String(compressed, StandardCharsets.UTF_8); 98 | return new String(Base64.getEncoder().encode(compressed), StandardCharsets.UTF_8); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/df/api/CodeClientAPI.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.df.api; 2 | 3 | import lombok.SneakyThrows; 4 | import net.ryanland.dfschematics.df.code.CodeBlock; 5 | import net.ryanland.dfschematics.df.code.CodeLine; 6 | import net.ryanland.dfschematics.fxml.MainController; 7 | 8 | import java.net.URI; 9 | import java.util.List; 10 | 11 | public class CodeClientAPI { 12 | 13 | private static WSClient socket; 14 | public static boolean connected = false; 15 | 16 | @SneakyThrows 17 | public static void attemptConnection() { 18 | if (connected) return; 19 | 20 | socket = new WSClient(new URI("ws://localhost:31375")){ 21 | @Override 22 | public void onMessage(String s) { 23 | handleMessage(s); 24 | } 25 | }; // codeclient api endpoint 26 | if (!socket.connectBlocking()) { 27 | // Failed connecting 28 | MainController.instance.setCodeClientStatus("Failed connecting to CodeClient", false); 29 | return; 30 | } 31 | // connected 32 | MainController.instance.setCodeClientStatus("Connected to CodeClient", true); 33 | connected = true; 34 | } 35 | 36 | public static void sendTemplates(List codeLines, String name) { 37 | sendRawTemplates(codeLines.stream().map(CodeLine::toCompressedJson).toList(), name); 38 | } 39 | 40 | public static void sendRawTemplates(List codeLines, String name) { 41 | int i = 0; 42 | for (String codeLine : codeLines) { 43 | i++; 44 | String counter = codeLines.size() > 1 ? " "+i+"/"+codeLines.size() : ""; 45 | String nbt = "{components:{\"minecraft:custom_data\":{PublicBukkitValues:{\"hypercube:codetemplatedata\":'{\"author\":\"RyanLand\",\"name\":\"" + name + counter + "\",\"version\":1,\"code\":\""+ codeLine + "\"}'}},\"minecraft:custom_name\":'{\"text\":\""+ name + counter +"\", \"color\":\"aqua\"}'},count:1,id:\"minecraft:ender_chest\"}"; 46 | socket.send("give " + nbt); 47 | } 48 | } 49 | 50 | private static void handleMessage(String msg) { 51 | if (msg.equals("default")) { 52 | // Attempting to place templates but unauthorized 53 | socket.send("scopes write_code read_plot");//sends user an auth request 54 | MainController.instance.error("Please authorize DFSchematics to place templates in-game..."); 55 | } 56 | if (msg.equals("auth") || msg.equals("write_code read_plot default")) { 57 | // User authorized DFSchematics or has already authorized, get plot size 58 | socket.send("size"); 59 | } 60 | if (msg.equals("BASIC") || msg.equals("LARGE") || msg.equals("MASSIVE") || msg.equals("MEGA")) { 61 | // Got plot size, check and place templates 62 | List codeBlocks = MainController.schematic.getTemplateFactory().generateCodeBlocks(); 63 | // codeclient currently returns wrong plot size 64 | //if (codeBlocks.size() * 2 > getPlotSize(msg)) { 65 | // MainController.instance.error("Plot size too small for templates"); 66 | // return; 67 | //} 68 | placeTemplates(MainController.schematic.getTemplateFactory().splitCodeBlocks(codeBlocks)); 69 | } 70 | } 71 | 72 | private static int getPlotSize(String type) { 73 | return switch (type) { 74 | case "BASIC" -> 51; 75 | case "LARGE" -> 101; 76 | case "MASSIVE", "MEGA" ->//Mega codespace limit is 301, not 1001 77 | 301; 78 | default -> 0; 79 | }; 80 | } 81 | 82 | public static void auth() { 83 | socket.send("scopes");//Ask codeclient what scopes we have 84 | } 85 | 86 | public static void placeTemplates(List codeLines) { 87 | socket.send("place compact"); 88 | for (CodeLine codeLine : codeLines) { 89 | socket.send("place " + codeLine.toCompressedJson()); 90 | } 91 | socket.send("place go"); 92 | MainController.instance.success("Templates placed"); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/resources/net/ryanland/dfschematics/fxml/main.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 28 | 33 | 38 | 43 | 44 | 45 | 50 | 63 | 64 | 65 | 66 | 67 | 68 | 73 | 74 | 75 |
76 | 77 | 78 | 79 | 80 | 81 | 86 | 87 | 88 | 93 | 98 | 103 | 104 | 105 | 106 |
107 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | net.ryanland 8 | DFSchematics 9 | 1.5.2 10 | DFSchematics 11 | 12 | 13 | UTF-8 14 | 15 | 16 | 17 | 18 | org.openjfx 19 | javafx-controls 20 | 17-ea+11 21 | 22 | 23 | org.openjfx 24 | javafx-fxml 25 | 17-ea+11 26 | 27 | 28 | 29 | com.google.code.gson 30 | gson 31 | 2.10.1 32 | 33 | 34 | org.apache.logging.log4j 35 | log4j-api 36 | 2.22.0 37 | 38 | 39 | org.apache.logging.log4j 40 | log4j-core 41 | 2.22.0 42 | 43 | 44 | org.java-websocket 45 | Java-WebSocket 46 | 1.5.4 47 | 48 | 49 | 50 | net.sandrohc 51 | schematic4j 52 | 1.1.0 53 | 54 | 55 | 56 | net.kyori 57 | adventure-api 58 | 4.14.0 59 | 60 | 61 | net.kyori 62 | adventure-text-serializer-gson 63 | 4.14.0 64 | 65 | 66 | net.kyori 67 | adventure-text-serializer-legacy 68 | 4.14.0 69 | 70 | 71 | se.llbit 72 | jo-nbt 73 | 1.3.0 74 | 75 | 76 | org.projectlombok 77 | lombok 78 | 1.18.34 79 | provided 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-compiler-plugin 88 | 3.8.1 89 | 90 | 17 91 | 17 92 | 93 | 94 | org.projectlombok 95 | lombok 96 | 1.18.34 97 | 98 | 99 | 100 | 101 | 102 | org.openjfx 103 | javafx-maven-plugin 104 | 0.0.6 105 | 106 | 107 | 108 | default-cli 109 | 110 | net.ryanland.dfschematics/net.ryanland.dfschematics.Main 111 | 112 | 113 | 114 | 115 | 116 | org.apache.maven.plugins 117 | maven-shade-plugin 118 | 119 | 120 | 121 | shade 122 | 123 | 124 | true 125 | 126 | 128 | net.ryanland.dfschematics.Main 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | src/main/resources 139 | true 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/fxml/ConfigController.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.fxml; 2 | 3 | import javafx.collections.FXCollections; 4 | import javafx.event.ActionEvent; 5 | import javafx.fxml.FXML; 6 | import javafx.fxml.Initializable; 7 | import javafx.scene.Node; 8 | import javafx.scene.control.*; 9 | import javafx.stage.Stage; 10 | import javafx.util.StringConverter; 11 | import net.ryanland.dfschematics.df.value.Scope; 12 | import net.ryanland.dfschematics.df.value.Variable; 13 | import net.ryanland.dfschematics.schematic.special.TrackedBlock; 14 | 15 | import java.net.URL; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.ResourceBundle; 19 | 20 | import static net.ryanland.dfschematics.fxml.MainController.schematic; 21 | 22 | // "Configure schematic..." controller 23 | public class ConfigController implements Initializable { 24 | 25 | static Label configLabel; 26 | 27 | @FXML private Label label; 28 | @FXML private TextField schemNameField; 29 | @FXML private TextField authorField; 30 | 31 | @FXML private CheckBox removeCheckBox; 32 | @FXML private ChoiceBox varScopePicker; 33 | @FXML private ListView trackedBlocks; 34 | @FXML private TextField varNameField; 35 | @FXML private TextField blockField; 36 | @FXML private Button addTrackedBlockButton; 37 | 38 | @FXML private Spinner offsetX; 39 | @FXML private Spinner offsetY; 40 | @FXML private Spinner offsetZ; 41 | 42 | @Override 43 | public void initialize(URL location, ResourceBundle resources) { 44 | configLabel = label; 45 | label.setText(MainController.selectedFile.getName()); 46 | 47 | schemNameField.setText(schematic.getName()); 48 | schemNameField.textProperty().addListener((l, oldName, newName) -> schematic.setName(newName)); 49 | authorField.setText(schematic.getAuthor()); 50 | authorField.textProperty().addListener((l, oldAuthor, newAuthor) -> schematic.setAuthor(newAuthor)); 51 | 52 | trackedBlocks.setCellFactory(view -> new ListCell<>() { 53 | @Override 54 | protected void updateItem(TrackedBlock item, boolean empty) { 55 | super.updateItem(item, empty); 56 | 57 | if (empty || item == null) { 58 | setText(null); 59 | setGraphic(null); 60 | } else { 61 | setText("%s [%s] (%s) - %s found%s".formatted( 62 | item.getVariable().getName(), item.getVariable().getScope().name(), 63 | item.getMaterial(), item.getOccurrences(), 64 | item.isRemoved() ? " & removed" : "")); 65 | setGraphic(item.getIcon()); 66 | } 67 | setOnContextMenuRequested(event -> { 68 | view.getItems().remove(item); 69 | usedVariables.remove(item.getVariable()); 70 | schematic.getTrackedBlocks().remove(item); 71 | schematic.read(); 72 | }); 73 | } 74 | }); 75 | trackedBlocks.setItems(FXCollections.observableArrayList(schematic.getTrackedBlocks().getBlocks())); 76 | usedVariables = new ArrayList<>(); 77 | 78 | varNameField.textProperty().addListener((l, oldName, newName) -> addTrackedBlockButton.setDisable(newName.isBlank() || blockField.getText().isBlank())); 79 | blockField.textProperty().addListener((l, oldBlock, newBlock) -> addTrackedBlockButton.setDisable(newBlock.isBlank() || varNameField.getText().isBlank())); 80 | varScopePicker.setItems(FXCollections.observableArrayList(Scope.LINE, Scope.LOCAL, Scope.GAME, Scope.SAVED)); 81 | varScopePicker.setValue(Scope.LOCAL); 82 | 83 | offsetX.setValueFactory(getFactory(0)); 84 | offsetY.setValueFactory(getFactory(1)); 85 | offsetZ.setValueFactory(getFactory(2)); 86 | } 87 | 88 | private SpinnerValueFactory.DoubleSpinnerValueFactory getFactory(int index) { 89 | SpinnerValueFactory.DoubleSpinnerValueFactory factory = new SpinnerValueFactory.DoubleSpinnerValueFactory(0.0, 301.0, 0.0, 0.1); 90 | factory.setConverter(new OffsetConverter(index)); 91 | factory.valueProperty().setValue(schematic.getTrackedBlocks().getOffset()[index]); 92 | return factory; 93 | } 94 | 95 | @FXML 96 | void close(ActionEvent event) { 97 | ((Stage) ((Node) event.getSource()).getScene().getWindow()).close(); 98 | MainController.instance.disableRoot(false); 99 | } 100 | 101 | private List usedVariables = new ArrayList<>(); 102 | 103 | @FXML 104 | void addTrackedBlock() { 105 | Variable var = new Variable(varNameField.getText(), varScopePicker.getValue()); 106 | if (usedVariables.contains(var)) return; 107 | usedVariables.add(var); 108 | 109 | TrackedBlock block = new TrackedBlock(var, blockField.getText(), removeCheckBox.isSelected()); 110 | schematic.getTrackedBlocks().add(block); 111 | schematic.read(); 112 | trackedBlocks.getItems().add(block); 113 | } 114 | 115 | static class OffsetConverter extends StringConverter { 116 | 117 | private Double value = 0.0; 118 | private int index; 119 | 120 | public OffsetConverter(int index) { 121 | this.index = index; 122 | } 123 | 124 | @Override 125 | public String toString(Double object) { 126 | String str = String.format("%.1f", object).replaceAll(",", "."); 127 | value = Double.valueOf(str); 128 | schematic.getTrackedBlocks().setOffset(index, value); 129 | return str; 130 | } 131 | 132 | @Override 133 | public Double fromString(String string) { 134 | try { 135 | return Double.parseDouble(string); 136 | } catch (NumberFormatException e) { 137 | return value; 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/UpdateChecker.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics; 2 | 3 | import org.jetbrains.annotations.Contract; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.io.IOException; 7 | import java.net.HttpURLConnection; 8 | import java.net.MalformedURLException; 9 | import java.net.URL; 10 | import java.util.Optional; 11 | import java.util.concurrent.CompletableFuture; 12 | import java.util.concurrent.CompletionException; 13 | import java.util.logging.Logger; 14 | 15 | /** 16 | * Checks for updates on a GitHub repository 17 | */ 18 | public class UpdateChecker { 19 | 20 | private final String currentVersion; 21 | private final URL url; 22 | private final boolean disabled; 23 | 24 | private transient CompletableFuture latestVersionFuture = null; 25 | 26 | /** 27 | * Start the program with -Dtechnicjelle.updatechecker.disabled to disable the update checker 28 | * 29 | * @param author GitHub Username 30 | * @param repoName GitHub Repository Name 31 | * @param currentVersion Current version of the program. This must be in the same format as the version tags on GitHub 32 | */ 33 | public UpdateChecker(@NotNull String author, @NotNull String repoName, @NotNull String currentVersion) { 34 | this.currentVersion = removePrefix(currentVersion); 35 | this.disabled = System.getProperty("technicjelle.updatechecker.disabled") != null; 36 | try { 37 | this.url = new URL("https://github.com/" + author + "/" + repoName + "/releases/latest"); 38 | } catch (MalformedURLException e) { 39 | throw new RuntimeException(e); 40 | } 41 | } 42 | 43 | /** 44 | * Checks for updates from a GitHub repository's releases
45 | * This method blocks the thread it is called from 46 | * 47 | * @see #checkAsync() 48 | */ 49 | public void check() { 50 | checkAsync(); 51 | latestVersionFuture.join(); 52 | } 53 | 54 | /** 55 | * Checks for updates from a GitHub repository's releases
56 | * This method does not block the thread it is called from 57 | * 58 | * @see #check() 59 | */ 60 | public void checkAsync() { 61 | latestVersionFuture = CompletableFuture.supplyAsync(this::fetchLatestVersion); 62 | } 63 | 64 | /** 65 | * Checks if necessary and returns the latest available version 66 | * 67 | * @return the latest available version 68 | */ 69 | public synchronized String getLatestVersion() { 70 | if (latestVersionFuture == null) checkAsync(); 71 | return latestVersionFuture.join(); 72 | } 73 | 74 | private String fetchLatestVersion() { 75 | if (disabled) return currentVersion; 76 | try { 77 | // Connect to GitHub website 78 | HttpURLConnection con; 79 | con = (HttpURLConnection) url.openConnection(); 80 | con.setInstanceFollowRedirects(false); 81 | 82 | // Check if the response is a redirect 83 | String newUrl = con.getHeaderField("Location"); 84 | 85 | if (newUrl == null) { 86 | throw new IOException("Did not get a redirect"); 87 | } 88 | 89 | // Get the latest version tag from the redirect url 90 | String[] split = newUrl.split("/"); 91 | return removePrefix(split[split.length - 1]); 92 | } catch (IOException ex) { 93 | throw new CompletionException("Exception trying to fetch the latest version", ex); 94 | } 95 | } 96 | 97 | /** 98 | * Checks if necessary and returns whether an update is available or not 99 | * 100 | * @return true if there is an update available or false otherwise. 101 | */ 102 | public boolean isUpdateAvailable() { 103 | return !getLatestVersion().equals(currentVersion); 104 | } 105 | 106 | /** 107 | * Checks if necessary and returns a message if an update is available.
108 | * The message will contain the latest version and a link to the GitHub releases page.
109 | * Useful if you don't use Java's own {@link java.util.logging.Logger} and you want to use your own.
110 | * Example:
111 | * New version available: v2.5 (current: v2.4)
112 | * Download it at https://github.com/TechnicJelle/UpdateCheckerJava/releases/latest
113 | * 114 | * @return An optional containing the update message or an empty optional if there is no update available 115 | */ 116 | public Optional getUpdateMessage() { 117 | if (isUpdateAvailable()) { 118 | return Optional.of("New version available: v" + getLatestVersion() + " (current: v" + currentVersion + ")\nDownload it at " + url); 119 | } 120 | return Optional.empty(); 121 | } 122 | 123 | /** 124 | * Gets the current version of the program.
125 | * Useful in case you want to log a custom message.
126 | *
127 | * Does not actually check for updates 128 | * 129 | * @return The current version of the program 130 | */ 131 | public String getCurrentVersion() { 132 | return currentVersion; 133 | } 134 | 135 | /** 136 | * Gets the URL to the GitHub releases page, 137 | * where the latest version can be downloaded.
138 | * Useful in case you want to log a custom message.
139 | *
140 | * Does not actually check for updates 141 | * 142 | * @return The URL to the GitHub releases page 143 | */ 144 | public String getUpdateUrl() { 145 | return url.toString(); 146 | } 147 | 148 | /** 149 | * This method logs a message to the console if an update is available
150 | * 151 | * @param logger Logger to log a potential update notification to 152 | */ 153 | public void logUpdateMessage(@NotNull Logger logger) { 154 | getUpdateMessage().ifPresent(logger::warning); 155 | } 156 | 157 | /** 158 | * This method logs a message to the console if an update is available, asynchronously
159 | * 160 | * @param logger Logger to log a potential update notification to 161 | */ 162 | public synchronized void logUpdateMessageAsync(@NotNull Logger logger) { 163 | if (latestVersionFuture == null) checkAsync(); 164 | latestVersionFuture.thenRun(() -> logUpdateMessage(logger)); 165 | } 166 | 167 | /** 168 | * Removes a potential v prefix from a version 169 | * 170 | * @param version Version to remove the prefix from 171 | * @return The version without the prefix 172 | */ 173 | @Contract(pure = true) 174 | private static @NotNull String removePrefix(@NotNull String version) { 175 | return version.replaceFirst("^v", ""); 176 | } 177 | } -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/schematic/TemplateFactory.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.schematic; 2 | 3 | import net.ryanland.dfschematics.df.code.*; 4 | import net.ryanland.dfschematics.df.value.*; 5 | import net.ryanland.dfschematics.schematic.special.Head; 6 | import net.ryanland.dfschematics.schematic.special.TrackedBlock; 7 | import net.sandrohc.schematic4j.schematic.Schematic; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class TemplateFactory { 13 | 14 | private final DFSchematic schematic; 15 | private final Schematic file; 16 | 17 | public TemplateFactory(DFSchematic schematic) { 18 | this.schematic = schematic; 19 | this.file = schematic.getSchematic(); 20 | } 21 | 22 | public List generate() { 23 | return splitCodeBlocks(generateCodeBlocks()); 24 | } 25 | 26 | public List generateCodeBlocks() { 27 | // put codeblocks together 28 | List codeBlocks = new ArrayList<>(); 29 | codeBlocks.add(new Function(schematic.getName())); 30 | codeBlocks.add(getMetadata()); 31 | codeBlocks.addAll(getPalette()); 32 | if (!schematic.getHeads().isEmpty() || !schematic.getSigns().isEmpty()) { 33 | codeBlocks.addAll(getBlockEntities()); 34 | } 35 | codeBlocks.addAll(getBlocks()); 36 | if (!schematic.getTrackedBlocks().getBlocks().isEmpty()) { 37 | codeBlocks.addAll(getTrackedBlocks()); 38 | } 39 | return codeBlocks; 40 | } 41 | 42 | public List splitCodeBlocks(List codeBlocks) { 43 | // splitter 44 | List lines = new ArrayList<>(); 45 | int weight = 0; 46 | CodeLine line = new CodeLine(); 47 | for (CodeBlock block : codeBlocks) { 48 | weight += block.getWeight(); 49 | line.add(block); 50 | if (weight >= 52) { 51 | lines.add(line); 52 | line = new CodeLine(); 53 | weight = 0; 54 | } 55 | } 56 | if (!line.isEmpty()) lines.add(line); 57 | 58 | return lines; 59 | } 60 | 61 | private SetVariableCreateList getMetadata() { 62 | List values = new ArrayList<>(); 63 | 64 | values.add(new Str(schematic.getName())); 65 | values.add(new Str(schematic.getAuthor())); 66 | values.add(new Vector(file.width(), file.height(), file.length())); 67 | 68 | return new SetVariableCreateList(new Variable("Metadata"), values); 69 | } 70 | 71 | private List getPalette() { 72 | // convert palette to texts 73 | List palette = schematic.getStructure().getPalette(); 74 | List texts = new ArrayList<>(); 75 | String text = ""; 76 | for (String material : palette) { 77 | text += material + "."; 78 | if (text.length() > 2600) {//create the next string, don't go over data limit 79 | texts.add(text.substring(0, text.length()-1));//-1 removes final comma 80 | text = ""; 81 | } 82 | } 83 | texts.add(text.substring(0, text.length()-1)); 84 | 85 | return textsListToCodeBlocks(texts, "Palette"); 86 | } 87 | 88 | private List getBlocks() { 89 | // convert structure to texts 90 | StructureContainer structure = schematic.getStructure(); 91 | List texts = new ArrayList<>(); 92 | String text = ""; 93 | for (StructurePart part : structure.getParts()) { 94 | text += part.getText() + "."; 95 | if (text.length() > 2600) {//create the next string, don't go over data limit 96 | texts.add(text.substring(0, text.length()-1));//-1 removes final comma 97 | text = ""; 98 | } 99 | } 100 | texts.add(text.substring(0, text.length()-1)); 101 | 102 | return textsListToCodeBlocks(texts, "Blocks"); 103 | } 104 | 105 | private List getBlockEntities() { 106 | List values = new ArrayList<>(); 107 | 108 | // heads format: H6,1,8,RyanLand;3,4,5,oiaejroaiejroaapPEOFIJapeoifja 109 | List heads = schematic.getHeads(); 110 | if (heads.size() > 0) { 111 | String str = "H"; 112 | for (Head head : heads) { 113 | str += head.pos().x + "," + head.pos().y + "," + head.pos().z + "," + head.texture() + ";"; 114 | if (str.length() > 2600) { 115 | values.add(new Str(str.substring(0, str.length() - 1))); 116 | str = "H"; 117 | } 118 | } 119 | values.add(new Str(str.substring(0, str.length() - 1))); 120 | } 121 | 122 | // signs use a sign item with lores 123 | values.addAll(schematic.getSigns()); 124 | 125 | return listToCodeBlocks(values, "BlockEntities"); 126 | } 127 | 128 | private List getTrackedBlocks() { 129 | List blocks = new ArrayList<>(); 130 | Location offset = new Location((float) schematic.getTrackedBlocks().getOffset()[0], (float) schematic.getTrackedBlocks().getOffset()[1], (float) schematic.getTrackedBlocks().getOffset()[2]); 131 | for (TrackedBlock block : schematic.getTrackedBlocks().getBlocks()) { 132 | blocks.addAll(listToCodeBlocks(block.getLocations().stream().map(loc -> loc.add(offset)).toList(), block.getVariable())); 133 | } 134 | return blocks; 135 | } 136 | 137 | private static List textsListToCodeBlocks(List texts, String listName) { 138 | return listToCodeBlocks(texts.stream().map(Str::new).toList(), listName); 139 | } 140 | 141 | private static List listToCodeBlocks(List list, String listName) { 142 | return listToCodeBlocks(list, new Variable(listName)); 143 | } 144 | 145 | private static List listToCodeBlocks(List list, Variable var) { 146 | List> valuesLists = partition(list, 26);// split values into batches of 26, to fit in codeblock 147 | 148 | List line = new ArrayList<>(); 149 | int i = 0; 150 | for (List l : valuesLists) { 151 | if (i == 0) line.add(new SetVariableCreateList(var, l)); 152 | else line.add(new SetVariableAppendValue(var, l)); 153 | i++; 154 | } 155 | return line; 156 | } 157 | 158 | private static List> partition(List collection, int batchSize) { 159 | int i = 0; 160 | List> batches = new ArrayList<>(); 161 | while (i < collection.size()) { 162 | int nextInc = Math.min(collection.size()-i, batchSize); 163 | List batch = collection.subList(i, i+nextInc); 164 | batches.add(batch); 165 | i = i + nextInc; 166 | } 167 | 168 | return batches; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/fxml/MainController.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.fxml; 2 | 3 | import javafx.application.Platform; 4 | import javafx.event.ActionEvent; 5 | import javafx.fxml.FXML; 6 | import javafx.fxml.FXMLLoader; 7 | import javafx.fxml.Initializable; 8 | import javafx.scene.Node; 9 | import javafx.scene.Parent; 10 | import javafx.scene.Scene; 11 | import javafx.scene.control.Button; 12 | import javafx.scene.control.Label; 13 | import javafx.scene.image.Image; 14 | import javafx.scene.layout.BorderPane; 15 | import javafx.scene.paint.Color; 16 | import javafx.stage.FileChooser; 17 | import javafx.stage.Modality; 18 | import javafx.stage.Stage; 19 | import lombok.SneakyThrows; 20 | import net.ryanland.dfschematics.DFSchematics; 21 | import net.ryanland.dfschematics.df.api.API; 22 | import net.ryanland.dfschematics.df.api.CodeClientAPI; 23 | import net.ryanland.dfschematics.df.code.CodeLine; 24 | import net.ryanland.dfschematics.schematic.DFSchematic; 25 | import net.sandrohc.schematic4j.SchematicLoader; 26 | 27 | import java.io.File; 28 | import java.net.URL; 29 | import java.util.List; 30 | import java.util.ResourceBundle; 31 | import java.util.concurrent.Executors; 32 | import java.util.concurrent.TimeUnit; 33 | 34 | public class MainController implements Initializable { 35 | 36 | public static MainController instance; 37 | 38 | @FXML BorderPane root; 39 | 40 | @Override 41 | public void initialize(URL location, ResourceBundle resources) { 42 | instance = this; 43 | versionLabel.setText("v" + DFSchematics.version); 44 | Executors.newScheduledThreadPool(0).schedule(UpdateCheckController::checkForUpdates, 500, TimeUnit.MILLISECONDS); 45 | } 46 | 47 | public void disableRoot(boolean disable) { 48 | root.setDisable(disable); 49 | } 50 | 51 | // CONNECTIONS ---------------- 52 | 53 | @FXML Label recodeStatus; 54 | @FXML Label codeClientStatus; 55 | @FXML public Button retryButton; 56 | 57 | @FXML 58 | void retryConnections() { 59 | API.attemptConnections(); 60 | } 61 | 62 | public void setRecodeStatus(String text, boolean success) { 63 | recodeStatus.setTextFill(success ? Color.GREEN : Color.RED); 64 | recodeStatus.setText(text); 65 | } 66 | 67 | public void setCodeClientStatus(String text, boolean success) { 68 | codeClientStatus.setTextFill(success ? Color.GREEN : Color.RED); 69 | codeClientStatus.setText(text); 70 | } 71 | 72 | // PICK SCHEMATIC ---------------- 73 | 74 | @FXML private Button filePicker; 75 | @FXML private Label fileStatus; 76 | 77 | public static File selectedFile; 78 | public static DFSchematic schematic; 79 | 80 | @FXML 81 | void pickFile() { 82 | // create file chooser 83 | FileChooser fileChooser = new FileChooser(); 84 | fileChooser.setTitle("Select Schematic..."); 85 | fileChooser.setInitialDirectory(new File(System.getProperty("user.home") + "/Downloads")); 86 | 87 | // extension filter 88 | FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("Schematic files (*.schem *.litematic *.schematic)", "*.schem", "*.litematic", "*.schematic"); 89 | fileChooser.getExtensionFilters().add(extFilter); 90 | 91 | // choose file 92 | File file = fileChooser.showOpenDialog(DFSchematics.stage); 93 | if (file == null) { 94 | System.out.println("No file selected"); 95 | return; 96 | } 97 | 98 | fileStatus.setVisible(true); 99 | fileStatus.setTextFill(Color.LIGHTBLUE); 100 | fileStatus.setText("Reading..."); 101 | 102 | // file selected 103 | System.out.println("Selected file: " + file.getAbsolutePath()); 104 | filePicker.setText(file.getName()); 105 | selectedFile = file; 106 | String format = file.getName().replaceAll("^.+\\.", ""); 107 | 108 | // read schematic 109 | Platform.runLater(() -> { 110 | try { 111 | schematic = new DFSchematic(SchematicLoader.load(file)); 112 | } catch (Exception e) { 113 | error("Error: " + e.getMessage()); 114 | e.printStackTrace(); 115 | return; 116 | } 117 | 118 | API.attemptConnections(); 119 | configureButton.setDisable(false); 120 | 121 | System.out.println(schematic.getSchematic().format()); 122 | success("Successfully loaded (Size: %sx%sx%s)" 123 | .formatted(schematic.getSchematic().width(), schematic.getSchematic().height(), schematic.getSchematic().length())); 124 | System.out.println("Loaded Schematic: " + file.getName()); 125 | }); 126 | } 127 | 128 | public void error(String msg) { 129 | Platform.runLater(() -> { 130 | fileStatus.setVisible(true); 131 | fileStatus.setTextFill(Color.RED); 132 | fileStatus.setText(msg); 133 | }); 134 | } 135 | 136 | public void success(String msg) { 137 | Platform.runLater(() -> { 138 | fileStatus.setVisible(true); 139 | fileStatus.setTextFill(Color.GREEN); 140 | fileStatus.setText(msg); 141 | }); 142 | } 143 | 144 | // CONFIGURE SCHEMATIC -------------- 145 | 146 | @FXML private Button configureButton; 147 | 148 | @FXML @SneakyThrows 149 | void configure(ActionEvent event) { 150 | FXMLLoader loader = new FXMLLoader(getClass().getResource("configuration.fxml")); 151 | Parent root = loader.load(); 152 | 153 | Stage stage = new Stage(); 154 | stage.setTitle("Schematic Configuration"); 155 | Scene scene = new Scene(root); 156 | stage.setScene(scene); 157 | stage.getIcons().add(new Image(String.valueOf(DFSchematics.class.getResource("logo.png")))); 158 | stage.setResizable(false); 159 | stage.initModality(Modality.WINDOW_MODAL); 160 | stage.initOwner(((Node) event.getSource()).getScene().getWindow()); 161 | stage.setOnCloseRequest(evt -> this.root.setDisable(false)); 162 | this.root.setDisable(true); 163 | stage.showAndWait(); 164 | } 165 | 166 | // SEND ------------------ 167 | 168 | @FXML public Button sendTemplates; 169 | @FXML public Button placeTemplates; 170 | @FXML public Button sendBuilder; 171 | 172 | @FXML 173 | void sendTemplates() { 174 | List codeLines = schematic.getTemplateFactory().generate(); 175 | API.sendTemplates(codeLines, selectedFile.getName()); 176 | success(codeLines.size() + " template" + (codeLines.size() == 1 ? "" : "s") + " sent"); 177 | } 178 | 179 | @FXML 180 | void sendBuilderTemplate() { 181 | API.sendRawTemplates(List.of(DFSchematics.builderTemplate), "DFSchematics Builder"); 182 | success("Template sent"); 183 | } 184 | 185 | @FXML 186 | void placeTemplates() {// CodeClient only 187 | CodeClientAPI.auth(); 188 | } 189 | 190 | @FXML 191 | void viewInstructions() { 192 | DFSchematics.hostServices.showDocument("http://github.com/RyanLandDev/DFSchematics#how-to-use"); 193 | } 194 | 195 | // VERSION ------------------- 196 | 197 | @FXML private Label versionLabel; 198 | 199 | @FXML 200 | void onVersionClick() { 201 | DFSchematics.hostServices.showDocument("http://github.com/RyanLandDev/DFSchematics"); 202 | } 203 | 204 | @FXML 205 | void onVersionEnter() { 206 | versionLabel.setUnderline(true); 207 | } 208 | 209 | @FXML 210 | void onVersionExit() { 211 | versionLabel.setUnderline(false); 212 | } 213 | } -------------------------------------------------------------------------------- /src/main/resources/net/ryanland/dfschematics/fxml/configuration.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 26 | 27 | 28 | 141 | 142 | 143 | 144 | 145 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/schematic/DFSchematic.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.schematic; 2 | 3 | import net.ryanland.dfschematics.fxml.MainController; 4 | import net.ryanland.dfschematics.df.value.Location; 5 | import net.ryanland.dfschematics.schematic.special.Head; 6 | import net.ryanland.dfschematics.schematic.special.Sign; 7 | import net.ryanland.dfschematics.schematic.special.TrackedBlock; 8 | import net.ryanland.dfschematics.schematic.special.TrackedBlocks; 9 | import net.sandrohc.schematic4j.schematic.Schematic; 10 | import net.sandrohc.schematic4j.schematic.types.SchematicBlock; 11 | import net.sandrohc.schematic4j.schematic.types.SchematicBlockEntity; 12 | import net.sandrohc.schematic4j.schematic.types.SchematicBlockPos; 13 | 14 | import java.util.*; 15 | 16 | public class DFSchematic { 17 | 18 | private final Schematic schematic; 19 | private final TemplateFactory templateFactory; 20 | 21 | private StructureContainer structure; 22 | private List heads; 23 | private List signs; 24 | private final TrackedBlocks trackedBlocks = new TrackedBlocks(); 25 | 26 | private String name; 27 | private String author; 28 | 29 | public DFSchematic(Schematic schematic) { 30 | this.schematic = schematic; 31 | read(); 32 | 33 | name = Objects.requireNonNullElse(schematic.name(), MainController.selectedFile.getName().replaceAll("\\.schem$|\\.litematic$|\\.schematic$|", "")); 34 | author = Objects.requireNonNullElse(schematic.author(), "Unknown"); 35 | 36 | templateFactory = new TemplateFactory(this); 37 | } 38 | 39 | public Schematic getSchematic() { 40 | return schematic; 41 | } 42 | 43 | public StructureContainer getStructure() { 44 | return structure; 45 | } 46 | 47 | public TemplateFactory getTemplateFactory() { 48 | return templateFactory; 49 | } 50 | 51 | public List getHeads() { 52 | return heads; 53 | } 54 | 55 | public List getSigns() { 56 | return signs; 57 | } 58 | 59 | public TrackedBlocks getTrackedBlocks() { 60 | return trackedBlocks; 61 | } 62 | 63 | public String getName() { 64 | return name; 65 | } 66 | 67 | public void setName(String name) { 68 | this.name = name; 69 | } 70 | 71 | public String getAuthor() { 72 | return author; 73 | } 74 | 75 | public void setAuthor(String author) { 76 | this.author = author; 77 | } 78 | 79 | private static final Map DEFAULT_BLOCK_STATES = Map.of( 80 | "snowy", "false", 81 | "axis", "y", 82 | "powered", "false", 83 | "open", "false" 84 | ); 85 | 86 | public void read() { 87 | //reset in case of a re-read 88 | structure = new StructureContainer(); 89 | heads = new ArrayList<>(); 90 | signs = new ArrayList<>(); 91 | trackedBlocks.reset(); 92 | 93 | // iterates through all x, then z, then y - same as RepeatOnGrid 94 | SchematicBlockPos offset = schematic.offset(); 95 | System.out.println("offset: " + offset); 96 | for (int y = 0; y < schematic.height(); y++) { 97 | for (int z = 0; z < schematic.length(); z++) { 98 | for (int x = 0; x < schematic.width(); x++) { 99 | //System.out.println("xyz: " + x + " " + y + " " + z); 100 | SchematicBlock offsetBlock = schematic.block(x + offset.x, y + offset.y, z + offset.z); 101 | SchematicBlock block = schematic.block(x, y, z); 102 | String material = block.block; 103 | //System.out.println("offset: " + offsetBlock.block + " regular: " + block.block); 104 | 105 | // Tracked Blocks 106 | boolean remove = false; 107 | for (TrackedBlock trackedBlock : trackedBlocks.getBlocks()) { 108 | if (material.equals("minecraft:"+trackedBlock.getMaterial())) { 109 | trackedBlock.addLocation(new Location(x, y, z)); 110 | remove = trackedBlock.isRemoved(); 111 | break; 112 | } 113 | } 114 | if (remove) { 115 | material = "air"; 116 | block = new SchematicBlock("air"); 117 | } 118 | 119 | Map states = new HashMap<>(block.states); 120 | DEFAULT_BLOCK_STATES.forEach(states::remove); 121 | String result = material + (states.isEmpty() ? "" : "[" + String.join(",", 122 | states.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).toList()) + "]"); 123 | 124 | structure.addToPalette(result); // includes block states such as facing=up 125 | structure.addBlock(result); 126 | } 127 | } 128 | } 129 | structure.finalizePart(); 130 | 131 | for (SchematicBlockEntity block : schematic.blockEntities().toList()) { 132 | if (block.data.containsKey("front_text")) { 133 | // Signs after MC 1.20 ---------- 134 | Map frontCompound = ((Map) block.data.get("front_text")); 135 | Map backCompound = ((Map) block.data.get("back_text")); 136 | 137 | Sign.Side front = new Sign.Side( 138 | ((List) frontCompound.get("messages")), 139 | ((byte) frontCompound.get("has_glowing_text")) == 1, 140 | (String) frontCompound.get("color") 141 | ); 142 | 143 | Sign.Side back = new Sign.Side( 144 | ((List) backCompound.get("messages")), 145 | ((byte) backCompound.get("has_glowing_text")) == 1, 146 | (String) backCompound.get("color") 147 | ); 148 | 149 | Sign sign = new Sign(block.pos, front, back); 150 | if (!sign.isEmpty()) signs.add(sign); 151 | 152 | } else if (block.data.containsKey("Text1")) { 153 | // Signs before MC 1.20 --------- 154 | Sign sign = new Sign(block.pos, new Sign.Side(List.of( 155 | ((String) block.data.get("Text1")), 156 | ((String) block.data.get("Text2")), 157 | ((String) block.data.get("Text3")), 158 | ((String) block.data.get("Text4"))), false, "black"), 159 | new Sign.Side(List.of("", "", "", ""), false, "black")); 160 | if (!sign.isEmpty()) signs.add(sign); 161 | } else if (block.data.containsKey("profile")) { 162 | // Heads after MC 1.20.5 ------- 163 | Map data = (TreeMap) block.data.get("profile"); 164 | if (data.containsKey("properties")) { 165 | String owner = (String) data.get("name"); 166 | String value = (String) ((Map) ((List) data.get("properties")).get(0)).get("value"); 167 | String texture = (owner == null || owner.equals("DF-HEAD") ? value.substring(88) : owner);//substring removes unnecessary eyJ0ZX.... 168 | heads.add(new Head(block.pos(), texture)); 169 | } 170 | } else if (block.data.containsKey("SkullOwner")) { 171 | // Heads before MC 1.20.5 ------- 172 | Map data = (TreeMap) block.data.get("SkullOwner"); 173 | if (data.containsKey("Properties")) { 174 | String owner = (String) data.get("Name"); 175 | String value = (String) ((Map) ((List) ((Map) data.get("Properties")).get("textures")).get(0)).get("Value"); 176 | String texture = (owner == null || owner.equals("DF-HEAD") ? value.substring(88) : owner);//substring removes unnecessary eyJ0ZX.... 177 | heads.add(new Head(block.pos(), texture)); 178 | } 179 | } 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/main/resources/project.properties: -------------------------------------------------------------------------------- 1 | version=${project.version} 2 | template=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 -------------------------------------------------------------------------------- /src/main/java/net/ryanland/dfschematics/schematic/LitematicConverter.java: -------------------------------------------------------------------------------- 1 | package net.ryanland.dfschematics.schematic; 2 | 3 | import se.llbit.nbt.*; 4 | 5 | import java.io.*; 6 | import java.nio.file.Files; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.zip.GZIPInputStream; 11 | import java.util.zip.GZIPOutputStream; 12 | 13 | // Taken from https://github.com/GoldenDelicios/Lite2Edit, slightly modified by Ryan 14 | public class LitematicConverter { 15 | 16 | // .litematic to .schem 17 | public static List toWorldEdit(File inputFile, File outputDir) throws IOException { 18 | File tempFile = new File("lite2edit_" + Thread.currentThread().getId() + ".tmp"); 19 | DataInputStream inStream = new DataInputStream(new GZIPInputStream(new FileInputStream(inputFile))); 20 | Tag litematica = CompoundTag.read(inStream).get(""); 21 | inStream.close(); 22 | int dataVersion = litematica.get("MinecraftDataVersion").intValue(); 23 | 24 | List files = new ArrayList<>(); 25 | CompoundTag regions = litematica.get("Regions").asCompound(); 26 | for (NamedTag regionTag : regions) { 27 | CompoundTag region = regionTag.asCompound(); 28 | ListTag palette = region.get("BlockStatePalette").asList(); 29 | int bitsPerBlock = Math.max(2, Integer.SIZE - Integer.numberOfLeadingZeros(palette.size() - 1)); 30 | 31 | // Litematica dimensions can be negative. 32 | Tag size = region.get("Size"); 33 | int x = size.get("x").intValue(); 34 | int y = size.get("y").intValue(); 35 | int z = size.get("z").intValue(); 36 | 37 | // get offset 38 | Tag position = region.get("Position"); 39 | int offsetx = position.get("x").intValue() + (x < 0 ? x+1 : 0); 40 | int offsety = position.get("y").intValue() + (y < 0 ? y+1 : 0); 41 | int offsetz = position.get("z").intValue() + (z < 0 ? z+1 : 0); 42 | 43 | // convert blocks 44 | // use a temporary file to avoid OutOfMemoryErrors for large schematics 45 | BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(tempFile)); 46 | int numBlocks = Math.abs(x * y * z); 47 | long bitmask, bits = 0; 48 | int i = 0, bitCount = 0, weSize = 0; 49 | for (long num : region.get("BlockStates").longArray()) { 50 | int remainingBits = bitCount + 64; 51 | if (bitCount != 0) { 52 | bitmask = (1 << (bitsPerBlock - bitCount)) - 1; 53 | long newBits = (num & bitmask) << bitCount; 54 | bits = bits | newBits; 55 | num = num >>> (bitsPerBlock - bitCount); 56 | remainingBits -= bitsPerBlock; 57 | weSize += writeBlock(fout, (short) bits); 58 | i++; 59 | } 60 | 61 | bitmask = (1 << bitsPerBlock) - 1; 62 | while (remainingBits >= bitsPerBlock) { 63 | bits = num & bitmask; 64 | num = num >>> bitsPerBlock; 65 | remainingBits -= bitsPerBlock; 66 | if (i >= numBlocks) 67 | break; 68 | weSize += writeBlock(fout, (short) bits); 69 | i++; 70 | } 71 | bits = num; 72 | bitCount = remainingBits; 73 | } 74 | fout.close(); 75 | 76 | i = 0; 77 | String[] blockPalette = new String[palette.size()]; 78 | for (SpecificTag blockState : palette) { 79 | String name = blockState.get("Name").stringValue(); 80 | CompoundTag properties = blockState.get("Properties").asCompound(); 81 | if (!properties.isEmpty()) { 82 | List propertyNames = new ArrayList<>(); 83 | for (NamedTag property : properties) { 84 | propertyNames.add(property.name() + "=" + property.unpack().stringValue()); 85 | } 86 | name += "[" + String.join(",", propertyNames) + "]"; 87 | } 88 | blockPalette[i++] = name; 89 | } 90 | 91 | /* 92 | * Convert to WorldEdit format now 93 | */ 94 | // read block data 95 | byte[] weBlocks = new byte[weSize]; 96 | FileInputStream stream = new FileInputStream(tempFile); 97 | int r = stream.read(weBlocks), len = 0; 98 | // keep reading if we didn't get the whole file in one go 99 | while (r != -1 && len + r != weSize) { 100 | len += r; 101 | r = stream.read(weBlocks, len, weSize - len); 102 | } 103 | stream.close(); 104 | 105 | // Convert palette 106 | CompoundTag wePalette = new CompoundTag(); 107 | for (i = 0; i < blockPalette.length; ++i) { 108 | wePalette.add(blockPalette[i], new IntTag(i)); 109 | } 110 | 111 | // Copy tile entity data 112 | List weTileEntities = new ArrayList<>(); 113 | List skip = Arrays.asList("x", "y", "z", "id"); 114 | for (SpecificTag tileEntity : region.get("TileEntities").asList()) { 115 | CompoundTag liteTileEntity = tileEntity.asCompound(); 116 | CompoundTag weTileEntity = new CompoundTag(); 117 | 118 | // Litematica uses integer "x", "y", and "z" tags 119 | // WorldEdit uses one integer array "Pos" tag 120 | int tx = liteTileEntity.get("x").intValue(); 121 | int ty = liteTileEntity.get("y").intValue(); 122 | int tz = liteTileEntity.get("z").intValue(); 123 | weTileEntity.add("Pos", new IntArrayTag(new int[] {tx, ty, tz})); 124 | 125 | // Litematica uses a lowercase "id" 126 | // WorldEdit uses a capitalized "Id" 127 | String tid = liteTileEntity.get("id").stringValue(); 128 | weTileEntity.add("Id", new StringTag(tid)); 129 | 130 | for (NamedTag tileEntityTag : liteTileEntity) { 131 | String name = tileEntityTag.name(); 132 | if (!skip.contains(name)) 133 | weTileEntity.add(tileEntityTag); 134 | } 135 | weTileEntities.add(weTileEntity); 136 | } 137 | 138 | // metadata 139 | CompoundTag metadata = new CompoundTag(); 140 | metadata.add("WEOffsetX", new IntTag(offsetx)); 141 | metadata.add("WEOffsetY", new IntTag(offsety)); 142 | metadata.add("WEOffsetZ", new IntTag(offsetz)); 143 | 144 | CompoundTag worldEdit = new CompoundTag(); 145 | worldEdit.add(new NamedTag("Metadata", metadata)); 146 | worldEdit.add(new NamedTag("Palette", wePalette)); 147 | worldEdit.add(new NamedTag("BlockEntities", new ListTag(Tag.TAG_COMPOUND, weTileEntities))); 148 | worldEdit.add(new NamedTag("DataVersion", new IntTag(dataVersion))); 149 | worldEdit.add(new NamedTag("Height", new ShortTag((short) Math.abs(y)))); 150 | worldEdit.add(new NamedTag("Length", new ShortTag((short) Math.abs(z)))); 151 | worldEdit.add(new NamedTag("PaletteMax", new IntTag(wePalette.size()))); 152 | worldEdit.add(new NamedTag("Version", new IntTag(2))); 153 | worldEdit.add(new NamedTag("Width", new ShortTag((short) Math.abs(x)))); 154 | worldEdit.add(new NamedTag("BlockData", new ByteArrayTag(weBlocks))); 155 | worldEdit.add(new NamedTag("Offset", new IntArrayTag(new int[3]))); 156 | 157 | CompoundTag worldEditRoot = new CompoundTag(); 158 | worldEditRoot.add("Schematic", worldEdit); 159 | 160 | // determine outputFileName 161 | String outputFileName = inputFile.getName(); 162 | if (outputFileName.contains(".")) { 163 | outputFileName = outputFileName.substring(0, outputFileName.lastIndexOf('.')); 164 | } 165 | if (regions.size() > 1) { 166 | outputFileName += "-" + regionTag.name(); 167 | } 168 | outputFileName = outputFileName.replaceAll("[^\\w-]+", "_") + ".schem"; 169 | 170 | // make sure directory exists, and write to the provided path 171 | Files.createDirectories(outputDir.toPath()); 172 | File outputFile = new File(outputDir + "/" + outputFileName); 173 | DataOutputStream outStream = new DataOutputStream(new GZIPOutputStream(new FileOutputStream(outputFile))); 174 | worldEditRoot.write(outStream); 175 | outStream.close(); 176 | files.add(outputFile); 177 | } 178 | 179 | tempFile.delete(); 180 | return files; 181 | } 182 | 183 | private static int writeBlock(BufferedOutputStream fout, short block) throws IOException { 184 | int b = block >>> 7; 185 | if (b == 0) { 186 | fout.write(block); 187 | return 1; 188 | } 189 | else { 190 | fout.write(block | 128); 191 | fout.write(b); 192 | return 2; 193 | } 194 | } 195 | } 196 | --------------------------------------------------------------------------------