From 0b8deb4c54f6ad96e0a63692c09a5929f39920fa Mon Sep 17 00:00:00 2001 From: Dico Date: Mon, 6 Aug 2018 02:53:59 +0100 Subject: Add todo.md --- src/main/kotlin/io/dico/parcels2/AddedData.kt | 4 +- src/main/kotlin/io/dico/parcels2/Parcel.kt | 4 ++ src/main/kotlin/io/dico/parcels2/ParcelId.kt | 2 +- src/main/kotlin/io/dico/parcels2/PlayerProfile.kt | 1 + .../io/dico/parcels2/command/CommandsGeneral.kt | 34 +++++---- .../io/dico/parcels2/command/ParcelTarget.kt | 51 +++++++------- .../io/dico/parcels2/defaultimpl/ParcelImpl.kt | 2 - .../parcels2/storage/exposed/ExposedBacking.kt | 3 +- .../io/dico/parcels2/storage/exposed/IdTables.kt | 10 ++- .../kotlin/io/dico/parcels2/util/MiscExtensions.kt | 4 ++ todo.md | 80 ++++++++++++++++++++++ 11 files changed, 149 insertions(+), 46 deletions(-) create mode 100644 todo.md diff --git a/src/main/kotlin/io/dico/parcels2/AddedData.kt b/src/main/kotlin/io/dico/parcels2/AddedData.kt index 9249c7f..9835950 100644 --- a/src/main/kotlin/io/dico/parcels2/AddedData.kt +++ b/src/main/kotlin/io/dico/parcels2/AddedData.kt @@ -17,7 +17,7 @@ interface AddedData { fun setAddedStatus(key: StatusKey, status: AddedStatus): Boolean fun compareAndSetAddedStatus(key: StatusKey, expect: AddedStatus, status: AddedStatus): Boolean = - (getAddedStatus(key) == expect).also { if (it) setAddedStatus(key, status) } + getAddedStatus(key) == expect && setAddedStatus(key, status) fun isAllowed(key: StatusKey) = getAddedStatus(key) == AddedStatus.ALLOWED fun allow(key: StatusKey) = setAddedStatus(key, AddedStatus.ALLOWED) @@ -36,7 +36,7 @@ interface AddedData { inline val OfflinePlayer.statusKey get() = PlayerProfile.nameless(this) -open class AddedDataHolder(override var addedMap: MutableAddedDataMap = mutableMapOf()) : AddedData { +open class AddedDataHolder(override var addedMap: MutableAddedDataMap = MutableAddedDataMap()) : AddedData { override var addedStatusOfStar: AddedStatus = AddedStatus.DEFAULT override fun getAddedStatus(key: StatusKey): AddedStatus = addedMap.getOrDefault(key, addedStatusOfStar) diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index e7c5209..a9e5de9 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -44,6 +44,10 @@ interface ParcelData : AddedData { fun isOwner(uuid: UUID): Boolean { return owner?.uuid == uuid } + + fun isOwner(profile: PlayerProfile?): Boolean { + return owner == profile + } } class ParcelDataHolder(addedMap: MutableAddedDataMap = mutableMapOf()) : AddedDataHolder(addedMap), ParcelData { diff --git a/src/main/kotlin/io/dico/parcels2/ParcelId.kt b/src/main/kotlin/io/dico/parcels2/ParcelId.kt index 951a172..20d4cc8 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) } diff --git a/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt b/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt index c5403cd..2e543dc 100644 --- a/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt +++ b/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt @@ -18,6 +18,7 @@ interface PlayerProfile { val name: String? val notNullName: String val isStar: Boolean get() = false + val exists: Boolean get() = this is RealImpl fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index 7442019..439d653 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -6,8 +6,8 @@ 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.PlayerProfile import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.hasParcelHomeOthers import io.dico.parcels2.util.uuid @@ -44,24 +44,34 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { 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") - } + return cmdGoto(player, target) + } - val ownedParcelsResult = plugin.storage.getOwnedParcels(ownerTarget.owner).await() + @Cmd("tp", aliases = ["teleport"]) + suspend fun cmdTp(player: Player, @ParcelTarget.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, @ParcelTarget.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(targetMatch.world.getHomeLocation(targetMatch.id)) + player.teleport(match.world.getHomeLocation(match.id)) return "" } + @Cmd("goto_fake") + suspend fun cmdGotoFake(player: Player, @ParcelTarget.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") diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt index cbab80a..bbdcdb8 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt @@ -5,20 +5,21 @@ 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 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 = functionHelper.deferUndispatchedOnMainThread { getParcelSuspend() } + fun ParcelsPlugin.getParcelDeferred(): Deferred = functionHelper.deferUndispatchedOnMainThread { 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,31 +27,31 @@ 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) } @@ -65,7 +66,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val isDefault: Boolean) { 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,7 +74,7 @@ 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 } @@ -95,12 +96,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 { @@ -156,10 +157,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/ParcelImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt index 128705f..c154955 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt @@ -4,10 +4,8 @@ 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 org.bukkit.OfflinePlayer import org.joda.time.DateTime -import java.util.UUID import kotlin.reflect.KProperty class ParcelImpl(override val world: ParcelWorld, 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..11d26c4 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -92,8 +92,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 } 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 33314aa..45386e8 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt @@ -91,15 +91,19 @@ object ParcelsT : IdTransactionsTable("parcels", "parcel_id" object ProfilesT : IdTransactionsTable("parcel_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 + 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,7 +123,7 @@ object ProfilesT : IdTransactionsTable("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.name) is PlayerProfile.Fake -> getOrInitId(profile.name) else -> throw IllegalArgumentException() } diff --git a/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt b/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt index 877d1cc..24f9401 100644 --- a/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt +++ b/src/main/kotlin/io/dico/parcels2/util/MiscExtensions.kt @@ -22,6 +22,10 @@ inline fun Any.synchronized(block: () -> R): R = synchronized(this, block) inline fun T?.isNullOr(condition: T.() -> Boolean): Boolean = this == null || condition() inline fun T?.isPresentAnd(condition: T.() -> Boolean): Boolean = this != null && condition() +inline fun T?.ifNullRun(block: () -> Unit): T? { + if (this == null) block() + return this +} inline fun MutableMap.editLoop(block: EditLoopScope.(T, U) -> Unit) { return EditLoopScope(this).doEditLoop(block) diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..959c628 --- /dev/null +++ b/todo.md @@ -0,0 +1,80 @@ +# 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). + + -- cgit v1.2.3