From 5bd0970c54a843c897126116d5eaff88014360fb Mon Sep 17 00:00:00 2001 From: Dico Date: Sun, 12 Aug 2018 18:07:43 +0100 Subject: Work on a couple of the todos --- .../dico/dicore/command/ChildCommandAddress.java | 3 +- .../io/dico/dicore/command/CommandBuilder.java | 24 ++++ .../dicore/command/ModifiableCommandAddress.java | 3 +- .../io/dico/dicore/command/RootCommandAddress.java | 5 +- .../command/annotation/RequirePermissions.java | 3 + .../dicore/command/predef/DefaultGroupCommand.java | 24 ++++ src/main/kotlin/io/dico/parcels2/Parcel.kt | 9 +- .../kotlin/io/dico/parcels2/ParcelGenerator.kt | 55 ++++++++-- src/main/kotlin/io/dico/parcels2/ParcelWorld.kt | 8 +- src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt | 11 +- src/main/kotlin/io/dico/parcels2/PlayerProfile.kt | 3 +- .../dico/parcels2/blockvisitor/RegionTraversal.kt | 43 -------- .../dico/parcels2/blockvisitor/RegionTraverser.kt | 67 +++++++++++ .../io/dico/parcels2/blockvisitor/Schematic.kt | 4 +- .../dico/parcels2/blockvisitor/WorktimeLimiter.kt | 8 +- .../io/dico/parcels2/command/CommandsDebug.kt | 5 +- .../io/dico/parcels2/command/CommandsGeneral.kt | 6 +- .../dico/parcels2/command/ParcelCommandBuilder.kt | 3 +- .../parcels2/defaultimpl/DefaultParcelGenerator.kt | 122 ++++++++++++++++----- .../io/dico/parcels2/defaultimpl/ParcelImpl.kt | 12 +- .../parcels2/defaultimpl/ParcelProviderImpl.kt | 20 +++- .../dico/parcels2/defaultimpl/ParcelWorldImpl.kt | 47 ++++---- .../io/dico/parcels2/listener/ParcelListeners.kt | 31 +++++- .../kotlin/io/dico/parcels2/storage/Backing.kt | 9 ++ .../kotlin/io/dico/parcels2/storage/Storage.kt | 17 +++ .../parcels2/storage/exposed/ExposedBacking.kt | 38 ++++++- .../io/dico/parcels2/storage/exposed/IdTables.kt | 25 ++++- .../storage/migration/plotme/PlotmeMigration.kt | 1 + src/main/kotlin/io/dico/parcels2/util/Region.kt | 8 ++ src/main/kotlin/io/dico/parcels2/util/Vec2i.kt | 4 + src/main/kotlin/io/dico/parcels2/util/Vec3i.kt | 19 ++++ todo.md | 12 +- 32 files changed, 502 insertions(+), 147 deletions(-) create mode 100644 dicore3/command/src/main/java/io/dico/dicore/command/predef/DefaultGroupCommand.java delete mode 100644 src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt create mode 100644 src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/ChildCommandAddress.java b/dicore3/command/src/main/java/io/dico/dicore/command/ChildCommandAddress.java index 7593492..73d82ca 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/ChildCommandAddress.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/ChildCommandAddress.java @@ -1,5 +1,6 @@ package io.dico.dicore.command; +import io.dico.dicore.command.predef.DefaultGroupCommand; import io.dico.dicore.command.predef.HelpCommand; import java.util.*; @@ -23,7 +24,7 @@ public class ChildCommandAddress extends ModifiableCommandAddress { } public static ChildCommandAddress newPlaceHolderCommand(String name, String... aliases) { - ChildCommandAddress rv = new ChildCommandAddress(null, name, aliases); + ChildCommandAddress rv = new ChildCommandAddress(DefaultGroupCommand.getInstance(), name, aliases); HelpCommand.registerAsChild(rv); return rv; } diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/CommandBuilder.java b/dicore3/command/src/main/java/io/dico/dicore/command/CommandBuilder.java index 0ffc960..e72d478 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/CommandBuilder.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/CommandBuilder.java @@ -210,15 +210,39 @@ public final class CommandBuilder { * @param shortDescription a short description * @param description the lines of a full description. * @return this + * @throws IllegalStateException if the current group has no command */ public CommandBuilder setGroupDescription(String shortDescription, String... description) { Command command = cur.getCommand(); + if (command == null) throw new IllegalStateException(); cur.setCommand(command .setShortDescription(shortDescription) .setDescription(description)); return this; } + /** + * Add a context filter to the command of the current group + * @return this + * @throws IllegalStateException if the current group has no command + */ + public CommandBuilder addContextFilter(IContextFilter contextFilter) { + Command command = cur.getCommand(); + if (command == null) throw new IllegalStateException(); + cur.setCommand(command + .addContextFilter(contextFilter)); + return this; + } + + /** + * Add a required permission to the command of the current group + * @return this + * @throws IllegalStateException if the current group has no command + */ + public CommandBuilder addRequiredPermission(String permission) { + return addContextFilter(IContextFilter.permission(permission)); + } + /** * Jump up a level in the address * diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/ModifiableCommandAddress.java b/dicore3/command/src/main/java/io/dico/dicore/command/ModifiableCommandAddress.java index 698eee8..8c2ab67 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/ModifiableCommandAddress.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/ModifiableCommandAddress.java @@ -2,6 +2,7 @@ package io.dico.dicore.command; import io.dico.dicore.command.chat.ChatControllers; import io.dico.dicore.command.chat.IChatController; +import io.dico.dicore.command.predef.DefaultGroupCommand; import io.dico.dicore.command.predef.HelpCommand; import io.dico.dicore.command.predef.PredefinedCommand; @@ -32,7 +33,7 @@ public abstract class ModifiableCommandAddress implements ICommandAddress { @Override public boolean hasUserDeclaredCommand() { Command command = getCommand(); - return command != null && !(command instanceof PredefinedCommand); + return command != null && !(command instanceof PredefinedCommand) && !(command instanceof DefaultGroupCommand); } @Override diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java b/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java index 91dcc5b..6d38174 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java @@ -1,6 +1,7 @@ package io.dico.dicore.command; import io.dico.dicore.command.parameter.ArgumentBuffer; +import io.dico.dicore.command.predef.DefaultGroupCommand; import io.dico.dicore.command.registration.BukkitCommand; import org.bukkit.Location; import org.bukkit.command.CommandSender; @@ -167,10 +168,10 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom ModifiableCommandAddress targetAddress = getCommandTarget(sender, buffer); Command target = targetAddress.getCommand(); - if (target == null) { + if (target == null || target instanceof DefaultGroupCommand) { if (targetAddress.hasHelpCommand()) { target = targetAddress.getHelpCommand().getCommand(); - } else { + } else if (target == null){ return false; } } diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/annotation/RequirePermissions.java b/dicore3/command/src/main/java/io/dico/dicore/command/annotation/RequirePermissions.java index 0fbe9a4..d2ba782 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/annotation/RequirePermissions.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/annotation/RequirePermissions.java @@ -7,6 +7,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * If this annotation is not present, inheriting permissions is default. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequirePermissions { diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/predef/DefaultGroupCommand.java b/dicore3/command/src/main/java/io/dico/dicore/command/predef/DefaultGroupCommand.java new file mode 100644 index 0000000..3d96a7d --- /dev/null +++ b/dicore3/command/src/main/java/io/dico/dicore/command/predef/DefaultGroupCommand.java @@ -0,0 +1,24 @@ +package io.dico.dicore.command.predef; + +import io.dico.dicore.command.Command; +import io.dico.dicore.command.CommandException; +import io.dico.dicore.command.ExecutionContext; +import org.bukkit.command.CommandSender; + +public class DefaultGroupCommand extends Command { + private static final DefaultGroupCommand instance = new DefaultGroupCommand(); + + public static DefaultGroupCommand getInstance() { + return instance; + } + + private DefaultGroupCommand() { + + } + + @Override public String execute(CommandSender sender, ExecutionContext context) throws CommandException { + context.getAddress().getChatController().sendHelpMessage(sender, context, context.getAddress(), 1); + return null; + } + +} diff --git a/src/main/kotlin/io/dico/parcels2/Parcel.kt b/src/main/kotlin/io/dico/parcels2/Parcel.kt index a9e5de9..3b82d32 100644 --- a/src/main/kotlin/io/dico/parcels2/Parcel.kt +++ b/src/main/kotlin/io/dico/parcels2/Parcel.kt @@ -2,6 +2,7 @@ package io.dico.parcels2 import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.hasBuildAnywhere +import org.bukkit.Location import org.bukkit.OfflinePlayer import org.bukkit.entity.Player import org.joda.time.DateTime @@ -30,11 +31,14 @@ interface Parcel : ParcelData { fun copyData(data: ParcelData) fun dispose() + + val homeLocation: Location get() = world.blockManager.getHomeLocation(id) } interface ParcelData : AddedData { var owner: PlayerProfile? - val since: DateTime? + val lastClaimTime: DateTime? + var ownerSignOutdated: Boolean fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): Boolean @@ -53,7 +57,8 @@ interface ParcelData : AddedData { class ParcelDataHolder(addedMap: MutableAddedDataMap = mutableMapOf()) : AddedDataHolder(addedMap), ParcelData { override var owner: PlayerProfile? = null - override var since: DateTime? = null + override var lastClaimTime: DateTime? = null + override var ownerSignOutdated = false override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.statusKey) || owner.let { it != null && it.matches(player, allowNameMatch = false) } || (checkAdmin && player is Player && player.hasBuildAnywhere) diff --git a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt index b9d2e3d..f8e4fd0 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelGenerator.kt @@ -1,9 +1,12 @@ package io.dico.parcels2 -import io.dico.parcels2.blockvisitor.RegionTraversal +import io.dico.parcels2.blockvisitor.RegionTraverser import io.dico.parcels2.blockvisitor.Worker +import io.dico.parcels2.blockvisitor.WorkerScope import io.dico.parcels2.blockvisitor.WorktimeLimiter +import io.dico.parcels2.util.Region import io.dico.parcels2.util.Vec2i +import io.dico.parcels2.util.get import org.bukkit.Chunk import org.bukkit.Location import org.bukkit.World @@ -15,6 +18,8 @@ import org.bukkit.generator.ChunkGenerator import java.util.Random abstract class ParcelGenerator : ChunkGenerator() { + abstract val worldName: String + abstract val world: World abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData @@ -31,31 +36,57 @@ abstract class ParcelGenerator : ChunkGenerator() { }) } - abstract fun makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager - - abstract fun makeParcelLocator(container: ParcelContainer): ParcelLocator + abstract fun makeParcelLocatorAndBlockManager(worldId: ParcelWorldId, + container: ParcelContainer, + worktimeLimiter: WorktimeLimiter): Pair } -@Suppress("DeprecatedCallableAddReplaceWith") interface ParcelBlockManager { val world: World val worktimeLimiter: WorktimeLimiter + val parcelTraverser: RegionTraverser - fun getBottomBlock(parcel: ParcelId): Vec2i + // fun getBottomBlock(parcel: ParcelId): Vec2i fun getHomeLocation(parcel: ParcelId): Location - fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) + fun getRegion(parcel: ParcelId): Region - @Deprecated("") - fun getEntities(parcel: ParcelId): Collection = TODO() + fun getEntities(parcel: ParcelId): Collection - @Deprecated("") - fun getBlocks(parcel: ParcelId, yRange: IntRange = 0..255): Iterator = TODO() + fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) fun setBiome(parcel: ParcelId, biome: Biome): Worker fun clearParcel(parcel: ParcelId): Worker - fun doBlockOperation(parcel: ParcelId, direction: RegionTraversal = RegionTraversal.DOWNWARD, operation: (Block) -> Unit): Worker + /** + * Used to update owner blocks in the corner of the parcel + */ + fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection +} + +inline fun ParcelBlockManager.doBlockOperation(parcel: ParcelId, + traverser: RegionTraverser, + crossinline operation: suspend WorkerScope.(Block) -> Unit) = worktimeLimiter.submit { + val region = getRegion(parcel) + val blockCount = region.blockCount.toDouble() + val blocks = traverser.traverseRegion(region) + for ((index, vec) in blocks.withIndex()) { + markSuspensionPoint() + operation(world[vec]) + setProgress((index + 1) / blockCount) + } +} + +abstract class ParcelBlockManagerBase : ParcelBlockManager { + + override fun getEntities(parcel: ParcelId): Collection { + val region = getRegion(parcel) + val center = region.center + val centerLoc = Location(world, center.x, center.y, center.z) + val centerDist = (center - region.origin).add(0.2, 0.2, 0.2) + return world.getNearbyEntities(centerLoc, centerDist.x, centerDist.y, centerDist.z) + } + } diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index 0dcdfbd..d307947 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -4,10 +4,12 @@ import io.dico.parcels2.options.RuntimeWorldOptions import io.dico.parcels2.storage.Storage import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.floor +import org.bukkit.Chunk import org.bukkit.Location import org.bukkit.World import org.bukkit.block.Block import org.bukkit.entity.Entity +import org.joda.time.DateTime import java.util.UUID interface ParcelProvider { @@ -58,7 +60,6 @@ interface ParcelLocator { fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world } fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world } - } typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer @@ -73,7 +74,7 @@ interface ParcelContainer { } -interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager { +interface ParcelWorld : ParcelLocator, ParcelContainer { val id: ParcelWorldId val name: String val uid: UUID? @@ -84,4 +85,7 @@ interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager { val locator: ParcelLocator val blockManager: ParcelBlockManager val globalAddedData: GlobalAddedDataManager + + val creationTime: DateTime? + } diff --git a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt index e9c70f6..01ee857 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelsPlugin.kt @@ -21,6 +21,7 @@ import org.bukkit.plugin.java.JavaPlugin import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.File +import java.util.Random val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin") private inline val plogger get() = logger @@ -49,7 +50,15 @@ class ParcelsPlugin : JavaPlugin() { } override fun onDisable() { + val hasWorkers = worktimeLimiter.workers.isNotEmpty() + if (hasWorkers) { + plogger.warn("Parcels is attempting to complete all ${worktimeLimiter.workers.size} remaining jobs before shutdown...") + } worktimeLimiter.completeAllTasks() + if (hasWorkers) { + plogger.info("Parcels has completed the remaining jobs.") + } + cmdDispatcher?.unregisterFromCommandMap() } @@ -124,7 +133,7 @@ class ParcelsPlugin : JavaPlugin() { private fun registerListeners() { if (listeners == null) { - listeners = ParcelListeners(parcelProvider, entityTracker) + listeners = ParcelListeners(parcelProvider, entityTracker, storage) registrator.registerListeners(listeners!!) } diff --git a/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt b/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt index b096665..c735c68 100644 --- a/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt +++ b/src/main/kotlin/io/dico/parcels2/PlayerProfile.kt @@ -77,7 +77,8 @@ interface PlayerProfile { interface Real : PlayerProfile { override val uuid: UUID override val nameOrBukkitName: String? - get() = name ?: Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }?.name + // If a player is online, their name is prioritized to get name changes right immediately + get() = Bukkit.getPlayer(uuid)?.name ?: name ?: Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }?.name override val notNullName: String get() = name ?: getPlayerNameOrDefault(uuid) diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt deleted file mode 100644 index 85fe946..0000000 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraversal.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.dico.parcels2.blockvisitor - -import io.dico.parcels2.util.Region -import io.dico.parcels2.util.Vec3i -import kotlin.coroutines.experimental.SequenceBuilder -import kotlin.coroutines.experimental.buildIterator - -enum class RegionTraversal(private val builder: suspend SequenceBuilder.(Region) -> Unit) { - DOWNWARD({ region -> - val origin = region.origin - val size = region.size - - repeat(size.y) { y -> - repeat(size.z) { z -> - repeat(size.x) { x -> - yield(origin.add(x, size.y - y - 1, z)) - } - } - } - - }), - - UPWARD({ region -> - val origin = region.origin - val size = region.size - - repeat(size.y) { y -> - repeat(size.z) { z -> - repeat(size.x) { x -> - yield(origin.add(x, y, z)) - } - } - } - }), - - ; - - fun regionTraverser(region: Region) = Iterable { buildIterator { builder(region) } } - -} - - - diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt new file mode 100644 index 0000000..1cac5f9 --- /dev/null +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/RegionTraverser.kt @@ -0,0 +1,67 @@ +package io.dico.parcels2.blockvisitor + +import io.dico.parcels2.util.Region +import io.dico.parcels2.util.Vec3i +import kotlin.coroutines.experimental.SequenceBuilder +import kotlin.coroutines.experimental.buildIterator + +abstract class RegionTraverser { + fun traverseRegion(region: Region): Iterable = Iterable { buildIterator { build(region) } } + + protected abstract suspend fun SequenceBuilder.build(region: Region) + + companion object { + val upward = create { traverseUpward(it) } + val downward = create { traverseDownward(it) } + val forClearing get() = downward + val forFilling get() = upward + + inline fun create(crossinline builder: suspend SequenceBuilder.(Region) -> Unit) = object : RegionTraverser() { + override suspend fun SequenceBuilder.build(region: Region) { + builder(region) + } + } + + private suspend fun SequenceBuilder.traverseDownward(region: Region) { + val origin = region.origin + val size = region.size + repeat(size.y) { y -> + repeat(size.z) { z -> + repeat(size.x) { x -> + yield(origin.add(x, size.y - y - 1, z)) + } + } + } + } + + private suspend fun SequenceBuilder.traverseUpward(region: Region) { + val origin = region.origin + val size = region.size + repeat(size.y) { y -> + repeat(size.z) { z -> + repeat(size.x) { x -> + yield(origin.add(x, size.y - y - 1, z)) + } + } + } + } + + private fun slice(region: Region, atY: Int): Pair { + if (atY < region.size.y + 1) { + val first = Region(region.origin, region.size.withY(atY + 1)) + val second = Region(region.origin.withY(atY), region.size.addY(-atY-1)) + return first to second + } + return region to null + } + + fun upToAndDownUntil(y: Int) = create { region -> + val (bottom, top) = slice(region, y) + traverseUpward(bottom) + top?.let { traverseDownward(it) } + } + + } + + +} diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt index c375e5a..7e109c8 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt @@ -23,7 +23,7 @@ class Schematic { val size = region.size.also { _size = it } val data = arrayOfNulls(region.blockCount).also { _data = it } //val extra = mutableMapOf Unit>().also { extra = it } - val blocks = RegionTraversal.DOWNWARD.regionTraverser(region) + val blocks = RegionTraverser.downward.traverseRegion(region) for ((index, vec) in blocks.withIndex()) { markSuspensionPoint() @@ -39,7 +39,7 @@ class Schematic { fun getPasteTask(world: World, position: Vec3i): TimeLimitedTask = { if (!isLoaded) throw IllegalStateException() val region = Region(position, _size!!) - val blocks = RegionTraversal.DOWNWARD.regionTraverser(region) + val blocks = RegionTraverser.downward.traverseRegion(region) val data = _data!! for ((index, vec) in blocks.withIndex()) { diff --git a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt index 30eaabd..ea4db62 100644 --- a/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt +++ b/src/main/kotlin/io/dico/parcels2/blockvisitor/WorktimeLimiter.kt @@ -122,8 +122,14 @@ class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWo override fun submit(task: TimeLimitedTask): Worker { val worker: WorkerContinuation = WorkerImpl(plugin.functionHelper, task) + + if (bukkitTask == null) { + val completed = worker.resume(options.workTime.toLong()) + if (completed) return worker + bukkitTask = plugin.functionHelper.scheduleRepeating(0, options.tickInterval) { tickJobs() } + } + _workers.addFirst(worker) - if (bukkitTask == null) bukkitTask = plugin.functionHelper.scheduleRepeating(0, options.tickInterval) { tickJobs() } return worker } diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt index 3c5ba41..3ae17f2 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsDebug.kt @@ -5,7 +5,8 @@ import io.dico.dicore.command.EMessageType import io.dico.dicore.command.ExecutionContext import io.dico.dicore.command.annotation.Cmd import io.dico.parcels2.ParcelsPlugin -import io.dico.parcels2.blockvisitor.RegionTraversal +import io.dico.parcels2.blockvisitor.RegionTraverser +import io.dico.parcels2.doBlockOperation import org.bukkit.Bukkit import org.bukkit.Material import org.bukkit.entity.Player @@ -43,7 +44,7 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { ) val random = Random() - world.doBlockOperation(parcel.id, direction = RegionTraversal.UPWARD) { block -> + world.blockManager.doBlockOperation(parcel.id, traverser = RegionTraverser.upward) { block -> block.blockData = blockDatas[random.nextInt(7)] }.onProgressUpdate(1000, 1000) { progress, elapsedTime -> context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt index 439d653..d02c974 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandsGeneral.kt @@ -26,7 +26,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { val parcel = world.nextEmptyParcel() ?: error("This world is full, please ask an admin to upsize it") parcel.owner = PlayerProfile(uuid = player.uuid) - player.teleport(parcel.world.getHomeLocation(parcel.id)) + player.teleport(parcel.homeLocation) return "Enjoy your new parcel!" } @@ -63,7 +63,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { val match = target.getParcelSuspend(plugin.storage) ?: error("The specified parcel could not be matched") - player.teleport(match.world.getHomeLocation(match.id)) + player.teleport(match.homeLocation) return "" } @@ -100,7 +100,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) { if (!sure) return "Are you sure? You cannot undo this action!\n" + "Run \"/${context.rawInput} -sure\" if you want to go through with this." - world.clearParcel(parcel.id) + world.blockManager.clearParcel(parcel.id) .onProgressUpdate(1000, 1000) { progress, elapsedTime -> val alt = context.getFormat(EMessageType.NUMBER) val main = context.getFormat(EMessageType.INFORMATIVE) diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt index b633c3e..e2c7a1d 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommandBuilder.kt @@ -17,11 +17,12 @@ fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher { .addParameterType(true, ParcelTarget.PType(plugin.parcelProvider)) .group("parcel", "plot", "plots", "p") + .addRequiredPermission("parcels.command") .registerCommands(CommandsGeneral(plugin)) .registerCommands(CommandsAddedStatusLocal(plugin)) .group("option", "opt", "o") - //.apply { CommandsParcelOptions.setGroupDescription(this) } + .apply { CommandsParcelOptions.setGroupDescription(this) } .registerCommands(CommandsParcelOptions(plugin)) .parent() diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt index 7e75f52..244d38c 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt @@ -1,14 +1,13 @@ package io.dico.parcels2.defaultimpl import io.dico.parcels2.* -import io.dico.parcels2.blockvisitor.RegionTraversal +import io.dico.parcels2.blockvisitor.RegionTraverser import io.dico.parcels2.blockvisitor.Worker import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.options.DefaultGeneratorOptions import io.dico.parcels2.util.* import org.bukkit.* import org.bukkit.block.Biome -import org.bukkit.block.Block import org.bukkit.block.BlockFace import org.bukkit.block.Skull import org.bukkit.block.data.BlockData @@ -18,11 +17,13 @@ import java.util.Random private val airType = Bukkit.createBlockData(Material.AIR) -class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() { +private const val chunkSize = 16 + +class DefaultParcelGenerator(override val worldName: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() { private var _world: World? = null override val world: World get() { - if (_world == null) _world = Bukkit.getWorld(name)!!.also { + if (_world == null) _world = Bukkit.getWorld(worldName)!!.also { maxHeight = it.maxHeight return it } @@ -103,12 +104,10 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix) } - override fun makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager { - return ParcelBlockManagerImpl(worktimeLimiter) - } - - override fun makeParcelLocator(container: ParcelContainer): ParcelLocator { - return ParcelLocatorImpl(container) + override fun makeParcelLocatorAndBlockManager(worldId: ParcelWorldId, + container: ParcelContainer, + worktimeLimiter: WorktimeLimiter): Pair { + return ParcelLocatorImpl(worldId, container) to ParcelBlockManagerImpl(worldId, worktimeLimiter) } private inline fun convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? { @@ -124,22 +123,26 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp return null } - private inner class ParcelLocatorImpl(val container: ParcelContainer) : ParcelLocator { + private inner class ParcelLocatorImpl(val worldId: ParcelWorldId, + val container: ParcelContainer) : ParcelLocator { override val world: World = this@DefaultParcelGenerator.world + override fun getParcelAt(x: Int, z: Int): Parcel? { return convertBlockLocationToId(x, z, container::getParcelById) } override fun getParcelIdAt(x: Int, z: Int): ParcelId? { - return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(world.name, world.uid, idx, idz) } + return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(worldId, idx, idz) } } } @Suppress("DEPRECATION") - private inner class ParcelBlockManagerImpl(override val worktimeLimiter: WorktimeLimiter) : ParcelBlockManager { + private inner class ParcelBlockManagerImpl(val worldId: ParcelWorldId, + override val worktimeLimiter: WorktimeLimiter) : ParcelBlockManagerBase() { override val world: World = this@DefaultParcelGenerator.world + override val parcelTraverser: RegionTraverser = RegionTraverser.upToAndDownUntil(o.floorHeight) - override fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i( + /*override*/ fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i( sectionSize * (parcel.x - 1) + pathOffset + o.offsetX, sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ ) @@ -151,6 +154,11 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F) } + override fun getRegion(parcel: ParcelId): Region { + val bottom = getBottomBlock(parcel) + return Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize)) + } + override fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) { val b = getBottomBlock(parcel) @@ -203,9 +211,8 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp } override fun clearParcel(parcel: ParcelId): Worker = worktimeLimiter.submit { - val bottom = getBottomBlock(parcel) - val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize)) - val blocks = RegionTraversal.DOWNWARD.regionTraverser(region) + val region = getRegion(parcel) + val blocks = parcelTraverser.traverseRegion(region) val blockCount = region.blockCount.toDouble() val world = world @@ -227,17 +234,78 @@ class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOp } } - override fun doBlockOperation(parcel: ParcelId, direction: RegionTraversal, operation: (Block) -> Unit): Worker = worktimeLimiter.submit { - val bottom = getBottomBlock(parcel) - val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize)) - val blocks = direction.regionTraverser(region) - val blockCount = region.blockCount.toDouble() - val world = world + override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection { + /* + * Get the offsets for the world out of the way + * to simplify the calculation that follows. + */ + + val x = chunk.x.shl(4) - (o.offsetX + pathOffset) + val z = chunk.z.shl(4) - (o.offsetZ + pathOffset) + + /* Locations of wall corners (where owner blocks are placed) are defined as: + * + * x umod sectionSize == sectionSize-1 + * + * This check needs to be made for all 16 slices of the chunk in 2 dimensions + * How to optimize this? + * Let's take the expression + * + * x umod sectionSize + * + * And call it modX + * x can be shifted (chunkSize -1) times to attempt to get a modX of 0. + * This means that if the modX is 1, and sectionSize == (chunkSize-1), there would be a match at the last shift. + * To check that there are any matches, we can see if the following holds: + * + * modX >= ((sectionSize-1) - (chunkSize-1)) + * + * Which can be simplified to: + * modX >= sectionSize - chunkSize + * + * if sectionSize == chunkSize, this expression can be simplified to + * modX >= 0 + * which is always true. This is expected. + * To get the total number of matches on a dimension, we can evaluate the following: + * + * (modX - (sectionSize - chunkSize) + sectionSize) / sectionSize + * + * We add sectionSize to the lhs because, if the other part of the lhs is 0, we need at least 1. + * This can be simplified to: + * + * (modX + chunkSize) / sectionSize + */ + + val sectionSize = sectionSize + + val modX = x umod sectionSize + val matchesOnDimensionX = (modX + chunkSize) / sectionSize + if (matchesOnDimensionX <= 0) return emptyList() + + val modZ = z umod sectionSize + val matchesOnDimensionZ = (modZ + chunkSize) / sectionSize + if (matchesOnDimensionZ <= 0) return emptyList() + + /* + * Now we need to find the first id within the matches, + * and then return the subsequent matches in a rectangle following it. + * + * On each dimension, get the distance to the first match, which is equal to (sectionSize-1 - modX) + * and add it to the coordinate value + */ + val firstX = x + (sectionSize - 1 - modX) + val firstZ = z + (sectionSize - 1 - modZ) + + val firstIdX = (firstX + 1) / sectionSize + 1 + val firstIdZ = (firstZ + 1) / sectionSize + 1 + + if (matchesOnDimensionX == 1 && matchesOnDimensionZ == 1) { + // fast-path optimization + return listOf(Vec2i(firstIdX, firstIdZ)) + } - for ((index, vec) in blocks.withIndex()) { - markSuspensionPoint() - operation(world[vec]) - setProgress((index + 1) / blockCount) + return (0 until matchesOnDimensionX).flatMap { idOffsetX -> + (0 until matchesOnDimensionZ).map { idOffsetZ -> Vec2i(firstIdX + idOffsetX, firstIdZ + idOffsetZ) } } } diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt index c154955..7755363 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelImpl.kt @@ -47,13 +47,23 @@ class ParcelImpl(override val world: ParcelWorld, val globalAddedMap: AddedDataMap? get() = owner?.let { world.globalAddedData[it].addedMap } - override val since: DateTime? get() = data.since + override val lastClaimTime: DateTime? get() = data.lastClaimTime + + override var ownerSignOutdated: Boolean + get() = data.ownerSignOutdated + set(value) { + if (data.ownerSignOutdated != value) { + world.storage.setParcelOwnerSignOutdated(this, value) + data.ownerSignOutdated = value + } + } override var owner: PlayerProfile? get() = data.owner set(value) { if (data.owner != value) { world.storage.setParcelOwner(this, value) + world.blockManager.setOwnerBlock(this, value) data.owner = value } } diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt index 70d428d..0c1dadc 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelProviderImpl.kt @@ -1,8 +1,11 @@ package io.dico.parcels2.defaultimpl import io.dico.parcels2.* +import kotlinx.coroutines.experimental.Unconfined +import kotlinx.coroutines.experimental.launch import org.bukkit.Bukkit import org.bukkit.WorldCreator +import org.joda.time.DateTime class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { inline val options get() = plugin.options @@ -49,9 +52,24 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { if (parcelWorld != null) continue val generator: ParcelGenerator = getWorldGenerator(worldName)!! - val bukkitWorld = Bukkit.getWorld(worldName) ?: WorldCreator(worldName).generator(generator).createWorld() + val worldExists = Bukkit.getWorld(worldName) == null + val bukkitWorld = + if (worldExists) Bukkit.getWorld(worldName)!! + else WorldCreator(worldName).generator(generator).createWorld().also { logger.info("Creating world $worldName") } + parcelWorld = ParcelWorldImpl(bukkitWorld, generator, worldOptions.runtime, plugin.storage, plugin.globalAddedData, ::DefaultParcelContainer, plugin.worktimeLimiter) + + if (!worldExists) { + val time = DateTime.now() + plugin.storage.setWorldCreationTime(parcelWorld.id, time) + parcelWorld.creationTime = time + } else { + launch(context = Unconfined) { + parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?: DateTime.now() + } + } + _worlds[worldName] = parcelWorld } diff --git a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt index 4a168f5..a54e519 100644 --- a/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt +++ b/src/main/kotlin/io/dico/parcels2/defaultimpl/ParcelWorldImpl.kt @@ -7,21 +7,21 @@ import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.options.RuntimeWorldOptions import io.dico.parcels2.storage.Storage import org.bukkit.World +import org.joda.time.DateTime import java.util.UUID -class ParcelWorldImpl private -constructor(override val world: World, - override val generator: ParcelGenerator, - override var options: RuntimeWorldOptions, - override val storage: Storage, - override val globalAddedData: GlobalAddedDataManager, - containerFactory: ParcelContainerFactory, - blockManager: ParcelBlockManager) +class ParcelWorldImpl(override val world: World, + override val generator: ParcelGenerator, + override var options: RuntimeWorldOptions, + override val storage: Storage, + override val globalAddedData: GlobalAddedDataManager, + containerFactory: ParcelContainerFactory, + worktimeLimiter: WorktimeLimiter) : ParcelWorld, ParcelWorldId, - ParcelContainer, // missing delegation - ParcelLocator, // missing delegation - ParcelBlockManager by blockManager { + ParcelContainer, /* missing delegation */ + ParcelLocator /* missing delegation */ { + override val id: ParcelWorldId get() = this override val uid: UUID? get() = world.uid @@ -33,10 +33,14 @@ constructor(override val world: World, override val name: String = world.name!! override val container: ParcelContainer = containerFactory(this) - override val locator: ParcelLocator = generator.makeParcelLocator(container) - override val blockManager: ParcelBlockManager = blockManager + override val locator: ParcelLocator + override val blockManager: ParcelBlockManager init { + val pair = generator.makeParcelLocatorAndBlockManager(id, container, worktimeLimiter) + locator = pair.first + blockManager = pair.second + enforceOptions() } @@ -55,24 +59,13 @@ constructor(override val world: World, world.setGameRuleValue("doTileDrops", "${options.doTileDrops}") } + // Updated by ParcelProviderImpl + override var creationTime: DateTime? = null + /* Interface delegation needs to be implemented manually because JetBrains has yet to fix it. */ - companion object { - // Use this to be able to delegate blockManager and assign it to a property too, at least. - operator fun invoke(world: World, - generator: ParcelGenerator, - options: RuntimeWorldOptions, - storage: Storage, - globalAddedData: GlobalAddedDataManager, - containerFactory: ParcelContainerFactory, - worktimeLimiter: WorktimeLimiter): ParcelWorldImpl { - val blockManager = generator.makeParcelBlockManager(worktimeLimiter) - return ParcelWorldImpl(world, generator, options, storage, globalAddedData, containerFactory, blockManager) - } - } - // ParcelLocator interface override fun getParcelAt(x: Int, z: Int): Parcel? { return locator.getParcelAt(x, z) diff --git a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt index fa5ccb4..eedc416 100644 --- a/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt +++ b/src/main/kotlin/io/dico/parcels2/listener/ParcelListeners.kt @@ -7,6 +7,7 @@ import io.dico.parcels2.Parcel import io.dico.parcels2.ParcelProvider import io.dico.parcels2.ParcelWorld import io.dico.parcels2.statusKey +import io.dico.parcels2.storage.Storage import io.dico.parcels2.util.* import org.bukkit.Material.* import org.bukkit.World @@ -16,6 +17,7 @@ import org.bukkit.block.data.Directional import org.bukkit.block.data.type.Bed import org.bukkit.entity.* import org.bukkit.entity.minecart.ExplosiveMinecart +import org.bukkit.event.EventPriority import org.bukkit.event.EventPriority.NORMAL import org.bukkit.event.block.* import org.bukkit.event.entity.* @@ -26,11 +28,14 @@ import org.bukkit.event.inventory.InventoryInteractEvent import org.bukkit.event.player.* import org.bukkit.event.vehicle.VehicleMoveEvent import org.bukkit.event.weather.WeatherChangeEvent +import org.bukkit.event.world.ChunkLoadEvent import org.bukkit.event.world.StructureGrowEvent import org.bukkit.inventory.InventoryHolder @Suppress("NOTHING_TO_INLINE") -class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: ParcelEntityTracker) { +class ParcelListeners(val parcelProvider: ParcelProvider, + val entityTracker: ParcelEntityTracker, + val storage: Storage) { private inline fun Parcel?.canBuildN(user: Player) = isPresentAnd { canBuild(user) } || user.hasBuildAnywhere /** @@ -54,7 +59,7 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par val parcel = parcelProvider.getParcelAt(event.to) ?: return@l if (parcel.isBanned(user.statusKey)) { parcelProvider.getParcelAt(event.from)?.also { - user.teleport(it.world.getHomeLocation(it.id)) + user.teleport(it.homeLocation) user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel") } ?: run { event.to = event.from } } @@ -575,4 +580,26 @@ class ParcelListeners(val parcelProvider: ParcelProvider, val entityTracker: Par } } + /** + * Updates owner signs of parcels that get loaded if it is marked outdated + */ + @ListenerMarker(priority = EventPriority.NORMAL) + val onChunkLoadEvent = RegistratorListener l@{ event -> + val world = parcelProvider.getWorld(event.chunk.world) ?: return@l + val parcels = world.blockManager.getParcelsWithOwnerBlockIn(event.chunk) + if (parcels.isEmpty()) return@l + + parcels.forEach { id -> + val parcel = world.getParcelById(id)?.takeIf { it.ownerSignOutdated } ?: return@forEach + world.blockManager.setOwnerBlock(parcel.id, parcel.owner) + parcel.ownerSignOutdated = false + } + + } + + @ListenerMarker + val onPlayerJoinEvent = RegistratorListener l@{ event -> + storage.updatePlayerName(event.player.uuid, event.player.name) + } + } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt index b658d10..6bef483 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Backing.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Backing.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.experimental.Deferred import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.channels.ReceiveChannel import kotlinx.coroutines.experimental.channels.SendChannel +import org.joda.time.DateTime import java.util.UUID interface Backing { @@ -30,8 +31,14 @@ interface Backing { fun shutdown() + fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? + + fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) + fun getPlayerUuidForName(name: String): UUID? + fun updatePlayerName(uuid: UUID, name: String) + fun transmitParcelData(channel: SendChannel, parcels: Sequence) fun transmitAllParcelData(channel: SendChannel) @@ -47,6 +54,8 @@ interface Backing { fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) + fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean) + fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) diff --git a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt index 2116b46..a0e94e0 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/Storage.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/Storage.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.channels.ReceiveChannel import kotlinx.coroutines.experimental.channels.SendChannel import kotlinx.coroutines.experimental.launch +import org.joda.time.DateTime import java.util.UUID typealias DataPair = Pair @@ -22,8 +23,14 @@ interface Storage { fun shutdown(): Job + fun getWorldCreationTime(worldId: ParcelWorldId): Deferred + + fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job + fun getPlayerUuidForName(name: String): Deferred + fun updatePlayerName(uuid: UUID, name: String): Job + fun readParcelData(parcel: ParcelId): Deferred fun transmitParcelData(parcels: Sequence): ReceiveChannel @@ -39,6 +46,8 @@ interface Storage { fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job + fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job + fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus): Job fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): Job @@ -65,8 +74,14 @@ class BackedStorage internal constructor(val b: Backing) : Storage { override fun shutdown() = launch(b.dispatcher) { b.shutdown() } + override fun getWorldCreationTime(worldId: ParcelWorldId): Deferred = b.launchFuture { b.getWorldCreationTime(worldId) } + + override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job = b.launchJob { b.setWorldCreationTime(worldId, time) } + override fun getPlayerUuidForName(name: String): Deferred = b.launchFuture { b.getPlayerUuidForName(name) } + override fun updatePlayerName(uuid: UUID, name: String): Job = b.launchJob { b.updatePlayerName(uuid, name) } + override fun readParcelData(parcel: ParcelId) = b.launchFuture { b.readParcelData(parcel) } override fun transmitParcelData(parcels: Sequence) = b.openChannel { b.transmitParcelData(it, parcels) } @@ -81,6 +96,8 @@ class BackedStorage internal constructor(val b: Backing) : Storage { override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) = b.launchJob { b.setParcelOwner(parcel, owner) } + override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job = b.launchJob { b.setParcelOwnerSignOutdated(parcel, outdated) } + override fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) = b.launchJob { b.setLocalPlayerStatus(parcel, player, status) } override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) = b.launchJob { b.setParcelAllowsInteractInventory(parcel, value) } 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 11d26c4..8cd2804 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/ExposedBacking.kt @@ -4,10 +4,12 @@ package io.dico.parcels2.storage.exposed import com.zaxxer.hikari.HikariDataSource import io.dico.parcels2.* +import io.dico.parcels2.PlayerProfile.Star.name import io.dico.parcels2.storage.AddedDataPair import io.dico.parcels2.storage.Backing import io.dico.parcels2.storage.DataPair import io.dico.parcels2.util.synchronized +import io.dico.parcels2.util.toByteArray import io.dico.parcels2.util.toUUID import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.channels.ArrayChannel @@ -114,11 +116,28 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi else -> throw InternalError("Case should not be reached") } + + override fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? { + return WorldsT.getWorldCreationTime(worldId) + } + + override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) { + WorldsT.setWorldCreationTime(worldId, time) + } + override fun getPlayerUuidForName(name: String): UUID? { return ProfilesT.slice(ProfilesT.uuid).select { ProfilesT.name.upperCase() eq name.toUpperCase() } .firstOrNull()?.let { it[ProfilesT.uuid]?.toUUID() } } + override fun updatePlayerName(uuid: UUID, name: String) { + val binaryUuid = uuid.toByteArray() + ProfilesT.upsert(ProfilesT.uuid) { + it[ProfilesT.uuid] = binaryUuid + it[ProfilesT.name] = name + } + } + override fun transmitParcelData(channel: SendChannel, parcels: Sequence) { for (parcel in parcels) { val data = readParcelData(parcel) @@ -193,6 +212,14 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi ParcelsT.update({ ParcelsT.id eq id }) { it[ParcelsT.owner_id] = owner_id it[claim_time] = time + it[sign_oudated] = false + } + } + + override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean) { + val id = ParcelsT.getId(parcel) ?: return + ParcelsT.update({ ParcelsT.id eq id }) { + it[sign_oudated] = outdated } } @@ -203,16 +230,16 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) { val id = ParcelsT.getOrInitId(parcel) ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { - it[ParcelOptionsT.parcel_id] = id - it[ParcelOptionsT.interact_inventory] = value + it[parcel_id] = id + it[interact_inventory] = value } } override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) { val id = ParcelsT.getOrInitId(parcel) ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { - it[ParcelOptionsT.parcel_id] = id - it[ParcelOptionsT.interact_inputs] = value + it[parcel_id] = id + it[interact_inputs] = value } } @@ -231,7 +258,8 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) } - since = row[ParcelsT.claim_time] + lastClaimTime = row[ParcelsT.claim_time] + ownerSignOutdated = row[ParcelsT.sign_oudated] val id = row[ParcelsT.id] ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow -> 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 98e209d..d8315fd 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt @@ -9,6 +9,7 @@ import io.dico.parcels2.util.toByteArray import io.dico.parcels2.util.toUUID import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.statements.UpdateBuilder +import org.joda.time.DateTime import java.util.UUID sealed class IdTransactionsTable, QueryObj>(tableName: String, columnName: String) @@ -24,7 +25,8 @@ sealed class IdTransactionsTable, } internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int { - return getId() ?: table.insertIgnore(body)[id] ?: getId() ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its id") + return getId() ?: table.insertIgnore(body)[id] ?: getId() + ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its id") } abstract fun getId(obj: QueryObj): Int? @@ -35,9 +37,10 @@ sealed class IdTransactionsTable, fun getId(obj: QueryObj, init: Boolean): Int? = if (init) getOrInitId(obj) else getId(obj) } -object WorldsT : IdTransactionsTable("parcel_worlds", "world_id") { +object WorldsT : IdTransactionsTable("parcels_worlds", "world_id") { val name = varchar("name", 50) val uid = binary("uid", 16).nullable() + val creation_time = datetime("creation_time").nullable() val index_name = uniqueIndexR("index_name", name) val index_uid = uniqueIndexR("index_uid", uid) @@ -56,6 +59,18 @@ object WorldsT : IdTransactionsTable("parcel_worlds", "w override fun getItem(row: ResultRow): ParcelWorldId { return ParcelWorldId(row[name], row[uid]?.toUUID()) } + + fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? { + val id = getId(worldId) ?: return null + return select { WorldsT.id eq id }.firstOrNull()?.let { it[WorldsT.creation_time] } + } + + fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) { + val id = getOrInitId(worldId) + update({ WorldsT.id eq id }) { + it[WorldsT.creation_time] = time + } + } } object ParcelsT : IdTransactionsTable("parcels", "parcel_id") { @@ -63,6 +78,7 @@ object ParcelsT : IdTransactionsTable("parcels", "parcel_id" val px = integer("px") val pz = integer("pz") val owner_id = integer("owner_id").references(ProfilesT.id).nullable() + val sign_oudated = bool("sign_outdated").default(false) val claim_time = datetime("claim_time").nullable() val index_location = uniqueIndexR("index_location", world_id, px, pz) @@ -89,7 +105,7 @@ object ParcelsT : IdTransactionsTable("parcels", "parcel_id" } } -object ProfilesT : IdTransactionsTable("parcel_profiles", "owner_id") { +object ProfilesT : IdTransactionsTable("parcels_profiles", "owner_id") { val uuid = binary("uuid", 16).nullable() val name = varchar("name", 32).nullable() @@ -103,7 +119,8 @@ object ProfilesT : IdTransactionsTable("parcel_profile 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)" }) diff --git a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt index f0c0cd8..9921268 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/migration/plotme/PlotmeMigration.kt @@ -83,6 +83,7 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration { val parcel = getParcelId(PlotmePlotsT, row) ?: return@forEach val owner = PlayerProfile.safe(row[PlotmePlotsT.owner_uuid]?.toUUID(), row[PlotmePlotsT.owner_name]) target.setParcelOwner(parcel, owner) + target.setParcelOwnerSignOutdated(parcel, true) } } diff --git a/src/main/kotlin/io/dico/parcels2/util/Region.kt b/src/main/kotlin/io/dico/parcels2/util/Region.kt index 5717906..f786693 100644 --- a/src/main/kotlin/io/dico/parcels2/util/Region.kt +++ b/src/main/kotlin/io/dico/parcels2/util/Region.kt @@ -2,4 +2,12 @@ package io.dico.parcels2.util data class Region(val origin: Vec3i, val size: Vec3i) { val blockCount: Int get() = size.x * size.y * size.z + + val center: Vec3d + get() { + val x = (origin.x + size.x) / 2.0 + val y = (origin.y + size.y) / 2.0 + val z = (origin.z + size.z) / 2.0 + return Vec3d(x, y, z) + } } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/Vec2i.kt b/src/main/kotlin/io/dico/parcels2/util/Vec2i.kt index 933c18b..62ac97f 100644 --- a/src/main/kotlin/io/dico/parcels2/util/Vec2i.kt +++ b/src/main/kotlin/io/dico/parcels2/util/Vec2i.kt @@ -5,3 +5,7 @@ data class Vec2i( val z: Int ) +data class Region2i( + val bottom: Vec2i, + val top: Vec2i +) \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt index 6db98af..ded1e0c 100644 --- a/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt +++ b/src/main/kotlin/io/dico/parcels2/util/Vec3i.kt @@ -3,6 +3,22 @@ package io.dico.parcels2.util import org.bukkit.World import org.bukkit.block.Block +data class Vec3d( + val x: Double, + val y: Double, + val z: Double +) { + operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z) + operator fun minus(o: Vec3i) = Vec3d(x - o.x, y - o.y, z - o.z) + infix fun addX(o: Double) = Vec3d(x + o, y, z) + infix fun addY(o: Double) = Vec3d(x, y + o, z) + infix fun addZ(o: Double) = Vec3d(x, y, z + o) + infix fun withX(o: Double) = Vec3d(o, y, z) + infix fun withY(o: Double) = Vec3d(x, o, z) + infix fun withZ(o: Double) = Vec3d(x, y, o) + fun add(ox: Double, oy: Double, oz: Double) = Vec3d(x + ox, y + oy, z + oz) +} + data class Vec3i( val x: Int, val y: Int, @@ -12,6 +28,9 @@ data class Vec3i( infix fun addX(o: Int) = Vec3i(x + o, y, z) infix fun addY(o: Int) = Vec3i(x, y + o, z) infix fun addZ(o: Int) = Vec3i(x, y, z + o) + infix fun withX(o: Int) = Vec3i(o, y, z) + infix fun withY(o: Int) = Vec3i(x, o, z) + infix fun withZ(o: Int) = Vec3i(x, y, o) fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz) } diff --git a/todo.md b/todo.md index a3d98ed..541aecf 100644 --- a/todo.md +++ b/todo.md @@ -17,8 +17,8 @@ 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). +~~Add permissions to commands (replace or fix `IContextFilter` from command lib +to allow inheriting permissions properly).~~ Parcel Options - @@ -39,8 +39,8 @@ This could become optional. 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. +~~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. @@ -50,9 +50,9 @@ Swap - schematic is in place, but proper placement order must be enforced to mak 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. +~~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. +layers are done upwards, and the rest downwards.~~ Events - -- cgit v1.2.3