diff --git a/code/WorkInProgress/mantaObjects.dm b/code/WorkInProgress/mantaObjects.dm
index 62b4821ede211..6afd1af591c34 100644
--- a/code/WorkInProgress/mantaObjects.dm
+++ b/code/WorkInProgress/mantaObjects.dm
@@ -717,6 +717,7 @@ var/obj/manta_speed_lever/mantaLever = null
max_stack = 5
item_state = "cone_1"
wear_state = "cone_hat_1"
+ hat_offset_y = 8
setupProperties()
..()
diff --git a/code/datums/components/hattable.dm b/code/datums/components/hattable.dm
new file mode 100644
index 0000000000000..bc5f3f14eec3e
--- /dev/null
+++ b/code/datums/components/hattable.dm
@@ -0,0 +1,107 @@
+TYPEINFO(/datum/component/hattable) // Take a walk through my TWISTED mind.... I'm sorry
+ initialization_args = list(
+ ARG_INFO("death_remove", DATA_INPUT_BOOL, "Remove component and signals on death?", FALSE), // Use this for things that don't revive, or explode on death
+ ARG_INFO("free_hat", DATA_INPUT_BOOL, "Should the parent get a free hat?", FALSE), // For AIs
+ ARG_INFO("default_hat_y", DATA_INPUT_NUM, "Y offset to start with", 0),
+ ARG_INFO("default_hat_x", DATA_INPUT_NUM, "X offset to start with", 0),
+ ARG_INFO("scale_amount", DATA_INPUT_NUM, "Amount to change the size of the hat by", 0)
+ )
+
+/datum/component/hattable // Compatible stuff should be given hat_offset x and y vars in their own file
+ dupe_mode = COMPONENT_DUPE_HIGHLANDER
+ var/default_hat_y = 0
+ var/default_hat_x = 0
+ var/obj/item/hat = null
+ var/death_throw = FALSE
+ var/death_remove = FALSE
+ var/free_hat = FALSE
+ var/scale_amount = 0
+
+/datum/component/hattable/Initialize(death_remove, free_hat, default_hat_y, default_hat_x, scale_amount)
+ . = ..()
+ RegisterSignal(src.parent, COMSIG_ATTACKBY, PROC_REF(hat_on_thing), override = TRUE)
+ src.death_throw = FALSE
+ src.death_remove = death_remove
+ src.default_hat_y = default_hat_y
+ src.default_hat_x = default_hat_x
+ src.scale_amount = scale_amount
+ if (free_hat) // If the thing gets a free hat (currently just AIs with spacebux) and they haven't received it yet, do that
+ var/free_hat_type = pick(filtered_concrete_typesof(/obj/item/clothing/head, /proc/filter_trait_hats))
+ free_hat_type = new free_hat_type(get_turf(src))
+ hat_on_thing(src.parent, free_hat_type)
+
+
+/datum/component/hattable/proc/hat_on_thing(mob/target as mob, obj/item/item as obj, mob/attacker)
+ if (src.hat)
+ return
+
+ var/atom/movable/hatted = src.parent
+ var/offsetBy_y = 0
+ var/offsetBy_x = 0
+ src.hat = item
+
+ if (istype(src.hat, /obj/item/clothing/head/))
+ ADD_FLAG(src.hat.appearance_flags, KEEP_TOGETHER) // Flags needed for wigs!
+ ADD_FLAG(src.hat.vis_flags, VIS_INHERIT_DIR)
+ else
+ return
+
+ if (attacker)
+ attacker.drop_item()
+
+ offsetBy_y = src.default_hat_y + src.hat.hat_offset_y // Add a hat's own offsets if they have them
+ offsetBy_x = src.default_hat_x + src.hat.hat_offset_x
+ src.hat.pixel_y = offsetBy_y
+ src.hat.pixel_x = offsetBy_x
+
+ src.hat.transform *= src.scale_amount
+ src.hat.layer = hatted.layer + 1
+ src.hat.set_loc(src.parent)
+ hatted.vis_contents += src.hat
+
+ if (src.death_remove)
+ RegisterSignal(src.parent, COMSIG_MOB_DEATH, PROC_REF(die), override = TRUE)
+ RegisterSignal(src.hat, COMSIG_ITEM_PICKUP, PROC_REF(take_hat_off), override = TRUE)
+ return TRUE
+
+/datum/component/hattable/proc/take_hat_off(mob/target, mob/user)
+ if (!src.hat)
+ return
+ var/atom/movable/hatted = src.parent
+
+ src.hat.set_loc(get_turf(hatted))
+ hatted.vis_contents -= src.hat
+ src.hat.layer = OBJ_LAYER
+ src.hat.transform = 1
+ src.hat.pixel_y = 0
+ src.hat.pixel_x = 0
+
+ if(src.death_throw) // If the thing dies, this proc is called and death_throw is set to true, then false
+ var/turf/T = get_ranged_target_turf(src.hat, pick(alldirs), 3)
+ src.hat.throw_at(T, 3, 1)
+ UnregisterSignal(src.parent, list(COMSIG_MOB_DEATH, COMSIG_ATTACKBY))
+ UnregisterSignal(src.hat, COMSIG_ITEM_PICKUP)
+ src.death_throw = FALSE
+ return
+ else
+ UnregisterSignal(src.hat, COMSIG_ITEM_PICKUP)
+
+ if (istype(user, /mob/living/silicon/ghostdrone))
+ SPAWN(0) // Magtractors use an action bar until they do stuff, and then they'll clone a ghost image of the item that's on the floor. Drop that!
+ var/mob/living/silicon/ghostdrone/drone = user
+ var/obj/item/magtractor/mag = locate(/obj/item/magtractor) in drone.tools
+ if (mag.holding)
+ mag.dropItem(src)
+
+ src.hat = null
+
+/datum/component/hattable/proc/die()
+ if (src.hat)
+ src.death_throw = TRUE
+ take_hat_off(src.parent)
+
+
+
+
+
+
diff --git a/code/mob/living/silicon/ai.dm b/code/mob/living/silicon/ai.dm
index 76671e30b5c1d..203c9232e1b59 100644
--- a/code/mob/living/silicon/ai.dm
+++ b/code/mob/living/silicon/ai.dm
@@ -86,6 +86,7 @@ var/global/list/ai_emotions = list("Annoyed" = "ai_annoyed-dol", \
density = 1
emaggable = 0 // Can't be emagged...
syndicate_possible = 1 // ...but we can become a rogue computer.
+ var/default_hat_y = 14
var/datum/hud/silicon/ai/hud
var/last_notice = 0//attack notices
var/network = "SS13"
@@ -108,6 +109,7 @@ var/global/list/ai_emotions = list("Annoyed" = "ai_annoyed-dol", \
var/termMute = FALSE
var/canvox = 1
var/can_announce = 1
+ var/bought_hat = FALSE
var/last_announcement = -INFINITY
var/announcement_cooldown = 1200
var/dismantle_stage = 0
@@ -194,7 +196,6 @@ or don't if it uses a custom topopen overlay
sound_fart = 'sound/voice/farts/poo2_robot.ogg'
req_access = list(access_heads)
- var/obj/item/clothing/head/hat = null
var/fire_res_on_core = 0
@@ -210,26 +211,6 @@ or don't if it uses a custom topopen overlay
var/datum/ai_hologram_data/holoHolder = new
var/list/hologramContextActions
- proc/set_hat(obj/item/clothing/head/hat, var/mob/user as mob)
- if( src.hat )
- src.hat.wear_image.pixel_y = 0
- src.UpdateOverlays(null, "hat")
- if (user)
- user.put_in_hand_or_drop(src.hat)
- else
- src.hat.set_loc(src.loc)
- src.hat = null
- // src.hat.wear_image.pixel_y = 10
- // src.UpdateOverlays(src.hat.wear_image, "hat")
- var/image/hat_image = SafeGetOverlayImage(hat.icon_state, hat.icon, hat.icon_state, src.layer+0.3)
- hat_image.pixel_y = 12
- if (istype(hat, /obj/item/clothing/head/bighat))
- hat_image.pixel_y = 20
-
- src.UpdateOverlays(hat_image, "hat")
- src.hat = hat
- hat.set_loc(src)
-
/mob/living/silicon/ai/proc/give_feet()
animate(src, pixel_y = 14, time = 5, easing = SINE_EASING)
has_feet = 1
@@ -293,7 +274,11 @@ or don't if it uses a custom topopen overlay
ai_station_map = new /obj/minimap/ai
AddComponent(/datum/component/minimap_marker, MAP_AI | MAP_SYNDICATE, "ai")
-
+ SPAWN(0)
+ if (bought_hat || prob(5))
+ AddComponent(/datum/component/hattable, TRUE, TRUE, default_hat_y)
+ else
+ AddComponent(/datum/component/hattable, TRUE, FALSE, default_hat_y)
light = new /datum/light/point
light.set_color(0.4, 0.7, 0.95)
light.set_brightness(0.6)
@@ -340,9 +325,7 @@ or don't if it uses a custom topopen overlay
var/datum/contextAction/ai_hologram/action = new actionType(src)
hologramContextActions += action
- if(prob(5))
- var/hat_type = pick(childrentypesof(/obj/item/clothing/head))
- src.set_hat(new hat_type)
+
SPAWN(0)
@@ -595,11 +578,6 @@ or don't if it uses a custom topopen overlay
user.visible_message(SPAN_ALERT("[user.name] uploads a moustache to [src.name]!"))
else if (src.dismantle_stage == 4 || isdead(src))
boutput(user, SPAN_ALERT("Using this on a deactivated AI would be silly."))
- else if( istype(W,/obj/item/clothing/head))
- user.drop_item()
- src.set_hat(W, user)
- user.visible_message( SPAN_NOTICE("[user] places the [W] on the [src]!") )
- src.show_message( SPAN_NOTICE("[user] places the [W] on you!") )
if(istype(W, /obj/item/clothing/head/butt))
var/obj/item/clothing/head/butt/butt = W
if(butt.donor == user)
@@ -1010,8 +988,6 @@ or don't if it uses a custom topopen overlay
return list()
. = list("[SPAN_NOTICE("This is [bicon(src)] [src.name]!")] [skinsList[coreSkin]]
") // skinList[coreSkin] points to the appropriate desc for the current core skin
- if (src.hat)
- . += SPAN_NOTICE("[src.name] is wearing the [bicon(src.hat)] [src.hat.name].")
if (isdead(src))
. += SPAN_ALERT("[src.name] is nonfunctional...")
diff --git a/code/mob/living/silicon/ghostdrone.dm b/code/mob/living/silicon/ghostdrone.dm
index 10e9e2dafe947..16a577f3c6e49 100644
--- a/code/mob/living/silicon/ghostdrone.dm
+++ b/code/mob/living/silicon/ghostdrone.dm
@@ -14,6 +14,8 @@
punchMessage = "whaps"
kickMessage = "bonks"
+ var/default_hat_y = 7
+
var/datum/hud/ghostdrone/hud
var/obj/item/device/radio/radio = null
@@ -25,17 +27,16 @@
var/faceType
var/charging = 0
var/newDrone = 0
-
var/jetpack = 1 //fuck whoever made this
var/sees_static = TRUE
//gimmicky things
- var/obj/item/clothing/head/hat = null
var/obj/item/clothing/suit/bedsheet/bedsheet = null
New()
..()
+ AddComponent(/datum/component/hattable, TRUE, FALSE, default_hat_y, 0, 0.75)
remove_lifeprocess(/datum/lifeprocess/radiation)
APPLY_ATOM_PROPERTY(src, PROP_MOB_RADPROT_INT, src, 100)
START_TRACKING
@@ -163,7 +164,6 @@
src.visible_message(SPAN_COMBAT("[src.name] explodes in a shower of lost hopes and dreams."))
var/turf/T = get_ranged_target_turf(src, pick(alldirs), 3)
if (magHeld) magHeld.throw_at(T, 3, 1) //flying...anything
- if (src.hat) src.takeoffHat(pick(alldirs)) //flying hats
if (src.bedsheet) //flying bedsheets
bedsheet.set_loc(get_turf(src))
bedsheet.throw_at(T, 3, 1)
@@ -180,7 +180,6 @@
msg = "[src.name]'s scream's gain echo and lose their electronic modulation as its soul is ripped monstrously from the cold metal body it once inhabited."
src.visible_message(SPAN_COMBAT("[msg]"))
- if (src.hat) src.takeoffHat()
src.updateSprite()
..()
@@ -425,38 +424,7 @@
step(AM, t)
src.now_pushing = null
- //Four very important procs follow
- proc/putonHat(obj/item/clothing/head/W as obj, mob/user as mob)
- src.hat = W
- W.set_loc(src)
- var/image/hatImage = null
- // Treat wigs differently as their icon_state is always bald
- if (istype(W, /obj/item/clothing/head/wig))
- hatImage = W.wear_image
- hatImage.layer = src.layer+0.1
- hatImage.pixel_y = -7
- else
- hatImage = image(icon = W.icon, icon_state = W.icon_state, layer = src.layer+0.1)
- hatImage.pixel_y = 5
- hatImage.transform *= 0.9
- UpdateOverlays(hatImage, "hat")
- return 1
-
- proc/takeoffHat(forcedDir = null)
- UpdateOverlays(null, "hat")
- src.hat.set_loc(get_turf(src))
-
- var/turf/T
- if (isnum(forcedDir))
- T = get_ranged_target_turf(src, forcedDir, 3)
- if (isturf(forcedDir))
- T = forcedDir
- if (isturf(T))
- src.hat.throw_at(T, 3, 1)
-
- src.hat = null
- return 1
-
+ // Two important procs follow
proc/putonSheet(obj/item/clothing/suit/bedsheet/W as obj, mob/user as mob)
W.set_loc(src)
src.bedsheet = W
@@ -517,16 +485,6 @@
playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1)
if (src.health >= 25)
boutput(user, SPAN_NOTICE("The wiring is fully repaired. Now you need to weld the external plating."))
-
- else if (istype(W, /obj/item/clothing/head))
- if(src.hat)
- boutput(user, SPAN_ALERT("[src] is already wearing a hat!"))
- return
-
- user.drop_item()
- src.putonHat(W, user)
- if (user != src)
- user.visible_message("[user] gently places a hat on [src]!", "You gently place a hat on [src]!")
return
else if (istype(W, /obj/item/clothing/suit/bedsheet))
@@ -551,10 +509,7 @@
if(INTENT_DISARM) //Shove
SPAWN(0) playsound(src.loc, 'sound/impact_sounds/Generic_Swing_1.ogg', 40, 1)
user.visible_message(SPAN_ALERT("[user] shoves [src]! [prob(40) ? pick_string("descriptors.txt", "jerks") : null]"))
- if (src.hat)
- user.visible_message("[user] knocks \the [src.hat] off [src]!", "You knock the hat off [src]!")
- src.takeoffHat()
- else if (src.bedsheet)
+ if (src.bedsheet)
user.visible_message("[user] pulls the sheet off [src]!", "You pull the sheet off [src]!")
src.takeoffSheet()
if(INTENT_GRAB) //Shake
@@ -1080,17 +1035,6 @@
src.changeStatus("stunned", stun SECONDS)
- if (src.hat) //For hats getting shot off
- UpdateOverlays(null, "hat")
- src.hat.set_loc(get_turf(src))
- //get target turf
- var/x = round(P.xo * 4)
- var/y = round(P.yo * 4)
- var/turf/target = get_offset_target_turf(src, x, y)
-
- src.visible_message(SPAN_COMBAT("[src]'s [src.hat] goes flying!"))
- src.takeoffHat(target)
-
if (damage < 1)
return
diff --git a/code/modules/barber/barber_shop.dm b/code/modules/barber/barber_shop.dm
index 843fdc8455c35..afb351f5e9045 100644
--- a/code/modules/barber/barber_shop.dm
+++ b/code/modules/barber/barber_shop.dm
@@ -18,6 +18,8 @@
desc = "You can't tell the difference, Honest!"
icon_state= "wig"
wear_layer = MOB_HAIR_LAYER2 //it IS hair afterall
+ hat_offset_y = -15 // offsets for hattable component
+ hat_offset_x = 0
///Takes a list of style ids to colors and generates a wig from it
proc/setup_wig(var/style_list)
diff --git a/code/modules/economy/persistent_bank_purchases.dm b/code/modules/economy/persistent_bank_purchases.dm
index b46e6bed6c21c..8f700175dc9f6 100644
--- a/code/modules/economy/persistent_bank_purchases.dm
+++ b/code/modules/economy/persistent_bank_purchases.dm
@@ -127,11 +127,9 @@ var/global/list/persistent_bank_purchaseables = list(\
if(isAI(M))
var/mob/living/silicon/ai/AI = M
- if (ispath(path, /obj/item/clothing))
- if(ispath(path,/obj/item/clothing/head))
- AI.set_hat(new path(AI))
- equip_success = 1
-
+ path = null
+ AI.bought_hat = TRUE
+ return
//The AI can't really wear items...
@@ -788,7 +786,6 @@ var/global/list/persistent_bank_purchaseables = list(\
Create(var/mob/living/M)
if (isAI(M))
var/mob/living/silicon/ai/A = M
- var/picked = pick(filtered_concrete_typesof(/obj/item/clothing/head, /proc/filter_trait_hats))
- A.set_hat(new picked())
+ A.bought_hat = TRUE
return 1
return 0
diff --git a/code/modules/mechanics/MechanicMadness.dm b/code/modules/mechanics/MechanicMadness.dm
index c8844c5479605..941a910fe8f65 100644
--- a/code/modules/mechanics/MechanicMadness.dm
+++ b/code/modules/mechanics/MechanicMadness.dm
@@ -30,6 +30,8 @@
var/welded = FALSE
var/can_be_welded = FALSE
var/can_be_anchored = UNANCHORED
+ var/default_hat_y = 0
+ var/default_hat_x = 0
custom_suicide = TRUE
open_to_sound = TRUE
@@ -224,6 +226,11 @@
icon_state="housing_cabinet"
flags = FPRINT | EXTRADELAY | CONDUCT
light_color = list(0, 179, 255, 255)
+ default_hat_y = 14
+
+ New()
+ AddComponent(/datum/component/hattable, FALSE, FALSE, default_hat_y)
+ ..()
attack_hand(mob/user)
if (istype(user,/mob/living/object) && user == src.loc) // prevent wacky nullspace bug
@@ -265,6 +272,12 @@
c_flags = ONBELT
light_color = list(51, 0, 0, 0)
spawn_contents=list(/obj/item/mechanics/trigger/trigger)
+ default_hat_y = 7
+ default_hat_x = -1
+
+ New()
+ AddComponent(/datum/component/hattable, FALSE, FALSE, default_hat_y, default_hat_x)
+ ..()
proc/find_trigger() // find the trigger comp, return 1 if found.
if (!istype(src.the_trigger))
diff --git a/code/obj/item.dm b/code/obj/item.dm
index 037d98be4edbc..5db6e73897d10 100644
--- a/code/obj/item.dm
+++ b/code/obj/item.dm
@@ -22,6 +22,10 @@ ABSTRACT_TYPE(/obj/item)
var/inhand_color = null
/// storage datum holding it
var/datum/storage/stored = null
+ /// Used for the hattable component
+ var/hat_offset_y = 0
+ /// Used for the hattable component
+ var/hat_offset_x = 0
/*_______*/
/*Burning*/
@@ -1154,14 +1158,15 @@ ADMIN_INTERACT_PROCS(/obj/item, proc/admin_set_stack_amount)
return "It is \an [t] item."
/obj/item/attack_hand(mob/user)
- var/checkloc = src.loc
- while(checkloc && !istype(checkloc,/turf))
- if (isliving(checkloc) && checkloc != user)
- if(src in bible_contents)
- break
- else
- return 0
- checkloc = checkloc:loc
+ var/obj/item/checkloc = src.loc
+ if (!ismob(src.loc)) // Skip this loop if the FIRST loc is a mob, allowing component/hattable to proc take_hat_off on AIs/ghostdrones
+ while(checkloc && !istype(checkloc,/turf))
+ if(isliving(checkloc) && checkloc != user) // This heinous block is to make sure you're not swiping things from other people's backpacks
+ if(src in bible_contents) // Bibles share their contents globally, so magically taking stuff from them is fine
+ break
+ else
+ return 0
+ checkloc = checkloc.loc // Get the loc of the loc! The loop continues until it's the turf of what you clicked on
if(!src.can_pickup(user))
// unholdable storage items
diff --git a/code/obj/item/clothing/hats.dm b/code/obj/item/clothing/hats.dm
index db9eaf464a8c7..620bdd1adb703 100644
--- a/code/obj/item/clothing/hats.dm
+++ b/code/obj/item/clothing/hats.dm
@@ -2036,6 +2036,7 @@ ABSTRACT_TYPE(/obj/item/clothing/head/mushroomcap)
name = "mushroom cap"
desc = "Makes your lungs feel a little fuzzy."
var/additional_desc = ""
+ hat_offset_y = 4
icon_state = "mushroom-red"
item_state = "mushroom-red"
diff --git a/goonstation.dme b/goonstation.dme
index cbf1c0b1b5f61..ab727c02057e5 100644
--- a/goonstation.dme
+++ b/goonstation.dme
@@ -224,6 +224,7 @@ var/datum/preMapLoad/preMapLoad = new
#include "code\datums\components\glue_ready.dm"
#include "code\datums\components\glued.dm"
#include "code\datums\components\hallucinations.dm"
+#include "code\datums\components\hattable.dm"
#include "code\datums\components\health_maptext.dm"
#include "code\datums\components\legs.dm"
#include "code\datums\components\light.dm"