From ee79dd4bf413bf71940c477f05c6b90a6fe2169b Mon Sep 17 00:00:00 2001 From: TDSTOS Date: Wed, 25 Mar 2026 23:33:57 +0100 Subject: [PATCH] Add Venom kit and integrate into game Introduce a new Venom kit with aggressive (deafening wither beam) and defensive (shield of darkness) abilities by adding src/main/kotlin/.../VenomKit.kt. Wire the kit into the plugin: import and register Venom in SpeedHG (extracted registerKits()), and extend KitEventDispatcher to handle projectile interactions for Venom defensive playstyle (cancel/stop projectiles and reflect thrown potions). Update en_US.yml: add Venom localization/messages, adjust default prefix and some kit/scoreboard copytext, and add a no_permission message. --- .../kotlin/club/mcscrims/speedhg/SpeedHG.kt | 14 +- .../mcscrims/speedhg/kit/impl/VenomKit.kt | 300 ++++++++++++++++++ .../kit/listener/KitEventDispatcher.kt | 40 +++ src/main/resources/languages/en_US.yml | 54 +++- 4 files changed, 396 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/club/mcscrims/speedhg/kit/impl/VenomKit.kt diff --git a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt index 014dfaf..af18a1c 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt @@ -8,6 +8,7 @@ import club.mcscrims.speedhg.kit.KitManager import club.mcscrims.speedhg.kit.impl.BackupKit import club.mcscrims.speedhg.kit.impl.GoblinKit import club.mcscrims.speedhg.kit.impl.IceMageKit +import club.mcscrims.speedhg.kit.impl.VenomKit import club.mcscrims.speedhg.kit.listener.KitEventDispatcher import club.mcscrims.speedhg.listener.ConnectListener import club.mcscrims.speedhg.listener.GameStateListener @@ -53,10 +54,7 @@ class SpeedHG : JavaPlugin() { scoreboardManager = ScoreboardManager( this ) kitManager = KitManager( this ) - // Register kits - kitManager.registerKit( BackupKit() ) - kitManager.registerKit( GoblinKit() ) - kitManager.registerKit( IceMageKit() ) + registerKits() registerCommands() registerListener() @@ -70,6 +68,14 @@ class SpeedHG : JavaPlugin() { super.onDisable() } + private fun registerKits() + { + kitManager.registerKit( BackupKit() ) + kitManager.registerKit( GoblinKit() ) + kitManager.registerKit( IceMageKit() ) + kitManager.registerKit( VenomKit() ) + } + private fun registerCommands() { val kitCommand = KitCommand() diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/VenomKit.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/VenomKit.kt new file mode 100644 index 0000000..1b06229 --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/VenomKit.kt @@ -0,0 +1,300 @@ +package club.mcscrims.speedhg.kit.impl + +import club.mcscrims.speedhg.SpeedHG +import club.mcscrims.speedhg.kit.Kit +import club.mcscrims.speedhg.kit.Playstyle +import club.mcscrims.speedhg.kit.ability.AbilityResult +import club.mcscrims.speedhg.kit.ability.ActiveAbility +import club.mcscrims.speedhg.kit.ability.PassiveAbility +import club.mcscrims.speedhg.util.AbilityUtils +import club.mcscrims.speedhg.util.ItemBuilder +import club.mcscrims.speedhg.util.trans +import net.kyori.adventure.text.Component +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.potion.PotionEffect +import org.bukkit.potion.PotionEffectType +import org.bukkit.scheduler.BukkitRunnable +import org.bukkit.scheduler.BukkitTask +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import kotlin.math.cos +import kotlin.math.sin + +class VenomKit : Kit() { + + data class ActiveShield( + var remainingCapacity: Double = 15.0, + val expireTask: BukkitTask, + val particleTask: BukkitTask + ) + + private val plugin get() = SpeedHG.instance + private val activeShields = ConcurrentHashMap() + + override val id: String + get() = "venom" + + override val displayName: Component + get() = plugin.languageManager.getDefaultComponent( "kits.venom.name", mapOf() ) + + override val lore: List + get() = plugin.languageManager.getDefaultRawMessageList( "kits.venom.lore" ) + + override val icon: Material + get() = Material.SPIDER_EYE + + // ── Cached ability instances (avoid allocating per event call) ──────────── + private val aggressiveActive = AggressiveActive() + private val defensiveActive = DefensiveActive() + private val aggressivePassive = NoPassive() + private val defensivePassive = DefensivePassive() + + // ── Playstyle routing ───────────────────────────────────────────────────── + + override fun getActiveAbility( + playstyle: Playstyle + ): ActiveAbility = when( playstyle ) + { + Playstyle.AGGRESSIVE -> aggressiveActive + Playstyle.DEFENSIVE -> defensiveActive + } + + override fun getPassiveAbility( + playstyle: Playstyle + ): PassiveAbility = when( playstyle ) + { + Playstyle.AGGRESSIVE -> aggressivePassive + Playstyle.DEFENSIVE -> defensivePassive + } + + // ── Item distribution ───────────────────────────────────────────────────── + + override val cachedItems = ConcurrentHashMap>() + + override fun giveItems( + player: Player, + playstyle: Playstyle + ) { + when( playstyle ) + { + Playstyle.AGGRESSIVE -> + { + val witherItem = ItemBuilder( Material.WITHER_SKELETON_SKULL ) + .name( aggressiveActive.name ) + .lore(listOf( aggressiveActive.description )) + .build() + + cachedItems[ player.uniqueId ] = listOf( witherItem ) + player.inventory.addItem( witherItem ) + } + + Playstyle.DEFENSIVE -> + { + val shieldItem = ItemBuilder( Material.BLACK_SHULKER_BOX ) + .name( defensiveActive.name ) + .lore(listOf( defensiveActive.description )) + .build() + + cachedItems[ player.uniqueId ] = listOf( shieldItem ) + player.inventory.addItem( shieldItem ) + } + } + } + + // ── Optional lifecycle hooks ────────────────────────────────────────────── + + override fun onRemove( + player: Player + ) { + val items = cachedItems.remove( player.uniqueId ) ?: return + items.forEach { player.inventory.remove( it ) } + } + + private class AggressiveActive : ActiveAbility( Playstyle.AGGRESSIVE ) { + + private val plugin get() = SpeedHG.instance + + override val name: String + get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.items.wither.name" ) + + override val description: String + get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.items.wither.description" ) + + override val hitsRequired: Int + get() = 15 + + override val triggerMaterial: Material + get() = Material.WITHER_SKELETON_SKULL + + override fun execute( + player: Player + ): AbilityResult + { + player.playSound( player.location, Sound.ENTITY_BLAZE_SHOOT, 1f, 0.8f ) + + AbilityUtils.createBeam( + player.location, + player.eyeLocation.toVector(), + Particle.DRAGON_BREATH, + 7.5, 0.1 + ) { target -> + target.addPotionEffects(listOf( + PotionEffect( PotionEffectType.BLINDNESS, 100, 0 ), + PotionEffect( PotionEffectType.WITHER, 100, 0 ) + )) + target.damage( 4.0, player ) + player.world.playSound( target.location, Sound.ENTITY_DRAGON_FIREBALL_EXPLODE, 1f, 0.8f ) + } + + player.sendActionBar(player.trans( "kits.venom.messages.wither_beam" )) + return AbilityResult.Success + } + + override fun onFullyCharged( + player: Player + ) { + player.playSound( player.location, Sound.BLOCK_ANVIL_USE, 0.8f, 1.5f ) + player.sendActionBar(player.trans( "kits.venom.messages.ability_charged" )) + } + + } + + private inner class DefensiveActive : ActiveAbility( Playstyle.DEFENSIVE ) { + + private val plugin get() = SpeedHG.instance + + override val name: String + get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.items.shield.name" ) + + override val description: String + get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.items.shield.description" ) + + override val hitsRequired: Int + get() = 15 + + override val triggerMaterial: Material + get() = Material.BLACK_SHULKER_BOX + + override fun execute( + player: Player + ): AbilityResult + { + if (activeShields.containsKey( player.uniqueId )) + return AbilityResult.ConditionNotMet( "Shield is already active!" ) + + player.playSound( player.location, Sound.ENTITY_BLAZE_AMBIENT, 1f, 0.5f ) + + val particleTask = object : BukkitRunnable() { + + var rotation = 0.0 + + override fun run() + { + if ( !player.isOnline || + !plugin.gameManager.alivePlayers.contains( player.uniqueId ) || + !activeShields.containsKey( player.uniqueId )) + { + this.cancel() + return + } + + val loc = player.location + val radius = 1.2 + + for ( i in 0 until 8 ) + { + val angle = ( 2 * Math.PI * i / 8 ) + rotation + val x = cos( angle ) * radius + val z = sin( angle ) * radius + + loc.world.spawnParticle( + Particle.LARGE_SMOKE, + loc.clone().add( x, 1.2, z ), + 1, 0.0, 0.0, 0.0, 0.0 + ) + } + rotation += 0.3 + } + }.runTaskTimer( plugin, 0L, 2L ) + + val expireTask = object : BukkitRunnable() { + override fun run() + { + if (activeShields.containsKey( player.uniqueId )) + breakShield( player ) + } + }.runTaskLater( plugin, 160L ) + + activeShields[ player.uniqueId ] = ActiveShield( + remainingCapacity = 15.0, + expireTask = expireTask, + particleTask = particleTask + ) + + player.sendActionBar(player.trans( "kits.venom.messages.shield_activate" )) + return AbilityResult.Success + } + + override fun onFullyCharged( + player: Player + ) { + player.playSound( player.location, Sound.BLOCK_ANVIL_USE, 0.8f, 1.5f ) + player.sendActionBar(player.trans( "kits.venom.messages.ability_charged" )) + } + + } + + private inner class DefensivePassive : PassiveAbility( Playstyle.DEFENSIVE ) { + + private val plugin get() = SpeedHG.instance + + override val name: String + get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.passive.name" ) + + override val description: String + get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.passive.description" ) + + override fun onHitByEnemy( + victim: Player, + attacker: Player, + event: EntityDamageByEntityEvent + ) { + val shield = activeShields[ victim.uniqueId ] ?: return + shield.remainingCapacity -= event.damage + val isCrit = event.isCritical + event.damage = if ( isCrit ) 3.0 else 2.0 + if ( shield.remainingCapacity <= 0 ) breakShield( victim ) + } + + } + + private class NoPassive : PassiveAbility( Playstyle.AGGRESSIVE ) { + + override val name: String + get() = "None" + + override val description: String + get() = "None" + + } + + // ── Helper methods ────────────────────────────────────────────── + + private fun breakShield( + player: Player + ) { + val shield = activeShields.remove( player.uniqueId ) ?: return + + shield.expireTask.cancel() + shield.particleTask.cancel() + + player.world.playSound( player.location, Sound.ENTITY_WITHER_BREAK_BLOCK, 1f, 1f ) + player.sendActionBar(player.trans( "kits.venom.messages.shield_break" )) + } + +} \ No newline at end of file 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 668e3d7..1f9c1ba 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/listener/KitEventDispatcher.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/listener/KitEventDispatcher.kt @@ -3,14 +3,20 @@ package club.mcscrims.speedhg.kit.listener import club.mcscrims.speedhg.SpeedHG import club.mcscrims.speedhg.game.GameState import club.mcscrims.speedhg.kit.KitManager +import club.mcscrims.speedhg.kit.Playstyle import club.mcscrims.speedhg.kit.ability.AbilityResult import club.mcscrims.speedhg.kit.impl.IceMageKit +import club.mcscrims.speedhg.kit.impl.VenomKit import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor import org.bukkit.NamespacedKey +import org.bukkit.Sound +import org.bukkit.entity.Arrow +import org.bukkit.entity.Egg import org.bukkit.entity.LivingEntity import org.bukkit.entity.Player import org.bukkit.entity.Snowball +import org.bukkit.entity.ThrownPotion import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener @@ -237,6 +243,40 @@ class KitEventDispatcher( } } + // ========================================================================= + // Venom Listener + // ========================================================================= + + @EventHandler + fun onProjectileHit( + event: ProjectileHitEvent + ) { + val victim = event.hitEntity as? Player ?: return + val projectile = event.entity + + if (kitManager.getSelectedKit( victim ) !is VenomKit ) return + if (kitManager.getSelectedPlaystyle( victim ) != Playstyle.DEFENSIVE ) return + + when( projectile ) + { + is Snowball, is Egg, is Arrow -> + { + event.isCancelled = true + projectile.velocity = Vector( 0.0, -0.2, 0.0 ) + } + is ThrownPotion -> + { + event.isCancelled = true + + val reverseVector = projectile.velocity.multiply( -1.5 ) + projectile.velocity = reverseVector + + projectile.shooter = victim + victim.world.playSound( victim.location, Sound.ITEM_SHIELD_BLOCK, 1f, 1.5f ) + } + } + } + // ========================================================================= // Helpers // ========================================================================= diff --git a/src/main/resources/languages/en_US.yml b/src/main/resources/languages/en_US.yml index 7bd86c6..61f7cb3 100644 --- a/src/main/resources/languages/en_US.yml +++ b/src/main/resources/languages/en_US.yml @@ -4,7 +4,8 @@ # default: - prefix: 'McScrims | ' + prefix: 'SpeedHG | ' + no_permission: 'Insufficient permissions.' game: join: ' has joined the game.' @@ -57,7 +58,7 @@ scoreboard: title: 'SpeedHG' lobby: - " " - - "Spieler: /" + - "Players: /" - "Kit: " - "" - "Waiting for start..." @@ -89,8 +90,11 @@ kits: name: 'Goblin' lore: - ' ' - - 'Use your abilities to either copy' - - 'your enemies kit or hide in a bunker' + - 'AGGRESSIVE:' + - 'Copy your enemies kit' + - ' ' + - 'DEFENSIVE:' + - 'Summon a bunker for protection' - ' ' - 'PlayStyle: §e%playstyle%' - ' ' @@ -111,9 +115,11 @@ kits: name: 'IceMage' lore: - ' ' - - 'Use your abilities to freeze players' - - 'or gain speed in ice biomes and slow' - - 'enemies.' + - 'AGGRESSIVE:' + - 'Gain speed in ice biomes and give slowness' + - ' ' + - 'DEFENSIVE:' + - 'Summon snowballs and freeze enemies' - ' ' - 'PlayStyle: §e%playstyle%' - ' ' @@ -123,6 +129,38 @@ kits: snowball: name: '§bFreeze' description: 'Freeze your enemies by throwing snowballs in all directions' + passive: + name: '§bIceStorm' + description: 'Gain speed in cold biomes and give enemies slowness' messages: shoot_snowballs: 'You have shot frozen snowballs in all directions!' - ability_charged: 'Your ability has been recharged!' \ No newline at end of file + ability_charged: 'Your ability has been recharged!' + venom: + name: 'Venom' + lore: + - ' ' + - 'AGGRESSIVE:' + - 'Summon a deafening beam' + - ' ' + - 'DEFENSIVE:' + - 'Create a shield for protection' + - ' ' + - 'PlayStyle: §e%playstyle%' + - ' ' + - 'Left-click to select' + - 'Right-click to change playstyle' + items: + wither: + name: '§8Deafening Beam' + description: 'Summon a deafening beam against an enemy' + shield: + name: '§8Shield of Darkness' + description: 'Create a shield and get protected against hits and projectiles' + passive: + name: '§8Shield of Darkness' + description: 'Create a shield and get protected against hits and projectiles' + messages: + wither_beam: 'You have summoned your deafening beam!' + shield_activate: 'Your shield of darkness has been activated!' + shield_break: 'Your shield of darkness has broken!' + ability_charged: 'Your ability has been recharged' \ No newline at end of file