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.
This commit is contained in:
TDSTOS
2026-03-25 23:33:57 +01:00
parent 0f95499a0f
commit ee79dd4bf4
4 changed files with 396 additions and 12 deletions

View File

@@ -8,6 +8,7 @@ import club.mcscrims.speedhg.kit.KitManager
import club.mcscrims.speedhg.kit.impl.BackupKit import club.mcscrims.speedhg.kit.impl.BackupKit
import club.mcscrims.speedhg.kit.impl.GoblinKit import club.mcscrims.speedhg.kit.impl.GoblinKit
import club.mcscrims.speedhg.kit.impl.IceMageKit 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.kit.listener.KitEventDispatcher
import club.mcscrims.speedhg.listener.ConnectListener import club.mcscrims.speedhg.listener.ConnectListener
import club.mcscrims.speedhg.listener.GameStateListener import club.mcscrims.speedhg.listener.GameStateListener
@@ -53,10 +54,7 @@ class SpeedHG : JavaPlugin() {
scoreboardManager = ScoreboardManager( this ) scoreboardManager = ScoreboardManager( this )
kitManager = KitManager( this ) kitManager = KitManager( this )
// Register kits registerKits()
kitManager.registerKit( BackupKit() )
kitManager.registerKit( GoblinKit() )
kitManager.registerKit( IceMageKit() )
registerCommands() registerCommands()
registerListener() registerListener()
@@ -70,6 +68,14 @@ class SpeedHG : JavaPlugin() {
super.onDisable() super.onDisable()
} }
private fun registerKits()
{
kitManager.registerKit( BackupKit() )
kitManager.registerKit( GoblinKit() )
kitManager.registerKit( IceMageKit() )
kitManager.registerKit( VenomKit() )
}
private fun registerCommands() private fun registerCommands()
{ {
val kitCommand = KitCommand() val kitCommand = KitCommand()

View File

@@ -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<UUID, ActiveShield>()
override val id: String
get() = "venom"
override val displayName: Component
get() = plugin.languageManager.getDefaultComponent( "kits.venom.name", mapOf() )
override val lore: List<String>
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<UUID, List<ItemStack>>()
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" ))
}
}

View File

@@ -3,14 +3,20 @@ package club.mcscrims.speedhg.kit.listener
import club.mcscrims.speedhg.SpeedHG import club.mcscrims.speedhg.SpeedHG
import club.mcscrims.speedhg.game.GameState import club.mcscrims.speedhg.game.GameState
import club.mcscrims.speedhg.kit.KitManager import club.mcscrims.speedhg.kit.KitManager
import club.mcscrims.speedhg.kit.Playstyle
import club.mcscrims.speedhg.kit.ability.AbilityResult import club.mcscrims.speedhg.kit.ability.AbilityResult
import club.mcscrims.speedhg.kit.impl.IceMageKit 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.Component
import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.NamespacedKey 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.LivingEntity
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.entity.Snowball import org.bukkit.entity.Snowball
import org.bukkit.entity.ThrownPotion
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority import org.bukkit.event.EventPriority
import org.bukkit.event.Listener 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 // Helpers
// ========================================================================= // =========================================================================

View File

@@ -4,7 +4,8 @@
# #
default: default:
prefix: '<gradient:dark_green:gold>McScrims</gradient> <dark_gray>|</dark_gray> <reset>' prefix: '<gradient:red:gold>SpeedHG</gradient> <dark_gray>|</dark_gray> <reset>'
no_permission: '<prefix><red>Insufficient permissions.</red>'
game: game:
join: '<prefix><green><name></green> <gray>has joined the game.</gray>' join: '<prefix><green><name></green> <gray>has joined the game.</gray>'
@@ -57,7 +58,7 @@ scoreboard:
title: '<gradient:red:gold><bold>SpeedHG</bold></gradient>' title: '<gradient:red:gold><bold>SpeedHG</bold></gradient>'
lobby: lobby:
- "<gray><st> " - "<gray><st> "
- "Spieler: <green><online>/<max>" - "Players: <green><online>/<max>"
- "Kit: <yellow><kit>" - "Kit: <yellow><kit>"
- "" - ""
- "<gray>Waiting for start..." - "<gray>Waiting for start..."
@@ -89,8 +90,11 @@ kits:
name: '<gradient:dark_green:gray><bold>Goblin</bold></gradient>' name: '<gradient:dark_green:gray><bold>Goblin</bold></gradient>'
lore: lore:
- ' ' - ' '
- 'Use your abilities to either copy' - 'AGGRESSIVE:'
- 'your enemies kit or hide in a bunker' - 'Copy your enemies kit'
- ' '
- 'DEFENSIVE:'
- 'Summon a bunker for protection'
- ' ' - ' '
- 'PlayStyle: §e%playstyle%' - 'PlayStyle: §e%playstyle%'
- ' ' - ' '
@@ -111,9 +115,11 @@ kits:
name: '<gradient:dark_aqua:aqua>IceMage</gradient>' name: '<gradient:dark_aqua:aqua>IceMage</gradient>'
lore: lore:
- ' ' - ' '
- 'Use your abilities to freeze players' - 'AGGRESSIVE:'
- 'or gain speed in ice biomes and slow' - 'Gain speed in ice biomes and give slowness'
- 'enemies.' - ' '
- 'DEFENSIVE:'
- 'Summon snowballs and freeze enemies'
- ' ' - ' '
- 'PlayStyle: §e%playstyle%' - 'PlayStyle: §e%playstyle%'
- ' ' - ' '
@@ -123,6 +129,38 @@ kits:
snowball: snowball:
name: '§bFreeze' name: '§bFreeze'
description: 'Freeze your enemies by throwing snowballs in all directions' description: 'Freeze your enemies by throwing snowballs in all directions'
passive:
name: '§bIceStorm'
description: 'Gain speed in cold biomes and give enemies slowness'
messages: messages:
shoot_snowballs: '<aqua>You have shot frozen snowballs in all directions!</aqua>' shoot_snowballs: '<aqua>You have shot frozen snowballs in all directions!</aqua>'
ability_charged: '<yellow>Your ability has been recharged!</yellow>' ability_charged: '<yellow>Your ability has been recharged!</yellow>'
venom:
name: '<gradient:dark_red:red>Venom</gradient>'
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: '<gray>You have summoned your deafening beam!</gray>'
shield_activate: '<gray>Your shield of darkness has been activated!</gray>'
shield_break: '<red>Your shield of darkness has broken!</red>'
ability_charged: '<yellow>Your ability has been recharged</yellow>'