Skip to content

Commit

Permalink
Merge pull request #3724 from MistakeNot4892/feature/grooming
Browse files Browse the repository at this point in the history
Reworks combs and brushes.
  • Loading branch information
out-of-phaze authored Mar 7, 2024
2 parents 5bc86a7 + 8e31fa8 commit b39d052
Show file tree
Hide file tree
Showing 41 changed files with 362 additions and 74 deletions.
16 changes: 16 additions & 0 deletions code/__defines/misc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,19 @@
#define UTENSIL_FLAG_SLICE BITFLAG(2)
/// Unimplemented; condiments that are collected before being spread on other food.
#define UTENSIL_FLAG_SPREAD BITFLAG(3)

// Default.
#define GROOMABLE_NONE 0
// Hair, feathers.
#define GROOMABLE_COMB BITFLAG(0)
// Hair, beards.
#define GROOMABLE_BRUSH BITFLAG(1)
// Horns.
#define GROOMABLE_FILE BITFLAG(2)

// Nothing to groom on this organ.
#define GROOMING_RESULT_FAILED 0
// Can groom somewhat (short hair with a comb)
#define GROOMING_RESULT_PARTIAL 1
// Can groom properly (long hair with a brush)
#define GROOMING_RESULT_SUCCESS 2
7 changes: 4 additions & 3 deletions code/game/machinery/vending/misc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
product_ads = "Impress the love of your life!;Don't look poor, look rich!;100% authentic designers!;All sales are final!;Lowest prices guaranteed!"
products = list(
/obj/item/mirror = 8,
/obj/item/haircomb = 8,
/obj/item/grooming/comb = 8,
/obj/item/clothing/glasses/eyepatch/monocle = 5,
/obj/item/clothing/glasses/sunglasses = 5,
/obj/random/makeup = 3,
Expand Down Expand Up @@ -121,8 +121,9 @@
products = list(
/obj/item/soap = 12,
/obj/item/mirror = 8,
/obj/item/haircomb/random = 8,
/obj/item/haircomb/brush = 4,
/obj/item/grooming/comb/colorable/random = 8,
/obj/item/grooming/brush/colorable/random = 4,
/obj/item/grooming/file = 1,
/obj/item/towel/random = 6,
/obj/item/chems/spray/cleaner/deodorant = 5
)
Expand Down
44 changes: 0 additions & 44 deletions code/game/objects/items/weapons/hair_care.dm

This file was deleted.

5 changes: 3 additions & 2 deletions code/game/objects/items/weapons/storage/wall_mirror.dm
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@

/obj/item/storage/internal/mirror_storage/WillContain()
return list(
/obj/item/haircomb/random,
/obj/item/haircomb/brush,
/obj/item/grooming/comb/colorable/random,
/obj/item/grooming/brush/colorable/random,
/obj/item/grooming/file,
/obj/random/medical/lite,
/obj/random/lipstick,
/obj/random/eyeshadow,
Expand Down
2 changes: 1 addition & 1 deletion code/game/objects/items/weapons/storage/wallets.dm
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/obj/item/card,
/obj/item/clothing/mask/smokable,
/obj/item/cosmetics,
/obj/item/haircomb,
/obj/item/grooming,
/obj/item/mirror,
/obj/item/clothing/accessory/locket,
/obj/item/clothing/head/hairflower,
Expand Down
7 changes: 4 additions & 3 deletions code/game/objects/random/subtypes/misc.dm
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/obj/random/contraband
name = "Random Illegal Item"
desc = "Hot Stuff."
icon = 'icons/obj/items/comb.dmi'
icon_state = "purplecomb"
icon = 'icons/obj/items/grooming/comb.dmi'
icon_state = ICON_STATE_WORLD
color = COLOR_PURPLE
spawn_nothing_percentage = 50

/obj/random/contraband/spawn_choices()
var/static/list/spawnable_choices = list(
/obj/item/haircomb = 4,
/obj/item/grooming/comb = 4,
/obj/item/storage/pill_bottle/painkillers = 3,
/obj/item/storage/pill_bottle/strong_painkillers = 1,
/obj/item/storage/pill_bottle/happy = 2,
Expand Down
16 changes: 13 additions & 3 deletions code/modules/client/preference_setup/loadout/lists/misc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,21 @@
path = /obj/item/cosmetics/eyeshadow
loadout_flags = GEAR_HAS_SUBTYPE_SELECTION

/decl/loadout_option/comb
name = "plastic comb"
path = /obj/item/haircomb
/decl/loadout_option/grooming
name = "grooming tool selection"
path = /obj/item/grooming
loadout_flags = GEAR_HAS_COLOR_SELECTION

/decl/loadout_option/grooming/get_gear_tweak_options()
. = ..()
LAZYINITLIST(.[/datum/gear_tweak/path])
.[/datum/gear_tweak/path] |= list(
"comb" = /obj/item/grooming/comb/colorable,
"butterfly comb" = /obj/item/grooming/comb/butterfly/colorable,
"brush" = /obj/item/grooming/brush/colorable,
"file" = /obj/item/grooming/file/colorable
)

/decl/loadout_option/mask
name = "sterile mask"
path = /obj/item/clothing/mask/surgical
Expand Down
87 changes: 87 additions & 0 deletions code/modules/grooming/_grooming.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@

/obj/item/grooming
abstract_type = /obj/item/grooming
icon_state = ICON_STATE_WORLD
w_class = ITEM_SIZE_TINY
slot_flags = SLOT_EARS
material_alteration = MAT_FLAG_ALTERATION_COLOR | MAT_FLAG_ALTERATION_NAME | MAT_FLAG_ALTERATION_DESC

var/message_target_other_generic = "$USER$ grooms $TARGET$ with $TOOL$."
var/message_target_self_generic = "$USER$ grooms $USER_SELF$ with $TOOL$."
var/message_target_other = "$USER$ grooms $TARGET$'s $DESCRIPTOR$ with $TOOL$."
var/message_target_self = "$USER$ grooms $USER_HIS$ $DESCRIPTOR$ with $TOOL$."
var/message_target_other_partial = "$USER$ just sort of runs $TOOL$ over $TARGET$'s $DESCRIPTOR$."
var/message_target_self_partial = "$USER$ just sort of runs $TOOL$ over $USER_HIS$ $DESCRIPTOR$."
var/message_target_other_missing = "$TARGET$'s $LIMB$ has nothing to groom!"
var/message_target_self_missing = "Your $LIMB$ has nothing to groom!"
var/grooming_flags = GROOMABLE_NONE

/obj/item/grooming/Initialize()
. = ..()
update_icon()

/obj/item/grooming/proc/replace_message_tokens(message, mob/living/user, mob/living/target, obj/item/tool, limb, descriptor)
var/decl/pronouns/user_pronouns = user.get_pronouns()
var/decl/pronouns/target_pronouns = target.get_pronouns()
. = message
. = replacetext(., "$USER$", "\the [user]")
. = replacetext(., "$USER_HIS$", user_pronouns.his)
. = replacetext(., "$USER_HIM$", user_pronouns.him)
. = replacetext(., "$USER_SELF$", user_pronouns.self)
. = replacetext(., "$TARGET$", "\the [target]")
. = replacetext(., "$TARGET_HIS$", target_pronouns.his)
. = replacetext(., "$TARGET_HIM$", target_pronouns.him)
. = replacetext(., "$TARGET_SELF$", target_pronouns.self)
. = replacetext(., "$TOOL$", "\the [tool]")
. = replacetext(., "$DESCRIPTOR$", descriptor)
. = replacetext(., "$LIMB$", limb)
. = capitalize(.)

/obj/item/grooming/proc/try_groom(mob/living/user, mob/living/target)

if(!istype(user) || !istype(target) || user.incapacitated() || user.a_intent == I_HURT)
return FALSE

if(!length(target.get_external_organs()))
return target.handle_general_grooming(user, src)

var/zone = user.get_target_zone()
if(!zone)
return FALSE

var/target_self = user == target
var/obj/item/organ/external/affecting = GET_EXTERNAL_ORGAN(target, zone)
if(!affecting)
to_chat(user, SPAN_WARNING("[target_self ? "You are" : "\The [target] [target.get_pronouns().is]"] missing [target_self ? "your" : target.get_pronouns().his] [parse_zone(zone)]."))
return TRUE

var/obj/item/blocking = target.bodypart_is_covered(zone)
if(blocking)
to_chat(user, "\The [blocking] [blocking.get_pronouns().is] in the way!")
return TRUE

user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)

// return type is assoc list ie. list("success" = GROOMING_RESULT_PARTIAL, "descriptor" = "hair")
var/list/grooming_results = affecting.get_grooming_results(src)
var/show_message
switch(LAZYACCESS(grooming_results, "success"))
if(GROOMING_RESULT_PARTIAL)
show_message = target_self ? message_target_self_partial : message_target_other_partial
if(GROOMING_RESULT_SUCCESS)
show_message = target_self ? message_target_self : message_target_other
else
show_message = target_self ? message_target_self_missing : message_target_other_missing
visible_message(SPAN_NOTICE(replace_message_tokens(show_message, user, target, src, parse_zone(zone), LAZYACCESS(grooming_results, "descriptor") || "marking")))
target.add_stressor(/datum/stressor/well_groomed, 5 MINUTES)
return TRUE

/obj/item/grooming/attack_self(mob/user)
if(try_groom(user, user))
return TRUE
return ..()

/obj/item/grooming/attack(mob/living/M, mob/living/user, var/target_zone, animate = TRUE)
if(try_groom(user, M))
return TRUE
return ..()
58 changes: 58 additions & 0 deletions code/modules/grooming/grooming_comb.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/obj/item/grooming/comb
name = "comb"
desc = "A pristine comb."
icon = 'icons/obj/items/grooming/comb.dmi'
material = /decl/material/solid/organic/plastic

message_target_other_generic = "$USER$ tidily combs $TARGET$ with $TOOL$."
message_target_self_generic = "$USER$ tidily combs $USER_SELF$ with $TOOL$."
message_target_other = "$USER$ tidily combs $TARGET$'s $DESCRIPTOR$ with $TOOL$."
message_target_self = "$USER$ tidily combs $USER_HIS$ $DESCRIPTOR$ with $TOOL$."
grooming_flags = GROOMABLE_COMB

/obj/item/grooming/comb/colorable
material_alteration = MAT_FLAG_ALTERATION_NAME | MAT_FLAG_ALTERATION_DESC

/obj/item/grooming/comb/colorable/random/Initialize()
color = get_random_colour(lower = 150)
. = ..()

// Looks exactly like a butterfly knife inhand.
/obj/item/grooming/comb/butterfly
name = "butterfly comb"
desc = "A very stylish comb that folds into a handle."
icon = 'icons/obj/items/grooming/comb_butterfly.dmi'
material = /decl/material/solid/metal/steel

message_target_other_generic = "$USER$ uses $TOOL$ to comb $TARGET$ with incredible style and sophistication."
message_target_self_generic = "$USER$ uses $TOOL$ to comb $USER_SELF$ with incredible style and sophistication. What a $USER_GUY$."
message_target_other = "$USER$ uses $TOOL$ to comb $TARGET$'s hair with incredible style and sophistication."
message_target_self = "$USER$ uses $TOOL$ to comb $USER_HIS$ hair with incredible style and sophistication. What a $USER_GUY$."

var/opened = FALSE

/obj/item/grooming/comb/butterfly/attack_self(mob/user)
if(user.a_intent == I_HURT)
return ..()
opened = !opened
if(opened)
playsound(user, 'sound/weapons/flipblade.ogg', 15, 1)
user.visible_message(SPAN_NOTICE("\The [user] flicks \the [src] [opened ? "open" : "closed"]."))
update_icon()
return TRUE

/obj/item/grooming/comb/butterfly/try_groom(mob/living/user, mob/living/target)
return opened && ..()

/obj/item/grooming/comb/butterfly/on_update_icon()
. = ..()
icon_state = get_world_inventory_state()
if(opened)
icon_state = "[icon_state]_open"

/obj/item/grooming/comb/butterfly/replace_message_tokens(message, mob/living/user, mob/living/target, obj/item/tool, limb, descriptor)
message = replacetext(message, "$USER_GUY$", user.get_pronouns().informal_term)
return ..()

/obj/item/grooming/comb/butterfly/colorable
material_alteration = MAT_FLAG_ALTERATION_NAME | MAT_FLAG_ALTERATION_DESC
14 changes: 14 additions & 0 deletions code/modules/grooming/grooming_file.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/obj/item/grooming/file
name = "nail file"
desc = "A rugged file suitable for smoothing down unruly nails or horns."
icon = 'icons/obj/items/grooming/file.dmi'
material = /decl/material/solid/metal/steel

message_target_other_generic = "$USER$ uses $TOOL$ to neaten $TARGET$ up."
message_target_self_generic = "$USER$ uses $TOOL$ to neaten $USER_SELF$ up."
message_target_other = "$USER$ carefully files down $TARGET$'s $DESCRIPTOR$ with $TOOL$."
message_target_self = "$USER$ carefully files down $USER_HIS$ $DESCRIPTOR$ with $TOOL$."
grooming_flags = GROOMABLE_FILE

/obj/item/grooming/file/colorable
material_alteration = MAT_FLAG_ALTERATION_NAME | MAT_FLAG_ALTERATION_DESC
27 changes: 27 additions & 0 deletions code/modules/grooming/grooming_hairbrush.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/obj/item/grooming/brush
name = "hairbrush"
desc = "A surprisingly decent hairbrush with a sturdy handle and semi-soft bristles."
icon = 'icons/obj/items/grooming/hairbrush.dmi'
w_class = ITEM_SIZE_SMALL
slot_flags = null
material = /decl/material/solid/organic/plastic

message_target_other_generic = "$USER$ meticulously brushes $TARGET$ with $TOOL$."
message_target_self_generic = "$USER$ meticulously brushes $USER_SELF$ with $TOOL$."
message_target_other = "$USER$ meticulously brushes $TARGET$'s $DESCRIPTOR$ with $TOOL$."
message_target_self = "$USER$ meticulously brushes $USER_HIS$ $DESCRIPTOR$ with $TOOL$."
grooming_flags = GROOMABLE_BRUSH

/obj/item/grooming/brush/on_update_icon()
..()
var/image/I = image(icon, "[icon_state]-bristles")
I.color = COLOR_GRAY40
I.appearance_flags |= RESET_COLOR
add_overlay(I)

/obj/item/grooming/brush/colorable
material_alteration = MAT_FLAG_ALTERATION_NAME

/obj/item/grooming/brush/colorable/random/Initialize()
color = get_random_colour(lower = 150)
. = ..()
9 changes: 9 additions & 0 deletions code/modules/mob/living/carbon/human/human.dm
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,15 @@
if(length(default_languages) && isnull(default_language))
default_language = default_languages[1]

/mob/living/proc/bodypart_is_covered(target_zone)
var/obj/item/organ/external/affecting = GET_EXTERNAL_ORGAN(src, target_zone)
if(!affecting?.body_part)
return FALSE
for(var/obj/item/clothing/thing in get_equipped_items())
if(thing.body_parts_covered & affecting.body_part)
return thing
return FALSE

/mob/living/carbon/human/can_inject(var/mob/user, var/target_zone)
var/obj/item/organ/external/affecting = GET_EXTERNAL_ORGAN(src, target_zone)

Expand Down
7 changes: 7 additions & 0 deletions code/modules/mob/living/living.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1432,3 +1432,10 @@ default behaviour is:
buckled_mob.layer = layer + 0.01
buckled_mob.plane = plane

/mob/living/proc/handle_general_grooming(user, obj/item/grooming/tool)
if(tool.grooming_flags & (GROOMABLE_BRUSH|GROOMABLE_COMB))
visible_message(SPAN_NOTICE(tool.replace_message_tokens((user == src) ? tool.message_target_self_generic : tool.message_target_other_generic, user, src, tool)))
add_stressor(/datum/stressor/well_groomed, 5 MINUTES)
return TRUE
return FALSE

2 changes: 1 addition & 1 deletion code/modules/mob/mob.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1399,7 +1399,7 @@
return

/mob/proc/get_target_zone()
return zone_sel?.selecting
return zone_sel?.selecting || BP_CHEST

/mob/proc/get_default_temperature_threshold(threshold)
switch(threshold)
Expand Down
18 changes: 18 additions & 0 deletions code/modules/organs/external/_external.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1593,3 +1593,21 @@ Note that amputating the affected organ does in fact remove the infection from t
vital_to_owner = TRUE
break
return vital_to_owner

/obj/item/organ/external/proc/get_grooming_results(obj/item/grooming/tool)

for(var/accessory_category in _sprite_accessories)
var/list/draw_accessories = _sprite_accessories[accessory_category]
for(var/accessory in draw_accessories)
var/decl/sprite_accessory/accessory_decl = resolve_accessory_to_decl(accessory)
var/grooming_result = accessory_decl.can_be_groomed_with(src, tool)
. = list(
"success" = grooming_result,
"descriptor" = accessory_decl.get_grooming_descriptor(grooming_result, src, tool)
)
if(grooming_result != GROOMING_RESULT_FAILED)
return

var/default_results = bodytype.get_default_grooming_results(src, tool)
if(default_results)
. = default_results
Loading

0 comments on commit b39d052

Please sign in to comment.