summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.gradle.kts19
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/ChildCommandAddress.java3
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/CommandBuilder.java24
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/ExecutionContext.java9
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/ICommandReceiver.java5
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/ModifiableCommandAddress.java3
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java5
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/annotation/RequirePermissions.java3
-rw-r--r--dicore3/command/src/main/java/io/dico/dicore/command/predef/DefaultGroupCommand.java24
-rw-r--r--dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt37
-rw-r--r--gradle.properties1
-rw-r--r--settings.gradle.kts6
-rw-r--r--src/main/kotlin/io/dico/parcels2/AddedData.kt50
-rw-r--r--src/main/kotlin/io/dico/parcels2/Interactable.kt165
-rw-r--r--src/main/kotlin/io/dico/parcels2/Parcel.kt28
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt60
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelId.kt15
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelWorld.kt11
-rw-r--r--src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt31
-rw-r--r--src/main/kotlin/io/dico/parcels2/PlayerProfile.kt27
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt43
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt65
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt4
-rw-r--r--src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt79
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt31
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt36
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt5
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt105
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt3
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt3
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt4
-rw-r--r--src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt90
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt15
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt186
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt6
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt99
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt31
-rw-r--r--src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt51
-rw-r--r--src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt4
-rw-r--r--src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt102
-rw-r--r--src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt80
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Backing.kt23
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt38
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/Storage.kt35
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt78
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt46
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt13
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt2
-rw-r--r--src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt9
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt53
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/MainThreadDispatcher.kt31
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/PluginScheduler.kt20
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/Region.kt8
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt15
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/Vec2i.kt4
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/Vec3i.kt19
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/ext/Material.kt (renamed from src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt)34
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/ext/Math.kt (renamed from src/main/kotlin/io/dico/parcels2/util/NumberExtensions.kt)7
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/ext/Misc.kt (renamed from src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt)6
-rw-r--r--src/main/kotlin/io/dico/parcels2/util/ext/Player.kt (renamed from src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt)3
-rw-r--r--todo.md84
62 files changed, 1544 insertions, 554 deletions
diff --git a/build.gradle.kts b/build.gradle.kts
index fa4eb09..34a9737 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -14,12 +14,10 @@ version = "0.2"
plugins {
java
- kotlin("jvm") version "1.2.51"
+ kotlin("jvm") version "1.3.0-rc-57"
id("com.github.johnrengelman.plugin-shadow") version "2.0.3"
}
-kotlin.experimental.coroutines = ENABLE
-
allprojects {
apply<JavaPlugin>()
@@ -28,6 +26,8 @@ allprojects {
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://dl.bintray.com/kotlin/kotlinx/")
}
dependencies {
@@ -51,7 +51,7 @@ project(":dicore3:dicore3-command") {
dependencies {
c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect"))
- c.kotlinStd(kotlinx("coroutines-core:0.24.0"))
+ c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13"))
compile(project(":dicore3:dicore3-core"))
compile("com.thoughtworks.paranamer:paranamer:2.8")
@@ -59,17 +59,19 @@ project(":dicore3:dicore3-command") {
}
}
-
dependencies {
compile(project(":dicore3:dicore3-core"))
compile(project(":dicore3:dicore3-command"))
c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect"))
- c.kotlinStd(kotlinx("coroutines-core:0.23.4"))
- c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.0")
+ c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13"))
+ c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.7-rc-conf")
+
+ // not on sk89q maven repo yet
+ compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar"))
- compile("org.jetbrains.exposed:exposed:0.10.3") { isTransitive = false }
+ compile("org.jetbrains.exposed:exposed:0.10.5") { isTransitive = false }
compile("joda-time:joda-time:2.10")
compile("com.zaxxer:HikariCP:3.2.0")
compile("ch.qos.logback:logback-classic:1.2.3") { isTransitive = false }
@@ -131,6 +133,7 @@ tasks {
}
val createDebugServer by creating {
+ // todo
val jarUrl = URL("https://yivesmirror.com/files/spigot/spigot-latest.jar")
val serverJarFile = file("$serverDir/lib/spigot.jar")
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/ChildCommandAddress.java b/dicore3/command/src/main/java/io/dico/dicore/command/ChildCommandAddress.java
index 7593492..73d82ca 100644
--- a/dicore3/command/src/main/java/io/dico/dicore/command/ChildCommandAddress.java
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/ChildCommandAddress.java
@@ -1,5 +1,6 @@
package io.dico.dicore.command;
+import io.dico.dicore.command.predef.DefaultGroupCommand;
import io.dico.dicore.command.predef.HelpCommand;
import java.util.*;
@@ -23,7 +24,7 @@ public class ChildCommandAddress extends ModifiableCommandAddress {
}
public static ChildCommandAddress newPlaceHolderCommand(String name, String... aliases) {
- ChildCommandAddress rv = new ChildCommandAddress(null, name, aliases);
+ ChildCommandAddress rv = new ChildCommandAddress(DefaultGroupCommand.getInstance(), name, aliases);
HelpCommand.registerAsChild(rv);
return rv;
}
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/CommandBuilder.java b/dicore3/command/src/main/java/io/dico/dicore/command/CommandBuilder.java
index 0ffc960..e72d478 100644
--- a/dicore3/command/src/main/java/io/dico/dicore/command/CommandBuilder.java
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/CommandBuilder.java
@@ -210,9 +210,11 @@ public final class CommandBuilder {
* @param shortDescription a short description
* @param description the lines of a full description.
* @return this
+ * @throws IllegalStateException if the current group has no command
*/
public CommandBuilder setGroupDescription(String shortDescription, String... description) {
Command command = cur.getCommand();
+ if (command == null) throw new IllegalStateException();
cur.setCommand(command
.setShortDescription(shortDescription)
.setDescription(description));
@@ -220,6 +222,28 @@ public final class CommandBuilder {
}
/**
+ * Add a context filter to the command of the current group
+ * @return this
+ * @throws IllegalStateException if the current group has no command
+ */
+ public CommandBuilder addContextFilter(IContextFilter contextFilter) {
+ Command command = cur.getCommand();
+ if (command == null) throw new IllegalStateException();
+ cur.setCommand(command
+ .addContextFilter(contextFilter));
+ return this;
+ }
+
+ /**
+ * Add a required permission to the command of the current group
+ * @return this
+ * @throws IllegalStateException if the current group has no command
+ */
+ public CommandBuilder addRequiredPermission(String permission) {
+ return addContextFilter(IContextFilter.permission(permission));
+ }
+
+ /**
* Jump up a level in the address
*
* @return this
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/ExecutionContext.java b/dicore3/command/src/main/java/io/dico/dicore/command/ExecutionContext.java
index 4c014fb..4450a92 100644
--- a/dicore3/command/src/main/java/io/dico/dicore/command/ExecutionContext.java
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/ExecutionContext.java
@@ -200,6 +200,15 @@ public class ExecutionContext {
return originalBuffer.getArrayFromIndex(cursorStart);
}
+ /**
+ * The path used to access this address.
+ *
+ * @return the path used to access this address.
+ */
+ public String[] getRoute() {
+ return Arrays.copyOf(originalBuffer.toArray(), address.getDepth());
+ }
+
public Formatting getFormat(EMessageType type) {
return address.getChatController().getChatFormatForType(type);
}
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/ICommandReceiver.java b/dicore3/command/src/main/java/io/dico/dicore/command/ICommandReceiver.java
index 88b0d50..6660bf8 100644
--- a/dicore3/command/src/main/java/io/dico/dicore/command/ICommandReceiver.java
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/ICommandReceiver.java
@@ -12,6 +12,11 @@ public interface ICommandReceiver {
Plugin getPlugin();
+ // type is CoroutineContext, but we avoid referring to Kotlin runtime here
+ default Object getCoroutineContext() {
+ return null;
+ }
+
}
}
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/ModifiableCommandAddress.java b/dicore3/command/src/main/java/io/dico/dicore/command/ModifiableCommandAddress.java
index 698eee8..8c2ab67 100644
--- a/dicore3/command/src/main/java/io/dico/dicore/command/ModifiableCommandAddress.java
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/ModifiableCommandAddress.java
@@ -2,6 +2,7 @@ package io.dico.dicore.command;
import io.dico.dicore.command.chat.ChatControllers;
import io.dico.dicore.command.chat.IChatController;
+import io.dico.dicore.command.predef.DefaultGroupCommand;
import io.dico.dicore.command.predef.HelpCommand;
import io.dico.dicore.command.predef.PredefinedCommand;
@@ -32,7 +33,7 @@ public abstract class ModifiableCommandAddress implements ICommandAddress {
@Override
public boolean hasUserDeclaredCommand() {
Command command = getCommand();
- return command != null && !(command instanceof PredefinedCommand);
+ return command != null && !(command instanceof PredefinedCommand) && !(command instanceof DefaultGroupCommand);
}
@Override
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java b/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java
index 91dcc5b..6d38174 100644
--- a/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java
@@ -1,6 +1,7 @@
package io.dico.dicore.command;
import io.dico.dicore.command.parameter.ArgumentBuffer;
+import io.dico.dicore.command.predef.DefaultGroupCommand;
import io.dico.dicore.command.registration.BukkitCommand;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
@@ -167,10 +168,10 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
ModifiableCommandAddress targetAddress = getCommandTarget(sender, buffer);
Command target = targetAddress.getCommand();
- if (target == null) {
+ if (target == null || target instanceof DefaultGroupCommand) {
if (targetAddress.hasHelpCommand()) {
target = targetAddress.getHelpCommand().getCommand();
- } else {
+ } else if (target == null){
return false;
}
}
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/annotation/RequirePermissions.java b/dicore3/command/src/main/java/io/dico/dicore/command/annotation/RequirePermissions.java
index 0fbe9a4..d2ba782 100644
--- a/dicore3/command/src/main/java/io/dico/dicore/command/annotation/RequirePermissions.java
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/annotation/RequirePermissions.java
@@ -7,6 +7,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+/**
+ * If this annotation is not present, inheriting permissions is default.
+ */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequirePermissions {
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/predef/DefaultGroupCommand.java b/dicore3/command/src/main/java/io/dico/dicore/command/predef/DefaultGroupCommand.java
new file mode 100644
index 0000000..3d96a7d
--- /dev/null
+++ b/dicore3/command/src/main/java/io/dico/dicore/command/predef/DefaultGroupCommand.java
@@ -0,0 +1,24 @@
+package io.dico.dicore.command.predef;
+
+import io.dico.dicore.command.Command;
+import io.dico.dicore.command.CommandException;
+import io.dico.dicore.command.ExecutionContext;
+import org.bukkit.command.CommandSender;
+
+public class DefaultGroupCommand extends Command {
+ private static final DefaultGroupCommand instance = new DefaultGroupCommand();
+
+ public static DefaultGroupCommand getInstance() {
+ return instance;
+ }
+
+ private DefaultGroupCommand() {
+
+ }
+
+ @Override public String execute(CommandSender sender, ExecutionContext context) throws CommandException {
+ context.getAddress().getChatController().sendHelpMessage(sender, context, context.getAddress(), 1);
+ return null;
+ }
+
+}
diff --git a/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt b/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt
index 9d23ee6..c09088e 100644
--- a/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt
+++ b/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt
@@ -4,15 +4,15 @@ import io.dico.dicore.command.CommandException
import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.ICommandReceiver
-import kotlinx.coroutines.experimental.CoroutineStart.UNDISPATCHED
-import kotlinx.coroutines.experimental.Deferred
-import kotlinx.coroutines.experimental.asCoroutineDispatcher
-import kotlinx.coroutines.experimental.async
+import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
import java.lang.reflect.Method
-import java.util.*
import java.util.concurrent.CancellationException
-import java.util.concurrent.Executor
-import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.intrinsics.intercepted
+import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.reflect.jvm.kotlinFunction
fun isSuspendFunction(method: Method): Boolean {
@@ -20,16 +20,21 @@ fun isSuspendFunction(method: Method): Boolean {
return func.isSuspend
}
-fun callAsCoroutine(command: ReflectiveCommand,
- factory: ICommandReceiver.Factory,
- context: ExecutionContext,
- args: Array<Any?>): String? {
- val dispatcher = Executor { task -> factory.plugin.server.scheduler.runTask(factory.plugin, task) }.asCoroutineDispatcher()
+fun callAsCoroutine(
+ command: ReflectiveCommand,
+ factory: ICommandReceiver.Factory,
+ context: ExecutionContext,
+ args: Array<Any?>
+): String? {
// UNDISPATCHED causes the handler to run until the first suspension point on the current thread,
// meaning command handlers that don't have suspension points will run completely synchronously.
// Tasks that take time to compute should suspend the coroutine and resume on another thread.
- val job = async(context = dispatcher, start = UNDISPATCHED) { command.method.invokeSuspend(command.instance, args) }
+ val job = GlobalScope.async(context = factory.coroutineContext as CoroutineContext, start = UNDISPATCHED) {
+ suspendCoroutineUninterceptedOrReturn<Any?> { cont ->
+ command.method.invoke(command.instance, *args, cont.intercepted())
+ }
+ }
if (job.isCompleted) {
return job.getResult()
@@ -48,12 +53,6 @@ fun callAsCoroutine(command: ReflectiveCommand,
return null
}
-private suspend fun Method.invokeSuspend(instance: Any?, args: Array<Any?>): Any? {
- return suspendCoroutineOrReturn { cont ->
- invoke(instance, *args, cont)
- }
-}
-
@Throws(CommandException::class)
private fun Deferred<Any?>.getResult(): String? {
getCompletionExceptionOrNull()?.let { ex ->
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..29e08e8
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+kotlin.code.style=official \ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 020ed4e..2977cab 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,3 +1,9 @@
+pluginManagement.repositories {
+ maven("http://dl.bintray.com/kotlin/kotlin-eap")
+ mavenCentral()
+ maven("https://plugins.gradle.org/m2/")
+}
+
rootProject.name = "parcels2"
include("dicore3:core")
diff --git a/src/main/kotlin/io/dico/parcels2/AddedData.kt b/src/main/kotlin/io/dico/parcels2/AddedData.kt
index 9249c7f..a23b36e 100644
--- a/src/main/kotlin/io/dico/parcels2/AddedData.kt
+++ b/src/main/kotlin/io/dico/parcels2/AddedData.kt
@@ -1,7 +1,12 @@
package io.dico.parcels2
+import io.dico.parcels2.AddedStatus.*
import org.bukkit.OfflinePlayer
+enum class AddedStatus {
+ DEFAULT, ALLOWED, BANNED;
+}
+
typealias StatusKey = PlayerProfile.Real
typealias MutableAddedDataMap = MutableMap<StatusKey, AddedStatus>
typealias AddedDataMap = Map<StatusKey, AddedStatus>
@@ -11,20 +16,20 @@ fun MutableAddedDataMap(): MutableAddedDataMap = hashMapOf()
interface AddedData {
val addedMap: AddedDataMap
- var addedStatusOfStar: AddedStatus
+ var statusOfStar: AddedStatus
- fun getAddedStatus(key: StatusKey): AddedStatus
- fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean
+ fun getStatus(key: StatusKey): AddedStatus
+ fun setStatus(key: StatusKey, status: AddedStatus): Boolean
- fun compareAndSetAddedStatus(key: StatusKey, expect: AddedStatus, status: AddedStatus): Boolean =
- (getAddedStatus(key) == expect).also { if (it) setAddedStatus(key, status) }
+ fun casStatus(key: StatusKey, expect: AddedStatus, status: AddedStatus): Boolean =
+ getStatus(key) == expect && setStatus(key, status)
- fun isAllowed(key: StatusKey) = getAddedStatus(key) == AddedStatus.ALLOWED
- fun allow(key: StatusKey) = setAddedStatus(key, AddedStatus.ALLOWED)
- fun disallow(key: StatusKey) = compareAndSetAddedStatus(key, AddedStatus.ALLOWED, AddedStatus.DEFAULT)
- fun isBanned(key: StatusKey) = getAddedStatus(key) == AddedStatus.BANNED
- fun ban(key: StatusKey) = setAddedStatus(key, AddedStatus.BANNED)
- fun unban(key: StatusKey) = compareAndSetAddedStatus(key, AddedStatus.BANNED, AddedStatus.DEFAULT)
+ fun isAllowed(key: StatusKey) = getStatus(key) == ALLOWED
+ fun allow(key: StatusKey) = setStatus(key, ALLOWED)
+ fun disallow(key: StatusKey) = casStatus(key, ALLOWED, DEFAULT)
+ fun isBanned(key: StatusKey) = getStatus(key) == BANNED
+ fun ban(key: StatusKey) = setStatus(key, BANNED)
+ fun unban(key: StatusKey) = casStatus(key, BANNED, DEFAULT)
fun isAllowed(player: OfflinePlayer) = isAllowed(player.statusKey)
fun allow(player: OfflinePlayer) = allow(player.statusKey)
@@ -34,29 +39,20 @@ interface AddedData {
fun unban(player: OfflinePlayer) = unban(player.statusKey)
}
-inline val OfflinePlayer.statusKey get() = PlayerProfile.nameless(this)
+inline val OfflinePlayer.statusKey: StatusKey
+ get() = PlayerProfile.nameless(this)
-open class AddedDataHolder(override var addedMap: MutableAddedDataMap = mutableMapOf()) : AddedData {
- override var addedStatusOfStar: AddedStatus = AddedStatus.DEFAULT
+open class AddedDataHolder(override var addedMap: MutableAddedDataMap = MutableAddedDataMap()) : AddedData {
+ override var statusOfStar: AddedStatus = DEFAULT
- override fun getAddedStatus(key: StatusKey): AddedStatus = addedMap.getOrDefault(key, addedStatusOfStar)
+ override fun getStatus(key: StatusKey): AddedStatus = addedMap.getOrDefault(key, statusOfStar)
- override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean {
- return if (status.isDefault) addedMap.remove(key) != null
+ override fun setStatus(key: StatusKey, status: AddedStatus): Boolean {
+ return if (status == DEFAULT) addedMap.remove(key) != null
else addedMap.put(key, status) != status
}
}
-enum class AddedStatus {
- DEFAULT,
- ALLOWED,
- BANNED;
-
- inline val isDefault get() = this == DEFAULT
- inline val isAllowed get() = this == ALLOWED
- inline val isBanned get() = this == BANNED
-}
-
interface GlobalAddedData : AddedData {
val owner: PlayerProfile
}
diff --git a/src/main/kotlin/io/dico/parcels2/Interactable.kt b/src/main/kotlin/io/dico/parcels2/Interactable.kt
new file mode 100644
index 0000000..60539aa
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/Interactable.kt
@@ -0,0 +1,165 @@
+package io.dico.parcels2
+
+import io.dico.parcels2.util.ext.getMaterialsWithWoodTypePrefix
+import org.bukkit.Material
+import java.lang.IllegalArgumentException
+import java.util.EnumMap
+
+class Interactables
+private constructor(
+ val id: Int,
+ val name: String,
+ val interactableByDefault: Boolean,
+ vararg val materials: Material
+) {
+
+ companion object {
+ val classesById: List<Interactables>
+ val classesByName: Map<String, Interactables>
+ val listedMaterials: Map<Material, Int>
+
+ init {
+ val array = getClassesArray()
+ classesById = array.asList()
+ classesByName = mapOf(*array.map { it.name to it }.toTypedArray())
+ listedMaterials = EnumMap(mapOf(*array.flatMap { clazz -> clazz.materials.map { it to clazz.id } }.toTypedArray()))
+ }
+
+ operator fun get(material: Material): Interactables? {
+ val id = listedMaterials[material] ?: return null
+ return classesById[id]
+ }
+
+ operator fun get(name: String): Interactables {
+ return classesByName[name] ?: throw IllegalArgumentException("Interactables class does not exist: $name")
+ }
+
+ operator fun get(id: Int): Interactables {
+ return classesById[id]
+ }
+
+ private fun getClassesArray() = run {
+ var id = 0
+ @Suppress("UNUSED_CHANGED_VALUE")
+ arrayOf(
+ Interactables(
+ id++, "buttons", true,
+ Material.STONE_BUTTON,
+ *getMaterialsWithWoodTypePrefix("BUTTON")
+ ),
+
+ Interactables(
+ id++, "levers", true,
+ Material.LEVER
+ ),
+
+ Interactables(
+ id++, "pressure_plates", true,
+ Material.STONE_PRESSURE_PLATE,
+ *getMaterialsWithWoodTypePrefix("PRESSURE_PLATE"),
+ Material.HEAVY_WEIGHTED_PRESSURE_PLATE,
+ Material.LIGHT_WEIGHTED_PRESSURE_PLATE
+ ),
+
+ Interactables(
+ id++, "redstone", false,
+ Material.COMPARATOR,
+ Material.REPEATER
+ ),
+
+ Interactables(
+ id++, "containers", false,
+ Material.CHEST,
+ Material.TRAPPED_CHEST,
+ Material.DISPENSER,
+ Material.DROPPER,
+ Material.HOPPER,
+ Material.FURNACE
+ ),
+
+ Interactables(
+ id++, "gates", true,
+ *getMaterialsWithWoodTypePrefix("DOOR"),
+ *getMaterialsWithWoodTypePrefix("TRAPDOOR"),
+ *getMaterialsWithWoodTypePrefix("FENCE_GATE")
+ )
+ )
+ }
+
+ }
+
+}
+
+val Parcel?.effectiveInteractableConfig: InteractableConfiguration
+ get() = this?.interactableConfig ?: pathInteractableConfig
+
+val pathInteractableConfig: InteractableConfiguration = run {
+ val data = BitmaskInteractableConfiguration().apply {
+ Interactables.classesById.forEach {
+ setInteractable(it, false)
+ }
+ }
+ object : InteractableConfiguration by data {
+ override fun setInteractable(clazz: Interactables, interactable: Boolean) =
+ throw IllegalStateException("pathInteractableConfig is immutable")
+
+ override fun clear() =
+ throw IllegalStateException("pathInteractableConfig is immutable")
+
+ override fun copyFrom(other: InteractableConfiguration) =
+ throw IllegalStateException("pathInteractableConfig is immutable")
+ }
+}
+
+interface InteractableConfiguration {
+ val interactableClasses: List<Interactables> get() = Interactables.classesById.filter { isInteractable(it) }
+ fun isInteractable(material: Material): Boolean
+ fun isInteractable(clazz: Interactables): Boolean
+ fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean
+ fun clear(): Boolean
+ fun copyFrom(other: InteractableConfiguration) {
+ Interactables.classesById.forEach { setInteractable(it, other.isInteractable(it)) }
+ }
+
+ operator fun invoke(material: Material) = isInteractable(material)
+ operator fun invoke(className: String) = isInteractable(Interactables[className])
+}
+
+fun InteractableConfiguration.isInteractable(clazz: Interactables?) = clazz != null && isInteractable(clazz)
+
+class BitmaskInteractableConfiguration : InteractableConfiguration {
+ val bitmaskArray = IntArray((Interactables.classesById.size + 31) / 32)
+
+ private fun isBitSet(classId: Int): Boolean {
+ val idx = classId.ushr(5)
+ return idx < bitmaskArray.size && bitmaskArray[idx].and(0x1.shl(classId.and(0x1F))) != 0
+ }
+
+ override fun isInteractable(material: Material): Boolean {
+ val classId = Interactables.listedMaterials[material] ?: return false
+ return isBitSet(classId) != Interactables.classesById[classId].interactableByDefault
+ }
+
+ override fun isInteractable(clazz: Interactables): Boolean {
+ return isBitSet(clazz.id) != clazz.interactableByDefault
+ }
+
+ override fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean {
+ val idx = clazz.id.ushr(5)
+ if (idx >= bitmaskArray.size) return false
+ val bit = 0x1.shl(clazz.id.and(0x1F))
+ val oldBitmask = bitmaskArray[idx]
+ bitmaskArray[idx] = if (interactable != clazz.interactableByDefault) oldBitmask.or(bit) else oldBitmask.and(bit.inv())
+ return bitmaskArray[idx] != oldBitmask
+ }
+
+ override fun clear(): Boolean {
+ var change = false
+ for (i in bitmaskArray.indices) {
+ if (!change && bitmaskArray[i] != 0) change = true
+ bitmaskArray[i] = 0
+ }
+ return change
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt
index e7c5209..2c56627 100644
--- a/src/main/kotlin/io/dico/parcels2/Parcel.kt
+++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt
@@ -1,7 +1,8 @@
package io.dico.parcels2
import io.dico.parcels2.util.Vec2i
-import io.dico.parcels2.util.hasBuildAnywhere
+import io.dico.parcels2.util.ext.hasBuildAnywhere
+import org.bukkit.Location
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
import org.joda.time.DateTime
@@ -30,31 +31,38 @@ interface Parcel : ParcelData {
fun copyData(data: ParcelData)
fun dispose()
+
+ suspend fun withBlockVisitorPermit(block: suspend () -> Unit)
+
+ val homeLocation: Location get() = world.blockManager.getHomeLocation(id)
}
interface ParcelData : AddedData {
var owner: PlayerProfile?
- val since: DateTime?
+ val lastClaimTime: DateTime?
+ var ownerSignOutdated: Boolean
+ var interactableConfig: InteractableConfiguration
fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean
- var allowInteractInputs: Boolean
- var allowInteractInventory: Boolean
-
fun isOwner(uuid: UUID): Boolean {
return owner?.uuid == uuid
}
-}
-class ParcelDataHolder(addedMap: MutableAddedDataMap = mutableMapOf()) : AddedDataHolder(addedMap), ParcelData {
+ fun isOwner(profile: PlayerProfile?): Boolean {
+ return owner == profile
+ }
+}
+class ParcelDataHolder(addedMap: MutableAddedDataMap = mutableMapOf())
+ : ParcelData, AddedDataHolder(addedMap) {
override var owner: PlayerProfile? = null
- override var since: DateTime? = null
+ override var lastClaimTime: DateTime? = null
+ override var ownerSignOutdated = false
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.statusKey)
|| owner.let { it != null && it.matches(player, allowNameMatch = false) }
|| (checkAdmin && player is Player && player.hasBuildAnywhere)
- override var allowInteractInputs = true
- override var allowInteractInventory = true
+ override var interactableConfig: InteractableConfiguration = BitmaskInteractableConfiguration()
}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt
index b9d2e3d..d69984b 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt
@@ -1,9 +1,10 @@
package io.dico.parcels2
-import io.dico.parcels2.blockvisitor.RegionTraversal
-import io.dico.parcels2.blockvisitor.Worker
-import io.dico.parcels2.blockvisitor.WorktimeLimiter
+import io.dico.parcels2.blockvisitor.*
+import io.dico.parcels2.util.Region
import io.dico.parcels2.util.Vec2i
+import io.dico.parcels2.util.get
+import kotlinx.coroutines.CoroutineScope
import org.bukkit.Chunk
import org.bukkit.Location
import org.bukkit.World
@@ -15,6 +16,8 @@ import org.bukkit.generator.ChunkGenerator
import java.util.Random
abstract class ParcelGenerator : ChunkGenerator() {
+ abstract val worldName: String
+
abstract val world: World
abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData
@@ -31,31 +34,60 @@ abstract class ParcelGenerator : ChunkGenerator() {
})
}
- abstract fun makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager
-
- abstract fun makeParcelLocator(container: ParcelContainer): ParcelLocator
+ abstract fun makeParcelLocatorAndBlockManager(worldId: ParcelWorldId,
+ container: ParcelContainer,
+ coroutineScope: CoroutineScope,
+ worktimeLimiter: WorktimeLimiter): Pair<ParcelLocator, ParcelBlockManager>
}
-@Suppress("DeprecatedCallableAddReplaceWith")
interface ParcelBlockManager {
val world: World
val worktimeLimiter: WorktimeLimiter
+ val parcelTraverser: RegionTraverser
- fun getBottomBlock(parcel: ParcelId): Vec2i
+ // fun getBottomBlock(parcel: ParcelId): Vec2i
fun getHomeLocation(parcel: ParcelId): Location
- fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?)
+ fun getRegion(parcel: ParcelId): Region
- @Deprecated("")
- fun getEntities(parcel: ParcelId): Collection<Entity> = TODO()
+ fun getEntities(parcel: ParcelId): Collection<Entity>
- @Deprecated("")
- fun getBlocks(parcel: ParcelId, yRange: IntRange = 0..255): Iterator<Block> = TODO()
+ fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?)
fun setBiome(parcel: ParcelId, biome: Biome): Worker
fun clearParcel(parcel: ParcelId): Worker
- fun doBlockOperation(parcel: ParcelId, direction: RegionTraversal = RegionTraversal.DOWNWARD, operation: (Block) -> Unit): Worker
+ fun submitBlockVisitor(parcelId: ParcelId, task: TimeLimitedTask): Worker
+
+ /**
+ * Used to update owner blocks in the corner of the parcel
+ */
+ fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i>
+}
+
+inline fun ParcelBlockManager.doBlockOperation(parcel: ParcelId,
+ traverser: RegionTraverser,
+ crossinline operation: suspend WorkerScope.(Block) -> Unit) = submitBlockVisitor(parcel) {
+ val region = getRegion(parcel)
+ val blockCount = region.blockCount.toDouble()
+ val blocks = traverser.traverseRegion(region)
+ for ((index, vec) in blocks.withIndex()) {
+ markSuspensionPoint()
+ operation(world[vec])
+ setProgress((index + 1) / blockCount)
+ }
+}
+
+abstract class ParcelBlockManagerBase : ParcelBlockManager {
+
+ override fun getEntities(parcel: ParcelId): Collection<Entity> {
+ val region = getRegion(parcel)
+ val center = region.center
+ val centerLoc = Location(world, center.x, center.y, center.z)
+ val centerDist = (center - region.origin).add(0.2, 0.2, 0.2)
+ return world.getNearbyEntities(centerLoc, centerDist.x, centerDist.y, centerDist.z)
+ }
+
}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelId.kt b/src/main/kotlin/io/dico/parcels2/ParcelId.kt
index 951a172..72eceb9 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelId.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelId.kt
@@ -12,7 +12,7 @@ import java.util.UUID
interface ParcelWorldId {
val name: String
val uid: UUID?
- fun equals(id: ParcelWorldId): Boolean = name == name || (uid != null && uid == id.uid)
+ fun equals(id: ParcelWorldId): Boolean = name == id.name || (uid != null && uid == id.uid)
val bukkitWorld: World? get() = Bukkit.getWorld(name) ?: uid?.let { Bukkit.getWorld(it) }
@@ -22,6 +22,8 @@ interface ParcelWorldId {
}
}
+fun ParcelWorldId.toStringExt() = "ParcelWorld($name)"
+
/**
* Used by storage backing options to encompass the location of a parcel
* Does NOT support equality operator.
@@ -31,6 +33,7 @@ interface ParcelId {
val x: Int
val z: Int
val pos: Vec2i get() = Vec2i(x, z)
+ val idString get() = "$x,$z"
fun equals(id: ParcelId): Boolean = x == id.x && z == id.z && worldId.equals(id.worldId)
companion object {
@@ -41,9 +44,15 @@ interface ParcelId {
}
}
+fun ParcelId.toStringExt() = "Parcel(${worldId.name},$idString)"
+
private class ParcelWorldIdImpl(override val name: String,
- override val uid: UUID?) : ParcelWorldId
+ override val uid: UUID?) : ParcelWorldId {
+ override fun toString() = toStringExt()
+}
private class ParcelIdImpl(override val worldId: ParcelWorldId,
override val x: Int,
- override val z: Int) : ParcelId
+ override val z: Int) : ParcelId {
+ override fun toString() = toStringExt()
+}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
index 0dcdfbd..d70368f 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt
@@ -3,11 +3,12 @@ package io.dico.parcels2
import io.dico.parcels2.options.RuntimeWorldOptions
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.Vec2i
-import io.dico.parcels2.util.floor
+import io.dico.parcels2.util.ext.floor
import org.bukkit.Location
import org.bukkit.World
import org.bukkit.block.Block
import org.bukkit.entity.Entity
+import org.joda.time.DateTime
import java.util.UUID
interface ParcelProvider {
@@ -58,7 +59,6 @@ interface ParcelLocator {
fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world }
fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world }
-
}
typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
@@ -69,11 +69,13 @@ interface ParcelContainer {
fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z)
+ fun getParcelById(id: ParcelId): Parcel? = getParcelById(id.x, id.z)
+
fun nextEmptyParcel(): Parcel?
}
-interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager {
+interface ParcelWorld : ParcelLocator, ParcelContainer {
val id: ParcelWorldId
val name: String
val uid: UUID?
@@ -84,4 +86,7 @@ interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager {
val locator: ParcelLocator
val blockManager: ParcelBlockManager
val globalAddedData: GlobalAddedDataManager
+
+ val creationTime: DateTime?
+
}
diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
index e9c70f6..c863716 100644
--- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
+++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt
@@ -10,22 +10,27 @@ import io.dico.parcels2.defaultimpl.GlobalAddedDataManagerImpl
import io.dico.parcels2.defaultimpl.ParcelProviderImpl
import io.dico.parcels2.listener.ParcelEntityTracker
import io.dico.parcels2.listener.ParcelListeners
+import io.dico.parcels2.listener.WorldEditListener
import io.dico.parcels2.options.Options
import io.dico.parcels2.options.optionsMapper
import io.dico.parcels2.storage.Storage
-import io.dico.parcels2.util.FunctionHelper
-import io.dico.parcels2.util.tryCreate
+import io.dico.parcels2.util.MainThreadDispatcher
+import io.dico.parcels2.util.PluginScheduler
+import io.dico.parcels2.util.ext.tryCreate
+import kotlinx.coroutines.CoroutineScope
import org.bukkit.Bukkit
import org.bukkit.generator.ChunkGenerator
+import org.bukkit.plugin.Plugin
import org.bukkit.plugin.java.JavaPlugin
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
+import kotlin.coroutines.CoroutineContext
val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
private inline val plogger get() = logger
-class ParcelsPlugin : JavaPlugin() {
+class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
lateinit var optionsFile: File; private set
lateinit var options: Options; private set
lateinit var parcelProvider: ParcelProvider; private set
@@ -37,7 +42,8 @@ class ParcelsPlugin : JavaPlugin() {
private var listeners: ParcelListeners? = null
private var cmdDispatcher: ICommandDispatcher? = null
- val functionHelper: FunctionHelper = FunctionHelper(this)
+ override val coroutineContext: CoroutineContext = MainThreadDispatcher(this)
+ override val plugin: Plugin get() = this
val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options.tickWorktime) }
override fun onEnable() {
@@ -49,7 +55,15 @@ class ParcelsPlugin : JavaPlugin() {
}
override fun onDisable() {
+ val hasWorkers = worktimeLimiter.workers.isNotEmpty()
+ if (hasWorkers) {
+ plogger.warn("Parcels is attempting to complete all ${worktimeLimiter.workers.size} remaining jobs before shutdown...")
+ }
worktimeLimiter.completeAllTasks()
+ if (hasWorkers) {
+ plogger.info("Parcels has completed the remaining jobs.")
+ }
+
cmdDispatcher?.unregisterFromCommandMap()
}
@@ -124,11 +138,16 @@ class ParcelsPlugin : JavaPlugin() {
private fun registerListeners() {
if (listeners == null) {
- listeners = ParcelListeners(parcelProvider, entityTracker)
+ listeners = ParcelListeners(parcelProvider, entityTracker, storage)
registrator.registerListeners(listeners!!)
+
+ val worldEditPlugin = server.pluginManager.getPlugin("WorldEdit")
+ if (worldEditPlugin != null) {
+ WorldEditListener.register(this, worldEditPlugin)
+ }
}
- functionHelper.scheduleRepeating(100, 5, entityTracker::tick)
+ scheduleRepeating(100, 5, entityTracker::tick)
}
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt b/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt
index c5403cd..0ef9f9d 100644
--- a/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt
+++ b/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt
@@ -3,12 +3,13 @@
package io.dico.parcels2
import io.dico.parcels2.storage.Storage
-import io.dico.parcels2.util.getPlayerNameOrDefault
-import io.dico.parcels2.util.isValid
-import io.dico.parcels2.util.uuid
-import kotlinx.coroutines.experimental.Deferred
-import kotlinx.coroutines.experimental.Unconfined
-import kotlinx.coroutines.experimental.async
+import io.dico.parcels2.util.PLAYER_NAME_PLACEHOLDER
+import io.dico.parcels2.util.getPlayerName
+import io.dico.parcels2.util.ext.isValid
+import io.dico.parcels2.util.ext.uuid
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Unconfined
+import kotlinx.coroutines.async
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import java.util.UUID
@@ -16,8 +17,10 @@ import java.util.UUID
interface PlayerProfile {
val uuid: UUID? get() = null
val name: String?
+ val nameOrBukkitName: String?
val notNullName: String
- val isStar: Boolean get() = false
+ val isStar: Boolean get() = this is Star
+ val exists: Boolean get() = this is RealImpl
fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean
@@ -74,8 +77,11 @@ interface PlayerProfile {
interface Real : PlayerProfile {
override val uuid: UUID
+ override val nameOrBukkitName: String?
+ // If a player is online, their name is prioritized to get name changes right immediately
+ get() = Bukkit.getPlayer(uuid)?.name ?: name ?: getPlayerName(uuid)
override val notNullName: String
- get() = name ?: getPlayerNameOrDefault(uuid)
+ get() = nameOrBukkitName ?: PLAYER_NAME_PLACEHOLDER
val player: OfflinePlayer? get() = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }
val playerUnchecked: OfflinePlayer get() = Bukkit.getOfflinePlayer(uuid)
@@ -110,8 +116,8 @@ interface PlayerProfile {
object Star : BaseImpl(), Real {
override val name: String = "*"
+ // hopefully nobody will have this random UUID :)
override val uuid: UUID = UUID.fromString("7d09c4c6-117d-4f36-9778-c4d24618cee1")
- override val isStar: Boolean get() = true
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
return true
@@ -120,6 +126,7 @@ interface PlayerProfile {
abstract class NameOnly(override val name: String) : BaseImpl() {
override val notNullName get() = name
+ override val nameOrBukkitName: String get() = name
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
return allowNameMatch && player.name == name
@@ -142,7 +149,7 @@ interface PlayerProfile {
}
suspend fun tryResolveSuspendedly(storage: Storage): Real? {
- return storage.getPlayerUuidForName(name).await()?.let { RealImpl(it, name) }
+ return storage.getPlayerUuidForName(name).await()?.let { resolve(it) }
}
fun resolve(uuid: UUID): Real {
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt
deleted file mode 100644
index 85fe946..0000000
--- a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package io.dico.parcels2.blockvisitor
-
-import io.dico.parcels2.util.Region
-import io.dico.parcels2.util.Vec3i
-import kotlin.coroutines.experimental.SequenceBuilder
-import kotlin.coroutines.experimental.buildIterator
-
-enum class RegionTraversal(private val builder: suspend SequenceBuilder<Vec3i>.(Region) -> Unit) {
- DOWNWARD({ region ->
- val origin = region.origin
- val size = region.size
-
- repeat(size.y) { y ->
- repeat(size.z) { z ->
- repeat(size.x) { x ->
- yield(origin.add(x, size.y - y - 1, z))
- }
- }
- }
-
- }),
-
- UPWARD({ region ->
- val origin = region.origin
- val size = region.size
-
- repeat(size.y) { y ->
- repeat(size.z) { z ->
- repeat(size.x) { x ->
- yield(origin.add(x, y, z))
- }
- }
- }
- }),
-
- ;
-
- fun regionTraverser(region: Region) = Iterable { buildIterator { builder(region) } }
-
-}
-
-
-
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt
new file mode 100644
index 0000000..3899db9
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt
@@ -0,0 +1,65 @@
+package io.dico.parcels2.blockvisitor
+
+import io.dico.parcels2.util.Region
+import io.dico.parcels2.util.Vec3i
+
+abstract class RegionTraverser {
+ fun traverseRegion(region: Region): Iterable<Vec3i> = Iterable { iterator<Vec3i> { build(region) } }
+
+ protected abstract suspend fun SequenceScope<Vec3i>.build(region: Region)
+
+ companion object {
+ val upward = create { traverseUpward(it) }
+ val downward = create { traverseDownward(it) }
+ val forClearing get() = downward
+ val forFilling get() = upward
+
+ inline fun create(crossinline builder: suspend SequenceScope<Vec3i>.(Region) -> Unit) = object : RegionTraverser() {
+ override suspend fun SequenceScope<Vec3i>.build(region: Region) {
+ builder(region)
+ }
+ }
+
+ private suspend fun SequenceScope<Vec3i>.traverseDownward(region: Region) {
+ val origin = region.origin
+ val size = region.size
+ repeat(size.y) { y ->
+ repeat(size.z) { z ->
+ repeat(size.x) { x ->
+ yield(origin.add(x, size.y - y - 1, z))
+ }
+ }
+ }
+ }
+
+ private suspend fun SequenceScope<Vec3i>.traverseUpward(region: Region) {
+ val origin = region.origin
+ val size = region.size
+ repeat(size.y) { y ->
+ repeat(size.z) { z ->
+ repeat(size.x) { x ->
+ yield(origin.add(x, size.y - y - 1, z))
+ }
+ }
+ }
+ }
+
+ private fun slice(region: Region, atY: Int): Pair<Region, Region?> {
+ if (atY < region.size.y + 1) {
+ val first = Region(region.origin, region.size.withY(atY + 1))
+ val second = Region(region.origin.withY(atY), region.size.addY(-atY-1))
+ return first to second
+ }
+ return region to null
+ }
+
+ fun upToAndDownUntil(y: Int) = create { region ->
+ val (bottom, top) = slice(region, y)
+ traverseUpward(bottom)
+ top?.let { traverseDownward(it) }
+ }
+
+ }
+
+
+}
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt
index c375e5a..7e109c8 100644
--- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt
@@ -23,7 +23,7 @@ class Schematic {
val size = region.size.also { _size = it }
val data = arrayOfNulls<BlockData>(region.blockCount).also { _data = it }
//val extra = mutableMapOf<Vec3i, (Block) -> Unit>().also { extra = it }
- val blocks = RegionTraversal.DOWNWARD.regionTraverser(region)
+ val blocks = RegionTraverser.downward.traverseRegion(region)
for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint()
@@ -39,7 +39,7 @@ class Schematic {
fun getPasteTask(world: World, position: Vec3i): TimeLimitedTask = {
if (!isLoaded) throw IllegalStateException()
val region = Region(position, _size!!)
- val blocks = RegionTraversal.DOWNWARD.regionTraverser(region)
+ val blocks = RegionTraverser.downward.traverseRegion(region)
val data = _data!!
for ((index, vec) in blocks.withIndex()) {
diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt
index 33ac351..4cfd2a3 100644
--- a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt
+++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt
@@ -1,16 +1,20 @@
package io.dico.parcels2.blockvisitor
import io.dico.parcels2.ParcelsPlugin
-import io.dico.parcels2.util.FunctionHelper
-import kotlinx.coroutines.experimental.CancellationException
-import kotlinx.coroutines.experimental.Job
+import io.dico.parcels2.logger
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart.LAZY
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
import org.bukkit.scheduler.BukkitTask
import java.lang.System.currentTimeMillis
import java.util.LinkedList
-import java.util.logging.Level
-import kotlin.coroutines.experimental.Continuation
-import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED
-import kotlin.coroutines.experimental.intrinsics.suspendCoroutineUninterceptedOrReturn
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
+import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
typealias TimeLimitedTask = suspend WorkerScope.() -> Unit
typealias WorkerUpdateLister = Worker.(Double, Long) -> Unit
@@ -77,9 +81,14 @@ interface Worker : Timed {
/**
* Calls the given [block] when this worker completes, with the progress value 1.0.
- * Repeated invocations of this method result in an [IllegalStateException]
+ * Multiple listeners may be registered to this function.
*/
fun onCompleted(block: WorkerUpdateLister): Worker
+
+ /**
+ * Await completion of this worker
+ */
+ suspend fun awaitCompletion()
}
interface WorkerScope : Timed {
@@ -121,9 +130,14 @@ class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWo
override val workers: List<Worker> = _workers
override fun submit(task: TimeLimitedTask): Worker {
- val worker: WorkerInternal = WorkerImpl(plugin.functionHelper, task)
+ val worker: WorkerInternal = WorkerImpl(plugin, task)
+
+ if (bukkitTask == null) {
+ val completed = worker.resume(options.workTime.toLong())
+ if (completed) return worker
+ bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickJobs() }
+ }
_workers.addFirst(worker)
- if (bukkitTask == null) bukkitTask = plugin.functionHelper.scheduleRepeating(0, options.tickInterval) { tickJobs() }
return worker
}
@@ -165,8 +179,10 @@ class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWo
}
-private class WorkerImpl(val functionHelper: FunctionHelper,
- val task: TimeLimitedTask) : WorkerInternal {
+private class WorkerImpl(
+ val scope: CoroutineScope,
+ val task: TimeLimitedTask
+) : WorkerInternal, CoroutineScope by scope {
override var job: Job? = null; private set
override val elapsedTime
@@ -198,27 +214,44 @@ private class WorkerImpl(val functionHelper: FunctionHelper,
// report any error that occurred
completionException = exception?.also {
if (it !is CancellationException)
- functionHelper.plugin.logger.log(Level.SEVERE, "TimeLimitedTask for plugin ${functionHelper.plugin.name} generated an exception", it)
+ logger.error("TimeLimitedTask generated an exception", it)
}
// convert to elapsed time here
startTimeOrElapsedTime = System.currentTimeMillis() - startTimeOrElapsedTime
onCompleted?.let { it(1.0, elapsedTime) }
+
+ onCompleted = null
+ onProgressUpdate = { prog, el -> }
}
}
override fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean, block: WorkerUpdateLister): Worker {
onProgressUpdate?.let { throw IllegalStateException() }
+ if (asCompletionListener) onCompleted(block)
+ if (isComplete) return this
onProgressUpdate = block
progressUpdateInterval = minInterval
lastUpdateTime = System.currentTimeMillis() + minDelay - minInterval
- if (asCompletionListener) onCompleted(block)
+
return this
}
override fun onCompleted(block: WorkerUpdateLister): Worker {
- onCompleted?.let { throw IllegalStateException() }
- onCompleted = block
+ if (isComplete) {
+ block(1.0, startTimeOrElapsedTime)
+ return this
+ }
+
+ val cur = onCompleted
+ onCompleted = if (cur == null) {
+ block
+ } else {
+ fun Worker.(prog: Double, el: Long) {
+ cur(prog, el)
+ block(prog, el)
+ }
+ }
return this
}
@@ -259,7 +292,7 @@ private class WorkerImpl(val functionHelper: FunctionHelper,
}
try {
- val job = functionHelper.launchLazilyOnMainThread { task() }
+ val job = launch(start = LAZY) { task() }
initJob(job = job)
job.start()
} catch (t: Throwable) {
@@ -269,6 +302,18 @@ private class WorkerImpl(val functionHelper: FunctionHelper,
return continuation == null
}
+ override suspend fun awaitCompletion() {
+ if (isComplete) return
+
+ // easy path - if the job was initialized already
+ job?.apply { join(); return }
+
+ // other way.
+ return suspendCoroutine { cont ->
+ onCompleted { prog, el -> cont.resume(Unit) }
+ }
+ }
+
}
/*
diff --git a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt
index 31a551a..2339309 100644
--- a/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/AbstractParcelCommands.kt
@@ -1,26 +1,22 @@
package io.dico.parcels2.command
-import io.dico.dicore.command.CommandException
-import io.dico.dicore.command.ExecutionContext
-import io.dico.dicore.command.ICommandReceiver
-import io.dico.parcels2.PlayerProfile
+import io.dico.dicore.command.*
import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.ParcelsPlugin
-import io.dico.parcels2.util.hasAdminManage
-import io.dico.parcels2.util.parcelLimit
+import io.dico.parcels2.PlayerProfile
+import io.dico.parcels2.util.ext.hasAdminManage
+import io.dico.parcels2.util.ext.parcelLimit
import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin
import java.lang.reflect.Method
abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandReceiver.Factory {
-
override fun getPlugin(): Plugin = plugin
+
override fun getReceiver(context: ExecutionContext, target: Method, cmdName: String): ICommandReceiver {
return getParcelCommandReceiver(plugin.parcelProvider, context, target, cmdName)
}
- protected inline val worlds get() = plugin.parcelProvider
-
protected fun error(message: String): Nothing {
throw CommandException(message)
}
@@ -40,5 +36,22 @@ abstract class AbstractParcelCommands(val plugin: ParcelsPlugin) : ICommandRecei
}
}
+ protected fun areYouSureMessage(context: ExecutionContext) = "Are you sure? You cannot undo this action!\n" +
+ "Run \"/${context.route.joinToString(" ")} -sure\" if you want to go through with this."
+
+ protected fun ParcelScope.clearWithProgressUpdates(context: ExecutionContext, action: String) {
+ Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
+ world.blockManager.clearParcel(parcel.id)
+ .onProgressUpdate(1000, 1000) { progress, elapsedTime ->
+ val alt = context.getFormat(EMessageType.NUMBER)
+ val main = context.getFormat(EMessageType.INFORMATIVE)
+ context.sendMessage(
+ EMessageType.INFORMATIVE, false, "$action progress: $alt%.02f$main%%, $alt%.2f${main}s elapsed"
+ .format(progress * 100, elapsedTime / 1000.0)
+ )
+ }
+ }
+
+ override fun getCoroutineContext() = plugin.coroutineContext
}
diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt
index 69da341..223e504 100644
--- a/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAddedStatusLocal.kt
@@ -4,7 +4,7 @@ import io.dico.dicore.command.Validate
import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc
import io.dico.parcels2.ParcelsPlugin
-import io.dico.parcels2.util.hasAdminManage
+import io.dico.parcels2.util.ext.hasAdminManage
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt
index 2fe18ed..35ede71 100644
--- a/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/CommandsAdmin.kt
@@ -1,7 +1,43 @@
package io.dico.parcels2.command
+import io.dico.dicore.command.ExecutionContext
+import io.dico.dicore.command.annotation.Cmd
+import io.dico.dicore.command.annotation.Flag
import io.dico.parcels2.ParcelsPlugin
+import io.dico.parcels2.PlayerProfile
class CommandsAdmin(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
+ @Cmd("setowner")
+ @ParcelRequire(admin = true)
+ fun ParcelScope.cmdSetowner(target: PlayerProfile): Any? {
+ parcel.owner = target
+
+ val fakeString = if (target.isFake) " (fake)" else ""
+ return "${target.notNullName}$fakeString is the new owner of (${parcel.id.idString})"
+ }
+
+ @Cmd("dispose")
+ @ParcelRequire(admin = true)
+ fun ParcelScope.cmdDispose(): Any? {
+ parcel.dispose()
+ return "Data of (${parcel.id.idString}) has been disposed"
+ }
+
+ @Cmd("reset")
+ @ParcelRequire(admin = true)
+ fun ParcelScope.cmdReset(context: ExecutionContext, @Flag sure: Boolean): Any? {
+ if (!sure) return areYouSureMessage(context)
+ parcel.dispose()
+ clearWithProgressUpdates(context, "Reset")
+ return null
+ }
+
+ @Cmd("swap")
+ fun ParcelScope.cmdSwap(context: ExecutionContext, @Flag sure: Boolean): Any? {
+ if (!sure) return areYouSureMessage(context)
+ TODO()
+ }
+
+
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt
index 3c5ba41..3ae17f2 100644
--- a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt
@@ -5,7 +5,8 @@ import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.annotation.Cmd
import io.dico.parcels2.ParcelsPlugin
-import io.dico.parcels2.blockvisitor.RegionTraversal
+import io.dico.parcels2.blockvisitor.RegionTraverser
+import io.dico.parcels2.doBlockOperation
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.entity.Player
@@ -43,7 +44,7 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
)
val random = Random()
- world.doBlockOperation(parcel.id, direction = RegionTraversal.UPWARD) { block ->
+ world.blockManager.doBlockOperation(parcel.id, traverser = RegionTraverser.upward) { block ->
block.blockData = blockDatas[random.nextInt(7)]
}.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"
diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt
index 7442019..eef07fd 100644
--- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt
@@ -1,24 +1,28 @@
package io.dico.parcels2.command
-import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext
+import io.dico.dicore.command.Validate
import io.dico.dicore.command.annotation.Cmd
import io.dico.dicore.command.annotation.Desc
import io.dico.dicore.command.annotation.Flag
import io.dico.dicore.command.annotation.RequireParameters
-import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.ParcelsPlugin
-import io.dico.parcels2.util.hasAdminManage
-import io.dico.parcels2.util.hasParcelHomeOthers
-import io.dico.parcels2.util.uuid
+import io.dico.parcels2.PlayerProfile
+import io.dico.parcels2.command.ParcelTarget.Kind
+import io.dico.parcels2.util.ext.hasAdminManage
+import io.dico.parcels2.util.ext.hasParcelHomeOthers
+import io.dico.parcels2.util.ext.uuid
+import org.bukkit.block.Biome
import org.bukkit.entity.Player
class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("auto")
- @Desc("Finds the unclaimed parcel nearest to origin,",
+ @Desc(
+ "Finds the unclaimed parcel nearest to origin,",
"and gives it to you",
- shortVersion = "sets you up with a fresh, unclaimed parcel")
+ shortVersion = "sets you up with a fresh, unclaimed parcel"
+ )
suspend fun WorldScope.cmdAuto(player: Player): Any? {
checkConnected("be claimed")
checkParcelLimit(player, world)
@@ -26,45 +30,73 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
val parcel = world.nextEmptyParcel()
?: error("This world is full, please ask an admin to upsize it")
parcel.owner = PlayerProfile(uuid = player.uuid)
- player.teleport(parcel.world.getHomeLocation(parcel.id))
+ player.teleport(parcel.homeLocation)
return "Enjoy your new parcel!"
}
@Cmd("info", aliases = ["i"])
- @Desc("Displays general information",
+ @Desc(
+ "Displays general information",
"about the parcel you're on",
- shortVersion = "displays information about this parcel")
+ shortVersion = "displays information about this parcel"
+ )
fun ParcelScope.cmdInfo(player: Player) = parcel.infoString
@Cmd("home", aliases = ["h"])
- @Desc("Teleports you to your parcels,",
+ @Desc(
+ "Teleports you to your parcels,",
"unless another player was specified.",
"You can specify an index number if you have",
"more than one parcel",
- shortVersion = "teleports you to parcels")
+ shortVersion = "teleports you to parcels"
+ )
@RequireParameters(0)
- suspend fun cmdHome(player: Player, @ParcelTarget.Kind(ParcelTarget.OWNER_REAL) target: ParcelTarget): Any? {
- val ownerTarget = target as ParcelTarget.ByOwner
- if (!ownerTarget.owner.matches(player) && !player.hasParcelHomeOthers) {
- error("You do not have permission to teleport to other people's parcels")
- }
+ suspend fun cmdHome(
+ player: Player,
+ @Kind(ParcelTarget.OWNER_REAL) target: ParcelTarget
+ ): Any? {
+ return cmdGoto(player, target)
+ }
- val ownedParcelsResult = plugin.storage.getOwnedParcels(ownerTarget.owner).await()
+ @Cmd("tp", aliases = ["teleport"])
+ suspend fun cmdTp(
+ player: Player,
+ @Kind(ParcelTarget.ID) target: ParcelTarget
+ ): Any? {
+ return cmdGoto(player, target)
+ }
- val ownedParcels = ownedParcelsResult
- .map { worlds.getParcelById(it) }
- .filter { it != null && ownerTarget.world == it.world }
+ @Cmd("goto")
+ suspend fun cmdGoto(
+ player: Player,
+ @Kind(ParcelTarget.ANY) target: ParcelTarget
+ ): Any? {
+ if (target is ParcelTarget.ByOwner) {
+ target.resolveOwner(plugin.storage)
+ if (!target.owner.matches(player) && !player.hasParcelHomeOthers) {
+ error("You do not have permission to teleport to other people's parcels")
+ }
+ }
- val targetMatch = ownedParcels.getOrNull(target.index)
+ val match = target.getParcelSuspend(plugin.storage)
?: error("The specified parcel could not be matched")
+ player.teleport(match.homeLocation)
+ return null
+ }
- player.teleport(targetMatch.world.getHomeLocation(targetMatch.id))
- return ""
+ @Cmd("goto_fake")
+ suspend fun cmdGotoFake(
+ player: Player,
+ @Kind(ParcelTarget.OWNER_FAKE) target: ParcelTarget
+ ): Any? {
+ return cmdGoto(player, target)
}
@Cmd("claim")
- @Desc("If this parcel is unowned, makes you the owner",
- shortVersion = "claims this parcel")
+ @Desc(
+ "If this parcel is unowned, makes you the owner",
+ shortVersion = "claims this parcel"
+ )
suspend fun ParcelScope.cmdClaim(player: Player): Any? {
checkConnected("be claimed")
parcel.owner.takeIf { !player.hasAdminManage }?.let {
@@ -87,23 +119,18 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
@Cmd("clear")
@ParcelRequire(owner = true)
fun ParcelScope.cmdClear(context: ExecutionContext, @Flag sure: Boolean): Any? {
- if (!sure) return "Are you sure? You cannot undo this action!\n" +
- "Run \"/${context.rawInput} -sure\" if you want to go through with this."
-
- world.clearParcel(parcel.id)
- .onProgressUpdate(1000, 1000) { progress, elapsedTime ->
- val alt = context.getFormat(EMessageType.NUMBER)
- val main = context.getFormat(EMessageType.INFORMATIVE)
- context.sendMessage(EMessageType.INFORMATIVE, false, "Clear progress: $alt%.02f$main%%, $alt%.2f${main}s elapsed"
- .format(progress * 100, elapsedTime / 1000.0))
- }
+ if (!sure) return areYouSureMessage(context)
+ clearWithProgressUpdates(context, "Clear")
return null
}
- @Cmd("swap")
- fun ParcelScope.cmdSwap(context: ExecutionContext, @Flag sure: Boolean): Any? {
- TODO()
+ @Cmd("setbiome")
+ @ParcelRequire(owner = true)
+ fun ParcelScope.cmdSetbiome(context: ExecutionContext, biome: Biome): Any? {
+ Validate.isTrue(!parcel.hasBlockVisitors, "A process is already running in this parcel")
+ world.blockManager.setBiome(parcel.id, biome)
+ return "Biome has been set to $biome"
}
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt
index c77686e..b46e972 100644
--- a/src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/CommandsParcelOptions.kt
@@ -11,6 +11,7 @@ import org.bukkit.entity.Player
import kotlin.reflect.KMutableProperty
class CommandsParcelOptions(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
+ /* TODO options
@Cmd("inputs")
@Desc("Sets whether players who are not allowed to",
"build here can use levers, buttons,",
@@ -28,7 +29,7 @@ class CommandsParcelOptions(plugin: ParcelsPlugin) : AbstractParcelCommands(plug
@RequireParameters(0)
fun ParcelScope.cmdInventory(player: Player, enabled: Boolean?): Any? {
return runOptionCommand(player, Parcel::allowInteractInventory, enabled, "interaction with inventories")
- }
+ }*/
private inline val Boolean.enabledWord get() = if (this) "enabled" else "disabled"
private fun ParcelScope.runOptionCommand(player: Player,
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt
index b633c3e..e2c7a1d 100644
--- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt
@@ -17,11 +17,12 @@ fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher {
.addParameterType(true, ParcelTarget.PType(plugin.parcelProvider))
.group("parcel", "plot", "plots", "p")
+ .addRequiredPermission("parcels.command")
.registerCommands(CommandsGeneral(plugin))
.registerCommands(CommandsAddedStatusLocal(plugin))
.group("option", "opt", "o")
- //.apply { CommandsParcelOptions.setGroupDescription(this) }
+ .apply { CommandsParcelOptions.setGroupDescription(this) }
.registerCommands(CommandsParcelOptions(plugin))
.parent()
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt
index eab02c4..488148d 100644
--- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandReceivers.kt
@@ -7,8 +7,8 @@ import io.dico.dicore.command.Validate
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld
-import io.dico.parcels2.util.hasAdminManage
-import io.dico.parcels2.util.uuid
+import io.dico.parcels2.util.ext.hasAdminManage
+import io.dico.parcels2.util.ext.uuid
import org.bukkit.entity.Player
import java.lang.reflect.Method
import kotlin.reflect.full.extensionReceiverParameter
diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt
index cbab80a..abf7d40 100644
--- a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt
+++ b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt
@@ -5,20 +5,23 @@ 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.storage.Storage
import io.dico.parcels2.util.Vec2i
-import io.dico.parcels2.util.floor
-import kotlinx.coroutines.experimental.Deferred
+import io.dico.parcels2.util.ext.floor
+import kotlinx.coroutines.CoroutineStart.*
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.async
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
-sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
+sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDefault: Boolean) {
- abstract suspend fun ParcelsPlugin.getParcelSuspend(): Parcel?
+ abstract suspend fun getParcelSuspend(storage: Storage): Parcel?
- fun ParcelsPlugin.getParcelDeferred(): Deferred<Parcel?> = functionHelper.deferUndispatchedOnMainThread { getParcelSuspend() }
+ fun ParcelsPlugin.getParcelDeferred(): Deferred<Parcel?> = async(start = UNDISPATCHED) { getParcelSuspend(storage) }
- class ByID(world: ParcelWorld, val id: Vec2i?, isDefault: Boolean) : ParcelTarget(world, isDefault) {
- override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? = getParcel()
+ 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
}
@@ -26,46 +29,40 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
class ByOwner(world: ParcelWorld,
owner: PlayerProfile,
val index: Int,
+ parsedKind: Int,
isDefault: Boolean,
- val onResolveFailure: (() -> Unit)? = null) : ParcelTarget(world, isDefault) {
+ val onResolveFailure: (() -> Unit)? = null) : ParcelTarget(world, parsedKind, isDefault) {
init {
if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index")
}
var owner = owner; private set
- override suspend fun ParcelsPlugin.getParcelSuspend(): Parcel? {
- onResolveFailure?.let { onFail ->
- val owner = owner
- if (owner is PlayerProfile.Unresolved) {
- val new = owner.tryResolveSuspendedly(storage)
- if (new == null) {
- onFail()
- return@let
- }
- this@ByOwner.owner = new
- }
+ suspend fun resolveOwner(storage: Storage): Boolean {
+ val owner = owner
+ if (owner is PlayerProfile.Unresolved) {
+ this.owner = owner.tryResolveSuspendedly(storage) ?: if (parsedKind and OWNER_FAKE != 0) PlayerProfile.Fake(owner.name)
+ else run { onResolveFailure?.invoke(); return false }
}
+ return true
+ }
+
+ override suspend fun getParcelSuspend(storage: Storage): Parcel? {
+ onResolveFailure?.let { resolveOwner(storage) }
val ownedParcelsSerialized = storage.getOwnedParcels(owner).await()
val ownedParcels = ownedParcelsSerialized
- .map { parcelProvider.getParcelById(it) }
- .filter { it != null && world == it.world && owner == it.owner }
+ .filter { it.worldId.equals(world.id) }
+ .map { world.getParcelById(it.x, it.z) }
return ownedParcels.getOrNull(index)
}
}
- annotation class Kind(val kind: Int)
-
- companion object Config : ParameterConfig<Kind, Int>(Kind::class.java) {
- override fun toParameterInfo(annotation: Kind): Int {
- return annotation.kind
- }
-
+ companion object {
const val ID = 1 // ID
const val OWNER_REAL = 2 // an owner backed by a UUID
- const val OWNER_FAKE = 3 // an owner not backed by a UUID
+ const val OWNER_FAKE = 4 // an owner not backed by a UUID
const val OWNER = OWNER_REAL or OWNER_FAKE // any owner
const val ANY = ID or OWNER_REAL or OWNER_FAKE // any
@@ -73,11 +70,18 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
const val DEFAULT_KIND = REAL
- const val PREFER_OWNED_FOR_DEFAULT = 4 // 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
}
- class PType(val parcelProvider: ParcelProvider) : ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, ParcelTarget.Config) {
+ annotation class Kind(val kind: Int)
+ private object Config : ParameterConfig<Kind, Int>(Kind::class.java) {
+ override fun toParameterInfo(annotation: Kind): Int {
+ return annotation.kind
+ }
+ }
+
+ class PType(val parcelProvider: ParcelProvider) : ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, Config) {
override fun parse(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget {
var input = buffer.next()
@@ -95,12 +99,12 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
val kind = parameter.paramInfo ?: DEFAULT_KIND
if (input.contains(',')) {
if (kind and ID == 0) invalidInput(parameter, "You must specify a parcel by OWNER, that is, an owner and index")
- return ByID(world, getId(parameter, input), 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")
val (owner, index) = getHomeIndex(parameter, kind, sender, input)
- return ByOwner(world, owner, index, 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 {
@@ -116,15 +120,18 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
private fun getHomeIndex(parameter: Parameter<*, *>, kind: Int, sender: CommandSender, input: String): Pair<PlayerProfile, Int> {
val splitIdx = input.indexOf(':')
val ownerString: String
- val indexString: String
+ val index: Int?
if (splitIdx == -1) {
// just the index.
- ownerString = ""
- indexString = input
+ index = input.toIntOrNull()
+ ownerString = if (index == null) input else ""
} else {
ownerString = input.substring(0, splitIdx)
- indexString = input.substring(splitIdx + 1)
+
+ val indexString = input.substring(splitIdx + 1)
+ index = indexString.toIntOrNull()
+ ?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
}
val owner = if (ownerString.isEmpty())
@@ -132,10 +139,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
else
PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0)
- val index = if (indexString.isEmpty()) 0 else indexString.toIntOrNull()
- ?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
-
- return owner to index
+ return owner to (index ?: 0)
}
private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>, objName: String): Player {
@@ -156,10 +160,10 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) {
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, true)
+ return ByID(world, id, kind, true)
}
- return ByOwner(world, PlayerProfile(player), 0, true)
+ return ByOwner(world, PlayerProfile(player), 0, kind, true)
}
}
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt
index 0597a9f..e6d29f4 100644
--- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelContainer.kt
@@ -2,9 +2,8 @@ package io.dico.parcels2.defaultimpl
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelContainer
+import io.dico.parcels2.ParcelId
import io.dico.parcels2.ParcelWorld
-import kotlin.coroutines.experimental.buildIterator
-import kotlin.coroutines.experimental.buildSequence
class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer {
private var parcels: Array<Array<Parcel>>
@@ -38,12 +37,20 @@ class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer {
return parcels.getOrNull(x + world.options.axisLimit)?.getOrNull(z + world.options.axisLimit)
}
+ override fun getParcelById(id: ParcelId): Parcel? {
+ if (!world.id.equals(id.worldId)) throw IllegalArgumentException()
+ return when (id) {
+ is Parcel -> id
+ else -> getParcelById(id.x, id.z)
+ }
+ }
+
override fun nextEmptyParcel(): Parcel? {
return walkInCircle().find { it.owner == null }
}
private fun walkInCircle(): Iterable<Parcel> = Iterable {
- buildIterator {
+ iterator {
val center = world.options.axisLimit
for (radius in 0..center) {
var x = center - radius;
@@ -56,7 +63,7 @@ class DefaultParcelContainer(val world: ParcelWorld) : ParcelContainer {
}
}
- fun allParcels(): Sequence<Parcel> = buildSequence {
+ fun allParcels(): Sequence<Parcel> = sequence {
for (array in parcels) {
yieldAll(array.iterator())
}
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt
index 7e75f52..08d5f2f 100644
--- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt
@@ -1,28 +1,42 @@
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
-import io.dico.parcels2.blockvisitor.RegionTraversal
+import io.dico.parcels2.blockvisitor.RegionTraverser
+import io.dico.parcels2.blockvisitor.TimeLimitedTask
import io.dico.parcels2.blockvisitor.Worker
import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.options.DefaultGeneratorOptions
-import io.dico.parcels2.util.*
+import io.dico.parcels2.util.Region
+import io.dico.parcels2.util.Vec2i
+import io.dico.parcels2.util.Vec3i
+import io.dico.parcels2.util.ext.even
+import io.dico.parcels2.util.ext.umod
+import io.dico.parcels2.util.get
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
+import kotlinx.coroutines.launch
import org.bukkit.*
import org.bukkit.block.Biome
-import org.bukkit.block.Block
import org.bukkit.block.BlockFace
import org.bukkit.block.Skull
import org.bukkit.block.data.BlockData
import org.bukkit.block.data.type.Sign
import org.bukkit.block.data.type.Slab
+import java.lang.IllegalArgumentException
import java.util.Random
private val airType = Bukkit.createBlockData(Material.AIR)
-class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() {
+private const val chunkSize = 16
+
+class DefaultParcelGenerator(
+ override val worldName: String,
+ private val o: DefaultGeneratorOptions
+) : ParcelGenerator() {
private var _world: World? = null
override val world: World
get() {
- if (_world == null) _world = Bukkit.getWorld(name)!!.also {
+ if (_world == null) _world = Bukkit.getWorld(worldName)!!.also {
maxHeight = it.maxHeight
return it
}
@@ -35,13 +49,15 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
val makePathMain = o.pathSize > 2
val makePathAlt = o.pathSize > 4
- private inline fun <T> generate(chunkX: Int,
- chunkZ: Int,
- floor: T, wall:
- T, pathMain: T,
- pathAlt: T,
- fill: T,
- setter: (Int, Int, Int, T) -> Unit) {
+ private inline fun <T> generate(
+ chunkX: Int,
+ chunkZ: Int,
+ floor: T, wall:
+ T, pathMain: T,
+ pathAlt: T,
+ fill: T,
+ setter: (Int, Int, Int, T) -> Unit
+ ) {
val floorHeight = o.floorHeight
val parcelSize = o.parcelSize
@@ -103,12 +119,13 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix)
}
- override fun makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager {
- return ParcelBlockManagerImpl(worktimeLimiter)
- }
-
- override fun makeParcelLocator(container: ParcelContainer): ParcelLocator {
- return ParcelLocatorImpl(container)
+ override fun makeParcelLocatorAndBlockManager(
+ worldId: ParcelWorldId,
+ container: ParcelContainer,
+ coroutineScope: CoroutineScope,
+ worktimeLimiter: WorktimeLimiter
+ ): Pair<ParcelLocator, ParcelBlockManager> {
+ return ParcelLocatorImpl(worldId, container) to ParcelBlockManagerImpl(worldId, coroutineScope, worktimeLimiter)
}
private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
@@ -124,22 +141,31 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
return null
}
- private inner class ParcelLocatorImpl(val container: ParcelContainer) : ParcelLocator {
+ private inner class ParcelLocatorImpl(
+ val worldId: ParcelWorldId,
+ val container: ParcelContainer
+ ) : ParcelLocator {
override val world: World = this@DefaultParcelGenerator.world
+
override fun getParcelAt(x: Int, z: Int): Parcel? {
return convertBlockLocationToId(x, z, container::getParcelById)
}
override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
- return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(world.name, world.uid, idx, idz) }
+ return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(worldId, idx, idz) }
}
}
@Suppress("DEPRECATION")
- private inner class ParcelBlockManagerImpl(override val worktimeLimiter: WorktimeLimiter) : ParcelBlockManager {
+ private inner class ParcelBlockManagerImpl(
+ val worldId: ParcelWorldId,
+ coroutineScope: CoroutineScope,
+ override val worktimeLimiter: WorktimeLimiter
+ ) : ParcelBlockManagerBase(), CoroutineScope by coroutineScope {
override val world: World = this@DefaultParcelGenerator.world
+ override val parcelTraverser: RegionTraverser = RegionTraverser.upToAndDownUntil(o.floorHeight)
- override fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i(
+ /*override*/ fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i(
sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,
sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ
)
@@ -151,6 +177,11 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F)
}
+ override fun getRegion(parcel: ParcelId): Region {
+ val bottom = getBottomBlock(parcel)
+ return Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize))
+ }
+
override fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) {
val b = getBottomBlock(parcel)
@@ -190,7 +221,28 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
}
}
- override fun setBiome(parcel: ParcelId, biome: Biome): Worker = worktimeLimiter.submit {
+ private fun getParcel(parcelId: ParcelId): Parcel? {
+ // todo dont rely on this cast
+ val world = worldId as? ParcelWorld ?: return null
+ return world.getParcelById(parcelId)
+ }
+
+ override fun submitBlockVisitor(parcelId: ParcelId, task: TimeLimitedTask): Worker {
+ val parcel = getParcel(parcelId) ?: return worktimeLimiter.submit(task)
+ if (parcel.hasBlockVisitors) throw IllegalArgumentException("This parcel already has a block visitor")
+
+ val worker = worktimeLimiter.submit(task)
+
+ launch(start = UNDISPATCHED) {
+ parcel.withBlockVisitorPermit {
+ worker.awaitCompletion()
+ }
+ }
+
+ return worker
+ }
+
+ override fun setBiome(parcel: ParcelId, biome: Biome): Worker = submitBlockVisitor(parcel) {
val world = world
val b = getBottomBlock(parcel)
val parcelSize = o.parcelSize
@@ -202,10 +254,9 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
}
}
- override fun clearParcel(parcel: ParcelId): Worker = worktimeLimiter.submit {
- val bottom = getBottomBlock(parcel)
- val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize))
- val blocks = RegionTraversal.DOWNWARD.regionTraverser(region)
+ override fun clearParcel(parcel: ParcelId): Worker = submitBlockVisitor(parcel) {
+ val region = getRegion(parcel)
+ val blocks = parcelTraverser.traverseRegion(region)
val blockCount = region.blockCount.toDouble()
val world = world
@@ -227,17 +278,78 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp
}
}
- override fun doBlockOperation(parcel: ParcelId, direction: RegionTraversal, operation: (Block) -> Unit): Worker = worktimeLimiter.submit {
- val bottom = getBottomBlock(parcel)
- val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize))
- val blocks = direction.regionTraverser(region)
- val blockCount = region.blockCount.toDouble()
- val world = world
+ override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
+ /*
+ * Get the offsets for the world out of the way
+ * to simplify the calculation that follows.
+ */
+
+ val x = chunk.x.shl(4) - (o.offsetX + pathOffset)
+ val z = chunk.z.shl(4) - (o.offsetZ + pathOffset)
+
+ /* Locations of wall corners (where owner blocks are placed) are defined as:
+ *
+ * x umod sectionSize == sectionSize-1
+ *
+ * This check needs to be made for all 16 slices of the chunk in 2 dimensions
+ * How to optimize this?
+ * Let's take the expression
+ *
+ * x umod sectionSize
+ *
+ * And call it modX
+ * x can be shifted (chunkSize -1) times to attempt to get a modX of 0.
+ * This means that if the modX is 1, and sectionSize == (chunkSize-1), there would be a match at the last shift.
+ * To check that there are any matches, we can see if the following holds:
+ *
+ * modX >= ((sectionSize-1) - (chunkSize-1))
+ *
+ * Which can be simplified to:
+ * modX >= sectionSize - chunkSize
+ *
+ * if sectionSize == chunkSize, this expression can be simplified to
+ * modX >= 0
+ * which is always true. This is expected.
+ * To get the total number of matches on a dimension, we can evaluate the following:
+ *
+ * (modX - (sectionSize - chunkSize) + sectionSize) / sectionSize
+ *
+ * We add sectionSize to the lhs because, if the other part of the lhs is 0, we need at least 1.
+ * This can be simplified to:
+ *
+ * (modX + chunkSize) / sectionSize
+ */
+
+ val sectionSize = sectionSize
+
+ val modX = x umod sectionSize
+ val matchesOnDimensionX = (modX + chunkSize) / sectionSize
+ if (matchesOnDimensionX <= 0) return emptyList()
+
+ val modZ = z umod sectionSize
+ val matchesOnDimensionZ = (modZ + chunkSize) / sectionSize
+ if (matchesOnDimensionZ <= 0) return emptyList()
+
+ /*
+ * Now we need to find the first id within the matches,
+ * and then return the subsequent matches in a rectangle following it.
+ *
+ * On each dimension, get the distance to the first match, which is equal to (sectionSize-1 - modX)
+ * and add it to the coordinate value
+ */
+ val firstX = x + (sectionSize - 1 - modX)
+ val firstZ = z + (sectionSize - 1 - modZ)
+
+ val firstIdX = (firstX + 1) / sectionSize + 1
+ val firstIdZ = (firstZ + 1) / sectionSize + 1
+
+ if (matchesOnDimensionX == 1 && matchesOnDimensionZ == 1) {
+ // fast-path optimization
+ return listOf(Vec2i(firstIdX, firstIdZ))
+ }
- for ((index, vec) in blocks.withIndex()) {
- markSuspensionPoint()
- operation(world[vec])
- setProgress((index + 1) / blockCount)
+ return (0 until matchesOnDimensionX).flatMap { idOffsetX ->
+ (0 until matchesOnDimensionZ).map { idOffsetZ -> Vec2i(firstIdX + idOffsetX, firstIdZ + idOffsetZ) }
}
}
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt
index ba54375..ad7048c 100644
--- a/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/GlobalAddedDataManagerImpl.kt
@@ -3,7 +3,7 @@
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
-import io.dico.parcels2.util.alsoIfTrue
+import io.dico.parcels2.util.ext.alsoIfTrue
import java.util.Collections
class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataManager {
@@ -20,12 +20,12 @@ class GlobalAddedDataManagerImpl(val plugin: ParcelsPlugin) : GlobalAddedDataMan
private inline var data get() = addedMap; set(value) = run { addedMap = value }
private inline val isEmpty get() = data === emptyData
- override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean {
+ override fun setStatus(key: StatusKey, status: AddedStatus): Boolean {
if (isEmpty) {
if (status == AddedStatus.DEFAULT) return false
data = mutableMapOf()
}
- return super.setAddedStatus(key, status).alsoIfTrue {
+ return super.setStatus(key, status).alsoIfTrue {
plugin.storage.setGlobalAddedStatus(owner, key, status)
}
}
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt
index 128705f..d392a8f 100644
--- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt
@@ -3,21 +3,22 @@ package io.dico.parcels2.defaultimpl
import io.dico.dicore.Formatting
import io.dico.parcels2.*
import io.dico.parcels2.util.Vec2i
-import io.dico.parcels2.util.alsoIfTrue
-import io.dico.parcels2.util.getPlayerName
+import io.dico.parcels2.util.ext.alsoIfTrue
+import org.bukkit.Material
import org.bukkit.OfflinePlayer
import org.joda.time.DateTime
-import java.util.UUID
-import kotlin.reflect.KProperty
+import java.util.concurrent.atomic.AtomicInteger
-class ParcelImpl(override val world: ParcelWorld,
- override val x: Int,
- override val z: Int) : Parcel, ParcelId {
+class ParcelImpl(
+ override val world: ParcelWorld,
+ override val x: Int,
+ override val z: Int
+) : Parcel, ParcelId {
override val id: ParcelId = this
override val pos get() = Vec2i(x, z)
override var data: ParcelDataHolder = ParcelDataHolder(); private set
- override val infoString by ParcelInfoStringComputer
- override var hasBlockVisitors: Boolean = false; private set
+ override val infoString get() = ParcelInfoStringComputer.getInfoString(this)
+ override val hasBlockVisitors get() = blockVisitors.get() > 0
override val worldId: ParcelWorldId get() = world.id
override fun copyDataIgnoringDatabase(data: ParcelData) {
@@ -35,7 +36,7 @@ class ParcelImpl(override val world: ParcelWorld,
}
override val addedMap: AddedDataMap get() = data.addedMap
- override fun getAddedStatus(key: StatusKey) = data.getAddedStatus(key)
+ override fun getStatus(key: StatusKey) = data.getStatus(key)
override fun isBanned(key: StatusKey) = data.isBanned(key)
override fun isAllowed(key: StatusKey) = data.isAllowed(key)
override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean): Boolean {
@@ -43,46 +44,77 @@ class ParcelImpl(override val world: ParcelWorld,
|| checkGlobal && world.globalAddedData[owner ?: return false].isAllowed(player)
}
- override var addedStatusOfStar: AddedStatus
- get() = data.addedStatusOfStar
- set(value) = run { setAddedStatus(PlayerProfile.Star, value) }
+ override var statusOfStar: AddedStatus
+ get() = data.statusOfStar
+ set(value) = run { setStatus(PlayerProfile.Star, value) }
val globalAddedMap: AddedDataMap? get() = owner?.let { world.globalAddedData[it].addedMap }
- override val since: DateTime? get() = data.since
+ override val lastClaimTime: DateTime? get() = data.lastClaimTime
+
+ override var ownerSignOutdated: Boolean
+ get() = data.ownerSignOutdated
+ set(value) {
+ if (data.ownerSignOutdated != value) {
+ world.storage.setParcelOwnerSignOutdated(this, value)
+ data.ownerSignOutdated = value
+ }
+ }
override var owner: PlayerProfile?
get() = data.owner
set(value) {
if (data.owner != value) {
world.storage.setParcelOwner(this, value)
+ world.blockManager.setOwnerBlock(this, value)
data.owner = value
}
}
- override fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean {
- return data.setAddedStatus(key, status).alsoIfTrue {
+ override fun setStatus(key: StatusKey, status: AddedStatus): Boolean {
+ return data.setStatus(key, status).alsoIfTrue {
world.storage.setParcelPlayerStatus(this, key, status)
}
}
- override var allowInteractInputs: Boolean
- get() = data.allowInteractInputs
- set(value) {
- if (data.allowInteractInputs == value) return
- world.storage.setParcelAllowsInteractInputs(this, value)
- data.allowInteractInputs = value
+ private var _interactableConfig: InteractableConfiguration? = null
+ override var interactableConfig: InteractableConfiguration
+ get() {
+ if (_interactableConfig == null) {
+ _interactableConfig = object : InteractableConfiguration {
+ override fun isInteractable(material: Material): Boolean = data.interactableConfig.isInteractable(material)
+ override fun isInteractable(clazz: Interactables): Boolean = data.interactableConfig.isInteractable(clazz)
+
+ override fun setInteractable(clazz: Interactables, interactable: Boolean): Boolean =
+ data.interactableConfig.setInteractable(clazz, interactable).alsoIfTrue {
+ // TODO update storage
+ }
+
+ override fun clear(): Boolean =
+ data.interactableConfig.clear().alsoIfTrue {
+ // TODO update storage
+ }
+ }
+ }
+ return _interactableConfig!!
}
-
- override var allowInteractInventory: Boolean
- get() = data.allowInteractInventory
set(value) {
- if (data.allowInteractInventory == value) return
- world.storage.setParcelAllowsInteractInventory(this, value)
- data.allowInteractInventory = value
+ data.interactableConfig.copyFrom(value)
+ // TODO update storage
}
+ private var blockVisitors = AtomicInteger(0)
+ override suspend fun withBlockVisitorPermit(block: suspend () -> Unit) {
+ try {
+ blockVisitors.getAndIncrement()
+ block()
+ } finally {
+ blockVisitors.getAndDecrement()
+ }
+ }
+
+ override fun toString() = toStringExt()
}
private object ParcelInfoStringComputer {
@@ -121,13 +153,15 @@ private object ParcelInfoStringComputer {
append(infoStringColor1)
append(')')
}) {
- stringList.joinTo(this,
+ stringList.joinTo(
+ this,
separator = infoStringColor1.toString() + ", " + infoStringColor2,
- limit = 150)
+ limit = 150
+ )
}
}
- operator fun getValue(parcel: Parcel, property: KProperty<*>): String = buildString {
+ fun getInfoString(parcel: Parcel): String = buildString {
appendField("ID") {
append(parcel.x)
append(',')
@@ -154,6 +188,7 @@ private object ParcelInfoStringComputer {
append('\n')
appendAddedList(local, global, AddedStatus.BANNED, "Banned")
+ /* TODO options
if (!parcel.allowInteractInputs || !parcel.allowInteractInventory) {
appendField("Options") {
append("(")
@@ -162,7 +197,7 @@ private object ParcelInfoStringComputer {
appendField("inventory") { append(parcel.allowInteractInventory) }
append(")")
}
- }
+ }*/
}
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt
index 70d428d..8920e2e 100644
--- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt
@@ -1,8 +1,14 @@
package io.dico.parcels2.defaultimpl
import io.dico.parcels2.*
+import io.dico.parcels2.util.schedule
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.CoroutineStart.*
+import kotlinx.coroutines.Unconfined
+import kotlinx.coroutines.launch
import org.bukkit.Bukkit
import org.bukkit.WorldCreator
+import org.joda.time.DateTime
class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
inline val options get() = plugin.options
@@ -39,7 +45,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
private fun loadWorlds0() {
if (Bukkit.getWorlds().isEmpty()) {
- plugin.functionHelper.schedule(::loadWorlds0)
+ plugin.schedule(::loadWorlds0)
plugin.logger.warning("Scheduling to load worlds in the next tick, \nbecause no bukkit worlds are loaded yet")
return
}
@@ -49,9 +55,24 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
if (parcelWorld != null) continue
val generator: ParcelGenerator = getWorldGenerator(worldName)!!
- val bukkitWorld = Bukkit.getWorld(worldName) ?: WorldCreator(worldName).generator(generator).createWorld()
+ val worldExists = Bukkit.getWorld(worldName) != null
+ val bukkitWorld =
+ if (worldExists) Bukkit.getWorld(worldName)!!
+ else WorldCreator(worldName).generator(generator).createWorld().also { logger.info("Creating world $worldName") }
+
parcelWorld = ParcelWorldImpl(bukkitWorld, generator, worldOptions.runtime, plugin.storage,
- plugin.globalAddedData, ::DefaultParcelContainer, plugin.worktimeLimiter)
+ plugin.globalAddedData, ::DefaultParcelContainer, plugin, plugin.worktimeLimiter)
+
+ if (!worldExists) {
+ val time = DateTime.now()
+ plugin.storage.setWorldCreationTime(parcelWorld.id, time)
+ parcelWorld.creationTime = time
+ } else {
+ launch(context = Unconfined) {
+ parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?: DateTime.now()
+ }
+ }
+
_worlds[worldName] = parcelWorld
}
@@ -59,7 +80,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
}
private fun loadStoredData() {
- plugin.functionHelper.launchLazilyOnMainThread {
+ plugin.launch {
val migration = plugin.options.migration
if (migration.enabled) {
migration.instance?.newInstance()?.apply {
@@ -84,7 +105,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
logger.info("Loading data completed")
_dataIsLoaded = true
- }.start()
+ }
}
/*
diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt
index 4a168f5..9f96a8c 100644
--- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt
+++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt
@@ -6,22 +6,24 @@ import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.options.RuntimeWorldOptions
import io.dico.parcels2.storage.Storage
+import kotlinx.coroutines.CoroutineScope
import org.bukkit.World
+import org.joda.time.DateTime
import java.util.UUID
-class ParcelWorldImpl private
-constructor(override val world: World,
- override val generator: ParcelGenerator,
- override var options: RuntimeWorldOptions,
- override val storage: Storage,
- override val globalAddedData: GlobalAddedDataManager,
- containerFactory: ParcelContainerFactory,
- blockManager: ParcelBlockManager)
+class ParcelWorldImpl(override val world: World,
+ override val generator: ParcelGenerator,
+ override var options: RuntimeWorldOptions,
+ override val storage: Storage,
+ override val globalAddedData: GlobalAddedDataManager,
+ containerFactory: ParcelContainerFactory,
+ coroutineScope: CoroutineScope,
+ worktimeLimiter: WorktimeLimiter)
: ParcelWorld,
ParcelWorldId,
- ParcelContainer, // missing delegation
- ParcelLocator, // missing delegation
- ParcelBlockManager by blockManager {
+ ParcelContainer, /* missing delegation */
+ ParcelLocator /* missing delegation */ {
+
override val id: ParcelWorldId get() = this
override val uid: UUID? get() = world.uid
@@ -33,10 +35,14 @@ constructor(override val world: World,
override val name: String = world.name!!
override val container: ParcelContainer = containerFactory(this)
- override val locator: ParcelLocator = generator.makeParcelLocator(container)
- override val blockManager: ParcelBlockManager = blockManager
+ override val locator: ParcelLocator
+ override val blockManager: ParcelBlockManager
init {
+ val pair = generator.makeParcelLocatorAndBlockManager(id, container, coroutineScope, worktimeLimiter)
+ locator = pair.first
+ blockManager = pair.second
+
enforceOptions()
}
@@ -55,24 +61,13 @@ constructor(override val world: World,
world.setGameRuleValue("doTileDrops", "${options.doTileDrops}")
}
+ // Updated by ParcelProviderImpl
+ override var creationTime: DateTime? = null
+
/*
Interface delegation needs to be implemented manually because JetBrains has yet to fix it.
*/
- companion object {
- // Use this to be able to delegate blockManager and assign it to a property too, at least.
- operator fun invoke(world: World,
- generator: ParcelGenerator,
- options: RuntimeWorldOptions,
- storage: Storage,
- globalAddedData: GlobalAddedDataManager,
- containerFactory: ParcelContainerFactory,
- worktimeLimiter: WorktimeLimiter): ParcelWorldImpl {
- val blockManager = generator.makeParcelBlockManager(worktimeLimiter)
- return ParcelWorldImpl(world, generator, options, storage, globalAddedData, containerFactory, blockManager)
- }
- }
-
// ParcelLocator interface
override fun getParcelAt(x: Int, z: Int): Parcel? {
return locator.getParcelAt(x, z)
@@ -91,5 +86,5 @@ constructor(override val world: World,
return container.nextEmptyParcel()
}
-
+ override fun toString() = toStringExt()
}
diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt
index edb48b5..eaacf93 100644
--- a/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt
+++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelEntityTracker.kt
@@ -2,8 +2,8 @@ package io.dico.parcels2.listener
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider
-import io.dico.parcels2.util.editLoop
-import io.dico.parcels2.util.isPresentAnd
+import io.dico.parcels2.util.ext.editLoop
+import io.dico.parcels2.util.ext.isPresentAnd
import org.bukkit.entity.Entity
class ParcelEntityTracker(val parcelProvider: ParcelProvider) {
diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt
index fa5ccb4..e39583c 100644
--- a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt
+++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt
@@ -3,11 +3,9 @@ package io.dico.parcels2.listener
import gnu.trove.TLongCollection
import io.dico.dicore.ListenerMarker
import io.dico.dicore.RegistratorListener
-import io.dico.parcels2.Parcel
-import io.dico.parcels2.ParcelProvider
-import io.dico.parcels2.ParcelWorld
-import io.dico.parcels2.statusKey
-import io.dico.parcels2.util.*
+import io.dico.parcels2.*
+import io.dico.parcels2.storage.Storage
+import io.dico.parcels2.util.ext.*
import org.bukkit.Material.*
import org.bukkit.World
import org.bukkit.block.Biome
@@ -16,6 +14,7 @@ import org.bukkit.block.data.Directional
import org.bukkit.block.data.type.Bed
import org.bukkit.entity.*
import org.bukkit.entity.minecart.ExplosiveMinecart
+import org.bukkit.event.EventPriority
import org.bukkit.event.EventPriority.NORMAL
import org.bukkit.event.block.*
import org.bukkit.event.entity.*
@@ -26,11 +25,17 @@ import org.bukkit.event.inventory.InventoryInteractEvent
import org.bukkit.event.player.*
import org.bukkit.event.vehicle.VehicleMoveEvent
import org.bukkit.event.weather.WeatherChangeEvent
+import org.bukkit.event.world.ChunkLoadEvent
import org.bukkit.event.world.StructureGrowEvent
import org.bukkit.inventory.InventoryHolder
+import java.util.EnumSet
@Suppress("NOTHING_TO_INLINE")
-class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: ParcelEntityTracker) {
+class ParcelListeners(
+ val parcelProvider: ParcelProvider,
+ val entityTracker: ParcelEntityTracker,
+ val storage: Storage
+) {
private inline fun Parcel?.canBuildN(user: Player) = isPresentAnd { canBuild(user) } || user.hasBuildAnywhere
/**
@@ -54,7 +59,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
val parcel = parcelProvider.getParcelAt(event.to) ?: return@l
if (parcel.isBanned(user.statusKey)) {
parcelProvider.getParcelAt(event.from)?.also {
- user.teleport(it.world.getHomeLocation(it.id))
+ user.teleport(it.homeLocation)
user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel")
} ?: run { event.to = event.from }
}
@@ -165,6 +170,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
if (ppa.isNullOr { hasBlockVisitors }) event.isCancelled = true
}
+ private val bedTypes = EnumSet.copyOf(getMaterialsWithWoodTypePrefix("BED").toList())
/*
* Prevents players from placing liquids, using flint and steel, changing redstone components,
* using inputs (unless allowed by the plot),
@@ -186,49 +192,33 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
when (event.action) {
Action.RIGHT_CLICK_BLOCK -> run {
- when (clickedBlock.type) {
- REPEATER,
- COMPARATOR -> run {
- if (!parcel.canBuildN(user)) {
- event.isCancelled = true; return@l
- }
- }
- LEVER,
- STONE_BUTTON,
- ANVIL,
- TRAPPED_CHEST,
- OAK_BUTTON, BIRCH_BUTTON, SPRUCE_BUTTON, JUNGLE_BUTTON, ACACIA_BUTTON, DARK_OAK_BUTTON,
- OAK_FENCE_GATE, BIRCH_FENCE_GATE, SPRUCE_FENCE_GATE, JUNGLE_FENCE_GATE, ACACIA_FENCE_GATE, DARK_OAK_FENCE_GATE,
- OAK_DOOR, BIRCH_DOOR, SPRUCE_DOOR, JUNGLE_DOOR, ACACIA_DOOR, DARK_OAK_DOOR,
- OAK_TRAPDOOR, BIRCH_TRAPDOOR, SPRUCE_TRAPDOOR, JUNGLE_TRAPDOOR, ACACIA_TRAPDOOR, DARK_OAK_TRAPDOOR
- -> run {
- if (!user.hasBuildAnywhere && !parcel.isNullOr { canBuild(user) || allowInteractInputs }) {
- user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
- event.isCancelled = true; return@l
- }
- }
+ val type = clickedBlock.type
+ val interactable = parcel.effectiveInteractableConfig.isInteractable(type) || parcel.isPresentAnd { canBuild(user) }
+ if (!interactable) {
+ val interactableClassName = Interactables[type]!!.name
+ user.sendParcelMessage(nopermit = true, message = "You cannot interact with $interactableClassName in this parcel")
+ event.isCancelled = true
+ return@l
+ }
- WHITE_BED, ORANGE_BED, MAGENTA_BED, LIGHT_BLUE_BED, YELLOW_BED, LIME_BED, PINK_BED, GRAY_BED, LIGHT_GRAY_BED, CYAN_BED, PURPLE_BED, BLUE_BED, BROWN_BED, GREEN_BED, RED_BED, BLACK_BED
- -> run {
- if (world.options.disableExplosions) {
- val bed = clickedBlock.blockData as Bed
- val head = if (bed == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock
- when (head.biome) {
- Biome.NETHER, Biome.THE_END -> run {
- user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode")
- event.isCancelled = true; return@l
- }
+ if (bedTypes.contains(type)) {
+ val bed = clickedBlock.blockData as Bed
+ val head = if (bed == Bed.Part.FOOT) clickedBlock.getRelative(bed.facing) else clickedBlock
+ when (head.biome) {
+ Biome.NETHER, Biome.THE_END -> {
+ if (world.options.disableExplosions || parcel.isNullOr { !canBuild(user) }) {
+ user.sendParcelMessage(nopermit = true, message = "You cannot use this bed because it would explode")
+ event.isCancelled = true; return@l
}
-
}
-
}
}
+
onPlayerInteractEvent_RightClick(event, world, parcel)
}
Action.RIGHT_CLICK_AIR -> onPlayerInteractEvent_RightClick(event, world, parcel)
- Action.PHYSICAL -> if (!user.hasBuildAnywhere && !parcel.isPresentAnd { canBuild(user) || allowInteractInputs }) {
+ Action.PHYSICAL -> if (!user.hasBuildAnywhere && !parcel.isPresentAnd { canBuild(user) || interactableConfig("pressure_plates") }) {
user.sendParcelMessage(nopermit = true, message = "You cannot use inputs in this parcel")
event.isCancelled = true; return@l
}
@@ -317,7 +307,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
@field:ListenerMarker(priority = NORMAL)
val onPlayerDropItemEvent = RegistratorListener<PlayerDropItemEvent> l@{ event ->
val (wo, ppa) = getWoAndPPa(event.itemDrop.location.block) ?: return@l
- if (!ppa.canBuildN(event.player) && !ppa.isPresentAnd { allowInteractInventory }) event.isCancelled = true
+ if (!ppa.canBuildN(event.player) && !ppa.isPresentAnd { interactableConfig("containers") }) event.isCancelled = true
}
/*
@@ -338,7 +328,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
val user = event.whoClicked as? Player ?: return@l
if ((event.inventory ?: return@l).holder === user) return@l // inventory null: hotbar
val (wo, ppa) = getWoAndPPa(event.inventory.location.block) ?: return@l
- if (ppa.isNullOr { !canBuild(user) && !allowInteractInventory }) {
+ if (ppa.isNullOr { !canBuild(user) && !interactableConfig("containers") }) {
event.isCancelled = true
}
}
@@ -380,10 +370,10 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
val cancel: Boolean = when (event.newState.type) {
- // prevent ice generation from Frost Walkers enchantment
+ // prevent ice generation from Frost Walkers enchantment
FROSTED_ICE -> player != null && !ppa.canBuild(player)
- // prevent snow generation from weather
+ // prevent snow generation from weather
SNOW -> !hasEntity && wo.options.preventWeatherBlockChanges
else -> false
@@ -575,4 +565,26 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par
}
}
+ /**
+ * Updates owner signs of parcels that get loaded if it is marked outdated
+ */
+ @ListenerMarker(priority = EventPriority.NORMAL)
+ val onChunkLoadEvent = RegistratorListener<ChunkLoadEvent> l@{ event ->
+ val world = parcelProvider.getWorld(event.chunk.world) ?: return@l
+ val parcels = world.blockManager.getParcelsWithOwnerBlockIn(event.chunk)
+ if (parcels.isEmpty()) return@l
+
+ parcels.forEach { id ->
+ val parcel = world.getParcelById(id)?.takeIf { it.ownerSignOutdated } ?: return@forEach
+ world.blockManager.setOwnerBlock(parcel.id, parcel.owner)
+ parcel.ownerSignOutdated = false
+ }
+
+ }
+
+ @ListenerMarker
+ val onPlayerJoinEvent = RegistratorListener<PlayerJoinEvent> l@{ event ->
+ storage.updatePlayerName(event.player.uuid, event.player.name)
+ }
+
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt b/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt
new file mode 100644
index 0000000..b68f7c2
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/listener/WorldEditListener.kt
@@ -0,0 +1,80 @@
+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.WorldEditException
+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.util.eventbus.EventHandler.Priority.VERY_EARLY
+import com.sk89q.worldedit.util.eventbus.Subscribe
+import com.sk89q.worldedit.world.biome.BaseBiome
+import com.sk89q.worldedit.world.block.BaseBlock
+import com.sk89q.worldedit.world.block.BlockStateHolder
+import io.dico.parcels2.ParcelWorld
+import io.dico.parcels2.ParcelsPlugin
+import io.dico.parcels2.util.ext.hasBuildAnywhere
+import io.dico.parcels2.util.ext.sendParcelMessage
+import org.bukkit.entity.Player
+import org.bukkit.plugin.Plugin
+
+class WorldEditListener(val parcels: ParcelsPlugin, val worldEdit: WorldEdit) {
+
+ @Subscribe(priority = VERY_EARLY)
+ fun onEditSession(event: EditSessionEvent) {
+ val worldName = event.world?.name ?: return
+ val world = parcels.parcelProvider.getWorld(worldName) ?: return
+ if (event.stage == BEFORE_REORDER) return
+
+ val actor = event.actor
+ if (actor == null || !actor.isPlayer) return
+
+ val player = parcels.server.getPlayer(actor.uniqueId)
+ if (player.hasBuildAnywhere) return
+
+ event.extent = ParcelsExtent(event.extent, world, player)
+ }
+
+ private class ParcelsExtent(extent: Extent,
+ val world: ParcelWorld,
+ val player: Player) : AbstractDelegateExtent(extent) {
+ private var messageSent = false
+
+ private fun canBuild(x: Int, z: Int): Boolean {
+ world.getParcelAt(x, z)?.let { parcel ->
+ if (parcel.canBuild(player, checkAdmin = false)) {
+ return true
+ }
+ }
+
+ if (!messageSent) {
+ messageSent = true
+ player.sendParcelMessage(except = true, message = "You can't use WorldEdit there")
+ }
+
+ 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 {
+ return canBuild(coord.blockX, coord.blockZ) && super.setBiome(coord, biome)
+ }
+
+ }
+
+ companion object {
+ fun register(parcels: ParcelsPlugin, worldEditPlugin: Plugin) {
+ if (worldEditPlugin !is WorldEditPlugin) return
+ val worldEdit = worldEditPlugin.worldEdit
+ val listener = WorldEditListener(parcels, worldEdit)
+ worldEdit.eventBus.register(listener)
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt
index b658d10..6c91714 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt
@@ -1,11 +1,12 @@
package io.dico.parcels2.storage
import io.dico.parcels2.*
-import kotlinx.coroutines.experimental.CoroutineDispatcher
-import kotlinx.coroutines.experimental.Deferred
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.channels.ReceiveChannel
-import kotlinx.coroutines.experimental.channels.SendChannel
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.SendChannel
+import org.joda.time.DateTime
import java.util.UUID
interface Backing {
@@ -30,8 +31,14 @@ interface Backing {
fun shutdown()
+ fun getWorldCreationTime(worldId: ParcelWorldId): DateTime?
+
+ fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime)
+
fun getPlayerUuidForName(name: String): UUID?
+ fun updatePlayerName(uuid: UUID, name: String)
+
fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>)
fun transmitAllParcelData(channel: SendChannel<DataPair>)
@@ -47,12 +54,16 @@ interface Backing {
fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?)
+ fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean)
+
fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus)
+ fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray?)
+/*
fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean)
fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean)
-
+*/
fun transmitAllGlobalAddedData(channel: SendChannel<AddedDataPair<PlayerProfile>>)
diff --git a/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt b/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt
new file mode 100644
index 0000000..80f41b2
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/storage/DataConverters.kt
@@ -0,0 +1,38 @@
+package io.dico.parcels2.storage
+
+import java.lang.IllegalArgumentException
+import java.nio.ByteBuffer
+import java.util.UUID
+
+/* For putting it into the database */
+fun UUID.toByteArray(): ByteArray =
+ ByteBuffer.allocate(16).apply {
+ putLong(mostSignificantBits)
+ putLong(leastSignificantBits)
+ }.array()
+
+/* For getting it out of the database */
+fun ByteArray.toUUID(): UUID =
+ ByteBuffer.wrap(this).run {
+ val mostSignificantBits = getLong()
+ val leastSignificantBits = getLong()
+ UUID(mostSignificantBits, leastSignificantBits)
+ }
+
+/* For putting it into the database */
+fun IntArray.toByteArray(): ByteArray =
+ ByteBuffer.allocate(size * Int.SIZE_BYTES).also { buf ->
+ buf.asIntBuffer().put(this)
+ }.array()
+
+/* For getting it out of the database */
+fun ByteArray.toIntArray(): IntArray {
+ if (this.size % Int.SIZE_BYTES != 0)
+ throw IllegalArgumentException("Size must be divisible by ${Int.SIZE_BYTES}")
+
+ return ByteBuffer.wrap(this).run {
+ IntArray(remaining() / 4).also { array ->
+ asIntBuffer().get(array)
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt
index 2116b46..c9c0d4a 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt
@@ -3,11 +3,12 @@
package io.dico.parcels2.storage
import io.dico.parcels2.*
-import kotlinx.coroutines.experimental.Deferred
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.channels.ReceiveChannel
-import kotlinx.coroutines.experimental.channels.SendChannel
-import kotlinx.coroutines.experimental.launch
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.launch
+import org.joda.time.DateTime
import java.util.UUID
typealias DataPair = Pair<ParcelId, ParcelData?>
@@ -22,8 +23,14 @@ interface Storage {
fun shutdown(): Job
+ fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?>
+
+ fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job
+
fun getPlayerUuidForName(name: String): Deferred<UUID?>
+ fun updatePlayerName(uuid: UUID, name: String): Job
+
fun readParcelData(parcel: ParcelId): Deferred<ParcelData?>
fun transmitParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair>
@@ -39,11 +46,11 @@ interface Storage {
fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job
- fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus): Job
+ fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job
- fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Job
+ fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus): Job
- fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean): Job
+ fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray): Job
fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>>
@@ -65,8 +72,14 @@ class BackedStorage internal constructor(val b: Backing) : Storage {
override fun shutdown() = launch(b.dispatcher) { b.shutdown() }
+ override fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?> = b.launchFuture { b.getWorldCreationTime(worldId) }
+
+ override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job = b.launchJob { b.setWorldCreationTime(worldId, time) }
+
override fun getPlayerUuidForName(name: String): Deferred<UUID?> = b.launchFuture { b.getPlayerUuidForName(name) }
+ override fun updatePlayerName(uuid: UUID, name: String): Job = b.launchJob { b.updatePlayerName(uuid, name) }
+
override fun readParcelData(parcel: ParcelId) = b.launchFuture { b.readParcelData(parcel) }
override fun transmitParcelData(parcels: Sequence<ParcelId>) = b.openChannel<DataPair> { b.transmitParcelData(it, parcels) }
@@ -81,11 +94,11 @@ class BackedStorage internal constructor(val b: Backing) : Storage {
override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) = b.launchJob { b.setParcelOwner(parcel, owner) }
- override fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) = b.launchJob { b.setLocalPlayerStatus(parcel, player, status) }
+ override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job = b.launchJob { b.setParcelOwnerSignOutdated(parcel, outdated) }
- override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) = b.launchJob { b.setParcelAllowsInteractInventory(parcel, value) }
+ override fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) = b.launchJob { b.setLocalPlayerStatus(parcel, player, status) }
- override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) = b.launchJob { b.setParcelAllowsInteractInputs(parcel, value) }
+ override fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray) = b.launchJob { b.setParcelOptionsInteractBitmask(parcel, bitmask) }
override fun transmitAllGlobalAddedData(): ReceiveChannel<AddedDataPair<PlayerProfile>> = b.openChannel { b.transmitAllGlobalAddedData(it) }
diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt
index 8ea6653..b7f9f82 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt
@@ -4,16 +4,15 @@ package io.dico.parcels2.storage.exposed
import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.*
-import io.dico.parcels2.storage.AddedDataPair
-import io.dico.parcels2.storage.Backing
-import io.dico.parcels2.storage.DataPair
-import io.dico.parcels2.util.synchronized
-import io.dico.parcels2.util.toUUID
-import kotlinx.coroutines.experimental.*
-import kotlinx.coroutines.experimental.channels.ArrayChannel
-import kotlinx.coroutines.experimental.channels.LinkedListChannel
-import kotlinx.coroutines.experimental.channels.ReceiveChannel
-import kotlinx.coroutines.experimental.channels.SendChannel
+import io.dico.parcels2.PlayerProfile.Star.name
+import io.dico.parcels2.storage.*
+import io.dico.parcels2.util.ext.clampMax
+import io.dico.parcels2.util.ext.synchronized
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.ArrayChannel
+import kotlinx.coroutines.channels.LinkedListChannel
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.SendChannel
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SchemaUtils.create
import org.jetbrains.exposed.sql.transactions.transaction
@@ -92,8 +91,9 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
}
}
+ @Suppress("RedundantObjectTypeCheck")
private fun PlayerProfile.toOwnerProfile(): PlayerProfile {
- if (this is PlayerProfile.Star) return PlayerProfile.Fake(PlayerProfile.Star.name)
+ if (this is PlayerProfile.Star) return PlayerProfile.Fake(name)
return this
}
@@ -113,11 +113,28 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
else -> throw InternalError("Case should not be reached")
}
+
+ override fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? {
+ return WorldsT.getWorldCreationTime(worldId)
+ }
+
+ override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) {
+ WorldsT.setWorldCreationTime(worldId, time)
+ }
+
override fun getPlayerUuidForName(name: String): UUID? {
return ProfilesT.slice(ProfilesT.uuid).select { ProfilesT.name.upperCase() eq name.toUpperCase() }
.firstOrNull()?.let { it[ProfilesT.uuid]?.toUUID() }
}
+ override fun updatePlayerName(uuid: UUID, name: String) {
+ val binaryUuid = uuid.toByteArray()
+ ProfilesT.upsert(ProfilesT.uuid) {
+ it[ProfilesT.uuid] = binaryUuid
+ it[ProfilesT.name] = name
+ }
+ }
+
override fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) {
for (parcel in parcels) {
val data = readParcelData(parcel)
@@ -176,8 +193,9 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
AddedLocalT.setPlayerStatus(parcel, profile, status)
}
- setParcelAllowsInteractInputs(parcel, data.allowInteractInputs)
- setParcelAllowsInteractInventory(parcel, data.allowInteractInventory)
+ val bitmaskArray = (data.interactableConfig as? BitmaskInteractableConfiguration ?: return).bitmaskArray
+ val isAllZero = bitmaskArray.fold(false) { cur, elem -> cur || elem != 0 }
+ setParcelOptionsInteractBitmask(parcel, if (isAllZero) null else bitmaskArray)
}
override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) {
@@ -192,6 +210,14 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
ParcelsT.update({ ParcelsT.id eq id }) {
it[ParcelsT.owner_id] = owner_id
it[claim_time] = time
+ it[sign_oudated] = false
+ }
+ }
+
+ override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean) {
+ val id = ParcelsT.getId(parcel) ?: return
+ ParcelsT.update({ ParcelsT.id eq id }) {
+ it[sign_oudated] = outdated
}
}
@@ -199,19 +225,19 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
AddedLocalT.setPlayerStatus(parcel, player.toRealProfile(), status)
}
- override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) {
- val id = ParcelsT.getOrInitId(parcel)
- ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
- it[ParcelOptionsT.parcel_id] = id
- it[ParcelOptionsT.interact_inventory] = value
+ override fun setParcelOptionsInteractBitmask(parcel: ParcelId, bitmask: IntArray?) {
+ if (bitmask == null) {
+ val id = ParcelsT.getId(parcel) ?: return
+ ParcelOptionsT.deleteWhere { ParcelOptionsT.parcel_id eq id }
+ return
}
- }
- override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) {
+ if (bitmask.size != 1) throw IllegalArgumentException()
+ val array = bitmask.toByteArray()
val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
- it[ParcelOptionsT.parcel_id] = id
- it[ParcelOptionsT.interact_inputs] = value
+ it[parcel_id] = id
+ it[interact_bitmask] = array
}
}
@@ -230,12 +256,14 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) }
- since = row[ParcelsT.claim_time]
+ lastClaimTime = row[ParcelsT.claim_time]
+ ownerSignOutdated = row[ParcelsT.sign_oudated]
val id = row[ParcelsT.id]
ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow ->
- allowInteractInputs = optrow[ParcelOptionsT.interact_inputs]
- allowInteractInventory = optrow[ParcelOptionsT.interact_inventory]
+ val source = optrow[ParcelOptionsT.interact_bitmask].toIntArray()
+ val target = (interactableConfig as? BitmaskInteractableConfiguration ?: return@let).bitmaskArray
+ System.arraycopy(source, 0, target, 0, source.size.clampMax(target.size))
}
addedMap = AddedLocalT.readAddedData(id)
diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt
index 0aa4ba0..696b84c 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt
@@ -5,10 +5,11 @@ package io.dico.parcels2.storage.exposed
import io.dico.parcels2.ParcelId
import io.dico.parcels2.ParcelWorldId
import io.dico.parcels2.PlayerProfile
-import io.dico.parcels2.util.toByteArray
-import io.dico.parcels2.util.toUUID
+import io.dico.parcels2.storage.toByteArray
+import io.dico.parcels2.storage.toUUID
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.UpdateBuilder
+import org.joda.time.DateTime
import java.util.UUID
abstract class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String)
@@ -24,7 +25,8 @@ abstract class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj
}
internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int {
- return getId() ?: table.insertIgnore(body)[id] ?: getId() ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its id")
+ return getId() ?: table.insertIgnore(body)[id] ?: getId()
+ ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its id")
}
abstract fun getId(obj: QueryObj): Int?
@@ -35,9 +37,10 @@ abstract class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj
fun getId(obj: QueryObj, init: Boolean): Int? = if (init) getOrInitId(obj) else getId(obj)
}
-object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcel_worlds", "world_id") {
+object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcels_worlds", "world_id") {
val name = varchar("name", 50)
val uid = binary("uid", 16).nullable()
+ val creation_time = datetime("creation_time").nullable()
val index_name = uniqueIndexR("index_name", name)
val index_uid = uniqueIndexR("index_uid", uid)
@@ -56,6 +59,18 @@ object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcel_worlds", "w
override fun getItem(row: ResultRow): ParcelWorldId {
return ParcelWorldId(row[name], row[uid]?.toUUID())
}
+
+ fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? {
+ val id = getId(worldId) ?: return null
+ return select { WorldsT.id eq id }.firstOrNull()?.let { it[WorldsT.creation_time] }
+ }
+
+ fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) {
+ val id = getOrInitId(worldId)
+ update({ WorldsT.id eq id }) {
+ it[WorldsT.creation_time] = time
+ }
+ }
}
object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id") {
@@ -63,6 +78,7 @@ object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id"
val px = integer("px")
val pz = integer("pz")
val owner_id = integer("owner_id").references(ProfilesT.id).nullable()
+ val sign_oudated = bool("sign_outdated").default(false)
val claim_time = datetime("claim_time").nullable()
val index_location = uniqueIndexR("index_location", world_id, px, pz)
@@ -89,17 +105,22 @@ object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id"
}
}
-object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcel_profiles", "owner_id") {
+object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcels_profiles", "owner_id") {
val uuid = binary("uuid", 16).nullable()
- val name = varchar("name", 32)
+ val name = varchar("name", 32).nullable()
+
+ // MySQL dialect MUST permit multiple null values for this to work. Server SQL does not allow this. That dialect is shit anyway.
+ val uuid_constraint = uniqueIndexR("uuid_constraint", uuid)
val index_pair = uniqueIndexR("index_pair", uuid, name)
+
private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid }
private inline fun getId(uuid: UUID) = getId(uuid.toByteArray())
private inline fun getId(nameIn: String) = getId { uuid.isNull() and (name.lowerCase() eq nameIn.toLowerCase()) }
private inline fun getRealId(nameIn: String) = getId { uuid.isNotNull() and (name.lowerCase() eq nameIn.toLowerCase()) }
- private inline fun getOrInitId(uuid: UUID, name: String) = uuid.toByteArray().let { binaryUuid -> getOrInitId(
+ private inline fun getOrInitId(uuid: UUID, name: String?) = uuid.toByteArray().let { binaryUuid ->
+ getOrInitId(
{ getId(binaryUuid) },
{ it[this@ProfilesT.uuid] = binaryUuid; it[this@ProfilesT.name] = name },
{ "profile(uuid = $uuid, name = $name)" })
@@ -119,9 +140,9 @@ object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcel_profile
}
override fun getOrInitId(profile: PlayerProfile): Int = when (profile) {
- is PlayerProfile.Real -> getOrInitId(profile.uuid, profile.notNullName)
+ is PlayerProfile.Real -> getOrInitId(profile.uuid, profile.nameOrBukkitName)
is PlayerProfile.Fake -> getOrInitId(profile.name)
- else -> throw IllegalArgumentException()
+ else -> throw IllegalArgumentException() // Unresolved profiles cannot be added to the database
}
override fun getItem(row: ResultRow): PlayerProfile {
@@ -132,6 +153,13 @@ object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcel_profile
return getItem(id) as? PlayerProfile.Real
}
+ /*
+ fun updatePlayerProfile(profile: PlayerProfile.Real) {
+ update({ uuid eq profile.uuid.toByteArray() }) {
+ it[name] = profile.nameOrBukkitName
+ }
+ }*/
+
}
// val ParcelsWithOptionsT = ParcelsT.join(ParcelOptionsT, JoinType.INNER, onColumn = ParcelsT.id, otherColumn = ParcelOptionsT.parcel_id) \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt
index bbf6872..f41d545 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ListTables.kt
@@ -3,7 +3,9 @@
package io.dico.parcels2.storage.exposed
import io.dico.parcels2.*
-import kotlinx.coroutines.experimental.channels.SendChannel
+import io.dico.parcels2.AddedStatus.ALLOWED
+import io.dico.parcels2.AddedStatus.DEFAULT
+import kotlinx.coroutines.channels.SendChannel
import org.jetbrains.exposed.sql.*
import java.util.UUID
@@ -12,8 +14,7 @@ object AddedGlobalT : AddedTable<PlayerProfile>("parcels_added_global", Profiles
object ParcelOptionsT : Table("parcel_options") {
val parcel_id = integer("parcel_id").primaryKey().references(ParcelsT.id, ReferenceOption.CASCADE)
- val interact_inventory = bool("interact_inventory").default(true)
- val interact_inputs = bool("interact_inputs").default(true)
+ val interact_bitmask = binary("interact_bitmask", 4).default(ByteArray(4) { 0 }) // all zero by default
}
typealias AddedStatusSendChannel<AttachT> = SendChannel<Pair<AttachT, MutableAddedDataMap>>
@@ -25,7 +26,7 @@ sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<
val index_pair = uniqueIndexR("index_pair", attach_id, profile_id)
fun setPlayerStatus(attachedOn: AttachT, player: PlayerProfile.Real, status: AddedStatus) {
- if (status.isDefault) {
+ if (status == DEFAULT) {
val player_id = ProfilesT.getId(player) ?: return
idTable.getId(attachedOn)?.let { holder ->
deleteWhere { (attach_id eq holder) and (profile_id eq player_id) }
@@ -38,7 +39,7 @@ sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<
upsert(conflictIndex = index_pair) {
it[attach_id] = holder
it[profile_id] = player_id
- it[allowed_flag] = status.isAllowed
+ it[allowed_flag] = status == ALLOWED
}
}
@@ -97,6 +98,6 @@ sealed class AddedTable<AttachT>(name: String, val idTable: IdTransactionsTable<
}
}
- private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) AddedStatus.ALLOWED else AddedStatus.BANNED
+ private inline fun Boolean?.asAddedStatus() = if (this == null) AddedStatus.DEFAULT else if (this) ALLOWED else AddedStatus.BANNED
}
diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt
index 0db669a..acc7c5e 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/migration/Migration.kt
@@ -1,7 +1,7 @@
package io.dico.parcels2.storage.migration
import io.dico.parcels2.storage.Storage
-import kotlinx.coroutines.experimental.Job
+import kotlinx.coroutines.Job
interface Migration {
fun migrateTo(storage: Storage): Job
diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt
index f0c0cd8..0dcf36d 100644
--- a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt
+++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt
@@ -9,10 +9,10 @@ import io.dico.parcels2.storage.Storage
import io.dico.parcels2.storage.exposed.abs
import io.dico.parcels2.storage.exposed.greater
import io.dico.parcels2.storage.migration.Migration
-import io.dico.parcels2.util.toUUID
-import kotlinx.coroutines.experimental.Job
-import kotlinx.coroutines.experimental.launch
-import kotlinx.coroutines.experimental.newFixedThreadPoolContext
+import io.dico.parcels2.storage.toUUID
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.newFixedThreadPoolContext
import org.jetbrains.exposed.sql.*
import org.slf4j.LoggerFactory
import java.sql.Blob
@@ -83,6 +83,7 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
val parcel = getParcelId(PlotmePlotsT, row) ?: return@forEach
val owner = PlayerProfile.safe(row[PlotmePlotsT.owner_uuid]?.toUUID(), row[PlotmePlotsT.owner_name])
target.setParcelOwner(parcel, owner)
+ target.setParcelOwnerSignOutdated(parcel, true)
}
}
diff --git a/src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt b/src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt
deleted file mode 100644
index ea16652..0000000
--- a/src/main/kotlin/io/dico/parcels2/util/FunctionHelper.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package io.dico.parcels2.util
-
-import io.dico.parcels2.ParcelsPlugin
-import kotlinx.coroutines.experimental.*
-import org.bukkit.scheduler.BukkitTask
-import kotlin.coroutines.experimental.CoroutineContext
-
-@Suppress("NOTHING_TO_INLINE")
-class FunctionHelper(val plugin: ParcelsPlugin) {
- val mainThreadDispatcher: MainThreadDispatcher = MainThreadDispatcherImpl()
-
- fun <T> deferLazilyOnMainThread(block: suspend CoroutineScope.() -> T): Deferred<T> {
- return async(context = mainThreadDispatcher, start = CoroutineStart.LAZY, block = block)
- }
-
- fun <T> deferUndispatchedOnMainThread(block: suspend CoroutineScope.() -> T): Deferred<T> {
- return async(context = mainThreadDispatcher, start = CoroutineStart.UNDISPATCHED, block = block)
- }
-
- fun launchLazilyOnMainThread(block: suspend CoroutineScope.() -> Unit): Job {
- return launch(context = mainThreadDispatcher, start = CoroutineStart.LAZY, block = block)
- }
-
- inline fun schedule(noinline task: () -> Unit) = schedule(0, task)
-
- 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())
- }
-
- abstract class MainThreadDispatcher : CoroutineDispatcher() {
- abstract val mainThread: Thread
- abstract fun runOnMainThread(task: Runnable)
- }
-
- private inner class MainThreadDispatcherImpl : MainThreadDispatcher() {
- override val mainThread: Thread = Thread.currentThread()
-
- override fun dispatch(context: CoroutineContext, block: Runnable) {
- runOnMainThread(block)
- }
-
- @Suppress("OVERRIDE_BY_INLINE")
- override inline fun runOnMainThread(task: Runnable) {
- if (Thread.currentThread() === mainThread) task.run()
- else plugin.server.scheduler.runTaskLater(plugin, task, 0)
- }
- }
-
-}
diff --git a/src/main/kotlin/io/dico/parcels2/util/MainThreadDispatcher.kt b/src/main/kotlin/io/dico/parcels2/util/MainThreadDispatcher.kt
new file mode 100644
index 0000000..b1d18ab
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/util/MainThreadDispatcher.kt
@@ -0,0 +1,31 @@
+package io.dico.parcels2.util
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Runnable
+import org.bukkit.plugin.Plugin
+import kotlin.coroutines.CoroutineContext
+
+abstract class MainThreadDispatcher : CoroutineDispatcher() {
+ abstract val mainThread: Thread
+ abstract fun runOnMainThread(task: Runnable)
+}
+
+@Suppress("FunctionName")
+fun MainThreadDispatcher(plugin: Plugin): MainThreadDispatcher {
+ return object : MainThreadDispatcher() {
+ override val mainThread: Thread = Thread.currentThread()
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ doDispatch(block)
+ }
+
+ override fun runOnMainThread(task: Runnable) {
+ doDispatch(task)
+ }
+
+ private fun doDispatch(task: Runnable) {
+ if (Thread.currentThread() === mainThread) task.run()
+ else plugin.server.scheduler.runTaskLater(plugin, task, 0)
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/util/PluginScheduler.kt b/src/main/kotlin/io/dico/parcels2/util/PluginScheduler.kt
new file mode 100644
index 0000000..f29ba2b
--- /dev/null
+++ b/src/main/kotlin/io/dico/parcels2/util/PluginScheduler.kt
@@ -0,0 +1,20 @@
+package io.dico.parcels2.util
+
+import org.bukkit.plugin.Plugin
+import org.bukkit.scheduler.BukkitTask
+
+interface PluginScheduler {
+ val plugin: Plugin
+
+ fun schedule(delay: Int, task: () -> Unit): BukkitTask {
+ return plugin.server.scheduler.runTaskLater(plugin, task, delay.toLong())
+ }
+
+ fun scheduleRepeating(delay: Int, interval: Int, task: () -> Unit): BukkitTask {
+ return plugin.server.scheduler.runTaskTimer(plugin, task, delay.toLong(), interval.toLong())
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline fun PluginScheduler.schedule(noinline task: () -> Unit) = schedule(0, task)
+
diff --git a/src/main/kotlin/io/dico/parcels2/util/Region.kt b/src/main/kotlin/io/dico/parcels2/util/Region.kt
index 5717906..f786693 100644
--- a/src/main/kotlin/io/dico/parcels2/util/Region.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/Region.kt
@@ -2,4 +2,12 @@ package io.dico.parcels2.util
data class Region(val origin: Vec3i, val size: Vec3i) {
val blockCount: Int get() = size.x * size.y * size.z
+
+ val center: Vec3d
+ get() {
+ val x = (origin.x + size.x) / 2.0
+ val y = (origin.y + size.y) / 2.0
+ val z = (origin.z + size.z) / 2.0
+ return Vec3d(x, y, z)
+ }
} \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt
index bca2428..1398037 100644
--- a/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/UUIDUtil.kt
@@ -1,25 +1,14 @@
package io.dico.parcels2.util
+import io.dico.parcels2.util.ext.isValid
import org.bukkit.Bukkit
import java.nio.ByteBuffer
import java.util.UUID
-@Suppress("UsePropertyAccessSyntax")
-fun getPlayerNameOrDefault(uuid: UUID?, ifUnknown: String? = null): String {
- return uuid
- ?.let { getPlayerName(it) }
- ?: ifUnknown
- ?: ":unknown_name:"
-}
+const val PLAYER_NAME_PLACEHOLDER = ":unknown_name:"
fun getPlayerName(uuid: UUID): String? {
return Bukkit.getOfflinePlayer(uuid)?.takeIf { it.isValid }?.name
}
-fun UUID.toByteArray(): ByteArray =
- ByteBuffer.allocate(16).apply {
- putLong(mostSignificantBits)
- putLong(leastSignificantBits)
- }.array()
-fun ByteArray.toUUID(): UUID = ByteBuffer.wrap(this).run { UUID(long, long) }
diff --git a/src/main/kotlin/io/dico/parcels2/util/Vec2i.kt b/src/main/kotlin/io/dico/parcels2/util/Vec2i.kt
index 933c18b..62ac97f 100644
--- a/src/main/kotlin/io/dico/parcels2/util/Vec2i.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/Vec2i.kt
@@ -5,3 +5,7 @@ data class Vec2i(
val z: Int
)
+data class Region2i(
+ val bottom: Vec2i,
+ val top: Vec2i
+) \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt
index 6db98af..ded1e0c 100644
--- a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt
@@ -3,6 +3,22 @@ package io.dico.parcels2.util
import org.bukkit.World
import org.bukkit.block.Block
+data class Vec3d(
+ val x: Double,
+ val y: Double,
+ val z: Double
+) {
+ operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z)
+ operator fun minus(o: Vec3i) = Vec3d(x - o.x, y - o.y, z - o.z)
+ infix fun addX(o: Double) = Vec3d(x + o, y, z)
+ infix fun addY(o: Double) = Vec3d(x, y + o, z)
+ infix fun addZ(o: Double) = Vec3d(x, y, z + o)
+ infix fun withX(o: Double) = Vec3d(o, y, z)
+ infix fun withY(o: Double) = Vec3d(x, o, z)
+ infix fun withZ(o: Double) = Vec3d(x, y, o)
+ fun add(ox: Double, oy: Double, oz: Double) = Vec3d(x + ox, y + oy, z + oz)
+}
+
data class Vec3i(
val x: Int,
val y: Int,
@@ -12,6 +28,9 @@ data class Vec3i(
infix fun addX(o: Int) = Vec3i(x + o, y, z)
infix fun addY(o: Int) = Vec3i(x, y + o, z)
infix fun addZ(o: Int) = Vec3i(x, y, z + o)
+ infix fun withX(o: Int) = Vec3i(o, y, z)
+ infix fun withY(o: Int) = Vec3i(x, o, z)
+ infix fun withZ(o: Int) = Vec3i(x, y, o)
fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz)
}
diff --git a/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Material.kt
index a2aefc8..e160e55 100644
--- a/src/main/kotlin/io/dico/parcels2/util/MaterialExtensions.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/ext/Material.kt
@@ -1,4 +1,4 @@
-package io.dico.parcels2.util
+package io.dico.parcels2.util.ext
import org.bukkit.Material
import org.bukkit.Material.*
@@ -74,3 +74,35 @@ val Material.isWoodButton
DARK_OAK_BUTTON -> true
else -> false
}
+
+private fun getMaterialPrefixed(prefix: String, name: String): Material {
+ return Material.getMaterial("${prefix}_$name") ?: throw IllegalArgumentException("Material ${prefix}_$name doesn't exist")
+}
+
+fun getMaterialsWithWoodTypePrefix(name: String) = arrayOf(
+ getMaterialPrefixed("OAK", name),
+ getMaterialPrefixed("BIRCH", name),
+ getMaterialPrefixed("SPRUCE", name),
+ getMaterialPrefixed("JUNGLE", name),
+ getMaterialPrefixed("ACACIA", name),
+ getMaterialPrefixed("DARK_OAK", name)
+)
+
+fun getMaterialsWithWoolColorPrefix(name: String) = arrayOf(
+ getMaterialPrefixed("WHITE", name),
+ getMaterialPrefixed("ORANGE", name),
+ getMaterialPrefixed("MAGENTA", name),
+ getMaterialPrefixed("LIGHT_BLUE", name),
+ getMaterialPrefixed("YELLOW", name),
+ getMaterialPrefixed("LIME", name),
+ getMaterialPrefixed("PINK", name),
+ getMaterialPrefixed("GRAY", name),
+ getMaterialPrefixed("LIGHT_GRAY", name),
+ getMaterialPrefixed("CYAN", name),
+ getMaterialPrefixed("PURPLE", name),
+ getMaterialPrefixed("BLUE", name),
+ getMaterialPrefixed("BROWN", name),
+ getMaterialPrefixed("GREEN", name),
+ getMaterialPrefixed("RED", name),
+ getMaterialPrefixed("BLACK", name)
+) \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/util/NumberExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Math.kt
index 214a797..62ee220 100644
--- a/src/main/kotlin/io/dico/parcels2/util/NumberExtensions.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/ext/Math.kt
@@ -1,4 +1,4 @@
-package io.dico.parcels2.util
+package io.dico.parcels2.util.ext
fun Double.floor(): Int {
val down = toInt()
@@ -29,4 +29,7 @@ fun IntRange.clamp(min: Int, max: Int): IntRange {
return IntRange(first, max)
}
return this
-} \ No newline at end of file
+}
+
+// the name coerceAtMost is bad
+fun Int.clampMax(max: Int) = coerceAtMost(max) \ No newline at end of file
diff --git a/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Misc.kt
index 877d1cc..d7feabf 100644
--- a/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/ext/Misc.kt
@@ -1,4 +1,4 @@
-package io.dico.parcels2.util
+package io.dico.parcels2.util.ext
import io.dico.parcels2.logger
import java.io.File
@@ -22,6 +22,10 @@ inline fun <R> Any.synchronized(block: () -> R): R = synchronized(this, block)
inline fun <T> T?.isNullOr(condition: T.() -> Boolean): Boolean = this == null || condition()
inline fun <T> T?.isPresentAnd(condition: T.() -> Boolean): Boolean = this != null && condition()
+inline fun <T> T?.ifNullRun(block: () -> Unit): T? {
+ if (this == null) block()
+ return this
+}
inline fun <T, U> MutableMap<T, U>.editLoop(block: EditLoopScope<T, U>.(T, U) -> Unit) {
return EditLoopScope(this).doEditLoop(block)
diff --git a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/ext/Player.kt
index 9604365..38402f0 100644
--- a/src/main/kotlin/io/dico/parcels2/util/PlayerExtensions.kt
+++ b/src/main/kotlin/io/dico/parcels2/util/ext/Player.kt
@@ -1,8 +1,7 @@
-package io.dico.parcels2.util
+package io.dico.parcels2.util.ext
import io.dico.dicore.Formatting
import io.dico.parcels2.ParcelsPlugin
-import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.logger
import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player
diff --git a/todo.md b/todo.md
new file mode 100644
index 0000000..93e9c02
--- /dev/null
+++ b/todo.md
@@ -0,0 +1,84 @@
+# Parcels Todo list
+
+Commands
+-
+Basically all admin commands.
+* ~~setowner~~
+* ~~dispose~~
+* ~~reset~~
+* swap
+* New admin commands that I can't think of right now.
+
+Also
+* ~~setbiome~~
+* random
+
+Modify home command:
+* ~~Make `:` not be required if prior component cannot be parsed to an int~~
+* Listen for command events that use plotme-style argument, and transform the command
+
+~~Add permissions to commands (replace or fix `IContextFilter` from command lib
+to allow inheriting permissions properly).~~
+
+Parcel Options
+-
+
+Parcel options apply to any player with `DEFAULT` added status.
+They affect what their permissions might be within the parcel.
+
+Apart from `/p option inputs`, `/p option inventory`, the following might be considered.
+
+Move existing options to "interact" namespace (`/p o interact`)
+
+Then,
+* Split `/p option interact inputs` into a list of interactible block types.
+The list could include container blocks, merging the existing inventory option.
+* Players cannot launch projectiles in locations where they can't build.
+This could become optional.
+* Option to control spreading and/or forming of blocks such as grass and ice within the parcel.
+
+Block Management
+-
+~~Update the parcel corner with owner info when a player flies into the parcel (after migrations).
+Parcels has a player head in that corner in addition to the sign that PlotMe uses.~~
+
+Commands that modify parcel blocks must be kept track of to prevent multiple
+from running simultaneously in the same parcel. `hasBlockVisitors` field must be updated.
+In general, spamming the commands must be caught at all cost to avoid lots of lag.
+
+Swap - schematic is in place, but proper placement order must be enforced to make sure that attachable
+blocks are placed properly. Alternatively, if a block change method can be found that doesn't
+cause block updates, that would be preferred subject to having good performance.
+
+~~Change `RegionTraversal` to allow traversing different parts of a region in a different order.
+This could apply to clearing of plots, for example. It would be better if the bottom 64 (floor height)
+layers are done upwards, and the rest downwards.~~
+
+Events
+-
+Prevent block spreading subject to conditions.
+
+Scan through blocks that were added since original Parcels implementation,
+that might introduce things that need to be checked or listened for.
+
+~~WorldEdit Listener.~~
+
+Limit number of beacons in a parcel and/or avoid potion effects being applied outside the parcel.
+
+Database
+-
+Find and patch ways to add new useless entries (for regular players at least)
+
+Prevent invalid player names from being saved to the database.
+Here, invalid player names mean names that contain invalid characters.
+
+Use an atomic GET OR INSERT query so that parallel execution doesn't cause problems
+(as is currently the case when migrating).
+
+Implement a container that doesn't require loading all parcel data on startup (Complex).
+
+~~Update player profiles in the database on join to account for name changes.~~
+
+Store player status on parcel (allowed, default banned) as a number to allow for future additions to this set of possibilities
+
+