diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm
index 73a307246a..14d54bb1b0 100644
--- a/code/__defines/_planes+layers.dm
+++ b/code/__defines/_planes+layers.dm
@@ -198,7 +198,6 @@ What is the naming convention for planes or layers?
#define LAYER_HUD_ABOVE 4 //Things that reside above items (highlights)
#define PLANE_PLAYER_HUD_ITEMS 96 //Separate layer with which to apply colorblindness
#define PLANE_PLAYER_HUD_ABOVE 97 //Things above the player hud
-#define PLANE_PLAYER_SPLASH 98 //Splash screen //CHOMPEdit
#define RADIAL_BACKGROUND_LAYER 0
///1000 is an unimportant number, it's just to normalize copied layers
diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm
index 3852940e67..28a4e27f93 100644
--- a/code/__defines/subsystems.dm
+++ b/code/__defines/subsystems.dm
@@ -119,7 +119,6 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
// Subsystem init_order, from highest priority to lowest priority
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.
-#define INIT_ORDER_TITLE 99 //CHOMPEdit
#define INIT_ORDER_SERVER_MAINT 93
#define INIT_ORDER_ADMIN_VERBS 84 // needs to be pretty high, admins can't do much without it
#define INIT_ORDER_WEBHOOKS 50
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index cc0398c3cb..b884dd4e7c 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -706,51 +706,6 @@
/obj/screen/setup_preview/bg/Click(params)
pref?.bgstate = next_in_list(pref.bgstate, pref.bgstate_options)
pref?.update_preview_icon()
-
-/obj/screen/splash
- screen_loc = "1,1"
- layer = LAYER_HUD_ABOVE
- plane = PLANE_PLAYER_HUD_ABOVE
- var/client/holder
-
-INITIALIZE_IMMEDIATE(/obj/screen/splash)
-/obj/screen/splash/Initialize(mapload, visible)
- . = ..()
-
- if(!isclient(loc))
- return INITIALIZE_HINT_QDEL
-
- holder = loc
-
- if(!visible)
- alpha = 0
-
- if(!lobby_image)
- return INITIALIZE_HINT_QDEL
-
- icon = lobby_image.icon
- icon_state = lobby_image.icon_state
-
- holder.screen += src
-
-/obj/screen/splash/proc/Fade(out, qdel_after = TRUE)
- if(QDELETED(src))
- return
- if(out)
- animate(src, alpha = 0, time = 30)
- else
- alpha = 0
- animate(src, alpha = 255, time = 30)
- if(qdel_after)
- QDEL_IN(src, 30)
-
-/obj/screen/splash/Destroy()
- if(holder)
- holder.screen -= src
- holder = null
- return ..()
-
-
/**
* This object holds all the on-screen elements of the mapping unit.
* It has a decorative frame and onscreen buttons. The map itself is drawn
diff --git a/code/controllers/subsystems/tgui.dm b/code/controllers/subsystems/tgui.dm
index a03e3452c3..1563be683d 100644
--- a/code/controllers/subsystems/tgui.dm
+++ b/code/controllers/subsystems/tgui.dm
@@ -279,7 +279,7 @@ SUBSYSTEM_DEF(tgui)
if(length(user?.tgui_open_uis) == 0)
return count
for(var/datum/tgui/ui in user.tgui_open_uis)
- if(isnull(src_object) || ui.src_object == src_object)
+ if((isnull(src_object) || ui.src_object == src_object) && ui.closeable)
ui.close(logout = logout)
count++
return count
diff --git a/code/controllers/subsystems/ticker.dm b/code/controllers/subsystems/ticker.dm
index b00fbac78f..907eb83f32 100644
--- a/code/controllers/subsystems/ticker.dm
+++ b/code/controllers/subsystems/ticker.dm
@@ -421,8 +421,6 @@ var/global/datum/controller/subsystem/ticker/ticker
if(new_char)
qdel(player)
if(new_char.client)
- var/obj/screen/splash/S = new(new_char.client, TRUE)
- S.Fade(TRUE)
new_char.client.init_verbs()
// If they're a carbon, they can get manifested
diff --git a/code/controllers/subsystems/title_ch.dm b/code/controllers/subsystems/title_ch.dm
deleted file mode 100644
index e70ae8662b..0000000000
--- a/code/controllers/subsystems/title_ch.dm
+++ /dev/null
@@ -1,96 +0,0 @@
-SUBSYSTEM_DEF(title)
- name = "Title Screen"
- flags = SS_NO_FIRE
- init_order = INIT_ORDER_TITLE
-// init_stage = INITSTAGE_EARLY
-
- var/file_path
- var/icon/icon
- var/icon_state
- var/icon/previous_icon
- var/turf/closed/indestructible/splashscreen/splash_turf
-
-/datum/controller/subsystem/title/Initialize()
- if(file_path && icon)
- return SS_INIT_SUCCESS
- /*
- if(fexists("data/previous_title.dat"))
- var/previous_path = file2text("data/previous_title.dat")
- if(istext(previous_path))
- previous_icon = new(previous_icon)
- fdel("data/previous_title.dat")
-
- var/list/provisional_title_screens = flist("[global.config.directory]/title_screens/images/")
- var/list/title_screens = list()
- var/use_rare_screens = prob(1)
-
- for(var/S in provisional_title_screens)
- var/list/L = splittext(S,"+")
- if((L.len == 1 && (L[1] != "exclude" && L[1] != "blank.png")) || (L.len > 1 && ((use_rare_screens && LOWER_TEXT(L[1]) == "rare") || (LOWER_TEXT(L[1]) == LOWER_TEXT(SSmapping.config.map_name)))))
- title_screens += S
-
- if(length(title_screens))
- file_path = "[global.config.directory]/title_screens/images/[pick(title_screens)]"
-
- if(!file_path)
- file_path = "icons/runtime/default_title.dmi"
-
- ASSERT(fexists(file_path))
-
- icon = new(fcopy_rsc(file_path))
- */
- icon = new(using_map.lobby_icon)
- var/known_icon_states = cached_icon_states(icon)
- for(var/lobby_screen in using_map.lobby_screens)
- if(!(lobby_screen in known_icon_states))
- error("Lobby screen '[lobby_screen]' did not exist in the icon set [icon].")
- using_map.lobby_screens -= lobby_screen
-
- if(using_map.lobby_screens.len)
- icon_state = pick(using_map.lobby_screens)
- else
- icon_state = known_icon_states[1]
-
- if(splash_turf)
- splash_turf.icon = icon
- splash_turf.icon_state = icon_state
- splash_turf.handle_generic_titlescreen_sizes()
-
- return SS_INIT_SUCCESS
-
-/datum/controller/subsystem/title/vv_edit_var(var_name, var_value)
- . = ..()
- if(.)
- switch(var_name)
- if(NAMEOF(src, icon))
- if(splash_turf)
- splash_turf.icon = icon
-
-/datum/controller/subsystem/title/Shutdown()
- /*if(file_path)
- var/F = file("data/previous_title.dat")
- WRITE_FILE(F, file_path)*/
-
- /*for(var/thing in GLOB.clients)
- if(!thing)
- continue
- var/atom/movable/screen/splash/S = new(thing, FALSE)
- S.Fade(FALSE,FALSE)*/
-
-/datum/controller/subsystem/title/Recover()
- icon = SStitle.icon
- splash_turf = SStitle.splash_turf
- file_path = SStitle.file_path
- previous_icon = SStitle.previous_icon
-
-// Must be immediate because players will
-// join before SSatom initializes everything.
-INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player)
-
-/obj/effect/landmark/start/new_player
- name = "New Player"
-
-/obj/effect/landmark/start/new_player/Initialize(mapload)
- ..()
- GLOB.newplayer_start += loc
- return INITIALIZE_HINT_QDEL
diff --git a/code/datums/managed_browsers/feedback_form.dm b/code/datums/managed_browsers/feedback_form.dm
index 3717e3e16a..f04d162b0a 100644
--- a/code/datums/managed_browsers/feedback_form.dm
+++ b/code/datums/managed_browsers/feedback_form.dm
@@ -141,7 +141,4 @@ GENERAL_PROTECT_DATUM(/datum/managed_browser/feedback_form)
return
my_client.mob << browse(null, "window=[browser_id]") // Closes the window.
- if(isnewplayer(my_client.mob))
- var/mob/new_player/NP = my_client.mob
- NP.new_player_panel_proc() // So the feedback button goes away, if the user gets put on cooldown.
qdel(src)
diff --git a/code/game/jobs/job_controller.dm b/code/game/jobs/job_controller.dm
index 104c8bedcd..d02118c24c 100644
--- a/code/game/jobs/job_controller.dm
+++ b/code/game/jobs/job_controller.dm
@@ -357,7 +357,6 @@ var/global/datum/controller/occupations/job_master
for(var/mob/new_player/player in unassigned)
if(player.client.prefs.alternate_option == RETURN_TO_LOBBY)
player.ready = 0
- player.new_player_panel_proc()
unassigned -= player
return 1
diff --git a/code/game/turfs/closed/_closed_ch.dm b/code/game/turfs/closed/_closed_ch.dm
deleted file mode 100644
index 8d4df6acbd..0000000000
--- a/code/game/turfs/closed/_closed_ch.dm
+++ /dev/null
@@ -1,14 +0,0 @@
-/turf/closed
- //layer = CLOSED_TURF_LAYER
- //plane = WALL_PLANE
- //turf_flags = IS_SOLID
- opacity = TRUE
- density = TRUE
- blocks_air = TRUE
- //init_air = FALSE
- //rad_insulation = RAD_MEDIUM_INSULATION
- //pass_flags_self = PASSCLOSEDTURF
-/*
-/turf/closed/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
- return FALSE
-*/
diff --git a/code/game/turfs/closed/indestructible_ch.dm b/code/game/turfs/closed/indestructible_ch.dm
deleted file mode 100644
index 1f871f3b3b..0000000000
--- a/code/game/turfs/closed/indestructible_ch.dm
+++ /dev/null
@@ -1,76 +0,0 @@
-/turf/closed/indestructible
- name = "wall"
- desc = "Effectively impervious to conventional methods of destruction."
- icon = 'icons/turf/walls.dmi'
- //explosive_resistance = 50
-/*
-/turf/closed/indestructible/rust_heretic_act()
- return
-
-/turf/closed/indestructible/TerraformTurf(path, new_baseturf, flags, defer_change = FALSE, ignore_air = FALSE)
- return
-
-/turf/closed/indestructible/acid_act(acidpwr, acid_volume, acid_id)
- return FALSE
-*/
-/turf/closed/indestructible/melt()
- return
-
-/turf/closed/indestructible/singularity_act()
- return
-
-/turf/closed/indestructible/ex_act()
- return
-/*
-/turf/closed/indestructible/attackby(obj/item/attacking_item, mob/user, params)
- if(istype(attacking_item, /obj/item/poster) && Adjacent(user))
- return place_poster(attacking_item, user)
-
- return ..()
-*/
-//Splashscreen
-
-/turf/closed/indestructible/splashscreen
- name = "Space Station 13"
- desc = null
- icon = 'icons/misc/loading.dmi'
- icon_state = "loading(old)"
- pixel_x = 0
- plane = PLANE_PLAYER_SPLASH
-
-INITIALIZE_IMMEDIATE(/turf/closed/indestructible/splashscreen)
-
-/turf/closed/indestructible/splashscreen/Initialize(mapload)
- . = ..()
- SStitle.splash_turf = src
- if(SStitle.icon)
- icon = SStitle.icon
- icon_state = SStitle.icon_state
- handle_generic_titlescreen_sizes()
-
-///helper proc that will center the screen if the icon is changed to a generic width, to make admins have to fudge around with pixel_x less. returns null
-/turf/closed/indestructible/splashscreen/proc/handle_generic_titlescreen_sizes()
- var/icon/size_check = icon(SStitle.icon, icon_state)
- var/width = size_check.Width()
- if(width == 480 || width == 500) // 480x480 is nonwidescreen
- pixel_x = 0
- else if(width == 608) // 608x480 is widescreen
- pixel_x = -64
-
-/turf/closed/indestructible/splashscreen/vv_edit_var(var_name, var_value)
- . = ..()
- if(.)
- switch(var_name)
- if(NAMEOF(src, icon))
- SStitle.icon = icon
- handle_generic_titlescreen_sizes()
-
-/turf/closed/indestructible/splashscreen/examine()
- //desc = pick(strings(SPLASH_FILE, "splashes"))
- return ..()
-
-/turf/closed/indestructible/start_area
- name = null
- desc = null
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- icon_state = "0"
diff --git a/code/modules/asset_cache/assets/lobby.dm b/code/modules/asset_cache/assets/lobby.dm
new file mode 100644
index 0000000000..46d61ef305
--- /dev/null
+++ b/code/modules/asset_cache/assets/lobby.dm
@@ -0,0 +1,10 @@
+/datum/asset/simple/lobby_files
+ keep_local_name = TRUE
+ assets = list(
+ "lobby_loading.gif" = 'html/lobby/loading.gif',
+ "load.ogg" = 'sound/lobby/lobby_load.ogg',
+ )
+
+/datum/asset/simple/lobby_files/register()
+ assets["lobby_bg.gif"] = icon(using_map.lobby_icon, pick(using_map.lobby_screens))
+ . = ..()
diff --git a/code/modules/clothing/gloves/miscellaneous_vr.dm b/code/modules/clothing/gloves/miscellaneous_vr.dm
index 04359c3ccb..8f286ed21c 100644
--- a/code/modules/clothing/gloves/miscellaneous_vr.dm
+++ b/code/modules/clothing/gloves/miscellaneous_vr.dm
@@ -17,6 +17,7 @@
item_state = "wedring_s"
/obj/item/clothing/gloves/color
+ name = "gloves"
desc = "A pair of gloves, they don't look special in any way."
item_state_slots = list(slot_r_hand_str = "white", slot_l_hand_str = "white")
icon_state = "latex"
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index d6a5ffcbdd..9d539edaa9 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -31,6 +31,7 @@
log_adminwarn("Notice: [key_name(src)] has the same [matches] as [key_name(M)] (no longer logged in).")
/mob/Login()
+ persistent_ckey = client.ckey
player_list |= src
update_Login_details()
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index bc7dca8590..9507e5ad89 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -246,3 +246,6 @@
var/custom_footstep = FOOTSTEP_MOB_SHOE
VAR_PRIVATE/is_motion_tracking = FALSE // Prevent multiple unsubs and resubs, also used to check if the vis layer is enabled, use has_motiontracking() to get externally.
VAR_PRIVATE/wants_to_see_motion_echos = TRUE
+
+ /// a ckey that persists client logout / ghosting, replaced when a client inhabits the mob
+ var/persistent_ckey
diff --git a/code/modules/mob/new_player/lobby_browser.dm b/code/modules/mob/new_player/lobby_browser.dm
new file mode 100644
index 0000000000..a7349886b0
--- /dev/null
+++ b/code/modules/mob/new_player/lobby_browser.dm
@@ -0,0 +1,162 @@
+/mob/new_player/proc/initialize_lobby_screen()
+ if(!client)
+ return
+
+ var/datum/tgui/ui = SStgui.get_open_ui(src, src)
+ if(ui)
+ ui.close()
+
+ winset(src, "lobby_browser", "is-disabled=false;is-visible=true")
+ // winset(src, "mapwindow.status_bar", "is-visible=false")
+ lobby_window = new(client, "lobby_browser")
+ lobby_window.initialize(
+ assets = list(
+ get_asset_datum(/datum/asset/simple/tgui)
+ )
+ )
+
+ tgui_interact(src)
+
+/mob/new_player/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui, custom_state)
+ . = ..()
+
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "LobbyMenu", window = lobby_window)
+ ui.closeable = FALSE
+ ui.open(preinitialized = TRUE)
+
+/mob/new_player/tgui_state(mob/user)
+ return GLOB.tgui_always_state
+
+/mob/new_player/ui_assets(mob/user)
+ . = ..()
+ . += get_asset_datum(/datum/asset/simple/lobby_files)
+
+/mob/new_player/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
+ var/list/data = ..()
+
+ var/displayed_name = world.name
+ if(config && CONFIG_GET(string/servername))
+ displayed_name = CONFIG_GET(string/servername)
+
+ data["server_name"] = displayed_name
+ data["map"] = using_map.full_name
+ data["station_time"] = stationtime2text()
+ data["display_loading"] = SSticker.current_state == GAME_STATE_INIT
+ data["round_start"] = !SSticker.mode || SSticker.current_state <= GAME_STATE_PREGAME
+ data["round_time"] = roundduration2text()
+ data["ready"] = ready
+ data["new_news"] = client?.check_for_new_server_news()
+ data["can_submit_feedback"] = SSsqlite.can_submit_feedback(client)
+ data["show_station_news"] = GLOB.news_data.station_newspaper
+ data["new_station_news"] = client.prefs.lastlorenews != GLOB.news_data.newsindex
+ data["new_changelog"] = read_preference(/datum/preference/text/lastchangelog) == GLOB.changelog_hash
+
+ return data
+
+/mob/new_player/tgui_static_data(mob/user)
+ var/list/data = ..()
+
+ data["bg"] = 'icons/misc/loading.dmi'
+ data["bg_state"] = "loading"
+
+ return data
+
+/mob/new_player/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("character_setup")
+ client.prefs.ShowChoices(src)
+ return TRUE
+ if("ready")
+ if(!SSticker || SSticker.current_state <= GAME_STATE_PREGAME)
+ ready = !ready
+ else
+ ready = 0
+ return TRUE
+ if("manifest")
+ ViewManifest()
+ return TRUE
+ if("late_join")
+ if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
+ to_chat(usr, span_red("The round is either not ready, or has already finished..."))
+ return TRUE
+
+ var/time_till_respawn = time_till_respawn()
+ if(time_till_respawn == -1) // Special case, never allowed to respawn
+ to_chat(usr, span_warning("Respawning is not allowed!"))
+ else if(time_till_respawn) // Nonzero time to respawn
+ to_chat(usr, span_warning("You can't respawn yet! You need to wait another [round(time_till_respawn/10/60, 0.1)] minutes."))
+ return TRUE
+ LateChoices()
+ return TRUE
+ if("observe")
+ if(!SSticker || SSticker.current_state == GAME_STATE_INIT)
+ to_chat(src, span_warning("The game is still setting up, please try again later."))
+ return TRUE
+ if(tgui_alert(src,"Are you sure you wish to observe? If you do, make sure to not use any knowledge gained from observing if you decide to join later.","Observe Round?",list("Yes","No")) == "Yes")
+ if(!client)
+ return TRUE
+
+ //Make a new mannequin quickly, and allow the observer to take the appearance
+ var/mob/living/carbon/human/dummy/mannequin = get_mannequin(client.ckey)
+ client.prefs.dress_preview_mob(mannequin)
+ var/mob/observer/dead/observer = new(mannequin)
+ observer.moveToNullspace() //Let's not stay in our doomed mannequin
+
+ spawning = 1
+ if(client.media)
+ client.media.stop_music() // MAD JAMS cant last forever yo
+
+ observer.started_as_observer = 1
+ close_spawn_windows()
+ var/obj/O = locate("landmark*Observer-Start")
+ if(istype(O))
+ to_chat(src, span_notice("Now teleporting."))
+ observer.forceMove(O.loc)
+ else
+ to_chat(src, span_danger("Could not locate an observer spawn point. Use the Teleport verb to jump to the station map."))
+
+ announce_ghost_joinleave(src)
+
+ if(client.prefs.read_preference(/datum/preference/toggle/human/name_is_always_random))
+ client.prefs.real_name = random_name(client.prefs.identifying_gender)
+ observer.real_name = client.prefs.real_name
+ observer.name = observer.real_name
+ if(!client.holder && !CONFIG_GET(flag/antag_hud_allowed)) // For new ghosts we remove the verb from even showing up if it's not allowed.
+ remove_verb(observer, /mob/observer/dead/verb/toggle_antagHUD) // Poor guys, don't know what they are missing!
+ observer.key = key
+ observer.set_respawn_timer(time_till_respawn()) // Will keep their existing time if any, or return 0 and pass 0 into set_respawn_timer which will use the defaults
+ observer.client.init_verbs()
+ qdel(src)
+
+ return TRUE
+ if("shownews")
+ handle_server_news()
+ return TRUE
+ if("give_feedback")
+ if(!SSsqlite.can_submit_feedback(GLOB.directory[persistent_ckey]))
+ return
+
+ if(client.feedback_form)
+ client.feedback_form.display() // In case they closed the form early.
+ else
+ client.feedback_form = new(client)
+ return TRUE
+ if("open_station_news")
+ show_latest_news(GLOB.news_data.station_newspaper)
+ return TRUE
+ if("open_changelog")
+ write_preference_directly(/datum/preference/text/lastchangelog, GLOB.changelog_hash)
+ client.changes()
+ return TRUE
+ if("keyboard")
+ if(!SSsounds.subsystem_initialized)
+ return
+
+ playsound_local(ui.user, get_sfx("keyboard"), vol = 20)
+ return TRUE
diff --git a/code/modules/mob/new_player/login.dm b/code/modules/mob/new_player/login.dm
index 684c47358f..c4befc15e8 100644
--- a/code/modules/mob/new_player/login.dm
+++ b/code/modules/mob/new_player/login.dm
@@ -1,31 +1,3 @@
-///var/atom/movable/lobby_image = new /atom/movable{icon = 'icons/misc/title.dmi'; icon_state = lobby_image_state; screen_loc = "1,1"; name = "Polaris"}
-
-var/obj/effect/lobby_image = new /obj/effect/lobby_image
-
-/obj/effect/lobby_image
- name = "CHOMPStation" // CHOMPEdit
- desc = "How are you reading this?"
- screen_loc = "1,1"
- icon = 'icons/misc/loading.dmi'
- icon_state = "loading(old)" // CHOMPEdit
-
-/obj/effect/lobby_image/Initialize(mapload)
- icon = using_map.lobby_icon
- var/known_icon_states = cached_icon_states(icon)
- for(var/lobby_screen in using_map.lobby_screens)
- if(!(lobby_screen in known_icon_states))
- error("Lobby screen '[lobby_screen]' did not exist in the icon set [icon].")
- using_map.lobby_screens -= lobby_screen
-
- if(using_map.lobby_screens.len)
- icon_state = pick(using_map.lobby_screens)
- else
- icon_state = known_icon_states[1]
- . = ..()
-
-/mob/new_player
- var/client/my_client // Need to keep track of this ourselves, since by the time Logout() is called the client has already been nulled
-
/mob/new_player/Login()
update_Login_details() //handles setting lastKnownIP and computer_id for use by the ban systems as well as checking for multikeying
if(GLOB.join_motd)
@@ -41,23 +13,19 @@ var/obj/effect/lobby_image = new /obj/effect/lobby_image
mind.active = 1
mind.current = src
- //CHOMPEdit Begin
- if(length(GLOB.newplayer_start))
- forceMove(pick(GLOB.newplayer_start))
- else
- forceMove(locate(1,1,1))
- //CHOMPEdit End
+ if(client)
+ persistent_ckey = client.ckey
- //loc = null CHOMPEdit Removal
- //client.screen += lobby_image CHOMPEdit Removal
- my_client = client
+ loc = null
sight |= SEE_TURFS
+
+ initialize_lobby_screen()
+
player_list |= src
created_for = ckey
if(!QDELETED(src))
- new_player_panel()
addtimer(CALLBACK(src, PROC_REF(do_after_login)), 4 SECONDS, TIMER_DELETE_ME)
/mob/new_player/proc/do_after_login()
diff --git a/code/modules/mob/new_player/logout.dm b/code/modules/mob/new_player/logout.dm
index bbffe5e9ba..29a3abddad 100644
--- a/code/modules/mob/new_player/logout.dm
+++ b/code/modules/mob/new_player/logout.dm
@@ -1,10 +1,11 @@
/mob/new_player/Logout()
ready = 0
- // see login.dm
- if(my_client)
- my_client.screen -= lobby_image
- my_client = null
+ QDEL_NULL(lobby_window)
+
+ var/client/exiting_client = GLOB.directory[persistent_ckey]
+ if(exiting_client)
+ winset(exiting_client, "lobby_browser", "is-disabled=true;is-visible=false")
..()
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index 6a2a0d3801..cbb295fb70 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -5,9 +5,8 @@
var/spawning = 0 //Referenced when you want to delete the new_player later on in the code.
var/totalPlayers = 0 //Player counts for the Lobby tab
var/totalPlayersReady = 0
- var/show_hidden_jobs = 0 //Show jobs that are set to "Never" in preferences
var/has_respawned = FALSE //Determines if we're using RESPAWN_MESSAGE
- var/datum/browser/panel
+ var/datum/tgui_window/lobby_window = null
var/datum/tgui_module/crew_manifest/new_player/manifest_dialog = null
var/datum/tgui_module/late_choices/late_choices_dialog = null
universal_speak = 1
@@ -27,103 +26,12 @@
add_verb(src, /mob/proc/insidePanel)
/mob/new_player/Destroy()
- if(panel)
- QDEL_NULL(panel)
if(manifest_dialog)
QDEL_NULL(manifest_dialog)
if(late_choices_dialog)
QDEL_NULL(late_choices_dialog)
. = ..()
-/mob/new_player/verb/new_player_panel()
- set src = usr
- new_player_panel_proc()
-
-
-/mob/new_player/proc/new_player_panel_proc()
- var/output = "
"
-
- output += span_bold("Map:") + " [using_map.full_name]
"
- output += span_bold("Station Time:") + " [stationtime2text()]
"
-
- if(!ticker || ticker.current_state <= GAME_STATE_PREGAME)
- output += span_bold("Server Initializing!")
- else
- output += span_bold("Round Duration:") + " [roundduration2text()]
"
- output += "
"
-
- output += "
Character Setup
"
-
- if(!ticker || ticker.current_state <= GAME_STATE_PREGAME)
- if(ready)
- output += "
\[ " + span_linkOn(span_bold("Ready")) + " | Not Ready \]
" //ChompEDIT - fixed height
- else
- output += "
\[ Ready | " + span_linkOn(span_bold("Not Ready")) + " \]
"
- output += "
Join Game!
"
-
- else
- output += "
View the Crew Manifest
"
- output += "
Join Game!
"
-
- output += "
Observe
"
-
- output += "
" //ChompADD - a line divider between functional and info buttons
-
- //nobody uses this feature //WELL WE'RE GONNA
- if(!IsGuestKey(src.key))
- establish_db_connection()
-
- if(SSdbcore.IsConnected())
- var/isadmin = 0
- if(src.client && src.client.holder)
- isadmin = 1
- var/datum/db_query/query = SSdbcore.NewQuery("SELECT id FROM erro_poll_question WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM erro_poll_vote WHERE ckey = \"[ckey]\") AND id NOT IN (SELECT pollid FROM erro_poll_textreply WHERE ckey = \"[ckey]\")")
- query.Execute()
- var/newpoll = 0
- while(query.NextRow())
- newpoll = 1
- break
- qdel(query)
- if(newpoll)
- output += "
Show Player Polls (NEW!)
"
- else
- output += "
Show Player Polls No Changes
"
-
- if(client?.check_for_new_server_news())
- output += "
Show Server News (NEW!)
"
- else
- output += "
Show Server News No Changes
"
-
- if(SSsqlite.can_submit_feedback(client))
- output += "
[href(src, list("give_feedback" = 1), "Give Feedback")]
"
-
- if(GLOB.news_data.station_newspaper)
- if(client.prefs.lastlorenews == GLOB.news_data.newsindex)
- output += "
Show [using_map.station_name] NewsNo Changes
"
- else
- output += "
Show [using_map.station_name] News (NEW!)
"
-
- if(read_preference(/datum/preference/text/lastchangelog) == GLOB.changelog_hash)
- output += "
Show Changelog No Changes
"
- else
- output += "
Show Changelog (NEW!)
"
-
- output += "
"
-
- if (client.prefs?.lastlorenews == GLOB.news_data.newsindex)
- client.seen_news = 1
-
- if(GLOB.news_data.station_newspaper && !client.seen_news && client.prefs?.read_preference(/datum/preference/toggle/show_lore_news))
- show_latest_news(GLOB.news_data.station_newspaper)
- client.prefs.lastlorenews = GLOB.news_data.newsindex
- SScharacter_setup.queue_preferences_save(client.prefs)
-
- panel = new(src, "Welcome","Welcome", 210, 500, src)
- panel.set_window_options("can_close=0")
- panel.set_content(output)
- panel.open()
- return
-
/mob/new_player/get_status_tab_items()
. = ..()
. += ""
@@ -161,78 +69,6 @@
/mob/new_player/Topic(href, href_list[])
if(!client) return 0
- if(href_list["show_preferences"])
- client.prefs.ShowChoices(src)
- return 1
-
- if(href_list["ready"])
- if(!ticker || ticker.current_state <= GAME_STATE_PREGAME) // Make sure we don't ready up after the round has started
- ready = text2num(href_list["ready"])
- else
- ready = 0
-
- if(href_list["refresh"])
- panel.close()
- new_player_panel_proc()
-
- if(href_list["observe"])
- if(!SSticker || SSticker.current_state == GAME_STATE_INIT)
- to_chat(src, span_warning("The game is still setting up, please try again later."))
- return 0
- if(tgui_alert(src,"Are you sure you wish to observe? If you do, make sure to not use any knowledge gained from observing if you decide to join later.","Observe Round?",list("Yes","No")) == "Yes")
- if(!client) return 1
-
- //Make a new mannequin quickly, and allow the observer to take the appearance
- var/mob/living/carbon/human/dummy/mannequin = get_mannequin(client.ckey)
- client.prefs.dress_preview_mob(mannequin)
- var/mob/observer/dead/observer = new(mannequin)
- observer.moveToNullspace() //Let's not stay in our doomed mannequin
-
- spawning = 1
- if(client.media)
- client.media.stop_music() // MAD JAMS cant last forever yo
-
- observer.started_as_observer = 1
- close_spawn_windows()
- var/obj/O = locate("landmark*Observer-Start")
- if(istype(O))
- to_chat(src, span_notice("Now teleporting."))
- observer.forceMove(O.loc)
- else
- to_chat(src, span_danger("Could not locate an observer spawn point. Use the Teleport verb to jump to the station map."))
-
- announce_ghost_joinleave(src)
-
- if(client.prefs.read_preference(/datum/preference/toggle/human/name_is_always_random))
- client.prefs.real_name = random_name(client.prefs.identifying_gender)
- observer.real_name = client.prefs.real_name
- observer.name = observer.real_name
- if(!client.holder && !CONFIG_GET(flag/antag_hud_allowed)) // For new ghosts we remove the verb from even showing up if it's not allowed.
- remove_verb(observer, /mob/observer/dead/verb/toggle_antagHUD) // Poor guys, don't know what they are missing!
- observer.key = key
- observer.set_respawn_timer(time_till_respawn()) // Will keep their existing time if any, or return 0 and pass 0 into set_respawn_timer which will use the defaults
- observer.client.init_verbs()
- qdel(src)
-
- return TRUE
-
- if(href_list["late_join"])
-
- if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
- to_chat(usr, span_red("The round is either not ready, or has already finished..."))
- return
-
- var/time_till_respawn = time_till_respawn()
- if(time_till_respawn == -1) // Special case, never allowed to respawn
- to_chat(usr, span_warning("Respawning is not allowed!"))
- else if(time_till_respawn) // Nonzero time to respawn
- to_chat(usr, span_warning("You can't respawn yet! You need to wait another [round(time_till_respawn/10/60, 0.1)] minutes."))
- return
- LateChoices()
-
- if(href_list["manifest"])
- ViewManifest()
-
if(href_list["privacy_poll"])
establish_db_connection()
if(!SSdbcore.IsConnected())
@@ -275,13 +111,6 @@
if(!ready && href_list["preference"])
if(client)
client.prefs.process_link(src, href_list)
- else if(!href_list["late_join"])
- new_player_panel()
-
- if(href_list["showpoll"])
-
- handle_player_polling()
- return
if(href_list["pollid"])
@@ -333,27 +162,6 @@
if(!isnull(href_list["option_[optionid]"])) //Test if this optionid was selected
vote_on_poll(pollid, optionid, 1)
- if(href_list["shownews"])
- handle_server_news()
- return
-
- if(href_list["hidden_jobs"])
- show_hidden_jobs = !show_hidden_jobs
- LateChoices()
-
- if(href_list["give_feedback"])
- if(!SSsqlite.can_submit_feedback(my_client))
- return
-
- if(client.feedback_form)
- client.feedback_form.display() // In case they closed the form early.
- else
- client.feedback_form = new(client)
-
- if(href_list["open_changelog"])
- write_preference_directly(/datum/preference/text/lastchangelog, GLOB.changelog_hash)
- client.changes()
- return
/mob/new_player/proc/handle_server_news()
if(!client)
@@ -479,9 +287,6 @@
var/mob/living/character = create_character(T) //creates the human and transfers vars and mind
character = job_master.EquipRank(character, rank, 1) //equips the human
UpdateFactionList(character)
- if(character && character.client)
- var/obj/screen/splash/Spl = new(character.client, TRUE)
- Spl.Fade(TRUE)
var/datum/job/J = SSjob.get_job(rank)
@@ -674,7 +479,6 @@
src << browse(null, "window=latechoices") //closes late choices window
src << browse(null, "window=preferences_window") //VOREStation Edit?
src << browse(null, "window=News") //closes news window
- panel.close()
/mob/new_player/get_species()
var/datum/species/chosen_species
diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm
index 1fa12e3cbe..3c6ea6fffc 100644
--- a/code/modules/tgui/tgui.dm
+++ b/code/modules/tgui/tgui.dm
@@ -47,6 +47,8 @@
var/list/children = list()
/// Any partial packets that we have received from TGUI, waiting to be sent
var/partial_packets
+ /// If the window should be closed with other windows when requested
+ var/closeable = TRUE
/**
* public
@@ -60,13 +62,13 @@
* optional parent_ui datum/tgui The parent of this UI.
* optional ui_x int Deprecated: Window width.
* optional ui_y int Deprecated: Window height.
+ * optional window datum/tgui_window: The window to display this TGUI within
*
* return datum/tgui The requested UI.
*/
-/datum/tgui/New(mob/user, datum/src_object, interface, title, datum/tgui/parent_ui, ui_x, ui_y)
+/datum/tgui/New(mob/user, datum/src_object, interface, title, datum/tgui/parent_ui, ui_x, ui_y, datum/tgui_window/window)
src.user = user
src.src_object = src_object
- src.window_key = "[REF(src_object)]-main"
src.interface = interface
if(title)
src.title = title
@@ -78,6 +80,12 @@
if(ui_x && ui_y)
src.window_size = list(ui_x, ui_y)
+ if(window)
+ src.window = window
+ src.window_key = window.id
+ else
+ src.window_key = "[REF(src_object)]-main"
+
/datum/tgui/Destroy()
user = null
src_object = null
@@ -88,22 +96,26 @@
*
* Open this UI (and initialize it with data).
*
+ * Args:
+ * preinitialized: bool - if TRUE, we will not attempt to force strict mode on the tgui's window datum
+ *
* return bool - TRUE if a new pooled window is opened, FALSE in all other situations including if a new pooled window didn't open because one already exists.
*/
-/datum/tgui/proc/open()
+/datum/tgui/proc/open(preinitialized = FALSE)
if(!user?.client)
return FALSE
- if(window)
+ if(window && window.status > TGUI_WINDOW_LOADING)
return FALSE
process_status()
if(status < STATUS_UPDATE)
return FALSE
- window = SStgui.request_pooled_window(user)
+ if(!window)
+ window = SStgui.request_pooled_window(user)
if(!window)
return FALSE
opened_at = world.time
window.acquire_lock(src)
- if(!window.is_ready())
+ if(!window.is_ready() && !preinitialized)
window.initialize(
strict_mode = TRUE,
fancy = user.read_preference(/datum/preference/toggle/tgui_fancy),
diff --git a/html/lobby/loading.gif b/html/lobby/loading.gif
new file mode 100644
index 0000000000..4d2f0623d3
Binary files /dev/null and b/html/lobby/loading.gif differ
diff --git a/icons/misc/splash_screen.dmi b/icons/misc/splash_screen.dmi
index 93f7163dd5..62e4cc7843 100644
Binary files a/icons/misc/splash_screen.dmi and b/icons/misc/splash_screen.dmi differ
diff --git a/interface/skin.dmf b/interface/skin.dmf
index bfeeb3b11a..52e109c44e 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -1283,6 +1283,15 @@ window "mapwindow"
on-show = ".winset\"mainwindow.mainvsplit.left=mapwindow\""
on-hide = ".winset\"mainwindow.mainvsplit.left=\""
style = ".center { text-align: center; } .runechatdiv {background-color: #20202070} .black_outline { -dm-text-outline: 1px black } .boldtext { font-weight: bold; } .maptext { font-family: 'Grand9K Pixel'; font-size: 6pt; -dm-text-outline: 1px black; color: white; line-height: 1.0; } .command_headset { font-weight: bold;\tfont-size: 8px; } .small { font-size: 6px; } .very_small { font-size: 5px;} .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .greentext { color: #00FF00; font-size: 7px; } .redtext { color: #FF0000; font-size: 7px; } .clown { color: #FF69Bf; font-size: 7px; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-size: 7px; font-style: italic; }"
+ elem "lobby_browser"
+ type = BROWSER
+ pos = 0,0
+ size = 640,480
+ anchor1 = 0,0
+ anchor2 = 100,100
+ is-visible = false
+ is-disabled = true
+ background-color = #222222
window "outputwindow"
elem "outputwindow"
diff --git a/modular_chomp/maps/relic_base/relicbase-12.dmm b/modular_chomp/maps/relic_base/relicbase-12.dmm
index 8195e9fdd1..9a82ebb193 100644
--- a/modular_chomp/maps/relic_base/relicbase-12.dmm
+++ b/modular_chomp/maps/relic_base/relicbase-12.dmm
@@ -5944,9 +5944,6 @@
icon_state = "freezerfloor"
},
/area/syndicate_station)
-"fWI" = (
-/turf/closed/indestructible/start_area,
-/area/space)
"fWK" = (
/obj/structure/closet{
name = "Evidence Closet"
@@ -20350,9 +20347,6 @@
/obj/item/grenade/spawnergrenade/manhacks,
/turf/simulated/shuttle/floor/darkred,
/area/shuttle/skipjack)
-"wPe" = (
-/turf/closed/indestructible/splashscreen,
-/area/space)
"wPA" = (
/obj/structure/table/reinforced,
/obj/item/aicard,
@@ -20448,10 +20442,6 @@
/obj/structure/table/steel_reinforced,
/turf/simulated/floor/reinforced,
/area/centcom/specops)
-"wUm" = (
-/obj/effect/landmark/start/new_player,
-/turf/closed/indestructible/start_area,
-/area/space)
"wVn" = (
/obj/machinery/door/airlock/silver{
name = "Toilet"
@@ -21438,21 +21428,21 @@
(1,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-wPe
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -21695,21 +21685,21 @@ xUf
(2,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -21952,21 +21942,21 @@ xUf
(3,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -22209,21 +22199,21 @@ xUf
(4,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -22466,21 +22456,21 @@ xUf
(5,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -22723,21 +22713,21 @@ xUf
(6,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -22980,21 +22970,21 @@ xUf
(7,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -23237,21 +23227,21 @@ xUf
(8,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-wUm
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -23494,21 +23484,21 @@ xUf
(9,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -23751,21 +23741,21 @@ xUf
(10,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -24008,21 +23998,21 @@ xUf
(11,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -24265,21 +24255,21 @@ xUf
(12,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -24522,21 +24512,21 @@ xUf
(13,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -24779,21 +24769,21 @@ xUf
(14,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -25036,21 +25026,21 @@ xUf
(15,1,1) = {"
vFi
vFi
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
-fWI
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
diff --git a/modular_chomp/maps/soluna_nexus/soluna_nexus-7.dmm b/modular_chomp/maps/soluna_nexus/soluna_nexus-7.dmm
index 58d95c2c5f..bac491af68 100644
--- a/modular_chomp/maps/soluna_nexus/soluna_nexus-7.dmm
+++ b/modular_chomp/maps/soluna_nexus/soluna_nexus-7.dmm
@@ -15485,9 +15485,6 @@
},
/turf/simulated/shuttle/plating,
/area/centcom/evac)
-"ndq" = (
-/turf/closed/indestructible/start_area,
-/area/space)
"nec" = (
/obj/effect/floor_decal/corner/green{
dir = 10
@@ -18137,9 +18134,6 @@
},
/turf/simulated/floor/tiled/techmaint,
/area/shadekin)
-"pBj" = (
-/turf/closed/indestructible/splashscreen,
-/area/space)
"pBu" = (
/obj/structure/table/steel,
/obj/item/gun/projectile/pistol,
@@ -20317,10 +20311,6 @@
/obj/random/donkpocketbox,
/turf/simulated/floor/tiled/techmaint,
/area/shadekin)
-"rCS" = (
-/obj/effect/landmark/start/new_player,
-/turf/closed/indestructible/start_area,
-/area/space)
"rDo" = (
/obj/effect/floor_decal/corner/blue{
dir = 5
@@ -89819,21 +89809,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-pBj
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -90077,21 +90067,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -90335,21 +90325,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -90593,21 +90583,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -90851,21 +90841,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -91109,21 +91099,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -91367,21 +91357,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -91625,21 +91615,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-rCS
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -91883,21 +91873,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -92141,21 +92131,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -92399,21 +92389,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -92657,21 +92647,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -92915,21 +92905,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -93173,21 +93163,21 @@ mUJ
mUJ
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
@@ -93431,21 +93421,21 @@ rZY
rZY
rZY
vFi
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
-ndq
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
dtV
dtV
diff --git a/modular_chomp/maps/soluna_nexus/soluna_nexus_defines.dm b/modular_chomp/maps/soluna_nexus/soluna_nexus_defines.dm
index a15c180519..e8cf8d714f 100644
--- a/modular_chomp/maps/soluna_nexus/soluna_nexus_defines.dm
+++ b/modular_chomp/maps/soluna_nexus/soluna_nexus_defines.dm
@@ -39,7 +39,7 @@ but they don't actually change anything about the load order
path = "soluna_nexus"
lobby_icon = 'icons/misc/splash_screen.dmi' //CHOMPStation Edit
- lobby_screens = list() //CHOMPStation Edit - CHOMPStation image
+ lobby_screens = list("southern_cross1") //CHOMPStation Edit - CHOMPStation image
id_hud_icons = 'icons/mob/hud_jobs_vr.dmi' //CHOMPStation Edit - Job icons for off-duty/exploration
holomap_smoosh = list(list(
diff --git a/modular_chomp/maps/southern_cross/southern_cross-8.dmm b/modular_chomp/maps/southern_cross/southern_cross-8.dmm
index 76ccb604ae..01156be0e5 100644
--- a/modular_chomp/maps/southern_cross/southern_cross-8.dmm
+++ b/modular_chomp/maps/southern_cross/southern_cross-8.dmm
@@ -5999,9 +5999,6 @@
/obj/machinery/vending/donksoft,
/turf/simulated/floor/reinforced,
/area/shadekin)
-"faJ" = (
-/turf/closed/indestructible/splashscreen,
-/area/space)
"faN" = (
/obj/structure/table/standard,
/obj/effect/decal/cleanable/cobweb2,
@@ -14503,9 +14500,6 @@
/obj/effect/floor_decal/carpet,
/turf/simulated/floor/holofloor/carpet,
/area/holodeck/source_courtroom)
-"mSU" = (
-/turf/closed/indestructible/start_area,
-/area/space)
"mTk" = (
/obj/machinery/portable_atmospherics/canister/oxygen/prechilled,
/obj/machinery/atmospherics/portables_connector,
@@ -15077,10 +15071,6 @@
/obj/machinery/meter,
/turf/simulated/shuttle/plating,
/area/shuttle/skipjack)
-"nvE" = (
-/obj/effect/landmark/start/new_player,
-/turf/closed/indestructible/start_area,
-/area/space)
"nvF" = (
/obj/machinery/embedded_controller/radio/airlock/airlock_controller{
dir = 8;
@@ -25979,21 +25969,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-faJ
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -26237,21 +26227,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -26495,21 +26485,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -26753,21 +26743,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -27011,21 +27001,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -27269,21 +27259,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -27527,21 +27517,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -27785,21 +27775,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-nvE
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -28043,21 +28033,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -28301,21 +28291,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -28559,21 +28549,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -28817,21 +28807,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -29075,21 +29065,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -29333,21 +29323,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
@@ -29591,21 +29581,21 @@ vFi
vFi
vFi
vFi
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
-mSU
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
+vFi
vFi
vFi
vFi
diff --git a/modular_chomp/maps/southern_cross/southern_cross_defines.dm b/modular_chomp/maps/southern_cross/southern_cross_defines.dm
index 43d33593d3..c8794b262b 100644
--- a/modular_chomp/maps/southern_cross/southern_cross_defines.dm
+++ b/modular_chomp/maps/southern_cross/southern_cross_defines.dm
@@ -39,7 +39,7 @@ but they don't actually change anything about the load order
path = "southern_cross"
lobby_icon = 'icons/misc/splash_screen.dmi' //CHOMPStation Edit
- lobby_screens = list() //CHOMPStation Edit - CHOMPStation image
+ lobby_screens = list("southern_cross1") //CHOMPStation Edit - CHOMPStation image
id_hud_icons = 'icons/mob/hud_jobs_vr.dmi' //CHOMPStation Edit - Job icons for off-duty/exploration
holomap_smoosh = list(list(
diff --git a/modular_chomp/maps/virgo_minitest/virgo_minitest-1.dmm b/modular_chomp/maps/virgo_minitest/virgo_minitest-1.dmm
index 073b8724ef..630879445b 100644
--- a/modular_chomp/maps/virgo_minitest/virgo_minitest-1.dmm
+++ b/modular_chomp/maps/virgo_minitest/virgo_minitest-1.dmm
@@ -5756,13 +5756,6 @@
},
/turf/simulated/floor,
/area/engineering/workshop)
-"Lu" = (
-/obj/effect/landmark{
- name = "Observer-Start"
- },
-/obj/effect/landmark/start/new_player,
-/turf/simulated/floor/tiled,
-/area/bridge)
"Lz" = (
/obj/machinery/computer/power_monitor{
dir = 4;
@@ -9138,7 +9131,7 @@ ni
gr
gf
gf
-Lu
+gz
gf
gf
gN
diff --git a/sound/lobby/lobby_load.ogg b/sound/lobby/lobby_load.ogg
new file mode 100644
index 0000000000..71e30bf2a6
Binary files /dev/null and b/sound/lobby/lobby_load.ogg differ
diff --git a/tgui/packages/tgui/interfaces/AlertModal.tsx b/tgui/packages/tgui/interfaces/AlertModal.tsx
index 791bc38105..dc7f549a7c 100644
--- a/tgui/packages/tgui/interfaces/AlertModal.tsx
+++ b/tgui/packages/tgui/interfaces/AlertModal.tsx
@@ -1,26 +1,29 @@
-import { useState } from 'react';
+import { type KeyboardEvent, useState } from 'react';
import { useBackend } from 'tgui/backend';
import { Window } from 'tgui/layouts';
import { Autofocus, Box, Button, Section, Stack } from 'tgui-core/components';
import { isEscape, KEY } from 'tgui-core/keys';
+import { type BooleanLike } from 'tgui-core/react';
import { Loader } from './common/Loader';
-type AlertModalData = {
- autofocus: boolean;
+type Data = {
+ autofocus: BooleanLike;
buttons: string[];
- large_buttons: boolean;
+ large_buttons: BooleanLike;
message: string;
- swapped_buttons: boolean;
+ swapped_buttons: BooleanLike;
timeout: number;
title: string;
};
-const KEY_DECREMENT = -1;
-const KEY_INCREMENT = 1;
+enum DIRECTION {
+ Increment = 1,
+ Decrement = -1,
+}
-export const AlertModal = (props) => {
- const { act, data } = useBackend();
+export function AlertModal(props) {
+ const { act, data } = useBackend();
const {
autofocus,
buttons = [],
@@ -29,128 +32,168 @@ export const AlertModal = (props) => {
timeout,
title,
} = data;
+
+ // Stolen wholesale from fontcode
+ function textWidth(text: string, font: string, fontsize: number) {
+ // default font height is 12 in tgui
+ font = fontsize + 'x ' + font;
+ const c = document.createElement('canvas');
+ const ctx = c.getContext('2d') as CanvasRenderingContext2D;
+ ctx.font = font;
+ return ctx.measureText(text).width;
+ }
+
const [selected, setSelected] = useState(0);
+
+ const windowWidth = 345 + (buttons.length > 2 ? 55 : 0);
+
+ // very accurate estimate of padding for each num of buttons
+ const paddingMagicNumber = 67 / buttons.length + 23;
+
+ // At least one of the buttons has a long text message
+ const isVerbose = buttons.some(
+ (button) =>
+ textWidth(button, '', large_buttons ? 14 : 12) > // 14 is the larger font size for large buttons
+ windowWidth / buttons.length - paddingMagicNumber,
+ );
+ const largeSpacing = isVerbose && large_buttons ? 20 : 15;
+
// Dynamically sets window dimensions
const windowHeight =
- 115 +
+ 120 +
+ (isVerbose ? largeSpacing * buttons.length : 0) +
(message.length > 30 ? Math.ceil(message.length / 4) : 0) +
(message.length && large_buttons ? 5 : 0);
- const windowWidth = 325 + (buttons.length > 2 ? 55 : 0);
- const onKey = (direction: number) => {
- if (selected === 0 && direction === KEY_DECREMENT) {
- setSelected(buttons.length - 1);
- } else if (selected === buttons.length - 1 && direction === KEY_INCREMENT) {
- setSelected(0);
- } else {
- setSelected(selected + direction);
- }
- };
- function handleKeyDown(event: React.KeyboardEvent) {
- const key = event.key;
- /**
- * Simulate a click when pressing space or enter,
- * allow keyboard navigation, override tab behavior
- */
- if (key === KEY.Space || key === KEY.Enter) {
- act('choose', { choice: buttons[selected] });
- } else if (isEscape(key)) {
- act('cancel');
- } else if (key === KEY.Left) {
- event.preventDefault();
- onKey(KEY_DECREMENT);
- } else if (key === KEY.Tab || key === KEY.Right) {
- event.preventDefault();
- onKey(KEY_INCREMENT);
+ /** Changes button selection, etc */
+ function keyDownHandler(event: KeyboardEvent) {
+ switch (event.key) {
+ case KEY.Space:
+ case KEY.Enter:
+ act('choose', { choice: buttons[selected] });
+ return;
+ case KEY.Left:
+ event.preventDefault();
+ onKey(DIRECTION.Decrement);
+ return;
+ case KEY.Tab:
+ case KEY.Right:
+ event.preventDefault();
+ onKey(DIRECTION.Increment);
+ return;
+
+ default:
+ if (isEscape(event.key)) {
+ act('cancel');
+ return;
+ }
}
}
+ /** Manages iterating through the buttons */
+ function onKey(direction: DIRECTION) {
+ const newIndex = (selected + direction + buttons.length) % buttons.length;
+ setSelected(newIndex);
+ }
+
return (
{!!timeout && }
- handleKeyDown(e)}>
+
-
+
{message}
-
+
{!!autofocus && }
-
+ {isVerbose ? (
+
+ ) : (
+
+ )}
);
+}
+
+type ButtonDisplayProps = {
+ selected: number;
};
/**
* Displays a list of buttons ordered by user prefs.
- * Technically this handles more than 2 buttons, but you
+ */
+function HorizontalButtons(props: ButtonDisplayProps) {
+ const { act, data } = useBackend();
+ const { buttons = [], large_buttons, swapped_buttons } = data;
+ const { selected } = props;
+
+ return (
+
+ {buttons.map((button, index) => (
+
+ act('choose', { choice: button })}
+ overflowX="hidden"
+ px={2}
+ py={large_buttons ? 0.5 : 0}
+ selected={selected === index}
+ textAlign="center"
+ >
+ {!large_buttons ? button : button.toUpperCase()}
+
+
+ ))}
+
+ );
+}
+
+/**
+ * Technically the parent handles more than 2 buttons, but you
* should just be using a list input in that case.
*/
-const ButtonDisplay = (props) => {
- const { data } = useBackend();
+function VerticalButtons(props: ButtonDisplayProps) {
+ const { act, data } = useBackend();
const { buttons = [], large_buttons, swapped_buttons } = data;
const { selected } = props;
return (
- {buttons?.map((button, index) =>
- !!large_buttons && buttons.length < 3 ? (
-
-
-
- ) : (
-
-
-
- ),
- )}
+ {buttons.map((button, index) => (
+
+ act('choose', { choice: button })}
+ overflowX="hidden"
+ px={2}
+ py={large_buttons ? 0.5 : 0}
+ selected={selected === index}
+ textAlign="center"
+ >
+ {!large_buttons ? button : button.toUpperCase()}
+
+
+ ))}
);
-};
-
-/**
- * Displays a button with variable sizing.
- */
-const AlertButton = (props) => {
- const { act, data } = useBackend();
- const { large_buttons } = data;
- const { button, selected } = props;
- const buttonWidth = button.length > 7 ? button.length : 7;
-
- return (
- act('choose', { choice: button })}
- m={0.5}
- pl={2}
- pr={2}
- pt={large_buttons ? 0.33 : 0}
- selected={selected}
- textAlign="center"
- width={!large_buttons && buttonWidth}
- >
- {!large_buttons ? button : button.toUpperCase()}
-
- );
-};
+}
diff --git a/tgui/packages/tgui/interfaces/LobbyMenu/LobbyButtons.tsx b/tgui/packages/tgui/interfaces/LobbyMenu/LobbyButtons.tsx
new file mode 100644
index 0000000000..e52ebd7a6e
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/LobbyMenu/LobbyButtons.tsx
@@ -0,0 +1,137 @@
+import { useBackend } from 'tgui/backend';
+import { Box, Section, Stack } from 'tgui-core/components';
+
+import { LobbyButton, TimedDivider } from './LobbyElements';
+import type { LobbyData } from './types';
+
+export const LobbyButtons = (props: {
+ readonly setModal: (_) => void;
+ readonly hidden: boolean;
+ readonly setHidden: (_: boolean) => void;
+}) => {
+ const { act, data } = useBackend();
+ const { setModal, hidden, setHidden } = props;
+
+ const {
+ server_name,
+ map,
+ station_time,
+ round_start,
+ round_time,
+ ready,
+ new_news,
+ can_submit_feedback,
+ show_station_news,
+ new_station_news,
+ new_changelog,
+ } = data;
+
+ return (
+
+
+
+
+
+
+ setHidden(true)}
+ />
+
+
+
+
+
+ Welcome to {server_name}.
+
+
+ Map: {map}
+
+
+ Station Time: {station_time}
+
+
+
+ Round Duration:{' '}
+ {round_start ? 'Initializing...' : round_time}{' '}
+
+
+
+
+
+
+
+
+ act('character_setup')}
+ icon="file-lines"
+ >
+ Setup Character
+
+
+ act('manifest')}
+ icon="circle-user"
+ >
+ View Crew Manifest
+
+
+ act('shownews')} icon="newspaper">
+ Show Server News {new_news ? '(NEW!)' : null}
+
+
+ {can_submit_feedback ? (
+ act('give_feedback')}
+ icon="clipboard-question"
+ >
+ Give Feedback
+
+ ) : null}
+
+ act('open_changelog')}
+ icon="pencil"
+ >
+ Show Changelog {new_changelog ? '(NEW!)' : null}
+
+
+ {show_station_news ? (
+ act('')} icon="newspaper">
+ Show Station News {new_station_news ? '(NEW!)' : null}
+
+ ) : null}
+
+
+ act('observe')}>
+ Observe
+
+
+ {round_start ? (
+ act('ready')}
+ icon={ready ? 'check' : 'xmark'}
+ >
+ {ready ? 'Unready' : 'Ready'}
+
+ ) : (
+ act('late_join')} icon="users">
+ Join Game
+
+ )}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/LobbyMenu/LobbyElements.tsx b/tgui/packages/tgui/interfaces/LobbyMenu/LobbyElements.tsx
new file mode 100644
index 0000000000..19467b82ad
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/LobbyMenu/LobbyElements.tsx
@@ -0,0 +1,68 @@
+import { useContext, useEffect, useRef } from 'react';
+import { useBackend } from 'tgui/backend';
+import { Box, Button, Stack } from 'tgui-core/components';
+
+import { LobbyContext } from './constants';
+import type { LobbyButtonProps, LobbyContextType } from './types';
+
+export const TimedDivider = () => {
+ const ref = useRef(null);
+
+ const context = useContext(LobbyContext);
+ const { animationsDisabled, animationsFinished } = context;
+
+ useEffect(() => {
+ if (!animationsFinished && !animationsDisabled) {
+ setTimeout(() => {
+ ref.current!.style.display = 'block';
+ }, 1500);
+ }
+ }, [animationsFinished, animationsDisabled]);
+
+ return (
+
+
+
+ );
+};
+
+export const LobbyButton = (props: LobbyButtonProps) => {
+ const { children, index, className, ...rest } = props;
+
+ const context = useContext(LobbyContext);
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export const CustomButton = (props) => {
+ const { act } = useBackend();
+
+ return (
+ // this works because of event propagation
+ act('keyboard')}>
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/LobbyMenu/constants.ts b/tgui/packages/tgui/interfaces/LobbyMenu/constants.ts
new file mode 100644
index 0000000000..018f6a02a6
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/LobbyMenu/constants.ts
@@ -0,0 +1,8 @@
+import { createContext } from 'react';
+
+import type { LobbyContextType } from './types';
+
+export const LobbyContext = createContext({
+ animationsDisabled: false,
+ animationsFinished: false,
+});
diff --git a/tgui/packages/tgui/interfaces/LobbyMenu/index.tsx b/tgui/packages/tgui/interfaces/LobbyMenu/index.tsx
new file mode 100644
index 0000000000..3e1858c6c3
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/LobbyMenu/index.tsx
@@ -0,0 +1,165 @@
+import { storage } from 'common/storage';
+import { type ReactNode, useEffect, useRef, useState } from 'react';
+import { resolveAsset } from 'tgui/assets';
+import { useBackend } from 'tgui/backend';
+import { Window } from 'tgui/layouts';
+import { Box, Modal, Section, Stack } from 'tgui-core/components';
+import { classes } from 'tgui-core/react';
+
+import { LobbyContext } from './constants';
+import { LobbyButtons } from './LobbyButtons';
+import { CustomButton } from './LobbyElements';
+import type { LobbyData } from './types';
+
+export const LobbyMenu = (props) => {
+ const { act, data } = useBackend();
+
+ const onLoadPlayer = useRef(null);
+
+ const [modal, setModal] = useState(false);
+
+ const [filterDisabled, setFilterDisabled] = useState(false);
+ const [animationsDisabled, setAnimationsDisabled] = useState(false);
+ const [audioDisabled, setAudioDisabled] = useState(false);
+ const [animationsFinished, setAnimationsFinished] = useState(false);
+
+ useEffect(() => {
+ storage
+ .get('lobby-filter-disabled')
+ .then((val) => setFilterDisabled(!!val));
+ storage
+ .get('lobby-animations-disabled')
+ .then((val) => setAnimationsDisabled(!!val));
+ storage.get('lobby-audio-disabled').then((val) => setAudioDisabled(!!val));
+
+ setTimeout(() => {
+ if (onLoadPlayer.current) {
+ onLoadPlayer.current!.play();
+ }
+ }, 250);
+
+ setTimeout(() => {
+ setAnimationsFinished(true);
+ }, 10000);
+ }, []);
+
+ const [hidden, setHidden] = useState(false);
+
+ return (
+
+ {!audioDisabled && (
+
+ )}
+
+
+ {!!modal && {modal} }
+
+
+
+ {
+ setModal(
+ setModal(false)}
+ />
+ }
+ >
+
+
+ {
+ storage.set(
+ 'lobby-filter-disabled',
+ !filterDisabled,
+ );
+ setFilterDisabled(!filterDisabled);
+ setModal(false);
+ }}
+ tooltip="Removes the CRT filter background"
+ >
+ {`${filterDisabled ? 'Enable' : 'Disable'} Scanlines`}
+
+
+
+ {
+ storage.set('lobby-audio-disabled', !audioDisabled);
+ setAudioDisabled(!audioDisabled);
+ setModal(false);
+ }}
+ tooltip="Removes the loading audio"
+ >
+ {`${audioDisabled ? 'Enable' : 'Disable'} Audio`}
+
+
+
+ {
+ storage.set(
+ 'lobby-animations-disabled',
+ !animationsDisabled,
+ );
+ setAnimationsDisabled(!animationsDisabled);
+ setModal(false);
+ }}
+ tooltip="Disables animations."
+ >
+ {`${animationsDisabled ? 'Enable' : 'Disable'} Animations`}
+
+
+
+ ,
+ );
+ }}
+ />
+
+ {hidden && (
+
+ setHidden(false)} />
+
+ )}
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/LobbyMenu/types.ts b/tgui/packages/tgui/interfaces/LobbyMenu/types.ts
new file mode 100644
index 0000000000..a69b959c0b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/LobbyMenu/types.ts
@@ -0,0 +1,32 @@
+import type { ReactNode } from 'react';
+import type { Box } from 'tgui-core/components';
+import type { BooleanLike } from 'tgui-core/react';
+
+export type LobbyData = {
+ display_loading: BooleanLike;
+ server_name: string;
+ map: string;
+ station_time: string;
+ round_start: BooleanLike;
+ round_time: string;
+ ready: BooleanLike;
+ new_news: BooleanLike;
+ can_submit_feedback: BooleanLike;
+ show_station_news: BooleanLike;
+ new_station_news: BooleanLike;
+ new_changelog: BooleanLike;
+};
+
+export type LobbyContextType = {
+ animationsDisabled: boolean;
+ animationsFinished: boolean;
+ setModal?: (_: ReactNode | false) => void;
+};
+
+export type LobbyButtonProps = React.ComponentProps & {
+ readonly index: number;
+ readonly selected?: boolean;
+ readonly disabled?: boolean;
+ readonly icon?: string;
+ readonly tooltip?: string;
+};
diff --git a/tgui/packages/tgui/interfaces/MobSpawner.tsx b/tgui/packages/tgui/interfaces/MobSpawner.tsx
index 42355086da..7fb5317032 100644
--- a/tgui/packages/tgui/interfaces/MobSpawner.tsx
+++ b/tgui/packages/tgui/interfaces/MobSpawner.tsx
@@ -363,6 +363,7 @@ const GeneralMobSettings = (props: {
Description: