Fix serialization
This commit is contained in:
parent
67f2901c26
commit
4510f2bfe3
@ -15,8 +15,8 @@ repositories {
|
||||
dependencies {
|
||||
compileOnly(spigot("1.17.1"))
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.5.30")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.0")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.0")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
|
||||
}
|
||||
|
||||
tasks.getByName<Test>("test") {
|
||||
@ -77,7 +77,7 @@ spigot {
|
||||
|
||||
create("portals.tp") {
|
||||
description = "Allows teleporting to a portal"
|
||||
defaults = "op"
|
||||
defaults = "true"
|
||||
}
|
||||
|
||||
create("portals.tp.other") {
|
||||
@ -85,6 +85,16 @@ spigot {
|
||||
defaults = "op"
|
||||
}
|
||||
|
||||
create("portals.info") {
|
||||
description = "Get info about a portal"
|
||||
defaults = "true"
|
||||
}
|
||||
|
||||
create("portals.info.other") {
|
||||
description = "Get info about another player's portal"
|
||||
defaults = "op"
|
||||
}
|
||||
|
||||
create("portals.modify.remove") {
|
||||
description = "Allows portal removal"
|
||||
defaults = "true"
|
||||
@ -101,7 +111,7 @@ spigot {
|
||||
}
|
||||
|
||||
create("portals.modify.allow") {
|
||||
description = "Allows another player to use a portal"
|
||||
description = "Allows another player to edit a portal"
|
||||
defaults = "true"
|
||||
}
|
||||
|
||||
@ -131,6 +141,8 @@ spigot {
|
||||
"portals.list.other" to true,
|
||||
"portals.tp" to true,
|
||||
"portals.tp.other" to true,
|
||||
"portals.info" to true,
|
||||
"portals.info.other" to true,
|
||||
"portals.modify.*" to true,
|
||||
"portals.invite" to true,
|
||||
"portals.invite.other" to true
|
||||
|
@ -8,19 +8,23 @@ const val RESULT_ERROR_NOMATCH = "Unknown command"
|
||||
const val RESULT_ERROR_NOPERMS = "You don't have permission to use this command"
|
||||
const val RESULT_ERROR_PLAYER = "Player does not exist"
|
||||
const val RESULT_ERROR_NOTPLAYER = "Command can only be run by players"
|
||||
const val RESULT_ERROR_NOTDECIMAL = "\"%s\" is not a number"
|
||||
const val RESULT_ERROR_NOTINT = "\"%s\" is not an integer"
|
||||
const val RESULT_ERROR_NOTENUM = "Value is not one of following: %s"
|
||||
|
||||
typealias Suggestor = (args: List<*>, sender: CommandSender, current: String) -> List<String>?
|
||||
typealias ArgParser<T> = (parsed: List<*>, current: String, sender: CommandSender) -> NodeParseResult<T>
|
||||
typealias ArgNode<T> = Pair<ArgParser<out T>, Suggestor>
|
||||
|
||||
inline fun <reified T> constantParseNode(value: T, crossinline toStringFunc: T.() -> String = { this.toString() }): ArgNode<T> =
|
||||
inline fun <reified T> constantParseNode(value: T, crossinline toStringFunc: T.() -> String = { this.toString() }): ArgNode<T> = value.toStringFunc().let {
|
||||
{ _: List<*>, current: String, _: CommandSender ->
|
||||
if (current == value.toStringFunc()) NodeParseResult.SuccessResult(value)
|
||||
if (current == it) NodeParseResult.SuccessResult(value)
|
||||
else NodeParseResult.FailResult(RESULT_ERROR_NOMATCH)
|
||||
} to { _, _, current ->
|
||||
if (current.startsWith(value.toStringFunc())) listOf(value.toStringFunc())
|
||||
if (it.startsWith(current)) listOf(it)
|
||||
else null
|
||||
}
|
||||
}
|
||||
|
||||
val PARSE_NODE_STRING: ArgNode<String> = { _: List<*>, current: String, _: CommandSender -> NodeParseResult.SuccessResult(current) } to { _, _, _ -> emptyList() }
|
||||
val PARSE_NODE_PLAYER: ArgNode<OfflinePlayer> =
|
||||
@ -35,6 +39,38 @@ val PARSE_NODE_PLAYER: ArgNode<OfflinePlayer> =
|
||||
.ifEmpty { null }
|
||||
}
|
||||
|
||||
val PARSE_NODE_DECIMAL: ArgNode<Double> =
|
||||
{ _: List<*>, current: String, _: CommandSender ->
|
||||
val result = current.toDoubleOrNull()
|
||||
if (result == null) NodeParseResult.FailResult(RESULT_ERROR_NOTDECIMAL.format(current))
|
||||
else NodeParseResult.SuccessResult(result)
|
||||
} to { _, _, current ->
|
||||
if (current.toDoubleOrNull() == null) null
|
||||
else listOf("0.0", "90.0", "180.0", "270.0", "360.0")
|
||||
}
|
||||
|
||||
val PARSE_NODE_INTEGER: ArgNode<Int> =
|
||||
{ _: List<*>, current: String, _: CommandSender ->
|
||||
val result = current.toIntOrNull()
|
||||
if (result == null) NodeParseResult.FailResult(RESULT_ERROR_NOTINT.format(current))
|
||||
else NodeParseResult.SuccessResult(result)
|
||||
} to { _, _, current ->
|
||||
if (current.toIntOrNull() == null) null
|
||||
else emptyList()
|
||||
}
|
||||
|
||||
inline fun <reified T: Enum<T>> enumParseNode(): ArgNode<T> =
|
||||
{ _: List<*>, current: String, _: CommandSender ->
|
||||
val parsed = T::class.java.enumConstants.firstOrNull { it.name.equals(current, ignoreCase = true) }
|
||||
if (parsed == null) NodeParseResult.FailResult(RESULT_ERROR_NOTENUM.format(T::class.java.enumConstants.joinToString { it.name }))
|
||||
else NodeParseResult.SuccessResult(parsed)
|
||||
} to { _, _, current: String ->
|
||||
T::class.java.enumConstants
|
||||
.filter { it.name.startsWith(current, ignoreCase = true) }
|
||||
.map { it.name }
|
||||
.ifEmpty { null }
|
||||
}
|
||||
|
||||
open class ParseBranch(private vararg val nodes: ArgNode<*>) {
|
||||
open fun getFailReason(sender: CommandSender): String? = null
|
||||
fun isEligible(sender: CommandSender) = getFailReason(sender) == null
|
||||
|
@ -1,18 +1,28 @@
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.math.MathContext
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.Charset
|
||||
import kotlin.math.min
|
||||
import kotlin.reflect.KMutableProperty0
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KProperty0
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
fun ByteBuffer.writePackedRange(value: Double, min: Double, max: Double) {
|
||||
packedULong = ((value - min)/max).toULong()
|
||||
private val threadLocalBuffer = ThreadLocal.withInitial { ByteBuffer.allocate(9) }
|
||||
|
||||
internal val PRECISION_1024 = MathContext(1024)
|
||||
|
||||
fun ByteBuffer.writePackedRange(_value: BigDecimal, min: BigDecimal, max: BigDecimal) {
|
||||
val actualMax = max - min
|
||||
packedULong = _value
|
||||
.subtract(min)
|
||||
.coerceIn(min .. actualMax)
|
||||
.multiply(ULONG_MAX_FLOAT.divide(actualMax, PRECISION_1024))
|
||||
.toBigInteger()
|
||||
.toULong()
|
||||
}
|
||||
|
||||
fun ByteBuffer.writePackedRange(value: Float, min: Float, max: Float) {
|
||||
packedULong = ((value - min)/max).toULong()
|
||||
}
|
||||
fun ByteBuffer.writePackedRange(_value: Double, min: Double, max: Double) = writePackedRange(BigDecimal(_value), BigDecimal(min), BigDecimal(max))
|
||||
fun ByteBuffer.writePackedRange(value: Float, min: Float, max: Float) = writePackedRange(value.toDouble(), min.toDouble(), max.toDouble())
|
||||
|
||||
var ByteBuffer.packedULong: ULong
|
||||
get() = readPacked().first
|
||||
@ -42,24 +52,29 @@ var ByteBuffer.packedChar: Char
|
||||
get() = packedInt.toChar()
|
||||
set(value) { packedInt = value.code }
|
||||
|
||||
fun ByteBuffer.readPackedRangeDouble(min: Double, max: Double) = (packedLong * max) + min
|
||||
fun ByteBuffer.readPackedRangeFloat(min: Float, max: Float) = (packedLong * max) + min
|
||||
fun ByteBuffer.readPackedRangeDouble(min: Double, max: Double): Double {
|
||||
val buffer = threadLocalBuffer.get()
|
||||
return ((BigInteger(buffer.position(0).put(0).putLong(packedULong.toLong()).array(), 0, 9).toBigDecimal(mathContext = MathContext.UNLIMITED) *
|
||||
(BigDecimal(max).divide(BigInteger(buffer.position(1).putLong(-1L).array(), 0, 9).toBigDecimal(mathContext = MathContext.UNLIMITED), PRECISION_1024))) + BigDecimal(min)).toDouble()
|
||||
}
|
||||
|
||||
fun ByteBuffer.readPackedRangeFloat(min: Float, max: Float) = readPackedRangeDouble(min.toDouble(), max.toDouble())
|
||||
|
||||
|
||||
class ReallocatingBuffer(buffer: ByteBuffer, val growthFactor: Float = 1.0f) {
|
||||
// Handles reads/writes
|
||||
private abstract inner class ReallocatingAccessor<T>(
|
||||
private val getter: () -> T,
|
||||
private val setter: (T) -> Unit
|
||||
private val getter: () -> () -> T,
|
||||
private val setter: () -> (T) -> Unit
|
||||
) {
|
||||
protected abstract fun sizeOf(value: T): Int
|
||||
|
||||
constructor(property: KMutableProperty0<T>): this(property::get, property::set)
|
||||
constructor(property: () -> KMutableProperty0<T>): this({ property()::get }, { property()::set })
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>) = getter()
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>) = getter()()
|
||||
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
ensureSize(size)
|
||||
setter(value)
|
||||
ensureSize(sizeOf(value))
|
||||
setter()(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,20 +82,16 @@ class ReallocatingBuffer(buffer: ByteBuffer, val growthFactor: Float = 1.0f) {
|
||||
getter: () -> T,
|
||||
setter: (T) -> Unit,
|
||||
private val size: Int
|
||||
): ReallocatingAccessor<T>(getter, setter) {
|
||||
constructor(property: KMutableProperty0<T>, size: Int): this(property::get, property::set, size)
|
||||
|
||||
): ReallocatingAccessor<T>({ getter }, { setter }) {
|
||||
override fun sizeOf(value: T) = size
|
||||
}
|
||||
|
||||
private inner class VarIntReallocatingAccessor<T>(
|
||||
getter: () -> T,
|
||||
setter: (T) -> Unit,
|
||||
getter: () -> () -> T,
|
||||
setter: () -> (T) -> Unit,
|
||||
private val sizeGetter: T.() -> Int
|
||||
): ReallocatingAccessor<T>(getter, setter) {
|
||||
constructor(getter: () -> T, setter: (T) -> Unit, sizeGetter: KProperty1<T, Int>): this(getter, setter, sizeGetter::get)
|
||||
constructor(property: KMutableProperty0<T>, sizeGetter: T.() -> Int): this(property::get, property::set, sizeGetter)
|
||||
constructor(property: KMutableProperty0<T>, sizeGetter: KProperty1<T, Int>): this(property::get, property::set, sizeGetter::get)
|
||||
constructor(property: () -> KMutableProperty0<T>, sizeGetter: T.() -> Int): this({ property()::get }, { property()::set }, sizeGetter)
|
||||
|
||||
override fun sizeOf(value: T) = sizeGetter(value)
|
||||
}
|
||||
@ -88,24 +99,24 @@ class ReallocatingBuffer(buffer: ByteBuffer, val growthFactor: Float = 1.0f) {
|
||||
var buffer = buffer
|
||||
private set
|
||||
|
||||
var byte: Byte by StaticReallocatingAccessor(buffer::get, buffer::put, 1)
|
||||
var char: Char by StaticReallocatingAccessor(buffer::getChar, buffer::putChar, 2)
|
||||
var short: Short by StaticReallocatingAccessor(buffer::getShort, buffer::putShort, 2)
|
||||
var int: Int by StaticReallocatingAccessor(buffer::getInt, buffer::putInt, 4)
|
||||
var long: Long by StaticReallocatingAccessor(buffer::getLong, buffer::putLong, 8)
|
||||
var uShort: UShort by StaticReallocatingAccessor({ buffer.short.toUShort() }, { buffer.putShort(it.toShort()) }, 2)
|
||||
var uInt: UInt by StaticReallocatingAccessor({ buffer.int.toUInt() }, { buffer.putInt(it.toInt()) }, 4)
|
||||
var uLong: ULong by StaticReallocatingAccessor({ buffer.long.toULong() }, { buffer.putLong(it.toLong()) }, 8)
|
||||
var float: Float by StaticReallocatingAccessor(buffer::getFloat, buffer::putFloat, 4)
|
||||
var double: Double by StaticReallocatingAccessor(buffer::getDouble, buffer::putDouble, 4)
|
||||
var byte: Byte by StaticReallocatingAccessor({ this.buffer.get() }, { this.buffer.put(it) }, 1)
|
||||
var char: Char by StaticReallocatingAccessor({ this.buffer.char }, { this.buffer.putChar(it) }, 2)
|
||||
var short: Short by StaticReallocatingAccessor({ this.buffer.short }, { this.buffer.putShort(it) }, 2)
|
||||
var int: Int by StaticReallocatingAccessor({ this.buffer.int }, { this.buffer.putInt(it) }, 4)
|
||||
var long: Long by StaticReallocatingAccessor({ this.buffer.long }, { this.buffer.putLong(it) }, 8)
|
||||
var uShort: UShort by StaticReallocatingAccessor({ this.buffer.short.toUShort() }, { this.buffer.putShort(it.toShort()) }, 2)
|
||||
var uInt: UInt by StaticReallocatingAccessor({ this.buffer.int.toUInt() }, { this.buffer.putInt(it.toInt()) }, 4)
|
||||
var uLong: ULong by StaticReallocatingAccessor({ this.buffer.long.toULong() }, { this.buffer.putLong(it.toLong()) }, 8)
|
||||
var float: Float by StaticReallocatingAccessor({ this.buffer.float }, { this.buffer.putFloat(it) }, 4)
|
||||
var double: Double by StaticReallocatingAccessor({ this.buffer.double }, { this.buffer.putDouble(it) }, 8)
|
||||
|
||||
var packedChar: Char by VarIntReallocatingAccessor(buffer::packedChar, Char::varIntSize)
|
||||
var packedShort: Short by VarIntReallocatingAccessor(buffer::packedShort, Short::varIntSize)
|
||||
var packedInt: Int by VarIntReallocatingAccessor(buffer::packedInt, Int::varIntSize)
|
||||
var packedLong: Long by VarIntReallocatingAccessor(buffer::packedLong, Long::varIntSize)
|
||||
var packedUShort: UShort by VarIntReallocatingAccessor(buffer::packedUShort, UShort::varIntSize)
|
||||
var packedUInt: UInt by VarIntReallocatingAccessor(buffer::packedUInt, UInt::varIntSize)
|
||||
var packedULong: ULong by VarIntReallocatingAccessor(buffer::packedULong, ULong::varIntSize)
|
||||
var packedChar: Char by VarIntReallocatingAccessor({ this.buffer::packedChar }, Char::varIntSize)
|
||||
var packedShort: Short by VarIntReallocatingAccessor({ this.buffer::packedShort }, Short::varIntSize)
|
||||
var packedInt: Int by VarIntReallocatingAccessor({ this.buffer::packedInt }, Int::varIntSize)
|
||||
var packedLong: Long by VarIntReallocatingAccessor({ this.buffer::packedLong }, Long::varIntSize)
|
||||
var packedUShort: UShort by VarIntReallocatingAccessor({ this.buffer::packedUShort }, UShort::varIntSize)
|
||||
var packedUInt: UInt by VarIntReallocatingAccessor({ this.buffer::packedUInt }, UInt::varIntSize)
|
||||
var packedULong: ULong by VarIntReallocatingAccessor({ this.buffer::packedULong }, ULong::varIntSize)
|
||||
|
||||
var position: Int
|
||||
get() = buffer.position()
|
||||
@ -119,7 +130,7 @@ class ReallocatingBuffer(buffer: ByteBuffer, val growthFactor: Float = 1.0f) {
|
||||
val newBuffer = if(buffer.isDirect) ByteBuffer.allocateDirect(value) else ByteBuffer.allocate(value)
|
||||
|
||||
position = 0
|
||||
newBuffer.put(buffer)
|
||||
newBuffer.put(0, buffer, 0, min(oldPosition, value))
|
||||
position = min(oldPosition, value)
|
||||
|
||||
buffer = newBuffer
|
||||
@ -142,7 +153,7 @@ class ReallocatingBuffer(buffer: ByteBuffer, val growthFactor: Float = 1.0f) {
|
||||
buffer.writePackedRange(value, min, max)
|
||||
}
|
||||
|
||||
fun getPackedFloat(min: Float, max: Float) = buffer.readPackedRangeFloat(min, max)
|
||||
fun getPackedFloat(min: Float, max: Float) = buffer.readPackedRangeFloat(min, max).toFloat()
|
||||
fun getPackedDouble(min: Double, max: Double) = buffer.readPackedRangeDouble(min, max)
|
||||
|
||||
fun putByteArrayDirect(array: ByteArray, off: Int = 0, len: Int = array.size) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.OfflinePlayer
|
||||
import org.bukkit.World
|
||||
import java.util.*
|
||||
import kotlin.Comparator
|
||||
|
||||
@ -12,17 +13,11 @@ val COMPARATOR_COOLDOWN_PLAYER = Comparator<Cooldown> { a, b -> a.first.uniqueId
|
||||
val COMPARATOR_COOLDOWN_EXPIRY = Comparator<Cooldown> { a, b -> a.second.compareTo(b.second) }
|
||||
|
||||
val OfflinePlayer.COMPARISON_COOLDOWN: Comparison<Cooldown>
|
||||
get() = { uniqueId.compareTo(it.first.uniqueId) }
|
||||
|
||||
val Long.COMPARISON_COOLDOWN: Comparison<Cooldown>
|
||||
get() = { compareTo(it.second) }
|
||||
|
||||
val COMPARATOR_PLAYER = Comparator<OfflinePlayer> { a, b -> a.uniqueId.compareTo(b.uniqueId) }
|
||||
val COMPARATOR_LOCATION = Comparator<Location> { a, b -> a.compareByOrder(b, { world!!.uid }, Location::getBlockX, Location::getBlockY, Location::getBlockZ) }
|
||||
get() = { it.first.uniqueId.compareTo(uniqueId) }
|
||||
|
||||
val COMPARATOR_UUID = Comparator<UUID?> { a, b ->
|
||||
val aUnlinked = a == null
|
||||
val bUnlinked = a == null
|
||||
val bUnlinked = b == null
|
||||
|
||||
if (aUnlinked || bUnlinked) {
|
||||
return@Comparator if (aUnlinked == bUnlinked) 0 else if (aUnlinked) -1 else 1
|
||||
@ -32,29 +27,25 @@ val COMPARATOR_UUID = Comparator<UUID?> { a, b ->
|
||||
}
|
||||
|
||||
// An owner cannot place two portals on the same block, implying that this comparator defines a partial order
|
||||
val COMPARATOR_PORTAL_LOCATION_OWNER = Comparator<Portal> { a, b -> a.compareByOrder(b, { world.uid }, Portal::x, Portal::y, Portal::z, { owner.uniqueId }) }
|
||||
val COMPARATOR_PORTAL_LOCATION = Comparator<Portal> { a, b -> a.compareByOrder(b, { world.uid }, Portal::x, Portal::y, Portal::z) }
|
||||
val COMPARATOR_PORTAL_OWNER_NAME = Comparator<Portal> { a, b -> a.compareByOrder(b, { owner.uniqueId }, Portal::name) }
|
||||
val COMPARATOR_PORTAL_LOCATION_OWNER = Comparator<Portal> { a, b -> a.compareByOrder(b, Portal::worldIndex, Portal::x, Portal::y, Portal::z, Portal::ownerIndex) }
|
||||
val COMPARATOR_PORTAL_LOCATION = Comparator<Portal> { a, b -> a.compareByOrder(b, Portal::worldIndex, Portal::x, Portal::y, Portal::z) }
|
||||
val COMPARATOR_PORTAL_OWNER_NAME = Comparator<Portal> { a, b -> a.compareByOrder(b, Portal::ownerIndex, Portal::name) }
|
||||
val COMPARATOR_PORTAL_LINKS = Comparator<Portal> { a, b -> COMPARATOR_UUID.compare(a.link, b.link) }
|
||||
|
||||
val Location.COMPARISON_PORTAL: Comparison<Portal>
|
||||
get() = {
|
||||
compareValues(
|
||||
world!!::getUID to it.world::getUID,
|
||||
this::getBlockX to it::x,
|
||||
this::getBlockY to it::y,
|
||||
this::getBlockZ to it::z
|
||||
)
|
||||
}
|
||||
|
||||
val OfflinePlayer.COMPARISON_PORTAL: Comparison<Portal>
|
||||
get() = { uniqueId.compareTo(it.owner.uniqueId) }
|
||||
fun Location.portalComparison(fromWorldMapper: MapFunction<World, UInt>): Comparison<Portal> = {
|
||||
compareValues(
|
||||
it::worldIndex to { fromWorldMapper(world!!) },
|
||||
it::x to this::getBlockX,
|
||||
it::y to this::getBlockY,
|
||||
it::z to this::getBlockZ,
|
||||
)
|
||||
}
|
||||
|
||||
val UUID.COMPARISON_PORTAL_ID: Comparison<Portal>
|
||||
get() = { compareTo(it.id) }
|
||||
get() = { it.id.compareTo(this) }
|
||||
|
||||
val Portal.COMPARISON_PORTAL_LINKEDTO: Comparison<Portal>
|
||||
get() = { COMPARATOR_UUID.compare(id, it.link) }
|
||||
get() = { COMPARATOR_UUID.compare(it.link, id) }
|
||||
|
||||
// IDs are unique, so this comparator inherently defines a partial order
|
||||
val COMPARATOR_PORTAL_UID = Comparator<Portal> { a, b -> a.id.compareTo(b.id) }
|
||||
@ -68,7 +59,7 @@ val COMPARATOR_INVITE_PORTAL = Comparator<Invite> { a, b ->
|
||||
}
|
||||
|
||||
val OfflinePlayer.COMPARISON_INVITE: Comparison<Invite>
|
||||
get() = { uniqueId.compareTo(it.recipient.uniqueId) }
|
||||
get() = { it.recipient.uniqueId.compareTo(uniqueId) }
|
||||
|
||||
val Portal.COMPARISON_INVITE: Comparison<Invite>
|
||||
get() = { id.compareTo(it.portalID) }
|
||||
get() = { it.portalID.compareTo(id) }
|
@ -5,7 +5,7 @@ import java.util.*
|
||||
|
||||
private val threadLocalBuffer = ThreadLocal.withInitial { ByteBuffer.allocate(32) }
|
||||
|
||||
data class Invite(val recipient: OfflinePlayer, val portalID: UUID) {
|
||||
class Invite(val recipient: OfflinePlayer, val portalID: UUID) {
|
||||
private constructor(data: Pair<OfflinePlayer, UUID>): this(data.first, data.second)
|
||||
constructor(data: String): this(parseData(data))
|
||||
constructor(recipient: OfflinePlayer, portal: Portal): this(recipient, portal.id)
|
||||
|
@ -28,7 +28,7 @@ class MultiSortedList<E> constructor(
|
||||
|
||||
private var extraLists = extraComparators.associateWith {
|
||||
val list = generator()
|
||||
Collections.copy(list, underlying)
|
||||
list.addAll(underlying)
|
||||
SortedList(list, it)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,12 @@
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.math.MathContext
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
internal val ULONG_MAX_INTEGER = BigInteger(byteArrayOf(0, 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte()))
|
||||
internal val ULONG_MAX_FLOAT = ULONG_MAX_INTEGER.toBigDecimal(mathContext = MathContext.UNLIMITED)
|
||||
|
||||
private val threadLocalBuffer = ThreadLocal.withInitial { ByteBuffer.allocateDirect(9) }
|
||||
|
||||
private fun ByteBuffer.putUByte(value: ULong) = put((value and 0xFFUL).toByte())
|
||||
@ -87,12 +93,12 @@ val ULong.varIntSize
|
||||
get() =
|
||||
if (this <= 240UL) 1
|
||||
else if(this <= 2287UL) 2
|
||||
else if(this <= 67823UL) 2
|
||||
else if(this <= 16777215UL) 2
|
||||
else if(this <= 4294967295UL) 2
|
||||
else if(this <= 1099511627775UL) 2
|
||||
else if(this <= 281474976710655UL) 2
|
||||
else if(this <= 72057594037927935UL) 2
|
||||
else if(this <= 67823UL) 3
|
||||
else if(this <= 16777215UL) 4
|
||||
else if(this <= 4294967295UL) 5
|
||||
else if(this <= 1099511627775UL) 6
|
||||
else if(this <= 281474976710655UL) 7
|
||||
else if(this <= 72057594037927935UL) 8
|
||||
else 9
|
||||
|
||||
val UInt.varIntSize get() = toULong().varIntSize
|
||||
@ -103,9 +109,26 @@ val Int.varIntSize get() = toLong().interlace().varIntSize
|
||||
val Short.varIntSize get() = toLong().interlace().varIntSize
|
||||
val Char.varIntSize get() = code.varIntSize
|
||||
|
||||
fun Float.varIntSize(min: Float, max: Float) = ((this - min)/max).toULong().varIntSize
|
||||
fun Double.varIntSize(min: Double, max: Double) = ((this - min)/max).toULong().varIntSize
|
||||
fun Float.varIntSize(min: Float, max: Float) = toDouble().varIntSize(min.toDouble(), max.toDouble())
|
||||
fun Double.varIntSize(min: Double, max: Double) = BigDecimal(this).varIntSize(BigDecimal(min), BigDecimal(max))
|
||||
fun BigDecimal.varIntSize(min: BigDecimal, max: BigDecimal) =
|
||||
this.subtract(min)
|
||||
.coerceIn(min .. (max - min))
|
||||
.multiply(ULONG_MAX_FLOAT.divide((max - min), PRECISION_1024))
|
||||
.toBigInteger()
|
||||
.toULong()
|
||||
.varIntSize
|
||||
|
||||
fun BigInteger.toULong(): ULong {
|
||||
val array = toByteArray()
|
||||
return threadLocalBuffer
|
||||
.get()
|
||||
.put(0, 0)
|
||||
.putLong(1, 0)
|
||||
.put(0, array, 0, array.size.coerceAtMost(9))
|
||||
.getLong(kotlin.math.max(array.size - 8, 0))
|
||||
.toULong()
|
||||
}
|
||||
|
||||
operator fun UUID.plus(value: ULong): UUID {
|
||||
val lsb = leastSignificantBits.toULong() + value
|
||||
|
@ -12,10 +12,10 @@ import kotlin.experimental.or
|
||||
private val threadLocalInputBuffer = ThreadLocal.withInitial { ReallocatingBuffer(ByteBuffer.allocate(96)) }
|
||||
private val threadLocalOutputBuffer = ThreadLocal.withInitial { ReallocatingBuffer(ByteBuffer.allocate(96)) }
|
||||
|
||||
enum class PortalResult {
|
||||
NO_LINK,
|
||||
DISALLOWED,
|
||||
SUCCESS
|
||||
sealed class PortalResult {
|
||||
object NO_LINK: PortalResult()
|
||||
object DISALLOWED: PortalResult()
|
||||
data class SUCCESS(val link: Portal): PortalResult()
|
||||
}
|
||||
|
||||
enum class PortalFlag {
|
||||
@ -60,10 +60,10 @@ fun readFlags(flagMap: Byte): List<PortalFlag> {
|
||||
}
|
||||
|
||||
|
||||
class Portal(
|
||||
class Portal private constructor(
|
||||
val id: UUID,
|
||||
val owner: OfflinePlayer,
|
||||
val world: World,
|
||||
val ownerIndex: UInt,
|
||||
val worldIndex: UInt,
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
val z: Int,
|
||||
@ -72,13 +72,39 @@ class Portal(
|
||||
private var flags: Byte,
|
||||
link: UUID?,
|
||||
var name: String,
|
||||
val accessExclusions: SortedList<OfflinePlayer>
|
||||
private val _accessExclusions: SortedList<UInt>,
|
||||
private val toPlayerMapper: MapFunction<UInt, OfflinePlayer?>,
|
||||
private val fromPlayerMapper: MapFunction<OfflinePlayer, UInt>,
|
||||
private val toWorldMapper: MapFunction<UInt, World?>,
|
||||
private val fromWorldMapper: MapFunction<World, UInt>
|
||||
) {
|
||||
constructor(
|
||||
toPlayerMapper: MapFunction<UInt, OfflinePlayer?>,
|
||||
fromPlayerMapper: MapFunction<OfflinePlayer, UInt>,
|
||||
toWorldMapper: MapFunction<UInt, World?>,
|
||||
fromWorldMapper: MapFunction<World, UInt>,
|
||||
id: UUID,
|
||||
owner: OfflinePlayer,
|
||||
world: World,
|
||||
x: Int,
|
||||
y: Int,
|
||||
z: Int,
|
||||
yaw: Float,
|
||||
pitch: Float,
|
||||
name: String,
|
||||
link: Portal? = null
|
||||
): this(
|
||||
id, fromPlayerMapper(owner),
|
||||
fromWorldMapper(world), x, y, z, yaw, pitch,
|
||||
0, link?.id, name, SortedList(comparator = UInt::compareTo),
|
||||
toPlayerMapper, fromPlayerMapper, toWorldMapper, fromWorldMapper
|
||||
)
|
||||
|
||||
init {
|
||||
flags = applyFlags(
|
||||
flags,
|
||||
mapOf(
|
||||
PortalFlag.NO_EXCLUSIONS to accessExclusions.isEmpty(),
|
||||
PortalFlag.NO_EXCLUSIONS to _accessExclusions.isEmpty(),
|
||||
PortalFlag.LINKED to (link != null)
|
||||
)
|
||||
)
|
||||
@ -87,7 +113,7 @@ class Portal(
|
||||
var public: Boolean
|
||||
get() = PortalFlag.PUBLIC.isFlagSet(flags)
|
||||
set(value) {
|
||||
accessExclusions.clear()
|
||||
_accessExclusions.clear()
|
||||
flags = PortalFlag.PUBLIC.setFlagValue(flags, value)
|
||||
}
|
||||
|
||||
@ -97,27 +123,50 @@ class Portal(
|
||||
field = value
|
||||
}
|
||||
|
||||
internal val location: Location
|
||||
get() = Location(world, x.toDouble(), y.toDouble(), z.toDouble(), yaw, pitch)
|
||||
fun getAccessExclusionsSize() = _accessExclusions.size
|
||||
fun getAccessExclusion(index: Int) = toPlayerMapper(_accessExclusions[index])
|
||||
fun addAccessExclusion(player: OfflinePlayer) = _accessExclusions.add(fromPlayerMapper(player))
|
||||
fun removeAccessExclusion(player: OfflinePlayer) = _accessExclusions.remove(fromPlayerMapper(player))
|
||||
fun containsAccessExclusion(player: OfflinePlayer) = fromPlayerMapper(player) in _accessExclusions
|
||||
|
||||
fun canEnter(player: OfflinePlayer) =
|
||||
player.uniqueId == owner.uniqueId || (accessExclusions.contains(player) != public)
|
||||
val owner: OfflinePlayer
|
||||
get() = toPlayerMapper(ownerIndex)!!
|
||||
|
||||
fun enterPortal(player: Player, portalMapper: MapFunction<UUID, Portal?>): PortalResult {
|
||||
val remoteLink = link
|
||||
val world: World
|
||||
get() = toWorldMapper(worldIndex)!!
|
||||
|
||||
return if (remoteLink == null) PortalResult.NO_LINK
|
||||
val blockLocation: Location
|
||||
get() = Location(toWorldMapper(worldIndex), x.toDouble(), y.toDouble(), z.toDouble(), yaw, pitch)
|
||||
|
||||
val portalLocation: Location
|
||||
get() = Location(toWorldMapper(worldIndex), x.toDouble() + 0.5, y.toDouble(), z.toDouble() + 0.5, yaw, pitch)
|
||||
|
||||
private fun canEnter(player: OfflinePlayer) =
|
||||
player.uniqueId == toPlayerMapper(ownerIndex)!!.uniqueId || (_accessExclusions.contains(fromPlayerMapper(player)) != public)
|
||||
|
||||
fun checkEnter(player: OfflinePlayer, portalMapper: MapFunction<UUID, Portal?>) =
|
||||
if (link == null) PortalResult.NO_LINK
|
||||
else if (!canEnter(player)) PortalResult.DISALLOWED
|
||||
else {
|
||||
val portal = portalMapper(remoteLink)
|
||||
if (portal == null) {
|
||||
link = null
|
||||
return PortalResult.NO_LINK
|
||||
}
|
||||
|
||||
player.teleport(portal.location)
|
||||
PortalResult.SUCCESS
|
||||
val lnk = getPortalLink(portalMapper)
|
||||
if (lnk == null) PortalResult.NO_LINK
|
||||
else PortalResult.SUCCESS(lnk)
|
||||
}
|
||||
|
||||
fun enterPortal(player: Player, portalMapper: MapFunction<UUID, Portal?>): PortalResult {
|
||||
return if (link == null) PortalResult.NO_LINK
|
||||
else if (!canEnter(player)) PortalResult.DISALLOWED
|
||||
else {
|
||||
val portal = getPortalLink(portalMapper) ?: return PortalResult.NO_LINK
|
||||
|
||||
portal.teleportPlayerTo(player)
|
||||
|
||||
PortalResult.SUCCESS(portal)
|
||||
}
|
||||
}
|
||||
|
||||
fun teleportPlayerTo(player: Player) {
|
||||
player.teleport(portalLocation)
|
||||
}
|
||||
|
||||
fun unlink() {
|
||||
@ -130,32 +179,39 @@ class Portal(
|
||||
return true
|
||||
}
|
||||
|
||||
fun toCompressedString(worldMapper: MapFunction<World, UInt>, playerMapper: MapFunction<OfflinePlayer, UInt>): String {
|
||||
fun getPortalLink(portalMapper: MapFunction<UUID, Portal?>): Portal? {
|
||||
val portal = portalMapper(this.link ?: return null)
|
||||
|
||||
if (portal == null) unlink()
|
||||
|
||||
return portal
|
||||
}
|
||||
|
||||
fun toCompressedString(): String {
|
||||
val buffer = threadLocalInputBuffer.get()
|
||||
buffer.position = 0
|
||||
|
||||
buffer.long = id.mostSignificantBits
|
||||
buffer.long = id.leastSignificantBits
|
||||
buffer.packedUInt = playerMapper(owner)
|
||||
buffer.packedUInt = worldMapper(world)
|
||||
// IDs are sequential, starting at 0, so packing the value is worth it
|
||||
buffer.packedULong = id.mostSignificantBits.toULong()
|
||||
buffer.packedULong = id.leastSignificantBits.toULong()
|
||||
buffer.packedUInt = ownerIndex
|
||||
buffer.packedUInt = worldIndex
|
||||
buffer.packedInt = x
|
||||
buffer.packedInt = y
|
||||
buffer.packedInt = z
|
||||
buffer.putPackedFloat(yaw, 0f, 360f)
|
||||
buffer.putPackedFloat(pitch, 0f, 360f)
|
||||
buffer.putPackedFloat(yaw.mod(360f), 0f, 360f)
|
||||
buffer.putPackedFloat((pitch + 90f).mod(180f) - 90f, -90f, 90f)
|
||||
buffer.byte = flags
|
||||
if (PortalFlag.LINKED.isFlagSet(flags)) {
|
||||
val link = link!!
|
||||
buffer.long = link.mostSignificantBits
|
||||
buffer.long = link.leastSignificantBits
|
||||
buffer.packedULong = link.mostSignificantBits.toULong()
|
||||
buffer.packedULong = link.leastSignificantBits.toULong()
|
||||
}
|
||||
|
||||
buffer.putString(name)
|
||||
|
||||
if (accessExclusions.size > 0) {
|
||||
for (player in accessExclusions)
|
||||
buffer.packedUInt = playerMapper(player)
|
||||
}
|
||||
for (player in _accessExclusions)
|
||||
buffer.packedUInt = player
|
||||
|
||||
val outputBuffer = threadLocalOutputBuffer.get()
|
||||
outputBuffer.position = 0
|
||||
@ -166,45 +222,53 @@ class Portal(
|
||||
|
||||
return buffer.position.toString(16).padStart(8, '0') + String(outputBuffer.buffer.array(), 0, len)
|
||||
}
|
||||
}
|
||||
|
||||
fun readCompressedPortal(
|
||||
data: String,
|
||||
worldMapper: MapFunction<UInt, World?>,
|
||||
playerMapper: MapFunction<UInt, OfflinePlayer?>
|
||||
): Portal? {
|
||||
val inputBuffer = threadLocalInputBuffer.get()
|
||||
inputBuffer.position = 0
|
||||
companion object {
|
||||
fun readCompressedPortal(
|
||||
data: String,
|
||||
toPlayerMapper: MapFunction<UInt, OfflinePlayer?>,
|
||||
fromPlayerMapper: MapFunction<OfflinePlayer, UInt>,
|
||||
toWorldMapper: MapFunction<UInt, World?>,
|
||||
fromWorldMapper: MapFunction<World, UInt>
|
||||
): Portal {
|
||||
val inputBuffer = threadLocalInputBuffer.get()
|
||||
|
||||
val dataLen = data.substring(0 until 8).toInt(16)
|
||||
val dataLen = data.substring(0 until 8).toInt(16)
|
||||
|
||||
inputBuffer.ensureAtLeast(dataLen)
|
||||
inputBuffer.ensureAtLeast(dataLen)
|
||||
|
||||
Base64.getDecoder().decode(data.substring(8).toByteArray(Charsets.ISO_8859_1), inputBuffer.buffer.array())
|
||||
Base64.getDecoder().decode(data.substring(8).toByteArray(Charsets.ISO_8859_1), inputBuffer.buffer.array())
|
||||
inputBuffer.position = 0
|
||||
|
||||
val flags: Byte
|
||||
return Portal(
|
||||
UUID(inputBuffer.long, inputBuffer.long),
|
||||
playerMapper(inputBuffer.packedUInt) ?: return null,
|
||||
worldMapper(inputBuffer.packedUInt) ?: return null,
|
||||
inputBuffer.packedInt,
|
||||
inputBuffer.packedInt,
|
||||
inputBuffer.packedInt,
|
||||
inputBuffer.getPackedFloat(0f, 360f),
|
||||
inputBuffer.getPackedFloat(0f, 360f),
|
||||
run {
|
||||
flags = inputBuffer.byte
|
||||
return@run flags
|
||||
},
|
||||
if (PortalFlag.LINKED.isFlagSet(flags)) UUID(inputBuffer.long, inputBuffer.long)
|
||||
else null,
|
||||
inputBuffer.getString(),
|
||||
if (PortalFlag.NO_EXCLUSIONS.isFlagSet(flags)) SortedList(comparator = COMPARATOR_PLAYER)
|
||||
else run {
|
||||
val collect = SortedList(comparator = COMPARATOR_PLAYER)
|
||||
while (inputBuffer.position < dataLen)
|
||||
collect += playerMapper(inputBuffer.packedUInt) ?: continue
|
||||
return@run collect
|
||||
val flags: Byte
|
||||
return Portal(
|
||||
UUID(inputBuffer.packedULong.toLong(), inputBuffer.packedULong.toLong()),
|
||||
inputBuffer.packedUInt,
|
||||
inputBuffer.packedUInt,
|
||||
inputBuffer.packedInt,
|
||||
inputBuffer.packedInt,
|
||||
inputBuffer.packedInt,
|
||||
inputBuffer.getPackedFloat(0f, 360f),
|
||||
inputBuffer.getPackedFloat(0f, 360f),
|
||||
run {
|
||||
flags = inputBuffer.byte
|
||||
return@run flags
|
||||
},
|
||||
if (PortalFlag.LINKED.isFlagSet(flags)) UUID(inputBuffer.packedULong.toLong(), inputBuffer.packedULong.toLong())
|
||||
else null,
|
||||
inputBuffer.getString(),
|
||||
if (PortalFlag.NO_EXCLUSIONS.isFlagSet(flags)) SortedList(comparator = UInt::compareTo)
|
||||
else run {
|
||||
val collect = SortedList(comparator = UInt::compareTo)
|
||||
while (inputBuffer.position < dataLen)
|
||||
collect += inputBuffer.packedUInt
|
||||
return@run collect
|
||||
},
|
||||
toPlayerMapper,
|
||||
fromPlayerMapper,
|
||||
toWorldMapper,
|
||||
fromWorldMapper
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -30,11 +30,18 @@ private const val RESULT_SUCCESS_INVITEOTHER = "Invited %s to portal \"%s\", own
|
||||
private const val RESULT_SUCCESS_CANCEL = "Cancelled invitation of %s to portal \"%s\""
|
||||
private const val RESULT_SUCCESS_ACCEPT = "Accepted invite from %s to portal \"%s\""
|
||||
private const val RESULT_SUCCESS_DECLINE = "Declined invite from %s to portal \"%s\""
|
||||
private const val RESULT_SUCCESS_TP = "Teleported to portal \"%s\""
|
||||
private const val RESULT_SUCCESS_TPO = "Teleported to portal \"%s\", owned by %s"
|
||||
private const val RESULT_SUCCESS_EDIT_YAW = "Set yaw to %f degrees"
|
||||
private const val RESULT_SUCCESS_EDIT_PITCH = "Set pitch to %f degrees"
|
||||
|
||||
private const val RESULT_INFO_LIST = "List of portals owned by %s:"
|
||||
private const val RESULT_INFO_PORTAL = "Portal \"%s\" (%s; %d, %d, %d; %f, %f) (%s)"
|
||||
private const val RESULT_INFO_PORTAL_LINKED = "Linked to \"%s\""
|
||||
private const val RESULT_INFO_PORTAL_UNLINKED = "Un-linked"
|
||||
|
||||
|
||||
private val OfflinePlayer.playerName: String
|
||||
val OfflinePlayer.playerName: String
|
||||
get() = name ?: "<Name Missing>"
|
||||
|
||||
|
||||
@ -45,15 +52,20 @@ class PortalCommand(
|
||||
permissionRemoveOther: Permission,
|
||||
permissionInvite: Permission,
|
||||
permissionInviteOther: Permission,
|
||||
permissionListOther: Permission
|
||||
permissionListOther: Permission,
|
||||
permissionTp: Permission,
|
||||
permissionTpOther: Permission,
|
||||
permissionInfo: Permission,
|
||||
permissionInfoOther: Permission,
|
||||
permissionEdit: Permission
|
||||
): CommandExecutor, TabCompleter {
|
||||
// Arg parse node for targeting a portal owned by the sender
|
||||
private val senderPortalParseNode: ArgNode<Portal> =
|
||||
{ parsed: List<*>, current: String, sender: CommandSender ->
|
||||
{ _: List<*>, current: String, sender: CommandSender ->
|
||||
val portal = portalManager.getPortal(sender as OfflinePlayer, current)
|
||||
if (portal == null) NodeParseResult.FailResult(RESULT_ERROR_NOPORTAL)
|
||||
else NodeParseResult.SuccessResult(portal)
|
||||
} to { parsed: List<*>, sender: CommandSender, current: String ->
|
||||
} to { _: List<*>, sender: CommandSender, current: String ->
|
||||
portalManager.getPortalsByPartialName(sender as OfflinePlayer, current)?.mapTo(ArrayList(), Portal::name)
|
||||
}
|
||||
|
||||
@ -105,35 +117,53 @@ class PortalCommand(
|
||||
private val portalParse = ParseTree()
|
||||
.branch(PermissionParseBranch(permissionCreate, false, constantParseNode("create"), PARSE_NODE_STRING, senderPortalParseNode)) // portals create [name] [linkName]
|
||||
.branch(PermissionParseBranch(permissionCreate, false, constantParseNode("create"), PARSE_NODE_STRING)) // portals create [name]
|
||||
.branch(PermissionParseBranch(permissionRemove, false, constantParseNode("remove"), PARSE_NODE_STRING)) // portals remove [name]
|
||||
.branch(PermissionParseBranch(permissionRemove, false, constantParseNode("remove"), senderPortalParseNode)) // portals remove [name]
|
||||
.branch(PermissionParseBranch(permissionRemoveOther, constantParseNode("remove"), PARSE_NODE_PLAYER, otherPortalParseNode)) // portals remove [player] [name]
|
||||
.branch(PlayerParseBranch(constantParseNode("uninvite"), senderPortalParseNode, PARSE_NODE_PLAYER)) // portals uninvite [name] [player]
|
||||
.branch(PermissionParseBranch(permissionInviteOther, false, constantParseNode("uninvite"), PARSE_NODE_PLAYER, otherPortalParseNode, PARSE_NODE_PLAYER)) // portals uninvite [owner] [name] [player]
|
||||
.branch(constantParseNode("link"), senderPortalParseNode, senderPortalParseNode) // portals link [name] [linkName]
|
||||
.branch(constantParseNode("unlink"), senderPortalParseNode) // portals unlink [name]
|
||||
.branch(constantParseNode("list")) // portals list
|
||||
.branch(PlayerParseBranch(constantParseNode("link"), senderPortalParseNode, senderPortalParseNode)) // portals link [name] [linkName]
|
||||
.branch(PlayerParseBranch(constantParseNode("unlink"), senderPortalParseNode)) // portals unlink [name]
|
||||
.branch(PlayerParseBranch(constantParseNode("list"))) // portals list
|
||||
.branch(PermissionParseBranch(permissionListOther, constantParseNode("list"), PARSE_NODE_PLAYER)) // portals list [player]
|
||||
.branch(PermissionParseBranch(permissionInvite, constantParseNode("invite"), senderPortalParseNode, PARSE_NODE_PLAYER)) // portals invite [name] [player]
|
||||
.branch(PermissionParseBranch(permissionInviteOther, constantParseNode("invite"), PARSE_NODE_PLAYER, otherPortalParseNode, PARSE_NODE_PLAYER)) // portals invite [owner] [name] [player]
|
||||
.branch(constantParseNode("invite"), constantParseNode("cancel"), PARSE_NODE_PLAYER, senderInviteParseNode) // portals invite cancel [player] [name]
|
||||
.branch(constantParseNode("invite"), constantParseNode("accept"), PARSE_NODE_PLAYER, recipientInviteParseNode) // portals invite accept [player] [name]
|
||||
.branch(constantParseNode("invite"), constantParseNode("decline"), PARSE_NODE_PLAYER, recipientInviteParseNode) // portals invite decline [player] [name]
|
||||
.branch(PlayerParseBranch(constantParseNode("invite"), constantParseNode("cancel"), PARSE_NODE_PLAYER, senderInviteParseNode)) // portals invite cancel [player] [name]
|
||||
.branch(PlayerParseBranch(constantParseNode("invite"), constantParseNode("accept"), PARSE_NODE_PLAYER, recipientInviteParseNode)) // portals invite accept [player] [name]
|
||||
.branch(PlayerParseBranch(constantParseNode("invite"), constantParseNode("decline"), PARSE_NODE_PLAYER, recipientInviteParseNode)) // portals invite decline [player] [name]
|
||||
.branch(PermissionParseBranch(permissionTp, false, constantParseNode("tp"), senderPortalParseNode)) // portals tp [name]
|
||||
.branch(PermissionParseBranch(permissionTpOther, false, constantParseNode("tp"), PARSE_NODE_PLAYER, otherPortalParseNode)) // portals tp [owner] [name]
|
||||
.branch(PermissionParseBranch(permissionInfo, false, constantParseNode("info"), senderPortalParseNode)) // portals info [name]
|
||||
.branch(PermissionParseBranch(permissionInfoOther, constantParseNode("info"), PARSE_NODE_PLAYER, otherPortalParseNode)) // portals info [owner] [name]
|
||||
.branch(PermissionParseBranch(permissionEdit, false, constantParseNode("edit"), senderPortalParseNode, constantParseNode("yaw"), PARSE_NODE_DECIMAL)) // portals edit [name] yaw [number]
|
||||
.branch(PermissionParseBranch(permissionEdit, false, constantParseNode("edit"), senderPortalParseNode, constantParseNode("pitch"), PARSE_NODE_DECIMAL)) // portals edit [name] pitch [number]
|
||||
|
||||
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
|
||||
when (val result = portalParse.getMatch(args, sender)) {
|
||||
is ParseResult.FailResult -> sender.spigot().sendMessage(TextComponent(result.reason))
|
||||
is ParseResult.SuccessResult ->
|
||||
sender.spigot().sendMessage(TextComponent(when(result.match[0] as String) {
|
||||
is ParseResult.SuccessResult -> {
|
||||
val message = when (result.match[0] as String) {
|
||||
"create" -> {
|
||||
val portal = portalManager.makePortal(sender as Player, result.match[1] as String, sender.location, if (result.match.size == 2) null else result.match[2] as Portal)
|
||||
if (portal == null) RESULT_ERROR_PORTALEXISTS else RESULT_SUCCESS_NEWPORTAL.format(Locale.ROOT, result.match[1] as String)
|
||||
val portal = portalManager.makePortal(
|
||||
sender as Player,
|
||||
result.match[1] as String,
|
||||
sender.location,
|
||||
if (result.match.size == 2) null else result.match[2] as Portal
|
||||
)
|
||||
if (portal == null) RESULT_ERROR_PORTALEXISTS else RESULT_SUCCESS_NEWPORTAL.format(
|
||||
Locale.ROOT,
|
||||
result.match[1] as String
|
||||
)
|
||||
}
|
||||
|
||||
"remove" -> {
|
||||
val portal = result.match.last() as Portal
|
||||
portalManager.removePortal(portal)
|
||||
if (result.match.size == 2) RESULT_SUCCESS_DELPORTAL.format(Locale.ROOT, portal.name)
|
||||
else RESULT_SUCCESS_DELPORTALOTHER.format(Locale.ROOT, portal.name, portal.owner.playerName)
|
||||
else RESULT_SUCCESS_DELPORTALOTHER.format(
|
||||
Locale.ROOT,
|
||||
portal.name,
|
||||
portal.owner.playerName
|
||||
)
|
||||
}
|
||||
|
||||
"uninvite" -> {
|
||||
@ -142,11 +172,15 @@ class PortalCommand(
|
||||
|
||||
if (toRemove.uniqueId == portal.owner.uniqueId) RESULT_ERROR_BANOWNER
|
||||
else if (!portal.public) {
|
||||
portal.accessExclusions.remove(toRemove)
|
||||
portal.removeAccessExclusion(toRemove)
|
||||
RESULT_SUCCESS_BAN.format(toRemove.playerName, portal.name)
|
||||
} else {
|
||||
portal.accessExclusions.add(toRemove)
|
||||
RESULT_SUCCESS_BANOTHER.format(toRemove.playerName, portal.name, portal.owner.playerName)
|
||||
portal.addAccessExclusion(toRemove)
|
||||
RESULT_SUCCESS_BANOTHER.format(
|
||||
toRemove.playerName,
|
||||
portal.name,
|
||||
portal.owner.playerName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,25 +202,34 @@ class PortalCommand(
|
||||
val owner = sender as? OfflinePlayer ?: result.match.last() as OfflinePlayer
|
||||
val portals = portalManager.getPortals(owner)
|
||||
|
||||
if (portals == null || !portals.iterator().hasNext()) RESULT_ERROR_NOPORTALS
|
||||
if (portals == null || !portals.iterator().hasNext()) RESULT_ERROR_NOPORTALS.format(owner.playerName)
|
||||
else {
|
||||
val builder = StringBuilder(RESULT_INFO_LIST)
|
||||
var counter = 0
|
||||
for (portal in portals)
|
||||
builder.append(counter++).append(". ").append(portal.name)
|
||||
builder.toString()
|
||||
sender.spigot().sendMessage(TextComponent(RESULT_INFO_LIST.format(owner.playerName)))
|
||||
for ((counter, portal) in portals.withIndex()) {
|
||||
val portalLink = portal.getPortalLink(portalManager::getPortal)
|
||||
sender.spigot().sendMessage(TextComponent("${counter}. ${portal.name}" + if (portalLink == null) "" else " -> ${portalLink.name}"))
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
"invite" ->
|
||||
when(result.match[1]) {
|
||||
when (result.match[1]) {
|
||||
is Portal, is OfflinePlayer -> {
|
||||
val recipient = result.match.last() as OfflinePlayer
|
||||
val portal = result.match[result.match.size - 2] as Portal
|
||||
|
||||
if (recipient.uniqueId == portal.owner.uniqueId || !portalManager.invitePlayer(recipient, portal)) RESULT_ERROR_INVITED
|
||||
if (recipient.uniqueId == portal.owner.uniqueId || !portalManager.invitePlayer(
|
||||
recipient,
|
||||
portal
|
||||
)
|
||||
) RESULT_ERROR_INVITED.format(recipient.playerName)
|
||||
else if (sender !is OfflinePlayer || portal.owner.uniqueId != (sender as OfflinePlayer).uniqueId)
|
||||
RESULT_SUCCESS_INVITEOTHER.format(recipient.playerName, portal.name, portal.owner.playerName)
|
||||
RESULT_SUCCESS_INVITEOTHER.format(
|
||||
recipient.playerName,
|
||||
portal.name,
|
||||
portal.owner.playerName
|
||||
)
|
||||
else RESULT_SUCCESS_INVITE.format(recipient.playerName, portal.name)
|
||||
}
|
||||
|
||||
@ -217,8 +260,51 @@ class PortalCommand(
|
||||
else -> RESULT_ERROR_UNKNOWN
|
||||
}
|
||||
|
||||
"tp" -> {
|
||||
portalManager.teleportPlayerTo(sender as Player, result.match.last() as Portal)
|
||||
null
|
||||
}
|
||||
|
||||
"info" -> {
|
||||
val portal = result.match.last() as Portal
|
||||
val link = portal.getPortalLink(portalManager::getPortal)
|
||||
|
||||
sender.spigot().sendMessage(TextComponent(RESULT_INFO_PORTAL.format(
|
||||
portal.name,
|
||||
portal.world.name,
|
||||
portal.x,
|
||||
portal.y,
|
||||
portal.z,
|
||||
portal.yaw,
|
||||
portal.pitch,
|
||||
if (link == null) RESULT_INFO_PORTAL_UNLINKED else RESULT_INFO_PORTAL_LINKED.format(link.name)
|
||||
)))
|
||||
null
|
||||
}
|
||||
|
||||
"edit" -> {
|
||||
val value = result.match.last() as Double
|
||||
val portal = result.match[result.match.size - 3] as Portal
|
||||
|
||||
when(result.match[result.match.size - 2] as String) {
|
||||
"yaw" -> {
|
||||
portal.yaw = value.toFloat()
|
||||
RESULT_SUCCESS_EDIT_YAW.format(value)
|
||||
}
|
||||
"pitch" -> {
|
||||
portal.pitch = value.toFloat()
|
||||
RESULT_SUCCESS_EDIT_PITCH.format(value)
|
||||
}
|
||||
else -> RESULT_ERROR_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
else -> RESULT_ERROR_UNKNOWN
|
||||
}))
|
||||
}
|
||||
|
||||
if (message != null)
|
||||
sender.spigot().sendMessage(TextComponent(message))
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
|
@ -1,13 +1,19 @@
|
||||
import net.md_5.bungee.api.chat.TextComponent
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.OfflinePlayer
|
||||
import org.bukkit.configuration.ConfigurationSection
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.HandlerList
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.event.player.PlayerMoveEvent
|
||||
import org.bukkit.event.player.PlayerQuitEvent
|
||||
import org.bukkit.plugin.Plugin
|
||||
import java.lang.Long.max
|
||||
import java.util.*
|
||||
import java.util.logging.Logger
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
private const val PATH_DATA_PLAYERS = "players"
|
||||
private const val PATH_DATA_WORLDS = "worlds"
|
||||
@ -29,6 +35,7 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
|
||||
// Player-based list needs to handle random access efficiently, whereas expiry list will always be accessed sequentially
|
||||
private val cooldowns = MultiSortedList(ArrayList(), ::LinkedList, COMPARATOR_COOLDOWN_PLAYER, COMPARATOR_COOLDOWN_EXPIRY)
|
||||
private val touchPortalCooldown = HashMap<UUID, Portal>()
|
||||
|
||||
private var cooldownTime = DEFAULT_COOLDOWN
|
||||
|
||||
@ -50,8 +57,8 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
// Start sequential search at the resulting index if it is populated
|
||||
val index = portals.search(COMPARATOR_PORTAL_UID) {
|
||||
compareValues(
|
||||
{ msb } to { it.id.mostSignificantBits.toULong() },
|
||||
{ lsb } to { it.id.leastSignificantBits.toULong() }
|
||||
{ it.id.mostSignificantBits.toULong() } to { msb },
|
||||
{ it.id.leastSignificantBits.toULong() } to { lsb }
|
||||
)
|
||||
}
|
||||
|
||||
@ -86,20 +93,17 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
|
||||
val portalList = ArrayList<Portal>()
|
||||
data.getStringList(PATH_DATA_PORTALS).forEach {
|
||||
val portal = readCompressedPortal(it, worlds::getValue, players::getValue) ?: return@forEach
|
||||
val portal = Portal.readCompressedPortal(it, players::getValue, players::getIndex, worlds::getValue, worlds::getIndex)
|
||||
portalList += portal
|
||||
|
||||
if (portal.id >= nextUUID)
|
||||
nextUUID = portal.id + 1UL
|
||||
}
|
||||
portals = MultiSortedList(portalList, ::ArrayList, COMPARATOR_PORTAL_LOCATION_OWNER, COMPARATOR_PORTAL_UID)
|
||||
portals = MultiSortedList(portalList, ::ArrayList, COMPARATOR_PORTAL_LOCATION_OWNER, COMPARATOR_PORTAL_UID, COMPARATOR_PORTAL_OWNER_NAME, COMPARATOR_PORTAL_LINKS)
|
||||
|
||||
if(portals.isEmpty()) nextUUID = UUID(0, 0)
|
||||
else {
|
||||
nextUUID = portals.get(0, COMPARATOR_PORTAL_UID).id + 1UL
|
||||
|
||||
// Compute next UUID
|
||||
nextUUID
|
||||
nextUUIDUsed = false
|
||||
}
|
||||
|
||||
@ -114,7 +118,7 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
fun save() {
|
||||
players.save()
|
||||
worlds.save()
|
||||
data.set(PATH_DATA_PORTALS, portals.map { it.toCompressedString(worlds::getIndex, players::getIndex) })
|
||||
data.set(PATH_DATA_PORTALS, portals.map { it.toCompressedString() })
|
||||
}
|
||||
|
||||
fun onEnable(plugin: Plugin) {
|
||||
@ -123,13 +127,14 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
|
||||
fun onDisable() {
|
||||
HandlerList.unregisterAll(this)
|
||||
save()
|
||||
}
|
||||
|
||||
fun getInvitationsForPlayer(player: OfflinePlayer) =
|
||||
invitations.getAll(COMPARATOR_INVITE_RECIPIENT) { player.uniqueId.compareTo(it.recipient.uniqueId) }
|
||||
invitations.getAll(COMPARATOR_INVITE_RECIPIENT) { it.recipient.uniqueId.compareTo(player.uniqueId) }
|
||||
|
||||
fun getInvitationsForPortal(portalID: UUID) =
|
||||
invitations.getAll(COMPARATOR_INVITE_PORTAL) { portalID.compareTo(it.portalID) }
|
||||
invitations.getAll(COMPARATOR_INVITE_PORTAL) { it.portalID.compareTo(portalID) }
|
||||
|
||||
fun getInvitationsForPortal(portal: Portal) = getInvitationsForPortal(portal.id)
|
||||
|
||||
@ -146,8 +151,8 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
|
||||
fun invitePlayer(player: OfflinePlayer, portal: Portal): Boolean {
|
||||
// Player is already invited or already has a pending invitation
|
||||
if (player in portal.accessExclusions || invitations.search(COMPARATOR_INVITE_RECIPIENT) {
|
||||
compareValues(player::getUniqueId to it.recipient::getUniqueId, portal::id to it::portalID)
|
||||
if (portal.containsAccessExclusion(player) || invitations.search(COMPARATOR_INVITE_RECIPIENT) {
|
||||
compareValues(it.recipient::getUniqueId to player::getUniqueId, it::portalID to portal::id)
|
||||
} >= 0)
|
||||
return false
|
||||
|
||||
@ -159,8 +164,8 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
fun cancelInvite(player: OfflinePlayer, portal: Portal): Boolean {
|
||||
val index = invitations.search(COMPARATOR_INVITE_RECIPIENT) {
|
||||
compareValues(
|
||||
player::getUniqueId to it.recipient::getUniqueId,
|
||||
portal::id to it::portalID
|
||||
it.recipient::getUniqueId to player::getUniqueId,
|
||||
it::portalID to portal::id
|
||||
)
|
||||
}
|
||||
|
||||
@ -174,12 +179,12 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
invitations.remove(invite)
|
||||
|
||||
private fun acceptInvite0(player: OfflinePlayer, portal: Portal) {
|
||||
if (portal.public) portal.accessExclusions -= player
|
||||
else portal.accessExclusions += player
|
||||
if (portal.public) portal.removeAccessExclusion(player)
|
||||
else portal.addAccessExclusion(player)
|
||||
}
|
||||
|
||||
fun acceptInvite(player: OfflinePlayer, portal: Portal): Boolean {
|
||||
if (!cancelInvite(player, portal) || (player in portal.accessExclusions != portal.public)) return false
|
||||
if (!cancelInvite(player, portal) || (portal.containsAccessExclusion(player) != portal.public)) return false
|
||||
|
||||
acceptInvite0(player, portal)
|
||||
|
||||
@ -188,7 +193,7 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
|
||||
fun acceptInvite(invite: Invite): Boolean {
|
||||
val portal = getPortal(invite.portalID) ?: return false
|
||||
if (!cancelInvite(invite) || (invite.recipient in portal.accessExclusions != portal.public)) return false
|
||||
if (!cancelInvite(invite) || (portal.containsAccessExclusion(invite.recipient) != portal.public)) return false
|
||||
|
||||
acceptInvite0(invite.recipient, portal)
|
||||
|
||||
@ -201,6 +206,7 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
|
||||
fun makePortal(owner: OfflinePlayer, name: String, location: Location, link: Portal? = null): Portal? {
|
||||
val portal = Portal(
|
||||
players::getValue, players::getIndex, worlds::getValue, worlds::getIndex,
|
||||
nextUUID,
|
||||
owner,
|
||||
location.world!!,
|
||||
@ -209,10 +215,8 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
location.blockZ,
|
||||
location.yaw,
|
||||
location.pitch,
|
||||
0,
|
||||
link?.id,
|
||||
name,
|
||||
SortedList(comparator = COMPARATOR_PLAYER)
|
||||
link
|
||||
)
|
||||
|
||||
return if (makePortal(portal)) portal else null
|
||||
@ -230,8 +234,9 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
invitations.getAll(comparator, comparison)?.forEach(invitations::remove)
|
||||
|
||||
fun removePortal(owner: OfflinePlayer, name: String): Boolean {
|
||||
val ownerIndex = players.getIndex(owner)
|
||||
val index = portals.search(COMPARATOR_PORTAL_OWNER_NAME) {
|
||||
compareValues(owner::getUniqueId to it.owner::getUniqueId, { name } to it::name)
|
||||
compareValues(it::ownerIndex to { ownerIndex }, it::name to { name })
|
||||
}
|
||||
|
||||
if (index < 0) return false
|
||||
@ -243,10 +248,21 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
// Unlink portals
|
||||
portals.getAll(COMPARATOR_PORTAL_LINKS, removed.COMPARISON_PORTAL_LINKEDTO)?.forEach(Portal::unlink)
|
||||
|
||||
onPortalRemove(removed)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun removePortal(portal: Portal) = portals.remove(portal)
|
||||
fun removePortal(portal: Portal) {
|
||||
portals.remove(portal)
|
||||
onPortalRemove(portal)
|
||||
}
|
||||
|
||||
private fun onPortalRemove(portal: Portal) {
|
||||
synchronized(touchPortalCooldown) {
|
||||
touchPortalCooldown.values.removeIf(portal::equals)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPortal(uuid: UUID): Portal? {
|
||||
val index = portals.search(COMPARATOR_PORTAL_UID, uuid.COMPARISON_PORTAL_ID)
|
||||
@ -256,7 +272,7 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
|
||||
fun getPortal(owner: OfflinePlayer, name: String): Portal? {
|
||||
val index = portals.search(COMPARATOR_PORTAL_OWNER_NAME) {
|
||||
compareValues(owner::getUniqueId to it.owner::getUniqueId, { name } to it::name)
|
||||
compareValues(it.owner::getUniqueId to owner::getUniqueId, it::name to { name })
|
||||
}
|
||||
|
||||
if (index < 0) return null
|
||||
@ -270,52 +286,70 @@ class PortalManager(private val data: ConfigurationSection, private val config:
|
||||
fun getPortalsByPartialName(owner: OfflinePlayer, namePart: String) =
|
||||
portals.getAll(COMPARATOR_PORTAL_OWNER_NAME) {
|
||||
compareValues(
|
||||
owner::getUniqueId to it.owner::getUniqueId,
|
||||
{ namePart } to { it.name.substring(0, namePart.length.coerceAtMost(it.name.length)) }
|
||||
it.owner::getUniqueId to owner::getUniqueId,
|
||||
{ it.name.substring(0, namePart.length.coerceAtMost(it.name.length)) } to { namePart }
|
||||
)
|
||||
}
|
||||
|
||||
fun getPortalsAt(location: Location) =
|
||||
portals.getAll(COMPARATOR_PORTAL_LOCATION_OWNER, location.COMPARISON_PORTAL)
|
||||
portals.getAll(COMPARATOR_PORTAL_LOCATION_OWNER, location.portalComparison(worlds::getIndex))
|
||||
|
||||
private fun popCooldowns() {
|
||||
fun teleportPlayerTo(player: Player, portal: Portal) {
|
||||
val result = portal.enterPortal(player, this::getPortal)
|
||||
if (result is PortalResult.SUCCESS)
|
||||
triggerCooldown(player, result.link)
|
||||
else
|
||||
Logger.getLogger("SpigotPortals")
|
||||
.warning("${player.name} failed to enter portal ${portal.name} (${portal.owner.playerName}; ${portal.world.name}; ${portal.x}, ${portal.y}, ${portal.z})")
|
||||
}
|
||||
|
||||
private fun popCooldowns(player: OfflinePlayer, moveTo: Location) {
|
||||
val time = System.currentTimeMillis()
|
||||
while (cooldowns.isNotEmpty()) {
|
||||
val front = cooldowns.get(0, COMPARATOR_COOLDOWN_EXPIRY)
|
||||
if (front.isExpired(time)) cooldowns.removeAt(0, COMPARATOR_COOLDOWN_EXPIRY)
|
||||
else break
|
||||
}
|
||||
|
||||
if (moveTo.portalComparison(worlds::getIndex)(touchPortalCooldown[player.uniqueId] ?: return) != 0) {
|
||||
touchPortalCooldown.remove(player.uniqueId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isOnCooldown(player: OfflinePlayer): Boolean {
|
||||
popCooldowns()
|
||||
return cooldowns.search(COMPARATOR_COOLDOWN_PLAYER, player.COMPARISON_COOLDOWN) >= 0
|
||||
private fun isOnCooldown(player: OfflinePlayer, moveTo: Location): Boolean {
|
||||
popCooldowns(player, moveTo)
|
||||
return cooldowns.search(COMPARATOR_COOLDOWN_PLAYER, player.COMPARISON_COOLDOWN) >= 0 || player.uniqueId in touchPortalCooldown
|
||||
}
|
||||
|
||||
private fun triggerCooldown(player: OfflinePlayer) {
|
||||
private fun triggerCooldown(player: OfflinePlayer, portal: Portal) {
|
||||
cooldowns.add(Pair(player, System.currentTimeMillis() + cooldownTime), false)
|
||||
touchPortalCooldown[player.uniqueId] = portal
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onPlayerMove(moveEvent: PlayerMoveEvent) {
|
||||
// If we're ignoring player movements for this player, just return immediately
|
||||
if (isOnCooldown(moveEvent.player)) return
|
||||
|
||||
fun UUID.portalMapper() = portals.firstOrNull { it.id == this }
|
||||
val to = moveEvent.to
|
||||
|
||||
if (!moveEvent.isCancelled && to != null) {
|
||||
// If we're ignoring player movements for this player, just return immediately
|
||||
if (isOnCooldown(moveEvent.player, to)) return
|
||||
|
||||
val found = getPortalsAt(to)
|
||||
|
||||
if ((found?.firstOrNull { it.owner.uniqueId == moveEvent.player.uniqueId }
|
||||
?.enterPortal(moveEvent.player, UUID::portalMapper)
|
||||
?: found?.firstOrNull {
|
||||
it.enterPortal(
|
||||
moveEvent.player,
|
||||
UUID::portalMapper
|
||||
) == PortalResult.SUCCESS
|
||||
}) != null)
|
||||
triggerCooldown(moveEvent.player)
|
||||
val triggered = found?.firstOrNull {
|
||||
it.owner.uniqueId == moveEvent.player.uniqueId && it.checkEnter(moveEvent.player, this::getPortal) is PortalResult.SUCCESS
|
||||
}
|
||||
?: found?.firstOrNull { it.checkEnter(moveEvent.player, this::getPortal) is PortalResult.SUCCESS }
|
||||
|
||||
if (triggered != null)
|
||||
teleportPlayerTo(moveEvent.player, triggered)
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onPlayerDisconnect(disconnectEvent: PlayerQuitEvent) {
|
||||
synchronized(touchPortalCooldown) {
|
||||
touchPortalCooldown.remove(disconnectEvent.player.uniqueId)
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,11 @@ class PortalsPlugin: JavaPlugin() {
|
||||
description.permissions.first { it.name == "portals.invite" },
|
||||
description.permissions.first { it.name == "portals.invite.other" },
|
||||
description.permissions.first { it.name == "portals.list.other" },
|
||||
description.permissions.first { it.name == "portals.tp" },
|
||||
description.permissions.first { it.name == "portals.tp.other" },
|
||||
description.permissions.first { it.name == "portals.info" },
|
||||
description.permissions.first { it.name == "portals.info.other" },
|
||||
description.permissions.first { it.name == "portals.modify.edit" }
|
||||
)
|
||||
|
||||
val pluginCommand = getCommand("portals")!!
|
||||
|
Loading…
x
Reference in New Issue
Block a user