From cd8e3e37a78c445763825bd12adb82694447cf0d Mon Sep 17 00:00:00 2001 From: TDSTOS Date: Sat, 11 Apr 2026 00:49:42 +0200 Subject: [PATCH] Integrate Lunar Client and gameplay tweaks Add Lunar Client support and make various kit/listener/ui adjustments. - Add Apollo (Lunar Client) compileOnly dependencies and new LunarClientManager wired into plugin initialization. Update plugin.yml (depend Apollo-Bukkit, add speedhg.admin.staff permission) and config.yml (lunarclient name/variantName) for rich presence and mod settings. - Rework Tesla kit passive: remove periodic aura task in favor of chance-on-hit effect (AURA_CHANCE) to reduce continuous scheduling and simplify behavior; adjust constants and particle/sound logic accordingly. - Add Anchor golem death handler to KitEventDispatcher to suppress drops/exp for golems spawned by AnchorKit. - Update AnvilSearchMenu to clear item-on-cursor before closing and ensure search tracker is unregistered in the right order. - Remove join/quit attack-speed cooldown handling in GameStateListener and tidy up some permission/command descriptions in plugin.yml. These changes enable Lunar features, improve performance by removing repeated scheduled tasks, and fix gameplay/drop/UI edge cases. --- build.gradle.kts | 3 + .../kotlin/club/mcscrims/speedhg/SpeedHG.kt | 5 + .../speedhg/client/LunarClientManager.kt | 85 +++++++++++++++ .../speedhg/gui/anvil/AnvilSearchMenu.kt | 11 +- .../mcscrims/speedhg/kit/impl/TeslaKit.kt | 102 +++++------------- .../kit/listener/KitEventDispatcher.kt | 15 +++ .../speedhg/listener/GameStateListener.kt | 13 --- src/main/resources/config.yml | 4 + src/main/resources/plugin.yml | 16 +-- 9 files changed, 158 insertions(+), 96 deletions(-) create mode 100644 src/main/kotlin/club/mcscrims/speedhg/client/LunarClientManager.kt diff --git a/build.gradle.kts b/build.gradle.kts index d389978..3a93179 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,9 @@ dependencies { implementation(libs.kotlinxCoroutines) implementation(libs.kotlinxSerialization) + compileOnly("com.lunarclient:apollo-api:1.2.4") + compileOnly("com.lunarclient:apollo-extra-adventure4:1.2.4") + compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT") compileOnly("com.sk89q.worldedit:worldedit-core:7.2.17-SNAPSHOT") compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.2.17-SNAPSHOT") diff --git a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt index cfa125f..e5b3020 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt @@ -1,5 +1,6 @@ package club.mcscrims.speedhg +import club.mcscrims.speedhg.client.LunarClientManager import club.mcscrims.speedhg.command.KitCommand import club.mcscrims.speedhg.command.LeaderboardCommand import club.mcscrims.speedhg.command.PerksCommand @@ -106,6 +107,9 @@ class SpeedHG : JavaPlugin() { lateinit var teamManager: TeamManager private set + lateinit var lunarClientManager: LunarClientManager + private set + override fun onLoad() { instance = this @@ -159,6 +163,7 @@ class SpeedHG : JavaPlugin() { scoreboardManager = ScoreboardManager( this ) kitManager = KitManager( this ) discordWebhookManager = DiscordWebhookManager( this ) + lunarClientManager = LunarClientManager( this ) perkManager = PerkManager( this ) perkManager.initialize() diff --git a/src/main/kotlin/club/mcscrims/speedhg/client/LunarClientManager.kt b/src/main/kotlin/club/mcscrims/speedhg/client/LunarClientManager.kt new file mode 100644 index 0000000..b77e700 --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/client/LunarClientManager.kt @@ -0,0 +1,85 @@ +package club.mcscrims.speedhg.client + +import club.mcscrims.speedhg.SpeedHG +import com.lunarclient.apollo.Apollo +import com.lunarclient.apollo.event.ApolloListener +import com.lunarclient.apollo.event.Listen +import com.lunarclient.apollo.event.player.ApolloRegisterPlayerEvent +import com.lunarclient.apollo.mods.impl.ModFreelook +import com.lunarclient.apollo.mods.impl.ModMinimap +import com.lunarclient.apollo.mods.impl.ModSnaplook +import com.lunarclient.apollo.mods.impl.ModTeamView +import com.lunarclient.apollo.mods.impl.ModWaypoints +import com.lunarclient.apollo.module.modsetting.ModSettingModule +import com.lunarclient.apollo.module.richpresence.RichPresenceModule +import com.lunarclient.apollo.module.richpresence.ServerRichPresence +import com.lunarclient.apollo.module.staffmod.StaffMod +import com.lunarclient.apollo.module.staffmod.StaffModModule +import com.lunarclient.apollo.player.ApolloPlayer +import org.bukkit.Bukkit + +class LunarClientManager( + private val plugin: SpeedHG +) : ApolloListener { + + private val modSettingModule: ModSettingModule + = Apollo.getModuleManager().getModule( ModSettingModule::class.java ) + + private val staffModModule: StaffModModule + = Apollo.getModuleManager().getModule( StaffModModule::class.java ) + + private val richPresenceModule: RichPresenceModule + = Apollo.getModuleManager().getModule( RichPresenceModule::class.java ) + + init { + this.handle( ApolloRegisterPlayerEvent::class.java, this::onApolloRegister ) + Bukkit.getScheduler().runTaskTimer( plugin, { -> Apollo.getPlayerManager().players.forEach( this::setRichPresence ) }, 20L, 20L ) + } + + @Listen + fun onApolloRegister( + event: ApolloRegisterPlayerEvent + ) { + val player = event.player + setMods( player ) + setRichPresence( player ) + } + + private fun setRichPresence( + player: ApolloPlayer + ) { + val teamSize = plugin.teamManager.getTeam( player.uniqueId )?.size ?: 1 + val currentState = plugin.gameManager.currentState.name + + val presence = ServerRichPresence.builder() + .gameName(plugin.config.getString( "lunarclient.name" )) + .gameState( currentState ) + .gameVariantName(plugin.config.getString( "lunarclient.variantName" )) + .playerState( currentState ) + .teamCurrentSize( teamSize ) + .teamMaxSize( plugin.teamManager.maxTeamSize ) + .build() + + richPresenceModule.overrideServerRichPresence( player, presence ) + } + + private fun setMods( + player: ApolloPlayer + ) { + if (player.hasPermission( "speedhg.admin.staff" )) + { + staffModModule.enableStaffMods( player, StaffMod.entries ) + } + else + { + staffModModule.disableAllStaffMods( player ) + } + + modSettingModule.options.set( player, ModMinimap.ENABLED, false ) + modSettingModule.options.set( player, ModFreelook.ENABLED, false ) + modSettingModule.options.set( player, ModSnaplook.ENABLED, false ) + modSettingModule.options.set( player, ModWaypoints.ENABLED, false ) + modSettingModule.options.set( player, ModTeamView.ENABLED, true ) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/gui/anvil/AnvilSearchMenu.kt b/src/main/kotlin/club/mcscrims/speedhg/gui/anvil/AnvilSearchMenu.kt index b1e7956..49a252d 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/gui/anvil/AnvilSearchMenu.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/gui/anvil/AnvilSearchMenu.kt @@ -35,12 +35,17 @@ class AnvilSearchMenu( fun onClick(event: InventoryClickEvent, view: AnvilView) { event.isCancelled = true - if (event.rawSlot != 2) return + if ( event.rawSlot != 2 ) return val query = view.renameText ?: "" + + if ( !player.itemOnCursor.type.isAir ) { + player.setItemOnCursor(ItemStack( Material.AIR )) + } + + AnvilSearchTracker.unregister( player ) player.closeInventory() - AnvilSearchTracker.unregister(player) - returnMenu.applySearch(query) + returnMenu.applySearch( query ) } fun onClose() { diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/TeslaKit.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/TeslaKit.kt index e17149e..859252f 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/TeslaKit.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/TeslaKit.kt @@ -14,6 +14,7 @@ import org.bukkit.Material import org.bukkit.Particle import org.bukkit.Sound import org.bukkit.entity.Player +import org.bukkit.event.entity.EntityDamageByEntityEvent import org.bukkit.inventory.ItemStack import org.bukkit.scheduler.BukkitTask import org.bukkit.util.Vector @@ -72,12 +73,8 @@ class TeslaKit : Kit() { const val BOLT_STAGGER_TICKS = 8L // Passive Aura - const val AURA_RADIUS_AGGRESSIVE = 4.0 - const val AURA_RADIUS_DEFENSIVE = 6.0 - const val AURA_INTERVAL_TICKS = 60L + const val AURA_CHANCE = 0.05 const val AURA_FIRE_TICKS = 60 - const val KNOCKBACK_AGGRESSIVE = 1.6 - const val KNOCKBACK_DEFENSIVE = 2.3 } // ── Gecachte Instanzen ──────────────────────────────────────────────────── @@ -85,14 +82,10 @@ class TeslaKit : Kit() { private val aggressiveActive = AggressiveActive() private val defensiveActive = NoActive(Playstyle.DEFENSIVE) private val aggressivePassive = TeslaPassive( - playstyle = Playstyle.AGGRESSIVE, - auraRadius = AURA_RADIUS_AGGRESSIVE, - knockbackStrength = KNOCKBACK_AGGRESSIVE + playstyle = Playstyle.AGGRESSIVE ) private val defensivePassive = TeslaPassive( - playstyle = Playstyle.DEFENSIVE, - auraRadius = AURA_RADIUS_DEFENSIVE, - knockbackStrength = KNOCKBACK_DEFENSIVE + playstyle = Playstyle.DEFENSIVE ) override fun getActiveAbility( @@ -216,83 +209,44 @@ class TeslaKit : Kit() { // ========================================================================= class TeslaPassive( - playstyle: Playstyle, - private val auraRadius: Double, - private val knockbackStrength: Double + playstyle: Playstyle ) : PassiveAbility( playstyle ) { private val plugin get() = SpeedHG.instance - private val auraTasks = ConcurrentHashMap() + private val rng = Random() override val name: String get() = plugin.languageManager.getDefaultRawMessage( "kits.tesla.passive.name" ) override val description: String get() = plugin.languageManager.getDefaultRawMessage( "kits.tesla.passive.description" ) - override fun onActivate( - player: Player + override fun onHitByEnemy( + victim: Player, + attacker: Player, + event: EntityDamageByEntityEvent ) { - val task = Bukkit.getScheduler().runTaskTimer( plugin, { -> + if ( rng.nextDouble() > AURA_CHANCE ) + return - // Spieler oder Spielstatus nicht mehr gültig -> Task beenden - if ( !player.isOnline || - !plugin.gameManager.alivePlayers.contains( player.uniqueId )) - { - auraTasks.remove( player.uniqueId )?.cancel() - return@runTaskTimer - } + attacker.fireTicks = AURA_FIRE_TICKS - // Höhen-Check; kein Effekt über der Grenze - if ( player.location.y > MAX_HEIGHT_Y ) - return@runTaskTimer + attacker.world.spawnParticle( + Particle.ELECTRIC_SPARK, + attacker.location.clone().add( 0.0, 1.0, 0.0 ), + 10, 0.3, 0.4, 0.3, 0.06 + ) - val nearbyEnemies = player.world - .getNearbyEntities( player.location, auraRadius, auraRadius, auraRadius ) - .filterIsInstance() - .filter { it != player && plugin.gameManager.alivePlayers.contains( it.uniqueId ) } + victim.world.spawnParticle( + Particle.ELECTRIC_SPARK, + victim.location.clone().add( 0.0, 1.0, 0.0 ), + 6, 0.6, 0.6, 0.6, 0.02 + ) - if ( nearbyEnemies.isEmpty() ) - return@runTaskTimer - - nearbyEnemies.forEach { enemy -> - // Velocity-basierter Rückschlag (radial nach außen) - val pushDir: Vector = enemy.location.toVector() - .subtract( player.location.toVector() ) - .normalize() - .multiply( knockbackStrength ) - .setY( 0.3 ) - - enemy.velocity = enemy.velocity.add( pushDir ) - enemy.fireTicks = AURA_FIRE_TICKS - - enemy.world.spawnParticle( - Particle.ELECTRIC_SPARK, - enemy.location.clone().add( 0.0, 1.0, 0.0 ), - 10, 0.3, 0.4, 0.3, 0.06 - ) - } - - // Visuelles Feedback am Tesla-Spieler - player.world.spawnParticle( - Particle.ELECTRIC_SPARK, - player.location.clone().add( 0.0, 1.0, 0.0 ), - 6, 0.6, 0.6, 0.6, 0.02 - ) - player.world.playSound( - player.location, - Sound.ENTITY_LIGHTNING_BOLT_IMPACT, - 0.4f, 1.9f - ) - - }, AURA_INTERVAL_TICKS, AURA_INTERVAL_TICKS ) - - auraTasks[ player.uniqueId ] = task - } - - override fun onDeactivate( - player: Player - ) { - auraTasks.remove( player.uniqueId )?.cancel() + victim.world.playSound( + victim.location, + Sound.ENTITY_LIGHTNING_BOLT_IMPACT, + 0.4f, 1.9f + ) } } 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 6c02d3e..4ec5f72 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/listener/KitEventDispatcher.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/listener/KitEventDispatcher.kt @@ -7,6 +7,7 @@ import club.mcscrims.speedhg.kit.KitMetaData import club.mcscrims.speedhg.kit.Playstyle import club.mcscrims.speedhg.kit.ability.AbilityResult import club.mcscrims.speedhg.kit.charge.ChargeState +import club.mcscrims.speedhg.kit.impl.AnchorKit import club.mcscrims.speedhg.kit.impl.BlackPantherKit import club.mcscrims.speedhg.kit.impl.IceMageKit import club.mcscrims.speedhg.kit.impl.VenomKit @@ -25,6 +26,7 @@ import org.bukkit.event.EventPriority import org.bukkit.event.Listener import org.bukkit.event.block.BlockBreakEvent import org.bukkit.event.entity.EntityDamageByEntityEvent +import org.bukkit.event.entity.EntityDeathEvent import org.bukkit.event.entity.EntityExplodeEvent import org.bukkit.event.entity.PlayerDeathEvent import org.bukkit.event.entity.ProjectileHitEvent @@ -377,6 +379,19 @@ class KitEventDispatcher( kit.onItemBreak( event.player, event.brokenItem ) } + @EventHandler(priority = EventPriority.HIGH) + fun onAnchorGolemDeath( + event: EntityDeathEvent + ) { + val golem = event.entity as? IronGolem ?: return + + val pdcKey = NamespacedKey( plugin, AnchorKit.PDC_KEY ) + if (!golem.persistentDataContainer.has( pdcKey, PersistentDataType.STRING )) return + + event.drops.clear() + event.droppedExp = 0 + } + // ========================================================================= // Helpers // ========================================================================= diff --git a/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt b/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt index e211efd..cb33ad2 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt @@ -235,19 +235,6 @@ class GameStateListener : Listener { event.isCancelled = true } - @EventHandler - fun onJoin( event: PlayerJoinEvent ) { disableHitCooldown( event.player ) } - - @EventHandler - fun onQuit( event: PlayerQuitEvent ) { disableHitCooldown( event.player ) } - - private fun disableHitCooldown( - player: Player - ) { - val attackSpeed = player.getAttribute( Attribute.GENERIC_ATTACK_SPEED ) - if ( attackSpeed != null ) attackSpeed.baseValue = 40.0 - } - private val lapisLazuli = Material.LAPIS_LAZULI @EventHandler diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 63d6831..27c926b 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -21,6 +21,10 @@ 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 +lunarclient: + name: 'McScrims Network' + variantName: 'SpeedHG - Solo' + teams: enabled: true max-size: 2 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index cae3080..6566710 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,16 +5,20 @@ api-version: '1.21' depend: - "WorldEdit" + - "Apollo-Bukkit" permissions: speedhg.bypass: description: 'Allows joining the server while its running' default: false speedhg.admin.timer: - description: 'Change the current game time' - default: false + description: 'Allows changing the game timer' + default: op speedhg.admin.ranking: - description: 'Manage the ranking system (toggle unranked mode, inspect ranks)' + description: 'Allows managing the ranking system' + default: op + speedhg.admin.staff: + description: 'Staff permission for Lunar Client' default: false commands: @@ -25,11 +29,11 @@ commands: description: 'View the top 10 players' usage: '/leaderboard' timer: - description: 'Change the current game time (Admin Command)' - usage: '/timer ' + description: 'Change the current game time (Admin)' + usage: '/timer