diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index ad82d86b4260..de0def5d91d5 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -2,15 +2,5 @@
#To make use of this file by default, run 'git config blame.ignoreRevsFile .git-blame-ignore-revs'
#in the project folder
-## Line ending conversions
-
-# Force LF line endings with gitattributes and convert repo
-62676e72a85cd23e7a87d94adff96d17859dbdc5
-# Line ending apocalypse
-134a76cc8f5517bdc1bba5a8cbe01dc820df1c2a
-# Initial pass to convert LF to CRLF
-8af8a43d6f27e342e79d4aacb22b7668cbdc8559
-# Many changes
-931da9e7ef8c0f52a768eed7998e7e62ccdefcdc
-# Remove hideous inline tab indentation, and bans it in contributing guidelines
-0f435d5dff0a7957e8cba60a41a7fc10439064c3
+## Naming changes
+23b3274778693cdbca768cd0251e306cea090d12
diff --git a/README.md b/README.md
index da9805985b3b..8b1f8b6807bb 100644
--- a/README.md
+++ b/README.md
@@ -37,14 +37,32 @@ On **May 9, 2022** we have changed the way to compile the codebase.
[Guides for Contributors](.github/CONTRIBUTING.md)
-[CitadelRP HACKMD account](https://hackmd.io/@CitadelStation13RP)
+[CitadelRP HackMD - Design Documents & Planning](https://hackmd.io/@CitadelStation13RP)
## SQL Setup
-The SQL backend for the library and stats tracking requires a MySQL server.
+The SQL backend for the library and stats tracking requires a MariaDB server.
Your server details go in /config/legacy/dbconfig.txt, and the SQL schema is in /SQL/tgstation_schema.sql.
More detailed setup instructions arecoming soon, for now ask in our Discord.
+todo: update this section
+
+## Static Files
+
+The following folders are considered 'static folders' and should be added to TGS4's static files:
+
+These are also the folders you are likely going to encounter while managing the server.
+
+- /config: server configuration
+ - /legacy: legacy configuration data go in here
+- /data: server persistent data
+ - /logs: logs are dumped in here
+ - /players: player data, like saves and characters get dumped in here
+
+You only need to make the top level folders (e.g. config, data) static folders in TGS4.
+
+Subfolders are automatically included.
+
## LICENSE
The code for Citadel-Station-13-RP is licensed under the [GNU AGPL v3](http://www.gnu.org/licenses/agpl-3.0.html).
diff --git a/SQL/database_schema.sql b/SQL/database_schema.sql
index 9d827388db4d..f7237e6ac2db 100644
--- a/SQL/database_schema.sql
+++ b/SQL/database_schema.sql
@@ -128,7 +128,7 @@ CREATE TABLE IF NOT EXISTS `%_PREFIX_%photographs` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--- players --
+-- Players --
-- Player lookup table --
-- Used to look up player ID from ckey, as well as --
@@ -153,6 +153,7 @@ CREATE TABLE IF NOT EXISTS `%_PREFIX_%player` (
`flags` int(24) NOT NULL DEFAULT 0,
`firstseen` datetime NOT NULL DEFAULT Now(),
`lastseen` datetime NOT NULL,
+ `misc` MEDIUMTEXT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
@@ -194,6 +195,25 @@ END
$$
DELIMITER ;
+
+-- Preferences --
+
+-- Stores game preferences --
+CREATE TABLE IF NOT EXISTS `%_PREFIX_%game_preferences` (
+ `player` INT(11) NOT NULL,
+ `entries` MEDIUMTEXT NOT NULL,
+ `misc` MEDIUMTEXT NOT NULL,
+ `keybinds` MEDIUMTEXT NOT NULL,
+ `toggles` MEDIUMTEXT NOT NULL,
+ `modified` DATETIME NOT NULL,
+ `version` INT(11) NOT NULL,
+ PRIMARY KEY (`player`),
+ CONSTRAINT `linked_player` FOREIGN KEY (`player`)
+ REFERENCES `%_PREFIX_%player` (`id`)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
-- Security - Ipintel --
-- Ipintel Cache Table --
diff --git a/SQL/database_schema_prefixed.sql b/SQL/database_schema_prefixed.sql
index e792364b8f79..58b5b32cc103 100644
--- a/SQL/database_schema_prefixed.sql
+++ b/SQL/database_schema_prefixed.sql
@@ -128,7 +128,7 @@ CREATE TABLE IF NOT EXISTS `rp_photographs` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--- players --
+-- Players --
-- Player lookup table --
-- Used to look up player ID from ckey, as well as --
@@ -153,6 +153,7 @@ CREATE TABLE IF NOT EXISTS `rp_player` (
`flags` int(24) NOT NULL DEFAULT 0,
`firstseen` datetime NOT NULL DEFAULT Now(),
`lastseen` datetime NOT NULL,
+ `misc` MEDIUMTEXT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
@@ -194,6 +195,25 @@ END
$$
DELIMITER ;
+
+-- Preferences --
+
+-- Stores game preferences --
+CREATE TABLE IF NOT EXISTS `rp_game_preferences` (
+ `player` INT(11) NOT NULL,
+ `entries` MEDIUMTEXT NOT NULL,
+ `misc` MEDIUMTEXT NOT NULL,
+ `keybinds` MEDIUMTEXT NOT NULL,
+ `toggles` MEDIUMTEXT NOT NULL,
+ `modified` DATETIME NOT NULL,
+ `version` INT(11) NOT NULL,
+ PRIMARY KEY (`player`),
+ CONSTRAINT `linked_player` FOREIGN KEY (`player`)
+ REFERENCES `rp_player` (`id`)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
-- Security - Ipintel --
-- Ipintel Cache Table --
diff --git a/citadel.dme b/citadel.dme
index 97f80de0530a..4535fc5d5f70 100644
--- a/citadel.dme
+++ b/citadel.dme
@@ -78,7 +78,6 @@
#include "code\__DEFINES\shuttle.dm"
#include "code\__DEFINES\shuttles.dm"
#include "code\__DEFINES\singletons.dm"
-#include "code\__DEFINES\skin.dm"
#include "code\__DEFINES\sonar.dm"
#include "code\__DEFINES\space.dm"
#include "code\__DEFINES\spaceman_dmm.dm"
@@ -131,6 +130,7 @@
#include "code\__DEFINES\atmospherics\machinery\scrubber.dm"
#include "code\__DEFINES\atmospherics\machinery\vent.dm"
#include "code\__DEFINES\cargo\supply.dm"
+#include "code\__DEFINES\client\game_preferences.dm"
#include "code\__DEFINES\client\player_flags.dm"
#include "code\__DEFINES\client\playtime.dm"
#include "code\__DEFINES\color\color.dm"
@@ -200,6 +200,8 @@
#include "code\__DEFINES\fishing\aquarium.dm"
#include "code\__DEFINES\fishing\fish.dm"
#include "code\__DEFINES\fishing\fishing.dm"
+#include "code\__DEFINES\interface\skin.dm"
+#include "code\__DEFINES\interface\verbs.dm"
#include "code\__DEFINES\inventory\accessories.dm"
#include "code\__DEFINES\inventory\bodytypes.dm"
#include "code\__DEFINES\inventory\carry_weight.dm"
@@ -532,6 +534,7 @@
#include "code\controllers\subsystem\planets.dm"
#include "code\controllers\subsystem\plants.dm"
#include "code\controllers\subsystem\playtime.dm"
+#include "code\controllers\subsystem\preferences.dm"
#include "code\controllers\subsystem\radiation.dm"
#include "code\controllers\subsystem\repository.dm"
#include "code\controllers\subsystem\research.dm"
@@ -1246,7 +1249,6 @@
#include "code\game\machinery\transportpod.dm"
#include "code\game\machinery\turnstile.dm"
#include "code\game\machinery\turret_control.dm"
-#include "code\game\machinery\vending.dm"
#include "code\game\machinery\vitals_monitor.dm"
#include "code\game\machinery\wall_frames.dm"
#include "code\game\machinery\washing_machine.dm"
@@ -1350,6 +1352,17 @@
#include "code\game\machinery\teleporter\console.dm"
#include "code\game\machinery\teleporter\pad.dm"
#include "code\game\machinery\teleporter\projector.dm"
+#include "code\game\machinery\vending\drinks.dm"
+#include "code\game\machinery\vending\engineering.dm"
+#include "code\game\machinery\vending\loadout.dm"
+#include "code\game\machinery\vending\medical.dm"
+#include "code\game\machinery\vending\misc.dm"
+#include "code\game\machinery\vending\plants.dm"
+#include "code\game\machinery\vending\research.dm"
+#include "code\game\machinery\vending\security.dm"
+#include "code\game\machinery\vending\service.dm"
+#include "code\game\machinery\vending\snacks.dm"
+#include "code\game\machinery\vending\vending.dm"
#include "code\game\machinery\virtual_reality\ar_console.dm"
#include "code\game\machinery\virtual_reality\vr_console.dm"
#include "code\game\machinery\vitruvius\anti_heater.dm"
@@ -2389,15 +2402,21 @@
#include "code\modules\client\wrappers.dm"
#include "code\modules\client\data\client_data.dm"
#include "code\modules\client\data\player_data.dm"
+#include "code\modules\client\game_preferences\game_preference_entry.dm"
+#include "code\modules\client\game_preferences\game_preference_middleware.dm"
+#include "code\modules\client\game_preferences\game_preference_toggle.dm"
+#include "code\modules\client\game_preferences\game_preferences.dm"
+#include "code\modules\client\game_preferences\entries\game.dm"
+#include "code\modules\client\game_preferences\entries\graphics.dm"
+#include "code\modules\client\game_preferences\middleware\keybindings.dm"
+#include "code\modules\client\game_preferences\middleware\toggles.dm"
#include "code\modules\client\onboarding\_onboarding.dm"
#include "code\modules\client\onboarding\age_verification.dm"
#include "code\modules\client\onboarding\panic_bunker.dm"
#include "code\modules\client\onboarding\security_checks.dm"
#include "code\modules\client\verbs\minimap.dm"
#include "code\modules\client\verbs\ooc.dm"
-#include "code\modules\client\verbs\panic_bunker_player.dm"
-#include "code\modules\client\verbs\ping.dm"
-#include "code\modules\client\verbs\preferences.dm"
+#include "code\modules\client\verbs\system.dm"
#include "code\modules\client\verbs\view.dm"
#include "code\modules\clothing\chameleon.dm"
#include "code\modules\clothing\clothing.dm"
@@ -3171,6 +3190,7 @@
#include "code\modules\maps\misc_maps\lavaland\_lavaland.dm"
#include "code\modules\maps\overmap\planets\_lythios43c.dm"
#include "code\modules\maps\overmap\planets\_virgo3b.dm"
+#include "code\modules\maps\overmap\planets\admin_croatoan.dm"
#include "code\modules\maps\overmap\planets\classd.dm"
#include "code\modules\maps\overmap\planets\classg.dm"
#include "code\modules\maps\overmap\planets\classh.dm"
@@ -3593,6 +3613,7 @@
#include "code\modules\mob\living\silicon\pai\death.dm"
#include "code\modules\mob\living\silicon\pai\defense.dm"
#include "code\modules\mob\living\silicon\pai\examine.dm"
+#include "code\modules\mob\living\silicon\pai\hologram_effect.dm"
#include "code\modules\mob\living\silicon\pai\life.dm"
#include "code\modules\mob\living\silicon\pai\mobility.dm"
#include "code\modules\mob\living\silicon\pai\pai.dm"
@@ -4219,16 +4240,12 @@
#include "code\modules\preferences\preference_setup\general\04_equipment.dm"
#include "code\modules\preferences\preference_setup\general\05_background.dm"
#include "code\modules\preferences\preference_setup\general\06_flavor.dm"
-#include "code\modules\preferences\preference_setup\global\01_ui.dm"
#include "code\modules\preferences\preference_setup\global\02_settings.dm"
#include "code\modules\preferences\preference_setup\global\03_pai.dm"
#include "code\modules\preferences\preference_setup\global\04_ooc.dm"
#include "code\modules\preferences\preference_setup\global\05_media.dm"
-#include "code\modules\preferences\preference_setup\global\language_prefix.dm"
-#include "code\modules\preferences\preference_setup\global\setting_datums.dm"
#include "code\modules\preferences\preference_setup\helpers\language_pick.dm"
#include "code\modules\preferences\preference_setup\helpers\species_pick.dm"
-#include "code\modules\preferences\preference_setup\keybindings\keybindings.dm"
#include "code\modules\preferences\preference_setup\loadout\loadout.dm"
#include "code\modules\preferences\preference_setup\occupation\occupation.dm"
#include "code\modules\preferences\preference_setup\vore\01_ears.dm"
diff --git a/code/__DEFINES/client/game_preferences.dm b/code/__DEFINES/client/game_preferences.dm
new file mode 100644
index 000000000000..7fe82c5f8f1f
--- /dev/null
+++ b/code/__DEFINES/client/game_preferences.dm
@@ -0,0 +1,32 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+//* Versioning *//
+
+// Migration Directives:
+// * prefer append-update instead of change-remove.
+// append-update means write new, changed data instead of overwrite old data
+// this means that reverting a testmerge doesn't cause problems.
+
+// todo: how to do migration in a downstream-friendly way? current
+// would require downstreams to manually edit code.
+// this is bad!
+
+// current version; bump to trigger migrations
+#define GAME_PREFERENCES_VERSION_CURRENT 1
+// prefs start at this version when legacy
+#define GAME_PREFERENCES_VERSION_LEGACY 1
+// at or below this, we throw out all data
+#define GAME_PREFERENCES_VERSION_DROP 0
+
+//* Misc Keys *//
+
+/// boolean value for if we're in hotkeys mode
+#define GAME_PREFERENCE_MISC_KEY_HOTKEY_MODE "hotkey-mode"
+
+//* Category Names
+
+// these are the same due to low entry count
+
+#define GAME_PREFERENCE_CATEGORY_GAME "Game"
+#define GAME_PREFERENCE_CATEGORY_GRAPHICS "Game"
diff --git a/code/__DEFINES/controllers/_subsystems.dm b/code/__DEFINES/controllers/_subsystems.dm
index 49fd3741c400..58c84f5d48ce 100644
--- a/code/__DEFINES/controllers/_subsystems.dm
+++ b/code/__DEFINES/controllers/_subsystems.dm
@@ -84,6 +84,7 @@ DEFINE_BITFIELD(runlevels, list(
#define INIT_ORDER_EARLY_INIT 185
#define INIT_ORDER_REPOSITORY 180
#define INIT_ORDER_STATPANELS 170
+#define INIT_ORDER_PREFERENCES 165
#define INIT_ORDER_INPUT 160
#define INIT_ORDER_JOBS 150
#define INIT_ORDER_CHARACTERS 140
diff --git a/code/__DEFINES/skin.dm b/code/__DEFINES/interface/skin.dm
similarity index 92%
rename from code/__DEFINES/skin.dm
rename to code/__DEFINES/interface/skin.dm
index 7772d6251340..f4c2eeea2f8a 100644
--- a/code/__DEFINES/skin.dm
+++ b/code/__DEFINES/interface/skin.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
//? WINCLONE IDs; NO SKIN CONTROL MAY HAVE THESE IDs, OR WINCLONE WILL NOT CREATE "NAKED" CONTROLS OF THIS TYPE!
#define SKIN_ID_ABSTRACT_MENU "menu"
#define SKIN_ID_ABSTRACT_WINDOW "window"
@@ -17,6 +20,10 @@
/// main window split
#define SKIN_SPLITTER_ID_MAIN "mainwindow.split"
+//* Chat System
+#define SKIN_OUTPUT_ID_LEGACY_CHAT "output"
+#define SKIN_BROWSER_ID_CHAT "outputbrowser"
+
//* Cutscene System
#define SKIN_BROWSER_ID_CUTSCENE "cutscenebrowser"
diff --git a/code/__DEFINES/interface/verbs.dm b/code/__DEFINES/interface/verbs.dm
new file mode 100644
index 000000000000..22a3b39eb81f
--- /dev/null
+++ b/code/__DEFINES/interface/verbs.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+#define VERB_CATEGORY_IC "IC"
+#define VERB_CATEGORY_OOC "OOC"
+#define VERB_CATEGORY_OBJECT "Object"
+#define VERB_CATEGORY_SYSTEM "System"
+
+// todo: admin verb categories too
diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm
index 751813269470..d3dd80bd1c46 100644
--- a/code/__HELPERS/_logging.dm
+++ b/code/__HELPERS/_logging.dm
@@ -424,13 +424,13 @@ GLOBAL_LIST_INIT(testing_global_profiler, list("_PROFILE_NAME" = "Global"))
if (config_legacy.log_debug)
WRITE_LOG(GLOB.world_runtime_log, "DEBUG: [text]")
- for(var/client/C in GLOB.admins)
- if(C.is_preference_enabled(/datum/client_preference/debug/show_debug_logs))
- to_chat(C,
- type = MESSAGE_TYPE_DEBUG,
- html = "DEBUG: [text]",
- confidential = TRUE,
- )
+ // for(var/client/C in GLOB.admins)
+ // if(C.get_preference_toggle(/datum/client_preference/debug/show_debug_logs))
+ // to_chat(C,
+ // type = MESSAGE_TYPE_DEBUG,
+ // html = "DEBUG: [text]",
+ // confidential = TRUE,
+ // )
/proc/log_ghostsay(text, mob/speaker)
if (config_legacy.log_say)
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index e5511d5a9f66..4dcc19101528 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -267,7 +267,7 @@
var/turf/ear = get_turf(M)
if(ear)
// Ghostship is magic: Ghosts can hear radio chatter from anywhere
- if(speaker_coverage[ear] || (istype(M, /mob/observer/dead) && M.is_preference_enabled(/datum/client_preference/ghost_radio)))
+ if(speaker_coverage[ear] || (istype(M, /mob/observer/dead) && M.get_preference_toggle(/datum/game_preference_toggle/observer/ghost_radio)))
. |= M // Since we're already looping through mobs, why bother using |= ? This only slows things down.
return .
@@ -308,11 +308,11 @@
switch(type)
// Audio messages use ghost_ears.
if(1)
- if(M.is_preference_enabled(/datum/client_preference/ghost_ears))
+ if(M.get_preference_toggle(/datum/game_preference_toggle/observer/ghost_ears))
mobs |= M
// Visual messages use ghost_sight.
if(2)
- if(M.is_preference_enabled(/datum/client_preference/ghost_sight))
+ if(M.get_preference_toggle(/datum/game_preference_toggle/observer/ghost_sight))
mobs |= M
// For objects below the top level who still want to hear.
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index e4a7c7c00503..08ba49b5fd2e 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -320,41 +320,6 @@
/proc/capitalize(t as text)
return uppertext(copytext_char(t, 1, 2)) + copytext_char(t, 2)
-/**
- * Syntax is "stringtoreplace"="stringtoreplacewith".
- */
-/proc/autocorrect(input as text)
- return input = replace_characters(input, list(
- " i " = " I ",
- "i'm" = "I'm",
- "s's" = "s'",
- "isnt" = "isn't",
- "dont" = "don't",
- "shouldnt" = "shouldn't",
- " ive " = " I've ",
- "whove" = "who've",
- "whod" = "who’d",
- "whats " = "what’s ",
- "whatd" = "what’d",
- "thats" = "that’s",
- "thatll" = "that’ll",
- "thatd" = "that’d",
- " nows " = " now’s ",
- "isnt" = "isn’t",
- " arent " = " aren’t ",
- "wasnt" = "wasn’t",
- "werent" = "weren’t",
- "havent" = "haven’t",
- "hasnt" = "hasn’t",
- "hadnt" = "hadn’t",
- "doesnt" = "doesn’t",
- "didnt" = "didn’t",
- "couldnt" = "couldn’t",
- "wouldnt" = "wouldn’t",
- "mustnt" = "mustn’t",
- "shouldnt" = "shouldn’t",
- ))
-
/**
* This proc strips html properly, remove < > and all text between
* for complete text sanitizing should be used sanitize()
@@ -460,7 +425,7 @@
GLOBAL_VAR_INIT(text_tag_icons, new /icon('./icons/chattags.dmi'))
/proc/create_text_tag(tagname, tagdesc = tagname, client/C)
- if(!(C && C.is_preference_enabled(/datum/client_preference/chat_tags)))
+ if(!(C && C.get_preference_toggle(/datum/game_preference_toggle/chat/legacy_chat_tags)))
return tagdesc
return icon2html(GLOB.text_tag_icons, C, tagname)
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 2e3e736f72bd..1cda55518d3c 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -430,7 +430,7 @@
var/list/creatures = list()
var/list/namecounts = list()
for(var/mob/M in mobs)
- if(isobserver(M) && ghostfollow && M.client?.holder && M.client.holder.fakekey && M.is_preference_enabled(/datum/client_preference/holder/stealth_ghost_mode))
+ if(isobserver(M) && ghostfollow && M.client?.holder && M.client.holder.fakekey && M.get_preference_toggle(/datum/game_preference_toggle/admin/stealth_hides_ghost))
continue
var/name = M.name
if (name in names)
diff --git a/code/controllers/configuration/entries/cross_server.dm b/code/controllers/configuration/entries/cross_server.dm
index d8d29b913129..9a7bd1f9d92c 100644
--- a/code/controllers/configuration/entries/cross_server.dm
+++ b/code/controllers/configuration/entries/cross_server.dm
@@ -1,28 +1,14 @@
+// todo: we need to rethink if we need/want a cluster at some point (and why is the answer yes :^))
+// and how to orchestrate. topics are nice, but they shouldn't be the end-all backend for this.
+//
+// not to mention we also need a cross-server/unified DB.
+// 2025 DB rework..?
+
/datum/config_entry/string/comms_key
protection = CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/comms_key/ValidateAndSet(str_val)
return str_val != "default_pwd" && length(str_val) > 6 && ..()
-// todo: remove
-/datum/config_entry/keyed_list/cross_server_bunker_override
- key_mode = KEY_MODE_TEXT
- value_mode = VALUE_MODE_TEXT
- protection = CONFIG_ENTRY_LOCKED
-
-/datum/config_entry/keyed_list/cross_server_bunker_override/ValidateAndSet(str_val)
- . = ..()
- if(.)
- var/list/newv = list()
- for(var/I in config_entry_value)
- newv[replacetext(I, "+", " ")] = config_entry_value[I]
- config_entry_value = newv
-
-/datum/config_entry/keyed_list/cross_server_bunker_override/ValidateListEntry(key_name, key_value)
- return key_value != "byond:\\address:port" && ..()
-
-/datum/config_entry/flag/allow_cross_server_bunker_override
- protection = CONFIG_ENTRY_LOCKED
-
// todo: remove, cluster staging/organization should be in a database
/datum/config_entry/string/cross_comms_name
diff --git a/code/controllers/configuration_old/configuration.dm b/code/controllers/configuration_old/configuration.dm
index 3bd2f44c0539..95e30b2287ef 100644
--- a/code/controllers/configuration_old/configuration.dm
+++ b/code/controllers/configuration_old/configuration.dm
@@ -225,12 +225,6 @@
var/list/gamemode_cache = list()
- var/lock_client_view_x
- var/lock_client_view_y
- var/max_client_view_x
- var/max_client_view_y
-
-
/datum/configuration_legacy/New()
var/list/L = subtypesof(/datum/game_mode)
for (var/T in L)
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index fe3ef114b9c8..8958eba3cd6d 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -405,5 +405,8 @@
/datum/controller/subsystem/proc/on_max_z_changed(old_z_count, new_z_count)
return
-// todo: generic json-based save/load for subsystems, for simple state storage
-// todo: generic K-V store for subsystems, for storing stuff that's rare needed but still persistent.
+/**
+ * Called when SQL is reconnected after being disconnected
+ */
+/datum/controller/subsystem/proc/on_sql_reconnect()
+ return
diff --git a/code/controllers/subsystem/dbcore/_dbcore.dm b/code/controllers/subsystem/dbcore/_dbcore.dm
index a3fb78f2fd39..728980a93fc8 100644
--- a/code/controllers/subsystem/dbcore/_dbcore.dm
+++ b/code/controllers/subsystem/dbcore/_dbcore.dm
@@ -15,6 +15,8 @@ SUBSYSTEM_DEF(dbcore)
var/connection // Arbitrary handle returned from rust_g.
+ var/was_ever_connected = FALSE
+
/datum/controller/subsystem/dbcore/Initialize()
//We send warnings to the admins during subsystem init, as the clients will be New'd and messages
//will queue properly with goonchat
@@ -95,6 +97,12 @@ SUBSYSTEM_DEF(dbcore)
. = (result["status"] == "ok")
if (.)
connection = result["handle"]
+ if(was_ever_connected)
+ // we got re-connected.
+ for(var/datum/controller/subsystem/subsystem in Master.subsystems)
+ subsystem.on_sql_reconnect()
+ world.on_sql_reconnect()
+ was_ever_connected = TRUE
else
connection = null
last_error = result["data"]
@@ -361,3 +369,9 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table
text = replacetext(text, ";", "")
text = replacetext(text, "&", "")
return text
+
+/**
+ * Override this proc when you need to hook into it.
+ */
+/world/proc/on_sql_reconnect()
+ return
diff --git a/code/controllers/subsystem/playtime.dm b/code/controllers/subsystem/playtime.dm
index 24802707054e..4dbfd544fc68 100644
--- a/code/controllers/subsystem/playtime.dm
+++ b/code/controllers/subsystem/playtime.dm
@@ -67,6 +67,10 @@ SUBSYSTEM_DEF(playtime)
. += PLAYER_PLAYTIME_ROLE(J.id)
/datum/controller/subsystem/playtime/proc/queue_playtimes(client/C)
+ set waitfor = FALSE
+ queue_playtimes_sync(C)
+
+/datum/controller/subsystem/playtime/proc/queue_playtimes_sync(client/C)
if(isnull(C))
return
if(!C.initialized)
diff --git a/code/controllers/subsystem/preferences.dm b/code/controllers/subsystem/preferences.dm
new file mode 100644
index 000000000000..08bdf079e1cb
--- /dev/null
+++ b/code/controllers/subsystem/preferences.dm
@@ -0,0 +1,76 @@
+SUBSYSTEM_DEF(preferences)
+ name = "Preferences"
+ init_order = INIT_ORDER_PREFERENCES
+ subsystem_flags = SS_NO_FIRE
+
+ var/list/datum/game_preference_entry/entries_by_key
+ var/list/datum/game_preference_toggle/toggles_by_key
+ var/static/list/datum/game_preferences/preferences_by_key = list()
+
+/datum/controller/subsystem/preferences/Initialize()
+ init_preference_entries()
+ init_preference_toggles()
+ return ..()
+
+/datum/controller/subsystem/preferences/proc/resolve_preference_entry(datum/game_preference_entry/entrylike)
+ if(ispath(entrylike))
+ entrylike = initial(entrylike.key)
+ entrylike = entries_by_key[entrylike]
+ else if(istype(entrylike))
+ else
+ entrylike = entries_by_key[entrylike]
+ return entrylike
+
+/datum/controller/subsystem/preferences/proc/resolve_preference_toggle(datum/game_preference_toggle/togglelike)
+ if(ispath(togglelike))
+ togglelike = initial(togglelike.key)
+ togglelike = toggles_by_key[togglelike]
+ else if(istype(togglelike))
+ else
+ togglelike = toggles_by_key[togglelike]
+ return togglelike
+
+/datum/controller/subsystem/preferences/proc/init_preference_entries()
+ . = list()
+ for(var/datum/game_preference_entry/casted as anything in subtypesof(/datum/game_preference_entry))
+ if(initial(casted.abstract_type) == casted)
+ continue
+ casted = new casted
+ if(!casted.key || !istext(casted.key))
+ STACK_TRACE("bad key: [casted.key]")
+ continue
+ if(!isnull(.[casted.key]))
+ STACK_TRACE("dupe key between [casted.type] and [.[casted.key]:type]")
+ continue
+ .[casted.key] = casted
+ entries_by_key = .
+
+/datum/controller/subsystem/preferences/proc/init_preference_toggles()
+ . = list()
+ for(var/datum/game_preference_toggle/casted as anything in subtypesof(/datum/game_preference_toggle))
+ if(initial(casted.abstract_type) == casted)
+ continue
+ casted = new casted
+ if(!casted.key || !istext(casted.key))
+ STACK_TRACE("bad key: [casted.key]")
+ continue
+ if(!isnull(.[casted.key]))
+ STACK_TRACE("dupe key between [casted.type] and [.[casted.key]:type]")
+ continue
+ .[casted.key] = casted
+ toggles_by_key = .
+
+/datum/controller/subsystem/preferences/proc/resolve_game_preferences(key, ckey)
+ if(!istype(preferences_by_key[ckey], /datum/game_preferences))
+ var/datum/game_preferences/initializing = new(key, ckey)
+ preferences_by_key[ckey] = initializing
+ initializing.initialize()
+ return preferences_by_key[ckey]
+
+/datum/controller/subsystem/preferences/on_sql_reconnect()
+ for(var/ckey in SSpreferences.preferences_by_key)
+ var/datum/game_preferences/preferences = SSpreferences.preferences_by_key[ckey]
+ if(!istype(preferences))
+ continue
+ preferences.oops_sql_came_back_perform_a_reload()
+ return ..()
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index 5dea5d4896ea..165dbf162920 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -223,7 +223,7 @@ SUBSYSTEM_DEF(vote)
usr.client.vote()
/client/verb/vote()
- set category = "OOC"
+ set category = VERB_CATEGORY_OOC
set name = "Vote"
if(SSvote)
diff --git a/code/datums/elements/clothing/dynamic_recolor.dm b/code/datums/elements/clothing/dynamic_recolor.dm
index 4c0802d97dba..23968124d2ac 100644
--- a/code/datums/elements/clothing/dynamic_recolor.dm
+++ b/code/datums/elements/clothing/dynamic_recolor.dm
@@ -40,7 +40,7 @@
/obj/item/clothing/proc/dynamic_recolor_verb()
set name = "Set Color Style"
- set category = "IC"
+ set category = VERB_CATEGORY_IC
set desc = "Set the coloration of this piece of clothing."
set src in usr
diff --git a/code/datums/helper_datums/getrev.dm b/code/datums/helper_datums/getrev.dm
index 6985d31efd37..84dbe96bd6a9 100644
--- a/code/datums/helper_datums/getrev.dm
+++ b/code/datums/helper_datums/getrev.dm
@@ -52,7 +52,7 @@
. += "#[tm.number][details]
"
/client/verb/showrevinfo()
- set category = "OOC"
+ set category = VERB_CATEGORY_OOC
set name = "Show Server Revision"
set desc = "Check the current server code revision"
diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm
index 66bb5e0a7424..78954674804a 100644
--- a/code/datums/looping_sounds/_looping_sound.dm
+++ b/code/datums/looping_sounds/_looping_sound.dm
@@ -91,7 +91,7 @@
if(direct)
if(ismob(thing))
var/mob/M = thing
- if(!M.is_preference_enabled(pref_check))
+ if(!M.get_preference_toggle(pref_check))
continue
SEND_SOUND(thing, S)
else
diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm
index aee8a07408d6..83466f14b430 100644
--- a/code/datums/looping_sounds/machinery_sounds.dm
+++ b/code/datums/looping_sounds/machinery_sounds.dm
@@ -13,7 +13,7 @@
mid_length = 60
volume = 40
extra_range = 10
- pref_check = /datum/client_preference/supermatter_hum
+ pref_check = /datum/game_preference_toggle/ambience/supermatter_hum
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -102,4 +102,4 @@
mid_length = 4
end_sound = 'sound/machines/air_pump/airpumpshutdown.ogg'
volume = 15
- pref_check = /datum/client_preference/air_pump_noise
+ pref_check = /datum/game_preference_toggle/ambience/atmospherics
diff --git a/code/datums/looping_sounds/weather_sounds.dm b/code/datums/looping_sounds/weather_sounds.dm
index 869673df27a4..df7410b0eca9 100644
--- a/code/datums/looping_sounds/weather_sounds.dm
+++ b/code/datums/looping_sounds/weather_sounds.dm
@@ -1,5 +1,5 @@
/datum/looping_sound/weather
- pref_check = /datum/client_preference/weather_sounds
+ pref_check = /datum/game_preference_toggle/ambience/weather
soundenvwet = -10000
soundenvdry = 0
diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm
index ee4a71eee143..5db22df83520 100644
--- a/code/datums/progressbar.dm
+++ b/code/datums/progressbar.dm
@@ -46,6 +46,6 @@
progress = clamp(progress, 0, goal)
bar.icon_state = "prog_bar_[round(((progress / goal) * 100), 5)]"
- if (!shown && user.is_preference_enabled(/datum/client_preference/show_progress_bar))
+ if (!shown)
user.client.images += bar
shown = 1
diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm
index 4b984a1beac1..d25c2379d647 100644
--- a/code/datums/world_topic.dm
+++ b/code/datums/world_topic.dm
@@ -75,25 +75,6 @@
for(var/client/C in GLOB.clients)
C.AnnouncePR(final_composed)
-/datum/world_topic/auto_bunker_passthrough
- keyword = "auto_bunker_override"
- require_comms_key = TRUE
-
-/datum/world_topic/auto_bunker_passthrough/Run(list/input)
- if(!CONFIG_GET(flag/allow_cross_server_bunker_override))
- return "Function Disabled"
- var/ckeytobypass = input["ckey"]
- var/is_new_ckey = !(ckey(ckeytobypass) in GLOB.bunker_passthrough)
- var/sender = input["source"] || "UNKNOWN"
- GLOB.bunker_passthrough |= ckey(ckeytobypass)
- GLOB.bunker_passthrough[ckey(ckeytobypass)] = world.realtime
- SSpersistence.SavePanicBunker() //we can do this every time, it's okay
- if(!is_new_ckey)
- log_admin("AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).")
- message_admins("AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).")
- send2irc("Panic Bunker", "AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).")
- return "Success"
-
/datum/world_topic/jsonstatus
keyword = "jsonstatus"
diff --git a/code/game/antagonist/antagonist_objectives.dm b/code/game/antagonist/antagonist_objectives.dm
index da542288ea5f..b502cea8cae3 100644
--- a/code/game/antagonist/antagonist_objectives.dm
+++ b/code/game/antagonist/antagonist_objectives.dm
@@ -32,7 +32,7 @@
/mob/living/proc/write_ambition()
set name = "Set Ambition"
- set category = "IC"
+ set category = VERB_CATEGORY_IC
set src = usr
if(!mind)
diff --git a/code/game/area/area.dm b/code/game/area/area.dm
index c436fc18f1e1..675cf7864d9c 100644
--- a/code/game/area/area.dm
+++ b/code/game/area/area.dm
@@ -564,7 +564,7 @@ GLOBAL_LIST_EMPTY(forced_ambiance_list)
/area/proc/play_ambience(var/mob/living/L)
// Ambience goes down here -- make sure to list each area seperately for ease of adding things in later, thanks! Note: areas adjacent to each other should have the same sounds to prevent cutoff when possible.- LastyScratch
- if(!L?.is_preference_enabled(/datum/client_preference/play_ambiance))
+ if(!L?.get_preference_toggle(/datum/game_preference_toggle/ambience/area_ambience))
return
// If we previously were in an area with force-played ambiance, stop it.
diff --git a/code/game/click/other_mobs.dm b/code/game/click/other_mobs.dm
index 0b0ae6ee3461..8cbc452f270a 100644
--- a/code/game/click/other_mobs.dm
+++ b/code/game/click/other_mobs.dm
@@ -139,13 +139,6 @@
setClickCooldown(get_attack_speed())
A.attack_generic(src,rand(5,6),"bitten")
-/*
- pAI
-*/
-
-/mob/living/silicon/pai/UnarmedAttack(atom/A)//Stops runtimes due to attack_animal being the default
- return
-
/*
New Players:
Have no reason to click on anything at all.
diff --git a/code/game/click/rig.dm b/code/game/click/rig.dm
index 360aa04cd046..d1b0f87f6fe2 100644
--- a/code/game/click/rig.dm
+++ b/code/game/click/rig.dm
@@ -10,7 +10,7 @@
/client/verb/toggle_hardsuit_mode()
set name = "Toggle Hardsuit Activation Mode"
set desc = "Switch between hardsuit activation modes."
- set category = "OOC"
+ set category = VERB_CATEGORY_OOC
hardsuit_click_mode++
if(hardsuit_click_mode > MAX_HARDSUIT_CLICK_MODE)
diff --git a/code/game/dna/dna_modifier.dm b/code/game/dna/dna_modifier.dm
index ff5617444f56..3f2835b7d3dd 100644
--- a/code/game/dna/dna_modifier.dm
+++ b/code/game/dna/dna_modifier.dm
@@ -65,7 +65,7 @@
/obj/machinery/dna_scannernew/verb/eject()
set src in oview(1)
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Eject DNA Scanner"
if (usr.stat != 0)
@@ -95,7 +95,7 @@
/obj/machinery/dna_scannernew/verb/move_inside()
set src in oview(1)
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Enter DNA Scanner"
if (usr.stat != 0)
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 5a430ae0741d..f5b71ab2dc0c 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -539,7 +539,7 @@ var/global/list/additional_antag_types = list()
/mob/verb/check_round_info()
set name = "Check Round Info"
- set category = "OOC"
+ set category = VERB_CATEGORY_OOC
if(!SSticker || !SSticker.mode)
to_chat(usr, "Something is terribly wrong; there is no gametype.")
diff --git a/code/game/gamemodes/nuclear/pinpointer.dm b/code/game/gamemodes/nuclear/pinpointer.dm
index 7140731a6ed1..e086731d45d5 100644
--- a/code/game/gamemodes/nuclear/pinpointer.dm
+++ b/code/game/gamemodes/nuclear/pinpointer.dm
@@ -119,7 +119,7 @@
spawn(5) .()
/obj/item/pinpointer/advpinpointer/verb/toggle_mode()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Toggle Pinpointer Mode"
set src in view(1)
diff --git a/code/game/gamemodes/technomancer/core_obj.dm b/code/game/gamemodes/technomancer/core_obj.dm
index 879c6cb70993..01d47d273471 100644
--- a/code/game/gamemodes/technomancer/core_obj.dm
+++ b/code/game/gamemodes/technomancer/core_obj.dm
@@ -341,7 +341,7 @@
/obj/item/technomancer_core/verb/toggle_lock()
set name = "Toggle Core Lock"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set desc = "Toggles the locking mechanism on your manipulation core."
var/had = HAS_TRAIT_FROM(src, TRAIT_ITEM_NODROP, TECHNOMANCER_TRAIT)
diff --git a/code/game/machinery/OpTable.dm b/code/game/machinery/OpTable.dm
index 0a95097a3876..dea856fe44ae 100644
--- a/code/game/machinery/OpTable.dm
+++ b/code/game/machinery/OpTable.dm
@@ -92,7 +92,7 @@
/obj/machinery/optable/verb/climb_on()
set name = "Climb On Table"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr.stat || !ishuman(usr) || usr.restrained() || !check_table(usr))
diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm
index 6e36d6b781d3..c894c4e47efd 100644
--- a/code/game/machinery/Sleeper.dm
+++ b/code/game/machinery/Sleeper.dm
@@ -295,7 +295,7 @@
/obj/machinery/sleeper/verb/move_eject()
set name = "Eject occupant"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr == occupant)
switch(usr.stat)
diff --git a/code/game/machinery/_frame.dm b/code/game/machinery/_frame.dm
index 0c32c989ef4b..171763dd9dd9 100644
--- a/code/game/machinery/_frame.dm
+++ b/code/game/machinery/_frame.dm
@@ -603,7 +603,7 @@
/obj/structure/frame/verb/rotate_counterclockwise()
set name = "Rotate Frame Counter-Clockwise"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr.incapacitated())
@@ -622,7 +622,7 @@
/obj/structure/frame/verb/rotate_clockwise()
set name = "Rotate Frame Clockwise"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr.incapacitated())
diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm
index 5efd47539ee3..a9948a020498 100644
--- a/code/game/machinery/adv_med.dm
+++ b/code/game/machinery/adv_med.dm
@@ -107,7 +107,7 @@
/obj/machinery/bodyscanner/verb/eject()
set src in oview(1)
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Eject Body Scanner"
if(usr.incapacitated())
diff --git a/code/game/machinery/bioprinter.dm b/code/game/machinery/bioprinter.dm
index 5c27edd487b8..2b89e6e6b2e8 100644
--- a/code/game/machinery/bioprinter.dm
+++ b/code/game/machinery/bioprinter.dm
@@ -187,7 +187,7 @@
/obj/machinery/organ_printer/verb/eject_beaker()
set name = "Eject Beaker"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr.stat != NONE)
diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm
index 87610114a575..d041ea1fc64d 100644
--- a/code/game/machinery/cloning.dm
+++ b/code/game/machinery/cloning.dm
@@ -305,7 +305,7 @@
/obj/machinery/clonepod/verb/eject()
set name = "Eject Cloner"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr.stat != 0)
@@ -377,7 +377,7 @@
// Empties all of the beakers from the cloning pod, used to refill it
/obj/machinery/clonepod/verb/empty_beakers()
set name = "Eject Beakers"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr.stat != 0)
diff --git a/code/game/machinery/computer/law.dm b/code/game/machinery/computer/law.dm
index 0b78a2ed1e77..f6d34ea319fd 100644
--- a/code/game/machinery/computer/law.dm
+++ b/code/game/machinery/computer/law.dm
@@ -10,7 +10,7 @@
/obj/machinery/computer/aiupload/verb/AccessInternals()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Access Computer's Internals"
set src in oview(1)
if(get_dist(src, usr) > 1 || usr.restrained() || usr.lying || usr.stat || istype(usr, /mob/living/silicon))
diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm
index 1867b9da4302..40f65b2fb923 100644
--- a/code/game/machinery/computer/medical.dm
+++ b/code/game/machinery/computer/medical.dm
@@ -72,7 +72,7 @@
return ..()
/obj/machinery/computer/med_data/verb/eject_id()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Eject ID Card"
set src in oview(1)
diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm
index edfeb3565c32..981fc2fdfd99 100644
--- a/code/game/machinery/computer/security.dm
+++ b/code/game/machinery/computer/security.dm
@@ -58,7 +58,7 @@
return ..()
/obj/machinery/computer/secure_data/verb/eject_id()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Eject ID Card"
set src in oview(1)
diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm
index b574db389871..eb4321638e1f 100644
--- a/code/game/machinery/cryo.dm
+++ b/code/game/machinery/cryo.dm
@@ -69,6 +69,7 @@
temperature_archived = air_contents.temperature
heat_gas_contents()
expel_gas()
+ update_icon()
if(abs(temperature_archived-air_contents.temperature) > 1)
network.update = TRUE
@@ -214,6 +215,7 @@
/obj/machinery/atmospherics/component/unary/cryo_cell/update_icon()
cut_overlay(fluid)
fluid.color = null
+ fluid.alpha = max(255 - air_contents.temperature, 50)
if(on)
if(beaker)
fluid.color = beaker.reagents.get_color()
@@ -321,7 +323,7 @@
/obj/machinery/atmospherics/component/unary/cryo_cell/verb/move_eject()
set name = "Eject occupant"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr == occupant)//If the user is inside the tube...
if(usr.stat == 2)//and he's not dead....
@@ -340,7 +342,7 @@
/obj/machinery/atmospherics/component/unary/cryo_cell/verb/move_inside()
set name = "Move Inside"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(isliving(usr))
var/mob/living/L = usr
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index 52b0754c80fb..0e49fb098544 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -555,7 +555,7 @@
/obj/machinery/cryopod/verb/eject()
set name = "Eject Pod"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr.stat != 0)
return
@@ -581,7 +581,7 @@
/obj/machinery/cryopod/verb/move_inside()
set name = "Enter Pod"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr.stat != 0 || !check_occupant_allowed(usr))
diff --git a/code/game/machinery/iv_drip.dm b/code/game/machinery/iv_drip.dm
index 023687111b6c..65c62f2eb074 100644
--- a/code/game/machinery/iv_drip.dm
+++ b/code/game/machinery/iv_drip.dm
@@ -260,7 +260,7 @@
return reagent_container?.reagents
/obj/machinery/iv_drip/verb/eject_beaker()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Remove IV Container"
set src in oview(1)
@@ -280,7 +280,7 @@
update_appearance()
/obj/machinery/iv_drip/verb/toggle_mode()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Toggle Mode"
set src in oview(1)
diff --git a/code/game/machinery/nuclear_bomb.dm b/code/game/machinery/nuclear_bomb.dm
index ab29e0b796fd..2cd56489cbdd 100644
--- a/code/game/machinery/nuclear_bomb.dm
+++ b/code/game/machinery/nuclear_bomb.dm
@@ -213,7 +213,7 @@ var/bomb_set
onclose(user, "nukebomb_hack")
/obj/machinery/nuclearbomb/verb/make_deployable()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Make Deployable"
set src in oview(1)
diff --git a/code/game/machinery/oxygen_pump.dm b/code/game/machinery/oxygen_pump.dm
index 331d22d233ba..3f1e3fcba9cd 100644
--- a/code/game/machinery/oxygen_pump.dm
+++ b/code/game/machinery/oxygen_pump.dm
@@ -173,7 +173,7 @@
//Create rightclick to view tank settings
/obj/machinery/oxygen_pump/verb/settings()
set src in oview(1)
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Show Tank Settings"
nano_ui_interact(usr)
diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm
index 1d33af8d1ad3..d63fd7c04691 100644
--- a/code/game/machinery/pipe/construction.dm
+++ b/code/game/machinery/pipe/construction.dm
@@ -100,7 +100,7 @@ Buildable meters
icon_state = initial(fakeA.pipe_state)
/obj/item/pipe/verb/flip()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Flip Pipe"
set src in view(1)
@@ -122,7 +122,7 @@ Buildable meters
icon_state = "[initial(fakeA.pipe_state)][mirrored ? "m" : ""]"
/obj/item/pipe/verb/rotate_clockwise()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Rotate Pipe Clockwise"
set src in view(1)
diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm
index 958c142cccc1..e7a0d712e6f1 100644
--- a/code/game/machinery/rechargestation.dm
+++ b/code/game/machinery/rechargestation.dm
@@ -282,7 +282,7 @@
update_appearance()
/obj/machinery/recharge_station/verb/move_eject()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Eject Recharger"
set src in oview(1)
@@ -294,7 +294,7 @@
return
/obj/machinery/recharge_station/verb/move_inside()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Enter Recharger"
set src in oview(1)
diff --git a/code/game/machinery/suit_storage/suit_cycler.dm b/code/game/machinery/suit_storage/suit_cycler.dm
index ed8a64779beb..dbdc44a1ea6a 100644
--- a/code/game/machinery/suit_storage/suit_cycler.dm
+++ b/code/game/machinery/suit_storage/suit_cycler.dm
@@ -434,7 +434,7 @@
/obj/machinery/suit_cycler/verb/leave()
set name = "Eject Cycler"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr.stat != 0)
diff --git a/code/game/machinery/suit_storage/suit_storage_unit.dm b/code/game/machinery/suit_storage/suit_storage_unit.dm
index 7698d7d9cb65..a1b8ff57cca6 100644
--- a/code/game/machinery/suit_storage/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage/suit_storage_unit.dm
@@ -409,7 +409,7 @@
/obj/machinery/suit_storage_unit/verb/get_out()
set name = "Eject Suit Storage Unit"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr.stat != 0)
@@ -423,7 +423,7 @@
/obj/machinery/suit_storage_unit/verb/move_inside()
set name = "Hide in Suit Storage Unit"
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set src in oview(1)
if(usr.stat != 0)
diff --git a/code/game/machinery/telecomms/broadcaster.dm b/code/game/machinery/telecomms/broadcaster.dm
index 178cf7e403f0..3aadb57ee041 100644
--- a/code/game/machinery/telecomms/broadcaster.dm
+++ b/code/game/machinery/telecomms/broadcaster.dm
@@ -246,14 +246,12 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
for (var/mob/R in receive)
/* --- Loop through the receivers and categorize them --- */
- if(!R.is_preference_enabled(/datum/client_preference/holder/hear_radio))
- continue
if(istype(R, /mob/new_player)) // we don't want new players to hear messages. rare but generates runtimes.
continue
// Ghosts hearing all radio chat don't want to hear syndicate intercepts, they're duplicates
- if(data == DATA_ANTAG && istype(R, /mob/observer/dead) && R.is_preference_enabled(/datum/client_preference/ghost_radio))
+ if(data == DATA_ANTAG && istype(R, /mob/observer/dead) && R.get_preference_toggle(/datum/game_preference_toggle/observer/ghost_radio))
continue
// --- Check for compression ---
@@ -446,10 +444,6 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
/* --- Loop through the receivers and categorize them --- */
- if(!R.is_preference_enabled(/datum/client_preference/holder/hear_radio)) //Adminning with 80 people on can be fun when you're trying to talk and all you can hear is radios.
- continue
-
-
// --- Check for compression ---
if(compression > 0)
diff --git a/code/game/machinery/teleporter/console.dm b/code/game/machinery/teleporter/console.dm
index 6b5a96a44705..d2c7551f9240 100644
--- a/code/game/machinery/teleporter/console.dm
+++ b/code/game/machinery/teleporter/console.dm
@@ -190,7 +190,7 @@
playsound(get_turf(src), 'sound/machines/ping.ogg', 50, 0)
/obj/machinery/computer/teleporter/verb/set_id(t as text)
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Set teleporter ID"
set src in oview(1)
set desc = "ID Tag:"
diff --git a/code/game/machinery/transportpod.dm b/code/game/machinery/transportpod.dm
index aab18a872520..e0718e9210ac 100644
--- a/code/game/machinery/transportpod.dm
+++ b/code/game/machinery/transportpod.dm
@@ -79,7 +79,7 @@
update_icon()
/obj/machinery/transportpod/verb/move_eject()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Eject Pod"
set src in oview(1)
@@ -91,7 +91,7 @@
return
/obj/machinery/transportpod/verb/move_inside()
- set category = "Object"
+ set category = VERB_CATEGORY_OBJECT
set name = "Enter Pod"
set src in oview(1)
diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm
deleted file mode 100644
index 9d2abe72bc2a..000000000000
--- a/code/game/machinery/vending.dm
+++ /dev/null
@@ -1,2569 +0,0 @@
-/**
- * A vending machine
- */
-/obj/machinery/vending
- name = "Vendomat"
- desc = "A generic vending machine."
- icon = 'icons/obj/vending.dmi'
- icon_state = "generic"
- anchored = TRUE
- density = TRUE
- // todo: temporary, as this is unbuildable
- integrity_flags = INTEGRITY_INDESTRUCTIBLE
-
-//! ## Icons
- /// Icon_state when vending.
- var/icon_vend
- /// Icon_state when denying access.
- var/icon_deny
-
-//! ## Power
- use_power = USE_POWER_IDLE
- idle_power_usage = 10
- var/vend_power_usage = 150 //actuators and stuff
-
-//! ## Vending-related
- /// No sales pitches if off!
- var/active = TRUE
- /// Are we ready to vend?? Is it time??
- var/vend_ready = TRUE
- /// How long does it take to vend?
- var/vend_delay = 1 SECOND
- /// Bitmask of categories we're currently showing.
- var/categories = CAT_NORMAL
- /// What we're requesting payment for right now.
- var/datum/stored_item/vending_product/currently_vending = null
- /// Status screen messages like "insufficient funds", displayed in NanoUI.
- var/status_message = ""
- /// Set to TRUE if status_message is an error.
- var/status_error = FALSE
-
- /**
- * Variables used to initialize the product list
- * These are used for initialization only, and so are optional if
- * product_records is specified
- */
- var/list/products = list() // For each, use the following pattern:
- var/list/contraband = list() // list(/type/path = amount, /type/path2 = amount2)
- var/list/premium = list() // No specified amount = only one in stock
- var/list/prices = list() // Prices for each item, list(/type/path = price), items not in the list don't have a price.
- var/price_default = 0
-
- /// List of vending_product items available.
- var/list/product_records = list()
-
-
- // Variables used to initialize advertising
- /// String of slogans spoken out loud, separated by semicolons.
- var/product_slogans = ""
- /// String of small ad messages in the vending screen.
- var/product_ads = ""
-
- var/list/ads_list = list()
-
- // Stuff relating vocalizations
- var/list/slogan_list = list()
- /// Set to true to silence it.
- var/shut_up = TRUE
- /// Thank you for shopping!
- var/vend_reply
- var/last_reply = FALSE
- /// When did we last pitch?
- var/last_slogan = 0
- /// How long until we can pitch again?
- var/slogan_delay = 10 MINUTES
-
- // Things that can go wrong
- /// Ignores if somebody doesn't have card access to that machine.
- emagged = FALSE
- /// Shock customers like an airlock.
- var/seconds_electrified = 0
- /// Fire items at customers! We're broken!
- var/shoot_inventory = FALSE
-
- var/scan_id = TRUE
- var/obj/item/coin/coin
- var/datum/wires/vending/wires = null
-
- var/list/log = list()
-
- /// Default access for checking logs is cargo.
- var/req_log_access = ACCESS_SUPPLY_BAY
- /// Defaults to 0, set to anything else for vendor to have logs.
- var/has_logs = NONE
-
-
-/obj/machinery/vending/Initialize(mapload)
- . = ..()
- wires = new(src)
- spawn(4)
- if(product_slogans)
- slogan_list += splittext(product_slogans, ";")
-
- // So not all machines speak at the exact same time.
- // The first time this machine says something will be at slogantime + this random value,
- // so if slogantime is 10 minutes, it will say it at somewhere between 10 and 20 minutes after the machine is crated.
- last_slogan = world.time + rand(0, slogan_delay)
-
- if(product_ads)
- ads_list += splittext(product_ads, ";")
-
- build_inventory()
- power_change()
-
-/**
- * Build produdct_records from the products lists
- *
- * products, contraband, premium, and prices allow specifying
- * products that the vending machine is to carry without manually populating
- * product_records.
- */
-/obj/machinery/vending/proc/build_inventory()
- var/list/all_products = list(
- list(products, CAT_NORMAL),
- list(contraband, CAT_HIDDEN),
- list(premium, CAT_COIN))
-
- for(var/current_list in all_products)
- var/category = current_list[2]
-
- for(var/entry in current_list[1])
- var/datum/stored_item/vending_product/product = new/datum/stored_item/vending_product(src, entry)
-
- product.price = (entry in prices) ? prices[entry] : price_default
- product.amount = (current_list[1][entry]) ? current_list[1][entry] : 1
- product.category = category
-
- product_records.Add(product)
-
-/obj/machinery/vending/Destroy()
- qdel(wires)
- wires = null
- qdel(coin)
- coin = null
- for(var/datum/stored_item/vending_product/R in product_records)
- qdel(R)
- product_records = null
- return ..()
-
-/obj/machinery/vending/legacy_ex_act(severity)
- switch(severity)
- if(1.0)
- qdel(src)
- return
- if(2.0)
- if(prob(50))
- qdel(src)
- return
- if(3.0)
- if(prob(25))
- spawn(0)
- malfunction()
- return
- return
- else
- return
-
-/obj/machinery/vending/emag_act(var/remaining_charges, var/mob/user)
- if(!emagged)
- emagged = 1
- to_chat(user, "You short out \the [src]'s product lock.")
- return 1
-
-/obj/machinery/vending/attackby(obj/item/W, mob/user)
- var/obj/item/card/id/I = W.GetID()
-
- if(currently_vending && GLOB.vendor_account && !GLOB.vendor_account.suspended)
- var/paid = FALSE
- var/handled = FALSE
-
- var/obj/item/paying_with = I || W
- var/list/data = list()
- var/amount = paying_with.attempt_use_currency(user, src, currently_vending.price, FALSE, NONE, data, FALSE, 7)
- switch(amount)
- if(PAYMENT_DYNAMIC_ERROR)
- if(data[DYNAMIC_PAYMENT_DATA_FAIL_REASON])
- status_message = data[DYNAMIC_PAYMENT_DATA_FAIL_REASON]
- status_error = TRUE
- SSnanoui.update_uis(src)
- return
- if(PAYMENT_NOT_CURRENCY)
- handled = FALSE
- if(PAYMENT_INSUFFICIENT)
- handled = TRUE
- to_chat(user, SPAN_WARNING("That is not enough money!"))
- else
- handled = TRUE
- paid = amount == currently_vending.price
-
- if(handled)
- if(paid)
- var/payer_name = "Unknown"
- switch(data[DYNAMIC_PAYMENT_DATA_CURRENCY_TYPE])
- if(PAYMENT_TYPE_BANK_CARD)
- var/datum/money_account/A = data[DYNAMIC_PAYMENT_DATA_BANK_ACCOUNT]
- if(A)
- payer_name = A.owner_name
- else
- payer_name = "(cash)"
- credit_purchase(payer_name)
- vend(currently_vending, usr)
- SSnanoui.update_uis(src)
- return // don't smack that machine with your 2 thalers
-
- if(I || istype(W, /obj/item/spacecash))
- attack_hand(user)
- return
- else if(W.is_screwdriver())
- panel_open = !panel_open
- to_chat(user, "You [panel_open ? "open" : "close"] the maintenance panel.")
- playsound(src, W.tool_sound, 50, 1)
- cut_overlays()
- if(panel_open)
- add_overlay(image(icon, "[initial(icon_state)]-panel"))
-
- SSnanoui.update_uis(src) // Speaker switch is on the main UI, not wires UI
- return
- else if(istype(W, /obj/item/multitool) || W.is_wirecutter())
- if(panel_open)
- attack_hand(user)
- return
- else if(istype(W, /obj/item/coin) && premium.len > 0)
- if(!user.attempt_insert_item_for_installation(W, src))
- return
- coin = W
- categories |= CAT_COIN
- to_chat(user, "You insert \the [W] into \the [src].")
- SSnanoui.update_uis(src)
- return
- else if(W.is_wrench())
- playsound(src, W.tool_sound, 100, 1)
- if(anchored)
- user.visible_message("[user] begins unsecuring \the [src] from the floor.", "You start unsecuring \the [src] from the floor.")
- else
- user.visible_message("[user] begins securing \the [src] to the floor.", "You start securing \the [src] to the floor.")
-
- if(do_after(user, 20 * W.tool_speed))
- if(!src) return
- to_chat(user, "You [anchored? "un" : ""]secured \the [src]!")
- anchored = !anchored
- return
- else
-
- for(var/datum/stored_item/vending_product/R in product_records)
- if(istype(W, R.item_path) && (W.name == R.item_name))
- stock(W, R, user)
- return
- ..()
-
-/obj/machinery/vending/query_transaction_details(list/data)
- . = ..()
- .[CHARGE_DETAIL_DEVICE] = name
- .[CHARGE_DETAIL_LOCATION] = get_area(src).name
- .[CHARGE_DETAIL_REASON] = currently_vending? "Purchase of [currently_vending.item_name]" : "Unknown"
- .[CHARGE_DETAIL_RECIPIENT] = GLOB.vendor_account.owner_name
-
-/**
- * Add money for current purchase to the vendor account.
- *
- * Called after the money has already been taken from the customer.
- */
-/obj/machinery/vending/proc/credit_purchase(var/target as text)
- GLOB.vendor_account.money += currently_vending.price
-
- var/datum/transaction/T = new()
- T.target_name = target
- T.purpose = "Purchase of [currently_vending.item_name]"
- T.amount = "[currently_vending.price]"
- T.source_terminal = name
- T.date = GLOB.current_date_string
- T.time = stationtime2text()
- GLOB.vendor_account.transaction_log.Add(T)
-
-/obj/machinery/vending/attack_ai(mob/user as mob)
- return attack_hand(user)
-
-/obj/machinery/vending/attack_hand(mob/user, list/params)
- if(machine_stat & (BROKEN|NOPOWER))
- return
-
- if(seconds_electrified != 0)
- if(shock(user, 100))
- return
-
- wires.Interact(user)
- nano_ui_interact(user)
-
-/**
- * Display the NanoUI window for the vending machine.
- *
- * See NanoUI documentation for details.
- */
-/obj/machinery/vending/nano_ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- user.set_machine(src)
-
- var/list/data = list()
- if(currently_vending)
- data["mode"] = 1
- data["product"] = currently_vending.item_name
- data["price"] = currently_vending.price
- data["message_err"] = 0
- data["message"] = status_message
- data["message_err"] = status_error
- else
- data["mode"] = 0
- var/list/listed_products = list()
-
- for(var/key = 1 to product_records.len)
- var/datum/stored_item/vending_product/I = product_records[key]
-
- if(!(I.category & categories))
- continue
-
- listed_products.Add(list(list(
- "key" = key,
- "name" = I.item_name,
- "price" = I.price,
- "color" = I.display_color,
- "amount" = I.get_amount())))
-
- data["products"] = listed_products
-
- if(coin)
- data["coin"] = coin.name
-
- if(panel_open)
- data["panel"] = 1
- data["speaker"] = shut_up ? 0 : 1
- else
- data["panel"] = 0
-
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "vending_machine.tmpl", name, 440, 600)
- ui.set_initial_data(data)
- ui.open()
-
-/obj/machinery/vending/Topic(href, href_list)
- if(machine_stat & (BROKEN|NOPOWER))
- return
- if(!IsAdminGhost(usr) && (usr.stat || usr.restrained()))
- return
-
- if(href_list["remove_coin"] && !istype(usr,/mob/living/silicon))
- if(!coin)
- to_chat(usr, "There is no coin in this machine.")
- return
-
- coin.forceMove(src.loc)
- if(!usr.get_active_held_item())
- usr.put_in_hands(coin)
- to_chat(usr, "You remove \the [coin] from \the [src]")
- coin = null
- categories &= ~CAT_COIN
-
- if(IsAdminGhost(usr) || (usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))))
- if((href_list["vend"]) && (vend_ready) && (!currently_vending))
- if((!allowed(usr)) && !emagged && scan_id) //For SECURE VENDING MACHINES YEAH
- to_chat(usr, "Access denied.") //Unless emagged of course
- flick(icon_deny,src)
- playsound(src.loc, 'sound/machines/deniedbeep.ogg', 50, 0)
- return
-
- var/key = text2num(href_list["vend"])
- var/datum/stored_item/vending_product/R = product_records[key]
-
- // This should not happen unless the request from NanoUI was bad
- if(!(R.category & categories))
- return
-
- if((R.price <= 0) || IsAdminGhost(usr))
- vend(R, usr)
- else if(istype(usr,/mob/living/silicon)) //If the item is not free, provide feedback if a synth is trying to buy something.
- to_chat(usr, "Lawed unit recognized. Lawed units cannot complete this transaction. Purchase canceled.")
- return
- else
- currently_vending = R
- if(!GLOB.vendor_account || GLOB.vendor_account.suspended)
- status_message = "This machine is currently unable to process payments due to issues with the associated account."
- status_error = 1
- else
- status_message = "Please swipe a card or insert cash to pay for the item."
- status_error = 0
-
- else if(href_list["cancelpurchase"])
- currently_vending = null
-
- else if((href_list["togglevoice"]) && (panel_open))
- shut_up = !shut_up
-
- SSnanoui.update_uis(src)
-
-/obj/machinery/vending/proc/vend(datum/stored_item/vending_product/R, mob/user)
- if((!allowed(usr)) && !emagged && scan_id) //For SECURE VENDING MACHINES YEAH
- to_chat(usr, "Access denied.") //Unless emagged of course
- flick(icon_deny,src)
- playsound(src.loc, 'sound/machines/deniedbeep.ogg', 50, 0)
- return
- vend_ready = 0 //One thing at a time!!
- status_message = "Vending..."
- status_error = 0
- SSnanoui.update_uis(src)
-
- if(R.category & CAT_COIN)
- if(!coin)
- to_chat(user, "You need to insert a coin to get this item.")
- return
- if(coin.string_attached)
- if(prob(50))
- to_chat(user, "You successfully pull the coin out before \the [src] could swallow it.")
- else
- to_chat(user, "You weren't able to pull the coin out fast enough, the machine ate it, string and all.")
- qdel(coin)
- coin = null
- categories &= ~CAT_COIN
- else
- qdel(coin)
- coin = null
- categories &= ~CAT_COIN
-
- if(((last_reply + (vend_delay + 200)) <= world.time) && vend_reply)
- spawn(0)
- speak(vend_reply)
- last_reply = world.time
-
- use_power(vend_power_usage) //actuators and stuff
- if(icon_vend) //Show the vending animation if needed
- flick(icon_vend,src)
- spawn(vend_delay)
- R.get_product(get_turf(src))
- if(has_logs)
- do_logging(R, user, 1)
- if(prob(1))
- sleep(3)
- if(R.get_product(get_turf(src)))
- visible_message("\The [src] clunks as it vends an additional item.")
-
- playsound(src, 'sound/items/vending.ogg', 50, 1, 1)
-
- status_message = ""
- status_error = 0
- vend_ready = 1
- currently_vending = null
- SSnanoui.update_uis(src)
-
- return 1
-
-/obj/machinery/vending/proc/do_logging(datum/stored_item/vending_product/R, mob/user, var/vending = 0)
- if(user.GetIdCard())
- var/obj/item/card/id/tempid = user.GetIdCard()
- var/list/list_item = list()
- if(vending)
- list_item += "vend"
- else
- list_item += "stock"
- list_item += tempid.registered_name
- list_item += stationtime2text()
- list_item += R.item_name
- log[++log.len] = list_item
-
-/obj/machinery/vending/proc/show_log(mob/user as mob)
- if(user.GetIdCard())
- var/obj/item/card/id/tempid = user.GetIdCard()
- if(req_log_access in tempid.GetAccess())
- var/datum/browser/popup = new(user, "vending_log", "Vending Log", 700, 500)
- var/dat = ""
- dat += "
Signed stats gathering"
- output += "
Pick this option if you think usernames should be logged with stats. This allows us to have personalized stats as well as polls."
-
- output += "
Anonymous stats gathering"
- output += "
Pick this option if you think only hashed (indecipherable) usernames should be logged with stats. This doesn't allow us to have personalized stats, as we can't tell who is who (hashed values aren't readable), we can however have ingame polls."
-
- output += "
No stats gathering"
- output += "
Pick this option if you don't want player-specific stats gathered. This does not allow us to have player-specific stats or polls."
-
- output += "
Ask again later"
- output += "
This poll will be brought up again next round."
-
- output += "
Don't ask again"
- output += "
Only pick this if you are fine with whatever option wins."
-
- output += "
__ |
[client_pref.description]: | " - if(pref_mob.is_preference_enabled(client_pref.key)) - . += "[client_pref.enabled_description] | [client_pref.disabled_description] | " - else - . += "[client_pref.enabled_description] | [client_pref.disabled_description] | " - . += "