Add Gladiator kit and WorldEdit support

Introduce the Gladiator kit feature and supporting infrastructure: add KitMetaData enum and new GladiatorKit implementation (items, abilities, fight lifecycle and region logic), register the kit in SpeedHG, and add language entries for the kit and some bolded kit names. Add WorldEditUtils.createCylinder helper to build/tear down arenas. Extend KitEventDispatcher with listeners to handle gladiator arena block interactions and explosion handling (with a helper to step glass color -> air). Also add ingame guards in several projectile handlers and remove a small TODO/comment in GameStateListener.
This commit is contained in:
TDSTOS
2026-03-26 04:13:26 +01:00
parent 0043a3e82c
commit b2edcff447
7 changed files with 432 additions and 9 deletions

View File

@@ -9,6 +9,7 @@ import club.mcscrims.speedhg.game.GameManager
import club.mcscrims.speedhg.game.modules.AntiRunningManager import club.mcscrims.speedhg.game.modules.AntiRunningManager
import club.mcscrims.speedhg.kit.KitManager import club.mcscrims.speedhg.kit.KitManager
import club.mcscrims.speedhg.kit.impl.BackupKit 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.GoblinKit
import club.mcscrims.speedhg.kit.impl.IceMageKit import club.mcscrims.speedhg.kit.impl.IceMageKit
import club.mcscrims.speedhg.kit.impl.VenomKit import club.mcscrims.speedhg.kit.impl.VenomKit
@@ -93,6 +94,7 @@ class SpeedHG : JavaPlugin() {
private fun registerKits() private fun registerKits()
{ {
kitManager.registerKit( BackupKit() ) kitManager.registerKit( BackupKit() )
kitManager.registerKit( GladiatorKit() )
kitManager.registerKit( GoblinKit() ) kitManager.registerKit( GoblinKit() )
kitManager.registerKit( IceMageKit() ) kitManager.registerKit( IceMageKit() )
kitManager.registerKit( VenomKit() ) kitManager.registerKit( VenomKit() )

View File

@@ -0,0 +1,12 @@
package club.mcscrims.speedhg.kit
enum class KitMetaData {
IN_GLADIATOR,
GLADIATOR_BLOCK;
fun getKey(): String
{
return name
}
}

View File

@@ -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<String>
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<UUID, List<ItemStack>>()
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()
}
}
}

View File

@@ -3,24 +3,30 @@ package club.mcscrims.speedhg.kit.listener
import club.mcscrims.speedhg.SpeedHG import club.mcscrims.speedhg.SpeedHG
import club.mcscrims.speedhg.game.GameState import club.mcscrims.speedhg.game.GameState
import club.mcscrims.speedhg.kit.KitManager import club.mcscrims.speedhg.kit.KitManager
import club.mcscrims.speedhg.kit.KitMetaData
import club.mcscrims.speedhg.kit.Playstyle import club.mcscrims.speedhg.kit.Playstyle
import club.mcscrims.speedhg.kit.ability.AbilityResult import club.mcscrims.speedhg.kit.ability.AbilityResult
import club.mcscrims.speedhg.kit.impl.IceMageKit import club.mcscrims.speedhg.kit.impl.IceMageKit
import club.mcscrims.speedhg.kit.impl.VenomKit import club.mcscrims.speedhg.kit.impl.VenomKit
import net.kyori.adventure.text.Component import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Material
import org.bukkit.NamespacedKey import org.bukkit.NamespacedKey
import org.bukkit.Sound import org.bukkit.Sound
import org.bukkit.block.Block
import org.bukkit.entity.Arrow import org.bukkit.entity.Arrow
import org.bukkit.entity.Egg import org.bukkit.entity.Egg
import org.bukkit.entity.LivingEntity import org.bukkit.entity.LivingEntity
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.entity.Snowball import org.bukkit.entity.Snowball
import org.bukkit.entity.ThrownPotion import org.bukkit.entity.ThrownPotion
import org.bukkit.event.Cancellable
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority import org.bukkit.event.EventPriority
import org.bukkit.event.Listener import org.bukkit.event.Listener
import org.bukkit.event.block.BlockBreakEvent
import org.bukkit.event.entity.EntityDamageByEntityEvent import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.entity.EntityExplodeEvent
import org.bukkit.event.entity.ProjectileHitEvent import org.bukkit.event.entity.ProjectileHitEvent
import org.bukkit.event.entity.ProjectileLaunchEvent import org.bukkit.event.entity.ProjectileLaunchEvent
import org.bukkit.event.player.PlayerInteractEvent import org.bukkit.event.player.PlayerInteractEvent
@@ -199,6 +205,9 @@ class KitEventDispatcher(
fun onSnowballThrow( fun onSnowballThrow(
event: ProjectileLaunchEvent event: ProjectileLaunchEvent
) { ) {
if ( !isIngame() )
return
val projectile = event.entity as? Snowball ?: return val projectile = event.entity as? Snowball ?: return
val shooter = projectile.shooter as? Player ?: return val shooter = projectile.shooter as? Player ?: return
if (kitManager.getSelectedKit( shooter ) !is IceMageKit ) return if (kitManager.getSelectedKit( shooter ) !is IceMageKit ) return
@@ -231,6 +240,9 @@ class KitEventDispatcher(
fun onSnowballHit( fun onSnowballHit(
event: ProjectileHitEvent event: ProjectileHitEvent
) { ) {
if ( !isIngame() )
return
val projectile = event.entity as? Snowball ?: return val projectile = event.entity as? Snowball ?: return
if (!projectile.persistentDataContainer.has( iceMageKey, PersistentDataType.BYTE )) return if (!projectile.persistentDataContainer.has( iceMageKey, PersistentDataType.BYTE )) return
@@ -251,6 +263,9 @@ class KitEventDispatcher(
fun onProjectileHit( fun onProjectileHit(
event: ProjectileHitEvent event: ProjectileHitEvent
) { ) {
if ( !isIngame() )
return
val victim = event.hitEntity as? Player ?: return val victim = event.hitEntity as? Player ?: return
val projectile = event.entity 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 // 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 ) private fun isIngame(): Boolean = when ( plugin.gameManager.currentState )
{ {
GameState.INGAME, GameState.INVINCIBILITY -> true GameState.INGAME, GameState.INVINCIBILITY -> true

View File

@@ -123,10 +123,6 @@ class GameStateListener : Listener {
player.sendMsg("build.no_iron_before_feast") player.sendMsg("build.no_iron_before_feast")
player.playSound(player.location, Sound.ENTITY_VILLAGER_NO, 1f, 1f) player.playSound(player.location, Sound.ENTITY_VILLAGER_NO, 1f, 1f)
} }
else if ( block.type == Material.IRON_ORE && feastStarted )
{
// TODO: Database-Aufruf
}
} }
private fun pickupBlock( private fun pickupBlock(
@@ -354,8 +350,6 @@ class GameStateListener : Listener {
item.editMeta { meta -> ( meta as Damageable ).damage /= 2 } item.editMeta { meta -> ( meta as Damageable ).damage /= 2 }
player.sendMsg( "craft.iron_nerf" ) player.sendMsg( "craft.iron_nerf" )
} }
// TODO: add 0.1 to ironFarmed in database
} }
} }

View File

@@ -34,4 +34,29 @@ object WorldEditUtils {
e.printStackTrace() 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()
}
} }

View File

@@ -81,7 +81,7 @@ scoreboard:
kits: kits:
backup: backup:
name: '<gradient:gold:#ff841f>Backup</gradient>' name: '<gradient:gold:#ff841f><bold>Backup</bold></gradient>'
lore: lore:
- ' ' - ' '
- 'Select a kit mid-round at any time.' - 'Select a kit mid-round at any time.'
@@ -91,6 +91,23 @@ kits:
- ' ' - ' '
- 'Left-click to select' - 'Left-click to select'
- 'Right-click to change playstyle' - 'Right-click to change playstyle'
gladiator:
name: '<gradient:dark_gray:gray><bold>Gladiator</bold></gradient>'
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: '<gray>Cage</gray>'
description: 'Fight an enemy in a 1v1 above the skies'
messages:
ability_charged: '<yellow>Your ability has been recharged!</yellow>'
goblin: goblin:
name: '<gradient:dark_green:gray><bold>Goblin</bold></gradient>' name: '<gradient:dark_green:gray><bold>Goblin</bold></gradient>'
lore: lore:
@@ -117,7 +134,7 @@ kits:
spawn_bunker: '<green>You have created a bunker around yourself!' spawn_bunker: '<green>You have created a bunker around yourself!'
ability_charged: '<yellow>Your ability has been recharged!</yellow>' ability_charged: '<yellow>Your ability has been recharged!</yellow>'
icemage: icemage:
name: '<gradient:dark_aqua:aqua>IceMage</gradient>' name: '<gradient:dark_aqua:aqua><bold>IceMage</bold></gradient>'
lore: lore:
- ' ' - ' '
- 'AGGRESSIVE:' - 'AGGRESSIVE:'
@@ -141,7 +158,7 @@ kits:
shoot_snowballs: '<aqua>You have shot frozen snowballs in all directions!</aqua>' 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: venom:
name: '<gradient:dark_red:red>Venom</gradient>' name: '<gradient:dark_red:red><bold>Venom</bold></gradient>'
lore: lore:
- ' ' - ' '
- 'AGGRESSIVE:' - 'AGGRESSIVE:'