summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDico Karssiens <dico.karssiens@gmail.com>2018-11-17 21:32:43 +0000
committerDico Karssiens <dico.karssiens@gmail.com>2018-11-17 21:32:43 +0000
commit5ef2584fdb6e4db482aa4c57e6ecf0202c67a48d (patch)
tree7954c8fb9048c81246d5c53a4cba5b7646d4b6e1
parent0f196f59c6a4cb76ab8409da62ff1f35505f94a8 (diff)
Tweak some command stuff, clear/swap entities
-rw-r--r--build.gradle.kts19
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java556
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/parameter/ContextParser.java554
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCallFlags.java186
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java356
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java821
-rw-r--r--dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt135
-rw-r--r--src/main/kotlin/io/dico/parcels2/JobDispatcher.kt705
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt205
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelWorld.kt206
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt305
-rw-r--r--src/main/kotlin/io/dico/parcels2/PlayerProfile.kt390
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/Entities.kt38
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt173
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt406
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt767
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt505
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt566
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt37
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/PluginAware.kt18
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/PluginScheduler.kt20
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt112
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt212
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/parallel.kt9
-rw-r--r--todo.md206
25 files changed, 3953 insertions, 3554 deletions
diff --git a/build.gradle.kts b/build.gradle.kts
index 211a978..9194e56 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -13,7 +13,7 @@ version = "0.2"
plugins {
java
- kotlin("jvm") version "1.3.0-rc-146"
+ kotlin("jvm") version "1.3.0"
id("com.github.johnrengelman.plugin-shadow") version "2.0.3"
}
@@ -30,10 +30,11 @@ allprojects {
maven("https://dl.bintray.com/kotlin/kotlin-dev/")
maven("https://dl.bintray.com/kotlin/kotlin-eap/")
maven("https://dl.bintray.com/kotlin/kotlinx/")
+ maven("http://maven.sk89q.com/repo")
}
dependencies {
- val spigotVersion = "1.13.1-R0.1-SNAPSHOT"
+ val spigotVersion = "1.13.2-R0.1-SNAPSHOT"
c.provided("org.bukkit:bukkit:$spigotVersion") { isTransitive = false }
c.provided("org.spigotmc:spigot-api:$spigotVersion") { isTransitive = false }
@@ -52,13 +53,15 @@ project(":dicore3:dicore3-core") {
}
}
+val coroutinesCore = kotlinx("coroutines-core:0.26.1-eap13")
+
project(":dicore3:dicore3-command") {
apply<KotlinPlatformJvmPlugin>()
dependencies {
c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect"))
- c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13"))
+ c.kotlinStd(coroutinesCore)
compile(project(":dicore3:dicore3-core"))
compile("com.thoughtworks.paranamer:paranamer:2.8")
@@ -72,12 +75,13 @@ dependencies {
c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect"))
- c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13"))
- c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.7-eap13")
+ c.kotlinStd(coroutinesCore)
+ c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.12")
// not on sk89q maven repo yet
- compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar"))
- compileClasspath(files("$rootDir/debug/lib/spigot-1.13.1.jar"))
+ //compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar"))
+ // compileClasspath(files("$rootDir/debug/lib/spigot-1.13.2.jar"))
+ compileClasspath("com.sk89q.worldedit:worldedit-bukkit:7.0.0-SNAPSHOT")
compile("org.jetbrains.exposed:exposed:0.10.5") { isTransitive = false }
compile("joda-time:joda-time:2.10")
@@ -167,7 +171,6 @@ val ConfigurationContainer.`kotlinStd`: Configuration
get() = findByName("kotlinStd") ?: create("kotlinStd").let { compileClasspath.extendsFrom(it) }
fun Jar.fromFiles(files: Iterable<File>) {
- return
afterEvaluate { from(*files.map { if (it.isDirectory) it else zipTree(it) }.toTypedArray()) }
}
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java b/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java
index 2b70eaf..4df8c49 100644
--- a/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java
@@ -1,275 +1,281 @@
-package io.dico.dicore.command;
-
-import io.dico.dicore.command.parameter.ArgumentBuffer;
-import io.dico.dicore.command.registration.BukkitCommand;
-import org.bukkit.Location;
-import org.bukkit.command.CommandSender;
-
-import java.util.*;
-
-public class RootCommandAddress extends ModifiableCommandAddress implements ICommandDispatcher {
- @Deprecated
- public static final RootCommandAddress INSTANCE = new RootCommandAddress();
-
- public RootCommandAddress() {
- }
-
- @Override
- public Command getCommand() {
- return null;
- }
-
- @Override
- public boolean isRoot() {
- return true;
- }
-
- @Override
- public List<String> getNames() {
- return Collections.emptyList();
- }
-
- @Override
- public ModifiableCommandAddress getParent() {
- return null;
- }
-
- @Override
- public String getMainKey() {
- return null;
- }
-
- @Override
- public String getAddress() {
- return "";
- }
-
- @Override
- public void registerToCommandMap(String fallbackPrefix, Map<String, org.bukkit.command.Command> map, EOverridePolicy overridePolicy) {
- Objects.requireNonNull(overridePolicy);
- //debugChildren(this);
- Map<String, ChildCommandAddress> children = this.children;
- Map<ChildCommandAddress, BukkitCommand> wrappers = new IdentityHashMap<>();
-
- for (ChildCommandAddress address : children.values()) {
- if (!wrappers.containsKey(address)) {
- wrappers.put(address, new BukkitCommand(address));
- }
- }
-
- for (Map.Entry<String, ChildCommandAddress> entry : children.entrySet()) {
- String key = entry.getKey();
- ChildCommandAddress address = entry.getValue();
- boolean override = overridePolicy == EOverridePolicy.OVERRIDE_ALL;
- if (!override && key.equals(address.getMainKey())) {
- override = overridePolicy == EOverridePolicy.MAIN_KEY_ONLY || overridePolicy == EOverridePolicy.MAIN_AND_FALLBACK;
- }
-
- registerMember(map, key, wrappers.get(address), override);
-
- if (fallbackPrefix != null) {
- key = fallbackPrefix + key;
- override = overridePolicy != EOverridePolicy.OVERRIDE_NONE && overridePolicy != EOverridePolicy.MAIN_KEY_ONLY;
- registerMember(map, key, wrappers.get(address), override);
- }
- }
-
- }
-
- private static void debugChildren(ModifiableCommandAddress address) {
- Collection<String> keys = address.getChildrenMainKeys();
- for (String key : keys) {
- ChildCommandAddress child = address.getChild(key);
- System.out.println(child.getAddress());
- debugChildren(child);
- }
- }
-
- private static void registerMember(Map<String, org.bukkit.command.Command> map,
- String key, org.bukkit.command.Command value, boolean override) {
- if (override) {
- map.put(key, value);
- } else {
- map.putIfAbsent(key, value);
- }
- }
-
- @Override
- public void unregisterFromCommandMap(Map<String, org.bukkit.command.Command> map) {
- Set<ICommandAddress> children = new HashSet<>(this.children.values());
- Iterator<Map.Entry<String, org.bukkit.command.Command>> iterator = map.entrySet().iterator();
- while (iterator.hasNext()) {
- Map.Entry<String, org.bukkit.command.Command> entry = iterator.next();
- org.bukkit.command.Command cmd = entry.getValue();
- if (cmd instanceof BukkitCommand && children.contains(((BukkitCommand) cmd).getOrigin())) {
- iterator.remove();
- }
- }
- }
-
- @Override
- public ModifiableCommandAddress getDeepChild(ArgumentBuffer buffer) {
- ModifiableCommandAddress cur = this;
- ChildCommandAddress child;
- while (buffer.hasNext()) {
- child = cur.getChild(buffer.next());
- if (child == null) {
- buffer.rewind();
- return cur;
- }
-
- cur = child;
- }
- return cur;
- }
-
- @Override
- public ModifiableCommandAddress getCommandTarget(CommandSender sender, ArgumentBuffer buffer) {
- ModifiableCommandAddress cur = this;
- ChildCommandAddress child;
- while (buffer.hasNext()) {
- child = cur.getChild(buffer.next());
- if (child == null
- || (child.hasCommand() && !child.getCommand().isVisibleTo(sender))
- || (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) {
- buffer.rewind();
- break;
- }
-
- cur = child;
- }
-
- return cur;
- }
-
- @Override
- public ModifiableCommandAddress getCommandTarget(ExecutionContext context, ArgumentBuffer buffer) throws CommandException {
- CommandSender sender = context.getSender();
- ModifiableCommandAddress cur = this;
- ChildCommandAddress child;
- while (buffer.hasNext()) {
- int cursor = buffer.getCursor();
-
- child = cur.getChild(context, buffer);
-
- if (child == null
- || (context.isTabComplete() && !buffer.hasNext())
- || (child.hasCommand() && !child.getCommand().isVisibleTo(sender))
- || (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) {
- buffer.setCursor(cursor);
- break;
- }
-
- cur = child;
-
- context.setAddress(child);
- if (child.hasCommand() && child.isCommandTrailing()) {
- child.getCommand().initializeAndFilterContext(context);
- child.getCommand().execute(context.getSender(), context);
- }
- }
-
- return cur;
- }
-
- @Override
- public boolean dispatchCommand(CommandSender sender, String[] command) {
- return dispatchCommand(sender, new ArgumentBuffer(command));
- }
-
- @Override
- public boolean dispatchCommand(CommandSender sender, String usedLabel, String[] args) {
- return dispatchCommand(sender, new ArgumentBuffer(usedLabel, args));
- }
-
- @Override
- public boolean dispatchCommand(CommandSender sender, ArgumentBuffer buffer) {
- ExecutionContext context = new ExecutionContext(sender, buffer, false);
-
- ModifiableCommandAddress targetAddress = null;
-
- try {
- targetAddress = getCommandTarget(context, buffer);
- Command target = targetAddress.getCommand();
-
- if (target == null) {
- if (targetAddress.hasHelpCommand()) {
- target = targetAddress.getHelpCommand().getCommand();
- } else {
- return false;
- }
- }
-
- context.setCommand(target);
-
- if (!targetAddress.isCommandTrailing()) {
- target.initializeAndFilterContext(context);
- String message = target.execute(sender, context);
- if (message != null && !message.isEmpty()) {
- context.sendMessage(EMessageType.RESULT, message);
- }
- }
-
- } catch (Throwable t) {
- if (targetAddress == null) {
- targetAddress = this;
- }
- targetAddress.getChatHandler().handleException(sender, context, t);
- }
-
- return true;
- }
-
- @Override
- public List<String> getTabCompletions(CommandSender sender, Location location, String[] args) {
- return getTabCompletions(sender, location, new ArgumentBuffer(args));
- }
-
- @Override
- public List<String> getTabCompletions(CommandSender sender, String usedLabel, Location location, String[] args) {
- return getTabCompletions(sender, location, new ArgumentBuffer(usedLabel, args));
- }
-
- @Override
- public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) {
- ExecutionContext context = new ExecutionContext(sender, buffer, true);
-
- try {
- ICommandAddress target = getCommandTarget(context, buffer);
-
- List<String> out;
- if (target.hasCommand()) {
- context.setCommand(target.getCommand());
- target.getCommand().initializeAndFilterContext(context);
- out = target.getCommand().tabComplete(sender, context, location);
- } else {
- out = Collections.emptyList();
- }
-
- int cursor = buffer.getCursor();
- String input;
- if (cursor >= buffer.size()) {
- input = "";
- } else {
- input = buffer.get(cursor).toLowerCase();
- }
-
- boolean wrapped = false;
- for (String child : target.getChildrenMainKeys()) {
- if (child.toLowerCase().startsWith(input)) {
- if (!wrapped) {
- out = new ArrayList<>(out);
- wrapped = true;
- }
- out.add(child);
- }
- }
-
- return out;
-
- } catch (CommandException ex) {
- return Collections.emptyList();
- }
-
- }
-}
+package io.dico.dicore.command;
+
+import io.dico.dicore.command.parameter.ArgumentBuffer;
+import io.dico.dicore.command.registration.BukkitCommand;
+import org.bukkit.Location;
+import org.bukkit.command.CommandSender;
+
+import java.util.*;
+
+public class RootCommandAddress extends ModifiableCommandAddress implements ICommandDispatcher {
+ @Deprecated
+ public static final RootCommandAddress INSTANCE = new RootCommandAddress();
+
+ public RootCommandAddress() {
+ }
+
+ @Override
+ public Command getCommand() {
+ return null;
+ }
+
+ @Override
+ public boolean isRoot() {
+ return true;
+ }
+
+ @Override
+ public List<String> getNames() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public ModifiableCommandAddress getParent() {
+ return null;
+ }
+
+ @Override
+ public String getMainKey() {
+ return null;
+ }
+
+ @Override
+ public String getAddress() {
+ return "";
+ }
+
+ @Override
+ public void registerToCommandMap(String fallbackPrefix, Map<String, org.bukkit.command.Command> map, EOverridePolicy overridePolicy) {
+ Objects.requireNonNull(overridePolicy);
+ //debugChildren(this);
+ Map<String, ChildCommandAddress> children = this.children;
+ Map<ChildCommandAddress, BukkitCommand> wrappers = new IdentityHashMap<>();
+
+ for (ChildCommandAddress address : children.values()) {
+ if (!wrappers.containsKey(address)) {
+ wrappers.put(address, new BukkitCommand(address));
+ }
+ }
+
+ for (Map.Entry<String, ChildCommandAddress> entry : children.entrySet()) {
+ String key = entry.getKey();
+ ChildCommandAddress address = entry.getValue();
+ boolean override = overridePolicy == EOverridePolicy.OVERRIDE_ALL;
+ if (!override && key.equals(address.getMainKey())) {
+ override = overridePolicy == EOverridePolicy.MAIN_KEY_ONLY || overridePolicy == EOverridePolicy.MAIN_AND_FALLBACK;
+ }
+
+ registerMember(map, key, wrappers.get(address), override);
+
+ if (fallbackPrefix != null) {
+ key = fallbackPrefix + key;
+ override = overridePolicy != EOverridePolicy.OVERRIDE_NONE && overridePolicy != EOverridePolicy.MAIN_KEY_ONLY;
+ registerMember(map, key, wrappers.get(address), override);
+ }
+ }
+
+ }
+
+ private static void debugChildren(ModifiableCommandAddress address) {
+ Collection<String> keys = address.getChildrenMainKeys();
+ for (String key : keys) {
+ ChildCommandAddress child = address.getChild(key);
+ System.out.println(child.getAddress());
+ debugChildren(child);
+ }
+ }
+
+ private static void registerMember(Map<String, org.bukkit.command.Command> map,
+ String key, org.bukkit.command.Command value, boolean override) {
+ if (override) {
+ map.put(key, value);
+ } else {
+ map.putIfAbsent(key, value);
+ }
+ }
+
+ @Override
+ public void unregisterFromCommandMap(Map<String, org.bukkit.command.Command> map) {
+ Set<ICommandAddress> children = new HashSet<>(this.children.values());
+ Iterator<Map.Entry<String, org.bukkit.command.Command>> iterator = map.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry<String, org.bukkit.command.Command> entry = iterator.next();
+ org.bukkit.command.Command cmd = entry.getValue();
+ if (cmd instanceof BukkitCommand && children.contains(((BukkitCommand) cmd).getOrigin())) {
+ iterator.remove();
+ }
+ }
+ }
+
+ @Override
+ public ModifiableCommandAddress getDeepChild(ArgumentBuffer buffer) {
+ ModifiableCommandAddress cur = this;
+ ChildCommandAddress child;
+ while (buffer.hasNext()) {
+ child = cur.getChild(buffer.next());
+ if (child == null) {
+ buffer.rewind();
+ return cur;
+ }
+
+ cur = child;
+ }
+ return cur;
+ }
+
+ @Override
+ public ModifiableCommandAddress getCommandTarget(CommandSender sender, ArgumentBuffer buffer) {
+ ModifiableCommandAddress cur = this;
+ ChildCommandAddress child;
+ while (buffer.hasNext()) {
+ child = cur.getChild(buffer.next());
+ if (child == null
+ || (child.hasCommand() && !child.getCommand().isVisibleTo(sender))
+ || (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) {
+ buffer.rewind();
+ break;
+ }
+
+ cur = child;
+ }
+
+ return cur;
+ }
+
+ @Override
+ public ModifiableCommandAddress getCommandTarget(ExecutionContext context, ArgumentBuffer buffer) throws CommandException {
+ CommandSender sender = context.getSender();
+ ModifiableCommandAddress cur = this;
+ ChildCommandAddress child;
+ while (buffer.hasNext()) {
+ int cursor = buffer.getCursor();
+
+ child = cur.getChild(context, buffer);
+
+ if (child == null
+ || (context.isTabComplete() && !buffer.hasNext())
+ || (child.hasCommand() && !child.getCommand().isVisibleTo(sender))
+ || (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) {
+ buffer.setCursor(cursor);
+ break;
+ }
+
+ cur = child;
+
+ context.setAddress(child);
+ if (child.hasCommand() && child.isCommandTrailing()) {
+ child.getCommand().initializeAndFilterContext(context);
+ child.getCommand().execute(context.getSender(), context);
+ }
+ }
+
+ return cur;
+ }
+
+ @Override
+ public boolean dispatchCommand(CommandSender sender, String[] command) {
+ return dispatchCommand(sender, new ArgumentBuffer(command));
+ }
+
+ @Override
+ public boolean dispatchCommand(CommandSender sender, String usedLabel, String[] args) {
+ return dispatchCommand(sender, new ArgumentBuffer(usedLabel, args));
+ }
+
+ @Override
+ public boolean dispatchCommand(CommandSender sender, ArgumentBuffer buffer) {
+ ExecutionContext context = new ExecutionContext(sender, buffer, false);
+
+ ModifiableCommandAddress targetAddress = null;
+
+ try {
+ targetAddress = getCommandTarget(context, buffer);
+ Command target = targetAddress.getCommand();
+
+ if (target == null) {
+ if (targetAddress.hasHelpCommand()) {
+ target = targetAddress.getHelpCommand().getCommand();
+ } else {
+ return false;
+ }
+ }
+
+ context.setCommand(target);
+
+ if (!targetAddress.isCommandTrailing()) {
+ target.initializeAndFilterContext(context);
+ String message = target.execute(sender, context);
+ if (message != null && !message.isEmpty()) {
+ context.sendMessage(EMessageType.RESULT, message);
+ }
+ }
+
+ } catch (Throwable t) {
+ if (targetAddress == null) {
+ targetAddress = this;
+ }
+ targetAddress.getChatHandler().handleException(sender, context, t);
+ }
+
+ return true;
+ }
+
+ @Override
+ public List<String> getTabCompletions(CommandSender sender, Location location, String[] args) {
+ return getTabCompletions(sender, location, new ArgumentBuffer(args));
+ }
+
+ @Override
+ public List<String> getTabCompletions(CommandSender sender, String usedLabel, Location location, String[] args) {
+ return getTabCompletions(sender, location, new ArgumentBuffer(usedLabel, args));
+ }
+
+ @Override
+ public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) {
+ ExecutionContext context = new ExecutionContext(sender, buffer, true);
+ long start = System.currentTimeMillis();
+
+ try {
+ ICommandAddress target = getCommandTarget(context, buffer);
+
+ List<String> out;
+ if (target.hasCommand()) {
+ context.setCommand(target.getCommand());
+ target.getCommand().initializeAndFilterContext(context);
+ out = target.getCommand().tabComplete(sender, context, location);
+ } else {
+ out = Collections.emptyList();
+ }
+
+ int cursor = buffer.getCursor();
+ String input;
+ if (cursor >= buffer.size()) {
+ input = "";
+ } else {
+ input = buffer.get(cursor).toLowerCase();
+ }
+
+ boolean wrapped = false;
+ for (String child : target.getChildrenMainKeys()) {
+ if (child.toLowerCase().startsWith(input)) {
+ if (!wrapped) {
+ out = new ArrayList<>(out);
+ wrapped = true;
+ }
+ out.add(child);
+ }
+ }
+
+ return out;
+
+ } catch (CommandException ex) {
+ return Collections.emptyList();
+ } finally {
+ long duration = System.currentTimeMillis() - start;
+ if (duration > 2) {
+ System.out.println(String.format("Complete took %.3f seconds", duration / 1000.0));
+ }
+ }
+
+ }
+}
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/parameter/ContextParser.java b/dicore3/command/src/main/java/io/dico/dicore/command/parameter/ContextParser.java
index f486f52..7418c4a 100644
--- a/dicore3/command/src/main/java/io/dico/dicore/command/parameter/ContextParser.java
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/parameter/ContextParser.java
@@ -1,276 +1,278 @@
-package io.dico.dicore.command.parameter;
-
-import io.dico.dicore.command.CommandException;
-import io.dico.dicore.command.ExecutionContext;
-
-import java.lang.reflect.Array;
-import java.util.*;
-
-public class ContextParser {
- private final ExecutionContext m_context;
- private final ArgumentBuffer m_buffer;
- private final ParameterList m_paramList;
- private final Parameter<?, ?> m_repeatedParam;
- private final List<Parameter<?, ?>> m_indexedParams;
- private final int m_maxIndex;
- private final int m_maxRequiredIndex;
-
- private Map<String, Object> m_valueMap;
- private Set<String> m_parsedKeys;
- private int m_completionCursor = -1;
- private Parameter<?, ?> m_completionTarget = null;
-
- public ContextParser(ExecutionContext context,
- ParameterList parameterList,
- Map<String, Object> valueMap,
- Set<String> keySet) {
- m_context = context;
- m_paramList = parameterList;
- m_valueMap = valueMap;
- m_parsedKeys = keySet;
-
- m_buffer = context.getBuffer();
- m_repeatedParam = m_paramList.getRepeatedParameter();
- m_indexedParams = m_paramList.getIndexedParameters();
- m_maxIndex = m_indexedParams.size() - 1;
- m_maxRequiredIndex = m_paramList.getRequiredCount() - 1;
- }
-
- public ExecutionContext getContext() {
- return m_context;
- }
-
- public Map<String, Object> getValueMap() {
- return m_valueMap;
- }
-
- public Set<String> getParsedKeys() {
- return m_parsedKeys;
- }
-
- public void parse() throws CommandException {
- parseAllParameters();
- }
-
- public int getCompletionCursor() {
- if (!m_done) {
- throw new IllegalStateException();
- }
- return m_completionCursor;
- }
-
- public Parameter<?, ?> getCompletionTarget() {
- if (!m_done) {
- throw new IllegalStateException();
- }
- return m_completionTarget;
- }
-
- // ################################
- // # PARSING METHODS #
- // ################################
-
- private boolean m_repeating = false;
- private boolean m_done = false;
- private int m_curParamIndex = -1;
- private Parameter<?, ?> m_curParam = null;
- private List<Object> m_curRepeatingList = null;
-
- private void parseAllParameters() throws CommandException {
- try {
- do {
- prepareStateToParseParam();
- if (m_done) break;
- parseCurParam();
- } while (!m_done);
-
- } finally {
- m_curParam = null;
- m_curRepeatingList = null;
- assignDefaultValuesToUncomputedParams();
- arrayifyRepeatedParamValue();
- }
- }
-
- private void prepareStateToParseParam() throws CommandException {
-
- boolean requireInput;
- if (identifyFlag()) {
- m_buffer.advance();
- prepareRepeatedParameterIfSet();
- requireInput = false;
-
- } else if (m_repeating) {
- m_curParam = m_repeatedParam;
- requireInput = false;
-
- } else if (m_curParamIndex < m_maxIndex) {
- m_curParamIndex++;
- m_curParam = m_indexedParams.get(m_curParamIndex);
- prepareRepeatedParameterIfSet();
- requireInput = m_curParamIndex <= m_maxRequiredIndex;
-
- } else if (m_buffer.hasNext()) {
- throw new CommandException("Too many arguments for /" + m_context.getAddress().getAddress());
-
- } else {
- m_done = true;
- return;
- }
-
- if (!m_buffer.hasNext()) {
- if (requireInput) {
- reportParameterRequired(m_curParam);
- }
-
- if (m_repeating) {
- m_done = true;
- }
- }
-
- }
-
- private boolean identifyFlag() {
- String potentialFlag = m_buffer.peekNext();
- Parameter<?, ?> target;
- if (potentialFlag != null
- && potentialFlag.startsWith("-")
- && (target = m_paramList.getParameterByName(potentialFlag)) != null
- && target.isFlag()
- && !m_valueMap.containsKey(potentialFlag)
-
-// Disabled because it's checked by {@link Parameter#parse(ExecutionContext, ArgumentBuffer)}
-// && (target.getFlagPermission() == null || m_context.getSender().hasPermission(target.getFlagPermission()))
- ) {
- m_curParam = target;
- return true;
- }
-
- return false;
- }
-
- private void prepareRepeatedParameterIfSet() throws CommandException {
- if (m_curParam != null && m_curParam == m_repeatedParam) {
-
- if (m_curParam.isFlag() && m_curParamIndex < m_maxRequiredIndex) {
- Parameter<?, ?> requiredParam = m_indexedParams.get(m_curParamIndex + 1);
- reportParameterRequired(requiredParam);
- }
-
- m_curRepeatingList = new ArrayList<>();
- assignValue(m_curRepeatingList);
- m_repeating = true;
- }
- }
-
- private void reportParameterRequired(Parameter<?, ?> param) throws CommandException {
- throw new CommandException("The argument '" + param.getName() + "' is required");
- }
-
- private void parseCurParam() throws CommandException {
- if (!m_buffer.hasNext() && !m_curParam.isFlag()) {
- assignDefaultValue();
- return;
- }
-
- int cursorStart = m_buffer.getCursor();
-
- if (m_context.isTabComplete() && "".equals(m_buffer.peekNext())) {
- assignAsCompletionTarget(cursorStart);
- return;
- }
-
- Object parseResult;
- try {
- parseResult = m_curParam.parse(m_context, m_buffer);
- } catch (CommandException e) {
- assignAsCompletionTarget(cursorStart);
- throw e;
- }
-
- assignValue(parseResult);
- m_parsedKeys.add(m_curParam.getName());
- }
-
- private void assignDefaultValue() throws CommandException {
- assignValue(m_curParam.getDefaultValue(m_context, m_buffer));
- }
-
- private void assignAsCompletionTarget(int cursor) {
- m_completionCursor = cursor;
- m_completionTarget = m_curParam;
- m_done = true;
- }
-
- private void assignValue(Object value) {
- if (m_repeating) {
- m_curRepeatingList.add(value);
- } else {
- m_valueMap.put(m_curParam.getName(), value);
- }
- }
-
- private void assignDefaultValuesToUncomputedParams() throws CommandException {
- // add default values for unset parameters
- for (Map.Entry<String, Parameter<?, ?>> entry : m_paramList.getParametersByName().entrySet()) {
- String name = entry.getKey();
- if (!m_valueMap.containsKey(name)) {
- if (m_repeatedParam == entry.getValue()) {
- // below value will be turned into an array later
- m_valueMap.put(name, Collections.emptyList());
- } else {
- m_valueMap.put(name, entry.getValue().getDefaultValue(m_context, m_buffer));
- }
- }
- }
- }
-
- private void arrayifyRepeatedParamValue() {
- if (m_repeatedParam != null) {
- m_valueMap.computeIfPresent(m_repeatedParam.getName(), (k, v) -> {
- List list = (List) v;
- Class<?> returnType = m_repeatedParam.getType().getReturnType();
- Object array = Array.newInstance(returnType, list.size());
- ArraySetter setter = ArraySetter.getSetter(returnType);
- for (int i = 0, n = list.size(); i < n; i++) {
- setter.set(array, i, list.get(i));
- }
-
- return array;
- });
- }
- }
-
- private interface ArraySetter {
- void set(Object array, int index, Object value);
-
- static ArraySetter getSetter(Class<?> clazz) {
- if (!clazz.isPrimitive()) {
- return (array, index, value) -> ((Object[]) array)[index] = value;
- }
-
- switch (clazz.getSimpleName()) {
- case "boolean":
- return (array, index, value) -> ((boolean[]) array)[index] = (boolean) value;
- case "int":
- return (array, index, value) -> ((int[]) array)[index] = (int) value;
- case "double":
- return (array, index, value) -> ((double[]) array)[index] = (double) value;
- case "long":
- return (array, index, value) -> ((long[]) array)[index] = (long) value;
- case "short":
- return (array, index, value) -> ((short[]) array)[index] = (short) value;
- case "byte":
- return (array, index, value) -> ((byte[]) array)[index] = (byte) value;
- case "float":
- return (array, index, value) -> ((float[]) array)[index] = (float) value;
- case "char":
- return (array, index, value) -> ((char[]) array)[index] = (char) value;
- case "void":
- default:
- throw new InternalError("This should not happen");
- }
- }
- }
-
-}
+package io.dico.dicore.command.parameter;
+
+import io.dico.dicore.command.CommandException;
+import io.dico.dicore.command.ExecutionContext;
+
+import java.lang.reflect.Array;
+import java.util.*;
+
+public class ContextParser {
+ private final ExecutionContext m_context;
+ private final ArgumentBuffer m_buffer;
+ private final ParameterList m_paramList;
+ private final Parameter<?, ?> m_repeatedParam;
+ private final List<Parameter<?, ?>> m_indexedParams;
+ private final int m_maxIndex;
+ private final int m_maxRequiredIndex;
+
+ private Map<String, Object> m_valueMap;
+ private Set<String> m_parsedKeys;
+ private int m_completionCursor = -1;
+ private Parameter<?, ?> m_completionTarget = null;
+
+ public ContextParser(ExecutionContext context,
+ ParameterList parameterList,
+ Map<String, Object> valueMap,
+ Set<String> keySet) {
+ m_context = context;
+ m_paramList = parameterList;
+ m_valueMap = valueMap;
+ m_parsedKeys = keySet;
+
+ m_buffer = context.getBuffer();
+ m_repeatedParam = m_paramList.getRepeatedParameter();
+ m_indexedParams = m_paramList.getIndexedParameters();
+ m_maxIndex = m_indexedParams.size() - 1;
+ m_maxRequiredIndex = m_paramList.getRequiredCount() - 1;
+ }
+
+ public ExecutionContext getContext() {
+ return m_context;
+ }
+
+ public Map<String, Object> getValueMap() {
+ return m_valueMap;
+ }
+
+ public Set<String> getParsedKeys() {
+ return m_parsedKeys;
+ }
+
+ public void parse() throws CommandException {
+ parseAllParameters();
+ }
+
+ public int getCompletionCursor() {
+ if (!m_done) {
+ throw new IllegalStateException();
+ }
+ return m_completionCursor;
+ }
+
+ public Parameter<?, ?> getCompletionTarget() {
+ if (!m_done) {
+ throw new IllegalStateException();
+ }
+ return m_completionTarget;
+ }
+
+ // ################################
+ // # PARSING METHODS #
+ // ################################
+
+ private boolean m_repeating = false;
+ private boolean m_done = false;
+ private int m_curParamIndex = -1;
+ private Parameter<?, ?> m_curParam = null;
+ private List<Object> m_curRepeatingList = null;
+
+ private void parseAllParameters() throws CommandException {
+ try {
+ do {
+ prepareStateToParseParam();
+ if (m_done) break;
+ parseCurParam();
+ } while (!m_done);
+
+ } finally {
+ m_curParam = null;
+ m_curRepeatingList = null;
+ assignDefaultValuesToUncomputedParams();
+ arrayifyRepeatedParamValue();
+
+ m_done = true;
+ }
+ }
+
+ private void prepareStateToParseParam() throws CommandException {
+
+ boolean requireInput;
+ if (identifyFlag()) {
+ m_buffer.advance();
+ prepareRepeatedParameterIfSet();
+ requireInput = false;
+
+ } else if (m_repeating) {
+ m_curParam = m_repeatedParam;
+ requireInput = false;
+
+ } else if (m_curParamIndex < m_maxIndex) {
+ m_curParamIndex++;
+ m_curParam = m_indexedParams.get(m_curParamIndex);
+ prepareRepeatedParameterIfSet();
+ requireInput = m_curParamIndex <= m_maxRequiredIndex;
+
+ } else if (m_buffer.hasNext()) {
+ throw new CommandException("Too many arguments for /" + m_context.getAddress().getAddress());
+
+ } else {
+ m_done = true;
+ return;
+ }
+
+ if (!m_buffer.hasNext()) {
+ if (requireInput) {
+ reportParameterRequired(m_curParam);
+ }
+
+ if (m_repeating) {
+ m_done = true;
+ }
+ }
+
+ }
+
+ private boolean identifyFlag() {
+ String potentialFlag = m_buffer.peekNext();
+ Parameter<?, ?> target;
+ if (potentialFlag != null
+ && potentialFlag.startsWith("-")
+ && (target = m_paramList.getParameterByName(potentialFlag)) != null
+ && target.isFlag()
+ && !m_valueMap.containsKey(potentialFlag)
+
+// Disabled because it's checked by {@link Parameter#parse(ExecutionContext, ArgumentBuffer)}
+// && (target.getFlagPermission() == null || m_context.getSender().hasPermission(target.getFlagPermission()))
+ ) {
+ m_curParam = target;
+ return true;
+ }
+
+ return false;
+ }
+
+ private void prepareRepeatedParameterIfSet() throws CommandException {
+ if (m_curParam != null && m_curParam == m_repeatedParam) {
+
+ if (m_curParam.isFlag() && m_curParamIndex < m_maxRequiredIndex) {
+ Parameter<?, ?> requiredParam = m_indexedParams.get(m_curParamIndex + 1);
+ reportParameterRequired(requiredParam);
+ }
+
+ m_curRepeatingList = new ArrayList<>();
+ assignValue(m_curRepeatingList);
+ m_repeating = true;
+ }
+ }
+
+ private void reportParameterRequired(Parameter<?, ?> param) throws CommandException {
+ throw new CommandException("The argument '" + param.getName() + "' is required");
+ }
+
+ private void parseCurParam() throws CommandException {
+ if (!m_buffer.hasNext() && !m_curParam.isFlag()) {
+ assignDefaultValue();
+ return;
+ }
+
+ int cursorStart = m_buffer.getCursor();
+
+ if (m_context.isTabComplete() && "".equals(m_buffer.peekNext())) {
+ assignAsCompletionTarget(cursorStart);
+ return;
+ }
+
+ Object parseResult;
+ try {
+ parseResult = m_curParam.parse(m_context, m_buffer);
+ } catch (CommandException e) {
+ assignAsCompletionTarget(cursorStart);
+ throw e;
+ }
+
+ assignValue(parseResult);
+ m_parsedKeys.add(m_curParam.getName());
+ }
+
+ private void assignDefaultValue() throws CommandException {
+ assignValue(m_curParam.getDefaultValue(m_context, m_buffer));
+ }
+
+ private void assignAsCompletionTarget(int cursor) {
+ m_completionCursor = cursor;
+ m_completionTarget = m_curParam;
+ m_done = true;
+ }
+
+ private void assignValue(Object value) {
+ if (m_repeating) {
+ m_curRepeatingList.add(value);
+ } else {
+ m_valueMap.put(m_curParam.getName(), value);
+ }
+ }
+
+ private void assignDefaultValuesToUncomputedParams() throws CommandException {
+ // add default values for unset parameters
+ for (Map.Entry<String, Parameter<?, ?>> entry : m_paramList.getParametersByName().entrySet()) {
+ String name = entry.getKey();
+ if (!m_valueMap.containsKey(name)) {
+ if (m_repeatedParam == entry.getValue()) {
+ // below value will be turned into an array later
+ m_valueMap.put(name, Collections.emptyList());
+ } else {
+ m_valueMap.put(name, entry.getValue().getDefaultValue(m_context, m_buffer));
+ }
+ }
+ }
+ }
+
+ private void arrayifyRepeatedParamValue() {
+ if (m_repeatedParam != null) {
+ m_valueMap.computeIfPresent(m_repeatedParam.getName(), (k, v) -> {
+ List list = (List) v;
+ Class<?> returnType = m_repeatedParam.getType().getReturnType();
+ Object array = Array.newInstance(returnType, list.size());
+ ArraySetter setter = ArraySetter.getSetter(returnType);
+ for (int i = 0, n = list.size(); i < n; i++) {
+ setter.set(array, i, list.get(i));
+ }
+
+ return array;
+ });
+ }
+ }
+
+ private interface ArraySetter {
+ void set(Object array, int index, Object value);
+
+ static ArraySetter getSetter(Class<?> clazz) {
+ if (!clazz.isPrimitive()) {
+ return (array, index, value) -> ((Object[]) array)[index] = value;
+ }
+
+ switch (clazz.getSimpleName()) {
+ case "boolean":
+ return (array, index, value) -> ((boolean[]) array)[index] = (boolean) value;
+ case "int":
+ return (array, index, value) -> ((int[]) array)[index] = (int) value;
+ case "double":
+ return (array, index, value) -> ((double[]) array)[index] = (double) value;
+ case "long":
+ return (array, index, value) -> ((long[]) array)[index] = (long) value;
+ case "short":
+ return (array, index, value) -> ((short[]) array)[index] = (short) value;
+ case "byte":
+ return (array, index, value) -> ((byte[]) array)[index] = (byte) value;
+ case "float":
+ return (array, index, value) -> ((float[]) array)[index] = (float) value;
+ case "char":
+ return (array, index, value) -> ((char[]) array)[index] = (char) value;
+ case "void":
+ default:
+ throw new InternalError("This should not happen");
+ }
+ }
+ }
+
+}
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCallFlags.java b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCallFlags.java
new file mode 100644
index 0000000..fa198c2
--- /dev/null
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCallFlags.java
@@ -0,0 +1,186 @@
+package io.dico.dicore.command.registration.reflect;
+
+import io.dico.dicore.command.CommandException;
+import io.dico.dicore.command.ExecutionContext;
+import io.dico.dicore.exceptions.checkedfunctions.CheckedSupplier;
+
+/**
+ * Call flags store which extra parameters the target function expects on top of command parameters.
+ * All 4 possible extra parameters are listed below.
+ * <p>
+ * Extra parameters are ordered by the bit that represents them in the call flags.
+ * They can either be leading or trailing the command's parameters.
+ */
+public class ReflectiveCallFlags {
+
+ /**
+ * Receiver ({@code this} in some kotlin functions - always first parameter)
+ *
+ * @see ICommandInterceptor#getReceiver(io.dico.dicore.command.ExecutionContext, java.lang.reflect.Method, String)
+ */
+ public static final int RECEIVER_BIT = 1 << 0;
+
+ /**
+ * CommandSender
+ *
+ * @see org.bukkit.command.CommandSender
+ */
+ public static final int SENDER_BIT = 1 << 1;
+
+ /**
+ * ExecutionContext
+ *
+ * @see io.dico.dicore.command.ExecutionContext
+ */
+ public static final int CONTEXT_BIT = 1 << 2;
+
+ /**
+ * Continuation (trailing parameters of kotlin suspended functions)
+ *
+ * @see kotlin.coroutines.Continuation
+ */
+ public static final int CONTINUATION_BIT = 1 << 3;
+
+ /**
+ * Mask of extra parameters that trail the command's parameters, instead of leading.
+ */
+ public static final int TRAILING_MASK = CONTINUATION_BIT;
+
+ /**
+ * Check if the call arg is trailing the command's parameters.
+ *
+ * @param bit the bit used for the call flag
+ * @return true if the call arg is trailing the command's parameters
+ */
+ public static boolean isTrailingCallArg(int bit) {
+ return (bit & TRAILING_MASK) != 0;
+ }
+
+ /**
+ * Number of call arguments leading the command parameters.
+ *
+ * @param flags the call flags
+ * @return the number of call arguments leading the command parameters
+ */
+ public static int getLeadingCallArgNum(int flags) {
+ return Integer.bitCount(flags & ~TRAILING_MASK);
+ }
+
+ /**
+ * Number of call arguments trailing the command parameters.
+ *
+ * @param flags the call flags
+ * @return the number of call arguments trailing the command parameters
+ */
+ public static int getTrailingCallArgNum(int flags) {
+ return Integer.bitCount(flags & TRAILING_MASK);
+ }
+
+ /**
+ * Check if the flags contain the call arg.
+ *
+ * @param flags the call flags
+ * @param bit the bit used for the call flag
+ * @return true if the flags contain the call arg
+ */
+ public static boolean hasCallArg(int flags, int bit) {
+ return (flags & bit) != 0;
+ }
+
+ /**
+ * Get the index used for the call arg when calling the reflective function
+ *
+ * @param flags the call flags
+ * @param bit the bit used for the call flag
+ * @param cmdParameterNum the number of parameters of the command
+ * @return the index used for the call arg
+ */
+ public static int getCallArgIndex(int flags, int bit, int cmdParameterNum) {
+ if ((bit & TRAILING_MASK) == 0) {
+ // Leading.
+
+ int preceding = precedingMaskFrom(bit);
+ int mask = flags & precedingMaskFrom(bit) & ~TRAILING_MASK;
+
+ // Count the number of present call args that are leading and precede the given bit
+ return Integer.bitCount(flags & precedingMaskFrom(bit) & ~TRAILING_MASK);
+ } else {
+ // Trailing.
+
+ // Count the number of present call args that are leading
+ // plus the number of present call args that are trailing and precede the given bit
+ // plus the command's parameters
+
+ return Integer.bitCount(flags & ~TRAILING_MASK)
+ + Integer.bitCount(flags & precedingMaskFrom(bit) & TRAILING_MASK)
+ + cmdParameterNum;
+ }
+ }
+
+ /**
+ * Get the mask for all bits trailing the given fromBit
+ *
+ * <p>
+ * For example, if the bit is 00010000
+ * This function returns 00001111
+ * <p>
+ *
+ * @param fromBit number with the bit set there the ones should stop.
+ * @return the mask for all bits trailing the given fromBit
+ */
+ private static int precedingMaskFrom(int fromBit) {
+ int trailingZeros = Integer.numberOfTrailingZeros(fromBit);
+ if (trailingZeros == 0) return 0;
+ return -1 >>> -trailingZeros;
+ }
+
+ /**
+ * Get the object array used to call the function.
+ *
+ * @param callFlags the call flags
+ * @param context the context
+ * @param parameterOrder the order of parameters in the function
+ * @param receiverFunction the function that will create the receiver for this call, if applicable
+ * @return the call args
+ */
+ public static Object[] getCallArgs(
+ int callFlags,
+ ExecutionContext context,
+ String[] parameterOrder,
+ CheckedSupplier<Object, CommandException> receiverFunction
+ ) throws CommandException {
+ int leadingParameterNum = getLeadingCallArgNum(callFlags);
+ int cmdParameterNum = parameterOrder.length;
+ int trailingParameterNum = getTrailingCallArgNum(callFlags);
+
+ Object[] result = new Object[leadingParameterNum + cmdParameterNum + trailingParameterNum];
+
+ if (hasCallArg(callFlags, RECEIVER_BIT)) {
+ int index = getCallArgIndex(callFlags, RECEIVER_BIT, cmdParameterNum);
+ result[index] = receiverFunction.get();
+ }
+
+ if (hasCallArg(callFlags, SENDER_BIT)) {
+ int index = getCallArgIndex(callFlags, SENDER_BIT, cmdParameterNum);
+ result[index] = context.getSender();
+ }
+
+ if (hasCallArg(callFlags, CONTEXT_BIT)) {
+ int index = getCallArgIndex(callFlags, CONTEXT_BIT, cmdParameterNum);
+ result[index] = context;
+ }
+
+ if (hasCallArg(callFlags, CONTINUATION_BIT)) {
+ int index = getCallArgIndex(callFlags, CONTINUATION_BIT, cmdParameterNum);
+ result[index] = null; // filled in later.
+ }
+
+ for (int i = 0; i < parameterOrder.length; i++) {
+ String parameterName = parameterOrder[i];
+ result[leadingParameterNum + i] = context.get(parameterName);
+ }
+
+ return result;
+ }
+
+}
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java
index 34ea8de..f4ddc70 100644
--- a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java
@@ -1,187 +1,169 @@
-package io.dico.dicore.command.registration.reflect;
-
-import io.dico.dicore.command.*;
-import io.dico.dicore.command.annotation.Cmd;
-import io.dico.dicore.command.annotation.GenerateCommands;
-import io.dico.dicore.command.parameter.type.IParameterTypeSelector;
-import org.bukkit.command.CommandSender;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-
-public final class ReflectiveCommand extends Command {
- private static final int continuationMask = 1 << 3;
- private final Cmd cmdAnnotation;
- private final Method method;
- private final Object instance;
- private String[] parameterOrder;
-
- // hasContinuation | hasContext | hasSender | hasReceiver
- private final int flags;
-
- ReflectiveCommand(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
- if (!method.isAnnotationPresent(Cmd.class)) {
- throw new CommandParseException("No @Cmd present for the method " + method.toGenericString());
- }
- cmdAnnotation = method.getAnnotation(Cmd.class);
-
- java.lang.reflect.Parameter[] parameters = method.getParameters();
-
- if (!method.isAccessible()) try {
- method.setAccessible(true);
- } catch (Exception ex) {
- throw new CommandParseException("Failed to make method accessible");
- }
-
- if (!Modifier.isStatic(method.getModifiers())) {
- if (instance == null) {
- try {
- instance = method.getDeclaringClass().newInstance();
- } catch (Exception ex) {
- throw new CommandParseException("No instance given for instance method, and failed to create new instance", ex);
- }
- } else if (!method.getDeclaringClass().isInstance(instance)) {
- throw new CommandParseException("Given instance is not an instance of the method's declaring class");
- }
- }
-
- this.method = method;
- this.instance = instance;
- this.flags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
- }
-
- public Method getMethod() {
- return method;
- }
-
- public Object getInstance() {
- return instance;
- }
-
- public String getCmdName() { return cmdAnnotation.value(); }
-
- void setParameterOrder(String[] parameterOrder) {
- this.parameterOrder = parameterOrder;
- }
-
- ICommandAddress getAddress() {
- ChildCommandAddress result = new ChildCommandAddress();
- result.setCommand(this);
-
- Cmd cmd = cmdAnnotation;
- result.getNames().add(cmd.value());
- for (String alias : cmd.aliases()) {
- result.getNames().add(alias);
- }
- result.finalizeNames();
-
- GenerateCommands generateCommands = method.getAnnotation(GenerateCommands.class);
- if (generateCommands != null) {
- ReflectiveRegistration.generateCommands(result, generateCommands.value());
- }
-
- return result;
- }
-
- @Override
- public String execute(CommandSender sender, ExecutionContext context) throws CommandException {
- String[] parameterOrder = this.parameterOrder;
- int extraArgumentCount = Integer.bitCount(flags);
- int parameterStartIndex = Integer.bitCount(flags & ~continuationMask);
-
- Object[] args = new Object[parameterOrder.length + extraArgumentCount];
-
- int i = 0;
-
- int mask = 1;
- if ((flags & mask) != 0) {
- // Has receiver
- try {
- args[i++] = ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName());
- } catch (Exception ex) {
- handleException(ex);
- return null; // unreachable
- }
- }
-
- mask <<= 1;
- if ((flags & mask) != 0) {
- // Has sender
- args[i++] = sender;
- }
-
- mask <<= 1;
- if ((flags & mask) != 0) {
- // Has context
- args[i++] = context;
- }
-
- mask <<= 1;
- if ((flags & mask) != 0) {
- // Has continuation
-
- extraArgumentCount--;
- }
-
- for (int n = args.length; i < n; i++) {
- args[i] = context.get(parameterOrder[i - extraArgumentCount]);
- }
-
- if ((flags & mask) != 0) {
- // Since it has continuation, call as coroutine
- return callAsCoroutine(context, args);
- }
-
- return callSynchronously(args);
- }
-
- private boolean isSuspendFunction() {
- try {
- return KotlinReflectiveRegistrationKt.isSuspendFunction(method);
- } catch (Throwable ex) {
- return false;
- }
- }
-
- public String callSynchronously(Object[] args) throws CommandException {
- try {
- return getResult(method.invoke(instance, args), null);
- } catch (Exception ex) {
- return getResult(null, ex);
- }
- }
-
- public static String getResult(Object returned, Exception ex) throws CommandException {
- if (ex != null) {
- handleException(ex);
- return null; // unreachable
- }
-
- if (returned instanceof String) {
- return (String) returned;
- }
- return null;
- }
-
- public static void handleException(Exception ex) throws CommandException {
- if (ex instanceof InvocationTargetException) {
- if (ex.getCause() instanceof CommandException) {
- throw (CommandException) ex.getCause();
- }
-
- ex.printStackTrace();
- throw new CommandException("An internal error occurred while executing this command.", ex);
- }
- if (ex instanceof CommandException) {
- throw (CommandException) ex;
- }
- ex.printStackTrace();
- throw new CommandException("An internal error occurred while executing this command.", ex);
- }
-
- private String callAsCoroutine(ExecutionContext context, Object[] args) {
- return KotlinReflectiveRegistrationKt.callAsCoroutine(this, (ICommandInterceptor) instance, context, args);
- }
-
-}
+package io.dico.dicore.command.registration.reflect;
+
+import io.dico.dicore.command.*;
+import io.dico.dicore.command.annotation.Cmd;
+import io.dico.dicore.command.annotation.GenerateCommands;
+import io.dico.dicore.command.parameter.type.IParameterTypeSelector;
+import io.dico.dicore.exceptions.checkedfunctions.CheckedSupplier;
+import kotlin.coroutines.CoroutineContext;
+import org.bukkit.command.CommandSender;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+public final class ReflectiveCommand extends Command {
+ private final Cmd cmdAnnotation;
+ private final Method method;
+ private final Object instance;
+ private String[] parameterOrder;
+ private final int callFlags;
+
+ ReflectiveCommand(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
+ if (!method.isAnnotationPresent(Cmd.class)) {
+ throw new CommandParseException("No @Cmd present for the method " + method.toGenericString());
+ }
+ cmdAnnotation = method.getAnnotation(Cmd.class);
+
+ java.lang.reflect.Parameter[] parameters = method.getParameters();
+
+ if (!method.isAccessible()) try {
+ method.setAccessible(true);
+ } catch (Exception ex) {
+ throw new CommandParseException("Failed to make method accessible");
+ }
+
+ if (!Modifier.isStatic(method.getModifiers())) {
+ if (instance == null) {
+ try {
+ instance = method.getDeclaringClass().newInstance();
+ } catch (Exception ex) {
+ throw new CommandParseException("No instance given for instance method, and failed to create new instance", ex);
+ }
+ } else if (!method.getDeclaringClass().isInstance(instance)) {
+ throw new CommandParseException("Given instance is not an instance of the method's declaring class");
+ }
+ }
+
+ this.method = method;
+ this.instance = instance;
+ this.callFlags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+
+ public Object getInstance() {
+ return instance;
+ }
+
+ public String getCmdName() {
+ return cmdAnnotation.value();
+ }
+
+ public int getCallFlags() {
+ return callFlags;
+ }
+
+ void setParameterOrder(String[] parameterOrder) {
+ this.parameterOrder = parameterOrder;
+ }
+
+ public int getParameterNum() {
+ return parameterOrder.length;
+ }
+
+ ICommandAddress getAddress() {
+ ChildCommandAddress result = new ChildCommandAddress();
+ result.setCommand(this);
+
+ Cmd cmd = cmdAnnotation;
+ result.getNames().add(cmd.value());
+ for (String alias : cmd.aliases()) {
+ result.getNames().add(alias);
+ }
+ result.finalizeNames();
+
+ GenerateCommands generateCommands = method.getAnnotation(GenerateCommands.class);
+ if (generateCommands != null) {
+ ReflectiveRegistration.generateCommands(result, generateCommands.value());
+ }
+
+ return result;
+ }
+
+ @Override
+ public String execute(CommandSender sender, ExecutionContext context) throws CommandException {
+
+ CheckedSupplier<Object, CommandException> receiverFunction = () -> {
+ try {
+ return ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName());
+ } catch (Exception ex) {
+ handleException(ex);
+ return null; // unreachable
+ }
+ };
+
+ Object[] callArgs = ReflectiveCallFlags.getCallArgs(callFlags, context, parameterOrder, receiverFunction);
+
+ if (ReflectiveCallFlags.hasCallArg(callFlags, ReflectiveCallFlags.CONTINUATION_BIT)) {
+ // If it has a continuation, call as coroutine
+ return callAsCoroutine(context, callArgs);
+ }
+
+ return callSynchronously(callArgs);
+ }
+
+ private boolean isSuspendFunction() {
+ try {
+ return KotlinReflectiveRegistrationKt.isSuspendFunction(method);
+ } catch (Throwable ex) {
+ return false;
+ }
+ }
+
+ public String callSynchronously(Object[] args) throws CommandException {
+ try {
+ return getResult(method.invoke(instance, args), null);
+ } catch (Exception ex) {
+ return getResult(null, ex);
+ }
+ }
+
+ public static String getResult(Object returned, Exception ex) throws CommandException {
+ if (ex != null) {
+ handleException(ex);
+ return null; // unreachable
+ }
+
+ if (returned instanceof String) {
+ return (String) returned;
+ }
+ return null;
+ }
+
+ public static void handleException(Exception ex) throws CommandException {
+ if (ex instanceof InvocationTargetException) {
+ if (ex.getCause() instanceof CommandException) {
+ throw (CommandException) ex.getCause();
+ }
+
+ ex.printStackTrace();
+ throw new CommandException("An internal error occurred while executing this command.", ex);
+ }
+ if (ex instanceof CommandException) {
+ throw (CommandException) ex;
+ }
+ ex.printStackTrace();
+ throw new CommandException("An internal error occurred while executing this command.", ex);
+ }
+
+ private String callAsCoroutine(ExecutionContext executionContext, Object[] args) throws CommandException {
+ ICommandInterceptor factory = (ICommandInterceptor) instance;
+ CoroutineContext coroutineContext = (CoroutineContext) factory.getCoroutineContext(executionContext, method, getCmdName());
+ int continuationIndex = ReflectiveCallFlags.getCallArgIndex(callFlags, ReflectiveCallFlags.CONTINUATION_BIT, parameterOrder.length);
+ return KotlinReflectiveRegistrationKt.callCommandAsCoroutine(executionContext, coroutineContext, continuationIndex, method, instance, args);
+ }
+
+}
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java
index 93ac0ee..ddd5420 100644
--- a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java
@@ -1,415 +1,406 @@
-package io.dico.dicore.command.registration.reflect;
-
-import io.dico.dicore.command.*;
-import io.dico.dicore.command.annotation.*;
-import io.dico.dicore.command.annotation.GroupMatchedCommands.GroupEntry;
-import io.dico.dicore.command.parameter.Parameter;
-import io.dico.dicore.command.parameter.ParameterList;
-import io.dico.dicore.command.parameter.type.IParameterTypeSelector;
-import io.dico.dicore.command.parameter.type.MapBasedParameterTypeSelector;
-import io.dico.dicore.command.parameter.type.ParameterType;
-import io.dico.dicore.command.parameter.type.ParameterTypes;
-import io.dico.dicore.command.predef.PredefinedCommand;
-import org.bukkit.command.CommandSender;
-import org.bukkit.command.ConsoleCommandSender;
-import org.bukkit.entity.Player;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.*;
-import java.util.function.Consumer;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-/**
- * Takes care of turning a reflection {@link Method} into a command and more.
- */
-public class ReflectiveRegistration {
- /**
- * This object provides names of the parameters.
- * Oddly, the AnnotationParanamer extensions require a 'fallback' paranamer to function properly without
- * requiring ALL parameters to have that flag. This is weird because it should just use the AdaptiveParanamer on an upper level to
- * determine the name of each individual flag. Oddly this isn't how it works, so the fallback works the same way as the AdaptiveParanamer does.
- * It's just linked instead of using an array for that part. Then we can use an AdaptiveParanamer for the latest fallback, to get bytecode names
- * or, finally, to get the Jvm-provided parameter names.
- */
- //private static final Paranamer paranamer = new CachingParanamer(new BytecodeReadingParanamer());
- @SuppressWarnings("StatementWithEmptyBody")
- private static String[] lookupParameterNames(Method method, java.lang.reflect.Parameter[] parameters, int start) {
- int n = parameters.length;
- String[] out = new String[n - start];
-
- //String[] bytecode;
- //try {
- // bytecode = paranamer.lookupParameterNames(method, false);
- //} catch (Exception ex) {
- // bytecode = new String[0];
- // System.err.println("ReflectiveRegistration.lookupParameterNames failed to read bytecode");
- // //ex.printStackTrace();
- //}
- //int bn = bytecode.length;
-
- for (int i = start; i < n; i++) {
- java.lang.reflect.Parameter parameter = parameters[i];
- Flag flag = parameter.getAnnotation(Flag.class);
- NamedArg namedArg = parameter.getAnnotation(NamedArg.class);
-
- boolean isFlag = flag != null;
- String name;
- if (namedArg != null && !(name = namedArg.value()).isEmpty()) {
- } else if (isFlag && !(name = flag.value()).isEmpty()) {
- //} else if (i < bn && (name = bytecode[i]) != null && !name.isEmpty()) {
- } else {
- name = parameter.getName();
- }
-
- if (isFlag) {
- name = '-' + name;
- } else {
- int idx = 0;
- while (name.startsWith("-", idx)) {
- idx++;
- }
- name = name.substring(idx);
- }
-
- out[i - start] = name;
- }
-
- return out;
- }
-
- public static void parseCommandGroup(ICommandAddress address, Class<?> clazz, Object instance) throws CommandParseException {
- parseCommandGroup(address, ParameterTypes.getSelector(), clazz, instance);
- }
-
- public static void parseCommandGroup(ICommandAddress address, IParameterTypeSelector selector, Class<?> clazz, Object instance) throws CommandParseException {
- boolean requireStatic = instance == null;
- if (!requireStatic && !clazz.isInstance(instance)) {
- throw new CommandParseException();
- }
-
- List<Method> methods = new LinkedList<>(Arrays.asList(clazz.getDeclaredMethods()));
-
- Iterator<Method> it = methods.iterator();
- for (Method method; it.hasNext(); ) {
- method = it.next();
-
- if (requireStatic && !Modifier.isStatic(method.getModifiers())) {
- it.remove();
- continue;
- }
-
- if (method.isAnnotationPresent(CmdParamType.class)) {
- it.remove();
-
- if (method.getReturnType() != ParameterType.class || method.getParameterCount() != 0) {
- throw new CommandParseException("Invalid CmdParamType method: must return ParameterType and take no arguments");
- }
-
- ParameterType<?, ?> type;
- try {
- Object inst = Modifier.isStatic(method.getModifiers()) ? null : instance;
- type = (ParameterType<?, ?>) method.invoke(inst);
- Objects.requireNonNull(type, "ParameterType returned is null");
- } catch (Exception ex) {
- throw new CommandParseException("Error occurred whilst getting ParameterType from CmdParamType method '" + method.toGenericString() + "'", ex);
- }
-
- if (selector == ParameterTypes.getSelector()) {
- selector = new MapBasedParameterTypeSelector(true);
- }
-
- selector.addType(method.getAnnotation(CmdParamType.class).infolessAlias(), type);
- }
- }
-
- GroupMatcherCache groupMatcherCache = new GroupMatcherCache(clazz, address);
- for (Method method : methods) {
- if (method.isAnnotationPresent(Cmd.class)) {
- ICommandAddress parsed = parseCommandMethod(selector, method, instance);
- groupMatcherCache.getGroupFor(method).addChild(parsed);
- }
- }
-
- }
-
- private static final class GroupMatcherCache {
- private ModifiableCommandAddress groupRootAddress;
- private GroupEntry[] matchEntries;
- private Pattern[] patterns;
- private ModifiableCommandAddress[] addresses;
-
- GroupMatcherCache(Class<?> clazz, ICommandAddress groupRootAddress) throws CommandParseException {
- this.groupRootAddress = (ModifiableCommandAddress) groupRootAddress;
-
- GroupMatchedCommands groupMatchedCommands = clazz.getAnnotation(GroupMatchedCommands.class);
- GroupEntry[] matchEntries = groupMatchedCommands == null ? new GroupEntry[0] : groupMatchedCommands.value();
-
- Pattern[] patterns = new Pattern[matchEntries.length];
- for (int i = 0; i < matchEntries.length; i++) {
- GroupEntry matchEntry = matchEntries[i];
- if (matchEntry.group().isEmpty() || matchEntry.regex().isEmpty()) {
- throw new CommandParseException("Empty group or regex in GroupMatchedCommands entry");
- }
- try {
- patterns[i] = Pattern.compile(matchEntry.regex());
- } catch (PatternSyntaxException ex) {
- throw new CommandParseException(ex);
- }
- }
-
- this.matchEntries = matchEntries;
- this.patterns = patterns;
- this.addresses = new ModifiableCommandAddress[this.matchEntries.length];
- }
-
- ModifiableCommandAddress getGroupFor(Method method) {
- String name = method.getName();
-
- GroupEntry[] matchEntries = this.matchEntries;
- Pattern[] patterns = this.patterns;
- ModifiableCommandAddress[] addresses = this.addresses;
-
- for (int i = 0; i < matchEntries.length; i++) {
- GroupEntry matchEntry = matchEntries[i];
- if (patterns[i].matcher(name).matches()) {
- if (addresses[i] == null) {
- ChildCommandAddress placeholder = new ChildCommandAddress();
- placeholder.setupAsPlaceholder(matchEntry.group(), matchEntry.groupAliases());
- addresses[i] = placeholder;
- groupRootAddress.addChild(placeholder);
- generateCommands(placeholder, matchEntry.generatedCommands());
- setDescription(placeholder, matchEntry.description(), matchEntry.shortDescription());
- }
- return addresses[i];
- }
- }
-
- return groupRootAddress;
- }
-
- }
-
- public static ICommandAddress parseCommandMethod(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
- return new ReflectiveCommand(selector, method, instance).getAddress();
- }
-
- static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] parameters) throws CommandParseException {
- ParameterList list = command.getParameterList();
-
- boolean hasReceiverParameter = false;
- boolean hasSenderParameter = false;
- boolean hasContextParameter = false;
- boolean hasContinuationParameter = false;
-
- int start = 0;
- int end = parameters.length;
-
- Class<?> senderParameterType = null;
-
- if (parameters.length > start
- && command.getInstance() instanceof ICommandInterceptor
- && ICommandReceiver.class.isAssignableFrom(parameters[start].getType())) {
- hasReceiverParameter = true;
- start++;
- }
-
- if (parameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = parameters[start].getType())) {
- hasSenderParameter = true;
- start++;
- }
-
- if (parameters.length > start && parameters[start].getType() == ExecutionContext.class) {
- hasContextParameter = true;
- start++;
- }
-
- if (parameters.length > start && parameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) {
- hasContinuationParameter = true;
- end--;
- }
-
- String[] parameterNames = lookupParameterNames(method, parameters, start);
- for (int i = start, n = end; i < n; i++) {
- Parameter<?, ?> parameter = parseParameter(selector, method, parameters[i], parameterNames[i - start]);
- list.addParameter(parameter);
- }
- command.setParameterOrder(hasContinuationParameter ? Arrays.copyOfRange(parameterNames, 0, parameterNames.length - 1) : parameterNames);
-
- RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class);
- if (cmdPermissions != null) {
- for (String permission : cmdPermissions.value()) {
- command.addContextFilter(IContextFilter.permission(permission));
- }
-
- if (cmdPermissions.inherit()) {
- command.addContextFilter(IContextFilter.INHERIT_PERMISSIONS);
- }
- } else {
- command.addContextFilter(IContextFilter.INHERIT_PERMISSIONS);
- }
-
- RequireParameters reqPar = method.getAnnotation(RequireParameters.class);
- if (reqPar != null) {
- list.setRequiredCount(reqPar.value() < 0 ? Integer.MAX_VALUE : reqPar.value());
- } else {
- list.setRequiredCount(list.getIndexedParameters().size());
- }
-
- /*
- PreprocessArgs preprocessArgs = method.getAnnotation(PreprocessArgs.class);
- if (preprocessArgs != null) {
- IArgumentPreProcessor preProcessor = IArgumentPreProcessor.mergeOnTokens(preprocessArgs.tokens(), preprocessArgs.escapeChar());
- list.setArgumentPreProcessor(preProcessor);
- }*/
-
- Desc desc = method.getAnnotation(Desc.class);
- if (desc != null) {
- String[] array = desc.value();
- if (array.length == 0) {
- command.setDescription(desc.shortVersion());
- } else {
- command.setDescription(array);
- }
- } else {
- command.setDescription();
- }
-
- if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) {
- command.addContextFilter(IContextFilter.PLAYER_ONLY);
- } else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) {
- command.addContextFilter(IContextFilter.CONSOLE_ONLY);
- } else if (method.isAnnotationPresent(RequirePlayer.class)) {
- command.addContextFilter(IContextFilter.PLAYER_ONLY);
- } else if (method.isAnnotationPresent(RequireConsole.class)) {
- command.addContextFilter(IContextFilter.CONSOLE_ONLY);
- }
-
- list.setRepeatFinalParameter(parameters.length > start && parameters[parameters.length - 1].isVarArgs());
- list.setFinalParameterMayBeFlag(true);
-
- int flags = 0;
- if (hasContinuationParameter) flags |= 1;
- flags <<= 1;
- if (hasContextParameter) flags |= 1;
- flags <<= 1;
- if (hasSenderParameter) flags |= 1;
- flags <<= 1;
- if (hasReceiverParameter) flags |= 1;
- return flags;
- }
-
- public static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command) throws CommandParseException {
- return parseCommandAttributes(selector, method, command, method.getParameters());
- }
-
- public static Parameter<?, ?> parseParameter(IParameterTypeSelector selector, Method method, java.lang.reflect.Parameter parameter, String name) throws CommandParseException {
- Class<?> type = parameter.getType();
- if (parameter.isVarArgs()) {
- type = type.getComponentType();
- }
-
- Annotation[] annotations = parameter.getAnnotations();
- Flag flag = null;
- Annotation typeAnnotation = null;
- Desc desc = null;
-
- for (Annotation annotation : annotations) {
- //noinspection StatementWithEmptyBody
- if (annotation instanceof NamedArg) {
- // do nothing
- } else if (annotation instanceof Flag) {
- if (flag != null) {
- throw new CommandParseException("Multiple flags for the same parameter");
- }
- flag = (Flag) annotation;
- } else if (annotation instanceof Desc) {
- if (desc != null) {
- throw new CommandParseException("Multiple descriptions for the same parameter");
- }
- desc = (Desc) annotation;
- } else {
- if (typeAnnotation != null) {
- throw new CommandParseException("Multiple parameter type annotations for the same parameter");
- }
- typeAnnotation = annotation;
- }
- }
-
- if (flag == null && name.startsWith("-")) {
- throw new CommandParseException("Non-flag parameter's name starts with -");
- } else if (flag != null && !name.startsWith("-")) {
- throw new CommandParseException("Flag parameter's name doesn't start with -");
- }
-
- ParameterType<Object, Object> parameterType = selector.selectAny(type, typeAnnotation == null ? null : typeAnnotation.getClass());
- if (parameterType == null) {
- throw new CommandParseException("IParameter type not found for parameter " + name + " in method " + method.toString());
- }
-
- Object parameterInfo;
- if (typeAnnotation == null) {
- parameterInfo = null;
- } else try {
- parameterInfo = parameterType.getParameterConfig() == null ? null : parameterType.getParameterConfig().getParameterInfo(typeAnnotation);
- } catch (Exception ex) {
- throw new CommandParseException("Invalid parameter config", ex);
- }
-
- String descString = desc == null ? null : CommandAnnotationUtils.getShortDescription(desc);
-
- try {
- //noinspection unchecked
- String flagPermission = flag == null || flag.permission().isEmpty() ? null : flag.permission();
- return new Parameter<>(name, descString, parameterType, parameterInfo, type.isPrimitive(), name.startsWith("-"), flagPermission);
- } catch (Exception ex) {
- throw new CommandParseException("Invalid parameter", ex);
- }
- }
-
- public static void generateCommands(ICommandAddress address, String[] input) {
- for (String value : input) {
- Consumer<ICommandAddress> consumer = PredefinedCommand.getPredefinedCommandGenerator(value);
- if (consumer == null) {
- System.out.println("[Command Warning] generated command '" + value + "' could not be found");
- } else {
- consumer.accept(address);
- }
- }
- }
-
- /*
- Desired format
-
- @Cmd({"tp", "tpto"})
- @RequirePermissions("teleport.self")
- public (static) String|void onCommand(Player sender, Player target, @Flag("force", permission = "teleport.self.force") boolean force) {
- Validate.isTrue(force || !hasTpToggledOff(target), "Target has teleportation disabled. Use -force to ignore");
- sender.teleport(target);
- //return
- }
-
- parser needs to:
- - see the @Cmd and create a CommandTree for it
- - see that it must be a Player executing the command
- - add an indexed IParameter for a Player type
- - add a flag parameter named force, that consumes no arguments.
- - see that setting the force flag requires a permission
- */
-
- private static void setDescription(ICommandAddress address, String[] array, String shortVersion) {
- if (!address.hasCommand()) {
- return;
- }
-
- if (array.length == 0) {
- address.getCommand().setDescription(shortVersion);
- } else {
- address.getCommand().setDescription(array);
- }
-
- }
-
-}
+package io.dico.dicore.command.registration.reflect;
+
+import io.dico.dicore.command.*;
+import io.dico.dicore.command.annotation.*;
+import io.dico.dicore.command.annotation.GroupMatchedCommands.GroupEntry;
+import io.dico.dicore.command.parameter.Parameter;
+import io.dico.dicore.command.parameter.ParameterList;
+import io.dico.dicore.command.parameter.type.IParameterTypeSelector;
+import io.dico.dicore.command.parameter.type.MapBasedParameterTypeSelector;
+import io.dico.dicore.command.parameter.type.ParameterType;
+import io.dico.dicore.command.parameter.type.ParameterTypes;
+import io.dico.dicore.command.predef.PredefinedCommand;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.entity.Player;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import static io.dico.dicore.command.registration.reflect.ReflectiveCallFlags.*;
+
+/**
+ * Takes care of turning a reflection {@link Method} into a command and more.
+ */
+public class ReflectiveRegistration {
+ /**
+ * This object provides names of the parameters.
+ * Oddly, the AnnotationParanamer extensions require a 'fallback' paranamer to function properly without
+ * requiring ALL parameters to have that flag. This is weird because it should just use the AdaptiveParanamer on an upper level to
+ * determine the name of each individual flag. Oddly this isn't how it works, so the fallback works the same way as the AdaptiveParanamer does.
+ * It's just linked instead of using an array for that part. Then we can use an AdaptiveParanamer for the latest fallback, to get bytecode names
+ * or, finally, to get the Jvm-provided parameter names.
+ */
+ //private static final Paranamer paranamer = new CachingParanamer(new BytecodeReadingParanamer());
+ @SuppressWarnings("StatementWithEmptyBody")
+ private static String[] lookupParameterNames(Method method, java.lang.reflect.Parameter[] parameters, int start) {
+ int n = parameters.length;
+ String[] out = new String[n - start];
+
+ //String[] bytecode;
+ //try {
+ // bytecode = paranamer.lookupParameterNames(method, false);
+ //} catch (Exception ex) {
+ // bytecode = new String[0];
+ // System.err.println("ReflectiveRegistration.lookupParameterNames failed to read bytecode");
+ // //ex.printStackTrace();
+ //}
+ //int bn = bytecode.length;
+
+ for (int i = start; i < n; i++) {
+ java.lang.reflect.Parameter parameter = parameters[i];
+ Flag flag = parameter.getAnnotation(Flag.class);
+ NamedArg namedArg = parameter.getAnnotation(NamedArg.class);
+
+ boolean isFlag = flag != null;
+ String name;
+ if (namedArg != null && !(name = namedArg.value()).isEmpty()) {
+ } else if (isFlag && !(name = flag.value()).isEmpty()) {
+ //} else if (i < bn && (name = bytecode[i]) != null && !name.isEmpty()) {
+ } else {
+ name = parameter.getName();
+ }
+
+ if (isFlag) {
+ name = '-' + name;
+ } else {
+ int idx = 0;
+ while (name.startsWith("-", idx)) {
+ idx++;
+ }
+ name = name.substring(idx);
+ }
+
+ out[i - start] = name;
+ }
+
+ return out;
+ }
+
+ public static void parseCommandGroup(ICommandAddress address, Class<?> clazz, Object instance) throws CommandParseException {
+ parseCommandGroup(address, ParameterTypes.getSelector(), clazz, instance);
+ }
+
+ public static void parseCommandGroup(ICommandAddress address, IParameterTypeSelector selector, Class<?> clazz, Object instance) throws CommandParseException {
+ boolean requireStatic = instance == null;
+ if (!requireStatic && !clazz.isInstance(instance)) {
+ throw new CommandParseException();
+ }
+
+ List<Method> methods = new LinkedList<>(Arrays.asList(clazz.getDeclaredMethods()));
+
+ Iterator<Method> it = methods.iterator();
+ for (Method method; it.hasNext(); ) {
+ method = it.next();
+
+ if (requireStatic && !Modifier.isStatic(method.getModifiers())) {
+ it.remove();
+ continue;
+ }
+
+ if (method.isAnnotationPresent(CmdParamType.class)) {
+ it.remove();
+
+ if (method.getReturnType() != ParameterType.class || method.getParameterCount() != 0) {
+ throw new CommandParseException("Invalid CmdParamType method: must return ParameterType and take no arguments");
+ }
+
+ ParameterType<?, ?> type;
+ try {
+ Object inst = Modifier.isStatic(method.getModifiers()) ? null : instance;
+ type = (ParameterType<?, ?>) method.invoke(inst);
+ Objects.requireNonNull(type, "ParameterType returned is null");
+ } catch (Exception ex) {
+ throw new CommandParseException("Error occurred whilst getting ParameterType from CmdParamType method '" + method.toGenericString() + "'", ex);
+ }
+
+ if (selector == ParameterTypes.getSelector()) {
+ selector = new MapBasedParameterTypeSelector(true);
+ }
+
+ selector.addType(method.getAnnotation(CmdParamType.class).infolessAlias(), type);
+ }
+ }
+
+ GroupMatcherCache groupMatcherCache = new GroupMatcherCache(clazz, address);
+ for (Method method : methods) {
+ if (method.isAnnotationPresent(Cmd.class)) {
+ ICommandAddress parsed = parseCommandMethod(selector, method, instance);
+ groupMatcherCache.getGroupFor(method).addChild(parsed);
+ }
+ }
+
+ }
+
+ private static final class GroupMatcherCache {
+ private ModifiableCommandAddress groupRootAddress;
+ private GroupEntry[] matchEntries;
+ private Pattern[] patterns;
+ private ModifiableCommandAddress[] addresses;
+
+ GroupMatcherCache(Class<?> clazz, ICommandAddress groupRootAddress) throws CommandParseException {
+ this.groupRootAddress = (ModifiableCommandAddress) groupRootAddress;
+
+ GroupMatchedCommands groupMatchedCommands = clazz.getAnnotation(GroupMatchedCommands.class);
+ GroupEntry[] matchEntries = groupMatchedCommands == null ? new GroupEntry[0] : groupMatchedCommands.value();
+
+ Pattern[] patterns = new Pattern[matchEntries.length];
+ for (int i = 0; i < matchEntries.length; i++) {
+ GroupEntry matchEntry = matchEntries[i];
+ if (matchEntry.group().isEmpty() || matchEntry.regex().isEmpty()) {
+ throw new CommandParseException("Empty group or regex in GroupMatchedCommands entry");
+ }
+ try {
+ patterns[i] = Pattern.compile(matchEntry.regex());
+ } catch (PatternSyntaxException ex) {
+ throw new CommandParseException(ex);
+ }
+ }
+
+ this.matchEntries = matchEntries;
+ this.patterns = patterns;
+ this.addresses = new ModifiableCommandAddress[this.matchEntries.length];
+ }
+
+ ModifiableCommandAddress getGroupFor(Method method) {
+ String name = method.getName();
+
+ GroupEntry[] matchEntries = this.matchEntries;
+ Pattern[] patterns = this.patterns;
+ ModifiableCommandAddress[] addresses = this.addresses;
+
+ for (int i = 0; i < matchEntries.length; i++) {
+ GroupEntry matchEntry = matchEntries[i];
+ if (patterns[i].matcher(name).matches()) {
+ if (addresses[i] == null) {
+ ChildCommandAddress placeholder = new ChildCommandAddress();
+ placeholder.setupAsPlaceholder(matchEntry.group(), matchEntry.groupAliases());
+ addresses[i] = placeholder;
+ groupRootAddress.addChild(placeholder);
+ generateCommands(placeholder, matchEntry.generatedCommands());
+ setDescription(placeholder, matchEntry.description(), matchEntry.shortDescription());
+ }
+ return addresses[i];
+ }
+ }
+
+ return groupRootAddress;
+ }
+
+ }
+
+ public static ICommandAddress parseCommandMethod(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
+ return new ReflectiveCommand(selector, method, instance).getAddress();
+ }
+
+ static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] callParameters) throws CommandParseException {
+ ParameterList list = command.getParameterList();
+
+ Class<?> senderParameterType = null;
+ int flags = 0;
+ int start = 0;
+ int end = callParameters.length;
+
+ if (callParameters.length > start
+ && command.getInstance() instanceof ICommandInterceptor
+ && ICommandReceiver.class.isAssignableFrom(callParameters[start].getType())) {
+ flags |= RECEIVER_BIT;
+ ++start;
+ }
+
+ if (callParameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = callParameters[start].getType())) {
+ flags |= SENDER_BIT;
+ ++start;
+ }
+
+ if (callParameters.length > start && callParameters[start].getType() == ExecutionContext.class) {
+ flags |= CONTEXT_BIT;
+ ++start;
+ }
+
+ if (callParameters.length > start && callParameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) {
+ flags |= CONTINUATION_BIT;
+ --end;
+ }
+
+ String[] parameterNames = lookupParameterNames(method, callParameters, start);
+ for (int i = start, n = end; i < n; i++) {
+ Parameter<?, ?> parameter = parseParameter(selector, method, callParameters[i], parameterNames[i - start]);
+ list.addParameter(parameter);
+ }
+
+ command.setParameterOrder(hasCallArg(flags, CONTINUATION_BIT) ? Arrays.copyOfRange(parameterNames, 0, parameterNames.length - 1) : parameterNames);
+
+ RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class);
+ if (cmdPermissions != null) {
+ for (String permission : cmdPermissions.value()) {
+ command.addContextFilter(IContextFilter.permission(permission));
+ }
+
+ if (cmdPermissions.inherit()) {
+ command.addContextFilter(IContextFilter.INHERIT_PERMISSIONS);
+ }
+ } else {
+ command.addContextFilter(IContextFilter.INHERIT_PERMISSIONS);
+ }
+
+ RequireParameters reqPar = method.getAnnotation(RequireParameters.class);
+ if (reqPar != null) {
+ list.setRequiredCount(reqPar.value() < 0 ? Integer.MAX_VALUE : reqPar.value());
+ } else {
+ list.setRequiredCount(list.getIndexedParameters().size());
+ }
+
+ /*
+ PreprocessArgs preprocessArgs = method.getAnnotation(PreprocessArgs.class);
+ if (preprocessArgs != null) {
+ IArgumentPreProcessor preProcessor = IArgumentPreProcessor.mergeOnTokens(preprocessArgs.tokens(), preprocessArgs.escapeChar());
+ list.setArgumentPreProcessor(preProcessor);
+ }*/
+
+ Desc desc = method.getAnnotation(Desc.class);
+ if (desc != null) {
+ String[] array = desc.value();
+ if (array.length == 0) {
+ command.setDescription(desc.shortVersion());
+ } else {
+ command.setDescription(array);
+ }
+ } else {
+ command.setDescription();
+ }
+
+ boolean hasSenderParameter = hasCallArg(flags, SENDER_BIT);
+ if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) {
+ command.addContextFilter(IContextFilter.PLAYER_ONLY);
+ } else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) {
+ command.addContextFilter(IContextFilter.CONSOLE_ONLY);
+ } else if (method.isAnnotationPresent(RequirePlayer.class)) {
+ command.addContextFilter(IContextFilter.PLAYER_ONLY);
+ } else if (method.isAnnotationPresent(RequireConsole.class)) {
+ command.addContextFilter(IContextFilter.CONSOLE_ONLY);
+ }
+
+ list.setRepeatFinalParameter(callParameters.length > start && callParameters[callParameters.length - 1].isVarArgs());
+ list.setFinalParameterMayBeFlag(true);
+
+ return flags;
+ }
+
+ public static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command) throws CommandParseException {
+ return parseCommandAttributes(selector, method, command, method.getParameters());
+ }
+
+ public static Parameter<?, ?> parseParameter(IParameterTypeSelector selector, Method method, java.lang.reflect.Parameter parameter, String name) throws CommandParseException {
+ Class<?> type = parameter.getType();
+ if (parameter.isVarArgs()) {
+ type = type.getComponentType();
+ }
+
+ Annotation[] annotations = parameter.getAnnotations();
+ Flag flag = null;
+ Annotation typeAnnotation = null;
+ Desc desc = null;
+
+ for (Annotation annotation : annotations) {
+ //noinspection StatementWithEmptyBody
+ if (annotation instanceof NamedArg) {
+ // do nothing
+ } else if (annotation instanceof Flag) {
+ if (flag != null) {
+ throw new CommandParseException("Multiple flags for the same parameter");
+ }
+ flag = (Flag) annotation;
+ } else if (annotation instanceof Desc) {
+ if (desc != null) {
+ throw new CommandParseException("Multiple descriptions for the same parameter");
+ }
+ desc = (Desc) annotation;
+ } else {
+ if (typeAnnotation != null) {
+ throw new CommandParseException("Multiple parameter type annotations for the same parameter");
+ }
+ typeAnnotation = annotation;
+ }
+ }
+
+ if (flag == null && name.startsWith("-")) {
+ throw new CommandParseException("Non-flag parameter's name starts with -");
+ } else if (flag != null && !name.startsWith("-")) {
+ throw new CommandParseException("Flag parameter's name doesn't start with -");
+ }
+
+ ParameterType<Object, Object> parameterType = selector.selectAny(type, typeAnnotation == null ? null : typeAnnotation.getClass());
+ if (parameterType == null) {
+ throw new CommandParseException("IParameter type not found for parameter " + name + " in method " + method.toString());
+ }
+
+ Object parameterInfo;
+ if (typeAnnotation == null) {
+ parameterInfo = null;
+ } else try {
+ parameterInfo = parameterType.getParameterConfig() == null ? null : parameterType.getParameterConfig().getParameterInfo(typeAnnotation);
+ } catch (Exception ex) {
+ throw new CommandParseException("Invalid parameter config", ex);
+ }
+
+ String descString = desc == null ? null : CommandAnnotationUtils.getShortDescription(desc);
+
+ try {
+ //noinspection unchecked
+ String flagPermission = flag == null || flag.permission().isEmpty() ? null : flag.permission();
+ return new Parameter<>(name, descString, parameterType, parameterInfo, type.isPrimitive(), name.startsWith("-"), flagPermission);
+ } catch (Exception ex) {
+ throw new CommandParseException("Invalid parameter", ex);
+ }
+ }
+
+ public static void generateCommands(ICommandAddress address, String[] input) {
+ for (String value : input) {
+ Consumer<ICommandAddress> consumer = PredefinedCommand.getPredefinedCommandGenerator(value);
+ if (consumer == null) {
+ System.out.println("[Command Warning] generated command '" + value + "' could not be found");
+ } else {
+ consumer.accept(address);
+ }
+ }
+ }
+
+ /*
+ Desired format
+
+ @Cmd({"tp", "tpto"})
+ @RequirePermissions("teleport.self")
+ public (static) String|void onCommand(Player sender, Player target, @Flag("force", permission = "teleport.self.force") boolean force) {
+ Validate.isTrue(force || !hasTpToggledOff(target), "Target has teleportation disabled. Use -force to ignore");
+ sender.teleport(target);
+ //return
+ }
+
+ parser needs to:
+ - see the @Cmd and create a CommandTree for it
+ - see that it must be a Player executing the command
+ - add an indexed IParameter for a Player type
+ - add a flag parameter named force, that consumes no arguments.
+ - see that setting the force flag requires a permission
+ */
+
+ private static void setDescription(ICommandAddress address, String[] array, String shortVersion) {
+ if (!address.hasCommand()) {
+ return;
+ }
+
+ if (array.length == 0) {
+ address.getCommand().setDescription(shortVersion);
+ } else {
+ address.getCommand().setDescription(array);
+ }
+
+ }
+
+}
diff --git a/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt b/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt
index 1a6692b..273cadc 100644
--- a/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt
+++ b/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt
@@ -1,66 +1,69 @@
-package io.dico.dicore.command.registration.reflect
-
-import io.dico.dicore.command.*
-import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
-import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.async
-import java.lang.reflect.Method
-import java.util.concurrent.CancellationException
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.intrinsics.intercepted
-import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
-import kotlin.reflect.jvm.kotlinFunction
-
-fun isSuspendFunction(method: Method): Boolean {
- val func = method.kotlinFunction ?: return false
- return func.isSuspend
-}
-
-fun callAsCoroutine(
- command: ReflectiveCommand,
- factory: ICommandInterceptor,
- context: ExecutionContext,
- args: Array<Any?>
-): String? {
- val coroutineContext = factory.getCoroutineContext(context, command.method, command.cmdName) as CoroutineContext
-
- // UNDISPATCHED causes the handler to run until the first suspension point on the current thread,
- // meaning command handlers that don't have suspension points will run completely synchronously.
- // Tasks that take time to compute should suspend the coroutine and resume on another thread.
- val job = GlobalScope.async(context = coroutineContext, start = UNDISPATCHED) {
- suspendCoroutineUninterceptedOrReturn<Any?> { cont ->
- command.method.invoke(command.instance, *args, cont.intercepted())
- }
- }
-
- if (job.isCompleted) {
- return job.getResult()
- }
-
- job.invokeOnCompletion {
- val chatHandler = context.address.chatHandler
- try {
- val result = job.getResult()
- chatHandler.sendMessage(context.sender, EMessageType.RESULT, result)
- } catch (ex: Throwable) {
- chatHandler.handleException(context.sender, context, ex)
- }
- }
-
- return null
-}
-
-@Throws(CommandException::class)
-private fun Deferred<Any?>.getResult(): String? {
- getCompletionExceptionOrNull()?.let { ex ->
- if (ex is CancellationException) {
- System.err.println("An asynchronously dispatched command was cancelled unexpectedly")
- ex.printStackTrace()
- throw CommandException("The command was cancelled unexpectedly (see console)")
- }
- if (ex is Exception) return ReflectiveCommand.getResult(null, ex)
- throw ex
- }
- return ReflectiveCommand.getResult(getCompleted(), null)
-}
+package io.dico.dicore.command.registration.reflect
+
+import io.dico.dicore.command.*
+import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import java.lang.reflect.Method
+import java.util.concurrent.CancellationException
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.intrinsics.intercepted
+import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
+import kotlin.reflect.jvm.kotlinFunction
+
+fun isSuspendFunction(method: Method): Boolean {
+ val func = method.kotlinFunction ?: return false
+ return func.isSuspend
+}
+
+@Throws(CommandException::class)
+fun callCommandAsCoroutine(
+ executionContext: ExecutionContext,
+ coroutineContext: CoroutineContext,
+ continuationIndex: Int,
+ method: Method,
+ instance: Any?,
+ args: Array<Any?>
+): String? {
+
+ // UNDISPATCHED causes the handler to run until the first suspension point on the current thread,
+ // meaning command handlers that don't have suspension points will run completely synchronously.
+ // Tasks that take time to compute should suspend the coroutine and resume on another thread.
+ val job = GlobalScope.async(context = coroutineContext, start = UNDISPATCHED) {
+ suspendCoroutineUninterceptedOrReturn<Any?> { cont ->
+ args[continuationIndex] = cont.intercepted()
+ method.invoke(instance, *args)
+ }
+ }
+
+ if (job.isCompleted) {
+ return job.getResult()
+ }
+
+ job.invokeOnCompletion {
+ val chatHandler = executionContext.address.chatHandler
+ try {
+ val result = job.getResult()
+ chatHandler.sendMessage(executionContext.sender, EMessageType.RESULT, result)
+ } catch (ex: Throwable) {
+ chatHandler.handleException(executionContext.sender, executionContext, ex)
+ }
+ }
+
+ return null
+}
+
+@Throws(CommandException::class)
+private fun Deferred<Any?>.getResult(): String? {
+ getCompletionExceptionOrNull()?.let { ex ->
+ if (ex is CancellationException) {
+ System.err.println("An asynchronously dispatched command was cancelled unexpectedly")
+ ex.printStackTrace()
+ throw CommandException("The command was cancelled unexpectedly (see console)")
+ }
+ if (ex is Exception) return ReflectiveCommand.getResult(null, ex)
+ throw ex
+ }
+ return ReflectiveCommand.getResult(getCompleted(), null)
+}
diff --git a/src/main/kotlin/io/dico/parcels2/JobDispatcher.kt b/src/main/kotlin/io/dico/parcels2/JobDispatcher.kt
index 12be89a..ebbe334 100644
--- a/src/main/kotlin/io/dico/parcels2/JobDispatcher.kt
+++ b/src/main/kotlin/io/dico/parcels2/JobDispatcher.kt
@@ -1,337 +1,368 @@
-package io.dico.parcels2
-
-import io.dico.parcels2.util.math.clampMin
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart.LAZY
-import kotlinx.coroutines.Job as CoroutineJob
-import kotlinx.coroutines.launch
-import org.bukkit.scheduler.BukkitTask
-import java.lang.System.currentTimeMillis
-import java.util.LinkedList
-import kotlin.coroutines.Continuation
-import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
-import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
-import kotlin.coroutines.resume
-
-typealias JobFunction = suspend JobScope.() -> Unit
-typealias JobUpdateLister = Job.(Double, Long) -> Unit
-
-data class TickJobtimeOptions(var jobTime: Int, var tickInterval: Int)
-
-interface JobDispatcher {
- /**
- * Submit a [function] that should be run synchronously, but limited such that it does not stall the server
- */
- fun dispatch(function: JobFunction): Job
-
- /**
- * Get a list of all jobs
- */
- val jobs: List<Job>
-
- /**
- * Attempts to complete any remaining tasks immediately, without suspension.
- */
- fun completeAllTasks()
-}
-
-interface JobAndScopeMembersUnion {
- /**
- * The time that elapsed since this job was dispatched, in milliseconds
- */
- val elapsedTime: Long
-
- /**
- * A value indicating the progress of this job, in the range 0.0 <= progress <= 1.0
- * with no guarantees to its accuracy.
- */
- val progress: Double
-}
-
-interface Job : JobAndScopeMembersUnion {
- /**
- * The coroutine associated with this job
- */
- val coroutine: CoroutineJob
-
- /**
- * true if this job has completed
- */
- val isComplete: Boolean
-
- /**
- * If an exception was thrown during the execution of this task,
- * returns that exception. Returns null otherwise.
- */
- val completionException: Throwable?
-
- /**
- * Calls the given [block] whenever the progress of this job is updated,
- * if [minInterval] milliseconds expired since the last call.
- * The first call occurs after at least [minDelay] milliseconds in a likewise manner.
- * Repeated invocations of this method result in an [IllegalStateException]
- *
- * if [asCompletionListener] is true, [onCompleted] is called with the same [block]
- */
- fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean = true, block: JobUpdateLister): Job
-
- /**
- * Calls the given [block] when this job completes, with the progress value 1.0.
- * Multiple listeners may be registered to this function.
- */
- fun onCompleted(block: JobUpdateLister): Job
-
- /**
- * Await completion of this job
- */
- suspend fun awaitCompletion()
-}
-
-interface JobScope : JobAndScopeMembersUnion {
- /**
- * A task should call this frequently during its execution, such that the timer can suspend it when necessary.
- */
- suspend fun markSuspensionPoint()
-
- /**
- * A task should call this method to indicate its progress
- */
- fun setProgress(progress: Double)
-
- /**
- * Indicate that this job is complete
- */
- fun markComplete() = setProgress(1.0)
-
- /**
- * Get a [JobScope] that is responsible for [portion] part of the progress
- * If [portion] is negative, the remaining progress is used
- */
- fun delegateProgress(portion: Double = -1.0): JobScope
-}
-
-inline fun <T> JobScope.delegateWork(portion: Double = -1.0, block: JobScope.() -> T): T {
- delegateProgress(portion).apply {
- val result = block()
- markComplete()
- return result
- }
-}
-
-interface JobInternal : Job, JobScope {
- /**
- * Start or resumes the execution of this job
- * and returns true if the job completed
- *
- * [worktime] is the maximum amount of time, in milliseconds,
- * that this job may run for until suspension.
- *
- * If [worktime] is not positive, the job will complete
- * without suspension and this method will always return true.
- */
- fun resume(worktime: Long): Boolean
-}
-
-/**
- * An object that controls one or more jobs, ensuring that they don't stall the server too much.
- * There is a configurable maxiumum amount of milliseconds that can be allocated to all jobs together in each server tick
- * This object attempts to split that maximum amount of milliseconds equally between all jobs
- */
-class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJobtimeOptions) : JobDispatcher {
- // The currently registered bukkit scheduler task
- private var bukkitTask: BukkitTask? = null
- // The jobs.
- private val _jobs = LinkedList<JobInternal>()
- override val jobs: List<Job> = _jobs
-
- override fun dispatch(function: JobFunction): Job {
- val job: JobInternal = JobImpl(plugin, function)
-
- if (bukkitTask == null) {
- val completed = job.resume(options.jobTime.toLong())
- if (completed) return job
- bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickCoroutineJobs() }
- }
- _jobs.addFirst(job)
- return job
- }
-
- private fun tickCoroutineJobs() {
- val jobs = _jobs
- if (jobs.isEmpty()) return
- val tickStartTime = System.currentTimeMillis()
-
- val iterator = jobs.listIterator(index = 0)
- while (iterator.hasNext()) {
- val time = System.currentTimeMillis()
- val timeElapsed = time - tickStartTime
- val timeLeft = options.jobTime - timeElapsed
- if (timeLeft <= 0) return
-
- val count = jobs.size - iterator.nextIndex()
- val timePerJob = (timeLeft + count - 1) / count
- val job = iterator.next()
- val completed = job.resume(timePerJob)
- if (completed) {
- iterator.remove()
- }
- }
-
- if (jobs.isEmpty()) {
- bukkitTask?.cancel()
- bukkitTask = null
- }
- }
-
- override fun completeAllTasks() {
- _jobs.forEach {
- it.resume(-1)
- }
- _jobs.clear()
- bukkitTask?.cancel()
- bukkitTask = null
- }
-
-}
-
-private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
- override val coroutine: CoroutineJob = scope.launch(start = LAZY) { task() }
-
- private var continuation: Continuation<Unit>? = null
- private var nextSuspensionTime: Long = 0L
- private var completeForcefully = false
- private var isStarted = false
-
- override val elapsedTime
- get() =
- if (coroutine.isCompleted) startTimeOrElapsedTime
- else currentTimeMillis() - startTimeOrElapsedTime
-
- override val isComplete get() = coroutine.isCompleted
-
- private var _progress = 0.0
- override val progress get() = _progress
- override var completionException: Throwable? = null; private set
-
- private var startTimeOrElapsedTime: Long = 0L // startTime before completed, elapsed time otherwise
- private var onProgressUpdate: JobUpdateLister? = null
- private var progressUpdateInterval: Int = 0
- private var lastUpdateTime: Long = 0L
- private var onCompleted: JobUpdateLister? = null
-
- init {
- coroutine.invokeOnCompletion { exception ->
- // report any error that occurred
- completionException = exception?.also {
- if (it !is CancellationException)
- logger.error("JobFunction generated an exception", it)
- }
-
- // convert to elapsed time here
- startTimeOrElapsedTime = System.currentTimeMillis() - startTimeOrElapsedTime
- onCompleted?.let { it(1.0, elapsedTime) }
-
- onCompleted = null
- onProgressUpdate = { prog, el -> }
- }
- }
-
- override fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean, block: JobUpdateLister): Job {
- onProgressUpdate?.let { throw IllegalStateException() }
- if (asCompletionListener) onCompleted(block)
- if (isComplete) return this
- onProgressUpdate = block
- progressUpdateInterval = minInterval
- lastUpdateTime = System.currentTimeMillis() + minDelay - minInterval
-
- return this
- }
-
- override fun onCompleted(block: JobUpdateLister): Job {
- if (isComplete) {
- block(1.0, startTimeOrElapsedTime)
- return this
- }
-
- val cur = onCompleted
- onCompleted = if (cur == null) {
- block
- } else {
- fun Job.(prog: Double, el: Long) {
- cur(prog, el)
- block(prog, el)
- }
- }
- return this
- }
-
- override suspend fun markSuspensionPoint() {
- if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully)
- suspendCoroutineUninterceptedOrReturn { cont: Continuation<Unit> ->
- continuation = cont
- COROUTINE_SUSPENDED
- }
- }
-
- override fun setProgress(progress: Double) {
- this._progress = progress
- val onProgressUpdate = onProgressUpdate ?: return
- val time = System.currentTimeMillis()
- if (time > lastUpdateTime + progressUpdateInterval) {
- onProgressUpdate(progress, elapsedTime)
- lastUpdateTime = time
- }
- }
-
- override fun resume(worktime: Long): Boolean {
- if (isComplete) return true
-
- if (worktime > 0) {
- nextSuspensionTime = currentTimeMillis() + worktime
- } else {
- completeForcefully = true
- }
-
- if (isStarted) {
- continuation?.let {
- continuation = null
- it.resume(Unit)
- return continuation == null
- }
- return true
- }
-
- isStarted = true
- startTimeOrElapsedTime = System.currentTimeMillis()
- coroutine.start()
-
- return continuation == null
- }
-
- override suspend fun awaitCompletion() {
- coroutine.join()
- }
-
- private fun delegateProgress(curPortion: Double, portion: Double): JobScope =
- DelegateScope(progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0))
-
- override fun delegateProgress(portion: Double): JobScope = delegateProgress(1.0, portion)
-
- private inner class DelegateScope(val progressStart: Double, val portion: Double) : JobScope {
- override val elapsedTime: Long
- get() = this@JobImpl.elapsedTime
-
- override suspend fun markSuspensionPoint() =
- this@JobImpl.markSuspensionPoint()
-
- override val progress: Double
- get() = (this@JobImpl.progress - progressStart) / portion
-
- override fun setProgress(progress: Double) =
- this@JobImpl.setProgress(progressStart + progress * portion)
-
- override fun delegateProgress(portion: Double): JobScope =
- this@JobImpl.delegateProgress(this.portion, portion)
- }
-}
+package io.dico.parcels2
+
+import io.dico.parcels2.util.PluginAware
+import io.dico.parcels2.util.math.clampMin
+import io.dico.parcels2.util.scheduleRepeating
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart.LAZY
+import kotlinx.coroutines.Job as CoroutineJob
+import kotlinx.coroutines.launch
+import org.bukkit.scheduler.BukkitTask
+import java.lang.System.currentTimeMillis
+import java.util.LinkedList
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
+import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
+import kotlin.coroutines.resume
+
+typealias JobFunction = suspend JobScope.() -> Unit
+typealias JobUpdateLister = Job.(Double, Long) -> Unit
+
+data class TickJobtimeOptions(var jobTime: Int, var tickInterval: Int)
+
+interface JobDispatcher {
+ /**
+ * Submit a [function] that should be run synchronously, but limited such that it does not stall the server
+ */
+ fun dispatch(function: JobFunction): Job
+
+ /**
+ * Get a list of all jobs
+ */
+ val jobs: List<Job>
+
+ /**
+ * Attempts to complete any remaining tasks immediately, without suspension.
+ */
+ fun completeAllTasks()
+}
+
+interface JobAndScopeMembersUnion {
+ /**
+ * The time that elapsed since this job was dispatched, in milliseconds
+ */
+ val elapsedTime: Long
+
+ /**
+ * A value indicating the progress of this job, in the range 0.0 <= progress <= 1.0
+ * with no guarantees to its accuracy.
+ */
+ val progress: Double
+}
+
+interface Job : JobAndScopeMembersUnion {
+ /**
+ * The coroutine associated with this job
+ */
+ val coroutine: CoroutineJob
+
+ /**
+ * true if this job has completed
+ */
+ val isComplete: Boolean
+
+ /**
+ * If an exception was thrown during the execution of this task,
+ * returns that exception. Returns null otherwise.
+ */
+ val completionException: Throwable?
+
+ /**
+ * Calls the given [block] whenever the progress of this job is updated,
+ * if [minInterval] milliseconds expired since the last call.
+ * The first call occurs after at least [minDelay] milliseconds in a likewise manner.
+ * Repeated invocations of this method result in an [IllegalStateException]
+ *
+ * if [asCompletionListener] is true, [onCompleted] is called with the same [block]
+ */
+ fun onProgressUpdate(
+ minDelay: Int,
+ minInterval: Int,
+ asCompletionListener: Boolean = true,
+ block: JobUpdateLister
+ ): Job
+
+ /**
+ * Calls the given [block] when this job completes, with the progress value 1.0.
+ * Multiple listeners may be registered to this function.
+ */
+ fun onCompleted(block: JobUpdateLister): Job
+
+ /**
+ * Await completion of this job
+ */
+ suspend fun awaitCompletion()
+}
+
+interface JobScope : JobAndScopeMembersUnion {
+ /**
+ * A task should call this frequently during its execution, such that the timer can suspend it when necessary.
+ */
+ suspend fun markSuspensionPoint()
+
+ /**
+ * A task should call this method to indicate its progress
+ */
+ fun setProgress(progress: Double)
+
+ /**
+ * Indicate that this job is complete
+ */
+ fun markComplete() = setProgress(1.0)
+
+ /**
+ * Get a [JobScope] that is responsible for [portion] part of the progress
+ * If [portion] is negative, the remaining progress is used
+ */
+ fun delegateProgress(portion: Double = -1.0): JobScope
+}
+
+inline fun <T> JobScope.delegateWork(portion: Double = -1.0, block: JobScope.() -> T): T {
+ delegateProgress(portion).apply {
+ val result = block()
+ markComplete()
+ return result
+ }
+}
+
+interface JobInternal : Job, JobScope {
+ /**
+ * Start or resumes the execution of this job
+ * and returns true if the job completed
+ *
+ * [worktime] is the maximum amount of time, in milliseconds,
+ * that this job may run for until suspension.
+ *
+ * If [worktime] is not positive, the job will complete
+ * without suspension and this method will always return true.
+ */
+ fun resume(worktime: Long): Boolean
+}
+
+/**
+ * An object that controls one or more jobs, ensuring that they don't stall the server too much.
+ * There is a configurable maxiumum amount of milliseconds that can be allocated to all jobs together in each server tick
+ * This object attempts to split that maximum amount of milliseconds equally between all jobs
+ */
+class BukkitJobDispatcher(
+ private val plugin: PluginAware,
+ private val scope: CoroutineScope,
+ var options: TickJobtimeOptions
+) : JobDispatcher {
+ // The currently registered bukkit scheduler task
+ private var bukkitTask: BukkitTask? = null
+ // The jobs.
+ private val _jobs = LinkedList<JobInternal>()
+ override val jobs: List<Job> = _jobs
+
+ override fun dispatch(function: JobFunction): Job {
+ val job: JobInternal = JobImpl(scope, function)
+
+ if (bukkitTask == null) {
+ val completed = job.resume(options.jobTime.toLong())
+ if (completed) return job
+ bukkitTask = plugin.scheduleRepeating(options.tickInterval) { tickJobs() }
+ }
+ _jobs.addFirst(job)
+ return job
+ }
+
+ private fun tickJobs() {
+ val jobs = _jobs
+ if (jobs.isEmpty()) return
+ val tickStartTime = System.currentTimeMillis()
+
+ val iterator = jobs.listIterator(index = 0)
+ while (iterator.hasNext()) {
+ val time = System.currentTimeMillis()
+ val timeElapsed = time - tickStartTime
+ val timeLeft = options.jobTime - timeElapsed
+ if (timeLeft <= 0) return
+
+ val count = jobs.size - iterator.nextIndex()
+ val timePerJob = (timeLeft + count - 1) / count
+ val job = iterator.next()
+ val completed = job.resume(timePerJob)
+ if (completed) {
+ iterator.remove()
+ }
+ }
+
+ if (jobs.isEmpty()) {
+ bukkitTask?.cancel()
+ bukkitTask = null
+ }
+ }
+
+ override fun completeAllTasks() {
+ _jobs.forEach {
+ it.resume(-1)
+ }
+ _jobs.clear()
+ bukkitTask?.cancel()
+ bukkitTask = null
+ }
+
+}
+
+private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
+ override val coroutine: CoroutineJob = scope.launch(start = LAZY) { task() }
+
+ private var continuation: Continuation<Unit>? = null
+ private var nextSuspensionTime: Long = 0L
+ private var completeForcefully = false
+ private var isStarted = false
+
+ override val elapsedTime
+ get() =
+ if (coroutine.isCompleted) startTimeOrElapsedTime
+ else currentTimeMillis() - startTimeOrElapsedTime
+
+ override val isComplete get() = coroutine.isCompleted
+
+ private var _progress = 0.0
+ override val progress get() = _progress
+ override var completionException: Throwable? = null; private set
+
+ private var startTimeOrElapsedTime: Long = 0L // startTime before completed, elapsed time otherwise
+ private var onProgressUpdate: JobUpdateLister? = null
+ private var progressUpdateInterval: Int = 0
+ private var lastUpdateTime: Long = 0L
+ private var onCompleted: JobUpdateLister? = null
+
+ init {
+ coroutine.invokeOnCompletion { exception ->
+ // report any error that occurred
+ completionException = exception?.also {
+ if (it !is CancellationException)
+ logger.error("JobFunction generated an exception", it)
+ }
+
+ // convert to elapsed time here
+ startTimeOrElapsedTime = System.currentTimeMillis() - startTimeOrElapsedTime
+ onCompleted?.let { it(1.0, elapsedTime) }
+
+ onCompleted = null
+ onProgressUpdate = { prog, el -> }
+ }
+ }
+
+ override fun onProgressUpdate(
+ minDelay: Int,
+ minInterval: Int,
+ asCompletionListener: Boolean,
+ block: JobUpdateLister
+ ): Job {
+ onProgressUpdate?.let { throw IllegalStateException() }
+ if (asCompletionListener) onCompleted(block)
+ if (isComplete) return this
+ onProgressUpdate = block
+ progressUpdateInterval = minInterval
+ lastUpdateTime = System.currentTimeMillis() + minDelay - minInterval
+
+ return this
+ }
+
+ override fun onCompleted(block: JobUpdateLister): Job {
+ if (isComplete) {
+ block(1.0, startTimeOrElapsedTime)
+ return this
+ }
+
+ val cur = onCompleted
+ onCompleted = if (cur == null) {
+ block
+ } else {
+ fun Job.(prog: Double, el: Long) {
+ cur(prog, el)
+ block(prog, el)
+ }
+ }
+ return this
+ }
+
+ override suspend fun markSuspensionPoint() {
+ if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully)
+ suspendCoroutineUninterceptedOrReturn { cont: Continuation<Unit> ->
+ continuation = cont
+ COROUTINE_SUSPENDED
+ }
+ }
+
+ override fun setProgress(progress: Double) {
+ this._progress = progress
+ val onProgressUpdate = onProgressUpdate ?: return
+ val time = System.currentTimeMillis()
+ if (time > lastUpdateTime + progressUpdateInterval) {
+ onProgressUpdate(progress, elapsedTime)
+ lastUpdateTime = time
+ }
+ }
+
+ override fun resume(worktime: Long): Boolean {
+ if (isComplete) return true
+
+ if (worktime > 0) {
+ nextSuspensionTime = currentTimeMillis() + worktime
+ } else {
+ completeForcefully = true
+ }
+
+ if (isStarted) {
+ continuation?.let {
+ continuation = null
+
+ wrapExternalCall {
+ it.resume(Unit)
+ }
+
+ return continuation == null
+ }
+ return true
+ }
+
+ isStarted = true
+ startTimeOrElapsedTime = System.currentTimeMillis()
+
+ wrapExternalCall {
+ coroutine.start()
+ }
+
+ return continuation == null
+ }
+
+ private inline fun wrapExternalCall(block: () -> Unit) {
+ try {
+ block()
+ } catch (ex: Throwable) {
+ logger.error("Job $coroutine generated an exception", ex)
+ }
+ }
+
+ override suspend fun awaitCompletion() {
+ coroutine.join()
+ }
+
+ private fun delegateProgress(curPortion: Double, portion: Double): JobScope =
+ DelegateScope(this, progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0))
+
+ override fun delegateProgress(portion: Double): JobScope = delegateProgress(1.0, portion)
+
+ private class DelegateScope(val parent: JobImpl, val progressStart: Double, val portion: Double) : JobScope {
+ override val elapsedTime: Long
+ get() = parent.elapsedTime
+
+ override suspend fun markSuspensionPoint() =
+ parent.markSuspensionPoint()
+
+ override val progress: Double
+ get() = (parent.progress - progressStart) / portion
+
+ override fun setProgress(progress: Double) =
+ parent.setProgress(progressStart + progress * portion)
+
+ override fun delegateProgress(portion: Double): JobScope =
+ parent.delegateProgress(this.portion, portion)
+ }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt
index 63ec02c..ada6d12 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt
@@ -1,103 +1,102 @@
-package io.dico.parcels2
-
-import io.dico.parcels2.blockvisitor.RegionTraverser
-import io.dico.parcels2.util.math.Region
-import io.dico.parcels2.util.math.Vec2i
-import io.dico.parcels2.util.math.Vec3i
-import io.dico.parcels2.util.math.get
-import kotlinx.coroutines.CoroutineScope
-import org.bukkit.Chunk
-import org.bukkit.Location
-import org.bukkit.Material
-import org.bukkit.World
-import org.bukkit.block.Biome
-import org.bukkit.block.Block
-import org.bukkit.block.BlockFace
-import org.bukkit.entity.Entity
-import org.bukkit.generator.BlockPopulator
-import org.bukkit.generator.ChunkGenerator
-import java.util.Random
-
-abstract class ParcelGenerator : ChunkGenerator() {
- abstract val worldName: String
-
- abstract val world: World
-
- abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData
-
- abstract fun populate(world: World?, random: Random?, chunk: Chunk?)
-
- abstract override fun getFixedSpawnLocation(world: World?, random: Random?): Location
-
- override fun getDefaultPopulators(world: World?): MutableList<BlockPopulator> {
- return mutableListOf(object : BlockPopulator() {
- override fun populate(world: World?, random: Random?, chunk: Chunk?) {
- this@ParcelGenerator.populate(world, random, chunk)
- }
- })
- }
-
- abstract fun makeParcelLocatorAndBlockManager(
- parcelProvider: ParcelProvider,
- container: ParcelContainer,
- coroutineScope: CoroutineScope,
- jobDispatcher: JobDispatcher
- ): Pair<ParcelLocator, ParcelBlockManager>
-}
-
-interface ParcelBlockManager {
- val world: World
- val jobDispatcher: JobDispatcher
- val parcelTraverser: RegionTraverser
-
- fun getRegionOrigin(parcel: ParcelId) = getRegion(parcel).origin.toVec2i()
-
- fun getHomeLocation(parcel: ParcelId): Location
-
- fun getRegion(parcel: ParcelId): Region
-
- fun getEntities(parcel: ParcelId): Collection<Entity>
-
- fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean
-
- fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?)
-
- fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel?
-
- fun setBiome(parcel: ParcelId, biome: Biome): Job?
-
- fun clearParcel(parcel: ParcelId): Job?
-
- /**
- * Used to update owner blocks in the corner of the parcel
- */
- fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i>
-}
-
-inline fun ParcelBlockManager.tryDoBlockOperation(
- parcelProvider: ParcelProvider,
- parcel: ParcelId,
- traverser: RegionTraverser,
- crossinline operation: suspend JobScope.(Block) -> Unit
-) = parcelProvider.trySubmitBlockVisitor(Permit(), arrayOf(parcel)) {
- val region = getRegion(parcel)
- val blockCount = region.blockCount.toDouble()
- val blocks = traverser.traverseRegion(region)
- for ((index, vec) in blocks.withIndex()) {
- markSuspensionPoint()
- operation(world[vec])
- setProgress((index + 1) / blockCount)
- }
-}
-
-abstract class ParcelBlockManagerBase : ParcelBlockManager {
-
- override fun getEntities(parcel: ParcelId): Collection<Entity> {
- val region = getRegion(parcel)
- val center = region.center
- val centerLoc = Location(world, center.x, center.y, center.z)
- val centerDist = (center - region.origin).add(0.2, 0.2, 0.2)
- return world.getNearbyEntities(centerLoc, centerDist.x, centerDist.y, centerDist.z)
- }
-
-}
+package io.dico.parcels2
+
+import io.dico.parcels2.blockvisitor.RegionTraverser
+import io.dico.parcels2.util.math.Region
+import io.dico.parcels2.util.math.Vec2i
+import io.dico.parcels2.util.math.Vec3i
+import io.dico.parcels2.util.math.get
+import kotlinx.coroutines.CoroutineScope
+import org.bukkit.Chunk
+import org.bukkit.Location
+import org.bukkit.Material
+import org.bukkit.World
+import org.bukkit.block.Biome
+import org.bukkit.block.Block
+import org.bukkit.block.BlockFace
+import org.bukkit.entity.Entity
+import org.bukkit.generator.BlockPopulator
+import org.bukkit.generator.ChunkGenerator
+import java.util.Random
+
+abstract class ParcelGenerator : ChunkGenerator() {
+ abstract val worldName: String
+
+ abstract val world: World
+
+ abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData
+
+ abstract fun populate(world: World?, random: Random?, chunk: Chunk?)
+
+ abstract override fun getFixedSpawnLocation(world: World?, random: Random?): Location
+
+ override fun getDefaultPopulators(world: World?): MutableList<BlockPopulator> {
+ return mutableListOf(object : BlockPopulator() {
+ override fun populate(world: World?, random: Random?, chunk: Chunk?) {
+ this@ParcelGenerator.populate(world, random, chunk)
+ }
+ })
+ }
+
+ abstract fun makeParcelLocatorAndBlockManager(
+ parcelProvider: ParcelProvider,
+ container: ParcelContainer,
+ coroutineScope: CoroutineScope,
+ jobDispatcher: JobDispatcher
+ ): Pair<ParcelLocator, ParcelBlockManager>
+}
+
+interface ParcelBlockManager {
+ val world: World
+ val jobDispatcher: JobDispatcher
+ val parcelTraverser: RegionTraverser
+
+ fun getRegionOrigin(parcel: ParcelId) = getRegion(parcel).origin.toVec2i()
+
+ fun getHomeLocation(parcel: ParcelId): Location
+
+ fun getRegion(parcel: ParcelId): Region
+
+ fun getEntities(region: Region): Collection<Entity>
+
+ fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean
+
+ fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?)
+
+ fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel?
+
+ fun setBiome(parcel: ParcelId, biome: Biome): Job?
+
+ fun clearParcel(parcel: ParcelId): Job?
+
+ /**
+ * Used to update owner blocks in the corner of the parcel
+ */
+ fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i>
+}
+
+inline fun ParcelBlockManager.tryDoBlockOperation(
+ parcelProvider: ParcelProvider,
+ parcel: ParcelId,
+ traverser: RegionTraverser,
+ crossinline operation: suspend JobScope.(Block) -> Unit
+) = parcelProvider.trySubmitBlockVisitor(Permit(), arrayOf(parcel)) {
+ val region = getRegion(parcel)
+ val blockCount = region.blockCount.toDouble()
+ val blocks = traverser.traverseRegion(region)
+ for ((index, vec) in blocks.withIndex()) {
+ markSuspensionPoint()
+ operation(world[vec])
+ setProgress((index + 1) / blockCount)
+ }
+}
+
+abstract class ParcelBlockManagerBase : ParcelBlockManager {
+
+ override fun getEntities(region: Region): Collection<Entity> {
+ val center = region.center
+ val centerLoc = Location(world, center.x, center.y, center.z)
+ val centerDist = (center - region.origin).add(0.2, 0.2, 0.2)
+ return world.getNearbyEntities(centerLoc, centerDist.x, centerDist.y, centerDist.z)
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
index 36dfe1c..054d9d6 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
@@ -1,103 +1,103 @@
-package io.dico.parcels2
-
-import io.dico.parcels2.options.RuntimeWorldOptions
-import io.dico.parcels2.storage.Storage
-import io.dico.parcels2.util.math.Vec2i
-import io.dico.parcels2.util.math.floor
-import org.bukkit.Location
-import org.bukkit.World
-import org.bukkit.block.Block
-import org.bukkit.entity.Entity
-import org.joda.time.DateTime
-import java.lang.IllegalStateException
-import java.util.UUID
-
-class Permit
-
-interface ParcelProvider {
- val worlds: Map<String, ParcelWorld>
-
- fun getWorldById(id: ParcelWorldId): ParcelWorld?
-
- fun getParcelById(id: ParcelId): Parcel?
-
- fun getWorld(name: String): ParcelWorld?
-
- fun getWorld(world: World): ParcelWorld? = getWorld(world.name)
-
- fun getWorld(block: Block): ParcelWorld? = getWorld(block.world)
-
- fun getWorld(loc: Location): ParcelWorld? = getWorld(loc.world)
-
- fun getWorld(entity: Entity): ParcelWorld? = getWorld(entity.location)
-
- fun getParcelAt(worldName: String, x: Int, z: Int): Parcel? = getWorld(worldName)?.locator?.getParcelAt(x, z)
-
- fun getParcelAt(world: World, x: Int, z: Int): Parcel? = getParcelAt(world.name, x, z)
-
- fun getParcelAt(world: World, vec: Vec2i): Parcel? = getParcelAt(world, vec.x, vec.z)
-
- fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.world, loc.x.floor(), loc.z.floor())
-
- fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location)
-
- fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z)
-
- fun getWorldGenerator(worldName: String): ParcelGenerator?
-
- fun loadWorlds()
-
- fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean
-
- @Throws(IllegalStateException::class)
- fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit)
-
- fun trySubmitBlockVisitor(permit: Permit, parcelIds: Array<out ParcelId>, function: JobFunction): Job?
-
- fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job?
-}
-
-interface ParcelLocator {
- val world: World
-
- fun getParcelIdAt(x: Int, z: Int): ParcelId?
-
- fun getParcelAt(x: Int, z: Int): Parcel?
-
- fun getParcelAt(vec: Vec2i): Parcel? = getParcelAt(vec.x, vec.z)
-
- fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.x.floor(), loc.z.floor()).takeIf { loc.world == world }
-
- fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world }
-
- fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world }
-}
-
-typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
-
-interface ParcelContainer {
-
- fun getParcelById(x: Int, z: Int): Parcel?
-
- fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z)
-
- fun getParcelById(id: ParcelId): Parcel?
-
- fun nextEmptyParcel(): Parcel?
-
-}
-
-interface ParcelWorld : ParcelLocator, ParcelContainer {
- val id: ParcelWorldId
- val name: String
- val uid: UUID?
- val options: RuntimeWorldOptions
- val generator: ParcelGenerator
- val storage: Storage
- val container: ParcelContainer
- val locator: ParcelLocator
- val blockManager: ParcelBlockManager
- val globalPrivileges: GlobalPrivilegesManager
-
- val creationTime: DateTime?
-}
+package io.dico.parcels2
+
+import io.dico.parcels2.options.RuntimeWorldOptions
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.util.math.Vec2i
+import io.dico.parcels2.util.math.floor
+import org.bukkit.Location
+import org.bukkit.World
+import org.bukkit.block.Block
+import org.bukkit.entity.Entity
+import org.joda.time.DateTime
+import java.lang.IllegalStateException
+import java.util.UUID
+
+class Permit
+
+interface ParcelProvider {
+ val worlds: Map<String, ParcelWorld>
+
+ fun getWorldById(id: ParcelWorldId): ParcelWorld?
+
+ fun getParcelById(id: ParcelId): Parcel?
+
+ fun getWorld(name: String): ParcelWorld?
+
+ fun getWorld(world: World): ParcelWorld? = getWorld(world.name)
+
+ fun getWorld(block: Block): ParcelWorld? = getWorld(block.world)
+
+ fun getWorld(loc: Location): ParcelWorld? = getWorld(loc.world)
+
+ fun getWorld(entity: Entity): ParcelWorld? = getWorld(entity.location)
+
+ fun getParcelAt(worldName: String, x: Int, z: Int): Parcel? = getWorld(worldName)?.locator?.getParcelAt(x, z)
+
+ fun getParcelAt(world: World, x: Int, z: Int): Parcel? = getParcelAt(world.name, x, z)
+
+ fun getParcelAt(world: World, vec: Vec2i): Parcel? = getParcelAt(world, vec.x, vec.z)
+
+ fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.world, loc.x.floor(), loc.z.floor())
+
+ fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location)
+
+ fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z)
+
+ fun getWorldGenerator(worldName: String): ParcelGenerator?
+
+ fun loadWorlds()
+
+ fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean
+
+ @Throws(IllegalStateException::class)
+ fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit)
+
+ fun trySubmitBlockVisitor(permit: Permit, parcelIds: Array<out ParcelId>, function: JobFunction): Job?
+
+ fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job?
+}
+
+interface ParcelLocator {
+ val world: World
+
+ fun getParcelIdAt(x: Int, z: Int): ParcelId?
+
+ fun getParcelAt(x: Int, z: Int): Parcel?
+
+ fun getParcelAt(vec: Vec2i): Parcel? = getParcelAt(vec.x, vec.z)
+
+ fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.x.floor(), loc.z.floor()).takeIf { loc.world == world }
+
+ fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world }
+
+ fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world }
+}
+
+typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
+
+interface ParcelContainer {
+
+ fun getParcelById(x: Int, z: Int): Parcel?
+
+ fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z)
+
+ fun getParcelById(id: ParcelId): Parcel?
+
+ suspend fun nextEmptyParcel(): Parcel?
+
+}
+
+interface ParcelWorld : ParcelLocator, ParcelContainer {
+ val id: ParcelWorldId
+ val name: String
+ val uid: UUID?
+ val options: RuntimeWorldOptions
+ val generator: ParcelGenerator
+ val storage: Storage
+ val container: ParcelContainer
+ val locator: ParcelLocator
+ val blockManager: ParcelBlockManager
+ val globalPrivileges: GlobalPrivilegesManager
+
+ val creationTime: DateTime?
+}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
index b2d52a9..2ffef06 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
@@ -1,153 +1,154 @@
-package io.dico.parcels2
-
-import io.dico.dicore.Registrator
-import io.dico.dicore.command.EOverridePolicy
-import io.dico.dicore.command.ICommandDispatcher
-import io.dico.parcels2.command.getParcelCommands
-import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl
-import io.dico.parcels2.defaultimpl.ParcelProviderImpl
-import io.dico.parcels2.listener.ParcelEntityTracker
-import io.dico.parcels2.listener.ParcelListeners
-import io.dico.parcels2.listener.WorldEditListener
-import io.dico.parcels2.options.Options
-import io.dico.parcels2.options.optionsMapper
-import io.dico.parcels2.storage.Storage
-import io.dico.parcels2.util.MainThreadDispatcher
-import io.dico.parcels2.util.PluginScheduler
-import io.dico.parcels2.util.ext.tryCreate
-import io.dico.parcels2.util.isServerThread
-import kotlinx.coroutines.CoroutineScope
-import org.bukkit.Bukkit
-import org.bukkit.generator.ChunkGenerator
-import org.bukkit.plugin.Plugin
-import org.bukkit.plugin.java.JavaPlugin
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import java.io.File
-import kotlin.coroutines.CoroutineContext
-
-val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
-private inline val plogger get() = logger
-
-class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
- lateinit var optionsFile: File; private set
- lateinit var options: Options; private set
- lateinit var parcelProvider: ParcelProvider; private set
- lateinit var storage: Storage; private set
- lateinit var globalPrivileges: GlobalPrivilegesManager; private set
-
- val registrator = Registrator(this)
- lateinit var entityTracker: ParcelEntityTracker; private set
- private var listeners: ParcelListeners? = null
- private var cmdDispatcher: ICommandDispatcher? = null
-
- override val coroutineContext: CoroutineContext = MainThreadDispatcher(this)
- override val plugin: Plugin get() = this
- val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, options.tickJobtime) }
-
- override fun onEnable() {
- plogger.info("Is server thread: ${isServerThread()}")
- plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
- plogger.debug(System.getProperty("user.dir"))
- if (!init()) {
- Bukkit.getPluginManager().disablePlugin(this)
- }
- }
-
- override fun onDisable() {
- val hasWorkers = jobDispatcher.jobs.isNotEmpty()
- if (hasWorkers) {
- plogger.warn("Parcels is attempting to complete all ${jobDispatcher.jobs.size} remaining jobs before shutdown...")
- }
- jobDispatcher.completeAllTasks()
- if (hasWorkers) {
- plogger.info("Parcels has completed the remaining jobs.")
- }
-
- cmdDispatcher?.unregisterFromCommandMap()
- }
-
- private fun init(): Boolean {
- optionsFile = File(dataFolder, "options.yml")
- options = Options()
- parcelProvider = ParcelProviderImpl(this)
-
- try {
- if (!loadOptions()) return false
-
- try {
- storage = options.storage.newInstance()
- storage.init()
- } catch (ex: Exception) {
- plogger.error("Failed to connect to database", ex)
- return false
- }
-
- globalPrivileges = GlobalPrivilegesManagerImpl(this)
- entityTracker = ParcelEntityTracker(parcelProvider)
- } catch (ex: Exception) {
- plogger.error("Error loading options", ex)
- return false
- }
-
- registerListeners()
- registerCommands()
-
- parcelProvider.loadWorlds()
- return true
- }
-
- fun loadOptions(): Boolean {
- when {
- optionsFile.exists() -> optionsMapper.readerForUpdating(options).readValue<Options>(optionsFile)
- else -> run {
- options.addWorld("parcels")
- if (saveOptions()) {
- plogger.warn("Created options file with a world template. Please review it before next start.")
- } else {
- plogger.error("Failed to save options file ${optionsFile.canonicalPath}")
- }
- return false
- }
- }
- return true
- }
-
- fun saveOptions(): Boolean {
- if (optionsFile.tryCreate()) {
- try {
- optionsMapper.writeValue(optionsFile, options)
- } catch (ex: Throwable) {
- optionsFile.delete()
- throw ex
- }
- return true
- }
- return false
- }
-
- override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? {
- return parcelProvider.getWorldGenerator(worldName)
- }
-
- private fun registerCommands() {
- cmdDispatcher = getParcelCommands(this).apply {
- registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY)
- }
- }
-
- private fun registerListeners() {
- if (listeners == null) {
- listeners = ParcelListeners(parcelProvider, entityTracker, storage)
- registrator.registerListeners(listeners!!)
-
- val worldEditPlugin = server.pluginManager.getPlugin("WorldEdit")
- if (worldEditPlugin != null) {
- WorldEditListener.register(this, worldEditPlugin)
- }
- }
-
- scheduleRepeating(100, 5, entityTracker::tick)
- }
-
+package io.dico.parcels2
+
+import io.dico.dicore.Registrator
+import io.dico.dicore.command.EOverridePolicy
+import io.dico.dicore.command.ICommandDispatcher
+import io.dico.parcels2.command.getParcelCommands
+import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl
+import io.dico.parcels2.defaultimpl.ParcelProviderImpl
+import io.dico.parcels2.listener.ParcelEntityTracker
+import io.dico.parcels2.listener.ParcelListeners
+import io.dico.parcels2.listener.WorldEditListener
+import io.dico.parcels2.options.Options
+import io.dico.parcels2.options.optionsMapper
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.util.MainThreadDispatcher
+import io.dico.parcels2.util.PluginAware
+import io.dico.parcels2.util.ext.tryCreate
+import io.dico.parcels2.util.isServerThread
+import io.dico.parcels2.util.scheduleRepeating
+import kotlinx.coroutines.CoroutineScope
+import org.bukkit.Bukkit
+import org.bukkit.generator.ChunkGenerator
+import org.bukkit.plugin.Plugin
+import org.bukkit.plugin.java.JavaPlugin
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.File
+import kotlin.coroutines.CoroutineContext
+
+val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
+private inline val plogger get() = logger
+
+class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginAware {
+ lateinit var optionsFile: File; private set
+ lateinit var options: Options; private set
+ lateinit var parcelProvider: ParcelProvider; private set
+ lateinit var storage: Storage; private set
+ lateinit var globalPrivileges: GlobalPrivilegesManager; private set
+
+ val registrator = Registrator(this)
+ lateinit var entityTracker: ParcelEntityTracker; private set
+ private var listeners: ParcelListeners? = null
+ private var cmdDispatcher: ICommandDispatcher? = null
+
+ override val coroutineContext: CoroutineContext = MainThreadDispatcher(this)
+ override val plugin: Plugin get() = this
+ val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, this, options.tickJobtime) }
+
+ override fun onEnable() {
+ plogger.info("Is server thread: ${isServerThread()}")
+ plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
+ plogger.debug(System.getProperty("user.dir"))
+ if (!init()) {
+ Bukkit.getPluginManager().disablePlugin(this)
+ }
+ }
+
+ override fun onDisable() {
+ val hasWorkers = jobDispatcher.jobs.isNotEmpty()
+ if (hasWorkers) {
+ plogger.warn("Parcels is attempting to complete all ${jobDispatcher.jobs.size} remaining jobs before shutdown...")
+ }
+ jobDispatcher.completeAllTasks()
+ if (hasWorkers) {
+ plogger.info("Parcels has completed the remaining jobs.")
+ }
+
+ cmdDispatcher?.unregisterFromCommandMap()
+ }
+
+ private fun init(): Boolean {
+ optionsFile = File(dataFolder, "options.yml")
+ options = Options()
+ parcelProvider = ParcelProviderImpl(this)
+
+ try {
+ if (!loadOptions()) return false
+
+ try {
+ storage = options.storage.newInstance()
+ storage.init()
+ } catch (ex: Exception) {
+ plogger.error("Failed to connect to database", ex)
+ return false
+ }
+
+ globalPrivileges = GlobalPrivilegesManagerImpl(this)
+ entityTracker = ParcelEntityTracker(parcelProvider)
+ } catch (ex: Exception) {
+ plogger.error("Error loading options", ex)
+ return false
+ }
+
+ registerListeners()
+ registerCommands()
+
+ parcelProvider.loadWorlds()
+ return true
+ }
+
+ fun loadOptions(): Boolean {
+ when {
+ optionsFile.exists() -> optionsMapper.readerForUpdating(options).readValue<Options>(optionsFile)
+ else -> run {
+ options.addWorld("parcels")
+ if (saveOptions()) {
+ plogger.warn("Created options file with a world template. Please review it before next start.")
+ } else {
+ plogger.error("Failed to save options file ${optionsFile.canonicalPath}")
+ }
+ return false
+ }
+ }
+ return true
+ }
+
+ fun saveOptions(): Boolean {
+ if (optionsFile.tryCreate()) {
+ try {
+ optionsMapper.writeValue(optionsFile, options)
+ } catch (ex: Throwable) {
+ optionsFile.delete()
+ throw ex
+ }
+ return true
+ }
+ return false
+ }
+
+ override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? {
+ return parcelProvider.getWorldGenerator(worldName)
+ }
+
+ private fun registerCommands() {
+ cmdDispatcher = getParcelCommands(this).apply {
+ registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY)
+ }
+ }
+
+ private fun registerListeners() {
+ if (listeners == null) {
+ listeners = ParcelListeners(parcelProvider, entityTracker, storage)
+ registrator.registerListeners(listeners!!)
+
+ val worldEditPlugin = server.pluginManager.getPlugin("WorldEdit")
+ if (worldEditPlugin != null) {
+ WorldEditListener.register(this, worldEditPlugin)
+ }
+ }
+
+ scheduleRepeating(5, delay = 100, task = entityTracker::tick)
+ }
+
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt b/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt
index 6c30c27..b73f7ba 100644
--- a/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt
+++ b/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt
@@ -1,184 +1,208 @@
-@file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION")
-
-package io.dico.parcels2
-
-import io.dico.parcels2.storage.Storage
-import io.dico.parcels2.util.ext.PLAYER_NAME_PLACEHOLDER
-import io.dico.parcels2.util.ext.isValid
-import io.dico.parcels2.util.ext.uuid
-import io.dico.parcels2.util.getOfflinePlayer
-import io.dico.parcels2.util.getPlayerName
-import org.bukkit.Bukkit
-import org.bukkit.OfflinePlayer
-import java.util.UUID
-
-interface PlayerProfile {
- val uuid: UUID? get() = null
- val name: String?
- val nameOrBukkitName: String?
- val notNullName: String
- val isStar: Boolean get() = this is Star
- val exists: Boolean get() = this is RealImpl
-
- fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean
-
- fun equals(other: PlayerProfile): Boolean
-
- override fun equals(other: Any?): Boolean
- override fun hashCode(): Int
-
- val isFake: Boolean get() = this is Fake
- val isReal: Boolean get() = this is Real
-
- companion object {
- fun safe(uuid: UUID?, name: String?): PlayerProfile? {
- if (uuid != null) return Real(uuid, name)
- if (name != null) return invoke(name)
- return null
- }
-
- operator fun invoke(uuid: UUID?, name: String?): PlayerProfile {
- return safe(uuid, name) ?: throw IllegalArgumentException("One of uuid and name must not be null")
- }
-
- operator fun invoke(uuid: UUID): Real {
- if (uuid == Star.uuid) return Star
- return RealImpl(uuid, null)
- }
-
- operator fun invoke(name: String): PlayerProfile {
- if (name == Star.name) return Star
- return Fake(name)
- }
-
- operator fun invoke(player: OfflinePlayer): PlayerProfile {
- return if (player.isValid) Real(player.uuid, player.name) else Fake(player.name)
- }
-
- fun nameless(player: OfflinePlayer): Real {
- if (!player.isValid) throw IllegalArgumentException("The given OfflinePlayer is not valid")
- return RealImpl(player.uuid, null)
- }
-
- fun byName(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile {
- if (!allowReal) {
- if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true")
- return Fake(input)
- }
-
- if (input == Star.name) return Star
-
- return getOfflinePlayer(input)?.let { PlayerProfile(it) } ?: Unresolved(input)
- }
- }
-
- interface Real : PlayerProfile {
- override val uuid: UUID
- override val nameOrBukkitName: String?
- // If a player is online, their name is prioritized to get name changes right immediately
- get() = Bukkit.getPlayer(uuid)?.name ?: name ?: getPlayerName(uuid)
- override val notNullName: String
- get() = nameOrBukkitName ?: PLAYER_NAME_PLACEHOLDER
-
- val player: OfflinePlayer? get() = getOfflinePlayer(uuid)
-
- override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
- return uuid == player.uuid || (allowNameMatch && name?.let { it == player.name } == true)
- }
-
- override fun equals(other: PlayerProfile): Boolean {
- return other is Real && uuid == other.uuid
- }
-
- companion object {
- fun byName(name: String): PlayerProfile {
- if (name == Star.name) return Star
- return Unresolved(name)
- }
-
- operator fun invoke(uuid: UUID, name: String?): Real {
- if (name == Star.name || uuid == Star.uuid) return Star
- return RealImpl(uuid, name)
- }
-
- fun safe(uuid: UUID?, name: String?): Real? {
- if (name == Star.name || uuid == Star.uuid) return Star
- if (uuid == null) return null
- return RealImpl(uuid, name)
- }
-
- }
- }
-
- object Star : BaseImpl(), Real {
- override val name get() = "*"
- override val nameOrBukkitName get() = name
- override val notNullName get() = name
-
- // hopefully nobody will have this random UUID :)
- override val uuid: UUID = UUID.fromString("7d09c4c6-117d-4f36-9778-c4d24618cee1")
-
- override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
- return true
- }
-
- override fun toString() = "Star"
- }
-
- abstract class NameOnly(override val name: String) : BaseImpl() {
- override val notNullName get() = name
- override val nameOrBukkitName: String get() = name
-
- override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
- return allowNameMatch && player.name == name
- }
-
- override fun toString() = "${javaClass.simpleName}($name)"
- }
-
- class Fake(name: String) : NameOnly(name) {
- override fun equals(other: PlayerProfile): Boolean {
- return other is Fake && other.name == name
- }
- }
-
- class Unresolved(name: String) : NameOnly(name) {
- override fun equals(other: PlayerProfile): Boolean {
- return other is Unresolved && name == other.name
- }
-
- suspend fun tryResolveSuspendedly(storage: Storage): Real? {
- return storage.getPlayerUuidForName(name).await()?.let { resolve(it) }
- }
-
- fun resolve(uuid: UUID): Real {
- return RealImpl(uuid, name)
- }
-
- fun throwException(): Nothing {
- throw IllegalArgumentException("A UUID for the player $name can not be found")
- }
- }
-
- abstract class BaseImpl : PlayerProfile {
- override fun equals(other: Any?): Boolean {
- return this === other || (other is PlayerProfile && equals(other))
- }
-
- override fun hashCode(): Int {
- return uuid?.hashCode() ?: name!!.hashCode()
- }
- }
-
- private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real {
- override fun toString() = "Real($notNullName)"
- }
-
-}
-
-suspend fun PlayerProfile.resolved(storage: Storage, resolveToFake: Boolean = false): PlayerProfile? =
- when (this) {
- is PlayerProfile.Unresolved -> tryResolveSuspendedly(storage)
- ?: if (resolveToFake) PlayerProfile.Fake(name) else null
- else -> this
+@file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION")
+
+package io.dico.parcels2
+
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.util.checkPlayerNameValid
+import io.dico.parcels2.util.ext.PLAYER_NAME_PLACEHOLDER
+import io.dico.parcels2.util.ext.isValid
+import io.dico.parcels2.util.ext.uuid
+import io.dico.parcels2.util.getOfflinePlayer
+import io.dico.parcels2.util.getPlayerName
+import io.dico.parcels2.util.isPlayerNameValid
+import org.bukkit.Bukkit
+import org.bukkit.OfflinePlayer
+import java.util.UUID
+
+interface PlayerProfile {
+ val uuid: UUID? get() = null
+ val name: String?
+ val nameOrBukkitName: String?
+ val notNullName: String
+ val isStar: Boolean get() = this is Star
+ val exists: Boolean get() = this is RealImpl
+
+ fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean
+
+ fun equals(other: PlayerProfile): Boolean
+
+ override fun equals(other: Any?): Boolean
+ override fun hashCode(): Int
+
+ val isFake: Boolean get() = this is Fake
+ val isReal: Boolean get() = this is Real
+
+ companion object {
+ fun safe(uuid: UUID?, name: String?): PlayerProfile? {
+ if (uuid != null) return Real(uuid, if (name != null && !isPlayerNameValid(name)) null else name)
+ if (name != null) return invoke(name)
+ return null
+ }
+
+ operator fun invoke(uuid: UUID?, name: String?): PlayerProfile {
+ return safe(uuid, name) ?: throw IllegalArgumentException("One of uuid and name must not be null")
+ }
+
+ operator fun invoke(uuid: UUID): Real {
+ if (uuid == Star.uuid) return Star
+ return RealImpl(uuid, null)
+ }
+
+ operator fun invoke(name: String): PlayerProfile {
+ if (name equalsIgnoreCase Star.name) return Star
+ return Fake(name)
+ }
+
+ operator fun invoke(player: OfflinePlayer): PlayerProfile {
+ return if (player.isValid) Real(player.uuid, player.name) else Fake(player.name)
+ }
+
+ fun nameless(player: OfflinePlayer): Real {
+ if (!player.isValid) throw IllegalArgumentException("The given OfflinePlayer is not valid")
+ return RealImpl(player.uuid, null)
+ }
+
+ fun byName(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile? {
+ if (!allowReal) {
+ if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true")
+ return Fake(input)
+ }
+
+ if (!isPlayerNameValid(input)) {
+ if (!allowFake) return null
+ return Fake(input)
+ }
+
+ if (input == Star.name) return Star
+
+ return getOfflinePlayer(input)?.let { PlayerProfile(it) } ?: Unresolved(input)
+ }
+ }
+
+ interface Real : PlayerProfile {
+ override val uuid: UUID
+ override val nameOrBukkitName: String?
+ // If a player is online, their name is prioritized to get name changes right immediately
+ get() = Bukkit.getPlayer(uuid)?.name ?: name ?: getPlayerName(uuid)
+ override val notNullName: String
+ get() = nameOrBukkitName ?: PLAYER_NAME_PLACEHOLDER
+
+ val player: OfflinePlayer? get() = getOfflinePlayer(uuid)
+
+ override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
+ return uuid == player.uuid || (allowNameMatch && name?.let { it == player.name } == true)
+ }
+
+ override fun equals(other: PlayerProfile): Boolean {
+ return other is Real && uuid == other.uuid
+ }
+
+ companion object {
+ fun byName(name: String): PlayerProfile {
+ if (name equalsIgnoreCase Star.name) return Star
+ return Unresolved(name)
+ }
+
+ operator fun invoke(uuid: UUID, name: String?): Real {
+ if (name equalsIgnoreCase Star.name || uuid == Star.uuid) return Star
+ return RealImpl(uuid, name)
+ }
+
+ fun safe(uuid: UUID?, name: String?): Real? {
+ if (name equalsIgnoreCase Star.name || uuid == Star.uuid) return Star
+ if (uuid == null) return null
+ return RealImpl(uuid, if (name != null && !isPlayerNameValid(name)) null else name)
+ }
+
+ }
+ }
+
+ object Star : BaseImpl(), Real {
+ override val name get() = "*"
+ override val nameOrBukkitName get() = name
+ override val notNullName get() = name
+
+ // hopefully nobody will have this random UUID :)
+ override val uuid: UUID = UUID.fromString("7d09c4c6-117d-4f36-9778-c4d24618cee1")
+
+ override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
+ return true
+ }
+
+ override fun toString() = "Star"
+ }
+
+ abstract class NameOnly(override val name: String) : BaseImpl() {
+ override val notNullName get() = name
+ override val nameOrBukkitName: String get() = name
+
+ override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
+ return allowNameMatch && player.name equalsIgnoreCase name
+ }
+
+ override fun toString() = "${javaClass.simpleName}($name)"
+ }
+
+ class Fake(name: String) : NameOnly(name) {
+ override fun equals(other: PlayerProfile): Boolean {
+ return other is Fake && other.name equalsIgnoreCase name
+ }
+ }
+
+ class Unresolved(name: String) : NameOnly(name) {
+ init {
+ checkPlayerNameValid(name)
+ }
+
+ override fun equals(other: PlayerProfile): Boolean {
+ return other is Unresolved && name equalsIgnoreCase other.name
+ }
+
+ suspend fun tryResolveSuspendedly(storage: Storage): Real? {
+ return storage.getPlayerUuidForName(name).await()?.let { resolve(it) }
+ }
+
+ fun resolve(uuid: UUID): Real {
+ return RealImpl(uuid, name)
+ }
+
+ fun throwException(): Nothing {
+ throw IllegalArgumentException("A UUID for the player $name can not be found")
+ }
+ }
+
+ abstract class BaseImpl : PlayerProfile {
+ override fun equals(other: Any?): Boolean {
+ return this === other || (other is PlayerProfile && equals(other))
+ }
+
+ override fun hashCode(): Int {
+ return uuid?.hashCode() ?: name!!.hashCode()
+ }
+ }
+
+ private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real {
+ init {
+ name?.let { checkPlayerNameValid(it) }
+ }
+
+ override fun toString() = "Real($notNullName)"
+ }
+
+}
+
+private infix fun String?.equalsIgnoreCase(other: String): Boolean {
+ if (this == null) return false
+ if (length != other.length) return false
+ repeat(length) { i ->
+ if (this[i].toLowerCase() != other[i].toLowerCase()) return false
+ }
+ return true
+}
+
+suspend fun PlayerProfile.resolved(storage: Storage, resolveToFake: Boolean = false): PlayerProfile? =
+ when (this) {
+ is PlayerProfile.Unresolved -> tryResolveSuspendedly(storage)
+ ?: if (resolveToFake) PlayerProfile.Fake(name) else null
+ else -> this
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Entities.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Entities.kt
new file mode 100644
index 0000000..d9ea09f
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Entities.kt
@@ -0,0 +1,38 @@
+package io.dico.parcels2.blockvisitor
+
+import io.dico.parcels2.util.math.Vec3d
+import org.bukkit.Location
+import org.bukkit.World
+import org.bukkit.entity.Entity
+import org.bukkit.entity.Minecart
+
+/*
+open class EntityCopy<T : Entity>(entity: T) {
+ val type = entity.type
+
+ @Suppress("UNCHECKED_CAST")
+ fun spawn(world: World, position: Vec3d): T {
+ val entity = world.spawnEntity(Location(null, position.x, position.y, position.z), type) as T
+ setAttributes(entity)
+ return entity
+ }
+
+ open fun setAttributes(entity: T) {}
+}
+
+open class MinecartCopy<T : Minecart>(entity: T) : EntityCopy<T>(entity) {
+ val damage = entity.damage
+ val maxSpeed = entity.maxSpeed
+ val isSlowWhenEmpty = entity.isSlowWhenEmpty
+ val flyingVelocityMod = entity.flyingVelocityMod
+ val derailedVelocityMod = entity.derailedVelocityMod
+ val displayBlockData = entity.displayBlockData
+ val displayBlockOffset = entity.displayBlockOffset
+
+ override fun setAttributes(entity: T) {
+ super.setAttributes(entity)
+ entity.damage = damage
+ entity.displayBlockData = displayBlockData
+ entity.displayBlockOffset = displayBlockOffset
+ }
+}*/ \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt
index 730625e..1b20f72 100644
--- a/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/ParcelParameterTypes.kt
@@ -1,83 +1,90 @@
-package io.dico.parcels2.command
-
-import io.dico.dicore.command.CommandException
-import io.dico.dicore.command.parameter.ArgumentBuffer
-import io.dico.dicore.command.parameter.Parameter
-import io.dico.dicore.command.parameter.type.ParameterConfig
-import io.dico.dicore.command.parameter.type.ParameterType
-import io.dico.parcels2.*
-import io.dico.parcels2.command.ProfileKind.Companion.ANY
-import io.dico.parcels2.command.ProfileKind.Companion.FAKE
-import io.dico.parcels2.command.ProfileKind.Companion.REAL
-import org.bukkit.Location
-import org.bukkit.command.CommandSender
-import org.bukkit.entity.Player
-
-fun invalidInput(parameter: Parameter<*, *>, message: String): Nothing {
- throw CommandException("invalid input for ${parameter.name}: $message")
-}
-
-fun ParcelProvider.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld {
- val worldName = input
- ?.takeUnless { it.isEmpty() }
- ?: (sender as? Player)?.world?.name
- ?: invalidInput(parameter, "console cannot omit the world name")
-
- return getWorld(worldName)
- ?: invalidInput(parameter, "$worldName is not a parcel world")
-}
-
-class ParcelParameterType(val parcelProvider: ParcelProvider) : ParameterType<Parcel, Void>(Parcel::class.java) {
- val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)")
-
- override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel {
- val matchResult = regex.matchEntire(buffer.next()!!)
- ?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)")
-
- val world = parcelProvider.getTargetWorld(matchResult.groupValues[2], sender, parameter)
-
- val x = matchResult.groupValues[3].toIntOrNull()
- ?: invalidInput(parameter, "couldn't parse int")
-
- val z = matchResult.groupValues[4].toIntOrNull()
- ?: invalidInput(parameter, "couldn't parse int")
-
- return world.getParcelById(x, z)
- ?: invalidInput(parameter, "parcel id is out of range")
- }
-
-}
-
-annotation class ProfileKind(val kind: Int) {
- companion object : ParameterConfig<ProfileKind, Int>(ProfileKind::class.java) {
- const val REAL = 1
- const val FAKE = 2
- const val ANY = REAL or FAKE
-
- override fun toParameterInfo(annotation: ProfileKind): Int {
- return annotation.kind
- }
- }
-}
-
-class ProfileParameterType : ParameterType<PlayerProfile, Int>(PlayerProfile::class.java, ProfileKind) {
-
- override fun parse(parameter: Parameter<PlayerProfile, Int>, sender: CommandSender, buffer: ArgumentBuffer): PlayerProfile {
- val info = parameter.paramInfo ?: REAL
- val allowReal = (info and REAL) != 0
- val allowFake = (info and FAKE) != 0
-
- val input = buffer.next()!!
- return PlayerProfile.byName(input, allowReal, allowFake)
- }
-
- override fun complete(
- parameter: Parameter<PlayerProfile, Int>,
- sender: CommandSender,
- location: Location?,
- buffer: ArgumentBuffer
- ): MutableList<String> {
- logger.info("Completing PlayerProfile: ${buffer.next()}")
- return super.complete(parameter, sender, location, buffer)
- }
-}
+package io.dico.parcels2.command
+
+import io.dico.dicore.command.CommandException
+import io.dico.dicore.command.parameter.ArgumentBuffer
+import io.dico.dicore.command.parameter.Parameter
+import io.dico.dicore.command.parameter.type.ParameterConfig
+import io.dico.dicore.command.parameter.type.ParameterType
+import io.dico.parcels2.*
+import io.dico.parcels2.command.ProfileKind.Companion.FAKE
+import io.dico.parcels2.command.ProfileKind.Companion.REAL
+import org.bukkit.Location
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Player
+
+fun invalidInput(parameter: Parameter<*, *>, message: String): Nothing {
+ throw CommandException("invalid input for ${parameter.name}: $message")
+}
+
+fun ParcelProvider.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld {
+ val worldName = input
+ ?.takeUnless { it.isEmpty() }
+ ?: (sender as? Player)?.world?.name
+ ?: invalidInput(parameter, "console cannot omit the world name")
+
+ return getWorld(worldName)
+ ?: invalidInput(parameter, "$worldName is not a parcel world")
+}
+
+class ParcelParameterType(val parcelProvider: ParcelProvider) : ParameterType<Parcel, Void>(Parcel::class.java) {
+ val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)")
+
+ override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel {
+ val matchResult = regex.matchEntire(buffer.next()!!)
+ ?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)")
+
+ val world = parcelProvider.getTargetWorld(matchResult.groupValues[2], sender, parameter)
+
+ val x = matchResult.groupValues[3].toIntOrNull()
+ ?: invalidInput(parameter, "couldn't parse int")
+
+ val z = matchResult.groupValues[4].toIntOrNull()
+ ?: invalidInput(parameter, "couldn't parse int")
+
+ return world.getParcelById(x, z)
+ ?: invalidInput(parameter, "parcel id is out of range")
+ }
+
+}
+
+annotation class ProfileKind(val kind: Int) {
+ companion object : ParameterConfig<ProfileKind, Int>(ProfileKind::class.java) {
+ const val REAL = 1
+ const val FAKE = 2
+ const val ANY = REAL or FAKE
+ const val ALLOW_INVALID = 4
+
+ override fun toParameterInfo(annotation: ProfileKind): Int {
+ return annotation.kind
+ }
+ }
+}
+
+class ProfileParameterType : ParameterType<PlayerProfile, Int>(PlayerProfile::class.java, ProfileKind) {
+
+ override fun parse(parameter: Parameter<PlayerProfile, Int>, sender: CommandSender, buffer: ArgumentBuffer): PlayerProfile? {
+ val info = parameter.paramInfo ?: REAL
+ val allowReal = (info and REAL) != 0
+ val allowFake = (info and FAKE) != 0
+
+ val input = buffer.next()!!
+
+ val profile = PlayerProfile.byName(input, allowReal, allowFake)
+
+ if (profile == null && (info and ProfileKind.ALLOW_INVALID) == 0) {
+ invalidInput(parameter, "\'$input\' is not a valid player name")
+ }
+
+ return profile
+ }
+
+ override fun complete(
+ parameter: Parameter<PlayerProfile, Int>,
+ sender: CommandSender,
+ location: Location?,
+ buffer: ArgumentBuffer
+ ): MutableList<String> {
+ logger.info("Completing PlayerProfile: ${buffer.next()}")
+ return super.complete(parameter, sender, location, buffer)
+ }
+}
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt
index c39c4b6..934a993 100644
--- a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt
@@ -1,191 +1,215 @@
-package io.dico.parcels2.command
-
-import io.dico.dicore.command.parameter.ArgumentBuffer
-import io.dico.dicore.command.parameter.Parameter
-import io.dico.dicore.command.parameter.type.ParameterConfig
-import io.dico.dicore.command.parameter.type.ParameterType
-import io.dico.parcels2.Parcel
-import io.dico.parcels2.ParcelProvider
-import io.dico.parcels2.ParcelWorld
-import io.dico.parcels2.PlayerProfile
-import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.DEFAULT_KIND
-import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.ID
-import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER
-import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_FAKE
-import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_REAL
-import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.PREFER_OWNED_FOR_DEFAULT
-import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.REAL
-import io.dico.parcels2.storage.Storage
-import io.dico.parcels2.util.math.Vec2i
-import io.dico.parcels2.util.math.floor
-import org.bukkit.command.CommandSender
-import org.bukkit.entity.Player
-
-sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDefault: Boolean) {
-
- abstract suspend fun getParcelSuspend(storage: Storage): Parcel?
-
- class ByID(world: ParcelWorld, val id: Vec2i?, parsedKind: Int, isDefault: Boolean) : ParcelTarget(world, parsedKind, isDefault) {
- override suspend fun getParcelSuspend(storage: Storage): Parcel? = getParcel()
- fun getParcel() = id?.let { world.getParcelById(it) }
- val isPath: Boolean get() = id == null
- }
-
- class ByOwner(
- world: ParcelWorld,
- owner: PlayerProfile,
- val index: Int,
- parsedKind: Int,
- isDefault: Boolean,
- val onResolveFailure: (() -> Unit)? = null
- ) : ParcelTarget(world, parsedKind, isDefault) {
- init {
- if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index")
- }
-
- var owner = owner; private set
-
- suspend fun resolveOwner(storage: Storage): Boolean {
- val owner = owner
- if (owner is PlayerProfile.Unresolved) {
- this.owner = owner.tryResolveSuspendedly(storage) ?: if (parsedKind and OWNER_FAKE != 0) PlayerProfile.Fake(owner.name)
- else run { onResolveFailure?.invoke(); return false }
- }
- return true
- }
-
- override suspend fun getParcelSuspend(storage: Storage): Parcel? {
- onResolveFailure?.let { resolveOwner(storage) }
-
- val ownedParcelsSerialized = storage.getOwnedParcels(owner).await()
- val ownedParcels = ownedParcelsSerialized
- .filter { it.worldId.equals(world.id) }
- .map { world.getParcelById(it.x, it.z) }
-
- return ownedParcels.getOrNull(index)
- }
- }
-
- annotation class TargetKind(val kind: Int) {
- companion object : ParameterConfig<TargetKind, Int>(TargetKind::class.java) {
- const val ID = 1 // ID
- const val OWNER_REAL = 2 // an owner backed by a UUID
- const val OWNER_FAKE = 4 // an owner not backed by a UUID
-
- const val OWNER = OWNER_REAL or OWNER_FAKE // any owner
- const val ANY = ID or OWNER_REAL or OWNER_FAKE // any
- const val REAL = ID or OWNER_REAL // no owner not backed by a UUID
-
- const val DEFAULT_KIND = REAL
-
- const val PREFER_OWNED_FOR_DEFAULT = 8 // if the kind can be ID and OWNER_REAL, prefer OWNER_REAL for default
- // instead of parcel that the player is in
-
- override fun toParameterInfo(annotation: TargetKind): Int {
- return annotation.kind
- }
- }
- }
-
- class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) :
- ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, TargetKind) {
-
- override fun parse(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget {
- var input = buffer.next()!!
- val worldString = input.substringBefore("/", missingDelimiterValue = "")
- input = input.substringAfter("/")
-
- val world = if (worldString.isEmpty()) {
- val player = requirePlayer(sender, parameter, "the world")
- parcelProvider.getWorld(player.world)
- ?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world")
- } else {
- parcelProvider.getWorld(worldString) ?: invalidInput(parameter, "$worldString is not a parcel world")
- }
-
- val kind = parameter.paramInfo ?: DEFAULT_KIND
- if (input.contains(',')) {
- if (kind and ID == 0) invalidInput(parameter, "You must specify a parcel by OWNER, that is, an owner and index")
- return ByID(world, getId(parameter, input), kind, false)
- }
-
- if (kind and OWNER == 0) invalidInput(parameter, "You must specify a parcel by ID, that is, the x and z component separated by a comma")
- val (owner, index) = getHomeIndex(parameter, kind, sender, input)
- return ByOwner(world, owner, index, kind, false, onResolveFailure = { invalidInput(parameter, "The player $input does not exist") })
- }
-
- private fun getId(parameter: Parameter<*, *>, input: String): Vec2i {
- val x = input.substringBefore(',').run {
- toIntOrNull() ?: invalidInput(parameter, "ID(x) must be an integer, $this is not an integer")
- }
- val z = input.substringAfter(',').run {
- toIntOrNull() ?: invalidInput(parameter, "ID(z) must be an integer, $this is not an integer")
- }
- return Vec2i(x, z)
- }
-
- private fun getHomeIndex(parameter: Parameter<*, *>, kind: Int, sender: CommandSender, input: String): Pair<PlayerProfile, Int> {
- val splitIdx = input.indexOf(':')
- val ownerString: String
- val index: Int?
-
- val speciallyParsedIndex = parcelAddress?.speciallyParsedIndex
-
- if (splitIdx == -1) {
-
- if (speciallyParsedIndex == null) {
- // just the index.
- index = input.toIntOrNull()
- ownerString = if (index == null) input else ""
- } else {
- // just the owner.
- index = speciallyParsedIndex
- ownerString = input
- }
-
- } else {
- if (speciallyParsedIndex != null) {
- invalidInput(parameter, "Duplicate home index")
- }
-
- ownerString = input.substring(0, splitIdx)
-
- val indexString = input.substring(splitIdx + 1)
- index = indexString.toIntOrNull()
- ?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
- }
-
- val owner = if (ownerString.isEmpty())
- PlayerProfile(requirePlayer(sender, parameter, "the player"))
- else
- PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0)
-
- return owner to (index ?: 0)
- }
-
- private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>, objName: String): Player {
- if (sender !is Player) invalidInput(parameter, "console cannot omit the $objName")
- return sender
- }
-
- override fun getDefaultValue(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget? {
- val kind = parameter.paramInfo ?: DEFAULT_KIND
- val useLocation = when {
- kind and REAL == REAL -> kind and PREFER_OWNED_FOR_DEFAULT == 0
- kind and ID != 0 -> true
- kind and OWNER_REAL != 0 -> false
- else -> return null
- }
-
- val player = requirePlayer(sender, parameter, "the parcel")
- val world = parcelProvider.getWorld(player.world) ?: invalidInput(parameter, "You must be in a parcel world to omit the parcel")
- if (useLocation) {
- val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos }
- return ByID(world, id, kind, true)
- }
-
- return ByOwner(world, PlayerProfile(player), parcelAddress?.speciallyParsedIndex ?: 0, kind, true)
- }
- }
-
-}
+package io.dico.parcels2.command
+
+import io.dico.dicore.command.parameter.ArgumentBuffer
+import io.dico.dicore.command.parameter.Parameter
+import io.dico.dicore.command.parameter.type.ParameterConfig
+import io.dico.dicore.command.parameter.type.ParameterType
+import io.dico.parcels2.Parcel
+import io.dico.parcels2.ParcelProvider
+import io.dico.parcels2.ParcelWorld
+import io.dico.parcels2.PlayerProfile
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.DEFAULT_KIND
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.ID
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_FAKE
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_REAL
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.PREFER_OWNED_FOR_DEFAULT
+import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.REAL
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.util.math.Vec2i
+import io.dico.parcels2.util.math.floor
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Player
+
+sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDefault: Boolean) {
+
+ abstract suspend fun getParcelSuspend(storage: Storage): Parcel?
+
+ class ByID(world: ParcelWorld, val id: Vec2i?, parsedKind: Int, isDefault: Boolean) :
+ ParcelTarget(world, parsedKind, isDefault) {
+ override suspend fun getParcelSuspend(storage: Storage): Parcel? = getParcel()
+ fun getParcel() = id?.let { world.getParcelById(it) }
+ val isPath: Boolean get() = id == null
+ }
+
+ class ByOwner(
+ world: ParcelWorld,
+ owner: PlayerProfile,
+ val index: Int,
+ parsedKind: Int,
+ isDefault: Boolean,
+ val onResolveFailure: (() -> Unit)? = null
+ ) : ParcelTarget(world, parsedKind, isDefault) {
+ init {
+ if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index")
+ }
+
+ var owner = owner; private set
+
+ suspend fun resolveOwner(storage: Storage): Boolean {
+ val owner = owner
+ if (owner is PlayerProfile.Unresolved) {
+ this.owner = owner.tryResolveSuspendedly(storage) ?: if (parsedKind and OWNER_FAKE != 0) PlayerProfile.Fake(owner.name)
+ else run { onResolveFailure?.invoke(); return false }
+ }
+ return true
+ }
+
+ override suspend fun getParcelSuspend(storage: Storage): Parcel? {
+ onResolveFailure?.let { resolveOwner(storage) }
+
+ val ownedParcelsSerialized = storage.getOwnedParcels(owner).await()
+ val ownedParcels = ownedParcelsSerialized
+ .filter { it.worldId.equals(world.id) }
+ .map { world.getParcelById(it.x, it.z) }
+
+ return ownedParcels.getOrNull(index)
+ }
+ }
+
+ annotation class TargetKind(val kind: Int) {
+ companion object : ParameterConfig<TargetKind, Int>(TargetKind::class.java) {
+ const val ID = 1 // ID
+ const val OWNER_REAL = 2 // an owner backed by a UUID
+ const val OWNER_FAKE = 4 // an owner not backed by a UUID
+
+ const val OWNER = OWNER_REAL or OWNER_FAKE // any owner
+ const val ANY = ID or OWNER_REAL or OWNER_FAKE // any
+ const val REAL = ID or OWNER_REAL // no owner not backed by a UUID
+
+ const val DEFAULT_KIND = REAL
+
+ const val PREFER_OWNED_FOR_DEFAULT =
+ 8 // if the kind can be ID and OWNER_REAL, prefer OWNER_REAL for default
+ // instead of parcel that the player is in
+
+ override fun toParameterInfo(annotation: TargetKind): Int {
+ return annotation.kind
+ }
+ }
+ }
+
+ class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) :
+ ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, TargetKind) {
+
+ override fun parse(
+ parameter: Parameter<ParcelTarget, Int>,
+ sender: CommandSender,
+ buffer: ArgumentBuffer
+ ): ParcelTarget {
+ var input = buffer.next()!!
+ val worldString = input.substringBefore("/", missingDelimiterValue = "")
+ input = input.substringAfter("/")
+
+ val world = if (worldString.isEmpty()) {
+ val player = requirePlayer(sender, parameter, "the world")
+ parcelProvider.getWorld(player.world)
+ ?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world")
+ } else {
+ parcelProvider.getWorld(worldString) ?: invalidInput(parameter, "$worldString is not a parcel world")
+ }
+
+ val kind = parameter.paramInfo ?: DEFAULT_KIND
+ if (input.contains(',')) {
+ if (kind and ID == 0) invalidInput(parameter,
+ "You must specify a parcel by OWNER, that is, an owner and index")
+ return ByID(world, getId(parameter, input), kind, false)
+ }
+
+ if (kind and OWNER == 0) invalidInput(parameter,
+ "You must specify a parcel by ID, that is, the x and z component separated by a comma")
+ val (owner, index) = getHomeIndex(parameter, kind, sender, input)
+ return ByOwner(world,
+ owner,
+ index,
+ kind,
+ false,
+ onResolveFailure = { invalidInput(parameter, "The player $input does not exist") })
+ }
+
+ private fun getId(parameter: Parameter<*, *>, input: String): Vec2i {
+ val x = input.substringBefore(',').run {
+ toIntOrNull() ?: invalidInput(parameter, "ID(x) must be an integer, $this is not an integer")
+ }
+ val z = input.substringAfter(',').run {
+ toIntOrNull() ?: invalidInput(parameter, "ID(z) must be an integer, $this is not an integer")
+ }
+ return Vec2i(x, z)
+ }
+
+ private fun getHomeIndex(
+ parameter: Parameter<*, *>,
+ kind: Int,
+ sender: CommandSender,
+ input: String
+ ): Pair<PlayerProfile, Int> {
+ val splitIdx = input.indexOf(':')
+ val ownerString: String
+ val index: Int?
+
+ val speciallyParsedIndex = parcelAddress?.speciallyParsedIndex
+
+ if (splitIdx == -1) {
+
+ if (speciallyParsedIndex == null) {
+ // just the index.
+ index = input.toIntOrNull()
+ ownerString = if (index == null) input else ""
+ } else {
+ // just the owner.
+ index = speciallyParsedIndex
+ ownerString = input
+ }
+
+ } else {
+ if (speciallyParsedIndex != null) {
+ invalidInput(parameter, "Duplicate home index")
+ }
+
+ ownerString = input.substring(0, splitIdx)
+
+ val indexString = input.substring(splitIdx + 1)
+ index = indexString.toIntOrNull()
+ ?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
+ }
+
+ val owner = (if (ownerString.isEmpty())
+ PlayerProfile(requirePlayer(sender, parameter, "the player"))
+ else
+ PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0))
+ ?: invalidInput(parameter, "\'$ownerString\' is not a valid player name")
+
+ return owner to (index ?: 0)
+ }
+
+ private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>, objName: String): Player {
+ if (sender !is Player) invalidInput(parameter, "console cannot omit the $objName")
+ return sender
+ }
+
+ override fun getDefaultValue(
+ parameter: Parameter<ParcelTarget, Int>,
+ sender: CommandSender,
+ buffer: ArgumentBuffer
+ ): ParcelTarget? {
+ val kind = parameter.paramInfo ?: DEFAULT_KIND
+ val useLocation = when {
+ kind and REAL == REAL -> kind and PREFER_OWNED_FOR_DEFAULT == 0
+ kind and ID != 0 -> true
+ kind and OWNER_REAL != 0 -> false
+ else -> return null
+ }
+
+ val player = requirePlayer(sender, parameter, "the parcel")
+ val world = parcelProvider.getWorld(player.world) ?: invalidInput(parameter,
+ "You must be in a parcel world to omit the parcel")
+ if (useLocation) {
+ val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos }
+ return ByID(world, id, kind, true)
+ }
+
+ return ByOwner(world, PlayerProfile(player), parcelAddress?.speciallyParsedIndex ?: 0, kind, true)
+ }
+ }
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt
index caa3f1f..73b6b4d 100644
--- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt
@@ -1,378 +1,391 @@
-package io.dico.parcels2.defaultimpl
-
-import io.dico.parcels2.*
-import io.dico.parcels2.blockvisitor.RegionTraverser
-import io.dico.parcels2.options.DefaultGeneratorOptions
-import io.dico.parcels2.util.math.*
-import kotlinx.coroutines.CoroutineScope
-import org.bukkit.*
-import org.bukkit.block.Biome
-import org.bukkit.block.BlockFace
-import org.bukkit.block.Skull
-import org.bukkit.block.data.type.Slab
-import org.bukkit.block.data.type.WallSign
-import java.util.Random
-
-private val airType = Bukkit.createBlockData(Material.AIR)
-
-private const val chunkSize = 16
-
-class DefaultParcelGenerator(
- override val worldName: String,
- private val o: DefaultGeneratorOptions
-) : ParcelGenerator() {
- private var _world: World? = null
- override val world: World
- get() {
- if (_world == null) {
- val world = Bukkit.getWorld(worldName)
- maxHeight = world.maxHeight
- _world = world
- return world
- }
- return _world!!
- }
-
- private var maxHeight = 0
- val sectionSize = o.parcelSize + o.pathSize
- val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2
- val makePathMain = o.pathSize > 2
- val makePathAlt = o.pathSize > 4
-
- private inline fun <T> generate(
- chunkX: Int,
- chunkZ: Int,
- floor: T, wall:
- T, pathMain: T,
- pathAlt: T,
- fill: T,
- setter: (Int, Int, Int, T) -> Unit
- ) {
-
- val floorHeight = o.floorHeight
- val parcelSize = o.parcelSize
- val sectionSize = sectionSize
- val pathOffset = pathOffset
- val makePathMain = makePathMain
- val makePathAlt = makePathAlt
-
- // parcel bottom x and z
- // umod is unsigned %: the result is always >= 0
- val pbx = ((chunkX shl 4) - o.offsetX) umod sectionSize
- val pbz = ((chunkZ shl 4) - o.offsetZ) umod sectionSize
-
- var curHeight: Int
- var x: Int
- var z: Int
- for (cx in 0..15) {
- for (cz in 0..15) {
- x = (pbx + cx) % sectionSize - pathOffset
- z = (pbz + cz) % sectionSize - pathOffset
- curHeight = floorHeight
-
- val type = when {
- (x in 0 until parcelSize && z in 0 until parcelSize) -> floor
- (x in -1..parcelSize && z in -1..parcelSize) -> {
- curHeight++
- wall
- }
- (makePathAlt && x in -2 until parcelSize + 2 && z in -2 until parcelSize + 2) -> pathAlt
- (makePathMain) -> pathMain
- else -> {
- curHeight++
- wall
- }
- }
-
- for (y in 0 until curHeight) {
- setter(cx, y, cz, fill)
- }
- setter(cx, curHeight, cz, type)
- }
- }
- }
-
- override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData {
- val out = Bukkit.createChunkData(world)
- generate(chunkX, chunkZ, o.floorType, o.wallType, o.pathMainType, o.pathAltType, o.fillType) { x, y, z, type ->
- out.setBlock(x, y, z, type)
- }
- return out
- }
-
- override fun populate(world: World?, random: Random?, chunk: Chunk?) {
- // do nothing
- }
-
- override fun getFixedSpawnLocation(world: World?, random: Random?): Location {
- val fix = if (o.parcelSize.even) 0.5 else 0.0
- return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix)
- }
-
- override fun makeParcelLocatorAndBlockManager(
- parcelProvider: ParcelProvider,
- container: ParcelContainer,
- coroutineScope: CoroutineScope,
- jobDispatcher: JobDispatcher
- ): Pair<ParcelLocator, ParcelBlockManager> {
- val impl = ParcelLocatorAndBlockManagerImpl(parcelProvider, container, coroutineScope, jobDispatcher)
- return impl to impl
- }
-
- private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
- val sectionSize = sectionSize
- val parcelSize = o.parcelSize
- val absX = x - o.offsetX - pathOffset
- val absZ = z - o.offsetZ - pathOffset
- val modX = absX umod sectionSize
- val modZ = absZ umod sectionSize
- if (modX in 0 until parcelSize && modZ in 0 until parcelSize) {
- return mapper((absX - modX) / sectionSize + 1, (absZ - modZ) / sectionSize + 1)
- }
- return null
- }
-
- @Suppress("DEPRECATION")
- private inner class ParcelLocatorAndBlockManagerImpl(
- val parcelProvider: ParcelProvider,
- val container: ParcelContainer,
- coroutineScope: CoroutineScope,
- override val jobDispatcher: JobDispatcher
- ) : ParcelBlockManagerBase(), ParcelLocator, CoroutineScope by coroutineScope {
-
- override val world: World get() = this@DefaultParcelGenerator.world
- val worldId = parcelProvider.getWorld(world)?.id ?: ParcelWorldId(world)
- override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight)
-
- private val cornerWallType = when {
- o.wallType is Slab -> (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE }
- o.wallType.material.name.endsWith("CARPET") -> {
- Bukkit.createBlockData(Material.getMaterial(o.wallType.material.name.substringBefore("CARPET") + "WOOL"))
- }
- else -> null
- }
-
- override fun getParcelAt(x: Int, z: Int): Parcel? {
- return convertBlockLocationToId(x, z, container::getParcelById)
- }
-
- override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
- return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(worldId, idx, idz) }
- }
-
-
- private fun checkParcelId(parcel: ParcelId): ParcelId {
- if (!parcel.worldId.equals(worldId)) {
- throw IllegalArgumentException()
- }
- return parcel
- }
-
- override fun getRegionOrigin(parcel: ParcelId): Vec2i {
- checkParcelId(parcel)
- return Vec2i(
- sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,
- sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ
- )
- }
-
- override fun getRegion(parcel: ParcelId): Region {
- val origin = getRegionOrigin(parcel)
- return Region(
- Vec3i(origin.x, 0, origin.z),
- Vec3i(o.parcelSize, maxHeight, o.parcelSize)
- )
- }
-
- override fun getHomeLocation(parcel: ParcelId): Location {
- val origin = getRegionOrigin(parcel)
- val x = origin.x + (o.parcelSize - 1) / 2.0
- val z = origin.z - 2
- return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F)
- }
-
- override fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? {
- if (block.y != o.floorHeight + 1) return null
-
- val expectedParcelOrigin = when (type) {
- Material.WALL_SIGN -> Vec2i(block.x + 1, block.z + 2)
- o.wallType.material, cornerWallType?.material -> {
- if (face != BlockFace.NORTH || world[block + Vec3i.convert(BlockFace.NORTH)].type == Material.WALL_SIGN) {
- return null
- }
-
- Vec2i(block.x + 1, block.z + 1)
- }
- else -> return null
- }
-
- return getParcelAt(expectedParcelOrigin.x, expectedParcelOrigin.z)
- ?.takeIf { expectedParcelOrigin == getRegionOrigin(it.id) }
- ?.also { parcel ->
- if (type != Material.WALL_SIGN && parcel.owner != null) {
- updateParcelInfo(parcel.id, parcel.owner)
- parcel.isOwnerSignOutdated = false
- }
- }
- }
-
- override fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean {
- val wallBlockChunk = getRegionOrigin(parcel).add(-1, -1).toChunk()
- return world.isChunkLoaded(wallBlockChunk.x, wallBlockChunk.z)
- }
-
- override fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?) {
- val b = getRegionOrigin(parcel)
-
- val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
- val signBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 2)
- val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1)
-
- if (owner == null) {
- wallBlock.blockData = o.wallType
- signBlock.type = Material.AIR
- skullBlock.type = Material.AIR
-
- } else {
- cornerWallType?.let { wallBlock.blockData = it }
- signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as WallSign).apply { facing = BlockFace.NORTH }
-
- val sign = signBlock.state as org.bukkit.block.Sign
- sign.setLine(0, "${parcel.x},${parcel.z}")
- sign.setLine(2, owner.name ?: "")
- sign.update()
-
- skullBlock.type = Material.AIR
- skullBlock.type = Material.PLAYER_HEAD
- val skull = skullBlock.state as Skull
- if (owner is PlayerProfile.Real) {
- skull.owningPlayer = Bukkit.getOfflinePlayer(owner.uuid)
-
- } else if (!skull.setOwner(owner.name)) {
- skullBlock.type = Material.AIR
- return
- }
-
- skull.rotation = BlockFace.SOUTH
- skull.update()
- }
- }
-
- private fun trySubmitBlockVisitor(vararg parcels: ParcelId, function: JobFunction): Job? {
- parcels.forEach { checkParcelId(it) }
- return parcelProvider.trySubmitBlockVisitor(Permit(), parcels, function)
- }
-
- override fun setBiome(parcel: ParcelId, biome: Biome) = trySubmitBlockVisitor(checkParcelId(parcel)) {
- val world = world
- val b = getRegionOrigin(parcel)
- val parcelSize = o.parcelSize
- for (x in b.x until b.x + parcelSize) {
- for (z in b.z until b.z + parcelSize) {
- markSuspensionPoint()
- world.setBiome(x, z, biome)
- }
- }
- }
-
- override fun clearParcel(parcel: ParcelId) = trySubmitBlockVisitor(checkParcelId(parcel)) {
- val region = getRegion(parcel)
- val blocks = parcelTraverser.traverseRegion(region)
- val blockCount = region.blockCount.toDouble()
- val world = world
- val floorHeight = o.floorHeight
- val airType = airType
- val floorType = o.floorType
- val fillType = o.fillType
-
- for ((index, vec) in blocks.withIndex()) {
- markSuspensionPoint()
- val y = vec.y
- val blockType = when {
- y > floorHeight -> airType
- y == floorHeight -> floorType
- else -> fillType
- }
- world[vec].blockData = blockType
- setProgress((index + 1) / blockCount)
- }
- }
-
- override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
- /*
- * Get the offsets for the world out of the way
- * to simplify the calculation that follows.
- */
-
- val x = chunk.x.shl(4) - (o.offsetX + pathOffset)
- val z = chunk.z.shl(4) - (o.offsetZ + pathOffset)
-
- /* Locations of wall corners (where owner blocks are placed) are defined as:
- *
- * x umod sectionSize == sectionSize-1
- *
- * This check needs to be made for all 16 slices of the chunk in 2 dimensions
- * How to optimize this?
- * Let's take the expression
- *
- * x umod sectionSize
- *
- * And call it modX
- * x can be shifted (chunkSize -1) times to attempt to get a modX of 0.
- * This means that if the modX is 1, and sectionSize == (chunkSize-1), there would be a match at the last shift.
- * To check that there are any matches, we can see if the following holds:
- *
- * modX >= ((sectionSize-1) - (chunkSize-1))
- *
- * Which can be simplified to:
- * modX >= sectionSize - chunkSize
- *
- * if sectionSize == chunkSize, this expression can be simplified to
- * modX >= 0
- * which is always true. This is expected.
- * To get the total number of matches on a dimension, we can evaluate the following:
- *
- * (modX - (sectionSize - chunkSize) + sectionSize) / sectionSize
- *
- * We add sectionSize to the lhs because, if the other part of the lhs is 0, we need at least 1.
- * This can be simplified to:
- *
- * (modX + chunkSize) / sectionSize
- */
-
- val sectionSize = sectionSize
-
- val modX = x umod sectionSize
- val matchesOnDimensionX = (modX + chunkSize) / sectionSize
- if (matchesOnDimensionX <= 0) return emptyList()
-
- val modZ = z umod sectionSize
- val matchesOnDimensionZ = (modZ + chunkSize) / sectionSize
- if (matchesOnDimensionZ <= 0) return emptyList()
-
- /*
- * Now we need to find the first id within the matches,
- * and then return the subsequent matches in a rectangle following it.
- *
- * On each dimension, get the distance to the first match, which is equal to (sectionSize-1 - modX)
- * and add it to the coordinate value
- */
- val firstX = x + (sectionSize - 1 - modX)
- val firstZ = z + (sectionSize - 1 - modZ)
-
- val firstIdX = (firstX + 1) / sectionSize + 1
- val firstIdZ = (firstZ + 1) / sectionSize + 1
-
- if (matchesOnDimensionX == 1 && matchesOnDimensionZ == 1) {
- // fast-path optimization
- return listOf(Vec2i(firstIdX, firstIdZ))
- }
-
- return (0 until matchesOnDimensionX).flatMap { idOffsetX ->
- (0 until matchesOnDimensionZ).map { idOffsetZ -> Vec2i(firstIdX + idOffsetX, firstIdZ + idOffsetZ) }
- }
- }
-
- }
-
+package io.dico.parcels2.defaultimpl
+
+import io.dico.parcels2.*
+import io.dico.parcels2.blockvisitor.RegionTraverser
+import io.dico.parcels2.options.DefaultGeneratorOptions
+import io.dico.parcels2.util.math.*
+import kotlinx.coroutines.CoroutineScope
+import org.bukkit.*
+import org.bukkit.block.Biome
+import org.bukkit.block.BlockFace
+import org.bukkit.block.Skull
+import org.bukkit.block.data.type.Slab
+import org.bukkit.block.data.type.WallSign
+import org.bukkit.entity.Player
+import java.util.Random
+
+private val airType = Bukkit.createBlockData(Material.AIR)
+
+private const val chunkSize = 16
+
+class DefaultParcelGenerator(
+ override val worldName: String,
+ private val o: DefaultGeneratorOptions
+) : ParcelGenerator() {
+ private var _world: World? = null
+ override val world: World
+ get() {
+ if (_world == null) {
+ val world = Bukkit.getWorld(worldName)
+ maxHeight = world.maxHeight
+ _world = world
+ return world
+ }
+ return _world!!
+ }
+
+ private var maxHeight = 0
+ val sectionSize = o.parcelSize + o.pathSize
+ val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2
+ val makePathMain = o.pathSize > 2
+ val makePathAlt = o.pathSize > 4
+
+ private inline fun <T> generate(
+ chunkX: Int,
+ chunkZ: Int,
+ floor: T, wall:
+ T, pathMain: T,
+ pathAlt: T,
+ fill: T,
+ setter: (Int, Int, Int, T) -> Unit
+ ) {
+
+ val floorHeight = o.floorHeight
+ val parcelSize = o.parcelSize
+ val sectionSize = sectionSize
+ val pathOffset = pathOffset
+ val makePathMain = makePathMain
+ val makePathAlt = makePathAlt
+
+ // parcel bottom x and z
+ // umod is unsigned %: the result is always >= 0
+ val pbx = ((chunkX shl 4) - o.offsetX) umod sectionSize
+ val pbz = ((chunkZ shl 4) - o.offsetZ) umod sectionSize
+
+ var curHeight: Int
+ var x: Int
+ var z: Int
+ for (cx in 0..15) {
+ for (cz in 0..15) {
+ x = (pbx + cx) % sectionSize - pathOffset
+ z = (pbz + cz) % sectionSize - pathOffset
+ curHeight = floorHeight
+
+ val type = when {
+ (x in 0 until parcelSize && z in 0 until parcelSize) -> floor
+ (x in -1..parcelSize && z in -1..parcelSize) -> {
+ curHeight++
+ wall
+ }
+ (makePathAlt && x in -2 until parcelSize + 2 && z in -2 until parcelSize + 2) -> pathAlt
+ (makePathMain) -> pathMain
+ else -> {
+ curHeight++
+ wall
+ }
+ }
+
+ for (y in 0 until curHeight) {
+ setter(cx, y, cz, fill)
+ }
+ setter(cx, curHeight, cz, type)
+ }
+ }
+ }
+
+ override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData {
+ val out = Bukkit.createChunkData(world)
+ generate(chunkX, chunkZ, o.floorType, o.wallType, o.pathMainType, o.pathAltType, o.fillType) { x, y, z, type ->
+ out.setBlock(x, y, z, type)
+ }
+ return out
+ }
+
+ override fun populate(world: World?, random: Random?, chunk: Chunk?) {
+ // do nothing
+ }
+
+ override fun getFixedSpawnLocation(world: World?, random: Random?): Location {
+ val fix = if (o.parcelSize.even) 0.5 else 0.0
+ return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix)
+ }
+
+ override fun makeParcelLocatorAndBlockManager(
+ parcelProvider: ParcelProvider,
+ container: ParcelContainer,
+ coroutineScope: CoroutineScope,
+ jobDispatcher: JobDispatcher
+ ): Pair<ParcelLocator, ParcelBlockManager> {
+ val impl = ParcelLocatorAndBlockManagerImpl(parcelProvider, container, coroutineScope, jobDispatcher)
+ return impl to impl
+ }
+
+ private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
+ val sectionSize = sectionSize
+ val parcelSize = o.parcelSize
+ val absX = x - o.offsetX - pathOffset
+ val absZ = z - o.offsetZ - pathOffset
+ val modX = absX umod sectionSize
+ val modZ = absZ umod sectionSize
+ if (modX in 0 until parcelSize && modZ in 0 until parcelSize) {
+ return mapper((absX - modX) / sectionSize + 1, (absZ - modZ) / sectionSize + 1)
+ }
+ return null
+ }
+
+ @Suppress("DEPRECATION")
+ private inner class ParcelLocatorAndBlockManagerImpl(
+ val parcelProvider: ParcelProvider,
+ val container: ParcelContainer,
+ coroutineScope: CoroutineScope,
+ override val jobDispatcher: JobDispatcher
+ ) : ParcelBlockManagerBase(), ParcelLocator, CoroutineScope by coroutineScope {
+
+ override val world: World get() = this@DefaultParcelGenerator.world
+ val worldId = parcelProvider.getWorld(world)?.id ?: ParcelWorldId(world)
+ override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight)
+
+ private val cornerWallType = when {
+ o.wallType is Slab -> (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE }
+ o.wallType.material.name.endsWith("CARPET") -> {
+ Bukkit.createBlockData(Material.getMaterial(o.wallType.material.name.substringBefore("CARPET") + "WOOL"))
+ }
+ else -> null
+ }
+
+ override fun getParcelAt(x: Int, z: Int): Parcel? {
+ return convertBlockLocationToId(x, z, container::getParcelById)
+ }
+
+ override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
+ return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(worldId, idx, idz) }
+ }
+
+
+ private fun checkParcelId(parcel: ParcelId): ParcelId {
+ if (!parcel.worldId.equals(worldId)) {
+ throw IllegalArgumentException()
+ }
+ return parcel
+ }
+
+ override fun getRegionOrigin(parcel: ParcelId): Vec2i {
+ checkParcelId(parcel)
+ return Vec2i(
+ sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,
+ sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ
+ )
+ }
+
+ override fun getRegion(parcel: ParcelId): Region {
+ val origin = getRegionOrigin(parcel)
+ return Region(
+ Vec3i(origin.x, 0, origin.z),
+ Vec3i(o.parcelSize, maxHeight, o.parcelSize)
+ )
+ }
+
+ override fun getHomeLocation(parcel: ParcelId): Location {
+ val origin = getRegionOrigin(parcel)
+ val x = origin.x + (o.parcelSize - 1) / 2.0
+ val z = origin.z - 2
+ return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F)
+ }
+
+ override fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? {
+ if (block.y != o.floorHeight + 1) return null
+
+ val expectedParcelOrigin = when (type) {
+ Material.WALL_SIGN -> Vec2i(block.x + 1, block.z + 2)
+ o.wallType.material, cornerWallType?.material -> {
+ if (face != BlockFace.NORTH || world[block + Vec3i.convert(BlockFace.NORTH)].type == Material.WALL_SIGN) {
+ return null
+ }
+
+ Vec2i(block.x + 1, block.z + 1)
+ }
+ else -> return null
+ }
+
+ return getParcelAt(expectedParcelOrigin.x, expectedParcelOrigin.z)
+ ?.takeIf { expectedParcelOrigin == getRegionOrigin(it.id) }
+ ?.also { parcel ->
+ if (type != Material.WALL_SIGN && parcel.owner != null) {
+ updateParcelInfo(parcel.id, parcel.owner)
+ parcel.isOwnerSignOutdated = false
+ }
+ }
+ }
+
+ override fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean {
+ val wallBlockChunk = getRegionOrigin(parcel).add(-1, -1).toChunk()
+ return world.isChunkLoaded(wallBlockChunk.x, wallBlockChunk.z)
+ }
+
+ override fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?) {
+ val b = getRegionOrigin(parcel)
+
+ val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
+ val signBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 2)
+ val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1)
+
+ if (owner == null) {
+ wallBlock.blockData = o.wallType
+ signBlock.type = Material.AIR
+ skullBlock.type = Material.AIR
+
+ } else {
+ cornerWallType?.let { wallBlock.blockData = it }
+ signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as WallSign).apply { facing = BlockFace.NORTH }
+
+ val sign = signBlock.state as org.bukkit.block.Sign
+ sign.setLine(0, "${parcel.x},${parcel.z}")
+ sign.setLine(2, owner.name ?: "")
+ sign.update()
+
+ skullBlock.type = Material.AIR
+ skullBlock.type = Material.PLAYER_HEAD
+ val skull = skullBlock.state as Skull
+ if (owner is PlayerProfile.Real) {
+ skull.owningPlayer = Bukkit.getOfflinePlayer(owner.uuid)
+
+ } else if (!skull.setOwner(owner.name)) {
+ skullBlock.type = Material.AIR
+ return
+ }
+
+ skull.rotation = BlockFace.SOUTH
+ skull.update()
+ }
+ }
+
+ private fun trySubmitBlockVisitor(vararg parcels: ParcelId, function: JobFunction): Job? {
+ parcels.forEach { checkParcelId(it) }
+ return parcelProvider.trySubmitBlockVisitor(Permit(), parcels, function)
+ }
+
+ override fun setBiome(parcel: ParcelId, biome: Biome) = trySubmitBlockVisitor(checkParcelId(parcel)) {
+ val world = world
+ val b = getRegionOrigin(parcel)
+ val parcelSize = o.parcelSize
+ for (x in b.x until b.x + parcelSize) {
+ for (z in b.z until b.z + parcelSize) {
+ markSuspensionPoint()
+ world.setBiome(x, z, biome)
+ }
+ }
+ }
+
+ override fun clearParcel(parcel: ParcelId) = trySubmitBlockVisitor(checkParcelId(parcel)) {
+ val region = getRegion(parcel)
+ val blocks = parcelTraverser.traverseRegion(region)
+ val blockCount = region.blockCount.toDouble()
+ val world = world
+ val floorHeight = o.floorHeight
+ val airType = airType
+ val floorType = o.floorType
+ val fillType = o.fillType
+
+ delegateWork(0.95) {
+ for ((index, vec) in blocks.withIndex()) {
+ markSuspensionPoint()
+ val y = vec.y
+ val blockType = when {
+ y > floorHeight -> airType
+ y == floorHeight -> floorType
+ else -> fillType
+ }
+ world[vec].blockData = blockType
+ setProgress((index + 1) / blockCount)
+ }
+ }
+
+ delegateWork {
+ val entities = getEntities(region)
+ for ((index, entity) in entities.withIndex()) {
+ if (entity is Player) continue
+ entity.remove()
+ setProgress((index + 1) / entities.size.toDouble())
+ }
+ }
+
+ }
+
+ override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
+ /*
+ * Get the offsets for the world out of the way
+ * to simplify the calculation that follows.
+ */
+
+ val x = chunk.x.shl(4) - (o.offsetX + pathOffset)
+ val z = chunk.z.shl(4) - (o.offsetZ + pathOffset)
+
+ /* Locations of wall corners (where owner blocks are placed) are defined as:
+ *
+ * x umod sectionSize == sectionSize-1
+ *
+ * This check needs to be made for all 16 slices of the chunk in 2 dimensions
+ * How to optimize this?
+ * Let's take the expression
+ *
+ * x umod sectionSize
+ *
+ * And call it modX
+ * x can be shifted (chunkSize -1) times to attempt to get a modX of 0.
+ * This means that if the modX is 1, and sectionSize == (chunkSize-1), there would be a match at the last shift.
+ * To check that there are any matches, we can see if the following holds:
+ *
+ * modX >= ((sectionSize-1) - (chunkSize-1))
+ *
+ * Which can be simplified to:
+ * modX >= sectionSize - chunkSize
+ *
+ * if sectionSize == chunkSize, this expression can be simplified to
+ * modX >= 0
+ * which is always true. This is expected.
+ * To get the total number of matches on a dimension, we can evaluate the following:
+ *
+ * (modX - (sectionSize - chunkSize) + sectionSize) / sectionSize
+ *
+ * We add sectionSize to the lhs because, if the other part of the lhs is 0, we need at least 1.
+ * This can be simplified to:
+ *
+ * (modX + chunkSize) / sectionSize
+ */
+
+ val sectionSize = sectionSize
+
+ val modX = x umod sectionSize
+ val matchesOnDimensionX = (modX + chunkSize) / sectionSize
+ if (matchesOnDimensionX <= 0) return emptyList()
+
+ val modZ = z umod sectionSize
+ val matchesOnDimensionZ = (modZ + chunkSize) / sectionSize
+ if (matchesOnDimensionZ <= 0) return emptyList()
+
+ /*
+ * Now we need to find the first id within the matches,
+ * and then return the subsequent matches in a rectangle following it.
+ *
+ * On each dimension, get the distance to the first match, which is equal to (sectionSize-1 - modX)
+ * and add it to the coordinate value
+ */
+ val firstX = x + (sectionSize - 1 - modX)
+ val firstZ = z + (sectionSize - 1 - modZ)
+
+ val firstIdX = (firstX + 1) / sectionSize + 1
+ val firstIdZ = (firstZ + 1) / sectionSize + 1
+
+ if (matchesOnDimensionX == 1 && matchesOnDimensionZ == 1) {
+ // fast-path optimization
+ return listOf(Vec2i(firstIdX, firstIdZ))
+ }
+
+ return (0 until matchesOnDimensionX).flatMap { idOffsetX ->
+ (0 until matchesOnDimensionZ).map { idOffsetZ -> Vec2i(firstIdX + idOffsetX, firstIdZ + idOffsetZ) }
+ }
+ }
+
+ }
+
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt
index da004d6..7748fc7 100644
--- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt
@@ -1,223 +1,284 @@
-package io.dico.parcels2.defaultimpl
-
-import io.dico.parcels2.*
-import io.dico.parcels2.blockvisitor.Schematic
-import io.dico.parcels2.util.schedule
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import org.bukkit.Bukkit
-import org.bukkit.WorldCreator
-import org.joda.time.DateTime
-
-class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
- inline val options get() = plugin.options
- override val worlds: Map<String, ParcelWorld> get() = _worlds
- private val _worlds: MutableMap<String, ParcelWorld> = hashMapOf()
- private val _generators: MutableMap<String, ParcelGenerator> = hashMapOf()
- private var _worldsLoaded = false
- private var _dataIsLoaded = false
-
- // disabled while !_dataIsLoaded. getParcelById() will work though for data loading.
- override fun getWorld(name: String): ParcelWorld? = _worlds[name]?.takeIf { _dataIsLoaded }
-
- override fun getWorldById(id: ParcelWorldId): ParcelWorld? {
- if (id is ParcelWorld) return id
- return _worlds[id.name] ?: id.bukkitWorld?.let { getWorld(it) }
- }
-
- override fun getParcelById(id: ParcelId): Parcel? {
- if (id is Parcel) return id
- return getWorldById(id.worldId)?.container?.getParcelById(id.x, id.z)
- }
-
- override fun getWorldGenerator(worldName: String): ParcelGenerator? {
- return _worlds[worldName]?.generator
- ?: _generators[worldName]
- ?: options.worlds[worldName]?.generator?.newInstance(worldName)?.also { _generators[worldName] = it }
- }
-
- override fun loadWorlds() {
- if (_worldsLoaded) throw IllegalStateException()
- _worldsLoaded = true
- loadWorlds0()
- }
-
- private fun loadWorlds0() {
- if (Bukkit.getWorlds().isEmpty()) {
- plugin.schedule(::loadWorlds0)
- plugin.logger.warning("Scheduling to load worlds in the next tick because no bukkit worlds are loaded yet")
- return
- }
-
- val newlyCreatedWorlds = mutableListOf<ParcelWorld>()
- for ((worldName, worldOptions) in options.worlds.entries) {
- var parcelWorld = _worlds[worldName]
- if (parcelWorld != null) continue
-
- val generator: ParcelGenerator = getWorldGenerator(worldName)!!
- val worldExists = Bukkit.getWorld(worldName) != null
- val bukkitWorld =
- if (worldExists) Bukkit.getWorld(worldName)!!
- else {
- logger.info("Creating world $worldName")
- WorldCreator(worldName).generator(generator).createWorld()
- }
-
- parcelWorld = ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime,::DefaultParcelContainer)
-
- if (!worldExists) {
- val time = DateTime.now()
- plugin.storage.setWorldCreationTime(parcelWorld.id, time)
- parcelWorld.creationTime = time
- newlyCreatedWorlds.add(parcelWorld)
- } else {
- GlobalScope.launch(context = Dispatchers.Unconfined) {
- parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?: DateTime.now()
- }
- }
-
- _worlds[worldName] = parcelWorld
- }
-
- loadStoredData(newlyCreatedWorlds.toSet())
- }
-
- private fun loadStoredData(newlyCreatedWorlds: Collection<ParcelWorld> = emptyList()) {
- plugin.launch(Dispatchers.Default) {
- val migration = plugin.options.migration
- if (migration.enabled) {
- migration.instance?.newInstance()?.apply {
- logger.warn("Migrating database now...")
- migrateTo(plugin.storage).join()
- logger.warn("Migration completed")
-
- if (migration.disableWhenComplete) {
- migration.enabled = false
- plugin.saveOptions()
- }
- }
- }
-
- logger.info("Loading all parcel data...")
-
- val job1 = launch {
- val channel = plugin.storage.transmitAllParcelData()
- while (true) {
- val (id, data) = channel.receiveOrNull() ?: break
- val parcel = getParcelById(id) ?: continue
- data?.let { parcel.copyData(it, callerIsDatabase = true) }
- }
- }
-
- val channel2 = plugin.storage.transmitAllGlobalPrivileges()
- while (true) {
- val (profile, data) = channel2.receiveOrNull() ?: break
- if (profile !is PrivilegeKey) {
- logger.error("Received profile that is not a privilege key: ${profile.javaClass}, $profile")
- continue
- }
- (plugin.globalPrivileges[profile] as PrivilegesHolder).copyPrivilegesFrom(data)
- }
-
- job1.join()
-
- logger.info("Loading data completed")
- _dataIsLoaded = true
- }
- }
-
- override fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean {
- val parcel = getParcelById(parcelId) as? ParcelImpl ?: return true
- return parcel.acquireBlockVisitorPermit(with)
- }
-
- override fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit) {
- val parcel = getParcelById(parcelId) as? ParcelImpl ?: return
- parcel.releaseBlockVisitorPermit(with)
- }
-
- override fun trySubmitBlockVisitor(permit: Permit, vararg parcelIds: ParcelId, function: JobFunction): Job? {
- val withPermit = parcelIds.filter { acquireBlockVisitorPermit(it, permit) }
- if (withPermit.size != parcelIds.size) {
- withPermit.forEach { releaseBlockVisitorPermit(it, permit) }
- return null
- }
-
- val job = plugin.jobDispatcher.dispatch(function)
-
- plugin.launch {
- job.awaitCompletion()
- withPermit.forEach { releaseBlockVisitorPermit(it, permit) }
- }
-
- return job
- }
-
- override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? {
- val blockManager1 = getWorldById(parcelId1.worldId)?.blockManager ?: return null
- val blockManager2 = getWorldById(parcelId2.worldId)?.blockManager ?: return null
-
- return trySubmitBlockVisitor(Permit(), parcelId1, parcelId2) {
- var region1 = blockManager1.getRegion(parcelId1)
- var region2 = blockManager2.getRegion(parcelId2)
-
- val size = region1.size.clampMax(region2.size)
- if (size != region1.size) {
- region1 = region1.withSize(size)
- region2 = region2.withSize(size)
- }
-
- val schematicOf1 = delegateWork(0.25) { Schematic().apply { load(blockManager1.world, region1) } }
- val schematicOf2 = delegateWork(0.25) { Schematic().apply { load(blockManager2.world, region2) } }
- delegateWork(0.25) { with(schematicOf1) { paste(blockManager2.world, region2.origin) } }
- delegateWork(0.25) { with(schematicOf2) { paste(blockManager1.world, region1.origin) } }
- }
- }
-
- /*
- fun loadWorlds(options: Options) {
- for ((worldName, worldOptions) in options.worlds.entries) {
- val world: ParcelWorld
- try {
-
- world = ParcelWorldImpl(
- worldName,
- worldOptions,
- worldOptions.generator.newGenerator(this, worldName),
- plugin.storage,
- plugin.globalPrivileges,
- ::DefaultParcelContainer)
-
- } catch (ex: Exception) {
- ex.printStackTrace()
- continue
- }
-
- _worlds[worldName] = world
- }
-
- plugin.functionHelper.schedule(10) {
- println("Parcels generating parcelProvider now")
- for ((name, world) in _worlds) {
- if (Bukkit.getWorld(name) == null) {
- val bworld = WorldCreator(name).generator(world.generator).createWorld()
- val spawn = world.generator.getFixedSpawnLocation(bworld, null)
- bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor())
- }
- }
-
- val channel = plugin.storage.transmitAllParcelData()
- val job = plugin.functionHelper.launchLazilyOnMainThread {
- do {
- val pair = channel.receiveOrNull() ?: break
- val parcel = getParcelById(pair.first) ?: continue
- pair.second?.let { parcel.copyDataIgnoringDatabase(it) }
- } while (true)
- }
- job.start()
- }
-
- }
- */
+package io.dico.parcels2.defaultimpl
+
+import io.dico.parcels2.*
+import io.dico.parcels2.blockvisitor.Schematic
+import io.dico.parcels2.util.math.Region
+import io.dico.parcels2.util.math.Vec3d
+import io.dico.parcels2.util.math.Vec3i
+import io.dico.parcels2.util.schedule
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import org.bukkit.Bukkit
+import org.bukkit.World
+import org.bukkit.WorldCreator
+import org.bukkit.entity.Entity
+import org.bukkit.util.Vector
+import org.joda.time.DateTime
+
+class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
+ inline val options get() = plugin.options
+ override val worlds: Map<String, ParcelWorld> get() = _worlds
+ private val _worlds: MutableMap<String, ParcelWorld> = hashMapOf()
+ private val _generators: MutableMap<String, ParcelGenerator> = hashMapOf()
+ private var _worldsLoaded = false
+ private var _dataIsLoaded = false
+
+ // disabled while !_dataIsLoaded. getParcelById() will work though for data loading.
+ override fun getWorld(name: String): ParcelWorld? = _worlds[name]?.takeIf { _dataIsLoaded }
+
+ override fun getWorldById(id: ParcelWorldId): ParcelWorld? {
+ if (id is ParcelWorld) return id
+ return _worlds[id.name] ?: id.bukkitWorld?.let { getWorld(it) }
+ }
+
+ override fun getParcelById(id: ParcelId): Parcel? {
+ if (id is Parcel) return id
+ return getWorldById(id.worldId)?.container?.getParcelById(id.x, id.z)
+ }
+
+ override fun getWorldGenerator(worldName: String): ParcelGenerator? {
+ return _worlds[worldName]?.generator
+ ?: _generators[worldName]
+ ?: options.worlds[worldName]?.generator?.newInstance(worldName)?.also { _generators[worldName] = it }
+ }
+
+ override fun loadWorlds() {
+ if (_worldsLoaded) throw IllegalStateException()
+ _worldsLoaded = true
+ loadWorlds0()
+ }
+
+ private fun loadWorlds0() {
+ if (Bukkit.getWorlds().isEmpty()) {
+ plugin.schedule { loadWorlds0() }
+ plugin.logger.warning("Scheduling to load worlds in the next tick because no bukkit worlds are loaded yet")
+ return
+ }
+
+ val newlyCreatedWorlds = mutableListOf<ParcelWorld>()
+ for ((worldName, worldOptions) in options.worlds.entries) {
+ var parcelWorld = _worlds[worldName]
+ if (parcelWorld != null) continue
+
+ val generator: ParcelGenerator = getWorldGenerator(worldName)!!
+ val worldExists = Bukkit.getWorld(worldName) != null
+ val bukkitWorld =
+ if (worldExists) Bukkit.getWorld(worldName)!!
+ else {
+ logger.info("Creating world $worldName")
+ WorldCreator(worldName).generator(generator).createWorld()
+ }
+
+ parcelWorld =
+ ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime, ::DefaultParcelContainer)
+
+ if (!worldExists) {
+ val time = DateTime.now()
+ plugin.storage.setWorldCreationTime(parcelWorld.id, time)
+ parcelWorld.creationTime = time
+ newlyCreatedWorlds.add(parcelWorld)
+ } else {
+ GlobalScope.launch(context = Dispatchers.Unconfined) {
+ parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?:
+ DateTime.now()
+ }
+ }
+
+ _worlds[worldName] = parcelWorld
+ }
+
+ loadStoredData(newlyCreatedWorlds.toSet())
+ }
+
+ private fun loadStoredData(newlyCreatedWorlds: Collection<ParcelWorld> = emptyList()) {
+ plugin.launch {
+ val migration = plugin.options.migration
+ if (migration.enabled) {
+ migration.instance?.newInstance()?.apply {
+ logger.warn("Migrating database now...")
+ migrateTo(plugin.storage).join()
+ logger.warn("Migration completed")
+
+ if (migration.disableWhenComplete) {
+ migration.enabled = false
+ plugin.saveOptions()
+ }
+ }
+ }
+
+ logger.info("Loading all parcel data...")
+
+ val job1 = launch {
+ val channel = plugin.storage.transmitAllParcelData()
+ while (true) {
+ val (id, data) = channel.receiveOrNull() ?: break
+ val parcel = getParcelById(id) ?: continue
+ data?.let { parcel.copyData(it, callerIsDatabase = true) }
+ }
+ }
+
+ val channel2 = plugin.storage.transmitAllGlobalPrivileges()
+ while (true) {
+ val (profile, data) = channel2.receiveOrNull() ?: break
+ if (profile !is PrivilegeKey) {
+ logger.error("Received profile that is not a privilege key: ${profile.javaClass}, $profile")
+ continue
+ }
+ (plugin.globalPrivileges[profile] as PrivilegesHolder).copyPrivilegesFrom(data)
+ }
+
+ job1.join()
+
+ logger.info("Loading data completed")
+ _dataIsLoaded = true
+ }
+ }
+
+ override fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean {
+ val parcel = getParcelById(parcelId) as? ParcelImpl ?: return true
+ return parcel.acquireBlockVisitorPermit(with)
+ }
+
+ override fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit) {
+ val parcel = getParcelById(parcelId) as? ParcelImpl ?: return
+ parcel.releaseBlockVisitorPermit(with)
+ }
+
+ override fun trySubmitBlockVisitor(permit: Permit, vararg parcelIds: ParcelId, function: JobFunction): Job? {
+ val withPermit = parcelIds.filter { acquireBlockVisitorPermit(it, permit) }
+ if (withPermit.size != parcelIds.size) {
+ withPermit.forEach { releaseBlockVisitorPermit(it, permit) }
+ return null
+ }
+
+ val job = plugin.jobDispatcher.dispatch(function)
+
+ plugin.launch {
+ job.awaitCompletion()
+ withPermit.forEach { releaseBlockVisitorPermit(it, permit) }
+ }
+
+ return job
+ }
+
+ override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? {
+ val world1 = getWorldById(parcelId1.worldId) ?: return null
+ val world2 = getWorldById(parcelId2.worldId) ?: return null
+ val blockManager1 = world1.blockManager
+ val blockManager2 = world2.blockManager
+
+ class CopyTarget(val world: World, val region: Region)
+ class CopySource(val origin: Vec3i, val schematic: Schematic, val entities: Collection<Entity>)
+
+ suspend fun JobScope.copy(source: CopySource, target: CopyTarget) {
+ with(source.schematic) { paste(target.world, target.region.origin) }
+
+ for (entity in source.entities) {
+ entity.velocity = Vector(0, 0, 0)
+ val location = entity.location
+ location.world = target.world
+ val coords = target.region.origin + (Vec3d(entity.location) - source.origin)
+ coords.copyInto(location)
+ entity.teleport(location)
+ }
+ }
+
+ return trySubmitBlockVisitor(Permit(), parcelId1, parcelId2) {
+ val temporaryParcel = world1.nextEmptyParcel()
+ ?: world2.nextEmptyParcel()
+ ?: return@trySubmitBlockVisitor
+
+ var region1 = blockManager1.getRegion(parcelId1)
+ var region2 = blockManager2.getRegion(parcelId2)
+
+ val size = region1.size.clampMax(region2.size)
+ if (size != region1.size) {
+ region1 = region1.withSize(size)
+ region2 = region2.withSize(size)
+ }
+
+ // Teleporting entities safely requires a different approach:
+ // * Copy schematic1 into temporary location
+ // * Teleport entities1 into temporary location
+ // * Copy schematic2 into parcel1
+ // * Teleport entities2 into parcel1
+ // * Copy schematic1 into parcel2
+ // * Teleport entities1 into parcel2
+ // * Clear temporary location
+
+ lateinit var source1: CopySource
+ lateinit var source2: CopySource
+
+ delegateWork(0.30) {
+ val schematicOf1 = delegateWork(0.50) { Schematic().apply { load(blockManager1.world, region1) } }
+ val schematicOf2 = delegateWork(0.50) { Schematic().apply { load(blockManager2.world, region2) } }
+
+ source1 = CopySource(region1.origin, schematicOf1, blockManager1.getEntities(region1))
+ source2 = CopySource(region2.origin, schematicOf2, blockManager2.getEntities(region2))
+ }
+
+ val target1 = CopyTarget(blockManager1.world, region1)
+ val target2 = CopyTarget(blockManager2.world, region2)
+ val targetTemp = CopyTarget(
+ temporaryParcel.world.world,
+ temporaryParcel.world.blockManager.getRegion(temporaryParcel.id)
+ )
+
+ delegateWork {
+ delegateWork(1.0 / 3.0) { copy(source1, targetTemp) }
+ delegateWork(1.0 / 3.0) { copy(source2, target1) }
+ delegateWork(1.0 / 3.0) { copy(source1, target2) }
+ }
+
+ // Separate job. Whatever
+ temporaryParcel.world.blockManager.clearParcel(temporaryParcel.id)
+ }
+ }
+
+ /*
+ fun loadWorlds(options: Options) {
+ for ((worldName, worldOptions) in options.worlds.entries) {
+ val world: ParcelWorld
+ try {
+
+ world = ParcelWorldImpl(
+ worldName,
+ worldOptions,
+ worldOptions.generator.newGenerator(this, worldName),
+ plugin.storage,
+ plugin.globalPrivileges,
+ ::DefaultParcelContainer)
+
+ } catch (ex: Exception) {
+ ex.printStackTrace()
+ continue
+ }
+
+ _worlds[worldName] = world
+ }
+
+ plugin.functionHelper.schedule(10) {
+ println("Parcels generating parcelProvider now")
+ for ((name, world) in _worlds) {
+ if (Bukkit.getWorld(name) == null) {
+ val bworld = WorldCreator(name).generator(world.generator).createWorld()
+ val spawn = world.generator.getFixedSpawnLocation(bworld, null)
+ bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor())
+ }
+ }
+
+ val channel = plugin.storage.transmitAllParcelData()
+ val job = plugin.functionHelper.launchLazilyOnMainThread {
+ do {
+ val pair = channel.receiveOrNull() ?: break
+ val parcel = getParcelById(pair.first) ?: continue
+ pair.second?.let { parcel.copyDataIgnoringDatabase(it) }
+ } while (true)
+ }
+ job.start()
+ }
+
+ }
+ */
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt
index 32065bc..d9e3071 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt
@@ -1,282 +1,284 @@
-@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName", "UNUSED_EXPRESSION")
-
-package io.dico.parcels2.storage.exposed
-
-import com.zaxxer.hikari.HikariDataSource
-import io.dico.parcels2.*
-import io.dico.parcels2.PlayerProfile.Star.name
-import io.dico.parcels2.storage.*
-import io.dico.parcels2.util.math.clampMax
-import io.dico.parcels2.util.ext.synchronized
-import kotlinx.coroutines.*
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.channels.ArrayChannel
-import kotlinx.coroutines.channels.LinkedListChannel
-import kotlinx.coroutines.channels.ReceiveChannel
-import kotlinx.coroutines.channels.SendChannel
-import org.jetbrains.exposed.sql.*
-import org.jetbrains.exposed.sql.SchemaUtils.create
-import org.jetbrains.exposed.sql.transactions.transaction
-import org.jetbrains.exposed.sql.vendors.DatabaseDialect
-import org.joda.time.DateTime
-import java.util.UUID
-import javax.sql.DataSource
-
-class ExposedDatabaseException(message: String? = null) : Exception(message)
-
-class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSize: Int) : Backing, CoroutineScope {
- override val name get() = "Exposed"
- override val coroutineContext = Job() + newFixedThreadPoolContext(poolSize, "Parcels StorageThread")
- private var dataSource: DataSource? = null
- private var database: Database? = null
- private var isShutdown: Boolean = false
- override val isConnected get() = database != null
-
- override fun launchJob(job: Backing.() -> Unit): Job = launch { transaction { job() } }
- override fun <T> launchFuture(future: Backing.() -> T): Deferred<T> = async { transaction { future() } }
-
- override fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T> {
- val channel = LinkedListChannel<T>()
- launchJob { future(channel) }
- return channel
- }
-
- override fun <T> openChannelForWriting(action: Backing.(T) -> Unit): SendChannel<T> {
- val channel = ArrayChannel<T>(poolSize * 2)
-
- repeat(poolSize.clampMax(3)) {
- launch {
- try {
- while (true) {
- action(channel.receive())
- }
- } catch (ex: Exception) {
- // channel closed
- }
- }
- }
-
- return channel
- }
-
- private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement)
-
- companion object {
- init {
- Database.registerDialect("mariadb") {
- Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect
- }
- }
- }
-
- override fun init() {
- synchronized {
- if (isShutdown || isConnected) throw IllegalStateException()
- dataSource = dataSourceFactory()
- database = Database.connect(dataSource!!)
- transaction(database!!) {
- create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT)
- }
- }
- }
-
- override fun shutdown() {
- synchronized {
- if (isShutdown) throw IllegalStateException()
- isShutdown = true
- coroutineContext[Job]!!.cancel(CancellationException("ExposedBacking shutdown"))
- dataSource?.let {
- (it as? HikariDataSource)?.close()
- }
- database = null
- }
- }
-
- @Suppress("RedundantObjectTypeCheck")
- private fun PlayerProfile.toOwnerProfile(): PlayerProfile {
- if (this is PlayerProfile.Star) return PlayerProfile.Fake(name)
- return this
- }
-
- private fun PlayerProfile.Unresolved.toResolvedProfile(): PlayerProfile.Real {
- return resolve(getPlayerUuidForName(name) ?: throwException())
- }
-
- private fun PlayerProfile.toResolvedProfile(): PlayerProfile {
- if (this is PlayerProfile.Unresolved) return toResolvedProfile()
- return this
- }
-
- private fun PlayerProfile.toRealProfile(): PlayerProfile.Real = when (this) {
- is PlayerProfile.Real -> this
- is PlayerProfile.Fake -> throw IllegalArgumentException("Fake profiles are not accepted")
- is PlayerProfile.Unresolved -> toResolvedProfile()
- else -> throw InternalError("Case should not be reached")
- }
-
-
- override fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? {
- return WorldsT.getWorldCreationTime(worldId)
- }
-
- override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) {
- WorldsT.setWorldCreationTime(worldId, time)
- }
-
- override fun getPlayerUuidForName(name: String): UUID? {
- return ProfilesT.slice(ProfilesT.uuid).select { ProfilesT.name.upperCase() eq name.toUpperCase() }
- .firstOrNull()?.let { it[ProfilesT.uuid]?.toUUID() }
- }
-
- override fun updatePlayerName(uuid: UUID, name: String) {
- val binaryUuid = uuid.toByteArray()
- ProfilesT.upsert(ProfilesT.uuid) {
- it[ProfilesT.uuid] = binaryUuid
- it[ProfilesT.name] = name
- }
- }
-
- override fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) {
- for (parcel in parcels) {
- val data = readParcelData(parcel)
- channel.offer(parcel to data)
- }
- channel.close()
- }
-
- override fun transmitAllParcelData(channel: SendChannel<DataPair>) {
- ParcelsT.selectAll().forEach { row ->
- val parcel = ParcelsT.getItem(row) ?: return@forEach
- val data = rowToParcelData(row)
- channel.offer(parcel to data)
- }
- channel.close()
- }
-
- override fun readParcelData(parcel: ParcelId): ParcelDataHolder? {
- val row = ParcelsT.getRow(parcel) ?: return null
- return rowToParcelData(row)
- }
-
- override fun getOwnedParcels(user: PlayerProfile): List<ParcelId> {
- val user_id = ProfilesT.getId(user.toOwnerProfile()) ?: return emptyList()
- return ParcelsT.select { ParcelsT.owner_id eq user_id }
- .orderBy(ParcelsT.claim_time, isAsc = true)
- .mapNotNull(ParcelsT::getItem)
- .toList()
- }
-
- override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) {
- if (data == null) {
- transaction {
- ParcelsT.getId(parcel)?.let { id ->
- ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id }
-
- // Below should cascade automatically
- /*
- PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.parcel_id eq id }
- ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id }
- */
- }
-
- }
- return
- }
-
- transaction {
- val id = ParcelsT.getOrInitId(parcel)
- PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.attach_id eq id }
- }
-
- setParcelOwner(parcel, data.owner)
-
- for ((profile, privilege) in data.privilegeMap) {
- PrivilegesLocalT.setPrivilege(parcel, profile, privilege)
- }
-
- data.privilegeOfStar.takeIf { it != Privilege.DEFAULT }?.let { privilege ->
- PrivilegesLocalT.setPrivilege(parcel, PlayerProfile.Star, privilege)
- }
-
- setParcelOptionsInteractConfig(parcel, data.interactableConfig)
- }
-
- override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) {
- val id = if (owner == null)
- ParcelsT.getId(parcel) ?: return
- else
- ParcelsT.getOrInitId(parcel)
-
- val owner_id = owner?.let { ProfilesT.getOrInitId(it.toOwnerProfile()) }
- val time = owner?.let { DateTime.now() }
-
- ParcelsT.update({ ParcelsT.id eq id }) {
- it[ParcelsT.owner_id] = owner_id
- it[claim_time] = time
- it[sign_oudated] = false
- }
- }
-
- override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean) {
- val id = ParcelsT.getId(parcel) ?: return
- ParcelsT.update({ ParcelsT.id eq id }) {
- it[sign_oudated] = outdated
- }
- }
-
- override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) {
- PrivilegesLocalT.setPrivilege(parcel, player.toRealProfile(), privilege)
- }
-
- override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) {
- val bitmaskArray = (config as? BitmaskInteractableConfiguration ?: return).bitmaskArray
- val isAllZero = !bitmaskArray.fold(false) { cur, elem -> cur || elem != 0 }
-
- if (isAllZero) {
- val id = ParcelsT.getId(parcel) ?: return
- ParcelOptionsT.deleteWhere { ParcelOptionsT.parcel_id eq id }
- return
- }
-
- if (bitmaskArray.size != 1) throw IllegalArgumentException()
- val array = bitmaskArray.toByteArray()
- val id = ParcelsT.getOrInitId(parcel)
- ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
- it[parcel_id] = id
- it[interact_bitmask] = array
- }
- }
-
- override fun transmitAllGlobalPrivileges(channel: SendChannel<PrivilegePair<PlayerProfile>>) {
- PrivilegesGlobalT.sendAllPrivilegesH(channel)
- channel.close()
- }
-
- override fun readGlobalPrivileges(owner: PlayerProfile): PrivilegesHolder? {
- return PrivilegesGlobalT.readPrivileges(ProfilesT.getId(owner.toOwnerProfile()) ?: return null)
- }
-
- override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) {
- PrivilegesGlobalT.setPrivilege(owner, player.toRealProfile(), privilege)
- }
-
- private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
- owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) }
- lastClaimTime = row[ParcelsT.claim_time]
- isOwnerSignOutdated = row[ParcelsT.sign_oudated]
-
- val id = row[ParcelsT.id]
- ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow ->
- val source = optrow[ParcelOptionsT.interact_bitmask].toIntArray()
- val target = (interactableConfig as? BitmaskInteractableConfiguration ?: return@let).bitmaskArray
- System.arraycopy(source, 0, target, 0, source.size.clampMax(target.size))
- }
-
- val privileges = PrivilegesLocalT.readPrivileges(id)
- if (privileges != null) {
- copyPrivilegesFrom(privileges)
- }
- }
-
-}
-
+@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName", "UNUSED_EXPRESSION")
+
+package io.dico.parcels2.storage.exposed
+
+import com.zaxxer.hikari.HikariDataSource
+import io.dico.parcels2.*
+import io.dico.parcels2.PlayerProfile.Star.name
+import io.dico.parcels2.storage.*
+import io.dico.parcels2.util.math.clampMax
+import io.dico.parcels2.util.ext.synchronized
+import kotlinx.coroutines.*
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.ArrayChannel
+import kotlinx.coroutines.channels.LinkedListChannel
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.SendChannel
+import org.jetbrains.exposed.sql.*
+import org.jetbrains.exposed.sql.SchemaUtils.create
+import org.jetbrains.exposed.sql.transactions.transaction
+import org.jetbrains.exposed.sql.vendors.DatabaseDialect
+import org.joda.time.DateTime
+import java.util.UUID
+import javax.sql.DataSource
+
+class ExposedDatabaseException(message: String? = null) : Exception(message)
+
+class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSize: Int) : Backing, CoroutineScope {
+ override val name get() = "Exposed"
+ override val coroutineContext = Job() + newFixedThreadPoolContext(poolSize, "Parcels StorageThread")
+ private var dataSource: DataSource? = null
+ private var database: Database? = null
+ private var isShutdown: Boolean = false
+ override val isConnected get() = database != null
+
+ override fun launchJob(job: Backing.() -> Unit): Job = launch { transaction { job() } }
+ override fun <T> launchFuture(future: Backing.() -> T): Deferred<T> = async { transaction { future() } }
+
+ override fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T> {
+ val channel = LinkedListChannel<T>()
+ launchJob { future(channel) }
+ return channel
+ }
+
+ override fun <T> openChannelForWriting(action: Backing.(T) -> Unit): SendChannel<T> {
+ val channel = ArrayChannel<T>(poolSize * 2)
+
+ repeat(poolSize.clampMax(3)) {
+ launch {
+ try {
+ while (true) {
+ action(channel.receive())
+ }
+ } catch (ex: Exception) {
+ // channel closed
+ }
+ }
+ }
+
+ return channel
+ }
+
+ private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement)
+
+ companion object {
+ init {
+ Database.registerDialect("mariadb") {
+ Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect
+ }
+ }
+ }
+
+ override fun init() {
+ synchronized {
+ if (isShutdown || isConnected) throw IllegalStateException()
+ val dataSource = dataSourceFactory()
+ this.dataSource = dataSource
+ val database = Database.connect(dataSource)
+ this.database = database
+ transaction(database) {
+ create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT)
+ }
+ }
+ }
+
+ override fun shutdown() {
+ synchronized {
+ if (isShutdown) throw IllegalStateException()
+ isShutdown = true
+ coroutineContext.cancel(CancellationException("ExposedBacking shutdown"))
+ dataSource?.let {
+ (it as? HikariDataSource)?.close()
+ }
+ database = null
+ }
+ }
+
+ @Suppress("RedundantObjectTypeCheck")
+ private fun PlayerProfile.toOwnerProfile(): PlayerProfile {
+ if (this is PlayerProfile.Star) return PlayerProfile.Fake(name)
+ return this
+ }
+
+ private fun PlayerProfile.Unresolved.toResolvedProfile(): PlayerProfile.Real {
+ return resolve(getPlayerUuidForName(name) ?: throwException())
+ }
+
+ private fun PlayerProfile.toResolvedProfile(): PlayerProfile {
+ if (this is PlayerProfile.Unresolved) return toResolvedProfile()
+ return this
+ }
+
+ private fun PlayerProfile.toRealProfile(): PlayerProfile.Real = when (this) {
+ is PlayerProfile.Real -> this
+ is PlayerProfile.Fake -> throw IllegalArgumentException("Fake profiles are not accepted")
+ is PlayerProfile.Unresolved -> toResolvedProfile()
+ else -> throw InternalError("Case should not be reached")
+ }
+
+
+ override fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? {
+ return WorldsT.getWorldCreationTime(worldId)
+ }
+
+ override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) {
+ WorldsT.setWorldCreationTime(worldId, time)
+ }
+
+ override fun getPlayerUuidForName(name: String): UUID? {
+ return ProfilesT.slice(ProfilesT.uuid).select { ProfilesT.name.upperCase() eq name.toUpperCase() }
+ .firstOrNull()?.let { it[ProfilesT.uuid]?.toUUID() }
+ }
+
+ override fun updatePlayerName(uuid: UUID, name: String) {
+ val binaryUuid = uuid.toByteArray()
+ ProfilesT.upsert(ProfilesT.uuid) {
+ it[ProfilesT.uuid] = binaryUuid
+ it[ProfilesT.name] = name
+ }
+ }
+
+ override fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) {
+ for (parcel in parcels) {
+ val data = readParcelData(parcel)
+ channel.offer(parcel to data)
+ }
+ channel.close()
+ }
+
+ override fun transmitAllParcelData(channel: SendChannel<DataPair>) {
+ ParcelsT.selectAll().forEach { row ->
+ val parcel = ParcelsT.getItem(row) ?: return@forEach
+ val data = rowToParcelData(row)
+ channel.offer(parcel to data)
+ }
+ channel.close()
+ }
+
+ override fun readParcelData(parcel: ParcelId): ParcelDataHolder? {
+ val row = ParcelsT.getRow(parcel) ?: return null
+ return rowToParcelData(row)
+ }
+
+ override fun getOwnedParcels(user: PlayerProfile): List<ParcelId> {
+ val user_id = ProfilesT.getId(user.toOwnerProfile()) ?: return emptyList()
+ return ParcelsT.select { ParcelsT.owner_id eq user_id }
+ .orderBy(ParcelsT.claim_time, isAsc = true)
+ .mapNotNull(ParcelsT::getItem)
+ .toList()
+ }
+
+ override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) {
+ if (data == null) {
+ transaction {
+ ParcelsT.getId(parcel)?.let { id ->
+ ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id }
+
+ // Below should cascade automatically
+ /*
+ PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.parcel_id eq id }
+ ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id }
+ */
+ }
+
+ }
+ return
+ }
+
+ transaction {
+ val id = ParcelsT.getOrInitId(parcel)
+ PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.attach_id eq id }
+ }
+
+ setParcelOwner(parcel, data.owner)
+
+ for ((profile, privilege) in data.privilegeMap) {
+ PrivilegesLocalT.setPrivilege(parcel, profile, privilege)
+ }
+
+ data.privilegeOfStar.takeIf { it != Privilege.DEFAULT }?.let { privilege ->
+ PrivilegesLocalT.setPrivilege(parcel, PlayerProfile.Star, privilege)
+ }
+
+ setParcelOptionsInteractConfig(parcel, data.interactableConfig)
+ }
+
+ override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) {
+ val id = if (owner == null)
+ ParcelsT.getId(parcel) ?: return
+ else
+ ParcelsT.getOrInitId(parcel)
+
+ val owner_id = owner?.let { ProfilesT.getOrInitId(it.toOwnerProfile()) }
+ val time = owner?.let { DateTime.now() }
+
+ ParcelsT.update({ ParcelsT.id eq id }) {
+ it[ParcelsT.owner_id] = owner_id
+ it[claim_time] = time
+ it[sign_oudated] = false
+ }
+ }
+
+ override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean) {
+ val id = ParcelsT.getId(parcel) ?: return
+ ParcelsT.update({ ParcelsT.id eq id }) {
+ it[sign_oudated] = outdated
+ }
+ }
+
+ override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) {
+ PrivilegesLocalT.setPrivilege(parcel, player.toRealProfile(), privilege)
+ }
+
+ override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) {
+ val bitmaskArray = (config as? BitmaskInteractableConfiguration ?: return).bitmaskArray
+ val isAllZero = !bitmaskArray.fold(false) { cur, elem -> cur || elem != 0 }
+
+ if (isAllZero) {
+ val id = ParcelsT.getId(parcel) ?: return
+ ParcelOptionsT.deleteWhere { ParcelOptionsT.parcel_id eq id }
+ return
+ }
+
+ if (bitmaskArray.size != 1) throw IllegalArgumentException()
+ val array = bitmaskArray.toByteArray()
+ val id = ParcelsT.getOrInitId(parcel)
+ ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
+ it[parcel_id] = id
+ it[interact_bitmask] = array
+ }
+ }
+
+ override fun transmitAllGlobalPrivileges(channel: SendChannel<PrivilegePair<PlayerProfile>>) {
+ PrivilegesGlobalT.sendAllPrivilegesH(channel)
+ channel.close()
+ }
+
+ override fun readGlobalPrivileges(owner: PlayerProfile): PrivilegesHolder? {
+ return PrivilegesGlobalT.readPrivileges(ProfilesT.getId(owner.toOwnerProfile()) ?: return null)
+ }
+
+ override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) {
+ PrivilegesGlobalT.setPrivilege(owner, player.toRealProfile(), privilege)
+ }
+
+ private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
+ owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) }
+ lastClaimTime = row[ParcelsT.claim_time]
+ isOwnerSignOutdated = row[ParcelsT.sign_oudated]
+
+ val id = row[ParcelsT.id]
+ ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow ->
+ val source = optrow[ParcelOptionsT.interact_bitmask].toIntArray()
+ val target = (interactableConfig as? BitmaskInteractableConfiguration ?: return@let).bitmaskArray
+ System.arraycopy(source, 0, target, 0, source.size.clampMax(target.size))
+ }
+
+ val privileges = PrivilegesLocalT.readPrivileges(id)
+ if (privileges != null) {
+ copyPrivilegesFrom(privileges)
+ }
+ }
+
+}
+
diff --git a/src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt b/src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt
index a4a6da9..c7d813b 100644
--- a/src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/BukkitUtil.kt
@@ -1,14 +1,23 @@
-package io.dico.parcels2.util
-
-import io.dico.parcels2.util.ext.isValid
-import org.bukkit.Bukkit
-import org.bukkit.OfflinePlayer
-import java.util.UUID
-
-fun getPlayerName(uuid: UUID): String? = getOfflinePlayer(uuid)?.name
-
-fun getOfflinePlayer(uuid: UUID): OfflinePlayer? = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }
-
-fun getOfflinePlayer(name: String): OfflinePlayer? = Bukkit.getOfflinePlayer(name).takeIf { it.isValid }
-
-fun isServerThread(): Boolean = Thread.currentThread().name == "Server thread"
+package io.dico.parcels2.util
+
+import io.dico.parcels2.util.ext.isValid
+import org.bukkit.Bukkit
+import org.bukkit.OfflinePlayer
+import java.lang.IllegalArgumentException
+import java.util.UUID
+
+fun getPlayerName(uuid: UUID): String? = getOfflinePlayer(uuid)?.name
+
+fun getOfflinePlayer(uuid: UUID): OfflinePlayer? = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }
+
+fun getOfflinePlayer(name: String): OfflinePlayer? = Bukkit.getOfflinePlayer(name).takeIf { it.isValid }
+
+fun isServerThread(): Boolean = Thread.currentThread().name == "Server thread"
+
+fun isPlayerNameValid(name: String): Boolean =
+ name.length in 3..16
+ && name.find { it !in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" } == null
+
+fun checkPlayerNameValid(name: String) {
+ if (!isPlayerNameValid(name)) throw IllegalArgumentException("Invalid player name: $name")
+}
diff --git a/src/main/kotlin/io/dico/parcels2/util/PluginAware.kt b/src/main/kotlin/io/dico/parcels2/util/PluginAware.kt
new file mode 100644
index 0000000..de75519
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/util/PluginAware.kt
@@ -0,0 +1,18 @@
+package io.dico.parcels2.util
+
+import org.bukkit.plugin.Plugin
+import org.bukkit.scheduler.BukkitTask
+
+interface PluginAware {
+ val plugin: Plugin
+}
+
+inline fun PluginAware.schedule(delay: Int = 0, crossinline task: () -> Unit): BukkitTask {
+ return plugin.server.scheduler.runTaskLater(plugin, { task() }, delay.toLong())
+}
+
+inline fun PluginAware.scheduleRepeating(interval: Int, delay: Int = 0, crossinline task: () -> Unit): BukkitTask {
+ return plugin.server.scheduler.runTaskTimer(plugin, { task() }, delay.toLong(), interval.toLong())
+}
+
+
diff --git a/src/main/kotlin/io/dico/parcels2/util/PluginScheduler.kt b/src/main/kotlin/io/dico/parcels2/util/PluginScheduler.kt
deleted file mode 100644
index 268a083..0000000
--- a/src/main/kotlin/io/dico/parcels2/util/PluginScheduler.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package io.dico.parcels2.util
-
-import org.bukkit.plugin.Plugin
-import org.bukkit.scheduler.BukkitTask
-
-interface PluginScheduler {
- val plugin: Plugin
-
- fun schedule(delay: Int, task: () -> Unit): BukkitTask {
- return plugin.server.scheduler.runTaskLater(plugin, task, delay.toLong())
- }
-
- fun scheduleRepeating(delay: Int, interval: Int, task: () -> Unit): BukkitTask {
- return plugin.server.scheduler.runTaskTimer(plugin, task, delay.toLong(), interval.toLong())
- }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline fun PluginScheduler.schedule(noinline task: () -> Unit) = schedule(0, task)
-
diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt b/src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt
index 72b6dcd..2c3512f 100644
--- a/src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/math/Vec3d.kt
@@ -1,53 +1,61 @@
-package io.dico.parcels2.util.math
-
-import org.bukkit.Location
-import kotlin.math.sqrt
-
-data class Vec3d(
- val x: Double,
- val y: Double,
- val z: Double
-) {
- constructor(loc: Location) : this(loc.x, loc.y, loc.z)
-
- operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z)
- operator fun minus(o: Vec3i) = Vec3d(x - o.x, y - o.y, z - o.z)
- infix fun addX(o: Double) = Vec3d(x + o, y, z)
- infix fun addY(o: Double) = Vec3d(x, y + o, z)
- infix fun addZ(o: Double) = Vec3d(x, y, z + o)
- infix fun withX(o: Double) = Vec3d(o, y, z)
- infix fun withY(o: Double) = Vec3d(x, o, z)
- infix fun withZ(o: Double) = Vec3d(x, y, o)
- fun add(ox: Double, oy: Double, oz: Double) = Vec3d(x + ox, y + oy, z + oz)
- fun toVec3i() = Vec3i(x.floor(), y.floor(), z.floor())
-
- fun distanceSquared(o: Vec3d): Double {
- val dx = o.x - x
- val dy = o.y - y
- val dz = o.z - z
- return dx * dx + dy * dy + dz * dz
- }
-
- fun distance(o: Vec3d) = sqrt(distanceSquared(o))
-
- operator fun get(dimension: Dimension) =
- when (dimension) {
- Dimension.X -> x
- Dimension.Y -> y
- Dimension.Z -> z
- }
-
- fun with(dimension: Dimension, value: Double) =
- when (dimension) {
- Dimension.X -> withX(value)
- Dimension.Y -> withY(value)
- Dimension.Z -> withZ(value)
- }
-
- fun add(dimension: Dimension, value: Double) =
- when (dimension) {
- Dimension.X -> addX(value)
- Dimension.Y -> addY(value)
- Dimension.Z -> addZ(value)
- }
+package io.dico.parcels2.util.math
+
+import org.bukkit.Location
+import kotlin.math.sqrt
+
+data class Vec3d(
+ val x: Double,
+ val y: Double,
+ val z: Double
+) {
+ constructor(loc: Location) : this(loc.x, loc.y, loc.z)
+
+ operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z)
+ operator fun plus(o: Vec3i) = Vec3d(x + o.x, y + o.y, z + o.z)
+ operator fun minus(o: Vec3d) = Vec3d(x - o.x, y - o.y, z - o.z)
+ operator fun minus(o: Vec3i) = Vec3d(x - o.x, y - o.y, z - o.z)
+ infix fun addX(o: Double) = Vec3d(x + o, y, z)
+ infix fun addY(o: Double) = Vec3d(x, y + o, z)
+ infix fun addZ(o: Double) = Vec3d(x, y, z + o)
+ infix fun withX(o: Double) = Vec3d(o, y, z)
+ infix fun withY(o: Double) = Vec3d(x, o, z)
+ infix fun withZ(o: Double) = Vec3d(x, y, o)
+ fun add(ox: Double, oy: Double, oz: Double) = Vec3d(x + ox, y + oy, z + oz)
+ fun toVec3i() = Vec3i(x.floor(), y.floor(), z.floor())
+
+ fun distanceSquared(o: Vec3d): Double {
+ val dx = o.x - x
+ val dy = o.y - y
+ val dz = o.z - z
+ return dx * dx + dy * dy + dz * dz
+ }
+
+ fun distance(o: Vec3d) = sqrt(distanceSquared(o))
+
+ operator fun get(dimension: Dimension) =
+ when (dimension) {
+ Dimension.X -> x
+ Dimension.Y -> y
+ Dimension.Z -> z
+ }
+
+ fun with(dimension: Dimension, value: Double) =
+ when (dimension) {
+ Dimension.X -> withX(value)
+ Dimension.Y -> withY(value)
+ Dimension.Z -> withZ(value)
+ }
+
+ fun add(dimension: Dimension, value: Double) =
+ when (dimension) {
+ Dimension.X -> addX(value)
+ Dimension.Y -> addY(value)
+ Dimension.Z -> addZ(value)
+ }
+
+ fun copyInto(loc: Location) {
+ loc.x = x
+ loc.y = y
+ loc.z = z
+ }
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt b/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt
index 484ad13..b3ba169 100644
--- a/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/math/Vec3i.kt
@@ -1,105 +1,107 @@
-package io.dico.parcels2.util.math
-
-import org.bukkit.Location
-import org.bukkit.World
-import org.bukkit.block.Block
-import org.bukkit.block.BlockFace
-
-data class Vec3i(
- val x: Int,
- val y: Int,
- val z: Int
-) {
- constructor(loc: Location) : this(loc.blockX, loc.blockY, loc.blockZ)
- constructor(block: Block) : this(block.x, block.y, block.z)
-
- fun toVec2i() = Vec2i(x, z)
- operator fun plus(o: Vec3i) = Vec3i(x + o.x, y + o.y, z + o.z)
- operator fun minus(o: Vec3i) = Vec3i(x - o.x, y - o.y, z - o.z)
- infix fun addX(o: Int) = Vec3i(x + o, y, z)
- infix fun addY(o: Int) = Vec3i(x, y + o, z)
- infix fun addZ(o: Int) = Vec3i(x, y, z + o)
- infix fun withX(o: Int) = Vec3i(o, y, z)
- infix fun withY(o: Int) = Vec3i(x, o, z)
- infix fun withZ(o: Int) = Vec3i(x, y, o)
- fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz)
- fun neg() = Vec3i(-x, -y, -z)
- fun clampMax(o: Vec3i) = Vec3i(x.clampMax(o.x), y.clampMax(o.y), z.clampMax(o.z))
-
- operator fun get(dimension: Dimension) =
- when (dimension) {
- Dimension.X -> x
- Dimension.Y -> y
- Dimension.Z -> z
- }
-
- fun with(dimension: Dimension, value: Int) =
- when (dimension) {
- Dimension.X -> withX(value)
- Dimension.Y -> withY(value)
- Dimension.Z -> withZ(value)
- }
-
- fun add(dimension: Dimension, value: Int) =
- when (dimension) {
- Dimension.X -> addX(value)
- Dimension.Y -> addY(value)
- Dimension.Z -> addZ(value)
- }
-
- companion object {
- private operator fun invoke(face: BlockFace) = Vec3i(face.modX, face.modY, face.modZ)
- val down = Vec3i(BlockFace.DOWN)
- val up = Vec3i(BlockFace.UP)
- val north = Vec3i(BlockFace.NORTH)
- val east = Vec3i(BlockFace.EAST)
- val south = Vec3i(BlockFace.SOUTH)
- val west = Vec3i(BlockFace.WEST)
-
- fun convert(face: BlockFace) = when (face) {
- BlockFace.DOWN -> down
- BlockFace.UP -> up
- BlockFace.NORTH -> north
- BlockFace.EAST -> east
- BlockFace.SOUTH -> south
- BlockFace.WEST -> west
- else -> Vec3i(face)
- }
- }
-}
-
-@Suppress("NOTHING_TO_INLINE")
-inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z)
-
-/*
-private /*inline */class IVec3i(private val data: Long) {
-
- private companion object {
- const val mask = 0x001F_FFFF
- const val max: Int = 0x000F_FFFF // +1048575
- const val min: Int = -max - 1 // -1048575 // 0xFFF0_0000
-
- @Suppress("NOTHING_TO_INLINE")
- inline fun Int.compressIntoLong(offset: Int): Long {
- if (this !in min..max) throw IllegalArgumentException()
- return and(mask).toLong().shl(offset)
- }
-
- @Suppress("NOTHING_TO_INLINE")
- inline fun Long.extractInt(offset: Int): Int {
- val result = ushr(offset).toInt().and(mask)
- return if (result > max) result or mask.inv() else result
- }
- }
-
- constructor(x: Int, y: Int, z: Int) : this(
- x.compressIntoLong(42)
- or y.compressIntoLong(21)
- or z.compressIntoLong(0))
-
- val x: Int get() = data.extractInt(42)
- val y: Int get() = data.extractInt(21)
- val z: Int get() = data.extractInt(0)
-
-}
-*/
+package io.dico.parcels2.util.math
+
+import org.bukkit.Location
+import org.bukkit.World
+import org.bukkit.block.Block
+import org.bukkit.block.BlockFace
+
+data class Vec3i(
+ val x: Int,
+ val y: Int,
+ val z: Int
+) {
+ constructor(loc: Location) : this(loc.blockX, loc.blockY, loc.blockZ)
+ constructor(block: Block) : this(block.x, block.y, block.z)
+
+ fun toVec2i() = Vec2i(x, z)
+ operator fun plus(o: Vec3i) = Vec3i(x + o.x, y + o.y, z + o.z)
+ operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z)
+ operator fun minus(o: Vec3i) = Vec3i(x - o.x, y - o.y, z - o.z)
+ operator fun minus(o: Vec3d) = Vec3d(x - o.x, y - o.y, z - o.z)
+ infix fun addX(o: Int) = Vec3i(x + o, y, z)
+ infix fun addY(o: Int) = Vec3i(x, y + o, z)
+ infix fun addZ(o: Int) = Vec3i(x, y, z + o)
+ infix fun withX(o: Int) = Vec3i(o, y, z)
+ infix fun withY(o: Int) = Vec3i(x, o, z)
+ infix fun withZ(o: Int) = Vec3i(x, y, o)
+ fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz)
+ fun neg() = Vec3i(-x, -y, -z)
+ fun clampMax(o: Vec3i) = Vec3i(x.clampMax(o.x), y.clampMax(o.y), z.clampMax(o.z))
+
+ operator fun get(dimension: Dimension) =
+ when (dimension) {
+ Dimension.X -> x
+ Dimension.Y -> y
+ Dimension.Z -> z
+ }
+
+ fun with(dimension: Dimension, value: Int) =
+ when (dimension) {
+ Dimension.X -> withX(value)
+ Dimension.Y -> withY(value)
+ Dimension.Z -> withZ(value)
+ }
+
+ fun add(dimension: Dimension, value: Int) =
+ when (dimension) {
+ Dimension.X -> addX(value)
+ Dimension.Y -> addY(value)
+ Dimension.Z -> addZ(value)
+ }
+
+ companion object {
+ private operator fun invoke(face: BlockFace) = Vec3i(face.modX, face.modY, face.modZ)
+ val down = Vec3i(BlockFace.DOWN)
+ val up = Vec3i(BlockFace.UP)
+ val north = Vec3i(BlockFace.NORTH)
+ val east = Vec3i(BlockFace.EAST)
+ val south = Vec3i(BlockFace.SOUTH)
+ val west = Vec3i(BlockFace.WEST)
+
+ fun convert(face: BlockFace) = when (face) {
+ BlockFace.DOWN -> down
+ BlockFace.UP -> up
+ BlockFace.NORTH -> north
+ BlockFace.EAST -> east
+ BlockFace.SOUTH -> south
+ BlockFace.WEST -> west
+ else -> Vec3i(face)
+ }
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z)
+
+/*
+private /*inline */class IVec3i(private val data: Long) {
+
+ private companion object {
+ const val mask = 0x001F_FFFF
+ const val max: Int = 0x000F_FFFF // +1048575
+ const val min: Int = -max - 1 // -1048575 // 0xFFF0_0000
+
+ @Suppress("NOTHING_TO_INLINE")
+ inline fun Int.compressIntoLong(offset: Int): Long {
+ if (this !in min..max) throw IllegalArgumentException()
+ return and(mask).toLong().shl(offset)
+ }
+
+ @Suppress("NOTHING_TO_INLINE")
+ inline fun Long.extractInt(offset: Int): Int {
+ val result = ushr(offset).toInt().and(mask)
+ return if (result > max) result or mask.inv() else result
+ }
+ }
+
+ constructor(x: Int, y: Int, z: Int) : this(
+ x.compressIntoLong(42)
+ or y.compressIntoLong(21)
+ or z.compressIntoLong(0))
+
+ val x: Int get() = data.extractInt(42)
+ val y: Int get() = data.extractInt(21)
+ val z: Int get() = data.extractInt(0)
+
+}
+*/
diff --git a/src/main/kotlin/io/dico/parcels2/util/parallel.kt b/src/main/kotlin/io/dico/parcels2/util/parallel.kt
new file mode 100644
index 0000000..a4edc3c
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/util/parallel.kt
@@ -0,0 +1,9 @@
+package io.dico.parcels2.util
+
+fun doParallel() {
+
+ val array = IntArray(1000)
+ IntRange(0, 1000).chunked()
+
+
+} \ No newline at end of file
diff --git a/todo.md b/todo.md
index cb073df..74a1dca 100644
--- a/todo.md
+++ b/todo.md
@@ -1,103 +1,103 @@
-# Parcels Todo list
-
-Commands
--
-Basically all admin commands.
-* ~~setowner~~
-* ~~dispose~~
-* ~~reset~~
-* ~~swap~~
-* New admin commands that I can't think of right now.
-
-Also
-* ~~setbiome~~
-* random
-
-~~Modify home command:~~
-* ~~Make `:` not be required if prior component cannot be parsed to an int~~
-* ~~Listen for command events that use plotme-style argument, and transform the command~~
-
-~~Add permissions to commands (replace or fix `IContextFilter` from command lib
-to allow inheriting permissions properly).~~
-
-Parcel Options
--
-
-Parcel options apply to any player with `DEFAULT` added status.
-They affect what their permissions might be within the parcel.
-
-Apart from `/p option inputs`, `/p option inventory`, the following might be considered.
-
-~~Move existing options to "interact" namespace (`/p o interact`)
-Add classes for different things you can interact with~~
-
-~~Then,~~
-~~* Split `/p option interact inputs` into a list of interactible block types.~~
-~~The list could include container blocks, merging the existing inventory option.~~
-* Players cannot launch projectiles in locations where they can't build.~~
-This could become optional.
-
-* Option to control spreading and/or forming of blocks such as grass and ice within the parcel.~~
-
-Block Management
--
-~~Update the parcel corner with owner info when a player flies into the parcel (after migrations).
-Parcels has a player head in that corner in addition to the sign that PlotMe uses.~~
-
-~~Commands that modify parcel blocks must be kept track of to prevent multiple
-from running simultaneously in the same parcel. `hasBlockVisitors` field must be updated.
-In general, spamming the commands must be caught at all cost to avoid lots of lag.~~
-
-~~Swap - schematic is in place, but proper placement order must be enforced to make sure that attachable
-blocks are placed properly. Alternatively, if a block change method can be found that doesn't
-cause block updates, that would be preferred subject to having good performance.~~
-
-~~Change `RegionTraversal` to allow traversing different parts of a region in a different order.
-This could apply to clearing of plots, for example. It would be better if the bottom 64 (floor height)
-layers are done upwards, and the rest downwards.~~
-
-Events
--
-Prevent block spreading subject to conditions.
-
-Scan through blocks that were added since original Parcels implementation,
-that might introduce things that need to be checked or listened for.
-
-~~WorldEdit Listener.~~
-
-Limit number of beacons in a parcel and/or avoid potion effects being applied outside the parcel.
-
-Database
--
-Find and patch ways to add new useless entries (for regular players at least)
-
-Prevent invalid player names from being saved to the database.
-Here, invalid player names mean names that contain invalid characters.
-
-Use an atomic GET OR INSERT query so that parallel execution doesn't cause problems
-(as is currently the case when migrating).
-
-Implement a container that doesn't require loading all parcel data on startup (Complex).
-
-~~Update player profiles in the database on join to account for name changes.~~
-
-~~Store player status on parcel (allowed, default banned) as a number to allow for future additions to this set of possibilities~~
-
-
-After testing on Redstoner
--
-
-Clear (and swap) entities on /p clear etc
-Fix command lag
-Chorus fruit can grow outside plots
-Vines can grow outside plots
-Ghasts, bats, phantoms and magma cubes can be spawned with eggs
-ParcelTarget doesn't report a world that wasn't found correctly
-Jumping on turtle eggs is considered as interacting with pressure plates
-Setbiome internal error when progress reporting is attached
-Unclaim doesn't clear the plot. It probably should.
-Players can shoot boats and minecarts.
-You can use disabled items by rightclicking air.
-Tab complete isn't working correctly.
-~~Bed use in nether and end might not have to be blocked.~~
-
+# Parcels Todo list
+
+Commands
+-
+Basically all admin commands.
+* ~~setowner~~
+* ~~dispose~~
+* ~~reset~~
+* ~~swap~~
+* New admin commands that I can't think of right now.
+
+Also
+* ~~setbiome~~
+* random
+
+~~Modify home command:~~
+* ~~Make `:` not be required if prior component cannot be parsed to an int~~
+* ~~Listen for command events that use plotme-style argument, and transform the command~~
+
+~~Add permissions to commands (replace or fix `IContextFilter` from command lib
+to allow inheriting permissions properly).~~
+
+Parcel Options
+-
+
+Parcel options apply to any player with `DEFAULT` added status.
+They affect what their permissions might be within the parcel.
+
+Apart from `/p option inputs`, `/p option inventory`, the following might be considered.
+
+~~Move existing options to "interact" namespace (`/p o interact`)
+Add classes for different things you can interact with~~
+
+~~Then,~~
+~~* Split `/p option interact inputs` into a list of interactible block types.~~
+~~The list could include container blocks, merging the existing inventory option.~~
+* Players cannot launch projectiles in locations where they can't build.~~
+This could become optional.
+
+* Option to control spreading and/or forming of blocks such as grass and ice within the parcel.~~
+
+Block Management
+-
+~~Update the parcel corner with owner info when a player flies into the parcel (after migrations).
+Parcels has a player head in that corner in addition to the sign that PlotMe uses.~~
+
+~~Commands that modify parcel blocks must be kept track of to prevent multiple
+from running simultaneously in the same parcel. `hasBlockVisitors` field must be updated.
+In general, spamming the commands must be caught at all cost to avoid lots of lag.~~
+
+~~Swap - schematic is in place, but proper placement order must be enforced to make sure that attachable
+blocks are placed properly. Alternatively, if a block change method can be found that doesn't
+cause block updates, that would be preferred subject to having good performance.~~
+
+~~Change `RegionTraversal` to allow traversing different parts of a region in a different order.
+This could apply to clearing of plots, for example. It would be better if the bottom 64 (floor height)
+layers are done upwards, and the rest downwards.~~
+
+Events
+-
+Prevent block spreading subject to conditions.
+
+Scan through blocks that were added since original Parcels implementation,
+that might introduce things that need to be checked or listened for.
+
+~~WorldEdit Listener.~~
+
+Limit number of beacons in a parcel and/or avoid potion effects being applied outside the parcel.
+
+Database
+-
+Find and patch ways to add new useless entries (for regular players at least)
+
+~~Prevent invalid player names from being saved to the database.
+Here, invalid player names mean names that contain invalid characters.~~
+
+Use an atomic GET OR INSERT query so that parallel execution doesn't cause problems
+(as is currently the case when migrating).
+
+Implement a container that doesn't require loading all parcel data on startup (Complex).
+
+~~Update player profiles in the database on join to account for name changes.~~
+
+~~Store player status on parcel (allowed, default banned) as a number to allow for future additions to this set of possibilities~~
+
+
+After testing on Redstoner
+-
+
+~~Clear (and swap) entities on /p clear etc~~
+Fix command lag
+Chorus fruit can grow outside plots
+Vines can grow outside plots
+Ghasts, bats, phantoms and magma cubes can be spawned with eggs
+ParcelTarget doesn't report a world that wasn't found correctly
+Jumping on turtle eggs is considered as interacting with pressure plates
+Setbiome internal error when progress reporting is attached
+Unclaim doesn't clear the plot. It probably should.
+Players can shoot boats and minecarts.
+You can use disabled items by rightclicking air.
+Tab complete isn't working correctly.
+~~Bed use in nether and end might not have to be blocked.~~
+