From 0e11e93acf7e80520eef44e6556d8d9d1e43ba6d Mon Sep 17 00:00:00 2001 From: ariaworld <143797359+ariaworld@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:35:21 +0100 Subject: [PATCH 1/4] Blow kisses! --- code/__DEFINES/traits.dm | 31 ++++ code/_globalvars/traits.dm | 2 + .../mood_events/generic_positive_events.dm | 5 + code/datums/traits/negative.dm | 11 +- code/game/objects/items/cosmetics.dm | 93 ++++++------ code/game/objects/items/weaponry.dm | 138 +++++++++++++++++- code/modules/mob/living/carbon/human/human.dm | 35 +++++ code/modules/mob/living/emote.dm | 20 +++ .../surgery/bodyparts/dismemberment.dm | 3 +- code/modules/surgery/bodyparts/head.dm | 4 + sound/effects/kiss.ogg | Bin 0 -> 8415 bytes sound/weapons/attributions.txt | 4 +- 12 files changed, 287 insertions(+), 59 deletions(-) create mode 100644 sound/effects/kiss.ogg diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index dbacbcbda5..295f8f9a90 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 709d558a42..09a3d052c0 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 8dd6d80a92..475715212a 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 a30e27c8f8..20cda350d6 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 b9a3852862..6871ebf6c0 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.update_lips(null) + 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.update_lips(null) /obj/item/razor name = "electric razor" diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index 636190b548..37534d1897 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -1117,6 +1117,11 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 else qdel(target) +/obj/item/proc/can_trigger_gun(mob/living/user) + if(!user.can_use_guns(src)) + return FALSE + return TRUE + /obj/item/circlegame name = "circled hand" desc = "If somebody looks at this while it's below your waist, you get to bop them." @@ -1240,10 +1245,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 "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 @@ -1359,6 +1360,135 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 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.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??", "You too!!", "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(3 SECONDS, 8 SECONDS)) + + living_target.visible_message("[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) + /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/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 3f6779c03b..3c1b0833bb 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -547,6 +547,41 @@ 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/proc/update_lips(new_style, new_color, apply_trait, update = TRUE) + return + +/mob/living/carbon/human/update_lips(new_style, new_color, apply_trait, update = TRUE) + lip_style = new_style + lip_color = new_color + + var/obj/item/bodypart/head/hopefully_a_head = get_bodypart(BODY_ZONE_HEAD) + REMOVE_TRAITS_IN(src, LIPSTICK_TRAIT) + if(hopefully_a_head) + hopefully_a_head.stored_lipstick_trait = null + hopefully_a_head.lip_style = new_style + hopefully_a_head.lip_color = new_color + if(new_style && apply_trait) + ADD_TRAIT(src, apply_trait, LIPSTICK_TRAIT) + hopefully_a_head?.stored_lipstick_trait = apply_trait + + if(update) + update_body_parts() + +/** + * A wrapper for [mob/living/carbon/human/update_lips] that sets the lip style and color to null. + **/ +/mob/living/proc/clean_lips() + return + +/mob/living/carbon/human/clean_lips() + if(!lip_style) + return FALSE + update_lips(null, null, update = TRUE) + 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 c2aca1f76f..39049cbc65 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 diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 96b9290239..35faec8bb3 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 ea43740093..6b5c8b725f 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/sound/effects/kiss.ogg b/sound/effects/kiss.ogg new file mode 100644 index 0000000000000000000000000000000000000000..699b13a8de84a65421744ea450e42f51824666da GIT binary patch literal 8415 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 Dv|lH6 literal 0 HcmV?d00001 diff --git a/sound/weapons/attributions.txt b/sound/weapons/attributions.txt index fe2911c873..9106b7cfe2 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/ From b6bab5b5a967576026f918054287e066d35a3d4f Mon Sep 17 00:00:00 2001 From: ariaworld <143797359+ariaworld@users.noreply.github.com> Date: Thu, 22 Feb 2024 13:16:53 +0100 Subject: [PATCH 2/4] Fix indentation + emote cooldown --- code/game/objects/items/weaponry.dm | 38 ++++++++++++++--------------- code/modules/mob/living/emote.dm | 1 + 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index 37534d1897..5e060382af 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -1452,26 +1452,26 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 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??", "You too!!", "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(3 SECONDS, 8 SECONDS)) + 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??", "You too!!", "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(3 SECONDS, 8 SECONDS)) - living_target.visible_message("[living_target] [other_msg]", span_userdanger("Whoa! [self_msg]")) + living_target.visible_message("[living_target] [other_msg]", span_userdanger("Whoa! [self_msg]")) /obj/item/projectile/kiss/death name = "kiss of death" diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index 39049cbc65..359abf3625 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -195,6 +195,7 @@ /datum/emote/living/kiss key = "kiss" key_third_person = "kisses" + emote_cooldown = 1 SECONDS /datum/emote/living/kiss/run_emote(mob/living/user, params, type_override, intentional) . = ..() From aefd604725935e311b1a853250f47018711755f9 Mon Sep 17 00:00:00 2001 From: ariaworld <143797359+ariaworld@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:59:13 +0100 Subject: [PATCH 3/4] Final tweaks for blow kiss update --- code/game/objects/items/cosmetics.dm | 4 +- code/game/objects/items/hand_items.dm | 368 ++++++++++++++++++ code/game/objects/items/weaponry.dm | 367 ----------------- code/game/objects/structures/watercloset.dm | 3 +- code/modules/antagonists/gang/gang.dm | 2 +- code/modules/mob/living/carbon/human/human.dm | 31 +- code/modules/mob/living/emote.dm | 5 +- .../code/modules/mob/living/emotes.dm | 2 +- tgstation.dme | 1 + 9 files changed, 387 insertions(+), 396 deletions(-) create mode 100644 code/game/objects/items/hand_items.dm diff --git a/code/game/objects/items/cosmetics.dm b/code/game/objects/items/cosmetics.dm index 6871ebf6c0..e808d3486f 100644 --- a/code/game/objects/items/cosmetics.dm +++ b/code/game/objects/items/cosmetics.dm @@ -87,7 +87,7 @@ var/mob/living/carbon/human/target = M if(target == user) to_chat(user, span_notice("You wipe off the lipstick with [src].")) - target.update_lips(null) + target.clean_lips() return user.visible_message(span_warning("[user] begins to wipe [target]'s lipstick off with \the [src]."), \ @@ -96,7 +96,7 @@ return user.visible_message(span_notice("[user] wipes [target]'s lipstick off with \the [src]."), \ span_notice("You wipe off [target]'s lipstick.")) - target.update_lips(null) + 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 0000000000..643035739f --- /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(8) // 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 5e060382af..e4c71ee6ff 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -1122,373 +1122,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 return FALSE return TRUE -/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/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/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.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??", "You too!!", "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(3 SECONDS, 8 SECONDS)) - - living_target.visible_message("[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) - /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 d46a40d598..7fc2bab6fd 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 050754a91e..1dcb054153 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 3c1b0833bb..da30304d2e 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -549,37 +549,28 @@ /** * 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/proc/update_lips(new_style, new_color, apply_trait, update = TRUE) - return - -/mob/living/carbon/human/update_lips(new_style, new_color, apply_trait, update = TRUE) + */ +/mob/living/carbon/human/proc/update_lips(new_style, new_colour, apply_trait) lip_style = new_style - lip_color = new_color + 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) - if(hopefully_a_head) - hopefully_a_head.stored_lipstick_trait = null - hopefully_a_head.lip_style = new_style - hopefully_a_head.lip_color = new_color + 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 - if(update) - update_body_parts() - /** - * A wrapper for [mob/living/carbon/human/update_lips] that sets the lip style and color to null. - **/ -/mob/living/proc/clean_lips() - return + * A wrapper for [mob/living/carbon/human/proc/update_lips] that tells us if there were lip styles to change + */ -/mob/living/carbon/human/clean_lips() - if(!lip_style) +/mob/living/carbon/human/proc/clean_lips() + if(isnull(lip_style) && lip_color == initial(lip_color)) return FALSE - update_lips(null, null, update = TRUE) + update_lips(null) return TRUE /mob/living/carbon/human/clean_blood() diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index 359abf3625..cd67dd2ff4 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -195,7 +195,6 @@ /datum/emote/living/kiss key = "kiss" key_third_person = "kisses" - emote_cooldown = 1 SECONDS /datum/emote/living/kiss/run_emote(mob/living/user, params, type_override, intentional) . = ..() @@ -549,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 @@ -565,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/modular_splurt/code/modules/mob/living/emotes.dm b/modular_splurt/code/modules/mob/living/emotes.dm index 09d5dfe062..65f6f695b2 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/tgstation.dme b/tgstation.dme index bf434bd612..11caad0601 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" From c62528cede45236ac8b24c74f240bccd6de086cf Mon Sep 17 00:00:00 2001 From: ariaworld <143797359+ariaworld@users.noreply.github.com> Date: Fri, 23 Feb 2024 21:25:10 +0100 Subject: [PATCH 4/4] Adjust speed of blown kiss --- code/game/objects/items/hand_items.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index 643035739f..e3a3d72579 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -264,7 +264,7 @@ //Shooting Code: blown_kiss.original = target blown_kiss.fired_from = user - blown_kiss.pixels_per_second = TILES_TO_PIXELS(8) // Speed of blown kiss + 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)