Add custom game settings
Each kit now supports custom settings aswell as custom game settings like min_players
This commit is contained in:
@@ -25,7 +25,9 @@ dependencies {
|
|||||||
|
|
||||||
implementation("com.zaxxer:HikariCP:5.1.0")
|
implementation("com.zaxxer:HikariCP:5.1.0")
|
||||||
implementation("com.mysql:mysql-connector-j:8.4.0")
|
implementation("com.mysql:mysql-connector-j:8.4.0")
|
||||||
|
|
||||||
implementation(libs.kotlinxCoroutines)
|
implementation(libs.kotlinxCoroutines)
|
||||||
|
implementation(libs.kotlinxSerialization)
|
||||||
|
|
||||||
compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT")
|
compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT")
|
||||||
compileOnly("com.sk89q.worldedit:worldedit-core:7.2.17-SNAPSHOT")
|
compileOnly("com.sk89q.worldedit:worldedit-core:7.2.17-SNAPSHOT")
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package club.mcscrims.speedhg
|
|||||||
import club.mcscrims.speedhg.command.KitCommand
|
import club.mcscrims.speedhg.command.KitCommand
|
||||||
import club.mcscrims.speedhg.command.LeaderboardCommand
|
import club.mcscrims.speedhg.command.LeaderboardCommand
|
||||||
import club.mcscrims.speedhg.command.TimerCommand
|
import club.mcscrims.speedhg.command.TimerCommand
|
||||||
|
import club.mcscrims.speedhg.config.CustomGameManager
|
||||||
|
import club.mcscrims.speedhg.config.CustomGameSettings
|
||||||
import club.mcscrims.speedhg.config.LanguageManager
|
import club.mcscrims.speedhg.config.LanguageManager
|
||||||
import club.mcscrims.speedhg.database.DatabaseManager
|
import club.mcscrims.speedhg.database.DatabaseManager
|
||||||
import club.mcscrims.speedhg.database.StatsManager
|
import club.mcscrims.speedhg.database.StatsManager
|
||||||
@@ -59,10 +61,16 @@ class SpeedHG : JavaPlugin() {
|
|||||||
lateinit var discordWebhookManager: DiscordWebhookManager
|
lateinit var discordWebhookManager: DiscordWebhookManager
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
lateinit var customGameManager: CustomGameManager
|
||||||
|
private set
|
||||||
|
|
||||||
override fun onLoad()
|
override fun onLoad()
|
||||||
{
|
{
|
||||||
instance = this
|
instance = this
|
||||||
|
|
||||||
|
customGameManager = CustomGameManager( this )
|
||||||
|
customGameManager.load()
|
||||||
|
|
||||||
saveDefaultConfig()
|
saveDefaultConfig()
|
||||||
reloadConfig()
|
reloadConfig()
|
||||||
|
|
||||||
@@ -117,6 +125,7 @@ class SpeedHG : JavaPlugin() {
|
|||||||
kitManager.registerKit( GoblinKit() )
|
kitManager.registerKit( GoblinKit() )
|
||||||
kitManager.registerKit( IceMageKit() )
|
kitManager.registerKit( IceMageKit() )
|
||||||
kitManager.registerKit( RattlesnakeKit() )
|
kitManager.registerKit( RattlesnakeKit() )
|
||||||
|
kitManager.registerKit( TheWorldKit() )
|
||||||
kitManager.registerKit( VenomKit() )
|
kitManager.registerKit( VenomKit() )
|
||||||
kitManager.registerKit( VoodooKit() )
|
kitManager.registerKit( VoodooKit() )
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package club.mcscrims.speedhg.config
|
||||||
|
|
||||||
|
import club.mcscrims.speedhg.SpeedHG
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
class CustomGameManager(
|
||||||
|
private val plugin: SpeedHG
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val json = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
isLenient = true
|
||||||
|
encodeDefaults = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings: CustomGameSettings = CustomGameSettings()
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun load()
|
||||||
|
{
|
||||||
|
val raw = System.getenv("SPEEDHG_CUSTOM_SETTINGS")
|
||||||
|
|
||||||
|
settings = if ( raw.isNullOrBlank() ) {
|
||||||
|
plugin.logger.info("[CustomGameManager] Keine SPEEDHG_CUSTOM_SETTINGS gefunden - nutze Defaults.")
|
||||||
|
CustomGameSettings()
|
||||||
|
} else {
|
||||||
|
runCatching { json.decodeFromString<CustomGameSettings>( raw ) }
|
||||||
|
.onSuccess { plugin.logger.info( "[CustomGameManager] Settings geladen." ) }
|
||||||
|
.onFailure { plugin.logger.severe( "[CustomGameManager] Parse-Fehler: ${it.message}" ) }
|
||||||
|
.getOrDefault( CustomGameSettings() )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package club.mcscrims.speedhg.config
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CustomGameSettings(
|
||||||
|
val game: GameSettings = GameSettings(),
|
||||||
|
val kits: KitSettings = KitSettings()
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GameSettings(
|
||||||
|
@SerialName("min_players") val minPlayers: Int = 2,
|
||||||
|
@SerialName("lobby_time") val lobbyTime: Int = 60,
|
||||||
|
@SerialName("invincibility_time") val invincibilityTime: Int = 60,
|
||||||
|
@SerialName("border_start") val borderStart: Double = 300.0,
|
||||||
|
@SerialName("border_end") val borderEnd: Double = 20.0,
|
||||||
|
@SerialName("border_shrink_time") val borderShrinkTime: Long = 600L
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class KitSettings(
|
||||||
|
|
||||||
|
/** Globaler Fallback – gilt für alle Kits, die keinen eigenen Wert setzen. */
|
||||||
|
@SerialName("global_hits_required")
|
||||||
|
val globalHitsRequired: Int = 15,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kit-spezifische Overrides.
|
||||||
|
* Key = Kit.id (z. B. "gladiator", "venom").
|
||||||
|
* Unbekannte Keys werden von kotlinx.serialization ignoriert.
|
||||||
|
*/
|
||||||
|
val kits: Map<String, KitOverride> = emptyMap()
|
||||||
|
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Gibt den hitsRequired-Wert für ein Kit zurück.
|
||||||
|
* Priorität: kit-spezifisch > global > hardcoded Default
|
||||||
|
*/
|
||||||
|
fun hitsRequired(kitId: String, hardcodedDefault: Int): Int =
|
||||||
|
kits[kitId]?.hitsRequired ?: globalHitsRequired.takeIf { it >= 0 } ?: hardcodedDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
// Kit-spezifische Override-Klassen
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gemeinsamer Wrapper für alle Kit-Overrides.
|
||||||
|
* `hitsRequired = -1` bedeutet "nicht gesetzt, nutze Global/Default".
|
||||||
|
* `extra` nimmt beliebige zusätzliche Kit-Felder auf, ohne dass
|
||||||
|
* für jedes Kit eine eigene Klasse notwendig ist.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class KitOverride(
|
||||||
|
@SerialName("hits_required") val hitsRequired: Int = -1,
|
||||||
|
|
||||||
|
// Goblin
|
||||||
|
@SerialName("steal_duration_seconds") val stealDuration: Int = 60,
|
||||||
|
@SerialName("bunker_radius") val bunkerRadius: Double = 10.0,
|
||||||
|
|
||||||
|
// Gladiator
|
||||||
|
@SerialName("arena_radius") val arenaRadius: Int = 23,
|
||||||
|
@SerialName("arena_height") val arenaHeight: Int = 10,
|
||||||
|
@SerialName("wither_after_seconds") val witherAfterSeconds: Int = 180,
|
||||||
|
|
||||||
|
// Venom
|
||||||
|
@SerialName("shield_duration_ticks") val shieldDurationTicks: Long = 160L,
|
||||||
|
@SerialName("shield_capacity") val shieldCapacity: Double = 15.0,
|
||||||
|
|
||||||
|
// Voodoo
|
||||||
|
@SerialName("curse_duration_ms") val curseDurationMs: Long = 15_000L,
|
||||||
|
|
||||||
|
// BlackPanther
|
||||||
|
@SerialName("fist_mode_ms") val fistModeDurationMs: Long = 12_000L,
|
||||||
|
@SerialName("push_bonus_damage") val pushBonusDamage: Double = 4.0,
|
||||||
|
@SerialName("push_radius") val pushRadius: Double = 5.0,
|
||||||
|
@SerialName("pounce_min_fall") val pounceMinFall: Float = 3.0f,
|
||||||
|
@SerialName("pounce_radius") val pounceRadius: Double = 3.0,
|
||||||
|
@SerialName("pounce_damage") val pounceDamage: Double = 6.0,
|
||||||
|
|
||||||
|
// Rattlesnake
|
||||||
|
@SerialName("pounce_cooldown_ms") val pounceCooldownMs: Long = 20_000L,
|
||||||
|
@SerialName("pounce_max_sneak_ms") val pounceMaxSneakMs: Long = 3_000L,
|
||||||
|
@SerialName("pounce_min_range") val pounceMinRange: Double = 3.0,
|
||||||
|
@SerialName("pounce_max_range") val pounceMaxRange: Double = 10.0,
|
||||||
|
@SerialName("pounce_timeout_ticks") val pounceTimeoutTicks: Long = 30L,
|
||||||
|
|
||||||
|
// TheWorld
|
||||||
|
@SerialName("tw_ability_cooldown_ms") val abilityCooldownMs: Long = 20_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,
|
||||||
|
@SerialName("tw_freeze_duration_ticks") val freezeDurationTicks: Int = 200,
|
||||||
|
@SerialName("tw_max_hits_on_frozen") val maxHitsOnFrozen: Int = 5
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -99,7 +99,11 @@ class KitManager(
|
|||||||
val playstyle = getSelectedPlaystyle( player )
|
val playstyle = getSelectedPlaystyle( player )
|
||||||
val active = kit.getActiveAbility( playstyle )
|
val active = kit.getActiveAbility( playstyle )
|
||||||
|
|
||||||
chargeData[player.uniqueId] = PlayerChargeData( active.hitsRequired )
|
// Settings einmalig in die Ability cachen
|
||||||
|
active.cacheHitsRequired( plugin.customGameManager.settings )
|
||||||
|
|
||||||
|
val chargeData = PlayerChargeData( active.hitsRequired )
|
||||||
|
this.chargeData[ player.uniqueId ] = chargeData
|
||||||
|
|
||||||
kit.onAssign( player, playstyle )
|
kit.onAssign( player, playstyle )
|
||||||
kit.getPassiveAbility( playstyle ).onActivate( player )
|
kit.getPassiveAbility( playstyle ).onActivate( player )
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package club.mcscrims.speedhg.kit.ability
|
package club.mcscrims.speedhg.kit.ability
|
||||||
|
|
||||||
|
import club.mcscrims.speedhg.config.CustomGameSettings
|
||||||
import club.mcscrims.speedhg.kit.Playstyle
|
import club.mcscrims.speedhg.kit.Playstyle
|
||||||
import org.bukkit.Material
|
import org.bukkit.Material
|
||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
@@ -24,18 +25,43 @@ abstract class ActiveAbility(
|
|||||||
abstract val name: String
|
abstract val name: String
|
||||||
abstract val description: String
|
abstract val description: String
|
||||||
|
|
||||||
/**
|
|
||||||
* Melee hits required to recharge after one use.
|
|
||||||
* Set to 0 for an unlimited / always-ready ability (e.g. in debug kits).
|
|
||||||
*/
|
|
||||||
abstract val hitsRequired: Int
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The item material that triggers this ability on right-click.
|
* The item material that triggers this ability on right-click.
|
||||||
* The item must be in the player's main hand.
|
* The item must be in the player's main hand.
|
||||||
*/
|
*/
|
||||||
abstract val triggerMaterial: Material
|
abstract val triggerMaterial: Material
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der hardcodierte Default dieses spezifischen Kits/Playstyles.
|
||||||
|
* Wird nur als letzter Fallback genutzt.
|
||||||
|
*/
|
||||||
|
protected abstract val hardcodedHitsRequired: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Kit-ID, unter der Settings nachgeschlagen werden.
|
||||||
|
* Muss von der äußeren Kit-Klasse gesetzt werden.
|
||||||
|
*/
|
||||||
|
abstract val kitId: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gecachter Wert – wird einmalig in [cacheHitsRequired] berechnet
|
||||||
|
* und dann O(1) gelesen. Initialisiert mit dem Hardcoded-Default
|
||||||
|
* als Safety-Net falls cacheHitsRequired() nie aufgerufen wird.
|
||||||
|
*/
|
||||||
|
private var _hitsRequired: Int = -1
|
||||||
|
|
||||||
|
val hitsRequired: Int
|
||||||
|
get() = _hitsRequired.takeIf { it >= 0 } ?: hardcodedHitsRequired
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Einmalig beim applyKit() aufgerufen – danach ist der Wert gecacht.
|
||||||
|
*/
|
||||||
|
fun cacheHitsRequired(
|
||||||
|
settings: CustomGameSettings
|
||||||
|
) {
|
||||||
|
_hitsRequired = settings.kits.hitsRequired( kitId, hardcodedHitsRequired )
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the ability. Called only when [PlayerChargeData.isReady] is true.
|
* Execute the ability. Called only when [PlayerChargeData.isReady] is true.
|
||||||
* The dispatcher has already called [PlayerChargeData.consume] before this runs.
|
* The dispatcher has already called [PlayerChargeData.consume] before this runs.
|
||||||
|
|||||||
@@ -187,10 +187,11 @@ class ArmorerKit : Kit() {
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
inner class NoActive(playstyle: Playstyle) : ActiveAbility(playstyle) {
|
inner class NoActive(playstyle: Playstyle) : ActiveAbility(playstyle) {
|
||||||
|
override val kitId: String = "armorer"
|
||||||
override val name = "None"
|
override val name = "None"
|
||||||
override val description = "None"
|
override val description = "None"
|
||||||
override val hitsRequired = 0
|
|
||||||
override val triggerMaterial = Material.BARRIER
|
override val triggerMaterial = Material.BARRIER
|
||||||
|
override val hardcodedHitsRequired: Int = 0
|
||||||
override fun execute(player: Player) = AbilityResult.Success
|
override fun execute(player: Player) = AbilityResult.Success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,13 +61,16 @@ class BackupKit : Kit() {
|
|||||||
|
|
||||||
private class NoActive( playstyle: Playstyle ) : ActiveAbility( playstyle ) {
|
private class NoActive( playstyle: Playstyle ) : ActiveAbility( playstyle ) {
|
||||||
|
|
||||||
|
override val kitId: String
|
||||||
|
get() = "backup"
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = "None"
|
get() = "None"
|
||||||
|
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = "None"
|
get() = "None"
|
||||||
|
|
||||||
override val hitsRequired: Int
|
override val hardcodedHitsRequired: Int
|
||||||
get() = 0
|
get() = 0
|
||||||
|
|
||||||
override val triggerMaterial: Material
|
override val triggerMaterial: Material
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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
|
||||||
@@ -61,14 +62,18 @@ class BlackPantherKit : Kit()
|
|||||||
|
|
||||||
companion object
|
companion object
|
||||||
{
|
{
|
||||||
|
private val kitOverride get() =
|
||||||
|
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 const val FIST_MODE_MS = 12_000L // 12 seconds
|
private val FIST_MODE_MS = kitOverride.fistModeDurationMs // 12 seconds
|
||||||
private const val PUSH_RADIUS = 5.0
|
private val PUSH_RADIUS = kitOverride.pushRadius
|
||||||
private const val POUNCE_MIN_FALL = 3.0f
|
private val POUNCE_MIN_FALL = kitOverride.pounceMinFall
|
||||||
private const val POUNCE_RADIUS = 3.0
|
private val POUNCE_RADIUS = kitOverride.pounceRadius
|
||||||
private const val POUNCE_DAMAGE = 6.0 // 3 hearts = 6 HP
|
private val POUNCE_DAMAGE = kitOverride.pounceDamage // 3 hearts = 6 HP
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Cached ability instances ──────────────────────────────────────────────
|
// ── Cached ability instances ──────────────────────────────────────────────
|
||||||
@@ -114,11 +119,13 @@ class BlackPantherKit : Kit()
|
|||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
|
override val kitId: String 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 hitsRequired = 15
|
|
||||||
override val triggerMaterial = Material.BLACK_DYE
|
override val triggerMaterial = Material.BLACK_DYE
|
||||||
|
|
||||||
override fun execute(player: Player): AbilityResult {
|
override fun execute(player: Player): AbilityResult {
|
||||||
@@ -184,9 +191,10 @@ class BlackPantherKit : Kit()
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
private class DefensiveActive : ActiveAbility(Playstyle.DEFENSIVE) {
|
private class DefensiveActive : ActiveAbility(Playstyle.DEFENSIVE) {
|
||||||
|
override val kitId: String = "blackpanther"
|
||||||
override val name = "None"
|
override val name = "None"
|
||||||
override val description = "None"
|
override val description = "None"
|
||||||
override val hitsRequired = 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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.KitMetaData
|
import club.mcscrims.speedhg.kit.KitMetaData
|
||||||
import club.mcscrims.speedhg.kit.Playstyle
|
import club.mcscrims.speedhg.kit.Playstyle
|
||||||
@@ -41,6 +42,10 @@ class GladiatorKit : Kit() {
|
|||||||
override val icon: Material
|
override val icon: Material
|
||||||
get() = Material.IRON_BARS
|
get() = Material.IRON_BARS
|
||||||
|
|
||||||
|
private val kitOverride get() =
|
||||||
|
plugin.customGameManager.settings.kits.kits["gladiator"]
|
||||||
|
?: CustomGameSettings.KitOverride()
|
||||||
|
|
||||||
// ── Cached ability instances (avoid allocating per event call) ────────────
|
// ── Cached ability instances (avoid allocating per event call) ────────────
|
||||||
private val aggressiveActive = AllActive( Playstyle.AGGRESSIVE )
|
private val aggressiveActive = AllActive( Playstyle.AGGRESSIVE )
|
||||||
private val defensiveActive = AllActive( Playstyle.DEFENSIVE )
|
private val defensiveActive = AllActive( Playstyle.DEFENSIVE )
|
||||||
@@ -95,13 +100,16 @@ class GladiatorKit : Kit() {
|
|||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
|
override val kitId: String
|
||||||
|
get() = "gladiator"
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.gladiator.items.ironBars.name" )
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.gladiator.items.ironBars.name" )
|
||||||
|
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.gladiator.items.ironBars.description" )
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.gladiator.items.ironBars.description" )
|
||||||
|
|
||||||
override val hitsRequired: Int
|
override val hardcodedHitsRequired: Int
|
||||||
get() = 15
|
get() = 15
|
||||||
|
|
||||||
override val triggerMaterial: Material
|
override val triggerMaterial: Material
|
||||||
@@ -118,8 +126,8 @@ class GladiatorKit : Kit() {
|
|||||||
lineOfSight.hasMetadata( KitMetaData.IN_GLADIATOR.getKey() ))
|
lineOfSight.hasMetadata( KitMetaData.IN_GLADIATOR.getKey() ))
|
||||||
return AbilityResult.ConditionNotMet( "Already in gladiator fight" )
|
return AbilityResult.ConditionNotMet( "Already in gladiator fight" )
|
||||||
|
|
||||||
val radius = 23
|
val radius = kitOverride.arenaRadius
|
||||||
val height = 10
|
val height = kitOverride.arenaHeight
|
||||||
|
|
||||||
player.setMetadata( KitMetaData.IN_GLADIATOR.getKey(), FixedMetadataValue( plugin, true ))
|
player.setMetadata( KitMetaData.IN_GLADIATOR.getKey(), FixedMetadataValue( plugin, true ))
|
||||||
lineOfSight.setMetadata( KitMetaData.IN_GLADIATOR.getKey(), FixedMetadataValue( plugin, true ))
|
lineOfSight.setMetadata( KitMetaData.IN_GLADIATOR.getKey(), FixedMetadataValue( plugin, true ))
|
||||||
@@ -209,7 +217,7 @@ class GladiatorKit : Kit() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private class GladiatorFight(
|
private inner class GladiatorFight(
|
||||||
val region: Region,
|
val region: Region,
|
||||||
val gladiator: Player,
|
val gladiator: Player,
|
||||||
val enemy: Player,
|
val enemy: Player,
|
||||||
@@ -254,7 +262,7 @@ class GladiatorKit : Kit() {
|
|||||||
{
|
{
|
||||||
timer++
|
timer++
|
||||||
|
|
||||||
if ( timer > 180 )
|
if ( timer > kitOverride.witherAfterSeconds )
|
||||||
{
|
{
|
||||||
gladiator.addPotionEffect(PotionEffect( PotionEffectType.WITHER, Int.MAX_VALUE, 2 ))
|
gladiator.addPotionEffect(PotionEffect( PotionEffectType.WITHER, Int.MAX_VALUE, 2 ))
|
||||||
enemy.addPotionEffect(PotionEffect( PotionEffectType.WITHER, Int.MAX_VALUE, 2 ))
|
enemy.addPotionEffect(PotionEffect( PotionEffectType.WITHER, Int.MAX_VALUE, 2 ))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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
|
||||||
@@ -35,6 +36,10 @@ class GoblinKit : Kit() {
|
|||||||
override val icon: Material
|
override val icon: Material
|
||||||
get() = Material.MOSSY_COBBLESTONE
|
get() = Material.MOSSY_COBBLESTONE
|
||||||
|
|
||||||
|
private val kitOverride get() =
|
||||||
|
plugin.customGameManager.settings.kits.kits["goblin"]
|
||||||
|
?: CustomGameSettings.KitOverride()
|
||||||
|
|
||||||
// ── Cached ability instances (avoid allocating per event call) ────────────
|
// ── Cached ability instances (avoid allocating per event call) ────────────
|
||||||
private val aggressiveActive = AggressiveActive()
|
private val aggressiveActive = AggressiveActive()
|
||||||
private val defensiveActive = DefensiveActive()
|
private val defensiveActive = DefensiveActive()
|
||||||
@@ -103,17 +108,20 @@ class GoblinKit : Kit() {
|
|||||||
items.forEach { player.inventory.remove( it ) }
|
items.forEach { player.inventory.remove( it ) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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() = "goblin"
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.steal.name" )
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.steal.name" )
|
||||||
|
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.steal.description" )
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.steal.description" )
|
||||||
|
|
||||||
override val hitsRequired: Int
|
override val hardcodedHitsRequired: Int
|
||||||
get() = 15
|
get() = 15
|
||||||
|
|
||||||
override val triggerMaterial: Material
|
override val triggerMaterial: Material
|
||||||
@@ -151,7 +159,7 @@ class GoblinKit : Kit() {
|
|||||||
plugin.kitManager.selectPlaystyle( player, currentPlaystyle )
|
plugin.kitManager.selectPlaystyle( player, currentPlaystyle )
|
||||||
plugin.kitManager.applyKit( player )
|
plugin.kitManager.applyKit( player )
|
||||||
}
|
}
|
||||||
}, 20L * 60)
|
}, 20L * kitOverride.stealDuration)
|
||||||
|
|
||||||
activeStealTasks[ player.uniqueId ] = task
|
activeStealTasks[ player.uniqueId ] = task
|
||||||
|
|
||||||
@@ -176,17 +184,20 @@ class GoblinKit : Kit() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DefensiveActive : ActiveAbility( Playstyle.DEFENSIVE ) {
|
private inner class DefensiveActive : ActiveAbility( Playstyle.DEFENSIVE ) {
|
||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
|
override val kitId: String
|
||||||
|
get() = "goblin"
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.bunker.name" )
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.bunker.name" )
|
||||||
|
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.bunker.description" )
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.goblin.items.bunker.description" )
|
||||||
|
|
||||||
override val hitsRequired: Int
|
override val hardcodedHitsRequired: Int
|
||||||
get() = 15
|
get() = 15
|
||||||
|
|
||||||
override val triggerMaterial: Material
|
override val triggerMaterial: Material
|
||||||
@@ -202,7 +213,7 @@ class GoblinKit : Kit() {
|
|||||||
WorldEditUtils.createSphere(
|
WorldEditUtils.createSphere(
|
||||||
world,
|
world,
|
||||||
location,
|
location,
|
||||||
10.0,
|
kitOverride.bunkerRadius,
|
||||||
false,
|
false,
|
||||||
Material.MOSSY_COBBLESTONE
|
Material.MOSSY_COBBLESTONE
|
||||||
)
|
)
|
||||||
@@ -211,7 +222,7 @@ class GoblinKit : Kit() {
|
|||||||
WorldEditUtils.createSphere(
|
WorldEditUtils.createSphere(
|
||||||
world,
|
world,
|
||||||
location,
|
location,
|
||||||
10.0,
|
kitOverride.bunkerRadius,
|
||||||
false,
|
false,
|
||||||
Material.AIR
|
Material.AIR
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -91,14 +91,17 @@ class IceMageKit : Kit() {
|
|||||||
|
|
||||||
private class AggressiveActive : ActiveAbility( Playstyle.AGGRESSIVE ) {
|
private class AggressiveActive : ActiveAbility( Playstyle.AGGRESSIVE ) {
|
||||||
|
|
||||||
|
override val kitId: String
|
||||||
|
get() = "icemage"
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = "None"
|
get() = "None"
|
||||||
|
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = "None"
|
get() = "None"
|
||||||
|
|
||||||
override val hitsRequired: Int
|
override val hardcodedHitsRequired: Int
|
||||||
get() = 0
|
get() = 15
|
||||||
|
|
||||||
override val triggerMaterial: Material
|
override val triggerMaterial: Material
|
||||||
get() = Material.BARRIER
|
get() = Material.BARRIER
|
||||||
@@ -116,13 +119,16 @@ class IceMageKit : Kit() {
|
|||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
|
override val kitId: String
|
||||||
|
get() = "icemage"
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.icemage.items.snowball.name" )
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.icemage.items.snowball.name" )
|
||||||
|
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.icemage.items.snowball.description" )
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.icemage.items.snowball.description" )
|
||||||
|
|
||||||
override val hitsRequired: Int
|
override val hardcodedHitsRequired: Int
|
||||||
get() = 15
|
get() = 15
|
||||||
|
|
||||||
override val triggerMaterial: Material
|
override val triggerMaterial: Material
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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
|
||||||
@@ -56,11 +57,15 @@ class RattlesnakeKit : Kit() {
|
|||||||
internal val lastPounceUse: MutableMap<UUID, Long> = ConcurrentHashMap()
|
internal val lastPounceUse: MutableMap<UUID, Long> = ConcurrentHashMap()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val POUNCE_COOLDOWN_MS = 20_000L
|
private val kitOverride get() =
|
||||||
private const val MAX_SNEAK_MS = 3_000L
|
SpeedHG.instance.customGameManager.settings.kits.kits["rattlesnake"]
|
||||||
private const val MIN_RANGE = 3.0
|
?: CustomGameSettings.KitOverride()
|
||||||
private const val MAX_RANGE = 10.0
|
|
||||||
private const val POUNCE_TIMEOUT_TICKS = 30L // 1.5 s
|
private val POUNCE_COOLDOWN_MS = kitOverride.pounceCooldownMs
|
||||||
|
private val MAX_SNEAK_MS = kitOverride.pounceMaxSneakMs
|
||||||
|
private val MIN_RANGE = kitOverride.pounceMinRange
|
||||||
|
private val MAX_RANGE = kitOverride.pounceMaxRange
|
||||||
|
private val POUNCE_TIMEOUT_TICKS = kitOverride.pounceTimeoutTicks
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Cached ability instances ──────────────────────────────────────────────
|
// ── Cached ability instances ──────────────────────────────────────────────
|
||||||
@@ -117,11 +122,15 @@ class RattlesnakeKit : Kit() {
|
|||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
|
override val kitId: String
|
||||||
|
get() = "rattlesnake"
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.rattlesnake.items.pounce.name")
|
get() = plugin.languageManager.getDefaultRawMessage("kits.rattlesnake.items.pounce.name")
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.rattlesnake.items.pounce.description")
|
get() = plugin.languageManager.getDefaultRawMessage("kits.rattlesnake.items.pounce.description")
|
||||||
override val hitsRequired = 0 // charged via sneaking, not hits
|
override val hardcodedHitsRequired: Int
|
||||||
|
get() = 0
|
||||||
override val triggerMaterial = Material.SLIME_BALL
|
override val triggerMaterial = Material.SLIME_BALL
|
||||||
|
|
||||||
override fun execute(player: Player): AbilityResult {
|
override fun execute(player: Player): AbilityResult {
|
||||||
@@ -235,9 +244,10 @@ class RattlesnakeKit : Kit() {
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
private class DefensiveActive : ActiveAbility(Playstyle.DEFENSIVE) {
|
private class DefensiveActive : ActiveAbility(Playstyle.DEFENSIVE) {
|
||||||
|
override val kitId: String = "rattlesnake"
|
||||||
override val name = "None"
|
override val name = "None"
|
||||||
override val description = "None"
|
override val description = "None"
|
||||||
override val hitsRequired = 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,231 +0,0 @@
|
|||||||
package club.mcscrims.speedhg.kit.impl
|
|
||||||
|
|
||||||
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 net.kyori.adventure.text.Component
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor
|
|
||||||
import org.bukkit.Material
|
|
||||||
import org.bukkit.Sound
|
|
||||||
import org.bukkit.entity.Player
|
|
||||||
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
|
||||||
import org.bukkit.inventory.ItemStack
|
|
||||||
import org.bukkit.potion.PotionEffect
|
|
||||||
import org.bukkit.potion.PotionEffectType
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ──────────────────────────────────────────────────────────────────────────────
|
|
||||||
* TEMPLATE KIT — reference implementation, do not use in production
|
|
||||||
* ──────────────────────────────────────────────────────────────────────────────
|
|
||||||
*
|
|
||||||
* Copy this file, rename the class, change the abilities, and register your kit:
|
|
||||||
* ```kotlin
|
|
||||||
* // In SpeedHG.onEnable():
|
|
||||||
* kitManager.registerKit(YourKit())
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* ## Playstyle overview (Template)
|
|
||||||
*
|
|
||||||
* | Playstyle | Active | Passive |
|
|
||||||
* |-------------|----------------------------|----------------------------|
|
|
||||||
* | AGGRESSIVE | Power Strike (10 hits) | Bloodlust – speed on hit |
|
|
||||||
* | DEFENSIVE | Iron Skin (5 hits) | Fortitude – 10% dmg reduc. |
|
|
||||||
*/
|
|
||||||
class TemplateKit : Kit() {
|
|
||||||
|
|
||||||
override val id = "template"
|
|
||||||
override val displayName: Component = Component.text("Template Kit", NamedTextColor.YELLOW)
|
|
||||||
override val lore = listOf("An example kit.", "Replace with your own.")
|
|
||||||
override val icon = Material.NETHER_STAR
|
|
||||||
|
|
||||||
// ── Cached ability instances (avoid allocating per event call) ────────────
|
|
||||||
private val aggressiveActive = AggressiveActive()
|
|
||||||
private val defensiveActive = DefensiveActive()
|
|
||||||
private val aggressivePassive = AggressivePassive()
|
|
||||||
private val defensivePassive = DefensivePassive()
|
|
||||||
|
|
||||||
// ── Playstyle routing ─────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
override fun getActiveAbility(playstyle: Playstyle): ActiveAbility = when (playstyle) {
|
|
||||||
Playstyle.AGGRESSIVE -> aggressiveActive
|
|
||||||
Playstyle.DEFENSIVE -> defensiveActive
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPassiveAbility(playstyle: Playstyle): PassiveAbility = when (playstyle) {
|
|
||||||
Playstyle.AGGRESSIVE -> aggressivePassive
|
|
||||||
Playstyle.DEFENSIVE -> defensivePassive
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Item distribution ─────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
override val cachedItems = ConcurrentHashMap<UUID, List<ItemStack>>()
|
|
||||||
|
|
||||||
override fun giveItems(player: Player, playstyle: Playstyle) {
|
|
||||||
// Slot 8 = ability trigger item (always present)
|
|
||||||
player.inventory.setItem(8, ItemStack(Material.BLAZE_ROD))
|
|
||||||
|
|
||||||
when (playstyle) {
|
|
||||||
Playstyle.AGGRESSIVE -> {
|
|
||||||
// e.g. extra offensive item
|
|
||||||
}
|
|
||||||
Playstyle.DEFENSIVE -> {
|
|
||||||
// e.g. extra defensive item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Optional lifecycle hooks ──────────────────────────────────────────────
|
|
||||||
|
|
||||||
override fun onAssign(player: Player, playstyle: Playstyle) {
|
|
||||||
// Example: a kit that always gives permanent speed I
|
|
||||||
// player.addPotionEffect(PotionEffect(PotionEffectType.SPEED, Int.MAX_VALUE, 0, false, false, true))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRemove(player: Player) {
|
|
||||||
// Undo anything done in onAssign
|
|
||||||
// player.removePotionEffect(PotionEffectType.SPEED)
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// AGGRESSIVE active ability
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Power Strike — marks the player so their next hit deals 1.5× damage.
|
|
||||||
*
|
|
||||||
* Demonstrates:
|
|
||||||
* - Returning [AbilityResult.Success]
|
|
||||||
* - Returning [AbilityResult.ConditionNotMet] (no example here, but shown below)
|
|
||||||
* - [onFullyCharged] for charge-complete feedback
|
|
||||||
*/
|
|
||||||
private inner class AggressiveActive : ActiveAbility(Playstyle.AGGRESSIVE) {
|
|
||||||
|
|
||||||
override val name = "Power Strike"
|
|
||||||
override val description = "Your next melee attack deals 1.5× damage."
|
|
||||||
override val hitsRequired = 10
|
|
||||||
override val triggerMaterial = Material.BLAZE_ROD
|
|
||||||
|
|
||||||
// In a real kit, store pending-strike players in a companion Set<UUID>
|
|
||||||
// and apply the bonus in onHitEnemy / a damage event listener.
|
|
||||||
|
|
||||||
override fun execute(player: Player): AbilityResult {
|
|
||||||
// Example: guard clause returning ConditionNotMet
|
|
||||||
// val nearbyEnemies = player.getNearbyEntities(10.0, 10.0, 10.0).filterIsInstance<Player>()
|
|
||||||
// if (nearbyEnemies.isEmpty()) return AbilityResult.ConditionNotMet("No enemies nearby!")
|
|
||||||
|
|
||||||
// TODO: add player.uniqueId to a "powerStrikePending" set
|
|
||||||
|
|
||||||
player.playSound(player.location, Sound.ENTITY_BLAZE_SHOOT, 1f, 1.2f)
|
|
||||||
player.sendActionBar(Component.text("⚔ Power Strike ready!", NamedTextColor.RED))
|
|
||||||
return AbilityResult.Success
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFullyCharged(player: Player) {
|
|
||||||
player.playSound(player.location, Sound.BLOCK_ANVIL_USE, 0.8f, 1.5f)
|
|
||||||
player.sendActionBar(Component.text("⚡ Ability recharged!", NamedTextColor.GREEN))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// DEFENSIVE active ability
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iron Skin — grants Resistance I for 4 seconds.
|
|
||||||
*
|
|
||||||
* Demonstrates a simpler ability with fewer required hits (5 vs 10).
|
|
||||||
*/
|
|
||||||
private inner class DefensiveActive : ActiveAbility(Playstyle.DEFENSIVE) {
|
|
||||||
|
|
||||||
override val name = "Iron Skin"
|
|
||||||
override val description = "Gain Resistance I for 4 seconds."
|
|
||||||
override val hitsRequired = 5
|
|
||||||
override val triggerMaterial = Material.BLAZE_ROD
|
|
||||||
|
|
||||||
override fun execute(player: Player): AbilityResult {
|
|
||||||
player.addPotionEffect(
|
|
||||||
PotionEffect(
|
|
||||||
PotionEffectType.RESISTANCE,
|
|
||||||
/* duration */ 4 * 20,
|
|
||||||
/* amplifier */ 0,
|
|
||||||
/* ambient */ false,
|
|
||||||
/* particles */ false,
|
|
||||||
/* icon */ true
|
|
||||||
)
|
|
||||||
)
|
|
||||||
player.playSound(player.location, Sound.ITEM_TOTEM_USE, 0.8f, 1.5f)
|
|
||||||
player.sendActionBar(Component.text("🛡 Iron Skin active!", NamedTextColor.AQUA))
|
|
||||||
return AbilityResult.Success
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFullyCharged(player: Player) {
|
|
||||||
player.playSound(player.location, Sound.BLOCK_ANVIL_USE, 0.8f, 1.5f)
|
|
||||||
player.sendActionBar(Component.text("⚡ Ability recharged!", NamedTextColor.GREEN))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// AGGRESSIVE passive — Bloodlust
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bloodlust — grants Speed I for 2 seconds after landing a hit.
|
|
||||||
*
|
|
||||||
* Demonstrates [onHitEnemy] and [onActivate] / [onDeactivate] usage.
|
|
||||||
*/
|
|
||||||
private inner class AggressivePassive : PassiveAbility(Playstyle.AGGRESSIVE) {
|
|
||||||
|
|
||||||
override val name = "Bloodlust"
|
|
||||||
override val description = "Gain Speed I for 2 s after hitting an enemy."
|
|
||||||
|
|
||||||
override fun onActivate(player: Player) {
|
|
||||||
// Called once at game start.
|
|
||||||
// Start any repeating BukkitTasks here and store the returned BukkitTask
|
|
||||||
// so you can cancel it in onDeactivate. Example:
|
|
||||||
// task = Bukkit.getScheduler().runTaskTimer(plugin, { checkCooldowns(player) }, 0L, 10L)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDeactivate(player: Player) {
|
|
||||||
// task?.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Called AFTER the charge counter has already been incremented.
|
|
||||||
override fun onHitEnemy(attacker: Player, victim: Player, event: EntityDamageByEntityEvent) {
|
|
||||||
attacker.addPotionEffect(
|
|
||||||
PotionEffect(
|
|
||||||
PotionEffectType.SPEED,
|
|
||||||
/* duration */ 2 * 20,
|
|
||||||
/* amplifier */ 0,
|
|
||||||
/* ambient */ false,
|
|
||||||
/* particles */ false,
|
|
||||||
/* icon */ false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// DEFENSIVE passive — Fortitude
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fortitude — reduces all incoming melee damage by 10%.
|
|
||||||
*
|
|
||||||
* Demonstrates [onHitByEnemy] — the simplest passive pattern.
|
|
||||||
*/
|
|
||||||
private inner class DefensivePassive : PassiveAbility(Playstyle.DEFENSIVE) {
|
|
||||||
|
|
||||||
override val name = "Fortitude"
|
|
||||||
override val description = "Take 10% less damage from melee attacks."
|
|
||||||
|
|
||||||
// onActivate / onDeactivate are no-ops for this passive (default impl. is fine)
|
|
||||||
|
|
||||||
override fun onHitByEnemy(victim: Player, attacker: Player, event: EntityDamageByEntityEvent) {
|
|
||||||
event.damage *= 0.90
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
455
src/main/kotlin/club/mcscrims/speedhg/kit/impl/TheWorldKit.kt
Normal file
455
src/main/kotlin/club/mcscrims/speedhg/kit/impl/TheWorldKit.kt
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
package club.mcscrims.speedhg.kit.impl
|
||||||
|
|
||||||
|
import club.mcscrims.speedhg.SpeedHG
|
||||||
|
import club.mcscrims.speedhg.config.CustomGameSettings
|
||||||
|
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 org.bukkit.*
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
||||||
|
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.*
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ## TheWorld
|
||||||
|
*
|
||||||
|
* | Playstyle | Active | Passive |
|
||||||
|
* |-------------|-------------------------------------------------|------------------------------------------|
|
||||||
|
* | AGGRESSIVE | Shockwave + 3× Blink in looking direction | – |
|
||||||
|
* | DEFENSIVE | Shockwave + Freeze nearby enemies for 10 s | Hit-cap: frozen enemies survive max 5 hits|
|
||||||
|
*
|
||||||
|
* ### AGGRESSIVE active
|
||||||
|
* First use (off cooldown): radial shockwave → grants 3 blink charges.
|
||||||
|
* Each subsequent right-click: teleport up to [TELEPORT_RANGE] blocks in the
|
||||||
|
* player's looking direction (stops before solid blocks). After all 3 charges
|
||||||
|
* are spent, the 20 s cooldown begins.
|
||||||
|
*
|
||||||
|
* ### DEFENSIVE active
|
||||||
|
* Radial shockwave + [applyFreeze] on every nearby alive enemy. Each frozen
|
||||||
|
* enemy gets a 1-tick velocity-zeroing task for 10 s. The [DefensivePassive]
|
||||||
|
* monitors hits from this player on frozen enemies and unfreezes them after
|
||||||
|
* [MAX_HITS_ON_FROZEN] hits or when time expires.
|
||||||
|
*
|
||||||
|
* ### Why hitsRequired = 0?
|
||||||
|
* Both active abilities require full control over when [execute] fires. Using
|
||||||
|
* the built-in charge system (hitsRequired > 0) would block [execute] after
|
||||||
|
* the first use and prevent the blink/freeze logic from re-running per click.
|
||||||
|
* With hitsRequired = 0 the charge state stays READY permanently and
|
||||||
|
* [execute] is called on every right-click — internal cooldown maps govern
|
||||||
|
* actual recharge.
|
||||||
|
*/
|
||||||
|
class TheWorldKit : Kit() {
|
||||||
|
|
||||||
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
|
override val id = "theworld"
|
||||||
|
override val displayName: Component
|
||||||
|
get() = plugin.languageManager.getDefaultComponent("kits.theworld.name", mapOf())
|
||||||
|
override val lore: List<String>
|
||||||
|
get() = plugin.languageManager.getDefaultRawMessageList("kits.theworld.lore")
|
||||||
|
override val icon = Material.CLOCK
|
||||||
|
|
||||||
|
// ── Shared kit state ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggressive blink charges: playerUUID → remaining uses.
|
||||||
|
* Set to [MAX_TELEPORT_CHARGES] on first right-click, decremented per blink.
|
||||||
|
*/
|
||||||
|
internal val teleportCharges: MutableMap<UUID, Int> = ConcurrentHashMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active freezes: victimUUID → (attackerUUID, [FrozenData]).
|
||||||
|
* Tracked separately per attacker so [onRemove] only thaws enemies
|
||||||
|
* frozen by the leaving player.
|
||||||
|
*/
|
||||||
|
internal val frozenEnemies: MutableMap<UUID, Pair<UUID, FrozenData>> = ConcurrentHashMap()
|
||||||
|
|
||||||
|
data class FrozenData(
|
||||||
|
var hitsRemaining: Int,
|
||||||
|
val task: BukkitTask
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val kitOverride get() =
|
||||||
|
SpeedHG.instance.customGameManager.settings.kits.kits["theworld"]
|
||||||
|
?: CustomGameSettings.KitOverride()
|
||||||
|
|
||||||
|
private val ABILITY_COOLDOWN_MS = kitOverride.abilityCooldownMs
|
||||||
|
private val SHOCKWAVE_RADIUS = kitOverride.shockwaveRadius
|
||||||
|
private val TELEPORT_RANGE = kitOverride.teleportRange
|
||||||
|
private val MAX_TELEPORT_CHARGES = kitOverride.maxTeleportCharges
|
||||||
|
private val FREEZE_DURATION_TICKS = kitOverride.freezeDurationTicks
|
||||||
|
private val MAX_HITS_ON_FROZEN = kitOverride.maxHitsOnFrozen
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Cached ability instances ──────────────────────────────────────────────
|
||||||
|
private val aggressiveActive = AggressiveActive()
|
||||||
|
private val defensiveActive = DefensiveActive()
|
||||||
|
private val aggressivePassive = NoPassive(Playstyle.AGGRESSIVE)
|
||||||
|
private val defensivePassive = DefensivePassive()
|
||||||
|
|
||||||
|
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 active = getActiveAbility(playstyle)
|
||||||
|
val item = ItemBuilder(Material.CLOCK)
|
||||||
|
.name(active.name)
|
||||||
|
.lore(listOf(active.description))
|
||||||
|
.build()
|
||||||
|
cachedItems[player.uniqueId] = listOf(item)
|
||||||
|
player.inventory.addItem(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRemove(player: Player) {
|
||||||
|
teleportCharges.remove(player.uniqueId)
|
||||||
|
|
||||||
|
// Cancel tasks + thaw every enemy that was frozen by this player
|
||||||
|
val toUnfreeze = frozenEnemies.entries
|
||||||
|
.filter { (_, pair) -> pair.first == player.uniqueId }
|
||||||
|
.map { (victimUUID, pair) -> victimUUID to pair.second }
|
||||||
|
|
||||||
|
toUnfreeze.forEach { (victimUUID, data) ->
|
||||||
|
data.task.cancel()
|
||||||
|
frozenEnemies.remove(victimUUID)
|
||||||
|
Bukkit.getPlayer(victimUUID)?.clearFreezeEffects()
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedItems.remove(player.uniqueId)?.forEach { player.inventory.remove(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Shared helpers
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expanding ring of particles + radial knockback.
|
||||||
|
*
|
||||||
|
* The ring BukkitRunnable adds one ring per tick, radius grows by 1 block/tick.
|
||||||
|
* This gives the visual impression of a shockwave spreading outward.
|
||||||
|
*/
|
||||||
|
private fun doShockwave(origin: Player) {
|
||||||
|
val world = origin.world
|
||||||
|
|
||||||
|
// ── Visual: expanding particle ring ───────────────────────────────────
|
||||||
|
object : BukkitRunnable() {
|
||||||
|
var r = 0.5
|
||||||
|
override fun run() {
|
||||||
|
if (r > SHOCKWAVE_RADIUS + 1.0) { cancel(); return }
|
||||||
|
val steps = (2 * Math.PI * r * 5).toInt().coerceAtLeast(8)
|
||||||
|
for (i in 0 until steps) {
|
||||||
|
val angle = 2 * Math.PI * i / steps
|
||||||
|
val loc = origin.location.clone().add(cos(angle) * r, 1.0, sin(angle) * r)
|
||||||
|
world.spawnParticle(Particle.SWEEP_ATTACK, loc, 1, 0.0, 0.0, 0.0, 0.0)
|
||||||
|
world.spawnParticle(Particle.CRIT, loc, 2, 0.1, 0.1, 0.1, 0.0)
|
||||||
|
}
|
||||||
|
r += 1.0
|
||||||
|
}
|
||||||
|
}.runTaskTimer(plugin, 0L, 1L)
|
||||||
|
|
||||||
|
// ── Physics: knock all nearby alive enemies outward ───────────────────
|
||||||
|
world.getNearbyEntities(origin.location, SHOCKWAVE_RADIUS, SHOCKWAVE_RADIUS, SHOCKWAVE_RADIUS)
|
||||||
|
.filterIsInstance<Player>()
|
||||||
|
.filter { it != origin && plugin.gameManager.alivePlayers.contains(it.uniqueId) }
|
||||||
|
.forEach { enemy ->
|
||||||
|
val dir = enemy.location.toVector()
|
||||||
|
.subtract(origin.location.toVector())
|
||||||
|
.normalize()
|
||||||
|
.multiply(2.0)
|
||||||
|
.setY(0.45)
|
||||||
|
enemy.velocity = dir
|
||||||
|
}
|
||||||
|
|
||||||
|
world.playSound(origin.location, Sound.ENTITY_WARDEN_SONIC_BOOM, 1f, 0.8f)
|
||||||
|
world.playSound(origin.location, Sound.BLOCK_BEACON_ACTIVATE, 0.6f, 1.5f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teleports [player] up to [TELEPORT_RANGE] blocks in their looking direction.
|
||||||
|
* Raycast in 0.4-block increments — stops at the last non-solid block.
|
||||||
|
*/
|
||||||
|
private fun blink(player: Player) {
|
||||||
|
val dir = player.location.direction.normalize()
|
||||||
|
var target = player.eyeLocation.clone()
|
||||||
|
|
||||||
|
repeat((TELEPORT_RANGE / 0.4).toInt()) {
|
||||||
|
val next = target.clone().add(dir.clone().multiply(0.4))
|
||||||
|
if (next.block.type.isSolid) return@repeat
|
||||||
|
target = next
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust to feet position
|
||||||
|
target.y -= 1.0
|
||||||
|
target.yaw = player.location.yaw
|
||||||
|
target.pitch = player.location.pitch
|
||||||
|
|
||||||
|
player.teleport(target)
|
||||||
|
player.world.spawnParticle(Particle.PORTAL, target, 30, 0.2, 0.5, 0.2, 0.05)
|
||||||
|
player.world.spawnParticle(Particle.REVERSE_PORTAL, target, 12, 0.3, 0.5, 0.3, 0.0)
|
||||||
|
player.playSound(target, Sound.ENTITY_ENDERMAN_TELEPORT, 0.9f, 1.4f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immobilises [target], capping hits from [attacker] at [MAX_HITS_ON_FROZEN].
|
||||||
|
* A 1-tick repeating task zeros horizontal + upward velocity for 10 seconds.
|
||||||
|
*/
|
||||||
|
private fun applyFreeze(attacker: Player, target: Player) {
|
||||||
|
// Overwrite any existing freeze
|
||||||
|
frozenEnemies.remove(target.uniqueId)?.second?.task?.cancel()
|
||||||
|
|
||||||
|
target.applyFreezeEffects()
|
||||||
|
|
||||||
|
val task = object : BukkitRunnable() {
|
||||||
|
var ticks = 0
|
||||||
|
override fun run() {
|
||||||
|
ticks++
|
||||||
|
|
||||||
|
if (ticks >= FREEZE_DURATION_TICKS ||
|
||||||
|
!target.isOnline ||
|
||||||
|
!plugin.gameManager.alivePlayers.contains(target.uniqueId) ||
|
||||||
|
!frozenEnemies.containsKey(target.uniqueId))
|
||||||
|
{
|
||||||
|
doUnfreeze(target)
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero horizontal + upward velocity every tick
|
||||||
|
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 }
|
||||||
|
|
||||||
|
// Refresh slowness every second so it doesn't expire mid-freeze
|
||||||
|
if (ticks % 20 == 0) target.applyFreezeEffects()
|
||||||
|
|
||||||
|
// Powder-snow visual (cosmetic)
|
||||||
|
if (target.freezeTicks < 140) target.freezeTicks = 140
|
||||||
|
}
|
||||||
|
}.runTaskTimer(plugin, 0L, 1L)
|
||||||
|
|
||||||
|
frozenEnemies[target.uniqueId] = Pair(
|
||||||
|
attacker.uniqueId,
|
||||||
|
FrozenData(hitsRemaining = MAX_HITS_ON_FROZEN, task = task)
|
||||||
|
)
|
||||||
|
|
||||||
|
target.sendActionBar(target.trans("kits.theworld.messages.frozen_received"))
|
||||||
|
target.world.spawnParticle(Particle.SNOWFLAKE,
|
||||||
|
target.location.clone().add(0.0, 1.0, 0.0), 15, 0.3, 0.5, 0.3, 0.05)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doUnfreeze(target: Player) {
|
||||||
|
frozenEnemies.remove(target.uniqueId)
|
||||||
|
target.clearFreezeEffects()
|
||||||
|
if (target.isOnline)
|
||||||
|
target.sendActionBar(target.trans("kits.theworld.messages.frozen_expired"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Player extension helpers ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
private fun Player.applyFreezeEffects() {
|
||||||
|
addPotionEffect(PotionEffect(PotionEffectType.SLOWNESS,
|
||||||
|
/* duration */ 25,
|
||||||
|
/* amplifier */ 127,
|
||||||
|
/* ambient */ false,
|
||||||
|
/* particles */ false,
|
||||||
|
/* icon */ true))
|
||||||
|
addPotionEffect(PotionEffect(PotionEffectType.MINING_FATIGUE,
|
||||||
|
25, 127, false, false, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Player.clearFreezeEffects() {
|
||||||
|
removePotionEffect(PotionEffectType.SLOWNESS)
|
||||||
|
removePotionEffect(PotionEffectType.MINING_FATIGUE)
|
||||||
|
freezeTicks = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// AGGRESSIVE active – Shockwave → 3× blink
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
private inner class AggressiveActive : ActiveAbility(Playstyle.AGGRESSIVE) {
|
||||||
|
|
||||||
|
private val plugin get() = SpeedHG.instance
|
||||||
|
private val cooldowns: MutableMap<UUID, Long> = ConcurrentHashMap()
|
||||||
|
|
||||||
|
override val kitId: String
|
||||||
|
get() = "theworld"
|
||||||
|
|
||||||
|
override val name: String
|
||||||
|
get() = plugin.languageManager.getDefaultRawMessage(
|
||||||
|
"kits.theworld.items.clock.aggressive.name")
|
||||||
|
override val description: String
|
||||||
|
get() = plugin.languageManager.getDefaultRawMessage(
|
||||||
|
"kits.theworld.items.clock.aggressive.description")
|
||||||
|
override val hardcodedHitsRequired: Int
|
||||||
|
get() = 0
|
||||||
|
override val triggerMaterial = Material.CLOCK
|
||||||
|
|
||||||
|
override fun execute(player: Player): AbilityResult {
|
||||||
|
|
||||||
|
// ── Spend a blink charge if any are available ─────────────────────
|
||||||
|
val charges = teleportCharges[player.uniqueId] ?: 0
|
||||||
|
if (charges > 0) {
|
||||||
|
blink(player)
|
||||||
|
val remaining = charges - 1
|
||||||
|
teleportCharges[player.uniqueId] = remaining
|
||||||
|
|
||||||
|
if (remaining > 0)
|
||||||
|
player.sendActionBar(player.trans(
|
||||||
|
"kits.theworld.messages.teleport_charges",
|
||||||
|
mapOf("charges" to remaining.toString())))
|
||||||
|
else {
|
||||||
|
teleportCharges.remove(player.uniqueId)
|
||||||
|
player.sendActionBar(player.trans("kits.theworld.messages.charges_exhausted"))
|
||||||
|
}
|
||||||
|
return AbilityResult.Success
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Cooldown gate ─────────────────────────────────────────────────
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val lastUse = cooldowns[player.uniqueId] ?: 0L
|
||||||
|
if (now - lastUse < ABILITY_COOLDOWN_MS) {
|
||||||
|
val secsLeft = (ABILITY_COOLDOWN_MS - (now - lastUse)) / 1000
|
||||||
|
return AbilityResult.ConditionNotMet("Cooldown: ${secsLeft}s")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Shockwave + grant 3 blink charges ────────────────────────────
|
||||||
|
doShockwave(player)
|
||||||
|
teleportCharges[player.uniqueId] = MAX_TELEPORT_CHARGES
|
||||||
|
cooldowns[player.uniqueId] = now
|
||||||
|
|
||||||
|
player.sendActionBar(player.trans(
|
||||||
|
"kits.theworld.messages.shockwave_and_blink",
|
||||||
|
mapOf("charges" to MAX_TELEPORT_CHARGES.toString())))
|
||||||
|
|
||||||
|
return AbilityResult.Success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// DEFENSIVE active – Shockwave + freeze + 5-hit cap
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
private inner class DefensiveActive : ActiveAbility(Playstyle.DEFENSIVE) {
|
||||||
|
|
||||||
|
private val plugin get() = SpeedHG.instance
|
||||||
|
private val cooldowns: MutableMap<UUID, Long> = ConcurrentHashMap()
|
||||||
|
|
||||||
|
override val kitId: String
|
||||||
|
get() = "theworld"
|
||||||
|
|
||||||
|
override val name: String
|
||||||
|
get() = plugin.languageManager.getDefaultRawMessage(
|
||||||
|
"kits.theworld.items.clock.defensive.name")
|
||||||
|
override val description: String
|
||||||
|
get() = plugin.languageManager.getDefaultRawMessage(
|
||||||
|
"kits.theworld.items.clock.defensive.description")
|
||||||
|
override val hardcodedHitsRequired: Int
|
||||||
|
get() = 0
|
||||||
|
override val triggerMaterial = Material.CLOCK
|
||||||
|
|
||||||
|
override fun execute(player: Player): AbilityResult {
|
||||||
|
|
||||||
|
// ── Cooldown gate ─────────────────────────────────────────────────
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val lastUse = cooldowns[player.uniqueId] ?: 0L
|
||||||
|
if (now - lastUse < ABILITY_COOLDOWN_MS) {
|
||||||
|
val secsLeft = (ABILITY_COOLDOWN_MS - (now - lastUse)) / 1000
|
||||||
|
return AbilityResult.ConditionNotMet("Cooldown: ${secsLeft}s")
|
||||||
|
}
|
||||||
|
|
||||||
|
val targets = player.world
|
||||||
|
.getNearbyEntities(
|
||||||
|
player.location,
|
||||||
|
SHOCKWAVE_RADIUS, SHOCKWAVE_RADIUS, SHOCKWAVE_RADIUS)
|
||||||
|
.filterIsInstance<Player>()
|
||||||
|
.filter { it != player &&
|
||||||
|
plugin.gameManager.alivePlayers.contains(it.uniqueId) }
|
||||||
|
|
||||||
|
if (targets.isEmpty())
|
||||||
|
return AbilityResult.ConditionNotMet("No enemies within range!")
|
||||||
|
|
||||||
|
doShockwave(player)
|
||||||
|
targets.forEach { applyFreeze(player, it) }
|
||||||
|
cooldowns[player.uniqueId] = now
|
||||||
|
|
||||||
|
player.sendActionBar(player.trans(
|
||||||
|
"kits.theworld.messages.freeze_activated",
|
||||||
|
mapOf("count" to targets.size.toString())))
|
||||||
|
|
||||||
|
return AbilityResult.Success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// DEFENSIVE passive – 5-hit cap on frozen enemies
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
private inner class DefensivePassive : PassiveAbility(Playstyle.DEFENSIVE) {
|
||||||
|
|
||||||
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
|
override val name: String
|
||||||
|
get() = plugin.languageManager.getDefaultRawMessage(
|
||||||
|
"kits.theworld.passive.defensive.name")
|
||||||
|
override val description: String
|
||||||
|
get() = plugin.languageManager.getDefaultRawMessage(
|
||||||
|
"kits.theworld.passive.defensive.description")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called only when the TheWorld player (attacker) hits someone.
|
||||||
|
* If that someone is frozen and was frozen by this attacker,
|
||||||
|
* decrement their remaining hit allowance.
|
||||||
|
*/
|
||||||
|
override fun onHitEnemy(
|
||||||
|
attacker: Player,
|
||||||
|
victim: Player,
|
||||||
|
event: EntityDamageByEntityEvent
|
||||||
|
) {
|
||||||
|
val (frozenBy, data) = frozenEnemies[victim.uniqueId] ?: return
|
||||||
|
// Only count hits from the player who applied this specific freeze
|
||||||
|
if (frozenBy != attacker.uniqueId) return
|
||||||
|
|
||||||
|
data.hitsRemaining--
|
||||||
|
|
||||||
|
if (data.hitsRemaining <= 0) {
|
||||||
|
doUnfreeze(victim)
|
||||||
|
attacker.sendActionBar(attacker.trans("kits.theworld.messages.freeze_broken"))
|
||||||
|
} else {
|
||||||
|
attacker.sendActionBar(attacker.trans(
|
||||||
|
"kits.theworld.messages.freeze_hits_left",
|
||||||
|
mapOf("hits" to data.hitsRemaining.toString())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// AGGRESSIVE no-passive
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
private class NoPassive(playstyle: Playstyle) : PassiveAbility(playstyle) {
|
||||||
|
override val name = "None"
|
||||||
|
override val description = "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
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
|
||||||
@@ -48,6 +49,10 @@ class VenomKit : Kit() {
|
|||||||
override val icon: Material
|
override val icon: Material
|
||||||
get() = Material.SPIDER_EYE
|
get() = Material.SPIDER_EYE
|
||||||
|
|
||||||
|
private val kitOverride get() =
|
||||||
|
plugin.customGameManager.settings.kits.kits["venom"]
|
||||||
|
?: CustomGameSettings.KitOverride()
|
||||||
|
|
||||||
// ── Cached ability instances (avoid allocating per event call) ────────────
|
// ── Cached ability instances (avoid allocating per event call) ────────────
|
||||||
private val aggressiveActive = AggressiveActive()
|
private val aggressiveActive = AggressiveActive()
|
||||||
private val defensiveActive = DefensiveActive()
|
private val defensiveActive = DefensiveActive()
|
||||||
@@ -115,17 +120,20 @@ class VenomKit : Kit() {
|
|||||||
items.forEach { player.inventory.remove( it ) }
|
items.forEach { player.inventory.remove( it ) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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() = "venom"
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.items.wither.name" )
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.items.wither.name" )
|
||||||
|
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.items.wither.description" )
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.items.wither.description" )
|
||||||
|
|
||||||
override val hitsRequired: Int
|
override val hardcodedHitsRequired: Int
|
||||||
get() = 15
|
get() = 15
|
||||||
|
|
||||||
override val triggerMaterial: Material
|
override val triggerMaterial: Material
|
||||||
@@ -168,13 +176,16 @@ class VenomKit : Kit() {
|
|||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
|
override val kitId: String
|
||||||
|
get() = "venom"
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.items.shield.name" )
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.items.shield.name" )
|
||||||
|
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.items.shield.description" )
|
get() = plugin.languageManager.getDefaultRawMessage( "kits.venom.items.shield.description" )
|
||||||
|
|
||||||
override val hitsRequired: Int
|
override val hardcodedHitsRequired: Int
|
||||||
get() = 15
|
get() = 15
|
||||||
|
|
||||||
override val triggerMaterial: Material
|
override val triggerMaterial: Material
|
||||||
@@ -228,10 +239,10 @@ class VenomKit : Kit() {
|
|||||||
if (activeShields.containsKey( player.uniqueId ))
|
if (activeShields.containsKey( player.uniqueId ))
|
||||||
breakShield( player )
|
breakShield( player )
|
||||||
}
|
}
|
||||||
}.runTaskLater( plugin, 160L )
|
}.runTaskLater( plugin, kitOverride.shieldDurationTicks )
|
||||||
|
|
||||||
activeShields[ player.uniqueId ] = ActiveShield(
|
activeShields[ player.uniqueId ] = ActiveShield(
|
||||||
remainingCapacity = 15.0,
|
remainingCapacity = kitOverride.shieldCapacity,
|
||||||
expireTask = expireTask,
|
expireTask = expireTask,
|
||||||
particleTask = particleTask
|
particleTask = particleTask
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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
|
||||||
@@ -57,6 +58,10 @@ class VoodooKit : Kit() {
|
|||||||
/** Tracks active curses: victim UUID → System.currentTimeMillis() expiry. */
|
/** Tracks active curses: victim UUID → System.currentTimeMillis() expiry. */
|
||||||
internal val cursedExpiry: MutableMap<UUID, Long> = ConcurrentHashMap()
|
internal val cursedExpiry: MutableMap<UUID, Long> = ConcurrentHashMap()
|
||||||
|
|
||||||
|
private val kitOverride get() =
|
||||||
|
plugin.customGameManager.settings.kits.kits["voodoo"]
|
||||||
|
?: CustomGameSettings.KitOverride()
|
||||||
|
|
||||||
// ── Cached ability instances ──────────────────────────────────────────────
|
// ── Cached ability instances ──────────────────────────────────────────────
|
||||||
private val aggressiveActive = AggressiveActive()
|
private val aggressiveActive = AggressiveActive()
|
||||||
private val defensiveActive = DefensiveActive()
|
private val defensiveActive = DefensiveActive()
|
||||||
@@ -100,11 +105,15 @@ class VoodooKit : Kit() {
|
|||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
|
override val kitId: String
|
||||||
|
get() = "voodoo"
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.voodoo.items.root.name")
|
get() = plugin.languageManager.getDefaultRawMessage("kits.voodoo.items.root.name")
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.voodoo.items.root.description")
|
get() = plugin.languageManager.getDefaultRawMessage("kits.voodoo.items.root.description")
|
||||||
override val hitsRequired = 15
|
override val hardcodedHitsRequired: Int
|
||||||
|
get() = 15
|
||||||
override val triggerMaterial = Material.WITHER_ROSE
|
override val triggerMaterial = Material.WITHER_ROSE
|
||||||
|
|
||||||
override fun execute(player: Player): AbilityResult {
|
override fun execute(player: Player): AbilityResult {
|
||||||
@@ -164,11 +173,15 @@ class VoodooKit : Kit() {
|
|||||||
|
|
||||||
private val plugin get() = SpeedHG.instance
|
private val plugin get() = SpeedHG.instance
|
||||||
|
|
||||||
|
override val kitId: String
|
||||||
|
get() = "voodoo"
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.voodoo.items.curse.name")
|
get() = plugin.languageManager.getDefaultRawMessage("kits.voodoo.items.curse.name")
|
||||||
override val description: String
|
override val description: String
|
||||||
get() = plugin.languageManager.getDefaultRawMessage("kits.voodoo.items.curse.description")
|
get() = plugin.languageManager.getDefaultRawMessage("kits.voodoo.items.curse.description")
|
||||||
override val hitsRequired = 10
|
override val hardcodedHitsRequired: Int
|
||||||
|
get() = 10
|
||||||
override val triggerMaterial = Material.SOUL_TORCH
|
override val triggerMaterial = Material.SOUL_TORCH
|
||||||
|
|
||||||
override fun execute(player: Player): AbilityResult {
|
override fun execute(player: Player): AbilityResult {
|
||||||
@@ -180,7 +193,7 @@ class VoodooKit : Kit() {
|
|||||||
if (targets.isEmpty())
|
if (targets.isEmpty())
|
||||||
return AbilityResult.ConditionNotMet("No enemies within 8 blocks!")
|
return AbilityResult.ConditionNotMet("No enemies within 8 blocks!")
|
||||||
|
|
||||||
val expiry = System.currentTimeMillis() + 15_000L
|
val expiry = System.currentTimeMillis() + kitOverride.curseDurationMs
|
||||||
targets.forEach { t ->
|
targets.forEach { t ->
|
||||||
cursedExpiry[t.uniqueId] = expiry
|
cursedExpiry[t.uniqueId] = expiry
|
||||||
t.addPotionEffect(PotionEffect(PotionEffectType.GLOWING, 15 * 20, 0, false, true, false))
|
t.addPotionEffect(PotionEffect(PotionEffectType.GLOWING, 15 * 20, 0, false, true, false))
|
||||||
|
|||||||
@@ -282,4 +282,31 @@ kits:
|
|||||||
messages:
|
messages:
|
||||||
fist_mode_active: '<gray>⚡ Vibranium Fists active for 12 seconds!</gray>'
|
fist_mode_active: '<gray>⚡ Vibranium Fists active for 12 seconds!</gray>'
|
||||||
wakanda_impact: '<white>Wakanda Forever! Hit <count> enemy(s)!</white>'
|
wakanda_impact: '<white>Wakanda Forever! Hit <count> enemy(s)!</white>'
|
||||||
ability_charged: '<yellow>Ability recharged!</yellow>'
|
ability_charged: '<yellow>Ability recharged!</yellow>'
|
||||||
|
theworld:
|
||||||
|
name: '<gradient:dark_gray:white><bold>The World</bold></gradient>'
|
||||||
|
lore:
|
||||||
|
- ' '
|
||||||
|
- 'AGGRESSIVE: Shockwave + 3x Blink'
|
||||||
|
- 'DEFENSIVE: Shockwave + Freeze + 5-Hit Cap'
|
||||||
|
items:
|
||||||
|
clock:
|
||||||
|
aggressive:
|
||||||
|
name: '<gray>The World</gray>'
|
||||||
|
description: 'Shockwave → right-click 3x to blink in looking direction'
|
||||||
|
defensive:
|
||||||
|
name: '<gray>The World</gray>'
|
||||||
|
description: 'Shockwave + freeze nearby enemies (max 5 hits each)'
|
||||||
|
passive:
|
||||||
|
defensive:
|
||||||
|
name: '<aqua>Time Stop</aqua>'
|
||||||
|
description: 'Frozen enemies can only be hit 5 times'
|
||||||
|
messages:
|
||||||
|
shockwave_and_blink: '<gray>Shockwave! <white><charges> blink charge(s) ready.</white>'
|
||||||
|
teleport_charges: '<aqua>Blinked! <charges> charge(s) remaining.</aqua>'
|
||||||
|
charges_exhausted: '<red>All blink charges spent!</red>'
|
||||||
|
freeze_activated: '<aqua>⏸ Time stopped for <count> enemy(s)!</aqua>'
|
||||||
|
frozen_received: '<red>⏸ You are frozen for 10 seconds!</red>'
|
||||||
|
frozen_expired: '<gray>The freeze has worn off.</gray>'
|
||||||
|
freeze_broken: '<gold>Freeze broken — 5 hits reached!</gold>'
|
||||||
|
freeze_hits_left: '<aqua>Frozen enemy — <hits> hit(s) remaining.</aqua>'
|
||||||
Reference in New Issue
Block a user