Archived
0

Merge upstream

This commit is contained in:
Dico Karssiens
2019-01-10 18:52:51 +00:00
187 changed files with 13983 additions and 13504 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ build/
target/ target/
/gradle-output.txt /gradle-output.txt
/*.java /*.java
*.dump

View File

@@ -1,8 +1,7 @@
# Parcels # Parcels
Plot management and world generator plugin inspired by [PlotMe](https://github.com/WorldCretornica/PlotMe-Core). Plot management and world generator plugin inspired by [PlotMe](https://github.com/WorldCretornica/PlotMe-Core).
Newer version of discontinued [Parcels](https://github.com/Dico200/Parcels-Java).
Newer version of the discontinued [Parcels-Java](https://github.com/Dico200/Parcels-Java).
Written in Kotlin. Written in Kotlin.
This project is WIP. This project is WIP.
@@ -11,6 +10,5 @@ This project is WIP.
## Build ## Build
1. Add `worldedit-bukkit-7.0.0-beta-01.jar` to `/debug/plugins` directory 1. Run `gradle releaseJar`
2. Run `gradle releaseJar` 1. Kotlin stdlib classpath is placed in `/debug/lib` directory and artifact can be found in `/debug/plugins` directory
3. Kotlin stdlib classpath is placed in `/debug/lib` directory and artifact can be found in `/debug/plugins` directory

View File

@@ -9,31 +9,32 @@ import java.net.URL
val stdout = PrintWriter("gradle-output.txt") val stdout = PrintWriter("gradle-output.txt")
group = "io.dico" group = "io.dico"
version = "0.2" version = "0.3"
plugins { plugins {
java 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" id("com.github.johnrengelman.plugin-shadow") version "2.0.3"
} }
allprojects { allprojects {
apply<JavaPlugin>() apply<JavaPlugin>()
apply(plugin = "idea") apply(plugin = "idea")
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://hub.spigotmc.org/nexus/content/repositories/sonatype-nexus-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/exposed/")
maven("https://dl.bintray.com/kotlin/kotlin-eap") maven("https://dl.bintray.com/kotlin/kotlin-dev/")
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")
@@ -137,7 +141,7 @@ tasks {
// spigot ships a later version in the root, so we must relocate ours // spigot ships a later version in the root, so we must relocate ours
relocate("org.yaml.snakeyaml.", "io.dico.parcels2.lib.org.yaml.snakeyaml.") 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) dependsOn(kotlinStdlibJar)
} }
@@ -146,11 +150,6 @@ tasks {
val jarUrl = URL("https://yivesmirror.com/files/spigot/spigot-latest.jar") val jarUrl = URL("https://yivesmirror.com/files/spigot/spigot-latest.jar")
val serverJarFile = file("$serverDir/lib/spigot.jar") val serverJarFile = file("$serverDir/lib/spigot.jar")
doFirst {
}
} }
} }
@@ -171,5 +170,7 @@ val ConfigurationContainer.`provided`: Configuration
val ConfigurationContainer.`kotlinStd`: Configuration 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>) {
afterEvaluate { from(*files.map { if (it.isDirectory) it else zipTree(it) }.toTypedArray()) } afterEvaluate { from(*files.map { if (it.isDirectory) it else zipTree(it) }.toTypedArray()) }
}

View File

@@ -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(); Collection<String> keys = address.getChildrenMainKeys();
for (String key : keys) { for (String key : keys) {
ChildCommandAddress child = address.getChild(key); ChildCommandAddress child = address.getChild(key);
@@ -233,18 +233,19 @@ 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);
List<String> out; List<String> out = Collections.emptyList();
if (target.hasCommand()) { /*if (target.hasCommand()) {
context.setCommand(target.getCommand()); context.setCommand(target.getCommand());
target.getCommand().initializeAndFilterContext(context); target.getCommand().initializeAndFilterContext(context);
out = target.getCommand().tabComplete(sender, context, location); out = target.getCommand().tabComplete(sender, context, location);
} else { } else {
out = Collections.emptyList(); out = Collections.emptyList();
} }*/
int cursor = buffer.getCursor(); int cursor = buffer.getCursor();
String input; String input;
@@ -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));
}
} }
} }

View File

@@ -89,6 +89,8 @@ public class ContextParser {
m_curRepeatingList = null; m_curRepeatingList = null;
assignDefaultValuesToUncomputedParams(); assignDefaultValuesToUncomputedParams();
arrayifyRepeatedParamValue(); arrayifyRepeatedParamValue();
m_done = true;
} }
} }

View File

@@ -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;
}
}

View File

@@ -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);
} }
} }

View File

@@ -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;
} }

View File

@@ -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)
} }
} }

71
permissions.md Normal file
View 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

View File

@@ -1,5 +1,6 @@
pluginManagement.repositories { 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() mavenCentral()
maven("https://plugins.gradle.org/m2/") maven("https://plugins.gradle.org/m2/")
} }

View File

@@ -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)
} }
} }

View File

@@ -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)

View File

@@ -83,7 +83,7 @@ interface ParcelContainer {
fun getParcelById(id: ParcelId): Parcel? fun getParcelById(id: ParcelId): Parcel?
fun nextEmptyParcel(): Parcel? suspend fun nextEmptyParcel(): Parcel?
} }

View File

@@ -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)
} }
} }

View File

@@ -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)

View 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
}
}*/

View File

@@ -113,6 +113,7 @@ class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : Ab
return "Enjoy your new parcel!" return "Enjoy your new parcel!"
} }
/*
@Cmd("unclaim") @Cmd("unclaim")
@Desc("Unclaims this parcel") @Desc("Unclaims this parcel")
@RequireParcelPrivilege(Privilege.OWNER) @RequireParcelPrivilege(Privilege.OWNER)
@@ -120,7 +121,7 @@ class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : Ab
checkConnected("be unclaimed") checkConnected("be unclaimed")
parcel.dispose() parcel.dispose()
return "Your parcel has been disposed" return "Your parcel has been disposed"
} }*/
@Cmd("clear") @Cmd("clear")
@RequireParcelPrivilege(Privilege.OWNER) @RequireParcelPrivilege(Privilege.OWNER)

View File

@@ -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(

View File

@@ -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("/")
@@ -100,18 +106,26 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
parcelProvider.getWorld(player.world) parcelProvider.getWorld(player.world)
?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world") ?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world")
} else { } 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 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 +138,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 +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") ?: 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 +188,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 +202,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)

View File

@@ -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 } return walkInCircle().find { it.owner == null }
} }

View File

@@ -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

View File

@@ -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)
} }
} }

View File

@@ -69,7 +69,7 @@ class ParcelWorldImpl(
override fun getParcelById(id: ParcelId): Parcel? = container.getParcelById(id) 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() override fun toString() = parcelWorldIdToString()
} }

View File

@@ -10,6 +10,7 @@ import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.ext.* import io.dico.parcels2.util.ext.*
import io.dico.parcels2.util.math.* import io.dico.parcels2.util.math.*
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.Material.* import org.bukkit.Material.*
import org.bukkit.World import org.bukkit.World
import org.bukkit.block.Biome 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. * Prevents player from using beds in HELL or SKY biomes if explosions are disabled.
*/ */
@Suppress("NON_EXHAUSTIVE_WHEN") @Suppress("NON_EXHAUSTIVE_WHEN")
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL, ignoreCancelled = false)
val onPlayerInteractEvent = RegistratorListener<PlayerInteractEvent> l@{ event -> val onPlayerInteractEvent = RegistratorListener<PlayerInteractEvent> l@{ event ->
val user = event.player val user = event.player
val world = parcelProvider.getWorld(user.world) ?: return@l val world = parcelProvider.getWorld(user.world) ?: return@l
@@ -223,6 +224,7 @@ class ParcelListeners(
when (event.action) { when (event.action) {
Action.RIGHT_CLICK_BLOCK -> run { Action.RIGHT_CLICK_BLOCK -> run {
if (event.isCancelled) return@l
val type = clickedBlock.type val type = clickedBlock.type
val interactableClass = Interactables[type] val interactableClass = Interactables[type]
@@ -259,12 +261,18 @@ class ParcelListeners(
} }
Action.RIGHT_CLICK_AIR -> onPlayerRightClick(event, world, parcel) 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") user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
event.isCancelled = true; return@l event.isCancelled = true; return@l
} }
} }
} }
}
// private val blockPlaceInteractItems = EnumSet.of(LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL) // private val blockPlaceInteractItems = EnumSet.of(LAVA_BUCKET, WATER_BUCKET, BUCKET, FLINT_AND_STEEL)
@@ -437,13 +445,15 @@ class ParcelListeners(
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onEntitySpawnEvent = RegistratorListener<EntitySpawnEvent> l@{ event -> val onEntitySpawnEvent = RegistratorListener<EntitySpawnEvent> l@{ event ->
val world = parcelProvider.getWorld(event.entity.world) ?: return@l 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 event.isCancelled = true
} else if (world.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) { } else if (world.getParcelAt(event.entity).let { it != null && it.hasBlockVisitors }) {
event.isCancelled = true event.isCancelled = true
} }
} }
/* /*
* Prevents minecarts/boats from moving outside a plot * Prevents minecarts/boats from moving outside a plot
*/ */
@@ -471,7 +481,7 @@ class ParcelListeners(
@field:ListenerMarker(priority = NORMAL) @field:ListenerMarker(priority = NORMAL)
val onEntityDamageByEntityEvent = RegistratorListener<EntityDamageByEntityEvent> l@{ event -> val onEntityDamageByEntityEvent = RegistratorListener<EntityDamageByEntityEvent> l@{ event ->
val world = parcelProvider.getWorld(event.entity.world) ?: return@l 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 event.isCancelled = true; return@l
} }
@@ -538,6 +548,14 @@ class ParcelListeners(
event.blocks.removeIf { world.getParcelAt(it.block) !== area } 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 * Prevents dispensers/droppers from dispensing out of parcels
*/ */

View File

@@ -1,13 +1,13 @@
package io.dico.parcels2.listener package io.dico.parcels2.listener
import com.sk89q.worldedit.EditSession.Stage.BEFORE_REORDER 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.WorldEdit
import com.sk89q.worldedit.bukkit.WorldEditPlugin import com.sk89q.worldedit.bukkit.WorldEditPlugin
import com.sk89q.worldedit.event.extent.EditSessionEvent import com.sk89q.worldedit.event.extent.EditSessionEvent
import com.sk89q.worldedit.extent.AbstractDelegateExtent import com.sk89q.worldedit.extent.AbstractDelegateExtent
import com.sk89q.worldedit.extent.Extent 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.EventHandler.Priority.VERY_EARLY
import com.sk89q.worldedit.util.eventbus.Subscribe import com.sk89q.worldedit.util.eventbus.Subscribe
import com.sk89q.worldedit.world.biome.BaseBiome import com.sk89q.worldedit.world.biome.BaseBiome
@@ -57,14 +57,13 @@ class WorldEditListener(val parcels: ParcelsPlugin, val worldEdit: WorldEdit) {
return false return false
} }
override fun setBlock(location: Vector, block: BlockStateHolder<*>): Boolean { override fun setBiome(coord: BlockVector2, biome: BaseBiome): Boolean {
return canBuild(location.blockX, location.blockZ) && super.setBlock(location, block)
}
override fun setBiome(coord: Vector2D, biome: BaseBiome): Boolean {
return canBuild(coord.blockX, coord.blockZ) && super.setBiome(coord, biome) 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 { companion object {

View File

@@ -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()
} }

View File

@@ -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")
}

View 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())
}

View File

@@ -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)

View File

@@ -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
}
} }

View File

@@ -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)

26
todo.md
View File

@@ -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,17 +87,17 @@ 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 -- not detectable?
Vines can grow outside plots ~~Vines can grow outside plots~~
Ghasts, bats, phantoms and magma cubes can be spawned with eggs ~~Ghasts, bats, phantoms and magma cubes can be spawned with eggs~~
ParcelTarget doesn't report a world that wasn't found correctly ParcelTarget doesn't report a world that wasn't found correctly -- ??
Jumping on turtle eggs is considered as interacting with pressure plates ~~Jumping on turtle eggs is considered as interacting with pressure plates~~
Setbiome internal error when progress reporting is attached Setbiome internal error when progress reporting is attached
Unclaim doesn't clear the plot. It probably should. ~~Unclaim doesn't clear the plot. It probably should.~~ removed
Players can shoot boats and minecarts. Players can shoot boats and minecarts. -- ??
You can use disabled items by rightclicking air. ~~You can use disabled items by rightclicking air.~~
Tab complete isn't working correctly. Tab complete isn't working correctly. -- disabled much of it now
~~Bed use in nether and end might not have to be blocked.~~ ~~Bed use in nether and end might not have to be blocked.~~