diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index c7fbb102bdf88..eebbe74230285 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -242,6 +242,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_COAGULATING "coagulating" //from coagulant reagents, this doesn't affect the bleeding itself but does affect the bleed warning messages
/// The holder of this trait has antennae or whatever that hurt a ton when noogied
#define TRAIT_ANTENNAE "antennae"
+/// Blowing kisses actually does damage to the victim
+#define TRAIT_KISS_OF_DEATH "kiss_of_death"
#define TRAIT_NOBLEED "nobleed" //This carbon doesn't bleed
@@ -350,6 +352,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_BADTOUCH "bad_touch"
#define TRAIT_EXTROVERT "extrovert"
#define TRAIT_INTROVERT "introvert"
+#define TRAIT_ANXIOUS "anxious"
///Trait for dryable items
#define TRAIT_DRYABLE "trait_dryable"
///Trait for dried items
@@ -479,6 +482,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define ELEMENT_TRAIT "element_trait"
/// Trait granted by [/obj/item/clothing/head/helmet/space/hardsuit/berserker]
#define BERSERK_TRAIT "berserk_trait"
+/// Trait granted by lipstick
+#define LIPSTICK_TRAIT "lipstick_trait"
/**
* Trait granted by [/mob/living/carbon/Initialize] and
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 0e98cecae9568..2d0697ccaebf6 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -156,7 +156,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_SNOB" = TRAIT_SNOB,
"TRAIT_BALD" = TRAIT_BALD,
"TRAIT_BADTOUCH" = TRAIT_BADTOUCH,
- "TRAIT_NOBLEED" = TRAIT_NOBLEED
+ "TRAIT_NOBLEED" = TRAIT_NOBLEED,
+ "TRAIT_KISS_OF_DEATH" = TRAIT_KISS_OF_DEATH,
+ "TRAIT_ANXIOUS" = TRAIT_ANXIOUS,
),
/obj/item/bodypart = list(
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index bd65ccf29b3d6..b4f7427262e77 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -223,3 +223,12 @@
description = "Watching fish in aquarium is calming.\n"
mood_change = 3
timeout = 1.5 MINUTES
+
+/datum/mood_event/kiss
+ description = "Someone blew a kiss at me, I must be a real catch!\n"
+ mood_change = 1.5
+ timeout = 2 MINUTES
+
+/datum/mood_event/kiss/add_effects(mob/beau)
+ if(beau)
+ description = "[beau.name] blew a kiss at me, I must be a real catch!\n"
diff --git a/code/datums/quirks/negative.dm b/code/datums/quirks/negative.dm
index 5bdb6854e4b2b..ec2c50da4a22b 100644
--- a/code/datums/quirks/negative.dm
+++ b/code/datums/quirks/negative.dm
@@ -484,6 +484,7 @@
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."
hardcore_value = 4
+ mob_trait = TRAIT_ANXIOUS
var/dumb_thing = TRUE
/datum/quirk/social_anxiety/add()
diff --git a/code/game/objects/items/clown_items.dm b/code/game/objects/items/clown_items.dm
index 92e0530f2622e..7640952bbab82 100644
--- a/code/game/objects/items/clown_items.dm
+++ b/code/game/objects/items/clown_items.dm
@@ -139,8 +139,7 @@
user.visible_message("\the [user] washes \the [target]'s mouth out with [src.name]!", "You wash \the [target]'s mouth out with [src.name]!") //washes mouth out with soap sounds better than 'the soap' here if(user.zone_selected == "mouth")
if(human_user.lip_style)
user.mind?.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP)
- human_user.lip_style = null //removes lipstick
- human_user.update_body()
+ human_user.update_lips(null)
decreaseUses(user)
return
else if(istype(target, /obj/structure/window))
diff --git a/code/game/objects/items/cosmetics.dm b/code/game/objects/items/cosmetics.dm
index cabdcdf449c19..de4c587a5f4bd 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"
@@ -44,60 +50,55 @@
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, "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, "Remove [ target == user ? "your" : "[target.p_their()]" ] mask!")
+ return
+ if(target.lip_style) //if they already have lipstick on
+ to_chat(user, "You need to wipe off the old lipstick first!")
+ return
+
+ if(target == user)
+ user.visible_message("[user] does [user.p_their()] lips with \the [src].", \
+ "You take a moment to apply \the [src]. Perfect!")
+ target.update_lips("lipstick", colour, lipstick_trait)
+ return
+
+ user.visible_message("[user] begins to do [target]'s lips with \the [src].", \
+ "You begin to apply \the [src] on [target]'s lips...")
+ if(!do_after(user, 2 SECONDS, target = target))
+ return
+ user.visible_message("[user] does [target]'s lips with \the [src].", \
+ "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 ..()
+
+ var/mob/living/carbon/human/target = M
+ if(target == user)
+ to_chat(user, "You wipe off the lipstick with [src].")
+ target.update_lips(null)
+ return
+
+ user.visible_message("[user] begins to wipe [target]'s lipstick off with \the [src].", \
+ "You begin to wipe off [target]'s lipstick...")
+ if(!do_after(user, 10, target = target))
+ return
+ user.visible_message("[user] wipes [target]'s lipstick off with \the [src].", \
+ "You wipe off [target]'s lipstick.")
+ target.update_lips(null)
- 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
- ..()
/obj/item/razor
name = "electric razor"
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index 376d17b5269f5..0f064ee187d51 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -687,9 +687,7 @@
paint_color = "#C0C0C0"
update_icon()
if(actually_paints)
- H.lip_style = "spray_face"
- H.lip_color = paint_color
- H.update_body()
+ H.update_lips("spray_face", paint_color)
var/used = use_charges(user, 10, FALSE)
reagents.trans_to(user, used, volume_multiplier, transfered_by = user, methods = VAPOR)
@@ -740,10 +738,7 @@
flash_color(C, flash_color=paint_color, flash_time=40)
if(ishuman(C) && actually_paints)
var/mob/living/carbon/human/H = C
- H.lip_style = "spray_face"
- H.lip_color = paint_color
- H.update_body()
-
+ H.update_lips("spray_face", paint_color)
. = use_charges(user, 10, FALSE)
var/fraction = min(1, . / reagents.maximum_volume)
reagents.expose(C, VAPOR, fraction * volume_multiplier)
diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm
new file mode 100644
index 0000000000000..dd5a39d603bd2
--- /dev/null
+++ b/code/game/objects/items/hand_items.dm
@@ -0,0 +1,319 @@
+// For all of the items that are really just the user's hand used in different ways, mostly (all, really) from emotes
+
+/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_continuous = list("bops")
+ attack_verb_simple = list("bop")
+
+/obj/item/circlegame/Initialize()
+ . = ..()
+ 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"
+ inhand_icon_state = "nothing"
+ force = 0
+ throwforce = 0
+ item_flags = DROPDEL | ABSTRACT | HAND_ITEM
+ attack_verb_continuous = list("slaps")
+ attack_verb_simple = list("slap")
+ 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/noogie
+ name = "noogie"
+ desc = "Get someone in an aggressive grab then use this on them to ruin their day."
+ icon_state = "latexballon"
+ inhand_icon_state = "nothing"
+ force = 0
+ throwforce = 0
+ item_flags = DROPDEL | ABSTRACT | HAND_ITEM
+
+/obj/item/noogie/attack(mob/living/carbon/target, mob/living/carbon/human/user)
+ if(!istype(target))
+ to_chat(user, "You don't think you can give this a noogie!")
+ return
+
+ if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || user.pulling != target || user.grab_state < GRAB_AGGRESSIVE || user.getStaminaLoss() > 80)
+ return FALSE
+
+ var/obj/item/bodypart/head/the_head = target.get_bodypart(BODY_ZONE_HEAD)
+ if((target.get_biological_state() != BIO_FLESH_BONE && target.get_biological_state() != BIO_JUST_FLESH) || !the_head.is_organic_limb())
+ to_chat(user, "You can't noogie [target], [target.p_they()] [target.p_have()] no skin on [target.p_their()] head!")
+ return
+
+ // [user] gives [target] a [prefix_desc] noogie[affix_desc]!
+ var/brutal_noogie = FALSE // was it an extra hard noogie?
+ var/prefix_desc = "rough"
+ var/affix_desc = ""
+ var/affix_desc_target = ""
+
+ if(HAS_TRAIT(target, TRAIT_ANTENNAE))
+ prefix_desc = "violent"
+ affix_desc = "on [target.p_their()] sensitive antennae"
+ affix_desc_target = "on your highly sensitive antennae"
+ brutal_noogie = TRUE
+ if(user.dna?.check_mutation(HULK))
+ prefix_desc = "sickeningly brutal"
+ brutal_noogie = TRUE
+
+ var/message_others = "[prefix_desc] noogie[affix_desc]"
+ var/message_target = "[prefix_desc] noogie[affix_desc_target]"
+
+ user.visible_message("[user] begins giving [target] a [message_others]!", "You start giving [target] a [message_others]!", vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=target)
+ to_chat(target, "[user] starts giving you a [message_target]!")
+
+ if(!do_after(user, 1.5 SECONDS, target))
+ to_chat(user, "You fail to give [target] a noogie!")
+ to_chat(target, "[user] fails to give you a noogie!")
+ return
+
+ if(brutal_noogie)
+ SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "noogie_harsh", /datum/mood_event/noogie_harsh)
+ else
+ SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "noogie", /datum/mood_event/noogie)
+
+ noogie_loop(user, target, 0)
+
+/// The actual meat and bones of the noogie'ing
+/obj/item/noogie/proc/noogie_loop(mob/living/carbon/human/user, mob/living/carbon/target, iteration)
+ if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || user.pulling != target)
+ return FALSE
+
+ if(user.getStaminaLoss() > 80)
+ to_chat(user, "You're too tired to continue giving [target] a noogie!")
+ to_chat(target, "[user] is too tired to continue giving you a noogie!")
+ return
+
+ var/damage = rand(1, 5)
+ if(HAS_TRAIT(target, TRAIT_ANTENNAE))
+ damage += rand(3,7)
+ if(user.dna?.check_mutation(HULK))
+ damage += rand(3,7)
+
+ if(damage >= 5)
+ target.emote("scream")
+
+ log_combat(user, target, "given a noogie to", addition = "([damage] brute before armor)")
+ target.apply_damage(damage, BRUTE, BODY_ZONE_HEAD)
+ user.adjustStaminaLoss(iteration + 5)
+ playsound(get_turf(user), pick('sound/effects/rustle1.ogg','sound/effects/rustle2.ogg','sound/effects/rustle3.ogg','sound/effects/rustle4.ogg','sound/effects/rustle5.ogg'), 50)
+
+ if(prob(33))
+ user.visible_message("[user] continues noogie'ing [target]!", "You continue giving [target] a noogie!", vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=target)
+ to_chat(target, "[user] continues giving you a noogie!")
+
+ if(!do_after(user, 1 SECONDS + (iteration * 2), target))
+ to_chat(user, "You fail to give [target] a noogie!")
+ to_chat(target, "[user] fails to give you a noogie!")
+ return
+
+ iteration++
+ noogie_loop(user, target, iteration)
+
+
+/obj/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"
+ inhand_icon_state = "nothing"
+ force = 0
+ throwforce = 0
+ item_flags = DROPDEL | ABSTRACT | HAND_ITEM
+ /// The kind of projectile this version of the kiss blower fires
+ var/kiss_type = /obj/projectile/kiss
+
+/obj/item/kisser/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ var/obj/projectile/blown_kiss = new kiss_type(get_turf(user))
+ user.visible_message("[user] blows \a [blown_kiss] at [target]!", "You blow \a [blown_kiss] at [target]!")
+
+ //Shooting Code:
+ blown_kiss.spread = 0
+ blown_kiss.original = target
+ blown_kiss.fired_from = user
+ 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/kisser/death
+ name = "kiss of death"
+ desc = "If looks could kill, they'd be this."
+ color = COLOR_BLACK
+ kiss_type = /obj/projectile/kiss/death
+
+/obj/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
+ speed = 1.6
+ 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/projectile/kiss/fire(angle, atom/direct_target)
+ if(firer)
+ name = "[name] blown by [firer]"
+ return ..()
+
+/obj/projectile/kiss/on_hit(atom/target, blocked, pierce_hit)
+ def_zone = BODY_ZONE_HEAD // let's keep it PG, people
+ . = ..()
+ if(!ismob(target))
+ return
+ SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "kiss", /datum/mood_event/kiss, firer)
+ var/mob/living/target_living = target
+ if(!(HAS_TRAIT(target_living, TRAIT_ANXIOUS) && prob(30)))
+ return
+
+ // flustered!!!
+ 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!"
+ target_living.add_confusion(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(target_living, /atom/movable.proc/say, pick("Uhhh...", "O-oh, uhm...", "I- uhhhhh??", "You too!!", "What?")), rand(0.5 SECONDS, 1.5 SECONDS))
+ target_living.stuttering += rand(5, 15)
+ if(3)
+ other_msg = "locks up with a stunned look on [target_living.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!"
+ target_living.face_atom(firer)
+ target_living.Stun(rand(3 SECONDS, 8 SECONDS))
+
+ target_living.visible_message("[target_living] [other_msg]", "Whoa! [self_msg]")
+
+/obj/projectile/kiss/death
+ name = "kiss of death"
+ nodamage = FALSE // okay i kinda lied about love not being able to hurt you
+ damage = 35
+ wound_bonus = 0
+ sharpness = SHARP_POINTY
+ color = COLOR_BLACK
+
+/obj/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(999)
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 3dcf6d30e7ed2..d2e225377b881 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -722,223 +722,6 @@ 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_continuous = list("bops")
- attack_verb_simple = list("bop")
-
-/obj/item/circlegame/Initialize()
- . = ..()
- 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"
- inhand_icon_state = "nothing"
- force = 0
- throwforce = 0
- item_flags = DROPDEL | ABSTRACT | HAND_ITEM
- attack_verb_continuous = list("slaps")
- attack_verb_simple = list("slap")
- 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/noogie
- name = "noogie"
- desc = "Get someone in an aggressive grab then use this on them to ruin their day."
- icon_state = "latexballon"
- inhand_icon_state = "nothing"
- force = 0
- throwforce = 0
- item_flags = DROPDEL | ABSTRACT | HAND_ITEM
-
-/obj/item/noogie/attack(mob/living/carbon/target, mob/living/carbon/human/user)
- if(!istype(target))
- to_chat(user, "You don't think you can give this a noogie!")
- return
-
- if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || user.pulling != target || user.grab_state < GRAB_AGGRESSIVE || user.getStaminaLoss() > 80)
- return FALSE
-
- var/obj/item/bodypart/head/the_head = target.get_bodypart(BODY_ZONE_HEAD)
- if((target.get_biological_state() != BIO_FLESH_BONE && target.get_biological_state() != BIO_JUST_FLESH) || !the_head.is_organic_limb())
- to_chat(user, "You can't noogie [target], [target.p_they()] [target.p_have()] no skin on [target.p_their()] head!")
- return
-
- // [user] gives [target] a [prefix_desc] noogie[affix_desc]!
- var/brutal_noogie = FALSE // was it an extra hard noogie?
- var/prefix_desc = "rough"
- var/affix_desc = ""
- var/affix_desc_target = ""
-
- if(HAS_TRAIT(target, TRAIT_ANTENNAE))
- prefix_desc = "violent"
- affix_desc = "on [target.p_their()] sensitive antennae"
- affix_desc_target = "on your highly sensitive antennae"
- brutal_noogie = TRUE
- if(user.dna?.check_mutation(HULK))
- prefix_desc = "sickeningly brutal"
- brutal_noogie = TRUE
-
- var/message_others = "[prefix_desc] noogie[affix_desc]"
- var/message_target = "[prefix_desc] noogie[affix_desc_target]"
-
- user.visible_message("[user] begins giving [target] a [message_others]!", "You start giving [target] a [message_others]!", vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=target)
- to_chat(target, "[user] starts giving you a [message_target]!")
-
- if(!do_after(user, 1.5 SECONDS, target))
- to_chat(user, "You fail to give [target] a noogie!")
- to_chat(target, "[user] fails to give you a noogie!")
- return
-
- if(brutal_noogie)
- SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "noogie_harsh", /datum/mood_event/noogie_harsh)
- else
- SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "noogie", /datum/mood_event/noogie)
-
- noogie_loop(user, target, 0)
-
-/// The actual meat and bones of the noogie'ing
-/obj/item/noogie/proc/noogie_loop(mob/living/carbon/human/user, mob/living/carbon/target, iteration)
- if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || user.pulling != target)
- return FALSE
-
- if(user.getStaminaLoss() > 80)
- to_chat(user, "You're too tired to continue giving [target] a noogie!")
- to_chat(target, "[user] is too tired to continue giving you a noogie!")
- return
-
- var/damage = rand(1, 5)
- if(HAS_TRAIT(target, TRAIT_ANTENNAE))
- damage += rand(3,7)
- if(user.dna?.check_mutation(HULK))
- damage += rand(3,7)
-
- if(damage >= 5)
- target.emote("scream")
-
- log_combat(user, target, "given a noogie to", addition = "([damage] brute before armor)")
- target.apply_damage(damage, BRUTE, BODY_ZONE_HEAD)
- user.adjustStaminaLoss(iteration + 5)
- playsound(get_turf(user), pick('sound/effects/rustle1.ogg','sound/effects/rustle2.ogg','sound/effects/rustle3.ogg','sound/effects/rustle4.ogg','sound/effects/rustle5.ogg'), 50)
-
- if(prob(33))
- user.visible_message("[user] continues noogie'ing [target]!", "You continue giving [target] a noogie!", vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=target)
- to_chat(target, "[user] continues giving you a noogie!")
-
- if(!do_after(user, 1 SECONDS + (iteration * 2), target))
- to_chat(user, "You fail to give [target] a noogie!")
- to_chat(target, "[user] fails to give you a noogie!")
- return
-
- iteration++
- noogie_loop(user, target, iteration)
-
/obj/item/proc/can_trigger_gun(mob/living/user)
if(!user.can_use_guns(src))
return FALSE
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 430b2509a496e..9f966a1836770 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -735,14 +735,28 @@
return TRUE
/**
- * Cleans the lips of any lipstick. Returns TRUE if the lips had any lipstick and was thus cleaned
+ * 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
- lip_style = null
- lip_color = initial(lip_color)
- update_body()
+ update_lips(null)
return TRUE
/**
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index 4a620418bd03c..dcbd19c7bbcdd 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -196,8 +196,22 @@
/datum/emote/living/kiss
key = "kiss"
key_third_person = "kisses"
- message = "blows a kiss."
- message_param = "blows a kiss to %t."
+
+/datum/emote/living/kiss/run_emote(mob/living/user, params, type_override, intentional)
+ . = ..()
+ if(!.)
+ return
+ var/kiss_type = /obj/item/kisser
+
+ if(HAS_TRAIT(user, TRAIT_KISS_OF_DEATH))
+ kiss_type = /obj/item/kisser/death
+
+ var/obj/item/kiss_blower = new kiss_type(user)
+ if(user.put_in_hands(kiss_blower))
+ to_chat(user, "You ready your kiss-blowing hand.")
+ else
+ qdel(kiss_blower)
+ to_chat(user, "You're incapable of blowing a kiss in your current state.")
/datum/emote/living/laugh
key = "laugh"
diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm
index b8418ac7c6469..6fd34a8c9c921 100644
--- a/code/modules/surgery/bodyparts/dismemberment.dm
+++ b/code/modules/surgery/bodyparts/dismemberment.dm
@@ -414,8 +414,7 @@
H.hairstyle = hairstyle
H.facial_hair_color = facial_hair_color
H.facial_hairstyle = facial_hairstyle
- 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 d5d220b6f2ae2..c45c3e4eff0fc 100644
--- a/code/modules/surgery/bodyparts/head.dm
+++ b/code/modules/surgery/bodyparts/head.dm
@@ -37,6 +37,8 @@
var/lip_style = null
var/lip_color = "white"
+ var/stored_lipstick_trait
+
/obj/item/bodypart/head/Destroy()
QDEL_NULL(brainmob) //order is sensitive, see warning in handle_atom_del() below
@@ -139,6 +141,7 @@
hairstyle = "Bald"
facial_hairstyle = "Shaved"
lip_style = null
+ stored_lipstick_trait = null
else if(!animal_origin)
var/mob/living/carbon/human/H = C
diff --git a/sound/weapons/attributions.txt b/sound/attributions.txt
similarity index 93%
rename from sound/weapons/attributions.txt
rename to sound/attributions.txt
index d42313fe06192..1c5a6f388d716 100644
--- a/sound/weapons/attributions.txt
+++ b/sound/attributions.txt
@@ -16,3 +16,5 @@ revolverspin1, revolverspin2, and revolverspin3 are cut from a recording
captured by the BBC
"Gunfire & Guns: Revolver, Chambers spinning.": http://bbcsfx.acropolis.org.uk/assets/07027165.wav
bbc.co.uk - (c) copyright 2018 BBC
+
+kiss sound: https://freesound.org/people/stereostereo/sounds/117355/
\ No newline at end of file
diff --git a/sound/effects/kiss.ogg b/sound/effects/kiss.ogg
new file mode 100644
index 0000000000000..699b13a8de84a
Binary files /dev/null and b/sound/effects/kiss.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index e6352b5f58b28..e56742ed58c31 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1058,6 +1058,7 @@
#include "code\game\objects\items\flamethrower.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"