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:
@@ -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()
|
||||
|
||||
300
src/main/kotlin/club/mcscrims/speedhg/kit/impl/VenomKit.kt
Normal file
300
src/main/kotlin/club/mcscrims/speedhg/kit/impl/VenomKit.kt
Normal 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" ))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
// =========================================================================
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
#
|
||||
|
||||
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:
|
||||
join: '<prefix><green><name></green> <gray>has joined the game.</gray>'
|
||||
@@ -57,7 +58,7 @@ scoreboard:
|
||||
title: '<gradient:red:gold><bold>SpeedHG</bold></gradient>'
|
||||
lobby:
|
||||
- "<gray><st> "
|
||||
- "Spieler: <green><online>/<max>"
|
||||
- "Players: <green><online>/<max>"
|
||||
- "Kit: <yellow><kit>"
|
||||
- ""
|
||||
- "<gray>Waiting for start..."
|
||||
@@ -89,8 +90,11 @@ kits:
|
||||
name: '<gradient:dark_green:gray><bold>Goblin</bold></gradient>'
|
||||
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: '<gradient:dark_aqua:aqua>IceMage</gradient>'
|
||||
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: '<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>'
|
||||
Reference in New Issue
Block a user