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.
This commit is contained in:
TDSTOS
2026-03-25 03:28:52 +01:00
parent cc265a1dea
commit 19bd708b59
7 changed files with 84 additions and 47 deletions

View File

@@ -13,8 +13,13 @@ class LanguageManager(
private val plugin: JavaPlugin private val plugin: JavaPlugin
) { ) {
private data class LangData(
val strings: Map<String, String>,
val lists: Map<String, List<String>>
)
// Map: Sprachcode -> (Key -> Nachricht) // Map: Sprachcode -> (Key -> Nachricht)
private val languages = ConcurrentHashMap<String, Map<String, String>>() private val languages = ConcurrentHashMap<String, LangData>()
private val miniMessage = MiniMessage.miniMessage() private val miniMessage = MiniMessage.miniMessage()
private val defaultLanguage = plugin.config.getString("default.language", "en_US")!! private val defaultLanguage = plugin.config.getString("default.language", "en_US")!!
@@ -37,12 +42,15 @@ class LanguageManager(
val langCode = file.nameWithoutExtension val langCode = file.nameWithoutExtension
val config = YamlConfiguration.loadConfiguration( file ) val config = YamlConfiguration.loadConfiguration( file )
val messages = config.getKeys( true ) val strings = config.getKeys( true )
.filter { config.isString( it ) } .filter { config.isString( it ) }
.associateWith { config.getString( it ) } .associateWith { config.getString( it )!! }
languages[ langCode ] = messages as Map<String, String> val lists = config.getKeys( true )
plugin.logger.info("Sprache geladen: $langCode (${messages.size} Nachrichten)") .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 locale = player.locale().toString()
val langMap = languages[ locale ] ?: languages[ defaultLanguage ] val langMap = languages[ locale ] ?: languages[ defaultLanguage ]
return langMap?.get( key ) ?: "<red>Missing Key: $key</red>" return langMap?.strings?.get( key ) ?: "<red>Missing Key: $key</red>"
} }
fun getDefaultRawMessage( fun getDefaultRawMessage(
key: String key: String
): String ): String
{ {
return languages[ defaultLanguage ]?.get( key ) ?: "<red>Missing Key: $key</red>" return languages[ defaultLanguage ]?.strings?.get( key ) ?: "<red>Missing Key: $key</red>"
} }
fun getRawMessageList( fun getRawMessageList(
@@ -77,25 +85,17 @@ class LanguageManager(
key: String key: String
): List<String> ): List<String>
{ {
var locale = player.locale().toString() val locale = player.locale().toString()
val langMap = languages[ locale ] val data = languages[ locale ] ?: languages[ defaultLanguage ]
return data?.lists?.get( key ) ?: listOf( "<red>Missing List: $key</red>" )
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( "<red>Missing List: $key</red>" )
} }
fun getDefaultRawMessageList( fun getDefaultRawMessageList(
key: String key: String
): List<String> ): List<String>
{ {
val file = File( plugin.dataFolder, "languages/$defaultLanguage.yml" ) val data = languages[ defaultLanguage ]
val config = YamlConfiguration.loadConfiguration( file ) return data?.lists?.get( key ) ?: listOf( "<red>Missing List: $key</red>" )
return if (config.contains( key )) config.getStringList( key ) else listOf( "<red>Missing List: $key</red>" )
} }
fun getComponent( fun getComponent(
@@ -116,7 +116,7 @@ class LanguageManager(
placeholders: Map<String, String> placeholders: Map<String, String>
): Component ): Component
{ {
val raw = languages[ defaultLanguage ]?.get( key ) ?: "<red>Missing Key: $key</red>" val raw = languages[ defaultLanguage ]?.strings?.get( key ) ?: "<red>Missing Key: $key</red>"
val tags = placeholders.map { (k, v) -> Placeholder.parsed( k, v ) } val tags = placeholders.map { (k, v) -> Placeholder.parsed( k, v ) }
return miniMessage.deserialize( raw, *tags.toTypedArray() ) return miniMessage.deserialize( raw, *tags.toTypedArray() )
} }

View File

@@ -134,11 +134,12 @@ class GameManager(
world.time = 0 world.time = 0
world.setStorm( false ) world.setStorm( false )
val border = world.worldBorder world.worldBorder.apply {
border.center = Location( world, 0.0, 0.0, 0.0 ) center = Location( world, 0.0, 0.0, 0.0 )
border.size = startBorder size = startBorder
border.damageBuffer = 0.0 damageBuffer = 0.0
border.damageAmount = 1.0 damageAmount = 1.0
}
val speedEffect = PotionEffect( val speedEffect = PotionEffect(
PotionEffectType.SPEED, PotionEffectType.SPEED,

View File

@@ -129,7 +129,16 @@ class KitManager(
fun clearAll() fun clearAll()
{ {
selectedKits.keys.toList().forEach { uuid -> 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() selectedKits.clear()
selectedPlaystyles.clear() selectedPlaystyles.clear()

View File

@@ -16,6 +16,7 @@ import org.bukkit.Material
import org.bukkit.Sound import org.bukkit.Sound
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.scheduler.BukkitTask
import java.util.UUID import java.util.UUID
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@@ -98,12 +99,15 @@ class GoblinKit : Kit() {
override fun onRemove( override fun onRemove(
player: Player player: Player
) { ) {
val items = cachedItems[ player.uniqueId ] ?: return aggressiveActive.cancelStealTask( player )
player.inventory.removeAll { items.contains( it ) } val items = cachedItems.remove( player.uniqueId ) ?: return
items.forEach { player.inventory.remove( it ) }
} }
private inner class AggressiveActive : ActiveAbility( Playstyle.AGGRESSIVE ) { private inner class AggressiveActive : ActiveAbility( Playstyle.AGGRESSIVE ) {
private val plugin get() = SpeedHG.instance
override val name: String override val name: String
get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.steal.name" ) get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.steal.name" )
@@ -116,6 +120,8 @@ class GoblinKit : Kit() {
override val triggerMaterial: Material override val triggerMaterial: Material
get() = Material.GLASS get() = Material.GLASS
private val activeStealTasks = ConcurrentHashMap<UUID, BukkitTask>()
override fun execute( override fun execute(
player: Player player: Player
): AbilityResult ): AbilityResult
@@ -129,16 +135,25 @@ class GoblinKit : Kit() {
val currentKit = plugin.kitManager.getSelectedKit( player ) val currentKit = plugin.kitManager.getSelectedKit( player )
?: return AbilityResult.ConditionNotMet( "Error while copying kit" ) ?: return AbilityResult.ConditionNotMet( "Error while copying kit" )
activeStealTasks.remove( player.uniqueId )
plugin.kitManager.removeKit( player ) plugin.kitManager.removeKit( player )
plugin.kitManager.selectKit( player, targetKit ) plugin.kitManager.selectKit( player, targetKit )
plugin.kitManager.applyKit( player ) plugin.kitManager.applyKit( player )
Bukkit.getScheduler().runTaskLater( plugin, { -> val task = Bukkit.getScheduler().runTaskLater( plugin, { ->
plugin.kitManager.removeKit( player ) activeStealTasks.remove( player.uniqueId )
plugin.kitManager.selectKit( player, currentKit ) // Nur wiederherstellen, wenn Spieler noch alive und Spiel läuft
plugin.kitManager.applyKit( player ) if (plugin.gameManager.alivePlayers.contains( player.uniqueId ))
{
plugin.kitManager.removeKit( player )
plugin.kitManager.selectKit( player, currentKit )
plugin.kitManager.applyKit( player )
}
}, 20L * 60) }, 20L * 60)
activeStealTasks[ player.uniqueId ] = task
player.playSound( player.location, Sound.ENTITY_EVOKER_CAST_SPELL, 1f, 1.5f ) 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 ))) 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" )) 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 inner class DefensiveActive : ActiveAbility( Playstyle.DEFENSIVE ) {
private val plugin get() = SpeedHG.instance
override val name: String override val name: String
get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.bunker.name" ) get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.bunker.name" )

View File

@@ -75,9 +75,9 @@ class KitEventDispatcher(
.onHitEnemy( attacker, victim, event ) .onHitEnemy( attacker, victim, event )
// ── 3. Victim passive hook ──────────────────────────────────────────── // ── 3. Victim passive hook ────────────────────────────────────────────
val victimKit = kitManager.getSelectedKit( victim ) ?: return kitManager.getSelectedKit( victim )
victimKit.getPassiveAbility(kitManager.getSelectedPlaystyle( victim )) ?.getPassiveAbility(kitManager.getSelectedPlaystyle( victim ))
.onHitByEnemy( victim, attacker, event ) ?.onHitByEnemy( victim, attacker, event )
} }
// ========================================================================= // =========================================================================
@@ -159,13 +159,16 @@ class KitEventDispatcher(
fun onMove( fun onMove(
event: PlayerMoveEvent 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 if ( !isIngame() ) return
val player = event.player val player = event.player
val kit = kitManager.getSelectedKit( player ) ?: return val kit = kitManager.getSelectedKit( player ) ?: return
kit.getPassiveAbility(kitManager.getSelectedPlaystyle( player )).onMove( player, event )
kit.getPassiveAbility(kitManager.getSelectedPlaystyle( player ))
.onMove( player, event )
} }
// ========================================================================= // =========================================================================

View File

@@ -36,6 +36,8 @@ class GameStateListener : Listener {
private val plugin = SpeedHG.instance private val plugin = SpeedHG.instance
private val gameManager = plugin.gameManager private val gameManager = plugin.gameManager
private val feastStarted = false // später ersetzen
@EventHandler @EventHandler
fun onLeavesDecay( fun onLeavesDecay(
event: LeavesDecayEvent event: LeavesDecayEvent
@@ -118,16 +120,15 @@ class GameStateListener : Listener {
return return
} }
// TODO: add feast check if ( block.type == Material.IRON_ORE && !feastStarted )
if ( block.type == Material.IRON_ORE && TODO( "Add before feast check" ))
{ {
event.isCancelled = true event.isCancelled = true
player.sendMsg( "build.no_iron_before_feast" ) player.sendMsg("build.no_iron_before_feast")
player.playSound( player.location, Sound.ENTITY_VILLAGER_NO, 1f, 1f ) 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
} }
} }

View File

@@ -8,7 +8,7 @@ import org.bukkit.entity.Player
private val langManager get() = SpeedHG.instance.languageManager private val langManager get() = SpeedHG.instance.languageManager
val legacySerializer = LegacyComponentSerializer.builder() internal val legacySerializer = LegacyComponentSerializer.builder()
.character('§') .character('§')
.hexColors() .hexColors()
.useUnusualXRepeatedCharacterHexFormat() .useUnusualXRepeatedCharacterHexFormat()