mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-20 20:45:28 +01:00
Improves Kissing (#56698)
Co-authored-by: Mothblocks <35135081+Jared-Fogle@users.noreply.github.com>
This commit is contained in:
@@ -242,6 +242,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
|
||||
#define TRAIT_COAGULATING "coagulating" //from coagulant reagents, this doesn't affect the bleeding itself but does affect the bleed warning messages
|
||||
/// The holder of this trait has antennae or whatever that hurt a ton when noogied
|
||||
#define TRAIT_ANTENNAE "antennae"
|
||||
/// Blowing kisses actually does damage to the victim
|
||||
#define TRAIT_KISS_OF_DEATH "kiss_of_death"
|
||||
|
||||
#define TRAIT_NOBLEED "nobleed" //This carbon doesn't bleed
|
||||
|
||||
@@ -350,6 +352,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
|
||||
#define TRAIT_BADTOUCH "bad_touch"
|
||||
#define TRAIT_EXTROVERT "extrovert"
|
||||
#define TRAIT_INTROVERT "introvert"
|
||||
#define TRAIT_ANXIOUS "anxious"
|
||||
///Trait for dryable items
|
||||
#define TRAIT_DRYABLE "trait_dryable"
|
||||
///Trait for dried items
|
||||
@@ -479,6 +482,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
|
||||
#define ELEMENT_TRAIT "element_trait"
|
||||
/// Trait granted by [/obj/item/clothing/head/helmet/space/hardsuit/berserker]
|
||||
#define BERSERK_TRAIT "berserk_trait"
|
||||
/// Trait granted by lipstick
|
||||
#define LIPSTICK_TRAIT "lipstick_trait"
|
||||
|
||||
/**
|
||||
* Trait granted by [/mob/living/carbon/Initialize] and
|
||||
|
||||
@@ -156,7 +156,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
|
||||
"TRAIT_SNOB" = TRAIT_SNOB,
|
||||
"TRAIT_BALD" = TRAIT_BALD,
|
||||
"TRAIT_BADTOUCH" = TRAIT_BADTOUCH,
|
||||
"TRAIT_NOBLEED" = TRAIT_NOBLEED
|
||||
"TRAIT_NOBLEED" = TRAIT_NOBLEED,
|
||||
"TRAIT_KISS_OF_DEATH" = TRAIT_KISS_OF_DEATH,
|
||||
"TRAIT_ANXIOUS" = TRAIT_ANXIOUS,
|
||||
|
||||
),
|
||||
/obj/item/bodypart = list(
|
||||
|
||||
@@ -223,3 +223,12 @@
|
||||
description = "<span class='nicegreen'>Watching fish in aquarium is calming.</span>\n"
|
||||
mood_change = 3
|
||||
timeout = 1.5 MINUTES
|
||||
|
||||
/datum/mood_event/kiss
|
||||
description = "<span class='nicegreen'>Someone blew a kiss at me, I must be a real catch!</span>\n"
|
||||
mood_change = 1.5
|
||||
timeout = 2 MINUTES
|
||||
|
||||
/datum/mood_event/kiss/add_effects(mob/beau)
|
||||
if(beau)
|
||||
description = "<span class='nicegreen'>[beau.name] blew a kiss at me, I must be a real catch!</span>\n"
|
||||
|
||||
@@ -484,6 +484,7 @@
|
||||
lose_text = "<span class='notice'>You feel easier about talking again.</span>" //if only it were that easy!
|
||||
medical_record_text = "Patient is usually anxious in social encounters and prefers to avoid them."
|
||||
hardcore_value = 4
|
||||
mob_trait = TRAIT_ANXIOUS
|
||||
var/dumb_thing = TRUE
|
||||
|
||||
/datum/quirk/social_anxiety/add()
|
||||
|
||||
@@ -139,8 +139,7 @@
|
||||
user.visible_message("<span class='warning'>\the [user] washes \the [target]'s mouth out with [src.name]!</span>", "<span class='notice'>You wash \the [target]'s mouth out with [src.name]!</span>") //washes mouth out with soap sounds better than 'the soap' here if(user.zone_selected == "mouth")
|
||||
if(human_user.lip_style)
|
||||
user.mind?.adjust_experience(/datum/skill/cleaning, CLEAN_SKILL_GENERIC_WASH_XP)
|
||||
human_user.lip_style = null //removes lipstick
|
||||
human_user.update_body()
|
||||
human_user.update_lips(null)
|
||||
decreaseUses(user)
|
||||
return
|
||||
else if(istype(target, /obj/structure/window))
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
var/colour = "red"
|
||||
var/open = FALSE
|
||||
/// A trait that's applied while someone has this lipstick applied, and is removed when the lipstick is removed
|
||||
var/lipstick_trait
|
||||
|
||||
/obj/item/lipstick/purple
|
||||
name = "purple lipstick"
|
||||
@@ -21,6 +23,10 @@
|
||||
name = "black lipstick"
|
||||
colour = "black"
|
||||
|
||||
/obj/item/lipstick/black/death
|
||||
name = "Kiss of Death"
|
||||
lipstick_trait = TRAIT_KISS_OF_DEATH
|
||||
|
||||
/obj/item/lipstick/random
|
||||
name = "lipstick"
|
||||
icon_state = "random_lipstick"
|
||||
@@ -44,60 +50,55 @@
|
||||
icon_state = "lipstick"
|
||||
|
||||
/obj/item/lipstick/attack(mob/M, mob/user)
|
||||
if(!open)
|
||||
if(!open || !ismob(M))
|
||||
return
|
||||
|
||||
if(!ismob(M))
|
||||
return
|
||||
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
if(H.is_mouth_covered())
|
||||
to_chat(user, "<span class='warning'>Remove [ H == user ? "your" : "[H.p_their()]" ] mask!</span>")
|
||||
return
|
||||
if(H.lip_style) //if they already have lipstick on
|
||||
to_chat(user, "<span class='warning'>You need to wipe off the old lipstick first!</span>")
|
||||
return
|
||||
if(H == user)
|
||||
user.visible_message("<span class='notice'>[user] does [user.p_their()] lips with \the [src].</span>", \
|
||||
"<span class='notice'>You take a moment to apply \the [src]. Perfect!</span>")
|
||||
H.lip_style = "lipstick"
|
||||
H.lip_color = colour
|
||||
H.update_body()
|
||||
else
|
||||
user.visible_message("<span class='warning'>[user] begins to do [H]'s lips with \the [src].</span>", \
|
||||
"<span class='notice'>You begin to apply \the [src] on [H]'s lips...</span>")
|
||||
if(do_after(user, 20, target = H))
|
||||
user.visible_message("<span class='notice'>[user] does [H]'s lips with \the [src].</span>", \
|
||||
"<span class='notice'>You apply \the [src] on [H]'s lips.</span>")
|
||||
H.lip_style = "lipstick"
|
||||
H.lip_color = colour
|
||||
H.update_body()
|
||||
else
|
||||
if(!ishuman(M))
|
||||
to_chat(user, "<span class='warning'>Where are the lips on that?</span>")
|
||||
return
|
||||
|
||||
var/mob/living/carbon/human/target = M
|
||||
if(target.is_mouth_covered())
|
||||
to_chat(user, "<span class='warning'>Remove [ target == user ? "your" : "[target.p_their()]" ] mask!</span>")
|
||||
return
|
||||
if(target.lip_style) //if they already have lipstick on
|
||||
to_chat(user, "<span class='warning'>You need to wipe off the old lipstick first!</span>")
|
||||
return
|
||||
|
||||
if(target == user)
|
||||
user.visible_message("<span class='notice'>[user] does [user.p_their()] lips with \the [src].</span>", \
|
||||
"<span class='notice'>You take a moment to apply \the [src]. Perfect!</span>")
|
||||
target.update_lips("lipstick", colour, lipstick_trait)
|
||||
return
|
||||
|
||||
user.visible_message("<span class='warning'>[user] begins to do [target]'s lips with \the [src].</span>", \
|
||||
"<span class='notice'>You begin to apply \the [src] on [target]'s lips...</span>")
|
||||
if(!do_after(user, 2 SECONDS, target = target))
|
||||
return
|
||||
user.visible_message("<span class='notice'>[user] does [target]'s lips with \the [src].</span>", \
|
||||
"<span class='notice'>You apply \the [src] on [target]'s lips.</span>")
|
||||
target.update_lips("lipstick", colour, lipstick_trait)
|
||||
|
||||
|
||||
//you can wipe off lipstick with paper!
|
||||
/obj/item/paper/attack(mob/M, mob/user)
|
||||
if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH)
|
||||
if(!ismob(M))
|
||||
return
|
||||
if(user.zone_selected != BODY_ZONE_PRECISE_MOUTH || !ishuman(M))
|
||||
return ..()
|
||||
|
||||
var/mob/living/carbon/human/target = M
|
||||
if(target == user)
|
||||
to_chat(user, "<span class='notice'>You wipe off the lipstick with [src].</span>")
|
||||
target.update_lips(null)
|
||||
return
|
||||
|
||||
user.visible_message("<span class='warning'>[user] begins to wipe [target]'s lipstick off with \the [src].</span>", \
|
||||
"<span class='notice'>You begin to wipe off [target]'s lipstick...</span>")
|
||||
if(!do_after(user, 10, target = target))
|
||||
return
|
||||
user.visible_message("<span class='notice'>[user] wipes [target]'s lipstick off with \the [src].</span>", \
|
||||
"<span class='notice'>You wipe off [target]'s lipstick.</span>")
|
||||
target.update_lips(null)
|
||||
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
if(H == user)
|
||||
to_chat(user, "<span class='notice'>You wipe off the lipstick with [src].</span>")
|
||||
H.lip_style = null
|
||||
H.update_body()
|
||||
else
|
||||
user.visible_message("<span class='warning'>[user] begins to wipe [H]'s lipstick off with \the [src].</span>", \
|
||||
"<span class='notice'>You begin to wipe off [H]'s lipstick...</span>")
|
||||
if(do_after(user, 10, target = H))
|
||||
user.visible_message("<span class='notice'>[user] wipes [H]'s lipstick off with \the [src].</span>", \
|
||||
"<span class='notice'>You wipe off [H]'s lipstick.</span>")
|
||||
H.lip_style = null
|
||||
H.update_body()
|
||||
else
|
||||
..()
|
||||
|
||||
/obj/item/razor
|
||||
name = "electric razor"
|
||||
|
||||
@@ -687,9 +687,7 @@
|
||||
paint_color = "#C0C0C0"
|
||||
update_icon()
|
||||
if(actually_paints)
|
||||
H.lip_style = "spray_face"
|
||||
H.lip_color = paint_color
|
||||
H.update_body()
|
||||
H.update_lips("spray_face", paint_color)
|
||||
var/used = use_charges(user, 10, FALSE)
|
||||
reagents.trans_to(user, used, volume_multiplier, transfered_by = user, methods = VAPOR)
|
||||
|
||||
@@ -740,10 +738,7 @@
|
||||
flash_color(C, flash_color=paint_color, flash_time=40)
|
||||
if(ishuman(C) && actually_paints)
|
||||
var/mob/living/carbon/human/H = C
|
||||
H.lip_style = "spray_face"
|
||||
H.lip_color = paint_color
|
||||
H.update_body()
|
||||
|
||||
H.update_lips("spray_face", paint_color)
|
||||
. = use_charges(user, 10, FALSE)
|
||||
var/fraction = min(1, . / reagents.maximum_volume)
|
||||
reagents.expose(C, VAPOR, fraction * volume_multiplier)
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
// For all of the items that are really just the user's hand used in different ways, mostly (all, really) from emotes
|
||||
|
||||
/obj/item/circlegame
|
||||
name = "circled hand"
|
||||
desc = "If somebody looks at this while it's below your waist, you get to bop them."
|
||||
icon_state = "madeyoulook"
|
||||
force = 0
|
||||
throwforce = 0
|
||||
item_flags = DROPDEL | ABSTRACT | HAND_ITEM
|
||||
attack_verb_continuous = list("bops")
|
||||
attack_verb_simple = list("bop")
|
||||
|
||||
/obj/item/circlegame/Initialize()
|
||||
. = ..()
|
||||
var/mob/living/owner = loc
|
||||
if(!istype(owner))
|
||||
return
|
||||
RegisterSignal(owner, COMSIG_PARENT_EXAMINE, .proc/ownerExamined)
|
||||
|
||||
/obj/item/circlegame/Destroy()
|
||||
var/mob/owner = loc
|
||||
if(istype(owner))
|
||||
UnregisterSignal(owner, COMSIG_PARENT_EXAMINE)
|
||||
return ..()
|
||||
|
||||
/obj/item/circlegame/dropped(mob/user)
|
||||
UnregisterSignal(user, COMSIG_PARENT_EXAMINE) //loc will have changed by the time this is called, so Destroy() can't catch it
|
||||
// this is a dropdel item.
|
||||
return ..()
|
||||
|
||||
/// Stage 1: The mistake is made
|
||||
/obj/item/circlegame/proc/ownerExamined(mob/living/owner, mob/living/sucker)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!istype(sucker) || !in_range(owner, sucker))
|
||||
return
|
||||
addtimer(CALLBACK(src, .proc/waitASecond, owner, sucker), 4)
|
||||
|
||||
/// Stage 2: Fear sets in
|
||||
/obj/item/circlegame/proc/waitASecond(mob/living/owner, mob/living/sucker)
|
||||
if(QDELETED(sucker) || QDELETED(src) || QDELETED(owner))
|
||||
return
|
||||
|
||||
if(owner == sucker) // big mood
|
||||
to_chat(owner, "<span class='danger'>Wait a second... you just looked at your own [src.name]!</span>")
|
||||
addtimer(CALLBACK(src, .proc/selfGottem, owner), 10)
|
||||
else
|
||||
to_chat(sucker, "<span class='danger'>Wait a second... was that a-</span>")
|
||||
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("<span class='danger'>[owner] shamefully bops [owner.p_them()]self with [owner.p_their()] [src.name].</span>", "<span class='userdanger'>You shamefully bop yourself with your [src.name].</span>", \
|
||||
"<span class='hear'>You hear a dull thud!</span>")
|
||||
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, "<span class='warning'>Nevermind... must've been your imagination...</span>")
|
||||
return
|
||||
|
||||
if(!in_range(owner, sucker) || !(owner.mobility_flags & MOBILITY_USE))
|
||||
to_chat(sucker, "<span class='notice'>Phew... you moved away before [owner] noticed you saw [owner.p_their()] [src.name]...</span>")
|
||||
return
|
||||
|
||||
to_chat(owner, "<span class='warning'>[sucker] looks down at your [src.name] before trying to avert [sucker.p_their()] eyes, but it's too late!</span>")
|
||||
to_chat(sucker, "<span class='danger'><b>[owner] sees the fear in your eyes as you try to look away from [owner.p_their()] [src.name]!</b></span>")
|
||||
|
||||
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 class='danger'>[owner] bops [sucker] with [owner.p_their()] [src.name] much harder than intended, sending [sucker.p_them()] flying!</span>", \
|
||||
"<span class='danger'>You bop [sucker] with your [src.name] much harder than intended, sending [sucker.p_them()] flying!</span>", "<span class='hear'>You hear a sickening sound of flesh hitting flesh!</span>", ignored_mobs=list(sucker))
|
||||
to_chat(sucker, "<span class='userdanger'>[owner] bops you incredibly hard with [owner.p_their()] [src.name], sending you flying!</span>")
|
||||
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 class='danger'>[owner] bops [sucker] with [owner.p_their()] [src.name]!</span>", "<span class='danger'>You bop [sucker] with your [src.name]!</span>", \
|
||||
"<span class='hear'>You hear a dull thud!</span>", ignored_mobs=list(sucker))
|
||||
sucker.apply_damage(15, STAMINA)
|
||||
log_combat(owner, sucker, "bopped", src.name, "(setup)")
|
||||
to_chat(sucker, "<span class='userdanger'>[owner] bops you with [owner.p_their()] [src.name]!</span>")
|
||||
qdel(src)
|
||||
|
||||
/obj/item/slapper
|
||||
name = "slapper"
|
||||
desc = "This is how real men fight."
|
||||
icon_state = "latexballon"
|
||||
inhand_icon_state = "nothing"
|
||||
force = 0
|
||||
throwforce = 0
|
||||
item_flags = DROPDEL | ABSTRACT | HAND_ITEM
|
||||
attack_verb_continuous = list("slaps")
|
||||
attack_verb_simple = list("slap")
|
||||
hitsound = 'sound/effects/snap.ogg'
|
||||
|
||||
/obj/item/slapper/attack(mob/M, mob/living/carbon/human/user)
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/L = M
|
||||
if(L && L.dna && L.dna.species)
|
||||
L.dna.species.stop_wagging_tail(M)
|
||||
user.do_attack_animation(M)
|
||||
playsound(M, 'sound/weapons/slap.ogg', 50, TRUE, -1)
|
||||
user.visible_message("<span class='danger'>[user] slaps [M]!</span>",
|
||||
"<span class='notice'>You slap [M]!</span>",\
|
||||
"<span class='hear'>You hear a slap.</span>")
|
||||
return
|
||||
|
||||
/obj/item/noogie
|
||||
name = "noogie"
|
||||
desc = "Get someone in an aggressive grab then use this on them to ruin their day."
|
||||
icon_state = "latexballon"
|
||||
inhand_icon_state = "nothing"
|
||||
force = 0
|
||||
throwforce = 0
|
||||
item_flags = DROPDEL | ABSTRACT | HAND_ITEM
|
||||
|
||||
/obj/item/noogie/attack(mob/living/carbon/target, mob/living/carbon/human/user)
|
||||
if(!istype(target))
|
||||
to_chat(user, "<span class='warning'>You don't think you can give this a noogie!</span>")
|
||||
return
|
||||
|
||||
if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || user.pulling != target || user.grab_state < GRAB_AGGRESSIVE || user.getStaminaLoss() > 80)
|
||||
return FALSE
|
||||
|
||||
var/obj/item/bodypart/head/the_head = target.get_bodypart(BODY_ZONE_HEAD)
|
||||
if((target.get_biological_state() != BIO_FLESH_BONE && target.get_biological_state() != BIO_JUST_FLESH) || !the_head.is_organic_limb())
|
||||
to_chat(user, "<span class='warning'>You can't noogie [target], [target.p_they()] [target.p_have()] no skin on [target.p_their()] head!</span>")
|
||||
return
|
||||
|
||||
// [user] gives [target] a [prefix_desc] noogie[affix_desc]!
|
||||
var/brutal_noogie = FALSE // was it an extra hard noogie?
|
||||
var/prefix_desc = "rough"
|
||||
var/affix_desc = ""
|
||||
var/affix_desc_target = ""
|
||||
|
||||
if(HAS_TRAIT(target, TRAIT_ANTENNAE))
|
||||
prefix_desc = "violent"
|
||||
affix_desc = "on [target.p_their()] sensitive antennae"
|
||||
affix_desc_target = "on your highly sensitive antennae"
|
||||
brutal_noogie = TRUE
|
||||
if(user.dna?.check_mutation(HULK))
|
||||
prefix_desc = "sickeningly brutal"
|
||||
brutal_noogie = TRUE
|
||||
|
||||
var/message_others = "[prefix_desc] noogie[affix_desc]"
|
||||
var/message_target = "[prefix_desc] noogie[affix_desc_target]"
|
||||
|
||||
user.visible_message("<span class='danger'>[user] begins giving [target] a [message_others]!</span>", "<span class='warning'>You start giving [target] a [message_others]!</span>", vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=target)
|
||||
to_chat(target, "<span class='userdanger'>[user] starts giving you a [message_target]!</span>")
|
||||
|
||||
if(!do_after(user, 1.5 SECONDS, target))
|
||||
to_chat(user, "<span class='warning'>You fail to give [target] a noogie!</span>")
|
||||
to_chat(target, "<span class='danger'>[user] fails to give you a noogie!</span>")
|
||||
return
|
||||
|
||||
if(brutal_noogie)
|
||||
SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "noogie_harsh", /datum/mood_event/noogie_harsh)
|
||||
else
|
||||
SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "noogie", /datum/mood_event/noogie)
|
||||
|
||||
noogie_loop(user, target, 0)
|
||||
|
||||
/// The actual meat and bones of the noogie'ing
|
||||
/obj/item/noogie/proc/noogie_loop(mob/living/carbon/human/user, mob/living/carbon/target, iteration)
|
||||
if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || user.pulling != target)
|
||||
return FALSE
|
||||
|
||||
if(user.getStaminaLoss() > 80)
|
||||
to_chat(user, "<span class='warning'>You're too tired to continue giving [target] a noogie!</span>")
|
||||
to_chat(target, "<span class='danger'>[user] is too tired to continue giving you a noogie!</span>")
|
||||
return
|
||||
|
||||
var/damage = rand(1, 5)
|
||||
if(HAS_TRAIT(target, TRAIT_ANTENNAE))
|
||||
damage += rand(3,7)
|
||||
if(user.dna?.check_mutation(HULK))
|
||||
damage += rand(3,7)
|
||||
|
||||
if(damage >= 5)
|
||||
target.emote("scream")
|
||||
|
||||
log_combat(user, target, "given a noogie to", addition = "([damage] brute before armor)")
|
||||
target.apply_damage(damage, BRUTE, BODY_ZONE_HEAD)
|
||||
user.adjustStaminaLoss(iteration + 5)
|
||||
playsound(get_turf(user), pick('sound/effects/rustle1.ogg','sound/effects/rustle2.ogg','sound/effects/rustle3.ogg','sound/effects/rustle4.ogg','sound/effects/rustle5.ogg'), 50)
|
||||
|
||||
if(prob(33))
|
||||
user.visible_message("<span class='danger'>[user] continues noogie'ing [target]!</span>", "<span class='warning'>You continue giving [target] a noogie!</span>", vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=target)
|
||||
to_chat(target, "<span class='userdanger'>[user] continues giving you a noogie!</span>")
|
||||
|
||||
if(!do_after(user, 1 SECONDS + (iteration * 2), target))
|
||||
to_chat(user, "<span class='warning'>You fail to give [target] a noogie!</span>")
|
||||
to_chat(target, "<span class='danger'>[user] fails to give you a noogie!</span>")
|
||||
return
|
||||
|
||||
iteration++
|
||||
noogie_loop(user, target, iteration)
|
||||
|
||||
|
||||
/obj/item/kisser
|
||||
name = "kiss"
|
||||
desc = "I want you all to know, everyone and anyone, to seal it with a kiss."
|
||||
icon = 'icons/mob/animal.dmi'
|
||||
icon_state = "heart"
|
||||
inhand_icon_state = "nothing"
|
||||
force = 0
|
||||
throwforce = 0
|
||||
item_flags = DROPDEL | ABSTRACT | HAND_ITEM
|
||||
/// The kind of projectile this version of the kiss blower fires
|
||||
var/kiss_type = /obj/projectile/kiss
|
||||
|
||||
/obj/item/kisser/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
|
||||
. = ..()
|
||||
var/obj/projectile/blown_kiss = new kiss_type(get_turf(user))
|
||||
user.visible_message("<b>[user]</b> blows \a [blown_kiss] at [target]!", "<span class='notice'>You blow \a [blown_kiss] at [target]!</span>")
|
||||
|
||||
//Shooting Code:
|
||||
blown_kiss.spread = 0
|
||||
blown_kiss.original = target
|
||||
blown_kiss.fired_from = user
|
||||
blown_kiss.firer = user // don't hit ourself that would be really annoying
|
||||
blown_kiss.impacted = list(user = TRUE) // just to make sure we don't hit the wearer
|
||||
blown_kiss.preparePixelProjectile(target, user)
|
||||
blown_kiss.fire()
|
||||
qdel(src)
|
||||
|
||||
/obj/item/kisser/death
|
||||
name = "kiss of death"
|
||||
desc = "If looks could kill, they'd be this."
|
||||
color = COLOR_BLACK
|
||||
kiss_type = /obj/projectile/kiss/death
|
||||
|
||||
/obj/projectile/kiss
|
||||
name = "kiss"
|
||||
icon = 'icons/mob/animal.dmi'
|
||||
icon_state = "heart"
|
||||
hitsound = 'sound/effects/kiss.ogg'
|
||||
hitsound_wall = 'sound/effects/kiss.ogg'
|
||||
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE
|
||||
speed = 1.6
|
||||
damage_type = BRUTE
|
||||
damage = 0
|
||||
nodamage = TRUE // love can't actually hurt you
|
||||
armour_penetration = 100 // but if it could, it would cut through even the thickest plate
|
||||
flag = MAGIC // and most importantly, love is magic~
|
||||
|
||||
/obj/projectile/kiss/fire(angle, atom/direct_target)
|
||||
if(firer)
|
||||
name = "[name] blown by [firer]"
|
||||
return ..()
|
||||
|
||||
/obj/projectile/kiss/on_hit(atom/target, blocked, pierce_hit)
|
||||
def_zone = BODY_ZONE_HEAD // let's keep it PG, people
|
||||
. = ..()
|
||||
if(!ismob(target))
|
||||
return
|
||||
SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "kiss", /datum/mood_event/kiss, firer)
|
||||
var/mob/living/target_living = target
|
||||
if(!(HAS_TRAIT(target_living, TRAIT_ANXIOUS) && prob(30)))
|
||||
return
|
||||
|
||||
// flustered!!!
|
||||
var/other_msg
|
||||
var/self_msg
|
||||
var/roll = rand(1, 3)
|
||||
switch(roll)
|
||||
if(1)
|
||||
other_msg = "stumbles slightly, turning a bright red!"
|
||||
self_msg = "You lose control of your limbs for a moment as your blood rushes to your face, turning it bright red!"
|
||||
target_living.add_confusion(rand(5, 10))
|
||||
if(2)
|
||||
other_msg = "stammers softly for a moment before choking on something!"
|
||||
self_msg = "You feel your tongue disappear down your throat as you fight to remember how to make words!"
|
||||
addtimer(CALLBACK(target_living, /atom/movable.proc/say, pick("Uhhh...", "O-oh, uhm...", "I- uhhhhh??", "You too!!", "What?")), rand(0.5 SECONDS, 1.5 SECONDS))
|
||||
target_living.stuttering += rand(5, 15)
|
||||
if(3)
|
||||
other_msg = "locks up with a stunned look on [target_living.p_their()] face, staring at [firer ? firer : "the ceiling"]!"
|
||||
self_msg = "Your brain completely fails to process what just happened, leaving you rooted in place staring [firer ? "at [firer]" : "the ceiling"] for what feels like an eternity!"
|
||||
target_living.face_atom(firer)
|
||||
target_living.Stun(rand(3 SECONDS, 8 SECONDS))
|
||||
|
||||
target_living.visible_message("<b>[target_living]</b> [other_msg]", "<span class='userdanger'>Whoa! [self_msg]</span>")
|
||||
|
||||
/obj/projectile/kiss/death
|
||||
name = "kiss of death"
|
||||
nodamage = FALSE // okay i kinda lied about love not being able to hurt you
|
||||
damage = 35
|
||||
wound_bonus = 0
|
||||
sharpness = SHARP_POINTY
|
||||
color = COLOR_BLACK
|
||||
|
||||
/obj/projectile/kiss/death/on_hit(atom/target, blocked, pierce_hit)
|
||||
. = ..()
|
||||
if(!iscarbon(target))
|
||||
return
|
||||
var/mob/living/carbon/heartbreakee = target
|
||||
var/obj/item/organ/heart/dont_go_breakin_my_heart = heartbreakee.getorganslot(ORGAN_SLOT_HEART)
|
||||
dont_go_breakin_my_heart.applyOrganDamage(999)
|
||||
@@ -722,223 +722,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
|
||||
else
|
||||
qdel(target)
|
||||
|
||||
/obj/item/circlegame
|
||||
name = "circled hand"
|
||||
desc = "If somebody looks at this while it's below your waist, you get to bop them."
|
||||
icon_state = "madeyoulook"
|
||||
force = 0
|
||||
throwforce = 0
|
||||
item_flags = DROPDEL | ABSTRACT | HAND_ITEM
|
||||
attack_verb_continuous = list("bops")
|
||||
attack_verb_simple = list("bop")
|
||||
|
||||
/obj/item/circlegame/Initialize()
|
||||
. = ..()
|
||||
var/mob/living/owner = loc
|
||||
if(!istype(owner))
|
||||
return
|
||||
RegisterSignal(owner, COMSIG_PARENT_EXAMINE, .proc/ownerExamined)
|
||||
|
||||
/obj/item/circlegame/Destroy()
|
||||
var/mob/owner = loc
|
||||
if(istype(owner))
|
||||
UnregisterSignal(owner, COMSIG_PARENT_EXAMINE)
|
||||
return ..()
|
||||
|
||||
/obj/item/circlegame/dropped(mob/user)
|
||||
UnregisterSignal(user, COMSIG_PARENT_EXAMINE) //loc will have changed by the time this is called, so Destroy() can't catch it
|
||||
// this is a dropdel item.
|
||||
return ..()
|
||||
|
||||
/// Stage 1: The mistake is made
|
||||
/obj/item/circlegame/proc/ownerExamined(mob/living/owner, mob/living/sucker)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!istype(sucker) || !in_range(owner, sucker))
|
||||
return
|
||||
addtimer(CALLBACK(src, .proc/waitASecond, owner, sucker), 4)
|
||||
|
||||
/// Stage 2: Fear sets in
|
||||
/obj/item/circlegame/proc/waitASecond(mob/living/owner, mob/living/sucker)
|
||||
if(QDELETED(sucker) || QDELETED(src) || QDELETED(owner))
|
||||
return
|
||||
|
||||
if(owner == sucker) // big mood
|
||||
to_chat(owner, "<span class='danger'>Wait a second... you just looked at your own [src.name]!</span>")
|
||||
addtimer(CALLBACK(src, .proc/selfGottem, owner), 10)
|
||||
else
|
||||
to_chat(sucker, "<span class='danger'>Wait a second... was that a-</span>")
|
||||
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("<span class='danger'>[owner] shamefully bops [owner.p_them()]self with [owner.p_their()] [src.name].</span>", "<span class='userdanger'>You shamefully bop yourself with your [src.name].</span>", \
|
||||
"<span class='hear'>You hear a dull thud!</span>")
|
||||
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, "<span class='warning'>Nevermind... must've been your imagination...</span>")
|
||||
return
|
||||
|
||||
if(!in_range(owner, sucker) || !(owner.mobility_flags & MOBILITY_USE))
|
||||
to_chat(sucker, "<span class='notice'>Phew... you moved away before [owner] noticed you saw [owner.p_their()] [src.name]...</span>")
|
||||
return
|
||||
|
||||
to_chat(owner, "<span class='warning'>[sucker] looks down at your [src.name] before trying to avert [sucker.p_their()] eyes, but it's too late!</span>")
|
||||
to_chat(sucker, "<span class='danger'><b>[owner] sees the fear in your eyes as you try to look away from [owner.p_their()] [src.name]!</b></span>")
|
||||
|
||||
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 class='danger'>[owner] bops [sucker] with [owner.p_their()] [src.name] much harder than intended, sending [sucker.p_them()] flying!</span>", \
|
||||
"<span class='danger'>You bop [sucker] with your [src.name] much harder than intended, sending [sucker.p_them()] flying!</span>", "<span class='hear'>You hear a sickening sound of flesh hitting flesh!</span>", ignored_mobs=list(sucker))
|
||||
to_chat(sucker, "<span class='userdanger'>[owner] bops you incredibly hard with [owner.p_their()] [src.name], sending you flying!</span>")
|
||||
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 class='danger'>[owner] bops [sucker] with [owner.p_their()] [src.name]!</span>", "<span class='danger'>You bop [sucker] with your [src.name]!</span>", \
|
||||
"<span class='hear'>You hear a dull thud!</span>", ignored_mobs=list(sucker))
|
||||
sucker.apply_damage(15, STAMINA)
|
||||
log_combat(owner, sucker, "bopped", src.name, "(setup)")
|
||||
to_chat(sucker, "<span class='userdanger'>[owner] bops you with [owner.p_their()] [src.name]!</span>")
|
||||
qdel(src)
|
||||
|
||||
/obj/item/slapper
|
||||
name = "slapper"
|
||||
desc = "This is how real men fight."
|
||||
icon_state = "latexballon"
|
||||
inhand_icon_state = "nothing"
|
||||
force = 0
|
||||
throwforce = 0
|
||||
item_flags = DROPDEL | ABSTRACT | HAND_ITEM
|
||||
attack_verb_continuous = list("slaps")
|
||||
attack_verb_simple = list("slap")
|
||||
hitsound = 'sound/effects/snap.ogg'
|
||||
|
||||
/obj/item/slapper/attack(mob/M, mob/living/carbon/human/user)
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/L = M
|
||||
if(L && L.dna && L.dna.species)
|
||||
L.dna.species.stop_wagging_tail(M)
|
||||
user.do_attack_animation(M)
|
||||
playsound(M, 'sound/weapons/slap.ogg', 50, TRUE, -1)
|
||||
user.visible_message("<span class='danger'>[user] slaps [M]!</span>",
|
||||
"<span class='notice'>You slap [M]!</span>",\
|
||||
"<span class='hear'>You hear a slap.</span>")
|
||||
return
|
||||
|
||||
/obj/item/noogie
|
||||
name = "noogie"
|
||||
desc = "Get someone in an aggressive grab then use this on them to ruin their day."
|
||||
icon_state = "latexballon"
|
||||
inhand_icon_state = "nothing"
|
||||
force = 0
|
||||
throwforce = 0
|
||||
item_flags = DROPDEL | ABSTRACT | HAND_ITEM
|
||||
|
||||
/obj/item/noogie/attack(mob/living/carbon/target, mob/living/carbon/human/user)
|
||||
if(!istype(target))
|
||||
to_chat(user, "<span class='warning'>You don't think you can give this a noogie!</span>")
|
||||
return
|
||||
|
||||
if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || user.pulling != target || user.grab_state < GRAB_AGGRESSIVE || user.getStaminaLoss() > 80)
|
||||
return FALSE
|
||||
|
||||
var/obj/item/bodypart/head/the_head = target.get_bodypart(BODY_ZONE_HEAD)
|
||||
if((target.get_biological_state() != BIO_FLESH_BONE && target.get_biological_state() != BIO_JUST_FLESH) || !the_head.is_organic_limb())
|
||||
to_chat(user, "<span class='warning'>You can't noogie [target], [target.p_they()] [target.p_have()] no skin on [target.p_their()] head!</span>")
|
||||
return
|
||||
|
||||
// [user] gives [target] a [prefix_desc] noogie[affix_desc]!
|
||||
var/brutal_noogie = FALSE // was it an extra hard noogie?
|
||||
var/prefix_desc = "rough"
|
||||
var/affix_desc = ""
|
||||
var/affix_desc_target = ""
|
||||
|
||||
if(HAS_TRAIT(target, TRAIT_ANTENNAE))
|
||||
prefix_desc = "violent"
|
||||
affix_desc = "on [target.p_their()] sensitive antennae"
|
||||
affix_desc_target = "on your highly sensitive antennae"
|
||||
brutal_noogie = TRUE
|
||||
if(user.dna?.check_mutation(HULK))
|
||||
prefix_desc = "sickeningly brutal"
|
||||
brutal_noogie = TRUE
|
||||
|
||||
var/message_others = "[prefix_desc] noogie[affix_desc]"
|
||||
var/message_target = "[prefix_desc] noogie[affix_desc_target]"
|
||||
|
||||
user.visible_message("<span class='danger'>[user] begins giving [target] a [message_others]!</span>", "<span class='warning'>You start giving [target] a [message_others]!</span>", vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=target)
|
||||
to_chat(target, "<span class='userdanger'>[user] starts giving you a [message_target]!</span>")
|
||||
|
||||
if(!do_after(user, 1.5 SECONDS, target))
|
||||
to_chat(user, "<span class='warning'>You fail to give [target] a noogie!</span>")
|
||||
to_chat(target, "<span class='danger'>[user] fails to give you a noogie!</span>")
|
||||
return
|
||||
|
||||
if(brutal_noogie)
|
||||
SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "noogie_harsh", /datum/mood_event/noogie_harsh)
|
||||
else
|
||||
SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "noogie", /datum/mood_event/noogie)
|
||||
|
||||
noogie_loop(user, target, 0)
|
||||
|
||||
/// The actual meat and bones of the noogie'ing
|
||||
/obj/item/noogie/proc/noogie_loop(mob/living/carbon/human/user, mob/living/carbon/target, iteration)
|
||||
if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || user.pulling != target)
|
||||
return FALSE
|
||||
|
||||
if(user.getStaminaLoss() > 80)
|
||||
to_chat(user, "<span class='warning'>You're too tired to continue giving [target] a noogie!</span>")
|
||||
to_chat(target, "<span class='danger'>[user] is too tired to continue giving you a noogie!</span>")
|
||||
return
|
||||
|
||||
var/damage = rand(1, 5)
|
||||
if(HAS_TRAIT(target, TRAIT_ANTENNAE))
|
||||
damage += rand(3,7)
|
||||
if(user.dna?.check_mutation(HULK))
|
||||
damage += rand(3,7)
|
||||
|
||||
if(damage >= 5)
|
||||
target.emote("scream")
|
||||
|
||||
log_combat(user, target, "given a noogie to", addition = "([damage] brute before armor)")
|
||||
target.apply_damage(damage, BRUTE, BODY_ZONE_HEAD)
|
||||
user.adjustStaminaLoss(iteration + 5)
|
||||
playsound(get_turf(user), pick('sound/effects/rustle1.ogg','sound/effects/rustle2.ogg','sound/effects/rustle3.ogg','sound/effects/rustle4.ogg','sound/effects/rustle5.ogg'), 50)
|
||||
|
||||
if(prob(33))
|
||||
user.visible_message("<span class='danger'>[user] continues noogie'ing [target]!</span>", "<span class='warning'>You continue giving [target] a noogie!</span>", vision_distance=COMBAT_MESSAGE_RANGE, ignored_mobs=target)
|
||||
to_chat(target, "<span class='userdanger'>[user] continues giving you a noogie!</span>")
|
||||
|
||||
if(!do_after(user, 1 SECONDS + (iteration * 2), target))
|
||||
to_chat(user, "<span class='warning'>You fail to give [target] a noogie!</span>")
|
||||
to_chat(target, "<span class='danger'>[user] fails to give you a noogie!</span>")
|
||||
return
|
||||
|
||||
iteration++
|
||||
noogie_loop(user, target, iteration)
|
||||
|
||||
/obj/item/proc/can_trigger_gun(mob/living/user)
|
||||
if(!user.can_use_guns(src))
|
||||
return FALSE
|
||||
|
||||
@@ -735,14 +735,28 @@
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Cleans the lips of any lipstick. Returns TRUE if the lips had any lipstick and was thus cleaned
|
||||
* Used to update the makeup on a human and apply/remove lipstick traits, then store/unstore them on the head object in case it gets severed
|
||||
*/
|
||||
/mob/living/carbon/human/proc/update_lips(new_style, new_colour, apply_trait)
|
||||
lip_style = new_style
|
||||
lip_color = new_colour
|
||||
update_body()
|
||||
|
||||
var/obj/item/bodypart/head/hopefully_a_head = get_bodypart(BODY_ZONE_HEAD)
|
||||
REMOVE_TRAITS_IN(src, LIPSTICK_TRAIT)
|
||||
hopefully_a_head?.stored_lipstick_trait = null
|
||||
|
||||
if(new_style && apply_trait)
|
||||
ADD_TRAIT(src, apply_trait, LIPSTICK_TRAIT)
|
||||
hopefully_a_head?.stored_lipstick_trait = apply_trait
|
||||
|
||||
/**
|
||||
* A wrapper for [mob/living/carbon/human/proc/update_lips] that tells us if there were lip styles to change
|
||||
*/
|
||||
/mob/living/carbon/human/proc/clean_lips()
|
||||
if(isnull(lip_style) && lip_color == initial(lip_color))
|
||||
return FALSE
|
||||
lip_style = null
|
||||
lip_color = initial(lip_color)
|
||||
update_body()
|
||||
update_lips(null)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
|
||||
@@ -196,8 +196,22 @@
|
||||
/datum/emote/living/kiss
|
||||
key = "kiss"
|
||||
key_third_person = "kisses"
|
||||
message = "blows a kiss."
|
||||
message_param = "blows a kiss to %t."
|
||||
|
||||
/datum/emote/living/kiss/run_emote(mob/living/user, params, type_override, intentional)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
var/kiss_type = /obj/item/kisser
|
||||
|
||||
if(HAS_TRAIT(user, TRAIT_KISS_OF_DEATH))
|
||||
kiss_type = /obj/item/kisser/death
|
||||
|
||||
var/obj/item/kiss_blower = new kiss_type(user)
|
||||
if(user.put_in_hands(kiss_blower))
|
||||
to_chat(user, "<span class='notice'>You ready your kiss-blowing hand.</span>")
|
||||
else
|
||||
qdel(kiss_blower)
|
||||
to_chat(user, "<span class='warning'>You're incapable of blowing a kiss in your current state.</span>")
|
||||
|
||||
/datum/emote/living/laugh
|
||||
key = "laugh"
|
||||
|
||||
@@ -414,8 +414,7 @@
|
||||
H.hairstyle = hairstyle
|
||||
H.facial_hair_color = facial_hair_color
|
||||
H.facial_hairstyle = facial_hairstyle
|
||||
H.lip_style = lip_style
|
||||
H.lip_color = lip_color
|
||||
H.update_lips(lip_style, lip_color, stored_lipstick_trait)
|
||||
if(real_name)
|
||||
C.real_name = real_name
|
||||
real_name = ""
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
var/lip_style = null
|
||||
var/lip_color = "white"
|
||||
|
||||
var/stored_lipstick_trait
|
||||
|
||||
|
||||
/obj/item/bodypart/head/Destroy()
|
||||
QDEL_NULL(brainmob) //order is sensitive, see warning in handle_atom_del() below
|
||||
@@ -139,6 +141,7 @@
|
||||
hairstyle = "Bald"
|
||||
facial_hairstyle = "Shaved"
|
||||
lip_style = null
|
||||
stored_lipstick_trait = null
|
||||
|
||||
else if(!animal_origin)
|
||||
var/mob/living/carbon/human/H = C
|
||||
|
||||
@@ -16,3 +16,5 @@ revolverspin1, revolverspin2, and revolverspin3 are cut from a recording
|
||||
captured by the BBC
|
||||
"Gunfire & Guns: Revolver, Chambers spinning.": http://bbcsfx.acropolis.org.uk/assets/07027165.wav
|
||||
bbc.co.uk - (c) copyright 2018 BBC
|
||||
|
||||
kiss sound: https://freesound.org/people/stereostereo/sounds/117355/
|
||||
Binary file not shown.
@@ -1058,6 +1058,7 @@
|
||||
#include "code\game\objects\items\flamethrower.dm"
|
||||
#include "code\game\objects\items\gift.dm"
|
||||
#include "code\game\objects\items\granters.dm"
|
||||
#include "code\game\objects\items\hand_items.dm"
|
||||
#include "code\game\objects\items\handcuffs.dm"
|
||||
#include "code\game\objects\items\his_grace.dm"
|
||||
#include "code\game\objects\items\holosign_creator.dm"
|
||||
|
||||
Reference in New Issue
Block a user