diff --git a/code/__DEFINES/dcs/signals/signals_mod.dm b/code/__DEFINES/dcs/signals/signals_mod.dm index 8cabf7537ab..09b29c93784 100644 --- a/code/__DEFINES/dcs/signals/signals_mod.dm +++ b/code/__DEFINES/dcs/signals/signals_mod.dm @@ -41,3 +41,5 @@ #define COMSIG_MOD_WEARER_UNSET "mod_wearer_unset" /// Sent by the tether module when it triggers its snapping function #define COMSIG_MOD_TETHER_SNAP "mod_tether_snap" +/// Called when a MOD module generats its worn overlay +#define COMSIG_MODULE_GENERATE_WORN_OVERLAY "mod_module_generate_worn_overlay" diff --git a/code/datums/components/hat_stabilizer.dm b/code/datums/components/hat_stabilizer.dm new file mode 100644 index 00000000000..e7951e49956 --- /dev/null +++ b/code/datums/components/hat_stabilizer.dm @@ -0,0 +1,140 @@ +/// Allows players to place hats on the atom this is attached to +/datum/component/hat_stabilizer + /// Currently "stored" hat. No armor or function will be inherited, only the icon and cover flags. + var/obj/item/clothing/head/attached_hat + /// Original cover flags for the helmet, before a hat is placed + var/former_flags + var/former_visor_flags + /// If true, add_overlay will use worn overlay instead of item appearance + var/use_worn_icon = TRUE + /// Pixel_y offset for the hat + var/pixel_y_offset + +/datum/component/hat_stabilizer/Initialize(add_overlay = FALSE, use_worn_icon = TRUE, pixel_y_offset = 0) + if(!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + + src.use_worn_icon = use_worn_icon + src.pixel_y_offset = pixel_y_offset + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby)) + RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_qdel)) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND_SECONDARY, PROC_REF(on_secondary_attack_hand)) + RegisterSignals(parent, list(COMSIG_MODULE_GENERATE_WORN_OVERLAY, COMSIG_ITEM_GET_WORN_OVERLAYS), PROC_REF(get_worn_overlays)) + if (add_overlay) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays)) + +/datum/component/hat_stabilizer/UnregisterFromParent() + if (attached_hat) + remove_hat() + UnregisterSignal(parent, list(COMSIG_ATOM_EXAMINE, COMSIG_ATOM_ATTACKBY, + COMSIG_ATOM_ATTACK_HAND_SECONDARY, COMSIG_MODULE_GENERATE_WORN_OVERLAY, + COMSIG_ITEM_GET_WORN_OVERLAYS, COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_QDELETING)) + +/datum/component/hat_stabilizer/proc/on_examine(datum/source, mob/user, list/base_examine) + SIGNAL_HANDLER + if(attached_hat) + base_examine += span_notice("There's \a [attached_hat] placed on [parent]. Right-click to remove it.") + else + base_examine += span_notice("There's nothing placed on [parent]. Yet.") + +/datum/component/hat_stabilizer/proc/get_worn_overlays(atom/movable/source, list/overlays, mutable_appearance/standing, isinhands, icon_file) + SIGNAL_HANDLER + if (isinhands) + return + if(attached_hat) + var/mutable_appearance/worn_overlay = attached_hat.build_worn_icon(default_layer = ABOVE_BODY_FRONT_HEAD_LAYER-0.1, default_icon_file = 'icons/mob/clothing/head/default.dmi') + worn_overlay.pixel_y = pixel_y_offset + overlays += worn_overlay + +/datum/component/hat_stabilizer/proc/on_update_overlays(atom/movable/source, list/overlays) + SIGNAL_HANDLER + var/mutable_appearance/worn_overlay = use_worn_icon ? attached_hat.build_worn_icon(default_layer = ABOVE_OBJ_LAYER, default_icon_file = 'icons/mob/clothing/head/default.dmi') : mutable_appearance(attached_hat, layer = ABOVE_OBJ_LAYER) + worn_overlay.pixel_y = pixel_y_offset + overlays += worn_overlay + +/datum/component/hat_stabilizer/proc/on_qdel(atom/movable/source) + SIGNAL_HANDLER + + if (attached_hat) + QDEL_NULL(attached_hat) + +/datum/component/hat_stabilizer/proc/on_attackby(datum/source, obj/item/hitting_item, mob/user) + SIGNAL_HANDLER + + var/atom/movable/movable_parent = parent + if(!istype(hitting_item, /obj/item/clothing/head)) + return + + if(attached_hat) + movable_parent.balloon_alert(user, "hat already attached!") + return + + var/obj/item/clothing/hat = hitting_item + if(hat.clothing_flags & STACKABLE_HELMET_EXEMPT) + movable_parent.balloon_alert(user, "invalid hat!") + return + + if(!user.transferItemToLoc(hat, parent, force = FALSE, silent = TRUE)) + return + attached_hat = hat + RegisterSignal(hat, COMSIG_MOVABLE_MOVED, PROC_REF(remove_hat)) + movable_parent.balloon_alert(user, "hat attached, right-click to remove") + + if (!istype(parent, /obj/item/clothing)) + movable_parent.update_appearance() + return + + var/obj/item/clothing/apparel = parent + apparel.attach_clothing_traits(attached_hat.clothing_traits) + former_flags = apparel.flags_cover + former_visor_flags = apparel.visor_flags_cover + apparel.flags_cover |= attached_hat.flags_cover + apparel.visor_flags_cover |= attached_hat.visor_flags_cover + apparel.update_appearance() + if (ismob(apparel.loc)) + var/mob/wearer = apparel.loc + wearer.update_clothing(wearer.get_slot_by_item(apparel)) + +/datum/component/hat_stabilizer/proc/on_secondary_attack_hand(datum/source, mob/user) + SIGNAL_HANDLER + . = SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + if(!attached_hat) + return + var/atom/movable/movable_parent = parent + if (remove_hat(user)) + movable_parent.balloon_alert(user, "hat removed") + else + movable_parent.balloon_alert_to_viewers("the hat falls to the floor!") + +/datum/component/hat_stabilizer/proc/remove_hat(mob/user) + SIGNAL_HANDLER + + if(QDELETED(attached_hat)) + return + + var/atom/movable/movable_parent = parent + UnregisterSignal(attached_hat, COMSIG_MOVABLE_MOVED) + + if (attached_hat.loc == parent) + attached_hat.forceMove(movable_parent.drop_location()) + + if(!isnull(user)) + . = user.put_in_active_hand(attached_hat) + else + movable_parent.balloon_alert_to_viewers("the hat falls to the floor!") + + if (!istype(parent, /obj/item/clothing)) + attached_hat = null + movable_parent.update_appearance() + return + + var/obj/item/clothing/apparel = parent + apparel.detach_clothing_traits(attached_hat) + apparel.flags_cover = former_flags + apparel.visor_flags_cover = former_visor_flags + apparel.update_appearance() + attached_hat = null + if (ismob(apparel.loc)) + var/mob/wearer = apparel.loc + wearer.update_clothing(wearer.get_slot_by_item(apparel)) diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm index d1578c20342..616ff463d8d 100644 --- a/code/modules/clothing/spacesuits/plasmamen.dm +++ b/code/modules/clothing/spacesuits/plasmamen.dm @@ -89,18 +89,17 @@ light_color = "#ffcc99" light_on = FALSE fishing_modifier = 0 - var/helmet_on = FALSE - var/smile = FALSE - var/smile_color = COLOR_RED - var/visor_icon = "envisor" - var/smile_state = "envirohelm_smile" - var/obj/item/clothing/head/attached_hat actions_types = list(/datum/action/item_action/toggle_helmet_light, /datum/action/item_action/toggle_welding_screen) visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT flags_cover = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF visor_flags_inv = HIDEEYES|HIDEFACE slowdown = 0 + var/helmet_on = FALSE + var/smile = FALSE + var/smile_color = COLOR_RED + var/visor_icon = "envisor" + var/smile_state = "envirohelm_smile" /datum/armor/space_plasmaman bio = 100 @@ -110,30 +109,17 @@ /obj/item/clothing/head/helmet/space/plasmaman/Initialize(mapload) . = ..() visor_toggling() + AddComponent(/datum/component/hat_stabilizer) update_appearance() register_context() /obj/item/clothing/head/helmet/space/plasmaman/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) context[SCREENTIP_CONTEXT_ALT_LMB] = "Toggle Welding Screen" - - if(attached_hat) - context[SCREENTIP_CONTEXT_RMB] = "Remove hat" - - if(istype(held_item, /obj/item/clothing/head)) - context[SCREENTIP_CONTEXT_LMB] = "Attach hat" - if(istype(held_item, /obj/item/toy/crayon)) context[SCREENTIP_CONTEXT_LMB] = "Vandalize" return CONTEXTUAL_SCREENTIP_SET -/obj/item/clothing/head/helmet/space/plasmaman/examine() - . = ..() - if(attached_hat) - . += span_notice("There's [attached_hat.name] placed on the helmet.") - else - . += span_notice("There's nothing placed on the helmet.") - /obj/item/clothing/head/helmet/space/plasmaman/click_alt(mob/user) if(user.can_perform_action(src)) adjust_visor(user) @@ -171,37 +157,21 @@ . += smiley /obj/item/clothing/head/helmet/space/plasmaman/item_interaction(mob/living/user, obj/item/tool, list/modifiers) - if(istype(tool, /obj/item/toy/crayon)) - if(smile) - to_chat(user, span_warning("Seems like someone already drew something on [src]'s visor!")) - return ITEM_INTERACT_BLOCKING - - var/obj/item/toy/crayon/crayon = tool - to_chat(user, span_notice("You start drawing a smiley face on [src]'s visor...")) - if(!do_after(user, 2.5 SECONDS, target = src)) - return ITEM_INTERACT_BLOCKING - - smile = TRUE - smile_color = crayon.paint_color - to_chat(user, "You draw a smiley on [src] visor.") - update_appearance() - return ITEM_INTERACT_SUCCESS - - if(!istype(tool, /obj/item/clothing/head)) + if(!istype(tool, /obj/item/toy/crayon)) return NONE - var/obj/item/clothing/hitting_clothing = tool - if(hitting_clothing.clothing_flags & STACKABLE_HELMET_EXEMPT) - to_chat(user, span_notice("You cannot place [hitting_clothing.name] on [src]!")) + if(smile) + to_chat(user, span_warning("Seems like someone already drew something on [src]'s visor!")) return ITEM_INTERACT_BLOCKING - if(attached_hat) - to_chat(user, span_notice("There's already something placed on [src]!")) + var/obj/item/toy/crayon/crayon = tool + to_chat(user, span_notice("You start drawing a smiley face on [src]'s visor...")) + if(!do_after(user, 2.5 SECONDS, target = src)) return ITEM_INTERACT_BLOCKING - attached_hat = hitting_clothing - to_chat(user, span_notice("You placed [hitting_clothing.name] on [src]!")) - hitting_clothing.forceMove(src) + smile = TRUE + smile_color = crayon.paint_color + to_chat(user, "You draw a smiley on [src] visor.") update_appearance() return ITEM_INTERACT_SUCCESS @@ -209,11 +179,9 @@ /obj/item/clothing/head/helmet/space/plasmaman/worn_overlays(mutable_appearance/standing, isinhands) . = ..() if(!isinhands && smile) - var/mutable_appearance/M = mutable_appearance('icons/mob/clothing/head/plasmaman_head.dmi', smile_state) - M.color = smile_color - . += M - if(!isinhands && attached_hat) - . += attached_hat.build_worn_icon(default_layer = HEAD_LAYER, default_icon_file = 'icons/mob/clothing/head/default.dmi') + var/mutable_appearance/smiley = mutable_appearance('icons/mob/clothing/head/plasmaman_head.dmi', smile_state) + smiley.color = smile_color + . += smiley if(!isinhands && !up) . += mutable_appearance('icons/mob/clothing/head/plasmaman_head.dmi', visor_icon) else @@ -249,16 +217,6 @@ update_appearance() return TRUE -/obj/item/clothing/head/helmet/space/plasmaman/attack_hand_secondary(mob/user) - ..() - . = SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN - if(!attached_hat) - return - user.put_in_active_hand(attached_hat) - to_chat(user, span_notice("You removed [attached_hat.name] from helmet!")) - attached_hat = null - update_appearance() - /obj/item/clothing/head/helmet/space/plasmaman/security name = "security plasma envirosuit helmet" desc = "A plasmaman containment helmet designed for security officers, protecting them from burning alive, alongside other undesirables." diff --git a/code/modules/mod/modules/_module.dm b/code/modules/mod/modules/_module.dm index 551145b5e6d..6065a486d0c 100644 --- a/code/modules/mod/modules/_module.dm +++ b/code/modules/mod/modules/_module.dm @@ -340,6 +340,7 @@ if(!use_mod_colors) module_icon.appearance_flags |= RESET_COLOR . += module_icon + SEND_SIGNAL(src, COMSIG_MODULE_GENERATE_WORN_OVERLAY, ., standing) /// Updates the signal used by active modules to be activated /obj/item/mod/module/proc/update_signal(value) diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm index 485b66941d4..4ebd0c177ac 100644 --- a/code/modules/mod/modules/modules_general.dm +++ b/code/modules/mod/modules/modules_general.dm @@ -704,77 +704,15 @@ var/obj/item/clothing/helmet = mod.get_part_from_slot(ITEM_SLOT_HEAD) if(!istype(helmet)) return - RegisterSignal(helmet, COMSIG_ATOM_EXAMINE, PROC_REF(add_examine)) - RegisterSignal(helmet, COMSIG_ATOM_ATTACKBY, PROC_REF(place_hat)) - RegisterSignal(helmet, COMSIG_ATOM_ATTACK_HAND_SECONDARY, PROC_REF(remove_hat)) + helmet.AddComponent(/datum/component/hat_stabilizer) /obj/item/mod/module/hat_stabilizer/on_suit_deactivation(deleting = FALSE) if(deleting) return - if(attached_hat) //knock off the helmet if its on their head. Or, technically, auto-rightclick it for them; that way it saves us code, AND gives them the bubble - remove_hat(src, mod.wearer) var/obj/item/clothing/helmet = mod.get_part_from_slot(ITEM_SLOT_HEAD) if(!istype(helmet)) return - UnregisterSignal(helmet, COMSIG_ATOM_EXAMINE) - UnregisterSignal(helmet, COMSIG_ATOM_ATTACKBY) - UnregisterSignal(helmet, COMSIG_ATOM_ATTACK_HAND_SECONDARY) - -/obj/item/mod/module/hat_stabilizer/proc/add_examine(datum/source, mob/user, list/base_examine) - SIGNAL_HANDLER - if(attached_hat) - base_examine += span_notice("There's \a [attached_hat] placed on the helmet. Right-click to remove it.") - else - base_examine += span_notice("There's nothing placed on the helmet. Yet.") - -/obj/item/mod/module/hat_stabilizer/proc/place_hat(datum/source, obj/item/hitting_item, mob/user) - SIGNAL_HANDLER - if(!istype(hitting_item, /obj/item/clothing/head)) - return - var/obj/item/clothing/hat = hitting_item - if(!mod.active) - balloon_alert(user, "suit must be active!") - return - if(attached_hat) - balloon_alert(user, "hat already attached!") - return - if(hat.clothing_flags & STACKABLE_HELMET_EXEMPT) - balloon_alert(user, "invalid hat!") - return - if(mod.wearer.transferItemToLoc(hitting_item, src, force = FALSE, silent = TRUE)) - attached_hat = hat - var/obj/item/clothing/helmet = mod.get_part_from_slot(ITEM_SLOT_HEAD) - if(istype(helmet)) - helmet.attach_clothing_traits(attached_hat.clothing_traits) - former_flags = helmet.flags_cover - former_visor_flags = helmet.visor_flags_cover - helmet.flags_cover |= attached_hat.flags_cover - helmet.visor_flags_cover |= attached_hat.visor_flags_cover - balloon_alert(user, "hat attached, right-click to remove") - mod.wearer.update_clothing(mod.slot_flags) - -/obj/item/mod/module/hat_stabilizer/generate_worn_overlay() - . = ..() - if(attached_hat) - . += attached_hat.build_worn_icon(default_layer = ABOVE_BODY_FRONT_HEAD_LAYER-0.1, default_icon_file = 'icons/mob/clothing/head/default.dmi') - -/obj/item/mod/module/hat_stabilizer/proc/remove_hat(datum/source, mob/user) - SIGNAL_HANDLER - . = SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN - if(!attached_hat) - return - attached_hat.forceMove(drop_location()) - if(user.put_in_active_hand(attached_hat)) - balloon_alert(user, "hat removed") - else - balloon_alert_to_viewers("the hat falls to the floor!") - var/obj/item/clothing/helmet = mod.get_part_from_slot(ITEM_SLOT_HEAD) - if(istype(helmet)) - helmet.detach_clothing_traits(attached_hat) - helmet.flags_cover = former_flags - helmet.visor_flags_cover = former_visor_flags - attached_hat = null - mod.wearer.update_clothing(mod.slot_flags) + qdel(helmet.GetComponent(/datum/component/hat_stabilizer)) /obj/item/mod/module/hat_stabilizer/syndicate name = "MOD elite hat stabilizer module" diff --git a/tgstation.dme b/tgstation.dme index e4d5d5a3f96..3b955640875 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1134,6 +1134,7 @@ #include "code\datums\components\growth_and_differentiation.dm" #include "code\datums\components\gunpoint.dm" #include "code\datums\components\happiness.dm" +#include "code\datums\components\hat_stabilizer.dm" #include "code\datums\components\hazard_area.dm" #include "code\datums\components\healing_touch.dm" #include "code\datums\components\health_scaling_effects.dm"