mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-07-18 02:25:06 +01:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
..()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
@@ -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.
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user