diff --git a/src/main/java/com/volmit/iris/core/decrees/CMDIrisStudio.java b/src/main/java/com/volmit/iris/core/decrees/CMDIrisStudio.java index ed71b7620..54b0feede 100644 --- a/src/main/java/com/volmit/iris/core/decrees/CMDIrisStudio.java +++ b/src/main/java/com/volmit/iris/core/decrees/CMDIrisStudio.java @@ -19,13 +19,31 @@ package com.volmit.iris.core.decrees; import com.volmit.iris.Iris; +import com.volmit.iris.core.project.IrisProject; +import com.volmit.iris.core.project.loader.IrisData; import com.volmit.iris.engine.object.dimensional.IrisDimension; +import com.volmit.iris.engine.object.objects.IrisObject; +import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.decree.DecreeExecutor; import com.volmit.iris.util.decree.annotations.Decree; import com.volmit.iris.util.decree.annotations.Param; import com.volmit.iris.util.format.C; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.json.JSONArray; +import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.jobs.Job; +import com.volmit.iris.util.scheduling.jobs.JobCollection; +import com.volmit.iris.util.scheduling.jobs.QueueJob; +import com.volmit.iris.util.scheduling.jobs.SingleJob; -@Decree(name = "studio", aliases = {"std", "s"}, description = "Studio Commands", studio = true) +import java.io.File; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +@Decree(name = "studio", aliases = "std", description = "Studio Commands", studio = true) public class CMDIrisStudio implements DecreeExecutor { @Decree(description = "Open a new studio world", aliases = "o", sync = true) @@ -50,4 +68,184 @@ public class CMDIrisStudio implements DecreeExecutor Iris.proj.close(); sender().sendMessage(C.YELLOW + "Project Closed"); } + + @Decree(description = "Clean an Iris Project, optionally beautifying JSON & fixing block ids with missing keys. Also rebuilds the vscode schemas. ") + public void clean( + @Param(name = "project", required = true, description = "The project to update") + IrisDimension project, + + @Param(name = "beautify", defaultValue = "false", description = "Filters all valid JSON files with a beautifier (indentation: 4)") + boolean beautify, + + @Param(name = "fix-ids", defaultValue = "false", description = "Fixes any block ids used such as \"dirt\" will be converted to \"minecraft:dirt\"") + boolean fixIds, + + @Param(name = "rewriteObjects", defaultValue = "false", description = "Imports all objects and re-writes them cleaning up positions & block data in the process.") + boolean rewriteObjects + ) { + KList jobs = new KList<>(); + KList files = new KList(); + files(Iris.instance.getDataFolder("packs", project.getLoadKey()), files); + MultiBurst burst = new MultiBurst("Cleaner", Thread.MIN_PRIORITY, Runtime.getRuntime().availableProcessors() * 2); + + jobs.add(new SingleJob("Updating Workspace", () -> { + if (!new IrisProject(Iris.proj.getWorkspaceFolder(project.getLoadKey())).updateWorkspace()) { + sender().sendMessage(C.GOLD + "Invalid project: " + project.getLoadKey() + ". Try deleting the code-workspace file and try again."); + } + J.sleep(250); + })); + + sender().sendMessage("Files: " + files.size()); + + if(fixIds) + { + QueueJob r = new QueueJob<>() { + @Override + public void execute(File f) { + try { + JSONObject p = new JSONObject(IO.readAll(f)); + fixBlocks(p); + J.sleep(1); + IO.writeAll(f, p.toString(4)); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public String getName() { + return "Fixing IDs"; + } + }; + + r.queue(files); + jobs.add(r); + } + + if(beautify) + { + QueueJob r = new QueueJob<>() { + @Override + public void execute(File f) { + try { + JSONObject p = new JSONObject(IO.readAll(f)); + IO.writeAll(f, p.toString(4)); + J.sleep(1); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public String getName() { + return "Beautify"; + } + }; + + r.queue(files); + jobs.add(r); + } + + if(rewriteObjects) + { + QueueJob q = new QueueJob() { + @Override + public void execute(Runnable runnable) { + runnable.run(); + J.sleep(50); + } + + @Override + public String getName() { + return "Rewriting Objects"; + } + }; + + IrisData data = new IrisData(Iris.proj.getWorkspaceFolder(project.getLoadKey())); + for (String f : data.getObjectLoader().getPossibleKeys()) { + CompletableFuture gg = burst.complete(() ->{ + File ff = data.getObjectLoader().findFile(f); + IrisObject oo = new IrisObject(0, 0, 0); + try { + oo.read(ff); + } catch (Throwable e) { + Iris.error("FAILER TO READ: " + f); + return; + } + + if (oo == null) { + Iris.error("FAILER TO READ: " + f); + return; + } + + try { + oo.write(ff); + } catch (IOException e) { + Iris.error("FAILURE TO WRITE: " + oo.getLoadFile()); + } + }); + + q.queue(() -> { + try { + gg.get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + }); + } + + jobs.add(q); + } + + jobs.add(new SingleJob("Finishing Up", burst::shutdownNow)); + + new JobCollection("Cleaning", jobs).execute(sender()); + } + + public void files(File clean, KList files) + { + if (clean.isDirectory()) { + for (File i : clean.listFiles()) { + files(i, files); + } + } else if (clean.getName().endsWith(".json")) { + try { + files.add(clean); + } catch (Throwable e) { + Iris.reportError(e); + Iris.error("Failed to beautify " + clean.getAbsolutePath() + " You may have errors in your json!"); + } + } + } + + private void fixBlocks(JSONObject obj) { + for (String i : obj.keySet()) { + Object o = obj.get(i); + + if (i.equals("block") && o instanceof String && !o.toString().trim().isEmpty() && !o.toString().contains(":")) { + obj.put(i, "minecraft:" + o); + } + + if (o instanceof JSONObject) { + fixBlocks((JSONObject) o); + } else if (o instanceof JSONArray) { + fixBlocks((JSONArray) o); + } + } + } + + private void fixBlocks(JSONArray obj) { + for (int i = 0; i < obj.length(); i++) { + Object o = obj.get(i); + + if (o instanceof JSONObject) { + fixBlocks((JSONObject) o); + } else if (o instanceof JSONArray) { + fixBlocks((JSONArray) o); + } + } + } } diff --git a/src/main/java/com/volmit/iris/core/project/IrisProject.java b/src/main/java/com/volmit/iris/core/project/IrisProject.java index 77bfa184d..0601e80e3 100644 --- a/src/main/java/com/volmit/iris/core/project/IrisProject.java +++ b/src/main/java/com/volmit/iris/core/project/IrisProject.java @@ -446,4 +446,62 @@ public class IrisProject { sender.sendMessage("Failed!"); return null; } + + + + public static int clean(VolmitSender s, File clean) { + int c = 0; + if (clean.isDirectory()) { + for (File i : clean.listFiles()) { + c += clean(s, i); + } + } else if (clean.getName().endsWith(".json")) { + try { + clean(clean); + } catch (Throwable e) { + Iris.reportError(e); + Iris.error("Failed to beautify " + clean.getAbsolutePath() + " You may have errors in your json!"); + } + + c++; + } + + return c; + } + + public static void clean(File clean) throws IOException { + JSONObject obj = new JSONObject(IO.readAll(clean)); + fixBlocks(obj, clean); + + IO.writeAll(clean, obj.toString(4)); + } + + public static void fixBlocks(JSONObject obj, File f) { + for (String i : obj.keySet()) { + Object o = obj.get(i); + + if (i.equals("block") && o instanceof String && !o.toString().trim().isEmpty() && !o.toString().contains(":")) { + obj.put(i, "minecraft:" + o); + Iris.debug("Updated Block Key: " + o + " to " + obj.getString(i) + " in " + f.getPath()); + } + + if (o instanceof JSONObject) { + fixBlocks((JSONObject) o, f); + } else if (o instanceof JSONArray) { + fixBlocks((JSONArray) o, f); + } + } + } + + public static void fixBlocks(JSONArray obj, File f) { + for (int i = 0; i < obj.length(); i++) { + Object o = obj.get(i); + + if (o instanceof JSONObject) { + fixBlocks((JSONObject) o, f); + } else if (o instanceof JSONArray) { + fixBlocks((JSONArray) o, f); + } + } + } } diff --git a/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java b/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java index 2a3611255..0c207a83e 100644 --- a/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java +++ b/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java @@ -47,10 +47,8 @@ public interface DecreeSystem extends CommandExecutor, TabCompleter { @Nullable @Override default List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { - Iris.debug("TAB COMPLETE ======================================================"); KList enhanced = new KList<>(args); KList v = getRoot().tabComplete(enhanced, enhanced.toString(" ")); - Iris.debug("input: '" + enhanced.toString(" ") + "'"); v.removeDuplicates(); return v; } diff --git a/src/main/java/com/volmit/iris/util/decree/handlers/BooleanHandler.java b/src/main/java/com/volmit/iris/util/decree/handlers/BooleanHandler.java new file mode 100644 index 000000000..6a7f1ba98 --- /dev/null +++ b/src/main/java/com/volmit/iris/util/decree/handlers/BooleanHandler.java @@ -0,0 +1,61 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.util.decree.handlers; + +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.decree.DecreeParameterHandler; +import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.RNG; + +public class BooleanHandler implements DecreeParameterHandler { + @Override + public KList getPossibilities() { + return null; + } + + @Override + public String toString(Boolean aByte) { + return aByte.toString(); + } + + @Override + public Boolean parse(String in) throws DecreeParsingException { + try + { + return Boolean.parseBoolean(in); + } + + catch(Throwable e) + { + throw new DecreeParsingException("Unable to parse boolean \"" + in + "\""); + } + } + + @Override + public boolean supports(Class type) { + return type.equals(Boolean.class) || type.equals(boolean.class); + } + + @Override + public String getRandomDefault() + { + return M.r(0.5) + ""; + } +} diff --git a/src/main/java/com/volmit/iris/util/plugin/VolmitSender.java b/src/main/java/com/volmit/iris/util/plugin/VolmitSender.java index f5da8e435..9fb389ac9 100644 --- a/src/main/java/com/volmit/iris/util/plugin/VolmitSender.java +++ b/src/main/java/com/volmit/iris/util/plugin/VolmitSender.java @@ -367,14 +367,14 @@ public class VolmitSender implements CommandSender { { m.add((i.isNode() ? (i.getNode().getParameters().isNotEmpty()) - ? "" + ? "<#aebef2>✦ <#5ef288>" + i.getParentPath() - + " " + + " <#42ecf5>" + i.getName() + " " + i.getNode().getParameters().shuffleCopy(RNG.r).convert((f) -> (f.isRequired() || RNG.r.b(0.5) - ? "" + f.getNames().getRandom() + "=" - + "" + f.example() + ? "<#f2e15e>" + f.getNames().getRandom() + "=" + + "<#d665f0>" + f.example() : "")) .toString(" ") : "" @@ -419,7 +419,7 @@ public class VolmitSender implements CommandSender { sendHeader(Form.capitalize(v.getName()) + " Help"); if(isPlayer() && v.getParent() != null) { - sendMessageRaw("Click to go back to " + Form.capitalize(v.getParent().getName()) + " Help" +"'>〈 Back"); + sendMessageRaw("Click to go back to <#3299bf>" + Form.capitalize(v.getParent().getName()) + " Help" +"'><#f58571>〈 Back"); } for(VirtualDecreeCommand i : v.getNodes()) @@ -427,23 +427,23 @@ public class VolmitSender implements CommandSender { if(isPlayer()) { //@builder - sendMessageRaw( + String s = ( " "" + f).toString(", ") + "\n" - + "<#3fe05a>✎ " + i.getDescription() + "\n" - + "<#bbe03f>✒ " + (i.isNode() + i.getNames().copy().reverse().convert((f) -> "<#42ecf5>" + f).toString(", ") + "\n" + + "<#3fe05a>✎ <#6ad97d>" + i.getDescription() + "\n" + + "<#bbe03f>✒ <#a8e0a2>" + (i.isNode() ? ((i.getNode().getParameters().isEmpty() ? "There are no parameters." : "Hover over all of the parameters to learn more.") + "\n") - : "This is a command category. Run " + i.getPath()) + : "This is a command category. Run <#98eda5>" + i.getPath()) + (i.isNode() ? (i.getNode().getParameters().isNotEmpty()) - ? "" + ? "<#aebef2>✦ <#5ef288>" + i.getParentPath() - + " " + + " <#42ecf5>" + i.getName() + " " + i.getNode().getParameters().convert((f) - -> "" + f.example()) + -> "<#d665f0>" + f.example()) .toString(" ") + "\n" : "" : "") @@ -453,13 +453,13 @@ public class VolmitSender implements CommandSender { + (i.isNode() ? " " + i.getNode().getParameters().convert((f) -> " "" + ff).toString(", ") + "\n" - + "<#3fe05a>✎ " + f.getDescription() + "\n" + + f.getNames().convert((ff) -> "<#d665f0>" + ff).toString(", ") + "\n" + + "<#3fe05a>✎ <#6ad97d>" + f.getDescription() + "\n" + (f.isRequired() - ? "<#db4321>⚠ This parameter is required." + ? "<#db4321>⚠ <#faa796>This parameter is required." : (f.hasDefault() - ? "<#2181db>✔ Defaults to \""+f.getParam().defaultValue()+"\" if undefined." - : "<#a73abd>✔ This parameter is optional.")) + ? "<#2181db>✔ <#78dcf0>Defaults to \""+f.getParam().defaultValue()+"\" if undefined." + : "<#a73abd>✔ <#78dcf0>This parameter is optional.")) + "'>" + (f.isRequired() ? "[" : "") + "" + f.getName() @@ -469,6 +469,8 @@ public class VolmitSender implements CommandSender { ) ); //@done + sendMessageRaw(s); + System.out.println(s); } else diff --git a/src/main/java/com/volmit/iris/util/scheduling/jobs/Job.java b/src/main/java/com/volmit/iris/util/scheduling/jobs/Job.java new file mode 100644 index 000000000..309841b88 --- /dev/null +++ b/src/main/java/com/volmit/iris/util/scheduling/jobs/Job.java @@ -0,0 +1,76 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.util.scheduling.jobs; + +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.plugin.VolmitSender; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; + +import java.util.concurrent.CompletableFuture; + +public interface Job +{ + String getName(); + + void execute(); + + void completeWork(); + + int getTotalWork(); + + default int getWorkRemaining() + { + return getTotalWork() - getWorkCompleted(); + } + + int getWorkCompleted(); + + default String getProgressString() + { + return Form.pc(getProgress(), 0); + } + + default double getProgress() + { + return (double)getWorkCompleted() / (double)getTotalWork(); + } + + default void execute(VolmitSender sender) + { + PrecisionStopwatch p = PrecisionStopwatch.start(); + CompletableFuture f = J.afut(this::execute); + int c = J.ar(() -> { + if(sender.isPlayer()) + { + sender.sendProgress(getProgress(), getName()); + } + + else + { + sender.sendMessage(getName() + ": " + getProgressString()); + } + }, sender.isPlayer() ? 0 : 20); + f.whenComplete((fs, ff) -> { + J.car(c); + sender.sendMessage("Completed " + getName() + " in " + Form.duration(p.getMilliseconds(), 1)); + }); + } +} diff --git a/src/main/java/com/volmit/iris/util/scheduling/jobs/JobCollection.java b/src/main/java/com/volmit/iris/util/scheduling/jobs/JobCollection.java new file mode 100644 index 000000000..0accefafb --- /dev/null +++ b/src/main/java/com/volmit/iris/util/scheduling/jobs/JobCollection.java @@ -0,0 +1,70 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.util.scheduling.jobs; + +import com.volmit.iris.util.collection.KList; + +public class JobCollection implements Job { + private final String name; + private String status; + private final KList jobs; + + public JobCollection(String name, Job... jobs) + { + this(name, new KList<>(jobs)); + } + + public JobCollection(String name, KList jobs) + { + this.name = name; + status = null; + this.jobs = new KList<>(jobs); + } + + @Override + public String getName() { + return status == null ? name : (name + " 》" + status); + } + + @Override + public void execute() { + for(Job i : jobs) + { + status = i.getName(); + i.execute(); + } + + status = null; + } + + @Override + public void completeWork() { + + } + + @Override + public int getTotalWork() { + return jobs.stream().mapToInt(Job::getTotalWork).sum(); + } + + @Override + public int getWorkCompleted() { + return jobs.stream().mapToInt(Job::getWorkCompleted).sum(); + } +} diff --git a/src/main/java/com/volmit/iris/util/scheduling/jobs/QueueJob.java b/src/main/java/com/volmit/iris/util/scheduling/jobs/QueueJob.java new file mode 100644 index 000000000..5fb92c5f3 --- /dev/null +++ b/src/main/java/com/volmit/iris/util/scheduling/jobs/QueueJob.java @@ -0,0 +1,73 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.util.scheduling.jobs; + +import com.volmit.iris.util.collection.KList; + +public abstract class QueueJob implements Job { + private final KList queue; + private int totalWork; + private int completed; + + public QueueJob() + { + totalWork = 0; + completed = 0; + queue = new KList<>(); + } + + public void queue(T t) + { + queue.add(t); + totalWork++; + } + + public void queue(KList f) + { + queue.addAll(f); + totalWork += f.size(); + } + + public abstract void execute(T t); + + @Override + public void execute() { + totalWork = queue.size(); + while(queue.isNotEmpty()) + { + execute(queue.pop()); + completeWork(); + } + } + + @Override + public void completeWork() { + completed++; + } + + @Override + public int getTotalWork() { + return totalWork; + } + + @Override + public int getWorkCompleted() { + return completed; + } +} diff --git a/src/main/java/com/volmit/iris/util/scheduling/jobs/SingleJob.java b/src/main/java/com/volmit/iris/util/scheduling/jobs/SingleJob.java new file mode 100644 index 000000000..c422b0ecd --- /dev/null +++ b/src/main/java/com/volmit/iris/util/scheduling/jobs/SingleJob.java @@ -0,0 +1,58 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.util.scheduling.jobs; + +public class SingleJob implements Job{ + private boolean done; + private final String name; + private final Runnable runnable; + + public SingleJob(String name, Runnable runnable) + { + this.name = name; + done = false; + this.runnable = runnable; + } + + @Override + public String getName() { + return name; + } + + @Override + public void execute() { + runnable.run(); + completeWork(); + } + + @Override + public void completeWork() { + done = true; + } + + @Override + public int getTotalWork() { + return 1; + } + + @Override + public int getWorkCompleted() { + return done ? 1 : 0; + } +}