From 19bd708b5970dffc78cd783e99ab1e68fc8196ae Mon Sep 17 00:00:00 2001 From: TDSTOS Date: Wed, 25 Mar 2026 03:28:52 +0100 Subject: [PATCH] Refactor language, kits, and event handling Major refactor and bugfixes across language loading, kit lifecycle, and event handling: - LanguageManager: Introduce LangData to hold string and list entries, load YAML lists alongside strings, and centralize retrieval (avoid reloading files at access time). - GameManager: Use apply {} to configure worldBorder more concisely. - KitManager.clearAll: Handle offline players by cleaning chargeData without calling lifecycle hooks (onActivate/onDeactivate cannot run for offline players). - GoblinKit: Track per-player steal BukkitTask, cancel tasks on kit removal, only restore kits if player is still alive, and fix inventory removal by removing cached items and cleaning cache. - KitEventDispatcher: Use null-safe chaining for victim passive hooks and early-return on non-block-position moves to ignore head-rotation events. - GameStateListener: Add feastStarted flag placeholder and adjust iron ore handling/messages; add TODO for DB update. - Extensions: Change legacySerializer visibility to internal. These changes improve stability around scheduled tasks, offline cleanup, and language list support. --- .../speedhg/config/LanguageManager.kt | 42 +++++++++---------- .../club/mcscrims/speedhg/game/GameManager.kt | 11 ++--- .../club/mcscrims/speedhg/kit/KitManager.kt | 11 ++++- .../mcscrims/speedhg/kit/impl/GoblinKit.kt | 35 +++++++++++++--- .../kit/listener/KitEventDispatcher.kt | 17 ++++---- .../speedhg/listener/GameStateListener.kt | 13 +++--- .../club/mcscrims/speedhg/util/Extensions.kt | 2 +- 7 files changed, 84 insertions(+), 47 deletions(-) diff --git a/src/main/kotlin/club/mcscrims/speedhg/config/LanguageManager.kt b/src/main/kotlin/club/mcscrims/speedhg/config/LanguageManager.kt index fbb372c..04db74f 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/config/LanguageManager.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/config/LanguageManager.kt @@ -13,8 +13,13 @@ class LanguageManager( private val plugin: JavaPlugin ) { + private data class LangData( + val strings: Map, + val lists: Map> + ) + // Map: Sprachcode -> (Key -> Nachricht) - private val languages = ConcurrentHashMap>() + private val languages = ConcurrentHashMap() private val miniMessage = MiniMessage.miniMessage() private val defaultLanguage = plugin.config.getString("default.language", "en_US")!! @@ -37,12 +42,15 @@ class LanguageManager( val langCode = file.nameWithoutExtension val config = YamlConfiguration.loadConfiguration( file ) - val messages = config.getKeys( true ) + val strings = config.getKeys( true ) .filter { config.isString( it ) } - .associateWith { config.getString( it ) } + .associateWith { config.getString( it )!! } - languages[ langCode ] = messages as Map - plugin.logger.info("Sprache geladen: $langCode (${messages.size} Nachrichten)") + val lists = config.getKeys( true ) + .filter { config.isList( it ) } + .associateWith { config.getStringList( it ) } + + languages[ langCode ] = LangData( strings, lists ) } } @@ -62,14 +70,14 @@ class LanguageManager( { val locale = player.locale().toString() val langMap = languages[ locale ] ?: languages[ defaultLanguage ] - return langMap?.get( key ) ?: "Missing Key: $key" + return langMap?.strings?.get( key ) ?: "Missing Key: $key" } fun getDefaultRawMessage( key: String ): String { - return languages[ defaultLanguage ]?.get( key ) ?: "Missing Key: $key" + return languages[ defaultLanguage ]?.strings?.get( key ) ?: "Missing Key: $key" } fun getRawMessageList( @@ -77,25 +85,17 @@ class LanguageManager( key: String ): List { - var locale = player.locale().toString() - val langMap = languages[ locale ] - - if ( langMap == null ) { - locale = defaultLanguage - } - - val file = File( plugin.dataFolder, "languages/$locale.yml" ) - val config = YamlConfiguration.loadConfiguration( file ) - return if (config.contains( key )) config.getStringList( key ) else listOf( "Missing List: $key" ) + val locale = player.locale().toString() + val data = languages[ locale ] ?: languages[ defaultLanguage ] + return data?.lists?.get( key ) ?: listOf( "Missing List: $key" ) } fun getDefaultRawMessageList( key: String ): List { - val file = File( plugin.dataFolder, "languages/$defaultLanguage.yml" ) - val config = YamlConfiguration.loadConfiguration( file ) - return if (config.contains( key )) config.getStringList( key ) else listOf( "Missing List: $key" ) + val data = languages[ defaultLanguage ] + return data?.lists?.get( key ) ?: listOf( "Missing List: $key" ) } fun getComponent( @@ -116,7 +116,7 @@ class LanguageManager( placeholders: Map ): Component { - val raw = languages[ defaultLanguage ]?.get( key ) ?: "Missing Key: $key" + val raw = languages[ defaultLanguage ]?.strings?.get( key ) ?: "Missing Key: $key" val tags = placeholders.map { (k, v) -> Placeholder.parsed( k, v ) } return miniMessage.deserialize( raw, *tags.toTypedArray() ) } diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt b/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt index f30cfdf..aaddaf2 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/game/GameManager.kt @@ -134,11 +134,12 @@ class GameManager( world.time = 0 world.setStorm( false ) - val border = world.worldBorder - border.center = Location( world, 0.0, 0.0, 0.0 ) - border.size = startBorder - border.damageBuffer = 0.0 - border.damageAmount = 1.0 + world.worldBorder.apply { + center = Location( world, 0.0, 0.0, 0.0 ) + size = startBorder + damageBuffer = 0.0 + damageAmount = 1.0 + } val speedEffect = PotionEffect( PotionEffectType.SPEED, diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/KitManager.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/KitManager.kt index 1686beb..c5a3ff8 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/KitManager.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/KitManager.kt @@ -129,7 +129,16 @@ class KitManager( fun clearAll() { selectedKits.keys.toList().forEach { uuid -> - plugin.server.getPlayer( uuid )?.let { removeKit( it ) } + val player = plugin.server.getPlayer( uuid ) + if ( player != null ) + removeKit( player ) + else + { + // Daten bereinigen ohne Lifecycle-Hooks (Spieler ist offline) + chargeData.remove( uuid ) + // Hinweis: onDeactivate/onRemove können nicht aufgerufen werden + // → Kits müssen in onActivate gestartete Tasks UUID-basiert führen + } } selectedKits.clear() selectedPlaystyles.clear() diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/GoblinKit.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/GoblinKit.kt index b0fbe07..3c695da 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/GoblinKit.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/GoblinKit.kt @@ -16,6 +16,7 @@ import org.bukkit.Material import org.bukkit.Sound import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack +import org.bukkit.scheduler.BukkitTask import java.util.UUID import java.util.concurrent.ConcurrentHashMap @@ -98,12 +99,15 @@ class GoblinKit : Kit() { override fun onRemove( player: Player ) { - val items = cachedItems[ player.uniqueId ] ?: return - player.inventory.removeAll { items.contains( it ) } + aggressiveActive.cancelStealTask( player ) + val items = cachedItems.remove( player.uniqueId ) ?: return + items.forEach { player.inventory.remove( it ) } } private inner class AggressiveActive : ActiveAbility( Playstyle.AGGRESSIVE ) { + private val plugin get() = SpeedHG.instance + override val name: String get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.steal.name" ) @@ -116,6 +120,8 @@ class GoblinKit : Kit() { override val triggerMaterial: Material get() = Material.GLASS + private val activeStealTasks = ConcurrentHashMap() + override fun execute( player: Player ): AbilityResult @@ -129,16 +135,25 @@ class GoblinKit : Kit() { val currentKit = plugin.kitManager.getSelectedKit( player ) ?: return AbilityResult.ConditionNotMet( "Error while copying kit" ) + activeStealTasks.remove( player.uniqueId ) + plugin.kitManager.removeKit( player ) plugin.kitManager.selectKit( player, targetKit ) plugin.kitManager.applyKit( player ) - Bukkit.getScheduler().runTaskLater( plugin, { -> - plugin.kitManager.removeKit( player ) - plugin.kitManager.selectKit( player, currentKit ) - plugin.kitManager.applyKit( player ) + val task = Bukkit.getScheduler().runTaskLater( plugin, { -> + activeStealTasks.remove( player.uniqueId ) + // Nur wiederherstellen, wenn Spieler noch alive und Spiel läuft + if (plugin.gameManager.alivePlayers.contains( player.uniqueId )) + { + plugin.kitManager.removeKit( player ) + plugin.kitManager.selectKit( player, currentKit ) + plugin.kitManager.applyKit( player ) + } }, 20L * 60) + activeStealTasks[ player.uniqueId ] = task + player.playSound( player.location, Sound.ENTITY_EVOKER_CAST_SPELL, 1f, 1.5f ) player.sendActionBar(player.trans( "kits.goblin.messages.stole_kit", "kit" to legacySerializer.serialize( targetKit.displayName ))) @@ -152,10 +167,18 @@ class GoblinKit : Kit() { player.sendActionBar(player.trans( "kits.goblin.messages.ability_charged" )) } + fun cancelStealTask( + player: Player + ) { + activeStealTasks.remove( player.uniqueId )?.cancel() + } + } private inner class DefensiveActive : ActiveAbility( Playstyle.DEFENSIVE ) { + private val plugin get() = SpeedHG.instance + override val name: String get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.bunker.name" ) diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/listener/KitEventDispatcher.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/listener/KitEventDispatcher.kt index ba76445..9fe1181 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/listener/KitEventDispatcher.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/listener/KitEventDispatcher.kt @@ -75,9 +75,9 @@ class KitEventDispatcher( .onHitEnemy( attacker, victim, event ) // ── 3. Victim passive hook ──────────────────────────────────────────── - val victimKit = kitManager.getSelectedKit( victim ) ?: return - victimKit.getPassiveAbility(kitManager.getSelectedPlaystyle( victim )) - .onHitByEnemy( victim, attacker, event ) + kitManager.getSelectedKit( victim ) + ?.getPassiveAbility(kitManager.getSelectedPlaystyle( victim )) + ?.onHitByEnemy( victim, attacker, event ) } // ========================================================================= @@ -159,13 +159,16 @@ class KitEventDispatcher( fun onMove( event: PlayerMoveEvent ) { + // Frühexit: nur echte Positionsänderungen, keine Kopfdrehungen + val from = event.from + val to = event.to + if ( from.blockX == to.blockX && from.blockY == to.blockY && from.blockZ == to.blockZ ) return + if ( !isIngame() ) return val player = event.player - val kit = kitManager.getSelectedKit( player ) ?: return - - kit.getPassiveAbility(kitManager.getSelectedPlaystyle( player )) - .onMove( player, event ) + val kit = kitManager.getSelectedKit( player ) ?: return + kit.getPassiveAbility(kitManager.getSelectedPlaystyle( player )).onMove( player, event ) } // ========================================================================= diff --git a/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt b/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt index 4061d53..4550f62 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt @@ -36,6 +36,8 @@ class GameStateListener : Listener { private val plugin = SpeedHG.instance private val gameManager = plugin.gameManager + private val feastStarted = false // später ersetzen + @EventHandler fun onLeavesDecay( event: LeavesDecayEvent @@ -118,16 +120,15 @@ class GameStateListener : Listener { return } - // TODO: add feast check - if ( block.type == Material.IRON_ORE && TODO( "Add before feast check" )) + if ( block.type == Material.IRON_ORE && !feastStarted ) { event.isCancelled = true - player.sendMsg( "build.no_iron_before_feast" ) - player.playSound( player.location, Sound.ENTITY_VILLAGER_NO, 1f, 1f ) + player.sendMsg("build.no_iron_before_feast") + player.playSound(player.location, Sound.ENTITY_VILLAGER_NO, 1f, 1f) } - else if ( block.type == Material.IRON_ORE && TODO( "Add after feast check" )) + else if ( block.type == Material.IRON_ORE && feastStarted ) { - // TODO: add 0.1 to ironFarmed in database + // TODO: Database-Aufruf } } diff --git a/src/main/kotlin/club/mcscrims/speedhg/util/Extensions.kt b/src/main/kotlin/club/mcscrims/speedhg/util/Extensions.kt index 3cdd8bf..dde2a33 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/util/Extensions.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/util/Extensions.kt @@ -8,7 +8,7 @@ import org.bukkit.entity.Player private val langManager get() = SpeedHG.instance.languageManager -val legacySerializer = LegacyComponentSerializer.builder() +internal val legacySerializer = LegacyComponentSerializer.builder() .character('§') .hexColors() .useUnusualXRepeatedCharacterHexFormat()