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 {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id("xyz.jpenilla.run-paper") version "2.3.1"
|
id 'idea'
|
||||||
id 'org.jetbrains.kotlin.jvm'
|
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'
|
group = 'com.ncguy'
|
||||||
|
@ -31,6 +35,8 @@ dependencies {
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||||
|
|
||||||
|
testImplementation("com.google.code.gson:gson:2.13.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
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 {
|
processResources {
|
||||||
def props = [version: version]
|
def props = [version: version]
|
||||||
|
@ -70,3 +88,7 @@ processResources {
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(21)
|
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'
|
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;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Structure getActualStructure() {
|
public Structure getActualStructure() {
|
||||||
if (actualStructure == null) {
|
if (actualStructure == null) {
|
||||||
StructureManager structureManager = Bukkit.getStructureManager();
|
StructureManager structureManager = Bukkit.getStructureManager();
|
||||||
|
|
||||||
actualStructure = structureManager.loadStructure(name);
|
actualStructure = structureManager.loadStructure(name);
|
||||||
if(actualStructure == null) {
|
if(actualStructure == null) {
|
||||||
try (InputStream resourceAsStream = getClass().getResourceAsStream("/datapacks/usefulskyblock/data/" + name.getNamespace() + "/structures/" + name.getKey() + ".nbt")) {
|
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);
|
structureManager.registerStructure(name, actualStructure);
|
||||||
} catch (IOException e) {
|
} catch (IllegalArgumentException | IOException e) {
|
||||||
throw new RuntimeException(e);
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,66 +1,67 @@
|
||||||
package com.ncguy.usefulskyblock;
|
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.recipe.BiomeRod;
|
||||||
import com.ncguy.usefulskyblock.utils.Remark;
|
import com.ncguy.usefulskyblock.recipe.IRecipeProvider;
|
||||||
import com.ncguy.usefulskyblock.utils.RemarkSet;
|
import com.ncguy.usefulskyblock.recipe.SmeltingCraftingHandler;
|
||||||
import com.ncguy.usefulskyblock.world.PortalHandler;
|
import com.ncguy.usefulskyblock.world.PortalHandler;
|
||||||
import net.kyori.adventure.text.Component;
|
import org.bukkit.*;
|
||||||
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.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
import org.bukkit.inventory.Recipe;
|
||||||
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.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Iterator;
|
||||||
|
|
||||||
public final class UsefulSkyblock extends JavaPlugin implements Listener {
|
public final class UsefulSkyblock extends JavaPlugin implements Listener {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(UsefulSkyblock.class);
|
private static final Logger log = LoggerFactory.getLogger(UsefulSkyblock.class);
|
||||||
|
|
||||||
private BiomeRod biomeRodListener;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
|
|
||||||
System.out.println("OM EMABL:ED");
|
|
||||||
|
|
||||||
saveDefaultConfig();
|
saveDefaultConfig();
|
||||||
|
|
||||||
// Plugin startup logic
|
// Plugin startup logic
|
||||||
|
BiomeRod biomeRod = new BiomeRod();
|
||||||
|
SmeltingCraftingHandler smeltingCraftingHandler = new SmeltingCraftingHandler();
|
||||||
|
|
||||||
|
IRecipeProvider[] recipeProviders = new IRecipeProvider[]{
|
||||||
|
biomeRod,
|
||||||
|
smeltingCraftingHandler
|
||||||
|
};
|
||||||
|
|
||||||
PluginManager pluginManager = Bukkit.getPluginManager();
|
PluginManager pluginManager = Bukkit.getPluginManager();
|
||||||
pluginManager.registerEvents(this, this);
|
pluginManager.registerEvents(this, this);
|
||||||
pluginManager.registerEvents(new PortalHandler(), this);
|
pluginManager.registerEvents(new PortalHandler(), this);
|
||||||
biomeRodListener = new BiomeRod();
|
pluginManager.registerEvents(biomeRod, this);
|
||||||
pluginManager.registerEvents(biomeRodListener, this);
|
pluginManager.registerEvents(new CauldronCraftingHandler(), this);
|
||||||
BiomeRod.Init(Bukkit.getServer());
|
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
|
@Override
|
||||||
public void reloadConfig() {
|
public void reloadConfig() {
|
||||||
super.reloadConfig();
|
super.reloadConfig();
|
||||||
if(biomeRodListener == null)
|
new SkyblockConfigReloadEvent(getConfig()).callEvent();
|
||||||
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")))));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -68,82 +69,4 @@ public final class UsefulSkyblock extends JavaPlugin implements Listener {
|
||||||
// Plugin shutdown logic
|
// 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;
|
package com.ncguy.usefulskyblock.command;
|
||||||
|
|
||||||
|
import com.ncguy.usefulskyblock.Reference;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
|
@ -33,11 +34,7 @@ public abstract class AbstractSkyblockCommand {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected NamespacedKey key(String name) {
|
protected NamespacedKey overworldKey = Reference.key("void");
|
||||||
return new NamespacedKey("usefulskyblock", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected NamespacedKey overworldKey = key("void");
|
|
||||||
|
|
||||||
private Server serverInstance;
|
private Server serverInstance;
|
||||||
protected Server getServer() {
|
protected Server getServer() {
|
||||||
|
|
|
@ -4,17 +4,23 @@ import com.mojang.brigadier.StringReader;
|
||||||
import com.mojang.brigadier.arguments.ArgumentType;
|
import com.mojang.brigadier.arguments.ArgumentType;
|
||||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
import com.mojang.brigadier.context.CommandContext;
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
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.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
|
import com.ncguy.usefulskyblock.Reference;
|
||||||
|
import com.ncguy.usefulskyblock.SkyblockTeam;
|
||||||
import com.ncguy.usefulskyblock.StructureRef;
|
import com.ncguy.usefulskyblock.StructureRef;
|
||||||
|
import com.ncguy.usefulskyblock.handlers.InitialisationHandler;
|
||||||
import com.ncguy.usefulskyblock.utils.BoxVisualizer;
|
import com.ncguy.usefulskyblock.utils.BoxVisualizer;
|
||||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||||
import io.papermc.paper.command.brigadier.Commands;
|
import io.papermc.paper.command.brigadier.Commands;
|
||||||
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
|
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
|
||||||
import org.bukkit.Location;
|
import io.papermc.paper.registry.RegistryAccess;
|
||||||
import org.bukkit.Material;
|
import io.papermc.paper.registry.RegistryKey;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.structure.Mirror;
|
import org.bukkit.block.structure.Mirror;
|
||||||
import org.bukkit.block.structure.StructureRotation;
|
import org.bukkit.block.structure.StructureRotation;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
|
@ -22,6 +28,9 @@ import org.bukkit.entity.Player;
|
||||||
import org.bukkit.persistence.PersistentDataContainer;
|
import org.bukkit.persistence.PersistentDataContainer;
|
||||||
import org.bukkit.persistence.PersistentDataType;
|
import org.bukkit.persistence.PersistentDataType;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
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.Structure;
|
||||||
import org.bukkit.structure.StructureManager;
|
import org.bukkit.structure.StructureManager;
|
||||||
import org.bukkit.util.RayTraceResult;
|
import org.bukkit.util.RayTraceResult;
|
||||||
|
@ -32,242 +41,343 @@ import org.slf4j.LoggerFactory;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public class SkyblockAdminCommand extends AbstractSkyblockCommand {
|
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();
|
StructureManager structureManager = getServer().getStructureManager();
|
||||||
Map<NamespacedKey, Structure> structureMap = structureManager.getStructures();
|
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");
|
ctx.getSource().getExecutor().sendMessage("Found " + filteredKeys.size() + " structures");
|
||||||
filteredKeys.forEach(k -> ctx.getSource().getExecutor().sendMessage(k.toString()));
|
filteredKeys.forEach(k -> ctx.getSource().getExecutor().sendMessage(k.toString()));
|
||||||
|
|
||||||
log.info("Classic: {}", structureManager.loadStructure(key("classic")) != null);
|
return 0;
|
||||||
log.info("Classic-sand: {}", structureManager.loadStructure(key("classic-sand")) != null);
|
}
|
||||||
log.info("skyblock: {}", structureManager.loadStructure(key("skyblock")) != null);
|
|
||||||
|
|
||||||
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) {
|
return 0;
|
||||||
int radius = ctx.getArgument("radius", Integer.class);
|
}
|
||||||
String name = ctx.getArgument("name", String.class);
|
|
||||||
|
|
||||||
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();
|
if (structure == null) {
|
||||||
|
ctx.getSource().getExecutor().sendMessage("Structure not found");
|
||||||
Location start = origin.clone().subtract(radius, radius, radius);
|
return -1;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
return 0;
|
||||||
ctx.getSource().getExecutor().sendMessage("Structure not found");
|
}
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Location loc = ctx.getSource().getLocation();
|
public int executePlaceAnchor(CommandContext<CommandSourceStack> ctx) {
|
||||||
Vector halfSize = structure.getSize().divide(new Vector(2, 2, 2));
|
ctx.getSource().getExecutor().sendMessage("Placing anchor");
|
||||||
loc.subtract(halfSize);
|
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) {
|
return 0;
|
||||||
ctx.getSource().getExecutor().sendMessage("Placing anchor");
|
}
|
||||||
Location target = ctx.getSource().getExecutor().getLocation().subtract(0, 1, 0);
|
|
||||||
target.getBlock().setType(Material.STONE);
|
public int executeP1(CommandContext<CommandSourceStack> ctx) {
|
||||||
return 0;
|
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) {
|
return 0;
|
||||||
Entity executor = ctx.getSource().getExecutor();
|
}
|
||||||
executor.sendMessage("Placing P0");
|
|
||||||
|
|
||||||
if (executor instanceof Player) {
|
public int executePSave(CommandContext<CommandSourceStack> ctx) {
|
||||||
Player p = (Player) executor;
|
Entity executor = ctx.getSource().getExecutor();
|
||||||
RayTraceResult rayTraceResult = p.rayTraceBlocks(8);
|
String name = ctx.getArgument("name", String.class);
|
||||||
|
executor.sendMessage("Saving structure");
|
||||||
|
|
||||||
Vector hitPosition = rayTraceResult.getHitPosition();
|
if (!(executor instanceof Player)) return 0;
|
||||||
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
Player p = (Player) executor;
|
||||||
pdc.set(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());
|
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
||||||
Location p1 = p0.clone();
|
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 (p0 == null) {
|
||||||
if(pdc.has(p1Key, PersistentDataType.INTEGER_ARRAY)) {
|
executor.sendMessage("No P0 found");
|
||||||
int[] ints = pdc.get(p1Key, PersistentDataType.INTEGER_ARRAY);
|
return -1;
|
||||||
p1.set(ints[0], ints[1], ints[2]);
|
} else if (p1 == null) {
|
||||||
}
|
executor.sendMessage("No P1 found");
|
||||||
|
return -1;
|
||||||
box.createBox(p0, p1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int executeP1(CommandContext<CommandSourceStack> ctx) {
|
Structure structure = getServer().getStructureManager().createStructure();
|
||||||
Entity executor = ctx.getSource().getExecutor();
|
|
||||||
executor.sendMessage("Placing P1");
|
|
||||||
|
|
||||||
if (executor instanceof Player) {
|
World world = executor.getWorld();
|
||||||
Player p = (Player) executor;
|
Location p0Loc = new Location(world, p0[0], p0[1], p0[2]);
|
||||||
RayTraceResult rayTraceResult = p.rayTraceBlocks(8);
|
Location p1Loc = new Location(world, p1[0], p1[1], p1[2]);
|
||||||
|
|
||||||
Vector hitPosition = rayTraceResult.getHitPosition();
|
structure.fill(p0Loc, p1Loc, true);
|
||||||
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
|
||||||
pdc.set(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());
|
ctx.getSource().getExecutor().sendMessage("From " + p0Loc + " to " + p1Loc);
|
||||||
Location p0 = p1.clone();
|
ctx.getSource().getExecutor().sendMessage("Palette count: " + (long) structure.getPaletteCount());
|
||||||
|
ctx.getSource().getExecutor().sendMessage("Block count: " + structure.getPalettes().getFirst().getBlockCount());
|
||||||
|
|
||||||
NamespacedKey p0Key = key("_p0");
|
NamespacedKey nmKey = Reference.key(name);
|
||||||
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);
|
try {
|
||||||
|
File f = new File("structures/" + nmKey.getKey() + ".nbt");
|
||||||
}
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
f.getParentFile().mkdirs();
|
||||||
return 0;
|
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) {
|
return 0;
|
||||||
Entity executor = ctx.getSource().getExecutor();
|
}
|
||||||
String name = ctx.getArgument("name", String.class);
|
|
||||||
executor.sendMessage("Saving structure");
|
|
||||||
|
|
||||||
if (!(executor instanceof Player)) return 0;
|
public static LiteralCommandNode<CommandSourceStack> create() {
|
||||||
Player p = (Player) executor;
|
var root = Commands.literal("skyblock-admin");
|
||||||
|
var cmd = Get(SkyblockAdminCommand.class);
|
||||||
|
|
||||||
PersistentDataContainer pdc = p.getPersistentDataContainer();
|
root.requires(cmd::auth);
|
||||||
int[] p0 = pdc.get(key("_p0"), PersistentDataType.INTEGER_ARRAY);
|
|
||||||
int[] p1 = pdc.get(key("_p1"), PersistentDataType.INTEGER_ARRAY);
|
|
||||||
|
|
||||||
if (p0 == null) {
|
root.then(Commands.literal("reload").executes(cmd::executeReloadConfig));
|
||||||
executor.sendMessage("No P0 found");
|
|
||||||
return -1;
|
|
||||||
} else if (p1 == null) {
|
|
||||||
executor.sendMessage("No P1 found");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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]);
|
structures.then(Commands.literal("p0").executes(cmd::executeP0));
|
||||||
Location p1Loc = new Location(getOverworld(), p1[0], p1[1], p1[2]);
|
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);
|
root.then(structures);
|
||||||
ctx.getSource().getExecutor().sendMessage("Palette count: " + (long) structure.getPaletteCount());
|
root.then(islands);
|
||||||
ctx.getSource().getExecutor().sendMessage("Block count: " + structure.getPalettes().getFirst().getBlockCount());
|
|
||||||
|
|
||||||
NamespacedKey nmKey = key(name);
|
return root.build();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
private int executeBuildCentralEndPortal(CommandContext<CommandSourceStack> ctx) {
|
||||||
File f = new File("structures/" + nmKey.getKey() + ".nbt");
|
ctx.getSource().getExecutor().sendMessage("Building central end portal");
|
||||||
//noinspection ResultOfMethodCallIgnored
|
InitialisationHandler.initOverworld(ctx.getSource().getLocation().getWorld(), true);
|
||||||
f.getParentFile().mkdirs();
|
return 0;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
ScoreboardManager scoreboardManager = Bukkit.getServer().getScoreboardManager();
|
||||||
var root = Commands.literal("skyblock-admin");
|
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||||
var cmd = Get(SkyblockAdminCommand.class);
|
Team team = scoreboard.getPlayerTeam(player);
|
||||||
|
|
||||||
root.requires(cmd::auth);
|
if (team == null) {
|
||||||
|
player.sendMessage("No team found");
|
||||||
|
return -1;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int executeReloadConfig(CommandContext<CommandSourceStack> context) {
|
if (!new SkyblockTeam(team).unlockTier(tier)) player.sendMessage("Failed to unlock tier, likely already unlocked.");
|
||||||
JavaPlugin.getProvidingPlugin(SkyblockAdminCommand.class).reloadConfig();
|
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) {
|
player.sendMessage("Unlocking island tier " + tier);
|
||||||
return stack.getSender().isOp();
|
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
|
private int executeReloadConfig(CommandContext<CommandSourceStack> context) {
|
||||||
public Structure parse(StringReader reader) throws CommandSyntaxException {
|
JavaPlugin.getProvidingPlugin(SkyblockAdminCommand.class).reloadConfig();
|
||||||
NamespacedKey key = new NamespacedKey("usefulskyblock", reader.readString());
|
|
||||||
return new StructureRef(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
context.getSource().getExecutor().sendMessage("Reloaded config");
|
||||||
public ArgumentType<String> getNativeType() {
|
return 0;
|
||||||
return StringArgumentType.word();
|
}
|
||||||
}
|
|
||||||
|
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;
|
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.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.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.StructureRef;
|
||||||
|
import com.ncguy.usefulskyblock.data.BiomedStructure;
|
||||||
import com.ncguy.usefulskyblock.utils.MathsUtils;
|
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.CommandSourceStack;
|
||||||
import io.papermc.paper.command.brigadier.Commands;
|
import io.papermc.paper.command.brigadier.Commands;
|
||||||
import org.bukkit.Location;
|
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
|
||||||
import org.bukkit.Material;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.bukkit.NamespacedKey;
|
import net.kyori.adventure.text.format.Style;
|
||||||
import org.bukkit.World;
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.*;
|
||||||
import org.bukkit.block.BlockState;
|
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.Mirror;
|
||||||
import org.bukkit.block.structure.StructureRotation;
|
import org.bukkit.block.structure.StructureRotation;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.persistence.PersistentDataContainer;
|
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||||
import org.bukkit.persistence.PersistentDataType;
|
|
||||||
import org.bukkit.scoreboard.Scoreboard;
|
import org.bukkit.scoreboard.Scoreboard;
|
||||||
import org.bukkit.scoreboard.ScoreboardManager;
|
import org.bukkit.scoreboard.ScoreboardManager;
|
||||||
import org.bukkit.scoreboard.Team;
|
import org.bukkit.scoreboard.Team;
|
||||||
import org.bukkit.structure.Structure;
|
import org.bukkit.util.BlockVector;
|
||||||
import org.bukkit.util.Vector;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import static com.ncguy.usefulskyblock.Reference.key;
|
||||||
|
|
||||||
public class SkyblockGenCommand extends AbstractSkyblockCommand {
|
public class SkyblockGenCommand extends AbstractSkyblockCommand {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(SkyblockGenCommand.class);
|
private static final Logger log = LoggerFactory.getLogger(SkyblockGenCommand.class);
|
||||||
|
|
||||||
private StructureRef[] centralIslands = {
|
private float playerIslandSpacing = 4096;
|
||||||
new StructureRef(key("classic")),
|
|
||||||
};
|
|
||||||
|
|
||||||
private StructureRef[] t1Islands = {
|
private StructureRef[] centralIslands = {new BiomedStructure(key("classic"), Biome.OCEAN),};
|
||||||
new StructureRef(key("classic-sand")),
|
|
||||||
new StructureRef(key("classic-sand")),
|
|
||||||
new StructureRef(key("classic-sand")),
|
|
||||||
new StructureRef(key("classic-sand")),
|
|
||||||
};
|
|
||||||
|
|
||||||
private StructureRef[] t2Islands = {
|
private static StructureRef[] t1Islands = {new BiomedStructure(key("classic-sand"), Biome.BEACH),};
|
||||||
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 StructureRef[] t3Islands = {
|
private static StructureRef[] t2Islands = {new BiomedStructure(NamespacedKey.minecraft("igloo/top"),
|
||||||
new StructureRef(key("classic")),
|
Biome.SNOWY_PLAINS), new BiomedStructure(
|
||||||
new StructureRef(key("classic-sand")),
|
NamespacedKey.minecraft("swamp_hut"), Biome.SWAMP),};
|
||||||
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[] t3Islands = {new BiomedStructure(NamespacedKey.minecraft("monument"),
|
||||||
private float playerIslandSpacing = 1024;
|
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 T1_ISLAND_SLOTS = 8;
|
||||||
private static final int T2_ISLAND_SLOTS = 24;
|
private static final int T2_ISLAND_SLOTS = t2Islands.length;
|
||||||
private static final int T3_ISLAND_SLOTS = 48;
|
private static final int T3_ISLAND_SLOTS = t3Islands.length;
|
||||||
|
|
||||||
private static float T1_ISLAND_SPACING = T1_ISLAND_SLOTS << 3;
|
private static float T1_ISLAND_SPACING = T1_ISLAND_SLOTS << 3;
|
||||||
private static float T2_ISLAND_SPACING = T2_ISLAND_SLOTS << 3;
|
private static float T2_ISLAND_SPACING = 192; // 24 << 3
|
||||||
private static float T3_ISLAND_SPACING = T3_ISLAND_SLOTS << 3;
|
private static float T3_ISLAND_SPACING = 384; // 48 << 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int executeGenerate(CommandContext<CommandSourceStack> ctx) {
|
public int executeGenerate(CommandContext<CommandSourceStack> ctx) {
|
||||||
Entity executor = ctx.getSource().getExecutor();
|
Entity executor = ctx.getSource().getExecutor();
|
||||||
if(!(executor instanceof Player))
|
if (!(executor instanceof Player player)) return 0;
|
||||||
return 0;
|
|
||||||
|
|
||||||
World overworld = getServer().getWorld(new NamespacedKey("minecraft", "overworld"));
|
World overworld = getServer().getWorld(new NamespacedKey("minecraft", "overworld"));
|
||||||
|
|
||||||
PersistentDataContainer pdc = executor.getPersistentDataContainer();
|
|
||||||
|
|
||||||
ScoreboardManager scoreboardManager = getServer().getScoreboardManager();
|
ScoreboardManager scoreboardManager = getServer().getScoreboardManager();
|
||||||
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
|
||||||
Player player = (Player) executor;
|
|
||||||
Team playerTeam = scoreboard.getPlayerTeam(player);
|
Team playerTeam = scoreboard.getPlayerTeam(player);
|
||||||
if(playerTeam == null) {
|
if (playerTeam == null) {
|
||||||
executor.sendMessage("Not part of a team, can't generate skyblock world");
|
executor.sendMessage("Not part of a team, can't generate skyblock world");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// TODO Sanitize playerTeam.getName()
|
|
||||||
String playerTeamName = playerTeam.getName();
|
String playerTeamName = playerTeam.getName();
|
||||||
|
String sanitizedTeamName = playerTeamName.replaceAll("[^a-z0-9.]", "_");
|
||||||
|
|
||||||
PersistentDataContainer worldPDC = overworld.getPersistentDataContainer();
|
var teamSpawn = Reference.SKYBLOCK_TEAM_ROOT.withSuffix("." + sanitizedTeamName).assign(overworld);
|
||||||
// worldPDC.set(key("skyblock.team." + playerTeam.getName()), PersistentDataType.INTEGER_ARRAY, new int[]{tpLoc.getBlockX(), tpLoc.getBlockY(), tpLoc.getBlockZ()});
|
var islandHomeLoc = Reference.ISLAND_HOME_LOC.assign(player);
|
||||||
if(worldPDC.has(key("skyblock.team." + playerTeamName), PersistentDataType.INTEGER_ARRAY)) {
|
var worldTeamCount = Reference.SKYBLOCK_TEAM_COUNT.assign(overworld);
|
||||||
int[] ints = worldPDC.get(key("skyblock.team." + playerTeamName), PersistentDataType.INTEGER_ARRAY);
|
|
||||||
Location loc = new Location(overworld, ints[0], ints[1], ints[2]);
|
if (islandHomeLoc.has()) {
|
||||||
executor.teleport(loc);
|
executor.sendMessage("You already have an island assigned to you.");
|
||||||
pdc.set(key("island.home.loc"), PersistentDataType.INTEGER_ARRAY, ints);
|
|
||||||
executor.sendMessage("Teleported to island home");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
executor.sendMessage("Generating skyblock world for " + playerTeam.getName() + "...");
|
if (teamSpawn.has()) {
|
||||||
|
BlockVector vec = teamSpawn.get();
|
||||||
int count = 0;
|
Location loc = new Location(overworld, vec.getX(), vec.getY(), vec.getZ());
|
||||||
if(worldPDC.has(key("skyblock.team.count"), PersistentDataType.INTEGER)) {
|
executor.teleport(loc);
|
||||||
//noinspection DataFlowIssue
|
islandHomeLoc.set(vec);
|
||||||
count = worldPDC.get(key("skyblock.team.count"), PersistentDataType.INTEGER);
|
executor.sendMessage("Teleported to island home..");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Location originLoc;
|
executor.sendMessage(
|
||||||
if(count == 0) {
|
Component.text("Generating skyblock world for ")
|
||||||
originLoc = new Location(overworld, 0, 96, 0);
|
.append(playerTeam.displayName(), Component.text(" >>> ")));
|
||||||
}else{
|
|
||||||
int ring = MathsUtils.getRing(count);
|
|
||||||
int slot = MathsUtils.getSlot(count);
|
|
||||||
|
|
||||||
int numSlots = MathsUtils.getSlotsInRing(ring);
|
int count = worldTeamCount.getOrDefault(0);
|
||||||
|
|
||||||
float step = 360f / numSlots;
|
// int ring = MathsUtils.getRing(count);
|
||||||
float angle = slot * step;
|
// int slot = MathsUtils.getSlot(count);
|
||||||
float x = (float) Math.cos(angle) * playerIslandSpacing;
|
// int numSlots = MathsUtils.getSlotsInRing(ring);
|
||||||
float y = (float) Math.sin(angle) * playerIslandSpacing;
|
|
||||||
originLoc = new Location(overworld, x, 96, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
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()});
|
float step = 360f / numSlots;
|
||||||
worldPDC.set(key("skyblock.team.count"), PersistentDataType.INTEGER, count + 1);
|
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);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,9 +272,307 @@ public class SkyblockGenCommand extends AbstractSkyblockCommand {
|
||||||
var root = Commands.literal("skyblock");
|
var root = Commands.literal("skyblock");
|
||||||
|
|
||||||
var cmd = Get(SkyblockGenCommand.class);
|
var cmd = Get(SkyblockGenCommand.class);
|
||||||
|
|
||||||
root.then(Commands.literal("generate").executes(cmd::executeGenerate));
|
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();
|
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;
|
package com.ncguy.usefulskyblock.recipe;
|
||||||
|
|
||||||
import com.ncguy.usefulskyblock.UsefulSkyblock;
|
import com.ncguy.usefulskyblock.UsefulSkyblock;
|
||||||
|
import com.ncguy.usefulskyblock.events.SkyblockConfigReloadEvent;
|
||||||
import com.ncguy.usefulskyblock.utils.BossBarProgressMonitor;
|
import com.ncguy.usefulskyblock.utils.BossBarProgressMonitor;
|
||||||
import com.ncguy.usefulskyblock.utils.IProgressMonitor;
|
import com.ncguy.usefulskyblock.utils.IProgressMonitor;
|
||||||
import com.ncguy.usefulskyblock.utils.Remark;
|
import com.ncguy.usefulskyblock.utils.Remark;
|
||||||
import com.ncguy.usefulskyblock.utils.RemarkSet;
|
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.RegistryAccess;
|
||||||
import io.papermc.paper.registry.RegistryKey;
|
import io.papermc.paper.registry.RegistryKey;
|
||||||
import io.papermc.paper.util.Tick;
|
import io.papermc.paper.util.Tick;
|
||||||
import net.kyori.adventure.bossbar.BossBar;
|
import net.kyori.adventure.bossbar.BossBar;
|
||||||
import net.kyori.adventure.text.Component;
|
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.*;
|
||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.Block;
|
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.configuration.file.FileConfiguration;
|
||||||
import org.bukkit.entity.BlockDisplay;
|
import org.bukkit.entity.BlockDisplay;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
@ -20,6 +29,7 @@ import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.block.BlockPlaceEvent;
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.Recipe;
|
||||||
import org.bukkit.inventory.ShapedRecipe;
|
import org.bukkit.inventory.ShapedRecipe;
|
||||||
import org.bukkit.inventory.meta.ItemMeta;
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
import org.bukkit.persistence.PersistentDataContainer;
|
import org.bukkit.persistence.PersistentDataContainer;
|
||||||
|
@ -29,206 +39,239 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.bukkit.scheduler.BukkitScheduler;
|
import org.bukkit.scheduler.BukkitScheduler;
|
||||||
import org.bukkit.util.BlockVector;
|
import org.bukkit.util.BlockVector;
|
||||||
import org.bukkit.util.Transformation;
|
import org.bukkit.util.Transformation;
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
import org.joml.AxisAngle4f;
|
import org.joml.AxisAngle4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.*;
|
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 rodkey = new NamespacedKey(Namespace, "biomerod");
|
||||||
private static NamespacedKey flag = new NamespacedKey(Namespace, "flag_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);
|
ShapedRecipe signRecipe = new ShapedRecipe(rodkey, sign);
|
||||||
ItemMeta meta = sign.getItemMeta();
|
signRecipe.shape(" ", "DSD", " ");
|
||||||
PersistentDataContainer pdc = meta.getPersistentDataContainer();
|
signRecipe.setIngredient('D', Material.DIAMOND);
|
||||||
pdc.set(flag, PersistentDataType.STRING, flag.asString());
|
signRecipe.setIngredient('S', Material.STICK);
|
||||||
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);
|
return List.of(signRecipe);
|
||||||
signRecipe.shape(" ", "DSD", " ");
|
}
|
||||||
signRecipe.setIngredient('D', Material.DIAMOND);
|
|
||||||
signRecipe.setIngredient('S', Material.STICK);
|
|
||||||
|
|
||||||
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() {
|
final World world = event.getBlockAgainst().getWorld();
|
||||||
reloadBiomeMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
public RemarkSet reloadBiomeMap() {
|
List<Location> biomeLocations = new ArrayList<>();
|
||||||
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");
|
|
||||||
|
|
||||||
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)) {
|
BlockVector center = new BlockVector(event.getBlockAgainst().getX(), event.getBlockAgainst().getY(),
|
||||||
String materialName = m.get("material");
|
event.getBlockAgainst().getZ());
|
||||||
String biomeName = m.get("biome");
|
|
||||||
Material material = Material.getMaterial(materialName);
|
|
||||||
Biome biome = biomeRegistry.get(Objects.requireNonNull(NamespacedKey.fromString(biomeName)));
|
|
||||||
|
|
||||||
if(material == null || biome == null) {
|
for (int x = -radius; x <= radius; x += 4) {
|
||||||
System.err.println("Invalid biome rod material or biome: " + materialName + " " + biomeName);
|
for (int y = -radius; y <= radius; y += 4) {
|
||||||
if(material == null)
|
for (int z = -radius; z <= radius; z += 4) {
|
||||||
set.add("biome-map", "Invalid biome rod material: " + materialName);
|
BlockVector vec = new BlockVector(x, y, z);
|
||||||
if(biome == null)
|
vec.add(center);
|
||||||
set.add("biome-map", "Invalid biome rod biome: " + biomeName);
|
if (vec.distance(center) <= radius)
|
||||||
continue;
|
biomeLocations.add(new Location(world, vec.getX(), vec.getY(), vec.getZ()));
|
||||||
}
|
|
||||||
|
|
||||||
biomeMap.put(material, biome);
|
|
||||||
}
|
}
|
||||||
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) {
|
float progress = (float) remainder / sinkDurationTicks;
|
||||||
if(itemStack == null)
|
float scale = 1.0f - progress;
|
||||||
return false;
|
|
||||||
PersistentDataContainer pdc = itemStack.getItemMeta().getPersistentDataContainer();
|
float meshScale = (progress * 0.5f) + 0.5f;
|
||||||
return pdc.has(flag, PersistentDataType.STRING);
|
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) {
|
scheduler.runTaskLater(plugin, () -> sinkRodIntoBlock(display, plugin, scheduler, remainder - 1, facing), 1);
|
||||||
return biomeMap.containsKey(block.getType());
|
}
|
||||||
|
|
||||||
|
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
|
Set<Chunk> chunksToRefresh = new HashSet<>();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
final World world = event.getBlockAgainst().getWorld();
|
for (int i = 0; i < Math.min(batchSize, locations.size()); i++) {
|
||||||
|
Location loc = locations.remove();
|
||||||
List<Location> biomeLocations = new ArrayList<>();
|
if (loc == null) break;
|
||||||
|
world.setBiome(loc, biome);
|
||||||
JavaPlugin plugin = JavaPlugin.getPlugin(UsefulSkyblock.class);
|
Chunk chunkAt = world.getChunkAt(loc);
|
||||||
FileConfiguration config = plugin.getConfig();
|
chunksToRefresh.add(chunkAt);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int sinkDurationTicks = Tick.tick().fromDuration(Duration.ofSeconds(2));
|
progress.setProgress(locations.size());
|
||||||
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)
|
|
||||||
));
|
|
||||||
|
|
||||||
e.setPersistent(false);
|
chunksToRefresh.forEach(x -> world.refreshChunk(x.getX(), x.getZ()));
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
float progress = (float) remainder / sinkDurationTicks;
|
if (locations.isEmpty()) {
|
||||||
float scale = 1.0f - progress;
|
progress.finish();
|
||||||
|
return true;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean modifyBiomeAsync(World world, Queue<Location> locations, Biome biome, JavaPlugin plugin, BukkitScheduler scheduler, int batchSize, IProgressMonitor progress) {
|
scheduler.runTaskLater(plugin,
|
||||||
if(locations.isEmpty()) {
|
() -> modifyBiomeAsync(world, locations, biome, plugin, scheduler, batchSize, progress), 1);
|
||||||
progress.finish();
|
return false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
package com.ncguy.usefulskyblock.utils;
|
||||||
|
|
||||||
|
import org.bukkit.util.Vector;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
@ -70,4 +72,38 @@ public class MathsUtils {
|
||||||
return ring << 2;
|
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;
|
package com.ncguy.usefulskyblock.utils;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class RemarkSet extends HashSet<Remark> {
|
public class RemarkSet extends HashSet<Remark> {
|
||||||
|
|
||||||
|
@ -8,4 +9,9 @@ public class RemarkSet extends HashSet<Remark> {
|
||||||
add(new Remark(domain, message));
|
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;
|
package com.ncguy.usefulskyblock.world;
|
||||||
|
|
||||||
|
import com.ncguy.usefulskyblock.command.SkyblockGenCommand;
|
||||||
import io.papermc.paper.math.BlockPosition;
|
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.BlockState;
|
||||||
|
import org.bukkit.block.data.Orientable;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.world.PortalCreateEvent;
|
import org.bukkit.event.world.PortalCreateEvent;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.bukkit.util.BoundingBox;
|
import org.bukkit.util.BoundingBox;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.bukkit.plugin.java.JavaPlugin.getProvidingPlugin;
|
||||||
|
|
||||||
public class PortalHandler implements Listener {
|
public class PortalHandler implements Listener {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(PortalHandler.class);
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onPortalCreate(PortalCreateEvent event) {
|
public void onPortalCreate(PortalCreateEvent event) {
|
||||||
|
log.info("Portal created: {}, reason: {}", event, event.getReason().name());
|
||||||
if(event.getReason() != PortalCreateEvent.CreateReason.NETHER_PAIR)
|
if(event.getReason() != PortalCreateEvent.CreateReason.NETHER_PAIR)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Entity entity = event.getEntity();
|
Entity entity = event.getEntity();
|
||||||
|
log.info("Portal entity: {}", entity);
|
||||||
if(entity == null)
|
if(entity == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
BoundingBox bb = new BoundingBox();
|
|
||||||
List<BlockState> blocks = event.getBlocks();
|
List<BlockState> blocks = event.getBlocks();
|
||||||
bb.shift(blocks.getFirst().getLocation());
|
BlockState first = blocks.getFirst();
|
||||||
for (int i = 1; i < blocks.size(); i++)
|
|
||||||
bb.expand(blocks.get(i).getLocation().toVector());
|
|
||||||
|
|
||||||
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:
|
skyblock:
|
||||||
|
fishing:
|
||||||
|
sand_chance: 0.4
|
||||||
biomes:
|
biomes:
|
||||||
rod-radius: 16
|
rod-radius: 16
|
||||||
biome-map:
|
biome-map:
|
||||||
|
@ -14,4 +16,5 @@ skyblock:
|
||||||
biome: "minecraft:forest"
|
biome: "minecraft:forest"
|
||||||
- material: "SNOW_BLOCK"
|
- material: "SNOW_BLOCK"
|
||||||
biome: "minecraft:snowy_plains"
|
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",
|
"type": "minecraft:the_nether",
|
||||||
"generator": {
|
"generator": {
|
||||||
"type": "flat",
|
"type": "minecraft:flat",
|
||||||
"settings":{
|
"settings": {
|
||||||
"layers": [],
|
"biome": "minecraft:nether_wastes",
|
||||||
"biome": "minecraft:hell",
|
"lakes": false,
|
||||||
"structure_overrides": []
|
"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