Files
Bubberstation/code/game/objects/items/hand_items.dm
SkyratBot 4912b160a8 [MIRROR] Removes bad nodamage var from projectiles, fixes Juggernaut / Rust Walker projectiles doing zero damage [MDB IGNORE] (#19768)
* Removes bad `nodamage` var from projectiles, fixes Juggernaut / Rust Walker projectiles doing zero damage

* wew

* Update drinks.dm

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
2023-03-11 04:19:58 +00:00

668 lines
28 KiB
Plaintext

/// For all of the items that are really just the user's hand used in different ways, mostly (all, really) from emotes
/obj/item/hand_item
force = 0
throwforce = 0
item_flags = DROPDEL | ABSTRACT | HAND_ITEM
/obj/item/hand_item/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_NO_STORAGE_INSERT, TRAIT_GENERIC)
/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"
attack_verb_continuous = list("bops")
attack_verb_simple = list("bop")
/obj/item/hand_item/circlegame/Initialize(mapload)
. = ..()
var/mob/living/owner = loc
if(!istype(owner))
return
RegisterSignal(owner, COMSIG_PARENT_EXAMINE, PROC_REF(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_REF(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, span_danger("Wait a second... you just looked at your own [src.name]!"))
addtimer(CALLBACK(src, PROC_REF(selfGottem), owner), 10)
else
to_chat(sucker, span_danger("Wait a second... was that a-"))
addtimer(CALLBACK(src, PROC_REF(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(span_danger("[owner] shamefully bops [owner.p_them()]self with [owner.p_their()] [src.name]."), span_userdanger("You shamefully bop yourself with your [src.name]."), \
span_hear("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, span_warning("Nevermind... must've been your imagination..."))
return
if(!in_range(owner, sucker) || !(owner.mobility_flags & MOBILITY_USE))
to_chat(sucker, span_notice("Phew... you moved away before [owner] noticed you saw [owner.p_their()] [src.name]..."))
return
to_chat(owner, span_warning("[sucker] looks down at your [src.name] before trying to avert [sucker.p_their()] eyes, but it's too late!"))
to_chat(sucker, span_danger("<b>[owner] sees the fear in your eyes as you try to look away from [owner.p_their()] [src.name]!</b>"))
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(span_danger("[owner] bops [sucker] with [owner.p_their()] [src.name] much harder than intended, sending [sucker.p_them()] flying!"), \
span_danger("You bop [sucker] with your [src.name] much harder than intended, sending [sucker.p_them()] flying!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), ignored_mobs=list(sucker))
to_chat(sucker, span_userdanger("[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(span_danger("[owner] bops [sucker] with [owner.p_their()] [src.name]!"), span_danger("You bop [sucker] with your [src.name]!"), \
span_hear("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, span_userdanger("[owner] bops you with [owner.p_their()] [src.name]!"))
qdel(src)
/obj/item/hand_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"
/obj/item/hand_item/noogie/attack(mob/living/carbon/target, mob/living/carbon/human/user)
if(!istype(target))
to_chat(user, span_warning("You don't think you can give this a noogie!"))
return
if(HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, span_warning("You can't bring yourself to noogie [target]! You don't want to risk harming anyone..."))
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(!(the_head.biological_state & BIO_FLESH) || !IS_ORGANIC_LIMB(the_head))
to_chat(user, span_warning("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(/datum/mutation/human/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(span_danger("[user] begins giving [target] a [message_others]!"), span_warning("You start giving [target] a [message_others]!"), vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=target)
to_chat(target, span_userdanger("[user] starts giving you a [message_target]!"))
if(!do_after(user, 1.5 SECONDS, target))
to_chat(user, span_warning("You fail to give [target] a noogie!"))
to_chat(target, span_danger("[user] fails to give you a noogie!"))
return
if(brutal_noogie)
target.add_mood_event("noogie_harsh", /datum/mood_event/noogie_harsh)
else
target.add_mood_event("noogie", /datum/mood_event/noogie)
noogie_loop(user, target, 0)
/// The actual meat and bones of the noogie'ing
/obj/item/hand_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, span_warning("You're too tired to continue giving [target] a noogie!"))
to_chat(target, span_danger("[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(/datum/mutation/human/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(span_danger("[user] continues noogie'ing [target]!"), span_warning("You continue giving [target] a noogie!"), vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=target)
to_chat(target, span_userdanger("[user] continues giving you a noogie!"))
if(!do_after(user, 1 SECONDS + (iteration * 2), target))
to_chat(user, span_warning("You fail to give [target] a noogie!"))
to_chat(target, span_danger("[user] fails to give you a noogie!"))
return
iteration++
noogie_loop(user, target, iteration)
/obj/item/hand_item/slapper
name = "slapper"
desc = "This is how real men fight."
icon_state = "latexballon"
inhand_icon_state = "nothing"
attack_verb_continuous = list("slaps")
attack_verb_simple = list("slap")
hitsound = 'sound/effects/snap.ogg'
/// How many smaller table smacks we can do before we're out
var/table_smacks_left = 3
/obj/item/hand_item/slapper/Initialize(mapload)
. = ..()
AddElement(/datum/element/high_fiver)
/obj/item/hand_item/slapper/attack(mob/living/slapped, mob/living/carbon/human/user)
SEND_SIGNAL(user, COMSIG_LIVING_SLAP_MOB, slapped)
if(ishuman(slapped))
var/mob/living/carbon/human/human_slapped = slapped
SEND_SIGNAL(human_slapped, COMSIG_ORGAN_WAG_TAIL, FALSE)
user.do_attack_animation(slapped)
var/slap_volume = 50
var/datum/status_effect/offering/kiss_check = slapped.has_status_effect(/datum/status_effect/offering)
if(kiss_check && istype(kiss_check.offered_item, /obj/item/hand_item/kisser) && (user in kiss_check.possible_takers))
user.visible_message(
span_danger("[user] scoffs at [slapped]'s advance, winds up, and smacks [slapped.p_them()] hard to the ground!"),
span_notice("The nerve! You wind back your hand and smack [slapped] hard enough to knock [slapped.p_them()] over!"),
span_hear("You hear someone get the everloving shit smacked out of them!"),
ignored_mobs = slapped,
)
to_chat(slapped, span_userdanger("You see [user] scoff and pull back [user.p_their()] arm, then suddenly you're on the ground with an ungodly ringing in your ears!"))
slap_volume = 120
SEND_SOUND(slapped, sound('sound/weapons/flash_ring.ogg'))
shake_camera(slapped, 2, 2)
slapped.Paralyze(2.5 SECONDS)
slapped.adjust_confusion(7 SECONDS)
slapped.adjustStaminaLoss(40)
else if(user.zone_selected == BODY_ZONE_HEAD || user.zone_selected == BODY_ZONE_PRECISE_MOUTH)
if(user == slapped)
user.visible_message(
span_notice("[user] facepalms!"),
span_notice("You facepalm."),
span_hear("You hear a slap."),
)
else
if(slapped.IsSleeping() || slapped.IsUnconscious())
user.visible_message(
span_notice("[user] slaps [slapped] in the face, trying to wake [slapped.p_them()] up!"),
span_notice("You slap [slapped] in the face, trying to wake [slapped.p_them()] up!"),
span_hear("You hear a slap."),
)
// Worse than just help intenting people.
slapped.AdjustSleeping(-75)
slapped.AdjustUnconscious(-50)
else
user.visible_message(
span_danger("[user] slaps [slapped] in the face!"),
span_notice("You slap [slapped] in the face!"),
span_hear("You hear a slap."),
)
else
user.visible_message(
span_danger("[user] slaps [slapped]!"),
span_notice("You slap [slapped]!"),
span_hear("You hear a slap."),
)
playsound(slapped, 'sound/weapons/slap.ogg', slap_volume, TRUE, -1)
return
/obj/item/hand_item/slapper/pre_attack_secondary(atom/target, mob/living/user, params)
if(!loc.Adjacent(target) || !istype(target, /obj/structure/table))
return ..()
slam_table(target, user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/item/hand_item/slapper/pre_attack(atom/target, mob/living/user, params)
if(!loc.Adjacent(target) || !istype(target, /obj/structure/table))
return ..()
slap_table(target, user)
return TRUE
/// Slap the table, get some attention
/obj/item/hand_item/slapper/proc/slap_table(obj/structure/table/table, mob/living/user)
user.do_attack_animation(table)
playsound(get_turf(table), 'sound/effects/tableslam.ogg', 40, TRUE)
user.visible_message(span_notice("[user] slaps [user.p_their()] hand on [table]."), span_notice("You slap your hand on [table]."), vision_distance=COMBAT_MESSAGE_RANGE)
table_smacks_left--
if(table_smacks_left <= 0)
qdel(src)
/// Slam the table, demand some attention
/obj/item/hand_item/slapper/proc/slam_table(obj/structure/table/table, mob/living/user)
if(table_smacks_left < initial(table_smacks_left))
return slap_table(table, user)
user.do_attack_animation(table)
transform = transform.Scale(5) // BIG slap
if(HAS_TRAIT(user, TRAIT_HULK))
transform = transform.Scale(2)
color = COLOR_GREEN
SEND_SIGNAL(user, COMSIG_LIVING_SLAM_TABLE, table)
SEND_SIGNAL(table, COMSIG_TABLE_SLAMMED, user)
playsound(get_turf(table), 'sound/effects/tableslam.ogg', 110, TRUE)
user.visible_message("<b>[span_danger("[user] slams [user.p_their()] fist down on [table]!")]</b>", "<b>[span_danger("You slam your fist down on [table]!")]</b>")
qdel(src)
// Successful takes will qdel our hand after
/obj/item/hand_item/slapper/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
. = ..()
if(!.)
return
qdel(src)
/obj/item/hand_item/hand
name = "hand"
desc = "Sometimes, you just want to act gentlemanly."
icon_state = "latexballon"
inhand_icon_state = "nothing"
/obj/item/hand_item/hand/pre_attack(mob/living/carbon/help_target, mob/living/carbon/helper, params)
if(!loc.Adjacent(help_target) || !istype(helper) || !istype(help_target))
return ..()
if(helper.resting)
to_chat(helper, span_warning("You can't act gentlemanly when you're lying down!"))
return TRUE
/obj/item/hand_item/hand/pre_attack_secondary(mob/living/carbon/help_target, mob/living/carbon/helper, params)
if(!loc.Adjacent(help_target) || !istype(helper) || !istype(help_target))
return ..()
if(helper.resting)
to_chat(helper, span_warning("You can't act gentlemanly when you're lying down!"))
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
return SECONDARY_ATTACK_CALL_NORMAL
/obj/item/hand_item/hand/attack(mob/living/carbon/target_mob, mob/living/carbon/user, params)
if(!loc.Adjacent(target_mob) || !istype(user) || !istype(target_mob))
return TRUE
user.give(target_mob)
return TRUE
/obj/item/hand_item/hand/on_offered(mob/living/carbon/offerer, mob/living/carbon/offered)
. = TRUE
if(!istype(offerer))
return
if(offerer.body_position == LYING_DOWN)
to_chat(offerer, span_warning("You can't act gentlemanly when you're lying down!"))
return
if(!offered)
offered = locate(/mob/living/carbon) in orange(1, offerer)
if(offered && istype(offered) && offered.body_position == LYING_DOWN)
offerer.visible_message(span_notice("[offerer] offers [offerer.p_their()] hand to [offered], looking to help them up!"),
span_notice("You offer [offered] your hand, to try to help them up!"), null, 2)
offerer.apply_status_effect(/datum/status_effect/offering/no_item_received/needs_resting, src, /atom/movable/screen/alert/give/hand/helping, offered)
return
offerer.visible_message(span_notice("[offerer] extends out [offerer.p_their()] hand."),
span_notice("You extend out your hand."), null, 2)
offerer.apply_status_effect(/datum/status_effect/offering/no_item_received, src, /atom/movable/screen/alert/give/hand)
return
/obj/item/hand_item/hand/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
. = TRUE
if(taker.body_position == LYING_DOWN)
taker.help_shake_act(offerer)
if(taker.body_position == LYING_DOWN)
return // That didn't help them. Awkwaaaaard.
offerer.visible_message(span_notice("[offerer] helps [taker] up!"), span_nicegreen("You help [taker] up!"), span_hear("You hear someone helping someone else up!"), ignored_mobs = taker)
to_chat(taker, span_nicegreen("You take [offerer]'s hand, letting [offerer.p_them()] help your up! How nice of them!"))
offerer.add_mob_memory(/datum/memory/helped_up, protagonist = offerer, deuteragonist = taker)
taker.add_mob_memory(/datum/memory/helped_up, protagonist = offerer, deuteragonist = taker)
offerer.add_mood_event("helping_up", /datum/mood_event/helped_up, taker, TRUE) // Different IDs because you could be helped up and then help someone else up.
taker.add_mood_event("helped_up", /datum/mood_event/helped_up, offerer, FALSE)
qdel(src)
return
if(taker.buckled?.buckle_prevents_pull)
return // Can't start pulling them if they're buckled and that prevents pulls.
// We do a little switcheroo to ensure that it displays the pulling message that mentions
// taking taker by their hands.
var/offerer_zone_selected = offerer.zone_selected
offerer.zone_selected = "r_arm"
var/did_we_pull = offerer.start_pulling(taker) // Will return either null or FALSE. We only want to silence FALSE.
offerer.zone_selected = offerer_zone_selected
if(did_we_pull == FALSE)
return // That didn't work for one reason or the other. No need to display anything.
to_chat(offerer, span_notice("[taker] takes your hand, allowing you to pull [taker.p_them()] along."))
to_chat(taker, span_notice("You take [offerer]'s hand, which allows [offerer.p_them()] to pull you along. How polite!"))
qdel(src)
/obj/item/hand_item/stealer
name = "steal"
desc = "Your filthy little fingers are ready to commit crimes."
icon_state = "latexballon"
inhand_icon_state = "nothing"
attack_verb_continuous = list("steals")
attack_verb_simple = list("steal")
/obj/item/hand_item/stealer/attack(mob/living/target_mob, mob/living/user, params)
. = ..()
if (!ishuman(target_mob))
return
var/mob/living/carbon/human/target_human = target_mob
if(target_human == user)
to_chat(user, span_notice("Why would you try stealing your own shoes?"))
return
if (!target_human.shoes)
return
if (user.body_position != LYING_DOWN)
return
var/obj/item/clothing/shoes/item_to_strip = target_human.shoes
user.visible_message(span_warning("[user] starts stealing [target_human]'s [item_to_strip.name]!"), \
span_danger("You start stealing [target_human]'s [item_to_strip.name]..."))
to_chat(target_human, span_userdanger("[user] starts stealing your [item_to_strip.name]!"))
if (!do_after(user, item_to_strip.strip_delay, target_human))
return
if(!target_human.dropItemToGround(item_to_strip))
return
user.put_in_hands(item_to_strip)
user.visible_message(span_warning("[user] stole [target_human]'s [item_to_strip.name]!"), \
span_notice("You stole [target_human]'s [item_to_strip.name]!"))
to_chat(target_human, span_userdanger("[user] stole your [item_to_strip.name]!"))
/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/simple/animal.dmi'
icon_state = "heart"
inhand_icon_state = "nothing"
/// The kind of projectile this version of the kiss blower fires
var/kiss_type = /obj/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/projectile/blown_kiss = new kiss_type(get_turf(user))
user.visible_message("<b>[user]</b> 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/on_offered(mob/living/carbon/offerer, mob/living/carbon/offered)
if(!(locate(/mob/living/carbon) in orange(1, offerer)))
return TRUE
cheek_kiss = (offerer.zone_selected != BODY_ZONE_PRECISE_MOUTH)
offerer.visible_message(span_notice("[offerer] leans in slightly, offering a kiss[cheek_kiss ? " on the cheek" : ""]!"),
span_notice("You lean in slightly, indicating you'd like to offer a kiss[cheek_kiss ? " on the cheek" : ""]!"), null, 2)
offerer.apply_status_effect(/datum/status_effect/offering/no_item_received, src)
return TRUE
/obj/item/hand_item/kisser/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)
var/obj/projectile/blown_kiss = new kiss_type(get_turf(offerer))
offerer.visible_message("<b>[offerer]</b> gives [taker] \a [blown_kiss][cheek_kiss ? " on the cheek" : ""]!!", span_notice("You give [taker] \a [blown_kiss][cheek_kiss ? " on the cheek" : ""]!"), ignored_mobs = taker)
to_chat(taker, span_nicegreen("[offerer] gives you \a [blown_kiss][cheek_kiss ? " on the cheek" : ""]!"))
offerer.face_atom(taker)
taker.face_atom(offerer)
offerer.do_item_attack_animation(taker, used_item=src)
//We're still firing a shot here because I don't want to deal with some weird edgecase where direct impacting them with the projectile causes it to freak out because there's no angle or something
blown_kiss.original = taker
blown_kiss.fired_from = offerer
blown_kiss.firer = offerer // don't hit ourself that would be really annoying
blown_kiss.impacted = list(offerer = TRUE) // just to make sure we don't hit the wearer
blown_kiss.preparePixelProjectile(taker, offerer)
blown_kiss.suppressed = SUPPRESSED_VERY // this also means it's a direct offer
blown_kiss.fire()
qdel(src)
return TRUE // so the core offering code knows to halt
/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/projectile/kiss/death
/obj/projectile/kiss
name = "kiss"
icon = 'icons/mob/simple/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 // love can't actually hurt you
armour_penetration = 100 // but if it could, it would cut through even the thickest plate
/obj/projectile/kiss/fire(angle, atom/direct_target)
if(firer)
name = "[name] blown by [firer]"
return ..()
/obj/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/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/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.adjust_confusion(rand(5 SECONDS, 10 SECONDS))
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, TYPE_PROC_REF(/atom/movable, say), pick("Uhhh...", "O-oh, uhm...", "I- uhhhhh??", "You too!!", "What?")), rand(0.5 SECONDS, 1.5 SECONDS))
living_target.adjust_stutter(rand(10 SECONDS, 30 SECONDS))
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 at [firer ? "[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("<b>[living_target]</b> [other_msg]", span_userdanger("Whoa! [self_msg]"))
/obj/projectile/kiss/on_hit(atom/target, blocked, pierce_hit)
def_zone = BODY_ZONE_HEAD // let's keep it PG, people
. = ..()
if(isliving(target))
var/mob/living/living_target = target
living_target.add_mood_event("kiss", /datum/mood_event/kiss, firer, suppressed)
try_fluster(living_target)
/obj/projectile/kiss/death
name = "kiss of death"
damage = 35 // okay i kinda lied about love not being able to hurt you
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/internal/heart/dont_go_breakin_my_heart = heartbreakee.getorganslot(ORGAN_SLOT_HEART)
dont_go_breakin_my_heart.applyOrganDamage(999)
/obj/projectile/kiss/french
name = "french kiss (is that a hint of garlic?)"
color = "#f2e9d2" //Scientifically proven to be the colour of garlic
/obj/projectile/kiss/french/harmless_on_hit(mob/living/living_target)
. = ..()
if(isnull(living_target.reagents))
return
//Don't stack the garlic
if(!living_target.has_reagent(/datum/reagent/consumable/garlic))
//Phwoar
living_target.reagents.add_reagent(/datum/reagent/consumable/garlic, 1)
living_target.visible_message("[living_target] has a funny look on [living_target.p_their()] face.", "Wow, that is a strong after taste of garlic!", vision_distance=COMBAT_MESSAGE_RANGE)
/obj/projectile/kiss/chef
name = "chef's kiss"
// If our chef's kiss hits a food item, we will improve it with love.
/obj/projectile/kiss/chef/on_hit(atom/target, blocked, pierce_hit)
. = ..()
if(!IS_EDIBLE(target) || !target.reagents)
return
if(!firer || !target.Adjacent(firer) || !ismob(firer))
return
var/mob/kisser = firer
// From here on, no message
suppressed = SUPPRESSED_VERY
if(!(kisser.mind && HAS_TRAIT_FROM(target, TRAIT_FOOD_CHEF_MADE, REF(kisser.mind))))
to_chat(firer, span_warning("Wait a second, you didn't make this [target.name]. How can you claim it as your own?"))
return
if(target.reagents.has_reagent(/datum/reagent/love))
to_chat(firer, span_warning("You've already blessed [target.name] with your heart and soul."))
return
var/amount_nutriment = target.reagents.get_multiple_reagent_amounts(typesof(/datum/reagent/consumable/nutriment))
if(amount_nutriment <= 0)
to_chat(firer, span_warning("There's not enough nutrition in [target.name] for it to be a proper meal."))
return
to_chat(firer, span_green("You deliver a chef's kiss over [target], declaring it perfect."))
target.visible_message(span_notice("[firer] delivers a chef's kiss over [target]."), ignored_mobs = firer)
target.reagents.add_reagent(/datum/reagent/love, clamp(amount_nutriment / 4, 1, 10)) // clamped to about half of the most dense food I think we have (super bite burger)