Merge upstream
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ build/
|
||||
target/
|
||||
/gradle-output.txt
|
||||
/*.java
|
||||
*.dump
|
||||
@@ -1,8 +1,7 @@
|
||||
# Parcels
|
||||
|
||||
Plot management and world generator plugin inspired by [PlotMe](https://github.com/WorldCretornica/PlotMe-Core).
|
||||
|
||||
Newer version of the discontinued [Parcels-Java](https://github.com/Dico200/Parcels-Java).
|
||||
Newer version of discontinued [Parcels](https://github.com/Dico200/Parcels-Java).
|
||||
|
||||
Written in Kotlin.
|
||||
This project is WIP.
|
||||
@@ -11,6 +10,5 @@ This project is WIP.
|
||||
|
||||
## Build
|
||||
|
||||
1. Add `worldedit-bukkit-7.0.0-beta-01.jar` to `/debug/plugins` directory
|
||||
2. Run `gradle releaseJar`
|
||||
3. Kotlin stdlib classpath is placed in `/debug/lib` directory and artifact can be found in `/debug/plugins` directory
|
||||
1. Run `gradle releaseJar`
|
||||
1. Kotlin stdlib classpath is placed in `/debug/lib` directory and artifact can be found in `/debug/plugins` directory
|
||||
|
||||
@@ -9,31 +9,32 @@ import java.net.URL
|
||||
val stdout = PrintWriter("gradle-output.txt")
|
||||
|
||||
group = "io.dico"
|
||||
version = "0.2"
|
||||
version = "0.3"
|
||||
|
||||
plugins {
|
||||
java
|
||||
kotlin("jvm") version "1.3.0-rc-57"
|
||||
kotlin("jvm") version "1.3.0"
|
||||
id("com.github.johnrengelman.plugin-shadow") version "2.0.3"
|
||||
}
|
||||
|
||||
|
||||
|
||||
allprojects {
|
||||
apply<JavaPlugin>()
|
||||
apply(plugin = "idea")
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots")
|
||||
maven("https://hub.spigotmc.org/nexus/content/repositories/sonatype-nexus-snapshots")
|
||||
maven("https://dl.bintray.com/kotlin/exposed")
|
||||
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
|
||||
maven("https://hub.spigotmc.org/nexus/content/repositories/sonatype-nexus-snapshots/")
|
||||
maven("https://dl.bintray.com/kotlin/exposed/")
|
||||
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")
|
||||
@@ -137,7 +141,7 @@ tasks {
|
||||
// spigot ships a later version in the root, so we must relocate ours
|
||||
relocate("org.yaml.snakeyaml.", "io.dico.parcels2.lib.org.yaml.snakeyaml.")
|
||||
|
||||
manifest.attributes["Class-Path"] = "../lib/kotlin-stdlib.jar"
|
||||
manifest.attributes["Class-Path"] = "../lib/kotlin-stdlib.jar ../lib/mariadb-java-client-2.2.6.jar"
|
||||
dependsOn(kotlinStdlibJar)
|
||||
}
|
||||
|
||||
@@ -146,11 +150,6 @@ tasks {
|
||||
|
||||
val jarUrl = URL("https://yivesmirror.com/files/spigot/spigot-latest.jar")
|
||||
val serverJarFile = file("$serverDir/lib/spigot.jar")
|
||||
|
||||
|
||||
doFirst {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,5 +170,7 @@ val ConfigurationContainer.`provided`: Configuration
|
||||
val ConfigurationContainer.`kotlinStd`: Configuration
|
||||
get() = findByName("kotlinStd") ?: create("kotlinStd").let { compileClasspath.extendsFrom(it) }
|
||||
|
||||
fun Jar.fromFiles(files: Iterable<File>) =
|
||||
fun Jar.fromFiles(files: Iterable<File>) {
|
||||
afterEvaluate { from(*files.map { if (it.isDirectory) it else zipTree(it) }.toTypedArray()) }
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
|
||||
|
||||
}
|
||||
|
||||
private static void debugChildren(ModifiableCommandAddress address) {
|
||||
public static void debugChildren(ModifiableCommandAddress address) {
|
||||
Collection<String> keys = address.getChildrenMainKeys();
|
||||
for (String key : keys) {
|
||||
ChildCommandAddress child = address.getChild(key);
|
||||
@@ -233,18 +233,19 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
|
||||
@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()) {
|
||||
List<String> out = Collections.emptyList();
|
||||
/*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;
|
||||
@@ -269,6 +270,11 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
|
||||
|
||||
} 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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ public class ContextParser {
|
||||
m_curRepeatingList = null;
|
||||
assignDefaultValuesToUncomputedParams();
|
||||
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.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;
|
||||
@@ -11,14 +13,11 @@ 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;
|
||||
private final int callFlags;
|
||||
|
||||
ReflectiveCommand(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
|
||||
if (!method.isAnnotationPresent(Cmd.class)) {
|
||||
@@ -48,7 +47,7 @@ public final class ReflectiveCommand extends Command {
|
||||
|
||||
this.method = method;
|
||||
this.instance = instance;
|
||||
this.flags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
|
||||
this.callFlags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
@@ -59,12 +58,22 @@ public final class ReflectiveCommand extends Command {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public String getCmdName() { return cmdAnnotation.value(); }
|
||||
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);
|
||||
@@ -86,54 +95,24 @@ public final class ReflectiveCommand extends Command {
|
||||
|
||||
@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
|
||||
CheckedSupplier<Object, CommandException> receiverFunction = () -> {
|
||||
try {
|
||||
args[i++] = ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName());
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
return callSynchronously(callArgs);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private String callAsCoroutine(ExecutionContext context, Object[] args) {
|
||||
return KotlinReflectiveRegistrationKt.callAsCoroutine(this, (ICommandInterceptor) instance, context, args);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ 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.
|
||||
*/
|
||||
@@ -196,47 +198,43 @@ public class ReflectiveRegistration {
|
||||
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();
|
||||
|
||||
boolean hasReceiverParameter = false;
|
||||
boolean hasSenderParameter = false;
|
||||
boolean hasContextParameter = false;
|
||||
boolean hasContinuationParameter = false;
|
||||
|
||||
int start = 0;
|
||||
int end = parameters.length;
|
||||
|
||||
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
|
||||
&& ICommandReceiver.class.isAssignableFrom(parameters[start].getType())) {
|
||||
hasReceiverParameter = true;
|
||||
start++;
|
||||
&& ICommandReceiver.class.isAssignableFrom(callParameters[start].getType())) {
|
||||
flags |= RECEIVER_BIT;
|
||||
++start;
|
||||
}
|
||||
|
||||
if (parameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = parameters[start].getType())) {
|
||||
hasSenderParameter = true;
|
||||
start++;
|
||||
if (callParameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = callParameters[start].getType())) {
|
||||
flags |= SENDER_BIT;
|
||||
++start;
|
||||
}
|
||||
|
||||
if (parameters.length > start && parameters[start].getType() == ExecutionContext.class) {
|
||||
hasContextParameter = true;
|
||||
start++;
|
||||
if (callParameters.length > start && callParameters[start].getType() == ExecutionContext.class) {
|
||||
flags |= CONTEXT_BIT;
|
||||
++start;
|
||||
}
|
||||
|
||||
if (parameters.length > start && parameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) {
|
||||
hasContinuationParameter = true;
|
||||
end--;
|
||||
if (callParameters.length > start && callParameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) {
|
||||
flags |= CONTINUATION_BIT;
|
||||
--end;
|
||||
}
|
||||
|
||||
String[] parameterNames = lookupParameterNames(method, parameters, start);
|
||||
String[] parameterNames = lookupParameterNames(method, callParameters, start);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
if (cmdPermissions != null) {
|
||||
@@ -277,6 +275,7 @@ public class ReflectiveRegistration {
|
||||
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)) {
|
||||
@@ -287,17 +286,9 @@ public class ReflectiveRegistration {
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,20 +17,23 @@ fun isSuspendFunction(method: Method): Boolean {
|
||||
return func.isSuspend
|
||||
}
|
||||
|
||||
fun callAsCoroutine(
|
||||
command: ReflectiveCommand,
|
||||
factory: ICommandInterceptor,
|
||||
context: ExecutionContext,
|
||||
@Throws(CommandException::class)
|
||||
fun callCommandAsCoroutine(
|
||||
executionContext: ExecutionContext,
|
||||
coroutineContext: CoroutineContext,
|
||||
continuationIndex: Int,
|
||||
method: Method,
|
||||
instance: Any?,
|
||||
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())
|
||||
args[continuationIndex] = cont.intercepted()
|
||||
method.invoke(instance, *args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +42,12 @@ fun callAsCoroutine(
|
||||
}
|
||||
|
||||
job.invokeOnCompletion {
|
||||
val chatHandler = context.address.chatHandler
|
||||
val chatHandler = executionContext.address.chatHandler
|
||||
try {
|
||||
val result = job.getResult()
|
||||
chatHandler.sendMessage(context.sender, EMessageType.RESULT, result)
|
||||
chatHandler.sendMessage(executionContext.sender, EMessageType.RESULT, result)
|
||||
} catch (ex: Throwable) {
|
||||
chatHandler.handleException(context.sender, context, ex)
|
||||
chatHandler.handleException(executionContext.sender, executionContext, ex)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
71
permissions.md
Normal file
71
permissions.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Permission Nodes
|
||||
|
||||
Node|Description
|
||||
---|---
|
||||
parcels.command.home | Access to `/p home` and aliases (example 1)
|
||||
parcels.command.option.interact.gates | Access to `/p option interact gates` and aliases (example 2)
|
||||
parcels.admin.bypass.ban | Ability to enter plots that listed a player as banned
|
||||
parcels.admin.bypass.build | Ability to build anywhere
|
||||
parcels.admin.bypass.gamemode | Be exempt from the world-specific gamemode enforcement
|
||||
parcels.command.home.others | Ability to use `/p home` for plots other than your own
|
||||
parcels.admin.manage | Admin rights. Required for staff commands and operations.
|
||||
|
||||
## All Commands
|
||||
|
||||
* parcel
|
||||
* parcel goto
|
||||
* parcel clear
|
||||
* parcel goto_fake
|
||||
* parcel claim
|
||||
* parcel auto
|
||||
* parcel tp
|
||||
* parcel home
|
||||
* parcel info
|
||||
* parcel setbiome
|
||||
* parcel ban
|
||||
* parcel disallow
|
||||
* parcel distrust
|
||||
* parcel allow
|
||||
* parcel unban
|
||||
* parcel entrust
|
||||
* parcel option
|
||||
* parcel option interact
|
||||
* parcel option interact buttons
|
||||
* parcel option interact levers
|
||||
* parcel option interact pressure_plates
|
||||
* parcel option interact redstone
|
||||
* parcel option interact containers
|
||||
* parcel option interact gates
|
||||
* parcel global
|
||||
* parcel global ban
|
||||
* parcel global disallow
|
||||
* parcel global distrust
|
||||
* parcel global allow
|
||||
* parcel global unban
|
||||
* parcel global entrust
|
||||
* parcel global list
|
||||
* parcel admin
|
||||
* parcel admin swap
|
||||
* parcel admin reset
|
||||
* parcel admin setowner
|
||||
* parcel admin dispose
|
||||
* parcel admin update_all_owner_signs
|
||||
* parcel admin global
|
||||
* parcel admin global ban
|
||||
* parcel admin global disallow
|
||||
* parcel admin global distrust
|
||||
* parcel admin global allow
|
||||
* parcel admin global unban
|
||||
* parcel admin global entrust
|
||||
* parcel admin global list
|
||||
* parcel debug
|
||||
* parcel debug permissions
|
||||
* parcel debug reloadoptions
|
||||
* parcel debug privilege
|
||||
* parcel debug complete_jobs
|
||||
* parcel debug jobs
|
||||
* parcel debug tpworld
|
||||
* parcel debug make_mess
|
||||
* parcel debug hasperm
|
||||
* parcel debug message
|
||||
* parcel debug directionality
|
||||
@@ -1,5 +1,6 @@
|
||||
pluginManagement.repositories {
|
||||
maven("http://dl.bintray.com/kotlin/kotlin-eap")
|
||||
maven("https://dl.bintray.com/kotlin/kotlin-eap/")
|
||||
maven("https://dl.bintray.com/kotlin/kotlin-dev/")
|
||||
mavenCentral()
|
||||
maven("https://plugins.gradle.org/m2/")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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
|
||||
@@ -74,7 +76,12 @@ interface Job : JobAndScopeMembersUnion {
|
||||
*
|
||||
* 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.
|
||||
@@ -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
|
||||
* 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
|
||||
private var bukkitTask: BukkitTask? = null
|
||||
// The jobs.
|
||||
@@ -146,18 +157,18 @@ class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJo
|
||||
override val jobs: List<Job> = _jobs
|
||||
|
||||
override fun dispatch(function: JobFunction): Job {
|
||||
val job: JobInternal = JobImpl(plugin, function)
|
||||
val job: JobInternal = JobImpl(scope, function)
|
||||
|
||||
if (bukkitTask == null) {
|
||||
val completed = job.resume(options.jobTime.toLong())
|
||||
if (completed) return job
|
||||
bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickCoroutineJobs() }
|
||||
bukkitTask = plugin.scheduleRepeating(options.tickInterval) { tickJobs() }
|
||||
}
|
||||
_jobs.addFirst(job)
|
||||
return job
|
||||
}
|
||||
|
||||
private fun tickCoroutineJobs() {
|
||||
private fun tickJobs() {
|
||||
val jobs = _jobs
|
||||
if (jobs.isEmpty()) return
|
||||
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() }
|
||||
if (asCompletionListener) onCompleted(block)
|
||||
if (isComplete) return this
|
||||
@@ -296,7 +312,11 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
|
||||
if (isStarted) {
|
||||
continuation?.let {
|
||||
continuation = null
|
||||
|
||||
wrapExternalCall {
|
||||
it.resume(Unit)
|
||||
}
|
||||
|
||||
return continuation == null
|
||||
}
|
||||
return true
|
||||
@@ -304,34 +324,45 @@ private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
|
||||
|
||||
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(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)
|
||||
|
||||
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
|
||||
get() = this@JobImpl.elapsedTime
|
||||
get() = parent.elapsedTime
|
||||
|
||||
override suspend fun markSuspensionPoint() =
|
||||
this@JobImpl.markSuspensionPoint()
|
||||
parent.markSuspensionPoint()
|
||||
|
||||
override val progress: Double
|
||||
get() = (this@JobImpl.progress - progressStart) / portion
|
||||
get() = (parent.progress - progressStart) / portion
|
||||
|
||||
override fun setProgress(progress: Double) =
|
||||
this@JobImpl.setProgress(progressStart + progress * portion)
|
||||
parent.setProgress(progressStart + progress * portion)
|
||||
|
||||
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 getEntities(parcel: ParcelId): Collection<Entity>
|
||||
fun getEntities(region: Region): Collection<Entity>
|
||||
|
||||
fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean
|
||||
|
||||
@@ -92,8 +92,7 @@ inline fun ParcelBlockManager.tryDoBlockOperation(
|
||||
|
||||
abstract class ParcelBlockManagerBase : ParcelBlockManager {
|
||||
|
||||
override fun getEntities(parcel: ParcelId): Collection<Entity> {
|
||||
val region = getRegion(parcel)
|
||||
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)
|
||||
|
||||
@@ -83,7 +83,7 @@ interface ParcelContainer {
|
||||
|
||||
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.storage.Storage
|
||||
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.isServerThread
|
||||
import io.dico.parcels2.util.scheduleRepeating
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.generator.ChunkGenerator
|
||||
@@ -29,7 +30,7 @@ import kotlin.coroutines.CoroutineContext
|
||||
val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
|
||||
private inline val plogger get() = logger
|
||||
|
||||
class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
|
||||
class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginAware {
|
||||
lateinit var optionsFile: File; private set
|
||||
lateinit var options: Options; 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 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() {
|
||||
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
|
||||
|
||||
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
|
||||
@@ -32,7 +34,7 @@ interface PlayerProfile {
|
||||
|
||||
companion object {
|
||||
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)
|
||||
return null
|
||||
}
|
||||
@@ -47,7 +49,7 @@ interface PlayerProfile {
|
||||
}
|
||||
|
||||
operator fun invoke(name: String): PlayerProfile {
|
||||
if (name == Star.name) return Star
|
||||
if (name equalsIgnoreCase Star.name) return Star
|
||||
return Fake(name)
|
||||
}
|
||||
|
||||
@@ -60,12 +62,17 @@ interface PlayerProfile {
|
||||
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 (!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)
|
||||
@@ -92,19 +99,19 @@ interface PlayerProfile {
|
||||
|
||||
companion object {
|
||||
fun byName(name: String): PlayerProfile {
|
||||
if (name == Star.name) return Star
|
||||
if (name equalsIgnoreCase Star.name) return Star
|
||||
return Unresolved(name)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
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 fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
|
||||
return allowNameMatch && player.name == name
|
||||
return allowNameMatch && player.name equalsIgnoreCase name
|
||||
}
|
||||
|
||||
override fun toString() = "${javaClass.simpleName}($name)"
|
||||
@@ -138,13 +145,17 @@ interface PlayerProfile {
|
||||
|
||||
class Fake(name: String) : NameOnly(name) {
|
||||
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) {
|
||||
init {
|
||||
checkPlayerNameValid(name)
|
||||
}
|
||||
|
||||
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? {
|
||||
@@ -171,11 +182,24 @@ interface PlayerProfile {
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
}*/
|
||||
@@ -113,6 +113,7 @@ class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : Ab
|
||||
return "Enjoy your new parcel!"
|
||||
}
|
||||
|
||||
/*
|
||||
@Cmd("unclaim")
|
||||
@Desc("Unclaims this parcel")
|
||||
@RequireParcelPrivilege(Privilege.OWNER)
|
||||
@@ -120,7 +121,7 @@ class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : Ab
|
||||
checkConnected("be unclaimed")
|
||||
parcel.dispose()
|
||||
return "Your parcel has been disposed"
|
||||
}
|
||||
}*/
|
||||
|
||||
@Cmd("clear")
|
||||
@RequireParcelPrivilege(Privilege.OWNER)
|
||||
|
||||
@@ -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.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
|
||||
@@ -53,6 +52,7 @@ annotation class ProfileKind(val kind: Int) {
|
||||
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
|
||||
@@ -62,13 +62,20 @@ annotation class ProfileKind(val kind: Int) {
|
||||
|
||||
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 allowReal = (info and REAL) != 0
|
||||
val allowFake = (info and FAKE) != 0
|
||||
|
||||
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(
|
||||
|
||||
@@ -25,7 +25,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
||||
|
||||
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()
|
||||
fun getParcel() = id?.let { world.getParcelById(it) }
|
||||
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 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
|
||||
|
||||
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) :
|
||||
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()!!
|
||||
val worldString = input.substringBefore("/", missingDelimiterValue = "")
|
||||
input = input.substringAfter("/")
|
||||
@@ -100,18 +106,26 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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") })
|
||||
return ByOwner(world,
|
||||
owner,
|
||||
index,
|
||||
kind,
|
||||
false,
|
||||
onResolveFailure = { invalidInput(parameter, "The player $input does not exist") })
|
||||
}
|
||||
|
||||
private fun getId(parameter: Parameter<*, *>, input: String): Vec2i {
|
||||
@@ -124,7 +138,12 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
||||
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 ownerString: String
|
||||
val index: Int?
|
||||
@@ -155,10 +174,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")
|
||||
}
|
||||
|
||||
val owner = if (ownerString.isEmpty())
|
||||
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)
|
||||
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)
|
||||
}
|
||||
@@ -168,7 +188,11 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
||||
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 useLocation = when {
|
||||
kind and REAL == REAL -> kind and PREFER_OWNED_FOR_DEFAULT == 0
|
||||
@@ -178,7 +202,8 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
||||
}
|
||||
|
||||
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) {
|
||||
val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos }
|
||||
return ByID(world, id, kind, true)
|
||||
|
||||
@@ -45,7 +45,7 @@ class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer {
|
||||
}
|
||||
}
|
||||
|
||||
override fun nextEmptyParcel(): Parcel? {
|
||||
override suspend fun nextEmptyParcel(): Parcel? {
|
||||
return walkInCircle().find { it.owner == null }
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ 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)
|
||||
@@ -285,6 +286,7 @@ class DefaultParcelGenerator(
|
||||
val floorType = o.floorType
|
||||
val fillType = o.fillType
|
||||
|
||||
delegateWork(0.95) {
|
||||
for ((index, vec) in blocks.withIndex()) {
|
||||
markSuspensionPoint()
|
||||
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> {
|
||||
/*
|
||||
* 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.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 {
|
||||
@@ -45,7 +51,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
||||
|
||||
private fun loadWorlds0() {
|
||||
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")
|
||||
return
|
||||
}
|
||||
@@ -64,7 +70,8 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
||||
WorldCreator(worldName).generator(generator).createWorld()
|
||||
}
|
||||
|
||||
parcelWorld = ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime,::DefaultParcelContainer)
|
||||
parcelWorld =
|
||||
ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime, ::DefaultParcelContainer)
|
||||
|
||||
if (!worldExists) {
|
||||
val time = DateTime.now()
|
||||
@@ -73,7 +80,8 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
||||
newlyCreatedWorlds.add(parcelWorld)
|
||||
} else {
|
||||
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()) {
|
||||
plugin.launch(Dispatchers.Default) {
|
||||
plugin.launch {
|
||||
val migration = plugin.options.migration
|
||||
if (migration.enabled) {
|
||||
migration.instance?.newInstance()?.apply {
|
||||
@@ -155,10 +163,32 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
||||
}
|
||||
|
||||
override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? {
|
||||
val blockManager1 = getWorldById(parcelId1.worldId)?.blockManager ?: return null
|
||||
val blockManager2 = getWorldById(parcelId2.worldId)?.blockManager ?: return null
|
||||
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)
|
||||
|
||||
@@ -168,10 +198,41 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
||||
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) } }
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class ParcelWorldImpl(
|
||||
|
||||
override fun getParcelById(id: ParcelId): Parcel? = container.getParcelById(id)
|
||||
|
||||
override fun nextEmptyParcel(): Parcel? = container.nextEmptyParcel()
|
||||
override suspend fun nextEmptyParcel(): Parcel? = container.nextEmptyParcel()
|
||||
|
||||
override fun toString() = parcelWorldIdToString()
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import io.dico.parcels2.storage.Storage
|
||||
import io.dico.parcels2.util.ext.*
|
||||
import io.dico.parcels2.util.math.*
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.Material.*
|
||||
import org.bukkit.World
|
||||
import org.bukkit.block.Biome
|
||||
@@ -209,7 +210,7 @@ class ParcelListeners(
|
||||
* Prevents player from using beds in HELL or SKY biomes if explosions are disabled.
|
||||
*/
|
||||
@Suppress("NON_EXHAUSTIVE_WHEN")
|
||||
@field:ListenerMarker(priority = NORMAL)
|
||||
@field:ListenerMarker(priority = NORMAL, ignoreCancelled = false)
|
||||
val onPlayerInteractEvent = RegistratorListener<PlayerInteractEvent> l@{ event ->
|
||||
val user = event.player
|
||||
val world = parcelProvider.getWorld(user.world) ?: return@l
|
||||
@@ -223,6 +224,7 @@ class ParcelListeners(
|
||||
|
||||
when (event.action) {
|
||||
Action.RIGHT_CLICK_BLOCK -> run {
|
||||
if (event.isCancelled) return@l
|
||||
val type = clickedBlock.type
|
||||
|
||||
val interactableClass = Interactables[type]
|
||||
@@ -259,12 +261,18 @@ class ParcelListeners(
|
||||
}
|
||||
|
||||
Action.RIGHT_CLICK_AIR -> onPlayerRightClick(event, world, parcel)
|
||||
Action.PHYSICAL -> if (!canBuildOnArea(user, parcel) && !(parcel != null && parcel.interactableConfig("pressure_plates"))) {
|
||||
Action.PHYSICAL -> if (!event.isCancelled && !canBuildOnArea(user, parcel)) {
|
||||
if (clickedBlock.type == Material.TURTLE_EGG) {
|
||||
event.isCancelled = true; return@l
|
||||
}
|
||||
|
||||
if (!(parcel != null && parcel.interactableConfig("pressure_plates"))) {
|
||||
user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
|
||||
event.isCancelled = true; return@l
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private val blockPlaceInteractItems = EnumSet.of(LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL)
|
||||
|
||||
@@ -437,13 +445,15 @@ class ParcelListeners(
|
||||
@field:ListenerMarker(priority = NORMAL)
|
||||
val onEntitySpawnEvent = RegistratorListener<EntitySpawnEvent> l@{ event ->
|
||||
val world = parcelProvider.getWorld(event.entity.world) ?: return@l
|
||||
if (event.entity is Creature && world.options.blockMobSpawning) {
|
||||
if (event.entity is Mob && world.options.blockMobSpawning) {
|
||||
event.isCancelled = true
|
||||
} else if (world.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) {
|
||||
event.isCancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Prevents minecarts/boats from moving outside a plot
|
||||
*/
|
||||
@@ -471,7 +481,7 @@ class ParcelListeners(
|
||||
@field:ListenerMarker(priority = NORMAL)
|
||||
val onEntityDamageByEntityEvent = RegistratorListener<EntityDamageByEntityEvent> l@{ event ->
|
||||
val world = parcelProvider.getWorld(event.entity.world) ?: return@l
|
||||
if (world.options.disableExplosions && event.damager is ExplosiveMinecart || event.damager is Creeper) {
|
||||
if (world.options.disableExplosions && (event.damager is ExplosiveMinecart || event.damager is Creeper)) {
|
||||
event.isCancelled = true; return@l
|
||||
}
|
||||
|
||||
@@ -538,6 +548,14 @@ class ParcelListeners(
|
||||
event.blocks.removeIf { world.getParcelAt(it.block) !== area }
|
||||
}
|
||||
|
||||
@field:ListenerMarker(priority = NORMAL)
|
||||
val onBlockGrowEvent = RegistratorListener<BlockGrowEvent> l@{ event ->
|
||||
val (world, area) = getWorldAndArea(event.block) ?: return@l
|
||||
if (area == null) {
|
||||
event.isCancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents dispensers/droppers from dispensing out of parcels
|
||||
*/
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package io.dico.parcels2.listener
|
||||
|
||||
import com.sk89q.worldedit.EditSession.Stage.BEFORE_REORDER
|
||||
import com.sk89q.worldedit.Vector
|
||||
import com.sk89q.worldedit.Vector2D
|
||||
import com.sk89q.worldedit.WorldEdit
|
||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin
|
||||
import com.sk89q.worldedit.event.extent.EditSessionEvent
|
||||
import com.sk89q.worldedit.extent.AbstractDelegateExtent
|
||||
import com.sk89q.worldedit.extent.Extent
|
||||
import com.sk89q.worldedit.math.BlockVector2
|
||||
import com.sk89q.worldedit.math.BlockVector3
|
||||
import com.sk89q.worldedit.util.eventbus.EventHandler.Priority.VERY_EARLY
|
||||
import com.sk89q.worldedit.util.eventbus.Subscribe
|
||||
import com.sk89q.worldedit.world.biome.BaseBiome
|
||||
@@ -57,14 +57,13 @@ class WorldEditListener(val parcels: ParcelsPlugin, val worldEdit: WorldEdit) {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setBlock(location: Vector, block: BlockStateHolder<*>): Boolean {
|
||||
return canBuild(location.blockX, location.blockZ) && super.setBlock(location, block)
|
||||
}
|
||||
|
||||
override fun setBiome(coord: Vector2D, biome: BaseBiome): Boolean {
|
||||
override fun setBiome(coord: BlockVector2, biome: BaseBiome): Boolean {
|
||||
return canBuild(coord.blockX, coord.blockZ) && super.setBiome(coord, biome)
|
||||
}
|
||||
|
||||
override fun <T : BlockStateHolder<T>> setBlock(location: BlockVector3, block: T): Boolean {
|
||||
return canBuild(location.blockX, location.blockZ) && super.setBlock(location, block)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -72,9 +72,11 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
|
||||
override fun init() {
|
||||
synchronized {
|
||||
if (isShutdown || isConnected) throw IllegalStateException()
|
||||
dataSource = dataSourceFactory()
|
||||
database = Database.connect(dataSource!!)
|
||||
transaction(database!!) {
|
||||
val dataSource = dataSourceFactory()
|
||||
this.dataSource = dataSource
|
||||
val database = Database.connect(dataSource)
|
||||
this.database = database
|
||||
transaction(database) {
|
||||
create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT)
|
||||
}
|
||||
}
|
||||
@@ -84,7 +86,7 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
|
||||
synchronized {
|
||||
if (isShutdown) throw IllegalStateException()
|
||||
isShutdown = true
|
||||
coroutineContext[Job]!!.cancel(CancellationException("ExposedBacking shutdown"))
|
||||
coroutineContext.cancel(CancellationException("ExposedBacking shutdown"))
|
||||
dataSource?.let {
|
||||
(it as? HikariDataSource)?.close()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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
|
||||
@@ -12,3 +13,11 @@ fun getOfflinePlayer(uuid: UUID): OfflinePlayer? = Bukkit.getOfflinePlayer(uuid)
|
||||
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")
|
||||
}
|
||||
|
||||
19
src/main/kotlin/io/dico/parcels2/util/PluginAware.kt
Normal file
19
src/main/kotlin/io/dico/parcels2/util/PluginAware.kt
Normal file
@@ -0,0 +1,19 @@
|
||||
@file:Suppress("RedundantLambdaArrow")
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
@@ -50,4 +52,10 @@ data class Vec3d(
|
||||
Dimension.Y -> addY(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)
|
||||
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)
|
||||
|
||||
26
todo.md
26
todo.md
@@ -71,8 +71,8 @@ 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.
|
||||
~~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).
|
||||
@@ -87,17 +87,17 @@ Implement a container that doesn't require loading all parcel data on startup (C
|
||||
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
|
||||
~~Clear (and swap) entities on /p clear etc~~
|
||||
~~Fix command lag~~
|
||||
Chorus fruit can grow outside plots -- not detectable?
|
||||
~~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.
|
||||
~~Unclaim doesn't clear the plot. It probably should.~~ removed
|
||||
Players can shoot boats and minecarts. -- ??
|
||||
~~You can use disabled items by rightclicking air.~~
|
||||
Tab complete isn't working correctly. -- disabled much of it now
|
||||
~~Bed use in nether and end might not have to be blocked.~~
|
||||
|
||||
|
||||
Reference in New Issue
Block a user