diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 678486f019..1b5698ba5e 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -4,8 +4,9 @@
# In the event that multiple org members are to be informed of changes
# to the same file or dir, add them to the end under Multiple Owners
-# ShadowLarkens
-/code/__DEFINES/tgui.dm @ShadowLarkens
-/code/controllers/subsystem/tgui.dm @ShadowLarkens
-/code/modules/tgui @ShadowLarkens
-/tgui @ShadowLarkens
\ No newline at end of file
+# ItsSelis
+
+/code/__DEFINES/tgui.dm @ItsSelis
+/code/controllers/subsystem/tgui.dm @ItsSelis
+/code/modules/tgui @ItsSelis
+/tgui @ItsSelis
diff --git a/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm b/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm
index 7b8b7155f6..686efa6508 100644
--- a/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm
+++ b/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm
@@ -261,7 +261,7 @@
if("max")
target_pressure = max_pressure_setting
if("set")
- var/new_pressure = input(usr,"Enter new output pressure (0-[max_pressure_setting]kPa)","Pressure Control",src.target_pressure) as num
+ var/new_pressure = tgui_input_number(usr,"Enter new output pressure (0-[max_pressure_setting]kPa)","Pressure Control",src.target_pressure,max_pressure_setting,0)
src.target_pressure = between(0, new_pressure, max_pressure_setting)
if("set_flow_rate")
@@ -272,7 +272,7 @@
if("max")
set_flow_rate = air1.volume
if("set")
- var/new_flow_rate = input(usr,"Enter new flow rate limit (0-[air1.volume]L/s)","Flow Rate Control",src.set_flow_rate) as num
+ var/new_flow_rate = tgui_input_number(usr,"Enter new flow rate limit (0-[air1.volume]L/s)","Flow Rate Control",src.set_flow_rate,air1.volume,0)
src.set_flow_rate = between(0, new_flow_rate, air1.volume)
update_icon()
diff --git a/code/ATMOSPHERICS/components/binary_devices/pump.dm b/code/ATMOSPHERICS/components/binary_devices/pump.dm
index 665d912b00..514216e7fa 100644
--- a/code/ATMOSPHERICS/components/binary_devices/pump.dm
+++ b/code/ATMOSPHERICS/components/binary_devices/pump.dm
@@ -225,7 +225,7 @@ Thus, the two variables affect pump operation are set in New():
if("max")
target_pressure = max_pressure_setting
if("set")
- var/new_pressure = input(usr,"Enter new output pressure (0-[max_pressure_setting]kPa)","Pressure control",src.target_pressure) as num
+ var/new_pressure = tgui_input_number(usr,"Enter new output pressure (0-[max_pressure_setting]kPa)","Pressure control",src.target_pressure,max_pressure_setting,0)
src.target_pressure = between(0, new_pressure, max_pressure_setting)
. = TRUE
diff --git a/code/ATMOSPHERICS/components/omni_devices/filter.dm b/code/ATMOSPHERICS/components/omni_devices/filter.dm
index dae4648790..1b51693389 100644
--- a/code/ATMOSPHERICS/components/omni_devices/filter.dm
+++ b/code/ATMOSPHERICS/components/omni_devices/filter.dm
@@ -170,7 +170,7 @@
if("set_flow_rate")
if(!configuring || use_power)
return
- var/new_flow_rate = input(usr,"Enter new flow rate limit (0-[max_flow_rate]L/s)","Flow Rate Control",set_flow_rate) as num
+ var/new_flow_rate = tgui_input_number(usr,"Enter new flow rate limit (0-[max_flow_rate]L/s)","Flow Rate Control",set_flow_rate,max_flow_rate,0)
set_flow_rate = between(0, new_flow_rate, max_flow_rate)
. = TRUE
if("switch_mode")
diff --git a/code/ATMOSPHERICS/components/omni_devices/mixer.dm b/code/ATMOSPHERICS/components/omni_devices/mixer.dm
index 077be63b30..3487482be3 100644
--- a/code/ATMOSPHERICS/components/omni_devices/mixer.dm
+++ b/code/ATMOSPHERICS/components/omni_devices/mixer.dm
@@ -183,7 +183,7 @@
. = TRUE
if(!configuring || use_power)
return
- var/new_flow_rate = input(usr,"Enter new flow rate limit (0-[max_flow_rate]L/s)","Flow Rate Control",set_flow_rate) as num
+ var/new_flow_rate = tgui_input_number(usr,"Enter new flow rate limit (0-[max_flow_rate]L/s)","Flow Rate Control",set_flow_rate,max_flow_rate,0)
set_flow_rate = between(0, new_flow_rate, max_flow_rate)
if("switch_mode")
. = TRUE
@@ -265,7 +265,7 @@
if(non_locked < 1)
return
- var/new_con = (input(usr,"Enter a new concentration (0-[round(remain_con * 100, 0.5)])%","Concentration control", min(remain_con, old_con)*100) as num) / 100
+ var/new_con = (tgui_input_number(usr,"Enter a new concentration (0-[round(remain_con * 100, 0.5)])%","Concentration control", min(remain_con, old_con)*100, round(remain_con * 100, 0.5), 0)) / 100
//cap it between 0 and the max remaining concentration
new_con = between(0, new_con, remain_con)
diff --git a/code/ZAS/Variable Settings.dm b/code/ZAS/Variable Settings.dm
index 067fc6d1d8..c4643632dc 100644
--- a/code/ZAS/Variable Settings.dm
+++ b/code/ZAS/Variable Settings.dm
@@ -147,7 +147,7 @@ var/global/vs_control/vsc = new
var/newvar = vw
switch(how)
if("Numeric")
- newvar = input(user,"Enter a number:","Settings",newvar) as num
+ newvar = tgui_input_number(user,"Enter a number:","Settings",newvar)
if("Bit Flag")
var/flag = tgui_input_list(user,"Toggle which bit?","Settings", bitflags)
flag = text2num(flag)
@@ -158,9 +158,9 @@ var/global/vs_control/vsc = new
if("Toggle")
newvar = !newvar
if("Text")
- newvar = input(user,"Enter a string:","Settings",newvar) as text
+ newvar = tgui_input_text(user,"Enter a string:","Settings",newvar)
if("Long Text")
- newvar = input(user,"Enter text:","Settings",newvar) as message
+ newvar = tgui_input_text(user,"Enter text:","Settings",newvar, multiline = TRUE)
vw = newvar
if(ch in plc.settings)
plc.vars[ch] = vw
diff --git a/code/__defines/cooldowns.dm b/code/__defines/cooldowns.dm
new file mode 100644
index 0000000000..c3855d7ab3
--- /dev/null
+++ b/code/__defines/cooldowns.dm
@@ -0,0 +1,15 @@
+/*
+ * Cooldown system based on storing world.time on a variable, plus the cooldown time.
+ * Better performance over timer cooldowns, lower control. Same functionality.
+*/
+
+#define COOLDOWN_DECLARE(cd_index) var/##cd_index = 0
+
+#define COOLDOWN_START(cd_source, cd_index, cd_time) (cd_source.cd_index = world.time + (cd_time))
+
+//Returns true if the cooldown has run its course, false otherwise
+#define COOLDOWN_FINISHED(cd_source, cd_index) (cd_source.cd_index < world.time)
+
+#define COOLDOWN_RESET(cd_source, cd_index) cd_source.cd_index = 0
+
+#define COOLDOWN_TIMELEFT(cd_source, cd_index) (max(0, cd_source.cd_index - world.time))
\ No newline at end of file
diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm
index d9a5f7a847..df7a90632c 100644
--- a/code/__defines/mobs.dm
+++ b/code/__defines/mobs.dm
@@ -447,3 +447,7 @@
#define DEATHGASP_NO_MESSAGE "no message"
#define RESIST_COOLDOWN 2 SECONDS
+
+#define VISIBLE_GENDER_FORCE_PLURAL 1 // Used by get_visible_gender to return PLURAL
+#define VISIBLE_GENDER_FORCE_IDENTIFYING 2 // Used by get_visible_gender to return the mob's identifying gender
+#define VISIBLE_GENDER_FORCE_BIOLOGICAL 3 // Used by get_visible_gender to return the mob's biological gender
diff --git a/code/__defines/tgui.dm b/code/__defines/tgui.dm
index c261e5ecdc..1215382ce7 100644
--- a/code/__defines/tgui.dm
+++ b/code/__defines/tgui.dm
@@ -5,6 +5,8 @@
/// Maximum ping timeout allowed to detect zombie windows
#define TGUI_PING_TIMEOUT 4 SECONDS
+/// Used for rate-limiting to prevent DoS by excessively refreshing a TGUI window
+#define TGUI_REFRESH_FULL_UPDATE_COOLDOWN 5 SECONDS
/// Window does not exist
#define TGUI_WINDOW_CLOSED 0
diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm
index 6bf02bf77f..333f2edc15 100644
--- a/code/_helpers/text.dm
+++ b/code/_helpers/text.dm
@@ -481,20 +481,23 @@
/// Used to get a properly sanitized input, of max_length
/// no_trim is self explanatory but it prevents the input from being trimed if you intend to parse newlines or whitespace.
/proc/stripped_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE)
- var/name = input(user, message, title, default) as text|null
-
+ var/user_input = input(user, message, title, default) as text|null
+ if(isnull(user_input))
+ return
if(no_trim)
- return copytext(html_encode(name), 1, max_length)
+ return copytext(html_encode(user_input), 1, max_length)
else
- return trim(html_encode(name), max_length) //trim is "outside" because html_encode can expand single symbols into multiple symbols (such as turning < into <)
+ return trim(html_encode(user_input), max_length) //trim is "outside" because html_encode can expand single symbols into multiple symbols (such as turning < into <)
// Used to get a properly sanitized multiline input, of max_length
/proc/stripped_multiline_input(mob/user, message = "", title = "", default = "", max_length=MAX_MESSAGE_LEN, no_trim=FALSE)
- var/name = input(user, message, title, default) as message|null
+ var/user_input = input(user, message, title, default) as message|null
+ if(isnull(user_input))
+ return
if(no_trim)
- return copytext(html_encode(name), 1, max_length)
+ return copytext(html_encode(user_input), 1, max_length)
else
- return trim(html_encode(name), max_length)
+ return trim(html_encode(user_input), max_length)
//Adds 'char' ahead of 'text' until there are 'count' characters total
/proc/add_leading(text, count, char = " ")
diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm
index c8bce2d2f9..c1d5cbcaef 100644
--- a/code/_helpers/unsorted.dm
+++ b/code/_helpers/unsorted.dm
@@ -344,7 +344,7 @@ Turf and target are seperate in case you want to teleport some distance from a t
var/newname
for(var/i=1,i<=3,i++) //we get 3 attempts to pick a suitable name.
- newname = input(src,"You are \a [role]. Would you like to change your name to something else?", "Name change",oldname) as text
+ newname = tgui_input_text(src,"You are \a [role]. Would you like to change your name to something else?", "Name change",oldname)
if((world.time-time_passed)>3000)
return //took too long
newname = sanitizeName(newname, ,allow_numbers) //returns null if the name doesn't meet some basic requirements. Tidies up a few other things like bad-characters.
@@ -1357,7 +1357,7 @@ var/mob/dview/dview_mob = new
/proc/pick_closest_path(value, list/matches = get_fancy_list_of_atom_types())
if (value == FALSE) //nothing should be calling us with a number, so this is safe
- value = input(usr, "Enter type to find (blank for all, cancel to cancel)", "Search for type") as null|text
+ value = tgui_input_text(usr, "Enter type to find (blank for all, cancel to cancel)", "Search for type")
if (isnull(value))
return
value = trim(value)
diff --git a/code/_onclick/hud/movable_screen_objects.dm b/code/_onclick/hud/movable_screen_objects.dm
index 40e5977c83..39d36738aa 100644
--- a/code/_onclick/hud/movable_screen_objects.dm
+++ b/code/_onclick/hud/movable_screen_objects.dm
@@ -114,7 +114,7 @@
M.maptext = "Movable"
M.maptext_width = 64
- var/screen_l = input(usr,"Where on the screen? (Formatted as 'X,Y' e.g: '1,1' for bottom left)","Spawn Movable UI Object") as text
+ var/screen_l = tgui_input_text(usr,"Where on the screen? (Formatted as 'X,Y' e.g: '1,1' for bottom left)","Spawn Movable UI Object")
if(!screen_l)
return
@@ -133,7 +133,7 @@
S.maptext = "Snap"
S.maptext_width = 64
- var/screen_l = input(usr,"Where on the screen? (Formatted as 'X,Y' e.g: '1,1' for bottom left)","Spawn Snap UI Object") as text
+ var/screen_l = tgui_input_text(usr,"Where on the screen? (Formatted as 'X,Y' e.g: '1,1' for bottom left)","Spawn Snap UI Object")
if(!screen_l)
return
diff --git a/code/controllers/subsystems/game_master.dm b/code/controllers/subsystems/game_master.dm
index 3ac815c5d9..88a0ed6b16 100644
--- a/code/controllers/subsystems/game_master.dm
+++ b/code/controllers/subsystems/game_master.dm
@@ -352,13 +352,13 @@ SUBSYSTEM_DEF(game_master)
choose_game_master(usr)
if(href_list["set_staleness"])
- var/amount = input(usr, "How much staleness should there be?", "Game Master") as null|num
+ var/amount = tgui_input_number(usr, "How much staleness should there be?", "Game Master")
if(!isnull(amount))
staleness = amount
message_admins("GM staleness was set to [amount] by [usr.key].")
if(href_list["set_danger"])
- var/amount = input(usr, "How much danger should there be?", "Game Master") as null|num
+ var/amount = tgui_input_number(usr, "How much danger should there be?", "Game Master")
if(!isnull(amount))
danger = amount
message_admins("GM danger was set to [amount] by [usr.key].")
diff --git a/code/controllers/subsystems/media_tracks.dm b/code/controllers/subsystems/media_tracks.dm
index d85e4baa9e..534de10704 100644
--- a/code/controllers/subsystems/media_tracks.dm
+++ b/code/controllers/subsystems/media_tracks.dm
@@ -88,7 +88,7 @@ SUBSYSTEM_DEF(media_tracks)
return
// Required
- var/url = input(C, "REQUIRED: Provide URL for track, or paste JSON if you know what you're doing. See code comments.", "Track URL") as message|null
+ var/url = tgui_input_text(C, "REQUIRED: Provide URL for track, or paste JSON if you know what you're doing. See code comments.", "Track URL", multiline = TRUE)
if(!url)
return
@@ -126,21 +126,21 @@ SUBSYSTEM_DEF(media_tracks)
report_progress("New media track added by [C]: [T.title]")
sort_tracks()
return
-
- var/title = input(C, "REQUIRED: Provide title for track", "Track Title") as text|null
+
+ var/title = tgui_input_text(C, "REQUIRED: Provide title for track", "Track Title")
if(!title)
return
-
- var/duration = input(C, "REQUIRED: Provide duration for track (in deciseconds, aka seconds*10)", "Track Duration") as num|null
+
+ var/duration = tgui_input_number(C, "REQUIRED: Provide duration for track (in deciseconds, aka seconds*10)", "Track Duration")
if(!duration)
return
// Optional
- var/artist = input(C, "Optional: Provide artist for track", "Track Artist") as text|null
+ var/artist = tgui_input_text(C, "Optional: Provide artist for track", "Track Artist")
if(isnull(artist)) // Cancel rather than empty string
return
-
- var/genre = input(C, "Optional: Provide genre for track (try to match an existing one)", "Track Genre") as text|null
+
+ var/genre = tgui_input_text(C, "Optional: Provide genre for track (try to match an existing one)", "Track Genre")
if(isnull(genre)) // Cancel rather than empty string
return
@@ -188,7 +188,7 @@ SUBSYSTEM_DEF(media_tracks)
if(!check_rights(R_DEBUG|R_FUN))
return
- var/track = input(C, "Input track title or URL to remove (must be exact)", "Remove Track") as text|null
+ var/track = tgui_input_text(C, "Input track title or URL to remove (must be exact)", "Remove Track")
if(!track)
return
diff --git a/code/controllers/subsystems/supply.dm b/code/controllers/subsystems/supply.dm
index af096c33a8..c30d559e4b 100644
--- a/code/controllers/subsystems/supply.dm
+++ b/code/controllers/subsystems/supply.dm
@@ -363,15 +363,15 @@ SUBSYSTEM_DEF(supply)
// Will add an item entry to the specified export receipt on the user-side list
/datum/controller/subsystem/supply/proc/add_export_item(var/datum/exported_crate/E, var/mob/user)
- var/new_name = input(user, "Name", "Please enter the name of the item.") as null|text
+ var/new_name = tgui_input_text(user, "Name", "Please enter the name of the item.")
if(!new_name)
return
- var/new_quantity = input(user, "Name", "Please enter the quantity of the item.") as null|num
+ var/new_quantity = tgui_input_number(user, "Name", "Please enter the quantity of the item.")
if(!new_quantity)
return
- var/new_value = input(user, "Name", "Please enter the value of the item.") as null|num
+ var/new_value = tgui_input_number(user, "Name", "Please enter the value of the item.")
if(!new_value)
return
diff --git a/code/controllers/subsystems/vote.dm b/code/controllers/subsystems/vote.dm
index c1e4288869..44076a407b 100644
--- a/code/controllers/subsystems/vote.dm
+++ b/code/controllers/subsystems/vote.dm
@@ -244,11 +244,11 @@ SUBSYSTEM_DEF(vote)
choices.Add(antag.role_text)
choices.Add("None")
if(VOTE_CUSTOM)
- question = sanitizeSafe(input(usr, "What is the vote for?") as text|null)
+ question = sanitizeSafe(tgui_input_text(usr, "What is the vote for?"))
if(!question)
return 0
for(var/i = 1 to 10)
- var/option = capitalize(sanitize(input(usr, "Please enter an option or hit cancel to finish") as text|null))
+ var/option = capitalize(sanitize(tgui_input_text(usr, "Please enter an option or hit cancel to finish")))
if(!option || mode || !usr.client)
break
choices.Add(option)
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index 75c3c64c7f..8da8bab272 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -68,7 +68,7 @@
names += componentsubtypes
names += "---Elements---"
names += sortTim(subtypesof(/datum/element), /proc/cmp_typepaths_asc)
- var/result = input(usr, "Choose a component/element to add:", "Add Component/Element", names)
+ var/result = tgui_input_text(usr, "Choose a component/element to add:", "Add Component/Element", names)
if(!usr || !result || result == "---Components---" || result == "---Elements---")
return
if(QDELETED(src))
diff --git a/code/datums/managed_browsers/feedback_form.dm b/code/datums/managed_browsers/feedback_form.dm
index b380018bea..da7a028240 100644
--- a/code/datums/managed_browsers/feedback_form.dm
+++ b/code/datums/managed_browsers/feedback_form.dm
@@ -99,7 +99,7 @@ GENERAL_PROTECT_DATUM(/datum/managed_browser/feedback_form)
if(href_list["feedback_edit_body"])
// This is deliberately not sanitized here, and is instead checked when hitting the submission button,
// as we want to give the user a chance to fix it without needing to rewrite the whole thing.
- feedback_body = input(my_client, "Please write your feedback here.", "Feedback Body", feedback_body) as null|message
+ feedback_body = tgui_input_text(my_client, "Please write your feedback here.", "Feedback Body", feedback_body, multiline = TRUE)
display() // Refresh the window with new information.
return
diff --git a/code/datums/managed_browsers/feedback_viewer.dm b/code/datums/managed_browsers/feedback_viewer.dm
index 895bd36fae..0236036338 100644
--- a/code/datums/managed_browsers/feedback_viewer.dm
+++ b/code/datums/managed_browsers/feedback_viewer.dm
@@ -132,29 +132,29 @@
return
if(href_list["filter_id"])
- var/id_to_search = input(my_client, "Write feedback ID here.", "Filter by ID", null) as null|num
+ var/id_to_search = tgui_input_number(my_client, "Write feedback ID here.", "Filter by ID", null)
if(id_to_search)
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_ID, id_to_search, TRUE)
if(href_list["filter_author"])
- var/author_to_search = input(my_client, "Write desired key or hash here. Partial keys/hashes are allowed.", "Filter by Author", null) as null|text
+ var/author_to_search = tgui_input_text(my_client, "Write desired key or hash here. Partial keys/hashes are allowed.", "Filter by Author", null)
if(author_to_search)
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_AUTHOR, author_to_search)
if(href_list["filter_topic"])
- var/topic_to_search = input(my_client, "Write desired topic here. Partial topics are allowed. \
- \nThe current topics in the config are [english_list(config.sqlite_feedback_topics)].", "Filter by Topic", null) as null|text
+ var/topic_to_search = tgui_input_text(my_client, "Write desired topic here. Partial topics are allowed. \
+ \nThe current topics in the config are [english_list(config.sqlite_feedback_topics)].", "Filter by Topic", null)
if(topic_to_search)
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_TOPIC, topic_to_search)
if(href_list["filter_content"])
- var/content_to_search = input(my_client, "Write desired content to find here. Partial matches are allowed.", "Filter by Content", null) as null|message
+ var/content_to_search = tgui_input_text(my_client, "Write desired content to find here. Partial matches are allowed.", "Filter by Content", null, multiline = TRUE)
if(content_to_search)
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_CONTENT, content_to_search)
if(href_list["filter_datetime"])
- var/datetime_to_search = input(my_client, "Write desired datetime. Partial matches are allowed.\n\
- Format is 'YYYY-MM-DD HH:MM:SS'.", "Filter by Datetime", null) as null|text
+ var/datetime_to_search = tgui_input_text(my_client, "Write desired datetime. Partial matches are allowed.\n\
+ Format is 'YYYY-MM-DD HH:MM:SS'.", "Filter by Datetime", null)
if(datetime_to_search)
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_DATETIME, datetime_to_search)
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 736fdff021..34b4105e86 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -190,7 +190,7 @@
assigned_role = new_role
else if (href_list["memory_edit"])
- var/new_memo = sanitize(input("Write new memory", "Memory", memory) as null|message)
+ var/new_memo = sanitize(tgui_input_text("Write new memory", "Memory", memory, multiline = TRUE))
if (isnull(new_memo)) return
memory = new_memo
@@ -198,7 +198,7 @@
var/datum/mind/mind = locate(href_list["amb_edit"])
if(!mind)
return
- var/new_ambition = input("Enter a new ambition", "Memory", mind.ambitions) as null|message
+ var/new_ambition = tgui_input_text("Enter a new ambition", "Memory", mind.ambitions, multiline = TRUE)
if(isnull(new_ambition))
return
if(mind)
@@ -296,7 +296,7 @@
if(objective&&objective.type==text2path("/datum/objective/[new_obj_type]"))
def_num = objective.target_amount
- var/target_number = input("Input target number:", "Objective", def_num) as num|null
+ var/target_number = tgui_input_number("Input target number:", "Objective", def_num)
if (isnull(target_number))//Ordinarily, you wouldn't need isnull. In this case, the value may already exist.
return
@@ -314,7 +314,7 @@
new_objective.target_amount = target_number
if ("custom")
- var/expl = sanitize(input("Custom objective:", "Objective", objective ? objective.explanation_text : "") as text|null)
+ var/expl = sanitize(tgui_input_text("Custom objective:", "Objective", objective ? objective.explanation_text : ""))
if (!expl) return
new_objective = new /datum/objective
new_objective.owner = src
@@ -410,7 +410,7 @@
// var/obj/item/device/uplink/hidden/suplink = find_syndicate_uplink() No longer needed, uses stored in mind
var/crystals
crystals = tcrystals
- crystals = input("Amount of telecrystals for [key]", crystals) as null|num
+ crystals = tgui_input_number("Amount of telecrystals for [key]", crystals)
if (!isnull(crystals))
tcrystals = crystals
diff --git a/code/datums/supplypacks/materials.dm b/code/datums/supplypacks/materials.dm
index 5e181d8af8..3268d05a46 100644
--- a/code/datums/supplypacks/materials.dm
+++ b/code/datums/supplypacks/materials.dm
@@ -66,17 +66,19 @@
/obj/fiftyspawner/tealcarpet
)
-/datum/supply_pack/materials/arcade_carpet
- name = "Retro carpets"
+/datum/supply_pack/materials/retrocarpet
+ name = "Retro carpet"
containertype = /obj/structure/closet/crate/grayson
- containername = "Retro carpets crate"
+ containername = "Retro carpet crate"
cost = 15
contains = list(
- /obj/fiftyspawner/decocarpet,
- /obj/fiftyspawner/retrocarpet
+ /obj/fiftyspawner/geocarpet,
+ /obj/fiftyspawner/retrocarpet,
+ /obj/fiftyspawner/retrocarpet_red,
+ /obj/fiftyspawner/happycarpet
)
-/datum/supply_pack/misc/linoleum
+/datum/supply_pack/materials/linoleum
name = "Linoleum"
containertype = /obj/structure/closet/crate/grayson
containername = "Linoleum crate"
diff --git a/code/datums/supplypacks/supply.dm b/code/datums/supplypacks/supply.dm
index eff6fb84fd..70f8386a91 100644
--- a/code/datums/supplypacks/supply.dm
+++ b/code/datums/supplypacks/supply.dm
@@ -9,18 +9,30 @@
/datum/supply_pack/supply/food
name = "Kitchen supply crate"
contains = list(
- /obj/item/weapon/reagent_containers/food/condiment/flour = 6,
+ /obj/item/weapon/reagent_containers/food/condiment/carton/flour = 6,
/obj/item/weapon/reagent_containers/food/drinks/milk = 3,
/obj/item/weapon/reagent_containers/food/drinks/soymilk = 2,
/obj/item/weapon/storage/fancy/egg_box = 2,
/obj/item/weapon/reagent_containers/food/snacks/tofu = 4,
/obj/item/weapon/reagent_containers/food/snacks/meat = 4,
- /obj/item/weapon/reagent_containers/food/condiment/yeast = 3
+ /obj/item/weapon/reagent_containers/food/condiment/yeast = 3,
+ /obj/item/weapon/reagent_containers/food/condiment/sprinkles = 1
)
cost = 10
containertype = /obj/structure/closet/crate/freezer/centauri
containername = "Food crate"
+/datum/supply_pack/supply/fancyfood
+ name = "Artisanal food delivery"
+ contains = list(
+ /obj/item/weapon/reagent_containers/food/condiment/carton/flour/rustic = 6,
+ /obj/item/weapon/reagent_containers/food/condiment/carton/sugar/rustic = 6
+ )
+ cost = 25
+ containertype = /obj/structure/closet/crate/freezer/centauri
+ containername = "Artisanal food crate"
+
+
/datum/supply_pack/supply/toner
name = "Toner cartridges"
contains = list(/obj/item/device/toner = 6)
diff --git a/code/datums/uplink/announcements.dm b/code/datums/uplink/announcements.dm
index f1db67a647..f6742fb815 100644
--- a/code/datums/uplink/announcements.dm
+++ b/code/datums/uplink/announcements.dm
@@ -16,10 +16,10 @@
item_cost = 20
/datum/uplink_item/abstract/announcements/fake_centcom/extra_args(var/mob/user)
- var/title = sanitize(input(usr, "Enter your announcement title.", "Announcement Title") as null|text)
+ var/title = sanitize(tgui_input_text(usr, "Enter your announcement title.", "Announcement Title"))
if(!title)
return
- var/message = sanitize(input(usr, "Enter your announcement message.", "Announcement Title") as null|text)
+ var/message = sanitize(tgui_input_text(usr, "Enter your announcement message.", "Announcement Title"))
if(!message)
return
return list("title" = title, "message" = message)
diff --git a/code/game/antagonist/antagonist_objectives.dm b/code/game/antagonist/antagonist_objectives.dm
index c2c56c7be1..223db7dda8 100644
--- a/code/game/antagonist/antagonist_objectives.dm
+++ b/code/game/antagonist/antagonist_objectives.dm
@@ -41,9 +41,9 @@
to_chat(src, "While you may perhaps have goals, this verb's meant to only be visible \
to antagonists. Please make a bug report! ")
return
- var/new_ambitions = input(src, "Write a short sentence of what your character hopes to accomplish \
+ var/new_ambitions = tgui_input_text(src, "Write a short sentence of what your character hopes to accomplish \
today as an antagonist. Remember that this is purely optional. It will be shown at the end of the \
- round for everybody else.", "Ambitions", mind.ambitions) as null|message
+ round for everybody else.", "Ambitions", mind.ambitions, multiline = TRUE)
if(isnull(new_ambitions))
return
new_ambitions = sanitize(new_ambitions)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index d56186f5b0..5ef2fd6818 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -27,7 +27,7 @@
// Overlays
///Our local copy of (non-priority) overlays without byond magic. Use procs in SSoverlays to manipulate
- var/list/our_overlays
+ var/list/our_overlays
///Overlays that should remain on top and not normally removed when using cut_overlay functions, like c4.
var/list/priority_overlays
///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays
@@ -35,7 +35,7 @@
///Our local copy of filter data so we can add/remove it
var/list/filter_data
-
+
//Detective Work, used for the duplicate data points kept in the scanners
var/list/original_atom
// Track if we are already had initialize() called to prevent double-initialization.
@@ -681,7 +681,7 @@
if(!isnull(.))
datum_flags |= DF_VAR_EDITED
return
-
+
. = ..()
/atom/proc/atom_say(message)
@@ -716,7 +716,7 @@
. = ..()
SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, new_loc)
-/atom/proc/get_visible_gender()
+/atom/proc/get_visible_gender(mob/user, force)
return gender
/atom/proc/interact(mob/user)
diff --git a/code/game/base_turf.dm b/code/game/base_turf.dm
index 3f78c6fba4..238015d6ed 100644
--- a/code/game/base_turf.dm
+++ b/code/game/base_turf.dm
@@ -19,7 +19,7 @@
if(!holder) return
- var/choice = input(usr, "Which Z-level do you wish to set the base turf for?") as num|null
+ var/choice = tgui_input_number(usr, "Which Z-level do you wish to set the base turf for?")
if(!choice)
return
diff --git a/code/game/gamemodes/events/holidays/Holidays.dm b/code/game/gamemodes/events/holidays/Holidays.dm
index 4955aa2559..b5f836e599 100644
--- a/code/game/gamemodes/events/holidays/Holidays.dm
+++ b/code/game/gamemodes/events/holidays/Holidays.dm
@@ -214,8 +214,8 @@ var/global/list/Holiday = list() //Holidays are lists now, so we can have more t
Holiday = list()
- var/H = input(src,"What holiday is it today?","Set Holiday") as text
- var/B = input(src,"Now explain what the holiday is about","Set Holiday") as message
+ var/H = tgui_input_text(src,"What holiday is it today?","Set Holiday")
+ var/B = tgui_input_text(src,"Now explain what the holiday is about","Set Holiday", multiline = TRUE)
Holiday[H] = B
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 7201142678..0099cf6ffd 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -59,23 +59,23 @@ var/global/list/additional_antag_types = list()
var/choice = ""
switch(href_list["set"])
if("shuttle_delay")
- choice = input(usr, "Enter a new shuttle delay multiplier") as num
+ choice = tgui_input_number(usr, "Enter a new shuttle delay multiplier", null, null, 20, 1)
if(!choice || choice < 1 || choice > 20)
return
shuttle_delay = choice
if("antag_scaling")
- choice = input(usr, "Enter a new antagonist cap scaling coefficient.") as num
+ choice = tgui_input_number(usr, "Enter a new antagonist cap scaling coefficient.", null, null, 100, 0)
if(isnull(choice) || choice < 0 || choice > 100)
return
antag_scaling_coeff = choice
if("event_modifier_moderate")
- choice = input(usr, "Enter a new moderate event time modifier.") as num
+ choice = tgui_input_number(usr, "Enter a new moderate event time modifier.", null, null, 100, 0)
if(isnull(choice) || choice < 0 || choice > 100)
return
event_delay_mod_moderate = choice
refresh_event_modifiers()
if("event_modifier_severe")
- choice = input(usr, "Enter a new moderate event time modifier.") as num
+ choice = tgui_input_number(usr, "Enter a new moderate event time modifier.", null, null, 100, 0)
if(isnull(choice) || choice < 0 || choice > 100)
return
event_delay_mod_major = choice
diff --git a/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_networking.dm b/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_networking.dm
index d8229e1562..2ee412194e 100644
--- a/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_networking.dm
+++ b/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_networking.dm
@@ -93,8 +93,8 @@
if(!ability_prechecks(user, price))
return
- var/title = input(usr, "Select message title: ")
- var/text = input(usr, "Select message text: ")
+ var/title = tgui_input_text(usr, "Select message title: ")
+ var/text = tgui_input_text(usr, "Select message text: ")
if(!title || !text || !ability_pay(user, price))
to_chat(user, "Hack Aborted")
return
diff --git a/code/game/gamemodes/nuclear/pinpointer.dm b/code/game/gamemodes/nuclear/pinpointer.dm
index aec6442f8d..7c51611bf2 100644
--- a/code/game/gamemodes/nuclear/pinpointer.dm
+++ b/code/game/gamemodes/nuclear/pinpointer.dm
@@ -124,10 +124,10 @@
if("Location")
mode = 1
- var/locationx = input(usr, "Please input the x coordinate to search for.", "Location?" , "") as num
+ var/locationx = tgui_input_number(usr, "Please input the x coordinate to search for.", "Location?" , "")
if(!locationx || !(usr in view(1,src)))
return
- var/locationy = input(usr, "Please input the y coordinate to search for.", "Location?" , "") as num
+ var/locationy = tgui_input_number(usr, "Please input the y coordinate to search for.", "Location?" , "")
if(!locationy || !(usr in view(1,src)))
return
@@ -160,7 +160,7 @@
to_chat(usr, "You set the pinpointer to locate [targetitem]")
if("DNA")
- var/DNAstring = input(usr, "Input DNA string to search for." , "Please Enter String." , "")
+ var/DNAstring = tgui_input_text(usr, "Input DNA string to search for." , "Please Enter String." , "")
if(!DNAstring)
return
for(var/mob/living/carbon/M in mob_list)
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 9e4e554426..2ab94983f5 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -481,7 +481,7 @@ var/global/list/all_objectives = list()
var/tmp_obj = new custom_target
var/custom_name = tmp_obj:name
qdel(tmp_obj)
- custom_name = sanitize(input(usr, "Enter target name:", "Objective target", custom_name) as text|null)
+ custom_name = sanitize(tgui_input_text(usr, "Enter target name:", "Objective target", custom_name))
if (!custom_name) return
target_name = custom_name
steal_target = custom_target
diff --git a/code/game/gamemodes/technomancer/spells/illusion.dm b/code/game/gamemodes/technomancer/spells/illusion.dm
index dcadf53ffb..ab83319bc5 100644
--- a/code/game/gamemodes/technomancer/spells/illusion.dm
+++ b/code/game/gamemodes/technomancer/spells/illusion.dm
@@ -49,12 +49,12 @@
if("Cancel")
return
if("Speak")
- var/what_to_say = input(user, "What do you want \the [illusion] to say?","Illusion Speak") as null|text
+ var/what_to_say = tgui_input_text(user, "What do you want \the [illusion] to say?","Illusion Speak")
//what_to_say = sanitize(what_to_say) //Sanitize occurs inside say() already.
if(what_to_say)
illusion.say(what_to_say)
if("Emote")
- var/what_to_emote = input(user, "What do you want \the [illusion] to do?","Illusion Emote") as null|text
+ var/what_to_emote = tgui_input_text(user, "What do you want \the [illusion] to do?","Illusion Emote")
if(what_to_emote)
illusion.emote(what_to_emote)
diff --git a/code/game/machinery/air_alarm.dm b/code/game/machinery/air_alarm.dm
index dd653d746a..ca40f0dc53 100644
--- a/code/game/machinery/air_alarm.dm
+++ b/code/game/machinery/air_alarm.dm
@@ -675,7 +675,7 @@
var/list/selected = TLV["temperature"]
var/max_temperature = min(selected[3] - T0C, MAX_TEMPERATURE)
var/min_temperature = max(selected[2] - T0C, MIN_TEMPERATURE)
- var/input_temperature = input(usr, "What temperature would you like the system to mantain? (Capped between [min_temperature] and [max_temperature]C)", "Thermostat Controls", target_temperature - T0C) as num|null
+ var/input_temperature = tgui_input_number(usr, "What temperature would you like the system to mantain? (Capped between [min_temperature] and [max_temperature]C)", "Thermostat Controls", target_temperature - T0C, max_temperature, min_temperature)
if(isnum(input_temperature))
if(input_temperature > max_temperature || input_temperature < min_temperature)
to_chat(usr, "Temperature must be between [min_temperature]C and [max_temperature]C")
@@ -729,7 +729,7 @@
var/env = params["env"]
var/name = params["var"]
- var/value = input(usr, "New [name] for [env]:", name, TLV[env][name]) as num|null
+ var/value = tgui_input_number(usr, "New [name] for [env]:", name, TLV[env][name])
if(!isnull(value) && !..())
if(value < 0)
TLV[env][name] = -1
diff --git a/code/game/machinery/airconditioner_vr.dm b/code/game/machinery/airconditioner_vr.dm
index f9a1437041..364d4bf377 100644
--- a/code/game/machinery/airconditioner_vr.dm
+++ b/code/game/machinery/airconditioner_vr.dm
@@ -47,7 +47,7 @@
turn_off()
return
if(istype(I, /obj/item/device/multitool))
- var/new_temp = input(usr, "Input a new target temperature, in degrees C.","Target Temperature", 20) as num
+ var/new_temp = tgui_input_number(usr, "Input a new target temperature, in degrees C.","Target Temperature", 20)
if(!Adjacent(user) || user.incapacitated())
return
new_temp = convert_c2k(new_temp)
diff --git a/code/game/machinery/atmoalter/canister.dm b/code/game/machinery/atmoalter/canister.dm
index 783ee16225..cb11dce0b5 100644
--- a/code/game/machinery/atmoalter/canister.dm
+++ b/code/game/machinery/atmoalter/canister.dm
@@ -332,7 +332,7 @@ update_flag
pressure = 10*ONE_ATMOSPHERE
. = TRUE
else if(pressure == "input")
- pressure = input(usr, "New release pressure ([ONE_ATMOSPHERE/10]-[10*ONE_ATMOSPHERE] kPa):", name, release_pressure) as num|null
+ pressure = tgui_input_number(usr, "New release pressure ([ONE_ATMOSPHERE/10]-[10*ONE_ATMOSPHERE] kPa):", name, release_pressure, 10*ONE_ATMOSPHERE, ONE_ATMOSPHERE/10)
if(!isnull(pressure) && !..())
. = TRUE
else if(text2num(pressure) != null)
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index bc8a8081d0..3e8cf00207 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -176,7 +176,7 @@
if(!isnull(materials.get_material_amount(material)) && materials.get_material_amount(material) < round(making.resources[material] * coeff))
max_sheets = 0
//Build list of multipliers for sheets.
- multiplier = input(usr, "How many do you want to print? (0-[max_sheets])") as num|null
+ multiplier = tgui_input_number(usr, "How many do you want to print? (0-[max_sheets])", null, null, max_sheets, 0)
if(!multiplier || multiplier <= 0 || multiplier > max_sheets || tgui_status(usr, state) != STATUS_INTERACTIVE)
return FALSE
diff --git a/code/game/machinery/camera/camera_assembly.dm b/code/game/machinery/camera/camera_assembly.dm
index 0660bff759..a73eb6fe91 100644
--- a/code/game/machinery/camera/camera_assembly.dm
+++ b/code/game/machinery/camera/camera_assembly.dm
@@ -80,7 +80,7 @@
if(W.is_screwdriver())
playsound(src, W.usesound, 50, 1)
- var/input = sanitize(input(usr, "Which networks would you like to connect this camera to? Separate networks with a comma. No Spaces!\nFor example: "+using_map.station_short+",Security,Secret ", "Set Network", camera_network ? camera_network : NETWORK_DEFAULT))
+ var/input = sanitize(tgui_input_text(usr, "Which networks would you like to connect this camera to? Separate networks with a comma. No Spaces!\nFor example: "+using_map.station_short+",Security,Secret ", "Set Network", camera_network ? camera_network : NETWORK_DEFAULT))
if(!input)
to_chat(usr, "No input found please hang up and try your call again.")
return
@@ -92,7 +92,7 @@
var/area/camera_area = get_area(src)
var/temptag = "[sanitize(camera_area.name)] ([rand(1, 999)])"
- input = sanitizeSafe(input(usr, "How would you like to name the camera?", "Set Camera Name", camera_name ? camera_name : temptag), MAX_NAME_LEN)
+ input = sanitizeSafe(tgui_input_text(usr, "How would you like to name the camera?", "Set Camera Name", camera_name ? camera_name : temptag), MAX_NAME_LEN)
state = 4
var/obj/machinery/camera/C = new(src.loc)
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 7836b0bdf7..7def7f8b8e 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -1182,7 +1182,7 @@
// Have the customer punch in the PIN before checking if there's enough money. Prevents people from figuring out acct is
// empty at high security levels
if(customer_account.security_level != 0) //If card requires pin authentication (ie seclevel 1 or 2)
- var/attempt_pin = input(usr, "Enter pin code", "Vendor transaction") as num
+ var/attempt_pin = tgui_input_number(usr, "Enter pin code", "Vendor transaction")
customer_account = attempt_account_access(I.associated_account_number, attempt_pin, 2)
if(!customer_account)
diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm
index c15a9d1524..75b860330d 100644
--- a/code/game/machinery/computer/card.dm
+++ b/code/game/machinery/computer/card.dm
@@ -205,7 +205,7 @@
if(is_authenticated() && modify)
var/t1 = params["assign_target"]
if(t1 == "Custom")
- var/temp_t = sanitize(input(usr, "Enter a custom job assignment.","Assignment"), 45)
+ var/temp_t = sanitize(tgui_input_text(usr, "Enter a custom job assignment.","Assignment"), 45)
//let custom jobs function as an impromptu alt title, mainly for sechuds
if(temp_t && modify)
modify.assignment = temp_t
diff --git a/code/game/machinery/computer/guestpass.dm b/code/game/machinery/computer/guestpass.dm
index ed37a18c41..dd2644f794 100644
--- a/code/game/machinery/computer/guestpass.dm
+++ b/code/game/machinery/computer/guestpass.dm
@@ -170,15 +170,15 @@
mode = params["mode"]
if("giv_name")
- var/nam = sanitizeName(input(usr, "Person pass is issued to", "Name", giv_name) as text|null)
+ var/nam = sanitizeName(tgui_input_text(usr, "Person pass is issued to", "Name", giv_name))
if(nam)
giv_name = nam
if("reason")
- var/reas = sanitize(input(usr, "Reason why pass is issued", "Reason", reason) as text|null)
+ var/reas = sanitize(tgui_input_text(usr, "Reason why pass is issued", "Reason", reason))
if(reas)
reason = reas
if("duration")
- var/dur = input(usr, "Duration (in minutes) during which pass is valid (up to 360 minutes).", "Duration") as num|null //VOREStation Edit
+ var/dur = tgui_input_number(usr, "Duration (in minutes) during which pass is valid (up to 360 minutes).", "Duration", null, 360, 0)
if(dur)
if(dur > 0 && dur <= 360) //VOREStation Edit
duration = dur
diff --git a/code/game/machinery/computer/message.dm b/code/game/machinery/computer/message.dm
index 5962f9aa8f..ec5a1f5fcd 100644
--- a/code/game/machinery/computer/message.dm
+++ b/code/game/machinery/computer/message.dm
@@ -243,7 +243,7 @@
. = TRUE
//Change the password - KEY REQUIRED
if("pass")
- var/dkey = trim(input(usr, "Please enter the current decryption key.") as text|null)
+ var/dkey = trim(tgui_input_text(usr, "Please enter the current decryption key."))
if(dkey && dkey != "")
if(linkedServer.decryptkey == dkey)
var/newkey = trim(input(usr,"Please enter the new key (3 - 16 characters max):"))
@@ -325,7 +325,7 @@
. = TRUE
if("addtoken")
- linkedServer.spamfilter += input(usr,"Enter text you want to be filtered out","Token creation") as text|null
+ linkedServer.spamfilter += tgui_input_text(usr,"Enter text you want to be filtered out","Token creation")
. = TRUE
if("deltoken")
diff --git a/code/game/machinery/computer/prisoner.dm b/code/game/machinery/computer/prisoner.dm
index ac4868bc17..ceae88e54d 100644
--- a/code/game/machinery/computer/prisoner.dm
+++ b/code/game/machinery/computer/prisoner.dm
@@ -82,7 +82,7 @@
to_chat(usr, "Unauthorized Access.")
. = TRUE
if("warn")
- var/warning = sanitize(input(usr, "Message:", "Enter your message here!", ""))
+ var/warning = sanitize(tgui_input_text(usr, "Message:", "Enter your message here!", ""))
if(!warning)
return
var/obj/item/weapon/implant/I = locate(params["imp"])
diff --git a/code/game/machinery/computer/supply.dm b/code/game/machinery/computer/supply.dm
index e805e151bd..ce345e429e 100644
--- a/code/game/machinery/computer/supply.dm
+++ b/code/game/machinery/computer/supply.dm
@@ -221,12 +221,12 @@
visible_message("[src]'s monitor flashes, \"[reqtime - world.time] seconds remaining until another requisition form may be printed.\" ")
return FALSE
- var/amount = clamp(input(usr, "How many crates? (0 to 20)") as num|null, 0, 20)
+ var/amount = clamp(tgui_input_number(usr, "How many crates? (0 to 20)", null, null, 20, 0), 0, 20)
if(!amount)
return FALSE
var/timeout = world.time + 600
- var/reason = sanitize(input(usr, "Reason:","Why do you require this item?","") as null|text)
+ var/reason = sanitize(tgui_input_text(usr, "Reason:","Why do you require this item?",""))
if(world.time > timeout)
to_chat(usr, "Error. Request timed out. ")
return FALSE
@@ -280,7 +280,7 @@
return FALSE
var/timeout = world.time + 600
- var/reason = sanitize(input(usr, "Reason:","Why do you require this item?","") as null|text)
+ var/reason = sanitize(tgui_input_text(usr, "Reason:","Why do you require this item?",""))
if(world.time > timeout)
to_chat(usr, "Error. Request timed out. ")
return FALSE
@@ -323,7 +323,7 @@
return FALSE
if(!(authorization & SUP_ACCEPT_ORDERS))
return FALSE
- var/new_val = sanitize(input(usr, params["edit"], "Enter the new value for this field:", params["default"]) as null|text)
+ var/new_val = sanitize(tgui_input_text(usr, params["edit"], "Enter the new value for this field:", params["default"]))
if(!new_val)
return FALSE
@@ -396,7 +396,7 @@
var/list/L = E.contents[params["index"]]
var/field = tgui_alert(usr, "Select which field to edit", "Field Choice", list("Name", "Quantity", "Value"))
- var/new_val = sanitize(input(usr, field, "Enter the new value for this field:", L[lowertext(field)]) as null|text)
+ var/new_val = sanitize(tgui_input_text(usr, field, "Enter the new value for this field:", L[lowertext(field)]))
if(!new_val)
return
@@ -439,7 +439,7 @@
return FALSE
if(!(authorization & SUP_ACCEPT_ORDERS))
return FALSE
- var/new_val = sanitize(input(usr, params["edit"], "Enter the new value for this field:", params["default"]) as null|text)
+ var/new_val = sanitize(tgui_input_text(usr, params["edit"], "Enter the new value for this field:", params["default"]))
if(!new_val)
return
diff --git a/code/game/machinery/computer3/computers/card.dm b/code/game/machinery/computer3/computers/card.dm
index d2f6074c78..7412f6b464 100644
--- a/code/game/machinery/computer3/computers/card.dm
+++ b/code/game/machinery/computer3/computers/card.dm
@@ -305,7 +305,7 @@
if(auth)
var/t1 = href_list["assign"]
if(t1 == "Custom")
- var/temp_t = sanitize(input(usr, "Enter a custom job assignment.","Assignment"))
+ var/temp_t = sanitize(tgui_input_text(usr, "Enter a custom job assignment.","Assignment"))
if(temp_t)
t1 = temp_t
set_default_access(t1)
diff --git a/code/game/machinery/gear_dispenser.dm b/code/game/machinery/gear_dispenser.dm
index 1a11c26023..d08bb9c774 100644
--- a/code/game/machinery/gear_dispenser.dm
+++ b/code/game/machinery/gear_dispenser.dm
@@ -693,7 +693,7 @@ var/list/dispenser_presets = list()
* "gearlist" = array of types (yes the types are not valid json, byond parses them into real types.)
* "req_one_access" = array of numbers (accesses)
*/
- var/input = input(usr, "Paste new gear pack JSON below. See example/code comments.", "Admin-load Dispenser", example) as null|message
+ var/input = tgui_input_text(usr, "Paste new gear pack JSON below. See example/code comments.", "Admin-load Dispenser", example, multiline = TRUE)
if(!input)
return
diff --git a/code/game/machinery/jukebox.dm b/code/game/machinery/jukebox.dm
index 955b99009a..0e184683ec 100644
--- a/code/game/machinery/jukebox.dm
+++ b/code/game/machinery/jukebox.dm
@@ -401,20 +401,20 @@
return
// Required
- var/url = input(C, "REQUIRED: Provide URL for track", "Track URL") as text|null
+ var/url = tgui_input_text(C, "REQUIRED: Provide URL for track", "Track URL")
if(!url)
return
- var/title = input(C, "REQUIRED: Provide title for track", "Track Title") as text|null
+ var/title = tgui_input_text(C, "REQUIRED: Provide title for track", "Track Title")
if(!title)
return
- var/duration = input(C, "REQUIRED: Provide duration for track (in deciseconds, aka seconds*10)", "Track Duration") as num|null
+ var/duration = tgui_input_number(C, "REQUIRED: Provide duration for track (in deciseconds, aka seconds*10)", "Track Duration")
if(!duration)
return
// Optional
- var/artist = input(C, "Optional: Provide artist for track", "Track Artist") as text|null
+ var/artist = tgui_input_text(C, "Optional: Provide artist for track", "Track Artist")
if(isnull(artist)) // Cancel rather than empty string
return
@@ -428,7 +428,7 @@
if(!check_rights(R_FUN|R_ADMIN))
return
- var/track = input(C, "Input track title or URL to remove (must be exact)", "Remove Track") as text|null
+ var/track = tgui_input_text(C, "Input track title or URL to remove (must be exact)", "Remove Track")
if(!track)
return
diff --git a/code/game/machinery/magnet.dm b/code/game/machinery/magnet.dm
index f713cea2d0..e2b76f3632 100644
--- a/code/game/machinery/magnet.dm
+++ b/code/game/machinery/magnet.dm
@@ -314,7 +314,7 @@
if(speed <= 0)
speed = 1
if("setpath")
- var/newpath = sanitize(input(usr, "Please define a new path!",,path) as text|null)
+ var/newpath = sanitize(tgui_input_text(usr, "Please define a new path!",,path))
if(newpath && newpath != "")
moving = 0 // stop moving
path = newpath
diff --git a/code/game/machinery/mass_driver.dm b/code/game/machinery/mass_driver.dm
index 7e6721f109..1002e3fa21 100644
--- a/code/game/machinery/mass_driver.dm
+++ b/code/game/machinery/mass_driver.dm
@@ -28,7 +28,7 @@
if(istype(I, /obj/item/device/multitool))
if(panel_open)
- var/input = sanitize(input(usr, "What id would you like to give this conveyor?", "Multitool-Conveyor interface", id))
+ var/input = sanitize(tgui_input_text(usr, "What id would you like to give this conveyor?", "Multitool-Conveyor interface", id))
if(!input)
to_chat(usr, "No input found please hang up and try your call again.")
return
diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm
index 90a623d0b9..1e1fff180e 100644
--- a/code/game/machinery/navbeacon.dm
+++ b/code/game/machinery/navbeacon.dm
@@ -150,7 +150,7 @@ Transponder Codes:
"}
usr.set_machine(src)
if(href_list["locedit"])
- var/newloc = sanitize(input(usr, "Enter New Location", "Navigation Beacon", location) as text|null)
+ var/newloc = sanitize(tgui_input_text(usr, "Enter New Location", "Navigation Beacon", location))
if(newloc)
location = newloc
updateDialog()
@@ -158,12 +158,12 @@ Transponder Codes:"}
else if(href_list["edit"])
var/codekey = href_list["code"]
- var/newkey = input(usr, "Enter Transponder Code Key", "Navigation Beacon", codekey) as text|null
+ var/newkey = tgui_input_text(usr, "Enter Transponder Code Key", "Navigation Beacon", codekey)
if(!newkey)
return
var/codeval = codes[codekey]
- var/newval = input(usr, "Enter Transponder Code Value", "Navigation Beacon", codeval) as text|null
+ var/newval = tgui_input_text(usr, "Enter Transponder Code Value", "Navigation Beacon", codeval)
if(!newval)
newval = codekey
return
@@ -180,11 +180,11 @@ Transponder Codes:"}
else if(href_list["add"])
- var/newkey = input(usr, "Enter New Transponder Code Key", "Navigation Beacon") as text|null
+ var/newkey = tgui_input_text(usr, "Enter New Transponder Code Key", "Navigation Beacon")
if(!newkey)
return
- var/newval = input(usr, "Enter New Transponder Code Value", "Navigation Beacon") as text|null
+ var/newval = tgui_input_text(usr, "Enter New Transponder Code Value", "Navigation Beacon")
if(!newval)
newval = "1"
return
diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm
index 4a40ad8ed2..eab08e2e43 100644
--- a/code/game/machinery/newscaster.dm
+++ b/code/game/machinery/newscaster.dm
@@ -447,7 +447,7 @@ GLOBAL_LIST_BOILERPLATE(allCasters, /obj/machinery/newscaster)
return TRUE
if("set_new_message")
- msg = sanitize(tgui_input_message(usr, "Write your Feed story", "Network Channel Handler"))
+ msg = sanitize(tgui_input_text(usr, "Write your Feed story", "Network Channel Handler", multiline = TRUE))
return TRUE
if("set_new_title")
diff --git a/code/game/machinery/pointdefense.dm b/code/game/machinery/pointdefense.dm
index b4cfce40d5..2a98271836 100644
--- a/code/game/machinery/pointdefense.dm
+++ b/code/game/machinery/pointdefense.dm
@@ -97,7 +97,7 @@ GLOBAL_LIST_BOILERPLATE(pointdefense_turrets, /obj/machinery/pointdefense)
/obj/machinery/pointdefense_control/attackby(var/obj/item/W, var/mob/user)
if(W?.is_multitool())
- var/new_ident = input(user, "Enter a new ident tag.", "[src]", id_tag) as null|text
+ var/new_ident = tgui_input_text(user, "Enter a new ident tag.", "[src]", id_tag)
if(new_ident && new_ident != id_tag && user.Adjacent(src) && CanInteract(user, GLOB.tgui_physical_state))
// Check for duplicate controllers with this ID
for(var/obj/machinery/pointdefense_control/PC as anything in GLOB.pointdefense_controllers)
@@ -212,7 +212,7 @@ GLOBAL_LIST_BOILERPLATE(pointdefense_turrets, /obj/machinery/pointdefense)
/obj/machinery/pointdefense/attackby(var/obj/item/W, var/mob/user)
if(W?.is_multitool())
- var/new_ident = input(user, "Enter a new ident tag.", "[src]", id_tag) as null|text
+ var/new_ident = tgui_input_text(user, "Enter a new ident tag.", "[src]", id_tag)
if(new_ident && new_ident != id_tag && user.Adjacent(src) && CanInteract(user, GLOB.tgui_physical_state))
to_chat(user, "You register [src] with the [new_ident] network. ")
id_tag = new_ident
diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm
index a8c4e21dbc..7350ca0c1e 100644
--- a/code/game/machinery/requests_console.dm
+++ b/code/game/machinery/requests_console.dm
@@ -153,7 +153,7 @@ var/list/obj/machinery/requests_console/allConsoles = list()
if(reject_bad_text(params["write"]))
recipient = params["write"] //write contains the string of the receiving department's name
- var/new_message = sanitize(input(usr, "Write your message:", "Awaiting Input", ""))
+ var/new_message = sanitize(tgui_input_text(usr, "Write your message:", "Awaiting Input", ""))
if(new_message)
message = new_message
screen = RCS_MESSAUTH
@@ -169,7 +169,7 @@ var/list/obj/machinery/requests_console/allConsoles = list()
. = TRUE
if("writeAnnouncement")
- var/new_message = sanitize(input(usr, "Write your message:", "Awaiting Input", ""))
+ var/new_message = sanitize(tgui_input_text(usr, "Write your message:", "Awaiting Input", ""))
if(new_message)
message = new_message
else
@@ -238,7 +238,7 @@ var/list/obj/machinery/requests_console/allConsoles = list()
if(computer_deconstruction_screwdriver(user, O))
return
if(istype(O, /obj/item/device/multitool))
- var/input = sanitize(input(usr, "What Department ID would you like to give this request console?", "Multitool-Request Console Interface", department))
+ var/input = sanitize(tgui_input_text(usr, "What Department ID would you like to give this request console?", "Multitool-Request Console Interface", department))
if(!input)
to_chat(usr, "No input found. Please hang up and try your call again.")
return
diff --git a/code/game/machinery/telecomms/logbrowser.dm b/code/game/machinery/telecomms/logbrowser.dm
index ba63655b9c..461a200481 100644
--- a/code/game/machinery/telecomms/logbrowser.dm
+++ b/code/game/machinery/telecomms/logbrowser.dm
@@ -128,7 +128,7 @@
. = TRUE
if("network")
- var/newnet = input(usr, "Which network do you want to view?", "Comm Monitor", network) as null|text
+ var/newnet = tgui_input_text(usr, "Which network do you want to view?", "Comm Monitor", network)
if(newnet && ((usr in range(1, src) || issilicon(usr))))
if(length(newnet) > 15)
diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/machine_interactions.dm
index 9e8f68921b..51af9f85e1 100644
--- a/code/game/machinery/telecomms/machine_interactions.dm
+++ b/code/game/machinery/telecomms/machine_interactions.dm
@@ -295,7 +295,7 @@
. = TRUE
if("network")
- var/newnet = input(usr, "Specify the new network for this machine. This will break all current links.", src, network) as null|text
+ var/newnet = tgui_input_text(usr, "Specify the new network for this machine. This will break all current links.", src, network)
if(newnet && canAccess(usr))
if(length(newnet) > 15)
diff --git a/code/game/machinery/telecomms/telemonitor.dm b/code/game/machinery/telecomms/telemonitor.dm
index 384790e60d..3014474b8e 100644
--- a/code/game/machinery/telecomms/telemonitor.dm
+++ b/code/game/machinery/telecomms/telemonitor.dm
@@ -26,13 +26,13 @@
data["network"] = network
data["temp"] = temp
- var/list/machinelistData = list()
+ var/list/machinelistData = list()
for(var/obj/machinery/telecomms/T in machinelist)
- machinelistData.Add(list(list(
+ machinelistData.Add(list(list(
"id" = T.id,
"name" = T.name,
)))
- data["machinelist"] = machinelistData
+ data["machinelist"] = machinelistData
data["selectedMachine"] = null
if(SelectedMachine)
@@ -100,7 +100,7 @@
. = TRUE
if("network")
- var/newnet = input(usr, "Which network do you want to view?", "Comm Monitor", network) as null|text
+ var/newnet = tgui_input_text(usr, "Which network do you want to view?", "Comm Monitor", network)
if(newnet && ((usr in range(1, src) || issilicon(usr))))
if(length(newnet) > 15)
set_temp("FAILED: NETWORK TAG STRING TOO LENGTHY", "bad")
diff --git a/code/game/machinery/telecomms/traffic_control.dm b/code/game/machinery/telecomms/traffic_control.dm
index 4565ab1668..cf7f9946aa 100644
--- a/code/game/machinery/telecomms/traffic_control.dm
+++ b/code/game/machinery/telecomms/traffic_control.dm
@@ -192,7 +192,7 @@
if(href_list["network"])
- var/newnet = input(usr, "Which network do you want to view?", "Comm Monitor", network) as null|text
+ var/newnet = tgui_input_text(usr, "Which network do you want to view?", "Comm Monitor", network)
if(newnet && ((usr in range(1, src) || issilicon(usr))))
if(length(newnet) > 15)
diff --git a/code/game/magic/archived_book.dm b/code/game/magic/archived_book.dm
index ed77f3c90b..1aab354815 100644
--- a/code/game/magic/archived_book.dm
+++ b/code/game/magic/archived_book.dm
@@ -40,7 +40,7 @@ var/global/datum/book_manager/book_mgr = new()
to_chat(src, "Only administrators may use this command.")
return
- var/isbn = input(usr, "ISBN number?", "Delete Book") as num | null
+ var/isbn = tgui_input_number(usr, "ISBN number?", "Delete Book")
if(!isbn)
return
diff --git a/code/game/mecha/mecha_control_console.dm b/code/game/mecha/mecha_control_console.dm
index aa5226c172..1c5003ac95 100644
--- a/code/game/mecha/mecha_control_console.dm
+++ b/code/game/mecha/mecha_control_console.dm
@@ -48,7 +48,7 @@
if("send_message")
var/obj/item/mecha_parts/mecha_tracking/MT = locate(params["mt"])
if(istype(MT))
- var/message = sanitize(input(usr, "Input message", "Transmit message") as text)
+ var/message = sanitize(tgui_input_text(usr, "Input message", "Transmit message"))
var/obj/mecha/M = MT.in_mecha()
if(message && M)
M.occupant_message(message)
diff --git a/code/game/objects/effects/spawners/bombspawner.dm b/code/game/objects/effects/spawners/bombspawner.dm
index b3b22fc082..c7fc6962ba 100644
--- a/code/game/objects/effects/spawners/bombspawner.dm
+++ b/code/game/objects/effects/spawners/bombspawner.dm
@@ -7,13 +7,13 @@
var/obj/effect/spawner/newbomb/proto = /obj/effect/spawner/newbomb/radio/custom
- var/p = input(usr, "Enter phoron amount (mol):","Phoron", initial(proto.phoron_amt)) as num|null
+ var/p = tgui_input_number(usr, "Enter phoron amount (mol):","Phoron", initial(proto.phoron_amt))
if(p == null) return
- var/o = input(usr, "Enter oxygen amount (mol):","Oxygen", initial(proto.oxygen_amt)) as num|null
+ var/o = tgui_input_number(usr, "Enter oxygen amount (mol):","Oxygen", initial(proto.oxygen_amt))
if(o == null) return
- var/c = input(usr, "Enter carbon dioxide amount (mol):","Carbon Dioxide", initial(proto.carbon_amt)) as num|null
+ var/c = tgui_input_number(usr, "Enter carbon dioxide amount (mol):","Carbon Dioxide", initial(proto.carbon_amt))
if(c == null) return
new /obj/effect/spawner/newbomb/radio/custom(get_turf(mob), p, o, c)
diff --git a/code/game/objects/explosion_recursive.dm b/code/game/objects/explosion_recursive.dm
index 56cc76f602..c530e37692 100644
--- a/code/game/objects/explosion_recursive.dm
+++ b/code/game/objects/explosion_recursive.dm
@@ -1,5 +1,5 @@
/client/proc/kaboom()
- var/power = input(src, "power?", "power?") as num
+ var/power = tgui_input_number(src, "power?", "power?")
var/turf/T = get_turf(src.mob)
explosion_rec(T, power)
diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm
index 18c152c770..099bdfad1b 100644
--- a/code/game/objects/items/bodybag.dm
+++ b/code/game/objects/items/bodybag.dm
@@ -53,7 +53,7 @@
/obj/structure/closet/body_bag/attackby(var/obj/item/W as obj, mob/user as mob)
if (istype(W, /obj/item/weapon/pen))
- var/t = input(user, "What would you like the label to be?", text("[]", src.name), null) as text
+ var/t = tgui_input_text(user, "What would you like the label to be?", text("[]", src.name), null)
if (user.get_active_hand() != W)
return
if (!in_range(src, user) && src.loc != user)
diff --git a/code/game/objects/items/devices/communicator/UI_tgui.dm b/code/game/objects/items/devices/communicator/UI_tgui.dm
index d9c4e3b7ce..03048abd6f 100644
--- a/code/game/objects/items/devices/communicator/UI_tgui.dm
+++ b/code/game/objects/items/devices/communicator/UI_tgui.dm
@@ -322,7 +322,7 @@
. = TRUE
switch(action)
if("rename")
- var/new_name = sanitizeSafe(input(usr,"Please enter your name.","Communicator",usr.name) )
+ var/new_name = sanitizeSafe(tgui_input_text(usr,"Please enter your name.","Communicator",usr.name) )
if(new_name)
register_device(new_name)
@@ -376,7 +376,7 @@
to_chat(usr, "Error: Cannot connect to Exonet node. ")
return FALSE
var/their_address = params["message"]
- var/text = sanitizeSafe(input(usr,"Enter your message.","Text Message"))
+ var/text = sanitizeSafe(tgui_input_text(usr,"Enter your message.","Text Message"))
if(text)
exonet.send_message(their_address, "text", text)
im_list += list(list("address" = exonet.address, "to_address" = their_address, "im" = text))
@@ -424,7 +424,7 @@
selected_tab = params["switch_tab"]
if("edit")
- var/n = input(usr, "Please enter message", name, notehtml) as message|null
+ var/n = tgui_input_text(usr, "Please enter message", name, notehtml, multiline = TRUE)
n = sanitizeSafe(n, extra = 0)
if(n)
note = html_decode(n)
diff --git a/code/game/objects/items/devices/communicator/messaging.dm b/code/game/objects/items/devices/communicator/messaging.dm
index a9bc3afe6d..d56ed07c2e 100644
--- a/code/game/objects/items/devices/communicator/messaging.dm
+++ b/code/game/objects/items/devices/communicator/messaging.dm
@@ -102,7 +102,7 @@
switch(href_list["action"])
if("Reply")
var/obj/item/device/communicator/comm = locate(href_list["target"])
- var/message = input(usr, "Enter your message below.", "Reply")
+ var/message = tgui_input_text(usr, "Enter your message below.", "Reply")
if(message)
exonet.send_message(comm.exonet.address, "text", message)
@@ -153,7 +153,7 @@
if(choice)
var/obj/item/device/communicator/chosen_communicator = choice
var/mob/observer/dead/O = src
- var/text_message = sanitize(input(src, "What do you want the message to say?") as message)
+ var/text_message = sanitize(tgui_input_text(src, "What do you want the message to say?", multiline = TRUE))
if(text_message && O.exonet)
O.exonet.send_message(chosen_communicator.exonet.address, "text", text_message)
diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm
index 4fb99a9fc0..21ec2ea3b6 100644
--- a/code/game/objects/items/devices/gps.dm
+++ b/code/game/objects/items/devices/gps.dm
@@ -323,7 +323,7 @@ var/list/GPS_list = list()
. = TRUE
if(href_list["tag"])
- var/a = input(usr, "Please enter desired tag.", name, gps_tag) as text
+ var/a = tgui_input_text(usr, "Please enter desired tag.", name, gps_tag)
a = uppertext(copytext(sanitize(a), 1, 11))
if(in_range(src, usr))
gps_tag = a
diff --git a/code/game/objects/items/devices/megaphone.dm b/code/game/objects/items/devices/megaphone.dm
index 55f92f0cc8..26d58082b6 100644
--- a/code/game/objects/items/devices/megaphone.dm
+++ b/code/game/objects/items/devices/megaphone.dm
@@ -40,7 +40,7 @@
user.audible_message("[user.GetVoice()] [user.GetAltName()] broadcasts, \"[message]\" ", runemessage = message)
/obj/item/device/megaphone/attack_self(var/mob/living/user)
- var/message = sanitize(input(user, "Shout a message?", "Megaphone", null) as text)
+ var/message = sanitize(tgui_input_text(user, "Shout a message?", "Megaphone", null))
if(!message)
return
message = capitalize(message)
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index 188efd77e1..cd73303a73 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -50,7 +50,7 @@ GLOBAL_LIST_BOILERPLATE(all_pai_cards, /obj/item/device/paicard)
var/actual_pai_name
var/turf/location = get_turf(src)
if(choice == "No")
- var/pai_name = input(user, "Choose your character's name", "Character Name") as text
+ var/pai_name = tgui_input_text(user, "Choose your character's name", "Character Name")
actual_pai_name = sanitize_name(pai_name, ,1)
if(isnull(actual_pai_name))
return ..()
@@ -74,7 +74,7 @@ GLOBAL_LIST_BOILERPLATE(all_pai_cards, /obj/item/device/paicard)
new_pai.key = user.key
card.setPersonality(new_pai)
if(!new_pai.savefile_load(new_pai))
- var/pai_name = input(new_pai, "Choose your character's name", "Character Name") as text
+ var/pai_name = tgui_input_text(new_pai, "Choose your character's name", "Character Name")
actual_pai_name = sanitize_name(pai_name, ,1)
if(isnull(actual_pai_name))
return ..()
@@ -84,7 +84,7 @@ GLOBAL_LIST_BOILERPLATE(all_pai_cards, /obj/item/device/paicard)
new_pai.key = user.key
card.setPersonality(new_pai)
if(!new_pai.savefile_load(new_pai))
- var/pai_name = input(new_pai, "Choose your character's name", "Character Name") as text
+ var/pai_name = tgui_input_text(new_pai, "Choose your character's name", "Character Name")
actual_pai_name = sanitize_name(pai_name, ,1)
if(isnull(actual_pai_name))
return ..()
@@ -331,7 +331,7 @@ GLOBAL_LIST_BOILERPLATE(all_pai_cards, /obj/item/device/paicard)
if(2)
radio.ToggleReception()
if(href_list["setlaws"])
- var/newlaws = sanitize(input(usr, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.pai_laws) as message)
+ var/newlaws = sanitize(tgui_input_text(usr, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.pai_laws, multiline = TRUE))
if(newlaws)
pai.pai_laws = newlaws
to_chat(pai, "Your supplemental directives have been updated. Your new directives are:")
diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm
index a4ba173aed..88701319cd 100644
--- a/code/game/objects/items/devices/taperecorder.dm
+++ b/code/game/objects/items/devices/taperecorder.dm
@@ -417,7 +417,7 @@
return
else if(istype(I, /obj/item/weapon/pen))
if(loc == user && !user.incapacitated())
- var/new_name = input(user, "What would you like to label the tape?", "Tape labeling") as null|text
+ var/new_name = tgui_input_text(user, "What would you like to label the tape?", "Tape labeling")
if(isnull(new_name)) return
new_name = sanitizeSafe(new_name)
if(new_name)
diff --git a/code/game/objects/items/devices/text_to_speech.dm b/code/game/objects/items/devices/text_to_speech.dm
index 54510fb9a4..330ca97d73 100644
--- a/code/game/objects/items/devices/text_to_speech.dm
+++ b/code/game/objects/items/devices/text_to_speech.dm
@@ -22,7 +22,7 @@
named = 1
*/
- var/message = sanitize(input(user,"Choose a message to relay to those around you.") as text|null)
+ var/message = sanitize(tgui_input_text(user,"Choose a message to relay to those around you."))
if(message)
audible_message("[bicon(src)] \The [src.name] states, \"[message]\"", runemessage = "synthesized speech")
if(ismob(loc))
diff --git a/code/game/objects/items/devices/tvcamera.dm b/code/game/objects/items/devices/tvcamera.dm
index b7d7933a88..78f5a381c7 100644
--- a/code/game/objects/items/devices/tvcamera.dm
+++ b/code/game/objects/items/devices/tvcamera.dm
@@ -65,7 +65,7 @@
if(..())
return 1
if(href_list["channel"])
- var/nc = input(usr, "Channel name", "Select new channel name", channel) as text|null
+ var/nc = tgui_input_text(usr, "Channel name", "Select new channel name", channel)
if(nc)
channel = nc
camera.c_tag = channel
diff --git a/code/game/objects/items/devices/whistle.dm b/code/game/objects/items/devices/whistle.dm
index 54ed540475..846fae358d 100644
--- a/code/game/objects/items/devices/whistle.dm
+++ b/code/game/objects/items/devices/whistle.dm
@@ -19,7 +19,7 @@
to_chat(usr, "The hailer is fried. The tiny input screen just shows a waving ASCII penis.")
return
- var/new_message = input(usr, "Please enter new message (leave blank to reset).") as text
+ var/new_message = tgui_input_text(usr, "Please enter new message (leave blank to reset).")
if(!new_message || new_message == "")
use_message = "Halt! Security!"
else
diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm
index ff9bbad216..28c89140d0 100644
--- a/code/game/objects/items/robot/robot_parts.dm
+++ b/code/game/objects/items/robot/robot_parts.dm
@@ -229,7 +229,7 @@
to_chat(user, "The MMI must go in after everything else! ")
if (istype(W, /obj/item/weapon/pen))
- var/t = sanitizeSafe(input(user, "Enter new robot name", src.name, src.created_name), MAX_NAME_LEN)
+ var/t = sanitizeSafe(tgui_input_text(user, "Enter new robot name", src.name, src.created_name), MAX_NAME_LEN)
if (!t)
return
if (!in_range(src, usr) && src.loc != usr)
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index f4f6269231..675c1141d3 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -399,7 +399,7 @@
/obj/item/stack/attack_hand(mob/user as mob)
if (user.get_inactive_hand() == src)
- var/N = input(usr, "How many stacks of [src] would you like to split off? There are currently [amount].", "Split stacks", 1) as num|null
+ var/N = tgui_input_number(usr, "How many stacks of [src] would you like to split off? There are currently [amount].", "Split stacks", 1, amount, 1)
if(N)
var/obj/item/stack/F = src.split(N)
if (F)
diff --git a/code/game/objects/items/stacks/tiles/fifty_spawner_tiles.dm b/code/game/objects/items/stacks/tiles/fifty_spawner_tiles.dm
index a4a2f7739b..a1ba9ae0e2 100644
--- a/code/game/objects/items/stacks/tiles/fifty_spawner_tiles.dm
+++ b/code/game/objects/items/stacks/tiles/fifty_spawner_tiles.dm
@@ -28,14 +28,22 @@
name = "stack of teal carpet"
type_to_spawn = /obj/item/stack/tile/carpet/teal
-/obj/fiftyspawner/decocarpet
- name = "stack of deco carpet"
- type_to_spawn = /obj/item/stack/tile/carpet/deco
+/obj/fiftyspawner/geocarpet
+ name = "stack of geometric carpet"
+ type_to_spawn = /obj/item/stack/tile/carpet/geo
/obj/fiftyspawner/retrocarpet
- name = "stack of retro carpet"
+ name = "stack of blue retro carpet"
type_to_spawn = /obj/item/stack/tile/carpet/retro
+/obj/fiftyspawner/retrocarpet_red
+ name = "stack of red retro carpet"
+ type_to_spawn = /obj/item/stack/tile/carpet/retro_red
+
+/obj/fiftyspawner/happycarpet
+ name = "stack of happy carpet"
+ type_to_spawn = /obj/item/stack/tile/carpet/happy
+
/obj/fiftyspawner/floor
name = "stack of floor tiles"
type_to_spawn = /obj/item/stack/tile/floor
diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm
index 286f368740..c878032b4c 100644
--- a/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -85,6 +85,26 @@
desc = "An easy to fit wooden floor tile. It's blue!"
icon_state = "tile-sifwood"
+/obj/item/stack/tile/wood/alt
+ name = "wood floor tile"
+ singular_name = "wood floor tile"
+ icon_state = "tile-wood_tile"
+
+/obj/item/stack/tile/wood/parquet
+ name = "parquet wood floor tile"
+ singular_name = "parquet wood floor tile"
+ icon_state = "tile-wood_parquet"
+
+/obj/item/stack/tile/wood/panel
+ name = "large wood floor tile"
+ singular_name = "large wood floor tile"
+ icon_state = "tile-wood_large"
+
+/obj/item/stack/tile/wood/tile
+ name = "tiled wood floor tile"
+ singular_name = "tiled wood floor tile"
+ icon_state = "tile-wood_tile"
+
/obj/item/stack/tile/wood/cyborg
name = "wood floor tile synthesizer"
desc = "A device that makes wood floor tiles."
@@ -117,8 +137,23 @@
icon_state = "tile-tealcarpet"
no_variants = FALSE
-/obj/item/stack/tile/carpet/bcarpet //YW EDIT: Commented out to help with upstream merging. Get on this you fucking virgo bois. -yw //CHOMP Comment: Yawn commented out this block, but CHOMP already commented out this stuff so I just removed theirs.
+/obj/item/stack/tile/carpet/geo
+ icon_state = "tile-carpet-deco"
+ desc = "A piece of carpet with a gnarly geometric design. It is the same size as a normal floor tile!"
+/obj/item/stack/tile/carpet/retro
+ icon_state = "tile-carpet-retro"
+ desc = "A piece of carpet with totally wicked blue space patterns. It is the same size as a normal floor tile!"
+
+/obj/item/stack/tile/carpet/retro_red
+ icon_state = "tile-carpet-retro-red"
+ desc = "A piece of carpet with red-ical space patterns. It is the same size as a normal floor tile!"
+
+/obj/item/stack/tile/carpet/happy
+ icon_state = "tile-carpet-happy"
+ desc = "A piece of carpet with happy patterns. It is the same size as a normal floor tile!"
+
+/obj/item/stack/tile/carpet/bcarpet //YW EDIT: Commented out to help with upstream merging. Get on this you fucking virgo bois. -yw //CHOMP Comment: Yawn commented out this block, but CHOMP already commented out this stuff so I just removed theirs.
icon_state = "tile-carpet"
/obj/item/stack/tile/carpet/blucarpet
icon_state = "tile-carpet"
@@ -133,10 +168,6 @@
/obj/item/stack/tile/carpet/oracarpet
icon_state = "tile-carpet"
*/
-/obj/item/stack/tile/carpet/deco
- icon_state = "tile-carpet-deco"
-/obj/item/stack/tile/carpet/retro
- icon_state = "tile-carpet-retro"
/obj/item/stack/tile/floor
name = "floor tile"
diff --git a/code/game/objects/items/stacks/tiles/tile_types_ch.dm b/code/game/objects/items/stacks/tiles/tile_types_ch.dm
index 06afcec73b..eeac79667e 100644
--- a/code/game/objects/items/stacks/tiles/tile_types_ch.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types_ch.dm
@@ -101,4 +101,20 @@
singular_name = "orange carpet"
desc = "A piece of orange carpet. It is the same size as a normal floor tile!"
icon_state = "tile-carpet"
- default_type = MAT_CARPET_ORANGE
\ No newline at end of file
+ default_type = MAT_CARPET_ORANGE
+
+/obj/item/stack/tile/carpet/geo
+ icon_state = "tile-carpet-deco"
+ desc = "A piece of carpet with a gnarly geometric design. It is the same size as a normal floor tile!"
+
+/obj/item/stack/tile/carpet/retro
+ icon_state = "tile-carpet-retro"
+ desc = "A piece of carpet with totally wicked blue space patterns. It is the same size as a normal floor tile!"
+
+/obj/item/stack/tile/carpet/retro_red
+ icon_state = "tile-carpet-retro-red"
+ desc = "A piece of carpet with red-ical space patterns. It is the same size as a normal floor tile!"
+
+/obj/item/stack/tile/carpet/happy
+ icon_state = "tile-carpet-happy"
+ desc = "A piece of carpet with happy patterns. It is the same size as a normal floor tile!"
diff --git a/code/game/objects/items/surplus_voucher_ch.dm b/code/game/objects/items/surplus_voucher_ch.dm
index 818bda9bbc..1b7fde05fd 100644
--- a/code/game/objects/items/surplus_voucher_ch.dm
+++ b/code/game/objects/items/surplus_voucher_ch.dm
@@ -161,7 +161,7 @@
/obj/item/surplus_voucher/ser/proc/spawn_item(var/turf/T)
var/path = pick(prob(4);/obj/item/weapon/reagent_containers/food/drinks/milk,
- prob(4);/obj/item/weapon/reagent_containers/food/condiment/flour,
+ prob(4);/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
prob(4);/obj/item/weapon/reagent_containers/food/drinks/soymilk,
prob(4);/obj/item/weapon/storage/fancy/egg_box,
prob(3);/obj/item/weapon/reagent_containers/food/snacks/meat,
diff --git a/code/game/objects/items/toys/toys.dm b/code/game/objects/items/toys/toys.dm
index 4b5e671e49..4c451f583e 100644
--- a/code/game/objects/items/toys/toys.dm
+++ b/code/game/objects/items/toys/toys.dm
@@ -843,7 +843,7 @@
if(!M.mind)
return 0
- var/input = sanitizeSafe(input(usr, "What do you want to name the plushie?", ,""), MAX_NAME_LEN)
+ var/input = sanitizeSafe(tgui_input_text(usr, "What do you want to name the plushie?", ,""), MAX_NAME_LEN)
if(src && input && !M.stat && in_range(M,src))
name = input
diff --git a/code/game/objects/items/weapons/AI_modules.dm b/code/game/objects/items/weapons/AI_modules.dm
index e366a3e8ee..2eec58b136 100644
--- a/code/game/objects/items/weapons/AI_modules.dm
+++ b/code/game/objects/items/weapons/AI_modules.dm
@@ -133,7 +133,7 @@ AI MODULES
/obj/item/weapon/aiModule/safeguard/attack_self(var/mob/user as mob)
..()
- var/targName = sanitize(input(usr, "Please enter the name of the person to safeguard.", "Safeguard who?", user.name))
+ var/targName = sanitize(tgui_input_text(usr, "Please enter the name of the person to safeguard.", "Safeguard who?", user.name))
targetName = targName
desc = text("A 'safeguard' AI module: 'Safeguard []. Anyone threatening or attempting to harm [] is no longer to be considered a crew member, and is a threat which must be neutralized.'", targetName, targetName)
@@ -159,7 +159,7 @@ AI MODULES
/obj/item/weapon/aiModule/oneHuman/attack_self(var/mob/user as mob)
..()
- var/targName = sanitize(input(usr, "Please enter the name of the person who is the only crew member.", "Who?", user.real_name))
+ var/targName = sanitize(tgui_input_text(usr, "Please enter the name of the person who is the only crew member.", "Who?", user.real_name))
targetName = targName
desc = text("A 'one crew member' AI module: 'Only [] is a crew member.'", targetName)
@@ -244,7 +244,7 @@ AI MODULES
if(new_lawpos < MIN_SUPPLIED_LAW_NUMBER) return
lawpos = min(new_lawpos, MAX_SUPPLIED_LAW_NUMBER)
var/newlaw = ""
- var/targName = sanitize(input(usr, "Please enter a new law for the AI.", "Freeform Law Entry", newlaw))
+ var/targName = sanitize(tgui_input_text(usr, "Please enter a new law for the AI.", "Freeform Law Entry", newlaw))
newFreeFormLaw = targName
desc = "A 'freeform' AI module: ([lawpos]) '[newFreeFormLaw]'"
@@ -357,7 +357,7 @@ AI MODULES
/obj/item/weapon/aiModule/freeformcore/attack_self(var/mob/user as mob)
..()
var/newlaw = ""
- var/targName = sanitize(input(usr, "Please enter a new core law for the AI.", "Freeform Law Entry", newlaw))
+ var/targName = sanitize(tgui_input_text(usr, "Please enter a new core law for the AI.", "Freeform Law Entry", newlaw))
newFreeFormLaw = targName
desc = "A 'freeform' Core AI module: '[newFreeFormLaw]'"
@@ -381,7 +381,7 @@ AI MODULES
/obj/item/weapon/aiModule/syndicate/attack_self(var/mob/user as mob)
..()
var/newlaw = ""
- var/targName = sanitize(input(usr, "Please enter a new law for the AI.", "Freeform Law Entry", newlaw))
+ var/targName = sanitize(tgui_input_text(usr, "Please enter a new law for the AI.", "Freeform Law Entry", newlaw))
newFreeFormLaw = targName
desc = "A hacked AI law module: '[newFreeFormLaw]'"
diff --git a/code/game/objects/items/weapons/circuitboards/computer/camera_monitor.dm b/code/game/objects/items/weapons/circuitboards/computer/camera_monitor.dm
index 9d47a962e7..05ef9a7428 100644
--- a/code/game/objects/items/weapons/circuitboards/computer/camera_monitor.dm
+++ b/code/game/objects/items/weapons/circuitboards/computer/camera_monitor.dm
@@ -74,7 +74,7 @@
to_chat(user, "Circuit controls are locked. ")
return
var/existing_networks = jointext(network,",")
- var/input = sanitize(input(usr, "Which networks would you like to connect this camera console circuit to? Seperate networks with a comma. No Spaces!\nFor example: SS13,Security,Secret ", "Multitool-Circuitboard interface", existing_networks))
+ var/input = sanitize(tgui_input_text(usr, "Which networks would you like to connect this camera console circuit to? Seperate networks with a comma. No Spaces!\nFor example: SS13,Security,Secret ", "Multitool-Circuitboard interface", existing_networks))
if(!input)
to_chat(usr, "No input found please hang up and try your call again.")
return
diff --git a/code/game/objects/items/weapons/explosives.dm b/code/game/objects/items/weapons/explosives.dm
index d81e6e444e..5e1d700602 100644
--- a/code/game/objects/items/weapons/explosives.dm
+++ b/code/game/objects/items/weapons/explosives.dm
@@ -39,7 +39,7 @@
..()
/obj/item/weapon/plastique/attack_self(mob/user as mob)
- var/newtime = input(usr, "Please set the timer.", "Timer", 10) as num
+ var/newtime = tgui_input_number(usr, "Please set the timer.", "Timer", 10, 60000, 10)
if(user.get_active_hand() == src)
newtime = CLAMP(newtime, 10, 60000)
timer = newtime
diff --git a/code/game/objects/items/weapons/implants/implant.dm b/code/game/objects/items/weapons/implants/implant.dm
index 8de1fb7f80..52ebd29e7c 100644
--- a/code/game/objects/items/weapons/implants/implant.dm
+++ b/code/game/objects/items/weapons/implants/implant.dm
@@ -288,7 +288,7 @@ Implant Specifics: "}
/obj/item/weapon/implant/explosive/post_implant(mob/source as mob)
elevel = tgui_alert(usr, "What sort of explosion would you prefer?", "Implant Intent", list("Localized Limb", "Destroy Body", "Full Explosion"))
- phrase = input(usr, "Choose activation phrase:") as text
+ phrase = tgui_input_text(usr, "Choose activation phrase:")
var/list/replacechars = list("'" = "","\"" = "",">" = "","<" = "","(" = "",")" = "")
phrase = replace_characters(phrase, replacechars)
usr.mind.store_memory("Explosive implant in [source] can be activated by saying something containing the phrase ''[src.phrase]'', say [src.phrase] to attempt to activate.", 0, 0)
diff --git a/code/game/objects/items/weapons/implants/implantcase.dm b/code/game/objects/items/weapons/implants/implantcase.dm
index 9162be15eb..1517e2ada3 100644
--- a/code/game/objects/items/weapons/implants/implantcase.dm
+++ b/code/game/objects/items/weapons/implants/implantcase.dm
@@ -21,7 +21,7 @@
/obj/item/weapon/implantcase/attackby(obj/item/weapon/I as obj, mob/user as mob)
..()
if (istype(I, /obj/item/weapon/pen))
- var/t = input(user, "What would you like the label to be?", text("[]", src.name), null) as text
+ var/t = tgui_input_text(user, "What would you like the label to be?", text("[]", src.name), null)
if (user.get_active_hand() != I)
return
if((!in_range(src, usr) && src.loc != user))
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
index 63a5613c88..66695f3511 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
@@ -3,8 +3,10 @@
req_access = list(access_kitchen)
starts_with = list(
- /obj/item/weapon/reagent_containers/food/condiment/flour = 7,
- /obj/item/weapon/reagent_containers/food/condiment/sugar = 2,
+ /obj/item/weapon/reagent_containers/food/condiment/carton/flour = 6,
+ /obj/item/weapon/reagent_containers/food/condiment/carton/sugar = 1,
+ /obj/item/weapon/reagent_containers/food/condiment/carton/flour/rustic = 1,
+ /obj/item/weapon/reagent_containers/food/condiment/carton/sugar/rustic = 1,
/obj/item/weapon/reagent_containers/food/condiment/spacespice = 2
)
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index 45cb599429..330c4e4046 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -90,7 +90,9 @@
src.set_dir(turn(src.dir, 90))
/obj/structure/closet/crate/attackby(obj/item/weapon/W as obj, mob/user as mob)
- if(opened)
+ if(W.is_wrench() && istype(src,/obj/structure/closet/crate/bin))
+ return ..()
+ else if(opened)
if(isrobot(user))
return
if(W.loc != user) // This should stop mounted modules ending up outside the module.
diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index 760c111466..b18feb7f88 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -3,7 +3,7 @@
icon_state = "girder"
anchored = TRUE
density = TRUE
- plane = PLATING_PLANE
+ layer = TABLE_LAYER //CHOMPEdit - moved so that they render above catwalks.
w_class = ITEMSIZE_HUGE
var/state = 0
var/health = 200
diff --git a/code/game/turfs/flooring/flooring.dm b/code/game/turfs/flooring/flooring.dm
index ec821278a8..f6087a26c8 100644
--- a/code/game/turfs/flooring/flooring.dm
+++ b/code/game/turfs/flooring/flooring.dm
@@ -332,18 +332,30 @@ var/list/flooring_types
icon_base = "tealcarpet"
build_type = /obj/item/stack/tile/carpet/teal
-/decl/flooring/carpet/deco
- name = "deco carpet"
- icon_base = "decocarpet"
- build_type = /obj/item/stack/tile/carpet/deco
+/decl/flooring/carpet/geo
+ name = "geometric carpet"
+ icon_base = "geocarpet"
+ build_type = /obj/item/stack/tile/carpet/geo
flags = TURF_REMOVE_CROWBAR | TURF_CAN_BURN
/decl/flooring/carpet/retro
- name = "retro carpet"
+ name = "blue retro carpet"
icon_base = "retrocarpet"
build_type = /obj/item/stack/tile/carpet/retro
flags = TURF_REMOVE_CROWBAR | TURF_CAN_BURN
+/decl/flooring/carpet/retro_red
+ name = "red retro carpet"
+ icon_base = "retrocarpet_red"
+ build_type = /obj/item/stack/tile/carpet/retro_red
+ flags = TURF_REMOVE_CROWBAR | TURF_CAN_BURN
+
+/decl/flooring/carpet/happy
+ name = "happy carpet"
+ icon_base = "happycarpet"
+ build_type = /obj/item/stack/tile/carpet/happy
+ flags = TURF_REMOVE_CROWBAR | TURF_CAN_BURN
+
/decl/flooring/tiling
name = "floor"
desc = "Scuffed from the passage of countless greyshirts."
@@ -507,6 +519,31 @@ var/list/flooring_types
icon_base = "sifwood"
build_type = /obj/item/stack/tile/wood/sif
+/decl/flooring/wood/alt
+ icon = 'icons/turf/flooring/wood.dmi'
+ icon_base = "wood"
+ build_type = /obj/item/stack/tile/wood/alt
+
+/decl/flooring/wood/alt/panel
+ desc = "Polished wooden panels."
+ icon = 'icons/turf/flooring/wood.dmi'
+ icon_base = "wood_panel"
+ has_damage_range = 2
+ build_type = /obj/item/stack/tile/wood/panel
+
+/decl/flooring/wood/alt/parquet
+ desc = "Polished wooden tiles."
+ icon = 'icons/turf/flooring/wood.dmi'
+ icon_base = "wood_parquet"
+ build_type = /obj/item/stack/tile/wood/parquet
+
+/decl/flooring/wood/alt/tile
+ desc = "Polished wooden tiles."
+ icon = 'icons/turf/flooring/wood.dmi'
+ icon_base = "wood_tile"
+ has_damage_range = 2
+ build_type = /obj/item/stack/tile/wood/tile
+
/decl/flooring/reinforced
name = "reinforced floor"
desc = "Heavily reinforced with steel rods."
diff --git a/code/game/turfs/flooring/flooring_premade.dm b/code/game/turfs/flooring/flooring_premade.dm
index 685f2ee374..fad0d29562 100644
--- a/code/game/turfs/flooring/flooring_premade.dm
+++ b/code/game/turfs/flooring/flooring_premade.dm
@@ -19,11 +19,11 @@
icon_state = "tealcarpet"
initial_flooring = /decl/flooring/carpet/tealcarpet
-/turf/simulated/floor/carpet/deco
+/turf/simulated/floor/carpet/geo
name = "deco carpet"
icon_state = "decocarpet"
- initial_flooring = /decl/flooring/carpet/deco
-
+ initial_flooring = /decl/flooring/carpet/geo
+
/turf/simulated/floor/carpet/retro
name = "retro carpet"
icon_state = "retrocarpet"
@@ -60,6 +60,26 @@
icon_state = "oracarpet"
initial_flooring = /decl/flooring/carpet/oracarpet
+/turf/simulated/floor/carpet/geo
+ name = "geometric carpet"
+ icon_state = "geocarpet"
+ initial_flooring = /decl/flooring/carpet/geo
+
+/turf/simulated/floor/carpet/retro
+ name = "blue retro carpet"
+ icon_state = "retrocarpet"
+ initial_flooring = /decl/flooring/carpet/retro
+
+/turf/simulated/floor/carpet/retro_red
+ name = "red retro carpet"
+ icon_state = "retrocarpet_red"
+ initial_flooring = /decl/flooring/carpet/retro_red
+
+/turf/simulated/floor/carpet/happy
+ name = "happy carpet"
+ icon_state = "happycarpet"
+ initial_flooring = /decl/flooring/carpet/happy
+
/turf/simulated/floor/bluegrid
name = "mainframe floor"
icon = 'icons/turf/flooring/circuit.dmi'
@@ -79,24 +99,67 @@
initial_flooring = /decl/flooring/wood
/turf/simulated/floor/wood/broken
- icon_state = "wood_broken0" // This gets changed when spawned.
+ icon_state = "wood-broken0" // This gets changed when spawned.
-/turf/simulated/floor/wood/broken/Initialize()
+/turf/simulated/floor/wood/broken/LateInitialize()
+ . = ..()
break_tile()
- return ..()
/turf/simulated/floor/wood/sif
name = "alien wooden floor"
- icon = 'icons/turf/flooring/wood.dmi'
icon_state = "sifwood"
initial_flooring = /decl/flooring/wood/sif
/turf/simulated/floor/wood/sif/broken
- icon_state = "sifwood_broken0" // This gets changed when spawned.
+ icon_state = "sifwood-broken0" // This gets changed when spawned.
-/turf/simulated/floor/wood/sif/broken/Initialize()
+/turf/simulated/floor/wood/sif/broken/LateInitialize()
+ . = ..()
+ break_tile()
+
+/turf/simulated/floor/wood/alt
+ icon = 'icons/turf/flooring/wood.dmi'
+ initial_flooring = /decl/flooring/wood/alt
+
+/turf/simulated/floor/wood/alt/broken
+ icon_state = "wood-broken0" // This gets changed when spawned.
+
+/turf/simulated/floor/wood/alt/broken/LateInitialize()
+ . = ..()
+ break_tile()
+
+/turf/simulated/floor/wood/alt/tile
+ icon_state = "wood_tile"
+ initial_flooring = /decl/flooring/wood/alt/tile
+
+/turf/simulated/floor/wood/alt/tile/broken
+ icon_state = "wood_tile-broken0" // This gets changed when spawned.
+
+/turf/simulated/floor/wood/alt/tile/broken/LateInitialize()
+ . = ..()
+ break_tile()
+
+/turf/simulated/floor/wood/alt/panel
+ icon_state = "wood_panel"
+ initial_flooring = /decl/flooring/wood/alt/panel
+
+/turf/simulated/floor/wood/alt/panel/broken
+ icon_state = "wood_panel-broken0" // This gets changed when spawned.
+
+/turf/simulated/floor/wood/alt/panel/broken/LateInitialize()
+ . = ..()
+ break_tile()
+
+/turf/simulated/floor/wood/alt/parquet
+ icon_state = "wood_parquet"
+ initial_flooring = /decl/flooring/wood/alt/parquet
+
+/turf/simulated/floor/wood/alt/parquet/broken
+ icon_state = "wood_parquet-broken0" // This gets changed when spawned.
+
+/turf/simulated/floor/wood/alt/parquet/broken/LateInitialize()
+ . = ..()
break_tile()
- return ..()
/turf/simulated/floor/grass
name = "grass patch"
diff --git a/code/game/turfs/simulated/floor_icon.dm b/code/game/turfs/simulated/floor_icon.dm
index 420d8628b5..82153557a8 100644
--- a/code/game/turfs/simulated/floor_icon.dm
+++ b/code/game/turfs/simulated/floor_icon.dm
@@ -75,7 +75,10 @@ var/image/no_ceiling_image = null
icon_state = "dmg[rand(1,4)]"
else if(flooring)
if(!isnull(broken) && (flooring.flags & TURF_CAN_BREAK))
- add_overlay(flooring.get_flooring_overlay("[flooring.icon_base]-broken-[broken]","broken[broken]"))
+ if(istype(src, /turf/simulated/floor/wood))
+ add_overlay(flooring.get_flooring_overlay("[flooring.icon_base]-broken-[broken]","[flooring.icon_base]-broken[broken]"))
+ else
+ add_overlay(flooring.get_flooring_overlay("[flooring.icon_base]-broken-[broken]","broken[broken]"))
if(!isnull(burnt) && (flooring.flags & TURF_CAN_BURN))
add_overlay(flooring.get_flooring_overlay("[flooring.icon_base]-burned-[burnt]","burned[burnt]"))
diff --git a/code/modules/admin/DB ban/functions.dm b/code/modules/admin/DB ban/functions.dm
index 1637c6d7f3..9c25b4f1e5 100644
--- a/code/modules/admin/DB ban/functions.dm
+++ b/code/modules/admin/DB ban/functions.dm
@@ -184,7 +184,7 @@
switch(param)
if("reason")
if(!value)
- value = sanitize(input(usr, "Insert the new reason for [pckey]'s ban", "New Reason", "[reason]", null) as null|text)
+ value = sanitize(tgui_input_text(usr, "Insert the new reason for [pckey]'s ban", "New Reason", "[reason]", null))
value = sql_sanitize_text(value)
if(!value)
to_chat(usr, "Cancelled")
@@ -196,7 +196,7 @@
qdel(update_query) //CHOMPEdit TGSQL
if("duration")
if(!value)
- value = input(usr, "Insert the new duration (in minutes) for [pckey]'s ban", "New Duration", "[duration]", null) as null|num
+ value = tgui_input_number(usr, "Insert the new duration (in minutes) for [pckey]'s ban", "New Duration", "[duration]", null)
if(!isnum(value) || !value)
to_chat(usr, "Cancelled")
return
diff --git a/code/modules/admin/ToRban.dm b/code/modules/admin/ToRban.dm
index 4171ea6a37..dfe4ba88e2 100644
--- a/code/modules/admin/ToRban.dm
+++ b/code/modules/admin/ToRban.dm
@@ -77,7 +77,7 @@
if("remove all")
to_chat(src, "[TORFILE] was [fdel(TORFILE)?"":"not "]removed. ")
if("find")
- var/input = input(src,"Please input an IP address to search for:","Find ToR ban",null) as null|text
+ var/input = tgui_input_text(src,"Please input an IP address to search for:","Find ToR ban",null)
if(input)
if(ToRban_isbanned(input))
to_chat(src, "Address is a known ToR address ")
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index e015cbd7db..bb88d9d7fb 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -218,7 +218,6 @@ var/global/floorIsLava = 0
/datum/player_info/var/content // text content of the information
/datum/player_info/var/timestamp // Because this is bloody annoying
-#define PLAYER_NOTES_ENTRIES_PER_PAGE 50
/datum/admins/proc/PlayerNotes()
set category = "Admin"
set name = "Player Notes"
@@ -235,56 +234,20 @@ var/global/floorIsLava = 0
if (!istype(src,/datum/admins))
to_chat(usr, "Error: you are not an admin!")
return
- var/filter = input(usr, "Filter string (case-insensitive regex)", "Player notes filter") as text|null
+ var/filter = tgui_input_text(usr, "Filter string (case-insensitive regex)", "Player notes filter")
PlayerNotesPage(1, filter)
/datum/admins/proc/PlayerNotesPage(page, filter)
- var/dat = "Player notes - Apply Filter "
var/savefile/S=new("data/player_notes.sav")
var/list/note_keys
S >> note_keys
- if(!note_keys)
- dat += "No notes found."
- else
- dat += ""
+
+ if(note_keys)
note_keys = sortList(note_keys)
- if(filter)
- var/list/results = list()
- var/regex/needle = regex(filter, "i")
- for(var/haystack in note_keys)
- if(needle.Find(haystack))
- results += haystack
- note_keys = results
-
- // Display the notes on the current page
- var/number_pages = note_keys.len / PLAYER_NOTES_ENTRIES_PER_PAGE
- // Emulate CEILING(why does BYOND not have ceil, 1)
- if(number_pages != round(number_pages))
- number_pages = round(number_pages) + 1
- var/page_index = page - 1
-
- if(page_index < 0 || page_index >= number_pages)
- dat += "No keys found. "
- else
- var/lower_bound = page_index * PLAYER_NOTES_ENTRIES_PER_PAGE + 1
- var/upper_bound = (page_index + 1) * PLAYER_NOTES_ENTRIES_PER_PAGE
- upper_bound = min(upper_bound, note_keys.len)
- for(var/index = lower_bound, index <= upper_bound, index++)
- var/t = note_keys[index]
- dat += "[t] "
-
- dat += "
"
-
- // Display a footer to select different pages
- for(var/index = 1, index <= number_pages, index++)
- if(index == page)
- dat += ""
- dat += "[index] "
- if(index == page)
- dat += " "
-
- usr << browse(dat, "window=player_notes;size=400x400")
+ var/datum/tgui_module/player_notes/A = new(src)
+ A.ckeys = note_keys
+ A.tgui_interact(usr)
/datum/admins/proc/player_has_info(var/key as text)
@@ -303,44 +266,10 @@ var/global/floorIsLava = 0
if (!istype(src,/datum/admins))
to_chat(usr, "Error: you are not an admin!")
return
- var/dat = "Info on [key] "
- dat += ""
-
- var/p_age = "unknown"
- for(var/client/C in GLOB.clients)
- if(C.ckey == key)
- p_age = C.player_age
- break
- dat +="Player age: [p_age] "
-
- var/savefile/info = new("data/player_saves/[copytext(key, 1, 2)]/[key]/info.sav")
- var/list/infos
- info >> infos
- if(!infos)
- dat += "No information found on the given key. "
- else
- var/update_file = 0
- var/i = 0
- for(var/datum/player_info/I in infos)
- i += 1
- if(!I.timestamp)
- I.timestamp = "Pre-4/3/2012"
- update_file = 1
- if(!I.rank)
- I.rank = "N/A"
- update_file = 1
- dat += "[I.content] by [I.author] ([I.rank]) on [I.timestamp] "
- if(I.author == usr.key || I.author == "Adminbot" || ishost(usr))
- dat += "Remove "
- dat += " "
- if(update_file) info << infos
-
- dat += " "
- dat += "Add Comment "
-
- dat += ""
- usr << browse(dat, "window=adminplayerinfo;size=480x480")
+ var/datum/tgui_module/player_notes_info/A = new(src)
+ A.key = key
+ A.tgui_interact(usr)
/datum/admins/proc/access_news_network() //MARKER
@@ -688,7 +617,7 @@ var/global/floorIsLava = 0
set desc="Announce your desires to the world"
if(!check_rights(0)) return
- var/message = tgui_input_message(usr, "Global message to send:", "Admin Announce")
+ var/message = tgui_input_text(usr, "Global message to send:", "Admin Announce", multiline = TRUE)
if(message)
if(!check_rights(R_SERVER,0))
message = sanitize(message, 500, extra = 0)
@@ -709,12 +638,12 @@ var/datum/announcement/minor/admin_min_announcer = new
var/channel = tgui_input_list(usr, "Channel for message:","Channel", radiochannels)
if(channel) //They picked a channel
- var/sender = input(usr, "Name of sender (max 75):", "Announcement", "Announcement Computer") as null|text
+ var/sender = tgui_input_text(usr, "Name of sender (max 75):", "Announcement", "Announcement Computer")
if(sender) //They put a sender
sender = sanitize(sender, 75, extra = 0)
- var/message = input(usr, "Message content (max 500):", "Contents", "This is a test of the announcement system.") as null|message
- var/msgverb = input(usr, "Name of verb (Such as 'states', 'says', 'asks', etc):", "Verb", "says") as null|text //VOREStation Addition
+ var/message = tgui_input_text(usr, "Message content (max 500):", "Contents", "This is a test of the announcement system.", multiline = TRUE)
+ var/msgverb = tgui_input_text(usr, "Name of verb (Such as 'states', 'says', 'asks', etc):", "Verb", "says")
if(message) //They put a message
message = sanitize(message, 500, extra = 0)
//VOREStation Edit Start
@@ -752,7 +681,7 @@ var/datum/announcement/minor/admin_min_announcer = new
The above will result in those messages playing, with a 5 second gap between each. Maximum of 20 messages allowed.")
var/list/decomposed
- var/message = input(usr,"See your chat box for instructions. Keep a copy elsewhere in case it is rejected when you click OK.", "Input Conversation", "") as null|message
+ var/message = tgui_input_text(usr,"See your chat box for instructions. Keep a copy elsewhere in case it is rejected when you click OK.", "Input Conversation", "", multiline = TRUE)
if(!message)
return
@@ -1120,7 +1049,7 @@ var/datum/announcement/minor/admin_min_announcer = new
if(!seedtype || !SSplants.seeds[seedtype])
return
- var/amount = input(usr, "Amount of fruit to spawn", "Fruit Amount", 1) as null|num
+ var/amount = tgui_input_number(usr, "Amount of fruit to spawn", "Fruit Amount", 1)
if(!isnull(amount))
var/datum/seed/S = SSplants.seeds[seedtype]
S.harvest(usr,0,0,amount)
@@ -1533,7 +1462,7 @@ var/datum/announcement/minor/admin_min_announcer = new
var/crystals
if(check_rights(R_ADMIN|R_EVENT))
- crystals = input(usr, "Amount of telecrystals for [H.ckey], currently [H.mind.tcrystals].", crystals) as null|num
+ crystals = tgui_input_number(usr, "Amount of telecrystals for [H.ckey], currently [H.mind.tcrystals].", crystals)
if (!isnull(crystals))
H.mind.tcrystals = crystals
var/msg = "[key_name(usr)] has modified [H.ckey]'s telecrystals to [crystals]."
@@ -1549,7 +1478,7 @@ var/datum/announcement/minor/admin_min_announcer = new
var/crystals
if(check_rights(R_ADMIN|R_EVENT))
- crystals = input(usr, "Amount of telecrystals to give to [H.ckey], currently [H.mind.tcrystals].", crystals) as null|num
+ crystals = tgui_input_number(usr, "Amount of telecrystals to give to [H.ckey], currently [H.mind.tcrystals].", crystals)
if (!isnull(crystals))
H.mind.tcrystals += crystals
var/msg = "[key_name(usr)] has added [crystals] to [H.ckey]'s telecrystals."
@@ -1572,7 +1501,7 @@ var/datum/announcement/minor/admin_min_announcer = new
to_chat(usr, "Error: you are not an admin!")
return
- var/replyorigin = input(src.owner, "Please specify who the fax is coming from", "Origin") as text|null
+ var/replyorigin = tgui_input_text(src.owner, "Please specify who the fax is coming from", "Origin")
var/obj/item/weapon/paper/admin/P = new /obj/item/weapon/paper/admin( null ) //hopefully the null loc won't cause trouble for us
faxreply = P
@@ -1587,7 +1516,7 @@ var/datum/announcement/minor/admin_min_announcer = new
/datum/admins/var/obj/item/weapon/paper/admin/faxreply // var to hold fax replies in
/datum/admins/proc/faxCallback(var/obj/item/weapon/paper/admin/P, var/obj/machinery/photocopier/faxmachine/destination)
- var/customname = input(src.owner, "Pick a title for the report", "Title") as text|null
+ var/customname = tgui_input_text(src.owner, "Pick a title for the report", "Title")
P.name = "[P.origin] - [customname]"
P.desc = "This is a paper titled '" + P.name + "'."
diff --git a/code/modules/admin/admin_memo.dm b/code/modules/admin/admin_memo.dm
index 57f3fe3197..ba15e99c2d 100644
--- a/code/modules/admin/admin_memo.dm
+++ b/code/modules/admin/admin_memo.dm
@@ -18,7 +18,7 @@
/client/proc/admin_memo_write()
var/savefile/F = new(MEMOFILE)
if(F)
- var/memo = sanitize(input(src,"Type your memo\n(Leaving it blank will delete your current memo):","Write Memo",null) as null|message, extra = 0)
+ var/memo = sanitize(tgui_input_text(src,"Type your memo\n(Leaving it blank will delete your current memo):","Write Memo",null, multiline = TRUE), extra = 0)
switch(memo)
if(null)
return
diff --git a/code/modules/admin/admin_report.dm b/code/modules/admin/admin_report.dm
index 6a6a5d241e..db87c39c71 100644
--- a/code/modules/admin/admin_report.dm
+++ b/code/modules/admin/admin_report.dm
@@ -127,7 +127,7 @@ world/New()
if(M.client)
CID = M.client.computer_id
- var/body = input(src.mob, "Describe in detail what you're reporting [M] for", "Report") as null|text
+ var/body = tgui_input_text(src.mob, "Describe in detail what you're reporting [M] for", "Report")
if(!body) return
@@ -174,7 +174,7 @@ world/New()
if(!found)
to_chat(src, "* An error occured, sorry. ")
- var/body = input(src.mob, "Enter a body for the news", "Body") as null|message
+ var/body = tgui_input_text(src.mob, "Enter a body for the news", "Body", multiline = TRUE)
if(!body) return
found.body = body
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 3207e293a5..5cc05e5360 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -35,11 +35,24 @@
set category = "Admin"
set name = "Aghost"
if(!holder) return
+
+ var/build_mode
+ if(src.buildmode)
+ build_mode = tgui_alert(src, "You appear to be currently in buildmode. Do you want to re-enter buildmode after aghosting?", "Buildmode", list("Yes", "No"))
+ if(build_mode != "Yes")
+ to_chat(src, "Will not re-enter buildmode after switch.")
+
if(istype(mob,/mob/observer/dead))
//re-enter
var/mob/observer/dead/ghost = mob
if(ghost.can_reenter_corpse)
- ghost.reenter_corpse()
+ if(build_mode)
+ togglebuildmode(mob)
+ ghost.reenter_corpse()
+ if(build_mode == "Yes")
+ togglebuildmode(mob)
+ else
+ ghost.reenter_corpse()
else
to_chat(ghost, "Error: Aghost: Can't reenter corpse. ")
return
@@ -51,9 +64,18 @@
else
//ghostize
var/mob/body = mob
- var/mob/observer/dead/ghost = body.ghostize(1)
- ghost.admin_ghosted = 1
- log_and_message_admins("[key_name(src)] admin-ghosted.") // CHOMPEdit - Add logging.
+ var/mob/observer/dead/ghost
+ if(build_mode)
+ togglebuildmode(body)
+ ghost = body.ghostize(1)
+ ghost.admin_ghosted = 1
+ log_and_message_admins("[key_name(src)] admin-ghosted.") // CHOMPEdit - Add logging.
+ if(build_mode == "Yes")
+ togglebuildmode(ghost)
+ else
+ ghost = body.ghostize(1)
+ ghost.admin_ghosted = 1
+ log_and_message_admins("[key_name(src)] admin-ghosted.") // CHOMPEdit - Add logging.
if(body)
body.teleop = ghost
if(!body.key)
@@ -180,7 +202,7 @@
if(istype(src.mob, /mob/new_player))
mob.name = capitalize(ckey)
else
- var/new_key = ckeyEx(input(usr, "Enter your desired display name.", "Fake Key", key) as text|null)
+ var/new_key = ckeyEx(tgui_input_text(usr, "Enter your desired display name.", "Fake Key", key))
if(!new_key)
return
if(length(new_key) >= 26)
@@ -254,10 +276,10 @@
if("Big Bomb")
explosion(epicenter, 3, 5, 7, 5)
if("Custom Bomb")
- var/devastation_range = input(usr, "Devastation range (in tiles):") as num
- var/heavy_impact_range = input(usr, "Heavy impact range (in tiles):") as num
- var/light_impact_range = input(usr, "Light impact range (in tiles):") as num
- var/flash_range = input(usr, "Flash range (in tiles):") as num
+ var/devastation_range = tgui_input_number(usr, "Devastation range (in tiles):")
+ var/heavy_impact_range = tgui_input_number(usr, "Heavy impact range (in tiles):")
+ var/light_impact_range = tgui_input_number(usr, "Light impact range (in tiles):")
+ var/flash_range = tgui_input_number(usr, "Flash range (in tiles):")
explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range)
message_admins("[ckey] creating an admin explosion at [epicenter.loc]. ")
feedback_add_details("admin_verb","DB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -277,7 +299,7 @@
if ("Badmin") severity = 99
D.makerandom(severity)
- D.infectionchance = input(usr, "How virulent is this disease? (1-100)", "Give Disease", D.infectionchance) as num
+ D.infectionchance = tgui_input_number(usr, "How virulent is this disease? (1-100)", "Give Disease", D.infectionchance)
if(istype(T,/mob/living/carbon/human))
var/mob/living/carbon/human/H = T
@@ -308,7 +330,7 @@
var/new_modifier_type = tgui_input_list(usr, "What modifier should we add to [L]?", "Modifier Type", possible_modifiers)
if(!new_modifier_type)
return
- var/duration = input(usr, "How long should the new modifier last, in seconds. To make it last forever, write '0'.", "Modifier Duration") as num
+ var/duration = tgui_input_number(usr, "How long should the new modifier last, in seconds. To make it last forever, write '0'.", "Modifier Duration")
if(duration == 0)
duration = null
else
@@ -322,7 +344,7 @@
set name = "Make Sound"
set desc = "Display a message to everyone who can hear the target"
if(O)
- var/message = sanitize(input(usr, "What do you want the message to be?", "Make Sound") as text|null)
+ var/message = sanitize(tgui_input_text(usr, "What do you want the message to be?", "Make Sound"))
if(!message)
return
O.audible_message(message)
@@ -406,7 +428,7 @@
var/mob/living/silicon/S = tgui_input_list(usr, "Select silicon.", "Rename Silicon.", silicon_mob_list)
if(!S) return
- var/new_name = sanitizeSafe(input(src, "Enter new name. Leave blank or as is to cancel.", "[S.real_name] - Enter new silicon name", S.real_name))
+ var/new_name = sanitizeSafe(tgui_input_text(src, "Enter new name. Leave blank or as is to cancel.", "[S.real_name] - Enter new silicon name", S.real_name))
if(new_name && new_name != S.real_name)
log_and_message_admins("has renamed the silicon '[S.real_name]' to '[new_name]'")
S.SetName(new_name)
@@ -538,4 +560,4 @@
T.spell_list += new S
feedback_add_details("admin_verb","GS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
log_admin("[key_name(usr)] gave [key_name(T)] the spell [S].")
- message_admins("[key_name_admin(usr)] gave [key_name(T)] the spell [S]. ", 1)
\ No newline at end of file
+ message_admins("[key_name_admin(usr)] gave [key_name(T)] the spell [S]. ", 1)
diff --git a/code/modules/admin/admin_verbs_vr.dm b/code/modules/admin/admin_verbs_vr.dm
index 6d92fde40b..994d12a4ea 100644
--- a/code/modules/admin/admin_verbs_vr.dm
+++ b/code/modules/admin/admin_verbs_vr.dm
@@ -39,9 +39,9 @@
if(isturf(orbiter))
to_chat(usr, "The orbiter cannot be a turf. It can only be used as a center. ")
return
- var/distance = input(usr, "How large will their orbit radius be? (In pixels. 32 is 'near around a character)", "Orbit Radius", 32) as num|null
- var/speed = input(usr, "How fast will they orbit (negative numbers spin clockwise)", "Orbit Speed", 20) as num|null
- var/segments = input(usr, "How many segments will they have in their orbit? (3 is a triangle, 36 is a circle, etc)", "Orbit Segments", 36) as num|null
+ var/distance = tgui_input_number(usr, "How large will their orbit radius be? (In pixels. 32 is 'near around a character)", "Orbit Radius", 32)
+ var/speed = tgui_input_number(usr, "How fast will they orbit (negative numbers spin clockwise)", "Orbit Speed", 20)
+ var/segments = tgui_input_number(usr, "How many segments will they have in their orbit? (3 is a triangle, 36 is a circle, etc)", "Orbit Segments", 36)
var/clock = FALSE
if(!distance)
distance = 32
diff --git a/code/modules/admin/callproc/callproc.dm b/code/modules/admin/callproc/callproc.dm
index 4a0d3c496f..58e9012277 100644
--- a/code/modules/admin/callproc/callproc.dm
+++ b/code/modules/admin/callproc/callproc.dm
@@ -21,7 +21,7 @@
target = null
targetselected = 0
- var/procname = input(usr, "Proc path, eg: /proc/fake_blood","Path:", null) as text|null
+ var/procname = tgui_input_text(usr, "Proc path, eg: /proc/fake_blood","Path:", null)
if(!procname)
return
@@ -136,7 +136,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
if(!check_rights(R_DEBUG))
return
- var/procname = input(usr, "Proc name, eg: fake_blood","Proc:", null) as text|null
+ var/procname = tgui_input_text(usr, "Proc name, eg: fake_blood","Proc:", null)
if(!procname)
return
if(!hascall(A,procname))
@@ -161,7 +161,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
to_chat(usr, .)
/client/proc/get_callproc_args()
- var/argnum = input(usr, "Number of arguments","Number:",0) as num|null
+ var/argnum = tgui_input_number(usr, "Number of arguments","Number:",0)
if(isnull(argnum))
return null //Cancel
diff --git a/code/modules/admin/news.dm b/code/modules/admin/news.dm
index 179391987c..3a518bdef0 100644
--- a/code/modules/admin/news.dm
+++ b/code/modules/admin/news.dm
@@ -22,13 +22,13 @@
if(F)
var/title = F["title"]
var/body = html2paper_markup(F["body"])
- var/new_title = sanitize(input(src,"Write a good title for the news update. Note: HTML is NOT supported.","Write News", title) as null|text, extra = 0)
+ var/new_title = sanitize(tgui_input_text(src,"Write a good title for the news update. Note: HTML is NOT supported.","Write News", title), extra = 0)
if(!new_title)
return
- var/new_body = sanitize(input(src,"Write the body of the news update here. Note: HTML is NOT supported, however paper markup is supported. \n\
+ var/new_body = sanitize(tgui_input_text(src,"Write the body of the news update here. Note: HTML is NOT supported, however paper markup is supported. \n\
Hitting enter will automatically add a line break. \n\
Valid markup includes: \[b\], \[i\], \[u\], \[large\], \[h1\], \[h2\], \[h3\]\ \[*\], \[hr\], \[small\], \[list\], \[table\], \[grid\], \
- \[row\], \[cell\], \[logo\], \[sglogo\].","Write News", body) as null|message, extra = 0)
+ \[row\], \[cell\], \[logo\], \[sglogo\].","Write News", body, multiline = TRUE), extra = 0)
new_body = paper_markup2html(new_body)
diff --git a/code/modules/admin/secrets/admin_secrets/jump_shuttle.dm b/code/modules/admin/secrets/admin_secrets/jump_shuttle.dm
index 594b3c34d2..942ff60d77 100644
--- a/code/modules/admin/secrets/admin_secrets/jump_shuttle.dm
+++ b/code/modules/admin/secrets/admin_secrets/jump_shuttle.dm
@@ -26,7 +26,7 @@
var/transition_area = tgui_input_list(user, "Which area is the transition area? (MAKE SURE THIS IS CORRECT OR THINGS WILL BREAK)", "Area Choice", area_choices)
if (!transition_area) return
- var/move_duration = input(user, "How many seconds will this jump take?") as num
+ var/move_duration = tgui_input_number(user, "How many seconds will this jump take?")
S.long_jump(area_choices[origin_area], area_choices[destination_area], area_choices[transition_area], move_duration)
message_admins("[key_name_admin(user)] has initiated a jump from [origin_area] to [destination_area] lasting [move_duration] seconds for the [shuttle_tag] shuttle ", 1)
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 157cc8c25c..e4c9ff198c 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -113,7 +113,7 @@
var/task = href_list["editrights"]
if(task == "add")
- var/new_ckey = ckey(input(usr,"New admin's ckey","Admin ckey", null) as text|null)
+ var/new_ckey = ckey(tgui_input_text(usr,"New admin's ckey","Admin ckey", null))
if(!new_ckey) return
if(new_ckey in admin_datums)
to_chat(usr, "Error: Topic 'editrights': [new_ckey] is already an admin ")
@@ -151,7 +151,7 @@
switch(new_rank)
if(null,"") return
if("*New Rank*")
- new_rank = input(usr, "Please input a new rank", "New custom rank", null, null) as null|text
+ new_rank = tgui_input_text(usr, "Please input a new rank", "New custom rank")
if(config.admin_legacy_system)
new_rank = ckeyEx(new_rank)
if(!new_rank)
@@ -232,7 +232,7 @@
if(!check_rights(R_SERVER)) return
if (emergency_shuttle.wait_for_launch)
- var/new_time_left = input(usr, "Enter new shuttle launch countdown (seconds):","Edit Shuttle Launch Time", emergency_shuttle.estimate_launch_time() ) as num
+ var/new_time_left = tgui_input_number(usr, "Enter new shuttle launch countdown (seconds):","Edit Shuttle Launch Time", emergency_shuttle.estimate_launch_time() )
emergency_shuttle.launch_time = world.time + new_time_left*10
@@ -240,7 +240,7 @@
message_admins("[key_name_admin(usr)] edited the Emergency Shuttle's launch time to [new_time_left*10] ", 1)
else if (emergency_shuttle.shuttle.has_arrive_time())
- var/new_time_left = input(usr, "Enter new shuttle arrival time (seconds):","Edit Shuttle Arrival Time", emergency_shuttle.estimate_arrival_time() ) as num
+ var/new_time_left = tgui_input_number(usr, "Enter new shuttle arrival time (seconds):","Edit Shuttle Arrival Time", emergency_shuttle.estimate_arrival_time() )
emergency_shuttle.shuttle.arrive_time = world.time + new_time_left*10
log_admin("[key_name(usr)] edited the Emergency Shuttle's arrival time to [new_time_left]")
@@ -338,17 +338,17 @@
var/mins = 0
if(minutes > CMinutes)
mins = minutes - CMinutes
- mins = input(usr,"How long (in minutes)? (Default: 1440)","Ban time",mins ? mins : 1440) as num|null
+ mins = tgui_input_number(usr,"How long (in minutes)? (Default: 1440)","Ban time",mins ? mins : 1440)
if(!mins) return
mins = min(525599,mins)
minutes = CMinutes + mins
duration = GetExp(minutes)
- reason = sanitize(input(usr,"Reason?","reason",reason2) as text|null)
+ reason = sanitize(tgui_input_text(usr,"Reason?","reason",reason2))
if(!reason) return
if("No")
temp = 0
duration = "Perma"
- reason = sanitize(input(usr,"Reason?","reason",reason2) as text|null)
+ reason = sanitize(tgui_input_text(usr,"Reason?","reason",reason2))
if(!reason) return
log_admin("[key_name(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]")
@@ -758,13 +758,13 @@
if(config.ban_legacy_system)
to_chat(usr, "Your server is using the legacy banning system, which does not support temporary job bans. Consider upgrading. Aborting ban. ")
return
- var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null
+ var/mins = tgui_input_number(usr,"How long (in minutes)?","Ban time",1440)
if(!mins)
return
if(check_rights(R_MOD, 0) && !check_rights(R_BAN, 0) && mins > config.mod_job_tempban_max)
to_chat(usr, " Moderators can only job tempban up to [config.mod_job_tempban_max] minutes! ")
return
- var/reason = sanitize(input(usr,"Reason?","Please State Reason","") as text|null)
+ var/reason = sanitize(tgui_input_text(usr,"Reason?","Please State Reason",""))
if(!reason)
return
@@ -789,7 +789,7 @@
return 1
if("No")
if(!check_rights(R_BAN)) return
- var/reason = sanitize(input(usr,"Reason?","Please State Reason","") as text|null)
+ var/reason = sanitize(tgui_input_text(usr,"Reason?","Please State Reason",""))
if(reason)
var/msg
for(var/job in notbannedlist)
@@ -846,7 +846,7 @@
if (ismob(M))
if(!check_if_greater_rights_than(M.client))
return
- var/reason = sanitize(input(usr, "Please enter reason.") as null|message)
+ var/reason = sanitize(tgui_input_text(usr, "Please enter reason.", multiline = TRUE))
if(!reason)
return
@@ -888,14 +888,14 @@
switch(tgui_alert(usr, "Temporary Ban?","Temporary Ban",list("Yes","No","Cancel")))
if("Yes")
- var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null
+ var/mins = tgui_input_number(usr,"How long (in minutes)?","Ban time",1440)
if(!mins)
return
if(check_rights(R_MOD, 0) && !check_rights(R_BAN, 0) && mins > config.mod_tempban_max)
to_chat(usr, "Moderators can only job tempban up to [config.mod_tempban_max] minutes! ")
return
if(mins >= 525600) mins = 525599
- var/reason = sanitize(input(usr,"Reason?","reason","Griefer") as text|null)
+ var/reason = sanitize(tgui_input_text(usr,"Reason?","reason","Griefer"))
if(!reason)
return
AddBan(M.ckey, M.computer_id, reason, usr.ckey, 1, mins)
@@ -919,7 +919,7 @@
//qdel(M) // See no reason why to delete mob. Important stuff can be lost. And ban can be lifted before round ends.
if("No")
if(!check_rights(R_BAN)) return
- var/reason = sanitize(input(usr,"Reason?","reason","Griefer") as text|null)
+ var/reason = sanitize(tgui_input_text(usr,"Reason?","reason","Griefer"))
if(!reason)
return
switch(tgui_alert(usr,"IP ban?","IP Ban",list("Yes","No","Cancel")))
@@ -1045,7 +1045,7 @@
if(!ismob(M))
to_chat(usr, "this can only be used on instances of type /mob ")
- var/speech = input(usr, "What will [key_name(M)] say?.", "Force speech", "") // Don't need to sanitize, since it does that in say(), we also trust our admins.
+ var/speech = tgui_input_text(usr, "What will [key_name(M)] say?.", "Force speech", "") // Don't need to sanitize, since it does that in say(), we also trust our admins.
if(!speech) return
M.say(speech)
speech = sanitize(speech) // Nah, we don't trust them
@@ -1463,7 +1463,7 @@
return
if(L.can_centcom_reply())
- var/input = sanitize(input(src.owner, "Please enter a message to reply to [key_name(L)] via their headset.","Outgoing message from CentCom", ""))
+ var/input = sanitize(tgui_input_text(src.owner, "Please enter a message to reply to [key_name(L)] via their headset.","Outgoing message from CentCom", ""))
if(!input) return
to_chat(src.owner, "You sent [input] to [L] via a secure channel. ")
@@ -1488,7 +1488,7 @@
to_chat(usr, "The person you are trying to contact is not wearing a headset ")
return
- var/input = sanitize(input(src.owner, "Please enter a message to reply to [key_name(H)] via their headset.","Outgoing message from a shadowy figure...", ""))
+ var/input = sanitize(tgui_input_text(src.owner, "Please enter a message to reply to [key_name(H)] via their headset.","Outgoing message from a shadowy figure...", ""))
if(!input) return
to_chat(src.owner, "You sent [input] to [H] via a secure channel. ")
@@ -1785,7 +1785,7 @@
src.access_news_network()
else if(href_list["ac_set_channel_name"])
- src.admincaster_feed_channel.channel_name = sanitizeSafe(input(usr, "Provide a Feed Channel Name", "Network Channel Handler", ""))
+ src.admincaster_feed_channel.channel_name = sanitizeSafe(tgui_input_text(usr, "Provide a Feed Channel Name", "Network Channel Handler", ""))
src.access_news_network()
else if(href_list["ac_set_channel_lock"])
@@ -1817,11 +1817,11 @@
src.access_news_network()
else if(href_list["ac_set_new_title"])
- src.admincaster_feed_message.title = sanitize(input(usr, "Enter the Feed title", "Network Channel Handler", ""))
+ src.admincaster_feed_message.title = sanitize(tgui_input_text(usr, "Enter the Feed title", "Network Channel Handler", ""))
src.access_news_network()
else if(href_list["ac_set_new_message"])
- src.admincaster_feed_message.body = sanitize(input(usr, "Write your Feed story", "Network Channel Handler", "") as message)
+ src.admincaster_feed_message.body = sanitize(tgui_input_text(usr, "Write your Feed story", "Network Channel Handler", "", multiline = TRUE))
src.access_news_network()
else if(href_list["ac_submit_new_message"])
@@ -1863,11 +1863,11 @@
src.access_news_network()
else if(href_list["ac_set_wanted_name"])
- src.admincaster_feed_message.author = sanitize(input(usr, "Provide the name of the Wanted person", "Network Security Handler", ""))
+ src.admincaster_feed_message.author = sanitize(tgui_input_text(usr, "Provide the name of the Wanted person", "Network Security Handler", ""))
src.access_news_network()
else if(href_list["ac_set_wanted_desc"])
- src.admincaster_feed_message.body = sanitize(input(usr, "Provide the a description of the Wanted person and any other details you deem important", "Network Security Handler", ""))
+ src.admincaster_feed_message.body = sanitize(tgui_input_text(usr, "Provide the a description of the Wanted person and any other details you deem important", "Network Security Handler", ""))
src.access_news_network()
else if(href_list["ac_submit_wanted"])
@@ -1972,7 +1972,7 @@
src.access_news_network()
else if(href_list["ac_set_signature"])
- src.admincaster_signature = sanitize(input(usr, "Provide your desired signature", "Network Identity Handler", ""))
+ src.admincaster_signature = sanitize(tgui_input_text(usr, "Provide your desired signature", "Network Identity Handler", ""))
src.access_news_network()
else if(href_list["populate_inactive_customitems"])
@@ -2019,21 +2019,6 @@
// player info stuff
- if(href_list["add_player_info"])
- var/key = href_list["add_player_info"]
- var/add = sanitize(input(usr, "Add Player Info") as null|text)
- if(!add) return
-
- notes_add(key,add,usr)
- show_player_info(key)
-
- if(href_list["remove_player_info"])
- var/key = href_list["remove_player_info"]
- var/index = text2num(href_list["remove_index"])
-
- notes_del(key, index)
- show_player_info(key)
-
if(href_list["notes"])
var/ckey = href_list["ckey"]
if(!ckey)
@@ -2043,7 +2028,9 @@
switch(href_list["notes"])
if("show")
- show_player_info(ckey)
+ var/datum/tgui_module/player_notes_info/A = new(src)
+ A.key = ckey
+ A.tgui_interact(usr)
if("list")
var/filter
if(href_list["filter"] && href_list["filter"] != "0")
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index 1943147aee..0be7586c59 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -491,7 +491,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
usr << browse(dat.Join(), "window=ahelp[id];size=620x480")
/datum/admin_help/proc/Retitle()
- var/new_title = input(usr, "Enter a title for the ticket", "Rename Ticket", name) as text|null
+ var/new_title = tgui_input_text(usr, "Enter a title for the ticket", "Rename Ticket", name)
if(new_title)
name = new_title
//not saying the original name cause it could be a long ass message
diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm
index c108bbf14b..ee127136da 100644
--- a/code/modules/admin/verbs/adminjump.dm
+++ b/code/modules/admin/verbs/adminjump.dm
@@ -210,13 +210,13 @@
if(config.allow_admin_jump)
if(isnull(tx))
- tx = input(usr, "Select X coordinate", "Move Atom", null, null) as null|num
+ tx = tgui_input_number(usr, "Select X coordinate", "Move Atom", null, null)
if(!tx) return
if(isnull(ty))
- ty = input(usr, "Select Y coordinate", "Move Atom", null, null) as null|num
+ ty = tgui_input_number(usr, "Select Y coordinate", "Move Atom", null, null)
if(!ty) return
if(isnull(tz))
- tz = input(usr, "Select Z coordinate", "Move Atom", null, null) as null|num
+ tz = tgui_input_number(usr, "Select Z coordinate", "Move Atom", null, null)
if(!tz) return
var/turf/T = locate(tx, ty, tz)
if(!T)
diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm
index a25fac29e9..592b20b358 100644
--- a/code/modules/admin/verbs/adminpm.dm
+++ b/code/modules/admin/verbs/adminpm.dm
@@ -57,7 +57,7 @@
if(AH)
message_admins("[key_name_admin(src)] has started replying to [key_name(C, 0, 0)]'s admin help. ")
- var/msg = input(src,"Message:", "Private message to [key_name(C, 0, 0)]") as text|null
+ var/msg = tgui_input_text(src,"Message:", "Private message to [key_name(C, 0, 0)]")
if (!msg)
message_admins("[key_name_admin(src)] has cancelled their reply to [key_name(C, 0, 0)]'s admin help. ")
return
@@ -92,7 +92,7 @@
if(!ircreplyamount) //to prevent people from spamming irc
return
if(!msg)
- msg = input(src,"Message:", "Private message to Administrator") as text|null
+ msg = tgui_input_text(src,"Message:", "Private message to Administrator")
if(!msg)
return
@@ -112,7 +112,7 @@
//get message text, limit it's length.and clean/escape html
if(!msg)
- msg = input(src,"Message:", "Private message to [key_name(recipient, 0, 0)]") as text|null
+ msg = tgui_input_text(src,"Message:", "Private message to [key_name(recipient, 0, 0)]")
if(!msg)
return
@@ -188,7 +188,7 @@
spawn() //so we don't hold the caller proc up
var/sender = src
var/sendername = key
- var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as text|null //show message and await a reply
+ var/reply = tgui_input_text(recipient, msg,"Admin PM from-[sendername]", "") //show message and await a reply
if(recipient && reply)
if(sender)
recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them
diff --git a/code/modules/admin/verbs/buildmode.dm b/code/modules/admin/verbs/buildmode.dm
index 290b04bdde..28649fe3b5 100644
--- a/code/modules/admin/verbs/buildmode.dm
+++ b/code/modules/admin/verbs/buildmode.dm
@@ -257,16 +257,16 @@
if(BUILDMODE_EDIT)
var/list/locked = list("vars", "key", "ckey", "client", "firemut", "ishulk", "telekinesis", "xray", "virus", "viruses", "cuffed", "ka", "last_eaten", "urine")
- master.buildmode.varholder = input(usr,"Enter variable name:" ,"Name", "name")
+ master.buildmode.varholder = tgui_input_text(usr,"Enter variable name:" ,"Name", "name")
if(master.buildmode.varholder in locked && !check_rights(R_DEBUG,0))
return 1
var/thetype = tgui_input_list(usr,"Select variable type:", "Type", list("text","number","mob-reference","obj-reference","turf-reference"))
if(!thetype) return 1
switch(thetype)
if("text")
- master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value", "value") as text
+ master.buildmode.valueholder = tgui_input_text(usr,"Enter variable value:" ,"Value", "value")
if("number")
- master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value", 123) as num
+ master.buildmode.valueholder = tgui_input_number(usr,"Enter variable value:" ,"Value", 123)
if("mob-reference")
master.buildmode.valueholder = tgui_input_list(usr,"Enter variable value:", "Value", mob_list)
if("obj-reference")
@@ -286,11 +286,11 @@
var/choice = tgui_alert(usr, "Change the new light range, power, or color?", "Light Maker", list("Range", "Power", "Color"))
switch(choice)
if("Range")
- var/input = input(usr, "New light range.","Light Maker",3) as null|num
+ var/input = tgui_input_number(usr, "New light range.","Light Maker",3)
if(input)
new_light_range = input
if("Power")
- var/input = input(usr, "New light power.","Light Maker",3) as null|num
+ var/input = tgui_input_number(usr, "New light power.","Light Maker",3)
if(input)
new_light_intensity = input
if("Color")
@@ -625,7 +625,7 @@
return
/obj/effect/bmode/buildmode/proc/get_path_from_partial_text(default_path)
- var/desired_path = input(usr, "Enter full or partial typepath.","Typepath","[default_path]")
+ var/desired_path = tgui_input_text(usr, "Enter full or partial typepath.","Typepath","[default_path]")
var/list/types = typesof(/atom)
var/list/matches = list()
diff --git a/code/modules/admin/verbs/change_appearance.dm b/code/modules/admin/verbs/change_appearance.dm
index 717a8ce5bd..2cafd14ee4 100644
--- a/code/modules/admin/verbs/change_appearance.dm
+++ b/code/modules/admin/verbs/change_appearance.dm
@@ -74,7 +74,7 @@
M.g_skin = hex2num(copytext(new_skin, 4, 6))
M.b_skin = hex2num(copytext(new_skin, 6, 8))
- var/new_tone = input(usr, "Please select skin tone level: 1-220 (1=albino, 35=caucasian, 150=black, 220='very' black)", "Character Generation") as text
+ var/new_tone = tgui_input_number(usr, "Please select skin tone level: 1-220 (1=albino, 35=caucasian, 150=black, 220='very' black)", "Character Generation", null, 220, 1)
if (new_tone)
M.s_tone = max(min(round(text2num(new_tone)), 220), 1)
diff --git a/code/modules/admin/verbs/cinematic.dm b/code/modules/admin/verbs/cinematic.dm
index 51f0364843..477f2f032c 100644
--- a/code/modules/admin/verbs/cinematic.dm
+++ b/code/modules/admin/verbs/cinematic.dm
@@ -12,7 +12,7 @@
if("explosion")
if(tgui_alert(usr, "The game will be over. Are you really sure?", "Confirmation", list("Continue","Cancel")) == "Cancel")
return
- var/parameter = input(src,"station_missed = ?","Enter Parameter",0) as num
+ var/parameter = tgui_input_number(src,"station_missed = ?","Enter Parameter",0,1,0)
var/override
switch(parameter)
if(1)
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 40635bb7fe..c2e4b671f7 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -152,7 +152,7 @@
if(tgui_alert(pai, "Do you want to load your pAI data?", "Load", list("Yes", "No")) == "Yes")
pai.savefile_load(pai)
else
- pai.name = sanitizeSafe(input(pai, "Enter your pAI name:", "pAI Name", "Personal AI") as text)
+ pai.name = sanitizeSafe(tgui_input_text(pai, "Enter your pAI name:", "pAI Name", "Personal AI"))
card.setPersonality(pai)
for(var/datum/paiCandidate/candidate in paiController.pai_candidates)
if(candidate.key == choice.key)
@@ -662,9 +662,9 @@
var/datum/planet/planet = tgui_input_list(usr, "Which planet do you want to modify time on?", "Change Time", SSplanets.planets)
if(istype(planet))
var/datum/time/current_time_datum = planet.current_time
- var/new_hour = input(usr, "What hour do you want to change to?", "Change Time", text2num(current_time_datum.show_time("hh"))) as null|num
+ var/new_hour = tgui_input_number(usr, "What hour do you want to change to?", "Change Time", text2num(current_time_datum.show_time("hh")))
if(!isnull(new_hour))
- var/new_minute = input(usr, "What minute do you want to change to?", "Change Time", text2num(current_time_datum.show_time("mm")) ) as null|num
+ var/new_minute = tgui_input_number(usr, "What minute do you want to change to?", "Change Time", text2num(current_time_datum.show_time("mm")) )
if(!isnull(new_minute))
var/type_needed = current_time_datum.type
var/datum/time/new_time = new type_needed()
diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm
index b70a091980..ce39bbb0b8 100644
--- a/code/modules/admin/verbs/diagnostics.dm
+++ b/code/modules/admin/verbs/diagnostics.dm
@@ -177,7 +177,7 @@
set desc = "This searches all the active jobban entries for the current round and outputs the results to standard output."
set category = "Debug"
- var/job_filter = input(usr, "Contains what?","Job Filter") as text|null
+ var/job_filter = tgui_input_text(usr, "Contains what?","Job Filter")
if(!job_filter)
return
diff --git a/code/modules/admin/verbs/dice.dm b/code/modules/admin/verbs/dice.dm
index 2aa6f91a66..cc21521859 100644
--- a/code/modules/admin/verbs/dice.dm
+++ b/code/modules/admin/verbs/dice.dm
@@ -4,8 +4,8 @@
if(!check_rights(R_FUN))
return
- var/sum = input(usr, "How many times should we throw?") as num
- var/side = input(usr, "Select the number of sides.") as num
+ var/sum = tgui_input_number(usr, "How many times should we throw?")
+ var/side = tgui_input_number(usr, "Select the number of sides.")
if(!side)
side = 6
if(!sum)
diff --git a/code/modules/admin/verbs/fps.dm b/code/modules/admin/verbs/fps.dm
index 4b357a704d..c3b9999afb 100644
--- a/code/modules/admin/verbs/fps.dm
+++ b/code/modules/admin/verbs/fps.dm
@@ -8,7 +8,7 @@
if(!check_rights(R_DEBUG))
return
- var/new_fps = round(input(usr, "Sets game frames-per-second. Can potentially break the game (default: [config.fps])", "FPS", world.fps) as num|null)
+ var/new_fps = round(tgui_input_number(usr, "Sets game frames-per-second. Can potentially break the game (default: [config.fps])", "FPS", world.fps))
if(new_fps <= 0)
to_chat(src, "Error: set_server_fps(): Invalid world.fps value. No changes made. ")
return
diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm
index 31c4342606..7f80b39540 100644
--- a/code/modules/admin/verbs/mapping.dm
+++ b/code/modules/admin/verbs/mapping.dm
@@ -279,13 +279,13 @@ var/list/debug_verbs = list (
/client/proc/count_objects_on_z_level()
set category = "Mapping"
set name = "Count Objects On Level"
- var/level = input(usr, "Which z-level?","Level?") as text
+ var/level = tgui_input_text(usr, "Which z-level?","Level?")
if(!level) return
var/num_level = text2num(level)
if(!num_level) return
if(!isnum(num_level)) return
- var/type_text = input(usr, "Which type path?","Path?") as text
+ var/type_text = tgui_input_text(usr, "Which type path?","Path?")
if(!type_text) return
var/type_path = text2path(type_text)
if(!type_path) return
@@ -323,7 +323,7 @@ var/list/debug_verbs = list (
set category = "Mapping"
set name = "Count Objects All"
- var/type_text = input(usr, "Which type path?","") as text
+ var/type_text = tgui_input_text(usr, "Which type path?","")
if(!type_text) return
var/type_path = text2path(type_text)
if(!type_path) return
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 064a950ddb..e071eb84ff 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -86,7 +86,7 @@
if (!holder)
return
- var/msg = sanitize(input(usr, "Message:", text("Subtle PM to [M.key]")) as text)
+ var/msg = sanitize(tgui_input_text(usr, "Message:", text("Subtle PM to [M.key]")))
if (!msg)
return
@@ -108,7 +108,7 @@
if (!holder)
return
- var/msg = input(usr, "Message:", text("Enter the text you wish to appear to everyone:")) as text
+ var/msg = tgui_input_text(usr, "Message:", text("Enter the text you wish to appear to everyone:"))
if(!(msg[1] == "<" && msg[length(msg)] == ">")) //You can use HTML but only if the whole thing is HTML. Tries to prevent admin 'accidents'.
msg = sanitize(msg)
@@ -132,7 +132,7 @@
if(!M)
return
- var/msg = input(usr, "Message:", text("Enter the text you wish to appear to your target:")) as text
+ var/msg = tgui_input_text(usr, "Message:", text("Enter the text you wish to appear to your target:"))
if(msg && !(msg[1] == "<" && msg[length(msg)] == ">")) //You can use HTML but only if the whole thing is HTML. Tries to prevent admin 'accidents'.
msg = sanitize(msg)
@@ -575,7 +575,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
if(!holder)
return
- var/input = sanitize(input(usr, "Please enter anything you want the AI to do. Anything. Serious.", "What?", "") as text|null)
+ var/input = sanitize(tgui_input_text(usr, "Please enter anything you want the AI to do. Anything. Serious.", "What?", ""))
if(!input)
return
for(var/mob/living/silicon/ai/M in mob_list)
@@ -627,8 +627,8 @@ Traitors and the like can also be revived with the previous role mostly intact.
if(!holder)
return
- var/input = sanitize(input(usr, "Please enter anything you want. Anything. Serious.", "What?", "") as message|null, extra = 0)
- var/customname = sanitizeSafe(input(usr, "Pick a title for the report.", "Title") as text|null)
+ var/input = sanitize(tgui_input_text(usr, "Please enter anything you want. Anything. Serious.", "What?", "", multiline = TRUE), extra = 0)
+ var/customname = sanitizeSafe(tgui_input_text(usr, "Pick a title for the report.", "Title"))
if(!input)
return
if(!customname)
@@ -675,13 +675,13 @@ Traitors and the like can also be revived with the previous role mostly intact.
if(!check_rights(R_DEBUG|R_FUN)) return //VOREStation Edit
- var/devastation = input(usr, "Range of total devastation. -1 to none", text("Input")) as num|null
+ var/devastation = tgui_input_number(usr, "Range of total devastation. -1 to none", text("Input"))
if(devastation == null) return
- var/heavy = input(usr, "Range of heavy impact. -1 to none", text("Input")) as num|null
+ var/heavy = tgui_input_number(usr, "Range of heavy impact. -1 to none", text("Input"))
if(heavy == null) return
- var/light = input(usr, "Range of light impact. -1 to none", text("Input")) as num|null
+ var/light = tgui_input_number(usr, "Range of light impact. -1 to none", text("Input"))
if(light == null) return
- var/flash = input(usr, "Range of flash. -1 to none", text("Input")) as num|null
+ var/flash = tgui_input_number(usr, "Range of flash. -1 to none", text("Input"))
if(flash == null) return
if ((devastation != -1) || (heavy != -1) || (light != -1) || (flash != -1))
@@ -703,13 +703,13 @@ Traitors and the like can also be revived with the previous role mostly intact.
if(!check_rights(R_DEBUG|R_FUN)) return //VOREStation Edit
- var/heavy = input(usr, "Range of heavy pulse.", text("Input")) as num|null
+ var/heavy = tgui_input_number(usr, "Range of heavy pulse.", text("Input"))
if(heavy == null) return
- var/med = input(usr, "Range of medium pulse.", text("Input")) as num|null
+ var/med = tgui_input_number(usr, "Range of medium pulse.", text("Input"))
if(med == null) return
- var/light = input(usr, "Range of light pulse.", text("Input")) as num|null
+ var/light = tgui_input_number(usr, "Range of light pulse.", text("Input"))
if(light == null) return
- var/long = input(usr, "Range of long pulse.", text("Input")) as num|null
+ var/long = tgui_input_number(usr, "Range of long pulse.", text("Input"))
if(long == null) return
if (heavy || med || light || long)
diff --git a/code/modules/admin/verbs/randomverbs_vr.dm b/code/modules/admin/verbs/randomverbs_vr.dm
index 1d76ad8581..4d3ab6141b 100644
--- a/code/modules/admin/verbs/randomverbs_vr.dm
+++ b/code/modules/admin/verbs/randomverbs_vr.dm
@@ -10,7 +10,7 @@
if(!picked_client)
return
var/list/types = typesof(/mob/living)
- var/mob_type = input(src, "Mob path to spawn as?", "Mob") as text
+ var/mob_type = tgui_input_text(src, "Mob path to spawn as?", "Mob")
if(!mob_type)
return
var/list/matches = new()
@@ -81,7 +81,7 @@
if (!holder)
return
- var/msg = input(usr, "Message:", text("Enter the text you wish to appear to everyone:")) as text
+ var/msg = tgui_input_text(usr, "Message:", text("Enter the text you wish to appear to everyone:"))
if(!(msg[1] == "<" && msg[length(msg)] == ">")) //You can use HTML but only if the whole thing is HTML. Tries to prevent admin 'accidents'.
msg = sanitize(msg)
diff --git a/code/modules/admin/verbs/resize.dm b/code/modules/admin/verbs/resize.dm
index 1379625cc4..917a3a55c5 100644
--- a/code/modules/admin/verbs/resize.dm
+++ b/code/modules/admin/verbs/resize.dm
@@ -4,8 +4,8 @@
set category = "Fun"
if(!check_rights(R_ADMIN, R_FUN))
return
-
- var/size_multiplier = input(usr, "Input size multiplier.", "Resize", 1) as num|null
+
+ var/size_multiplier = tgui_input_number(usr, "Input size multiplier.", "Resize", 1)
if(!size_multiplier)
return //cancelled
@@ -21,4 +21,4 @@
L.resize(size_multiplier, animate = TRUE, uncapped = TRUE, ignore_prefs = TRUE)
log_and_message_admins("has changed [key_name(L)]'s size multiplier to [size_multiplier].")
- feedback_add_details("admin_verb","RESIZE")
\ No newline at end of file
+ feedback_add_details("admin_verb","RESIZE")
diff --git a/code/modules/admin/verbs/striketeam.dm b/code/modules/admin/verbs/striketeam.dm
index d7b2c1ffff..7724062a6e 100644
--- a/code/modules/admin/verbs/striketeam.dm
+++ b/code/modules/admin/verbs/striketeam.dm
@@ -43,7 +43,7 @@ var/const/commandos_possible = 6 //if more Commandos are needed in the future
choice = null
while(!choice)
- choice = sanitize(input(src, "Please specify which mission the strike team shall undertake.", "Specify Mission", ""))
+ choice = sanitize(tgui_input_text(src, "Please specify which mission the strike team shall undertake.", "Specify Mission", ""))
if(!choice)
if(tgui_alert(usr, "Error, no mission set. Do you want to exit the setup process?","Strike Team",list("Yes","No"))=="Yes")
return
diff --git a/code/modules/admin/view_variables/get_variables.dm b/code/modules/admin/view_variables/get_variables.dm
index 64920cdc9e..f3fb4ea145 100644
--- a/code/modules/admin/view_variables/get_variables.dm
+++ b/code/modules/admin/view_variables/get_variables.dm
@@ -85,19 +85,19 @@
switch(.["class"])
if (VV_TEXT)
- .["value"] = input(usr, "Enter new text:", "Text", current_value) as null|text
+ .["value"] = tgui_input_text(usr, "Enter new text:", "Text", current_value)
if (.["value"] == null)
.["class"] = null
return
if (VV_MESSAGE)
- .["value"] = input(usr, "Enter new text:", "Text", current_value) as null|message
+ .["value"] = tgui_input_text(usr, "Enter new text:", "Text", current_value, multiline = TRUE)
if (.["value"] == null)
.["class"] = null
return
if (VV_NUM)
- .["value"] = input(usr, "Enter new number:", "Num", current_value) as null|num
+ .["value"] = tgui_input_number(usr, "Enter new number:", "Num", current_value)
if (.["value"] == null)
.["class"] = null
return
@@ -124,7 +124,7 @@
var/type = current_value
var/error = ""
do
- type = input(usr, "Enter type:[error]", "Type", type) as null|text
+ type = tgui_input_text(usr, "Enter type:[error]", "Type", type)
if (!type)
break
type = text2path(type)
@@ -229,7 +229,7 @@
var/type = current_value
var/error = ""
do
- type = input(usr, "Enter type:[error]", "Type", type) as null|text
+ type = tgui_input_text(usr, "Enter type:[error]", "Type", type)
if (!type)
break
type = text2path(type)
diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm
index ddab5464e6..a014544090 100644
--- a/code/modules/admin/view_variables/topic.dm
+++ b/code/modules/admin/view_variables/topic.dm
@@ -475,7 +475,7 @@
var/Text = href_list["adjustDamage"]
- var/amount = input(usr, "Deal how much damage to mob? (Negative values here heal)","Adjust [Text]loss",0) as num
+ var/amount = tgui_input_number(usr, "Deal how much damage to mob? (Negative values here heal)","Adjust [Text]loss",0)
if(!L)
to_chat(usr, "Mob doesn't exist anymore")
diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm
index 437da93467..0f5fe03f8a 100644
--- a/code/modules/assembly/holder.dm
+++ b/code/modules/assembly/holder.dm
@@ -214,7 +214,7 @@
if(tmr.timing)
to_chat(usr, "Clock is ticking already. ")
else
- var/ntime = input(usr, "Enter desired time in seconds", "Time", "5") as num
+ var/ntime = tgui_input_number(usr, "Enter desired time in seconds", "Time", "5", 1000, 0)
if (ntime>0 && ntime<1000)
tmr.time = ntime
name = initial(name) + "([tmr.time] secs)"
diff --git a/code/modules/casino/casino.dm b/code/modules/casino/casino.dm
new file mode 100644
index 0000000000..224e457ee9
--- /dev/null
+++ b/code/modules/casino/casino.dm
@@ -0,0 +1,485 @@
+
+//Original Casino Code created by Shadowfire117#1269 - Ported from CHOMPstation
+//Modified by GhostActual#2055 for use with VOREstation
+
+//
+//Roulette Table
+//
+
+/obj/structure/casino_table
+ name = "casino table"
+ desc = "this is an unremarkable table for a casino."
+ icon = 'icons/obj/casino.dmi'
+ icon_state = "roulette_table"
+ density = 1
+ anchored = 1
+ climbable = 1
+ layer = TABLE_LAYER
+ throwpass = 1
+ var/item_place = 1 //allows items to be placed on the table, but not on benches.
+
+ var/busy = 0
+
+/obj/structure/casino_table/attackby(obj/item/W as obj, mob/user as mob)
+ if(item_place)
+ user.drop_item(src.loc)
+ return
+
+/obj/structure/casino_table/roulette_table
+ name = "roulette"
+ desc = "Spin the roulette to try your luck."
+ icon_state = "roulette_wheel"
+
+/obj/structure/casino_table/roulette_table/attack_hand(mob/user as mob)
+ if (busy)
+ to_chat(user,"You cannot spin now! The roulette is already spinning. ")
+ return
+ visible_message("\ [user] spins the roulette and throws inside little ball. ")
+ playsound(src.loc, 'sound/machines/roulette.ogg', 40, 1)
+ busy = 1
+ icon_state = "roulette_wheel_spinning"
+ var/result = rand(0,36)
+ var/color = "green"
+ add_fingerprint(user)
+ if ((result>0 && result<11) || (result>18 && result<29))
+ if (result%2)
+ color="red"
+ else
+ color="black"
+ if ( (result>10 && result<19) || (result>28) )
+ if (result%2)
+ color="black"
+ else
+ color="red"
+ spawn(5 SECONDS)
+ visible_message("The roulette stops spinning, the ball landing on [result], [color]. ")
+ busy=0
+ icon_state = "roulette_wheel"
+
+/obj/structure/casino_table/roulette_chart
+ name = "roulette chart"
+ desc = "Roulette chart. Place your bets!"
+ icon_state = "roulette_table"
+
+//
+//Blackjack table - no sprite
+//
+
+/obj/structure/casino_table/blackjack_l
+ name = "gambling table"
+ desc = "Gambling table, try your luck and skills! "
+ icon_state = "blackjack_l"
+
+/obj/structure/casino_table/blackjack_r
+ name = "gambling table"
+ desc = "Gambling table, try your luck and skills! "
+ icon_state = "blackjack_r"
+
+/obj/structure/casino_table/blackjack_m
+ name = "gambling table"
+ desc = "Gambling table, try your luck and skills! "
+ icon_state = "blackjack_m"
+
+//
+//Wheel. Of. FORTUNE!
+//
+
+/obj/machinery/wheel_of_fortune
+ name = "wheel of fortune"
+ desc = "The Wheel of Fortune! Insert chips and may fortune favour the lucky one at the next lottery!"
+ icon = 'icons/obj/64x64.dmi'
+ icon_state = "wheel_of_fortune"
+ density = 1
+ anchored = 1
+ pixel_x = -16
+
+ req_access = list(300)
+ var/interval = 1
+ var/busy = 0
+ var/public_spin = 0
+ var/lottery_sale = "disabled"
+ var/lottery_price = 100
+ var/lottery_entries = 0
+ var/lottery_tickets = list()
+ var/lottery_tickets_ckeys = list()
+
+ var/datum/effect/effect/system/confetti_spread
+ var/confetti_strength = 15
+
+
+/obj/machinery/wheel_of_fortune/attack_hand(mob/user as mob)
+ if (busy)
+ to_chat(user,"The wheel of fortune is already spinning! ")
+ return
+
+ if(usr.incapacitated())
+ return
+ if(ishuman(usr) || istype(usr, /mob/living/silicon/robot))
+ switch(input(user,"Choose what to do","Wheel Of Fortune") in list("Spin the Wheel! (Not Lottery)", "Set the interval", "Cancel"))
+ if("Cancel")
+ return
+ if("Spin the Wheel! (Not Lottery)")
+ if(public_spin == 0)
+ to_chat(user,"The Wheel makes a sad beep, public spins are not enabled right now.. ")
+ return
+ else
+ to_chat(user,"You spin the wheel! ")
+ spin_the_wheel("not_lottery")
+ if("Set the interval")
+ setinterval()
+
+
+/obj/machinery/wheel_of_fortune/attackby(obj/item/weapon/W as obj, mob/user as mob)
+ if (busy)
+ to_chat(user,"The wheel of fortune is already spinning! ")
+ return
+
+ if(usr.incapacitated())
+ return
+
+ if(istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda))
+ if(!check_access(W))
+ to_chat(user, "Access Denied. ")
+ return
+ else
+ to_chat(user, "Proper access, allowed staff controls. ")
+ if(ishuman(usr) || istype(usr, /mob/living/silicon/robot))
+ switch(input(user,"Choose what to do (Management)","Wheel Of Fortune (Management)") in list("Spin the Lottery Wheel!", "Toggle Lottery Sales", "Toggle Public Spins", "Reset Lottery", "Cancel"))
+ if("Cancel")
+ return
+ if("Spin the Lottery Wheel!")
+ to_chat(user,"You spin the wheel for the lottery! ")
+ spin_the_wheel("lottery")
+
+ if("Toggle Lottery Sales")
+ if(lottery_sale == "disabled")
+ lottery_sale = "enabled"
+ to_chat(user,"Public Lottery sale has been enabled. ")
+ else
+ lottery_sale = "disabled"
+ to_chat(user,"Public Lottery sale has been disabled. ")
+
+ if("Toggle Public Spins")
+ if(public_spin == 0)
+ public_spin = 1
+ to_chat(user,"Public spins has been enabled. ")
+ else
+ public_spin = 0
+ to_chat(user,"Public spins has been disabled. ")
+
+ if("Reset Lottery")
+ var/confirm = tgui_alert(usr, "Are you sure you want to reset Lottery?", "Confirm Lottery Reset", list("Yes", "No"))
+ if(confirm == "Yes")
+ to_chat(user, "Lottery has been Reset! ")
+ lottery_entries = 0
+ lottery_tickets = list()
+ lottery_tickets_ckeys = list()
+
+ if(istype(W, /obj/item/weapon/spacecasinocash))
+ if(lottery_sale == "disabled")
+ to_chat(user, "Lottery sales are currently disabled. ")
+ return
+ else
+ if(user.client.ckey in lottery_tickets_ckeys)
+ to_chat(user, "The scanner beeps in an upset manner, you already have a ticket! ")
+ return
+
+ var/obj/item/weapon/spacecasinocash/C = W
+ insert_chip(C, user)
+
+/obj/machinery/wheel_of_fortune/proc/insert_chip(var/obj/item/weapon/spacecasinocash/cashmoney, mob/user)
+ if (busy)
+ to_chat(user,"The Wheel of Fortune is busy, wait for it to be done to buy a lottery ticket. ")
+ return
+ if(cashmoney.worth < lottery_price)
+ to_chat(user,"You dont have enough chips to buy a lottery ticket! ")
+ return
+
+ to_chat(user,"You put [lottery_price] credits worth of chips into the Wheel of Fortune and it pings to notify of your lottery ticket registered! ")
+ cashmoney.worth -= lottery_price
+ cashmoney.update_icon()
+
+ if(cashmoney.worth <= 0)
+ usr.drop_from_inventory(cashmoney)
+ qdel(cashmoney)
+ cashmoney.update_icon()
+
+ lottery_entries++
+ lottery_tickets += "Number.[lottery_entries] [user.name]"
+ lottery_tickets_ckeys += user.client.ckey
+
+/obj/machinery/wheel_of_fortune/proc/spin_the_wheel(var/mode)
+ var/result = 0
+
+ if(mode == "not_lottery")
+ busy = 1
+ icon_state = "wheel_of_fortune_spinning"
+ result = rand(1,interval)
+
+ spawn(5 SECONDS)
+ visible_message("The wheel of fortune stops spinning, the number is [result]! ")
+ src.confetti_spread = new /datum/effect/effect/system/confetti_spread()
+ src.confetti_spread.attach(src) //If somehow people start dragging slot machine
+ spawn(0)
+ for(var/i = 1 to confetti_strength)
+ src.confetti_spread.start()
+ sleep(10)
+
+ flick("[icon_state]-winning",src)
+ busy = 0
+ icon_state = "wheel_of_fortune"
+
+ if(mode == "lottery")
+ if(lottery_entries == 0)
+ visible_message("There are no tickets in the system! ")
+ return
+
+ busy = 1
+ icon_state = "wheel_of_fortune_spinning"
+ result = pick(lottery_tickets)
+
+ spawn(5 SECONDS)
+ visible_message("The wheel of fortune stops spinning, and the winner is [result]! ")
+ src.confetti_spread = new /datum/effect/effect/system/confetti_spread()
+ src.confetti_spread.attach(src) //If somehow people start dragging slot machine
+ spawn(0)
+ for(var/i = 1 to confetti_strength)
+ src.confetti_spread.start()
+ sleep(10)
+
+ flick("[icon_state]-winning",src)
+ busy = 0
+ icon_state = "wheel_of_fortune"
+
+/obj/machinery/wheel_of_fortune/verb/setinterval()
+ set name = "Change interval"
+ set category = "Object"
+ set src in view(1)
+
+ if(usr.incapacitated())
+ return
+ if(ishuman(usr) || istype(usr, /mob/living/silicon/robot))
+ interval = tgui_input_number(usr, "Put the desired interval (1-1000)", "Set Interval", null, 1000, 1)
+ if(interval>1000 || interval<1)
+ usr << "Invalid interval. "
+ return
+ usr << "You set the interval to [interval] "
+ return
+
+//
+//Slave Terminal
+//
+
+/obj/machinery/casinoslave_handler
+ name = "Sentient Prize Automated Sales Machinery"
+ desc = "The Sentient Prize Automated Sales Machinery, also known as SPASM! Here one can see who is on sale as sentinet prizes, as well as selling self and also buying prizes."
+ icon = 'icons/obj/casino.dmi'
+ icon_state = "casinoslave_hub_off"
+ density = 1
+ anchored = 1
+ req_access = list(300)
+
+ var/casinoslave_sale = "disabled"
+ var/casinoslave_price = 100
+ var/collar_list = list()
+ var/slaves_ckeys_list = list() //Same trick as lottery, to keep life simple
+ var/obj/item/clothing/accessory/collar/casinoslave/selected_collar = null
+
+/obj/machinery/casinoslave_handler/attack_hand(mob/living/user as mob)
+ if(usr.incapacitated())
+ return
+ if(casinoslave_sale == "disabled")
+ to_chat(user,"The SPASM is disabled. ")
+ return
+
+ if(ishuman(usr) || istype(usr, /mob/living/silicon/robot))
+ switch(input(user,"Choose what to do","SPASM") in list("Show selected Prize", "Select Prize", "Become Prize (Please examine yourself first)", "Cancel"))
+ if("Cancel")
+ return
+ if("Show selected Prize")
+ if(QDELETED(selected_collar))
+ collar_list -= selected_collar
+ slaves_ckeys_list -= selected_collar.slaveckey
+ to_chat(user, "No collar is currently selected or the currently selected one has been destroyed or disabled. ")
+ selected_collar = null
+ return
+ to_chat(user, "Sentient Prize information ")
+ to_chat(user, "Name: [selected_collar.slavename] ")
+ to_chat(user, "Description: [selected_collar.slaveflavor] ")
+ to_chat(user, "OOC: [selected_collar.slaveooc] ")
+ if(selected_collar.ownername != null)
+ to_chat(user, "This prize is already owned by [selected_collar.ownername] ")
+
+ if("Select Prize")
+ selected_collar = tgui_input_list(user, "Select a prize", "Chose a collar", collar_list)
+ if(QDELETED(selected_collar))
+ collar_list -= selected_collar
+ slaves_ckeys_list -= selected_collar.slaveckey
+ to_chat(user, "No collars to chose, or selected collar has been destroyed or deactived, selection has been removed from list. ")
+ selected_collar = null
+ return
+
+ if("Become Prize (Please examine yourself first)") //Its awkward, but no easy way to obtain flavor_text due to server not loading text of mob until its been examined at least once.
+ var/safety_ckey = user.client.ckey
+ if(safety_ckey in slaves_ckeys_list)
+ to_chat(user, "The SPASM beeps in an upset manner, you already have a collar! ")
+ return
+ var/confirm = tgui_alert(usr, "Are you sure you want to become a sentient prize?", "Confirm Sentient Prize", list("Yes", "No"))
+ if(confirm == "Yes")
+ to_chat(user, "You are now a prize! ")
+ if(safety_ckey in slaves_ckeys_list)
+ to_chat(user, "The SPASM beeps in an upset manner, you already have a collar! ")
+ return
+ slaves_ckeys_list += user.ckey
+ var/obj/item/clothing/accessory/collar/casinoslave/C = new(src.loc)
+ C.slavename = "[user.name]"
+ C.slaveckey = "[user.ckey]"
+ C.slaveflavor = user.flavor_text
+ C.slaveooc = user.ooc_notes
+ C.name = "Sentient Prize Collar: Available! [user.name] purchaseable at the SPASM!"
+ C.desc = "SPASM collar. The tags shows in flashy colorful text the wearer is [user.name] and is currently available to buy at the Sentient Prize Automated Sales Machinery!"
+ C.icon_state = "casinoslave_available"
+ C.update_icon()
+ collar_list += C
+
+ spawn_casinochips(casinoslave_price, src.loc)
+
+/obj/machinery/casinoslave_handler/attackby(obj/item/weapon/W as obj, mob/user as mob)
+ if(usr.incapacitated())
+ return
+
+ if(istype(W, /obj/item/weapon/spacecasinocash))
+ if(casinoslave_sale == "disabled")
+ to_chat(user, "Sentient Prize sales are currently disabled. ")
+ return
+ if(!selected_collar.ownername)
+ var/obj/item/weapon/spacecasinocash/C = W
+ if(user.client.ckey == selected_collar.slaveckey)
+ insert_chip(C, user, "selfbuy")
+ return
+ else
+ insert_chip(C, user, "buy")
+ return
+ else
+ to_chat(user, "This Sentient Prize is already owned! If you are the owner you can release the prize by swiping the collar on the SPASM! ")
+ return
+
+ if(istype(W, /obj/item/clothing/accessory/collar/casinoslave))
+ var/obj/item/clothing/accessory/collar/casinoslave/C = W
+ if(user.name != C.slavename && user.name != C.ownername)
+ to_chat(user, "This Sentient Prize collar isn't yours, please give it to the one it tagged for, belongs to, or a casino staff member! ")
+ return
+ if(user.name == C.slavename)
+ if(!C.ownername)
+ to_chat(user,"If collar isn't disabled and entry removed, please select your entry and insert chips. Or contact staff if you need assistance. ")
+ return
+ else
+ to_chat(user,"If collar isn't disabled and entry removed, please ask your owner to free you with collar swipe on the SPASM, or contact staff if you need assistance. ")
+ return
+ if(user.name == C.ownername)
+ var/confirm = tgui_alert(usr, "Are you sure you want to wipe [C.slavename] entry?", "Confirm Sentient Prize Release", list("Yes", "No"))
+ if(confirm == "Yes")
+ to_chat(user, "[C.slavename] collar has been deleted from registry! ")
+ C.icon_state = "casinoslave"
+ C.update_icon()
+ C.name = "a disabled Sentient Prize Collar: [C.slavename]"
+ C.desc = "A collar worn by sentient prizes registered to a SPASM. The tag says its registered to [C.slavename], but harsh red text informs you its been disabled."
+ slaves_ckeys_list -= C.slaveckey
+ C.slaveckey = null
+ collar_list -= C
+
+ if(istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda))
+ if(!check_access(W))
+ to_chat(user, "Access Denied. ")
+ return
+ else
+ to_chat(user, "Proper access, allowed staff controls. ")
+ if(ishuman(usr) || istype(usr, /mob/living/silicon/robot))
+ switch(input(user,"Choose what to do (Management)","SPASM (Management)") in list("Toggle Sentient Prize Sales", "Wipe Selected Prize Entry", "Change Prize Value", "Cancel"))
+ if("Cancel")
+ return
+
+ if("Toggle Sentient Prize Sales")
+ if(casinoslave_sale == "disabled")
+ casinoslave_sale = "enabled"
+ icon_state = "casinoslave_hub_on"
+ update_icon()
+ to_chat(user,"Prize sale has been enabled. ")
+ else
+ casinoslave_sale = "disabled"
+ icon_state = "casinoslave_hub_off"
+ update_icon()
+ to_chat(user,"Prize sale has been disabled. ")
+
+ if("Wipe Selected Prize Entry")
+ if(!selected_collar)
+ to_chat(user, "No collar selected! ")
+ return
+ if(QDELETED(selected_collar))
+ collar_list -= selected_collar
+ slaves_ckeys_list -= selected_collar.slaveckey
+ to_chat(user, "Collar has been destroyed! ")
+ selected_collar = null
+ return
+ var/safety_ckey = selected_collar.slaveckey
+ var/confirm = tgui_alert(usr, "Are you sure you want to wipe [selected_collar.slavename] entry?", "Confirm Sentient Prize", list("Yes", "No"))
+ if(confirm == "Yes")
+ if(safety_ckey == selected_collar.slaveckey)
+ to_chat(user, "[selected_collar.slavename] collar has been deleted from registry! ")
+ selected_collar.icon_state = "casinoslave"
+ selected_collar.update_icon()
+ selected_collar.name = "a disabled Sentient Prize Collar: [selected_collar.slavename]"
+ selected_collar.desc = "A collar worn by sentient prizes registered to a SPASM. The tag says its registered to [selected_collar.slavename], but harsh red text informs you its been disabled."
+ slaves_ckeys_list -= selected_collar.slaveckey
+ selected_collar.slaveckey = null
+ collar_list -= selected_collar
+ selected_collar = null
+ else
+ to_chat(user, "Registry deletion aborted! Changed collar selection! ")
+ return
+
+ if("Change Prize Value")
+ setprice(user)
+
+/obj/machinery/casinoslave_handler/proc/insert_chip(var/obj/item/weapon/spacecasinocash/cashmoney, mob/user, var/buystate)
+ if(cashmoney.worth < casinoslave_price)
+ to_chat(user,"You dont have enough chips to pay for the sentient prize! ")
+ return
+
+ cashmoney.worth -= casinoslave_price
+ cashmoney.update_icon()
+
+ if(cashmoney.worth <= 0)
+ usr.drop_from_inventory(cashmoney)
+ qdel(cashmoney)
+ cashmoney.update_icon()
+
+ if(buystate == "selfbuy")
+ to_chat(user,"You put [casinoslave_price] credits worth of chips into the SPASM and nullify your collar! ")
+ selected_collar.icon_state = "casinoslave"
+ selected_collar.update_icon()
+ selected_collar.name = "a disabled Sentient Prize Collar: [selected_collar.slavename]"
+ selected_collar.desc = "A collar worn by sentient prizes registered to a SPASM. The tag says its registered to [selected_collar.slavename], but harsh red text informs you its been disabled."
+ slaves_ckeys_list -= selected_collar.slaveckey
+ selected_collar.slaveckey = null
+ collar_list -= selected_collar
+ selected_collar = null
+
+ if(buystate == "buy")
+ to_chat(user,"You put [casinoslave_price] credits worth of chips into the SPASM and it pings to inform you bought [selected_collar.slavename]! ")
+ selected_collar.icon_state = "casinoslave_owned"
+ selected_collar.update_icon()
+ selected_collar.ownername = user.name
+ selected_collar.name = "Sentient Prize Collar: [selected_collar.slavename] owned by [selected_collar.ownername]!"
+ selected_collar.desc = "A collar worn by sentient prizes registered to a SPASM. The tag says its registered to [selected_collar.slavename] and they are owned by [selected_collar.ownername]."
+ selected_collar = null
+
+/obj/machinery/casinoslave_handler/proc/setprice(mob/living/user as mob)
+ if(usr.incapacitated())
+ return
+ if(ishuman(usr) || istype(usr, /mob/living/silicon/robot))
+ casinoslave_price = tgui_input_number("Select the desired price (1-1000)", "Set Price", null, null, 1000, 1)
+ if(casinoslave_price>1000 || casinoslave_price<1)
+ to_chat(user,"Invalid price. ")
+ return
+ to_chat(user,"You set the price to [casinoslave_price] ")
diff --git a/code/modules/client/preference_setup/general/01_basic.dm b/code/modules/client/preference_setup/general/01_basic.dm
index 4211bb8add..1bf1a03bd2 100644
--- a/code/modules/client/preference_setup/general/01_basic.dm
+++ b/code/modules/client/preference_setup/general/01_basic.dm
@@ -81,7 +81,7 @@
/datum/category_item/player_setup_item/general/basic/OnTopic(var/href,var/list/href_list, var/mob/user)
if(href_list["rename"])
- var/raw_name = input(user, "Choose your character's name:", "Character Name") as text|null
+ var/raw_name = tgui_input_text(user, "Choose your character's name:", "Character Name")
if (!isnull(raw_name) && CanUseTopic(user))
var/new_name = sanitize_name(raw_name, pref.species, is_FBP())
if(new_name)
@@ -125,7 +125,7 @@
else if(href_list["age"])
var/min_age = get_min_age()
var/max_age = get_max_age()
- var/new_age = input(user, "Choose your character's age:\n([min_age]-[max_age])", "Character Preference", pref.age) as num|null
+ var/new_age = tgui_input_number(user, "Choose your character's age:\n([min_age]-[max_age])", "Character Preference", pref.age, max_age, min_age)
if(new_age && CanUseTopic(user))
pref.age = max(min(round(text2num(new_age)), max_age), min_age)
return TOPIC_REFRESH
@@ -140,7 +140,7 @@
return TOPIC_REFRESH
else if(href_list["metadata"])
- var/new_metadata = sanitize(tgui_input_message(user, "Enter any information you'd like others to see, such as Roleplay-preferences:", "Game Preference" , html_decode(pref.metadata)), extra = 0) //VOREStation Edit
+ var/new_metadata = sanitize(tgui_input_text(user, "Enter any information you'd like others to see, such as Roleplay-preferences:", "Game Preference" , html_decode(pref.metadata), multiline = TRUE), extra = 0) //VOREStation Edit
if(new_metadata && CanUseTopic(user))
pref.metadata = new_metadata
return TOPIC_REFRESH
diff --git a/code/modules/client/preference_setup/general/02_language.dm b/code/modules/client/preference_setup/general/02_language.dm
index 730c05649f..039498c3f9 100644
--- a/code/modules/client/preference_setup/general/02_language.dm
+++ b/code/modules/client/preference_setup/general/02_language.dm
@@ -97,7 +97,7 @@
var/char
var/keys[0]
do
- char = input(usr, "Enter a single special character.\nYou may re-select the same characters.\nThe following characters are already in use by radio: ; : .\nThe following characters are already in use by special say commands: ! * ^", "Enter Character - [3 - keys.len] remaining") as null|text
+ char = tgui_input_text(usr, "Enter a single special character.\nYou may re-select the same characters.\nThe following characters are already in use by radio: ; : .\nThe following characters are already in use by special say commands: ! * ^", "Enter Character - [3 - keys.len] remaining")
if(char)
if(length(char) > 1)
tgui_alert_async(user, "Only single characters allowed.", "Error")
diff --git a/code/modules/client/preference_setup/general/03_body.dm b/code/modules/client/preference_setup/general/03_body.dm
index 210a9d1539..f69f0beea9 100644
--- a/code/modules/client/preference_setup/general/03_body.dm
+++ b/code/modules/client/preference_setup/general/03_body.dm
@@ -836,7 +836,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O
else if(href_list["skin_tone"])
if(!has_flag(mob_species, HAS_SKIN_TONE))
return TOPIC_NOACTION
- var/new_s_tone = input(user, "Choose your character's skin-tone:\n(Light 1 - 220 Dark)", "Character Preference", (-pref.s_tone) + 35) as num|null
+ var/new_s_tone = tgui_input_number(user, "Choose your character's skin-tone:\n(Light 1 - 220 Dark)", "Character Preference", (-pref.s_tone) + 35, 220, 1)
if(new_s_tone && has_flag(mob_species, HAS_SKIN_TONE) && CanUseTopic(user))
pref.s_tone = 35 - max(min( round(new_s_tone), 220),1)
return TOPIC_REFRESH_UPDATE_PREVIEW
diff --git a/code/modules/client/preference_setup/general/06_flavor.dm b/code/modules/client/preference_setup/general/06_flavor.dm
index 7a178f1836..7f5e95f69b 100644
--- a/code/modules/client/preference_setup/general/06_flavor.dm
+++ b/code/modules/client/preference_setup/general/06_flavor.dm
@@ -59,11 +59,11 @@
switch(href_list["flavor_text"])
if("open")
if("general")
- var/msg = sanitize(input(usr,"Give a general description of your character. This will be shown regardless of clothings.","Flavor Text",html_decode(pref.flavor_texts[href_list["flavor_text"]])) as message, MAX_RECORD_LENGTH, extra = 0) //VOREStation Edit: separating out OOC notes
+ var/msg = sanitize(tgui_input_text(usr,"Give a general description of your character. This will be shown regardless of clothings.","Flavor Text",html_decode(pref.flavor_texts[href_list["flavor_text"]]), multiline = TRUE), extra = 0) //VOREStation Edit: separating out OOC notes
if(CanUseTopic(user))
pref.flavor_texts[href_list["flavor_text"]] = msg
else
- var/msg = sanitize(input(usr,"Set the flavor text for your [href_list["flavor_text"]].","Flavor Text",html_decode(pref.flavor_texts[href_list["flavor_text"]])) as message, extra = 0)
+ var/msg = sanitize(tgui_input_text(usr,"Set the flavor text for your [href_list["flavor_text"]].","Flavor Text",html_decode(pref.flavor_texts[href_list["flavor_text"]]), multiline = TRUE), extra = 0)
if(CanUseTopic(user))
pref.flavor_texts[href_list["flavor_text"]] = msg
SetFlavorText(user)
@@ -73,11 +73,11 @@
switch(href_list["flavour_text_robot"])
if("open")
if("Default")
- var/msg = sanitize(input(usr,"Set the default flavour text for your robot. It will be used for any module without individual setting.","Flavour Text",html_decode(pref.flavour_texts_robot["Default"])) as message, extra = 0)
+ var/msg = sanitize(tgui_input_text(usr,"Set the default flavour text for your robot. It will be used for any module without individual setting.","Flavour Text",html_decode(pref.flavour_texts_robot["Default"]), multiline = TRUE), extra = 0)
if(CanUseTopic(user))
pref.flavour_texts_robot[href_list["flavour_text_robot"]] = msg
else
- var/msg = sanitize(input(usr,"Set the flavour text for your robot with [href_list["flavour_text_robot"]] module. If you leave this empty, default flavour text will be used for this module.","Flavour Text",html_decode(pref.flavour_texts_robot[href_list["flavour_text_robot"]])) as message, extra = 0)
+ var/msg = sanitize(tgui_input_text(usr,"Set the flavour text for your robot with [href_list["flavour_text_robot"]] module. If you leave this empty, default flavour text will be used for this module.","Flavour Text",html_decode(pref.flavour_texts_robot[href_list["flavour_text_robot"]]), multiline = TRUE), extra = 0)
if(CanUseTopic(user))
pref.flavour_texts_robot[href_list["flavour_text_robot"]] = msg
SetFlavourTextRobot(user)
diff --git a/code/modules/client/preference_setup/global/01_ui.dm b/code/modules/client/preference_setup/global/01_ui.dm
index 68c569c04e..fbe54b00cd 100644
--- a/code/modules/client/preference_setup/global/01_ui.dm
+++ b/code/modules/client/preference_setup/global/01_ui.dm
@@ -3,30 +3,34 @@
sort_order = 1
/datum/category_item/player_setup_item/player_global/ui/load_preferences(var/savefile/S)
- S["UI_style"] >> pref.UI_style
- S["UI_style_color"] >> pref.UI_style_color
- S["UI_style_alpha"] >> pref.UI_style_alpha
- S["ooccolor"] >> pref.ooccolor
- S["tooltipstyle"] >> pref.tooltipstyle
- S["client_fps"] >> pref.client_fps
- S["ambience_freq"] >> pref.ambience_freq
- S["ambience_chance"] >> pref.ambience_chance
- S["tgui_fancy"] >> pref.tgui_fancy
- S["tgui_lock"] >> pref.tgui_lock
- S["tgui_input_mode"] >> pref.tgui_input_mode
+ S["UI_style"] >> pref.UI_style
+ S["UI_style_color"] >> pref.UI_style_color
+ S["UI_style_alpha"] >> pref.UI_style_alpha
+ S["ooccolor"] >> pref.ooccolor
+ S["tooltipstyle"] >> pref.tooltipstyle
+ S["client_fps"] >> pref.client_fps
+ S["ambience_freq"] >> pref.ambience_freq
+ S["ambience_chance"] >> pref.ambience_chance
+ S["tgui_fancy"] >> pref.tgui_fancy
+ S["tgui_lock"] >> pref.tgui_lock
+ S["tgui_input_mode"] >> pref.tgui_input_mode
+ S["tgui_large_buttons"] >> pref.tgui_large_buttons
+ S["tgui_swapped_buttons"] >> pref.tgui_swapped_buttons
/datum/category_item/player_setup_item/player_global/ui/save_preferences(var/savefile/S)
- S["UI_style"] << pref.UI_style
- S["UI_style_color"] << pref.UI_style_color
- S["UI_style_alpha"] << pref.UI_style_alpha
- S["ooccolor"] << pref.ooccolor
- S["tooltipstyle"] << pref.tooltipstyle
- S["client_fps"] << pref.client_fps
- S["ambience_freq"] << pref.ambience_freq
- S["ambience_chance"] << pref.ambience_chance
- S["tgui_fancy"] << pref.tgui_fancy
- S["tgui_lock"] << pref.tgui_lock
- S["tgui_input_mode"] << pref.tgui_input_mode
+ S["UI_style"] << pref.UI_style
+ S["UI_style_color"] << pref.UI_style_color
+ S["UI_style_alpha"] << pref.UI_style_alpha
+ S["ooccolor"] << pref.ooccolor
+ S["tooltipstyle"] << pref.tooltipstyle
+ S["client_fps"] << pref.client_fps
+ S["ambience_freq"] << pref.ambience_freq
+ S["ambience_chance"] << pref.ambience_chance
+ S["tgui_fancy"] << pref.tgui_fancy
+ S["tgui_lock"] << pref.tgui_lock
+ S["tgui_input_mode"] << pref.tgui_input_mode
+ S["tgui_large_buttons"] << pref.tgui_large_buttons
+ S["tgui_swapped_buttons"] << pref.tgui_swapped_buttons
/datum/category_item/player_setup_item/player_global/ui/sanitize_preferences()
pref.UI_style = sanitize_inlist(pref.UI_style, all_ui_styles, initial(pref.UI_style))
@@ -40,6 +44,8 @@
pref.tgui_fancy = sanitize_integer(pref.tgui_fancy, 0, 1, initial(pref.tgui_fancy))
pref.tgui_lock = sanitize_integer(pref.tgui_lock, 0, 1, initial(pref.tgui_lock))
pref.tgui_input_mode = sanitize_integer(pref.tgui_input_mode, 0, 1, initial(pref.tgui_input_mode))
+ pref.tgui_large_buttons = sanitize_integer(pref.tgui_large_buttons, 0, 1, initial(pref.tgui_large_buttons))
+ pref.tgui_swapped_buttons = sanitize_integer(pref.tgui_swapped_buttons, 0, 1, initial(pref.tgui_swapped_buttons))
/datum/category_item/player_setup_item/player_global/ui/content(var/mob/user)
. = "UI Style: [pref.UI_style] "
@@ -52,7 +58,9 @@
. += "Ambience Chance: [pref.ambience_chance] "
. += "tgui Window Mode: [(pref.tgui_fancy) ? "Fancy (default)" : "Compatible (slower)"] "
. += "tgui Window Placement: [(pref.tgui_lock) ? "Primary Monitor" : "Free (default)"] "
- . += "Input Mode (Say, Me, Whisper, Subtle): [(pref.tgui_input_mode) ? "TGUI" : "BYOND (default)"] "
+ . += "TGUI Input Framework: [(pref.tgui_input_mode) ? "Enabled" : "Disabled (default)"] "
+ . += "TGUI Large Buttons: [(pref.tgui_large_buttons) ? "Enabled (default)" : "Disabled"] "
+ . += "TGUI Swapped Buttons: [(pref.tgui_swapped_buttons) ? "Enabled" : "Disabled (default)"] "
if(can_select_ooc_color(user))
. += "OOC Color: "
if(pref.ooccolor == initial(pref.ooccolor))
@@ -74,7 +82,7 @@
return TOPIC_REFRESH
else if(href_list["select_alpha"])
- var/UI_style_alpha_new = input(user, "Select UI alpha (transparency) level, between 50 and 255.", "Global Preference", pref.UI_style_alpha) as num|null
+ var/UI_style_alpha_new = tgui_input_number(user, "Select UI alpha (transparency) level, between 50 and 255.", "Global Preference", pref.UI_style_alpha, 255, 50)
if(isnull(UI_style_alpha_new) || (UI_style_alpha_new < 50 || UI_style_alpha_new > 255) || !CanUseTopic(user)) return TOPIC_NOACTION
pref.UI_style_alpha = UI_style_alpha_new
return TOPIC_REFRESH
@@ -92,7 +100,7 @@
return TOPIC_REFRESH
else if(href_list["select_client_fps"])
- var/fps_new = input(user, "Input Client FPS (1-200, 0 uses server FPS)", "Global Preference", pref.client_fps) as null|num
+ var/fps_new = tgui_input_number(user, "Input Client FPS (1-200, 0 uses server FPS)", "Global Preference", pref.client_fps, 200, 1)
if(isnull(fps_new) || !CanUseTopic(user)) return TOPIC_NOACTION
if(fps_new < 0 || fps_new > MAX_CLIENT_FPS) return TOPIC_NOACTION
pref.client_fps = fps_new
@@ -101,14 +109,14 @@
return TOPIC_REFRESH
else if(href_list["select_ambience_freq"])
- var/ambience_new = input(user, "Input how often you wish to hear ambience repeated! (1-60 MINUTES, 0 for disabled)", "Global Preference", pref.ambience_freq) as null|num
+ var/ambience_new = tgui_input_number(user, "Input how often you wish to hear ambience repeated! (1-60 MINUTES, 0 for disabled)", "Global Preference", pref.ambience_freq, 60, 0)
if(isnull(ambience_new) || !CanUseTopic(user)) return TOPIC_NOACTION
if(ambience_new < 0 || ambience_new > 60) return TOPIC_NOACTION
pref.ambience_freq = ambience_new
return TOPIC_REFRESH
else if(href_list["select_ambience_chance"])
- var/ambience_chance_new = input(user, "Input the chance you'd like to hear ambience played to you (On area change, or by random ambience). 35 means a 35% chance to play ambience. This is a range from 0-100. 0 disables ambience playing entirely. This is also affected by Ambience Frequency.", "Global Preference", pref.ambience_freq) as null|num
+ var/ambience_chance_new = tgui_input_number(user, "Input the chance you'd like to hear ambience played to you (On area change, or by random ambience). 35 means a 35% chance to play ambience. This is a range from 0-100. 0 disables ambience playing entirely. This is also affected by Ambience Frequency.", "Global Preference", pref.ambience_freq, 100, 0)
if(isnull(ambience_chance_new) || !CanUseTopic(user)) return TOPIC_NOACTION
if(ambience_chance_new < 0 || ambience_chance_new > 100) return TOPIC_NOACTION
pref.ambience_chance = ambience_chance_new
@@ -126,6 +134,14 @@
pref.tgui_input_mode = !pref.tgui_input_mode
return TOPIC_REFRESH
+ else if(href_list["tgui_large_buttons"])
+ pref.tgui_large_buttons = !pref.tgui_large_buttons
+ return TOPIC_REFRESH
+
+ else if(href_list["tgui_swapped_buttons"])
+ pref.tgui_swapped_buttons = !pref.tgui_swapped_buttons
+ return TOPIC_REFRESH
+
else if(href_list["reset"])
switch(href_list["reset"])
if("ui")
diff --git a/code/modules/client/preference_setup/global/03_pai.dm b/code/modules/client/preference_setup/global/03_pai.dm
index 01c5f7a850..6993e0dc6d 100644
--- a/code/modules/client/preference_setup/global/03_pai.dm
+++ b/code/modules/client/preference_setup/global/03_pai.dm
@@ -46,15 +46,15 @@
if(t && CanUseTopic(user))
candidate.name = t
if("desc")
- t = input(user, "Enter a description for your pAI", "Global Preference", html_decode(candidate.description)) as message|null
+ t = tgui_input_text(user, "Enter a description for your pAI", "Global Preference", html_decode(candidate.description), multiline = TRUE)
if(!isnull(t) && CanUseTopic(user))
candidate.description = sanitize(t)
if("role")
- t = input(user, "Enter a role for your pAI", "Global Preference", html_decode(candidate.role)) as text|null
+ t = tgui_input_text(user, "Enter a role for your pAI", "Global Preference", html_decode(candidate.role))
if(!isnull(t) && CanUseTopic(user))
candidate.role = sanitize(t)
if("ooc")
- t = input(user, "Enter any OOC comments", "Global Preference", html_decode(candidate.comments)) as message
+ t = tgui_input_text(user, "Enter any OOC comments", "Global Preference", html_decode(candidate.comments), multiline = TRUE)
if(!isnull(t) && CanUseTopic(user))
candidate.comments = sanitize(t)
return TOPIC_REFRESH
diff --git a/code/modules/client/preference_setup/volume_sliders/01_volume.dm b/code/modules/client/preference_setup/volume_sliders/01_volume.dm
index 90c0b227c0..7dea5d2053 100644
--- a/code/modules/client/preference_setup/volume_sliders/01_volume.dm
+++ b/code/modules/client/preference_setup/volume_sliders/01_volume.dm
@@ -40,7 +40,7 @@
var/channel = href_list["change_volume"]
if(!(channel in pref.volume_channels))
pref.volume_channels["[channel]"] = 1
- var/value = input(usr, "Choose your volume for [channel] (0-200%)", "[channel] volume", (pref.volume_channels[channel] * 100))
+ var/value = tgui_input_number(usr, "Choose your volume for [channel] (0-200%)", "[channel] volume", (pref.volume_channels[channel] * 100), 200, 0)
if(isnum(value))
value = CLAMP(value, 0, 200)
pref.volume_channels["[channel]"] = (value / 100)
diff --git a/code/modules/client/preference_setup/volume_sliders/02_media.dm b/code/modules/client/preference_setup/volume_sliders/02_media.dm
index 3fbb972ad2..98f7bb4cfe 100644
--- a/code/modules/client/preference_setup/volume_sliders/02_media.dm
+++ b/code/modules/client/preference_setup/volume_sliders/02_media.dm
@@ -33,7 +33,7 @@
/datum/category_item/player_setup_item/volume_sliders/media/OnTopic(var/href, var/list/href_list, var/mob/user)
if(href_list["change_media_volume"])
if(CanUseTopic(user))
- var/value = input(usr, "Choose your Jukebox volume (0-100%)", "Jukebox volume", round(pref.media_volume * 100))
+ var/value = tgui_input_number(usr, "Choose your Jukebox volume (0-100%)", "Jukebox volume", round(pref.media_volume * 100), 100, 0)
if(isnum(value))
value = CLAMP(value, 0, 100)
pref.media_volume = value/100.0
diff --git a/code/modules/client/preference_setup/vore/02_size.dm b/code/modules/client/preference_setup/vore/02_size.dm
index 95a944e931..37a71587b1 100644
--- a/code/modules/client/preference_setup/vore/02_size.dm
+++ b/code/modules/client/preference_setup/vore/02_size.dm
@@ -58,7 +58,7 @@
/datum/category_item/player_setup_item/vore/size/OnTopic(var/href, var/list/href_list, var/mob/user)
if(href_list["size_multiplier"])
- var/new_size = input(user, "Choose your character's size, ranging from 25% to 200%", "Set Size") as num|null
+ var/new_size = tgui_input_number(user, "Choose your character's size, ranging from 25% to 200%", "Set Size", null, 200, 25)
if (!ISINRANGE(new_size,25,200))
pref.size_multiplier = 1
to_chat(user, "Invalid size. ")
@@ -72,11 +72,11 @@
return TOPIC_REFRESH_UPDATE_PREVIEW
else if(href_list["weight"])
- var/new_weight = input(user, "Choose your character's relative body weight.\n\
+ var/new_weight = tgui_input_number(user, "Choose your character's relative body weight.\n\
This measurement should be set relative to a normal 5'10'' person's body and not the actual size of your character.\n\
If you set your weight to 500 because you're a naga or have metal implants then complain that you're a blob I\n\
swear to god I will find you and I will punch you for not reading these directions!\n\
- ([WEIGHT_MIN]-[WEIGHT_MAX])", "Character Preference") as num|null
+ ([WEIGHT_MIN]-[WEIGHT_MAX])", "Character Preference", null, WEIGHT_MAX, WEIGHT_MIN)
if(new_weight)
var/unit_of_measurement = tgui_alert(user, "Is that number in pounds (lb) or kilograms (kg)?", "Confirmation", list("Pounds", "Kilograms"))
if(unit_of_measurement == "Pounds")
@@ -87,7 +87,7 @@
return TOPIC_REFRESH
else if(href_list["weight_gain"])
- var/weight_gain_rate = tgui_input_num(user, "Choose your character's rate of weight gain between 100% \
+ var/weight_gain_rate = tgui_input_number(user, "Choose your character's rate of weight gain between 100% \
(full realism body fat gain) and 0% (no body fat gain).\n\
(If you want to disable weight gain, set this to 0.01 to round it to 0%.)\
([WEIGHT_CHANGE_MIN]-[WEIGHT_CHANGE_MAX])", "Character Preference", pref.weight_gain)
@@ -96,7 +96,7 @@
return TOPIC_REFRESH
else if(href_list["weight_loss"])
- var/weight_loss_rate = tgui_input_num(user, "Choose your character's rate of weight loss between 100% \
+ var/weight_loss_rate = tgui_input_number(user, "Choose your character's rate of weight loss between 100% \
(full realism body fat loss) and 0% (no body fat loss).\n\
(If you want to disable weight loss, set this to 0.01 round it to 0%.)\
([WEIGHT_CHANGE_MIN]-[WEIGHT_CHANGE_MAX])", "Character Preference", pref.weight_loss)
diff --git a/code/modules/client/preference_setup/vore/09_misc.dm b/code/modules/client/preference_setup/vore/09_misc.dm
index 8f3d467ff5..cda9c15d75 100644
--- a/code/modules/client/preference_setup/vore/09_misc.dm
+++ b/code/modules/client/preference_setup/vore/09_misc.dm
@@ -58,7 +58,7 @@
pref.directory_erptag = new_erptag
return TOPIC_REFRESH
else if(href_list["directory_ad"])
- var/msg = sanitize(input(user,"Write your advertisement here!", "Flavor Text", html_decode(pref.directory_ad)) as message, extra = 0) //VOREStation Edit: separating out OOC notes
+ var/msg = sanitize(tgui_input_text(user,"Write your advertisement here!", "Flavor Text", html_decode(pref.directory_ad), multiline = TRUE), extra = 0) //VOREStation Edit: separating out OOC notes
pref.directory_ad = msg
return TOPIC_REFRESH
else if(href_list["toggle_sensor_setting"])
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index d401436c3d..c06d0ab35d 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -28,7 +28,9 @@ var/list/preferences_datums = list()
var/tgui_fancy = TRUE
var/tgui_lock = FALSE
- var/tgui_input_mode = FALSE // Say, Me, Whisper, Subtle Input Mode; Disabled by default; FALSE = BYOND, TRUE = TGUI
+ var/tgui_input_mode = FALSE // All the Input Boxes (Text,Number,List,Alert)
+ var/tgui_large_buttons = TRUE
+ var/tgui_swapped_buttons = FALSE
//character preferences
var/num_languages = 0 //CHOMPEdit
diff --git a/code/modules/client/ui_style.dm b/code/modules/client/ui_style.dm
index 4b321f2f53..8317d977e3 100644
--- a/code/modules/client/ui_style.dm
+++ b/code/modules/client/ui_style.dm
@@ -48,7 +48,7 @@ var/global/list/all_tooltip_styles = list(
var/UI_style_new = tgui_input_list(usr, "Select a style. White is recommended for customization", "UI Style Choice", 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
+ var/UI_style_alpha_new = tgui_input_number(usr, "Select a new alpha (transparency) parameter for your UI, between 50 and 255", null, null, 255, 50)
if(!UI_style_alpha_new || !(UI_style_alpha_new <= 255 && UI_style_alpha_new >= 50)) return
var/UI_style_color_new = input(usr, "Choose your UI color. Dark colors are not recommended!") as color|null
diff --git a/code/modules/clothing/under/miscellaneous_vr.dm b/code/modules/clothing/under/miscellaneous_vr.dm
index 72690c590f..e52df06471 100644
--- a/code/modules/clothing/under/miscellaneous_vr.dm
+++ b/code/modules/clothing/under/miscellaneous_vr.dm
@@ -76,7 +76,7 @@
to_chat(H,"You must be WEARING the uniform to change your size. ")
return
- var/new_size = input(usr, "Put the desired size (25-200%), or (1-600%) in dormitory areas.", "Set Size", 200) as num|null
+ var/new_size = tgui_input_number(usr, "Put the desired size (25-200%), or (1-600%) in dormitory areas.", "Set Size", 200, 600, 1)
if(!new_size)
return //cancelled
diff --git a/code/modules/economy/Accounts_DB.dm b/code/modules/economy/Accounts_DB.dm
index 237f1206cb..8fa9fe73e4 100644
--- a/code/modules/economy/Accounts_DB.dm
+++ b/code/modules/economy/Accounts_DB.dm
@@ -127,12 +127,12 @@
creating_new_account = 1
if("add_funds")
- var/amount = input(usr, "Enter the amount you wish to add", "Silently add funds") as num
+ var/amount = tgui_input_number(usr, "Enter the amount you wish to add", "Silently add funds")
if(detailed_account_view)
detailed_account_view.money = min(detailed_account_view.money + amount, fund_cap)
if("remove_funds")
- var/amount = input(usr, "Enter the amount you wish to remove", "Silently remove funds") as num
+ var/amount = tgui_input_number(usr, "Enter the amount you wish to remove", "Silently remove funds")
if(detailed_account_view)
detailed_account_view.money = max(detailed_account_view.money - amount, -fund_cap)
diff --git a/code/modules/economy/EFTPOS.dm b/code/modules/economy/EFTPOS.dm
index 1517af0cc0..beae04ee7c 100644
--- a/code/modules/economy/EFTPOS.dm
+++ b/code/modules/economy/EFTPOS.dm
@@ -145,9 +145,9 @@
if(href_list["choice"])
switch(href_list["choice"])
if("change_code")
- var/attempt_code = input(usr, "Re-enter the current EFTPOS access code", "Confirm old EFTPOS code") as num
+ var/attempt_code = tgui_input_number(usr, "Re-enter the current EFTPOS access code", "Confirm old EFTPOS code")
if(attempt_code == access_code)
- var/trycode = input(usr, "Enter a new access code for this device (4-6 digits, numbers only)", "Enter new EFTPOS code") as num
+ var/trycode = tgui_input_number(usr, "Enter a new access code for this device (4-6 digits, numbers only)", "Enter new EFTPOS code", null, 999999, 1000)
if(trycode >= 1000 && trycode <= 999999)
access_code = trycode
else
@@ -163,8 +163,8 @@
else
to_chat(usr, "[bicon(src)]Incorrect code entered. ")
if("link_account")
- var/attempt_account_num = input(usr, "Enter account number to pay EFTPOS charges into", "New account number") as num
- var/attempt_pin = input(usr, "Enter pin code", "Account pin") as num
+ var/attempt_account_num = tgui_input_number(usr, "Enter account number to pay EFTPOS charges into", "New account number")
+ var/attempt_pin = tgui_input_number(usr, "Enter pin code", "Account pin")
linked_account = attempt_account_access(attempt_account_num, attempt_pin, 1)
if(linked_account)
if(linked_account.suspended)
@@ -176,7 +176,7 @@
var/choice = sanitize(input(usr, "Enter reason for EFTPOS transaction", "Transaction purpose"))
if(choice) transaction_purpose = choice
if("trans_value")
- var/try_num = input(usr, "Enter amount for EFTPOS transaction", "Transaction amount") as num
+ var/try_num = tgui_input_number(usr, "Enter amount for EFTPOS transaction", "Transaction amount")
if(try_num < 0)
tgui_alert_async(usr, "That is not a valid amount!")
else
@@ -187,7 +187,7 @@
transaction_locked = 0
transaction_paid = 0
else
- var/attempt_code = input(usr, "Enter EFTPOS access code", "Reset Transaction") as num
+ var/attempt_code = tgui_input_number(usr, "Enter EFTPOS access code", "Reset Transaction")
if(attempt_code == access_code)
transaction_locked = 0
transaction_paid = 0
@@ -229,7 +229,7 @@
var/attempt_pin = ""
var/datum/money_account/D = get_account(C.associated_account_number)
if(D.security_level)
- attempt_pin = input(usr, "Enter pin code", "EFTPOS transaction") as num
+ attempt_pin = tgui_input_number(usr, "Enter pin code", "EFTPOS transaction")
D = null
D = attempt_account_access(C.associated_account_number, attempt_pin, 2)
if(D)
diff --git a/code/modules/economy/cash.dm b/code/modules/economy/cash.dm
index ec2ad51dca..e5a539f5e8 100644
--- a/code/modules/economy/cash.dm
+++ b/code/modules/economy/cash.dm
@@ -80,7 +80,7 @@
return worth
/obj/item/weapon/spacecash/attack_self()
- var/amount = input(usr, "How many [initial_name]s do you want to take? (0 to [src.worth])", "Take Money", 20) as num
+ var/amount = tgui_input_number(usr, "How many [initial_name]s do you want to take? (0 to [src.worth])", "Take Money", 20)
if(!src || QDELETED(src))
return
amount = round(CLAMP(amount, 0, src.worth))
diff --git a/code/modules/economy/cash_register.dm b/code/modules/economy/cash_register.dm
index 9286401aae..08d4ab66d8 100644
--- a/code/modules/economy/cash_register.dm
+++ b/code/modules/economy/cash_register.dm
@@ -107,8 +107,8 @@
if("toggle_cash_lock")
cash_locked = !cash_locked
if("link_account")
- var/attempt_account_num = input(usr, "Enter account number", "New account number") as num
- var/attempt_pin = input(usr, "Enter PIN", "Account PIN") as num
+ var/attempt_account_num = tgui_input_number(usr, "Enter account number", "New account number")
+ var/attempt_pin = tgui_input_number(usr, "Enter PIN", "Account PIN")
linked_account = attempt_account_access(attempt_account_num, attempt_pin, 1)
if(linked_account)
if(linked_account.suspended)
@@ -117,11 +117,11 @@
else
to_chat(usr, "[bicon(src)]Account not found. ")
if("custom_order")
- var/t_purpose = sanitize(input(usr, "Enter purpose", "New purpose") as text)
+ var/t_purpose = sanitize(tgui_input_text(usr, "Enter purpose", "New purpose"))
if (!t_purpose || !Adjacent(usr)) return
transaction_purpose = t_purpose
item_list += t_purpose
- var/t_amount = round(input(usr, "Enter price", "New price") as num)
+ var/t_amount = round(tgui_input_number(usr, "Enter price", "New price"))
if (!t_amount || !Adjacent(usr) || t_amount < 0) return
transaction_amount += t_amount
price_list += t_amount
@@ -129,7 +129,7 @@
src.visible_message("[bicon(src)][transaction_purpose]: [t_amount] Thaler\s.")
if("set_amount")
var/item_name = locate(href_list["item"])
- var/n_amount = round(input(usr, "Enter amount", "New amount") as num)
+ var/n_amount = round(tgui_input_number(usr, "Enter amount", "New amount"))
n_amount = CLAMP(n_amount, 0, 20)
if (!item_list[item_name] || !Adjacent(usr)) return
transaction_amount += (n_amount - item_list[item_name]) * price_list[item_name]
@@ -234,7 +234,7 @@
var/datum/money_account/D = get_account(I.associated_account_number)
var/attempt_pin = ""
if(D && D.security_level)
- attempt_pin = input(usr, "Enter PIN", "Transaction") as num
+ attempt_pin = tgui_input_number(usr, "Enter PIN", "Transaction")
D = null
D = attempt_account_access(I.associated_account_number, attempt_pin, 2)
diff --git a/code/modules/economy/casinocash_ch.dm b/code/modules/economy/casinocash_ch.dm
index f321acb1dc..99e0e36f26 100644
--- a/code/modules/economy/casinocash_ch.dm
+++ b/code/modules/economy/casinocash_ch.dm
@@ -76,7 +76,7 @@
return worth
/obj/item/weapon/spacecasinocash/attack_self()
- var/amount = input(usr, "How much credits worth of chips do you want to take? (0 to [src.worth])", "Take chips", 20) as num
+ var/amount = tgui_input_number(usr, "How much credits worth of chips do you want to take? (0 to [src.worth])", "Take chips", 20)
if(!src || QDELETED(src))
return
amount = round(CLAMP(amount, 0, src.worth))
diff --git a/code/modules/economy/retail_scanner.dm b/code/modules/economy/retail_scanner.dm
index 6e68b644aa..d9755b5041 100644
--- a/code/modules/economy/retail_scanner.dm
+++ b/code/modules/economy/retail_scanner.dm
@@ -101,8 +101,8 @@
else
to_chat(usr, "[bicon(src)]Insufficient access. ")
if("link_account")
- var/attempt_account_num = input(usr, "Enter account number", "New account number") as num
- var/attempt_pin = input(usr, "Enter PIN", "Account PIN") as num
+ var/attempt_account_num = tgui_input_number(usr, "Enter account number", "New account number")
+ var/attempt_pin = tgui_input_number(usr, "Enter PIN", "Account PIN")
linked_account = attempt_account_access(attempt_account_num, attempt_pin, 1)
if(linked_account)
if(linked_account.suspended)
@@ -111,11 +111,11 @@
else
to_chat(usr, "[bicon(src)]Account not found. ")
if("custom_order")
- var/t_purpose = sanitize(input(usr, "Enter purpose", "New purpose") as text)
+ var/t_purpose = sanitize(tgui_input_text(usr, "Enter purpose", "New purpose"))
if (!t_purpose || !Adjacent(usr)) return
transaction_purpose = t_purpose
item_list += t_purpose
- var/t_amount = round(input(usr, "Enter price", "New price") as num)
+ var/t_amount = round(tgui_input_number(usr, "Enter price", "New price"))
if (!t_amount || !Adjacent(usr)) return
transaction_amount += t_amount
price_list += t_amount
@@ -123,7 +123,7 @@
src.visible_message("[bicon(src)][transaction_purpose]: [t_amount] Thaler\s.")
if("set_amount")
var/item_name = locate(href_list["item"])
- var/n_amount = round(input(usr, "Enter amount", "New amount") as num)
+ var/n_amount = round(tgui_input_number(usr, "Enter amount", "New amount"))
n_amount = CLAMP(n_amount, 0, 20)
if (!item_list[item_name] || !Adjacent(usr)) return
transaction_amount += (n_amount - item_list[item_name]) * price_list[item_name]
@@ -211,7 +211,7 @@
var/datum/money_account/D = get_account(I.associated_account_number)
var/attempt_pin = ""
if(D && D.security_level)
- attempt_pin = input(usr, "Enter PIN", "Transaction") as num
+ attempt_pin = tgui_input_number(usr, "Enter PIN", "Transaction")
D = null
D = attempt_account_access(I.associated_account_number, attempt_pin, 2)
diff --git a/code/modules/economy/vending.dm b/code/modules/economy/vending.dm
index f073f3f396..55c726539a 100644
--- a/code/modules/economy/vending.dm
+++ b/code/modules/economy/vending.dm
@@ -327,7 +327,7 @@ GLOBAL_LIST_EMPTY(vending_products)
// Have the customer punch in the PIN before checking if there's enough money. Prevents people from figuring out acct is
// empty at high security levels
if(customer_account.security_level != 0) //If card requires pin authentication (ie seclevel 1 or 2)
- var/attempt_pin = input(usr, "Enter pin code", "Vendor transaction") as num
+ var/attempt_pin = tgui_input_number(usr, "Enter pin code", "Vendor transaction")
customer_account = attempt_account_access(I.associated_account_number, attempt_pin, 2)
if(!customer_account)
diff --git a/code/modules/emotes/definitions/audible.dm b/code/modules/emotes/definitions/audible.dm
index 4df078aa11..d702a32d3a 100644
--- a/code/modules/emotes/definitions/audible.dm
+++ b/code/modules/emotes/definitions/audible.dm
@@ -205,6 +205,14 @@
emote_message_3p = "purrs."
emote_sound = 'sound/voice/cat_purr_long.ogg'
+/decl/emote/audible/fennecscream
+ key = "fennecscream"
+ emote_message_3p = "screeches!"
+
+/decl/emote/audible/zoom
+ key = "zoom"
+ emote_message_3p = "zooms."
+
/decl/emote/audible/teshsqueak
key = "surprised"
emote_message_1p = "You chirp in surprise!"
diff --git a/code/modules/events/event_manager.dm b/code/modules/events/event_manager.dm
index ea4f8d59cd..f8dca1824b 100644
--- a/code/modules/events/event_manager.dm
+++ b/code/modules/events/event_manager.dm
@@ -156,7 +156,7 @@
config.allow_random_events = text2num(href_list["pause_all"])
log_and_message_admins("has [config.allow_random_events ? "resumed" : "paused"] countdown for all events.")
else if(href_list["interval"])
- var/delay = input(usr, "Enter delay modifier. A value less than one means events fire more often, higher than one less often.", "Set Interval Modifier") as num|null
+ var/delay = tgui_input_number(usr, "Enter delay modifier. A value less than one means events fire more often, higher than one less often.", "Set Interval Modifier")
if(delay && delay > 0)
var/datum/event_container/EC = locate(href_list["interval"])
EC.delay_modifier = delay
@@ -173,7 +173,7 @@
else if(href_list["back"])
selected_event_container = null
else if(href_list["set_name"])
- var/name = sanitize(input(usr, "Enter event name.", "Set Name") as text|null)
+ var/name = sanitize(tgui_input_text(usr, "Enter event name.", "Set Name"))
if(name)
var/datum/event_meta/EM = locate(href_list["set_name"])
EM.name = name
@@ -183,7 +183,7 @@
var/datum/event_meta/EM = locate(href_list["set_type"])
EM.event_type = type
else if(href_list["set_weight"])
- var/weight = input(usr, "Enter weight. A higher value means higher chance for the event of being selected.", "Set Weight") as num|null
+ var/weight = tgui_input_number(usr, "Enter weight. A higher value means higher chance for the event of being selected.", "Set Weight")
if(weight && weight > 0)
var/datum/event_meta/EM = locate(href_list["set_weight"])
EM.weight = weight
diff --git a/code/modules/food/food/condiment.dm b/code/modules/food/food/condiment.dm
index 5b137957db..2373db3bc2 100644
--- a/code/modules/food/food/condiment.dm
+++ b/code/modules/food/food/condiment.dm
@@ -135,6 +135,11 @@
desc = "Barbecue sauce, it's labeled 'sweet and spicy'."
icon_state = "barbecue"
center_of_mass = list("x"=16, "y"=6)
+ if("sprinkles")
+ name = "sprinkles"
+ desc = "Bottle of sprinkles, colourful!"
+ icon_state= "sprinkles"
+ center_of_mass = list("x"=16, "y"=6)
else
name = "Misc Condiment Bottle"
if (reagents.reagent_list.len==1)
@@ -208,6 +213,13 @@
. = ..()
reagents.add_reagent("yeast", 50)
+/obj/item/weapon/reagent_containers/food/condiment/sprinkles
+ name = "Sprinkles"
+
+/obj/item/weapon/reagent_containers/food/condiment/sprinkles/Initialize()
+ . = ..()
+ reagents.add_reagent("sprinkles", 50)
+
/obj/item/weapon/reagent_containers/food/condiment/small
possible_transfer_amounts = list(1,20)
amount_per_transfer_from_this = 1
@@ -448,22 +460,58 @@
//End of MRE stuff.
-/obj/item/weapon/reagent_containers/food/condiment/flour
- name = "flour sack"
- desc = "A big bag of flour. Good for baking!"
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour
+ name = "flour carton"
+ desc = "A big carton of flour. Good for baking!"
icon = 'icons/obj/food.dmi'
icon_state = "flour"
volume = 220
center_of_mass = list("x"=16, "y"=8)
-/obj/item/weapon/reagent_containers/food/condiment/flour/on_reagent_change()
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour/on_reagent_change()
+ update_icon()
return
-/obj/item/weapon/reagent_containers/food/condiment/flour/Initialize()
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour/Initialize()
. = ..()
reagents.add_reagent("flour", 200)
randpixel_xy()
+/obj/item/weapon/reagent_containers/food/condiment/carton/update_icon()
+ overlays.Cut()
+
+ if(reagents.total_volume)
+ var/image/filling = image('icons/obj/food.dmi', src, "[icon_state]10")
+
+ filling.icon_state = "[icon_state]-[clamp(round(100 * reagents.total_volume / volume, 25), 0, 100)]"
+
+ overlays += filling
+
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour/rustic
+ name = "flour sack"
+ desc = "An artisanal sack of flour. Classy!"
+ icon_state = "flour_bag"
+
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar
+ name = "sugar carton"
+ desc = "A big carton of sugar. Sweet!"
+ icon_state = "sugar"
+ volume = 120
+ center_of_mass = list("x"=16, "y"=8)
+
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar/on_reagent_change()
+ update_icon()
+ return
+
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar/Initialize()
+ . = ..()
+ reagents.add_reagent("sugar", 100)
+
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar/rustic
+ name = "sugar sack"
+ desc = "An artisanal sack of sugar. Classy!"
+ icon_state = "sugar_bag"
+
/obj/item/weapon/reagent_containers/food/condiment/spacespice
name = "space spices"
desc = "An exotic blend of spices for cooking. Definitely not worms."
@@ -477,4 +525,4 @@
/obj/item/weapon/reagent_containers/food/condiment/spacespice/Initialize()
. = ..()
- reagents.add_reagent("spacespice", 40)
\ No newline at end of file
+ reagents.add_reagent("spacespice", 40)
diff --git a/code/modules/food/kitchen/cooking_machines/container.dm b/code/modules/food/kitchen/cooking_machines/container.dm
index 981a809fd4..bd4a9d8e00 100644
--- a/code/modules/food/kitchen/cooking_machines/container.dm
+++ b/code/modules/food/kitchen/cooking_machines/container.dm
@@ -7,6 +7,7 @@
var/shortname
var/max_space = 20//Maximum sum of w-classes of foods in this container at once
var/max_reagents = 80//Maximum units of reagents
+ var/food_items = 0 // Used for icon updates
flags = OPENCONTAINER | NOREACT
var/list/insertable = list(
/obj/item/weapon/reagent_containers/food/snacks,
@@ -59,6 +60,8 @@
return
I.forceMove(src)
to_chat(user, "You put the [I] into the [src]. ")
+ food_items += 1
+ update_icon()
return
/obj/item/weapon/reagent_containers/cooking_container/verb/empty()
@@ -102,6 +105,8 @@
/obj/item/weapon/reagent_containers/cooking_container/AltClick(var/mob/user)
do_empty(user)
+ food_items = 0
+ update_icon()
//Deletes contents of container.
//Used when food is burned, before replacing it with a burned mess
@@ -158,6 +163,22 @@
if (weights[I])
holder.trans_to(I, weights[I] / total)
+/obj/item/weapon/reagent_containers/cooking_container/update_icon()
+ overlays.Cut()
+
+ if(food_items)
+ var/image/filling = image('icons/obj/cooking_machines.dmi', src, "[icon_state]10")
+
+ var/percent = round((food_items / max_space) * 100)
+ switch(percent)
+ if(0 to 2) filling.icon_state = "[icon_state]"
+ if(3 to 24) filling.icon_state = "[icon_state]1"
+ if(25 to 49) filling.icon_state = "[icon_state]2"
+ if(50 to 74) filling.icon_state = "[icon_state]3"
+ if(75 to 79) filling.icon_state = "[icon_state]4"
+ if(80 to INFINITY) filling.icon_state = "[icon_state]5"
+
+ overlays += filling
/obj/item/weapon/reagent_containers/cooking_container/oven
name = "oven dish"
@@ -186,4 +207,4 @@
name = "grill rack"
shortname = "rack"
desc = "Put ingredients 'in'/on this; designed for use with a grill. Warranty void if used incorrectly. Alt click to remove contents."
- icon_state = "grillrack"
\ No newline at end of file
+ icon_state = "grillrack"
diff --git a/code/modules/food/kitchen/smartfridge/smartfridge.dm b/code/modules/food/kitchen/smartfridge/smartfridge.dm
index f98da20f8c..11ce0e461e 100644
--- a/code/modules/food/kitchen/smartfridge/smartfridge.dm
+++ b/code/modules/food/kitchen/smartfridge/smartfridge.dm
@@ -216,7 +216,7 @@
if(params["amount"])
amount = params["amount"]
else
- amount = input(usr, "How many items?", "How many items would you like to take out?", 1) as num|null
+ amount = tgui_input_number(usr, "How many items?", "How many items would you like to take out?", 1)
if(QDELETED(src) || QDELETED(usr) || !usr.Adjacent(src))
return FALSE
diff --git a/code/modules/games/cards.dm b/code/modules/games/cards.dm
index ad8b592f27..bb8a707010 100644
--- a/code/modules/games/cards.dm
+++ b/code/modules/games/cards.dm
@@ -157,7 +157,7 @@
players += player
//players -= usr
var/maxcards = max(min(cards.len,10),1)
- var/dcard = input(usr, "How many card(s) do you wish to deal? You may deal up to [maxcards] cards.") as num
+ var/dcard = tgui_input_number(usr, "How many card(s) do you wish to deal? You may deal up to [maxcards] cards.", null, null, maxcards)
if(dcard > maxcards)
return
var/mob/living/M = tgui_input_list(usr, "Who do you wish to deal [dcard] card(s)?", "Deal to whom?", players)
@@ -321,7 +321,7 @@
var/i
var/maxcards = min(cards.len,5) // Maximum of 5 cards at once
- var/discards = input(usr, "How many cards do you want to discard? You may discard up to [maxcards] card(s)") as num
+ var/discards = tgui_input_number(usr, "How many cards do you want to discard? You may discard up to [maxcards] card(s)", null, null, maxcards, 0)
if(discards > maxcards)
return
for (i = 0;i < discards;i++)
diff --git a/code/modules/instruments/songs/editor.dm b/code/modules/instruments/songs/editor.dm
index f88e06bce1..af884bf2d3 100644
--- a/code/modules/instruments/songs/editor.dm
+++ b/code/modules/instruments/songs/editor.dm
@@ -130,7 +130,7 @@
else if(href_list["import"])
var/t = ""
do
- t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
+ t = html_encode(tgui_input_text(usr, "Please paste the entire song, formatted:", text("[]", name), t, multiline = TRUE))
if(!in_range(parent, usr))
return
@@ -163,7 +163,7 @@
INVOKE_ASYNC(src, .proc/start_playing, usr)
else if(href_list["newline"])
- var/newline = html_encode(input(usr, "Enter your line: ", parent.name) as text|null)
+ var/newline = html_encode(tgui_input_text(usr, "Enter your line: ", parent.name))
if(!newline || !in_range(parent, usr))
return
if(lines.len > MUSIC_MAXLINES)
@@ -191,22 +191,22 @@
stop_playing()
else if(href_list["setlinearfalloff"])
- var/amount = input(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration") as null|num
+ var/amount = tgui_input_number(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration")
if(!isnull(amount))
set_linear_falloff_duration(round(amount * 10, world.tick_lag))
else if(href_list["setexpfalloff"])
- var/amount = input(usr, "Set exponential sustain factor", "Exponential sustain factor") as null|num
+ var/amount = tgui_input_number(usr, "Set exponential sustain factor", "Exponential sustain factor")
if(!isnull(amount))
set_exponential_drop_rate(round(amount, 0.00001))
else if(href_list["setvolume"])
- var/amount = input(usr, "Set volume", "Volume") as null|num
+ var/amount = tgui_input_number(usr, "Set volume", "Volume")
if(!isnull(amount))
set_volume(round(amount, 1))
else if(href_list["setdropoffvolume"])
- var/amount = input(usr, "Set dropoff threshold", "Dropoff Threshold Volume") as null|num
+ var/amount = tgui_input_number(usr, "Set dropoff threshold", "Dropoff Threshold Volume")
if(!isnull(amount))
set_dropoff_volume(round(amount, 0.01))
@@ -233,7 +233,7 @@
set_instrument(choice)
else if(href_list["setnoteshift"])
- var/amount = input(usr, "Set note shift", "Note Shift") as null|num
+ var/amount = tgui_input_number(usr, "Set note shift", "Note Shift")
if(!isnull(amount))
note_shift = clamp(amount, note_shift_min, note_shift_max)
diff --git a/code/modules/integrated_electronics/core/pins.dm b/code/modules/integrated_electronics/core/pins.dm
index 4020d03a34..a52e626dcc 100644
--- a/code/modules/integrated_electronics/core/pins.dm
+++ b/code/modules/integrated_electronics/core/pins.dm
@@ -154,7 +154,7 @@ list[](
var/new_data = null
switch(type_to_use)
if("string")
- new_data = input(usr, "Now type in a string.","[src] string writing", istext(default) ? default : null) as null|text
+ new_data = tgui_input_text(usr, "Now type in a string.","[src] string writing", istext(default) ? default : null)
if(istext(new_data) && holder.check_interactivity(user) )
to_chat(user, "You input [new_data] into the pin. ")
return new_data
diff --git a/code/modules/integrated_electronics/core/special_pins/string_pin.dm b/code/modules/integrated_electronics/core/special_pins/string_pin.dm
index a0428a12de..db9bc31944 100644
--- a/code/modules/integrated_electronics/core/special_pins/string_pin.dm
+++ b/code/modules/integrated_electronics/core/special_pins/string_pin.dm
@@ -3,7 +3,7 @@
name = "string pin"
/datum/integrated_io/string/ask_for_pin_data(mob/user)
- var/new_data = input(usr, "Please type in a string.","[src] string writing") as null|text
+ var/new_data = tgui_input_text(usr, "Please type in a string.","[src] string writing")
new_data = sanitizeSafe(new_data, MAX_MESSAGE_LEN, 0, 0)
if(new_data && holder.check_interactivity(user) )
diff --git a/code/modules/integrated_electronics/core/tools.dm b/code/modules/integrated_electronics/core/tools.dm
index a2b32f27e3..43cc7eac9a 100644
--- a/code/modules/integrated_electronics/core/tools.dm
+++ b/code/modules/integrated_electronics/core/tools.dm
@@ -122,7 +122,7 @@
switch(type_to_use)
if("string")
accepting_refs = 0
- new_data = input(usr, "Now type in a string.","[src] string writing") as null|text
+ new_data = tgui_input_text(usr, "Now type in a string.","[src] string writing")
new_data = sanitizeSafe(new_data, MAX_MESSAGE_LEN, 0, 0)
if(istext(new_data) && CanInteract(user, GLOB.tgui_physical_state))
data_to_write = new_data
diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm
index e53b1397de..2d275e2386 100644
--- a/code/modules/integrated_electronics/subtypes/input.dm
+++ b/code/modules/integrated_electronics/subtypes/input.dm
@@ -72,7 +72,7 @@
power_draw_per_use = 4
/obj/item/integrated_circuit/input/textpad/ask_for_input(mob/user)
- var/new_input = input(user, "Enter some words, please.","Number pad", get_pin_data(IC_OUTPUT, 1)) as null|text
+ var/new_input = tgui_input_text(user, "Enter some words, please.","Number pad", get_pin_data(IC_OUTPUT, 1))
if(istext(new_input) && CanInteract(user, GLOB.tgui_physical_state))
set_pin_data(IC_OUTPUT, 1, new_input)
push_data()
diff --git a/code/modules/integrated_electronics/subtypes/memory.dm b/code/modules/integrated_electronics/subtypes/memory.dm
index 2fb6ad4598..0148c01c05 100644
--- a/code/modules/integrated_electronics/subtypes/memory.dm
+++ b/code/modules/integrated_electronics/subtypes/memory.dm
@@ -96,13 +96,13 @@
switch(type_to_use)
if("string")
accepting_refs = 0
- new_data = input(usr, "Now type in a string.","[src] string writing") as null|text
+ new_data = tgui_input_text(usr, "Now type in a string.","[src] string writing")
if(istext(new_data) && CanInteract(user, GLOB.tgui_physical_state))
O.data = new_data
to_chat(user, "You set \the [src]'s memory to [O.display_data(O.data)]. ")
if("number")
accepting_refs = 0
- new_data = input(usr, "Now type in a number.","[src] number writing") as null|num
+ new_data = tgui_input_number(usr, "Now type in a number.","[src] number writing")
if(isnum(new_data) && CanInteract(user, GLOB.tgui_physical_state))
O.data = new_data
to_chat(user, "You set \the [src]'s memory to [O.display_data(O.data)]. ")
diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm
index 85a06cd571..421110c391 100644
--- a/code/modules/library/lib_items.dm
+++ b/code/modules/library/lib_items.dm
@@ -229,7 +229,7 @@ Book Cart End
var/choice = tgui_input_list(usr, "What would you like to change?", "Change What?", list("Title", "Contents", "Author", "Cancel"))
switch(choice)
if("Title")
- var/newtitle = reject_bad_text(sanitizeSafe(input(usr, "Write a new title:")))
+ var/newtitle = reject_bad_text(sanitizeSafe(tgui_input_text(usr, "Write a new title:")))
if(!newtitle)
to_chat(usr, "The title is invalid.")
return
@@ -244,7 +244,7 @@ Book Cart End
else
src.dat += content
if("Author")
- var/newauthor = sanitize(input(usr, "Write the author's name:"))
+ var/newauthor = sanitize(tgui_input_text(usr, "Write the author's name:"))
if(!newauthor)
to_chat(usr, "The name is invalid.")
return
diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm
index 5800fb826d..f030c5f5f9 100644
--- a/code/modules/library/lib_machines.dm
+++ b/code/modules/library/lib_machines.dm
@@ -75,7 +75,7 @@
return
if(href_list["settitle"])
- var/newtitle = input(usr, "Enter a title to search for:") as text|null
+ var/newtitle = tgui_input_text(usr, "Enter a title to search for:")
if(newtitle)
title = sanitize(newtitle)
else
@@ -89,7 +89,7 @@
category = "Any"
category = sanitizeSQL(category)
if(href_list["setauthor"])
- var/newauthor = input(usr, "Enter an author to search for:") as text|null
+ var/newauthor = tgui_input_text(usr, "Enter an author to search for:")
if(newauthor)
author = sanitize(newauthor)
else
@@ -369,7 +369,7 @@
if(checkoutperiod < 1)
checkoutperiod = 1
if(href_list["editbook"])
- buffer_book = sanitizeSafe(input(usr, "Enter the book's title:") as text|null)
+ buffer_book = sanitizeSafe(tgui_input_text(usr, "Enter the book's title:"))
if(href_list["editmob"])
buffer_mob = sanitize(input(usr, "Enter the recipient's name:") as text|null, MAX_NAME_LEN)
if(href_list["checkout"])
diff --git a/code/modules/mining/abandonedcrates.dm b/code/modules/mining/abandonedcrates.dm
index 1b043ec8c1..f3a315827d 100644
--- a/code/modules/mining/abandonedcrates.dm
+++ b/code/modules/mining/abandonedcrates.dm
@@ -146,7 +146,7 @@
return
to_chat(user, "The crate is locked with a Deca-code lock. ")
- var/input = input(usr, "Enter [codelen] digits. All digits must be unique.", "Deca-Code Lock", "") as text
+ var/input = tgui_input_text(usr, "Enter [codelen] digits. All digits must be unique.", "Deca-Code Lock", "")
if(!Adjacent(user))
return
var/list/sanitised = list()
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 66bf95cbb6..193aa62d58 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -928,7 +928,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/mob/living/M = tgui_input_list(src, "Select who to whisper to:", "Whisper to?", options)
if(!M)
return 0
- var/msg = sanitize(input(src, "Message:", "Spectral Whisper") as text|null)
+ var/msg = sanitize(tgui_input_text(src, "Message:", "Spectral Whisper"))
if(msg)
log_say("(SPECWHISP to [key_name(M)]): [msg]", src)
to_chat(M, " You hear a strange, unidentifiable voice in your head... [msg] ")
diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm
index d9d6b79663..b2a61b9c12 100644
--- a/code/modules/mob/holder.dm
+++ b/code/modules/mob/holder.dm
@@ -173,6 +173,12 @@ var/list/holder_mob_icon_cache = list()
item_state = "cat"
/obj/item/weapon/holder/cat/runtime
+
+/obj/item/holder/fennec
+ origin_tech = list(TECH_BIO = 2)
+
+/obj/item/holder/cat/runtime
+
origin_tech = list(TECH_BIO = 2, TECH_DATA = 4)
/obj/item/weapon/holder/cat/cak
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index 47cc1e8f57..bac825919a 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -143,7 +143,9 @@ var/list/_human_default_emotes = list(
/decl/emote/audible/coyawoo2,
/decl/emote/audible/coyawoo3,
/decl/emote/audible/coyawoo4,
- /decl/emote/audible/coyawoo5
+ /decl/emote/audible/coyawoo5,
+ /decl/emote/audible/fennecscream,
+ /decl/emote/audible/zoom
//VOREStation Add End
)
@@ -273,7 +275,10 @@ var/list/_simple_mob_default_emotes = list(
/decl/emote/visible/blep,
/decl/emote/audible/prbt,
/decl/emote/audible/gyoh,
- /decl/emote/audible/rumble
+ /decl/emote/audible/rumble,
+ /decl/emote/audible/fennecscream,
+ /decl/emote/audible/zoom
+
)
//VOREStation Add End
@@ -292,7 +297,7 @@ var/list/_simple_mob_default_emotes = list(
var/datum/gender/T = gender_datums[get_visible_gender()]
- pose = sanitize(input(usr, "This is [src]. [T.he]...", "Pose", null) as text)
+ pose = sanitize(tgui_input_text(usr, "This is [src]. [T.he]...", "Pose", null))
/mob/living/carbon/human/verb/set_flavor()
set name = "Set Flavour Text"
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 14e2f452ac..65560c651f 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -76,22 +76,12 @@
BP_L_LEG = skip_body & EXAMINE_SKIPLEGS,
BP_R_LEG = skip_body & EXAMINE_SKIPLEGS)
- var/datum/gender/T = gender_datums[get_visible_gender()]
+ var/gender_hidden = (skip_gear & EXAMINE_SKIPJUMPSUIT) && (skip_body & EXAMINE_SKIPFACE)
+ var/gender_key = get_visible_gender(user, gender_hidden)
- if((skip_gear & EXAMINE_SKIPJUMPSUIT) && (skip_body & EXAMINE_SKIPFACE)) //big suits/masks/helmets make it hard to tell their gender
- T = gender_datums[PLURAL]
-
- else if(species && species.ambiguous_genders)
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- if(H.species && !istype(species, H.species))
- T = gender_datums[PLURAL]// Species with ambiguous_genders will not show their true gender upon examine if the examiner is not also the same species.
- if(!(issilicon(user) || isobserver(user))) // Ghosts and borgs are all knowing
- T = gender_datums[PLURAL]
-
- if(!T)
- // Just in case someone VVs the gender to something strange. It'll runtime anyway when it hits usages, better to CRASH() now with a helpful message.
- CRASH("Gender datum was null; key was '[((skip_gear & EXAMINE_SKIPJUMPSUIT) && (skip_body & EXAMINE_SKIPFACE)) ? PLURAL : gender]'")
+ var/datum/gender/T = gender_datums[gender_key]
+ if (!T)
+ CRASH({"Null gender datum on examine: mob="[src]",hidden="[gender_hidden]",key="[gender_key]",bio="[gender]",id="[identifying_gender]""})
var/name_ender = ""
if(!((skip_gear & EXAMINE_SKIPJUMPSUIT) && (skip_body & EXAMINE_SKIPFACE)))
@@ -144,7 +134,7 @@
LAZYADD(pocket_msg, r_store_message)
if(LAZYLEN(pocket_msg))
tie_msg += " Near the waist it has [english_list(pocket_msg)]."
-
+
if(w_uniform.blood_DNA)
msg += "[T.He] [T.is] wearing [bicon(w_uniform)] [w_uniform.gender==PLURAL?"some":"a"] [(w_uniform.blood_color != SYNTH_BLOOD_COLOUR) ? "blood" : "oil"]-stained [w_uniform.name]![tie_msg] "
else
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 52e2b7c5ac..3a1a77826c 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -439,7 +439,7 @@
for (var/datum/data/record/R in data_core.security)
if (R.fields["id"] == E.fields["id"])
if(hasHUD(usr,"security"))
- var/t1 = sanitize(input(usr, "Add Comment:", "Sec. records", null, null) as message)
+ var/t1 = sanitize(tgui_input_text(usr, "Add Comment:", "Sec. records", null, null, multiline = TRUE))
if ( !(t1) || usr.stat || usr.restrained() || !(hasHUD(usr,"security")) )
return
var/counter = 1
@@ -556,7 +556,7 @@
for (var/datum/data/record/R in data_core.medical)
if (R.fields["id"] == E.fields["id"])
if(hasHUD(usr,"medical"))
- var/t1 = sanitize(input(usr, "Add Comment:", "Med. records", null, null) as message)
+ var/t1 = sanitize(tgui_input_text(usr, "Add Comment:", "Med. records", null, null, multiline = TRUE))
if ( !(t1) || usr.stat || usr.restrained() || !(hasHUD(usr,"medical")) )
return
var/counter = 1
@@ -588,11 +588,11 @@
src << browse(null, "window=flavor_changes")
return
if("general")
- var/msg = sanitize(input(usr,"Update the general description of your character. This will be shown regardless of clothing.","Flavor Text",html_decode(flavor_texts[href_list["flavor_change"]])) as message, extra = 0) //VOREStation Edit: separating out OOC notes
+ var/msg = sanitize(tgui_input_text(usr,"Update the general description of your character. This will be shown regardless of clothing.","Flavor Text",html_decode(flavor_texts[href_list["flavor_change"]]), multiline = TRUE), extra = 0) //VOREStation Edit: separating out OOC notes
flavor_texts[href_list["flavor_change"]] = msg
return
else
- var/msg = sanitize(input(usr,"Update the flavor text for your [href_list["flavor_change"]].","Flavor Text",html_decode(flavor_texts[href_list["flavor_change"]])) as message, extra = 0)
+ var/msg = sanitize(tgui_input_text(usr,"Update the flavor text for your [href_list["flavor_change"]].","Flavor Text",html_decode(flavor_texts[href_list["flavor_change"]]), multiline = TRUE), extra = 0)
flavor_texts[href_list["flavor_change"]] = msg
set_flavor()
return
@@ -808,7 +808,7 @@
if (isnull(target))
return
- var/say = sanitize(input(usr, "What do you wish to say"))
+ var/say = sanitize(tgui_input_text(usr, "What do you wish to say"))
if(mRemotetalk in target.mutations)
target.show_message(" You hear [src.real_name]'s voice: [say] ")
else
@@ -855,13 +855,25 @@
remoteview_target = null
reset_view(0)
-/mob/living/carbon/human/get_visible_gender()
- if(wear_suit && wear_suit.flags_inv & HIDEJUMPSUIT && ((head && head.flags_inv & HIDEMASK) || wear_mask))
- return PLURAL //plural is the gender-neutral default
- if(species)
- if(species.ambiguous_genders)
- return PLURAL // regardless of what you're wearing, your gender can't be figured out
- return get_gender()
+/mob/living/carbon/human/get_visible_gender(mob/user, force)
+ switch(force)
+ if(VISIBLE_GENDER_FORCE_PLURAL)
+ return PLURAL
+ if(VISIBLE_GENDER_FORCE_IDENTIFYING)
+ return get_gender()
+ if(VISIBLE_GENDER_FORCE_BIOLOGICAL)
+ return gender
+ else
+ if((wear_mask || (head?.flags_inv & HIDEMASK)) && (wear_suit?.flags_inv & HIDEJUMPSUIT))
+ return PLURAL
+ if(species?.ambiguous_genders && user)
+ if(ishuman(user))
+ var/mob/living/carbon/human/human = user
+ if(!istype(human.species, species))
+ return PLURAL
+ else if(!isobserver(user) && !issilicon(user))
+ return PLURAL
+ return get_gender()
/mob/living/carbon/human/proc/increase_germ_level(n)
if(gloves)
@@ -1211,7 +1223,7 @@
var/max_length = bloody_hands * 30 //tweeter style
- var/message = sanitize(input(usr, "Write a message. It cannot be longer than [max_length] characters.","Blood writing", ""))
+ var/message = sanitize(tgui_input_text(usr, "Write a message. It cannot be longer than [max_length] characters.","Blood writing", ""))
if (message)
var/used_blood_amount = round(length(message) / 30, 1)
diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm
index df2994530d..1a9686e7e8 100644
--- a/code/modules/mob/living/carbon/human/human_movement.dm
+++ b/code/modules/mob/living/carbon/human/human_movement.dm
@@ -291,3 +291,8 @@
playsound(T, S, volume, FALSE)
return
+
+/mob/living/carbon/human/set_dir(var/new_dir)
+ . = ..()
+ if(. && (species.tail || tail_style))
+ update_tail_showing()
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/human_powers.dm b/code/modules/mob/living/carbon/human/human_powers.dm
index 74dfba1b36..f8d49b046a 100644
--- a/code/modules/mob/living/carbon/human/human_powers.dm
+++ b/code/modules/mob/living/carbon/human/human_powers.dm
@@ -94,7 +94,7 @@
if(!target) return
- text = input(usr, "What would you like to say?", "Speak to creature", null, null)
+ text = tgui_input_text(usr, "What would you like to say?", "Speak to creature", null, null)
text = sanitize(text)
@@ -134,7 +134,7 @@
set desc = "Whisper silently to someone over a distance."
set category = "Abilities"
- var/msg = sanitize(input(usr, "Message:", "Psychic Whisper") as text|null)
+ var/msg = sanitize(tgui_input_text(usr, "Message:", "Psychic Whisper"))
if(msg)
log_say("(PWHISPER to [key_name(M)]) [msg]", src)
to_chat(M, "You hear a strange, alien voice in your head... [msg] ")
diff --git a/code/modules/mob/living/carbon/human/species/station/teshari.dm b/code/modules/mob/living/carbon/human/species/station/teshari.dm
index 753dd211ce..17fe6606da 100644
--- a/code/modules/mob/living/carbon/human/species/station/teshari.dm
+++ b/code/modules/mob/living/carbon/human/species/station/teshari.dm
@@ -64,7 +64,7 @@
ambiguous_genders = TRUE
- spawn_flags = SPECIES_CAN_JOIN | SPECIES_IS_WHITELISTED | SPECIES_NO_POSIBRAIN //CHOMPedit: This is overriden by teshari_vr.dm. Noting here for future reference.
+ spawn_flags = SPECIES_CAN_JOIN | SPECIES_IS_WHITELISTED
appearance_flags = HAS_HAIR_COLOR | HAS_SKIN_COLOR | HAS_EYE_COLOR
bump_flag = MONKEY
swap_flags = MONKEY|SLIME|SIMPLE_ANIMAL
diff --git a/code/modules/mob/living/carbon/human/species/xenomorphs/alien_powers.dm b/code/modules/mob/living/carbon/human/species/xenomorphs/alien_powers.dm
index acd1b8f540..f627a191fe 100644
--- a/code/modules/mob/living/carbon/human/species/xenomorphs/alien_powers.dm
+++ b/code/modules/mob/living/carbon/human/species/xenomorphs/alien_powers.dm
@@ -67,7 +67,7 @@
to_chat(src, "Their plasma vessel is missing. ")
return
- var/amount = input(usr, "Amount:", "Transfer Plasma to [M]") as num
+ var/amount = tgui_input_number(usr, "Amount:", "Transfer Plasma to [M]")
if (amount)
amount = abs(round(amount))
if(check_alien_ability(amount,0,O_PLASMA))
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index aff59d4010..868fd0e467 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -66,37 +66,40 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
#define MOB_DAM_LAYER 4 //Injury overlay sprites like open wounds
#define SURGERY_LAYER 5 //Overlays for open surgical sites
#define UNDERWEAR_LAYER 6 //Underwear/bras/etc
-#define SHOES_LAYER_ALT 7 //Shoe-slot item (when set to be under uniform via verb)
-#define UNIFORM_LAYER 8 //Uniform-slot item
-#define ID_LAYER 9 //ID-slot item
-#define SHOES_LAYER 10 //Shoe-slot item
-#define GLOVES_LAYER 11 //Glove-slot item
-#define BELT_LAYER 12 //Belt-slot item
-#define SUIT_LAYER 13 //Suit-slot item
-#define TAIL_LAYER 14 //Some species have tails to render
-#define GLASSES_LAYER 15 //Eye-slot item
-#define BELT_LAYER_ALT 16 //Belt-slot item (when set to be above suit via verb)
-#define SUIT_STORE_LAYER 17 //Suit storage-slot item
-#define BACK_LAYER 18 //Back-slot item
-#define HAIR_LAYER 19 //The human's hair
-#define HAIR_ACCESSORY_LAYER 20 //VOREStation edit. Simply move this up a number if things are added.
-#define EARS_LAYER 21 //Both ear-slot items (combined image)
-#define EYES_LAYER 22 //Mob's eyes (used for glowing eyes)
-#define FACEMASK_LAYER 23 //Mask-slot item
-#define HEAD_LAYER 24 //Head-slot item
-#define HANDCUFF_LAYER 25 //Handcuffs, if the human is handcuffed, in a secret inv slot
-#define LEGCUFF_LAYER 26 //Same as handcuffs, for legcuffs
-#define L_HAND_LAYER 27 //Left-hand item
-#define R_HAND_LAYER 28 //Right-hand item
-#define WING_LAYER 29 //Wings or protrusions over the suit.
-#define TAIL_LAYER_ALT 30 //Modified tail-sprite layer. Tend to be larger.
-#define MODIFIER_EFFECTS_LAYER 31 //Effects drawn by modifiers
-#define FIRE_LAYER 32 //'Mob on fire' overlay layer
-#define MOB_WATER_LAYER 33 //'Mob submerged' overlay layer
-#define TARGETED_LAYER 34 //'Aimed at' overlay layer
-#define TOTAL_LAYERS 34 //VOREStation edit. <---- KEEP THIS UPDATED, should always equal the highest number here, used to initialize a list.
+#define TAIL_SOUTH_LAYER 7 //Tail as viewed from the south
+#define SHOES_LAYER_ALT 8 //Shoe-slot item (when set to be under uniform via verb)
+#define UNIFORM_LAYER 9 //Uniform-slot item
+#define ID_LAYER 10 //ID-slot item
+#define SHOES_LAYER 11 //Shoe-slot item
+#define GLOVES_LAYER 12 //Glove-slot item
+#define BELT_LAYER 13 //Belt-slot item
+#define SUIT_LAYER 14 //Suit-slot item
+#define TAIL_NORTH_LAYER 15 //Some species have tails to render (As viewed from the N, E, or W)
+#define GLASSES_LAYER 16 //Eye-slot item
+#define BELT_LAYER_ALT 17 //Belt-slot item (when set to be above suit via verb)
+#define SUIT_STORE_LAYER 18 //Suit storage-slot item
+#define BACK_LAYER 19 //Back-slot item
+#define HAIR_LAYER 20 //The human's hair
+#define HAIR_ACCESSORY_LAYER 21 //VOREStation edit. Simply move this up a number if things are added.
+#define EARS_LAYER 22 //Both ear-slot items (combined image)
+#define EYES_LAYER 23 //Mob's eyes (used for glowing eyes)
+#define FACEMASK_LAYER 24 //Mask-slot item
+#define HEAD_LAYER 25 //Head-slot item
+#define HANDCUFF_LAYER 26 //Handcuffs, if the human is handcuffed, in a secret inv slot
+#define LEGCUFF_LAYER 27 //Same as handcuffs, for legcuffs
+#define L_HAND_LAYER 28 //Left-hand item
+#define R_HAND_LAYER 29 //Right-hand item
+#define WING_LAYER 30 //Wings or protrusions over the suit.
+#define TAIL_NORTH_LAYER_ALT 31 //Modified tail-sprite layer. Tend to be larger.
+#define MODIFIER_EFFECTS_LAYER 32 //Effects drawn by modifiers
+#define FIRE_LAYER 33 //'Mob on fire' overlay layer
+#define MOB_WATER_LAYER 34 //'Mob submerged' overlay layer
+#define TARGETED_LAYER 35 //'Aimed at' overlay layer
+#define TOTAL_LAYERS 35 //VOREStation edit. <---- KEEP THIS UPDATED, should always equal the highest number here, used to initialize a list.
//////////////////////////////////
+#define GET_TAIL_LAYER (dir == SOUTH ? TAIL_SOUTH_LAYER : TAIL_NORTH_LAYER)
+
/mob/living/carbon/human
var/list/overlays_standing[TOTAL_LAYERS]
var/previous_damage_appearance // store what the body last looked like, so we only have to update it if something changed
@@ -376,6 +379,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
update_tail_showing()
update_wing_showing()
+
/mob/living/carbon/human/proc/update_skin()
if(QDESTROYING(src))
return
@@ -838,7 +842,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
suit_sprite = INV_SUIT_DEF_ICON
var/icon/c_mask = null
- var/tail_is_rendered = (overlays_standing[TAIL_LAYER] || overlays_standing[TAIL_LAYER_ALT])
+ var/tail_is_rendered = (overlays_standing[TAIL_NORTH_LAYER] || overlays_standing[TAIL_NORTH_LAYER_ALT] || overlays_standing[TAIL_SOUTH_LAYER])
var/valid_clip_mask = tail_style?.clip_mask
if(tail_is_rendered && valid_clip_mask && !(istype(suit) && suit.taurized)) //Clip the lower half of the suit off using the tail's clip mask for taurs since taur bodies aren't hidden.
@@ -955,16 +959,19 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
if(QDESTROYING(src))
return
- remove_layer(TAIL_LAYER)
- remove_layer(TAIL_LAYER_ALT) // Alt Tail Layer
+ remove_layer(TAIL_NORTH_LAYER)
+ remove_layer(TAIL_NORTH_LAYER_ALT)
+ remove_layer(TAIL_SOUTH_LAYER)
- var/used_tail_layer = tail_alt ? TAIL_LAYER_ALT : TAIL_LAYER
+ var/tail_layer = GET_TAIL_LAYER
+ if(tail_alt && tail_layer == TAIL_NORTH_LAYER)
+ tail_layer = TAIL_NORTH_LAYER_ALT
var/image/tail_image = get_tail_image()
if(tail_image)
- tail_image.layer = BODY_LAYER+used_tail_layer
- overlays_standing[used_tail_layer] = tail_image
- apply_layer(used_tail_layer)
+ tail_image.layer = BODY_LAYER+tail_layer
+ overlays_standing[tail_layer] = tail_image
+ apply_layer(tail_layer)
return
var/species_tail = species.get_tail(src) // Species tail icon_state prefix.
@@ -972,7 +979,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
//This one is actually not that bad I guess.
if(species_tail && !(wear_suit && wear_suit.flags_inv & HIDETAIL))
var/icon/tail_s = get_tail_icon()
- overlays_standing[used_tail_layer] = image(icon = tail_s, icon_state = "[species_tail]_s", layer = BODY_LAYER+used_tail_layer) // Alt Tail Layer
+ overlays_standing[tail_layer] = image(icon = tail_s, icon_state = "[species_tail]_s", layer = BODY_LAYER+tail_layer)
animate_tail_reset()
//TODO: Is this the appropriate place for this, and not on species...?
@@ -997,19 +1004,22 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
return tail_icon
/mob/living/carbon/human/proc/set_tail_state(var/t_state)
- var/used_tail_layer = tail_alt ? TAIL_LAYER_ALT : TAIL_LAYER // Alt Tail Layer
- var/image/tail_overlay = overlays_standing[used_tail_layer]
+ var/tail_layer = GET_TAIL_LAYER
+ if(tail_alt && tail_layer == TAIL_NORTH_LAYER)
+ tail_layer = TAIL_NORTH_LAYER_ALT
+ var/image/tail_overlay = overlays_standing[tail_layer]
- remove_layer(TAIL_LAYER)
- remove_layer(TAIL_LAYER_ALT)
+ remove_layer(TAIL_NORTH_LAYER)
+ remove_layer(TAIL_NORTH_LAYER_ALT)
+ remove_layer(TAIL_SOUTH_LAYER)
if(tail_overlay)
- overlays_standing[used_tail_layer] = tail_overlay
+ overlays_standing[tail_layer] = tail_overlay
if(species.get_tail_animation(src))
tail_overlay.icon_state = t_state
. = tail_overlay
- apply_layer(used_tail_layer)
+ apply_layer(tail_layer)
//Not really once, since BYOND can't do that.
//Update this if the ability to flick() images or make looping animation start at the first frame is ever added.
@@ -1019,9 +1029,9 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
return
var/t_state = "[species.get_tail(src)]_once"
- var/used_tail_layer = tail_alt ? TAIL_LAYER_ALT : TAIL_LAYER // Alt Tail Layer
+ var/tail_layer = GET_TAIL_LAYER
- var/image/tail_overlay = overlays_standing[used_tail_layer] // Alt Tail Layer
+ var/image/tail_overlay = overlays_standing[tail_layer]
if(tail_overlay && tail_overlay.icon_state == t_state)
return //let the existing animation finish
@@ -1029,7 +1039,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
if(tail_overlay)
spawn(20)
//check that the animation hasn't changed in the meantime
- if(overlays_standing[used_tail_layer] == tail_overlay && tail_overlay.icon_state == t_state) // Alt Tail Layer
+ if(overlays_standing[tail_layer] == tail_overlay && tail_overlay.icon_state == t_state)
animate_tail_stop()
/mob/living/carbon/human/proc/animate_tail_start()
@@ -1279,7 +1289,9 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
#undef GLOVES_LAYER
#undef BELT_LAYER
#undef SUIT_LAYER
-#undef TAIL_LAYER
+#undef TAIL_NORTH_LAYER
+#undef TAIL_SOUTH_LAYER
+#undef GET_TAIL_LAYER
#undef GLASSES_LAYER
#undef BELT_LAYER_ALT
#undef SUIT_STORE_LAYER
diff --git a/code/modules/mob/living/living_powers.dm b/code/modules/mob/living/living_powers.dm
index 787dd31361..ac24abfbc5 100644
--- a/code/modules/mob/living/living_powers.dm
+++ b/code/modules/mob/living/living_powers.dm
@@ -14,7 +14,7 @@
return
if(status_flags & HIDING)
- reveal("You have stopped hiding. ")
+ reveal(FALSE, "You have stopped hiding. ")
else
status_flags |= HIDING
layer = HIDING_LAYER //Just above cables with their 2.44
diff --git a/code/modules/mob/living/living_vr.dm b/code/modules/mob/living/living_vr.dm
index a570ed60db..ee2ecc04d7 100644
--- a/code/modules/mob/living/living_vr.dm
+++ b/code/modules/mob/living/living_vr.dm
@@ -12,13 +12,13 @@
var/sayselect = tgui_alert(src, "Which say-verb do you wish to customize?", "Select Verb", list("Say","Whisper","Ask (?)","Exclaim/Shout/Yell (!)","Cancel"))
if(sayselect == "Say")
- custom_say = lowertext(sanitize(input(usr, "This word or phrase will appear instead of 'says': [src] says, \"Hi.\"", "Custom Say", null) as text))
+ custom_say = lowertext(sanitize(tgui_input_text(usr, "This word or phrase will appear instead of 'says': [src] says, \"Hi.\"", "Custom Say", null)))
else if(sayselect == "Whisper")
- custom_whisper = lowertext(sanitize(input(usr, "This word or phrase will appear instead of 'whispers': [src] whispers, \"Hi...\"", "Custom Whisper", null) as text))
+ custom_whisper = lowertext(sanitize(tgui_input_text(usr, "This word or phrase will appear instead of 'whispers': [src] whispers, \"Hi...\"", "Custom Whisper", null)))
else if(sayselect == "Ask (?)")
- custom_ask = lowertext(sanitize(input(usr, "This word or phrase will appear instead of 'asks': [src] asks, \"Hi?\"", "Custom Ask", null) as text))
+ custom_ask = lowertext(sanitize(tgui_input_text(usr, "This word or phrase will appear instead of 'asks': [src] asks, \"Hi?\"", "Custom Ask", null)))
else if(sayselect == "Exclaim/Shout/Yell (!)")
- custom_exclaim = lowertext(sanitize(input(usr, "This word or phrase will appear instead of 'exclaims', 'shouts' or 'yells': [src] exclaims, \"Hi!\"", "Custom Exclaim", null) as text))
+ custom_exclaim = lowertext(sanitize(tgui_input_text(usr, "This word or phrase will appear instead of 'exclaims', 'shouts' or 'yells': [src] exclaims, \"Hi!\"", "Custom Exclaim", null)))
else
return
@@ -27,7 +27,7 @@
set desc = "Sets OOC notes about yourself or your RP preferences or status."
set category = "OOC"
- var/new_metadata = sanitize(input(usr, "Enter any information you'd like others to see, such as Roleplay-preferences. This will not be saved permanently, only for this round.", "Game Preference" , html_decode(ooc_notes)) as message, extra = 0)
+ var/new_metadata = sanitize(tgui_input_text(usr, "Enter any information you'd like others to see, such as Roleplay-preferences. This will not be saved permanently, only for this round.", "Game Preference" , html_decode(ooc_notes), multiline = TRUE), extra = 0)
if(new_metadata && CanUseTopic(usr))
ooc_notes = new_metadata
to_chat(usr, "OOC notes updated.")
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index dd3fc5f064..ea15eaa8c3 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -363,7 +363,7 @@ var/list/ai_verbs_default = list(
if(message_cooldown)
to_chat(src, "Please allow one minute to pass between announcements.")
return
- var/input = input(usr, "Please write a message to announce to the station crew.", "A.I. Announcement")
+ var/input = tgui_input_text(usr, "Please write a message to announce to the station crew.", "A.I. Announcement")
if(!input)
return
@@ -418,7 +418,7 @@ var/list/ai_verbs_default = list(
if(emergency_message_cooldown)
to_chat(usr, "Arrays recycling. Please stand by. ")
return
- var/input = sanitize(input(usr, "Please choose a message to transmit to [using_map.boss_short] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response. There is a 30 second delay before you may send another message, be clear, full and concise.", "To abort, send an empty message.", ""))
+ var/input = sanitize(tgui_input_text(usr, "Please choose a message to transmit to [using_map.boss_short] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response. There is a 30 second delay before you may send another message, be clear, full and concise.", "To abort, send an empty message.", ""))
if(!input)
return
CentCom_announce(input, usr)
diff --git a/code/modules/mob/living/silicon/pai/pai_vr.dm b/code/modules/mob/living/silicon/pai/pai_vr.dm
index 9aaa5e200b..d4de9085e0 100644
--- a/code/modules/mob/living/silicon/pai/pai_vr.dm
+++ b/code/modules/mob/living/silicon/pai/pai_vr.dm
@@ -3,6 +3,7 @@
icon = 'icons/mob/pai_vr.dmi'
softfall = TRUE
var/eye_glow = TRUE
+ var/hide_glow = FALSE
var/image/eye_layer = null // Holds the eye overlay.
var/eye_color = "#00ff0d"
var/global/list/wide_chassis = list(
@@ -46,7 +47,6 @@
/mob/living/silicon/pai/Initialize()
. = ..()
- verbs |= /mob/living/proc/hide
verbs |= /mob/proc/dominate_predator
verbs |= /mob/living/proc/dominate_prey
verbs |= /mob/living/proc/set_size
@@ -147,10 +147,11 @@
set category = "pAI Commands"
set name = "Toggle Eye Glow"
if(chassis in allows_eye_color)
- if(eye_glow)
+ if(eye_glow && !hide_glow)
eye_glow = FALSE
else
eye_glow = TRUE
+ hide_glow = FALSE
update_icon()
else
to_chat(src, "Your selected chassis cannot modify its eye glow!")
@@ -182,7 +183,7 @@
eye_layer = image(icon, "[icon_state]-eyes")
eye_layer.appearance_flags = appearance_flags
eye_layer.color = eye_color
- if(eye_glow)
+ if(eye_glow && !hide_glow)
eye_layer.plane = PLANE_LIGHTING_ABOVE
add_overlay(eye_layer)
@@ -429,4 +430,4 @@
continue
else if(isobserver(G) && G.is_preference_enabled(/datum/client_preference/ghost_ears))
if(is_preference_enabled(/datum/client_preference/whisubtle_vis) || G.client.holder)
- to_chat(G, "[src.name]'s screen prints, \"[message]\" ")
+ to_chat(G, "[src.name]'s screen prints, \"[message]\" ")
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/pai/recruit.dm b/code/modules/mob/living/silicon/pai/recruit.dm
index 05bede4172..f62b5134cd 100644
--- a/code/modules/mob/living/silicon/pai/recruit.dm
+++ b/code/modules/mob/living/silicon/pai/recruit.dm
@@ -73,15 +73,15 @@ var/datum/paiController/paiController // Global handler for pAI candidates
if(t)
candidate.name = t
if("desc")
- t = input(usr, "Enter a description for your pAI", "pAI Description", candidate.description) as message
+ t = tgui_input_text(usr, "Enter a description for your pAI", "pAI Description", candidate.description, multiline = TRUE)
if(t)
candidate.description = sanitize(t)
if("role")
- t = input(usr, "Enter a role for your pAI", "pAI Role", candidate.role) as text
+ t = tgui_input_text(usr, "Enter a role for your pAI", "pAI Role", candidate.role)
if(t)
candidate.role = sanitize(t)
if("ooc")
- t = input(usr, "Enter any OOC comments", "pAI OOC Comments", candidate.comments) as message
+ t = tgui_input_text(usr, "Enter any OOC comments", "pAI OOC Comments", candidate.comments, multiline = TRUE)
if(t)
candidate.comments = sanitize(t)
if("save")
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 7caad30448..d014bf30b4 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -259,14 +259,14 @@
set desc = "Sets a description which will be shown when someone examines you."
set category = "IC"
- pose = sanitize(input(usr, "This is [src]. It is...", "Pose", null) as text)
+ pose = sanitize(tgui_input_text(usr, "This is [src]. It is...", "Pose", null))
/mob/living/silicon/verb/set_flavor()
set name = "Set Flavour Text"
set desc = "Sets an extended description of your character's features."
set category = "IC"
- flavor_text = sanitize(input(usr, "Please enter your new flavour text.", "Flavour text", null) as text)
+ flavor_text = sanitize(tgui_input_text(usr, "Please enter your new flavour text.", "Flavour text", null))
/mob/living/silicon/binarycheck()
return 1
diff --git a/code/modules/mob/living/simple_mob/simple_mob_vr.dm b/code/modules/mob/living/simple_mob/simple_mob_vr.dm
index ae30050e95..e7866ec375 100644
--- a/code/modules/mob/living/simple_mob/simple_mob_vr.dm
+++ b/code/modules/mob/living/simple_mob/simple_mob_vr.dm
@@ -215,6 +215,7 @@
// Since they have bellies, add verbs to toggle settings on them.
verbs |= /mob/living/simple_mob/proc/toggle_digestion
verbs |= /mob/living/simple_mob/proc/toggle_fancygurgle
+ verbs |= /mob/living/proc/vertical_nom
//A much more detailed version of the default /living implementation
var/obj/belly/B = new /obj/belly(src)
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/fennec.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/fennec.dm
new file mode 100644
index 0000000000..0ea451246f
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/fennec.dm
@@ -0,0 +1,29 @@
+/mob/living/simple_mob/animal/passive/fennec
+ name = "fennec"
+ desc = "A fox preferring arid climates, also known as a dingler, or a goob."
+ tt_desc = "Vulpes Zerda"
+ icon_state = "fennec"
+ item_state = "fennec"
+
+ movement_cooldown = 0.5 SECONDS
+
+ see_in_dark = 6
+ response_help = "pets"
+ response_disarm = "gently pushes aside"
+ response_harm = "kicks"
+
+ holder_type = /obj/item/holder/fennec
+ mob_size = MOB_SMALL
+
+ has_langs = list("Cat, Dog") //they're similar, why not.
+
+/mob/living/simple_mob/animal/passive/fennec/faux
+ name = "faux"
+ desc = "Domesticated fennec. Seems to like screaming just as much though."
+
+/mob/living/simple_mob/animal/passive/fennec/Initialize()
+ icon_living = "[initial(icon_state)]"
+ icon_dead = "[initial(icon_state)]_dead"
+ icon_rest = "[initial(icon_state)]_rest"
+ update_icon()
+ return ..()
diff --git a/code/modules/mob/mob_transformation_simple.dm b/code/modules/mob/mob_transformation_simple.dm
index b3a1f39454..2534755873 100644
--- a/code/modules/mob/mob_transformation_simple.dm
+++ b/code/modules/mob/mob_transformation_simple.dm
@@ -9,7 +9,7 @@
return
if(!new_type)
- new_type = input(usr, "Mob type path:", "Mob type") as text|null
+ new_type = tgui_input_text(usr, "Mob type path:", "Mob type")
if(istext(new_type))
new_type = text2path(new_type)
diff --git a/code/modules/mob/new_player/preferences_setup.dm b/code/modules/mob/new_player/preferences_setup.dm
index 66d712bc44..e564bba7b0 100644
--- a/code/modules/mob/new_player/preferences_setup.dm
+++ b/code/modules/mob/new_player/preferences_setup.dm
@@ -259,6 +259,7 @@
mannequin.update_transform() //VOREStation Edit to update size/shape stuff.
mannequin.toggle_tail(setting = TRUE)
mannequin.toggle_wing(setting = TRUE)
+ mannequin.update_tail_showing()
COMPILE_OVERLAYS(mannequin)
update_character_previews(new /mutable_appearance(mannequin))
diff --git a/code/modules/mob/new_player/sprite_accessories_ear_vr.dm b/code/modules/mob/new_player/sprite_accessories_ear_vr.dm
index d9be7399c4..62f0bfeda3 100644
--- a/code/modules/mob/new_player/sprite_accessories_ear_vr.dm
+++ b/code/modules/mob/new_player/sprite_accessories_ear_vr.dm
@@ -119,6 +119,15 @@
do_colouration = 1
color_blend_mode = ICON_MULTIPLY
+/datum/sprite_accessory/ears/antennae_eye
+ name = "antennae eye, colorable"
+ desc = ""
+ icon_state = "antennae"
+ extra_overlay = "antennae_eye_1"
+ extra_overlay2 = "antennae_eye_2"
+ do_colouration = 1
+ color_blend_mode = ICON_MULTIPLY
+
/datum/sprite_accessory/ears/curly_bug
name = "curly antennae, colorable"
desc = ""
diff --git a/code/modules/mob/new_player/sprite_accessories_taur.dm b/code/modules/mob/new_player/sprite_accessories_taur.dm
index 4637ac008e..4288f9bbc6 100644
--- a/code/modules/mob/new_player/sprite_accessories_taur.dm
+++ b/code/modules/mob/new_player/sprite_accessories_taur.dm
@@ -285,6 +285,11 @@
suit_sprites = 'icons/mob/taursuits_slug.dmi'
icon_sprite_tag = "slug"
+/datum/sprite_accessory/tail/taur/slug/snail
+ name = "Snail (Taur)"
+ icon_state = "slug_s"
+ extra_overlay = "snail_shell_marking"
+
/datum/sprite_accessory/tail/taur/frog
name = "Frog (Taur)"
icon_state = "frog_s"
diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm
index 020ded9ea5..bd338109b4 100644
--- a/code/modules/mob/say_vr.dm
+++ b/code/modules/mob/say_vr.dm
@@ -42,7 +42,7 @@
var/input
if(!message)
- input = sanitize_or_reflect(input(src,"Choose an emote to display.") as text|null, src)
+ input = sanitize_or_reflect(tgui_input_text(src,"Choose an emote to display."), src)
else
input = message
@@ -123,7 +123,7 @@
to_chat(src, "You cannot speak in IC (muted). ")
return
if (!message)
- message = input(usr, "Type a message to say.","Psay") as text|null
+ message = tgui_input_text(usr, "Type a message to say.","Psay")
message = sanitize_or_reflect(message,src)
if (!message)
return
@@ -204,7 +204,7 @@
to_chat(src, "You cannot speak in IC (muted). ")
return
if (!message)
- message = input(usr, "Type a message to emote.","Pme") as text|null
+ message = tgui_input_text(usr, "Type a message to emote.","Pme")
message = sanitize_or_reflect(message,src)
if (!message)
return
diff --git a/code/modules/mob/typing_indicator.dm b/code/modules/mob/typing_indicator.dm
index 7ab6de6fe5..bdd5865a9b 100644
--- a/code/modules/mob/typing_indicator.dm
+++ b/code/modules/mob/typing_indicator.dm
@@ -40,11 +40,7 @@
set hidden = 1
set_typing_indicator(TRUE)
- var/message
- if(usr.client.prefs.tgui_input_mode)
- message = tgui_input_text(usr, "Type your message:", "Say")
- else
- message = input(usr, "Type your message:", "Say") as text
+ var/message = tgui_input_text(usr, "Type your message:", "Say")
set_typing_indicator(FALSE)
if(message)
@@ -55,11 +51,7 @@
set hidden = 1
set_typing_indicator(TRUE)
- var/message
- if(usr.client.prefs.tgui_input_mode)
- message = tgui_input_message(usr, "Type your message:", "Emote")
- else
- message = input(usr, "Type your message:", "Emote") as message
+ var/message = tgui_input_text(usr, "Type your message:", "Emote", multiline = TRUE)
set_typing_indicator(FALSE)
if(message)
@@ -70,11 +62,7 @@
set name = ".Whisper"
set hidden = 1
- var/message
- if(usr.client.prefs.tgui_input_mode)
- message = tgui_input_text(usr, "Type your message:", "Whisper")
- else
- message = input(usr, "Type your message:", "Whisper") as text
+ var/message = tgui_input_text(usr, "Type your message:", "Whisper")
if(message)
whisper(message)
@@ -83,11 +71,6 @@
set name = ".Subtle"
set hidden = 1
- var/message
- if(usr.client.prefs.tgui_input_mode)
- message = tgui_input_message(usr, "Type your message:", "Subtle")
- else
- message = input(usr, "Type your message:", "Subtle") as message
-
+ var/message = tgui_input_text(usr, "Type your message:", "Subtle", multiline = TRUE)
if(message)
me_verb_subtle(message)
diff --git a/code/modules/modular_computers/file_system/programs/generic/file_browser.dm b/code/modules/modular_computers/file_system/programs/generic/file_browser.dm
index b3827e507c..e37e994f43 100644
--- a/code/modules/modular_computers/file_system/programs/generic/file_browser.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/file_browser.dm
@@ -28,7 +28,7 @@
if("PRG_newtextfile")
if(!HDD)
return
- var/newname = sanitize(input(usr, "Enter file name or leave blank to cancel:", "File rename"))
+ var/newname = sanitize(tgui_input_text(usr, "Enter file name or leave blank to cancel:", "File rename"))
if(!newname)
return
var/datum/computer_file/data/F = new/datum/computer_file/data()
diff --git a/code/modules/modular_computers/file_system/programs/generic/news_browser.dm b/code/modules/modular_computers/file_system/programs/generic/news_browser.dm
index e72e30fd38..20fdd42f81 100644
--- a/code/modules/modular_computers/file_system/programs/generic/news_browser.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/news_browser.dm
@@ -106,7 +106,7 @@
if(downloading || !loaded_article)
return
- var/savename = sanitize(input(usr, "Enter file name or leave blank to cancel:", "Save article", loaded_article.filename))
+ var/savename = sanitize(tgui_input_text(usr, "Enter file name or leave blank to cancel:", "Save article", loaded_article.filename))
if(!savename)
return TRUE
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
diff --git a/code/modules/modular_computers/file_system/programs/generic/nttransfer.dm b/code/modules/modular_computers/file_system/programs/generic/nttransfer.dm
index ea88d3597e..8471288b09 100644
--- a/code/modules/modular_computers/file_system/programs/generic/nttransfer.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/nttransfer.dm
@@ -133,7 +133,7 @@ var/global/nttransfer_uid = 0
if(!remote || !remote.provided_file)
return
if(remote.server_password)
- var/pass = sanitize(input(usr, "Code 401 Unauthorized. Please enter password:", "Password required"))
+ var/pass = sanitize(tgui_input_text(usr, "Code 401 Unauthorized. Please enter password:", "Password required"))
if(pass != remote.server_password)
error = "Incorrect Password"
return
@@ -151,7 +151,7 @@ var/global/nttransfer_uid = 0
provided_file = null
return TRUE
if("PRG_setpassword")
- var/pass = sanitize(input(usr, "Enter new server password. Leave blank to cancel, input 'none' to disable password.", "Server security", "none"))
+ var/pass = sanitize(tgui_input_text(usr, "Enter new server password. Leave blank to cancel, input 'none' to disable password.", "Server security", "none"))
if(!pass)
return
if(pass == "none")
diff --git a/code/modules/modular_computers/file_system/programs/generic/wordprocessor.dm b/code/modules/modular_computers/file_system/programs/generic/wordprocessor.dm
index b22be72d1e..44c5a821ae 100644
--- a/code/modules/modular_computers/file_system/programs/generic/wordprocessor.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/wordprocessor.dm
@@ -130,7 +130,7 @@
if(tgui_alert(usr, "Would you like to save your changes first?","Save Changes",list("Yes","No")) == "Yes")
save_file(open_file)
- var/newname = sanitize(input(usr, "Enter file name:", "New File") as text|null)
+ var/newname = sanitize(tgui_input_text(usr, "Enter file name:", "New File"))
if(!newname)
return TRUE
var/datum/computer_file/data/F = create_file(newname)
@@ -143,7 +143,7 @@
return TRUE
if("PRG_saveasfile")
- var/newname = sanitize(input(usr, "Enter file name:", "Save As") as text|null)
+ var/newname = sanitize(tgui_input_text(usr, "Enter file name:", "Save As"))
if(!newname)
return TRUE
var/datum/computer_file/data/F = create_file(newname, loaded_data)
@@ -155,7 +155,7 @@
if("PRG_savefile")
if(!open_file)
- open_file = sanitize(input(usr, "Enter file name:", "Save As") as text|null)
+ open_file = sanitize(tgui_input_text(usr, "Enter file name:", "Save As"))
if(!open_file)
return 0
if(!save_file(open_file))
diff --git a/code/modules/modular_computers/file_system/programs/research/ntmonitor.dm b/code/modules/modular_computers/file_system/programs/research/ntmonitor.dm
index d1aa9b7833..8814e2a16b 100644
--- a/code/modules/modular_computers/file_system/programs/research/ntmonitor.dm
+++ b/code/modules/modular_computers/file_system/programs/research/ntmonitor.dm
@@ -80,14 +80,14 @@
if("ban_nid")
if(!ntnet_global)
return
- var/nid = input(usr,"Enter NID of device which you want to block from the network:", "Enter NID") as null|num
+ var/nid = tgui_input_number(usr,"Enter NID of device which you want to block from the network:", "Enter NID")
if(nid && tgui_status(usr, state) == STATUS_INTERACTIVE)
ntnet_global.banned_nids |= nid
return TRUE
if("unban_nid")
if(!ntnet_global)
return
- var/nid = input(usr,"Enter NID of device which you want to unblock from the network:", "Enter NID") as null|num
+ var/nid = tgui_input_number(usr,"Enter NID of device which you want to unblock from the network:", "Enter NID")
if(nid && tgui_status(usr, state) == STATUS_INTERACTIVE)
ntnet_global.banned_nids -= nid
return TRUE
diff --git a/code/modules/modular_computers/file_system/programs/security/digitalwarrant.dm b/code/modules/modular_computers/file_system/programs/security/digitalwarrant.dm
index 752d6d849e..a9ba1d90c0 100644
--- a/code/modules/modular_computers/file_system/programs/security/digitalwarrant.dm
+++ b/code/modules/modular_computers/file_system/programs/security/digitalwarrant.dm
@@ -119,7 +119,7 @@ var/warrant_uid = 0
if("editwarrantnamecustom")
. = TRUE
- var/new_name = sanitize(input(usr, "Please input name") as null|text)
+ var/new_name = sanitize(tgui_input_text(usr, "Please input name"))
if(tgui_status(usr, state) == STATUS_INTERACTIVE)
if (!new_name)
return
@@ -127,7 +127,7 @@ var/warrant_uid = 0
if("editwarrantcharges")
. = TRUE
- var/new_charges = sanitize(input(usr, "Please input charges", "Charges", activewarrant.fields["charges"]) as null|text)
+ var/new_charges = sanitize(tgui_input_text(usr, "Please input charges", "Charges", activewarrant.fields["charges"]))
if(tgui_status(usr, state) == STATUS_INTERACTIVE)
if (!new_charges)
return
diff --git a/code/modules/modular_computers/laptop_vendor.dm b/code/modules/modular_computers/laptop_vendor.dm
index f673e060ef..0242ad2a1f 100644
--- a/code/modules/modular_computers/laptop_vendor.dm
+++ b/code/modules/modular_computers/laptop_vendor.dm
@@ -285,7 +285,7 @@
return 0
if(customer_account.security_level != 0) //If card requires pin authentication (ie seclevel 1 or 2)
- var/attempt_pin = input(usr, "Enter pin code", "Vendor transaction") as num
+ var/attempt_pin = tgui_input_number(usr, "Enter pin code", "Vendor transaction")
customer_account = attempt_account_access(I.associated_account_number, attempt_pin, 2)
if(!customer_account)
diff --git a/code/modules/news/new_newspaper.dm b/code/modules/news/new_newspaper.dm
index c9759a5a83..3962f80cf4 100644
--- a/code/modules/news/new_newspaper.dm
+++ b/code/modules/news/new_newspaper.dm
@@ -134,7 +134,7 @@ obj/item/weapon/newspaper/attackby(obj/item/weapon/W as obj, mob/user)
if(scribble_page == curr_page)
to_chat(user, "There's already a scribble in this page... You wouldn't want to make things too cluttered, would you? ")
else
- var/s = sanitize(input(user, "Write something", "Newspaper", ""))
+ var/s = sanitize(tgui_input_text(user, "Write something", "Newspaper", ""))
s = sanitize(s)
if(!s)
return
diff --git a/code/modules/news/newspaper.dm b/code/modules/news/newspaper.dm
index 86a1149169..9eae4b19ee 100644
--- a/code/modules/news/newspaper.dm
+++ b/code/modules/news/newspaper.dm
@@ -135,7 +135,7 @@
if(scribble_page == curr_page)
to_chat(user, "There's already a scribble in this page... You wouldn't want to make things too cluttered, would you? ")
else
- var/s = sanitize(input(user, "Write something", "Newspaper", ""))
+ var/s = sanitize(tgui_input_text(user, "Write something", "Newspaper", ""))
s = sanitize(s)
if(!s)
return
diff --git a/code/modules/nifsoft/nifsoft.dm b/code/modules/nifsoft/nifsoft.dm
index ad1af357f1..64e6772a36 100644
--- a/code/modules/nifsoft/nifsoft.dm
+++ b/code/modules/nifsoft/nifsoft.dm
@@ -263,7 +263,7 @@
..(A,user,flag,params)
/obj/item/weapon/disk/nifsoft/compliance/attack_self(mob/user)
- var/newlaws = input(user,"Please Input Laws","Compliance Laws",laws) as message
+ var/newlaws = tgui_input_text(user, "Please Input Laws", "Compliance Laws", laws, multiline = TRUE)
newlaws = sanitize(newlaws,2048)
if(newlaws)
to_chat(user,"You set the laws to: [newlaws] ")
diff --git a/code/modules/nifsoft/software/13_soulcatcher.dm b/code/modules/nifsoft/software/13_soulcatcher.dm
index 750f1b7df2..a2da8ecdea 100644
--- a/code/modules/nifsoft/software/13_soulcatcher.dm
+++ b/code/modules/nifsoft/software/13_soulcatcher.dm
@@ -494,7 +494,7 @@
to_chat(src,SPAN_WARNING("You need a loaded mind to use NSay."))
return
if(!message)
- message = input(usr, "Type a message to say.","Speak into Soulcatcher") as text|null
+ message = tgui_input_text(usr, "Type a message to say.","Speak into Soulcatcher")
if(message)
var/sane_message = sanitize(message)
SC.say_into(sane_message,src)
@@ -525,7 +525,7 @@
return
if(!message)
- message = input(usr, "Type an action to perform.","Emote into Soulcatcher") as text|null
+ message = tgui_input_text(usr, "Type an action to perform.","Emote into Soulcatcher")
if(message)
var/sane_message = sanitize(message)
SC.emote_into(sane_message,src)
@@ -580,7 +580,7 @@
set category = "Soulcatcher"
if(!message)
- message = input(usr, "Type a message to say.","Speak into Soulcatcher") as text|null
+ message = tgui_input_text(usr, "Type a message to say.","Speak into Soulcatcher")
if(message)
var/sane_message = sanitize(message)
soulcatcher.say_into(sane_message,src,null)
@@ -591,7 +591,7 @@
set category = "Soulcatcher"
if(!message)
- message = input(usr, "Type an action to perform.","Emote into Soulcatcher") as text|null
+ message = tgui_input_text(usr, "Type an action to perform.","Emote into Soulcatcher")
if(message)
var/sane_message = sanitize(message)
soulcatcher.emote_into(sane_message,src,null)
diff --git a/code/modules/nifsoft/software/15_misc.dm b/code/modules/nifsoft/software/15_misc.dm
index 46db84c235..8e7101395f 100644
--- a/code/modules/nifsoft/software/15_misc.dm
+++ b/code/modules/nifsoft/software/15_misc.dm
@@ -127,7 +127,7 @@
/datum/nifsoft/sizechange/activate()
if((. = ..()))
- var/new_size = input(usr, "Put the desired size (25-200%), or (1-600%) in dormitory areas.", "Set Size", 200) as num|null
+ var/new_size = tgui_input_number(usr, "Put the desired size (25-200%), or (1-600%) in dormitory areas.", "Set Size", 200, 600, 1)
if (!nif.human.size_range_check(new_size))
if(new_size)
diff --git a/code/modules/overmap/champagne.dm b/code/modules/overmap/champagne.dm
index 477f9cc072..0d36766535 100644
--- a/code/modules/overmap/champagne.dm
+++ b/code/modules/overmap/champagne.dm
@@ -27,7 +27,7 @@
return
user.visible_message("[user] lifts [src] bottle over [comp]! ")
- var/shuttle_name = input(usr, "Choose a name for the shuttle", "New Shuttle Name") as null|text
+ var/shuttle_name = tgui_input_text(usr, "Choose a name for the shuttle", "New Shuttle Name")
if(!shuttle_name || QDELETED(src) || QDELETED(comp) || comp.shuttle_tag || user.incapacitated())
return // After input() safety re-checks
diff --git a/code/modules/overmap/disperser/disperser_console.dm b/code/modules/overmap/disperser/disperser_console.dm
index 789d768e6b..d67bb87c5c 100644
--- a/code/modules/overmap/disperser/disperser_console.dm
+++ b/code/modules/overmap/disperser/disperser_console.dm
@@ -177,7 +177,7 @@
. = TRUE
if("calibration")
- var/input = input(usr, "0-9", "disperser calibration", 0) as num|null
+ var/input = tgui_input_number(usr, "0-9", "disperser calibration", 0, 9, 0)
if(!isnull(input)) //can be zero so we explicitly check for null
var/calnum = sanitize_integer(text2num(params["calibration"]), 0, caldigit)//sanitiiiiize
calibration[calnum + 1] = sanitize_integer(input, 0, 9, 0)//must add 1 because js indexes from 0
@@ -189,14 +189,14 @@
. = TRUE
if("strength")
- var/input = input(usr, "1-5", "disperser strength", 1) as num|null
+ var/input = tgui_input_number(usr, "1-5", "disperser strength", 1, 5, 1)
if(input && tgui_status(usr, state) == STATUS_INTERACTIVE)
strength = sanitize_integer(input, 1, 5, 1)
middle.update_idle_power_usage(strength * range * 100)
. = TRUE
if("range")
- var/input = input(usr, "1-5", "disperser radius", 1) as num|null
+ var/input = tgui_input_number(usr, "1-5", "disperser radius", 1, 5, 1)
if(input && tgui_status(usr, state) == STATUS_INTERACTIVE)
range = sanitize_integer(input, 1, 5, 1)
middle.update_idle_power_usage(strength * range * 100)
diff --git a/code/modules/overmap/ships/computers/engine_control.dm b/code/modules/overmap/ships/computers/engine_control.dm
index 1110f6b246..ab1bb61b74 100644
--- a/code/modules/overmap/ships/computers/engine_control.dm
+++ b/code/modules/overmap/ships/computers/engine_control.dm
@@ -62,7 +62,7 @@
. = TRUE
if("set_global_limit")
- var/newlim = input(usr, "Input new thrust limit (0..100%)", "Thrust limit", linked.thrust_limit*100) as num
+ var/newlim = tgui_input_number(usr, "Input new thrust limit (0..100%)", "Thrust limit", linked.thrust_limit*100, 100, 0)
if(tgui_status(usr, state) != STATUS_INTERACTIVE)
return FALSE
linked.thrust_limit = clamp(newlim/100, 0, 1)
@@ -78,7 +78,7 @@
if("set_limit")
var/datum/ship_engine/E = locate(params["engine"])
- var/newlim = input(usr, "Input new thrust limit (0..100)", "Thrust limit", E.get_thrust_limit()) as num
+ var/newlim = tgui_input_number(usr, "Input new thrust limit (0..100)", "Thrust limit", E.get_thrust_limit(), 100, 0)
if(tgui_status(usr, state) != STATUS_INTERACTIVE)
return FALSE
var/limit = clamp(newlim/100, 0, 1)
diff --git a/code/modules/overmap/ships/computers/helm.dm b/code/modules/overmap/ships/computers/helm.dm
index 2c821ef793..64212fd360 100644
--- a/code/modules/overmap/ships/computers/helm.dm
+++ b/code/modules/overmap/ships/computers/helm.dm
@@ -157,7 +157,7 @@ GLOBAL_LIST_EMPTY(all_waypoints)
switch(action)
if("add")
var/datum/computer_file/data/waypoint/R = new()
- var/sec_name = input(usr, "Input navigation entry name", "New navigation entry", "Sector #[known_sectors.len]") as text
+ var/sec_name = tgui_input_text(usr, "Input navigation entry name", "New navigation entry", "Sector #[known_sectors.len]")
if(tgui_status(usr, state) != STATUS_INTERACTIVE)
return FALSE
if(!sec_name)
@@ -171,10 +171,10 @@ GLOBAL_LIST_EMPTY(all_waypoints)
R.fields["x"] = linked.x
R.fields["y"] = linked.y
if("new")
- var/newx = input(usr, "Input new entry x coordinate", "Coordinate input", linked.x) as num
+ var/newx = tgui_input_number(usr, "Input new entry x coordinate", "Coordinate input", linked.x)
if(tgui_status(usr, state) != STATUS_INTERACTIVE)
return TRUE
- var/newy = input(usr, "Input new entry y coordinate", "Coordinate input", linked.y) as num
+ var/newy = tgui_input_number(usr, "Input new entry y coordinate", "Coordinate input", linked.y)
if(tgui_status(usr, state) != STATUS_INTERACTIVE)
return FALSE
R.fields["x"] = CLAMP(newx, 1, world.maxx)
@@ -191,14 +191,14 @@ GLOBAL_LIST_EMPTY(all_waypoints)
if("setcoord")
if(params["setx"])
- var/newx = input(usr, "Input new destiniation x coordinate", "Coordinate input", dx) as num|null
+ var/newx = tgui_input_number(usr, "Input new destiniation x coordinate", "Coordinate input", dx)
if(tgui_status(usr, state) != STATUS_INTERACTIVE)
return
if(newx)
dx = CLAMP(newx, 1, world.maxx)
if(params["sety"])
- var/newy = input(usr, "Input new destiniation y coordinate", "Coordinate input", dy) as num|null
+ var/newy = tgui_input_number(usr, "Input new destiniation y coordinate", "Coordinate input", dy)
if(tgui_status(usr, state) != STATUS_INTERACTIVE)
return
if(newy)
@@ -216,13 +216,13 @@ GLOBAL_LIST_EMPTY(all_waypoints)
. = TRUE
if("speedlimit")
- var/newlimit = input(usr, "Input new speed limit for autopilot (0 to brake)", "Autopilot speed limit", speedlimit*1000) as num|null
+ var/newlimit = tgui_input_number(usr, "Input new speed limit for autopilot (0 to brake)", "Autopilot speed limit", speedlimit*1000)
if(newlimit)
speedlimit = CLAMP(newlimit/1000, 0, 100)
. = TRUE
if("accellimit")
- var/newlimit = input(usr, "Input new acceleration limit", "Acceleration limit", accellimit*1000) as num|null
+ var/newlimit = tgui_input_number(usr, "Input new acceleration limit", "Acceleration limit", accellimit*1000)
if(newlimit)
accellimit = max(newlimit/1000, 0)
. = TRUE
diff --git a/code/modules/overmap/ships/computers/sensors.dm b/code/modules/overmap/ships/computers/sensors.dm
index e09678f9b0..21cd9654ee 100644
--- a/code/modules/overmap/ships/computers/sensors.dm
+++ b/code/modules/overmap/ships/computers/sensors.dm
@@ -106,7 +106,7 @@
if(sensors)
switch(action)
if("range")
- var/nrange = input(usr, "Set new sensors range", "Sensor range", sensors.range) as num|null
+ var/nrange = tgui_input_number(usr, "Set new sensors range", "Sensor range", sensors.range)
if(tgui_status(usr, state) != STATUS_INTERACTIVE)
return FALSE
if(nrange)
diff --git a/code/modules/paperwork/adminpaper.dm b/code/modules/paperwork/adminpaper.dm
index 1a6077c30a..91d3fe241c 100644
--- a/code/modules/paperwork/adminpaper.dm
+++ b/code/modules/paperwork/adminpaper.dm
@@ -87,7 +87,7 @@
to_chat(usr, "There isn't enough space left on \the [src] to write anything. ")
return
- var/raw_t = tgui_input_message(usr, "Enter what you want to write:", "Write")
+ var/raw_t = tgui_input_text(usr, "Enter what you want to write:", "Write", multiline = TRUE)
if(!raw_t)
return
var/t = sanitize(raw_t, free_space, extra = 0)
@@ -158,4 +158,4 @@
return
/obj/item/weapon/paper/admin/get_signature()
- return input(usr, "Enter the name you wish to sign the paper with (will prompt for multiple entries, in order of entry)", "Signature") as text|null
+ return tgui_input_text(usr, "Enter the name you wish to sign the paper with (will prompt for multiple entries, in order of entry)", "Signature")
diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm
index 51f46e4341..93c6f4a059 100644
--- a/code/modules/paperwork/faxmachine.dm
+++ b/code/modules/paperwork/faxmachine.dm
@@ -137,7 +137,7 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins
/obj/machinery/photocopier/faxmachine/attackby(obj/item/O as obj, mob/user as mob)
if(O.is_multitool() && panel_open)
- var/input = sanitize(input(usr, "What Department ID would you like to give this fax machine?", "Multitool-Fax Machine Interface", department))
+ var/input = sanitize(tgui_input_text(usr, "What Department ID would you like to give this fax machine?", "Multitool-Fax Machine Interface", department))
if(!input)
to_chat(usr, "No input found. Please hang up and try your call again.")
return
diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm
index 85da7c2106..5409fbb2c1 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -281,7 +281,7 @@
if(new_signature)
signature = new_signature
*/
- signature = sanitize(input(usr, "Enter new signature. Leave blank for 'Anonymous'", "New Signature", signature))
+ signature = sanitize(tgui_input_text(usr, "Enter new signature. Leave blank for 'Anonymous'", "New Signature", signature))
/obj/item/weapon/pen/proc/get_signature(var/mob/user)
return (user && user.real_name) ? user.real_name : "Anonymous"
diff --git a/code/modules/pda/core_apps.dm b/code/modules/pda/core_apps.dm
index 141f41a8fb..f15c07bfff 100644
--- a/code/modules/pda/core_apps.dm
+++ b/code/modules/pda/core_apps.dm
@@ -62,7 +62,7 @@
return TRUE
switch(action)
if("Edit")
- var/n = input(usr, "Please enter message", name, notehtml) as message
+ var/n = tgui_input_text(usr, "Please enter message", name, notehtml, multiline = TRUE)
if(pda.loc == usr)
note = adminscrub(n)
notehtml = html_decode(note)
diff --git a/code/modules/pda/messenger.dm b/code/modules/pda/messenger.dm
index 03996dc35c..6aa833fd83 100644
--- a/code/modules/pda/messenger.dm
+++ b/code/modules/pda/messenger.dm
@@ -120,7 +120,7 @@
/datum/data/pda/app/messenger/proc/create_message(var/mob/living/U, var/obj/item/device/pda/P)
- var/t = input(U, "Please enter message", name, null) as text|null
+ var/t = tgui_input_text(U, "Please enter message", name, null)
if(!t)
return
t = sanitize(copytext(t, 1, MAX_MESSAGE_LEN))
diff --git a/code/modules/pda/pda.dm b/code/modules/pda/pda.dm
index a19552d368..c61624e40b 100644
--- a/code/modules/pda/pda.dm
+++ b/code/modules/pda/pda.dm
@@ -102,7 +102,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
O.show_message(text("[bicon(src)] *[ttone]*"))
/obj/item/device/pda/proc/set_ringtone()
- var/t = input(usr, "Please enter new ringtone", name, ttone) as text
+ var/t = tgui_input_text(usr, "Please enter new ringtone", name, ttone)
if(in_range(src, usr) && loc == usr)
if(t)
if(hidden_uplink && hidden_uplink.check_trigger(usr, lowertext(t), lowertext(lock_code)))
diff --git a/code/modules/power/breaker_box.dm b/code/modules/power/breaker_box.dm
index 83d81f99a7..f8915e08af 100644
--- a/code/modules/power/breaker_box.dm
+++ b/code/modules/power/breaker_box.dm
@@ -93,7 +93,7 @@
/obj/machinery/power/breakerbox/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob)
if(istype(W, /obj/item/device/multitool))
- var/newtag = input(user, "Enter new RCON tag. Use \"NO_TAG\" to disable RCON or leave empty to cancel.", "SMES RCON system") as text
+ var/newtag = tgui_input_text(user, "Enter new RCON tag. Use \"NO_TAG\" to disable RCON or leave empty to cancel.", "SMES RCON system")
if(newtag)
RCon_tag = newtag
to_chat(user, "You changed the RCON tag to: [newtag] ")
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index a4efe1336c..c850085bbd 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -978,7 +978,7 @@ var/list/possible_cable_coil_colours = list(
/obj/item/stack/cable_coil/alien/attack_hand(mob/user as mob)
if (user.get_inactive_hand() == src)
- var/N = input(usr, "How many units of wire do you want to take from [src]? You can only take up to [amount] at a time.", "Split stacks", 1) as num|null
+ var/N = tgui_input_number(usr, "How many units of wire do you want to take from [src]? You can only take up to [amount] at a time.", "Split stacks", 1)
if(N && N <= amount)
var/obj/item/stack/cable_coil/CC = new/obj/item/stack/cable_coil(user.loc)
CC.amount = N
diff --git a/code/modules/power/fusion/core/_core.dm b/code/modules/power/fusion/core/_core.dm
index 0fb5626a57..308c930783 100644
--- a/code/modules/power/fusion/core/_core.dm
+++ b/code/modules/power/fusion/core/_core.dm
@@ -149,7 +149,7 @@ GLOBAL_LIST_EMPTY(fusion_cores)
return
if(istype(W, /obj/item/device/multitool))
- var/new_ident = input(usr, "Enter a new ident tag.", "Fusion Core", id_tag) as null|text
+ var/new_ident = tgui_input_text(usr, "Enter a new ident tag.", "Fusion Core", id_tag)
if(new_ident && user.Adjacent(src))
id_tag = new_ident
return
diff --git a/code/modules/power/fusion/core/core_control.dm b/code/modules/power/fusion/core/core_control.dm
index 7f909d4896..ae3af61c9a 100644
--- a/code/modules/power/fusion/core/core_control.dm
+++ b/code/modules/power/fusion/core/core_control.dm
@@ -24,7 +24,7 @@
/obj/machinery/computer/fusion_core_control/attackby(var/obj/item/thing, var/mob/user)
..()
if(istype(thing, /obj/item/device/multitool))
- var/new_ident = sanitize_text(input(usr, "Enter a new ident tag.", "Core Control", monitor.core_tag) as null|text)
+ var/new_ident = sanitize_text(tgui_input_text(usr, "Enter a new ident tag.", "Core Control", monitor.core_tag))
if(new_ident && user.Adjacent(src))
monitor.core_tag = new_ident
// id_tag = new_ident
diff --git a/code/modules/power/fusion/fuel_assembly/fuel_control.dm b/code/modules/power/fusion/fuel_assembly/fuel_control.dm
index 6fd14755af..24394b7973 100644
--- a/code/modules/power/fusion/fuel_assembly/fuel_control.dm
+++ b/code/modules/power/fusion/fuel_assembly/fuel_control.dm
@@ -117,7 +117,7 @@
/obj/machinery/computer/fusion_fuel_control/attackby(var/obj/item/W, var/mob/user)
..()
if(istype(W, /obj/item/device/multitool))
- var/new_ident = input(usr, "Enter a new ident tag.", "Fuel Control", monitor.fuel_tag) as null|text
+ var/new_ident = tgui_input_text(usr, "Enter a new ident tag.", "Fuel Control", monitor.fuel_tag)
if(new_ident && user.Adjacent(src))
monitor.fuel_tag = new_ident
return
diff --git a/code/modules/power/fusion/fuel_assembly/fuel_injector.dm b/code/modules/power/fusion/fuel_assembly/fuel_injector.dm
index c928399c29..f37b8fcd21 100644
--- a/code/modules/power/fusion/fuel_assembly/fuel_injector.dm
+++ b/code/modules/power/fusion/fuel_assembly/fuel_injector.dm
@@ -43,7 +43,7 @@ GLOBAL_LIST_EMPTY(fuel_injectors)
/obj/machinery/fusion_fuel_injector/attackby(obj/item/W, mob/user)
if(istype(W, /obj/item/device/multitool))
- var/new_ident = input(usr, "Enter a new ident tag.", "Fuel Injector", id_tag) as null|text
+ var/new_ident = tgui_input_text(usr, "Enter a new ident tag.", "Fuel Injector", id_tag)
if(new_ident && user.Adjacent(src))
id_tag = new_ident
return
diff --git a/code/modules/power/fusion/gyrotron/gyrotron.dm b/code/modules/power/fusion/gyrotron/gyrotron.dm
index 8f7e1f9f52..b2b33ef96b 100644
--- a/code/modules/power/fusion/gyrotron/gyrotron.dm
+++ b/code/modules/power/fusion/gyrotron/gyrotron.dm
@@ -53,7 +53,7 @@ GLOBAL_LIST_EMPTY(gyrotrons)
/obj/machinery/power/emitter/gyrotron/attackby(var/obj/item/W, var/mob/user)
if(istype(W, /obj/item/device/multitool))
- var/new_ident = input(usr, "Enter a new ident tag.", "Gyrotron", id_tag) as null|text
+ var/new_ident = tgui_input_text(usr, "Enter a new ident tag.", "Gyrotron", id_tag)
if(new_ident && user.Adjacent(src))
id_tag = new_ident
return
diff --git a/code/modules/power/fusion/gyrotron/gyrotron_control.dm b/code/modules/power/fusion/gyrotron/gyrotron_control.dm
index ef4dd1641c..9ff359fd29 100644
--- a/code/modules/power/fusion/gyrotron/gyrotron_control.dm
+++ b/code/modules/power/fusion/gyrotron/gyrotron_control.dm
@@ -119,7 +119,7 @@
/obj/machinery/computer/gyrotron_control/attackby(var/obj/item/W, var/mob/user)
..()
if(istype(W, /obj/item/device/multitool))
- var/new_ident = input(usr, "Enter a new ident tag.", "Gyrotron Control", monitor.gyro_tag) as null|text
+ var/new_ident = tgui_input_text(usr, "Enter a new ident tag.", "Gyrotron Control", monitor.gyro_tag)
if(new_ident && user.Adjacent(src))
monitor.gyro_tag = new_ident
return
diff --git a/code/modules/power/smes_construction.dm b/code/modules/power/smes_construction.dm
index 4f1b4e5dcd..381f6b09f6 100644
--- a/code/modules/power/smes_construction.dm
+++ b/code/modules/power/smes_construction.dm
@@ -312,7 +312,7 @@
// Multitool - change RCON tag
if(istype(W, /obj/item/device/multitool))
- var/newtag = input(user, "Enter new RCON tag. Use \"NO_TAG\" to disable RCON or leave empty to cancel.", "SMES RCON system") as text
+ var/newtag = tgui_input_text(user, "Enter new RCON tag. Use \"NO_TAG\" to disable RCON or leave empty to cancel.", "SMES RCON system")
if(newtag)
RCon_tag = newtag
to_chat(user, "You changed the RCON tag to: [newtag] ")
diff --git a/code/modules/power/turbine.dm b/code/modules/power/turbine.dm
index 297a749aa7..77a5659cdd 100644
--- a/code/modules/power/turbine.dm
+++ b/code/modules/power/turbine.dm
@@ -124,7 +124,7 @@
if(default_deconstruction_crowbar(user, W))
return
if(istype(W, /obj/item/device/multitool))
- var/new_ident = input(usr, "Enter a new ident tag.", name, comp_id) as null|text
+ var/new_ident = tgui_input_text(usr, "Enter a new ident tag.", name, comp_id)
if(new_ident && user.Adjacent(src))
comp_id = new_ident
return
@@ -337,7 +337,7 @@
/obj/machinery/computer/turbine_computer/attackby(obj/item/W, mob/user)
if(istype(W, /obj/item/device/multitool))
- var/new_ident = input(usr, "Enter a new ident tag.", name, id) as null|text
+ var/new_ident = tgui_input_text(usr, "Enter a new ident tag.", name, id)
if(new_ident && user.Adjacent(src))
id = new_ident
return
diff --git a/code/modules/projectiles/guns/energy/stun_vr.dm b/code/modules/projectiles/guns/energy/stun_vr.dm
index b0bfd53e46..3ddc43986c 100644
--- a/code/modules/projectiles/guns/energy/stun_vr.dm
+++ b/code/modules/projectiles/guns/energy/stun_vr.dm
@@ -3,4 +3,7 @@
fire_delay = 4
/obj/item/weapon/gun/energy/stunrevolver
- charge_cost = 400
\ No newline at end of file
+ charge_cost = 400
+
+/obj/item/weapon/gun/energy/taser/mounted/cyborg
+ charge_cost = 160
\ No newline at end of file
diff --git a/code/modules/random_map/drop/droppod.dm b/code/modules/random_map/drop/droppod.dm
index 2b1cda09a8..e842772f4a 100644
--- a/code/modules/random_map/drop/droppod.dm
+++ b/code/modules/random_map/drop/droppod.dm
@@ -164,7 +164,7 @@
return
if(tgui_alert(usr, "Do you wish the mob to have a player?","Assign Player?",list("No","Yes")) == "No")
- var/spawn_count = input(usr, "How many mobs do you wish the pod to contain?", "Drop Pod Selection", null) as num
+ var/spawn_count = tgui_input_number(usr, "How many mobs do you wish the pod to contain?", "Drop Pod Selection", null)
if(spawn_count <= 0)
return
for(var/i=0;iInvalid text.")
return
@@ -169,7 +169,7 @@
nameset = 1
if("Description")
- var/str = sanitize(input(usr,"Label text?","Set label",""))
+ var/str = sanitize(tgui_input_text(usr,"Label text?","Set label",""))
if(!str || !length(str))
to_chat(user, "Invalid text. ")
return
diff --git a/code/modules/resleeving/designer.dm b/code/modules/resleeving/designer.dm
index b6747144e7..8dd854fa66 100644
--- a/code/modules/resleeving/designer.dm
+++ b/code/modules/resleeving/designer.dm
@@ -369,7 +369,7 @@
return
if(params["target_href"] == "size_multiplier")
- var/new_size = input(user, "Choose your character's size, ranging from 25% to 200%", "Character Preference") as num|null
+ var/new_size = tgui_input_number(user, "Choose your character's size, ranging from 25% to 200%", "Character Preference", null, 200, 25)
if(new_size && ISINRANGE(new_size,25,200))
active_br.sizemult = (new_size/100)
update_preview_icon()
diff --git a/code/modules/shieldgen/shield_generator.dm b/code/modules/shieldgen/shield_generator.dm
index 48d712bf53..2158a53d02 100644
--- a/code/modules/shieldgen/shield_generator.dm
+++ b/code/modules/shieldgen/shield_generator.dm
@@ -505,14 +505,14 @@
switch(action)
if("set_range")
- var/new_range = input(usr, "Enter new field range (1-[world.maxx]). Leave blank to cancel.", "Field Radius Control", field_radius) as num
+ var/new_range = tgui_input_number(usr, "Enter new field range (1-[world.maxx]). Leave blank to cancel.", "Field Radius Control", field_radius, world.maxx, 1)
if(!new_range)
return TRUE
target_radius = between(1, new_range, world.maxx)
return TRUE
if("set_input_cap")
- var/new_cap = round(input(usr, "Enter new input cap (in kW). Enter 0 or nothing to disable input cap.", "Generator Power Control", round(input_cap / 1000)) as num)
+ var/new_cap = round(tgui_input_number(usr, "Enter new input cap (in kW). Enter 0 or nothing to disable input cap.", "Generator Power Control", round(input_cap / 1000)))
if(!new_cap)
input_cap = 0
return
diff --git a/code/modules/shuttles/shuttle_console.dm b/code/modules/shuttles/shuttle_console.dm
index 75ccefa492..b6d3eddfd8 100644
--- a/code/modules/shuttles/shuttle_console.dm
+++ b/code/modules/shuttles/shuttle_console.dm
@@ -111,7 +111,7 @@
return TRUE
if("set_codes")
- var/newcode = input(usr, "Input new docking codes", "Docking codes", shuttle.docking_codes) as text|null
+ var/newcode = tgui_input_text(usr, "Input new docking codes", "Docking codes", shuttle.docking_codes)
if(newcode && !..())
shuttle.set_docking_codes(uppertext(newcode))
return TRUE
diff --git a/code/modules/shuttles/shuttles_web.dm b/code/modules/shuttles/shuttles_web.dm
index 935b47ca72..1051ba6c2d 100644
--- a/code/modules/shuttles/shuttles_web.dm
+++ b/code/modules/shuttles/shuttles_web.dm
@@ -149,7 +149,7 @@
if(!can_rename)
to_chat(user, "You can't rename this vessel. ")
return
- var/new_name = input(user, "Please enter a new name for this vessel. Note that you can only set its name once, so choose wisely.", "Rename Shuttle", visible_name) as null|text
+ var/new_name = tgui_input_text(user, "Please enter a new name for this vessel. Note that you can only set its name once, so choose wisely.", "Rename Shuttle", visible_name)
var/sanitized_name = sanitizeName(new_name, MAX_NAME_LEN, TRUE)
if(sanitized_name)
//can_rename = FALSE //VOREStation Removal
diff --git a/code/modules/stockmarket/computer.dm b/code/modules/stockmarket/computer.dm
index ffa09437c5..4252d86cfe 100644
--- a/code/modules/stockmarket/computer.dm
+++ b/code/modules/stockmarket/computer.dm
@@ -274,7 +274,7 @@
to_chat(user, "This account does not own any shares of [S.name]! ")
return
var/price = S.current_value
- var/amt = round(input(user, "How many shares? \n(Have: [avail], unit price: [price])", "Sell shares in [S.name]", 0) as num|null)
+ var/amt = round(tgui_input_number(user, "How many shares? \n(Have: [avail], unit price: [price])", "Sell shares in [S.name]", 0))
amt = min(amt, S.shareholders[logged_in])
if (!user || (!(user in range(1, src)) && iscarbon(user)))
@@ -309,7 +309,7 @@
var/avail = S.available_shares
var/price = S.current_value
var/canbuy = round(b / price)
- var/amt = round(input(user, "How many shares? \n(Available: [avail], unit price: [price], can buy: [canbuy])", "Buy shares in [S.name]", 0) as num|null)
+ var/amt = round(tgui_input_number(user, "How many shares? \n(Available: [avail], unit price: [price], can buy: [canbuy])", "Buy shares in [S.name]", 0))
if (!user || (!(user in range(1, src)) && iscarbon(user)))
return
if (li != logged_in)
diff --git a/code/modules/surgery/robotics.dm b/code/modules/surgery/robotics.dm
index 9c210652c2..b424852ad9 100644
--- a/code/modules/surgery/robotics.dm
+++ b/code/modules/surgery/robotics.dm
@@ -470,7 +470,7 @@
var/new_name = target.real_name
while(target.client)
if(!target) return
- var/try_name = input(target,"Pick a name for your new form!", "New Name", target.name)
+ var/try_name = tgui_input_text(target,"Pick a name for your new form!", "New Name", target.name)
var/clean_name = sanitizeName(try_name, allow_numbers = TRUE)
if(clean_name)
var/okay = tgui_alert(target,"New name will be '[clean_name]', ok?", "Confirmation",list("Cancel","Ok"))
@@ -562,7 +562,7 @@
var/new_name = ""
while(!new_name)
if(!target) return
- var/try_name = input(target,"Pick a name for your new form!", "New Name", target.name)
+ var/try_name = tgui_input_text(target,"Pick a name for your new form!", "New Name", target.name)
var/clean_name = sanitizeName(try_name, allow_numbers = TRUE)
if(clean_name)
var/okay = tgui_alert(target,"New name will be '[clean_name]', ok?", "Confirmation",list("Cancel","Ok"))
diff --git a/code/modules/telesci/gps_advanced.dm b/code/modules/telesci/gps_advanced.dm
index bc0d322318..4b67718570 100644
--- a/code/modules/telesci/gps_advanced.dm
+++ b/code/modules/telesci/gps_advanced.dm
@@ -59,7 +59,7 @@
/obj/item/device/gps/advanced/Topic(href, href_list)
..()
if(href_list["tag"] )
- var/a = input(usr, "Please enter desired tag.", name, gpstag) as text
+ var/a = tgui_input_text(usr, "Please enter desired tag.", name, gpstag)
a = uppertext(copytext(sanitize(a), 1, 5))
if(src.loc == usr)
gpstag = a
diff --git a/code/modules/tgui/modules/admin/player_notes.dm b/code/modules/tgui/modules/admin/player_notes.dm
new file mode 100644
index 0000000000..ce17eb043d
--- /dev/null
+++ b/code/modules/tgui/modules/admin/player_notes.dm
@@ -0,0 +1,326 @@
+#define PLAYER_NOTES_ENTRIES_PER_PAGE 50
+
+/datum/tgui_module/player_notes
+ name = "Player Notes"
+ tgui_id = "PlayerNotes"
+
+ var/ckeys = list()
+
+ var/current_filter = ""
+ var/current_page = 1
+
+ var/number_pages = 0
+
+/datum/tgui_module/player_notes/proc/filter_ckeys(var/page, var/filter)
+ var/savefile/S=new("data/player_notes.sav")
+ var/list/note_keys
+ S >> note_keys
+ if(!note_keys)
+ to_chat(usr, "No notes found.")
+ else
+ note_keys = sortList(note_keys)
+
+ if(filter)
+ var/list/results = list()
+ var/regex/needle = regex(filter, "i")
+ for(var/haystack in note_keys)
+ if(needle.Find(haystack))
+ results += haystack
+ note_keys = results
+
+ // Display the notes on the current page
+ number_pages = note_keys.len / PLAYER_NOTES_ENTRIES_PER_PAGE
+ // Emulate CEILING(why does BYOND not have ceil, 1)
+ if(number_pages != round(number_pages))
+ number_pages = round(number_pages) + 1
+ var/page_index = page - 1
+
+ if(page_index < 0 || page_index >= number_pages)
+ to_chat(usr, "No keys found.")
+ else
+ var/lower_bound = page_index * PLAYER_NOTES_ENTRIES_PER_PAGE + 1
+ var/upper_bound = (page_index + 1) * PLAYER_NOTES_ENTRIES_PER_PAGE
+ upper_bound = min(upper_bound, note_keys.len)
+ ckeys = list()
+ for(var/index = lower_bound, index <= upper_bound, index++)
+ ckeys += note_keys[index]
+
+ current_filter = filter
+
+/datum/tgui_module/player_notes/proc/open_legacy()
+ var/datum/admins/A = admin_datums[usr.ckey]
+ A.PlayerNotesLegacy()
+
+/datum/tgui_module/player_notes/tgui_state(mob/user)
+ return GLOB.tgui_admin_state
+
+/datum/tgui_module/player_notes/tgui_act(action, params, datum/tgui/ui)
+ if(..())
+ return TRUE
+
+ switch(action)
+ if("__fallback")
+ log_runtime(EXCEPTION("TGUI Fallback Triggered: \"[ui.user]\" tried to use/open \"[ui.title]/[ui.interface]\"! Trying to open legacy UI!"))
+ open_legacy()
+
+ if("show_player_info")
+ var/datum/tgui_module/player_notes_info/A = new(src)
+ A.key = params["name"]
+ A.tgui_interact(usr)
+
+ if("filter_player_notes")
+ var/input = tgui_input_text(usr, "Filter string (case-insensitive regex)", "Player notes filter")
+ current_filter = input
+
+ if("set_page")
+ var/page = params["index"]
+ current_page = page
+
+ if("clear_player_info_filter")
+ current_filter = ""
+
+ if("open_legacy_ui")
+ open_legacy()
+
+/datum/tgui_module/player_notes/tgui_data(mob/user)
+ var/list/data = list()
+
+ filter_ckeys(current_page, current_filter)
+ data["ckeys"] = list()
+ data["pages"] = number_pages + 1
+ data["filter"] = current_filter
+
+ for(var/ckey in ckeys)
+ data["ckeys"] += list(list(
+ "name" = ckey
+ ))
+
+ return data
+
+// PLAYER NOTES INFO
+/datum/tgui_module/player_notes_info
+ name = "Player Notes Info"
+ tgui_id = "PlayerNotesInfo"
+
+ var/key = null
+
+/datum/tgui_module/player_notes_info/tgui_state(mob/user)
+ return GLOB.tgui_admin_state
+
+/datum/tgui_module/player_notes_info/tgui_act(action, params, datum/tgui/ui)
+ if(..())
+ return TRUE
+
+ switch(action)
+ if("__fallback")
+ var/datum/admins/A = admin_datums[usr.ckey]
+ A.show_player_info_legacy(key)
+
+ if("add_player_info")
+ var/key = params["ckey"]
+ var/add = tgui_input_text(usr, "Write your comment below.", "Add Player Info", multiline = TRUE)
+ if(!add) return
+
+ notes_add(key,add,usr)
+
+ if("remove_player_info")
+ var/key = params["ckey"]
+ var/index = params["index"]
+
+ notes_del(key, index)
+
+/datum/tgui_module/player_notes_info/tgui_data(mob/user)
+ var/list/data = list()
+
+ if(!key)
+ return
+
+ var/p_age = "unknown"
+ for(var/client/C in GLOB.clients)
+ if(C.ckey == key)
+ p_age = C.player_age
+ break
+
+ data["entries"] = list()
+
+ var/savefile/info = new("data/player_saves/[copytext(key, 1, 2)]/[key]/info.sav")
+ var/list/infos
+ info >> infos
+ if(infos)
+ var/update_file = 0
+ var/i = 0
+ for(var/datum/player_info/I in infos)
+ i += 1
+ if(!I.timestamp)
+ I.timestamp = "Pre-4/3/2012"
+ update_file = 1
+ if(!I.rank)
+ I.rank = "N/A"
+ update_file = 1
+
+ data["entries"] += list(list(
+ "comment" = I.content,
+ "author" = "[I.author] ([I.rank])",
+ "date" = "[I.timestamp]"
+ ))
+ if(update_file) info << infos
+
+ data["ckey"] = key
+ data["age"] = p_age
+
+ return data
+
+// ==== LEGACY UI ====
+
+/datum/admins/proc/PlayerNotesLegacy()
+ if (!istype(src,/datum/admins))
+ src = usr.client.holder
+ if (!istype(src,/datum/admins))
+ to_chat(usr, "Error: you are not an admin!")
+ return
+ PlayerNotesPageLegacy(1)
+
+/datum/admins/proc/PlayerNotesFilterLegacy()
+ if (!istype(src,/datum/admins))
+ src = usr.client.holder
+ if (!istype(src,/datum/admins))
+ to_chat(usr, "Error: you are not an admin!")
+ return
+ var/filter = tgui_input_text(usr, "Filter string (case-insensitive regex)", "Player notes filter")
+ PlayerNotesPageLegacy(1, filter)
+
+/datum/admins/proc/PlayerNotesPageLegacy(page, filter)
+ var/dat = "Player notes - Apply Filter "
+ var/savefile/S=new("data/player_notes.sav")
+ var/list/note_keys
+ S >> note_keys
+ if(!note_keys)
+ dat += "No notes found."
+ else
+ dat += ""
+ note_keys = sortList(note_keys)
+
+ if(filter)
+ var/list/results = list()
+ var/regex/needle = regex(filter, "i")
+ for(var/haystack in note_keys)
+ if(needle.Find(haystack))
+ results += haystack
+ note_keys = results
+
+ // Display the notes on the current page
+ var/number_pages = note_keys.len / PLAYER_NOTES_ENTRIES_PER_PAGE
+ // Emulate CEILING(why does BYOND not have ceil, 1)
+ if(number_pages != round(number_pages))
+ number_pages = round(number_pages) + 1
+ var/page_index = page - 1
+
+ if(page_index < 0 || page_index >= number_pages)
+ dat += "No keys found. "
+ else
+ var/lower_bound = page_index * PLAYER_NOTES_ENTRIES_PER_PAGE + 1
+ var/upper_bound = (page_index + 1) * PLAYER_NOTES_ENTRIES_PER_PAGE
+ upper_bound = min(upper_bound, note_keys.len)
+ for(var/index = lower_bound, index <= upper_bound, index++)
+ var/t = note_keys[index]
+ dat += "[t] "
+
+ dat += "
"
+
+ // Display a footer to select different pages
+ for(var/index = 1, index <= number_pages, index++)
+ if(index == page)
+ dat += ""
+ dat += "[index] "
+ if(index == page)
+ dat += " "
+
+ usr << browse(dat, "window=player_notes;size=400x400")
+
+/datum/admins/proc/player_has_info_legacy(var/key as text)
+ var/savefile/info = new("data/player_saves/[copytext(key, 1, 2)]/[key]/info.sav")
+ var/list/infos
+ info >> infos
+ if(!infos || !infos.len) return 0
+ else return 1
+
+/datum/admins/proc/show_player_info_legacy(var/key as text)
+ if (!istype(src,/datum/admins))
+ src = usr.client.holder
+ if (!istype(src,/datum/admins))
+ to_chat(usr, "Error: you are not an admin!")
+ return
+ var/dat = "Info on [key] "
+ dat += ""
+
+ var/p_age = "unknown"
+ for(var/client/C in GLOB.clients)
+ if(C.ckey == key)
+ p_age = C.player_age
+ break
+ dat +="Player age: [p_age] "
+
+ var/savefile/info = new("data/player_saves/[copytext(key, 1, 2)]/[key]/info.sav")
+ var/list/infos
+ info >> infos
+ if(!infos)
+ dat += "No information found on the given key. "
+ else
+ var/update_file = 0
+ var/i = 0
+ for(var/datum/player_info/I in infos)
+ i += 1
+ if(!I.timestamp)
+ I.timestamp = "Pre-4/3/2012"
+ update_file = 1
+ if(!I.rank)
+ I.rank = "N/A"
+ update_file = 1
+ dat += "[I.content] by [I.author] ([I.rank]) on [I.timestamp] "
+ if(I.author == usr.key || I.author == "Adminbot" || ishost(usr))
+ dat += "Remove "
+ dat += " "
+ if(update_file) info << infos
+
+ dat += " "
+ dat += "Add Comment "
+
+ dat += ""
+ usr << browse(dat, "window=adminplayerinfo;size=480x480")
+
+/datum/admins/Topic(href, href_list)
+ ..()
+
+ if(href_list["add_player_info_legacy"])
+ var/key = href_list["add_player_info_legacy"]
+ var/add = sanitize(tgui_input_text(usr, "Add Player Info (Legacy)"))
+ if(!add) return
+
+ notes_add(key,add,usr)
+ show_player_info_legacy(key)
+
+ if(href_list["remove_player_info_legacy"])
+ var/key = href_list["remove_player_info_legacy"]
+ var/index = text2num(href_list["remove_index"])
+
+ notes_del(key, index)
+ show_player_info_legacy(key)
+
+ if(href_list["notes_legacy"])
+ var/ckey = href_list["ckey"]
+ if(!ckey)
+ var/mob/M = locate(href_list["mob"])
+ if(ismob(M))
+ ckey = M.ckey
+
+ switch(href_list["notes_legacy"])
+ if("show")
+ show_player_info_legacy(ckey)
+ if("list")
+ var/filter
+ if(href_list["filter"] && href_list["filter"] != "0")
+ filter = url_decode(href_list["filter"])
+ PlayerNotesPageLegacy(text2num(href_list["index"]), filter)
+ if("filter")
+ PlayerNotesFilterLegacy()
+ return
\ No newline at end of file
diff --git a/code/modules/tgui/modules/agentcard.dm b/code/modules/tgui/modules/agentcard.dm
index 3046021436..012438dda6 100644
--- a/code/modules/tgui/modules/agentcard.dm
+++ b/code/modules/tgui/modules/agentcard.dm
@@ -75,7 +75,7 @@
var/mob/living/carbon/human/H = usr
if(H.dna)
default = H.dna.b_type
- var/new_blood_type = sanitize(input(usr,"What blood type would you like to be written on this card?","Agent Card Blood Type",default) as null|text)
+ var/new_blood_type = sanitize(tgui_input_text(usr,"What blood type would you like to be written on this card?","Agent Card Blood Type",default))
if(!isnull(new_blood_type) && tgui_status(usr, state) == STATUS_INTERACTIVE)
S.blood_type = new_blood_type
to_chat(usr, "Blood type changed to '[new_blood_type]'. ")
@@ -86,7 +86,7 @@
var/mob/living/carbon/human/H = usr
if(H.dna)
default = H.dna.unique_enzymes
- var/new_dna_hash = sanitize(input(usr,"What DNA hash would you like to be written on this card?","Agent Card DNA Hash",default) as null|text)
+ var/new_dna_hash = sanitize(tgui_input_text(usr,"What DNA hash would you like to be written on this card?","Agent Card DNA Hash",default))
if(!isnull(new_dna_hash) && tgui_status(usr, state) == STATUS_INTERACTIVE)
S.dna_hash = new_dna_hash
to_chat(usr, "DNA hash changed to '[new_dna_hash]'. ")
@@ -103,7 +103,7 @@
to_chat(usr, "Fingerprint hash changed to '[new_fingerprint_hash]'. ")
. = TRUE
if("name")
- var/new_name = sanitizeName(input(usr,"What name would you like to put on this card?","Agent Card Name", S.registered_name) as null|text)
+ var/new_name = sanitizeName(tgui_input_text(usr,"What name would you like to put on this card?","Agent Card Name", S.registered_name))
if(!isnull(new_name) && tgui_status(usr, state) == STATUS_INTERACTIVE)
S.registered_name = new_name
S.update_name()
@@ -114,7 +114,7 @@
to_chat(usr, "Photo changed. ")
. = TRUE
if("sex")
- var/new_sex = sanitize(input(usr,"What sex would you like to put on this card?","Agent Card Sex", S.sex) as null|text)
+ var/new_sex = sanitize(tgui_input_text(usr,"What sex would you like to put on this card?","Agent Card Sex", S.sex))
if(!isnull(new_sex) && tgui_status(usr, state) == STATUS_INTERACTIVE)
S.sex = new_sex
to_chat(usr, "Sex changed to '[new_sex]'. ")
diff --git a/code/modules/tgui/modules/communications.dm b/code/modules/tgui/modules/communications.dm
index 15be10bdd5..37f6ca047b 100644
--- a/code/modules/tgui/modules/communications.dm
+++ b/code/modules/tgui/modules/communications.dm
@@ -260,7 +260,7 @@
if(message_cooldown > world.time)
to_chat(usr, "Please allow at least one minute to pass between announcements. ")
return
- var/input = input(usr, "Please write a message to announce to the station crew.", "Priority Announcement") as null|message
+ var/input = tgui_input_text(usr, "Please write a message to announce to the station crew.", "Priority Announcement", multiline = TRUE)
if(!input || message_cooldown > world.time || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX))
return
if(length(input) < COMM_MSGLEN_MINIMUM)
@@ -337,10 +337,10 @@
if(centcomm_message_cooldown > world.time)
to_chat(usr, "Arrays recycling. Please stand by. ")
return
- var/input = sanitize(input(usr, "Please choose a message to transmit to [using_map.boss_short] via quantum entanglement. \
+ var/input = sanitize(tgui_input_text(usr, "Please choose a message to transmit to [using_map.boss_short] via quantum entanglement. \
Please be aware that this process is very expensive, and abuse will lead to... termination. \
Transmission does not guarantee a response. \
- There is a 30 second delay before you may send another message, be clear, full and concise.", "Central Command Quantum Messaging") as null|message)
+ There is a 30 second delay before you may send another message, be clear, full and concise.", "Central Command Quantum Messaging", multiline = TRUE))
if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX))
return
if(length(input) < COMM_CCMSGLEN_MINIMUM)
@@ -358,7 +358,7 @@
if(centcomm_message_cooldown > world.time)
to_chat(usr, "Arrays recycling. Please stand by.")
return
- var/input = sanitize(input(usr, "Please choose a message to transmit to \[ABNORMAL ROUTING CORDINATES\] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response. There is a 30 second delay before you may send another message, be clear, full and concise.", "To abort, send an empty message.", ""))
+ var/input = sanitize(tgui_input_text(usr, "Please choose a message to transmit to \[ABNORMAL ROUTING CORDINATES\] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response. There is a 30 second delay before you may send another message, be clear, full and concise.", "To abort, send an empty message.", ""))
if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX))
return
if(length(input) < COMM_CCMSGLEN_MINIMUM)
diff --git a/code/modules/tgui/modules/gyrotron_control.dm b/code/modules/tgui/modules/gyrotron_control.dm
index 66dd061068..b035ae4347 100644
--- a/code/modules/tgui/modules/gyrotron_control.dm
+++ b/code/modules/tgui/modules/gyrotron_control.dm
@@ -18,7 +18,7 @@
switch(action)
if("set_tag")
- var/new_ident = sanitize_text(input(usr, "Enter a new ident tag.", "Gyrotron Control", gyro_tag) as null|text)
+ var/new_ident = sanitize_text(tgui_input_text(usr, "Enter a new ident tag.", "Gyrotron Control", gyro_tag))
if(new_ident)
gyro_tag = new_ident
return TRUE
diff --git a/code/modules/tgui/modules/overmap.dm b/code/modules/tgui/modules/overmap.dm
index 5aac12d7c1..a9fff0e6d4 100644
--- a/code/modules/tgui/modules/overmap.dm
+++ b/code/modules/tgui/modules/overmap.dm
@@ -307,7 +307,7 @@
/* HELM */
if("add")
var/datum/computer_file/data/waypoint/R = new()
- var/sec_name = input(usr, "Input navigation entry name", "New navigation entry", "Sector #[known_sectors.len]") as text
+ var/sec_name = tgui_input_text(usr, "Input navigation entry name", "New navigation entry", "Sector #[known_sectors.len]")
if(!sec_name)
sec_name = "Sector #[known_sectors.len]"
R.fields["name"] = sec_name
@@ -319,8 +319,8 @@
R.fields["x"] = linked.x
R.fields["y"] = linked.y
if("new")
- var/newx = input(usr, "Input new entry x coordinate", "Coordinate input", linked.x) as num
- var/newy = input(usr, "Input new entry y coordinate", "Coordinate input", linked.y) as num
+ var/newx = tgui_input_number(usr, "Input new entry x coordinate", "Coordinate input", linked.x)
+ var/newy = tgui_input_number(usr, "Input new entry y coordinate", "Coordinate input", linked.y)
R.fields["x"] = CLAMP(newx, 1, world.maxx)
R.fields["y"] = CLAMP(newy, 1, world.maxy)
known_sectors[sec_name] = R
@@ -335,12 +335,12 @@
if("setcoord")
if(params["setx"])
- var/newx = input(usr, "Input new destiniation x coordinate", "Coordinate input", dx) as num|null
+ var/newx = tgui_input_number(usr, "Input new destiniation x coordinate", "Coordinate input", dx)
if(newx)
dx = CLAMP(newx, 1, world.maxx)
if(params["sety"])
- var/newy = input(usr, "Input new destiniation y coordinate", "Coordinate input", dy) as num|null
+ var/newy = tgui_input_number(usr, "Input new destiniation y coordinate", "Coordinate input", dy)
if(newy)
dy = CLAMP(newy, 1, world.maxy)
. = TRUE
@@ -356,13 +356,13 @@
. = TRUE
if("speedlimit")
- var/newlimit = input(usr, "Input new speed limit for autopilot (0 to brake)", "Autopilot speed limit", speedlimit*1000) as num|null
+ var/newlimit = tgui_input_number(usr, "Input new speed limit for autopilot (0 to brake)", "Autopilot speed limit", speedlimit*1000)
if(newlimit)
speedlimit = CLAMP(newlimit/1000, 0, 100)
. = TRUE
if("accellimit")
- var/newlimit = input(usr, "Input new acceleration limit", "Acceleration limit", accellimit*1000) as num|null
+ var/newlimit = tgui_input_number(usr, "Input new acceleration limit", "Acceleration limit", accellimit*1000)
if(newlimit)
accellimit = max(newlimit/1000, 0)
. = TRUE
@@ -402,7 +402,7 @@
. = TRUE
if("set_global_limit")
- var/newlim = input(usr, "Input new thrust limit (0..100%)", "Thrust limit", linked.thrust_limit*100) as num
+ var/newlim = tgui_input_number(usr, "Input new thrust limit (0..100%)", "Thrust limit", linked.thrust_limit*100, 100, 0)
linked.thrust_limit = clamp(newlim/100, 0, 1)
for(var/datum/ship_engine/E in linked.engines)
E.set_thrust_limit(linked.thrust_limit)
@@ -416,7 +416,7 @@
if("set_limit")
var/datum/ship_engine/E = locate(params["engine"])
- var/newlim = input(usr, "Input new thrust limit (0..100)", "Thrust limit", E.get_thrust_limit()) as num
+ var/newlim = tgui_input_number(usr, "Input new thrust limit (0..100)", "Thrust limit", E.get_thrust_limit(), 100, 0)
var/limit = clamp(newlim/100, 0, 1)
if(istype(E))
E.set_thrust_limit(limit)
@@ -437,7 +437,7 @@
/* END ENGINES */
/* SENSORS */
if("range")
- var/nrange = input(usr, "Set new sensors range", "Sensor range", sensors.range) as num|null
+ var/nrange = tgui_input_number(usr, "Set new sensors range", "Sensor range", sensors.range)
if(nrange)
sensors.set_range(CLAMP(nrange, 1, world.view))
. = TRUE
diff --git a/code/modules/tgui/modules/rcon.dm b/code/modules/tgui/modules/rcon.dm
index 624b353c4b..0a29153d76 100644
--- a/code/modules/tgui/modules/rcon.dm
+++ b/code/modules/tgui/modules/rcon.dm
@@ -40,7 +40,7 @@
smes_data["RCON_tag"] = SMES.RCon_tag
smeslist.Add(list(smes_data))
- data["pages"] = number_pages
+ data["pages"] = number_pages + 1
data["current_page"] = current_page
data["smes_info"] = sortByKey(smeslist, "RCON_tag")
diff --git a/code/modules/tgui/modules/rustcore_monitor.dm b/code/modules/tgui/modules/rustcore_monitor.dm
index 553363a963..a7ce6edbbb 100644
--- a/code/modules/tgui/modules/rustcore_monitor.dm
+++ b/code/modules/tgui/modules/rustcore_monitor.dm
@@ -25,7 +25,7 @@
return TRUE
if("set_tag")
- var/new_ident = sanitize_text(input(usr, "Enter a new ident tag.", "Core Control", core_tag) as null|text)
+ var/new_ident = sanitize_text(tgui_input_text(usr, "Enter a new ident tag.", "Core Control", core_tag))
if(new_ident)
core_tag = new_ident
return TRUE
diff --git a/code/modules/tgui/modules/rustfuel_control.dm b/code/modules/tgui/modules/rustfuel_control.dm
index f910ff632e..2d4d43639c 100644
--- a/code/modules/tgui/modules/rustfuel_control.dm
+++ b/code/modules/tgui/modules/rustfuel_control.dm
@@ -22,7 +22,7 @@
return TRUE
if("set_tag")
- var/new_ident = sanitize_text(input(usr, "Enter a new ident tag.", "Gyrotron Control", fuel_tag) as null|text)
+ var/new_ident = sanitize_text(tgui_input_text(usr, "Enter a new ident tag.", "Gyrotron Control", fuel_tag))
if(new_ident)
fuel_tag = new_ident
diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm
index c34ccf63d7..42acb1eac0 100644
--- a/code/modules/tgui/tgui.dm
+++ b/code/modules/tgui/tgui.dm
@@ -32,8 +32,12 @@
var/closing = FALSE
/// The status/visibility of the UI.
var/status = STATUS_INTERACTIVE
+ /// Timed refreshing state
+ var/refreshing = FALSE
/// Topic state used to determine status/interactability.
var/datum/tgui_state/state = null
+ /// Rate limit client refreshes to prevent DoS.
+ COOLDOWN_DECLARE(refresh_cooldown)
/// The map z-level to display.
var/map_z_level = 1
/// The Parent UI
@@ -176,11 +180,17 @@
/datum/tgui/proc/send_full_update(custom_data, force)
if(!user.client || !initialized || closing)
return
+ if(!COOLDOWN_FINISHED(src, refresh_cooldown))
+ refreshing = TRUE
+ addtimer(CALLBACK(src, .proc/send_full_update), TGUI_REFRESH_FULL_UPDATE_COOLDOWN, TIMER_UNIQUE)
+ return
+ refreshing = FALSE
var/should_update_data = force || status >= STATUS_UPDATE
window.send_message("update", get_payload(
custom_data,
with_data = should_update_data,
with_static_data = TRUE))
+ COOLDOWN_START(src, refresh_cooldown, TGUI_REFRESH_FULL_UPDATE_COOLDOWN)
/**
* public
@@ -211,6 +221,7 @@
"title" = title,
"status" = status,
"interface" = interface,
+ "refreshing" = refreshing,
"map" = (using_map && using_map.path) ? using_map.path : "Unknown",
"mapZLevel" = map_z_level,
"window" = list(
@@ -312,6 +323,9 @@
return FALSE
switch(type)
if("ready")
+ // Send a full update when the user manually refreshes the UI
+ if(initialized)
+ send_full_update()
initialized = TRUE
if("pingReply")
initialized = TRUE
diff --git a/code/modules/tgui/tgui_input_text.dm b/code/modules/tgui/tgui_input_text.dm
deleted file mode 100644
index e2247a32ce..0000000000
--- a/code/modules/tgui/tgui_input_text.dm
+++ /dev/null
@@ -1,299 +0,0 @@
-/**
- * Creates a TGUI input text window and returns the user's response.
- *
- * This proc should be used to create alerts that the caller will wait for a response from.
- * Arguments:
- * * user - The user to show the input box to.
- * * message - The content of the input box, shown in the body of the TGUI window.
- * * title - The title of the input box, shown on the top of the TGUI window.
- * * default - The default value pre-populated in the input box.
- * * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout.
- */
-/proc/tgui_input_text(mob/user, message, title, default, timeout = 0)
- if (istext(user))
- stack_trace("tgui_input_text() received text for user instead of mob")
- return
- if (!user)
- user = usr
- if (!istype(user))
- if (istype(user, /client))
- var/client/client = user
- user = client.mob
- else
- return
- var/datum/tgui_input_dialog/input = new(user, message, title, default, timeout)
- input.input_type = "text"
- input.tgui_interact(user)
- input.wait()
- if (input)
- . = input.choice
- qdel(input)
-
-/**
- * Creates a TGUI input message window and returns the user's response.
- *
- * This proc should be used to create alerts that the caller will wait for a response from.
- * Arguments:
- * * user - The user to show the input box to.
- * * message - The content of the input box, shown in the body of the TGUI window.
- * * title - The title of the input box, shown on the top of the TGUI window.
- * * default - The default value pre-populated in the input box.
- * * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout.
- */
-/proc/tgui_input_message(mob/user, message, title, default, timeout = 0)
- if (istext(user))
- stack_trace("tgui_input_message() received text for user instead of mob")
- return
- if (!user)
- user = usr
- if (!istype(user))
- if (istype(user, /client))
- var/client/client = user
- user = client.mob
- else
- return
- var/datum/tgui_input_dialog/input = new(user, message, title, default, timeout)
- input.input_type = "message"
- input.tgui_interact(user)
- input.wait()
- if (input)
- . = input.choice
- qdel(input)
-
-/**
- * Creates a TGUI input num window and returns the user's response.
- *
- * This proc should be used to create alerts that the caller will wait for a response from.
- * Arguments:
- * * user - The user to show the input box to.
- * * message - The content of the input box, shown in the body of the TGUI window.
- * * title - The title of the input box, shown on the top of the TGUI window.
- * * default - The default value pre-populated in the input box.
- * * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout.
- */
-/proc/tgui_input_num(mob/user, message, title, default, timeout = 0)
- if (istext(user))
- stack_trace("tgui_input_num() received text for user instead of mob")
- return
- if (!user)
- user = usr
- if (!istype(user))
- if (istype(user, /client))
- var/client/client = user
- user = client.mob
- else
- return
- var/datum/tgui_input_dialog/input = new(user, message, title, default, timeout)
- input.input_type = "num"
- input.tgui_interact(user)
- input.wait()
- if (input)
- . = input.choice
- qdel(input)
-
-/**
- * Creates an asynchronous TGUI input text window with an associated callback.
- *
- * This proc should be used to create inputs that invoke a callback with the user's chosen option.
- * Arguments:
- * * user - The user to show the input box to.
- * * message - The content of the input box, shown in the body of the TGUI window.
- * * title - The title of the input box, shown on the top of the TGUI window.
- * * default - The default value pre-populated in the input box.
- * * callback - The callback to be invoked when a choice is made.
- * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
- */
-/proc/tgui_input_text_async(mob/user, message, title, default, datum/callback/callback, timeout = 60 SECONDS)
- if (istext(user))
- stack_trace("tgui_input_text_async() received text for user instead of mob")
- return
- if (!user)
- user = usr
- if (!istype(user))
- if (istype(user, /client))
- var/client/client = user
- user = client.mob
- else
- return
- var/datum/tgui_input_dialog/async/input = new(user, message, title, default, callback, timeout)
- input.input_type = "text"
- input.tgui_interact(user)
-
-/**
- * Creates an asynchronous TGUI input message window with an associated callback.
- *
- * This proc should be used to create inputs that invoke a callback with the user's chosen option.
- * Arguments:
- * * user - The user to show the input box to.
- * * message - The content of the input box, shown in the body of the TGUI window.
- * * title - The title of the input box, shown on the top of the TGUI window.
- * * default - The default value pre-populated in the input box.
- * * callback - The callback to be invoked when a choice is made.
- * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
- */
-/proc/tgui_input_message_async(mob/user, message, title, default, datum/callback/callback, timeout = 60 SECONDS)
- if (istext(user))
- stack_trace("tgui_input_message_async() received text for user instead of mob")
- return
- if (!user)
- user = usr
- if (!istype(user))
- if (istype(user, /client))
- var/client/client = user
- user = client.mob
- else
- return
- var/datum/tgui_input_dialog/async/input = new(user, message, title, default, callback, timeout)
- input.input_type = "message"
- input.tgui_interact(user)
-
-/**
- * Creates an asynchronous TGUI input num window with an associated callback.
- *
- * This proc should be used to create inputs that invoke a callback with the user's chosen option.
- * Arguments:
- * * user - The user to show the input box to.
- * * message - The content of the input box, shown in the body of the TGUI window.
- * * title - The title of the input box, shown on the top of the TGUI window.
- * * default - The default value pre-populated in the input box.
- * * callback - The callback to be invoked when a choice is made.
- * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
- */
-/proc/tgui_input_num_async(mob/user, message, title, default, datum/callback/callback, timeout = 60 SECONDS)
- if (istext(user))
- stack_trace("tgui_input_num_async() received text for user instead of mob")
- return
- if (!user)
- user = usr
- if (!istype(user))
- if (istype(user, /client))
- var/client/client = user
- user = client.mob
- else
- return
- var/datum/tgui_input_dialog/async/input = new(user, message, title, default, callback, timeout)
- input.input_type = "num"
- input.tgui_interact(user)
-
-/**
- * # tgui_input_dialog
- *
- * Datum used for instantiating and using a TGUI-controlled input that prompts the user with
- * a message and a box for accepting text/message/num input.
- */
-/datum/tgui_input_dialog
- /// The title of the TGUI window
- var/title
- /// The textual body of the TGUI window
- var/message
- /// The default value to initially populate the input box.
- var/initial
- /// The value that the user input into the input box, null if cancelled.
- var/choice
- /// The time at which the tgui_text_input was created, for displaying timeout progress.
- var/start_time
- /// The lifespan of the tgui_text_input, after which the window will close and delete itself.
- var/timeout
- /// Boolean field describing if the tgui_text_input was closed by the user.
- var/closed
- /// Indicates the data type we want to collect ("text", "message", "num")
- var/input_type = "text"
-
-/datum/tgui_input_dialog/New(mob/user, message, title, default, timeout)
- src.title = title
- src.message = message
- // TODO - Do we need to sanitize the initial value for illegal characters?
- src.initial = default
- if (timeout)
- src.timeout = timeout
- start_time = world.time
- QDEL_IN(src, timeout)
-
-/datum/tgui_input_dialog/Destroy(force, ...)
- SStgui.close_uis(src)
- . = ..()
-
-/**
- * Waits for a user's response to the tgui_text_input's prompt before returning. Returns early if
- * the window was closed by the user.
- */
-/datum/tgui_input_dialog/proc/wait()
- while (!choice && !closed)
- stoplag(1)
-
-/datum/tgui_input_dialog/tgui_interact(mob/user, datum/tgui/ui)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "InputModal")
- ui.open()
-
-/datum/tgui_input_dialog/tgui_close(mob/user)
- . = ..()
- closed = TRUE
-
-/datum/tgui_input_dialog/tgui_state(mob/user)
- return GLOB.tgui_always_state
-
-/datum/tgui_input_dialog/tgui_static_data(mob/user)
- . = list(
- "title" = title,
- "message" = message,
- "initial" = initial,
- "input_type" = input_type
- )
-
-/datum/tgui_input_dialog/tgui_data(mob/user)
- . = list()
- if(timeout)
- .["timeout"] = clamp((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS), 0, 1)
-
-/datum/tgui_input_dialog/tgui_act(action, list/params)
- . = ..()
- if (.)
- return
- switch(action)
- if("choose")
- set_choice(params["choice"])
- if(isnull(src.choice))
- return
- SStgui.close_uis(src)
- return TRUE
- if("cancel")
- SStgui.close_uis(src)
- closed = TRUE
- return TRUE
-
-/datum/tgui_input_dialog/proc/set_choice(choice)
- if(input_type == "num")
- src.choice = text2num(choice)
- return
- src.choice = choice
-
-/**
- * # async tgui_text_input
- *
- * An asynchronous version of tgui_text_input to be used with callbacks instead of waiting on user responses.
- */
-/datum/tgui_input_dialog/async
- /// The callback to be invoked by the tgui_text_input upon having a choice made.
- var/datum/callback/callback
-
-/datum/tgui_input_dialog/async/New(mob/user, message, title, default, callback, timeout)
- ..(user, title, message, default, timeout)
- src.callback = callback
-
-/datum/tgui_input_dialog/async/Destroy(force, ...)
- QDEL_NULL(callback)
- . = ..()
-
-/datum/tgui_input_dialog/async/tgui_close(mob/user)
- . = ..()
- qdel(src)
-
-/datum/tgui_input_dialog/async/set_choice(choice)
- . = ..()
- if(!isnull(src.choice))
- callback?.InvokeAsync(src.choice)
-
-/datum/tgui_input_dialog/async/wait()
- return
diff --git a/code/modules/tgui/tgui_alert.dm b/code/modules/tgui_input/alert.dm
similarity index 74%
rename from code/modules/tgui/tgui_alert.dm
rename to code/modules/tgui_input/alert.dm
index 4ae15da886..a53089e4b0 100644
--- a/code/modules/tgui/tgui_alert.dm
+++ b/code/modules/tgui_input/alert.dm
@@ -8,8 +8,9 @@
* * title - The of the alert modal, shown on the top of the TGUI window.
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
* * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout.
+ * * autofocus - The bool that controls if this alert should grab window focus.
*/
-/proc/tgui_alert(mob/user, message = null, title = null, list/buttons = list("Ok"), timeout = 0)
+/proc/tgui_alert(mob/user, message = "", title, list/buttons = list("Ok"), timeout = 0, autofocus = TRUE)
if (istext(buttons))
stack_trace("tgui_alert() received text for buttons instead of list")
return
@@ -24,7 +25,19 @@
user = client.mob
else
return
- var/datum/tgui_alert/alert = new(user, message, title, buttons, timeout)
+ // A gentle nudge - you should not be using TGUI alert for anything other than a simple message.
+ if(length(buttons) > 3)
+ log_tgui(user, "Error: TGUI Alert initiated with too many buttons. Use a list.", "TguiAlert")
+ return tgui_input_list(user, message, title, buttons, timeout, autofocus)
+
+ // Client does NOT have tgui_input on: Returns regular input
+ if(!usr.client.prefs.tgui_input_mode)
+ if(length(buttons) == 2)
+ return alert(user, message, title, buttons[1], buttons[2])
+ if(length(buttons) == 3)
+ return alert(user, message, title, buttons[1], buttons[2], buttons[3])
+
+ var/datum/tgui_alert/alert = new(user, message, title, buttons, timeout, autofocus)
alert.tgui_interact(user)
alert.wait()
if (alert)
@@ -32,37 +45,7 @@
qdel(alert)
/**
- * Creates an asynchronous TGUI alert window with an associated callback.
- *
- * This proc should be used to create alerts that invoke a callback with the user's chosen option.
- * Arguments:
- * * user - The user to show the alert to.
- * * message - The content of the alert, shown in the body of the TGUI window.
- * * title - The of the alert modal, shown on the top of the TGUI window.
- * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
- * * callback - The callback to be invoked when a choice is made.
- * * timeout - The timeout of the alert, after which the modal will close and qdel itself. Disabled by default, can be set to seconds otherwise.
- */
-/proc/tgui_alert_async(mob/user, message = null, title = null, list/buttons = list("Ok"), datum/callback/callback, timeout = 0)
- if (istext(buttons))
- stack_trace("tgui_alert() received text for buttons instead of list")
- return
- if (istext(user))
- stack_trace("tgui_alert() received text for user instead of list")
- return
- if (!user)
- user = usr
- if (!istype(user))
- if (istype(user, /client))
- var/client/client = user
- user = client.mob
- else
- return
- var/datum/tgui_alert/async/alert = new(user, message, title, buttons, callback, timeout)
- alert.tgui_interact(user)
-
-/**
- * # tgui_modal
+ * # tgui_alert
*
* Datum used for instantiating and using a TGUI-controlled modal that prompts the user with
* a message and has buttons for responses.
@@ -80,13 +63,16 @@
var/start_time
/// The lifespan of the tgui_modal, after which the window will close and delete itself.
var/timeout
+ /// The bool that controls if this modal should grab window focus
+ var/autofocus
/// Boolean field describing if the tgui_modal was closed by the user.
var/closed
-/datum/tgui_alert/New(mob/user, message, title, list/buttons, timeout)
- src.title = title
- src.message = message
+/datum/tgui_alert/New(mob/user, message, title, list/buttons, timeout, autofocus)
+ src.autofocus = autofocus
src.buttons = buttons.Copy()
+ src.message = message
+ src.title = title
if (timeout)
src.timeout = timeout
start_time = world.time
@@ -118,15 +104,21 @@
/datum/tgui_alert/tgui_state(mob/user)
return GLOB.tgui_always_state
-/datum/tgui_alert/tgui_data(mob/user)
- . = list(
- "title" = title,
- "message" = message,
- "buttons" = buttons
- )
+/datum/tgui_alert/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["autofocus"] = autofocus
+ data["buttons"] = buttons
+ data["message"] = message
+ data["large_buttons"] = usr.client.prefs.tgui_large_buttons
+ data["swapped_buttons"] = !usr.client.prefs.tgui_swapped_buttons
+ data["title"] = title
+ return data
+/datum/tgui_alert/tgui_data(mob/user)
+ var/list/data = list()
if(timeout)
.["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
+ return data
/datum/tgui_alert/tgui_act(action, list/params)
. = ..()
@@ -139,10 +131,44 @@
set_choice(params["choice"])
SStgui.close_uis(src)
return TRUE
+ if("cancel")
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
/datum/tgui_alert/proc/set_choice(choice)
src.choice = choice
+/**
+ * Creates an asynchronous TGUI alert window with an associated callback.
+ *
+ * This proc should be used to create alerts that invoke a callback with the user's chosen option.
+ * Arguments:
+ * * user - The user to show the alert to.
+ * * message - The content of the alert, shown in the body of the TGUI window.
+ * * title - The of the alert modal, shown on the top of the TGUI window.
+ * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
+ * * callback - The callback to be invoked when a choice is made.
+ * * timeout - The timeout of the alert, after which the modal will close and qdel itself. Disabled by default, can be set to seconds otherwise.
+ */
+/proc/tgui_alert_async(mob/user, message = "", title, list/buttons = list("Ok"), datum/callback/callback, timeout = 0, autofocus = TRUE)
+ if (istext(buttons))
+ stack_trace("tgui_alert() received text for buttons instead of list")
+ return
+ if (istext(user))
+ stack_trace("tgui_alert() received text for user instead of list")
+ return
+ if (!user)
+ user = usr
+ if (!istype(user))
+ if (istype(user, /client))
+ var/client/client = user
+ user = client.mob
+ else
+ return
+ var/datum/tgui_alert/async/alert = new(user, message, title, buttons, callback, timeout, autofocus)
+ alert.tgui_interact(user)
+
/**
* # async tgui_modal
*
@@ -152,8 +178,8 @@
/// The callback to be invoked by the tgui_modal upon having a choice made.
var/datum/callback/callback
-/datum/tgui_alert/async/New(mob/user, message, title, list/buttons, callback, timeout)
- ..(user, message, title, buttons, timeout)
+/datum/tgui_alert/async/New(mob/user, message, title, list/buttons, callback, timeout, autofocus)
+ ..(user, message, title, buttons, timeout, autofocus)
src.callback = callback
/datum/tgui_alert/async/Destroy(force, ...)
diff --git a/code/modules/tgui/tgui_input_list.dm b/code/modules/tgui_input/list.dm
similarity index 69%
rename from code/modules/tgui/tgui_input_list.dm
rename to code/modules/tgui_input/list.dm
index 8b1af90196..fc0fdc9c1e 100644
--- a/code/modules/tgui/tgui_input_list.dm
+++ b/code/modules/tgui_input/list.dm
@@ -6,17 +6,18 @@
* * user - The user to show the input box to.
* * message - The content of the input box, shown in the body of the TGUI window.
* * title - The title of the input box, shown on the top of the TGUI window.
- * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
+ * * items - The options that can be chosen by the user, each string is assigned a button on the UI.
* * default - The option with this value will be selected on first paint of the TGUI window.
* * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout.
+ * * strict_modern - Disabled the preference check of the input box, only allowing the TGUI window to show.
*/
-/proc/tgui_input_list(mob/user, message, title, list/buttons, default, timeout = 0)
+/proc/tgui_input_list(mob/user, message, title = "Select", list/items, default, timeout = 0, strict_modern = FALSE)
if (istext(user))
stack_trace("tgui_alert() received text for user instead of mob")
return
if (!user)
user = usr
- if(!length(buttons))
+ if(!length(items))
return
if (!istype(user))
if (istype(user, /client))
@@ -24,43 +25,16 @@
user = client.mob
else
return
- var/datum/tgui_list_input/input = new(user, message, title, buttons, default, timeout)
+ /// Client does NOT have tgui_input on: Returns regular input
+ if(!usr.client.prefs.tgui_input_mode && !strict_modern)
+ return input(user, message, title, default) as null|anything in items
+ var/datum/tgui_list_input/input = new(user, message, title, items, default, timeout)
input.tgui_interact(user)
input.wait()
if (input)
. = input.choice
qdel(input)
-/**
- * Creates an asynchronous TGUI input list window with an associated callback.
- *
- * This proc should be used to create inputs that invoke a callback with the user's chosen option.
- * Arguments:
- * * user - The user to show the input box to.
- * * message - The content of the input box, shown in the body of the TGUI window.
- * * title - The title of the input box, shown on the top of the TGUI window.
- * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
- * * default - The option with this value will be selected on first paint of the TGUI window.
- * * callback - The callback to be invoked when a choice is made.
- * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
- */
-/proc/tgui_input_list_async(mob/user, message, title, list/buttons, default, datum/callback/callback, timeout = 60 SECONDS)
- if (istext(user))
- stack_trace("tgui_alert() received text for user instead of mob")
- return
- if (!user)
- user = usr
- if(!length(buttons))
- return
- if (!istype(user))
- if (istype(user, /client))
- var/client/client = user
- user = client.mob
- else
- return
- var/datum/tgui_list_input/async/input = new(user, message, title, buttons, default, callback, timeout)
- input.tgui_interact(user)
-
/**
* # tgui_list_input
*
@@ -72,14 +46,14 @@
var/title
/// The textual body of the TGUI window
var/message
- /// The list of buttons (responses) provided on the TGUI window
- var/list/buttons
- /// Buttons (strings specifically) mapped to the actual value (e.g. a mob or a verb)
- var/list/buttons_map
- /// Value of the button that should be pre-selected on first paint.
- var/initial
+ /// The list of items (responses) provided on the TGUI window
+ var/list/items
+ /// Items (strings specifically) mapped to the actual value (e.g. a mob or a verb)
+ var/list/items_map
/// The button that the user has pressed, null if no selection has been made
var/choice
+ /// The default item to be selected
+ var/default
/// The time at which the tgui_list_input was created, for displaying timeout progress.
var/start_time
/// The lifespan of the tgui_list_input, after which the window will close and delete itself.
@@ -87,29 +61,29 @@
/// Boolean field describing if the tgui_list_input was closed by the user.
var/closed
-/datum/tgui_list_input/New(mob/user, message, title, list/buttons, default, timeout)
+/datum/tgui_list_input/New(mob/user, message, title, list/items, default, timeout)
src.title = title
src.message = message
- src.buttons = list()
- src.buttons_map = list()
- src.initial = default
- var/list/repeat_buttons = list()
+ src.items = list()
+ src.items_map = list()
+ src.default = default
+ var/list/repeat_items = list()
// Gets rid of illegal characters
var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"})
- for(var/i in buttons)
+ for(var/i in items)
if(isnull(i))
- stack_trace("Null in a tgui_input_list() buttons")
+ stack_trace("Null in a tgui_input_list() items")
continue
var/string_key = whitelistedWords.Replace("[i]", "")
//avoids duplicated keys E.g: when areas have the same name
- string_key = avoid_assoc_duplicate_keys(string_key, repeat_buttons)
+ string_key = avoid_assoc_duplicate_keys(string_key, repeat_items)
- src.buttons += string_key
- src.buttons_map[string_key] = i
+ src.items += string_key
+ src.items_map[string_key] = i
if (timeout)
@@ -119,7 +93,7 @@
/datum/tgui_list_input/Destroy(force, ...)
SStgui.close_uis(src)
- QDEL_NULL(buttons)
+ QDEL_NULL(items)
. = ..()
/**
@@ -133,7 +107,7 @@
/datum/tgui_list_input/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, "ListInput")
+ ui = new(user, src, "ListInputModal")
ui.open()
/datum/tgui_list_input/tgui_close(mob/user)
@@ -144,27 +118,31 @@
return GLOB.tgui_always_state
/datum/tgui_list_input/tgui_static_data(mob/user)
- . = list(
- "title" = title,
- "message" = message,
- "buttons" = buttons,
- "initial" = initial
- )
+ var/list/data = list()
+ data["init_value"] = default || items[1]
+ data["items"] = items
+ data["large_buttons"] = usr.client.prefs.tgui_large_buttons
+ data["message"] = message
+ data["swapped_buttons"] = !usr.client.prefs.tgui_swapped_buttons
+ data["title"] = title
+ return data
/datum/tgui_list_input/tgui_data(mob/user)
- . = list()
+ var/list/data = list()
if(timeout)
.["timeout"] = clamp((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS), 0, 1)
+ return data
/datum/tgui_list_input/tgui_act(action, list/params)
. = ..()
if (.)
return
switch(action)
- if("choose")
- if (!(params["choice"] in buttons))
+ if("submit")
+ if (!(params["entry"] in items))
return
- set_choice(buttons_map[params["choice"]])
+ set_choice(items_map[params["entry"]])
+ closed = TRUE
SStgui.close_uis(src)
return TRUE
if("cancel")
@@ -175,6 +153,36 @@
/datum/tgui_list_input/proc/set_choice(choice)
src.choice = choice
+/**
+ * Creates an asynchronous TGUI input list window with an associated callback.
+ *
+ * This proc should be used to create inputs that invoke a callback with the user's chosen option.
+ * Arguments:
+ * * user - The user to show the input box to.
+ * * message - The content of the input box, shown in the body of the TGUI window.
+ * * title - The title of the input box, shown on the top of the TGUI window.
+ * * items - The options that can be chosen by the user, each string is assigned a button on the UI.
+ * * default - The option with this value will be selected on first paint of the TGUI window.
+ * * callback - The callback to be invoked when a choice is made.
+ * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
+ */
+/proc/tgui_input_list_async(mob/user, message, title, list/items, default, datum/callback/callback, timeout = 60 SECONDS)
+ if (istext(user))
+ stack_trace("tgui_alert() received text for user instead of mob")
+ return
+ if (!user)
+ user = usr
+ if(!length(items))
+ return
+ if (!istype(user))
+ if (istype(user, /client))
+ var/client/client = user
+ user = client.mob
+ else
+ return
+ var/datum/tgui_list_input/async/input = new(user, message, title, items, default, callback, timeout)
+ input.tgui_interact(user)
+
/**
* # async tgui_list_input
*
@@ -184,8 +192,8 @@
/// The callback to be invoked by the tgui_list_input upon having a choice made.
var/datum/callback/callback
-/datum/tgui_list_input/async/New(mob/user, message, title, list/buttons, default, callback, timeout)
- ..(user, title, message, buttons, default, timeout)
+/datum/tgui_list_input/async/New(mob/user, message, title, list/items, default, callback, timeout)
+ ..(user, title, message, items, default, timeout)
src.callback = callback
/datum/tgui_list_input/async/Destroy(force, ...)
diff --git a/code/modules/tgui_input/number.dm b/code/modules/tgui_input/number.dm
new file mode 100644
index 0000000000..3f7ffccd96
--- /dev/null
+++ b/code/modules/tgui_input/number.dm
@@ -0,0 +1,209 @@
+/**
+ * Creates a TGUI window with a number input. Returns the user's response as num | null.
+ *
+ * This proc should be used to create windows for number entry that the caller will wait for a response from.
+ * If tgui fancy chat is turned off: Will return a normal input. If a max or min value is specified, will
+ * validate the input inside the UI and ui_act.
+ *
+ * Arguments:
+ * * user - The user to show the number input to.
+ * * message - The content of the number input, shown in the body of the TGUI window.
+ * * title - The title of the number input modal, shown on the top of the TGUI window.
+ * * default - The default (or current) value, shown as a placeholder. Users can press refresh with this.
+ * * max_value - Specifies a maximum value. If none is set, any number can be entered. Pressing "max" defaults to 1000.
+ * * min_value - Specifies a minimum value. Often 0.
+ * * timeout - The timeout of the number input, after which the modal will close and qdel itself. Set to zero for no timeout.
+ * * round_value - whether the inputted number is rounded down into an integer.
+ */
+/proc/tgui_input_number(mob/user, message, title = "Number Input", default = 0, max_value = 10000, min_value = 0, timeout = 0, round_value = TRUE)
+ if (!user)
+ user = usr
+ if (!istype(user))
+ if (istype(user, /client))
+ var/client/client = user
+ user = client.mob
+ else
+ return
+ // Client does NOT have tgui_input on: Returns regular input
+ if(!usr.client.prefs.tgui_input_mode)
+ var/input_number = input(user, message, title, default) as null|num
+ return clamp(round_value ? round(input_number) : input_number, min_value, max_value)
+ var/datum/tgui_input_number/number_input = new(user, message, title, default, max_value, min_value, timeout, round_value)
+ number_input.tgui_interact(user)
+ number_input.wait()
+ if (number_input)
+ . = number_input.entry
+ qdel(number_input)
+
+/**
+ * # tgui_input_number
+ *
+ * Datum used for instantiating and using a TGUI-controlled number input that prompts the user with
+ * a message and has an input for number entry.
+ */
+/datum/tgui_input_number
+ /// Boolean field describing if the tgui_input_number was closed by the user.
+ var/closed
+ /// The default (or current) value, shown as a default. Users can press reset with this.
+ var/default
+ /// The entry that the user has return_typed in.
+ var/entry
+ /// The maximum value that can be entered.
+ var/max_value
+ /// The prompt's body, if any, of the TGUI window.
+ var/message
+ /// The minimum value that can be entered.
+ var/min_value
+ /// Whether the submitted number is rounded down into an integer.
+ var/round_value
+ /// The time at which the number input was created, for displaying timeout progress.
+ var/start_time
+ /// The lifespan of the number input, after which the window will close and delete itself.
+ var/timeout
+ /// The title of the TGUI window
+ var/title
+
+/datum/tgui_input_number/New(mob/user, message, title, default, max_value, min_value, timeout, round_value)
+ src.default = default
+ src.max_value = max_value
+ src.message = message
+ src.min_value = min_value
+ src.title = title
+ src.round_value = round_value
+ if (timeout)
+ src.timeout = timeout
+ start_time = world.time
+ QDEL_IN(src, timeout)
+ /// Checks for empty numbers - bank accounts, etc.
+ if(max_value == 0)
+ src.min_value = 0
+ if(default)
+ src.default = 0
+ /// Sanity check
+ if(default < min_value)
+ src.default = min_value
+ if(default > max_value)
+ CRASH("Default value is greater than max value.")
+
+/datum/tgui_input_number/Destroy(force, ...)
+ SStgui.close_uis(src)
+ return ..()
+
+/**
+ * Waits for a user's response to the tgui_input_number's prompt before returning. Returns early if
+ * the window was closed by the user.
+ */
+/datum/tgui_input_number/proc/wait()
+ while (!entry && !closed && !QDELETED(src))
+ stoplag(1)
+
+/datum/tgui_input_number/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "NumberInputModal")
+ ui.open()
+
+/datum/tgui_input_number/tgui_close(mob/user)
+ . = ..()
+ closed = TRUE
+
+/datum/tgui_input_number/tgui_state(mob/user)
+ return GLOB.tgui_always_state
+
+/datum/tgui_input_number/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["init_value"] = default // Default is a reserved keyword
+ data["large_buttons"] = usr.client.prefs.tgui_large_buttons
+ data["max_value"] = max_value
+ data["message"] = message
+ data["min_value"] = min_value
+ data["swapped_buttons"] = !usr.client.prefs.tgui_swapped_buttons
+ data["title"] = title
+ return data
+
+/datum/tgui_input_number/tgui_data(mob/user)
+ var/list/data = list()
+ if(timeout)
+ data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
+ return data
+
+/datum/tgui_input_number/tgui_act(action, list/params)
+ . = ..()
+ if (.)
+ return
+ switch(action)
+ if("submit")
+ if(!isnum(params["entry"]))
+ CRASH("A non number was input into tgui input number by [usr]")
+ var/choice = round_value ? round(params["entry"]) : params["entry"]
+ if(choice > max_value)
+ CRASH("A number greater than the max value was input into tgui input number by [usr]")
+ if(choice < min_value)
+ CRASH("A number less than the min value was input into tgui input number by [usr]")
+ set_entry(choice)
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+ if("cancel")
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+
+/datum/tgui_input_number/proc/set_entry(entry)
+ src.entry = entry
+
+/**
+ * Creates an asynchronous TGUI input num window with an associated callback.
+ *
+ * This proc should be used to create inputs that invoke a callback with the user's chosen option.
+ * Arguments:
+ * * user - The user to show the input box to.
+ * * message - The content of the input box, shown in the body of the TGUI window.
+ * * title - The title of the input box, shown on the top of the TGUI window.
+ * * default - The default value pre-populated in the input box.
+ * * callback - The callback to be invoked when a choice is made.
+ * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
+ */
+/proc/tgui_input_number_async(mob/user, message, title, default, datum/callback/callback, timeout = 60 SECONDS)
+ if (istext(user))
+ stack_trace("tgui_input_num_async() received text for user instead of mob")
+ return
+ if (!user)
+ user = usr
+ if (!istype(user))
+ if (istype(user, /client))
+ var/client/client = user
+ user = client.mob
+ else
+ return
+ var/datum/tgui_input_number/async/input = new(user, message, title, default, callback, timeout)
+ input.tgui_interact(user)
+
+/**
+ * # async tgui_text_input
+ *
+ * An asynchronous version of tgui_text_input to be used with callbacks instead of waiting on user responses.
+ */
+/datum/tgui_input_number/async
+ /// The callback to be invoked by the tgui_text_input upon having a choice made.
+ var/datum/callback/callback
+
+/datum/tgui_input_number/async/New(mob/user, message, title, default, callback, timeout)
+ ..(user, title, message, default, timeout)
+ src.callback = callback
+
+/datum/tgui_input_number/async/Destroy(force, ...)
+ QDEL_NULL(callback)
+ . = ..()
+
+/datum/tgui_input_number/async/tgui_close(mob/user)
+ . = ..()
+ qdel(src)
+
+/datum/tgui_input_number/async/set_entry(entry)
+ . = ..()
+ if(!isnull(src.entry))
+ callback?.InvokeAsync(src.entry)
+
+/datum/tgui_input_number/async/wait()
+ return
\ No newline at end of file
diff --git a/code/modules/tgui_input/text.dm b/code/modules/tgui_input/text.dm
new file mode 100644
index 0000000000..42c50d377a
--- /dev/null
+++ b/code/modules/tgui_input/text.dm
@@ -0,0 +1,210 @@
+/**
+ * Creates a TGUI window with a text input. Returns the user's response.
+ *
+ * This proc should be used to create windows for text entry that the caller will wait for a response from.
+ * If tgui fancy chat is turned off: Will return a normal input. If max_length is specified, will return
+ * stripped_multiline_input.
+ *
+ * Arguments:
+ * * user - The user to show the text input to.
+ * * message - The content of the text input, shown in the body of the TGUI window.
+ * * title - The title of the text input modal, shown on the top of the TGUI window.
+ * * default - The default (or current) value, shown as a placeholder.
+ * * max_length - Specifies a max length for input. MAX_MESSAGE_LEN is default (1024)
+ * * multiline - Bool that determines if the input box is much larger. Good for large messages, laws, etc.
+ * * encode - Toggling this determines if input is filtered via html_encode. Setting this to FALSE gives raw input.
+ * * timeout - The timeout of the textbox, after which the modal will close and qdel itself. Set to zero for no timeout.
+ */
+/proc/tgui_input_text(mob/user, message = "", title = "Text Input", default, max_length = MAX_MESSAGE_LEN, multiline = FALSE, encode = TRUE, timeout = 0)
+ if (istext(user))
+ stack_trace("tgui_input_text() received text for user instead of mob")
+ return
+ if (!user)
+ user = usr
+ if (!istype(user))
+ if (istype(user, /client))
+ var/client/client = user
+ user = client.mob
+ else
+ return
+ // Client does NOT have tgui_input on: Returns regular input
+ if(!usr.client.prefs.tgui_input_mode)
+ if(encode)
+ if(multiline)
+ return stripped_multiline_input(user, message, title, default, max_length)
+ else
+ return stripped_input(user, message, title, default, max_length)
+ else
+ if(multiline)
+ return input(user, message, title, default) as message|null
+ else
+ return input(user, message, title, default) as text|null
+ var/datum/tgui_input_text/text_input = new(user, message, title, default, max_length, multiline, encode, timeout)
+ text_input.tgui_interact(user)
+ text_input.wait()
+ if (text_input)
+ . = text_input.entry
+ qdel(text_input)
+
+/**
+ * tgui_input_text
+ *
+ * Datum used for instantiating and using a TGUI-controlled text input that prompts the user with
+ * a message and has an input for text entry.
+ */
+/datum/tgui_input_text
+ /// Boolean field describing if the tgui_input_text was closed by the user.
+ var/closed
+ /// The default (or current) value, shown as a default.
+ var/default
+ /// Whether the input should be stripped using html_encode
+ var/encode
+ /// The entry that the user has return_typed in.
+ var/entry
+ /// The maximum length for text entry
+ var/max_length
+ /// The prompt's body, if any, of the TGUI window.
+ var/message
+ /// Multiline input for larger input boxes.
+ var/multiline
+ /// The time at which the text input was created, for displaying timeout progress.
+ var/start_time
+ /// The lifespan of the text input, after which the window will close and delete itself.
+ var/timeout
+ /// The title of the TGUI window
+ var/title
+
+/datum/tgui_input_text/New(mob/user, message, title, default, max_length, multiline, encode, timeout)
+ src.default = default
+ src.encode = encode
+ src.max_length = max_length
+ src.message = message
+ src.multiline = multiline
+ src.title = title
+ if (timeout)
+ src.timeout = timeout
+ start_time = world.time
+ QDEL_IN(src, timeout)
+
+/datum/tgui_input_text/Destroy(force, ...)
+ SStgui.close_uis(src)
+ . = ..()
+
+/**
+ * Waits for a user's response to the tgui_text_input's prompt before returning. Returns early if
+ * the window was closed by the user.
+ */
+/datum/tgui_input_text/proc/wait()
+ while (!entry && !closed)
+ stoplag(1)
+
+/datum/tgui_input_text/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "TextInputModal")
+ ui.open()
+
+/datum/tgui_input_text/tgui_close(mob/user)
+ . = ..()
+ closed = TRUE
+
+/datum/tgui_input_text/tgui_state(mob/user)
+ return GLOB.tgui_always_state
+
+/datum/tgui_input_text/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["large_buttons"] = usr.client.prefs.tgui_large_buttons
+ data["max_length"] = max_length
+ data["message"] = message
+ data["multiline"] = multiline
+ data["placeholder"] = default // Default is a reserved keyword
+ data["swapped_buttons"] = !usr.client.prefs.tgui_swapped_buttons
+ data["title"] = title
+ return data
+
+/datum/tgui_input_text/tgui_data(mob/user)
+ var/list/data = list()
+ if(timeout)
+ .["timeout"] = clamp((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS), 0, 1)
+ return data
+
+/datum/tgui_input_text/tgui_act(action, list/params)
+ . = ..()
+ if (.)
+ return
+ switch(action)
+ if("submit")
+ if(length(params["entry"]) > max_length)
+ return
+ if(encode && (length(html_encode(params["entry"])) > max_length))
+ to_chat(usr, span_notice("Your message was clipped due to special character usage."))
+ set_entry(params["entry"])
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+ if("cancel")
+ SStgui.close_uis(src)
+ closed = TRUE
+ return TRUE
+
+/datum/tgui_input_text/proc/set_entry(entry)
+ if(!isnull(entry))
+ var/converted_entry = encode ? html_encode(entry) : entry
+ converted_entry = readd_quotes(converted_entry)
+ src.entry = trim(converted_entry, max_length)
+
+/**
+ * Creates an asynchronous TGUI input text window with an associated callback.
+ *
+ * This proc should be used to create inputs that invoke a callback with the user's chosen option.
+ * Arguments:
+ * * user - The user to show the input box to.
+ * * message - The content of the input box, shown in the body of the TGUI window.
+ * * title - The title of the input box, shown on the top of the TGUI window.
+ * * default - The default value pre-populated in the input box.
+ * * callback - The callback to be invoked when a choice is made.
+ * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout.
+ */
+/proc/tgui_input_text_async(mob/user, message, title, default, datum/callback/callback, max_length, multiline, encode, timeout = 60 SECONDS)
+ if (istext(user))
+ stack_trace("tgui_input_text_async() received text for user instead of mob")
+ return
+ if (!user)
+ user = usr
+ if (!istype(user))
+ if (istype(user, /client))
+ var/client/client = user
+ user = client.mob
+ else
+ return
+ var/datum/tgui_input_text/async/input = new(user, message, title, default, callback, max_length, multiline, encode, timeout)
+ input.tgui_interact(user)
+
+/**
+ * # async tgui_text_input
+ *
+ * An asynchronous version of tgui_text_input to be used with callbacks instead of waiting on user responses.
+ */
+/datum/tgui_input_text/async
+ /// The callback to be invoked by the tgui_text_input upon having a choice made.
+ var/datum/callback/callback
+
+/datum/tgui_input_text/async/New(mob/user, message, title, default, callback, max_length, multiline, encode, timeout)
+ ..(user, title, message, default, max_length, multiline, encode, timeout)
+ src.callback = callback
+
+/datum/tgui_input_text/async/Destroy(force, ...)
+ QDEL_NULL(callback)
+ . = ..()
+
+/datum/tgui_input_text/async/tgui_close(mob/user)
+ . = ..()
+ qdel(src)
+
+/datum/tgui_input_text/async/set_entry(entry)
+ . = ..()
+ if(!isnull(src.entry))
+ callback?.InvokeAsync(src.entry)
+
+/datum/tgui_input_text/async/wait()
+ return
diff --git a/code/modules/virus2/admin.dm b/code/modules/virus2/admin.dm
index dd9ae7ead7..5ca289bd2a 100644
--- a/code/modules/virus2/admin.dm
+++ b/code/modules/virus2/admin.dm
@@ -129,12 +129,12 @@
s_multiplier[stage] = max(1, round(initial(E.maxm)/2))
else if(href_list["chance"])
var/datum/disease2/effect/Eff = s[stage]
- var/I = input(usr, "Chance, per tick, of this effect happening (min 0, max [initial(Eff.chance_maxm)])", "Effect Chance", s_chance[stage]) as null|num
+ var/I = tgui_input_number(usr, "Chance, per tick, of this effect happening (min 0, max [initial(Eff.chance_maxm)])", "Effect Chance", s_chance[stage], initial(Eff.chance_maxm), 0)
if(I == null || I < 0 || I > initial(Eff.chance_maxm)) return
s_chance[stage] = I
else if(href_list["multiplier"])
var/datum/disease2/effect/Eff = s[stage]
- var/I = input(usr, "Multiplier for this effect (min 1, max [initial(Eff.maxm)])", "Effect Multiplier", s_multiplier[stage]) as null|num
+ var/I = tgui_input_number(usr, "Multiplier for this effect (min 1, max [initial(Eff.maxm)])", "Effect Multiplier", s_multiplier[stage], initial(Eff.maxm), 1)
if(I == null || I < 1 || I > initial(Eff.maxm)) return
s_multiplier[stage] = I
if("species")
@@ -150,7 +150,7 @@
if(!infectee.species || !(infectee.species.get_bodytype() in species))
infectee = null
if("ichance")
- var/I = input(usr, "Input infection chance", "Infection Chance", infectionchance) as null|num
+ var/I = tgui_input_number(usr, "Input infection chance", "Infection Chance", infectionchance)
if(!I) return
infectionchance = I
if("stype")
@@ -158,7 +158,7 @@
if(!S) return
spreadtype = S
if("speed")
- var/S = input(usr, "Input speed", "Speed", speed) as null|num
+ var/S = tgui_input_number(usr, "Input speed", "Speed", speed)
if(!S) return
speed = S
if("antigen")
@@ -172,7 +172,7 @@
else if(href_list["reset"])
antigens = list()
if("resistance")
- var/S = input(usr, "Input % resistance to antibiotics", "Resistance", resistance) as null|num
+ var/S = tgui_input_number(usr, "Input % resistance to antibiotics", "Resistance", resistance)
if(!S) return
resistance = S
if("infectee")
diff --git a/code/modules/vore/eating/bellymodes_vr.dm b/code/modules/vore/eating/bellymodes_vr.dm
index be63fdffdc..b3cdbabaf7 100644
--- a/code/modules/vore/eating/bellymodes_vr.dm
+++ b/code/modules/vore/eating/bellymodes_vr.dm
@@ -157,7 +157,8 @@
formatted_message = replacetext(formatted_message, "%pred", owner)
formatted_message = replacetext(formatted_message, "%prey", M)
formatted_message = replacetext(formatted_message, "%countprey", absorbed_count)
- to_chat(M, "[formatted_message] ")
+ if(formatted_message)
+ to_chat(M, "[formatted_message] ")
else
if(digest_mode == DM_DIGEST && !M.digestable)
EL = emote_lists[DM_HOLD] // Use Hold's emote list if we're indigestible
@@ -169,7 +170,8 @@
formatted_message = replacetext(formatted_message, "%prey", M)
formatted_message = replacetext(formatted_message, "%countprey", living_count)
formatted_message = replacetext(formatted_message, "%count", contents.len)
- to_chat(M, "[formatted_message] ")
+ if(formatted_message)
+ to_chat(M, "[formatted_message] ")
if(to_update)
updateVRPanels()
diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm
index edd0e6979d..7fffe45bb0 100644
--- a/code/modules/vore/eating/vorepanel_vr.dm
+++ b/code/modules/vore/eating/vorepanel_vr.dm
@@ -349,7 +349,7 @@
if(host.vore_organs.len >= BELLIES_MAX)
return FALSE
- var/new_name = html_encode(input(usr,"New belly's name:","New Belly") as text|null)
+ var/new_name = html_encode(tgui_input_text(usr,"New belly's name:","New Belly"))
var/failure_msg
if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN)
@@ -412,7 +412,7 @@
unsaved_changes = FALSE
return TRUE
if("setflavor")
- var/new_flavor = html_encode(input(usr,"What your character tastes like (400ch limit). This text will be printed to the pred after 'X tastes of...' so just put something like 'strawberries and cream':","Character Flavor",host.vore_taste) as text|null)
+ var/new_flavor = html_encode(tgui_input_text(usr,"What your character tastes like (400ch limit). This text will be printed to the pred after 'X tastes of...' so just put something like 'strawberries and cream':","Character Flavor",host.vore_taste))
if(!new_flavor)
return FALSE
@@ -424,7 +424,7 @@
unsaved_changes = TRUE
return TRUE
if("setsmell")
- var/new_smell = html_encode(input(usr,"What your character smells like (400ch limit). This text will be printed to the pred after 'X smells of...' so just put something like 'strawberries and cream':","Character Smell",host.vore_smell) as text|null)
+ var/new_smell = html_encode(tgui_input_text(usr,"What your character smells like (400ch limit). This text will be printed to the pred after 'X smells of...' so just put something like 'strawberries and cream':","Character Smell",host.vore_smell))
if(!new_smell)
return FALSE
@@ -750,7 +750,7 @@
var/attr = params["attribute"]
switch(attr)
if("b_name")
- var/new_name = html_encode(input(usr,"Belly's new name:","New Name") as text|null)
+ var/new_name = html_encode(tgui_input_text(usr,"Belly's new name:","New Name"))
var/failure_msg
if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN)
@@ -827,7 +827,7 @@
host.vore_selected.egg_type = new_egg_type
. = TRUE
if("b_desc")
- var/new_desc = html_encode(input(usr,"Belly Description, '%pred' will be replaced with your name. '%prey' will be replaced with the prey's name. '%belly' will be replaced with your belly's name. ([BELLIES_DESC_MAX] char limit):","New Description",host.vore_selected.desc) as message|null)
+ var/new_desc = html_encode(tgui_input_text(usr,"Belly Description, '%pred' will be replaced with your name. '%prey' will be replaced with the prey's name. '%belly' will be replaced with your belly's name. ([BELLIES_DESC_MAX] char limit):","New Description",host.vore_selected.desc, multiline = TRUE))
if(new_desc)
new_desc = readd_quotes(new_desc)
@@ -837,7 +837,7 @@
host.vore_selected.desc = new_desc
. = TRUE
if("b_absorbed_desc")
- var/new_desc = html_encode(input(usr,"Belly Description for absorbed prey, '%pred' will be replaced with your name. '%prey' will be replaced with the prey's name. '%belly' will be replaced with your belly's name. ([BELLIES_DESC_MAX] char limit):","New Description",host.vore_selected.absorbed_desc) as message|null)
+ var/new_desc = html_encode(tgui_input_text(usr,"Belly Description for absorbed prey, '%pred' will be replaced with your name. '%prey' will be replaced with the prey's name. '%belly' will be replaced with your belly's name. ([BELLIES_DESC_MAX] char limit):","New Description",host.vore_selected.absorbed_desc, multiline = TRUE))
if(new_desc)
new_desc = readd_quotes(new_desc)
@@ -851,117 +851,117 @@
var/help = " Press enter twice to separate messages. '%pred' will be replaced with your name. '%prey' will be replaced with the prey's name. '%belly' will be replaced with your belly's name. '%count' will be replaced with the number of anything in your belly. '%countprey' will be replaced with the number of living prey in your belly."
switch(params["msgtype"])
if("dmp")
- var/new_message = input(user,"These are sent to prey when they expire. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Digest Message (to prey)",host.vore_selected.get_messages("dmp")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey when they expire. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Digest Message (to prey)",host.vore_selected.get_messages("dmp"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"dmp")
if("dmo")
- var/new_message = input(user,"These are sent to you when prey expires in you. Write them in 2nd person ('you feel X'). Avoid using %pred in this type."+help,"Digest Message (to you)",host.vore_selected.get_messages("dmo")) as message
+ var/new_message = tgui_input_text(user,"These are sent to you when prey expires in you. Write them in 2nd person ('you feel X'). Avoid using %pred in this type."+help,"Digest Message (to you)",host.vore_selected.get_messages("dmo"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"dmo")
if("amp")
- var/new_message = input(user,"These are sent to prey when their absorption finishes. Write them in 2nd person ('you feel X'). Avoid using %prey in this type. %count will not work for this type, and %countprey will only count absorbed victims."+help,"Digest Message (to prey)",host.vore_selected.get_messages("amp")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey when their absorption finishes. Write them in 2nd person ('you feel X'). Avoid using %prey in this type. %count will not work for this type, and %countprey will only count absorbed victims."+help,"Digest Message (to prey)",host.vore_selected.get_messages("amp"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"amp")
if("amo")
- var/new_message = input(user,"These are sent to you when prey's absorption finishes. Write them in 2nd person ('you feel X'). Avoid using %pred in this type. %count will not work for this type, and %countprey will only count absorbed victims."+help,"Digest Message (to you)",host.vore_selected.get_messages("amo")) as message
+ var/new_message = tgui_input_text(user,"These are sent to you when prey's absorption finishes. Write them in 2nd person ('you feel X'). Avoid using %pred in this type. %count will not work for this type, and %countprey will only count absorbed victims."+help,"Digest Message (to you)",host.vore_selected.get_messages("amo"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"amo")
if("uamp")
- var/new_message = input(user,"These are sent to prey when their unnabsorption finishes. Write them in 2nd person ('you feel X'). Avoid using %prey in this type. %count will not work for this type, and %countprey will only count absorbed victims."+help,"Digest Message (to prey)",host.vore_selected.get_messages("uamp")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey when their unnabsorption finishes. Write them in 2nd person ('you feel X'). Avoid using %prey in this type. %count will not work for this type, and %countprey will only count absorbed victims."+help,"Digest Message (to prey)",host.vore_selected.get_messages("uamp"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"uamp")
if("uamo")
- var/new_message = input(user,"These are sent to you when prey's unabsorption finishes. Write them in 2nd person ('you feel X'). Avoid using %pred in this type. %count will not work for this type, and %countprey will only count absorbed victims."+help,"Digest Message (to you)",host.vore_selected.get_messages("uamo")) as message
+ var/new_message = tgui_input_text(user,"These are sent to you when prey's unabsorption finishes. Write them in 2nd person ('you feel X'). Avoid using %pred in this type. %count will not work for this type, and %countprey will only count absorbed victims."+help,"Digest Message (to you)",host.vore_selected.get_messages("uamo"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"uamo")
if("smo")
- var/new_message = input(user,"These are sent to those nearby when prey struggles. Write them in 3rd person ('X's Y bulges')."+help,"Struggle Message (outside)",host.vore_selected.get_messages("smo")) as message
+ var/new_message = tgui_input_text(user,"These are sent to those nearby when prey struggles. Write them in 3rd person ('X's Y bulges')."+help,"Struggle Message (outside)",host.vore_selected.get_messages("smo"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"smo")
if("smi")
- var/new_message = input(user,"These are sent to prey when they struggle. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Struggle Message (inside)",host.vore_selected.get_messages("smi")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey when they struggle. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Struggle Message (inside)",host.vore_selected.get_messages("smi"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"smi")
if("asmo")
- var/new_message = input(user,"These are sent to those nearby when absorbed prey struggles. Write them in 3rd person ('X's Y bulges'). %count will not work for this type, and %countprey will only count absorbed victims."+help,"Struggle Message (outside)",host.vore_selected.get_messages("asmo")) as message
+ var/new_message = tgui_input_text(user,"These are sent to those nearby when absorbed prey struggles. Write them in 3rd person ('X's Y bulges'). %count will not work for this type, and %countprey will only count absorbed victims."+help,"Struggle Message (outside)",host.vore_selected.get_messages("asmo"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"asmo")
if("asmi")
- var/new_message = input(user,"These are sent to absorbed prey when they struggle. Write them in 2nd person ('you feel X'). Avoid using %prey in this type. %count will not work for this type, and %countprey will only count absorbed victims."+help,"Struggle Message (inside)",host.vore_selected.get_messages("asmi")) as message
+ var/new_message = tgui_input_text(user,"These are sent to absorbed prey when they struggle. Write them in 2nd person ('you feel X'). Avoid using %prey in this type. %count will not work for this type, and %countprey will only count absorbed victims."+help,"Struggle Message (inside)",host.vore_selected.get_messages("asmi"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"asmi")
if("em")
- var/new_message = input(user,"These are sent to people who examine you when this belly has contents. Write them in 3rd person ('Their %belly is bulging')."+help,"Examine Message (when full)",host.vore_selected.get_messages("em")) as message
+ var/new_message = tgui_input_text(user,"These are sent to people who examine you when this belly has contents. Write them in 3rd person ('Their %belly is bulging')."+help,"Examine Message (when full)",host.vore_selected.get_messages("em"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"em")
if("ema")
- var/new_message = input(user,"These are sent to people who examine you when this belly has absorbed victims. Write them in 3rd person ('Their %belly is larger'). %count will not work for this type, and %countprey will only count absorbed victims."+help,"Examine Message (with absorbed victims)",host.vore_selected.get_messages("ema")) as message
+ var/new_message = tgui_input_text(user,"These are sent to people who examine you when this belly has absorbed victims. Write them in 3rd person ('Their %belly is larger'). %count will not work for this type, and %countprey will only count absorbed victims."+help,"Examine Message (with absorbed victims)",host.vore_selected.get_messages("ema"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"ema")
if("im_digest")
- var/new_message = input(user,"These are sent to prey every minute when you are on Digest mode. Write them in 2nd person ('%pred's %belly squishes down on you.')."+help,"Idle Message (Digest)",host.vore_selected.get_messages("im_digest")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey every minute when you are on Digest mode. Write them in 2nd person ('%pred's %belly squishes down on you.')."+help,"Idle Message (Digest)",host.vore_selected.get_messages("im_digest"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"im_digest")
if("im_hold")
- var/new_message = input(user,"These are sent to prey every minute when you are on Hold mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Hold)",host.vore_selected.get_messages("im_hold")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey every minute when you are on Hold mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Hold)",host.vore_selected.get_messages("im_hold"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"im_hold")
if("im_holdabsorbed")
- var/new_message = input(user,"These are sent to prey every minute when you are absorbed. Write them in 2nd person ('%pred's %belly squishes down on you.') %count will not work for this type, and %countprey will only count absorbed victims."+help,"Idle Message (Hold Absorbed)",host.vore_selected.get_messages("im_holdabsorbed")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey every minute when you are absorbed. Write them in 2nd person ('%pred's %belly squishes down on you.') %count will not work for this type, and %countprey will only count absorbed victims."+help,"Idle Message (Hold Absorbed)",host.vore_selected.get_messages("im_holdabsorbed"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"im_holdabsorbed")
if("im_absorb")
- var/new_message = input(user,"These are sent to prey every minute when you are on Absorb mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Absorb)",host.vore_selected.get_messages("im_absorb")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey every minute when you are on Absorb mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Absorb)",host.vore_selected.get_messages("im_absorb"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"im_absorb")
if("im_heal")
- var/new_message = input(user,"These are sent to prey every minute when you are on Heal mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Heal)",host.vore_selected.get_messages("im_heal")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey every minute when you are on Heal mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Heal)",host.vore_selected.get_messages("im_heal"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"im_heal")
if("im_drain")
- var/new_message = input(user,"These are sent to prey every minute when you are on Drain mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Drain)",host.vore_selected.get_messages("im_drain")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey every minute when you are on Drain mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Drain)",host.vore_selected.get_messages("im_drain"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"im_drain")
if("im_steal")
- var/new_message = input(user,"These are sent to prey every minute when you are on Size Steal mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Size Steal)",host.vore_selected.get_messages("im_steal")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey every minute when you are on Size Steal mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Size Steal)",host.vore_selected.get_messages("im_steal"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"im_steal")
if("im_egg")
- var/new_message = input(user,"These are sent to prey every minute when you are on Encase In Egg mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Encase In Egg)",host.vore_selected.get_messages("im_egg")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey every minute when you are on Encase In Egg mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Encase In Egg)",host.vore_selected.get_messages("im_egg"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"im_egg")
if("im_shrink")
- var/new_message = input(user,"These are sent to prey every minute when you are on Shrink mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Shrink)",host.vore_selected.get_messages("im_shrink")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey every minute when you are on Shrink mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Shrink)",host.vore_selected.get_messages("im_shrink"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"im_shrink")
if("im_grow")
- var/new_message = input(user,"These are sent to prey every minute when you are on Grow mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Grow)",host.vore_selected.get_messages("im_grow")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey every minute when you are on Grow mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Grow)",host.vore_selected.get_messages("im_grow"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"im_grow")
if("im_unabsorb")
- var/new_message = input(user,"These are sent to prey every minute when you are on Unabsorb mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Unabsorb)",host.vore_selected.get_messages("im_unabsorb")) as message
+ var/new_message = tgui_input_text(user,"These are sent to prey every minute when you are on Unabsorb mode. Write them in 2nd person ('%pred's %belly squishes down on you.')"+help,"Idle Message (Unabsorb)",host.vore_selected.get_messages("im_unabsorb"), multiline = TRUE)
if(new_message)
host.vore_selected.set_messages(new_message,"im_unabsorb")
@@ -983,7 +983,7 @@
host.vore_selected.emote_lists = initial(host.vore_selected.emote_lists)
. = TRUE
if("b_verb")
- var/new_verb = html_encode(input(usr,"New verb when eating (infinitive tense, e.g. nom or swallow):","New Verb") as text|null)
+ var/new_verb = html_encode(tgui_input_text(usr,"New verb when eating (infinitive tense, e.g. nom or swallow):","New Verb"))
if(length(new_verb) > BELLIES_NAME_MAX || length(new_verb) < BELLIES_NAME_MIN)
tgui_alert_async(usr, "Entered verb length invalid (must be longer than [BELLIES_NAME_MIN], no longer than [BELLIES_NAME_MAX]).","Error")
@@ -1044,7 +1044,7 @@
host.vore_selected.can_taste = !host.vore_selected.can_taste
. = TRUE
if("b_bulge_size")
- var/new_bulge = input(user, "Choose the required size prey must be to show up on examine, ranging from 25% to 200% Set this to 0 for no text on examine.", "Set Belly Examine Size.") as num|null
+ var/new_bulge = tgui_input_number(user, "Choose the required size prey must be to show up on examine, ranging from 25% to 200% Set this to 0 for no text on examine.", "Set Belly Examine Size.", max_value = 200, min_value = 25)
if(new_bulge == null)
return FALSE
if(new_bulge == 0) //Disable.
@@ -1060,7 +1060,7 @@
host.vore_selected.display_absorbed_examine = !host.vore_selected.display_absorbed_examine
. = TRUE
if("b_grow_shrink")
- var/new_grow = input(user, "Choose the size that prey will be grown/shrunk to, ranging from 25% to 200%", "Set Growth Shrink Size.", host.vore_selected.shrink_grow_size) as num|null
+ var/new_grow = tgui_input_number(user, "Choose the size that prey will be grown/shrunk to, ranging from 25% to 200%", "Set Growth Shrink Size.", host.vore_selected.shrink_grow_size, 200, 25)
if (new_grow == null)
return FALSE
if (!ISINRANGE(new_grow,25,200))
@@ -1070,28 +1070,28 @@
host.vore_selected.shrink_grow_size = (new_grow*0.01)
. = TRUE
if("b_nutritionpercent")
- var/new_nutrition = input(user, "Choose the nutrition gain percentage you will recieve per tick from prey. Ranges from 0.01 to 100.", "Set Nutrition Gain Percentage.", host.vore_selected.nutrition_percent) as num|null
+ var/new_nutrition = tgui_input_number(user, "Choose the nutrition gain percentage you will recieve per tick from prey. Ranges from 0.01 to 100.", "Set Nutrition Gain Percentage.", host.vore_selected.nutrition_percent, 100, 0.01)
if(new_nutrition == null)
return FALSE
var/new_new_nutrition = CLAMP(new_nutrition, 0.01, 100)
host.vore_selected.nutrition_percent = new_new_nutrition
. = TRUE
if("b_burn_dmg")
- var/new_damage = input(user, "Choose the amount of burn damage prey will take per tick. Ranges from 0 to 6.", "Set Belly Burn Damage.", host.vore_selected.digest_burn) as num|null
+ var/new_damage = tgui_input_number(user, "Choose the amount of burn damage prey will take per tick. Ranges from 0 to 6.", "Set Belly Burn Damage.", host.vore_selected.digest_burn, 6, 0)
if(new_damage == null)
return FALSE
var/new_new_damage = CLAMP(new_damage, 0, 6)
host.vore_selected.digest_burn = new_new_damage
. = TRUE
if("b_brute_dmg")
- var/new_damage = input(user, "Choose the amount of brute damage prey will take per tick. Ranges from 0 to 6", "Set Belly Brute Damage.", host.vore_selected.digest_brute) as num|null
+ var/new_damage = tgui_input_number(user, "Choose the amount of brute damage prey will take per tick. Ranges from 0 to 6", "Set Belly Brute Damage.", host.vore_selected.digest_brute, 6, 0)
if(new_damage == null)
return FALSE
var/new_new_damage = CLAMP(new_damage, 0, 6)
host.vore_selected.digest_brute = new_new_damage
. = TRUE
if("b_oxy_dmg")
- var/new_damage = input(user, "Choose the amount of suffocation damage prey will take per tick. Ranges from 0 to 12.", "Set Belly Suffocation Damage.", host.vore_selected.digest_oxy) as num|null
+ var/new_damage = tgui_input_number(user, "Choose the amount of suffocation damage prey will take per tick. Ranges from 0 to 12.", "Set Belly Suffocation Damage.", host.vore_selected.digest_oxy, 12, 0)
if(new_damage == null)
return FALSE
var/new_new_damage = CLAMP(new_damage, 0, 12)
@@ -1101,7 +1101,7 @@
host.vore_selected.emote_active = !host.vore_selected.emote_active
. = TRUE
if("b_emotetime")
- var/new_time = input(user, "Choose the period it takes for idle belly emotes to be shown to prey. Measured in seconds, Minimum 1 minute, Maximum 10 minutes.", "Set Belly Emote Delay.", host.vore_selected.digest_brute)
+ var/new_time = tgui_input_number(user, "Choose the period it takes for idle belly emotes to be shown to prey. Measured in seconds, Minimum 1 minute, Maximum 10 minutes.", "Set Belly Emote Delay.", host.vore_selected.digest_brute, 10, 1)
if(new_time == null)
return FALSE
var/new_new_time = CLAMP(new_time, 60, 600)
@@ -1119,17 +1119,17 @@
host.vore_selected.escapable = 0
. = TRUE
if("b_escapechance")
- var/escape_chance_input = input(user, "Set prey escape chance on resist (as %)", "Prey Escape Chance") as num|null
+ var/escape_chance_input = tgui_input_number(user, "Set prey escape chance on resist (as %)", "Prey Escape Chance", null, 100, 0)
if(!isnull(escape_chance_input)) //These have to be 'null' because both cancel and 0 are valid, separate options
host.vore_selected.escapechance = sanitize_integer(escape_chance_input, 0, 100, initial(host.vore_selected.escapechance))
. = TRUE
if("b_escapetime")
- var/escape_time_input = input(user, "Set number of seconds for prey to escape on resist (1-60)", "Prey Escape Time") as num|null
+ var/escape_time_input = tgui_input_number(user, "Set number of seconds for prey to escape on resist (1-60)", "Prey Escape Time", null, 60, 1)
if(!isnull(escape_time_input))
host.vore_selected.escapetime = sanitize_integer(escape_time_input*10, 10, 600, initial(host.vore_selected.escapetime))
. = TRUE
if("b_transferchance")
- var/transfer_chance_input = input(user, "Set belly transfer chance on resist (as %). You must also set the location for this to have any effect.", "Prey Escape Time") as num|null
+ var/transfer_chance_input = tgui_input_number(user, "Set belly transfer chance on resist (as %). You must also set the location for this to have any effect.", "Prey Escape Time", null, 100, 0)
if(!isnull(transfer_chance_input))
host.vore_selected.transferchance = sanitize_integer(transfer_chance_input, 0, 100, initial(host.vore_selected.transferchance))
. = TRUE
@@ -1143,7 +1143,7 @@
host.vore_selected.transferlocation = choice.name
. = TRUE
if("b_transferchance_secondary")
- var/transfer_secondary_chance_input = input(user, "Set secondary belly transfer chance on resist (as %). You must also set the location for this to have any effect.", "Prey Escape Time") as num|null
+ var/transfer_secondary_chance_input = tgui_input_number(user, "Set secondary belly transfer chance on resist (as %). You must also set the location for this to have any effect.", "Prey Escape Time", null, 100, 0)
if(!isnull(transfer_secondary_chance_input))
host.vore_selected.transferchance_secondary = sanitize_integer(transfer_secondary_chance_input, 0, 100, initial(host.vore_selected.transferchance_secondary))
. = TRUE
@@ -1158,12 +1158,12 @@
host.vore_selected.transferlocation_secondary = choice_secondary.name
. = TRUE
if("b_absorbchance")
- var/absorb_chance_input = input(user, "Set belly absorb mode chance on resist (as %)", "Prey Absorb Chance") as num|null
+ var/absorb_chance_input = tgui_input_number(user, "Set belly absorb mode chance on resist (as %)", "Prey Absorb Chance", null, 100, 0)
if(!isnull(absorb_chance_input))
host.vore_selected.absorbchance = sanitize_integer(absorb_chance_input, 0, 100, initial(host.vore_selected.absorbchance))
. = TRUE
if("b_digestchance")
- var/digest_chance_input = input(user, "Set belly digest mode chance on resist (as %)", "Prey Digest Chance") as num|null
+ var/digest_chance_input = tgui_input_number(user, "Set belly digest mode chance on resist (as %)", "Prey Digest Chance", null, 100, 0)
if(!isnull(digest_chance_input))
host.vore_selected.digestchance = sanitize_integer(digest_chance_input, 0, 100, initial(host.vore_selected.digestchance))
. = TRUE
diff --git a/code/modules/vore/resizing/resize_vr.dm b/code/modules/vore/resizing/resize_vr.dm
index 85ad11d4bf..759fe3046f 100644
--- a/code/modules/vore/resizing/resize_vr.dm
+++ b/code/modules/vore/resizing/resize_vr.dm
@@ -130,7 +130,6 @@
* Ace was here! Redid this a little so we'd use math for shrinking characters. This is the old code.
*/
-
/mob/living/proc/set_size()
set name = "Adjust Mass"
set category = "Abilities" //Seeing as prometheans have an IC reason to be changing mass.
@@ -140,7 +139,8 @@
return
var/nagmessage = "Adjust your mass to be a size between 25 to 200% (or 1% to 600% in dormitories). (DO NOT ABUSE)"
- var/new_size = input(nagmessage, "Pick a Size") as num|null
+ var/default = size_multiplier * 100
+ var/new_size = tgui_input_number(usr, nagmessage, "Pick a Size", default)
if(size_range_check(new_size))
resize(new_size/100, uncapped = has_large_resize_bounds(), ignore_prefs = TRUE)
if(temporary_form) //CHOMPEdit - resizing both our forms
diff --git a/code/modules/vore/resizing/sizegun_vr.dm b/code/modules/vore/resizing/sizegun_vr.dm
index b56f827340..d932351db1 100644
--- a/code/modules/vore/resizing/sizegun_vr.dm
+++ b/code/modules/vore/resizing/sizegun_vr.dm
@@ -41,7 +41,7 @@
set category = "Object"
set src in view(1)
- var/size_select = input(usr, "Put the desired size (25-200%), (1-600%) in dormitory areas.", "Set Size", size_set_to * 100) as num|null
+ var/size_select = tgui_input_number(usr, "Put the desired size (25-200%), (1-600%) in dormitory areas.", "Set Size", size_set_to * 100, 600, 1)
if(!size_select)
return //cancelled
//We do valid resize testing in actual firings because people move after setting these things.
@@ -115,7 +115,7 @@
set category = "Object"
set src in view(1)
- var/size_select = input(usr, "Put the desired size (1-600%)", "Set Size", size_set_to * 100) as num|null
+ var/size_select = tgui_input_number(usr, "Put the desired size (1-600%)", "Set Size", size_set_to * 100, 600, 1)
if(!size_select)
return //cancelled
size_set_to = clamp((size_select/100), 0, 1000) //eheh
diff --git a/code/modules/xenoarcheaology/finds/fossils.dm b/code/modules/xenoarcheaology/finds/fossils.dm
index 5448db97f2..19516f47cc 100644
--- a/code/modules/xenoarcheaology/finds/fossils.dm
+++ b/code/modules/xenoarcheaology/finds/fossils.dm
@@ -76,7 +76,7 @@
else
..()
else if(istype(W,/obj/item/weapon/pen))
- plaque_contents = sanitize(input(usr, "What would you like to write on the plaque:","Skeleton plaque",""))
+ plaque_contents = sanitize(tgui_input_text(usr, "What would you like to write on the plaque:","Skeleton plaque",""))
user.visible_message("[user] writes something on the base of [src].","You relabel the plaque on the base of [bicon(src)] [src].")
if(src.contents.Find(/obj/item/weapon/fossil/skull/horned))
src.desc = "A creature made of [src.contents.len-1] assorted bones and a horned skull. The plaque reads \'[plaque_contents]\'."
diff --git a/code/modules/xenoarcheaology/tools/tools_pickaxe.dm b/code/modules/xenoarcheaology/tools/tools_pickaxe.dm
index ddca8b6fe5..c7015c2c29 100644
--- a/code/modules/xenoarcheaology/tools/tools_pickaxe.dm
+++ b/code/modules/xenoarcheaology/tools/tools_pickaxe.dm
@@ -174,7 +174,7 @@
attack_verb = list("drilled")
/obj/item/weapon/pickaxe/excavationdrill/attack_self(mob/user as mob)
- var/depth = input(usr, "Put the desired depth (1-30 centimeters).", "Set Depth", 30) as num
+ var/depth = tgui_input_number(usr, "Put the desired depth (1-30 centimeters).", "Set Depth", 30, 30, 1)
if(depth>30 || depth<1)
to_chat(user, "Invalid depth. ")
return
diff --git a/code/modules/xenobio2/mob/slime/slime_monkey.dm b/code/modules/xenobio2/mob/slime/slime_monkey.dm
index 39660b1728..8915e58e9b 100644
--- a/code/modules/xenobio2/mob/slime/slime_monkey.dm
+++ b/code/modules/xenobio2/mob/slime/slime_monkey.dm
@@ -44,7 +44,7 @@ Slime cube lives here.
S.shapeshifter_set_colour("#05FF9B")
for(var/mob/M in viewers(get_turf_or_move(loc)))
M.show_message("The monkey cube suddenly takes the shape of a humanoid! ")
- var/newname = sanitize(input(S, "You are a Promethean. Would you like to change your name to something else?", "Name change") as null|text, MAX_NAME_LEN)
+ var/newname = sanitize(tgui_input_text(S, "You are a Promethean. Would you like to change your name to something else?", "Name change"), MAX_NAME_LEN)
if(newname)
S.real_name = newname
S.name = S.real_name
diff --git a/icons/inventory/head/mob_vox.dmi b/icons/inventory/head/mob_vox.dmi
index 346ea9b7d1..f1570089c8 100644
Binary files a/icons/inventory/head/mob_vox.dmi and b/icons/inventory/head/mob_vox.dmi differ
diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi
index def737dcc3..2cb35cf667 100644
Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ
diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi
new file mode 100644
index 0000000000..3e8d60a579
Binary files /dev/null and b/icons/mob/head.dmi differ
diff --git a/icons/mob/species/teshari/head.dmi b/icons/mob/species/teshari/head.dmi
index 0bb0637c4e..6cf6b2ac7d 100644
Binary files a/icons/mob/species/teshari/head.dmi and b/icons/mob/species/teshari/head.dmi differ
diff --git a/icons/mob/vore/ears_vr.dmi b/icons/mob/vore/ears_vr.dmi
index 032ba8e4fa..4bde02b8bf 100644
Binary files a/icons/mob/vore/ears_vr.dmi and b/icons/mob/vore/ears_vr.dmi differ
diff --git a/icons/mob/vore/taurs_vr.dmi b/icons/mob/vore/taurs_vr.dmi
index 4466b9a466..20ddaee2a4 100644
Binary files a/icons/mob/vore/taurs_vr.dmi and b/icons/mob/vore/taurs_vr.dmi differ
diff --git a/icons/obj/cooking_machines.dmi b/icons/obj/cooking_machines.dmi
index 4009cb6224..b8c3ea6f5d 100644
Binary files a/icons/obj/cooking_machines.dmi and b/icons/obj/cooking_machines.dmi differ
diff --git a/icons/obj/food.dmi b/icons/obj/food.dmi
index 915c170551..f1637aca4b 100644
Binary files a/icons/obj/food.dmi and b/icons/obj/food.dmi differ
diff --git a/icons/obj/stacks.dmi b/icons/obj/stacks.dmi
index 6a70a2982d..b08f3b086a 100644
Binary files a/icons/obj/stacks.dmi and b/icons/obj/stacks.dmi differ
diff --git a/icons/turf/flooring/carpet.dmi b/icons/turf/flooring/carpet.dmi
index f668ae8943..f7dcf3c046 100644
Binary files a/icons/turf/flooring/carpet.dmi and b/icons/turf/flooring/carpet.dmi differ
diff --git a/icons/turf/flooring/wood.dmi b/icons/turf/flooring/wood.dmi
index eb91333673..afec20c851 100644
Binary files a/icons/turf/flooring/wood.dmi and b/icons/turf/flooring/wood.dmi differ
diff --git a/icons/turf/flooring/wood_vr.dmi b/icons/turf/flooring/wood_vr.dmi
index d6024f3de0..f072969e54 100644
Binary files a/icons/turf/flooring/wood_vr.dmi and b/icons/turf/flooring/wood_vr.dmi differ
diff --git a/maps/expedition_vr/beach/beach.dmm b/maps/expedition_vr/beach/beach.dmm
index c8eda05079..1e6f8a8769 100644
--- a/maps/expedition_vr/beach/beach.dmm
+++ b/maps/expedition_vr/beach/beach.dmm
@@ -863,6 +863,10 @@
"nD" = (
/turf/simulated/mineral/ignore_mapgen,
/area/tether_away/beach/water)
+"nP" = (
+/mob/living/simple_mob/animal/passive/fennec/faux,
+/turf/simulated/floor/wood,
+/area/tether_away/beach/resort/kitchen)
"of" = (
/obj/structure/table/woodentable,
/obj/random/drinkbottle,
@@ -1316,16 +1320,16 @@
/turf/simulated/floor/tiled/asteroid_steel,
/area/tether_away/beach/resort/kitchen)
"Bk" = (
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/obj/item/weapon/reagent_containers/food/condiment/small/sugar,
/obj/item/weapon/reagent_containers/food/condiment/small/sugar,
/obj/item/weapon/reagent_containers/food/condiment/small/sugar,
@@ -1334,10 +1338,10 @@
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/obj/item/weapon/material/knife/butch,
/obj/item/weapon/material/minihoe,
/obj/item/weapon/material/knife/machete/hatchet,
@@ -11025,7 +11029,7 @@ ad
ad
ad
ad
-ad
+nP
ad
ad
ad
diff --git a/maps/gateway_vr/eggnogtown.dmm b/maps/gateway_vr/eggnogtown.dmm
index 89e98be639..2b394135f0 100644
--- a/maps/gateway_vr/eggnogtown.dmm
+++ b/maps/gateway_vr/eggnogtown.dmm
@@ -1000,16 +1000,16 @@
/turf/simulated/floor/tiled,
/area/gateway/eggnogtown/loniabode)
"gk" = (
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/obj/item/weapon/reagent_containers/food/condiment/small/sugar,
/obj/item/weapon/reagent_containers/food/condiment/small/sugar,
/obj/item/weapon/reagent_containers/food/condiment/small/sugar,
@@ -1018,10 +1018,10 @@
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/obj/structure/closet,
/obj/item/weapon/reagent_containers/food/condiment/small/saltshaker,
/obj/item/weapon/reagent_containers/food/condiment/small/peppermill,
@@ -3649,7 +3649,7 @@
"Rd" = (
/obj/structure/table/marble,
/obj/item/weapon/material/kitchen/rollingpin,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/obj/item/weapon/reagent_containers/food/condiment/yeast,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/yeast,
diff --git a/maps/gateway_vr/variable/arynthilake_a.dmm b/maps/gateway_vr/variable/arynthilake_a.dmm
index 7b0f1d4cc6..8f08ec212b 100644
--- a/maps/gateway_vr/variable/arynthilake_a.dmm
+++ b/maps/gateway_vr/variable/arynthilake_a.dmm
@@ -1143,16 +1143,16 @@
/obj/random/meat,
/obj/random/meat,
/obj/random/meat,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
diff --git a/maps/gateway_vr/variable/arynthilake_b.dmm b/maps/gateway_vr/variable/arynthilake_b.dmm
index 712b51a588..5538a63096 100644
--- a/maps/gateway_vr/variable/arynthilake_b.dmm
+++ b/maps/gateway_vr/variable/arynthilake_b.dmm
@@ -157,16 +157,16 @@
/obj/random/meat,
/obj/random/meat,
/obj/random/meat,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
diff --git a/maps/gateway_vr/wildwest.dmm b/maps/gateway_vr/wildwest.dmm
index 2a42bcc14f..12a79c33ec 100644
--- a/maps/gateway_vr/wildwest.dmm
+++ b/maps/gateway_vr/wildwest.dmm
@@ -676,9 +676,9 @@
/area/awaymission/wwmines)
"fb" = (
/obj/structure/closet/secure_closet/freezer/kitchen,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/turf/simulated/floor/tiled/eris/cafe,
/area/awaymission/wwmines)
"fc" = (
diff --git a/maps/groundbase/gb-z2.dmm b/maps/groundbase/gb-z2.dmm
index 0efdbb2a7a..40f2faadc9 100644
--- a/maps/groundbase/gb-z2.dmm
+++ b/maps/groundbase/gb-z2.dmm
@@ -471,7 +471,7 @@
/turf/simulated/floor/lino,
/area/groundbase/civilian/chapel/office)
"bl" = (
-/turf/simulated/floor/carpet/deco,
+/turf/simulated/floor/carpet/geo,
/area/groundbase/civilian/kitchen/backroom)
"bn" = (
/obj/machinery/door/airlock{
@@ -2359,7 +2359,7 @@
/obj/machinery/light{
dir = 4
},
-/turf/simulated/floor/carpet/deco,
+/turf/simulated/floor/carpet/geo,
/area/groundbase/civilian/kitchen/backroom)
"gZ" = (
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
@@ -2455,7 +2455,7 @@
/obj/structure/bed/chair/sofa/black,
/obj/machinery/alarm,
/obj/effect/landmark/start/chef,
-/turf/simulated/floor/carpet/deco,
+/turf/simulated/floor/carpet/geo,
/area/groundbase/civilian/kitchen/backroom)
"hp" = (
/obj/structure/bed/chair/sofa/pew/left{
@@ -4202,7 +4202,7 @@
/obj/machinery/light{
dir = 4
},
-/turf/simulated/floor/carpet/deco,
+/turf/simulated/floor/carpet/geo,
/area/groundbase/civilian/kitchen/backroom)
"mo" = (
/obj/structure/table/woodentable,
@@ -4457,7 +4457,7 @@
/area/groundbase/civilian/janitor)
"nf" = (
/obj/structure/table/wooden_reinforced,
-/turf/simulated/floor/carpet/deco,
+/turf/simulated/floor/carpet/geo,
/area/groundbase/civilian/kitchen/backroom)
"ng" = (
/obj/machinery/camera/network/research{
@@ -6988,18 +6988,18 @@
/turf/simulated/floor/outdoors/sidewalk/slab/virgo3c,
/area/groundbase/level2/northspur)
"uU" = (
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
/obj/item/weapon/reagent_containers/food/condiment/small/saltshaker,
/obj/item/weapon/reagent_containers/food/condiment/small/saltshaker,
/obj/item/weapon/reagent_containers/food/condiment/small/peppermill,
@@ -10333,7 +10333,7 @@
/area/groundbase/medical/psych)
"EW" = (
/obj/structure/bed/chair/sofa/right/black,
-/turf/simulated/floor/carpet/deco,
+/turf/simulated/floor/carpet/geo,
/area/groundbase/civilian/kitchen/backroom)
"EX" = (
/obj/structure/closet/secure_closet/quartermaster,
@@ -13486,7 +13486,7 @@
/obj/structure/bed/chair/sofa/black{
dir = 8
},
-/turf/simulated/floor/carpet/deco,
+/turf/simulated/floor/carpet/geo,
/area/groundbase/civilian/kitchen/backroom)
"NU" = (
/obj/structure/sign/painting/library_secure{
@@ -13583,7 +13583,7 @@
/obj/structure/bed/chair/sofa/left/black{
dir = 8
},
-/turf/simulated/floor/carpet/deco,
+/turf/simulated/floor/carpet/geo,
/area/groundbase/civilian/kitchen/backroom)
"Oi" = (
/obj/structure/disposalpipe/tagger{
@@ -13605,7 +13605,7 @@
layer = 3.3;
pixel_y = -27
},
-/turf/simulated/floor/carpet/deco,
+/turf/simulated/floor/carpet/geo,
/area/groundbase/civilian/kitchen/backroom)
"Ok" = (
/obj/machinery/power/apc{
@@ -17276,7 +17276,7 @@
/area/groundbase/cargo/office)
"Zi" = (
/obj/machinery/firealarm,
-/turf/simulated/floor/carpet/deco,
+/turf/simulated/floor/carpet/geo,
/area/groundbase/civilian/kitchen/backroom)
"Zk" = (
/obj/item/weapon/bedsheet/browndouble,
diff --git a/maps/groundbase/southwilds/villagepois/square7.dmm b/maps/groundbase/southwilds/villagepois/square7.dmm
index 5cc3728a75..e92257e2a1 100644
--- a/maps/groundbase/southwilds/villagepois/square7.dmm
+++ b/maps/groundbase/southwilds/villagepois/square7.dmm
@@ -119,18 +119,18 @@
/turf/simulated/floor/tiled/eris/cafe,
/area/submap/groundbase/poi/wildvillage/square/square7)
"G" = (
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
/obj/item/weapon/reagent_containers/food/condiment/small/saltshaker,
/obj/item/weapon/reagent_containers/food/condiment/small/saltshaker,
/obj/item/weapon/reagent_containers/food/condiment/small/peppermill,
diff --git a/maps/offmap_vr/om_ships/itglight.dmm b/maps/offmap_vr/om_ships/itglight.dmm
index d4cd850274..2893bca848 100644
--- a/maps/offmap_vr/om_ships/itglight.dmm
+++ b/maps/offmap_vr/om_ships/itglight.dmm
@@ -2014,16 +2014,16 @@
/turf/simulated/floor,
/area/itglight/portengi)
"lO" = (
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/obj/item/weapon/reagent_containers/food/condiment/small/sugar,
/obj/item/weapon/reagent_containers/food/condiment/small/sugar,
/obj/item/weapon/reagent_containers/food/condiment/small/sugar,
@@ -2032,10 +2032,10 @@
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/obj/item/weapon/material/knife/butch,
/obj/item/weapon/material/minihoe,
/obj/item/weapon/material/knife/machete/hatchet,
diff --git a/maps/stellardelight/overmap.dmm b/maps/stellardelight/overmap.dmm
index 2890f0159b..8edb64c502 100644
--- a/maps/stellardelight/overmap.dmm
+++ b/maps/stellardelight/overmap.dmm
@@ -9,7 +9,7 @@
/turf/unsimulated/map,
/area/overmap)
"I" = (
-/turf/simulated/floor/carpet/deco,
+/turf/simulated/floor/carpet/geo,
/area/space)
"J" = (
/obj/effect/overmap/bluespace_rift,
diff --git a/maps/stellardelight/stellar_delight2.dmm b/maps/stellardelight/stellar_delight2.dmm
index 24a6192ea5..73422b6915 100644
--- a/maps/stellardelight/stellar_delight2.dmm
+++ b/maps/stellardelight/stellar_delight2.dmm
@@ -7772,18 +7772,18 @@
pixel_x = -1;
pixel_y = -42
},
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
/obj/item/weapon/reagent_containers/food/condiment/spacespice,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
/obj/item/weapon/reagent_containers/food/condiment/small/saltshaker,
/obj/item/weapon/reagent_containers/food/condiment/small/saltshaker,
/obj/item/weapon/reagent_containers/food/condiment/small/peppermill,
diff --git a/maps/stellardelight/stellar_delight3.dmm b/maps/stellardelight/stellar_delight3.dmm
index 3dff8303ce..e0a350f536 100644
--- a/maps/stellardelight/stellar_delight3.dmm
+++ b/maps/stellardelight/stellar_delight3.dmm
@@ -8094,7 +8094,7 @@
/obj/machinery/atmospherics/pipe/manifold/hidden/supply{
dir = 1
},
-/turf/simulated/floor/carpet/deco,
+/turf/simulated/floor/carpet/geo,
/area/ai)
"Dy" = (
/obj/machinery/atmospherics/pipe/simple/hidden/black,
diff --git a/maps/submaps/surface_submaps/plains/Diner.dmm b/maps/submaps/surface_submaps/plains/Diner.dmm
index b1a74d4f0b..f52213586e 100644
--- a/maps/submaps/surface_submaps/plains/Diner.dmm
+++ b/maps/submaps/surface_submaps/plains/Diner.dmm
@@ -43,7 +43,7 @@
/turf/simulated/floor/tiled/white,
/area/submap/diner)
"ak" = (
-/obj/machinery/vending/cola,
+/obj/machinery/vending/hotfood,
/turf/simulated/floor/tiled/white,
/area/submap/diner)
"al" = (
@@ -130,7 +130,7 @@
/obj/item/weapon/reagent_containers/food/drinks/bottle/cream,
/obj/item/weapon/reagent_containers/food/drinks/bottle/cream,
/obj/item/weapon/reagent_containers/food/drinks/bottle/cream,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
/turf/simulated/floor/tiled/white,
/area/submap/diner)
"az" = (
@@ -176,13 +176,13 @@
/area/submap/diner)
"aI" = (
/obj/structure/closet/crate/freezer,
-/obj/item/weapon/reagent_containers/food/snacks/meat,
-/obj/item/weapon/reagent_containers/food/snacks/meat,
-/obj/item/weapon/reagent_containers/food/snacks/meat,
-/obj/item/weapon/reagent_containers/food/snacks/meat,
-/obj/item/weapon/reagent_containers/food/snacks/meat,
-/obj/item/weapon/reagent_containers/food/snacks/meat,
-/obj/item/weapon/reagent_containers/food/snacks/meat,
+/obj/random/meat,
+/obj/random/meat,
+/obj/random/meat,
+/obj/random/meat,
+/obj/random/meat,
+/obj/random/meat,
+/obj/random/meat,
/turf/simulated/floor/tiled/freezer,
/area/submap/diner)
"aJ" = (
@@ -245,13 +245,13 @@
/area/submap/diner)
"aQ" = (
/obj/structure/closet/crate/freezer,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/turf/simulated/floor/tiled/freezer,
/area/submap/diner)
"aR" = (
@@ -375,6 +375,7 @@
/area/submap/diner)
"bo" = (
/obj/structure/table/woodentable,
+/obj/random/mug,
/turf/simulated/floor/lino,
/area/submap/diner)
"bp" = (
@@ -407,6 +408,7 @@
"bu" = (
/obj/structure/sink{
dir = 8;
+ icon_state = "sink";
pixel_x = -12;
pixel_y = 2
},
@@ -872,7 +874,7 @@ aa
(16,1,1) = {"
aa
ad
-ah
+bj
ap
ap
ap
diff --git a/maps/submaps/surface_submaps/plains/Diner_vr.dmm b/maps/submaps/surface_submaps/plains/Diner_vr.dmm
index 67e2a712c0..6c3cc6070d 100644
--- a/maps/submaps/surface_submaps/plains/Diner_vr.dmm
+++ b/maps/submaps/surface_submaps/plains/Diner_vr.dmm
@@ -130,7 +130,7 @@
/obj/item/weapon/reagent_containers/food/drinks/bottle/cream,
/obj/item/weapon/reagent_containers/food/drinks/bottle/cream,
/obj/item/weapon/reagent_containers/food/drinks/bottle/cream,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
/turf/simulated/floor/tiled/white,
/area/submap/Diner)
"az" = (
@@ -245,13 +245,13 @@
/area/submap/Diner)
"aQ" = (
/obj/structure/closet/crate/freezer,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
-/obj/item/weapon/reagent_containers/food/condiment/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
+/obj/item/weapon/reagent_containers/food/condiment/carton/flour,
/turf/simulated/floor/tiled/freezer,
/area/submap/Diner)
"aR" = (
diff --git a/maps/submaps/surface_submaps/plains/lonehome.dmm b/maps/submaps/surface_submaps/plains/lonehome.dmm
index 229116b3b9..3331c0dc1b 100644
--- a/maps/submaps/surface_submaps/plains/lonehome.dmm
+++ b/maps/submaps/surface_submaps/plains/lonehome.dmm
@@ -555,7 +555,7 @@
/obj/structure/table/rack/shelf/steel,
/obj/item/weapon/storage/box/donkpockets,
/obj/item/weapon/storage/box/condimentbottles,
-/obj/item/weapon/reagent_containers/food/condiment/sugar,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
/obj/structure/curtain/open/bed,
/obj/structure/window/reinforced/polarized{
dir = 4;
@@ -1054,544 +1054,3 @@
},
/turf/simulated/floor/outdoors/grass/heavy,
/area/submap/lonehome)
-
-(1,1,1) = {"
-NL
-NL
-NL
-NL
-NL
-NL
-yh
-NL
-Xz
-NL
-Xz
-NL
-NL
-NL
-NL
-uR
-NL
-NL
-MM
-NL
-NL
-NL
-NL
-MM
-NL
-"}
-(2,1,1) = {"
-NL
-NL
-NL
-NL
-NL
-Gm
-NL
-gn
-Sp
-Ek
-Ek
-uc
-Gm
-Gm
-Gm
-Gm
-Gm
-NL
-NL
-MM
-MM
-NL
-MM
-MM
-NL
-"}
-(3,1,1) = {"
-NL
-NL
-NL
-yh
-Gm
-Gm
-ND
-yn
-rg
-rg
-WQ
-Gm
-Gm
-DL
-cn
-ox
-po
-Gm
-Gm
-qa
-Ji
-qa
-jt
-NL
-NL
-"}
-(4,1,1) = {"
-NL
-NL
-NL
-Gm
-Gm
-Fh
-wa
-nD
-ve
-ve
-yF
-Gm
-ru
-dv
-Ys
-na
-Ll
-Gm
-Kr
-MM
-MM
-MM
-VI
-NL
-NL
-"}
-(5,1,1) = {"
-NL
-yh
-yh
-yh
-JW
-Lv
-qz
-eA
-Fu
-Fu
-oZ
-Gm
-gI
-Tw
-KM
-xA
-iw
-Or
-Fy
-MM
-MM
-MM
-pb
-NL
-NL
-"}
-(6,1,1) = {"
-yh
-yh
-NL
-Gm
-Gm
-Ll
-iw
-Lv
-Dr
-Dr
-wa
-Gm
-um
-SN
-NK
-Fu
-wa
-Gr
-Kr
-rE
-MM
-MM
-uh
-NL
-NL
-"}
-(7,1,1) = {"
-NL
-yh
-NL
-NL
-Gm
-uc
-wa
-iw
-Eo
-ZL
-Zg
-uc
-Zg
-Aq
-Fu
-Fu
-wj
-hq
-uI
-MM
-MM
-MM
-ZR
-NL
-NL
-"}
-(8,1,1) = {"
-yh
-NL
-NL
-NL
-NL
-Gm
-lJ
-QX
-wa
-Nx
-Xc
-Gm
-aJ
-iw
-ks
-Jq
-qo
-Gm
-Fy
-MM
-MM
-MM
-ZR
-NL
-NL
-"}
-(9,1,1) = {"
-NL
-NL
-aw
-NL
-NL
-Gm
-Gm
-uc
-JW
-Gm
-tp
-Gm
-JW
-Gm
-uc
-Gm
-Gm
-Gm
-Gm
-uK
-MM
-MM
-pb
-NL
-NL
-"}
-(10,1,1) = {"
-NL
-NL
-NL
-NL
-fl
-Gm
-Od
-JW
-Zg
-Zg
-jy
-bK
-yv
-ck
-qz
-qz
-qz
-Lv
-JW
-MM
-Kb
-MM
-UA
-NL
-NL
-"}
-(11,1,1) = {"
-NL
-NL
-NL
-NL
-Gm
-Gm
-Gm
-Gm
-JW
-Gm
-aI
-uc
-Gm
-Gm
-Gm
-pX
-Gm
-Gm
-Gm
-DG
-Gm
-MM
-VI
-NL
-aw
-"}
-(12,1,1) = {"
-NL
-NL
-NL
-NL
-Gm
-Kw
-BS
-iQ
-wa
-Zi
-wa
-ip
-uC
-Gm
-UB
-yv
-Ll
-aK
-ld
-fi
-Gm
-MM
-pb
-NL
-NL
-"}
-(13,1,1) = {"
-NL
-NL
-NL
-NL
-uc
-nL
-BS
-kg
-Zg
-nT
-Zg
-jH
-uw
-uc
-Im
-xr
-Ea
-Gm
-Hu
-fi
-Gm
-MM
-VI
-NL
-NL
-"}
-(14,1,1) = {"
-NL
-NL
-NL
-NL
-uc
-qk
-TX
-hH
-ii
-Lv
-Vn
-wa
-Uj
-Gm
-kT
-ro
-Wf
-Gm
-GS
-nz
-uc
-MM
-Wn
-NL
-NL
-"}
-(15,1,1) = {"
-NL
-NL
-NL
-NL
-Gm
-qi
-Ta
-Ta
-Tg
-Lv
-Pc
-hh
-lz
-Gm
-Ki
-yr
-oS
-Gm
-kU
-ew
-Gm
-MM
-VI
-NL
-NL
-"}
-(16,1,1) = {"
-NL
-NL
-NL
-NL
-Gm
-Gm
-DE
-US
-jK
-LS
-GA
-Mb
-Gm
-Gm
-Gm
-Gm
-Gm
-Gm
-Gm
-gC
-Gm
-MM
-VI
-NL
-NL
-"}
-(17,1,1) = {"
-NL
-NL
-NL
-NL
-NL
-Gm
-Dd
-Mn
-kn
-OL
-lB
-hK
-Gm
-Hc
-xO
-lS
-mn
-QU
-qE
-au
-uc
-MM
-xM
-NL
-NL
-"}
-(18,1,1) = {"
-NL
-aw
-NL
-NL
-NL
-Gm
-yh
-AQ
-Qx
-RI
-Xz
-Oe
-Gm
-Gm
-Wj
-EZ
-cE
-ur
-YK
-Gm
-Gm
-qa
-Sd
-MM
-NL
-"}
-(19,1,1) = {"
-NL
-NL
-NL
-NL
-NL
-yh
-NL
-NL
-uo
-NL
-NL
-yh
-uR
-Gm
-Gm
-Gm
-Gm
-Gm
-Gm
-Gm
-NL
-NL
-NL
-NL
-NL
-"}
-(20,1,1) = {"
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-NL
-"}
diff --git a/maps/submaps/surface_submaps/plains/lonehome_vr.dmm b/maps/submaps/surface_submaps/plains/lonehome_vr.dmm
new file mode 100644
index 0000000000..6cee63decc
--- /dev/null
+++ b/maps/submaps/surface_submaps/plains/lonehome_vr.dmm
@@ -0,0 +1,1595 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"aa" = (
+/turf/template_noop,
+/area/submap/lonehome)
+"ab" = (
+/turf/simulated/floor/outdoors/dirt/virgo3b,
+/area/submap/lonehome)
+"ac" = (
+/obj/structure/flora/tree/sif,
+/turf/template_noop,
+/area/submap/lonehome)
+"ad" = (
+/turf/simulated/wall/wood,
+/area/submap/lonehome)
+"ae" = (
+/obj/structure/simple_door/wood,
+/turf/simulated/floor/wood,
+/area/submap/lonehome)
+"af" = (
+/obj/structure/loot_pile/maint/trash,
+/turf/template_noop,
+/area/submap/lonehome)
+"ag" = (
+/obj/structure/girder,
+/turf/simulated/floor,
+/area/submap/lonehome)
+"ah" = (
+/obj/machinery/button/windowtint{
+ id = "h_living"
+ },
+/turf/simulated/wall/wood,
+/area/submap/lonehome)
+"ai" = (
+/obj/structure/coatrack,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aj" = (
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/broken,
+/area/submap/lonehome)
+"ak" = (
+/obj/machinery/space_heater,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"al" = (
+/obj/structure/closet/secure_closet/freezer/fridge,
+/obj/random/meat,
+/obj/random/meat,
+/obj/random/meat,
+/obj/random/meat,
+/obj/random/meat,
+/turf/simulated/floor/tiled/white/virgo3b,
+/area/submap/lonehome)
+"am" = (
+/obj/machinery/appliance/cooker/oven,
+/turf/simulated/floor/tiled/white/virgo3b,
+/area/submap/lonehome)
+"an" = (
+/obj/structure/table/marble,
+/obj/machinery/light{
+ dir = 1
+ },
+/obj/machinery/microwave,
+/turf/simulated/floor/tiled/white/virgo3b,
+/area/submap/lonehome)
+"ao" = (
+/obj/structure/table/marble,
+/obj/machinery/chemical_dispenser/bar_alc/full,
+/turf/simulated/floor/tiled/white/virgo3b,
+/area/submap/lonehome)
+"ap" = (
+/obj/structure/table/bench/marble,
+/obj/structure/window/reinforced/polarized{
+ dir = 8;
+ id = "h_living"
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aq" = (
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"ar" = (
+/obj/effect/decal/cleanable/dirt,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"as" = (
+/turf/simulated/floor/wood/broken,
+/area/submap/lonehome)
+"au" = (
+/obj/item/device/tape,
+/obj/structure/table/wooden_reinforced,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"av" = (
+/obj/item/weapon/extinguisher{
+ pixel_x = 8;
+ pixel_y = 1
+ },
+/obj/random/maintenance/medical,
+/obj/random/maintenance/medical,
+/obj/random/maintenance/medical,
+/obj/random/medical/lite,
+/obj/random/medical/lite,
+/obj/structure/mopbucket{
+ pixel_x = -8;
+ pixel_y = -4
+ },
+/obj/item/weapon/mop,
+/obj/item/device/multitool,
+/obj/item/device/flashlight{
+ pixel_x = 2;
+ pixel_y = 2
+ },
+/obj/random/unidentified_medicine,
+/obj/random/unidentified_medicine,
+/obj/random/unidentified_medicine,
+/obj/structure/table/steel,
+/turf/simulated/floor/tiled,
+/area/submap/lonehome)
+"aw" = (
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/tiled/white/virgo3b,
+/area/submap/lonehome)
+"ax" = (
+/obj/effect/decal/cleanable/dirt,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/tiled/white/virgo3b,
+/area/submap/lonehome)
+"ay" = (
+/obj/item/clothing/suit/storage/apron/white,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/tiled/white/virgo3b,
+/area/submap/lonehome)
+"az" = (
+/obj/structure/window/reinforced/tinted/frosted{
+ dir = 8
+ },
+/obj/item/weapon/storage/box/glasses/square{
+ pixel_y = -2
+ },
+/obj/item/weapon/storage/box/glass_extras/sticks{
+ pixel_y = 4
+ },
+/obj/item/weapon/storage/box/glass_extras/straws{
+ pixel_y = 7
+ },
+/obj/item/weapon/packageWrap,
+/obj/structure/curtain/open/bed,
+/obj/item/weapon/material/kitchen/utensil/fork,
+/obj/item/weapon/material/kitchen/utensil/fork,
+/obj/item/weapon/material/kitchen/utensil/spoon{
+ pixel_x = 2
+ },
+/obj/item/weapon/material/kitchen/utensil/spoon{
+ pixel_x = 2
+ },
+/obj/structure/table/rack,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aA" = (
+/obj/machinery/button/windowtint{
+ id = "h_kitchen"
+ },
+/obj/item/weapon/storage/box/donkpockets,
+/obj/item/weapon/storage/box/condimentbottles,
+/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,
+/obj/structure/curtain/open/bed,
+/obj/structure/table/rack,
+/obj/structure/window/reinforced/polarized{
+ dir = 4;
+ id = "h_kitchen"
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aB" = (
+/obj/structure/window/reinforced{
+ dir = 4;
+ health = 1e+006
+ },
+/turf/template_noop,
+/area/submap/lonehome)
+"aC" = (
+/obj/structure/window/reinforced/polarized{
+ dir = 8;
+ id = "h_living"
+ },
+/obj/structure/bed/chair/sofa/corner/black{
+ dir = 1
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aD" = (
+/obj/structure/bed/chair/sofa/black,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aE" = (
+/obj/structure/bed/chair/sofa/left/black,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aF" = (
+/obj/random/trash,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aG" = (
+/obj/structure/table/marble,
+/obj/item/weapon/material/knife/butch,
+/obj/item/weapon/material/kitchen/rollingpin,
+/turf/simulated/floor/tiled/white/virgo3b,
+/area/submap/lonehome)
+"aH" = (
+/obj/structure/table/marble,
+/obj/item/weapon/material/kitchen/utensil/spoon,
+/turf/simulated/floor/tiled/white/virgo3b,
+/area/submap/lonehome)
+"aI" = (
+/obj/structure/table/marble,
+/obj/item/weapon/material/sharpeningkit,
+/obj/item/weapon/reagent_containers/dropper,
+/turf/simulated/floor/tiled/white/virgo3b,
+/area/submap/lonehome)
+"aJ" = (
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aK" = (
+/obj/machinery/button/windowtint{
+ id = "h_kitchen"
+ },
+/obj/item/weapon/reagent_containers/food/condiment/enzyme,
+/obj/structure/window/reinforced/polarized{
+ dir = 4;
+ id = "h_kitchen"
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aL" = (
+/obj/structure/window/reinforced{
+ dir = 8;
+ health = 1e+006
+ },
+/obj/item/weapon/material/shard,
+/turf/simulated/floor/outdoors/dirt/virgo3b,
+/area/submap/lonehome)
+"aM" = (
+/obj/item/weapon/material/shard,
+/turf/template_noop,
+/area/submap/lonehome)
+"aN" = (
+/obj/item/weapon/material/shard,
+/obj/item/weapon/material/shard{
+ pixel_x = 5;
+ pixel_y = 3
+ },
+/turf/simulated/floor/outdoors/dirt/virgo3b,
+/area/submap/lonehome)
+"aO" = (
+/obj/structure/window/reinforced/polarized{
+ dir = 8;
+ id = "h_living"
+ },
+/obj/structure/bed/chair/sofa/black{
+ dir = 4
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aP" = (
+/obj/structure/table/bench/glass,
+/turf/simulated/floor/carpet/sblucarpet/virgo3b,
+/area/submap/lonehome)
+"aQ" = (
+/turf/simulated/floor/carpet/sblucarpet/virgo3b,
+/area/submap/lonehome)
+"aR" = (
+/obj/random/junk,
+/turf/simulated/floor/carpet/sblucarpet/virgo3b,
+/area/submap/lonehome)
+"aS" = (
+/obj/random/junk,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aT" = (
+/obj/item/weapon/material/kitchen/utensil/fork,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aU" = (
+/obj/structure/bed/chair/wood,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aV" = (
+/obj/structure/bed/chair/wood,
+/obj/machinery/button/windowtint{
+ id = "h_kitchen"
+ },
+/obj/structure/window/reinforced/polarized{
+ dir = 4;
+ id = "h_kitchen"
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"aW" = (
+/obj/item/weapon/material/shard{
+ pixel_x = 6
+ },
+/obj/item/weapon/material/shard,
+/turf/simulated/floor/outdoors/dirt/virgo3b,
+/area/submap/lonehome)
+"aX" = (
+/obj/item/weapon/material/shard,
+/turf/simulated/floor/outdoors/dirt/virgo3b,
+/area/submap/lonehome)
+"aY" = (
+/obj/structure/window/reinforced{
+ dir = 4;
+ health = 1e+006
+ },
+/turf/simulated/floor/outdoors/dirt/virgo3b,
+/area/submap/lonehome)
+"aZ" = (
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/carpet/sblucarpet/virgo3b,
+/area/submap/lonehome)
+"ba" = (
+/obj/machinery/light{
+ dir = 4
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bb" = (
+/obj/machinery/light{
+ dir = 8
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bc" = (
+/obj/item/stack/cable_coil,
+/turf/simulated/floor/wood/broken,
+/area/submap/lonehome)
+"bd" = (
+/obj/item/trash/plate,
+/obj/item/weapon/reagent_containers/food/condiment/small/saltshaker,
+/obj/structure/table/sifwooden_reinforced,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"be" = (
+/obj/machinery/button/windowtint{
+ id = "h_kitchen"
+ },
+/obj/item/weapon/material/kitchen/utensil/spoon{
+ pixel_x = 2
+ },
+/obj/structure/table/sifwooden_reinforced,
+/obj/structure/window/reinforced/polarized{
+ dir = 4;
+ id = "h_kitchen"
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bf" = (
+/obj/structure/window/reinforced{
+ dir = 8;
+ health = 1e+006
+ },
+/turf/simulated/floor/outdoors/dirt/virgo3b,
+/area/submap/lonehome)
+"bg" = (
+/obj/structure/window/reinforced/polarized{
+ dir = 8;
+ id = "h_living"
+ },
+/obj/structure/bed/chair/sofa/corner/black{
+ dir = 4
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bh" = (
+/obj/structure/bed/chair/sofa/black{
+ dir = 1
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bi" = (
+/obj/structure/bed/chair/sofa/right/black{
+ dir = 1
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bj" = (
+/obj/item/weapon/module/power_control,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bk" = (
+/obj/structure/flora/pottedplant/bamboo{
+ pixel_y = 12
+ },
+/obj/structure/table/bench/wooden,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bl" = (
+/obj/item/weapon/material/shard,
+/obj/item/weapon/material/shard{
+ pixel_x = 5;
+ pixel_y = 10
+ },
+/obj/effect/decal/cleanable/dirt,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bm" = (
+/obj/structure/window/reinforced{
+ dir = 8;
+ health = 1e+006
+ },
+/obj/structure/flora/pottedplant/fern{
+ pixel_y = 12
+ },
+/obj/structure/table/bench/wooden,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bn" = (
+/obj/item/weapon/reagent_containers/food/condiment/small/sugar,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/broken,
+/area/submap/lonehome)
+"bo" = (
+/obj/item/weapon/material/kitchen/utensil/fork,
+/obj/random/trash,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bp" = (
+/obj/item/weapon/reagent_containers/food/snacks/ghostmuffin/poison,
+/obj/structure/table/sifwooden_reinforced,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bq" = (
+/obj/machinery/button/windowtint{
+ id = "h_kitchen"
+ },
+/obj/item/trash/plate,
+/obj/structure/table/sifwooden_reinforced,
+/obj/structure/window/reinforced/polarized{
+ dir = 4;
+ id = "h_kitchen"
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"br" = (
+/obj/effect/decal/cleanable/dirt,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/broken,
+/area/submap/lonehome)
+"bs" = (
+/obj/item/weapon/pen,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bt" = (
+/obj/item/weapon/material/kitchen/utensil/spoon{
+ pixel_x = 2
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bu" = (
+/obj/item/weapon/reagent_containers/food/condiment/small/peppermill,
+/obj/item/weapon/pen,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bv" = (
+/obj/structure/bed/chair/wood{
+ dir = 1
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bw" = (
+/obj/structure/bed/chair/wood{
+ dir = 1
+ },
+/obj/structure/window/reinforced/polarized{
+ dir = 4;
+ id = "h_kitchen"
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bx" = (
+/obj/structure/window/reinforced{
+ dir = 8;
+ health = 1e+006
+ },
+/turf/template_noop,
+/area/submap/lonehome)
+"by" = (
+/obj/structure/table/rack,
+/obj/item/weapon/makeover,
+/obj/structure/curtain/open/bed,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bz" = (
+/obj/structure/table/rack,
+/obj/item/weapon/material/knife/ritual,
+/obj/structure/curtain/open/bed,
+/obj/item/clothing/under/suit_jacket/really_black,
+/obj/item/clothing/under/suit_jacket/navy,
+/obj/item/clothing/head/soft/solgov/veteranhat,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bA" = (
+/obj/structure/window/reinforced/tinted/frosted{
+ dir = 8
+ },
+/obj/structure/table/wooden_reinforced,
+/obj/item/weapon/book/tome,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bB" = (
+/obj/item/weapon/paper{
+ info = "Seems to be filled with odd runes drawn with blood";
+ name = "Blood stained paper"
+ },
+/obj/effect/decal/cleanable/dirt,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/broken,
+/area/submap/lonehome)
+"bC" = (
+/obj/effect/decal/cleanable/dirt,
+/obj/item/inflatable/door,
+/turf/simulated/floor/wood/broken,
+/area/submap/lonehome)
+"bD" = (
+/obj/item/seeds/random,
+/obj/item/seeds/random,
+/obj/machinery/space_heater,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bE" = (
+/obj/item/seeds/random,
+/obj/item/weapon/reagent_containers/spray/cleaner,
+/obj/structure/table/wooden_reinforced,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bF" = (
+/obj/machinery/light/small,
+/obj/item/weapon/ore/diamond,
+/obj/structure/sign/goldenplaque{
+ desc = "Done No Harm.";
+ name = "Best Doctor 2552";
+ pixel_y = -32
+ },
+/obj/structure/table/wooden_reinforced,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bG" = (
+/obj/item/weapon/reagent_containers/glass/rag,
+/obj/item/clothing/gloves/ring/engagement,
+/obj/structure/table/wooden_reinforced,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bH" = (
+/obj/machinery/button/windowtint{
+ id = "h_kitchen"
+ },
+/turf/simulated/wall/wood,
+/area/submap/lonehome)
+"bI" = (
+/obj/structure/closet/cabinet,
+/obj/item/weapon/storage/wallet/random,
+/obj/item/weapon/towel/random,
+/obj/item/weapon/melee/umbrella/random,
+/obj/random/carp_plushie,
+/obj/random/curseditem,
+/obj/random/junk,
+/obj/random/maintenance/medical,
+/obj/random/maintenance/medical,
+/obj/random/maintenance/medical,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bJ" = (
+/obj/item/weapon/paper{
+ info = "Seems to be filled with odd runes drawn with blood";
+ name = "Blood stained paper"
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bK" = (
+/obj/item/weapon/cell/high/empty,
+/turf/simulated/floor/carpet/sblucarpet/virgo3b,
+/area/submap/lonehome)
+"bL" = (
+/obj/structure/bed/chair/wood/wings{
+ dir = 1
+ },
+/obj/effect/gibspawner/human,
+/turf/simulated/floor/carpet/sblucarpet/virgo3b,
+/area/submap/lonehome)
+"bM" = (
+/obj/item/weapon/paper{
+ info = "F%$K this detective, I swear I can see them peeking from behind the window, these tinted glasses help but I swear I can still see them snooping around. His days are counted... I am so close to figuring this out, just need a few more days. Then Shepiffany will be part of the family once again, then our two kids and I can go back on having a normal life... a normal life...";
+ name = "Stained sheet of paper"
+ },
+/obj/effect/decal/cleanable/dirt,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/carpet/sblucarpet/virgo3b,
+/area/submap/lonehome)
+"bN" = (
+/obj/machinery/light/small{
+ dir = 8
+ },
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bO" = (
+/obj/effect/decal/cleanable/blood/gibs,
+/obj/structure/kitchenspike,
+/obj/effect/rune,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"bP" = (
+/obj/machinery/light{
+ dir = 8
+ },
+/obj/item/weapon/paper/courtroom,
+/obj/item/weapon/paper/courtroom,
+/obj/item/weapon/paper_bin,
+/obj/structure/table/wooden_reinforced,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bQ" = (
+/obj/item/weapon/pen/fountain,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bR" = (
+/obj/item/weapon/paper{
+ info = "Seems to be filled with odd runes drawn with blood";
+ name = "Blood stained paper"
+ },
+/turf/simulated/floor/carpet/sblucarpet/virgo3b,
+/area/submap/lonehome)
+"bS" = (
+/obj/random/trash,
+/obj/effect/decal/cleanable/dirt,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/carpet/sblucarpet/virgo3b,
+/area/submap/lonehome)
+"bT" = (
+/obj/effect/decal/cleanable/dirt,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/carpet/sblucarpet/virgo3b,
+/area/submap/lonehome)
+"bU" = (
+/obj/item/weapon/photo,
+/obj/item/weapon/photo{
+ pixel_x = 4
+ },
+/obj/structure/table/bench/wooden,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bV" = (
+/obj/item/weapon/storage/box/characters,
+/obj/structure/curtain/open/bed,
+/obj/structure/table/rack,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bW" = (
+/obj/structure/window/reinforced/tinted/frosted{
+ dir = 8
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bX" = (
+/obj/machinery/light/small{
+ dir = 1
+ },
+/obj/structure/bed/padded,
+/obj/item/weapon/book/custom_library/fiction/truelovehathmyheart,
+/obj/item/weapon/bedsheet/clown,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bY" = (
+/obj/structure/closet/cabinet,
+/obj/random/multiple,
+/obj/random/multiple,
+/obj/random/multiple,
+/obj/random/multiple,
+/obj/random/multiple,
+/obj/random/toy,
+/obj/random/toy,
+/obj/item/weapon/storage/mre/random,
+/obj/item/weapon/storage/mre/random,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"bZ" = (
+/obj/item/clothing/suit/straight_jacket,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"ca" = (
+/obj/machinery/gibber/autogibber{
+ emagged = 1
+ },
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cb" = (
+/obj/structure/closet/cabinet,
+/obj/item/weapon/storage/wallet/random,
+/obj/item/weapon/towel/random,
+/obj/item/weapon/melee/umbrella/random,
+/obj/random/ammo,
+/obj/random/cigarettes,
+/obj/random/contraband,
+/obj/random/junk,
+/obj/random/maintenance/security,
+/obj/random/maintenance/medical,
+/obj/random/maintenance/medical,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"cc" = (
+/obj/effect/decal/cleanable/dirt,
+/mob/living/simple_mob/animal/passive/cat/bones{
+ desc = "A very odd behaved cat, their scratched name tag reads 'Felix'. They look very malnourished.";
+ name = "Felix"
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"cd" = (
+/obj/structure/bed/double/padded,
+/obj/machinery/light/small{
+ dir = 4
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"ce" = (
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/carpet/bcarpet,
+/area/submap/lonehome)
+"cf" = (
+/obj/item/weapon/pack/cardemon,
+/turf/simulated/floor/carpet/bcarpet,
+/area/submap/lonehome)
+"cg" = (
+/obj/item/weapon/reagent_containers/blood,
+/obj/item/weapon/pack/cardemon,
+/obj/item/weapon/pack/cardemon,
+/obj/item/weapon/deck/tarot,
+/obj/structure/table/wooden_reinforced,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"ch" = (
+/obj/item/organ/internal/liver,
+/obj/random/trash,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"ci" = (
+/obj/effect/decal/cleanable/blood/gibs,
+/obj/structure/table/marble,
+/obj/item/organ/internal/intestine/unathi,
+/obj/item/weapon/surgical/bonesetter,
+/obj/item/weapon/bone/ribs,
+/obj/item/weapon/bone/skull{
+ pixel_x = 4;
+ pixel_y = 6
+ },
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cj" = (
+/obj/structure/sign/periodic,
+/turf/simulated/wall/wood,
+/area/submap/lonehome)
+"ck" = (
+/obj/item/weapon/bedsheet/rddouble,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"cl" = (
+/obj/item/weapon/phone,
+/obj/structure/table/bench/wooden,
+/obj/item/weapon/paper{
+ info = "Seems to be filled with odd runes drawn with blood";
+ name = "Blood stained paper"
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"cm" = (
+/obj/item/weapon/pack/cardemon,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"cn" = (
+/obj/structure/bed/padded,
+/obj/random/trash,
+/obj/random/trash,
+/obj/random/trash,
+/obj/random/trash,
+/obj/random/trash,
+/obj/random/junk,
+/obj/random/junk,
+/obj/random/junk,
+/obj/random/junk,
+/obj/item/weapon/bedsheet/ian,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"co" = (
+/obj/structure/closet/cabinet,
+/obj/random/tech_supply/component,
+/obj/random/tech_supply/component,
+/obj/random/tech_supply/component,
+/obj/random/tech_supply/component,
+/obj/random/tech_supply/component,
+/obj/random/toolbox,
+/obj/random/toolbox,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"cp" = (
+/obj/item/clothing/mask/muzzle,
+/obj/random/trash,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cq" = (
+/obj/structure/table/marble,
+/obj/item/organ/internal/brain/grey,
+/obj/item/weapon/material/knife/plastic,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cr" = (
+/obj/structure/table/bench/marble,
+/obj/structure/window/reinforced/polarized{
+ id = "h_master"
+ },
+/obj/structure/flora/pottedplant/overgrown{
+ pixel_y = 7
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"cs" = (
+/obj/structure/table/bench/marble,
+/obj/structure/window/reinforced/polarized{
+ id = "h_master"
+ },
+/obj/structure/flora/pottedplant/bamboo{
+ pixel_y = 7
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"ct" = (
+/obj/structure/table/bench/marble,
+/obj/structure/window/reinforced/polarized{
+ id = "h_master"
+ },
+/obj/structure/flora/pottedplant/dead{
+ pixel_y = 7
+ },
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+"cu" = (
+/obj/machinery/button/windowtint{
+ id = "h_master"
+ },
+/turf/simulated/wall/wood,
+/area/submap/lonehome)
+"cv" = (
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cw" = (
+/obj/effect/decal/cleanable/blood/gibs,
+/obj/item/clothing/gloves/yellow,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cx" = (
+/obj/structure/table/marble,
+/obj/item/weapon/material/knife/hook,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cy" = (
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"cz" = (
+/obj/machinery/portable_atmospherics/hydroponics/soil,
+/obj/item/weapon/material/shard,
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"cA" = (
+/obj/structure/window/reinforced{
+ dir = 1
+ },
+/obj/machinery/portable_atmospherics/hydroponics/soil,
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"cB" = (
+/obj/machinery/portable_atmospherics/hydroponics/soil,
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"cC" = (
+/obj/machinery/power/port_gen/pacman,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cD" = (
+/obj/item/frame/apc,
+/obj/machinery/light_switch{
+ pixel_x = 11;
+ pixel_y = 22
+ },
+/obj/random/junk,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cE" = (
+/obj/structure/table/steel,
+/obj/item/stack/material/phoron{
+ amount = 5
+ },
+/obj/item/stack/material/phoron{
+ amount = 5
+ },
+/obj/fiftyspawner/wood,
+/obj/fiftyspawner/steel,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cF" = (
+/obj/item/weapon/chainsaw,
+/obj/item/weapon/storage/box/lights/mixed,
+/obj/item/weapon/extinguisher,
+/obj/item/weapon/storage/toolbox/mechanical,
+/obj/structure/table/rack,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cG" = (
+/obj/effect/decal/cleanable/blood/gibs,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cH" = (
+/obj/structure/table/marble,
+/obj/item/organ/internal/intestine/unathi{
+ pixel_x = -1;
+ pixel_y = 8
+ },
+/obj/item/organ/internal/lungs,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cI" = (
+/obj/structure/fence,
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"cJ" = (
+/obj/item/weapon/material/shard,
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"cK" = (
+/obj/item/weapon/material/minihoe,
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"cL" = (
+/obj/structure/simple_door/wood,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cM" = (
+/obj/effect/decal/cleanable/dirt,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cN" = (
+/obj/item/device/flashlight{
+ pixel_x = 2;
+ pixel_y = 2
+ },
+/obj/effect/decal/cleanable/dirt,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cO" = (
+/obj/machinery/light/small{
+ dir = 4
+ },
+/obj/effect/decal/cleanable/dirt,
+/obj/effect/decal/cleanable/dirt,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cP" = (
+/obj/item/organ/internal/lungs/vox,
+/obj/item/weapon/beartrap/hunting{
+ anchored = 1;
+ deployed = 1
+ },
+/obj/structure/curtain/black,
+/obj/random/junk,
+/obj/random/junk,
+/obj/random/junk,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"cQ" = (
+/obj/structure/fence/cut/medium,
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"cR" = (
+/obj/structure/loot_pile/maint/trash,
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"cS" = (
+/obj/structure/fence/corner{
+ dir = 8
+ },
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"cT" = (
+/obj/structure/fence{
+ dir = 4
+ },
+/turf/template_noop,
+/area/submap/lonehome)
+"cU" = (
+/obj/structure/fence/cut/large{
+ dir = 8
+ },
+/turf/template_noop,
+/area/submap/lonehome)
+"cV" = (
+/obj/structure/fence/cut/medium{
+ dir = 4
+ },
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"cW" = (
+/obj/structure/fence{
+ dir = 4
+ },
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"cX" = (
+/obj/structure/fence/door/opened,
+/turf/template_noop,
+/area/submap/lonehome)
+"cY" = (
+/obj/structure/fence/cut/medium{
+ dir = 4
+ },
+/turf/template_noop,
+/area/submap/lonehome)
+"cZ" = (
+/obj/structure/fence/cut/large{
+ dir = 8
+ },
+/turf/simulated/floor/outdoors/grass/sif/forest/virgo3b,
+/area/submap/lonehome)
+"da" = (
+/obj/structure/fence/corner,
+/turf/template_noop,
+/area/submap/lonehome)
+"mx" = (
+/obj/structure/girder,
+/turf/simulated/floor/plating/external/virgo3b,
+/area/submap/lonehome)
+"ES" = (
+/obj/structure/simple_door/wood,
+/turf/simulated/floor/wood/virgo3b,
+/area/submap/lonehome)
+
+(1,1,1) = {"
+aa
+aa
+aa
+aa
+aa
+aa
+ab
+aa
+aM
+aa
+aM
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+cy
+aa
+aa
+aa
+aa
+cy
+aa
+"}
+(2,1,1) = {"
+aa
+aa
+aa
+aa
+aa
+ad
+aa
+aB
+aN
+aY
+aY
+mx
+ad
+ad
+ad
+ad
+ad
+af
+aa
+cy
+cy
+aa
+cy
+cy
+aa
+"}
+(3,1,1) = {"
+aa
+aa
+aa
+ab
+ad
+ah
+ap
+aC
+aO
+aO
+bg
+ad
+ad
+bI
+bP
+cb
+cj
+ad
+ad
+cI
+cQ
+cI
+cS
+aa
+aa
+"}
+(4,1,1) = {"
+aa
+aa
+aa
+ad
+ad
+ai
+aq
+aD
+aP
+aP
+bh
+ad
+by
+bJ
+bQ
+cc
+ak
+ad
+cz
+cy
+cy
+cy
+cT
+aa
+aa
+"}
+(5,1,1) = {"
+aa
+ab
+ab
+ab
+ae
+aj
+ar
+aE
+aQ
+aQ
+bi
+ad
+bz
+bK
+bR
+bS
+as
+cr
+cA
+cy
+cy
+cy
+cU
+aa
+aa
+"}
+(6,1,1) = {"
+ab
+ab
+aa
+ad
+ad
+ak
+as
+aj
+aQ
+aZ
+aJ
+ad
+bA
+bL
+bS
+aQ
+aJ
+cs
+cz
+cJ
+cy
+cy
+cV
+aa
+aa
+"}
+(7,1,1) = {"
+aa
+ab
+aa
+aa
+ad
+mx
+aJ
+as
+aR
+aZ
+ar
+ad
+aq
+bM
+bT
+aZ
+ck
+ct
+cB
+cy
+cy
+cy
+cW
+aa
+aa
+"}
+(8,1,1) = {"
+ab
+aa
+aa
+aa
+af
+ad
+au
+aF
+aJ
+ba
+bj
+ad
+bB
+br
+bU
+cd
+cl
+cu
+cA
+cy
+cy
+cy
+cW
+aa
+aa
+"}
+(9,1,1) = {"
+aa
+aa
+ac
+aa
+aa
+ad
+ad
+mx
+ES
+ad
+bk
+ad
+ES
+ad
+ad
+ad
+ad
+ad
+ad
+cK
+cy
+cy
+cU
+aa
+aa
+"}
+(10,1,1) = {"
+aa
+aa
+aa
+aa
+af
+ad
+av
+ES
+ar
+ar
+bl
+br
+bC
+bN
+ar
+ar
+aq
+as
+ES
+cy
+cR
+cy
+cX
+aa
+aa
+"}
+(11,1,1) = {"
+aa
+aa
+aa
+aa
+ad
+ad
+ad
+ad
+ES
+ad
+bm
+mx
+ad
+ad
+ad
+ES
+ad
+ad
+ad
+cL
+ad
+cy
+cT
+aa
+ac
+"}
+(12,1,1) = {"
+aa
+aa
+aa
+aa
+ad
+al
+aw
+aG
+aJ
+bb
+aq
+bs
+bD
+ad
+bV
+aj
+ak
+cv
+cC
+cM
+ad
+cy
+cU
+aa
+aa
+"}
+(13,1,1) = {"
+aa
+aa
+aa
+aa
+ag
+am
+ax
+aH
+aq
+bc
+aq
+bt
+bE
+mx
+bW
+ce
+cm
+ad
+cD
+cM
+ad
+cy
+cT
+aa
+aa
+"}
+(14,1,1) = {"
+aa
+aa
+aa
+aa
+ag
+an
+ay
+aI
+aS
+as
+bn
+aJ
+bF
+ad
+bX
+cf
+cn
+ad
+cE
+cN
+mx
+cy
+cY
+aa
+aa
+"}
+(15,1,1) = {"
+aa
+aa
+aa
+aa
+ad
+ao
+ax
+aw
+aT
+as
+bo
+bu
+bG
+ad
+bY
+cg
+co
+ad
+cF
+cO
+ad
+cy
+cT
+aa
+aa
+"}
+(16,1,1) = {"
+aa
+aa
+aa
+aa
+ad
+ad
+az
+aJ
+aU
+bd
+bp
+bv
+bH
+ad
+ad
+ad
+ad
+ad
+ad
+mx
+ad
+cy
+cT
+aa
+aa
+"}
+(17,1,1) = {"
+aa
+aa
+aa
+aa
+aa
+ad
+aA
+aK
+aV
+be
+bq
+bw
+ad
+bO
+bZ
+ch
+cp
+cw
+cG
+cP
+mx
+cy
+cZ
+aa
+aa
+"}
+(18,1,1) = {"
+aa
+ac
+aa
+aa
+aa
+ad
+ab
+aL
+aW
+bf
+aM
+bx
+ad
+ad
+ca
+ci
+cq
+cx
+cH
+ad
+ad
+cI
+da
+cy
+aa
+"}
+(19,1,1) = {"
+aa
+aa
+aa
+aa
+aa
+ab
+aa
+aa
+aX
+aa
+aa
+ab
+aa
+ad
+ad
+ad
+ad
+ad
+ad
+ad
+aa
+aa
+aa
+aa
+aa
+"}
+(20,1,1) = {"
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+aa
+"}
diff --git a/maps/tether/tether_turfs.dm b/maps/tether/tether_turfs.dm
index 6bdd1f42fe..9b73406590 100644
--- a/maps/tether/tether_turfs.dm
+++ b/maps/tether/tether_turfs.dm
@@ -276,6 +276,9 @@ VIRGO3B_TURF_CREATE(/turf/simulated/mineral/floor)
for(var/obj/effect/step_trigger/teleporter/planetary_fall/virgo3b/F in src)
qdel(F)
+/turf/space/v3b_midpoint/CanZPass(atom, direction)
+ return 0 // We're not Space
+
// Tram transit floor
/turf/simulated/floor/tiled/techfloor/grid/transit
icon = 'icons/turf/transit_vr.dmi'
diff --git a/modular_chomp/code/modules/mob/living/carbon/human/species/station/protean/protean_powers.dm b/modular_chomp/code/modules/mob/living/carbon/human/species/station/protean/protean_powers.dm
index c192ef6b81..87d9c42f08 100644
--- a/modular_chomp/code/modules/mob/living/carbon/human/species/station/protean/protean_powers.dm
+++ b/modular_chomp/code/modules/mob/living/carbon/human/species/station/protean/protean_powers.dm
@@ -352,7 +352,7 @@
return
var/nagmessage = "Adjust your mass to be a size between 25 to 200% (or between 1 to 600% in dorms area). Up-sizing consumes metal, downsizing returns metal."
- var/new_size = input(user, nagmessage, "Pick a Size", user.size_multiplier*100) as num|null
+ var/new_size = tgui_input_number(user, nagmessage, "Pick a Size", user.size_multiplier*100, 600, 1)
if(!new_size || !size_range_check(new_size))
return
diff --git a/tgui/.eslintrc.yml b/tgui/.eslintrc.yml
index 5337f19f6f..7290ff0a13 100644
--- a/tgui/.eslintrc.yml
+++ b/tgui/.eslintrc.yml
@@ -670,7 +670,7 @@ rules:
# react/sort-prop-types: error
## Enforce the state initialization style to be either in a constructor or
## with a class property
- react/state-in-constructor: error
+ # react/state-in-constructor: error
## Enforces where React component static properties should be positioned.
# react/static-property-placement: error
## Enforce style prop value being an object
diff --git a/tgui/.gitignore b/tgui/.gitignore
index 56cb38b290..bcdbd1c91a 100644
--- a/tgui/.gitignore
+++ b/tgui/.gitignore
@@ -14,6 +14,7 @@ package-lock.json
## Build artifacts
/public/.tmp/**/*
+/public/*.map
## Previously ignored locations that are kept to avoid confusing git
## while transitioning to a new project structure.
diff --git a/tgui/babel.config.js b/tgui/babel.config.js
index e2f3248066..d8ddb75721 100644
--- a/tgui/babel.config.js
+++ b/tgui/babel.config.js
@@ -5,7 +5,7 @@
*/
const createBabelConfig = options => {
- const { mode, presets = [], plugins = [] } = options;
+ const { presets = [], plugins = [], removeConsole } = options;
return {
presets: [
[require.resolve('@babel/preset-typescript'), {
@@ -20,17 +20,17 @@ const createBabelConfig = options => {
targets: [],
}],
...presets,
- ],
+ ].filter(Boolean),
plugins: [
[require.resolve('@babel/plugin-proposal-class-properties'), {
loose: true,
}],
require.resolve('@babel/plugin-transform-jscript'),
require.resolve('babel-plugin-inferno'),
- require.resolve('babel-plugin-transform-remove-console'),
+ removeConsole && require.resolve('babel-plugin-transform-remove-console'),
require.resolve('common/string.babel-plugin.cjs'),
...plugins,
- ],
+ ].filter(Boolean),
};
};
diff --git a/tgui/bin/tgui b/tgui/bin/tgui
index dc1d05578b..256f0e579e 100644
--- a/tgui/bin/tgui
+++ b/tgui/bin/tgui
@@ -92,7 +92,7 @@ task-clean() {
rm -f .yarn/build-state.yml
rm -f .yarn/install-state.gz
rm -f .yarn/install-target
- rm -f .pnp.js
+ rm -f .pnp.*
## NPM artifacts
rm -rf **/node_modules
rm -f **/package-lock.json
diff --git a/tgui/bin/tgui-bench.bat b/tgui/bin/tgui-bench.bat
new file mode 100644
index 0000000000..da22a7b2ae
--- /dev/null
+++ b/tgui/bin/tgui-bench.bat
@@ -0,0 +1,9 @@
+@echo off
+rem Copyright (c) 2020 Aleksej Komarov
+rem SPDX-License-Identifier: MIT
+call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\tgui_.ps1" --bench %*
+rem Pause if launched in a separate shell unless initiated from powershell
+echo %PSModulePath% | findstr %USERPROFILE% >NUL
+if %errorlevel% equ 0 exit 0
+echo %cmdcmdline% | find /i "/c"
+if %errorlevel% equ 0 pause
diff --git a/tgui/bin/tgui_.ps1 b/tgui/bin/tgui_.ps1
index 54585c6252..a1909fe70e 100644
--- a/tgui/bin/tgui_.ps1
+++ b/tgui/bin/tgui_.ps1
@@ -50,6 +50,10 @@ function task-dev-server {
yarn node --experimental-modules "packages/tgui-dev-server/index.js" @Args
}
+function task-bench {
+ yarn tgui:bench @Args
+}
+
## Run a linter through all packages
function task-lint {
yarn run tsc
@@ -75,7 +79,7 @@ function task-clean {
Remove-Quiet -Force ".yarn\build-state.yml"
Remove-Quiet -Force ".yarn\install-state.gz"
Remove-Quiet -Force ".yarn\install-target"
- Remove-Quiet -Force ".pnp.js"
+ Remove-Quiet -Force ".pnp.*"
## NPM artifacts
Get-ChildItem -Path "." -Include "node_modules" -Recurse -File:$false | Remove-Item -Recurse -Force
Remove-Quiet -Force "package-lock.json"
@@ -132,6 +136,13 @@ if ($Args.Length -gt 0) {
task-webpack --mode=production --analyze
exit 0
}
+
+ if ($Args[0] -eq "--bench") {
+ $Rest = $Args | Select-Object -Skip 1
+ task-install
+ task-bench --wait-on-error
+ exit 0
+ }
}
## Make a production webpack build
diff --git a/tgui/global.d.ts b/tgui/global.d.ts
index aa8495a249..0f69e37153 100644
--- a/tgui/global.d.ts
+++ b/tgui/global.d.ts
@@ -4,184 +4,180 @@
* @license MIT
*/
-declare global {
- // Webpack asset modules.
- // Should match extensions used in webpack config.
- declare module '*.png' {
- const content: string;
- export default content;
- }
-
- declare module '*.jpg' {
- const content: string;
- export default content;
- }
-
- declare module '*.svg' {
- const content: string;
- export default content;
- }
-
- type TguiMessage = {
- type: string;
- payload?: any;
- [key: string]: any;
- };
-
- type ByondType = {
- /**
- * ID of the Byond window this script is running on.
- * Can be used as a parameter to winget/winset.
- */
- windowId: string;
-
- /**
- * True if javascript is running in BYOND.
- */
- IS_BYOND: boolean;
-
- /**
- * Version of Trident engine of Internet Explorer. Null if N/A.
- */
- TRIDENT: number | null;
-
- /**
- * True if browser is IE8 or lower.
- */
- IS_LTE_IE8: boolean;
-
- /**
- * True if browser is IE9 or lower.
- */
- IS_LTE_IE9: boolean;
-
- /**
- * True if browser is IE10 or lower.
- */
- IS_LTE_IE10: boolean;
-
- /**
- * True if browser is IE11 or lower.
- */
- IS_LTE_IE11: boolean;
-
- /**
- * Makes a BYOND call.
- *
- * If path is empty, this will trigger a Topic call.
- * You can reference a specific object by setting the "src" parameter.
- *
- * See: https://secure.byond.com/docs/ref/skinparams.html
- */
- call(path: string, params: object): void;
-
- /**
- * Makes an asynchronous BYOND call. Returns a promise.
- */
- callAsync(path: string, params: object): Promise;
-
- /**
- * Makes a Topic call.
- *
- * You can reference a specific object by setting the "src" parameter.
- */
- topic(params: object): void;
-
- /**
- * Runs a command or a verb.
- */
- command(command: string): void;
-
- /**
- * Retrieves all properties of the BYOND skin element.
- *
- * Returns a promise with a key-value object containing all properties.
- */
- winget(id: string | null): Promise;
-
- /**
- * Retrieves all properties of the BYOND skin element.
- *
- * Returns a promise with a key-value object containing all properties.
- */
- winget(id: string | null, propName: '*'): Promise;
-
- /**
- * Retrieves an exactly one property of the BYOND skin element,
- * as defined in `propName`.
- *
- * Returns a promise with the value of that property.
- */
- winget(id: string | null, propName: string): Promise;
-
- /**
- * Retrieves multiple properties of the BYOND skin element,
- * as defined in the `propNames` array.
- *
- * Returns a promise with a key-value object containing listed properties.
- */
- winget(id: string | null, propNames: string[]): Promise;
-
- /**
- * Assigns properties to BYOND skin elements in bulk.
- */
- winset(props: object): void;
-
- /**
- * Assigns properties to the BYOND skin element.
- */
- winset(id: string | null, props: object): void;
-
- /**
- * Sets a property on the BYOND skin element to a certain value.
- */
- winset(id: string | null, propName: string, propValue: any): void;
-
- /**
- * Parses BYOND JSON.
- *
- * Uses a special encoding to preserve `Infinity` and `NaN`.
- */
- parseJson(text: string): any;
-
- /**
- * Sends a message to `/datum/tgui_window` which hosts this window instance.
- */
- sendMessage(type: string, payload?: any): void;
- sendMessage(message: TguiMessage): void;
-
- /**
- * Subscribe to incoming messages that were sent from `/datum/tgui_window`.
- */
- subscribe(listener: (type: string, payload: any) => void): void;
-
- /**
- * Subscribe to incoming messages *of some specific type*
- * that were sent from `/datum/tgui_window`.
- */
- subscribeTo(type: string, listener: (payload: any) => void): void;
-
- /**
- * Loads a stylesheet into the document.
- */
- loadCss(url: string): void;
-
- /**
- * Loads a script into the document.
- */
- loadJs(url: string): void;
- };
-
- /**
- * Object that provides access to Byond Skin API and is available in
- * any tgui application.
- */
- const Byond: ByondType;
-
- interface Window {
- Byond: ByondType;
- }
-
+// Webpack asset modules.
+// Should match extensions used in webpack config.
+declare module '*.png' {
+ const content: string;
+ export default content;
}
-export {};
+declare module '*.jpg' {
+ const content: string;
+ export default content;
+}
+
+declare module '*.svg' {
+ const content: string;
+ export default content;
+}
+
+type TguiMessage = {
+ type: string;
+ payload?: any;
+ [key: string]: any;
+};
+
+
+type ByondType = {
+ /**
+ * ID of the Byond window this script is running on.
+ * Can be used as a parameter to winget/winset.
+ */
+ windowId: string;
+
+ /**
+ * True if javascript is running in BYOND.
+ */
+ IS_BYOND: boolean;
+
+ /**
+ * Version of Trident engine of Internet Explorer. Null if N/A.
+ */
+ TRIDENT: number | null;
+
+ /**
+ * True if browser is IE8 or lower.
+ */
+ IS_LTE_IE8: boolean;
+
+ /**
+ * True if browser is IE9 or lower.
+ */
+ IS_LTE_IE9: boolean;
+
+ /**
+ * True if browser is IE10 or lower.
+ */
+ IS_LTE_IE10: boolean;
+
+ /**
+ * True if browser is IE11 or lower.
+ */
+ IS_LTE_IE11: boolean;
+
+ /**
+ * Makes a BYOND call.
+ *
+ * If path is empty, this will trigger a Topic call.
+ * You can reference a specific object by setting the "src" parameter.
+ *
+ * See: https://secure.byond.com/docs/ref/skinparams.html
+ */
+ call(path: string, params: object): void;
+
+ /**
+ * Makes an asynchronous BYOND call. Returns a promise.
+ */
+ callAsync(path: string, params: object): Promise;
+
+ /**
+ * Makes a Topic call.
+ *
+ * You can reference a specific object by setting the "src" parameter.
+ */
+ topic(params: object): void;
+
+ /**
+ * Runs a command or a verb.
+ */
+ command(command: string): void;
+
+ /**
+ * Retrieves all properties of the BYOND skin element.
+ *
+ * Returns a promise with a key-value object containing all properties.
+ */
+ winget(id: string | null): Promise;
+
+ /**
+ * Retrieves all properties of the BYOND skin element.
+ *
+ * Returns a promise with a key-value object containing all properties.
+ */
+ winget(id: string | null, propName: '*'): Promise;
+
+ /**
+ * Retrieves an exactly one property of the BYOND skin element,
+ * as defined in `propName`.
+ *
+ * Returns a promise with the value of that property.
+ */
+ winget(id: string | null, propName: string): Promise;
+
+ /**
+ * Retrieves multiple properties of the BYOND skin element,
+ * as defined in the `propNames` array.
+ *
+ * Returns a promise with a key-value object containing listed properties.
+ */
+ winget(id: string | null, propNames: string[]): Promise;
+
+ /**
+ * Assigns properties to BYOND skin elements in bulk.
+ */
+ winset(props: object): void;
+
+ /**
+ * Assigns properties to the BYOND skin element.
+ */
+ winset(id: string | null, props: object): void;
+
+ /**
+ * Sets a property on the BYOND skin element to a certain value.
+ */
+ winset(id: string | null, propName: string, propValue: any): void;
+
+ /**
+ * Parses BYOND JSON.
+ *
+ * Uses a special encoding to preserve `Infinity` and `NaN`.
+ */
+ parseJson(text: string): any;
+
+ /**
+ * Sends a message to `/datum/tgui_window` which hosts this window instance.
+ */
+ sendMessage(type: string, payload?: any): void;
+ sendMessage(message: TguiMessage): void;
+
+ /**
+ * Subscribe to incoming messages that were sent from `/datum/tgui_window`.
+ */
+ subscribe(listener: (type: string, payload: any) => void): void;
+
+ /**
+ * Subscribe to incoming messages *of some specific type*
+ * that were sent from `/datum/tgui_window`.
+ */
+ subscribeTo(type: string, listener: (payload: any) => void): void;
+
+ /**
+ * Loads a stylesheet into the document.
+ */
+ loadCss(url: string): void;
+
+ /**
+ * Loads a script into the document.
+ */
+ loadJs(url: string): void;
+};
+
+/**
+ * Object that provides access to Byond Skin API and is available in
+ * any tgui application.
+ */
+const Byond: ByondType;
+
+interface Window {
+ Byond: ByondType;
+}
diff --git a/tgui/jest.config.js b/tgui/jest.config.js
index 5802332817..e654f0089b 100644
--- a/tgui/jest.config.js
+++ b/tgui/jest.config.js
@@ -4,6 +4,9 @@ module.exports = {
'/packages/**/__tests__/*.{js,ts,tsx}',
'/packages/**/*.{spec,test}.{js,ts,tsx}',
],
+ testPathIgnorePatterns: [
+ '/packages/tgui-bench',
+ ],
testEnvironment: 'jsdom',
testRunner: require.resolve('jest-circus/runner'),
transform: {
diff --git a/tgui/package.json b/tgui/package.json
index cdaea68107..7fc6d67cd2 100644
--- a/tgui/package.json
+++ b/tgui/package.json
@@ -6,6 +6,18 @@
"workspaces": [
"packages/*"
],
+ "scripts": {
+ "tgui:build": "webpack",
+ "tgui:analyze": "webpack --analyze",
+ "tgui:dev": "node --experimental-modules packages/tgui-dev-server/index.js",
+ "tgui:lint": "eslint packages --ext .js,.cjs,.ts,.tsx",
+ "tgui:sonar": "eslint packages --ext .js,.cjs,.ts,.tsx -c .eslintrc-harder.yml",
+ "tgui:tsc": "tsc",
+ "tgui:test": "jest --watch",
+ "tgui:test-simple": "CI=true jest --color",
+ "tgui:test-ci": "CI=true jest --color --collect-coverage",
+ "tgui:bench": "webpack --env TGUI_BENCH=1 && node packages/tgui-bench/index.js"
+ },
"dependencies": {
"@babel/core": "^7.18.0",
"@babel/eslint-parser": "^7.17.0",
@@ -16,6 +28,8 @@
"@types/jest": "^27.5.1",
"@types/jsdom": "^16.2.14",
"@types/node": "^17.0.35",
+ "@types/webpack": "^5.28.0",
+ "@types/webpack-env": "^1.17.0",
"@typescript-eslint/parser": "^5.25.0",
"babel-jest": "^28.1.0",
"babel-loader": "^8.2.5",
diff --git a/tgui/packages/common/collections.ts b/tgui/packages/common/collections.ts
index 7abe49ff23..f0a8bfeba3 100644
--- a/tgui/packages/common/collections.ts
+++ b/tgui/packages/common/collections.ts
@@ -4,64 +4,6 @@
* @license MIT
*/
-/**
- * Converts a given collection to an array.
- *
- * - Arrays are returned unmodified;
- * - If object was provided, keys will be discarded;
- * - Everything else will result in an empty array.
- *
- * @returns {any[]}
- */
-export const toArray = collection => {
- if (Array.isArray(collection)) {
- return collection;
- }
- if (typeof collection === 'object') {
- const hasOwnProperty = Object.prototype.hasOwnProperty;
- const result = [];
- for (let i in collection) {
- if (hasOwnProperty.call(collection, i)) {
- result.push(collection[i]);
- }
- }
- return result;
- }
- return [];
-};
-
-/**
- * Converts a given object to an array, and appends a key to every
- * object inside of that array.
- *
- * Example input (object):
- * ```
- * {
- * 'Foo': { info: 'Hello world!' },
- * 'Bar': { info: 'Hello world!' },
- * }
- * ```
- *
- * Example output (array):
- * ```
- * [
- * { key: 'Foo', info: 'Hello world!' },
- * { key: 'Bar', info: 'Hello world!' },
- * ]
- * ```
- *
- * @template T
- * @param {{ [key: string]: T }} obj Object, or in DM terms, an assoc array
- * @param {string} keyProp Property, to which key will be assigned
- * @returns {T[]} Array of keyed objects
- */
-export const toKeyedArray = (obj, keyProp = 'key') => {
- return map((item, key) => ({
- [keyProp]: key,
- ...item,
- }))(obj);
-};
-
/**
* Iterates over elements of collection, returning an array of all elements
* iteratee returns truthy for. The predicate is invoked with three
@@ -72,21 +14,40 @@ export const toKeyedArray = (obj, keyProp = 'key') => {
*
* @returns {any[]}
*/
-export const filter = iterateeFn => collection => {
- if (collection === null || collection === undefined) {
- return collection;
- }
- if (Array.isArray(collection)) {
- const result = [];
- for (let i = 0; i < collection.length; i++) {
- const item = collection[i];
- if (iterateeFn(item, i, collection)) {
- result.push(item);
+export const filter = (iterateeFn: (
+ input: T,
+ index: number,
+ collection: T[],
+) => boolean) =>
+ (collection: T[]): T[] => {
+ if (collection === null || collection === undefined) {
+ return collection;
}
- }
- return result;
- }
- throw new Error(`filter() can't iterate on type ${typeof collection}`);
+ if (Array.isArray(collection)) {
+ const result: T[] = [];
+ for (let i = 0; i < collection.length; i++) {
+ const item = collection[i];
+ if (iterateeFn(item, i, collection)) {
+ result.push(item);
+ }
+ }
+ return result;
+ }
+ throw new Error(`filter() can't iterate on type ${typeof collection}`);
+ };
+
+type MapFunction = {
+ (iterateeFn: (
+ value: T,
+ index: number,
+ collection: T[],
+ ) => U): (collection: T[]) => U[];
+
+ (iterateeFn: (
+ value: T,
+ index: K,
+ collection: Record,
+ ) => U): (collection: Record) => U[];
};
/**
@@ -96,32 +57,25 @@ export const filter = iterateeFn => collection => {
*
* If collection is 'null' or 'undefined', it will be returned "as is"
* without emitting any errors (which can be useful in some cases).
- *
- * @returns {any[]}
*/
-export const map = iterateeFn => collection => {
- if (collection === null || collection === undefined) {
- return collection;
- }
- if (Array.isArray(collection)) {
- const result = [];
- for (let i = 0; i < collection.length; i++) {
- result.push(iterateeFn(collection[i], i, collection));
+export const map: MapFunction = (iterateeFn) =>
+ (collection: T[]): U[] => {
+ if (collection === null || collection === undefined) {
+ return collection;
}
- return result;
- }
- if (typeof collection === 'object') {
- const hasOwnProperty = Object.prototype.hasOwnProperty;
- const result = [];
- for (let i in collection) {
- if (hasOwnProperty.call(collection, i)) {
- result.push(iterateeFn(collection[i], i, collection));
- }
+
+ if (Array.isArray(collection)) {
+ return collection.map(iterateeFn);
}
- return result;
- }
- throw new Error(`map() can't iterate on type ${typeof collection}`);
-};
+
+ if (typeof collection === 'object') {
+ return Object.entries(collection).map(([key, value]) => {
+ return iterateeFn(value, key, collection);
+ });
+ }
+
+ throw new Error(`map() can't iterate on type ${typeof collection}`);
+ };
const COMPARATOR = (objA, objB) => {
const criteriaA = objA.criteria;
@@ -148,28 +102,35 @@ const COMPARATOR = (objA, objB) => {
*
* @returns {any[]}
*/
-export const sortBy = (...iterateeFns) => array => {
- if (!Array.isArray(array)) {
- return array;
- }
- let length = array.length;
- // Iterate over the array to collect criteria to sort it by
- let mappedArray = [];
- for (let i = 0; i < length; i++) {
- const value = array[i];
- mappedArray.push({
- criteria: iterateeFns.map(fn => fn(value)),
- value,
- });
- }
- // Sort criteria using the base comparator
- mappedArray.sort(COMPARATOR);
- // Unwrap values
- while (length--) {
- mappedArray[length] = mappedArray[length].value;
- }
- return mappedArray;
-};
+export const sortBy = (
+ ...iterateeFns: ((input: T) => unknown)[]
+) => (array: T[]): T[] => {
+ if (!Array.isArray(array)) {
+ return array;
+ }
+ let length = array.length;
+ // Iterate over the array to collect criteria to sort it by
+ let mappedArray: {
+ criteria: unknown[],
+ value: T,
+ }[] = [];
+ for (let i = 0; i < length; i++) {
+ const value = array[i];
+ mappedArray.push({
+ criteria: iterateeFns.map(fn => fn(value)),
+ value,
+ });
+ }
+ // Sort criteria using the base comparator
+ mappedArray.sort(COMPARATOR);
+
+ // Unwrap values
+ const values: T[] = [];
+ while (length--) {
+ values[length] = mappedArray[length].value;
+ }
+ return values;
+ };
export const sort = sortBy();
@@ -212,40 +173,38 @@ export const reduce = (reducerFn, initialValue) => array => {
* is determined by the order they occur in the array. The iteratee is
* invoked with one argument: value.
*/
-/* eslint-disable indent */
export const uniqBy = (
iterateeFn?: (value: T) => unknown
-) => (array: T[]) => {
- const { length } = array;
- const result = [];
- const seen = iterateeFn ? [] : result;
- let index = -1;
- outer:
- while (++index < length) {
- let value: T | 0 = array[index];
- const computed = iterateeFn ? iterateeFn(value) : value;
- value = value !== 0 ? value : 0;
- if (computed === computed) {
- let seenIndex = seen.length;
- while (seenIndex--) {
- if (seen[seenIndex] === computed) {
- continue outer;
+) => (array: T[]): T[] => {
+ const { length } = array;
+ const result: T[] = [];
+ const seen: unknown[] = iterateeFn ? [] : result;
+ let index = -1;
+ outer:
+ while (++index < length) {
+ let value: T | 0 = array[index];
+ const computed = iterateeFn ? iterateeFn(value) : value;
+ if (computed === computed) {
+ let seenIndex = seen.length;
+ while (seenIndex--) {
+ if (seen[seenIndex] === computed) {
+ continue outer;
+ }
}
+ if (iterateeFn) {
+ seen.push(computed);
+ }
+ result.push(value);
}
- if (iterateeFn) {
- seen.push(computed);
+ else if (!seen.includes(computed)) {
+ if (seen !== result) {
+ seen.push(computed);
+ }
+ result.push(value);
}
- result.push(value);
}
- else if (!seen.includes(computed)) {
- if (seen !== result) {
- seen.push(computed);
- }
- result.push(value);
- }
- }
- return result;
-};
+ return result;
+ };
/* eslint-enable indent */
export const uniq = uniqBy();
@@ -261,17 +220,19 @@ type Zip = {
*/
export const zip = (...arrays: T): Zip => {
if (arrays.length === 0) {
- return;
+ return [];
}
const numArrays = arrays.length;
const numValues = arrays[0].length;
- const result = [];
+ const result: Zip = [];
for (let valueIndex = 0; valueIndex < numValues; valueIndex++) {
- const entry = [];
+ const entry: unknown[] = [];
for (let arrayIndex = 0; arrayIndex < numArrays; arrayIndex++) {
entry.push(arrays[arrayIndex][valueIndex]);
}
- result.push(entry);
+
+ // I tried everything to remove this any, and have no idea how to do it.
+ result.push(entry as any);
}
return result;
};
@@ -280,9 +241,8 @@ export const zip = (...arrays: T): Zip => {
* This method is like "zip" except that it accepts iteratee to
* specify how grouped values should be combined. The iteratee is
* invoked with the elements of each group.
- *
- * @returns {any[]}
*/
-export const zipWith = iterateeFn => (...arrays) => {
- return map(values => iterateeFn(...values))(zip(...arrays));
-};
+export const zipWith = (iterateeFn: (...values: T[]) => U) =>
+ (...arrays: T[][]): U[] => {
+ return map((values: T[]) => iterateeFn(...values))(zip(...arrays));
+ };
diff --git a/tgui/packages/common/types.ts b/tgui/packages/common/types.ts
new file mode 100644
index 0000000000..a92ac122d9
--- /dev/null
+++ b/tgui/packages/common/types.ts
@@ -0,0 +1,5 @@
+/**
+ * Returns the arguments of a function F as an array.
+ */
+export type ArgumentsOf
+ = F extends (...args: infer A) => unknown ? A : never;
diff --git a/tgui/packages/tgfont/dist/tgfont.css b/tgui/packages/tgfont/dist/tgfont.css
index 6295dcce8f..ad732b8fc0 100644
--- a/tgui/packages/tgfont/dist/tgfont.css
+++ b/tgui/packages/tgfont/dist/tgfont.css
@@ -1,7 +1,7 @@
@font-face {
font-family: "tgfont";
- src: url("./tgfont.woff2?8fcc44d209cc0a286e2fedc5edea15e7") format("woff2"),
-url("./tgfont.eot?8fcc44d209cc0a286e2fedc5edea15e7#iefix") format("embedded-opentype");
+ src: url("./tgfont.woff2?45c3c7acc69dd413375d77898d24e41e") format("woff2"),
+url("./tgfont.eot?45c3c7acc69dd413375d77898d24e41e#iefix") format("embedded-opentype");
}
i[class^="tg-"]:before, i[class*=" tg-"]:before {
@@ -21,21 +21,30 @@ i[class^="tg-"]:before, i[class*=" tg-"]:before {
.tg-air-tank:before {
content: "\f102";
}
-.tg-image-minus:before {
+.tg-bad-touch:before {
content: "\f103";
}
-.tg-image-plus:before {
+.tg-image-minus:before {
content: "\f104";
}
-.tg-nanotrasen-logo:before {
+.tg-image-plus:before {
content: "\f105";
}
-.tg-sound-minus:before {
+.tg-nanotrasen-logo:before {
content: "\f106";
}
-.tg-sound-plus:before {
+.tg-non-binary:before {
content: "\f107";
}
-.tg-syndicate-logo:before {
+.tg-prosthetic-leg:before {
content: "\f108";
}
+.tg-sound-minus:before {
+ content: "\f109";
+}
+.tg-sound-plus:before {
+ content: "\f10a";
+}
+.tg-syndicate-logo:before {
+ content: "\f10b";
+}
diff --git a/tgui/packages/tgfont/dist/tgfont.eot b/tgui/packages/tgfont/dist/tgfont.eot
index a1ffb10df4..ba307e0672 100644
Binary files a/tgui/packages/tgfont/dist/tgfont.eot and b/tgui/packages/tgfont/dist/tgfont.eot differ
diff --git a/tgui/packages/tgfont/dist/tgfont.woff2 b/tgui/packages/tgfont/dist/tgfont.woff2
index 4e2dae731a..3f54573899 100644
Binary files a/tgui/packages/tgfont/dist/tgfont.woff2 and b/tgui/packages/tgfont/dist/tgfont.woff2 differ
diff --git a/tgui/packages/tgfont/icons/ATTRIBUTIONS.md b/tgui/packages/tgfont/icons/ATTRIBUTIONS.md
new file mode 100644
index 0000000000..2f218388d3
--- /dev/null
+++ b/tgui/packages/tgfont/icons/ATTRIBUTIONS.md
@@ -0,0 +1,6 @@
+bad-touch.svg contains:
+- hug by Phạm Thanh Lộc from the Noun Project
+- Fight by Rudez Studio from the Noun Project
+
+prosthetic-leg.svg contains:
+- prosthetic leg by Gan Khoon Lay from the Noun Project
diff --git a/tgui/packages/tgfont/icons/bad-touch.svg b/tgui/packages/tgfont/icons/bad-touch.svg
new file mode 100644
index 0000000000..a2566c1670
--- /dev/null
+++ b/tgui/packages/tgfont/icons/bad-touch.svg
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tgui/packages/tgfont/icons/non-binary.svg b/tgui/packages/tgfont/icons/non-binary.svg
new file mode 100644
index 0000000000..9aaec674bb
--- /dev/null
+++ b/tgui/packages/tgfont/icons/non-binary.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tgui/packages/tgfont/icons/prosthetic-leg.svg b/tgui/packages/tgfont/icons/prosthetic-leg.svg
new file mode 100644
index 0000000000..c1f6ceee3f
--- /dev/null
+++ b/tgui/packages/tgfont/icons/prosthetic-leg.svg
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/tgui/packages/tgfont/package.json b/tgui/packages/tgfont/package.json
index 90c38f82db..5e5a3f060c 100644
--- a/tgui/packages/tgfont/package.json
+++ b/tgui/packages/tgfont/package.json
@@ -2,10 +2,10 @@
"private": true,
"name": "tgfont",
"version": "1.0.0",
- "dependencies": {
- "fantasticon": "^1.2.3"
- },
"scripts": {
"build": "node mkdist.cjs && fantasticon --config config.cjs"
+ },
+ "dependencies": {
+ "fantasticon": "^1.2.3"
}
}
diff --git a/tgui/packages/tgui-bench/entrypoint.tsx b/tgui/packages/tgui-bench/entrypoint.tsx
new file mode 100644
index 0000000000..d41d667894
--- /dev/null
+++ b/tgui/packages/tgui-bench/entrypoint.tsx
@@ -0,0 +1,73 @@
+/**
+ * @file
+ * @copyright 2021 Aleksej Komarov
+ * @license MIT
+ */
+
+import { setupGlobalEvents } from 'tgui/events';
+import 'tgui/styles/main.scss';
+import Benchmark from './lib/benchmark';
+
+const sendMessage = (obj: any) => {
+ const req = new XMLHttpRequest();
+ req.open('POST', `/message`, false);
+ req.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
+ // req.timeout = 250;
+ req.send(JSON.stringify(obj));
+};
+
+const setupApp = async () => {
+ // Delay setup
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', setupApp);
+ return;
+ }
+
+ setupGlobalEvents({
+ ignoreWindowFocus: true,
+ });
+
+ const requireTest = require.context('./tests', false, /\.test\./);
+
+ for (const file of requireTest.keys()) {
+ sendMessage({ type: 'suite-start', file });
+ try {
+ const tests = requireTest(file);
+ await new Promise((resolve) => {
+ const suite = new Benchmark.Suite(file, {
+ onCycle(e) {
+ sendMessage({
+ type: 'suite-cycle',
+ message: String(e.target),
+ });
+ },
+ onComplete() {
+ // This message is somewhat useless, but leaving it here in case
+ // someone has an idea how to show more useful data.
+ // sendMessage({
+ // type: 'suite-complete',
+ // message: 'Fastest is ' + this.filter('fastest').map('name'),
+ // });
+ resolve();
+ },
+ onError(e) {
+ sendMessage({ type: 'error', e });
+ resolve();
+ },
+ });
+ for (const [name, fn] of Object.entries(tests)) {
+ if (typeof fn === 'function') {
+ suite.add(name, fn);
+ }
+ }
+ suite.run();
+ });
+ }
+ catch (error) {
+ sendMessage({ type: 'error', error });
+ }
+ }
+ sendMessage({ type: 'finished' });
+};
+
+setupApp();
diff --git a/tgui/packages/tgui-bench/index.js b/tgui/packages/tgui-bench/index.js
new file mode 100644
index 0000000000..e29ad9a5c2
--- /dev/null
+++ b/tgui/packages/tgui-bench/index.js
@@ -0,0 +1,93 @@
+/**
+ * @file
+ * @copyright 2021 Aleksej Komarov
+ * @license MIT
+ */
+
+const fs = require('fs');
+const path = require('path');
+const { exec } = require('child_process');
+const { fastify } = require('fastify');
+
+process.chdir(__dirname);
+
+const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time));
+
+const IE_TIMEOUT_SECONDS = 60;
+
+const setup = async () => {
+ const server = fastify();
+
+ let hasResponded = false;
+
+ let assets = '';
+ assets += `\n`;
+
+ const publicDir = path.resolve(__dirname, '../../public');
+ const page = fs.readFileSync(path.join(publicDir, 'tgui.html'), 'utf-8')
+ .replace('\n', assets);
+
+ server.register(require('@fastify/static'), {
+ root: publicDir,
+ });
+
+ server.get('/', async (req, res) => {
+ return res.type('text/html').send(page);
+ });
+
+ server.post('/message', async (req, res) => {
+ if (!hasResponded) {
+ process.stdout.write('\n');
+ hasResponded = true;
+ }
+ const { type, ...rest } = req.body;
+ if (type === 'suite-start') {
+ console.log(`=> Test '${rest.file}'`);
+ return res.send();
+ }
+ if (type === 'suite-cycle') {
+ console.log(rest.message);
+ return res.send();
+ }
+ if (type === 'suite-complete') {
+ console.log(rest.message);
+ return res.send();
+ }
+ if (type === 'finished') {
+ await res.send();
+ process.exit(0);
+ }
+ // Unhandled message
+ console.log(req.body);
+ return res.send();
+ });
+
+ try {
+ await server.listen(3002, '0.0.0.0');
+ }
+ catch (err) {
+ console.error(err);
+ process.exit(1);
+ }
+
+ if (process.platform === 'win32') {
+ exec(`start "" "iexplore" "http://127.0.0.1:3002"`);
+ }
+
+ console.log('Waiting for Internet Explorer to respond.');
+ for (let i = 0; i < IE_TIMEOUT_SECONDS; i++) {
+ await sleep(1000);
+ if (hasResponded) {
+ return;
+ }
+ process.stdout.write('.');
+ }
+ process.stdout.write('\n');
+ console.error('Did not receive a response, exiting.');
+ process.exit(1);
+};
+
+setup();
diff --git a/tgui/packages/tgui-bench/lib/benchmark.d.ts b/tgui/packages/tgui-bench/lib/benchmark.d.ts
new file mode 100644
index 0000000000..7f3310005f
--- /dev/null
+++ b/tgui/packages/tgui-bench/lib/benchmark.d.ts
@@ -0,0 +1,219 @@
+/* eslint-disable */
+// Type definitions for Benchmark v2.1.4
+// Project: https://benchmarkjs.com
+// Definitions by: Asana
+// Charlie Fish
+// Blair Zajac
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+declare class Benchmark {
+ static filter(arr: T[], callback: (value: T) => any, thisArg?: any): T[];
+ static filter(arr: T[], filter: string, thisArg?: any): T[];
+ static formatNumber(num: number): string;
+ static join(obj: Object, separator1?: string, separator2?: string): string;
+ static invoke(
+ benches: Benchmark[],
+ name: string | Object,
+ ...args: any[]
+ ): any[];
+ static runInContext(context: Object): Function;
+
+ static each(obj: Object | any[], callback: Function, thisArg?: any): void;
+ static forEach(arr: T[], callback: (value: T) => any, thisArg?: any): void;
+ static forOwn(obj: Object, callback: Function, thisArg?: any): void;
+ static has(obj: Object, path: any[] | string): boolean;
+ static indexOf(arr: T[], value: T, fromIndex?: number): number;
+ static map(arr: T[], callback: (value: T) => K, thisArg?: any): K[];
+ static reduce(
+ arr: T[],
+ callback: (accumulator: K, value: T) => K,
+ thisArg?: any
+ ): K;
+
+ static options: Benchmark.Options;
+ static platform: Benchmark.Platform;
+ static support: Benchmark.Support;
+ static version: string;
+
+ constructor(fn: Function | string, options?: Benchmark.Options);
+ constructor(name: string, fn: Function | string, options?: Benchmark.Options);
+ constructor(name: string, options?: Benchmark.Options);
+ constructor(options: Benchmark.Options);
+
+ id: number;
+ name: string;
+ count: number;
+ cycles: number;
+ hz: number;
+ compiled: Function | string;
+ error: Error;
+ fn: Function | string;
+ aborted: boolean;
+ running: boolean;
+ setup: Function | string;
+ teardown: Function | string;
+
+ stats: Benchmark.Stats;
+ times: Benchmark.Times;
+
+ abort(): Benchmark;
+ clone(options: Benchmark.Options): Benchmark;
+ compare(benchmark: Benchmark): number;
+ emit(type: string | Object): any;
+ listeners(type: string): Function[];
+ off(type?: string, listener?: Function): Benchmark;
+ off(types: string[]): Benchmark;
+ on(type?: string, listener?: Function): Benchmark;
+ on(types: string[]): Benchmark;
+ reset(): Benchmark;
+ run(options?: Benchmark.Options): Benchmark;
+ toString(): string;
+}
+
+declare namespace Benchmark {
+ export interface Options {
+ async?: boolean | undefined;
+ defer?: boolean | undefined;
+ delay?: number | undefined;
+ id?: string | undefined;
+ initCount?: number | undefined;
+ maxTime?: number | undefined;
+ minSamples?: number | undefined;
+ minTime?: number | undefined;
+ name?: string | undefined;
+ onAbort?: Function | undefined;
+ onComplete?: Function | undefined;
+ onCycle?: Function | undefined;
+ onError?: Function | undefined;
+ onReset?: Function | undefined;
+ onStart?: Function | undefined;
+ setup?: Function | string | undefined;
+ teardown?: Function | string | undefined;
+ fn?: Function | string | undefined;
+ queued?: boolean | undefined;
+ }
+
+ export interface Platform {
+ description: string;
+ layout: string;
+ product: string;
+ name: string;
+ manufacturer: string;
+ os: string;
+ prerelease: string;
+ version: string;
+ toString(): string;
+ }
+
+ export interface Support {
+ browser: boolean;
+ timeout: boolean;
+ decompilation: boolean;
+ }
+
+ export interface Stats {
+ moe: number;
+ rme: number;
+ sem: number;
+ deviation: number;
+ mean: number;
+ sample: any[];
+ variance: number;
+ }
+
+ export interface Times {
+ cycle: number;
+ elapsed: number;
+ period: number;
+ timeStamp: number;
+ }
+
+ export class Deferred {
+ constructor(clone: Benchmark);
+
+ benchmark: Benchmark;
+ cycles: number;
+ elapsed: number;
+ timeStamp: number;
+
+ resolve(): void;
+ }
+
+ export interface Target {
+ options: Options;
+ async?: boolean | undefined;
+ defer?: boolean | undefined;
+ delay?: number | undefined;
+ initCount?: number | undefined;
+ maxTime?: number | undefined;
+ minSamples?: number | undefined;
+ minTime?: number | undefined;
+ name?: string | undefined;
+ fn?: Function | undefined;
+ id: number;
+ stats?: Stats | undefined;
+ times?: Times | undefined;
+ running: boolean;
+ count?: number | undefined;
+ compiled?: Function | undefined;
+ cycles?: number | undefined;
+ hz?: number | undefined;
+ }
+
+ export class Event {
+ constructor(type: string | Object);
+
+ aborted: boolean;
+ cancelled: boolean;
+ currentTarget: Object;
+ result: any;
+ target: Target;
+ timeStamp: number;
+ type: string;
+ }
+
+ export class Suite {
+ static options: { name: string };
+
+ constructor(name?: string, options?: Options);
+
+ length: number;
+ aborted: boolean;
+ running: boolean;
+
+ abort(): Suite;
+ add(name: string, fn: Function | string, options?: Options): Suite;
+ add(fn: Function | string, options?: Options): Suite;
+ add(name: string, options?: Options): Suite;
+ add(options: Options): Suite;
+ clone(options: Options): Suite;
+ emit(type: string | Object): any;
+ filter(callback: Function | string): Suite;
+ join(separator?: string): string;
+ listeners(type: string): Function[];
+ off(type?: string, callback?: Function): Suite;
+ off(types: string[]): Suite;
+ on(type?: string, callback?: Function): Suite;
+ on(types: string[]): Suite;
+ push(benchmark: Benchmark): number;
+ reset(): Suite;
+ run(options?: Options): Suite;
+ reverse(): any[];
+ sort(compareFn: (a: any, b: any) => number): any[];
+ splice(start: number, deleteCount?: number): any[];
+ unshift(benchmark: Benchmark): number;
+
+ each(callback: Function): Suite;
+ forEach(callback: Function): Suite;
+ indexOf(value: any): number;
+ map(callback: Function | string): any[];
+ reduce(callback: Function, accumulator: T): T;
+
+ pop(): Function;
+ shift(): Benchmark;
+ slice(start: number, end: number): any[];
+ slice(start: number, deleteCount: number, ...values: any[]): any[];
+ }
+}
+
+export = Benchmark;
diff --git a/tgui/packages/tgui-bench/lib/benchmark.js b/tgui/packages/tgui-bench/lib/benchmark.js
new file mode 100644
index 0000000000..1e76cec708
--- /dev/null
+++ b/tgui/packages/tgui-bench/lib/benchmark.js
@@ -0,0 +1,2759 @@
+/* eslint-disable */
+/*!
+ * Benchmark.js
+ * Copyright 2010-2016 Mathias Bynens
+ * Based on JSLitmus.js, copyright Robert Kieffer
+ * Modified by John-David Dalton
+ * Manually stripped from useless junk by /tg/station13 maintainers.
+ * Available under MIT license
+ */
+module.exports = (function() {
+ 'use strict';
+
+ /** Used as a safe reference for `undefined` in pre ES5 environments. */
+ var undefined;
+
+ /** Used to determine if values are of the language type Object. */
+ var objectTypes = {
+ 'function': true,
+ 'object': true
+ };
+
+ /** Used as a reference to the global object. */
+ var root = (objectTypes[typeof window] && window) || this;
+
+ /** Detect free variable `define`. */
+ var freeDefine = false;
+
+ /** Used to assign each benchmark an incremented id. */
+ var counter = 0;
+
+ /** Used to detect primitive types. */
+ var rePrimitive = /^(?:boolean|number|string|undefined)$/;
+
+ /** Used to make every compiled test unique. */
+ var uidCounter = 0;
+
+ /** Used to assign default `context` object properties. */
+ var contextProps = [
+ 'Array', 'Date', 'Function', 'Math', 'Object', 'RegExp', 'String', '_',
+ 'clearTimeout', 'chrome', 'chromium', 'document', 'navigator', 'phantom',
+ 'platform', 'process', 'runtime', 'setTimeout'
+ ];
+
+ /** Used to avoid hz of Infinity. */
+ var divisors = {
+ '1': 4096,
+ '2': 512,
+ '3': 64,
+ '4': 8,
+ '5': 0
+ };
+
+ /**
+ * T-Distribution two-tailed critical values for 95% confidence.
+ * For more info see http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm.
+ */
+ var tTable = {
+ '1': 12.706, '2': 4.303, '3': 3.182, '4': 2.776, '5': 2.571, '6': 2.447,
+ '7': 2.365, '8': 2.306, '9': 2.262, '10': 2.228, '11': 2.201, '12': 2.179,
+ '13': 2.16, '14': 2.145, '15': 2.131, '16': 2.12, '17': 2.11, '18': 2.101,
+ '19': 2.093, '20': 2.086, '21': 2.08, '22': 2.074, '23': 2.069, '24': 2.064,
+ '25': 2.06, '26': 2.056, '27': 2.052, '28': 2.048, '29': 2.045, '30': 2.042,
+ 'infinity': 1.96
+ };
+
+ /**
+ * Critical Mann-Whitney U-values for 95% confidence.
+ * For more info see http://www.saburchill.com/IBbiology/stats/003.html.
+ */
+ var uTable = {
+ '5': [0, 1, 2],
+ '6': [1, 2, 3, 5],
+ '7': [1, 3, 5, 6, 8],
+ '8': [2, 4, 6, 8, 10, 13],
+ '9': [2, 4, 7, 10, 12, 15, 17],
+ '10': [3, 5, 8, 11, 14, 17, 20, 23],
+ '11': [3, 6, 9, 13, 16, 19, 23, 26, 30],
+ '12': [4, 7, 11, 14, 18, 22, 26, 29, 33, 37],
+ '13': [4, 8, 12, 16, 20, 24, 28, 33, 37, 41, 45],
+ '14': [5, 9, 13, 17, 22, 26, 31, 36, 40, 45, 50, 55],
+ '15': [5, 10, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64],
+ '16': [6, 11, 15, 21, 26, 31, 37, 42, 47, 53, 59, 64, 70, 75],
+ '17': [6, 11, 17, 22, 28, 34, 39, 45, 51, 57, 63, 67, 75, 81, 87],
+ '18': [7, 12, 18, 24, 30, 36, 42, 48, 55, 61, 67, 74, 80, 86, 93, 99],
+ '19': [7, 13, 19, 25, 32, 38, 45, 52, 58, 65, 72, 78, 85, 92, 99, 106, 113],
+ '20': [8, 14, 20, 27, 34, 41, 48, 55, 62, 69, 76, 83, 90, 98, 105, 112, 119, 127],
+ '21': [8, 15, 22, 29, 36, 43, 50, 58, 65, 73, 80, 88, 96, 103, 111, 119, 126, 134, 142],
+ '22': [9, 16, 23, 30, 38, 45, 53, 61, 69, 77, 85, 93, 101, 109, 117, 125, 133, 141, 150, 158],
+ '23': [9, 17, 24, 32, 40, 48, 56, 64, 73, 81, 89, 98, 106, 115, 123, 132, 140, 149, 157, 166, 175],
+ '24': [10, 17, 25, 33, 42, 50, 59, 67, 76, 85, 94, 102, 111, 120, 129, 138, 147, 156, 165, 174, 183, 192],
+ '25': [10, 18, 27, 35, 44, 53, 62, 71, 80, 89, 98, 107, 117, 126, 135, 145, 154, 163, 173, 182, 192, 201, 211],
+ '26': [11, 19, 28, 37, 46, 55, 64, 74, 83, 93, 102, 112, 122, 132, 141, 151, 161, 171, 181, 191, 200, 210, 220, 230],
+ '27': [11, 20, 29, 38, 48, 57, 67, 77, 87, 97, 107, 118, 125, 138, 147, 158, 168, 178, 188, 199, 209, 219, 230, 240, 250],
+ '28': [12, 21, 30, 40, 50, 60, 70, 80, 90, 101, 111, 122, 132, 143, 154, 164, 175, 186, 196, 207, 218, 228, 239, 250, 261, 272],
+ '29': [13, 22, 32, 42, 52, 62, 73, 83, 94, 105, 116, 127, 138, 149, 160, 171, 182, 193, 204, 215, 226, 238, 249, 260, 271, 282, 294],
+ '30': [13, 23, 33, 43, 54, 65, 76, 87, 98, 109, 120, 131, 143, 154, 166, 177, 189, 200, 212, 223, 235, 247, 258, 270, 282, 293, 305, 317]
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Create a new `Benchmark` function using the given `context` object.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Object} [context=root] The context object.
+ * @returns {Function} Returns a new `Benchmark` function.
+ */
+ function runInContext(context) {
+ // Exit early if unable to acquire lodash.
+ var _ = context && context._ || require('lodash') || root._;
+ if (!_) {
+ Benchmark.runInContext = runInContext;
+ return Benchmark;
+ }
+ // Avoid issues with some ES3 environments that attempt to use values, named
+ // after built-in constructors like `Object`, for the creation of literals.
+ // ES5 clears this up by stating that literals must use built-in constructors.
+ // See http://es5.github.io/#x11.1.5.
+ context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;
+
+ /** Native constructor references. */
+ var Array = context.Array,
+ Date = context.Date,
+ Function = context.Function,
+ Math = context.Math,
+ Object = context.Object,
+ RegExp = context.RegExp,
+ String = context.String;
+
+ /** Used for `Array` and `Object` method references. */
+ var arrayRef = [],
+ objectProto = Object.prototype;
+
+ /** Native method shortcuts. */
+ var abs = Math.abs,
+ clearTimeout = context.clearTimeout,
+ floor = Math.floor,
+ log = Math.log,
+ max = Math.max,
+ min = Math.min,
+ pow = Math.pow,
+ push = arrayRef.push,
+ setTimeout = context.setTimeout,
+ shift = arrayRef.shift,
+ slice = arrayRef.slice,
+ sqrt = Math.sqrt,
+ toString = objectProto.toString,
+ unshift = arrayRef.unshift;
+
+ /** Detect DOM document object. */
+ var doc = isHostType(context, 'document') && context.document;
+
+ /** Used to access Node.js's high resolution timer. */
+ var processObject = isHostType(context, 'process') && context.process;
+
+ /** Used to prevent a `removeChild` memory leak in IE < 9. */
+ var trash = doc && doc.createElement('div');
+
+ /** Used to integrity check compiled tests. */
+ var uid = 'uid' + _.now();
+
+ /** Used to avoid infinite recursion when methods call each other. */
+ var calledBy = {};
+
+ /**
+ * An object used to flag environments/features.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @type Object
+ */
+ var support = {};
+
+ (function() {
+
+ /**
+ * Detect if running in a browser environment.
+ *
+ * @memberOf Benchmark.support
+ * @type boolean
+ */
+ support.browser = doc && isHostType(context, 'navigator') && !isHostType(context, 'phantom');
+
+ /**
+ * Detect if the Timers API exists.
+ *
+ * @memberOf Benchmark.support
+ * @type boolean
+ */
+ support.timeout = isHostType(context, 'setTimeout') && isHostType(context, 'clearTimeout');
+
+ /**
+ * Detect if function decompilation is support.
+ *
+ * @name decompilation
+ * @memberOf Benchmark.support
+ * @type boolean
+ */
+ try {
+ // Safari 2.x removes commas in object literals from `Function#toString` results.
+ // See http://webk.it/11609 for more details.
+ // Firefox 3.6 and Opera 9.25 strip grouping parentheses from `Function#toString` results.
+ // See http://bugzil.la/559438 for more details.
+ support.decompilation = Function(
+ ('return (' + (function(x) { return { 'x': '' + (1 + x) + '', 'y': 0 }; }) + ')')
+ // Avoid issues with code added by Istanbul.
+ .replace(/__cov__[^;]+;/g, '')
+ )()(0).x === '1';
+ } catch(e) {
+ support.decompilation = false;
+ }
+ }());
+
+ /**
+ * Timer object used by `clock()` and `Deferred#resolve`.
+ *
+ * @private
+ * @type Object
+ */
+ var timer = {
+
+ /**
+ * The timer namespace object or constructor.
+ *
+ * @private
+ * @memberOf timer
+ * @type {Function|Object}
+ */
+ 'ns': Date,
+
+ /**
+ * Starts the deferred timer.
+ *
+ * @private
+ * @memberOf timer
+ * @param {Object} deferred The deferred instance.
+ */
+ 'start': null, // Lazy defined in `clock()`.
+
+ /**
+ * Stops the deferred timer.
+ *
+ * @private
+ * @memberOf timer
+ * @param {Object} deferred The deferred instance.
+ */
+ 'stop': null // Lazy defined in `clock()`.
+ };
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * The Benchmark constructor.
+ *
+ * Note: The Benchmark constructor exposes a handful of lodash methods to
+ * make working with arrays, collections, and objects easier. The lodash
+ * methods are:
+ * [`each/forEach`](https://lodash.com/docs#forEach), [`forOwn`](https://lodash.com/docs#forOwn),
+ * [`has`](https://lodash.com/docs#has), [`indexOf`](https://lodash.com/docs#indexOf),
+ * [`map`](https://lodash.com/docs#map), and [`reduce`](https://lodash.com/docs#reduce)
+ *
+ * @constructor
+ * @param {string} name A name to identify the benchmark.
+ * @param {Function|string} fn The test to benchmark.
+ * @param {Object} [options={}] Options object.
+ * @example
+ *
+ * // basic usage (the `new` operator is optional)
+ * var bench = new Benchmark(fn);
+ *
+ * // or using a name first
+ * var bench = new Benchmark('foo', fn);
+ *
+ * // or with options
+ * var bench = new Benchmark('foo', fn, {
+ *
+ * // displayed by `Benchmark#toString` if `name` is not available
+ * 'id': 'xyz',
+ *
+ * // called when the benchmark starts running
+ * 'onStart': onStart,
+ *
+ * // called after each run cycle
+ * 'onCycle': onCycle,
+ *
+ * // called when aborted
+ * 'onAbort': onAbort,
+ *
+ * // called when a test errors
+ * 'onError': onError,
+ *
+ * // called when reset
+ * 'onReset': onReset,
+ *
+ * // called when the benchmark completes running
+ * 'onComplete': onComplete,
+ *
+ * // compiled/called before the test loop
+ * 'setup': setup,
+ *
+ * // compiled/called after the test loop
+ * 'teardown': teardown
+ * });
+ *
+ * // or name and options
+ * var bench = new Benchmark('foo', {
+ *
+ * // a flag to indicate the benchmark is deferred
+ * 'defer': true,
+ *
+ * // benchmark test function
+ * 'fn': function(deferred) {
+ * // call `Deferred#resolve` when the deferred test is finished
+ * deferred.resolve();
+ * }
+ * });
+ *
+ * // or options only
+ * var bench = new Benchmark({
+ *
+ * // benchmark name
+ * 'name': 'foo',
+ *
+ * // benchmark test as a string
+ * 'fn': '[1,2,3,4].sort()'
+ * });
+ *
+ * // a test's `this` binding is set to the benchmark instance
+ * var bench = new Benchmark('foo', function() {
+ * 'My name is '.concat(this.name); // "My name is foo"
+ * });
+ */
+ function Benchmark(name, fn, options) {
+ var bench = this;
+
+ // Allow instance creation without the `new` operator.
+ if (!(bench instanceof Benchmark)) {
+ return new Benchmark(name, fn, options);
+ }
+ // Juggle arguments.
+ if (_.isPlainObject(name)) {
+ // 1 argument (options).
+ options = name;
+ }
+ else if (_.isFunction(name)) {
+ // 2 arguments (fn, options).
+ options = fn;
+ fn = name;
+ }
+ else if (_.isPlainObject(fn)) {
+ // 2 arguments (name, options).
+ options = fn;
+ fn = null;
+ bench.name = name;
+ }
+ else {
+ // 3 arguments (name, fn [, options]).
+ bench.name = name;
+ }
+ setOptions(bench, options);
+
+ bench.id || (bench.id = ++counter);
+ bench.fn == null && (bench.fn = fn);
+
+ bench.stats = cloneDeep(bench.stats);
+ bench.times = cloneDeep(bench.times);
+ }
+
+ /**
+ * The Deferred constructor.
+ *
+ * @constructor
+ * @memberOf Benchmark
+ * @param {Object} clone The cloned benchmark instance.
+ */
+ function Deferred(clone) {
+ var deferred = this;
+ if (!(deferred instanceof Deferred)) {
+ return new Deferred(clone);
+ }
+ deferred.benchmark = clone;
+ clock(deferred);
+ }
+
+ /**
+ * The Event constructor.
+ *
+ * @constructor
+ * @memberOf Benchmark
+ * @param {Object|string} type The event type.
+ */
+ function Event(type) {
+ var event = this;
+ if (type instanceof Event) {
+ return type;
+ }
+ return (event instanceof Event)
+ ? _.assign(event, { 'timeStamp': _.now() }, typeof type == 'string' ? { 'type': type } : type)
+ : new Event(type);
+ }
+
+ /**
+ * The Suite constructor.
+ *
+ * Note: Each Suite instance has a handful of wrapped lodash methods to
+ * make working with Suites easier. The wrapped lodash methods are:
+ * [`each/forEach`](https://lodash.com/docs#forEach), [`indexOf`](https://lodash.com/docs#indexOf),
+ * [`map`](https://lodash.com/docs#map), and [`reduce`](https://lodash.com/docs#reduce)
+ *
+ * @constructor
+ * @memberOf Benchmark
+ * @param {string} name A name to identify the suite.
+ * @param {Object} [options={}] Options object.
+ * @example
+ *
+ * // basic usage (the `new` operator is optional)
+ * var suite = new Benchmark.Suite;
+ *
+ * // or using a name first
+ * var suite = new Benchmark.Suite('foo');
+ *
+ * // or with options
+ * var suite = new Benchmark.Suite('foo', {
+ *
+ * // called when the suite starts running
+ * 'onStart': onStart,
+ *
+ * // called between running benchmarks
+ * 'onCycle': onCycle,
+ *
+ * // called when aborted
+ * 'onAbort': onAbort,
+ *
+ * // called when a test errors
+ * 'onError': onError,
+ *
+ * // called when reset
+ * 'onReset': onReset,
+ *
+ * // called when the suite completes running
+ * 'onComplete': onComplete
+ * });
+ */
+ function Suite(name, options) {
+ var suite = this;
+
+ // Allow instance creation without the `new` operator.
+ if (!(suite instanceof Suite)) {
+ return new Suite(name, options);
+ }
+ // Juggle arguments.
+ if (_.isPlainObject(name)) {
+ // 1 argument (options).
+ options = name;
+ } else {
+ // 2 arguments (name [, options]).
+ suite.name = name;
+ }
+ setOptions(suite, options);
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * A specialized version of `_.cloneDeep` which only clones arrays and plain
+ * objects assigning all other values by reference.
+ *
+ * @private
+ * @param {*} value The value to clone.
+ * @returns {*} The cloned value.
+ */
+ var cloneDeep = _.partial(_.cloneDeepWith, _, function(value) {
+ // Only clone primitives, arrays, and plain objects.
+ return (_.isObject(value) && !_.isArray(value) && !_.isPlainObject(value))
+ ? value
+ : undefined;
+ });
+
+ /**
+ * Creates a function from the given arguments string and body.
+ *
+ * @private
+ * @param {string} args The comma separated function arguments.
+ * @param {string} body The function body.
+ * @returns {Function} The new function.
+ */
+ function createFunction() {
+ // Lazy define.
+ createFunction = function(args, body) {
+ var result,
+ anchor = freeDefine ? freeDefine.amd : Benchmark,
+ prop = uid + 'createFunction';
+
+ runScript((freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '=function(' + args + '){' + body + '}');
+ result = anchor[prop];
+ delete anchor[prop];
+ return result;
+ };
+ // Fix JaegerMonkey bug.
+ // For more information see http://bugzil.la/639720.
+ createFunction = support.browser && (createFunction('', 'return"' + uid + '"') || _.noop)() == uid ? createFunction : Function;
+ return createFunction.apply(null, arguments);
+ }
+
+ /**
+ * Delay the execution of a function based on the benchmark's `delay` property.
+ *
+ * @private
+ * @param {Object} bench The benchmark instance.
+ * @param {Object} fn The function to execute.
+ */
+ function delay(bench, fn) {
+ bench._timerId = _.delay(fn, bench.delay * 1e3);
+ }
+
+ /**
+ * Destroys the given element.
+ *
+ * @private
+ * @param {Element} element The element to destroy.
+ */
+ function destroyElement(element) {
+ trash.appendChild(element);
+ trash.innerHTML = '';
+ }
+
+ /**
+ * Gets the name of the first argument from a function's source.
+ *
+ * @private
+ * @param {Function} fn The function.
+ * @returns {string} The argument name.
+ */
+ function getFirstArgument(fn) {
+ return (!_.has(fn, 'toString') &&
+ (/^[\s(]*function[^(]*\(([^\s,)]+)/.exec(fn) || 0)[1]) || '';
+ }
+
+ /**
+ * Computes the arithmetic mean of a sample.
+ *
+ * @private
+ * @param {Array} sample The sample.
+ * @returns {number} The mean.
+ */
+ function getMean(sample) {
+ return (_.reduce(sample, function(sum, x) {
+ return sum + x;
+ }) / sample.length) || 0;
+ }
+
+ /**
+ * Gets the source code of a function.
+ *
+ * @private
+ * @param {Function} fn The function.
+ * @returns {string} The function's source code.
+ */
+ function getSource(fn) {
+ var result = '';
+ if (isStringable(fn)) {
+ result = String(fn);
+ } else if (support.decompilation) {
+ // Escape the `{` for Firefox 1.
+ result = _.result(/^[^{]+\{([\s\S]*)\}\s*$/.exec(fn), 1);
+ }
+ // Trim string.
+ result = (result || '').replace(/^\s+|\s+$/g, '');
+
+ // Detect strings containing only the "use strict" directive.
+ return /^(?:\/\*+[\w\W]*?\*\/|\/\/.*?[\n\r\u2028\u2029]|\s)*(["'])use strict\1;?$/.test(result)
+ ? ''
+ : result;
+ }
+
+ /**
+ * Checks if an object is of the specified class.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @param {string} name The name of the class.
+ * @returns {boolean} Returns `true` if the value is of the specified class, else `false`.
+ */
+ function isClassOf(value, name) {
+ return value != null && toString.call(value) == '[object ' + name + ']';
+ }
+
+ /**
+ * Host objects can return type values that are different from their actual
+ * data type. The objects we are concerned with usually return non-primitive
+ * types of "object", "function", or "unknown".
+ *
+ * @private
+ * @param {*} object The owner of the property.
+ * @param {string} property The property to check.
+ * @returns {boolean} Returns `true` if the property value is a non-primitive, else `false`.
+ */
+ function isHostType(object, property) {
+ if (object == null) {
+ return false;
+ }
+ var type = typeof object[property];
+ return !rePrimitive.test(type) && (type != 'object' || !!object[property]);
+ }
+
+ /**
+ * Checks if a value can be safely coerced to a string.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the value can be coerced, else `false`.
+ */
+ function isStringable(value) {
+ return _.isString(value) || (_.has(value, 'toString') && _.isFunction(value.toString));
+ }
+
+ /**
+ * Runs a snippet of JavaScript via script injection.
+ *
+ * @private
+ * @param {string} code The code to run.
+ */
+ function runScript(code) {
+ var anchor = freeDefine ? define.amd : Benchmark,
+ script = doc.createElement('script'),
+ sibling = doc.getElementsByTagName('script')[0],
+ parent = sibling.parentNode,
+ prop = uid + 'runScript',
+ prefix = '(' + (freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '||function(){})();';
+
+ // Firefox 2.0.0.2 cannot use script injection as intended because it executes
+ // asynchronously, but that's OK because script injection is only used to avoid
+ // the previously commented JaegerMonkey bug.
+ try {
+ // Remove the inserted script *before* running the code to avoid differences
+ // in the expected script element count/order of the document.
+ script.appendChild(doc.createTextNode(prefix + code));
+ anchor[prop] = function() { destroyElement(script); };
+ } catch(e) {
+ parent = parent.cloneNode(false);
+ sibling = null;
+ script.text = code;
+ }
+ parent.insertBefore(script, sibling);
+ delete anchor[prop];
+ }
+
+ /**
+ * A helper function for setting options/event handlers.
+ *
+ * @private
+ * @param {Object} object The benchmark or suite instance.
+ * @param {Object} [options={}] Options object.
+ */
+ function setOptions(object, options) {
+ options = object.options = _.assign({}, cloneDeep(object.constructor.options), cloneDeep(options));
+
+ _.forOwn(options, function(value, key) {
+ if (value != null) {
+ // Add event listeners.
+ if (/^on[A-Z]/.test(key)) {
+ _.each(key.split(' '), function(key) {
+ object.on(key.slice(2).toLowerCase(), value);
+ });
+ } else if (!_.has(object, key)) {
+ object[key] = cloneDeep(value);
+ }
+ }
+ });
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Handles cycling/completing the deferred benchmark.
+ *
+ * @memberOf Benchmark.Deferred
+ */
+ function resolve() {
+ var deferred = this,
+ clone = deferred.benchmark,
+ bench = clone._original;
+
+ if (bench.aborted) {
+ // cycle() -> clone cycle/complete event -> compute()'s invoked bench.run() cycle/complete.
+ deferred.teardown();
+ clone.running = false;
+ cycle(deferred);
+ }
+ else if (++deferred.cycles < clone.count) {
+ clone.compiled.call(deferred, context, timer);
+ }
+ else {
+ timer.stop(deferred);
+ deferred.teardown();
+ delay(clone, function() { cycle(deferred); });
+ }
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * A generic `Array#filter` like method.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Array} array The array to iterate over.
+ * @param {Function|string} callback The function/alias called per iteration.
+ * @returns {Array} A new array of values that passed callback filter.
+ * @example
+ *
+ * // get odd numbers
+ * Benchmark.filter([1, 2, 3, 4, 5], function(n) {
+ * return n % 2;
+ * }); // -> [1, 3, 5];
+ *
+ * // get fastest benchmarks
+ * Benchmark.filter(benches, 'fastest');
+ *
+ * // get slowest benchmarks
+ * Benchmark.filter(benches, 'slowest');
+ *
+ * // get benchmarks that completed without erroring
+ * Benchmark.filter(benches, 'successful');
+ */
+ function filter(array, callback) {
+ if (callback === 'successful') {
+ // Callback to exclude those that are errored, unrun, or have hz of Infinity.
+ callback = function(bench) {
+ return bench.cycles && _.isFinite(bench.hz) && !bench.error;
+ };
+ }
+ else if (callback === 'fastest' || callback === 'slowest') {
+ // Get successful, sort by period + margin of error, and filter fastest/slowest.
+ var result = filter(array, 'successful').sort(function(a, b) {
+ a = a.stats; b = b.stats;
+ return (a.mean + a.moe > b.mean + b.moe ? 1 : -1) * (callback === 'fastest' ? 1 : -1);
+ });
+
+ return _.filter(result, function(bench) {
+ return result[0].compare(bench) == 0;
+ });
+ }
+ return _.filter(array, callback);
+ }
+
+ /**
+ * Converts a number to a more readable comma-separated string representation.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {number} number The number to convert.
+ * @returns {string} The more readable string representation.
+ */
+ function formatNumber(number) {
+ number = String(number).split('.');
+ return number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') +
+ (number[1] ? '.' + number[1] : '');
+ }
+
+ /**
+ * Invokes a method on all items in an array.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Array} benches Array of benchmarks to iterate over.
+ * @param {Object|string} name The name of the method to invoke OR options object.
+ * @param {...*} [args] Arguments to invoke the method with.
+ * @returns {Array} A new array of values returned from each method invoked.
+ * @example
+ *
+ * // invoke `reset` on all benchmarks
+ * Benchmark.invoke(benches, 'reset');
+ *
+ * // invoke `emit` with arguments
+ * Benchmark.invoke(benches, 'emit', 'complete', listener);
+ *
+ * // invoke `run(true)`, treat benchmarks as a queue, and register invoke callbacks
+ * Benchmark.invoke(benches, {
+ *
+ * // invoke the `run` method
+ * 'name': 'run',
+ *
+ * // pass a single argument
+ * 'args': true,
+ *
+ * // treat as queue, removing benchmarks from front of `benches` until empty
+ * 'queued': true,
+ *
+ * // called before any benchmarks have been invoked.
+ * 'onStart': onStart,
+ *
+ * // called between invoking benchmarks
+ * 'onCycle': onCycle,
+ *
+ * // called after all benchmarks have been invoked.
+ * 'onComplete': onComplete
+ * });
+ */
+ function invoke(benches, name) {
+ var args,
+ bench,
+ queued,
+ index = -1,
+ eventProps = { 'currentTarget': benches },
+ options = { 'onStart': _.noop, 'onCycle': _.noop, 'onComplete': _.noop },
+ result = _.toArray(benches);
+
+ /**
+ * Invokes the method of the current object and if synchronous, fetches the next.
+ */
+ function execute() {
+ var listeners,
+ async = isAsync(bench);
+
+ if (async) {
+ // Use `getNext` as the first listener.
+ bench.on('complete', getNext);
+ listeners = bench.events.complete;
+ listeners.splice(0, 0, listeners.pop());
+ }
+ // Execute method.
+ result[index] = _.isFunction(bench && bench[name]) ? bench[name].apply(bench, args) : undefined;
+ // If synchronous return `true` until finished.
+ return !async && getNext();
+ }
+
+ /**
+ * Fetches the next bench or executes `onComplete` callback.
+ */
+ function getNext(event) {
+ var cycleEvent,
+ last = bench,
+ async = isAsync(last);
+
+ if (async) {
+ last.off('complete', getNext);
+ last.emit('complete');
+ }
+ // Emit "cycle" event.
+ eventProps.type = 'cycle';
+ eventProps.target = last;
+ cycleEvent = Event(eventProps);
+ options.onCycle.call(benches, cycleEvent);
+
+ // Choose next benchmark if not exiting early.
+ if (!cycleEvent.aborted && raiseIndex() !== false) {
+ bench = queued ? benches[0] : result[index];
+ if (isAsync(bench)) {
+ delay(bench, execute);
+ }
+ else if (async) {
+ // Resume execution if previously asynchronous but now synchronous.
+ while (execute()) {}
+ }
+ else {
+ // Continue synchronous execution.
+ return true;
+ }
+ } else {
+ // Emit "complete" event.
+ eventProps.type = 'complete';
+ options.onComplete.call(benches, Event(eventProps));
+ }
+ // When used as a listener `event.aborted = true` will cancel the rest of
+ // the "complete" listeners because they were already called above and when
+ // used as part of `getNext` the `return false` will exit the execution while-loop.
+ if (event) {
+ event.aborted = true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if invoking `Benchmark#run` with asynchronous cycles.
+ */
+ function isAsync(object) {
+ // Avoid using `instanceof` here because of IE memory leak issues with host objects.
+ var async = args[0] && args[0].async;
+ return name == 'run' && (object instanceof Benchmark) &&
+ ((async == null ? object.options.async : async) && support.timeout || object.defer);
+ }
+
+ /**
+ * Raises `index` to the next defined index or returns `false`.
+ */
+ function raiseIndex() {
+ index++;
+
+ // If queued remove the previous bench.
+ if (queued && index > 0) {
+ shift.call(benches);
+ }
+ // If we reached the last index then return `false`.
+ return (queued ? benches.length : index < result.length)
+ ? index
+ : (index = false);
+ }
+ // Juggle arguments.
+ if (_.isString(name)) {
+ // 2 arguments (array, name).
+ args = slice.call(arguments, 2);
+ } else {
+ // 2 arguments (array, options).
+ options = _.assign(options, name);
+ name = options.name;
+ args = _.isArray(args = 'args' in options ? options.args : []) ? args : [args];
+ queued = options.queued;
+ }
+ // Start iterating over the array.
+ if (raiseIndex() !== false) {
+ // Emit "start" event.
+ bench = result[index];
+ eventProps.type = 'start';
+ eventProps.target = bench;
+ options.onStart.call(benches, Event(eventProps));
+
+ // End early if the suite was aborted in an "onStart" listener.
+ if (name == 'run' && (benches instanceof Suite) && benches.aborted) {
+ // Emit "cycle" event.
+ eventProps.type = 'cycle';
+ options.onCycle.call(benches, Event(eventProps));
+ // Emit "complete" event.
+ eventProps.type = 'complete';
+ options.onComplete.call(benches, Event(eventProps));
+ }
+ // Start method execution.
+ else {
+ if (isAsync(bench)) {
+ delay(bench, execute);
+ } else {
+ while (execute()) {}
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates a string of joined array values or object key-value pairs.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @param {Array|Object} object The object to operate on.
+ * @param {string} [separator1=','] The separator used between key-value pairs.
+ * @param {string} [separator2=': '] The separator used between keys and values.
+ * @returns {string} The joined result.
+ */
+ function join(object, separator1, separator2) {
+ var result = [],
+ length = (object = Object(object)).length,
+ arrayLike = length === length >>> 0;
+
+ separator2 || (separator2 = ': ');
+ _.each(object, function(value, key) {
+ result.push(arrayLike ? value : key + separator2 + value);
+ });
+ return result.join(separator1 || ',');
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Aborts all benchmarks in the suite.
+ *
+ * @name abort
+ * @memberOf Benchmark.Suite
+ * @returns {Object} The suite instance.
+ */
+ function abortSuite() {
+ var event,
+ suite = this,
+ resetting = calledBy.resetSuite;
+
+ if (suite.running) {
+ event = Event('abort');
+ suite.emit(event);
+ if (!event.cancelled || resetting) {
+ // Avoid infinite recursion.
+ calledBy.abortSuite = true;
+ suite.reset();
+ delete calledBy.abortSuite;
+
+ if (!resetting) {
+ suite.aborted = true;
+ invoke(suite, 'abort');
+ }
+ }
+ }
+ return suite;
+ }
+
+ /**
+ * Adds a test to the benchmark suite.
+ *
+ * @memberOf Benchmark.Suite
+ * @param {string} name A name to identify the benchmark.
+ * @param {Function|string} fn The test to benchmark.
+ * @param {Object} [options={}] Options object.
+ * @returns {Object} The suite instance.
+ * @example
+ *
+ * // basic usage
+ * suite.add(fn);
+ *
+ * // or using a name first
+ * suite.add('foo', fn);
+ *
+ * // or with options
+ * suite.add('foo', fn, {
+ * 'onCycle': onCycle,
+ * 'onComplete': onComplete
+ * });
+ *
+ * // or name and options
+ * suite.add('foo', {
+ * 'fn': fn,
+ * 'onCycle': onCycle,
+ * 'onComplete': onComplete
+ * });
+ *
+ * // or options only
+ * suite.add({
+ * 'name': 'foo',
+ * 'fn': fn,
+ * 'onCycle': onCycle,
+ * 'onComplete': onComplete
+ * });
+ */
+ function add(name, fn, options) {
+ var suite = this,
+ bench = new Benchmark(name, fn, options),
+ event = Event({ 'type': 'add', 'target': bench });
+
+ if (suite.emit(event), !event.cancelled) {
+ suite.push(bench);
+ }
+ return suite;
+ }
+
+ /**
+ * Creates a new suite with cloned benchmarks.
+ *
+ * @name clone
+ * @memberOf Benchmark.Suite
+ * @param {Object} options Options object to overwrite cloned options.
+ * @returns {Object} The new suite instance.
+ */
+ function cloneSuite(options) {
+ var suite = this,
+ result = new suite.constructor(_.assign({}, suite.options, options));
+
+ // Copy own properties.
+ _.forOwn(suite, function(value, key) {
+ if (!_.has(result, key)) {
+ result[key] = value && _.isFunction(value.clone)
+ ? value.clone()
+ : cloneDeep(value);
+ }
+ });
+ return result;
+ }
+
+ /**
+ * An `Array#filter` like method.
+ *
+ * @name filter
+ * @memberOf Benchmark.Suite
+ * @param {Function|string} callback The function/alias called per iteration.
+ * @returns {Object} A new suite of benchmarks that passed callback filter.
+ */
+ function filterSuite(callback) {
+ var suite = this,
+ result = new suite.constructor(suite.options);
+
+ result.push.apply(result, filter(suite, callback));
+ return result;
+ }
+
+ /**
+ * Resets all benchmarks in the suite.
+ *
+ * @name reset
+ * @memberOf Benchmark.Suite
+ * @returns {Object} The suite instance.
+ */
+ function resetSuite() {
+ var event,
+ suite = this,
+ aborting = calledBy.abortSuite;
+
+ if (suite.running && !aborting) {
+ // No worries, `resetSuite()` is called within `abortSuite()`.
+ calledBy.resetSuite = true;
+ suite.abort();
+ delete calledBy.resetSuite;
+ }
+ // Reset if the state has changed.
+ else if ((suite.aborted || suite.running) &&
+ (suite.emit(event = Event('reset')), !event.cancelled)) {
+ suite.aborted = suite.running = false;
+ if (!aborting) {
+ invoke(suite, 'reset');
+ }
+ }
+ return suite;
+ }
+
+ /**
+ * Runs the suite.
+ *
+ * @name run
+ * @memberOf Benchmark.Suite
+ * @param {Object} [options={}] Options object.
+ * @returns {Object} The suite instance.
+ * @example
+ *
+ * // basic usage
+ * suite.run();
+ *
+ * // or with options
+ * suite.run({ 'async': true, 'queued': true });
+ */
+ function runSuite(options) {
+ var suite = this;
+
+ suite.reset();
+ suite.running = true;
+ options || (options = {});
+
+ invoke(suite, {
+ 'name': 'run',
+ 'args': options,
+ 'queued': options.queued,
+ 'onStart': function(event) {
+ suite.emit(event);
+ },
+ 'onCycle': function(event) {
+ var bench = event.target;
+ if (bench.error) {
+ suite.emit({ 'type': 'error', 'target': bench });
+ }
+ suite.emit(event);
+ event.aborted = suite.aborted;
+ },
+ 'onComplete': function(event) {
+ suite.running = false;
+ suite.emit(event);
+ }
+ });
+ return suite;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Executes all registered listeners of the specified event type.
+ *
+ * @memberOf Benchmark, Benchmark.Suite
+ * @param {Object|string} type The event type or object.
+ * @param {...*} [args] Arguments to invoke the listener with.
+ * @returns {*} Returns the return value of the last listener executed.
+ */
+ function emit(type) {
+ var listeners,
+ object = this,
+ event = Event(type),
+ events = object.events,
+ args = (arguments[0] = event, arguments);
+
+ event.currentTarget || (event.currentTarget = object);
+ event.target || (event.target = object);
+ delete event.result;
+
+ if (events && (listeners = _.has(events, event.type) && events[event.type])) {
+ _.each(listeners.slice(), function(listener) {
+ if ((event.result = listener.apply(object, args)) === false) {
+ event.cancelled = true;
+ }
+ return !event.aborted;
+ });
+ }
+ return event.result;
+ }
+
+ /**
+ * Returns an array of event listeners for a given type that can be manipulated
+ * to add or remove listeners.
+ *
+ * @memberOf Benchmark, Benchmark.Suite
+ * @param {string} type The event type.
+ * @returns {Array} The listeners array.
+ */
+ function listeners(type) {
+ var object = this,
+ events = object.events || (object.events = {});
+
+ return _.has(events, type) ? events[type] : (events[type] = []);
+ }
+
+ /**
+ * Unregisters a listener for the specified event type(s),
+ * or unregisters all listeners for the specified event type(s),
+ * or unregisters all listeners for all event types.
+ *
+ * @memberOf Benchmark, Benchmark.Suite
+ * @param {string} [type] The event type.
+ * @param {Function} [listener] The function to unregister.
+ * @returns {Object} The current instance.
+ * @example
+ *
+ * // unregister a listener for an event type
+ * bench.off('cycle', listener);
+ *
+ * // unregister a listener for multiple event types
+ * bench.off('start cycle', listener);
+ *
+ * // unregister all listeners for an event type
+ * bench.off('cycle');
+ *
+ * // unregister all listeners for multiple event types
+ * bench.off('start cycle complete');
+ *
+ * // unregister all listeners for all event types
+ * bench.off();
+ */
+ function off(type, listener) {
+ var object = this,
+ events = object.events;
+
+ if (!events) {
+ return object;
+ }
+ _.each(type ? type.split(' ') : events, function(listeners, type) {
+ var index;
+ if (typeof listeners == 'string') {
+ type = listeners;
+ listeners = _.has(events, type) && events[type];
+ }
+ if (listeners) {
+ if (listener) {
+ index = _.indexOf(listeners, listener);
+ if (index > -1) {
+ listeners.splice(index, 1);
+ }
+ } else {
+ listeners.length = 0;
+ }
+ }
+ });
+ return object;
+ }
+
+ /**
+ * Registers a listener for the specified event type(s).
+ *
+ * @memberOf Benchmark, Benchmark.Suite
+ * @param {string} type The event type.
+ * @param {Function} listener The function to register.
+ * @returns {Object} The current instance.
+ * @example
+ *
+ * // register a listener for an event type
+ * bench.on('cycle', listener);
+ *
+ * // register a listener for multiple event types
+ * bench.on('start cycle', listener);
+ */
+ function on(type, listener) {
+ var object = this,
+ events = object.events || (object.events = {});
+
+ _.each(type.split(' '), function(type) {
+ (_.has(events, type)
+ ? events[type]
+ : (events[type] = [])
+ ).push(listener);
+ });
+ return object;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Aborts the benchmark without recording times.
+ *
+ * @memberOf Benchmark
+ * @returns {Object} The benchmark instance.
+ */
+ function abort() {
+ var event,
+ bench = this,
+ resetting = calledBy.reset;
+
+ if (bench.running) {
+ event = Event('abort');
+ bench.emit(event);
+ if (!event.cancelled || resetting) {
+ // Avoid infinite recursion.
+ calledBy.abort = true;
+ bench.reset();
+ delete calledBy.abort;
+
+ if (support.timeout) {
+ clearTimeout(bench._timerId);
+ delete bench._timerId;
+ }
+ if (!resetting) {
+ bench.aborted = true;
+ bench.running = false;
+ }
+ }
+ }
+ return bench;
+ }
+
+ /**
+ * Creates a new benchmark using the same test and options.
+ *
+ * @memberOf Benchmark
+ * @param {Object} options Options object to overwrite cloned options.
+ * @returns {Object} The new benchmark instance.
+ * @example
+ *
+ * var bizarro = bench.clone({
+ * 'name': 'doppelganger'
+ * });
+ */
+ function clone(options) {
+ var bench = this,
+ result = new bench.constructor(_.assign({}, bench, options));
+
+ // Correct the `options` object.
+ result.options = _.assign({}, cloneDeep(bench.options), cloneDeep(options));
+
+ // Copy own custom properties.
+ _.forOwn(bench, function(value, key) {
+ if (!_.has(result, key)) {
+ result[key] = cloneDeep(value);
+ }
+ });
+
+ return result;
+ }
+
+ /**
+ * Determines if a benchmark is faster than another.
+ *
+ * @memberOf Benchmark
+ * @param {Object} other The benchmark to compare.
+ * @returns {number} Returns `-1` if slower, `1` if faster, and `0` if indeterminate.
+ */
+ function compare(other) {
+ var bench = this;
+
+ // Exit early if comparing the same benchmark.
+ if (bench == other) {
+ return 0;
+ }
+ var critical,
+ zStat,
+ sample1 = bench.stats.sample,
+ sample2 = other.stats.sample,
+ size1 = sample1.length,
+ size2 = sample2.length,
+ maxSize = max(size1, size2),
+ minSize = min(size1, size2),
+ u1 = getU(sample1, sample2),
+ u2 = getU(sample2, sample1),
+ u = min(u1, u2);
+
+ function getScore(xA, sampleB) {
+ return _.reduce(sampleB, function(total, xB) {
+ return total + (xB > xA ? 0 : xB < xA ? 1 : 0.5);
+ }, 0);
+ }
+
+ function getU(sampleA, sampleB) {
+ return _.reduce(sampleA, function(total, xA) {
+ return total + getScore(xA, sampleB);
+ }, 0);
+ }
+
+ function getZ(u) {
+ return (u - ((size1 * size2) / 2)) / sqrt((size1 * size2 * (size1 + size2 + 1)) / 12);
+ }
+ // Reject the null hypothesis the two samples come from the
+ // same population (i.e. have the same median) if...
+ if (size1 + size2 > 30) {
+ // ...the z-stat is greater than 1.96 or less than -1.96
+ // http://www.statisticslectures.com/topics/mannwhitneyu/
+ zStat = getZ(u);
+ return abs(zStat) > 1.96 ? (u == u1 ? 1 : -1) : 0;
+ }
+ // ...the U value is less than or equal the critical U value.
+ critical = maxSize < 5 || minSize < 3 ? 0 : uTable[maxSize][minSize - 3];
+ return u <= critical ? (u == u1 ? 1 : -1) : 0;
+ }
+
+ /**
+ * Reset properties and abort if running.
+ *
+ * @memberOf Benchmark
+ * @returns {Object} The benchmark instance.
+ */
+ function reset() {
+ var bench = this;
+ if (bench.running && !calledBy.abort) {
+ // No worries, `reset()` is called within `abort()`.
+ calledBy.reset = true;
+ bench.abort();
+ delete calledBy.reset;
+ return bench;
+ }
+ var event,
+ index = 0,
+ changes = [],
+ queue = [];
+
+ // A non-recursive solution to check if properties have changed.
+ // For more information see http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4.
+ var data = {
+ 'destination': bench,
+ 'source': _.assign({}, cloneDeep(bench.constructor.prototype), cloneDeep(bench.options))
+ };
+
+ do {
+ _.forOwn(data.source, function(value, key) {
+ var changed,
+ destination = data.destination,
+ currValue = destination[key];
+
+ // Skip pseudo private properties like `_timerId` which could be a
+ // Java object in environments like RingoJS.
+ if (key.charAt(0) == '_') {
+ return;
+ }
+ if (value && typeof value == 'object') {
+ if (_.isArray(value)) {
+ // Check if an array value has changed to a non-array value.
+ if (!_.isArray(currValue)) {
+ changed = currValue = [];
+ }
+ // Check if an array has changed its length.
+ if (currValue.length != value.length) {
+ changed = currValue = currValue.slice(0, value.length);
+ currValue.length = value.length;
+ }
+ }
+ // Check if an object has changed to a non-object value.
+ else if (!currValue || typeof currValue != 'object') {
+ changed = currValue = {};
+ }
+ // Register a changed object.
+ if (changed) {
+ changes.push({ 'destination': destination, 'key': key, 'value': currValue });
+ }
+ queue.push({ 'destination': currValue, 'source': value });
+ }
+ // Register a changed primitive.
+ else if (value !== currValue && !(value == null || _.isFunction(value))) {
+ changes.push({ 'destination': destination, 'key': key, 'value': value });
+ }
+ });
+ }
+ while ((data = queue[index++]));
+
+ // If changed emit the `reset` event and if it isn't cancelled reset the benchmark.
+ if (changes.length && (bench.emit(event = Event('reset')), !event.cancelled)) {
+ _.each(changes, function(data) {
+ data.destination[data.key] = data.value;
+ });
+ }
+ return bench;
+ }
+
+ /**
+ * Displays relevant benchmark information when coerced to a string.
+ *
+ * @name toString
+ * @memberOf Benchmark
+ * @returns {string} A string representation of the benchmark instance.
+ */
+ function toStringBench() {
+ var bench = this,
+ error = bench.error,
+ hz = bench.hz,
+ id = bench.id,
+ stats = bench.stats,
+ size = stats.sample.length,
+ pm = '\xb1',
+ result = bench.name || (_.isNaN(id) ? id : '');
+
+ if (error) {
+ var errorStr;
+ if (!_.isObject(error)) {
+ errorStr = String(error);
+ } else if (!_.isError(Error)) {
+ errorStr = join(error);
+ } else {
+ // Error#name and Error#message properties are non-enumerable.
+ errorStr = join(_.assign({ 'name': error.name, 'message': error.message }, error));
+ }
+ result += ': ' + errorStr;
+ }
+ else {
+ result += ' x ' + formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) + ' ops/sec ' + pm +
+ stats.rme.toFixed(2) + '% (' + size + ' run' + (size == 1 ? '' : 's') + ' sampled)';
+ }
+ return result;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Clocks the time taken to execute a test per cycle (secs).
+ *
+ * @private
+ * @param {Object} bench The benchmark instance.
+ * @returns {number} The time taken.
+ */
+ function clock() {
+ var options = Benchmark.options,
+ templateData = {},
+ timers = [{ 'ns': timer.ns, 'res': max(0.0015, getRes('ms')), 'unit': 'ms' }];
+
+ // Lazy define for hi-res timers.
+ clock = function(clone) {
+ var deferred;
+
+ if (clone instanceof Deferred) {
+ deferred = clone;
+ clone = deferred.benchmark;
+ }
+ var bench = clone._original,
+ stringable = isStringable(bench.fn),
+ count = bench.count = clone.count,
+ decompilable = stringable || (support.decompilation && (clone.setup !== _.noop || clone.teardown !== _.noop)),
+ id = bench.id,
+ name = bench.name || (typeof id == 'number' ? '' : id),
+ result = 0;
+
+ // Init `minTime` if needed.
+ clone.minTime = bench.minTime || (bench.minTime = bench.options.minTime = options.minTime);
+
+ // Compile in setup/teardown functions and the test loop.
+ // Create a new compiled test, instead of using the cached `bench.compiled`,
+ // to avoid potential engine optimizations enabled over the life of the test.
+ var funcBody = deferred
+ ? 'var d#=this,${fnArg}=d#,m#=d#.benchmark._original,f#=m#.fn,su#=m#.setup,td#=m#.teardown;' +
+ // When `deferred.cycles` is `0` then...
+ 'if(!d#.cycles){' +
+ // set `deferred.fn`,
+ 'd#.fn=function(){var ${fnArg}=d#;if(typeof f#=="function"){try{${fn}\n}catch(e#){f#(d#)}}else{${fn}\n}};' +
+ // set `deferred.teardown`,
+ 'd#.teardown=function(){d#.cycles=0;if(typeof td#=="function"){try{${teardown}\n}catch(e#){td#()}}else{${teardown}\n}};' +
+ // execute the benchmark's `setup`,
+ 'if(typeof su#=="function"){try{${setup}\n}catch(e#){su#()}}else{${setup}\n};' +
+ // start timer,
+ 't#.start(d#);' +
+ // and then execute `deferred.fn` and return a dummy object.
+ '}d#.fn();return{uid:"${uid}"}'
+
+ : 'var r#,s#,m#=this,f#=m#.fn,i#=m#.count,n#=t#.ns;${setup}\n${begin};' +
+ 'while(i#--){${fn}\n}${end};${teardown}\nreturn{elapsed:r#,uid:"${uid}"}';
+
+ var compiled = bench.compiled = clone.compiled = createCompiled(bench, decompilable, deferred, funcBody),
+ isEmpty = !(templateData.fn || stringable);
+
+ try {
+ if (isEmpty) {
+ // Firefox may remove dead code from `Function#toString` results.
+ // For more information see http://bugzil.la/536085.
+ throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.');
+ }
+ else if (!deferred) {
+ // Pretest to determine if compiled code exits early, usually by a
+ // rogue `return` statement, by checking for a return object with the uid.
+ bench.count = 1;
+ compiled = decompilable && (compiled.call(bench, context, timer) || {}).uid == templateData.uid && compiled;
+ bench.count = count;
+ }
+ } catch(e) {
+ compiled = null;
+ clone.error = e || new Error(String(e));
+ bench.count = count;
+ }
+ // Fallback when a test exits early or errors during pretest.
+ if (!compiled && !deferred && !isEmpty) {
+ funcBody = (
+ stringable || (decompilable && !clone.error)
+ ? 'function f#(){${fn}\n}var r#,s#,m#=this,i#=m#.count'
+ : 'var r#,s#,m#=this,f#=m#.fn,i#=m#.count'
+ ) +
+ ',n#=t#.ns;${setup}\n${begin};m#.f#=f#;while(i#--){m#.f#()}${end};' +
+ 'delete m#.f#;${teardown}\nreturn{elapsed:r#}';
+
+ compiled = createCompiled(bench, decompilable, deferred, funcBody);
+
+ try {
+ // Pretest one more time to check for errors.
+ bench.count = 1;
+ compiled.call(bench, context, timer);
+ bench.count = count;
+ delete clone.error;
+ }
+ catch(e) {
+ bench.count = count;
+ if (!clone.error) {
+ clone.error = e || new Error(String(e));
+ }
+ }
+ }
+ // If no errors run the full test loop.
+ if (!clone.error) {
+ compiled = bench.compiled = clone.compiled = createCompiled(bench, decompilable, deferred, funcBody);
+ result = compiled.call(deferred || bench, context, timer).elapsed;
+ }
+ return result;
+ };
+
+ /*----------------------------------------------------------------------*/
+
+ /**
+ * Creates a compiled function from the given function `body`.
+ */
+ function createCompiled(bench, decompilable, deferred, body) {
+ var fn = bench.fn,
+ fnArg = deferred ? getFirstArgument(fn) || 'deferred' : '';
+
+ templateData.uid = uid + uidCounter++;
+
+ _.assign(templateData, {
+ 'setup': decompilable ? getSource(bench.setup) : interpolate('m#.setup()'),
+ 'fn': decompilable ? getSource(fn) : interpolate('m#.fn(' + fnArg + ')'),
+ 'fnArg': fnArg,
+ 'teardown': decompilable ? getSource(bench.teardown) : interpolate('m#.teardown()')
+ });
+
+ // Use API of chosen timer.
+ if (timer.unit == 'ns') {
+ _.assign(templateData, {
+ 'begin': interpolate('s#=n#()'),
+ 'end': interpolate('r#=n#(s#);r#=r#[0]+(r#[1]/1e9)')
+ });
+ }
+ else if (timer.unit == 'us') {
+ if (timer.ns.stop) {
+ _.assign(templateData, {
+ 'begin': interpolate('s#=n#.start()'),
+ 'end': interpolate('r#=n#.microseconds()/1e6')
+ });
+ } else {
+ _.assign(templateData, {
+ 'begin': interpolate('s#=n#()'),
+ 'end': interpolate('r#=(n#()-s#)/1e6')
+ });
+ }
+ }
+ else if (timer.ns.now) {
+ _.assign(templateData, {
+ 'begin': interpolate('s#=n#.now()'),
+ 'end': interpolate('r#=(n#.now()-s#)/1e3')
+ });
+ }
+ else {
+ _.assign(templateData, {
+ 'begin': interpolate('s#=new n#().getTime()'),
+ 'end': interpolate('r#=(new n#().getTime()-s#)/1e3')
+ });
+ }
+ // Define `timer` methods.
+ timer.start = createFunction(
+ interpolate('o#'),
+ interpolate('var n#=this.ns,${begin};o#.elapsed=0;o#.timeStamp=s#')
+ );
+
+ timer.stop = createFunction(
+ interpolate('o#'),
+ interpolate('var n#=this.ns,s#=o#.timeStamp,${end};o#.elapsed=r#')
+ );
+
+ // Create compiled test.
+ return createFunction(
+ interpolate('window,t#'),
+ 'var global = window, clearTimeout = global.clearTimeout, setTimeout = global.setTimeout;\n' +
+ interpolate(body)
+ );
+ }
+
+ /**
+ * Gets the current timer's minimum resolution (secs).
+ */
+ function getRes(unit) {
+ var measured,
+ begin,
+ count = 30,
+ divisor = 1e3,
+ ns = timer.ns,
+ sample = [];
+
+ // Get average smallest measurable time.
+ while (count--) {
+ if (unit == 'us') {
+ divisor = 1e6;
+ if (ns.stop) {
+ ns.start();
+ while (!(measured = ns.microseconds())) {}
+ } else {
+ begin = ns();
+ while (!(measured = ns() - begin)) {}
+ }
+ }
+ else if (unit == 'ns') {
+ divisor = 1e9;
+ begin = (begin = ns())[0] + (begin[1] / divisor);
+ while (!(measured = ((measured = ns())[0] + (measured[1] / divisor)) - begin)) {}
+ divisor = 1;
+ }
+ else if (ns.now) {
+ begin = ns.now();
+ while (!(measured = ns.now() - begin)) {}
+ }
+ else {
+ begin = new ns().getTime();
+ while (!(measured = new ns().getTime() - begin)) {}
+ }
+ // Check for broken timers.
+ if (measured > 0) {
+ sample.push(measured);
+ } else {
+ sample.push(Infinity);
+ break;
+ }
+ }
+ // Convert to seconds.
+ return getMean(sample) / divisor;
+ }
+
+ /**
+ * Interpolates a given template string.
+ */
+ function interpolate(string) {
+ // Replaces all occurrences of `#` with a unique number and template tokens with content.
+ return _.template(string.replace(/\#/g, /\d+/.exec(templateData.uid)))(templateData);
+ }
+
+ /*----------------------------------------------------------------------*/
+
+ // Detect Chrome's microsecond timer:
+ // enable benchmarking via the --enable-benchmarking command
+ // line switch in at least Chrome 7 to use chrome.Interval
+ try {
+ if ((timer.ns = new (context.chrome || context.chromium).Interval)) {
+ timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' });
+ }
+ } catch(e) {}
+
+ // Detect Node.js's nanosecond resolution timer available in Node.js >= 0.8.
+ if (processObject && typeof (timer.ns = processObject.hrtime) == 'function') {
+ timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' });
+ }
+ // Pick timer with highest resolution.
+ timer = _.minBy(timers, 'res');
+
+ // Error if there are no working timers.
+ if (timer.res == Infinity) {
+ throw new Error('Benchmark.js was unable to find a working timer.');
+ }
+ // Resolve time span required to achieve a percent uncertainty of at most 1%.
+ // For more information see http://spiff.rit.edu/classes/phys273/uncert/uncert.html.
+ options.minTime || (options.minTime = max(timer.res / 2 / 0.01, 0.05));
+ return clock.apply(null, arguments);
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Computes stats on benchmark results.
+ *
+ * @private
+ * @param {Object} bench The benchmark instance.
+ * @param {Object} options The options object.
+ */
+ function compute(bench, options) {
+ options || (options = {});
+
+ var async = options.async,
+ elapsed = 0,
+ initCount = bench.initCount,
+ minSamples = bench.minSamples,
+ queue = [],
+ sample = bench.stats.sample;
+
+ /**
+ * Adds a clone to the queue.
+ */
+ function enqueue() {
+ queue.push(bench.clone({
+ '_original': bench,
+ 'events': {
+ 'abort': [update],
+ 'cycle': [update],
+ 'error': [update],
+ 'start': [update]
+ }
+ }));
+ }
+
+ /**
+ * Updates the clone/original benchmarks to keep their data in sync.
+ */
+ function update(event) {
+ var clone = this,
+ type = event.type;
+
+ if (bench.running) {
+ if (type == 'start') {
+ // Note: `clone.minTime` prop is inited in `clock()`.
+ clone.count = bench.initCount;
+ }
+ else {
+ if (type == 'error') {
+ bench.error = clone.error;
+ }
+ if (type == 'abort') {
+ bench.abort();
+ bench.emit('cycle');
+ } else {
+ event.currentTarget = event.target = bench;
+ bench.emit(event);
+ }
+ }
+ } else if (bench.aborted) {
+ // Clear abort listeners to avoid triggering bench's abort/cycle again.
+ clone.events.abort.length = 0;
+ clone.abort();
+ }
+ }
+
+ /**
+ * Determines if more clones should be queued or if cycling should stop.
+ */
+ function evaluate(event) {
+ var critical,
+ df,
+ mean,
+ moe,
+ rme,
+ sd,
+ sem,
+ variance,
+ clone = event.target,
+ done = bench.aborted,
+ now = _.now(),
+ size = sample.push(clone.times.period),
+ maxedOut = size >= minSamples && (elapsed += now - clone.times.timeStamp) / 1e3 > bench.maxTime,
+ times = bench.times,
+ varOf = function(sum, x) { return sum + pow(x - mean, 2); };
+
+ // Exit early for aborted or unclockable tests.
+ if (done || clone.hz == Infinity) {
+ maxedOut = !(size = sample.length = queue.length = 0);
+ }
+
+ if (!done) {
+ // Compute the sample mean (estimate of the population mean).
+ mean = getMean(sample);
+ // Compute the sample variance (estimate of the population variance).
+ variance = _.reduce(sample, varOf, 0) / (size - 1) || 0;
+ // Compute the sample standard deviation (estimate of the population standard deviation).
+ sd = sqrt(variance);
+ // Compute the standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean).
+ sem = sd / sqrt(size);
+ // Compute the degrees of freedom.
+ df = size - 1;
+ // Compute the critical value.
+ critical = tTable[Math.round(df) || 1] || tTable.infinity;
+ // Compute the margin of error.
+ moe = sem * critical;
+ // Compute the relative margin of error.
+ rme = (moe / mean) * 100 || 0;
+
+ _.assign(bench.stats, {
+ 'deviation': sd,
+ 'mean': mean,
+ 'moe': moe,
+ 'rme': rme,
+ 'sem': sem,
+ 'variance': variance
+ });
+
+ // Abort the cycle loop when the minimum sample size has been collected
+ // and the elapsed time exceeds the maximum time allowed per benchmark.
+ // We don't count cycle delays toward the max time because delays may be
+ // increased by browsers that clamp timeouts for inactive tabs. For more
+ // information see https://developer.mozilla.org/en/window.setTimeout#Inactive_tabs.
+ if (maxedOut) {
+ // Reset the `initCount` in case the benchmark is rerun.
+ bench.initCount = initCount;
+ bench.running = false;
+ done = true;
+ times.elapsed = (now - times.timeStamp) / 1e3;
+ }
+ if (bench.hz != Infinity) {
+ bench.hz = 1 / mean;
+ times.cycle = mean * bench.count;
+ times.period = mean;
+ }
+ }
+ // If time permits, increase sample size to reduce the margin of error.
+ if (queue.length < 2 && !maxedOut) {
+ enqueue();
+ }
+ // Abort the `invoke` cycle when done.
+ event.aborted = done;
+ }
+
+ // Init queue and begin.
+ enqueue();
+ invoke(queue, {
+ 'name': 'run',
+ 'args': { 'async': async },
+ 'queued': true,
+ 'onCycle': evaluate,
+ 'onComplete': function() { bench.emit('complete'); }
+ });
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Cycles a benchmark until a run `count` can be established.
+ *
+ * @private
+ * @param {Object} clone The cloned benchmark instance.
+ * @param {Object} options The options object.
+ */
+ function cycle(clone, options) {
+ options || (options = {});
+
+ var deferred;
+ if (clone instanceof Deferred) {
+ deferred = clone;
+ clone = clone.benchmark;
+ }
+ var clocked,
+ cycles,
+ divisor,
+ event,
+ minTime,
+ period,
+ async = options.async,
+ bench = clone._original,
+ count = clone.count,
+ times = clone.times;
+
+ // Continue, if not aborted between cycles.
+ if (clone.running) {
+ // `minTime` is set to `Benchmark.options.minTime` in `clock()`.
+ cycles = ++clone.cycles;
+ clocked = deferred ? deferred.elapsed : clock(clone);
+ minTime = clone.minTime;
+
+ if (cycles > bench.cycles) {
+ bench.cycles = cycles;
+ }
+ if (clone.error) {
+ event = Event('error');
+ event.message = clone.error;
+ clone.emit(event);
+ if (!event.cancelled) {
+ clone.abort();
+ }
+ }
+ }
+ // Continue, if not errored.
+ if (clone.running) {
+ // Compute the time taken to complete last test cycle.
+ bench.times.cycle = times.cycle = clocked;
+ // Compute the seconds per operation.
+ period = bench.times.period = times.period = clocked / count;
+ // Compute the ops per second.
+ bench.hz = clone.hz = 1 / period;
+ // Avoid working our way up to this next time.
+ bench.initCount = clone.initCount = count;
+ // Do we need to do another cycle?
+ clone.running = clocked < minTime;
+
+ if (clone.running) {
+ // Tests may clock at `0` when `initCount` is a small number,
+ // to avoid that we set its count to something a bit higher.
+ if (!clocked && (divisor = divisors[clone.cycles]) != null) {
+ count = floor(4e6 / divisor);
+ }
+ // Calculate how many more iterations it will take to achieve the `minTime`.
+ if (count <= clone.count) {
+ count += Math.ceil((minTime - clocked) / period);
+ }
+ clone.running = count != Infinity;
+ }
+ }
+ // Should we exit early?
+ event = Event('cycle');
+ clone.emit(event);
+ if (event.aborted) {
+ clone.abort();
+ }
+ // Figure out what to do next.
+ if (clone.running) {
+ // Start a new cycle.
+ clone.count = count;
+ if (deferred) {
+ clone.compiled.call(deferred, context, timer);
+ } else if (async) {
+ delay(clone, function() { cycle(clone, options); });
+ } else {
+ cycle(clone);
+ }
+ }
+ else {
+ // Fix TraceMonkey bug associated with clock fallbacks.
+ // For more information see http://bugzil.la/509069.
+ if (support.browser) {
+ runScript(uid + '=1;delete ' + uid);
+ }
+ // We're done.
+ clone.emit('complete');
+ }
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Runs the benchmark.
+ *
+ * @memberOf Benchmark
+ * @param {Object} [options={}] Options object.
+ * @returns {Object} The benchmark instance.
+ * @example
+ *
+ * // basic usage
+ * bench.run();
+ *
+ * // or with options
+ * bench.run({ 'async': true });
+ */
+ function run(options) {
+ var bench = this,
+ event = Event('start');
+
+ // Set `running` to `false` so `reset()` won't call `abort()`.
+ bench.running = false;
+ bench.reset();
+ bench.running = true;
+
+ bench.count = bench.initCount;
+ bench.times.timeStamp = _.now();
+ bench.emit(event);
+
+ if (!event.cancelled) {
+ options = { 'async': ((options = options && options.async) == null ? bench.async : options) && support.timeout };
+
+ // For clones created within `compute()`.
+ if (bench._original) {
+ if (bench.defer) {
+ Deferred(bench);
+ } else {
+ cycle(bench, options);
+ }
+ }
+ // For original benchmarks.
+ else {
+ compute(bench, options);
+ }
+ }
+ return bench;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ // Firefox 1 erroneously defines variable and argument names of functions on
+ // the function itself as non-configurable properties with `undefined` values.
+ // The bugginess continues as the `Benchmark` constructor has an argument
+ // named `options` and Firefox 1 will not assign a value to `Benchmark.options`,
+ // making it non-writable in the process, unless it is the first property
+ // assigned by for-in loop of `_.assign()`.
+ _.assign(Benchmark, {
+
+ /**
+ * The default options copied by benchmark instances.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @type Object
+ */
+ 'options': {
+
+ /**
+ * A flag to indicate that benchmark cycles will execute asynchronously
+ * by default.
+ *
+ * @memberOf Benchmark.options
+ * @type boolean
+ */
+ 'async': false,
+
+ /**
+ * A flag to indicate that the benchmark clock is deferred.
+ *
+ * @memberOf Benchmark.options
+ * @type boolean
+ */
+ 'defer': false,
+
+ /**
+ * The delay between test cycles (secs).
+ * @memberOf Benchmark.options
+ * @type number
+ */
+ 'delay': 0.005,
+
+ /**
+ * Displayed by `Benchmark#toString` when a `name` is not available
+ * (auto-generated if absent).
+ *
+ * @memberOf Benchmark.options
+ * @type string
+ */
+ 'id': undefined,
+
+ /**
+ * The default number of times to execute a test on a benchmark's first cycle.
+ *
+ * @memberOf Benchmark.options
+ * @type number
+ */
+ 'initCount': 1,
+
+ /**
+ * The maximum time a benchmark is allowed to run before finishing (secs).
+ *
+ * Note: Cycle delays aren't counted toward the maximum time.
+ *
+ * @memberOf Benchmark.options
+ * @type number
+ */
+ 'maxTime': 5,
+
+ /**
+ * The minimum sample size required to perform statistical analysis.
+ *
+ * @memberOf Benchmark.options
+ * @type number
+ */
+ 'minSamples': 5,
+
+ /**
+ * The time needed to reduce the percent uncertainty of measurement to 1% (secs).
+ *
+ * @memberOf Benchmark.options
+ * @type number
+ */
+ 'minTime': 0,
+
+ /**
+ * The name of the benchmark.
+ *
+ * @memberOf Benchmark.options
+ * @type string
+ */
+ 'name': undefined,
+
+ /**
+ * An event listener called when the benchmark is aborted.
+ *
+ * @memberOf Benchmark.options
+ * @type Function
+ */
+ 'onAbort': undefined,
+
+ /**
+ * An event listener called when the benchmark completes running.
+ *
+ * @memberOf Benchmark.options
+ * @type Function
+ */
+ 'onComplete': undefined,
+
+ /**
+ * An event listener called after each run cycle.
+ *
+ * @memberOf Benchmark.options
+ * @type Function
+ */
+ 'onCycle': undefined,
+
+ /**
+ * An event listener called when a test errors.
+ *
+ * @memberOf Benchmark.options
+ * @type Function
+ */
+ 'onError': undefined,
+
+ /**
+ * An event listener called when the benchmark is reset.
+ *
+ * @memberOf Benchmark.options
+ * @type Function
+ */
+ 'onReset': undefined,
+
+ /**
+ * An event listener called when the benchmark starts running.
+ *
+ * @memberOf Benchmark.options
+ * @type Function
+ */
+ 'onStart': undefined
+ },
+
+ /**
+ * Platform object with properties describing things like browser name,
+ * version, and operating system. See [`platform.js`](https://mths.be/platform).
+ *
+ * @static
+ * @memberOf Benchmark
+ * @type Object
+ */
+ 'platform': context.platform || require('platform') || ({
+ 'description': context.navigator && context.navigator.userAgent || null,
+ 'layout': null,
+ 'product': null,
+ 'name': null,
+ 'manufacturer': null,
+ 'os': null,
+ 'prerelease': null,
+ 'version': null,
+ 'toString': function() {
+ return this.description || '';
+ }
+ }),
+
+ /**
+ * The semantic version number.
+ *
+ * @static
+ * @memberOf Benchmark
+ * @type string
+ */
+ 'version': '2.1.2'
+ });
+
+ _.assign(Benchmark, {
+ 'filter': filter,
+ 'formatNumber': formatNumber,
+ 'invoke': invoke,
+ 'join': join,
+ 'runInContext': runInContext,
+ 'support': support
+ });
+
+ // Add lodash methods to Benchmark.
+ _.each(['each', 'forEach', 'forOwn', 'has', 'indexOf', 'map', 'reduce'], function(methodName) {
+ Benchmark[methodName] = _[methodName];
+ });
+
+ /*------------------------------------------------------------------------*/
+
+ _.assign(Benchmark.prototype, {
+
+ /**
+ * The number of times a test was executed.
+ *
+ * @memberOf Benchmark
+ * @type number
+ */
+ 'count': 0,
+
+ /**
+ * The number of cycles performed while benchmarking.
+ *
+ * @memberOf Benchmark
+ * @type number
+ */
+ 'cycles': 0,
+
+ /**
+ * The number of executions per second.
+ *
+ * @memberOf Benchmark
+ * @type number
+ */
+ 'hz': 0,
+
+ /**
+ * The compiled test function.
+ *
+ * @memberOf Benchmark
+ * @type {Function|string}
+ */
+ 'compiled': undefined,
+
+ /**
+ * The error object if the test failed.
+ *
+ * @memberOf Benchmark
+ * @type Object
+ */
+ 'error': undefined,
+
+ /**
+ * The test to benchmark.
+ *
+ * @memberOf Benchmark
+ * @type {Function|string}
+ */
+ 'fn': undefined,
+
+ /**
+ * A flag to indicate if the benchmark is aborted.
+ *
+ * @memberOf Benchmark
+ * @type boolean
+ */
+ 'aborted': false,
+
+ /**
+ * A flag to indicate if the benchmark is running.
+ *
+ * @memberOf Benchmark
+ * @type boolean
+ */
+ 'running': false,
+
+ /**
+ * Compiled into the test and executed immediately **before** the test loop.
+ *
+ * @memberOf Benchmark
+ * @type {Function|string}
+ * @example
+ *
+ * // basic usage
+ * var bench = Benchmark({
+ * 'setup': function() {
+ * var c = this.count,
+ * element = document.getElementById('container');
+ * while (c--) {
+ * element.appendChild(document.createElement('div'));
+ * }
+ * },
+ * 'fn': function() {
+ * element.removeChild(element.lastChild);
+ * }
+ * });
+ *
+ * // compiles to something like:
+ * var c = this.count,
+ * element = document.getElementById('container');
+ * while (c--) {
+ * element.appendChild(document.createElement('div'));
+ * }
+ * var start = new Date;
+ * while (count--) {
+ * element.removeChild(element.lastChild);
+ * }
+ * var end = new Date - start;
+ *
+ * // or using strings
+ * var bench = Benchmark({
+ * 'setup': '\
+ * var a = 0;\n\
+ * (function() {\n\
+ * (function() {\n\
+ * (function() {',
+ * 'fn': 'a += 1;',
+ * 'teardown': '\
+ * }())\n\
+ * }())\n\
+ * }())'
+ * });
+ *
+ * // compiles to something like:
+ * var a = 0;
+ * (function() {
+ * (function() {
+ * (function() {
+ * var start = new Date;
+ * while (count--) {
+ * a += 1;
+ * }
+ * var end = new Date - start;
+ * }())
+ * }())
+ * }())
+ */
+ 'setup': _.noop,
+
+ /**
+ * Compiled into the test and executed immediately **after** the test loop.
+ *
+ * @memberOf Benchmark
+ * @type {Function|string}
+ */
+ 'teardown': _.noop,
+
+ /**
+ * An object of stats including mean, margin or error, and standard deviation.
+ *
+ * @memberOf Benchmark
+ * @type Object
+ */
+ 'stats': {
+
+ /**
+ * The margin of error.
+ *
+ * @memberOf Benchmark#stats
+ * @type number
+ */
+ 'moe': 0,
+
+ /**
+ * The relative margin of error (expressed as a percentage of the mean).
+ *
+ * @memberOf Benchmark#stats
+ * @type number
+ */
+ 'rme': 0,
+
+ /**
+ * The standard error of the mean.
+ *
+ * @memberOf Benchmark#stats
+ * @type number
+ */
+ 'sem': 0,
+
+ /**
+ * The sample standard deviation.
+ *
+ * @memberOf Benchmark#stats
+ * @type number
+ */
+ 'deviation': 0,
+
+ /**
+ * The sample arithmetic mean (secs).
+ *
+ * @memberOf Benchmark#stats
+ * @type number
+ */
+ 'mean': 0,
+
+ /**
+ * The array of sampled periods.
+ *
+ * @memberOf Benchmark#stats
+ * @type Array
+ */
+ 'sample': [],
+
+ /**
+ * The sample variance.
+ *
+ * @memberOf Benchmark#stats
+ * @type number
+ */
+ 'variance': 0
+ },
+
+ /**
+ * An object of timing data including cycle, elapsed, period, start, and stop.
+ *
+ * @memberOf Benchmark
+ * @type Object
+ */
+ 'times': {
+
+ /**
+ * The time taken to complete the last cycle (secs).
+ *
+ * @memberOf Benchmark#times
+ * @type number
+ */
+ 'cycle': 0,
+
+ /**
+ * The time taken to complete the benchmark (secs).
+ *
+ * @memberOf Benchmark#times
+ * @type number
+ */
+ 'elapsed': 0,
+
+ /**
+ * The time taken to execute the test once (secs).
+ *
+ * @memberOf Benchmark#times
+ * @type number
+ */
+ 'period': 0,
+
+ /**
+ * A timestamp of when the benchmark started (ms).
+ *
+ * @memberOf Benchmark#times
+ * @type number
+ */
+ 'timeStamp': 0
+ }
+ });
+
+ _.assign(Benchmark.prototype, {
+ 'abort': abort,
+ 'clone': clone,
+ 'compare': compare,
+ 'emit': emit,
+ 'listeners': listeners,
+ 'off': off,
+ 'on': on,
+ 'reset': reset,
+ 'run': run,
+ 'toString': toStringBench
+ });
+
+ /*------------------------------------------------------------------------*/
+
+ _.assign(Deferred.prototype, {
+
+ /**
+ * The deferred benchmark instance.
+ *
+ * @memberOf Benchmark.Deferred
+ * @type Object
+ */
+ 'benchmark': null,
+
+ /**
+ * The number of deferred cycles performed while benchmarking.
+ *
+ * @memberOf Benchmark.Deferred
+ * @type number
+ */
+ 'cycles': 0,
+
+ /**
+ * The time taken to complete the deferred benchmark (secs).
+ *
+ * @memberOf Benchmark.Deferred
+ * @type number
+ */
+ 'elapsed': 0,
+
+ /**
+ * A timestamp of when the deferred benchmark started (ms).
+ *
+ * @memberOf Benchmark.Deferred
+ * @type number
+ */
+ 'timeStamp': 0
+ });
+
+ _.assign(Deferred.prototype, {
+ 'resolve': resolve
+ });
+
+ /*------------------------------------------------------------------------*/
+
+ _.assign(Event.prototype, {
+
+ /**
+ * A flag to indicate if the emitters listener iteration is aborted.
+ *
+ * @memberOf Benchmark.Event
+ * @type boolean
+ */
+ 'aborted': false,
+
+ /**
+ * A flag to indicate if the default action is cancelled.
+ *
+ * @memberOf Benchmark.Event
+ * @type boolean
+ */
+ 'cancelled': false,
+
+ /**
+ * The object whose listeners are currently being processed.
+ *
+ * @memberOf Benchmark.Event
+ * @type Object
+ */
+ 'currentTarget': undefined,
+
+ /**
+ * The return value of the last executed listener.
+ *
+ * @memberOf Benchmark.Event
+ * @type Mixed
+ */
+ 'result': undefined,
+
+ /**
+ * The object to which the event was originally emitted.
+ *
+ * @memberOf Benchmark.Event
+ * @type Object
+ */
+ 'target': undefined,
+
+ /**
+ * A timestamp of when the event was created (ms).
+ *
+ * @memberOf Benchmark.Event
+ * @type number
+ */
+ 'timeStamp': 0,
+
+ /**
+ * The event type.
+ *
+ * @memberOf Benchmark.Event
+ * @type string
+ */
+ 'type': ''
+ });
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * The default options copied by suite instances.
+ *
+ * @static
+ * @memberOf Benchmark.Suite
+ * @type Object
+ */
+ Suite.options = {
+
+ /**
+ * The name of the suite.
+ *
+ * @memberOf Benchmark.Suite.options
+ * @type string
+ */
+ 'name': undefined
+ };
+
+ /*------------------------------------------------------------------------*/
+
+ _.assign(Suite.prototype, {
+
+ /**
+ * The number of benchmarks in the suite.
+ *
+ * @memberOf Benchmark.Suite
+ * @type number
+ */
+ 'length': 0,
+
+ /**
+ * A flag to indicate if the suite is aborted.
+ *
+ * @memberOf Benchmark.Suite
+ * @type boolean
+ */
+ 'aborted': false,
+
+ /**
+ * A flag to indicate if the suite is running.
+ *
+ * @memberOf Benchmark.Suite
+ * @type boolean
+ */
+ 'running': false
+ });
+
+ _.assign(Suite.prototype, {
+ 'abort': abortSuite,
+ 'add': add,
+ 'clone': cloneSuite,
+ 'emit': emit,
+ 'filter': filterSuite,
+ 'join': arrayRef.join,
+ 'listeners': listeners,
+ 'off': off,
+ 'on': on,
+ 'pop': arrayRef.pop,
+ 'push': push,
+ 'reset': resetSuite,
+ 'run': runSuite,
+ 'reverse': arrayRef.reverse,
+ 'shift': shift,
+ 'slice': slice,
+ 'sort': arrayRef.sort,
+ 'splice': arrayRef.splice,
+ 'unshift': unshift
+ });
+
+ /*------------------------------------------------------------------------*/
+
+ // Expose Deferred, Event, and Suite.
+ _.assign(Benchmark, {
+ 'Deferred': Deferred,
+ 'Event': Event,
+ 'Suite': Suite
+ });
+
+ /*------------------------------------------------------------------------*/
+
+ // Add lodash methods as Suite methods.
+ _.each(['each', 'forEach', 'indexOf', 'map', 'reduce'], function(methodName) {
+ var func = _[methodName];
+ Suite.prototype[methodName] = function() {
+ var args = [this];
+ push.apply(args, arguments);
+ return func.apply(_, args);
+ };
+ });
+
+ // Avoid array-like object bugs with `Array#shift` and `Array#splice`
+ // in Firefox < 10 and IE < 9.
+ _.each(['pop', 'shift', 'splice'], function(methodName) {
+ var func = arrayRef[methodName];
+
+ Suite.prototype[methodName] = function() {
+ var value = this,
+ result = func.apply(value, arguments);
+
+ if (value.length === 0) {
+ delete value[0];
+ }
+ return result;
+ };
+ });
+
+ // Avoid buggy `Array#unshift` in IE < 8 which doesn't return the new
+ // length of the array.
+ Suite.prototype.unshift = function() {
+ var value = this;
+ unshift.apply(value, arguments);
+ return value.length;
+ };
+
+ return Benchmark;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ var Benchmark = runInContext();
+
+ window.Benchmark = Benchmark;
+
+ return Benchmark;
+
+}.call(this));
diff --git a/tgui/packages/tgui-bench/package.json b/tgui/packages/tgui-bench/package.json
new file mode 100644
index 0000000000..ae83ca8666
--- /dev/null
+++ b/tgui/packages/tgui-bench/package.json
@@ -0,0 +1,15 @@
+{
+ "private": true,
+ "name": "tgui-bench",
+ "version": "4.3.0",
+ "dependencies": {
+ "@fastify/static": "^5.0.2",
+ "common": "workspace:*",
+ "fastify": "^3.20.2",
+ "inferno": "^7.4.8",
+ "inferno-vnode-flags": "^7.4.8",
+ "lodash": "^4.17.21",
+ "platform": "^1.3.6",
+ "tgui": "workspace:*"
+ }
+}
diff --git a/tgui/packages/tgui-bench/tests/Button.test.tsx b/tgui/packages/tgui-bench/tests/Button.test.tsx
new file mode 100644
index 0000000000..e3472cbbbf
--- /dev/null
+++ b/tgui/packages/tgui-bench/tests/Button.test.tsx
@@ -0,0 +1,99 @@
+import { linkEvent } from 'inferno';
+import { Button } from 'tgui/components';
+import { createRenderer } from 'tgui/renderer';
+
+const render = createRenderer();
+
+const handleClick = () => undefined;
+
+export const SingleButton = () => {
+ const node = (
+
+ Hello world!
+
+ );
+ render(node);
+};
+
+export const SingleButtonWithCallback = () => {
+ const node = (
+ undefined}>
+ Hello world!
+
+ );
+ render(node);
+};
+
+export const SingleButtonWithLinkEvent = () => {
+ const node = (
+
+ Hello world!
+
+ );
+ render(node);
+};
+
+export const ListOfButtons = () => {
+ const nodes: JSX.Element[] = [];
+ for (let i = 0; i < 100; i++) {
+ const node = (
+
+ Hello world! {i}
+
+ );
+ nodes.push(node);
+ }
+ render({nodes}
);
+};
+
+export const ListOfButtonsWithCallback = () => {
+ const nodes: JSX.Element[] = [];
+ for (let i = 0; i < 100; i++) {
+ const node = (
+ undefined}>
+ Hello world! {i}
+
+ );
+ nodes.push(node);
+ }
+ render({nodes}
);
+};
+
+export const ListOfButtonsWithLinkEvent = () => {
+ const nodes: JSX.Element[] = [];
+ for (let i = 0; i < 100; i++) {
+ const node = (
+
+ Hello world! {i}
+
+ );
+ nodes.push(node);
+ }
+ render({nodes}
);
+};
+
+export const ListOfButtonsWithIcons = () => {
+ const nodes: JSX.Element[] = [];
+ for (let i = 0; i < 100; i++) {
+ const node = (
+
+ Hello world! {i}
+
+ );
+ nodes.push(node);
+ }
+ render({nodes}
);
+};
+
+export const ListOfButtonsWithTooltips = () => {
+ const nodes: JSX.Element[] = [];
+ for (let i = 0; i < 100; i++) {
+ const node = (
+
+ Hello world! {i}
+
+ );
+ nodes.push(node);
+ }
+ render({nodes}
);
+};
diff --git a/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx b/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx
new file mode 100644
index 0000000000..99367d2016
--- /dev/null
+++ b/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx
@@ -0,0 +1,28 @@
+import { backendUpdate } from 'tgui/backend';
+import { DisposalBin } from 'tgui/interfaces/DisposalBin';
+import { createRenderer } from 'tgui/renderer';
+import { configureStore, StoreProvider } from 'tgui/store';
+
+const store = configureStore({ sideEffets: false });
+
+const renderUi = createRenderer((dataJson: string) => {
+ store.dispatch(backendUpdate({
+ data: Byond.parseJson(dataJson),
+ }));
+ return (
+
+
+
+ );
+});
+
+export const data = JSON.stringify({
+ flush: 0,
+ full_pressure: 1,
+ pressure_charging: 0,
+ panel_open: 0,
+ per: 1,
+ isai: 0,
+});
+
+export const Default = () => renderUi(data);
diff --git a/tgui/packages/tgui-bench/tests/Flex.test.tsx b/tgui/packages/tgui-bench/tests/Flex.test.tsx
new file mode 100644
index 0000000000..e81ebc6f61
--- /dev/null
+++ b/tgui/packages/tgui-bench/tests/Flex.test.tsx
@@ -0,0 +1,18 @@
+import { Flex } from 'tgui/components';
+import { createRenderer } from 'tgui/renderer';
+
+const render = createRenderer();
+
+export const Default = () => {
+ const node = (
+
+
+ Text {Math.random()}
+
+
+ Text {Math.random()}
+
+
+ );
+ render(node);
+};
diff --git a/tgui/packages/tgui-bench/tests/Stack.test.tsx b/tgui/packages/tgui-bench/tests/Stack.test.tsx
new file mode 100644
index 0000000000..952dba2029
--- /dev/null
+++ b/tgui/packages/tgui-bench/tests/Stack.test.tsx
@@ -0,0 +1,18 @@
+import { Stack } from 'tgui/components';
+import { createRenderer } from 'tgui/renderer';
+
+const render = createRenderer();
+
+export const Default = () => {
+ const node = (
+
+
+ Text {Math.random()}
+
+
+ Text {Math.random()}
+
+
+ );
+ render(node);
+};
diff --git a/tgui/packages/tgui-bench/tests/Tooltip.test.tsx b/tgui/packages/tgui-bench/tests/Tooltip.test.tsx
new file mode 100644
index 0000000000..b953fc911d
--- /dev/null
+++ b/tgui/packages/tgui-bench/tests/Tooltip.test.tsx
@@ -0,0 +1,20 @@
+import { Box, Tooltip } from "tgui/components";
+import { createRenderer } from "tgui/renderer";
+
+const render = createRenderer();
+
+export const ListOfTooltips = () => {
+ const nodes: JSX.Element[] = [];
+
+ for (let i = 0; i < 100; i++) {
+ nodes.push(
+
+
+ Tooltip #{i}
+
+
+ );
+ }
+
+ render({nodes}
);
+};
diff --git a/tgui/packages/tgui-panel/Notifications.js b/tgui/packages/tgui-panel/Notifications.js
deleted file mode 100644
index a64ddd8e30..0000000000
--- a/tgui/packages/tgui-panel/Notifications.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { Flex } from 'tgui/components';
-
-export const Notifications = props => {
- const { children } = props;
- return (
-
- {children}
-
- );
-};
-
-const NotificationsItem = props => {
- const {
- rightSlot,
- children,
- } = props;
- return (
-
-
- {children}
-
- {rightSlot && (
-
- {rightSlot}
-
- )}
-
- );
-};
-
-Notifications.Item = NotificationsItem;
diff --git a/tgui/packages/tgui-panel/Panel.js b/tgui/packages/tgui-panel/Panel.js
deleted file mode 100644
index d6cc9eb7ef..0000000000
--- a/tgui/packages/tgui-panel/Panel.js
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { Button, Section, Stack } from 'tgui/components';
-import { Pane } from 'tgui/layouts';
-import { NowPlayingWidget, useAudio } from './audio';
-import { ChatPanel, ChatTabs } from './chat';
-import { useGame } from './game';
-import { Notifications } from './Notifications';
-import { PingIndicator } from './ping';
-import { SettingsPanel, useSettings } from './settings';
-
-export const Panel = (props, context) => {
- // IE8-10: Needs special treatment due to missing Flex support
- if (Byond.IS_LTE_IE10) {
- return (
-
- );
- }
- const audio = useAudio(context);
- const settings = useSettings(context);
- const game = useGame(context);
- if (process.env.NODE_ENV !== 'production') {
- const { useDebug, KitchenSink } = require('tgui/debug');
- const debug = useDebug(context);
- if (debug.kitchenSink) {
- return (
-
- );
- }
- }
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- audio.toggle()} />
-
-
- settings.toggle()} />
-
-
-
-
- {audio.visible && (
-
-
-
- )}
- {settings.visible && (
-
-
-
- )}
-
-
-
-
-
-
- {game.connectionLostAt && (
- Byond.command('.reconnect')}>
- Reconnect
-
- )}>
- You are either AFK, experiencing lag or the connection
- has closed.
-
- )}
- {game.roundRestartedAt && (
-
- The connection has been closed because the server is
- restarting. Please wait while you automatically reconnect.
-
- )}
-
-
-
-
-
- );
-};
-
-const HoboPanel = (props, context) => {
- const settings = useSettings(context);
- return (
-
-
- settings.toggle()}>
- Settings
-
- {settings.visible && (
-
- ) || (
-
- )}
-
-
- );
-};
diff --git a/tgui/packages/tgui-panel/audio/NowPlayingWidget.js b/tgui/packages/tgui-panel/audio/NowPlayingWidget.js
deleted file mode 100644
index e4fe04eed1..0000000000
--- a/tgui/packages/tgui-panel/audio/NowPlayingWidget.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { toFixed } from 'common/math';
-import { useDispatch, useSelector } from 'common/redux';
-import { Button, Flex, Knob } from 'tgui/components';
-import { useSettings } from '../settings';
-import { selectAudio } from './selectors';
-
-export const NowPlayingWidget = (props, context) => {
- const audio = useSelector(context, selectAudio);
- const dispatch = useDispatch(context);
- const settings = useSettings(context);
- const title = audio.meta?.title;
- return (
-
- {audio.playing && (
- <>
-
- Now playing:
-
-
- {title || 'Unknown Track'}
-
- >
- ) || (
-
- Nothing to play.
-
- )}
- {audio.playing && (
-
- dispatch({
- type: 'audio/stopMusic',
- })} />
-
- )}
-
- toFixed(value * 100) + '%'}
- onDrag={(e, value) => settings.update({
- adminMusicVolume: value,
- })} />
-
-
- );
-};
diff --git a/tgui/packages/tgui-panel/audio/hooks.js b/tgui/packages/tgui-panel/audio/hooks.js
deleted file mode 100644
index 17b29a9597..0000000000
--- a/tgui/packages/tgui-panel/audio/hooks.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { useSelector, useDispatch } from 'common/redux';
-import { selectAudio } from './selectors';
-
-export const useAudio = context => {
- const state = useSelector(context, selectAudio);
- const dispatch = useDispatch(context);
- return {
- ...state,
- toggle: () => dispatch({ type: 'audio/toggle' }),
- };
-};
diff --git a/tgui/packages/tgui-panel/audio/index.js b/tgui/packages/tgui-panel/audio/index.js
deleted file mode 100644
index ac7d4c2ca7..0000000000
--- a/tgui/packages/tgui-panel/audio/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export { useAudio } from './hooks';
-export { audioMiddleware } from './middleware';
-export { NowPlayingWidget } from './NowPlayingWidget';
-export { audioReducer } from './reducer';
diff --git a/tgui/packages/tgui-panel/audio/middleware.js b/tgui/packages/tgui-panel/audio/middleware.js
deleted file mode 100644
index c9a409aa93..0000000000
--- a/tgui/packages/tgui-panel/audio/middleware.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { AudioPlayer } from './player';
-
-export const audioMiddleware = store => {
- const player = new AudioPlayer();
- player.onPlay(() => {
- store.dispatch({ type: 'audio/playing' });
- });
- player.onStop(() => {
- store.dispatch({ type: 'audio/stopped' });
- });
- return next => action => {
- const { type, payload } = action;
- if (type === 'audio/playMusic') {
- const { url, ...options } = payload;
- player.play(url, options);
- return next(action);
- }
- if (type === 'audio/stopMusic') {
- player.stop();
- return next(action);
- }
- if (type === 'settings/update' || type === 'settings/load') {
- const volume = payload?.adminMusicVolume;
- if (typeof volume === 'number') {
- player.setVolume(volume);
- }
- return next(action);
- }
- return next(action);
- };
-};
diff --git a/tgui/packages/tgui-panel/audio/player.js b/tgui/packages/tgui-panel/audio/player.js
deleted file mode 100644
index 7848533e84..0000000000
--- a/tgui/packages/tgui-panel/audio/player.js
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { createLogger } from 'tgui/logging';
-
-const logger = createLogger('AudioPlayer');
-
-export class AudioPlayer {
- constructor() {
- // Doesn't support HTMLAudioElement
- if (Byond.IS_LTE_IE9) {
- return;
- }
- // Set up the HTMLAudioElement node
- this.node = document.createElement('audio');
- this.node.style.setProperty('display', 'none');
- document.body.appendChild(this.node);
- // Set up other properties
- this.playing = false;
- this.volume = 1;
- this.options = {};
- this.onPlaySubscribers = [];
- this.onStopSubscribers = [];
- // Listen for playback start events
- this.node.addEventListener('canplaythrough', () => {
- logger.log('canplaythrough');
- this.playing = true;
- this.node.playbackRate = this.options.pitch || 1;
- this.node.currentTime = this.options.start || 0;
- this.node.volume = this.volume;
- this.node.play();
- for (let subscriber of this.onPlaySubscribers) {
- subscriber();
- }
- });
- // Listen for playback stop events
- this.node.addEventListener('ended', () => {
- logger.log('ended');
- this.stop();
- });
- // Listen for playback errors
- this.node.addEventListener('error', e => {
- if (this.playing) {
- logger.log('playback error', e.error);
- this.stop();
- }
- });
- // Check every second to stop the playback at the right time
- this.playbackInterval = setInterval(() => {
- if (!this.playing) {
- return;
- }
- const shouldStop = this.options.end > 0
- && this.node.currentTime >= this.options.end;
- if (shouldStop) {
- this.stop();
- }
- }, 1000);
- }
-
- destroy() {
- if (!this.node) {
- return;
- }
- this.node.stop();
- document.removeChild(this.node);
- clearInterval(this.playbackInterval);
- }
-
- play(url, options = {}) {
- if (!this.node) {
- return;
- }
- logger.log('playing', url, options);
- this.options = options;
- this.node.src = url;
- }
-
- stop() {
- if (!this.node) {
- return;
- }
- if (this.playing) {
- for (let subscriber of this.onStopSubscribers) {
- subscriber();
- }
- }
- logger.log('stopping');
- this.playing = false;
- this.node.src = '';
- }
-
- setVolume(volume) {
- if (!this.node) {
- return;
- }
- this.volume = volume;
- this.node.volume = volume;
- }
-
- onPlay(subscriber) {
- if (!this.node) {
- return;
- }
- this.onPlaySubscribers.push(subscriber);
- }
-
- onStop(subscriber) {
- if (!this.node) {
- return;
- }
- this.onStopSubscribers.push(subscriber);
- }
-}
diff --git a/tgui/packages/tgui-panel/audio/reducer.js b/tgui/packages/tgui-panel/audio/reducer.js
deleted file mode 100644
index 17684060ff..0000000000
--- a/tgui/packages/tgui-panel/audio/reducer.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-const initialState = {
- visible: false,
- playing: false,
- track: null,
-};
-
-export const audioReducer = (state = initialState, action) => {
- const { type, payload } = action;
- if (type === 'audio/playing') {
- return {
- ...state,
- visible: true,
- playing: true,
- };
- }
- if (type === 'audio/stopped') {
- return {
- ...state,
- visible: false,
- playing: false,
- };
- }
- if (type === 'audio/playMusic') {
- return {
- ...state,
- meta: payload,
- };
- }
- if (type === 'audio/stopMusic') {
- return {
- ...state,
- visible: false,
- playing: false,
- meta: null,
- };
- }
- if (type === 'audio/toggle') {
- return {
- ...state,
- visible: !state.visible,
- };
- }
- return state;
-};
diff --git a/tgui/packages/tgui-panel/audio/selectors.js b/tgui/packages/tgui-panel/audio/selectors.js
deleted file mode 100644
index 156f509cd7..0000000000
--- a/tgui/packages/tgui-panel/audio/selectors.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export const selectAudio = state => state.audio;
diff --git a/tgui/packages/tgui-panel/chat/ChatPageSettings.js b/tgui/packages/tgui-panel/chat/ChatPageSettings.js
deleted file mode 100644
index ba045983d2..0000000000
--- a/tgui/packages/tgui-panel/chat/ChatPageSettings.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { useDispatch, useSelector } from 'common/redux';
-import { Button, Collapsible, Divider, Input, Section, Stack } from 'tgui/components';
-import { removeChatPage, toggleAcceptedType, updateChatPage } from './actions';
-import { MESSAGE_TYPES } from './constants';
-import { selectCurrentChatPage } from './selectors';
-
-export const ChatPageSettings = (props, context) => {
- const page = useSelector(context, selectCurrentChatPage);
- const dispatch = useDispatch(context);
- return (
-
- );
-};
diff --git a/tgui/packages/tgui-panel/chat/ChatPanel.js b/tgui/packages/tgui-panel/chat/ChatPanel.js
deleted file mode 100644
index 0a5deaf9fe..0000000000
--- a/tgui/packages/tgui-panel/chat/ChatPanel.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { shallowDiffers } from 'common/react';
-import { Component, createRef } from 'inferno';
-import { Button } from 'tgui/components';
-import { chatRenderer } from './renderer';
-
-export class ChatPanel extends Component {
- constructor() {
- super();
- this.ref = createRef();
- this.state = {
- scrollTracking: true,
- };
- this.handleScrollTrackingChange = value => this.setState({
- scrollTracking: value,
- });
- }
-
- componentDidMount() {
- chatRenderer.mount(this.ref.current);
- chatRenderer.events.on('scrollTrackingChanged',
- this.handleScrollTrackingChange);
- this.componentDidUpdate();
- }
-
- componentWillUnmount() {
- chatRenderer.events.off('scrollTrackingChanged',
- this.handleScrollTrackingChange);
- }
-
- componentDidUpdate(prevProps) {
- requestAnimationFrame(() => {
- chatRenderer.ensureScrollTracking();
- });
- const shouldUpdateStyle = (
- !prevProps || shallowDiffers(this.props, prevProps)
- );
- if (shouldUpdateStyle) {
- chatRenderer.assignStyle({
- 'width': '100%',
- 'white-space': 'pre-wrap',
- 'font-size': this.props.fontSize,
- 'line-height': this.props.lineHeight,
- });
- }
- }
-
- render() {
- const {
- scrollTracking,
- } = this.state;
- return (
- <>
-
- {!scrollTracking && (
- chatRenderer.scrollToBottom()}>
- Scroll to bottom
-
- )}
- >
- );
- }
-}
diff --git a/tgui/packages/tgui-panel/chat/ChatTabs.js b/tgui/packages/tgui-panel/chat/ChatTabs.js
deleted file mode 100644
index a0e6cc59e5..0000000000
--- a/tgui/packages/tgui-panel/chat/ChatTabs.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { useDispatch, useSelector } from 'common/redux';
-import { Box, Tabs, Flex, Button } from 'tgui/components';
-import { changeChatPage, addChatPage } from './actions';
-import { selectChatPages, selectCurrentChatPage } from './selectors';
-import { openChatSettings } from '../settings/actions';
-
-const UnreadCountWidget = ({ value }) => (
-
- {Math.min(value, 99)}
-
-);
-
-export const ChatTabs = (props, context) => {
- const pages = useSelector(context, selectChatPages);
- const currentPage = useSelector(context, selectCurrentChatPage);
- const dispatch = useDispatch(context);
- return (
-
-
-
- {pages.map(page => (
- 0 && (
-
- )}
- onClick={() => dispatch(changeChatPage({
- pageId: page.id,
- }))}>
- {page.name}
-
- ))}
-
-
-
- {
- dispatch(addChatPage());
- dispatch(openChatSettings());
- }} />
-
-
- );
-};
diff --git a/tgui/packages/tgui-panel/chat/actions.js b/tgui/packages/tgui-panel/chat/actions.js
deleted file mode 100644
index e9919fcfa2..0000000000
--- a/tgui/packages/tgui-panel/chat/actions.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { createAction } from 'common/redux';
-import { createPage } from './model';
-
-export const loadChat = createAction('chat/load');
-export const rebuildChat = createAction('chat/rebuild');
-export const updateMessageCount = createAction('chat/updateMessageCount');
-export const addChatPage = createAction('chat/addPage', () => ({
- payload: createPage(),
-}));
-export const changeChatPage = createAction('chat/changePage');
-export const updateChatPage = createAction('chat/updatePage');
-export const toggleAcceptedType = createAction('chat/toggleAcceptedType');
-export const removeChatPage = createAction('chat/removePage');
-export const changeScrollTracking = createAction('chat/changeScrollTracking');
-export const saveChatToDisk = createAction('chat/saveToDisk');
diff --git a/tgui/packages/tgui-panel/chat/constants.js b/tgui/packages/tgui-panel/chat/constants.js
deleted file mode 100644
index d2e472dc07..0000000000
--- a/tgui/packages/tgui-panel/chat/constants.js
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export const MAX_VISIBLE_MESSAGES = 2500;
-export const MAX_PERSISTED_MESSAGES = 1000;
-export const MESSAGE_SAVE_INTERVAL = 10000;
-export const MESSAGE_PRUNE_INTERVAL = 60000;
-export const COMBINE_MAX_MESSAGES = 5;
-export const COMBINE_MAX_TIME_WINDOW = 5000;
-export const IMAGE_RETRY_DELAY = 250;
-export const IMAGE_RETRY_LIMIT = 10;
-export const IMAGE_RETRY_MESSAGE_AGE = 60000;
-
-// Default message type
-export const MESSAGE_TYPE_UNKNOWN = 'unknown';
-
-// Internal message type
-export const MESSAGE_TYPE_INTERNAL = 'internal';
-
-// Must match the set of defines in code/__DEFINES/chat.dm
-export const MESSAGE_TYPE_SYSTEM = 'system';
-export const MESSAGE_TYPE_LOCALCHAT = 'localchat';
-export const MESSAGE_TYPE_RADIO = 'radio';
-export const MESSAGE_TYPE_INFO = 'info';
-export const MESSAGE_TYPE_WARNING = 'warning';
-export const MESSAGE_TYPE_DEADCHAT = 'deadchat';
-export const MESSAGE_TYPE_OOC = 'ooc';
-export const MESSAGE_TYPE_ADMINPM = 'adminpm';
-export const MESSAGE_TYPE_COMBAT = 'combat';
-export const MESSAGE_TYPE_ADMINCHAT = 'adminchat';
-export const MESSAGE_TYPE_MODCHAT = 'modchat';
-export const MESSAGE_TYPE_EVENTCHAT = 'eventchat';
-export const MESSAGE_TYPE_ADMINLOG = 'adminlog';
-export const MESSAGE_TYPE_ATTACKLOG = 'attacklog';
-export const MESSAGE_TYPE_DEBUG = 'debug';
-
-// Metadata for each message type
-export const MESSAGE_TYPES = [
- // Always-on types
- {
- type: MESSAGE_TYPE_SYSTEM,
- name: 'System Messages',
- description: 'Messages from your client, always enabled',
- selector: '.boldannounce',
- important: true,
- },
- // Basic types
- {
- type: MESSAGE_TYPE_LOCALCHAT,
- name: 'Local',
- description: 'In-character local messages (say, emote, etc)',
- selector: '.say, .emote',
- },
- {
- type: MESSAGE_TYPE_RADIO,
- name: 'Radio',
- description: 'All departments of radio messages',
- selector: '.alert, .minorannounce, .syndradio, .centcomradio, .aiprivradio, .comradio, .secradio, .engradio, .medradio, .sciradio, .suppradio, .servradio, .radio, .deptradio, .binarysay, .newscaster, .resonate',
- },
- {
- type: MESSAGE_TYPE_INFO,
- name: 'Info',
- description: 'Non-urgent messages from the game and items',
- selector: '.notice:not(.pm), .adminnotice, .info, .sinister, .cult, .infoplain, .announce, .hear, .smallnotice, .holoparasite',
- },
- {
- type: MESSAGE_TYPE_WARNING,
- name: 'Warnings',
- description: 'Urgent messages from the game and items',
- selector: '.warning:not(.pm), .critical, .userdanger, .italics, .alertsyndie, .warningplain',
- },
- {
- type: MESSAGE_TYPE_DEADCHAT,
- name: 'Deadchat',
- description: 'All of deadchat',
- selector: '.deadsay, .ghostalert',
- },
- {
- type: MESSAGE_TYPE_OOC,
- name: 'OOC',
- description: 'The bluewall of global OOC messages',
- selector: '.ooc, .adminooc, .oocplain',
- },
- {
- type: MESSAGE_TYPE_ADMINPM,
- name: 'Admin PMs',
- description: 'Messages to/from admins (adminhelp)',
- selector: '.pm, .adminhelp',
- },
- {
- type: MESSAGE_TYPE_COMBAT,
- name: 'Combat Log',
- description: 'Urist McTraitor has stabbed you with a knife!',
- selector: '.danger',
- },
- {
- type: MESSAGE_TYPE_UNKNOWN,
- name: 'Unsorted',
- description: 'Everything we could not sort, always enabled',
- },
- // Admin stuff
- {
- type: MESSAGE_TYPE_ADMINCHAT,
- name: 'Admin Chat',
- description: 'ASAY messages',
- selector: '.admin_channel, .adminsay',
- admin: true,
- },
- {
- type: MESSAGE_TYPE_MODCHAT,
- name: 'Mod Chat',
- description: 'MSAY messages',
- selector: '.mod_channel',
- admin: true,
- },
- {
- type: MESSAGE_TYPE_ADMINLOG,
- name: 'Admin Log',
- description: 'ADMIN LOG: Urist McAdmin has jumped to coordinates X, Y, Z',
- selector: '.log_message',
- admin: true,
- },
- {
- type: MESSAGE_TYPE_ATTACKLOG,
- name: 'Attack Log',
- description: 'Urist McTraitor has shot John Doe',
- admin: true,
- },
- {
- type: MESSAGE_TYPE_DEBUG,
- name: 'Debug Log',
- description: 'DEBUG: SSPlanets subsystem Recover().',
- admin: true,
- },
-];
diff --git a/tgui/packages/tgui-panel/chat/index.js b/tgui/packages/tgui-panel/chat/index.js
deleted file mode 100644
index d5ad6fa921..0000000000
--- a/tgui/packages/tgui-panel/chat/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export { ChatPageSettings } from './ChatPageSettings';
-export { ChatPanel } from './ChatPanel';
-export { ChatTabs } from './ChatTabs';
-export { chatMiddleware } from './middleware';
-export { chatReducer } from './reducer';
diff --git a/tgui/packages/tgui-panel/chat/middleware.js b/tgui/packages/tgui-panel/chat/middleware.js
deleted file mode 100644
index 88a896b992..0000000000
--- a/tgui/packages/tgui-panel/chat/middleware.js
+++ /dev/null
@@ -1,134 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import DOMPurify from 'dompurify';
-import { storage } from 'common/storage';
-import { loadSettings, updateSettings } from '../settings/actions';
-import { selectSettings } from '../settings/selectors';
-import { addChatPage, changeChatPage, changeScrollTracking, loadChat, rebuildChat, removeChatPage, saveChatToDisk, toggleAcceptedType, updateMessageCount } from './actions';
-import { MAX_PERSISTED_MESSAGES, MESSAGE_SAVE_INTERVAL } from './constants';
-import { createMessage, serializeMessage } from './model';
-import { chatRenderer } from './renderer';
-import { selectChat, selectCurrentChatPage } from './selectors';
-
-// List of blacklisted tags
-const FORBID_TAGS = [
- 'a',
- 'iframe',
- 'link',
- 'video',
-];
-
-const saveChatToStorage = async store => {
- const state = selectChat(store.getState());
- const fromIndex = Math.max(0,
- chatRenderer.messages.length - MAX_PERSISTED_MESSAGES);
- const messages = chatRenderer.messages
- .slice(fromIndex)
- .map(message => serializeMessage(message));
- storage.set('chat-state', state);
- storage.set('chat-messages', messages);
-};
-
-const loadChatFromStorage = async store => {
- const [state, messages] = await Promise.all([
- storage.get('chat-state'),
- storage.get('chat-messages'),
- ]);
- // Discard incompatible versions
- if (state && state.version <= 4) {
- store.dispatch(loadChat());
- return;
- }
- if (messages) {
- for (let message of messages) {
- if (message.html) {
- message.html = DOMPurify.sanitize(message.html, {
- FORBID_TAGS,
- });
- }
- }
- const batch = [
- ...messages,
- createMessage({
- type: 'internal/reconnected',
- }),
- ];
- chatRenderer.processBatch(batch, {
- prepend: true,
- });
- }
- store.dispatch(loadChat(state));
-};
-
-export const chatMiddleware = store => {
- let initialized = false;
- let loaded = false;
- chatRenderer.events.on('batchProcessed', countByType => {
- // Use this flag to workaround unread messages caused by
- // loading them from storage. Side effect of that, is that
- // message count can not be trusted, only unread count.
- if (loaded) {
- store.dispatch(updateMessageCount(countByType));
- }
- });
- chatRenderer.events.on('scrollTrackingChanged', scrollTracking => {
- store.dispatch(changeScrollTracking(scrollTracking));
- });
- setInterval(() => saveChatToStorage(store), MESSAGE_SAVE_INTERVAL);
- return next => action => {
- const { type, payload } = action;
- if (!initialized) {
- initialized = true;
- loadChatFromStorage(store);
- }
- if (type === 'chat/message') {
- // Normalize the payload
- const batch = Array.isArray(payload) ? payload : [payload];
- chatRenderer.processBatch(batch);
- return;
- }
- if (type === loadChat.type) {
- next(action);
- const page = selectCurrentChatPage(store.getState());
- chatRenderer.changePage(page);
- chatRenderer.onStateLoaded();
- loaded = true;
- return;
- }
- if (type === changeChatPage.type
- || type === addChatPage.type
- || type === removeChatPage.type
- || type === toggleAcceptedType.type) {
- next(action);
- const page = selectCurrentChatPage(store.getState());
- chatRenderer.changePage(page);
- return;
- }
- if (type === rebuildChat.type) {
- chatRenderer.rebuildChat();
- return next(action);
- }
- if (type === updateSettings.type || type === loadSettings.type) {
- next(action);
- const settings = selectSettings(store.getState());
- chatRenderer.setHighlight(
- settings.highlightText,
- settings.highlightColor);
- return;
- }
- if (type === 'roundrestart') {
- // Save chat as soon as possible
- saveChatToStorage(store);
- return next(action);
- }
- if (type === saveChatToDisk.type) {
- chatRenderer.saveToDisk();
- return;
- }
- return next(action);
- };
-};
diff --git a/tgui/packages/tgui-panel/chat/model.js b/tgui/packages/tgui-panel/chat/model.js
deleted file mode 100644
index 035bd0cf27..0000000000
--- a/tgui/packages/tgui-panel/chat/model.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { createUuid } from 'common/uuid';
-import { MESSAGE_TYPES, MESSAGE_TYPE_INTERNAL } from './constants';
-
-export const canPageAcceptType = (page, type) => (
- type.startsWith(MESSAGE_TYPE_INTERNAL) || page.acceptedTypes[type]
-);
-
-export const createPage = obj => ({
- id: createUuid(),
- name: 'New Tab',
- acceptedTypes: {},
- unreadCount: 0,
- createdAt: Date.now(),
- ...obj,
-});
-
-export const createMainPage = () => {
- const acceptedTypes = {};
- for (let typeDef of MESSAGE_TYPES) {
- acceptedTypes[typeDef.type] = true;
- }
- return createPage({
- name: 'Main',
- acceptedTypes,
- });
-};
-
-export const createMessage = payload => ({
- createdAt: Date.now(),
- ...payload,
-});
-
-export const serializeMessage = message => ({
- type: message.type,
- text: message.text,
- html: message.html,
- times: message.times,
- createdAt: message.createdAt,
-});
-
-export const isSameMessage = (a, b) => (
- typeof a.text === 'string' && a.text === b.text
- || typeof a.html === 'string' && a.html === b.html
-);
diff --git a/tgui/packages/tgui-panel/chat/reducer.js b/tgui/packages/tgui-panel/chat/reducer.js
deleted file mode 100644
index 6342e0012f..0000000000
--- a/tgui/packages/tgui-panel/chat/reducer.js
+++ /dev/null
@@ -1,172 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { addChatPage, changeChatPage, loadChat, removeChatPage, toggleAcceptedType, updateChatPage, updateMessageCount, changeScrollTracking } from './actions';
-import { canPageAcceptType, createMainPage } from './model';
-
-const mainPage = createMainPage();
-
-export const initialState = {
- version: 5,
- currentPageId: mainPage.id,
- scrollTracking: true,
- pages: [
- mainPage.id,
- ],
- pageById: {
- [mainPage.id]: mainPage,
- },
-};
-
-export const chatReducer = (state = initialState, action) => {
- const { type, payload } = action;
- if (type === loadChat.type) {
- // Validate version and/or migrate state
- if (payload?.version !== state.version) {
- return state;
- }
- // Reset page message counts
- // NOTE: We are mutably changing the payload on the assumption
- // that it is a copy that comes straight from the web storage.
- for (let id of Object.keys(payload.pageById)) {
- const page = payload.pageById[id];
- page.unreadCount = 0;
- }
- return {
- ...state,
- ...payload,
- };
- }
- if (type === changeScrollTracking.type) {
- const scrollTracking = payload;
- const nextState = {
- ...state,
- scrollTracking,
- };
- if (scrollTracking) {
- const pageId = state.currentPageId;
- const page = {
- ...state.pageById[pageId],
- unreadCount: 0,
- };
- nextState.pageById = {
- ...state.pageById,
- [pageId]: page,
- };
- }
- return nextState;
- }
- if (type === updateMessageCount.type) {
- const countByType = payload;
- const pages = state.pages.map(id => state.pageById[id]);
- const currentPage = state.pageById[state.currentPageId];
- const nextPageById = { ...state.pageById };
- for (let page of pages) {
- let unreadCount = 0;
- for (let type of Object.keys(countByType)) {
- // Message does not belong here
- if (!canPageAcceptType(page, type)) {
- continue;
- }
- // Current page is scroll tracked
- if (page === currentPage && state.scrollTracking) {
- continue;
- }
- // This page received the same message which we can read
- // on the current page.
- if (page !== currentPage && canPageAcceptType(currentPage, type)) {
- continue;
- }
- unreadCount += countByType[type];
- }
- if (unreadCount > 0) {
- nextPageById[page.id] = {
- ...page,
- unreadCount: page.unreadCount + unreadCount,
- };
- }
- }
- return {
- ...state,
- pageById: nextPageById,
- };
- }
- if (type === addChatPage.type) {
- return {
- ...state,
- currentPageId: payload.id,
- pages: [...state.pages, payload.id],
- pageById: {
- ...state.pageById,
- [payload.id]: payload,
- },
- };
- }
- if (type === changeChatPage.type) {
- const { pageId } = payload;
- const page = {
- ...state.pageById[pageId],
- unreadCount: 0,
- };
- return {
- ...state,
- currentPageId: pageId,
- pageById: {
- ...state.pageById,
- [pageId]: page,
- },
- };
- }
- if (type === updateChatPage.type) {
- const { pageId, ...update } = payload;
- const page = {
- ...state.pageById[pageId],
- ...update,
- };
- return {
- ...state,
- pageById: {
- ...state.pageById,
- [pageId]: page,
- },
- };
- }
- if (type === toggleAcceptedType.type) {
- const { pageId, type } = payload;
- const page = { ...state.pageById[pageId] };
- page.acceptedTypes = { ...page.acceptedTypes };
- page.acceptedTypes[type] = !page.acceptedTypes[type];
- return {
- ...state,
- pageById: {
- ...state.pageById,
- [pageId]: page,
- },
- };
- }
- if (type === removeChatPage.type) {
- const { pageId } = payload;
- const nextState = {
- ...state,
- pages: [...state.pages],
- pageById: {
- ...state.pageById,
- },
- };
- delete nextState.pageById[pageId];
- nextState.pages = nextState.pages.filter(id => id !== pageId);
- if (nextState.pages.length === 0) {
- nextState.pages.push(mainPage.id);
- nextState.pageById[mainPage.id] = mainPage;
- nextState.currentPageId = mainPage.id;
- }
- if (!nextState.currentPageId || nextState.currentPageId === pageId) {
- nextState.currentPageId = nextState.pages[0];
- }
- return nextState;
- }
- return state;
-};
diff --git a/tgui/packages/tgui-panel/chat/renderer.js b/tgui/packages/tgui-panel/chat/renderer.js
deleted file mode 100644
index 6fc9da7473..0000000000
--- a/tgui/packages/tgui-panel/chat/renderer.js
+++ /dev/null
@@ -1,488 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { EventEmitter } from 'common/events';
-import { classes } from 'common/react';
-import { createLogger } from 'tgui/logging';
-import { COMBINE_MAX_MESSAGES, COMBINE_MAX_TIME_WINDOW, IMAGE_RETRY_DELAY, IMAGE_RETRY_LIMIT, IMAGE_RETRY_MESSAGE_AGE, MAX_PERSISTED_MESSAGES, MAX_VISIBLE_MESSAGES, MESSAGE_PRUNE_INTERVAL, MESSAGE_TYPES, MESSAGE_TYPE_INTERNAL, MESSAGE_TYPE_UNKNOWN } from './constants';
-import { canPageAcceptType, createMessage, isSameMessage } from './model';
-import { highlightNode, linkifyNode } from './replaceInTextNode';
-
-const logger = createLogger('chatRenderer');
-
-// We consider this as the smallest possible scroll offset
-// that is still trackable.
-const SCROLL_TRACKING_TOLERANCE = 24;
-
-const findNearestScrollableParent = startingNode => {
- const body = document.body;
- let node = startingNode;
- while (node && node !== body) {
- // This definitely has a vertical scrollbar, because it reduces
- // scrollWidth of the element. Might not work if element uses
- // overflow: hidden.
- if (node.scrollWidth < node.offsetWidth) {
- return node;
- }
- node = node.parentNode;
- }
- return window;
-};
-
-const createHighlightNode = (text, color) => {
- const node = document.createElement('span');
- node.className = 'Chat__highlight';
- node.setAttribute('style', 'background-color:' + color);
- node.textContent = text;
- return node;
-};
-
-const createMessageNode = () => {
- const node = document.createElement('div');
- node.className = 'ChatMessage';
- return node;
-};
-
-const createReconnectedNode = () => {
- const node = document.createElement('div');
- node.className = 'Chat__reconnected';
- return node;
-};
-
-const handleImageError = e => {
- setTimeout(() => {
- /** @type {HTMLImageElement} */
- const node = e.target;
- const attempts = parseInt(node.getAttribute('data-reload-n'), 10) || 0;
- if (attempts >= IMAGE_RETRY_LIMIT) {
- logger.error(`failed to load an image after ${attempts} attempts`);
- return;
- }
- const src = node.src;
- node.src = null;
- node.src = src + '#' + attempts;
- node.setAttribute('data-reload-n', attempts + 1);
- }, IMAGE_RETRY_DELAY);
-};
-
-/**
- * Assigns a "times-repeated" badge to the message.
- */
-const updateMessageBadge = message => {
- const { node, times } = message;
- if (!node || !times) {
- // Nothing to update
- return;
- }
- const foundBadge = node.querySelector('.Chat__badge');
- const badge = foundBadge || document.createElement('div');
- badge.textContent = times;
- badge.className = classes([
- 'Chat__badge',
- 'Chat__badge--animate',
- ]);
- requestAnimationFrame(() => {
- badge.className = 'Chat__badge';
- });
- if (!foundBadge) {
- node.appendChild(badge);
- }
-};
-
-class ChatRenderer {
- constructor() {
- /** @type {HTMLElement} */
- this.loaded = false;
- /** @type {HTMLElement} */
- this.rootNode = null;
- this.queue = [];
- this.messages = [];
- this.visibleMessages = [];
- this.page = null;
- this.events = new EventEmitter();
- // Scroll handler
- /** @type {HTMLElement} */
- this.scrollNode = null;
- this.scrollTracking = true;
- this.handleScroll = type => {
- const node = this.scrollNode;
- const height = node.scrollHeight;
- const bottom = node.scrollTop + node.offsetHeight;
- const scrollTracking = (
- Math.abs(height - bottom) < SCROLL_TRACKING_TOLERANCE
- );
- if (scrollTracking !== this.scrollTracking) {
- this.scrollTracking = scrollTracking;
- this.events.emit('scrollTrackingChanged', scrollTracking);
- logger.debug('tracking', this.scrollTracking);
- }
- };
- this.ensureScrollTracking = () => {
- if (this.scrollTracking) {
- this.scrollToBottom();
- }
- };
- // Periodic message pruning
- setInterval(() => this.pruneMessages(), MESSAGE_PRUNE_INTERVAL);
- }
-
- isReady() {
- return this.loaded && this.rootNode && this.page;
- }
-
- mount(node) {
- // Mount existing root node on top of the new node
- if (this.rootNode) {
- node.appendChild(this.rootNode);
- }
- // Initialize the root node
- else {
- this.rootNode = node;
- }
- // Find scrollable parent
- this.scrollNode = findNearestScrollableParent(this.rootNode);
- this.scrollNode.addEventListener('scroll', this.handleScroll);
- setImmediate(() => {
- this.scrollToBottom();
- });
- // Flush the queue
- this.tryFlushQueue();
- }
-
- onStateLoaded() {
- this.loaded = true;
- this.tryFlushQueue();
- }
-
- tryFlushQueue() {
- if (this.isReady() && this.queue.length > 0) {
- this.processBatch(this.queue);
- this.queue = [];
- }
- }
-
- assignStyle(style = {}) {
- for (let key of Object.keys(style)) {
- this.rootNode.style.setProperty(key, style[key]);
- }
- }
-
- setHighlight(text, color) {
- if (!text || !color) {
- this.highlightRegex = null;
- this.highlightColor = null;
- return;
- }
- const allowedRegex = /^[a-z0-9_\-\s]+$/ig;
- const lines = String(text)
- .split(',')
- .map(str => str.trim())
- .filter(str => (
- // Must be longer than one character
- str && str.length > 1
- // Must be alphanumeric (with some punctuation)
- && allowedRegex.test(str)
- ));
- // Nothing to match, reset highlighting
- if (lines.length === 0) {
- this.highlightRegex = null;
- this.highlightColor = null;
- return;
- }
- this.highlightRegex = new RegExp('(' + lines.join('|') + ')', 'gi');
- this.highlightColor = color;
- }
-
- scrollToBottom() {
- // scrollHeight is always bigger than scrollTop and is
- // automatically clamped to the valid range.
- this.scrollNode.scrollTop = this.scrollNode.scrollHeight;
- }
-
- changePage(page) {
- if (!this.isReady()) {
- this.page = page;
- this.tryFlushQueue();
- return;
- }
- this.page = page;
- // Fast clear of the root node
- this.rootNode.textContent = '';
- this.visibleMessages = [];
- // Re-add message nodes
- const fragment = document.createDocumentFragment();
- let node;
- for (let message of this.messages) {
- if (canPageAcceptType(page, message.type)) {
- node = message.node;
- fragment.appendChild(node);
- this.visibleMessages.push(message);
- }
- }
- if (node) {
- this.rootNode.appendChild(fragment);
- node.scrollIntoView();
- }
- }
-
- getCombinableMessage(predicate) {
- const now = Date.now();
- const len = this.visibleMessages.length;
- const from = len - 1;
- const to = Math.max(0, len - COMBINE_MAX_MESSAGES);
- for (let i = from; i >= to; i--) {
- const message = this.visibleMessages[i];
- const matches = (
- // Is not an internal message
- !message.type.startsWith(MESSAGE_TYPE_INTERNAL)
- // Text payload must fully match
- && isSameMessage(message, predicate)
- // Must land within the specified time window
- && now < message.createdAt + COMBINE_MAX_TIME_WINDOW
- );
- if (matches) {
- return message;
- }
- }
- return null;
- }
-
- processBatch(batch, options = {}) {
- const {
- prepend,
- notifyListeners = true,
- } = options;
- const now = Date.now();
- // Queue up messages until chat is ready
- if (!this.isReady()) {
- if (prepend) {
- this.queue = [...batch, ...this.queue];
- }
- else {
- this.queue = [...this.queue, ...batch];
- }
- return;
- }
- // Insert messages
- const fragment = document.createDocumentFragment();
- const countByType = {};
- let node;
- for (let payload of batch) {
- const message = createMessage(payload);
- // Combine messages
- const combinable = this.getCombinableMessage(message);
- if (combinable) {
- combinable.times = (combinable.times || 1) + 1;
- updateMessageBadge(combinable);
- continue;
- }
- // Reuse message node
- if (message.node) {
- node = message.node;
- }
- // Reconnected
- else if (message.type === 'internal/reconnected') {
- node = createReconnectedNode();
- }
- // Create message node
- else {
- node = createMessageNode();
- // Payload is plain text
- if (message.text) {
- node.textContent = message.text;
- }
- // Payload is HTML
- else if (message.html) {
- node.innerHTML = message.html;
- }
- else {
- logger.error('Error: message is missing text payload', message);
- }
- // Highlight text
- if (!message.avoidHighlighting && this.highlightRegex) {
- const highlighted = highlightNode(node,
- this.highlightRegex,
- text => (
- createHighlightNode(text, this.highlightColor)
- ));
- if (highlighted) {
- node.className += ' ChatMessage--highlighted';
- }
- }
- // Linkify text
- const linkifyNodes = node.querySelectorAll('.linkify');
- for (let i = 0; i < linkifyNodes.length; ++i) {
- linkifyNode(linkifyNodes[i]);
- }
- // Assign an image error handler
- if (now < message.createdAt + IMAGE_RETRY_MESSAGE_AGE) {
- const imgNodes = node.querySelectorAll('img');
- for (let i = 0; i < imgNodes.length; i++) {
- const imgNode = imgNodes[i];
- imgNode.addEventListener('error', handleImageError);
- }
- }
- }
- // Store the node in the message
- message.node = node;
- // Query all possible selectors to find out the message type
- if (!message.type) {
- // IE8: Does not support querySelector on elements that
- // are not yet in the document.
- const typeDef = !Byond.IS_LTE_IE8 && MESSAGE_TYPES
- .find(typeDef => (
- typeDef.selector && node.querySelector(typeDef.selector)
- ));
- message.type = typeDef?.type || MESSAGE_TYPE_UNKNOWN;
- }
- updateMessageBadge(message);
- if (!countByType[message.type]) {
- countByType[message.type] = 0;
- }
- countByType[message.type] += 1;
- // TODO: Detect duplicates
- this.messages.push(message);
- if (canPageAcceptType(this.page, message.type)) {
- fragment.appendChild(node);
- this.visibleMessages.push(message);
- }
- }
- if (node) {
- const firstChild = this.rootNode.childNodes[0];
- if (prepend && firstChild) {
- this.rootNode.insertBefore(fragment, firstChild);
- }
- else {
- this.rootNode.appendChild(fragment);
- }
- if (this.scrollTracking) {
- setImmediate(() => this.scrollToBottom());
- }
- }
- // Notify listeners that we have processed the batch
- if (notifyListeners) {
- this.events.emit('batchProcessed', countByType);
- }
- }
-
- pruneMessages() {
- if (!this.isReady()) {
- return;
- }
- // Delay pruning because user is currently interacting
- // with chat history
- if (!this.scrollTracking) {
- logger.debug('pruning delayed');
- return;
- }
- // Visible messages
- {
- const messages = this.visibleMessages;
- const fromIndex = Math.max(0,
- messages.length - MAX_VISIBLE_MESSAGES);
- if (fromIndex > 0) {
- this.visibleMessages = messages.slice(fromIndex);
- for (let i = 0; i < fromIndex; i++) {
- const message = messages[i];
- this.rootNode.removeChild(message.node);
- // Mark this message as pruned
- message.node = 'pruned';
- }
- // Remove pruned messages from the message array
- this.messages = this.messages.filter(message => (
- message.node !== 'pruned'
- ));
- logger.log(`pruned ${fromIndex} visible messages`);
- }
- }
- // All messages
- {
- const fromIndex = Math.max(0,
- this.messages.length - MAX_PERSISTED_MESSAGES);
- if (fromIndex > 0) {
- this.messages = this.messages.slice(fromIndex);
- logger.log(`pruned ${fromIndex} stored messages`);
- }
- }
- }
-
- rebuildChat() {
- if (!this.isReady()) {
- return;
- }
- // Make a copy of messages
- const fromIndex = Math.max(0,
- this.messages.length - MAX_PERSISTED_MESSAGES);
- const messages = this.messages.slice(fromIndex);
- // Remove existing nodes
- for (let message of messages) {
- message.node = undefined;
- }
- // Fast clear of the root node
- this.rootNode.textContent = '';
- this.messages = [];
- this.visibleMessages = [];
- // Repopulate the chat log
- this.processBatch(messages, {
- notifyListeners: false,
- });
- }
-
- saveToDisk() {
- // Allow only on IE11
- if (Byond.IS_LTE_IE10) {
- return;
- }
- // Compile currently loaded stylesheets as CSS text
- let cssText = '';
- const styleSheets = document.styleSheets;
- for (let i = 0; i < styleSheets.length; i++) {
- const cssRules = styleSheets[i].cssRules;
- for (let i = 0; i < cssRules.length; i++) {
- const rule = cssRules[i];
- cssText += rule.cssText + '\n';
- }
- }
- cssText += 'body, html { background-color: #141414 }\n';
- // Compile chat log as HTML text
- let messagesHtml = '';
- for (let message of this.visibleMessages) {
- if (message.node) {
- messagesHtml += message.node.outerHTML + '\n';
- }
- }
- // Create a page
- const pageHtml = '\n'
- + '\n'
- + '\n'
- + 'SS13 Chat Log \n'
- + '\n'
- + '\n'
- + '\n'
- + '\n'
- + messagesHtml
- + '
\n'
- + '\n'
- + '\n';
- // Create and send a nice blob
- const blob = new Blob([pageHtml]);
- const timestamp = new Date()
- .toISOString()
- .substring(0, 19)
- .replace(/[-:]/g, '')
- .replace('T', '-');
- window.navigator.msSaveBlob(blob, `ss13-chatlog-${timestamp}.html`);
- }
-}
-
-// Make chat renderer global so that we can continue using the same
-// instance after hot code replacement.
-if (!window.__chatRenderer__) {
- window.__chatRenderer__ = new ChatRenderer();
-}
-
-/** @type {ChatRenderer} */
-export const chatRenderer = window.__chatRenderer__;
diff --git a/tgui/packages/tgui-panel/chat/replaceInTextNode.js b/tgui/packages/tgui-panel/chat/replaceInTextNode.js
deleted file mode 100644
index 0db2b2193e..0000000000
--- a/tgui/packages/tgui-panel/chat/replaceInTextNode.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-/**
- * Replaces text matching a regular expression with a custom node.
- */
-export const replaceInTextNode = (regex, createNode) => node => {
- const text = node.textContent;
- const textLength = text.length;
- let match;
- let lastIndex = 0;
- let fragment;
- let n = 0;
- // eslint-disable-next-line no-cond-assign
- while (match = regex.exec(text)) {
- n += 1;
- // Lazy init fragment
- if (!fragment) {
- fragment = document.createDocumentFragment();
- }
- const matchText = match[0];
- const matchLength = matchText.length;
- const matchIndex = match.index;
- // Insert previous unmatched chunk
- if (lastIndex < matchIndex) {
- fragment.appendChild(document.createTextNode(
- text.substring(lastIndex, matchIndex)));
- }
- lastIndex = matchIndex + matchLength;
- // Create a wrapper node
- fragment.appendChild(createNode(matchText));
- }
- if (fragment) {
- // Insert the remaining unmatched chunk
- if (lastIndex < textLength) {
- fragment.appendChild(document.createTextNode(
- text.substring(lastIndex, textLength)));
- }
- // Commit the fragment
- node.parentNode.replaceChild(fragment, node);
- }
- return n;
-};
-
-
-// Highlight
-// --------------------------------------------------------
-
-/**
- * Default highlight node.
- */
-const createHighlightNode = text => {
- const node = document.createElement('span');
- node.setAttribute('style',
- 'background-color:#fd4;color:#000');
- node.textContent = text;
- return node;
-};
-
-/**
- * Highlights the text in the node based on the provided regular expression.
- *
- * @param {Node} node Node which you want to process
- * @param {RegExp} regex Regular expression to highlight
- * @param {(text: string) => Node} createNode Highlight node creator
- * @returns {number} Number of matches
- */
-export const highlightNode = (
- node,
- regex,
- createNode = createHighlightNode,
-) => {
- if (!createNode) {
- createNode = createHighlightNode;
- }
- let n = 0;
- const childNodes = node.childNodes;
- for (let i = 0; i < childNodes.length; i++) {
- const node = childNodes[i];
- // Is a text node
- if (node.nodeType === 3) {
- n += replaceInTextNode(regex, createNode)(node);
- }
- else {
- n += highlightNode(node, regex, createNode);
- }
- }
- return n;
-};
-
-
-// Linkify
-// --------------------------------------------------------
-
-const URL_REGEX = /(?:(?:https?:\/\/)|(?:www\.))(?:[^ ]*?\.[^ ]*?)+[-A-Za-z0-9+&@#/%?=~_|$!:,.;()]+/ig;
-
-/**
- * Highlights the text in the node based on the provided regular expression.
- *
- * @param {Node} node Node which you want to process
- * @returns {number} Number of matches
- */
-export const linkifyNode = node => {
- let n = 0;
- const childNodes = node.childNodes;
- for (let i = 0; i < childNodes.length; i++) {
- const node = childNodes[i];
- const tag = String(node.nodeName).toLowerCase();
- // Is a text node
- if (node.nodeType === 3) {
- n += linkifyTextNode(node);
- }
- else if (tag !== 'a') {
- n += linkifyNode(node);
- }
- }
- return n;
-};
-
-const linkifyTextNode = replaceInTextNode(URL_REGEX, text => {
- const node = document.createElement('a');
- node.href = text;
- node.textContent = text;
- return node;
-});
diff --git a/tgui/packages/tgui-panel/chat/selectors.js b/tgui/packages/tgui-panel/chat/selectors.js
deleted file mode 100644
index 2bcc653184..0000000000
--- a/tgui/packages/tgui-panel/chat/selectors.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { map } from 'common/collections';
-
-export const selectChat = state => state.chat;
-
-export const selectChatPages = state => (
- map(id => state.chat.pageById[id])(state.chat.pages)
-);
-
-export const selectCurrentChatPage = state => (
- state.chat.pageById[state.chat.currentPageId]
-);
-
-export const selectChatPageById = id => state => (
- state.chat.pageById[id]
-);
diff --git a/tgui/packages/tgui-panel/game/actions.js b/tgui/packages/tgui-panel/game/actions.js
deleted file mode 100644
index e40014c44b..0000000000
--- a/tgui/packages/tgui-panel/game/actions.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { createAction } from 'common/redux';
-
-export const roundRestarted = createAction('roundrestart');
-export const connectionLost = createAction('game/connectionLost');
-export const connectionRestored = createAction('game/connectionRestored');
diff --git a/tgui/packages/tgui-panel/game/constants.js b/tgui/packages/tgui-panel/game/constants.js
deleted file mode 100644
index 9df3a58d29..0000000000
--- a/tgui/packages/tgui-panel/game/constants.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export const CONNECTION_LOST_AFTER = 15000;
diff --git a/tgui/packages/tgui-panel/game/hooks.js b/tgui/packages/tgui-panel/game/hooks.js
deleted file mode 100644
index e9567b916b..0000000000
--- a/tgui/packages/tgui-panel/game/hooks.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { useSelector } from 'common/redux';
-import { selectGame } from './selectors';
-
-export const useGame = context => {
- return useSelector(context, selectGame);
-};
diff --git a/tgui/packages/tgui-panel/game/index.js b/tgui/packages/tgui-panel/game/index.js
deleted file mode 100644
index 3cca2e6557..0000000000
--- a/tgui/packages/tgui-panel/game/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export { useGame } from './hooks';
-export { gameMiddleware } from './middleware';
-export { gameReducer } from './reducer';
diff --git a/tgui/packages/tgui-panel/game/middleware.js b/tgui/packages/tgui-panel/game/middleware.js
deleted file mode 100644
index 854369dc54..0000000000
--- a/tgui/packages/tgui-panel/game/middleware.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { pingSuccess } from '../ping/actions';
-import { connectionLost, connectionRestored, roundRestarted } from './actions';
-import { selectGame } from './selectors';
-import { CONNECTION_LOST_AFTER } from './constants';
-
-const withTimestamp = action => ({
- ...action,
- meta: {
- ...action.meta,
- now: Date.now(),
- },
-});
-
-export const gameMiddleware = store => {
- let lastPingedAt;
- setInterval(() => {
- const state = store.getState();
- if (!state) {
- return;
- }
- const game = selectGame(state);
- const pingsAreFailing = lastPingedAt
- && Date.now() >= lastPingedAt + CONNECTION_LOST_AFTER;
- if (!game.connectionLostAt && pingsAreFailing) {
- store.dispatch(withTimestamp(connectionLost()));
- }
- if (game.connectionLostAt && !pingsAreFailing) {
- store.dispatch(withTimestamp(connectionRestored()));
- }
- }, 1000);
- return next => action => {
- const { type, payload, meta } = action;
- if (type === pingSuccess.type) {
- lastPingedAt = meta.now;
- return next(action);
- }
- if (type === roundRestarted.type) {
- return next(withTimestamp(action));
- }
- return next(action);
- };
-};
diff --git a/tgui/packages/tgui-panel/game/reducer.js b/tgui/packages/tgui-panel/game/reducer.js
deleted file mode 100644
index 97535524c5..0000000000
--- a/tgui/packages/tgui-panel/game/reducer.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { connectionLost } from './actions';
-import { connectionRestored } from './actions';
-
-const initialState = {
- // TODO: This is where round info should be.
- roundId: null,
- roundTime: null,
- roundRestartedAt: null,
- connectionLostAt: null,
-};
-
-export const gameReducer = (state = initialState, action) => {
- const { type, payload, meta } = action;
- if (type === 'roundrestart') {
- return {
- ...state,
- roundRestartedAt: meta.now,
- };
- }
- if (type === connectionLost.type) {
- return {
- ...state,
- connectionLostAt: meta.now,
- };
- }
- if (type === connectionRestored.type) {
- return {
- ...state,
- connectionLostAt: null,
- };
- }
- return state;
-};
diff --git a/tgui/packages/tgui-panel/game/selectors.js b/tgui/packages/tgui-panel/game/selectors.js
deleted file mode 100644
index dc2d7040f9..0000000000
--- a/tgui/packages/tgui-panel/game/selectors.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export const selectGame = state => state.game;
diff --git a/tgui/packages/tgui-panel/index.js b/tgui/packages/tgui-panel/index.js
deleted file mode 100644
index 721fa97fb5..0000000000
--- a/tgui/packages/tgui-panel/index.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-// Themes
-import './styles/main.scss';
-import './styles/themes/light.scss';
-
-import { perf } from 'common/perf';
-import { combineReducers } from 'common/redux';
-import { setupHotReloading } from 'tgui-dev-server/link/client.cjs';
-import { setupGlobalEvents } from 'tgui/events';
-import { captureExternalLinks } from 'tgui/links';
-import { createRenderer } from 'tgui/renderer';
-import { configureStore, StoreProvider } from 'tgui/store';
-import { audioMiddleware, audioReducer } from './audio';
-import { chatMiddleware, chatReducer } from './chat';
-import { gameMiddleware, gameReducer } from './game';
-import { setupPanelFocusHacks } from './panelFocus';
-import { pingMiddleware, pingReducer } from './ping';
-import { settingsMiddleware, settingsReducer } from './settings';
-import { telemetryMiddleware } from './telemetry';
-
-perf.mark('inception', window.performance?.timing?.navigationStart);
-perf.mark('init');
-
-const store = configureStore({
- reducer: combineReducers({
- audio: audioReducer,
- chat: chatReducer,
- game: gameReducer,
- ping: pingReducer,
- settings: settingsReducer,
- }),
- middleware: {
- pre: [
- chatMiddleware,
- pingMiddleware,
- telemetryMiddleware,
- settingsMiddleware,
- audioMiddleware,
- gameMiddleware,
- ],
- },
-});
-
-const renderApp = createRenderer(() => {
- const { Panel } = require('./Panel');
- return (
-
-
-
- );
-});
-
-const setupApp = () => {
- // Delay setup
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', setupApp);
- return;
- }
-
- setupGlobalEvents({
- ignoreWindowFocus: true,
- });
- setupPanelFocusHacks();
- captureExternalLinks();
-
- // Re-render UI on store updates
- store.subscribe(renderApp);
-
- // Dispatch incoming messages as store actions
- Byond.subscribe((type, payload) => store.dispatch({ type, payload }));
-
- // Unhide the panel
- Byond.winset('output', {
- 'is-visible': false,
- });
- Byond.winset('browseroutput', {
- 'is-visible': true,
- 'is-disabled': false,
- 'pos': '0x0',
- 'size': '0x0',
- });
-
- // Resize the panel to match the non-browser output
- Byond.winget('output').then(output => {
- Byond.winset('browseroutput', {
- 'size': output.size,
- });
- });
-
- // Enable hot module reloading
- if (module.hot) {
- setupHotReloading();
- module.hot.accept([
- './audio',
- './chat',
- './game',
- './Notifications',
- './Panel',
- './ping',
- './settings',
- './telemetry',
- ], () => {
- renderApp();
- });
- }
-};
-
-setupApp();
diff --git a/tgui/packages/tgui-panel/package.json b/tgui/packages/tgui-panel/package.json
deleted file mode 100644
index 347acc9a7f..0000000000
--- a/tgui/packages/tgui-panel/package.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "private": true,
- "name": "tgui-panel",
- "version": "4.3.0",
- "dependencies": {
- "common": "workspace:*",
- "dompurify": "^2.3.8",
- "inferno": "^7.4.11",
- "tgui": "workspace:*",
- "tgui-dev-server": "workspace:*",
- "tgui-polyfill": "workspace:*"
- }
-}
diff --git a/tgui/packages/tgui-panel/panelFocus.js b/tgui/packages/tgui-panel/panelFocus.js
deleted file mode 100644
index 6922418c89..0000000000
--- a/tgui/packages/tgui-panel/panelFocus.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * Basically, hacks from goonchat which try to keep the map focused at all
- * times, except for when some meaningful action happens o
- *
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { vecLength, vecSubtract } from 'common/vector';
-import { canStealFocus, globalEvents } from 'tgui/events';
-import { focusMap } from 'tgui/focus';
-
-// Empyrically determined number for the smallest possible
-// text you can select with the mouse.
-const MIN_SELECTION_DISTANCE = 10;
-
-const deferredFocusMap = () => setImmediate(() => focusMap());
-
-export const setupPanelFocusHacks = () => {
- let focusStolen = false;
- let clickStartPos = null;
- window.addEventListener('focusin', e => {
- focusStolen = canStealFocus(e.target);
- });
- window.addEventListener('mousedown', e => {
- clickStartPos = [e.screenX, e.screenY];
- });
- window.addEventListener('mouseup', e => {
- if (clickStartPos) {
- const clickEndPos = [e.screenX, e.screenY];
- const dist = vecLength(vecSubtract(clickEndPos, clickStartPos));
- if (dist >= MIN_SELECTION_DISTANCE) {
- focusStolen = true;
- }
- }
- if (!focusStolen) {
- deferredFocusMap();
- }
- });
- globalEvents.on('keydown', key => {
- if (key.isModifierKey()) {
- return;
- }
- deferredFocusMap();
- });
-};
diff --git a/tgui/packages/tgui-panel/ping/PingIndicator.js b/tgui/packages/tgui-panel/ping/PingIndicator.js
deleted file mode 100644
index b663cd3a18..0000000000
--- a/tgui/packages/tgui-panel/ping/PingIndicator.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { Color } from 'common/color';
-import { toFixed } from 'common/math';
-import { useSelector } from 'common/redux';
-import { Box } from 'tgui/components';
-import { selectPing } from './selectors';
-
-export const PingIndicator = (props, context) => {
- const ping = useSelector(context, selectPing);
- const color = Color.lookup(ping.networkQuality, [
- new Color(220, 40, 40),
- new Color(220, 200, 40),
- new Color(60, 220, 40),
- ]);
- const roundtrip = ping.roundtrip
- ? toFixed(ping.roundtrip)
- : '--';
- return (
-
-
- {roundtrip}
-
- );
-};
diff --git a/tgui/packages/tgui-panel/ping/actions.js b/tgui/packages/tgui-panel/ping/actions.js
deleted file mode 100644
index ba3582f131..0000000000
--- a/tgui/packages/tgui-panel/ping/actions.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { createAction } from 'common/redux';
-
-export const pingSuccess = createAction(
- 'ping/success',
- ping => {
- const now = Date.now();
- const roundtrip = (now - ping.sentAt) * 0.5;
- return {
- payload: {
- lastId: ping.id,
- roundtrip,
- },
- meta: { now },
- };
- }
-);
-
-export const pingFail = createAction('ping/fail');
-export const pingReply = createAction('ping/reply');
diff --git a/tgui/packages/tgui-panel/ping/constants.js b/tgui/packages/tgui-panel/ping/constants.js
deleted file mode 100644
index 5d9a0472c5..0000000000
--- a/tgui/packages/tgui-panel/ping/constants.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export const PING_INTERVAL = 2500;
-export const PING_TIMEOUT = 2000;
-export const PING_MAX_FAILS = 3;
-export const PING_QUEUE_SIZE = 8;
-export const PING_ROUNDTRIP_BEST = 50;
-export const PING_ROUNDTRIP_WORST = 200;
diff --git a/tgui/packages/tgui-panel/ping/index.js b/tgui/packages/tgui-panel/ping/index.js
deleted file mode 100644
index 1bbbe3ed34..0000000000
--- a/tgui/packages/tgui-panel/ping/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export { pingMiddleware } from './middleware';
-export { PingIndicator } from './PingIndicator';
-export { pingReducer } from './reducer';
diff --git a/tgui/packages/tgui-panel/ping/middleware.js b/tgui/packages/tgui-panel/ping/middleware.js
deleted file mode 100644
index 2d607c8417..0000000000
--- a/tgui/packages/tgui-panel/ping/middleware.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { pingFail, pingSuccess } from './actions';
-import { PING_INTERVAL, PING_QUEUE_SIZE, PING_TIMEOUT } from './constants';
-
-export const pingMiddleware = store => {
- let initialized = false;
- let index = 0;
- let interval;
- const pings = [];
- const sendPing = () => {
- for (let i = 0; i < PING_QUEUE_SIZE; i++) {
- const ping = pings[i];
- if (ping && Date.now() - ping.sentAt > PING_TIMEOUT) {
- pings[i] = null;
- store.dispatch(pingFail());
- }
- }
- const ping = { index, sentAt: Date.now() };
- pings[index] = ping;
- Byond.sendMessage('ping', { index });
- index = (index + 1) % PING_QUEUE_SIZE;
- };
- return next => action => {
- const { type, payload } = action;
- if (!initialized) {
- initialized = true;
- interval = setInterval(sendPing, PING_INTERVAL);
- sendPing();
- }
- if (type === 'roundrestart') {
- // Stop pinging because dreamseeker is currently reconnecting.
- // Topic calls in the middle of reconnect will crash the connection.
- clearInterval(interval);
- return next(action);
- }
- if (type === 'pingReply') {
- const { index } = payload;
- const ping = pings[index];
- // Received a timed out ping
- if (!ping) {
- return;
- }
- pings[index] = null;
- return next(pingSuccess(ping));
- }
- return next(action);
- };
-};
diff --git a/tgui/packages/tgui-panel/ping/reducer.js b/tgui/packages/tgui-panel/ping/reducer.js
deleted file mode 100644
index 22d146f8b8..0000000000
--- a/tgui/packages/tgui-panel/ping/reducer.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { clamp01, scale } from 'common/math';
-import { pingFail, pingSuccess } from './actions';
-import { PING_MAX_FAILS, PING_ROUNDTRIP_BEST, PING_ROUNDTRIP_WORST } from './constants';
-
-export const pingReducer = (state = {}, action) => {
- const { type, payload } = action;
- if (type === pingSuccess.type) {
- const { roundtrip } = payload;
- const prevRoundtrip = state.roundtripAvg || roundtrip;
- const roundtripAvg = Math.round(prevRoundtrip * 0.4 + roundtrip * 0.6);
- const networkQuality = 1 - scale(roundtripAvg,
- PING_ROUNDTRIP_BEST, PING_ROUNDTRIP_WORST);
- return {
- roundtrip,
- roundtripAvg,
- failCount: 0,
- networkQuality,
- };
- }
- if (type === pingFail.type) {
- const { failCount = 0 } = state;
- const networkQuality = clamp01(state.networkQuality
- - failCount / PING_MAX_FAILS);
- const nextState = {
- ...state,
- failCount: failCount + 1,
- networkQuality,
- };
- if (failCount > PING_MAX_FAILS) {
- nextState.roundtrip = undefined;
- nextState.roundtripAvg = undefined;
- }
- return nextState;
- }
- return state;
-};
diff --git a/tgui/packages/tgui-panel/ping/selectors.js b/tgui/packages/tgui-panel/ping/selectors.js
deleted file mode 100644
index cfbe95c2f3..0000000000
--- a/tgui/packages/tgui-panel/ping/selectors.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export const selectPing = state => state.ping;
diff --git a/tgui/packages/tgui-panel/settings/SettingsPanel.js b/tgui/packages/tgui-panel/settings/SettingsPanel.js
deleted file mode 100644
index 46de4afc8f..0000000000
--- a/tgui/packages/tgui-panel/settings/SettingsPanel.js
+++ /dev/null
@@ -1,177 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { toFixed } from 'common/math';
-import { useLocalState } from 'tgui/backend';
-import { useDispatch, useSelector } from 'common/redux';
-import { Box, Button, ColorBox, Divider, Dropdown, Flex, Input, LabeledList, NumberInput, Section, Stack, Tabs, TextArea } from 'tgui/components';
-import { ChatPageSettings } from '../chat';
-import { rebuildChat, saveChatToDisk } from '../chat/actions';
-import { THEMES } from '../themes';
-import { changeSettingsTab, updateSettings } from './actions';
-import { FONTS, SETTINGS_TABS } from './constants';
-import { selectActiveTab, selectSettings } from './selectors';
-
-export const SettingsPanel = (props, context) => {
- const activeTab = useSelector(context, selectActiveTab);
- const dispatch = useDispatch(context);
- return (
-
-
-
-
- {SETTINGS_TABS.map(tab => (
- dispatch(changeSettingsTab({
- tabId: tab.id,
- }))}>
- {tab.name}
-
- ))}
-
-
-
-
- {activeTab === 'general' && (
-
- )}
- {activeTab === 'chatPage' && (
-
- )}
-
-
- );
-};
-
-export const SettingsGeneral = (props, context) => {
- const {
- theme,
- fontFamily,
- fontSize,
- lineHeight,
- highlightText,
- highlightColor,
- } = useSelector(context, selectSettings);
- const dispatch = useDispatch(context);
- const [freeFont, setFreeFont] = useLocalState(context, "freeFont", false);
- return (
-
- );
-};
diff --git a/tgui/packages/tgui-panel/settings/actions.js b/tgui/packages/tgui-panel/settings/actions.js
deleted file mode 100644
index df20ad5611..0000000000
--- a/tgui/packages/tgui-panel/settings/actions.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { createAction } from 'common/redux';
-
-export const updateSettings = createAction('settings/update');
-export const loadSettings = createAction('settings/load');
-export const changeSettingsTab = createAction('settings/changeTab');
-export const toggleSettings = createAction('settings/toggle');
-export const openChatSettings = createAction('settings/openChatTab');
diff --git a/tgui/packages/tgui-panel/settings/constants.js b/tgui/packages/tgui-panel/settings/constants.js
deleted file mode 100644
index af446fad58..0000000000
--- a/tgui/packages/tgui-panel/settings/constants.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export const SETTINGS_TABS = [
- {
- id: 'general',
- name: 'General',
- },
- {
- id: 'chatPage',
- name: 'Chat Tabs',
- },
-];
-
-
-export const FONTS_DISABLED = "Default";
-
-export const FONTS = [
- FONTS_DISABLED,
- 'Verdana',
- 'Arial',
- 'Arial Black',
- 'Comic Sans MS',
- 'Impact',
- 'Lucida Sans Unicode',
- 'Tahoma',
- 'Trebuchet MS',
- 'Courier New',
- 'Lucida Console',
-];
diff --git a/tgui/packages/tgui-panel/settings/hooks.js b/tgui/packages/tgui-panel/settings/hooks.js
deleted file mode 100644
index 1cdcaac736..0000000000
--- a/tgui/packages/tgui-panel/settings/hooks.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { useDispatch, useSelector } from 'common/redux';
-import { updateSettings, toggleSettings } from './actions';
-import { selectSettings } from './selectors';
-
-export const useSettings = context => {
- const settings = useSelector(context, selectSettings);
- const dispatch = useDispatch(context);
- return {
- ...settings,
- visible: settings.view.visible,
- toggle: () => dispatch(toggleSettings()),
- update: obj => dispatch(updateSettings(obj)),
- };
-};
diff --git a/tgui/packages/tgui-panel/settings/index.js b/tgui/packages/tgui-panel/settings/index.js
deleted file mode 100644
index 7d5dcda427..0000000000
--- a/tgui/packages/tgui-panel/settings/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export { useSettings } from './hooks';
-export { settingsMiddleware } from './middleware';
-export { settingsReducer } from './reducer';
-export { SettingsPanel } from './SettingsPanel';
diff --git a/tgui/packages/tgui-panel/settings/middleware.js b/tgui/packages/tgui-panel/settings/middleware.js
deleted file mode 100644
index b5ce06c5cc..0000000000
--- a/tgui/packages/tgui-panel/settings/middleware.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { storage } from 'common/storage';
-import { setClientTheme } from '../themes';
-import { loadSettings, updateSettings } from './actions';
-import { selectSettings } from './selectors';
-import { FONTS_DISABLED } from './constants';
-
-const setGlobalFontSize = fontSize => {
- document.documentElement.style
- .setProperty('font-size', fontSize + 'px');
- document.body.style
- .setProperty('font-size', fontSize + 'px');
-};
-
-const setGlobalFontFamily = fontFamily => {
- if (fontFamily === FONTS_DISABLED) fontFamily = null;
-
- document.documentElement.style
- .setProperty('font-family', fontFamily);
- document.body.style
- .setProperty('font-family', fontFamily);
-};
-
-export const settingsMiddleware = store => {
- let initialized = false;
- return next => action => {
- const { type, payload } = action;
- if (!initialized) {
- initialized = true;
- storage.get('panel-settings').then(settings => {
- store.dispatch(loadSettings(settings));
- });
- }
- if (type === updateSettings.type || type === loadSettings.type) {
- // Set client theme
- const theme = payload?.theme;
- if (theme) {
- setClientTheme(theme);
- }
- // Pass action to get an updated state
- next(action);
- const settings = selectSettings(store.getState());
- // Update global UI font size
- setGlobalFontSize(settings.fontSize);
- setGlobalFontFamily(settings.fontFamily);
- // Save settings to the web storage
- storage.set('panel-settings', settings);
- return;
- }
- return next(action);
- };
-};
diff --git a/tgui/packages/tgui-panel/settings/reducer.js b/tgui/packages/tgui-panel/settings/reducer.js
deleted file mode 100644
index 25bd25d66f..0000000000
--- a/tgui/packages/tgui-panel/settings/reducer.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { changeSettingsTab, loadSettings, openChatSettings, toggleSettings, updateSettings } from './actions';
-import { FONTS, SETTINGS_TABS } from './constants';
-
-const initialState = {
- version: 1,
- fontSize: 13,
- fontFamily: FONTS[0],
- lineHeight: 1.2,
- theme: 'light',
- adminMusicVolume: 0.5,
- highlightText: '',
- highlightColor: '#ffdd44',
- view: {
- visible: false,
- activeTab: SETTINGS_TABS[0].id,
- },
-};
-
-export const settingsReducer = (state = initialState, action) => {
- const { type, payload } = action;
- if (type === updateSettings.type) {
- return {
- ...state,
- ...payload,
- };
- }
- if (type === loadSettings.type) {
- // Validate version and/or migrate state
- if (!payload?.version) {
- return state;
- }
- delete payload.view;
- return {
- ...state,
- ...payload,
- };
- }
- if (type === toggleSettings.type) {
- return {
- ...state,
- view: {
- ...state.view,
- visible: !state.view.visible,
- },
- };
- }
- if (type === openChatSettings.type) {
- return {
- ...state,
- view: {
- ...state.view,
- visible: true,
- activeTab: 'chatPage',
- },
- };
- }
- if (type === changeSettingsTab.type) {
- const { tabId } = payload;
- return {
- ...state,
- view: {
- ...state.view,
- activeTab: tabId,
- },
- };
- }
- return state;
-};
diff --git a/tgui/packages/tgui-panel/settings/selectors.js b/tgui/packages/tgui-panel/settings/selectors.js
deleted file mode 100644
index b1504457cf..0000000000
--- a/tgui/packages/tgui-panel/settings/selectors.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export const selectSettings = state => state.settings;
-export const selectActiveTab = state => state.settings.view.activeTab;
diff --git a/tgui/packages/tgui-panel/styles/components/Chat.scss b/tgui/packages/tgui-panel/styles/components/Chat.scss
deleted file mode 100644
index b5701b094d..0000000000
--- a/tgui/packages/tgui-panel/styles/components/Chat.scss
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * Copyright (c) 2020 Aleksej Komarov
- * SPDX-License-Identifier: MIT
- */
-
-@use 'sass:color';
-@use "sass:math";
-@use '~tgui/styles/base.scss';
-@use '~tgui/styles/colors.scss';
-
-$text-color: #abc6ec !default;
-$color-bg-section: base.$color-bg-section !default;
-
-.Chat {
- color: $text-color;
-}
-
-.Chat__badge {
- display: inline-block;
- min-width: 0.5em;
- font-size: 0.7em;
- padding: 0.2em 0.3em;
- line-height: 1;
- color: white;
- text-align: center;
- white-space: nowrap;
- vertical-align: middle;
- background-color: crimson;
- border-radius: 10px;
- transition: font-size 200ms ease-out;
-
- &:before {
- content: 'x';
- }
-}
-
-.Chat__badge--animate {
- font-size: 0.9em;
- transition: font-size 0ms;
-}
-
-.Chat__scrollButton {
- position: fixed;
- right: 2em;
- bottom: 1em;
-}
-
-.Chat__reconnected {
- font-size: 0.85em;
- text-align: center;
- margin: 1em 0 2em;
-
- &:before {
- content: 'Reconnected';
- display: inline-block;
- border-radius: 1em;
- padding: 0 0.7em;
- color: colors.$red;
- background-color: $color-bg-section;
- }
-
- &:after {
- content: '';
- display: block;
- margin-top: -0.75em;
- border-bottom: (math.div(1em, 6)) solid colors.$red;
- }
-}
-
-.Chat__highlight {
- color: #000;
-}
-
-.Chat__highlight--restricted {
- color: #fff;
- background-color: #a00;
- font-weight: bold;
-}
-
-.ChatMessage {
- word-wrap: break-word;
-}
-
-.ChatMessage--highlighted {
- position: relative;
- border-left: (math.div(1em, 6)) solid rgba(255, 221, 68);
- padding-left: 0.5em;
-
- &:after {
- content: '';
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- background-color: rgba(255, 221, 68, 0.1);
- // Make this click-through since this is an overlay
- pointer-events: none;
- }
-}
diff --git a/tgui/packages/tgui-panel/styles/components/Notifications.scss b/tgui/packages/tgui-panel/styles/components/Notifications.scss
deleted file mode 100644
index 6b5160f078..0000000000
--- a/tgui/packages/tgui-panel/styles/components/Notifications.scss
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Copyright (c) 2020 Aleksej Komarov
- * SPDX-License-Identifier: MIT
- */
-
-.Notifications {
- position: absolute;
- bottom: 1em;
- left: 1em;
- right: 2em;
-}
-
-.Notification {
- color: #fff;
- background-color: crimson;
- padding: 0.5em;
- margin: 1em 0;
-
- &:first-child {
- margin-top: 0;
- }
-
- &:last-child {
- margin-bottom: 0;
- }
-}
diff --git a/tgui/packages/tgui-panel/styles/components/Ping.scss b/tgui/packages/tgui-panel/styles/components/Ping.scss
deleted file mode 100644
index fba0906907..0000000000
--- a/tgui/packages/tgui-panel/styles/components/Ping.scss
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright (c) 2020 Aleksej Komarov
- * SPDX-License-Identifier: MIT
- */
-
-@use "sass:math";
-
-$border-color: rgba(140, 140, 140, 0.5) !default;
-
-.Ping {
- position: relative;
- padding: 0.125em 0.25em;
- border: (math.div(1em, 12)) solid $border-color;
- border-radius: 0.25em;
- width: 3.75em;
- text-align: right;
-}
-
-.Ping__indicator {
- content: '';
- position: absolute;
- top: 0.5em;
- left: 0.5em;
- width: 0.5em;
- height: 0.5em;
- background-color: #888;
- border-radius: 0.25em;
-}
diff --git a/tgui/packages/tgui-panel/styles/goon/chat-dark.scss b/tgui/packages/tgui-panel/styles/goon/chat-dark.scss
deleted file mode 100644
index e459ed3573..0000000000
--- a/tgui/packages/tgui-panel/styles/goon/chat-dark.scss
+++ /dev/null
@@ -1,883 +0,0 @@
-/**
- * Copyright (c) 2020 Aleksej Komarov
- * SPDX-License-Identifier: MIT
- */
-
-em {
- font-style: normal;
- font-weight: bold;
-}
-
-img {
- margin: 0;
- padding: 0;
- line-height: 1;
- -ms-interpolation-mode: nearest-neighbor;
- image-rendering: pixelated;
-}
-
-img.icon {
- height: 1em;
- min-height: 16px;
- width: auto;
- vertical-align: bottom;
-}
-
-a {
- color: #397ea5;
-}
-
-a.visited {
- color: #7c00e6;
-}
-
-a:visited {
- color: #7c00e6;
-}
-
-a.popt {
- text-decoration: none;
-}
-
-/* POPUPS */
-
-.popup {
- position: fixed;
- top: 50%;
- left: 50%;
- background: #ddd;
-}
-
-.popup .close {
- position: absolute;
- background: #aaa;
- top: 0;
- right: 0;
- color: #333;
- text-decoration: none;
- z-index: 2;
- padding: 0 10px;
- height: 30px;
- line-height: 30px;
-}
-
-.popup .close:hover {
- background: #999;
-}
-
-.popup .head {
- background: #999;
- color: #ddd;
- padding: 0 10px;
- height: 30px;
- line-height: 30px;
- text-transform: uppercase;
- font-size: 0.9em;
- font-weight: bold;
- border-bottom: 2px solid green;
-}
-
-.popup input {
- border: 1px solid #999;
- background: #fff;
- margin: 0;
- padding: 5px;
- outline: none;
- color: #333;
-}
-
-.popup input[type=text]:hover,
-.popup input[type=text]:active,
-.popup input[type=text]:focus {
- border-color: green;
-}
-
-.popup input[type=submit] {
- padding: 5px 10px;
- background: #999;
- color: #ddd;
- text-transform: uppercase;
- font-size: 0.9em;
- font-weight: bold;
-}
-
-.popup input[type=submit]:hover,
-.popup input[type=submit]:focus,
-.popup input[type=submit]:active {
- background: #aaa;
- cursor: pointer;
-}
-
-.changeFont {
- padding: 10px;
-}
-
-.changeFont a {
- display: block;
- text-decoration: none;
- padding: 3px;
- color: #333;
-}
-
-.changeFont a:hover {
- background: #ccc;
-}
-
-.highlightPopup {
- padding: 10px;
- text-align: center;
-}
-
-.highlightPopup input[type=text] {
- display: block;
- width: 215px;
- text-align: left;
- margin-top: 5px;
-}
-
-.highlightPopup input.highlightColor {
- background-color: #FFFF00;
-}
-
-.highlightPopup input.highlightTermSubmit {
- margin-top: 5px;
-}
-
-
-
-/* ADMIN CONTEXT MENU */
-
-
-.contextMenu {
- background-color: #ddd;
- position: fixed;
- margin: 2px;
- width: 150px;
-}
-
-.contextMenu a {
- display: block;
- padding: 2px 5px;
- text-decoration: none;
- color: #333;
-}
-
-.contextMenu a:hover {
- background-color: #ccc;
-}
-
-
-
-/* ADMIN FILTER MESSAGES MENU */
-
-
-.filterMessages {
- padding: 5px;
-}
-
-.filterMessages div {
- padding: 2px 0;
-}
-
-.filterMessages input {
-}
-
-.filterMessages label {
-}
-
-.icon-stack {
- height: 1em;
- line-height: 1em;
- width: 1em;
- vertical-align: middle;
- margin-top: -2px;
-}
-
-
-
-
-/*****************************************
-*
-* OUTPUT ACTUALLY RELATED TO MESSAGES
-*
-******************************************/
-
-
-
-
-
-/* MOTD */
-
-
-.motd {
- color: #a4bad6;
- font-family: Verdana, sans-serif;
- white-space: normal;
-}
-
-.motd h1,
-.motd h2,
-.motd h3,
-.motd h4,
-.motd h5,
-.motd h6 {
- color: #a4bad6;
- text-decoration: underline;
-}
-
-.motd a,
-.motd a:link,
-.motd a:visited,
-.motd a:active,
-.motd a:hover {
- color: #a4bad6;
-}
-
-
-
-/* ADD HERE FOR BOLD */
-
-
-.bold,
-.name,
-.prefix,
-.ooc,
-.looc,
-.adminooc,
-.admin,
-.medal,
-.yell {
- font-weight: bold;
-}
-
-
-
-/* ADD HERE FOR ITALIC */
-
-
-.italic, .italics {
- font-style: italic;
-}
-
-
-
-/* OUTPUT COLORS */
-
-
-.highlight {
- background: yellow;
-}
-
-h1, h2, h3, h4, h5, h6 {
- color: #a4bad6;
- font-family: Georgia, Verdana, sans-serif;
-}
-
-h1.alert, h2.alert {
- color: #a4bad6;
-}
-
-em {
- font-style: normal;
- font-weight: bold;
-}
-
-.ooc {
- color: #cca300;
- font-weight: bold;
-}
-
-.adminobserverooc {
- color: #0099cc;
- font-weight: bold;
-}
-
-.adminooc {
- color: #3d5bc3;
- font-weight: bold;
-}
-
-.adminsay {
- color: #ff4500;
- font-weight: bold;
-}
-
-.admin {
- color: #5975da;
- font-weight: bold;
-}
-
-.name {
- font-weight: bold;
-}
-
-.say, .emote, .infoplain, .oocplain, .warningplain {
-}
-
-.deadsay {
- color: #e2c1ff;
-}
-
-.binarysay {
- color: #1e90ff;
-}
-
-.binarysay a {
- color: #00ff00;
-}
-
-.binarysay a:active, .binarysay a:visited {
- color: #88ff88;
-}
-
-/* RADIO COLORS */
-/* IF YOU CHANGE THIS KEEP IT IN SYNC WITH TGUI CONSTANTS */
-
-.radio {
- color: #1ecc43;
-}
-
-.sciradio {
- color: #c68cfa;
-}
-
-.comradio {
- color: #fcdf03;
-}
-
-.secradio {
- color: #dd3535;
-}
-
-.medradio {
- color: #57b8f0;
-}
-
-.engradio {
- color: #f37746;
-}
-
-.suppradio {
- color: #b88646;
-}
-
-.servradio {
- color: #6ca729;
-}
-
-.syndradio {
- color: #8f4a4b;
-}
-
-.centcomradio {
- color: #2681a5;
-}
-
-.aiprivradio {
- color: #d65d95;
-}
-
-.redteamradio {
- color: #ff4444;
-}
-
-.blueteamradio {
- color: #3434fd;
-}
-
-.greenteamradio {
- color: #34fd34;
-}
-
-.yellowteamradio {
- color: #fdfd34;
-}
-
-.yell {
- font-weight: bold;
-}
-
-.alert {
- color: #d82020;
-}
-
-.userdanger {
- color: #c51e1e;
- font-weight: bold;
- font-size: 185%;
-}
-
-.danger {
- color: #c51e1e;
-}
-
-.warning {
- color: #c51e1e;
- font-style: italic;
-}
-
-.alertwarning {
- color: #FF0000;
- font-weight: bold;
-}
-
-.boldwarning {
- color: #c51e1e;
- font-style: italic;
- font-weight: bold;
-}
-
-.announce {
- color: #c51e1e;
- font-weight: bold;
-}
-
-.boldannounce {
- color: #c51e1e;
- font-weight: bold;
-}
-
-.minorannounce {
- font-weight: bold;
- font-size: 185%;
-}
-
-.greenannounce {
- color: #059223;
- font-weight: bold;
-}
-
-.rose {
- color: #ff5050;
-}
-
-.info {
- color: #9ab0ff;
-}
-
-.notice {
- color: #6685f5;
-}
-
-.tinynotice {
- color: #6685f5;
- font-size: 85%;
-}
-
-.tinynoticeital {
- color: #6685f5;
- font-style: italic;
- font-size: 85%;
-}
-
-.smallnotice {
- color: #6685f5;
- font-size: 90%;
-}
-
-.smallnoticeital {
- color: #6685f5;
- font-style: italic;
- font-size: 90%;
-}
-
-.boldnotice {
- color: #6685f5;
- font-weight: bold;
-}
-
-.hear {
- color: #6685f5;
- font-style: italic;
-}
-
-.adminnotice {
- color: #6685f5;
-}
-
-.adminhelp {
- color: #ff0000;
- font-weight: bold;
-}
-
-.unconscious {
- color: #a4bad6;
- font-weight: bold;
-}
-
-.suicide {
- color: #ff5050;
- font-style: italic;
-}
-
-.green {
- color: #059223;
-}
-
-.red {
- color: #FF0000;
-}
-
-.blue {
- color: #215cff;
-}
-
-.nicegreen {
- color: #059223;
-}
-
-.cult {
- color: #973e3b;
-}
-
-.cultitalic {
- color: #973e3b;
- font-style: italic;
-}
-
-.cultbold {
- color: #973e3b;
- font-style: italic;
- font-weight: bold;
-}
-
-.cultboldtalic {
- color: #973e3b;
- font-weight: bold;
- font-size: 185%;
-}
-
-.cultlarge {
- color: #973e3b;
- font-weight: bold;
- font-size: 185%;
-}
-
-.narsie {
- color: #973e3b;
- font-weight: bold;
- font-size: 925%;
-}
-
-.narsiesmall {
- color: #973e3b;
- font-weight: bold;
- font-size: 370%;
-}
-
-.colossus {
- color: #7F282A;
- font-size: 310%;
-}
-
-.hierophant {
- color: #b441ee;
- font-weight: bold;
- font-style: italic;
-}
-
-.hierophant_warning {
- color: #c56bf1;
- font-style: italic;
-}
-
-.purple {
- color: #9956d3;
-}
-
-.holoparasite {
- color: #88809c;
-}
-
-.revennotice {
- color: #c099e2;
-}
-
-.revenboldnotice {
- color: #c099e2;
- font-weight: bold;
-}
-
-.revenbignotice {
- color: #c099e2;
- font-weight: bold;
- font-size: 185%;
-}
-
-.revenminor {
- color: #823abb;
-}
-
-.revenwarning {
- color: #760fbb;
- font-style: italic;
-}
-
-.revendanger {
- color: #760fbb;
- font-weight: bold;
- font-size: 185%;
-}
-
-.deconversion_message {
- color: #a947ff;
- font-size: 185%;
- font-style: italic;
-}
-
-.ghostalert {
- color: #6600ff;
- font-style: italic;
- font-weight: bold;
-}
-
-.alien {
- color: #855d85;
-}
-
-.noticealien {
- color: #059223;
-}
-
-.alertalien {
- color: #059223;
- font-weight: bold;
-}
-
-.changeling {
- color: #059223;
- font-style: italic;
-}
-
-.alertsyndie {
- color: #FF0000;
- font-size: 185%;
- font-weight: bold;
-}
-
-.spider {
- color: #8800ff;
- font-weight: bold;
- font-size: 185%;
-}
-
-.interface {
- color: #750e75;
-}
-
-.sans {
- font-family: "Comic Sans MS", cursive, sans-serif;
-}
-
-.papyrus {
- font-family: "Papyrus", cursive, sans-serif;
-}
-
-.robot {
- font-family: "Courier New", cursive, sans-serif;
-}
-
-.tape_recorder {
- color: #FF0000;
- font-family: "Courier New", cursive, sans-serif;
-}
-
-.command_headset {
- font-weight: bold;
- font-size: 160%;
-}
-
-.small {
- font-size: 60%;
-}
-
-.big {
- font-size: 185%;
-}
-
-.reallybig {
- font-size: 245%;
-}
-
-.extremelybig {
- font-size: 310%;
-}
-
-.greentext {
- color: #059223;
- font-size: 185%;
-}
-
-.redtext {
- color: #c51e1e;
- font-size: 185%;
-}
-
-.clown {
- color: #ff70c1;
- font-size: 160%;
- font-family: "Comic Sans MS", cursive, sans-serif;
- font-weight: bold;
-}
-
-.singing {
- font-family: "Trebuchet MS", cursive, sans-serif;
- font-style: italic;
-}
-
-.his_grace {
- color: #15D512;
- font-family: "Courier New", cursive, sans-serif;
- font-style: italic;
-}
-
-.hypnophrase {
- color: #202020;
- font-weight: bold;
- animation: hypnocolor 1500ms infinite;
- animation-direction: alternate;
-}
-
-@keyframes hypnocolor {
- 0% {
- color: #202020;
- }
-
- 25% {
- color: #4b02ac;
- }
-
- 50% {
- color: #9f41f1;
- }
-
- 75% {
- color: #541c9c;
- }
-
- 100% {
- color: #7adbf3;
- }
-}
-
-.phobia {
- color: #dd0000;
- font-weight: bold;
- animation: phobia 750ms infinite;
-}
-
-@keyframes phobia {
- 0% {
- color: #f75a5a;
- }
-
- 50% {
- color: #dd0000;
- }
-
- 100% {
- color: #f75a5a;
- }
-}
-
-.icon {
- height: 1em;
- width: auto;
-}
-
-.bigicon {
- font-size: 2.5em;
-}
-
-.memo {
- color: #638500;
- text-align: center;
-}
-
-.memoedit {
- text-align: center;
- font-size: 125%;
-}
-
-.abductor {
- color: #c204c2;
- font-style: italic;
-}
-
-.mind_control {
- color: #df3da9;
- font-size: 100%;
- font-weight: bold;
- font-style: italic;
-}
-
-.slime {
- color: #00CED1;
-}
-
-.drone {
- color: #848482;
-}
-
-.monkey {
- color: #975032;
-}
-
-.swarmer {
- color: #2C75FF;
-}
-
-.resonate {
- color: #298F85;
-}
-
-.monkeyhive {
- color: #a56408;
-}
-
-.monkeylead {
- color: #af6805;
- font-size: 80%;
-}
-
-.connectionClosed, .fatalError {
- background: red;
- color: white;
- padding: 5px;
-}
-
-.connectionClosed.restored {
- background: green;
-}
-
-.internal.boldnshit {
- color: #3d5bc3;
- font-weight: bold;
-}
-
-
-
-/* HELPER CLASSES */
-
-
-.text-normal {
- font-weight: normal;
- font-style: normal;
-}
-
-.hidden {
- display: none;
- visibility: hidden;
-}
-
-.ml-1 {
- margin-left: 1em;
-}
-
-.ml-2 {
- margin-left: 2em;
-}
-
-.ml-3 {
- margin-left: 3em;
-}
diff --git a/tgui/packages/tgui-panel/styles/goon/chat-light.scss b/tgui/packages/tgui-panel/styles/goon/chat-light.scss
deleted file mode 100644
index 48275ca7ed..0000000000
--- a/tgui/packages/tgui-panel/styles/goon/chat-light.scss
+++ /dev/null
@@ -1,921 +0,0 @@
-/**
- * Copyright (c) 2020 Aleksej Komarov
- * SPDX-License-Identifier: MIT
- */
-
-html, body {
- padding: 0;
- margin: 0;
- height: 100%;
- color: #000000;
-}
-
-body {
- background: #fff;
- font-family: Verdana, sans-serif;
- font-size: 13px;
- line-height: 1.2;
- overflow-x: hidden;
- overflow-y: scroll;
- word-wrap: break-word;
-}
-
-em {
- font-style: normal;
- font-weight: bold;
-}
-
-img {
- margin: 0;
- padding: 0;
- line-height: 1;
- -ms-interpolation-mode: nearest-neighbor;
- image-rendering: pixelated;
-}
-
-img.icon {
- height: 1em;
- min-height: 16px;
- width: auto;
- vertical-align: bottom;
-}
-
-a {
- color: #0000ff;
-}
-
-a.visited {
- color: #ff00ff;
-}
-
-a:visited {
- color: #ff00ff;
-}
-
-a.popt {
- text-decoration: none;
-}
-
-/* POPUPS */
-
-.popup {
- position: fixed;
- top: 50%;
- left: 50%;
- background: #ddd;
-}
-
-.popup .close {
- position: absolute;
- background: #aaa;
- top: 0;
- right: 0;
- color: #333;
- text-decoration: none;
- z-index: 2;
- padding: 0 10px;
- height: 30px;
- line-height: 30px;
-}
-
-.popup .close:hover {
- background: #999;
-}
-
-.popup .head {
- background: #999;
- color: #ddd;
- padding: 0 10px;
- height: 30px;
- line-height: 30px;
- text-transform: uppercase;
- font-size: 0.9em;
- font-weight: bold;
- border-bottom: 2px solid green;
-}
-
-.popup input {
- border: 1px solid #999;
- background: #fff;
- margin: 0;
- padding: 5px;
- outline: none;
- color: #333;
-}
-
-.popup input[type=text]:hover,
-.popup input[type=text]:active,
-.popup input[type=text]:focus {
- border-color: green;
-}
-
-.popup input[type=submit] {
- padding: 5px 10px;
- background: #999;
- color: #ddd;
- text-transform: uppercase;
- font-size: 0.9em;
- font-weight: bold;
-}
-
-.popup input[type=submit]:hover,
-.popup input[type=submit]:focus,
-.popup input[type=submit]:active {
- background: #aaa;
- cursor: pointer;
-}
-
-.changeFont {
- padding: 10px;
-}
-
-.changeFont a {
- display: block;
- text-decoration: none;
- padding: 3px;
- color: #333;
-}
-
-.changeFont a:hover {
- background: #ccc;
-}
-
-.highlightPopup {
- padding: 10px;
- text-align: center;
-}
-
-.highlightPopup input[type=text] {
- display: block;
- width: 215px;
- text-align: left;
- margin-top: 5px;
-}
-
-.highlightPopup input.highlightColor {
- background-color: #FFFF00;
-}
-
-.highlightPopup input.highlightTermSubmit {
- margin-top: 5px;
-}
-
-
-
-/* ADMIN CONTEXT MENU */
-
-
-.contextMenu {
- background-color: #ddd;
- position: fixed;
- margin: 2px;
- width: 150px;
-}
-
-.contextMenu a {
- display: block;
- padding: 2px 5px;
- text-decoration: none;
- color: #333;
-}
-
-.contextMenu a:hover {
- background-color: #ccc;
-}
-
-
-
-/* ADMIN FILTER MESSAGES MENU */
-
-
-.filterMessages {
- padding: 5px;
-}
-
-.filterMessages div {
- padding: 2px 0;
-}
-
-.filterMessages input {
-}
-
-.filterMessages label {
-}
-
-.icon-stack {
- height: 1em;
- line-height: 1em;
- width: 1em;
- vertical-align: middle;
- margin-top: -2px;
-}
-
-
-
-
-/*****************************************
-*
-* OUTPUT ACTUALLY RELATED TO MESSAGES
-*
-******************************************/
-
-
-
-
-
-/* MOTD */
-
-
-.motd {
- color: #638500;
- font-family: Verdana, sans-serif;
- white-space: normal;
-}
-
-.motd h1,
-.motd h2,
-.motd h3,
-.motd h4,
-.motd h5,
-.motd h6 {
- color: #638500;
- text-decoration: underline;
-}
-
-.motd a,
-.motd a:link,
-.motd a:visited,
-.motd a:active,
-.motd a:hover {
- color: #638500;
-}
-
-
-
-/* ADD HERE FOR BOLD */
-
-
-.bold,
-.name,
-.prefix,
-.ooc,
-.looc,
-.adminooc,
-.admin,
-.medal,
-.yell {
- font-weight: bold;
-}
-
-
-
-/* ADD HERE FOR ITALIC */
-
-
-.italic,
-.italics {
- font-style: italic;
-}
-
-
-
-/* OUTPUT COLORS */
-
-
-.highlight {
- background: yellow;
-}
-
-h1, h2, h3, h4, h5, h6 {
- color: #0000ff;
- font-family: Georgia, Verdana, sans-serif;
-}
-
-h1.alert, h2.alert {
- color: #000000;
-}
-
-em {
- font-style: normal;
- font-weight: bold;
-}
-
-.ooc {
- color: #002eb8;
- font-weight: bold;
-}
-
-.adminobserverooc {
- color: #0099cc;
- font-weight: bold;
-}
-
-.adminooc {
- color: #700038;
- font-weight: bold;
-}
-
-.adminsay {
- color: #ff4500;
- font-weight: bold;
-}
-
-.admin {
- color: #4473ff;
- font-weight: bold;
-}
-
-.name {
- font-weight: bold;
-}
-
-.say, .emote, .infoplain, .oocplain, .warningplain {
-}
-
-.deadsay {
- color: #5c00e6;
-}
-
-.binarysay {
- color: #20c20e;
- background-color: #000000;
- display: block;
-}
-
-.binarysay a {
- color: #00ff00;
-}
-
-.binarysay a:active,
-.binarysay a:visited {
- color: #88ff88;
-}
-
-.radio {
- color: #008000;
-}
-
-.sciradio {
- color: #993399;
-}
-
-.comradio {
- color: #948f02;
-}
-
-.secradio {
- color: #a30000;
-}
-
-.medradio {
- color: #337296;
-}
-
-.engradio {
- color: #fb5613;
-}
-
-.suppradio {
- color: #a8732b;
-}
-
-.servradio {
- color: #6eaa2c;
-}
-
-.syndradio {
- color: #6d3f40;
-}
-
-.centcomradio {
- color: #686868;
-}
-
-.aiprivradio {
- color: #ff00ff;
-}
-
-.redteamradio {
- color: #ff0000;
-}
-
-.blueteamradio {
- color: #0000ff;
-}
-
-.greenteamradio {
- color: #00ff00;
-}
-
-.yellowteamradio {
- color: #d1ba22;
-}
-
-.yell {
- font-weight: bold;
-}
-
-.alert {
- color: #ff0000;
-}
-
-h1.alert, h2.alert {
- color: #000000;
-}
-
-.userdanger {
- color: #ff0000;
- font-weight: bold;
- font-size: 185%;
-}
-
-.bolddanger {
- color: #ff0000;
- font-weight: bold;
-}
-
-.danger {
- color: #ff0000;
-}
-
-.tinydanger {
- color: #ff0000;
- font-size: 85%;
-}
-
-.smalldanger {
- color: #ff0000;
- font-size: 90%;
-}
-
-.warning {
- color: #ff0000;
- font-style: italic;
-}
-
-.alertwarning {
- color: #FF0000;
- font-weight: bold;
-}
-
-.boldwarning {
- color: #ff0000;
- font-style: italic;
- font-weight: bold;
-}
-
-.announce {
- color: #228b22;
- font-weight: bold;
-}
-
-.boldannounce {
- color: #ff0000;
- font-weight: bold;
-}
-
-.minorannounce {
- font-weight: bold;
- font-size: 185%;
-}
-
-.greenannounce {
- color: #00ff00;
- font-weight: bold;
-}
-
-.rose {
- color: #ff5050;
-}
-
-.info {
- color: #0000CC;
-}
-
-.notice {
- color: #000099;
-}
-
-.tinynotice {
- color: #000099;
- font-size: 85%;
-}
-
-.tinynoticeital {
- color: #000099;
- font-style: italic;
- font-size: 85%;
-}
-
-.smallnotice {
- color: #000099;
- font-size: 90%;
-}
-
-.smallnoticeital {
- color: #000099;
- font-style: italic;
- font-size: 90%;
-}
-
-.boldnotice {
- color: #000099;
- font-weight: bold;
-}
-
-.hear {
- color: #000099;
- font-style: italic;
-}
-
-.adminnotice {
- color: #0000ff;
-}
-
-.adminhelp {
- color: #ff0000;
- font-weight: bold;
-}
-
-.unconscious {
- color: #0000ff;
- font-weight: bold;
-}
-
-.suicide {
- color: #ff5050;
- font-style: italic;
-}
-
-.green {
- color: #03ff39;
-}
-
-.red {
- color: #FF0000;
-}
-
-.blue {
- color: #0000FF;
-}
-
-.nicegreen {
- color: #14a833;
-}
-
-.cult {
- color: #973e3b;
-}
-
-.cultitalic {
- color: #973e3b;
- font-style: italic;
-}
-
-.cultbold {
- color: #973e3b;
- font-style: italic;
- font-weight: bold;
-}
-
-.cultboldtalic {
- color: #973e3b;
- font-weight: bold;
- font-size: 185%;
-}
-
-.cultlarge {
- color: #973e3b;
- font-weight: bold;
- font-size: 185%;
-}
-
-.narsie {
- color: #973e3b;
- font-weight: bold;
- font-size: 925%;
-}
-
-.narsiesmall {
- color: #973e3b;
- font-weight: bold;
- font-size: 370%;
-}
-
-.colossus {
- color: #7F282A;
- font-size: 310%;
-}
-
-.hierophant {
- color: #660099;
- font-weight: bold;
- font-style: italic;
-}
-
-.hierophant_warning {
- color: #660099;
- font-style: italic;
-}
-
-.purple {
- color: #5e2d79;
-}
-
-.holoparasite {
- color: #35333a;
-}
-
-.revennotice {
- color: #1d2953;
-}
-
-.revenboldnotice {
- color: #1d2953;
- font-weight: bold;
-}
-
-.revenbignotice {
- color: #1d2953;
- font-weight: bold;
- font-size: 185%;
-}
-
-.revenminor {
- color: #823abb;
-}
-
-.revenwarning {
- color: #760fbb;
- font-style: italic;
-}
-
-.revendanger {
- color: #760fbb;
- font-weight: bold;
- font-size: 185%;
-}
-
-.deconversion_message {
- color: #5000A0;
- font-size: 185%;
- font-style: italic;
-}
-
-.ghostalert {
- color: #5c00e6;
- font-style: italic;
- font-weight: bold;
-}
-
-.alien {
- color: #543354;
-}
-
-.noticealien {
- color: #00c000;
-}
-
-.alertalien {
- color: #00c000;
- font-weight: bold;
-}
-
-.changeling {
- color: #800080;
- font-style: italic;
-}
-
-.alertsyndie {
- color: #FF0000;
- font-size: 185%;
- font-weight: bold;
-}
-
-.spider {
- color: #4d004d;
- font-weight: bold;
- font-size: 185%;
-}
-
-.interface {
- color: #330033;
-}
-
-.sans {
- font-family: "Comic Sans MS", cursive, sans-serif;
-}
-
-.papyrus {
- font-family: "Papyrus", cursive, sans-serif;
-}
-
-.robot {
- font-family: "Courier New", cursive, sans-serif;
-}
-
-.tape_recorder {
- color: #800000;
- font-family: "Courier New", cursive, sans-serif;
-}
-
-.command_headset {
- font-weight: bold;
- font-size: 160%;
-}
-
-.small {
- font-size: 60%;
-}
-
-.big {
- font-size: 185%;
-}
-
-.reallybig {
- font-size: 245%;
-}
-
-.extremelybig {
- font-size: 310%;
-}
-
-.greentext {
- color: #00FF00;
- font-size: 185%;
-}
-
-.redtext {
- color: #FF0000;
- font-size: 185%;
-}
-
-.clown {
- color: #FF69Bf;
- font-size: 160%;
- font-family: "Comic Sans MS", cursive, sans-serif;
- font-weight: bold;
-}
-
-.singing {
- font-family: "Trebuchet MS", cursive, sans-serif;
- font-style: italic;
-}
-
-.his_grace {
- color: #15D512;
- font-family: "Courier New", cursive, sans-serif;
- font-style: italic;
-}
-
-.hypnophrase {
- color: #0d0d0d;
- font-weight: bold;
- animation: hypnocolor 1500ms infinite;
- animation-direction: alternate;
-}
-
-@keyframes hypnocolor {
- 0% {
- color: #0d0d0d;
- }
-
- 25% {
- color: #410194;
- }
-
- 50% {
- color: #7f17d8;
- }
-
- 75% {
- color: #410194;
- }
-
- 100% {
- color: #3bb5d3;
- }
-}
-
-.phobia {
- color: #dd0000;
- font-weight: bold;
- animation: phobia 750ms infinite;
-}
-
-@keyframes phobia {
- 0% {
- color: #0d0d0d;
- }
-
- 50% {
- color: #dd0000;
- }
-
- 100% {
- color: #0d0d0d;
- }
-}
-
-.icon {
- height: 1em;
- width: auto;
-}
-
-.bigicon {
- font-size: 2.5em;
-}
-
-.memo {
- color: #638500;
- text-align: center;
-}
-
-.memoedit {
- text-align: center;
- font-size: 125%;
-}
-
-.abductor {
- color: #800080;
- font-style: italic;
-}
-
-.mind_control {
- color: #A00D6F;
- font-size: 100%;
- font-weight: bold;
- font-style: italic;
-}
-
-.slime {
- color: #00CED1;
-}
-
-.drone {
- color: #848482;
-}
-
-.monkey {
- color: #975032;
-}
-
-.swarmer {
- color: #2C75FF;
-}
-
-.resonate {
- color: #298F85;
-}
-
-.monkeyhive {
- color: #774704;
-}
-
-.monkeylead {
- color: #774704;
- font-size: 80%;
-}
-
-.connectionClosed,
-.fatalError {
- background: red;
- color: white;
- padding: 5px;
-}
-
-.connectionClosed.restored {
- background: green;
-}
-
-.internal.boldnshit {
- color: blue;
- font-weight: bold;
-}
-
-
-
-/* HELPER CLASSES */
-
-
-.text-normal {
- font-weight: normal;
- font-style: normal;
-}
-
-.hidden {
- display: none;
- visibility: hidden;
-}
-
-.ml-1 {
- margin-left: 1em;
-}
-
-.ml-2 {
- margin-left: 2em;
-}
-
-.ml-3 {
- margin-left: 3em;
-}
diff --git a/tgui/packages/tgui-panel/styles/main.scss b/tgui/packages/tgui-panel/styles/main.scss
deleted file mode 100644
index 8a18612e04..0000000000
--- a/tgui/packages/tgui-panel/styles/main.scss
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * Copyright (c) 2020 Aleksej Komarov
- * SPDX-License-Identifier: MIT
- */
-
-@use 'sass:meta';
-@use "sass:math";
-@use 'sass:color';
-
-@use '~tgui/styles/colors.scss';
-@use '~tgui/styles/base.scss' with (
- $color-bg: #202020,
- $color-bg-section: color.adjust(#202020, $lightness: -5%),
- $color-bg-grad-spread: 0%,
-);
-
-// Core styles
-@include meta.load-css('~tgui/styles/reset.scss');
-
-// Atomic classes
-@include meta.load-css('~tgui/styles/atomic/candystripe.scss');
-@include meta.load-css('~tgui/styles/atomic/color.scss');
-@include meta.load-css('~tgui/styles/atomic/debug-layout.scss');
-@include meta.load-css('~tgui/styles/atomic/outline.scss');
-@include meta.load-css('~tgui/styles/atomic/text.scss');
-
-// Components
-@include meta.load-css('~tgui/styles/components/BlockQuote.scss');
-@include meta.load-css('~tgui/styles/components/Button.scss');
-@include meta.load-css('~tgui/styles/components/ColorBox.scss');
-@include meta.load-css('~tgui/styles/components/Dimmer.scss');
-@include meta.load-css('~tgui/styles/components/Divider.scss');
-@include meta.load-css('~tgui/styles/components/Dropdown.scss');
-@include meta.load-css('~tgui/styles/components/Flex.scss');
-@include meta.load-css('~tgui/styles/components/Input.scss');
-@include meta.load-css('~tgui/styles/components/Knob.scss');
-@include meta.load-css('~tgui/styles/components/LabeledList.scss');
-@include meta.load-css('~tgui/styles/components/Modal.scss');
-@include meta.load-css('~tgui/styles/components/NanoMap.scss');
-@include meta.load-css('~tgui/styles/components/NoticeBox.scss');
-@include meta.load-css('~tgui/styles/components/NumberInput.scss');
-@include meta.load-css('~tgui/styles/components/ProgressBar.scss');
-@include meta.load-css('~tgui/styles/components/Section.scss');
-@include meta.load-css('~tgui/styles/components/Slider.scss');
-@include meta.load-css('~tgui/styles/components/Stack.scss');
-@include meta.load-css('~tgui/styles/components/Table.scss');
-@include meta.load-css('~tgui/styles/components/Tabs.scss');
-@include meta.load-css('~tgui/styles/components/TextArea.scss');
-@include meta.load-css('~tgui/styles/components/Tooltip.scss');
-
-// Components specific to tgui-panel
-@include meta.load-css('./components/Chat.scss');
-@include meta.load-css('./components/Ping.scss');
-@include meta.load-css('./components/Notifications.scss');
-
-// Layouts
-@include meta.load-css('~tgui/styles/layouts/Layout.scss');
-// @include meta.load-css('~tgui/styles/layouts/TitleBar.scss');
-@include meta.load-css('~tgui/styles/layouts/Window.scss');
-
-// Goonchat styles
-@include meta.load-css('./goon/chat-dark.scss');
diff --git a/tgui/packages/tgui-panel/styles/themes/light.scss b/tgui/packages/tgui-panel/styles/themes/light.scss
deleted file mode 100644
index 41cd8c888b..0000000000
--- a/tgui/packages/tgui-panel/styles/themes/light.scss
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * Copyright (c) 2020 Aleksej Komarov
- * SPDX-License-Identifier: MIT
- */
-
-@use 'sass:color';
-@use 'sass:meta';
-
-@use '~tgui/styles/colors.scss' with (
- $primary: #ffffff,
- $bg-lightness: -25%,
- $fg-lightness: -10%,
- $label: #3b3b3b,
- // Makes button look actually grey due to weird maths.
- $grey: #ffffff,
- // Commenting out color maps will adjust all colors based on the lightness
- // settings above, but will add extra 10KB to the theme.
- // $fg-map-keys: (),
- // $bg-map-keys: (),
-);
-@use '~tgui/styles/base.scss' with (
- $color-fg: #000000,
- $color-bg: #eeeeee,
- $color-bg-section: #ffffff,
- $color-bg-grad-spread: 0%,
-);
-
-// A fat warning to anyone who wants to use this: this only half works.
-// It was made almost purely for the nuke ui, and requires a good amount of manual hacks to get it working as intended.
-.theme-light {
- // Atomic classes
- @include meta.load-css('~tgui/styles/atomic/color.scss');
-
- // Components
- @include meta.load-css('~tgui/styles/components/Tabs.scss', $with: (
- 'text-color': rgba(0, 0, 0, 0.5),
- 'color-default': rgba(0, 0, 0, 1),
- ));
- @include meta.load-css('~tgui/styles/components/Section.scss');
- @include meta.load-css('~tgui/styles/components/Button.scss', $with: (
- 'color-default': #bbbbbb,
- 'color-disabled': #363636,
- 'color-selected': #0668b8,
- 'color-caution': #be6209,
- 'color-danger': #9a9d00,
- 'color-transparent-text': rgba(0, 0, 0, 0.5),
- ));
- @include meta.load-css('~tgui/styles/components/Input.scss', $with: (
- 'border-color': colors.fg(colors.$label),
- 'background-color': #ffffff,
- ));
- @include meta.load-css('~tgui/styles/components/NumberInput.scss');
- @include meta.load-css('~tgui/styles/components/TextArea.scss');
- @include meta.load-css('~tgui/styles/components/Knob.scss');
- @include meta.load-css('~tgui/styles/components/Slider.scss');
- @include meta.load-css('~tgui/styles/components/ProgressBar.scss');
-
- // Components specific to tgui-panel
- @include meta.load-css('../components/Chat.scss', $with: (
- 'text-color': #000000,
- ));
-
- // Layouts
- @include meta.load-css('~tgui/styles/layouts/Layout.scss', $with: (
- 'scrollbar-color-multiplier': -1,
- ));
- @include meta.load-css('~tgui/styles/layouts/Window.scss');
- @include meta.load-css('~tgui/styles/layouts/TitleBar.scss', $with: (
- 'text-color': rgba(0, 0, 0, 0.75),
- 'background-color': base.$color-bg,
- 'shadow-color-core': rgba(0, 0, 0, 0.25),
- ));
-
- // Goonchat styles
- @include meta.load-css('../goon/chat-light.scss');
-}
diff --git a/tgui/packages/tgui-panel/telemetry.js b/tgui/packages/tgui-panel/telemetry.js
deleted file mode 100644
index afad245c48..0000000000
--- a/tgui/packages/tgui-panel/telemetry.js
+++ /dev/null
@@ -1,88 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { storage } from 'common/storage';
-import { createLogger } from 'tgui/logging';
-
-const logger = createLogger('telemetry');
-
-const MAX_CONNECTIONS_STORED = 10;
-
-const connectionsMatch = (a, b) => (
- a.ckey === b.ckey
- && a.address === b.address
- && a.computer_id === b.computer_id
-);
-
-export const telemetryMiddleware = store => {
- let telemetry;
- let wasRequestedWithPayload;
- return next => action => {
- const { type, payload } = action;
- // Handle telemetry requests
- if (type === 'telemetry/request') {
- // Defer telemetry request until we have the actual telemetry
- if (!telemetry) {
- logger.debug('deferred');
- wasRequestedWithPayload = payload;
- return;
- }
- logger.debug('sending');
- const limits = payload?.limits || {};
- // Trim connections according to the server limit
- const connections = telemetry.connections.slice(0, limits.connections);
- Byond.sendMessage('telemetry', { connections });
- return;
- }
- // Keep telemetry up to date
- if (type === 'backend/update') {
- next(action);
- (async () => {
- // Extract client data
- const client = payload?.config?.client;
- if (!client) {
- logger.error('backend/update payload is missing client data!');
- return;
- }
- // Load telemetry
- if (!telemetry) {
- telemetry = await storage.get('telemetry') || {};
- if (!telemetry.connections) {
- telemetry.connections = [];
- }
- logger.debug('retrieved telemetry from storage', telemetry);
- }
- // Append a connection record
- let telemetryMutated = false;
- const duplicateConnection = telemetry.connections
- .find(conn => connectionsMatch(conn, client));
- if (!duplicateConnection) {
- telemetryMutated = true;
- telemetry.connections.unshift(client);
- if (telemetry.connections.length > MAX_CONNECTIONS_STORED) {
- telemetry.connections.pop();
- }
- }
- // Save telemetry
- if (telemetryMutated) {
- logger.debug('saving telemetry to storage', telemetry);
- storage.set('telemetry', telemetry);
- }
- // Continue deferred telemetry requests
- if (wasRequestedWithPayload) {
- const payload = wasRequestedWithPayload;
- wasRequestedWithPayload = null;
- store.dispatch({
- type: 'telemetry/request',
- payload,
- });
- }
- })();
- return;
- }
- return next(action);
- };
-};
diff --git a/tgui/packages/tgui-panel/themes.js b/tgui/packages/tgui-panel/themes.js
deleted file mode 100644
index 1ef6caaf45..0000000000
--- a/tgui/packages/tgui-panel/themes.js
+++ /dev/null
@@ -1,134 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-export const THEMES = ['light', 'dark'];
-
-const COLOR_DARK_BG = '#202020';
-const COLOR_DARK_BG_DARKER = '#171717';
-const COLOR_DARK_TEXT = '#a4bad6';
-
-let setClientThemeTimer = null;
-
-/**
- * Darkmode preference, originally by Kmc2000.
- *
- * This lets you switch client themes by using winset.
- *
- * If you change ANYTHING in interface/skin.dmf you need to change it here.
- *
- * There's no way round it. We're essentially changing the skin by hand.
- * It's painful but it works, and is the way Lummox suggested.
- */
-export const setClientTheme = name => {
- // Transmit once for fast updates and again in a little while in case we won
- // the race against statbrowser init.
- clearInterval(setClientThemeTimer);
- Byond.command(`.output statbrowser:set_theme ${name}`);
- setClientThemeTimer = setTimeout(() => {
- Byond.command(`.output statbrowser:set_theme ${name}`);
- }, 1500);
-
- if (name === 'light') {
- return Byond.winset({
- // Main windows
- 'infowindow.background-color': 'none',
- 'infowindow.text-color': '#000000',
- 'info.background-color': 'none',
- 'info.text-color': '#000000',
- 'browseroutput.background-color': 'none',
- 'browseroutput.text-color': '#000000',
- 'outputwindow.background-color': 'none',
- 'outputwindow.text-color': '#000000',
- 'mainwindow.background-color': 'none',
- 'split.background-color': 'none',
- // Buttons
- 'changelog.background-color': 'none',
- 'changelog.text-color': '#000000',
- 'rules.background-color': 'none',
- 'rules.text-color': '#000000',
- 'wiki.background-color': 'none',
- 'wiki.text-color': '#000000',
- 'forum.background-color': 'none',
- 'forum.text-color': '#000000',
- 'github.background-color': 'none',
- 'github.text-color': '#000000',
- 'report-issue.background-color': 'none',
- 'report-issue.text-color': '#000000',
- // Status and verb tabs
- 'output.background-color': 'none',
- 'output.text-color': '#000000',
- 'statwindow.background-color': 'none',
- 'statwindow.text-color': '#000000',
- 'stat.background-color': '#FFFFFF',
- 'stat.tab-background-color': 'none',
- 'stat.text-color': '#000000',
- 'stat.tab-text-color': '#000000',
- 'stat.prefix-color': '#000000',
- 'stat.suffix-color': '#000000',
- // Say, OOC, me Buttons etc.
- 'saybutton.background-color': 'none',
- 'saybutton.text-color': '#000000',
- 'oocbutton.background-color': 'none',
- 'oocbutton.text-color': '#000000',
- 'mebutton.background-color': 'none',
- 'mebutton.text-color': '#000000',
- 'asset_cache_browser.background-color': 'none',
- 'asset_cache_browser.text-color': '#000000',
- 'tooltip.background-color': 'none',
- 'tooltip.text-color': '#000000',
- });
- }
- if (name === 'dark') {
- Byond.winset({
- // Main windows
- 'infowindow.background-color': COLOR_DARK_BG,
- 'infowindow.text-color': COLOR_DARK_TEXT,
- 'info.background-color': COLOR_DARK_BG,
- 'info.text-color': COLOR_DARK_TEXT,
- 'browseroutput.background-color': COLOR_DARK_BG,
- 'browseroutput.text-color': COLOR_DARK_TEXT,
- 'outputwindow.background-color': COLOR_DARK_BG,
- 'outputwindow.text-color': COLOR_DARK_TEXT,
- 'mainwindow.background-color': COLOR_DARK_BG,
- 'split.background-color': COLOR_DARK_BG,
- // Buttons
- 'changelog.background-color': '#494949',
- 'changelog.text-color': COLOR_DARK_TEXT,
- 'rules.background-color': '#494949',
- 'rules.text-color': COLOR_DARK_TEXT,
- 'wiki.background-color': '#494949',
- 'wiki.text-color': COLOR_DARK_TEXT,
- 'forum.background-color': '#494949',
- 'forum.text-color': COLOR_DARK_TEXT,
- 'github.background-color': '#3a3a3a',
- 'github.text-color': COLOR_DARK_TEXT,
- 'report-issue.background-color': '#492020',
- 'report-issue.text-color': COLOR_DARK_TEXT,
- // Status and verb tabs
- 'output.background-color': COLOR_DARK_BG_DARKER,
- 'output.text-color': COLOR_DARK_TEXT,
- 'statwindow.background-color': COLOR_DARK_BG_DARKER,
- 'statwindow.text-color': COLOR_DARK_TEXT,
- 'stat.background-color': COLOR_DARK_BG_DARKER,
- 'stat.tab-background-color': COLOR_DARK_BG,
- 'stat.text-color': COLOR_DARK_TEXT,
- 'stat.tab-text-color': COLOR_DARK_TEXT,
- 'stat.prefix-color': COLOR_DARK_TEXT,
- 'stat.suffix-color': COLOR_DARK_TEXT,
- // Say, OOC, me Buttons etc.
- 'saybutton.background-color': COLOR_DARK_BG,
- 'saybutton.text-color': COLOR_DARK_TEXT,
- 'oocbutton.background-color': COLOR_DARK_BG,
- 'oocbutton.text-color': COLOR_DARK_TEXT,
- 'mebutton.background-color': COLOR_DARK_BG,
- 'mebutton.text-color': COLOR_DARK_TEXT,
- 'asset_cache_browser.background-color': COLOR_DARK_BG,
- 'asset_cache_browser.text-color': COLOR_DARK_TEXT,
- 'tooltip.background-color': COLOR_DARK_BG,
- 'tooltip.text-color': COLOR_DARK_TEXT,
- });
- }
-};
diff --git a/tgui/packages/tgui-polyfill/01-ie8.js b/tgui/packages/tgui-polyfill/01-ie8.js
index af4b3a5ee8..3da4e2393d 100644
--- a/tgui/packages/tgui-polyfill/01-ie8.js
+++ b/tgui/packages/tgui-polyfill/01-ie8.js
@@ -743,7 +743,7 @@
ontype = 'on' + type,
handlers = (window[ontype] || Object)[SECRET],
i = handlers ? find(handlers, handler) : -1
- ;
+ ;
if (-1 < i) handlers.splice(i, 1);
}),
pageXOffset: {get: getter('scrollLeft')},
diff --git a/tgui/packages/tgui/backend.ts b/tgui/packages/tgui/backend.ts
index 3e57bca72b..2b3499bfac 100644
--- a/tgui/packages/tgui/backend.ts
+++ b/tgui/packages/tgui/backend.ts
@@ -227,6 +227,7 @@ type BackendState = {
title: string,
status: number,
interface: string,
+ refreshing: boolean,
window: {
key: string,
size: [number, number],
diff --git a/tgui/packages/tgui/components/Autofocus.tsx b/tgui/packages/tgui/components/Autofocus.tsx
new file mode 100644
index 0000000000..f5ae00650a
--- /dev/null
+++ b/tgui/packages/tgui/components/Autofocus.tsx
@@ -0,0 +1,19 @@
+import { Component, createRef } from "inferno";
+
+export class Autofocus extends Component {
+ ref = createRef();
+
+ componentDidMount() {
+ setTimeout(() => {
+ this.ref.current?.focus();
+ }, 1);
+ }
+
+ render() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}
diff --git a/tgui/packages/tgui/components/Box.tsx b/tgui/packages/tgui/components/Box.tsx
index 015d07b23f..bcd8447c8b 100644
--- a/tgui/packages/tgui/components/Box.tsx
+++ b/tgui/packages/tgui/components/Box.tsx
@@ -5,7 +5,7 @@
*/
import { BooleanLike, classes, pureComponentHooks } from 'common/react';
-import { createVNode, InfernoNode } from 'inferno';
+import { createVNode, InfernoNode, SFC } from 'inferno';
import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags';
import { CSS_COLORS } from '../constants';
@@ -109,9 +109,7 @@ export const halfUnit = (value: unknown): string | undefined => {
const isColorCode = (str: unknown) => !isColorClass(str);
const isColorClass = (str: unknown): boolean => {
- if (typeof str === 'string') {
- return CSS_COLORS.includes(str);
- }
+ return typeof str === "string" && CSS_COLORS.includes(str);
};
const mapRawPropTo = attrName => (style, value) => {
@@ -290,7 +288,7 @@ export const computeBoxClassName = (props: BoxProps) => {
]);
};
-export const Box = (props: BoxProps) => {
+export const Box: SFC = (props: BoxProps) => {
const {
as = 'div',
className,
@@ -312,7 +310,9 @@ export const Box = (props: BoxProps) => {
computedClassName,
children,
ChildFlags.UnknownChildren,
- computedProps);
+ computedProps,
+ undefined,
+ );
};
Box.defaultHooks = pureComponentHooks;
diff --git a/tgui/packages/tgui/components/Button.js b/tgui/packages/tgui/components/Button.js
index 6c5d1736b3..58c3dc7697 100644
--- a/tgui/packages/tgui/components/Button.js
+++ b/tgui/packages/tgui/components/Button.js
@@ -8,7 +8,7 @@ import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from 'common/keycodes';
import { classes, pureComponentHooks } from 'common/react';
import { Component, createRef } from 'inferno';
import { createLogger } from '../logging';
-import { Box } from './Box';
+import { Box, computeBoxClassName, computeBoxProps } from './Box';
import { Icon } from './Icon';
import { Tooltip } from './Tooltip';
@@ -47,10 +47,17 @@ export const Button = props => {
+ `'onClick' instead and read: `
+ `https://infernojs.org/docs/guides/event-handling`);
}
- // IE8: Use a lowercase "onclick" because synthetic events are fucked.
- // IE8: Use an "unselectable" prop because "user-select" doesn't work.
+ rest.onClick = e => {
+ if (!disabled && onClick) {
+ onClick(e);
+ }
+ };
+ // IE8: Use "unselectable" because "user-select" doesn't work.
+ if (Byond.IS_LTE_IE8) {
+ rest.unselectable = true;
+ }
let buttonContent = (
- {
? 'Button--color--' + color
: 'Button--color--default',
className,
+ computeBoxClassName(rest),
])}
tabIndex={!disabled && '0'}
- unselectable={Byond.IS_LTE_IE8}
- onClick={e => {
- if (!disabled && onClick) {
- onClick(e);
- }
- }}
onKeyDown={e => {
+ if (props.captureKeys === false) {
+ return;
+ }
+
const keyCode = window.event ? e.which : e.keyCode;
// Simulate a click when pressing space or enter.
if (keyCode === KEY_SPACE || keyCode === KEY_ENTER) {
@@ -89,7 +95,7 @@ export const Button = props => {
return;
}
}}
- {...rest}>
+ {...computeBoxProps(rest)}>
{(icon && iconPosition !== 'right') && (
{
fontSize={iconSize} // VOREStation Addition
/>
)}
-
+
);
if (tooltip) {
diff --git a/tgui/packages/tgui/components/ByondUi.js b/tgui/packages/tgui/components/ByondUi.js
index 43d4039fcc..5b4f04dd0e 100644
--- a/tgui/packages/tgui/components/ByondUi.js
+++ b/tgui/packages/tgui/components/ByondUi.js
@@ -133,11 +133,10 @@ export class ByondUi extends Component {
render() {
const { params, ...rest } = this.props;
- const boxProps = computeBoxProps(rest);
return (
+ {...computeBoxProps(rest)}>
{/* Filler */}
diff --git a/tgui/packages/tgui/components/Flex.tsx b/tgui/packages/tgui/components/Flex.tsx
index 172fcad93d..95379ea587 100644
--- a/tgui/packages/tgui/components/Flex.tsx
+++ b/tgui/packages/tgui/components/Flex.tsx
@@ -5,16 +5,26 @@
*/
import { BooleanLike, classes, pureComponentHooks } from 'common/react';
-import { Box, BoxProps, unit } from './Box';
+import { BoxProps, computeBoxClassName, computeBoxProps, unit } from './Box';
-export interface FlexProps extends BoxProps {
+export type FlexProps = BoxProps & {
direction?: string | BooleanLike;
wrap?: string | BooleanLike;
align?: string | BooleanLike;
alignContent?: string | BooleanLike; // VOREStation Addition
justify?: string | BooleanLike;
inline?: BooleanLike;
-}
+};
+
+export const computeFlexClassName = (props: FlexProps) => {
+ return classes([
+ 'Flex',
+ props.inline && 'Flex--inline',
+ Byond.IS_LTE_IE10 && 'Flex--iefix',
+ Byond.IS_LTE_IE10 && props.direction === 'column' && 'Flex--iefix--column',
+ computeBoxClassName(props),
+ ]);
+};
export const computeFlexProps = (props: FlexProps) => {
const {
@@ -27,17 +37,7 @@ export const computeFlexProps = (props: FlexProps) => {
inline,
...rest
} = props;
- return {
- className: classes([
- 'Flex',
- Byond.IS_LTE_IE10 && (
- direction === 'column'
- ? 'Flex--iefix--column'
- : 'Flex--iefix'
- ),
- inline && 'Flex--inline',
- className,
- ]),
+ return computeBoxProps({
style: {
...rest.style,
'flex-direction': direction,
@@ -47,22 +47,39 @@ export const computeFlexProps = (props: FlexProps) => {
'justify-content': justify,
},
...rest,
- };
+ });
};
-export const Flex = props => (
-
-);
+export const Flex = props => {
+ const { className, ...rest } = props;
+ return (
+
+ );
+};
Flex.defaultHooks = pureComponentHooks;
-export interface FlexItemProps extends BoxProps {
+export type FlexItemProps = BoxProps & {
grow?: number;
order?: number;
shrink?: number;
basis?: string | BooleanLike;
align?: string | BooleanLike;
-}
+};
+
+export const computeFlexItemClassName = (props: FlexItemProps) => {
+ return classes([
+ 'Flex__item',
+ Byond.IS_LTE_IE10 && 'Flex__item--iefix',
+ computeBoxClassName(props),
+ ]);
+};
export const computeFlexItemProps = (props: FlexItemProps) => {
const {
@@ -77,13 +94,7 @@ export const computeFlexItemProps = (props: FlexItemProps) => {
align,
...rest
} = props;
- return {
- className: classes([
- 'Flex__item',
- Byond.IS_LTE_IE10 && 'Flex__item--iefix',
- Byond.IS_LTE_IE10 && grow > 0 && 'Flex__item--iefix--grow',
- className,
- ]),
+ return computeBoxProps({
style: {
...style,
'flex-grow': grow !== undefined && Number(grow),
@@ -93,12 +104,22 @@ export const computeFlexItemProps = (props: FlexItemProps) => {
'align-self': align,
},
...rest,
- };
+ });
};
-const FlexItem = props => (
-
-);
+const FlexItem = props => {
+ const { className, ...rest } = props;
+ return (
+
+ );
+};
FlexItem.defaultHooks = pureComponentHooks;
diff --git a/tgui/packages/tgui/components/Icon.js b/tgui/packages/tgui/components/Icon.js
index 13efaffca7..98a40bf5d9 100644
--- a/tgui/packages/tgui/components/Icon.js
+++ b/tgui/packages/tgui/components/Icon.js
@@ -7,7 +7,7 @@
*/
import { classes, pureComponentHooks } from 'common/react';
-import { Box } from './Box';
+import { computeBoxClassName, computeBoxProps } from './Box';
const FA_OUTLINE_REGEX = /-o$/;
@@ -17,17 +17,26 @@ export const Icon = props => {
size,
spin,
className,
- style = {},
rotation,
inverse,
...rest
} = props;
+
if (size) {
- style['font-size'] = (size * 100) + '%';
+ if (!rest.style) {
+ rest.style = {};
+ }
+ rest.style['font-size'] = (size * 100) + '%';
}
if (typeof rotation === 'number') {
- style['transform'] = `rotate(${rotation}deg)`;
+ if (!rest.style) {
+ rest.style = {};
+ }
+ rest.style['transform'] = `rotate(${rotation}deg)`;
}
+
+ const boxProps = computeBoxProps(rest);
+
let iconClass = "";
if (name.startsWith("tg-")) {
// tgfont icon
@@ -39,15 +48,14 @@ export const Icon = props => {
iconClass = (faRegular ? 'far ' : 'fas ') + 'fa-'+ faName + (spin ? " fa-spin" : "");
}
return (
-
+ {...boxProps} />
);
};
@@ -56,21 +64,19 @@ Icon.defaultHooks = pureComponentHooks;
export const IconStack = props => {
const {
className,
- style = {},
children,
...rest
} = props;
return (
-
+ {...computeBoxProps(rest)}>
{children}
-
+
);
};
diff --git a/tgui/packages/tgui/components/Input.js b/tgui/packages/tgui/components/Input.js
index e09c015d3e..a95dbab614 100644
--- a/tgui/packages/tgui/components/Input.js
+++ b/tgui/packages/tgui/components/Input.js
@@ -73,6 +73,11 @@ export class Input extends Component {
return;
}
if (e.keyCode === KEY_ESCAPE) {
+ if (this.props.onEscape) {
+ this.props.onEscape(e);
+ return;
+ }
+
this.setEditing(false);
e.target.value = toInputValue(this.props.value);
e.target.blur();
@@ -87,8 +92,15 @@ export class Input extends Component {
if (input) {
input.value = toInputValue(nextValue);
}
- if (this.props.autoFocus) {
- setTimeout(() => input.focus(), 1);
+
+ if (this.props.autoFocus || this.props.autoSelect) {
+ setTimeout(() => {
+ input.focus();
+
+ if (this.props.autoSelect) {
+ input.select();
+ }
+ }, 1);
}
}
diff --git a/tgui/packages/tgui/components/KeyListener.tsx b/tgui/packages/tgui/components/KeyListener.tsx
new file mode 100644
index 0000000000..46b5276536
--- /dev/null
+++ b/tgui/packages/tgui/components/KeyListener.tsx
@@ -0,0 +1,39 @@
+import { Component } from "inferno";
+import { KeyEvent } from "../events";
+import { listenForKeyEvents } from "../hotkeys";
+
+type KeyListenerProps = Partial<{
+ onKey: (key: KeyEvent) => void,
+ onKeyDown: (key: KeyEvent) => void,
+ onKeyUp: (key: KeyEvent) => void,
+}>;
+
+export class KeyListener extends Component {
+ dispose: () => void;
+
+ constructor() {
+ super();
+
+ this.dispose = listenForKeyEvents(key => {
+ if (this.props.onKey) {
+ this.props.onKey(key);
+ }
+
+ if (key.isDown() && this.props.onKeyDown) {
+ this.props.onKeyDown(key);
+ }
+
+ if (key.isUp() && this.props.onKeyUp) {
+ this.props.onKeyUp(key);
+ }
+ });
+ }
+
+ componentWillUnmount() {
+ this.dispose();
+ }
+
+ render() {
+ return null;
+ }
+}
diff --git a/tgui/packages/tgui/components/Popper.tsx b/tgui/packages/tgui/components/Popper.tsx
index f5a50f6433..f288dc67e7 100644
--- a/tgui/packages/tgui/components/Popper.tsx
+++ b/tgui/packages/tgui/components/Popper.tsx
@@ -1,10 +1,10 @@
-// import { createPopper, OptionsGeneric } from "@popperjs/core";
-import { createPopper, OptionsGeneric } from '@popperjs/core';
+import { createPopper } from '@popperjs/core';
+import { ArgumentsOf } from 'common/types';
import { Component, findDOMfromVNode, InfernoNode, render } from 'inferno';
type PopperProps = {
popperContent: InfernoNode;
- options?: Partial>;
+ options?: ArgumentsOf[2];
additionalStyles?: CSSProperties;
};
@@ -33,17 +33,22 @@ export class Popper extends Component {
this.renderPopperContent(() => {
document.body.appendChild(this.renderedContent);
+ // HACK: We don't want to create a wrapper, as it could break the layout
+ // of consumers, so we do the inferno equivalent of `findDOMNode(this)`.
+ // This is usually bad as refs are usually better, but refs did
+ // not work in this case, as they weren't propagating correctly.
+ // A previous attempt was made as a render prop that passed an ID,
+ // but this made consuming use too unwieldly.
+ // This code is copied from `findDOMNode` in inferno-extras.
+ // Because this component is written in TypeScript, we will know
+ // immediately if this internal variable is removed.
+ const domNode = findDOMfromVNode(this.$LI, true);
+ if (!domNode) {
+ return;
+ }
+
this.popperInstance = createPopper(
- // HACK: We don't want to create a wrapper, as it could break the layout
- // of consumers, so we do the inferno equivalent of `findDOMNode(this)`.
- // This is usually bad as refs are usually better, but refs did
- // not work in this case, as they weren't propagating correctly.
- // A previous attempt was made as a render prop that passed an ID,
- // but this made consuming use too unwieldly.
- // This code is copied from `findDOMNode` in inferno-extras.
- // Because this component is written in TypeScript, we will know
- // immediately if this internal variable is removed.
- findDOMfromVNode(this.$LI, true),
+ domNode,
this.renderedContent,
options
);
@@ -56,12 +61,20 @@ export class Popper extends Component {
componentWillUnmount() {
this.popperInstance?.destroy();
- this.renderedContent.remove();
- this.renderedContent = null;
+ render(null, this.renderedContent, () => {
+ this.renderedContent.remove();
+ });
}
renderPopperContent(callback: () => void) {
- render(this.props.popperContent, this.renderedContent, callback);
+ // `render` errors when given false, so we convert it to `null`,
+ // which is supported.
+ render(
+ this.props.popperContent || null,
+ this.renderedContent,
+ callback,
+ this.context,
+ );
}
render() {
diff --git a/tgui/packages/tgui/components/RestrictedInput.js b/tgui/packages/tgui/components/RestrictedInput.js
new file mode 100644
index 0000000000..0a6e2cb440
--- /dev/null
+++ b/tgui/packages/tgui/components/RestrictedInput.js
@@ -0,0 +1,154 @@
+import { classes } from 'common/react';
+import { clamp } from 'common/math';
+import { Component, createRef } from 'inferno';
+import { Box } from './Box';
+import { KEY_ESCAPE, KEY_ENTER } from 'common/keycodes';
+
+const DEFAULT_MIN = 0;
+const DEFAULT_MAX = 10000;
+
+/**
+ * Takes a string input and parses integers from it.
+ * If none: Minimum is set.
+ * Else: Clamps it to the given range.
+ */
+const getClampedNumber = (value, minValue, maxValue) => {
+ const minimum = minValue || DEFAULT_MIN;
+ const maximum = maxValue || maxValue === 0 ? maxValue : DEFAULT_MAX;
+ if (!value || !value.length) {
+ return String(minimum);
+ }
+ let parsedValue = parseInt(value.replace(/\D/g, ''), 10);
+ if (isNaN(parsedValue)) {
+ return String(minimum);
+ } else {
+ return String(clamp(parsedValue, minimum, maximum));
+ }
+};
+
+export class RestrictedInput extends Component {
+ constructor() {
+ super();
+ this.inputRef = createRef();
+ this.state = {
+ editing: false,
+ };
+ this.handleBlur = (e) => {
+ const { editing } = this.state;
+ if (editing) {
+ this.setEditing(false);
+ }
+ };
+ this.handleChange = (e) => {
+ const { maxValue, minValue, onChange } = this.props;
+ e.target.value = getClampedNumber(e.target.value, minValue, maxValue);
+ if (onChange) {
+ onChange(e, +e.target.value);
+ }
+ };
+ this.handleFocus = (e) => {
+ const { editing } = this.state;
+ if (!editing) {
+ this.setEditing(true);
+ }
+ };
+ this.handleInput = (e) => {
+ const { editing } = this.state;
+ const { onInput } = this.props;
+ if (!editing) {
+ this.setEditing(true);
+ }
+ if (onInput) {
+ onInput(e, +e.target.value);
+ }
+ };
+ this.handleKeyDown = (e) => {
+ const { maxValue, minValue, onChange, onEnter } = this.props;
+ if (e.keyCode === KEY_ENTER) {
+ const safeNum = getClampedNumber(e.target.value, minValue, maxValue);
+ this.setEditing(false);
+ if (onChange) {
+ onChange(e, +safeNum);
+ }
+ if (onEnter) {
+ onEnter(e, +safeNum);
+ }
+ e.target.blur();
+ return;
+ }
+ if (e.keyCode === KEY_ESCAPE) {
+ if (this.props.onEscape) {
+ this.props.onEscape(e);
+ return;
+ }
+ this.setEditing(false);
+ e.target.value = this.props.value;
+ e.target.blur();
+ return;
+ }
+ };
+ }
+
+ componentDidMount() {
+ const { maxValue, minValue } = this.props;
+ const nextValue = this.props.value?.toString();
+ const input = this.inputRef.current;
+ if (input) {
+ input.value = getClampedNumber(nextValue, minValue, maxValue);
+ }
+ if (this.props.autoFocus || this.props.autoSelect) {
+ setTimeout(() => {
+ input.focus();
+
+ if (this.props.autoSelect) {
+ input.select();
+ }
+ }, 1);
+ }
+ }
+
+ componentDidUpdate(prevProps, _) {
+ const { maxValue, minValue } = this.props;
+ const { editing } = this.state;
+ const prevValue = prevProps.value?.toString();
+ const nextValue = this.props.value?.toString();
+ const input = this.inputRef.current;
+ if (input && !editing) {
+ if (nextValue !== prevValue && nextValue !== input.value) {
+ input.value = getClampedNumber(nextValue, minValue, maxValue);
+ }
+ }
+ }
+
+ setEditing(editing) {
+ this.setState({ editing });
+ }
+
+ render() {
+ const { props } = this;
+ const { onChange, onEnter, onInput, value, ...boxProps } = props;
+ const { className, fluid, monospace, ...rest } = boxProps;
+ return (
+
+ .
+
+
+ );
+ }
+}
diff --git a/tgui/packages/tgui/components/Section.tsx b/tgui/packages/tgui/components/Section.tsx
index 43317fd43b..d49043ee8a 100644
--- a/tgui/packages/tgui/components/Section.tsx
+++ b/tgui/packages/tgui/components/Section.tsx
@@ -40,7 +40,11 @@ export class Section extends Component {
addScrollableNode(this.scrollableRef.current);
}
if (this.props.autoFocus) {
- setTimeout(() => this.scrollableRef.current.focus(), 1);
+ setTimeout(() => {
+ if (this.scrollableRef.current) {
+ return this.scrollableRef.current.focus();
+ }
+ }, 1);
}
}
diff --git a/tgui/packages/tgui/components/Stack.tsx b/tgui/packages/tgui/components/Stack.tsx
index 1801c3a145..66de1b2557 100644
--- a/tgui/packages/tgui/components/Stack.tsx
+++ b/tgui/packages/tgui/components/Stack.tsx
@@ -5,6 +5,7 @@
*/
import { classes } from 'common/react';
+import { RefObject } from 'inferno';
import { Flex, FlexItemProps, FlexProps } from './Flex';
interface StackProps extends FlexProps {
@@ -29,14 +30,19 @@ export const Stack = (props: StackProps) => {
);
};
-const StackItem = (props: FlexProps) => {
- const { className, ...rest } = props;
+type StackItemProps = FlexProps & {
+ innerRef?: RefObject,
+};
+
+const StackItem = (props: StackItemProps) => {
+ const { className, innerRef, ...rest } = props;
return (
);
};
diff --git a/tgui/packages/tgui/components/Tooltip.tsx b/tgui/packages/tgui/components/Tooltip.tsx
index 7b699d715a..0cf61ed04f 100644
--- a/tgui/packages/tgui/components/Tooltip.tsx
+++ b/tgui/packages/tgui/components/Tooltip.tsx
@@ -7,7 +7,7 @@ const DEFAULT_PLACEMENT = "top";
type TooltipProps = {
children?: InfernoNode;
- content: string;
+ content: InfernoNode;
position?: Placement,
};
@@ -15,14 +15,15 @@ type TooltipState = {
hovered: boolean;
};
-export class Tooltip extends Component {
- constructor() {
- super();
+const DISABLE_EVENT_LISTENERS = [{
+ name: "eventListeners",
+ enabled: false,
+}];
- this.state = {
- hovered: false,
- };
- }
+export class Tooltip extends Component {
+ state = {
+ hovered: false,
+ };
componentDidMount() {
// HACK: We don't want to create a wrapper, as it could break the layout
@@ -35,6 +36,10 @@ export class Tooltip extends Component {
// immediately if this internal variable is removed.
const domNode = findDOMfromVNode(this.$LI, true);
+ if (!domNode) {
+ return;
+ }
+
domNode.addEventListener("mouseenter", () => {
this.setState({
hovered: true,
@@ -53,10 +58,7 @@ export class Tooltip extends Component {
{
}
additionalStyles={{
"pointer-events": "none",
+ "z-index": 2,
}}>
{this.props.children}
diff --git a/tgui/packages/tgui/components/TrackOutsideClicks.tsx b/tgui/packages/tgui/components/TrackOutsideClicks.tsx
new file mode 100644
index 0000000000..abf8286c7f
--- /dev/null
+++ b/tgui/packages/tgui/components/TrackOutsideClicks.tsx
@@ -0,0 +1,37 @@
+import { Component, createRef } from "inferno";
+
+export class TrackOutsideClicks extends Component<{
+ onOutsideClick: () => void,
+}> {
+ ref = createRef();
+
+ constructor() {
+ super();
+
+ this.handleOutsideClick = this.handleOutsideClick.bind(this);
+
+ document.addEventListener("click", this.handleOutsideClick);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener("click", this.handleOutsideClick);
+ }
+
+ handleOutsideClick(event: MouseEvent) {
+ if (!(event.target instanceof Node)) {
+ return;
+ }
+
+ if (this.ref.current && !this.ref.current.contains(event.target)) {
+ this.props.onOutsideClick();
+ }
+ }
+
+ render() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}
diff --git a/tgui/packages/tgui/components/index.js b/tgui/packages/tgui/components/index.js
index 93457d1234..139305c3ca 100644
--- a/tgui/packages/tgui/components/index.js
+++ b/tgui/packages/tgui/components/index.js
@@ -5,6 +5,7 @@
*/
export { AnimatedNumber } from './AnimatedNumber';
+export { Autofocus } from './Autofocus';
export { Blink } from './Blink';
export { BlockQuote } from './BlockQuote';
export { Box } from './Box';
@@ -22,6 +23,7 @@ export { Grid } from './Grid';
export { Icon } from './Icon';
export { InfinitePlane } from './InfinitePlane';
export { Input } from './Input';
+export { KeyListener } from './KeyListener';
export { Knob } from './Knob';
export { LabeledControls } from './LabeledControls';
export { LabeledList } from './LabeledList';
@@ -31,6 +33,7 @@ export { NoticeBox } from './NoticeBox';
export { NumberInput } from './NumberInput';
export { ProgressBar } from './ProgressBar';
export { Popper } from './Popper';
+export { RestrictedInput } from './RestrictedInput';
export { RoundGauge } from './RoundGauge';
export { Section } from './Section';
export { Slider } from './Slider';
@@ -39,4 +42,5 @@ export { Table } from './Table';
export { Tabs } from './Tabs';
export { TextArea } from './TextArea';
export { TimeDisplay } from './TimeDisplay';
+export { TrackOutsideClicks } from './TrackOutsideClicks';
export { Tooltip } from './Tooltip';
diff --git a/tgui/packages/tgui/hotkeys.ts b/tgui/packages/tgui/hotkeys.ts
index 3334c892cb..b11c8cfcee 100644
--- a/tgui/packages/tgui/hotkeys.ts
+++ b/tgui/packages/tgui/hotkeys.ts
@@ -31,6 +31,9 @@ const hotKeysAcquired = [
// State of passed-through keys.
const keyState: Record = {};
+// Custom listeners for key events
+const keyListeners: ((key: KeyEvent) => void)[] = [];
+
/**
* Converts a browser keycode to BYOND keycode.
*/
@@ -178,6 +181,38 @@ export const setupHotKeys = () => {
releaseHeldKeys();
});
globalEvents.on('key', (key: KeyEvent) => {
+ for (const keyListener of keyListeners) {
+ keyListener(key);
+ }
+
handlePassthrough(key);
});
};
+
+/**
+ * Registers for any key events, such as key down or key up.
+ * This should be preferred over directly connecting to keydown/keyup
+ * as it lets tgui prevent the key from reaching BYOND.
+ *
+ * If using in a component, prefer KeyListener, which automatically handles
+ * stopping listening when unmounting.
+ *
+ * @param callback The function to call whenever a key event occurs
+ * @returns A callback to stop listening
+ */
+export const listenForKeyEvents = (
+ callback: (key: KeyEvent) => void,
+): () => void => {
+ keyListeners.push(callback);
+
+ let removed = false;
+
+ return () => {
+ if (removed) {
+ return;
+ }
+
+ removed = true;
+ keyListeners.splice(keyListeners.indexOf(callback), 1);
+ };
+};
diff --git a/tgui/packages/tgui/http.ts b/tgui/packages/tgui/http.ts
new file mode 100644
index 0000000000..96a2d9b56d
--- /dev/null
+++ b/tgui/packages/tgui/http.ts
@@ -0,0 +1,17 @@
+/**
+ * An equivalent to `fetch`, except will automatically retry.
+ */
+export const fetchRetry
+ = (
+ url: string,
+ options?: RequestInit,
+ retryTimer: number = 1000,
+ ): Promise => {
+ return fetch(url, options).catch(() => {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ fetchRetry(url, options, retryTimer).then(resolve);
+ }, retryTimer);
+ });
+ });
+ };
diff --git a/tgui/packages/tgui/interfaces/AlertModal.js b/tgui/packages/tgui/interfaces/AlertModal.js
deleted file mode 100644
index 2815bd93d5..0000000000
--- a/tgui/packages/tgui/interfaces/AlertModal.js
+++ /dev/null
@@ -1,137 +0,0 @@
-/**
- * @file
- * @copyright 2020 bobbahbrown (https://github.com/bobbahbrown)
- * @license MIT
- */
-
-import { Loader } from "./common/Loader";
-import { useBackend } from '../backend';
-import { Component, createRef } from 'inferno';
-import { Box, Flex, Section } from '../components';
-import { Window } from '../layouts';
-import {
- KEY_ENTER,
- KEY_LEFT,
- KEY_RIGHT,
- KEY_SPACE,
- KEY_TAB,
-} from 'common/keycodes';
-
-export class AlertModal extends Component {
- constructor() {
- super();
-
- this.buttonRefs = [createRef()];
- this.state = { current: 0 };
- }
-
- componentDidMount() {
- const { data } = useBackend(this.context);
- const { buttons } = data;
- const { current } = this.state;
- const button = this.buttonRefs[current].current;
-
- // Fill ref array with refs for other buttons
- for (let i = 1; i < buttons.length; i++) {
- this.buttonRefs.push(createRef());
- }
-
- setTimeout(() => button.focus(), 1);
- }
-
- setCurrent(current, isArrowKey) {
- const { data } = useBackend(this.context);
- const { buttons } = data;
-
- // Mimic alert() behavior for tabs and arrow keys
- if (current >= buttons.length) {
- current = isArrowKey ? current - 1 : 0;
- } else if (current < 0) {
- current = isArrowKey ? 0 : buttons.length - 1;
- }
-
- const button = this.buttonRefs[current].current;
-
- // Prevents an error from occurring on close
- if (button) {
- setTimeout(() => button.focus(), 1);
- }
- this.setState({ current });
- }
-
- render() {
- const { act, data } = useBackend(this.context);
- const { title, message, buttons, timeout } = data;
- const { current } = this.state;
- const focusCurrentButton = () => this.setCurrent(current, false);
-
- const windowHeight = Math.max(150, message.length);
-
- return (
- 0}>
- {timeout && }
-
-
-
-
-
-
-
- {message}
-
-
-
-
-
-
- {buttons.map((button, buttonIndex) => (
-
- act("choose", { choice: button })}
- onKeyDown={e => {
- const keyCode = window.event ? e.which : e.keyCode;
-
- /**
- * Simulate a click when pressing space or enter,
- * allow keyboard navigation, override tab behavior
- */
- if (keyCode === KEY_SPACE || keyCode === KEY_ENTER) {
- act("choose", { choice: button });
- } else if (
- keyCode === KEY_LEFT
- || (e.shiftKey && keyCode === KEY_TAB)
- ) {
- this.setCurrent(current - 1, keyCode === KEY_LEFT);
- } else if (
- keyCode === KEY_RIGHT || keyCode === KEY_TAB
- ) {
- this.setCurrent(current + 1, keyCode === KEY_RIGHT);
- }
- }}>
- {button}
-
-
- ))}
-
-
-
-
-
-
- );
- }
-
-}
diff --git a/tgui/packages/tgui/interfaces/AlertModal.tsx b/tgui/packages/tgui/interfaces/AlertModal.tsx
new file mode 100644
index 0000000000..965faf6838
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AlertModal.tsx
@@ -0,0 +1,151 @@
+import { Loader } from './common/Loader';
+import { useBackend, useLocalState } from '../backend';
+import { KEY_ENTER, KEY_ESCAPE, KEY_LEFT, KEY_RIGHT, KEY_SPACE, KEY_TAB } from '../../common/keycodes';
+import { Autofocus, Box, Button, Flex, Section, Stack } from '../components';
+import { Window } from '../layouts';
+
+type AlertModalData = {
+ autofocus: boolean;
+ buttons: string[];
+ large_buttons: boolean;
+ message: string;
+ swapped_buttons: boolean;
+ timeout: number;
+ title: string;
+};
+
+const KEY_DECREMENT = -1;
+const KEY_INCREMENT = 1;
+
+export const AlertModal = (_, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ autofocus,
+ buttons = [],
+ large_buttons,
+ message = '',
+ timeout,
+ title,
+ } = data;
+ const [selected, setSelected] = useLocalState(context, 'selected', 0);
+ // Dynamically sets window dimensions
+ const windowHeight
+ = 115
+ + (message.length > 30 ? Math.ceil(message.length / 4) : 0)
+ + (message.length && large_buttons ? 5 : 0);
+ const windowWidth = 325 + (buttons.length > 2 ? 55 : 0);
+ const onKey = (direction: number) => {
+ if (selected === 0 && direction === KEY_DECREMENT) {
+ setSelected(buttons.length - 1);
+ } else if (selected === buttons.length - 1 && direction === KEY_INCREMENT) {
+ setSelected(0);
+ } else {
+ setSelected(selected + direction);
+ }
+ };
+
+ return (
+
+ {!!timeout && }
+ {
+ const keyCode = window.event ? e.which : e.keyCode;
+ /**
+ * Simulate a click when pressing space or enter,
+ * allow keyboard navigation, override tab behavior
+ */
+ if (keyCode === KEY_SPACE || keyCode === KEY_ENTER) {
+ act('choose', { choice: buttons[selected] });
+ } else if (keyCode === KEY_ESCAPE) {
+ act('cancel');
+ } else if (keyCode === KEY_LEFT) {
+ e.preventDefault();
+ onKey(KEY_DECREMENT);
+ } else if (keyCode === KEY_TAB || keyCode === KEY_RIGHT) {
+ e.preventDefault();
+ onKey(KEY_INCREMENT);
+ }
+ }}>
+
+
+
+
+ {message}
+
+
+
+ {!!autofocus && }
+
+
+
+
+
+
+ );
+};
+
+/**
+ * Displays a list of buttons ordered by user prefs.
+ * Technically this handles more than 2 buttons, but you
+ * should just be using a list input in that case.
+ */
+const ButtonDisplay = (props, context) => {
+ const { data } = useBackend(context);
+ const { buttons = [], large_buttons, swapped_buttons } = data;
+ const { selected } = props;
+
+ return (
+
+ {buttons?.map((button, index) =>
+ !!large_buttons && buttons.length < 3 ? (
+
+
+
+ ) : (
+
+
+
+ )
+ )}
+
+ );
+};
+
+/**
+ * Displays a button with variable sizing.
+ */
+const AlertButton = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { large_buttons } = data;
+ const { button, selected } = props;
+ const buttonWidth = button.length > 7 ? button.length : 7;
+
+ return (
+ act('choose', { choice: button })}
+ m={0.5}
+ pl={2}
+ pr={2}
+ pt={large_buttons ? 0.33 : 0}
+ selected={selected}
+ textAlign="center"
+ width={!large_buttons && buttonWidth}>
+ {!large_buttons ? button : button.toUpperCase()}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/InputModal.js b/tgui/packages/tgui/interfaces/InputModal.js
deleted file mode 100644
index 6366e775a0..0000000000
--- a/tgui/packages/tgui/interfaces/InputModal.js
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * @file
- * @copyright 2021 Leshana
- * @license MIT
- */
-
-import { clamp01 } from 'common/math';
-import { useBackend, useLocalState } from '../backend';
-import { Box, Button, Section, Input, Stack, TextArea } from '../components';
-import { KEY_ESCAPE } from 'common/keycodes';
-import { Window } from '../layouts';
-import { createLogger } from '../logging';
-
-const logger = createLogger('inputmodal');
-
-export const InputModal = (props, context) => {
- const { act, data } = useBackend(context);
- const { title, message, initial, input_type, timeout } = data;
-
- // Current Input Value
- const [curValue, setCurValue] = useLocalState(context, 'curValue', initial);
-
- const handleKeyDown = e => {
- if (e.keyCode === KEY_ESCAPE) {
- e.preventDefault();
- act("cancel");
- return;
- }
- };
-
- let initialHeight, initialWidth;
- let modalBody;
- switch (input_type) {
- case 'text':
- case 'num':
- initialWidth = 325;
- initialHeight = Math.max(150, message.length);
- modalBody = (
- {
- setCurValue(val);
- }}
- onEnter={(_e, val) => {
- act('choose', { choice: val });
- }}
- />
- );
- break;
- case 'message':
- initialWidth = 450;
- initialHeight = 350;
- modalBody = (
-