Make kit parameters configurable and refactor
Introduce live-configurable parameters and refactor three kits (Anchor, BlackPanther, Blitzcrank). - AnchorKit: replace hardcoded constants with default values + live config accessors (partial_resistance, golem_hp, radii, bonus dmg, monitor interval). Snapshot config on activation, update doc comments to JSON keys, minor API/formatting cleanup, and make passive/active logic use dynamic radii and damages. - BlackPantherKit: add defaults and live getters for fist mode, push, pounce and extras (push_knockback_speed/y, fist_mode_damage, projectile_delay_ticks). Snapshot values on activate, update docs to expose JSON keys, and small behavioral formatting fixes. - BlitzcrankKit: add defaults and live getters for hook, stun and ult settings (range, pull_strength, radii, durations, cooldown, raycast params), update docs and remove unused imports; snapshot config where needed. Also: several minor code cleanups (whitespace/formatting, removal of unused imports, fully qualified event references) and improved in-code documentation for custom settings. These changes enable per-kit overrides via SPEEDHG_CUSTOM_SETTINGS and make runtime behavior consistent by snapshotting values at ability execution.
This commit is contained in:
@@ -19,7 +19,6 @@ import org.bukkit.Sound
|
|||||||
import org.bukkit.attribute.Attribute
|
import org.bukkit.attribute.Attribute
|
||||||
import org.bukkit.entity.IronGolem
|
import org.bukkit.entity.IronGolem
|
||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
|
||||||
import org.bukkit.inventory.ItemStack
|
import org.bukkit.inventory.ItemStack
|
||||||
import org.bukkit.persistence.PersistentDataType
|
import org.bukkit.persistence.PersistentDataType
|
||||||
import org.bukkit.scheduler.BukkitTask
|
import org.bukkit.scheduler.BukkitTask
|
||||||
@@ -29,198 +28,265 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
/**
|
/**
|
||||||
* ## AnchorKit
|
* ## AnchorKit
|
||||||
*
|
*
|
||||||
* **Passiv (immer aktiv):** 40 % Rückschlag-Reduktion über `GENERIC_KNOCKBACK_RESISTANCE`.
|
* **Passiv (immer aktiv):** [partialResistance] Rückschlag-Reduktion über `GENERIC_KNOCKBACK_RESISTANCE`.
|
||||||
*
|
*
|
||||||
* **Active (beide Playstyles):** Beschwört einen Eisengolem als „Anker".
|
* **Active (beide Playstyles):** Beschwört einen Eisengolem als „Anker".
|
||||||
* - Während der Spieler im Radius des Ankers ist: voller NoKnock + Bonus-Schaden.
|
* - Während der Spieler im Radius des Ankers ist: voller NoKnock + Bonus-Schaden.
|
||||||
* - Der Golem kann von Gegnern zerstört werden (20 HP). Bei Tod spielt er den
|
* - Der Golem kann von Gegnern zerstört werden ([golemHp] HP). Bei Tod spielt er den
|
||||||
* Eisengolem-Todesklang und benachrichtigt den Besitzer.
|
* Eisengolem-Todesklang und benachrichtigt den Besitzer.
|
||||||
* - Nur ein aktiver Anker gleichzeitig; neuer Anker entfernt den alten.
|
* - Nur ein aktiver Anker gleichzeitig; neuer Anker entfernt den alten.
|
||||||
*
|
*
|
||||||
* | Playstyle | Radius | Bonus-Schaden |
|
* | Playstyle | Radius | Bonus-Schaden |
|
||||||
* |-------------|--------|----------------------------|
|
* |-------------|-------------------|---------------------------------------------------|
|
||||||
* | AGGRESSIVE | 5 Blöcke | +1,0 HP (0,5 Herzen) auf jedem Treffer |
|
* | AGGRESSIVE | [aggressiveRadius] Blöcke | +[aggressiveBonusDmg] HP (0,5 Herzen) auf jedem Treffer |
|
||||||
* | DEFENSIVE | 8 Blöcke | kein Schaden-Bonus, aber +Resistance I |
|
* | DEFENSIVE | [defensiveRadius] Blöcke | kein Schaden-Bonus, aber +Resistance I |
|
||||||
*
|
*
|
||||||
* ### Technische Lösung – Golem-Tod-Erkennung ohne eigenen Listener:
|
* ## Konfiguration (via `SPEEDHG_CUSTOM_SETTINGS`)
|
||||||
* Ein `BukkitTask` prüft alle 10 Ticks (0,5 s), ob `golem.isDead || !golem.isValid`.
|
|
||||||
* Der Golem wird mit `isSilent = true` gespawnt, sodass wir den Eisengolem-Todesklang
|
|
||||||
* manuell abspielen können (kein unerwarteter Doppel-Sound).
|
|
||||||
* Der Golem erhält 20 HP (statt 100 vanilla), damit er in HG-Kämpfen destroybar ist.
|
|
||||||
*
|
*
|
||||||
* ### Rückschlag-Reduktion:
|
* | JSON-Schlüssel | Typ | Default | Beschreibung |
|
||||||
* `onAssign` setzt `GENERIC_KNOCKBACK_RESISTANCE.baseValue = PARTIAL_RESISTANCE`.
|
* |-----------------------------|--------|---------|---------------------------------------------------|
|
||||||
* Ein periodischer Task aktualisiert den Wert auf 1.0 (wenn im Radius) oder zurück
|
* | `partial_resistance` | Double | `0.4` | Basis-Rückschlag-Reduktion (40 %) |
|
||||||
* auf PARTIAL_RESISTANCE (wenn außerhalb).
|
* | `golem_hp` | Double | `20.0` | HP des Anker-Golems (10 Herzen) |
|
||||||
* `onRemove` setzt den Attributwert auf 0,0 zurück.
|
* | `aggressive_radius` | Double | `5.0` | Radius im Aggressive-Modus (Blöcke) |
|
||||||
|
* | `defensive_radius` | Double | `8.0` | Radius im Defensive-Modus (Blöcke) |
|
||||||
|
* | `aggressive_bonus_dmg` | Double | `1.0` | Bonus-Schaden im Radius (HP) |
|
||||||
|
* | `monitor_interval_ticks` | Long | `10` | Ticks zwischen Golem-Zustand-Prüfungen |
|
||||||
*/
|
*/
|
||||||
class AnchorKit : Kit() {
|
class AnchorKit : Kit()
|
||||||
|
{
|
||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
private val mm = MiniMessage.miniMessage()
|
private val mm = MiniMessage.miniMessage()
|
||||||
|
|
||||||
override val id = "anchor"
|
override val id = "anchor"
|
||||||
override val displayName: Component
|
override val displayName: Component
|
||||||
get() = plugin.languageManager.getDefaultComponent("kits.anchor.name", mapOf())
|
get() = plugin.languageManager.getDefaultComponent( "kits.anchor.name", mapOf() )
|
||||||
override val lore: List<String>
|
override val lore: List<String>
|
||||||
get() = plugin.languageManager.getDefaultRawMessageList("kits.anchor.lore")
|
get() = plugin.languageManager.getDefaultRawMessageList( "kits.anchor.lore" )
|
||||||
override val icon = Material.ANVIL
|
override val icon = Material.ANVIL
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val PARTIAL_RESISTANCE = 0.4 // 40 % – immer aktiv
|
const val DEFAULT_PARTIAL_RESISTANCE = 0.4
|
||||||
const val GOLEM_HP = 20.0 // 10 Herzen
|
const val DEFAULT_GOLEM_HP = 20.0
|
||||||
const val AGGRESSIVE_RADIUS = 5.0
|
const val DEFAULT_AGGRESSIVE_RADIUS = 5.0
|
||||||
const val DEFENSIVE_RADIUS = 8.0
|
const val DEFAULT_DEFENSIVE_RADIUS = 8.0
|
||||||
const val AGGRESSIVE_BONUS_DMG = 1.0 // +0,5 Herzen
|
const val DEFAULT_AGGRESSIVE_BONUS_DMG = 1.0
|
||||||
const val MONITOR_INTERVAL_TICKS = 10L // alle 0,5 s prüfen
|
const val DEFAULT_MONITOR_INTERVAL_TICKS = 10L
|
||||||
|
|
||||||
const val PDC_KEY = "anchor_owner_uuid"
|
const val PDC_KEY = "anchor_owner_uuid"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val anchorGolems : MutableMap<UUID, IronGolem> = ConcurrentHashMap()
|
// ── Live config accessors ─────────────────────────────────────────────────
|
||||||
private val monitorTasks : MutableMap<UUID, BukkitTask> = ConcurrentHashMap()
|
|
||||||
|
/**
|
||||||
|
* Basis-Rückschlag-Reduktion, die immer aktiv ist (40 % Standard).
|
||||||
|
* JSON-Schlüssel: `partial_resistance`
|
||||||
|
*/
|
||||||
|
private val partialResistance: Double
|
||||||
|
get() = override().getDouble( "partial_resistance" ) ?: DEFAULT_PARTIAL_RESISTANCE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HP des Eisengolem-Ankers. Vanilla = 100 HP; niedrigerer Wert macht ihn destroybar.
|
||||||
|
* JSON-Schlüssel: `golem_hp`
|
||||||
|
*/
|
||||||
|
private val golemHp: Double
|
||||||
|
get() = override().getDouble( "golem_hp" ) ?: DEFAULT_GOLEM_HP
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Radius im Aggressive-Modus in Blöcken.
|
||||||
|
* JSON-Schlüssel: `aggressive_radius`
|
||||||
|
*/
|
||||||
|
private val aggressiveRadius: Double
|
||||||
|
get() = override().getDouble( "aggressive_radius" ) ?: DEFAULT_AGGRESSIVE_RADIUS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Radius im Defensive-Modus in Blöcken.
|
||||||
|
* JSON-Schlüssel: `defensive_radius`
|
||||||
|
*/
|
||||||
|
private val defensiveRadius: Double
|
||||||
|
get() = override().getDouble( "defensive_radius" ) ?: DEFAULT_DEFENSIVE_RADIUS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bonus-Schaden pro Treffer im Aggressive-Modus (+0,5 Herzen Standard).
|
||||||
|
* JSON-Schlüssel: `aggressive_bonus_dmg`
|
||||||
|
*/
|
||||||
|
private val aggressiveBonusDmg: Double
|
||||||
|
get() = override().getDouble( "aggressive_bonus_dmg" ) ?: DEFAULT_AGGRESSIVE_BONUS_DMG
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ticks zwischen Golem-Zustand-Prüfungen und Resistenz-Updates.
|
||||||
|
* JSON-Schlüssel: `monitor_interval_ticks`
|
||||||
|
*/
|
||||||
|
private val monitorIntervalTicks: Long
|
||||||
|
get() = override().getLong( "monitor_interval_ticks" ) ?: DEFAULT_MONITOR_INTERVAL_TICKS
|
||||||
|
|
||||||
|
// ── Shared kit state ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private val anchorGolems: MutableMap<UUID, IronGolem> = ConcurrentHashMap()
|
||||||
|
private val monitorTasks: MutableMap<UUID, BukkitTask> = ConcurrentHashMap()
|
||||||
|
|
||||||
// ── Gecachte Instanzen ────────────────────────────────────────────────────
|
// ── Gecachte Instanzen ────────────────────────────────────────────────────
|
||||||
|
|
||||||
private val aggressiveActive = AnchorActive(Playstyle.AGGRESSIVE, AGGRESSIVE_RADIUS)
|
private val aggressiveActive = AnchorActive( Playstyle.AGGRESSIVE )
|
||||||
private val defensiveActive = AnchorActive(Playstyle.DEFENSIVE, DEFENSIVE_RADIUS)
|
private val defensiveActive = AnchorActive( Playstyle.DEFENSIVE )
|
||||||
private val aggressivePassive = AnchorPassive(Playstyle.AGGRESSIVE, AGGRESSIVE_RADIUS, bonusDamage = AGGRESSIVE_BONUS_DMG, resistanceBonus = false)
|
private val aggressivePassive = AnchorPassive( Playstyle.AGGRESSIVE, bonusDamage = true, resistanceBonus = false )
|
||||||
private val defensivePassive = AnchorPassive(Playstyle.DEFENSIVE, DEFENSIVE_RADIUS, bonusDamage = 0.0, resistanceBonus = true)
|
private val defensivePassive = AnchorPassive( Playstyle.DEFENSIVE, bonusDamage = false, resistanceBonus = true )
|
||||||
|
|
||||||
override fun getActiveAbility(playstyle: Playstyle) = when (playstyle) {
|
override fun getActiveAbility(
|
||||||
|
playstyle: Playstyle
|
||||||
|
) = when( playstyle )
|
||||||
|
{
|
||||||
Playstyle.AGGRESSIVE -> aggressiveActive
|
Playstyle.AGGRESSIVE -> aggressiveActive
|
||||||
Playstyle.DEFENSIVE -> defensiveActive
|
Playstyle.DEFENSIVE -> defensiveActive
|
||||||
}
|
}
|
||||||
override fun getPassiveAbility(playstyle: Playstyle) = when (playstyle) {
|
|
||||||
|
override fun getPassiveAbility(
|
||||||
|
playstyle: Playstyle
|
||||||
|
) = when( playstyle )
|
||||||
|
{
|
||||||
Playstyle.AGGRESSIVE -> aggressivePassive
|
Playstyle.AGGRESSIVE -> aggressivePassive
|
||||||
Playstyle.DEFENSIVE -> defensivePassive
|
Playstyle.DEFENSIVE -> defensivePassive
|
||||||
}
|
}
|
||||||
|
|
||||||
override val cachedItems = ConcurrentHashMap<UUID, List<ItemStack>>()
|
override val cachedItems = ConcurrentHashMap<UUID, List<ItemStack>>()
|
||||||
|
|
||||||
override fun giveItems(player: Player, playstyle: Playstyle) {
|
override fun giveItems(
|
||||||
val active = getActiveAbility(playstyle)
|
player: Player,
|
||||||
val item = ItemBuilder(Material.CHAIN)
|
playstyle: Playstyle
|
||||||
.name(active.name)
|
) {
|
||||||
.lore(listOf(active.description))
|
val active = getActiveAbility( playstyle )
|
||||||
|
val item = ItemBuilder( Material.CHAIN )
|
||||||
|
.name( active.name )
|
||||||
|
.lore(listOf( active.description ))
|
||||||
.build()
|
.build()
|
||||||
cachedItems[player.uniqueId] = listOf(item)
|
cachedItems[ player.uniqueId ] = listOf( item )
|
||||||
player.inventory.addItem(item)
|
player.inventory.addItem( item )
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Lifecycle: Rückschlag-Basis-Resistenz setzen/entfernen ───────────────
|
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
override fun onAssign(player: Player, playstyle: Playstyle) {
|
override fun onAssign(
|
||||||
player.getAttribute(Attribute.GENERIC_KNOCKBACK_RESISTANCE)
|
player: Player,
|
||||||
?.baseValue = PARTIAL_RESISTANCE
|
playstyle: Playstyle
|
||||||
|
) {
|
||||||
|
val capturedPartialResistance = partialResistance
|
||||||
|
player.getAttribute( Attribute.GENERIC_KNOCKBACK_RESISTANCE )
|
||||||
|
?.baseValue = capturedPartialResistance
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRemove(player: Player) {
|
override fun onRemove(
|
||||||
// Golem entfernen
|
player: Player
|
||||||
removeAnchor(player, playDeathSound = false)
|
) {
|
||||||
// Rückschlag-Resistenz zurücksetzen
|
removeAnchor( player, playDeathSound = false )
|
||||||
player.getAttribute(Attribute.GENERIC_KNOCKBACK_RESISTANCE)
|
player.getAttribute( Attribute.GENERIC_KNOCKBACK_RESISTANCE )
|
||||||
?.baseValue = 0.0
|
?.baseValue = 0.0
|
||||||
cachedItems.remove(player.uniqueId)?.forEach { player.inventory.remove(it) }
|
cachedItems.remove( player.uniqueId )?.forEach { player.inventory.remove( it ) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Active Ability – Anker-Golem beschwören (beide Playstyles, unterschiedlicher Radius)
|
// Active Ability – Anker-Golem beschwören (beide Playstyles)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
inner class AnchorActive(
|
inner class AnchorActive(
|
||||||
playstyle: Playstyle,
|
playstyle: Playstyle
|
||||||
private val radius: Double
|
) : ActiveAbility( playstyle ) {
|
||||||
) : ActiveAbility(playstyle) {
|
|
||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
override val kitId = "anchor"
|
override val kitId = "anchor"
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.anchor.items.chain.name")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.anchor.items.chain.name" )
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.anchor.items.chain.description")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.anchor.items.chain.description" )
|
||||||
override val hardcodedHitsRequired = 15
|
override val hardcodedHitsRequired = 15
|
||||||
override val triggerMaterial = Material.ANVIL
|
override val triggerMaterial = Material.ANVIL
|
||||||
|
|
||||||
override fun execute(player: Player): AbilityResult {
|
override fun execute(
|
||||||
// Alten Anker entfernen (kein Todesklang – Spieler beschwört neuen)
|
player: Player
|
||||||
removeAnchor(player, playDeathSound = false)
|
): AbilityResult
|
||||||
|
{
|
||||||
|
removeAnchor( player, playDeathSound = false )
|
||||||
|
|
||||||
val spawnLoc = player.location.clone()
|
val spawnLoc = player.location.clone()
|
||||||
val world = spawnLoc.world ?: return AbilityResult.ConditionNotMet("World is null")
|
val world = spawnLoc.world ?: return AbilityResult.ConditionNotMet( "World is null" )
|
||||||
|
|
||||||
// Eisengolem spawnen
|
// Werte zum Aktivierungszeitpunkt snapshotten
|
||||||
val golem = world.spawn(spawnLoc, IronGolem::class.java) { g ->
|
val capturedGolemHp = golemHp
|
||||||
g.setAI(false) // keine Bewegung, kein Angriff
|
val capturedPartialResistance = partialResistance
|
||||||
g.isSilent = true // Todesklang manuell kontrollieren
|
val capturedMonitorInterval = monitorIntervalTicks
|
||||||
g.isInvulnerable = false // muss zerstörbar sein
|
val capturedRadius = when( playstyle )
|
||||||
g.customName(mm.deserialize("<gray>⚓ <white>Anker</white>"))
|
{
|
||||||
|
Playstyle.AGGRESSIVE -> aggressiveRadius
|
||||||
|
Playstyle.DEFENSIVE -> defensiveRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
val golem = world.spawn( spawnLoc, IronGolem::class.java ) { g ->
|
||||||
|
g.setAI( false )
|
||||||
|
g.isSilent = true
|
||||||
|
g.isInvulnerable = false
|
||||||
|
g.customName(mm.deserialize( "<gray>⚓ <white>Anker</white>" ))
|
||||||
g.isCustomNameVisible = true
|
g.isCustomNameVisible = true
|
||||||
|
|
||||||
// HP reduzieren (vanilla = 100 HP)
|
g.getAttribute( Attribute.GENERIC_MAX_HEALTH )?.baseValue = capturedGolemHp
|
||||||
g.getAttribute(Attribute.GENERIC_MAX_HEALTH)?.baseValue = GOLEM_HP
|
g.health = capturedGolemHp
|
||||||
g.health = GOLEM_HP
|
|
||||||
|
|
||||||
// PDC: Besitzer-UUID für spätere Identifikation
|
|
||||||
g.persistentDataContainer.set(
|
g.persistentDataContainer.set(
|
||||||
NamespacedKey(plugin, PDC_KEY),
|
NamespacedKey( plugin, PDC_KEY ),
|
||||||
PersistentDataType.STRING,
|
PersistentDataType.STRING,
|
||||||
player.uniqueId.toString()
|
player.uniqueId.toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
anchorGolems[player.uniqueId] = golem
|
anchorGolems[ player.uniqueId ] = golem
|
||||||
|
|
||||||
// Monitor-Task: prüft Golem-Zustand + aktualisiert Rückschlag-Resistenz
|
val task = Bukkit.getScheduler().runTaskTimer( plugin, { ->
|
||||||
val task = Bukkit.getScheduler().runTaskTimer(plugin, { ->
|
val activeGolem = anchorGolems[ player.uniqueId ]
|
||||||
val activeGolem = anchorGolems[player.uniqueId]
|
|
||||||
|
|
||||||
if (activeGolem == null || activeGolem.isDead || !activeGolem.isValid) {
|
if ( activeGolem == null || activeGolem.isDead || !activeGolem.isValid )
|
||||||
// Golem wurde von Gegnern zerstört
|
{
|
||||||
if (activeGolem?.isDead == true) {
|
if ( activeGolem?.isDead == true )
|
||||||
onAnchorDestroyed(player, activeGolem.location)
|
{
|
||||||
|
onAnchorDestroyed( player, activeGolem.location )
|
||||||
}
|
}
|
||||||
monitorTasks.remove(player.uniqueId)?.cancel()
|
monitorTasks.remove( player.uniqueId )?.cancel()
|
||||||
// Resistenz zurück auf Basis-Wert (Golem ist weg)
|
if ( player.isOnline )
|
||||||
if (player.isOnline) {
|
{
|
||||||
player.getAttribute(Attribute.GENERIC_KNOCKBACK_RESISTANCE)
|
player.getAttribute( Attribute.GENERIC_KNOCKBACK_RESISTANCE )
|
||||||
?.baseValue = PARTIAL_RESISTANCE
|
?.baseValue = capturedPartialResistance
|
||||||
}
|
}
|
||||||
return@runTaskTimer
|
return@runTaskTimer
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!player.isOnline) {
|
if ( !player.isOnline )
|
||||||
|
{
|
||||||
activeGolem.remove()
|
activeGolem.remove()
|
||||||
anchorGolems.remove(player.uniqueId)
|
anchorGolems.remove( player.uniqueId )
|
||||||
monitorTasks.remove(player.uniqueId)?.cancel()
|
monitorTasks.remove( player.uniqueId )?.cancel()
|
||||||
return@runTaskTimer
|
return@runTaskTimer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Radius-Check: voller NoKnock im Anker-Radius
|
val inRadius = player.location.distanceSquared( activeGolem.location ) <= capturedRadius * capturedRadius
|
||||||
val inRadius = player.location.distanceSquared(activeGolem.location) <= radius * radius
|
val targetResistance = if ( inRadius ) 1.0 else capturedPartialResistance
|
||||||
val targetResistance = if (inRadius) 1.0 else PARTIAL_RESISTANCE
|
player.getAttribute( Attribute.GENERIC_KNOCKBACK_RESISTANCE )?.baseValue = targetResistance
|
||||||
player.getAttribute(Attribute.GENERIC_KNOCKBACK_RESISTANCE)?.baseValue = targetResistance
|
|
||||||
|
|
||||||
// Visueller Indikator am Golem (Partikelring)
|
if ( inRadius )
|
||||||
if (inRadius) {
|
{
|
||||||
world.spawnParticle(
|
world.spawnParticle(
|
||||||
Particle.CRIT,
|
Particle.CRIT,
|
||||||
activeGolem.location.clone().add(0.0, 2.5, 0.0),
|
activeGolem.location.clone().add( 0.0, 2.5, 0.0 ),
|
||||||
2, 0.1, 0.1, 0.1, 0.0
|
2, 0.1, 0.1, 0.1, 0.0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}, 0L, MONITOR_INTERVAL_TICKS)
|
}, 0L, capturedMonitorInterval )
|
||||||
|
|
||||||
monitorTasks[player.uniqueId] = task
|
monitorTasks[ player.uniqueId ] = task
|
||||||
|
|
||||||
// Feedback
|
world.playSound( spawnLoc, Sound.ENTITY_IRON_GOLEM_HURT, 1f, 0.5f )
|
||||||
world.playSound(spawnLoc, Sound.ENTITY_IRON_GOLEM_HURT, 1f, 0.5f)
|
world.spawnParticle( Particle.CLOUD, spawnLoc.clone().add( 0.0, 1.0, 0.0 ), 20, 0.5, 0.3, 0.5, 0.05 )
|
||||||
world.spawnParticle(Particle.CLOUD, spawnLoc.clone().add(0.0, 1.0, 0.0), 20, 0.5, 0.3, 0.5, 0.05)
|
|
||||||
player.sendActionBar(
|
player.sendActionBar(
|
||||||
player.trans("kits.anchor.messages.anchor_placed",
|
player.trans( "kits.anchor.messages.anchor_placed",
|
||||||
"radius" to radius.toInt().toString())
|
"radius" to capturedRadius.toInt().toString() )
|
||||||
)
|
)
|
||||||
return AbilityResult.Success
|
return AbilityResult.Success
|
||||||
}
|
}
|
||||||
@@ -232,42 +298,56 @@ class AnchorKit : Kit() {
|
|||||||
|
|
||||||
inner class AnchorPassive(
|
inner class AnchorPassive(
|
||||||
playstyle: Playstyle,
|
playstyle: Playstyle,
|
||||||
private val radius: Double,
|
private val bonusDamage: Boolean,
|
||||||
private val bonusDamage: Double,
|
|
||||||
private val resistanceBonus: Boolean
|
private val resistanceBonus: Boolean
|
||||||
) : PassiveAbility(playstyle) {
|
) : PassiveAbility( playstyle ) {
|
||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.anchor.passive.name")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.anchor.passive.name" )
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.anchor.passive.description")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.anchor.passive.description" )
|
||||||
|
|
||||||
override fun onHitEnemy(attacker: Player, victim: Player, event: EntityDamageByEntityEvent) {
|
override fun onHitEnemy(
|
||||||
val golem = anchorGolems[attacker.uniqueId] ?: return
|
attacker: Player,
|
||||||
|
victim: Player,
|
||||||
|
event: org.bukkit.event.entity.EntityDamageByEntityEvent
|
||||||
|
) {
|
||||||
|
val golem = anchorGolems[ attacker.uniqueId ] ?: return
|
||||||
|
|
||||||
// Nur wirksam wenn Angreifer im Radius
|
val capturedRadius = when( playstyle )
|
||||||
if (attacker.location.distanceSquared(golem.location) > radius * radius) return
|
{
|
||||||
|
Playstyle.AGGRESSIVE -> aggressiveRadius
|
||||||
|
Playstyle.DEFENSIVE -> defensiveRadius
|
||||||
|
}
|
||||||
|
|
||||||
// Bonus-Schaden (Aggressive playstyle)
|
if ( attacker.location.distanceSquared( golem.location ) > capturedRadius * capturedRadius ) return
|
||||||
if (bonusDamage > 0.0) {
|
|
||||||
event.damage += bonusDamage
|
if ( bonusDamage )
|
||||||
|
{
|
||||||
|
val capturedBonusDmg = aggressiveBonusDmg
|
||||||
|
event.damage += capturedBonusDmg
|
||||||
attacker.world.spawnParticle(
|
attacker.world.spawnParticle(
|
||||||
Particle.CRIT,
|
Particle.CRIT,
|
||||||
victim.location.clone().add(0.0, 1.2, 0.0),
|
victim.location.clone().add( 0.0, 1.2, 0.0 ),
|
||||||
5, 0.2, 0.2, 0.2, 0.0
|
5, 0.2, 0.2, 0.2, 0.0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onHitByEnemy(victim: Player, attacker: Player, event: EntityDamageByEntityEvent) {
|
override fun onHitByEnemy(
|
||||||
if (!resistanceBonus) return
|
victim: Player,
|
||||||
val golem = anchorGolems[victim.uniqueId] ?: return
|
attacker: Player,
|
||||||
|
event: org.bukkit.event.entity.EntityDamageByEntityEvent
|
||||||
|
) {
|
||||||
|
if ( !resistanceBonus ) return
|
||||||
|
val golem = anchorGolems[ victim.uniqueId ] ?: return
|
||||||
|
|
||||||
// Resistance I während im Radius (Defensive playstyle)
|
val capturedRadius = defensiveRadius
|
||||||
if (victim.location.distanceSquared(golem.location) <= radius * radius) {
|
|
||||||
// Schaden um ~20 % reduzieren (Resistance I Äquivalent)
|
if ( victim.location.distanceSquared( golem.location ) <= capturedRadius * capturedRadius )
|
||||||
|
{
|
||||||
event.damage *= 0.80
|
event.damage *= 0.80
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,36 +357,36 @@ class AnchorKit : Kit() {
|
|||||||
// Hilfsmethoden
|
// Hilfsmethoden
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
/**
|
private fun removeAnchor(
|
||||||
* Entfernt den aktiven Anker eines Spielers sauber.
|
player: Player,
|
||||||
* @param playDeathSound Falls `true`, wird der Eisengolem-Todesklang abgespielt.
|
playDeathSound: Boolean
|
||||||
*/
|
) {
|
||||||
private fun removeAnchor(player: Player, playDeathSound: Boolean) {
|
monitorTasks.remove( player.uniqueId )?.cancel()
|
||||||
monitorTasks.remove(player.uniqueId)?.cancel()
|
|
||||||
|
|
||||||
val golem = anchorGolems.remove(player.uniqueId) ?: return
|
val golem = anchorGolems.remove( player.uniqueId ) ?: return
|
||||||
if (playDeathSound && golem.isValid) {
|
if ( playDeathSound && golem.isValid )
|
||||||
golem.world.playSound(golem.location, Sound.ENTITY_IRON_GOLEM_DEATH, 1f, 1f)
|
{
|
||||||
|
golem.world.playSound( golem.location, Sound.ENTITY_IRON_GOLEM_DEATH, 1f, 1f )
|
||||||
}
|
}
|
||||||
if (golem.isValid) golem.remove()
|
if ( golem.isValid ) golem.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun onAnchorDestroyed(
|
||||||
* Wird aufgerufen, wenn der Golem von Gegnern zerstört wurde (HP == 0).
|
player: Player,
|
||||||
* Der Golem ist zu diesem Zeitpunkt bereits `isDead`, wir spielen den Sound manuell
|
deathLocation: Location
|
||||||
* (weil der Golem mit `isSilent = true` gespawnt wurde).
|
) {
|
||||||
*/
|
anchorGolems.remove( player.uniqueId )
|
||||||
private fun onAnchorDestroyed(player: Player, deathLocation: Location) {
|
|
||||||
anchorGolems.remove(player.uniqueId)
|
|
||||||
|
|
||||||
deathLocation.world?.playSound(deathLocation, Sound.ENTITY_IRON_GOLEM_DEATH, 1f, 1f)
|
deathLocation.world?.playSound( deathLocation, Sound.ENTITY_IRON_GOLEM_DEATH, 1f, 1f )
|
||||||
deathLocation.world?.spawnParticle(
|
deathLocation.world?.spawnParticle(
|
||||||
Particle.EXPLOSION, deathLocation, 3, 0.3, 0.3, 0.3, 0.0
|
Particle.EXPLOSION, deathLocation, 3, 0.3, 0.3, 0.3, 0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
if (player.isOnline) {
|
if ( player.isOnline )
|
||||||
player.sendActionBar(player.trans("kits.anchor.messages.anchor_destroyed"))
|
{
|
||||||
player.playSound(player.location, Sound.ENTITY_IRON_GOLEM_DEATH, 0.8f, 1.3f)
|
player.sendActionBar( player.trans( "kits.anchor.messages.anchor_destroyed" ) )
|
||||||
|
player.playSound( player.location, Sound.ENTITY_IRON_GOLEM_DEATH, 0.8f, 1.3f )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package club.mcscrims.speedhg.kit.impl
|
package club.mcscrims.speedhg.kit.impl
|
||||||
|
|
||||||
import club.mcscrims.speedhg.SpeedHG
|
import club.mcscrims.speedhg.SpeedHG
|
||||||
import club.mcscrims.speedhg.config.CustomGameSettings
|
|
||||||
import club.mcscrims.speedhg.kit.Kit
|
import club.mcscrims.speedhg.kit.Kit
|
||||||
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
|
||||||
@@ -27,25 +26,27 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
*
|
*
|
||||||
* | Playstyle | Active | Passive |
|
* | Playstyle | Active | Passive |
|
||||||
* |-------------|-------------------------------------------------|-------------------------------------------------|
|
* |-------------|-------------------------------------------------|-------------------------------------------------|
|
||||||
* | AGGRESSIVE | **Push** – knockback all enemies ≤ 5 blocks + | **Vibranium Fists** – 6.5 dmg bare-hand for 12 s|
|
* | AGGRESSIVE | **Push** – knockback all enemies ≤ [pushRadius] blocks + | **Vibranium Fists** – [fistModeDurationMs]ms bare-hand |
|
||||||
* | | shoot push-projectiles + activate Fist Mode | |
|
* | | shoot push-projectiles + activate Fist Mode | |
|
||||||
* | DEFENSIVE | – (no active item) | **Wakanda Forever!** – fall-pounce on enemies |
|
* | DEFENSIVE | – (no active item) | **Wakanda Forever!** – fall-pounce on enemies |
|
||||||
*
|
*
|
||||||
* ### Push (AGGRESSIVE active)
|
* ## Konfiguration (via `SPEEDHG_CUSTOM_SETTINGS`)
|
||||||
* All enemies within 5 blocks are launched outward. A marked Snowball is fired
|
|
||||||
* toward each pushed enemy 5 ticks later; on hit it deals **4 bonus damage**
|
|
||||||
* (handled by [KitEventDispatcher] via [PUSH_PROJECTILE_KEY]).
|
|
||||||
* CRIT particles spawn at each pushed enemy's position for visual feedback.
|
|
||||||
* Directly after the push, **Fist Mode** activates for 12 seconds.
|
|
||||||
*
|
*
|
||||||
* ### Vibranium Fists (AGGRESSIVE passive)
|
* Typisierte Felder in [CustomGameSettings.KitOverride] werden direkt gelesen.
|
||||||
* During Fist Mode, bare-hand attacks (`itemInMainHand == AIR`) override the
|
* Zusätzliche Einstellungen über die `extras`-Map.
|
||||||
* normal damage to **6.5 HP (3.25 hearts)**.
|
|
||||||
*
|
*
|
||||||
* ### Wakanda Forever! (DEFENSIVE passive)
|
* | Quelle | JSON-Schlüssel | Typ | Default | Beschreibung |
|
||||||
* Triggers in [onHitEnemy] when `attacker.fallDistance ≥ 3`. Deals **6 HP**
|
* |------------------|-----------------------------|--------|----------|-------------------------------------------|
|
||||||
* to all enemies within 3 blocks of the victim, then creates an explosion
|
* | Typisiertes Feld | `fist_mode_ms` | Long | `12000` | Dauer des Fist-Modus (ms) |
|
||||||
* visual and a small WorldEdit crater at the landing site.
|
* | Typisiertes Feld | `push_radius` | Double | `5.0` | Radius der Push-Schockwelle (Blöcke) |
|
||||||
|
* | Typisiertes Feld | `push_bonus_damage` | Double | `4.0` | Bonus-Schaden der Push-Projektile (HP) |
|
||||||
|
* | Typisiertes Feld | `pounce_min_fall` | Float | `3.0` | Mindest-Fallhöhe für Wakanda-Pounce |
|
||||||
|
* | Typisiertes Feld | `pounce_radius` | Double | `3.0` | AoE-Radius des Pounce-Aufpralls (Blöcke) |
|
||||||
|
* | Typisiertes Feld | `pounce_damage` | Double | `6.0` | Schaden des Pounce-Aufpralls (HP) |
|
||||||
|
* | `extras` | `push_knockback_speed` | Double | `2.0` | Horizontaler Velocity-Multiplikator |
|
||||||
|
* | `extras` | `push_knockback_y` | Double | `0.45` | Vertikaler Y-Impuls des Pushes |
|
||||||
|
* | `extras` | `fist_mode_damage` | Double | `6.5` | Schaden der Vibranium-Fists (HP) |
|
||||||
|
* | `extras` | `projectile_delay_ticks` | Long | `5` | Ticks Verzögerung vor dem Projektil |
|
||||||
*/
|
*/
|
||||||
class BlackPantherKit : Kit()
|
class BlackPantherKit : Kit()
|
||||||
{
|
{
|
||||||
@@ -54,9 +55,9 @@ class BlackPantherKit : Kit()
|
|||||||
|
|
||||||
override val id = "blackpanther"
|
override val id = "blackpanther"
|
||||||
override val displayName: Component
|
override val displayName: Component
|
||||||
get() = plugin.languageManager.getDefaultComponent("kits.blackpanther.name", mapOf())
|
get() = plugin.languageManager.getDefaultComponent( "kits.blackpanther.name", mapOf() )
|
||||||
override val lore: List<String>
|
override val lore: List<String>
|
||||||
get() = plugin.languageManager.getDefaultRawMessageList("kits.blackpanther.lore")
|
get() = plugin.languageManager.getDefaultRawMessageList( "kits.blackpanther.lore" )
|
||||||
override val icon = Material.BLACK_DYE
|
override val icon = Material.BLACK_DYE
|
||||||
|
|
||||||
/** Players currently in Fist Mode: UUID → expiry timestamp (ms). */
|
/** Players currently in Fist Mode: UUID → expiry timestamp (ms). */
|
||||||
@@ -67,47 +68,129 @@ class BlackPantherKit : Kit()
|
|||||||
|
|
||||||
companion object
|
companion object
|
||||||
{
|
{
|
||||||
private fun override() = SpeedHG.instance.customGameManager.settings.kits.kits["blackpanther"]
|
|
||||||
?: CustomGameSettings.KitOverride()
|
|
||||||
|
|
||||||
/** PDC key string shared with [KitEventDispatcher] for push-projectiles. */
|
/** PDC key string shared with [KitEventDispatcher] for push-projectiles. */
|
||||||
const val PUSH_PROJECTILE_KEY = "blackpanther_push_projectile"
|
const val PUSH_PROJECTILE_KEY = "blackpanther_push_projectile"
|
||||||
|
|
||||||
private val FIST_MODE_MS = override().fistModeDurationMs // 12 seconds
|
const val DEFAULT_FIST_MODE_DURATION_MS = 12_000L
|
||||||
private val PUSH_RADIUS = override().pushRadius
|
const val DEFAULT_PUSH_RADIUS = 5.0
|
||||||
private val POUNCE_MIN_FALL = override().pounceMinFall
|
const val DEFAULT_PUSH_BONUS_DAMAGE = 4.0
|
||||||
private val POUNCE_RADIUS = override().pounceRadius
|
const val DEFAULT_POUNCE_MIN_FALL = 3.0f
|
||||||
private val POUNCE_DAMAGE = override().pounceDamage // 3 hearts = 6 HP
|
const val DEFAULT_POUNCE_RADIUS = 3.0
|
||||||
|
const val DEFAULT_POUNCE_DAMAGE = 6.0
|
||||||
|
const val DEFAULT_PUSH_KNOCKBACK_SPEED = 2.0
|
||||||
|
const val DEFAULT_PUSH_KNOCKBACK_Y = 0.45
|
||||||
|
const val DEFAULT_FIST_MODE_DAMAGE = 6.5
|
||||||
|
const val DEFAULT_PROJECTILE_DELAY_TICKS = 5L
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Cached ability instances ──────────────────────────────────────────────
|
// ── Live config accessors ─────────────────────────────────────────────────
|
||||||
private val aggressiveActive = AggressiveActive()
|
|
||||||
private val defensiveActive = DefensiveActive()
|
|
||||||
private val aggressivePassive = AggressivePassive()
|
|
||||||
private val defensivePassive = DefensivePassive()
|
|
||||||
|
|
||||||
override fun getActiveAbility(playstyle: Playstyle) = when (playstyle)
|
/**
|
||||||
|
* Dauer des Vibranium-Fist-Modus in Millisekunden.
|
||||||
|
* Quelle: typisiertes Feld `fist_mode_ms`.
|
||||||
|
*/
|
||||||
|
private val fistModeDurationMs: Long
|
||||||
|
get() = override().fistModeDurationMs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Radius der Push-Schockwelle in Blöcken.
|
||||||
|
* Quelle: typisiertes Feld `push_radius`.
|
||||||
|
*/
|
||||||
|
private val pushRadius: Double
|
||||||
|
get() = override().pushRadius
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bonus-Schaden der nachfolgenden Push-Projektile in HP.
|
||||||
|
* Quelle: typisiertes Feld `push_bonus_damage`.
|
||||||
|
*/
|
||||||
|
private val pushBonusDamage: Double
|
||||||
|
get() = override().pushBonusDamage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mindest-Fallhöhe für den Wakanda-Forever-Pounce.
|
||||||
|
* Quelle: typisiertes Feld `pounce_min_fall`.
|
||||||
|
*/
|
||||||
|
private val pounceMinFall: Float
|
||||||
|
get() = override().pounceMinFall
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AoE-Radius des Wakanda-Pounce-Aufpralls in Blöcken.
|
||||||
|
* Quelle: typisiertes Feld `pounce_radius`.
|
||||||
|
*/
|
||||||
|
private val pounceRadius: Double
|
||||||
|
get() = override().pounceRadius
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schaden des Wakanda-Pounce-Aufpralls in HP.
|
||||||
|
* Quelle: typisiertes Feld `pounce_damage`.
|
||||||
|
*/
|
||||||
|
private val pounceDamage: Double
|
||||||
|
get() = override().pounceDamage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Horizontaler Velocity-Multiplikator des Pushes.
|
||||||
|
* Quelle: `extras["push_knockback_speed"]`.
|
||||||
|
*/
|
||||||
|
private val pushKnockbackSpeed: Double
|
||||||
|
get() = override().getDouble( "push_knockback_speed" ) ?: DEFAULT_PUSH_KNOCKBACK_SPEED
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertikaler Y-Impuls des Pushes.
|
||||||
|
* Quelle: `extras["push_knockback_y"]`.
|
||||||
|
*/
|
||||||
|
private val pushKnockbackY: Double
|
||||||
|
get() = override().getDouble( "push_knockback_y" ) ?: DEFAULT_PUSH_KNOCKBACK_Y
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schaden der Vibranium-Fists pro Treffer in HP (3,25 Herzen Standard).
|
||||||
|
* Quelle: `extras["fist_mode_damage"]`.
|
||||||
|
*/
|
||||||
|
private val fistModeDamage: Double
|
||||||
|
get() = override().getDouble( "fist_mode_damage" ) ?: DEFAULT_FIST_MODE_DAMAGE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ticks Verzögerung vor dem Abschuss des Push-Projektils.
|
||||||
|
* Quelle: `extras["projectile_delay_ticks"]`.
|
||||||
|
*/
|
||||||
|
private val projectileDelayTicks: Long
|
||||||
|
get() = override().getLong( "projectile_delay_ticks" ) ?: DEFAULT_PROJECTILE_DELAY_TICKS
|
||||||
|
|
||||||
|
// ── Gecachte Instanzen ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private val aggressiveActive = AggressiveActive()
|
||||||
|
private val defensiveActive = DefensiveActive()
|
||||||
|
private val aggressivePassive = AggressivePassive()
|
||||||
|
private val defensivePassive = DefensivePassive()
|
||||||
|
|
||||||
|
override fun getActiveAbility(
|
||||||
|
playstyle: Playstyle
|
||||||
|
) = when( playstyle )
|
||||||
{
|
{
|
||||||
Playstyle.AGGRESSIVE -> aggressiveActive
|
Playstyle.AGGRESSIVE -> aggressiveActive
|
||||||
Playstyle.DEFENSIVE -> defensiveActive
|
Playstyle.DEFENSIVE -> defensiveActive
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPassiveAbility(playstyle: Playstyle) = when (playstyle)
|
override fun getPassiveAbility(
|
||||||
|
playstyle: Playstyle
|
||||||
|
) = when( playstyle )
|
||||||
{
|
{
|
||||||
Playstyle.AGGRESSIVE -> aggressivePassive
|
Playstyle.AGGRESSIVE -> aggressivePassive
|
||||||
Playstyle.DEFENSIVE -> defensivePassive
|
Playstyle.DEFENSIVE -> defensivePassive
|
||||||
}
|
}
|
||||||
|
|
||||||
override val cachedItems = ConcurrentHashMap<UUID, List<ItemStack>>()
|
override val cachedItems = ConcurrentHashMap<UUID, List<ItemStack>>()
|
||||||
|
|
||||||
override fun giveItems(player: Player, playstyle: Playstyle) {
|
override fun giveItems(
|
||||||
if (playstyle != Playstyle.AGGRESSIVE) return
|
player: Player,
|
||||||
val item = ItemBuilder(Material.BLACK_DYE)
|
playstyle: Playstyle
|
||||||
.name(aggressiveActive.name)
|
) {
|
||||||
.lore(listOf(aggressiveActive.description))
|
if ( playstyle != Playstyle.AGGRESSIVE ) return
|
||||||
|
val item = ItemBuilder( Material.BLACK_DYE )
|
||||||
|
.name( aggressiveActive.name )
|
||||||
|
.lore(listOf( aggressiveActive.description ))
|
||||||
.build()
|
.build()
|
||||||
cachedItems[player.uniqueId] = listOf(item)
|
cachedItems[ player.uniqueId ] = listOf( item )
|
||||||
player.inventory.addItem(item)
|
player.inventory.addItem( item )
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRemove(
|
override fun onRemove(
|
||||||
@@ -118,21 +201,24 @@ class BlackPantherKit : Kit()
|
|||||||
cachedItems.remove( player.uniqueId )?.forEach { player.inventory.remove( it ) }
|
cachedItems.remove( player.uniqueId )?.forEach { player.inventory.remove( it ) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// AGGRESSIVE active – Push + activate Fist Mode
|
// AGGRESSIVE active – Push + activate Fist Mode
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
private inner class AggressiveActive : ActiveAbility(Playstyle.AGGRESSIVE) {
|
private inner class AggressiveActive : ActiveAbility( Playstyle.AGGRESSIVE )
|
||||||
|
{
|
||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
override val kitId: String get() = "blackpanther"
|
override val kitId: String
|
||||||
override val hardcodedHitsRequired: Int get() = 15
|
get() = "blackpanther"
|
||||||
|
override val hardcodedHitsRequired: Int
|
||||||
|
get() = 15
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blackpanther.items.push.name")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.blackpanther.items.push.name" )
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blackpanther.items.push.description")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.blackpanther.items.push.description" )
|
||||||
override val triggerMaterial = Material.BLACK_DYE
|
override val triggerMaterial = Material.BLACK_DYE
|
||||||
|
|
||||||
override fun execute(
|
override fun execute(
|
||||||
@@ -144,148 +230,171 @@ class BlackPantherKit : Kit()
|
|||||||
plugin.languageManager.getRawMessage( player, "kits.height_restriction" )
|
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 enemies = player.world
|
val enemies = player.world
|
||||||
.getNearbyEntities(player.location, PUSH_RADIUS, PUSH_RADIUS, PUSH_RADIUS)
|
.getNearbyEntities( player.location, capturedPushRadius, capturedPushRadius, capturedPushRadius )
|
||||||
.filterIsInstance<Player>()
|
.filterIsInstance<Player>()
|
||||||
.filter { it != player && plugin.gameManager.alivePlayers.contains(it.uniqueId) }
|
.filter { it != player && plugin.gameManager.alivePlayers.contains( it.uniqueId ) }
|
||||||
|
|
||||||
if (enemies.isEmpty())
|
if ( enemies.isEmpty() )
|
||||||
return AbilityResult.ConditionNotMet("No enemies within ${PUSH_RADIUS.toInt()} blocks!")
|
return AbilityResult.ConditionNotMet( "No enemies within ${capturedPushRadius.toInt()} blocks!" )
|
||||||
|
|
||||||
val pushKey = (plugin.kitManager.getSelectedKit(player) as? BlackPantherKit)
|
val pushKey = NamespacedKey( plugin, PUSH_PROJECTILE_KEY )
|
||||||
?.let { NamespacedKey(plugin, PUSH_PROJECTILE_KEY) }
|
|
||||||
|
|
||||||
enemies.forEach { enemy ->
|
enemies.forEach { enemy ->
|
||||||
// ── Knockback ──────────────────────────────────────────────────
|
|
||||||
val knockDir = enemy.location.toVector()
|
val knockDir = enemy.location.toVector()
|
||||||
.subtract(player.location.toVector())
|
.subtract( player.location.toVector() )
|
||||||
.normalize()
|
.normalize()
|
||||||
.multiply(2.0)
|
.multiply( capturedKnockbackSpeed )
|
||||||
.setY(0.45)
|
.setY( capturedKnockbackY )
|
||||||
|
|
||||||
enemy.velocity = knockDir
|
enemy.velocity = knockDir
|
||||||
enemy.world.spawnParticle(Particle.CRIT,
|
enemy.world.spawnParticle(
|
||||||
enemy.location.clone().add(0.0, 1.0, 0.0), 10, 0.3, 0.3, 0.3, 0.0)
|
Particle.CRIT,
|
||||||
|
enemy.location.clone().add( 0.0, 1.0, 0.0 ),
|
||||||
|
10, 0.3, 0.3, 0.3, 0.0
|
||||||
|
)
|
||||||
|
|
||||||
// ── Trailing push-projectile (deals 4 HP on hit) ──────────────
|
Bukkit.getScheduler().runTaskLater( plugin, { ->
|
||||||
if (pushKey != null) {
|
if ( !player.isOnline ) return@runTaskLater
|
||||||
Bukkit.getScheduler().runTaskLater(plugin, { ->
|
val snowball = player.world.spawn( player.eyeLocation, Snowball::class.java )
|
||||||
if (!player.isOnline) return@runTaskLater
|
snowball.shooter = player
|
||||||
val snowball = player.world.spawn(
|
val travelDir = enemy.location.toVector()
|
||||||
player.eyeLocation, Snowball::class.java
|
.subtract( player.eyeLocation.toVector() )
|
||||||
)
|
.normalize()
|
||||||
snowball.shooter = player
|
.multiply( 1.8 )
|
||||||
val travelDir = enemy.location.toVector()
|
snowball.velocity = travelDir
|
||||||
.subtract(player.eyeLocation.toVector())
|
snowball.persistentDataContainer.set( pushKey, PersistentDataType.BYTE, 1 )
|
||||||
.normalize()
|
}, capturedProjectileDelay )
|
||||||
.multiply(1.8)
|
|
||||||
snowball.velocity = travelDir
|
|
||||||
snowball.persistentDataContainer.set(pushKey, PersistentDataType.BYTE, 1)
|
|
||||||
}, 5L)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Activate Fist Mode ─────────────────────────────────────────────
|
fistModeExpiry[ player.uniqueId ] = System.currentTimeMillis() + capturedFistModeDurationMs
|
||||||
fistModeExpiry[player.uniqueId] = System.currentTimeMillis() + FIST_MODE_MS
|
player.sendActionBar( player.trans( "kits.blackpanther.messages.fist_mode_active" ) )
|
||||||
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)
|
player.world.playSound( player.location, Sound.ENTITY_PLAYER_ATTACK_SWEEP, 0.8f, 0.7f )
|
||||||
|
|
||||||
return AbilityResult.Success
|
return AbilityResult.Success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// DEFENSIVE active – no active ability
|
// DEFENSIVE active – no active ability
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
private class DefensiveActive : ActiveAbility(Playstyle.DEFENSIVE) {
|
private class DefensiveActive : ActiveAbility( Playstyle.DEFENSIVE )
|
||||||
|
{
|
||||||
override val kitId: String = "blackpanther"
|
override val kitId: String = "blackpanther"
|
||||||
override val name = "None"
|
override val name = "None"
|
||||||
override val description = "None"
|
override val description = "None"
|
||||||
override val hardcodedHitsRequired: Int = 0
|
override val hardcodedHitsRequired: Int = 0
|
||||||
override val triggerMaterial = Material.BARRIER
|
override val triggerMaterial = Material.BARRIER
|
||||||
override fun execute(player: Player) = AbilityResult.Success
|
override fun execute(
|
||||||
|
player: Player
|
||||||
|
) = AbilityResult.Success
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// AGGRESSIVE passive – Vibranium Fists (6.5 dmg bare-hand during Fist Mode)
|
// AGGRESSIVE passive – Vibranium Fists (bare-hand during Fist Mode)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
private inner class AggressivePassive : PassiveAbility(Playstyle.AGGRESSIVE) {
|
private inner class AggressivePassive : PassiveAbility( Playstyle.AGGRESSIVE )
|
||||||
|
{
|
||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blackpanther.passive.aggressive.name")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.blackpanther.passive.aggressive.name" )
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blackpanther.passive.aggressive.description")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.blackpanther.passive.aggressive.description" )
|
||||||
|
|
||||||
override fun onHitEnemy(attacker: Player, victim: Player, event: EntityDamageByEntityEvent) {
|
override fun onHitEnemy(
|
||||||
val expiry = fistModeExpiry[attacker.uniqueId] ?: return
|
attacker: Player,
|
||||||
if (System.currentTimeMillis() > expiry) {
|
victim: Player,
|
||||||
fistModeExpiry.remove(attacker.uniqueId)
|
event: EntityDamageByEntityEvent
|
||||||
|
) {
|
||||||
|
val expiry = fistModeExpiry[ attacker.uniqueId ] ?: return
|
||||||
|
if ( System.currentTimeMillis() > expiry )
|
||||||
|
{
|
||||||
|
fistModeExpiry.remove( attacker.uniqueId )
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attacker.inventory.itemInMainHand.type != Material.AIR) return
|
if ( attacker.inventory.itemInMainHand.type != Material.AIR ) return
|
||||||
|
|
||||||
event.damage = 6.5 // 3.25 hearts
|
val capturedFistDamage = fistModeDamage
|
||||||
victim.world.spawnParticle(Particle.CRIT,
|
event.damage = capturedFistDamage
|
||||||
victim.location.clone().add(0.0, 1.0, 0.0), 8, 0.3, 0.3, 0.3, 0.0)
|
victim.world.spawnParticle(
|
||||||
attacker.playSound(attacker.location, Sound.ENTITY_PLAYER_ATTACK_CRIT, 1f, 0.9f)
|
Particle.CRIT,
|
||||||
|
victim.location.clone().add( 0.0, 1.0, 0.0 ),
|
||||||
|
8, 0.3, 0.3, 0.3, 0.0
|
||||||
|
)
|
||||||
|
attacker.playSound( attacker.location, Sound.ENTITY_PLAYER_ATTACK_CRIT, 1f, 0.9f )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// DEFENSIVE passive – Wakanda Forever! (fall-pounce → AOE + crater)
|
// DEFENSIVE passive – Wakanda Forever! (fall-pounce → AOE + crater)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
private inner class DefensivePassive : PassiveAbility(Playstyle.DEFENSIVE) {
|
private inner class DefensivePassive : PassiveAbility( Playstyle.DEFENSIVE )
|
||||||
|
{
|
||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blackpanther.passive.defensive.name")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.blackpanther.passive.defensive.name" )
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blackpanther.passive.defensive.description")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.blackpanther.passive.defensive.description" )
|
||||||
|
|
||||||
override fun onMove(
|
override fun onMove(
|
||||||
player: Player,
|
player: Player,
|
||||||
event: PlayerMoveEvent
|
event: PlayerMoveEvent
|
||||||
) {
|
) {
|
||||||
if ( event.to.y >= event.from.y ) return
|
if ( event.to.y >= event.from.y ) return
|
||||||
if ( player.fallDistance < POUNCE_MIN_FALL ) return
|
|
||||||
|
val capturedPounceMinFall = pounceMinFall
|
||||||
|
if ( player.fallDistance < capturedPounceMinFall ) return
|
||||||
|
|
||||||
val blockBelow = event.to.clone().subtract( 0.0, 0.1, 0.0 ).block
|
val blockBelow = event.to.clone().subtract( 0.0, 0.1, 0.0 ).block
|
||||||
if ( !blockBelow.type.isSolid ) return
|
if ( !blockBelow.type.isSolid ) return
|
||||||
|
|
||||||
val impactLoc = event.to.clone()
|
val impactLoc = event.to.clone()
|
||||||
|
|
||||||
|
// Werte zum Aktivierungszeitpunkt snapshotten
|
||||||
|
val capturedPounceRadius = pounceRadius
|
||||||
|
val capturedPounceDamage = pounceDamage
|
||||||
|
|
||||||
val splashTargets = impactLoc.world
|
val splashTargets = impactLoc.world
|
||||||
.getNearbyEntities( impactLoc, POUNCE_RADIUS, POUNCE_RADIUS, POUNCE_RADIUS )
|
.getNearbyEntities( impactLoc, capturedPounceRadius, capturedPounceRadius, capturedPounceRadius )
|
||||||
.filterIsInstance<Player>()
|
.filterIsInstance<Player>()
|
||||||
.filter { it != player && plugin.gameManager.alivePlayers.contains( it.uniqueId ) }
|
.filter { it != player && plugin.gameManager.alivePlayers.contains( it.uniqueId ) }
|
||||||
|
|
||||||
splashTargets.forEach { it.damage( POUNCE_DAMAGE, player ) }
|
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.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)
|
impactLoc.world.spawnParticle( Particle.LARGE_SMOKE, impactLoc, 20, 1.0, 0.5, 1.0, 0.05 )
|
||||||
impactLoc.world.playSound(impactLoc, Sound.ENTITY_GENERIC_EXPLODE, 1f, 0.7f)
|
impactLoc.world.playSound( impactLoc, Sound.ENTITY_GENERIC_EXPLODE, 1f, 0.7f )
|
||||||
impactLoc.world.playSound(impactLoc, Sound.ENTITY_IRON_GOLEM_HURT, 1f, 0.5f)
|
impactLoc.world.playSound( impactLoc, Sound.ENTITY_IRON_GOLEM_HURT, 1f, 0.5f )
|
||||||
|
|
||||||
Bukkit.getScheduler().runTaskLater(plugin, Runnable {
|
Bukkit.getScheduler().runTaskLater( plugin, Runnable {
|
||||||
WorldEditUtils.createCylinder(
|
WorldEditUtils.createCylinder(
|
||||||
impactLoc.world, impactLoc.clone().subtract(0.0, 1.0, 0.0),
|
impactLoc.world, impactLoc.clone().subtract( 0.0, 1.0, 0.0 ),
|
||||||
3, true, 2, Material.AIR
|
3, true, 2, Material.AIR
|
||||||
)
|
)
|
||||||
}, 2L)
|
}, 2L )
|
||||||
|
|
||||||
player.sendActionBar(player.trans("kits.blackpanther.messages.wakanda_impact",
|
player.sendActionBar(
|
||||||
mapOf("count" to splashTargets.size.toString())))
|
player.trans( "kits.blackpanther.messages.wakanda_impact",
|
||||||
|
mapOf( "count" to splashTargets.size.toString() ) )
|
||||||
|
)
|
||||||
|
|
||||||
// Suppress fall damage for this landing
|
|
||||||
noFallDamagePlayers.add( player.uniqueId )
|
noFallDamagePlayers.add( player.uniqueId )
|
||||||
player.fallDistance = 0f
|
player.fallDistance = 0f
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ 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.ability.ActiveAbility
|
import club.mcscrims.speedhg.kit.ability.ActiveAbility
|
||||||
import club.mcscrims.speedhg.kit.ability.PassiveAbility
|
import club.mcscrims.speedhg.kit.ability.PassiveAbility
|
||||||
import club.mcscrims.speedhg.kit.listener.KitEventDispatcher
|
|
||||||
import club.mcscrims.speedhg.kit.listener.KitEventDispatcher.Companion.MAX_KNOCKBACK_HEIGHT_Y
|
import club.mcscrims.speedhg.kit.listener.KitEventDispatcher.Companion.MAX_KNOCKBACK_HEIGHT_Y
|
||||||
import club.mcscrims.speedhg.util.ItemBuilder
|
import club.mcscrims.speedhg.util.ItemBuilder
|
||||||
import club.mcscrims.speedhg.util.trans
|
import club.mcscrims.speedhg.util.trans
|
||||||
@@ -35,26 +34,28 @@ import kotlin.math.sin
|
|||||||
* | Playstyle | Aktive Fähigkeit |
|
* | Playstyle | Aktive Fähigkeit |
|
||||||
* |-------------|---------------------------------------------------------------|
|
* |-------------|---------------------------------------------------------------|
|
||||||
* | AGGRESSIVE | **Hook** – zieht ersten Feind in der Schusslinie heran |
|
* | AGGRESSIVE | **Hook** – zieht ersten Feind in der Schusslinie heran |
|
||||||
* | DEFENSIVE | **Stun** – friert alle nahen Feinde für 3 s ein |
|
* | DEFENSIVE | **Stun** – friert alle nahen Feinde für [stunDurationTicks] Ticks ein |
|
||||||
* | Beide | **Ult** – expandierende Schockwelle + AoE-Schaden |
|
* | Beide | **Ult** – expandierende Schockwelle + AoE-Schaden |
|
||||||
*
|
*
|
||||||
* ### Hook – synchroner Raycast
|
* ## Konfiguration (via `SPEEDHG_CUSTOM_SETTINGS`)
|
||||||
* 0,4-Block-Schritte von `eyeLocation` entlang `eyeLocation.direction`.
|
|
||||||
* Erster Feind getroffen → Velocity-Pull Richtung Caster. Alle Partikel werden
|
|
||||||
* synchron im selben Tick gezeichnet.
|
|
||||||
*
|
*
|
||||||
* ### Stun – Freeze-Mechanismus
|
* Alle Werte werden über die `extras`-Map konfiguriert und fallen auf die
|
||||||
* Slowness 127 + Mining Fatigue 127 für [STUN_DURATION_TICKS] Ticks.
|
* Defaults im [companion object] zurück, wenn kein Wert gesetzt ist.
|
||||||
* Zusätzlich setzt ein BukkitTask die Velocity aller gestunnten Spieler auf 0.
|
|
||||||
*
|
*
|
||||||
* ### Ult – passive onInteract als Auslöser
|
* | JSON-Schlüssel | Typ | Default | Beschreibung |
|
||||||
* Das Ult-Item (BLAZE_POWDER) besitzt einen PDC-Tag ([ultItemKey]).
|
* |-------------------------|--------|-------------|----------------------------------------------|
|
||||||
* `KitEventDispatcher.onInteract` ruft **zuerst** `passive.onInteract` auf,
|
* | `hook_range` | Double | `10.0` | Maximale Reichweite des Hooks (Blöcke) |
|
||||||
* dann erst den triggerMaterial-Check. [UltPassive.onInteract] fängt das
|
* | `hook_pull_strength` | Double | `2.7` | Velocity-Multiplikator beim Pull |
|
||||||
* BLAZE_POWDER-Rechtsklick-Event ab und cancelt es, bevor der Dispatcher
|
* | `stun_radius` | Double | `5.0` | AoE-Radius des Stuns (Blöcke) |
|
||||||
* etwas unternimmt → kein Dispatcher-Umbau notwendig.
|
* | `stun_duration_ticks` | Long | `60` | Dauer des Stuns in Ticks (3 Sekunden) |
|
||||||
|
* | `ult_radius` | Double | `6.0` | Radius der Ult-Schockwelle (Blöcke) |
|
||||||
|
* | `ult_damage` | Double | `5.0` | Schaden der Ult pro Treffer (HP) |
|
||||||
|
* | `ult_cooldown_ms` | Long | `30000` | Cooldown der Ult zwischen Uses (ms) |
|
||||||
|
* | `hook_step_size` | Double | `0.4` | Raycast-Schrittgröße für den Hook (Blöcke) |
|
||||||
|
* | `hook_hit_radius` | Double | `0.6` | Kollisionsradius des Hooks (Blöcke) |
|
||||||
*/
|
*/
|
||||||
class BlitzcrankKit : Kit() {
|
class BlitzcrankKit : Kit()
|
||||||
|
{
|
||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
@@ -76,15 +77,84 @@ class BlitzcrankKit : Kit() {
|
|||||||
private val ultCooldowns: MutableMap<UUID, Long> = ConcurrentHashMap()
|
private val ultCooldowns: MutableMap<UUID, Long> = ConcurrentHashMap()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val HOOK_RANGE = 10.0 // Blöcke
|
const val DEFAULT_HOOK_RANGE = 10.0
|
||||||
const val HOOK_PULL_STRENGTH = 2.7 // Velocity-Multiplikator
|
const val DEFAULT_HOOK_PULL_STRENGTH = 2.7
|
||||||
const val STUN_RADIUS = 5.0 // Blöcke
|
const val DEFAULT_STUN_RADIUS = 5.0
|
||||||
const val STUN_DURATION_TICKS = 60 // 3 Sekunden
|
const val DEFAULT_STUN_DURATION_TICKS = 60L
|
||||||
const val ULT_RADIUS = 6.0 // Blöcke
|
const val DEFAULT_ULT_RADIUS = 6.0
|
||||||
const val ULT_DAMAGE = 5.0 // 2,5 Herzen
|
const val DEFAULT_ULT_DAMAGE = 5.0
|
||||||
const val ULT_COOLDOWN_MS = 30_000L
|
const val DEFAULT_ULT_COOLDOWN_MS = 30_000L
|
||||||
|
const val DEFAULT_HOOK_STEP_SIZE = 0.4
|
||||||
|
const val DEFAULT_HOOK_HIT_RADIUS = 0.6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Live config accessors ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximale Reichweite des Hooks in Blöcken.
|
||||||
|
* JSON-Schlüssel: `hook_range`
|
||||||
|
*/
|
||||||
|
private val hookRange: Double
|
||||||
|
get() = override().getDouble( "hook_range" ) ?: DEFAULT_HOOK_RANGE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Velocity-Multiplikator des Hook-Pulls.
|
||||||
|
* JSON-Schlüssel: `hook_pull_strength`
|
||||||
|
*/
|
||||||
|
private val hookPullStrength: Double
|
||||||
|
get() = override().getDouble( "hook_pull_strength" ) ?: DEFAULT_HOOK_PULL_STRENGTH
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AoE-Radius des Stuns in Blöcken.
|
||||||
|
* JSON-Schlüssel: `stun_radius`
|
||||||
|
*/
|
||||||
|
private val stunRadius: Double
|
||||||
|
get() = override().getDouble( "stun_radius" ) ?: DEFAULT_STUN_RADIUS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dauer des Stuns in Ticks.
|
||||||
|
* JSON-Schlüssel: `stun_duration_ticks`
|
||||||
|
*/
|
||||||
|
private val stunDurationTicks: Long
|
||||||
|
get() = override().getLong( "stun_duration_ticks" ) ?: DEFAULT_STUN_DURATION_TICKS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Radius der Ult-Schockwelle in Blöcken.
|
||||||
|
* JSON-Schlüssel: `ult_radius`
|
||||||
|
*/
|
||||||
|
private val ultRadius: Double
|
||||||
|
get() = override().getDouble( "ult_radius" ) ?: DEFAULT_ULT_RADIUS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schaden der Ult pro getroffenen Spieler in HP.
|
||||||
|
* JSON-Schlüssel: `ult_damage`
|
||||||
|
*/
|
||||||
|
private val ultDamage: Double
|
||||||
|
get() = override().getDouble( "ult_damage" ) ?: DEFAULT_ULT_DAMAGE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cooldown der Ult in Millisekunden.
|
||||||
|
* JSON-Schlüssel: `ult_cooldown_ms`
|
||||||
|
*/
|
||||||
|
private val ultCooldownMs: Long
|
||||||
|
get() = override().getLong( "ult_cooldown_ms" ) ?: DEFAULT_ULT_COOLDOWN_MS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raycast-Schrittgröße des Hooks in Blöcken.
|
||||||
|
* JSON-Schlüssel: `hook_step_size`
|
||||||
|
*/
|
||||||
|
private val hookStepSize: Double
|
||||||
|
get() = override().getDouble( "hook_step_size" ) ?: DEFAULT_HOOK_STEP_SIZE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kollisionsradius des Hooks in Blöcken.
|
||||||
|
* JSON-Schlüssel: `hook_hit_radius`
|
||||||
|
*/
|
||||||
|
private val hookHitRadius: Double
|
||||||
|
get() = override().getDouble( "hook_hit_radius" ) ?: DEFAULT_HOOK_HIT_RADIUS
|
||||||
|
|
||||||
|
// ── Gecachte Instanzen ────────────────────────────────────────────────────
|
||||||
|
|
||||||
private val aggressiveActive = HookActive()
|
private val aggressiveActive = HookActive()
|
||||||
private val defensiveActive = StunActive()
|
private val defensiveActive = StunActive()
|
||||||
private val aggressivePassive = UltPassive( Playstyle.AGGRESSIVE )
|
private val aggressivePassive = UltPassive( Playstyle.AGGRESSIVE )
|
||||||
@@ -112,7 +182,8 @@ class BlitzcrankKit : Kit() {
|
|||||||
player: Player,
|
player: Player,
|
||||||
playstyle: Playstyle
|
playstyle: Playstyle
|
||||||
) {
|
) {
|
||||||
val mainItem = when (playstyle) {
|
val mainItem = when( playstyle )
|
||||||
|
{
|
||||||
Playstyle.AGGRESSIVE -> ItemBuilder( Material.FISHING_ROD )
|
Playstyle.AGGRESSIVE -> ItemBuilder( Material.FISHING_ROD )
|
||||||
.name( aggressiveActive.name )
|
.name( aggressiveActive.name )
|
||||||
.lore(listOf( aggressiveActive.description ))
|
.lore(listOf( aggressiveActive.description ))
|
||||||
@@ -150,28 +221,33 @@ class BlitzcrankKit : Kit() {
|
|||||||
) {
|
) {
|
||||||
if ( caster.location.y > MAX_KNOCKBACK_HEIGHT_Y )
|
if ( caster.location.y > MAX_KNOCKBACK_HEIGHT_Y )
|
||||||
{
|
{
|
||||||
caster.sendActionBar(caster.trans( "kits.height_restriction" ))
|
caster.sendActionBar( caster.trans( "kits.height_restriction" ) )
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
val lastUlt = ultCooldowns[ caster.uniqueId ] ?: 0L
|
val lastUlt = ultCooldowns[ caster.uniqueId ] ?: 0L
|
||||||
|
|
||||||
if ( now - lastUlt < ULT_COOLDOWN_MS )
|
// Werte zum Aktivierungszeitpunkt snapshotten
|
||||||
|
val capturedUltCooldownMs = ultCooldownMs
|
||||||
|
val capturedUltRadius = ultRadius
|
||||||
|
val capturedUltDamage = ultDamage
|
||||||
|
|
||||||
|
if ( now - lastUlt < capturedUltCooldownMs )
|
||||||
{
|
{
|
||||||
val secLeft = ( ULT_COOLDOWN_MS - ( now - lastUlt )) / 1000
|
val secLeft = ( capturedUltCooldownMs - ( now - lastUlt ) ) / 1000
|
||||||
caster.sendActionBar(caster.trans( "kits.blitzcrank.messages.ult_cooldown", "time" to secLeft.toString() ))
|
caster.sendActionBar( caster.trans( "kits.blitzcrank.messages.ult_cooldown", "time" to secLeft.toString() ) )
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val targets = caster.world
|
val targets = caster.world
|
||||||
.getNearbyEntities( caster.location, ULT_RADIUS, ULT_RADIUS, ULT_RADIUS )
|
.getNearbyEntities( caster.location, capturedUltRadius, capturedUltRadius, capturedUltRadius )
|
||||||
.filterIsInstance<Player>()
|
.filterIsInstance<Player>()
|
||||||
.filter { it != caster && plugin.gameManager.alivePlayers.contains( it.uniqueId ) }
|
.filter { it != caster && plugin.gameManager.alivePlayers.contains( it.uniqueId ) }
|
||||||
|
|
||||||
if ( targets.isEmpty() )
|
if ( targets.isEmpty() )
|
||||||
{
|
{
|
||||||
caster.sendActionBar(caster.trans( "kits.blitzcrank.messages.ult_no_targets" ))
|
caster.sendActionBar( caster.trans( "kits.blitzcrank.messages.ult_no_targets" ) )
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,13 +256,13 @@ class BlitzcrankKit : Kit() {
|
|||||||
|
|
||||||
override fun run()
|
override fun run()
|
||||||
{
|
{
|
||||||
if ( r > ULT_RADIUS + 1.0 ) { cancel(); return }
|
if ( r > capturedUltRadius + 1.0 ) { cancel(); return }
|
||||||
val steps = ( 2 * Math.PI * r * 5 ).toInt().coerceAtLeast( 8 )
|
val steps = ( 2 * Math.PI * r * 5 ).toInt().coerceAtLeast( 8 )
|
||||||
repeat( steps ) { i ->
|
repeat( steps ) { i ->
|
||||||
val angle = 2.0 * Math.PI * i / steps
|
val angle = 2.0 * Math.PI * i / steps
|
||||||
caster.world.spawnParticle(
|
caster.world.spawnParticle(
|
||||||
Particle.ELECTRIC_SPARK,
|
Particle.ELECTRIC_SPARK,
|
||||||
caster.location.clone().add(cos( angle ) * r, 1.0, sin( angle ) * r ),
|
caster.location.clone().add( cos( angle ) * r, 1.0, sin( angle ) * r ),
|
||||||
1, 0.0, 0.0, 0.0, 0.0
|
1, 0.0, 0.0, 0.0, 0.0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -195,7 +271,7 @@ class BlitzcrankKit : Kit() {
|
|||||||
}.runTaskTimer( plugin, 0L, 1L )
|
}.runTaskTimer( plugin, 0L, 1L )
|
||||||
|
|
||||||
targets.forEach { target ->
|
targets.forEach { target ->
|
||||||
target.damage( ULT_DAMAGE, caster )
|
target.damage( capturedUltDamage, caster )
|
||||||
target.velocity = target.location.toVector()
|
target.velocity = target.location.toVector()
|
||||||
.subtract( caster.location.toVector() )
|
.subtract( caster.location.toVector() )
|
||||||
.normalize()
|
.normalize()
|
||||||
@@ -203,9 +279,9 @@ class BlitzcrankKit : Kit() {
|
|||||||
.setY( 0.5 )
|
.setY( 0.5 )
|
||||||
}
|
}
|
||||||
|
|
||||||
caster.world.playSound( caster.location, Sound.ENTITY_GENERIC_EXPLODE, 1f, 1.5f )
|
caster.world.playSound( caster.location, Sound.ENTITY_GENERIC_EXPLODE, 1f, 1.5f )
|
||||||
caster.world.playSound( caster.location, Sound.ENTITY_LIGHTNING_BOLT_THUNDER, 0.8f, 1.8f )
|
caster.world.playSound( caster.location, Sound.ENTITY_LIGHTNING_BOLT_THUNDER, 0.8f, 1.8f )
|
||||||
caster.sendActionBar(caster.trans( "kits.blitzcrank.messages.ult_fired", "count" to targets.size.toString() ))
|
caster.sendActionBar( caster.trans( "kits.blitzcrank.messages.ult_fired", "count" to targets.size.toString() ) )
|
||||||
|
|
||||||
ultCooldowns[ caster.uniqueId ] = now
|
ultCooldowns[ caster.uniqueId ] = now
|
||||||
}
|
}
|
||||||
@@ -214,19 +290,22 @@ class BlitzcrankKit : Kit() {
|
|||||||
// AGGRESSIVE active – Hook (synchroner Raycast)
|
// AGGRESSIVE active – Hook (synchroner Raycast)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
private inner class HookActive : ActiveAbility(Playstyle.AGGRESSIVE) {
|
private inner class HookActive : ActiveAbility( Playstyle.AGGRESSIVE )
|
||||||
|
{
|
||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
override val kitId = "blitzcrank"
|
override val kitId = "blitzcrank"
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blitzcrank.items.hook.name")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.blitzcrank.items.hook.name" )
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blitzcrank.items.hook.description")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.blitzcrank.items.hook.description" )
|
||||||
override val hardcodedHitsRequired = 15
|
override val hardcodedHitsRequired = 15
|
||||||
override val triggerMaterial = Material.FISHING_ROD
|
override val triggerMaterial = Material.FISHING_ROD
|
||||||
|
|
||||||
override fun execute(player: Player): AbilityResult
|
override fun execute(
|
||||||
|
player: Player
|
||||||
|
): AbilityResult
|
||||||
{
|
{
|
||||||
if ( player.location.y > MAX_KNOCKBACK_HEIGHT_Y )
|
if ( player.location.y > MAX_KNOCKBACK_HEIGHT_Y )
|
||||||
return AbilityResult.ConditionNotMet(
|
return AbilityResult.ConditionNotMet(
|
||||||
@@ -236,51 +315,57 @@ class BlitzcrankKit : Kit() {
|
|||||||
val eyeLoc = player.eyeLocation
|
val eyeLoc = player.eyeLocation
|
||||||
val dir = eyeLoc.direction.normalize()
|
val dir = eyeLoc.direction.normalize()
|
||||||
|
|
||||||
|
// Werte zum Aktivierungszeitpunkt snapshotten
|
||||||
|
val capturedHookRange = hookRange
|
||||||
|
val capturedHookStepSize = hookStepSize
|
||||||
|
val capturedHookHitRadius = hookHitRadius
|
||||||
|
val capturedPullStrength = hookPullStrength
|
||||||
|
|
||||||
var hookTarget: Player? = null
|
var hookTarget: Player? = null
|
||||||
var dist = 0.4
|
var dist = capturedHookStepSize
|
||||||
|
|
||||||
// Synchroner Scan: trivial schnell (max ~25 Iterationen)
|
while ( dist <= capturedHookRange && hookTarget == null )
|
||||||
while (dist <= HOOK_RANGE && hookTarget == null) {
|
{
|
||||||
val point = eyeLoc.clone().add(dir.clone().multiply(dist))
|
val point = eyeLoc.clone().add( dir.clone().multiply( dist ) )
|
||||||
|
|
||||||
// Block im Weg → Hook stoppt hier
|
if ( point.block.type.isSolid ) break
|
||||||
if (point.block.type.isSolid) break
|
|
||||||
|
|
||||||
// Partikel-Trail entlang des Strahls
|
player.world.spawnParticle( Particle.ELECTRIC_SPARK, point, 1, 0.0, 0.0, 0.0, 0.0 )
|
||||||
player.world.spawnParticle(Particle.ELECTRIC_SPARK, point, 1, 0.0, 0.0, 0.0, 0.0)
|
|
||||||
|
|
||||||
hookTarget = point.world
|
hookTarget = point.world
|
||||||
?.getNearbyEntities(point, 0.6, 0.6, 0.6)
|
?.getNearbyEntities( point, capturedHookHitRadius, capturedHookHitRadius, capturedHookHitRadius )
|
||||||
?.filterIsInstance<Player>()
|
?.filterIsInstance<Player>()
|
||||||
?.filter { it != player && plugin.gameManager.alivePlayers.contains(it.uniqueId) }
|
?.filter { it != player && plugin.gameManager.alivePlayers.contains( it.uniqueId ) }
|
||||||
?.minByOrNull { it.location.distanceSquared(point) }
|
?.minByOrNull { it.location.distanceSquared( point ) }
|
||||||
|
|
||||||
dist += 0.4
|
dist += capturedHookStepSize
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hookTarget == null) {
|
if ( hookTarget == null )
|
||||||
// Kein Treffer – Funken am Strahlende
|
{
|
||||||
val endPt = eyeLoc.clone().add(dir.multiply(dist.coerceAtMost(HOOK_RANGE)))
|
val endPt = eyeLoc.clone().add( dir.multiply( dist.coerceAtMost( capturedHookRange ) ) )
|
||||||
player.world.spawnParticle(Particle.ELECTRIC_SPARK, endPt, 10, 0.3, 0.3, 0.3, 0.06)
|
player.world.spawnParticle( Particle.ELECTRIC_SPARK, endPt, 10, 0.3, 0.3, 0.3, 0.06 )
|
||||||
return AbilityResult.ConditionNotMet("Kein Ziel in Reichweite!")
|
return AbilityResult.ConditionNotMet( "Kein Ziel in Reichweite!" )
|
||||||
}
|
}
|
||||||
|
|
||||||
val target = hookTarget
|
val target = hookTarget
|
||||||
|
|
||||||
// Pull: Velocity in Richtung Caster
|
|
||||||
target.velocity = player.location.toVector()
|
target.velocity = player.location.toVector()
|
||||||
.subtract(target.location.toVector())
|
.subtract( target.location.toVector() )
|
||||||
.normalize()
|
.normalize()
|
||||||
.multiply(HOOK_PULL_STRENGTH)
|
.multiply( capturedPullStrength )
|
||||||
.setY(0.65)
|
.setY( 0.65 )
|
||||||
|
|
||||||
target.world.spawnParticle(Particle.ELECTRIC_SPARK,
|
target.world.spawnParticle(
|
||||||
target.location.clone().add(0.0, 1.0, 0.0), 22, 0.4, 0.4, 0.4, 0.14)
|
Particle.ELECTRIC_SPARK,
|
||||||
target.world.playSound(target.location, Sound.ENTITY_IRON_GOLEM_HURT, 0.9f, 1.6f)
|
target.location.clone().add( 0.0, 1.0, 0.0 ),
|
||||||
target.sendActionBar(target.trans("kits.blitzcrank.messages.hooked"))
|
22, 0.4, 0.4, 0.4, 0.14
|
||||||
|
)
|
||||||
|
target.world.playSound( target.location, Sound.ENTITY_IRON_GOLEM_HURT, 0.9f, 1.6f )
|
||||||
|
target.sendActionBar( target.trans( "kits.blitzcrank.messages.hooked" ) )
|
||||||
|
|
||||||
player.playSound(player.location, Sound.ENTITY_FISHING_BOBBER_RETRIEVE, 1f, 0.4f)
|
player.playSound( player.location, Sound.ENTITY_FISHING_BOBBER_RETRIEVE, 1f, 0.4f )
|
||||||
player.sendActionBar(player.trans("kits.blitzcrank.messages.hook_hit"))
|
player.sendActionBar( player.trans( "kits.blitzcrank.messages.hook_hit" ) )
|
||||||
|
|
||||||
return AbilityResult.Success
|
return AbilityResult.Success
|
||||||
}
|
}
|
||||||
@@ -290,15 +375,16 @@ class BlitzcrankKit : Kit() {
|
|||||||
// DEFENSIVE active – Stun (AoE-Freeze)
|
// DEFENSIVE active – Stun (AoE-Freeze)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
private inner class StunActive : ActiveAbility(Playstyle.DEFENSIVE) {
|
private inner class StunActive : ActiveAbility( Playstyle.DEFENSIVE )
|
||||||
|
{
|
||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
override val kitId = "blitzcrank"
|
override val kitId = "blitzcrank"
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blitzcrank.items.stun.name")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.blitzcrank.items.stun.name" )
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blitzcrank.items.stun.description")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.blitzcrank.items.stun.description" )
|
||||||
override val hardcodedHitsRequired = 15
|
override val hardcodedHitsRequired = 15
|
||||||
override val triggerMaterial = Material.PISTON
|
override val triggerMaterial = Material.PISTON
|
||||||
|
|
||||||
@@ -311,48 +397,60 @@ class BlitzcrankKit : Kit() {
|
|||||||
plugin.languageManager.getRawMessage( player, "kits.height_restriction" )
|
plugin.languageManager.getRawMessage( player, "kits.height_restriction" )
|
||||||
)
|
)
|
||||||
|
|
||||||
val targets = player.world
|
// Werte zum Aktivierungszeitpunkt snapshotten
|
||||||
.getNearbyEntities(player.location, STUN_RADIUS, STUN_RADIUS, STUN_RADIUS)
|
val capturedStunRadius = stunRadius
|
||||||
.filterIsInstance<Player>()
|
val capturedStunDurationTicks = stunDurationTicks
|
||||||
.filter { it != player && plugin.gameManager.alivePlayers.contains(it.uniqueId) }
|
|
||||||
|
|
||||||
if (targets.isEmpty())
|
val targets = player.world
|
||||||
return AbilityResult.ConditionNotMet("Keine Feinde in ${STUN_RADIUS.toInt()} Blöcken!")
|
.getNearbyEntities( player.location, capturedStunRadius, capturedStunRadius, capturedStunRadius )
|
||||||
|
.filterIsInstance<Player>()
|
||||||
|
.filter { it != player && plugin.gameManager.alivePlayers.contains( it.uniqueId ) }
|
||||||
|
|
||||||
|
if ( targets.isEmpty() )
|
||||||
|
return AbilityResult.ConditionNotMet( "Keine Feinde in ${capturedStunRadius.toInt()} Blöcken!" )
|
||||||
|
|
||||||
targets.forEach { target ->
|
targets.forEach { target ->
|
||||||
// Potion-Effekte für maximales Einfrieren (Amplifier 127 = sofortiger Stopp)
|
|
||||||
target.addPotionEffect(
|
target.addPotionEffect(
|
||||||
PotionEffect(PotionEffectType.SLOWNESS, STUN_DURATION_TICKS, 127, false, false, true)
|
PotionEffect( PotionEffectType.SLOWNESS, capturedStunDurationTicks.toInt(), 127, false, false, true )
|
||||||
)
|
)
|
||||||
target.addPotionEffect(
|
target.addPotionEffect(
|
||||||
PotionEffect(PotionEffectType.MINING_FATIGUE, STUN_DURATION_TICKS, 127, false, false, false)
|
PotionEffect( PotionEffectType.MINING_FATIGUE, capturedStunDurationTicks.toInt(), 127, false, false, false )
|
||||||
)
|
)
|
||||||
|
|
||||||
// Velocity-Reset-Task: verhindert Springen und Rutschen
|
|
||||||
var stunTick = 0
|
var stunTick = 0
|
||||||
val task = Bukkit.getScheduler().runTaskTimer(plugin, { ->
|
val task = Bukkit.getScheduler().runTaskTimer( plugin, { ->
|
||||||
stunTick++
|
stunTick++
|
||||||
if (stunTick >= STUN_DURATION_TICKS || !target.isOnline ||
|
if ( stunTick >= capturedStunDurationTicks ||
|
||||||
!plugin.gameManager.alivePlayers.contains(target.uniqueId)) {
|
!target.isOnline ||
|
||||||
stunTasks.remove(target.uniqueId)?.cancel()
|
!plugin.gameManager.alivePlayers.contains( target.uniqueId ) )
|
||||||
|
{
|
||||||
|
stunTasks.remove( target.uniqueId )?.cancel()
|
||||||
return@runTaskTimer
|
return@runTaskTimer
|
||||||
}
|
}
|
||||||
val v = target.velocity
|
val v = target.velocity
|
||||||
target.velocity = v.setX(0.0).setZ(0.0).let { if (it.y > 0.0) it.setY(0.0) else it }
|
target.velocity = v.setX( 0.0 ).setZ( 0.0 ).let { if ( it.y > 0.0 ) it.setY( 0.0 ) else it }
|
||||||
}, 0L, 1L)
|
}, 0L, 1L )
|
||||||
|
|
||||||
stunTasks[target.uniqueId] = task
|
stunTasks[ target.uniqueId ] = task
|
||||||
|
|
||||||
target.world.spawnParticle(Particle.ELECTRIC_SPARK,
|
target.world.spawnParticle(
|
||||||
target.location.clone().add(0.0, 1.5, 0.0), 25, 0.3, 0.5, 0.3, 0.14)
|
Particle.ELECTRIC_SPARK,
|
||||||
target.sendActionBar(target.trans("kits.blitzcrank.messages.stunned"))
|
target.location.clone().add( 0.0, 1.5, 0.0 ),
|
||||||
|
25, 0.3, 0.5, 0.3, 0.14
|
||||||
|
)
|
||||||
|
target.sendActionBar( target.trans( "kits.blitzcrank.messages.stunned" ) )
|
||||||
}
|
}
|
||||||
|
|
||||||
player.world.playSound(player.location, Sound.ENTITY_LIGHTNING_BOLT_IMPACT, 1f, 0.7f)
|
player.world.playSound( player.location, Sound.ENTITY_LIGHTNING_BOLT_IMPACT, 1f, 0.7f )
|
||||||
player.world.spawnParticle(Particle.ELECTRIC_SPARK,
|
player.world.spawnParticle(
|
||||||
player.location.clone().add(0.0, 1.0, 0.0), 35, 2.0, 0.5, 2.0, 0.14)
|
Particle.ELECTRIC_SPARK,
|
||||||
player.sendActionBar(player.trans("kits.blitzcrank.messages.stun_cast",
|
player.location.clone().add( 0.0, 1.0, 0.0 ),
|
||||||
"count" to targets.size.toString()))
|
35, 2.0, 0.5, 2.0, 0.14
|
||||||
|
)
|
||||||
|
player.sendActionBar(
|
||||||
|
player.trans( "kits.blitzcrank.messages.stun_cast",
|
||||||
|
"count" to targets.size.toString() )
|
||||||
|
)
|
||||||
|
|
||||||
return AbilityResult.Success
|
return AbilityResult.Success
|
||||||
}
|
}
|
||||||
@@ -362,28 +460,31 @@ class BlitzcrankKit : Kit() {
|
|||||||
// Shared Ult-Passive – fängt BLAZE_POWDER-Rechtsklick via onInteract ab
|
// Shared Ult-Passive – fängt BLAZE_POWDER-Rechtsklick via onInteract ab
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
inner class UltPassive(playstyle: Playstyle) : PassiveAbility(playstyle) {
|
inner class UltPassive(
|
||||||
|
playstyle: Playstyle
|
||||||
|
) : PassiveAbility( playstyle )
|
||||||
|
{
|
||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blitzcrank.passive.name")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.blitzcrank.passive.name" )
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blitzcrank.passive.description")
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.blitzcrank.passive.description" )
|
||||||
|
|
||||||
/**
|
override fun onInteract(
|
||||||
* Wird vom KitEventDispatcher **vor** dem triggerMaterial-Check aufgerufen.
|
player: Player,
|
||||||
* Prüft PDC-Tag → falls Ult-Item: Event canceln + Ult feuern.
|
event: PlayerInteractEvent
|
||||||
*/
|
) {
|
||||||
override fun onInteract(player: Player, event: PlayerInteractEvent) {
|
if ( !event.action.isRightClick ) return
|
||||||
if (!event.action.isRightClick) return
|
|
||||||
|
|
||||||
val pdc = player.inventory.itemInMainHand.itemMeta
|
val pdc = player.inventory.itemInMainHand.itemMeta
|
||||||
?.persistentDataContainer ?: return
|
?.persistentDataContainer ?: return
|
||||||
if (!pdc.has(ultItemKey, PersistentDataType.BYTE)) return
|
if ( !pdc.has( ultItemKey, PersistentDataType.BYTE ) ) return
|
||||||
|
|
||||||
event.isCancelled = true // Vanilla-Interaktion (Feuer-Charge) unterbinden
|
event.isCancelled = true
|
||||||
fireUlt(player)
|
fireUlt( player )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user