diff --git a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt index 77d094b..fd9fbb9 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt @@ -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 ) } diff --git a/src/main/kotlin/club/mcscrims/speedhg/game/impl/ImmunityState.kt b/src/main/kotlin/club/mcscrims/speedhg/game/impl/ImmunityState.kt index 4fd80c5..6e4d98b 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/game/impl/ImmunityState.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/game/impl/ImmunityState.kt @@ -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() diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/AbstractKit.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/AbstractKit.kt index 2766e66..531bd0a 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/AbstractKit.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/AbstractKit.kt @@ -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, 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 - 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 + } + } diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/KitListener.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/KitListener.kt index e6b244d..7f503c8 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/KitListener.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/KitListener.kt @@ -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" ) + } + } diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/KitManager.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/KitManager.kt index 12b714d..fded346 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/KitManager.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/KitManager.kt @@ -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() private val selectedKits = ConcurrentHashMap() - fun registerKit(kit: AbstractKit) { - kit.config = plugin.kitConfig.data.getConfigForKit(kit.id) + fun initialize() + { + registerKit( + kitClass = AnchorKit::class.java as Class, + id = "anchor", + displayName = plugin.chatFormatter.format( "kits.anchor.displayName" ), + description = emptyList(), + icon = Material.ANVIL + ) + } + + fun registerKit( + kitClass: Class, + id: String, + displayName: Component, + description: List, + 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 { + fun getAllKits(): Collection + { 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() } + } diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/AnchorKit.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/AnchorKit.kt index b6d1ab5..1a597f5 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/AnchorKit.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/AnchorKit.kt @@ -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, + 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 ) } -} + +} \ No newline at end of file