Work on a couple of the todos

This commit is contained in:
Dico
2018-08-12 18:07:43 +01:00
parent 957d6f2434
commit 5bd0970c54
32 changed files with 503 additions and 148 deletions
@@ -1,5 +1,6 @@
package io.dico.dicore.command; package io.dico.dicore.command;
import io.dico.dicore.command.predef.DefaultGroupCommand;
import io.dico.dicore.command.predef.HelpCommand; import io.dico.dicore.command.predef.HelpCommand;
import java.util.*; import java.util.*;
@@ -23,7 +24,7 @@ public class ChildCommandAddress extends ModifiableCommandAddress {
} }
public static ChildCommandAddress newPlaceHolderCommand(String name, String... aliases) { 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); HelpCommand.registerAsChild(rv);
return rv; return rv;
} }
@@ -210,15 +210,39 @@ public final class CommandBuilder {
* @param shortDescription a short description * @param shortDescription a short description
* @param description the lines of a full description. * @param description the lines of a full description.
* @return this * @return this
* @throws IllegalStateException if the current group has no command
*/ */
public CommandBuilder setGroupDescription(String shortDescription, String... description) { public CommandBuilder setGroupDescription(String shortDescription, String... description) {
Command command = cur.getCommand(); Command command = cur.getCommand();
if (command == null) throw new IllegalStateException();
cur.setCommand(command cur.setCommand(command
.setShortDescription(shortDescription) .setShortDescription(shortDescription)
.setDescription(description)); .setDescription(description));
return this; 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 * Jump up a level in the address
* *
@@ -2,6 +2,7 @@ package io.dico.dicore.command;
import io.dico.dicore.command.chat.ChatControllers; import io.dico.dicore.command.chat.ChatControllers;
import io.dico.dicore.command.chat.IChatController; 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.HelpCommand;
import io.dico.dicore.command.predef.PredefinedCommand; import io.dico.dicore.command.predef.PredefinedCommand;
@@ -32,7 +33,7 @@ public abstract class ModifiableCommandAddress implements ICommandAddress {
@Override @Override
public boolean hasUserDeclaredCommand() { public boolean hasUserDeclaredCommand() {
Command command = getCommand(); Command command = getCommand();
return command != null && !(command instanceof PredefinedCommand); return command != null && !(command instanceof PredefinedCommand) && !(command instanceof DefaultGroupCommand);
} }
@Override @Override
@@ -1,6 +1,7 @@
package io.dico.dicore.command; package io.dico.dicore.command;
import io.dico.dicore.command.parameter.ArgumentBuffer; import io.dico.dicore.command.parameter.ArgumentBuffer;
import io.dico.dicore.command.predef.DefaultGroupCommand;
import io.dico.dicore.command.registration.BukkitCommand; import io.dico.dicore.command.registration.BukkitCommand;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -167,10 +168,10 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
ModifiableCommandAddress targetAddress = getCommandTarget(sender, buffer); ModifiableCommandAddress targetAddress = getCommandTarget(sender, buffer);
Command target = targetAddress.getCommand(); Command target = targetAddress.getCommand();
if (target == null) { if (target == null || target instanceof DefaultGroupCommand) {
if (targetAddress.hasHelpCommand()) { if (targetAddress.hasHelpCommand()) {
target = targetAddress.getHelpCommand().getCommand(); target = targetAddress.getHelpCommand().getCommand();
} else { } else if (target == null){
return false; return false;
} }
} }
@@ -7,6 +7,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/**
* If this annotation is not present, inheriting permissions is default.
*/
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
public @interface RequirePermissions { public @interface RequirePermissions {
@@ -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;
}
}
+7 -2
View File
@@ -2,6 +2,7 @@ package io.dico.parcels2
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.hasBuildAnywhere import io.dico.parcels2.util.hasBuildAnywhere
import org.bukkit.Location
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.joda.time.DateTime import org.joda.time.DateTime
@@ -30,11 +31,14 @@ interface Parcel : ParcelData {
fun copyData(data: ParcelData) fun copyData(data: ParcelData)
fun dispose() fun dispose()
val homeLocation: Location get() = world.blockManager.getHomeLocation(id)
} }
interface ParcelData : AddedData { interface ParcelData : AddedData {
var owner: PlayerProfile? var owner: PlayerProfile?
val since: DateTime? val lastClaimTime: DateTime?
var ownerSignOutdated: Boolean
fun canBuild(player: OfflinePlayer, checkAdmin: Boolean = true, checkGlobal: Boolean = true): 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 { class ParcelDataHolder(addedMap: MutableAddedDataMap = mutableMapOf()) : AddedDataHolder(addedMap), ParcelData {
override var owner: PlayerProfile? = null 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) override fun canBuild(player: OfflinePlayer, checkAdmin: Boolean, checkGlobal: Boolean) = isAllowed(player.statusKey)
|| owner.let { it != null && it.matches(player, allowNameMatch = false) } || owner.let { it != null && it.matches(player, allowNameMatch = false) }
|| (checkAdmin && player is Player && player.hasBuildAnywhere) || (checkAdmin && player is Player && player.hasBuildAnywhere)
@@ -1,9 +1,12 @@
package io.dico.parcels2 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.Worker
import io.dico.parcels2.blockvisitor.WorkerScope
import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.util.Region
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.get
import org.bukkit.Chunk import org.bukkit.Chunk
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.World import org.bukkit.World
@@ -15,6 +18,8 @@ import org.bukkit.generator.ChunkGenerator
import java.util.Random import java.util.Random
abstract class ParcelGenerator : ChunkGenerator() { abstract class ParcelGenerator : ChunkGenerator() {
abstract val worldName: String
abstract val world: World abstract val world: World
abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData 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 makeParcelLocatorAndBlockManager(worldId: ParcelWorldId,
container: ParcelContainer,
abstract fun makeParcelLocator(container: ParcelContainer): ParcelLocator worktimeLimiter: WorktimeLimiter): Pair<ParcelLocator, ParcelBlockManager>
} }
@Suppress("DeprecatedCallableAddReplaceWith")
interface ParcelBlockManager { interface ParcelBlockManager {
val world: World val world: World
val worktimeLimiter: WorktimeLimiter val worktimeLimiter: WorktimeLimiter
val parcelTraverser: RegionTraverser
fun getBottomBlock(parcel: ParcelId): Vec2i // fun getBottomBlock(parcel: ParcelId): Vec2i
fun getHomeLocation(parcel: ParcelId): Location fun getHomeLocation(parcel: ParcelId): Location
fun getRegion(parcel: ParcelId): Region
fun getEntities(parcel: ParcelId): Collection<Entity>
fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?)
@Deprecated("")
fun getEntities(parcel: ParcelId): Collection<Entity> = TODO()
@Deprecated("")
fun getBlocks(parcel: ParcelId, yRange: IntRange = 0..255): Iterator<Block> = TODO()
fun setBiome(parcel: ParcelId, biome: Biome): Worker fun setBiome(parcel: ParcelId, biome: Biome): Worker
fun clearParcel(parcel: ParcelId): 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<Vec2i>
}
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<Entity> {
val region = getRegion(parcel)
val center = region.center
val centerLoc = Location(world, center.x, center.y, center.z)
val centerDist = (center - region.origin).add(0.2, 0.2, 0.2)
return world.getNearbyEntities(centerLoc, centerDist.x, centerDist.y, centerDist.z)
}
} }
@@ -4,10 +4,12 @@ import io.dico.parcels2.options.RuntimeWorldOptions
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.Vec2i import io.dico.parcels2.util.Vec2i
import io.dico.parcels2.util.floor import io.dico.parcels2.util.floor
import org.bukkit.Chunk
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.World import org.bukkit.World
import org.bukkit.block.Block import org.bukkit.block.Block
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
import org.joda.time.DateTime
import java.util.UUID import java.util.UUID
interface ParcelProvider { interface ParcelProvider {
@@ -58,7 +60,6 @@ interface ParcelLocator {
fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world } 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 } fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world }
} }
typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
@@ -73,7 +74,7 @@ interface ParcelContainer {
} }
interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager { interface ParcelWorld : ParcelLocator, ParcelContainer {
val id: ParcelWorldId val id: ParcelWorldId
val name: String val name: String
val uid: UUID? val uid: UUID?
@@ -84,4 +85,7 @@ interface ParcelWorld : ParcelLocator, ParcelContainer, ParcelBlockManager {
val locator: ParcelLocator val locator: ParcelLocator
val blockManager: ParcelBlockManager val blockManager: ParcelBlockManager
val globalAddedData: GlobalAddedDataManager val globalAddedData: GlobalAddedDataManager
val creationTime: DateTime?
} }
@@ -21,6 +21,7 @@ import org.bukkit.plugin.java.JavaPlugin
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File import java.io.File
import java.util.Random
val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin") val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
private inline val plogger get() = logger private inline val plogger get() = logger
@@ -49,7 +50,15 @@ class ParcelsPlugin : JavaPlugin() {
} }
override fun onDisable() { 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() worktimeLimiter.completeAllTasks()
if (hasWorkers) {
plogger.info("Parcels has completed the remaining jobs.")
}
cmdDispatcher?.unregisterFromCommandMap() cmdDispatcher?.unregisterFromCommandMap()
} }
@@ -124,7 +133,7 @@ class ParcelsPlugin : JavaPlugin() {
private fun registerListeners() { private fun registerListeners() {
if (listeners == null) { if (listeners == null) {
listeners = ParcelListeners(parcelProvider, entityTracker) listeners = ParcelListeners(parcelProvider, entityTracker, storage)
registrator.registerListeners(listeners!!) registrator.registerListeners(listeners!!)
} }
@@ -77,7 +77,8 @@ interface PlayerProfile {
interface Real : PlayerProfile { interface Real : PlayerProfile {
override val uuid: UUID override val uuid: UUID
override val nameOrBukkitName: String? 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 override val notNullName: String
get() = name ?: getPlayerNameOrDefault(uuid) get() = name ?: getPlayerNameOrDefault(uuid)
@@ -1,43 +0,0 @@
package io.dico.parcels2.blockvisitor
import io.dico.parcels2.util.Region
import io.dico.parcels2.util.Vec3i
import kotlin.coroutines.experimental.SequenceBuilder
import kotlin.coroutines.experimental.buildIterator
enum class RegionTraversal(private val builder: suspend SequenceBuilder<Vec3i>.(Region) -> Unit) {
DOWNWARD({ region ->
val origin = region.origin
val size = region.size
repeat(size.y) { y ->
repeat(size.z) { z ->
repeat(size.x) { x ->
yield(origin.add(x, size.y - y - 1, z))
}
}
}
}),
UPWARD({ region ->
val origin = region.origin
val size = region.size
repeat(size.y) { y ->
repeat(size.z) { z ->
repeat(size.x) { x ->
yield(origin.add(x, y, z))
}
}
}
}),
;
fun regionTraverser(region: Region) = Iterable { buildIterator { builder(region) } }
}
@@ -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<Vec3i> = Iterable { buildIterator { build(region) } }
protected abstract suspend fun SequenceBuilder<Vec3i>.build(region: Region)
companion object {
val upward = create { traverseUpward(it) }
val downward = create { traverseDownward(it) }
val forClearing get() = downward
val forFilling get() = upward
inline fun create(crossinline builder: suspend SequenceBuilder<Vec3i>.(Region) -> Unit) = object : RegionTraverser() {
override suspend fun SequenceBuilder<Vec3i>.build(region: Region) {
builder(region)
}
}
private suspend fun SequenceBuilder<Vec3i>.traverseDownward(region: Region) {
val origin = region.origin
val size = region.size
repeat(size.y) { y ->
repeat(size.z) { z ->
repeat(size.x) { x ->
yield(origin.add(x, size.y - y - 1, z))
}
}
}
}
private suspend fun SequenceBuilder<Vec3i>.traverseUpward(region: Region) {
val origin = region.origin
val size = region.size
repeat(size.y) { y ->
repeat(size.z) { z ->
repeat(size.x) { x ->
yield(origin.add(x, size.y - y - 1, z))
}
}
}
}
private fun slice(region: Region, atY: Int): Pair<Region, Region?> {
if (atY < region.size.y + 1) {
val first = Region(region.origin, region.size.withY(atY + 1))
val second = Region(region.origin.withY(atY), region.size.addY(-atY-1))
return first to second
}
return region to null
}
fun upToAndDownUntil(y: Int) = create { region ->
val (bottom, top) = slice(region, y)
traverseUpward(bottom)
top?.let { traverseDownward(it) }
}
}
}
@@ -23,7 +23,7 @@ class Schematic {
val size = region.size.also { _size = it } val size = region.size.also { _size = it }
val data = arrayOfNulls<BlockData>(region.blockCount).also { _data = it } val data = arrayOfNulls<BlockData>(region.blockCount).also { _data = it }
//val extra = mutableMapOf<Vec3i, (Block) -> Unit>().also { extra = it } //val extra = mutableMapOf<Vec3i, (Block) -> Unit>().also { extra = it }
val blocks = RegionTraversal.DOWNWARD.regionTraverser(region) val blocks = RegionTraverser.downward.traverseRegion(region)
for ((index, vec) in blocks.withIndex()) { for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint() markSuspensionPoint()
@@ -39,7 +39,7 @@ class Schematic {
fun getPasteTask(world: World, position: Vec3i): TimeLimitedTask = { fun getPasteTask(world: World, position: Vec3i): TimeLimitedTask = {
if (!isLoaded) throw IllegalStateException() if (!isLoaded) throw IllegalStateException()
val region = Region(position, _size!!) val region = Region(position, _size!!)
val blocks = RegionTraversal.DOWNWARD.regionTraverser(region) val blocks = RegionTraverser.downward.traverseRegion(region)
val data = _data!! val data = _data!!
for ((index, vec) in blocks.withIndex()) { for ((index, vec) in blocks.withIndex()) {
@@ -122,8 +122,14 @@ class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWo
override fun submit(task: TimeLimitedTask): Worker { override fun submit(task: TimeLimitedTask): Worker {
val worker: WorkerContinuation = WorkerImpl(plugin.functionHelper, task) 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) _workers.addFirst(worker)
if (bukkitTask == null) bukkitTask = plugin.functionHelper.scheduleRepeating(0, options.tickInterval) { tickJobs() }
return worker return worker
} }
@@ -5,7 +5,8 @@ import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.annotation.Cmd import io.dico.dicore.command.annotation.Cmd
import io.dico.parcels2.ParcelsPlugin 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.Bukkit
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.entity.Player import org.bukkit.entity.Player
@@ -43,7 +44,7 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
) )
val random = Random() 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)] block.blockData = blockDatas[random.nextInt(7)]
}.onProgressUpdate(1000, 1000) { progress, elapsedTime -> }.onProgressUpdate(1000, 1000) { progress, elapsedTime ->
context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed" context.sendMessage(EMessageType.INFORMATIVE, "Mess progress: %.02f%%, %.2fs elapsed"
@@ -26,7 +26,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
val parcel = world.nextEmptyParcel() val parcel = world.nextEmptyParcel()
?: error("This world is full, please ask an admin to upsize it") ?: error("This world is full, please ask an admin to upsize it")
parcel.owner = PlayerProfile(uuid = player.uuid) parcel.owner = PlayerProfile(uuid = player.uuid)
player.teleport(parcel.world.getHomeLocation(parcel.id)) player.teleport(parcel.homeLocation)
return "Enjoy your new parcel!" return "Enjoy your new parcel!"
} }
@@ -63,7 +63,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
val match = target.getParcelSuspend(plugin.storage) val match = target.getParcelSuspend(plugin.storage)
?: error("The specified parcel could not be matched") ?: error("The specified parcel could not be matched")
player.teleport(match.world.getHomeLocation(match.id)) player.teleport(match.homeLocation)
return "" return ""
} }
@@ -100,7 +100,7 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
if (!sure) return "Are you sure? You cannot undo this action!\n" + if (!sure) return "Are you sure? You cannot undo this action!\n" +
"Run \"/${context.rawInput} -sure\" if you want to go through with this." "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 -> .onProgressUpdate(1000, 1000) { progress, elapsedTime ->
val alt = context.getFormat(EMessageType.NUMBER) val alt = context.getFormat(EMessageType.NUMBER)
val main = context.getFormat(EMessageType.INFORMATIVE) val main = context.getFormat(EMessageType.INFORMATIVE)
@@ -17,11 +17,12 @@ fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher {
.addParameterType(true, ParcelTarget.PType(plugin.parcelProvider)) .addParameterType(true, ParcelTarget.PType(plugin.parcelProvider))
.group("parcel", "plot", "plots", "p") .group("parcel", "plot", "plots", "p")
.addRequiredPermission("parcels.command")
.registerCommands(CommandsGeneral(plugin)) .registerCommands(CommandsGeneral(plugin))
.registerCommands(CommandsAddedStatusLocal(plugin)) .registerCommands(CommandsAddedStatusLocal(plugin))
.group("option", "opt", "o") .group("option", "opt", "o")
//.apply { CommandsParcelOptions.setGroupDescription(this) } .apply { CommandsParcelOptions.setGroupDescription(this) }
.registerCommands(CommandsParcelOptions(plugin)) .registerCommands(CommandsParcelOptions(plugin))
.parent() .parent()
@@ -1,14 +1,13 @@
package io.dico.parcels2.defaultimpl package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* 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.Worker
import io.dico.parcels2.blockvisitor.WorktimeLimiter import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.options.DefaultGeneratorOptions import io.dico.parcels2.options.DefaultGeneratorOptions
import io.dico.parcels2.util.* import io.dico.parcels2.util.*
import org.bukkit.* import org.bukkit.*
import org.bukkit.block.Biome import org.bukkit.block.Biome
import org.bukkit.block.Block
import org.bukkit.block.BlockFace import org.bukkit.block.BlockFace
import org.bukkit.block.Skull import org.bukkit.block.Skull
import org.bukkit.block.data.BlockData import org.bukkit.block.data.BlockData
@@ -18,11 +17,13 @@ import java.util.Random
private val airType = Bukkit.createBlockData(Material.AIR) 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 private var _world: World? = null
override val world: World override val world: World
get() { get() {
if (_world == null) _world = Bukkit.getWorld(name)!!.also { if (_world == null) _world = Bukkit.getWorld(worldName)!!.also {
maxHeight = it.maxHeight maxHeight = it.maxHeight
return it 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) return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix)
} }
override fun makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager { override fun makeParcelLocatorAndBlockManager(worldId: ParcelWorldId,
return ParcelBlockManagerImpl(worktimeLimiter) container: ParcelContainer,
} worktimeLimiter: WorktimeLimiter): Pair<ParcelLocator, ParcelBlockManager> {
return ParcelLocatorImpl(worldId, container) to ParcelBlockManagerImpl(worldId, worktimeLimiter)
override fun makeParcelLocator(container: ParcelContainer): ParcelLocator {
return ParcelLocatorImpl(container)
} }
private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? { private inline fun <T> 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 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 val world: World = this@DefaultParcelGenerator.world
override fun getParcelAt(x: Int, z: Int): Parcel? { override fun getParcelAt(x: Int, z: Int): Parcel? {
return convertBlockLocationToId(x, z, container::getParcelById) return convertBlockLocationToId(x, z, container::getParcelById)
} }
override fun getParcelIdAt(x: Int, z: Int): ParcelId? { 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") @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 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.x - 1) + pathOffset + o.offsetX,
sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ 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) 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?) { override fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) {
val b = getBottomBlock(parcel) 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 { override fun clearParcel(parcel: ParcelId): Worker = worktimeLimiter.submit {
val bottom = getBottomBlock(parcel) val region = getRegion(parcel)
val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize)) val blocks = parcelTraverser.traverseRegion(region)
val blocks = RegionTraversal.DOWNWARD.regionTraverser(region)
val blockCount = region.blockCount.toDouble() val blockCount = region.blockCount.toDouble()
val world = world 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 { override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
val bottom = getBottomBlock(parcel) /*
val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize)) * Get the offsets for the world out of the way
val blocks = direction.regionTraverser(region) * to simplify the calculation that follows.
val blockCount = region.blockCount.toDouble() */
val world = world
for ((index, vec) in blocks.withIndex()) { val x = chunk.x.shl(4) - (o.offsetX + pathOffset)
markSuspensionPoint() val z = chunk.z.shl(4) - (o.offsetZ + pathOffset)
operation(world[vec])
setProgress((index + 1) / blockCount) /* 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))
}
return (0 until matchesOnDimensionX).flatMap { idOffsetX ->
(0 until matchesOnDimensionZ).map { idOffsetZ -> Vec2i(firstIdX + idOffsetX, firstIdZ + idOffsetZ) }
} }
} }
@@ -47,13 +47,23 @@ class ParcelImpl(override val world: ParcelWorld,
val globalAddedMap: AddedDataMap? get() = owner?.let { world.globalAddedData[it].addedMap } 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? override var owner: PlayerProfile?
get() = data.owner get() = data.owner
set(value) { set(value) {
if (data.owner != value) { if (data.owner != value) {
world.storage.setParcelOwner(this, value) world.storage.setParcelOwner(this, value)
world.blockManager.setOwnerBlock(this, value)
data.owner = value data.owner = value
} }
} }
@@ -1,8 +1,11 @@
package io.dico.parcels2.defaultimpl package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* import io.dico.parcels2.*
import kotlinx.coroutines.experimental.Unconfined
import kotlinx.coroutines.experimental.launch
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.WorldCreator import org.bukkit.WorldCreator
import org.joda.time.DateTime
class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
inline val options get() = plugin.options inline val options get() = plugin.options
@@ -49,9 +52,24 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
if (parcelWorld != null) continue if (parcelWorld != null) continue
val generator: ParcelGenerator = getWorldGenerator(worldName)!! 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, parcelWorld = ParcelWorldImpl(bukkitWorld, generator, worldOptions.runtime, plugin.storage,
plugin.globalAddedData, ::DefaultParcelContainer, plugin.worktimeLimiter) 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 _worlds[worldName] = parcelWorld
} }
@@ -7,21 +7,21 @@ import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.options.RuntimeWorldOptions import io.dico.parcels2.options.RuntimeWorldOptions
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import org.bukkit.World import org.bukkit.World
import org.joda.time.DateTime
import java.util.UUID import java.util.UUID
class ParcelWorldImpl private class ParcelWorldImpl(override val world: World,
constructor(override val world: World, override val generator: ParcelGenerator,
override val generator: ParcelGenerator, override var options: RuntimeWorldOptions,
override var options: RuntimeWorldOptions, override val storage: Storage,
override val storage: Storage, override val globalAddedData: GlobalAddedDataManager,
override val globalAddedData: GlobalAddedDataManager, containerFactory: ParcelContainerFactory,
containerFactory: ParcelContainerFactory, worktimeLimiter: WorktimeLimiter)
blockManager: ParcelBlockManager)
: ParcelWorld, : ParcelWorld,
ParcelWorldId, ParcelWorldId,
ParcelContainer, // missing delegation ParcelContainer, /* missing delegation */
ParcelLocator, // missing delegation ParcelLocator /* missing delegation */ {
ParcelBlockManager by blockManager {
override val id: ParcelWorldId get() = this override val id: ParcelWorldId get() = this
override val uid: UUID? get() = world.uid override val uid: UUID? get() = world.uid
@@ -33,10 +33,14 @@ constructor(override val world: World,
override val name: String = world.name!! override val name: String = world.name!!
override val container: ParcelContainer = containerFactory(this) override val container: ParcelContainer = containerFactory(this)
override val locator: ParcelLocator = generator.makeParcelLocator(container) override val locator: ParcelLocator
override val blockManager: ParcelBlockManager = blockManager override val blockManager: ParcelBlockManager
init { init {
val pair = generator.makeParcelLocatorAndBlockManager(id, container, worktimeLimiter)
locator = pair.first
blockManager = pair.second
enforceOptions() enforceOptions()
} }
@@ -55,24 +59,13 @@ constructor(override val world: World,
world.setGameRuleValue("doTileDrops", "${options.doTileDrops}") 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. 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 // ParcelLocator interface
override fun getParcelAt(x: Int, z: Int): Parcel? { override fun getParcelAt(x: Int, z: Int): Parcel? {
return locator.getParcelAt(x, z) return locator.getParcelAt(x, z)
@@ -7,6 +7,7 @@ import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.statusKey import io.dico.parcels2.statusKey
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.* import io.dico.parcels2.util.*
import org.bukkit.Material.* import org.bukkit.Material.*
import org.bukkit.World import org.bukkit.World
@@ -16,6 +17,7 @@ import org.bukkit.block.data.Directional
import org.bukkit.block.data.type.Bed import org.bukkit.block.data.type.Bed
import org.bukkit.entity.* import org.bukkit.entity.*
import org.bukkit.entity.minecart.ExplosiveMinecart import org.bukkit.entity.minecart.ExplosiveMinecart
import org.bukkit.event.EventPriority
import org.bukkit.event.EventPriority.NORMAL import org.bukkit.event.EventPriority.NORMAL
import org.bukkit.event.block.* import org.bukkit.event.block.*
import org.bukkit.event.entity.* import org.bukkit.event.entity.*
@@ -26,11 +28,14 @@ import org.bukkit.event.inventory.InventoryInteractEvent
import org.bukkit.event.player.* import org.bukkit.event.player.*
import org.bukkit.event.vehicle.VehicleMoveEvent import org.bukkit.event.vehicle.VehicleMoveEvent
import org.bukkit.event.weather.WeatherChangeEvent import org.bukkit.event.weather.WeatherChangeEvent
import org.bukkit.event.world.ChunkLoadEvent
import org.bukkit.event.world.StructureGrowEvent import org.bukkit.event.world.StructureGrowEvent
import org.bukkit.inventory.InventoryHolder import org.bukkit.inventory.InventoryHolder
@Suppress("NOTHING_TO_INLINE") @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 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 val parcel = parcelProvider.getParcelAt(event.to) ?: return@l
if (parcel.isBanned(user.statusKey)) { if (parcel.isBanned(user.statusKey)) {
parcelProvider.getParcelAt(event.from)?.also { 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") user.sendParcelMessage(nopermit = true, message = "You are banned from this parcel")
} ?: run { event.to = event.from } } ?: 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<ChunkLoadEvent> l@{ event ->
val world = parcelProvider.getWorld(event.chunk.world) ?: return@l
val parcels = world.blockManager.getParcelsWithOwnerBlockIn(event.chunk)
if (parcels.isEmpty()) return@l
parcels.forEach { id ->
val parcel = world.getParcelById(id)?.takeIf { it.ownerSignOutdated } ?: return@forEach
world.blockManager.setOwnerBlock(parcel.id, parcel.owner)
parcel.ownerSignOutdated = false
}
}
@ListenerMarker
val onPlayerJoinEvent = RegistratorListener<PlayerJoinEvent> l@{ event ->
storage.updatePlayerName(event.player.uuid, event.player.name)
}
} }
@@ -6,6 +6,7 @@ import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.ReceiveChannel import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.SendChannel import kotlinx.coroutines.experimental.channels.SendChannel
import org.joda.time.DateTime
import java.util.UUID import java.util.UUID
interface Backing { interface Backing {
@@ -30,8 +31,14 @@ interface Backing {
fun shutdown() fun shutdown()
fun getWorldCreationTime(worldId: ParcelWorldId): DateTime?
fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime)
fun getPlayerUuidForName(name: String): UUID? fun getPlayerUuidForName(name: String): UUID?
fun updatePlayerName(uuid: UUID, name: String)
fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>)
fun transmitAllParcelData(channel: SendChannel<DataPair>) fun transmitAllParcelData(channel: SendChannel<DataPair>)
@@ -47,6 +54,8 @@ interface Backing {
fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?)
fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean)
fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus) fun setLocalPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus)
fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean)
@@ -8,6 +8,7 @@ import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.ReceiveChannel import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.SendChannel import kotlinx.coroutines.experimental.channels.SendChannel
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import org.joda.time.DateTime
import java.util.UUID import java.util.UUID
typealias DataPair = Pair<ParcelId, ParcelData?> typealias DataPair = Pair<ParcelId, ParcelData?>
@@ -22,8 +23,14 @@ interface Storage {
fun shutdown(): Job fun shutdown(): Job
fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?>
fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job
fun getPlayerUuidForName(name: String): Deferred<UUID?> fun getPlayerUuidForName(name: String): Deferred<UUID?>
fun updatePlayerName(uuid: UUID, name: String): Job
fun readParcelData(parcel: ParcelId): Deferred<ParcelData?> fun readParcelData(parcel: ParcelId): Deferred<ParcelData?>
fun transmitParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair> fun transmitParcelData(parcels: Sequence<ParcelId>): ReceiveChannel<DataPair>
@@ -39,6 +46,8 @@ interface Storage {
fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?): Job
fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean): Job
fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus): Job fun setParcelPlayerStatus(parcel: ParcelId, player: PlayerProfile, status: AddedStatus): Job
fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean): 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 shutdown() = launch(b.dispatcher) { b.shutdown() }
override fun getWorldCreationTime(worldId: ParcelWorldId): Deferred<DateTime?> = b.launchFuture { b.getWorldCreationTime(worldId) }
override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime): Job = b.launchJob { b.setWorldCreationTime(worldId, time) }
override fun getPlayerUuidForName(name: String): Deferred<UUID?> = b.launchFuture { b.getPlayerUuidForName(name) } override fun getPlayerUuidForName(name: String): Deferred<UUID?> = b.launchFuture { b.getPlayerUuidForName(name) }
override fun updatePlayerName(uuid: UUID, name: String): Job = b.launchJob { b.updatePlayerName(uuid, name) }
override fun readParcelData(parcel: ParcelId) = b.launchFuture { b.readParcelData(parcel) } override fun readParcelData(parcel: ParcelId) = b.launchFuture { b.readParcelData(parcel) }
override fun transmitParcelData(parcels: Sequence<ParcelId>) = b.openChannel<DataPair> { b.transmitParcelData(it, parcels) } override fun transmitParcelData(parcels: Sequence<ParcelId>) = b.openChannel<DataPair> { 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 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 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) } override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) = b.launchJob { b.setParcelAllowsInteractInventory(parcel, value) }
@@ -4,10 +4,12 @@ package io.dico.parcels2.storage.exposed
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.PlayerProfile.Star.name
import io.dico.parcels2.storage.AddedDataPair import io.dico.parcels2.storage.AddedDataPair
import io.dico.parcels2.storage.Backing import io.dico.parcels2.storage.Backing
import io.dico.parcels2.storage.DataPair import io.dico.parcels2.storage.DataPair
import io.dico.parcels2.util.synchronized import io.dico.parcels2.util.synchronized
import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID import io.dico.parcels2.util.toUUID
import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.channels.ArrayChannel 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") 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? { override fun getPlayerUuidForName(name: String): UUID? {
return ProfilesT.slice(ProfilesT.uuid).select { ProfilesT.name.upperCase() eq name.toUpperCase() } return ProfilesT.slice(ProfilesT.uuid).select { ProfilesT.name.upperCase() eq name.toUpperCase() }
.firstOrNull()?.let { it[ProfilesT.uuid]?.toUUID() } .firstOrNull()?.let { it[ProfilesT.uuid]?.toUUID() }
} }
override fun updatePlayerName(uuid: UUID, name: String) {
val binaryUuid = uuid.toByteArray()
ProfilesT.upsert(ProfilesT.uuid) {
it[ProfilesT.uuid] = binaryUuid
it[ProfilesT.name] = name
}
}
override fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) { override fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) {
for (parcel in parcels) { for (parcel in parcels) {
val data = readParcelData(parcel) val data = readParcelData(parcel)
@@ -193,6 +212,14 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
ParcelsT.update({ ParcelsT.id eq id }) { ParcelsT.update({ ParcelsT.id eq id }) {
it[ParcelsT.owner_id] = owner_id it[ParcelsT.owner_id] = owner_id
it[claim_time] = time 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) { override fun setParcelAllowsInteractInventory(parcel: ParcelId, value: Boolean) {
val id = ParcelsT.getOrInitId(parcel) val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id it[parcel_id] = id
it[ParcelOptionsT.interact_inventory] = value it[interact_inventory] = value
} }
} }
override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) { override fun setParcelAllowsInteractInputs(parcel: ParcelId, value: Boolean) {
val id = ParcelsT.getOrInitId(parcel) val id = ParcelsT.getOrInitId(parcel)
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
it[ParcelOptionsT.parcel_id] = id it[parcel_id] = id
it[ParcelOptionsT.interact_inputs] = value it[interact_inputs] = value
} }
} }
@@ -231,7 +258,8 @@ class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSi
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) } 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] val id = row[ParcelsT.id]
ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow -> ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow ->
@@ -9,6 +9,7 @@ import io.dico.parcels2.util.toByteArray
import io.dico.parcels2.util.toUUID import io.dico.parcels2.util.toUUID
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.UpdateBuilder import org.jetbrains.exposed.sql.statements.UpdateBuilder
import org.joda.time.DateTime
import java.util.UUID import java.util.UUID
sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String) sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String)
@@ -24,7 +25,8 @@ sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>,
} }
internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int { 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? abstract fun getId(obj: QueryObj): Int?
@@ -35,9 +37,10 @@ sealed class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>,
fun getId(obj: QueryObj, init: Boolean): Int? = if (init) getOrInitId(obj) else getId(obj) fun getId(obj: QueryObj, init: Boolean): Int? = if (init) getOrInitId(obj) else getId(obj)
} }
object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcel_worlds", "world_id") { object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcels_worlds", "world_id") {
val name = varchar("name", 50) val name = varchar("name", 50)
val uid = binary("uid", 16).nullable() val uid = binary("uid", 16).nullable()
val creation_time = datetime("creation_time").nullable()
val index_name = uniqueIndexR("index_name", name) val index_name = uniqueIndexR("index_name", name)
val index_uid = uniqueIndexR("index_uid", uid) val index_uid = uniqueIndexR("index_uid", uid)
@@ -56,6 +59,18 @@ object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcel_worlds", "w
override fun getItem(row: ResultRow): ParcelWorldId { override fun getItem(row: ResultRow): ParcelWorldId {
return ParcelWorldId(row[name], row[uid]?.toUUID()) return ParcelWorldId(row[name], row[uid]?.toUUID())
} }
fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? {
val id = getId(worldId) ?: return null
return select { WorldsT.id eq id }.firstOrNull()?.let { it[WorldsT.creation_time] }
}
fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) {
val id = getOrInitId(worldId)
update({ WorldsT.id eq id }) {
it[WorldsT.creation_time] = time
}
}
} }
object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id") { object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id") {
@@ -63,6 +78,7 @@ object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id"
val px = integer("px") val px = integer("px")
val pz = integer("pz") val pz = integer("pz")
val owner_id = integer("owner_id").references(ProfilesT.id).nullable() 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 claim_time = datetime("claim_time").nullable()
val index_location = uniqueIndexR("index_location", world_id, px, pz) val index_location = uniqueIndexR("index_location", world_id, px, pz)
@@ -89,7 +105,7 @@ object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id"
} }
} }
object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcel_profiles", "owner_id") { object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcels_profiles", "owner_id") {
val uuid = binary("uuid", 16).nullable() val uuid = binary("uuid", 16).nullable()
val name = varchar("name", 32).nullable() val name = varchar("name", 32).nullable()
@@ -103,7 +119,8 @@ object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcel_profile
private inline fun getId(nameIn: String) = getId { uuid.isNull() and (name.lowerCase() eq nameIn.toLowerCase()) } 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 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) }, { getId(binaryUuid) },
{ it[this@ProfilesT.uuid] = binaryUuid; it[this@ProfilesT.name] = name }, { it[this@ProfilesT.uuid] = binaryUuid; it[this@ProfilesT.name] = name },
{ "profile(uuid = $uuid, name = $name)" }) { "profile(uuid = $uuid, name = $name)" })
@@ -83,6 +83,7 @@ class PlotmeMigration(val options: PlotmeMigrationOptions) : Migration {
val parcel = getParcelId(PlotmePlotsT, row) ?: return@forEach val parcel = getParcelId(PlotmePlotsT, row) ?: return@forEach
val owner = PlayerProfile.safe(row[PlotmePlotsT.owner_uuid]?.toUUID(), row[PlotmePlotsT.owner_name]) val owner = PlayerProfile.safe(row[PlotmePlotsT.owner_uuid]?.toUUID(), row[PlotmePlotsT.owner_name])
target.setParcelOwner(parcel, owner) target.setParcelOwner(parcel, owner)
target.setParcelOwnerSignOutdated(parcel, true)
} }
} }
@@ -2,4 +2,12 @@ package io.dico.parcels2.util
data class Region(val origin: Vec3i, val size: Vec3i) { data class Region(val origin: Vec3i, val size: Vec3i) {
val blockCount: Int get() = size.x * size.y * size.z 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)
}
} }
@@ -5,3 +5,7 @@ data class Vec2i(
val z: Int val z: Int
) )
data class Region2i(
val bottom: Vec2i,
val top: Vec2i
)
@@ -3,6 +3,22 @@ package io.dico.parcels2.util
import org.bukkit.World import org.bukkit.World
import org.bukkit.block.Block 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( data class Vec3i(
val x: Int, val x: Int,
val y: Int, val y: Int,
@@ -12,6 +28,9 @@ data class Vec3i(
infix fun addX(o: Int) = Vec3i(x + o, y, z) infix fun addX(o: Int) = Vec3i(x + o, y, z)
infix fun addY(o: Int) = Vec3i(x, y + o, z) infix fun addY(o: Int) = Vec3i(x, y + o, z)
infix fun addZ(o: Int) = Vec3i(x, y, z + o) 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) fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz)
} }
+6 -6
View File
@@ -17,8 +17,8 @@ Modify home command:
* Make `:` not be required if prior component cannot be parsed to an int * 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 * Listen for command events that use plotme-style argument, and transform the command
Add permissions to commands (replace or fix `IContextFilter` from command lib ~~Add permissions to commands (replace or fix `IContextFilter` from command lib
to allow inheriting permissions properly). to allow inheriting permissions properly).~~
Parcel Options Parcel Options
- -
@@ -39,8 +39,8 @@ This could become optional.
Block Management Block Management
- -
Update the parcel corner with owner info when a player flies into the parcel (after migrations). ~~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. 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 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. 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 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. 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) 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 Events
- -