Add IceMage kit and playstyle support
Introduce a new IceMage kit and wire playstyle support across the plugin. Changes include: - Add IceMageKit with aggressive/defensive active & passive abilities, item distribution, and lifecycle hooks; caches ability instances and given items to reduce allocations. - Register IceMageKit in SpeedHG on plugin startup. - Update KitCommand to require a playstyle argument, validate it, select the kit's playstyle, and provide tab-completion for playstyles. - Extend KitEventDispatcher with IceMage-specific handlers: spawn a circular volley of snowballs on ability use (cancelling the original projectile), mark spawned snowballs with persistent data, and apply freeze/slow effects on hit. - Adjust GoblinKit to use a plugin getter and make inner ability classes static to avoid capturing the outer instance. - Update language lines and plugin.yml usage to reflect the new /kit <kitName> <playstyle> usage. These changes implement the IceMage feature and ensure proper playstyle selection and event handling while keeping allocations low and behavior consistent.
This commit is contained in:
@@ -6,6 +6,7 @@ import club.mcscrims.speedhg.game.GameManager
|
||||
import club.mcscrims.speedhg.game.modules.AntiRunningManager
|
||||
import club.mcscrims.speedhg.kit.KitManager
|
||||
import club.mcscrims.speedhg.kit.impl.GoblinKit
|
||||
import club.mcscrims.speedhg.kit.impl.IceMageKit
|
||||
import club.mcscrims.speedhg.kit.listener.KitEventDispatcher
|
||||
import club.mcscrims.speedhg.listener.ConnectListener
|
||||
import club.mcscrims.speedhg.listener.GameStateListener
|
||||
@@ -53,6 +54,7 @@ class SpeedHG : JavaPlugin() {
|
||||
kitManager = KitManager( this )
|
||||
// Register kits
|
||||
kitManager.registerKit( GoblinKit() )
|
||||
kitManager.registerKit( IceMageKit() )
|
||||
|
||||
registerCommands()
|
||||
registerListener()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package club.mcscrims.speedhg.command
|
||||
|
||||
import club.mcscrims.speedhg.SpeedHG
|
||||
import club.mcscrims.speedhg.kit.Playstyle
|
||||
import club.mcscrims.speedhg.util.legacySerializer
|
||||
import club.mcscrims.speedhg.util.sendMsg
|
||||
import org.bukkit.command.Command
|
||||
@@ -28,7 +29,7 @@ class KitCommand : CommandExecutor, TabCompleter {
|
||||
return true
|
||||
}
|
||||
|
||||
if ( args.isNullOrEmpty() )
|
||||
if ( args.isNullOrEmpty() || args.size < 2 )
|
||||
{
|
||||
player.sendMsg( "commands.kit.usage" )
|
||||
return true
|
||||
@@ -43,8 +44,18 @@ class KitCommand : CommandExecutor, TabCompleter {
|
||||
return true
|
||||
}
|
||||
|
||||
val playstyle = Playstyle.entries.firstOrNull { it.name.equals( args[1], true ) }
|
||||
|
||||
if ( playstyle == null )
|
||||
{
|
||||
player.sendMsg( "commands.kit.playstyleNotFound", "playstyle" to args[1] )
|
||||
return true
|
||||
}
|
||||
|
||||
plugin.kitManager.selectKit( player, kit )
|
||||
player.sendMsg( "commands.kit.selected", "kit" to legacySerializer.serialize( kit.displayName ))
|
||||
plugin.kitManager.selectPlaystyle( player, playstyle )
|
||||
|
||||
player.sendMsg( "commands.kit.selected", "playstyle" to playstyle.displayName, "kit" to legacySerializer.serialize( kit.displayName ))
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -61,6 +72,9 @@ class KitCommand : CommandExecutor, TabCompleter {
|
||||
if ( args.size == 1 )
|
||||
return plugin.kitManager.getRegisteredKits().map { it.id }
|
||||
|
||||
if ( args.size == 2 )
|
||||
return Playstyle.entries.map { it.name.lowercase() }
|
||||
|
||||
return listOf()
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class GoblinKit : Kit() {
|
||||
|
||||
private val plugin = SpeedHG.instance
|
||||
private val plugin get() = SpeedHG.instance
|
||||
|
||||
override val id: String
|
||||
get() = "goblin"
|
||||
@@ -104,7 +104,7 @@ class GoblinKit : Kit() {
|
||||
items.forEach { player.inventory.remove( it ) }
|
||||
}
|
||||
|
||||
private inner class AggressiveActive : ActiveAbility( Playstyle.AGGRESSIVE ) {
|
||||
private class AggressiveActive : ActiveAbility( Playstyle.AGGRESSIVE ) {
|
||||
|
||||
private val plugin get() = SpeedHG.instance
|
||||
|
||||
@@ -175,7 +175,7 @@ class GoblinKit : Kit() {
|
||||
|
||||
}
|
||||
|
||||
private inner class DefensiveActive : ActiveAbility( Playstyle.DEFENSIVE ) {
|
||||
private class DefensiveActive : ActiveAbility( Playstyle.DEFENSIVE ) {
|
||||
|
||||
private val plugin get() = SpeedHG.instance
|
||||
|
||||
|
||||
203
src/main/kotlin/club/mcscrims/speedhg/kit/impl/IceMageKit.kt
Normal file
203
src/main/kotlin/club/mcscrims/speedhg/kit/impl/IceMageKit.kt
Normal file
@@ -0,0 +1,203 @@
|
||||
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.ItemBuilder
|
||||
import club.mcscrims.speedhg.util.sendMsg
|
||||
import club.mcscrims.speedhg.util.trans
|
||||
import net.kyori.adventure.text.Component
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.Sound
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
||||
import org.bukkit.event.player.PlayerMoveEvent
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.potion.PotionEffect
|
||||
import org.bukkit.potion.PotionEffectType
|
||||
import java.util.Random
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class IceMageKit : Kit() {
|
||||
|
||||
private val plugin get() = SpeedHG.instance
|
||||
|
||||
override val id: String
|
||||
get() = "icemage"
|
||||
|
||||
override val displayName: Component
|
||||
get() = plugin.languageManager.getDefaultComponent( "kits.icemage.name", mapOf() )
|
||||
|
||||
override val lore: List<String>
|
||||
get() = plugin.languageManager.getDefaultRawMessageList( "kits.icemage.lore" )
|
||||
|
||||
override val icon: Material
|
||||
get() = Material.SNOWBALL
|
||||
|
||||
// ── Cached ability instances (avoid allocating per event call) ────────────
|
||||
private val aggressiveActive = AggressiveActive()
|
||||
private val defensiveActive = DefensiveActive()
|
||||
private val aggressivePassive = AggressivePassive()
|
||||
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 ─────────────────────────────────────────────────────
|
||||
|
||||
private val cachedItems = ConcurrentHashMap<UUID, List<ItemStack>>()
|
||||
|
||||
override fun giveItems(
|
||||
player: Player,
|
||||
playstyle: Playstyle
|
||||
) {
|
||||
if ( playstyle != Playstyle.DEFENSIVE )
|
||||
return
|
||||
|
||||
val snowBall = ItemBuilder( Material.SNOWBALL )
|
||||
.name( defensiveActive.name )
|
||||
.lore(listOf( defensiveActive.description ))
|
||||
.build()
|
||||
|
||||
cachedItems[ player.uniqueId ] = listOf( snowBall )
|
||||
player.inventory.addItem( snowBall )
|
||||
}
|
||||
|
||||
// ── 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 ) {
|
||||
|
||||
override val name: String
|
||||
get() = "None"
|
||||
|
||||
override val description: String
|
||||
get() = "None"
|
||||
|
||||
override val hitsRequired: Int
|
||||
get() = 0
|
||||
|
||||
override val triggerMaterial: Material
|
||||
get() = Material.BARRIER
|
||||
|
||||
override fun execute(
|
||||
player: Player
|
||||
): AbilityResult
|
||||
{
|
||||
return AbilityResult.Success
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class DefensiveActive : ActiveAbility( Playstyle.DEFENSIVE ) {
|
||||
|
||||
private val plugin get() = SpeedHG.instance
|
||||
|
||||
override val name: String
|
||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.icemage.items.snowball.name" )
|
||||
|
||||
override val description: String
|
||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.icemage.items.snowball.description" )
|
||||
|
||||
override val hitsRequired: Int
|
||||
get() = 15
|
||||
|
||||
override val triggerMaterial: Material
|
||||
get() = Material.SNOWBALL
|
||||
|
||||
override fun execute(
|
||||
player: Player
|
||||
): AbilityResult
|
||||
{
|
||||
player.playSound( player.location, Sound.ENTITY_PLAYER_HURT_FREEZE, 1f, 1.5f )
|
||||
player.sendActionBar(player.trans( "kits.icemage.messages.shoot_snowballs" ))
|
||||
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.icemage.messages.ability_charged" ))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class AggressivePassive : PassiveAbility( Playstyle.AGGRESSIVE ) {
|
||||
|
||||
private val plugin get() = SpeedHG.instance
|
||||
private val random = Random()
|
||||
|
||||
private val biomeList = listOf(
|
||||
"taiga_cold",
|
||||
"snowy_tundra",
|
||||
"ice_spikes",
|
||||
"snowy_beach",
|
||||
"grove",
|
||||
"snowy_slopes",
|
||||
"jagged_peaks",
|
||||
"frozen_peaks"
|
||||
)
|
||||
|
||||
override val name: String
|
||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.icemage.passive.name" )
|
||||
|
||||
override val description: String
|
||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.icemage.passive.description" )
|
||||
|
||||
override fun onMove(
|
||||
player: Player,
|
||||
event: PlayerMoveEvent
|
||||
) {
|
||||
val biome = player.world.getBiome( player.location )
|
||||
if (!biomeList.contains( biome.name.lowercase() )) return
|
||||
player.addPotionEffect(PotionEffect( PotionEffectType.SPEED, 20, 0 ))
|
||||
}
|
||||
|
||||
override fun onHitEnemy(
|
||||
attacker: Player,
|
||||
victim: Player,
|
||||
event: EntityDamageByEntityEvent
|
||||
) {
|
||||
if ( random.nextBoolean() )
|
||||
victim.addPotionEffect(PotionEffect( PotionEffectType.SLOWNESS, 60, 0 ))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class DefensivePassive : PassiveAbility( Playstyle.DEFENSIVE ) {
|
||||
|
||||
override val name: String
|
||||
get() = "None"
|
||||
|
||||
override val description: String
|
||||
get() = "None"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,16 +4,28 @@ import club.mcscrims.speedhg.SpeedHG
|
||||
import club.mcscrims.speedhg.game.GameState
|
||||
import club.mcscrims.speedhg.kit.KitManager
|
||||
import club.mcscrims.speedhg.kit.ability.AbilityResult
|
||||
import club.mcscrims.speedhg.kit.impl.IceMageKit
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.format.NamedTextColor
|
||||
import org.bukkit.NamespacedKey
|
||||
import org.bukkit.entity.LivingEntity
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.entity.Snowball
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.EventPriority
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
||||
import org.bukkit.event.entity.ProjectileHitEvent
|
||||
import org.bukkit.event.entity.ProjectileLaunchEvent
|
||||
import org.bukkit.event.player.PlayerInteractEvent
|
||||
import org.bukkit.event.player.PlayerMoveEvent
|
||||
import org.bukkit.inventory.EquipmentSlot
|
||||
import org.bukkit.persistence.PersistentDataType
|
||||
import org.bukkit.potion.PotionEffect
|
||||
import org.bukkit.potion.PotionEffectType
|
||||
import org.bukkit.util.Vector
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
* The *single* Bukkit [Listener] responsible for all kit-related event handling.
|
||||
@@ -171,6 +183,60 @@ class KitEventDispatcher(
|
||||
kit.getPassiveAbility(kitManager.getSelectedPlaystyle( player )).onMove( player, event )
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// IceMage Listener
|
||||
// =========================================================================
|
||||
|
||||
private val iceMageKey = NamespacedKey( plugin, "icemage_snowball" )
|
||||
|
||||
@EventHandler
|
||||
fun onSnowballThrow(
|
||||
event: ProjectileLaunchEvent
|
||||
) {
|
||||
val projectile = event.entity as? Snowball ?: return
|
||||
val shooter = projectile.shooter as? Player ?: return
|
||||
if (kitManager.getSelectedKit( shooter ) !is IceMageKit ) return
|
||||
if (kitManager.getChargeData( shooter )?.isReady == false ) return
|
||||
|
||||
val amountOfSnowballs = 16
|
||||
val playerLocation = shooter.location
|
||||
val baseSpeed = 1.5
|
||||
|
||||
for ( i in 0 until amountOfSnowballs )
|
||||
{
|
||||
val angle = i * ( 2 * Math.PI / amountOfSnowballs )
|
||||
|
||||
val x = cos( angle )
|
||||
val z = sin( angle )
|
||||
|
||||
val direction = Vector( x, 0.0, z ).normalize().multiply( baseSpeed )
|
||||
|
||||
val snowBall = shooter.world.spawn( playerLocation, Snowball::class.java )
|
||||
snowBall.shooter = shooter
|
||||
snowBall.velocity = direction
|
||||
|
||||
snowBall.persistentDataContainer.set( iceMageKey, PersistentDataType.BYTE, 1.toByte() )
|
||||
}
|
||||
|
||||
event.isCancelled = true
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onSnowballHit(
|
||||
event: ProjectileHitEvent
|
||||
) {
|
||||
val projectile = event.entity as? Snowball ?: return
|
||||
if (!projectile.persistentDataContainer.has( iceMageKey, PersistentDataType.BYTE )) return
|
||||
|
||||
val hitEntity = event.hitEntity
|
||||
|
||||
if ( hitEntity is LivingEntity && hitEntity != projectile.shooter )
|
||||
{
|
||||
hitEntity.freezeTicks = 60
|
||||
hitEntity.addPotionEffect(PotionEffect( PotionEffectType.SLOWNESS, 40, 1 ))
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Helpers
|
||||
// =========================================================================
|
||||
|
||||
@@ -45,9 +45,10 @@ craft:
|
||||
|
||||
commands:
|
||||
kit:
|
||||
usage: '<red>Usage: /kit <kitName></red>'
|
||||
usage: '<red>Usage: /kit <kitName> <playstyle></red>'
|
||||
kitNotFound: '<red><kit> is not a registered kit!</red>'
|
||||
selected: '<green>You have selected <kit> as your Kit!</green>'
|
||||
playstyleNotFound: '<red><playstyle> is not an available playstyle!</red>'
|
||||
selected: '<green>You have selected <kit> as your Kit with playstyle <playstyle>!</green>'
|
||||
|
||||
scoreboard:
|
||||
title: '<gradient:red:gold><bold>SpeedHG</bold></gradient>'
|
||||
|
||||
@@ -9,4 +9,4 @@ depend:
|
||||
commands:
|
||||
kit:
|
||||
description: 'Select kits via command'
|
||||
usage: '/kit <kitName>'
|
||||
usage: '/kit <kitName> <playstyle>'
|
||||
Reference in New Issue
Block a user