[MANUAL FIXED MIRROR] 22129 and 22154 (#22379)

* Makes hoods into a component (#75977)

## About The Pull Request

Refactors the behaviour of "one clothing item deploying another clothing
item" from `/obj/item/clothing/suit/hooded` and makes it into a
component.
This allows you to make hooded items which are not part of that
typepath. It also means you could make (for instance) a hat which can
deploy a pair of sunglasses into the eye slot or a jumpsuit with
deployable clown shoes or something.

I need to pass in an assload of callbacks because we have a bunch of
special hoodies that want to do things when you raise and lower the
hood, but for a normal item you would not need these.

## Why It's Good For The Game

Frees people from the tyrrany of typepaths, mostly.
Plausibly you could use it to do something fun we don't currently do.

## Changelog

Not player facing, hopefully. As long as I did this all right.

* Makes hoods into a component

* [no gbp] Fixes item action buttons

* Update items.dm

* Fix mirror 22129

* Some last minute updates -- comment and small optimization

---------

Co-authored-by: Jacquerel <hnevard@gmail.com>
Co-authored-by: SkyratBot <59378654+SkyratBot@users.noreply.github.com>
Co-authored-by: lessthanthree <83487515+lessthnthree@users.noreply.github.com>
This commit is contained in:
Bloop
2023-07-11 19:57:19 -04:00
committed by GitHub
co-authored by Jacquerel SkyratBot lessthanthree
parent 765bcfb355
commit df074b9966
16 changed files with 340 additions and 180 deletions
@@ -126,12 +126,19 @@
#define COMSIG_ITEM_DROPPED "item_drop"
///from base of obj/item/pickup(): (/mob/taker)
#define COMSIG_ITEM_PICKUP "item_pickup"
///from base of obj/item/on_outfit_equip(): (mob/equipper, visuals_only, slot)
#define COMSIG_ITEM_EQUIPPED_AS_OUTFIT "item_equip_as_outfit"
/// Sebt from obj/item/ui_action_click(): (mob/user, datum/action)
#define COMSIG_ITEM_UI_ACTION_CLICK "item_action_click"
/// Return to prevent the default behavior (attack_selfing) from ocurring.
#define COMPONENT_ACTION_HANDLED (1<<0)
/// Sent from obj/item/item_action_slot_check(): (mob/user, datum/action, slot)
#define COMSIG_ITEM_UI_ACTION_SLOT_CHECKED "item_action_slot_checked"
/// Return to prevent the default behavior (attack_selfing) from ocurring.
#define COMPONENT_ITEM_ACTION_SLOT_INVALID (1<<0)
///from base of mob/living/carbon/attacked_by(): (mob/living/carbon/target, mob/living/user, hit_zone)
#define COMSIG_ITEM_ATTACK_ZONE "item_attack_zone"
///from base of obj/item/hit_reaction(): (owner, hitby, attack_text, final_block_chance, damage, attack_type, damage_type)
@@ -0,0 +1,213 @@
/**
* Component which allows clothing to deploy a different kind of clothing onto you.
* The simplest example is hooded suits deploying hoods onto your head.
*/
/datum/component/toggle_attached_clothing
/// Instance of the item we're creating
var/obj/item/deployable
/// Action used to toggle deployment
var/datum/action/item_action/toggle_action
/// Typepath of what we're creating
var/deployable_type
/// Which slot this item equips into
var/equipped_slot
/// Name of toggle action
var/action_name = ""
/// If true, we delete our deployable on toggle rather than putting it in nullspace
var/destroy_on_removal
/// Current state of our deployable equipment
var/currently_deployed = FALSE
/// What should be added to the end of the parent icon state when equipment is deployed? Set to "" for no change
var/parent_icon_state_suffix = ""
/// Icon state for overlay to display over the parent item while deployable item is not deployed
var/down_overlay_state_suffix = ""
/// Overlay to display over the parent item while deployable item is not deployed
var/mutable_appearance/undeployed_overlay
/// Optional callback triggered before deploying, return TRUE to continue or FALSE to cancel
var/datum/callback/pre_creation_check
/// Optional callback triggered when we create our deployable equipment
var/datum/callback/on_created
/// Optional callback triggered when we have deployed our equipment
var/datum/callback/on_deployed
/// Optional callback triggered before we hide our equipment, before as we may delete it afterwards
var/datum/callback/on_removed
/datum/component/toggle_attached_clothing/Initialize(
deployable_type,
equipped_slot,
action_name = "Toggle",
destroy_on_removal = FALSE,
parent_icon_state_suffix = "",
down_overlay_state_suffix = "",
datum/callback/pre_creation_check,
datum/callback/on_created,
datum/callback/on_deployed,
datum/callback/on_removed,
)
. = ..()
if (!isitem(parent))
return COMPONENT_INCOMPATIBLE
if (!deployable_type || !equipped_slot)
return COMPONENT_INCOMPATIBLE // Not strictly true but INITIALIZE_HINT_QDEL doesn't work from components
src.deployable_type = deployable_type
src.equipped_slot = equipped_slot
src.destroy_on_removal = destroy_on_removal
src.parent_icon_state_suffix = parent_icon_state_suffix
src.down_overlay_state_suffix = down_overlay_state_suffix
src.pre_creation_check = pre_creation_check
src.on_created = on_created
src.on_deployed = on_deployed
src.on_removed = on_removed
var/obj/item/clothing_parent = parent
toggle_action = new(parent)
toggle_action.name = action_name
clothing_parent.add_item_action(toggle_action)
RegisterSignal(parent, COMSIG_ITEM_UI_ACTION_CLICK, PROC_REF(on_toggle_pressed))
RegisterSignal(parent, COMSIG_ITEM_UI_ACTION_SLOT_CHECKED, PROC_REF(on_action_slot_checked))
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_parent_equipped))
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED_AS_OUTFIT, PROC_REF(on_parent_equipped_outfit))
if (down_overlay_state_suffix)
var/overlay_state = "[initial(clothing_parent.icon_state)][down_overlay_state_suffix]"
undeployed_overlay = mutable_appearance(initial(clothing_parent.worn_icon), overlay_state, -SUIT_LAYER)
RegisterSignal(parent, COMSIG_ITEM_GET_WORN_OVERLAYS, PROC_REF(on_checked_overlays))
clothing_parent.update_slot_icon()
if (!destroy_on_removal)
create_deployable()
/datum/component/toggle_attached_clothing/Destroy(force, silent)
unequip_deployable()
QDEL_NULL(deployable)
QDEL_NULL(toggle_action)
QDEL_NULL(on_created)
QDEL_NULL(on_deployed)
QDEL_NULL(on_removed)
return ..()
/// Toggle deployable when the UI button is clicked
/datum/component/toggle_attached_clothing/proc/on_toggle_pressed(obj/item/source, mob/user, datum/action)
SIGNAL_HANDLER
if (action != toggle_action)
return
toggle_deployable()
return COMPONENT_ACTION_HANDLED
/// Called when action attempts to check what slot the item is worn in
/datum/component/toggle_attached_clothing/proc/on_action_slot_checked(obj/item/clothing/source, mob/user, datum/action, slot)
SIGNAL_HANDLER
if (action != toggle_action)
return
if (!(source.slot_flags & slot))
return COMPONENT_ITEM_ACTION_SLOT_INVALID
/// Apply an overlay while the item is not deployed
/datum/component/toggle_attached_clothing/proc/on_checked_overlays(obj/item/source, list/overlays, mutable_appearance/standing, isinhands, icon_file)
SIGNAL_HANDLER
if (isinhands || currently_deployed)
return
overlays += undeployed_overlay
/// Deploys gear if it is hidden, hides it if it is deployed
/datum/component/toggle_attached_clothing/proc/toggle_deployable()
if (currently_deployed)
remove_deployable()
return
var/obj/item/parent_gear = parent
if (!ishuman(parent_gear.loc))
return
var/mob/living/carbon/human/wearer = parent_gear.loc
if (wearer.is_holding(parent_gear))
parent_gear.balloon_alert(wearer, "wear it first!")
return
if (wearer.get_item_by_slot(equipped_slot))
parent_gear.balloon_alert(wearer, "slot occupied!")
return
if (!deployable && !create_deployable())
return
if (!wearer.equip_to_slot_if_possible(deployable, slot = equipped_slot))
if(destroy_on_removal)
remove_deployable()
return
currently_deployed = TRUE
on_deployed?.Invoke(deployable)
if (parent_icon_state_suffix)
parent_gear.icon_state = "[initial(parent_gear.icon_state)][parent_icon_state_suffix]"
parent_gear.worn_icon_state = parent_gear.icon_state
parent_gear.update_slot_icon()
wearer.update_mob_action_buttons()
/// Undeploy gear if it moves slots somehow
/datum/component/toggle_attached_clothing/proc/on_parent_equipped(obj/item/clothing/source, mob/equipper, slot)
SIGNAL_HANDLER
if (slot & equipped_slot)
return
remove_deployable()
/// Display deployed if worn in an outfit
/datum/component/toggle_attached_clothing/proc/on_parent_equipped_outfit(obj/item/clothing/source, mob/equipper, visuals_only, slot)
SIGNAL_HANDLER
create_deployable()
toggle_deployable()
/// Create our gear, returns true if we actually made anything
/datum/component/toggle_attached_clothing/proc/create_deployable()
if (deployable)
return FALSE
if (pre_creation_check && !pre_creation_check.Invoke())
return FALSE
deployable = new deployable_type(parent)
if (!istype(deployable))
stack_trace("Tried to create non-clothing item from toggled clothing.")
RegisterSignal(deployable, COMSIG_ITEM_DROPPED, PROC_REF(on_deployed_dropped))
RegisterSignal(deployable, COMSIG_ITEM_EQUIPPED, PROC_REF(on_deployed_equipped))
RegisterSignal(deployable, COMSIG_QDELETING, PROC_REF(on_deployed_destroyed))
on_created?.Invoke(deployable)
return TRUE
/// Undeploy gear if you drop it
/datum/component/toggle_attached_clothing/proc/on_deployed_dropped()
SIGNAL_HANDLER
remove_deployable()
/// Undeploy gear if it moves slots somehow
/datum/component/toggle_attached_clothing/proc/on_deployed_equipped(obj/item/clothing/source, mob/equipper, slot)
SIGNAL_HANDLER
if (source.slot_flags & slot)
return
remove_deployable()
/// Undeploy gear if it is deleted
/datum/component/toggle_attached_clothing/proc/on_deployed_destroyed()
SIGNAL_HANDLER
remove_deployable()
deployable = null
/// Removes our deployed equipment from the wearer
/datum/component/toggle_attached_clothing/proc/remove_deployable()
unequip_deployable()
if (!currently_deployed)
return
currently_deployed = FALSE
on_removed?.Invoke(deployable)
var/obj/item/parent_gear = parent
if (destroy_on_removal)
QDEL_NULL(deployable)
else if (parent_icon_state_suffix)
parent_gear.icon_state = "[initial(parent_gear.icon_state)]"
parent_gear.worn_icon_state = parent_gear.icon_state
parent_gear.update_slot_icon()
parent_gear.update_item_action_buttons()
/// Removes an equipped deployable atom upon its retraction or destruction
/datum/component/toggle_attached_clothing/proc/unequip_deployable()
if (!deployable)
return
if (!ishuman(deployable.loc))
deployable.forceMove(parent)
return
var/mob/living/carbon/human/wearer = deployable.loc
wearer.transferItemToLoc(deployable, parent, force = TRUE, silent = TRUE)
+8 -7
View File
@@ -703,7 +703,7 @@
/// Gives one of our item actions to a mob, when equipped to a certain slot
/obj/item/proc/give_item_action(datum/action/action, mob/to_who, slot)
// Some items only give their actions buttons when in a specific slot.
if(!item_action_slot_check(slot, to_who))
if(!item_action_slot_check(slot, to_who, action) || SEND_SIGNAL(src, COMSIG_ITEM_UI_ACTION_SLOT_CHECKED, to_who, action, slot) & COMPONENT_ITEM_ACTION_SLOT_INVALID)
// There is a chance we still have our item action currently,
// and are moving it from a "valid slot" to an "invalid slot".
// So call Remove() here regardless, even if excessive.
@@ -713,7 +713,7 @@
action.Grant(to_who)
/// Sometimes we only want to grant the item's action if it's equipped in a specific slot.
/obj/item/proc/item_action_slot_check(slot, mob/user)
/obj/item/proc/item_action_slot_check(slot, mob/user, datum/action/action)
if(slot & (ITEM_SLOT_BACKPACK|ITEM_SLOT_LEGCUFFED)) //these aren't true slots, so avoid granting actions there
return FALSE
return TRUE
@@ -1253,9 +1253,9 @@
source_item?.reagents?.add_reagent(/datum/reagent/blood, 2)
else if(custom_materials?.len) //if we've got materials, lets see whats in it
/// How many mats have we found? You can only be affected by two material datums by default
// How many mats have we found? You can only be affected by two material datums by default
var/found_mats = 0
/// How much of each material is in it? Used to determine if the glass should break
// How much of each material is in it? Used to determine if the glass should break
var/total_material_amount = 0
for(var/mats in custom_materials)
@@ -1270,7 +1270,7 @@
//if there's glass in it and the glass is more than 60% of the item, then we can shatter it
if(custom_materials[GET_MATERIAL_REF(/datum/material/glass)] >= total_material_amount * 0.60)
if(prob(66)) //66% chance to break it
/// The glass shard that is spawned into the source item
// The glass shard that is spawned into the source item
var/obj/item/shard/broken_glass = new /obj/item/shard(loc)
broken_glass.name = "broken [name]"
broken_glass.desc = "This used to be \a [name], but it sure isn't anymore."
@@ -1287,7 +1287,7 @@
span_warning("Eugh! Did I just bite into something?"))
else if(w_class == WEIGHT_CLASS_TINY) //small items like soap or toys that don't have mat datums
/// victim's chest (for cavity implanting the item)
// victim's chest (for cavity implanting the item)
var/obj/item/bodypart/chest/victim_cavity = victim.get_bodypart(BODY_ZONE_CHEST)
if(victim_cavity.cavity_item)
victim.vomit(5, FALSE, FALSE, distance = 0)
@@ -1422,7 +1422,8 @@
/// Special stuff you want to do when an outfit equips this item.
/obj/item/proc/on_outfit_equip(mob/living/carbon/human/outfit_wearer, visuals_only, item_slot)
return
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED_AS_OUTFIT, outfit_wearer, visuals_only, item_slot)
/// Whether or not this item can be put into a storage item through attackby
/obj/item/proc/attackby_storage_insert(datum/storage, atom/storage_holder, mob/user)
+1
View File
@@ -584,6 +584,7 @@
uniform = /obj/item/clothing/under/color/black
suit = /obj/item/clothing/suit/hooded/cultrobes/alt
head = /obj/item/clothing/head/hooded/cult_hoodie/alt
shoes = /obj/item/clothing/shoes/cult/alt
r_hand = /obj/item/melee/blood_magic/stun
@@ -213,7 +213,16 @@ Striking a noncultist, however, will tear their flesh."}
heat_protection = CHEST|GROIN|LEGS|ARMS
max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT
hoodtype = /obj/item/clothing/head/hooded/cult_hoodie
/// Whether the hood is flipped up
var/hood_up = FALSE
/// Called when the hood is worn
/obj/item/clothing/suit/hooded/cultrobes/on_hood_up(obj/item/clothing/head/hooded/hood)
hood_up = TRUE
/// Called when the hood is hidden
/obj/item/clothing/suit/hooded/cultrobes/on_hood_down(obj/item/clothing/head/hooded/hood)
hood_up = FALSE
/datum/armor/hooded_cultrobes
melee = 40
@@ -767,4 +767,5 @@
name = "Heretic (Preview only)"
suit = /obj/item/clothing/suit/hooded/cultrobes/eldritch
head = /obj/item/clothing/head/hooded/cult_hoodie/eldritch
r_hand = /obj/item/melee/touch_attack/mansus_fist
@@ -109,26 +109,23 @@
// Let examiners know this works as a focus only if the hood is down
. += span_notice("Allows you to cast heretic spells while the hood is down.")
/obj/item/clothing/suit/hooded/cultrobes/void/RemoveHood()
// This is before the hood actually goes down
// We only make it visible if the hood is being moved from up to down
if(hood_up)
make_visible()
/obj/item/clothing/suit/hooded/cultrobes/void/on_hood_down(obj/item/clothing/head/hooded/hood)
make_visible()
return ..()
/obj/item/clothing/suit/hooded/cultrobes/void/MakeHood()
/obj/item/clothing/suit/hooded/cultrobes/void/can_create_hood()
if(!isliving(loc))
CRASH("[src] attempted to make a hood on a non-living thing: [loc]")
var/mob/living/wearer = loc
if(!IS_HERETIC_OR_MONSTER(wearer))
loc.balloon_alert(loc, "you can't get the hood up!")
return
if(IS_HERETIC_OR_MONSTER(wearer))
return TRUE
// When we make the hood, that means we're going invisible
loc.balloon_alert(loc, "can't get the hood up!")
return FALSE
/obj/item/clothing/suit/hooded/cultrobes/void/on_hood_created(obj/item/clothing/head/hooded/hood)
. = ..()
make_invisible()
return ..()
/// Makes our cloak "invisible". Not the wearer, the cloak itself.
/obj/item/clothing/suit/hooded/cultrobes/void/proc/make_invisible()
@@ -354,6 +354,7 @@
uniform = /obj/item/clothing/under/color/grey
suit = /obj/item/clothing/suit/hooded/ablative
head = /obj/item/clothing/head/hooded/ablative
gloves = /obj/item/clothing/gloves/color/yellow
mask = /obj/item/clothing/mask/gas
l_hand = /obj/item/melee/energy/sword
+4 -8
View File
@@ -46,22 +46,18 @@
if (prob(hit_reflect_chance))
return TRUE
/obj/item/clothing/suit/hooded/ablative/ToggleHood()
/obj/item/clothing/suit/hooded/ablative/on_hood_up(obj/item/clothing/head/hooded/hood)
. = ..()
if (!hood_up)
return
var/mob/living/carbon/user = loc
var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_SECURITY_ADVANCED]
ADD_TRAIT(user, TRAIT_SECURITY_HUD, HELMET_TRAIT)
hud.show_to(user)
balloon_alert(user, "you put on the hood, and enable the hud")
balloon_alert(user, "hud enabled")
/obj/item/clothing/suit/hooded/ablative/RemoveHood()
if (!hood_up)
return ..()
/obj/item/clothing/suit/hooded/ablative/on_hood_down(obj/item/clothing/head/hooded/hood)
var/mob/living/carbon/user = loc
var/datum/atom_hud/sec_hud = GLOB.huds[DATA_HUD_SECURITY_ADVANCED]
REMOVE_TRAIT(user, TRAIT_SECURITY_HUD, HELMET_TRAIT)
sec_hud.hide_from(user)
balloon_alert(user, "you take off the hood, and disable the hud")
balloon_alert(user, "hud disabled")
return ..()
+42 -105
View File
@@ -1,124 +1,61 @@
//Hoods for winter coats and chaplain hoodie etc
/// Subtype with support for hoods
/// You no longer actually need to extend this and can just add the component yourself without a lot of this boilerplate code
/obj/item/clothing/suit/hooded
actions_types = list(/datum/action/item_action/toggle_hood)
var/obj/item/clothing/head/hooded/hood
var/hoodtype = /obj/item/clothing/head/hooded/winterhood //so the chaplain hoodie or other hoodies can override this
///Alternative mode for hiding the hood, instead of storing the hood in the suit it qdels it, useful for when you deal with hooded suit with storage.
/// Alternative mode for hiding the hood, instead of storing the hood in the suit it qdels it, useful for when you deal with hooded suit with storage.
var/alternative_mode = FALSE
///Whether the hood is flipped up
var/hood_up = FALSE
/// What should be added to the end of the icon state when the hood is up? Set to "" for the suit sprite to not change at all
var/hood_up_affix = "_t"
/// Are we zipped? Mostly relevant for wintercoats, leaving this here to simplify logic and so someone else can extend it if they ever wish to.
var/zipped = FALSE
/// Icon state added as a worn overlay while the hood is down, leave as "" for no overlay
var/hood_down_overlay_suffix = ""
/// Reference to hood object, if it exists
var/obj/item/clothing/head/hooded/hood
/obj/item/clothing/suit/hooded/Initialize(mapload)
. = ..()
if(!alternative_mode)
MakeHood()
if (!hoodtype)
return
AddComponent(\
/datum/component/toggle_attached_clothing,\
deployable_type = hoodtype,\
equipped_slot = ITEM_SLOT_HEAD,\
action_name = "Toggle Hood",\
destroy_on_removal = alternative_mode,\
parent_icon_state_suffix = hood_up_affix,\
down_overlay_state_suffix = hood_down_overlay_suffix, \
pre_creation_check = CALLBACK(src, PROC_REF(can_create_hood)),\
on_created = CALLBACK(src, PROC_REF(on_hood_created)),\
on_deployed = CALLBACK(src, PROC_REF(on_hood_up)),\
on_removed = CALLBACK(src, PROC_REF(on_hood_down)),\
)
/obj/item/clothing/suit/hooded/Destroy()
. = ..()
QDEL_NULL(hood)
/obj/item/clothing/suit/hooded/proc/MakeHood()
if(!hood)
var/obj/item/clothing/head/hooded/W = new hoodtype(src)
W.suit = src
hood = W
/obj/item/clothing/suit/hooded/ui_action_click()
ToggleHood()
/obj/item/clothing/suit/hooded/item_action_slot_check(slot, mob/user)
if(slot & (ITEM_SLOT_OCLOTHING|ITEM_SLOT_NECK))
return TRUE
/obj/item/clothing/suit/hooded/equipped(mob/user, slot)
if(!(slot & (ITEM_SLOT_OCLOTHING|ITEM_SLOT_NECK)))
RemoveHood()
hood = null
return ..()
/obj/item/clothing/suit/hooded/on_outfit_equip(mob/living/carbon/human/outfit_wearer, visuals_only, item_slot)
if(visuals_only)
MakeHood()
ToggleHood()
/// Override to only create the hood conditionally
/obj/item/clothing/suit/hooded/proc/can_create_hood()
return TRUE
/obj/item/clothing/suit/hooded/proc/RemoveHood()
icon_state = "[initial(icon_state)]"
worn_icon_state = icon_state
zipped = FALSE
hood_up = FALSE
/// Called when the hood is instantiated
/obj/item/clothing/suit/hooded/proc/on_hood_created(obj/item/clothing/head/hooded/hood)
SHOULD_CALL_PARENT(TRUE)
src.hood = hood
RegisterSignal(hood, COMSIG_QDELETING, PROC_REF(on_hood_deleted))
if(hood)
if(ishuman(hood.loc))
var/mob/living/carbon/human/H = hood.loc
H.transferItemToLoc(hood, src, TRUE)
H.update_worn_oversuit()
else
hood.forceMove(src)
/// Called when hood is deleted
/obj/item/clothing/suit/hooded/proc/on_hood_deleted()
SIGNAL_HANDLER
SHOULD_CALL_PARENT(TRUE)
hood = null
if(alternative_mode)
QDEL_NULL(hood)
/// Called when the hood is worn
/obj/item/clothing/suit/hooded/proc/on_hood_up(obj/item/clothing/head/hooded/hood)
return
update_item_action_buttons()
/obj/item/clothing/suit/hooded/dropped()
..()
RemoveHood()
/obj/item/clothing/suit/hooded/proc/ToggleHood()
if(!hood_up)
if(!ishuman(loc))
return
var/mob/living/carbon/human/H = loc
if(H.is_holding(src))
to_chat(H, span_warning("You must be wearing [src] to put up the hood!"))
return
if(H.head)
to_chat(H, span_warning("You're already wearing something on your head!"))
return
else
if(alternative_mode)
MakeHood()
if(!H.equip_to_slot_if_possible(hood,ITEM_SLOT_HEAD,0,0,1))
if(alternative_mode)
RemoveHood()
return
hood_up = TRUE
icon_state = "[initial(icon_state)][hood_up_affix]"
worn_icon_state = icon_state
zipped = TRUE // Just to maintain the same behavior, and so we avoid any bugs that otherwise relied on this behavior of zipping the jacket when bringing up the hood
H.update_worn_oversuit()
H.update_mob_action_buttons()
else
RemoveHood()
/obj/item/clothing/head/hooded
var/obj/item/clothing/suit/hooded/suit
/obj/item/clothing/head/hooded/Destroy()
suit = null
return ..()
/obj/item/clothing/head/hooded/dropped()
..()
if(suit)
suit.RemoveHood()
/obj/item/clothing/head/hooded/equipped(mob/user, slot)
..()
if(!(slot & ITEM_SLOT_HEAD))
if(suit)
suit.RemoveHood()
else
qdel(src)
// Toggle exosuits for different aesthetic styles (hoodies, suit jacket buttons, etc)
// Pretty much just a holder for `/datum/component/toggle_icon`.
/// Called when the hood is hidden
/obj/item/clothing/suit/hooded/proc/on_hood_down(obj/item/clothing/head/hooded/hood)
return
/obj/item/clothing/suit/toggle
/// The noun that is displayed to the user on toggle. EX: "Toggles the suit's [buttons]".
+18 -21
View File
@@ -11,8 +11,9 @@
min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
allowed = list()
armor_type = /datum/armor/hooded_wintercoat
/// The mutable_appearance of the associated hood, when it's down. Can be null when the hood is up.
var/mutable_appearance/hood_overlay
hood_down_overlay_suffix = "_hood"
/// How snug are we?
var/zipped = FALSE
/datum/armor/hooded_wintercoat
bio = 10
@@ -31,8 +32,14 @@
/obj/item/toy,
)
generate_hood_overlay()
/obj/item/clothing/suit/hooded/wintercoat/on_hood_up(obj/item/clothing/head/hooded/hood)
. = ..()
zipped = TRUE
/// Called when the hood is hidden
/obj/item/clothing/suit/hooded/wintercoat/on_hood_down(obj/item/clothing/head/hooded/hood)
. = ..()
zipped = FALSE
/obj/item/clothing/suit/hooded/wintercoat/examine(mob/user)
. = ..()
@@ -54,19 +61,6 @@
var/mob/living/carbon/human/wearer = loc
wearer.update_worn_oversuit()
/// Helper proc to generate the `hood_overlay` associated to the wintercoat.
/obj/item/clothing/suit/hooded/wintercoat/proc/generate_hood_overlay()
hood_overlay = mutable_appearance(initial(worn_icon), "[initial(icon_state)]_hood", -SUIT_LAYER)
/obj/item/clothing/suit/hooded/wintercoat/worn_overlays(mutable_appearance/standing, isinhands)
. = ..()
if(!isinhands && !hood_up)
. += hood_overlay
/obj/item/clothing/head/hooded/winterhood
name = "winter hood"
desc = "A cozy winter hood attached to a heavy winter jacket."
@@ -649,6 +643,7 @@
/obj/item/clothing/suit/hooded/wintercoat/custom
name = "tailored winter coat"
desc = "A heavy jacket made from 'synthetic' animal furs, with custom colors."
hood_down_overlay_suffix = ""
greyscale_colors = "#ffffff#ffffff#808080#808080#808080#808080"
greyscale_config = /datum/greyscale_config/winter_coats
greyscale_config_worn = /datum/greyscale_config/winter_coats/worn
@@ -658,13 +653,15 @@
//In case colors are changed after initialization
/obj/item/clothing/suit/hooded/wintercoat/custom/set_greyscale(list/colors, new_config, new_worn_config, new_inhand_left, new_inhand_right)
. = ..()
if(hood)
var/list/coat_colors = SSgreyscale.ParseColorString(greyscale_colors)
var/list/new_coat_colors = coat_colors.Copy(1,4)
hood.set_greyscale(new_coat_colors) //Adopt the suit's grayscale coloring for visual clarity.
if(!hood)
return
var/list/coat_colors = SSgreyscale.ParseColorString(greyscale_colors)
var/list/new_coat_colors = coat_colors.Copy(1,4)
hood.set_greyscale(new_coat_colors) //Adopt the suit's grayscale coloring for visual clarity.
hood.update_slot_icon()
//But also keep old method in case the hood is (re-)created later
/obj/item/clothing/suit/hooded/wintercoat/custom/MakeHood()
/obj/item/clothing/suit/hooded/wintercoat/custom/on_hood_created(obj/item/clothing/head/hooded/hood)
. = ..()
var/list/coat_colors = (SSgreyscale.ParseColorString(greyscale_colors))
var/list/new_coat_colors = coat_colors.Copy(1,4)
+1
View File
@@ -445,6 +445,7 @@
set_wearer(user)
/obj/item/mod/control/on_outfit_equip(mob/living/carbon/human/outfit_wearer, visuals_only, item_slot)
. = ..()
quick_activation()
/obj/item/mod/control/doStrip(mob/stripper, mob/owner)
+5 -3
View File
@@ -4,11 +4,12 @@
/datum/unit_test/ablative_hood_hud/Run()
var/mob/living/carbon/human/person = allocate(/mob/living/carbon/human/consistent)
var/obj/item/clothing/suit/hooded/ablative/coat = allocate(/obj/item/clothing/suit/hooded/ablative)
var/datum/component/toggle_attached_clothing/hood = coat.GetComponent(/datum/component/toggle_attached_clothing)
person.equip_to_slot(coat, ITEM_SLOT_OCLOTHING)
TEST_ASSERT(!HAS_TRAIT(person, TRAIT_SECURITY_HUD), "Person already had a sechud before trying to equip the ablative hood.")
coat.ToggleHood()
hood.toggle_deployable()
TEST_ASSERT(HAS_TRAIT(person, TRAIT_SECURITY_HUD), "Person toggled the ablative hood but didn't gain a sechud.")
coat.ToggleHood()
hood.toggle_deployable()
TEST_ASSERT(!HAS_TRAIT(person, TRAIT_SECURITY_HUD), "Person lowered their ablative hood but still has a sechud.")
// Check that player doesn't gain sec hud if the hood is toggled when already wearing a helmet
@@ -17,9 +18,10 @@
/datum/unit_test/ablative_hood_hud_with_helmet/Run()
var/mob/living/carbon/human/person = allocate(/mob/living/carbon/human/consistent)
var/obj/item/clothing/suit/hooded/ablative/coat = allocate(/obj/item/clothing/suit/hooded/ablative)
var/datum/component/toggle_attached_clothing/hood = coat.GetComponent(/datum/component/toggle_attached_clothing)
var/obj/item/clothing/head/helmet/hat = allocate(/obj/item/clothing/head/helmet)
person.equip_to_slot(coat, ITEM_SLOT_OCLOTHING)
person.equip_to_slot(hat, ITEM_SLOT_HEAD)
TEST_ASSERT(!HAS_TRAIT(person, TRAIT_SECURITY_HUD), "Person already had a sechud before trying to equip the ablative hood.")
coat.ToggleHood()
hood.toggle_deployable()
TEST_ASSERT(!HAS_TRAIT(person, TRAIT_SECURITY_HUD), "Person has gained a sechud from toggling the ablative hood despite already wearing a helmet.")
@@ -110,8 +110,9 @@
equipped.eye_color_right = BLOODCULT_EYE
equipped.update_body()
var/obj/item/clothing/suit/hooded/hooded = locate() in equipped
hooded.ToggleHood()
var/obj/item/clothing/suit/hooded/hooded = equipped.wear_suit
var/datum/component/toggle_attached_clothing/hood = hooded.GetComponent(/datum/component/toggle_attached_clothing)
hood.toggle_deployable() // start unhooded
/obj/effect/mob_spawn/corpse/human/clock_cultist
@@ -10,36 +10,31 @@
greyscale_config_worn = /datum/greyscale_config/winter_coat_worn
greyscale_colors = "#666666#CCBBAA#0000FF"
flags_1 = IS_PLAYER_COLORABLE_1
hood_overlay = null // for this particular coat, the hood already is drawn onto the base sprite so we won't use this
//hood_down_overlay_suffix = "" future maintainers -- uncomment this when my toil gets undone by PR 22129.
// This should stop the hood overlay and negate the need for the two proc overrides below, as well as the 'hood_overlay = null'
hood_down_overlay_suffix = ""
/// Whether the hood is flipped up
var/hood_up = FALSE
// NO HOOD OVERLAYS EVER
/obj/item/clothing/suit/hooded/wintercoat/colourable/generate_hood_overlay()
return
// We are going to temporarily act as if the hood is up when it's down
// so that we don't ever add the hood_overlay in the parent proc.
// I cannot stress how much this coat hates hood overlays. We must avoid them at all costs.
/obj/item/clothing/suit/hooded/wintercoat/colourable/worn_overlays(mutable_appearance/standing, isinhands)
if(!hood_up)
hood_up = TRUE
. = ..()
else
return ..()
/// Called when the hood is worn
/obj/item/clothing/suit/hooded/wintercoat/colourable/on_hood_up(obj/item/clothing/head/hooded/hood)
hood_up = TRUE
/// Called when the hood is hidden
/obj/item/clothing/suit/hooded/wintercoat/colourable/on_hood_down(obj/item/clothing/head/hooded/hood)
hood_up = FALSE
//In case colors are changed after initialization
/obj/item/clothing/suit/hooded/wintercoat/colourable/set_greyscale(list/colors, new_config, new_worn_config, new_inhand_left, new_inhand_right)
. = ..()
if(hood)
var/list/coat_colors = SSgreyscale.ParseColorString(greyscale_colors)
var/list/new_coat_colors = coat_colors.Copy(1,3)
hood.set_greyscale(new_coat_colors) //Adopt the suit's grayscale coloring for visual clarity.
if(!hood)
return
var/list/coat_colors = SSgreyscale.ParseColorString(greyscale_colors)
var/list/new_coat_colors = coat_colors.Copy(1,3)
hood.set_greyscale(new_coat_colors) //Adopt the suit's grayscale coloring for visual clarity.
//But also keep old method in case the hood is (re-)created later
/obj/item/clothing/suit/hooded/wintercoat/colourable/MakeHood()
/obj/item/clothing/suit/hooded/wintercoat/colourable/on_hood_created(obj/item/clothing/head/hooded/hood)
. = ..()
var/list/coat_colors = (SSgreyscale.ParseColorString(greyscale_colors))
var/list/new_coat_colors = coat_colors.Copy(1,3)
+1
View File
@@ -1116,6 +1116,7 @@
#include "code\datums\components\tether.dm"
#include "code\datums\components\thermite.dm"
#include "code\datums\components\tippable.dm"
#include "code\datums\components\toggle_attached_clothing.dm"
#include "code\datums\components\toggle_suit.dm"
#include "code\datums\components\transforming.dm"
#include "code\datums\components\trapdoor.dm"