Add advancements, class generator, and recipe handling.
- Removed deprecated `Remarkable` annotation/processor. - Added advancements for Nether and Skyblock progression. - Introduced `ClassBuilder` for dynamic class generation. - Implemented `SmeltingCraftingHandler` for custom recipes. - Updated dependencies for Paper API integration.
This commit is contained in:
parent
c869fdefb1
commit
4032a0c6ff
123 changed files with 6731 additions and 680 deletions
31
achievement-crafter/build.gradle
Normal file
31
achievement-crafter/build.gradle
Normal file
|
@ -0,0 +1,31 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.code.gson:gson:2.11.0'
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass = 'com.ncguy.achievements.Crafter'
|
||||
}
|
||||
|
||||
// Swing is part of the JDK; no extra dependencies required.
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
options.release.set(21)
|
||||
}
|
||||
|
||||
run.jvmArgs += ["--add-opens", "java.base/java.io=ALL-UNNAMED", "--add-opens", "java.base/sun.nio.cs=ALL-UNNAMED", "--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED"]
|
|
@ -0,0 +1,26 @@
|
|||
package com.ncguy.achievements;
|
||||
|
||||
import com.ncguy.achievements.ui.TreeSplitPanePanel;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.SwingUtilities;
|
||||
import java.awt.Dimension;
|
||||
|
||||
public class Crafter {
|
||||
public static void main(String[] args) {
|
||||
SwingUtilities.invokeLater(Crafter::createAndShowUI);
|
||||
}
|
||||
|
||||
private static void createAndShowUI() {
|
||||
JFrame frame = new JFrame("UsefulSkyblock | Advancement Crafter");
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
TreeSplitPanePanel panel = new TreeSplitPanePanel();
|
||||
|
||||
frame.setContentPane(panel);
|
||||
frame.setPreferredSize(new Dimension(800, 500));
|
||||
frame.pack();
|
||||
frame.setLocationRelativeTo(null);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package com.ncguy.achievements.data;
|
||||
|
||||
import com.ncguy.achievements.ui.TreeSplitPanePanel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class TreeNode<T extends TreeNode<T>> {
|
||||
|
||||
public List<T> children;
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public Optional<T> parent;
|
||||
public transient String parentId;
|
||||
|
||||
public TreeNode() {
|
||||
children = new ArrayList<>();
|
||||
parent = Optional.empty();
|
||||
parentId = null;
|
||||
}
|
||||
|
||||
public void setParent(T parent) {
|
||||
this.parent = Optional.ofNullable(parent);
|
||||
this.parentId = null;
|
||||
}
|
||||
|
||||
public void addChild(T child) {
|
||||
this.children.add(child);
|
||||
//noinspection unchecked
|
||||
child.setParent((T) this);
|
||||
}
|
||||
|
||||
public void removeChild(T child) {
|
||||
child.setParent(null);
|
||||
this.children.remove(child);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,286 @@
|
|||
package com.ncguy.achievements.data.gen;
|
||||
|
||||
import com.ncguy.achievements.data.TreeNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a Minecraft datapack advancement JSON structure.
|
||||
* This model is designed to closely mirror the schema in
|
||||
* src/main/resources/schemas/minecraft_advancement_schema.json
|
||||
* while remaining library-agnostic (no JSON annotations).
|
||||
*/
|
||||
public class Advancement extends TreeNode<Advancement> {
|
||||
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Data related to the advancement's display (icon, title, etc.).
|
||||
*/
|
||||
private Display display;
|
||||
|
||||
/**
|
||||
* The criteria tracked by this advancement. Each key is a criterion name, mapping to
|
||||
* an object describing the criterion. The exact shape is flexible (e.g., may contain
|
||||
* fields like "trigger" and "conditions").
|
||||
*/
|
||||
private Map<String, Object> criteria;
|
||||
|
||||
/**
|
||||
* Defines how criteria are combined to grant the advancement. Each inner list is a group
|
||||
* of criterion names where at least one must be satisfied; the outer list requires all groups.
|
||||
*/
|
||||
private List<List<String>> requirements;
|
||||
|
||||
/**
|
||||
* Rewards granted when the advancement is completed.
|
||||
*/
|
||||
private Rewards rewards;
|
||||
|
||||
/**
|
||||
* Whether telemetry data should be collected on completion.
|
||||
*/
|
||||
private Boolean sendsTelemetryEvent;
|
||||
|
||||
public Advancement() {
|
||||
}
|
||||
|
||||
// Getters and setters
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Display getDisplay() {
|
||||
return display;
|
||||
}
|
||||
|
||||
public void setDisplay(Display display) {
|
||||
this.display = display;
|
||||
}
|
||||
|
||||
public Map<String, Object> getCriteria() {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
public void setCriteria(Map<String, Object> criteria) {
|
||||
this.criteria = criteria;
|
||||
}
|
||||
|
||||
public List<List<String>> getRequirements() {
|
||||
return requirements;
|
||||
}
|
||||
|
||||
public void setRequirements(List<List<String>> requirements) {
|
||||
this.requirements = requirements;
|
||||
}
|
||||
|
||||
public Rewards getRewards() {
|
||||
return rewards;
|
||||
}
|
||||
|
||||
public void setRewards(Rewards rewards) {
|
||||
this.rewards = rewards;
|
||||
}
|
||||
|
||||
public Boolean getSendsTelemetryEvent() {
|
||||
return sendsTelemetryEvent;
|
||||
}
|
||||
|
||||
public void setSendsTelemetryEvent(Boolean sendsTelemetryEvent) {
|
||||
this.sendsTelemetryEvent = sendsTelemetryEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display section of an advancement.
|
||||
*/
|
||||
public static class Display {
|
||||
|
||||
public enum DisplayFrame {
|
||||
CHALLENGE,
|
||||
GOAL,
|
||||
TASK;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
private Icon icon;
|
||||
/**
|
||||
* Title supports Minecraft JSON text component types: string, object, or array.
|
||||
* Represented as Object to stay flexible.
|
||||
*/
|
||||
private Object title;
|
||||
/**
|
||||
* Description supports Minecraft JSON text component types: string, object, or array.
|
||||
* Represented as Object to stay flexible.
|
||||
*/
|
||||
private Object description;
|
||||
/**
|
||||
* Frame type for the icon: "challenge", "goal", or "task".
|
||||
*/
|
||||
private DisplayFrame frame;
|
||||
/**
|
||||
* The directory for the background (used only for root advancements).
|
||||
*/
|
||||
private String background;
|
||||
private Boolean showToast;
|
||||
private Boolean announceToChat;
|
||||
private Boolean hidden;
|
||||
|
||||
public Display() {}
|
||||
|
||||
public Icon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(Icon icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public Object getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(Object title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public Object getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(Object description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public DisplayFrame getFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
public void setFrame(DisplayFrame frame) {
|
||||
this.frame = frame;
|
||||
}
|
||||
|
||||
public String getBackground() {
|
||||
return background;
|
||||
}
|
||||
|
||||
public void setBackground(String background) {
|
||||
this.background = background;
|
||||
}
|
||||
|
||||
public Boolean getShowToast() {
|
||||
return showToast;
|
||||
}
|
||||
|
||||
public void setShowToast(Boolean showToast) {
|
||||
this.showToast = showToast;
|
||||
}
|
||||
|
||||
public Boolean getAnnounceToChat() {
|
||||
return announceToChat;
|
||||
}
|
||||
|
||||
public void setAnnounceToChat(Boolean announceToChat) {
|
||||
this.announceToChat = announceToChat;
|
||||
}
|
||||
|
||||
public Boolean getHidden() {
|
||||
return hidden;
|
||||
}
|
||||
|
||||
public void setHidden(Boolean hidden) {
|
||||
this.hidden = hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon definition for the display section.
|
||||
*/
|
||||
public static class Icon {
|
||||
/** Item ID, e.g., "minecraft:stone" */
|
||||
private String id;
|
||||
/** Amount of the item. Defaults to 1 if omitted in JSON. */
|
||||
private Integer count;
|
||||
/** Additional item components/information. */
|
||||
private Map<String, Object> components;
|
||||
|
||||
public Icon() {}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Integer getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(Integer count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public Map<String, Object> getComponents() {
|
||||
return components;
|
||||
}
|
||||
|
||||
public void setComponents(Map<String, Object> components) {
|
||||
this.components = components;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewards section of an advancement.
|
||||
*/
|
||||
public static class Rewards {
|
||||
private Integer experience;
|
||||
private List<String> recipes;
|
||||
private List<String> loot;
|
||||
private String function;
|
||||
|
||||
public Rewards() {}
|
||||
|
||||
public Integer getExperience() {
|
||||
return experience;
|
||||
}
|
||||
|
||||
public void setExperience(Integer experience) {
|
||||
this.experience = experience;
|
||||
}
|
||||
|
||||
public List<String> getRecipes() {
|
||||
return recipes;
|
||||
}
|
||||
|
||||
public void setRecipes(List<String> recipes) {
|
||||
this.recipes = recipes;
|
||||
}
|
||||
|
||||
public List<String> getLoot() {
|
||||
return loot;
|
||||
}
|
||||
|
||||
public void setLoot(List<String> loot) {
|
||||
this.loot = loot;
|
||||
}
|
||||
|
||||
public String getFunction() {
|
||||
return function;
|
||||
}
|
||||
|
||||
public void setFunction(String function) {
|
||||
this.function = function;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package com.ncguy.achievements.data.gen;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
public class AdvancementTreeSetLoader {
|
||||
|
||||
private List<Advancement> orphanedAdvancements;
|
||||
|
||||
public AdvancementTreeSetLoader() {
|
||||
orphanedAdvancements = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Advancement load(Path directory) {
|
||||
File[] files = directory.toFile().listFiles();
|
||||
Gson gson = AdvancementTypeAdapter.createGsonWithAdapter();
|
||||
for (File file : files) {
|
||||
Path path = file.toPath();
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
Advancement result = gson.fromJson(reader, Advancement.class);
|
||||
orphanedAdvancements.add(result);
|
||||
} catch (IOException e) {
|
||||
System.err.println(e);
|
||||
}
|
||||
}
|
||||
|
||||
orphanedAdvancements.sort((a, b) -> {
|
||||
if (a.parentId == null)
|
||||
return -1;
|
||||
if (b.parentId == null)
|
||||
return 1;
|
||||
|
||||
return a.parentId.compareTo(b.parentId);
|
||||
});
|
||||
findParentsForOrphans();
|
||||
return orphanedAdvancements.getFirst();
|
||||
}
|
||||
|
||||
private void findParentsForOrphans() {
|
||||
while (orphanedAdvancements.size() > 1) {
|
||||
Advancement last = orphanedAdvancements.getLast();
|
||||
if (last.parentId != null)
|
||||
break;
|
||||
Optional<Advancement> first = orphanedAdvancements.stream().filter(x -> Objects.equals(x.getId(), last.parentId)).findFirst();
|
||||
first.ifPresent(parent -> {
|
||||
parent.addChild(last);
|
||||
});
|
||||
orphanedAdvancements.remove(last);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
package com.ncguy.achievements.data.gen;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.internal.Streams;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.ncguy.achievements.utils.ReflectionUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
|
||||
import static com.ncguy.achievements.utils.ReflectionUtils.*;
|
||||
|
||||
/**
|
||||
* GSON TypeAdapter for Advancement that preserves unknown/custom top-level fields.
|
||||
*
|
||||
* Notes:
|
||||
* - Known JSON keys follow the Minecraft advancement spec (snake_case).
|
||||
* - The Advancement model uses camelCase; this adapter handles the mapping.
|
||||
*/
|
||||
public class AdvancementTypeAdapter extends TypeAdapter<Advancement> {
|
||||
|
||||
private static final String K_DISPLAY = "display";
|
||||
private static final String K_CRITERIA = "criteria";
|
||||
private static final String K_REQUIREMENTS = "requirements";
|
||||
private static final String K_REWARDS = "rewards";
|
||||
private static final String K_SENDS_TELEMETRY = "sends_telemetry_event";
|
||||
private static final String K_PARENT = "parent";
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
public AdvancementTypeAdapter(Gson gson) {
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Advancement value) throws IOException {
|
||||
if (value == null) {
|
||||
out.nullValue();
|
||||
return;
|
||||
}
|
||||
JsonObject obj = new JsonObject();
|
||||
|
||||
if (value.getDisplay() != null) obj.add(K_DISPLAY, gson.toJsonTree(value.getDisplay()));
|
||||
if (value.getCriteria() != null) obj.add(K_CRITERIA, gson.toJsonTree(value.getCriteria()));
|
||||
if (value.getRequirements() != null) obj.add(K_REQUIREMENTS, gson.toJsonTree(value.getRequirements()));
|
||||
if (value.getRewards() != null) obj.add(K_REWARDS, gson.toJsonTree(value.getRewards()));
|
||||
if (value.getSendsTelemetryEvent() != null) obj.addProperty(K_SENDS_TELEMETRY, value.getSendsTelemetryEvent());
|
||||
value.parent.ifPresent(advancement -> obj.addProperty(K_PARENT, advancement.getId()));
|
||||
|
||||
Streams.write(obj, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Advancement read(JsonReader in) throws IOException {
|
||||
JsonElement root = Streams.parse(in);
|
||||
if (root == null || root.isJsonNull()) return null;
|
||||
JsonObject obj = root.getAsJsonObject();
|
||||
|
||||
BufferedReader br = get(in, "in");
|
||||
InputStreamReader isr = get(br, "in");
|
||||
var sd = get(isr, "sd");
|
||||
var cire = get(sd, "in");
|
||||
var fci = get(cire, "ch");
|
||||
String path = get(fci, "path");
|
||||
Advancement adv = new Advancement();
|
||||
|
||||
// TODO extract from path
|
||||
String namespace = "usefulskyblock";
|
||||
File f = new File(path);
|
||||
String group = f.getParentFile().getName();
|
||||
String name = f.getName().substring(0, f.getName().length() - 5);
|
||||
|
||||
adv.setId(namespace + ":" + group + "/" + name);
|
||||
|
||||
if (obj.has(K_DISPLAY)) adv.setDisplay(gson.fromJson(obj.get(K_DISPLAY), Advancement.Display.class));
|
||||
if (obj.has(K_CRITERIA)) {
|
||||
Type t = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
adv.setCriteria(gson.fromJson(obj.get(K_CRITERIA), t));
|
||||
}
|
||||
if (obj.has(K_REQUIREMENTS)) {
|
||||
Type t = new TypeToken<List<List<String>>>() {}.getType();
|
||||
adv.setRequirements(gson.fromJson(obj.get(K_REQUIREMENTS), t));
|
||||
}
|
||||
if (obj.has(K_REWARDS)) adv.setRewards(gson.fromJson(obj.get(K_REWARDS), Advancement.Rewards.class));
|
||||
if (obj.has(K_SENDS_TELEMETRY)) adv.setSendsTelemetryEvent(getAsBooleanSafe(obj.get(K_SENDS_TELEMETRY)));
|
||||
if (obj.has(K_PARENT)) adv.parentId = obj.get(K_PARENT).getAsString();
|
||||
|
||||
return adv;
|
||||
}
|
||||
|
||||
private static boolean isKnownKey(String key) {
|
||||
return K_DISPLAY.equals(key)
|
||||
|| K_CRITERIA.equals(key)
|
||||
|| K_REQUIREMENTS.equals(key)
|
||||
|| K_REWARDS.equals(key)
|
||||
|| K_SENDS_TELEMETRY.equals(key)
|
||||
|| K_PARENT.equals(key);
|
||||
}
|
||||
|
||||
private static Boolean getAsBooleanSafe(JsonElement el) {
|
||||
if (el == null || el.isJsonNull()) return null;
|
||||
if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isBoolean()) return el.getAsBoolean();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience helper to build a Gson instance with this adapter registered.
|
||||
*/
|
||||
public static Gson createGsonWithAdapter() {
|
||||
GsonBuilder b = new GsonBuilder();
|
||||
Gson gson = b.create();
|
||||
// The adapter requires a Gson reference; build another builder to register once we have it
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
builder.registerTypeAdapter(Advancement.class, new AdvancementTypeAdapter(gson));
|
||||
return builder.create();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,330 @@
|
|||
package com.ncguy.achievements.ui;
|
||||
|
||||
import com.ncguy.achievements.data.gen.Advancement;
|
||||
import com.ncguy.achievements.data.gen.AdvancementTreeSetLoader;
|
||||
import com.ncguy.achievements.ui.editor.AdvancementEditorPanel;
|
||||
import com.ncguy.achievements.ui.editor.extensions.AutoCompleteProvider;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.DropMode;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.TransferHandler;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JMenuItem;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.MutableTreeNode;
|
||||
import javax.swing.tree.TreePath;
|
||||
import java.awt.BorderLayout;
|
||||
import javax.swing.JComponent;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.io.Serializable;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A reusable UI control providing:
|
||||
* - Adjustable split pane
|
||||
* - Left: JTree with drag-and-drop reordering of nodes within the same tree
|
||||
* - Right: empty container for dynamic content
|
||||
*/
|
||||
public class TreeSplitPanePanel extends JPanel {
|
||||
|
||||
private final JSplitPane splitPane;
|
||||
private final JTree tree;
|
||||
private final JPanel rightContainer;
|
||||
|
||||
private static class AdvancementMutableTreeNode extends DefaultMutableTreeNode {
|
||||
public AdvancementMutableTreeNode(Advancement userObject) {
|
||||
super(userObject);
|
||||
}
|
||||
|
||||
public Advancement getAdvancement() {
|
||||
return (Advancement) getUserObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getAdvancement().getId();
|
||||
}
|
||||
}
|
||||
|
||||
public TreeSplitPanePanel() {
|
||||
super(new BorderLayout());
|
||||
|
||||
// Build sample tree model (can be replaced by callers as needed)
|
||||
|
||||
// TODO remove hardcoded filepath
|
||||
Advancement rootAdvancement = new AdvancementTreeSetLoader().load(Path.of("S:\\Development\\Minecraft-PaperMC\\UsefulSkyblock\\src\\main\\resources\\datapacks\\usefulskyblock\\data\\usefulskyblock\\advancement\\skyblock"));
|
||||
AdvancementMutableTreeNode root = new AdvancementMutableTreeNode(rootAdvancement);
|
||||
buildChildren(root);
|
||||
|
||||
DefaultTreeModel model = new DefaultTreeModel(root);
|
||||
this.tree = new JTree(model);
|
||||
this.tree.setRootVisible(true);
|
||||
this.tree.setShowsRootHandles(true);
|
||||
this.tree.setDragEnabled(true);
|
||||
this.tree.setDropMode(DropMode.ON_OR_INSERT);
|
||||
this.tree.setTransferHandler(new TreeNodeMoveTransferHandler());
|
||||
|
||||
// Context menu for adding/removing nodes
|
||||
JPopupMenu popupMenu = new JPopupMenu();
|
||||
JMenuItem addChildItem = new JMenuItem("Add Child");
|
||||
JMenuItem deleteItem = new JMenuItem("Delete");
|
||||
popupMenu.add(addChildItem);
|
||||
popupMenu.add(deleteItem);
|
||||
|
||||
// Add Child action
|
||||
addChildItem.addActionListener(e -> {
|
||||
TreePath selPath = this.tree.getSelectionPath();
|
||||
if (selPath == null) return;
|
||||
Object last = selPath.getLastPathComponent();
|
||||
if (!(last instanceof AdvancementMutableTreeNode parentNode)) return;
|
||||
|
||||
// Create a new child Advancement with a placeholder id
|
||||
Advancement newAdv = new Advancement();
|
||||
String baseId = "new_advancement";
|
||||
String id = baseId;
|
||||
// Try to make id unique among siblings
|
||||
DefaultTreeModel m = (DefaultTreeModel) this.tree.getModel();
|
||||
int suffix = 1;
|
||||
boolean unique;
|
||||
do {
|
||||
unique = true;
|
||||
for (int i = 0; i < parentNode.getChildCount(); i++) {
|
||||
Object c = ((DefaultMutableTreeNode) parentNode.getChildAt(i)).getUserObject();
|
||||
if (c instanceof Advancement a && id.equals(a.getId())) {
|
||||
unique = false; break;
|
||||
}
|
||||
}
|
||||
if (!unique) id = baseId + "_" + (suffix++);
|
||||
} while (!unique);
|
||||
newAdv.setId(id);
|
||||
|
||||
// Keep data model and tree model in sync
|
||||
parentNode.getAdvancement().addChild(newAdv);
|
||||
AdvancementMutableTreeNode newChild = new AdvancementMutableTreeNode(newAdv);
|
||||
m.insertNodeInto(newChild, parentNode, parentNode.getChildCount());
|
||||
|
||||
// Reveal and select the new node
|
||||
TreePath newPath = selPath.pathByAddingChild(newChild);
|
||||
this.tree.expandPath(selPath);
|
||||
this.tree.setSelectionPath(newPath);
|
||||
this.tree.scrollPathToVisible(newPath);
|
||||
});
|
||||
|
||||
// Delete action (intentionally left empty per requirement)
|
||||
deleteItem.addActionListener(e -> {
|
||||
// Intentionally empty – callback to be implemented later
|
||||
});
|
||||
|
||||
// Show popup on appropriate mouse event
|
||||
this.tree.addMouseListener(new MouseAdapter() {
|
||||
private void maybeShowPopup(MouseEvent e) {
|
||||
if (e.isPopupTrigger()) {
|
||||
int row = tree.getRowForLocation(e.getX(), e.getY());
|
||||
if (row != -1) {
|
||||
tree.setSelectionRow(row);
|
||||
popupMenu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override public void mousePressed(MouseEvent e) { maybeShowPopup(e); }
|
||||
@Override public void mouseReleased(MouseEvent e) { maybeShowPopup(e); }
|
||||
});
|
||||
|
||||
JScrollPane treeScroll = new JScrollPane(tree);
|
||||
|
||||
rightContainer = new JPanel(new BorderLayout());
|
||||
rightContainer.setBorder(BorderFactory.createEmptyBorder());
|
||||
|
||||
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeScroll, rightContainer);
|
||||
splitPane.setResizeWeight(0.3); // allocate 30% to the tree by default
|
||||
splitPane.setContinuousLayout(true);
|
||||
|
||||
add(splitPane, BorderLayout.CENTER);
|
||||
|
||||
// Selection listener to render editor on right
|
||||
this.tree.addTreeSelectionListener(e -> {
|
||||
TreePath path = e.getNewLeadSelectionPath();
|
||||
if (path == null) return;
|
||||
Object last = path.getLastPathComponent();
|
||||
if (last instanceof DefaultMutableTreeNode node) {
|
||||
Object uo = node.getUserObject();
|
||||
if (uo instanceof Advancement adv) {
|
||||
showEditorFor(adv);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Show editor for root initially
|
||||
Object rootObj = ((DefaultMutableTreeNode) this.tree.getModel().getRoot()).getUserObject();
|
||||
if (rootObj instanceof Advancement adv) {
|
||||
showEditorFor(adv);
|
||||
}
|
||||
}
|
||||
|
||||
private void showEditorFor(Advancement adv) {
|
||||
rightContainer.removeAll();
|
||||
rightContainer.add(new AdvancementEditorPanel(adv, p -> {
|
||||
p.provider = new AutoCompleteProvider() {
|
||||
@Override
|
||||
public boolean install(String fieldPath, JTextComponent component) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}), BorderLayout.CENTER);
|
||||
rightContainer.revalidate();
|
||||
rightContainer.repaint();
|
||||
}
|
||||
|
||||
private void buildChildren(AdvancementMutableTreeNode root) {
|
||||
Advancement adv = root.getAdvancement();
|
||||
adv.children.forEach(c -> {
|
||||
AdvancementMutableTreeNode newChild = new AdvancementMutableTreeNode(c);
|
||||
buildChildren(newChild);
|
||||
root.add(newChild);
|
||||
});
|
||||
}
|
||||
|
||||
public JTree getTree() {
|
||||
return tree;
|
||||
}
|
||||
|
||||
public JPanel getRightContainer() {
|
||||
return rightContainer;
|
||||
}
|
||||
|
||||
public JSplitPane getSplitPane() {
|
||||
return splitPane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty listener invoked after a node is dropped via drag-and-drop.
|
||||
* Subclasses may override to react to drops.
|
||||
*/
|
||||
protected void onNodeDropped(AdvancementMutableTreeNode node, TreePath destinationPath) {
|
||||
// Intentionally empty per requirement
|
||||
Object lastPathComponent = destinationPath.getLastPathComponent();
|
||||
|
||||
if(!(lastPathComponent instanceof AdvancementMutableTreeNode parent))
|
||||
return;
|
||||
|
||||
node.getAdvancement().parent.ifPresent(p -> p.removeChild(node.getAdvancement()));
|
||||
node.setParent(parent);
|
||||
node.getAdvancement().parent = Optional.ofNullable(parent.getAdvancement());
|
||||
}
|
||||
|
||||
// TransferHandler enabling moving nodes within the same JTree
|
||||
private class TreeNodeMoveTransferHandler extends TransferHandler {
|
||||
private static final DataFlavor NODES_FLAVOR = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=javax.swing.tree.DefaultMutableTreeNode", "TreeNode");
|
||||
private TreePath[] dragPaths;
|
||||
|
||||
@Override
|
||||
public int getSourceActions(JComponent c) {
|
||||
return MOVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Transferable createTransferable(JComponent c) {
|
||||
if (!(c instanceof JTree)) return null;
|
||||
JTree tree = (JTree) c;
|
||||
dragPaths = tree.getSelectionPaths();
|
||||
if (dragPaths == null || dragPaths.length == 0) return null;
|
||||
// Only support single-node drag for simplicity and clarity
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode) dragPaths[0].getLastPathComponent();
|
||||
return new NodesTransferable(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canImport(TransferSupport support) {
|
||||
if (!support.isDataFlavorSupported(NODES_FLAVOR)) return false;
|
||||
if (!(support.getComponent() instanceof JTree)) return false;
|
||||
support.setShowDropLocation(true);
|
||||
JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
|
||||
TreePath path = dl.getPath();
|
||||
if (path == null) return false;
|
||||
|
||||
// Prevent dropping a node into itself or its descendants
|
||||
try {
|
||||
DefaultMutableTreeNode dropTarget = (DefaultMutableTreeNode) path.getLastPathComponent();
|
||||
DefaultMutableTreeNode dragged = (DefaultMutableTreeNode) support.getTransferable().getTransferData(NODES_FLAVOR);
|
||||
if (dragged == dropTarget) return false;
|
||||
if (dragged.isNodeDescendant(dropTarget)) return false;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean importData(TransferSupport support) {
|
||||
if (!canImport(support)) return false;
|
||||
try {
|
||||
JTree tree = (JTree) support.getComponent();
|
||||
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
|
||||
DefaultMutableTreeNode dragged = (DefaultMutableTreeNode) support.getTransferable().getTransferData(NODES_FLAVOR);
|
||||
|
||||
// Remove from old location
|
||||
DefaultMutableTreeNode oldParent = (DefaultMutableTreeNode) dragged.getParent();
|
||||
int oldIndex = oldParent != null ? oldParent.getIndex(dragged) : -1;
|
||||
if (oldParent != null) {
|
||||
model.removeNodeFromParent(dragged);
|
||||
}
|
||||
|
||||
JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
|
||||
TreePath destPath = dl.getPath();
|
||||
DefaultMutableTreeNode target = (DefaultMutableTreeNode) destPath.getLastPathComponent();
|
||||
|
||||
if (dl.getChildIndex() == -1) {
|
||||
// ON: append as child of target
|
||||
model.insertNodeInto(dragged, target, target.getChildCount());
|
||||
} else {
|
||||
// INSERT: insert next to the target's parent at index
|
||||
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) target.getParent();
|
||||
int index = dl.getChildIndex();
|
||||
if (parent == null) {
|
||||
parent = target; // insert into root-level if target has no parent
|
||||
}
|
||||
model.insertNodeInto(dragged, parent, Math.min(index, parent.getChildCount()));
|
||||
}
|
||||
|
||||
// Expand the path to show the newly moved node
|
||||
tree.expandPath(new TreePath(((DefaultMutableTreeNode) model.getRoot()).getPath()));
|
||||
|
||||
// Invoke drop listener with details
|
||||
if (dragged instanceof AdvancementMutableTreeNode amt) {
|
||||
TreeSplitPanePanel.this.onNodeDropped(amt, destPath);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class NodesTransferable implements Transferable, Serializable {
|
||||
private final DefaultMutableTreeNode node;
|
||||
NodesTransferable(DefaultMutableTreeNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
@Override
|
||||
public DataFlavor[] getTransferDataFlavors() {
|
||||
return new DataFlavor[] { NODES_FLAVOR };
|
||||
}
|
||||
@Override
|
||||
public boolean isDataFlavorSupported(DataFlavor flavor) {
|
||||
return NODES_FLAVOR.equals(flavor);
|
||||
}
|
||||
@Override
|
||||
public Object getTransferData(DataFlavor flavor) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,303 @@
|
|||
package com.ncguy.achievements.ui.editor;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.ncguy.achievements.data.gen.Advancement;
|
||||
import com.ncguy.achievements.ui.editor.extensions.AutoCompleteProvider;
|
||||
import com.ncguy.achievements.ui.editor.extensions.TypeAdvisor;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* An editor panel for a single Advancement instance. It focuses on common top-level fields and offers
|
||||
* JSON text areas for complex structures. Extension hooks are provided via ExtensionRegistry but not implemented here.
|
||||
*/
|
||||
public class AdvancementEditorPanel extends JPanel {
|
||||
|
||||
private final Advancement advancement;
|
||||
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
// Display fields
|
||||
private final JTextField iconId = new JTextField();
|
||||
private final JSpinner iconCount = new JSpinner(new SpinnerNumberModel(1, 1, Integer.MAX_VALUE, 1));
|
||||
private final JTextField background = new JTextField();
|
||||
private final JComboBox<Advancement.Display.DisplayFrame> frame = new JComboBox<>(Advancement.Display.DisplayFrame.values());
|
||||
private final JCheckBox showToast = new JCheckBox("Show toast");
|
||||
private final JCheckBox announceToChat = new JCheckBox("Announce to chat");
|
||||
private final JCheckBox hidden = new JCheckBox("Hidden");
|
||||
private final JTextArea titleJson = new JTextArea(3, 30);
|
||||
private final JTextArea descriptionJson = new JTextArea(4, 30);
|
||||
|
||||
// Other fields
|
||||
private final JCheckBox telemetry = new JCheckBox("Sends telemetry event");
|
||||
|
||||
// Complex JSON fields
|
||||
private final JTextArea criteriaJson = new JTextArea(8, 30);
|
||||
private final JTextArea requirementsJson = new JTextArea(5, 30);
|
||||
|
||||
// Rewards
|
||||
private final JSpinner exp = new JSpinner(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 10));
|
||||
private final JTextArea recipesJson = new JTextArea(3, 30);
|
||||
private final JTextArea lootJson = new JTextArea(3, 30);
|
||||
private final JTextField functionField = new JTextField();
|
||||
|
||||
// Custom fields table (key, json)
|
||||
private final DefaultTableModel customModel = new DefaultTableModel(new Object[]{"Key", "JSON Value"}, 0);
|
||||
private final JTable customTable = new JTable(customModel);
|
||||
|
||||
public TypeAdvisor advisor;
|
||||
public AutoCompleteProvider provider;
|
||||
|
||||
public AdvancementEditorPanel(Advancement advancement) {
|
||||
this(advancement, null);
|
||||
}
|
||||
public AdvancementEditorPanel(Advancement advancement, Consumer<AdvancementEditorPanel> earlySetup) {
|
||||
super(new BorderLayout());
|
||||
|
||||
this.advancement = advancement;
|
||||
|
||||
if(earlySetup != null) earlySetup.accept(this);
|
||||
|
||||
JTabbedPane tabs = new JTabbedPane();
|
||||
tabs.addTab("Display", wrap(buildDisplayPanel()));
|
||||
tabs.addTab("Logic", wrap(buildLogicPanel()));
|
||||
tabs.addTab("Rewards", wrap(buildRewardsPanel()));
|
||||
tabs.addTab("Custom", wrap(buildCustomPanel()));
|
||||
|
||||
JPanel header = new JPanel(new BorderLayout());
|
||||
JLabel idLabel = new JLabel("ID: " + Optional.ofNullable(advancement.getId()).orElse("<unknown>"));
|
||||
JButton applyBtn = new JButton("Apply");
|
||||
applyBtn.addActionListener(e -> applyToModel());
|
||||
header.add(idLabel, BorderLayout.WEST);
|
||||
header.add(applyBtn, BorderLayout.EAST);
|
||||
header.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
|
||||
add(header, BorderLayout.NORTH);
|
||||
add(tabs, BorderLayout.CENTER);
|
||||
|
||||
loadFromModel();
|
||||
}
|
||||
|
||||
private JComponent wrap(JComponent inner) {
|
||||
return new JScrollPane(inner);
|
||||
}
|
||||
|
||||
private JPanel buildDisplayPanel() {
|
||||
JPanel p = new JPanel(new GridBagLayout());
|
||||
GridBagConstraints gbc = baseGbc();
|
||||
|
||||
// display.icon.id
|
||||
addLabeled(p, gbc, "Icon ID", enhance("display.icon.id", iconId));
|
||||
// display.icon.count
|
||||
addLabeled(p, gbc, "Icon Count", enhance("display.icon.count", iconCount));
|
||||
// display.frame
|
||||
addLabeled(p, gbc, "Frame", enhance("display.frame", frame));
|
||||
// display.background
|
||||
addLabeled(p, gbc, "Background", enhance("display.background", background));
|
||||
// display.booleans
|
||||
addLabeled(p, gbc, "Show Toast", enhance("display.show_toast", showToast));
|
||||
addLabeled(p, gbc, "Announce To Chat", enhance("display.announce_to_chat", announceToChat));
|
||||
addLabeled(p, gbc, "Hidden", enhance("display.hidden", hidden));
|
||||
|
||||
// title / description JSON
|
||||
titleJson.setBorder(BorderFactory.createTitledBorder("Title (JSON text component)"));
|
||||
descriptionJson.setBorder(BorderFactory.createTitledBorder("Description (JSON text component)"));
|
||||
gbc.gridwidth = 2; gbc.fill = GridBagConstraints.BOTH; gbc.weightx = 1.0; gbc.weighty = 0.2;
|
||||
p.add(enhance("display.title", new JScrollPane(titleJson)), gbc); gbc.gridy++;
|
||||
p.add(enhance("display.description", new JScrollPane(descriptionJson)), gbc); gbc.gridy++;
|
||||
return p;
|
||||
}
|
||||
|
||||
private JPanel buildLogicPanel() {
|
||||
JPanel p = new JPanel(new GridBagLayout());
|
||||
GridBagConstraints gbc = baseGbc();
|
||||
|
||||
addLabeled(p, gbc, "Telemetry", enhance("sends_telemetry_event", telemetry));
|
||||
|
||||
criteriaJson.setBorder(BorderFactory.createTitledBorder("Criteria (JSON object)"));
|
||||
requirementsJson.setBorder(BorderFactory.createTitledBorder("Requirements (JSON array of arrays)"));
|
||||
gbc.gridwidth = 2; gbc.fill = GridBagConstraints.BOTH; gbc.weightx = 1.0; gbc.weighty = 0.5;
|
||||
p.add(new JScrollPane(criteriaJson), gbc); gbc.gridy++;
|
||||
p.add(new JScrollPane(requirementsJson), gbc); gbc.gridy++;
|
||||
return p;
|
||||
}
|
||||
|
||||
private JPanel buildRewardsPanel() {
|
||||
JPanel p = new JPanel(new GridBagLayout());
|
||||
GridBagConstraints gbc = baseGbc();
|
||||
|
||||
addLabeled(p, gbc, "Experience", enhance("rewards.experience", exp));
|
||||
addLabeled(p, gbc, "Function", enhance("rewards.function", functionField));
|
||||
|
||||
recipesJson.setBorder(BorderFactory.createTitledBorder("Recipes (JSON array of strings)"));
|
||||
lootJson.setBorder(BorderFactory.createTitledBorder("Loot (JSON array of strings)"));
|
||||
gbc.gridwidth = 2; gbc.fill = GridBagConstraints.BOTH; gbc.weightx = 1.0; gbc.weighty = 0.3;
|
||||
p.add(new JScrollPane(recipesJson), gbc); gbc.gridy++;
|
||||
p.add(new JScrollPane(lootJson), gbc); gbc.gridy++;
|
||||
return p;
|
||||
}
|
||||
|
||||
private JPanel buildCustomPanel() {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
JPanel top = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
JButton addRow = new JButton("Add");
|
||||
JButton removeRow = new JButton("Remove");
|
||||
addRow.addActionListener(e -> customModel.addRow(new Object[]{"key", "null"}));
|
||||
removeRow.addActionListener(e -> {
|
||||
int i = customTable.getSelectedRow();
|
||||
if (i >= 0) customModel.removeRow(i);
|
||||
});
|
||||
top.add(addRow); top.add(removeRow);
|
||||
panel.add(top, BorderLayout.NORTH);
|
||||
panel.add(new JScrollPane(customTable), BorderLayout.CENTER);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void addLabeled(JPanel panel, GridBagConstraints gbc, String label, JComponent editor) {
|
||||
gbc.gridwidth = 1; gbc.weightx = 0; gbc.fill = GridBagConstraints.NONE;
|
||||
panel.add(new JLabel(label), gbc); gbc.gridx++;
|
||||
gbc.weightx = 1; gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
panel.add(editor, gbc); gbc.gridx = 0; gbc.gridy++;
|
||||
}
|
||||
|
||||
private GridBagConstraints baseGbc() {
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.gridx = 0; gbc.gridy = 0; gbc.insets = new Insets(4, 6, 4, 6);
|
||||
gbc.anchor = GridBagConstraints.WEST;
|
||||
return gbc;
|
||||
}
|
||||
|
||||
private <T extends JComponent> T enhance(String fieldPath, T component) {
|
||||
// Let TypeAdvisor replace or decorate the editor
|
||||
if(advisor != null) {
|
||||
Optional<JComponent> alt = advisor.provideEditor(fieldPath, component);
|
||||
if (alt.isPresent() && alt.get() != component) {
|
||||
//noinspection unchecked
|
||||
component = (T) alt.get();
|
||||
}
|
||||
}
|
||||
// Allow auto-complete providers to install behavior on text components
|
||||
if (provider != null && component instanceof JTextComponent jc) {
|
||||
provider.install(fieldPath, jc);
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
private void loadFromModel() {
|
||||
Advancement.Display display = advancement.getDisplay();
|
||||
if (display != null) {
|
||||
Advancement.Icon icon = display.getIcon();
|
||||
if (icon != null) {
|
||||
if (icon.getId() != null) iconId.setText(icon.getId());
|
||||
if (icon.getCount() != null) iconCount.setValue(icon.getCount());
|
||||
}
|
||||
if (display.getBackground() != null) background.setText(display.getBackground());
|
||||
if (display.getFrame() != null) frame.setSelectedItem(display.getFrame());
|
||||
if (display.getShowToast() != null) showToast.setSelected(display.getShowToast());
|
||||
if (display.getAnnounceToChat() != null) announceToChat.setSelected(display.getAnnounceToChat());
|
||||
if (display.getHidden() != null) hidden.setSelected(display.getHidden());
|
||||
if (display.getTitle() != null) titleJson.setText(toJson(display.getTitle()));
|
||||
if (display.getDescription() != null) descriptionJson.setText(toJson(display.getDescription()));
|
||||
}
|
||||
|
||||
if (advancement.getSendsTelemetryEvent() != null) telemetry.setSelected(advancement.getSendsTelemetryEvent());
|
||||
|
||||
if (advancement.getCriteria() != null) criteriaJson.setText(toJson(advancement.getCriteria()));
|
||||
if (advancement.getRequirements() != null) requirementsJson.setText(toJson(advancement.getRequirements()));
|
||||
|
||||
Advancement.Rewards rewards = advancement.getRewards();
|
||||
if (rewards != null) {
|
||||
if (rewards.getExperience() != null) exp.setValue(rewards.getExperience());
|
||||
if (rewards.getFunction() != null) functionField.setText(rewards.getFunction());
|
||||
if (rewards.getRecipes() != null) recipesJson.setText(toJson(rewards.getRecipes()));
|
||||
if (rewards.getLoot() != null) lootJson.setText(toJson(rewards.getLoot()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void applyToModel() {
|
||||
// Display
|
||||
Advancement.Display display = Optional.ofNullable(advancement.getDisplay()).orElseGet(Advancement.Display::new);
|
||||
Advancement.Icon icon = Optional.ofNullable(display.getIcon()).orElseGet(Advancement.Icon::new);
|
||||
icon.setId(emptyToNull(iconId.getText()));
|
||||
icon.setCount((Integer) iconCount.getValue());
|
||||
display.setIcon(icon);
|
||||
display.setBackground(emptyToNull(background.getText()));
|
||||
display.setFrame((Advancement.Display.DisplayFrame) frame.getSelectedItem());
|
||||
display.setShowToast(showToast.isSelected());
|
||||
display.setAnnounceToChat(announceToChat.isSelected());
|
||||
display.setHidden(hidden.isSelected());
|
||||
// Title/Description as JSON components
|
||||
display.setTitle(parseJsonValueNullable(titleJson.getText()));
|
||||
display.setDescription(parseJsonValueNullable(descriptionJson.getText()));
|
||||
advancement.setDisplay(display);
|
||||
|
||||
// Telemetry
|
||||
advancement.setSendsTelemetryEvent(telemetry.isSelected());
|
||||
|
||||
// Criteria and Requirements
|
||||
Type critType = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
advancement.setCriteria(parseJsonNullable(criteriaJson.getText(), critType));
|
||||
Type reqType = new TypeToken<java.util.List<java.util.List<String>>>() {}.getType();
|
||||
advancement.setRequirements(parseJsonNullable(requirementsJson.getText(), reqType));
|
||||
|
||||
// Rewards
|
||||
Advancement.Rewards rewards = Optional.ofNullable(advancement.getRewards()).orElseGet(Advancement.Rewards::new);
|
||||
rewards.setExperience((Integer) exp.getValue());
|
||||
rewards.setFunction(emptyToNull(functionField.getText()));
|
||||
Type arrStr = new TypeToken<java.util.List<String>>() {}.getType();
|
||||
rewards.setRecipes(parseJsonNullable(recipesJson.getText(), arrStr));
|
||||
rewards.setLoot(parseJsonNullable(lootJson.getText(), arrStr));
|
||||
advancement.setRewards(rewards);
|
||||
|
||||
JOptionPane.showMessageDialog(this, "Applied changes to model.", "Advancement Editor", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
|
||||
private String toJson(Object value) {
|
||||
try {
|
||||
JsonElement el = gson.toJsonTree(value);
|
||||
return gson.toJson(el);
|
||||
} catch (Exception e) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T parseJsonNullable(String text, Type type) {
|
||||
String t = text == null ? "" : text.trim();
|
||||
if (t.isEmpty()) return null;
|
||||
try {
|
||||
return gson.fromJson(t, type);
|
||||
} catch (Exception e) {
|
||||
// keep prior value if parse fails silently
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Object parseJsonValueNullable(String text) {
|
||||
String t = text == null ? "" : text.trim();
|
||||
if (t.isEmpty()) return null;
|
||||
try {
|
||||
return gson.fromJson(t, Object.class);
|
||||
} catch (Exception e) {
|
||||
return t; // fallback as plain string
|
||||
}
|
||||
}
|
||||
|
||||
private String emptyToNull(String s) {
|
||||
if (s == null) return null;
|
||||
String t = s.trim();
|
||||
return t.isEmpty() ? null : t;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.ncguy.achievements.ui.editor.extensions;
|
||||
|
||||
import javax.swing.text.JTextComponent;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Extension point for providing auto-complete suggestions for text-based fields.
|
||||
* This is a hook only; no implementation is provided here.
|
||||
*/
|
||||
public interface AutoCompleteProvider {
|
||||
|
||||
/**
|
||||
* @param fieldPath dot-separated path to the field (e.g., "display.icon.id").
|
||||
* @param component the text component to enhance with auto-complete.
|
||||
* @return true if auto-complete behavior was installed; otherwise false.
|
||||
*/
|
||||
boolean install(String fieldPath, JTextComponent component);
|
||||
|
||||
/**
|
||||
* Optional static suggestions for a field if install is not feasible (callers can render these externally).
|
||||
*/
|
||||
default Optional<List<String>> suggestions(String fieldPath, String currentInput) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.ncguy.achievements.ui.editor.extensions;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Extension point for providing type-aware editor customizations.
|
||||
* Implementations can return specialized editors or converters for specific fields.
|
||||
* This is a hook only; no default implementations are provided here.
|
||||
*/
|
||||
public interface TypeAdvisor {
|
||||
|
||||
/**
|
||||
* @param fieldPath dot-separated path to the field within Advancement, e.g., "display.icon.id" or "rewards.experience".
|
||||
* @param defaultEditor the default Swing editor component (typically a JTextField, JCheckBox, JSpinner, etc.).
|
||||
* @return an alternative editor component to use instead of the default (if present). Implementations may wrap or
|
||||
* decorate the default editor. Empty to use the provided default editor.
|
||||
*/
|
||||
Optional<JComponent> provideEditor(String fieldPath, JComponent defaultEditor);
|
||||
|
||||
/**
|
||||
* Optionally supply a converter that can translate from the editor's text/value to a typed value and back.
|
||||
* If not provided, the editor's raw value will be used.
|
||||
*/
|
||||
default Optional<ValueAdapter<?>> provideValueAdapter(String fieldPath) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple bidirectional conversion contract for editor values.
|
||||
*/
|
||||
interface ValueAdapter<T> {
|
||||
/** Convert from raw editor value (often String) to typed value */
|
||||
T parse(Object raw) throws Exception;
|
||||
/** Convert typed value to editor-presentable form */
|
||||
Object format(T value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.ncguy.achievements.utils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class ReflectionUtils {
|
||||
|
||||
public static Field getField(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> cls = obj.getClass();
|
||||
try {
|
||||
return cls.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
if(cls.getSuperclass() != Object.class)
|
||||
return getField(cls.getSuperclass(), fieldName);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T get(Object obj, String fieldName) {
|
||||
try {
|
||||
Field field = getField(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return (T) field.get(obj);
|
||||
} catch (IllegalAccessException | NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package com.ncguy.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Remarkable {
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package com.ncguy.processors;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import javax.annotation.processing.SupportedSourceVersion;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.JavaFileObject;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SupportedAnnotationTypes("com.ncguy.annotations.Remarkable")
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_21)
|
||||
public class RemarkableProcessor extends AbstractProcessor {
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
24
build.gradle
24
build.gradle
|
@ -1,7 +1,11 @@
|
|||
import com.ncguy.usefulskyblock.generators.AdvancementGenerator
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id("xyz.jpenilla.run-paper") version "2.3.1"
|
||||
id 'idea'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
id("xyz.jpenilla.run-paper") version "2.3.1"
|
||||
id("net.devrieze.gradlecodegen") version "0.6.0"
|
||||
}
|
||||
|
||||
group = 'com.ncguy'
|
||||
|
@ -31,6 +35,8 @@ dependencies {
|
|||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||
|
||||
testImplementation("com.google.code.gson:gson:2.13.1")
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
@ -58,6 +64,18 @@ tasks.withType(JavaCompile).configureEach {
|
|||
}
|
||||
}
|
||||
|
||||
task generateAdvancements {
|
||||
doLast {
|
||||
AdvancementGenerator.generate(file("src/main/resources/datapacks/usefulskyblock/data/usefulskyblock/advancement"),
|
||||
file("src/main/gen/com/ncguy/usefulskyblock/Advancements.java"))
|
||||
}
|
||||
}
|
||||
|
||||
task generateAll {
|
||||
dependsOn += generateAdvancements
|
||||
}
|
||||
|
||||
generate.dependsOn += generateAll
|
||||
|
||||
processResources {
|
||||
def props = [version: version]
|
||||
|
@ -70,3 +88,7 @@ processResources {
|
|||
kotlin {
|
||||
jvmToolchain(21)
|
||||
}
|
||||
|
||||
File genSrc = file('src/main/gen')
|
||||
sourceSets.main.java.srcDirs += genSrc
|
||||
idea.module.generatedSourceDirs += genSrc
|
19
buildSrc/build.gradle
Normal file
19
buildSrc/build.gradle
Normal file
|
@ -0,0 +1,19 @@
|
|||
plugins {
|
||||
id 'groovy-gradle-plugin'
|
||||
}
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven {
|
||||
name = "papermc-repo"
|
||||
url = "https://repo.papermc.io/repository/maven-public/"
|
||||
}
|
||||
maven {
|
||||
name = "sonatype"
|
||||
url = "https://oss.sonatype.org/content/groups/public/"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("io.papermc.paper:paper-api:1.21.5-R0.1-SNAPSHOT")
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package com.ncguy.usefulskyblock.generators;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class AdvancementGenerator {
|
||||
|
||||
public static void generate(File sourceDirectory, File output) throws IOException {
|
||||
System.out.println("Generating advancements from " + sourceDirectory.toString());
|
||||
System.out.println("Outputting to " + output.toString());
|
||||
|
||||
ClassBuilder builder = new ClassBuilder();
|
||||
|
||||
builder.packageName("com.ncguy.usefulskyblock").blankLine();
|
||||
|
||||
builder.importClass("org.bukkit.advancement.Advancement");
|
||||
builder.importClass(java.util.Arrays.class);
|
||||
builder.blankLine();
|
||||
|
||||
builder.newClass("Advancements");
|
||||
|
||||
File[] files = Objects.requireNonNull(sourceDirectory.listFiles());
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
generateGroup(file, builder);
|
||||
}
|
||||
}
|
||||
|
||||
builder.blankLine();
|
||||
builder.openMethod("init", "void", Modifier.PUBLIC | Modifier.STATIC);
|
||||
for (File file : files) {
|
||||
if(file.isDirectory())
|
||||
builder.functionCall(file.getName() + ".init");
|
||||
}
|
||||
builder.endMethod();
|
||||
|
||||
builder.openMethod("groups", "Advancement[][]", Modifier.PUBLIC | Modifier.STATIC);
|
||||
builder.genericLine("Advancement[][] result = new Advancement[][] {");
|
||||
List<String> groupNames = new ArrayList<>();
|
||||
for (File file : files) {
|
||||
if (!file.isDirectory())
|
||||
continue;
|
||||
builder.genericLine(" " + file.getName() + ".values,");
|
||||
}
|
||||
builder.genericLine("};");
|
||||
|
||||
builder.methodReturn("result");
|
||||
builder.endMethod();
|
||||
|
||||
builder.openMethod("values", "Advancement[]", Modifier.PUBLIC | Modifier.STATIC);
|
||||
builder.genericLine("return Arrays.stream(groups()).flatMap(Arrays::stream).toArray(Advancement[]::new);");
|
||||
builder.endMethod();
|
||||
|
||||
builder.openMethod("getGroupName", "String", Modifier.PUBLIC | Modifier.STATIC, "int groupIdx");
|
||||
ClassBuilder.SwitchBuilder groupSwitch = builder.switchCase("groupIdx");
|
||||
|
||||
for (int i = 0; i < files.length; i++)
|
||||
groupSwitch.caseReturn(String.valueOf(i), "\"" + files[i].getName() + "\"");
|
||||
groupSwitch.build();
|
||||
builder.methodReturn("\"UNKNOWN_GROUP: \" + groupIdx");
|
||||
builder.endMethod().blankLine();
|
||||
|
||||
builder.endClass();
|
||||
|
||||
output.getParentFile().mkdirs();
|
||||
Files.writeString(output.toPath(), builder.build());
|
||||
}
|
||||
|
||||
public static void generateGroup(File src, ClassBuilder builder) {
|
||||
builder.newClass(src.getName(), Modifier.PUBLIC | Modifier.STATIC);
|
||||
|
||||
// Declaration
|
||||
File[] files = Objects.requireNonNull(src.listFiles());
|
||||
for (File file : files) {
|
||||
String name = file.getName();
|
||||
if (!name.endsWith(".json"))
|
||||
continue;
|
||||
|
||||
String substring = name.substring(0, name.length() - 5);
|
||||
builder.field("Advancement", substring.toUpperCase(), Modifier.PUBLIC | Modifier.STATIC);
|
||||
}
|
||||
|
||||
builder.field("Advancement[]", "values", Modifier.PUBLIC | Modifier.STATIC);
|
||||
|
||||
// Definition
|
||||
|
||||
builder.blankLine();
|
||||
builder.openMethod("init", "void", Modifier.PUBLIC | Modifier.STATIC);
|
||||
List<String> advancementNames = new ArrayList<>();
|
||||
for (File file : files) {
|
||||
String name = file.getName();
|
||||
if (!name.endsWith(".json"))
|
||||
continue;
|
||||
String substring = name.substring(0, name.length() - 5);
|
||||
String key = src.getName() + "/" + substring;
|
||||
advancementNames.add(substring.toUpperCase());
|
||||
builder.memberAssign(substring.toUpperCase(), "new AdvancementRef(Reference.key(\"" + key + "\")).getActualAdvancement()");
|
||||
}
|
||||
|
||||
builder.blankLine();
|
||||
builder.memberAssign("values", "new Advancement[] {" + String.join(", ", advancementNames) + "}");
|
||||
builder.endMethod().blankLine();
|
||||
|
||||
builder.endClass();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
package com.ncguy.usefulskyblock.generators;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
|
||||
public class ClassBuilder {
|
||||
|
||||
private StringBuilder sb = new StringBuilder();
|
||||
private int scopeDepth = 0;
|
||||
private boolean isOpenField = false;
|
||||
private Stack<String> currentPath;
|
||||
|
||||
public ClassBuilder() {
|
||||
currentPath = new Stack<>();
|
||||
}
|
||||
|
||||
private void indent() {
|
||||
sb.append("\t".repeat(Math.max(0, scopeDepth)));
|
||||
}
|
||||
|
||||
public ClassBuilder blankLine() {
|
||||
sb.append("\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder packageName(String packageName) {
|
||||
indent();
|
||||
sb.append("package ").append(packageName).append(";\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder importClass(Class<?> cls) {
|
||||
return importClass(cls.getName());
|
||||
}
|
||||
|
||||
public ClassBuilder importClass(String cls) {
|
||||
indent();
|
||||
sb.append("import ").append(cls).append(";\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder newClass(String name) {
|
||||
return newClass(name, Modifier.PUBLIC);
|
||||
}
|
||||
|
||||
public ClassBuilder newClass(String name, int modifiers) {
|
||||
indent();
|
||||
sb.append(Modifier.toString(modifiers)).append(" class ").append(name).append(" {\n");
|
||||
scopeDepth++;
|
||||
currentPath.push(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder field(String type, String name) {
|
||||
return field(type, name, Modifier.PUBLIC);
|
||||
}
|
||||
|
||||
public ClassBuilder genericLine(String line) {
|
||||
indent();
|
||||
sb.append(line).append("\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder field(String type, String name, int modifiers) {
|
||||
openField(type, name, modifiers);
|
||||
isOpenField = false;
|
||||
sb.append(";\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder openField(String type, String name) {
|
||||
return openField(type, name, Modifier.PUBLIC);
|
||||
}
|
||||
|
||||
public ClassBuilder openField(String type, String name, int modifiers) {
|
||||
indent();
|
||||
sb.append(Modifier.toString(modifiers)).append(" ").append(type).append(" ").append(name);
|
||||
isOpenField = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder withValue(String value) {
|
||||
if(!isOpenField)
|
||||
throw new IllegalStateException("Cannot set value on a field that is not open");
|
||||
sb.append(" = ").append(value);
|
||||
isOpenField = false;
|
||||
return terminateStatement();
|
||||
}
|
||||
|
||||
public ClassBuilder terminateStatement() {
|
||||
sb.append(";\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder endClass() {
|
||||
endScope();
|
||||
currentPath.pop();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder endScope() {
|
||||
scopeDepth--;
|
||||
indent();
|
||||
sb.append("}\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
public String build() {
|
||||
if(isOpenField) {
|
||||
terminateStatement();
|
||||
System.out.println("Dangling field");
|
||||
}
|
||||
|
||||
if(scopeDepth > 0)
|
||||
System.out.println("Dangling scope of depth " + scopeDepth);
|
||||
while(scopeDepth > 0)
|
||||
endScope();
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public ClassBuilder openMethod(String name, String returnType, int modifiers, String... params) {
|
||||
indent();
|
||||
sb.append(Modifier.toString(modifiers)).append(" ").append(returnType).append(" ").append(name).append("(");
|
||||
Arrays.stream(params).reduce((a, b) -> a + ", " + b).ifPresent(sb::append);
|
||||
sb.append(") {\n");
|
||||
scopeDepth++;
|
||||
currentPath.push(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder memberAssign(String member, String value) {
|
||||
Optional<String> cls = currentPath.stream().limit(currentPath.size() - 1).reduce((a, b) -> a + "." + b);
|
||||
indent();
|
||||
sb.append(cls.orElse("UNKNOWN_CLASS_GENERATE_COMPILE_ERROR")).append(".").append(member).append(" = ").append(value).append(";\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder methodReturn() {
|
||||
indent();
|
||||
sb.append("return;\n");
|
||||
return this;
|
||||
}
|
||||
public ClassBuilder methodReturn(String variable) {
|
||||
indent();
|
||||
sb.append("return ").append(variable).append(";\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder endMethod() {
|
||||
scopeDepth--;
|
||||
indent();
|
||||
sb.append("}\n");
|
||||
currentPath.pop();
|
||||
return blankLine();
|
||||
}
|
||||
|
||||
public ClassBuilder functionCall(String name) {
|
||||
indent();
|
||||
sb.append(name).append("();\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
public SwitchBuilder switchCase(String variable) {
|
||||
return new SwitchBuilder(this, scopeDepth).start(variable);
|
||||
}
|
||||
|
||||
public static class SwitchBuilder {
|
||||
|
||||
private ClassBuilder builder;
|
||||
private StringBuilder sb;
|
||||
private int depth;
|
||||
private boolean isOpen;
|
||||
|
||||
public SwitchBuilder(ClassBuilder builder, int initialDepth) {
|
||||
sb = new StringBuilder();
|
||||
depth = initialDepth;
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
protected void pushScope() {
|
||||
depth++;
|
||||
}
|
||||
|
||||
protected void popScope() {
|
||||
depth--;
|
||||
}
|
||||
|
||||
private void indent() {
|
||||
sb.append("\t".repeat(Math.max(0, depth)));
|
||||
}
|
||||
|
||||
public SwitchBuilder start(String variable) {
|
||||
indent();
|
||||
isOpen = true;
|
||||
sb.append("switch(").append(variable).append(") {\n");
|
||||
pushScope();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SwitchBuilder caseReturn(String value, String ret) {
|
||||
return this.caseStatement(value).returnCase(ret);
|
||||
}
|
||||
|
||||
public SwitchBuilder caseStatement(String value) {
|
||||
indent();
|
||||
sb.append("case ").append(value).append(": ");
|
||||
pushScope();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SwitchBuilder returnCase(String res) {
|
||||
sb.append("return ").append(res).append(";\n");
|
||||
popScope();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SwitchBuilder breakCase() {
|
||||
sb.append("break;\n");
|
||||
popScope();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SwitchBuilder end() {
|
||||
isOpen = false;
|
||||
popScope();
|
||||
indent();
|
||||
sb.append("}\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClassBuilder build() {
|
||||
if(isOpen)
|
||||
this.end();
|
||||
builder.genericLine(sb.toString());
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
118
outline.txt
Normal file
118
outline.txt
Normal file
|
@ -0,0 +1,118 @@
|
|||
Tasks (actionable steps) Day 1 setup and safety
|
||||
- [ ] Preserve starting flora: harvest grass/flowers to secure at least one seed; leave a few grass blocks for spread.
|
||||
- [ ] Reposition dirt: place dirt around the starter tree to catch saplings.
|
||||
- [ ] Basic tools and bench: craft a crafting table and wooden pickaxe, then move to stone tools.
|
||||
- [ ] Cobblestone generator: build a safe, non-flammable design and begin mining.
|
||||
- [ ] Shelter and spawn-proofing: build a small shelter and light the area to reduce hostile spawns and phantoms.
|
||||
- [ ] Inventory audit: check the starter chest (crops, obsidian, lava, ice, turtle eggs if present) and plan usage.
|
||||
|
||||
Early farming and resources
|
||||
- [ ] Tree loop: replant saplings promptly; expand the platform to improve drop capture.
|
||||
- [ ] Crop start: plant starting crops (wheat/melon/pumpkin/sugar cane) near water.
|
||||
- [ ] Melon prep: craft melon seeds if you have a melon slice; plant and manage stems.
|
||||
- [ ] Early food: collect apples from oak; supplement with fish once you have string.
|
||||
|
||||
Mob farming and animal access
|
||||
- [ ] Hostile mob farm (low-tech): build a waterless/pathfinding drop-shaft farm to obtain bones, string, rotten flesh, and occasional iron/redstone from mobs.
|
||||
- [ ] Fishing unlock: use string from spiders to craft a fishing rod; fish in your generator’s water channel or a small pool.
|
||||
- [ ] Passive mob platform: bridge well away from your island, place grass, and create pens to capture spawned animals.
|
||||
|
||||
Crop progression and bone meal
|
||||
- [ ] Bone meal loop: convert skeleton bones to bone meal to accelerate crops and tree growth.
|
||||
- [ ] Crop diversity: add potato and carrot farming from mob drops; use carrots to lure/breed pigs.
|
||||
- [ ] Sugar cane line: expand near water for paper/trade potential.
|
||||
|
||||
Water acquisition and duplication
|
||||
- [ ] Ice to water: break ice to create flowing water.
|
||||
- [ ] Infinite source: create a 2×2 infinite water pool using two sources (or two ice blocks).
|
||||
- [ ] Rain capture: place a cauldron under open sky to gather water slowly; bucket it into an infinite source once you have two buckets’ worth.
|
||||
- [ ] Underwater bonemeal trick: use bone meal in water to create seagrass and generate source blocks (where mechanics allow).
|
||||
|
||||
Villager acquisition and utility
|
||||
- [ ] Secure a zombie villager: isolate one from your hostile farm or a night spawn.
|
||||
- [ ] Weakness application (no brewing path): bait a witch to throw Weakness at the zombie villager by staying close and with low health.
|
||||
- [ ] Weakness application (brewing path): obtain a brewing stand (blackstone/cobblestone), then brew a splash potion of Weakness.
|
||||
- [ ] Golden apple prep: acquire gold (Nether piglins, smelt mob-dropped armor, or drowned—version dependent) and craft a golden apple.
|
||||
- [ ] Cure and protect: cure the zombie villager and secure it; then build a basic villager breeder.
|
||||
- [ ] Trading starter: set up initial workstations (e.g., farmer/librarian/cleric) for emeralds, gear, and books.
|
||||
|
||||
Iron farming
|
||||
- [ ] Minimal iron farm: assemble 3 villagers, 3 beds, and a non-despawning zombie (holding an item); build a safe kill chamber.
|
||||
- [ ] Scale-out: duplicate the farm or upgrade design to increase iron per hour as needs grow.
|
||||
|
||||
Nether progression and farms
|
||||
- [ ] Portal access: light a Nether portal (using provided obsidian or lava/water methods as appropriate).
|
||||
- [ ] Biome-targeted farms:
|
||||
- Nether Wastes: gold from zombified piglins (simple roofed spawning pad).
|
||||
- Crimson Forest: hoglin farm for pork.
|
||||
- Warped Forest: enderman farm for pearls.
|
||||
- Basalt Deltas: magma cube farm for magma cream.
|
||||
- Soul Sand Valley: ghast farm for tears and additional gunpowder.
|
||||
|
||||
- [ ] Piglin bartering post: trade gold for utility items (blackstone, crying obsidian, gravel, quartz, soul sand).
|
||||
- [ ] Fortress utilization: locate a Nether fortress in the void; collect blaze rods, bones, coal, soul sand, and wither skulls.
|
||||
|
||||
Wither and late utility
|
||||
- [ ] Wither prep and fight: obtain three skulls and soul sand; defeat the Wither safely; craft a beacon.
|
||||
|
||||
Goals (milestones) Foundational milestones
|
||||
- [ ] Establish a reliable cobblestone generator and upgrade to full stone tools.
|
||||
- [ ] Secure renewable food sources (crops and/or fishing).
|
||||
- [ ] Build a functioning hostile mob farm yielding bones and string consistently.
|
||||
- [ ] Create an infinite water source using any valid method available to your map/version.
|
||||
- [ ] Acquire your first animals via a passive mob platform.
|
||||
|
||||
Villager and economy milestones
|
||||
- [ ] Cure your first zombie villager and protect them.
|
||||
- [ ] Build a villager breeder and produce multiple villagers.
|
||||
- [ ] Open a trading loop that generates emeralds steadily.
|
||||
- [ ] Obtain enchanted gear (tools/armor/books) through villager trades.
|
||||
|
||||
Metal and automation milestones
|
||||
- [ ] Construct your first iron farm and achieve steady iron income.
|
||||
- [ ] Scale your iron production to meet tool/automation demands.
|
||||
|
||||
Nether milestones
|
||||
- [ ] Activate and safely use a Nether portal.
|
||||
- [ ] Build a basic gold farm and unlock piglin bartering.
|
||||
- [ ] Acquire key bartering outputs (blackstone, gravel, quartz, crying obsidian, soul sand).
|
||||
- [ ] Locate and utilize a Nether fortress for blaze rods and skulls.
|
||||
|
||||
Combat and power milestones
|
||||
- [ ] Craft a brewing stand and brew splash Weakness potions.
|
||||
- [ ] Defeat the Wither and place your first beacon.
|
||||
|
||||
Challenges (optional constraints or variants) Resource and progression constraints
|
||||
- [ ] Waterless start: reach infinite water using only rain-in-cauldron collection.
|
||||
- [ ] No-redstone early: construct a fully passive/pathfinding-based hostile mob farm.
|
||||
- [ ] No-bone-meal: grow crops and trees without bone meal until Nether access.
|
||||
- [ ] No-fishing: establish sustainable food without fishing.
|
||||
- [ ] No-villager-trading until iron: reach an operational iron farm before any trades.
|
||||
|
||||
Villager-specific constraints
|
||||
- [ ] Witch-only cure: cure your first villager via a witch’s Weakness potion (no brewing).
|
||||
- [ ] No-Nether cure: obtain a golden apple and cure a villager without entering the Nether (relying on armor smelts/drowned for gold; version dependent).
|
||||
|
||||
Mob farm and safety constraints
|
||||
- [ ] Spiderless farm: design a hostile farm that prevents spider spawns while keeping rates high.
|
||||
- [ ] Phantom-proof base: eliminate phantom risk via shelter design and lighting without relying on frequent sleep.
|
||||
- [ ] Zero fall deaths: complete early bridging/expansion without falling into the void.
|
||||
|
||||
Nether farming variants
|
||||
- [ ] Biome mastery: build one working farm in each Nether biome (piglin, hoglin, enderman, magma cube, ghast).
|
||||
- [ ] Minimal gold farm: produce bartering-grade gold using a simple low-material pad (no turtle eggs or complex baiting).
|
||||
|
||||
Iron farm constraints
|
||||
- [ ] Lava-free iron farm: use fall damage or other non-lava methods for the kill chamber.
|
||||
- [ ] Minimal villagers: keep iron farm to the smallest villager count while maintaining uptime.
|
||||
|
||||
Version-aware constraints
|
||||
- [ ] Copper-era start: if drowned drop copper (not gold), reach your first golden apple via alternative gold paths.
|
||||
- [ ] Ice-free start: obtain infinite water without ice blocks present.
|
||||
|
||||
Collection and completionist variants
|
||||
- [ ] Barter unlock list: obtain each notable bartering item at least once.
|
||||
- [ ] Fortress-only advance: obtain blaze rods and wither skulls before building non-Nether automated farms.
|
||||
- [ ] Farm trifecta: operate simultaneous farms for bones, string, and gunpowder using low-tech designs.
|
||||
|
||||
Use these as a menu: pick Tasks to guide immediate actions, track progress with Goals, and spice up playthroughs using Challenges tailored to your map/version.
|
|
@ -8,4 +8,5 @@ plugins {
|
|||
}
|
||||
rootProject.name = 'UsefulSkyblock'
|
||||
|
||||
include 'annotation-processor'
|
||||
include(':achievement-crafter')
|
||||
|
||||
|
|
76
src/main/java/com/ncguy/usefulskyblock/AdvancementRef.java
Normal file
76
src/main/java/com/ncguy/usefulskyblock/AdvancementRef.java
Normal file
|
@ -0,0 +1,76 @@
|
|||
package com.ncguy.usefulskyblock;
|
||||
|
||||
import io.papermc.paper.advancement.AdvancementDisplay;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.advancement.Advancement;
|
||||
import org.bukkit.advancement.AdvancementRequirements;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
public class AdvancementRef implements Advancement {
|
||||
|
||||
private final NamespacedKey key;
|
||||
private Advancement advancement;
|
||||
|
||||
public AdvancementRef(NamespacedKey key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public Advancement getActualAdvancement() {
|
||||
if (advancement == null)
|
||||
advancement = Objects.requireNonNull(Bukkit.getAdvancement(key), "Advancement " + key + " not found");
|
||||
return advancement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<String> getCriteria() {
|
||||
return getActualAdvancement().getCriteria();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull AdvancementRequirements getRequirements() {
|
||||
return getActualAdvancement().getRequirements();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AdvancementDisplay getDisplay() {
|
||||
return getActualAdvancement().getDisplay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Component displayName() {
|
||||
return getActualAdvancement().displayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Advancement getParent() {
|
||||
return getActualAdvancement().getParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull @Unmodifiable Collection<Advancement> getChildren() {
|
||||
return getActualAdvancement().getChildren();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Advancement getRoot() {
|
||||
return getActualAdvancement().getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull NamespacedKey getKey() {
|
||||
return getActualAdvancement().getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Key key() {
|
||||
return getActualAdvancement().key();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
package com.ncguy.usefulskyblock;
|
||||
|
||||
import com.ncguy.usefulskyblock.data.BiomedStructure;
|
||||
import com.ncguy.usefulskyblock.utils.MathsUtils;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.structure.Mirror;
|
||||
import org.bukkit.block.structure.StructureRotation;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.structure.Structure;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class IslandNetworkGenerator {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(IslandNetworkGenerator.class);
|
||||
|
||||
public static class TierDefinition {
|
||||
public int tier;
|
||||
public int slotCount;
|
||||
public float spacing;
|
||||
public StructureRef[] structures;
|
||||
public StructureRotation[] validRotations;
|
||||
public Mirror[] validMirrors;
|
||||
|
||||
public Supplier<Float> yNoise;
|
||||
|
||||
public TierDefinition(int tier, int slotCount, float spacing, StructureRef[] structures, boolean canRotate, boolean canMirror, Supplier<Float> yNoise) {
|
||||
this.tier = tier;
|
||||
this.slotCount = slotCount;
|
||||
this.spacing = spacing;
|
||||
this.structures = structures;
|
||||
this.yNoise = yNoise;
|
||||
|
||||
if (canRotate)
|
||||
validRotations = StructureRotation.values();
|
||||
else
|
||||
validRotations = new StructureRotation[]{StructureRotation.NONE};
|
||||
|
||||
if (canMirror)
|
||||
validMirrors = Mirror.values();
|
||||
else
|
||||
validMirrors = new Mirror[]{Mirror.NONE};
|
||||
}
|
||||
|
||||
public TierDefinition(int tier, int slotCount, float spacing, StructureRef[] structures, StructureRotation[] validRotations, Mirror[] validMirrors, Supplier<Float> yNoise) {
|
||||
this.tier = tier;
|
||||
this.slotCount = slotCount;
|
||||
this.spacing = spacing;
|
||||
this.structures = structures;
|
||||
this.validRotations = validRotations;
|
||||
this.validMirrors = validMirrors;
|
||||
this.yNoise = yNoise;
|
||||
}
|
||||
|
||||
public TierDefinition(int tier, int slotCount, float spacing, StructureRef[] structures, boolean canRotate, boolean canMirror) {
|
||||
this(tier, slotCount, spacing, structures, canRotate, canMirror, () -> 0f);
|
||||
}
|
||||
|
||||
public TierDefinition(int tier, int slotCount, float spacing, StructureRef[] structures, Supplier<Float> yNoise) {
|
||||
this(tier, slotCount, spacing, structures, true, true, yNoise);
|
||||
}
|
||||
|
||||
public TierDefinition(int tier, int slotCount, float spacing, StructureRef[] structures) {
|
||||
this(tier, slotCount, spacing, structures, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Definition {
|
||||
public TierDefinition[] tiers;
|
||||
}
|
||||
|
||||
private void generateChunks(World world, Queue<Location> chunkCoords, Runnable onComplete) {
|
||||
world.getChunkAtAsync(chunkCoords.remove(), true).thenAccept(chunk -> {
|
||||
if(chunkCoords.isEmpty()) {
|
||||
onComplete.run();
|
||||
return;
|
||||
}
|
||||
generateChunks(world, chunkCoords, onComplete);
|
||||
});
|
||||
}
|
||||
|
||||
private Location placeStructureAtLocation(Structure structure, Location loc, StructureRotation[] validRotations, Mirror[] validMirrors) {
|
||||
StructureRotation rotation = randomElement(validRotations);
|
||||
Mirror mirror = randomElement(validMirrors);
|
||||
|
||||
int xRange, yRange, zRange;
|
||||
|
||||
World world = loc.getWorld();
|
||||
if((structure instanceof StructureRef ref) && ref.getActualStructure() == null) {
|
||||
log.info("Trying to place structure {}", ref.name);
|
||||
Location finalLoc = loc;
|
||||
final StructureRef refCopy = ref;
|
||||
|
||||
int minX = loc.getBlockX() - 256;
|
||||
int minZ = loc.getBlockZ() - 256;
|
||||
int maxX = loc.getBlockX() + 256;
|
||||
int maxZ = loc.getBlockZ() + 256;
|
||||
|
||||
Queue<Location> chunkCoords = new LinkedList<>();
|
||||
for(int x = minX; x <= maxX; x += 16) {
|
||||
for(int z = minZ; z <= maxZ; z += 16) {
|
||||
chunkCoords.add(new Location(world, x, 0, z));
|
||||
}
|
||||
}
|
||||
|
||||
generateChunks(world, chunkCoords, () -> {
|
||||
String cmd = String.format("execute in %s run place structure %s %d %d %d",
|
||||
world.getKey().asString(), refCopy.name, finalLoc.getBlockX(), finalLoc.getBlockY(), finalLoc.getBlockZ());
|
||||
log.info("Executing command {}", cmd);
|
||||
|
||||
int foundationRadius = 16;
|
||||
|
||||
List<Location> foundationLocations = new ArrayList<>();
|
||||
for(int x = -foundationRadius; x <= foundationRadius; x++) {
|
||||
for(int z = -foundationRadius; z <= foundationRadius; z++) {
|
||||
Location fLoc = finalLoc.clone().add(x, -2, z);
|
||||
fLoc.getBlock().setType(Material.SMOOTH_SANDSTONE_STAIRS, false);
|
||||
foundationLocations.add(fLoc);
|
||||
}
|
||||
}
|
||||
|
||||
if(!Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd))
|
||||
log.error("Cannot place structure \"{}\"", refCopy.name);
|
||||
else
|
||||
log.info("Structure {} placed at {}", refCopy.name, finalLoc);
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(JavaPlugin.getProvidingPlugin(getClass()), () -> {
|
||||
for (Location fLoc : foundationLocations) {
|
||||
Block block = fLoc.getBlock();
|
||||
if(block.getType() == Material.SMOOTH_SANDSTONE_STAIRS)
|
||||
block.setType(Material.AIR, false);
|
||||
}
|
||||
}, 1);
|
||||
});
|
||||
// world.getChunkAtAsync(loc).thenAccept(chunk -> {
|
||||
// });
|
||||
// TODO Make this respect the underlying structure as best as possible
|
||||
xRange = yRange = zRange = 8;
|
||||
} else {
|
||||
Vector extents = structure.getSize().clone().multiply(0.5);
|
||||
loc.subtract(extents);
|
||||
structure.place(loc, true, rotation, mirror, 0, 1, new Random());
|
||||
xRange = structure.getSize().getBlockX();
|
||||
yRange = structure.getSize().getBlockY();
|
||||
zRange = structure.getSize().getBlockZ();
|
||||
}
|
||||
|
||||
Optional<BlockState> bedrock = Optional.empty();
|
||||
|
||||
|
||||
|
||||
Biome newBiome = null;
|
||||
boolean hasNewBiome = false;
|
||||
if(structure instanceof BiomedStructure) {
|
||||
newBiome = ((BiomedStructure) structure).biome;
|
||||
hasNewBiome = true;
|
||||
}
|
||||
|
||||
Set<Chunk> chunksToUpdate = new HashSet<>();
|
||||
|
||||
log.trace("Start search for bedrock, range {}x{}x{}", xRange, yRange, zRange);
|
||||
for (int x = loc.getBlockX() - xRange; x < loc.getBlockX() + xRange; x++) {
|
||||
for (int y = loc.getBlockY() - yRange; y < loc.getBlockY() + yRange; y++) {
|
||||
for (int z = loc.getBlockZ() - zRange; z < loc.getBlockZ() + zRange; z++) {
|
||||
Block b = world.getBlockAt(x, y, z);
|
||||
if (b.getType() == Material.BEDROCK || b.getType() == Material.LODESTONE) {
|
||||
System.out.println("Found " + b.getType().name() + " in placed structure, using that as centre");
|
||||
bedrock = Optional.of(b.getState());
|
||||
}
|
||||
if(hasNewBiome) {
|
||||
world.setBiome(x, y, z, newBiome);
|
||||
Chunk chunkAt = world.getChunkAt(x, z);
|
||||
chunksToUpdate.add(chunkAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(hasNewBiome) {
|
||||
log.trace("Refreshing {} chunks", chunksToUpdate.size());
|
||||
chunksToUpdate.forEach(c -> c.getWorld().refreshChunk(c.getX(), c.getZ()));
|
||||
}
|
||||
|
||||
if (bedrock.isPresent()) {
|
||||
loc = bedrock.get().getLocation();
|
||||
loc.add(0, 2, 0);
|
||||
}
|
||||
|
||||
return loc;
|
||||
}
|
||||
|
||||
private <T extends Enum<?>> T randomEnum(Class<T> enumCls) {
|
||||
assert (enumCls.isEnum());
|
||||
return randomElement(enumCls.getEnumConstants());
|
||||
}
|
||||
|
||||
private <T> T randomElement(T[] array) {
|
||||
int idx = (int) (Math.random() * array.length);
|
||||
return array[idx];
|
||||
}
|
||||
|
||||
public Location generateIslandNetwork(Location origin, Definition definition) {
|
||||
|
||||
Location centralIslandSpawnLoc = origin.clone();
|
||||
|
||||
TierDefinition[] tiers = definition.tiers;
|
||||
for (int i = 0; i < tiers.length; i++) {
|
||||
TierDefinition tier = tiers[i];
|
||||
|
||||
if(tier.tier == 0) {
|
||||
centralIslandSpawnLoc = placeStructureAtLocation(randomElement(tier.structures), centralIslandSpawnLoc, tier.validRotations, tier.validMirrors);
|
||||
continue;
|
||||
}
|
||||
|
||||
var slots = MathsUtils.sampleUniqueInts(tier.slotCount, tier.structures.length);
|
||||
var step = 360.0 / tier.slotCount;
|
||||
|
||||
placeIslandSets(origin, tier.structures, tier.spacing, slots, step, tier.validRotations, tier.validMirrors, tier.yNoise);
|
||||
}
|
||||
|
||||
return centralIslandSpawnLoc;
|
||||
}
|
||||
|
||||
public void placeIslandSets(Location origin, StructureRef[] islands, float islandSpacing, int[] slots, double step, StructureRotation[] validRotations, Mirror[] validMirrors, Supplier<Float> yNoiseFunc) {
|
||||
for (int i = 0; i < islands.length; i++) {
|
||||
StructureRef island = islands[i];
|
||||
int slot = slots[i];
|
||||
double angle = step * slot;
|
||||
double x = Math.cos(angle) * islandSpacing;
|
||||
double z = Math.sin(angle) * islandSpacing;
|
||||
double y = yNoiseFunc.get();
|
||||
Location pos = origin.clone().add(x, y, z);
|
||||
placeStructureAtLocation(island, pos, validRotations, validMirrors);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
32
src/main/java/com/ncguy/usefulskyblock/Reference.java
Normal file
32
src/main/java/com/ncguy/usefulskyblock/Reference.java
Normal file
|
@ -0,0 +1,32 @@
|
|||
package com.ncguy.usefulskyblock;
|
||||
|
||||
import com.ncguy.usefulskyblock.pdc.AnonymousDataContainerRef;
|
||||
import com.ncguy.usefulskyblock.pdc.IDataContainerRef;
|
||||
import com.ncguy.usefulskyblock.pdc.VectorPersistentDataType;
|
||||
import io.papermc.paper.datacomponent.DataComponentType;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class Reference {
|
||||
|
||||
public static NamespacedKey key(String key) {
|
||||
// return new NamespacedKey(JavaPlugin.getProvidingPlugin(Reference.class), key);
|
||||
return new NamespacedKey("usefulskyblock", key);
|
||||
}
|
||||
|
||||
public static IDataContainerRef<BlockVector, World> SKYBLOCK_TEAM_ROOT = new AnonymousDataContainerRef<>(key("world.skyblock.team"), VectorPersistentDataType.Instance);
|
||||
public static IDataContainerRef<Integer, World> SKYBLOCK_TEAM_COUNT = SKYBLOCK_TEAM_ROOT.withSuffix(".count").withType(PersistentDataType.INTEGER);
|
||||
public static IDataContainerRef<Boolean, World> WORLD_INIT = new AnonymousDataContainerRef<>(key("world.init"), PersistentDataType.BOOLEAN);
|
||||
|
||||
public static IDataContainerRef<BlockVector, Player> ISLAND_HOME_LOC = new AnonymousDataContainerRef<>(key("player.island.home.loc"), VectorPersistentDataType.Instance);
|
||||
|
||||
public static IDataContainerRef<Boolean, Item> ITEM_LAVA_IMMUNE = new AnonymousDataContainerRef<>(key("item.lava.immune"), PersistentDataType.BOOLEAN);
|
||||
|
||||
}
|
150
src/main/java/com/ncguy/usefulskyblock/SkyblockTeam.java
Normal file
150
src/main/java/com/ncguy/usefulskyblock/SkyblockTeam.java
Normal file
|
@ -0,0 +1,150 @@
|
|||
package com.ncguy.usefulskyblock;
|
||||
|
||||
import com.ncguy.usefulskyblock.events.TeamProgressionEvent;
|
||||
import com.ncguy.usefulskyblock.pdc.IDataContainerRef;
|
||||
import com.ncguy.usefulskyblock.pdc.VectorPersistentDataType;
|
||||
import com.ncguy.usefulskyblock.utils.TextUtils;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.advancement.Advancement;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
|
||||
import static com.ncguy.usefulskyblock.Reference.SKYBLOCK_TEAM_ROOT;
|
||||
|
||||
public class SkyblockTeam {
|
||||
|
||||
// Once deployed, the values of these can never change
|
||||
public static enum Tiers {
|
||||
TIER_0(0),
|
||||
|
||||
TIER_1(1),
|
||||
TIER_2(1 << 1),
|
||||
TIER_3(1 << 2),
|
||||
|
||||
|
||||
NETHER_TIER_1(1 << 8),
|
||||
NETHER_TIER_2(1 << 9),
|
||||
NETHER_TIER_3(1 << 10),
|
||||
;
|
||||
|
||||
|
||||
Tiers(long value) {
|
||||
this.value = value;
|
||||
this.displayName = TextUtils.toTitleCase(name());
|
||||
}
|
||||
|
||||
private final long value;
|
||||
private final String displayName;
|
||||
|
||||
public String displayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public boolean isSet(long mask) {
|
||||
return (mask & value) == value;
|
||||
}
|
||||
|
||||
public long set(long mask) {
|
||||
return mask | value;
|
||||
}
|
||||
|
||||
public long unset(long mask) {
|
||||
return mask & ~value;
|
||||
}
|
||||
}
|
||||
|
||||
private final NamespacedKey worldKey = new NamespacedKey("minecraft", "overworld");
|
||||
|
||||
private final World world;
|
||||
private final Team team;
|
||||
private IDataContainerRef<Location, World> homeIslandLoc;
|
||||
private IDataContainerRef<Long, World> tierProgress;
|
||||
|
||||
public SkyblockTeam(Team team) {
|
||||
this.team = team;
|
||||
this.world = Bukkit.getWorld(worldKey);
|
||||
|
||||
String sanitizedTeamName = TextUtils.sanitizeTeamName(team);
|
||||
|
||||
var teamRoot = SKYBLOCK_TEAM_ROOT.withSuffix("." + sanitizedTeamName);
|
||||
|
||||
this.homeIslandLoc = teamRoot.withType(VectorPersistentDataType.Instance)
|
||||
.assign(world)
|
||||
.map(x -> new Location(world, x.getX(), x.getY(), x.getZ()), loc -> loc.toVector().toBlockVector());
|
||||
|
||||
this.tierProgress = teamRoot
|
||||
.withSuffix(".progress")
|
||||
.withType(PersistentDataType.LONG)
|
||||
.assign(world);
|
||||
}
|
||||
|
||||
public Location getHomeIslandLoc() {
|
||||
return this.homeIslandLoc.get();
|
||||
}
|
||||
|
||||
public Team getTeam() {
|
||||
return team;
|
||||
}
|
||||
|
||||
public static Tiers isUnlockingAdvancement(Advancement adv) {
|
||||
|
||||
Key key = adv.key();
|
||||
if (key == Advancements.skyblock.SKYBLOCK_BEGIN.key())
|
||||
return Tiers.TIER_1;
|
||||
|
||||
if (key == Advancements.skyblock.GET_COBBLE.key())
|
||||
return Tiers.TIER_2;
|
||||
|
||||
if (key == Advancements.skyblock.VOID_BIN.key())
|
||||
return Tiers.TIER_3;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void tryUnlockTier(Tiers unlockingTier) {
|
||||
if (unlockingTier == Tiers.TIER_0)
|
||||
return;
|
||||
|
||||
unlockTier(unlockingTier);
|
||||
}
|
||||
|
||||
public long getCurrentTier() {
|
||||
return tierProgress.getOrDefault(Tiers.TIER_0.value);
|
||||
}
|
||||
|
||||
public boolean unlockTier(Tiers tier) {
|
||||
if(tier.isSet(tierProgress.getOrDefault(0L))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
new TeamProgressionEvent(team, tier).callEvent();
|
||||
tierProgress.set(tier.set(tierProgress.getOrDefault(0L)));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isAdvancementComplete(Advancement adv) {
|
||||
for (String entry : team.getEntries()) {
|
||||
Player player = Bukkit.getPlayer(entry);
|
||||
if (player == null) continue;
|
||||
if (player.getAdvancementProgress(adv).isDone())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
public void broadcast(ComponentLike text) {
|
||||
for (Audience a : team.audiences()) {
|
||||
a.sendMessage(text);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,16 +33,18 @@ public class StructureRef implements Structure {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
private Structure getActualStructure() {
|
||||
public Structure getActualStructure() {
|
||||
if (actualStructure == null) {
|
||||
StructureManager structureManager = Bukkit.getStructureManager();
|
||||
|
||||
actualStructure = structureManager.loadStructure(name);
|
||||
if(actualStructure == null) {
|
||||
try (InputStream resourceAsStream = getClass().getResourceAsStream("/datapacks/usefulskyblock/data/" + name.getNamespace() + "/structures/" + name.getKey() + ".nbt")) {
|
||||
actualStructure = structureManager.loadStructure(Objects.requireNonNull(resourceAsStream));
|
||||
actualStructure = structureManager.loadStructure(resourceAsStream);
|
||||
structureManager.registerStructure(name, actualStructure);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalArgumentException | IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +1,67 @@
|
|||
package com.ncguy.usefulskyblock;
|
||||
|
||||
import com.destroystokyo.paper.event.player.PlayerPostRespawnEvent;
|
||||
import com.ncguy.usefulskyblock.events.SkyblockConfigReloadEvent;
|
||||
import com.ncguy.usefulskyblock.handlers.*;
|
||||
import com.ncguy.usefulskyblock.recipe.BiomeRod;
|
||||
import com.ncguy.usefulskyblock.utils.Remark;
|
||||
import com.ncguy.usefulskyblock.utils.RemarkSet;
|
||||
import com.ncguy.usefulskyblock.recipe.IRecipeProvider;
|
||||
import com.ncguy.usefulskyblock.recipe.SmeltingCraftingHandler;
|
||||
import com.ncguy.usefulskyblock.world.PortalHandler;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.structure.Mirror;
|
||||
import org.bukkit.block.structure.StructureRotation;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.event.server.ServerLoadEvent;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.inventory.Recipe;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.Iterator;
|
||||
|
||||
public final class UsefulSkyblock extends JavaPlugin implements Listener {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(UsefulSkyblock.class);
|
||||
|
||||
private BiomeRod biomeRodListener;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
|
||||
System.out.println("OM EMABL:ED");
|
||||
|
||||
saveDefaultConfig();
|
||||
|
||||
// Plugin startup logic
|
||||
BiomeRod biomeRod = new BiomeRod();
|
||||
SmeltingCraftingHandler smeltingCraftingHandler = new SmeltingCraftingHandler();
|
||||
|
||||
IRecipeProvider[] recipeProviders = new IRecipeProvider[]{
|
||||
biomeRod,
|
||||
smeltingCraftingHandler
|
||||
};
|
||||
|
||||
PluginManager pluginManager = Bukkit.getPluginManager();
|
||||
pluginManager.registerEvents(this, this);
|
||||
pluginManager.registerEvents(new PortalHandler(), this);
|
||||
biomeRodListener = new BiomeRod();
|
||||
pluginManager.registerEvents(biomeRodListener, this);
|
||||
BiomeRod.Init(Bukkit.getServer());
|
||||
pluginManager.registerEvents(biomeRod, this);
|
||||
pluginManager.registerEvents(new CauldronCraftingHandler(), this);
|
||||
pluginManager.registerEvents(new ItemEventHandler(), this);
|
||||
pluginManager.registerEvents(new InitialisationHandler(), this);
|
||||
pluginManager.registerEvents(new TeamProgressHandler(), this);
|
||||
pluginManager.registerEvents(new FishingHandler(), this);
|
||||
|
||||
Server server = Bukkit.getServer();
|
||||
for (int i = recipeProviders.length-1; i >= 0; i--) {
|
||||
IRecipeProvider provider = recipeProviders[i];
|
||||
Iterable<Recipe> recipes = provider.provideRecipes();
|
||||
Iterator<Recipe> iterator = recipes.iterator();
|
||||
boolean isLastProvider = i == 0;
|
||||
while (iterator.hasNext())
|
||||
server.addRecipe(iterator.next(), isLastProvider && !iterator.hasNext());
|
||||
}
|
||||
|
||||
Advancements.init();
|
||||
|
||||
new SkyblockConfigReloadEvent(getConfig()).callEvent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
super.reloadConfig();
|
||||
if(biomeRodListener == null)
|
||||
return;
|
||||
RemarkSet remarks = biomeRodListener.reloadBiomeMap();
|
||||
for (Remark remark : remarks) {
|
||||
Bukkit.getServer().broadcast(Component.text(remark.domain, Style.style(TextColor.fromHexString("#777"))).appendSpace().append(Component.text(remark.message, Style.style(TextColor.fromHexString("#fff")))));
|
||||
}
|
||||
new SkyblockConfigReloadEvent(getConfig()).callEvent();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -68,82 +69,4 @@ public final class UsefulSkyblock extends JavaPlugin implements Listener {
|
|||
// Plugin shutdown logic
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
player.sendMessage(Component.text("Hello, " + player.getName() + "!"));
|
||||
PersistentDataContainer pdc = player.getPersistentDataContainer();
|
||||
NamespacedKey initKey = new NamespacedKey(this, "player_init");
|
||||
// if (pdc.has(initKey))
|
||||
// return;
|
||||
|
||||
NamespacedKey worldKey = new NamespacedKey(this, "void");
|
||||
World world = Bukkit.getWorld(worldKey);
|
||||
player.teleport(world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
player.setRespawnLocation(world.getSpawnLocation());
|
||||
pdc.set(initKey, PersistentDataType.BOOLEAN, true);
|
||||
player.sendMessage(Component.text("This is your first time playing on this server."));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerPostRespawn(PlayerPostRespawnEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
player.sendMessage(Component.text("Hello, " + player.getName() + "!"));
|
||||
PersistentDataContainer pdc = player.getPersistentDataContainer();
|
||||
NamespacedKey initKey = new NamespacedKey(this, "player_init");
|
||||
// if (pdc.has(initKey))
|
||||
// return;
|
||||
|
||||
NamespacedKey worldKey = new NamespacedKey(this, "void");
|
||||
World world = Bukkit.getWorld(worldKey);
|
||||
player.teleport(world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
player.setRespawnLocation(world.getSpawnLocation());
|
||||
pdc.set(initKey, PersistentDataType.BOOLEAN, true);
|
||||
player.sendMessage(Component.text("This is your first time playing on this server."));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerLoad(ServerLoadEvent event) {
|
||||
if(event.getType() != ServerLoadEvent.LoadType.STARTUP)
|
||||
return;
|
||||
|
||||
World world = Bukkit.getWorld(new NamespacedKey(this, "void"));
|
||||
if (world == null)
|
||||
return;
|
||||
|
||||
PersistentDataContainer pdc = world.getPersistentDataContainer();
|
||||
NamespacedKey initKey = new NamespacedKey(this, "world_init");
|
||||
if(pdc.has(initKey))
|
||||
return;
|
||||
pdc.set(initKey, PersistentDataType.BOOLEAN, true);
|
||||
|
||||
log.info("Generating spawn point for world {}", world.getName());
|
||||
StructureRef spawn = new StructureRef(new NamespacedKey(this, "spawn"));
|
||||
log.info("Entities in spawn: {}", spawn.getEntities().size());
|
||||
log.info("Palettes in spawn: {}", spawn.getPalettes().size());
|
||||
log.info("Palette count in spawn: {}", spawn.getPaletteCount());
|
||||
log.info("spawn.getSize(): {}", spawn.getSize());
|
||||
spawn.place(new Location(world, 0, 64, 0), true, StructureRotation.NONE, Mirror.NONE, 0, 1, new Random(System.currentTimeMillis()));
|
||||
world.setSpawnLocation(0, 64, 0);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldLoad(WorldLoadEvent event) {
|
||||
World world = event.getWorld();
|
||||
if (!world.getKey().equals(new NamespacedKey(this, "void")))
|
||||
return;
|
||||
|
||||
PersistentDataContainer pdc = world.getPersistentDataContainer();
|
||||
NamespacedKey initKey = new NamespacedKey(this, "world_init");
|
||||
if(pdc.has(initKey))
|
||||
return;
|
||||
pdc.set(initKey, PersistentDataType.BOOLEAN, true);
|
||||
|
||||
log.info("Generating spawn point for world {}", world.getName());
|
||||
StructureRef spawn = new StructureRef(new NamespacedKey(this, "spawn"));
|
||||
spawn.place(new Location(world, 0, 64, 0), true, StructureRotation.NONE, Mirror.NONE, 0, 1, new Random(System.currentTimeMillis()));
|
||||
world.setSpawnLocation(0, 64, 0);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.ncguy.usefulskyblock.command;
|
||||
|
||||
import com.ncguy.usefulskyblock.Reference;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Server;
|
||||
|
@ -33,11 +34,7 @@ public abstract class AbstractSkyblockCommand {
|
|||
|
||||
}
|
||||
|
||||
protected NamespacedKey key(String name) {
|
||||
return new NamespacedKey("usefulskyblock", name);
|
||||
}
|
||||
|
||||
protected NamespacedKey overworldKey = key("void");
|
||||
protected NamespacedKey overworldKey = Reference.key("void");
|
||||
|
||||
private Server serverInstance;
|
||||
protected Server getServer() {
|
||||
|
|
|
@ -4,17 +4,23 @@ import com.mojang.brigadier.StringReader;
|
|||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import com.ncguy.usefulskyblock.Reference;
|
||||
import com.ncguy.usefulskyblock.SkyblockTeam;
|
||||
import com.ncguy.usefulskyblock.StructureRef;
|
||||
import com.ncguy.usefulskyblock.handlers.InitialisationHandler;
|
||||
import com.ncguy.usefulskyblock.utils.BoxVisualizer;
|
||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||
import io.papermc.paper.command.brigadier.Commands;
|
||||
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import io.papermc.paper.registry.RegistryAccess;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.structure.Mirror;
|
||||
import org.bukkit.block.structure.StructureRotation;
|
||||
import org.bukkit.entity.Entity;
|
||||
|
@ -22,6 +28,9 @@ import org.bukkit.entity.Player;
|
|||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scoreboard.Scoreboard;
|
||||
import org.bukkit.scoreboard.ScoreboardManager;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
import org.bukkit.structure.Structure;
|
||||
import org.bukkit.structure.StructureManager;
|
||||
import org.bukkit.util.RayTraceResult;
|
||||
|
@ -32,242 +41,343 @@ import org.slf4j.LoggerFactory;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class SkyblockAdminCommand extends AbstractSkyblockCommand {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SkyblockAdminCommand.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(SkyblockAdminCommand.class);
|
||||
|
||||
private BoxVisualizer box = new BoxVisualizer();
|
||||
private BoxVisualizer box = new BoxVisualizer();
|
||||
|
||||
public int executeStructuresList(CommandContext<CommandSourceStack> ctx) {
|
||||
public int executeStructuresList(CommandContext<CommandSourceStack> ctx) {
|
||||
|
||||
StructureManager structureManager = getServer().getStructureManager();
|
||||
Map<NamespacedKey, Structure> structureMap = structureManager.getStructures();
|
||||
StructureManager structureManager = getServer().getStructureManager();
|
||||
Map<NamespacedKey, Structure> structureMap = structureManager.getStructures();
|
||||
|
||||
// Set<NamespacedKey> filteredKeys = structureMap.keySet().stream().filter(k -> k.getNamespace() == "usefulskyblock").collect(Collectors.toSet());
|
||||
Set<NamespacedKey> filteredKeys = structureMap.keySet();
|
||||
Set<NamespacedKey> filteredKeys = structureMap.keySet();
|
||||
|
||||
ctx.getSource().getExecutor().sendMessage("Found " + filteredKeys.size() + " structures");
|
||||
filteredKeys.forEach(k -> ctx.getSource().getExecutor().sendMessage(k.toString()));
|
||||
ctx.getSource().getExecutor().sendMessage("Found " + filteredKeys.size() + " structures");
|
||||
filteredKeys.forEach(k -> ctx.getSource().getExecutor().sendMessage(k.toString()));
|
||||
|
||||
log.info("Classic: {}", structureManager.loadStructure(key("classic")) != null);
|
||||
log.info("Classic-sand: {}", structureManager.loadStructure(key("classic-sand")) != null);
|
||||
log.info("skyblock: {}", structureManager.loadStructure(key("skyblock")) != null);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
public int executeSave(CommandContext<CommandSourceStack> ctx) {
|
||||
int radius = ctx.getArgument("radius", Integer.class);
|
||||
String name = ctx.getArgument("name", String.class);
|
||||
|
||||
StructureManager structureManager = getServer().getStructureManager();
|
||||
|
||||
Structure structure = structureManager.createStructure();
|
||||
|
||||
Location origin = ctx.getSource().getLocation();
|
||||
|
||||
Location start = origin.clone().subtract(radius, radius, radius);
|
||||
Location end = origin.clone().add(radius, radius, radius);
|
||||
|
||||
structure.fill(start, end, true);
|
||||
|
||||
ctx.getSource().getExecutor().sendMessage("From " + start + " to " + end);
|
||||
ctx.getSource().getExecutor().sendMessage("Palette count: " + (long) structure.getPaletteCount());
|
||||
ctx.getSource().getExecutor().sendMessage("Block count: " + structure.getPalettes().getFirst().getBlockCount());
|
||||
|
||||
NamespacedKey nmKey = Reference.key(name);
|
||||
|
||||
try {
|
||||
File f = new File("structures/" + nmKey.getKey() + ".nbt");
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
f.getParentFile().mkdirs();
|
||||
structureManager.saveStructure(f, structure);
|
||||
log.info("Saved structure {} to {}", name, f.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
public int executeSave(CommandContext<CommandSourceStack> ctx) {
|
||||
int radius = ctx.getArgument("radius", Integer.class);
|
||||
String name = ctx.getArgument("name", String.class);
|
||||
return 0;
|
||||
}
|
||||
|
||||
StructureManager structureManager = getServer().getStructureManager();
|
||||
public int executeDropStructure(CommandContext<CommandSourceStack> ctx) {
|
||||
|
||||
Structure structure = structureManager.createStructure();
|
||||
Structure structure = ctx.getArgument("structure", Structure.class);
|
||||
|
||||
Location origin = ctx.getSource().getLocation();
|
||||
|
||||
Location start = origin.clone().subtract(radius, radius, radius);
|
||||
Location end = origin.clone().add(radius, radius, radius);
|
||||
|
||||
structure.fill(start, end, true);
|
||||
|
||||
ctx.getSource().getExecutor().sendMessage("From " + start + " to " + end);
|
||||
ctx.getSource().getExecutor().sendMessage("Palette count: " + (long) structure.getPaletteCount());
|
||||
ctx.getSource().getExecutor().sendMessage("Block count: " + structure.getPalettes().getFirst().getBlockCount());
|
||||
|
||||
NamespacedKey nmKey = key(name);
|
||||
|
||||
try {
|
||||
File f = new File("structures/" + nmKey.getKey() + ".nbt");
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
f.getParentFile().mkdirs();
|
||||
structureManager.saveStructure(f, structure);
|
||||
log.info("Saved structure {} to {}", name, f.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (structure == null) {
|
||||
ctx.getSource().getExecutor().sendMessage("Structure not found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int executeDropStructure(CommandContext<CommandSourceStack> ctx) {
|
||||
Location loc = ctx.getSource().getLocation();
|
||||
Vector halfSize = structure.getSize().divide(new Vector(2, 2, 2));
|
||||
loc.subtract(halfSize);
|
||||
|
||||
Structure structure = ctx.getArgument("structure", Structure.class);
|
||||
structure.place(loc, false, StructureRotation.NONE, Mirror.NONE, 0, 1, new Random(System.currentTimeMillis()));
|
||||
|
||||
if (structure == null) {
|
||||
ctx.getSource().getExecutor().sendMessage("Structure not found");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Location loc = ctx.getSource().getLocation();
|
||||
Vector halfSize = structure.getSize().divide(new Vector(2, 2, 2));
|
||||
loc.subtract(halfSize);
|
||||
public int executePlaceAnchor(CommandContext<CommandSourceStack> ctx) {
|
||||
ctx.getSource().getExecutor().sendMessage("Placing anchor");
|
||||
Location target = ctx.getSource().getExecutor().getLocation().subtract(0, 1, 0);
|
||||
target.getBlock().setType(Material.STONE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
structure.place(loc, false, StructureRotation.NONE, Mirror.NONE, 0, 1, new Random(System.currentTimeMillis()));
|
||||
public int executeP0(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
executor.sendMessage("Placing P0");
|
||||
|
||||
return 0;
|
||||
if (executor instanceof Player) {
|
||||
Player p = (Player) executor;
|
||||
RayTraceResult rayTraceResult = p.rayTraceBlocks(8);
|
||||
|
||||
Vector hitPosition = rayTraceResult.getHitPosition();
|
||||
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
||||
pdc.set(Reference.key("_p0"), PersistentDataType.INTEGER_ARRAY,
|
||||
new int[]{hitPosition.getBlockX(), hitPosition.getBlockY(), hitPosition.getBlockZ()});
|
||||
|
||||
Location p0 = new Location(p.getWorld(), hitPosition.getX(), hitPosition.getY(), hitPosition.getZ());
|
||||
Location p1 = p0.clone();
|
||||
|
||||
NamespacedKey p1Key = Reference.key("_p1");
|
||||
if (pdc.has(p1Key, PersistentDataType.INTEGER_ARRAY)) {
|
||||
int[] ints = pdc.get(p1Key, PersistentDataType.INTEGER_ARRAY);
|
||||
p1.set(ints[0], ints[1], ints[2]);
|
||||
}
|
||||
|
||||
box.createBox(p0, p1);
|
||||
}
|
||||
|
||||
public int executePlaceAnchor(CommandContext<CommandSourceStack> ctx) {
|
||||
ctx.getSource().getExecutor().sendMessage("Placing anchor");
|
||||
Location target = ctx.getSource().getExecutor().getLocation().subtract(0, 1, 0);
|
||||
target.getBlock().setType(Material.STONE);
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int executeP1(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
executor.sendMessage("Placing P1");
|
||||
|
||||
if (executor instanceof Player) {
|
||||
Player p = (Player) executor;
|
||||
RayTraceResult rayTraceResult = p.rayTraceBlocks(8);
|
||||
|
||||
Vector hitPosition = rayTraceResult.getHitPosition();
|
||||
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
||||
pdc.set(Reference.key("_p1"), PersistentDataType.INTEGER_ARRAY,
|
||||
new int[]{hitPosition.getBlockX(), hitPosition.getBlockY(), hitPosition.getBlockZ()});
|
||||
|
||||
Location p1 = new Location(p.getWorld(), hitPosition.getX(), hitPosition.getY(), hitPosition.getZ());
|
||||
Location p0 = p1.clone();
|
||||
|
||||
NamespacedKey p0Key = Reference.key("_p0");
|
||||
if (pdc.has(p0Key, PersistentDataType.INTEGER_ARRAY)) {
|
||||
int[] ints = pdc.get(p0Key, PersistentDataType.INTEGER_ARRAY);
|
||||
p0.set(ints[0], ints[1], ints[2]);
|
||||
}
|
||||
|
||||
box.createBox(p0, p1);
|
||||
|
||||
}
|
||||
|
||||
public int executeP0(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
executor.sendMessage("Placing P0");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (executor instanceof Player) {
|
||||
Player p = (Player) executor;
|
||||
RayTraceResult rayTraceResult = p.rayTraceBlocks(8);
|
||||
public int executePSave(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
String name = ctx.getArgument("name", String.class);
|
||||
executor.sendMessage("Saving structure");
|
||||
|
||||
Vector hitPosition = rayTraceResult.getHitPosition();
|
||||
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
||||
pdc.set(key("_p0"), PersistentDataType.INTEGER_ARRAY, new int[]{hitPosition.getBlockX(), hitPosition.getBlockY(), hitPosition.getBlockZ()});
|
||||
if (!(executor instanceof Player)) return 0;
|
||||
Player p = (Player) executor;
|
||||
|
||||
Location p0 = new Location(p.getWorld(), hitPosition.getX(), hitPosition.getY(), hitPosition.getZ());
|
||||
Location p1 = p0.clone();
|
||||
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
||||
int[] p0 = pdc.get(Reference.key("_p0"), PersistentDataType.INTEGER_ARRAY);
|
||||
int[] p1 = pdc.get(Reference.key("_p1"), PersistentDataType.INTEGER_ARRAY);
|
||||
|
||||
NamespacedKey p1Key = key("_p1");
|
||||
if(pdc.has(p1Key, PersistentDataType.INTEGER_ARRAY)) {
|
||||
int[] ints = pdc.get(p1Key, PersistentDataType.INTEGER_ARRAY);
|
||||
p1.set(ints[0], ints[1], ints[2]);
|
||||
}
|
||||
|
||||
box.createBox(p0, p1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (p0 == null) {
|
||||
executor.sendMessage("No P0 found");
|
||||
return -1;
|
||||
} else if (p1 == null) {
|
||||
executor.sendMessage("No P1 found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int executeP1(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
executor.sendMessage("Placing P1");
|
||||
Structure structure = getServer().getStructureManager().createStructure();
|
||||
|
||||
if (executor instanceof Player) {
|
||||
Player p = (Player) executor;
|
||||
RayTraceResult rayTraceResult = p.rayTraceBlocks(8);
|
||||
World world = executor.getWorld();
|
||||
Location p0Loc = new Location(world, p0[0], p0[1], p0[2]);
|
||||
Location p1Loc = new Location(world, p1[0], p1[1], p1[2]);
|
||||
|
||||
Vector hitPosition = rayTraceResult.getHitPosition();
|
||||
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
||||
pdc.set(key("_p1"), PersistentDataType.INTEGER_ARRAY, new int[]{hitPosition.getBlockX(), hitPosition.getBlockY(), hitPosition.getBlockZ()});
|
||||
structure.fill(p0Loc, p1Loc, true);
|
||||
|
||||
Location p1 = new Location(p.getWorld(), hitPosition.getX(), hitPosition.getY(), hitPosition.getZ());
|
||||
Location p0 = p1.clone();
|
||||
ctx.getSource().getExecutor().sendMessage("From " + p0Loc + " to " + p1Loc);
|
||||
ctx.getSource().getExecutor().sendMessage("Palette count: " + (long) structure.getPaletteCount());
|
||||
ctx.getSource().getExecutor().sendMessage("Block count: " + structure.getPalettes().getFirst().getBlockCount());
|
||||
|
||||
NamespacedKey p0Key = key("_p0");
|
||||
if(pdc.has(p0Key, PersistentDataType.INTEGER_ARRAY)) {
|
||||
int[] ints = pdc.get(p0Key, PersistentDataType.INTEGER_ARRAY);
|
||||
p0.set(ints[0], ints[1], ints[2]);
|
||||
}
|
||||
NamespacedKey nmKey = Reference.key(name);
|
||||
|
||||
box.createBox(p0, p1);
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
try {
|
||||
File f = new File("structures/" + nmKey.getKey() + ".nbt");
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
f.getParentFile().mkdirs();
|
||||
getServer().getStructureManager().saveStructure(f, structure);
|
||||
log.info("Saved structure {} to {}", name, f.getAbsolutePath());
|
||||
pdc.remove(Reference.key("_p0"));
|
||||
pdc.remove(Reference.key("_p1"));
|
||||
box.cleanup();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
public int executePSave(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
String name = ctx.getArgument("name", String.class);
|
||||
executor.sendMessage("Saving structure");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(executor instanceof Player)) return 0;
|
||||
Player p = (Player) executor;
|
||||
public static LiteralCommandNode<CommandSourceStack> create() {
|
||||
var root = Commands.literal("skyblock-admin");
|
||||
var cmd = Get(SkyblockAdminCommand.class);
|
||||
|
||||
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
||||
int[] p0 = pdc.get(key("_p0"), PersistentDataType.INTEGER_ARRAY);
|
||||
int[] p1 = pdc.get(key("_p1"), PersistentDataType.INTEGER_ARRAY);
|
||||
root.requires(cmd::auth);
|
||||
|
||||
if (p0 == null) {
|
||||
executor.sendMessage("No P0 found");
|
||||
return -1;
|
||||
} else if (p1 == null) {
|
||||
executor.sendMessage("No P1 found");
|
||||
return -1;
|
||||
}
|
||||
root.then(Commands.literal("reload").executes(cmd::executeReloadConfig));
|
||||
|
||||
Structure structure = getServer().getStructureManager().createStructure();
|
||||
var structures = Commands.literal("structures");
|
||||
structures.then(Commands.literal("list").executes(cmd::executeStructuresList));
|
||||
structures.then(Commands.literal("save")
|
||||
.then(Commands.argument("radius", IntegerArgumentType.integer())
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.executes(cmd::executeSave))));
|
||||
structures.then(Commands.literal("load")
|
||||
.then(Commands.argument("structure", new SkyblockStructureArgument())
|
||||
.executes(cmd::executeDropStructure)));
|
||||
structures.then(Commands.literal("anchor").executes(cmd::executePlaceAnchor));
|
||||
structures.then(Commands.literal("end").executes(cmd::executeBuildCentralEndPortal));
|
||||
|
||||
Location p0Loc = new Location(getOverworld(), p0[0], p0[1], p0[2]);
|
||||
Location p1Loc = new Location(getOverworld(), p1[0], p1[1], p1[2]);
|
||||
structures.then(Commands.literal("p0").executes(cmd::executeP0));
|
||||
structures.then(Commands.literal("p1").executes(cmd::executeP1));
|
||||
structures.then(Commands.literal("pSave")
|
||||
.then(Commands.argument("name", StringArgumentType.word()).executes(cmd::executePSave)));
|
||||
|
||||
structure.fill(p0Loc, p1Loc, true);
|
||||
var islands = Commands.literal("islands");
|
||||
var unlockCmd = Commands.literal("unlock");
|
||||
unlockCmd.then(
|
||||
Commands.argument("tier", new EnumArgument<>(SkyblockTeam.Tiers.class)).executes(cmd::executeIslandUnlock));
|
||||
unlockCmd.then(Commands.argument("team", new SkyblockGenCommand.SkyblockTeamArgument())
|
||||
.executes(cmd::executeIslandUnlockForTeam));
|
||||
islands.then(unlockCmd);
|
||||
|
||||
ctx.getSource().getExecutor().sendMessage("From " + p0Loc + " to " + p1Loc);
|
||||
ctx.getSource().getExecutor().sendMessage("Palette count: " + (long) structure.getPaletteCount());
|
||||
ctx.getSource().getExecutor().sendMessage("Block count: " + structure.getPalettes().getFirst().getBlockCount());
|
||||
root.then(structures);
|
||||
root.then(islands);
|
||||
|
||||
NamespacedKey nmKey = key(name);
|
||||
return root.build();
|
||||
}
|
||||
|
||||
try {
|
||||
File f = new File("structures/" + nmKey.getKey() + ".nbt");
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
f.getParentFile().mkdirs();
|
||||
getServer().getStructureManager().saveStructure(f, structure);
|
||||
log.info("Saved structure {} to {}", name, f.getAbsolutePath());
|
||||
pdc.remove(key("_p0"));
|
||||
pdc.remove(key("_p1"));
|
||||
box.cleanup();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
private int executeBuildCentralEndPortal(CommandContext<CommandSourceStack> ctx) {
|
||||
ctx.getSource().getExecutor().sendMessage("Building central end portal");
|
||||
InitialisationHandler.initOverworld(ctx.getSource().getLocation().getWorld(), true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
private int executeIslandUnlock(CommandContext<CommandSourceStack> ctx) {
|
||||
SkyblockTeam.Tiers tier = ctx.getArgument("tier", SkyblockTeam.Tiers.class);
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
|
||||
if (!(executor instanceof Player player)) {
|
||||
executor.sendMessage("Only players can use this command");
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static LiteralCommandNode<CommandSourceStack> create() {
|
||||
var root = Commands.literal("skyblock-admin");
|
||||
var cmd = Get(SkyblockAdminCommand.class);
|
||||
ScoreboardManager scoreboardManager = Bukkit.getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
Team team = scoreboard.getPlayerTeam(player);
|
||||
|
||||
root.requires(cmd::auth);
|
||||
|
||||
|
||||
root.then(Commands.literal("reload").executes(cmd::executeReloadConfig));
|
||||
|
||||
var structures = Commands.literal("structures");
|
||||
structures.then(Commands.literal("list").executes(cmd::executeStructuresList));
|
||||
structures.then(Commands.literal("save").then(Commands.argument("radius", IntegerArgumentType.integer()).then(Commands.argument("name", StringArgumentType.word()).executes(cmd::executeSave))));
|
||||
structures.then(Commands.literal("load").then(Commands.argument("structure", new SkyblockStructureArgument()).executes(cmd::executeDropStructure)));
|
||||
structures.then(Commands.literal("anchor").executes(cmd::executePlaceAnchor));
|
||||
|
||||
structures.then(Commands.literal("p0").executes(cmd::executeP0));
|
||||
structures.then(Commands.literal("p1").executes(cmd::executeP1));
|
||||
structures.then(Commands.literal("pSave").then(Commands.argument("name", StringArgumentType.word()).executes(cmd::executePSave)));
|
||||
|
||||
root.then(structures);
|
||||
|
||||
return root.build();
|
||||
if (team == null) {
|
||||
player.sendMessage("No team found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int executeReloadConfig(CommandContext<CommandSourceStack> context) {
|
||||
JavaPlugin.getProvidingPlugin(SkyblockAdminCommand.class).reloadConfig();
|
||||
if (!new SkyblockTeam(team).unlockTier(tier)) player.sendMessage("Failed to unlock tier, likely already unlocked.");
|
||||
else player.sendMessage("Successfully unlocked tier for team \"" + team.getName() + "\"");
|
||||
|
||||
context.getSource().getExecutor().sendMessage("Reloaded config");
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int executeIslandUnlockForTeam(CommandContext<CommandSourceStack> ctx) {
|
||||
SkyblockTeam.Tiers tier = ctx.getArgument("tier", SkyblockTeam.Tiers.class);
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
|
||||
if (!(executor instanceof Player player)) {
|
||||
executor.sendMessage("Only players can use this command");
|
||||
return -1;
|
||||
}
|
||||
|
||||
private boolean auth(CommandSourceStack stack) {
|
||||
return stack.getSender().isOp();
|
||||
player.sendMessage("Unlocking island tier " + tier);
|
||||
Team team = ctx.getArgument("team", Team.class);
|
||||
|
||||
if (team == null) {
|
||||
player.sendMessage("No team found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!new SkyblockTeam(team).unlockTier(tier)) player.sendMessage("Failed to unlock tier, likely already unlocked.");
|
||||
else player.sendMessage("Successfully unlocked tier for team \"" + team.getName() + "\"");
|
||||
|
||||
public static final class SkyblockStructureArgument implements CustomArgumentType<Structure, String> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Structure parse(StringReader reader) throws CommandSyntaxException {
|
||||
NamespacedKey key = new NamespacedKey("usefulskyblock", reader.readString());
|
||||
return new StructureRef(key);
|
||||
}
|
||||
private int executeReloadConfig(CommandContext<CommandSourceStack> context) {
|
||||
JavaPlugin.getProvidingPlugin(SkyblockAdminCommand.class).reloadConfig();
|
||||
|
||||
@Override
|
||||
public ArgumentType<String> getNativeType() {
|
||||
return StringArgumentType.word();
|
||||
}
|
||||
context.getSource().getExecutor().sendMessage("Reloaded config");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private boolean auth(CommandSourceStack stack) {
|
||||
return stack.getSender().isOp();
|
||||
}
|
||||
|
||||
|
||||
public static final class SkyblockStructureArgument implements CustomArgumentType<Structure, String> {
|
||||
|
||||
@Override
|
||||
public Structure parse(StringReader reader) throws CommandSyntaxException {
|
||||
NamespacedKey key = new NamespacedKey("usefulskyblock", reader.readString());
|
||||
return new StructureRef(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgumentType<String> getNativeType() {
|
||||
return StringArgumentType.word();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class EnumArgument<T extends Enum<T>> implements CustomArgumentType<T, String> {
|
||||
|
||||
private final Class<T> enumClass;
|
||||
|
||||
public EnumArgument(Class<T> enumClass) {
|
||||
this.enumClass = enumClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T parse(StringReader reader) throws CommandSyntaxException {
|
||||
T[] enumConstants = enumClass.getEnumConstants();
|
||||
String input = reader.readString();
|
||||
for (T enumConstant : enumConstants) {
|
||||
if (enumConstant.name().equals(input)) return enumConstant;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgumentType<String> getNativeType() {
|
||||
return StringArgumentType.word();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
|
||||
T[] enumConstants = enumClass.getEnumConstants();
|
||||
for (T c : enumConstants) {
|
||||
builder.suggest(c.name());
|
||||
}
|
||||
return CustomArgumentType.super.listSuggestions(context, builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,225 +1,270 @@
|
|||
package com.ncguy.usefulskyblock.command;
|
||||
|
||||
import com.mojang.brigadier.StringReader;
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import com.ncguy.usefulskyblock.Advancements;
|
||||
import com.ncguy.usefulskyblock.IslandNetworkGenerator;
|
||||
import com.ncguy.usefulskyblock.Reference;
|
||||
import com.ncguy.usefulskyblock.StructureRef;
|
||||
import com.ncguy.usefulskyblock.data.BiomedStructure;
|
||||
import com.ncguy.usefulskyblock.utils.MathsUtils;
|
||||
import com.ncguy.usefulskyblock.utils.TextUtils;
|
||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||
import io.papermc.paper.command.brigadier.Commands;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.advancement.Advancement;
|
||||
import org.bukkit.advancement.AdvancementProgress;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.structure.Mirror;
|
||||
import org.bukkit.block.structure.StructureRotation;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.scoreboard.Scoreboard;
|
||||
import org.bukkit.scoreboard.ScoreboardManager;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
import org.bukkit.structure.Structure;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static com.ncguy.usefulskyblock.Reference.key;
|
||||
|
||||
public class SkyblockGenCommand extends AbstractSkyblockCommand {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SkyblockGenCommand.class);
|
||||
|
||||
private StructureRef[] centralIslands = {
|
||||
new StructureRef(key("classic")),
|
||||
};
|
||||
private float playerIslandSpacing = 4096;
|
||||
|
||||
private StructureRef[] t1Islands = {
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
};
|
||||
private StructureRef[] centralIslands = {new BiomedStructure(key("classic"), Biome.OCEAN),};
|
||||
|
||||
private StructureRef[] t2Islands = {
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
};
|
||||
private static StructureRef[] t1Islands = {new BiomedStructure(key("classic-sand"), Biome.BEACH),};
|
||||
|
||||
private StructureRef[] t3Islands = {
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
new StructureRef(key("classic")),
|
||||
new StructureRef(key("classic-sand")),
|
||||
};
|
||||
private static StructureRef[] t2Islands = {new BiomedStructure(NamespacedKey.minecraft("igloo/top"),
|
||||
Biome.SNOWY_PLAINS), new BiomedStructure(
|
||||
NamespacedKey.minecraft("swamp_hut"), Biome.SWAMP),};
|
||||
|
||||
|
||||
private float playerIslandSpacing = 1024;
|
||||
private static StructureRef[] t3Islands = {new BiomedStructure(NamespacedKey.minecraft("monument"),
|
||||
Biome.DEEP_OCEAN), new BiomedStructure(
|
||||
NamespacedKey.minecraft("trail_ruins"), Biome.TAIGA), new BiomedStructure(NamespacedKey.minecraft("stronghold"),
|
||||
Biome.THE_VOID), new BiomedStructure(
|
||||
NamespacedKey.minecraft("mansion"), Biome.JUNGLE), new BiomedStructure(NamespacedKey.minecraft("ancient_city"),
|
||||
Biome.DEEP_DARK),};
|
||||
|
||||
private static final int T1_ISLAND_SLOTS = 8;
|
||||
private static final int T2_ISLAND_SLOTS = 24;
|
||||
private static final int T3_ISLAND_SLOTS = 48;
|
||||
private static final int T2_ISLAND_SLOTS = t2Islands.length;
|
||||
private static final int T3_ISLAND_SLOTS = t3Islands.length;
|
||||
|
||||
private static float T1_ISLAND_SPACING = T1_ISLAND_SLOTS << 3;
|
||||
private static float T2_ISLAND_SPACING = T2_ISLAND_SLOTS << 3;
|
||||
private static float T3_ISLAND_SPACING = T3_ISLAND_SLOTS << 3;
|
||||
|
||||
private Location placeStructureAtLocation(Structure structure, Location loc, boolean canRotate, boolean canMirror) {
|
||||
Vector extents = structure.getSize().clone().multiply(0.5);
|
||||
loc.subtract(extents);
|
||||
|
||||
StructureRotation rotation = canRotate ? randomEnum(StructureRotation.class) : StructureRotation.NONE;
|
||||
Mirror mirror = canMirror ? randomEnum(Mirror.class) : Mirror.NONE;
|
||||
structure.place(loc, true, rotation, mirror, 0, 1, new Random());
|
||||
|
||||
// Optional<BlockState> bedrock2 = structure.getPalettes().getFirst().getBlocks().stream().filter(b -> b.getBlockData().getMaterial() == Material.BEDROCK).findFirst();
|
||||
|
||||
Optional<BlockState> bedrock = Optional.empty();
|
||||
for(int x = loc.getBlockX(); x < loc.getBlockX() + structure.getSize().getBlockX(); x++) {
|
||||
for(int y = loc.getBlockY(); y < loc.getBlockY() + structure.getSize().getBlockY(); y++) {
|
||||
for(int z = loc.getBlockZ(); z < loc.getBlockZ() + structure.getSize().getBlockZ(); z++) {
|
||||
Block b = loc.getWorld().getBlockAt(x, y, z);
|
||||
if(b.getType() == Material.BEDROCK) {
|
||||
bedrock = Optional.of(b.getState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(bedrock.isPresent()) {
|
||||
loc = bedrock.get().getLocation();
|
||||
loc.add(0, 2, 0);
|
||||
}
|
||||
|
||||
return loc;
|
||||
}
|
||||
|
||||
private <T extends Enum<?>> T randomEnum(Class<T> enumCls) {
|
||||
assert(enumCls.isEnum());
|
||||
return randomElement(enumCls.getEnumConstants());
|
||||
}
|
||||
|
||||
private <T> T randomElement(T[] array) {
|
||||
int idx = (int) (Math.random() * array.length);
|
||||
return array[idx];
|
||||
}
|
||||
|
||||
private Location generateIslandNetwork(Location origin) {
|
||||
Location centralIslandSpawnLoc = placeStructureAtLocation(randomElement(centralIslands), origin.clone(), false, false);
|
||||
|
||||
int[] t1Slots = MathsUtils.sampleUniqueInts(T1_ISLAND_SLOTS, t1Islands.length);
|
||||
int[] t2Slots = MathsUtils.sampleUniqueInts(T2_ISLAND_SLOTS, t2Islands.length);
|
||||
int[] t3Slots = MathsUtils.sampleUniqueInts(T3_ISLAND_SLOTS, t3Islands.length);
|
||||
double t1Step = 360.0 / T1_ISLAND_SLOTS;
|
||||
double t2Step = 360.0 / T2_ISLAND_SLOTS;
|
||||
double t3Step = 360.0 / T3_ISLAND_SLOTS;
|
||||
|
||||
Supplier<Float> yNoiseFunc = () -> 0f;
|
||||
|
||||
extracted(origin, t1Islands, T1_ISLAND_SPACING, t1Slots, t1Step, yNoiseFunc);
|
||||
extracted(origin, t2Islands, T2_ISLAND_SPACING, t2Slots, t2Step, yNoiseFunc);
|
||||
extracted(origin, t3Islands, T3_ISLAND_SPACING, t3Slots, t3Step, yNoiseFunc);
|
||||
return centralIslandSpawnLoc;
|
||||
}
|
||||
|
||||
private void extracted(Location origin, StructureRef[] islands, float islandSpacing, int[] slots, double step, Supplier<Float> yNoiseFunc) {
|
||||
for (int i = 0; i < islands.length; i++) {
|
||||
StructureRef island = islands[i];
|
||||
int slot = slots[i];
|
||||
double angle = step * slot;
|
||||
double x = Math.cos(angle) * islandSpacing;
|
||||
double z = Math.sin(angle) * islandSpacing;
|
||||
double y = yNoiseFunc.get();
|
||||
Location pos = origin.clone().add(x, y, z);
|
||||
placeStructureAtLocation(island, pos, true, true);
|
||||
}
|
||||
}
|
||||
private static float T2_ISLAND_SPACING = 192; // 24 << 3
|
||||
private static float T3_ISLAND_SPACING = 384; // 48 << 3
|
||||
|
||||
public int executeGenerate(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
if(!(executor instanceof Player))
|
||||
return 0;
|
||||
if (!(executor instanceof Player player)) return 0;
|
||||
|
||||
World overworld = getServer().getWorld(new NamespacedKey("minecraft", "overworld"));
|
||||
|
||||
PersistentDataContainer pdc = executor.getPersistentDataContainer();
|
||||
|
||||
ScoreboardManager scoreboardManager = getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
Player player = (Player) executor;
|
||||
Team playerTeam = scoreboard.getPlayerTeam(player);
|
||||
if(playerTeam == null) {
|
||||
if (playerTeam == null) {
|
||||
executor.sendMessage("Not part of a team, can't generate skyblock world");
|
||||
return 0;
|
||||
}
|
||||
// TODO Sanitize playerTeam.getName()
|
||||
String playerTeamName = playerTeam.getName();
|
||||
String sanitizedTeamName = playerTeamName.replaceAll("[^a-z0-9.]", "_");
|
||||
|
||||
PersistentDataContainer worldPDC = overworld.getPersistentDataContainer();
|
||||
// worldPDC.set(key("skyblock.team." + playerTeam.getName()), PersistentDataType.INTEGER_ARRAY, new int[]{tpLoc.getBlockX(), tpLoc.getBlockY(), tpLoc.getBlockZ()});
|
||||
if(worldPDC.has(key("skyblock.team." + playerTeamName), PersistentDataType.INTEGER_ARRAY)) {
|
||||
int[] ints = worldPDC.get(key("skyblock.team." + playerTeamName), PersistentDataType.INTEGER_ARRAY);
|
||||
Location loc = new Location(overworld, ints[0], ints[1], ints[2]);
|
||||
executor.teleport(loc);
|
||||
pdc.set(key("island.home.loc"), PersistentDataType.INTEGER_ARRAY, ints);
|
||||
executor.sendMessage("Teleported to island home");
|
||||
var teamSpawn = Reference.SKYBLOCK_TEAM_ROOT.withSuffix("." + sanitizedTeamName).assign(overworld);
|
||||
var islandHomeLoc = Reference.ISLAND_HOME_LOC.assign(player);
|
||||
var worldTeamCount = Reference.SKYBLOCK_TEAM_COUNT.assign(overworld);
|
||||
|
||||
if (islandHomeLoc.has()) {
|
||||
executor.sendMessage("You already have an island assigned to you.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
executor.sendMessage("Generating skyblock world for " + playerTeam.getName() + "...");
|
||||
|
||||
int count = 0;
|
||||
if(worldPDC.has(key("skyblock.team.count"), PersistentDataType.INTEGER)) {
|
||||
//noinspection DataFlowIssue
|
||||
count = worldPDC.get(key("skyblock.team.count"), PersistentDataType.INTEGER);
|
||||
if (teamSpawn.has()) {
|
||||
BlockVector vec = teamSpawn.get();
|
||||
Location loc = new Location(overworld, vec.getX(), vec.getY(), vec.getZ());
|
||||
executor.teleport(loc);
|
||||
islandHomeLoc.set(vec);
|
||||
executor.sendMessage("Teleported to island home..");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Location originLoc;
|
||||
if(count == 0) {
|
||||
originLoc = new Location(overworld, 0, 96, 0);
|
||||
}else{
|
||||
int ring = MathsUtils.getRing(count);
|
||||
int slot = MathsUtils.getSlot(count);
|
||||
executor.sendMessage(
|
||||
Component.text("Generating skyblock world for ")
|
||||
.append(playerTeam.displayName(), Component.text(" >>> ")));
|
||||
|
||||
int numSlots = MathsUtils.getSlotsInRing(ring);
|
||||
int count = worldTeamCount.getOrDefault(0);
|
||||
|
||||
float step = 360f / numSlots;
|
||||
float angle = slot * step;
|
||||
float x = (float) Math.cos(angle) * playerIslandSpacing;
|
||||
float y = (float) Math.sin(angle) * playerIslandSpacing;
|
||||
originLoc = new Location(overworld, x, 96, y);
|
||||
}
|
||||
// int ring = MathsUtils.getRing(count);
|
||||
// int slot = MathsUtils.getSlot(count);
|
||||
// int numSlots = MathsUtils.getSlotsInRing(ring);
|
||||
|
||||
Location tpLoc = generateIslandNetwork(originLoc);
|
||||
int ring = (int) (Math.floor(count / 24.0) + 1);
|
||||
int slot = count % 24;
|
||||
int numSlots = 24;
|
||||
|
||||
worldPDC.set(key("skyblock.team." + playerTeamName), PersistentDataType.INTEGER_ARRAY, new int[]{tpLoc.getBlockX(), tpLoc.getBlockY(), tpLoc.getBlockZ()});
|
||||
worldPDC.set(key("skyblock.team.count"), PersistentDataType.INTEGER, count + 1);
|
||||
float step = 360f / numSlots;
|
||||
float angle = slot * step;
|
||||
float x = (float) Math.cos(angle) * (playerIslandSpacing * ring);
|
||||
float y = (float) Math.sin(angle) * (playerIslandSpacing * ring);
|
||||
Location originLoc = new Location(overworld, x, 96, y);
|
||||
|
||||
Location tpLoc = generateTier(originLoc,
|
||||
new IslandNetworkGenerator.TierDefinition(0, 1, 0, centralIslands, false, false));
|
||||
|
||||
teamSpawn.set(tpLoc.toVector().toBlockVector());
|
||||
worldTeamCount.set(count + 1);
|
||||
|
||||
executor.teleport(tpLoc);
|
||||
pdc.set(key("island.home.loc"), PersistentDataType.INTEGER_ARRAY, new int[]{tpLoc.getBlockX(), tpLoc.getBlockY(), tpLoc.getBlockZ()});
|
||||
islandHomeLoc.set(tpLoc.toVector().toBlockVector());
|
||||
|
||||
Advancement a = Advancements.skyblock.SKYBLOCK_BEGIN;
|
||||
AdvancementProgress aprog = player.getAdvancementProgress(a);
|
||||
if (!aprog.isDone()) aprog.getRemainingCriteria().forEach(aprog::awardCriteria);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static IslandNetworkGenerator.TierDefinition T1_DEF = new IslandNetworkGenerator.TierDefinition(1,
|
||||
T1_ISLAND_SLOTS,
|
||||
T1_ISLAND_SPACING,
|
||||
t1Islands);
|
||||
public static IslandNetworkGenerator.TierDefinition T2_DEF = new IslandNetworkGenerator.TierDefinition(2,
|
||||
T2_ISLAND_SLOTS,
|
||||
T2_ISLAND_SPACING,
|
||||
t2Islands);
|
||||
public static IslandNetworkGenerator.TierDefinition T3_DEF = new IslandNetworkGenerator.TierDefinition(3,
|
||||
T3_ISLAND_SLOTS,
|
||||
T3_ISLAND_SPACING,
|
||||
t3Islands);
|
||||
|
||||
public static Location generateTier(Location origin, IslandNetworkGenerator.TierDefinition tierDef) {
|
||||
IslandNetworkGenerator.Definition def = new IslandNetworkGenerator.Definition();
|
||||
def.tiers = new IslandNetworkGenerator.TierDefinition[]{tierDef};
|
||||
IslandNetworkGenerator gen = new IslandNetworkGenerator();
|
||||
return gen.generateIslandNetwork(origin, def);
|
||||
}
|
||||
|
||||
public static Location generateNetherIslands(Location origin, Axis axis) {
|
||||
IslandNetworkGenerator gen = new IslandNetworkGenerator();
|
||||
|
||||
StructureRef netherPortalStructure = new StructureRef(Reference.key("nether-start"));
|
||||
|
||||
StructureRef[] t1Islands = {
|
||||
new BiomedStructure(Reference.key("nether-warped-island"), Biome.WARPED_FOREST),
|
||||
new BiomedStructure(Reference.key("nether-crimson-island"), Biome.CRIMSON_FOREST),};
|
||||
StructureRef[] t2Islands = {
|
||||
new BiomedStructure(Reference.key("nether-warped-island"), Biome.WARPED_FOREST),
|
||||
new BiomedStructure(Reference.key("nether-crimson-island"), Biome.CRIMSON_FOREST),
|
||||
new BiomedStructure(Reference.key("nether-warped-island"), Biome.WARPED_FOREST),
|
||||
new BiomedStructure(Reference.key("nether-crimson-island"), Biome.CRIMSON_FOREST),
|
||||
new BiomedStructure(Reference.key("blaze-spawner"), Biome.BASALT_DELTAS),
|
||||
new BiomedStructure(Reference.key("blaze-spawner"), Biome.SOUL_SAND_VALLEY),
|
||||
new StructureRef(NamespacedKey.minecraft("ruined_portal_nether")),
|
||||
new StructureRef(NamespacedKey.minecraft("ruined_portal_nether"))};
|
||||
StructureRef[] t3Islands = {
|
||||
new BiomedStructure(Reference.key("nether-warped-island"), Biome.WARPED_FOREST),
|
||||
new BiomedStructure(Reference.key("nether-crimson-island"), Biome.CRIMSON_FOREST),
|
||||
new BiomedStructure(Reference.key("nether-warped-island"), Biome.WARPED_FOREST),
|
||||
new BiomedStructure(Reference.key("nether-crimson-island"), Biome.CRIMSON_FOREST),
|
||||
new BiomedStructure(Reference.key("nether-warped-island"), Biome.WARPED_FOREST),
|
||||
new BiomedStructure(Reference.key("nether-crimson-island"), Biome.CRIMSON_FOREST),
|
||||
new BiomedStructure(Reference.key("nether-warped-island"), Biome.WARPED_FOREST),
|
||||
new BiomedStructure(Reference.key("nether-crimson-island"), Biome.CRIMSON_FOREST)};
|
||||
|
||||
StructureRotation[] validCentralRotations = new StructureRotation[2];
|
||||
|
||||
if (axis == Axis.X) {
|
||||
validCentralRotations[0] = StructureRotation.NONE;
|
||||
validCentralRotations[1] = StructureRotation.CLOCKWISE_180;
|
||||
} else {
|
||||
validCentralRotations[0] = StructureRotation.CLOCKWISE_90;
|
||||
validCentralRotations[1] = StructureRotation.COUNTERCLOCKWISE_90;
|
||||
}
|
||||
|
||||
IslandNetworkGenerator.Definition def = new IslandNetworkGenerator.Definition();
|
||||
def.tiers = new IslandNetworkGenerator.TierDefinition[]{new IslandNetworkGenerator.TierDefinition(0, 1, 0,
|
||||
new StructureRef[]{netherPortalStructure},
|
||||
validCentralRotations,
|
||||
new Mirror[]{Mirror.NONE},
|
||||
() -> 0f),
|
||||
new IslandNetworkGenerator.TierDefinition(
|
||||
1, t1Islands.length, T1_ISLAND_SPACING, t1Islands), new IslandNetworkGenerator.TierDefinition(2,
|
||||
t2Islands.length,
|
||||
T2_ISLAND_SPACING,
|
||||
t2Islands),
|
||||
new IslandNetworkGenerator.TierDefinition(
|
||||
3, t3Islands.length, T3_ISLAND_SPACING, t3Islands),};
|
||||
|
||||
return gen.generateIslandNetwork(origin, def);
|
||||
}
|
||||
|
||||
private int executeRoot(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
if (!(executor instanceof Player)) return 0;
|
||||
|
||||
Player player = (Player) executor;
|
||||
var islandHomeLoc = Reference.ISLAND_HOME_LOC.assign(player);
|
||||
|
||||
World overworld = getServer().getWorld(new NamespacedKey("minecraft", "overworld"));
|
||||
|
||||
if (islandHomeLoc.has()) {
|
||||
BlockVector blockVector = islandHomeLoc.get();
|
||||
Location loc = new Location(overworld, blockVector.getX(), blockVector.getY(), blockVector.getZ());
|
||||
if (!player.teleport(loc, PlayerTeleportEvent.TeleportCause.COMMAND)) {
|
||||
player.sendMessage("Failed to teleport you to your island home, please notify an administrator");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ScoreboardManager scoreboardManager = getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
Team playerTeam = scoreboard.getPlayerTeam(player);
|
||||
if (playerTeam == null) {
|
||||
executor.sendMessage(Component.text("Please join a team first via ")
|
||||
.append(Component.text("/skyblock team",
|
||||
Style.style(TextColor.color(0x00, 0xff, 0xff)))));
|
||||
return 0;
|
||||
}
|
||||
|
||||
String playerTeamName = playerTeam.getName();
|
||||
String sanitizedTeamName = playerTeamName.replaceAll("[^a-z0-9_.]", "_");
|
||||
var teamSpawn = Reference.SKYBLOCK_TEAM_ROOT.withSuffix("." + sanitizedTeamName).assign(overworld);
|
||||
if (teamSpawn.has()) {
|
||||
islandHomeLoc.set(teamSpawn.get());
|
||||
executor.sendMessage("Teleported to island home..");
|
||||
BlockVector blockVector = islandHomeLoc.get();
|
||||
Location loc = new Location(overworld, blockVector.getX(), blockVector.getY(), blockVector.getZ());
|
||||
if (!player.teleport(loc, PlayerTeleportEvent.TeleportCause.COMMAND)) {
|
||||
player.sendMessage("Failed to teleport you to your island home, please notify an administrator");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
executor.sendMessage(Component.text("You have not yet generated a skyblock world for ")
|
||||
.append(playerTeam.displayName(), Component.text(". Please generate one via "))
|
||||
.append(Component.text("/skyblock generate",
|
||||
Style.style(TextColor.color(0x00, 0xff, 0xff)))));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -227,9 +272,307 @@ public class SkyblockGenCommand extends AbstractSkyblockCommand {
|
|||
var root = Commands.literal("skyblock");
|
||||
|
||||
var cmd = Get(SkyblockGenCommand.class);
|
||||
|
||||
root.then(Commands.literal("generate").executes(cmd::executeGenerate));
|
||||
|
||||
var team = Commands.literal("team");
|
||||
team.then(Commands.literal("create")
|
||||
.then(Commands.argument("name", StringArgumentType.word()).executes(cmd::executeTeamCreate)));
|
||||
team.then(Commands.literal("join")
|
||||
.then(Commands.argument("team", new SkyblockTeamArgument()).executes(cmd::executeTeamJoin)));
|
||||
team.then(Commands.literal("list").executes(cmd::executeTeamList));
|
||||
|
||||
// Team required
|
||||
team.then(Commands.literal("leave").requires(cmd::inTeam).executes(cmd::executeTeamLeave));
|
||||
team.then(Commands.literal("sync").requires(cmd::inTeam).executes(cmd::executeSyncAdvancements));
|
||||
team.then(Commands.literal("progress").requires(cmd::inTeam).executes(cmd::executeGetProgress));
|
||||
|
||||
// Auth required
|
||||
team.then(Commands.literal("prune").requires(cmd::auth).executes(cmd::executeTeamPrune));
|
||||
team.then(Commands.literal("remove")
|
||||
.requires(cmd::auth)
|
||||
.then(Commands.argument("team", new SkyblockTeamArgument())
|
||||
.executes(cmd::executeTeamRemove)));
|
||||
|
||||
root.then(team);
|
||||
root.executes(cmd::executeRoot);
|
||||
|
||||
return root.build();
|
||||
}
|
||||
|
||||
private int executeGetProgress(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
if (!(executor instanceof Player player)) return -1;
|
||||
|
||||
Component text = Component.text("Skyblock progress: ").appendNewline();
|
||||
|
||||
Advancement[][] groups = Advancements.groups();
|
||||
|
||||
Style defaultStyle = Style.style(TextColor.color(1.0f, 1.0f, 0.0f));
|
||||
Style completeStyle = Style.style(TextColor.color(0.0f, 1.0f, 0.0f));
|
||||
|
||||
for (int i = 0; i < groups.length; i++) {
|
||||
Advancement[] group = groups[i];
|
||||
|
||||
String groupName = Advancements.getGroupName(i);
|
||||
int unlocked = 0;
|
||||
for (Advancement advancement : group) {
|
||||
if (player.getAdvancementProgress(advancement).isDone()) unlocked++;
|
||||
}
|
||||
if (unlocked == 0) continue;
|
||||
|
||||
boolean complete = unlocked == group.length;
|
||||
|
||||
float percent = (float) unlocked / group.length;
|
||||
int percStr = Math.round(percent * 100);
|
||||
|
||||
Style style = complete ? completeStyle : defaultStyle;
|
||||
groupName = TextUtils.toTitleCase(groupName);
|
||||
text = text.append(
|
||||
Component.text(groupName, style).append(Component.text(": ")).append(Component.text(unlocked)));
|
||||
text = text.append(Component.text(" [").append(Component.text(percStr).append(Component.text("%]"))));
|
||||
text = text.appendNewline();
|
||||
}
|
||||
|
||||
player.sendMessage(text);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int executeSyncAdvancements(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = ctx.getSource().getExecutor();
|
||||
if (!(executor instanceof Player player)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ScoreboardManager scoreboardManager = Bukkit.getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
Team team = scoreboard.getPlayerTeam(player);
|
||||
assert team != null;
|
||||
|
||||
team.getEntries().forEach(entry -> {
|
||||
Player otherPlayer = Bukkit.getPlayer(entry);
|
||||
if (otherPlayer == null || otherPlayer.equals(player)) return;
|
||||
|
||||
for (Advancement adv : Advancements.values()) {
|
||||
AdvancementProgress advancementProgress = player.getAdvancementProgress(adv);
|
||||
advancementProgress.getAwardedCriteria().forEach(criterion -> {
|
||||
otherPlayer.getAdvancementProgress(adv).awardCriteria(criterion);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int executeTeamRemove(CommandContext<CommandSourceStack> ctx) {
|
||||
Team team = ctx.getArgument("team", Team.class);
|
||||
Entity executor = Objects.requireNonNull(ctx.getSource().getExecutor());
|
||||
|
||||
if (team.getSize() == 0) {
|
||||
team.unregister();
|
||||
executor.sendMessage("Team \"" + team.getName() + "\" removed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
executor.sendMessage("Team \"" + team.getName() + "\" is not empty, you cannot remove non-empty teams.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int executeTeamPrune(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = Objects.requireNonNull(ctx.getSource().getExecutor());
|
||||
|
||||
ScoreboardManager scoreboardManager = Bukkit.getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
|
||||
Set<Team> teams = scoreboard.getTeams();
|
||||
int initialSize = teams.size();
|
||||
teams.stream().filter(team -> team.getSize() == 0).forEach(Team::unregister);
|
||||
int newSize = scoreboard.getTeams().size();
|
||||
|
||||
if (initialSize == newSize) {
|
||||
executor.sendMessage("No teams were empty, nothing was removed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
executor.sendMessage("Removed " + (initialSize - newSize) + " empty teams");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private boolean auth(CommandSourceStack stack) {
|
||||
return stack.getSender().isOp();
|
||||
}
|
||||
|
||||
private boolean inTeam(CommandSourceStack stack) {
|
||||
ScoreboardManager scoreboardManager = Bukkit.getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
return stack.getSender() instanceof Player player && scoreboard.getPlayerTeam(player) != null;
|
||||
}
|
||||
|
||||
private int executeTeamList(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = Objects.requireNonNull(ctx.getSource().getExecutor());
|
||||
|
||||
|
||||
ScoreboardManager scoreboardManager = Bukkit.getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
Set<Team> teams = scoreboard.getTeams();
|
||||
|
||||
Component c = Component.text("Teams: ").appendNewline();
|
||||
c = c.append(Component.text("==========================").appendNewline());
|
||||
for (Team team : teams) {
|
||||
if (team.hasEntity(executor)) c = c.append(Component.text(" >> "));
|
||||
else c = c.append(Component.text(" "));
|
||||
c = c.append(team.displayName());
|
||||
|
||||
c = c.append(Component.text(" [")
|
||||
.append(Component.text(team.getSize()))
|
||||
.append(Component.text(" players]")));
|
||||
|
||||
c = c.appendNewline();
|
||||
}
|
||||
|
||||
executor.sendMessage(c);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int executeTeamCreate(CommandContext<CommandSourceStack> ctx) {
|
||||
String name = ctx.getArgument("name", String.class);
|
||||
Entity executor = Objects.requireNonNull(ctx.getSource().getExecutor());
|
||||
|
||||
if (!(executor instanceof Player player)) {
|
||||
executor.sendMessage("Only players can create teams");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (name.matches("[^a-z0-9_.]")) {
|
||||
player.sendMessage("Team name must only contain alphanumeric characters and underscores");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ScoreboardManager scoreboardManager = Bukkit.getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
|
||||
Team team = scoreboard.getPlayerTeam(player);
|
||||
if (team != null) {
|
||||
player.sendMessage("You are already part of a team, please leave it before creating a new one.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
team = scoreboard.getTeam(name);
|
||||
if (team != null) {
|
||||
player.sendMessage(
|
||||
"Team already exists with the name \"" + name + "\". Please choose a different name, or run the command " + "`/skyblock team join " + name + "` to join the team.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
team = scoreboard.registerNewTeam(name);
|
||||
player.sendMessage("Created team \"" + name + "\"");
|
||||
doTeamJoin(player, team);
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void doTeamJoin(Entity entity, Team team) {
|
||||
team.addEntity(entity);
|
||||
}
|
||||
|
||||
private int executeTeamJoin(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = Objects.requireNonNull(ctx.getSource().getExecutor());
|
||||
if (!(executor instanceof Player player)) {
|
||||
executor.sendMessage("Only players can join teams");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ScoreboardManager scoreboardManager = Bukkit.getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
|
||||
Team team = scoreboard.getPlayerTeam(player);
|
||||
if (team != null) {
|
||||
player.sendMessage("You are already part of a team, please leave it before joining a new one.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Team teamToJoin = ctx.getArgument("team", Team.class);
|
||||
if (teamToJoin == null) {
|
||||
player.sendMessage("Team not found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
player.sendMessage("Joining team \"" + teamToJoin.getName() + "\"");
|
||||
|
||||
Optional<Player> first = teamToJoin.getEntries()
|
||||
.stream()
|
||||
.map(Bukkit::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst();
|
||||
|
||||
doTeamJoin(player, teamToJoin);
|
||||
|
||||
if (first.isPresent()) {
|
||||
Player teamPlayer = first.get();
|
||||
for (Advancement adv : Advancements.values()) {
|
||||
final AdvancementProgress advancementProgress = teamPlayer.getAdvancementProgress(adv);
|
||||
final AdvancementProgress playerAdvProg = player.getAdvancementProgress(adv);
|
||||
advancementProgress.getAwardedCriteria().forEach(playerAdvProg::awardCriteria);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int executeTeamLeave(CommandContext<CommandSourceStack> ctx) {
|
||||
Entity executor = Objects.requireNonNull(ctx.getSource().getExecutor());
|
||||
if (!(executor instanceof Player player)) {
|
||||
executor.sendMessage("Only players can leave teams");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ScoreboardManager scoreboardManager = Bukkit.getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
|
||||
Team team = scoreboard.getPlayerTeam(player);
|
||||
assert (team != null);
|
||||
player.sendMessage("Leaving team \"" + team.getName() + "\"");
|
||||
team.removePlayer(player);
|
||||
Reference.ISLAND_HOME_LOC.assign(player).remove();
|
||||
|
||||
for (Advancement adv : Advancements.values()) {
|
||||
AdvancementProgress prg = player.getAdvancementProgress(adv);
|
||||
prg.getAwardedCriteria().forEach(prg::revokeCriteria);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final class SkyblockTeamArgument implements CustomArgumentType<Team, String> {
|
||||
|
||||
@Override
|
||||
public Team parse(StringReader reader) throws CommandSyntaxException {
|
||||
String teamName = reader.readString();
|
||||
|
||||
ScoreboardManager scoreboardManager = Bukkit.getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
|
||||
return Objects.requireNonNull(scoreboard.getTeams())
|
||||
.stream()
|
||||
.filter(x -> x.getName().equals(teamName))
|
||||
.findFirst()
|
||||
.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
|
||||
ScoreboardManager scoreboardManager = Bukkit.getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
scoreboard.getTeams().forEach(team -> builder.suggest(team.getName()));
|
||||
return CustomArgumentType.super.listSuggestions(context, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgumentType<String> getNativeType() {
|
||||
return StringArgumentType.word();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package com.ncguy.usefulskyblock.data;
|
||||
|
||||
import com.ncguy.usefulskyblock.StructureRef;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.Biome;
|
||||
|
||||
public class BiomedStructure extends StructureRef {
|
||||
|
||||
public final Biome biome;
|
||||
|
||||
public BiomedStructure(NamespacedKey name, Biome biome) {
|
||||
super(name);
|
||||
this.biome = biome;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.ncguy.usefulskyblock.events;
|
||||
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class SkyblockConfigReloadEvent extends Event {
|
||||
|
||||
public Configuration config;
|
||||
|
||||
public SkyblockConfigReloadEvent(Configuration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
@Override
|
||||
public @NotNull HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.ncguy.usefulskyblock.events;
|
||||
|
||||
import com.ncguy.usefulskyblock.SkyblockTeam;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class TeamProgressionEvent extends Event {
|
||||
|
||||
public Team team;
|
||||
public SkyblockTeam.Tiers unlockedTier;
|
||||
|
||||
public TeamProgressionEvent(Team team, SkyblockTeam.Tiers unlockedTier) {
|
||||
this.team = team;
|
||||
this.unlockedTier = unlockedTier;
|
||||
}
|
||||
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
@Override
|
||||
public @NotNull HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package com.ncguy.usefulskyblock.handlers;
|
||||
|
||||
import com.ncguy.usefulskyblock.Advancements;
|
||||
import com.ncguy.usefulskyblock.Reference;
|
||||
import io.papermc.paper.datacomponent.DataComponentType;
|
||||
import io.papermc.paper.datacomponent.DataComponentTypes;
|
||||
import net.kyori.adventure.sound.Sound;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.advancement.AdvancementProgress;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.damage.DamageType;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.kyori.adventure.sound.Sound.Source.BLOCK;
|
||||
|
||||
public class CauldronCraftingHandler implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onCauldronInteract(PlayerInteractEvent event) {
|
||||
Block clickedBlock = event.getClickedBlock();
|
||||
if (clickedBlock == null) return;
|
||||
|
||||
Material type = clickedBlock.getType();
|
||||
if (type == Material.LAVA_CAULDRON) {
|
||||
if (handleLavaCauldron(event)) return;
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onEntityDamage(EntityDamageEvent event) {
|
||||
if (event.getDamageSource().getDamageType() != DamageType.LAVA) return;
|
||||
if (event.getEntityType() != EntityType.ITEM) return;
|
||||
|
||||
Item item = (Item) event.getEntity();
|
||||
|
||||
Material type = item.getItemStack().getType();
|
||||
boolean isImmune = Reference.ITEM_LAVA_IMMUNE.assign(item).getOrDefault(false);
|
||||
if (type != Material.COBBLESTONE && !isImmune) return;
|
||||
|
||||
int health = item.getHealth();
|
||||
if (health - event.getFinalDamage() > 0) return;
|
||||
|
||||
ItemStack newItemStack = new ItemStack(Material.NETHERRACK, item.getItemStack().getAmount());
|
||||
item.getWorld().dropItem(item.getLocation(), newItemStack, i -> {
|
||||
Reference.ITEM_LAVA_IMMUNE.assign(i).set(true);
|
||||
i.setHealth(8);
|
||||
i.playSound(Sound.sound().type(new NamespacedKey("minecraft", "entity.generic.burn")).source(BLOCK).build());
|
||||
});
|
||||
|
||||
// Grant advancement
|
||||
UUID thrower = item.getThrower();
|
||||
if(thrower == null) return;
|
||||
Player player = Bukkit.getPlayer(thrower);
|
||||
if(player == null) return;
|
||||
AdvancementProgress advancementProgress = player.getAdvancementProgress(Advancements.nether.CRAFT_NETHERRACK);
|
||||
advancementProgress.getRemainingCriteria().forEach(advancementProgress::awardCriteria);
|
||||
}
|
||||
|
||||
public boolean handleLavaCauldron(PlayerInteractEvent event) {
|
||||
if (event.getBlockFace() != BlockFace.UP) return false;
|
||||
|
||||
if (!event.isBlockInHand()) return false;
|
||||
|
||||
ItemStack item = event.getItem();
|
||||
if (item == null) return false;
|
||||
|
||||
if (item.getType() == Material.COBBLESTONE) {
|
||||
EquipmentSlot hand = event.getHand();
|
||||
if (hand == null) return false;
|
||||
|
||||
event.setUseInteractedBlock(Event.Result.DENY);
|
||||
event.setUseItemInHand(Event.Result.DENY);
|
||||
event.getPlayer().getInventory().setItem(hand, item.withType(Material.NETHERRACK));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package com.ncguy.usefulskyblock.handlers;
|
||||
|
||||
import com.ncguy.usefulskyblock.events.SkyblockConfigReloadEvent;
|
||||
import com.ncguy.usefulskyblock.utils.MathsUtils;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.FishHook;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerFishEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
public class FishingHandler implements Listener {
|
||||
|
||||
private int sandChance = 800;
|
||||
private final int sandMax = 1000;
|
||||
|
||||
@EventHandler
|
||||
public void onConfigLoad(SkyblockConfigReloadEvent event) {
|
||||
double chance = Math.clamp(event.config.getDouble("skyblock.fishing.sand_chance", 0.8), 0.0, 1.0);
|
||||
this.sandChance = Math.toIntExact(Math.round(chance * sandMax));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerFish(PlayerFishEvent event) {
|
||||
if(event.getState() != PlayerFishEvent.State.REEL_IN)
|
||||
return;
|
||||
|
||||
final FishHook hook = event.getHook();
|
||||
int chance = Math.toIntExact(Math.round(Math.random() * sandMax));
|
||||
if(chance >= sandChance)
|
||||
return;
|
||||
|
||||
ItemStack stack = ItemStack.of(Material.SAND);
|
||||
hook.getWorld().dropItem(hook.getLocation(), stack, item -> {
|
||||
Vector vel = MathsUtils.getVelocityToTarget(item.getLocation().toVector(),
|
||||
event.getPlayer().getLocation().toVector());
|
||||
item.setVelocity(vel.multiply(0.75));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
package com.ncguy.usefulskyblock.handlers;
|
||||
|
||||
import com.destroystokyo.paper.event.player.PlayerPostRespawnEvent;
|
||||
import com.ncguy.usefulskyblock.Reference;
|
||||
import com.ncguy.usefulskyblock.StructureRef;
|
||||
import com.ncguy.usefulskyblock.pdc.IDataContainerRef;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.type.EndPortalFrame;
|
||||
import org.bukkit.block.structure.Mirror;
|
||||
import org.bukkit.block.structure.StructureRotation;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.event.server.ServerLoadEvent;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static com.ncguy.usefulskyblock.Reference.key;
|
||||
|
||||
public class InitialisationHandler implements Listener {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(InitialisationHandler.class);
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
player.sendMessage(Component.text("Hello, " + player.getName() + "!"));
|
||||
|
||||
if(Reference.ISLAND_HOME_LOC.assign(player).has())
|
||||
return;
|
||||
|
||||
// If the player doesn't have a home island, respawn them in the server lobby
|
||||
World world = Bukkit.getWorld(key("void"));
|
||||
player.teleport(world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
player.setRespawnLocation(world.getSpawnLocation());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerPostRespawn(PlayerPostRespawnEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
player.sendMessage(Component.text("Hello, " + player.getName() + "!"));
|
||||
|
||||
var islandHome = Reference.ISLAND_HOME_LOC.assign(player);
|
||||
if(islandHome.has()) {
|
||||
// TODO Handle respawning and such, especially when bed is missing
|
||||
return;
|
||||
}
|
||||
|
||||
// If the player doesn't have a home island, respawn them in the server lobby
|
||||
World world = Bukkit.getWorld(key("void"));
|
||||
player.teleport(world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
player.setRespawnLocation(world.getSpawnLocation());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerLoad(ServerLoadEvent event) {
|
||||
if(event.getType() != ServerLoadEvent.LoadType.STARTUP)
|
||||
return;
|
||||
|
||||
World world = Bukkit.getWorld(NamespacedKey.minecraft("overworld"));
|
||||
if(world != null)
|
||||
initOverworld(world);
|
||||
|
||||
world = Bukkit.getWorld(key("void"));
|
||||
if (world == null)
|
||||
return;
|
||||
|
||||
PersistentDataContainer pdc = world.getPersistentDataContainer();
|
||||
NamespacedKey initKey = key("world_init");
|
||||
if(pdc.has(initKey))
|
||||
return;
|
||||
pdc.set(initKey, PersistentDataType.BOOLEAN, true);
|
||||
|
||||
log.info("Generating spawn point for world {}", world.getName());
|
||||
StructureRef spawn = new StructureRef(key("spawn"));
|
||||
log.info("Entities in spawn: {}", spawn.getEntities().size());
|
||||
log.info("Palettes in spawn: {}", spawn.getPalettes().size());
|
||||
log.info("Palette count in spawn: {}", spawn.getPaletteCount());
|
||||
log.info("spawn.getSize(): {}", spawn.getSize());
|
||||
spawn.place(new Location(world, 0, 64, 0), true, StructureRotation.NONE, Mirror.NONE, 0, 1, new Random(System.currentTimeMillis()));
|
||||
world.setSpawnLocation(0, 64, 0);
|
||||
Location loc = world.getSpawnLocation().clone().subtract(0, 2, 0);
|
||||
loc.getBlock().setType(Material.BEDROCK);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onWorldLoad(WorldLoadEvent event) {
|
||||
World world = event.getWorld();
|
||||
if (world.getKey().equals(key("void")))
|
||||
initVoid(world);
|
||||
|
||||
if(world.getKey().equals(NamespacedKey.minecraft("overworld")))
|
||||
initOverworld(world);
|
||||
}
|
||||
|
||||
private void initVoid(World world) {
|
||||
var world_init = Reference.WORLD_INIT.assign(world);
|
||||
if(world_init.has())
|
||||
return;
|
||||
world_init.set(true);
|
||||
|
||||
log.info("Generating spawn point for world {}", world.getName());
|
||||
StructureRef spawn = new StructureRef(key("spawn"));
|
||||
spawn.place(new Location(world, 0, 64, 0), true, StructureRotation.NONE, Mirror.NONE, 0, 1, new Random(System.currentTimeMillis()));
|
||||
world.setSpawnLocation(0, 64, 0);
|
||||
Location loc = world.getSpawnLocation().clone().subtract(0, 2, 0);
|
||||
loc.getBlock().setType(Material.BEDROCK);
|
||||
}
|
||||
|
||||
private static class EndFrameLocation {
|
||||
public Location loc;
|
||||
public BlockFace facing;
|
||||
|
||||
public EndFrameLocation(Location loc, BlockFace facing) {
|
||||
this.loc = loc;
|
||||
this.facing = facing;
|
||||
}
|
||||
}
|
||||
|
||||
public static void initOverworld(World world) {
|
||||
initOverworld(world, false);
|
||||
}
|
||||
public static void initOverworld(World world, boolean override) {
|
||||
var world_init = Reference.WORLD_INIT.assign(world);
|
||||
if (!override && world_init.has()) return;
|
||||
|
||||
EndFrameLocation[] frameLocs = new EndFrameLocation[] {
|
||||
new EndFrameLocation(new Location(world, -2, 0, -1), BlockFace.EAST),
|
||||
new EndFrameLocation(new Location(world, -2, 0, -0), BlockFace.EAST),
|
||||
new EndFrameLocation(new Location(world, -2, 0, 1), BlockFace.EAST),
|
||||
|
||||
new EndFrameLocation(new Location(world, -1, 0, -2), BlockFace.SOUTH),
|
||||
new EndFrameLocation(new Location(world, 0, 0, -2), BlockFace.SOUTH),
|
||||
new EndFrameLocation(new Location(world, 1, 0, -2), BlockFace.SOUTH),
|
||||
|
||||
new EndFrameLocation(new Location(world, 2, 0, -1), BlockFace.WEST),
|
||||
new EndFrameLocation(new Location(world, 2, 0, -0), BlockFace.WEST),
|
||||
new EndFrameLocation(new Location(world, 2, 0, 1), BlockFace.WEST),
|
||||
|
||||
new EndFrameLocation(new Location(world, -1, 0, 2), BlockFace.NORTH),
|
||||
new EndFrameLocation(new Location(world, 0, 0, 2), BlockFace.NORTH),
|
||||
new EndFrameLocation(new Location(world, 1, 0, 2), BlockFace.NORTH),
|
||||
};
|
||||
|
||||
for (EndFrameLocation frameLoc : frameLocs) {
|
||||
world.getChunkAtAsync(frameLoc.loc, true).thenAccept(chunk -> {
|
||||
Block block = frameLoc.loc.getBlock();
|
||||
block.setType(Material.END_PORTAL_FRAME);
|
||||
EndPortalFrame frame = (EndPortalFrame) block.getBlockData();
|
||||
frame.setFacing(frameLoc.facing);
|
||||
frame.setEye(false);
|
||||
block.setBlockData(frame);
|
||||
});
|
||||
}
|
||||
|
||||
world_init.set(true);
|
||||
log.info("Generated end portal frame");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package com.ncguy.usefulskyblock.handlers;
|
||||
|
||||
import com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent;
|
||||
import com.ncguy.usefulskyblock.Advancements;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.advancement.AdvancementProgress;
|
||||
import org.bukkit.damage.DamageType;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.ItemDespawnEvent;
|
||||
import org.bukkit.event.player.PlayerPickupItemEvent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ItemEventHandler implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onEntityRemoveFromWorld(EntityRemoveFromWorldEvent event) {
|
||||
if(event.getEntityType() != EntityType.ITEM)
|
||||
return;
|
||||
|
||||
Item item = (Item) event.getEntity();
|
||||
Location location = item.getLocation();
|
||||
World world = location.getWorld();
|
||||
if(location.getY() > world.getMinHeight())
|
||||
return;
|
||||
|
||||
UUID throwerId = item.getThrower();
|
||||
if(throwerId == null)
|
||||
return;
|
||||
Player player = Bukkit.getPlayer(throwerId);
|
||||
if(player == null)
|
||||
return;
|
||||
|
||||
AdvancementProgress advancementProgress = player.getAdvancementProgress(Advancements.skyblock.VOID_BIN);
|
||||
if(!advancementProgress.isDone())
|
||||
advancementProgress.getRemainingCriteria().forEach(advancementProgress::awardCriteria);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package com.ncguy.usefulskyblock.handlers;
|
||||
|
||||
import com.ncguy.usefulskyblock.SkyblockTeam;
|
||||
import com.ncguy.usefulskyblock.command.SkyblockGenCommand;
|
||||
import com.ncguy.usefulskyblock.events.TeamProgressionEvent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.advancement.Advancement;
|
||||
import org.bukkit.advancement.AdvancementProgress;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerAdvancementDoneEvent;
|
||||
import org.bukkit.scoreboard.Scoreboard;
|
||||
import org.bukkit.scoreboard.ScoreboardManager;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class TeamProgressHandler implements Listener {
|
||||
|
||||
@EventHandler
|
||||
public void onAdvancement(PlayerAdvancementDoneEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
Advancement advancement = event.getAdvancement();
|
||||
|
||||
// Only share usefulskyblock advancements
|
||||
if(!advancement.getKey().getNamespace().equals("usefulskyblock"))
|
||||
return;
|
||||
|
||||
ScoreboardManager scoreboardManager = Bukkit.getServer().getScoreboardManager();
|
||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||
Team team = scoreboard.getPlayerTeam(player);
|
||||
if(team == null)
|
||||
return;
|
||||
|
||||
SkyblockTeam.Tiers unlockingTier = SkyblockTeam.isUnlockingAdvancement(advancement);
|
||||
if(unlockingTier != null)
|
||||
new SkyblockTeam(team).tryUnlockTier(unlockingTier);
|
||||
|
||||
// FIXME Caveat: only shares advancements among online members (and maybe offline, thanks Bukkit..)
|
||||
Set<String> entries = team.getEntries();
|
||||
entries.forEach(entry -> {
|
||||
Player entryPlayer = Bukkit.getPlayer(entry);
|
||||
if (entryPlayer == null || entryPlayer == player)
|
||||
return;
|
||||
|
||||
AdvancementProgress prg = entryPlayer.getAdvancementProgress(advancement);
|
||||
prg.getRemainingCriteria().forEach(prg::awardCriteria);
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onTeamProgression(TeamProgressionEvent event) {
|
||||
|
||||
SkyblockTeam sTeam = new SkyblockTeam(event.team);
|
||||
Location homeLoc = sTeam.getHomeIslandLoc();
|
||||
|
||||
sTeam.broadcast(Component.text("Tier ").append(Component.text(event.unlockedTier.displayName(), Style.style(
|
||||
TextColor.color(0x00, 0xff, 0xff))), Component.space(), Component.text("unlocked!")));
|
||||
switch(event.unlockedTier) {
|
||||
case TIER_1:
|
||||
SkyblockGenCommand.generateTier(homeLoc, SkyblockGenCommand.T1_DEF);
|
||||
break;
|
||||
case TIER_2:
|
||||
SkyblockGenCommand.generateTier(homeLoc, SkyblockGenCommand.T2_DEF);
|
||||
break;
|
||||
case TIER_3:
|
||||
SkyblockGenCommand.generateTier(homeLoc, SkyblockGenCommand.T3_DEF);
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package com.ncguy.usefulskyblock.pdc;
|
||||
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.persistence.PersistentDataHolder;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
public class AnonymousDataContainerRef<T, H extends PersistentDataHolder> implements IDataContainerRef<T, H> {
|
||||
|
||||
protected final NamespacedKey ref;
|
||||
protected final PersistentDataType<?, T> type;
|
||||
|
||||
public <P> AnonymousDataContainerRef(NamespacedKey ref, PersistentDataType<P, T> type) {
|
||||
this.ref = ref;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDataContainerRef<T, H> withSuffix(String suffix) {
|
||||
return new AnonymousDataContainerRef<>(new NamespacedKey(ref.getNamespace(), ref.getKey() + suffix), type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDataContainerRef<T, H> assign(H holder) {
|
||||
return new StrictDataContainerRef<>(holder, ref, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P, C> IDataContainerRef<C, H> withType(PersistentDataType<P, C> newType) {
|
||||
return new AnonymousDataContainerRef<>(ref, newType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NewH extends PersistentDataHolder> IDataContainerRef<T, NewH> restrict(Class<NewH> hClass) {
|
||||
return new AnonymousDataContainerRef<T, NewH>(ref, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
throw new UnsupportedOperationException("AnonymousDataContainerRef does not have an assigned holder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T val) {
|
||||
throw new UnsupportedOperationException("AnonymousDataContainerRef does not have an assigned holder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean has() {
|
||||
throw new UnsupportedOperationException("AnonymousDataContainerRef does not have an assigned holder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("AnonymousDataContainerRef does not have an assigned holder");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.ncguy.usefulskyblock.pdc;
|
||||
|
||||
import org.bukkit.persistence.PersistentDataHolder;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface IDataContainerRef<T, HOLDER extends PersistentDataHolder> {
|
||||
|
||||
public IDataContainerRef<T, HOLDER> assign(HOLDER holder);
|
||||
public IDataContainerRef<T, HOLDER> withSuffix(String suffix);
|
||||
public <P, C> IDataContainerRef<C, HOLDER> withType(PersistentDataType<P, C> newType);
|
||||
public <NewH extends PersistentDataHolder> IDataContainerRef<T, NewH> restrict(Class<NewH> hClass);
|
||||
|
||||
public T get();
|
||||
public boolean has();
|
||||
public void set(T val);
|
||||
void remove();
|
||||
|
||||
default public T getOrDefault(T def) {
|
||||
return has() ? get() : def;
|
||||
}
|
||||
|
||||
default public <NewT> IDataContainerRef<NewT, HOLDER> map(Function<T, NewT> mapper) {
|
||||
return map(mapper, null);
|
||||
}
|
||||
default public <NewT> IDataContainerRef<NewT, HOLDER> map(Function<T, NewT> mapper, Function<NewT, T> reverseMapper) {
|
||||
return new MappedDataContainerRef<>(this, mapper, reverseMapper);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package com.ncguy.usefulskyblock.pdc;
|
||||
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.persistence.PersistentDataHolder;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class MappedDataContainerRef<T, U, H extends PersistentDataHolder> implements IDataContainerRef<T, H> {
|
||||
|
||||
private IDataContainerRef<U, H> underlying;
|
||||
private Function<U, T> mapper;
|
||||
private Function<T, U> reverseMapper;
|
||||
|
||||
public MappedDataContainerRef(IDataContainerRef<U, H> underlying, Function<U, T> mapper, Function<T, U> reverseMapper) {
|
||||
this.underlying = underlying;
|
||||
this.mapper = mapper;
|
||||
this.reverseMapper = reverseMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDataContainerRef<T, H> assign(H holder) {
|
||||
return new MappedDataContainerRef<>(underlying.assign(holder), mapper, reverseMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDataContainerRef<T, H> withSuffix(String suffix) {
|
||||
return new MappedDataContainerRef<>(underlying.withSuffix(suffix), mapper, reverseMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P, C> IDataContainerRef<C, H> withType(PersistentDataType<P, C> newType) {
|
||||
throw new UnsupportedOperationException("MappedDataContainerRef does not support type conversion");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NewH extends PersistentDataHolder> IDataContainerRef<T, NewH> restrict(Class<NewH> newHClass) {
|
||||
return new MappedDataContainerRef<>(underlying.restrict(newHClass), mapper, reverseMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
return mapper.apply(underlying.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean has() {
|
||||
return underlying.has();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T val) {
|
||||
if(reverseMapper == null)
|
||||
throw new UnsupportedOperationException("MappedDataContainerRef does not support setting values without a reverse mapper");
|
||||
underlying.set(reverseMapper.apply(val));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
underlying.remove();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package com.ncguy.usefulskyblock.pdc;
|
||||
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataHolder;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
public class StrictDataContainerRef<T, H extends PersistentDataHolder> extends AnonymousDataContainerRef<T, H> {
|
||||
|
||||
private final H holder;
|
||||
|
||||
public <P> StrictDataContainerRef(H holder, NamespacedKey ref, PersistentDataType<P, T> type) {
|
||||
super(ref, type);
|
||||
this.holder = holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDataContainerRef<T, H> assign(PersistentDataHolder holder) {
|
||||
throw new UnsupportedOperationException("Cannot assign a holder to a strict ref");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDataContainerRef<T, H> withSuffix(String suffix) {
|
||||
return new StrictDataContainerRef<>(holder, new NamespacedKey(ref.getNamespace(), ref.getKey() + suffix), type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P, C> IDataContainerRef<C, H> withType(PersistentDataType<P, C> newType) {
|
||||
return new StrictDataContainerRef<>(holder, ref, newType);
|
||||
}
|
||||
|
||||
private PersistentDataContainer pdc() {
|
||||
return holder.getPersistentDataContainer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
if(holder == null)
|
||||
return super.get();
|
||||
return pdc().get(ref, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean has() {
|
||||
return pdc().has(ref, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T val) {
|
||||
pdc().set(ref, type, val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
pdc().remove(ref);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package com.ncguy.usefulskyblock.pdc;
|
||||
|
||||
import org.bukkit.persistence.PersistentDataAdapterContext;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
public class VectorPersistentDataType implements PersistentDataType<int[], BlockVector> {
|
||||
|
||||
public static final PersistentDataType<int[], BlockVector> Instance = new VectorPersistentDataType();
|
||||
|
||||
|
||||
@Override
|
||||
public @NotNull Class<int[]> getPrimitiveType() {
|
||||
return int[].class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Class<BlockVector> getComplexType() {
|
||||
return BlockVector.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int @NotNull [] toPrimitive(@NotNull BlockVector complex, @NotNull PersistentDataAdapterContext context) {
|
||||
return new int[] {
|
||||
complex.getBlockX(),
|
||||
complex.getBlockY(),
|
||||
complex.getBlockZ()
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BlockVector fromPrimitive(int @NotNull [] primitive, @NotNull PersistentDataAdapterContext context) {
|
||||
return new BlockVector(primitive[0], primitive[1], primitive[2]);
|
||||
}
|
||||
}
|
|
@ -1,18 +1,27 @@
|
|||
package com.ncguy.usefulskyblock.recipe;
|
||||
|
||||
import com.ncguy.usefulskyblock.UsefulSkyblock;
|
||||
import com.ncguy.usefulskyblock.events.SkyblockConfigReloadEvent;
|
||||
import com.ncguy.usefulskyblock.utils.BossBarProgressMonitor;
|
||||
import com.ncguy.usefulskyblock.utils.IProgressMonitor;
|
||||
import com.ncguy.usefulskyblock.utils.Remark;
|
||||
import com.ncguy.usefulskyblock.utils.RemarkSet;
|
||||
import io.papermc.paper.entity.LookAnchor;
|
||||
import io.papermc.paper.math.Position;
|
||||
import io.papermc.paper.registry.RegistryAccess;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import io.papermc.paper.util.Tick;
|
||||
import net.kyori.adventure.bossbar.BossBar;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.Directional;
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.BlockDisplay;
|
||||
import org.bukkit.entity.Player;
|
||||
|
@ -20,6 +29,7 @@ import org.bukkit.event.EventHandler;
|
|||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.Recipe;
|
||||
import org.bukkit.inventory.ShapedRecipe;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
|
@ -29,206 +39,239 @@ import org.bukkit.plugin.java.JavaPlugin;
|
|||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.bukkit.util.Transformation;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.joml.AxisAngle4f;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
|
||||
public class BiomeRod implements Listener {
|
||||
public class BiomeRod implements Listener, IRecipeProvider {
|
||||
|
||||
private static String Namespace = "usefulskyblock";
|
||||
private static String Namespace = "usefulskyblock";
|
||||
|
||||
private static NamespacedKey rodkey = new NamespacedKey(Namespace, "biomerod");
|
||||
private static NamespacedKey flag = new NamespacedKey(Namespace, "flag_biomerod");
|
||||
private static NamespacedKey rodkey = new NamespacedKey(Namespace, "biomerod");
|
||||
private static NamespacedKey flag = new NamespacedKey(Namespace, "flag_biomerod");
|
||||
|
||||
public static void Init(Server server) {
|
||||
@Override
|
||||
public Iterable<Recipe> provideRecipes() {
|
||||
ItemStack sign = ItemStack.of(Material.END_ROD);
|
||||
ItemMeta meta = sign.getItemMeta();
|
||||
PersistentDataContainer pdc = meta.getPersistentDataContainer();
|
||||
pdc.set(flag, PersistentDataType.STRING, flag.asString());
|
||||
meta.setMaxStackSize(1);
|
||||
meta.customName(Component.text("Biome Rod"));
|
||||
meta.lore(Arrays.stream(new String[]{"A mystical tool allowing manipulation of the world."})
|
||||
.map(Component::text)
|
||||
.toList());
|
||||
sign.setItemMeta(meta);
|
||||
|
||||
ItemStack sign = ItemStack.of(Material.END_ROD);
|
||||
ItemMeta meta = sign.getItemMeta();
|
||||
PersistentDataContainer pdc = meta.getPersistentDataContainer();
|
||||
pdc.set(flag, PersistentDataType.STRING, flag.asString());
|
||||
meta.setMaxStackSize(1);
|
||||
meta.customName(Component.text("Biome Rod"));
|
||||
meta.lore(Arrays.stream(new String[]{
|
||||
"A mystical tool allowing manipulation of the world."
|
||||
}).map(Component::text).toList());
|
||||
sign.setItemMeta(meta);
|
||||
ShapedRecipe signRecipe = new ShapedRecipe(rodkey, sign);
|
||||
signRecipe.shape(" ", "DSD", " ");
|
||||
signRecipe.setIngredient('D', Material.DIAMOND);
|
||||
signRecipe.setIngredient('S', Material.STICK);
|
||||
|
||||
ShapedRecipe signRecipe = new ShapedRecipe(rodkey, sign);
|
||||
signRecipe.shape(" ", "DSD", " ");
|
||||
signRecipe.setIngredient('D', Material.DIAMOND);
|
||||
signRecipe.setIngredient('S', Material.STICK);
|
||||
return List.of(signRecipe);
|
||||
}
|
||||
|
||||
server.addRecipe(signRecipe);
|
||||
public BiomeRod() {
|
||||
Plugin plugin = Bukkit.getPluginManager().getPlugin("UsefulSkyblock");
|
||||
reloadBiomeMap(plugin.getConfig());
|
||||
}
|
||||
|
||||
public RemarkSet reloadBiomeMap(Configuration config) {
|
||||
RemarkSet set = new RemarkSet();
|
||||
|
||||
List<Map<String, String>> o = (List<Map<String, String>>) config.getList("skyblock.biomes.biome-map");
|
||||
|
||||
Registry<Biome> biomeRegistry = RegistryAccess.registryAccess().getRegistry(RegistryKey.BIOME);
|
||||
|
||||
biomeMap.clear();
|
||||
|
||||
for (Map<String, String> m : Objects.requireNonNull(o)) {
|
||||
String materialName = m.get("material");
|
||||
String biomeName = m.get("biome");
|
||||
Material material = Material.getMaterial(materialName);
|
||||
Biome biome = biomeRegistry.get(Objects.requireNonNull(NamespacedKey.fromString(biomeName)));
|
||||
|
||||
if (material == null || biome == null) {
|
||||
System.err.println("Invalid biome rod material or biome: " + materialName + " " + biomeName);
|
||||
if (material == null) set.add("biome-map", "Invalid biome rod material: " + materialName);
|
||||
if (biome == null) set.add("biome-map", "Invalid biome rod biome: " + biomeName);
|
||||
continue;
|
||||
}
|
||||
|
||||
biomeMap.put(material, biome);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private boolean isItemStackBiomeSign(ItemStack itemStack) {
|
||||
if (itemStack == null) return false;
|
||||
PersistentDataContainer pdc = itemStack.getItemMeta().getPersistentDataContainer();
|
||||
return pdc.has(flag, PersistentDataType.STRING);
|
||||
}
|
||||
|
||||
private Map<Material, Biome> biomeMap = new HashMap<>();
|
||||
|
||||
private boolean isBlockValidBiomeKey(Block block) {
|
||||
return biomeMap.containsKey(block.getType());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onSkyblockConfigReload(SkyblockConfigReloadEvent event) {
|
||||
reloadBiomeMap(event.config).handleRemarks(remark -> {
|
||||
Bukkit.getServer()
|
||||
.broadcast(Component.text(remark.domain, Style.style(TextColor.fromHexString("#777")))
|
||||
.appendSpace()
|
||||
.append(Component.text(remark.message, Style.style(TextColor.fromHexString("#fff")))));
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBiomeSignPlace(BlockPlaceEvent event) {
|
||||
ItemStack itemInHand = event.getItemInHand();
|
||||
if (!isItemStackBiomeSign(itemInHand)) return;
|
||||
if (!isBlockValidBiomeKey(event.getBlockAgainst())) {
|
||||
event.setBuild(false);
|
||||
Player player = event.getPlayer();
|
||||
player.sendMessage(Component.text(event.getBlockAgainst().getType().name() + " is not a valid biome anchor."));
|
||||
return;
|
||||
}
|
||||
|
||||
public BiomeRod() {
|
||||
reloadBiomeMap();
|
||||
}
|
||||
final World world = event.getBlockAgainst().getWorld();
|
||||
|
||||
public RemarkSet reloadBiomeMap() {
|
||||
RemarkSet set = new RemarkSet();
|
||||
Plugin plugin = Bukkit.getPluginManager().getPlugin("UsefulSkyblock");
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
List<Map<String, String>> o = (List<Map<String, String>>) config.getList("skyblock.biomes.biome-map");
|
||||
List<Location> biomeLocations = new ArrayList<>();
|
||||
|
||||
Registry<Biome> biomeRegistry = RegistryAccess.registryAccess().getRegistry(RegistryKey.BIOME);
|
||||
JavaPlugin plugin = JavaPlugin.getPlugin(UsefulSkyblock.class);
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
final int radius = config.getInt("skyblock.biomes.rod-radius", 4);
|
||||
|
||||
biomeMap.clear();
|
||||
System.out.println("Modifying biomes with radius: " + radius);
|
||||
|
||||
for(Map<String, String> m : Objects.requireNonNull(o)) {
|
||||
String materialName = m.get("material");
|
||||
String biomeName = m.get("biome");
|
||||
Material material = Material.getMaterial(materialName);
|
||||
Biome biome = biomeRegistry.get(Objects.requireNonNull(NamespacedKey.fromString(biomeName)));
|
||||
BlockVector center = new BlockVector(event.getBlockAgainst().getX(), event.getBlockAgainst().getY(),
|
||||
event.getBlockAgainst().getZ());
|
||||
|
||||
if(material == null || biome == null) {
|
||||
System.err.println("Invalid biome rod material or biome: " + materialName + " " + biomeName);
|
||||
if(material == null)
|
||||
set.add("biome-map", "Invalid biome rod material: " + materialName);
|
||||
if(biome == null)
|
||||
set.add("biome-map", "Invalid biome rod biome: " + biomeName);
|
||||
continue;
|
||||
}
|
||||
|
||||
biomeMap.put(material, biome);
|
||||
for (int x = -radius; x <= radius; x += 4) {
|
||||
for (int y = -radius; y <= radius; y += 4) {
|
||||
for (int z = -radius; z <= radius; z += 4) {
|
||||
BlockVector vec = new BlockVector(x, y, z);
|
||||
vec.add(center);
|
||||
if (vec.distance(center) <= radius)
|
||||
biomeLocations.add(new Location(world, vec.getX(), vec.getY(), vec.getZ()));
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
Biome biome = biomeMap.get(event.getBlockAgainst().getType());
|
||||
event.getPlayer()
|
||||
.sendMessage(Component.text(
|
||||
"Converting to biome " + biome + " with radius " + radius + " at " + center + ". Blocks to change: " + biomeLocations.size()));
|
||||
|
||||
Queue<Location> locationQueue = new LinkedList<>(biomeLocations.stream()
|
||||
.sorted((a, b) -> (int) Math.round(a.toVector()
|
||||
.distanceSquared(
|
||||
center) - b.toVector()
|
||||
.distanceSquared(
|
||||
center)))
|
||||
.toList());
|
||||
BukkitScheduler scheduler = Bukkit.getServer().getScheduler();
|
||||
|
||||
BossBarProgressMonitor progress = new BossBarProgressMonitor(
|
||||
BossBar.bossBar(Component.text(""), 0, BossBar.Color.GREEN, BossBar.Overlay.PROGRESS), event.getPlayer());
|
||||
progress.setTitle(
|
||||
Component.text("Converting biome to").appendSpace().append(Component.translatable(biome.translationKey())));
|
||||
progress.setMaxProgress(biomeLocations.size());
|
||||
progress.setInverseProgress(true);
|
||||
|
||||
int batchSize = Math.max(1, biomeLocations.size() / sinkDurationTicks);
|
||||
|
||||
final int REASONABLE_THRESHOLD = 64;
|
||||
if (biomeLocations.size() < REASONABLE_THRESHOLD) batchSize = biomeLocations.size();
|
||||
|
||||
modifyBiomeAsync(world, locationQueue, biome, plugin, scheduler, batchSize, progress);
|
||||
sinkRodIntoBlock(event.getBlockPlaced(), plugin, scheduler);
|
||||
}
|
||||
|
||||
private static int sinkDurationTicks = Tick.tick().fromDuration(Duration.ofSeconds(2));
|
||||
|
||||
private static void sinkRodIntoBlock(Block block, JavaPlugin plugin, BukkitScheduler scheduler) {
|
||||
|
||||
final BlockData blockData = block.getBlockData();
|
||||
BlockFace facing = blockData instanceof Directional ? ((Directional) blockData).getFacing() : BlockFace.SELF;
|
||||
|
||||
World world = block.getWorld();
|
||||
world.spawn(block.getLocation(), BlockDisplay.class, e -> {
|
||||
e.setBlock(blockData);
|
||||
e.setTransformation(new Transformation(new Vector3f(0, 0, 0), new AxisAngle4f(0, 0, 0, 1), new Vector3f(1, 1, 1),
|
||||
new AxisAngle4f(0, 0, 0, 1)));
|
||||
|
||||
e.setPersistent(false);
|
||||
sinkRodIntoBlock(e, plugin, scheduler, sinkDurationTicks, facing);
|
||||
});
|
||||
block.setType(Material.AIR);
|
||||
}
|
||||
|
||||
private static void sinkRodIntoBlock(BlockDisplay display, JavaPlugin plugin, BukkitScheduler scheduler,
|
||||
int remainder, BlockFace facing) {
|
||||
if (remainder == 0) {
|
||||
display.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
private boolean isItemStackBiomeSign(ItemStack itemStack) {
|
||||
if(itemStack == null)
|
||||
return false;
|
||||
PersistentDataContainer pdc = itemStack.getItemMeta().getPersistentDataContainer();
|
||||
return pdc.has(flag, PersistentDataType.STRING);
|
||||
float progress = (float) remainder / sinkDurationTicks;
|
||||
float scale = 1.0f - progress;
|
||||
|
||||
float meshScale = (progress * 0.5f) + 0.5f;
|
||||
float meshOffset = scale * 0.5f;
|
||||
|
||||
Vector direction = facing.getDirection();
|
||||
Vector3f scaleVec = new Vector3f(direction.toVector3f().absolute());
|
||||
scaleVec.mul(1f - progress);
|
||||
scaleVec = new Vector3f(1, 1, 1).sub(scaleVec);
|
||||
|
||||
|
||||
Vector3f offset = new Vector3f();
|
||||
final float EPSILON = 0.00001f;
|
||||
if (direction.getX() < -EPSILON || direction.getY() < -EPSILON || direction.getZ() < -EPSILON) {
|
||||
offset.set(direction.toVector3f().mul(-1)).mul(1 - progress);
|
||||
}
|
||||
|
||||
private Map<Material, Biome> biomeMap = new HashMap<>();
|
||||
display.setTransformation(new Transformation(
|
||||
// new Vector3f(-meshOffset, -meshOffset, -meshOffset).mul(facing.getDirection().toVector3f()),
|
||||
offset, new AxisAngle4f(0, 0, 0, 1), scaleVec, new AxisAngle4f(0, 0, 0, 1)));
|
||||
|
||||
private boolean isBlockValidBiomeKey(Block block) {
|
||||
return biomeMap.containsKey(block.getType());
|
||||
scheduler.runTaskLater(plugin, () -> sinkRodIntoBlock(display, plugin, scheduler, remainder - 1, facing), 1);
|
||||
}
|
||||
|
||||
private static boolean modifyBiomeAsync(World world, Queue<Location> locations, Biome biome, JavaPlugin plugin,
|
||||
BukkitScheduler scheduler, int batchSize, IProgressMonitor progress) {
|
||||
if (locations.isEmpty()) {
|
||||
progress.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBiomeSignPlace(BlockPlaceEvent event) {
|
||||
ItemStack itemInHand = event.getItemInHand();
|
||||
if(!isItemStackBiomeSign(itemInHand))
|
||||
return;
|
||||
if(!isBlockValidBiomeKey(event.getBlockAgainst())) {
|
||||
event.setBuild(false);
|
||||
Player player = event.getPlayer();
|
||||
player.sendMessage(Component.text(event.getBlockAgainst().getType().name() + " is not a valid biome anchor."));
|
||||
return;
|
||||
}
|
||||
Set<Chunk> chunksToRefresh = new HashSet<>();
|
||||
|
||||
final World world = event.getBlockAgainst().getWorld();
|
||||
|
||||
List<Location> biomeLocations = new ArrayList<>();
|
||||
|
||||
JavaPlugin plugin = JavaPlugin.getPlugin(UsefulSkyblock.class);
|
||||
FileConfiguration config = plugin.getConfig();
|
||||
final int radius = config.getInt("skyblock.biomes.rod-radius", 4);
|
||||
|
||||
System.out.println("Modifying biomes with radius: " + radius);
|
||||
|
||||
BlockVector center = new BlockVector(event.getBlockAgainst().getX(), event.getBlockAgainst().getY(), event.getBlockAgainst().getZ());
|
||||
|
||||
for(int x = -radius; x <= radius; x++) {
|
||||
for(int y = -radius; y <= radius; y++) {
|
||||
for(int z = -radius; z <= radius; z++) {
|
||||
BlockVector vec = new BlockVector(x, y, z);
|
||||
vec.add(center);
|
||||
if(vec.distance(center) <= radius)
|
||||
biomeLocations.add(new Location(world, vec.getX(), vec.getY(), vec.getZ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Biome biome = biomeMap.get(event.getBlockAgainst().getType());
|
||||
event.getPlayer().sendMessage(Component.text("Converting to biome " + biome + " with radius " + radius + " at " + center + ". Blocks to change: " + biomeLocations.size()));
|
||||
|
||||
Queue<Location> locationQueue = new LinkedList<>(biomeLocations.stream().sorted((a, b) -> (int) Math.round(a.toVector().distanceSquared(center) - b.toVector().distanceSquared(center))).toList());
|
||||
BukkitScheduler scheduler = Bukkit.getServer().getScheduler();
|
||||
|
||||
BossBarProgressMonitor progress = new BossBarProgressMonitor(BossBar.bossBar(Component.text(""), 0, BossBar.Color.GREEN, BossBar.Overlay.PROGRESS), event.getPlayer());
|
||||
progress.setTitle(Component.text("Converting biome to").appendSpace().append(Component.translatable(biome.translationKey())));
|
||||
progress.setMaxProgress(biomeLocations.size());
|
||||
progress.setInverseProgress(true);
|
||||
|
||||
int batchSize = biomeLocations.size() / sinkDurationTicks;
|
||||
modifyBiomeAsync(world, locationQueue, biome, plugin, scheduler, batchSize, progress);
|
||||
sinkRodIntoBlock(event.getBlockPlaced(), plugin, scheduler);
|
||||
for (int i = 0; i < Math.min(batchSize, locations.size()); i++) {
|
||||
Location loc = locations.remove();
|
||||
if (loc == null) break;
|
||||
world.setBiome(loc, biome);
|
||||
Chunk chunkAt = world.getChunkAt(loc);
|
||||
chunksToRefresh.add(chunkAt);
|
||||
}
|
||||
|
||||
private static int sinkDurationTicks = Tick.tick().fromDuration(Duration.ofSeconds(2));
|
||||
private static void sinkRodIntoBlock(Block block, JavaPlugin plugin, BukkitScheduler scheduler) {
|
||||
World world = block.getWorld();
|
||||
world.spawn(block.getLocation(), BlockDisplay.class, e -> {
|
||||
e.setBlock(block.getBlockData());
|
||||
e.setTransformation(new Transformation(
|
||||
new Vector3f(0, 0, 0),
|
||||
new AxisAngle4f(0, 0, 0, 1),
|
||||
new Vector3f(1, 1, 1),
|
||||
new AxisAngle4f(0, 0, 0, 1)
|
||||
));
|
||||
progress.setProgress(locations.size());
|
||||
|
||||
e.setPersistent(false);
|
||||
sinkRodIntoBlock(e, plugin, scheduler, sinkDurationTicks);
|
||||
});
|
||||
block.setType(Material.AIR);
|
||||
}
|
||||
private static void sinkRodIntoBlock(BlockDisplay display, JavaPlugin plugin, BukkitScheduler scheduler, int remainder) {
|
||||
if(remainder == 0) {
|
||||
display.remove();
|
||||
return;
|
||||
}
|
||||
chunksToRefresh.forEach(x -> world.refreshChunk(x.getX(), x.getZ()));
|
||||
|
||||
float progress = (float) remainder / sinkDurationTicks;
|
||||
float scale = 1.0f - progress;
|
||||
|
||||
float meshScale = (progress * 0.5f) + 0.5f;
|
||||
float meshOffset = scale * 0.5f;
|
||||
|
||||
display.setTransformation(new Transformation(
|
||||
new Vector3f(0, -meshOffset, 0),
|
||||
new AxisAngle4f(0, 0, 0, 1),
|
||||
new Vector3f(1, meshScale, 1),
|
||||
new AxisAngle4f(0, 0, 0, 1)
|
||||
));
|
||||
|
||||
scheduler.runTaskLater(plugin, () -> sinkRodIntoBlock(display, plugin, scheduler, remainder - 1), 1);
|
||||
if (locations.isEmpty()) {
|
||||
progress.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean modifyBiomeAsync(World world, Queue<Location> locations, Biome biome, JavaPlugin plugin, BukkitScheduler scheduler, int batchSize, IProgressMonitor progress) {
|
||||
if(locations.isEmpty()) {
|
||||
progress.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
Set<Chunk> chunksToRefresh = new HashSet<>();
|
||||
|
||||
for(int i = 0; i < Math.min(batchSize, locations.size()); i++) {
|
||||
Location loc = locations.remove();
|
||||
if(loc == null) {
|
||||
progress.finish();
|
||||
return true;
|
||||
}
|
||||
world.setBiome(loc, biome);
|
||||
Chunk chunkAt = world.getChunkAt(loc);
|
||||
chunksToRefresh.add(chunkAt);
|
||||
}
|
||||
|
||||
progress.setProgress(locations.size());
|
||||
|
||||
chunksToRefresh.forEach(x -> world.refreshChunk(x.getX(), x.getZ()));
|
||||
|
||||
if(locations.isEmpty()) {
|
||||
progress.finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
scheduler.runTaskLater(plugin, () -> modifyBiomeAsync(world, locations, biome, plugin, scheduler, batchSize, progress), 1);
|
||||
return false;
|
||||
}
|
||||
scheduler.runTaskLater(plugin,
|
||||
() -> modifyBiomeAsync(world, locations, biome, plugin, scheduler, batchSize, progress), 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package com.ncguy.usefulskyblock.recipe;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.inventory.Recipe;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface IRecipeProvider {
|
||||
|
||||
Iterable<Recipe> provideRecipes();
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.ncguy.usefulskyblock.recipe;
|
||||
|
||||
import com.ncguy.usefulskyblock.Reference;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.FurnaceRecipe;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.Recipe;
|
||||
import org.bukkit.inventory.RecipeChoice;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SmeltingCraftingHandler implements IRecipeProvider {
|
||||
|
||||
private final List<Recipe> recipes;
|
||||
|
||||
public SmeltingCraftingHandler() {
|
||||
recipes = new ArrayList<>();
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
recipes.add(new FurnaceRecipe(Reference.key("netherrack_to_brick"), ItemStack.of(Material.NETHER_BRICK),
|
||||
Material.NETHERRACK, 0.7f, 200));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Recipe> provideRecipes() {
|
||||
return recipes;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package com.ncguy.usefulskyblock.utils;
|
||||
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
|
@ -70,4 +72,38 @@ public class MathsUtils {
|
|||
return ring << 2;
|
||||
}
|
||||
|
||||
public static Vector getVelocityToTarget(Vector from, Vector to) {
|
||||
if (from == null || to == null) {
|
||||
throw new IllegalArgumentException("from and to must not be null");
|
||||
}
|
||||
|
||||
Vector delta = to.clone().subtract(from);
|
||||
if (delta.lengthSquared() == 0.0)
|
||||
return new Vector(0, 0, 0);
|
||||
|
||||
double dx = delta.getX();
|
||||
double dy = delta.getY();
|
||||
double dz = delta.getZ();
|
||||
|
||||
double horizontal = Math.hypot(dx, dz);
|
||||
|
||||
// Entity gravity magnitude in vanilla Minecraft
|
||||
final double g = 0.08;
|
||||
|
||||
// Flight time in ticks based on horizontal distance
|
||||
final double minT = 5.0; // 0.25s
|
||||
final double maxT = 60.0; // 3.0s
|
||||
double T = Math.max(minT, Math.min(maxT, horizontal * 1.5));
|
||||
if (horizontal == 0.0)
|
||||
// Vertical shot, adjust flight time based on vertical delta
|
||||
T = Math.max(minT, Math.min(maxT, Math.abs(dy) * 2.0));
|
||||
|
||||
// Solve v from delta = v*T + 0.5*a*T^2 => v = (delta - 0.5*a*T^2)/T
|
||||
double vx = dx / T;
|
||||
double vz = dz / T;
|
||||
double vy = (dy + 0.5 * g * T * T) / T;
|
||||
|
||||
return new Vector(vx, vy, vz);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package com.ncguy.usefulskyblock.utils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class ReflectionUtils {
|
||||
|
||||
public static Field getField(Object obj, String fieldName) throws NoSuchFieldException {
|
||||
Class<?> cls = obj.getClass();
|
||||
try {
|
||||
return cls.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
if(cls.getSuperclass() != Object.class)
|
||||
return getField(cls.getSuperclass(), fieldName);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T get(Object obj, String fieldName) {
|
||||
try {
|
||||
Field field = getField(obj, fieldName);
|
||||
field.setAccessible(true);
|
||||
return (T) field.get(obj);
|
||||
} catch (IllegalAccessException | NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package com.ncguy.usefulskyblock.utils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class RemarkSet extends HashSet<Remark> {
|
||||
|
||||
|
@ -8,4 +9,9 @@ public class RemarkSet extends HashSet<Remark> {
|
|||
add(new Remark(domain, message));
|
||||
}
|
||||
|
||||
public RemarkSet handleRemarks(Consumer<Remark> handler) {
|
||||
this.forEach(handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
37
src/main/java/com/ncguy/usefulskyblock/utils/TextUtils.java
Normal file
37
src/main/java/com/ncguy/usefulskyblock/utils/TextUtils.java
Normal file
|
@ -0,0 +1,37 @@
|
|||
package com.ncguy.usefulskyblock.utils;
|
||||
|
||||
import org.bukkit.scoreboard.Team;
|
||||
|
||||
public class TextUtils {
|
||||
|
||||
public static String sanitizeTeamName(Team team) {
|
||||
return team.getName().replaceAll("[^a-z0-9_.]", "_");
|
||||
}
|
||||
|
||||
public static String toTitleCase(String text) {
|
||||
var lower = text.toLowerCase();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
char[] charArray = lower.toCharArray();
|
||||
for (int i = 0; i < charArray.length; i++) {
|
||||
if (i == 0) {
|
||||
sb.append(Character.toUpperCase(charArray[i]));
|
||||
continue;
|
||||
}
|
||||
if(charArray[i] == '_' || charArray[i] == ' ') {
|
||||
sb.append(' ');
|
||||
continue;
|
||||
}
|
||||
|
||||
if(charArray[i-1] == ' ' || charArray[i-1] == '_') {
|
||||
sb.append(Character.toUpperCase(charArray[i]));
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.append(charArray[i]);
|
||||
}
|
||||
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -1,35 +1,69 @@
|
|||
package com.ncguy.usefulskyblock.world;
|
||||
|
||||
import com.ncguy.usefulskyblock.command.SkyblockGenCommand;
|
||||
import io.papermc.paper.math.BlockPosition;
|
||||
import org.bukkit.Axis;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.data.Orientable;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.PortalCreateEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.bukkit.plugin.java.JavaPlugin.getProvidingPlugin;
|
||||
|
||||
public class PortalHandler implements Listener {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PortalHandler.class);
|
||||
|
||||
@EventHandler
|
||||
public void onPortalCreate(PortalCreateEvent event) {
|
||||
log.info("Portal created: {}, reason: {}", event, event.getReason().name());
|
||||
if(event.getReason() != PortalCreateEvent.CreateReason.NETHER_PAIR)
|
||||
return;
|
||||
|
||||
Entity entity = event.getEntity();
|
||||
log.info("Portal entity: {}", entity);
|
||||
if(entity == null)
|
||||
return;
|
||||
|
||||
BoundingBox bb = new BoundingBox();
|
||||
List<BlockState> blocks = event.getBlocks();
|
||||
bb.shift(blocks.getFirst().getLocation());
|
||||
for (int i = 1; i < blocks.size(); i++)
|
||||
bb.expand(blocks.get(i).getLocation().toVector());
|
||||
BlockState first = blocks.getFirst();
|
||||
|
||||
boolean isXForward = bb.getWidthX() < bb.getWidthZ();
|
||||
Optional<BlockState> portalState = event.getBlocks().stream().filter(x -> x.getBlock().getType() == Material.NETHER_PORTAL).findFirst();
|
||||
|
||||
for (BlockState block : event.getBlocks()) {
|
||||
log.info("Block: {}, type: {}", block, block.getType());
|
||||
if(block.getType() == Material.NETHER_PORTAL) {
|
||||
portalState = Optional.of(block);
|
||||
log.info("Found portal block: {}", block);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BlockState blockState1 = portalState.get();
|
||||
final Axis portalAxis = ((Orientable) blockState1.getBlockData()).getAxis();
|
||||
|
||||
Location location = first.getLocation();
|
||||
log.info("Removing old portal, block count: {}", blocks.size());
|
||||
blocks.forEach(blockState -> blockState.setType(Material.AIR));
|
||||
|
||||
|
||||
log.info("Created portal at {}", location);
|
||||
Bukkit.getScheduler().runTaskLater(getProvidingPlugin(getClass()), () -> {
|
||||
Location generateOrigin = SkyblockGenCommand.generateNetherIslands(location, portalAxis);
|
||||
entity.teleport(generateOrigin);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
skyblock:
|
||||
fishing:
|
||||
sand_chance: 0.4
|
||||
biomes:
|
||||
rod-radius: 16
|
||||
biome-map:
|
||||
|
@ -14,4 +16,5 @@ skyblock:
|
|||
biome: "minecraft:forest"
|
||||
- material: "SNOW_BLOCK"
|
||||
biome: "minecraft:snowy_plains"
|
||||
|
||||
- material: "CRAFTING_TABLE"
|
||||
biome: "minecraft:nether_wastes"
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"criteria": {
|
||||
"never": { "trigger": "minecraft:impossible" }
|
||||
},
|
||||
"requirements": [
|
||||
["never"]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"criteria": {
|
||||
"never": { "trigger": "minecraft:impossible" }
|
||||
},
|
||||
"requirements": [
|
||||
["never"]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"criteria": {
|
||||
"never": { "trigger": "minecraft:impossible" }
|
||||
},
|
||||
"requirements": [
|
||||
["never"]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"criteria": {
|
||||
"never": { "trigger": "minecraft:impossible" }
|
||||
},
|
||||
"requirements": [
|
||||
["never"]
|
||||
]
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
{
|
||||
"type": "minecraft:the_nether",
|
||||
"generator": {
|
||||
"type": "flat",
|
||||
"settings":{
|
||||
"layers": [],
|
||||
"biome": "minecraft:hell",
|
||||
"structure_overrides": []
|
||||
"type": "minecraft:flat",
|
||||
"settings": {
|
||||
"biome": "minecraft:nether_wastes",
|
||||
"lakes": false,
|
||||
"features": true,
|
||||
"layers": []
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:wheat"
|
||||
},
|
||||
"title": "Farming",
|
||||
"description": "What do you intend to eat",
|
||||
"background": "minecraft:block/oak_log",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": true
|
||||
},
|
||||
"criteria": {
|
||||
"eat-food-for-farm": {
|
||||
"trigger": "minecraft:consume_item"
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:beetroot"
|
||||
},
|
||||
"title": "Get a Carrot",
|
||||
"description": "See in the dark",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:farming/farm_unlock",
|
||||
"criteria": {
|
||||
"collect-wheat": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:beetroot",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:cactus"
|
||||
},
|
||||
"title": "Get cactus",
|
||||
"description": "Don't get pricked",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:farming/farm_unlock",
|
||||
"criteria": {
|
||||
"collect-wheat": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:cactus",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:carrot"
|
||||
},
|
||||
"title": "Get a Carrot",
|
||||
"description": "See in the dark",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:farming/farm_unlock",
|
||||
"criteria": {
|
||||
"collect-wheat": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:carrot",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:tropical_fish"
|
||||
},
|
||||
"title": "Catch a fish",
|
||||
"description": "Teach a man how to fish...",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:farming/farm_unlock",
|
||||
"criteria": {
|
||||
"collect-wheat": {
|
||||
"trigger": "minecraft:fishing_rod_hooked"
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:honey_bottle"
|
||||
},
|
||||
"title": "Do You Want To Bee Friends?",
|
||||
"description":"Harvest Honey from a Bee Nest from a tree grown near flowers",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": true,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:farming/farm_unlock",
|
||||
"criteria": {
|
||||
"harvest_honey": {
|
||||
"trigger": "minecraft:item_used_on_block",
|
||||
"conditions": {
|
||||
"location": [
|
||||
{
|
||||
"condition": "minecraft:location_check",
|
||||
"predicate": {
|
||||
"block": {
|
||||
"blocks": "#minecraft:beehives"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"condition": "minecraft:match_tool",
|
||||
"predicate": {
|
||||
"items": [
|
||||
"minecraft:glass_bottle"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:red_mushroom"
|
||||
},
|
||||
"title": "Mushroom!",
|
||||
"description": "There's plenty of room here, wait... I messed up",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:farming/farm_unlock",
|
||||
"criteria": {
|
||||
"get-red-mushroom": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:red_mushroom",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"get-brown-mushroom": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:brown_mushroom",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:pumpkin"
|
||||
},
|
||||
"title": "Get a Pumpkin",
|
||||
"description": "TODO",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:farming/farm_unlock",
|
||||
"criteria": {
|
||||
"collect-wheat": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:pumpkin",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:sugar_cane"
|
||||
},
|
||||
"title": "Get some Sugarcane",
|
||||
"description": "Sweet!",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:farming/farm_unlock",
|
||||
"criteria": {
|
||||
"collect-wheat": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:sugar_cane",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:wheat"
|
||||
},
|
||||
"title": "Get Wheat",
|
||||
"description": "If only we had a millstone",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:farming/farm_unlock",
|
||||
"criteria": {
|
||||
"collect-wheat": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:wheat",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:porkchop"
|
||||
},
|
||||
"title": "New Friends",
|
||||
"description": "Create a patch of grass and stand 24-48 blocks away to let passive mobs spawn. Then breed them.",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:farming/farm_unlock",
|
||||
"criteria": {
|
||||
"breed": {
|
||||
"trigger": "minecraft:bred_animals",
|
||||
"conditions": {}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": true
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:copper_ingot"
|
||||
},
|
||||
"title": "Underwater Mob Farm",
|
||||
"description": "Obtain a Copper Ingot from killing Drowned",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": true,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:monsters/zombie_hunter",
|
||||
"criteria": {
|
||||
"drowned": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
"minecraft:copper_ingot"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:gunpowder"
|
||||
},
|
||||
"title": "It Would Be A Ssshame...",
|
||||
"description": "Obtain Gunpowder",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:monsters/monster_unlock",
|
||||
"criteria": {
|
||||
"gunpowder": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
"minecraft:gunpowder"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": true
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:wither_skeleton_skull"
|
||||
},
|
||||
"title": "Defeat the Wither",
|
||||
"description": "A significant milestone",
|
||||
"frame": "challenge",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": true,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:monsters/skeleton_hunter",
|
||||
"criteria": {
|
||||
"slay-wither": {
|
||||
"trigger": "minecraft:player_killed_entity",
|
||||
"conditions": {
|
||||
"entity": [
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"type": "minecraft:wither"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:ender_pearl"
|
||||
},
|
||||
"title": "Enderman Hunter",
|
||||
"description": "Obtain an Ender Pearl",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:monsters/monster_unlock",
|
||||
"criteria": {
|
||||
"ender_pearl": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
"minecraft:ender_pearl"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:lightning_rod"
|
||||
},
|
||||
"title": "Terrifying Lightning",
|
||||
"description": "Witness a Pig being struck by lightning and transforming into a Zombified Piglin",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": true,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:monsters/copper",
|
||||
"criteria": {
|
||||
"lightning_rod_pigs": {
|
||||
"trigger": "minecraft:lightning_strike",
|
||||
"conditions": {
|
||||
"player": {
|
||||
"type_specific": {
|
||||
"type": "minecraft:player",
|
||||
"looking_at": {
|
||||
"type": "minecraft:zombified_piglin"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lightning": [
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"distance": {
|
||||
"absolute": {
|
||||
"max": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:stone_sword"
|
||||
},
|
||||
"title": "Time to Get Deadly",
|
||||
"description": "Build a mob farm",
|
||||
"background": "minecraft:iron_block",
|
||||
"frame": "challenge",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"criteria": {
|
||||
"hostile_mob": {
|
||||
"trigger": "minecraft:player_killed_entity",
|
||||
"conditions": {
|
||||
"player": [],
|
||||
"entity": [
|
||||
{
|
||||
"condition": "minecraft:any_of",
|
||||
"terms": [
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"type": "minecraft:creeper"
|
||||
}
|
||||
},
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"type": "minecraft:enderman"
|
||||
}
|
||||
},
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"type": "minecraft:skeleton"
|
||||
}
|
||||
},
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"type": "minecraft:spider"
|
||||
}
|
||||
},
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"type": "minecraft:witch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"type": "minecraft:zombie"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:bone"
|
||||
},
|
||||
"title": "Skeleton Hunter",
|
||||
"description": "Obtain Bones",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:monsters/monster_unlock",
|
||||
"criteria": {
|
||||
"skeleton": {
|
||||
"trigger": "minecraft:player_killed_entity",
|
||||
"conditions": {
|
||||
"entity": [
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"type": "minecraft:skeleton"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:string"
|
||||
},
|
||||
"title": "Spider Hunter",
|
||||
"description": "Kill a Spider",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:monsters/monster_unlock",
|
||||
"criteria": {
|
||||
"spider": {
|
||||
"trigger": "minecraft:player_killed_entity",
|
||||
"conditions": {
|
||||
"entity": [
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"type": "minecraft:spider"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:potion",
|
||||
"components": {
|
||||
"minecraft:potion_contents": "healing"
|
||||
}
|
||||
},
|
||||
"title": "Dangerous Passenger",
|
||||
"description": "Capture a Witch in a Boat",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": true,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:monsters/monster_unlock",
|
||||
"criteria": {
|
||||
"witch_passenger": {
|
||||
"trigger": "minecraft:started_riding",
|
||||
"conditions": {
|
||||
"player": [
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"vehicle": {
|
||||
"type": "#minecraft:boat",
|
||||
"passenger": {
|
||||
"type": "minecraft:witch"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:splash_potion",
|
||||
"components": {
|
||||
"minecraft:potion_contents": {
|
||||
"potion": "minecraft:weakness"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "(Slightly Less) Dangerous Passenger",
|
||||
"description": "Capture a Zombie Villager in a Boat",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:monsters/witch_hostage",
|
||||
"criteria": {
|
||||
"zombie_villager_passenger": {
|
||||
"trigger": "minecraft:started_riding",
|
||||
"conditions": {
|
||||
"player": [
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"vehicle": {
|
||||
"type": "#minecraft:boat",
|
||||
"passenger": {
|
||||
"type": "minecraft:zombie_villager"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:rotten_flesh"
|
||||
},
|
||||
"title": "Zombie Hunter",
|
||||
"description": "Kill a Zombie",
|
||||
"frame": "task",
|
||||
"show_toast": false,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:monsters/monster_unlock",
|
||||
"criteria": {
|
||||
"zombie": {
|
||||
"trigger": "minecraft:player_killed_entity",
|
||||
"conditions": {
|
||||
"entity": [
|
||||
{
|
||||
"condition": "minecraft:entity_properties",
|
||||
"entity": "this",
|
||||
"predicate": {
|
||||
"type": "minecraft:zombie"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:netherrack"
|
||||
},
|
||||
"title": "Create Netherrack",
|
||||
"description": "This doesn't seem right\n\n",
|
||||
"frame": "challenge",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": true
|
||||
},
|
||||
"parent": "usefulskyblock:nether/tier_1_unlock",
|
||||
"criteria": {
|
||||
"craft-netherrack": {
|
||||
"trigger": "minecraft:impossible"
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:bell"
|
||||
},
|
||||
"title": "Saving a villager",
|
||||
"description": "They seem a bit hungry for something other than brains now",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": true
|
||||
},
|
||||
"parent": "usefulskyblock:nether/golden_apple",
|
||||
"criteria": {
|
||||
"cure-villager": {
|
||||
"trigger": "minecraft:cured_zombie_villager"
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:grass_block"
|
||||
},
|
||||
"title": "The Nether!",
|
||||
"description": "It's a bit toasty here",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:nether/tier_1_unlock",
|
||||
"criteria": {
|
||||
"enter-nether": {
|
||||
"trigger": "minecraft:changed_dimension",
|
||||
"conditions": {
|
||||
"from": "minecraft:overworld",
|
||||
"to": "minecraft:the_nether"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:gold_ingot"
|
||||
},
|
||||
"title": "Gold",
|
||||
"description": "Pigs love it",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:nether/enter_nether",
|
||||
"criteria": {
|
||||
"attain-cobble": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:gold_ingot",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:gravel"
|
||||
},
|
||||
"title": "Gravel",
|
||||
"description": "Worth it's weight in gold, somehow",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:nether/enter_nether",
|
||||
"criteria": {
|
||||
"attain-cobble": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:gravel",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:golden_apple"
|
||||
},
|
||||
"title": "I've got a Golden Apple",
|
||||
"description": "What would warrant such a thing",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:nether/get_gold",
|
||||
"criteria": {
|
||||
"get-gold-apple": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:golden_apple",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:nether_brick"
|
||||
},
|
||||
"title": "Smelting netherrack",
|
||||
"description": "Something about bricks",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:nether/craft_netherrack",
|
||||
"criteria": {
|
||||
"smelt-netherbrick": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:nether_brick",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:oak_sapling"
|
||||
},
|
||||
"title": "The first tier",
|
||||
"description": "Your new home between the clouds",
|
||||
"background": "minecraft:block/oak_log",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": true
|
||||
},
|
||||
"criteria": {
|
||||
"cobble-stack": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"player": {
|
||||
"type_specific": {
|
||||
"type": "minecraft:player",
|
||||
"advancements": {
|
||||
"usefulskyblock:skyblock/collect_64_cobble": true,
|
||||
"usefulskyblock:skyblock/eat_food": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:cobblestone",
|
||||
"count": 64
|
||||
},
|
||||
"title": "10K of Cobblestone\n...Why?",
|
||||
"description": "No, really; Why?",
|
||||
"frame": "challenge",
|
||||
"announce_to_chat": true,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/collect_1728_cobble",
|
||||
"criteria": {
|
||||
"attain-cobble-10000": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"player": {
|
||||
"type_specific": {
|
||||
"type": "minecraft:player",
|
||||
"stats": [
|
||||
{
|
||||
"type": "minecraft:mined",
|
||||
"stat": "minecraft:cobblestone",
|
||||
"value": 10000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:cobblestone",
|
||||
"count": 16
|
||||
},
|
||||
"title": "A tonne of Cobblestone!",
|
||||
"description": "A healthy amount",
|
||||
"frame": "challenge",
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/collect_64_cobble",
|
||||
"criteria": {
|
||||
"attain-cobble-1024": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"player": {
|
||||
"type_specific": {
|
||||
"type": "minecraft:player",
|
||||
"stats": [
|
||||
{
|
||||
"type": "minecraft:mined",
|
||||
"stat": "minecraft:cobblestone",
|
||||
"value": 1024
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:cobblestone",
|
||||
"count": 32
|
||||
},
|
||||
"title": "A single chest of Cobblestone!",
|
||||
"description": "You can start building now",
|
||||
"frame": "challenge",
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/collect_1024_cobble",
|
||||
"criteria": {
|
||||
"attain-cobble-1728": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"player": {
|
||||
"type_specific": {
|
||||
"type": "minecraft:player",
|
||||
"stats": [
|
||||
{
|
||||
"type": "minecraft:mined",
|
||||
"stat": "minecraft:cobblestone",
|
||||
"value": 1728
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:cobblestone",
|
||||
"count": 8
|
||||
},
|
||||
"title": "A stack of Cobblestone!",
|
||||
"description": "A good start",
|
||||
"frame": "challenge",
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/get_cobble",
|
||||
"criteria": {
|
||||
"attain-cobble-64": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"player": {
|
||||
"type_specific": {
|
||||
"type": "minecraft:player",
|
||||
"stats": [
|
||||
{
|
||||
"type": "minecraft:mined",
|
||||
"stat": "minecraft:cobblestone",
|
||||
"value": 64
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:beacon"
|
||||
},
|
||||
"title": "The beacons are lit",
|
||||
"description": "Gondor calls for aid",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": true,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:monsters/defeat_wither",
|
||||
"criteria": {
|
||||
"example": {
|
||||
"trigger": "minecraft:construct_beacon",
|
||||
"conditions": {
|
||||
"level": {
|
||||
"min": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:apple"
|
||||
},
|
||||
"title": "Eat food",
|
||||
"description": "400 kCal is not a meal!",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": true
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/skyblock_begin",
|
||||
"criteria": {
|
||||
"eat-food": {
|
||||
"trigger": "minecraft:consume_item"
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:clay"
|
||||
},
|
||||
"title": "Dried Wet Dirt",
|
||||
"description": "Place a Pointed Dripstone underneath Mud to drip out its moisture and create Clay",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": true,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/get_mud",
|
||||
"criteria": {
|
||||
"clay_ball": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
"minecraft:clay_ball"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:cobblestone"
|
||||
},
|
||||
"title": "Cobblestone!",
|
||||
"description": "The first of many..",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/skyblock_begin",
|
||||
"criteria": {
|
||||
"attain-cobble": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:cobblestone",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:glass"
|
||||
},
|
||||
"title": "Glass time",
|
||||
"description": "Don't smash it\n\nIf you can't figure out how to get more sand, take some time away and have a good meal. I hear fish can help you think",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/skyblock_begin",
|
||||
"criteria": {
|
||||
"get-seeds": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:glass",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:iron_ingot"
|
||||
},
|
||||
"title": "Iron",
|
||||
"description": "Iron you glad I didn't say banana",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:monsters/monster_unlock",
|
||||
"criteria": {
|
||||
"get-iron": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": ["minecraft:iron_ingot"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:iron_pickaxe"
|
||||
},
|
||||
"title": "Iron Tools",
|
||||
"description": "The Iron Age!\n\n - Craft an iron pickaxe, axe, hoe, and shovel",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/get_stone_tools",
|
||||
"criteria": {
|
||||
"get-pickaxe": {
|
||||
"trigger": "minecraft:recipe_crafted",
|
||||
"conditions": {
|
||||
"recipe_id": "minecraft:iron_pickaxe"
|
||||
}
|
||||
},
|
||||
"get-axe": {
|
||||
"trigger": "minecraft:recipe_crafted",
|
||||
"conditions": {
|
||||
"recipe_id": "minecraft:iron_axe"
|
||||
}
|
||||
},
|
||||
"get-hoe": {
|
||||
"trigger": "minecraft:recipe_crafted",
|
||||
"conditions": {
|
||||
"recipe_id": "minecraft:iron_hoe"
|
||||
}
|
||||
},
|
||||
"get-shovel": {
|
||||
"trigger": "minecraft:recipe_crafted",
|
||||
"conditions": {
|
||||
"recipe_id": "minecraft:iron_shovel"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:oak_log"
|
||||
},
|
||||
"title": "Timber Collection",
|
||||
"description": "A solid oak floor",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/skyblock_begin",
|
||||
"criteria": {
|
||||
"get-logs": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"condition": "minecraft:match_tool",
|
||||
"predicate": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "minecraft:oak_logs"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:mud"
|
||||
},
|
||||
"title": "Glorious mud",
|
||||
"description": "Use a bottle of water on some dirt",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": true,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/renew_dirt",
|
||||
"criteria": {
|
||||
"clay_ball": {
|
||||
"trigger": "minecraft:item_used_on_block",
|
||||
"conditions": {
|
||||
"location": [
|
||||
{
|
||||
"condition": "minecraft:match_tool",
|
||||
"predicate": {
|
||||
"items": [
|
||||
"minecraft:potion"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"condition": "minecraft:location_check",
|
||||
"predicate": {
|
||||
"block": {
|
||||
"blocks": "minecraft:dirt"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:wheat_seeds"
|
||||
},
|
||||
"title": "Wheat",
|
||||
"description": "Let's get this bread",
|
||||
"frame": "goal",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": true
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/skyblock_begin",
|
||||
"criteria": {
|
||||
"get-seeds": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:wheat_seeds",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "minecraft:stone_pickaxe"
|
||||
},
|
||||
"title": "Stone Tools",
|
||||
"description": "Rock and Stone!\n\n - Craft a stone pickaxe, axe, hoe, and shovel",
|
||||
"frame": "task",
|
||||
"show_toast": true,
|
||||
"announce_to_chat": false,
|
||||
"hidden": false
|
||||
},
|
||||
"parent": "usefulskyblock:skyblock/get_cobble",
|
||||
"criteria": {
|
||||
"get-pickaxe": {
|
||||
"trigger": "minecraft:recipe_crafted",
|
||||
"conditions": {
|
||||
"recipe_id": "minecraft:stone_pickaxe"
|
||||
}
|
||||
},
|
||||
"get-axe": {
|
||||
"trigger": "minecraft:recipe_crafted",
|
||||
"conditions": {
|
||||
"recipe_id": "minecraft:stone_axe"
|
||||
}
|
||||
},
|
||||
"get-hoe": {
|
||||
"trigger": "minecraft:recipe_crafted",
|
||||
"conditions": {
|
||||
"recipe_id": "minecraft:stone_hoe"
|
||||
}
|
||||
},
|
||||
"get-shovel": {
|
||||
"trigger": "minecraft:recipe_crafted",
|
||||
"conditions": {
|
||||
"recipe_id": "minecraft:stone_shovel"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue