diff --git a/code/__DEFINES/atmospherics/atmos_machinery.dm b/code/__DEFINES/atmospherics/atmos_machinery.dm
index 25a34707643c..0fe49df30b02 100644
--- a/code/__DEFINES/atmospherics/atmos_machinery.dm
+++ b/code/__DEFINES/atmospherics/atmos_machinery.dm
@@ -41,6 +41,22 @@
/// Air alarm has all components but isn't completed
#define AIR_ALARM_BUILD_COMPLETE 2
+// Fire alarm buildstage [/obj/machinery/firealarm/buildstage]
+/// Fire alarm missing circuit
+#define FIRE_ALARM_BUILD_NO_CIRCUIT 0
+/// Fire alarm has circuit but is missing wires
+#define FIRE_ALARM_BUILD_NO_WIRES 1
+/// Fire alarm has all components but isn't completed
+#define FIRE_ALARM_BUILD_SECURED 2
+
+// Fault levels for air alarm display
+/// Area faults clear
+#define AREA_FAULT_NONE 0
+/// Fault triggered by manual intervention (ie: fire alarm pull)
+#define AREA_FAULT_MANUAL 1
+/// Fault triggered automatically (ie: firedoor detection)
+#define AREA_FAULT_AUTOMATIC 2
+
// threshold_type values for [/datum/tlv/proc/set_value] and [/datum/tlv/proc/reset_value]
/// [/datum/tlv/var/warning_min]
#define TLV_VAR_WARNING_MIN (1 << 0)
diff --git a/code/__DEFINES/cleaning.dm b/code/__DEFINES/cleaning.dm
index 7c954602215e..79708cd152b6 100644
--- a/code/__DEFINES/cleaning.dm
+++ b/code/__DEFINES/cleaning.dm
@@ -29,3 +29,8 @@
#define CLEAN_SCRUB (CLEAN_WASH | CLEAN_TYPE_FINGERPRINTS | CLEAN_TYPE_FIBERS | CLEAN_TYPE_HARD_DECAL)
#define CLEAN_RAD CLEAN_TYPE_RADIATION
#define CLEAN_ALL ALL
+
+// Footprint sprites to use when making footprints in blood, oil, etc.
+#define FOOTPRINT_SPRITE_SHOES "shoes"
+#define FOOTPRINT_SPRITE_PAWS "paws"
+#define FOOTPRINT_SPRITE_CLAWS "claws"
diff --git a/code/__DEFINES/fonts.dm b/code/__DEFINES/fonts.dm
index 6b00195f59b0..e3e2e7ab7f69 100644
--- a/code/__DEFINES/fonts.dm
+++ b/code/__DEFINES/fonts.dm
@@ -14,3 +14,11 @@
/// Emoji icon set
#define EMOJI_SET 'icons/ui_icons/emoji/emoji.dmi'
+
+// Font metrics bitfield
+/// Include leading A width and trailing C width in GetWidth() or in DrawText()
+#define INCLUDE_AC (1<<0)
+
+DEFINE_BITFIELD(font_flags, list(
+ "INCLUDE_AC" = INCLUDE_AC,
+))
diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm
index 3cffec0521f7..8a7731618bbf 100644
--- a/code/__DEFINES/text.dm
+++ b/code/__DEFINES/text.dm
@@ -1,10 +1,44 @@
/// Does 4 spaces. Used as a makeshift tabulator.
#define FOURSPACES " "
+/// Standard maptext
/// Prepares a text to be used for maptext. Use this so it doesn't look hideous.
#define MAPTEXT(text) {"[##text] "}
-/// Prepares a text to be used for maptext, using a font that can handle larger text better.
+/**
+ * Pixel-perfect scaled fonts for use in the MAP element as defined in skin.dmf
+ *
+ * Four sizes to choose from, use the sizes as mentioned below.
+ * Between the variations and a step there should be an option that fits your use case.
+ * BYOND uses pt sizing, different than px used in TGUI. Using px will make it look blurry due to poor antialiasing.
+ *
+ * Default sizes are prefilled in the macro for ease of use and a consistent visual look.
+ * To use a step other than the default in the macro, specify it in a span style.
+ * For example: MAPTEXT_PIXELLARI("Some large maptext here ")
+ */
+/// Large size (ie: context tooltips) - Size options: 12pt 24pt.
+#define MAPTEXT_PIXELLARI(text) {"[##text] "}
+
+/// Standard size (ie: normal runechat) - Size options: 6pt 12pt 18pt.
+#define MAPTEXT_GRAND9K(text) {"[##text] "}
+
+/// Small size. (ie: context subtooltips, spell delays) - Size options: 12pt 24pt.
+#define MAPTEXT_TINY_UNICODE(text) {"[##text] "}
+
+/// Smallest size. (ie: whisper runechat) - Size options: 6pt 12pt 18pt.
+#define MAPTEXT_SPESSFONT(text) {"[##text] "}
+
+/**
+ * Prepares a text to be used for maptext, using a variable size font.
+ *
+ * More flexible but doesn't scale pixel perfect to BYOND icon resolutions.
+ * (May be blurry.) Can use any size in pt or px.
+ *
+ * You MUST Specify the size when using the macro
+ * For example: MAPTEXT_VCR_OSD_MONO("Some large maptext here ")
+ */
+/// Prepares a text to be used for maptext, using a variable size font.
+/// Variable size font. More flexible but doesn't scale pixel perfect to BYOND icon resolutions. (May be blurry.) Can use any size in pt or px.
#define MAPTEXT_VCR_OSD_MONO(text) {"[##text] "}
/// Macro from Lummox used to get height from a MeasureText proc.
diff --git a/code/__HELPERS/colors.dm b/code/__HELPERS/colors.dm
index d44801af5d7a..1c4cbbd9704c 100644
--- a/code/__HELPERS/colors.dm
+++ b/code/__HELPERS/colors.dm
@@ -42,24 +42,24 @@
var/textb = copytext(HTMLstring, 6, 8)
return rgb(255 - hex2num(textr), 255 - hex2num(textg), 255 - hex2num(textb))
-///Flash a color on the client
+///Flash a color on the passed mob
/proc/flash_color(mob_or_client, flash_color="#960000", flash_time=20)
- var/client/flashed_client
+ var/mob/flashed_mob
if(ismob(mob_or_client))
- var/mob/client_mob = mob_or_client
- if(client_mob.client)
- flashed_client = client_mob.client
- else
- return
+ flashed_mob = mob_or_client
else if(istype(mob_or_client, /client))
- flashed_client = mob_or_client
+ var/client/flashed_client = mob_or_client
+ flashed_mob = flashed_client.mob
- if(!istype(flashed_client))
+ if(!istype(flashed_mob))
return
- var/animate_color = flashed_client.color
- flashed_client.color = flash_color
- animate(flashed_client, color = animate_color, time = flash_time)
+ var/datum/client_colour/temp/temp_color = new(flashed_mob)
+ temp_color.colour = flash_color
+ temp_color.fade_in = flash_time * 0.25
+ temp_color.fade_out = flash_time * 0.25
+ QDEL_IN(temp_color, (flash_time * 0.5) + 1)
+ flashed_mob.add_client_colour(temp_color)
/// Blends together two colors (passed as 3 or 4 length lists) using the screen blend mode
/// Much like multiply, screen effects the brightness of the resulting color
@@ -94,4 +94,3 @@
#define RANDOM_COLOUR (rgb(rand(0,255),rand(0,255),rand(0,255)))
-
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index adabbeee3ce4..6b197dbc5e43 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -992,7 +992,7 @@ world
letter = lowertext(letter)
var/image/text_image = new(loc = A)
- text_image.maptext = MAPTEXT("[letter] ")
+ text_image.maptext = MAPTEXT("[letter] ")
text_image.pixel_x = 7
text_image.pixel_y = 5
qdel(atom_icon)
diff --git a/code/__HELPERS/priority_announce.dm b/code/__HELPERS/priority_announce.dm
index bb121caa1d8b..68fd2f797e0e 100644
--- a/code/__HELPERS/priority_announce.dm
+++ b/code/__HELPERS/priority_announce.dm
@@ -37,7 +37,7 @@
* * encode_title - if TRUE, the title will be HTML encoded
* * encode_text - if TRUE, the text will be HTML encoded
*/
-/proc/priority_announce(text, title = "", sound, type, sender_override, has_important_message = FALSE, list/mob/players, encode_title = TRUE, encode_text = TRUE, color_override)
+/proc/priority_announce(text, title = "", sound, type, sender_override, has_important_message = FALSE, list/mob/players = GLOB.player_list, encode_title = TRUE, encode_text = TRUE, color_override)
if(!text)
return
@@ -92,7 +92,7 @@
dispatch_announcement_to_players(finalized_announcement, players, sound)
- if(isnull(sender_override))
+ if(isnull(sender_override) && players == GLOB.player_list)
if(length(title) > 0)
GLOB.news_network.submit_article(title + " " + text, "[command_name()]", "Station Announcements", null)
else
@@ -192,10 +192,7 @@
return jointext(returnable_strings, "")
/// Proc that just dispatches the announcement to our applicable audience. Only the announcement is a mandatory arg.
-/proc/dispatch_announcement_to_players(announcement, list/players, sound_override = null, should_play_sound = TRUE)
- if(!players)
- players = GLOB.player_list
-
+/proc/dispatch_announcement_to_players(announcement, list/players = GLOB.player_list, sound_override = null, should_play_sound = TRUE)
var/sound_to_play = !isnull(sound_override) ? sound_override : 'sound/misc/notice2.ogg'
for(var/mob/target in players)
diff --git a/code/_onclick/hud/credits.dm b/code/_onclick/hud/credits.dm
index 6de4b735bd19..e39e1ef36d07 100644
--- a/code/_onclick/hud/credits.dm
+++ b/code/_onclick/hud/credits.dm
@@ -44,7 +44,7 @@
icon = I
parent = P
icon_state = credited
- maptext = MAPTEXT(credited)
+ maptext = MAPTEXT_PIXELLARI(credited)
maptext_x = world.icon_size + 8
maptext_y = (world.icon_size / 2) - 4
maptext_width = world.icon_size * 3
diff --git a/code/datums/actions/cooldown_action.dm b/code/datums/actions/cooldown_action.dm
index d4d02901dda2..ed4309c36e1c 100644
--- a/code/datums/actions/cooldown_action.dm
+++ b/code/datums/actions/cooldown_action.dm
@@ -66,10 +66,10 @@
/datum/action/cooldown/create_button()
var/atom/movable/screen/movable/action_button/button = ..()
button.maptext = ""
- button.maptext_x = 6
+ button.maptext_x = 4
button.maptext_y = 2
- button.maptext_width = 24
- button.maptext_height = 12
+ button.maptext_width = 32
+ button.maptext_height = 16
return button
/datum/action/cooldown/update_button_status(atom/movable/screen/movable/action_button/button, force = FALSE)
@@ -79,9 +79,9 @@
button.maptext = ""
else
if (cooldown_rounding > 0)
- button.maptext = MAPTEXT("[round(time_left/10, cooldown_rounding)] ")
+ button.maptext = MAPTEXT_TINY_UNICODE("[round(time_left/10, cooldown_rounding)]")
else
- button.maptext = MAPTEXT("[round(time_left/10)] ")
+ button.maptext = MAPTEXT_TINY_UNICODE("[round(time_left/10)]")
if(!IsAvailable() || !is_action_active(button))
return
diff --git a/code/datums/browser.dm b/code/datums/browser.dm
index 962bfb7c887b..0a378cd83dda 100644
--- a/code/datums/browser.dm
+++ b/code/datums/browser.dm
@@ -397,7 +397,7 @@
if ("number")
settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]") as num
if ("color")
- settings["mainsettings"][setting]["value"] = input(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]", settings["mainsettings"][setting]["value"]) as color
+ settings["mainsettings"][setting]["value"] = tgui_color_picker(user, "Enter new value for [settings["mainsettings"][setting]["desc"]]", "Enter new value for [settings["mainsettings"][setting]["desc"]]", settings["mainsettings"][setting]["value"])
if ("boolean")
settings["mainsettings"][setting]["value"] = (settings["mainsettings"][setting]["value"] == "Yes") ? "No" : "Yes"
if ("ckey")
diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
index 361915e5941d..7c5c8a6df5b1 100644
--- a/code/datums/chatmessage.dm
+++ b/code/datums/chatmessage.dm
@@ -13,7 +13,7 @@
/// Approximate height in pixels of an 'average' line, used for height decay
#define CHAT_MESSAGE_APPROX_LHEIGHT 11
/// Max width of chat message in pixels
-#define CHAT_MESSAGE_WIDTH 96
+#define CHAT_MESSAGE_WIDTH 112
/// The dimensions of the chat message icons
#define CHAT_MESSAGE_ICON_SIZE 9
@@ -145,6 +145,10 @@
if (!ismob(target))
extra_classes |= "small"
+ // Why are you yelling?
+ if(copytext_char(text, -2) == "!!")
+ extra_classes |= SPAN_YELL
+
var/list/prefixes
// Append radio icon if from a virtual speaker
@@ -171,7 +175,7 @@
var/tgt_color = extra_classes.Find("italics") ? target.chat_color_darkened : target.chat_color
// Approximate text height
- var/complete_text = ""
+ var/complete_text = " "
var/mheight
WXH_TO_HEIGHT(owned_by.MeasureText(complete_text, null, CHAT_MESSAGE_WIDTH), mheight)
@@ -235,7 +239,7 @@
message.pixel_y = target.maptext_height
message.pixel_x = -target.base_pixel_x
message.maptext_width = CHAT_MESSAGE_WIDTH
- message.maptext_height = mheight
+ message.maptext_height = mheight * 1.25 // We add extra because some characters are superscript, like actions
message.maptext_x = (CHAT_MESSAGE_WIDTH - owner.bound_width) * -0.5
message.maptext = MAPTEXT(complete_text)
diff --git a/code/datums/components/admin_popup.dm b/code/datums/components/admin_popup.dm
index 6edd839942d5..1c821808a40d 100644
--- a/code/datums/components/admin_popup.dm
+++ b/code/datums/components/admin_popup.dm
@@ -104,9 +104,9 @@
last_color_index = (last_color_index % colors.len) + 1
- var/message = ""
- message += "HEY! An admin is trying to talk to you! Check your chat window, and click their name to respond!"
- message += " "
+ var/message = ""
+ message += "HEY! An admin is trying to talk to you! Check your chat window, and click their name to respond!"
+ message += " "
maptext = MAPTEXT(message)
last_update_time = world.time
diff --git a/code/datums/components/bloodysoles.dm b/code/datums/components/bloodysoles.dm
index e2f8a7d064fe..9fab9bf8d4b5 100644
--- a/code/datums/components/bloodysoles.dm
+++ b/code/datums/components/bloodysoles.dm
@@ -21,6 +21,8 @@
/// The world.time when we last picked up blood
var/last_pickup
+ var/footprint_sprite = FOOTPRINT_SPRITE_SHOES
+
/datum/component/bloodysoles/Initialize()
if(!isclothing(parent))
return COMPONENT_INCOMPATIBLE
@@ -100,9 +102,9 @@
/**
* Find a blood decal on a turf that matches our last_blood_state
*/
-/datum/component/bloodysoles/proc/find_pool_by_blood_state(turf/turfLoc, typeFilter = null)
+/datum/component/bloodysoles/proc/find_pool_by_blood_state(turf/turfLoc, typeFilter = null, footprint_sprite)
for(var/obj/effect/decal/cleanable/blood/pool in turfLoc)
- if(pool.blood_state == last_blood_state && (!typeFilter || istype(pool, typeFilter)))
+ if(pool.blood_state == last_blood_state && pool.footprint_sprite == footprint_sprite && (!typeFilter || istype(pool, typeFilter)))
return pool
/**
@@ -158,23 +160,23 @@
return
var/half_our_blood = bloody_shoes[last_blood_state] / 2
-
+ var/footprint_sprite = wielder.get_footprint_sprite()
// Add footprints in old loc if we have enough cream
if(half_our_blood >= BLOOD_FOOTPRINTS_MIN)
var/turf/oldLocTurf = get_turf(OldLoc)
- var/obj/effect/decal/cleanable/blood/footprints/oldLocFP = find_pool_by_blood_state(oldLocTurf, /obj/effect/decal/cleanable/blood/footprints)
+ var/obj/effect/decal/cleanable/blood/footprints/oldLocFP = find_pool_by_blood_state(oldLocTurf, /obj/effect/decal/cleanable/blood/footprints, footprint_sprite)
if(oldLocFP)
// Footprints found in the tile we left, add us to it
add_parent_to_footprint(oldLocFP)
if (!(oldLocFP.exited_dirs & wielder.dir))
oldLocFP.exited_dirs |= wielder.dir
oldLocFP.update_appearance()
- else if(find_pool_by_blood_state(oldLocTurf))
+ else if(find_pool_by_blood_state(oldLocTurf, footprint_sprite = footprint_sprite))
// No footprints in the tile we left, but there was some other blood pool there. Add exit footprints on it
adjust_bloody_shoes(last_blood_state, half_our_blood)
update_icon()
- oldLocFP = new(oldLocTurf)
+ oldLocFP = new(oldLocTurf, footprint_sprite)
if(!QDELETED(oldLocFP)) ///prints merged
oldLocFP.blood_state = last_blood_state
oldLocFP.exited_dirs |= wielder.dir
@@ -194,7 +196,7 @@
adjust_bloody_shoes(last_blood_state, half_our_blood)
update_icon()
- var/obj/effect/decal/cleanable/blood/footprints/FP = new(get_turf(parent_atom))
+ var/obj/effect/decal/cleanable/blood/footprints/FP = new(get_turf(parent_atom), footprint_sprite)
if(!QDELETED(FP)) ///prints merged
FP.blood_state = last_blood_state
FP.entered_dirs |= wielder.dir
@@ -253,7 +255,8 @@
return COMPONENT_INCOMPATIBLE
parent_atom = parent
wielder = parent
-
+ if(footprint_sprite)
+ src.footprint_sprite = footprint_sprite
if(!bloody_feet)
bloody_feet = mutable_appearance('icons/effects/blood.dmi', "shoeblood", SHOES_LAYER)
diff --git a/code/datums/components/palette.dm b/code/datums/components/palette.dm
index aa47262514af..a3b967516aa3 100644
--- a/code/datums/components/palette.dm
+++ b/code/datums/components/palette.dm
@@ -106,7 +106,7 @@
var/is_right_clicking = (user.istate & ISTATE_SECONDARY)
var/index = text2num(choice)
if(is_right_clicking)
- var/chosen_color = input(user, "Pick new color", "[parent]", colors[index]) as color|null
+ var/chosen_color = tgui_color_picker(user, "Pick new color", "[parent]", colors[index])
if(chosen_color && !QDELETED(src) && !IS_DEAD_OR_INCAP(user) && user.is_holding(parent))
colors[index] = chosen_color
update_radial_list()
diff --git a/code/datums/elements/climbable.dm b/code/datums/elements/climbable.dm
index 52a62fdb8fcb..3e2e2e419e83 100644
--- a/code/datums/elements/climbable.dm
+++ b/code/datums/elements/climbable.dm
@@ -77,8 +77,8 @@
vault_over_object(user, climbed_thing)
if(climb_stun)
user.Stun(climb_stun)
- user.visible_message("[user] flips over [src]! ", \
- "You flip over [climbed_thing]! ")
+ user.visible_message(span_warning("[user] flips over [climbed_thing]!"), \
+ span_notice("You flip over [climbed_thing]!"))
else if(do_climb(climbed_thing, user, params))
user.visible_message(span_warning("[user] climbs onto [climbed_thing]."), \
diff --git a/code/datums/greyscale/config_types/greyscale_configs.dm b/code/datums/greyscale/config_types/greyscale_configs.dm
index 0c44ddb748e8..dcd65739c9ca 100644
--- a/code/datums/greyscale/config_types/greyscale_configs.dm
+++ b/code/datums/greyscale/config_types/greyscale_configs.dm
@@ -801,7 +801,7 @@
/datum/greyscale_config/buckets
name = "Buckets"
- icon_file = 'icons/obj/janitor.dmi'
+ icon_file = 'icons/obj/service/janitor.dmi'
json_config = 'code/datums/greyscale/json_configs/buckets.json'
/datum/greyscale_config/buckets_worn
diff --git a/code/datums/mood_events/drink_events.dm b/code/datums/mood_events/drink_events.dm
index 311764789c2b..f9c54b1b8cc8 100644
--- a/code/datums/mood_events/drink_events.dm
+++ b/code/datums/mood_events/drink_events.dm
@@ -37,3 +37,8 @@
description = "Amazing taste!"
mood_change = 50
timeout = 10 MINUTES
+
+/datum/mood_event/wellcheers
+ description = "What a tasty can of Wellcheers! The salty grape taste is a great pick-me-up."
+ mood_change = 3
+ timeout = 7 MINUTES
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index d30933f0669f..ca18e3814dd5 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -36,6 +36,10 @@
var/list/firealarms = list()
///Alarm type to count of sources. Not usable for ^ because we handle fires differently
var/list/active_alarms = list()
+ /// The current alarm fault status
+ var/fault_status = AREA_FAULT_NONE
+ /// The source machinery for the area's fault status
+ var/fault_location
///List of all lights in our area
var/list/lights = list()
///We use this just for fire alarms, because they're area based right now so one alarm going poof shouldn't prevent you from clearing your alarms listing. Fire alarms and fire locks will set and clear alarms.
@@ -316,10 +320,15 @@ GLOBAL_LIST_EMPTY(teleportlocs)
*
* Allows interested parties (lights and fire alarms) to react
*/
-/area/proc/set_fire_effect(new_fire)
+/area/proc/set_fire_effect(new_fire, fault_type, fault_source)
if(new_fire == fire)
return
fire = new_fire
+ fault_status = fault_type
+ if(fire)
+ fault_location = fault_source
+ else
+ fault_location = null
SEND_SIGNAL(src, COMSIG_AREA_FIRE_CHANGED, fire)
/**
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 70d35dd1bdf1..d3d5f6095427 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -2046,7 +2046,7 @@
active_hud.screentip_text.maptext = ""
return
- active_hud.screentip_text.maptext_y = 0
+ active_hud.screentip_text.maptext_y = 10 // 10px lines us up with the action buttons top left corner
var/lmb_rmb_line = ""
var/ctrl_lmb_ctrl_rmb_line = ""
var/alt_lmb_alt_rmb_line = ""
@@ -2113,15 +2113,15 @@
extra_lines++
if(extra_lines)
- extra_context = "[lmb_rmb_line][ctrl_lmb_ctrl_rmb_line][alt_lmb_alt_rmb_line][shift_lmb_ctrl_shift_lmb_line] "
- //first extra line pushes atom name line up 10px, subsequent lines push it up 9px, this offsets that and keeps the first line in the same place
- active_hud.screentip_text.maptext_y = -10 + (extra_lines - 1) * -9
+ extra_context = "[lmb_rmb_line][ctrl_lmb_ctrl_rmb_line][alt_lmb_alt_rmb_line][shift_lmb_ctrl_shift_lmb_line] "
+ //first extra line pushes atom name line up 11px, subsequent lines push it up 9px, this offsets that and keeps the first line in the same place
+ active_hud.screentip_text.maptext_y = -1 + (extra_lines - 1) * -9
if (screentips_enabled == SCREENTIP_PREFERENCE_CONTEXT_ONLY && extra_context == "")
active_hud.screentip_text.maptext = ""
else
//We inline a MAPTEXT() here, because there's no good way to statically add to a string like this
- active_hud.screentip_text.maptext = "[name][extra_context] "
+ active_hud.screentip_text.maptext = "[name][extra_context] "
/// Gets a merger datum representing the connected blob of objects in the allowed_types argument
/atom/proc/GetMergeGroup(id, list/allowed_types)
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index 7ba6eca67fd0..e358297834c4 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -76,9 +76,12 @@
/obj/machinery/door/firedoor/Initialize(mapload)
. = ..()
+ id_tag = assign_random_name()
soundloop = new(src, FALSE)
CalculateAffectingAreas()
my_area = get_area(src)
+ if(name == initial(name))
+ update_name()
if(!merger_typecache)
merger_typecache = typecacheof(/obj/machinery/door/firedoor)
@@ -182,6 +185,10 @@
return .
+/obj/machinery/door/firedoor/update_name(updates)
+ . = ..()
+ name = "[get_area_name(my_area)] [initial(name)] [id_tag]"
+
/**
* Calculates what areas we should worry about.
*
@@ -324,6 +331,8 @@
return //We're already active
soundloop.start()
is_playing_alarm = TRUE
+ my_area.fault_status = AREA_FAULT_AUTOMATIC
+ my_area.fault_location = name
var/datum/merger/merge_group = GetMergeGroup(merger_id, merger_typecache)
for(var/obj/machinery/door/firedoor/buddylock as anything in merge_group.members)
buddylock.activate(code)
@@ -336,6 +345,8 @@
/obj/machinery/door/firedoor/proc/start_deactivation_process()
soundloop.stop()
is_playing_alarm = FALSE
+ my_area.fault_status = AREA_FAULT_NONE
+ my_area.fault_location = null
var/datum/merger/merge_group = GetMergeGroup(merger_id, merger_typecache)
for(var/obj/machinery/door/firedoor/buddylock as anything in merge_group.members)
buddylock.reset()
@@ -372,7 +383,7 @@
if(LAZYLEN(place.active_firelocks) != 1)
continue
//if we're the first to activate in this particular area
- place.set_fire_effect(TRUE) //bathe in red
+ place.set_fire_effect(TRUE, AREA_FAULT_AUTOMATIC, name) //bathe in red
if(place == my_area)
// We'll limit our reporting to just the area we're on. If the issue affects bordering areas, they can report it themselves
place.alarm_manager.send_alarm(ALARM_FIRE, place)
@@ -432,7 +443,7 @@
LAZYREMOVE(place.active_firelocks, src)
if(LAZYLEN(place.active_firelocks)) // If we were the last firelock still active, clear the area effects
continue
- place.set_fire_effect(FALSE)
+ place.set_fire_effect(FALSE, AREA_FAULT_NONE, name)
if(place == my_area)
place.alarm_manager.clear_alarm(ALARM_FIRE, place)
@@ -698,6 +709,11 @@
/obj/machinery/door/firedoor/border_only/Initialize(mapload)
. = ..()
adjust_lights_starting_offset()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_exit),
+ )
+
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/machinery/door/firedoor/border_only/adjust_lights_starting_offset()
light_xoffset = 0
diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm
index 45698fcb582c..e79869dfbf71 100644
--- a/code/game/machinery/firealarm.dm
+++ b/code/game/machinery/firealarm.dm
@@ -50,11 +50,12 @@
/obj/machinery/firealarm/Initialize(mapload, dir, building)
. = ..()
+ id_tag = assign_random_name()
if(building)
buildstage = ALARM_NO_CIRCUIT
set_panel_open(TRUE)
if(name == initial(name))
- name = "[get_area_name(src)] [initial(name)]"
+ update_name()
my_area = get_area(src)
LAZYADD(my_area.firealarms, src)
@@ -114,7 +115,7 @@
/obj/machinery/firealarm/update_name(updates)
. = ..()
- name = "[get_area_name(my_area)] [initial(name)]"
+ name = "[get_area_name(my_area)] [initial(name)] [id_tag]"
/obj/machinery/firealarm/on_exit_area(datum/source, area/area_to_unregister)
//we cannot unregister from an area we never registered to in the first place
@@ -259,6 +260,8 @@
if(user)
balloon_alert(user, "triggered alarm!")
user.log_message("triggered a fire alarm.", LOG_GAME)
+ my_area.fault_status = AREA_FAULT_MANUAL
+ my_area.fault_location = name
soundloop.start() //Manually pulled fire alarms will make the sound, rather than the doors.
SEND_SIGNAL(src, COMSIG_FIREALARM_ON_TRIGGER)
update_use_power(ACTIVE_POWER_USE)
@@ -476,6 +479,7 @@
. = ..()
if((my_area?.fire || LAZYLEN(my_area?.active_firelocks)))
. += "The local area hazard light is flashing."
+ . += "The fault location display is [my_area.fault_location] ([my_area.fault_status == AREA_FAULT_AUTOMATIC ? "Automatic Detection" : "Manual Trigger"])."
if(is_station_level(z))
. += "The station security alert level is [SSsecurity_level.get_current_level_as_text()]."
. += "Left-Click to activate all firelocks in this area."
diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm
index 0a7bfd308ede..96dc1918e7df 100644
--- a/code/game/machinery/status_display.dm
+++ b/code/game/machinery/status_display.dm
@@ -1,11 +1,15 @@
// Status display
-// (formerly Countdown timer display)
-#define MAX_STATIC_WIDTH 25
-#define FONT_STYLE "5pt 'Small Fonts'"
+#define MAX_STATIC_WIDTH 22
+#define FONT_STYLE "12pt 'TinyUnicode'"
#define SCROLL_RATE (0.04 SECONDS) // time per pixel
-#define LINE1_Y -8
-#define LINE2_Y -15
+#define SCROLL_PADDING 2 // how many pixels we chop to make a smooth loop
+#define LINE1_X 1
+#define LINE1_Y -4
+#define LINE2_X 1
+#define LINE2_Y -11
+#define STATUS_DISPLAY_FONT_DATUM /datum/font/tiny_unicode/size_12pt
+
/// Status display which can show images and scrolling text.
/obj/machinery/status_display
name = "status display"
@@ -124,14 +128,14 @@
* * message - the new message text.
* Returns new /obj/effect/overlay/status_display_text or null if unchanged.
*/
-/obj/machinery/status_display/proc/update_message(obj/effect/overlay/status_display_text/overlay, line_y, message, x_offset)
+/obj/machinery/status_display/proc/update_message(obj/effect/overlay/status_display_text/overlay, line_y, message, x_offset, line_pair)
if(overlay && message == overlay.message)
return null
if(overlay)
qdel(overlay)
- var/obj/effect/overlay/status_display_text/new_status_display_text = new(src, line_y, message, text_color, header_text_color, x_offset)
+ var/obj/effect/overlay/status_display_text/new_status_display_text = new(src, line_y, message, text_color, header_text_color, x_offset, line_pair)
// Draw our object visually "in front" of this display, taking advantage of sidemap
new_status_display_text.pixel_y = -32
new_status_display_text.pixel_z = 32
@@ -149,7 +153,7 @@
return
set_light(l_outer_range = 1.4, l_power = 0.7, l_color = LIGHT_COLOR_BLUE) // blue light
-/obj/machinery/status_display/update_overlays()
+/obj/machinery/status_display/update_overlays(updates)
. = ..()
if(machine_stat & (NOPOWER|BROKEN))
@@ -167,10 +171,18 @@
if(current_picture == AI_DISPLAY_DONT_GLOW) // If the thing's off, don't display the emissive yeah?
return .
else
- var/overlay = update_message(message1_overlay, LINE1_Y, message1)
+ var/line1_metric
+ var/line2_metric
+ var/line_pair
+ var/datum/font/display_font = new STATUS_DISPLAY_FONT_DATUM()
+ line1_metric = display_font.get_metrics(message1)
+ line2_metric = display_font.get_metrics(message2)
+ line_pair = (line1_metric > line2_metric ? line1_metric : line2_metric)
+
+ var/overlay = update_message(message1_overlay, LINE1_Y, message1, LINE1_X, line_pair)
if(overlay)
message1_overlay = overlay
- overlay = update_message(message2_overlay, LINE2_Y, message2)
+ overlay = update_message(message2_overlay, LINE2_Y, message2, LINE2_X, line_pair)
if(overlay)
message2_overlay = overlay
@@ -217,10 +229,10 @@
/obj/machinery/status_display/proc/display_shuttle_status(obj/docking_port/mobile/shuttle)
if(!shuttle)
// the shuttle is missing - no processing
- set_messages("shutl?","")
+ set_messages("shutl","not in service")
return PROCESS_KILL
else if(shuttle.timer)
- var/line1 = "- [shuttle.getModeStr()] -"
+ var/line1 = "<<< [shuttle.getModeStr()]"
var/line2 = shuttle.getTimerStr()
set_messages(line1, line2)
@@ -245,39 +257,23 @@
// If the line is short enough to not marquee, and it matches this, it's a header.
var/static/regex/header_regex = regex("^-.*-$")
- /// Width of each character, including kerning gap afterwards.
- /// We don't use rich text or anything fancy, so we can bake these values.
- var/static/list/char_widths = list(
- // ! " # $ % & ' ( ) * + , - . /
- 1, 2, 3, 5, 4, 5, 5, 2, 3, 3, 3, 4, 2, 3, 2, 3,
- // 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
- 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 3, 3, 3, 3,
- // @ A B C D E F G H I J K L M N O
- 7, 5, 5, 5, 5, 4, 4, 5, 5, 2, 4, 5, 4, 6, 5, 5,
- // P Q R S T U V W X Y Z [ \ ] ^ _
- 5, 5, 5, 5, 4, 5, 4, 6, 4, 4, 4, 3, 3, 3, 4, 4,
- // ` a b c d e f g h i j k l m n o
- 3, 5, 5, 5, 5, 4, 4, 5, 5, 2, 4, 5, 4, 6, 5, 5,
- // p q r s t u v w x y z { | } ~
- 5, 5, 5, 5, 4, 5, 4, 6, 4, 4, 4, 3, 2, 3, 4,
- )
-
-/obj/effect/overlay/status_display_text/Initialize(mapload, yoffset, line, text_color, header_text_color, xoffset = 0)
+/obj/effect/overlay/status_display_text/Initialize(mapload, yoffset, line, text_color, header_text_color, xoffset = 0, line_pair)
. = ..()
maptext_y = yoffset
message = line
- var/line_width = measure_width(line)
+ var/datum/font/display_font = new STATUS_DISPLAY_FONT_DATUM()
+ var/line_width = display_font.get_metrics(line)
if(line_width > MAX_STATIC_WIDTH)
// Marquee text
- var/marquee_message = "[line] - [line] - [line]"
+ var/marquee_message = "[line] [line] [line]"
// Width of full content. Must of these is never revealed unless the user inputted a single character.
- var/full_marquee_width = measure_width(marquee_message)
+ var/full_marquee_width = display_font.get_metrics("[marquee_message] ")
// We loop after only this much has passed.
- var/looping_marquee_width = measure_width("[line] - ")
+ var/looping_marquee_width = (display_font.get_metrics("[line] ]") - SCROLL_PADDING)
maptext = generate_text(marquee_message, center = FALSE, text_color = text_color)
maptext_width = full_marquee_width
@@ -287,45 +283,24 @@
add_filter("mask", 1, alpha_mask_filter(icon = icon(icon, "outline")))
// Scroll.
- var/time = looping_marquee_width * SCROLL_RATE
- animate(src, maptext_x = -looping_marquee_width, time = time, loop = -1)
- animate(maptext_x = 0, time = 0)
+ var/time = line_pair * SCROLL_RATE
+ animate(src, maptext_x = (-looping_marquee_width) + MAX_STATIC_WIDTH, time = time, loop = -1)
+ animate(maptext_x = MAX_STATIC_WIDTH, time = 0)
else
// Centered text
var/color = header_regex.Find(line) ? header_text_color : text_color
maptext = generate_text(line, center = TRUE, text_color = color)
maptext_x = xoffset //Defaults to 0, this would be centered unless overided
-/**
- * A hyper-streamlined version of MeasureText that doesn't support different fonts, rich formatting, or multiline.
- * But it also doesn't require a client.
- *
- * Returns the width in pixels
- *
- * Arguments:
- * * text - the text to measure
- */
-/obj/effect/overlay/status_display_text/proc/measure_width(text)
- var/width = 0
- for(var/text_idx in 1 to length(text))
- var/ascii = text2ascii(text, text_idx)
- if(!(ascii in 0x20 to 0x7E))
- // So we can't possibly runtime, even though the input should be in range already.
- width += 3
- continue
- width += char_widths[ascii - 0x1F]
-
- return width
-
/**
* Generate the actual maptext.
* Arguments:
* * text - the text to display
- * * center - center the text if TRUE, otherwise left-align
+ * * center - center the text if TRUE, otherwise right-align (the direction the text is coming from)
* * text_color - the text color
*/
/obj/effect/overlay/status_display_text/proc/generate_text(text, center, text_color)
- return {"
[text]
"}
+ return {"[text]
"}
/// Evac display which shows shuttle timer or message set by Command.
/obj/machinery/status_display/evac
@@ -412,8 +387,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/evac, 32)
if(!SSshuttle.supply)
// Might be missing in our first update on initialize before shuttles
// have loaded. Cross our fingers that it will soon return.
- line1 = "CARGO"
- line2 = "shutl?"
+ line1 = "shutl"
+ line2 = "not in service"
else if(SSshuttle.supply.mode == SHUTTLE_IDLE)
if(is_station_level(SSshuttle.supply.z))
line1 = "CARGO"
@@ -422,7 +397,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/evac, 32)
line1 = ""
line2 = ""
else
- line1 = "- [SSshuttle.supply.getModeStr()] -"
+ line1 = "<<< [SSshuttle.supply.getModeStr()]"
line2 = SSshuttle.supply.getTimerStr()
set_messages(line1, line2)
@@ -602,5 +577,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/ai, 32)
#undef MAX_STATIC_WIDTH
#undef FONT_STYLE
#undef SCROLL_RATE
+#undef LINE1_X
#undef LINE1_Y
+#undef LINE2_X
#undef LINE2_Y
+#undef STATUS_DISPLAY_FONT_DATUM
diff --git a/code/game/objects/effects/countdown.dm b/code/game/objects/effects/countdown.dm
index cccf77b20655..f820397c0d9f 100644
--- a/code/game/objects/effects/countdown.dm
+++ b/code/game/objects/effects/countdown.dm
@@ -64,7 +64,7 @@
displayed_text = new_val
if(displayed_text)
- maptext = MAPTEXT("[displayed_text] ")
+ maptext = MAPTEXT("[displayed_text]")
else
maptext = null
@@ -102,14 +102,13 @@
/obj/effect/countdown/supermatter
name = "supermatter damage"
- text_size = 1
color = "#00ff80"
/obj/effect/countdown/supermatter/get_value()
var/obj/machinery/power/supermatter_crystal/S = attached_to
if(!istype(S))
return
- return "[round(S.get_integrity_percent())]%
"
+ return "[round(S.get_integrity_percent())]%
"
/obj/effect/countdown/transformer
name = "transformer countdown"
diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm
index 27b5f1f97dc4..83d905882447 100644
--- a/code/game/objects/effects/decals/cleanable/humans.dm
+++ b/code/game/objects/effects/decals/cleanable/humans.dm
@@ -13,6 +13,7 @@
var/drydesc = "Looks like it's been here a while. Eew." //as above
var/drytime = 0
var/count = 0
+ var/footprint_sprite = null
/obj/effect/decal/cleanable/blood/Initialize(mapload)
. = ..()
@@ -232,7 +233,7 @@
name = "footprints"
desc = "WHOSE FOOTPRINTS ARE THESE?"
icon = 'icons/effects/footprints.dmi'
- icon_state = "blood1"
+ icon_state = "blood_shoes_enter"
random_icon_states = null
blood_state = BLOOD_STATE_HUMAN //the icon state to load images from
var/entered_dirs = 0
@@ -247,12 +248,13 @@
dryname = "dried footprints"
drydesc = "HMM... SOMEONE WAS HERE!"
-/obj/effect/decal/cleanable/blood/footprints/Initialize(mapload)
+/obj/effect/decal/cleanable/blood/footprints/Initialize(mapload, footprint_sprite)
+ src.footprint_sprite = footprint_sprite
. = ..()
icon_state = "" //All of the footprint visuals come from overlays
if(mapload)
entered_dirs |= dir //Keep the same appearance as in the map editor
- update_appearance()
+ update_appearance(mapload ? (ALL) : (UPDATE_NAME | UPDATE_DESC))
//Rotate all of the footprint directions too
/obj/effect/decal/cleanable/blood/footprints/setDir(newdir)
@@ -274,6 +276,21 @@
update_appearance()
return ..()
+/obj/effect/decal/cleanable/blood/footprints/update_name(updates)
+ switch(footprint_sprite)
+ if(FOOTPRINT_SPRITE_CLAWS)
+ name = "clawprints"
+ if(FOOTPRINT_SPRITE_SHOES)
+ name = "footprints"
+ if(FOOTPRINT_SPRITE_PAWS)
+ name = "pawprints"
+ dryname = "dried [name]"
+ return ..()
+
+/obj/effect/decal/cleanable/blood/footprints/update_desc(updates)
+ desc = "WHOSE [uppertext(name)] ARE THESE?"
+ return ..()
+
/obj/effect/decal/cleanable/blood/footprints/update_icon()
. = ..()
alpha = min(BLOODY_FOOTPRINT_BASE_ALPHA + (255 - BLOODY_FOOTPRINT_BASE_ALPHA) * bloodiness / (BLOOD_ITEM_MAX / 2), 255)
@@ -288,22 +305,22 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache)
. = ..()
for(var/Ddir in GLOB.cardinals)
if(entered_dirs & Ddir)
- var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"]
+ var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["entered-[footprint_sprite]-[blood_state]-[Ddir]"]
if(!bloodstep_overlay)
- GLOB.bloody_footprints_cache["entered-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]1", dir = Ddir)
+ GLOB.bloody_footprints_cache["entered-[footprint_sprite]-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]_[footprint_sprite]_enter", dir = Ddir)
. += bloodstep_overlay
if(exited_dirs & Ddir)
- var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"]
+ var/image/bloodstep_overlay = GLOB.bloody_footprints_cache["exited-[footprint_sprite]-[blood_state]-[Ddir]"]
if(!bloodstep_overlay)
- GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]2", dir = Ddir)
+ GLOB.bloody_footprints_cache["exited-[footprint_sprite]-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]_[footprint_sprite]_exit", dir = Ddir)
. += bloodstep_overlay
/obj/effect/decal/cleanable/blood/footprints/examine(mob/user)
. = ..()
if((shoe_types.len + species_types.len) > 0)
- . += "You recognise the footprints as belonging to:"
+ . += "You recognise the [name] as belonging to:"
for(var/sole in shoe_types)
var/obj/item/clothing/item = sole
var/article = initial(item.gender) == PLURAL ? "Some" : "A"
@@ -313,14 +330,18 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache)
if(species == "unknown")
. += "Some feet ."
else if(species == SPECIES_MONKEY)
- . += "[icon2html('icons/mob/species/human/human.dmi', user, "monkey")] Some monkey feet ."
+ . += "[icon2html('icons/mob/species/human/human.dmi', user, "monkey")] Some monkey paws ."
+ else if(species == SPECIES_SIMIAN)
+ . += "[icon2html('monkestation/icons/mob/species/simian/bodyparts.dmi', user, "simian_l_leg")] Some simian paws ."
+ else if(species == SPECIES_LIZARD)
+ . += "[icon2html('icons/mob/species/lizard/bodyparts.dmi', user, "digitigrade_l_leg")] Some lizard claws ."
else if(species == SPECIES_HUMAN)
. += "[icon2html('icons/mob/species/human/bodyparts.dmi', user, "default_human_l_leg")] Some human feet ."
else
. += "[icon2html('icons/mob/species/human/bodyparts.dmi', user, "[species]_l_leg")] Some [species] feet ."
-/obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/C)
- if(blood_state != C.blood_state) //We only replace footprints of the same type as us
+/obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/blood/blood_decal)
+ if(blood_state != blood_decal.blood_state || footprint_sprite != blood_decal.footprint_sprite) //We only replace footprints of the same type as us
return FALSE
return ..()
diff --git a/code/game/objects/effects/spawners/random/food_or_drink.dm b/code/game/objects/effects/spawners/random/food_or_drink.dm
index e6cf9db1f82c..b20f1c19d61a 100644
--- a/code/game/objects/effects/spawners/random/food_or_drink.dm
+++ b/code/game/objects/effects/spawners/random/food_or_drink.dm
@@ -156,6 +156,7 @@
/obj/item/reagent_containers/cup/glass/bottle/sake = 5,
/obj/item/reagent_containers/cup/glass/bottle/grappa = 5,
/obj/item/reagent_containers/cup/glass/bottle/applejack = 5,
+ /obj/item/reagent_containers/cup/glass/bottle/wine_voltaic = 5,
/obj/item/reagent_containers/cup/bottle/ethanol = 2,
/obj/item/reagent_containers/cup/glass/bottle/fernet = 2,
/obj/item/reagent_containers/cup/glass/bottle/champagne = 2,
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 02588d175c03..681c8ed996a8 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -1533,7 +1533,7 @@
/// Common proc used by painting tools like spraycans and palettes that can access the entire 24 bits color space.
/obj/item/proc/pick_painting_tool_color(mob/user, default_color)
- var/chosen_color = input(user,"Pick new color", "[src]", default_color) as color|null
+ var/chosen_color = tgui_color_picker(user, "Pick new color", "[src]", default_color)
if(!chosen_color || QDELETED(src) || IS_DEAD_OR_INCAP(user) || !user.is_holding(src))
return
set_painting_tool_color(chosen_color)
diff --git a/code/game/objects/items/broom.dm b/code/game/objects/items/broom.dm
index 7bf5cd885973..d3dd19b71b15 100644
--- a/code/game/objects/items/broom.dm
+++ b/code/game/objects/items/broom.dm
@@ -4,7 +4,7 @@
/obj/item/pushbroom
name = "push broom"
desc = "This is my BROOMSTICK! It can be used manually or braced with two hands to sweep items as you move. It has a telescopic handle for compact storage."
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/service/janitor.dmi'
icon_state = "broom0"
base_icon_state = "broom"
lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi'
diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm
index cf23e3ff4ab8..9cf424d0a6c6 100644
--- a/code/game/objects/items/devices/lightreplacer.dm
+++ b/code/game/objects/items/devices/lightreplacer.dm
@@ -34,7 +34,7 @@
/obj/item/lightreplacer
name = "light replacer"
desc = "A device to automatically replace lights. Refill with broken or working light bulbs, or sheets of glass."
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/service/janitor.dmi'
icon_state = "lightreplacer"
inhand_icon_state = "electronic"
worn_icon_state = "light_replacer"
diff --git a/code/game/objects/items/dyekit.dm b/code/game/objects/items/dyekit.dm
index f2b26a56dc3e..df6763cd94f1 100644
--- a/code/game/objects/items/dyekit.dm
+++ b/code/game/objects/items/dyekit.dm
@@ -34,7 +34,7 @@
if(!user.can_perform_action(src, NEED_DEXTERITY))
return
- var/new_grad_color = input(user, "Choose a secondary hair color:", "Character Preference",human_target.grad_color) as color|null
+ var/new_grad_color = tgui_color_picker(user, "Choose a secondary hair color:", "Character Preference",human_target.grad_color)
if(!new_grad_color || !user.can_perform_action(src, NEED_DEXTERITY) || !user.CanReach(target))
return
diff --git a/code/game/objects/items/janitor_key.dm b/code/game/objects/items/janitor_key.dm
index 06370b5af842..8f96205984b4 100644
--- a/code/game/objects/items/janitor_key.dm
+++ b/code/game/objects/items/janitor_key.dm
@@ -6,7 +6,7 @@
desc = "A key ring with a beeper, allowing the keys to change shape depending on which department it has access to."
icon_state = "access_key"
inhand_icon_state = "access_key"
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/service/janitor.dmi'
lefthand_file = 'icons/mob/inhands/items/keys_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items/keys_righthand.dmi'
hitsound = 'sound/items/rattling_keys_attack.ogg'
diff --git a/code/game/objects/items/mop.dm b/code/game/objects/items/mop.dm
index bfbf3a47a29b..871b5434d882 100644
--- a/code/game/objects/items/mop.dm
+++ b/code/game/objects/items/mop.dm
@@ -1,7 +1,7 @@
/obj/item/mop
desc = "The world of janitalia wouldn't be complete without a mop."
name = "mop"
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/service/janitor.dmi'
icon_state = "mop"
inhand_icon_state = "mop"
lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi'
diff --git a/code/game/objects/items/rcd/RLD.dm b/code/game/objects/items/rcd/RLD.dm
index 4cfc3d086c0e..9d07929a9091 100644
--- a/code/game/objects/items/rcd/RLD.dm
+++ b/code/game/objects/items/rcd/RLD.dm
@@ -65,7 +65,7 @@
mode = GLOW_MODE
to_chat(user, span_notice("You change RLD's mode to 'Light Launcher'."))
if("Color Pick")
- var/new_choice = input(user,"","Choose Color",color_choice) as color
+ var/new_choice = tgui_color_picker(user, "", "Choose Color", color_choice)
if(new_choice == null)
return
diff --git a/code/game/objects/items/stacks/wrap.dm b/code/game/objects/items/stacks/wrap.dm
index 43b7a497b767..4fa379a9796d 100644
--- a/code/game/objects/items/stacks/wrap.dm
+++ b/code/game/objects/items/stacks/wrap.dm
@@ -37,8 +37,8 @@
set_greyscale(colors = list(generated_base_color, generated_ribbon_color))
/obj/item/stack/wrapping_paper/AltClick(mob/user, modifiers)
- var/new_base = input(user, "", "Select a base color", color) as color
- var/new_ribbon = input(user, "", "Select a ribbon color", color) as color
+ var/new_base = tgui_color_picker(user, "", "Select a base color", color)
+ var/new_ribbon = tgui_color_picker(user, "", "Select a ribbon color", color)
if(!user.can_perform_action(src))
return
set_greyscale(colors = list(new_base, new_ribbon))
diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm
index 8b34adfbaab4..ef9e4f2b7d85 100644
--- a/code/game/objects/items/storage/bags.dm
+++ b/code/game/objects/items/storage/bags.dm
@@ -34,7 +34,7 @@
/obj/item/storage/bag/trash
name = "trash bag"
desc = "It's the heavy-duty black polymer kind. Time to take out the trash!"
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/service/janitor.dmi'
icon_state = "trashbag"
inhand_icon_state = "trashbag"
lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi'
diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm
index bd1124dcf1f4..7ce67bb360f0 100644
--- a/code/game/objects/items/trash.dm
+++ b/code/game/objects/items/trash.dm
@@ -1,6 +1,6 @@
//Added by Jack Rost
/obj/item/trash
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/service/janitor.dmi'
lefthand_file = 'icons/mob/inhands/items/food_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items/food_righthand.dmi'
desc = "This is rubbish."
diff --git a/code/game/objects/structures/dresser.dm b/code/game/objects/structures/dresser.dm
index 9d4e14339f11..4c0e3585af75 100644
--- a/code/game/objects/structures/dresser.dm
+++ b/code/game/objects/structures/dresser.dm
@@ -47,7 +47,7 @@
if(new_undies)
dressing_human.underwear = new_undies
if("Underwear Color")
- var/new_underwear_color = input(dressing_human, "Choose your underwear color", "Underwear Color", dressing_human.underwear_color) as color|null
+ var/new_underwear_color = tgui_color_picker(dressing_human, "Choose your underwear color", "Underwear Color", dressing_human.underwear_color)
if(new_underwear_color)
dressing_human.underwear_color = sanitize_hexcolor(new_underwear_color)
if("Undershirt")
diff --git a/code/game/objects/structures/janitor.dm b/code/game/objects/structures/janitor.dm
index 7b8426074f6b..3d7408e60475 100644
--- a/code/game/objects/structures/janitor.dm
+++ b/code/game/objects/structures/janitor.dm
@@ -3,7 +3,7 @@
/obj/structure/mop_bucket
name = "mop bucket"
desc = "Fill it with water, but don't forget a mop!"
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/service/janitor.dmi'
icon_state = "mopbucket"
density = TRUE
var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite
diff --git a/code/game/objects/structures/maintenance.dm b/code/game/objects/structures/maintenance.dm
index d0eef52a8fb3..ceb62ec3048e 100644
--- a/code/game/objects/structures/maintenance.dm
+++ b/code/game/objects/structures/maintenance.dm
@@ -150,7 +150,7 @@ at the cost of risking a vicious bite.**/
var/altar_result = show_radial_menu(user, src, altar_options, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE)
switch(altar_result)
if("Change Color")
- var/chosen_color = input(user, "", "Choose Color", pants_color) as color|null
+ var/chosen_color = tgui_color_picker(user, "", "Choose Color", pants_color)
if(!isnull(chosen_color) && user.can_perform_action(src))
pants_color = chosen_color
if("Create Artefact")
diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm
index 96abe485a18e..0692e7f3c1ba 100644
--- a/code/game/objects/structures/mirror.dm
+++ b/code/game/objects/structures/mirror.dm
@@ -203,7 +203,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror, 28)
amazed_human.dna.update_ui_block(DNA_SKIN_TONE_BLOCK)
if(MUTCOLORS in amazed_human.dna.species.species_traits)
- var/new_mutantcolor = input(user, "Choose your skin color:", "Race change", amazed_human.dna.features["mcolor"]) as color|null
+ var/new_mutantcolor = tgui_color_picker(user, "Choose your skin color:", "Race change", amazed_human.dna.features["mcolor"])
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return TRUE
if(new_mutantcolor)
@@ -253,21 +253,21 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror, 28)
if(hairchoice == "Style") //So you just want to use a mirror then?
return ..()
else
- var/new_hair_color = input(amazed_human, "Choose your hair color", "Hair Color",amazed_human.hair_color) as color|null
+ var/new_hair_color = tgui_color_picker(amazed_human, "Choose your hair color", "Hair Color", amazed_human.hair_color)
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return TRUE
if(new_hair_color)
amazed_human.hair_color = sanitize_hexcolor(new_hair_color)
amazed_human.dna.update_ui_block(DNA_HAIR_COLOR_BLOCK)
if(amazed_human.gender == "male")
- var/new_face_color = input(amazed_human, "Choose your facial hair color", "Hair Color", amazed_human.facial_hair_color) as color|null
+ var/new_face_color = tgui_color_picker(amazed_human, "Choose your facial hair color", "Hair Color", amazed_human.facial_hair_color)
if(new_face_color)
amazed_human.facial_hair_color = sanitize_hexcolor(new_face_color)
amazed_human.dna.update_ui_block(DNA_FACIAL_HAIR_COLOR_BLOCK)
amazed_human.update_body_parts()
if(BODY_ZONE_PRECISE_EYES)
- var/new_eye_color = input(amazed_human, "Choose your eye color", "Eye Color", amazed_human.eye_color_left) as color|null
+ var/new_eye_color = tgui_color_picker(amazed_human, "Choose your eye color", "Eye Color", amazed_human.eye_color_left)
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return TRUE
if(new_eye_color)
diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm
index 80aa46fc09b6..169f76a4b570 100644
--- a/code/game/objects/structures/stairs.dm
+++ b/code/game/objects/structures/stairs.dm
@@ -11,6 +11,7 @@
icon = 'icons/obj/stairs.dmi'
icon_state = "stairs"
anchored = TRUE
+ move_resist = INFINITY
var/force_open_above = FALSE // replaces the turf above this stair obj with /turf/open/openspace
var/terminator_mode = STAIR_TERMINATOR_AUTOMATIC
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index cc967005b9e3..d028743e3d87 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -763,7 +763,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink/kitchen, (-16))
/obj/structure/curtain/attackby(obj/item/W, mob/user)
if (istype(W, /obj/item/toy/crayon))
- color = input(user,"","Choose Color",color) as color
+ color = tgui_color_picker(user, "", "Choose Color", color)
else
return ..()
diff --git a/code/game/say.dm b/code/game/say.dm
index 2afb765f6066..83f14d6e7452 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -164,7 +164,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
/// Transforms the speech emphasis mods from [/atom/movable/proc/say_emphasis] into the appropriate HTML tags. Includes escaping.
#define ENCODE_HTML_EMPHASIS(input, char, html, varname) \
var/static/regex/##varname = regex("(?$1[html]>")
+ input = varname.Replace_char(input, "<[html]>$1[html]>") //zero-width space to force maptext to respect closing tags.
/// Scans the input sentence for speech emphasis modifiers, notably |italics|, +bold+, and _underline_ -mothblocks
/atom/movable/proc/say_emphasis(input)
diff --git a/code/modules/admin/greyscale_modify_menu.dm b/code/modules/admin/greyscale_modify_menu.dm
index e9936723f78a..d0a5cf921bae 100644
--- a/code/modules/admin/greyscale_modify_menu.dm
+++ b/code/modules/admin/greyscale_modify_menu.dm
@@ -147,12 +147,12 @@
if("pick_color")
var/group = params["color_index"]
- var/new_color = input(
+ var/new_color = tgui_color_picker(
usr,
"Choose color for greyscale color group [group]:",
"Greyscale Modification Menu",
split_colors[group]
- ) as color|null
+ )
if(new_color)
split_colors[group] = new_color
queue_refresh()
diff --git a/code/modules/admin/view_variables/filterrific.dm b/code/modules/admin/view_variables/filterrific.dm
index 0bd9f51c114f..b1fbee3dd4f5 100644
--- a/code/modules/admin/view_variables/filterrific.dm
+++ b/code/modules/admin/view_variables/filterrific.dm
@@ -67,7 +67,7 @@
target.add_filter(params["name"], old_filter_data["priority"], new_filter_data)
. = TRUE
if("modify_color_value")
- var/new_color = input(usr, "Pick new filter color", "Filteriffic Colors!") as color|null
+ var/new_color = tgui_color_picker(usr, "Pick new filter color", "Filteriffic Colors!")
if(new_color)
target.transition_filter(params["name"], list("color" = new_color), 4)
. = TRUE
diff --git a/code/modules/admin/view_variables/get_variables.dm b/code/modules/admin/view_variables/get_variables.dm
index c59391371dff..ef795e71f974 100644
--- a/code/modules/admin/view_variables/get_variables.dm
+++ b/code/modules/admin/view_variables/get_variables.dm
@@ -331,7 +331,7 @@
.["value"] = D
if(VV_COLOR)
- .["value"] = input("Enter new color:", "Color", current_value) as color|null
+ .["value"] = tgui_color_picker("Enter new color:", "Color", current_value)
if(.["value"] == null)
.["class"] = null
return
diff --git a/code/modules/art/paintings.dm b/code/modules/art/paintings.dm
index 26935a126136..f6b72fa22744 100644
--- a/code/modules/art/paintings.dm
+++ b/code/modules/art/paintings.dm
@@ -188,7 +188,7 @@
return FALSE
//I'd have this done inside the signal, but that'd have to be asynced,
//while we want the UI to be updated after the color is chosen, not before.
- var/chosen_color = input(user, "Pick new color", painting_implement, params["old_color"]) as color|null
+ var/chosen_color = tgui_color_picker(user, "Pick new color", painting_implement, params["old_color"])
if(!chosen_color || IS_DEAD_OR_INCAP(user) || !user.is_holding(painting_implement))
return FALSE
SEND_SIGNAL(painting_implement, COMSIG_PAINTING_TOOL_PALETTE_COLOR_CHANGED, chosen_color, params["color_index"])
diff --git a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
index 482386f1a165..5ec23a4f60a2 100644
--- a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
+++ b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
@@ -161,6 +161,8 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm)
data["dangerLevel"] = danger_level
data["atmosAlarm"] = !!my_area.active_alarms[ALARM_ATMOS]
data["fireAlarm"] = my_area.fire
+ data["faultStatus"] = my_area.fault_status
+ data["faultLocation"] = my_area.fault_location
var/turf/turf = get_turf(src)
var/datum/gas_mixture/environment = turf.return_air()
diff --git a/code/modules/client/client_colour.dm b/code/modules/client/client_colour.dm
index 9aed1d300727..1991efff9d40 100644
--- a/code/modules/client/client_colour.dm
+++ b/code/modules/client/client_colour.dm
@@ -38,35 +38,32 @@
/datum/client_colour/Destroy()
if(!QDELETED(owner))
owner.client_colours -= src
- if(fade_out)
- owner.animate_client_colour(fade_out)
- else
- owner.update_client_colour()
+ owner.animate_client_colour(fade_out)
owner = null
return ..()
///Sets a new colour, then updates the owner's screen colour.
/datum/client_colour/proc/update_colour(new_colour, anim_time, easing = 0)
colour = new_colour
- if(anim_time)
- owner.animate_client_colour(anim_time, easing)
- else
- owner.update_client_colour()
+ owner.animate_client_colour(anim_time, easing)
/**
* Adds an instance of colour_type to the mob's client_colours list
* colour_type - a typepath (subtyped from /datum/client_colour)
*/
-/mob/proc/add_client_colour(colour_type)
- if(!ispath(colour_type, /datum/client_colour) || QDELING(src))
+/mob/proc/add_client_colour(colour_type_or_datum)
+ if(QDELING(src))
return
+ var/datum/client_colour/colour
+ if(istype(colour_type_or_datum, /datum/client_colour))
+ colour = colour_type_or_datum
+ else if(ispath(colour_type_or_datum, /datum/client_colour))
+ colour = new colour_type_or_datum(src)
+ else
+ CRASH("Invalid colour type or datum for add_client_color: [colour_type_or_datum || "null"]")
- var/datum/client_colour/colour = new colour_type(src)
BINARY_INSERT(colour, client_colours, /datum/client_colour, colour, priority, COMPARE_KEY)
- if(colour.fade_in)
- animate_client_colour(colour.fade_in)
- else
- update_client_colour()
+ animate_client_colour(colour.fade_in)
return colour
/**
@@ -77,8 +74,7 @@
if(!ispath(colour_type, /datum/client_colour))
return
- for(var/cc in client_colours)
- var/datum/client_colour/colour = cc
+ for(var/datum/client_colour/colour as anything in client_colours)
if(colour.type == colour_type)
qdel(colour)
break
@@ -123,31 +119,49 @@
};\
target = _our_colour\
+#define CLIENT_COLOR_FILTER_KEY "fake_client_color"
/**
* Resets the mob's client.color to null, and then reapplies a new color based
* on the client_colour datums it currently has.
*/
/mob/proc/update_client_colour()
- if(!client)
+ if(isnull(hud_used))
return
- client.color = ""
- if(!client_colours.len)
- return
- MIX_CLIENT_COLOUR(client.color)
+
+ var/new_color = ""
+ if(length(client_colours))
+ MIX_CLIENT_COLOUR(new_color)
+
+ for(var/atom/movable/screen/plane_master/game_plane as anything in hud_used.get_true_plane_masters(RENDER_PLANE_GAME))
+ if(new_color)
+ game_plane.add_filter(CLIENT_COLOR_FILTER_KEY, 2, color_matrix_filter(new_color))
+ else
+ game_plane.remove_filter(CLIENT_COLOR_FILTER_KEY)
///Works similarly to 'update_client_colour', but animated.
-/mob/proc/animate_client_colour(anim_time = 20, anim_easing = 0)
- if(!client)
- return
- if(!client_colours.len)
- animate(client, color = "", time = anim_time, easing = anim_easing)
+/mob/proc/animate_client_colour(anim_time = 2 SECONDS, anim_easing = NONE)
+ if(anim_time <= 0)
+ return update_client_colour()
+ if(isnull(hud_used))
return
- MIX_CLIENT_COLOUR(var/anim_colour)
- animate(client, color = anim_colour, time = anim_time, easing = anim_easing)
+
+ var/anim_color = ""
+ if(length(client_colours))
+ MIX_CLIENT_COLOUR(anim_color)
+
+ for(var/atom/movable/screen/plane_master/game_plane as anything in hud_used.get_true_plane_masters(RENDER_PLANE_GAME))
+ if(anim_color)
+ game_plane.add_filter(CLIENT_COLOR_FILTER_KEY, 2, color_matrix_filter())
+ game_plane.transition_filter(CLIENT_COLOR_FILTER_KEY, color_matrix_filter(anim_color), anim_time, anim_easing)
+ else
+ game_plane.transition_filter(CLIENT_COLOR_FILTER_KEY, color_matrix_filter(), anim_time, anim_easing)
+ // This leaves a blank color filter on the hud which is, fine I guess?
#undef MIX_CLIENT_COLOUR
+#undef CLIENT_COLOR_FILTER_KEY
+
/datum/client_colour/glass_colour
priority = PRIORITY_LOW
colour = "red"
@@ -218,6 +232,9 @@
override = TRUE
colour = list(0.8,0,0,0, 0,0,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0)
+/datum/client_colour/temp
+ priority = PRIORITY_HIGH
+
#undef PRIORITY_ABSOLUTE
#undef PRIORITY_HIGH
#undef PRIORITY_NORMAL
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 49d434e5cb7e..9ad99ddc38b1 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -272,12 +272,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/default_value = read_preference(requested_preference.type)
// Yielding
- var/new_color = input(
+ var/new_color = tgui_color_picker(
usr,
"Select new color",
null,
default_value || COLOR_WHITE,
- ) as color | null
+ )
if (!new_color)
return FALSE
diff --git a/code/modules/client/preferences/screentips.dm b/code/modules/client/preferences/screentips.dm
index 5566f28b539f..fe2578233751 100644
--- a/code/modules/client/preferences/screentips.dm
+++ b/code/modules/client/preferences/screentips.dm
@@ -35,4 +35,4 @@
client.mob?.hud_used?.screentip_color = value
/datum/preference/color/screentip_color/create_default_value()
- return "#ffd391"
+ return LIGHT_COLOR_FAINT_BLUE
diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
index 032a7bb8d243..6d24e28e4f97 100644
--- a/code/modules/client/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -143,7 +143,7 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
set category = "Server"
if(IsAdminAdvancedProcCall())
return
- var/newColor = input(src, "Please select the new player OOC color.", "OOC color") as color|null
+ var/newColor = tgui_color_picker(src, "Please select the new player OOC color.", "OOC color")
if(isnull(newColor))
return
if(!check_rights(R_FUN))
diff --git a/code/modules/clothing/head/cone.dm b/code/modules/clothing/head/cone.dm
index 59d8cfe0d271..0bd60b633da5 100644
--- a/code/modules/clothing/head/cone.dm
+++ b/code/modules/clothing/head/cone.dm
@@ -1,7 +1,7 @@
/obj/item/clothing/head/cone
desc = "This cone is trying to warn you of something!"
name = "warning cone"
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/service/janitor.dmi'
worn_icon = 'icons/mob/clothing/head/utility.dmi'
icon_state = "cone"
inhand_icon_state = null
diff --git a/code/modules/clothing/head/wig.dm b/code/modules/clothing/head/wig.dm
index 08b160fb5f8f..d1ebedd1e13a 100644
--- a/code/modules/clothing/head/wig.dm
+++ b/code/modules/clothing/head/wig.dm
@@ -50,7 +50,7 @@
/obj/item/clothing/head/wig/attack_self(mob/user)
var/new_style = tgui_input_list(user, "Select a hairstyle", "Wig Styling", GLOB.roundstart_hairstyles_list - "Bald")
- var/newcolor = adjustablecolor ? input(usr,"","Choose Color",color) as color|null : null
+ var/newcolor = adjustablecolor ? tgui_color_picker(usr, "", "Choose Color", color) : null
if(!user.can_perform_action(src))
return
if(new_style && new_style != hairstyle)
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index b0ef5a29c703..c4e8f9133175 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -23,6 +23,7 @@
var/lace_time = 5 SECONDS
///An active alert
var/datum/weakref/our_alert_ref
+ var/footprint_sprite = FOOTPRINT_SPRITE_SHOES
/datum/armor/clothing_shoes
bio = 50
diff --git a/code/modules/events/wizard/rpgtitles.dm b/code/modules/events/wizard/rpgtitles.dm
index b2890150c93d..37eae2459f62 100644
--- a/code/modules/events/wizard/rpgtitles.dm
+++ b/code/modules/events/wizard/rpgtitles.dm
@@ -42,7 +42,7 @@ GLOBAL_DATUM(rpgtitle_controller, /datum/rpgtitle_controller)
//we must prepare for the mother of all strings
new_crewmember.maptext_height = max(new_crewmember.maptext_height, 32)
- new_crewmember.maptext_width = max(new_crewmember.maptext_width, 80)
+ new_crewmember.maptext_width = max(new_crewmember.maptext_width, 112)
new_crewmember.maptext_x = -24 - new_crewmember.base_pixel_x
new_crewmember.maptext_y = -32
@@ -90,7 +90,7 @@ GLOBAL_DATUM(rpgtitle_controller, /datum/rpgtitle_controller)
maptext_title += "[applicable_biotypes[iteration][1]] "
//mother of all strings...
- new_crewmember.maptext = "Level [rand(1, 100)] [maptext_title] "
+ new_crewmember.maptext = MAPTEXT_TINY_UNICODE("Level [rand(1, 100)] [maptext_title] ")
if(!(job.job_flags & JOB_CREW_MEMBER))
return
diff --git a/code/modules/food_and_drinks/recipes/drinks/drinks_alcoholic.dm b/code/modules/food_and_drinks/recipes/drinks/drinks_alcoholic.dm
index df0715a4bd98..091ad220c5c8 100644
--- a/code/modules/food_and_drinks/recipes/drinks/drinks_alcoholic.dm
+++ b/code/modules/food_and_drinks/recipes/drinks/drinks_alcoholic.dm
@@ -168,6 +168,10 @@
results = list(/datum/reagent/consumable/ethanol/whiskeysoda = 3)
required_reagents = list(/datum/reagent/consumable/ethanol/whiskey = 2, /datum/reagent/consumable/sodawater = 1)
+/datum/chemical_reaction/drink/wellcheers
+ results = list(/datum/reagent/consumable/wellcheers = 5)
+ required_reagents = list(/datum/reagent/consumable/berryjuice = 1, /datum/reagent/consumable/watermelonjuice = 1, /datum/reagent/consumable/sodawater = 1, /datum/reagent/consumable/salt = 1, /datum/reagent/consumable/ethanol/absinthe = 1)
+
/datum/chemical_reaction/drink/black_russian
results = list(/datum/reagent/consumable/ethanol/black_russian = 5)
required_reagents = list(/datum/reagent/consumable/ethanol/vodka = 3, /datum/reagent/consumable/ethanol/kahlua = 2)
@@ -570,3 +574,15 @@
/datum/chemical_reaction/drink/gin_garden
results = list(/datum/reagent/consumable/ethanol/gin_garden = 15)
required_reagents = list(/datum/reagent/consumable/limejuice = 1, /datum/reagent/consumable/sugar = 1, /datum/reagent/consumable/ethanol/gin = 3, /datum/reagent/consumable/cucumberjuice = 3, /datum/reagent/consumable/sol_dry = 5, /datum/reagent/consumable/ice = 2)
+
+/datum/chemical_reaction/drink/telepole
+ results = list(/datum/reagent/consumable/ethanol/telepole = 5)
+ required_reagents = list(/datum/reagent/consumable/ethanol/wine_voltaic = 1, /datum/reagent/consumable/ethanol/dark_and_stormy = 2, /datum/reagent/consumable/ethanol/sake = 1)
+ mix_message = "You swear you saw a spark fly from the glass..."
+
+/datum/chemical_reaction/drink/pod_tesla
+ results = list(/datum/reagent/consumable/ethanol/pod_tesla = 15)
+ required_reagents = list(/datum/reagent/consumable/ethanol/telepole = 5, /datum/reagent/consumable/ethanol/brave_bull = 3, /datum/reagent/consumable/ethanol/admiralty = 5)
+ mix_message = "Arcs of lightning fly from the mixture."
+ mix_sound = 'sound/weapons/zapbang.ogg'
+
diff --git a/code/modules/industrial_lift/elevator/elevator_indicator.dm b/code/modules/industrial_lift/elevator/elevator_indicator.dm
index 87ff496e3a44..77de8f5fed1e 100644
--- a/code/modules/industrial_lift/elevator/elevator_indicator.dm
+++ b/code/modules/industrial_lift/elevator/elevator_indicator.dm
@@ -19,10 +19,10 @@
light_color = LIGHT_COLOR_DARK_BLUE
luminosity = 1
- maptext_x = 17
- maptext_y = 21
- maptext_width = 4
- maptext_height = 8
+ maptext_x = 18
+ maptext_y = 20
+ maptext_width = 8
+ maptext_height = 16
/// What specific_lift_id do we link with?
var/linked_elevator_id
@@ -150,7 +150,7 @@
return
set_light(l_on = TRUE)
- maptext = {"[current_lift_floor]
"}
+ maptext = "[current_lift_floor]
"
/obj/machinery/lift_indicator/update_overlays()
. = ..()
diff --git a/code/modules/mob/living/basic/guardian/guardian.dm b/code/modules/mob/living/basic/guardian/guardian.dm
index 854489fccce6..895a31eb2ecb 100644
--- a/code/modules/mob/living/basic/guardian/guardian.dm
+++ b/code/modules/mob/living/basic/guardian/guardian.dm
@@ -136,7 +136,7 @@
/mob/living/basic/guardian/proc/guardian_recolour()
if (isnull(client))
return
- var/chosen_guardian_colour = input(src, "What would you like your colour to be?", "Choose Your Colour", "#ffffff") as color|null
+ var/chosen_guardian_colour = tgui_color_picker(src, "What would you like your colour to be?", "Choose Your Colour", "#ffffff")
if (isnull(chosen_guardian_colour)) //redo proc until we get a color
to_chat(src, span_warning("Invalid colour, please try again."))
return guardian_recolour()
diff --git a/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm b/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm
index 9497d8dd284d..1f41a95af858 100644
--- a/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm
+++ b/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm
@@ -100,7 +100,7 @@
/// Select scale colour with the colour picker
/mob/living/basic/space_dragon/proc/select_colour()
- chosen_colour = input(src, "What colour would you like to be?" ,"Colour Selection", COLOR_WHITE) as color|null
+ chosen_colour = tgui_color_picker(src, "What colour would you like to be?", "Colour Selection", COLOR_WHITE)
if(!chosen_colour) // Redo proc until we get a color
to_chat(src, span_warning("Not a valid colour, please try again."))
select_colour()
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index b61bd33abf2f..07aee95636fe 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -1008,6 +1008,9 @@
/proc/cmp_organ_slot_asc(slot_a, slot_b)
return GLOB.organ_process_order.Find(slot_a) - GLOB.organ_process_order.Find(slot_b)
+/mob/living/carbon/proc/get_footprint_sprite()
+ return FOOTPRINT_SPRITE_PAWS
+
/mob/living/carbon/vv_get_dropdown()
. = ..()
VV_DROPDOWN_OPTION("", "---------")
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 797d34e56bca..39a8f4487f69 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -24,7 +24,7 @@
RegisterSignal(src, COMSIG_COMPONENT_CLEAN_FACE_ACT, PROC_REF(clean_face))
AddComponent(/datum/component/personal_crafting)
AddElement(/datum/element/footstep, FOOTSTEP_MOB_HUMAN, 1, -6)
- AddComponent(/datum/component/bloodysoles/feet)
+ AddComponent(/datum/component/bloodysoles/feet, FOOTPRINT_SPRITE_SHOES)
AddElement(/datum/element/ridable, /datum/component/riding/creature/human)
AddElement(/datum/element/strippable, GLOB.strippable_human_items, TYPE_PROC_REF(/mob/living/carbon/human/, should_strip))
var/static/list/loc_connections = list(
@@ -345,6 +345,12 @@
var/obj/item/bodypart/the_part = isbodypart(target_zone) ? target_zone : get_bodypart(check_zone(target_zone)) //keep these synced
to_chat(user, span_alert("There is no exposed flesh or thin material on [p_their()] [the_part.name]."))
+/mob/living/carbon/human/get_footprint_sprite()
+ var/obj/item/bodypart/leg/L = get_bodypart(BODY_ZONE_R_LEG) || get_bodypart(BODY_ZONE_L_LEG)
+ return shoes?.footprint_sprite || L?.footprint_sprite
+
+#define CHECK_PERMIT(item) (item && item.item_flags & NEEDS_PERMIT)
+
/mob/living/carbon/human/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null)
if(judgement_criteria & JUDGE_EMAGGED)
return 10 //Everyone is a criminal!
@@ -911,7 +917,7 @@
if(diff < 0) //Taking damage, not healing
return diff * physiology.stamina_mod
return diff
-
+
/mob/living/carbon/human/adjust_nutrition(change) //Honestly FUCK the oldcoders for putting nutrition on /mob someone else can move it up because holy hell I'd have to fix SO many typechecks
if(HAS_TRAIT(src, TRAIT_NOHUNGER))
return FALSE
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 12650935847b..d3fb6dbd4eab 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -2598,7 +2598,7 @@ GLOBAL_LIST_EMPTY(fire_appearances)
if(picked_theme == "Random")
picked_theme = null //holopara code handles not having a theme by giving a random one
var/picked_name = tgui_input_text(admin, "Name the guardian, leave empty to let player name it.", "Guardian Controller")
- var/picked_color = input(admin, "Set the guardian's color, cancel to let player set it.", "Guardian Controller", "#ffffff") as color|null
+ var/picked_color = tgui_color_picker(admin, "Set the guardian's color, cancel to let player set it.", "Guardian Controller", "#ffffff")
if(tgui_alert(admin, "Confirm creation.", "Guardian Controller", list("Yes", "No")) != "Yes")
return
var/mob/living/basic/guardian/summoned_guardian = new picked_type(src, picked_theme)
diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm
index d11b97a739fd..a80bba158ca7 100644
--- a/code/modules/mod/modules/modules_general.dm
+++ b/code/modules/mod/modules/modules_general.dm
@@ -281,7 +281,7 @@
/obj/item/mod/module/flashlight/configure_edit(key, value)
switch(key)
if("light_color")
- value = input(usr, "Pick new light color", "Flashlight Color") as color|null
+ value = tgui_color_picker(usr, "Pick new light color", "Flashlight Color")
if(!value)
return
if(is_color_dark(value, 50))
diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm
index fb90a50604f8..697371c91d8c 100644
--- a/code/modules/modular_computers/computers/item/computer_ui.dm
+++ b/code/modules/modular_computers/computers/item/computer_ui.dm
@@ -155,7 +155,7 @@
var/mob/user = usr
var/new_color
while(!new_color)
- new_color = input(user, "Choose a new color for [src]'s flashlight.", "Light Color",light_color) as color|null
+ new_color = tgui_color_picker(user, "Choose a new color for [src]'s flashlight.", "Light Color", light_color)
if(!new_color)
return
if(is_color_dark(new_color, 50) ) //Colors too dark are rejected
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index 5d69b347046a..f26118c7ca81 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -602,5 +602,5 @@
/obj/item/borg/upgrade/modkit/tracer/adjustable/proc/choose_bolt_color(mob/user)
set waitfor = FALSE
- var/new_color = input(user,"","Choose Color",bolt_color) as color|null
+ var/new_color = tgui_color_picker(user, "", "Choose Color", bolt_color)
bolt_color = new_color || bolt_color
diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
index cba0167a996f..8daeefa8cdd4 100644
--- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
@@ -505,6 +505,7 @@
/datum/reagent/consumable/shamblers,
/datum/reagent/consumable/spacemountainwind,
/datum/reagent/consumable/sodawater,
+ /datum/reagent/consumable/sol_dry,
/datum/reagent/consumable/space_up,
/datum/reagent/consumable/sugar,
/datum/reagent/consumable/tea,
diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
index 2239574f258c..2b6a402c22cf 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -3695,5 +3695,98 @@
doll.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, doll.get_body_temp_normal())
..()
+/datum/reagent/consumable/ethanol/wine_voltaic
+ name = "Voltaic Yellow Wine"
+ description = "Electrically charged wine. Recharges etherials, but also nontoxic."
+ boozepwr = 30
+ color = "#FFAA00"
+ taste_description = "static with a hint of sweetness"
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+
+/datum/glass_style/drinking_glass/wine_voltaic
+ required_drink_type = /datum/reagent/consumable/ethanol/wine_voltaic
+ name = "Voltaic Yellow Wine"
+ desc = "Electrically charged wine. Recharges etherials, but also nontoxic."
+ icon = 'icons/obj/drinks/mixed_drinks.dmi'
+ icon_state = "wine_voltaic"
+
+/datum/reagent/consumable/ethanol/wine_voltaic/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) //can't be on life because of the way blood works.
+ . = ..()
+ if(!(methods & (INGEST|INJECT|PATCH)) || !iscarbon(exposed_mob))
+ return
+
+ var/mob/living/carbon/exposed_carbon = exposed_mob
+ var/obj/item/organ/internal/stomach/ethereal/stomach = exposed_carbon.get_organ_slot(ORGAN_SLOT_STOMACH)
+ if(istype(stomach))
+ stomach.adjust_charge(reac_volume * 3)
+
+/datum/reagent/consumable/ethanol/telepole
+ name = "Telepole"
+ description = "A grounding rod in the form of a drink. Recharges etherials, and gives temporary shock resistance."
+ boozepwr = 50
+ color = "#b300ff"
+ quality = DRINK_NICE
+ taste_description = "the howling storm"
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+
+/datum/glass_style/drinking_glass/telepole
+ required_drink_type = /datum/reagent/consumable/ethanol/telepole
+ name = "Telepole"
+ desc = "A liquid grounding rod. Recharges etherials and grants temporary shock resistance."
+ icon = 'icons/obj/drinks/mixed_drinks.dmi'
+ icon_state = "telepole"
+
+/datum/reagent/consumable/ethanol/telepole/on_mob_metabolize(mob/living/affected_mob)
+ . = ..()
+ ADD_TRAIT(affected_mob, TRAIT_SHOCKIMMUNE, type)
+
+/datum/reagent/consumable/ethanol/telepole/on_mob_end_metabolize(mob/living/affected_mob)
+ REMOVE_TRAIT(affected_mob, TRAIT_SHOCKIMMUNE, type)
+ return ..()
+
+/datum/reagent/consumable/ethanol/telepole/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) //can't be on life because of the way blood works.
+ . = ..()
+ if(!(methods & (INGEST|INJECT|PATCH)) || !iscarbon(exposed_mob))
+ return
+
+ var/mob/living/carbon/exposed_carbon = exposed_mob
+ var/obj/item/organ/internal/stomach/ethereal/stomach = exposed_carbon.get_organ_slot(ORGAN_SLOT_STOMACH)
+ if(istype(stomach))
+ stomach.adjust_charge(reac_volume * 2)
+
+/datum/reagent/consumable/ethanol/pod_tesla
+ name = "Pod Tesla"
+ description = "Ride the lightning! Recharges etherials, suppresses phobias, and gives strong temporary shock resistance."
+ boozepwr = 80
+ color = "#00fbff"
+ quality = DRINK_FANTASTIC
+ taste_description = "victory, with a hint of insanity"
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+
+/datum/glass_style/drinking_glass/pod_tesla
+ required_drink_type = /datum/reagent/consumable/ethanol/pod_tesla
+ name = "Pod Tesla"
+ desc = "Ride the lightning! Recharges etherials, suppresses phobias, and grants strong temporary shock resistance."
+ icon = 'icons/obj/drinks/mixed_drinks.dmi'
+ icon_state = "pod_tesla"
+
+/datum/reagent/consumable/ethanol/pod_tesla/on_mob_metabolize(mob/living/affected_mob)
+ ..()
+ affected_mob.add_traits(list(TRAIT_SHOCKIMMUNE,TRAIT_TESLA_SHOCKIMMUNE,TRAIT_FEARLESS), type)
+
+
+/datum/reagent/consumable/ethanol/pod_tesla/on_mob_end_metabolize(mob/living/affected_mob)
+ . = ..()
+ affected_mob.remove_traits(list(TRAIT_SHOCKIMMUNE,TRAIT_TESLA_SHOCKIMMUNE,TRAIT_FEARLESS), type)
+
+/datum/reagent/consumable/ethanol/pod_tesla/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) //can't be on life because of the way blood works.
+ . = ..()
+ if(!(methods & (INGEST|INJECT|PATCH)) || !iscarbon(exposed_mob))
+ return
+
+ var/mob/living/carbon/exposed_carbon = exposed_mob
+ var/obj/item/organ/internal/stomach/ethereal/stomach = exposed_carbon.get_organ_slot(ORGAN_SLOT_STOMACH)
+ if(istype(stomach))
+ stomach.adjust_charge(reac_volume * 5)
#undef ALCOHOL_EXPONENT
#undef ALCOHOL_THRESHOLD_MODIFIER
diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
index 2e3e7efd4543..ecc1c46be084 100644
--- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
@@ -866,6 +866,24 @@
..()
. = TRUE
+/datum/reagent/consumable/wellcheers
+ name = "Wellcheers"
+ description = "A strange purple drink, smelling of saltwater. Somewhere in the distance, you hear seagulls."
+ color = "#762399" // rgb: 118, 35, 153
+ taste_description = "grapes and the fresh open sea"
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+
+/datum/reagent/consumable/wellcheers/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
+ affected_mob.adjust_drowsiness(3 SECONDS * REM * seconds_per_tick)
+ switch(affected_mob.mob_mood.mood_level)
+ if (MOOD_LEVEL_SAD4 to MOOD_LEVEL_SAD2)
+ affected_mob.stamina.adjust(-3 * REM * seconds_per_tick)//Monkestation Edit: custom stamina
+ if (MOOD_LEVEL_SAD2 to MOOD_LEVEL_HAPPY2)
+ affected_mob.add_mood_event("wellcheers", /datum/mood_event/wellcheers)
+ if (MOOD_LEVEL_HAPPY2 to MOOD_LEVEL_HAPPY4)
+ affected_mob.adjustBruteLoss(-1.5 * REM * seconds_per_tick, 0)
+ return ..()
+
/datum/reagent/consumable/monkey_energy
name = "Monkey Energy"
description = "The only drink that will make you unleash the ape."
diff --git a/code/modules/reagents/reagent_containers/cups/_cup.dm b/code/modules/reagents/reagent_containers/cups/_cup.dm
index 9ac989742b2b..43ca9fec4e2f 100644
--- a/code/modules/reagents/reagent_containers/cups/_cup.dm
+++ b/code/modules/reagents/reagent_containers/cups/_cup.dm
@@ -344,7 +344,7 @@
/obj/item/reagent_containers/cup/bucket
name = "bucket"
desc = "It's a bucket."
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/service/janitor.dmi'
worn_icon = 'icons/mob/clothing/head/utility.dmi'
icon_state = "bucket"
inhand_icon_state = "bucket"
diff --git a/code/modules/reagents/reagent_containers/cups/drinks.dm b/code/modules/reagents/reagent_containers/cups/drinks.dm
index f87fdc35450d..7ff6ca1ab291 100644
--- a/code/modules/reagents/reagent_containers/cups/drinks.dm
+++ b/code/modules/reagents/reagent_containers/cups/drinks.dm
@@ -138,14 +138,14 @@
desc = "Careful, cold ice, do not chew."
custom_price = PAYCHECK_LOWER * 0.6
icon_state = "icecup"
- list_reagents = list(/datum/reagent/consumable/ice = 30)
+ //list_reagents = list(/datum/reagent/consumable/ice = 30) Monkestation Removal: Ice was Instantly melting from vending machines
spillable = TRUE
isGlass = FALSE
/obj/item/reagent_containers/cup/glass/ice/prison
name = "dirty ice cup"
desc = "Either Nanotrasen's water supply is contaminated, or this machine actually vends lemon, chocolate, and cherry snow cones."
- list_reagents = list(/datum/reagent/consumable/ice = 25, /datum/reagent/consumable/liquidgibs = 5)
+ //list_reagents = list(/datum/reagent/consumable/ice = 25, /datum/reagent/consumable/liquidgibs = 5) Monkestation Removal: Ice was Instantly melting from vending machines
/obj/item/reagent_containers/cup/glass/mug // parent type is literally just so empty mug sprites are a thing
name = "mug"
diff --git a/code/modules/reagents/reagent_containers/cups/glassbottle.dm b/code/modules/reagents/reagent_containers/cups/glassbottle.dm
index 9128e71f2b59..9710eab6d2d4 100644
--- a/code/modules/reagents/reagent_containers/cups/glassbottle.dm
+++ b/code/modules/reagents/reagent_containers/cups/glassbottle.dm
@@ -524,6 +524,14 @@
list_reagents = list(/datum/reagent/consumable/ethanol/applejack = 100)
drink_type = FRUIT
+/obj/item/reagent_containers/cup/glass/bottle/wine_voltaic
+ name = "Voltaic Yellow Wine"
+ desc = "Electrically infused wine! Recharges etherials, safe for consumption."
+ custom_price = PAYCHECK_CREW
+ icon_state = "wine_voltaic_bottle"
+ list_reagents = list(/datum/reagent/consumable/ethanol/wine_voltaic = 100)
+ drink_type = FRUIT
+
/obj/item/reagent_containers/cup/glass/bottle/champagne
name = "Eau d' Dandy Brut Champagne"
desc = "Finely sourced from only the most pretentious French vineyards."
@@ -784,7 +792,7 @@
/obj/item/reagent_containers/cup/glass/bottle/pruno
name = "pruno mix"
desc = "A trash bag filled with fruit, sugar, yeast, and water, pulped together into a pungent slurry to be fermented in an enclosed space, traditionally the toilet. Security would love to confiscate this, one of the many things wrong with them."
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/service/janitor.dmi'
icon_state = "trashbag"
list_reagents = list(/datum/reagent/consumable/prunomix = 50)
var/fermentation_time = 30 SECONDS /// time it takes to ferment
diff --git a/code/modules/reagents/reagent_containers/cups/soda.dm b/code/modules/reagents/reagent_containers/cups/soda.dm
index 3a84373471d2..6c350e2a4d0b 100644
--- a/code/modules/reagents/reagent_containers/cups/soda.dm
+++ b/code/modules/reagents/reagent_containers/cups/soda.dm
@@ -251,6 +251,21 @@
list_reagents = list(/datum/reagent/consumable/shamblers = 30)
drink_type = SUGAR | JUNKFOOD
+/obj/item/reagent_containers/cup/soda_cans/shamblers/eldritch
+ name = "Shambler's juice Eldritch Energy!"
+ desc = "~J'I'CE!~"
+ icon_state = "shamblerseldritch"
+ volume = 40
+ list_reagents = list(/datum/reagent/consumable/shamblers = 30, /datum/reagent/eldritch = 5)
+ drink_type = SUGAR | JUNKFOOD
+
+/obj/item/reagent_containers/cup/soda_cans/wellcheers
+ name = "Wellcheers Juice"
+ desc = "A strange purple drink, smelling of saltwater. Somewhere in the distance, you hear seagulls."
+ icon_state = "wellcheers"
+ list_reagents = list(/datum/reagent/consumable/wellcheers = 30)
+ drink_type = SUGAR | JUNKFOOD
+
/obj/item/reagent_containers/cup/soda_cans/grey_bull
name = "Grey Bull"
desc = "Grey Bull, it gives you gloves!"
diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm
index 93e5255f85eb..419455054e33 100644
--- a/code/modules/reagents/reagent_containers/spray.dm
+++ b/code/modules/reagents/reagent_containers/spray.dm
@@ -1,7 +1,7 @@
/obj/item/reagent_containers/spray
name = "spray bottle"
desc = "A spray bottle, with an unscrewable top."
- icon = 'icons/obj/janitor.dmi'
+ icon = 'icons/obj/service/janitor.dmi'
icon_state = "sprayer_large"
inhand_icon_state = "cleaner"
worn_icon_state = "spraybottle"
diff --git a/code/modules/research/xenobiology/crossbreeding/_clothing.dm b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
index 9a807201f669..03ed99dceccb 100644
--- a/code/modules/research/xenobiology/crossbreeding/_clothing.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
@@ -74,7 +74,7 @@ Slimecrossing Armor
if(!IsAvailable(feedback = TRUE))
return
var/obj/item/clothing/glasses/prism_glasses/glasses = target
- var/new_color = input(owner, "Choose the lens color:", "Color change",glasses.glasses_color) as color|null
+ var/new_color = tgui_color_picker(owner, "Choose the lens color:", "Color change", glasses.glasses_color)
if(!new_color)
return
glasses.glasses_color = new_color
diff --git a/code/modules/research/xenobiology/crossbreeding/prismatic.dm b/code/modules/research/xenobiology/crossbreeding/prismatic.dm
index 9cfc4542d526..5d6567ed15a6 100644
--- a/code/modules/research/xenobiology/crossbreeding/prismatic.dm
+++ b/code/modules/research/xenobiology/crossbreeding/prismatic.dm
@@ -115,7 +115,7 @@ Prismatic extracts:
colour = "rainbow"
/obj/item/slimecross/prismatic/rainbow/attack_self(mob/user)
- var/newcolor = input(user, "Choose the slime color:", "Color change",paintcolor) as color|null
+ var/newcolor = tgui_color_picker(user, "Choose the slime color:", "Color change", paintcolor)
if(user.get_active_held_item() != src || user.stat != CONSCIOUS || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
return
if(!newcolor)
diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm
index f441643ba768..55d42f4993d1 100644
--- a/code/modules/surgery/bodyparts/parts.dm
+++ b/code/modules/surgery/bodyparts/parts.dm
@@ -323,6 +323,8 @@
var/digitigrade_id
/// Used solely by digitigrade limbs to remember what their old limb ID was.
var/old_limb_id
+ /// Used by the bloodysoles component to make footprints
+ var/footprint_sprite = FOOTPRINT_SPRITE_SHOES
biological_state = BIO_STANDARD_JOINTED
/obj/item/bodypart/leg/Destroy()
@@ -407,6 +409,7 @@
unarmed_damage_low = 2
unarmed_damage_high = 3
unarmed_stun_threshold = 4
+ footprint_sprite = FOOTPRINT_SPRITE_PAWS
/obj/item/bodypart/leg/left/alien
icon = 'icons/mob/species/alien/bodyparts.dmi'
@@ -501,6 +504,7 @@
unarmed_damage_low = 2
unarmed_damage_high = 3
unarmed_stun_threshold = 4
+ footprint_sprite = FOOTPRINT_SPRITE_PAWS
/obj/item/bodypart/leg/right/alien
icon = 'icons/mob/species/alien/bodyparts.dmi'
diff --git a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm
index 233afcd9b19b..08622f070176 100644
--- a/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/species_parts/lizard_bodyparts.dm
@@ -35,9 +35,11 @@
limb_id = SPECIES_LIZARD
can_be_digitigrade = TRUE
digitigrade_id = "digitigrade"
+ footprint_sprite = FOOTPRINT_SPRITE_CLAWS
/obj/item/bodypart/leg/right/lizard
icon_greyscale = 'icons/mob/species/lizard/bodyparts.dmi'
limb_id = SPECIES_LIZARD
can_be_digitigrade = TRUE
digitigrade_id = "digitigrade"
+ footprint_sprite = FOOTPRINT_SPRITE_CLAWS
diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm
index 8aa349be967c..17ede9fa5948 100644
--- a/code/modules/surgery/organs/eyes.dm
+++ b/code/modules/surgery/organs/eyes.dm
@@ -405,7 +405,7 @@
activate()
/obj/item/organ/internal/eyes/robotic/glow/proc/prompt_for_controls(mob/user)
- var/color = input(owner, "Select Color", "Select color", "#ffffff") as color|null
+ var/color = tgui_color_picker(owner, "Select Color", "Select color", "#ffffff")
if(!color || QDELETED(src) || QDELETED(user) || QDELETED(owner) || owner != user)
return
var/range = input(user, "Enter range (0 - [max_light_beam_distance])", "Range Select", 0) as null|num
diff --git a/code/modules/tgui_input/color.dm b/code/modules/tgui_input/color.dm
new file mode 100644
index 000000000000..2c37ad03086e
--- /dev/null
+++ b/code/modules/tgui_input/color.dm
@@ -0,0 +1,123 @@
+/**
+ * Creates a TGUI color picker window and returns the user's response.
+ *
+ * This proc should be used to create a color picker that the caller will wait for a response from.
+ * Arguments:
+ * * user - The user to show the picker to.
+ * * title - The of the picker modal, shown on the top of the TGUI window.
+ * * timeout - The timeout of the picker, after which the modal will close and qdel itself. Set to zero for no timeout.
+ * * autofocus - The bool that controls if this picker should grab window focus.
+ */
+/proc/tgui_color_picker(mob/user, message, title, default = "#000000", timeout = 0, autofocus = 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(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input))
+ return input(user, message, title, default) as color|null
+ var/datum/tgui_color_picker/picker = new(user, message, title, default, timeout, autofocus)
+ picker.ui_interact(user)
+ picker.wait()
+ if (picker)
+ . = picker.choice
+ qdel(picker)
+
+/**
+ * # tgui_color_picker
+ *
+ * Datum used for instantiating and using a TGUI-controlled color picker.
+ */
+/datum/tgui_color_picker
+ /// The title of the TGUI window
+ var/title
+ /// The message to show the user
+ var/message
+ /// The default choice, used if there is an existing value
+ var/default
+ /// The color the user selected, null if no selection has been made
+ var/choice
+ /// The time at which the tgui_color_picker was created, for displaying timeout progress.
+ var/start_time
+ /// The lifespan of the tgui_color_picker, 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_color_picker was closed by the user.
+ var/closed
+
+/datum/tgui_color_picker/New(mob/user, message, title, default, timeout, autofocus)
+ src.autofocus = autofocus
+ src.title = title
+ src.default = default
+ src.message = message
+ if (timeout)
+ src.timeout = timeout
+ start_time = world.time
+ QDEL_IN(src, timeout)
+
+/datum/tgui_color_picker/Destroy(force, ...)
+ SStgui.close_uis(src)
+ . = ..()
+
+/**
+ * Waits for a user's response to the tgui_color_picker's prompt before returning. Returns early if
+ * the window was closed by the user.
+ */
+/datum/tgui_color_picker/proc/wait()
+ while (!choice && !closed && !QDELETED(src))
+ stoplag(1)
+
+/datum/tgui_color_picker/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ColorPickerModal")
+ ui.open()
+ ui.set_autoupdate(timeout > 0)
+
+/datum/tgui_color_picker/ui_close(mob/user)
+ . = ..()
+ closed = TRUE
+
+/datum/tgui_color_picker/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/tgui_color_picker/ui_static_data(mob/user)
+ . = list()
+ .["autofocus"] = autofocus
+ .["large_buttons"] = !user.client?.prefs || user.client.prefs.read_preference(/datum/preference/toggle/tgui_input_large)
+ .["swapped_buttons"] = !user.client?.prefs || user.client.prefs.read_preference(/datum/preference/toggle/tgui_input_swapped)
+ .["title"] = title
+ .["default_color"] = default
+ .["message"] = message
+
+/datum/tgui_color_picker/ui_data(mob/user)
+ . = list()
+ if(timeout)
+ .["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
+
+/datum/tgui_color_picker/ui_act(action, list/params)
+ . = ..()
+ if (.)
+ return
+ switch(action)
+ if("submit")
+ var/raw_data = lowertext(params["entry"])
+ var/hex = sanitize_hexcolor(raw_data, desired_format = 6, include_crunch = TRUE)
+ if (!hex)
+ return
+ set_choice(hex)
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+ if("cancel")
+ closed = TRUE
+ SStgui.close_uis(src)
+ return TRUE
+
+/datum/tgui_color_picker/proc/set_choice(choice)
+ src.choice = choice
diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_ethereal.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_ethereal.png
index 06def1164125..36487472d38f 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_ethereal.png and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_ethereal.png differ
diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_floran.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_floran.png
index 945f46a2153b..ca4cd88f2295 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_floran.png and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_floran.png differ
diff --git a/code/modules/vending/boozeomat.dm b/code/modules/vending/boozeomat.dm
index b79a05b30c49..7f4c2b5280be 100644
--- a/code/modules/vending/boozeomat.dm
+++ b/code/modules/vending/boozeomat.dm
@@ -12,6 +12,7 @@
"products" = list(
/obj/item/reagent_containers/cup/glass/bottle/curacao = 5,
/obj/item/reagent_containers/cup/glass/bottle/applejack = 5,
+ /obj/item/reagent_containers/cup/glass/bottle/wine_voltaic = 5,
/obj/item/reagent_containers/cup/glass/bottle/tequila = 5,
/obj/item/reagent_containers/cup/glass/bottle/rum = 5,
/obj/item/reagent_containers/cup/glass/bottle/cognac = 5,
diff --git a/code/modules/vending/cola.dm b/code/modules/vending/cola.dm
index 947db7f22749..262c3b5ebfd2 100644
--- a/code/modules/vending/cola.dm
+++ b/code/modules/vending/cola.dm
@@ -21,6 +21,7 @@
contraband = list(
/obj/item/reagent_containers/cup/soda_cans/thirteenloko = 6,
/obj/item/reagent_containers/cup/soda_cans/shamblers = 6,
+ /obj/item/reagent_containers/cup/soda_cans/wellcheers = 6,
)
premium = list(
/obj/item/reagent_containers/cup/glass/drinkingglass/filled/nuka_cola = 1,
@@ -101,6 +102,7 @@
/obj/item/reagent_containers/cup/soda_cans/lemon_lime = 10,
/obj/item/reagent_containers/cup/soda_cans/sol_dry = 10,
/obj/item/reagent_containers/cup/soda_cans/shamblers = 10,
+ /obj/item/reagent_containers/cup/soda_cans/wellcheers = 5,
)
product_slogans = "~Shake me up some of that Shambler's Juice!~"
product_ads = "Refreshing!;Thirsty for DNA? Satiate your craving!;Over 1 trillion souls drank!;Made with real DNA!;The hivemind demands your thirst!;Drink up!;Absorb your thirst."
diff --git a/html/changelogs/AutoChangeLog-pr-1227.yml b/html/changelogs/AutoChangeLog-pr-1227.yml
new file mode 100644
index 000000000000..7c935cdc4905
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-1227.yml
@@ -0,0 +1,4 @@
+author: "MrMelbert"
+delete-after: True
+changes:
+ - qol: "Glasses colors should be a lot less harsh, and being blind no longer also blinds your hud."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-1231.yml b/html/changelogs/AutoChangeLog-pr-1231.yml
new file mode 100644
index 000000000000..fe0dbeda0f10
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-1231.yml
@@ -0,0 +1,5 @@
+author: "Absolucy, itsmeowdev"
+delete-after: True
+changes:
+ - rscadd: "Added TGUI color picker and replaced all use of BYOND color pickers with it (TGUI input preference is still respected)."
+ - qol: "The Generic UI theme will now affect the border color of number inputs and text areas the same as text inputs."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-1294.yml b/html/changelogs/AutoChangeLog-pr-1294.yml
new file mode 100644
index 000000000000..b7c973001a33
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-1294.yml
@@ -0,0 +1,12 @@
+author: "Absolucy, LT3, Ghommie"
+delete-after: True
+changes:
+ - refactor: "Refactored maptext (those floating words)"
+ - bugfix: "Fixed special chat bubbles for yelling, clown, redtext, greentext"
+ - bugfix: "Fixed alignment of status display text"
+ - code_imp: "Status displays now synchronize their message lines when scrolling"
+ - image: "More maptext and font tweaks"
+ - spellcheck: "Context tooltips too small, too big... just right?"
+ - bugfix: "Maptext should now properly show superscript characters when performing actions"
+ - bugfix: "Fixed text effects for runechat messages (the stuff enclosed in +, | and _ characters)."
+ - spellcheck: "Improved the tip for say/text effects."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-1315.yml b/html/changelogs/AutoChangeLog-pr-1315.yml
new file mode 100644
index 000000000000..0f511f41e143
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-1315.yml
@@ -0,0 +1,6 @@
+author: "DexeeXI"
+delete-after: True
+changes:
+ - bugfix: "Baseline job titles for Cargo Tech, Quartermaster and Shaft Miner return."
+ - rscdel: "Removed a single extra air pipe on the Icebox Maid Cafe so it stops causing 11 lines of test failures"
+ - bugfix: "Tweaks the Metastation Singularity Engine Complex to also stop unit test failures."
\ No newline at end of file
diff --git a/html/changelogs/archive/2024-02.yml b/html/changelogs/archive/2024-02.yml
index f220b8c8a36b..9c2e5fcf2ffd 100644
--- a/html/changelogs/archive/2024-02.yml
+++ b/html/changelogs/archive/2024-02.yml
@@ -230,3 +230,69 @@
- rscadd: antag rep, for every roll you go without being antag your odds increase,
resetting when you become and antag, passing a roll won't remove your rep
- rscadd: replays are back
+2024-02-24:
+ Gboster-0:
+ - bugfix: Artefacts will no longer fail to activate their faults inside of peoples
+ backpacks. Beware!
+2024-02-25:
+ MrMelbert, mc-oofert:
+ - bugfix: Stairs are now resistant to being shoved by mobs with high move force.
+ - bugfix: fixed border-only firedoors being able to be walked through while closed
+2024-02-26:
+ DimWhat:
+ - rscadd: Arachnids have adopted the concept of gender.
+ - rscadd: Added ethereal gender and bodytype options.
+ - image: fixed some ethereal sprite inconsistencies.
+ - rscadd: Added dimorphism to florans.
+ - image: Fixes a few issues with bunnysuit and tailcoat sprites
+2024-02-27:
+ Absolucy:
+ - bugfix: The Monster Hunters midround event no longer has a blank name.
+ - rscadd: You can now light cigarettes by striking it across your local ethereal!
+ Don't worry, it doesn't harm them, although they may find it annoying.
+ AlbertNanotracen:
+ - rscadd: Voltaic Yellow Wine - New "base" drink, found in booze-o-mat. No special
+ effects besides acting as a weak ethereal food. Not very potent in terms of
+ alcohol.
+ - rscadd: Telepole - New mixed drink themed after thunderstorms, gives the same
+ shock-resist grey bull does. Made from 1 part Voltaic Wine, 1 Part Sake, and
+ 2 parts Dark & Stormy. Moderately potent.
+ - rscadd: Pod Tesla - New mixed drink, themed after the old removed tesla engine
+ (the singulo gets a cocktail, it should too!) Grants a brave - bull phobia resist,
+ and a stronger grey bull shock resist allowing you to (temporarily) resist tesla
+ arcs from reactive armor and the SM's tesla coils. Gives a pleasant thought
+ to whoever drank it, because this thing is a pain to make. Made from 5 parts
+ admiralty, 5 parts telepole, and 3 parts brave bull. Highly potent.
+ - qol: Unsure if this counts as QOL or balance, but the fact sol dry is in 3 different
+ cocktails but the bartender has to buy 30u cans of it made me feel it deserved
+ being added to the soda dispenser.
+ - image: 'Added graphics for the above drinks, shown below, from left to right:
+ Pod tesla, Voltaic Yellow wine, Telepole, Voltaic Wine (bottle)
+
+ '
+ - bugfix: Ice cups no longer instantly melt when vended
+ DimWhat:
+ - bugfix: Fixed the bunny wand not giving you cursed gloves
+ Melbert:
+ - bugfix: Fixes hallucination and encrypted announcements printing to the Newscaster.
+ lessthnthree:
+ - qol: Air alarms now display the source of triggered fire alarms/firedoors
+2024-02-28:
+ 13spacemen, Kapu1178, KnigTheThrasher:
+ - refactor: Footprint sprites are now based on your shoes and legs, shoes have priority
+ - image: Monkey legs now produce pawprints instead of footprints
+ - rscadd: Footprint support for simians and lizards
+ Absolucy:
+ - bugfix: Vaulting over climbables now displays the proper name of the object, instead
+ of just `/datum/element/climbable`
+ AlbertNanotracen:
+ - bugfix: Dirty ice cups should work as intended
+ - rscadd: Added wellcheers, a contraband soda with various side effects.
+ - image: resprites all cans in the drinks icon file
+ - image: resprites the canholder sprite in storage.dmi
+2024-02-29:
+ lmenvs:
+ - rscadd: Adds Rickdude's Nukie tier redeems. Thank you!
+ - rscadd: Adds Django's donator items, sprited by Dimwhat. Thank you!
diff --git a/icons/effects/footprints.dmi b/icons/effects/footprints.dmi
index a98344abe41e..a00c6a0f780d 100644
Binary files a/icons/effects/footprints.dmi and b/icons/effects/footprints.dmi differ
diff --git a/icons/mob/inhands/items/drinks_lefthand.dmi b/icons/mob/inhands/items/drinks_lefthand.dmi
index 6d7cc042f772..b1aa5e67822b 100644
Binary files a/icons/mob/inhands/items/drinks_lefthand.dmi and b/icons/mob/inhands/items/drinks_lefthand.dmi differ
diff --git a/icons/mob/inhands/items/drinks_righthand.dmi b/icons/mob/inhands/items/drinks_righthand.dmi
index 02756cb64483..3d1110960443 100644
Binary files a/icons/mob/inhands/items/drinks_righthand.dmi and b/icons/mob/inhands/items/drinks_righthand.dmi differ
diff --git a/icons/obj/drinks/bottles.dmi b/icons/obj/drinks/bottles.dmi
index fbdf45d3edc4..6f3622179546 100644
Binary files a/icons/obj/drinks/bottles.dmi and b/icons/obj/drinks/bottles.dmi differ
diff --git a/icons/obj/drinks/drink_effects.dmi b/icons/obj/drinks/drink_effects.dmi
index 63bc699c9f5c..b0441f73f346 100644
Binary files a/icons/obj/drinks/drink_effects.dmi and b/icons/obj/drinks/drink_effects.dmi differ
diff --git a/icons/obj/drinks/mixed_drinks.dmi b/icons/obj/drinks/mixed_drinks.dmi
index 3e636d9476b7..9d11040d0abc 100644
Binary files a/icons/obj/drinks/mixed_drinks.dmi and b/icons/obj/drinks/mixed_drinks.dmi differ
diff --git a/icons/obj/drinks/soda.dmi b/icons/obj/drinks/soda.dmi
index dedca0ff89e5..95b195789d13 100644
Binary files a/icons/obj/drinks/soda.dmi and b/icons/obj/drinks/soda.dmi differ
diff --git a/icons/obj/janitor.dmi b/icons/obj/janitor.dmi
deleted file mode 100644
index 6e007dc4fa59..000000000000
Binary files a/icons/obj/janitor.dmi and /dev/null differ
diff --git a/icons/obj/service/janitor.dmi b/icons/obj/service/janitor.dmi
new file mode 100644
index 000000000000..18a574c7cff0
Binary files /dev/null and b/icons/obj/service/janitor.dmi differ
diff --git a/icons/obj/storage/storage.dmi b/icons/obj/storage/storage.dmi
index 848f8326c844..db627027de76 100644
Binary files a/icons/obj/storage/storage.dmi and b/icons/obj/storage/storage.dmi differ
diff --git a/interface/fonts.dm b/interface/fonts.dm
deleted file mode 100644
index 0bfc9c728c7c..000000000000
--- a/interface/fonts.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-/// A font datum, it exists to define a custom font to use in a span style later.
-/datum/font
- /// Font name, just so people know what to put in their span style.
- var/name
- /// The font file we link to.
- var/font_family
-
-/datum/font/vcr_osd_mono
- name = "VCR OSD Mono"
- font_family = 'interface/VCR_OSD_Mono.ttf'
diff --git a/interface/fonts/Grand9K_Pixel.ttf b/interface/fonts/Grand9K_Pixel.ttf
new file mode 100644
index 000000000000..cf6fdf44e2ec
Binary files /dev/null and b/interface/fonts/Grand9K_Pixel.ttf differ
diff --git a/interface/fonts/Pixellari.ttf b/interface/fonts/Pixellari.ttf
new file mode 100644
index 000000000000..5a3a3c2b1104
Binary files /dev/null and b/interface/fonts/Pixellari.ttf differ
diff --git a/interface/fonts/SpessFont.ttf b/interface/fonts/SpessFont.ttf
new file mode 100644
index 000000000000..8f7c7e08d0d8
Binary files /dev/null and b/interface/fonts/SpessFont.ttf differ
diff --git a/interface/fonts/TinyUnicode.ttf b/interface/fonts/TinyUnicode.ttf
new file mode 100644
index 000000000000..74d0d3e386e6
Binary files /dev/null and b/interface/fonts/TinyUnicode.ttf differ
diff --git a/interface/VCR_OSD_Mono.ttf b/interface/fonts/VCR_OSD_Mono.ttf
similarity index 100%
rename from interface/VCR_OSD_Mono.ttf
rename to interface/fonts/VCR_OSD_Mono.ttf
diff --git a/interface/fonts/fonts_datum.dm b/interface/fonts/fonts_datum.dm
new file mode 100644
index 000000000000..a346706d7fa0
--- /dev/null
+++ b/interface/fonts/fonts_datum.dm
@@ -0,0 +1,78 @@
+/// A font datum, it exists to define a custom font to use in a span style later.
+/datum/font
+ /// Font name, just so people know what to put in their span style.
+ var/name
+ /// The font file we link to.
+ var/font_family
+
+ /// Font features and metrics
+ /// Generated by Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus)
+ /// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe
+
+ /// list of font size/spacing metrics
+ var/list/metrics
+ /// total height of a line
+ var/height
+ /// distance above baseline (including whitespace)
+ var/ascent
+ /// distance below baseline
+ var/descent
+ /// average character width
+ var/average_width
+ /// maximum character width
+ var/max_width
+ /// extra width, such as from italics, for a line
+ var/overhang
+ /// internal leading vertical space, for accent marks
+ var/in_leading
+ /// external leading vertical space, just plain blank
+ var/ex_leading
+ /// default character (for undefined chars)
+ var/default_character
+ /// first character in metrics
+ var/start
+ /// last character in metrics
+ var/end
+
+/// Get font metrics
+/// From Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus)
+/datum/font/proc/get_metrics(text, flags, first_line)
+ . = 0
+ var/longest = 0
+ if(!length(text))
+ return
+
+ var/i = 1
+ var/idx
+ while(i <= length(text))
+ var/character = text2ascii(text, i++)
+ if(character <= 10)
+ if(character <= 7)
+ . += character // spacers for justification
+
+ if(character <= 9)
+ continue // soft-break chars
+
+ if(. && idx && !(flags & INCLUDE_AC))
+ . -= max(metrics[idx + 3], 0)
+
+ longest = max(longest, . + first_line)
+ . = 0
+ first_line = 0
+ idx = 0
+ continue
+
+ idx = (character - start) * 3
+ if(idx <= 0 || idx >= metrics.len)
+ idx = (default_character - start) * 3
+
+ if(!. && !(flags & INCLUDE_AC))
+ . -= metrics[idx + 1]
+ . += metrics[idx + 1] + metrics[idx + 2] + metrics[idx +3]
+
+ if(. && idx && !(flags & INCLUDE_AC))
+ . -= max(metrics[idx + 3], 0)
+
+ . = max(. + first_line, longest)
+ if(. > 0)
+ . += overhang
diff --git a/interface/fonts/grand_9k.dm b/interface/fonts/grand_9k.dm
new file mode 100644
index 000000000000..7993d307bcbe
--- /dev/null
+++ b/interface/fonts/grand_9k.dm
@@ -0,0 +1,253 @@
+/// For clean results on map, use only sizing pt, multiples of 6: 6pt 12pt 18pt 24pt etc. - Not for use with px sizing
+/// Can be used in TGUI etc, px sizing is pt / 0.75. 6pt = 8px, 12pt = 16px etc.
+
+/// Base font
+/datum/font/grand9k
+ name = "Grand9K Pixel"
+ font_family = 'interface/fonts/Grand9K_Pixel.ttf'
+
+/// For icon overlays
+/// Grand9K 6pt metrics generated using Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus)
+/// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe
+/datum/font/grand9k/size_6pt
+ name = "Grand9K Pixel 6pt"
+ height = 12
+ ascent = 10
+ descent = 2
+ average_width = 4
+ max_width = 9
+ overhang = 0
+ in_leading = 4
+ ex_leading = 1
+ default_character = 31
+ start = 30
+ end = 255
+ metrics = list(
+ 0, 5, 1, // char 30
+ 0, 5, 1, // char 31
+ 0, 1, 1, // char 32
+ 0, 1, 1, // char 33
+ 0, 3, 1, // char 34
+ 0, 6, 1, // char 35
+ 0, 5, 1, // char 36
+ 0, 7, 1, // char 37
+ 0, 5, 1, // char 38
+ 0, 1, 1, // char 39
+ 0, 3, 1, // char 40
+ 0, 3, 1, // char 41
+ 0, 5, 1, // char 42
+ 0, 5, 1, // char 43
+ 0, 1, 1, // char 44
+ 0, 4, 1, // char 45
+ 0, 1, 1, // char 46
+ 0, 3, 1, // char 47
+ 0, 5, 1, // char 48
+ 0, 2, 1, // char 49
+ 0, 5, 1, // char 50
+ 0, 4, 1, // char 51
+ 0, 5, 1, // char 52
+ 0, 5, 1, // char 53
+ 0, 5, 1, // char 54
+ 0, 5, 1, // char 55
+ 0, 5, 1, // char 56
+ 0, 5, 1, // char 57
+ 0, 1, 1, // char 58
+ 0, 1, 1, // char 59
+ 0, 4, 1, // char 60
+ 0, 4, 1, // char 61
+ 0, 4, 1, // char 62
+ 0, 4, 1, // char 63
+ 0, 7, 1, // char 64
+ 0, 5, 1, // char 65
+ 0, 5, 1, // char 66
+ 0, 4, 1, // char 67
+ 0, 5, 1, // char 68
+ 0, 4, 1, // char 69
+ 0, 4, 1, // char 70
+ 0, 5, 1, // char 71
+ 0, 5, 1, // char 72
+ 0, 1, 1, // char 73
+ 0, 5, 1, // char 74
+ 0, 5, 1, // char 75
+ 0, 5, 1, // char 76
+ 0, 5, 1, // char 77
+ 0, 5, 1, // char 78
+ 0, 5, 1, // char 79
+ 0, 5, 1, // char 80
+ 0, 6, 1, // char 81
+ 0, 5, 1, // char 82
+ 0, 5, 1, // char 83
+ 0, 5, 1, // char 84
+ 0, 5, 1, // char 85
+ 0, 5, 1, // char 86
+ 0, 5, 1, // char 87
+ 0, 5, 1, // char 88
+ 0, 5, 1, // char 89
+ 0, 5, 1, // char 90
+ 0, 3, 1, // char 91
+ 0, 3, 1, // char 92
+ 0, 3, 1, // char 93
+ 0, 5, 1, // char 94
+ 0, 4, 0, // char 95
+ 0, 2, 1, // char 96
+ 0, 4, 1, // char 97
+ 0, 4, 1, // char 98
+ 0, 3, 1, // char 99
+ 0, 4, 1, // char 100
+ 0, 4, 1, // char 101
+ 0, 4, 1, // char 102
+ 0, 4, 1, // char 103
+ 0, 4, 1, // char 104
+ 0, 1, 1, // char 105
+ 0, 3, 1, // char 106
+ 0, 4, 1, // char 107
+ 0, 1, 1, // char 108
+ 0, 5, 1, // char 109
+ 0, 4, 1, // char 110
+ 0, 4, 1, // char 111
+ 0, 4, 1, // char 112
+ 0, 4, 1, // char 113
+ 0, 4, 1, // char 114
+ 0, 4, 1, // char 115
+ 0, 4, 1, // char 116
+ 0, 4, 1, // char 117
+ 0, 5, 1, // char 118
+ 0, 5, 1, // char 119
+ 0, 5, 1, // char 120
+ 0, 4, 1, // char 121
+ 0, 5, 1, // char 122
+ 0, 4, 1, // char 123
+ 0, 1, 1, // char 124
+ 0, 4, 1, // char 125
+ 0, 6, 1, // char 126
+ 0, 5, 1, // char 127
+ 0, 5, 1, // char 128
+ 0, 5, 1, // char 129
+ 0, 1, 1, // char 130
+ 0, 5, 1, // char 131
+ 0, 3, 1, // char 132
+ 0, 5, 1, // char 133
+ 0, 5, 1, // char 134
+ 0, 5, 1, // char 135
+ 0, 5, 1, // char 136
+ 0, 5, 1, // char 137
+ 0, 5, 1, // char 138
+ 0, 3, 1, // char 139
+ 0, 6, 1, // char 140
+ 0, 5, 1, // char 141
+ 0, 5, 1, // char 142
+ 0, 5, 1, // char 143
+ 0, 5, 1, // char 144
+ 0, 1, 1, // char 145
+ 0, 1, 1, // char 146
+ 0, 3, 1, // char 147
+ 0, 3, 1, // char 148
+ 0, 1, 1, // char 149
+ 0, 5, 1, // char 150
+ 0, 5, 1, // char 151
+ 0, 5, 1, // char 152
+ 0, 8, 1, // char 153
+ 0, 4, 1, // char 154
+ 0, 3, 1, // char 155
+ 0, 5, 1, // char 156
+ 0, 5, 1, // char 157
+ 0, 5, 1, // char 158
+ 0, 5, 1, // char 159
+ 0, 1, 1, // char 160
+ 0, 1, 1, // char 161
+ 0, 4, 1, // char 162
+ 0, 5, 1, // char 163
+ 0, 5, 1, // char 164
+ 0, 5, 1, // char 165
+ 0, 1, 1, // char 166
+ 0, 5, 1, // char 167
+ 0, 3, 1, // char 168
+ 0, 8, 1, // char 169
+ 0, 5, 1, // char 170
+ 0, 6, 1, // char 171
+ 0, 4, 1, // char 172
+ 0, 5, 1, // char 173
+ 0, 8, 1, // char 174
+ 0, 5, 1, // char 175
+ 0, 3, 1, // char 176
+ 0, 5, 1, // char 177
+ 0, 5, 1, // char 178
+ 0, 5, 1, // char 179
+ 0, 2, 1, // char 180
+ 0, 4, 1, // char 181
+ 0, 5, 1, // char 182
+ 0, 1, 1, // char 183
+ 0, 2, 1, // char 184
+ 0, 5, 1, // char 185
+ 0, 5, 1, // char 186
+ 0, 6, 1, // char 187
+ 0, 5, 1, // char 188
+ 0, 5, 1, // char 189
+ 0, 5, 1, // char 190
+ 0, 4, 1, // char 191
+ 0, 5, 1, // char 192
+ 0, 5, 1, // char 193
+ 0, 5, 1, // char 194
+ 0, 6, 0, // char 195
+ 0, 5, 1, // char 196
+ 0, 5, 1, // char 197
+ 0, 6, 1, // char 198
+ 0, 4, 1, // char 199
+ 0, 4, 1, // char 200
+ 0, 4, 1, // char 201
+ 0, 4, 1, // char 202
+ 0, 4, 1, // char 203
+ 1, 2, 0, // char 204
+ 0, 2, 1, // char 205
+ 0, 3, 0, // char 206
+ 0, 3, 0, // char 207
+ 0, 6, 1, // char 208
+ 0, 6, 0, // char 209
+ 0, 5, 1, // char 210
+ 0, 5, 1, // char 211
+ 0, 5, 1, // char 212
+ 0, 6, 1, // char 213
+ 0, 5, 1, // char 214
+ 0, 5, 1, // char 215
+ 0, 5, 1, // char 216
+ 0, 5, 1, // char 217
+ 0, 5, 1, // char 218
+ 0, 5, 1, // char 219
+ 0, 5, 1, // char 220
+ 0, 5, 1, // char 221
+ 0, 5, 1, // char 222
+ 0, 5, 1, // char 223
+ 0, 4, 1, // char 224
+ 0, 4, 1, // char 225
+ 0, 4, 1, // char 226
+ 0, 4, 1, // char 227
+ 0, 4, 1, // char 228
+ 0, 4, 1, // char 229
+ 0, 5, 1, // char 230
+ 0, 3, 1, // char 231
+ 0, 4, 1, // char 232
+ 0, 4, 1, // char 233
+ 0, 4, 1, // char 234
+ 0, 4, 1, // char 235
+ 0, 2, 1, // char 236
+ 1, 2, 0, // char 237
+ 0, 3, 0, // char 238
+ 0, 3, 0, // char 239
+ 0, 5, 0, // char 240
+ 0, 4, 1, // char 241
+ 0, 4, 1, // char 242
+ 0, 4, 1, // char 243
+ 0, 4, 1, // char 244
+ 0, 4, 1, // char 245
+ 0, 4, 1, // char 246
+ 0, 5, 1, // char 247
+ 0, 4, 1, // char 248
+ 0, 4, 1, // char 249
+ 0, 4, 1, // char 250
+ 0, 4, 1, // char 251
+ 0, 4, 1, // char 252
+ 0, 4, 1, // char 253
+ 0, 4, 1, // char 254
+ 0, 4, 1, // char 255
+ 226
+ )
diff --git a/interface/fonts/license.txt b/interface/fonts/license.txt
new file mode 100644
index 000000000000..9aa70fbac2a9
--- /dev/null
+++ b/interface/fonts/license.txt
@@ -0,0 +1,13 @@
+Grand9K Pixel created by Jayvee Enaguas. Licensed under Creative Commons Attribution 4.0 International (CC BY 4.0)
+(https://creativecommons.org/licenses/by/4.0/) (https://www.dafont.com/grand9k-pixel.font)
+
+Pixellari created by Zacchary Dempsey-Plante. Website indicates free for commercial use.
+(https://www.dafont.com/pixellari.font?fpp=200)
+
+Spess Font created by MTandi (discord) for /tg/station.
+
+Tiny Unicode created by Jakob Riedle/DuffsDevice. Website indicates free for commercial use.
+(https://fontmeme.com/fonts/tiny-unicode-font/)
+
+VCR OSD Mono created by Riciery Leal/mrmanet. Website indicates 100% free, author confirms it's free for all to use.
+(https://www.dafont.com/font-comment.php?file=vcr_osd_mono)
diff --git a/interface/fonts/pixellari.dm b/interface/fonts/pixellari.dm
new file mode 100644
index 000000000000..24fcd1961fec
--- /dev/null
+++ b/interface/fonts/pixellari.dm
@@ -0,0 +1,252 @@
+/// For clean results on map, use only sizing pt, multiples of 12: 12pt 24pt 48pt etc. - Not for use with px sizing
+/// Can be used in TGUI etc, px sizing is pt / 0.75. 12pt = 16px, 24pt = 32px etc.
+
+/// Base font
+/datum/font/pixellari
+ name = "Pixellari"
+ font_family = 'interface/fonts/Pixellari.ttf'
+
+/// For icon overlays
+/// Pixellari 12pt metrics generated using Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus)
+/// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe
+/datum/font/pixellari/size_12pt
+ name = "Pixellari 12pt"
+ height = 16
+ ascent = 12
+ descent = 4
+ average_width = 7
+ max_width = 15
+ overhang = 0
+ in_leading = 0
+ ex_leading = 1
+ default_character = 31
+ start = 30
+ end = 255
+ metrics = list(\
+ 1, 5, 0, /* char 30 */ \
+ 1, 5, 0, /* char 31 */ \
+ 0, 1, 4, /* char 32 */ \
+ 1, 2, 1, /* char 33 */ \
+ 1, 5, 1, /* char 34 */ \
+ 0, 8, 1, /* char 35 */ \
+ 2, 6, 1, /* char 36 */ \
+ 0, 13, 1, /* char 37 */ \
+ 1, 8, 1, /* char 38 */ \
+ 1, 2, 1, /* char 39 */ \
+ 1, 3, 1, /* char 40 */ \
+ 2, 3, 1, /* char 41 */ \
+ 0, 6, 1, /* char 42 */ \
+ 1, 6, 1, /* char 43 */ \
+ 1, 2, 1, /* char 44 */ \
+ 1, 6, 1, /* char 45 */ \
+ 1, 2, 1, /* char 46 */ \
+ 0, 6, 1, /* char 47 */ \
+ 1, 7, 1, /* char 48 */ \
+ 2, 6, 1, /* char 49 */ \
+ 1, 6, 1, /* char 50 */ \
+ 1, 6, 1, /* char 51 */ \
+ 1, 7, 1, /* char 52 */ \
+ 1, 6, 1, /* char 53 */ \
+ 1, 6, 1, /* char 54 */ \
+ 1, 7, 1, /* char 55 */ \
+ 1, 6, 1, /* char 56 */ \
+ 1, 6, 1, /* char 57 */ \
+ 1, 2, 1, /* char 58 */ \
+ 1, 2, 1, /* char 59 */ \
+ 0, 10, 1, /* char 60 */ \
+ 1, 6, 1, /* char 61 */ \
+ 0, 10, 1, /* char 62 */ \
+ 1, 6, 1, /* char 63 */ \
+ 1, 12, 1, /* char 64 */ \
+ 1, 8, 1, /* char 65 */ \
+ 1, 8, 1, /* char 66 */ \
+ 2, 7, 1, /* char 67 */ \
+ 2, 8, 1, /* char 68 */ \
+ 2, 6, 1, /* char 69 */ \
+ 2, 6, 1, /* char 70 */ \
+ 2, 7, 1, /* char 71 */ \
+ 1, 8, 1, /* char 72 */ \
+ 1, 4, 1, /* char 73 */ \
+ 0, 7, 1, /* char 74 */ \
+ 1, 8, 1, /* char 75 */ \
+ 1, 6, 1, /* char 76 */ \
+ 1, 10, 1, /* char 77 */ \
+ 1, 9, 1, /* char 78 */ \
+ 2, 8, 1, /* char 79 */ \
+ 1, 7, 1, /* char 80 */ \
+ 2, 9, 1, /* char 81 */ \
+ 1, 8, 1, /* char 82 */ \
+ 1, 8, 1, /* char 83 */ \
+ 1, 8, 1, /* char 84 */ \
+ 2, 8, 1, /* char 85 */ \
+ 2, 8, 1, /* char 86 */ \
+ 1, 10, 1, /* char 87 */ \
+ 1, 8, 1, /* char 88 */ \
+ 1, 8, 1, /* char 89 */ \
+ 0, 10, 1, /* char 90 */ \
+ 1, 3, 1, /* char 91 */ \
+ 0, 6, 1, /* char 92 */ \
+ 2, 3, 1, /* char 93 */ \
+ 0, 7, 1, /* char 94 */ \
+ 0, 8, 1, /* char 95 */ \
+ 1, 3, 1, /* char 96 */ \
+ 1, 6, 1, /* char 97 */ \
+ 1, 7, 1, /* char 98 */ \
+ 1, 6, 1, /* char 99 */ \
+ 1, 7, 1, /* char 100 */ \
+ 1, 6, 1, /* char 101 */ \
+ 1, 4, 1, /* char 102 */ \
+ 1, 7, 1, /* char 103 */ \
+ 1, 7, 1, /* char 104 */ \
+ 1, 2, 1, /* char 105 */ \
+ -1, 4, 1, /* char 106 */ \
+ 0, 7, 1, /* char 107 */ \
+ 1, 2, 1, /* char 108 */ \
+ 1, 10, 1, /* char 109 */ \
+ 1, 6, 1, /* char 110 */ \
+ 1, 6, 1, /* char 111 */ \
+ 1, 7, 1, /* char 112 */ \
+ 1, 7, 1, /* char 113 */ \
+ 1, 6, 1, /* char 114 */ \
+ 1, 6, 1, /* char 115 */ \
+ 0, 4, 1, /* char 116 */ \
+ 1, 6, 1, /* char 117 */ \
+ 1, 6, 1, /* char 118 */ \
+ 1, 10, 1, /* char 119 */ \
+ 1, 6, 1, /* char 120 */ \
+ 1, 6, 1, /* char 121 */ \
+ 1, 6, 1, /* char 122 */ \
+ 0, 5, 1, /* char 123 */ \
+ 1, 2, 1, /* char 124 */ \
+ 0, 5, 1, /* char 125 */ \
+ 1, 8, 1, /* char 126 */ \
+ 1, 5, 0, /* char 127 */ \
+ 1, 8, 1, /* char 128 */ \
+ 1, 5, 0, /* char 129 */ \
+ 1, 5, 0, /* char 130 */ \
+ 1, 5, 0, /* char 131 */ \
+ 1, 5, 0, /* char 132 */ \
+ 1, 5, 0, /* char 133 */ \
+ 1, 5, 0, /* char 134 */ \
+ 1, 5, 0, /* char 135 */ \
+ 1, 5, 0, /* char 136 */ \
+ 1, 5, 0, /* char 137 */ \
+ 1, 8, 1, /* char 138 */ \
+ 1, 5, 0, /* char 139 */ \
+ 0, 14, 1, /* char 140 */ \
+ 1, 5, 0, /* char 141 */ \
+ 0, 10, 1, /* char 142 */ \
+ 1, 5, 0, /* char 143 */ \
+ 1, 5, 0, /* char 144 */ \
+ 1, 5, 0, /* char 145 */ \
+ 1, 5, 0, /* char 146 */ \
+ 1, 5, 0, /* char 147 */ \
+ 1, 5, 0, /* char 148 */ \
+ 1, 5, 0, /* char 149 */ \
+ 1, 5, 0, /* char 150 */ \
+ 1, 5, 0, /* char 151 */ \
+ 1, 5, 0, /* char 152 */ \
+ 1, 5, 0, /* char 153 */ \
+ 1, 6, 1, /* char 154 */ \
+ 1, 5, 0, /* char 155 */ \
+ 1, 11, 1, /* char 156 */ \
+ 1, 5, 0, /* char 157 */ \
+ 1, 6, 1, /* char 158 */ \
+ 1, 8, 1, /* char 159 */ \
+ 0, 1, 4, /* char 160 */ \
+ 1, 2, 1, /* char 161 */ \
+ 1, 6, 1, /* char 162 */ \
+ 0, 8, 1, /* char 163 */ \
+ 0, 9, 1, /* char 164 */ \
+ 1, 8, 1, /* char 165 */ \
+ 1, 2, 1, /* char 166 */ \
+ 1, 7, 1, /* char 167 */ \
+ 0, 5, 1, /* char 168 */ \
+ -1, 12, 1, /* char 169 */ \
+ 0, 6, 1, /* char 170 */ \
+ 0, 8, 1, /* char 171 */ \
+ 1, 8, 1, /* char 172 */ \
+ 1, 5, 0, /* char 173 */ \
+ -1, 12, 1, /* char 174 */ \
+ 2, 4, 1, /* char 175 */ \
+ 0, 6, 1, /* char 176 */ \
+ 1, 6, 1, /* char 177 */ \
+ 0, 5, 1, /* char 178 */ \
+ 0, 5, 1, /* char 179 */ \
+ 1, 3, 1, /* char 180 */ \
+ 1, 6, 1, /* char 181 */ \
+ 1, 7, 1, /* char 182 */ \
+ 1, 2, 1, /* char 183 */ \
+ 1, 3, 1, /* char 184 */ \
+ 1, 4, 1, /* char 185 */ \
+ 0, 6, 1, /* char 186 */ \
+ 0, 8, 1, /* char 187 */ \
+ 1, 13, 1, /* char 188 */ \
+ 1, 12, 1, /* char 189 */ \
+ 0, 13, 1, /* char 190 */ \
+ 1, 6, 1, /* char 191 */ \
+ 1, 8, 1, /* char 192 */ \
+ 1, 8, 1, /* char 193 */ \
+ 1, 8, 1, /* char 194 */ \
+ 1, 8, 1, /* char 195 */ \
+ 1, 8, 1, /* char 196 */ \
+ 1, 8, 1, /* char 197 */ \
+ 0, 13, 1, /* char 198 */ \
+ 2, 7, 1, /* char 199 */ \
+ 2, 6, 1, /* char 200 */ \
+ 2, 6, 1, /* char 201 */ \
+ 2, 6, 1, /* char 202 */ \
+ 2, 6, 1, /* char 203 */ \
+ 1, 4, 1, /* char 204 */ \
+ 1, 4, 1, /* char 205 */ \
+ 1, 4, 1, /* char 206 */ \
+ 1, 4, 1, /* char 207 */ \
+ 0, 10, 1, /* char 208 */ \
+ 1, 9, 1, /* char 209 */ \
+ 2, 8, 1, /* char 210 */ \
+ 2, 8, 1, /* char 211 */ \
+ 2, 8, 1, /* char 212 */ \
+ 2, 8, 1, /* char 213 */ \
+ 2, 8, 1, /* char 214 */ \
+ 1, 6, 1, /* char 215 */ \
+ -2, 14, 1, /* char 216 */ \
+ 2, 8, 1, /* char 217 */ \
+ 2, 8, 1, /* char 218 */ \
+ 2, 8, 1, /* char 219 */ \
+ 2, 8, 1, /* char 220 */ \
+ 1, 8, 1, /* char 221 */ \
+ 1, 8, 1, /* char 222 */ \
+ 1, 8, 1, /* char 223 */ \
+ 1, 6, 1, /* char 224 */ \
+ 1, 6, 1, /* char 225 */ \
+ 1, 6, 1, /* char 226 */ \
+ 1, 6, 1, /* char 227 */ \
+ 1, 6, 1, /* char 228 */ \
+ 1, 6, 1, /* char 229 */ \
+ 1, 11, 1, /* char 230 */ \
+ 1, 6, 1, /* char 231 */ \
+ 1, 6, 1, /* char 232 */ \
+ 1, 6, 1, /* char 233 */ \
+ 1, 6, 1, /* char 234 */ \
+ 1, 6, 1, /* char 235 */ \
+ 1, 2, 1, /* char 236 */ \
+ 1, 2, 1, /* char 237 */ \
+ 0, 4, 1, /* char 238 */ \
+ 0, 4, 1, /* char 239 */ \
+ 1, 7, 1, /* char 240 */ \
+ 1, 6, 1, /* char 241 */ \
+ 1, 6, 1, /* char 242 */ \
+ 1, 6, 1, /* char 243 */ \
+ 1, 6, 1, /* char 244 */ \
+ 1, 6, 1, /* char 245 */ \
+ 1, 6, 1, /* char 246 */ \
+ 1, 6, 1, /* char 247 */ \
+ 0, 10, 1, /* char 248 */ \
+ 1, 6, 1, /* char 249 */ \
+ 1, 6, 1, /* char 250 */ \
+ 1, 6, 1, /* char 251 */ \
+ 1, 6, 1, /* char 252 */ \
+ 1, 6, 1, /* char 253 */ \
+ 1, 8, 1, /* char 254 */ \
+ 1, 6, 1, /* char 255 */ \
+ 226)
diff --git a/interface/fonts/spess_font.dm b/interface/fonts/spess_font.dm
new file mode 100644
index 000000000000..07e8ea5b3ba6
--- /dev/null
+++ b/interface/fonts/spess_font.dm
@@ -0,0 +1,252 @@
+/// For clean results on map, use only sizing pt, multiples of 6: 6t 12pt 18pt etc. - Not for use with px sizing
+/// Can be used in TGUI etc, px sizing is pt / 0.75. 12pt = 16px, 24pt = 32px etc.
+
+/// Base font
+/datum/font/spessfont
+ name = "Spess Font"
+ font_family = 'interface/fonts/SpessFont.ttf'
+
+/// For icon overlays
+/// Spess Font 6pt metrics generated using Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus)
+/// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe
+/datum/font/spessfont/size_6pt
+ name = "Spess Font 6pt"
+ height = 8
+ ascent = 6
+ descent = 2
+ average_width = 4
+ max_width = 6
+ overhang = 0
+ in_leading = 0
+ ex_leading = 0
+ default_character = 31
+ start = 30
+ end = 255
+ metrics = list(\
+ 0, 1, 0, /* char 30 */ \
+ 0, 1, 0, /* char 31 */ \
+ 0, 1, 1, /* char 32 */ \
+ 0, 1, 1, /* char 33 */ \
+ 0, 3, 1, /* char 34 */ \
+ 0, 5, 1, /* char 35 */ \
+ 0, 3, 1, /* char 36 */ \
+ 0, 5, 1, /* char 37 */ \
+ 0, 5, 1, /* char 38 */ \
+ 0, 1, 1, /* char 39 */ \
+ 0, 2, 1, /* char 40 */ \
+ 0, 2, 1, /* char 41 */ \
+ 0, 3, 1, /* char 42 */ \
+ 0, 3, 1, /* char 43 */ \
+ 0, 1, 1, /* char 44 */ \
+ 0, 3, 1, /* char 45 */ \
+ 0, 1, 1, /* char 46 */ \
+ 0, 3, 1, /* char 47 */ \
+ 0, 4, 1, /* char 48 */ \
+ 0, 2, 1, /* char 49 */ \
+ 0, 4, 1, /* char 50 */ \
+ 0, 4, 1, /* char 51 */ \
+ 0, 4, 1, /* char 52 */ \
+ 0, 4, 1, /* char 53 */ \
+ 0, 4, 1, /* char 54 */ \
+ 0, 4, 1, /* char 55 */ \
+ 0, 4, 1, /* char 56 */ \
+ 0, 4, 1, /* char 57 */ \
+ 0, 1, 1, /* char 58 */ \
+ 0, 1, 1, /* char 59 */ \
+ 0, 3, 1, /* char 60 */ \
+ 0, 3, 1, /* char 61 */ \
+ 0, 3, 1, /* char 62 */ \
+ 0, 3, 1, /* char 63 */ \
+ 0, 4, 1, /* char 64 */ \
+ 0, 4, 1, /* char 65 */ \
+ 0, 4, 1, /* char 66 */ \
+ 0, 4, 1, /* char 67 */ \
+ 0, 4, 1, /* char 68 */ \
+ 0, 4, 1, /* char 69 */ \
+ 0, 4, 1, /* char 70 */ \
+ 0, 4, 1, /* char 71 */ \
+ 0, 4, 1, /* char 72 */ \
+ 0, 3, 1, /* char 73 */ \
+ 0, 4, 1, /* char 74 */ \
+ 0, 4, 1, /* char 75 */ \
+ 0, 4, 1, /* char 76 */ \
+ 0, 5, 1, /* char 77 */ \
+ 0, 4, 1, /* char 78 */ \
+ 0, 4, 1, /* char 79 */ \
+ 0, 4, 1, /* char 80 */ \
+ 0, 4, 1, /* char 81 */ \
+ 0, 4, 1, /* char 82 */ \
+ 0, 4, 1, /* char 83 */ \
+ 0, 5, 1, /* char 84 */ \
+ 0, 4, 1, /* char 85 */ \
+ 0, 4, 1, /* char 86 */ \
+ 0, 5, 1, /* char 87 */ \
+ 0, 5, 1, /* char 88 */ \
+ 0, 4, 1, /* char 89 */ \
+ 0, 4, 1, /* char 90 */ \
+ 0, 2, 1, /* char 91 */ \
+ 0, 3, 1, /* char 92 */ \
+ 0, 2, 1, /* char 93 */ \
+ 0, 3, 1, /* char 94 */ \
+ 0, 4, 1, /* char 95 */ \
+ 0, 2, 1, /* char 96 */ \
+ 0, 3, 1, /* char 97 */ \
+ 0, 4, 1, /* char 98 */ \
+ 0, 3, 1, /* char 99 */ \
+ 0, 4, 1, /* char 100 */ \
+ 0, 3, 1, /* char 101 */ \
+ 0, 2, 1, /* char 102 */ \
+ 0, 4, 1, /* char 103 */ \
+ 0, 3, 1, /* char 104 */ \
+ 0, 1, 1, /* char 105 */ \
+ 0, 1, 1, /* char 106 */ \
+ 0, 3, 1, /* char 107 */ \
+ 0, 1, 1, /* char 108 */ \
+ 0, 5, 1, /* char 109 */ \
+ 0, 3, 1, /* char 110 */ \
+ 0, 4, 1, /* char 111 */ \
+ 0, 4, 1, /* char 112 */ \
+ 0, 4, 1, /* char 113 */ \
+ 0, 2, 1, /* char 114 */ \
+ 0, 3, 1, /* char 115 */ \
+ 0, 2, 1, /* char 116 */ \
+ 0, 3, 1, /* char 117 */ \
+ 0, 3, 1, /* char 118 */ \
+ 0, 5, 1, /* char 119 */ \
+ 0, 3, 1, /* char 120 */ \
+ 0, 3, 1, /* char 121 */ \
+ 0, 3, 1, /* char 122 */ \
+ 0, 3, 1, /* char 123 */ \
+ 0, 1, 1, /* char 124 */ \
+ 0, 3, 1, /* char 125 */ \
+ 0, 4, 1, /* char 126 */ \
+ 0, 1, 0, /* char 127 */ \
+ 0, 1, 0, /* char 128 */ \
+ 0, 1, 0, /* char 129 */ \
+ 0, 1, 0, /* char 130 */ \
+ 0, 1, 0, /* char 131 */ \
+ 0, 1, 0, /* char 132 */ \
+ 0, 1, 0, /* char 133 */ \
+ 0, 1, 0, /* char 134 */ \
+ 0, 1, 0, /* char 135 */ \
+ 0, 1, 0, /* char 136 */ \
+ 0, 1, 0, /* char 137 */ \
+ 0, 1, 0, /* char 138 */ \
+ 0, 1, 0, /* char 139 */ \
+ 0, 1, 0, /* char 140 */ \
+ 0, 1, 0, /* char 141 */ \
+ 0, 1, 0, /* char 142 */ \
+ 0, 1, 0, /* char 143 */ \
+ 0, 1, 0, /* char 144 */ \
+ 0, 1, 0, /* char 145 */ \
+ 0, 1, 0, /* char 146 */ \
+ 0, 1, 0, /* char 147 */ \
+ 0, 1, 0, /* char 148 */ \
+ 0, 1, 0, /* char 149 */ \
+ 0, 1, 0, /* char 150 */ \
+ 0, 1, 0, /* char 151 */ \
+ 0, 1, 0, /* char 152 */ \
+ 0, 1, 0, /* char 153 */ \
+ 0, 1, 0, /* char 154 */ \
+ 0, 1, 0, /* char 155 */ \
+ 0, 1, 0, /* char 156 */ \
+ 0, 1, 0, /* char 157 */ \
+ 0, 1, 0, /* char 158 */ \
+ 0, 1, 0, /* char 159 */ \
+ 0, 1, 0, /* char 160 */ \
+ 0, 1, 0, /* char 161 */ \
+ 0, 1, 0, /* char 162 */ \
+ 0, 1, 0, /* char 163 */ \
+ 0, 1, 0, /* char 164 */ \
+ 0, 1, 0, /* char 165 */ \
+ 0, 1, 0, /* char 166 */ \
+ 0, 1, 0, /* char 167 */ \
+ 0, 1, 0, /* char 168 */ \
+ 0, 1, 0, /* char 169 */ \
+ 0, 1, 0, /* char 170 */ \
+ 0, 1, 0, /* char 171 */ \
+ 0, 1, 0, /* char 172 */ \
+ 0, 1, 0, /* char 173 */ \
+ 0, 1, 0, /* char 174 */ \
+ 0, 1, 0, /* char 175 */ \
+ 0, 1, 0, /* char 176 */ \
+ 0, 1, 0, /* char 177 */ \
+ 0, 1, 0, /* char 178 */ \
+ 0, 1, 0, /* char 179 */ \
+ 0, 1, 0, /* char 180 */ \
+ 0, 1, 0, /* char 181 */ \
+ 0, 1, 0, /* char 182 */ \
+ 0, 1, 0, /* char 183 */ \
+ 0, 1, 0, /* char 184 */ \
+ 0, 1, 0, /* char 185 */ \
+ 0, 1, 0, /* char 186 */ \
+ 0, 1, 0, /* char 187 */ \
+ 0, 1, 0, /* char 188 */ \
+ 0, 1, 0, /* char 189 */ \
+ 0, 1, 0, /* char 190 */ \
+ 0, 1, 0, /* char 191 */ \
+ 0, 1, 0, /* char 192 */ \
+ 0, 1, 0, /* char 193 */ \
+ 0, 1, 0, /* char 194 */ \
+ 0, 1, 0, /* char 195 */ \
+ 0, 1, 0, /* char 196 */ \
+ 0, 1, 0, /* char 197 */ \
+ 0, 1, 0, /* char 198 */ \
+ 0, 1, 0, /* char 199 */ \
+ 0, 1, 0, /* char 200 */ \
+ 0, 1, 0, /* char 201 */ \
+ 0, 1, 0, /* char 202 */ \
+ 0, 1, 0, /* char 203 */ \
+ 0, 1, 0, /* char 204 */ \
+ 0, 1, 0, /* char 205 */ \
+ 0, 1, 0, /* char 206 */ \
+ 0, 1, 0, /* char 207 */ \
+ 0, 1, 0, /* char 208 */ \
+ 0, 1, 0, /* char 209 */ \
+ 0, 1, 0, /* char 210 */ \
+ 0, 1, 0, /* char 211 */ \
+ 0, 1, 0, /* char 212 */ \
+ 0, 1, 0, /* char 213 */ \
+ 0, 1, 0, /* char 214 */ \
+ 0, 1, 0, /* char 215 */ \
+ 0, 1, 0, /* char 216 */ \
+ 0, 1, 0, /* char 217 */ \
+ 0, 1, 0, /* char 218 */ \
+ 0, 1, 0, /* char 219 */ \
+ 0, 1, 0, /* char 220 */ \
+ 0, 1, 0, /* char 221 */ \
+ 0, 1, 0, /* char 222 */ \
+ 0, 1, 0, /* char 223 */ \
+ 0, 1, 0, /* char 224 */ \
+ 0, 1, 0, /* char 225 */ \
+ 0, 1, 0, /* char 226 */ \
+ 0, 1, 0, /* char 227 */ \
+ 0, 1, 0, /* char 228 */ \
+ 0, 1, 0, /* char 229 */ \
+ 0, 1, 0, /* char 230 */ \
+ 0, 1, 0, /* char 231 */ \
+ 0, 1, 0, /* char 232 */ \
+ 0, 1, 0, /* char 233 */ \
+ 0, 1, 0, /* char 234 */ \
+ 0, 1, 0, /* char 235 */ \
+ 0, 1, 0, /* char 236 */ \
+ 0, 1, 0, /* char 237 */ \
+ 0, 1, 0, /* char 238 */ \
+ 0, 1, 0, /* char 239 */ \
+ 0, 1, 0, /* char 240 */ \
+ 0, 1, 0, /* char 241 */ \
+ 0, 1, 0, /* char 242 */ \
+ 0, 1, 0, /* char 243 */ \
+ 0, 1, 0, /* char 244 */ \
+ 0, 1, 0, /* char 245 */ \
+ 0, 1, 0, /* char 246 */ \
+ 0, 1, 0, /* char 247 */ \
+ 0, 1, 0, /* char 248 */ \
+ 0, 1, 0, /* char 249 */ \
+ 0, 1, 0, /* char 250 */ \
+ 0, 1, 0, /* char 251 */ \
+ 0, 1, 0, /* char 252 */ \
+ 0, 1, 0, /* char 253 */ \
+ 0, 1, 0, /* char 254 */ \
+ 0, 1, 0, /* char 255 */ \
+ 226)
diff --git a/interface/fonts/tiny_unicode.dm b/interface/fonts/tiny_unicode.dm
new file mode 100644
index 000000000000..d6af265d5182
--- /dev/null
+++ b/interface/fonts/tiny_unicode.dm
@@ -0,0 +1,253 @@
+/// For clean results on map, use only sizing pt, multiples of 12: 12pt 24pt 48pt etc. - Not for use with px sizing
+/// Can be used in TGUI etc, px sizing is pt / 0.75. 12pt = 16px, 24pt = 32px etc.
+
+/// Base font
+/datum/font/tiny_unicode
+ name = "TinyUnicode"
+ font_family = 'interface/fonts/TinyUnicode.ttf'
+
+/// For icon overlays
+/// TinyUnicode 12pt metrics generated using Lummox's dmifontsplus (https://www.byond.com/developer/LummoxJR/DmiFontsPlus)
+/// Note: these variable names have been changed, so you can't straight copy/paste from dmifontsplus.exe
+/datum/font/tiny_unicode/size_12pt
+ name = "TinyUnicode 12pt"
+ height = 13
+ ascent = 11
+ descent = 2
+ average_width = 5
+ max_width = 11
+ overhang = 0
+ in_leading = -3
+ ex_leading = 1
+ default_character = 31
+ start = 30
+ end = 255
+ metrics = list(
+ 1, 5, 0, // char 30
+ 1, 5, 0, // char 31
+ 0, 1, 4, // char 32
+ 0, 1, 1, // char 33
+ 0, 3, 1, // char 34
+ 0, 5, 1, // char 35
+ 0, 4, 1, // char 36
+ 0, 3, 1, // char 37
+ 0, 5, 1, // char 38
+ 0, 1, 1, // char 39
+ 0, 2, 1, // char 40
+ 0, 2, 1, // char 41
+ 0, 3, 1, // char 42
+ 0, 3, 1, // char 43
+ 0, 2, 1, // char 44
+ 0, 3, 1, // char 45
+ 0, 1, 1, // char 46
+ 0, 3, 1, // char 47
+ 0, 4, 1, // char 48
+ 0, 2, 1, // char 49
+ 0, 4, 1, // char 50
+ 0, 4, 1, // char 51
+ 0, 4, 1, // char 52
+ 0, 4, 1, // char 53
+ 0, 4, 1, // char 54
+ 0, 4, 1, // char 55
+ 0, 4, 1, // char 56
+ 0, 4, 1, // char 57
+ 0, 1, 1, // char 58
+ 0, 2, 1, // char 59
+ 0, 2, 1, // char 60
+ 0, 4, 1, // char 61
+ 0, 2, 1, // char 62
+ 0, 4, 1, // char 63
+ 0, 7, 1, // char 64
+ 0, 4, 1, // char 65
+ 0, 4, 1, // char 66
+ 0, 3, 1, // char 67
+ 0, 4, 1, // char 68
+ 0, 3, 1, // char 69
+ 0, 3, 1, // char 70
+ 0, 4, 1, // char 71
+ 0, 4, 1, // char 72
+ 0, 3, 1, // char 73
+ 0, 4, 1, // char 74
+ 0, 4, 1, // char 75
+ 0, 3, 1, // char 76
+ 0, 5, 1, // char 77
+ 0, 4, 1, // char 78
+ 0, 4, 1, // char 79
+ 0, 4, 1, // char 80
+ 0, 4, 1, // char 81
+ 0, 4, 1, // char 82
+ 0, 4, 1, // char 83
+ 0, 3, 1, // char 84
+ 0, 4, 1, // char 85
+ 0, 4, 1, // char 86
+ 0, 5, 1, // char 87
+ 0, 4, 1, // char 88
+ 0, 4, 1, // char 89
+ 0, 3, 1, // char 90
+ 0, 2, 1, // char 91
+ 0, 3, 1, // char 92
+ 0, 2, 1, // char 93
+ 0, 3, 1, // char 94
+ 0, 5, 1, // char 95
+ 0, 2, 1, // char 96
+ 0, 4, 1, // char 97
+ 0, 4, 1, // char 98
+ 0, 3, 1, // char 99
+ 0, 4, 1, // char 100
+ 0, 4, 1, // char 101
+ 0, 3, 1, // char 102
+ 0, 4, 1, // char 103
+ 0, 4, 1, // char 104
+ 0, 1, 1, // char 105
+ 0, 2, 1, // char 106
+ 0, 4, 1, // char 107
+ 0, 1, 1, // char 108
+ 0, 5, 1, // char 109
+ 0, 4, 1, // char 110
+ 0, 4, 1, // char 111
+ 0, 4, 1, // char 112
+ 0, 4, 1, // char 113
+ 0, 3, 1, // char 114
+ 0, 4, 1, // char 115
+ 0, 3, 1, // char 116
+ 0, 4, 1, // char 117
+ 0, 4, 1, // char 118
+ 0, 5, 1, // char 119
+ 0, 3, 1, // char 120
+ 0, 4, 1, // char 121
+ 0, 4, 1, // char 122
+ 0, 3, 1, // char 123
+ 0, 1, 1, // char 124
+ 0, 3, 1, // char 125
+ 0, 5, 1, // char 126
+ 1, 5, 0, // char 127
+ 0, 4, 1, // char 128
+ 1, 5, 0, // char 129
+ 1, 5, 0, // char 130
+ 1, 5, 0, // char 131
+ 1, 5, 0, // char 132
+ 1, 5, 0, // char 133
+ 1, 5, 0, // char 134
+ 1, 5, 0, // char 135
+ 1, 5, 0, // char 136
+ 0, 5, 1, // char 137
+ 1, 5, 0, // char 138
+ 1, 5, 0, // char 139
+ 0, 6, 1, // char 140
+ 1, 5, 0, // char 141
+ 1, 5, 0, // char 142
+ 1, 5, 0, // char 143
+ 1, 5, 0, // char 144
+ 1, 5, 0, // char 145
+ 1, 5, 0, // char 146
+ 1, 5, 0, // char 147
+ 1, 5, 0, // char 148
+ 0, 2, 1, // char 149
+ 1, 5, 0, // char 150
+ 1, 5, 0, // char 151
+ 1, 5, 0, // char 152
+ 0, 4, 1, // char 153
+ 1, 5, 0, // char 154
+ 1, 5, 0, // char 155
+ 1, 5, 0, // char 156
+ 1, 5, 0, // char 157
+ 1, 5, 0, // char 158
+ 0, 4, 1, // char 159
+ 1, 5, 0, // char 160
+ 0, 1, 1, // char 161
+ 0, 4, 1, // char 162
+ 0, 4, 1, // char 163
+ 0, 5, 1, // char 164
+ 0, 3, 1, // char 165
+ 0, 1, 1, // char 166
+ 0, 4, 1, // char 167
+ 0, 3, 1, // char 168
+ 0, 2, 1, // char 169
+ 0, 8, 1, // char 170
+ 0, 4, 1, // char 171
+ 0, 4, 1, // char 172
+ 1, 5, 0, // char 173
+ 0, 2, 1, // char 174
+ 0, 4, 1, // char 175
+ 0, 3, 1, // char 176
+ 0, 3, 1, // char 177
+ 0, 2, 1, // char 178
+ 0, 2, 1, // char 179
+ 0, 2, 1, // char 180
+ 0, 4, 1, // char 181
+ 0, 5, 1, // char 182
+ 1, 1, 1, // char 183
+ 0, 8, 1, // char 184
+ 0, 2, 1, // char 185
+ 0, 2, 1, // char 186
+ 0, 4, 1, // char 187
+ 0, 7, 1, // char 188
+ 0, 8, 1, // char 189
+ 0, 8, 1, // char 190
+ 0, 4, 1, // char 191
+ 0, 4, 1, // char 192
+ 0, 4, 1, // char 193
+ 0, 4, 1, // char 194
+ 0, 4, 1, // char 195
+ 0, 4, 1, // char 196
+ 0, 4, 1, // char 197
+ 0, 6, 1, // char 198
+ 0, 3, 1, // char 199
+ 0, 3, 1, // char 200
+ 0, 3, 1, // char 201
+ 0, 3, 1, // char 202
+ 0, 3, 1, // char 203
+ 0, 3, 1, // char 204
+ 0, 3, 1, // char 205
+ 0, 3, 1, // char 206
+ 0, 3, 1, // char 207
+ 0, 10, 1, // char 208
+ 0, 4, 1, // char 209
+ 0, 4, 1, // char 210
+ 0, 4, 1, // char 211
+ 0, 4, 1, // char 212
+ 0, 4, 1, // char 213
+ 0, 4, 1, // char 214
+ 0, 3, 1, // char 215
+ 0, 5, 1, // char 216
+ 0, 4, 1, // char 217
+ 0, 4, 1, // char 218
+ 0, 4, 1, // char 219
+ 0, 4, 1, // char 220
+ 0, 4, 1, // char 221
+ 0, 3, 1, // char 222
+ 0, 3, 1, // char 223
+ 0, 4, 1, // char 224
+ 0, 4, 1, // char 225
+ 0, 4, 1, // char 226
+ 0, 4, 1, // char 227
+ 0, 4, 1, // char 228
+ 0, 4, 1, // char 229
+ 0, 7, 1, // char 230
+ 0, 3, 1, // char 231
+ 0, 4, 1, // char 232
+ 0, 4, 1, // char 233
+ 0, 4, 1, // char 234
+ 0, 4, 1, // char 235
+ 0, 2, 1, // char 236
+ 0, 2, 1, // char 237
+ 0, 3, 1, // char 238
+ 0, 3, 1, // char 239
+ 0, 5, 1, // char 240
+ 0, 4, 1, // char 241
+ 0, 4, 1, // char 242
+ 0, 4, 1, // char 243
+ 0, 4, 1, // char 244
+ 0, 4, 1, // char 245
+ 0, 4, 1, // char 246
+ 0, 5, 1, // char 247
+ 0, 4, 1, // char 248
+ 0, 4, 1, // char 249
+ 0, 4, 1, // char 250
+ 0, 4, 1, // char 251
+ 0, 4, 1, // char 252
+ 0, 4, 1, // char 253
+ 0, 10, 1, // char 254
+ 0, 4, 1, // char 255
+ 226
+ )
diff --git a/interface/fonts/vcr_osd_mono.dm b/interface/fonts/vcr_osd_mono.dm
new file mode 100644
index 000000000000..301d90d2f7ea
--- /dev/null
+++ b/interface/fonts/vcr_osd_mono.dm
@@ -0,0 +1,3 @@
+/datum/font/vcr_osd_mono
+ name = "VCR OSD Mono"
+ font_family = 'interface/fonts/VCR_OSD_Mono.ttf'
diff --git a/interface/license.txt b/interface/license.txt
deleted file mode 100644
index 5f74403c50af..000000000000
--- a/interface/license.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-VCR OSD Mono created by Riciery Leal/mrmanet. Website indicates 100% free, author confirms it's free for all to use.
-(https://www.dafont.com/font-comment.php?file=vcr_osd_mono)
diff --git a/interface/skin.dmf b/interface/skin.dmf
index 8ab3258f9a6b..cc5fa5de9af9 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -110,12 +110,12 @@ window "mapwindow"
size = 640x480
anchor1 = 0,0
anchor2 = 100,100
- font-family = "Arial"
- font-size = 7
+ font-family = "Grand9K Pixel"
+ font-size = 6pt
is-default = true
right-click = true
saved-params = "zoom;letterbox;zoom-mode"
- style = ".center { text-align: center; } .maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; } .command_headset { font-weight: bold;\tfont-size: 8px; } .small { font-size: 6px; } .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .greentext { color: #00FF00; font-size: 7px; } .redtext { color: #FF0000; font-size: 7px; } .clown { color: #FF69Bf; font-size: 7px; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-size: 6px; }"
+ style = ".center { text-align: center; } .maptext { font-family: 'Grand9K Pixel'; font-size: 6pt; -dm-text-outline: 1px black; color: white; line-height: 1.0; } .command_headset { font-weight: bold; } .context { font-family: 'Pixellari'; font-size: 12pt; -dm-text-outline: 1px black; } .subcontext { font-family: 'TinyUnicode'; font-size: 12pt; line-height: 0.75; } .small { font-family: 'Spess Font'; font-size: 6pt; line-height: 1.4; } .big { font-family: 'Pixellari'; font-size: 12pt; } .reallybig { font-size: 12pt; } .extremelybig { font-size: 12pt; } .greentext { color: #00FF00; font-size: 6pt; } .redtext { color: #FF0000; font-size: 6pt; } .clown { color: #FF69BF; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-family: 'Spess Font'; font-size: 6pt; line-height: 1.4; }"
elem "status_bar"
type = LABEL
pos = 0,464
diff --git a/monkestation/code/modules/art_sci_overrides/faults/_fault.dm b/monkestation/code/modules/art_sci_overrides/faults/_fault.dm
index 5848ba4104e5..607340139449 100644
--- a/monkestation/code/modules/art_sci_overrides/faults/_fault.dm
+++ b/monkestation/code/modules/art_sci_overrides/faults/_fault.dm
@@ -14,4 +14,3 @@
/datum/artifact_fault/on_trigger(datum/component/artifact/component)
if(component.active)
component.artifact_deactivate()
-
diff --git a/monkestation/code/modules/art_sci_overrides/faults/explosion.dm b/monkestation/code/modules/art_sci_overrides/faults/explosion.dm
index 819b66fa76b7..f7c028e58860 100644
--- a/monkestation/code/modules/art_sci_overrides/faults/explosion.dm
+++ b/monkestation/code/modules/art_sci_overrides/faults/explosion.dm
@@ -3,13 +3,10 @@
trigger_chance = 3
visible_message = "reaches a catastrophic overload, cracks forming at its surface!"
-
/datum/artifact_fault/explosion/on_trigger(datum/component/artifact/component)
component.holder.Shake(duration = 5 SECONDS, shake_interval = 0.08 SECONDS)
addtimer(CALLBACK(src, PROC_REF(payload), component), 5 SECONDS)
-
/datum/artifact_fault/explosion/proc/payload(datum/component/artifact/component)
explosion(component.holder, light_impact_range = 2, explosion_cause = src)
qdel(component.holder)
-
\ No newline at end of file
diff --git a/monkestation/code/modules/art_sci_overrides/faults/ignite.dm b/monkestation/code/modules/art_sci_overrides/faults/ignite.dm
index ca4e5a61d6bc..631f17f850ae 100644
--- a/monkestation/code/modules/art_sci_overrides/faults/ignite.dm
+++ b/monkestation/code/modules/art_sci_overrides/faults/ignite.dm
@@ -4,6 +4,11 @@
visible_message = "starts rapidly heating up while covering everything around it in something that seems to be oil."
/datum/artifact_fault/ignite/on_trigger(datum/component/artifact/component)
- for(var/mob/living/living in range(rand(3, 5), component.parent))
+ var/center_turf = get_turf(component.parent)
+
+ if(!center_turf)
+ CRASH("[src] had attempted to trigger, but failed to find the center turf!")
+
+ for(var/mob/living/living in range(rand(3, 5), center_turf))
living.adjust_fire_stacks(10)
living.ignite_mob()
diff --git a/monkestation/code/modules/art_sci_overrides/faults/just_death.dm b/monkestation/code/modules/art_sci_overrides/faults/just_death.dm
index 066b64cf5f28..8b7b2f8d3c45 100644
--- a/monkestation/code/modules/art_sci_overrides/faults/just_death.dm
+++ b/monkestation/code/modules/art_sci_overrides/faults/just_death.dm
@@ -3,11 +3,16 @@
trigger_chance = 1
visible_message = "blows someone up with mind."
-
/datum/artifact_fault/death/on_trigger(datum/component/artifact/component)
var/list/mobs = list()
var/mob/living/carbon/human
- for(var/mob/living/carbon/mob in range(rand(3, 4), component.holder))
+
+ var/center_turf = get_turf(component.parent)
+
+ if(!center_turf)
+ CRASH("[src] had attempted to trigger, but failed to find the center turf!")
+
+ for(var/mob/living/carbon/mob in range(rand(3, 4), center_turf))
mobs += mob
human = pick(mobs)
if(!human)
diff --git a/monkestation/code/modules/art_sci_overrides/faults/reagents.dm b/monkestation/code/modules/art_sci_overrides/faults/reagents.dm
index 886d3fc97398..c2ab103e18cb 100644
--- a/monkestation/code/modules/art_sci_overrides/faults/reagents.dm
+++ b/monkestation/code/modules/art_sci_overrides/faults/reagents.dm
@@ -8,7 +8,13 @@
. = ..()
if(!length(reagents))
return
- for(var/mob/living/carbon/living in range(rand(3, 5), component.parent))
+
+ var/center_turf = get_turf(component.parent)
+
+ if(!center_turf)
+ CRASH("[src] had attempted to trigger, but failed to find the center turf!")
+
+ for(var/mob/living/carbon/living in range(rand(3, 5), center_turf))
living.reagents.add_reagent(pick(reagents), rand(1, 5))
to_chat(living, span_warning("You feel a soft prick."))
diff --git a/monkestation/code/modules/art_sci_overrides/faults/say.dm b/monkestation/code/modules/art_sci_overrides/faults/say.dm
index a032caf8a630..fca9f1d6e653 100644
--- a/monkestation/code/modules/art_sci_overrides/faults/say.dm
+++ b/monkestation/code/modules/art_sci_overrides/faults/say.dm
@@ -7,7 +7,12 @@
if(!length(speech))
return
- for(var/mob/living/living in range(rand(7, 10), component.parent))
+ var/center_turf = get_turf(component.parent)
+
+ if(!center_turf)
+ CRASH("[src] had attempted to trigger, but failed to find the center turf!")
+
+ for(var/mob/living/living in range(rand(7, 10), center_turf))
if(prob(50))
living.say("; [pick(speech)]")
else
diff --git a/monkestation/code/modules/art_sci_overrides/faults/warps.dm b/monkestation/code/modules/art_sci_overrides/faults/warps.dm
index 54b298d59f99..6014fc2a03eb 100644
--- a/monkestation/code/modules/art_sci_overrides/faults/warps.dm
+++ b/monkestation/code/modules/art_sci_overrides/faults/warps.dm
@@ -8,7 +8,11 @@
if(!length(warp_areas))
warp_areas = GLOB.the_station_areas
var/turf/safe_turf = get_safe_random_station_turf(warp_areas)
+ var/center_turf = get_turf(component.parent)
- for(var/mob/living/living in range(rand(3, 5), component.parent))
+ if(!center_turf)
+ CRASH("[src] had attempted to trigger, but failed to find the center turf!")
+
+ for(var/mob/living/living in range(rand(3, 5), center_turf))
living.forceMove(safe_turf)
to_chat(living, span_warning("You feel woozy after being warped around."))
diff --git a/monkestation/code/modules/art_sci_overrides/faults/whispers.dm b/monkestation/code/modules/art_sci_overrides/faults/whispers.dm
index abd09bad75f2..b85fd5b57427 100644
--- a/monkestation/code/modules/art_sci_overrides/faults/whispers.dm
+++ b/monkestation/code/modules/art_sci_overrides/faults/whispers.dm
@@ -7,5 +7,10 @@
if(!length(whispers))
return
- for(var/mob/living/living in range(rand(7, 10), component.parent))
+ var/center_turf = get_turf(component.parent)
+
+ if(!center_turf)
+ CRASH("[src] had attempted to trigger, but failed to find the center turf!")
+
+ for(var/mob/living/living in range(rand(7, 10), center_turf))
to_chat(living, span_hear("[pick(whispers)]"))
diff --git a/monkestation/code/modules/bunny_wizard/outfits.dm b/monkestation/code/modules/bunny_wizard/outfits.dm
index ecf8a3423e3d..ddebbb22321d 100644
--- a/monkestation/code/modules/bunny_wizard/outfits.dm
+++ b/monkestation/code/modules/bunny_wizard/outfits.dm
@@ -23,6 +23,7 @@
no_drops += equipped_on.get_item_by_slot(ITEM_SLOT_OCLOTHING)
no_drops += equipped_on.get_item_by_slot(ITEM_SLOT_HEAD)
no_drops += equipped_on.get_item_by_slot(ITEM_SLOT_NECK)
+ no_drops += equipped_on.get_item_by_slot(ITEM_SLOT_GLOVES)
for(var/obj/item/trait_needed as anything in no_drops)
ADD_TRAIT(trait_needed, TRAIT_NODROP, CURSED_ITEM_TRAIT(trait_needed.type))
trait_needed.name = "cursed " + trait_needed.name
diff --git a/monkestation/code/modules/client/verbs.dm b/monkestation/code/modules/client/verbs.dm
index 4c11668236dd..505627ebbf73 100644
--- a/monkestation/code/modules/client/verbs.dm
+++ b/monkestation/code/modules/client/verbs.dm
@@ -47,13 +47,10 @@ GLOBAL_LIST_INIT(antag_token_config, load_antag_token_config())
return
var/list/chosen_tier = GLOB.antag_token_config[tier]
- to_chat(world, span_boldbig("tier=[tier] / [length(chosen_tier)]"))
var/antag_key = tgui_input_list(src, "Choose an Antagonist", "Spend Tokens", chosen_tier)
- to_chat(world, span_boldbig("antag_key=[antag_key]"))
if(!antag_key || !chosen_tier[antag_key])
return
var/datum/antagonist/chosen_antagonist = chosen_tier[antag_key]
- to_chat(world, span_boldbig("chosen_antagonist=[chosen_antagonist]"))
client_token_holder.queued_donor = using_donor
client_token_holder.in_queued_tier = tier
diff --git a/monkestation/code/modules/clothing/~donator/clothing.dm b/monkestation/code/modules/clothing/~donator/clothing.dm
index f91f8358c722..aada29f2b240 100644
--- a/monkestation/code/modules/clothing/~donator/clothing.dm
+++ b/monkestation/code/modules/clothing/~donator/clothing.dm
@@ -44,7 +44,7 @@
/obj/item/canvas/drawingtablet/ui_action_click(mob/user, action)
if(istype(action, /datum/action/item_action/dtselectcolor))
- currentcolor = input(user, "", "Choose Color", currentcolor) as color|null
+ currentcolor = tgui_color_picker(user, "", "Choose Color", currentcolor)
else if(istype(action, /datum/action/item_action/dtcolormenu))
var/list/selects = colors.Copy()
selects["Save"] = "Save"
diff --git a/monkestation/code/modules/donator/README.txt b/monkestation/code/modules/donator/README.txt
index c2c8b19a8f1d..36fc31477708 100644
--- a/monkestation/code/modules/donator/README.txt
+++ b/monkestation/code/modules/donator/README.txt
@@ -33,6 +33,8 @@ This PR adds adds donator specific items to the game. Also migrates old donator
-Code and sprites done by Lmenvs.
+Code and most sprites done by Lmenvs.
+Lamb cloak by Dimwhat.
+Sprites for Zed's clothing done by CannibalHunter.
diff --git a/monkestation/code/modules/donator/code/datum/loadout.dm b/monkestation/code/modules/donator/code/datum/loadout.dm
index 8523db21608c..815134d6cc0b 100644
--- a/monkestation/code/modules/donator/code/datum/loadout.dm
+++ b/monkestation/code/modules/donator/code/datum/loadout.dm
@@ -240,6 +240,31 @@
donator_only = TRUE
requires_purchase = FALSE
+/datum/loadout_item/under/miscellaneous/aotuniform
+ name = "Survey Corps Uniform"
+ item_path = /obj/item/clothing/under/costume/aotuniform
+ donator_only = TRUE
+ requires_purchase = FALSE
+
+/datum/loadout_item/pocket_items/donator/plantcrab
+ name = "Pet Delivery Beacon - Plant Crab"
+ item_path = /obj/item/choice_beacon/pet/donator/plantcrab
+ donator_only = TRUE
+ requires_purchase = FALSE
+ ckeywhitelist = list("Rickdude1231")
+
+/datum/loadout_item/effects/plantcrab
+ ckeywhitelist = list("Rickdude1231")
+ name = "Plant Crab Transformation"
+ item_path = /obj/item/effect_granter/donator/plant_crab
+ requires_purchase = FALSE
+
+/datum/loadout_item/toys/liefflos
+ name = "Lief Flos Plush"
+ item_path = /obj/item/toy/plush/liefflos
+ donator_only = TRUE
+ requires_purchase = FALSE
+
//kon chieko
/datum/loadout_item/head/bells
name = "Hair ribbons with bells"
@@ -268,7 +293,7 @@
donator_only = TRUE
requires_purchase = FALSE
-/datum/loadout_item/toys/puppycat
+/datum/loadout_item/toys/jackfrost
name = "Jack Frost Plush"
item_path = /obj/item/toy/plush/jackfrost
donator_only = TRUE
@@ -284,5 +309,13 @@
/datum/loadout_item/effects/void_butterfly
ckeywhitelist = list("tonymcsp")
name = "Void Butterfly Transformation"
- item_path = /obj/item/effect_granter/donator/void_butterfly/
+ item_path = /obj/item/effect_granter/donator/void_butterfly
+ requires_purchase = FALSE
+
+
+//Django
+/datum/loadout_item/suit/lambcloak
+ name = "Lamb's Cloak"
+ item_path = /obj/item/clothing/suit/lambcloak
+ donator_only = TRUE
requires_purchase = FALSE
diff --git a/monkestation/code/modules/donator/code/item/choice_beacon.dm b/monkestation/code/modules/donator/code/item/choice_beacon.dm
index 8e2ee55c063c..074a47087946 100644
--- a/monkestation/code/modules/donator/code/item/choice_beacon.dm
+++ b/monkestation/code/modules/donator/code/item/choice_beacon.dm
@@ -70,3 +70,8 @@
name = "Void Butterfly"
default_name = "Void Butterfly"
donator_pet = /mob/living/basic/butterfly/void/spacial
+
+/obj/item/choice_beacon/pet/donator/plantcrab
+ name = "Plant Crab"
+ default_name = "Plant Crab"
+ donator_pet = /mob/living/basic/crab/plant
diff --git a/monkestation/code/modules/donator/code/item/clothing.dm b/monkestation/code/modules/donator/code/item/clothing.dm
index 97d052f6f832..f3ff465f16f5 100644
--- a/monkestation/code/modules/donator/code/item/clothing.dm
+++ b/monkestation/code/modules/donator/code/item/clothing.dm
@@ -188,6 +188,17 @@
/obj/item/toy,
)
+/obj/item/clothing/under/costume/aotuniform
+ name = "\improper Survey Corps Uniform"
+ desc = "The uniform of a Survey Corps member."
+ icon = 'monkestation/code/modules/donator/icons/obj/clothing.dmi'
+ worn_icon = 'monkestation/code/modules/donator/icons/mob/clothing.dmi'
+ icon_state = "aotuniform"
+ worn_icon_state = "aotuniform"
+ inhand_icon_state = null
+ body_parts_covered = CHEST|ARMS|LEGS
+
+
/obj/item/clothing/head/hooded/aotcloak
name = "survey corps cloak hood"
desc = "A cozy winter hood attached to a heavy winter jacket."
@@ -231,3 +242,15 @@
icon_state = "violet_jacket"
inhand_icon_state = null
body_parts_covered = CHEST|ARMS
+
+
+/obj/item/clothing/suit/lambcloak
+ name = "lamb's cloak"
+ desc = "A brilliant red cloak adorned with a bell."
+ icon = 'monkestation/code/modules/donator/icons/obj/clothing.dmi'
+ worn_icon = 'monkestation/code/modules/donator/icons/mob/clothing.dmi'
+ icon_state = "lambcloak"
+ body_parts_covered = CHEST|ARMS
+ cold_protection = CHEST|ARMS
+ layer = NECK_LAYER
+
diff --git a/monkestation/code/modules/donator/code/item/effects.dm b/monkestation/code/modules/donator/code/item/effects.dm
index d439d5aa18de..f6ba30f474b0 100644
--- a/monkestation/code/modules/donator/code/item/effects.dm
+++ b/monkestation/code/modules/donator/code/item/effects.dm
@@ -64,3 +64,9 @@
name = "void butterfly transformation"
icon_state = "void_butterfly"
animal_transformation = /mob/living/basic/butterfly/void/spacial
+
+//rickdude
+/obj/item/effect_granter/donator/plant_crab
+ name = "plantcrab transformation"
+ icon_state = "crab_plant"
+ animal_transformation = /mob/living/basic/crab/plant
diff --git a/monkestation/code/modules/donator/code/item/plush.dm b/monkestation/code/modules/donator/code/item/plush.dm
index 593afad90f41..b2da3d9f5532 100644
--- a/monkestation/code/modules/donator/code/item/plush.dm
+++ b/monkestation/code/modules/donator/code/item/plush.dm
@@ -55,3 +55,10 @@
desc = "A plush of Jack Frost. ~Handcrafted lovingly for tonymcsp"
icon = 'monkestation/code/modules/donator/icons/obj/plushes.dmi'
icon_state = "jack_frost"
+
+/obj/item/toy/plush/liefflos
+ name = "lief flos plush"
+ desc = "A plush of the Floran Lief Flos. ~Handcrafted lovingly for rickdude"
+ icon = 'monkestation/code/modules/donator/icons/obj/plushes.dmi'
+ icon_state = "lief_flos"
+
diff --git a/monkestation/code/modules/donator/code/mob/pets.dm b/monkestation/code/modules/donator/code/mob/pets.dm
index 38665bbdd648..08331de53b3e 100644
--- a/monkestation/code/modules/donator/code/mob/pets.dm
+++ b/monkestation/code/modules/donator/code/mob/pets.dm
@@ -126,3 +126,12 @@
/mob/living/basic/butterfly/void/spacial
fixed_color = TRUE
+
+/mob/living/basic/crab/plant
+ name = "Plant crab"
+ desc = "Is it a crab made of plant or a plant made of crab?"
+ icon = 'monkestation/code/modules/donator/icons/mob/pets.dmi'
+ icon_state = "crab_plant"
+ icon_living = "crab_plant"
+ icon_dead = "crab_plant_dead"
+ gold_core_spawnable = NO_SPAWN
diff --git a/monkestation/code/modules/donator/icons/mob/clothing.dmi b/monkestation/code/modules/donator/icons/mob/clothing.dmi
index a4c6d9eeded3..bf7530cf5d63 100644
Binary files a/monkestation/code/modules/donator/icons/mob/clothing.dmi and b/monkestation/code/modules/donator/icons/mob/clothing.dmi differ
diff --git a/monkestation/code/modules/donator/icons/mob/pets.dmi b/monkestation/code/modules/donator/icons/mob/pets.dmi
index 594f3c184c76..348824e1c7b2 100644
Binary files a/monkestation/code/modules/donator/icons/mob/pets.dmi and b/monkestation/code/modules/donator/icons/mob/pets.dmi differ
diff --git a/monkestation/code/modules/donator/icons/obj/clothing.dmi b/monkestation/code/modules/donator/icons/obj/clothing.dmi
index 26886ccb21f3..facec0a55baf 100644
Binary files a/monkestation/code/modules/donator/icons/obj/clothing.dmi and b/monkestation/code/modules/donator/icons/obj/clothing.dmi differ
diff --git a/monkestation/code/modules/donator/icons/obj/plushes.dmi b/monkestation/code/modules/donator/icons/obj/plushes.dmi
index 83f81ea59204..21b301c38fd2 100644
Binary files a/monkestation/code/modules/donator/icons/obj/plushes.dmi and b/monkestation/code/modules/donator/icons/obj/plushes.dmi differ
diff --git a/monkestation/code/modules/mob/living/carbon/human/species_type/arachnid.dm b/monkestation/code/modules/mob/living/carbon/human/species_type/arachnid.dm
index 4bd428ad34ec..bc6c6ab1ccbc 100644
--- a/monkestation/code/modules/mob/living/carbon/human/species_type/arachnid.dm
+++ b/monkestation/code/modules/mob/living/carbon/human/species_type/arachnid.dm
@@ -3,7 +3,7 @@
plural_form = "Arachnids"
id = SPECIES_ARACHNIDS
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN
- sexes = FALSE
+ visual_gender = FALSE
species_traits = list(
MUTCOLORS,
EYECOLOR,
diff --git a/monkestation/code/modules/mob/living/carbon/human/species_type/ethereal.dm b/monkestation/code/modules/mob/living/carbon/human/species_type/ethereal.dm
index ecfb7a7207d0..71b7056d0a39 100644
--- a/monkestation/code/modules/mob/living/carbon/human/species_type/ethereal.dm
+++ b/monkestation/code/modules/mob/living/carbon/human/species_type/ethereal.dm
@@ -1,3 +1,28 @@
/datum/species/ethereal/get_laugh_sound(mob/living/carbon/human/ethereal)
return 'monkestation/sound/voice/laugh/ethereal/ethereal_laugh_1.ogg'
+/datum/species/ethereal
+ sexes = TRUE
+ species_traits = list(
+ DYNCOLORS,
+ NO_UNDERWEAR,
+ HAIR,
+ FACEHAIR,
+ )
+
+/datum/species/ethereal/on_species_gain(mob/living/carbon/new_ethereal, datum/species/old_species, pref_load)
+ . = ..()
+ RegisterSignal(new_ethereal, COMSIG_ATOM_AFTER_ATTACKEDBY, PROC_REF(on_after_attackedby))
+
+/datum/species/ethereal/on_species_loss(mob/living/carbon/human/former_ethereal, datum/species/new_species, pref_load)
+ . = ..()
+ UnregisterSignal(former_ethereal, COMSIG_ATOM_AFTER_ATTACKEDBY)
+
+/datum/species/ethereal/proc/on_after_attackedby(mob/living/lightbulb, obj/item/item, mob/living/user, proximity_flag, click_parameters)
+ SIGNAL_HANDLER
+ var/obj/item/clothing/mask/cigarette/cig = item
+ if(!proximity_flag || !istype(cig) || !istype(user) || cig.lit)
+ return
+ cig.light()
+ user.visible_message(span_notice("[user] quickly strikes [item] across [lightbulb]'s skin, [lightbulb.p_their()] warmth lighting it!"))
+ return COMPONENT_NO_AFTERATTACK
diff --git a/monkestation/code/modules/mob/living/carbon/human/species_type/floran.dm b/monkestation/code/modules/mob/living/carbon/human/species_type/floran.dm
index 931c68364596..2c5b4adfa92f 100644
--- a/monkestation/code/modules/mob/living/carbon/human/species_type/floran.dm
+++ b/monkestation/code/modules/mob/living/carbon/human/species_type/floran.dm
@@ -2,7 +2,7 @@
name = "\improper Floran"
plural_form = "Florans"
id = SPECIES_FLORAN
- sexes = FALSE
+ sexes = TRUE
species_traits = list(
MUTCOLORS,
MUTCOLORS_SECONDARY,
diff --git a/monkestation/code/modules/modular_guns/__base_attachment.dm b/monkestation/code/modules/modular_guns/__base_attachment.dm
index a8b44ab8587e..ecc02b42afdd 100644
--- a/monkestation/code/modules/modular_guns/__base_attachment.dm
+++ b/monkestation/code/modules/modular_guns/__base_attachment.dm
@@ -49,7 +49,7 @@
/obj/item/attachment/AltClick(mob/user)
. = ..()
if(attachment_flags & ATTACHMENT_COLORABLE)
- var/new_choice = input(user,"","Choose Color",attachment_color) as color
+ var/new_choice = tgui_color_picker(user,"","Choose Color",attachment_color)
if(new_choice == null)
return
attachment_color = new_choice
diff --git a/monkestation/code/modules/reagents/reagent_containers/drinks.dm b/monkestation/code/modules/reagents/reagent_containers/drinks.dm
new file mode 100644
index 000000000000..f9f99f185830
--- /dev/null
+++ b/monkestation/code/modules/reagents/reagent_containers/drinks.dm
@@ -0,0 +1,8 @@
+/obj/item/reagent_containers/cup/glass/ice/Initialize(mapload, vol)
+ . = ..()
+ reagents.add_reagent(reagent = /datum/reagent/consumable/ice, amount = 30, reagtemp = WATER_MATTERSTATE_CHANGE_TEMP)
+
+/obj/item/reagent_containers/cup/glass/ice/prison/Initialize(mapload, vol)
+ . = ..()
+ reagents.remove_reagent(reagent = /datum/reagent/consumable/ice, amount = 5)
+ reagents.add_reagent(reagent = /datum/reagent/consumable/liquidgibs, amount = 5, reagtemp = WATER_MATTERSTATE_CHANGE_TEMP)
diff --git a/monkestation/code/modules/storytellers/converted_events/solo/monsterhunter.dm b/monkestation/code/modules/storytellers/converted_events/solo/monsterhunter.dm
index 725c3e8de922..5947d45f23f6 100644
--- a/monkestation/code/modules/storytellers/converted_events/solo/monsterhunter.dm
+++ b/monkestation/code/modules/storytellers/converted_events/solo/monsterhunter.dm
@@ -1,6 +1,7 @@
#define MINIMUM_MONSTERS_REQUIRED 2
/datum/round_event_control/antagonist/solo/monsterhunter
+ name = "Monster Hunters"
track = EVENT_TRACK_MAJOR //being an anrtag event is for backend reasons, the event itself is major
antag_flag = ROLE_MONSTERHUNTER
tags = list(TAG_MAGICAL, TAG_TARGETED, TAG_COMBAT)
diff --git a/monkestation/code/modules/surgery/bodyparts/ethereal_bodyparts.dm b/monkestation/code/modules/surgery/bodyparts/ethereal_bodyparts.dm
new file mode 100644
index 000000000000..2a2ea1a8545a
--- /dev/null
+++ b/monkestation/code/modules/surgery/bodyparts/ethereal_bodyparts.dm
@@ -0,0 +1,19 @@
+/obj/item/bodypart/head/ethereal
+ icon_greyscale ='monkestation/icons/mob/species/ethereal/bodyparts.dmi'
+ is_dimorphic = TRUE
+
+/obj/item/bodypart/chest/ethereal
+ icon_greyscale = 'monkestation/icons/mob/species/ethereal/bodyparts.dmi'
+ is_dimorphic = TRUE
+
+/obj/item/bodypart/arm/left/ethereal
+ icon_greyscale = 'monkestation/icons/mob/species/ethereal/bodyparts.dmi'
+
+/obj/item/bodypart/arm/right/ethereal
+ icon_greyscale = 'monkestation/icons/mob/species/ethereal/bodyparts.dmi'
+
+/obj/item/bodypart/leg/right/ethereal
+ icon_greyscale = 'monkestation/icons/mob/species/ethereal/bodyparts.dmi'
+
+/obj/item/bodypart/leg/right/ethereal
+ icon_greyscale = 'monkestation/icons/mob/species/ethereal/bodyparts.dmi'
diff --git a/monkestation/code/modules/surgery/bodyparts/floran_bodyparts.dm b/monkestation/code/modules/surgery/bodyparts/floran_bodyparts.dm
index 5fa72f52c131..912d13d770b3 100644
--- a/monkestation/code/modules/surgery/bodyparts/floran_bodyparts.dm
+++ b/monkestation/code/modules/surgery/bodyparts/floran_bodyparts.dm
@@ -6,7 +6,7 @@
/obj/item/bodypart/chest/floran
icon_greyscale = 'monkestation/icons/mob/species/floran/bodyparts.dmi'
limb_id = SPECIES_FLORAN
- is_dimorphic = FALSE
+ is_dimorphic = TRUE
/obj/item/bodypart/arm/left/floran
icon_greyscale = 'monkestation/icons/mob/species/floran/bodyparts.dmi'
diff --git a/monkestation/code/modules/surgery/bodyparts/simian_bodyparts.dm b/monkestation/code/modules/surgery/bodyparts/simian_bodyparts.dm
index 6f9d58a8a585..da57550c7dc7 100644
--- a/monkestation/code/modules/surgery/bodyparts/simian_bodyparts.dm
+++ b/monkestation/code/modules/surgery/bodyparts/simian_bodyparts.dm
@@ -37,11 +37,13 @@
husk_type = "simian"
limb_id = SPECIES_SIMIAN
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ORGANIC | BODYTYPE_CUSTOM
+ footprint_sprite = FOOTPRINT_SPRITE_PAWS
/obj/item/bodypart/leg/right/simian
icon_greyscale = 'monkestation/icons/mob/species/simian/bodyparts.dmi'
husk_type = "simian"
limb_id = SPECIES_SIMIAN
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ORGANIC | BODYTYPE_CUSTOM
+ footprint_sprite = FOOTPRINT_SPRITE_PAWS
dmg_overlay_type = "monkey"
diff --git a/monkestation/code/modules/virology/disease/_disease.dm b/monkestation/code/modules/virology/disease/_disease.dm
index 92f616095322..1916fc30782c 100644
--- a/monkestation/code/modules/virology/disease/_disease.dm
+++ b/monkestation/code/modules/virology/disease/_disease.dm
@@ -824,10 +824,10 @@ GLOBAL_LIST_INIT(virusDB, list())
D.pattern = rand(1,6)
D.pattern_color = "#[pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)][pick(randomhexes)]"
if (alert("Do you want to specify the appearance of your pathogen in a petri dish?","Choose your appearance","Yes","No") == "Yes")
- D.color = input(C, "Choose the color of the dish", "Cosmetic") as color
+ D.color = tgui_color_picker(C, "Choose the color of the dish", "Cosmetic")
D.pattern = input(C, "Choose the shape of the pattern inside the dish (1 to 6)", "Cosmetic",rand(1,6)) as num
D.pattern = clamp(D.pattern,1,6)
- D.pattern_color = input(C, "Choose the color of the pattern", "Cosmetic") as color
+ D.pattern_color = tgui_color_picker(C, "Choose the color of the pattern", "Cosmetic")
D.spread_flags = 0
if (alert("Can this virus spread_flags into blood? (warning! if choosing No, this virus will be impossible to sample and analyse!)","Spreading Vectors","Yes","No") == "Yes")
diff --git a/monkestation/icons/mob/clothing/suit.dmi b/monkestation/icons/mob/clothing/suit.dmi
index bd73a1ca5ec7..ad409f682258 100644
Binary files a/monkestation/icons/mob/clothing/suit.dmi and b/monkestation/icons/mob/clothing/suit.dmi differ
diff --git a/monkestation/icons/mob/clothing/uniform.dmi b/monkestation/icons/mob/clothing/uniform.dmi
index 6cd1526c1dcf..520dba7ba5dc 100644
Binary files a/monkestation/icons/mob/clothing/uniform.dmi and b/monkestation/icons/mob/clothing/uniform.dmi differ
diff --git a/monkestation/icons/mob/species/ethereal/bodyparts.dmi b/monkestation/icons/mob/species/ethereal/bodyparts.dmi
new file mode 100644
index 000000000000..f54fb12f996a
Binary files /dev/null and b/monkestation/icons/mob/species/ethereal/bodyparts.dmi differ
diff --git a/monkestation/icons/mob/species/floran/bodyparts.dmi b/monkestation/icons/mob/species/floran/bodyparts.dmi
index 9151db05ad9d..18151bbe707d 100644
Binary files a/monkestation/icons/mob/species/floran/bodyparts.dmi and b/monkestation/icons/mob/species/floran/bodyparts.dmi differ
diff --git a/monkestation/icons/mob/species/misc/suit_digi.dmi b/monkestation/icons/mob/species/misc/suit_digi.dmi
index 648ba6f8bcf2..b11c02238f9d 100644
Binary files a/monkestation/icons/mob/species/misc/suit_digi.dmi and b/monkestation/icons/mob/species/misc/suit_digi.dmi differ
diff --git a/monkestation/icons/mob/species/misc/uniform_digi.dmi b/monkestation/icons/mob/species/misc/uniform_digi.dmi
index 95adf1571dbb..84fc3c50013a 100644
Binary files a/monkestation/icons/mob/species/misc/uniform_digi.dmi and b/monkestation/icons/mob/species/misc/uniform_digi.dmi differ
diff --git a/monkestation/icons/obj/clothing/suits.dmi b/monkestation/icons/obj/clothing/suits.dmi
index 0fd1b4f34e55..4a06fc1f705a 100644
Binary files a/monkestation/icons/obj/clothing/suits.dmi and b/monkestation/icons/obj/clothing/suits.dmi differ
diff --git a/strings/pill_names.json b/strings/pill_names.json
index dcf46f66cc82..2ce0142618c0 100644
--- a/strings/pill_names.json
+++ b/strings/pill_names.json
@@ -96,6 +96,7 @@
"Garfunkel",
"Glorious",
"Goblin",
+"God",
"Green",
"Grief",
"Grievous",
@@ -117,6 +118,7 @@
"Ice",
"Jumper",
"Kilgor",
+"Lad",
"Lettuce",
"Lightning",
"Liquid",
diff --git a/strings/tips.txt b/strings/tips.txt
index 0569c19c09af..146515ebd012 100644
--- a/strings/tips.txt
+++ b/strings/tips.txt
@@ -1,4 +1,4 @@
-@You can use the |, + and _ characters to emphasize parts of what you say in-game (e.g. say"my _ass_ |is| +heavy+." will be outputted as "my ass is heavy ."). You can also escape these emphasizers by appending backslashes before them (e.g. say"1\+2\+3" will come out as "1+2+3" and not "1\2\ 3").
+@You can italicize , embolden or underline portions of your messages by enclosing them with |, + or _ respectively. You can also avoid this by adding backslashes (they won't show in the message) before these characters.
♪ Hey, have you ever tried appending the % character before your messages when speaking in-game? ♫
A Scientist will pay top dollar for your frogs!
A thrown glass of water can make a slippery tile, allowing you to slow down your pursuers in a pinch.
diff --git a/tgstation.dme b/tgstation.dme
index 53605f9d21f9..1e9c53040b0b 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1,6 +1,7 @@
// DM Environment file for tgstation.dme.
// All manual changes should be made outside the BEGIN_ and END_ blocks.
// New source code should be placed in .dm files: choose File/New --> Code File.
+// interface\skin.dmf needs to be at the very end of the file.
// BEGIN_INTERNALS
// END_INTERNALS
@@ -5374,6 +5375,7 @@
#include "code\modules\tgui\states\zlevel.dm"
#include "code\modules\tgui_input\alert.dm"
#include "code\modules\tgui_input\checkboxes.dm"
+#include "code\modules\tgui_input\color.dm"
#include "code\modules\tgui_input\list.dm"
#include "code\modules\tgui_input\number.dm"
#include "code\modules\tgui_input\text.dm"
@@ -5623,10 +5625,15 @@
#include "code\modules\zombie\items.dm"
#include "code\modules\zombie\organs.dm"
#include "code\ze_genesis_call\genesis_call.dm"
-#include "interface\fonts.dm"
#include "interface\interface.dm"
#include "interface\menu.dm"
#include "interface\stylesheet.dm"
+#include "interface\fonts\fonts_datum.dm"
+#include "interface\fonts\grand_9k.dm"
+#include "interface\fonts\pixellari.dm"
+#include "interface\fonts\spess_font.dm"
+#include "interface\fonts\tiny_unicode.dm"
+#include "interface\fonts\vcr_osd_mono.dm"
#include "interface\skin.dmf"
#include "monkestation\code\__DEFINES\projectile.dm"
#include "monkestation\code\__HELPERS\_lists.dm"
@@ -6595,6 +6602,7 @@
#include "monkestation\code\modules\reagents\medical\solder.dm"
#include "monkestation\code\modules\reagents\medical\system_cleaner.dm"
#include "monkestation\code\modules\reagents\reagent_containers\blood_pack.dm"
+#include "monkestation\code\modules\reagents\reagent_containers\drinks.dm"
#include "monkestation\code\modules\reagents\recipes\fun.dm"
#include "monkestation\code\modules\reagents\recipes\medical.dm"
#include "monkestation\code\modules\replays\hooks\generic_hooks.dm"
@@ -6704,6 +6712,7 @@
#include "monkestation\code\modules\surgery\blood_filter.dm"
#include "monkestation\code\modules\surgery\bodyparts\arachnid_bodyparts.dm"
#include "monkestation\code\modules\surgery\bodyparts\clockwork_bodyparts.dm"
+#include "monkestation\code\modules\surgery\bodyparts\ethereal_bodyparts.dm"
#include "monkestation\code\modules\surgery\bodyparts\floran_bodyparts.dm"
#include "monkestation\code\modules\surgery\bodyparts\ipc_bodyparts.dm"
#include "monkestation\code\modules\surgery\bodyparts\oozeling_bodyparts.dm"
diff --git a/tgui/packages/common/color.js b/tgui/packages/common/color.js
deleted file mode 100644
index b59d82247aae..000000000000
--- a/tgui/packages/common/color.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-const EPSILON = 0.0001;
-
-export class Color {
- constructor(r = 0, g = 0, b = 0, a = 1) {
- this.r = r;
- this.g = g;
- this.b = b;
- this.a = a;
- }
-
- toString() {
- // Alpha component needs to permit fractional values, so cannot use |
- let alpha = parseFloat(this.a);
- if (isNaN(alpha)) {
- alpha = 1;
- }
- return `rgba(${this.r | 0}, ${this.g | 0}, ${this.b | 0}, ${alpha})`;
- }
-
- // Darkens a color by a given percent. Returns a color, which can have toString called to get it's rgba() css value.
- darken(percent) {
- percent /= 100;
- return new Color(
- this.r - this.r * percent,
- this.g - this.g * percent,
- this.b - this.b * percent,
- this.a
- );
- }
-
- // Brightens a color by a given percent. Returns a color, which can have toString called to get it's rgba() css value.
- lighten(percent) {
- // No point in rewriting code we already have.
- return this.darken(-percent);
- }
-}
-
-/**
- * Creates a color from the CSS hex color notation.
- */
-Color.fromHex = (hex) =>
- new Color(
- parseInt(hex.substr(1, 2), 16),
- parseInt(hex.substr(3, 2), 16),
- parseInt(hex.substr(5, 2), 16)
- );
-
-/**
- * Linear interpolation of two colors.
- */
-Color.lerp = (c1, c2, n) =>
- new Color(
- (c2.r - c1.r) * n + c1.r,
- (c2.g - c1.g) * n + c1.g,
- (c2.b - c1.b) * n + c1.b,
- (c2.a - c1.a) * n + c1.a
- );
-
-/**
- * Loops up the color in the provided list of colors
- * with linear interpolation.
- */
-Color.lookup = (value, colors = []) => {
- const len = colors.length;
- if (len < 2) {
- throw new Error('Needs at least two colors!');
- }
- const scaled = value * (len - 1);
- if (value < EPSILON) {
- return colors[0];
- }
- if (value >= 1 - EPSILON) {
- return colors[len - 1];
- }
- const ratio = scaled % 1;
- const index = scaled | 0;
- return Color.lerp(colors[index], colors[index + 1], ratio);
-};
diff --git a/tgui/packages/common/color.ts b/tgui/packages/common/color.ts
new file mode 100644
index 000000000000..724fa209d030
--- /dev/null
+++ b/tgui/packages/common/color.ts
@@ -0,0 +1,396 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+const EPSILON = 0.0001;
+
+export class Color {
+ r: number;
+ g: number;
+ b: number;
+ a: number;
+
+ constructor(r = 0, g = 0, b = 0, a = 1) {
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ this.a = a;
+ }
+
+ toString() {
+ return `rgba(${this.r | 0}, ${this.g | 0}, ${this.b | 0}, ${this.a | 0})`;
+ }
+
+ // Darkens a color by a given percent. Returns a color, which can have toString called to get it's rgba() css value.
+ darken(percent: number): Color {
+ percent /= 100;
+ return new Color(
+ this.r - this.r * percent,
+ this.g - this.g * percent,
+ this.b - this.b * percent,
+ this.a
+ );
+ }
+
+ // Brightens a color by a given percent. Returns a color, which can have toString called to get it's rgba() css value.
+ lighten(percent: number): Color {
+ // No point in rewriting code we already have.
+ return this.darken(-percent);
+ }
+
+ /**
+ * Creates a color from the CSS hex color notation.
+ */
+ static fromHex(hex: string): Color {
+ return new Color(
+ parseInt(hex.substr(1, 2), 16),
+ parseInt(hex.substr(3, 2), 16),
+ parseInt(hex.substr(5, 2), 16)
+ );
+ }
+
+ /**
+ * Linear interpolation of two colors.
+ */
+ static lerp(c1: Color, c2: Color, n: number): Color {
+ return new Color(
+ (c2.r - c1.r) * n + c1.r,
+ (c2.g - c1.g) * n + c1.g,
+ (c2.b - c1.b) * n + c1.b,
+ (c2.a - c1.a) * n + c1.a
+ );
+ }
+
+ /**
+ * Loops up the color in the provided list of colors
+ * with linear interpolation.
+ */
+ static lookup(value: number, colors: Color[] = []): Color {
+ const len = colors.length;
+ if (len < 2) {
+ throw new Error('Needs at least two colors!');
+ }
+ const scaled = value * (len - 1);
+ if (value < EPSILON) {
+ return colors[0];
+ }
+ if (value >= 1 - EPSILON) {
+ return colors[len - 1];
+ }
+ const ratio = scaled % 1;
+ const index = scaled | 0;
+ return Color.lerp(colors[index], colors[index + 1], ratio);
+ }
+}
+
+/*
+ * MIT License
+ * https://github.com/omgovich/react-colorful/
+ *
+ * Copyright (c) 2020 Vlad Shilov
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+const round = (
+ number: number,
+ digits = 0,
+ base = Math.pow(10, digits)
+): number => {
+ return Math.round(base * number) / base;
+};
+
+export interface RgbColor {
+ r: number;
+ g: number;
+ b: number;
+}
+
+export interface RgbaColor extends RgbColor {
+ a: number;
+}
+
+export interface HslColor {
+ h: number;
+ s: number;
+ l: number;
+}
+
+export interface HslaColor extends HslColor {
+ a: number;
+}
+
+export interface HsvColor {
+ h: number;
+ s: number;
+ v: number;
+}
+
+export interface HsvaColor extends HsvColor {
+ a: number;
+}
+
+export type ObjectColor =
+ | RgbColor
+ | HslColor
+ | HsvColor
+ | RgbaColor
+ | HslaColor
+ | HsvaColor;
+
+export type AnyColor = string | ObjectColor;
+
+/**
+ * Valid CSS units.
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/angle
+ */
+const angleUnits: Record = {
+ grad: 360 / 400,
+ turn: 360,
+ rad: 360 / (Math.PI * 2),
+};
+
+export const hexToHsva = (hex: string): HsvaColor => rgbaToHsva(hexToRgba(hex));
+
+export const hexToRgba = (hex: string): RgbaColor => {
+ if (hex[0] === '#') hex = hex.substring(1);
+
+ if (hex.length < 6) {
+ return {
+ r: parseInt(hex[0] + hex[0], 16),
+ g: parseInt(hex[1] + hex[1], 16),
+ b: parseInt(hex[2] + hex[2], 16),
+ a: hex.length === 4 ? round(parseInt(hex[3] + hex[3], 16) / 255, 2) : 1,
+ };
+ }
+
+ return {
+ r: parseInt(hex.substring(0, 2), 16),
+ g: parseInt(hex.substring(2, 4), 16),
+ b: parseInt(hex.substring(4, 6), 16),
+ a: hex.length === 8 ? round(parseInt(hex.substring(6, 8), 16) / 255, 2) : 1,
+ };
+};
+
+export const parseHue = (value: string, unit = 'deg'): number => {
+ return Number(value) * (angleUnits[unit] || 1);
+};
+
+export const hslaStringToHsva = (hslString: string): HsvaColor => {
+ const matcher =
+ /hsla?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
+ const match = matcher.exec(hslString);
+
+ if (!match) return { h: 0, s: 0, v: 0, a: 1 };
+
+ return hslaToHsva({
+ h: parseHue(match[1], match[2]),
+ s: Number(match[3]),
+ l: Number(match[4]),
+ a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),
+ });
+};
+
+export const hslStringToHsva = hslaStringToHsva;
+
+export const hslaToHsva = ({ h, s, l, a }: HslaColor): HsvaColor => {
+ s *= (l < 50 ? l : 100 - l) / 100;
+
+ return {
+ h: h,
+ s: s > 0 ? ((2 * s) / (l + s)) * 100 : 0,
+ v: l + s,
+ a,
+ };
+};
+
+export const hsvaToHex = (hsva: HsvaColor): string =>
+ rgbaToHex(hsvaToRgba(hsva));
+
+export const hsvaToHsla = ({ h, s, v, a }: HsvaColor): HslaColor => {
+ const hh = ((200 - s) * v) / 100;
+
+ return {
+ h: round(h),
+ s: round(
+ hh > 0 && hh < 200
+ ? ((s * v) / 100 / (hh <= 100 ? hh : 200 - hh)) * 100
+ : 0
+ ),
+ l: round(hh / 2),
+ a: round(a, 2),
+ };
+};
+
+export const hsvaToHslString = (hsva: HsvaColor): string => {
+ const { h, s, l } = hsvaToHsla(hsva);
+ return `hsl(${h}, ${s}%, ${l}%)`;
+};
+
+export const hsvaToHsvString = (hsva: HsvaColor): string => {
+ const { h, s, v } = roundHsva(hsva);
+ return `hsv(${h}, ${s}%, ${v}%)`;
+};
+
+export const hsvaToHsvaString = (hsva: HsvaColor): string => {
+ const { h, s, v, a } = roundHsva(hsva);
+ return `hsva(${h}, ${s}%, ${v}%, ${a})`;
+};
+
+export const hsvaToHslaString = (hsva: HsvaColor): string => {
+ const { h, s, l, a } = hsvaToHsla(hsva);
+ return `hsla(${h}, ${s}%, ${l}%, ${a})`;
+};
+
+export const hsvaToRgba = ({ h, s, v, a }: HsvaColor): RgbaColor => {
+ h = (h / 360) * 6;
+ s = s / 100;
+ v = v / 100;
+
+ const hh = Math.floor(h),
+ b = v * (1 - s),
+ c = v * (1 - (h - hh) * s),
+ d = v * (1 - (1 - h + hh) * s),
+ module = hh % 6;
+
+ return {
+ r: [v, c, b, b, d, v][module] * 255,
+ g: [d, v, v, c, b, b][module] * 255,
+ b: [b, b, d, v, v, c][module] * 255,
+ a: round(a, 2),
+ };
+};
+
+export const hsvaToRgbString = (hsva: HsvaColor): string => {
+ const { r, g, b } = hsvaToRgba(hsva);
+ return `rgb(${round(r)}, ${round(g)}, ${round(b)})`;
+};
+
+export const hsvaToRgbaString = (hsva: HsvaColor): string => {
+ const { r, g, b, a } = hsvaToRgba(hsva);
+ return `rgba(${round(r)}, ${round(g)}, ${round(b)}, ${round(a, 2)})`;
+};
+
+export const hsvaStringToHsva = (hsvString: string): HsvaColor => {
+ const matcher =
+ /hsva?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
+ const match = matcher.exec(hsvString);
+
+ if (!match) return { h: 0, s: 0, v: 0, a: 1 };
+
+ return roundHsva({
+ h: parseHue(match[1], match[2]),
+ s: Number(match[3]),
+ v: Number(match[4]),
+ a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),
+ });
+};
+
+export const hsvStringToHsva = hsvaStringToHsva;
+
+export const rgbaStringToHsva = (rgbaString: string): HsvaColor => {
+ const matcher =
+ /rgba?\(?\s*(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
+ const match = matcher.exec(rgbaString);
+
+ if (!match) return { h: 0, s: 0, v: 0, a: 1 };
+
+ return rgbaToHsva({
+ r: Number(match[1]) / (match[2] ? 100 / 255 : 1),
+ g: Number(match[3]) / (match[4] ? 100 / 255 : 1),
+ b: Number(match[5]) / (match[6] ? 100 / 255 : 1),
+ a: match[7] === undefined ? 1 : Number(match[7]) / (match[8] ? 100 : 1),
+ });
+};
+
+export const rgbStringToHsva = rgbaStringToHsva;
+
+const format = (number: number) => {
+ const hex = number.toString(16);
+ return hex.length < 2 ? '0' + hex : hex;
+};
+
+export const rgbaToHex = ({ r, g, b, a }: RgbaColor): string => {
+ const alphaHex = a < 1 ? format(round(a * 255)) : '';
+ return (
+ '#' + format(round(r)) + format(round(g)) + format(round(b)) + alphaHex
+ );
+};
+
+export const rgbaToHsva = ({ r, g, b, a }: RgbaColor): HsvaColor => {
+ const max = Math.max(r, g, b);
+ const delta = max - Math.min(r, g, b);
+
+ // prettier-ignore
+ const hh = delta
+ ? max === r
+ ? (g - b) / delta
+ : max === g
+ ? 2 + (b - r) / delta
+ : 4 + (r - g) / delta
+ : 0;
+
+ return {
+ h: 60 * (hh < 0 ? hh + 6 : hh),
+ s: max ? (delta / max) * 100 : 0,
+ v: (max / 255) * 100,
+ a,
+ };
+};
+
+export const roundHsva = (hsva: HsvaColor): HsvaColor => ({
+ h: round(hsva.h),
+ s: round(hsva.s),
+ v: round(hsva.v),
+ a: round(hsva.a, 2),
+});
+
+export const rgbaToRgb = ({ r, g, b }: RgbaColor): RgbColor => ({ r, g, b });
+
+export const hslaToHsl = ({ h, s, l }: HslaColor): HslColor => ({ h, s, l });
+
+export const hsvaToHsv = (hsva: HsvaColor): HsvColor => {
+ const { h, s, v } = roundHsva(hsva);
+ return { h, s, v };
+};
+
+const hexMatcher = /^#?([0-9A-F]{3,8})$/i;
+
+export const validHex = (value: string, alpha?: boolean): boolean => {
+ const match = hexMatcher.exec(value);
+ const length = match ? match[1].length : 0;
+
+ return (
+ length === 3 || // '#rgb' format
+ length === 6 || // '#rrggbb' format
+ (!!alpha && length === 4) || // '#rgba' format
+ (!!alpha && length === 8) // '#rrggbbaa' format
+ );
+};
+
+// Source for the following luminance and contrast calculation code: https://blog.cristiana.tech/calculating-color-contrast-in-typescript-using-web-content-accessibility-guidelines-wcag
+export const luminance = (rgb: RgbColor): number => {
+ const [r, g, b] = [rgb.r, rgb.g, rgb.b].map((v) => {
+ v /= 255;
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
+ });
+ return r * 0.2126 + g * 0.7152 + b * 0.0722;
+};
+
+export const contrast = (
+ foreground: RgbColor,
+ background: RgbColor
+): number => {
+ const foreground_luminance = luminance(foreground);
+ const background_luminance = luminance(background);
+ return background_luminance < foreground_luminance
+ ? (background_luminance + 0.05) / (foreground_luminance + 0.05)
+ : (foreground_luminance + 0.05) / (background_luminance + 0.05);
+};
diff --git a/tgui/packages/tgui/components/Interactive.tsx b/tgui/packages/tgui/components/Interactive.tsx
new file mode 100644
index 000000000000..36c21954c516
--- /dev/null
+++ b/tgui/packages/tgui/components/Interactive.tsx
@@ -0,0 +1,152 @@
+/**
+ * MIT License
+ * https://github.com/omgovich/react-colorful/
+ *
+ * Copyright (c) 2020 Vlad Shilov
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { clamp } from 'common/math';
+import { Component, InfernoNode, createRef, RefObject } from 'inferno';
+
+export interface Interaction {
+ left: number;
+ top: number;
+}
+
+// Finds the proper window object to fix iframe embedding issues
+const getParentWindow = (node?: HTMLDivElement | null): Window => {
+ return (node && node.ownerDocument.defaultView) || self;
+};
+
+// Returns a relative position of the pointer inside the node's bounding box
+const getRelativePosition = (
+ node: HTMLDivElement,
+ event: MouseEvent
+): Interaction => {
+ const rect = node.getBoundingClientRect();
+ const pointer = event as MouseEvent;
+ return {
+ left: clamp(
+ (pointer.pageX - (rect.left + getParentWindow(node).pageXOffset)) /
+ rect.width,
+ 0,
+ 1
+ ),
+ top: clamp(
+ (pointer.pageY - (rect.top + getParentWindow(node).pageYOffset)) /
+ rect.height,
+ 0,
+ 1
+ ),
+ };
+};
+
+export interface InteractiveProps {
+ onMove: (interaction: Interaction) => void;
+ onKey: (offset: Interaction) => void;
+ children: InfernoNode[];
+ style?: any;
+}
+
+export class Interactive extends Component {
+ containerRef: RefObject;
+ props: InteractiveProps;
+
+ constructor(props: InteractiveProps) {
+ super();
+ this.props = props;
+ this.containerRef = createRef();
+ }
+
+ handleMoveStart = (event: MouseEvent) => {
+ const el = this.containerRef?.current;
+ if (!el) return;
+
+ // Prevent text selection
+ event.preventDefault();
+ el.focus();
+ this.props.onMove(getRelativePosition(el, event));
+ this.toggleDocumentEvents(true);
+ };
+
+ handleMove = (event: MouseEvent) => {
+ // Prevent text selection
+ event.preventDefault();
+
+ // If user moves the pointer outside of the window or iframe bounds and release it there,
+ // `mouseup`/`touchend` won't be fired. In order to stop the picker from following the cursor
+ // after the user has moved the mouse/finger back to the document, we check `event.buttons`
+ // and `event.touches`. It allows us to detect that the user is just moving his pointer
+ // without pressing it down
+ const isDown = event.buttons > 0;
+
+ if (isDown && this.containerRef?.current) {
+ this.props.onMove(getRelativePosition(this.containerRef.current, event));
+ } else {
+ this.toggleDocumentEvents(false);
+ }
+ };
+
+ handleMoveEnd = () => {
+ this.toggleDocumentEvents(false);
+ };
+
+ handleKeyDown = (event: KeyboardEvent) => {
+ const keyCode = event.which || event.keyCode;
+
+ // Ignore all keys except arrow ones
+ if (keyCode < 37 || keyCode > 40) return;
+ // Do not scroll page by arrow keys when document is focused on the element
+ event.preventDefault();
+ // Send relative offset to the parent component.
+ // We use codes (37←, 38↑, 39→, 40↓) instead of keys ('ArrowRight', 'ArrowDown', etc)
+ // to reduce the size of the library
+ this.props.onKey({
+ left: keyCode === 39 ? 0.05 : keyCode === 37 ? -0.05 : 0,
+ top: keyCode === 40 ? 0.05 : keyCode === 38 ? -0.05 : 0,
+ });
+ };
+
+ toggleDocumentEvents(state?: boolean) {
+ const el = this.containerRef?.current;
+ const parentWindow = getParentWindow(el);
+
+ // Add or remove additional pointer event listeners
+ const toggleEvent = state
+ ? parentWindow.addEventListener
+ : parentWindow.removeEventListener;
+ toggleEvent('mousemove', this.handleMove);
+ toggleEvent('mouseup', this.handleMoveEnd);
+ }
+
+ componentDidMount() {
+ this.toggleDocumentEvents(true);
+ }
+
+ componentWillUnmount() {
+ this.toggleDocumentEvents(false);
+ }
+
+ render() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}
diff --git a/tgui/packages/tgui/components/Pointer.tsx b/tgui/packages/tgui/components/Pointer.tsx
new file mode 100644
index 000000000000..409972a7dfce
--- /dev/null
+++ b/tgui/packages/tgui/components/Pointer.tsx
@@ -0,0 +1,46 @@
+/**
+ * MIT License
+ * https://github.com/omgovich/react-colorful/
+ *
+ * Copyright (c) 2020 Vlad Shilov
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { classes } from 'common/react';
+import { InfernoNode } from 'inferno';
+
+interface PointerProps {
+ className?: string;
+ top?: number;
+ left: number;
+ color: string;
+}
+
+export const Pointer = ({
+ className,
+ color,
+ left,
+ top = 0.5,
+}: PointerProps): InfernoNode => {
+ const nodeClassName = classes(['react-colorful__pointer', className]);
+
+ const style = {
+ top: `${top * 100}%`,
+ left: `${left * 100}%`,
+ };
+
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/components/index.js b/tgui/packages/tgui/components/index.js
index fa613161ea1c..fefd280a590c 100644
--- a/tgui/packages/tgui/components/index.js
+++ b/tgui/packages/tgui/components/index.js
@@ -23,6 +23,7 @@ export { FitText } from './FitText';
export { Grid } from './Grid';
export { Icon } from './Icon';
export { InfinitePlane } from './InfinitePlane';
+export { Interactive } from './Interactive';
export { Input } from './Input';
export { KeyListener } from './KeyListener';
export { Knob } from './Knob';
@@ -33,6 +34,7 @@ export { Modal } from './Modal';
export { NoticeBox } from './NoticeBox';
export { NumberInput } from './NumberInput';
export { ProgressBar } from './ProgressBar';
+export { Pointer } from './Pointer';
export { Popper } from './Popper';
export { RestrictedInput } from './RestrictedInput';
export { RoundGauge } from './RoundGauge';
diff --git a/tgui/packages/tgui/interfaces/AirAlarm.tsx b/tgui/packages/tgui/interfaces/AirAlarm.tsx
index 7dd02230cd30..4bb751795ba6 100644
--- a/tgui/packages/tgui/interfaces/AirAlarm.tsx
+++ b/tgui/packages/tgui/interfaces/AirAlarm.tsx
@@ -13,6 +13,10 @@ type AirAlarmData = {
dangerLevel: 0 | 1 | 2;
atmosAlarm: BooleanLike; // fix this
fireAlarm: BooleanLike;
+ faultStatus: 0 | 1 | 2;
+ faultLocation: string;
+ sensor: BooleanLike;
+ allowLinkChange: BooleanLike;
envData: {
name: string;
value: string; // preformatted in backend, shorter code that way.
@@ -72,7 +76,22 @@ const AirAlarmStatus = (props, context) => {
localStatusText: 'Danger (Internals Required)',
},
};
+ const faultMap = {
+ 0: {
+ color: 'green',
+ areaFaultText: 'None',
+ },
+ 1: {
+ color: 'purple',
+ areaFaultText: 'Manual Trigger',
+ },
+ 2: {
+ color: 'average',
+ areaFaultText: 'Automatic Detection',
+ },
+ };
const localStatus = dangerMap[data.dangerLevel] || dangerMap[0];
+ const areaFault = faultMap[data.faultStatus] || faultMap[0];
return (
@@ -89,16 +108,24 @@ const AirAlarmStatus = (props, context) => {
);
})}
-
+
{localStatus.localStatusText}
{(data.atmosAlarm && 'Atmosphere Alarm') ||
(data.fireAlarm && 'Fire Alarm') ||
'Nominal'}
+
+ {areaFault.areaFaultText}
+
+
+ {data.faultLocation || 'None'}
+
>
)) || (
diff --git a/tgui/packages/tgui/interfaces/ColorPickerModal.tsx b/tgui/packages/tgui/interfaces/ColorPickerModal.tsx
new file mode 100644
index 000000000000..7fbda36d0874
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ColorPickerModal.tsx
@@ -0,0 +1,653 @@
+/**
+ * @file
+ * @copyright 2023 itsmeow
+ * @license MIT
+ */
+
+import { Loader } from './common/Loader';
+import { useBackend, useLocalState } from '../backend';
+import { Autofocus, Box, Flex, Section, Stack, Pointer, NumberInput, Tooltip } from '../components';
+import { Window } from '../layouts';
+import { clamp } from 'common/math';
+import { hexToHsva, HsvaColor, hsvaToHex, hsvaToHslString, hsvaToRgba, rgbaToHsva, validHex } from 'common/color';
+import { Interaction, Interactive } from 'tgui/components/Interactive';
+import { classes } from 'common/react';
+import { Component, FocusEvent, FormEvent, InfernoNode } from 'inferno';
+import { logger } from 'tgui/logging';
+import { InputButtons } from './common/InputButtons';
+
+type ColorPickerData = {
+ autofocus: boolean;
+ buttons: string[];
+ message: string;
+ large_buttons: boolean;
+ swapped_buttons: boolean;
+ timeout: number;
+ title: string;
+ default_color: string;
+};
+
+export const ColorPickerModal = (_, context) => {
+ const { data } = useBackend(context);
+ const {
+ timeout,
+ message,
+ title,
+ autofocus,
+ default_color = '#000000',
+ } = data;
+ let [selectedColor, setSelectedColor] = useLocalState(
+ context,
+ 'color_picker_choice',
+ hexToHsva(default_color)
+ );
+
+ return (
+
+ {!!timeout && }
+
+
+ {message && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const ColorSelector = (
+ {
+ color,
+ setColor,
+ defaultColor,
+ }: { color: HsvaColor; setColor; defaultColor: string },
+ context
+) => {
+ const handleChange = (params: Partial) => {
+ setColor((current: HsvaColor) => {
+ return Object.assign({}, current, params);
+ });
+ };
+ const rgb = hsvaToRgba(color);
+ const hexColor = hsvaToHex(color);
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ Current
+
+
+ Previous
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hex:
+
+
+ {
+ logger.info(value);
+ setColor(hexToHsva(value));
+ }}
+ prefixed
+ />
+
+
+
+
+
+
+
+ H:
+
+
+
+
+
+ handleChange({ h: v })}
+ max={360}
+ unit="°"
+ />
+
+
+
+
+
+
+ S:
+
+
+
+
+
+ handleChange({ s: v })}
+ unit="%"
+ />
+
+
+
+
+
+
+ V:
+
+
+
+
+
+ handleChange({ v: v })}
+ unit="%"
+ />
+
+
+
+
+
+
+
+ R:
+
+
+
+
+
+ {
+ rgb.r = v;
+ handleChange(rgbaToHsva(rgb));
+ }}
+ max={255}
+ />
+
+
+
+
+
+
+ G:
+
+
+
+
+
+ {
+ rgb.g = v;
+ handleChange(rgbaToHsva(rgb));
+ }}
+ max={255}
+ />
+
+
+
+
+
+
+ B:
+
+
+
+
+
+ {
+ rgb.b = v;
+ handleChange(rgbaToHsva(rgb));
+ }}
+ max={255}
+ />
+
+
+
+
+
+
+ );
+};
+
+const TextSetter = ({
+ value,
+ callback,
+ min = 0,
+ max = 100,
+ unit,
+}: {
+ value: number;
+ callback: any;
+ min?: number;
+ max?: number;
+ unit?: string;
+}) => {
+ return (
+
+ );
+};
+
+/**
+ * MIT License
+ * https://github.com/omgovich/react-colorful/
+ *
+ * Copyright (c) 2020 Vlad Shilov
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+interface HexColorInputProps
+ extends Omit {
+ /** Enables `#` prefix displaying */
+ prefixed?: boolean;
+ /** Allows `#rgba` and `#rrggbbaa` color formats */
+ alpha?: boolean;
+}
+
+/** Adds "#" symbol to the beginning of the string */
+const prefix = (value: string) => '#' + value;
+
+export const HexColorInput = (props: HexColorInputProps): InfernoNode => {
+ const { prefixed, alpha, color, fluid, onChange, ...rest } = props;
+
+ /** Escapes all non-hexadecimal characters including "#" */
+ const escape = (value: string) =>
+ value.replace(/([^0-9A-F]+)/gi, '').substring(0, alpha ? 8 : 6);
+
+ /** Validates hexadecimal strings */
+ const validate = (value: string) => validHex(value, alpha);
+
+ return (
+
+ );
+};
+
+interface ColorInputBaseProps {
+ fluid?: boolean;
+ color: string;
+ onChange: (newColor: string) => void;
+ /** Blocks typing invalid characters and limits string length */
+ escape: (value: string) => string;
+ /** Checks that value is valid color string */
+ validate: (value: string) => boolean;
+ /** Processes value before displaying it in the input */
+ format?: (value: string) => string;
+}
+
+export class ColorInput extends Component {
+ props: ColorInputBaseProps;
+ state: { localValue: string };
+
+ constructor(props: ColorInputBaseProps) {
+ super();
+ this.props = props;
+ this.state = { localValue: this.props.escape(this.props.color) };
+ }
+
+ // Trigger `onChange` handler only if the input value is a valid color
+ handleInput = (e: FormEvent) => {
+ const inputValue = this.props.escape(e.currentTarget.value);
+ this.setState({ localValue: inputValue });
+ };
+
+ // Take the color from props if the last typed color (in local state) is not valid
+ handleBlur = (e: FocusEvent) => {
+ if (e.currentTarget) {
+ if (!this.props.validate(e.currentTarget.value)) {
+ this.setState({ localValue: this.props.escape(this.props.color) }); // return to default;
+ } else {
+ this.props.onChange(
+ this.props.escape
+ ? this.props.escape(e.currentTarget.value)
+ : e.currentTarget.value
+ );
+ }
+ }
+ };
+
+ componentDidUpdate(prevProps, prevState): void {
+ if (prevProps.color !== this.props.color) {
+ // Update the local state when `color` property value is changed
+ // eslint-disable-next-line react/no-did-update-set-state
+ this.setState({ localValue: this.props.escape(this.props.color) });
+ }
+ }
+
+ render() {
+ return (
+
+ .
+
+
+ );
+ }
+}
+
+const SaturationValue = ({ hsva, onChange }) => {
+ const handleMove = (interaction: Interaction) => {
+ onChange({
+ s: interaction.left * 100,
+ v: 100 - interaction.top * 100,
+ });
+ };
+
+ const handleKey = (offset: Interaction) => {
+ // Saturation and brightness always fit into [0, 100] range
+ onChange({
+ s: clamp(hsva.s + offset.left * 100, 0, 100),
+ v: clamp(hsva.v - offset.top * 100, 0, 100),
+ });
+ };
+
+ const containerStyle = {
+ 'background-color': `${hsvaToHslString({
+ h: hsva.h,
+ s: 100,
+ v: 100,
+ a: 1,
+ })} !important`,
+ };
+
+ return (
+
+ );
+};
+
+const Hue = ({
+ className,
+ hue,
+ onChange,
+}: {
+ className?: string;
+ hue: number;
+ onChange: (newHue: { h: number }) => void;
+}) => {
+ const handleMove = (interaction: Interaction) => {
+ onChange({ h: 360 * interaction.left });
+ };
+
+ const handleKey = (offset: Interaction) => {
+ // Hue measured in degrees of the color circle ranging from 0 to 360
+ onChange({
+ h: clamp(hue + offset.left * 360, 0, 360),
+ });
+ };
+
+ const nodeClassName = classes(['react-colorful__hue', className]);
+
+ return (
+
+ );
+};
+
+const Saturation = ({
+ className,
+ color,
+ onChange,
+}: {
+ className?: string;
+ color: HsvaColor;
+ onChange: (newSaturation: { s: number }) => void;
+}) => {
+ const handleMove = (interaction: Interaction) => {
+ onChange({ s: 100 * interaction.left });
+ };
+
+ const handleKey = (offset: Interaction) => {
+ // Hue measured in degrees of the color circle ranging from 0 to 100
+ onChange({
+ s: clamp(color.s + offset.left * 100, 0, 100),
+ });
+ };
+
+ const nodeClassName = classes(['react-colorful__saturation', className]);
+
+ return (
+
+ );
+};
+
+const Value = ({
+ className,
+ color,
+ onChange,
+}: {
+ className?: string;
+ color: HsvaColor;
+ onChange: (newValue: { v: number }) => void;
+}) => {
+ const handleMove = (interaction: Interaction) => {
+ onChange({ v: 100 * interaction.left });
+ };
+
+ const handleKey = (offset: Interaction) => {
+ onChange({
+ v: clamp(color.v + offset.left * 100, 0, 100),
+ });
+ };
+
+ const nodeClassName = classes(['react-colorful__value', className]);
+
+ return (
+
+ );
+};
+
+const RGBSlider = ({
+ className,
+ color,
+ onChange,
+ target,
+}: {
+ className?: string;
+ color: HsvaColor;
+ onChange: (newValue: HsvaColor) => void;
+ target: string;
+}) => {
+ const rgb = hsvaToRgba(color);
+
+ const setNewTarget = (value: number) => {
+ rgb[target] = value;
+ onChange(rgbaToHsva(rgb));
+ };
+
+ const handleMove = (interaction: Interaction) => {
+ setNewTarget(255 * interaction.left);
+ };
+
+ const handleKey = (offset: Interaction) => {
+ setNewTarget(clamp(rgb[target] + offset.left * 255, 0, 255));
+ };
+
+ const nodeClassName = classes([`react-colorful__${target}`, className]);
+
+ let selected =
+ target === 'r'
+ ? `rgb(${Math.round(rgb.r)},0,0)`
+ : target === 'g'
+ ? `rgb(0,${Math.round(rgb.g)},0)`
+ : `rgb(0,0,${Math.round(rgb.b)})`;
+
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/styles/interfaces/ColorPicker.scss b/tgui/packages/tgui/styles/interfaces/ColorPicker.scss
new file mode 100644
index 000000000000..99f628c35e2e
--- /dev/null
+++ b/tgui/packages/tgui/styles/interfaces/ColorPicker.scss
@@ -0,0 +1,153 @@
+/**
+ * MIT License
+ * https://github.com/omgovich/react-colorful/
+ *
+ * Copyright (c) 2020 Vlad Shilov
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+@use '../colors.scss';
+@use '../base.scss';
+
+.react-colorful {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ width: 200px;
+ height: 200px;
+ user-select: none;
+ cursor: default;
+}
+
+.react-colorful__saturation_value {
+ position: relative;
+ flex-grow: 1;
+ border-color: transparent; /* Fixes https://github.com/omgovich/react-colorful/issues/139 */
+ border-bottom: 12px solid #000;
+ border-radius: 8px 8px 0 0;
+ background-image: linear-gradient(
+ to top,
+ rgba(0, 0, 0, 255),
+ rgba(0, 0, 0, 0)
+ ),
+ linear-gradient(to right, rgba(255, 255, 255, 255), rgba(255, 255, 255, 0));
+}
+
+.react-colorful__pointer-fill,
+.react-colorful__alpha-gradient {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ pointer-events: none;
+ border-radius: inherit;
+}
+
+/* Improve elements rendering on light backgrounds */
+.react-colorful__alpha-gradient,
+.react-colorful__saturation_value {
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05);
+}
+
+.react-colorful__hue,
+.react-colorful__r,
+.react-colorful__g,
+.react-colorful__b,
+.react-colorful__alpha,
+.react-colorful__saturation,
+.react-colorful__value {
+ position: relative;
+ height: 24px;
+}
+
+.react-colorful__hue {
+ background: linear-gradient(
+ to right,
+ #f00 0%,
+ #ff0 17%,
+ #0f0 33%,
+ #0ff 50%,
+ #00f 67%,
+ #f0f 83%,
+ #f00 100%
+ );
+}
+
+.react-colorful__r {
+ background: linear-gradient(to right, #000, #f00);
+}
+
+.react-colorful__g {
+ background: linear-gradient(to right, #000, #0f0);
+}
+
+.react-colorful__b {
+ background: linear-gradient(to right, #000, #00f);
+}
+
+/* Round bottom corners of the last element: `Hue` for `ColorPicker` or `Alpha` for `AlphaColorPicker` */
+.react-colorful__last-control {
+ border-radius: 0 0 8px 8px;
+}
+
+.react-colorful__interactive {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: inherit;
+ outline: none;
+ /* Don't trigger the default scrolling behavior when the event is originating from this element */
+ touch-action: none;
+}
+
+.react-colorful__pointer {
+ position: absolute;
+ z-index: 1;
+ box-sizing: border-box;
+ width: 28px;
+ height: 28px;
+ transform: translate(-50%, -50%);
+ background-color: #cfcfcf;
+ border: 2px solid #cfcfcf;
+ border-radius: 50%;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4);
+}
+
+.react-colorful__interactive:focus .react-colorful__pointer {
+ transform: translate(-50%, -50%) scale(1.1);
+ background-color: #fff;
+ border-color: #fff;
+}
+
+/* Chessboard-like pattern for alpha related elements */
+.react-colorful__alpha,
+.react-colorful__alpha-pointer {
+ background-color: #fff;
+ background-image: url('data:image/svg+xml, ');
+}
+
+.react-colorful__saturation-pointer,
+.react-colorful__value-pointer,
+.react-colorful__hue-pointer,
+.react-colorful__r-pointer,
+.react-colorful__g-pointer,
+.react-colorful__b-pointer {
+ z-index: 1;
+ width: 20px;
+ height: 20px;
+}
+
+/* Display the saturation value pointer over the hue one */
+.react-colorful__saturation_value-pointer {
+ z-index: 3;
+}
diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss
index 31696c901a4d..b76f1e64b36f 100644
--- a/tgui/packages/tgui/styles/main.scss
+++ b/tgui/packages/tgui/styles/main.scss
@@ -55,6 +55,7 @@
@include meta.load-css('./interfaces/AlertModal.scss');
@include meta.load-css('./interfaces/CameraConsole.scss');
@include meta.load-css('./interfaces/Changelog.scss');
+@include meta.load-css('./interfaces/ColorPicker.scss');
@include meta.load-css('./interfaces/CrewManifest.scss');
@include meta.load-css('./interfaces/ExperimentConfigure.scss');
@include meta.load-css('./interfaces/Fishing.scss');
diff --git a/tgui/packages/tgui/styles/themes/generic.scss b/tgui/packages/tgui/styles/themes/generic.scss
index 1d232af57aa7..0f38ceb4693a 100644
--- a/tgui/packages/tgui/styles/themes/generic.scss
+++ b/tgui/packages/tgui/styles/themes/generic.scss
@@ -4,6 +4,7 @@
$generic: #484455;
$accent: #4f56a5;
$accent-2: #daa520;
+$border-color: #7b86ff;
@use '../colors.scss' with (
$fg-map-keys: (),