Add chat listener and fix ability height/fall logic
Add a ChatListener and register it to render chat with rank prefixes/colors. Enforce a maximum knockback altitude (MAX_KNOCKBACK_HEIGHT_Y) and block height-restricted ability use in Blitzcrank and BlackPanther; surface a localized height_restriction message. Add per-kit noFallDamage tracking for BlackPanther and Trident and cancel fall damage for flagged players in the KitEventDispatcher. Improve hitsRequired resolution: CustomGameSettings will treat a hardcoded 0 as an explicit cooldown-only value (never overridden) and the ActiveAbility getter now only accepts positive override values. Misc fixes: allow throwable items (potions/pearls) to bypass active-item handling, tighten ninja last-hit tracking, fix kit item drop detection in GameStateListener, tweak tablist prefix/suffix handling, and bump TheWorld ability cooldown from 20000ms to 25000ms. Also update language entries and minor formatting/cleanup.
This commit is contained in:
@@ -21,6 +21,7 @@ import club.mcscrims.speedhg.gui.listener.MenuListener
|
||||
import club.mcscrims.speedhg.kit.KitManager
|
||||
import club.mcscrims.speedhg.kit.impl.*
|
||||
import club.mcscrims.speedhg.kit.listener.KitEventDispatcher
|
||||
import club.mcscrims.speedhg.listener.ChatListener
|
||||
import club.mcscrims.speedhg.listener.ConnectListener
|
||||
import club.mcscrims.speedhg.listener.GameStateListener
|
||||
import club.mcscrims.speedhg.listener.SoupListener
|
||||
@@ -288,6 +289,7 @@ class SpeedHG : JavaPlugin() {
|
||||
pm.registerEvents(PerkEventDispatcher( this, perkManager ), this )
|
||||
pm.registerEvents( TeamListener(), this )
|
||||
pm.registerEvents( lobbyItemManager, this )
|
||||
pm.registerEvents(ChatListener( this, VolcanoServerRankProvider() ), this )
|
||||
}
|
||||
|
||||
private fun registerRecipes()
|
||||
|
||||
@@ -37,9 +37,22 @@ data class CustomGameSettings(
|
||||
/**
|
||||
* Gibt den hitsRequired-Wert für ein Kit zurück.
|
||||
* Priorität: kit-spezifisch > global > hardcoded Default
|
||||
*
|
||||
* Wenn hardcodedDefault == 0, ist das Kit explizit als cooldown-only markiert
|
||||
* und der globale Wert wird niemals angewendet.
|
||||
*/
|
||||
fun hitsRequired(kitId: String, hardcodedDefault: Int): Int =
|
||||
kits[kitId]?.hitsRequired ?: globalHitsRequired.takeIf { it >= 0 } ?: hardcodedDefault
|
||||
fun hitsRequired(
|
||||
kitId: String,
|
||||
hardcodedDefault: Int
|
||||
): Int
|
||||
{
|
||||
// A hardcoded 0 means the kit is explicitly cooldown-based — never override it.
|
||||
if ( hardcodedDefault == 0 ) return 0
|
||||
|
||||
return kits[ kitId ]?.hitsRequired?.takeIf { it >= 0 }
|
||||
?: globalHitsRequired.takeIf { it >= 0 }
|
||||
?: hardcodedDefault
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
@@ -88,7 +101,7 @@ data class CustomGameSettings(
|
||||
@SerialName("pounce_timeout_ticks") val pounceTimeoutTicks: Long = 30L,
|
||||
|
||||
// TheWorld
|
||||
@SerialName("tw_ability_cooldown_ms") val abilityCooldownMs: Long = 20_000L,
|
||||
@SerialName("tw_ability_cooldown_ms") val abilityCooldownMs: Long = 25_000L,
|
||||
@SerialName("tw_shockwave_radius") val shockwaveRadius: Double = 6.0,
|
||||
@SerialName("tw_teleport_range") val teleportRange: Double = 10.0,
|
||||
@SerialName("tw_max_teleport_charges") val maxTeleportCharges: Int = 3,
|
||||
|
||||
@@ -52,7 +52,7 @@ abstract class ActiveAbility(
|
||||
private var _hitsRequired: Int = -1
|
||||
|
||||
val hitsRequired: Int
|
||||
get() = _hitsRequired.takeIf { it >= 0 } ?: hardcodedHitsRequired
|
||||
get() = _hitsRequired.takeIf { it > 0 } ?: hardcodedHitsRequired
|
||||
|
||||
/**
|
||||
* Einmalig beim applyKit() aufgerufen – danach ist der Wert gecacht.
|
||||
|
||||
@@ -7,6 +7,7 @@ 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.kit.listener.KitEventDispatcher.Companion.MAX_KNOCKBACK_HEIGHT_Y
|
||||
import club.mcscrims.speedhg.util.ItemBuilder
|
||||
import club.mcscrims.speedhg.util.WorldEditUtils
|
||||
import club.mcscrims.speedhg.util.trans
|
||||
@@ -61,6 +62,9 @@ class BlackPantherKit : Kit()
|
||||
/** Players currently in Fist Mode: UUID → expiry timestamp (ms). */
|
||||
internal val fistModeExpiry: MutableMap<UUID, Long> = ConcurrentHashMap()
|
||||
|
||||
/** Players currently in a pounce — fall damage is suppressed on landing. */
|
||||
internal val noFallDamagePlayers: MutableSet<UUID> = ConcurrentHashMap.newKeySet()
|
||||
|
||||
companion object
|
||||
{
|
||||
private fun override() = SpeedHG.instance.customGameManager.settings.kits.kits["blackpanther"]
|
||||
@@ -106,8 +110,11 @@ class BlackPantherKit : Kit()
|
||||
player.inventory.addItem(item)
|
||||
}
|
||||
|
||||
override fun onRemove(player: Player) {
|
||||
override fun onRemove(
|
||||
player: Player
|
||||
) {
|
||||
fistModeExpiry.remove( player.uniqueId )
|
||||
noFallDamagePlayers.remove( player.uniqueId )
|
||||
cachedItems.remove( player.uniqueId )?.forEach { player.inventory.remove( it ) }
|
||||
}
|
||||
|
||||
@@ -128,7 +135,15 @@ class BlackPantherKit : Kit()
|
||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blackpanther.items.push.description")
|
||||
override val triggerMaterial = Material.BLACK_DYE
|
||||
|
||||
override fun execute(player: Player): AbilityResult {
|
||||
override fun execute(
|
||||
player: Player
|
||||
): AbilityResult
|
||||
{
|
||||
if ( player.location.y > MAX_KNOCKBACK_HEIGHT_Y )
|
||||
return AbilityResult.ConditionNotMet(
|
||||
plugin.languageManager.getRawMessage( player, "kits.height_restriction" )
|
||||
)
|
||||
|
||||
val enemies = player.world
|
||||
.getNearbyEntities(player.location, PUSH_RADIUS, PUSH_RADIUS, PUSH_RADIUS)
|
||||
.filterIsInstance<Player>()
|
||||
@@ -236,8 +251,10 @@ class BlackPantherKit : Kit()
|
||||
override val description: String
|
||||
get() = plugin.languageManager.getDefaultRawMessage("kits.blackpanther.passive.defensive.description")
|
||||
|
||||
override fun onMove(player: Player, event: PlayerMoveEvent)
|
||||
{
|
||||
override fun onMove(
|
||||
player: Player,
|
||||
event: PlayerMoveEvent
|
||||
) {
|
||||
if ( event.to.y >= event.from.y ) return
|
||||
if ( player.fallDistance < POUNCE_MIN_FALL ) return
|
||||
|
||||
@@ -258,7 +275,6 @@ class BlackPantherKit : Kit()
|
||||
impactLoc.world.playSound(impactLoc, Sound.ENTITY_GENERIC_EXPLODE, 1f, 0.7f)
|
||||
impactLoc.world.playSound(impactLoc, Sound.ENTITY_IRON_GOLEM_HURT, 1f, 0.5f)
|
||||
|
||||
// Async WorldEdit Krater (Vorsicht: Blöcke setzen muss synchron passieren, also normaler Scheduler)
|
||||
Bukkit.getScheduler().runTaskLater(plugin, Runnable {
|
||||
WorldEditUtils.createCylinder(
|
||||
impactLoc.world, impactLoc.clone().subtract(0.0, 1.0, 0.0),
|
||||
@@ -269,7 +285,8 @@ class BlackPantherKit : Kit()
|
||||
player.sendActionBar(player.trans("kits.blackpanther.messages.wakanda_impact",
|
||||
mapOf("count" to splashTargets.size.toString())))
|
||||
|
||||
// Setze die Fall-Distanz auf 0 zurück, damit der Spieler selbst keinen Vanilla-Fallschaden bekommt
|
||||
// Suppress fall damage for this landing
|
||||
noFallDamagePlayers.add( player.uniqueId )
|
||||
player.fallDistance = 0f
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ 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.kit.listener.KitEventDispatcher
|
||||
import club.mcscrims.speedhg.kit.listener.KitEventDispatcher.Companion.MAX_KNOCKBACK_HEIGHT_Y
|
||||
import club.mcscrims.speedhg.util.ItemBuilder
|
||||
import club.mcscrims.speedhg.util.trans
|
||||
import net.kyori.adventure.text.Component
|
||||
@@ -146,6 +148,12 @@ class BlitzcrankKit : Kit() {
|
||||
private fun fireUlt(
|
||||
caster: Player
|
||||
) {
|
||||
if ( caster.location.y > MAX_KNOCKBACK_HEIGHT_Y )
|
||||
{
|
||||
caster.sendActionBar(caster.trans( "kits.height_restriction" ))
|
||||
return
|
||||
}
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
val lastUlt = ultCooldowns[ caster.uniqueId ] ?: 0L
|
||||
|
||||
@@ -220,6 +228,11 @@ class BlitzcrankKit : Kit() {
|
||||
|
||||
override fun execute(player: Player): AbilityResult
|
||||
{
|
||||
if ( player.location.y > MAX_KNOCKBACK_HEIGHT_Y )
|
||||
return AbilityResult.ConditionNotMet(
|
||||
plugin.languageManager.getRawMessage( player, "kits.height_restriction" )
|
||||
)
|
||||
|
||||
val eyeLoc = player.eyeLocation
|
||||
val dir = eyeLoc.direction.normalize()
|
||||
|
||||
@@ -289,7 +302,15 @@ class BlitzcrankKit : Kit() {
|
||||
override val hardcodedHitsRequired = 15
|
||||
override val triggerMaterial = Material.PISTON
|
||||
|
||||
override fun execute(player: Player): AbilityResult {
|
||||
override fun execute(
|
||||
player: Player
|
||||
): AbilityResult
|
||||
{
|
||||
if ( player.location.y > MAX_KNOCKBACK_HEIGHT_Y )
|
||||
return AbilityResult.ConditionNotMet(
|
||||
plugin.languageManager.getRawMessage( player, "kits.height_restriction" )
|
||||
)
|
||||
|
||||
val targets = player.world
|
||||
.getNearbyEntities(player.location, STUN_RADIUS, STUN_RADIUS, STUN_RADIUS)
|
||||
.filterIsInstance<Player>()
|
||||
|
||||
@@ -62,6 +62,9 @@ class TridentKit : Kit() {
|
||||
private val diveMonitors: MutableMap<UUID, BukkitTask> = ConcurrentHashMap()
|
||||
private val lastSequenceTime: MutableMap<UUID, Long> = ConcurrentHashMap()
|
||||
|
||||
/** Players who have recently launched a dive and should not receive fall damage. */
|
||||
internal val noFallDamagePlayers: MutableSet<UUID> = ConcurrentHashMap.newKeySet()
|
||||
|
||||
companion object {
|
||||
const val MAX_DIVE_CHARGES = 3
|
||||
const val SEQUENCE_COOLDOWN_MS = 25_000L // Cooldown zwischen vollst. Sequenzen
|
||||
@@ -129,6 +132,7 @@ class TridentKit : Kit() {
|
||||
diveCharges.remove( player.uniqueId )
|
||||
diveMonitors.remove( player.uniqueId )?.cancel()
|
||||
lastSequenceTime.remove( player.uniqueId )
|
||||
noFallDamagePlayers.remove( player.uniqueId )
|
||||
cachedItems.remove( player.uniqueId )?.forEach { player.inventory.remove( it ) }
|
||||
}
|
||||
|
||||
@@ -248,6 +252,7 @@ class TridentKit : Kit() {
|
||||
else diveCharges[ player.uniqueId ] = charges - 1
|
||||
|
||||
player.velocity = player.velocity.clone().setY( 1.38 )
|
||||
noFallDamagePlayers.add( player.uniqueId )
|
||||
|
||||
val remaining = diveCharges.getOrDefault( player.uniqueId, 0 )
|
||||
player.sendActionBar(player.trans( "kits.trident.messages.dive_launched", "charges" to remaining.toString() ))
|
||||
|
||||
@@ -10,6 +10,8 @@ import club.mcscrims.speedhg.kit.charge.ChargeState
|
||||
import club.mcscrims.speedhg.kit.impl.AnchorKit
|
||||
import club.mcscrims.speedhg.kit.impl.BlackPantherKit
|
||||
import club.mcscrims.speedhg.kit.impl.IceMageKit
|
||||
import club.mcscrims.speedhg.kit.impl.NinjaKit
|
||||
import club.mcscrims.speedhg.kit.impl.TridentKit
|
||||
import club.mcscrims.speedhg.kit.impl.VenomKit
|
||||
import club.mcscrims.speedhg.util.trans
|
||||
import net.kyori.adventure.text.Component
|
||||
@@ -26,6 +28,7 @@ import org.bukkit.event.EventPriority
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.event.block.BlockBreakEvent
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
||||
import org.bukkit.event.entity.EntityDamageEvent
|
||||
import org.bukkit.event.entity.EntityDeathEvent
|
||||
import org.bukkit.event.entity.EntityExplodeEvent
|
||||
import org.bukkit.event.entity.PlayerDeathEvent
|
||||
@@ -67,6 +70,11 @@ class KitEventDispatcher(
|
||||
private val kitManager: KitManager,
|
||||
) : Listener {
|
||||
|
||||
companion object {
|
||||
/** Above this Y-level, knockback abilities are disabled to prevent skybasing. */
|
||||
const val MAX_KNOCKBACK_HEIGHT_Y = 100.0
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Hit tracking + charge system + passive combat hook
|
||||
// =========================================================================
|
||||
@@ -102,11 +110,19 @@ class KitEventDispatcher(
|
||||
sendChargeUpdateActionBar( attacker, currentHits, chargeData.hitsRequired )
|
||||
}
|
||||
|
||||
// ── 2. Attacker passive hook ─────────────────────────────────────────
|
||||
// ── 2. Ninja last-hit tracking ───────────────────────────────────────
|
||||
if ( attackerKit is NinjaKit &&
|
||||
attackerPlaystyle == Playstyle.AGGRESSIVE )
|
||||
{
|
||||
attackerKit.lastHitEnemy[ attacker.uniqueId ] =
|
||||
Pair( victim.uniqueId, System.currentTimeMillis() )
|
||||
}
|
||||
|
||||
// ── 3. Attacker passive hook ─────────────────────────────────────────
|
||||
attackerKit.getPassiveAbility( attackerPlaystyle )
|
||||
.onHitEnemy( attacker, victim, event )
|
||||
|
||||
// ── 3. Victim passive hook ────────────────────────────────────────────
|
||||
// ── 4. Victim passive hook ────────────────────────────────────────────
|
||||
kitManager.getSelectedKit( victim )
|
||||
?.getPassiveAbility(kitManager.getSelectedPlaystyle( victim ))
|
||||
?.onHitByEnemy( victim, attacker, event )
|
||||
@@ -129,7 +145,6 @@ class KitEventDispatcher(
|
||||
) {
|
||||
val player = event.player
|
||||
|
||||
// Only main-hand right-clicks — ignore left-click and off-hand duplicates
|
||||
if ( event.hand != EquipmentSlot.HAND ) return
|
||||
if ( !event.action.isRightClick ) return
|
||||
if ( !isIngame() ) return
|
||||
@@ -144,6 +159,11 @@ class KitEventDispatcher(
|
||||
val itemInHand = player.inventory.itemInMainHand
|
||||
val active = kit.getActiveAbility( playstyle )
|
||||
|
||||
// Allow throwable items (potions, ender pearls, etc.) to pass through
|
||||
if ( itemInHand.type == Material.SPLASH_POTION ||
|
||||
itemInHand.type == Material.LINGERING_POTION ||
|
||||
itemInHand.type == Material.ENDER_PEARL ) return
|
||||
|
||||
if ( itemInHand.type != active.triggerMaterial ) return
|
||||
|
||||
event.isCancelled = true // prevent vanilla block interaction on ability item
|
||||
@@ -392,10 +412,39 @@ class KitEventDispatcher(
|
||||
event.droppedExp = 0
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = false)
|
||||
fun onLeapFallDamage(
|
||||
event: EntityDamageEvent
|
||||
) {
|
||||
if ( event.cause != EntityDamageEvent.DamageCause.FALL ) return
|
||||
if ( !isIngame() ) return
|
||||
|
||||
val player = event.entity as? Player ?: return
|
||||
|
||||
when(val kit = kitManager.getSelectedKit( player ))
|
||||
{
|
||||
is TridentKit ->
|
||||
{
|
||||
if ( kit.noFallDamagePlayers.remove( player.uniqueId ) )
|
||||
event.isCancelled = true
|
||||
}
|
||||
is BlackPantherKit ->
|
||||
{
|
||||
if ( kit.noFallDamagePlayers.remove( player.uniqueId ) )
|
||||
event.isCancelled = true
|
||||
}
|
||||
else -> return
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Helpers
|
||||
// =========================================================================
|
||||
|
||||
private fun isAboveKnockbackHeight(
|
||||
player: Player
|
||||
): Boolean = player.location.y > MAX_KNOCKBACK_HEIGHT_Y
|
||||
|
||||
private fun changeGladiatorBlock(
|
||||
event: Cancellable,
|
||||
block: Block
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package club.mcscrims.speedhg.listener
|
||||
|
||||
import club.mcscrims.speedhg.SpeedHG
|
||||
import club.mcscrims.speedhg.scoreboard.ServerRankProvider
|
||||
import io.papermc.paper.event.player.AsyncChatEvent
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.format.NamedTextColor
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.Listener
|
||||
|
||||
class ChatListener(
|
||||
private val plugin: SpeedHG,
|
||||
private val rankProvider: ServerRankProvider
|
||||
) : Listener {
|
||||
|
||||
private val mm = MiniMessage.miniMessage()
|
||||
|
||||
@EventHandler
|
||||
fun onAsyncChat(
|
||||
event: AsyncChatEvent
|
||||
) {
|
||||
val player = event.player
|
||||
|
||||
val prefix = rankProvider.getRankPrefix( player )
|
||||
val nameColor = rankProvider.getRankColor( player )
|
||||
|
||||
event.renderer { source, _, message, _ ->
|
||||
val coloredName = mm.deserialize(
|
||||
"${nameColor}${source.name}<reset>"
|
||||
)
|
||||
|
||||
Component.empty()
|
||||
.append( prefix )
|
||||
.append( Component.space() )
|
||||
.append( coloredName )
|
||||
.append(mm.deserialize( "<dark_gray>: <gray>" ))
|
||||
.append(message.colorIfAbsent( NamedTextColor.GRAY ))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import club.mcscrims.speedhg.game.GameState
|
||||
import club.mcscrims.speedhg.util.sendMsg
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.Sound
|
||||
import org.bukkit.attribute.Attribute
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.event.Event
|
||||
import org.bukkit.event.EventHandler
|
||||
@@ -164,10 +163,12 @@ class GameStateListener : Listener {
|
||||
return
|
||||
}
|
||||
|
||||
val kitItems = plugin.kitManager.getSelectedKit( player )?.cachedItems?.get( player.uniqueId ) ?: return
|
||||
val kitItems = plugin.kitManager.getSelectedKit( player )
|
||||
?.cachedItems?.get( player.uniqueId ) ?: return
|
||||
val item = event.itemDrop.itemStack
|
||||
|
||||
if (kitItems.contains( item ))
|
||||
val isKitItem = kitItems.any { kitItem -> kitItem.isSimilar( item ) }
|
||||
if ( isKitItem )
|
||||
{
|
||||
event.isCancelled = true
|
||||
player.playSound( player.location, Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f )
|
||||
|
||||
@@ -234,10 +234,9 @@ class TablistManager(
|
||||
team.prefix(rankProvider.getRankPrefix( player ))
|
||||
|
||||
// ── playerListName: farbiger Spielername ───────────────────────────
|
||||
// Ersetzt den Standard-Anzeigenamen in der Namens-Spalte.
|
||||
// Endergebnis: [PREFIX] [NAME] [SUFFIX]
|
||||
// WICHTIG: KEIN <reset> hier. Das <reset> machen wir am Anfang des Suffixes!
|
||||
val nameColor = rankProvider.getRankColor( player )
|
||||
player.playerListName(mm.deserialize( "${nameColor}${player.name}<reset>" ))
|
||||
player.playerListName(mm.deserialize( "${nameColor}${player.name}" ))
|
||||
|
||||
// ── Suffix: SpeedHG-Rang (z. B. "[Gold II]") ──────────────────────
|
||||
team.suffix(buildSpeedHGRankSuffix( player ))
|
||||
@@ -259,7 +258,9 @@ class TablistManager(
|
||||
val games = ( stats?.wins ?: 0 ) + ( stats?.losses ?: 0 )
|
||||
val rankTag = Rank.getFormattedRankTag( score, games )
|
||||
|
||||
mm.deserialize( " <dark_gray>[<reset>${rankTag}<dark_gray>]</dark_gray>" )
|
||||
// Führendes <reset> stellt sicher, dass die Spielerfarbe nicht in den Suffix blutet
|
||||
// und erzwingt einen Cut, den Bukkit/Paper als neues Suffix-Objekt erkennt.
|
||||
mm.deserialize( "<reset> <dark_gray>[<reset>${rankTag}<dark_gray>]</dark_gray>" )
|
||||
}
|
||||
|
||||
/** Entfernt das Scoreboard-Team des Spielers vollständig. */
|
||||
|
||||
@@ -307,6 +307,7 @@ perks:
|
||||
kits:
|
||||
needed_hits: '<gold>⚡ Ability: <white><current>/<required> Hits</white></gold>'
|
||||
ability_charged: '<green><bold>⚡ ABILITY READY!</bold></green>'
|
||||
height_restriction: '<red>⚠ This ability cannot be used at high altitudes!'
|
||||
|
||||
backup:
|
||||
name: '<gradient:gold:#ff841f><bold>Backup</bold></gradient>'
|
||||
@@ -624,8 +625,8 @@ kits:
|
||||
name: '<gradient:gold:yellow><bold>Spielo</bold></gradient>'
|
||||
lore:
|
||||
- ' '
|
||||
- '<gray>AGGRESSIVE: Gambling at the push of a button</gray>'
|
||||
- '<gray>DEFENSIVE: Slot machine - no instant death</gray>'
|
||||
- 'AGGRESSIVE: Gambling at the push of a button'
|
||||
- 'DEFENSIVE: Slot machine - no instant death'
|
||||
items:
|
||||
automat:
|
||||
name: '<gold><bold>Slot Machine</bold></gold>'
|
||||
|
||||
Reference in New Issue
Block a user