Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 9 additions & 45 deletions among_them/server.nim
Original file line number Diff line number Diff line change
Expand Up @@ -753,14 +753,6 @@ proc identityIsKicked(identity: string): bool =
identity in appState.kickedIdentities or
rewardIdentity in appState.kickedIdentities

proc identityIsConnected(identity: string): bool =
## Returns true when an identity already has a live websocket.
{.gcsafe.}:
withLock appState.lock:
for _, address in appState.playerAddresses.pairs:
if address == identity:
return true

proc respondKicked(request: Request) =
## Rejects a kicked player before upgrading to a WebSocket.
var headers: HttpHeaders
Expand All @@ -769,20 +761,6 @@ proc respondKicked(request: Request) =
headers["Connection"] = "close"
request.respond(409, headers, "player was kicked\n")

proc respondForbidden(request: Request, body: string) =
## Rejects an unauthorized player request before WebSocket upgrade.
var headers: HttpHeaders
headers["Content-Type"] = "text/plain; charset=utf-8"
headers["Cache-Control"] = "no-cache"
headers["Connection"] = "close"
request.respond(403, headers, body)

proc playerJoinAllowed(request: Request, identity: string, slot: int, token: string): bool =
## Checks configured slot auth before upgrading the player WebSocket.
{.gcsafe.}:
withLock appState.lock:
result = appState.config.playerJoinAllowed(identity, slot, token)

proc httpHandler(request: Request) =
if request.path == HealthPath and request.httpMethod == "GET":
var headers: HttpHeaders
Expand All @@ -798,12 +776,6 @@ proc httpHandler(request: Request) =
if identity.identityIsKicked():
request.respondKicked()
return
if identity.identityIsConnected():
request.respondForbidden("player already connected\n")
return
if not request.playerJoinAllowed(identity, slot, token):
request.respondForbidden("player token rejected\n")
return
let websocket = request.upgradeToWebSocket()
{.gcsafe.}:
withLock appState.lock:
Expand All @@ -820,12 +792,6 @@ proc httpHandler(request: Request) =
if identity.identityIsKicked():
request.respondKicked()
return
if identity.identityIsConnected():
request.respondForbidden("player already connected\n")
return
if not request.playerJoinAllowed(identity, slot, token):
request.respondForbidden("player token rejected\n")
return
let websocket = request.upgradeToWebSocket()
{.gcsafe.}:
withLock appState.lock:
Expand Down Expand Up @@ -1190,10 +1156,8 @@ proc runServerLoop*(
identity in appState.kickedIdentities:
sim.removePlayer(websocket)
socketsToClose.add(websocket)
elif sim.playerAddressOccupied(address):
sim.removePlayer(websocket)
socketsToClose.add(websocket)
elif sim.phase == Lobby and sim.canAddPlayer():
elif sim.phase == Lobby and
(sim.canAddPlayer() or slot >= 0 or token.len > 0):
try:
appState.playerIndices[websocket] = sim.addPlayer(
address,
Expand Down Expand Up @@ -1299,20 +1263,20 @@ proc runServerLoop*(
reconnectSockets.add(websocket)
appState.spectators = @[]
for websocket in reconnectSockets:
if not sim.canAddPlayer():
if websocket in appState.playerViewers:
appState.playerIndices[websocket] = -1
else:
appState.spectators.add(websocket)
appState.playerIndices.del(websocket)
continue
let address = appState.playerAddresses.getOrDefault(
websocket,
"unknown"
)
let
slot = appState.playerSlots.getOrDefault(websocket, -1)
token = appState.playerTokens.getOrDefault(websocket, "")
if not sim.canAddPlayer() and slot < 0 and token.len == 0:
if websocket in appState.playerViewers:
appState.playerIndices[websocket] = -1
else:
appState.spectators.add(websocket)
appState.playerIndices.del(websocket)
continue
try:
appState.playerIndices[websocket] = sim.addPlayer(
address,
Expand Down
53 changes: 9 additions & 44 deletions among_them/sim.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1478,6 +1478,14 @@ proc canAddPlayer*(sim: SimServer): bool =
## Returns whether the game has room for another player.
sim.players.len < sim.config.playerSlotLimit()

proc playerLimitError(config: GameConfig): string =
## Returns a user-facing message for the current player cap.
if config.closedRoster:
let limit = config.playerSlotLimit()
return "Configured roster is full (" & $limit &
(if limit == 1: " player)." else: " players).")
"can't do more than " & $MaxPlayers & " players."

proc slotConfig(config: GameConfig, slotIndex: int): PlayerSlotConfig =
## Returns one slot config or an empty config for missing entries.
if slotIndex >= 0 and slotIndex < config.slots.len:
Expand Down Expand Up @@ -1537,49 +1545,6 @@ proc configuredPlayerName*(config: GameConfig, requestedSlot: int, token: string
return slot.name
""

proc matchingConfiguredSlot(
config: GameConfig,
address,
token: string
): int =
## Returns a configured slot matched by name or token without occupancy checks.
for i in 0 ..< config.slots.len:
let slot = config.slots[i]
let couldMatchName = slot.name.len > 0 and slot.name == address
let couldMatchToken = slot.token.len > 0 and slot.token == token
if (couldMatchName or couldMatchToken) and
config.slotAuthMatches(i, address, token):
return i
-1

proc conflictingConfiguredSlot(
config: GameConfig,
address,
token: string
): int =
## Returns a configured slot matched by only one required credential.
for i in 0 ..< config.slots.len:
let slot = config.slots[i]
let matchedName = slot.name.len > 0 and slot.name == address
let matchedToken =
slot.token.len > 0 and token.len > 0 and slot.token == token
if (matchedName or matchedToken) and
not config.slotAuthMatches(i, address, token):
return i
-1

proc playerJoinAllowed*(config: GameConfig, address: string, requestedSlot: int, token: string): bool =
## Returns whether a player websocket request can pass configured slot auth.
if requestedSlot >= config.playerSlotLimit():
return false
if requestedSlot >= 0:
return config.slotAuthMatches(requestedSlot, address, token)
if config.matchingConfiguredSlot(address, token) >= 0:
return true
if config.conflictingConfiguredSlot(address, token) >= 0:
return false
not config.closedRoster

proc slotOccupied(sim: SimServer, slotIndex: int): bool =
## Returns true when a player already owns a slot.
for player in sim.players:
Expand Down Expand Up @@ -1793,7 +1758,7 @@ proc addPlayer*(
): int =
## Adds one player, optionally validating and using a requested slot.
if not sim.canAddPlayer():
raise newException(AmongThemError, "can't do more than 16 players.")
raise newException(AmongThemError, sim.config.playerLimitError())
if sim.playerAddressOccupied(address):
raise newException(
AmongThemError,
Expand Down