Archived
0

Add todo.md

This commit is contained in:
Dico
2018-08-06 02:53:59 +01:00
parent ba347a8053
commit 0b8deb4c54
11 changed files with 150 additions and 47 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)
}
@Cmd("tp", aliases = ["teleport"])
suspend fun cmdTp(player: Player, @ParcelTarget.Kind(ParcelTarget.ID) target: ParcelTarget): Any? {
return cmdGoto(player, target)
}
@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 ownedParcelsResult = plugin.storage.getOwnedParcels(ownerTarget.owner).await()
val ownedParcels = ownedParcelsResult
.map { worlds.getParcelById(it) }
.filter { it != null && ownerTarget.world == it.world }
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")

View File

@@ -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<Parcel?> = functionHelper.deferUndispatchedOnMainThread { getParcelSuspend() }
fun ParcelsPlugin.getParcelDeferred(): Deferred<Parcel?> = 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)
}
}

View File

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

View File

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

View File

@@ -91,15 +91,19 @@ object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id"
object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("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<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.name)
is PlayerProfile.Fake -> getOrInitId(profile.name)
else -> throw IllegalArgumentException()
}

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

80
todo.md Normal file
View File

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