diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 525ee27eb9e..7f335afed04 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1235,6 +1235,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_ELEVATING_OBJECT "elevating_object" /// From [/datum/element/elevation_core] for purpose of checking if the turf has the trait from an instance of the element #define TRAIT_ELEVATED_TURF "elevated_turf" +/// From [/datum/component/defaceable] marks that something has been... marked +#define TRAIT_DEFACED "defaced" /// This item is currently under the control of telekinesis #define TRAIT_TELEKINESIS_CONTROLLED "telekinesis_controlled" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index d60de14068b..8fff1e9688e 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -16,6 +16,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CLIMBABLE" = TRAIT_CLIMBABLE, "TRAIT_COMMISSIONED" = TRAIT_COMMISSIONED, "TRAIT_CURRENTLY_CLEANING" = TRAIT_CURRENTLY_CLEANING, + "TRAIT_DEFACED" = TRAIT_DEFACED, "TRAIT_DRIED" = TRAIT_DRIED, "TRAIT_DRYABLE" = TRAIT_DRYABLE, "TRAIT_ELEVATING_OBJECT" = TRAIT_ELEVATING_OBJECT, diff --git a/code/datums/components/defaceable.dm b/code/datums/components/defaceable.dm new file mode 100644 index 00000000000..645df67a533 --- /dev/null +++ b/code/datums/components/defaceable.dm @@ -0,0 +1,103 @@ +/// Lets you graffiti on an object +/datum/component/defaceable + /// Icon file from which to draw our overlay + var/icon + /// Icon states to draw from our file, optionally use as a list key for TRUE to skip recolouring that icon state + var/list/icon_states + /// String description of what you have drawn + var/drawing_of + /// What colour have we been drawn with? + var/ink_colour + /// Optional callback called when we are drawn on, we pass in the ink colour + var/datum/callback/on_defaced + +/datum/component/defaceable/Initialize(icon, list/icon_states, drawing_of, datum/callback/on_defaced) + if (!isatom(parent)) + return COMPONENT_INCOMPATIBLE + + src.icon = icon + src.icon_states = icon_states + src.drawing_of = drawing_of + src.on_defaced = on_defaced + +/datum/component/defaceable/RegisterWithParent() + RegisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(on_drawn)) + RegisterSignal(parent, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_hovered)) + var/atom/atom_parent = parent + atom_parent.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1 + +/datum/component/defaceable/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_ATOM_ITEM_INTERACTION, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, COMSIG_ATOM_EXAMINE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_ATOM_UPDATE_OVERLAYS)) + var/atom/atom_parent = parent + atom_parent.update_appearance(UPDATE_OVERLAYS) + +/datum/component/defaceable/Destroy(force = FALSE) + on_defaced = null + return ..() + +/// Inform people that they can mess us up +/datum/component/defaceable/proc/on_hovered(atom/source, list/context, obj/item/held_item, mob/user) + if (HAS_TRAIT(source, TRAIT_DEFACED)) + if (is_type_in_list(held_item, list(/obj/item/reagent_containers/spray, /obj/item/soap, /obj/item/rag))) + context[SCREENTIP_CONTEXT_LMB] = "Clean" + else + if (istype(held_item, /obj/item/pen) || istype(held_item, /obj/item/toy/crayon)) + context[SCREENTIP_CONTEXT_LMB] = "Draw [drawing_of ? drawing_of : "on"]" + return CONTEXTUAL_SCREENTIP_SET + +/// See if someone can draw on us +/datum/component/defaceable/proc/on_drawn(atom/source, mob/living/user, obj/item/tool) + SIGNAL_HANDLER + if (user.combat_mode) + return + if(istype(tool, /obj/item/toy/crayon)) + var/obj/item/toy/crayon/crayon = tool + ink_colour = crayon.paint_color + else if(istype(tool, /obj/item/pen)) + var/obj/item/pen/pen = tool + ink_colour = pen.colour + + if (!ink_colour) + return + + ADD_TRAIT(parent, TRAIT_DEFACED, ref(src)) + + on_defaced?.Invoke(ink_colour) + + RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_cleaned)) + + if (drawing_of) + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined)) + + if (icon && icon_states) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays)) + source.update_appearance(UPDATE_OVERLAYS) + + UnregisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION) + + return ITEM_INTERACT_SUCCESS + +/// Render our beautiful drawing +/datum/component/defaceable/proc/on_update_overlays(atom/source, list/overlays) + SIGNAL_HANDLER + for (var/state in icon_states) + var/mutable_appearance/appearance = mutable_appearance(icon, state) + if (!icon_states[state]) + appearance.color = ink_colour + overlays += appearance + +/// Wash it off +/datum/component/defaceable/proc/on_cleaned(atom/source, clean_types) + SIGNAL_HANDLER + if (!(clean_types & (CLEAN_WASH|CLEAN_SCRUB))) + return + + REMOVE_TRAIT(parent, TRAIT_DEFACED, ref(src)) + RegisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(on_drawn)) + UnregisterSignal(parent, list(COMSIG_ATOM_EXAMINE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_ATOM_UPDATE_OVERLAYS)) + source.update_appearance(UPDATE_OVERLAYS) + +/// See it there +/datum/component/defaceable/proc/on_examined(atom/source, mob/user, list/examine_list) + SIGNAL_HANDLER + examine_list += span_notice("Someone has crudely drawn [drawing_of] on [source.p_them()].") diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm index 40fa7e51dac..15d983b8745 100644 --- a/code/modules/mob/living/basic/bots/_bots.dm +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -108,6 +108,8 @@ GLOBAL_LIST_INIT(command_strings, list( var/list/original_allies /// If true we will offer this COOLDOWN_DECLARE(offer_ghosts_cooldown) + /// List of overlays to add to the bot if someone has drawn on it, index to a boolean of whether it should ignore paint colour + var/list/facepaint_overlays /mob/living/basic/bot/Initialize(mapload) . = ..() @@ -149,6 +151,13 @@ GLOBAL_LIST_INIT(command_strings, list( if(mapload && is_station_level(z) && (bot_mode_flags & BOT_MODE_CAN_BE_SAPIENT) && (bot_mode_flags & BOT_MODE_ROUNDSTART_POSSESSION)) enable_possession(mapload = mapload) + if (length(facepaint_overlays)) + AddComponent(/datum/component/defaceable, \ + icon = 'icons/mob/silicon/aibot_faces.dmi', \ + icon_states = facepaint_overlays, \ + drawing_of = "a face", \ + ) + pa_system = (isnull(announcement_type)) ? new(src, automated_announcements = generate_speak_list()) : new announcement_type(src, automated_announcements = generate_speak_list()) pa_system.Grant(src) ai_controller.set_blackboard_key(BB_ANNOUNCE_ABILITY, pa_system) diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm index 64f0b2cfd6a..4d26f285838 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm @@ -19,6 +19,7 @@ possessed_message = "You are a cleanbot! Clean the station to the best of your ability!" ai_controller = /datum/ai_controller/basic_controller/bot/cleanbot path_image_color = "#993299" + facepaint_overlays = list("cleanbot" = FALSE, "cleanbot_highlight" = TRUE) ///the bucket used to build us. var/obj/item/reagent_containers/cup/bucket/build_bucket ///Flags indicating what kind of cleanables we should scan for to set as our target to clean. diff --git a/code/modules/mob/living/basic/bots/medbot/medbot.dm b/code/modules/mob/living/basic/bots/medbot/medbot.dm index 1d2b157ce6c..3e6a22b0c6d 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot.dm @@ -28,6 +28,9 @@ announcement_type = /datum/action/cooldown/bot_announcement/medbot path_image_color = "#d9d9f4" + ///Because of our animation we need to handle drawn-on overlays ourself + var/painted_face_colour + ///anouncements when we find a target to heal var/static/list/wait_announcements = list( MEDIBOT_VOICED_HOLD_ON = 'sound/mobs/non-humanoids/medbot/coming.ogg', @@ -140,6 +143,11 @@ post_tipped_callback = CALLBACK(src, PROC_REF(after_tip_over)), \ post_untipped_callback = CALLBACK(src, PROC_REF(after_righted))) + AddComponent(/datum/component/defaceable, \ + drawing_of = "eyes", \ + on_defaced = CALLBACK(src, PROC_REF(on_defaced)), \ + ) + var/static/list/hat_offsets = list(4,-9) var/static/list/remove_hat = list(SIGNAL_ADDTRAIT(TRAIT_MOB_TIPPED)) var/static/list/prevent_checks = list(TRAIT_MOB_TIPPED) @@ -193,13 +201,24 @@ if(!(medical_mode_flags & MEDBOT_STATIONARY_MODE)) . += mutable_appearance(icon, "[base_icon_state]_overlay_wheels") - if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) - . += mutable_appearance(icon, "[base_icon_state]_overlay_incapacitated") - . += emissive_appearance(icon, "[base_icon_state]_overlay_incapacitated", src, alpha = src.alpha) - else if(bot_mode_flags & BOT_MODE_ON) - var/mode_suffix = mode == BOT_HEALING ? "active" : "idle" - . += mutable_appearance(icon, "[base_icon_state]_overlay_on_[mode_suffix]") - . += emissive_appearance(icon, "[base_icon_state]_overlay_on_[mode_suffix]", src, alpha = src.alpha) + var/mode_suffix = mode == BOT_HEALING ? "active" : "idle" + if (HAS_TRAIT(src, TRAIT_DEFACED)) + var/mutable_appearance/face = mutable_appearance('icons/mob/silicon/aibot_faces.dmi', "medbot_[mode_suffix]") + face.color = painted_face_colour + . += face + . += mutable_appearance('icons/mob/silicon/aibot_faces.dmi', "medbot_highlight_[mode_suffix]") + else + if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) + . += mutable_appearance(icon, "[base_icon_state]_overlay_incapacitated") + . += emissive_appearance(icon, "[base_icon_state]_overlay_incapacitated", src, alpha = src.alpha) + else if(bot_mode_flags & BOT_MODE_ON) + . += mutable_appearance(icon, "[base_icon_state]_overlay_on_[mode_suffix]") + . += emissive_appearance(icon, "[base_icon_state]_overlay_on_[mode_suffix]", src, alpha = src.alpha) + +/// Set our drawn-on ink colour +/mob/living/basic/bot/medbot/proc/on_defaced(ink_colour) + SIGNAL_HANDLER + painted_face_colour = ink_colour //this is sin /mob/living/basic/bot/medbot/generate_speak_list() diff --git a/code/modules/mob/living/basic/minebots/minebot.dm b/code/modules/mob/living/basic/minebots/minebot.dm index 212edad7da7..0409095f8a8 100644 --- a/code/modules/mob/living/basic/minebots/minebot.dm +++ b/code/modules/mob/living/basic/minebots/minebot.dm @@ -69,6 +69,11 @@ AddElement(/datum/element/death_drops, /obj/effect/decal/cleanable/blood/gibs/robot_debris/old) add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE, TRAIT_SNOWSTORM_IMMUNE, TRAIT_MINING_AOE_IMMUNE), INNATE_TRAIT) AddElement(/datum/element/footstep, FOOTSTEP_OBJ_ROBOT, 1, -6, sound_vary = TRUE) + AddComponent(/datum/component/defaceable, \ + icon = 'icons/mob/silicon/aibot_faces.dmi', \ + icon_states = list("minebot" = FALSE, "minebot_highlight" = TRUE), \ + drawing_of = "a face", \ + ) var/static/list/innate_actions = list( /datum/action/cooldown/mob_cooldown/missile_launcher = BB_MINEBOT_MISSILE_ABILITY, diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm index 5a615407f9a..945d0c2eba9 100644 --- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -27,6 +27,12 @@ . = ..() set_weapon() //giving it the right projectile and firing sound. + AddComponent(/datum/component/defaceable, \ + icon = 'icons/mob/silicon/aibot_faces.dmi', \ + icon_states = list("ed209" = FALSE, "ed209_highlight" = TRUE), \ + drawing_of = "a face", \ + ) + /mob/living/simple_animal/bot/secbot/ed209/bot_reset() ..() set_weapon() diff --git a/icons/mob/silicon/aibot_faces.dmi b/icons/mob/silicon/aibot_faces.dmi new file mode 100644 index 00000000000..5bb05a1d60d Binary files /dev/null and b/icons/mob/silicon/aibot_faces.dmi differ diff --git a/tgstation.dme b/tgstation.dme index b75023926d7..093b2c25dd0 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1163,6 +1163,7 @@ #include "code\datums\components\deadchat_control.dm" #include "code\datums\components\death_linked.dm" #include "code\datums\components\debris_bleeder.dm" +#include "code\datums\components\defaceable.dm" #include "code\datums\components\dejavu.dm" #include "code\datums\components\deployable.dm" #include "code\datums\components\direct_explosive_trap.dm"