mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-26 17:06:32 +01:00
reworks game preferences (characters v2 staging) (#6310)
Co-authored-by: silicons <no@you.cat> Co-authored-by: LordME <58342752+TheLordME@users.noreply.github.com>
This commit is contained in:
co-authored by
silicons
LordME
parent
9f1b911556
commit
39bc14bb4a
@@ -96,6 +96,10 @@
|
||||
/// menu group statuses
|
||||
var/list/menu_group_status = list()
|
||||
|
||||
//? Preferences
|
||||
/// client preferences
|
||||
var/datum/game_preferences/preferences
|
||||
|
||||
//? Statpanel
|
||||
/// statpanel tab ; can be null (e.g. we're looking at verb tabs)
|
||||
var/statpanel_tab
|
||||
|
||||
@@ -198,9 +198,21 @@
|
||||
// resolve persistent data
|
||||
persistent = resolve_client_data(ckey, key)
|
||||
//* Resolve database data
|
||||
player = new(key)
|
||||
player = resolve_player_data(ckey, key)
|
||||
player.log_connect()
|
||||
// todo: move preferences up here but above persistent
|
||||
//* Resolve preferences
|
||||
preferences = SSpreferences.resolve_game_preferences(key, ckey)
|
||||
preferences.active = src
|
||||
preferences.on_reconnect()
|
||||
//? WARNING: SHITCODE ALERT ?//
|
||||
// We allow a client/New sleep because preferences is currently required for
|
||||
// everything else to work
|
||||
// todo: maybe don't do this?
|
||||
if(!UNLINT(preferences.block_on_initialized(5 SECONDS)))
|
||||
security_kick("A fatal error occurred while attempting to load: preferences not initialized. Please notify a coder.")
|
||||
stack_trace("we just kicked a client due to prefs not loading; something is horribly wrong!")
|
||||
return
|
||||
//? END ?//
|
||||
|
||||
//* Setup user interface
|
||||
// todo: move top level menu here, for now it has to be under prefs.
|
||||
@@ -328,11 +340,12 @@
|
||||
pre_init_viewport()
|
||||
mob.reload_rendering()
|
||||
|
||||
if(prefs.lastchangelog != GLOB.changelog_hash) //bolds the changelog button on the interface so we know there are updates.
|
||||
to_chat(src, "<span class='info'>You have unread updates in the changelog.</span>")
|
||||
winset(src, "infowindow.changelog", "background-color=#eaeaea;font-style=bold")
|
||||
if(config_legacy.aggressive_changelog)
|
||||
changelog_async()
|
||||
// todo: this is dead because changelog is dead but we should fix it tbvqh
|
||||
// if(prefs.lastchangelog != GLOB.changelog_hash) //bolds the changelog button on the interface so we know there are updates.
|
||||
// to_chat(src, "<span class='info'>You have unread updates in the changelog.</span>")
|
||||
// winset(src, "infowindow.changelog", "background-color=#eaeaea;font-style=bold")
|
||||
// if(config_legacy.aggressive_changelog)
|
||||
// changelog_async()
|
||||
|
||||
// ensure asset cache is there
|
||||
INVOKE_ASYNC(src, PROC_REF(warn_if_no_asset_cache_browser))
|
||||
@@ -383,9 +396,11 @@
|
||||
// log
|
||||
log_access("Logout: [key_name(src)]")
|
||||
// unreference storage datums
|
||||
prefs = null
|
||||
persistent = null
|
||||
player = null
|
||||
if(preferences)
|
||||
preferences.active = null
|
||||
preferences = null
|
||||
|
||||
//* unsorted
|
||||
GLOB.ahelp_tickets.ClientLogout(src)
|
||||
@@ -512,7 +527,7 @@
|
||||
if(ab) //Citadel edit, things with stuff.
|
||||
return
|
||||
|
||||
if (prefs.hotkeys)
|
||||
if (preferences.is_hotkeys_mode())
|
||||
// If hotkey mode is enabled, then clicking the map will automatically
|
||||
// unfocus the text bar. This removes the red color from the text bar
|
||||
// so that the visual focus indicator matches reality.
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2023 Citadel Station developers. *//
|
||||
|
||||
GLOBAL_LIST_EMPTY(client_data)
|
||||
|
||||
/proc/resolve_client_data(ckey, key)
|
||||
@@ -65,7 +68,7 @@ GLOBAL_LIST_EMPTY(client_data)
|
||||
log_shadowban("[ckey] autobanned based on [why].")
|
||||
message_admins(SPAN_DANGER("Automatically shadowbanning [ckey] based on configuration (matched on [why]). Varedit client.persistent.ligma to change this."))
|
||||
|
||||
/datum/client_data/proc/block_on_account_age_loaded(timeout = INFINITY)
|
||||
/datum/client_data/proc/block_on_account_age_loaded(timeout = 10 SECONDS)
|
||||
var/timed_out = world.time + timeout
|
||||
UNTIL(!isnull(account_age) || world.time > timed_out)
|
||||
return account_age
|
||||
@@ -143,7 +146,8 @@ GLOBAL_LIST_EMPTY(client_data)
|
||||
playtime_loaded = TRUE
|
||||
playtime_mutex = FALSE
|
||||
|
||||
/datum/client_data/proc/block_on_playtime_loaded(timeout = INFINITY)
|
||||
/datum/client_data/proc/block_on_playtime_loaded(timeout = 10 SECONDS)
|
||||
var/timed_out = world.time + timeout
|
||||
load_playtime()
|
||||
UNTIL(playtime_loaded || world.time > timed_out)
|
||||
return playtime_loaded
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
// todo: this has to be globally cached for new admin panel
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2023 Citadel Station developers. *//
|
||||
|
||||
GLOBAL_LIST_EMPTY(player_data)
|
||||
|
||||
/proc/resolve_player_data(ckey, key)
|
||||
ckey = ckey(ckey) // just in case
|
||||
if(!islist(GLOB.player_data))
|
||||
// we CANNOT RUNTIME
|
||||
GLOB.player_data = list()
|
||||
if(!istype(GLOB.player_data[ckey], /datum/player_data))
|
||||
GLOB.player_data[ckey] = new /datum/player_data(ckey, key)
|
||||
return GLOB.player_data[ckey]
|
||||
|
||||
/**
|
||||
* holds db-related data
|
||||
@@ -31,9 +43,12 @@
|
||||
var/player_age
|
||||
/// join date
|
||||
var/player_first_seen
|
||||
/// arbitrary JSON-serializable key-value data
|
||||
/// you must handle errors yourself! we won't handle it for you.
|
||||
var/list/player_misc
|
||||
|
||||
/datum/player_data/New(key)
|
||||
src.ckey = ckey(key)
|
||||
/datum/player_data/New(ckey, key)
|
||||
src.ckey = ckey
|
||||
if(!src.ckey)
|
||||
return
|
||||
src.key = key
|
||||
@@ -94,7 +109,7 @@
|
||||
lookup_pid = text2num(lookup_pid)
|
||||
qdel(lookup)
|
||||
lookup = SSdbcore.ExecuteQuery(
|
||||
"SELECT id, flags, datediff(Now(), firstseen), firstseen FROM [format_table_name("player")] WHERE id = :id",
|
||||
"SELECT id, flags, datediff(Now(), firstseen), firstseen, misc FROM [format_table_name("player")] WHERE id = :id",
|
||||
list(
|
||||
"id" = lookup_pid
|
||||
)
|
||||
@@ -111,6 +126,7 @@
|
||||
player_flags = lookup_flags
|
||||
player_first_seen = lookup.item[4]
|
||||
player_age = lookup_age
|
||||
player_misc = safe_json_decode(lookup.item[5])
|
||||
qdel(lookup)
|
||||
available = TRUE
|
||||
else
|
||||
@@ -125,21 +141,24 @@
|
||||
// new person!
|
||||
player_age = 0
|
||||
player_flags = NONE
|
||||
player_misc = list()
|
||||
var/datum/db_query/insert
|
||||
if(migrate_firstseen)
|
||||
insert = SSdbcore.ExecuteQuery(
|
||||
"INSERT INTO [format_table_name("player")] (flags, firstseen, lastseen) VALUES (:flags, :fs, Now())",
|
||||
"INSERT INTO [format_table_name("player")] (flags, firstseen, lastseen, misc) VALUES (:flags, :fs, Now(), :misc)",
|
||||
list(
|
||||
"flags" = player_flags,
|
||||
"fs" = migrate_firstseen
|
||||
"fs" = migrate_firstseen,
|
||||
"misc" = safe_json_encode(player_misc),
|
||||
)
|
||||
)
|
||||
player_first_seen = migrate_firstseen
|
||||
else
|
||||
insert = SSdbcore.ExecuteQuery(
|
||||
"INSERT INTO [format_table_name("player")] (flags, firstseen, lastseen) VALUES (:flags, Now(), Now())",
|
||||
"INSERT INTO [format_table_name("player")] (flags, firstseen, lastseen, misc) VALUES (:flags, Now(), Now(), :misc)",
|
||||
list(
|
||||
"flags" = player_flags,
|
||||
"misc" = safe_json_encode(player_misc),
|
||||
)
|
||||
)
|
||||
player_first_seen = time_stamp()
|
||||
@@ -190,10 +209,11 @@
|
||||
|
||||
/datum/player_data/proc/_save()
|
||||
qdel(SSdbcore.ExecuteQuery(
|
||||
"UPDATE [format_table_name("player")] SET flags = :flags WHERE id = :id",
|
||||
"UPDATE [format_table_name("player")] SET flags = :flags, misc = :misc WHERE id = :id",
|
||||
list(
|
||||
"flags" = player_flags,
|
||||
"id" = player_id
|
||||
"id" = player_id,
|
||||
"misc" = safe_json_encode(player_misc, "{}"),
|
||||
)
|
||||
))
|
||||
|
||||
@@ -211,7 +231,7 @@
|
||||
qdel(SSdbcore.ExecuteQuery(
|
||||
"UPDATE [format_table_name("player")] SET lastseen = Now() WHERE id = :id",
|
||||
list(
|
||||
"id" = player_id
|
||||
"id" = player_id,
|
||||
)
|
||||
))
|
||||
return TRUE
|
||||
@@ -229,7 +249,7 @@
|
||||
*
|
||||
* WARNING: without database, or if this is for a guest key, we will never be available.
|
||||
*/
|
||||
/datum/player_data/proc/block_on_available(timeout = INFINITY)
|
||||
/datum/player_data/proc/block_on_available(timeout = 10 SECONDS)
|
||||
var/timed_out = world.time + timeout
|
||||
UNTIL(!isnull(available) || world.time > timed_out)
|
||||
return available
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 silicons *//
|
||||
|
||||
/datum/game_preference_entry/dropdown/hud_style
|
||||
name = "HUD Style"
|
||||
description = "Set the style of your inventory / game HUD"
|
||||
key = "hud_style"
|
||||
category = GAME_PREFERENCE_CATEGORY_GAME
|
||||
subcategory = "HUD Overlay"
|
||||
legacy_savefile_key = "UI_style"
|
||||
|
||||
/datum/game_preference_entry/dropdown/hud_style/New()
|
||||
options = list()
|
||||
for(var/key in all_ui_styles)
|
||||
options += key
|
||||
..()
|
||||
|
||||
/datum/game_preference_entry/dropdown/hud_style/on_set(client/user, value, first_init)
|
||||
. = ..()
|
||||
user.set_ui_style(value)
|
||||
|
||||
/datum/game_preference_entry/simple_color/hud_color
|
||||
name = "HUD Color"
|
||||
description = "Manually recolor your inventory / game HUD"
|
||||
key = "hud_color"
|
||||
category = GAME_PREFERENCE_CATEGORY_GAME
|
||||
subcategory = "HUD Overlay"
|
||||
default_value = "#ffffff"
|
||||
legacy_savefile_key = "UI_style_color"
|
||||
|
||||
/datum/game_preference_entry/simple_color/hud_color/on_set(client/user, value, first_init)
|
||||
. = ..()
|
||||
user.set_ui_color(value)
|
||||
|
||||
/datum/game_preference_entry/number/hud_alpha
|
||||
name = "HUD Alpha"
|
||||
description = "Manually set the transparency of your inventory / game HUD"
|
||||
key = "hud_alpha"
|
||||
category = GAME_PREFERENCE_CATEGORY_GAME
|
||||
subcategory = "HUD Overlay"
|
||||
default_value = 255
|
||||
min_value = 0
|
||||
max_value = 255
|
||||
round_to_nearest = 1
|
||||
legacy_savefile_key = "UI_style_alpha"
|
||||
|
||||
/datum/game_preference_entry/number/hud_alpha/on_set(client/user, value, first_init)
|
||||
. = ..()
|
||||
user.set_ui_alpha(value)
|
||||
|
||||
/datum/game_preference_entry/dropdown/tooltip_style
|
||||
name = "Tooltips Style"
|
||||
description = "Set the HUD style of pop up tooltips."
|
||||
key = "tooltip_style"
|
||||
category = GAME_PREFERENCE_CATEGORY_GAME
|
||||
subcategory = "Tooltips"
|
||||
legacy_savefile_key = "tooltipstyle"
|
||||
|
||||
/datum/game_preference_entry/dropdown/tooltip_style/New()
|
||||
options = all_tooltip_styles.Copy()
|
||||
..()
|
||||
|
||||
/datum/game_preference_entry/simple_color/admin_ooc_color
|
||||
name = "OOC Color (Admin)"
|
||||
description = "Choose your OOC color. Do not make it too eye-searing. Set to #000001 or reset to default to sync with server color."
|
||||
key = "admin_ooc_color"
|
||||
category = GAME_PREFERENCE_CATEGORY_GAME
|
||||
subcategory = "OOC"
|
||||
default_value = "#000001"
|
||||
legacy_savefile_key = "ooccolor"
|
||||
|
||||
/datum/game_preference_entry/simple_color/admin_ooc_color/is_visible(client/user)
|
||||
return check_rights(C = user, show_msg = FALSE) && CONFIG_GET(flag/allow_admin_ooccolor)
|
||||
@@ -0,0 +1,42 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 silicons *//
|
||||
|
||||
/datum/game_preference_entry/toggle/parallax
|
||||
name = "Parallax Enabled"
|
||||
description = "Render space via parallax. This should be kept on."
|
||||
key = "enable_parallax"
|
||||
category = GAME_PREFERENCE_CATEGORY_GRAPHICS
|
||||
subcategory = "Parallax"
|
||||
default_value = TRUE
|
||||
|
||||
/datum/game_preference_entry/toggle/parallax/on_set(client/user, value, first_init)
|
||||
. = ..()
|
||||
user.parallax_holder?.reset()
|
||||
|
||||
/datum/game_preference_entry/toggle/ambient_occlusion
|
||||
name = "Fake Ambient Occlusion"
|
||||
description = "Fake, filter ambient occlusion. This should be kept off."
|
||||
key = "fake_ambient_occlusion"
|
||||
category = GAME_PREFERENCE_CATEGORY_GRAPHICS
|
||||
subcategory = "Rendering"
|
||||
default_value = FALSE
|
||||
|
||||
/datum/game_preference_entry/toggle/ambient_occlusion/on_set(client/user, value, first_init)
|
||||
. = ..()
|
||||
user.using_perspective?.planes?.sync_owner(user)
|
||||
|
||||
/datum/game_preference_entry/number/fps
|
||||
name = "FPS"
|
||||
description = "Client rendering FPS. It is recommended to set this to a multiple of the server's tickrate. 0 to sync with server."
|
||||
key = "client_fps"
|
||||
category = GAME_PREFERENCE_CATEGORY_GRAPHICS
|
||||
subcategory = "Rendering"
|
||||
default_value = 0
|
||||
min_value = 0
|
||||
max_value = 180
|
||||
round_to_nearest = 1
|
||||
legacy_savefile_key = "client_fps"
|
||||
|
||||
/datum/game_preference_entry/number/fps/on_set(client/user, value, first_init)
|
||||
. = ..()
|
||||
user.fps = value
|
||||
@@ -0,0 +1,149 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 silicons *//
|
||||
|
||||
/datum/game_preference_entry
|
||||
abstract_type = /datum/game_preference_entry
|
||||
var/name = "-- broken entry --"
|
||||
var/description = "A preference entry."
|
||||
/// Must be unique
|
||||
var/key
|
||||
var/category = "Misc"
|
||||
var/subcategory = "Misc"
|
||||
/// priority - higher means it appears first. only valid within the same category.
|
||||
var/priority = 0
|
||||
/// default value
|
||||
var/default_value
|
||||
/// legacy import id - set if it's using new global prefs system
|
||||
var/legacy_global_key
|
||||
/// legacy import id - set if it's using old savefile direct write
|
||||
var/legacy_savefile_key
|
||||
|
||||
/datum/game_preference_entry/proc/default_value(client/user)
|
||||
return default_value
|
||||
|
||||
/datum/game_preference_entry/proc/is_visible(client/user, silent)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* called when a value is changed with a client active
|
||||
*
|
||||
* @params
|
||||
* * user - active client
|
||||
* * value - current (sanitized) value
|
||||
* * first_init - are we being called as a client is first being associated to a preferences datum, or on first load?
|
||||
*/
|
||||
/datum/game_preference_entry/proc/on_set(client/user, value, first_init)
|
||||
return
|
||||
|
||||
/datum/game_preference_entry/proc/filter_value(value)
|
||||
return value
|
||||
|
||||
/datum/game_preference_entry/proc/migrate_legacy_data(data)
|
||||
return data
|
||||
|
||||
/datum/game_preference_entry/proc/tgui_preference_schema()
|
||||
return list(
|
||||
"key" = key,
|
||||
"category" = category,
|
||||
"subcategory" = subcategory,
|
||||
"name" = name,
|
||||
"desc" = description,
|
||||
"priority" = priority,
|
||||
"defaultValue" = default_value,
|
||||
)
|
||||
|
||||
/datum/game_preference_entry/number
|
||||
abstract_type = /datum/game_preference_entry/number
|
||||
default_value = 0
|
||||
/// optional
|
||||
var/min_value
|
||||
/// optional
|
||||
var/max_value
|
||||
/// optional
|
||||
var/round_to_nearest
|
||||
|
||||
/datum/game_preference_entry/number/filter_value(value)
|
||||
. = isnum(value)? clamp(value, min_value, max_value) : default_value
|
||||
if(!isnull(.) && round_to_nearest)
|
||||
. = round(., round_to_nearest)
|
||||
|
||||
/datum/game_preference_entry/number/tgui_preference_schema()
|
||||
return ..() | list(
|
||||
"type" = "number",
|
||||
"minValue" = min_value,
|
||||
"maxValue" = max_value,
|
||||
"round_to" = round_to_nearest,
|
||||
)
|
||||
|
||||
/datum/game_preference_entry/string
|
||||
abstract_type = /datum/game_preference_entry/string
|
||||
default_value = ""
|
||||
/// mandatory
|
||||
var/min_length = 0
|
||||
/// mandatory
|
||||
var/max_length = 64
|
||||
|
||||
/datum/game_preference_entry/string/filter_value(value)
|
||||
. = "[value]"
|
||||
return copytext_char(., 1, min(length_char(.) + 1, max_length + 1))
|
||||
|
||||
/datum/game_preference_entry/string/tgui_preference_schema()
|
||||
return ..() | list(
|
||||
"type" = "string",
|
||||
"minLength" = min_length,
|
||||
"maxLength" = max_length,
|
||||
)
|
||||
|
||||
/datum/game_preference_entry/toggle
|
||||
abstract_type = /datum/game_preference_entry/toggle
|
||||
default_value = TRUE
|
||||
/// mandatory
|
||||
var/enabled_name = "On"
|
||||
/// mandatory
|
||||
var/disabled_name = "Off"
|
||||
|
||||
/datum/game_preference_entry/toggle/filter_value(value)
|
||||
return !!value
|
||||
|
||||
/datum/game_preference_entry/toggle/tgui_preference_schema()
|
||||
return ..() | list(
|
||||
"type" = "toggle",
|
||||
"enabledName" = enabled_name,
|
||||
"disabledName" = disabled_name,
|
||||
)
|
||||
|
||||
/datum/game_preference_entry/dropdown
|
||||
abstract_type = /datum/game_preference_entry/dropdown
|
||||
default_value = null
|
||||
/// entries must be strings
|
||||
var/list/options = list()
|
||||
|
||||
/datum/game_preference_entry/dropdown/New()
|
||||
if(isnull(default_value) && length(options))
|
||||
default_value = options[1]
|
||||
|
||||
/datum/game_preference_entry/dropdown/filter_value(value)
|
||||
return (value in options)? value : ((length(options) && options[1]) || null)
|
||||
|
||||
/datum/game_preference_entry/dropdown/tgui_preference_schema()
|
||||
return ..() | list(
|
||||
"type" = "dropdown",
|
||||
"options" = options,
|
||||
)
|
||||
|
||||
/datum/game_preference_entry/simple_color
|
||||
abstract_type = /datum/game_preference_entry/simple_color
|
||||
default_value = "#ffffff"
|
||||
|
||||
/datum/game_preference_entry/simple_color/filter_value(value)
|
||||
return sanitize_hexcolor(
|
||||
value,
|
||||
desired_format = 6,
|
||||
include_crunch = TRUE,
|
||||
default = default_value,
|
||||
)
|
||||
|
||||
/datum/game_preference_entry/simple_color/tgui_preference_schema()
|
||||
return ..() | list(
|
||||
"type" = "simpleColor",
|
||||
)
|
||||
@@ -0,0 +1,47 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 silicons *//
|
||||
|
||||
/// key value id = middleware instance
|
||||
GLOBAL_LIST_INIT(game_preference_middleware, init_game_preference_middleware())
|
||||
|
||||
/proc/init_game_preference_middleware()
|
||||
var/list/keyed_middleware = list()
|
||||
for(var/datum/game_preference_middleware/middleware as anything in subtypesof(/datum/game_preference_middleware))
|
||||
middleware = new middleware
|
||||
keyed_middleware[middleware.key] = middleware
|
||||
GLOB.game_preference_middleware = keyed_middleware
|
||||
return keyed_middleware
|
||||
|
||||
/datum/game_preference_middleware
|
||||
/// category name
|
||||
var/name
|
||||
/// key
|
||||
var/key
|
||||
|
||||
/**
|
||||
* return TRUE to stop handling
|
||||
*
|
||||
* we assume the 'usr' has been validated by the prefs datum!
|
||||
*/
|
||||
/datum/game_preference_middleware/proc/handle_topic(datum/game_preferences/prefs, action, list/params)
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* when we want to reset whatever this handles
|
||||
*/
|
||||
/datum/game_preference_middleware/proc/handle_reset(datum/game_preferences/prefs)
|
||||
prefs.mark_dirty()
|
||||
return
|
||||
|
||||
/**
|
||||
* sanitize everything
|
||||
*/
|
||||
/datum/game_preference_middleware/proc/handle_sanitize(datum/game_preferences/prefs)
|
||||
prefs.mark_dirty()
|
||||
return
|
||||
|
||||
/**
|
||||
* on initial load
|
||||
*/
|
||||
/datum/game_preference_middleware/proc/on_initial_load(datum/game_preferences/prefs)
|
||||
return
|
||||
@@ -0,0 +1,270 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 silicons *//
|
||||
|
||||
/datum/game_preference_toggle
|
||||
abstract_type = /datum/game_preference_toggle
|
||||
var/name = "A Toggle"
|
||||
var/description = "Someone fucked up"
|
||||
var/enabled_name = "On"
|
||||
var/disabled_name = "Off"
|
||||
/// must be unique
|
||||
var/key
|
||||
var/category = "Misc"
|
||||
/// priority - higher means it appears first. only valid within the same category.
|
||||
var/priority = 0
|
||||
/// legacy import id
|
||||
var/legacy_key
|
||||
var/default_value = TRUE
|
||||
|
||||
/datum/game_preference_toggle/proc/toggled(client/user, state)
|
||||
return
|
||||
|
||||
/datum/game_preference_toggle/proc/is_visible(client/user)
|
||||
return TRUE
|
||||
|
||||
/datum/game_preference_toggle/proc/tgui_preference_schema()
|
||||
return list(
|
||||
"key" = key,
|
||||
"category" = category,
|
||||
"priority" = priority,
|
||||
"default" = default_value,
|
||||
"name" = name,
|
||||
"desc" = description,
|
||||
"enabled" = enabled_name,
|
||||
"disabled" = disabled_name,
|
||||
)
|
||||
|
||||
/datum/game_preference_toggle/admin
|
||||
abstract_type = /datum/game_preference_toggle/admin
|
||||
category = "Admin"
|
||||
|
||||
/datum/game_preference_toggle/admin/is_visible(client/user, silent)
|
||||
return check_rights(C = user, show_msg = FALSE)
|
||||
|
||||
/datum/game_preference_toggle/admin/global_looc
|
||||
name = "Remote LOOC Sight"
|
||||
description = "See all LOOC, regardless of location."
|
||||
key = "admin_global_looc"
|
||||
default_value = TRUE
|
||||
legacy_key = "CHAT_RLOOC"
|
||||
|
||||
/datum/game_preference_toggle/admin/obfuscate_stealth_dsay
|
||||
name = "Obfuscate Stealthmin Dsay"
|
||||
description = "Randomize ckey to a list of joke ckeys when dsay'ing while stealthed."
|
||||
key = "admin_obfuscate_stealth_dsay"
|
||||
default_value = TRUE
|
||||
legacy_key = "OBFUSCATE_STEALTH_DSAY"
|
||||
|
||||
/datum/game_preference_toggle/admin/stealth_hides_ghost
|
||||
name = "Stealthmin Hides Ghost"
|
||||
description = "Hide your ghost while stealthed."
|
||||
key = "admin_hide_stealth_ghost"
|
||||
default_value = TRUE
|
||||
|
||||
/datum/game_preference_toggle/chat
|
||||
abstract_type = /datum/game_preference_toggle/chat
|
||||
category = "Chat"
|
||||
|
||||
/datum/game_preference_toggle/chat/ooc
|
||||
name = "OOC enabled"
|
||||
description = "Toggles whether you see OOC (Out of Character) chat."
|
||||
legacy_key = "CHAT_OOC"
|
||||
key = "toggle_ooc"
|
||||
|
||||
/datum/game_preference_toggle/chat/looc
|
||||
name = "LOOC enabled"
|
||||
description = "Toggles whether you see LOOC (Local Out of Character) chat."
|
||||
legacy_key = "CHAT_LOOC"
|
||||
key = "toggle_looc"
|
||||
|
||||
/datum/game_preference_toggle/chat/dsay
|
||||
name = "Deadchat enabled"
|
||||
description = "Toggles whether you see dead chat."
|
||||
legacy_key = "CHAT_DEAD"
|
||||
key = "toggle_deadchat"
|
||||
|
||||
/datum/game_preference_toggle/chat/legacy_chat_tags
|
||||
name = "Chat Tags"
|
||||
description = "Legacy chat visual system for tags to the left of messages; you should likely keep this on."
|
||||
key = "legacy_chat_tags"
|
||||
default_value = TRUE
|
||||
legacy_key = "CHAT_SHOWICONS"
|
||||
|
||||
/datum/game_preference_toggle/chat/language_indicators
|
||||
name = "Language Indicators"
|
||||
description = "Show language indicators in the chat when you hear a known language."
|
||||
key = "language_indicators"
|
||||
default_value = TRUE
|
||||
legacy_key = "LANGUAGE_INDICATOR"
|
||||
|
||||
/datum/game_preference_toggle/game
|
||||
abstract_type = /datum/game_preference_toggle/game
|
||||
category = "Game"
|
||||
|
||||
/datum/game_preference_toggle/game/mob_tooltips
|
||||
name = "Mob Tooltips"
|
||||
key = "mob_tooltips"
|
||||
legacy_key = "MOB_TOOLTIPS"
|
||||
|
||||
/datum/game_preference_toggle/game/overhead_chat
|
||||
name = "Overhead Chat"
|
||||
key = "runechat"
|
||||
legacy_key = "OVERHEAD_CHAT"
|
||||
description = "Enable rendering messages sent by mobs over their icon on the map."
|
||||
|
||||
/datum/game_preference_toggle/game/help_intent_firing
|
||||
name = "Help Intent Gun Safety"
|
||||
description = "If on, do not allow firing weapons in help intent."
|
||||
key = "help_intent_gun_safety"
|
||||
legacy_key = "HELP_INTENT_SAFETY"
|
||||
default_value = FALSE
|
||||
|
||||
/datum/game_preference_toggle/game/legacy_name_highlight
|
||||
name = "Emphasize Name Mentions"
|
||||
description = "Enlargen messages calling you by name."
|
||||
key = "legacy_name_mention"
|
||||
legacy_key = "CHAT_MENTION"
|
||||
|
||||
/datum/game_preference_toggle/ambience
|
||||
abstract_type = /datum/game_preference_toggle/ambience
|
||||
category = "Sound - Ambience"
|
||||
|
||||
/datum/game_preference_toggle/ambience/area_ambience
|
||||
name = "Area Ambience"
|
||||
key = "area_ambience"
|
||||
default_value = TRUE
|
||||
|
||||
/datum/game_preference_toggle/ambience/supermatter_hum
|
||||
name = "Supermatter Hum"
|
||||
key = "supermatter_ambience"
|
||||
legacy_key = "SOUND_SUPERMATTER"
|
||||
default_value = TRUE
|
||||
|
||||
/datum/game_preference_toggle/ambience/weather
|
||||
name = "Weather Sounds"
|
||||
key = "weather_ambience"
|
||||
legacy_key = "SOUND_WEATHER"
|
||||
default_value = TRUE
|
||||
|
||||
/datum/game_preference_toggle/ambience/atmospherics
|
||||
name = "Atmos Machinery Sounds"
|
||||
key = "atmos_machine_ambience"
|
||||
default_value = TRUE
|
||||
legacy_key = "SOUND_AIRPUMP"
|
||||
|
||||
/datum/game_preference_toggle/sfx
|
||||
abstract_type = /datum/game_preference_toggle/sfx
|
||||
category = "Sound - Effects"
|
||||
|
||||
/datum/game_preference_toggle/sfx/instruments
|
||||
name = "Instruments"
|
||||
description = "Play sounds from in-game instruments."
|
||||
key = "instrument_music"
|
||||
legacy_key = "SOUND_INSTRUMENT"
|
||||
|
||||
/datum/game_preference_toggle/music
|
||||
abstract_type = /datum/game_preference_toggle/music
|
||||
category = "Sound - Music"
|
||||
|
||||
/datum/game_preference_toggle/music/lobby
|
||||
name = "Lobby Music"
|
||||
description = "Play music in the lobby."
|
||||
key = "lobby_music"
|
||||
legacy_key = "SOUND_LOBBY"
|
||||
|
||||
/datum/game_preference_toggle/music/lobby/toggled(client/user, state)
|
||||
if(state)
|
||||
user.playtitlemusic()
|
||||
else
|
||||
user.media?.stop_music()
|
||||
|
||||
/datum/game_preference_toggle/music/admin
|
||||
name = "Admin Music"
|
||||
description = "Play admin MIDIs (or really just music nowadays)."
|
||||
key = "admin_music"
|
||||
legacy_key = "SOUND_MIDI"
|
||||
|
||||
/datum/game_preference_toggle/music/jukebox
|
||||
name = "Jukebox Music"
|
||||
description = "Play in-game jukebox music."
|
||||
key = "jukebox_music"
|
||||
legacy_key = "SOUND_JUKEBOX"
|
||||
|
||||
/datum/game_preference_toggle/music/jukebox/toggled(client/user, state)
|
||||
// todo: jukebox / music rework
|
||||
if(state)
|
||||
user.mob?.update_music()
|
||||
else
|
||||
user.mob?.stop_all_music()
|
||||
|
||||
// todo: this should probably not be in toggles
|
||||
|
||||
/datum/game_preference_toggle/vore_sounds
|
||||
abstract_type = /datum/game_preference_toggle/vore_sounds
|
||||
category = "Sound - Vore"
|
||||
|
||||
/datum/game_preference_toggle/vore_sounds/eating_noises
|
||||
name = "Eating Noises"
|
||||
key = "vore_eating_sounds"
|
||||
default_value = FALSE
|
||||
legacy_key = "EATING_NOISES"
|
||||
|
||||
/datum/game_preference_toggle/vore_sounds/digestion_noises
|
||||
name = "Digestion Noises"
|
||||
key = "vore_digestion_sounds"
|
||||
default_value = FALSE
|
||||
legacy_key = "DIGEST_NOISES"
|
||||
|
||||
/datum/game_preference_toggle/observer
|
||||
abstract_type = /datum/game_preference_toggle/observer
|
||||
category = "Observer"
|
||||
|
||||
/datum/game_preference_toggle/observer/ghost_ears
|
||||
name = "Hear All Speech"
|
||||
description = "Hear all speech, regardless of distance"
|
||||
key = "ghost_ears"
|
||||
default_value = TRUE
|
||||
legacy_key = "CHAT_GHOSTEARS"
|
||||
|
||||
/datum/game_preference_toggle/observer/ghost_sight
|
||||
name = "See All Emotes"
|
||||
description = "See all emotes, regardless of distance"
|
||||
key = "ghost_sight"
|
||||
default_value = TRUE
|
||||
legacy_key = "CHAT_GHOSTSIGHT"
|
||||
|
||||
/datum/game_preference_toggle/observer/ghost_radio
|
||||
name = "Hear All Radio"
|
||||
description = "Hear all radio chatter, regardless of distance"
|
||||
key = "ghost_radio"
|
||||
default_value = TRUE
|
||||
legacy_key = "CHAT_GHOSTRADIO"
|
||||
|
||||
/datum/game_preference_toggle/observer/ghost_subtle
|
||||
name = "See Subtle Emotes"
|
||||
description = "See subtle emotes"
|
||||
key = "ghost_subtle"
|
||||
default_value = TRUE
|
||||
legacy_key = "SUBTLE_SEE"
|
||||
|
||||
/datum/game_preference_toggle/presence
|
||||
abstract_type = /datum/game_preference_toggle/presence
|
||||
category = "Presence"
|
||||
|
||||
/datum/game_preference_toggle/presence/anonymous_ghost_chat
|
||||
name = "Anonymous Ghost Chat"
|
||||
description = "Hide your ckey when speaking in deadchat."
|
||||
key = "hide_ckey_from_deadchat"
|
||||
default_value = TRUE
|
||||
|
||||
/datum/game_preference_toggle/presence/show_advanced_who
|
||||
name = "Show Status in Advanced Who"
|
||||
description = "Show your playing status in advanced who."
|
||||
key = "advanced_who_status"
|
||||
default_value = FALSE
|
||||
|
||||
/datum/game_preference_toggle/presence/announce_ghost_joinleave
|
||||
name = "Announce Observer Join/Leave"
|
||||
description = "Announce to deadchat when you enter or exit deadchat."
|
||||
key = "announce_ghost_joinleave"
|
||||
default_value = FALSE
|
||||
@@ -0,0 +1,609 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 silicons *//
|
||||
|
||||
/**
|
||||
* Game preferences
|
||||
*
|
||||
* Game prefs don't need an init order because unlike character setup, there's no dependencies, in theory.
|
||||
*/
|
||||
/datum/game_preferences
|
||||
//* Loading *//
|
||||
/// loaded?
|
||||
var/initialized = FALSE
|
||||
|
||||
//* Preferences *//
|
||||
/// preferences by key - key = value
|
||||
var/list/entries_by_key
|
||||
/// arbitrary key access used by middleware
|
||||
var/list/misc_by_key
|
||||
// todo: move menu options in here and not from /datum/preferences
|
||||
|
||||
//* Middleware - Keybindings *//
|
||||
/// keybindings - key to list of keybinds
|
||||
var/list/keybindings
|
||||
|
||||
//* Middleware - Toggles *//
|
||||
/// toggles by key - TRUE or FALSE
|
||||
var/list/toggles_by_key
|
||||
|
||||
//* System *//
|
||||
/// were we originally sql loaded?
|
||||
/// used to determine if sql is authoritative when sql comes back
|
||||
///
|
||||
/// - if sql was originally loaded and sql comes back, we are still authoritative
|
||||
/// - if sql wasn't originally loaded and sql comes back and a sql save exists, we are overwritten
|
||||
/// - if a save doesn't exist, we are authoritative and are flushed to sql
|
||||
var/authoritatively_loaded_by_sql = FALSE
|
||||
/// something fucky wucky happened and the next time sql comes back,
|
||||
/// we need to flush data and assert authoritativeness
|
||||
var/sql_state_desynced = FALSE
|
||||
/// our player's ckey
|
||||
var/ckey
|
||||
/// we are a guest
|
||||
var/is_guest = FALSE
|
||||
/// our active client
|
||||
var/client/active
|
||||
/// our player's id
|
||||
///
|
||||
/// set upon successful sql save
|
||||
/// we will try to load a sql preferences of a given playerid
|
||||
/// when we load from sql
|
||||
var/authoritative_player_id
|
||||
/// our prefs version
|
||||
var/version = GAME_PREFERENCES_VERSION_CURRENT
|
||||
/// are we saved? if TRUE, we have modified vars
|
||||
var/is_dirty = FALSE
|
||||
|
||||
/datum/game_preferences/New(key, ckey)
|
||||
src.ckey = ckey
|
||||
src.is_guest = IsGuestKey(key)
|
||||
|
||||
//* Init *//
|
||||
|
||||
/datum/game_preferences/proc/initialize()
|
||||
perform_initial_load()
|
||||
initialized = TRUE
|
||||
|
||||
/datum/game_preferences/proc/on_reconnect()
|
||||
if(!initialized)
|
||||
return
|
||||
initialize_client()
|
||||
|
||||
/datum/game_preferences/proc/block_on_initialized(timeout = 10 SECONDS)
|
||||
var/wait_until = world.time + timeout
|
||||
UNTIL(initialized || (world.time >= wait_until))
|
||||
if(!initialized)
|
||||
. = FALSE
|
||||
CRASH("block_on_initialize timeout")
|
||||
return initialized
|
||||
|
||||
/datum/game_preferences/proc/initialize_client()
|
||||
if(isnull(active))
|
||||
return
|
||||
for(var/key in SSpreferences.entries_by_key)
|
||||
var/datum/game_preference_entry/entry = SSpreferences.entries_by_key[key]
|
||||
var/value = entries_by_key[entry.key]
|
||||
entry.on_set(active, value, TRUE)
|
||||
for(var/key in GLOB.game_preference_middleware)
|
||||
var/datum/game_preference_middleware/middleware = GLOB.game_preference_middleware[key]
|
||||
middleware.on_initial_load(src)
|
||||
|
||||
/datum/game_preferences/proc/oops_sql_came_back_perform_a_reload()
|
||||
// if we were sql loaded, don't desync from sql
|
||||
if(authoritatively_loaded_by_sql)
|
||||
if(!sql_state_desynced)
|
||||
return
|
||||
if(active)
|
||||
to_chat(active, SPAN_BOLDANNOUNCE("The server's SQL database has reconnected and your preferences were changed during the lapse. Your preferences has been automatically flushed to database."))
|
||||
save_to_sql()
|
||||
return
|
||||
// load from sql if we can; SQL is authoritative
|
||||
if(load_from_sql())
|
||||
if(active)
|
||||
to_chat(active, SPAN_BOLDANNOUNCE("The server's SQL database has reconnected and your preferences were found to be fully desynced from the copy in the database. Your preferences has been automatically reloaded from the database. Please ensure all settings are workable."))
|
||||
return
|
||||
// otherwise, save our current changes to SQL
|
||||
save_to_sql()
|
||||
if(active)
|
||||
to_chat(active, SPAN_BOLDANNOUNCE("The server's SQL database has reconnected and your preferences were not found in them. Your preferences have been automatically saved to database."))
|
||||
|
||||
/datum/game_preferences/proc/perform_legacy_migration()
|
||||
if(is_guest)
|
||||
return FALSE
|
||||
if(!fexists("data/player_saves/[copytext(ckey, 1, 2)]/[ckey]/preferences.sav"))
|
||||
return FALSE
|
||||
var/savefile/legacy_savefile = new /savefile("data/player_saves/[copytext(ckey, 1, 2)]/[ckey]/preferences.sav")
|
||||
var/list/legacy_options
|
||||
legacy_savefile["global"] >> legacy_options
|
||||
if(isnull(legacy_options))
|
||||
legacy_options = list()
|
||||
|
||||
// we are fired after reset, but before save
|
||||
// we assume lists are init'd
|
||||
for(var/key in SSpreferences.entries_by_key)
|
||||
var/datum/game_preference_entry/entry = SSpreferences.entries_by_key[key]
|
||||
var/migrated_value
|
||||
if(entry.legacy_global_key)
|
||||
migrated_value = legacy_options[entry.legacy_global_key]
|
||||
else if(entry.legacy_savefile_key)
|
||||
legacy_savefile[entry.legacy_savefile_key] >> migrated_value
|
||||
if(!isnull(migrated_value))
|
||||
migrated_value = entry.filter_value(migrated_value)
|
||||
entries_by_key[key] = migrated_value
|
||||
|
||||
var/list/old_toggles
|
||||
legacy_savefile["preferences"] >> old_toggles
|
||||
|
||||
for(var/key in SSpreferences.toggles_by_key)
|
||||
var/datum/game_preference_toggle/toggle = SSpreferences.toggles_by_key[key]
|
||||
if(!toggle.legacy_key)
|
||||
continue
|
||||
toggles_by_key[key] = (toggle.legacy_key in old_toggles)
|
||||
|
||||
var/list/old_keybinds
|
||||
legacy_savefile["key_bindings"] >> old_keybinds
|
||||
keybindings = sanitize_islist(old_keybinds)
|
||||
|
||||
var/old_hotkeys
|
||||
legacy_savefile["hotkeys"] >> old_hotkeys
|
||||
misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE] = !!old_hotkeys
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/game_preferences/proc/perform_initial_load()
|
||||
if(!is_guest)
|
||||
// only if not guest
|
||||
if(SSdbcore.IsConnected())
|
||||
// sql mode
|
||||
// load
|
||||
if(!load_from_sql())
|
||||
// if not, load from file
|
||||
if(!load_from_file())
|
||||
// if not, reset to defaults
|
||||
full_reset()
|
||||
// perform legacy migration to see if there's data
|
||||
if(perform_legacy_migration())
|
||||
// something was there, set our version to legacy
|
||||
version = GAME_PREFERENCES_VERSION_LEGACY
|
||||
// save results to sql, as sql is authoritative
|
||||
save_to_sql()
|
||||
// synchronize sql to file for backup for when sql is down
|
||||
save_to_file()
|
||||
else
|
||||
// synchronize sql to file for backup for when sql is down
|
||||
save_to_file()
|
||||
else
|
||||
// normal mode
|
||||
// load
|
||||
if(!load_from_file())
|
||||
// if not, reset to defaults
|
||||
full_reset()
|
||||
// perform legacy migration to see if there's data
|
||||
if(perform_legacy_migration())
|
||||
// something was there, set our version to legacy
|
||||
version = GAME_PREFERENCES_VERSION_LEGACY
|
||||
// save to file
|
||||
save_to_file()
|
||||
|
||||
// do we need to migrate?
|
||||
if(version < GAME_PREFERENCES_VERSION_CURRENT)
|
||||
// yes
|
||||
if(!perform_migration_sequence())
|
||||
// reset to defaults if failed
|
||||
full_reset()
|
||||
// perform save
|
||||
if(SSdbcore.IsConnected())
|
||||
save_to_sql()
|
||||
else
|
||||
save_to_file()
|
||||
else
|
||||
// if guest, just reset lmao
|
||||
full_reset()
|
||||
|
||||
// todo: shouldn't we save after sanitize..?
|
||||
sanitize_everything()
|
||||
// loaded; push initial update triggers.
|
||||
initialize_client()
|
||||
// initialized!
|
||||
initialized = TRUE
|
||||
|
||||
/**
|
||||
* @return FALSE if failed
|
||||
*/
|
||||
/datum/game_preferences/proc/perform_migration_sequence()
|
||||
if(version <= GAME_PREFERENCES_VERSION_DROP)
|
||||
return FALSE
|
||||
perform_migrations(version)
|
||||
version = GAME_PREFERENCES_VERSION_CURRENT
|
||||
|
||||
/datum/game_preferences/proc/perform_migrations(from_version)
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
//* Reset *//
|
||||
|
||||
/datum/game_preferences/proc/reset(category)
|
||||
if(!category)
|
||||
entries_by_key = list()
|
||||
for(var/key in SSpreferences.entries_by_key)
|
||||
var/datum/game_preference_entry/entry = SSpreferences.entries_by_key[key]
|
||||
if(category && entry.category != category)
|
||||
continue
|
||||
var/value = entry.default_value(active)
|
||||
entries_by_key[entry.key] = value
|
||||
if(!isnull(active))
|
||||
entry.on_set(active, value, TRUE)
|
||||
mark_dirty()
|
||||
push_ui_data(data = list("values" = entries_by_key))
|
||||
|
||||
/datum/game_preferences/proc/full_reset()
|
||||
// reset misc stuff first as everything potentially uses it
|
||||
misc_by_key = list()
|
||||
// reset normal categories
|
||||
reset()
|
||||
// reset middleware
|
||||
for(var/id in GLOB.game_preference_middleware)
|
||||
var/datum/game_preference_middleware/middleware = GLOB.game_preference_middleware[id]
|
||||
middleware.handle_reset(src)
|
||||
|
||||
//* Set / Get *//
|
||||
|
||||
/datum/game_preferences/proc/set_toggle(datum/game_preference_toggle/id_path_instance, value)
|
||||
var/datum/game_preference_toggle/toggle = SSpreferences.resolve_preference_toggle(id_path_instance)
|
||||
if(isnull(toggle))
|
||||
CRASH("invalid fetch")
|
||||
if(!initialized)
|
||||
return FALSE
|
||||
// we don't check is visible, as it's checked on 'get'
|
||||
// if(!toggle.is_visible(active))
|
||||
// return FALSE
|
||||
toggles_by_key[toggle.key] = value
|
||||
if(active)
|
||||
toggle.toggled(active, value)
|
||||
mark_dirty()
|
||||
return TRUE
|
||||
|
||||
/datum/game_preferences/proc/toggle(datum/game_preference_toggle/id_path_instance)
|
||||
var/datum/game_preference_toggle/toggle = SSpreferences.resolve_preference_toggle(id_path_instance)
|
||||
if(isnull(toggle))
|
||||
CRASH("invalid fetch")
|
||||
if(!initialized)
|
||||
return FALSE
|
||||
// we don't check is visible, as it's checked on 'get'
|
||||
// if(!toggle.is_visible(active))
|
||||
// return FALSE
|
||||
toggles_by_key[toggle.key] = !toggles_by_key[toggle.key]
|
||||
if(active)
|
||||
toggle.toggled(active, toggles_by_key[toggle.key])
|
||||
mark_dirty()
|
||||
return TRUE
|
||||
|
||||
/datum/game_preferences/proc/get_toggle(datum/game_preference_toggle/id_path_instance)
|
||||
var/datum/game_preference_toggle/toggle = SSpreferences.resolve_preference_toggle(id_path_instance)
|
||||
if(isnull(toggle))
|
||||
CRASH("invalid fetch")
|
||||
if(!initialized)
|
||||
return toggle.default_value
|
||||
if(!toggle.is_visible(active, TRUE))
|
||||
return toggle.default_value
|
||||
return toggles_by_key[toggle.key]
|
||||
|
||||
/datum/game_preferences/proc/set_entry(datum/game_preference_entry/id_path_instance, value)
|
||||
var/datum/game_preference_entry/entry = SSpreferences.resolve_preference_entry(id_path_instance)
|
||||
if(isnull(entry))
|
||||
CRASH("invalid fetch")
|
||||
if(!initialized)
|
||||
return FALSE
|
||||
if(!entry.is_visible(active, TRUE))
|
||||
return FALSE
|
||||
value = entry.filter_value(value)
|
||||
entries_by_key[entry.key] = value
|
||||
if(active)
|
||||
entry.on_set(active, value, FALSE)
|
||||
mark_dirty()
|
||||
push_ui_data(data = list("values" = entries_by_key))
|
||||
return TRUE
|
||||
|
||||
/datum/game_preferences/proc/get_entry(datum/game_preference_entry/id_path_instance)
|
||||
var/datum/game_preference_entry/entry = SSpreferences.resolve_preference_entry(id_path_instance)
|
||||
if(isnull(entry))
|
||||
CRASH("invalid fetch")
|
||||
if(!initialized)
|
||||
return entry.default_value(active)
|
||||
if(!entry.is_visible(active, TRUE))
|
||||
return entry.default_value(active)
|
||||
return entries_by_key[entry.key]
|
||||
|
||||
//* Save / Load *//
|
||||
|
||||
/datum/game_preferences/proc/auto_save()
|
||||
if(!is_dirty)
|
||||
return
|
||||
sanitize_everything()
|
||||
save()
|
||||
|
||||
/datum/game_preferences/proc/mark_dirty()
|
||||
is_dirty = TRUE
|
||||
push_ui_data(data = list("dirty" = TRUE))
|
||||
|
||||
/datum/game_preferences/proc/mark_saved()
|
||||
is_dirty = FALSE
|
||||
push_ui_data(data = list("dirty" = FALSE))
|
||||
|
||||
/datum/game_preferences/proc/save()
|
||||
if(!initialized)
|
||||
return FALSE
|
||||
if(SSdbcore.Connect())
|
||||
return save_to_sql()
|
||||
else
|
||||
return save_to_file()
|
||||
|
||||
/datum/game_preferences/proc/load()
|
||||
if(!initialized)
|
||||
return FALSE
|
||||
if(SSdbcore.Connect())
|
||||
. = load_from_sql()
|
||||
else
|
||||
. = load_from_file()
|
||||
sanitize_everything()
|
||||
initialize_client()
|
||||
|
||||
/**
|
||||
* this proc does not sanitize!
|
||||
*
|
||||
* @return FALSE if we couldn't load
|
||||
*/
|
||||
/datum/game_preferences/proc/load_from_sql()
|
||||
var/datum/player_data/player_data = resolve_player_data(ckey)
|
||||
if(!player_data.block_on_available(10 SECONDS))
|
||||
message_admins(SPAN_BOLDANNOUNCE("failed to resolve player data during prefs op for [ckey]. ping maintainers."))
|
||||
CRASH("failed to grab player data while loading via sql. something bad has happened!")
|
||||
authoritative_player_id = player_data.player_id
|
||||
|
||||
var/mob/allow_admin_proccalls = usr
|
||||
usr = null
|
||||
|
||||
var/datum/db_query/query = SSdbcore.NewQuery(
|
||||
"SELECT `toggles`, `entries`, `misc`, `keybinds`, `version` FROM [format_table_name("game_preferences")] \
|
||||
WHERE `player` = :player",
|
||||
list(
|
||||
"player" = authoritative_player_id,
|
||||
),
|
||||
)
|
||||
|
||||
usr = allow_admin_proccalls
|
||||
|
||||
query.warn_execute(FALSE)
|
||||
if(!query.NextRow())
|
||||
qdel(query)
|
||||
return FALSE
|
||||
var/toggles_json = query.item[1]
|
||||
var/entries_json = query.item[2]
|
||||
var/misc_json = query.item[3]
|
||||
var/keybinds_json = query.item[4]
|
||||
var/loaded_version = query.item[5]
|
||||
|
||||
toggles_by_key = sanitize_islist(safe_json_decode(toggles_json))
|
||||
entries_by_key = sanitize_islist(safe_json_decode(entries_json))
|
||||
misc_by_key = sanitize_islist(safe_json_decode(misc_json))
|
||||
keybindings = sanitize_islist(safe_json_decode(keybinds_json))
|
||||
version = loaded_version
|
||||
|
||||
qdel(query)
|
||||
|
||||
authoritatively_loaded_by_sql = TRUE
|
||||
mark_saved()
|
||||
update_static_data()
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/game_preferences/proc/save_to_sql()
|
||||
var/datum/player_data/player_data = resolve_player_data(ckey)
|
||||
if(!player_data.block_on_available(10 SECONDS))
|
||||
message_admins(SPAN_BOLDANNOUNCE("failed to resolve player data during prefs op for [ckey]. ping maintainers."))
|
||||
CRASH("failed to grab player data while loading via sql. something bad has happened!")
|
||||
authoritative_player_id = player_data.player_id
|
||||
|
||||
var/mob/allow_admin_proccalls = usr
|
||||
usr = null
|
||||
|
||||
var/datum/db_query/query = SSdbcore.NewQuery(
|
||||
"INSERT INTO [format_table_name("game_preferences")] \
|
||||
(`player`, `toggles`, `entries`, `misc`, `keybinds`, `version`, `modified`) VALUES \
|
||||
(:player, :toggles, :entries, :misc, :keybinds, :version, Now()) ON DUPLICATE KEY UPDATE \
|
||||
`player` = VALUES(player), `toggles` = VALUES(toggles), `entries` = VALUES(entries), `misc` = VALUES(misc), \
|
||||
`keybinds` = VALUES(keybinds), `version` = VALUES(version), `modified` = Now()",
|
||||
list(
|
||||
"player" = authoritative_player_id,
|
||||
"toggles" = safe_json_encode(toggles_by_key),
|
||||
"entries" = safe_json_encode(entries_by_key),
|
||||
"misc" = safe_json_encode(misc_by_key),
|
||||
"keybinds" = safe_json_encode(keybindings),
|
||||
"version" = version,
|
||||
)
|
||||
)
|
||||
|
||||
usr = allow_admin_proccalls
|
||||
|
||||
query.warn_execute(FALSE)
|
||||
qdel(query)
|
||||
|
||||
mark_saved()
|
||||
authoritatively_loaded_by_sql = TRUE
|
||||
sql_state_desynced = FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/game_preferences/proc/file_path()
|
||||
return "data/players/[copytext(ckey, 1, 2)]/[ckey]/preferences.json"
|
||||
|
||||
/**
|
||||
* this proc does not sanitize!
|
||||
*
|
||||
* @return FALSE if we couldn't load
|
||||
*/
|
||||
/datum/game_preferences/proc/load_from_file()
|
||||
var/savefile_path = file_path()
|
||||
|
||||
if(!fexists(savefile_path))
|
||||
return FALSE
|
||||
|
||||
var/list/deserialized = safe_json_decode(file2text(savefile_path))
|
||||
|
||||
entries_by_key = sanitize_islist(deserialized["entries"])
|
||||
toggles_by_key =sanitize_islist( deserialized["toggles"])
|
||||
keybindings = sanitize_islist(deserialized["keybindings"])
|
||||
misc_by_key = sanitize_islist(deserialized["misc"])
|
||||
|
||||
if(authoritatively_loaded_by_sql)
|
||||
sql_state_desynced = TRUE
|
||||
mark_saved()
|
||||
update_static_data()
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/game_preferences/proc/save_to_file()
|
||||
var/savefile_path = file_path()
|
||||
|
||||
var/list/serializing = list(
|
||||
"entries" = entries_by_key,
|
||||
"toggles" = toggles_by_key,
|
||||
"keybindings" = keybindings,
|
||||
"misc" = misc_by_key,
|
||||
)
|
||||
|
||||
if(fexists(savefile_path))
|
||||
fdel(savefile_path)
|
||||
|
||||
text2file(safe_json_encode(serializing), savefile_path)
|
||||
mark_saved()
|
||||
|
||||
if(authoritatively_loaded_by_sql)
|
||||
sql_state_desynced = TRUE
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/game_preferences/proc/sanitize_everything()
|
||||
// reset middleware
|
||||
for(var/id in GLOB.game_preference_middleware)
|
||||
var/datum/game_preference_middleware/middleware = GLOB.game_preference_middleware[id]
|
||||
middleware.handle_sanitize(src)
|
||||
for(var/key in SSpreferences.entries_by_key)
|
||||
var/datum/game_preference_entry/entry = SSpreferences.entries_by_key[key]
|
||||
var/current_value = entries_by_key[key]
|
||||
entries_by_key[key] = entry.filter_value(current_value)
|
||||
mark_dirty()
|
||||
|
||||
//* UI *//
|
||||
|
||||
/datum/game_preferences/ui_static_data(mob/user, datum/tgui/ui)
|
||||
. = ..()
|
||||
var/list/middleware = list()
|
||||
for(var/key in GLOB.game_preference_middleware)
|
||||
var/datum/game_preference_middleware/middleware_instance = GLOB.game_preference_middleware[key]
|
||||
middleware[key] = middleware_instance.name
|
||||
.["middleware"] = middleware
|
||||
var/list/entries = list()
|
||||
for(var/key in SSpreferences.entries_by_key)
|
||||
var/datum/game_preference_entry/entry = SSpreferences.entries_by_key[key]
|
||||
if(!entry.is_visible(user?.client, TRUE))
|
||||
continue
|
||||
entries[++entries.len] = entry.tgui_preference_schema()
|
||||
.["entries"] = entries
|
||||
.["values"] = entries_by_key
|
||||
|
||||
// todo: when do we refactor tgui again i don't like ui_interact because i'm a snowflake who likes being different
|
||||
// (real complaint: ui_interact is weird for being called from tgui as well as from external)
|
||||
// (update procs should be internally called, imo, not the 'open ui' proc, it leads to)
|
||||
// (confusion when people add code with side effects like shocking people on touch in it.)
|
||||
/datum/game_preferences/ui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
|
||||
if(!initialized)
|
||||
to_chat(user, SPAN_BOLDANNOUNCE("Your preferences are still being loaded. Please wait."))
|
||||
return
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(isnull(ui))
|
||||
ui = new(user, src, "GamePreferences")
|
||||
for(var/key in GLOB.game_preference_middleware)
|
||||
var/datum/game_preference_middleware/middleware = GLOB.game_preference_middleware[key]
|
||||
ui.register_module(middleware, key)
|
||||
ui.set_autoupdate(FALSE)
|
||||
ui.open()
|
||||
|
||||
/datum/game_preferences/ui_route(action, list/params, datum/tgui/ui, id)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
var/datum/game_preference_middleware/middleware = GLOB.game_preference_middleware[id]
|
||||
if(middleware?.handle_topic(src, action, params))
|
||||
return TRUE
|
||||
|
||||
/datum/game_preferences/ui_status(mob/user, datum/ui_state/state)
|
||||
if(user.ckey == ckey)
|
||||
return UI_INTERACTIVE
|
||||
if(check_rights(C = user, rights_required = R_DEBUG))
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
/datum/game_preferences/ui_act(action, list/params, datum/tgui/ui)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
switch(action)
|
||||
if("set")
|
||||
var/key = params["key"]
|
||||
if(isnull(SSpreferences.entries_by_key[key]))
|
||||
return TRUE
|
||||
set_entry(key, params["value"])
|
||||
return TRUE
|
||||
if("reset")
|
||||
reset(params["category"])
|
||||
return TRUE
|
||||
if("save")
|
||||
auto_save()
|
||||
return TRUE
|
||||
if("discard")
|
||||
load()
|
||||
return TRUE
|
||||
|
||||
//? Client Wrappers ?//
|
||||
|
||||
/client/proc/get_preference_toggle(datum/game_preference_toggle/id_path_instance)
|
||||
var/datum/game_preference_toggle/toggle = SSpreferences.resolve_preference_toggle(id_path_instance)
|
||||
if(isnull(toggle))
|
||||
CRASH("invalid fetch")
|
||||
if(!initialized || !preferences.initialized)
|
||||
return toggle.default_value
|
||||
if(!toggle.is_visible(src, TRUE))
|
||||
return toggle.default_value
|
||||
return preferences.toggles_by_key[toggle.key]
|
||||
|
||||
/client/proc/get_preference_entry(datum/game_preference_entry/id_path_instance)
|
||||
var/datum/game_preference_entry/entry = SSpreferences.resolve_preference_entry(id_path_instance)
|
||||
if(isnull(entry))
|
||||
CRASH("invalid fetch")
|
||||
if(!initialized)
|
||||
return entry.default_value(src)
|
||||
if(!entry.is_visible(src, TRUE))
|
||||
return entry.default_value(src)
|
||||
return preferences.entries_by_key[entry.key]
|
||||
|
||||
//? Mob Wrappers ?//
|
||||
|
||||
/mob/proc/get_preference_toggle(datum/game_preference_toggle/id_path_instance)
|
||||
var/datum/game_preference_toggle/toggle = SSpreferences.resolve_preference_toggle(id_path_instance)
|
||||
if(isnull(toggle))
|
||||
CRASH("invalid fetch")
|
||||
if(!client?.initialized || !client.preferences.initialized)
|
||||
return toggle.default_value
|
||||
if(!toggle.is_visible(client, TRUE))
|
||||
return toggle.default_value
|
||||
return client.preferences.toggles_by_key[toggle.key]
|
||||
|
||||
/mob/proc/get_preference_entry(datum/game_preference_entry/id_path_instance)
|
||||
var/datum/game_preference_entry/entry = SSpreferences.resolve_preference_entry(id_path_instance)
|
||||
if(isnull(entry))
|
||||
CRASH("invalid fetch")
|
||||
if(!client?.initialized || !client.preferences.initialized)
|
||||
return entry.default_value(client)
|
||||
if(!entry.is_visible(client, TRUE))
|
||||
return entry.default_value(client)
|
||||
return client.preferences.entries_by_key[entry.key]
|
||||
@@ -0,0 +1,140 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 silicons *//
|
||||
|
||||
/datum/game_preference_middleware/keybindings
|
||||
name = "Keybindings"
|
||||
key = "keybindings"
|
||||
|
||||
/datum/game_preference_middleware/keybindings/on_initial_load(datum/game_preferences/prefs)
|
||||
prefs.active?.update_movement_keys(src)
|
||||
prefs.active?.set_macros()
|
||||
return ..()
|
||||
|
||||
/datum/game_preference_middleware/keybindings/handle_reset(datum/game_preferences/prefs)
|
||||
. = ..()
|
||||
|
||||
prefs.keybindings = list()
|
||||
// don't change their hotkey mode.. unless it was never there.
|
||||
prefs.misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE] = \
|
||||
isnull(prefs.misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE])? TRUE : !!prefs.misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE]
|
||||
var/hotkey_mode = prefs.misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE]
|
||||
var/list/defaults = deep_copy_list(hotkey_mode? GLOB.hotkey_keybinding_list_by_key : GLOB.classic_keybinding_list_by_key)
|
||||
prefs.keybindings = defaults
|
||||
|
||||
prefs.push_ui_modules(updates = list((src.key) = list(
|
||||
"bindings" = prefs.keybindings,
|
||||
"hotkeyMode" = prefs.misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE],
|
||||
)))
|
||||
|
||||
/datum/game_preference_middleware/keybindings/handle_topic(datum/game_preferences/prefs, action, list/params)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
switch(action)
|
||||
if("reset")
|
||||
handle_reset(prefs)
|
||||
return TRUE
|
||||
if("hotkeys")
|
||||
var/value = params["value"]
|
||||
if(prefs.misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE] == value)
|
||||
return TRUE
|
||||
prefs.misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE] = !!value
|
||||
prefs.mark_dirty()
|
||||
prefs.push_ui_modules(updates = list((src.key) = list(
|
||||
"hotkeyMode" = value,
|
||||
)))
|
||||
prefs.active?.set_macros()
|
||||
return TRUE
|
||||
if("addBind")
|
||||
var/keybind_id = params["keybind"]
|
||||
// key can be blank if we're only using ctrl/alt/shift!!
|
||||
var/key = params["key"]
|
||||
var/alt_depressed = params["alt"]
|
||||
var/shift_depressed = params["shift"]
|
||||
var/ctrl_depressed = params["ctrl"]
|
||||
var/is_numpad = params["numpad"]
|
||||
var/replacing_key = params["replaceKey"]
|
||||
var/adding_key = "[alt_depressed? "Alt":""][ctrl_depressed? "Ctrl":""][shift_depressed? "Shift":""][is_numpad? "Numpad":""][key]"
|
||||
if(!adding_key)
|
||||
return TRUE
|
||||
if(adding_key == replacing_key)
|
||||
return TRUE
|
||||
if(!isnull(replacing_key))
|
||||
prefs.keybindings[replacing_key] -= keybind_id
|
||||
if(isnull(prefs.keybindings[adding_key]))
|
||||
prefs.keybindings[adding_key] = list()
|
||||
if(!(keybind_id in prefs.keybindings[adding_key]))
|
||||
prefs.keybindings[adding_key] += keybind_id
|
||||
prefs.push_ui_modules(updates = list((src.key) = list(
|
||||
"bindings" = prefs.keybindings,
|
||||
)))
|
||||
prefs.active?.update_movement_keys()
|
||||
return TRUE
|
||||
if("removeBind")
|
||||
var/keybind_id = params["keybind"]
|
||||
var/key = params["key"]
|
||||
if(!key)
|
||||
return TRUE
|
||||
prefs.keybindings[key] -= keybind_id
|
||||
prefs.push_ui_modules(updates = list((src.key) = list(
|
||||
"bindings" = prefs.keybindings,
|
||||
)))
|
||||
prefs.active?.update_movement_keys()
|
||||
return TRUE
|
||||
|
||||
/datum/game_preference_middleware/keybindings/handle_sanitize(datum/game_preferences/prefs)
|
||||
. = ..()
|
||||
prefs.misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE] = \
|
||||
isnull(prefs.misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE])? TRUE : !!prefs.misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE]
|
||||
var/list/keys_by_bind_id = list()
|
||||
//! legacy: get rid of known trash values
|
||||
prefs.keybindings -= "Unbound"
|
||||
//! end
|
||||
for(var/key in prefs.keybindings)
|
||||
var/had_something = FALSE
|
||||
if(islist(prefs.keybindings[key]))
|
||||
var/list/keybind_ids = prefs.keybindings[key]
|
||||
if(length(keybind_ids) > MAX_COMMANDS_PER_KEY)
|
||||
keybind_ids.len = MAX_COMMANDS_PER_KEY
|
||||
for(var/bind_id in prefs.keybindings[key])
|
||||
var/datum/keybinding/found = GLOB.keybindings_by_name[bind_id]
|
||||
if(isnull(found))
|
||||
prefs.keybindings[key] -= bind_id
|
||||
continue
|
||||
if(isnull(keys_by_bind_id[bind_id]))
|
||||
keys_by_bind_id[bind_id] = list()
|
||||
if(length(keys_by_bind_id[bind_id]) > MAX_KEYS_PER_KEYBIND)
|
||||
prefs.keybindings[key] -= bind_id
|
||||
continue
|
||||
keys_by_bind_id[bind_id] += key
|
||||
had_something = TRUE
|
||||
if(!had_something)
|
||||
prefs.keybindings -= key
|
||||
|
||||
/datum/game_preference_middleware/keybindings/ui_static_data(mob/user, datum/tgui/ui)
|
||||
. = ..()
|
||||
var/datum/game_preferences/prefs = ui.src_object
|
||||
.["bindings"] = prefs.keybindings
|
||||
var/list/constructed_keybinds = list()
|
||||
for(var/key in GLOB.keybindings_by_name)
|
||||
var/datum/keybinding/keybind = GLOB.keybindings_by_name[key]
|
||||
if(!keybind.is_visible(user?.client))
|
||||
continue
|
||||
constructed_keybinds[++constructed_keybinds.len] = keybind.tgui_keybinding_data()
|
||||
.["keybinds"] = constructed_keybinds
|
||||
.["hotkeyMode"] = prefs.misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE]
|
||||
.["maxBinds"] = MAX_KEYS_PER_KEYBIND
|
||||
.["maxPerKey"] = MAX_COMMANDS_PER_KEY
|
||||
|
||||
//? Preferences Helpers
|
||||
|
||||
/datum/game_preferences/proc/is_hotkeys_mode()
|
||||
if(!initialized)
|
||||
return TRUE
|
||||
return misc_by_key[GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE]
|
||||
|
||||
//? UI Design Assertions
|
||||
|
||||
#if MAX_KEYS_PER_KEYBIND != 3
|
||||
#error TGUI for prefs is only designed for 3 bindings per keybind datum. Fix it if you want more.
|
||||
#endif
|
||||
@@ -0,0 +1,59 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 silicons *//
|
||||
|
||||
/datum/game_preference_middleware/toggles
|
||||
name = "Toggles"
|
||||
key = "toggles"
|
||||
|
||||
/datum/game_preference_middleware/toggles/handle_reset(datum/game_preferences/prefs)
|
||||
. = ..()
|
||||
prefs.toggles_by_key = list()
|
||||
for(var/key in SSpreferences.toggles_by_key)
|
||||
var/datum/game_preference_toggle/toggle = SSpreferences.toggles_by_key[key]
|
||||
prefs.toggles_by_key[toggle.key] = toggle.default_value
|
||||
|
||||
prefs.push_ui_modules(updates = list((src.key) = list(
|
||||
"states" = prefs.toggles_by_key,
|
||||
)))
|
||||
|
||||
/datum/game_preference_middleware/toggles/handle_topic(datum/game_preferences/prefs, action, list/params)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
switch(action)
|
||||
if("reset")
|
||||
handle_reset(prefs)
|
||||
return TRUE
|
||||
if("toggle")
|
||||
var/key = params["key"]
|
||||
if(isnull(SSpreferences.toggles_by_key[key]))
|
||||
return TRUE
|
||||
prefs.set_toggle(key, !!params["val"])
|
||||
// todo: optimize this
|
||||
prefs.push_ui_modules(updates = list((src.key) = list(
|
||||
"states" = prefs.toggles_by_key,
|
||||
)))
|
||||
prefs.mark_dirty()
|
||||
return TRUE
|
||||
|
||||
/datum/game_preference_middleware/toggles/ui_static_data(mob/user, datum/tgui/ui)
|
||||
. = ..()
|
||||
var/datum/game_preferences/prefs = ui.src_object
|
||||
var/list/collected_toggles = list()
|
||||
for(var/key in SSpreferences.toggles_by_key)
|
||||
var/datum/game_preference_toggle/toggle = SSpreferences.toggles_by_key[key]
|
||||
if(!toggle.is_visible(user?.client))
|
||||
continue
|
||||
collected_toggles[toggle.key] = toggle.tgui_preference_schema()
|
||||
.["toggles"] = collected_toggles
|
||||
.["states"] = prefs.toggles_by_key
|
||||
|
||||
/datum/game_preference_middleware/toggles/handle_sanitize(datum/game_preferences/prefs)
|
||||
. = ..()
|
||||
prefs.toggles_by_key = sanitize_islist(prefs.toggles_by_key)
|
||||
for(var/key in prefs.toggles_by_key)
|
||||
var/datum/game_preference_toggle/toggle = SSpreferences.toggles_by_key[key]
|
||||
if(isnull(toggle))
|
||||
prefs.toggles_by_key -= key
|
||||
continue
|
||||
prefs.toggles_by_key[key] = !!prefs.toggles_by_key[key]
|
||||
@@ -41,7 +41,7 @@ GLOBAL_DATUM_INIT(age_verify_menu, /datum/age_verify_menu, new)
|
||||
/datum/age_verify_menu/ui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "AgeVerifyMenu")
|
||||
ui = new(user, src, "UIAgeVerifyMenu")
|
||||
ui.open()
|
||||
|
||||
/datum/age_verify_menu/ui_status(mob/user, datum/ui_state/state)
|
||||
@@ -53,6 +53,7 @@ GLOBAL_DATUM_INIT(age_verify_menu, /datum/age_verify_menu, new)
|
||||
return
|
||||
|
||||
if(usr.client.is_age_verified())
|
||||
qdel(ui)
|
||||
return TRUE
|
||||
|
||||
var/player_month = text2num(params["month"])
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
/client/proc/statpanel_boot_impl()
|
||||
PRIVATE_PROC(TRUE)
|
||||
// give client a second to load
|
||||
sleep(2 SECONDS)
|
||||
sleep(world.tick_lag)
|
||||
// loads statbrowser if it isn't there
|
||||
src << browse(file('html/statbrowser.html'), "window=statbrowser")
|
||||
// if it is there and we can't tell because byond is byond, send it a signal to reload
|
||||
@@ -364,6 +364,6 @@
|
||||
|
||||
/client/verb/fix_stat_panel()
|
||||
set name = "Fix Stat Panel"
|
||||
set category = "OOC"
|
||||
set category = VERB_CATEGORY_OOC
|
||||
|
||||
statpanel_reset()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
// todo: lol we should datumize these probably?
|
||||
// todo: refactor all of this
|
||||
|
||||
/var/all_ui_styles = list(
|
||||
/var/global/list/all_ui_styles = list(
|
||||
UI_STYLE_MIDNIGHT = 'icons/mob/screen/midnight.dmi',
|
||||
UI_STYLE_ORANGE = 'icons/mob/screen/orange.dmi',
|
||||
UI_STYLE_OLD = 'icons/mob/screen/old.dmi',
|
||||
@@ -10,7 +12,7 @@
|
||||
UI_STYLE_HOLOGRAM = 'icons/mob/screen/holo.dmi'
|
||||
)
|
||||
|
||||
/var/all_ui_styles_robot = list(
|
||||
/var/global/list/all_ui_styles_robot = list(
|
||||
UI_STYLE_MIDNIGHT = 'icons/mob/screen1_robot.dmi',
|
||||
UI_STYLE_ORANGE = 'icons/mob/screen1_robot.dmi',
|
||||
UI_STYLE_OLD = 'icons/mob/screen1_robot.dmi',
|
||||
@@ -34,27 +36,11 @@ var/global/list/all_tooltip_styles = list(
|
||||
return all_ui_styles[ui_style]
|
||||
return all_ui_styles[UI_STYLE_WHITE]
|
||||
|
||||
|
||||
/client/verb/change_ui()
|
||||
set name = "Change UI"
|
||||
set category = "Preferences"
|
||||
set desc = "Configure your user interface"
|
||||
|
||||
if(!ishuman(usr))
|
||||
if(!isrobot(usr))
|
||||
to_chat(usr, SPAN_WARNING("You must be a human or a robot to use this verb."))
|
||||
return
|
||||
|
||||
var/UI_style_new = input(usr, "Select a style. White is recommended for customization") as null|anything in all_ui_styles
|
||||
if(!UI_style_new) return
|
||||
|
||||
var/UI_style_alpha_new = input(usr, "Select a new alpha (transparency) parameter for your UI, between 50 and 255") as null|num
|
||||
if(!UI_style_alpha_new || !(UI_style_alpha_new <= 255 && UI_style_alpha_new >= 50))
|
||||
// todo: refactor
|
||||
/client/proc/set_ui_style(style)
|
||||
if(!usr?.hud_used.adding)
|
||||
return
|
||||
|
||||
var/UI_style_color_new = input(usr, "Choose your UI color. Dark colors are not recommended!") as color|null
|
||||
if(!UI_style_color_new) return
|
||||
|
||||
var/UI_style_new = style
|
||||
//update UI
|
||||
var/list/icons = usr.hud_used.adding + usr.hud_used.other + usr.hud_used.hotkeybuttons
|
||||
icons.Add(usr.zone_sel)
|
||||
@@ -70,13 +56,35 @@ var/global/list/all_tooltip_styles = list(
|
||||
for(var/atom/movable/screen/I in icons)
|
||||
if(I.name in list(INTENT_HELP, INTENT_HARM, INTENT_DISARM, INTENT_GRAB)) continue
|
||||
I.icon = ic
|
||||
I.color = UI_style_color_new
|
||||
I.alpha = UI_style_alpha_new
|
||||
|
||||
// todo: refactor
|
||||
/client/proc/set_ui_alpha(alpha)
|
||||
if(!usr?.hud_used.adding)
|
||||
return
|
||||
//update UI
|
||||
var/list/icons = usr.hud_used.adding + usr.hud_used.other + usr.hud_used.hotkeybuttons
|
||||
icons.Add(usr.zone_sel)
|
||||
icons.Add(usr.gun_setting_icon)
|
||||
icons.Add(usr.item_use_icon)
|
||||
icons.Add(usr.gun_move_icon)
|
||||
icons.Add(usr.radio_use_icon)
|
||||
|
||||
if(alert("Like it? Save changes?",,"Yes", "No") == "Yes")
|
||||
prefs.UI_style = UI_style_new
|
||||
prefs.UI_style_alpha = UI_style_alpha_new
|
||||
prefs.UI_style_color = UI_style_color_new
|
||||
SScharacters.queue_preferences_save(prefs)
|
||||
to_chat(usr, "UI was saved")
|
||||
for(var/atom/movable/screen/I in icons)
|
||||
if(I.name in list(INTENT_HELP, INTENT_HARM, INTENT_DISARM, INTENT_GRAB)) continue
|
||||
I.alpha = alpha
|
||||
|
||||
// todo: refactor
|
||||
/client/proc/set_ui_color(color)
|
||||
if(!usr?.hud_used.adding)
|
||||
return
|
||||
//update UI
|
||||
var/list/icons = usr.hud_used.adding + usr.hud_used.other + usr.hud_used.hotkeybuttons
|
||||
icons.Add(usr.zone_sel)
|
||||
icons.Add(usr.gun_setting_icon)
|
||||
icons.Add(usr.item_use_icon)
|
||||
icons.Add(usr.gun_move_icon)
|
||||
icons.Add(usr.radio_use_icon)
|
||||
|
||||
for(var/atom/movable/screen/I in icons)
|
||||
if(I.name in list(INTENT_HELP, INTENT_HARM, INTENT_DISARM, INTENT_GRAB)) continue
|
||||
I.color = color
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/client/verb/show_station_minimap()
|
||||
set category = "OOC"
|
||||
set category = VERB_CATEGORY_OOC
|
||||
set name = "Show Station Minimap"
|
||||
set desc = "Shows a minimap of the currently loaded station map."
|
||||
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
// todo: most of the things in here should probably be re-thought category wise;
|
||||
// verticality for one is more graphics/visuals
|
||||
// but isn't a preference because it's something you need to actively see at some times but not others..
|
||||
|
||||
/client/verb/toggle_verticality_visibility()
|
||||
set name = "Toggle Verticality Plane"
|
||||
set desc = "Toggle if you see ceiling overlays and similar."
|
||||
set category = VERB_CATEGORY_OOC
|
||||
|
||||
var/atom/movable/screen/plane_master/plane = global_planes.by_plane_type(/atom/movable/screen/plane_master/verticality)
|
||||
plane.alpha = plane.alpha == 255? 0 : 255
|
||||
to_chat(src, SPAN_NOTICE("You now [plane.alpha == 255? "see" : "no longer see"] verticality overlays."))
|
||||
|
||||
|
||||
/client/verb/motd()
|
||||
set name = "MOTD"
|
||||
set category = "OOC"
|
||||
set category = VERB_CATEGORY_OOC
|
||||
set desc ="Check the Message of the Day"
|
||||
|
||||
var/motd = config.motd
|
||||
@@ -45,7 +59,7 @@
|
||||
|
||||
/client/verb/ooc(msg as text)
|
||||
set name = "OOC" //Gave this shit a shorter name so you only have to time out "ooc" rather than "ooc message" to use it --NeoFite
|
||||
set category = "OOC"
|
||||
set category = VERB_CATEGORY_OOC
|
||||
|
||||
if(!reject_on_initialization_block())
|
||||
return
|
||||
@@ -56,7 +70,7 @@
|
||||
to_chat(src, "Guests may not use OOC.")
|
||||
return
|
||||
|
||||
if(!is_preference_enabled(/datum/client_preference/show_ooc))
|
||||
if(!get_preference_toggle(/datum/game_preference_toggle/chat/ooc))
|
||||
to_chat(src, "<span class='warning'>You have OOC muted.</span>")
|
||||
return
|
||||
|
||||
@@ -103,7 +117,7 @@
|
||||
return
|
||||
|
||||
|
||||
if(!is_preference_enabled(/datum/client_preference/show_ooc))
|
||||
if(!get_preference_toggle(/datum/game_preference_toggle/chat/ooc))
|
||||
to_chat(src, "<span class='warning'>You have OOC muted.</span>")
|
||||
return
|
||||
|
||||
@@ -126,11 +140,13 @@
|
||||
if(holder.rights & R_ADMIN)
|
||||
ooc_style = "admin"
|
||||
|
||||
var/effective_color = holder && preferences.get_entry(/datum/game_preference_entry/simple_color/admin_ooc_color)
|
||||
|
||||
for(var/client/target in GLOB.clients)
|
||||
if(!target.initialized)
|
||||
continue
|
||||
|
||||
if(target.is_preference_enabled(/datum/client_preference/show_ooc))
|
||||
if(target.get_preference_toggle(/datum/game_preference_toggle/chat/ooc))
|
||||
if(target.is_key_ignored(key)) // If we're ignored by this person, then do nothing.
|
||||
continue
|
||||
var/display_name = src.key
|
||||
@@ -140,8 +156,8 @@
|
||||
display_name = "[holder.fakekey]/([src.key])"
|
||||
else
|
||||
display_name = holder.fakekey
|
||||
if(holder && !holder.fakekey && (holder.rights & R_ADMIN) && CONFIG_GET(flag/allow_admin_ooccolor)) // keeping this for the badmins
|
||||
to_chat(target, "<span class='prefix [ooc_style]'><span class='ooc'><font color='[prefs.ooccolor]'>" + "OOC: " + "<EM>[display_name]: </EM><span class='linkify'>[msg]</span></span></span></font>")
|
||||
if(effective_color) // keeping this for the badmins
|
||||
to_chat(target, "<span class='prefix [ooc_style]'><span class='ooc'><font color='[effective_color]'>" + "OOC: " + "<EM>[display_name]: </EM><span class='linkify'>[msg]</span></span></span></font>")
|
||||
else
|
||||
to_chat(target, "<span class='ooc'><span class='[ooc_style]'><span class='message'>OOC: <EM>[display_name]: </EM><span class='linkify'>[msg]</span></span></span></span>")
|
||||
|
||||
@@ -153,7 +169,7 @@
|
||||
/client/verb/looc(msg as text)
|
||||
set name = "LOOC"
|
||||
set desc = "Local OOC, seen only by those in view."
|
||||
set category = "OOC"
|
||||
set category = VERB_CATEGORY_OOC
|
||||
|
||||
if(!reject_on_initialization_block())
|
||||
return
|
||||
@@ -175,7 +191,7 @@
|
||||
if(!msg)
|
||||
return
|
||||
|
||||
if(!is_preference_enabled(/datum/client_preference/show_looc))
|
||||
if(!get_preference_toggle(/datum/game_preference_toggle/chat/looc))
|
||||
to_chat(src, "<span class='danger'>You have LOOC muted.</span>")
|
||||
return
|
||||
|
||||
@@ -221,7 +237,7 @@
|
||||
|
||||
// Everyone in normal viewing range of the LOOC
|
||||
for(var/mob/viewer in m_viewers)
|
||||
if(viewer.client && viewer.client.is_preference_enabled(/datum/client_preference/show_looc))
|
||||
if(viewer.client && viewer.client.get_preference_toggle(/datum/game_preference_toggle/chat/looc))
|
||||
receivers |= viewer.client
|
||||
else if(istype(viewer,/mob/observer/eye)) // For AI eyes and the like
|
||||
var/mob/observer/eye/E = viewer
|
||||
@@ -230,7 +246,7 @@
|
||||
|
||||
// Admins with RLOOC displayed who weren't already in
|
||||
for(var/client/admin in GLOB.admins)
|
||||
if(!(admin in receivers) && admin.is_preference_enabled(/datum/client_preference/holder/show_rlooc))
|
||||
if(!(admin in receivers) && admin.get_preference_toggle(/datum/game_preference_toggle/admin/global_looc))
|
||||
r_receivers |= admin
|
||||
|
||||
msg = emoji_parse(msg)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/client/verb/bunker_auto_authorize()
|
||||
set name = "Auto Authorize Panic Bunker"
|
||||
set desc = "Authorizes your account in the panic bunker of any servers connected to this function."
|
||||
set category = "OOC"
|
||||
|
||||
world.send_cross_server_bunker_overrides(key, src)
|
||||
|
||||
/world/proc/send_cross_server_bunker_overrides(key, client/C)
|
||||
var/comms_key = CONFIG_GET(string/comms_key)
|
||||
if(!comms_key)
|
||||
return
|
||||
var/list/message = list()
|
||||
message["ckey"] = key
|
||||
message["source"] = "[CONFIG_GET(string/cross_comms_name)]"
|
||||
message["key"] = comms_key
|
||||
message["auto_bunker_override"] = TRUE
|
||||
var/list/servers = CONFIG_GET(keyed_list/cross_server_bunker_override)
|
||||
if(!length(servers))
|
||||
to_chat(C, "<span class='boldwarning'>AUTOBUNKER: No servers are configured to receive from this one.</span>")
|
||||
return
|
||||
var/logtext = "[key] ([key_name(C)]) has initiated an autobunker authentication with linked servers."
|
||||
message_admins(logtext)
|
||||
log_admin(logtext)
|
||||
for(var/name in servers)
|
||||
var/returned = world.Export("[servers[name]]?[list2params(message)]")
|
||||
switch(returned)
|
||||
if("Bad Key")
|
||||
to_chat(C, "<span class='boldwarning'>AUTOBuNKER: [name] failed to authenticate with this server.</span>")
|
||||
if("Function Disabled")
|
||||
to_chat(C, "<span class='boldwarning'>AUTOBUNKER: [name] has autobunker receive disabled.</span>")
|
||||
if("Success")
|
||||
to_chat(C, "<span class='boldwarning'>AUTOBUNKER: Successfully authenticated with [name]. Panic bunker bypass granted to [key].</span>.")
|
||||
else
|
||||
to_chat(C, "<span class='boldwarning'>AUTOBUNKER: Unknown error ([name]).</span>")
|
||||
@@ -1,11 +0,0 @@
|
||||
/client/verb/character_setup()
|
||||
set name = "Character Setup"
|
||||
set category = "Preferences"
|
||||
|
||||
prefs?.ShowChoices(usr)
|
||||
|
||||
/client/verb/preferences()
|
||||
set name = "Game Options"
|
||||
set category = "Preferences"
|
||||
|
||||
prefs?.ShowChoices(usr)
|
||||
@@ -1,3 +1,28 @@
|
||||
//? Setup Panels
|
||||
|
||||
/client/verb/character_setup()
|
||||
set name = "Character Setup"
|
||||
set category = VERB_CATEGORY_SYSTEM
|
||||
|
||||
prefs?.ShowChoices(usr)
|
||||
|
||||
/client/verb/preferences()
|
||||
set name = "Game Preferences"
|
||||
set category = VERB_CATEGORY_SYSTEM
|
||||
|
||||
preferences.ui_interact(mob)
|
||||
|
||||
//? Sound Management
|
||||
|
||||
/client/verb/stop_client_sounds()
|
||||
set name = "Stop Sounds"
|
||||
set category = VERB_CATEGORY_SYSTEM
|
||||
set desc = "Stop Current Sounds"
|
||||
usr << sound(null)
|
||||
usr.client?.tgui_panel?.stop_music()
|
||||
|
||||
//? Ping System
|
||||
|
||||
/client/verb/update_ping(time as num)
|
||||
set instant = TRUE
|
||||
set name = ".update_ping"
|
||||
@@ -18,5 +43,5 @@
|
||||
|
||||
/client/verb/ping()
|
||||
set name = "Ping"
|
||||
set category = "OOC"
|
||||
set category = VERB_CATEGORY_SYSTEM
|
||||
winset(src, null, "command=.display_ping+[world.time+world.tick_lag*TICK_USAGE_REAL/100]")
|
||||
@@ -1,7 +1 @@
|
||||
/client/verb/toggle_verticality_visibility()
|
||||
set name = "Toggle Verticality Plane"
|
||||
set desc = "Toggle if you see ceiling overlays and similar."
|
||||
|
||||
var/atom/movable/screen/plane_master/plane = global_planes.by_plane_type(/atom/movable/screen/plane_master/verticality)
|
||||
plane.alpha = plane.alpha == 255? 0 : 255
|
||||
to_chat(src, SPAN_NOTICE("You now [plane.alpha == 255? "see" : "no longer see"] verticality overlays."))
|
||||
// this section left intentionally blank for now
|
||||
|
||||
@@ -410,7 +410,7 @@ GLOBAL_VAR(lock_client_view_y)
|
||||
*/
|
||||
/client/verb/user_fit_viewport()
|
||||
set name = "Fit Viewport"
|
||||
set category = "OOC"
|
||||
set category = VERB_CATEGORY_OOC
|
||||
set desc = "Fit the width of the map window to match the viewport"
|
||||
|
||||
request_viewport_fit()
|
||||
|
||||
Reference in New Issue
Block a user