Update kit management & anchor kit

This commit is contained in:
TDSTOS
2025-12-09 01:01:00 +01:00
parent a3b521940c
commit 728b7534ac
6 changed files with 395 additions and 121 deletions

View File

@@ -5,11 +5,17 @@ import club.mcscrims.core.config.ConfigFormat
import club.mcscrims.core.config.ConfigLoader
import club.mcscrims.core.database.DatabaseConfig
import club.mcscrims.core.database.mongodb.MongoManager
import club.mcscrims.speedhg.ability.AbilityContext
import club.mcscrims.speedhg.ability.AbilityHitListener
import club.mcscrims.speedhg.ability.CooldownManager
import club.mcscrims.speedhg.ability.HitCounterManager
import club.mcscrims.speedhg.config.KitConfig
import club.mcscrims.speedhg.config.MessageConfig
import club.mcscrims.speedhg.config.PluginConfig
import club.mcscrims.speedhg.database.StatsRepository
import club.mcscrims.speedhg.game.GameManager
import club.mcscrims.speedhg.kit.KitListener
import club.mcscrims.speedhg.kit.KitManager
import club.mcscrims.speedhg.listener.GameStateListener
import club.mcscrims.speedhg.listener.LunarClientListener
import club.mcscrims.speedhg.world.WorldManager
@@ -20,13 +26,22 @@ import club.mcscrims.spigot.scheduler.SchedulerManager
import club.mcscrims.spigot.util.WorldEditUtils
import com.mongodb.client.model.Indexes
import kotlinx.coroutines.runBlocking
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer
import net.luckperms.api.LuckPerms
import org.bukkit.plugin.java.JavaPlugin
class SpeedHG : JavaPlugin() {
companion object {
internal lateinit var instance: SpeedHG
fun Component.content(): String
{
return LegacyComponentSerializer.legacySection().serialize( this )
}
}
private lateinit var configLoader: ConfigLoader
@@ -47,6 +62,9 @@ class SpeedHG : JavaPlugin() {
internal lateinit var gameManager: GameManager
internal lateinit var worldManager: WorldManager
internal lateinit var abilityContext: AbilityContext
internal lateinit var kitManager: KitManager
internal lateinit var worldEditUtils: WorldEditUtils
internal lateinit var luckPerms: LuckPerms
@@ -84,12 +102,20 @@ class SpeedHG : JavaPlugin() {
gameManager = GameManager( this )
gameManager.initialize()
val cooldownManager = CooldownManager()
val hitCounterManager = HitCounterManager()
abilityContext = AbilityContext( cooldownManager, hitCounterManager )
kitManager = KitManager( this )
kitManager.initialize()
setupLuckPerms()
registerListener()
}
override fun onDisable()
{
kitManager.clearAll()
mongoManager.shutdown()
networkManager.shutdown()
}
@@ -97,6 +123,8 @@ class SpeedHG : JavaPlugin() {
private fun registerListener()
{
server.pluginManager.registerEvents(GameStateListener( this, gameManager ), this )
server.pluginManager.registerEvents(KitListener( this, kitManager ), this )
server.pluginManager.registerEvents(AbilityHitListener( this, abilityContext ), this )
LunarClientListener( this )
}

View File

@@ -39,12 +39,11 @@ class ImmunityState(
player.inventory.setItem( 8, ItemStack( Material.COMPASS ))
TODO( "Give kits and perks" )
plugin.kitManager.startKitForPlayer( player )
TODO( "Give perks" )
}
broadcast( "gameStates.immunity.warnings.butterfly" )
TODO( "register kits & perks" )
}
override fun onTick()

View File

@@ -3,60 +3,82 @@ package club.mcscrims.speedhg.kit
import club.mcscrims.speedhg.SpeedHG
import club.mcscrims.speedhg.ability.AbilityContext
import club.mcscrims.speedhg.ability.AbilityResult
import club.mcscrims.speedhg.game.GameManager
import net.kyori.adventure.text.Component
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.event.player.PlayerMoveEvent
abstract class AbstractKit(
val id: String,
val displayName: String,
val displayName: Component,
val description: List<String>,
val icon: Material,
val playStyle: PlayStyle,
protected val plugin: SpeedHG,
protected val abilityContext: AbilityContext
protected val abilityContext: AbilityContext,
protected val gameManager: GameManager
) {
lateinit var config: Map<String, Double>
open fun onSelect(player: Player) {
abstract fun onSelect( player: Player )
abstract fun onStart( player: Player )
abstract fun onHit( attacker: Player, victim: Player, event: EntityDamageByEntityEvent )
abstract fun onDamaged( attacker: Player, victim: Player, event: EntityDamageByEntityEvent )
abstract fun onInteract( player: Player, event: PlayerInteractEvent )
abstract fun onMove( player: Player, event: PlayerMoveEvent )
open fun cleanup(
player: Player
) {
abilityContext.clearPlayerData( player )
}
open fun onStart(player: Player) {
protected fun hasCooldown(
player: Player,
key: String
): Boolean
{
return abilityContext.getRemainingCooldown( player, key ) > 0
}
open fun onHit(attacker: Player, victim: Player, event: EntityDamageByEntityEvent) {
protected fun startCooldown(
player: Player,
key: String,
seconds: Int
) {
abilityContext.cooldownManager.startCooldown( player, key, seconds )
}
open fun onInteract(player: Player, event: PlayerInteractEvent) {
protected fun getRemainingCooldown(
player: Player,
key: String
): Double
{
return abilityContext.getRemainingCooldown( player, key )
}
open fun cleanup(player: Player) {
abilityContext.clearPlayerData(player)
}
protected fun hasCooldown(player: Player, key: String): Boolean {
return abilityContext.getRemainingCooldown(player, key) > 0
}
protected fun startCooldown(player: Player, key: String, seconds: Int) {
abilityContext.cooldownManager.startCooldown(player, key, seconds)
}
protected fun getRemainingCooldown(player: Player, key: String): Double {
return abilityContext.getRemainingCooldown(player, key)
}
protected fun incrementHits(player: Player, key: String): Int {
return abilityContext.incrementHit(player, key)
}
protected fun getHits(player: Player, key: String): Int {
protected fun getHits(
player: Player,
key: String
): Int
{
return abilityContext.getHits(player, key)
}
protected fun resetHits(player: Player, key: String) {
abilityContext.hitCounterManager.resetHits(player, key)
protected fun resetHits(
player: Player,
key: String
) {
abilityContext.hitCounterManager.resetHits( player, key )
}
protected fun abilityResult(
@@ -65,6 +87,28 @@ abstract class AbstractKit(
requiredHits: Int? = null,
cooldownSeconds: Int? = null
): AbilityResult {
return abilityContext.canUseAbility(player, abilityKey, requiredHits, cooldownSeconds)
return abilityContext.canUseAbility( player, abilityKey, requiredHits, cooldownSeconds )
}
}
enum class PlayStyle {
OFFENSIVE, DEFENSIVE, NULL
}
enum class KitMetaData {
IN_GLADIATOR,
GLADIATOR_BLOCK,
IS_ANVIL,
IS_BLACK_PANTHER,
BP_EXTRA_DAMAGE,
VOODOO_HOLD,
ICEMAGE_SNOWBALL,
ICEMAGE_SPEED;
fun getKey(): String
{
return name
}
}

View File

@@ -1,29 +1,75 @@
package club.mcscrims.speedhg.kit
import club.mcscrims.speedhg.SpeedHG
import org.bukkit.Material
import org.bukkit.Sound
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.block.BlockBreakEvent
import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.event.player.PlayerMoveEvent
class KitListener(private val kitManager: KitManager) : Listener {
class KitListener(
private val plugin: SpeedHG,
private val kitManager: KitManager
) : Listener {
@EventHandler
fun onEntityDamage(event: EntityDamageByEntityEvent) {
fun onDamage(
event: EntityDamageByEntityEvent
) {
val attacker = event.damager as? Player ?: return
val victim = event.entity as? Player ?: return
if (kitManager.getSelectedKit(attacker) != null) {
kitManager.triggerHit(attacker, victim, event)
}
if (kitManager.getSelectedKit( attacker ) != null )
kitManager.triggerHit( attacker, victim, event )
if (kitManager.getSelectedKit( victim ) != null )
kitManager.triggerDamaged( victim, attacker, event )
}
@EventHandler
fun onPlayerInteract(event: PlayerInteractEvent) {
fun onPlayerInteract(
event: PlayerInteractEvent
) {
val player = event.player
if (kitManager.getSelectedKit(player) != null) {
kitManager.triggerInteract(player, event)
}
if (kitManager.getSelectedKit( player ) != null )
kitManager.triggerInteract( player, event )
}
@EventHandler
fun onPlayerMove(
event: PlayerMoveEvent
) {
val player = event.player
if (kitManager.getSelectedKit( player ) != null )
kitManager.triggerMove( player, event )
}
@EventHandler
fun onAnvilBreak(
event: BlockBreakEvent
) {
if ( !plugin.gameManager.isRunning() )
return
val block = event.block
if ( block.type != Material.ANVIL ||
!block.hasMetadata( KitMetaData.IS_ANVIL.getKey() ))
return
event.isCancelled = true
block.type = Material.AIR
block.removeMetadata( KitMetaData.IS_ANVIL.getKey(), plugin )
block.world.playSound( block.location, Sound.ENTITY_IRON_GOLEM_DEATH, 3f, 3f )
plugin.chatManager.broadcast( "kits.anchor.messages.broken" )
}
}

View File

@@ -1,41 +1,73 @@
package club.mcscrims.speedhg.kit
import club.mcscrims.speedhg.SpeedHG
import club.mcscrims.speedhg.kit.impl.AnchorKit
import net.kyori.adventure.text.Component
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.event.player.PlayerMoveEvent
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
class KitManager(private val plugin: SpeedHG) {
class KitManager(
private val plugin: SpeedHG
) {
private val kits = ConcurrentHashMap<String, AbstractKit>()
private val selectedKits = ConcurrentHashMap<UUID, AbstractKit>()
fun registerKit(kit: AbstractKit) {
kit.config = plugin.kitConfig.data.getConfigForKit(kit.id)
fun initialize()
{
registerKit(
kitClass = AnchorKit::class.java as Class<AbstractKit>,
id = "anchor",
displayName = plugin.chatFormatter.format( "kits.anchor.displayName" ),
description = emptyList(),
icon = Material.ANVIL
)
}
fun registerKit(
kitClass: Class<AbstractKit>,
id: String,
displayName: Component,
description: List<String>,
icon: Material
) {
val kit = kitClass.getDeclaredConstructor().newInstance( id, displayName, description, icon, PlayStyle.NULL, plugin, plugin.abilityContext, plugin.gameManager )
kit.config = plugin.kitConfig.data.getConfigForKit( kit.id )
kits[kit.id.lowercase()] = kit
plugin.logger.info("Registered kit: ${kit.displayName} (${kit.id})")
}
fun getKit(id: String): AbstractKit? {
fun getKit(
id: String
): AbstractKit?
{
return kits[id.lowercase()]
}
fun getAllKits(): Collection<AbstractKit> {
fun getAllKits(): Collection<AbstractKit>
{
return kits.values
}
fun selectKit(player: Player, kitId: String): Boolean {
val kit = getKit(kitId) ?: return false
fun selectKit(
player: Player,
kitId: String
): Boolean
{
val kit = getKit( kitId ) ?: return false
val previousKit = selectedKits[player.uniqueId]
previousKit?.cleanup(player)
previousKit?.cleanup( player )
selectedKits[player.uniqueId] = kit
try {
kit.onSelect(player)
kit.onSelect( player )
} catch (e: Exception) {
plugin.logger.severe("Error during onSelect for kit ${kit.id} and player ${player.name}: ${e.message}")
e.printStackTrace()
@@ -44,56 +76,99 @@ class KitManager(private val plugin: SpeedHG) {
return true
}
fun getSelectedKit(player: Player): AbstractKit? {
fun getSelectedKit(
player: Player
): AbstractKit?
{
return selectedKits[player.uniqueId]
}
fun startKitForPlayer(player: Player) {
fun startKitForPlayer(
player: Player
) {
val kit = selectedKits[player.uniqueId] ?: return
try {
kit.onStart(player)
kit.onStart( player )
} catch (e: Exception) {
plugin.logger.severe("Error during onStart for kit ${kit.id} and player ${player.name}: ${e.message}")
e.printStackTrace()
}
}
fun triggerHit(attacker: Player, victim: Player, event: EntityDamageByEntityEvent) {
fun triggerHit(
attacker: Player,
victim: Player,
event: EntityDamageByEntityEvent
) {
val kit = selectedKits[attacker.uniqueId] ?: return
try {
kit.onHit(attacker, victim, event)
kit.onHit( attacker, victim, event )
} catch (e: Exception) {
plugin.logger.severe("Error during onHit for kit ${kit.id} and player ${attacker.name}: ${e.message}")
e.printStackTrace()
}
}
fun triggerInteract(player: Player, event: PlayerInteractEvent) {
fun triggerDamaged(
attacker: Player,
victim: Player,
event: EntityDamageByEntityEvent
) {
val kit = selectedKits[victim.uniqueId] ?: return
try {
kit.onDamaged( attacker, victim, event )
} catch (e: Exception) {
plugin.logger.severe("Error during onDamaged for kit ${kit.id} and player ${victim.name}: ${e.message}")
e.printStackTrace()
}
}
fun triggerInteract(
player: Player,
event: PlayerInteractEvent
) {
val kit = selectedKits[player.uniqueId] ?: return
try {
kit.onInteract(player, event)
kit.onInteract( player, event )
} catch (e: Exception) {
plugin.logger.severe("Error during onInteract for kit ${kit.id} and player ${player.name}: ${e.message}")
e.printStackTrace()
}
}
fun clearPlayerSelection(player: Player) {
val kit = selectedKits.remove(player.uniqueId)
kit?.cleanup(player)
fun triggerMove(
player: Player,
event: PlayerMoveEvent
) {
val kit = selectedKits[player.uniqueId] ?: return
try {
kit.onMove( player, event )
} catch (e: Exception) {
plugin.logger.severe("Error during onMove for kit ${kit.id} and player ${player.name}: ${e.message}")
e.printStackTrace()
}
}
fun clearAll() {
fun clearPlayerSelection(
player: Player
) {
val kit = selectedKits.remove( player.uniqueId )
kit?.cleanup( player )
}
fun clearAll()
{
selectedKits.values.forEach { kit ->
plugin.server.onlinePlayers.forEach { player ->
if (selectedKits[player.uniqueId] == kit) {
kit.cleanup(player)
}
if ( selectedKits[player.uniqueId] == kit ) kit.cleanup( player )
}
}
selectedKits.clear()
}
}

View File

@@ -1,85 +1,167 @@
package club.mcscrims.speedhg.kit.impl
import club.mcscrims.speedhg.SpeedHG
import club.mcscrims.speedhg.SpeedHG.Companion.content
import club.mcscrims.speedhg.ability.AbilityContext
import club.mcscrims.speedhg.game.GameManager
import club.mcscrims.speedhg.kit.AbstractKit
import club.mcscrims.speedhg.kit.KitMetaData
import club.mcscrims.speedhg.kit.PlayStyle
import club.mcscrims.spigot.item.ItemBuilder
import net.kyori.adventure.text.Component
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.Sound
import org.bukkit.entity.Player
import org.bukkit.event.block.Action
import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.event.player.PlayerMoveEvent
import org.bukkit.inventory.ItemStack
import org.bukkit.potion.PotionEffect
import org.bukkit.potion.PotionEffectType
import org.bukkit.metadata.FixedMetadataValue
class AnchorKit(
id: String,
displayName: Component,
description: List<String>,
icon: Material,
playStyle: PlayStyle,
plugin: SpeedHG,
abilityContext: AbilityContext
) : AbstractKit(
id = "anchor",
displayName = "Anchor",
description = listOf(
"§7Heavy and strong.",
"§7Deal extra damage after 3 hits.",
"§7Gain resistance when standing still."
),
icon = Material.ANVIL,
plugin = plugin,
abilityContext = abilityContext
) {
abilityContext: AbilityContext,
gameManager: GameManager
) : AbstractKit( id, displayName, description, icon, playStyle, plugin, abilityContext, gameManager ) {
private val abilityKey = "anchor_ability"
private val hitCounterKey = "anchor_hits"
private lateinit var anvilItem: ItemStack
override fun onSelect(player: Player) {
player.sendMessage("§aYou selected §6Anchor§a!")
private var anvilPlaced: Boolean = false
private val extraDamage: Double = plugin.kitConfig.data.anchor[ "offensive extra damage" ]!!
private val radius = if ( playStyle == PlayStyle.DEFENSIVE ) 7.5 else 5.0
private lateinit var anvilLoc: Location
override fun onSelect( player: Player ) {}
override fun onStart(
player: Player
) {
anvilItem = ItemBuilder( plugin, Material.ANVIL )
.name(plugin.chatFormatter.format( "kits.anchor.items.anvil.${playStyle.name.lowercase()}" ).content())
.unbreakable( true )
.hideAttributes()
.build()
player.inventory.setItem( 0, anvilItem )
}
override fun onStart(player: Player) {
player.inventory.addItem(ItemStack(Material.STONE_SWORD))
player.inventory.helmet = ItemStack(Material.IRON_HELMET)
player.inventory.chestplate = ItemStack(Material.IRON_CHESTPLATE)
player.inventory.leggings = ItemStack(Material.IRON_LEGGINGS)
player.inventory.boots = ItemStack(Material.IRON_BOOTS)
override fun onHit(
attacker: Player,
victim: Player,
event: EntityDamageByEntityEvent
) {
if ( !gameManager.isRunning() )
return
if ( !anvilPlaced )
return
if ( playStyle != PlayStyle.OFFENSIVE )
return
event.damage += extraDamage
}
override fun onHit(attacker: Player, victim: Player, event: EntityDamageByEntityEvent) {
val currentHits = incrementHits(attacker, hitCounterKey)
override fun onDamaged(
attacker: Player,
victim: Player,
event: EntityDamageByEntityEvent
) {
if ( !gameManager.isRunning() )
return
val result = abilityResult(
player = attacker,
abilityKey = abilityKey,
requiredHits = 3,
cooldownSeconds = 10
)
if ( !anvilPlaced )
return
if (result.success) {
val extraDamage = config["offensive extra damage"] ?: 1.0
event.damage += extraDamage
victim.velocity.setX( 0.0 )
victim.velocity.setZ( 0.0 )
victim.world.playSound( victim.location, Sound.BLOCK_ANVIL_HIT, 3f, 3f )
}
attacker.sendMessage("§6⚓ Anchor Ability! §eDealt ${extraDamage} extra damage!")
victim.sendMessage("§c${attacker.name} used Anchor ability!")
} else if (result.missingHits > 0) {
attacker.sendMessage("§e⚓ Hits: $currentHits/3")
} else if (result.remainingCooldown > 0) {
attacker.sendMessage("§c⚓ Cooldown: ${String.format("%.1f", result.remainingCooldown)}s")
override fun onInteract(
player: Player,
event: PlayerInteractEvent
) {
if ( !gameManager.isRunning() )
return
val action = event.action
if ( action != Action.RIGHT_CLICK_AIR &&
action != Action.RIGHT_CLICK_BLOCK )
return
val item = event.item ?: return
if ( item != anvilItem )
return
event.isCancelled = true
val eyeLocation = player.eyeLocation
if (eyeLocation.distance( player.location ) > radius )
{
plugin.chatManager.sendMessage( player, "kits.anchor.messages.tooFarAway" )
return
}
}
override fun onInteract(player: Player, event: PlayerInteractEvent) {
if (event.action.isRightClick && event.item?.type == Material.ANVIL) {
if (!hasCooldown(player, "anchor_resistance")) {
player.addPotionEffect(PotionEffect(PotionEffectType.RESISTANCE, 100, 0))
player.sendMessage("§6⚓ Resistance activated!")
startCooldown(player, "anchor_resistance", 15)
} else {
val remaining = getRemainingCooldown(player, "anchor_resistance")
player.sendMessage("§c⚓ Resistance on cooldown: ${String.format("%.1f", remaining)}s")
if ( anvilPlaced )
{
plugin.chatManager.sendMessage( player, "kits.anchor.messages.alreadyActivated" )
return
}
val result = abilityContext.canUseAbility( player, "anchor-anvil", 15 )
if ( result.missingHits > 0 )
{
plugin.chatManager.sendMessage( player, "kits.missingHits", "{hits]" to result.missingHits.toString() )
return
}
anvilLoc = eyeLocation.toBlockLocation()
anvilLoc.add( 0.0, 1.0, 0.0 )
anvilLoc.block.type = Material.ANVIL
anvilLoc.block.setMetadata( KitMetaData.IS_ANVIL.getKey(), FixedMetadataValue( plugin, true ))
anvilPlaced = true
plugin.schedulerManager.runLater( 20 * 30L ) {
if ( anvilPlaced )
{
anvilPlaced = false
anvilLoc.block.type = Material.AIR
anvilLoc.block.removeMetadata( KitMetaData.IS_ANVIL.getKey(), plugin )
}
}
}
override fun cleanup(player: Player) {
super.cleanup(player)
player.sendMessage("§7Anchor kit cleaned up.")
override fun onMove(
player: Player,
event: PlayerMoveEvent
) {
if ( !gameManager.isRunning() )
return
if ( !anvilPlaced )
return
if (player.location.distance( anvilLoc ) <= radius )
return
player.teleport( player.location )
player.playSound( player, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f )
}
}
}