Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions src/main/kotlin/pl/syntaxdevteam/plotsx/PlotsX.kt
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,30 @@ class PlotsX : JavaPlugin() {
if (::databaseHandler.isInitialized) databaseHandler.closeConnection()
if (::pluginInitializer.isInitialized) pluginInitializer.onDisable()
}
fun onReload() {

/**
* Reloads configuration immediately on the server thread, then rebuilds the DB-backed runtime snapshot
* on a worker. Protection keeps serving the previous immutable snapshot until the replacement is ready.
*/
fun onReload(completion: (Exception?) -> Unit = {}) {
val candidate = org.bukkit.configuration.file.YamlConfiguration.loadConfiguration(File(dataFolder, "config.yml"))
require(validateClaimConfig(candidate) == claimMode) { "Changing plots.claiming.mode requires a server restart" }
if (::borderVisualizer.isInitialized) borderVisualizer.close()
protectionCoordinator.recover {
super.reloadConfig()
cacheManager.reloadAllCachesSync()
}
logger.success("Config reloaded.")
super.reloadConfig()

server.scheduler.runTaskAsynchronously(this, Runnable {
val failure = try {
protectionCoordinator.recover { cacheManager.reloadAllCachesSync() }
null
} catch (exception: Exception) {
exception
}
if (!isEnabled) return@Runnable
server.scheduler.runTask(this, Runnable {
if (failure == null) logger.success("Config reloaded.")
else logger.err("Config reload cache refresh failed: ${failure.message}")
completion(failure)
})
})
}
}
70 changes: 35 additions & 35 deletions src/main/kotlin/pl/syntaxdevteam/plotsx/commands/ClaimCMD.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ class ClaimCMD(private var plugin: PlotsX) : BasicCommand {
return
}

// Runtime eligibility checks are cache-only. The async transaction repeats authoritative limits/collision checks.
val maxPlots = plugin.hookHandler.getMaxPlots(player)
val ownerUuid = player.uniqueId
val ownedPlots = dbh.getPlotsByOwner(ownerUuid)
val cachedPlots = plugin.cacheManager.getCachedPlots()
val ownedPlots = cachedPlots.filter { it.ownerUuid == ownerUuid }
if (ownedPlots.size >= maxPlots) {
player.sendMessage(plugin.messageHandler.stringMessageToComponent(
"error",
Expand All @@ -62,13 +64,13 @@ class ClaimCMD(private var plugin: PlotsX) : BasicCommand {
return
}

val currentPlot = dbh.getPlotAtLocation(world, x, z)
val currentPlot = plugin.cacheManager.getPlotAt(world, x, z)
if (currentPlot != null) {
player.sendMessage(plugin.messageHandler.stringMessageToComponent("error", "is_plot"))
return
}

if (dbh.getPlotsFromAllUsers().any { it.world.equals(world, true) && it.geometry.intersects(claimGeometry(x, z, radius)) }) {
if (cachedPlots.any { it.world.equals(world, true) && it.geometry.intersects(claimGeometry(x, z, radius)) }) {
player.sendMessage(plugin.messageHandler.stringMessageToComponent("error", "in_collision"))
return
}
Expand Down Expand Up @@ -99,8 +101,8 @@ class ClaimCMD(private var plugin: PlotsX) : BasicCommand {
return@ClaimConfirmGUI
}
val loc = p.location
val uuid = player.uniqueId
val world = loc.world!!.name
val uuid = p.uniqueId
val world = loc.world.name
val x = loc.blockX
val z = loc.blockZ
val y = loc.blockY
Expand All @@ -120,13 +122,16 @@ class ClaimCMD(private var plugin: PlotsX) : BasicCommand {
return@ClaimConfirmGUI
}

if (overlapsExternalRegion(loc.world!!, x, z, radius)) {
if (overlapsExternalRegion(loc.world, x, z, radius)) {
p.sendMessage(plugin.messageHandler.stringMessageToComponent("error", "worldguard_collision"))
return@ClaimConfirmGUI
}

// Resolve every Bukkit/permission-derived value before leaving the server thread.
val maxChunksPerPlot = plugin.hookHandler.getMaxChunksPerPlot(p)
val maxChunksOwned = plugin.hookHandler.getMaxOwnedChunks(p)
val maxTotalArea = if (plugin.claimMode == pl.syntaxdevteam.plotsx.claiming.ClaimMode.CHUNKS)
plugin.hookHandler.getChunkMaxTotalArea(p) else limits.maxTotalArea
val actor = p.uniqueId
val namePrefix = "Działka ${p.name}"
plugin.server.scheduler.runTaskAsynchronously(plugin, Runnable {
Expand All @@ -139,43 +144,39 @@ class ClaimCMD(private var plugin: PlotsX) : BasicCommand {
y = y,
radius = radius,
maxPlots = maxPlots,
maxTotalArea = if (plugin.claimMode == pl.syntaxdevteam.plotsx.claiming.ClaimMode.CHUNKS)
plugin.hookHandler.getChunkMaxTotalArea(p) else limits.maxTotalArea,
maxTotalArea = maxTotalArea,
namePrefix = namePrefix,
maxChunksPerPlot = maxChunksPerPlot, maxChunksOwned = maxChunksOwned
maxChunksPerPlot = maxChunksPerPlot,
maxChunksOwned = maxChunksOwned
) } catch (failure: Exception) {
plugin.logger.err("Claim failed: ${failure.message}")
DatabaseHandler.ClaimResult.DatabaseError
}
when (result) {
is DatabaseHandler.ClaimResult.Success -> {
plugin.server.scheduler.runTask(plugin, Runnable {
if (!plugin.isEnabled) return@Runnable
plugin.server.scheduler.runTask(plugin, Runnable {
if (!p.isOnline) return@Runnable
when (result) {
is DatabaseHandler.ClaimResult.Success -> {
p.sendMessage(plugin.messageHandler.stringMessageToComponent("plots", "claim_success"))
plugin.cacheManager.getPlot(result.plotId)?.let { helpers.visualizePlotBorder3D(p, it, 20, 2, 4) }
})
}
DatabaseHandler.ClaimResult.LimitReached -> plugin.server.scheduler.runTask(plugin, Runnable {
p.sendMessage(plugin.messageHandler.stringMessageToComponent(
"error",
"max_plots_reached",
mapOf("max" to maxPlots.toString())
}
DatabaseHandler.ClaimResult.LimitReached -> p.sendMessage(plugin.messageHandler.stringMessageToComponent(
"error", "max_plots_reached", mapOf("max" to maxPlots.toString())
))
})
DatabaseHandler.ClaimResult.AreaLimitReached -> plugin.server.scheduler.runTask(plugin, Runnable {
p.sendMessage(plugin.messageHandler.stringMessageToComponent(
"error", "claim_area_limit", mapOf("max" to formatLimit(limits.maxTotalArea))
DatabaseHandler.ClaimResult.AreaLimitReached -> p.sendMessage(plugin.messageHandler.stringMessageToComponent(
"error", "claim_area_limit", mapOf("max" to formatLimit(maxTotalArea))
))
})
DatabaseHandler.ClaimResult.ChunkLimitReached -> plugin.server.scheduler.runTask(plugin, Runnable {
p.sendMessage(plugin.messageHandler.stringMessageToComponent("error", "claim_chunk_limit"))
})
DatabaseHandler.ClaimResult.Overlap -> plugin.server.scheduler.runTask(plugin, Runnable {
p.sendMessage(plugin.messageHandler.stringMessageToComponent("error", "in_collision"))
})
DatabaseHandler.ClaimResult.DatabaseError -> plugin.server.scheduler.runTask(plugin, Runnable {
p.sendMessage(plugin.messageHandler.stringMessageToComponent("error", "create_error"))
})
}
DatabaseHandler.ClaimResult.ChunkLimitReached -> p.sendMessage(
plugin.messageHandler.stringMessageToComponent("error", "claim_chunk_limit")
)
DatabaseHandler.ClaimResult.Overlap -> p.sendMessage(
plugin.messageHandler.stringMessageToComponent("error", "in_collision")
)
DatabaseHandler.ClaimResult.DatabaseError -> p.sendMessage(
plugin.messageHandler.stringMessageToComponent("error", "create_error")
)
}
})
})
},
onCancel = { p ->
Expand Down Expand Up @@ -207,5 +208,4 @@ class ClaimCMD(private var plugin: PlotsX) : BasicCommand {
pl.syntaxdevteam.plotsx.claiming.ClaimGeometryFactory.create(plugin.claimMode, x, z, radius)

private fun formatLimit(value: Long): String = if (value == Long.MAX_VALUE) "∞" else value.toString()

}
16 changes: 8 additions & 8 deletions src/main/kotlin/pl/syntaxdevteam/plotsx/commands/PlotCMD.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class PlotCMD(private val plugin: PlotsX) : BasicCommand {
?: plugin.server.getPlayerExact(input)?.uniqueId
?: plugin.server.offlinePlayers.firstOrNull { it.name.equals(input, true) }?.uniqueId
if (uuid == null) { members.reply(sender, "unknown_player"); return }
val plots = plugin.databaseHandler.getPlotsByOwner(uuid).sortedBy { it.id }
val plots = plugin.cacheManager.getCachedPlots().filter { it.ownerUuid == uuid }.sortedBy { it.id }
members.reply(sender, "admin_list_header", mapOf("player" to members.name(uuid), "count" to plots.size.toString()))
plots.forEach { plot ->
// Names are literal text, never MiniMessage markup.
Expand All @@ -41,7 +41,7 @@ class PlotCMD(private val plugin: PlotsX) : BasicCommand {
}
val id = args.getOrNull(1)?.toIntOrNull()
if (id == null) { members.reply(sender, "admin_usage"); return }
val plot = plugin.databaseHandler.getPlotById(id)
val plot = plugin.cacheManager.getPlot(id)
if (plot == null) { members.reply(sender, "stand_on_plot"); return }
if (args.size == 2 && sender is Player) {
plugin.guiHandler.registerGui(sender, MembersGUI(plugin, id))
Expand All @@ -54,8 +54,6 @@ class PlotCMD(private val plugin: PlotsX) : BasicCommand {
expandStandingChunk(player, args)
return
}
// Command routing runs on the server thread. The plot cache already contains the
// geometry needed here; querying SQL caused a visible pause before /plot expand.
val standing = plugin.cacheManager.getPlotAt(player.world.name, player.location.blockX, player.location.blockZ)
if (args.firstOrNull()?.lowercase() in actions) {
if (standing == null) { members.reply(player, "stand_on_plot"); return }
Expand All @@ -64,15 +62,16 @@ class PlotCMD(private val plugin: PlotsX) : BasicCommand {
} else members.execute(player, standing.id, args.toList())
return
}
val cachedPlots = plugin.cacheManager.getCachedPlots()
val plot = if (args.isEmpty()) standing else {
plugin.databaseHandler.getPlotByName(args[0], player.uniqueId) ?: plugin.databaseHandler.getPlotsFromAllUsers()
.firstOrNull { it.name.equals(args[0], true) && access.canOpen(player, it) }
cachedPlots.firstOrNull { it.ownerUuid == player.uniqueId && it.name.equals(args[0], true) }
?: cachedPlots.firstOrNull { it.name.equals(args[0], true) && access.canOpen(player, it) }
}
if (plot != null) {
if (!access.canOpen(player, plot)) { members.reply(player, "denied"); return }
plugin.guiHandler.registerGui(player, PlotGUI(plugin, plot))
} else if (args.isEmpty()) {
val plots = plugin.databaseHandler.getPlayerPlots(player.uniqueId)
val plots = plugin.cacheManager.getPlayerPlots(player.uniqueId)
if (plots.isEmpty()) members.reply(player, "stand_on_plot")
else plugin.guiHandler.registerGui(player, PlotListGUI(plugin, plots, player.uniqueId))
} else player.sendMessage(plugin.messageHandler.stringMessageToComponent("error", "plot_not_found"))
Expand Down Expand Up @@ -104,7 +103,8 @@ class PlotCMD(private val plugin: PlotsX) : BasicCommand {
words.size == 3 && words[0] == "transfer" -> listOf("confirm")
words.size == 2 && words[0] in listOf("add", "remove", "role", "transfer") -> {
if (words[0] == "add") plugin.server.onlinePlayers.filter { sender !is Player || sender.canSee(it) }.map { it.name }
else plugin.databaseHandler.getPlotMembers(plot.id).map { PlotMembers(plugin).name(java.util.UUID.fromString(it.memberUuid)) }
else plugin.cacheManager.getMembers(plot.id).orEmpty()
.map { PlotMembers(plugin).name(java.util.UUID.fromString(it.memberUuid)) }
}
else -> emptyList()
}
Expand Down
Loading
Loading