Add SpieloKit gambling kit and UI hooks
Introduce a new kit SpieloKit with aggressive (instant gamble) and defensive (slot-machine GUI) playstyles. Implements outcome resolution (instant death, disaster events, negative/neutral/positive loot), animations, cooldowns, safe-radius checks, and a 3×9 SlotMachineGui with spinning reel animation and spin button. Adds registration of SpieloKit in SpeedHG and integrates SlotMachineGui dispatch into MenuListener (inventory click + close handlers). Includes utility methods for effects, sounds, particles and loot pools.
This commit is contained in:
@@ -224,6 +224,7 @@ class SpeedHG : JavaPlugin() {
|
||||
kitManager.registerKit( NinjaKit() )
|
||||
kitManager.registerKit( PuppetKit() )
|
||||
kitManager.registerKit( RattlesnakeKit() )
|
||||
kitManager.registerKit( SpieloKit() )
|
||||
kitManager.registerKit( TeslaKit() )
|
||||
kitManager.registerKit( TheWorldKit() )
|
||||
kitManager.registerKit( TridentKit() )
|
||||
|
||||
@@ -3,6 +3,7 @@ package club.mcscrims.speedhg.gui.listener
|
||||
import club.mcscrims.speedhg.gui.anvil.AnvilSearchMenu
|
||||
import club.mcscrims.speedhg.gui.anvil.AnvilSearchTracker
|
||||
import club.mcscrims.speedhg.gui.menu.MenuHolder
|
||||
import club.mcscrims.speedhg.kit.impl.SpieloKit
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.EventPriority
|
||||
@@ -56,6 +57,15 @@ class MenuListener : Listener {
|
||||
return
|
||||
}
|
||||
|
||||
// ── Spielo-SlotMachine ────────────────────────────────────────────────
|
||||
val spieloHolder = event.inventory.holder as? SpieloKit.SlotMachineGui
|
||||
if ( spieloHolder != null )
|
||||
{
|
||||
event.isCancelled = true
|
||||
spieloHolder.onClick( event )
|
||||
return
|
||||
}
|
||||
|
||||
// ── Chest-Menü (MenuHolder-Dispatch) ───────────────────────────────────
|
||||
val holder = event.inventory.holder as? MenuHolder ?: return
|
||||
val menu = holder.menu
|
||||
@@ -91,6 +101,14 @@ class MenuListener : Listener {
|
||||
return
|
||||
}
|
||||
|
||||
// ── Spielo-SlotMachine ────────────────────────────────────────────────
|
||||
val spieloHolder = event.inventory.holder as? SpieloKit.SlotMachineGui
|
||||
if ( spieloHolder != null )
|
||||
{
|
||||
spieloHolder.onClose()
|
||||
return
|
||||
}
|
||||
|
||||
// ── Chest-Menü: onClose-Hook aufrufen ─────────────────────────────────
|
||||
val holder = event.inventory.holder as? MenuHolder ?: return
|
||||
holder.menu.onClose(event, player)
|
||||
|
||||
544
src/main/kotlin/club/mcscrims/speedhg/kit/impl/SpieloKit.kt
Normal file
544
src/main/kotlin/club/mcscrims/speedhg/kit/impl/SpieloKit.kt
Normal file
@@ -0,0 +1,544 @@
|
||||
package club.mcscrims.speedhg.kit.impl
|
||||
|
||||
import club.mcscrims.speedhg.SpeedHG
|
||||
import club.mcscrims.speedhg.disaster.impl.EarthquakeDisaster
|
||||
import club.mcscrims.speedhg.disaster.impl.MeteorDisaster
|
||||
import club.mcscrims.speedhg.disaster.impl.ThunderDisaster
|
||||
import club.mcscrims.speedhg.disaster.impl.TornadoDisaster
|
||||
import club.mcscrims.speedhg.kit.Kit
|
||||
import club.mcscrims.speedhg.kit.Playstyle
|
||||
import club.mcscrims.speedhg.kit.ability.AbilityResult
|
||||
import club.mcscrims.speedhg.kit.ability.ActiveAbility
|
||||
import club.mcscrims.speedhg.kit.ability.PassiveAbility
|
||||
import club.mcscrims.speedhg.util.ItemBuilder
|
||||
import club.mcscrims.speedhg.util.trans
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.format.TextDecoration
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.Particle
|
||||
import org.bukkit.Sound
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.inventory.InventoryClickEvent
|
||||
import org.bukkit.inventory.Inventory
|
||||
import org.bukkit.inventory.InventoryHolder
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.potion.PotionEffect
|
||||
import org.bukkit.potion.PotionEffectType
|
||||
import org.bukkit.scheduler.BukkitRunnable
|
||||
import org.bukkit.scheduler.BukkitTask
|
||||
import java.util.Random
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* ## SpieloKit
|
||||
*
|
||||
* | Playstyle | Beschreibung |
|
||||
* |-------------|---------------------------------------------------------------------------------|
|
||||
* | AGGRESSIVE | Gambeln per Knopfdruck – Items, Events oder **Instant Death** möglich |
|
||||
* | DEFENSIVE | Öffnet eine Slot-Maschinen-GUI (nur wenn kein Feind in der Nähe) – sicherer: |
|
||||
* | | keine Dia-Armor, kein Instant-Death-Outcome |
|
||||
*
|
||||
* ### Aggressive – Outcome-Wahrscheinlichkeiten
|
||||
* | 5 % | Instant Death |
|
||||
* | 15 % | Disaster-Event (Meteor, Tornado, ...) |
|
||||
* | 10 % | Negative Effekte (Slowness, Nausea, ...) |
|
||||
* | 20 % | Neutrale Items |
|
||||
* | 50 % | Positive Items (inkl. möglicher Dia-Armor) |
|
||||
*
|
||||
* ### Defensive – Slot-Maschinen-GUI
|
||||
* Öffnet sich nur wenn kein Feind in [SAFE_RADIUS] Blöcken ist.
|
||||
* Gleiche Outcome-Tabelle, ABER ohne Instant-Death und ohne Dia-Armor.
|
||||
* Die GUI animiert drei Walzen nacheinander, bevor das Ergebnis feststeht.
|
||||
*
|
||||
* ### Integration
|
||||
* Die [SlotMachineGui] nutzt einen eigenen [InventoryHolder]. Der Click-Dispatch
|
||||
* läuft über den zentralen [MenuListener] – dafür muss in [MenuListener.onInventoryClick]
|
||||
* ein zusätzlicher Branch ergänzt werden:
|
||||
* ```kotlin
|
||||
* val spieloHolder = event.inventory.holder as? SpieloKit.SlotMachineGui ?: ...
|
||||
* spieloHolder.onClick(event)
|
||||
* ```
|
||||
*/
|
||||
class SpieloKit : Kit() {
|
||||
|
||||
private val plugin get() = SpeedHG.instance
|
||||
private val rng = Random()
|
||||
private val mm = MiniMessage.miniMessage()
|
||||
|
||||
override val id = "spielo"
|
||||
override val displayName: Component
|
||||
get() = plugin.languageManager.getDefaultComponent("kits.spielo.name", mapOf())
|
||||
override val lore: List<String>
|
||||
get() = plugin.languageManager.getDefaultRawMessageList("kits.spielo.lore")
|
||||
override val icon = Material.GOLD_NUGGET
|
||||
|
||||
// Blockiert Doppel-Trigger während eine Animation läuft
|
||||
internal val gamblingPlayers: MutableSet<UUID> = ConcurrentHashMap.newKeySet()
|
||||
|
||||
// Cooldowns für den Aggressive-Automaten
|
||||
private val activeCooldowns: MutableMap<UUID, Long> = ConcurrentHashMap()
|
||||
|
||||
companion object {
|
||||
const val ACTIVE_COOLDOWN_MS = 12_000L // 12 s zwischen Aggressive-Uses
|
||||
const val SAFE_RADIUS = 12.0 // Feind-Radius für Defensive-GUI-Sperrung
|
||||
}
|
||||
|
||||
// ── Gecachte Instanzen ────────────────────────────────────────────────────
|
||||
|
||||
private val aggressiveActive = AggressiveActive()
|
||||
private val defensiveActive = DefensiveActive()
|
||||
private val aggressivePassive = NoPassive(Playstyle.AGGRESSIVE)
|
||||
private val defensivePassive = NoPassive(Playstyle.DEFENSIVE)
|
||||
|
||||
override fun getActiveAbility(playstyle: Playstyle) = when (playstyle) {
|
||||
Playstyle.AGGRESSIVE -> aggressiveActive
|
||||
Playstyle.DEFENSIVE -> defensiveActive
|
||||
}
|
||||
override fun getPassiveAbility(playstyle: Playstyle) = when (playstyle) {
|
||||
Playstyle.AGGRESSIVE -> aggressivePassive
|
||||
Playstyle.DEFENSIVE -> defensivePassive
|
||||
}
|
||||
|
||||
override val cachedItems = ConcurrentHashMap<UUID, List<ItemStack>>()
|
||||
|
||||
override fun giveItems(player: Player, playstyle: Playstyle) {
|
||||
val (mat, active) = when (playstyle) {
|
||||
Playstyle.AGGRESSIVE -> Material.GOLD_NUGGET to aggressiveActive
|
||||
Playstyle.DEFENSIVE -> Material.GOLD_BLOCK to defensiveActive
|
||||
}
|
||||
val item = ItemBuilder(mat)
|
||||
.name(active.name)
|
||||
.lore(listOf(active.description))
|
||||
.build()
|
||||
cachedItems[player.uniqueId] = listOf(item)
|
||||
player.inventory.addItem(item)
|
||||
}
|
||||
|
||||
override fun onRemove(player: Player) {
|
||||
gamblingPlayers.remove(player.uniqueId)
|
||||
activeCooldowns.remove(player.uniqueId)
|
||||
cachedItems.remove(player.uniqueId)?.forEach { player.inventory.remove(it) }
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// AGGRESSIVE active – Sofort-Gamble (Instant-Death möglich)
|
||||
// =========================================================================
|
||||
|
||||
private inner class AggressiveActive : ActiveAbility(Playstyle.AGGRESSIVE) {
|
||||
|
||||
private val plugin get() = SpeedHG.instance
|
||||
|
||||
override val kitId = "spielo"
|
||||
override val name: String
|
||||
get() = plugin.languageManager.getDefaultRawMessage("kits.spielo.items.automat.name")
|
||||
override val description: String
|
||||
get() = plugin.languageManager.getDefaultRawMessage("kits.spielo.items.automat.description")
|
||||
override val hardcodedHitsRequired = 12
|
||||
override val triggerMaterial = Material.GOLD_NUGGET
|
||||
|
||||
override fun execute(player: Player): AbilityResult {
|
||||
if (gamblingPlayers.contains(player.uniqueId))
|
||||
return AbilityResult.ConditionNotMet("Automat läuft bereits!")
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
val lastUse = activeCooldowns[player.uniqueId] ?: 0L
|
||||
if (now - lastUse < ACTIVE_COOLDOWN_MS) {
|
||||
val secLeft = (ACTIVE_COOLDOWN_MS - (now - lastUse)) / 1000
|
||||
return AbilityResult.ConditionNotMet("Cooldown: ${secLeft}s")
|
||||
}
|
||||
|
||||
activeCooldowns[player.uniqueId] = now
|
||||
gamblingPlayers.add(player.uniqueId)
|
||||
|
||||
// Kurze Sound-Animation (0,8 s) → dann Ergebnis
|
||||
playQuickAnimation(player) {
|
||||
gamblingPlayers.remove(player.uniqueId)
|
||||
if (!player.isOnline || !plugin.gameManager.alivePlayers.contains(player.uniqueId)) return@playQuickAnimation
|
||||
resolveOutcome(player, allowInstantDeath = true, allowDiamondArmor = true)
|
||||
}
|
||||
|
||||
return AbilityResult.Success
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DEFENSIVE active – Slot-Maschinen-GUI öffnen
|
||||
// =========================================================================
|
||||
|
||||
private inner class DefensiveActive : ActiveAbility(Playstyle.DEFENSIVE) {
|
||||
|
||||
private val plugin get() = SpeedHG.instance
|
||||
|
||||
override val kitId = "spielo"
|
||||
override val name: String
|
||||
get() = plugin.languageManager.getDefaultRawMessage("kits.spielo.items.slotautomat.name")
|
||||
override val description: String
|
||||
get() = plugin.languageManager.getDefaultRawMessage("kits.spielo.items.slotautomat.description")
|
||||
override val hardcodedHitsRequired = 15
|
||||
override val triggerMaterial = Material.GOLD_BLOCK
|
||||
|
||||
override fun execute(player: Player): AbilityResult {
|
||||
// Prüfen ob ein Feind zu nah ist
|
||||
val enemyNearby = plugin.gameManager.alivePlayers
|
||||
.asSequence()
|
||||
.filter { it != player.uniqueId }
|
||||
.mapNotNull { Bukkit.getPlayer(it) }
|
||||
.any { it.location.distanceSquared(player.location) <= SAFE_RADIUS * SAFE_RADIUS }
|
||||
|
||||
if (gamblingPlayers.contains(player.uniqueId))
|
||||
return AbilityResult.ConditionNotMet("Automat läuft bereits!")
|
||||
|
||||
if (enemyNearby)
|
||||
{
|
||||
playQuickAnimation(player) {
|
||||
gamblingPlayers.remove(player.uniqueId)
|
||||
if (!player.isOnline || !plugin.gameManager.alivePlayers.contains(player.uniqueId)) return@playQuickAnimation
|
||||
resolveOutcome(player, allowInstantDeath = false, allowDiamondArmor = false)
|
||||
}
|
||||
return AbilityResult.Success
|
||||
}
|
||||
|
||||
SlotMachineGui(player).open()
|
||||
return AbilityResult.Success
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Slot-Maschinen-GUI
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 3×9-Chest-GUI mit drei animierten Walzen.
|
||||
*
|
||||
* ### Slot-Layout (27 Slots):
|
||||
* ```
|
||||
* [ F ][ F ][ F ][ F ][ F ][ F ][ F ][ F ][ F ] ← Filler
|
||||
* [ F ][ F ][W1 ][ F ][W2 ][ F ][W3 ][ F ][ F ] ← Walzen (11, 13, 15)
|
||||
* [ F ][ F ][ F ][ F ][BTN][ F ][ F ][ F ][ F ] ← Spin-Button (22)
|
||||
* ```
|
||||
*
|
||||
* ### Ablauf:
|
||||
* 1. Spieler öffnet GUI → Walzen zeigen zufällige Symbole, Button ist grün.
|
||||
* 2. Spieler klickt Slot 22 ("Drehen") → Animation startet, Button wird gelb.
|
||||
* 3. Walzen stoppen gestaffelt (Walze 1 → 2 → 3).
|
||||
* 4. Nach dem letzten Stop: Outcome auflösen, GUI schließen.
|
||||
*
|
||||
* Der Click-Dispatch muss im [MenuListener] ergänzt werden:
|
||||
* ```kotlin
|
||||
* (event.inventory.holder as? SpieloKit.SlotMachineGui)?.onClick(event)
|
||||
* ```
|
||||
*/
|
||||
inner class SlotMachineGui(private val player: Player) : InventoryHolder {
|
||||
|
||||
private val inv: Inventory = Bukkit.createInventory(
|
||||
this, 27,
|
||||
mm.deserialize("<gold><bold>🎰 Slot-Automat</bold></gold>")
|
||||
)
|
||||
|
||||
private val reelSlots = intArrayOf(11, 13, 15)
|
||||
private val spinButton = 22
|
||||
|
||||
// Symbole die auf den Walzen erscheinen (nur visuell – kein Einfluss auf Outcome)
|
||||
private val reelSymbols = listOf(
|
||||
Material.GOLD_NUGGET, Material.EMERALD, Material.IRON_INGOT,
|
||||
Material.GOLDEN_APPLE, Material.MUSHROOM_STEW, Material.EXPERIENCE_BOTTLE,
|
||||
Material.TNT, Material.BARRIER, Material.NETHER_STAR, Material.LAPIS_LAZULI
|
||||
)
|
||||
|
||||
private var isSpinning = false
|
||||
private var lastAnimTask: BukkitTask? = null
|
||||
|
||||
override fun getInventory(): Inventory = inv
|
||||
|
||||
fun open() {
|
||||
drawLayout()
|
||||
player.openInventory(inv)
|
||||
}
|
||||
|
||||
private fun drawLayout() {
|
||||
val filler = buildFiller()
|
||||
repeat(27) { inv.setItem(it, filler) }
|
||||
reelSlots.forEach { inv.setItem(it, buildReelItem(reelSymbols.random())) }
|
||||
inv.setItem(spinButton, buildSpinButton(spinning = false))
|
||||
}
|
||||
|
||||
// ── Event-Hooks (aufgerufen von MenuListener) ─────────────────────────
|
||||
|
||||
fun onClick(event: InventoryClickEvent) {
|
||||
event.isCancelled = true
|
||||
if (isSpinning) return
|
||||
if (event.rawSlot != spinButton) return
|
||||
|
||||
isSpinning = true
|
||||
gamblingPlayers.add(player.uniqueId)
|
||||
inv.setItem(spinButton, buildSpinButton(spinning = true))
|
||||
|
||||
startSpinAnimation()
|
||||
}
|
||||
|
||||
/** Aufgerufen wenn Inventar geschlossen wird (z.B. ESC). */
|
||||
fun onClose() {
|
||||
lastAnimTask?.cancel()
|
||||
// Charge nur zurückgeben wenn noch nicht gedreht wurde
|
||||
if (!isSpinning) {
|
||||
gamblingPlayers.remove(player.uniqueId)
|
||||
}
|
||||
// Wenn isSpinning == true läuft die Animation noch – Cleanup in onAllReelsStopped
|
||||
}
|
||||
|
||||
// ── Animation ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Startet die gestaffelte Walzen-Animation.
|
||||
* Walze 1 stoppt nach 8 Frames, Walze 2 nach 12, Walze 3 nach 16.
|
||||
* Jeder Frame dauert 2 Ticks (0,1 s). Starts sind versetzt (+5 Ticks pro Walze).
|
||||
*/
|
||||
private fun startSpinAnimation() {
|
||||
val framesPerReel = intArrayOf(8, 12, 16)
|
||||
val startDelays = longArrayOf(0L, 5L, 10L)
|
||||
val ticksPerFrame = 2L
|
||||
var stoppedReels = 0
|
||||
|
||||
for (reelIdx in 0..2) {
|
||||
val slot = reelSlots[reelIdx]
|
||||
val maxFrames = framesPerReel[reelIdx]
|
||||
var frame = 0
|
||||
|
||||
val task = object : BukkitRunnable() {
|
||||
override fun run()
|
||||
{
|
||||
if (!player.isOnline) {
|
||||
this.cancel(); return
|
||||
}
|
||||
frame++
|
||||
|
||||
if (frame <= maxFrames) {
|
||||
// Zufälliges Walzen-Symbol während Rotation
|
||||
inv.setItem(slot, buildReelItem(reelSymbols.random()))
|
||||
val pitch = (0.6f + frame * 0.07f).coerceAtMost(2.0f)
|
||||
player.playSound(player.location, Sound.BLOCK_NOTE_BLOCK_HAT, 0.4f, pitch)
|
||||
} else {
|
||||
// Einrasten – finales Symbol (zufällig, rein visuell)
|
||||
inv.setItem(slot, buildReelItem(reelSymbols.random()))
|
||||
player.playSound(player.location, Sound.BLOCK_NOTE_BLOCK_CHIME, 0.9f, 1.1f + reelIdx * 0.2f)
|
||||
|
||||
stoppedReels++
|
||||
if (stoppedReels == 3) onAllReelsStopped()
|
||||
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
}.runTaskTimer( plugin, startDelays[ reelIdx ], ticksPerFrame )
|
||||
|
||||
lastAnimTask = task
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAllReelsStopped() {
|
||||
player.playSound(player.location, Sound.ENTITY_PLAYER_LEVELUP, 0.7f, 1.5f)
|
||||
|
||||
// Kurze Pause, dann Outcome auslösen und GUI schließen
|
||||
Bukkit.getScheduler().runTaskLater(plugin, { ->
|
||||
gamblingPlayers.remove(player.uniqueId)
|
||||
if (!player.isOnline || !plugin.gameManager.alivePlayers.contains(player.uniqueId)) return@runTaskLater
|
||||
player.closeInventory()
|
||||
// Defensive: kein Instant-Death, keine Dia-Armor
|
||||
resolveOutcome(player, allowInstantDeath = false, allowDiamondArmor = false)
|
||||
}, 20L)
|
||||
}
|
||||
|
||||
// ── Item-Builder ──────────────────────────────────────────────────────
|
||||
|
||||
private fun buildReelItem(material: Material) = ItemStack(material).also { item ->
|
||||
item.editMeta { meta ->
|
||||
meta.displayName(Component.text(" ").decoration(TextDecoration.ITALIC, false))
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildSpinButton(spinning: Boolean): ItemStack {
|
||||
val mat = if (spinning) Material.YELLOW_CONCRETE else Material.LIME_CONCRETE
|
||||
val name = if (spinning)
|
||||
mm.deserialize("<yellow><bold>⟳ Dreht...</bold></yellow>")
|
||||
else
|
||||
mm.deserialize("<green><bold>▶ Drehen!</bold></green>")
|
||||
|
||||
return ItemStack(mat).also { item ->
|
||||
item.editMeta { meta ->
|
||||
meta.displayName(name.decoration(TextDecoration.ITALIC, false))
|
||||
if (!spinning) {
|
||||
meta.lore(listOf(
|
||||
Component.empty(),
|
||||
mm.deserialize("<gray>Klicken um die Walzen zu drehen.")
|
||||
.decoration(TextDecoration.ITALIC, false),
|
||||
Component.empty()
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildFiller() = ItemStack(Material.BLACK_STAINED_GLASS_PANE).also { item ->
|
||||
item.editMeta { meta ->
|
||||
meta.displayName(Component.text(" ").decoration(TextDecoration.ITALIC, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Outcome-Auflösung – gemeinsam für Aggressive und Defensive
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Löst das Gamble-Ergebnis auf.
|
||||
* @param allowInstantDeath true = Aggressive (5 % Instant Death möglich)
|
||||
* @param allowDiamondArmor true = Aggressive (Dia-Armor in Loot möglich)
|
||||
*/
|
||||
fun resolveOutcome(player: Player, allowInstantDeath: Boolean, allowDiamondArmor: Boolean) {
|
||||
val roll = rng.nextDouble()
|
||||
|
||||
when {
|
||||
allowInstantDeath && roll < 0.05 -> triggerInstantDeath(player)
|
||||
allowInstantDeath && roll < 0.20 -> triggerRandomDisaster(player)
|
||||
roll < (if (allowInstantDeath) 0.30 else 0.10) -> applyNegativeEffect(player)
|
||||
roll < (if (allowInstantDeath) 0.50 else 0.30) -> giveNeutralItems(player)
|
||||
else -> givePositiveItems(player, allowDiamondArmor)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Einzelne Outcome-Typen ────────────────────────────────────────────────
|
||||
|
||||
private fun triggerInstantDeath(player: Player) {
|
||||
player.world.spawnParticle(Particle.EXPLOSION, player.location, 5, 0.5, 0.5, 0.5, 0.0)
|
||||
player.world.playSound(player.location, Sound.ENTITY_WITHER_SPAWN, 1f, 1.5f)
|
||||
player.sendActionBar(player.trans("kits.spielo.messages.instant_death"))
|
||||
|
||||
// Einen Tick später töten damit das ActionBar-Paket noch ankommt
|
||||
Bukkit.getScheduler().runTaskLater(plugin, { ->
|
||||
if (!player.isOnline || !plugin.gameManager.alivePlayers.contains(player.uniqueId)) return@runTaskLater
|
||||
player.health = 0.0
|
||||
}, 3L)
|
||||
}
|
||||
|
||||
private fun triggerRandomDisaster(player: Player) {
|
||||
val disaster = listOf(
|
||||
MeteorDisaster(), TornadoDisaster(), EarthquakeDisaster(), ThunderDisaster()
|
||||
).random()
|
||||
|
||||
disaster.warn(player)
|
||||
Bukkit.getScheduler().runTaskLater(plugin, { ->
|
||||
if (!player.isOnline || !plugin.gameManager.alivePlayers.contains(player.uniqueId)) return@runTaskLater
|
||||
disaster.trigger(plugin, player)
|
||||
}, disaster.warningDelayTicks)
|
||||
|
||||
player.world.playSound(player.location, Sound.ENTITY_LIGHTNING_BOLT_THUNDER, 0.8f, 0.6f)
|
||||
player.sendActionBar(player.trans("kits.spielo.messages.gamble_event"))
|
||||
}
|
||||
|
||||
private fun applyNegativeEffect(player: Player) {
|
||||
val outcomes: List<() -> Unit> = listOf(
|
||||
{ player.addPotionEffect(PotionEffect(PotionEffectType.SLOWNESS, 6 * 20, 1)) },
|
||||
{ player.addPotionEffect(PotionEffect(PotionEffectType.MINING_FATIGUE, 6 * 20, 1)) },
|
||||
{ player.addPotionEffect(PotionEffect(PotionEffectType.NAUSEA, 5 * 20, 0)) },
|
||||
{ player.addPotionEffect(PotionEffect(PotionEffectType.WEAKNESS, 8 * 20, 0)) },
|
||||
{ player.fireTicks = 4 * 20 }
|
||||
)
|
||||
outcomes.random().invoke()
|
||||
|
||||
player.playSound(player.location, Sound.ENTITY_VILLAGER_NO, 1f, 0.8f)
|
||||
player.world.spawnParticle(
|
||||
Particle.ANGRY_VILLAGER,
|
||||
player.location.clone().add(0.0, 2.0, 0.0),
|
||||
8, 0.4, 0.3, 0.4, 0.0
|
||||
)
|
||||
player.sendActionBar(player.trans("kits.spielo.messages.gamble_bad"))
|
||||
}
|
||||
|
||||
private fun giveNeutralItems(player: Player) {
|
||||
val items = listOf(
|
||||
ItemStack(Material.ARROW, rng.nextInt(5) + 3),
|
||||
ItemStack(Material.BREAD, rng.nextInt(4) + 2),
|
||||
ItemStack(Material.IRON_INGOT, rng.nextInt(3) + 1),
|
||||
ItemStack(Material.COBBLESTONE, rng.nextInt(8) + 4),
|
||||
)
|
||||
player.inventory.addItem(items.random())
|
||||
|
||||
player.playSound(player.location, Sound.ENTITY_ITEM_PICKUP, 0.8f, 1.0f)
|
||||
player.sendActionBar(player.trans("kits.spielo.messages.gamble_neutral"))
|
||||
}
|
||||
|
||||
private fun givePositiveItems(player: Player, allowDiamondArmor: Boolean) {
|
||||
data class LootEntry(val item: ItemStack, val weight: Int)
|
||||
|
||||
val pool = buildList {
|
||||
add(LootEntry(ItemStack(Material.MUSHROOM_STEW, 3), 30))
|
||||
add(LootEntry(ItemStack(Material.MUSHROOM_STEW, 5), 15))
|
||||
add(LootEntry(ItemStack(Material.GOLDEN_APPLE), 20))
|
||||
add(LootEntry(ItemStack(Material.ENCHANTED_GOLDEN_APPLE), 3))
|
||||
add(LootEntry(ItemStack(Material.EXPERIENCE_BOTTLE, 5), 12))
|
||||
add(LootEntry(buildSplashPotion(PotionEffectType.STRENGTH, 200, 0), 8))
|
||||
add(LootEntry(buildSplashPotion(PotionEffectType.SPEED, 400, 0), 8))
|
||||
add(LootEntry(buildSplashPotion(PotionEffectType.REGENERATION, 160, 1), 8))
|
||||
// Eisen-Rüstung: immer möglich
|
||||
add(LootEntry(ItemStack(Material.IRON_CHESTPLATE), 4))
|
||||
add(LootEntry(ItemStack(Material.IRON_HELMET), 4))
|
||||
// Dia-Rüstung: nur Aggressive
|
||||
if (allowDiamondArmor) {
|
||||
add(LootEntry(ItemStack(Material.DIAMOND_CHESTPLATE), 2))
|
||||
add(LootEntry(ItemStack(Material.DIAMOND_HELMET), 2))
|
||||
}
|
||||
}
|
||||
|
||||
val totalWeight = pool.sumOf { it.weight }
|
||||
var roll = rng.nextInt(totalWeight)
|
||||
val chosen = pool.first { entry -> roll -= entry.weight; roll < 0 }
|
||||
player.inventory.addItem(chosen.item.clone())
|
||||
|
||||
player.playSound(player.location, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1.4f)
|
||||
player.world.spawnParticle(
|
||||
Particle.HAPPY_VILLAGER,
|
||||
player.location.clone().add(0.0, 1.5, 0.0),
|
||||
12, 0.4, 0.4, 0.4, 0.0
|
||||
)
|
||||
player.sendActionBar(player.trans("kits.spielo.messages.gamble_good"))
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Stubs
|
||||
// =========================================================================
|
||||
|
||||
class NoPassive(playstyle: Playstyle) : PassiveAbility(playstyle) {
|
||||
override val name = "None"
|
||||
override val description = "None"
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Hilfsmethoden
|
||||
// =========================================================================
|
||||
|
||||
/** Klicker-Sounds mit steigendem Pitch, danach Callback. */
|
||||
private fun playQuickAnimation(player: Player, onFinish: () -> Unit) {
|
||||
for (i in 0..5) {
|
||||
Bukkit.getScheduler().runTaskLater(plugin, { ->
|
||||
if (!player.isOnline) return@runTaskLater
|
||||
player.playSound(player.location, Sound.BLOCK_NOTE_BLOCK_HAT, 0.9f, 0.5f + i * 0.25f)
|
||||
player.world.spawnParticle(
|
||||
Particle.NOTE,
|
||||
player.location.clone().add(0.0, 2.3, 0.0),
|
||||
1, 0.2, 0.1, 0.2, 0.0
|
||||
)
|
||||
}, i * 3L)
|
||||
}
|
||||
Bukkit.getScheduler().runTaskLater(plugin, Runnable(onFinish), 18L)
|
||||
}
|
||||
|
||||
private fun buildSplashPotion(type: PotionEffectType, duration: Int, amplifier: Int) =
|
||||
ItemStack(Material.SPLASH_POTION).also { potion ->
|
||||
potion.editMeta { meta ->
|
||||
if (meta is org.bukkit.inventory.meta.PotionMeta)
|
||||
meta.addCustomEffect(PotionEffect(type, duration, amplifier), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user