Add BackupKit and mid-game kit selection

Introduce a BackupKit and allow players holding it to change kits mid-game while preventing normal kits from being picked once the game started. Changes:
- Add BackupKit implementation (kit with no abilities/items used for mid-round kit selection).
- Update KitCommand: import BackupKit and GameState, add isIngame helper, disallow normal kit picks during INGAME/INVINCIBILITY, permit BackupKit to swap kits mid-game (but prevent picking the same kit) and apply playstyles accordingly.
- Fix GoblinKit to also copy/restore the target's playstyle when stealing a kit and restore the player's original playstyle after timeout.
- Tweak IceMageKit slow effect chance (now ~1/3 chance using random.nextInt(3) < 1).
- Update en_US.yml: add messages and kit strings for backup and icemage, adjust ability_charged color, and add game/cannot-pick messages.

Also includes small import and formatting adjustments.
This commit is contained in:
TDSTOS
2026-03-25 17:47:42 +01:00
parent ea86272c01
commit e199ae24d4
5 changed files with 157 additions and 6 deletions

View File

@@ -1,7 +1,9 @@
package club.mcscrims.speedhg.command package club.mcscrims.speedhg.command
import club.mcscrims.speedhg.SpeedHG import club.mcscrims.speedhg.SpeedHG
import club.mcscrims.speedhg.game.GameState
import club.mcscrims.speedhg.kit.Playstyle import club.mcscrims.speedhg.kit.Playstyle
import club.mcscrims.speedhg.kit.impl.BackupKit
import club.mcscrims.speedhg.util.legacySerializer import club.mcscrims.speedhg.util.legacySerializer
import club.mcscrims.speedhg.util.sendMsg import club.mcscrims.speedhg.util.sendMsg
import org.bukkit.command.Command import org.bukkit.command.Command
@@ -52,6 +54,30 @@ class KitCommand : CommandExecutor, TabCompleter {
return true return true
} }
val playerKit = plugin.kitManager.getSelectedKit( player )
val isBackup = playerKit != null && playerKit is BackupKit
if ( !isBackup && isIngame() )
{
player.sendMsg("commands.kit.gameHasStarted")
return true
}
else if ( isBackup && isIngame() )
{
if ( kit is BackupKit )
{
player.sendMsg( "commands.kit.cannotPickSameKit" )
return true
}
plugin.kitManager.selectKit( player, kit )
plugin.kitManager.selectPlaystyle( player, playstyle )
plugin.kitManager.applyKit( player )
player.sendMsg( "commands.kit.selected", "playstyle" to playstyle.displayName, "kit" to legacySerializer.serialize( kit.displayName ))
return true
}
plugin.kitManager.selectKit( player, kit ) plugin.kitManager.selectKit( player, kit )
plugin.kitManager.selectPlaystyle( player, playstyle ) plugin.kitManager.selectPlaystyle( player, playstyle )
@@ -78,4 +104,10 @@ class KitCommand : CommandExecutor, TabCompleter {
return listOf() return listOf()
} }
private fun isIngame(): Boolean = when ( plugin.gameManager.currentState )
{
GameState.INGAME, GameState.INVINCIBILITY -> true
else -> false
}
} }

View File

@@ -0,0 +1,85 @@
package club.mcscrims.speedhg.kit.impl
import club.mcscrims.speedhg.SpeedHG
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 org.bukkit.Material
import org.bukkit.entity.Player
class BackupKit : Kit() {
private val plugin get() = SpeedHG.instance
override val id: String
get() = "backup"
override val displayName: Component
get() = plugin.languageManager.getDefaultComponent( "kits.backup.name", mapOf() )
override val lore: List<String>
get() = plugin.languageManager.getDefaultRawMessageList( "kits.backup.lore" )
override val icon: Material
get() = Material.CHEST
// ── Cached ability instances (avoid allocating per event call) ────────────
private val aggressiveNoActive = NoActive( Playstyle.AGGRESSIVE )
private val defensiveNoActive = NoActive( Playstyle.DEFENSIVE )
private val aggressiveNoPassive = NoPassive( Playstyle.AGGRESSIVE )
private val defensiveNoPassive = NoPassive( Playstyle.DEFENSIVE )
// ── Playstyle routing ─────────────────────────────────────────────────────
override fun getActiveAbility(
playstyle: Playstyle
): ActiveAbility = when( playstyle )
{
Playstyle.AGGRESSIVE -> aggressiveNoActive
Playstyle.DEFENSIVE -> defensiveNoActive
}
override fun getPassiveAbility(
playstyle: Playstyle
): PassiveAbility = when( playstyle )
{
Playstyle.AGGRESSIVE -> aggressiveNoPassive
Playstyle.DEFENSIVE -> defensiveNoPassive
}
// ── Item distribution ─────────────────────────────────────────────────────
override fun giveItems( player: Player, playstyle: Playstyle ) {}
private class NoActive( playstyle: Playstyle ) : ActiveAbility( playstyle ) {
override val name: String
get() = "None"
override val description: String
get() = "None"
override val hitsRequired: Int
get() = 0
override val triggerMaterial: Material
get() = Material.BARRIER
override fun execute( player: Player ): AbilityResult { return AbilityResult.Success }
}
private class NoPassive( playstyle: Playstyle ) : PassiveAbility( playstyle ) {
override val name: String
get() = "None"
override val description: String
get() = "None"
}
}

View File

@@ -129,16 +129,17 @@ class GoblinKit : Kit() {
val lineOfSight = player.getTargetEntity( 3 ) as? Player val lineOfSight = player.getTargetEntity( 3 ) as? Player
?: return AbilityResult.ConditionNotMet( "No player in line of sight" ) ?: return AbilityResult.ConditionNotMet( "No player in line of sight" )
val targetKit = plugin.kitManager.getSelectedKit( lineOfSight ) val targetKit = plugin.kitManager.getSelectedKit( lineOfSight ) ?: return AbilityResult.ConditionNotMet( "Target has no kit" )
?: return AbilityResult.ConditionNotMet( "Target has no kit" ) val targetPlaystyle = plugin.kitManager.getSelectedPlaystyle( lineOfSight )
val currentKit = plugin.kitManager.getSelectedKit( player ) val currentKit = plugin.kitManager.getSelectedKit( player ) ?: return AbilityResult.ConditionNotMet( "Error while copying kit" )
?: return AbilityResult.ConditionNotMet( "Error while copying kit" ) val currentPlaystyle = plugin.kitManager.getSelectedPlaystyle( player )
activeStealTasks.remove( player.uniqueId ) activeStealTasks.remove( player.uniqueId )
plugin.kitManager.removeKit( player ) plugin.kitManager.removeKit( player )
plugin.kitManager.selectKit( player, targetKit ) plugin.kitManager.selectKit( player, targetKit )
plugin.kitManager.selectPlaystyle( player, targetPlaystyle )
plugin.kitManager.applyKit( player ) plugin.kitManager.applyKit( player )
val task = Bukkit.getScheduler().runTaskLater( plugin, { -> val task = Bukkit.getScheduler().runTaskLater( plugin, { ->
@@ -148,6 +149,7 @@ class GoblinKit : Kit() {
{ {
plugin.kitManager.removeKit( player ) plugin.kitManager.removeKit( player )
plugin.kitManager.selectKit( player, currentKit ) plugin.kitManager.selectKit( player, currentKit )
plugin.kitManager.selectPlaystyle( player, currentPlaystyle )
plugin.kitManager.applyKit( player ) plugin.kitManager.applyKit( player )
} }
}, 20L * 60) }, 20L * 60)

View File

@@ -184,7 +184,7 @@ class IceMageKit : Kit() {
victim: Player, victim: Player,
event: EntityDamageByEntityEvent event: EntityDamageByEntityEvent
) { ) {
if ( random.nextBoolean() ) if (random.nextInt( 3 ) < 1 )
victim.addPotionEffect(PotionEffect( PotionEffectType.SLOWNESS, 60, 0 )) victim.addPotionEffect(PotionEffect( PotionEffectType.SLOWNESS, 60, 0 ))
} }

View File

@@ -48,6 +48,8 @@ commands:
usage: '<red>Usage: /kit <kitName> <playstyle></red>' usage: '<red>Usage: /kit <kitName> <playstyle></red>'
kitNotFound: '<red><kit> is not a registered kit!</red>' kitNotFound: '<red><kit> is not a registered kit!</red>'
playstyleNotFound: '<red><playstyle> is not an available playstyle!</red>' playstyleNotFound: '<red><playstyle> is not an available playstyle!</red>'
gameHasStarted: '<red>The game has already started. You cannot select a kit right now!</red>'
cannotPickSameKit: '<red>You cannot pick the same kit!</red>'
selected: '<green>You have selected <kit> as your Kit with playstyle <playstyle>!</green>' selected: '<green>You have selected <kit> as your Kit with playstyle <playstyle>!</green>'
scoreboard: scoreboard:
@@ -71,6 +73,17 @@ scoreboard:
- "<yellow>play.mcscrims.club" - "<yellow>play.mcscrims.club"
kits: kits:
backup:
name: '<gradient:gold:#ff841f>Backup</gradient>'
lore:
- ' '
- 'Select a kit mid-round at any time.'
- 'All kits are available to pick.'
- ' '
- 'PlayStyle: §e%playstyle%'
- ' '
- 'Left-click to select'
- 'Right-click to change playstyle'
goblin: goblin:
name: '<gradient:dark_green:gray><bold>Goblin</bold></gradient>' name: '<gradient:dark_green:gray><bold>Goblin</bold></gradient>'
lore: lore:
@@ -92,4 +105,23 @@ kits:
messages: messages:
stole_kit: '<green>You have stolen the kit of your opponent (Kit: <kit>)!</green>' stole_kit: '<green>You have stolen the kit of your opponent (Kit: <kit>)!</green>'
spawn_bunker: '<green>You have created a bunker around yourself!' spawn_bunker: '<green>You have created a bunker around yourself!'
ability_charged: '<aqua>Your ability has been recharged!</aqua>' ability_charged: '<yellow>Your ability has been recharged!</yellow>'
icemage:
name: '<gradient:dark_aqua:aqua>IceMage</gradient>'
lore:
- ' '
- 'Use your abilities to freeze players'
- 'or gain speed in ice biomes and slow'
- 'enemies.'
- ' '
- 'PlayStyle: §e%playstyle%'
- ' '
- 'Left-click to select'
- 'Right-click to change playstyle'
items:
snowball:
name: '§bFreeze'
description: 'Freeze your enemies by throwing snowballs in all directions'
messages:
shoot_snowballs: '<aqua>You have shot frozen snowballs in all directions!</aqua>'
ability_charged: '<yellow>Your ability has been recharged!</yellow>'