Introduce a full in-memory team system and integrate it into gameplay and UI. - Add Team, TeamManager, TeamListener and TeamCommand to manage teams, invites, accept/deny, leave, kick and info. TeamManager stores teams and pending invites (INVITE_TTL_MS) using concurrent maps and exposes helper/query methods (areInSameTeam, allAliveInSameTeam, getInviterFor, cleanExpiredInvites, reset). - Wire TeamManager into SpeedHG: initialize, schedule periodic invite cleanup, register command and listener, and reset teams on plugin disable. - Update GameManager to consider teams for win conditions, build team winner names, exclude teammates from random teleport/compass logic, and credit all winning team members for stats/ranking. - Move ability charge feedback out of individual kits into KitEventDispatcher: remove per-kit onFullyCharged overrides and add centralized ActionBar/sound feedback (sendChargeUpdateActionBar/sendChargeReadyActionBar). ActiveAbility no longer defines onFullyCharged. - Make OraclePerk ignore teammates when finding nearest enemy. - Add config entries for teams and corresponding language strings; tweak some default values in CustomGameSettings (gladiator arena radius/height and minor formatting changes). These changes enable 2-player teams (configurable), friendly-fire prevention, invite lifecycle handling, and team-aware endgame logic.
232 lines
8.5 KiB
Kotlin
232 lines
8.5 KiB
Kotlin
package club.mcscrims.speedhg.command
|
|
|
|
import club.mcscrims.speedhg.SpeedHG
|
|
import club.mcscrims.speedhg.game.GameState
|
|
import club.mcscrims.speedhg.team.TeamManager
|
|
import club.mcscrims.speedhg.util.sendMsg
|
|
import org.bukkit.command.Command
|
|
import org.bukkit.command.CommandExecutor
|
|
import org.bukkit.command.CommandSender
|
|
import org.bukkit.command.TabCompleter
|
|
import org.bukkit.entity.Player
|
|
|
|
class TeamCommand : CommandExecutor, TabCompleter {
|
|
|
|
private val plugin get() = SpeedHG.instance
|
|
|
|
// ── Guard: Teams nur in Lobby-Phasen ändern ──────────────────────────────
|
|
|
|
private fun isLobbyPhase(): Boolean = when (plugin.gameManager.currentState) {
|
|
GameState.LOBBY, GameState.STARTING -> true
|
|
else -> false
|
|
}
|
|
|
|
// =========================================================================
|
|
// onCommand dispatch
|
|
// =========================================================================
|
|
|
|
override fun onCommand(
|
|
sender: CommandSender,
|
|
command: Command,
|
|
label: String,
|
|
args: Array<out String>
|
|
): Boolean {
|
|
val player = sender as? Player ?: run {
|
|
sender.sendMessage("§cNur Spieler können diesen Befehl nutzen.")
|
|
return true
|
|
}
|
|
|
|
if (!plugin.teamManager.isEnabled) {
|
|
player.sendMsg("team.disabled")
|
|
return true
|
|
}
|
|
|
|
when (args.firstOrNull()?.lowercase()) {
|
|
"invite" -> handleInvite(player, args)
|
|
"accept" -> handleAccept(player, args)
|
|
"deny" -> handleDeny(player, args)
|
|
"leave" -> handleLeave(player)
|
|
"kick" -> handleKick(player, args)
|
|
"info" -> handleInfo(player)
|
|
else -> player.sendMsg("team.usage")
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// =========================================================================
|
|
// Subcommand Handlers
|
|
// =========================================================================
|
|
|
|
private fun handleInvite(player: Player, args: Array<out String>) {
|
|
if (!isLobbyPhase()) { player.sendMsg("team.game_running"); return }
|
|
|
|
val targetName = args.getOrNull(1) ?: run { player.sendMsg("team.invite.usage"); return }
|
|
val target = plugin.server.getPlayerExact(targetName) ?: run {
|
|
player.sendMsg("team.player_not_found", "name" to targetName); return
|
|
}
|
|
|
|
when (plugin.teamManager.invite(player, target)) {
|
|
is TeamManager.InviteResult.Success -> {
|
|
player.sendMsg("team.invite.sent", "name" to target.name)
|
|
target.sendMsg("team.invite.received",
|
|
"name" to player.name,
|
|
"time" to (TeamManager.INVITE_TTL_MS / 1000).toString()
|
|
)
|
|
}
|
|
is TeamManager.InviteResult.TeamsDisabled -> player.sendMsg("team.disabled")
|
|
is TeamManager.InviteResult.InvitedSelf -> player.sendMsg("team.invite.self")
|
|
is TeamManager.InviteResult.AlreadyInSameTeam -> player.sendMsg("team.invite.already_teammate")
|
|
is TeamManager.InviteResult.TargetAlreadyInTeam -> player.sendMsg("team.invite.target_has_team", "name" to target.name)
|
|
is TeamManager.InviteResult.SenderAlreadyInFullTeam -> player.sendMsg("team.invite.team_full")
|
|
is TeamManager.InviteResult.InviteAlreadyPending -> player.sendMsg("team.invite.already_pending", "name" to target.name)
|
|
}
|
|
}
|
|
|
|
private fun handleAccept(player: Player, args: Array<out String>) {
|
|
if (!isLobbyPhase()) { player.sendMsg("team.game_running"); return }
|
|
|
|
val inviterName = args.getOrNull(1) ?: run { player.sendMsg("team.accept.usage"); return }
|
|
val inviter = plugin.server.getPlayerExact(inviterName) ?: run {
|
|
player.sendMsg("team.player_not_found", "name" to inviterName); return
|
|
}
|
|
|
|
when (val result = plugin.teamManager.accept(player, inviter.uniqueId)) {
|
|
is TeamManager.AcceptResult.Success -> {
|
|
val memberNames = result.team.members
|
|
.mapNotNull { plugin.server.getPlayer(it)?.name }
|
|
.joinToString(", ")
|
|
|
|
// Alle Teammitglieder benachrichtigen
|
|
result.team.members.forEach { uuid ->
|
|
plugin.server.getPlayer(uuid)?.sendMsg(
|
|
"team.accept.joined",
|
|
"name" to player.name,
|
|
"members" to memberNames
|
|
)
|
|
}
|
|
}
|
|
is TeamManager.AcceptResult.TeamsDisabled -> player.sendMsg("team.disabled")
|
|
is TeamManager.AcceptResult.NoInvite -> player.sendMsg("team.accept.no_invite", "name" to inviterName)
|
|
is TeamManager.AcceptResult.WrongInviter -> player.sendMsg("team.accept.no_invite", "name" to inviterName)
|
|
is TeamManager.AcceptResult.InviteExpired -> player.sendMsg("team.accept.expired", "name" to inviterName)
|
|
is TeamManager.AcceptResult.AlreadyInTeam -> player.sendMsg("team.already_in_team")
|
|
is TeamManager.AcceptResult.TeamFull -> player.sendMsg("team.invite.team_full")
|
|
is TeamManager.AcceptResult.InviterNotFound -> player.sendMsg("team.player_not_found", "name" to inviterName)
|
|
}
|
|
}
|
|
|
|
private fun handleDeny(player: Player, args: Array<out String>) {
|
|
if (!isLobbyPhase()) { player.sendMsg("team.game_running"); return }
|
|
|
|
val inviterName = args.getOrNull(1) ?: run { player.sendMsg("team.deny.usage"); return }
|
|
val inviter = plugin.server.getPlayerExact(inviterName) ?: run {
|
|
player.sendMsg("team.player_not_found", "name" to inviterName); return
|
|
}
|
|
|
|
if (plugin.teamManager.deny(player, inviter.uniqueId)) {
|
|
player.sendMsg("team.deny.success", "name" to inviterName)
|
|
inviter.sendMsg("team.deny.received", "name" to player.name)
|
|
} else {
|
|
player.sendMsg("team.accept.no_invite", "name" to inviterName)
|
|
}
|
|
}
|
|
|
|
private fun handleLeave(player: Player) {
|
|
if (!isLobbyPhase()) { player.sendMsg("team.game_running"); return }
|
|
|
|
val team = plugin.teamManager.getTeam(player)
|
|
|
|
when (plugin.teamManager.leave(player)) {
|
|
is TeamManager.LeaveResult.NotInTeam -> {
|
|
player.sendMsg("team.not_in_team")
|
|
}
|
|
is TeamManager.LeaveResult.TeamDisbanded -> {
|
|
// Alle Ex-Mitglieder (außer dem Verlassenden) benachrichtigen
|
|
team?.members?.forEach { uuid ->
|
|
plugin.server.getPlayer(uuid)?.sendMsg("team.leave.disbanded")
|
|
}
|
|
player.sendMsg("team.leave.disbanded")
|
|
}
|
|
is TeamManager.LeaveResult.Success -> {
|
|
player.sendMsg("team.leave.success")
|
|
// Verbleibende Mitglieder informieren
|
|
team?.members?.forEach { uuid ->
|
|
plugin.server.getPlayer(uuid)?.sendMsg(
|
|
"team.leave.member_left", "name" to player.name
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun handleKick(player: Player, args: Array<out String>) {
|
|
if (!isLobbyPhase()) { player.sendMsg("team.game_running"); return }
|
|
|
|
val targetName = args.getOrNull(1) ?: run { player.sendMsg("team.kick.usage"); return }
|
|
val target = plugin.server.getPlayerExact(targetName) ?: run {
|
|
player.sendMsg("team.player_not_found", "name" to targetName); return
|
|
}
|
|
|
|
when (plugin.teamManager.kick(player, target)) {
|
|
is TeamManager.KickResult.Success -> {
|
|
player.sendMsg("team.kick.success", "name" to target.name)
|
|
target.sendMsg("team.kick.received", "name" to player.name)
|
|
}
|
|
is TeamManager.KickResult.NotInTeam -> player.sendMsg("team.not_in_team")
|
|
is TeamManager.KickResult.NotLeader -> player.sendMsg("team.kick.not_leader")
|
|
is TeamManager.KickResult.TargetNotInTeam -> player.sendMsg("team.kick.not_in_your_team", "name" to targetName)
|
|
is TeamManager.KickResult.CannotKickSelf -> player.sendMsg("team.kick.self")
|
|
}
|
|
}
|
|
|
|
private fun handleInfo(player: Player) {
|
|
val team = plugin.teamManager.getTeam(player) ?: run {
|
|
player.sendMsg("team.not_in_team"); return
|
|
}
|
|
|
|
player.sendMsg("team.info.header")
|
|
team.members.forEachIndexed { i, uuid ->
|
|
val name = plugin.server.getPlayer(uuid)?.name ?: uuid.toString().take(8)
|
|
val isLeader = team.isLeader(uuid)
|
|
player.sendMsg(
|
|
"team.info.member",
|
|
"index" to (i + 1).toString(),
|
|
"name" to name,
|
|
"leader" to if (isLeader) " ★" else ""
|
|
)
|
|
}
|
|
player.sendMsg("team.info.footer", "size" to team.size.toString(), "max" to plugin.teamManager.maxTeamSize.toString())
|
|
}
|
|
|
|
// =========================================================================
|
|
// Tab Completion
|
|
// =========================================================================
|
|
|
|
override fun onTabComplete(
|
|
sender: CommandSender,
|
|
command: Command,
|
|
label: String,
|
|
args: Array<out String>
|
|
): List<String> {
|
|
if (sender !is Player) return emptyList()
|
|
|
|
if (args.size == 1) {
|
|
return listOf("invite", "accept", "deny", "leave", "kick", "info")
|
|
.filter { it.startsWith(args[0], ignoreCase = true) }
|
|
}
|
|
|
|
if (args.size == 2) {
|
|
return when (args[0].lowercase()) {
|
|
"invite", "accept", "deny", "kick" ->
|
|
plugin.server.onlinePlayers
|
|
.filter { it != sender }
|
|
.map { it.name }
|
|
.filter { it.startsWith(args[1], ignoreCase = true) }
|
|
else -> emptyList()
|
|
}
|
|
}
|
|
|
|
return emptyList()
|
|
}
|
|
} |