Fix 9 bugs and features

9 identified bugs have been fixed, aswell as the podium where you would not spawn in the middle and missing messages
This commit is contained in:
TDSTOS
2026-04-14 00:23:29 +02:00
parent 445618ffaf
commit 2b875399e8
8 changed files with 225 additions and 82 deletions

View File

@@ -233,9 +233,12 @@ class PodiumManager(
val xOff = COLUMN_X_OFFSET[ entry.placement ] ?: return@forEach
val height = COLUMN_HEIGHT[ entry.placement ] ?: return@forEach
val standLoc = center.clone()
.add( xOff.toDouble(), height.toDouble(), 0.0 )
.apply { yaw = 0f; pitch = 0f }
val standLoc = Location(
center.world,
center.blockX + xOff.toDouble() + 0.5, // Block-Mitte X
center.y + height,
center.blockZ.toDouble() + 0.5 // Block-Mitte Z
).apply { yaw = 0f; pitch = 0f }
entry.player.teleport( standLoc )
entry.player.gameMode = GameMode.ADVENTURE

View File

@@ -129,10 +129,14 @@ class FeastManager(
// State erst im nächsten Tick lesen — Block-Commit braucht einen Tick
Bukkit.getScheduler().runTaskLater( plugin, { ->
val chest = chestBlock.state as? Chest ?: return@runTaskLater
val freshBlock = world.getBlockAt( cx, platformY + 1, cz )
if ( freshBlock.type != Material.CHEST ) return@runTaskLater
val chest = freshBlock.state as? Chest ?: return@runTaskLater
chest.update( true, false ) // BlockState commiten, bevor Inventar beschrieben wird
fillChestWithLoot( chest )
chest.update( true )
}, 1L )
}, 2L )
}
}, 5L )
@@ -203,11 +207,12 @@ class FeastManager(
) {
val loot = buildLootTable()
val inventory = chest.blockInventory
val slots = ( 0 until inventory.size ).shuffled()
val slots = ( 0 until inventory.size ).shuffled().toMutableList()
loot.forEachIndexed { idx, item ->
if ( idx < slots.size ) inventory.setItem(slots[ idx ], item )
if ( idx < slots.size ) inventory.setItem( slots[ idx ], item )
}
// kein chest.update() nötig — blockInventory ist eine Live-Referenz
}
/**

View File

@@ -7,6 +7,7 @@ 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.kit.listener.KitEventDispatcher.Companion.MAX_KNOCKBACK_HEIGHT_Y
import club.mcscrims.speedhg.util.AbilityUtils
import club.mcscrims.speedhg.util.ItemBuilder
import club.mcscrims.speedhg.util.WorldEditUtils
import club.mcscrims.speedhg.util.trans
@@ -18,6 +19,7 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.player.PlayerMoveEvent
import org.bukkit.inventory.ItemStack
import org.bukkit.persistence.PersistentDataType
import org.bukkit.util.Vector
import java.util.*
import java.util.concurrent.ConcurrentHashMap
@@ -184,11 +186,23 @@ class BlackPantherKit : Kit()
player: Player,
playstyle: Playstyle
) {
if ( playstyle != Playstyle.AGGRESSIVE ) return
val item = ItemBuilder( Material.BLACK_DYE )
.name( aggressiveActive.name )
.lore(listOf( aggressiveActive.description ))
.build()
val item = when( playstyle )
{
Playstyle.AGGRESSIVE -> {
ItemBuilder( Material.BLACK_DYE )
.name( aggressiveActive.name )
.lore(listOf( aggressiveActive.description ))
.build()
}
Playstyle.DEFENSIVE -> {
ItemBuilder( Material.FEATHER )
.name( defensiveActive.name )
.lore(listOf( defensiveActive.description ))
.build()
}
}
cachedItems[ player.uniqueId ] = listOf( item )
player.inventory.addItem( item )
}
@@ -230,12 +244,11 @@ class BlackPantherKit : Kit()
plugin.languageManager.getRawMessage( player, "kits.height_restriction" )
)
// Werte zum Aktivierungszeitpunkt snapshotten
val capturedPushRadius = pushRadius
val capturedKnockbackSpeed = pushKnockbackSpeed
val capturedKnockbackY = pushKnockbackY
val capturedFistModeDurationMs = fistModeDurationMs
val capturedProjectileDelay = projectileDelayTicks
val capturedPushRadius = pushRadius
val capturedKnockbackSpeed = pushKnockbackSpeed
val capturedKnockbackY = pushKnockbackY
val capturedFistModeDurationMs = fistModeDurationMs
val capturedProjectileDelay = projectileDelayTicks
val enemies = player.world
.getNearbyEntities( player.location, capturedPushRadius, capturedPushRadius, capturedPushRadius )
@@ -245,6 +258,18 @@ class BlackPantherKit : Kit()
if ( enemies.isEmpty() )
return AbilityResult.ConditionNotMet( "No enemies within ${capturedPushRadius.toInt()} blocks!" )
// ── Schockwellen-Partikel am Spieler (Aktivierungs-Burst) ──────────────
player.world.spawnParticle(
Particle.LARGE_SMOKE,
player.location.clone().add( 0.0, 1.0, 0.0 ),
30, 1.0, 0.4, 1.0, 0.08
)
player.world.spawnParticle(
Particle.EXPLOSION,
player.location.clone().add( 0.0, 0.5, 0.0 ),
2, 0.3, 0.2, 0.3, 0.0
)
val pushKey = NamespacedKey( plugin, PUSH_PROJECTILE_KEY )
enemies.forEach { enemy ->
@@ -255,49 +280,116 @@ class BlackPantherKit : Kit()
.setY( capturedKnockbackY )
enemy.velocity = knockDir
// ── Partikel-Spur von Spieler → Gegner (farbige Dust-Linie) ─────────
AbilityUtils.drawColoredParticleLine(
player.eyeLocation,
enemy.location.clone().add( 0.0, 1.0, 0.0 ),
20,
Color.fromRGB( 10, 10, 10 ) // Schwarz/Vibranium-Dunkel
)
// ── Aufprall-Burst am Gegner ─────────────────────────────────────────
enemy.world.spawnParticle(
Particle.CRIT,
enemy.location.clone().add( 0.0, 1.0, 0.0 ),
10, 0.3, 0.3, 0.3, 0.0
20, 0.4, 0.4, 0.4, 0.1
)
enemy.world.spawnParticle(
Particle.LARGE_SMOKE,
enemy.location.clone().add( 0.0, 1.0, 0.0 ),
8, 0.3, 0.3, 0.3, 0.04
)
Bukkit.getScheduler().runTaskLater( plugin, { ->
if ( !player.isOnline ) return@runTaskLater
val snowball = player.world.spawn( player.eyeLocation, Snowball::class.java )
snowball.shooter = player
val travelDir = enemy.location.toVector()
val travelDir = enemy.location.clone().add( 0.0, 1.0, 0.0 )
.toVector()
.subtract( player.eyeLocation.toVector() )
.normalize()
.multiply( 1.8 )
snowball.velocity = travelDir
snowball.persistentDataContainer.set( pushKey, PersistentDataType.BYTE, 1 )
// ── Partikel-Spur für das Projektil (nachleuchtend) ─────────────
AbilityUtils.drawGenericParticleLine(
player.eyeLocation,
enemy.location.clone().add( 0.0, 1.0, 0.0 ),
15,
Particle.SOUL_FIRE_FLAME
)
}, capturedProjectileDelay )
}
fistModeExpiry[ player.uniqueId ] = System.currentTimeMillis() + capturedFistModeDurationMs
player.sendActionBar( player.trans( "kits.blackpanther.messages.fist_mode_active" ) )
player.world.playSound( player.location, Sound.ENTITY_RAVAGER_ROAR, 1f, 1.1f )
player.world.playSound( player.location, Sound.ENTITY_RAVAGER_ROAR, 1f, 1.1f )
player.world.playSound( player.location, Sound.ENTITY_PLAYER_ATTACK_SWEEP, 0.8f, 0.7f )
return AbilityResult.Success
}
}
// =========================================================================
// DEFENSIVE active no active ability
// DEFENSIVE active — Wakanda Leap (neu)
// =========================================================================
private class DefensiveActive : ActiveAbility( Playstyle.DEFENSIVE )
private inner class DefensiveActive : ActiveAbility( Playstyle.DEFENSIVE )
{
private val plugin get() = SpeedHG.instance
override val kitId: String = "blackpanther"
override val name = "None"
override val description = "None"
override val hardcodedHitsRequired: Int = 0
override val triggerMaterial = Material.BARRIER
override val name: String
get() = plugin.languageManager.getDefaultRawMessage( "kits.blackpanther.items.leap.name" )
override val description: String
get() = plugin.languageManager.getDefaultRawMessage( "kits.blackpanther.items.leap.description" )
override val hardcodedHitsRequired: Int = 10
override val triggerMaterial: Material
get() = Material.FEATHER
override fun execute(
player: Player
) = AbilityResult.Success
): AbilityResult
{
if ( player.location.y > MAX_KNOCKBACK_HEIGHT_Y )
return AbilityResult.ConditionNotMet(
plugin.languageManager.getRawMessage( player, "kits.height_restriction" )
)
// Steil nach oben schleudern — leicht in Blickrichtung geneigt (wie Riptide)
val lookDir = player.location.direction.setY( 0.0 ).normalize()
val launchVec = Vector( lookDir.x * 0.4, 2.6, lookDir.z * 0.4 )
player.velocity = launchVec
// Fallschaden unterdrücken + Spieler als "in der Luft per Ability" markieren
noFallDamagePlayers.add( player.uniqueId )
player.world.playSound( player.location, Sound.ENTITY_RAVAGER_ROAR, 1f, 1.4f )
player.world.playSound( player.location, Sound.ENTITY_PLAYER_ATTACK_SWEEP, 0.8f, 0.6f )
player.world.spawnParticle(
Particle.LARGE_SMOKE,
player.location.clone().add( 0.0, 0.5, 0.0 ),
20, 0.3, 0.2, 0.3, 0.07
)
player.world.spawnParticle(
Particle.CRIT,
player.location.clone().add( 0.0, 1.0, 0.0 ),
12, 0.4, 0.3, 0.4, 0.1
)
player.sendActionBar( player.trans( "kits.blackpanther.messages.wakanda_leap" ) )
return AbilityResult.Success
}
}
// =========================================================================
@@ -340,12 +432,11 @@ class BlackPantherKit : Kit()
}
// =========================================================================
// DEFENSIVE passive Wakanda Forever! (fall-pounce → AOE + crater)
// DEFENSIVE passive Wakanda Forever! (Aufprall-AoE nach Ability-Launch)
// =========================================================================
private inner class DefensivePassive : PassiveAbility( Playstyle.DEFENSIVE )
{
private val plugin get() = SpeedHG.instance
override val name: String
@@ -357,17 +448,16 @@ class BlackPantherKit : Kit()
player: Player,
event: PlayerMoveEvent
) {
// Nur feuern wenn Spieler per Ability in der Luft ist
if ( !noFallDamagePlayers.contains( player.uniqueId ) ) return
// Nur wenn Spieler nach unten bewegt (fällt)
if ( event.to.y >= event.from.y ) return
val capturedPounceMinFall = pounceMinFall
if ( player.fallDistance < capturedPounceMinFall ) return
val blockBelow = event.to.clone().subtract( 0.0, 0.1, 0.0 ).block
if ( !blockBelow.type.isSolid ) return
// ── Aufprall-AoE ────────────────────────────────────────────────────
val impactLoc = event.to.clone()
// Werte zum Aktivierungszeitpunkt snapshotten
val capturedPounceRadius = pounceRadius
val capturedPounceDamage = pounceDamage
@@ -378,8 +468,10 @@ class BlackPantherKit : Kit()
splashTargets.forEach { it.damage( capturedPounceDamage, player ) }
impactLoc.world.spawnParticle( Particle.EXPLOSION, impactLoc, 3, 0.5, 0.5, 0.5, 0.0 )
impactLoc.world.spawnParticle( Particle.LARGE_SMOKE, impactLoc, 20, 1.0, 0.5, 1.0, 0.05 )
// ── Partikel & Sounds ────────────────────────────────────────────────
impactLoc.world.spawnParticle( Particle.EXPLOSION, impactLoc, 3, 0.5, 0.5, 0.5, 0.0 )
impactLoc.world.spawnParticle( Particle.LARGE_SMOKE, impactLoc, 25, 1.2, 0.5, 1.2, 0.06 )
impactLoc.world.spawnParticle( Particle.CRIT, impactLoc, 20, 1.0, 0.3, 1.0, 0.15 )
impactLoc.world.playSound( impactLoc, Sound.ENTITY_GENERIC_EXPLODE, 1f, 0.7f )
impactLoc.world.playSound( impactLoc, Sound.ENTITY_IRON_GOLEM_HURT, 1f, 0.5f )
@@ -395,7 +487,8 @@ class BlackPantherKit : Kit()
mapOf( "count" to splashTargets.size.toString() ) )
)
noFallDamagePlayers.add( player.uniqueId )
// Fallschaden wird vom KitEventDispatcher.onLeapFallDamage unterdrückt
// (noFallDamagePlayers.remove() passiert dort, NICHT hier)
player.fallDistance = 0f
}
}

View File

@@ -169,7 +169,7 @@ class TridentKit : Kit()
val nameKey = if ( playstyle == Playstyle.AGGRESSIVE )
"kits.trident.items.trident.aggressive.name"
else
"kits.trident.item.trident.defensive.name"
"kits.trident.items.trident.defensive.name"
val trident = ItemBuilder( Material.TRIDENT )
.name( plugin.languageManager.getDefaultRawMessage( nameKey ) )

View File

@@ -191,8 +191,8 @@ class VenomKit : Kit()
AbilityUtils.createBeam(
player,
player.location,
player.eyeLocation.toVector(),
player.eyeLocation, // Startpunkt = Augenpunkt
player.eyeLocation.direction, // Richtungsvektor aus Blickrichtung
Particle.DRAGON_BREATH,
7.5, 0.1
) { target ->

View File

@@ -7,6 +7,7 @@ import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.Sound
import org.bukkit.attribute.Attribute
import org.bukkit.entity.LivingEntity
import org.bukkit.entity.Player
import org.bukkit.event.Event
import org.bukkit.event.EventHandler
@@ -114,10 +115,16 @@ class GameStateListener : Listener {
return
}
// BUG 2 FIX: Kein separater Invis-Whitelist-Block mehr.
// Beide States (INVINCIBILITY + INGAME) laufen direkt in pickupBlock —
// Map-Protection (Diamant, Eisen) ist bereits oben abgehandelt.
pickupBlock( event, player )
val isAlwaysPickup = alwaysMaterials.containsKey( block.type )
val isInvisOnlyPickup = beforeInvisMaterials.containsKey( block.type )
when {
isAlwaysPickup ->
pickupBlock( event, player )
isInvisOnlyPickup && gameManager.currentState == GameState.INVINCIBILITY ->
pickupBlock( event, player )
}
}
private fun pickupBlock(
@@ -135,6 +142,14 @@ class GameStateListener : Listener {
}
}
if ( block.type == Material.CACTUS )
{
activeCactusBreaker[ player.uniqueId ] = player
Bukkit.getScheduler().runTask( plugin ) { ->
activeCactusBreaker.remove( player.uniqueId )
}
}
event.isCancelled = true
// BUG 1 FIX: !! entfernt → Elvis mit null-Safe-Fallback auf GENERIC_SOUND.
@@ -178,38 +193,54 @@ class GameStateListener : Listener {
* Kettenreaktion-Drops bekommen soll.
*/
private val activeMushroomBreaker: MutableMap<UUID, Player> = ConcurrentHashMap()
private val activeCactusBreaker: MutableMap<UUID, Player> = ConcurrentHashMap()
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false)
fun onBlockPhysics(
event: BlockPhysicsEvent
) {
val block = event.block
if ( block.type != Material.RED_MUSHROOM &&
block.type != Material.BROWN_MUSHROOM ) return
if ( gameManager.currentState != GameState.INVINCIBILITY &&
gameManager.currentState != GameState.INGAME ) return
// Einen Spieler in der Nähe finden der gerade einen Pilz abbaut
val harvester = activeMushroomBreaker.values
.firstOrNull { it.location.distanceSquared( block.location ) <= 25.0 }
?: return // Keine aktive Spieleraktion — normales Physik-Event, ignorieren
// Block vor dem Pop einsammeln
event.isCancelled = true
val drops = block.getDrops( harvester.inventory.itemInMainHand, harvester )
if ( !hasInventorySpace( harvester ) )
// ── Pilze (bestehende Logik) ──────────────────────────────────────────
if ( block.type == Material.RED_MUSHROOM ||
block.type == Material.BROWN_MUSHROOM )
{
drops.forEach { harvester.world.dropItem( block.location, it ) }
}
else
{
drops.forEach { harvester.inventory.addItem( it ) }
val harvester = activeMushroomBreaker.values
.firstOrNull { it.location.distanceSquared( block.location ) <= 25.0 }
?: return
event.isCancelled = true
val drops = block.getDrops( harvester.inventory.itemInMainHand, harvester )
if ( !hasInventorySpace( harvester ) )
drops.forEach { harvester.world.dropItem( block.location, it ) }
else
drops.forEach { harvester.inventory.addItem( it ) }
block.type = Material.AIR
return
}
block.type = Material.AIR
// ── Kakteen (NEU) ─────────────────────────────────────────────────────
if ( block.type == Material.CACTUS )
{
val harvester = activeCactusBreaker.values
.firstOrNull { it.location.distanceSquared( block.location ) <= 25.0 }
?: return
event.isCancelled = true
val drops = block.getDrops( harvester.inventory.itemInMainHand, harvester )
if ( !hasInventorySpace( harvester ) )
drops.forEach { harvester.world.dropItem( block.location, it ) }
else
drops.forEach { harvester.inventory.addItem( it ) }
block.type = Material.AIR
}
}
@EventHandler
@@ -404,6 +435,9 @@ class GameStateListener : Listener {
event: EntitySpawnEvent
) {
if ( feastStarted ) return
// Item-Drops, XP-Orbs, Projekile tc. NICHT blockieren
if ( event.entity !is LivingEntity ) return
if ( event.entity is Player ) return
event.isCancelled = true
}

View File

@@ -89,21 +89,24 @@ class GourmetPerk : Perk(), Listener {
// ── Soup listener ─────────────────────────────────────────────────────────
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@EventHandler( priority = EventPriority.MONITOR, ignoreCancelled = true )
fun onSoupConsume(
event: PlayerInteractEvent
) {
// Nur Rechtsklick — verhindert Trigger beim Schlagen/Links-Klick
if ( !event.action.isRightClick ) return
if ( event.hand != EquipmentSlot.HAND ) return
val player = event.player
if ( !activePlayers.contains( player.uniqueId ) ) return
val item = event.item ?: return
if ( item.type != Material.MUSHROOM_STEW ) return
// Ursprüngliches Item muss MUSHROOM_STEW gewesen sein
val originalItem = event.item ?: return
if ( originalItem.type != Material.MUSHROOM_STEW ) return
// Only fire when the item is actually going to be consumed — i.e. when
// the player is not at full hunger (vanilla consumption gate).
if ( player.foodLevel >= 20 ) return
// Nach SoupListener (HIGHEST) ist der Slot eine Bowl, wenn tatsächlich getrunken
val currentItem = player.inventory.getItem( event.hand!! )
if ( currentItem.type != Material.BOWL ) return
val dur = durationTicks
player.addPotionEffect( PotionEffect( PotionEffectType.REGENERATION, dur, 0, false, true, true ) )

View File

@@ -599,29 +599,33 @@ kits:
curse_received: '<red>🔮 You have been cursed by a Voodoo player!</red>'
ability_charged: '<yellow>⚡ Ability recharged!</yellow>'
# ── Black Panther ─────────────────────────────────────────────────────────────
# FIX: Added clarity that AGGRESSIVE is Push + 12 s Vibranium Fists (6.5 dmg/hit).
# DEFENSIVE Wakanda pounce now mentions 3-block fall requirement and crater.
# ── BlackPanther (neue Defensive-Leap Fähigkeit aus vorherigem Fix) ─────────
blackpanther:
name: '<gradient:dark_gray:white><bold>Black Panther</bold></gradient>'
# Lore-Zeile für DEF aktualisieren:
lore:
- ' '
- '<dark_gray>[AGG]</dark_gray> <gray>Push nearby enemies, then unleash <yellow>6.5 dmg</yellow> fists for <yellow>12 s</yellow>.</gray>'
- '<dark_aqua>[DEF]</dark_aqua> <gray>Fall <yellow>3+ blocks</yellow> onto a foe — <red>AOE damage</red> + crater.</gray>'
- '<dark_aqua>[DEF]</dark_aqua> <gray>Activate to <yellow>leap skyward</yellow> — land for <red>AOE impact</red> + crater.</gray>'
items:
push:
name: '<gray>⚡ Vibranium Pulse</gray>'
description: 'Knock back all nearby enemies and activate 12 s Vibranium Fist Mode'
# NEU — Leap-Item für Defensive:
leap:
name: '<white>⬆ Wakanda Leap</white>'
description: 'Launch skyward — land on enemies for AOE damage + crater (fall damage disabled)'
passive:
aggressive:
name: '<gray>Vibranium Fists</gray>'
description: '6.5 bare-hand damage for 12 s after activating Push'
defensive:
name: '<white>Wakanda Forever!</white>'
description: 'Fall from 3+ blocks onto an enemy for AOE damage and a crater impact'
description: 'Activate Leap, then land on enemies for AOE damage and a crater'
messages:
fist_mode_active: '<gray>⚡ Vibranium Fists active for <yellow>12 seconds</yellow>!</gray>'
wakanda_impact: '<white>⚡ Wakanda Forever! Hit <yellow><count></yellow> enemy(s)!</white>'
# NEU:
wakanda_leap: '<white>⬆ Wakanda Forever — brace for impact!</white>'
ability_charged: '<yellow>⚡ Ability recharged!</yellow>'
# ── TheWorld ──────────────────────────────────────────────────────────────────
@@ -768,11 +772,12 @@ kits:
name: '<aqua>Trident Parry</aqua>'
description: '20% chance to parry melee attacks: launch attacker back with Slowness I'
messages:
dive_launched: '<aqua>⚡ Launched! <yellow><charges></yellow> charge(s) remaining.</aqua>'
charges_left: '<aqua>⚡ <yellow><charges></yellow> dive charge(s) left!</aqua>'
sequence_done: '<gray>Dive sequence complete.</gray>'
parry_success: '<aqua>⚡ Parried!</aqua>'
parried_by_victim: '<red>⚡ Your attack was parried!</red>'
dive_launched: '<aqua>⚡ Launched! Brace for impact!</aqua>'
parry_success: '<aqua>🛡 Parried! Attacker bounced back!</aqua>'
parried_by_victim: '<red>Your attack was parried!</red>'
charges_left: '<aqua>⚡ <yellow><charges></yellow> dive charge(s) remaining!</aqua>'
sequence_done: '<gray>Sequence complete — recharging...</gray>'
ability_charged: '<yellow>⚡ Ability recharged!</yellow>'
# ── Blitzcrank ────────────────────────────────────────────────────────────────
blitzcrank: