diff --git a/_maps/_basemap.dm b/_maps/_basemap.dm
index 4192461a4c6..de57328405c 100644
--- a/_maps/_basemap.dm
+++ b/_maps/_basemap.dm
@@ -7,7 +7,6 @@
#ifndef LOWMEMORYMODE
#ifdef ALL_MAPS
#include "map_files\Mining\Lavaland.dmm"
- #include "skyrat\planets\rockplanet.dmm"
#include "map_files\debug\runtimestation.dmm"
#include "map_files\debug\multiz.dmm"
#include "map_files\Deltastation\DeltaStation2.dmm"
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index 8c0f5b9703d..7275c7b647f 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -673,6 +673,7 @@
plane = SPLASHSCREEN_PLANE
var/client/holder
+/* SKYRAT EDIT REMOVAL
/atom/movable/screen/splash/New(client/C, visible, use_previous_title) //TODO: Make this use INITIALIZE_IMMEDIATE, except its not easy
. = ..()
@@ -691,6 +692,7 @@
icon = SStitle.previous_icon
holder.screen += src
+*/
/atom/movable/screen/splash/proc/Fade(out, qdel_after = TRUE)
if(QDELETED(src))
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 8f24bdb3de6..abdac644e0c 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -185,7 +185,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(init_sss)
init_subtypes(/datum/controller/subsystem, subsystems)
- to_chat(world, "Initializing subsystems... ")
+ //to_chat(world, "Initializing subsystems... ")
+ add_startupmessage("Initializing subsystems...") //SKYRAT EDIT CHANGE
// Sort subsystems by init_order, so they initialize in the correct order.
sortTim(subsystems, /proc/cmp_subsystem_init)
@@ -226,6 +227,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
initializations_finished_with_no_players_logged_in = initialized_tod < REALTIMEOFDAY - 10
// Loop.
Master.StartProcessing(0)
+ add_startupmessage("Clearing clutter...") //SKYRAT EDIT ADDITION
/datum/controller/master/proc/SetRunLevel(new_runlevel)
var/old_runlevel = current_runlevel
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index 091d495d3c4..52ad29ef94c 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -217,7 +217,8 @@
SEND_SIGNAL(src, COMSIG_SUBSYSTEM_POST_INITIALIZE, start_timeofday)
var/time = (REALTIMEOFDAY - start_timeofday) / 10
var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!"
- to_chat(world, "[msg] ")
+ //to_chat(world, "[msg] ")
+ add_startupmessage(msg) //SKYRAT EDIT CHANGE
log_world(msg)
return time
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index 8836ce36f5b..102c76bc91f 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -683,6 +683,7 @@ SUBSYSTEM_DEF(job)
to_chat(player, "You have failed to qualify for any job you desired. ")
unassigned -= player
player.ready = PLAYER_NOT_READY
+ player.client << output(player.ready, "lobbybrowser:imgsrc") //SKYRAT EDIT ADDITION
/datum/controller/subsystem/job/Recover()
diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm
index b818e211632..d7d7b75cfe6 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -246,7 +246,8 @@ Used by the AI doomsday and the self-destruct nuke.
if (!pm.load(1, 1, start_z + parsed_maps[P], no_changeturf = TRUE))
errorList |= pm.original_path
if(!silent)
- INIT_ANNOUNCE("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!")
+ //INIT_ANNOUNCE("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!")
+ add_startupmessage("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!") //SKYRAT EDIT CHANGE
return parsed_maps
/datum/controller/subsystem/mapping/proc/loadWorld()
@@ -258,7 +259,8 @@ Used by the AI doomsday and the self-destruct nuke.
// load the station
station_start = world.maxz + 1
- INIT_ANNOUNCE("Loading [config.map_name]...")
+ //INIT_ANNOUNCE("Loading [config.map_name]...") SKYRAT EDIT REMOVAL
+ add_startupmessage("Loading [config.map_name]...")
LoadGroup(FailedZs, "Station", config.map_path, config.map_file, config.traits, ZTRAITS_STATION)
if(SSdbcore.Connect())
@@ -274,8 +276,6 @@ Used by the AI doomsday and the self-destruct nuke.
++space_levels_so_far
add_new_zlevel("Empty Area [space_levels_so_far]", ZTRAITS_SPACE)
- LoadGroup(FailedZs, "Rockplanet", "skyrat/planets", "rockplanet.dmm", default_traits = ZTRAITS_ROCKPLANET)
-
if(config.minetype == "lavaland")
LoadGroup(FailedZs, "Lavaland", "map_files/Mining", "Lavaland.dmm", default_traits = ZTRAITS_LAVALAND)
else if (!isnull(config.minetype) && config.minetype != "none")
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 37dfd9fcbed..7556e3108f4 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -161,6 +161,7 @@ SUBSYSTEM_DEF(ticker)
to_chat(world, "Welcome to [station_name()]! ")
send2chat("<@&[CONFIG_GET(string/game_alert_role_id)]> New round starting on [SSmapping.config.map_name], [CONFIG_GET(string/servername)]! \nIf you wish to be pinged for game related stuff, go to <#[CONFIG_GET(string/role_assign_channel_id)]> and assign yourself the roles.", CONFIG_GET(string/chat_announce_new_game)) // Skyrat EDIT -- role ping
current_state = GAME_STATE_PREGAME
+ change_lobbyscreen() //SKYRAT EDIT ADDITION
//Everyone who wants to be an observer is now spawned
create_observers()
fire()
@@ -380,7 +381,7 @@ SUBSYSTEM_DEF(ticker)
GLOB.joined_player_list += player.ckey
player.create_character(FALSE)
else
- player.new_player_panel()
+ player.show_titlescreen()
CHECK_TICK
/datum/controller/subsystem/ticker/proc/collect_minds()
diff --git a/code/controllers/subsystem/title.dm b/code/controllers/subsystem/title.dm
index ce5d1d4dde2..daaa754cd0f 100644
--- a/code/controllers/subsystem/title.dm
+++ b/code/controllers/subsystem/title.dm
@@ -1,3 +1,4 @@
+/* SKYRAT EDIT REMOVAL - MOVED TO MODULAR
SUBSYSTEM_DEF(title)
name = "Title Screen"
flags = SS_NO_FIRE
@@ -67,3 +68,4 @@ SUBSYSTEM_DEF(title)
splash_turf = SStitle.splash_turf
file_path = SStitle.file_path
previous_icon = SStitle.previous_icon
+*/
diff --git a/code/datums/mapgen/CaveGenerator.dm b/code/datums/mapgen/CaveGenerator.dm
index 64c13410c0a..6e2a909c7aa 100644
--- a/code/datums/mapgen/CaveGenerator.dm
+++ b/code/datums/mapgen/CaveGenerator.dm
@@ -140,5 +140,6 @@
CHECK_TICK
var/message = "[name] finished in [(REALTIMEOFDAY - start_time)/10]s!"
- to_chat(world, "[message] ")
+ //to_chat(world, "[message] ")
+ add_startupmessage(message) //SKYRAT EDIT CHANGE
log_world(message)
diff --git a/code/game/turfs/closed/_closed.dm b/code/game/turfs/closed/_closed.dm
index 567c20a28f7..99ed3284c89 100644
--- a/code/game/turfs/closed/_closed.dm
+++ b/code/game/turfs/closed/_closed.dm
@@ -60,6 +60,7 @@
plane = SPLASHSCREEN_PLANE
bullet_bounce_sound = null
+/* SKYRAT EDIT REMOVAL
/turf/closed/indestructible/splashscreen/New()
SStitle.splash_turf = src
if(SStitle.icon)
@@ -72,7 +73,7 @@
switch(var_name)
if(NAMEOF(src, icon))
SStitle.icon = icon
-
+*/
/turf/closed/indestructible/reinforced
name = "reinforced wall"
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index aa9480c1ce4..fbae3ed8fdd 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -92,6 +92,7 @@ GLOBAL_PROTECT(admin_verbs_ban)
GLOBAL_LIST_INIT(admin_verbs_sounds, list(/client/proc/play_local_sound, /client/proc/play_direct_mob_sound, /client/proc/play_sound, /client/proc/set_round_end_sound))
GLOBAL_PROTECT(admin_verbs_sounds)
GLOBAL_LIST_INIT(admin_verbs_fun, list(
+ /client/proc/change_title_screen, //SKYRAT EDIT ADDITION
/client/proc/cmd_select_equipment,
/client/proc/cmd_admin_gib_self,
/client/proc/drop_bomb,
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index ca39b0ada1a..bbb8ba86613 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -2,7 +2,7 @@
//SECURITY//
////////////
#define UPLOAD_LIMIT 524288 //Restricts client uploads to the server to 0.5MB
-#define UPLOAD_LIMIT_ADMIN 2621440 //Restricts admin client uploads to the server to 2.5MB
+#define UPLOAD_LIMIT_ADMIN 5550000 //Restricts admin client uploads to the server to 2.5MB //SKYRAT EDIT CHANGE - CHANGED TO 5.55 MB
GLOBAL_LIST_INIT(blacklisted_builds, list(
"1407" = "bug preventing client display overrides from working leads to clients being able to see things/mobs they shouldn't be able to see",
diff --git a/code/modules/mob/dead/new_player/login.dm b/code/modules/mob/dead/new_player/login.dm
index 19aa1525057..b18361e769b 100644
--- a/code/modules/mob/dead/new_player/login.dm
+++ b/code/modules/mob/dead/new_player/login.dm
@@ -8,7 +8,7 @@
mind = new /datum/mind(key)
mind.active = TRUE
mind.current = src
-
+ my_client = client //SKYRAT EDIT ADDITION
. = ..()
if(!. || !client)
return FALSE
@@ -37,7 +37,7 @@
register_for_interview()
return
- new_player_panel()
+ show_titlescreen() //SKYRAT EDIT CHANGE
if(SSticker.current_state < GAME_STATE_SETTING_UP)
- var/tl = SSticker.GetTimeLeft()
- to_chat(src, "Please set up your character and select \"Ready\". The game will start [tl > 0 ? "in about [DisplayTimeText(tl)]" : "soon"].")
+ // var/tl = SSticker.GetTimeLeft()
+ //to_chat(src, "Please set up your character and select \"Ready\". The game will start [tl > 0 ? "in about [DisplayTimeText(tl)]" : "soon"].") SKYRAT EDIT REMOVAL
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index 15f51f5c34e..53832c03b60 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -1,3 +1,4 @@
+/* SKYRAT EDIT REMOVAL - MOVED TO MODULAR
#define LINKIFY_READY(string, value) "[string] "
/mob/dead/new_player
@@ -49,7 +50,7 @@
var/datum/asset/asset_datum = get_asset_datum(/datum/asset/simple/lobby)
asset_datum.send(client)
- var/list/output = list("
Setup Character([client.prefs.real_name] )
") //SKYRAT EDIT CHANGE - ORIGINAL: var/list/output = list("Setup Character
")
+ var/list/output = list("Setup Character
")
if(SSticker.current_state <= GAME_STATE_PREGAME)
switch(ready)
@@ -59,14 +60,10 @@
output += "\[ Ready | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)] \]
"
if(PLAYER_READY_TO_OBSERVE)
output += "\[ [LINKIFY_READY("Ready", PLAYER_READY_TO_PLAY)] | [LINKIFY_READY("Not Ready", PLAYER_NOT_READY)] | Observe \]
"
- output += "Be antagonist: [client.prefs.be_antag ? "Enabled" : "Disabled"]
" //SKYRAT EDIT ADDITION
- output += "Be midround antagonist: [(client.prefs.toggles & MIDROUND_ANTAG) ? "Enabled" : "Disabled"]
" //SKYRAT EDIT ADDITION
else
output += "View the Crew Manifest
"
output += "Join Game!
"
output += "[LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)]
"
- output += "Be Latejoin Antagonist: [client.prefs.be_antag ? "Enabled" : "Disabled"]
" //SKYRAT EDIT ADDITION
- output += "Be Midround Antagonist: [(client.prefs.toggles & MIDROUND_ANTAG) ? "Enabled" : "Disabled"]
" //SKYRAT EDIT ADDITION
if(!IsGuestKey(src.key))
output += playerpolls()
@@ -133,7 +130,6 @@
relevant_cap = max(hpc, epc)
if(href_list["show_preferences"])
- client.prefs.needs_update = TRUE //SKYRAT EDIT ADDITION - CUSTOMIZATION
client.prefs.ShowChoices(src)
return TRUE
@@ -154,18 +150,6 @@
src << browse(null, "window=playersetup") //closes the player setup window
new_player_panel()
- //SKYRAT EDIT ADDITION BEGIN
- if(href_list["antagtoggle"])
- client.prefs.be_antag = !client.prefs.be_antag
- to_chat(usr, "You will now [client.prefs.be_antag ? "be considered" : "not be considered"] for any antagonist positions set in your preferences. ")
- new_player_panel()
-
- if(href_list["midroundantag"])
- client.prefs.toggles ^= MIDROUND_ANTAG
- to_chat(usr, "You will now [(client.prefs.toggles & MIDROUND_ANTAG) ? "be considered" : "not be considered"] for any midround antagonist opportunities. ")
- new_player_panel()
- //SKYRAT EDIT END
-
if(href_list["late_join"])
if(!SSticker?.IsRoundInProgress())
to_chat(usr, "The round is either not ready, or has already finished... ")
@@ -278,12 +262,6 @@
return "Your account is not old enough for [jobtitle]."
if(JOB_UNAVAILABLE_SLOTFULL)
return "[jobtitle] is already filled to capacity."
- //SKYRAT EDIT ADDITION BEGIN - CUSTOMIZATION
- if(JOB_UNAVAILABLE_QUIRK)
- return "[jobtitle] is restricted from your quirks."
- if(JOB_UNAVAILABLE_SPECIES)
- return "[jobtitle] is restricted from your species."
- //SKYRAT EDIT END
return "Error: Unknown job availability."
/mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE)
@@ -307,12 +285,6 @@
return JOB_UNAVAILABLE_ACCOUNTAGE
if(job.required_playtime_remaining(client))
return JOB_UNAVAILABLE_PLAYTIME
- //SKYRAT EDIT ADDITION BEGIN - CUSTOMIZATION
- if(job.has_banned_quirk(client.prefs))
- return JOB_UNAVAILABLE_QUIRK
- if(job.has_banned_species(client.prefs))
- return JOB_UNAVAILABLE_SPECIES
- //SKYRAT EDIT END
if(latejoin && !job.special_check_latejoin(client))
return JOB_UNAVAILABLE_GENERIC
return JOB_AVAILABLE
@@ -372,15 +344,6 @@
SSticker.minds += character.mind
character.client.init_verbs() // init verbs for the late join
var/mob/living/carbon/human/humanc
-
- //SKRYAT EDIT ADDITION BEGIN - ASSAULTOPS
- if(SSticker.mode.name == "assaultops")
- if(is_assaultops_target(character.mind))
- remove_assaultops_target(character.mind, original=TRUE)
- if(check_assaultops_target(character))
- add_assaultops_target(character, notify_target = TRUE)
- //SKYRAT EDIT ADDITION END
-
if(ishuman(character))
humanc = character //Let's retypecast the var to be human,
@@ -426,9 +389,6 @@
/mob/dead/new_player/proc/LateChoices()
var/list/dat = list("Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
")
- //SKYRAT EDIT ADDITION BEGIN - ALERTS
- dat += "Alert Level: [capitalize(num2seclevel(GLOB.security_level))]
"
- //SKYRAT EDIT END
if(SSshuttle.emergency)
switch(SSshuttle.emergency.mode)
if(SHUTTLE_ESCAPE)
@@ -599,3 +559,4 @@
// Add verb for re-opening the interview panel, and re-init the verbs for the stat panel
add_verb(src, /mob/dead/new_player/proc/open_interview)
+*/
diff --git a/html/browser/FixedsysExcelsior3.01Regular.ttf b/html/browser/FixedsysExcelsior3.01Regular.ttf
new file mode 100644
index 00000000000..a4f177c41a8
Binary files /dev/null and b/html/browser/FixedsysExcelsior3.01Regular.ttf differ
diff --git a/html/browser/TerminusTTFWindows-4.47.0.ttf b/html/browser/TerminusTTFWindows-4.47.0.ttf
new file mode 100644
index 00000000000..180b60d3158
Binary files /dev/null and b/html/browser/TerminusTTFWindows-4.47.0.ttf differ
diff --git a/interface/skin.dmf b/interface/skin.dmf
index 168dd6eb06c..b616cd2c330 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -116,6 +116,17 @@ window "mapwindow"
right-click = true
saved-params = "zoom;letterbox;zoom-mode"
style = ".center { text-align: center; } .maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; } .command_headset { font-weight: bold;\tfont-size: 8px; } .small { font-size: 6px; } .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: 6px; }"
+ elem "lobbybrowser"
+ type = BROWSER
+ pos = 0,0
+ size = 640x480
+ anchor1 = 0,0
+ anchor2 = 100,100
+ background-color = none
+ is-visible = false
+ is-disabled = true
+ saved-params = ""
+ auto-format = false
elem "status_bar"
type = LABEL
pos = 0,464
diff --git a/modular_skyrat/master_files/code/modules/mob/living/dead/new_player/new_player.dm b/modular_skyrat/master_files/code/modules/mob/living/dead/new_player/new_player.dm
new file mode 100644
index 00000000000..259226694c7
--- /dev/null
+++ b/modular_skyrat/master_files/code/modules/mob/living/dead/new_player/new_player.dm
@@ -0,0 +1,572 @@
+#define LINKIFY_READY(string, value) "[string] "
+
+/mob/dead/new_player
+ var/ready = 0
+ var/spawning = 0//Referenced when you want to delete the new_player later on in the code.
+
+ flags_1 = NONE
+
+ invisibility = INVISIBILITY_ABSTRACT
+
+ density = FALSE
+ stat = DEAD
+ hud_possible = list()
+
+ var/mob/living/new_character //for instant transfer once the round is set up
+
+ //Used to make sure someone doesn't get spammed with messages if they're ineligible for roles
+ var/ineligible_for_roles = FALSE
+
+ var/client/my_client
+
+/mob/dead/new_player/Initialize()
+ if(client && SSticker.state == GAME_STATE_STARTUP)
+ var/atom/movable/screen/splash/S = new(client, TRUE, TRUE)
+ S.Fade(TRUE)
+
+ if(length(GLOB.newplayer_start))
+ forceMove(pick(GLOB.newplayer_start))
+ else
+ forceMove(locate(1,1,1))
+
+ ComponentInitialize()
+
+ . = ..()
+
+ GLOB.new_player_list += src
+
+/mob/dead/new_player/Destroy()
+ GLOB.new_player_list -= src
+ return ..()
+
+/mob/dead/new_player/prepare_huds()
+ return
+
+/**
+ * This proc generates the panel that opens to all newly joining players, allowing them to join, observe, view polls, view the current crew manifest, and open the character customization menu.
+ */
+/mob/dead/new_player/proc/show_titlescreen()
+ if (client?.interviewee)
+ return
+
+ winset(src, "lobbybrowser", "is-disabled=false;is-visible=true")
+
+ var/datum/asset/assets = get_asset_datum(/datum/asset/simple/lobby) //Sending pictures to the client
+ assets.send(src)
+
+ update_titlescreen()
+
+/mob/dead/new_player/proc/update_titlescreen()
+ var/dat = get_lobby_html()
+
+ src << browse(GLOB.current_lobby_screen, "file=titlescreen.gif;display=0")
+ src << browse(dat, "window=lobbybrowser")
+
+/datum/asset/simple/lobby
+ assets = list(
+ "FixedsysExcelsior3.01Regular.ttf" = 'html/browser/FixedsysExcelsior3.01Regular.ttf',
+ )
+
+/mob/dead/new_player/proc/hide_titlescreen()
+ if(my_client.mob)
+ winset(my_client, "lobbybrowser", "is-disabled=true;is-visible=false")
+
+/mob/dead/new_player/proc/playerpolls()
+ var/output
+ if (SSdbcore.Connect())
+ var/isadmin = FALSE
+ if(client?.holder)
+ isadmin = TRUE
+ var/datum/db_query/query_get_new_polls = SSdbcore.NewQuery({"
+ SELECT id FROM [format_table_name("poll_question")]
+ WHERE (adminonly = 0 OR :isadmin = 1)
+ AND Now() BETWEEN starttime AND endtime
+ AND deleted = 0
+ AND id NOT IN (
+ SELECT pollid FROM [format_table_name("poll_vote")]
+ WHERE ckey = :ckey
+ AND deleted = 0
+ )
+ AND id NOT IN (
+ SELECT pollid FROM [format_table_name("poll_textreply")]
+ WHERE ckey = :ckey
+ AND deleted = 0
+ )
+ "}, list("isadmin" = isadmin, "ckey" = ckey))
+ if(!query_get_new_polls.Execute())
+ qdel(query_get_new_polls)
+ return
+ if(query_get_new_polls.NextRow())
+ output +={""}
+ else
+ output +={""}
+ qdel(query_get_new_polls)
+ if(QDELETED(src))
+ return
+ return output
+
+/mob/dead/new_player/Topic(href, href_list[])
+ if(src != usr || !client)
+ return
+
+ if(client.interviewee)
+ return FALSE
+
+ //Determines Relevent Population Cap
+ var/relevant_cap
+ var/hpc = CONFIG_GET(number/hard_popcap)
+ var/epc = CONFIG_GET(number/extreme_popcap)
+ if(hpc && epc)
+ relevant_cap = min(hpc, epc)
+ else
+ relevant_cap = max(hpc, epc)
+
+ if(href_list["lobby_setup"])
+ client.prefs.needs_update = TRUE
+ client.prefs.ShowChoices(src)
+ return TRUE
+
+ if(href_list["lobby_ready"])
+ if(SSticker.current_state <= GAME_STATE_PREGAME)
+ client << output(null, "lobbybrowser:imgsrc")
+ ready = !ready
+ return
+
+ if(href_list["lobby_observe"])
+ ready = PLAYER_READY_TO_OBSERVE
+ hide_titlescreen()
+ make_me_an_observer()
+ return
+
+ if(href_list["lobby_antagtoggle"])
+ client.prefs.be_antag = !client.prefs.be_antag
+ update_titlescreen()
+ to_chat(usr, "You will now [client.prefs.be_antag ? "be considered" : "not be considered"] for any antagonist positions set in your preferences. ")
+ return
+
+ if(href_list["lobby_join"])
+ if(!SSticker?.IsRoundInProgress())
+ to_chat(usr, "The round is either not ready, or has already finished... ")
+ return
+
+ if(href_list["lobby_join"] == "override")
+ LateChoices()
+ return
+
+ if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums)))
+ to_chat(usr, "[CONFIG_GET(string/hard_popcap_message)] ")
+
+ var/queue_position = SSticker.queued_players.Find(usr)
+ if(queue_position == 1)
+ to_chat(usr, "You are next in line to join the game. You will be notified when a slot opens up. ")
+ else if(queue_position)
+ to_chat(usr, "There are [queue_position-1] players in front of you in the queue to join the game. ")
+ else
+ SSticker.queued_players += usr
+ to_chat(usr, "You have been added to the queue to join the game. Your position in queue is [SSticker.queued_players.len]. ")
+ return
+ LateChoices()
+
+ if(href_list["lobby_crew"])
+ ViewManifest()
+ return
+
+
+ if(href_list["SelectedJob"])
+ if(!SSticker?.IsRoundInProgress())
+ to_chat(usr, "The round is either not ready, or has already finished... ")
+ return
+
+ if(!GLOB.enter_allowed)
+ to_chat(usr, "There is an administrative lock on entering the game! ")
+ return
+
+ if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums))
+ if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1]))
+ to_chat(usr, "Server is full. ")
+ return
+
+ AttemptLateSpawn(href_list["SelectedJob"])
+ return
+
+ if(href_list["showpoll"])
+ handle_player_polling()
+ return
+
+ if(href_list["viewpoll"])
+ var/datum/poll_question/poll = locate(href_list["viewpoll"]) in GLOB.polls
+ poll_player(poll)
+
+ if(href_list["votepollref"])
+ var/datum/poll_question/poll = locate(href_list["votepollref"]) in GLOB.polls
+ vote_on_poll_handler(poll, href_list)
+
+ if(href_list["lobby_changelog"])
+ client.changelog()
+
+//When you cop out of the round (NB: this HAS A SLEEP FOR PLAYER INPUT IN IT)
+/mob/dead/new_player/proc/make_me_an_observer()
+ if(QDELETED(src) || !src.client)
+ ready = PLAYER_NOT_READY
+ return FALSE
+
+ var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will not be able to play this round!","Player Setup","Yes","No")
+
+ if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes")
+ ready = PLAYER_NOT_READY
+ show_titlescreen()
+ return FALSE
+
+ var/mob/dead/observer/observer = new()
+ spawning = TRUE
+
+ observer.started_as_observer = TRUE
+ close_spawn_windows()
+ var/obj/effect/landmark/observer_start/O = locate(/obj/effect/landmark/observer_start) in GLOB.landmarks_list
+ to_chat(src, "Now teleporting. ")
+ if (O)
+ observer.forceMove(O.loc)
+ else
+ to_chat(src, "Teleporting failed. Ahelp an admin please ")
+ stack_trace("There's no freaking observer landmark available on this map or you're making observers before the map is initialised")
+ observer.key = key
+ observer.client = client
+ observer.set_ghost_appearance()
+ if(observer.client && observer.client.prefs)
+ observer.real_name = observer.client.prefs.real_name
+ observer.name = observer.real_name
+ observer.client.init_verbs()
+ observer.update_appearance()
+ observer.stop_sound_channel(CHANNEL_LOBBYMUSIC)
+ deadchat_broadcast(" has observed.", "[observer.real_name] ", follow_target = observer, turf_target = get_turf(observer), message_type = DEADCHAT_DEATHRATTLE)
+ QDEL_NULL(mind)
+ qdel(src)
+ return TRUE
+
+/proc/get_job_unavailable_error_message(retval, jobtitle)
+ switch(retval)
+ if(JOB_AVAILABLE)
+ return "[jobtitle] is available."
+ if(JOB_UNAVAILABLE_GENERIC)
+ return "[jobtitle] is unavailable."
+ if(JOB_UNAVAILABLE_BANNED)
+ return "You are currently banned from [jobtitle]."
+ if(JOB_UNAVAILABLE_PLAYTIME)
+ return "You do not have enough relevant playtime for [jobtitle]."
+ if(JOB_UNAVAILABLE_ACCOUNTAGE)
+ return "Your account is not old enough for [jobtitle]."
+ if(JOB_UNAVAILABLE_SLOTFULL)
+ return "[jobtitle] is already filled to capacity."
+ if(JOB_UNAVAILABLE_QUIRK)
+ return "[jobtitle] is restricted from your quirks."
+ if(JOB_UNAVAILABLE_SPECIES)
+ return "[jobtitle] is restricted from your species."
+ return "Error: Unknown job availability."
+
+/mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE)
+ var/datum/job/job = SSjob.GetJob(rank)
+ if(!job)
+ return JOB_UNAVAILABLE_GENERIC
+ if((job.current_positions >= job.total_positions) && job.total_positions != -1)
+ if(job.title == "Assistant")
+ if(isnum(client.player_age) && client.player_age <= 14) //Newbies can always be assistants
+ return JOB_AVAILABLE
+ for(var/datum/job/J in SSjob.occupations)
+ if(J && J.current_positions < J.total_positions && J.title != job.title)
+ return JOB_UNAVAILABLE_SLOTFULL
+ else
+ return JOB_UNAVAILABLE_SLOTFULL
+ if(is_banned_from(ckey, rank))
+ return JOB_UNAVAILABLE_BANNED
+ if(QDELETED(src))
+ return JOB_UNAVAILABLE_GENERIC
+ if(!job.player_old_enough(client))
+ return JOB_UNAVAILABLE_ACCOUNTAGE
+ if(job.required_playtime_remaining(client))
+ return JOB_UNAVAILABLE_PLAYTIME
+ if(job.has_banned_quirk(client.prefs))
+ return JOB_UNAVAILABLE_QUIRK
+ if(job.has_banned_species(client.prefs))
+ return JOB_UNAVAILABLE_SPECIES
+ if(latejoin && !job.special_check_latejoin(client))
+ return JOB_UNAVAILABLE_GENERIC
+ return JOB_AVAILABLE
+
+/mob/dead/new_player/proc/AttemptLateSpawn(rank)
+ var/error = IsJobUnavailable(rank)
+ if(error != JOB_AVAILABLE)
+ alert(src, get_job_unavailable_error_message(error, rank))
+ return FALSE
+
+ if(SSticker.late_join_disabled)
+ alert(src, "An administrator has disabled late join spawning.")
+ return FALSE
+
+ var/arrivals_docked = TRUE
+ if(SSshuttle.arrivals)
+ close_spawn_windows() //In case we get held up
+ if(SSshuttle.arrivals.damaged && CONFIG_GET(flag/arrivals_shuttle_require_safe_latejoin))
+ src << alert("The arrivals shuttle is currently malfunctioning! You cannot join.")
+ return FALSE
+
+ if(CONFIG_GET(flag/arrivals_shuttle_require_undocked))
+ SSshuttle.arrivals.RequireUndocked(src)
+ arrivals_docked = SSshuttle.arrivals.mode != SHUTTLE_CALL
+
+ //Remove the player from the join queue if he was in one and reset the timer
+ SSticker.queued_players -= src
+ SSticker.queue_delay = 4
+
+ SSjob.AssignRole(src, rank, 1)
+
+ var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind
+
+ var/is_captain = FALSE
+ // If we don't have an assigned cap yet, check if this person qualifies for some from of captaincy.
+ if(!SSjob.assigned_captain && ishuman(character) && SSjob.chain_of_command[rank] && !is_banned_from(ckey, list("Captain")))
+ is_captain = TRUE
+ // If we already have a captain, are they a "Captain" rank and are we allowing multiple of them to be assigned?
+ else if(SSjob.always_promote_captain_job && (rank == "Captain"))
+ is_captain = TRUE
+
+ var/equip = SSjob.EquipRank(character, rank, TRUE, is_captain)
+ if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob.
+ character = equip
+
+ var/datum/job/job = SSjob.GetJob(rank)
+
+ if(job && !job.override_latejoin_spawn(character))
+ SSjob.SendToLateJoin(character)
+ if(!arrivals_docked)
+ var/atom/movable/screen/splash/Spl = new(character.client, TRUE)
+ Spl.Fade(TRUE)
+ character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25)
+
+ character.update_parallax_teleport()
+
+ SSticker.minds += character.mind
+ character.client.init_verbs() // init verbs for the late join
+ var/mob/living/carbon/human/humanc
+
+ if(SSticker.mode.name == "assaultops")
+ if(is_assaultops_target(character.mind))
+ remove_assaultops_target(character.mind, original=TRUE)
+ if(check_assaultops_target(character))
+ add_assaultops_target(character, notify_target = TRUE)
+
+ if(ishuman(character))
+ humanc = character //Let's retypecast the var to be human,
+
+ if(humanc) //These procs all expect humans
+ GLOB.data_core.manifest_inject(humanc)
+ if(SSshuttle.arrivals)
+ SSshuttle.arrivals.QueueAnnounce(humanc, rank)
+ else
+ AnnounceArrival(humanc, rank)
+ AddEmploymentContract(humanc)
+
+ humanc.increment_scar_slot()
+ humanc.load_persistent_scars()
+
+ if(GLOB.curse_of_madness_triggered)
+ give_madness(humanc, GLOB.curse_of_madness_triggered)
+
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_CREWMEMBER_JOINED, humanc, rank)
+
+ GLOB.joined_player_list += character.ckey
+
+ if(CONFIG_GET(flag/allow_latejoin_antagonists) && humanc) //Borgs aren't allowed to be antags. Will need to be tweaked if we get true latejoin ais.
+ if(SSshuttle.emergency)
+ switch(SSshuttle.emergency.mode)
+ if(SHUTTLE_RECALL, SHUTTLE_IDLE)
+ SSticker.mode.make_antag_chance(humanc)
+ if(SHUTTLE_CALL)
+ if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5)
+ SSticker.mode.make_antag_chance(humanc)
+
+ if(humanc && CONFIG_GET(flag/roundstart_traits))
+ SSquirks.AssignQuirks(humanc, humanc.client, TRUE)
+
+ log_manifest(character.mind.key,character.mind,character,latejoin = TRUE)
+
+/mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee)
+ //TODO: figure out a way to exclude wizards/nukeops/demons from this.
+ for(var/C in GLOB.employmentCabinets)
+ var/obj/structure/filingcabinet/employment/employmentCabinet = C
+ if(!employmentCabinet.virgin)
+ employmentCabinet.addFile(employee)
+
+
+/mob/dead/new_player/proc/LateChoices()
+ var/list/dat = list("Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
")
+ dat += "Alert Level: [capitalize(num2seclevel(GLOB.security_level))]
"
+ if(SSshuttle.emergency)
+ switch(SSshuttle.emergency.mode)
+ if(SHUTTLE_ESCAPE)
+ dat += "The station has been evacuated.
"
+ if(SHUTTLE_CALL)
+ if(!SSshuttle.canRecall())
+ dat += "The station is currently undergoing evacuation procedures.
"
+ for(var/datum/job/prioritized_job in SSjob.prioritized_jobs)
+ if(prioritized_job.current_positions >= prioritized_job.total_positions)
+ SSjob.prioritized_jobs -= prioritized_job
+ dat += ""
+ var/column_counter = 0
+ // render each category's available jobs
+ for(var/category in GLOB.position_categories)
+ // position_categories contains category names mapped to available jobs and an appropriate color
+ var/cat_color = GLOB.position_categories[category]["color"]
+ dat += ""
+ dat += "[category] "
+ var/list/dept_dat = list()
+ for(var/job in GLOB.position_categories[category]["jobs"])
+ var/datum/job/job_datum = SSjob.name_occupations[job]
+ if(job_datum && IsJobUnavailable(job_datum.title, TRUE) == JOB_AVAILABLE)
+ var/command_bold = ""
+ if(job in GLOB.command_positions)
+ command_bold = " command"
+ if(job_datum in SSjob.prioritized_jobs)
+ dept_dat += "[job_datum.title] ([job_datum.current_positions]) "
+ else
+ dept_dat += "[job_datum.title] ([job_datum.current_positions]) "
+ if(!dept_dat.len)
+ dept_dat += "No positions open. "
+ dat += jointext(dept_dat, "")
+ dat += " "
+ column_counter++
+ if(column_counter > 0 && (column_counter % 3 == 0))
+ dat += " "
+ dat += "
"
+ dat += ""
+ var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 680, 580)
+ popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css')
+ popup.set_content(jointext(dat, ""))
+ popup.open(FALSE) // 0 is passed to open so that it doesn't use the onclose() proc
+
+/mob/dead/new_player/proc/create_character(transfer_after)
+ spawning = 1
+ close_spawn_windows()
+
+ var/mob/living/carbon/human/H = new(loc)
+
+ var/frn = CONFIG_GET(flag/force_random_names)
+ var/admin_anon_names = SSticker.anonymousnames
+ if(!frn)
+ frn = is_banned_from(ckey, "Appearance")
+ if(QDELETED(src))
+ return
+ if(frn)
+ client.prefs.random_character()
+ client.prefs.real_name = client.prefs.pref_species.random_name(gender,1)
+
+ var/is_antag
+ if(mind in GLOB.pre_setup_antags)
+ is_antag = TRUE
+
+ client.prefs.copy_to(H, antagonist = is_antag, is_latejoiner = transfer_after)
+
+ if(admin_anon_names)//overrides random name because it achieves the same effect and is an admin enabled event tool
+ randomize_human(H)
+ H.fully_replace_character_name(null, SSticker.anonymousnames.anonymous_name(H))
+
+ H.dna.update_dna_identity()
+ if(mind)
+ if(transfer_after)
+ mind.late_joiner = TRUE
+ mind.active = FALSE //we wish to transfer the key manually
+ mind.original_character_slot_index = client.prefs.default_slot
+ mind.transfer_to(H) //won't transfer key since the mind is not active
+ mind.original_character = H
+
+ H.name = real_name
+ client.init_verbs()
+ . = H
+ new_character = .
+ if(transfer_after)
+ transfer_character()
+
+/mob/dead/new_player/proc/transfer_character()
+ . = new_character
+ if(.)
+ new_character.key = key //Manually transfer the key to log them in,
+ new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC)
+ new_character = null
+ qdel(src)
+
+/mob/dead/new_player/proc/ViewManifest()
+ if(!client)
+ return
+ if(world.time < client.crew_manifest_delay)
+ return
+ client.crew_manifest_delay = world.time + (1 SECONDS)
+
+ var/dat = " "
+ dat += "Crew Manifest "
+ dat += GLOB.data_core.get_manifest_html()
+
+ src << browse(dat, "window=manifest;size=387x420;can_close=1")
+
+/mob/dead/new_player/Move()
+ return 0
+
+
+/mob/dead/new_player/proc/close_spawn_windows()
+
+ src << browse(null, "window=latechoices") //closes late choices window
+ hide_titlescreen() //closes the player setup window
+ src << browse(null, "window=preferences") //closes job selection
+ src << browse(null, "window=mob_occupation")
+ src << browse(null, "window=latechoices") //closes late job selection
+
+// Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense.
+// A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled
+// Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not available"
+// Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role
+// This also does some admin notification and logging as well, as well as some extra logic to make sure things don't go wrong
+/mob/dead/new_player/proc/check_preferences()
+ if(!client)
+ return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe.
+ if(client.prefs.joblessrole != RETURNTOLOBBY)
+ return TRUE
+ // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so.
+ var/has_antags = FALSE
+ if(client.prefs.be_special.len > 0)
+ has_antags = TRUE
+ if(client.prefs.job_preferences.len == 0)
+ if(!ineligible_for_roles)
+ to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences. ")
+ ineligible_for_roles = TRUE
+ ready = PLAYER_NOT_READY
+ if(has_antags)
+ log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.")
+ message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.")
+
+ return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well
+ return TRUE
+
+/**
+ * Prepares a client for the interview system, and provides them with a new interview
+ *
+ * This proc will both prepare the user by removing all verbs from them, as well as
+ * giving them the interview form and forcing it to appear.
+ */
+/mob/dead/new_player/proc/register_for_interview()
+ // First we detain them by removing all the verbs they have on client
+ for (var/v in client.verbs)
+ var/procpath/verb_path = v
+ if (!(verb_path in GLOB.stat_panel_verbs))
+ remove_verb(client, verb_path)
+
+ // Then remove those on their mob as well
+ for (var/v in verbs)
+ var/procpath/verb_path = v
+ if (!(verb_path in GLOB.stat_panel_verbs))
+ remove_verb(src, verb_path)
+
+ // Then we create the interview form and show it to the client
+ var/datum/interview/I = GLOB.interviews.interview_for_client(client)
+ if (I)
+ I.ui_interact(src)
+
+ // Add verb for re-opening the interview panel, and re-init the verbs for the stat panel
+ add_verb(src, /mob/dead/new_player/proc/open_interview)
diff --git a/modular_skyrat/modules/customization/modules/client/preferences.dm b/modular_skyrat/modules/customization/modules/client/preferences.dm
index d4a2a4de7c8..c1d3f974cb6 100644
--- a/modular_skyrat/modules/customization/modules/client/preferences.dm
+++ b/modular_skyrat/modules/customization/modules/client/preferences.dm
@@ -2786,7 +2786,7 @@ GLOBAL_LIST_INIT(food, list(
if(istype(parent.mob, /mob/dead/new_player)) //is this shitcode? probably - I DONT CAREEE~
var/mob/dead/new_player/NP = parent.mob
- NP.new_player_panel()
+ NP.show_titlescreen()
if("tab")
if (href_list["tab"])
diff --git a/modular_skyrat/modules/lobbyscreen/code/change_titlescreen.dm b/modular_skyrat/modules/lobbyscreen/code/change_titlescreen.dm
new file mode 100644
index 00000000000..1c1cc5445f3
--- /dev/null
+++ b/modular_skyrat/modules/lobbyscreen/code/change_titlescreen.dm
@@ -0,0 +1,24 @@
+/client/proc/change_title_screen()
+ set category = "Admin.Fun"
+ set name = "Title Screen: Change"
+
+ if(!check_rights(R_FUN))
+ return
+
+ log_admin("[key_name(usr)] is changing the titlescreen.")
+ message_admins("[key_name_admin(usr)] is changing the titlescreen.")
+
+ switch(alert(usr, "Please select a new titlescreen.", "Title Screen", "Change", "Reset", "Cancel"))
+ if("Change")
+ var/file = input(usr) as icon|null
+ if(!file)
+ return
+ change_lobbyscreen(file)
+ if("Reset")
+ change_lobbyscreen()
+ if("Cancel")
+ return
+
+ for(var/mob/dead/new_player/N in GLOB.new_player_list)
+ N.show_titlescreen()
+
diff --git a/modular_skyrat/modules/lobbyscreen/code/lobby.dm b/modular_skyrat/modules/lobbyscreen/code/lobby.dm
new file mode 100644
index 00000000000..2cbf5275174
--- /dev/null
+++ b/modular_skyrat/modules/lobbyscreen/code/lobby.dm
@@ -0,0 +1,210 @@
+GLOBAL_LIST_EMPTY(startup_messages)
+
+/mob/dead/new_player/proc/get_lobby_html()
+ var/dat = {"
+
+
+
+
+
+
+
+ "}
+ if(SSticker.current_state == GAME_STATE_STARTUP)
+ dat += {" "}
+ dat += {"
+
+
+ "}
+ var/loop_index = 0
+ for(var/i in GLOB.startup_messages)
+ if(loop_index >= 25)
+ break
+ dat += i
+ loop_index++
+ dat += "
"
+
+ else
+
+ if(!IsGuestKey(src.key))
+ dat += {"
+
+ "}
+ dat += playerpolls()
+ dat += "
"
+
+ dat += {"
+
+
+ "}
+ if(!SSticker || SSticker.current_state <= GAME_STATE_PREGAME)
+ dat += {"
+ "}
+ else
+ dat += {"
+ "}
+ dat += {"
+ "}
+
+ dat += {"
+ "}
+
+ dat += {"
+ "}
+ dat += {"
+ "}
+
+ dat += "
"
+ dat += {" "}
+ dat += {"
+
+ "}
+
+ dat += ""
+
+ return dat
+
+/proc/add_startupmessage(msg)
+ var/msg_dat = {""}
+
+ GLOB.startup_messages.Insert(1, msg_dat)
+
+ for(var/mob/dead/new_player/N in GLOB.new_player_list)
+ INVOKE_ASYNC(N, /mob/dead/new_player.proc/update_titlescreen)
diff --git a/modular_skyrat/modules/lobbyscreen/code/title_screen.dm b/modular_skyrat/modules/lobbyscreen/code/title_screen.dm
new file mode 100644
index 00000000000..0b6c866367d
--- /dev/null
+++ b/modular_skyrat/modules/lobbyscreen/code/title_screen.dm
@@ -0,0 +1,59 @@
+GLOBAL_VAR(current_lobby_screen)
+
+GLOBAL_LIST_EMPTY(lobby_screens)
+
+SUBSYSTEM_DEF(title)
+ name = "Title Screen"
+ flags = SS_NO_FIRE
+ init_order = INIT_ORDER_TITLE
+
+ var/file_path
+ var/icon/startup_splash
+
+/datum/controller/subsystem/title/Initialize()
+ if(file_path)
+ return
+
+ var/list/provisional_title_screens = flist("[global.config.directory]/title_screens/images/")
+ var/list/title_screens = list()
+
+ for(var/S in provisional_title_screens)
+ var/list/L = splittext(S,"+")
+ if((L.len == 1 && (L[1] != "exclude" && L[1] != "blank.png" && L[1] != "startup_splash")))
+ title_screens += S
+
+ if(L.len > 1 && lowertext(L[1]) == "startup_splash")
+ var/file_path = "[global.config.directory]/title_screens/images/[S]"
+ ASSERT(fexists(file_path))
+ startup_splash = new(fcopy_rsc(file_path))
+
+ if(startup_splash)
+ change_lobbyscreen(startup_splash)
+ else
+ change_lobbyscreen('modular_skyrat/modules/lobbyscreen/icons/loading_screen.gif')
+
+ if(length(title_screens))
+ for(var/i in title_screens)
+ var/file_path = "[global.config.directory]/title_screens/images/[i]"
+ ASSERT(fexists(file_path))
+ var/icon/title2use = new(fcopy_rsc(file_path))
+ GLOB.lobby_screens += title2use
+
+ return ..()
+
+/datum/controller/subsystem/title/Recover()
+ startup_splash = SStitle.startup_splash
+ file_path = SStitle.file_path
+
+
+/proc/change_lobbyscreen(new_screen)
+ if(new_screen)
+ GLOB.current_lobby_screen = new_screen
+ else
+ if(GLOB.lobby_screens.len)
+ GLOB.current_lobby_screen = pick(GLOB.lobby_screens)
+ else
+ GLOB.current_lobby_screen = 'modular_skyrat/modules/lobbyscreen/icons/skyrat_lobbyscreen.png'
+
+ for(var/mob/dead/new_player/N in GLOB.new_player_list)
+ INVOKE_ASYNC(N, /mob/dead/new_player.proc/show_titlescreen)
diff --git a/modular_skyrat/modules/lobbyscreen/icons/loading_screen.gif b/modular_skyrat/modules/lobbyscreen/icons/loading_screen.gif
new file mode 100644
index 00000000000..c8a2c487037
Binary files /dev/null and b/modular_skyrat/modules/lobbyscreen/icons/loading_screen.gif differ
diff --git a/modular_skyrat/modules/lobbyscreen/icons/skyrat_lobbyscreen.png b/modular_skyrat/modules/lobbyscreen/icons/skyrat_lobbyscreen.png
new file mode 100644
index 00000000000..3141156a6bd
Binary files /dev/null and b/modular_skyrat/modules/lobbyscreen/icons/skyrat_lobbyscreen.png differ
diff --git a/modular_skyrat/modules/lobbyscreen/readme.md b/modular_skyrat/modules/lobbyscreen/readme.md
new file mode 100644
index 00000000000..713eee38319
--- /dev/null
+++ b/modular_skyrat/modules/lobbyscreen/readme.md
@@ -0,0 +1,10 @@
+## Title: Lobby screens!
+
+MODULE ID: lobbyscreen
+
+### Description:
+
+Adds a brand new look to the lobby screen, adds a splash screen too, since that shit is awesome. Inspired by TauCeti, heavily modified by me.
+
+### Credits:
+Gandalf2k15 & TauCeti
diff --git a/tgstation.dme b/tgstation.dme
index 30118cf6e83..b7f2b14d51f 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -3561,6 +3561,7 @@
#include "modular_skyrat\master_files\code\modules\language\language_holders.dm"
#include "modular_skyrat\master_files\code\modules\mob\living\emote_popup.dm"
#include "modular_skyrat\master_files\code\modules\mob\living\carbon\carbon_say.dm"
+#include "modular_skyrat\master_files\code\modules\mob\living\dead\new_player\new_player.dm"
#include "modular_skyrat\master_files\code\modules\mob\living\simple_animal\friendly\bumbles.dm"
#include "modular_skyrat\master_files\code\modules\power\lighting.dm"
#include "modular_skyrat\master_files\code\modules\projectiles\guns\gun.dm"
@@ -4024,6 +4025,9 @@
#include "modular_skyrat\modules\jungle\code\game\objects\structures\flora.dm"
#include "modular_skyrat\modules\large_doors\code\large_doors.dm"
#include "modular_skyrat\modules\layer_shift\code\modules\mob\mob_movement.dm"
+#include "modular_skyrat\modules\lobbyscreen\code\change_titlescreen.dm"
+#include "modular_skyrat\modules\lobbyscreen\code\lobby.dm"
+#include "modular_skyrat\modules\lobbyscreen\code\title_screen.dm"
#include "modular_skyrat\modules\mapping\code\dungeon.dm"
#include "modular_skyrat\modules\mapping\code\furniture.dm"
#include "modular_skyrat\modules\mapping\code\genpop.dm"