diff --git a/code/modules/deathmatch/deathmatch_controller.dm b/code/modules/deathmatch/deathmatch_controller.dm
new file mode 100644
index 00000000000..c288daefd4e
--- /dev/null
+++ b/code/modules/deathmatch/deathmatch_controller.dm
@@ -0,0 +1,125 @@
+/datum/deathmatch_controller
+ /// Assoc list of all lobbies (ckey = lobby)
+ var/list/datum/deathmatch_lobby/lobbies = list()
+ /// All deathmatch map templates
+ var/list/datum/lazy_template/deathmatch/maps = list()
+ /// All loadouts
+ var/list/datum/outfit/loadouts
+
+/datum/deathmatch_controller/New()
+ . = ..()
+ if (GLOB.deathmatch_game)
+ qdel(src)
+ CRASH("A deathmatch controller already exists.")
+ GLOB.deathmatch_game = src
+
+ for (var/datum/lazy_template/deathmatch/template as anything in subtypesof(/datum/lazy_template/deathmatch))
+ var/map_name = initial(template.name)
+ maps[map_name] = new template
+ loadouts = subtypesof(/datum/outfit/deathmatch_loadout)
+
+/datum/deathmatch_controller/proc/create_new_lobby(mob/host)
+ lobbies[host.ckey] = new /datum/deathmatch_lobby(host)
+ deadchat_broadcast(" has opened a new deathmatch lobby. (Join)", "[host]")
+
+/datum/deathmatch_controller/proc/remove_lobby(ckey)
+ var/lobby = lobbies[ckey]
+ lobbies[ckey] = null
+ lobbies.Remove(ckey)
+ qdel(lobby)
+
+/datum/deathmatch_controller/proc/passoff_lobby(host, new_host)
+ lobbies[new_host] = lobbies[host]
+ lobbies[host] = null
+ lobbies.Remove(host)
+
+/datum/deathmatch_controller/ui_state(mob/user)
+ return GLOB.observer_state
+
+/datum/deathmatch_controller/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, null)
+ if(!ui)
+ ui = new(user, src, "DeathmatchPanel")
+ ui.open()
+
+/datum/deathmatch_controller/ui_data(mob/user)
+ . = ..()
+ .["lobbies"] = list()
+ .["hosting"] = FALSE
+ .["admin"] = check_rights_for(user.client, R_ADMIN)
+ for (var/ckey in lobbies)
+ var/datum/deathmatch_lobby/lobby = lobbies[ckey]
+ if (user.ckey == ckey)
+ .["hosting"] = TRUE
+ if (user.ckey in lobby.observers+lobby.players)
+ .["playing"] = ckey
+ .["lobbies"] += list(list(
+ name = ckey,
+ players = lobby.players.len,
+ max_players = initial(lobby.map.max_players),
+ map = initial(lobby.map.name),
+ playing = lobby.playing
+ ))
+
+/datum/deathmatch_controller/proc/find_lobby_by_user(ckey)
+ for(var/lobbykey in lobbies)
+ var/datum/deathmatch_lobby/lobby = lobbies[lobbykey]
+ if(ckey in lobby.players+lobby.observers)
+ return lobby
+
+/datum/deathmatch_controller/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(. || !isobserver(usr))
+ return
+ switch (action)
+ if ("host")
+ if (lobbies[usr.ckey])
+ return
+ if(!SSticker.HasRoundStarted())
+ tgui_alert(usr, "The round hasn't started yet!")
+ return
+ ui.close()
+ create_new_lobby(usr)
+ if ("join")
+ if (!lobbies[params["id"]])
+ return
+ var/datum/deathmatch_lobby/playing_lobby = find_lobby_by_user(usr.ckey)
+ var/datum/deathmatch_lobby/chosen_lobby = lobbies[params["id"]]
+ if (!isnull(playing_lobby) && playing_lobby != chosen_lobby)
+ playing_lobby.leave(usr.ckey)
+
+ if(isnull(playing_lobby))
+ log_game("[usr.ckey] joined deathmatch lobby [params["id"]] as a player.")
+ chosen_lobby.join(usr)
+
+ chosen_lobby.ui_interact(usr)
+ if ("spectate")
+ var/datum/deathmatch_lobby/playing_lobby = find_lobby_by_user(usr.ckey)
+ if (!lobbies[params["id"]])
+ return
+ var/datum/deathmatch_lobby/chosen_lobby = lobbies[params["id"]]
+ // if the player is in this lobby
+ if(!isnull(playing_lobby) && playing_lobby != chosen_lobby)
+ playing_lobby.leave(usr.ckey)
+ else if(playing_lobby == chosen_lobby)
+ chosen_lobby.ui_interact(usr)
+ return
+ // they werent in the lobby, lets add them
+ if (!chosen_lobby.playing)
+ chosen_lobby.add_observer(usr)
+ chosen_lobby.ui_interact(usr)
+ else
+ chosen_lobby.spectate(usr)
+ log_game("[usr.ckey] joined deathmatch lobby [params["id"]] as an observer.")
+ if ("admin")
+ if (!check_rights(R_ADMIN))
+ message_admins("[usr.key] has attempted to use admin functions in the deathmatch panel!")
+ log_admin("[key_name(usr)] tried to use the deathmatch panel admin functions without authorization.")
+ return
+ var/lobby = params["id"]
+ switch (params["func"])
+ if ("Close")
+ remove_lobby(lobby)
+ log_admin("[key_name(usr)] removed deathmatch lobby [lobby].")
+ if ("View")
+ lobbies[lobby].ui_interact(usr)
diff --git a/code/modules/deathmatch/deathmatch_lobby.dm b/code/modules/deathmatch/deathmatch_lobby.dm
new file mode 100644
index 00000000000..bd5897c1da2
--- /dev/null
+++ b/code/modules/deathmatch/deathmatch_lobby.dm
@@ -0,0 +1,440 @@
+/datum/deathmatch_lobby
+ /// Ckey of the host
+ var/host
+ /// Assoc list of ckey to list()
+ var/list/players = list()
+ /// Assoc list of ckey to list()
+ var/list/observers = list()
+ /// The current chosen map
+ var/datum/lazy_template/deathmatch/map
+ /// Our turf reservation AKA where the arena is
+ var/datum/turf_reservation/location
+ /// Whether players hear deadchat and people through walls
+ var/global_chat = FALSE
+ /// Whether the lobby is currently playing
+ var/playing = FALSE
+ /// Number of total ready players
+ var/ready_count
+ /// List of loadouts, either gotten from the deathmatch controller or the map
+ var/list/loadouts
+ /// Current map player spawn locations, cleared after spawning
+ var/list/player_spawns = list()
+
+/datum/deathmatch_lobby/New(mob/player)
+ . = ..()
+ if (!player)
+ stack_trace("Attempted to create a deathmatch lobby without a host.")
+ return qdel(src)
+ host = player.ckey
+ map = GLOB.deathmatch_game.maps[pick(GLOB.deathmatch_game.maps)]
+ log_game("[host] created a deathmatch lobby.")
+ if (map.allowed_loadouts)
+ loadouts = map.allowed_loadouts
+ else
+ loadouts = GLOB.deathmatch_game.loadouts
+ add_player(player, loadouts[1], TRUE)
+ ui_interact(player)
+
+/datum/deathmatch_lobby/Destroy(force, ...)
+ . = ..()
+ for (var/key in players+observers)
+ var/datum/tgui/ui = SStgui.get_open_ui(get_mob_by_ckey(key), src)
+ if (ui) ui.close()
+ remove_ckey_from_play(key)
+ if(playing && !isnull(location))
+ clear_reservation()
+ players = null
+ observers = null
+ map = null
+ location = null
+ loadouts = null
+
+/datum/deathmatch_lobby/proc/start_game()
+ if (playing)
+ return
+ playing = TRUE
+
+ RegisterSignal(map, COMSIG_LAZY_TEMPLATE_LOADED, PROC_REF(find_spawns_and_start_delay))
+ location = map.lazy_load()
+ if (!location)
+ to_chat(get_mob_by_ckey(host), span_warning("Couldn't reserve/load a map location (all locations used?), try again later, or contact a coder."))
+ playing = FALSE
+ UnregisterSignal(map, COMSIG_LAZY_TEMPLATE_LOADED)
+ return FALSE
+
+/datum/deathmatch_lobby/proc/find_spawns_and_start_delay(datum/lazy_template/source, list/atoms)
+ SIGNAL_HANDLER
+ for(var/thing in atoms)
+ if(istype(thing, /obj/effect/landmark/deathmatch_player_spawn))
+ player_spawns += thing
+
+ UnregisterSignal(source, COMSIG_LAZY_TEMPLATE_LOADED)
+ addtimer(CALLBACK(src, PROC_REF(start_game_after_delay)), 8 SECONDS)
+
+/datum/deathmatch_lobby/proc/start_game_after_delay()
+ if (!length(player_spawns) || length(player_spawns) < length(players))
+ stack_trace("Failed to get spawns when loading deathmatch map [map.name] for lobby [host].")
+ clear_reservation()
+ playing = FALSE
+ return FALSE
+
+ for (var/key in players)
+ var/mob/dead/observer/observer = players[key]["mob"]
+ if (isnull(observer) || !observer.client)
+ log_game("Removed player [key] from deathmatch lobby [host], as they couldn't be found.")
+ remove_ckey_from_play(key)
+ continue
+
+ // pick spawn and remove it.
+ var/picked_spawn = pick_n_take(player_spawns)
+ spawn_observer_as_player(key, get_turf(picked_spawn))
+ qdel(picked_spawn)
+
+ // Remove rest of spawns.
+ QDEL_LIST(player_spawns)
+
+ for (var/observer_key in observers)
+ var/mob/observer = observers[observer_key]["mob"]
+ observer.forceMove(pick(location.reserved_turfs))
+
+ addtimer(CALLBACK(src, PROC_REF(game_took_too_long)), initial(map.automatic_gameend_time))
+ log_game("Deathmatch game [host] started.")
+ announce(span_reallybig("GO!"))
+ return TRUE
+
+/datum/deathmatch_lobby/proc/spawn_observer_as_player(ckey, loc)
+ var/mob/dead/observer/observer = players[ckey]["mob"]
+ if (isnull(observer) || !observer.client)
+ remove_ckey_from_play(ckey)
+ return
+
+ // equip player
+ var/datum/outfit/deathmatch_loadout/loadout = players[ckey]["loadout"]
+ if (!(loadout in loadouts))
+ loadout = loadouts[1]
+
+ observer.forceMove(loc)
+ var/datum/mind/observer_mind = observer.mind
+ var/mob/living/observer_current = observer.mind?.current
+ var/mob/living/carbon/human/new_player = observer.change_mob_type(/mob/living/carbon/human, delete_old_mob = TRUE)
+ if(!isnull(observer_mind) && observer_current)
+ new_player.AddComponent( \
+ /datum/component/temporary_body, \
+ old_mind = observer_mind, \
+ old_body = observer_current, \
+ )
+ new_player.equipOutfit(loadout) // Loadout
+ players[ckey]["mob"] = new_player
+
+ // register death handling.
+ RegisterSignals(new_player, list(COMSIG_LIVING_DEATH, COMSIG_MOB_GHOSTIZED, COMSIG_QDELETING), PROC_REF(player_died))
+ if (global_chat)
+ ADD_TRAIT(new_player, TRAIT_SIXTHSENSE, INNATE_TRAIT)
+ ADD_TRAIT(new_player, TRAIT_XRAY_HEARING, INNATE_TRAIT)
+
+/datum/deathmatch_lobby/proc/game_took_too_long()
+ if (!location || QDELING(src))
+ return
+ announce(span_reallybig("The players have took too long! Game ending!"))
+ end_game()
+
+/datum/deathmatch_lobby/proc/end_game()
+ if (!location)
+ CRASH("Reservation of deathmatch game [host] deleted during game.")
+ var/mob/winner
+ if(players.len)
+ var/list/winner_info = players[pick(players)]
+ if(!isnull(winner_info["mob"]))
+ winner = winner_info["mob"] //only one should remain anyway but incase of a draw
+
+ announce(span_reallybig("THE GAME HAS ENDED.
THE WINNER IS: [winner ? winner.real_name : "no one"]."))
+
+ for(var/ckey in players)
+ var/mob/loser = players[ckey]["mob"]
+ UnregisterSignal(loser, list(COMSIG_MOB_GHOSTIZED, COMSIG_QDELETING))
+ players[ckey]["mob"] = null
+ loser.ghostize()
+ qdel(loser)
+
+ clear_reservation()
+ GLOB.deathmatch_game.remove_lobby(host)
+ log_game("Deathmatch game [host] ended.")
+
+/datum/deathmatch_lobby/proc/player_died(mob/living/player, gibbed)
+ SIGNAL_HANDLER
+ if(isnull(player) || QDELING(src))
+ return
+
+ var/ckey = player.ckey
+ if(!islist(players[ckey])) // potentially the player info could hold a reference to this mob so we can figure the ckey out without worrying about ghosting and suicides n such
+ for(var/potential_ckey in players)
+ var/list/player_info = players[potential_ckey]
+ if(player_info["mob"] && player_info["mob"] == player)
+ ckey = potential_ckey
+ break
+
+ if(!islist(players[ckey])) // if we STILL didnt find a good ckey
+ return
+
+ players -= ckey
+
+ var/mob/dead/observer/ghost = !player.client ? player.get_ghost() : player.ghostize() //this doesnt work on those who used the ghost verb
+ if(!isnull(ghost))
+ add_observer(ghost, (host == ckey))
+
+ announce(span_reallybig("[player.real_name] HAS DIED.
[players.len] REMAIN."))
+
+ if(!gibbed && !QDELING(player)) // for some reason dusting or deleting in chasm storage messes up tgui bad
+ player.dust(TRUE, TRUE, TRUE)
+ if (players.len <= 1)
+ end_game()
+
+/datum/deathmatch_lobby/proc/add_observer(mob/mob, host = FALSE)
+ if (players[mob.ckey])
+ CRASH("Tried to add [mob.ckey] as an observer while being a player.")
+ observers[mob.ckey] = list("mob" = mob, "host" = host)
+
+/datum/deathmatch_lobby/proc/add_player(mob/mob, loadout, host = FALSE)
+ if (observers[mob.ckey])
+ CRASH("Tried to add [mob.ckey] as a player while being an observer.")
+ players[mob.ckey] = list("mob" = mob, "host" = host, "ready" = FALSE, "loadout" = loadout)
+
+/datum/deathmatch_lobby/proc/remove_ckey_from_play(ckey)
+ var/is_likely_player = (ckey in players)
+ var/list/main_list = is_likely_player ? players : observers
+ var/list/info = main_list[ckey]
+ if(is_likely_player && islist(info))
+ ready_count -= info["ready"]
+ main_list -= ckey
+
+/datum/deathmatch_lobby/proc/announce(message)
+ for (var/key in players+observers)
+ var/mob/player = get_mob_by_ckey(key)
+ if (!player.client)
+ remove_ckey_from_play(key)
+ continue
+ to_chat(player.client, message)
+
+/datum/deathmatch_lobby/proc/leave(ckey)
+ if (host == ckey)
+ var/total_count = players.len + observers.len
+ if (total_count <= 1) // <= just in case.
+ GLOB.deathmatch_game.remove_lobby(host)
+ return
+ else
+ if (players[ckey] && players.len <= 1)
+ for (var/key in observers)
+ if (host == key)
+ continue
+ host = key
+ observers[key]["host"] = TRUE
+ break
+ else
+ for (var/key in players)
+ if (host == key)
+ continue
+ host = key
+ players[key]["host"] = TRUE
+ break
+ GLOB.deathmatch_game.passoff_lobby(ckey, host)
+
+ remove_ckey_from_play(ckey)
+
+/datum/deathmatch_lobby/proc/join(mob/player)
+ if (playing || !player)
+ return
+ if(!(player.ckey in players+observers))
+ if (players.len >= map.max_players)
+ add_observer(player)
+ else
+ add_player(player, loadouts[1])
+ ui_interact(player)
+
+/datum/deathmatch_lobby/proc/spectate(mob/player)
+ if (!playing || !location || !player)
+ return
+ if (!observers[player.ckey])
+ add_observer(player)
+ player.forceMove(pick(location.reserved_turfs))
+
+/datum/deathmatch_lobby/proc/change_map(new_map)
+ if (!new_map || !GLOB.deathmatch_game.maps[new_map])
+ return
+ map = GLOB.deathmatch_game.maps[new_map]
+ var/max_players = map.max_players
+ for (var/possible_unlucky_loser in players)
+ max_players--
+ if (max_players <= 0)
+ var/loser_mob = players[possible_unlucky_loser]["mob"]
+ remove_ckey_from_play(possible_unlucky_loser)
+ add_observer(loser_mob)
+
+ loadouts = map.allowed_loadouts ? map.allowed_loadouts : GLOB.deathmatch_game.loadouts
+ for (var/player_key in players)
+ if (players[player_key]["loadout"] in loadouts)
+ continue
+ players[player_key]["loadout"] = loadouts[1]
+
+/datum/deathmatch_lobby/proc/clear_reservation()
+ if(isnull(location) || isnull(map))
+ return
+ for(var/turf/victimized_turf as anything in location.reserved_turfs) //remove this once clearing turf reservations is actually reliable
+ victimized_turf.empty()
+ map.reservations -= location
+ qdel(location)
+
+/datum/deathmatch_lobby/Topic(href, href_list) //This handles the chat Join button href, supposedly
+ var/mob/dead/observer/ghost = usr
+ if (!istype(ghost))
+ return
+ if(href_list["join"])
+ join(ghost)
+
+/datum/deathmatch_lobby/ui_state(mob/user)
+ return GLOB.observer_state
+
+/datum/deathmatch_lobby/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, null)
+ if(!ui)
+ ui = new(user, src, "DeathmatchLobby")
+ ui.open()
+
+/datum/deathmatch_lobby/ui_static_data(mob/user)
+ . = list()
+ .["maps"] = list()
+ for (var/map_key in GLOB.deathmatch_game.maps)
+ .["maps"] += map_key
+
+/datum/deathmatch_lobby/ui_data(mob/user)
+ . = list()
+ .["self"] = user.ckey
+ .["host"] = (user.ckey == host)
+ .["admin"] = check_rights_for(user.client, R_ADMIN)
+ .["global_chat"] = global_chat
+ .["playing"] = playing
+ .["loadouts"] = list()
+ for (var/datum/outfit/deathmatch_loadout/loadout as anything in loadouts)
+ .["loadouts"] += initial(loadout.display_name)
+ .["map"] = list()
+ .["map"]["name"] = map.name
+ .["map"]["desc"] = map.desc
+ .["map"]["time"] = map.automatic_gameend_time
+ .["map"]["min_players"] = map.min_players
+ .["map"]["max_players"] = map.max_players
+ if(!isnull(players[user.ckey]) && !isnull(players[user.ckey]["loadout"]))
+ var/datum/outfit/deathmatch_loadout/loadout = players[user.ckey]["loadout"]
+ .["loadoutdesc"] = initial(loadout.desc)
+ else
+ .["loadoutdesc"] = "You are an observer! As an observer, you can hear lobby announcements."
+ .["players"] = list()
+ for (var/player_key in players)
+ var/list/player_info = players[player_key]
+ var/mob/player_mob = player_info["mob"]
+ if (isnull(player_mob) || !player_mob.client)
+ leave(player_key)
+ continue
+ .["players"][player_key] = player_info.Copy()
+ var/datum/outfit/deathmatch_loadout/dm_loadout = player_info["loadout"]
+ .["players"][player_key]["loadout"] = initial(dm_loadout.display_name)
+ .["observers"] = list()
+ for (var/observer_key in observers)
+ var/mob/observer = observers[observer_key]["mob"]
+ if (isnull(observer) || !observer.client)
+ leave(observer_key)
+ continue
+ .["observers"][observer_key] = observers[observer_key]
+
+/datum/deathmatch_lobby/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(. || !isobserver(usr))
+ return
+ switch(action)
+ if ("start_game")
+ if (usr.ckey != host)
+ return FALSE
+ if (map.min_players > players.len)
+ to_chat(usr, span_warning("Not enough players to start yet."))
+ return FALSE
+ start_game()
+ return TRUE
+ if ("leave_game")
+ if (playing)
+ return FALSE
+ leave(usr.ckey)
+ ui.close()
+ GLOB.deathmatch_game.ui_interact(usr)
+ return TRUE
+ if ("change_loadout")
+ if (playing)
+ return FALSE
+ if (params["player"] != usr.ckey && host != usr.ckey)
+ return FALSE
+ for (var/datum/outfit/deathmatch_loadout/possible_loadout as anything in loadouts)
+ if (params["loadout"] != initial(possible_loadout.display_name))
+ continue
+ players[params["player"]]["loadout"] = possible_loadout
+ break
+ return TRUE
+ if ("observe")
+ if (playing)
+ return FALSE
+ if (players[usr.ckey])
+ remove_ckey_from_play(usr.ckey)
+ add_observer(usr, host == usr.ckey)
+ return TRUE
+ else if (observers[usr.ckey] && players.len < map.max_players)
+ remove_ckey_from_play(usr.ckey)
+ add_player(usr, loadouts[1], host == usr.ckey)
+ return TRUE
+ if ("ready")
+ players[usr.ckey]["ready"] ^= 1 // Toggle.
+ ready_count += (players[usr.ckey]["ready"] * 2) - 1 // scared?
+ if (ready_count >= players.len && players.len >= map.min_players)
+ start_game()
+ return TRUE
+ if ("host") // Host functions
+ if (playing || (usr.ckey != host && !check_rights(R_ADMIN)))
+ return FALSE
+ var/uckey = params["id"]
+ switch (params["func"])
+ if ("Kick")
+ leave(uckey)
+ var/umob = get_mob_by_ckey(uckey)
+ var/datum/tgui/uui = SStgui.get_open_ui(umob, src)
+ uui?.close()
+ GLOB.deathmatch_game.ui_interact(umob)
+ return TRUE
+ if ("Transfer host")
+ if (host == uckey)
+ return FALSE
+ GLOB.deathmatch_game.passoff_lobby(host, uckey)
+ host = uckey
+ return TRUE
+ if ("Toggle observe")
+ var/umob = get_mob_by_ckey(uckey)
+ if (players[uckey])
+ remove_ckey_from_play(uckey)
+ add_observer(umob, host == uckey)
+ else if (observers[uckey] && players.len < map.max_players)
+ remove_ckey_from_play(uckey)
+ add_player(umob, loadouts[1], host == uckey)
+ return TRUE
+ if ("change_map")
+ if (!(params["map"] in GLOB.deathmatch_game.maps))
+ return FALSE
+ change_map(params["map"])
+ return TRUE
+ if ("global_chat")
+ global_chat = !global_chat
+ return TRUE
+ if ("admin") // Admin functions
+ if (!check_rights(R_ADMIN))
+ message_admins("[usr.key] has attempted to use admin functions in a deathmatch lobby!")
+ log_admin("[key_name(usr)] tried to use the deathmatch lobby admin functions without authorization.")
+ return
+ switch (params["func"])
+ if ("Force start")
+ log_admin("[key_name(usr)] force started deathmatch lobby [host].")
+ start_game()
+
+
diff --git a/code/modules/deathmatch/deathmatch_mapping.dm b/code/modules/deathmatch/deathmatch_mapping.dm
new file mode 100644
index 00000000000..62eca161583
--- /dev/null
+++ b/code/modules/deathmatch/deathmatch_mapping.dm
@@ -0,0 +1,12 @@
+/area/deathmatch
+ name = "Deathmatch Arena"
+ requires_power = FALSE
+ has_gravity = STANDARD_GRAVITY
+ area_flags = UNIQUE_AREA | UNIQUE_AREA
+
+/area/deathmatch/fullbright
+ static_lighting = FALSE
+ base_lighting_alpha = 255
+
+/obj/effect/landmark/deathmatch_player_spawn
+ name = "Deathmatch Player Spawner"
diff --git a/tgui/packages/tgui/interfaces/DeathmatchLobby.tsx b/tgui/packages/tgui/interfaces/DeathmatchLobby.tsx
new file mode 100644
index 00000000000..6c1a2221a4d
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DeathmatchLobby.tsx
@@ -0,0 +1,212 @@
+import { BooleanLike } from 'common/react';
+
+import { useBackend } from '../backend';
+import {
+ Box,
+ Button,
+ Divider,
+ Dropdown,
+ Flex,
+ Icon,
+ Section,
+ Table,
+} from '../components';
+import { ButtonCheckbox } from '../components/Button';
+import { Window } from '../layouts';
+
+type PlayerLike = {
+ [key: string]: {
+ host: number;
+ ready: BooleanLike;
+ };
+};
+
+type Data = {
+ self: string;
+ host: BooleanLike;
+ admin: BooleanLike;
+ global_chat: BooleanLike;
+ playing: BooleanLike;
+ loadouts: string[];
+ maps: string[];
+ map: {
+ name: string;
+ desc: string;
+ time: number;
+ min_players: number;
+ max_players: number;
+ };
+ loadoutdesc: string;
+ players: PlayerLike[];
+ observers: PlayerLike[];
+};
+
+export const DeathmatchLobby = (props) => {
+ const { act, data } = useBackend();
+ return (
+
+
+
+
+
+
+
+
+ Name
+ Loadout
+ Ready
+
+ {Object.keys(data.players).map((player) => (
+
+
+ {!!data.players[player].host && }
+
+
+ {(!(
+ (data.host && !data.players[player].host) ||
+ data.admin
+ ) && {player}) || (
+
+ act('host', {
+ id: player,
+ func: value,
+ })
+ }
+ />
+ )}
+
+
+
+ act('change_loadout', {
+ player: player,
+ loadout: value,
+ })
+ }
+ />
+
+
+ act('ready')}
+ />
+
+
+ ))}
+ {Object.keys(data.observers).map((observer) => (
+
+
+ {(!!data.observers[observer].host && (
+
+ )) || }
+
+
+ {(!(
+ (data.host && !data.observers[observer].host) ||
+ data.admin
+ ) && {observer}) || (
+
+ act('host', {
+ id: observer,
+ func: value,
+ })
+ }
+ />
+ )}
+
+ Observing
+
+ ))}
+
+
+
+
+
+
+ {(!!data.host && (
+
+ act('host', {
+ func: 'change_map',
+ map: value,
+ })
+ }
+ />
+ )) || {data.map.name}}
+
+
+ {data.map.desc}
+
+ Maximum Play Time: {`${data.map.time / 600}min`}
+
+ Min players: {data.map.min_players}
+
+ Max players: {data.map.max_players}
+
+ Current players: {Object.keys(data.players).length}
+
+
+ act('host', {
+ func: 'global_chat',
+ })
+ }
+ />
+
+ Loadout Description
+
+ {data.loadoutdesc}
+ {!!data.playing && (
+ <>
+
+
+ The game is currently in progress, or loading.
+
+ >
+ )}
+
+
+
+
+
+ );
+};