From 8e96a07a65e4632668d82b76a09edc206871e742 Mon Sep 17 00:00:00 2001 From: TDSTOS Date: Fri, 27 Mar 2026 16:26:18 +0100 Subject: [PATCH] Add timer command, recraft module & recipes Introduce a /timer admin command with tab completion (speedhg.admin.timer) and a parseTimeToSeconds utility; add CommandSender.sendMsg extension and language strings. Add RecraftManager module that enforces a configurable recraft-nerf (reduces extra recraft points for alive players) and starts from GameManager; wire the new module into GameManager. Register mushroom-stew shapeless recipes for cocoa and cactus in plugin startup and tidy command registration. Update plugin.yml with permission/command entries and adjust imports as needed. --- .../kotlin/club/mcscrims/speedhg/SpeedHG.kt | 34 +++- .../mcscrims/speedhg/command/TimerCommand.kt | 74 ++++++++ .../club/mcscrims/speedhg/game/GameManager.kt | 4 + .../speedhg/game/modules/RecraftManager.kt | 179 ++++++++++++++++++ .../club/mcscrims/speedhg/util/Extensions.kt | 28 ++- src/main/resources/config.yml | 5 + src/main/resources/languages/en_US.yml | 5 + src/main/resources/plugin.yml | 9 +- 8 files changed, 334 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/club/mcscrims/speedhg/command/TimerCommand.kt create mode 100644 src/main/kotlin/club/mcscrims/speedhg/game/modules/RecraftManager.kt diff --git a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt index 7e46fdf..77709c9 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt @@ -2,6 +2,7 @@ package club.mcscrims.speedhg import club.mcscrims.speedhg.command.KitCommand import club.mcscrims.speedhg.command.LeaderboardCommand +import club.mcscrims.speedhg.command.TimerCommand import club.mcscrims.speedhg.config.LanguageManager import club.mcscrims.speedhg.database.DatabaseManager import club.mcscrims.speedhg.database.StatsManager @@ -18,6 +19,10 @@ import club.mcscrims.speedhg.listener.StatsListener import club.mcscrims.speedhg.scoreboard.ScoreboardManager import club.mcscrims.speedhg.webhook.DiscordWebhookManager import org.bukkit.Bukkit +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.ShapelessRecipe import org.bukkit.plugin.java.JavaPlugin class SpeedHG : JavaPlugin() { @@ -81,6 +86,7 @@ class SpeedHG : JavaPlugin() { registerKits() registerCommands() registerListener() + registerRecipes() logger.info("SpeedHG wurde geladen!") } @@ -105,8 +111,16 @@ class SpeedHG : JavaPlugin() { private fun registerCommands() { val kitCommand = KitCommand() - getCommand( "kit" )?.setExecutor( kitCommand ) - getCommand( "kit" )?.tabCompleter = kitCommand + getCommand( "kit" )?.apply { + setExecutor( kitCommand ) + tabCompleter = kitCommand + } + + val timerCommand = TimerCommand( this ) + getCommand( "timer" )?.apply { + setExecutor( timerCommand ) + tabCompleter = timerCommand + } getCommand( "leaderboard" )?.setExecutor( LeaderboardCommand() ) } @@ -123,4 +137,20 @@ class SpeedHG : JavaPlugin() { pm.registerEvents( MenuListener(), this ) } + private fun registerRecipes() + { + val soup = ItemStack( Material.MUSHROOM_STEW ) + + val cocoRecipe = ShapelessRecipe(NamespacedKey.minecraft( "cocoa_soup" ), soup ) + cocoRecipe.addIngredient( Material.COCOA_BEANS ) + cocoRecipe.addIngredient( Material.BOWL ) + + val cactiRecipe = ShapelessRecipe(NamespacedKey.minecraft( "cacti_soup" ), soup ) + cactiRecipe.addIngredient( Material.CACTUS ) + cactiRecipe.addIngredient( Material.BOWL ) + + Bukkit.addRecipe( cocoRecipe ) + Bukkit.addRecipe( cactiRecipe ) + } + } \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/command/TimerCommand.kt b/src/main/kotlin/club/mcscrims/speedhg/command/TimerCommand.kt new file mode 100644 index 0000000..3b55fe4 --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/command/TimerCommand.kt @@ -0,0 +1,74 @@ +package club.mcscrims.speedhg.command + +import club.mcscrims.speedhg.SpeedHG +import club.mcscrims.speedhg.game.GameState +import club.mcscrims.speedhg.util.parseTimeToSeconds +import club.mcscrims.speedhg.util.sendMsg +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import org.bukkit.command.TabCompleter + +class TimerCommand( + private val plugin: SpeedHG +) : CommandExecutor, TabCompleter { + + override fun onCommand( + sender: CommandSender, + command: Command, + label: String, + args: Array + ): Boolean + { + if (!sender.hasPermission( "speedhg.admin.timer" )) + { + sender.sendMsg( "default.no_permission" ) + return true + } + + if ( args.isEmpty() ) + { + sender.sendMsg( "commands.timer.usage" ) + return true + } + + val newTime = args[0].parseTimeToSeconds() + + if ( newTime == null || newTime < 0 ) + { + sender.sendMsg( "commands.timer.positiveNumber" ) + return true + } + + if ( plugin.gameManager.currentState != GameState.INGAME ) + { + sender.sendMsg( "commands.timer.onlyIngame" ) + return true + } + + plugin.gameManager.timer = newTime + + val minutes = newTime / 60 + val seconds = newTime % 60 + val formattedTime = if ( minutes > 0 ) "${minutes}m ${seconds}s" else "${seconds}s" + + sender.sendMsg( "commands.timer.set", "time" to formattedTime ) + return true + } + + override fun onTabComplete( + sender: CommandSender, + command: Command, + label: String, + args: Array + ): List + { + if ( args.size == 1 && sender.hasPermission( "speedhg.admin.timer" )) + { + val suggestions = listOf("10m", "30m", "1h", "60s", "600", "1800") + return suggestions.filter { it.startsWith( args[0], true ) }.toMutableList() + } + return mutableListOf() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt b/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt index 08d6882..95c2b2c 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt @@ -3,6 +3,7 @@ package club.mcscrims.speedhg.game import club.mcscrims.speedhg.SpeedHG import club.mcscrims.speedhg.game.modules.FeastManager import club.mcscrims.speedhg.game.modules.PitManager +import club.mcscrims.speedhg.game.modules.RecraftManager import club.mcscrims.speedhg.util.sendMsg import club.mcscrims.speedhg.util.trans import net.kyori.adventure.title.Title @@ -45,6 +46,7 @@ class GameManager( val feastManager = FeastManager( plugin ) val pitManager = PitManager( plugin ) + val recraftManager = RecraftManager( plugin ) init { plugin.server.pluginManager.registerEvents( this, plugin ) @@ -52,6 +54,8 @@ class GameManager( gameTask = Bukkit.getScheduler().runTaskTimer( plugin, { -> gameLoop() }, 20L, 20L ) + + recraftManager.startRunnable() } private var lobbyIdleCount: Int = 0 diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/modules/RecraftManager.kt b/src/main/kotlin/club/mcscrims/speedhg/game/modules/RecraftManager.kt new file mode 100644 index 0000000..f418c4f --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/game/modules/RecraftManager.kt @@ -0,0 +1,179 @@ +package club.mcscrims.speedhg.game.modules + +import club.mcscrims.speedhg.SpeedHG +import club.mcscrims.speedhg.util.sendMsg +import org.bukkit.Bukkit +import org.bukkit.Material +import org.bukkit.entity.Player +import org.bukkit.inventory.ItemStack +import org.bukkit.scheduler.BukkitRunnable + +class RecraftManager( + private val plugin: SpeedHG +) { + + private val beforeFeast = plugin.config.getBoolean( "game.recraftNerf.beforeFeast", true ) + private val recraftNerfEnabled = plugin.config.getBoolean( "game.recraftNerf.enabled", false ) + private val maxRecraftAmount = plugin.config.getInt( "game.recraftNerf.maxAmount", 64 ) + + fun startRunnable() + { + if ( !recraftNerfEnabled ) + return + + object : BukkitRunnable() { + + override fun run() + { + if ( beforeFeast && + plugin.gameManager.feastManager.hasSpawned ) + { + this.cancel() + return + } + + Bukkit.getOnlinePlayers().stream() + .filter { plugin.gameManager.alivePlayers.contains( it.uniqueId ) } + .forEach { + val recraft = Recraft() + recraft.calcRecraft( *it.inventory.contents ) + + if ( recraft.getRecraftPoints() > maxRecraftAmount ) + { + it.sendMsg( "recraftNerf.tooMuch" ) + + while ( recraft.getRecraftPoints() > maxRecraftAmount ) + recraft.decrease( it, 1 ) + } + } + } + + }.runTaskTimer( plugin, 10L, 10L ) + } + + private class Recraft { + + private val recraftMaterials = listOf( + RecraftMaterial( 1, arrayOf( Material.RED_MUSHROOM, Material.BROWN_MUSHROOM )), + RecraftMaterial( 1, arrayOf( Material.COCOA_BEANS )), + RecraftMaterial( 1, arrayOf( Material.CACTUS )) + ) + + fun calcRecraft( + vararg items: ItemStack? + ) { + recraftMaterials.forEach( RecraftMaterial::reset ) + + for ( item in items ) + { + if ( item == null ) + continue + + for ( recraftMaterial in recraftMaterials ) + { + val type = item.type + if (recraftMaterial.containsKey( type )) + recraftMaterial[ type ] = recraftMaterial.getOrDefault( type, 0 ) + item.amount + } + } + } + + fun decrease( + player: Player, + amount: Int + ) { + val lowestMaterials = mutableListOf() + + for ( recraftMaterial in recraftMaterials ) + if ( recraftMaterial.getLowestMaterial() != null ) + lowestMaterials.add( recraftMaterial.getLowestMaterial()!! ) + + var highestMaterial: Material? = null + var i = 0f + + for ( lowestMaterial in lowestMaterials ) + { + val recraftMaterial = byMaterial( lowestMaterial ) + ?: continue + + if (recraftMaterial[ lowestMaterial ]!! * recraftMaterial.getMaterialValue() > i ) + { + i = recraftMaterial[ lowestMaterial ]!! * recraftMaterial.getMaterialValue() + highestMaterial = lowestMaterial + } + } + + val recraftMaterial = byMaterial( highestMaterial!! ) + recraftMaterial?.decrease( highestMaterial, amount ) + + for ( item in player.inventory.contents ) + { + if ( item == null ) + continue + + if ( item.type == highestMaterial ) + { + item.amount -= amount + break + } + } + } + + fun getRecraftPoints(): Float + { + var points = 0f + + for ( recraftMaterial in recraftMaterials ) + points += recraftMaterial.getPoints() + + return points + } + + private fun byMaterial( + material: Material + ): RecraftMaterial? + { + return recraftMaterials.stream() + .filter { it.containsKey( material ) } + .findFirst().orElse( null ) + } + + } + + private class RecraftMaterial( + val maxSoupAmount: Int, + val materials: Array + ) : HashMap() { + + fun getPoints(): Float + { + return getOrDefault( getLowestMaterial(), 0 ).toFloat() + } + + fun decrease( + material: Material, + amount: Int + ) { + put( material, get( material )!! - amount ) + } + + fun getLowestMaterial(): Material? + { + if ( size > 1 ) + { + if (values.stream().anyMatch { int -> int == 0 }) + return null + + val materialIntegerEntry = entries.stream().min(Comparator.comparingInt { it.value }) + return materialIntegerEntry.map { it.key }.orElse( null ) + } + else return keys.stream().findFirst().orElse( null ) + } + + fun getMaterialValue() = ( maxSoupAmount / size ).toFloat() + + fun reset() = replaceAll { _, _ -> 0 } + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/util/Extensions.kt b/src/main/kotlin/club/mcscrims/speedhg/util/Extensions.kt index 48587a7..f0975cd 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/util/Extensions.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/util/Extensions.kt @@ -4,6 +4,7 @@ import club.mcscrims.speedhg.SpeedHG import net.kyori.adventure.text.Component import net.kyori.adventure.text.minimessage.MiniMessage import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import org.bukkit.command.CommandSender import org.bukkit.entity.Player private val langManager get() = SpeedHG.instance.languageManager @@ -31,6 +32,14 @@ fun Player.sendMsg( this.sendMessage( component ) } +fun CommandSender.sendMsg( + key: String, + vararg placeholders: Pair +) { + val component = langManager.getDefaultComponent( key, placeholders.toMap() ) + this.sendMessage( component ) +} + fun Player.trans( key: String, vararg placeholders: Pair @@ -70,4 +79,21 @@ fun Component.toLegacyString(): String } val Player.getDisplayName: String - get() = legacySerializer.serialize( this.displayName() ) \ No newline at end of file + get() = legacySerializer.serialize( this.displayName() ) + +fun String.parseTimeToSeconds(): Int? +{ + val regex = Regex( "^(\\d+)([smh]?)$", RegexOption.IGNORE_CASE ) + val match = regex.find( this.trim() ) ?: return null + + val value = match.groupValues[ 1 ].toIntOrNull() ?: return null + val unit = match.groupValues[ 2 ].lowercase() + + return when( unit ) + { + "h" -> value * 3600 + "m" -> value * 60 + "s", "" -> value + else -> null + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index a984470..4020c9a 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -20,6 +20,11 @@ anti-runner: ignore-vertical-distance: 15.0 # Wenn Höhenunterschied > 15, Timer ignorieren ignore-cave-surface-mix: true # Ignorieren, wenn einer Sonne hat und der andere nicht +recraftNerf: + enabled: false + beforeFeast: true + maxAmount: 64 + discord: enabled: false webhook-url: "DEINE_WEBHOOK_URL_HIER" diff --git a/src/main/resources/languages/en_US.yml b/src/main/resources/languages/en_US.yml index db48533..8c25af6 100644 --- a/src/main/resources/languages/en_US.yml +++ b/src/main/resources/languages/en_US.yml @@ -69,6 +69,11 @@ commands: empty: 'There are currently no stats' line: '# - - ' footer: '====== Leaderboard ======' + timer: + usage: 'Usage: /timer ' + positiveNumber: 'Invalid time format! Use, for example, 10m, 30s, or 600.' + onlyIngame: 'Timer can only be set in game.' + set: 'The game timer has been set to ' scoreboard: title: 'SpeedHG' diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index b40fd1e..1bf4f84 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -10,6 +10,9 @@ permissions: speedhg.bypass: description: 'Allows joining the server while its running' default: false + speedhg.admin.timer: + description: 'Change the current game time' + default: false commands: kit: @@ -17,4 +20,8 @@ commands: usage: '/kit ' leaderboard: description: 'View the top 10 players' - usage: '/leaderboard' \ No newline at end of file + usage: '/leaderboard' + timer: + description: 'Change the current game time (Admin Command)' + usage: '/timer ' + permission: speedhg.admin.timer \ No newline at end of file