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 += {"

\ -Turn on!
\ -
Eject ingredients!
\ -"} - - user << browse("Microwave Controls[dat]", "window=microwave") - onclose(user, "microwave") - return -*/ /*********************************** * Microwave Menu Handling/Cooking diff --git a/code/modules/mob/living/carbon/human/species/station/traits_vr/_traits.dm b/code/modules/mob/living/carbon/human/species/station/traits_vr/_traits.dm index 46a4b4c1d5..1c906b501b 100644 --- a/code/modules/mob/living/carbon/human/species/station/traits_vr/_traits.dm +++ b/code/modules/mob/living/carbon/human/species/station/traits_vr/_traits.dm @@ -8,6 +8,7 @@ #define TRAIT_PREF_TYPE_BOOLEAN 1 #define TRAIT_PREF_TYPE_COLOR 2 +#define TRAIT_PREF_TYPE_STRING 3 #define TRAIT_NO_VAREDIT_TARGET 0 #define TRAIT_VAREDIT_TARGET_SPECIES 1 diff --git a/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm b/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm index 6a262f24a4..1abaae82d3 100644 --- a/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm +++ b/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm @@ -1177,3 +1177,48 @@ YW CHANGE STOP*/ /datum/trait/neutral/agraviaphobia/apply(var/datum/species/S,var/mob/living/carbon/human/H, var/trait_prefs = null) ..() H.phobias |= AGRAVIAPHOBIA + +/datum/trait/neutral/gargoyle + name = "Gargoyle (Adjustable)" + desc = "You turn into a statue (or similar) at will, but also whenever you run out of energy. Being a statue replenishes your energy slowly." + cost = 0 + custom_only = FALSE //slimes, xenochimera, diona, proteans, etc, basically anything but custom doesn't make sense (as much as I wanna play a petrifying slime) + //Nah makes perfect sense, they could just be gene modded, not to mention we can expand this to have the statue and description of it renameable as well as color adjustable, to support general petrification + has_preferences = list("identifier" = list(TRAIT_PREF_TYPE_STRING, "Identifier", TRAIT_NO_VAREDIT_TARGET, "statue"), + "material" = list(TRAIT_PREF_TYPE_STRING, "Material", TRAIT_NO_VAREDIT_TARGET, "stone"), + "tint" = list(TRAIT_PREF_TYPE_COLOR, "Statue color", TRAIT_NO_VAREDIT_TARGET, "#FFFFFF"), + "adjective" = list(TRAIT_PREF_TYPE_STRING, "Adjective", TRAIT_NO_VAREDIT_TARGET, "hardens")/*, + "pickupable" = list(TRAIT_PREF_TYPE_BOOLEAN, "Can be picked up", TRAIT_NO_VAREDIT_TARGET, FALSE)*/) + +/datum/trait/neutral/gargoyle/apply(var/datum/species/S,var/mob/living/carbon/human/H, var/list/trait_prefs) + ..() + var/datum/component/gargoyle/G = H.LoadComponent(/datum/component/gargoyle) + if (trait_prefs) + G.tint = trait_prefs["tint"] + G.material = lowertext(trait_prefs["material"]) + G.identifier = lowertext(trait_prefs["identifier"]) + G.adjective = lowertext(trait_prefs["adjective"]) + +/datum/trait/neutral/gargoyle/apply_sanitization_to_string(var/pref, var/input) + if (has_preferences[pref][1] != TRAIT_PREF_TYPE_STRING || length(input) <= 0) + return + input = sanitizeSafe(input, 25) + if (length(input) <= 0) + return default_value_for_pref(pref) + input = lowertext(input) + if (pref == "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" + return input diff --git a/code/modules/mob/living/carbon/human/species/station/traits_vr/trait.dm b/code/modules/mob/living/carbon/human/species/station/traits_vr/trait.dm index d47eda0a34..fb02575361 100644 --- a/code/modules/mob/living/carbon/human/species/station/traits_vr/trait.dm +++ b/code/modules/mob/living/carbon/human/species/station/traits_vr/trait.dm @@ -75,4 +75,14 @@ return TRUE if(TRAIT_PREF_TYPE_COLOR) //color return "#ffffff" + if(TRAIT_PREF_TYPE_STRING) //string + return "" return + +/datum/trait/proc/apply_sanitization_to_string(var/pref, var/input) + if (has_preferences[pref][1] != TRAIT_PREF_TYPE_STRING || length(input) <= 0) + return default_value_for_pref(pref) + input = sanitizeSafe(input, MAX_NAME_LEN) + if (length(input) <= 0) + return default_value_for_pref(pref) + return input diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 116c312176..968f62939b 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -57,52 +57,6 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() cut_overlay(I) overlays_standing[cache_index] = null -// 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 MOB_DAM_LAYER 4 //Injury overlay sprites like open wounds -#define SURGERY_LAYER 5 //Overlays for open surgical sites -#define UNDERWEAR_LAYER 6 //Underwear/bras/etc -#define TAIL_LOWER_LAYER 7 //Tail as viewed from the south -#define WING_LOWER_LAYER 8 //Wings as viewed from the south -#define SHOES_LAYER_ALT 9 //Shoe-slot item (when set to be under uniform via verb) -#define UNIFORM_LAYER 10 //Uniform-slot item -#define ID_LAYER 11 //ID-slot item -#define SHOES_LAYER 12 //Shoe-slot item -#define GLOVES_LAYER 13 //Glove-slot item -#define BELT_LAYER 14 //Belt-slot item -#define SUIT_LAYER 15 //Suit-slot item -#define TAIL_UPPER_LAYER 16 //Some species have tails to render (As viewed from the N, E, or W) -#define GLASSES_LAYER 17 //Eye-slot item -#define BELT_LAYER_ALT 18 //Belt-slot item (when set to be above suit via verb) -#define SUIT_STORE_LAYER 19 //Suit storage-slot item -#define BACK_LAYER 20 //Back-slot item -#define HAIR_LAYER 21 //The human's hair -#define HAIR_ACCESSORY_LAYER 22 //VOREStation edit. Simply move this up a number if things are added. -#define EARS_LAYER 23 //Both ear-slot items (combined image) -#define EYES_LAYER 24 //Mob's eyes (used for glowing eyes) -#define FACEMASK_LAYER 25 //Mask-slot item -#define GLASSES_LAYER_ALT 26 //So some glasses can appear on top of hair and things -#define HEAD_LAYER 27 //Head-slot item -#define HANDCUFF_LAYER 28 //Handcuffs, if the human is handcuffed, in a secret inv slot -#define LEGCUFF_LAYER 29 //Same as handcuffs, for legcuffs -#define L_HAND_LAYER 30 //Left-hand item -#define R_HAND_LAYER 31 //Right-hand item -#define WING_LAYER 32 //Wings or protrusions over the suit. -#define TAIL_UPPER_LAYER_ALT 33 //Modified tail-sprite layer. Tend to be larger. -#define MODIFIER_EFFECTS_LAYER 34 //Effects drawn by modifiers -#define FIRE_LAYER 35 //'Mob on fire' overlay layer -// # define MOB_WATER_LAYER 36 //'Mob submerged' overlay layer // Moved to global defines -#define TARGETED_LAYER 37 //'Aimed at' overlay layer -#define VORE_BELLY_LAYER 38 -#define VORE_TAIL_LAYER 39 - -#define TOTAL_LAYERS 39 //VOREStation edit. <---- KEEP THIS UPDATED, should always equal the highest number here, used to initialize a list. -////////////////////////////////// - /mob/living/carbon/human var/list/overlays_standing[TOTAL_LAYERS] var/previous_damage_appearance // store what the body last looked like, so we only have to update it if something changed @@ -245,6 +199,8 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() if(QDESTROYING(src)) return + remove_layer(BODYPARTS_LAYER) + var/husk_color_mod = rgb(96,88,80) var/hulk_color_mod = rgb(48,224,40) @@ -427,6 +383,13 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() //END CACHED ICON GENERATION. stand_icon.Blend(base_icon,ICON_OVERLAY) + + var/image/body = image(stand_icon) + if (body) + body.layer = BODY_LAYER + BODYPARTS_LAYER + overlays_standing[BODYPARTS_LAYER] = body + apply_layer(BODYPARTS_LAYER) + icon = stand_icon //tail diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 21fc5a29cd..6278f731b7 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -376,7 +376,7 @@ var/list/channel_to_radio_key = new if(M && src) //If we still exist, when the spawn processes //VOREStation Add - Ghosts don't hear whispers if(whispering && isobserver(M) && (!M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle) || \ - (!client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) && !M.client?.holder))) + (!(client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) || (isbelly(M.loc) && src == M.loc:owner)) && !M.client?.holder))) M.show_message(span_game(span_say(span_name(src.name) + " [w_not_heard].")), 2) return //VOREStation Add End @@ -437,7 +437,7 @@ var/list/channel_to_radio_key = new /mob/living/proc/say_signlang(var/message, var/verb="gestures", var/verb_understood="gestures", var/datum/language/language, var/type = 1) var/turf/T = get_turf(src) //We're in something, gesture to people inside the same thing - if(loc != T) + if(loc != T && !istype(loc, /obj/item/holder)) // Partially fixes sign language while being held. for(var/mob/M in loc) M.hear_signlang(message, verb, verb_understood, language, src, type) diff --git a/code/modules/mob/living/silicon/pai/pai_vr.dm b/code/modules/mob/living/silicon/pai/pai_vr.dm index eda552040e..1fa7a3b6e6 100644 --- a/code/modules/mob/living/silicon/pai/pai_vr.dm +++ b/code/modules/mob/living/silicon/pai/pai_vr.dm @@ -291,6 +291,16 @@ if(I_GRAB) pai_nom(A) +// Allow card inhabited machines to be interacted with +// This has to override ClickOn because of storage depth nonsense with how pAIs are in cards in machines +/mob/living/silicon/pai/ClickOn(var/atom/A, var/params) + if(istype(A, /obj/machinery)) + var/obj/machinery/M = A + if(M.paicard == card) + M.attack_ai(src) + return + return ..() + /mob/living/silicon/pai/proc/hug(var/mob/living/silicon/pai/H, var/mob/living/target) var/t_him = "them" diff --git a/code/modules/mob/living/silicon/robot/drone/drone_items.dm b/code/modules/mob/living/silicon/robot/drone/drone_items.dm index 667fd23abb..bb9db80061 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_items.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_items.dm @@ -177,6 +177,23 @@ /obj/item/material/gravemarker ) +/obj/item/gripper/scene + name = "misc gripper" + desc = "A simple grasping tool that can hold a variety of 'general' objects..." + + can_hold = list( + /obj/item/capture_crystal, + /obj/item/clothing, + /obj/item/implanter, + /obj/item/disk/nifsoft/compliance, + /obj/item/handcuffs, + /obj/item/toy, + /obj/item/petrifier, + /obj/item/dice, + /obj/item/casino_platinum_chip, + /obj/item/spacecasinocash + ) + /obj/item/gripper/no_use/organ name = "organ gripper" icon_state = "gripper-flesh" diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station.dm b/code/modules/mob/living/silicon/robot/robot_modules/station.dm index bc54a72e63..5a4c4ea64c 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules/station.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules/station.dm @@ -208,6 +208,7 @@ var/global/list/robot_modules = list( src.modules += new /obj/item/flash/robot(src) src.modules += new /obj/item/extinguisher(src) src.modules += new /obj/item/tool/crowbar/cyborg(src) + src.modules += new /obj/item/gripper/scene(src) /obj/item/robot_module/robot/standard name = "standard robot module" diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/spookyghost.dm b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/spookyghost.dm index 5d2084d0f0..6991ecb70e 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/spookyghost.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/spookyghost.dm @@ -97,7 +97,8 @@ /mob/living/simple_mob/vore/alienanimals/space_ghost/apply_melee_effects(var/atom/A) var/mob/living/L = A - L.hallucination += 50 + if(L.hallucination <= 100) + L.hallucination += rand(1,10) /mob/living/simple_mob/vore/alienanimals/space_ghost/shoot(atom/A) //We're shooting ghosts at people and need them to have the same faction as their parent, okay? if(!projectiletype) @@ -209,7 +210,8 @@ /mob/living/simple_mob/vore/alienanimals/spooky_ghost/apply_melee_effects(var/atom/A) var/mob/living/L = A if(L && istype(L)) - L.hallucination += rand(1,50) + if(L.hallucination <= 100) + L.hallucination += rand(1,10) /mob/living/simple_mob/vore/alienanimals/spooky_ghost/Life() . = ..() diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 9407ec86c2..a0e9148b34 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -126,6 +126,17 @@ /atom/proc/drain_power(var/drain_check,var/surge, var/amount = 0) return -1 + // used for petrification machines +/atom/proc/get_ultimate_mob() + var/mob/ultimate_mob + var/atom/to_check = loc + var/n = 0 + while (to_check && !isturf(to_check) && n++ < 16) + if (ismob(to_check)) + ultimate_mob = to_check + to_check = to_check.loc + return ultimate_mob + // Show a message to all mobs and objects in earshot of this one // This would be for audible actions by the src mob // message is the message output to anyone who can hear. diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index c671eecae0..a57a2daa98 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -3,7 +3,6 @@ /mob/verb/whisper(message as text) set name = "Whisper" - set category = "IC.Subtle" set hidden = 1 //VOREStation Addition Start if(forced_psay) @@ -15,7 +14,6 @@ /mob/verb/say_verb(message as text) set name = "Say" - set category = "IC.Chat" set hidden = 1 set instant = TRUE @@ -33,7 +31,6 @@ /mob/verb/me_verb(message as message) set name = "Me" - set category = "IC.Chat" set desc = "Emote to nearby people (and your pred/prey)" set hidden = 1 @@ -110,7 +107,19 @@ if(speaking.flags & NONVERBAL) if(sdisabilities & BLIND || blinded) return FALSE - if(!other || !(other in view(src))) + if(!other) + return FALSE + // Fixes seeing non-verbal languages while being held + if(istype(other.loc, /obj/item/holder)) + if(istype(src.loc, /obj/item/holder)) + if(!(other.loc in view(src.loc.loc))) + return FALSE + else if(!(other.loc in view(src))) + return FALSE + else if(istype(src.loc, /obj/item/holder)) + if((!other) in view(src.loc.loc)) + return FALSE + else if((!other) in view(src)) return FALSE //Language check. diff --git a/code/modules/mob/say_old.dm b/code/modules/mob/say_old.dm new file mode 100644 index 0000000000..f67ffb58b5 --- /dev/null +++ b/code/modules/mob/say_old.dm @@ -0,0 +1,82 @@ +// Allows the usage of old style chat inputs even with TG Say enabled +/mob/verb/say_verb_old() + set name = "Say Old" + set category = "IC.Chat" + + client?.start_thinking() + client?.start_typing() + var/message = tgui_input_text(usr, "Speak to people in sight.\nType your message:", "Say") + client?.stop_thinking() + + if(message) + say_verb(message) + +/mob/verb/me_verb_old() + set name = "Me Old" + set category = "IC.Chat" + set desc = "Emote to nearby people (and your pred/prey)" + + client?.start_thinking() + client?.start_typing() + var/message = tgui_input_text(usr, "Emote to people in sight (and your pred/prey).\nType your message:", "Emote", multiline = TRUE) + client?.stop_thinking() + + if(message) + me_verb(message) + +/mob/verb/whisper_old() + set name = "Whisper Old" + set category = "IC.Subtle" + + var/message = tgui_input_text(usr, "Speak to nearby people.\nType your message:", "Whisper") + + if(message) + whisper(message) + + +/mob/verb/me_verb_subtle_old() + set name = "Subtle Old" + set category = "IC.Subtle" + set desc = "Emote to nearby people (and your pred/prey)" + + var/message = tgui_input_text(usr, "Emote to nearby people (and your pred/prey).\nType your message:", "Subtle", multiline = TRUE) + + if(message) + me_verb_subtle(message) + +/mob/verb/me_verb_subtle_custom_old() + set name = "Subtle (Custom) Old" + set category = "IC.Subtle" + set desc = "Emote to nearby people, with ability to choose which specific portion of people you wish to target." + + var/message = tgui_input_text(usr, "Emote to nearby people, with ability to choose which specific portion of people you wish to target.\nType your message:", "Subtle (Custom)", multiline = TRUE) + + if(message) + me_verb_subtle_custom(message) + +/mob/verb/psay_old() + set name = "Psay Old" + set category = "IC.Subtle" + + var/message = tgui_input_text(usr, "Talk to people affected by complete absorbed or dominate predator/prey.\nType your message:", "Psay") + + if(message) + psay(message) + +/mob/verb/pme_old() + set name = "Pme Old" + set category = "IC.Subtle" + + var/message = tgui_input_text(usr, "Emote to people affected by complete absorbed or dominate predator/prey.\nType your message:", "Pme") + + if(message) + pme(message) + +/mob/living/verb/player_narrate_ch() + set name = "Narrate (Player) Old" + set category = "IC.Chat" + + var/message = tgui_input_text(usr, "Narrate an action or event! An alternative to emoting, for when your emote shouldn't start with your name!\nType your message:", "Narrate (Player)") + + if(message) + player_narrate(message) diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm index 72630b09b9..08014a8290 100644 --- a/code/modules/mob/say_vr.dm +++ b/code/modules/mob/say_vr.dm @@ -4,7 +4,6 @@ /mob/verb/me_verb_subtle(message as message) //This would normally go in say.dm set name = "Subtle" - set category = "IC.Subtle" set desc = "Emote to nearby people (and your pred/prey)" set hidden = 1 @@ -24,7 +23,6 @@ /mob/verb/me_verb_subtle_custom(message as message) // Literally same as above but with mode_selection set to true set name = "Subtle (Custom)" - set category = "IC.Subtle" set desc = "Emote to nearby people, with ability to choose which specific portion of people you wish to target." if(forced_psay) @@ -197,7 +195,7 @@ continue if(src.client && M && !(get_z(src) == get_z(M))) message = span_multizsay("[message]") - if(isobserver(M) && (!M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle) || \ + if(isobserver(M) && (!(M.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle) || (isbelly(M.loc) && src == M.loc:owner)) || \ !client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) && !M.client?.holder)) spawn(0) M.show_message(undisplayed_message, 2) @@ -254,7 +252,6 @@ ///// PSAY ///// /mob/verb/psay(message as text) - set category = "IC.Subtle" set name = "Psay" set desc = "Talk to people affected by complete absorbed or dominate predator/prey." @@ -352,7 +349,6 @@ ///// PME ///// /mob/verb/pme(message as message) - set category = "IC.Subtle" set name = "Pme" set desc = "Emote to people affected by complete absorbed or dominate predator/prey." @@ -448,7 +444,6 @@ M.me_verb(message) /mob/living/verb/player_narrate(message as message) - set category = "IC.Chat" set name = "Narrate (Player)" set desc = "Narrate an action or event! An alternative to emoting, for when your emote shouldn't start with your name!" diff --git a/code/modules/reagents/machinery/grinder.dm b/code/modules/reagents/machinery/grinder.dm index a20920fc7e..80a85443b8 100644 --- a/code/modules/reagents/machinery/grinder.dm +++ b/code/modules/reagents/machinery/grinder.dm @@ -209,14 +209,7 @@ var/global/list/ore_reagents = list( //have a number of reageents divisible by R if(length(holdingitems)) options["grind"] = radial_grind - var/choice - if(length(options) < 1) - return - if(length(options) == 1) - for(var/key in options) - choice = key - else - choice = show_radial_menu(user, src, options, require_near = !issilicon(user)) + var/choice = show_radial_menu(user, src, options, require_near = !issilicon(user), autopick_single_option = FALSE) // post choice verification if(inuse || (isAI(user) && stat & NOPOWER) || user.incapacitated()) diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm index ae486f1e3f..b568f1b319 100644 --- a/code/modules/vore/eating/living_vr.dm +++ b/code/modules/vore/eating/living_vr.dm @@ -609,6 +609,12 @@ forceMove(get_turf(src)) log_and_message_admins("[key_name(src)] used the OOC escape button to get out of a microwave.") + else if(istype(loc, /obj/structure/gargoyle) && loc:was_rayed) + var/obj/structure/gargoyle/G = loc + G.can_revert = TRUE + qdel(G) + log_and_message_admins("[key_name(src)] used the OOC escape button to revert back from being petrified.") + //You are in food and for some reason can't resist out else if(istype(loc, /obj/item/reagent_containers/food)) var/obj/item/reagent_containers/food/F = src.loc diff --git a/code/modules/xenoarcheaology/finds/finds.dm b/code/modules/xenoarcheaology/finds/finds.dm index 2dd956bc27..8f30b0cb15 100644 --- a/code/modules/xenoarcheaology/finds/finds.dm +++ b/code/modules/xenoarcheaology/finds/finds.dm @@ -26,9 +26,32 @@ /obj/item/strangerock/New(loc, var/inside_item_type = 0) pixel_x = rand(0,16)-8 pixel_y = rand(0,8)-8 + var/d100 = rand(1,100) if(inside_item_type) - new /obj/item/archaeological_find(src, new_item_type = inside_item_type) + switch(d100) + if(51 to 100) //standard spawn logic 50% of the time + new /obj/item/archaeological_find(src, new_item_type = inside_item_type) + if(21 to 50) // 30% chance + new /obj/item/research_sample/common(src) + if(6 to 20) // 15% chance + new /obj/item/research_sample/uncommon(src) + if(1 to 5) // 5% chance + new /obj/item/research_sample/rare(src) + else //if something went wrong, somehow, generate the usual find + new /obj/item/archaeological_find(src, new_item_type = inside_item_type) + else //if this strange rock isn't set to generate a find for whatever reason, create a sample 75% of the time (this shouldn't happen unless the rock is mapped in or adminspawned) + switch(d100) + if(76 to 100) + return + if(21 to 75) + new /obj/item/research_sample/common(src) + if(6 to 20) + new /obj/item/research_sample/uncommon(src) + if(1 to 5) + new /obj/item/research_sample/rare(src) + else //if we somehow glitched + return //do nothing /obj/item/strangerock/attackby(var/obj/item/I, var/mob/user) if(istype(I, /obj/item/pickaxe/brush)) diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 4df86b72ff..97643ad7a8 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/mob/radial.dmi b/icons/mob/radial.dmi index bf14a19734..d38f1e63be 100644 Binary files a/icons/mob/radial.dmi and b/icons/mob/radial.dmi differ diff --git a/icons/obj/machines/petrification.dmi b/icons/obj/machines/petrification.dmi new file mode 100644 index 0000000000..932a461108 Binary files /dev/null and b/icons/obj/machines/petrification.dmi differ diff --git a/maps/submaps/space_rocks/space_rocks_stuff.dm b/maps/submaps/space_rocks/space_rocks_stuff.dm index ff0d03b854..dfa64b90ce 100644 --- a/maps/submaps/space_rocks/space_rocks_stuff.dm +++ b/maps/submaps/space_rocks/space_rocks_stuff.dm @@ -19,11 +19,13 @@ prob_fall = 40 //guard = 20 mobs_to_pick_from = list( - /mob/living/simple_mob/animal/space/bats = 10, - /mob/living/simple_mob/vore/alienanimals/space_jellyfish = 15, - /mob/living/simple_mob/vore/alienanimals/startreader = 15, - /mob/living/simple_mob/vore/alienanimals/space_ghost = 6, + /mob/living/simple_mob/vore/alienanimals/space_jellyfish = 1, + /mob/living/simple_mob/vore/alienanimals/startreader = 3, + /mob/living/simple_mob/vore/alienanimals/space_ghost = 2, /mob/living/simple_mob/vore/oregrub = 1, + /mob/living/simple_mob/animal/space/ray = 10, + /mob/living/simple_mob/animal/space/bats = 10, + /mob/living/simple_mob/animal/space/gnat = 15, /mob/living/simple_mob/animal/space/carp = 3, /mob/living/simple_mob/animal/space/carp/large = 1, /mob/living/simple_mob/animal/space/carp/large/huge = 1 diff --git a/tgui/packages/tgui/interfaces/CookingAppliance.tsx b/tgui/packages/tgui/interfaces/CookingAppliance.tsx index 7b951cd5db..2c721da681 100644 --- a/tgui/packages/tgui/interfaces/CookingAppliance.tsx +++ b/tgui/packages/tgui/interfaces/CookingAppliance.tsx @@ -12,6 +12,10 @@ import { import { Window } from '../layouts'; type Data = { + on: BooleanLike; + safety: BooleanLike; + selected_option: string | null; + show_selected_option: BooleanLike; temperature: number; optimalTemp: number; temperatureEnough: BooleanLike; @@ -29,6 +33,10 @@ export const CookingAppliance = (props) => { const { act, data } = useBackend(); const { + on, + safety, + selected_option, + show_selected_option, temperature, optimalTemp, temperatureEnough, @@ -40,8 +48,41 @@ export const CookingAppliance = (props) => { return ( -
+
act('toggle_power')} + > + {on ? 'On' : 'Off'} + + } + > + + + + {!!show_selected_option && ( + + + + )} {
+ {
{general.skills || 'No data found.'} + {
{general.comments && general.comments.length === 0 ? ( diff --git a/tgui/packages/tgui/interfaces/MedicalRecords/MedicalRecordsOptions.tsx b/tgui/packages/tgui/interfaces/MedicalRecords/MedicalRecordsOptions.tsx index 757e79b7cd..a711cdf931 100644 --- a/tgui/packages/tgui/interfaces/MedicalRecords/MedicalRecordsOptions.tsx +++ b/tgui/packages/tgui/interfaces/MedicalRecords/MedicalRecordsOptions.tsx @@ -36,6 +36,14 @@ export const MedicalRecordsView = (props) => {
+ { {field.value} - - - - } - > - - {items.map((item) => ( - - {item.amt} {item.extra} - - ))} - -
- )) || ( -
- {config.title} is empty. -
- )} - + + {inner} ); }; + +const MicrowaveContents = (props) => { + const { act, data } = useBackend(); + + const { items, reagents, recipe, recipe_name } = data; + + return ( +
+ + + + } + > + + + + {items.map((item) => ( + + + + x{item.amt} + + + + + ))} + {reagents.map((r) => ( + + + + {r.amt} + + {/* To be clear: This is fucking cursed + We're directly loading the rectangular glass and + manually colorizing a div that's set to be the right shape */} + + + + + ))} + + + + + + + + + {recipe ? ( + + ) : ( + + )} + + + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/PetrificationInterface.tsx b/tgui/packages/tgui/interfaces/PetrificationInterface.tsx new file mode 100644 index 0000000000..7d0bdea52a --- /dev/null +++ b/tgui/packages/tgui/interfaces/PetrificationInterface.tsx @@ -0,0 +1,148 @@ +import { BooleanLike } from 'common/react'; +import { useBackend } from 'tgui/backend'; +import { Button, LabeledList, Section } from 'tgui/components'; +import { Window } from 'tgui/layouts'; + +type Data = { + material: string; + identifier: string; + adjective: string; + tint: string; + t: BooleanLike; + target: string; + able_to_unpetrify: BooleanLike; + discard_clothes: BooleanLike; + can_remote: BooleanLike; +}; + +export const PetrificationInterface = (props) => { + const { act, data } = useBackend(); + + const { + material, + identifier, + adjective, + tint, + t, + able_to_unpetrify, + discard_clothes, + target, + can_remote, + } = data; + + return ( + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/SecurityRecords/SecurityRecordsOptions.tsx b/tgui/packages/tgui/interfaces/SecurityRecords/SecurityRecordsOptions.tsx index 276ad42d1a..03e2530eb2 100644 --- a/tgui/packages/tgui/interfaces/SecurityRecords/SecurityRecordsOptions.tsx +++ b/tgui/packages/tgui/interfaces/SecurityRecords/SecurityRecordsOptions.tsx @@ -35,6 +35,14 @@ export const SecurityRecordsView = (props) => {
+ { {field.value} -