diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm
index 2bc1f6d6ca..7da2ddbfe8 100644
--- a/code/__defines/_planes+layers.dm
+++ b/code/__defines/_planes+layers.dm
@@ -188,6 +188,10 @@ What is the naming convention for planes or layers?
#define PLANE_PLAYER_HUD_ITEMS 96 //Separate layer with which to apply colorblindness
#define PLANE_PLAYER_HUD_ABOVE 97 //Things above the player hud
+#define RADIAL_BACKGROUND_LAYER 0
+///1000 is an unimportant number, it's just to normalize copied layers
+#define RADIAL_CONTENT_LAYER 1000
+
#define PLANE_ADMIN3 99 //Purely for shenanigans (above HUD)
diff --git a/code/__defines/update_icons.dm b/code/__defines/update_icons.dm
index 4fa21751be..7120edd168 100644
--- a/code/__defines/update_icons.dm
+++ b/code/__defines/update_icons.dm
@@ -1 +1,49 @@
-#define MOB_WATER_LAYER 36
+// These are used as the layers for the icons, as well as indexes in a list that holds onto them.
+// Technically the layers used are all -100+layer to make them FLOAT_LAYER overlays.
+//Human Overlays Indexes/////////
+#define MUTATIONS_LAYER 1 //Mutations like fat, and lasereyes
+#define SKIN_LAYER 2 //Skin things added by a call on species
+#define BLOOD_LAYER 3 //Bloodied hands/feet/anything else
+#define BODYPARTS_LAYER 4 //Bodyparts layer
+#define MOB_DAM_LAYER 5 //Injury overlay sprites like open wounds
+#define SURGERY_LAYER 6 //Overlays for open surgical sites
+#define UNDERWEAR_LAYER 7 //Underwear/bras/etc
+#define TAIL_LOWER_LAYER 8 //Tail as viewed from the south
+#define WING_LOWER_LAYER 9 //Wings as viewed from the south
+#define SHOES_LAYER_ALT 10 //Shoe-slot item (when set to be under uniform via verb)
+#define UNIFORM_LAYER 11 //Uniform-slot item
+#define ID_LAYER 12 //ID-slot item
+#define SHOES_LAYER 13 //Shoe-slot item
+#define GLOVES_LAYER 14 //Glove-slot item
+#define BELT_LAYER 15 //Belt-slot item
+#define SUIT_LAYER 16 //Suit-slot item
+#define TAIL_UPPER_LAYER 17 //Some species have tails to render (As viewed from the N, E, or W)
+#define GLASSES_LAYER 18 //Eye-slot item
+#define BELT_LAYER_ALT 19 //Belt-slot item (when set to be above suit via verb)
+#define SUIT_STORE_LAYER 20 //Suit storage-slot item
+#define BACK_LAYER 21 //Back-slot item
+#define HAIR_LAYER 22 //The human's hair
+#define HAIR_ACCESSORY_LAYER 23 //Simply move this up a number if things are added.
+#define EARS_LAYER 24 //Both ear-slot items (combined image)
+#define EYES_LAYER 25 //Mob's eyes (used for glowing eyes)
+#define FACEMASK_LAYER 26 //Mask-slot item
+#define GLASSES_LAYER_ALT 27 //So some glasses can appear on top of hair and things
+#define HEAD_LAYER 28 //Head-slot item
+#define HANDCUFF_LAYER 29 //Handcuffs, if the human is handcuffed, in a secret inv slot
+#define LEGCUFF_LAYER 30 //Same as handcuffs, for legcuffs
+#define L_HAND_LAYER 31 //Left-hand item
+#define R_HAND_LAYER 32 //Right-hand item
+#define WING_LAYER 33 //Wings or protrusions over the suit.
+#define TAIL_UPPER_LAYER_ALT 34 //Modified tail-sprite layer. Tend to be larger.
+#define MODIFIER_EFFECTS_LAYER 35 //Effects drawn by modifiers
+#define FIRE_LAYER 36 //'Mob on fire' overlay layer
+#define MOB_WATER_LAYER 37
+#define TARGETED_LAYER 38 //'Aimed at' overlay layer
+#define VORE_BELLY_LAYER 39
+#define VORE_TAIL_LAYER 40
+
+#define TOTAL_LAYERS 40 // <---- KEEP THIS UPDATED, should always equal the highest number here, used to initialize a list.
+
+//These two are only used for gargoyles currently
+#define HUMAN_BODY_LAYERS list(MUTATIONS_LAYER, TAIL_LOWER_LAYER, WING_LOWER_LAYER, BODYPARTS_LAYER, SKIN_LAYER, BLOOD_LAYER, MOB_DAM_LAYER, TAIL_UPPER_LAYER, HAIR_LAYER, HAIR_ACCESSORY_LAYER, EYES_LAYER, WING_LAYER, VORE_BELLY_LAYER, VORE_TAIL_LAYER, TAIL_UPPER_LAYER_ALT)
+#define HUMAN_OTHER_LAYERS list(MODIFIER_EFFECTS_LAYER, FIRE_LAYER, MOB_WATER_LAYER, TARGETED_LAYER)
diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index 32725ec5ae..b803227325 100644
--- a/code/_onclick/hud/radial.dm
+++ b/code/_onclick/hud/radial.dm
@@ -3,33 +3,52 @@
GLOBAL_LIST_EMPTY(radial_menus)
-// Ported from TG
-
/obj/screen/radial
icon = 'icons/mob/radial.dmi'
- layer = LAYER_HUD_ABOVE
plane = PLANE_PLAYER_HUD_ABOVE
+ vis_flags = VIS_INHERIT_PLANE
+ var/click_on_hover = FALSE
var/datum/radial_menu/parent
+/obj/screen/radial/proc/set_parent(new_value)
+ if(parent)
+ UnregisterSignal(parent, COMSIG_PARENT_QDELETING)
+ parent = new_value
+ if(parent)
+ RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(handle_parent_del))
+
+/obj/screen/radial/proc/handle_parent_del()
+ SIGNAL_HANDLER
+ set_parent(null)
+
/obj/screen/radial/slice
icon_state = "radial_slice"
var/choice
var/next_page = FALSE
var/tooltips = FALSE
-/obj/screen/radial/Destroy()
- parent = null
- return ..()
+/obj/screen/radial/slice/set_parent(new_value)
+ . = ..()
+ if(parent)
+ icon_state = parent.radial_slice_icon
/obj/screen/radial/slice/MouseEntered(location, control, params)
. = ..()
- icon_state = "radial_slice_focus"
+ if(next_page || !parent)
+ icon_state = "radial_slice_focus"
+ else
+ icon_state = "[parent.radial_slice_icon]_focus"
if(tooltips)
openToolTip(usr, src, params, title = name)
+ if (click_on_hover && !isnull(usr) && !isnull(parent))
+ Click(location, control, params)
/obj/screen/radial/slice/MouseExited(location, control, params)
. = ..()
- icon_state = "radial_slice"
+ if(next_page || !parent)
+ icon_state = "radial_slice"
+ else
+ icon_state = parent.radial_slice_icon
if(tooltips)
closeToolTip(usr)
@@ -38,7 +57,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
if(next_page)
parent.next_page()
else
- parent.element_chosen(choice,usr)
+ parent.element_chosen(choice, usr, params)
/obj/screen/radial/center
name = "Close Menu"
@@ -57,9 +76,18 @@ GLOBAL_LIST_EMPTY(radial_menus)
parent.finished = TRUE
/datum/radial_menu
- var/list/choices = list() //List of choice id's
- var/list/choices_icons = list() //choice_id -> icon
- var/list/choices_values = list() //choice_id -> choice
+ /// List of choice IDs
+ var/list/choices = list()
+
+ /// choice_id -> icon
+ var/list/choices_icons = list()
+
+ /// choice_id -> choice
+ var/list/choices_values = list()
+
+ /// choice_id -> /datum/radial_menu_choice
+ var/list/choice_datums = list()
+
var/list/page_data = list() //list of choices per page
@@ -87,6 +115,9 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/py_shift = 0
var/entry_animation = TRUE
+ ///A replacement icon state for the generic radial slice bg icon. Doesn't affect the next page nor the center buttons
+ var/radial_slice_icon
+
//If we swap to vis_contens inventory these will need a redo
/datum/radial_menu/proc/check_screen_border(mob/user)
var/atom/movable/AM = anchor
@@ -98,6 +129,8 @@ GLOBAL_LIST_EMPTY(radial_menus)
else
py_shift = 32
restrict_to_dir(NORTH) //I was going to parse screen loc here but that's more effort than it's worth.
+ else if(hudfix_method && AM.loc)
+ anchor = get_atom_on_turf(anchor)
//Sets defaults
//These assume 45 deg min_angle
@@ -116,7 +149,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
starting_angle = 180
ending_angle = 45
-/datum/radial_menu/proc/setup_menu(use_tooltips)
+/datum/radial_menu/proc/setup_menu(use_tooltips, set_page = 1, click_on_hover = FALSE)
if(ending_angle > starting_angle)
zone = ending_angle - starting_angle
else
@@ -129,7 +162,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
for(var/i in 1 to elements_to_add) //Create all elements
var/obj/screen/radial/slice/new_element = new /obj/screen/radial/slice
new_element.tooltips = use_tooltips
- new_element.parent = src
+ new_element.set_parent(src)
elements += new_element
var/page = 1
@@ -152,22 +185,31 @@ GLOBAL_LIST_EMPTY(radial_menus)
page_data[page] = current
pages = page
- current_page = 1
- update_screen_objects(anim = entry_animation)
+ current_page = clamp(set_page, 1, pages)
+ update_screen_objects(entry_animation, click_on_hover)
-/datum/radial_menu/proc/update_screen_objects(anim = FALSE)
+/datum/radial_menu/proc/update_screen_objects(anim = FALSE, click_on_hover = FALSE)
var/list/page_choices = page_data[current_page]
var/angle_per_element = round(zone / page_choices.len)
for(var/i in 1 to elements.len)
- var/obj/screen/radial/E = elements[i]
+ var/obj/screen/radial/element = elements[i]
var/angle = WRAP(starting_angle + (i - 1) * angle_per_element,0,360)
if(i > page_choices.len)
- HideElement(E)
+ HideElement(element)
+ element.click_on_hover = FALSE
else
- SetElement(E,page_choices[i],angle,anim = anim,anim_order = i)
+ SetElement(element,page_choices[i],angle,anim = anim,anim_order = i)
+ // Only activate click on hover after the animation plays
+ if (!click_on_hover)
+ continue
+ if (anim)
+ addtimer(VARSET_CALLBACK(element, click_on_hover, TRUE), i * 0.5)
+ else
+ element.click_on_hover = TRUE
/datum/radial_menu/proc/HideElement(obj/screen/radial/slice/E)
E.cut_overlays()
+ E.vis_contents.Cut()
E.alpha = 0
E.name = "None"
E.maptext = null
@@ -194,13 +236,22 @@ GLOBAL_LIST_EMPTY(radial_menus)
E.alpha = 255
E.mouse_opacity = MOUSE_OPACITY_ICON
E.cut_overlays()
+ E.vis_contents.Cut()
if(choice_id == NEXT_PAGE_ID)
E.name = "Next Page"
E.next_page = TRUE
+ E.icon_state = "radial_slice" // Resets the bg icon state to the default for next page buttons.
E.add_overlay("radial_next")
else
- if(istext(choices_values[choice_id]))
+ //This isn't granted to exist, so use the ?. operator for conditionals that use it.
+ var/datum/radial_menu_choice/choice_datum = choice_datums[choice_id]
+ if(choice_datum?.name)
+ E.name = choice_datum.name
+ else if(istext(choices_values[choice_id]))
E.name = choices_values[choice_id]
+ else if(ispath(choices_values[choice_id],/atom))
+ var/atom/A = choices_values[choice_id]
+ E.name = initial(A.name)
else
var/atom/movable/AM = choices_values[choice_id] //Movables only
E.name = AM.name
@@ -209,15 +260,21 @@ GLOBAL_LIST_EMPTY(radial_menus)
E.next_page = FALSE
if(choices_icons[choice_id])
E.add_overlay(choices_icons[choice_id])
+ if (choice_datum?.info)
+ var/obj/effect/abstract/info/info_button = new(E, choice_datum.info)
+ info_button.plane = PLANE_PLAYER_HUD_ABOVE
+ info_button.layer = RADIAL_CONTENT_LAYER
+ E.vis_contents += info_button
/datum/radial_menu/New()
close_button = new
- close_button.parent = src
+ close_button.set_parent(src)
/datum/radial_menu/proc/Reset()
choices.Cut()
choices_icons.Cut()
choices_values.Cut()
+ choice_datums.Cut()
current_page = 1
/datum/radial_menu/proc/element_chosen(choice_id,mob/user)
@@ -226,7 +283,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
/datum/radial_menu/proc/get_next_id()
return "c_[choices.len]"
-/datum/radial_menu/proc/set_choices(list/new_choices, use_tooltips)
+/datum/radial_menu/proc/set_choices(list/new_choices, use_tooltips, click_on_hover = FALSE, set_page = 1)
if(choices.len)
Reset()
for(var/E in new_choices)
@@ -237,13 +294,20 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/I = extract_image(new_choices[E])
if(I)
choices_icons[id] = I
- setup_menu(use_tooltips)
+ if (istype(new_choices[E], /datum/radial_menu_choice))
+ choice_datums[id] = new_choices[E]
+ setup_menu(use_tooltips, set_page, click_on_hover)
-/datum/radial_menu/proc/extract_image(E)
- var/mutable_appearance/MA = new /mutable_appearance(E)
+/datum/radial_menu/proc/extract_image(to_extract_from)
+ if (istype(to_extract_from, /datum/radial_menu_choice))
+ var/datum/radial_menu_choice/choice = to_extract_from
+ to_extract_from = choice.image
+
+ var/mutable_appearance/MA = new /mutable_appearance(to_extract_from)
if(MA)
- MA.layer = LAYER_HUD_ABOVE
+ MA.plane = PLANE_PLAYER_HUD_ABOVE
+ MA.layer = RADIAL_CONTENT_LAYER
MA.appearance_flags |= RESET_TRANSFORM
return MA
@@ -253,15 +317,16 @@ GLOBAL_LIST_EMPTY(radial_menus)
current_page = WRAP(current_page + 1,1,pages+1)
update_screen_objects()
-/datum/radial_menu/proc/show_to(mob/M)
+/datum/radial_menu/proc/show_to(mob/M, offset_x = 0, offset_y = 0)
if(current_user)
hide()
if(!M.client || !anchor)
return
current_user = M.client
//Blank
- menu_holder = image(icon='icons/effects/effects.dmi',loc=anchor,icon_state="nothing",layer = LAYER_HUD_ABOVE)
- menu_holder.appearance_flags |= KEEP_APART
+ menu_holder = image(icon='icons/effects/effects.dmi',loc=anchor,icon_state="nothing", layer = RADIAL_BACKGROUND_LAYER, pixel_x = offset_x, pixel_y = offset_y)
+ menu_holder.plane = PLANE_PLAYER_HUD_ABOVE
+ menu_holder.appearance_flags |= KEEP_APART|RESET_ALPHA|RESET_COLOR|RESET_TRANSFORM
menu_holder.vis_contents += elements + close_button
current_user.images += menu_holder
@@ -283,9 +348,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
/datum/radial_menu/Destroy()
Reset()
hide()
- QDEL_LIST_NULL(elements)
- QDEL_NULL(close_button)
- QDEL_NULL(custom_check_callback)
+ custom_check_callback = null
. = ..()
/*
@@ -293,30 +356,66 @@ GLOBAL_LIST_EMPTY(radial_menus)
Choices should be a list where list keys are movables or text used for element names and return value
and list values are movables/icons/images used for element icons
*/
-/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE)
+/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice", autopick_single_option = TRUE, entry_animation = TRUE, click_on_hover = FALSE, user_space = FALSE)
if(!user || !anchor || !length(choices))
return
+
+ if(length(choices)==1 && autopick_single_option)
+ return choices[1]
+
if(!uniqueid)
uniqueid = "defmenu_[REF(user)]_[REF(anchor)]"
if(GLOB.radial_menus[uniqueid])
+ if(!no_repeat_close)
+ var/datum/radial_menu/menu = GLOB.radial_menus[uniqueid]
+ menu.finished = TRUE
return
var/datum/radial_menu/menu = new
+ menu.entry_animation = entry_animation
GLOB.radial_menus[uniqueid] = menu
if(radius)
menu.radius = radius
if(istype(custom_check))
menu.custom_check_callback = custom_check
- menu.anchor = anchor
+ menu.anchor = user_space ? user : anchor
+ menu.radial_slice_icon = radial_slice_icon
menu.check_screen_border(user) //Do what's needed to make it look good near borders or on hud
- menu.set_choices(choices, tooltips)
- menu.show_to(user)
+ menu.set_choices(choices, tooltips, click_on_hover)
+ var/offset_x = 0
+ var/offset_y = 0
+ if (user_space)
+ var/turf/user_turf = get_turf(user)
+ var/turf/anchor_turf = get_turf(anchor)
+ offset_x = (anchor_turf.x - user_turf.x) * ICON_SIZE_X + anchor.pixel_x - user.pixel_x
+ offset_y = (anchor_turf.y - user_turf.y) * ICON_SIZE_Y + anchor.pixel_y - user.pixel_y
+ menu.show_to(user, offset_x, offset_y)
menu.wait(user, anchor, require_near)
var/answer = menu.selected_choice
- QDEL_NULL(menu)
+ qdel(menu)
GLOB.radial_menus -= uniqueid
+ if(require_near && !in_range(anchor, user))
+ return
+ if(istype(custom_check))
+ if(!custom_check.Invoke())
+ return
return answer
+/// Can be provided to choices in radial menus if you want to provide more information
+/datum/radial_menu_choice
+ /// Required -- what to display for this button
+ var/image
+
+ /// If provided, this will be the name the radial slice hud button. This has priority over everything else.
+ var/name
+
+ /// If provided, will display an info button that will put this text in your chat
+ var/info
+
+/datum/radial_menu_choice/Destroy(force)
+ . = ..()
+ QDEL_NULL(image)
+
#undef NEXT_PAGE_ID
#undef DEFAULT_CHECK_DELAY
diff --git a/code/datums/components/gargoyle.dm b/code/datums/components/gargoyle.dm
new file mode 100644
index 0000000000..618c2e97e0
--- /dev/null
+++ b/code/datums/components/gargoyle.dm
@@ -0,0 +1,101 @@
+/datum/component/gargoyle
+ var/energy = 100
+ var/transformed = FALSE
+ var/paused = FALSE
+ var/paused_loc
+ var/cooldown
+
+ var/mob/living/carbon/human/gargoyle //easy reference
+ var/obj/structure/gargoyle/statue //another easy ref
+
+ //Adjustable mod
+ var/identifier = "statue"
+ var/adjective = "hardens"
+ var/material = "stone"
+ var/tint = "#FFFFFF"
+
+/datum/component/gargoyle/Initialize()
+ if (!ishuman(parent))
+ return COMPONENT_INCOMPATIBLE
+ gargoyle = parent
+ add_verb(gargoyle,/mob/living/carbon/human/proc/gargoyle_transformation)
+ add_verb(gargoyle,/mob/living/carbon/human/proc/gargoyle_pause)
+ add_verb(gargoyle,/mob/living/carbon/human/proc/gargoyle_checkenergy)
+
+ START_PROCESSING(SSprocessing, src)
+
+/datum/component/gargoyle/process()
+ if (QDELETED(gargoyle))
+ return
+ if (paused && gargoyle.loc != paused_loc)
+ unpause()
+ if (energy > 0)
+ if (!transformed && !paused)
+ energy = max(0,energy-0.05)
+ else if (!transformed && isturf(gargoyle.loc))
+ gargoyle.gargoyle_transformation()
+ if (transformed)
+ if (!statue)
+ transformed = FALSE
+ statue.damage(-0.5)
+ energy = min(energy+0.3, 100)
+
+/datum/component/gargoyle/proc/unpause()
+ if (!paused || transformed)
+ paused = FALSE
+ paused_loc = null
+ UnregisterSignal(gargoyle, COMSIG_ATOM_ENTERING)
+ return
+ if (gargoyle?.loc != paused_loc)
+ paused = FALSE
+ paused_loc = null
+ energy = max(energy - 5, 0)
+ if (energy == 0)
+ gargoyle.gargoyle_transformation()
+ UnregisterSignal(gargoyle, COMSIG_ATOM_ENTERING)
+
+//verbs or action buttons...?
+/mob/living/carbon/human/proc/gargoyle_transformation()
+ set name = "Gargoyle - Petrification"
+ set category = "Abilities.Gargoyle"
+ set desc = "Turn yourself into (or back from) being a gargoyle."
+
+ if (stat == DEAD)
+ return
+
+ var/datum/component/gargoyle/comp = GetComponent(/datum/component/gargoyle)
+ if (comp)
+ if (comp.energy <= 0 && isturf(loc))
+ to_chat(src, span_danger("You suddenly turn into a [comp.identifier] as you run out of energy!"))
+ else if (comp.cooldown > world.time)
+ var/time_to_wait = (comp.cooldown - world.time) / (1 SECONDS)
+ to_chat(src, span_warning("You can't transform just yet again! Wait for another [round(time_to_wait,0.1)] seconds!"))
+ return
+ if (istype(loc, /obj/structure/gargoyle))
+ qdel(loc)
+ else if (isturf(loc))
+ new /obj/structure/gargoyle(loc, src)
+
+/mob/living/carbon/human/proc/gargoyle_pause()
+ set name = "Gargoyle - Pause"
+ set category = "Abilities.Gargoyle"
+ set desc = "Pause your energy while standing still, so you don't use up any more, though you will lose a small amount upon moving again."
+
+ if (stat)
+ return
+
+ var/datum/component/gargoyle/comp = GetComponent(/datum/component/gargoyle)
+ if (comp && !comp.transformed && !comp.paused)
+ comp.paused = TRUE
+ comp.paused_loc = loc
+ comp.RegisterSignal(src, COMSIG_ATOM_ENTERING, /datum/component/gargoyle/proc/unpause)
+ to_chat(src, span_notice("You start conserving your energy."))
+
+/mob/living/carbon/human/proc/gargoyle_checkenergy()
+ set name = "Gargoyle - Check Energy"
+ set category = "Abilities.Gargoyle"
+ set desc = "Check how much energy you have remaining as a gargoyle."
+
+ var/datum/component/gargoyle/comp = GetComponent(/datum/component/gargoyle)
+ if (comp)
+ to_chat(src, span_notice("You have [round(comp.energy,0.01)] energy remaining. It is currently [comp.paused ? "stable" : (comp.transformed ? "increasing" : "decreasing")]."))
diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm
index 671671c497..afc2640e43 100644
--- a/code/game/machinery/computer/medical.dm
+++ b/code/game/machinery/computer/medical.dm
@@ -298,6 +298,16 @@
active1 = general_record
active2 = medical_record
screen = MED_DATA_RECORD
+ if("sync_r")
+ if(active2)
+ set_temp(client_update_record(src,usr))
+ if("edit_notes")
+ // The modal input in tgui is busted for this sadly...
+ var/new_notes = strip_html_simple(tgui_input_text(usr,"Enter new information here.","Character Preference", html_decode(active2.fields["notes"]), MAX_RECORD_LENGTH, TRUE, prevent_enter = TRUE), MAX_RECORD_LENGTH)
+ if(usr.Adjacent(src))
+ if(new_notes != "" || tgui_alert(usr, "Are you sure you want to delete the current record's notes?", "Confirm Delete", list("Delete", "No")) == "Delete")
+ if(usr.Adjacent(src))
+ active2.fields["notes"] = new_notes
if("new")
if(istype(active1, /datum/data/record) && !istype(active2, /datum/data/record))
var/datum/data/record/R = new /datum/data/record()
diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm
index f3155d98d1..86d6842df8 100644
--- a/code/game/machinery/computer/security.dm
+++ b/code/game/machinery/computer/security.dm
@@ -270,6 +270,16 @@
qdel(active1)
if(active2)
qdel(active2)
+ if("sync_r")
+ if(active2)
+ set_temp(client_update_record(src,usr))
+ if("edit_notes")
+ // The modal input in tgui is busted for this sadly...
+ var/new_notes = strip_html_simple(tgui_input_text(usr,"Enter new information here.","Character Preference", html_decode(active2.fields["notes"]), MAX_RECORD_LENGTH, TRUE, prevent_enter = TRUE), MAX_RECORD_LENGTH)
+ if(usr.Adjacent(src))
+ if(new_notes != "" || tgui_alert(usr, "Are you sure you want to delete the current record's notes?", "Confirm Delete", list("Delete", "No")) == "Delete")
+ if(usr.Adjacent(src))
+ active2.fields["notes"] = new_notes
if("d_rec")
var/datum/data/record/general_record = locate(params["d_rec"] || "")
if(!data_core.general.Find(general_record))
diff --git a/code/game/machinery/computer/skills.dm b/code/game/machinery/computer/skills.dm
index 36a498da35..bac1fa7c9c 100644
--- a/code/game/machinery/computer/skills.dm
+++ b/code/game/machinery/computer/skills.dm
@@ -214,6 +214,16 @@
for(var/datum/data/record/R in data_core.general)
qdel(R)
set_temp("All employment records deleted.")
+ if("sync_r")
+ if(active1)
+ set_temp(client_update_record(src,active1,usr))
+ if("edit_notes")
+ // The modal input in tgui is busted for this sadly...
+ var/new_notes = strip_html_simple(tgui_input_text(usr,"Enter new information here.","Character Preference", html_decode(active1.fields["notes"]), MAX_RECORD_LENGTH, TRUE, prevent_enter = TRUE), MAX_RECORD_LENGTH)
+ if(usr.Adjacent(src))
+ if(new_notes != "" || tgui_alert(usr, "Are you sure you want to delete the current record's notes?", "Confirm Delete", list("Delete", "No")) == "Delete")
+ if(usr.Adjacent(src))
+ active1.fields["notes"] = new_notes
if("del_r")
if(PDA_Manifest)
PDA_Manifest.Cut()
diff --git a/code/game/machinery/petrification.dm b/code/game/machinery/petrification.dm
new file mode 100644
index 0000000000..5b061e8006
--- /dev/null
+++ b/code/game/machinery/petrification.dm
@@ -0,0 +1,254 @@
+/obj/machinery/petrification
+ name = "odd interface"
+ desc = "An odd looking machine with an interface, some buttons and a tiny keyboard on the side."
+ icon = 'icons/obj/machines/petrification.dmi'
+ icon_state = "petrification"
+
+ idle_power_usage = 100
+ active_power_usage = 1000
+ use_power = USE_POWER_IDLE
+ anchored = TRUE
+ unacidable = TRUE
+ dir = EAST
+ var/material = "stone"
+ var/identifier = "statue"
+ var/adjective = "hardens"
+ var/tint = "#ffffff"
+ var/able_to_unpetrify = TRUE
+ var/discard_clothes = TRUE
+ var/mob/living/carbon/human/target
+ var/list/remotes = list()
+
+/obj/machinery/petrification/New()
+ . = ..()
+ if(!pixel_x && !pixel_y)
+ pixel_x = (dir & 3) ? 0 : (dir == 4 ? 26 : -26)
+ pixel_y = (dir & 3) ? (dir == 1 ? 26 : -26) : 0
+
+/obj/machinery/petrification/proc/get_viable_targets()
+ var/list/targets = list()
+ //dir is the opposite of whichever direction we want to scan
+ var/turf/center
+ center = get_step(src, turn(dir, 180))
+ if (!center)
+ return
+ //square of 3x3 in front of the device
+ for (var/n = center.x-1; n <= center.x+1; n++)
+ for (var/m = center.y-1; m <= center.y+1; m++)
+ var/turf/T = locate(n,m,z)
+ if (!isturf(T))
+ continue
+ for (var/mob/living/carbon/human/H in T)
+ if (H.stat == DEAD)
+ continue
+ var/option = "[H]["[H]" != H.real_name ? " ([H.real_name])" : ""]"
+ var/r = 1
+ if (option in targets)
+ while ("[option] ([r])" in targets)
+ r += 1
+ option = "[option] ([r])"
+ targets[option] = H
+ return targets
+
+/obj/machinery/petrification/proc/is_valid_target(var/mob/living/carbon/human/H)
+ if (QDELETED(H) || !istype(H) || !H.client)
+ return FALSE
+ var/turf/T = H.loc
+ if (!isturf(T))
+ return FALSE
+ var/turf/center
+ center = get_step(get_turf(src), turn(dir, 180))
+ if (!center)
+ return
+ if (T.z != z || T.x > center.x + 1 || T.x < center.x - 1 || T.y > center.y + 1 || T.y < center.y - 1)
+ return FALSE
+ return TRUE
+
+/obj/machinery/petrification/proc/popup_msg(var/mob/user, var/message, var/notice = TRUE)
+ if (notice)
+ message = "A notice pops up on the interface: \"[message]\""
+ if (target)
+ to_chat(user, span_notice("[message]"))
+
+/obj/machinery/petrification/proc/petrify(var/mob/user, var/obj/item/petrifier/petrifier = null)
+ . = FALSE
+ var/mat = material
+ var/idt = identifier
+ var/adj = adjective
+ var/tnt = tint
+ var/can_unpetrify = able_to_unpetrify
+ var/no_clothes = discard_clothes
+ var/mob/living/carbon/human/statue = target
+ if (petrifier && istype(petrifier))
+ mat = petrifier.material
+ idt = petrifier.identifier
+ adj = petrifier.adjective
+ tnt = petrifier.tint
+ can_unpetrify = petrifier.able_to_unpetrify
+ no_clothes = petrifier.discard_clothes
+ statue = petrifier.target
+ if (QDELETED(statue) || !istype(statue))
+ popup_msg(user, "Invalid target.")
+ return
+ if (statue.stat == DEAD)
+ popup_msg(user, "The target must be alive.")
+ return
+ if (!statue.client)
+ popup_msg(user, "The target must be capable of conscious thought.")
+ return
+ if (!istext(mat) || !istext(idt) || !istext(adj) || !istext(tnt))
+ popup_msg(user, "Invalid options.")
+ var/turf/T = statue.loc
+ if (!istype(T))
+ popup_msg(user, "They must be visible to the [petrifier ? "device" : "machine"].")
+ if (!petrifier)
+ var/turf/center = get_step(get_turf(src), turn(dir, 180))
+ if (!center)
+ return
+ if (T.z != z || T.x > center.x + 1 || T.x < center.x - 1 || T.y > center.y + 1 || T.y < center.y - 1)
+ popup_msg(user, "They are out of range. They must be standing within a 3x3 square in front of the machine.")
+ return
+ else
+ var/turf/center = get_turf(petrifier)
+ if (!center)
+ return
+ if (T.z != center.z || get_dist(center, T) > 4)
+ popup_msg(user, "They are out of range. They must be standing within 4 tiles of the device.")
+ return
+ var/datum/component/gargoyle/comp = statue.GetComponent(/datum/component/gargoyle)
+ if (no_clothes)
+ for(var/obj/item/W in statue)
+ if(istype(W, /obj/item/implant/backup) || istype(W, /obj/item/nif))
+ continue
+ statue.drop_from_inventory(W)
+
+ var/obj/structure/gargoyle/G = new(T, statue, idt, mat, adj, tnt, can_unpetrify, no_clothes)
+ G.was_rayed = TRUE
+
+ if (can_unpetrify)
+ add_verb(statue,/mob/living/carbon/human/proc/gargoyle_transformation)
+ comp?.cooldown = 0
+ else
+ remove_verb(statue,/mob/living/carbon/human/proc/gargoyle_transformation)
+ remove_verb(statue,/mob/living/carbon/human/proc/gargoyle_pause)
+ remove_verb(statue,/mob/living/carbon/human/proc/gargoyle_checkenergy)
+ comp?.cooldown = INFINITY
+
+ if (!petrifier)
+ visible_message(span_notice("A ray of purple light streams out of \the [src], aimed directly at [statue]. Everywhere the light touches on them quickly [adj] into [mat]."))
+ SStgui.update_uis(src)
+ return TRUE
+
+/obj/machinery/petrification/attack_hand(var/mob/user as mob)
+ if(..())
+ return
+ user.set_machine(src)
+ tgui_interact(user)
+
+/obj/machinery/petrification/tgui_interact(mob/user, datum/tgui/ui = null)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "PetrificationInterface", name)
+ ui.open()
+
+/obj/machinery/petrification/tgui_data(mob/user)
+ var/list/data = list()
+ data["material"] = material
+ data["identifier"] = identifier
+ data["adjective"] = adjective
+ data["tint"] = tint
+ var/list/h = rgb2num(tint)
+ data["t"] = ((h[1]*0.299)+(h[2]*0.587)+(h[3]*0.114)) > 102 //0.4 luminance
+ data["target"] = "[target ? target : "None"]"
+ data["able_to_unpetrify"] = able_to_unpetrify
+ data["discard_clothes"] = discard_clothes
+ data["can_remote"] = is_valid_target(target) && istext(material) && istext(identifier) && istext(adjective) && istext(tint)
+ return data
+
+/obj/machinery/petrification/proc/set_input(var/option, mob/user)
+ var/list/only_these = list("tint","material","identifier","adjective","able_to_unpetrify","discard_clothes","target")
+ if (!(option in only_these))
+ return
+ switch(option)
+ if("tint")
+ var/new_color = input(user, "Choose the color for the [identifier] to be:", "Statue color", tint) as color|null
+ if (new_color)
+ tint = new_color
+ if("material","identifier","adjective")
+ var/input = tgui_input_text(user, "What should the [option] be?", "Statue [option]", vars[option], MAX_NAME_LEN)
+ input = sanitizeSafe(input, 25)
+ if (length(input) <= 0)
+ return
+ if (option == "adjective")
+ if (copytext_char(input, -1) != "s")
+ switch(copytext_char(input, -2))
+ if ("ss")
+ input += "es"
+ if ("sh")
+ input += "es"
+ if ("ch")
+ input += "es"
+ else
+ switch(copytext_char(input, -1))
+ if("s", "x", "z")
+ input += "es"
+ else
+ input += "s"
+ vars[option] = input
+ if("able_to_unpetrify", "discard_clothes")
+ vars[option] = !vars[option]
+ if("target")
+ var/list/targets = get_viable_targets()
+ if (!length(targets))
+ popup_msg(user, "No targets within range. Make sure there is a humanoid being within a 3x3 metre square in front of the interface.")
+ return
+ var/selected = input(user, "Choose the target.", "Petrification Target") as null|anything in targets
+ if (selected && ishuman(targets[selected]) && is_valid_target(targets[selected]))
+ var/confirmation = tgui_alert(targets[selected], "You have been selected as a petrification target. If you press confirm, you will possibly be turned into a statue, and if the option is selected, possibly one that cannot be reverted back from a statue at all.","Petrification Target",list("Confirm", "Cancel"))
+ if (confirmation != "Confirm")
+ popup_msg(user, "They declined the request.", FALSE)
+ return
+ var/double = tgui_alert(targets[selected], "This is your last warning, are you -certain-?","Petrification Target",list("Confirm", "Cancel"))
+ if (confirmation == "Confirm" && double == "Confirm")
+ target = targets[selected]
+ else
+ popup_msg(user, "They declined the request.", FALSE)
+
+/obj/machinery/petrification/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
+ if(..())
+ return TRUE
+
+ if (ui.user)
+ add_fingerprint(ui.user)
+
+ switch(action)
+ if("set_option")
+ if (params["option"])
+ set_input(params["option"], ui.user)
+ SStgui.update_uis(src)
+ return TRUE
+ if("petrify")
+ petrify(ui.user)
+ return TRUE
+ if("remote")
+ if (is_valid_target(target) && istext(material) && istext(identifier) && istext(adjective) && istext(tint))
+ var/obj/item/petrifier/PE = remotes[target]
+ if (!QDELETED(PE))
+ PE.visible_message(span_warning("\The [PE] disappears!"))
+ qdel(PE)
+ var/obj/item/petrifier/P = new(loc, src)
+ P.material = material
+ P.identifier = identifier
+ P.adjective = adjective
+ P.tint = tint
+ P.able_to_unpetrify = able_to_unpetrify
+ P.discard_clothes = discard_clothes
+ P.target = target
+ remotes[target] = P
+ ui.user.put_in_hands(P)
+ return TRUE
+ return TRUE
+
+/obj/item/paper/petrification_notes
+ name = "written notes"
+ info = "" + span_italics("Found this buried in the machine over there after digging through it a bit- I hooked it up to one of our displays so it was a bit more usable- seems to be a spare part, it was right next to another one that actually " + span_bold("was") + " hooked up. Turns things into other materials, probably one of the components that makes that machine work.") + ""
diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm
index a27bff7038..5753d327b7 100644
--- a/code/game/objects/effects/decals/Cleanable/humans.dm
+++ b/code/game/objects/effects/decals/Cleanable/humans.dm
@@ -194,7 +194,7 @@ var/global/list/image/splatter_cache=list()
density = FALSE
anchored = TRUE
icon = 'icons/effects/blood.dmi'
- icon_state = "gibbl5"
+ icon_state = "gib1"
random_icon_states = list("gib1", "gib2", "gib3", "gib5", "gib6")
var/fleshcolor = "#FFFFFF"
diff --git a/code/game/objects/effects/info.dm b/code/game/objects/effects/info.dm
new file mode 100644
index 0000000000..e98cf020a1
--- /dev/null
+++ b/code/game/objects/effects/info.dm
@@ -0,0 +1,28 @@
+/// An info button that, when clicked, puts some text in the user's chat
+/obj/effect/abstract/info
+ name = "info"
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "info"
+
+ mouse_opacity = MOUSE_OPACITY_OPAQUE
+
+ /// What should the info button display when clicked?
+ var/info_text
+
+/obj/effect/abstract/info/Initialize(mapload, info_text)
+ . = ..()
+
+ if (!isnull(info_text))
+ src.info_text = info_text
+
+/obj/effect/abstract/info/Click()
+ . = ..()
+ to_chat(usr, info_text)
+
+/obj/effect/abstract/info/MouseEntered(location, control, params)
+ . = ..()
+ icon_state = "info_hovered"
+
+/obj/effect/abstract/info/MouseExited()
+ . = ..()
+ icon_state = initial(icon_state)
diff --git a/code/game/objects/items/petrifier.dm b/code/game/objects/items/petrifier.dm
new file mode 100644
index 0000000000..0e976bef48
--- /dev/null
+++ b/code/game/objects/items/petrifier.dm
@@ -0,0 +1,28 @@
+/obj/item/petrifier
+ name = "odd button"
+ desc = "A metal device with a single, purple button on it, and a tiny interface."
+ icon = 'icons/obj/machines/petrification.dmi'
+ icon_state = "petrifier"
+
+ var/mob/living/carbon/human/target
+ var/identifier = "statue"
+ var/material = "stone"
+ var/adjective = "hardens"
+ var/tint = "#FFFFFF"
+ var/discard_clothes = TRUE
+ var/able_to_unpetrify = TRUE
+ var/obj/machinery/petrification/linked
+
+/obj/item/petrifier/Initialize(mapload, var/to_link)
+ . = ..()
+ linked = to_link
+
+/obj/item/petrifier/attack_self(var/mob/user)
+ . = ..()
+ if (!isturf(user.loc) && user.get_ultimate_mob() != target)
+ to_chat(user, span_warning("The device beeps but does nothing."))
+ return
+ if (linked?.petrify(user, src))
+ visible_message(span_notice("A ray of purple light streams out of \the [src], aimed directly at [target]. Everywhere the light touches on them quickly [adjective] into [material]."))
+ to_chat(user, span_warning("The device fizzles and crumbles into dust."))
+ qdel(src)
diff --git a/code/game/objects/structures/gargoyle.dm b/code/game/objects/structures/gargoyle.dm
new file mode 100644
index 0000000000..04ef9cd127
--- /dev/null
+++ b/code/game/objects/structures/gargoyle.dm
@@ -0,0 +1,293 @@
+/obj/structure/gargoyle
+ name = "statue"
+ desc = "A very lifelike carving."
+ density = TRUE
+ anchored = TRUE
+ var/mob/living/carbon/human/gargoyle
+ var/initial_sleep
+ var/initial_blind
+ var/initial_is_shifted
+ var/initial_lying
+ var/initial_lying_prev
+ var/wagging
+ var/flapping
+ var/obj_integrity = 100
+ var/original_int = 100
+ var/max_integrity = 100
+ var/stored_examine
+ var/identifier = "statue"
+ var/material = "stone"
+ var/adjective = "hardens"
+ var/list/tail_lower_dirs = list(SOUTH, EAST, WEST)
+ var/image/tail_image
+ var/tail_alt = TAIL_UPPER_LAYER
+
+ var/can_revert = TRUE
+ var/was_rayed = FALSE
+
+/obj/structure/gargoyle/Initialize(mapload, var/mob/living/carbon/human/H, var/ident_ovr, var/mat_ovr, var/adj_ovr, var/tint_ovr, var/revert = TRUE, var/discard_clothes)
+ . = ..()
+ if (isspace(loc) || isopenspace(loc))
+ anchored = FALSE
+ if (!istype(H) || !isturf(H.loc))
+ return
+ var/datum/component/gargoyle/comp = H.GetComponent(/datum/component/gargoyle)
+ var/tint = "#FFFFFF"
+ if (comp)
+ comp.cooldown = world.time + (15 SECONDS)
+ comp.statue = src
+ comp.transformed = TRUE
+ comp.paused = FALSE
+ identifier = length(comp.identifier) > 0 ? comp.identifier : initial(identifier)
+ material = length(comp.material) > 0 ? comp.material : initial(material)
+ tint = length(comp.tint) > 0 ? comp.tint : initial(tint)
+ adjective = length(comp.adjective) > 0 ? comp.adjective : initial(adjective)
+ if (copytext_char(adjective, -1) != "s")
+ adjective += "s"
+ gargoyle = H
+
+ if (H.get_effective_size(TRUE) < 0.5) // "So small! I can step over it!"
+ density = FALSE
+
+ if (ident_ovr)
+ identifier = ident_ovr
+ if (mat_ovr)
+ material = mat_ovr
+ if (adj_ovr)
+ adjective = adj_ovr
+ if (tint_ovr)
+ tint = tint_ovr
+
+ if (H.tail_style?.clip_mask_state)
+ tail_lower_dirs.Cut()
+ else if (H.tail_style)
+ tail_lower_dirs = H.tail_style.lower_layer_dirs.Copy()
+ tail_alt = H.tail_alt ? TAIL_UPPER_LAYER_ALT : TAIL_UPPER_LAYER
+
+ max_integrity = H.getMaxHealth() + 100
+ obj_integrity = H.health + 100
+ original_int = obj_integrity
+ name = "[identifier] of [H.name]"
+ desc = "A very lifelike [identifier] made of [material]."
+ stored_examine = H.examine(H)
+ description_fluff = H.get_description_fluff()
+
+ if (H.buckled)
+ H.buckled.unbuckle_mob(H, TRUE)
+ //icon = H.icon
+ //copy_overlays(H)
+
+ //calculate our tints
+ var/list/RGB = rgb2num(tint)
+
+ var/colorr = rgb(RGB[1]*0.299, RGB[2]*0.299, RGB[3]*0.299)
+ var/colorg = rgb(RGB[1]*0.587, RGB[2]*0.587, RGB[3]*0.587)
+ var/colorb = rgb(RGB[1]*0.114, RGB[2]*0.114, RGB[3]*0.114)
+
+ var/tint_color = list(colorr, colorg, colorb, "#000000")
+
+ var/list/body_layers = HUMAN_BODY_LAYERS
+ var/list/other_layers = HUMAN_OTHER_LAYERS
+ for (var/i = 1; i <= length(H.overlays_standing); i++)
+ if (i in other_layers)
+ continue
+ if (discard_clothes && !(i in body_layers))
+ continue
+ if (istype(H.overlays_standing[i], /image) && (i in body_layers))
+ var/image/old_image = H.overlays_standing[i]
+ var/image/new_image = image(old_image)
+ if (i == TAIL_LOWER_LAYER || i == TAIL_UPPER_LAYER || i == TAIL_UPPER_LAYER_ALT)
+ tail_image = new_image
+ new_image.color = tint_color
+ new_image.layer = old_image.layer
+ add_overlay(new_image)
+ else
+ if (!isnull(H.overlays_standing[i]))
+ add_overlay(H.overlays_standing[i])
+
+ initial_sleep = H.sleeping
+ initial_blind = H.eye_blind
+ initial_is_shifted = H.is_shifted
+ transform = H.transform
+ layer = H.layer
+ pixel_x = H.pixel_x
+ pixel_y = H.pixel_y
+ dir = H.dir
+ initial_lying = H.lying
+ initial_lying_prev = H.lying_prev
+ H.sdisabilities |= MUTE
+ if (H.appearance_flags & PIXEL_SCALE)
+ appearance_flags |= PIXEL_SCALE
+ wagging = H.wagging
+ H.transforming = TRUE
+ flapping = H.flapping
+ H.toggle_tail(FALSE, FALSE)
+ H.toggle_wing(FALSE, FALSE)
+ H.visible_message(span_warning("[H]'s skin rapidly [adjective] as they turn to [material]!"), span_warning("Your skin abruptly [adjective] as you turn to [material]!"))
+ H.forceMove(src)
+ H.SetBlinded(0)
+ H.SetSleeping(0)
+ H.status_flags |= GODMODE
+ H.updatehealth()
+ H.canmove = 0
+
+ can_revert = revert
+
+ START_PROCESSING(SSprocessing, src)
+
+/obj/structure/gargoyle/Destroy()
+ STOP_PROCESSING(SSprocessing, src)
+ if (!gargoyle)
+ return ..()
+ if (can_revert)
+ unpetrify(deleting = FALSE) //don't delete if we're already deleting!
+ else
+ visible_message(span_warning("The [identifier] loses shape and crumbles into a pile of [material]!"))
+ . = ..()
+
+/obj/structure/gargoyle/process()
+ if (!gargoyle)
+ qdel(src)
+ if (gargoyle.loc != src)
+ can_revert = TRUE //something's gone wrong, they escaped, lets not qdel them
+ unpetrify(deal_damage = FALSE, deleting = TRUE)
+
+/obj/structure/gargoyle/examine_icon()
+ var/icon/examine_icon = icon(icon=src.icon, icon_state=src.icon_state, dir=SOUTH, frame=1, moving=0)
+ examine_icon.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0))
+ return examine_icon
+
+/obj/structure/gargoyle/get_description_info()
+ if (gargoyle)
+ if (isspace(loc) || isopenspace(loc))
+ return
+ return "It can be [anchored ? "un" : ""]anchored with a wrench."
+
+/obj/structure/gargoyle/examine(mob/user)
+ . = ..()
+ if (gargoyle && stored_examine)
+ . += "The [identifier] seems to have a bit more to them..."
+ . += stored_examine
+ return
+
+/obj/structure/gargoyle/proc/unpetrify(var/deal_damage = TRUE, var/deleting = FALSE)
+ if (!gargoyle)
+ return
+ var/datum/component/gargoyle/comp = gargoyle.GetComponent(/datum/component/gargoyle)
+ if (comp)
+ comp.cooldown = world.time + (15 SECONDS)
+ comp.statue = null
+ comp.transformed = FALSE
+ else
+ if (was_rayed)
+ remove_verb(gargoyle,/mob/living/carbon/human/proc/gargoyle_transformation)
+ if (gargoyle.loc == src)
+ gargoyle.forceMove(loc)
+ gargoyle.transform = transform
+ gargoyle.pixel_x = pixel_x
+ gargoyle.pixel_y = pixel_y
+ gargoyle.is_shifted = initial_is_shifted
+ gargoyle.dir = dir
+ gargoyle.lying = initial_lying
+ gargoyle.lying_prev = initial_lying_prev
+ gargoyle.toggle_tail(wagging, FALSE)
+ gargoyle.toggle_wing(flapping, FALSE)
+ gargoyle.sdisabilities &= ~MUTE //why is there no ADD_TRAIT etc here that's actually ussssed
+ gargoyle.status_flags &= ~GODMODE
+ gargoyle.SetBlinded(initial_blind)
+ gargoyle.SetSleeping(initial_sleep)
+ gargoyle.transforming = FALSE
+ gargoyle.canmove = 1
+ gargoyle.update_canmove()
+ var/hurtmessage = ""
+ if (deal_damage)
+ if (obj_integrity < original_int)
+ var/f = (original_int - obj_integrity) / 10
+ for (var/x in 1 to 10)
+ gargoyle.adjustBruteLoss(f)
+ hurtmessage = " " + span_bold("You feel your body take the damage that was dealt while being [material]!")
+ gargoyle.updatehealth()
+ alpha = 0
+ gargoyle.visible_message(span_warning("[gargoyle]'s skin rapidly reverts, returning them to normal!"), span_warning("Your skin reverts, freeing your movement once more![hurtmessage]"))
+ gargoyle = null
+ if (deleting)
+ qdel(src)
+
+/obj/structure/gargoyle/return_air()
+ return return_air_for_internal_lifeform()
+
+/obj/structure/gargoyle/return_air_for_internal_lifeform(var/mob/living/lifeform)
+ var/air_type = /datum/gas_mixture/belly_air
+ if(istype(lifeform))
+ air_type = lifeform.get_perfect_belly_air_type()
+ var/air = new air_type(1000)
+ return air
+
+/obj/structure/gargoyle/proc/damage(var/damage)
+ if (was_rayed)
+ return //gargoyle quick regenerates, the others don't, so let's not have them getting too damaged
+ obj_integrity = min(obj_integrity-damage, max_integrity)
+ if(obj_integrity <= 0)
+ qdel(src)
+
+/obj/structure/gargoyle/take_damage(var/damage)
+ damage(damage)
+
+/obj/structure/gargoyle/attack_generic(var/mob/user, var/damage, var/attack_message = "hits")
+ user.do_attack_animation(src)
+ visible_message(span_danger("[user] [attack_message] the [src]!"))
+ damage(damage)
+
+/obj/structure/gargoyle/attackby(var/obj/item/W as obj, var/mob/living/user as mob)
+ if(W.is_wrench())
+ if (isspace(loc) || isopenspace(loc))
+ to_chat(user, span_warning("You can't anchor that here!"))
+ anchored = FALSE
+ return ..()
+ playsound(src, W.usesound, 50, 1)
+ if (do_after(user, (2 SECONDS) * W.toolspeed, target = src))
+ to_chat(user, span_notice("You [anchored ? "un" : ""]anchor the [src]."))
+ anchored = !anchored
+ else if(!isrobot(user) && gargoyle && gargoyle.vore_selected && gargoyle.trash_catching)
+ if(istype(W,/obj/item/grab || /obj/item/holder))
+ gargoyle.vore_attackby(W, user)
+ return
+ if(gargoyle.adminbus_trash || is_type_in_list(W,edible_trash) && W.trash_eatable && !is_type_in_list(W,item_vore_blacklist))
+ to_chat(user, span_warning("You slip [W] into [gargoyle]'s [lowertext(gargoyle.vore_selected.name)] ."))
+ user.drop_item()
+ W.forceMove(gargoyle.vore_selected)
+ return
+ else if (!(W.flags & NOBLUDGEON))
+ user.setClickCooldown(user.get_attack_speed(W))
+ if(W.damtype == BRUTE || W.damtype == BURN)
+ user.do_attack_animation(src)
+ playsound(src, W.hitsound, 50, 1)
+ damage(W.force)
+ else
+ return ..()
+
+/obj/structure/gargoyle/set_dir(var/new_dir)
+ . = ..()
+ if(. && tail_image)
+ cut_overlay(tail_image)
+ tail_image.layer = BODY_LAYER + ((dir in tail_lower_dirs) ? TAIL_LOWER_LAYER : tail_alt)
+ add_overlay(tail_image)
+
+/obj/structure/gargoyle/hitby(atom/movable/AM as mob|obj,var/speed = THROWFORCE_SPEED_DIVISOR)
+ if(istype(AM,/obj/item) && gargoyle && gargoyle.vore_selected && gargoyle.trash_catching)
+ var/obj/item/I = AM
+ if(gargoyle.adminbus_trash || is_type_in_list(I,edible_trash) && I.trash_eatable && !is_type_in_list(I,item_vore_blacklist))
+ gargoyle.hitby(AM, speed)
+ return
+ else if(istype(AM,/mob/living) && gargoyle)
+ var/mob/living/L = AM
+ if(gargoyle.throw_vore && L.throw_vore && gargoyle.can_be_drop_pred && L.can_be_drop_prey)
+ var/drop_prey_temp = FALSE
+ if(gargoyle.can_be_drop_prey)
+ drop_prey_temp = TRUE
+ gargoyle.can_be_drop_prey = FALSE //Making sure the original gargoyle body is not the one getting throwvored instead.
+ gargoyle.hitby(L, speed)
+ if(drop_prey_temp)
+ gargoyle.can_be_drop_prey = TRUE
+ return
+ return ..()
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index f94a30756a..74c86c61e1 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -573,6 +573,9 @@
H.l_hand.clean_blood()
H.bloody_hands = 0
H.germ_level = 0
+ H.hand_blood_color = null
+ LAZYCLEARLIST(H.blood_DNA)
+ H.update_bloodied()
else
user.clean_blood()
for(var/mob/V in viewers(src, null))
diff --git a/code/modules/asset_cache/assets/kitchen_recipes.dm b/code/modules/asset_cache/assets/kitchen_recipes.dm
new file mode 100644
index 0000000000..2cca2ed670
--- /dev/null
+++ b/code/modules/asset_cache/assets/kitchen_recipes.dm
@@ -0,0 +1,26 @@
+/datum/asset/spritesheet/kitchen_recipes
+ name = "kitchen_recipes"
+
+/datum/asset/spritesheet/kitchen_recipes/create_spritesheets()
+ for(var/datum/recipe/R as anything in subtypesof(/datum/recipe))
+ add_atom_icon(R.result, sanitize_css_class_name("[R.type]"))
+
+/datum/asset/spritesheet/kitchen_recipes/proc/add_atom_icon(typepath, id)
+ var/icon_file
+ var/icon_state
+ var/obj/preview_item = typepath
+
+ // if(ispath(ingredient_typepath, /datum/reagent))
+ // var/datum/reagent/reagent = ingredient_typepath
+ // preview_item = initial(reagent.default_container)
+ // var/datum/glass_style/style = GLOB.glass_style_singletons[preview_item]?[reagent]
+ // if(istype(style))
+ // icon_file = style.icon
+ // icon_state = style.icon_state
+
+ // icon_file ||= initial(preview_item.icon_preview) || initial(preview_item.icon)
+ // icon_state ||= initial(preview_item.icon_state_preview) || initial(preview_item.icon_state)
+ icon_file = initial(preview_item.icon)
+ icon_state = initial(preview_item.icon_state)
+
+ Insert("[id]", icon_file, icon_state)
diff --git a/code/modules/client/preference_setup/vore/07_traits.dm b/code/modules/client/preference_setup/vore/07_traits.dm
index 2614b6a8f4..6fca677568 100644
--- a/code/modules/client/preference_setup/vore/07_traits.dm
+++ b/code/modules/client/preference_setup/vore/07_traits.dm
@@ -63,6 +63,9 @@ var/global/list/valid_bloodreagents = list("default","iron","copper","phoron","s
. += link + (trait_prefs[identifier] ? "Enabled" : "Disabled")
if (2) //TRAIT_PREF_TYPE_COLOR
. += " " + color_square(hex = trait_prefs[identifier]) + link + "Change"
+ if (3) //TRAIT_PREF_TYPE_STRING
+ var/string = trait_prefs[identifier]
+ . += link + (length(string) > 0 ? string : "\[Empty\]")
. += ""
. += ""
if (altered)
@@ -101,6 +104,9 @@ var/global/list/valid_bloodreagents = list("default","iron","copper","phoron","s
var/new_color = input(user, "Choose the color for this trait preference:", "Trait Preference", trait_prefs[preference]) as color|null
if (new_color)
trait_prefs[preference] = new_color
+ if (3) //TRAIT_PREF_TYPE_STRING
+ var/new_string = instance.apply_sanitization_to_string(preference, tgui_input_text(user, "What should the new value be?", instance.has_preferences[preference][2], trait_prefs[preference], MAX_NAME_LEN))
+ trait_prefs[preference] = new_string
// Definition of the stuff for Ears
/datum/category_item/player_setup_item/vore/traits
diff --git a/code/modules/client/record_updater.dm b/code/modules/client/record_updater.dm
new file mode 100644
index 0000000000..de802eedcf
--- /dev/null
+++ b/code/modules/client/record_updater.dm
@@ -0,0 +1,129 @@
+var/global/client_record_update_lock = FALSE
+
+// Manually updating records from medical console to a player's save.
+/proc/get_current_mob_from_record(var/datum/data/record/active)
+ var/datum/transcore_db/db = SStranscore.db_by_mind_name(active.fields["name"])
+ if(db)
+ var/datum/transhuman/mind_record/record = db.backed_up[active.fields["name"]]
+ if(record.mind_ref)
+ var/datum/mind/D = record.mind_ref
+ if(D.current)
+ var/client/C = D.current.client
+ if(C && C.ckey != record.ckey)
+ return null
+ return D.current
+ return null
+
+
+/proc/client_update_record(var/obj/machinery/computer/COM, var/user)
+ if(!COM || QDELETED(COM))
+ return "Invalid console"
+
+ if(jobban_isbanned(user, "Records") )
+ COM.visible_message(span_notice("\The [COM] buzzes!"))
+ playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0)
+ return "Update syncronization denied (OOC: You are banned from editing records)"
+
+ var/record_string = ""
+ var/datum/data/record/active
+ var/console_path = null
+ if(istype(COM,/obj/machinery/computer/med_data))
+ var/obj/machinery/computer/med_data/MCOM = COM
+ active = MCOM.active2
+ record_string = "medical"
+ console_path = /obj/machinery/computer/med_data
+ if(istype(COM,/obj/machinery/computer/skills))
+ var/obj/machinery/computer/skills/ECOM = COM
+ active = ECOM.active1
+ record_string = "employment"
+ console_path = /obj/machinery/computer/skills
+ if(istype(COM,/obj/machinery/computer/secure_data))
+ var/obj/machinery/computer/secure_data/SCOM = COM
+ active = SCOM.active2
+ record_string = "security"
+ console_path = /obj/machinery/computer/secure_data
+
+ if(client_record_update_lock)
+ to_chat(user,"Update already in progress! Please wait a moment...")
+ if(COM && !QDELETED(COM))
+ COM.visible_message(span_notice("\The [COM] buzzes!"))
+ playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0)
+ return "Update already in progress! Please wait a moment..."
+ client_record_update_lock = TRUE
+ spawn(60 SECONDS)
+ client_record_update_lock = FALSE
+
+ if(!active || !console_path)
+ if(COM && !QDELETED(COM))
+ COM.visible_message(span_notice("\The [COM] buzzes!"))
+ playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0)
+ return "Update syncronization failed (OOC: Record or console destroyed)"
+
+ to_chat(user,"Update sent! Please wait for a response...")
+ message_admins("[user] pushed [record_string] record update to [active.fields["name"]].")
+
+ var/mob/M = get_current_mob_from_record(active)
+ if(!M)
+ if(COM && !QDELETED(COM))
+ COM.visible_message(span_notice("\The [COM] buzzes!"))
+ playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0)
+ return "Update syncronization failed (OOC: Client mob does not exist, has no mind record, or is possesssed)"
+
+ var/client/C = M.client
+ if(!C)
+ if(COM && !QDELETED(COM))
+ COM.visible_message(span_notice("\The [COM] buzzes!"))
+ playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0)
+ return "Update syncronization failed (OOC: Record's owner is offline)"
+
+ var/choice = tgui_alert(M, "Your [record_string] record has been updated from the a records console by [user]. Please review the changes made to your [record_string] record. Accepting these changes will SAVE your CURRENT character slot! If your new [record_string] record has errors, it is recomended to have it corrected IC instead of editing it yourself.", "Record Updated", list("Review Changes","Refuse Update"))
+ if(choice == "Refuse Update")
+ message_admins("[active.fields["name"]] refused [record_string] record update from [user] without review.")
+ if(COM && !QDELETED(COM))
+ COM.visible_message(span_notice("\The [COM] buzzes!"))
+ playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0)
+ return "Update syncronization failed (OOC: Client refused without review)"
+
+ var/datum/preferences/P = C.prefs
+ var/new_data = strip_html_simple(tgui_input_text(M,"Please review [user]'s changes to your [record_string] record before confirming. Confirming will SAVE your CURRENT character slot! If your new [record_string] record major errors, it is recomended to have it corrected IC instead of editing it yourself.","Character Preference", html_decode(active.fields["notes"]), MAX_RECORD_LENGTH, TRUE, prevent_enter = TRUE), MAX_RECORD_LENGTH)
+ if(!new_data)
+ message_admins("[active.fields["name"]] refused [record_string] record update from [user] with review.")
+ if(COM && !QDELETED(COM))
+ COM.visible_message(span_notice("\The [COM] buzzes!"))
+ playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0)
+ return "Update syncronization failed (OOC: Client refused with review)"
+ if(!M || !M.client || !P)
+ message_admins("[active.fields["name"]]'s [record_string] record could not be updated, client disconnected.")
+ if(COM && !QDELETED(COM))
+ COM.visible_message(span_notice("\The [COM] buzzes!"))
+ playsound(COM, 'sound/machines/deniedbeep.ogg', 50, 0)
+ return "Update syncronization failed (OOC: Client does not exist)"
+
+ // Update records in the consoles, remember this can happen a while after a record is closed on the console... Use cached data.
+ switch(console_path)
+ if(/obj/machinery/computer/med_data)
+ P.med_record = new_data
+ if(active)
+ active.fields["notes"] = new_data
+ if(/obj/machinery/computer/skills)
+ P.gen_record = new_data
+ if(active)
+ active.fields["notes"] = new_data
+ if(/obj/machinery/computer/secure_data)
+ P.sec_record = new_data
+ if(active)
+ active.fields["notes"] = new_data
+
+ // Update player record
+ P.save_preferences()
+ P.save_character()
+ if(M)
+ to_chat(M,span_notice("Your [record_string] record for [active.fields["name"]] has been updated."))
+ message_admins("[active.fields["name"]] accepted the [record_string] record update from [user].")
+
+ // ding!
+ if(COM && !QDELETED(COM))
+ COM.visible_message(span_notice("\The [COM] dings!"))
+ playsound(COM, 'sound/machines/ding.ogg', 50, 1)
+
+ return "Record syncronized."
diff --git a/code/modules/emotes/emote_mob.dm b/code/modules/emotes/emote_mob.dm
index 1fc894d530..8c63ecbfc9 100644
--- a/code/modules/emotes/emote_mob.dm
+++ b/code/modules/emotes/emote_mob.dm
@@ -83,7 +83,7 @@
var/decl/emote/use_emote = get_emote_by_key(act)
if(!istype(use_emote))
- to_chat(src, span_warning("Unknown emote '[act]'. Type say *help for a list of usable emotes."))
+ to_chat(src, span_warning("Unknown emote '[act]'. Type " + span_bold("say *help") + " for a list of usable emotes. ([act] [message])")) // Add full message in the event you used * instead of ! or something like that
return
if(!use_emote.mob_can_use(src))
@@ -210,6 +210,7 @@
var/turf/T = get_turf(src)
if(!T) return
+
if(client)
playsound(T, pick(emote_sound), 25, TRUE, falloff = 1 , is_global = TRUE, frequency = ourfreq, ignore_walls = FALSE, preference = /datum/preference/toggle/emote_sounds)
@@ -224,7 +225,11 @@
message = span_emote(span_bold("[src]") + " ([ghost_follow_link(src, M)]) [input]")
if(usr && usr.client && M && !(get_z(usr) == get_z(M)))
message = span_multizsay("[message]")
- M.show_message(message, m_type)
+ // If you are in the same tile, right next to, or being held by a person doing an emote, you should be able to see it while blind
+ if(m_type != AUDIBLE_MESSAGE && (src.Adjacent(M) || (istype(src.loc, /obj/item/holder) && src.loc.loc == M)))
+ M.show_message(message)
+ else
+ M.show_message(message, m_type)
M.create_chat_message(src, "[runemessage]", FALSE, list("emote"), (m_type == AUDIBLE_MESSAGE))
for(var/obj/O as anything in o_viewers)
diff --git a/code/modules/food/kitchen/cooking_machines/_appliance.dm b/code/modules/food/kitchen/cooking_machines/_appliance.dm
index 5a37e9fb7b..6d95e881cd 100644
--- a/code/modules/food/kitchen/cooking_machines/_appliance.dm
+++ b/code/modules/food/kitchen/cooking_machines/_appliance.dm
@@ -43,6 +43,11 @@
var/combine_first = FALSE // If TRUE, this appliance will do combination cooking before checking recipes
var/food_safety = FALSE //RS ADD - If true, the appliance automatically ejects food instead of burning it
+ var/static/radial_eject = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_eject")
+ var/static/radial_power = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_power")
+ var/static/radial_safety = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_safety")
+ var/static/radial_output = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_change_output")
+
/obj/machinery/appliance/Initialize()
. = ..()
@@ -611,8 +616,31 @@
if(..())
return
- if(cooking_objs.len)
- removal_menu(user)
+ interact(user)
+
+/obj/machinery/appliance/interact(mob/user)
+ var/list/options = list(
+ "power" = radial_power,
+ "safety" = radial_safety,
+ )
+
+ if(LAZYLEN(cooking_objs))
+ options["remove"] = radial_eject
+
+ if(LAZYLEN(output_options))
+ options["select_output"] = radial_output
+
+ var/choice = show_radial_menu(user, src, options, require_near = !issilicon(user))
+
+ switch(choice)
+ if("power")
+ toggle_power()
+ if("safety")
+ toggle_safety()
+ if("remove")
+ removal_menu(user)
+ if("select_output")
+ choose_output()
/obj/machinery/appliance/proc/removal_menu(var/mob/user)
if (can_remove_items(user))
diff --git a/code/modules/food/kitchen/cooking_machines/_cooker.dm b/code/modules/food/kitchen/cooking_machines/_cooker.dm
index 7d62535095..50c9ea2ba3 100644
--- a/code/modules/food/kitchen/cooking_machines/_cooker.dm
+++ b/code/modules/food/kitchen/cooking_machines/_cooker.dm
@@ -26,11 +26,15 @@
/obj/machinery/appliance/cooker/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
var/list/data = ..()
+ data["on"] = !(stat & POWEROFF)
+ data["safety"] = food_safety
data["temperature"] = round(temperature - T0C, 0.1)
data["optimalTemp"] = round(optimal_temp - T0C, 0.1)
data["temperatureEnough"] = temperature >= min_temp
data["efficiency"] = round(get_efficiency(), 0.1)
data["containersRemovable"] = can_remove_items(user, show_warning = FALSE)
+ data["selected_option"] = selected_option
+ data["show_selected_option"] = LAZYLEN(output_options)
var/list/our_contents = list()
for(var/i in 1 to max_contents)
@@ -56,6 +60,15 @@
return TRUE
switch(action)
+ if("toggle_power")
+ attempt_toggle_power(usr)
+ return TRUE
+ if("toggle_safety")
+ toggle_safety()
+ return TRUE
+ if("change_output")
+ choose_output()
+ return TRUE
if("slot")
var/slot = params["slot"]
var/obj/item/I = ui.user.get_active_hand()
diff --git a/code/modules/food/kitchen/microwave.dm b/code/modules/food/kitchen/microwave.dm
index 41a01ee608..eeadee61e7 100644
--- a/code/modules/food/kitchen/microwave.dm
+++ b/code/modules/food/kitchen/microwave.dm
@@ -138,6 +138,7 @@
user.visible_message( \
span_notice("\The [user] has added one of [O] to \the [src]."), \
span_notice("You add one of [O] to \the [src]."))
+ update_static_data_for_all_viewers()
return
else
// user.remove_from_mob(O) //This just causes problems so far as I can tell. -Pete - Man whoever you are, it's been years. o7
@@ -145,7 +146,7 @@
user.visible_message( \
span_notice("\The [user] has added \the [O] to \the [src]."), \
span_notice("You add \the [O] to \the [src]."))
- SStgui.update_uis(src)
+ update_static_data_for_all_viewers()
return
else if (istype(O,/obj/item/storage/bag/plants)) // There might be a better way about making plant bags dump their contents into a microwave, but it works.
var/obj/item/storage/bag/plants/bag = O
@@ -173,6 +174,7 @@
to_chat(user, "You fill \the [src] from \the [O].")
SStgui.update_uis(src)
+ update_static_data_for_all_viewers()
return 0
else if(istype(O,/obj/item/reagent_containers/glass) || \
@@ -185,6 +187,8 @@
if (!(R.id in acceptable_reagents))
to_chat(user, span_warning("Your [O] contains components unsuitable for cookery."))
return 1
+ // gotta let afterattack resolve
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/datum, update_static_data_for_all_viewers)), 1 SECOND)
return
else if(istype(O,/obj/item/grab))
var/obj/item/grab/G = O
@@ -219,6 +223,11 @@
..()
SStgui.update_uis(src)
+/obj/machinery/microwave/tgui_status(mob/user)
+ if(user == paicard?.pai)
+ return STATUS_INTERACTIVE
+ . = ..()
+
/obj/machinery/microwave/tgui_state(mob/user)
return GLOB.tgui_physical_state
@@ -242,6 +251,20 @@
ui = new(user, src, "Microwave", name)
ui.open()
+/obj/machinery/microwave/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/kitchen_recipes)
+ )
+
+/obj/machinery/microwave/tgui_static_data(mob/user)
+ var/list/data = ..()
+
+ var/datum/recipe/recipe = select_recipe(available_recipes,src)
+ data["recipe"] = recipe ? sanitize_css_class_name("[recipe.type]") : null
+ data["recipe_name"] = recipe ? initial(recipe.result:name) : null
+
+ return data
+
/obj/machinery/microwave/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
var/list/data = ..()
@@ -250,6 +273,21 @@
data["dirty"] = dirty == 100
data["items"] = get_items_list()
+ var/list/reagents_data = list()
+ for(var/datum/reagent/R in reagents.reagent_list)
+ var/display_name = R.name
+ if(R.id == "capsaicin")
+ display_name = "Hotsauce"
+ if(R.id == "frostoil")
+ display_name = "Coldsauce"
+ UNTYPED_LIST_ADD(reagents_data, list(
+ "name" = display_name,
+ "amt" = R.volume,
+ "extra" = "unit[R.volume > 1 ? "s" : ""]",
+ "color" = R.color,
+ ))
+ data["reagents"] = reagents_data
+
return data
/obj/machinery/microwave/proc/get_items_list()
@@ -258,7 +296,8 @@
var/list/items_counts = list()
var/list/items_measures = list()
var/list/items_measures_p = list()
- //for(var/obj/O in ((contents - component_parts) - circuit))
+ var/list/icons = list()
+
for(var/obj/O in cookingContents())
var/display_name = O.name
if(istype(O,/obj/item/reagent_containers/food/snacks/egg))
@@ -278,33 +317,26 @@
items_measures[display_name] = "fillet of meat"
items_measures_p[display_name] = "fillets of meat"
items_counts[display_name]++
+ icons[display_name] = list("icon" = O.icon, "icon_state" = O.icon_state)
+
for(var/O in items_counts)
var/N = items_counts[O]
+ var/icon = icons[O]
if(!(O in items_measures))
data.Add(list(list(
"name" = capitalize(O),
"amt" = N,
"extra" = "[lowertext(O)][N > 1 ? "s" : ""]",
+ "icon" = icon,
)))
else
data.Add(list(list(
"name" = capitalize(O),
"amt" = N,
"extra" = N == 1 ? items_measures[O] : items_measures_p[O],
+ "icon" = icon,
)))
- for(var/datum/reagent/R in reagents.reagent_list)
- var/display_name = R.name
- if(R.id == "capsaicin")
- display_name = "Hotsauce"
- if(R.id == "frostoil")
- display_name = "Coldsauce"
- data.Add(list(list(
- "name" = display_name,
- "amt" = R.volume,
- "extra" = "unit[R.volume > 1 ? "s" : ""]"
- )))
-
return data
/obj/machinery/microwave/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
@@ -322,69 +354,6 @@
if("dispose")
dispose()
return TRUE
-/*
-/obj/machinery/microwave/interact(mob/user as mob) // The microwave Menu
- var/dat = ""
- if(src.broken > 0)
- dat = {"Bzzzzttttt"}
- else if(src.operating)
- dat = {"Microwaving in progress!
Please wait...!"}
- else if(src.dirty==100)
- dat = {"This microwave is dirty!
Please clean it before use!"}
- else
- var/list/items_counts = new
- var/list/items_measures = new
- var/list/items_measures_p = new
- for (var/obj/O in ((contents - component_parts) - circuit))
- var/display_name = O.name
- if (istype(O,/obj/item/reagent_containers/food/snacks/egg))
- items_measures[display_name] = "egg"
- items_measures_p[display_name] = "eggs"
- if (istype(O,/obj/item/reagent_containers/food/snacks/tofu))
- items_measures[display_name] = "tofu chunk"
- items_measures_p[display_name] = "tofu chunks"
- if (istype(O,/obj/item/reagent_containers/food/snacks/meat)) //any meat
- items_measures[display_name] = "slab of meat"
- items_measures_p[display_name] = "slabs of meat"
- if (istype(O,/obj/item/reagent_containers/food/snacks/donkpocket))
- display_name = "Turnovers"
- items_measures[display_name] = "turnover"
- items_measures_p[display_name] = "turnovers"
- if (istype(O,/obj/item/reagent_containers/food/snacks/carpmeat))
- items_measures[display_name] = "fillet of meat"
- items_measures_p[display_name] = "fillets of meat"
- items_counts[display_name]++
- for (var/O in items_counts)
- var/N = items_counts[O]
- if (!(O in items_measures))
- dat += span_bold("[capitalize(O)]:") + " [N] [lowertext(O)]\s
"
- else
- if (N==1)
- dat += span_bold("[capitalize(O)]:") + " [N] [items_measures[O]]
"
- else
- dat += span_bold("[capitalize(O)]:") + " [N] [items_measures_p[O]]
"
-
- for (var/datum/reagent/R in reagents.reagent_list)
- var/display_name = R.name
- if (R.id == "capsaicin")
- display_name = "Hotsauce"
- if (R.id == "frostoil")
- display_name = "Coldsauce"
- dat += span_bold("[display_name]:") + " [R.volume] unit\s
"
-
- if (items_counts.len==0 && reagents.reagent_list.len==0)
- dat = span_bold("The microwave is empty") + "
"
- else
- dat = span_bold("Ingredients:") + "
[dat]"
- dat += {"