diff --git a/common/src/main/kotlin/com/bluedragonmc/server/Game.kt b/common/src/main/kotlin/com/bluedragonmc/server/Game.kt index 70103fd..21ba7eb 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/Game.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/Game.kt @@ -5,10 +5,7 @@ import com.bluedragonmc.api.grpc.CommonTypes.GameType.GameTypeFieldSelector import com.bluedragonmc.api.grpc.gameState import com.bluedragonmc.api.grpc.gameType import com.bluedragonmc.server.api.Environment -import com.bluedragonmc.server.event.GameEvent -import com.bluedragonmc.server.event.GameStartEvent -import com.bluedragonmc.server.event.GameStateChangedEvent -import com.bluedragonmc.server.event.PlayerLeaveGameEvent +import com.bluedragonmc.server.event.* import com.bluedragonmc.server.model.GameDocument import com.bluedragonmc.server.model.InstanceRecord import com.bluedragonmc.server.model.PlayerRecord @@ -35,8 +32,7 @@ import net.minestom.server.event.Event import net.minestom.server.event.EventFilter import net.minestom.server.event.EventListener import net.minestom.server.event.EventNode -import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent -import net.minestom.server.event.player.PlayerSpawnEvent +import net.minestom.server.event.player.PlayerDisconnectEvent import net.minestom.server.event.server.ServerTickMonitorEvent import net.minestom.server.event.trait.InstanceEvent import net.minestom.server.event.trait.PlayerEvent @@ -75,7 +71,8 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n maxSlots = maxPlayers } - internal val players: MutableList = CopyOnWriteArrayList() + private val _players: MutableList = CopyOnWriteArrayList() + val players: List = _players protected val logger: Logger = LoggerFactory.getLogger(this::class.java) @@ -103,7 +100,7 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n return@event when (event) { is InstanceEvent -> ownsInstance(event.instance ?: return@event false) is GameEvent -> event.game === this - is PlayerEvent -> event.player.isActive && ownsInstance(event.player.instance ?: return@event false) + is PlayerEvent -> players.contains(event.player) is ServerTickMonitorEvent -> true else -> false } @@ -128,14 +125,11 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n protected open fun useMandatoryModules() { Messaging.outgoing.onGameCreated(this) - handleEvent { + handleEvent { playerHasJoined = true } - handleEvent { event -> - if (event.entity !is Player) return@handleEvent - callCancellable(PlayerLeaveGameEvent(this, event.entity as Player)) { - players.remove(event.entity) - } + handleEvent { event -> + removePlayer(event.player) } onGameStart { startTime = Date() @@ -191,12 +185,17 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n private val isJoinable get() = state.canPlayersJoin + private fun removePlayer(player: Player) { + _players.remove(player) + callEvent(PlayerLeaveGameEvent(this, player)) + } + fun addPlayer(player: Player, sendPlayer: Boolean = true): CompletableFuture { - findGame(player)?.players?.remove(player) - players.add(player) + findGame(player)?.removePlayer(player) + _players.add(player) if (sendPlayer && (player.instance == null || !ownsInstance(player.instance!!))) { try { - return sendPlayerToInstance(player) + return sendPlayerToInstance(player).whenComplete { _, _ -> callEvent(PlayerJoinGameEvent(player, this))} } catch (e: Throwable) { e.printStackTrace() player.sendMessage( @@ -209,8 +208,10 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n name = Environment.defaultGameName selectors += GameTypeFieldSelector.GAME_NAME }) + return AsyncUtils.empty() } } + callEvent(PlayerJoinGameEvent(player, this)) return AsyncUtils.empty() } @@ -223,7 +224,7 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n return player.setInstance(instance).thenApply { instance } } - override fun getPlayers(): MutableCollection = players + override fun getPlayers(): MutableCollection = _players open fun endGameLater(delay: Duration = Duration.ZERO) { state = GameState.ENDING @@ -320,7 +321,7 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n } }.executionType(ExecutionType.TICK_START).delay(Duration.ofSeconds(10)) - players.clear() + while (players.isNotEmpty()) removePlayer(players.first()) } open fun isInactive(): Boolean { @@ -413,7 +414,7 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n logger.info("Ending inactive game ${game.id} (${game.name}/${game.mapName}/${game.mode})") game.endGame(false) } - game.players.removeIf { player -> !player.isOnline } + game._players.removeIf { player -> !player.isOnline } } instances.forEach { instance -> diff --git a/common/src/main/kotlin/com/bluedragonmc/server/event/PlayerJoinGameEvent.kt b/common/src/main/kotlin/com/bluedragonmc/server/event/PlayerJoinGameEvent.kt new file mode 100644 index 0000000..346bdf4 --- /dev/null +++ b/common/src/main/kotlin/com/bluedragonmc/server/event/PlayerJoinGameEvent.kt @@ -0,0 +1,12 @@ +package com.bluedragonmc.server.event + +import com.bluedragonmc.server.Game +import net.minestom.server.entity.Player +import net.minestom.server.event.trait.PlayerEvent + +/** + * Called when a player joins the game. + */ +class PlayerJoinGameEvent(private val player: Player, game: Game) : GameEvent(game), PlayerEvent { + override fun getPlayer(): Player = player +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/bluedragonmc/server/module/combat/OldCombatModule.kt b/common/src/main/kotlin/com/bluedragonmc/server/module/combat/OldCombatModule.kt index d8fe24b..a3c0e76 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/module/combat/OldCombatModule.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/module/combat/OldCombatModule.kt @@ -2,6 +2,7 @@ package com.bluedragonmc.server.module.combat import com.bluedragonmc.server.CustomPlayer import com.bluedragonmc.server.Game +import com.bluedragonmc.server.event.PlayerJoinGameEvent import com.bluedragonmc.server.event.PlayerKillPlayerEvent import com.bluedragonmc.server.event.PlayerLeaveGameEvent import com.bluedragonmc.server.module.GameModule @@ -19,7 +20,6 @@ import net.minestom.server.event.entity.EntityAttackEvent import net.minestom.server.event.entity.EntityPotionAddEvent import net.minestom.server.event.entity.EntityTickEvent import net.minestom.server.event.item.PlayerFinishItemUseEvent -import net.minestom.server.event.player.PlayerSpawnEvent import net.minestom.server.event.trait.CancellableEvent import net.minestom.server.event.trait.PlayerInstanceEvent import net.minestom.server.instance.Instance @@ -94,7 +94,7 @@ class OldCombatModule(var allowDamage: Boolean = true, var allowKnockback: Boole } } - eventNode.addListener(PlayerSpawnEvent::class.java) { event -> + eventNode.addListener(PlayerJoinGameEvent::class.java) { event -> // Hint to client that there is no attack cooldown event.player.getAttribute(Attribute.ATTACK_SPEED).baseValue = 100.0 event.player.getAttribute(Attribute.ATTACK_DAMAGE).baseValue = 1.0 diff --git a/common/src/main/kotlin/com/bluedragonmc/server/module/gameplay/MaxHealthModule.kt b/common/src/main/kotlin/com/bluedragonmc/server/module/gameplay/MaxHealthModule.kt index df25a92..d0db677 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/module/gameplay/MaxHealthModule.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/module/gameplay/MaxHealthModule.kt @@ -1,22 +1,22 @@ package com.bluedragonmc.server.module.gameplay import com.bluedragonmc.server.Game +import com.bluedragonmc.server.event.PlayerJoinGameEvent import com.bluedragonmc.server.event.PlayerLeaveGameEvent import com.bluedragonmc.server.module.GameModule import net.minestom.server.entity.attribute.Attribute import net.minestom.server.event.Event import net.minestom.server.event.EventNode -import net.minestom.server.event.player.PlayerSpawnEvent /** - * Sets the max health of the player when they join the instance. + * Sets the max health of the player when they join the game. * This sets the base value of the attribute, it does not add a modifier. * The module automatically resets their max health to 20 when they leave the instance. */ class MaxHealthModule(private val maxHealth: Double) : GameModule() { override fun initialize(parent: Game, eventNode: EventNode) { - eventNode.addListener(PlayerSpawnEvent::class.java) { event -> + eventNode.addListener(PlayerJoinGameEvent::class.java) { event -> event.player.getAttribute(Attribute.MAX_HEALTH).baseValue = maxHealth } eventNode.addListener(PlayerLeaveGameEvent::class.java) { event -> diff --git a/common/src/main/kotlin/com/bluedragonmc/server/module/gameplay/SidebarModule.kt b/common/src/main/kotlin/com/bluedragonmc/server/module/gameplay/SidebarModule.kt index 8758f49..9d232c7 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/module/gameplay/SidebarModule.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/module/gameplay/SidebarModule.kt @@ -2,25 +2,25 @@ package com.bluedragonmc.server.module.gameplay import com.bluedragonmc.server.* import com.bluedragonmc.server.api.Environment -import com.bluedragonmc.server.event.CountdownEvent -import com.bluedragonmc.server.event.GameStartEvent -import com.bluedragonmc.server.event.GameStateChangedEvent -import com.bluedragonmc.server.event.PlayerLeaveGameEvent +import com.bluedragonmc.server.event.* import com.bluedragonmc.server.module.GameModule import com.bluedragonmc.server.utils.GameState +import com.bluedragonmc.server.utils.plus import com.bluedragonmc.server.utils.withGradient +import com.bluedragonmc.server.utils.withTransition import kotlinx.coroutines.runBlocking import net.kyori.adventure.text.Component import net.kyori.adventure.text.Component.text import net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY import net.kyori.adventure.text.format.NamedTextColor.RED +import net.kyori.adventure.text.format.NamedTextColor.YELLOW +import net.kyori.adventure.text.format.NamedTextColor.GREEN import net.kyori.adventure.text.format.TextDecoration import net.kyori.adventure.translation.GlobalTranslator import net.minestom.server.MinecraftServer import net.minestom.server.entity.Player import net.minestom.server.event.Event import net.minestom.server.event.EventNode -import net.minestom.server.event.player.PlayerSpawnEvent import net.minestom.server.scoreboard.Sidebar import net.minestom.server.scoreboard.Sidebar.ScoreboardLine import java.util.* @@ -43,7 +43,7 @@ class SidebarModule(private val title: String) : GameModule() { if (::binding.isInitialized) binding.updateFor(player) } - eventNode.addListener(PlayerSpawnEvent::class.java) { event -> + eventNode.addListener(PlayerJoinGameEvent::class.java) { event -> val sidebar = sidebars.getOrPut(event.player) { createSidebar() } sidebar.addViewer(event.player) if (::binding.isInitialized) diff --git a/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/CountdownModule.kt b/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/CountdownModule.kt index d202dba..9e8b2cb 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/CountdownModule.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/CountdownModule.kt @@ -4,6 +4,7 @@ import com.bluedragonmc.server.BRAND_COLOR_PRIMARY_2 import com.bluedragonmc.server.Game import com.bluedragonmc.server.event.CountdownEvent import com.bluedragonmc.server.event.GameStartEvent +import com.bluedragonmc.server.event.PlayerJoinGameEvent import com.bluedragonmc.server.module.GameModule import com.bluedragonmc.server.utils.GameState import net.kyori.adventure.text.Component @@ -34,7 +35,7 @@ class CountdownModule( fun getTimeLeft() = secondsLeft ?: countdownSeconds override fun initialize(parent: Game, eventNode: EventNode) { - eventNode.addListener(PlayerSpawnEvent::class.java) { + eventNode.addListener(PlayerJoinGameEvent::class.java) { if (parent.state == GameState.STARTING || parent.state == GameState.INGAME || parent.state == GameState.ENDING || countdownRunning) return@addListener if (threshold > 0 && parent.players.size >= threshold) { startCountdown(parent) diff --git a/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/KitsModule.kt b/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/KitsModule.kt index 9710ce9..d0958a8 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/KitsModule.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/KitsModule.kt @@ -3,6 +3,7 @@ package com.bluedragonmc.server.module.minigame import com.bluedragonmc.server.Game import com.bluedragonmc.server.event.GameStartEvent import com.bluedragonmc.server.event.KitSelectedEvent +import com.bluedragonmc.server.event.PlayerJoinGameEvent import com.bluedragonmc.server.module.DependsOn import com.bluedragonmc.server.module.GameModule import com.bluedragonmc.server.module.GuiModule @@ -13,7 +14,6 @@ import net.minestom.server.component.DataComponents import net.minestom.server.entity.Player import net.minestom.server.event.Event import net.minestom.server.event.EventNode -import net.minestom.server.event.player.PlayerSpawnEvent import net.minestom.server.inventory.InventoryType import net.minestom.server.item.ItemStack import net.minestom.server.item.Material @@ -40,8 +40,7 @@ open class KitsModule( override fun initialize(parent: Game, eventNode: EventNode) { this.parent = parent // todo add support for unlockable kits - // todo make this use a "player join game event" instead of spawn event - eventNode.addListener(PlayerSpawnEvent::class.java) { event -> + eventNode.addListener(PlayerJoinGameEvent::class.java) { event -> if (showMenu) { selectKit(event.player) } diff --git a/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/MOTDModule.kt b/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/MOTDModule.kt index c558acd..7f169ce 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/MOTDModule.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/MOTDModule.kt @@ -3,6 +3,7 @@ package com.bluedragonmc.server.module.minigame import com.bluedragonmc.server.BRAND_COLOR_PRIMARY_1 import com.bluedragonmc.server.BRAND_COLOR_PRIMARY_2 import com.bluedragonmc.server.Game +import com.bluedragonmc.server.event.PlayerJoinGameEvent import com.bluedragonmc.server.module.GameModule import com.bluedragonmc.server.module.SoftDependsOn import com.bluedragonmc.server.module.config.ConfigModule @@ -16,7 +17,6 @@ import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextDecoration import net.minestom.server.event.Event import net.minestom.server.event.EventNode -import net.minestom.server.event.player.PlayerSpawnEvent /** * Displays a message to players when they join the game. @@ -35,7 +35,7 @@ class MOTDModule(private val motd: Component, private var showMapName: Boolean = val description = node?.node("description")?.string ?: "An awesome map!" val author = node?.node("author")?.string ?: "BlueDragon Build Team" - eventNode.addListener(PlayerSpawnEvent::class.java) { event -> + eventNode.addListener(PlayerJoinGameEvent::class.java) { event -> event.player.sendMessage( buildComponent { // Game name diff --git a/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/PlayerResetModule.kt b/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/PlayerResetModule.kt index 3a8f384..47de32a 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/PlayerResetModule.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/PlayerResetModule.kt @@ -1,6 +1,7 @@ package com.bluedragonmc.server.module.minigame import com.bluedragonmc.server.Game +import com.bluedragonmc.server.event.PlayerJoinGameEvent import com.bluedragonmc.server.module.GameModule import net.kyori.adventure.nbt.CompoundBinaryTag import net.minestom.server.entity.GameMode @@ -8,7 +9,6 @@ import net.minestom.server.entity.Player import net.minestom.server.entity.attribute.Attribute import net.minestom.server.event.Event import net.minestom.server.event.EventNode -import net.minestom.server.event.player.PlayerSpawnEvent /** * "Resets" the player when they join the game. This changes some basic attributes to make sure effects don't persist in between games. @@ -25,7 +25,7 @@ import net.minestom.server.event.player.PlayerSpawnEvent */ class PlayerResetModule(val defaultGameMode: GameMode? = null) : GameModule() { override fun initialize(parent: Game, eventNode: EventNode) { - eventNode.addListener(PlayerSpawnEvent::class.java) { event -> + eventNode.addListener(PlayerJoinGameEvent::class.java) { event -> resetPlayer(event.player, defaultGameMode) } } diff --git a/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/TeamModule.kt b/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/TeamModule.kt index 7911877..d48c5eb 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/TeamModule.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/TeamModule.kt @@ -163,27 +163,29 @@ class TeamModule( TEAM_COUNT } - data class Team( + class Team( val name: Component = Component.empty(), - val players: MutableList = CopyOnWriteArrayList(), + players: List = CopyOnWriteArrayList(), val allowFriendlyFire: Boolean = false, val nameTagVisibility: NameTagVisibility = NameTagVisibility.ALWAYS, ) : PacketGroupingAudience { val uuid: UUID = UUID.randomUUID() + private val _players = players.toMutableList() + val players: List = _players lateinit var scoreboardTeam: net.minestom.server.scoreboard.Team private set - override fun getPlayers(): MutableCollection = players + override fun getPlayers(): Collection = players fun addPlayer(player: Player) { - players.add(player) + _players.add(player) if (::scoreboardTeam.isInitialized) scoreboardTeam.addMember(player.username) } fun removePlayer(player: Player) { - players.remove(player) + _players.remove(player) if (::scoreboardTeam.isInitialized) scoreboardTeam.removeMember(player.username) } diff --git a/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/VoteStartModule.kt b/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/VoteStartModule.kt index 38f81f3..aa293ed 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/VoteStartModule.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/module/minigame/VoteStartModule.kt @@ -2,10 +2,7 @@ package com.bluedragonmc.server.module.minigame import com.bluedragonmc.server.BRAND_COLOR_PRIMARY_2 import com.bluedragonmc.server.Game -import com.bluedragonmc.server.event.CountdownEvent -import com.bluedragonmc.server.event.GameStartEvent -import com.bluedragonmc.server.event.GameStateChangedEvent -import com.bluedragonmc.server.event.PlayerLeaveGameEvent +import com.bluedragonmc.server.event.* import com.bluedragonmc.server.module.GameModule import com.bluedragonmc.server.utils.GameState import com.bluedragonmc.server.utils.manage @@ -20,7 +17,6 @@ import net.minestom.server.entity.Player import net.minestom.server.entity.PlayerHand import net.minestom.server.event.Event import net.minestom.server.event.EventNode -import net.minestom.server.event.player.PlayerSpawnEvent import net.minestom.server.event.player.PlayerUseItemEvent import net.minestom.server.item.ItemStack import net.minestom.server.item.Material @@ -50,7 +46,7 @@ class VoteStartModule( override fun initialize(parent: Game, eventNode: EventNode) { this.parent = parent - eventNode.addListener(PlayerSpawnEvent::class.java) { event -> + eventNode.addListener(PlayerJoinGameEvent::class.java) { event -> fill(event.player, voteStartItem) } eventNode.addListener(PlayerUseItemEvent::class.java) { event -> diff --git a/common/src/main/kotlin/com/bluedragonmc/server/module/vanilla/ChestModule.kt b/common/src/main/kotlin/com/bluedragonmc/server/module/vanilla/ChestModule.kt index 50c1f80..34e1d21 100644 --- a/common/src/main/kotlin/com/bluedragonmc/server/module/vanilla/ChestModule.kt +++ b/common/src/main/kotlin/com/bluedragonmc/server/module/vanilla/ChestModule.kt @@ -71,7 +71,7 @@ class ChestModule : GameModule() { } } - private val chests = mutableMapOf() + val chests = mutableMapOf() abstract class ChestBlock { diff --git a/src/main/kotlin/com/bluedragonmc/server/bootstrap/dev/DevInstanceRouter.kt b/src/main/kotlin/com/bluedragonmc/server/bootstrap/dev/DevInstanceRouter.kt index 558c395..63ed883 100644 --- a/src/main/kotlin/com/bluedragonmc/server/bootstrap/dev/DevInstanceRouter.kt +++ b/src/main/kotlin/com/bluedragonmc/server/bootstrap/dev/DevInstanceRouter.kt @@ -15,9 +15,16 @@ import net.minestom.server.event.Event import net.minestom.server.event.EventNode import net.minestom.server.event.instance.InstanceTickEvent import net.minestom.server.event.player.AsyncPlayerConfigurationEvent +import net.minestom.server.event.player.PlayerSpawnEvent object DevInstanceRouter : Bootstrap(EnvType.DEVELOPMENT) { override fun hook(eventNode: EventNode) { + eventNode.addListener(PlayerSpawnEvent::class.java) { event -> + // When a player logs in and spawns in the lobby, add them to the lobby's player list + if (event.isFirstSpawn && event.instance == lobby.getInstance()) { + lobby.addPlayer(event.player, sendPlayer = false) + } + } eventNode.addListener(AsyncPlayerConfigurationEvent::class.java) { event -> try { runBlocking { @@ -33,7 +40,6 @@ object DevInstanceRouter : Bootstrap(EnvType.DEVELOPMENT) { if (isLobbyInitialized()) { // Send the player to the lobby event.spawningInstance = lobby.getInstance() - lobby.players.add(event.player) val spawnpoint = lobby.getModuleOrNull()?.spawnpointProvider?.getSpawnpoint(event.player) if (spawnpoint != null) { diff --git a/src/main/kotlin/com/bluedragonmc/server/command/LobbyCommand.kt b/src/main/kotlin/com/bluedragonmc/server/command/LobbyCommand.kt index 133cc92..a21fbf5 100644 --- a/src/main/kotlin/com/bluedragonmc/server/command/LobbyCommand.kt +++ b/src/main/kotlin/com/bluedragonmc/server/command/LobbyCommand.kt @@ -16,9 +16,7 @@ class LobbyCommand(name: String, vararg aliases: String?) : BlueDragonCommand(na } return@suspendSyntax } - player.setInstance( - lobby.getInstance(), lobby.getModule().spawnpointProvider.getSpawnpoint(player) - ) + lobby.addPlayer(player) // Remove the player from the queue when they go to the lobby Messaging.outgoing.removeFromQueue(player) }