CMSS Lobby Screen (#17581)

* Establish base working new_player tgui

* Tweak to fit virgo

* .

* fix that

* split that

* no as import

* clean up old code

* .

* .

---------

Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
ShadowLarkens
2025-04-26 14:52:01 -07:00
committed by GitHub
parent 4df82c3789
commit e627fb8d1d
29 changed files with 959 additions and 400 deletions
-45
View File
@@ -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
+1 -1
View File
@@ -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
-2
View File
@@ -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
@@ -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)
-1
View File
@@ -353,7 +353,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
+10
View File
@@ -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))
. = ..()
@@ -1,4 +1,5 @@
/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"
+1
View File
@@ -25,6 +25,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()
+3
View File
@@ -245,3 +245,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
@@ -0,0 +1,157 @@
/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 = ..()
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
+6 -31
View File
@@ -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 = "VORE Station"
desc = "How are you reading this?"
screen_loc = "1,1"
icon = 'icons/misc/loading.dmi'
icon_state = "loading"
/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,16 +13,19 @@ var/obj/effect/lobby_image = new /obj/effect/lobby_image
mind.active = 1
mind.current = src
if(client)
persistent_ckey = client.ckey
loc = null
client.screen += lobby_image
my_client = client
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()
+5 -4
View File
@@ -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")
..()
+1 -198
View File
@@ -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,104 +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 = "<div align='center'>"
output += span_bold("Map:") + " [using_map.full_name]<br>"
output += span_bold("Station Time:") + " [stationtime2text()]<br>"
if(!ticker || ticker.current_state <= GAME_STATE_PREGAME)
output += span_bold("Server Initializing!")
else
output += span_bold("Round Duration:") + " [roundduration2text()]<br>"
output += "<hr>"
output += "<p><a href='byond://?src=\ref[src];show_preferences=1'>Character Setup</A></p>"
if(!ticker || ticker.current_state <= GAME_STATE_PREGAME)
if(ready)
output += "<p>\[ " + span_linkOn(span_bold("Ready")) + " | <a href='byond://?src=\ref[src];ready=0'>Not Ready</a> \]</p>"
else
output += "<p>\[ <a href='byond://?src=\ref[src];ready=1'>Ready</a> | " + span_linkOn(span_bold("Not Ready")) + " \]</p>"
output += "<p><s>Join Game!</s></p>"
else
output += "<p><a href='byond://?src=\ref[src];manifest=1'>View the Crew Manifest</A></p>"
output += "<p><a href='byond://?src=\ref[src];late_join=1'>Join Game!</A></p>"
output += "<p><a href='byond://?src=\ref[src];observe=1'>Observe</A></p>"
/*
//nobody uses this feature
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 += "<p><b><a href='byond://?src=\ref[src];showpoll=1'>Show Player Polls</A><br>(NEW!)</b></p>"
else
output += "<p><a href='byond://?src=\ref[src];showpoll=1'>Show Player Polls</A><br><i>No Changes</i></p>"
*/
if(client?.check_for_new_server_news())
output += "<p><b><a href='byond://?src=\ref[src];shownews=1'>Show Server News</A><br>(NEW!)</b></p>"
else
output += "<p><a href='byond://?src=\ref[src];shownews=1'>Show Server News</A><br><i>No Changes</i></p>"
if(SSsqlite.can_submit_feedback(client))
output += "<p>[href(src, list("give_feedback" = 1), "Give Feedback")]</p>"
if(GLOB.news_data.station_newspaper)
if(client.prefs.lastlorenews == GLOB.news_data.newsindex)
output += "<p><a href='byond://?src=\ref[src];open_station_news=1'>Show [using_map.station_name] News<br><i>No Changes</i></A></p>"
else
output += "<p><b><a href='byond://?src=\ref[src];open_station_news=1'>Show [using_map.station_name] News<br>(NEW!)</A></b></p>"
if(read_preference(/datum/preference/text/lastchangelog) == GLOB.changelog_hash)
output += "<p><a href='byond://?src=\ref[src];open_changelog=1'>Show Changelog</A><br><i>No Changes</i></p>"
else
output += "<p><b><a href='byond://?src=\ref[src];open_changelog=1'>Show Changelog</A><br>(NEW!)</b></p>"
output += "</div>"
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()
. = ..()
. += ""
@@ -162,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())
@@ -276,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"])
@@ -334,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)
@@ -473,9 +280,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)
@@ -665,7 +469,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
+18 -6
View File
@@ -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),
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+9
View File
@@ -1284,6 +1284,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"
Binary file not shown.
+135 -92
View File
@@ -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<AlertModalData>();
export function AlertModal(props) {
const { act, data } = useBackend<Data>();
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<HTMLDivElement>) {
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<HTMLDivElement>) {
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 (
<Window height={windowHeight} title={title} width={windowWidth}>
{!!timeout && <Loader value={timeout} />}
<Window.Content onKeyDown={(e) => handleKeyDown(e)}>
<Window.Content onKeyDown={keyDownHandler}>
<Section fill>
<Stack fill vertical>
<Stack.Item grow m={1}>
<Stack.Item m={1}>
<Box color="label" overflow="hidden">
{message}
</Box>
</Stack.Item>
<Stack.Item>
<Stack.Item grow>
{!!autofocus && <Autofocus />}
<ButtonDisplay selected={selected} />
{isVerbose ? (
<VerticalButtons selected={selected} />
) : (
<HorizontalButtons selected={selected} />
)}
</Stack.Item>
</Stack>
</Section>
</Window.Content>
</Window>
);
}
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<Data>();
const { buttons = [], large_buttons, swapped_buttons } = data;
const { selected } = props;
return (
<Stack fill justify="space-around" reverse={!swapped_buttons}>
{buttons.map((button, index) => (
<Stack.Item grow={large_buttons ? 1 : undefined} key={index}>
<Button
fluid={!!large_buttons}
minWidth={5}
onClick={() => act('choose', { choice: button })}
overflowX="hidden"
px={2}
py={large_buttons ? 0.5 : 0}
selected={selected === index}
textAlign="center"
>
{!large_buttons ? button : button.toUpperCase()}
</Button>
</Stack.Item>
))}
</Stack>
);
}
/**
* 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<AlertModalData>();
function VerticalButtons(props: ButtonDisplayProps) {
const { act, data } = useBackend<Data>();
const { buttons = [], large_buttons, swapped_buttons } = data;
const { selected } = props;
return (
<Stack
align="center"
direction={!swapped_buttons ? 'row-reverse' : 'row'}
fill
justify="space-around"
wrap
reverse={!swapped_buttons}
vertical
>
{buttons?.map((button, index) =>
!!large_buttons && buttons.length < 3 ? (
<Stack.Item grow key={index}>
<AlertButton
button={button}
id={index.toString()}
selected={selected === index}
/>
</Stack.Item>
) : (
<Stack.Item key={index}>
<AlertButton
button={button}
id={index.toString()}
selected={selected === index}
/>
</Stack.Item>
),
)}
{buttons.map((button, index) => (
<Stack.Item
grow
width={large_buttons ? '100%' : undefined}
key={index}
m={0}
>
<Button
fluid
minWidth={20}
onClick={() => act('choose', { choice: button })}
overflowX="hidden"
px={2}
py={large_buttons ? 0.5 : 0}
selected={selected === index}
textAlign="center"
>
{!large_buttons ? button : button.toUpperCase()}
</Button>
</Stack.Item>
))}
</Stack>
);
};
/**
* Displays a button with variable sizing.
*/
const AlertButton = (props) => {
const { act, data } = useBackend<AlertModalData>();
const { large_buttons } = data;
const { button, selected } = props;
const buttonWidth = button.length > 7 ? button.length : 7;
return (
<Button
fluid={!!large_buttons}
height={!!large_buttons && 2}
onClick={() => 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()}
</Button>
);
};
}
@@ -0,0 +1,136 @@
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<LobbyData>();
const { setModal, hidden, setHidden } = props;
const {
map,
station_time,
round_start,
round_time,
ready,
new_news,
can_submit_feedback,
show_station_news,
new_station_news,
new_changelog,
} = data;
return (
<Section
p={3}
className="sectionLoad"
style={{ opacity: hidden ? '0' : '1' }}
>
<Stack vertical>
<Stack.Item>
<Stack align="center">
<Stack.Item>
<Box height="68px">
<Box
className="loadEffect NanotrasenIcon"
style={{ cursor: 'pointer' }}
width="100px"
onClick={() => setHidden(true)}
/>
</Box>
</Stack.Item>
<Stack.Item minWidth="200px">
<Stack vertical fill>
<Stack.Item>
<Box className="typeEffect">Welcome to VOREStation.</Box>
</Stack.Item>
<Stack.Item>
<Box className="typeEffect">Map: {map}</Box>
</Stack.Item>
<Stack.Item>
<Box className="typeEffect">Station Time: {station_time}</Box>
</Stack.Item>
<Stack.Item>
<Box className="typeEffect">
Round Duration:{' '}
{round_start ? 'Initializing...' : round_time}{' '}
</Box>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Stack.Item>
<TimedDivider />
<LobbyButton
index={1}
onClick={() => act('character_setup')}
icon="file-lines"
>
Setup Character
</LobbyButton>
<LobbyButton
index={2}
onClick={() => act('manifest')}
icon="circle-user"
>
View Crew Manifest
</LobbyButton>
<LobbyButton index={3} onClick={() => act('shownews')} icon="newspaper">
Show Server News {new_news ? '(NEW!)' : null}
</LobbyButton>
{can_submit_feedback ? (
<LobbyButton
index={3}
onClick={() => act('give_feedback')}
icon="clipboard-question"
>
Give Feedback
</LobbyButton>
) : null}
<LobbyButton
index={4}
onClick={() => act('open_changelog')}
icon="pencil"
>
Show Changelog {new_changelog ? '(NEW!)' : null}
</LobbyButton>
{show_station_news ? (
<LobbyButton index={5} onClick={() => act('')} icon="newspaper">
Show Station News {new_station_news ? '(NEW!)' : null}
</LobbyButton>
) : null}
<TimedDivider />
<LobbyButton index={5} icon="eye" onClick={() => act('observe')}>
Observe
</LobbyButton>
{round_start ? (
<LobbyButton
index={6}
selected={!!ready}
onClick={() => act('ready')}
icon={ready ? 'check' : 'xmark'}
>
{ready ? 'Unready' : 'Ready'}
</LobbyButton>
) : (
<LobbyButton index={6} onClick={() => act('late_join')} icon="users">
Join Game
</LobbyButton>
)}
</Stack>
</Section>
);
};
@@ -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<HTMLDivElement>(null);
const context = useContext(LobbyContext);
const { animationsDisabled, animationsFinished } = context;
useEffect(() => {
if (!animationsFinished && !animationsDisabled) {
setTimeout(() => {
ref.current!.style.display = 'block';
}, 1500);
}
}, [animationsFinished, animationsDisabled]);
return (
<Stack.Item>
<div
style={{
borderStyle: 'solid',
borderWidth: '1px',
display: animationsFinished || animationsDisabled ? 'block' : 'none',
}}
className="dividerEffect"
ref={ref}
/>
</Stack.Item>
);
};
export const LobbyButton = (props: LobbyButtonProps) => {
const { children, index, className, ...rest } = props;
const context = useContext<LobbyContextType>(LobbyContext);
return (
<Stack.Item
className="buttonEffect"
style={{
animationDelay:
context.animationsFinished || context.animationsDisabled
? '0s'
: `${1.5 + index * 0.2}s`,
}}
>
<CustomButton fluid className={'distinctButton ' + className} {...rest}>
{children}
</CustomButton>
</Stack.Item>
);
};
export const CustomButton = (props) => {
const { act } = useBackend();
return (
// this works because of event propagation
<Box onClick={() => act('keyboard')}>
<Button {...props} />
</Box>
);
};
@@ -0,0 +1,8 @@
import { createContext } from 'react';
import type { LobbyContextType } from './types';
export const LobbyContext = createContext<LobbyContextType>({
animationsDisabled: false,
animationsFinished: false,
});
@@ -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<LobbyData>();
const onLoadPlayer = useRef<HTMLAudioElement>(null);
const [modal, setModal] = useState<ReactNode | false>(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<boolean>(false);
return (
<Window fitted scrollbars={false} theme="ntos">
{!audioDisabled && (
<audio src={resolveAsset('load.ogg')} ref={onLoadPlayer} />
)}
<Window.Content
className={classes([
'LobbyScreen',
animationsDisabled ? null : 'animationsEnabled',
filterDisabled ? null : 'filterEnabled',
])}
fitted
>
<LobbyContext.Provider
value={{
animationsDisabled: animationsDisabled,
animationsFinished: animationsFinished,
setModal: setModal,
}}
>
{!!modal && <Modal>{modal}</Modal>}
<Box
height="100%"
width="100%"
style={{
backgroundImage: data.display_loading
? `url(${resolveAsset('lobby_loading.gif')})`
: `url(${resolveAsset('lobby_bg.gif')})`,
}}
className="bgLoad bgBackground"
/>
<Box height="100%" width="100%" position="absolute" className="crt" />
<Box position="absolute" top="10px" right="10px">
<CustomButton
icon="cog"
onClick={() => {
setModal(
<Section
p={5}
title="Lobby Settings"
buttons={
<CustomButton
icon="xmark"
onClick={() => setModal(false)}
/>
}
>
<Stack>
<Stack.Item>
<CustomButton
icon="tv"
onClick={() => {
storage.set(
'lobby-filter-disabled',
!filterDisabled,
);
setFilterDisabled(!filterDisabled);
setModal(false);
}}
tooltip="Removes the CRT filter background"
>
{`${filterDisabled ? 'Enable' : 'Disable'} Scanlines`}
</CustomButton>
</Stack.Item>
<Stack.Item>
<CustomButton
icon="volume-xmark"
onClick={() => {
storage.set('lobby-audio-disabled', !audioDisabled);
setAudioDisabled(!audioDisabled);
setModal(false);
}}
tooltip="Removes the loading audio"
>
{`${audioDisabled ? 'Enable' : 'Disable'} Audio`}
</CustomButton>
</Stack.Item>
<Stack.Item>
<CustomButton
icon="bolt"
onClick={() => {
storage.set(
'lobby-animations-disabled',
!animationsDisabled,
);
setAnimationsDisabled(!animationsDisabled);
setModal(false);
}}
tooltip="Disables animations."
>
{`${animationsDisabled ? 'Enable' : 'Disable'} Animations`}
</CustomButton>
</Stack.Item>
</Stack>
</Section>,
);
}}
/>
</Box>
{hidden && (
<Box position="absolute" top="10px" left="10px">
<CustomButton icon={'check'} onClick={() => setHidden(false)} />
</Box>
)}
<Stack vertical height="100%" justify="space-around" align="center">
<Stack.Item>
<LobbyButtons
setModal={setModal}
hidden={hidden}
setHidden={setHidden}
/>
</Stack.Item>
</Stack>
</LobbyContext.Provider>
</Window.Content>
</Window>
);
};
@@ -0,0 +1,31 @@
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;
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<typeof Box> & {
readonly index: number;
readonly selected?: boolean;
readonly disabled?: boolean;
readonly icon?: string;
readonly tooltip?: string;
};
@@ -363,6 +363,7 @@ const GeneralMobSettings = (props: {
Description:
<br />
<TextArea
fluid
height={'18rem'}
expensive
onChange={(val: string) => props.onDesc(val)}
@@ -373,6 +374,7 @@ const GeneralMobSettings = (props: {
Flavor Text:
<br />
<TextArea
fluid
height={'18rem'}
expensive
value={data.flavor_text}
+31 -16
View File
@@ -39,6 +39,8 @@ type Props = Partial<{
theme: string;
title: string;
width: number;
fitted: boolean;
scrollbars: boolean;
}> &
PropsWithChildren;
@@ -51,6 +53,8 @@ export const Window = (props: Props) => {
buttons,
width,
height,
fitted,
scrollbars = true,
} = props;
const { config, suspended } = useBackend();
@@ -82,7 +86,9 @@ export const Window = (props: Props) => {
if (config.window?.key) {
setWindowKey(config.window.key);
}
recallWindowGeometry(options);
if (!fitted) {
recallWindowGeometry(options);
}
Byond.winset(Byond.windowId, {
'is-visible': true,
});
@@ -93,6 +99,7 @@ export const Window = (props: Props) => {
'can-close': Boolean(canClose),
});
logger.log('mounting');
updateGeometry();
return () => {
@@ -113,25 +120,33 @@ export const Window = (props: Props) => {
return suspended ? null : (
<Layout className="Window" theme={theme}>
<TitleBar
className="Window__titleBar"
title={title || decodeHtmlEntities(config.title)}
status={config.status}
fancy={fancy}
onDragStart={dragStartHandler}
onClose={() => {
logger.log('pressed close');
dispatch(backendSuspendStart());
}}
canClose={canClose}
{!fitted && (
<TitleBar
className="Window__titleBar"
title={title || decodeHtmlEntities(config.title)}
status={config.status}
fancy={fancy}
onDragStart={dragStartHandler}
onClose={() => {
logger.log('pressed close');
dispatch(backendSuspendStart());
}}
canClose={canClose}
>
{buttons}
</TitleBar>
)}
<div
className={classes([
'Window__rest',
!fitted && 'Window__restwithTitlebar',
debugLayout && 'debug-layout',
])}
>
{buttons}
</TitleBar>
<div className={classes(['Window__rest', debugLayout && 'debug-layout'])}>
{!suspended && children}
{showDimmer && <div className="Window__dimmer" />}
</div>
{fancy && (
{fancy && scrollbars && (
<>
<div
className="Window__resizeHandle__e"
@@ -0,0 +1,167 @@
.LobbyScreen {
&.animationsEnabled {
.typeEffect {
overflow: hidden;
white-space: nowrap;
width: 0;
animation: typing 1s steps(20, end) forwards;
animation-delay: 1.3s;
}
.buttonEffect {
overflow: hidden;
white-space: nowrap;
width: 0;
animation: typing 1s steps(20, end) forwards;
}
.loadEffect {
height: 0;
animation: loading 1s steps(20, end) forwards;
animation-delay: 1.3s;
}
.sectionLoad {
animation: section-loading 1s forwards;
box-shadow: 0 0 15px;
background-color: #202020;
}
.dividerEffect {
animation: typing 1s steps(20, end) forwards;
width: 0;
}
.bgLoad {
opacity: 0;
animation: bg-load 10s forwards;
animation-delay: 5s;
}
@keyframes bg-load {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes typing {
from {
width: 0;
}
to {
width: 100%;
}
}
@keyframes loading {
from {
height: 0;
}
to {
height: 100%;
}
}
@keyframes section-loading {
from {
transform: scaleY(0);
}
to {
transform: scaleY(1);
}
}
}
&.filterEnabled {
&::before {
content: ' ';
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background:
linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%),
linear-gradient(
90deg,
rgba(255, 0, 0, 0.06),
rgba(0, 255, 0, 0.02),
rgba(0, 0, 255, 0.06)
);
z-index: 2;
background-size:
100% 8px,
6px 100%;
pointer-events: none;
}
&::after {
content: ' ';
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgba(18, 16, 16, 0.1);
opacity: 0;
z-index: 2;
pointer-events: none;
}
@supports (not (-webkit-hyphens: none)) and (not (-moz-appearance: none)) {
box-shadow: 0 0 500px 25px rgba(0, 0, 0, 0.9) inset;
}
}
}
.LobbyScreen {
font-size: large;
font-family: monospace;
font-weight: 700;
background-position: center;
background-size: cover;
background-image: none !important;
.bgBackground {
background-position: center;
background-size: cover;
position: absolute;
}
.Button__content {
.Icon {
vertical-align: middle;
margin-bottom: 3px;
}
}
.NanotrasenIcon {
background-color: #8dc0ff;
mask-image: url('../assets/bg-nanotrasen.svg');
mask-repeat: no-repeat;
mask-position: center;
}
.loadEffect {
height: 67px;
}
.sectionLoad {
border: 3px solid #384e68;
background-color: #202020;
}
.dividerEffect {
border-color: #384e68;
}
}
.noAnimation {
animation-delay: 0s !important;
}
@@ -18,7 +18,7 @@
}
// Everything after the title bar
.Window__rest {
.Window__rest.Window__restwithTitlebar {
position: fixed;
top: 32px;
top: base.rem(32px);
+1
View File
@@ -37,6 +37,7 @@
@include meta.load-css('./interfaces/CrewManifest.scss');
@include meta.load-css('./interfaces/ExperimentConfigure.scss');
@include meta.load-css('./interfaces/FishingMinigame.scss');
@include meta.load-css('./interfaces/LobbyMenu.scss');
@include meta.load-css('./interfaces/Microwave.scss');
@include meta.load-css('./interfaces/NuclearBomb.scss');
@include meta.load-css('./interfaces/Paper.scss');
+2
View File
@@ -2032,6 +2032,7 @@
#include "code\modules\asset_cache\assets\fontawesome.dm"
#include "code\modules\asset_cache\assets\icon_ref_map.dm"
#include "code\modules\asset_cache\assets\jquery.dm"
#include "code\modules\asset_cache\assets\lobby.dm"
#include "code\modules\asset_cache\assets\microwave.dm"
#include "code\modules\asset_cache\assets\ntos.dm"
#include "code\modules\asset_cache\assets\permissions.dm"
@@ -3564,6 +3565,7 @@
#include "code\modules\mob\living\simple_mob\subtypes\vore\shadekin\types.dm"
#include "code\modules\mob\living\voice\voice.dm"
#include "code\modules\mob\living\voice\voice_vr.dm"
#include "code\modules\mob\new_player\lobby_browser.dm"
#include "code\modules\mob\new_player\login.dm"
#include "code\modules\mob\new_player\logout.dm"
#include "code\modules\mob\new_player\new_player.dm"