diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm index 172f2cdf4682..3660cc6ebf3a 100644 --- a/code/__DEFINES/logging.dm +++ b/code/__DEFINES/logging.dm @@ -24,6 +24,7 @@ #define INVESTIGATE_WIRES "wires" #define INVESTIGATE_NANITES "nanites" #define INVESTIGATE_ARTIFACT "artifact" +#define INVESTIGATE_SIGNBOARD "signboard" // monkestation addition // Logging types for log_message() #define LOG_ATTACK (1 << 0) diff --git a/code/__DEFINES/~monkestation/_helpers.dm b/code/__DEFINES/~monkestation/_helpers.dm new file mode 100644 index 000000000000..d2234e5852ee --- /dev/null +++ b/code/__DEFINES/~monkestation/_helpers.dm @@ -0,0 +1,8 @@ +/// Basically, this is UNTIL(Condition), +/// but it also checks to see if Src has been qdeleted, and returns if so. +#define UNTIL_WHILE_EXISTS(Src, Condition) \ + while(!(Condition)) { \ + if(QDELETED(Src)) return; \ + stoplag(); \ + } \ + if(QDELETED(Src)) return; diff --git a/code/__HELPERS/~monkestation-helpers/colors.dm b/code/__HELPERS/~monkestation-helpers/colors.dm new file mode 100644 index 000000000000..40c4e5e93526 --- /dev/null +++ b/code/__HELPERS/~monkestation-helpers/colors.dm @@ -0,0 +1,19 @@ +/// Given a color in the format of "#RRGGBB", will return if the color +/// is dark. Value is mixed with Saturation and Brightness from HSV. +/proc/is_color_dark_with_saturation(color, threshold = 25) + var/hsl = rgb2num(color, COLORSPACE_HSL) + return hsl[3] < threshold + +/// it checks if a color is dark, but without saturation value. +/// This uses Brightness only, without Saturation from HSV +/proc/is_color_dark_without_saturation(color, threshold = 25) + return get_color_brightness_from_hex(color) < threshold + +/// returns HSV brightness 0 to 100 by color hex +/proc/get_color_brightness_from_hex(A) + if(!A || length(A) != length_char(A)) + return 0 + var/R = hex2num(copytext(A, 2, 4)) + var/G = hex2num(copytext(A, 4, 6)) + var/B = hex2num(copytext(A, 6, 8)) + return round(max(R, G, B)/2.55, 1) diff --git a/code/modules/admin/admin_investigate.dm b/code/modules/admin/admin_investigate.dm index 3f4c041f387a..406add5b76b9 100644 --- a/code/modules/admin/admin_investigate.dm +++ b/code/modules/admin/admin_investigate.dm @@ -35,6 +35,7 @@ INVESTIGATE_RESEARCH, INVESTIGATE_WIRES, INVESTIGATE_NANITES, + INVESTIGATE_SIGNBOARD, // monkestation addition ) var/list/logs_present = list("notes, memos, watchlist") diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index 8647b026adc4..e3446e24618a 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -254,6 +254,7 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests()) /obj/machinery/ocean_elevator, /atom/movable/outdoor_effect, /turf/closed/mineral/random/regrowth, + /obj/effect/abstract/signboard_holder, // monkestation addition: shouldn't exist outside of signboards ) //Say it with me now, type template ignore += typesof(/obj/effect/mapping_helpers) diff --git a/monkestation/code/modules/blueshift/structures/wooden_rack.dm b/monkestation/code/modules/blueshift/structures/wooden_rack.dm index e279f5520f75..0e624b64309e 100644 --- a/monkestation/code/modules/blueshift/structures/wooden_rack.dm +++ b/monkestation/code/modules/blueshift/structures/wooden_rack.dm @@ -141,6 +141,7 @@ GLOBAL_LIST_INIT(monke_wood_recipes, list( new/datum/stack_recipe("sturdy wooden fence", /obj/structure/railing/wooden_fencing, 5, time = 2 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), new/datum/stack_recipe("sturdy wooden fence gate", /obj/structure/railing/wooden_fencing/gate, 5, time = 2 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), new/datum/stack_recipe("large wooden gate", /obj/structure/mineral_door/wood/large_gate, 10, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), + new/datum/stack_recipe("signboard", /obj/structure/signboard, 5, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), )) diff --git a/monkestation/code/modules/signboards/_signboard.dm b/monkestation/code/modules/signboards/_signboard.dm new file mode 100644 index 000000000000..e37b88eee22e --- /dev/null +++ b/monkestation/code/modules/signboards/_signboard.dm @@ -0,0 +1,309 @@ +#define SIGNBOARD_WIDTH (world.icon_size * 3.5) +#define SIGNBOARD_HEIGHT (world.icon_size * 2.5) + +/obj/structure/signboard + name = "sign" + desc = "A foldable sign." + icon = 'monkestation/icons/obj/structures/signboards.dmi' + icon_state = "sign" + base_icon_state = "sign" + density = TRUE + anchored = TRUE + interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_REQUIRES_DEXTERITY + /// The current text written on the sign. + var/sign_text + /// The maximum length of text that can be input onto the sign. + var/max_length = MAX_PLAQUE_LEN + /// If true, the text cannot be changed by players. + var/locked = FALSE + /// If text should be shown while unanchored. + var/show_while_unanchored = FALSE + /// If TRUE, the sign can be edited without a pen. + var/edit_by_hand = FALSE + /// Holder for signboard maptext + var/obj/effect/abstract/signboard_holder/text_holder + /// Lazy assoc list of clients to images + VAR_PROTECTED/list/client_maptext_images + /// If a mass client add/removal is currently being done. + VAR_PRIVATE/doing_update = FALSE + +/obj/structure/signboard/Initialize(mapload) + . = ..() + text_holder = new(src) + vis_contents += text_holder + RegisterSignal(SSdcs, COMSIG_GLOB_MOB_LOGGED_IN, PROC_REF(on_mob_login)) + if(sign_text) + set_text(sign_text, force = TRUE) + investigate_log("had its text set on load to \"[sign_text]\"", INVESTIGATE_SIGNBOARD) + update_appearance() + register_context() + +/obj/structure/signboard/Destroy() + UnregisterSignal(SSdcs, COMSIG_GLOB_MOB_LOGGED_IN) + remove_from_all_clients_unsafe() + vis_contents -= text_holder + QDEL_NULL(text_holder) + return ..() + +/obj/structure/signboard/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(!is_locked(user)) + if(held_item?.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = anchored ? "Unsecure" : "Secure" + return CONTEXTUAL_SCREENTIP_SET + if((edit_by_hand || istype(held_item, /obj/item/pen)) && (anchored || show_while_unanchored)) + context[SCREENTIP_CONTEXT_LMB] = "Set Displayed Text" + if(sign_text) + context[SCREENTIP_CONTEXT_ALT_RMB] = "Clear Sign" + return CONTEXTUAL_SCREENTIP_SET + +/obj/structure/signboard/examine(mob/user) + . = ..() + if(!edit_by_hand) + . += span_info("You need a pen to write on the sign!") + if(anchored) + . += span_info("It is secured to the floor, you could use a wrench to unsecure and move it.") + else + . += span_info("It is unsecured, you could use a wrench to secure it in place.") + if(sign_text) + . += span_boldnotice("\nIt currently displays the following:") + . += span_info(html_encode(sign_text)) + else + . += span_info("\nIt is blank!") + +/obj/structure/signboard/update_icon_state() + . = ..() + icon_state = "[base_icon_state][sign_text ? "" : "_blank"]" + +/obj/structure/signboard/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, sign_text)) + if(!set_text(var_value, force = TRUE)) + return FALSE + datum_flags |= DF_VAR_EDITED + return TRUE + return ..() + +/obj/structure/signboard/attackby(obj/item/item, mob/user, params) + if(!istype(item, /obj/item/pen)) + return ..() + try_set_text(user) + +/obj/structure/signboard/attack_hand(mob/living/user, list/modifiers) + . = ..() + if(.) + return + if(!edit_by_hand && !user.is_holding_item_of_type(/obj/item/pen)) + balloon_alert(user, "need a pen!") + return TRUE + if(try_set_text(user)) + return TRUE + +/obj/structure/signboard/proc/try_set_text(mob/living/user) + . = FALSE + if(!anchored && !show_while_unanchored) + return FALSE + if(check_locked(user)) + return FALSE + var/new_text = tgui_input_text( + user, + message = "What would you like to set this sign's text to?", + title = full_capitalize(name), + default = sign_text, + max_length = max_length, + multiline = TRUE, + encode = FALSE + ) + if(QDELETED(src) || !new_text || check_locked(user)) + return FALSE + var/list/filter_result = CAN_BYPASS_FILTER(user) ? null : is_ic_filtered(new_text) + if(filter_result) + REPORT_CHAT_FILTER_TO_USER(user, filter_result) + return FALSE + var/list/soft_filter_result = CAN_BYPASS_FILTER(user) ? null : is_soft_ic_filtered(new_text) + if(soft_filter_result) + if(tgui_alert(user, "Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to say it?", "Soft Blocked Word", list("Yes", "No")) != "Yes") + return FALSE + message_admins("[ADMIN_LOOKUPFLW(user)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" when writing to the sign at [ADMIN_VERBOSEJMP(src)], they may be using a disallowed term. Sign text: \"[html_encode(new_text)]\"") + log_admin_private("[key_name(user)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" when writing to the sign at [loc_name(src)], they may be using a disallowed term. Sign text: \"[new_text]\"") + if(set_text(new_text)) + balloon_alert(user, "set text") + investigate_log("([key_name(user)]) set text to \"[sign_text || "(none)"]\"", INVESTIGATE_SIGNBOARD) + return TRUE + +/obj/structure/signboard/alt_click_secondary(mob/user) + . = ..() + if(!sign_text || !can_interact(user) || !user.can_perform_action(src, NEED_DEXTERITY)) + return + if(!edit_by_hand && !user.is_holding_item_of_type(/obj/item/pen)) + balloon_alert(user, "need a pen!") + return + if(check_locked(user)) + return + if(set_text(null)) + balloon_alert(user, "cleared text") + investigate_log("([key_name(user)]) cleared the text", INVESTIGATE_SIGNBOARD) + +/obj/structure/signboard/wrench_act(mob/living/user, obj/item/tool) + . = ..() + if(!anchored || !check_locked(user)) + default_unfasten_wrench(user, tool) + return TOOL_ACT_TOOLTYPE_SUCCESS + +/obj/structure/signboard/set_anchored(anchorvalue) + . = ..() + INVOKE_ASYNC(src, PROC_REF(add_to_all_clients)) + +/obj/structure/signboard/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + if(!isturf(old_loc) || !isturf(loc)) + INVOKE_ASYNC(src, PROC_REF(add_to_all_clients)) + +/obj/structure/signboard/proc/is_locked(mob/user) + . = locked + if(isAdminGhostAI(user)) + return FALSE + +/obj/structure/signboard/proc/check_locked(mob/user, silent = FALSE) + . = is_locked(user) + if(. && !silent) + balloon_alert(user, "locked!") + +/obj/structure/signboard/proc/should_display_text() + if(QDELETED(src) || !isturf(loc) || !sign_text) + return FALSE + if(!anchored && !show_while_unanchored) + return FALSE + return TRUE + +/obj/structure/signboard/proc/on_mob_login(datum/source, mob/user) + SIGNAL_HANDLER + var/client/client = user?.client + ASYNC + UNTIL_WHILE_EXISTS(src, !doing_update) + doing_update = TRUE + add_client(client) + doing_update = FALSE + +/obj/structure/signboard/proc/add_client(client/user) + if(QDELETED(user) || !should_display_text()) + return + if(LAZYACCESS(client_maptext_images, user)) + remove_client(user) + var/image/client_image = create_image_for_client(user) + if(!client_image || QDELETED(user)) + return + LAZYSET(client_maptext_images, user, client_image) + LAZYADD(update_on_z, client_image) + user.images |= client_image + RegisterSignal(user, COMSIG_QDELETING, PROC_REF(remove_client)) + +/obj/structure/signboard/proc/remove_client(client/user) + SIGNAL_HANDLER + if(isnull(user)) + return + UnregisterSignal(user, COMSIG_QDELETING) + var/image/client_image = LAZYACCESS(client_maptext_images, user) + if(!client_image) + return + user.images -= client_image + LAZYREMOVE(client_maptext_images, user) + LAZYREMOVE(update_on_z, client_image) + +/obj/structure/signboard/proc/add_to_all_clients() + UNTIL_WHILE_EXISTS(src, !doing_update) + doing_update = TRUE + add_to_all_clients_unsafe() + doing_update = FALSE + +/obj/structure/signboard/proc/add_to_all_clients_unsafe() + PRIVATE_PROC(TRUE) + if(QDELETED(src)) + return + remove_from_all_clients_unsafe() + if(!should_display_text()) + return + var/list/shown_first = list() + var/client/usr_client = usr?.client + add_client(usr_client) + for(var/mob/mob in viewers(world.view, src)) + if(QDELING(mob) || QDELETED(mob.client) || mob == usr) + continue + add_client(mob.client) + shown_first[mob.client] = TRUE + for(var/client/client as anything in GLOB.clients) + if(QDELETED(client) || shown_first[client] || client == usr_client) + continue + add_client(client) + +/obj/structure/signboard/proc/remove_from_all_clients() + UNTIL_WHILE_EXISTS(src, !doing_update) + doing_update = TRUE + remove_from_all_clients_unsafe() + doing_update = FALSE + +/obj/structure/signboard/proc/remove_from_all_clients_unsafe() + PRIVATE_PROC(TRUE) + for(var/client/client as anything in client_maptext_images) + remove_client(client) + LAZYNULL(client_maptext_images) + +/obj/structure/signboard/proc/create_image_for_client(client/user) as /image + RETURN_TYPE(/image) + if(QDELETED(user) || !sign_text) + return + var/bwidth = src.bound_width || world.icon_size + var/bheight = src.bound_height || world.icon_size + var/text_html = MAPTEXT_GRAND9K("[html_encode(sign_text)]") + var/mheight + WXH_TO_HEIGHT(user.MeasureText(text_html, null, SIGNBOARD_WIDTH), mheight) + var/image/maptext_holder = image(loc = text_holder) + SET_PLANE_EXPLICIT(maptext_holder, GAME_PLANE_UPPER_FOV_HIDDEN, src) + maptext_holder.layer = ABOVE_ALL_MOB_LAYER + maptext_holder.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART + maptext_holder.alpha = 192 + maptext_holder.maptext = text_html + maptext_holder.maptext_x = (SIGNBOARD_WIDTH - bwidth) * -0.5 + maptext_holder.maptext_y = bheight + maptext_holder.maptext_width = SIGNBOARD_WIDTH + maptext_holder.maptext_height = mheight + return maptext_holder + +/obj/structure/signboard/proc/set_text(new_text, force = FALSE) + . = FALSE + if(QDELETED(src) || (locked && !force)) + return + if(!istext(new_text) && !isnull(new_text)) + CRASH("Attempted to set invalid signtext: [new_text]") + . = TRUE + new_text = trimtext(copytext_char(new_text, 1, max_length)) + if(length(new_text)) + sign_text = new_text + INVOKE_ASYNC(src, PROC_REF(add_to_all_clients)) + else + sign_text = null + INVOKE_ASYNC(src, PROC_REF(remove_from_all_clients)) + update_appearance() + +/obj/effect/abstract/signboard_holder + name = "" + icon = null + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + vis_flags = VIS_INHERIT_PLANE + +/obj/effect/abstract/signboard_holder/Initialize(mapload) + . = ..() + if(!istype(loc, /obj/structure/signboard) || QDELING(loc)) + return INITIALIZE_HINT_QDEL + +/obj/effect/abstract/signboard_holder/Destroy(force) + if(!force && istype(loc, /obj/structure/signboard) && !QDELING(loc)) + stack_trace("Tried to delete a signboard holder that's inside of a non-deleted signboard!") + return QDEL_HINT_LETMELIVE + return ..() + +/obj/effect/abstract/signboard_holder/forceMove(atom/destination, no_tp = FALSE, harderforce = FALSE) + if(harderforce) + return ..() + +#undef SIGNBOARD_HEIGHT +#undef SIGNBOARD_WIDTH diff --git a/monkestation/code/modules/signboards/crafting.dm b/monkestation/code/modules/signboards/crafting.dm new file mode 100644 index 000000000000..80989c8b30f0 --- /dev/null +++ b/monkestation/code/modules/signboards/crafting.dm @@ -0,0 +1,23 @@ +/datum/crafting_recipe/signboard + name = "Signboard" + desc = "A sign, you can write anything on it!" + tool_behaviors = list(TOOL_WRENCH, TOOL_SCREWDRIVER) + result = /obj/structure/signboard + reqs = list( + /obj/item/stack/sheet/mineral/wood = 5, + ) + time = 5 SECONDS + category = CAT_FURNITURE + +/datum/crafting_recipe/holosign + name = "Holographic Signboard" + desc = "A sign, you can write anything on it! Now available in many colors!" + tool_behaviors = list(TOOL_WRENCH, TOOL_SCREWDRIVER, TOOL_MULTITOOL) + result = /obj/structure/signboard/holosign + reqs = list( + /obj/item/stack/sheet/iron = 5, + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/micro_laser = 1, + ) + time = 10 SECONDS + category = CAT_FURNITURE diff --git a/monkestation/code/modules/signboards/holosign.dm b/monkestation/code/modules/signboards/holosign.dm new file mode 100644 index 000000000000..a7133b5de9af --- /dev/null +++ b/monkestation/code/modules/signboards/holosign.dm @@ -0,0 +1,163 @@ +/obj/structure/signboard/holosign + name = "holographic sign" + desc = "A holographic signboard, projecting text above it." + icon_state = "holographic_sign" + base_icon_state = "holographic_sign" + edit_by_hand = TRUE + show_while_unanchored = TRUE + light_system = OVERLAY_LIGHT + light_outer_range = MINIMUM_USEFUL_LIGHT_RANGE + light_power = 0.3 + light_color = COLOR_CARP_TEAL + light_on = FALSE + /// If set, only IDs with this name can (un)lock the sign. + var/registered_owner + /// The current color of the sign. + /// The sign will be greyscale if this is set. + var/current_color + +/obj/structure/signboard/holosign/Initialize(mapload) + . = ..() + if(current_color) + INVOKE_ASYNC(src, PROC_REF(set_color), current_color) + +/obj/structure/signboard/holosign/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + var/locked = is_locked(user) + if(istype(held_item, /obj/item/card/emag)) + context[SCREENTIP_CONTEXT_LMB] = "Short Out Locking Mechanisms" + . = CONTEXTUAL_SCREENTIP_SET + else if(!locked && istype(held_item?.GetID(), /obj/item/card/id)) + context[SCREENTIP_CONTEXT_LMB] = registered_owner ? "Remove ID Lock" : "Lock To ID" + . = CONTEXTUAL_SCREENTIP_SET + if(!locked) + context[SCREENTIP_CONTEXT_RMB] = "Set Sign Color" + . = CONTEXTUAL_SCREENTIP_SET + +/obj/structure/signboard/holosign/update_icon_state() + base_icon_state = current_color ? "[initial(base_icon_state)]_greyscale" : initial(base_icon_state) + . = ..() + if(obj_flags & EMAGGED) + icon_state += "_emag" + +/obj/structure/signboard/holosign/update_desc(updates) + . = ..() + desc = initial(desc) + if(obj_flags & EMAGGED) + desc += span_warning("
Its locking mechanisms appear to be shorted out!") + else if(registered_owner) + desc += span_info("
It is locked to the ID of [span_name(registered_owner)].") + +/obj/structure/signboard/holosign/update_overlays() + . = ..() + if(sign_text) + . += emissive_appearance(icon, "holographic_sign_e", src) + +/obj/structure/signboard/holosign/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, color) || var_name == NAMEOF(src, current_color)) + INVOKE_ASYNC(src, PROC_REF(set_color), var_value) + datum_flags |= DF_VAR_EDITED + return TRUE + return ..() + +/obj/structure/signboard/holosign/attackby(obj/item/item, mob/user, params) + var/obj/item/card/id/id = item?.GetID() + if(!istype(id) || !can_interact(user) || !user.can_perform_action(src, NEED_DEXTERITY)) + return ..() + var/trimmed_id_name = trimtext(id.registered_name) + if(!trimmed_id_name) + balloon_alert(user, "no name on id!") + return + if(obj_flags & EMAGGED) + balloon_alert(user, "lock shorted out!") + return + if(registered_owner) + if(!check_locked(user)) + registered_owner = null + balloon_alert(user, "id lock removed") + investigate_log("([key_name(user)]) removed id lock", INVESTIGATE_SIGNBOARD) + else + registered_owner = trimmed_id_name + balloon_alert(user, "locked to id") + investigate_log("([key_name(user)]) added id lock for \"[registered_owner]\"", INVESTIGATE_SIGNBOARD) + update_appearance() + +/obj/structure/signboard/holosign/is_locked(mob/living/user) + . = ..() + if(.) + return + if(registered_owner && isliving(user)) + var/obj/item/card/id/id = user.get_idcard() + if(!istype(id) || QDELING(id)) + return TRUE + return !cmptext(trimtext(id.registered_name), registered_owner) + +/obj/structure/signboard/holosign/create_image_for_client(client/user) + RETURN_TYPE(/image) + var/image/client_image = ..() + if(current_color) + client_image?.color = current_color + return client_image + +/obj/structure/signboard/holosign/set_text(new_text, force) + . = ..() + set_light_on(!!sign_text) + +/obj/structure/signboard/holosign/attack_hand_secondary(mob/user, list/modifiers) + . = ..() + if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return + if(try_set_color(user)) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/structure/signboard/holosign/proc/try_set_color(mob/user) + . = TRUE + if(!can_interact(user) || !user.can_perform_action(src, NEED_DEXTERITY)) + return FALSE + if(check_locked(user)) + return + var/new_color = sanitize_color(tgui_color_picker(user, "Set Sign Color", full_capitalize(name), current_color)) + if(new_color && is_color_dark_with_saturation(new_color, 25)) + balloon_alert(user, "color too dark!") + return + if(check_locked(user)) + return + INVOKE_ASYNC(src, PROC_REF(set_color), new_color) + if(new_color) + balloon_alert(user, "set color to [new_color]") + investigate_log("([key_name(user)]) set the color to [new_color || "(none)"]", INVESTIGATE_SIGNBOARD) + else + balloon_alert(user, "unset color") + investigate_log("([key_name(user)]) cleared the color", INVESTIGATE_SIGNBOARD) + +/obj/structure/signboard/holosign/emag_act(mob/user, obj/item/card/emag/emag_card) + if(obj_flags & EMAGGED) + return FALSE + playsound(src, SFX_SPARKS, vol = 100, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + do_sparks(3, cardinal_only = FALSE, source = src) + balloon_alert(user, "lock broken") + investigate_log("was emagged by [key_name(user)] (previous owner: [registered_owner || "(none)"])", INVESTIGATE_SIGNBOARD) + registered_owner = null + obj_flags |= EMAGGED + update_appearance() + +/obj/structure/signboard/holosign/proc/sanitize_color(color) + . = sanitize_hexcolor(color) + if(!. || . == "#000000") + return null + +/obj/structure/signboard/holosign/proc/set_color(new_color) + new_color = sanitize_color(new_color) + if(!new_color) + current_color = null + remove_atom_colour(FIXED_COLOUR_PRIORITY) + else + current_color = new_color + add_atom_colour(new_color, FIXED_COLOUR_PRIORITY) + set_light_color(current_color || initial(light_color)) + for(var/client/client as anything in client_maptext_images) + if(QDELETED(client)) + continue + var/image/client_image = client_maptext_images[client] + client_image.color = current_color + update_appearance() diff --git a/monkestation/icons/obj/structures/signboards.dmi b/monkestation/icons/obj/structures/signboards.dmi new file mode 100644 index 000000000000..041afb87c52f Binary files /dev/null and b/monkestation/icons/obj/structures/signboards.dmi differ diff --git a/tgstation.dme b/tgstation.dme index bb345c3db7b4..cbd38f26457f 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -391,6 +391,7 @@ #include "code\__DEFINES\traits\sources.dm" #include "code\__DEFINES\traits\monkestation\declarations.dm" #include "code\__DEFINES\traits\monkestation\sources.dm" +#include "code\__DEFINES\~monkestation\_helpers.dm" #include "code\__DEFINES\~monkestation\_patreon.dm" #include "code\__DEFINES\~monkestation\abberant_organs.dm" #include "code\__DEFINES\~monkestation\access.dm" @@ -590,6 +591,7 @@ #include "code\__HELPERS\~monkestation-helpers\atoms.dm" #include "code\__HELPERS\~monkestation-helpers\clients.dm" #include "code\__HELPERS\~monkestation-helpers\cmp.dm" +#include "code\__HELPERS\~monkestation-helpers\colors.dm" #include "code\__HELPERS\~monkestation-helpers\icon_smoothing.dm" #include "code\__HELPERS\~monkestation-helpers\icons.dm" #include "code\__HELPERS\~monkestation-helpers\mapping.dm" @@ -7639,6 +7641,9 @@ #include "monkestation\code\modules\security\code\weapons\lawbringer.dm" #include "monkestation\code\modules\security\code\weapons\paco.dm" #include "monkestation\code\modules\shelves\shelf.dm" +#include "monkestation\code\modules\signboards\_signboard.dm" +#include "monkestation\code\modules\signboards\crafting.dm" +#include "monkestation\code\modules\signboards\holosign.dm" #include "monkestation\code\modules\skyrat_snipes\languages.dm" #include "monkestation\code\modules\skyrat_snipes\reagents\drink_reagents.dm" #include "monkestation\code\modules\skyrat_snipes\vending_machines\vending_food.dm"