Skip to content

Commit b05b09a

Browse files
Allow overriding the default game name with an environment variable
`BLUEDRAGON_DEFAULT_GAME` (defaults to "Lobby")
1 parent b11b152 commit b05b09a

File tree

9 files changed

+108
-36
lines changed

9 files changed

+108
-36
lines changed

INTEGRATION.md

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
# Integration Guide
2-
This guide covers how to integrate other server software with [BlueDragonMC/Puffin](https://github.com/BlueDragonMC/Puffin).
2+
3+
This guide covers how to integrate other server software
4+
with [BlueDragonMC/Puffin](https://github.com/BlueDragonMC/Puffin).
5+
36
## 1: Background
7+
48
### 1.1: Kubernetes
9+
510
In production, BlueDragon runs in a [Kubernetes](https://kubernetes.io/) cluster.
6-
* Game servers are controlled by [Agones](https://agones.dev) with a [fleet](https://agones.dev/site/docs/reference/fleet/).
11+
12+
* Game servers are controlled by [Agones](https://agones.dev) with
13+
a [fleet](https://agones.dev/site/docs/reference/fleet/).
714
* Proxies are not currently handled by a fleet, however this may change in the future.
15+
816
## 2: Messaging
17+
918
### 2.1: Reference - All gRPC Services
19+
1020
| Service name | Purpose | Implemented By |
1121
|-------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|:-------------------|
1222
| [agones](https://github.com/BlueDragonMC/RPC/blob/master/src/main/proto/agones.proto) | Interacting with the Agones SDK | Agones SDK |
@@ -18,80 +28,135 @@ In production, BlueDragon runs in a [Kubernetes](https://kubernetes.io/) cluster
1828
| [server_tracking](https://github.com/BlueDragonMC/RPC/blob/master/src/main/proto/server_tracking.proto) | Updating available instances on each server and their current states | Puffin |
1929
| [service_discovery](https://github.com/BlueDragonMC/RPC/blob/master/src/main/proto/service_discovery.proto) | Finding an available lobby for a player to join | Puffin |
2030
| [velocity_message](https://github.com/BlueDragonMC/RPC/blob/master/src/main/proto/velocity_message.proto) | Private messaging | Puffin |
31+
2132
### 2.2: Inbound
33+
2234
Inbound messages are messages received from other services in the cluster.
2335

24-
To receive messages, each game server starts its own gRPC server on port 50051. This port is exposed to other services in the cluster.
36+
To receive messages, each game server starts its own gRPC server on port 50051. This port is exposed to other services
37+
in the cluster.
2538

26-
Every game server's gRPC endpoint should implement all services which have "Game server" in the "Implemented By" column of the table in section 2.1.
39+
Every game server's gRPC endpoint should implement all services which have "Game server" in the "Implemented By" column
40+
of the table in section 2.1.
2741

2842
*See the [BlueDragonMC/RPC](https://github.com/BlueDragonMC/RPC/) repository for all of BlueDragon's proto files.*
43+
2944
### 2.3: Outbound
45+
3046
Outbound messages are messages sent from a game server to other services in the cluster.
3147

3248
To send messages, a gRPC client should be set up on each game server.
3349

34-
Game servers can send messages to (and expect RPC responses from) all services which have "Puffin" in the "Implemented By" column of the table in section 2.1.
35-
The IP address of Puffin can be found because it is exposed as a Kubernetes service. The DNS name "puffin" should resolve to an existing server address. Puffin uses port 50051 for its gRPC server.
50+
Game servers can send messages to (and expect RPC responses from) all services which have "Puffin" in the "Implemented
51+
By" column of the table in section 2.1.
52+
The IP address of Puffin can be found because it is exposed as a Kubernetes service. The DNS name "puffin" should
53+
resolve to an existing server address. Puffin uses port 50051 for its gRPC server.
54+
3655
## 3: Database
56+
3757
### 3.1: Database Internals
58+
3859
BlueDragon runs [MongoDB](https://www.mongodb.com/), a document database with no rigid schema.
39-
In the Kubernetes cluster, it is made available under the `mongo` [service](https://kubernetes.io/docs/concepts/services-networking/service/), so the hostname `mongo` should resolve to a valid MongoDB server address.
60+
In the Kubernetes cluster, it is made available under
61+
the `mongo` [service](https://kubernetes.io/docs/concepts/services-networking/service/), so the hostname `mongo` should
62+
resolve to a valid MongoDB server address.
4063
The service should be available on port 27017, the default MongoDB port.
64+
4165
### 3.2: Player Documents
66+
4267
Each player has their own document in the `players` collection of the database with the following fields:
68+
4369
* `_id`: The UUID of the player, represented as a string.
4470
* `username`: The username of the player. Updated whenever the name changes.
4571
* `coins`: The number of coins the player has.
4672
* `experience`: The amount of experience the player has.
47-
* `punishments`: A list of punishments that the player has. Punishments are not removed from the list when revoked or expired.
48-
* `type`: A string from the following list: `BAN`, `MUTE`.
49-
* `id`: A UUID (represented as a string) that uniquely identifies the punishment.
50-
* `issuedAt`: The time the punishment was issued, represented as a Unix timestamp (milliseconds after Jan 01, 1970, 00:00:00 GMT)
51-
* `expiresAt`: The time the punishment will expire/has expired, represented as a Unix timestamp (milliseconds after Jan 01, 1970, 00:00:00 GMT)
52-
* `moderator`: The UUID of the player which enacted the punishment.
53-
* `reason`: A short string description of why the punishment was enacted, provided by the `moderator`.
54-
* `active`: A boolean representing whether the punishment is currently effective. If set to `false`, the expiration should not be considered. If set to `true`, the expiration should still be checked. Expired punishments will never be active, even though this field may be set to `true`.
73+
* `punishments`: A list of punishments that the player has. Punishments are not removed from the list when revoked or
74+
expired.
75+
* `type`: A string from the following list: `BAN`, `MUTE`.
76+
* `id`: A UUID (represented as a string) that uniquely identifies the punishment.
77+
* `issuedAt`: The time the punishment was issued, represented as a Unix timestamp (milliseconds after Jan 01, 1970,
78+
00:00:00 GMT)
79+
* `expiresAt`: The time the punishment will expire/has expired, represented as a Unix timestamp (milliseconds after
80+
Jan 01, 1970, 00:00:00 GMT)
81+
* `moderator`: The UUID of the player which enacted the punishment.
82+
* `reason`: A short string description of why the punishment was enacted, provided by the `moderator`.
83+
* `active`: A boolean representing whether the punishment is currently effective. If set to `false`, the expiration
84+
should not be considered. If set to `true`, the expiration should still be checked. Expired punishments will never
85+
be active, even though this field may be set to `true`.
5586
* `achievements`: TBD - Not implemented
5687
* `cosmetics`: A list of cosmetics which the player owns. Non-equipped cosmetics are included in the list.
57-
* `id`: A string identifier for the cosmetic
58-
* `equipped`: A boolean representing whether the player has the cosmetic equipped or not.
88+
* `id`: A string identifier for the cosmetic
89+
* `equipped`: A boolean representing whether the player has the cosmetic equipped or not.
90+
5991
### 3.3: Database Behavior
92+
6093
* Every time a player log in to a game server, their player document should be fetched using their UUID.
61-
* If their username does not match the name in the document, it should be updated to reflect the username change.
94+
* If their username does not match the name in the document, it should be updated to reflect the username change.
6295
* Map data should be lazily fetched for a map when it is loaded.
63-
* Player documents are fetched by other services (mainly Puffin) to look up players' metadata. This is typically just their usernames (UUID <=> username conversion) and name colors for display in chat.
96+
* Player documents are fetched by other services (mainly Puffin) to look up players' metadata. This is typically just
97+
their usernames (UUID <=> username conversion) and name colors for display in chat.
98+
6499
## 4: Server Behavior
100+
65101
### 4.1: Proxy Connection
66-
Every game server is connected to at least one proxy. Connections are registered/handled by the proxies, but player information forwarding must be setup.
67-
The server receives a Velocity forwarding secret using the `PUFFIN_VELOCITY_SECRET` environment variable. If this is present, Velocity modern forwarding should be enabled using the provided secret. If not, Mojang authentication (online mode) should be enabled.
102+
103+
Every game server is connected to at least one proxy. Connections are registered/handled by the proxies, but player
104+
information forwarding must be setup.
105+
The server receives a Velocity forwarding secret using the `PUFFIN_VELOCITY_SECRET` environment variable. If this is
106+
present, Velocity modern forwarding should be enabled using the provided secret. If not, Mojang authentication (online
107+
mode) should be enabled.
108+
68109
### 4.2: Lobbies
110+
69111
Each server has at least one lobby, which is initialized on startup.
112+
70113
### 4.3: Agones Integration
114+
71115
When a server starts up, it should contact its local Agones gRPC or HTTP server and send a "Ready" request.
72-
Periodically, health pings should be sent to the same endpoint. If they are not sent, the server will be shut down due to a health check failure.
116+
Periodically, health pings should be sent to the same endpoint. If they are not sent, the server will be shut down due
117+
to a health check failure.
118+
73119
### 4.4: World Loading
120+
74121
Worlds are currently stored in the Anvil format and mounted into each game server's container.
75122
The directory structure looks something like this:
123+
76124
```
77125
/
78126
server/
79127
worlds/
80128
game_name/
81129
map_name_1/
130+
level.dat
131+
config.yml
132+
region/
82133
other_map_name/
134+
level.dat
135+
config.yml
136+
region/
83137
other_game_name/
84138
map_name_1/
139+
level.dat
140+
config.yml
141+
region/
85142
other_map_name/
143+
level.dat
144+
config.yml
145+
region/
86146
```
147+
87148
### 4.5: World Configuration
149+
88150
Each map can have a `config.yml` file inside the Anvil world folder with a few keys.
89151
All map-specific keys are namespaced under the `world` key.
152+
90153
* `world`: (the parent key)
91-
* `name`: A display name for the map
92-
* `description`: A short description of the map, shown to every player at the start of the game.
93-
* `author`: A string with the names of the map's builders.
94-
* `spawnpoints`: A list of spawnpoints for the map.
95-
* Each spawnpoint is an array of numbers with the format: [x, y, z, yaw, pitch]
96-
* The numbers may be integers or doubles. It is the responsibility of the client to convert the numbers to the correct format (usually Double).
97-
* `additionalLocations`: Each map or game may define additional locations. This field is a list of lists of coordinates. The coordinates are in the same format as above.
154+
* `name`: A display name for the map
155+
* `description`: A short description of the map, shown to every player at the start of the game.
156+
* `author`: A string with the names of the map's builders.
157+
* `spawnpoints`: A list of spawnpoints for the map.
158+
* Each spawnpoint is an array of numbers with the format: [x, y, z, yaw, pitch]
159+
* The numbers may be integers or doubles. It is the responsibility of the client to convert the numbers to the
160+
correct format (usually Double).
161+
* `additionalLocations`: Each map or game may define additional locations. This field is a list of lists of
162+
coordinates. The coordinates are in the same format as above.

common/src/main/kotlin/com/bluedragonmc/server/Game.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ abstract class Game(val name: String, val mapName: String, val mode: String? = n
196196
)
197197
)
198198
Environment.queue.queue(player, gameType {
199-
name = "Lobby"
199+
name = Environment.defaultGameName
200200
selectors += GameTypeFieldSelector.GAME_NAME
201201
})
202202
}

common/src/main/kotlin/com/bluedragonmc/server/api/Environment.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ abstract class Environment {
1212
val mongoHostname get() = current.mongoHostname
1313
val dbName get() = current.dbName
1414
val puffinHostname get() = current.puffinHostname
15+
val defaultGameName get() = current.defaultGameName
1516
val gameClasses get() = current.gameClasses
1617
val versionInfo get() = current.versionInfo
1718
val isDev get() = current.isDev
@@ -31,6 +32,7 @@ abstract class Environment {
3132
abstract val gameClasses: Collection<String>
3233
abstract val versionInfo: VersionInfo
3334
abstract val isDev: Boolean
35+
open val defaultGameName: String = "Lobby"
3436
open val dbName: String = "bluedragon"
3537

3638
abstract suspend fun getServerName(): String

common/src/main/kotlin/com/bluedragonmc/server/utils/InstanceUtils.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ object InstanceUtils {
5555
return CompletableFuture.completedFuture(null)
5656
} else {
5757
// If the instance is not empty, attempt to send all players to a lobby
58-
val lobby = Game.games.find { it.name == "Lobby" }
58+
val lobby = Game.games.find { it.name == Environment.defaultGameName }
5959
if (lobby != null) {
6060
val lobbyInstanceOf = lobby.getModule<InstanceModule>()::getSpawningInstance
6161
val spawnpointOf = lobby.getModule<SpawnpointModule>().spawnpointProvider::getSpawnpoint
@@ -72,7 +72,7 @@ object InstanceUtils {
7272
val queueTask: Task = MinecraftServer.getSchedulerManager().buildTask {
7373
instance.players.forEach {
7474
Environment.queue.queue(it, gameType {
75-
name = "Lobby"
75+
name = Environment.defaultGameName
7676
selectors += GameTypeFieldSelector.GAME_NAME
7777
})
7878
}

src/main/kotlin/com/bluedragonmc/server/Server.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ fun start() {
8888

8989
// Create a Lobby instance
9090
lobby = try {
91-
GameLoader.createNewGame("Lobby", null, null)
91+
GameLoader.createNewGame(Environment.defaultGameName, null, null)
9292
} catch (e: Throwable) {
9393
logger.error("There was an error initializing the Lobby. Shutting down...")
9494
e.printStackTrace()

src/main/kotlin/com/bluedragonmc/server/bootstrap/prod/InitialInstanceRouter.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.bluedragonmc.server.bootstrap.prod
22

33
import com.bluedragonmc.server.CustomPlayer
44
import com.bluedragonmc.server.Game
5+
import com.bluedragonmc.server.api.Environment
56
import com.bluedragonmc.server.bootstrap.Bootstrap
67
import com.bluedragonmc.server.model.EventLog
78
import com.bluedragonmc.server.model.Severity
@@ -82,7 +83,7 @@ object InitialInstanceRouter : Bootstrap(EnvType.PRODUCTION) {
8283
} else {
8384
logger.warn("Invalid destination ('$destination') supplied for player ${event.player.username}, sending to Lobby.")
8485
// If no destination was found, send the player to a lobby.
85-
Game.games.find { it.name.lowercase() == "lobby" }
86+
Game.games.find { it.name.equals(Environment.defaultGameName, ignoreCase = true) }
8687
}
8788
val instance = game?.getModule<InstanceModule>()?.getSpawningInstance(event.player)
8889
if (instance == null) {

src/main/kotlin/com/bluedragonmc/server/queue/IPCQueue.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.bluedragonmc.api.grpc.CommonTypes
44
import com.bluedragonmc.api.grpc.GsClient
55
import com.bluedragonmc.api.grpc.PlayerHolderOuterClass.SendPlayerRequest
66
import com.bluedragonmc.server.Game
7+
import com.bluedragonmc.server.api.Environment
78
import com.bluedragonmc.server.api.Queue
89
import com.bluedragonmc.server.lobby
910
import com.bluedragonmc.server.model.EventLog
@@ -27,7 +28,7 @@ object IPCQueue : Queue() {
2728
private val queuedPlayers = mutableListOf<Player>()
2829

2930
override fun queue(player: Player, gameType: CommonTypes.GameType) {
30-
if (gameType.name == "Lobby" && gameType.mapName == null && gameType.mode == null) {
31+
if (gameType.name == Environment.defaultGameName && gameType.mapName == null && gameType.mode == null) {
3132
lobby.addPlayer(player)
3233
return
3334
}

src/main/kotlin/com/bluedragonmc/server/queue/TestQueue.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.bluedragonmc.server.queue
22

33
import com.bluedragonmc.api.grpc.CommonTypes
44
import com.bluedragonmc.server.Game
5+
import com.bluedragonmc.server.api.Environment
56
import com.bluedragonmc.server.api.Queue
67
import com.bluedragonmc.server.lobby
78
import com.github.benmanes.caffeine.cache.Cache
@@ -34,7 +35,7 @@ class TestQueue : Queue() {
3435
* @param gameType The game type which the player wants to join.
3536
*/
3637
override fun queue(player: Player, gameType: CommonTypes.GameType) {
37-
if (gameType.name == "Lobby" && gameType.mapName.isEmpty() && gameType.mode.isEmpty()) {
38+
if (gameType.name == Environment.defaultGameName && gameType.mapName.isEmpty() && gameType.mode.isEmpty()) {
3839
lobby.addPlayer(player)
3940
return
4041
}

src/main/kotlin/com/bluedragonmc/server/queue/environments.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class ConfiguredEnvironment : Environment() {
3636
override val versionInfo: VersionInfo = GitVersionInfo
3737
override val isDev: Boolean = isDev()
3838

39+
override val defaultGameName: String = System.getenv("BLUEDRAGON_DEFAULT_GAME") ?: super.defaultGameName
40+
3941
private lateinit var serverName: String
4042
private val isAgonesEnabled get() = System.getenv("BLUEDRAGON_AGONES_DISABLED") == null
4143

0 commit comments

Comments
 (0)