diff --git a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt index 4f7bbb7..45b2137 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/SpeedHG.kt @@ -9,6 +9,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.BackupKit +import club.mcscrims.speedhg.kit.impl.GladiatorKit import club.mcscrims.speedhg.kit.impl.GoblinKit import club.mcscrims.speedhg.kit.impl.IceMageKit import club.mcscrims.speedhg.kit.impl.VenomKit @@ -93,6 +94,7 @@ class SpeedHG : JavaPlugin() { private fun registerKits() { kitManager.registerKit( BackupKit() ) + kitManager.registerKit( GladiatorKit() ) kitManager.registerKit( GoblinKit() ) kitManager.registerKit( IceMageKit() ) kitManager.registerKit( VenomKit() ) diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/KitMetaData.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/KitMetaData.kt new file mode 100644 index 0000000..52957ee --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/KitMetaData.kt @@ -0,0 +1,12 @@ +package club.mcscrims.speedhg.kit + +enum class KitMetaData { + IN_GLADIATOR, + GLADIATOR_BLOCK; + + fun getKey(): String + { + return name + } + +} \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/impl/GladiatorKit.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/GladiatorKit.kt new file mode 100644 index 0000000..d9b7cfe --- /dev/null +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/impl/GladiatorKit.kt @@ -0,0 +1,303 @@ +package club.mcscrims.speedhg.kit.impl + +import club.mcscrims.speedhg.SpeedHG +import club.mcscrims.speedhg.kit.Kit +import club.mcscrims.speedhg.kit.KitMetaData +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.WorldEditUtils +import club.mcscrims.speedhg.util.trans +import com.sk89q.worldedit.bukkit.BukkitAdapter +import com.sk89q.worldedit.math.Vector2 +import com.sk89q.worldedit.regions.CylinderRegion +import com.sk89q.worldedit.regions.Region +import net.kyori.adventure.text.Component +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.Sound +import org.bukkit.World +import org.bukkit.entity.Player +import org.bukkit.inventory.ItemStack +import org.bukkit.metadata.FixedMetadataValue +import org.bukkit.potion.PotionEffect +import org.bukkit.potion.PotionEffectType +import org.bukkit.scheduler.BukkitRunnable +import java.util.Random +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +class GladiatorKit : Kit() { + + private val plugin get() = SpeedHG.instance + + override val id: String + get() = "gladiator" + + override val displayName: Component + get() = plugin.languageManager.getDefaultComponent( "kits.gladiator.name", mapOf() ) + + override val lore: List + get() = plugin.languageManager.getDefaultRawMessageList( "kits.gladiator.lore" ) + + override val icon: Material + get() = Material.IRON_BARS + + // ── Cached ability instances (avoid allocating per event call) ──────────── + private val aggressiveActive = AllActive( Playstyle.AGGRESSIVE ) + private val defensiveActive = AllActive( Playstyle.DEFENSIVE ) + private val aggressivePassive = NoPassive( Playstyle.AGGRESSIVE ) + private val defensivePassive = NoPassive( Playstyle.DEFENSIVE ) + + // ── 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>() + + override fun giveItems( + player: Player, + playstyle: Playstyle + ) { + val ironBars = ItemBuilder( Material.IRON_BARS ) + .name( aggressiveActive.name ) + .lore(listOf( aggressiveActive.description )) + .build() + + cachedItems[ player.uniqueId ] = listOf( ironBars ) + player.inventory.addItem( ironBars ) + } + + // ── Optional lifecycle hooks ────────────────────────────────────────────── + + override fun onRemove( + player: Player + ) { + val items = cachedItems.remove( player.uniqueId ) ?: return + items.forEach { player.inventory.remove( it ) } + } + + private inner class AllActive( playstyle: Playstyle ) : ActiveAbility( playstyle ) { + + private val plugin get() = SpeedHG.instance + + override val name: String + get() = plugin.languageManager.getDefaultRawMessage( "kits.gladiator.items.ironBars.name" ) + + override val description: String + get() = plugin.languageManager.getDefaultRawMessage( "kits.gladiator.items.ironBars.description" ) + + override val hitsRequired: Int + get() = 15 + + override val triggerMaterial: Material + get() = Material.IRON_BARS + + override fun execute( + player: Player + ): AbilityResult + { + val lineOfSight = player.getTargetEntity( 3 ) as? Player + ?: return AbilityResult.ConditionNotMet( "No player in line of sight" ) + + if (player.hasMetadata( KitMetaData.IN_GLADIATOR.getKey() ) || + lineOfSight.hasMetadata( KitMetaData.IN_GLADIATOR.getKey() )) + return AbilityResult.ConditionNotMet( "Already in gladiator fight" ) + + val radius = 23 + val height = 10 + + player.setMetadata( KitMetaData.IN_GLADIATOR.getKey(), FixedMetadataValue( plugin, true )) + lineOfSight.setMetadata( KitMetaData.IN_GLADIATOR.getKey(), FixedMetadataValue( plugin, true )) + + val gladiatorRegion = getGladiatorLocation(player.location.clone().add( 0.0, 64.0, 0.0 ), radius, height + 2 ) + val center = BukkitAdapter.adapt( player.world, gladiatorRegion.center ) + + WorldEditUtils.createCylinder( player.world, center, radius - 1, true, 1, Material.WHITE_STAINED_GLASS ) + WorldEditUtils.createCylinder( player.world, center, radius - 1, false, height, Material.WHITE_STAINED_GLASS ) + WorldEditUtils.createCylinder( player.world, center.clone().add( 0.0, height - 1.0, 0.0 ), radius -1, true, 1, Material.WHITE_STAINED_GLASS ) + + Bukkit.getScheduler().runTaskLater( plugin, { -> + for ( vector3 in gladiatorRegion ) + { + val block = player.world.getBlockAt(BukkitAdapter.adapt( player.world, vector3 )) + if ( block.type.isAir ) continue + block.setMetadata( KitMetaData.GLADIATOR_BLOCK.getKey(), FixedMetadataValue( plugin, true )) + } + }, 5L ) + + val gladiatorFight = GladiatorFight( gladiatorRegion, player, lineOfSight, radius, height ) + gladiatorFight.runTaskTimer( plugin, 0, 20 ) + 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.gladiator.messages.ability_charged" )) + } + + } + + private class NoPassive( playstyle: Playstyle ) : PassiveAbility( playstyle ) { + + override val name: String + get() = "None" + + override val description: String + get() = "None" + + } + + // ── Helper methods ──────────────────────────────────────────────────────── + + private fun getGladiatorLocation( + location: Location, + radius: Int, + height: Int + ): Region + { + val random = Random() + + val region = CylinderRegion( + BukkitAdapter.adapt( location.world ), + BukkitAdapter.asBlockVector( location ), + Vector2.at( radius.toDouble(), radius.toDouble() ), + location.blockY, location.blockY + height + ) + + return if (!hasEnoughSpace( region )) + getGladiatorLocation(location.add(if ( random.nextBoolean() ) -10.0 else 10.0, 5.0, if ( random.nextBoolean() ) -10.0 else 10.0 ), radius, height ) + else region + } + + private fun hasEnoughSpace( + region: Region + ): Boolean + { + val world: World + + if ( region.world != null ) + world = BukkitAdapter.adapt( region.world ) + else return true + + for ( vector3 in region ) + { + val adapt = BukkitAdapter.adapt( world, vector3 ) + + if (!world.worldBorder.isInside( adapt )) + return false + + if (!world.getBlockAt( adapt ).type.isAir) + return false + } + return true + } + + private class GladiatorFight( + val region: Region, + val gladiator: Player, + val enemy: Player, + val radius: Int, + val height: Int + ) : BukkitRunnable() { + + private val plugin get() = SpeedHG.instance + + val world: World = BukkitAdapter.adapt( region.world ) + val center: Location = BukkitAdapter.adapt( world, region.center ) + private val oldLocationGladiator: Location = gladiator.location + private val oldLocationEnemy: Location = enemy.location + + var timer = 0 + init { init() } + + fun init() + { + gladiator.addPotionEffect(PotionEffect( PotionEffectType.RESISTANCE, 40, 20 )) + enemy.addPotionEffect(PotionEffect( PotionEffectType.RESISTANCE, 40, 20 )) + gladiator.teleport(Location( world, center.x + radius / 2, center.y + 1, center.z, 90f, 0f )) + enemy.teleport(Location( world, center.x - radius / 2, center.y + 1, center.z, -90f, 0f )) + } + + override fun run() + { + if ( !gladiator.isOnline || !enemy.isOnline ) + { + endFight() + return + } + + if ( gladiator.location.y < center.y || enemy.location.y < center.y ) + { + endFight() + return + } + + if (region.contains(BukkitAdapter.asBlockVector( gladiator.location )) && + region.contains(BukkitAdapter.asBlockVector( enemy.location ))) + { + timer++ + + if ( timer > 180 ) + { + gladiator.addPotionEffect(PotionEffect( PotionEffectType.WITHER, Int.MAX_VALUE, 2 )) + enemy.addPotionEffect(PotionEffect( PotionEffectType.WITHER, Int.MAX_VALUE, 2 )) + } + } + } + + private fun endFight() + { + gladiator.apply { + removeMetadata( KitMetaData.IN_GLADIATOR.getKey(), plugin ) + removePotionEffect( PotionEffectType.WITHER ) + addPotionEffect(PotionEffect( PotionEffectType.RESISTANCE, 40, 20 )) + + if ( isOnline && plugin.gameManager.alivePlayers.contains( uniqueId )) + teleport( oldLocationGladiator ) + } + + enemy.apply { + removeMetadata( KitMetaData.IN_GLADIATOR.getKey(), plugin ) + removePotionEffect( PotionEffectType.WITHER ) + addPotionEffect(PotionEffect( PotionEffectType.RESISTANCE, 40, 20 )) + + if ( isOnline && plugin.gameManager.alivePlayers.contains( uniqueId )) + teleport( oldLocationEnemy ) + } + + for ( vector3 in region ) + { + val block = world.getBlockAt(BukkitAdapter.adapt( world, vector3 )) + if (!block.hasMetadata( KitMetaData.GLADIATOR_BLOCK.getKey() )) continue + block.removeMetadata( KitMetaData.GLADIATOR_BLOCK.getKey(), plugin ) + } + + WorldEditUtils.createCylinder( world, center, radius, true, height, Material.AIR ) + cancel() + } + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/kit/listener/KitEventDispatcher.kt b/src/main/kotlin/club/mcscrims/speedhg/kit/listener/KitEventDispatcher.kt index 1f9c1ba..c278323 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/kit/listener/KitEventDispatcher.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/kit/listener/KitEventDispatcher.kt @@ -3,24 +3,30 @@ 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.KitMetaData 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.Material import org.bukkit.NamespacedKey import org.bukkit.Sound +import org.bukkit.block.Block 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.Cancellable import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener +import org.bukkit.event.block.BlockBreakEvent import org.bukkit.event.entity.EntityDamageByEntityEvent +import org.bukkit.event.entity.EntityExplodeEvent import org.bukkit.event.entity.ProjectileHitEvent import org.bukkit.event.entity.ProjectileLaunchEvent import org.bukkit.event.player.PlayerInteractEvent @@ -199,6 +205,9 @@ class KitEventDispatcher( fun onSnowballThrow( event: ProjectileLaunchEvent ) { + if ( !isIngame() ) + return + val projectile = event.entity as? Snowball ?: return val shooter = projectile.shooter as? Player ?: return if (kitManager.getSelectedKit( shooter ) !is IceMageKit ) return @@ -231,6 +240,9 @@ class KitEventDispatcher( fun onSnowballHit( event: ProjectileHitEvent ) { + if ( !isIngame() ) + return + val projectile = event.entity as? Snowball ?: return if (!projectile.persistentDataContainer.has( iceMageKey, PersistentDataType.BYTE )) return @@ -251,6 +263,9 @@ class KitEventDispatcher( fun onProjectileHit( event: ProjectileHitEvent ) { + if ( !isIngame() ) + return + val victim = event.hitEntity as? Player ?: return val projectile = event.entity @@ -277,10 +292,65 @@ class KitEventDispatcher( } } + // ========================================================================= + // Gladiator Listener + // ========================================================================= + + @EventHandler + fun onBlockBreak( + event: BlockBreakEvent + ) { + if ( !isIngame() ) + return + + val player = event.player + + if (!player.hasMetadata( KitMetaData.IN_GLADIATOR.getKey() )) + return + + val block = event.block + + if (!block.hasMetadata( KitMetaData.GLADIATOR_BLOCK.getKey() )) + return + + changeGladiatorBlock( event, block ) + } + + @EventHandler + fun onExplode( + event: EntityExplodeEvent + ) { + if ( !isIngame() ) + { + event.isCancelled = true + return + } + + event.blockList().stream() + .filter { block -> block.hasMetadata( KitMetaData.GLADIATOR_BLOCK.getKey() ) } + .forEach { block -> changeGladiatorBlock( event, block ) } + } + // ========================================================================= // Helpers // ========================================================================= + private fun changeGladiatorBlock( + event: Cancellable, + block: Block + ) { + event.isCancelled = true + + when( block.type ) + { + Material.WHITE_STAINED_GLASS -> block.type = Material.YELLOW_STAINED_GLASS + Material.YELLOW_STAINED_GLASS -> block.type = Material.ORANGE_STAINED_GLASS + Material.ORANGE_STAINED_GLASS -> block.type = Material.RED_STAINED_GLASS + Material.RED_STAINED_GLASS -> block.type = Material.AIR + else -> return + } + } + private fun isIngame(): Boolean = when ( plugin.gameManager.currentState ) { GameState.INGAME, GameState.INVINCIBILITY -> true diff --git a/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt b/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt index eab5fd2..c1b4109 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/listener/GameStateListener.kt @@ -123,10 +123,6 @@ class GameStateListener : Listener { player.sendMsg("build.no_iron_before_feast") player.playSound(player.location, Sound.ENTITY_VILLAGER_NO, 1f, 1f) } - else if ( block.type == Material.IRON_ORE && feastStarted ) - { - // TODO: Database-Aufruf - } } private fun pickupBlock( @@ -354,8 +350,6 @@ class GameStateListener : Listener { item.editMeta { meta -> ( meta as Damageable ).damage /= 2 } player.sendMsg( "craft.iron_nerf" ) } - - // TODO: add 0.1 to ironFarmed in database } } \ No newline at end of file diff --git a/src/main/kotlin/club/mcscrims/speedhg/util/WorldEditUtils.kt b/src/main/kotlin/club/mcscrims/speedhg/util/WorldEditUtils.kt index 847dfea..ec145a9 100644 --- a/src/main/kotlin/club/mcscrims/speedhg/util/WorldEditUtils.kt +++ b/src/main/kotlin/club/mcscrims/speedhg/util/WorldEditUtils.kt @@ -34,4 +34,29 @@ object WorldEditUtils { e.printStackTrace() } + fun createCylinder( + world: World, + startLocation: Location, + radius: Int, + filled: Boolean, + height: Int, + block: Material + ) = try { + val editSession = WorldEdit.getInstance().newEditSessionBuilder() + .world(BukkitAdapter.adapt( world )).maxBlocks( -1 ).build() + + editSession.sideEffectApplier = SideEffectSet.defaults() + + editSession.makeCylinder( + BukkitAdapter.asBlockVector( startLocation ), + BukkitAdapter.asBlockState(ItemStack( block )), + radius.toDouble(), height, filled + ) + + editSession.commit() + editSession.close() + } catch ( e: Exception ) { + e.printStackTrace() + } + } \ No newline at end of file diff --git a/src/main/resources/languages/en_US.yml b/src/main/resources/languages/en_US.yml index a929184..5ac2395 100644 --- a/src/main/resources/languages/en_US.yml +++ b/src/main/resources/languages/en_US.yml @@ -81,7 +81,7 @@ scoreboard: kits: backup: - name: 'Backup' + name: 'Backup' lore: - ' ' - 'Select a kit mid-round at any time.' @@ -91,6 +91,23 @@ kits: - ' ' - 'Left-click to select' - 'Right-click to change playstyle' + gladiator: + name: 'Gladiator' + lore: + - ' ' + - 'Use your ability to fight enemies' + - 'in a 1v1 above the skies.' + - ' ' + - 'PlayStyle: §e%playstyle%' + - ' ' + - 'Left-click to select' + - 'Right-click to change playstyle' + items: + ironBars: + name: 'Cage' + description: 'Fight an enemy in a 1v1 above the skies' + messages: + ability_charged: 'Your ability has been recharged!' goblin: name: 'Goblin' lore: @@ -117,7 +134,7 @@ kits: spawn_bunker: 'You have created a bunker around yourself!' ability_charged: 'Your ability has been recharged!' icemage: - name: 'IceMage' + name: 'IceMage' lore: - ' ' - 'AGGRESSIVE:' @@ -141,7 +158,7 @@ kits: shoot_snowballs: 'You have shot frozen snowballs in all directions!' ability_charged: 'Your ability has been recharged!' venom: - name: 'Venom' + name: 'Venom' lore: - ' ' - 'AGGRESSIVE:'