From 832b1c4b5528e26b7a5b81373ca8db54b3b01889 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 11 Feb 2020 15:29:47 +0100 Subject: [PATCH] Add command to set and get the center - Add getCenter command to get the center of the world used for generation - Add setCenter to set the world center for generation tasks. If there is already a task running for the world the center for that task will not be affected. - Fix SQL problem with multiple queries at the same time creating new connections - Add translations for new commands - Add Examples to Readme - Add new table to safe world centers - Add query to Generation manager to get the center of the world for new generation tasks --- README.md | 23 +++++ .../chunkmaster/commands/CmdGetCenter.kt | 65 ++++++++++++++ .../chunkmaster/commands/CmdSetCenter.kt | 85 +++++++++++++++++++ .../commands/CommandChunkmaster.kt | 8 +- .../chunkmaster/lib/SqliteManager.kt | 25 +++++- .../lib/generation/GenerationManager.kt | 57 ++++++++++++- .../resources/i18n/DEFAULT.i18n.properties | 7 +- src/main/resources/i18n/de.i18n.properties | 7 +- src/main/resources/i18n/en.i18n.properties | 7 +- src/main/resources/plugin.yml | 8 ++ 10 files changed, 284 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt create mode 100644 src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt diff --git a/README.md b/README.md index 2c1f8f0..a52e6c9 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,29 @@ All features can be accessed with the command `/chunkmaster` or the aliases `/ch - `/chunkmaster resume` Resumes all paused generation tasks. - `/chunkmaster reload` Reloads the configuration file. - `/chunkmaster tpchunk ` Teleports you to the specified chunk coordinates. +- `/ setCenter [] ` - sets the center chunk of the world +- `/ getCenter []` - returns the center chunk of the world + +#### Examples +**Generate a 100 chunks * 100 chunks square around the spawn:** + +`/chm generate [world] 100 diameter` + +**Generate a 100 blocks * 100 blocks square around the spawn:** + +`/chm generate [world] 50 blockradius` + +**Generate 100 Blocks in every direction from the spawn:** + +`/chm generate [world] 100 blockradius` + +**Generate 200 Chunks in every direction from the spawn:** + +`/chm generate [world] 200 radius` + +**Generate 1000 Chunks in total around the spawn:** + +`/chm generate [world] 1000` ### Config diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt new file mode 100644 index 0000000..b763cd3 --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdGetCenter.kt @@ -0,0 +1,65 @@ +package net.trivernis.chunkmaster.commands + +import net.trivernis.chunkmaster.Chunkmaster +import net.trivernis.chunkmaster.lib.Subcommand +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class CmdGetCenter(private val chunkmaster: Chunkmaster): Subcommand { + override val name = "getCenter"; + + override fun onTabComplete( + sender: CommandSender, + command: Command, + alias: String, + args: List + ): MutableList { + if (args.size == 1) { + return sender.server.worlds.filter { it.name.indexOf(args[0]) == 0 } + .map {it.name}.toMutableList() + } + return emptyList().toMutableList() + } + + override fun execute(sender: CommandSender, args: List): Boolean { + val worldName: String = if (sender is Player) { + if (args.isNotEmpty()) { + args[0] + } else { + sender.world.name; + } + } else { + if (args.isEmpty()) { + sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NAME_REQUIRED")) + return false + } else { + args[0] + } + } + if (chunkmaster.generationManager.worldCenters.isEmpty()) { + chunkmaster.generationManager.loadWorldCenters() { + sendCenterInfo(sender, worldName) + } + return true + } + sendCenterInfo(sender, worldName) + return true + } + + /** + * Sends the center information + */ + private fun sendCenterInfo(sender: CommandSender, worldName: String) { + var center = chunkmaster.generationManager.worldCenters[worldName] + if (center == null) { + val world = sender.server.worlds.find { it.name == worldName } + if (world == null) { + sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", worldName)) + return + } + center = Pair(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) + } + sender.sendMessage(chunkmaster.langManager.getLocalized("CENTER_INFO", worldName, center.first, center.second)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt new file mode 100644 index 0000000..eb09826 --- /dev/null +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CmdSetCenter.kt @@ -0,0 +1,85 @@ +package net.trivernis.chunkmaster.commands + +import net.trivernis.chunkmaster.Chunkmaster +import net.trivernis.chunkmaster.lib.Subcommand +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class CmdSetCenter(private val chunkmaster: Chunkmaster): Subcommand { + override val name = "setCenter"; + + override fun onTabComplete( + sender: CommandSender, + command: Command, + alias: String, + args: List + ): MutableList { + if (args.size == 1) { + if (args[0].toIntOrNull() == null) { + return sender.server.worlds.filter { it.name.indexOf(args[0]) == 0 } + .map {it.name}.toMutableList() + } + } + return emptyList().toMutableList(); + } + + override fun execute(sender: CommandSender, args: List): Boolean { + val world: String + val centerX: Int + val centerZ: Int + + if (args.size < 2) { + sender.sendMessage(chunkmaster.langManager.getLocalized("TOO_FEW_ARGUMENTS")) + return false + } + if (sender is Player) { + if (args.size == 2) { + world = sender.world.name + if (args[0].toIntOrNull() == null || args[1].toIntOrNull() == null) { + sender.sendMessage(chunkmaster.langManager.getLocalized("COORD_INVALID", args[0], args[1])) + return false + } + centerX = args[0].toInt() + centerZ = args[1].toInt() + } else { + if (!validateThreeArgs(sender, args)) { + return false + } + world = args[0] + centerX = args[1].toInt() + centerZ = args[2].toInt() + } + } else { + if (args.size < 3) { + sender.sendMessage(chunkmaster.langManager.getLocalized("TOO_FEW_ARGUMENTS")) + return false + } else { + if (!validateThreeArgs(sender, args)) { + return false + } + world = args[0] + centerX = args[1].toInt() + centerZ = args[2].toInt() + } + } + chunkmaster.generationManager.updateWorldCenter(world, Pair(centerX, centerZ)) + sender.sendMessage(chunkmaster.langManager.getLocalized("CENTER_UPDATED", world, centerX, centerZ)) + return true + } + + /** + * Validates the command values with three arguments + */ + private fun validateThreeArgs(sender: CommandSender, args: List): Boolean { + return if (sender.server.worlds.none { it.name == args[0] }) { + sender.sendMessage(chunkmaster.langManager.getLocalized("WORLD_NOT_FOUND", args[0])) + false + } else if (args[1].toIntOrNull() == null || args[2].toIntOrNull() == null) { + sender.sendMessage(chunkmaster.langManager.getLocalized("COORD_INVALID", args[1], args[2])) + false + } else { + true + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt index bf05ce2..30a0bdb 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/commands/CommandChunkmaster.kt @@ -40,7 +40,7 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve */ override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { if (args.isNotEmpty()) { - if (sender.hasPermission("chunkmaster.${args[0]}")) { + if (sender.hasPermission("chunkmaster.${args[0].toLowerCase()}")) { return if (commands.containsKey(args[0])) { commands[args[0]]!!.execute(sender, args.slice(1 until args.size)) } else { @@ -80,5 +80,11 @@ class CommandChunkmaster(private val chunkmaster: Chunkmaster, private val serve val cmdTpChunk = CmdTpChunk(chunkmaster) commands[cmdTpChunk.name] = cmdTpChunk + + val cmdSetCenter = CmdSetCenter(chunkmaster) + commands[cmdSetCenter.name] = cmdSetCenter + + val cmdGetCenter = CmdGetCenter(chunkmaster) + commands[cmdGetCenter.name] = cmdGetCenter } } \ No newline at end of file diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/SqliteManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/SqliteManager.kt index 507f97d..00dcd5b 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/SqliteManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/SqliteManager.kt @@ -22,19 +22,33 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { Pair("world", "text UNIQUE NOT NULL DEFAULT 'world'"), Pair("stop_after", "integer DEFAULT -1") ) + ), + Pair( + "world_properties", + listOf( + Pair("name", "text PRIMARY KEY"), + Pair("center_x", "integer NOT NULL DEFAULT 0"), + Pair("center_z", "integer NOT NULL DEFAULT 0") + ) ) ) private val needUpdate = HashSet>>() private val needCreation = HashSet() + private var connection: Connection? = null + private var activeTasks = 0 /** * Returns the connection to the database */ fun getConnection(): Connection? { + if (this.connection != null) { + return this.connection + } try { Class.forName("org.sqlite.JDBC") - return DriverManager.getConnection("jdbc:sqlite:${chunkmaster.dataFolder.absolutePath}/" + + this.connection = DriverManager.getConnection("jdbc:sqlite:${chunkmaster.dataFolder.absolutePath}/" + "${chunkmaster.config.getString("database.filename")}") + return this.connection } catch (e: Exception) { chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("DATABASE_CONNECTION_ERROR")) chunkmaster.logger.severe(e.message) @@ -79,6 +93,7 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { */ fun executeStatement(sql: String, values: HashMap, callback: ((ResultSet) -> Unit)?) { val connection = getConnection() + activeTasks++ if (connection != null) { try { val statement = connection.prepareStatement(sql) @@ -92,10 +107,14 @@ class SqliteManager(private val chunkmaster: Chunkmaster) { } statement.close() } catch (e: Exception) { - chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("SQL_ERROR", e.message!!)) + chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("SQL_ERROR", e.toString())) chunkmaster.logger.info(ExceptionUtils.getStackTrace(e)) } finally { - connection.close() + activeTasks-- + if (activeTasks == 0) { + connection.close() + this.connection = null + } } } else { chunkmaster.logger.severe(chunkmaster.langManager.getLocalized("NO_DATABASE_CONNECTION")) diff --git a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt index 830db4d..f822620 100644 --- a/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt +++ b/src/main/kotlin/net/trivernis/chunkmaster/lib/generation/GenerationManager.kt @@ -10,9 +10,13 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server val tasks: HashSet = HashSet() val pausedTasks: HashSet = HashSet() + val worldCenters: HashMap> = HashMap() val allTasks: HashSet get() { if (this.tasks.isEmpty() && this.pausedTasks.isEmpty()) { + if (this.worldCenters.isEmpty()) { + this.loadWorldCenters() + } this.startAll() if (!server.onlinePlayers.isEmpty()) { this.pauseAll() @@ -32,7 +36,12 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server fun addTask(world: World, stopAfter: Int = -1): Int { val foundTask = allTasks.find { it.generationTask.world == world } if (foundTask == null) { - val centerChunk = ChunkCoordinates(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) + val centerChunk = if (worldCenters[world.name] == null) { + ChunkCoordinates(world.spawnLocation.chunk.x, world.spawnLocation.chunk.z) + } else { + val center = worldCenters[world.name]!! + ChunkCoordinates(center.first, center.second) + } val generationTask = createGenerationTask(world, centerChunk, centerChunk, stopAfter) chunkmaster.sqliteManager.executeStatement( @@ -147,6 +156,7 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server saveProgress() // save progress every 30 seconds }, 600, 600) server.scheduler.runTaskLater(chunkmaster, Runnable { + this.loadWorldCenters() this.startAll() if (!server.onlinePlayers.isEmpty()) { this.pauseAll() @@ -222,6 +232,51 @@ class GenerationManager(private val chunkmaster: Chunkmaster, private val server startAll() } + /** + * Overload that doesn't need an argument + */ + fun loadWorldCenters() { + loadWorldCenters(null) + } + + /** + * Loads the world centers from the database + */ + fun loadWorldCenters(cb: (() -> Unit)?) { + chunkmaster.sqliteManager.executeStatement("SELECT * FROM world_properties", HashMap()) { + while (it.next()) { + worldCenters[it.getString("name")] = Pair(it.getInt("center_x"), it.getInt("center_z")) + } + cb?.invoke() + } + } + + /** + * Updates the center of a world + */ + fun updateWorldCenter(worldName: String, center: Pair) { + chunkmaster.sqliteManager.executeStatement("SELECT * FROM world_properties WHERE name = ?", HashMap(mapOf(1 to worldName))) { + if (it.next()) { + chunkmaster.sqliteManager.executeStatement("UPDATE world_properties SET center_x = ?, center_z = ? WHERE name = ?", HashMap( + mapOf( + 1 to center.first, + 2 to center.second, + 3 to worldName + ) + ), null) + } else { + chunkmaster.sqliteManager.executeStatement("INSERT INTO world_properties (name, center_x, center_z) VALUES (?, ?, ?)", HashMap( + mapOf( + 1 to worldName, + 2 to center.first, + 3 to center.second + ) + ), null) + } + } + worldCenters[worldName] = center + } + /** * Saves the task progress */ diff --git a/src/main/resources/i18n/DEFAULT.i18n.properties b/src/main/resources/i18n/DEFAULT.i18n.properties index 12530dd..1b70fdc 100644 --- a/src/main/resources/i18n/DEFAULT.i18n.properties +++ b/src/main/resources/i18n/DEFAULT.i18n.properties @@ -46,4 +46,9 @@ NO_DATABASE_CONNECTION = §cCould not execute sql: No database connection. CREATE_TABLE_DEFINITION = Created table %s with definition %s. TABLE_CREATE_ERROR = §cError when creation table %s. UPDATE_TABLE_DEFINITION = Updated table %s with sql %s. -UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. \ No newline at end of file +UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. + +TOO_FEW_ARGUMENTS = §cYou did not provide enough arguments. +COORD_INVALID = §cThe provided coordinate ('%s', '%s') is invalid! +CENTER_UPDATED = §9The center for world §2%s §9has been set to §2(%s, %s)§9. +CENTER_INFO = §9The center for world §2%s §9is §2(%s, %s)§9. \ No newline at end of file diff --git a/src/main/resources/i18n/de.i18n.properties b/src/main/resources/i18n/de.i18n.properties index 78e0547..02b0995 100644 --- a/src/main/resources/i18n/de.i18n.properties +++ b/src/main/resources/i18n/de.i18n.properties @@ -46,4 +46,9 @@ NO_DATABASE_CONNECTION = §cSql konnte nicht ausgeführt werden: Keine Datenbank CREATE_TABLE_DEFINITION = Tabelle %s mit Definition %s wurde erstellt. TABLE_CREATE_ERROR = §cFehler beim erstellen der Tabelle %s. UPDATE_TABLE_DEFINITION = Tabelle %s wurde mit sql %s geupdated. -UPDATE_TABLE_FAILED = Fehler beim Updaten der Tabelle %s mit sql %s. \ No newline at end of file +UPDATE_TABLE_FAILED = Fehler beim Updaten der Tabelle %s mit sql %s. + +TOO_FEW_ARGUMENTS = §cDu hast nicht genug arguments angegeben. +COORD_INVALID = §cDie Koordinate ('%s', '%s') ist ungültig! +CENTER_UPDATED = §9Die Mitte der Welt §2%s §9wurde auf §2(%s, %s)§9 gesetzt. +CENTER_INFO = §9Die Mitte der Welt §2%s §9ist §2(%s, %s)§9. \ No newline at end of file diff --git a/src/main/resources/i18n/en.i18n.properties b/src/main/resources/i18n/en.i18n.properties index 12530dd..1b70fdc 100644 --- a/src/main/resources/i18n/en.i18n.properties +++ b/src/main/resources/i18n/en.i18n.properties @@ -46,4 +46,9 @@ NO_DATABASE_CONNECTION = §cCould not execute sql: No database connection. CREATE_TABLE_DEFINITION = Created table %s with definition %s. TABLE_CREATE_ERROR = §cError when creation table %s. UPDATE_TABLE_DEFINITION = Updated table %s with sql %s. -UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. \ No newline at end of file +UPDATE_TABLE_FAILED = Failed to update table %s with sql %s. + +TOO_FEW_ARGUMENTS = §cYou did not provide enough arguments. +COORD_INVALID = §cThe provided coordinate ('%s', '%s') is invalid! +CENTER_UPDATED = §9The center for world §2%s §9has been set to §2(%s, %s)§9. +CENTER_INFO = §9The center for world §2%s §9is §2(%s, %s)§9. \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 19804bf..f61a978 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -17,6 +17,8 @@ commands: / resume - resumes all generation tasks / reload - reloads the configuration and restarts all tasks / tpchunk - teleports you to the chunk with the given chunk coordinates + / setCenter [] - sets the center chunk of the world + / getCenter [] - returns the center chunk of the world aliases: - chm - chunkm @@ -43,6 +45,12 @@ permissions: chunkmaster.tpchunk: description: Allows the tpchunk subcommand. default: op + chunkmaster.setcenter: + description: Allows the setCenter subcommand. + default: op + chunkmaster.getcenter: + description: Allows the getCenter subcommand. + default: op chunkmaster.chunkmaster: description: Allows Chunkmaster commands. default: op