Tweak some command stuff, clear/swap entities
This commit is contained in:
@@ -13,7 +13,7 @@ version = "0.2"
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
java
|
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"
|
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-dev/")
|
||||||
maven("https://dl.bintray.com/kotlin/kotlin-eap/")
|
maven("https://dl.bintray.com/kotlin/kotlin-eap/")
|
||||||
maven("https://dl.bintray.com/kotlin/kotlinx/")
|
maven("https://dl.bintray.com/kotlin/kotlinx/")
|
||||||
|
maven("http://maven.sk89q.com/repo")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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.bukkit:bukkit:$spigotVersion") { isTransitive = false }
|
||||||
c.provided("org.spigotmc:spigot-api:$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") {
|
project(":dicore3:dicore3-command") {
|
||||||
apply<KotlinPlatformJvmPlugin>()
|
apply<KotlinPlatformJvmPlugin>()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
c.kotlinStd(kotlin("stdlib-jdk8"))
|
c.kotlinStd(kotlin("stdlib-jdk8"))
|
||||||
c.kotlinStd(kotlin("reflect"))
|
c.kotlinStd(kotlin("reflect"))
|
||||||
c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13"))
|
c.kotlinStd(coroutinesCore)
|
||||||
|
|
||||||
compile(project(":dicore3:dicore3-core"))
|
compile(project(":dicore3:dicore3-core"))
|
||||||
compile("com.thoughtworks.paranamer:paranamer:2.8")
|
compile("com.thoughtworks.paranamer:paranamer:2.8")
|
||||||
@@ -72,12 +75,13 @@ dependencies {
|
|||||||
|
|
||||||
c.kotlinStd(kotlin("stdlib-jdk8"))
|
c.kotlinStd(kotlin("stdlib-jdk8"))
|
||||||
c.kotlinStd(kotlin("reflect"))
|
c.kotlinStd(kotlin("reflect"))
|
||||||
c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13"))
|
c.kotlinStd(coroutinesCore)
|
||||||
c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.7-eap13")
|
c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.12")
|
||||||
|
|
||||||
// not on sk89q maven repo yet
|
// not on sk89q maven repo yet
|
||||||
compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar"))
|
//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/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("org.jetbrains.exposed:exposed:0.10.5") { isTransitive = false }
|
||||||
compile("joda-time:joda-time:2.10")
|
compile("joda-time:joda-time:2.10")
|
||||||
@@ -167,7 +171,6 @@ val ConfigurationContainer.`kotlinStd`: Configuration
|
|||||||
get() = findByName("kotlinStd") ?: create("kotlinStd").let { compileClasspath.extendsFrom(it) }
|
get() = findByName("kotlinStd") ?: create("kotlinStd").let { compileClasspath.extendsFrom(it) }
|
||||||
|
|
||||||
fun Jar.fromFiles(files: Iterable<File>) {
|
fun Jar.fromFiles(files: Iterable<File>) {
|
||||||
return
|
|
||||||
afterEvaluate { from(*files.map { if (it.isDirectory) it else zipTree(it) }.toTypedArray()) }
|
afterEvaluate { from(*files.map { if (it.isDirectory) it else zipTree(it) }.toTypedArray()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
|
|||||||
@Override
|
@Override
|
||||||
public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) {
|
public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) {
|
||||||
ExecutionContext context = new ExecutionContext(sender, buffer, true);
|
ExecutionContext context = new ExecutionContext(sender, buffer, true);
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ICommandAddress target = getCommandTarget(context, buffer);
|
ICommandAddress target = getCommandTarget(context, buffer);
|
||||||
@@ -269,6 +270,11 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
|
|||||||
|
|
||||||
} catch (CommandException ex) {
|
} catch (CommandException ex) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
} finally {
|
||||||
|
long duration = System.currentTimeMillis() - start;
|
||||||
|
if (duration > 2) {
|
||||||
|
System.out.println(String.format("Complete took %.3f seconds", duration / 1000.0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ public class ContextParser {
|
|||||||
m_curRepeatingList = null;
|
m_curRepeatingList = null;
|
||||||
assignDefaultValuesToUncomputedParams();
|
assignDefaultValuesToUncomputedParams();
|
||||||
arrayifyRepeatedParamValue();
|
arrayifyRepeatedParamValue();
|
||||||
|
|
||||||
|
m_done = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import io.dico.dicore.command.*;
|
|||||||
import io.dico.dicore.command.annotation.Cmd;
|
import io.dico.dicore.command.annotation.Cmd;
|
||||||
import io.dico.dicore.command.annotation.GenerateCommands;
|
import io.dico.dicore.command.annotation.GenerateCommands;
|
||||||
import io.dico.dicore.command.parameter.type.IParameterTypeSelector;
|
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 org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
@@ -11,14 +13,11 @@ import java.lang.reflect.Method;
|
|||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
public final class ReflectiveCommand extends Command {
|
public final class ReflectiveCommand extends Command {
|
||||||
private static final int continuationMask = 1 << 3;
|
|
||||||
private final Cmd cmdAnnotation;
|
private final Cmd cmdAnnotation;
|
||||||
private final Method method;
|
private final Method method;
|
||||||
private final Object instance;
|
private final Object instance;
|
||||||
private String[] parameterOrder;
|
private String[] parameterOrder;
|
||||||
|
private final int callFlags;
|
||||||
// hasContinuation | hasContext | hasSender | hasReceiver
|
|
||||||
private final int flags;
|
|
||||||
|
|
||||||
ReflectiveCommand(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
|
ReflectiveCommand(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
|
||||||
if (!method.isAnnotationPresent(Cmd.class)) {
|
if (!method.isAnnotationPresent(Cmd.class)) {
|
||||||
@@ -48,7 +47,7 @@ public final class ReflectiveCommand extends Command {
|
|||||||
|
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.flags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
|
this.callFlags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Method getMethod() {
|
public Method getMethod() {
|
||||||
@@ -59,12 +58,22 @@ public final class ReflectiveCommand extends Command {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCmdName() { return cmdAnnotation.value(); }
|
public String getCmdName() {
|
||||||
|
return cmdAnnotation.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCallFlags() {
|
||||||
|
return callFlags;
|
||||||
|
}
|
||||||
|
|
||||||
void setParameterOrder(String[] parameterOrder) {
|
void setParameterOrder(String[] parameterOrder) {
|
||||||
this.parameterOrder = parameterOrder;
|
this.parameterOrder = parameterOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getParameterNum() {
|
||||||
|
return parameterOrder.length;
|
||||||
|
}
|
||||||
|
|
||||||
ICommandAddress getAddress() {
|
ICommandAddress getAddress() {
|
||||||
ChildCommandAddress result = new ChildCommandAddress();
|
ChildCommandAddress result = new ChildCommandAddress();
|
||||||
result.setCommand(this);
|
result.setCommand(this);
|
||||||
@@ -86,54 +95,24 @@ public final class ReflectiveCommand extends Command {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String execute(CommandSender sender, ExecutionContext context) throws CommandException {
|
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];
|
CheckedSupplier<Object, CommandException> receiverFunction = () -> {
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
int mask = 1;
|
|
||||||
if ((flags & mask) != 0) {
|
|
||||||
// Has receiver
|
|
||||||
try {
|
try {
|
||||||
args[i++] = ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName());
|
return ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName());
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
handleException(ex);
|
handleException(ex);
|
||||||
return null; // unreachable
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
mask <<= 1;
|
return callSynchronously(callArgs);
|
||||||
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() {
|
private boolean isSuspendFunction() {
|
||||||
@@ -180,8 +159,11 @@ public final class ReflectiveCommand extends Command {
|
|||||||
throw new CommandException("An internal error occurred while executing this command.", ex);
|
throw new CommandException("An internal error occurred while executing this command.", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String callAsCoroutine(ExecutionContext context, Object[] args) {
|
private String callAsCoroutine(ExecutionContext executionContext, Object[] args) throws CommandException {
|
||||||
return KotlinReflectiveRegistrationKt.callAsCoroutine(this, (ICommandInterceptor) instance, context, args);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import java.util.function.Consumer;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.regex.PatternSyntaxException;
|
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.
|
* Takes care of turning a reflection {@link Method} into a command and more.
|
||||||
*/
|
*/
|
||||||
@@ -196,47 +198,43 @@ public class ReflectiveRegistration {
|
|||||||
return new ReflectiveCommand(selector, method, instance).getAddress();
|
return new ReflectiveCommand(selector, method, instance).getAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] parameters) throws CommandParseException {
|
static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] callParameters) throws CommandParseException {
|
||||||
ParameterList list = command.getParameterList();
|
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;
|
Class<?> senderParameterType = null;
|
||||||
|
int flags = 0;
|
||||||
|
int start = 0;
|
||||||
|
int end = callParameters.length;
|
||||||
|
|
||||||
if (parameters.length > start
|
if (callParameters.length > start
|
||||||
&& command.getInstance() instanceof ICommandInterceptor
|
&& command.getInstance() instanceof ICommandInterceptor
|
||||||
&& ICommandReceiver.class.isAssignableFrom(parameters[start].getType())) {
|
&& ICommandReceiver.class.isAssignableFrom(callParameters[start].getType())) {
|
||||||
hasReceiverParameter = true;
|
flags |= RECEIVER_BIT;
|
||||||
start++;
|
++start;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = parameters[start].getType())) {
|
if (callParameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = callParameters[start].getType())) {
|
||||||
hasSenderParameter = true;
|
flags |= SENDER_BIT;
|
||||||
start++;
|
++start;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters.length > start && parameters[start].getType() == ExecutionContext.class) {
|
if (callParameters.length > start && callParameters[start].getType() == ExecutionContext.class) {
|
||||||
hasContextParameter = true;
|
flags |= CONTEXT_BIT;
|
||||||
start++;
|
++start;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameters.length > start && parameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) {
|
if (callParameters.length > start && callParameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) {
|
||||||
hasContinuationParameter = true;
|
flags |= CONTINUATION_BIT;
|
||||||
end--;
|
--end;
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] parameterNames = lookupParameterNames(method, parameters, start);
|
String[] parameterNames = lookupParameterNames(method, callParameters, start);
|
||||||
for (int i = start, n = end; i < n; i++) {
|
for (int i = start, n = end; i < n; i++) {
|
||||||
Parameter<?, ?> parameter = parseParameter(selector, method, parameters[i], parameterNames[i - start]);
|
Parameter<?, ?> parameter = parseParameter(selector, method, callParameters[i], parameterNames[i - start]);
|
||||||
list.addParameter(parameter);
|
list.addParameter(parameter);
|
||||||
}
|
}
|
||||||
command.setParameterOrder(hasContinuationParameter ? Arrays.copyOfRange(parameterNames, 0, parameterNames.length - 1) : parameterNames);
|
|
||||||
|
command.setParameterOrder(hasCallArg(flags, CONTINUATION_BIT) ? Arrays.copyOfRange(parameterNames, 0, parameterNames.length - 1) : parameterNames);
|
||||||
|
|
||||||
RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class);
|
RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class);
|
||||||
if (cmdPermissions != null) {
|
if (cmdPermissions != null) {
|
||||||
@@ -277,6 +275,7 @@ public class ReflectiveRegistration {
|
|||||||
command.setDescription();
|
command.setDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean hasSenderParameter = hasCallArg(flags, SENDER_BIT);
|
||||||
if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) {
|
if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) {
|
||||||
command.addContextFilter(IContextFilter.PLAYER_ONLY);
|
command.addContextFilter(IContextFilter.PLAYER_ONLY);
|
||||||
} else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) {
|
} else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) {
|
||||||
@@ -287,17 +286,9 @@ public class ReflectiveRegistration {
|
|||||||
command.addContextFilter(IContextFilter.CONSOLE_ONLY);
|
command.addContextFilter(IContextFilter.CONSOLE_ONLY);
|
||||||
}
|
}
|
||||||
|
|
||||||
list.setRepeatFinalParameter(parameters.length > start && parameters[parameters.length - 1].isVarArgs());
|
list.setRepeatFinalParameter(callParameters.length > start && callParameters[callParameters.length - 1].isVarArgs());
|
||||||
list.setFinalParameterMayBeFlag(true);
|
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;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,20 +17,23 @@ fun isSuspendFunction(method: Method): Boolean {
|
|||||||
return func.isSuspend
|
return func.isSuspend
|
||||||
}
|
}
|
||||||
|
|
||||||
fun callAsCoroutine(
|
@Throws(CommandException::class)
|
||||||
command: ReflectiveCommand,
|
fun callCommandAsCoroutine(
|
||||||
factory: ICommandInterceptor,
|
executionContext: ExecutionContext,
|
||||||
context: ExecutionContext,
|
coroutineContext: CoroutineContext,
|
||||||
|
continuationIndex: Int,
|
||||||
|
method: Method,
|
||||||
|
instance: Any?,
|
||||||
args: Array<Any?>
|
args: Array<Any?>
|
||||||
): String? {
|
): 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,
|
// 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.
|
// 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.
|
// Tasks that take time to compute should suspend the coroutine and resume on another thread.
|
||||||
val job = GlobalScope.async(context = coroutineContext, start = UNDISPATCHED) {
|
val job = GlobalScope.async(context = coroutineContext, start = UNDISPATCHED) {
|
||||||
suspendCoroutineUninterceptedOrReturn<Any?> { cont ->
|
suspendCoroutineUninterceptedOrReturn<Any?> { cont ->
|
||||||
command.method.invoke(command.instance, *args, cont.intercepted())
|
args[continuationIndex] = cont.intercepted()
|
||||||
|
method.invoke(instance, *args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,12 +42,12 @@ fun callAsCoroutine(
|
|||||||
}
|
}
|
||||||
|
|
||||||
job.invokeOnCompletion {
|
job.invokeOnCompletion {
|
||||||
val chatHandler = context.address.chatHandler
|
val chatHandler = executionContext.address.chatHandler
|
||||||
try {
|
try {
|
||||||
val result = job.getResult()
|
val result = job.getResult()
|
||||||
chatHandler.sendMessage(context.sender, EMessageType.RESULT, result)
|
chatHandler.sendMessage(executionContext.sender, EMessageType.RESULT, result)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
chatHandler.handleException(context.sender, context, ex)
|
chatHandler.handleException(executionContext.sender, executionContext, ex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package io.dico.parcels2
|
package io.dico.parcels2
|
||||||
|
|
||||||
|
import io.dico.parcels2.util.PluginAware
|
||||||
import io.dico.parcels2.util.math.clampMin
|
import io.dico.parcels2.util.math.clampMin
|
||||||
|
import io.dico.parcels2.util.scheduleRepeating
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.CoroutineStart.LAZY
|
import kotlinx.coroutines.CoroutineStart.LAZY
|
||||||
@@ -74,7 +76,12 @@ interface Job : JobAndScopeMembersUnion {
|
|||||||
*
|
*
|
||||||
* if [asCompletionListener] is true, [onCompleted] is called with the same [block]
|
* if [asCompletionListener] is true, [onCompleted] is called with the same [block]
|
||||||
*/
|
*/
|
||||||
fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean = true, block: JobUpdateLister): Job
|
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.
|
* Calls the given [block] when this job completes, with the progress value 1.0.
|
||||||
@@ -138,7 +145,11 @@ interface JobInternal : Job, JobScope {
|
|||||||
* There is a configurable maxiumum amount of milliseconds that can be allocated to all jobs together in each server tick
|
* 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
|
* This object attempts to split that maximum amount of milliseconds equally between all jobs
|
||||||
*/
|
*/
|
||||||
class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJobtimeOptions) : JobDispatcher {
|
class BukkitJobDispatcher(
|
||||||
|
private val plugin: PluginAware,
|
||||||
|
private val scope: CoroutineScope,
|
||||||
|
var options: TickJobtimeOptions
|
||||||
|
) : JobDispatcher {
|
||||||
// The currently registered bukkit scheduler task
|
// The currently registered bukkit scheduler task
|
||||||
private var bukkitTask: BukkitTask? = null
|
private var bukkitTask: BukkitTask? = null
|
||||||
// The jobs.
|
// The jobs.
|
||||||
@@ -146,18 +157,18 @@ class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJo
|
|||||||
override val jobs: List<Job> = _jobs
|
override val jobs: List<Job> = _jobs
|
||||||
|
|
||||||
override fun dispatch(function: JobFunction): Job {
|
override fun dispatch(function: JobFunction): Job {
|
||||||
val job: JobInternal = JobImpl(plugin, function)
|
val job: JobInternal = JobImpl(scope, function)
|
||||||
|
|
||||||
if (bukkitTask == null) {
|
if (bukkitTask == null) {
|
||||||
val completed = job.resume(options.jobTime.toLong())
|
val completed = job.resume(options.jobTime.toLong())
|
||||||
if (completed) return job
|
if (completed) return job
|
||||||
bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickCoroutineJobs() }
|
bukkitTask = plugin.scheduleRepeating(options.tickInterval) { tickJobs() }
|
||||||
}
|
}
|
||||||
_jobs.addFirst(job)
|
_jobs.addFirst(job)
|
||||||
return job
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tickCoroutineJobs() {
|
private fun tickJobs() {
|
||||||
val jobs = _jobs
|
val jobs = _jobs
|
||||||
if (jobs.isEmpty()) return
|
if (jobs.isEmpty()) return
|
||||||
val tickStartTime = System.currentTimeMillis()
|
val tickStartTime = System.currentTimeMillis()
|
||||||
@@ -237,7 +248,12 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean, block: JobUpdateLister): Job {
|
override fun onProgressUpdate(
|
||||||
|
minDelay: Int,
|
||||||
|
minInterval: Int,
|
||||||
|
asCompletionListener: Boolean,
|
||||||
|
block: JobUpdateLister
|
||||||
|
): Job {
|
||||||
onProgressUpdate?.let { throw IllegalStateException() }
|
onProgressUpdate?.let { throw IllegalStateException() }
|
||||||
if (asCompletionListener) onCompleted(block)
|
if (asCompletionListener) onCompleted(block)
|
||||||
if (isComplete) return this
|
if (isComplete) return this
|
||||||
@@ -296,7 +312,11 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
|
|||||||
if (isStarted) {
|
if (isStarted) {
|
||||||
continuation?.let {
|
continuation?.let {
|
||||||
continuation = null
|
continuation = null
|
||||||
|
|
||||||
|
wrapExternalCall {
|
||||||
it.resume(Unit)
|
it.resume(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
return continuation == null
|
return continuation == null
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -304,34 +324,45 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
|
|||||||
|
|
||||||
isStarted = true
|
isStarted = true
|
||||||
startTimeOrElapsedTime = System.currentTimeMillis()
|
startTimeOrElapsedTime = System.currentTimeMillis()
|
||||||
|
|
||||||
|
wrapExternalCall {
|
||||||
coroutine.start()
|
coroutine.start()
|
||||||
|
}
|
||||||
|
|
||||||
return continuation == null
|
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() {
|
override suspend fun awaitCompletion() {
|
||||||
coroutine.join()
|
coroutine.join()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delegateProgress(curPortion: Double, portion: Double): JobScope =
|
private fun delegateProgress(curPortion: Double, portion: Double): JobScope =
|
||||||
DelegateScope(progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0))
|
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)
|
override fun delegateProgress(portion: Double): JobScope = delegateProgress(1.0, portion)
|
||||||
|
|
||||||
private inner class DelegateScope(val progressStart: Double, val portion: Double) : JobScope {
|
private class DelegateScope(val parent: JobImpl, val progressStart: Double, val portion: Double) : JobScope {
|
||||||
override val elapsedTime: Long
|
override val elapsedTime: Long
|
||||||
get() = this@JobImpl.elapsedTime
|
get() = parent.elapsedTime
|
||||||
|
|
||||||
override suspend fun markSuspensionPoint() =
|
override suspend fun markSuspensionPoint() =
|
||||||
this@JobImpl.markSuspensionPoint()
|
parent.markSuspensionPoint()
|
||||||
|
|
||||||
override val progress: Double
|
override val progress: Double
|
||||||
get() = (this@JobImpl.progress - progressStart) / portion
|
get() = (parent.progress - progressStart) / portion
|
||||||
|
|
||||||
override fun setProgress(progress: Double) =
|
override fun setProgress(progress: Double) =
|
||||||
this@JobImpl.setProgress(progressStart + progress * portion)
|
parent.setProgress(progressStart + progress * portion)
|
||||||
|
|
||||||
override fun delegateProgress(portion: Double): JobScope =
|
override fun delegateProgress(portion: Double): JobScope =
|
||||||
this@JobImpl.delegateProgress(this.portion, portion)
|
parent.delegateProgress(this.portion, portion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ interface ParcelBlockManager {
|
|||||||
|
|
||||||
fun getRegion(parcel: ParcelId): Region
|
fun getRegion(parcel: ParcelId): Region
|
||||||
|
|
||||||
fun getEntities(parcel: ParcelId): Collection<Entity>
|
fun getEntities(region: Region): Collection<Entity>
|
||||||
|
|
||||||
fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean
|
fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean
|
||||||
|
|
||||||
@@ -92,8 +92,7 @@ inline fun ParcelBlockManager.tryDoBlockOperation(
|
|||||||
|
|
||||||
abstract class ParcelBlockManagerBase : ParcelBlockManager {
|
abstract class ParcelBlockManagerBase : ParcelBlockManager {
|
||||||
|
|
||||||
override fun getEntities(parcel: ParcelId): Collection<Entity> {
|
override fun getEntities(region: Region): Collection<Entity> {
|
||||||
val region = getRegion(parcel)
|
|
||||||
val center = region.center
|
val center = region.center
|
||||||
val centerLoc = Location(world, center.x, center.y, center.z)
|
val centerLoc = Location(world, center.x, center.y, center.z)
|
||||||
val centerDist = (center - region.origin).add(0.2, 0.2, 0.2)
|
val centerDist = (center - region.origin).add(0.2, 0.2, 0.2)
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ interface ParcelContainer {
|
|||||||
|
|
||||||
fun getParcelById(id: ParcelId): Parcel?
|
fun getParcelById(id: ParcelId): Parcel?
|
||||||
|
|
||||||
fun nextEmptyParcel(): Parcel?
|
suspend fun nextEmptyParcel(): Parcel?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ import io.dico.parcels2.options.Options
|
|||||||
import io.dico.parcels2.options.optionsMapper
|
import io.dico.parcels2.options.optionsMapper
|
||||||
import io.dico.parcels2.storage.Storage
|
import io.dico.parcels2.storage.Storage
|
||||||
import io.dico.parcels2.util.MainThreadDispatcher
|
import io.dico.parcels2.util.MainThreadDispatcher
|
||||||
import io.dico.parcels2.util.PluginScheduler
|
import io.dico.parcels2.util.PluginAware
|
||||||
import io.dico.parcels2.util.ext.tryCreate
|
import io.dico.parcels2.util.ext.tryCreate
|
||||||
import io.dico.parcels2.util.isServerThread
|
import io.dico.parcels2.util.isServerThread
|
||||||
|
import io.dico.parcels2.util.scheduleRepeating
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import org.bukkit.Bukkit
|
import org.bukkit.Bukkit
|
||||||
import org.bukkit.generator.ChunkGenerator
|
import org.bukkit.generator.ChunkGenerator
|
||||||
@@ -29,7 +30,7 @@ import kotlin.coroutines.CoroutineContext
|
|||||||
val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
|
val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
|
||||||
private inline val plogger get() = logger
|
private inline val plogger get() = logger
|
||||||
|
|
||||||
class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
|
class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginAware {
|
||||||
lateinit var optionsFile: File; private set
|
lateinit var optionsFile: File; private set
|
||||||
lateinit var options: Options; private set
|
lateinit var options: Options; private set
|
||||||
lateinit var parcelProvider: ParcelProvider; private set
|
lateinit var parcelProvider: ParcelProvider; private set
|
||||||
@@ -43,7 +44,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
|
|||||||
|
|
||||||
override val coroutineContext: CoroutineContext = MainThreadDispatcher(this)
|
override val coroutineContext: CoroutineContext = MainThreadDispatcher(this)
|
||||||
override val plugin: Plugin get() = this
|
override val plugin: Plugin get() = this
|
||||||
val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, options.tickJobtime) }
|
val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, this, options.tickJobtime) }
|
||||||
|
|
||||||
override fun onEnable() {
|
override fun onEnable() {
|
||||||
plogger.info("Is server thread: ${isServerThread()}")
|
plogger.info("Is server thread: ${isServerThread()}")
|
||||||
@@ -147,7 +148,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleRepeating(100, 5, entityTracker::tick)
|
scheduleRepeating(5, delay = 100, task = entityTracker::tick)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,11 +3,13 @@
|
|||||||
package io.dico.parcels2
|
package io.dico.parcels2
|
||||||
|
|
||||||
import io.dico.parcels2.storage.Storage
|
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.PLAYER_NAME_PLACEHOLDER
|
||||||
import io.dico.parcels2.util.ext.isValid
|
import io.dico.parcels2.util.ext.isValid
|
||||||
import io.dico.parcels2.util.ext.uuid
|
import io.dico.parcels2.util.ext.uuid
|
||||||
import io.dico.parcels2.util.getOfflinePlayer
|
import io.dico.parcels2.util.getOfflinePlayer
|
||||||
import io.dico.parcels2.util.getPlayerName
|
import io.dico.parcels2.util.getPlayerName
|
||||||
|
import io.dico.parcels2.util.isPlayerNameValid
|
||||||
import org.bukkit.Bukkit
|
import org.bukkit.Bukkit
|
||||||
import org.bukkit.OfflinePlayer
|
import org.bukkit.OfflinePlayer
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@@ -32,7 +34,7 @@ interface PlayerProfile {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun safe(uuid: UUID?, name: String?): PlayerProfile? {
|
fun safe(uuid: UUID?, name: String?): PlayerProfile? {
|
||||||
if (uuid != null) return Real(uuid, name)
|
if (uuid != null) return Real(uuid, if (name != null && !isPlayerNameValid(name)) null else name)
|
||||||
if (name != null) return invoke(name)
|
if (name != null) return invoke(name)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -47,7 +49,7 @@ interface PlayerProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
operator fun invoke(name: String): PlayerProfile {
|
operator fun invoke(name: String): PlayerProfile {
|
||||||
if (name == Star.name) return Star
|
if (name equalsIgnoreCase Star.name) return Star
|
||||||
return Fake(name)
|
return Fake(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,12 +62,17 @@ interface PlayerProfile {
|
|||||||
return RealImpl(player.uuid, null)
|
return RealImpl(player.uuid, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun byName(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile {
|
fun byName(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile? {
|
||||||
if (!allowReal) {
|
if (!allowReal) {
|
||||||
if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true")
|
if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true")
|
||||||
return Fake(input)
|
return Fake(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isPlayerNameValid(input)) {
|
||||||
|
if (!allowFake) return null
|
||||||
|
return Fake(input)
|
||||||
|
}
|
||||||
|
|
||||||
if (input == Star.name) return Star
|
if (input == Star.name) return Star
|
||||||
|
|
||||||
return getOfflinePlayer(input)?.let { PlayerProfile(it) } ?: Unresolved(input)
|
return getOfflinePlayer(input)?.let { PlayerProfile(it) } ?: Unresolved(input)
|
||||||
@@ -92,19 +99,19 @@ interface PlayerProfile {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun byName(name: String): PlayerProfile {
|
fun byName(name: String): PlayerProfile {
|
||||||
if (name == Star.name) return Star
|
if (name equalsIgnoreCase Star.name) return Star
|
||||||
return Unresolved(name)
|
return Unresolved(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun invoke(uuid: UUID, name: String?): Real {
|
operator fun invoke(uuid: UUID, name: String?): Real {
|
||||||
if (name == Star.name || uuid == Star.uuid) return Star
|
if (name equalsIgnoreCase Star.name || uuid == Star.uuid) return Star
|
||||||
return RealImpl(uuid, name)
|
return RealImpl(uuid, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun safe(uuid: UUID?, name: String?): Real? {
|
fun safe(uuid: UUID?, name: String?): Real? {
|
||||||
if (name == Star.name || uuid == Star.uuid) return Star
|
if (name equalsIgnoreCase Star.name || uuid == Star.uuid) return Star
|
||||||
if (uuid == null) return null
|
if (uuid == null) return null
|
||||||
return RealImpl(uuid, name)
|
return RealImpl(uuid, if (name != null && !isPlayerNameValid(name)) null else name)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -130,7 +137,7 @@ interface PlayerProfile {
|
|||||||
override val nameOrBukkitName: String get() = name
|
override val nameOrBukkitName: String get() = name
|
||||||
|
|
||||||
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
|
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
|
||||||
return allowNameMatch && player.name == name
|
return allowNameMatch && player.name equalsIgnoreCase name
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString() = "${javaClass.simpleName}($name)"
|
override fun toString() = "${javaClass.simpleName}($name)"
|
||||||
@@ -138,13 +145,17 @@ interface PlayerProfile {
|
|||||||
|
|
||||||
class Fake(name: String) : NameOnly(name) {
|
class Fake(name: String) : NameOnly(name) {
|
||||||
override fun equals(other: PlayerProfile): Boolean {
|
override fun equals(other: PlayerProfile): Boolean {
|
||||||
return other is Fake && other.name == name
|
return other is Fake && other.name equalsIgnoreCase name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Unresolved(name: String) : NameOnly(name) {
|
class Unresolved(name: String) : NameOnly(name) {
|
||||||
|
init {
|
||||||
|
checkPlayerNameValid(name)
|
||||||
|
}
|
||||||
|
|
||||||
override fun equals(other: PlayerProfile): Boolean {
|
override fun equals(other: PlayerProfile): Boolean {
|
||||||
return other is Unresolved && name == other.name
|
return other is Unresolved && name equalsIgnoreCase other.name
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun tryResolveSuspendedly(storage: Storage): Real? {
|
suspend fun tryResolveSuspendedly(storage: Storage): Real? {
|
||||||
@@ -171,11 +182,24 @@ interface PlayerProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real {
|
private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real {
|
||||||
|
init {
|
||||||
|
name?.let { checkPlayerNameValid(it) }
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString() = "Real($notNullName)"
|
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? =
|
suspend fun PlayerProfile.resolved(storage: Storage, resolveToFake: Boolean = false): PlayerProfile? =
|
||||||
when (this) {
|
when (this) {
|
||||||
is PlayerProfile.Unresolved -> tryResolveSuspendedly(storage)
|
is PlayerProfile.Unresolved -> tryResolveSuspendedly(storage)
|
||||||
|
|||||||
38
src/main/kotlin/io/dico/parcels2/blockvisitor/Entities.kt
Normal file
38
src/main/kotlin/io/dico/parcels2/blockvisitor/Entities.kt
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}*/
|
||||||
@@ -6,7 +6,6 @@ import io.dico.dicore.command.parameter.Parameter
|
|||||||
import io.dico.dicore.command.parameter.type.ParameterConfig
|
import io.dico.dicore.command.parameter.type.ParameterConfig
|
||||||
import io.dico.dicore.command.parameter.type.ParameterType
|
import io.dico.dicore.command.parameter.type.ParameterType
|
||||||
import io.dico.parcels2.*
|
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.FAKE
|
||||||
import io.dico.parcels2.command.ProfileKind.Companion.REAL
|
import io.dico.parcels2.command.ProfileKind.Companion.REAL
|
||||||
import org.bukkit.Location
|
import org.bukkit.Location
|
||||||
@@ -53,6 +52,7 @@ annotation class ProfileKind(val kind: Int) {
|
|||||||
const val REAL = 1
|
const val REAL = 1
|
||||||
const val FAKE = 2
|
const val FAKE = 2
|
||||||
const val ANY = REAL or FAKE
|
const val ANY = REAL or FAKE
|
||||||
|
const val ALLOW_INVALID = 4
|
||||||
|
|
||||||
override fun toParameterInfo(annotation: ProfileKind): Int {
|
override fun toParameterInfo(annotation: ProfileKind): Int {
|
||||||
return annotation.kind
|
return annotation.kind
|
||||||
@@ -62,13 +62,20 @@ annotation class ProfileKind(val kind: Int) {
|
|||||||
|
|
||||||
class ProfileParameterType : ParameterType<PlayerProfile, Int>(PlayerProfile::class.java, ProfileKind) {
|
class ProfileParameterType : ParameterType<PlayerProfile, Int>(PlayerProfile::class.java, ProfileKind) {
|
||||||
|
|
||||||
override fun parse(parameter: Parameter<PlayerProfile, Int>, sender: CommandSender, buffer: ArgumentBuffer): PlayerProfile {
|
override fun parse(parameter: Parameter<PlayerProfile, Int>, sender: CommandSender, buffer: ArgumentBuffer): PlayerProfile? {
|
||||||
val info = parameter.paramInfo ?: REAL
|
val info = parameter.paramInfo ?: REAL
|
||||||
val allowReal = (info and REAL) != 0
|
val allowReal = (info and REAL) != 0
|
||||||
val allowFake = (info and FAKE) != 0
|
val allowFake = (info and FAKE) != 0
|
||||||
|
|
||||||
val input = buffer.next()!!
|
val input = buffer.next()!!
|
||||||
return PlayerProfile.byName(input, allowReal, allowFake)
|
|
||||||
|
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(
|
override fun complete(
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
|||||||
|
|
||||||
abstract suspend fun getParcelSuspend(storage: Storage): Parcel?
|
abstract suspend fun getParcelSuspend(storage: Storage): Parcel?
|
||||||
|
|
||||||
class ByID(world: ParcelWorld, val id: Vec2i?, parsedKind: Int, isDefault: Boolean) : ParcelTarget(world, parsedKind, isDefault) {
|
class ByID(world: ParcelWorld, val id: Vec2i?, parsedKind: Int, isDefault: Boolean) :
|
||||||
|
ParcelTarget(world, parsedKind, isDefault) {
|
||||||
override suspend fun getParcelSuspend(storage: Storage): Parcel? = getParcel()
|
override suspend fun getParcelSuspend(storage: Storage): Parcel? = getParcel()
|
||||||
fun getParcel() = id?.let { world.getParcelById(it) }
|
fun getParcel() = id?.let { world.getParcelById(it) }
|
||||||
val isPath: Boolean get() = id == null
|
val isPath: Boolean get() = id == null
|
||||||
@@ -78,7 +79,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
|||||||
|
|
||||||
const val DEFAULT_KIND = REAL
|
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
|
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
|
// instead of parcel that the player is in
|
||||||
|
|
||||||
override fun toParameterInfo(annotation: TargetKind): Int {
|
override fun toParameterInfo(annotation: TargetKind): Int {
|
||||||
@@ -90,7 +92,11 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
|||||||
class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) :
|
class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) :
|
||||||
ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, TargetKind) {
|
ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, TargetKind) {
|
||||||
|
|
||||||
override fun parse(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget {
|
override fun parse(
|
||||||
|
parameter: Parameter<ParcelTarget, Int>,
|
||||||
|
sender: CommandSender,
|
||||||
|
buffer: ArgumentBuffer
|
||||||
|
): ParcelTarget {
|
||||||
var input = buffer.next()!!
|
var input = buffer.next()!!
|
||||||
val worldString = input.substringBefore("/", missingDelimiterValue = "")
|
val worldString = input.substringBefore("/", missingDelimiterValue = "")
|
||||||
input = input.substringAfter("/")
|
input = input.substringAfter("/")
|
||||||
@@ -105,13 +111,20 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
|||||||
|
|
||||||
val kind = parameter.paramInfo ?: DEFAULT_KIND
|
val kind = parameter.paramInfo ?: DEFAULT_KIND
|
||||||
if (input.contains(',')) {
|
if (input.contains(',')) {
|
||||||
if (kind and ID == 0) invalidInput(parameter, "You must specify a parcel by OWNER, that is, an owner and index")
|
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)
|
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")
|
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)
|
val (owner, index) = getHomeIndex(parameter, kind, sender, input)
|
||||||
return ByOwner(world, owner, index, kind, false, onResolveFailure = { invalidInput(parameter, "The player $input does not exist") })
|
return ByOwner(world,
|
||||||
|
owner,
|
||||||
|
index,
|
||||||
|
kind,
|
||||||
|
false,
|
||||||
|
onResolveFailure = { invalidInput(parameter, "The player $input does not exist") })
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getId(parameter: Parameter<*, *>, input: String): Vec2i {
|
private fun getId(parameter: Parameter<*, *>, input: String): Vec2i {
|
||||||
@@ -124,7 +137,12 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
|||||||
return Vec2i(x, z)
|
return Vec2i(x, z)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getHomeIndex(parameter: Parameter<*, *>, kind: Int, sender: CommandSender, input: String): Pair<PlayerProfile, Int> {
|
private fun getHomeIndex(
|
||||||
|
parameter: Parameter<*, *>,
|
||||||
|
kind: Int,
|
||||||
|
sender: CommandSender,
|
||||||
|
input: String
|
||||||
|
): Pair<PlayerProfile, Int> {
|
||||||
val splitIdx = input.indexOf(':')
|
val splitIdx = input.indexOf(':')
|
||||||
val ownerString: String
|
val ownerString: String
|
||||||
val index: Int?
|
val index: Int?
|
||||||
@@ -155,10 +173,11 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
|||||||
?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
|
?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
|
||||||
}
|
}
|
||||||
|
|
||||||
val owner = if (ownerString.isEmpty())
|
val owner = (if (ownerString.isEmpty())
|
||||||
PlayerProfile(requirePlayer(sender, parameter, "the player"))
|
PlayerProfile(requirePlayer(sender, parameter, "the player"))
|
||||||
else
|
else
|
||||||
PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0)
|
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)
|
return owner to (index ?: 0)
|
||||||
}
|
}
|
||||||
@@ -168,7 +187,11 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
|||||||
return sender
|
return sender
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDefaultValue(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget? {
|
override fun getDefaultValue(
|
||||||
|
parameter: Parameter<ParcelTarget, Int>,
|
||||||
|
sender: CommandSender,
|
||||||
|
buffer: ArgumentBuffer
|
||||||
|
): ParcelTarget? {
|
||||||
val kind = parameter.paramInfo ?: DEFAULT_KIND
|
val kind = parameter.paramInfo ?: DEFAULT_KIND
|
||||||
val useLocation = when {
|
val useLocation = when {
|
||||||
kind and REAL == REAL -> kind and PREFER_OWNED_FOR_DEFAULT == 0
|
kind and REAL == REAL -> kind and PREFER_OWNED_FOR_DEFAULT == 0
|
||||||
@@ -178,7 +201,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
|||||||
}
|
}
|
||||||
|
|
||||||
val player = requirePlayer(sender, parameter, "the parcel")
|
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")
|
val world = parcelProvider.getWorld(player.world) ?: invalidInput(parameter,
|
||||||
|
"You must be in a parcel world to omit the parcel")
|
||||||
if (useLocation) {
|
if (useLocation) {
|
||||||
val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos }
|
val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos }
|
||||||
return ByID(world, id, kind, true)
|
return ByID(world, id, kind, true)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.bukkit.block.BlockFace
|
|||||||
import org.bukkit.block.Skull
|
import org.bukkit.block.Skull
|
||||||
import org.bukkit.block.data.type.Slab
|
import org.bukkit.block.data.type.Slab
|
||||||
import org.bukkit.block.data.type.WallSign
|
import org.bukkit.block.data.type.WallSign
|
||||||
|
import org.bukkit.entity.Player
|
||||||
import java.util.Random
|
import java.util.Random
|
||||||
|
|
||||||
private val airType = Bukkit.createBlockData(Material.AIR)
|
private val airType = Bukkit.createBlockData(Material.AIR)
|
||||||
@@ -285,6 +286,7 @@ class DefaultParcelGenerator(
|
|||||||
val floorType = o.floorType
|
val floorType = o.floorType
|
||||||
val fillType = o.fillType
|
val fillType = o.fillType
|
||||||
|
|
||||||
|
delegateWork(0.95) {
|
||||||
for ((index, vec) in blocks.withIndex()) {
|
for ((index, vec) in blocks.withIndex()) {
|
||||||
markSuspensionPoint()
|
markSuspensionPoint()
|
||||||
val y = vec.y
|
val y = vec.y
|
||||||
@@ -298,6 +300,17 @@ class DefaultParcelGenerator(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
|
||||||
/*
|
/*
|
||||||
* Get the offsets for the world out of the way
|
* Get the offsets for the world out of the way
|
||||||
|
|||||||
@@ -2,12 +2,18 @@ package io.dico.parcels2.defaultimpl
|
|||||||
|
|
||||||
import io.dico.parcels2.*
|
import io.dico.parcels2.*
|
||||||
import io.dico.parcels2.blockvisitor.Schematic
|
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 io.dico.parcels2.util.schedule
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.bukkit.Bukkit
|
import org.bukkit.Bukkit
|
||||||
|
import org.bukkit.World
|
||||||
import org.bukkit.WorldCreator
|
import org.bukkit.WorldCreator
|
||||||
|
import org.bukkit.entity.Entity
|
||||||
|
import org.bukkit.util.Vector
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
|
|
||||||
class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
||||||
@@ -45,7 +51,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
|||||||
|
|
||||||
private fun loadWorlds0() {
|
private fun loadWorlds0() {
|
||||||
if (Bukkit.getWorlds().isEmpty()) {
|
if (Bukkit.getWorlds().isEmpty()) {
|
||||||
plugin.schedule(::loadWorlds0)
|
plugin.schedule { loadWorlds0() }
|
||||||
plugin.logger.warning("Scheduling to load worlds in the next tick because no bukkit worlds are loaded yet")
|
plugin.logger.warning("Scheduling to load worlds in the next tick because no bukkit worlds are loaded yet")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -64,7 +70,8 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
|||||||
WorldCreator(worldName).generator(generator).createWorld()
|
WorldCreator(worldName).generator(generator).createWorld()
|
||||||
}
|
}
|
||||||
|
|
||||||
parcelWorld = ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime,::DefaultParcelContainer)
|
parcelWorld =
|
||||||
|
ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime, ::DefaultParcelContainer)
|
||||||
|
|
||||||
if (!worldExists) {
|
if (!worldExists) {
|
||||||
val time = DateTime.now()
|
val time = DateTime.now()
|
||||||
@@ -73,7 +80,8 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
|||||||
newlyCreatedWorlds.add(parcelWorld)
|
newlyCreatedWorlds.add(parcelWorld)
|
||||||
} else {
|
} else {
|
||||||
GlobalScope.launch(context = Dispatchers.Unconfined) {
|
GlobalScope.launch(context = Dispatchers.Unconfined) {
|
||||||
parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?: DateTime.now()
|
parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?:
|
||||||
|
DateTime.now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +92,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun loadStoredData(newlyCreatedWorlds: Collection<ParcelWorld> = emptyList()) {
|
private fun loadStoredData(newlyCreatedWorlds: Collection<ParcelWorld> = emptyList()) {
|
||||||
plugin.launch(Dispatchers.Default) {
|
plugin.launch {
|
||||||
val migration = plugin.options.migration
|
val migration = plugin.options.migration
|
||||||
if (migration.enabled) {
|
if (migration.enabled) {
|
||||||
migration.instance?.newInstance()?.apply {
|
migration.instance?.newInstance()?.apply {
|
||||||
@@ -155,10 +163,32 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? {
|
override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? {
|
||||||
val blockManager1 = getWorldById(parcelId1.worldId)?.blockManager ?: return null
|
val world1 = getWorldById(parcelId1.worldId) ?: return null
|
||||||
val blockManager2 = getWorldById(parcelId2.worldId)?.blockManager ?: 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) {
|
return trySubmitBlockVisitor(Permit(), parcelId1, parcelId2) {
|
||||||
|
val temporaryParcel = world1.nextEmptyParcel()
|
||||||
|
?: world2.nextEmptyParcel()
|
||||||
|
?: return@trySubmitBlockVisitor
|
||||||
|
|
||||||
var region1 = blockManager1.getRegion(parcelId1)
|
var region1 = blockManager1.getRegion(parcelId1)
|
||||||
var region2 = blockManager2.getRegion(parcelId2)
|
var region2 = blockManager2.getRegion(parcelId2)
|
||||||
|
|
||||||
@@ -168,10 +198,41 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
|||||||
region2 = region2.withSize(size)
|
region2 = region2.withSize(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
val schematicOf1 = delegateWork(0.25) { Schematic().apply { load(blockManager1.world, region1) } }
|
// Teleporting entities safely requires a different approach:
|
||||||
val schematicOf2 = delegateWork(0.25) { Schematic().apply { load(blockManager2.world, region2) } }
|
// * Copy schematic1 into temporary location
|
||||||
delegateWork(0.25) { with(schematicOf1) { paste(blockManager2.world, region2.origin) } }
|
// * Teleport entities1 into temporary location
|
||||||
delegateWork(0.25) { with(schematicOf2) { paste(blockManager1.world, region1.origin) } }
|
// * 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,9 +72,11 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
|
|||||||
override fun init() {
|
override fun init() {
|
||||||
synchronized {
|
synchronized {
|
||||||
if (isShutdown || isConnected) throw IllegalStateException()
|
if (isShutdown || isConnected) throw IllegalStateException()
|
||||||
dataSource = dataSourceFactory()
|
val dataSource = dataSourceFactory()
|
||||||
database = Database.connect(dataSource!!)
|
this.dataSource = dataSource
|
||||||
transaction(database!!) {
|
val database = Database.connect(dataSource)
|
||||||
|
this.database = database
|
||||||
|
transaction(database) {
|
||||||
create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT)
|
create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +86,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
|
|||||||
synchronized {
|
synchronized {
|
||||||
if (isShutdown) throw IllegalStateException()
|
if (isShutdown) throw IllegalStateException()
|
||||||
isShutdown = true
|
isShutdown = true
|
||||||
coroutineContext[Job]!!.cancel(CancellationException("ExposedBacking shutdown"))
|
coroutineContext.cancel(CancellationException("ExposedBacking shutdown"))
|
||||||
dataSource?.let {
|
dataSource?.let {
|
||||||
(it as? HikariDataSource)?.close()
|
(it as? HikariDataSource)?.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package io.dico.parcels2.util
|
|||||||
import io.dico.parcels2.util.ext.isValid
|
import io.dico.parcels2.util.ext.isValid
|
||||||
import org.bukkit.Bukkit
|
import org.bukkit.Bukkit
|
||||||
import org.bukkit.OfflinePlayer
|
import org.bukkit.OfflinePlayer
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
fun getPlayerName(uuid: UUID): String? = getOfflinePlayer(uuid)?.name
|
fun getPlayerName(uuid: UUID): String? = getOfflinePlayer(uuid)?.name
|
||||||
@@ -12,3 +13,11 @@ fun getOfflinePlayer(uuid: UUID): OfflinePlayer? = Bukkit.getOfflinePlayer(uuid)
|
|||||||
fun getOfflinePlayer(name: String): OfflinePlayer? = Bukkit.getOfflinePlayer(name).takeIf { it.isValid }
|
fun getOfflinePlayer(name: String): OfflinePlayer? = Bukkit.getOfflinePlayer(name).takeIf { it.isValid }
|
||||||
|
|
||||||
fun isServerThread(): Boolean = Thread.currentThread().name == "Server thread"
|
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")
|
||||||
|
}
|
||||||
|
|||||||
18
src/main/kotlin/io/dico/parcels2/util/PluginAware.kt
Normal file
18
src/main/kotlin/io/dico/parcels2/util/PluginAware.kt
Normal file
@@ -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())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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)
|
|
||||||
|
|
||||||
@@ -11,6 +11,8 @@ data class Vec3d(
|
|||||||
constructor(loc: Location) : this(loc.x, loc.y, loc.z)
|
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: 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)
|
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 addX(o: Double) = Vec3d(x + o, y, z)
|
||||||
infix fun addY(o: Double) = Vec3d(x, y + o, z)
|
infix fun addY(o: Double) = Vec3d(x, y + o, z)
|
||||||
@@ -50,4 +52,10 @@ data class Vec3d(
|
|||||||
Dimension.Y -> addY(value)
|
Dimension.Y -> addY(value)
|
||||||
Dimension.Z -> addZ(value)
|
Dimension.Z -> addZ(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun copyInto(loc: Location) {
|
||||||
|
loc.x = x
|
||||||
|
loc.y = y
|
||||||
|
loc.z = z
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,9 @@ data class Vec3i(
|
|||||||
|
|
||||||
fun toVec2i() = Vec2i(x, 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: 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: 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 addX(o: Int) = Vec3i(x + o, y, z)
|
||||||
infix fun addY(o: Int) = Vec3i(x, y + o, z)
|
infix fun addY(o: Int) = Vec3i(x, y + o, z)
|
||||||
infix fun addZ(o: Int) = Vec3i(x, y, z + o)
|
infix fun addZ(o: Int) = Vec3i(x, y, z + o)
|
||||||
|
|||||||
9
src/main/kotlin/io/dico/parcels2/util/parallel.kt
Normal file
9
src/main/kotlin/io/dico/parcels2/util/parallel.kt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package io.dico.parcels2.util
|
||||||
|
|
||||||
|
fun doParallel() {
|
||||||
|
|
||||||
|
val array = IntArray(1000)
|
||||||
|
IntRange(0, 1000).chunked()
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
6
todo.md
6
todo.md
@@ -71,8 +71,8 @@ Database
|
|||||||
-
|
-
|
||||||
Find and patch ways to add new useless entries (for regular players at least)
|
Find and patch ways to add new useless entries (for regular players at least)
|
||||||
|
|
||||||
Prevent invalid player names from being saved to the database.
|
~~Prevent invalid player names from being saved to the database.
|
||||||
Here, invalid player names mean names that contain invalid characters.
|
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
|
Use an atomic GET OR INSERT query so that parallel execution doesn't cause problems
|
||||||
(as is currently the case when migrating).
|
(as is currently the case when migrating).
|
||||||
@@ -87,7 +87,7 @@ Implement a container that doesn't require loading all parcel data on startup (C
|
|||||||
After testing on Redstoner
|
After testing on Redstoner
|
||||||
-
|
-
|
||||||
|
|
||||||
Clear (and swap) entities on /p clear etc
|
~~Clear (and swap) entities on /p clear etc~~
|
||||||
Fix command lag
|
Fix command lag
|
||||||
Chorus fruit can grow outside plots
|
Chorus fruit can grow outside plots
|
||||||
Vines can grow outside plots
|
Vines can grow outside plots
|
||||||
|
|||||||
Reference in New Issue
Block a user