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"