diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index dbacbcbda59c..295f8f9a9066 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -1,4 +1,5 @@
#define SIGNAL_TRAIT(trait_ref) "trait [trait_ref]"
+#define SIGNAL_REMOVETRAIT(trait_ref) "removetrait [trait_ref]"
// trait accessor defines
#define ADD_TRAIT(target, trait, source) \
@@ -59,6 +60,30 @@
};\
}\
} while (0)
+
+#define REMOVE_TRAITS_IN(target, sources) \
+ do { \
+ var/list/_L = target.status_traits; \
+ var/list/_S = sources; \
+ if (sources && !islist(sources)) { \
+ _S = list(sources); \
+ } else { \
+ _S = sources\
+ }; \
+ if (_L) { \
+ for (var/_T in _L) { \
+ _L[_T] -= _S;\
+ if (!length(_L[_T])) { \
+ _L -= _T; \
+ SEND_SIGNAL(target, SIGNAL_REMOVETRAIT(_T)); \
+ }; \
+ };\
+ if (!length(_L)) { \
+ target.status_traits = null\
+ };\
+ }\
+ } while (0)
+
#define HAS_TRAIT(target, trait) (target.status_traits ? (target.status_traits[trait] ? TRUE : FALSE) : FALSE)
#define HAS_TRAIT_FROM(target, trait, source) (target.status_traits ? (target.status_traits[trait] ? (source in target.status_traits[trait]) : FALSE) : FALSE)
#define HAS_TRAIT_FROM_ONLY(target, trait, source) (\
@@ -236,6 +261,12 @@
#define TRAIT_NO_STAMINA_REGENERATION "block_stamina_regen" /// Prevents stamina regeneration
#define TRAIT_ARMOR_BROKEN "armor_broken" //acts as if you are wearing no clothing when taking damage, does not affect non-clothing sources of protection
#define TRAIT_IWASBATONED "iwasbatoned" //some dastardly fellow has struck you with a baton and thought to use another to strike you again, the rogue
+//Given by social anxiety quirk
+#define TRAIT_ANXIOUS "anxious"
+/// Trait granted by lipstick
+#define LIPSTICK_TRAIT "lipstick_trait"
+/// Blowing kisses that actually do damage to the victim
+#define TRAIT_KISS_OF_DEATH "kiss_of_death"
/// forces update_density to make us not dense
#define TRAIT_LIVING_NO_DENSITY "living_no_density"
/// forces us to not render our overlays
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 709d558a4203..09a3d052c045 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -132,6 +132,8 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_SNOWSTORM_IMMUNE" = TRAIT_SNOWSTORM_IMMUNE,
"TRAIT_VOIDSTORM_IMMUNE" = TRAIT_VOIDSTORM_IMMUNE,
"TRAIT_WEATHER_IMMUNE" = TRAIT_WEATHER_IMMUNE,
+ "TRAIT_KISS_OF_DEATH" = TRAIT_KISS_OF_DEATH,
+ "TRAIT_ANXIOUS" = TRAIT_ANXIOUS,
"TRAIT_SPACEWALK" = TRAIT_SPACEWALK,
"TRAIT_PRIMITIVE" = TRAIT_PRIMITIVE, //unable to use mechs. Given to Ash Walkers
),
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index 8dd6d80a9230..475715212ae2 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -225,3 +225,8 @@
description = "The gods are pleased with this offering!\n"
mood_change = 5
timeout = 3 MINUTES
+
+/datum/mood_event/kiss
+ description = span_nicegreen("Someone blew a kiss at me, I must be a real catch!\n")
+ mood_change = 1.5
+ timeout = 2 MINUTES
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
index a30e27c8f8b1..20cda350d69f 100644
--- a/code/datums/traits/negative.dm
+++ b/code/datums/traits/negative.dm
@@ -43,11 +43,11 @@
GLOBAL_LIST_EMPTY(family_heirlooms)
-/datum/quirk/family_heirloom/on_spawn()
+/datum/quirk/family_heirloom/on_spawn()
// Define holder and type
var/mob/living/carbon/human/human_holder = quirk_holder
var/obj/item/heirloom_type
-
+
// The quirk holder's species - we have a 50% chance, if we have a species with a set heirloom, to choose a species heirloom.
var/datum/species/holder_species = human_holder.dna?.species
if(holder_species && LAZYLEN(holder_species.family_heirlooms) && prob(50))
@@ -61,13 +61,13 @@ GLOBAL_LIST_EMPTY(family_heirlooms)
// If we didn't find an heirloom somehow, throw them a generic one
if(!heirloom_type)
heirloom_type = pick(/obj/item/toy/cards/deck, /obj/item/lighter, /obj/item/dice/d20)
-
+
// Create the heirloom item
heirloom = new heirloom_type(get_turf(quirk_holder))
-
+
// Add to global list
GLOB.family_heirlooms += heirloom
-
+
// Determine and assign item location
var/list/slots = list(
"in your left pocket" = ITEM_SLOT_LPOCKET,
@@ -330,6 +330,7 @@ GLOBAL_LIST_EMPTY(family_heirlooms)
gain_text = "You start worrying about what you're saying."
lose_text = "You feel easier about talking again." //if only it were that easy!
medical_record_text = "Patient is usually anxious in social encounters and prefers to avoid them."
+ mob_trait = TRAIT_ANXIOUS
var/dumb_thing = TRUE
processing_quirk = TRUE
diff --git a/code/game/objects/items/cosmetics.dm b/code/game/objects/items/cosmetics.dm
index b9a38528628b..e808d3486f0f 100644
--- a/code/game/objects/items/cosmetics.dm
+++ b/code/game/objects/items/cosmetics.dm
@@ -7,6 +7,8 @@
w_class = WEIGHT_CLASS_TINY
var/colour = "red"
var/open = FALSE
+ /// A trait that's applied while someone has this lipstick applied, and is removed when the lipstick is removed
+ var/lipstick_trait
/obj/item/lipstick/purple
name = "purple lipstick"
@@ -21,6 +23,10 @@
name = "black lipstick"
colour = "black"
+/obj/item/lipstick/black/death
+ name = "Kiss of Death"
+ lipstick_trait = TRAIT_KISS_OF_DEATH
+
/obj/item/lipstick/random
name = "lipstick"
icon_state = "random_lipstick"
@@ -33,7 +39,7 @@
/obj/item/lipstick/attack_self(mob/user)
cut_overlays()
- to_chat(user, "You twist \the [src] [open ? "closed" : "open"].")
+ to_chat(user, span_notice("You twist \the [src] [open ? "closed" : "open"]."))
open = !open
if(open)
var/mutable_appearance/colored_overlay = mutable_appearance(icon, "lipstick_uncap_color")
@@ -44,60 +50,53 @@
icon_state = "lipstick"
/obj/item/lipstick/attack(mob/M, mob/user)
- if(!open)
+ if(!open || !ismob(M))
return
- if(!ismob(M))
+ if(!ishuman(M))
+ to_chat(user, span_warning("Where are the lips on that?"))
return
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(H.is_mouth_covered())
- to_chat(user, "Remove [ H == user ? "your" : "[H.p_their()]" ] mask!")
- return
- if(H.lip_style) //if they already have lipstick on
- to_chat(user, "You need to wipe off the old lipstick first!")
- return
- if(H == user)
- user.visible_message("[user] does [user.p_their()] lips with \the [src].", \
- "You take a moment to apply \the [src]. Perfect!")
- H.lip_style = "lipstick"
- H.lip_color = colour
- H.update_body()
- else
- user.visible_message("[user] begins to do [H]'s lips with \the [src].", \
- "You begin to apply \the [src] on [H]'s lips...")
- if(do_after(user, 20, target = H))
- user.visible_message("[user] does [H]'s lips with \the [src].", \
- "You apply \the [src] on [H]'s lips.")
- H.lip_style = "lipstick"
- H.lip_color = colour
- H.update_body()
- else
- to_chat(user, "Where are the lips on that?")
+ var/mob/living/carbon/human/target = M
+ if(target.is_mouth_covered())
+ to_chat(user, span_warning("Remove [ target == user ? "your" : "[target.p_their()]" ] mask!"))
+ return
+ if(target.lip_style) //if they already have lipstick on
+ to_chat(user, span_warning("You need to wipe off the old lipstick first!"))
+ return
+
+ if(target == user)
+ user.visible_message(span_notice("[user] does [user.p_their()] lips with \the [src]."), \
+ span_notice("You take a moment to apply \the [src]. Perfect!"))
+ target.update_lips("lipstick", colour, lipstick_trait)
+ return
+
+ user.visible_message(span_warning("[user] begins to do [target]'s lips with \the [src]."), \
+ span_notice("You begin to apply \the [src] on [target]'s lips..."))
+ if(!do_after(user, 2 SECONDS, target = target))
+ return
+ user.visible_message(span_notice("[user] does [target]'s lips with \the [src]."), \
+ span_notice("You apply \the [src] on [target]'s lips."))
+ target.update_lips("lipstick", colour, lipstick_trait)
//you can wipe off lipstick with paper!
/obj/item/paper/attack(mob/M, mob/user)
- if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH)
- if(!ismob(M))
- return
+ if(user.zone_selected != BODY_ZONE_PRECISE_MOUTH || !ishuman(M))
+ return ..()
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(H == user)
- to_chat(user, "You wipe off the lipstick with [src].")
- H.lip_style = null
- H.update_body()
- else
- user.visible_message("[user] begins to wipe [H]'s lipstick off with \the [src].", \
- "You begin to wipe off [H]'s lipstick...")
- if(do_after(user, 10, target = H))
- user.visible_message("[user] wipes [H]'s lipstick off with \the [src].", \
- "You wipe off [H]'s lipstick.")
- H.lip_style = null
- H.update_body()
- else
- ..()
+ var/mob/living/carbon/human/target = M
+ if(target == user)
+ to_chat(user, span_notice("You wipe off the lipstick with [src]."))
+ target.clean_lips()
+ return
+
+ user.visible_message(span_warning("[user] begins to wipe [target]'s lipstick off with \the [src]."), \
+ span_notice("You begin to wipe off [target]'s lipstick..."))
+ if(!do_after(user, 10, target = target))
+ return
+ user.visible_message(span_notice("[user] wipes [target]'s lipstick off with \the [src]."), \
+ span_notice("You wipe off [target]'s lipstick."))
+ target.clean_lips()
/obj/item/razor
name = "electric razor"
diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm
new file mode 100644
index 000000000000..e3a3d7257950
--- /dev/null
+++ b/code/game/objects/items/hand_items.dm
@@ -0,0 +1,368 @@
+/obj/item/hand_item
+ item_flags = DROPDEL | ABSTRACT | HAND_ITEM
+
+
+/obj/item/hand_item/circlegame
+ name = "circled hand"
+ desc = "If somebody looks at this while it's below your waist, you get to bop them."
+ icon_state = "madeyoulook"
+ force = 0
+ throwforce = 0
+ attack_verb = list("bopped")
+
+/obj/item/hand_item/circlegame/Initialize(mapload)
+ . = ..()
+ var/mob/living/owner = loc
+ if(!istype(owner))
+ return
+ RegisterSignal(owner, COMSIG_PARENT_EXAMINE, .proc/ownerExamined)
+
+/obj/item/hand_item/circlegame/Destroy()
+ var/mob/owner = loc
+ if(istype(owner))
+ UnregisterSignal(owner, COMSIG_PARENT_EXAMINE)
+ return ..()
+
+/obj/item/hand_item/circlegame/dropped(mob/user)
+ UnregisterSignal(user, COMSIG_PARENT_EXAMINE) //loc will have changed by the time this is called, so Destroy() can't catch it
+ // this is a dropdel item.
+ return ..()
+
+/// Stage 1: The mistake is made
+/obj/item/hand_item/circlegame/proc/ownerExamined(mob/living/owner, mob/living/sucker)
+ SIGNAL_HANDLER
+
+ if(!istype(sucker) || !in_range(owner, sucker))
+ return
+ addtimer(CALLBACK(src, .proc/waitASecond, owner, sucker), 4)
+
+/// Stage 2: Fear sets in
+/obj/item/hand_item/circlegame/proc/waitASecond(mob/living/owner, mob/living/sucker)
+ if(QDELETED(sucker) || QDELETED(src) || QDELETED(owner))
+ return
+
+ if(owner == sucker) // big mood
+ to_chat(owner, "Wait a second... you just looked at your own [src.name]!")
+ addtimer(CALLBACK(src, .proc/selfGottem, owner), 10)
+ else
+ to_chat(sucker, "Wait a second... was that a-")
+ addtimer(CALLBACK(src, .proc/GOTTEM, owner, sucker), 6)
+
+/// Stage 3A: We face our own failures
+/obj/item/hand_item/circlegame/proc/selfGottem(mob/living/owner)
+ if(QDELETED(src) || QDELETED(owner))
+ return
+
+ playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1)
+ owner.visible_message("[owner] shamefully bops [owner.p_them()]self with [owner.p_their()] [src.name].", "You shamefully bop yourself with your [src.name].", \
+ "You hear a dull thud!")
+ log_combat(owner, owner, "bopped", src.name, "(self)")
+ owner.do_attack_animation(owner)
+ owner.apply_damage(100, STAMINA)
+ owner.Knockdown(10)
+ qdel(src)
+
+/// Stage 3B: We face our reckoning (unless we moved away or they're incapacitated)
+/obj/item/hand_item/circlegame/proc/GOTTEM(mob/living/owner, mob/living/sucker)
+ if(QDELETED(sucker))
+ return
+
+ if(QDELETED(src) || QDELETED(owner))
+ to_chat(sucker, "Nevermind... must've been your imagination...")
+ return
+
+ if(!in_range(owner, sucker) || !(owner.mobility_flags & MOBILITY_USE))
+ to_chat(sucker, "Phew... you moved away before [owner] noticed you saw [owner.p_their()] [src.name]...")
+ return
+
+ to_chat(owner, "[sucker] looks down at your [src.name] before trying to avert [sucker.p_their()] eyes, but it's too late!")
+ to_chat(sucker, "[owner] sees the fear in your eyes as you try to look away from [owner.p_their()] [src.name]!")
+
+ owner.face_atom(sucker)
+ if(owner.client)
+ owner.client.give_award(/datum/award/achievement/misc/gottem, owner) // then everybody clapped
+
+ playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1)
+ owner.do_attack_animation(sucker)
+
+ if(HAS_TRAIT(owner, TRAIT_HULK))
+ owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name] much harder than intended, sending [sucker.p_them()] flying!", \
+ "You bop [sucker] with your [src.name] much harder than intended, sending [sucker.p_them()] flying!", "You hear a sickening sound of flesh hitting flesh!", ignored_mobs=list(sucker))
+ to_chat(sucker, "[owner] bops you incredibly hard with [owner.p_their()] [src.name], sending you flying!")
+ sucker.apply_damage(50, STAMINA)
+ sucker.Knockdown(50)
+ log_combat(owner, sucker, "bopped", src.name, "(setup- Hulk)")
+ var/atom/throw_target = get_edge_target_turf(sucker, owner.dir)
+ sucker.throw_at(throw_target, 6, 3, owner)
+ else
+ owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name]!", "You bop [sucker] with your [src.name]!", \
+ "You hear a dull thud!", ignored_mobs=list(sucker))
+ sucker.apply_damage(15, STAMINA)
+ log_combat(owner, sucker, "bopped", src.name, "(setup)")
+ to_chat(sucker, "[owner] bops you with [owner.p_their()] [src.name]!")
+ qdel(src)
+
+/obj/item/hand_item/slapper
+ name = "slapper"
+ desc = "This is how real men fight."
+ icon_state = "latexballon"
+ item_state = "nothing"
+ force = 0
+ throwforce = 0
+ attack_verb = list("slapped")
+ hitsound = 'sound/effects/snap.ogg'
+
+/obj/item/hand_item/slapper/attack(mob/M, mob/living/carbon/human/user)
+ if(ishuman(M))
+ var/mob/living/carbon/human/L = M
+ if(L && L.dna && L.dna.species)
+ L.dna.species.stop_wagging_tail(M)
+ user.do_attack_animation(M)
+ playsound(M, 'sound/weapons/slap.ogg', 50, TRUE, -1)
+ user.visible_message("[user] slaps [M]!",
+ "You slap [M]!",\
+ "You hear a slap.")
+ return
+
+/obj/item/hand_item/slapper/on_offered(mob/living/carbon/offerer)
+ . = TRUE
+
+ if(!(locate(/mob/living/carbon) in orange(1, offerer)))
+ visible_message(span_danger("[offerer] raises [offerer.p_their()] arm, looking around for a high-five, but there's no one around!"), \
+ span_warning("You post up, looking for a high-five, but finding no one within range!"), null, 2)
+ return
+
+ offerer.visible_message(span_notice("[offerer] raises [offerer.p_their()] arm, looking for a high-five!"), \
+ span_notice("You post up, looking for a high-five!"), null, 2)
+ offerer.apply_status_effect(STATUS_EFFECT_OFFERING, src, /atom/movable/screen/alert/give/highfive)
+
+/// Yeah broh! This is where we do the high-fiving (or high-tenning :o)
+/obj/item/hand_item/slapper/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
+ . = TRUE
+
+ var/open_hands_taker
+ var/slappers_giver
+ for(var/i in taker.held_items) // see how many hands the taker has open for high'ing
+ if(isnull(i))
+ open_hands_taker++
+
+ if(!open_hands_taker)
+ to_chat(taker, span_warning("You can't high-five [offerer] with no open hands!"))
+ SEND_SIGNAL(taker, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_five_full_hand) // not so successful now!
+ return
+
+ for(var/i in offerer.held_items)
+ var/obj/item/hand_item/slapper/slap_check = i
+ if(istype(slap_check))
+ slappers_giver++
+
+ if(slappers_giver >= 2) // we only check this if it's already established the taker has 2+ hands free
+ offerer.visible_message(span_notice("[taker] enthusiastically high-tens [offerer]!"), span_nicegreen("Wow! You're high-tenned [taker]!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), ignored_mobs=taker)
+ to_chat(taker, span_nicegreen("You give high-tenning [offerer] your all!"))
+ playsound(offerer, 'sound/weapons/slap.ogg', 100, TRUE, 1)
+ SEND_SIGNAL(offerer, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_ten)
+ SEND_SIGNAL(taker, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_ten)
+ else
+ offerer.visible_message(span_notice("[taker] high-fives [offerer]!"), span_nicegreen("All right! You're high-fived by [taker]!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), ignored_mobs=taker)
+ to_chat(taker, span_nicegreen("You high-five [offerer]!"))
+ playsound(offerer, 'sound/weapons/slap.ogg', 50, TRUE, -1)
+ SEND_SIGNAL(offerer, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_five)
+ SEND_SIGNAL(taker, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_five)
+ qdel(src)
+
+/// Gangster secret handshakes.
+/obj/item/hand_item/slapper/secret_handshake
+ name = "Secret Handshake"
+ icon_state = "recruit"
+ icon = 'icons/obj/gang/actions.dmi'
+ /// References the active families gamemode handler (if one exists), for adding new family members to.
+ var/datum/gang_handler/handler
+ /// The typepath of the gang antagonist datum that the person who uses the package should have added to them -- remember that the distinction between e.g. Ballas and Grove Street is on the antag datum level, not the team datum level.
+ var/gang_to_use
+ /// The team datum that the person who uses this package should be added to.
+ var/datum/team/gang/team_to_use
+
+/// Adds the user to the family that this package corresponds to, dispenses the free_clothes of that family, and adds them to the handler if it exists.
+/obj/item/hand_item/slapper/secret_handshake/proc/add_to_gang(mob/living/user, original_name)
+ var/datum/antagonist/gang/swappin_sides = new gang_to_use()
+ swappin_sides.original_name = original_name
+ swappin_sides.handler = handler
+ user.mind.add_antag_datum(swappin_sides, team_to_use)
+ var/policy = get_policy(ROLE_FAMILIES)
+ if(policy)
+ to_chat(user, policy)
+ team_to_use.add_member(user.mind)
+ swappin_sides.equip_gangster_in_inventory()
+ if (!isnull(handler) && !handler.gangbangers.Find(user.mind)) // if we have a handler and they're not tracked by it
+ handler.gangbangers += user.mind
+
+/// Checks if the user is trying to use the package of the family they are in, and if not, adds them to the family, with some differing processing depending on whether the user is already a family member.
+/obj/item/hand_item/slapper/secret_handshake/proc/attempt_join_gang(mob/living/user)
+ if(!user?.mind)
+ return
+ var/datum/antagonist/gang/is_gangster = user.mind.has_antag_datum(/datum/antagonist/gang)
+ var/real_name_backup = user.real_name
+ if(is_gangster)
+ if(is_gangster.my_gang == team_to_use)
+ return
+ real_name_backup = is_gangster.original_name
+ is_gangster.my_gang.remove_member(user.mind)
+ user.mind.remove_antag_datum(/datum/antagonist/gang)
+ add_to_gang(user, real_name_backup)
+
+/obj/item/hand_item/slapper/secret_handshake/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
+ . = TRUE
+ if (!(null in taker.held_items))
+ to_chat(taker, span_warning("You can't get taught the secret handshake if [offerer] has no free hands!"))
+ return
+
+ if(HAS_TRAIT(taker, TRAIT_MINDSHIELD))
+ to_chat(taker, "You attended a seminar on not signing up for a gang and are not interested.")
+ return
+
+ var/datum/antagonist/gang/is_gangster = taker.mind.has_antag_datum(/datum/antagonist/gang)
+ if(is_gangster?.starter_gangster)
+ if(is_gangster.my_gang == team_to_use)
+ to_chat(taker, "You started your family. You don't need to join it.")
+ return
+ to_chat(taker, "You started your family. You can't turn your back on it now.")
+ return
+
+ offerer.visible_message(span_notice("[taker] is taught the secret handshake by [offerer]!"), span_nicegreen("All right! You've taught the secret handshake to [taker]!"), span_hear("You hear a bunch of weird shuffling and flesh slapping sounds!"), ignored_mobs=taker)
+ to_chat(taker, span_nicegreen("You get taught the secret handshake by [offerer]!"))
+ var/datum/antagonist/gang/owner_gang_datum = offerer.mind.has_antag_datum(/datum/antagonist/gang)
+ handler = owner_gang_datum.handler
+ gang_to_use = owner_gang_datum.type
+ team_to_use = owner_gang_datum.my_gang
+ attempt_join_gang(taker)
+ qdel(src)
+
+/obj/item/hand_item/kisser
+ name = "kiss"
+ desc = "I want you all to know, everyone and anyone, to seal it with a kiss."
+ icon = 'icons/mob/animal.dmi'
+ icon_state = "heart"
+ /// The kind of projectile this version of the kiss blower fires
+ var/kiss_type = /obj/item/projectile/kiss
+ /// TRUE if the user was aiming anywhere but the mouth when they offer the kiss, if it's offered
+ var/cheek_kiss
+
+/obj/item/hand_item/kisser/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ // . |= AFTERATTACK_PROCESSED_ITEM
+ /*
+ if(HAS_TRAIT(user, TRAIT_GARLIC_BREATH))
+ kiss_type = /obj/projectile/kiss/french
+
+ if(HAS_TRAIT(user, TRAIT_CHEF_KISS))
+ kiss_type = /obj/projectile/kiss/chef
+ */
+
+ var/obj/item/projectile/blown_kiss = new kiss_type(get_turf(user))
+ user.visible_message("[user] blows \a [blown_kiss] at [target]!", span_notice("You blow \a [blown_kiss] at [target]!"))
+
+ //Shooting Code:
+ blown_kiss.original = target
+ blown_kiss.fired_from = user
+ blown_kiss.pixels_per_second = TILES_TO_PIXELS(7) // Speed of blown kiss
+ blown_kiss.firer = user // don't hit ourself that would be really annoying
+ blown_kiss.impacted = list(user = TRUE) // just to make sure we don't hit the wearer
+ blown_kiss.preparePixelProjectile(target, user)
+ blown_kiss.fire()
+ qdel(src)
+
+/obj/item/hand_item/kisser/death
+ name = "kiss of death"
+ desc = "If looks could kill, they'd be this."
+ color = COLOR_BLACK
+ kiss_type = /obj/item/projectile/kiss/death
+
+/obj/item/projectile/kiss
+ name = "kiss"
+ icon = 'icons/mob/animal.dmi'
+ icon_state = "heart"
+ hitsound = 'sound/effects/kiss.ogg'
+ hitsound_wall = 'sound/effects/kiss.ogg'
+ pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE
+ damage_type = BRUTE
+ damage = 0
+ nodamage = TRUE // love can't actually hurt you
+ armour_penetration = 100 // but if it could, it would cut through even the thickest plate
+ flag = MAGIC // and most importantly, love is magic~
+
+/obj/item/projectile/kiss/fire(angle, atom/direct_target)
+ if(firer)
+ name = "[name] blown by [firer]"
+ return ..()
+
+/obj/item/projectile/kiss/Impact(atom/A)
+ if(damage > 0 || !isliving(A)) // if we do damage or we hit a nonliving thing, we don't have to worry about a harmless hit because we can't wrongly do damage anyway
+ return ..()
+
+ harmless_on_hit(A)
+ qdel(src)
+ return FALSE
+
+/**
+ * To get around shielded modsuits & such being set off by kisses when they shouldn't, we take a page from hallucination projectiles
+ * and simply fake our on hit effects. This lets kisses remain incorporeal without having to make some new trait for this one niche situation.
+ * This fake hit only happens if we can deal damage and if we hit a living thing. Otherwise, we just do normal on hit effects.
+ */
+/obj/item/projectile/kiss/proc/harmless_on_hit(mob/living/living_target)
+ playsound(get_turf(living_target), hitsound, 100, TRUE)
+ if(!suppressed) // direct
+ living_target.visible_message(span_danger("[living_target] is hit by \a [src]."), span_userdanger("You're hit by \a [src]!"), vision_distance=COMBAT_MESSAGE_RANGE)
+
+ /*
+ living_target.add_mob_memory(/datum/memory/kissed, deuteragonist = firer)
+ living_target.add_mood_event("kiss", /datum/mood_event/kiss, firer, suppressed)
+ if(isliving(firer))
+ var/mob/living/kisser = firer
+ kisser.add_mob_memory(/datum/memory/kissed, protagonist = living_target, deuteragonist = firer) */
+
+ try_fluster(living_target)
+
+/obj/item/projectile/kiss/proc/try_fluster(mob/living/living_target)
+ // people with the social anxiety quirk can get flustered when hit by a kiss
+ if(!HAS_TRAIT(living_target, TRAIT_ANXIOUS) || (living_target.stat > SOFT_CRIT) || living_target.is_blind())
+ return
+
+ if(HAS_TRAIT(living_target, TRAIT_FEARLESS) || prob(50)) // 50% chance for it to apply, also immune while on meds
+ return
+
+ var/other_msg
+ var/self_msg
+ var/roll = rand(1, 3)
+ switch(roll)
+ if(1)
+ other_msg = "stumbles slightly, turning a bright red!"
+ self_msg = "You lose control of your limbs for a moment as your blood rushes to your face, turning it bright red!"
+ living_target.confused += (rand(5, 10))
+ if(2)
+ other_msg = "stammers softly for a moment before choking on something!"
+ self_msg = "You feel your tongue disappear down your throat as you fight to remember how to make words!"
+ addtimer(CALLBACK(living_target, /atom/movable.proc/say, pick("Uhhh...", "O-oh, uhm...", "I- uhhhhh??", "What?")), rand(0.5 SECONDS, 1.5 SECONDS))
+ living_target.stuttering += rand(5, 15)
+ if(3)
+ other_msg = "locks up with a stunned look on [living_target.p_their()] face, staring at [firer ? firer : "the ceiling"]!"
+ self_msg = "Your brain completely fails to process what just happened, leaving you rooted in place staring [firer ? "at [firer]" : "the ceiling"] for what feels like an eternity!"
+ living_target.face_atom(firer)
+ living_target.Stun(rand(1 SECONDS, 3 SECONDS))
+
+ living_target.visible_message(span_notice("[living_target] [other_msg]"), span_userdanger("Whoa! [self_msg]"))
+
+/obj/item/projectile/kiss/death
+ name = "kiss of death"
+ nodamage = FALSE // okay i kinda lied about love not being able to hurt you
+ damage = 15
+ wound_bonus = 0
+ sharpness = SHARP_POINTY
+ color = COLOR_BLACK
+
+/obj/item/projectile/kiss/death/on_hit(atom/target, blocked, pierce_hit)
+ . = ..()
+ if(!iscarbon(target))
+ return
+ var/mob/living/carbon/heartbreakee = target
+ var/obj/item/organ/heart/dont_go_breakin_my_heart = heartbreakee.getorganslot(ORGAN_SLOT_HEART)
+ dont_go_breakin_my_heart.applyOrganDamage(15)
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 636190b548dc..e4c71ee6ffe9 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -1117,248 +1117,11 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
else
qdel(target)
-/obj/item/circlegame
- name = "circled hand"
- desc = "If somebody looks at this while it's below your waist, you get to bop them."
- icon_state = "madeyoulook"
- force = 0
- throwforce = 0
- item_flags = DROPDEL | ABSTRACT | HAND_ITEM
- attack_verb = list("bopped")
-
-/obj/item/circlegame/Initialize(mapload)
- . = ..()
- var/mob/living/owner = loc
- if(!istype(owner))
- return
- RegisterSignal(owner, COMSIG_PARENT_EXAMINE, .proc/ownerExamined)
-
-/obj/item/circlegame/Destroy()
- var/mob/owner = loc
- if(istype(owner))
- UnregisterSignal(owner, COMSIG_PARENT_EXAMINE)
- return ..()
-
-/obj/item/circlegame/dropped(mob/user)
- UnregisterSignal(user, COMSIG_PARENT_EXAMINE) //loc will have changed by the time this is called, so Destroy() can't catch it
- // this is a dropdel item.
- return ..()
-
-/// Stage 1: The mistake is made
-/obj/item/circlegame/proc/ownerExamined(mob/living/owner, mob/living/sucker)
- SIGNAL_HANDLER
-
- if(!istype(sucker) || !in_range(owner, sucker))
- return
- addtimer(CALLBACK(src, .proc/waitASecond, owner, sucker), 4)
-
-/// Stage 2: Fear sets in
-/obj/item/circlegame/proc/waitASecond(mob/living/owner, mob/living/sucker)
- if(QDELETED(sucker) || QDELETED(src) || QDELETED(owner))
- return
-
- if(owner == sucker) // big mood
- to_chat(owner, "Wait a second... you just looked at your own [src.name]!")
- addtimer(CALLBACK(src, .proc/selfGottem, owner), 10)
- else
- to_chat(sucker, "Wait a second... was that a-")
- addtimer(CALLBACK(src, .proc/GOTTEM, owner, sucker), 6)
-
-/// Stage 3A: We face our own failures
-/obj/item/circlegame/proc/selfGottem(mob/living/owner)
- if(QDELETED(src) || QDELETED(owner))
- return
-
- playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1)
- owner.visible_message("[owner] shamefully bops [owner.p_them()]self with [owner.p_their()] [src.name].", "You shamefully bop yourself with your [src.name].", \
- "You hear a dull thud!")
- log_combat(owner, owner, "bopped", src.name, "(self)")
- owner.do_attack_animation(owner)
- owner.apply_damage(100, STAMINA)
- owner.Knockdown(10)
- qdel(src)
-
-/// Stage 3B: We face our reckoning (unless we moved away or they're incapacitated)
-/obj/item/circlegame/proc/GOTTEM(mob/living/owner, mob/living/sucker)
- if(QDELETED(sucker))
- return
-
- if(QDELETED(src) || QDELETED(owner))
- to_chat(sucker, "Nevermind... must've been your imagination...")
- return
-
- if(!in_range(owner, sucker) || !(owner.mobility_flags & MOBILITY_USE))
- to_chat(sucker, "Phew... you moved away before [owner] noticed you saw [owner.p_their()] [src.name]...")
- return
-
- to_chat(owner, "[sucker] looks down at your [src.name] before trying to avert [sucker.p_their()] eyes, but it's too late!")
- to_chat(sucker, "[owner] sees the fear in your eyes as you try to look away from [owner.p_their()] [src.name]!")
-
- owner.face_atom(sucker)
- if(owner.client)
- owner.client.give_award(/datum/award/achievement/misc/gottem, owner) // then everybody clapped
-
- playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1)
- owner.do_attack_animation(sucker)
-
- if(HAS_TRAIT(owner, TRAIT_HULK))
- owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name] much harder than intended, sending [sucker.p_them()] flying!", \
- "You bop [sucker] with your [src.name] much harder than intended, sending [sucker.p_them()] flying!", "You hear a sickening sound of flesh hitting flesh!", ignored_mobs=list(sucker))
- to_chat(sucker, "[owner] bops you incredibly hard with [owner.p_their()] [src.name], sending you flying!")
- sucker.apply_damage(50, STAMINA)
- sucker.Knockdown(50)
- log_combat(owner, sucker, "bopped", src.name, "(setup- Hulk)")
- var/atom/throw_target = get_edge_target_turf(sucker, owner.dir)
- sucker.throw_at(throw_target, 6, 3, owner)
- else
- owner.visible_message("[owner] bops [sucker] with [owner.p_their()] [src.name]!", "You bop [sucker] with your [src.name]!", \
- "You hear a dull thud!", ignored_mobs=list(sucker))
- sucker.apply_damage(15, STAMINA)
- log_combat(owner, sucker, "bopped", src.name, "(setup)")
- to_chat(sucker, "[owner] bops you with [owner.p_their()] [src.name]!")
- qdel(src)
-
-/obj/item/slapper
- name = "slapper"
- desc = "This is how real men fight."
- icon_state = "latexballon"
- item_state = "nothing"
- force = 0
- throwforce = 0
- item_flags = DROPDEL | ABSTRACT | HAND_ITEM
- attack_verb = list("slapped")
- hitsound = 'sound/effects/snap.ogg'
-
-/obj/item/slapper/attack(mob/M, mob/living/carbon/human/user)
- if(ishuman(M))
- var/mob/living/carbon/human/L = M
- if(L && L.dna && L.dna.species)
- L.dna.species.stop_wagging_tail(M)
- user.do_attack_animation(M)
- playsound(M, 'sound/weapons/slap.ogg', 50, TRUE, -1)
- user.visible_message("[user] slaps [M]!",
- "You slap [M]!",\
- "You hear a slap.")
- return
/obj/item/proc/can_trigger_gun(mob/living/user)
if(!user.can_use_guns(src))
return FALSE
return TRUE
-/obj/item/slapper/on_offered(mob/living/carbon/offerer)
- . = TRUE
-
- if(!(locate(/mob/living/carbon) in orange(1, offerer)))
- visible_message(span_danger("[offerer] raises [offerer.p_their()] arm, looking around for a high-five, but there's no one around!"), \
- span_warning("You post up, looking for a high-five, but finding no one within range!"), null, 2)
- return
-
- offerer.visible_message(span_notice("[offerer] raises [offerer.p_their()] arm, looking for a high-five!"), \
- span_notice("You post up, looking for a high-five!"), null, 2)
- offerer.apply_status_effect(STATUS_EFFECT_OFFERING, src, /atom/movable/screen/alert/give/highfive)
-
-/// Yeah broh! This is where we do the high-fiving (or high-tenning :o)
-/obj/item/slapper/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
- . = TRUE
-
- var/open_hands_taker
- var/slappers_giver
- for(var/i in taker.held_items) // see how many hands the taker has open for high'ing
- if(isnull(i))
- open_hands_taker++
-
- if(!open_hands_taker)
- to_chat(taker, span_warning("You can't high-five [offerer] with no open hands!"))
- SEND_SIGNAL(taker, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_five_full_hand) // not so successful now!
- return
-
- for(var/i in offerer.held_items)
- var/obj/item/slapper/slap_check = i
- if(istype(slap_check))
- slappers_giver++
-
- if(slappers_giver >= 2) // we only check this if it's already established the taker has 2+ hands free
- offerer.visible_message(span_notice("[taker] enthusiastically high-tens [offerer]!"), span_nicegreen("Wow! You're high-tenned [taker]!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), ignored_mobs=taker)
- to_chat(taker, span_nicegreen("You give high-tenning [offerer] your all!"))
- playsound(offerer, 'sound/weapons/slap.ogg', 100, TRUE, 1)
- SEND_SIGNAL(offerer, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_ten)
- SEND_SIGNAL(taker, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_ten)
- else
- offerer.visible_message(span_notice("[taker] high-fives [offerer]!"), span_nicegreen("All right! You're high-fived by [taker]!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), ignored_mobs=taker)
- to_chat(taker, span_nicegreen("You high-five [offerer]!"))
- playsound(offerer, 'sound/weapons/slap.ogg', 50, TRUE, -1)
- SEND_SIGNAL(offerer, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_five)
- SEND_SIGNAL(taker, COMSIG_ADD_MOOD_EVENT, "high_five", /datum/mood_event/high_five)
- qdel(src)
-
-/// Gangster secret handshakes.
-/obj/item/slapper/secret_handshake
- name = "Secret Handshake"
- icon_state = "recruit"
- icon = 'icons/obj/gang/actions.dmi'
- /// References the active families gamemode handler (if one exists), for adding new family members to.
- var/datum/gang_handler/handler
- /// The typepath of the gang antagonist datum that the person who uses the package should have added to them -- remember that the distinction between e.g. Ballas and Grove Street is on the antag datum level, not the team datum level.
- var/gang_to_use
- /// The team datum that the person who uses this package should be added to.
- var/datum/team/gang/team_to_use
-
-
-/// Adds the user to the family that this package corresponds to, dispenses the free_clothes of that family, and adds them to the handler if it exists.
-/obj/item/slapper/secret_handshake/proc/add_to_gang(mob/living/user, original_name)
- var/datum/antagonist/gang/swappin_sides = new gang_to_use()
- swappin_sides.original_name = original_name
- swappin_sides.handler = handler
- user.mind.add_antag_datum(swappin_sides, team_to_use)
- var/policy = get_policy(ROLE_FAMILIES)
- if(policy)
- to_chat(user, policy)
- team_to_use.add_member(user.mind)
- swappin_sides.equip_gangster_in_inventory()
- if (!isnull(handler) && !handler.gangbangers.Find(user.mind)) // if we have a handler and they're not tracked by it
- handler.gangbangers += user.mind
-
-/// Checks if the user is trying to use the package of the family they are in, and if not, adds them to the family, with some differing processing depending on whether the user is already a family member.
-/obj/item/slapper/secret_handshake/proc/attempt_join_gang(mob/living/user)
- if(!user?.mind)
- return
- var/datum/antagonist/gang/is_gangster = user.mind.has_antag_datum(/datum/antagonist/gang)
- var/real_name_backup = user.real_name
- if(is_gangster)
- if(is_gangster.my_gang == team_to_use)
- return
- real_name_backup = is_gangster.original_name
- is_gangster.my_gang.remove_member(user.mind)
- user.mind.remove_antag_datum(/datum/antagonist/gang)
- add_to_gang(user, real_name_backup)
-
-/obj/item/slapper/secret_handshake/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
- . = TRUE
- if (!(null in taker.held_items))
- to_chat(taker, span_warning("You can't get taught the secret handshake if [offerer] has no free hands!"))
- return
-
- if(HAS_TRAIT(taker, TRAIT_MINDSHIELD))
- to_chat(taker, "You attended a seminar on not signing up for a gang and are not interested.")
- return
-
- var/datum/antagonist/gang/is_gangster = taker.mind.has_antag_datum(/datum/antagonist/gang)
- if(is_gangster?.starter_gangster)
- if(is_gangster.my_gang == team_to_use)
- to_chat(taker, "You started your family. You don't need to join it.")
- return
- to_chat(taker, "You started your family. You can't turn your back on it now.")
- return
-
- offerer.visible_message(span_notice("[taker] is taught the secret handshake by [offerer]!"), span_nicegreen("All right! You've taught the secret handshake to [taker]!"), span_hear("You hear a bunch of weird shuffling and flesh slapping sounds!"), ignored_mobs=taker)
- to_chat(taker, span_nicegreen("You get taught the secret handshake by [offerer]!"))
- var/datum/antagonist/gang/owner_gang_datum = offerer.mind.has_antag_datum(/datum/antagonist/gang)
- handler = owner_gang_datum.handler
- gang_to_use = owner_gang_datum.type
- team_to_use = owner_gang_datum.my_gang
- attempt_join_gang(taker)
- qdel(src)
-
/obj/item/extendohand
name = "extendo-hand"
desc = "Futuristic tech has allowed these classic spring-boxing toys to essentially act as a fully functional hand-operated hand prosthetic."
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index d46a40d59857..7fc2bab6fd56 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -546,8 +546,7 @@
if(washing_face)
if(ishuman(user))
var/mob/living/carbon/human/H = user
- H.lip_style = null //Washes off lipstick
- H.lip_color = initial(H.lip_color)
+ H.clean_lips() //Washes off lipstick
H.wash_cream()
H.wash_cum() //sandstorm edit
H.regenerate_icons()
diff --git a/code/modules/antagonists/gang/gang.dm b/code/modules/antagonists/gang/gang.dm
index 050754a91e87..1dcb054153ac 100644
--- a/code/modules/antagonists/gang/gang.dm
+++ b/code/modules/antagonists/gang/gang.dm
@@ -239,7 +239,7 @@
if(H.stat)
return FALSE
- var/obj/item/slapper/secret_handshake/secret_handshake_item = new(owner)
+ var/obj/item/hand_item/slapper/secret_handshake/secret_handshake_item = new(owner)
if(owner.put_in_hands(secret_handshake_item))
to_chat(owner, span_notice("You ready your secret handshake."))
else
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 3f6779c03b18..da30304d2ed1 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -547,6 +547,32 @@
if(..())
dropItemToGround(I)
+/**
+ * Used to update the makeup on a human and apply/remove lipstick traits, then store/unstore them on the head object in case it gets severed
+ */
+/mob/living/carbon/human/proc/update_lips(new_style, new_colour, apply_trait)
+ lip_style = new_style
+ lip_color = new_colour
+ update_body()
+
+ var/obj/item/bodypart/head/hopefully_a_head = get_bodypart(BODY_ZONE_HEAD)
+ REMOVE_TRAITS_IN(src, LIPSTICK_TRAIT)
+ hopefully_a_head?.stored_lipstick_trait = null
+
+ if(new_style && apply_trait)
+ ADD_TRAIT(src, apply_trait, LIPSTICK_TRAIT)
+ hopefully_a_head?.stored_lipstick_trait = apply_trait
+
+/**
+ * A wrapper for [mob/living/carbon/human/proc/update_lips] that tells us if there were lip styles to change
+ */
+
+/mob/living/carbon/human/proc/clean_lips()
+ if(isnull(lip_style) && lip_color == initial(lip_color))
+ return FALSE
+ update_lips(null)
+ return TRUE
+
/mob/living/carbon/human/clean_blood()
var/mob/living/carbon/human/H = src
if(H.gloves)
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index c2aca1f76f19..cd67dd2ff49b 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -195,6 +195,26 @@
/datum/emote/living/kiss
key = "kiss"
key_third_person = "kisses"
+
+/datum/emote/living/kiss/run_emote(mob/living/user, params, type_override, intentional)
+ . = ..()
+ if(!.)
+ return
+ var/kiss_type = /obj/item/hand_item/kisser
+
+ if(HAS_TRAIT(user, TRAIT_KISS_OF_DEATH))
+ kiss_type = /obj/item/hand_item/kisser/death
+
+ var/obj/item/kiss_blower = new kiss_type(user)
+ if(user.put_in_hands(kiss_blower))
+ to_chat(user, span_notice("You ready your kiss-blowing hand."))
+ else
+ qdel(kiss_blower)
+ to_chat(user, span_warning("You're incapable of blowing a kiss in your current state."))
+
+/datum/emote/living/kiss2
+ key = "kiss2"
+ key_third_person = "kisses"
message = "blows a kiss."
message_param = "blows a kiss to %t."
emote_type = EMOTE_AUDIBLE
@@ -528,7 +548,7 @@
. = ..()
if(!.)
return
- var/obj/item/circlegame/N = new(user)
+ var/obj/item/hand_item/circlegame/N = new(user)
if(user.put_in_hands(N))
to_chat(user, "You make a circle with your hand.")
else
@@ -544,7 +564,7 @@
. = ..()
if(!.)
return
- var/obj/item/slapper/N = new(user)
+ var/obj/item/hand_item/slapper/N = new(user)
if(user.put_in_hands(N))
to_chat(user, "You ready your slapping hand.")
else
diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm
index 96b92902397b..35faec8bb324 100644
--- a/code/modules/surgery/bodyparts/dismemberment.dm
+++ b/code/modules/surgery/bodyparts/dismemberment.dm
@@ -392,8 +392,7 @@
H.hair_style = hair_style
H.facial_hair_color = facial_hair_color
H.facial_hair_style = facial_hair_style
- H.lip_style = lip_style
- H.lip_color = lip_color
+ H.update_lips(lip_style, lip_color, stored_lipstick_trait)
if(real_name)
C.real_name = real_name
real_name = ""
diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm
index ea4374009368..6b5c8b725f63 100644
--- a/code/modules/surgery/bodyparts/head.dm
+++ b/code/modules/surgery/bodyparts/head.dm
@@ -32,6 +32,9 @@
var/lip_style = null
var/lip_color = "white"
+
+ var/stored_lipstick_trait
+
//If the head is a special sprite
var/custom_head
@@ -78,6 +81,7 @@
hair_style = "Bald"
facial_hair_style = "Shaved"
lip_style = null
+ stored_lipstick_trait = null
else if(!animal_origin)
var/mob/living/carbon/human/H = C
diff --git a/modular_splurt/code/modules/mob/living/emotes.dm b/modular_splurt/code/modules/mob/living/emotes.dm
index 34e04f4f78a9..bb7100aa2e91 100644
--- a/modular_splurt/code/modules/mob/living/emotes.dm
+++ b/modular_splurt/code/modules/mob/living/emotes.dm
@@ -135,7 +135,7 @@
// Accepts all possible parameters
playsound(user.loc, emote_sound, emote_volume, emote_pitch_variance, emote_range, emote_falloff_exponent, emote_frequency, emote_channel, emote_check_pressure, emote_ignore_walls, emote_falloff_distance, emote_wetness, emote_dryness, emote_distance_multiplier, emote_distance_multiplier_min_range)
- // Set coodown
+ // Set cooldown
user.nextsoundemote = world.time + emote_cooldown
/datum/emote/living/surrender/run_emote(mob/user, params, type_override, intentional)
diff --git a/sound/effects/kiss.ogg b/sound/effects/kiss.ogg
new file mode 100644
index 000000000000..699b13a8de84
Binary files /dev/null and b/sound/effects/kiss.ogg differ
diff --git a/sound/weapons/attributions.txt b/sound/weapons/attributions.txt
index fe2911c87388..9106b7cfe266 100644
--- a/sound/weapons/attributions.txt
+++ b/sound/weapons/attributions.txt
@@ -6,4 +6,6 @@ No changes were made to the sounds, and all credit goes to kMoon.
batreflect sounds are by shadoWisp on freesound.org:
https://www.freesound.org/people/shadoWisp/sounds/252044/
-Small parts of the sound are cut out and used.
\ No newline at end of file
+Small parts of the sound are cut out and used.
+
+kiss sound: https://freesound.org/people/stereostereo/sounds/117355/
diff --git a/tgstation.dme b/tgstation.dme
index b2c1200404ec..e2f8290441bf 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1207,6 +1207,7 @@
#include "code\game\objects\items\fluff.dm"
#include "code\game\objects\items\gift.dm"
#include "code\game\objects\items\granters.dm"
+#include "code\game\objects\items\hand_items.dm"
#include "code\game\objects\items\handcuffs.dm"
#include "code\game\objects\items\his_grace.dm"
#include "code\game\objects\items\holosign_creator.dm"