mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-28 23:58:07 +01:00
PKP Mind Virus: Glorfing Cigs edition (#69904)
* PKP Mind Virus: Glorfing Cigs edition If you glorf while smoking you'll get the cig stuck in your throat, leaving you unable to breath, speak, or eat. Based off the sort of cig you have a random delay before you vomit up the cig. (Pipes last forever don't philoso-larp kids). Other players can help you by using the help action on you while you're hurt, which leads to something imitating the himelich manuver, a broken chest, and a vomited up blockage. Other forms of vomiting will work too, if that's even applicable. Oh I fixed a bug in vomit code. Because paralyzing you forces you to the ground, thus changing your dir, paralyzing vomits always went down. This is cringe, easy fix tho. Ah and I added some extra functionality to sound loops too. You can set them to pick and take from the provided list, then refilling it once it's empty. This keeps some variety while preventing the risk of repeat sounds Oh and I added some new signals. Some misc ones, for force say and a different kind of help then the existing one, which I renamed. One for breathing, which I used to clean up leroxin. I was planning to hook this but didn't need to in the end. One for vomiting too. and eating. Also added a way to update looping timer delays. Forgot about that, I also added variable delay for sound loops, because I thought it sounded better here. Features audio courtesy of our very own san7890, and the quake jump sound.
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
///Called from /mob/living/carbon/help_shake_act, before any hugs have ocurred. (mob/living/helper)
|
||||
#define COMSIG_CARBON_PRE_HELP_ACT "carbon_pre_help"
|
||||
/// Stops the rest of help act (hugging, etc) from occuring
|
||||
///Called from /datum/species/proc/help : (mob/living/carbon/human/helper, datum/martial_art/helper_style)
|
||||
#define COMSIG_CARBON_PRE_HELP "carbon_pre_help"
|
||||
/// Stops the rest of the help
|
||||
#define COMPONENT_BLOCK_HELP_ACT (1<<0)
|
||||
|
||||
///Called from /mob/living/carbon/help_shake_act, before any hugs have ocurred. (mob/living/helper)
|
||||
#define COMSIG_CARBON_PRE_MISC_HELP "carbon_pre_misc_help"
|
||||
/// Stops the rest of help act (hugging, etc) from occuring
|
||||
#define COMPONENT_BLOCK_MISC_HELP (1<<0)
|
||||
|
||||
///Called from /mob/living/carbon/help_shake_act on the person being helped, after any hugs have ocurred. (mob/living/helper)
|
||||
#define COMSIG_CARBON_HELP_ACT "carbon_help"
|
||||
///Called from /mob/living/carbon/help_shake_act on the helper, after any hugs have ocurred. (mob/living/helped)
|
||||
@@ -84,10 +89,20 @@
|
||||
#define COMPONENT_OVERRIDE_HEALTH_HUD (1<<0)
|
||||
///Called when a carbon updates their sanity (source = carbon)
|
||||
#define COMSIG_CARBON_SANITY_UPDATE "carbon_sanity_update"
|
||||
///Called when a carbon attempts to breath, before the breath has actually occured
|
||||
#define COMSIG_CARBON_ATTEMPT_BREATHE "carbon_attempt_breathe"
|
||||
// Prevents the breath
|
||||
#define COMSIG_CARBON_BLOCK_BREATH (1 << 0)
|
||||
///Called when a carbon breathes, before the breath has actually occured
|
||||
#define COMSIG_CARBON_PRE_BREATHE "carbon_pre_breathe"
|
||||
///Called when a carbon updates their mood
|
||||
#define COMSIG_CARBON_MOOD_UPDATE "carbon_mood_update"
|
||||
///Called when a carbon attempts to eat (eating)
|
||||
#define COMSIG_CARBON_ATTEMPT_EAT "carbon_attempt_eat"
|
||||
// Prevents the breath
|
||||
#define COMSIG_CARBON_BLOCK_EAT (1 << 0)
|
||||
///Called when a carbon vomits : (distance, force)
|
||||
#define COMSIG_CARBON_VOMITED "carbon_vomited"
|
||||
|
||||
// /mob/living/carbon/human signals
|
||||
|
||||
@@ -103,9 +118,11 @@
|
||||
#define COMSIG_HUMAN_EARLY_UNARMED_ATTACK "human_early_unarmed_attack"
|
||||
///from mob/living/carbon/human/UnarmedAttack(): (atom/target, proximity, modifiers)
|
||||
#define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack"
|
||||
//from /mob/living/carbon/human/proc/check_shields(): (atom/hit_by, damage, attack_text, attack_type, armour_penetration)
|
||||
///from /mob/living/carbon/human/proc/check_shields(): (atom/hit_by, damage, attack_text, attack_type, armour_penetration)
|
||||
#define COMSIG_HUMAN_CHECK_SHIELDS "human_check_shields"
|
||||
#define SHIELD_BLOCK (1<<0)
|
||||
///from /mob/living/carbon/human/proc/force_say(): ()
|
||||
#define COMSIG_HUMAN_FORCESAY "human_forcesay"
|
||||
|
||||
// Mob transformation signals
|
||||
///Called when a human turns into a monkey, from /mob/living/carbon/proc/finish_monkeyize()
|
||||
|
||||
@@ -855,6 +855,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
|
||||
#define ITEM_SCALING_TRAIT "item_scaling"
|
||||
/// Trait given by Objects that provide blindsight
|
||||
#define ITEM_BLIND_TRAIT "blind_item_trait"
|
||||
/// Trait given by choking
|
||||
#define CHOKING_TRAIT "choking_trait"
|
||||
/// Trait given by hallucinations
|
||||
#define HALLUCINATION_TRAIT "hallucination_trait"
|
||||
|
||||
|
||||
@@ -662,6 +662,32 @@ SUBSYSTEM_DEF(timer)
|
||||
return null
|
||||
return timer.timeToRun - (timer.flags & TIMER_CLIENT_TIME ? REALTIMEOFDAY : world.time)
|
||||
|
||||
/**
|
||||
* Update the delay on an existing LOOPING timer
|
||||
* Will come into effect on the next process
|
||||
*
|
||||
* Arguments:
|
||||
* * id a timerid or a /datum/timedevent
|
||||
* * new_wait the new wait to give this looping timer
|
||||
*/
|
||||
/proc/updatetimedelay(id, new_wait, datum/controller/subsystem/timer/timer_subsystem)
|
||||
if (!id)
|
||||
return
|
||||
if (id == TIMER_ID_NULL)
|
||||
CRASH("Tried to update the wait of null timerid. Use TIMER_STOPPABLE flag")
|
||||
if (istype(id, /datum/timedevent))
|
||||
var/datum/timedevent/timer = id
|
||||
timer.wait = new_wait
|
||||
return
|
||||
timer_subsystem = timer_subsystem || SStimer
|
||||
//id is string
|
||||
var/datum/timedevent/timer = timer_subsystem.timer_id_dict[id]
|
||||
if(!timer || timer.spent)
|
||||
return
|
||||
if(!(timer.flags & TIMER_LOOP))
|
||||
CRASH("Tried to update the wait of a non looping timer. This is not supported")
|
||||
timer.wait = new_wait
|
||||
|
||||
#undef BUCKET_LEN
|
||||
#undef BUCKET_POS
|
||||
#undef TIMER_MAX
|
||||
|
||||
@@ -451,6 +451,8 @@ Behavior that's still missing from this component that original food items had t
|
||||
var/who = (isnull(feeder) || eater == feeder) ? "your" : "[eater.p_their()]"
|
||||
to_chat(feeder, span_warning("You have to remove [who] [covered] first!"))
|
||||
return FALSE
|
||||
if(SEND_SIGNAL(eater, COMSIG_CARBON_ATTEMPT_EAT, parent) & COMSIG_CARBON_BLOCK_EAT)
|
||||
return
|
||||
return TRUE
|
||||
|
||||
///Check foodtypes to see if we should send a moodlet
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
* A datum for sounds that need to loop, with a high amount of configurability.
|
||||
*/
|
||||
/datum/looping_sound
|
||||
/// The source of the sound, or the recipient of the sound.
|
||||
var/atom/parent
|
||||
/// (list or soundfile) Since this can be either a list or a single soundfile you can have random sounds. May contain further lists but must contain a soundfile at the end.
|
||||
var/mid_sounds
|
||||
/// The length of time to wait between playing mid_sounds.
|
||||
var/mid_length
|
||||
/// Amount of time to add/take away from the mid length, randomly
|
||||
var/mid_length_vary = 0
|
||||
/// If we should always play each sound once per loop of all sounds. Weights here only really effect order, and could be disgarded
|
||||
var/each_once = FALSE
|
||||
/// Override for volume of start sound.
|
||||
var/start_volume
|
||||
/// (soundfile) Played before starting the mid_sounds loop.
|
||||
@@ -26,26 +28,34 @@
|
||||
var/vary = FALSE
|
||||
/// The max amount of loops to run for.
|
||||
var/max_loops
|
||||
/// If true, plays directly to provided atoms instead of from them.
|
||||
var/direct
|
||||
/// The extra range of the sound in tiles, defaults to 0.
|
||||
var/extra_range = 0
|
||||
/// The ID of the timer that's used to loop the sounds.
|
||||
var/timer_id
|
||||
/// How much the sound will be affected by falloff per tile.
|
||||
var/falloff_exponent
|
||||
/// The falloff distance of the sound,
|
||||
var/falloff_distance
|
||||
/// Do we skip the starting sounds?
|
||||
var/skip_starting_sounds = FALSE
|
||||
/// Are the sounds affected by pressure? Defaults to TRUE.
|
||||
var/pressure_affected = TRUE
|
||||
/// Are the sounds subject to reverb? Defaults to TRUE.
|
||||
var/use_reverb = TRUE
|
||||
/// Are we ignoring walls? Defaults to TRUE.
|
||||
var/ignore_walls = TRUE
|
||||
|
||||
// State stuff
|
||||
/// The source of the sound, or the recipient of the sound.
|
||||
var/atom/parent
|
||||
/// The ID of the timer that's used to loop the sounds.
|
||||
var/timer_id
|
||||
/// Has the looping started yet?
|
||||
var/loop_started = FALSE
|
||||
/// If we're using cut_mid, this is the list we cut from
|
||||
var/list/cut_list
|
||||
|
||||
// Args
|
||||
/// Do we skip the starting sounds?
|
||||
var/skip_starting_sounds = FALSE
|
||||
/// If true, plays directly to provided atoms instead of from them.
|
||||
var/direct
|
||||
|
||||
/datum/looping_sound/New(_parent, start_immediately = FALSE, _direct = FALSE, _skip_starting_sounds = FALSE)
|
||||
if(!mid_sounds)
|
||||
@@ -108,6 +118,9 @@
|
||||
if(max_loops && world.time >= start_time + mid_length * max_loops)
|
||||
stop()
|
||||
return
|
||||
// If we have a timer, we're varying mid length, and this is happening while we're runnin mid_sounds
|
||||
if(timer_id && mid_length_vary && start_time)
|
||||
updatetimedelay(timer_id, mid_length + rand(-mid_length_vary, mid_length_vary))
|
||||
if(!chance || prob(chance))
|
||||
play(get_sound())
|
||||
|
||||
@@ -140,10 +153,41 @@
|
||||
|
||||
/// Returns the sound we should now be playing.
|
||||
/datum/looping_sound/proc/get_sound(_mid_sounds)
|
||||
. = _mid_sounds || mid_sounds
|
||||
var/list/play_from = _mid_sounds || mid_sounds
|
||||
if(!each_once)
|
||||
. = play_from
|
||||
while(!isfile(.) && !isnull(.))
|
||||
. = pick_weight(.)
|
||||
return .
|
||||
|
||||
|
||||
if(!length(cut_list))
|
||||
cut_list = shuffle(play_from.Copy())
|
||||
var/list/tree = list()
|
||||
. = cut_list
|
||||
while(!isfile(.) && !isnull(.))
|
||||
// Tree is a list of lists containign files
|
||||
// If an entry in the tree goes to 0 length, we cut it from the list
|
||||
tree += list(.)
|
||||
. = pick_weight(.)
|
||||
|
||||
if(!isfile(.))
|
||||
return
|
||||
|
||||
// Remove the sound file
|
||||
tree[length(tree)] -= .
|
||||
|
||||
// Walk the tree bottom up, remove any lists that are empty
|
||||
// Don't do anything for the topmost list, cause we do not care
|
||||
for(var/i in length(tree) to 2 step -1)
|
||||
var/list/branch = tree[i]
|
||||
if(length(branch))
|
||||
break
|
||||
tree[i - 1] -= list(branch) // Remove the empty list
|
||||
return .
|
||||
|
||||
|
||||
|
||||
/// A proc that's there to handle delaying the main sounds if there's a start_sound, and simply starting the sound loop in general.
|
||||
/datum/looping_sound/proc/on_start()
|
||||
var/start_wait = 0
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/datum/looping_sound/choking
|
||||
mid_sounds = list('sound/creatures/gag1.ogg' = 1, 'sound/creatures/gag2.ogg' = 1, 'sound/creatures/gag3.ogg' = 1, 'sound/creatures/gag4.ogg' = 1, 'sound/creatures/gag5.ogg' = 1)
|
||||
mid_length = 1.6 SECONDS
|
||||
mid_length_vary = 0.3 SECONDS
|
||||
each_once = TRUE
|
||||
volume = 90
|
||||
// We want you to be hard to hear far away
|
||||
falloff_exponent = 12
|
||||
pressure_affected = TRUE
|
||||
vary = TRUE
|
||||
// Same as above
|
||||
ignore_walls = FALSE
|
||||
@@ -154,6 +154,10 @@
|
||||
description = "I'm nervous and on edge and I can't stand still!!"
|
||||
mood_change = -2
|
||||
|
||||
/datum/mood_event/choke
|
||||
description = "I CAN'T BREATHE!!!"
|
||||
mood_change = -10
|
||||
|
||||
/datum/mood_event/vomit
|
||||
description = "I just threw up. Gross."
|
||||
mood_change = -2
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
// Status effect given to you if you're choking on something
|
||||
/datum/status_effect/choke
|
||||
id = "choke"
|
||||
tick_interval = 0.2 SECONDS
|
||||
alert_type = null
|
||||
/// Weakref to the thing we're choking on
|
||||
var/datum/weakref/choking_on_ref
|
||||
/// If the thing we're choking on is on fire
|
||||
var/flaming
|
||||
|
||||
/// The particle holder we're using to run our ash effects
|
||||
var/obj/effect/abstract/particle_holder/ash
|
||||
/// Our choking audio loop
|
||||
var/datum/looping_sound/choke_loop
|
||||
/// The delta client.pixel_y we've got
|
||||
var/delta_y = 0
|
||||
/// The delta client.pixel_x we've got
|
||||
var/delta_x = 0
|
||||
|
||||
/datum/status_effect/choke/on_creation(mob/living/new_owner, atom/movable/choke_on, flaming = FALSE, vomit_delay = -1)
|
||||
choking_on_ref = WEAKREF(choke_on)
|
||||
src.flaming = flaming
|
||||
src.duration = vomit_delay
|
||||
return ..()
|
||||
|
||||
/datum/status_effect/choke/on_apply()
|
||||
if(!iscarbon(owner)) // Ghosts do not have stomachs, and non carbons can't vomit
|
||||
return FALSE
|
||||
var/atom/movable/choking_on = choking_on_ref?.resolve()
|
||||
if(!choking_on)
|
||||
return FALSE
|
||||
// If we've got an item, remove it from storage
|
||||
if(isitem(choking_on))
|
||||
var/obj/item/chokin_item = choking_on
|
||||
if(chokin_item.item_flags & IN_INVENTORY)
|
||||
var/mob/inventory_in = chokin_item.loc
|
||||
inventory_in.transferItemToLoc(chokin_item, owner, force = TRUE, silent = TRUE)
|
||||
else if(!(chokin_item.item_flags & IN_STORAGE) || !chokin_item.remove_item_from_storage(owner))
|
||||
choking_on.forceMove(owner) // backup
|
||||
else
|
||||
choking_on.forceMove(owner)
|
||||
RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOBREATH), .proc/no_breathing)
|
||||
RegisterSignal(owner, COMSIG_MOB_LOGOUT, .proc/on_logout)
|
||||
RegisterSignal(owner, COMSIG_LIVING_POST_FULLY_HEAL, .proc/remove_choke)
|
||||
RegisterSignal(owner, COMSIG_LIVING_DEATH, .proc/on_death)
|
||||
// Of note, this means plasma lovers lose some methods of vomiting up
|
||||
RegisterSignal(owner, COMSIG_CARBON_VOMITED, .proc/on_vomit)
|
||||
RegisterSignal(owner, COMSIG_CARBON_ATTEMPT_EAT, .proc/attempt_eat)
|
||||
RegisterSignal(owner, COMSIG_CARBON_PRE_HELP, .proc/helped)
|
||||
RegisterSignal(owner, COMSIG_CARBON_PRE_MISC_HELP, .proc/shook)
|
||||
|
||||
RegisterSignal(choking_on, COMSIG_PARENT_QDELETING, .proc/remove_choke)
|
||||
RegisterSignal(choking_on, COMSIG_MOVABLE_MOVED, .proc/hazard_moved)
|
||||
ADD_TRAIT(owner, TRAIT_MUTE, CHOKING_TRAIT)
|
||||
|
||||
owner.add_mood_event(id, /datum/mood_event/choke)
|
||||
//stop on death
|
||||
choke_loop = new /datum/looping_sound/choking(owner)
|
||||
check_audio_state()
|
||||
|
||||
owner.visible_message(span_bolddanger("[owner] tries to speak, but can't! They're choking!"), \
|
||||
span_userdanger("You try to breath, but there's a block! You're choking!"), \
|
||||
)
|
||||
|
||||
//barticles
|
||||
if(flaming)
|
||||
ash = new(owner, /particles/smoke/ash)
|
||||
var/clear_in = rand(15 SECONDS, 25 SECONDS)
|
||||
if(duration != -1)
|
||||
clear_in = min(duration, clear_in)
|
||||
addtimer(CALLBACK(src, .proc/clear_flame), clear_in)
|
||||
return TRUE
|
||||
|
||||
/datum/status_effect/choke/proc/should_do_effects()
|
||||
return owner.stat != DEAD && !HAS_TRAIT(owner, TRAIT_NOBREATH)
|
||||
|
||||
/datum/status_effect/choke/proc/check_audio_state()
|
||||
if(!should_do_effects())
|
||||
choke_loop.stop()
|
||||
return
|
||||
choke_loop.start()
|
||||
|
||||
/datum/status_effect/choke/on_remove()
|
||||
owner.clear_mood_event(id)
|
||||
REMOVE_TRAIT(owner, TRAIT_MUTE, CHOKING_TRAIT)
|
||||
clean_client()
|
||||
clear_flame()
|
||||
if(choke_loop)
|
||||
QDEL_NULL(choke_loop)
|
||||
vomit_up()
|
||||
return ..()
|
||||
|
||||
/datum/status_effect/choke/proc/clean_client()
|
||||
// juuust in case, reset our x and y's
|
||||
var/client/client_owner = owner.canon_client
|
||||
if(client_owner)
|
||||
// Ok listen I want to use non relative animates here but BYOND WILL NOT LET ME
|
||||
// REEEEEEEEE
|
||||
animate(client_owner, pixel_x = client_owner.pixel_x - delta_x, pixel_y = client_owner.pixel_y - delta_y, 0.05 SECONDS, ANIMATION_PARALLEL)
|
||||
delta_x = 0
|
||||
delta_y = 0
|
||||
|
||||
/datum/status_effect/choke/proc/clear_flame()
|
||||
flaming = FALSE
|
||||
if(ash)
|
||||
QDEL_NULL(ash)
|
||||
|
||||
/datum/status_effect/choke/proc/vomit_up()
|
||||
var/atom/movable/choking_on = choking_on_ref?.resolve()
|
||||
if(choking_on && iscarbon(owner))
|
||||
var/mob/living/carbon/carbon_owner = owner
|
||||
// This will yeet the thing we're choking on out of us
|
||||
carbon_owner.vomit(lost_nutrition = 20, force = TRUE, distance = 2)
|
||||
|
||||
/datum/status_effect/choke/proc/on_vomit(mob/source, distance, force)
|
||||
SIGNAL_HANDLER
|
||||
var/atom/movable/choking_on = choking_on_ref?.resolve()
|
||||
if(choking_on)
|
||||
choking_on_ref = null
|
||||
choking_on.forceMove(get_turf(source)) // Gotta be on a tile to throw yourself bestie
|
||||
var/atom/target = get_edge_target_turf(source, source.dir)
|
||||
choking_on.throw_at(target, distance, 1, source)
|
||||
|
||||
/datum/status_effect/choke/get_examine_text()
|
||||
return span_boldwarning("[owner.p_they(TRUE)] [owner.p_are()] choking!")
|
||||
|
||||
/datum/status_effect/choke/proc/remove_choke(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
if(!QDELETED(src))
|
||||
qdel(src)
|
||||
|
||||
/datum/status_effect/choke/proc/hazard_moved(atom/movable/source)
|
||||
SIGNAL_HANDLER
|
||||
if(QDELETED(src))
|
||||
return
|
||||
if(source.loc != owner)
|
||||
qdel(src)
|
||||
|
||||
/datum/status_effect/choke/proc/no_breathing(mob/living/source)
|
||||
SIGNAL_HANDLER
|
||||
RegisterSignal(source, SIGNAL_REMOVETRAIT(TRAIT_NOBREATH), .proc/on_breathable)
|
||||
check_audio_state()
|
||||
|
||||
/datum/status_effect/choke/proc/on_breathable(mob/living/source)
|
||||
SIGNAL_HANDLER
|
||||
UnregisterSignal(source, SIGNAL_REMOVETRAIT(TRAIT_NOBREATH))
|
||||
check_audio_state()
|
||||
|
||||
/datum/status_effect/choke/proc/on_logout(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
clean_client()
|
||||
|
||||
/datum/status_effect/choke/proc/on_death(mob/living/source)
|
||||
SIGNAL_HANDLER
|
||||
RegisterSignal(source, COMSIG_LIVING_REVIVE, .proc/on_revive)
|
||||
check_audio_state()
|
||||
|
||||
/datum/status_effect/choke/proc/on_revive(mob/living/source)
|
||||
SIGNAL_HANDLER
|
||||
UnregisterSignal(source, COMSIG_LIVING_REVIVE)
|
||||
check_audio_state()
|
||||
|
||||
/datum/status_effect/choke/proc/attempt_eat(mob/source, atom/eating)
|
||||
SIGNAL_HANDLER
|
||||
source.balloon_alert(source, "can't get it down!")
|
||||
return COMSIG_CARBON_BLOCK_EAT
|
||||
|
||||
/datum/status_effect/choke/proc/helped(mob/source, mob/helping)
|
||||
SIGNAL_HANDLER
|
||||
INVOKE_ASYNC(src, .proc/heimlich, source, helping)
|
||||
return COMPONENT_BLOCK_HELP_ACT
|
||||
|
||||
/datum/status_effect/choke/proc/shook(mob/source, mob/helping)
|
||||
SIGNAL_HANDLER
|
||||
INVOKE_ASYNC(src, .proc/heimlich, source, helping)
|
||||
return COMPONENT_BLOCK_MISC_HELP
|
||||
|
||||
/datum/status_effect/choke/proc/heimlich(mob/victim, mob/aggressor)
|
||||
if(victim == aggressor)
|
||||
return
|
||||
if(DOING_INTERACTION_WITH_TARGET(aggressor, victim))
|
||||
victim.balloon_alert(aggressor, "already helping!")
|
||||
return
|
||||
if(DOING_INTERACTION(aggressor, "heimlich"))
|
||||
victim.balloon_alert(aggressor, "already helping someone!")
|
||||
return
|
||||
|
||||
if(!thrusting_continues(victim, aggressor, before_work = TRUE))
|
||||
return
|
||||
aggressor.start_pulling(victim)
|
||||
// I want the thing to look vaugely like the heimlich. Sue me
|
||||
victim.setDir(aggressor.dir)
|
||||
|
||||
var/hand_name = "hand"
|
||||
if(!iscarbon(aggressor))
|
||||
hand_name = "paw" // Fuck you
|
||||
|
||||
var/mob/living/livin_victim = victim
|
||||
if(iscarbon(aggressor) && livin_victim.body_position == STANDING_UP)
|
||||
owner.visible_message(span_warning("[aggressor] wraps [aggressor.p_their()] arms around [victim]'s stomach, and begins thrusting [aggressor.p_their()] fists towards themselves!"), \
|
||||
span_boldwarning("[aggressor] wraps [aggressor.p_their()] arms around you, and begins thrusting their hands into your chest. [capitalize(GLOB.deity)] that hurts!"), \
|
||||
)
|
||||
else
|
||||
owner.visible_message(span_warning("[aggressor] places [aggressor.p_their()] [hand_name]s on [victim]'s back, and begins forcefully striking it!"), \
|
||||
span_boldwarning("You feel [aggressor]\s [hand_name]s on your back, and then repeated striking!"))
|
||||
|
||||
if(!do_after_mob(aggressor, victim, 7 SECONDS, extra_checks = CALLBACK(src, .proc/thrusting_continues, victim, aggressor), interaction_key = "heimlich"))
|
||||
aggressor.stop_pulling()
|
||||
return
|
||||
aggressor.stop_pulling()
|
||||
|
||||
var/atom/movable/choking_on = choking_on_ref?.resolve()
|
||||
owner.visible_message(span_green("[victim] vomits up \the[choking_on]. [victim.p_theyre()] gonna make it!"), \
|
||||
span_green("You vomit up that accursed blockage. YOU CAN BREATHE! The broken chest is a hell of a price to pay."))
|
||||
if(iscarbon(victim))
|
||||
var/mob/living/carbon/carbon_victim = victim
|
||||
var/obj/item/bodypart/chest = carbon_victim.get_bodypart(BODY_ZONE_CHEST)
|
||||
if(chest)
|
||||
chest.force_wound_upwards(/datum/wound/blunt/severe)
|
||||
playsound(owner, 'sound/creatures/crack_vomit.ogg', 120, extrarange = 5, falloff_exponent = 4)
|
||||
vomit_up()
|
||||
|
||||
/datum/status_effect/choke/proc/mirror_dir(atom/source, old_dir, new_dir)
|
||||
SIGNAL_HANDLER
|
||||
owner.dir = new_dir
|
||||
|
||||
/datum/status_effect/choke/proc/thrusting_continues(mob/living/victim, mob/aggressor, before_work = FALSE)
|
||||
if(iscarbon(aggressor))
|
||||
var/free_hands = 0
|
||||
// Listen bud, you need at least 2 free hands for this
|
||||
for(var/hand_i in 1 to length(aggressor.held_items))
|
||||
if(!aggressor.has_hand_for_held_index(hand_i) || aggressor.held_items[hand_i])
|
||||
continue
|
||||
free_hands += 1
|
||||
if(free_hands < 2)
|
||||
victim.balloon_alert(aggressor, "need 2 free hands!")
|
||||
return FALSE
|
||||
|
||||
if(iscarbon(victim))
|
||||
var/mob/living/carbon/carbon_victim = victim
|
||||
if(!carbon_victim.appears_alive())
|
||||
victim.balloon_alert(aggressor, "too late...")
|
||||
return FALSE
|
||||
|
||||
if(!choking_on_ref)
|
||||
return FALSE
|
||||
|
||||
if(!before_work)
|
||||
// This check isn't valid at first because it looks dumb if other things fail
|
||||
if(victim.pulledby != aggressor)
|
||||
victim.balloon_alert(aggressor, "must be able to move them!")
|
||||
return FALSE
|
||||
|
||||
// Similarly, but also this is a burden of knowhow that's cringe
|
||||
if(aggressor.dir != get_dir(aggressor, victim))
|
||||
victim.balloon_alert(aggressor, "must be facing them!")
|
||||
return FALSE
|
||||
|
||||
// See above
|
||||
if(victim.dir != aggressor.dir)
|
||||
victim.balloon_alert(aggressor, "must be facing the same way!")
|
||||
return FALSE
|
||||
|
||||
// If we ain't starting, deal a tad bit of brute, as a treat
|
||||
// Note, we attempt to process 10 times a second, so over 7 seconds this'll deal 14 brute
|
||||
if(!before_work)
|
||||
victim.adjustBruteLoss(0.2)
|
||||
return TRUE
|
||||
|
||||
/datum/status_effect/choke/tick(delta_time)
|
||||
if(!should_do_effects())
|
||||
return
|
||||
|
||||
deal_damage(delta_time)
|
||||
|
||||
var/client/client_owner = owner.client
|
||||
if(client_owner)
|
||||
do_vfx(client_owner)
|
||||
|
||||
/datum/status_effect/choke/proc/deal_damage(delta_time)
|
||||
owner.losebreath += 1 * delta_time // 1 breath loss a second. This will deal additional breath damage, and prevent breathing
|
||||
if(flaming)
|
||||
var/obj/item/bodypart/head = owner.get_bodypart(BODY_ZONE_HEAD)
|
||||
if(head)
|
||||
head.receive_damage(0, 2 * delta_time, 2 * delta_time)
|
||||
|
||||
/datum/status_effect/choke/proc/do_vfx(client/vfx_on)
|
||||
var/old_x = delta_x
|
||||
delta_x = WRAP(delta_x + rand(1, 6), -13, 13)
|
||||
var/old_y = delta_y
|
||||
delta_y = WRAP(delta_y + rand(1, 6), -13, 13)
|
||||
// We run on fast process, which procs us once every 0.2 seconds. We want to move every 0.05, so we do 4 animates
|
||||
// In other news, ANIMATION_RELATIVE does not work on client.pixel_x/y. Thanks byond
|
||||
animate(vfx_on, pixel_x = vfx_on.pixel_x + (delta_x - old_x), pixel_y = vfx_on.pixel_y + (delta_y - old_y), time = 0.05 SECONDS, flags = ANIMATION_PARALLEL)
|
||||
for(var/i in 1 to 3)
|
||||
old_x = delta_x
|
||||
delta_x = WRAP(delta_x + rand(1, 6), -13, 13)
|
||||
old_y = delta_y
|
||||
delta_y = WRAP(delta_y + rand(1, 6), -13, 13)
|
||||
animate(pixel_x = vfx_on.pixel_x + (delta_x - old_x), pixel_y = vfx_on.pixel_y + (delta_y - old_y), time = 0.05 SECONDS)
|
||||
@@ -12,11 +12,11 @@
|
||||
/datum/status_effect/strandling/on_apply()
|
||||
RegisterSignal(owner, COMSIG_CARBON_PRE_BREATHE, .proc/on_breathe)
|
||||
RegisterSignal(owner, COMSIG_ATOM_TOOL_ACT(TOOL_WIRECUTTER), .proc/on_cut)
|
||||
RegisterSignal(owner, COMSIG_CARBON_PRE_HELP_ACT, .proc/on_self_check)
|
||||
RegisterSignal(owner, COMSIG_CARBON_PRE_MISC_HELP, .proc/on_self_check)
|
||||
return TRUE
|
||||
|
||||
/datum/status_effect/strandling/on_remove()
|
||||
UnregisterSignal(owner, list(COMSIG_CARBON_PRE_BREATHE, COMSIG_ATOM_TOOL_ACT(TOOL_WIRECUTTER), COMSIG_CARBON_PRE_HELP_ACT))
|
||||
UnregisterSignal(owner, list(COMSIG_CARBON_PRE_BREATHE, COMSIG_ATOM_TOOL_ACT(TOOL_WIRECUTTER), COMSIG_CARBON_PRE_MISC_HELP))
|
||||
|
||||
/datum/status_effect/strandling/get_examine_text()
|
||||
return span_warning("[owner.p_they(TRUE)] seem[owner.p_s()] to be being choked by some durathread strands. You may be able to <b>cut</b> them off.")
|
||||
@@ -40,7 +40,7 @@
|
||||
INVOKE_ASYNC(src, .proc/try_remove_effect, user, tool)
|
||||
return COMPONENT_BLOCK_TOOL_ATTACK
|
||||
|
||||
/// Signal proc for [COMSIG_CARBON_PRE_HELP_ACT], allowing someone to remove the effect by hand
|
||||
/// Signal proc for [COMSIG_CARBON_PRE_MISC_HELP], allowing someone to remove the effect by hand
|
||||
/datum/status_effect/strandling/proc/on_self_check(mob/living/carbon/source, mob/living/helper)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
return
|
||||
|
||||
INVOKE_ASYNC(src, .proc/try_remove_effect, helper)
|
||||
return COMPONENT_BLOCK_HELP_ACT
|
||||
return COMPONENT_BLOCK_MISC_HELP
|
||||
|
||||
/**
|
||||
* Attempts a do_after to remove the effect and stop the strangling.
|
||||
|
||||
@@ -22,3 +22,13 @@
|
||||
spawning = 1
|
||||
velocity = list(0, 0.3, 0)
|
||||
friction = 0.25
|
||||
|
||||
/particles/smoke/ash
|
||||
icon_state = list("ash_1" = 2, "ash_2" = 2, "ash_3" = 1, "smoke_1" = 3, "smoke_2" = 2)
|
||||
count = 500
|
||||
spawning = 1
|
||||
lifespan = 1 SECONDS
|
||||
fade = 0.2 SECONDS
|
||||
fadein = 0.7 SECONDS
|
||||
position = generator("vector", list(-3, 5, 0), list(3, 6.5, 0), NORMAL_RAND)
|
||||
velocity = generator("vector", list(-0.1, 0.4, 0), list(0.1, 0.5, 0), NORMAL_RAND)
|
||||
|
||||
@@ -163,7 +163,10 @@ CIGARETTE PACKETS ARE IN FANCY.DM
|
||||
var/smoke_all = FALSE
|
||||
/// How much damage this deals to the lungs per drag.
|
||||
var/lung_harm = 1
|
||||
|
||||
/// If, when glorf'd, we will choke on this cig forever
|
||||
var/choke_forever = FALSE
|
||||
/// When choking, what is the maximum amount of time we COULD choke for
|
||||
var/choke_time_max = 30 SECONDS // I am mean
|
||||
|
||||
/obj/item/clothing/mask/cigarette/Initialize(mapload)
|
||||
. = ..()
|
||||
@@ -181,6 +184,21 @@ CIGARETTE PACKETS ARE IN FANCY.DM
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
return ..()
|
||||
|
||||
/obj/item/clothing/mask/cigarette/equipped(mob/equipee, slot)
|
||||
. = ..()
|
||||
if(!(slot & ITEM_SLOT_MASK))
|
||||
UnregisterSignal(equipee, COMSIG_HUMAN_FORCESAY)
|
||||
return
|
||||
RegisterSignal(equipee, COMSIG_HUMAN_FORCESAY, .proc/on_forcesay)
|
||||
|
||||
/obj/item/clothing/mask/cigarette/dropped(mob/dropee)
|
||||
. = ..()
|
||||
UnregisterSignal(dropee, COMSIG_HUMAN_FORCESAY)
|
||||
|
||||
/obj/item/clothing/mask/cigarette/proc/on_forcesay(mob/living/source)
|
||||
SIGNAL_HANDLER
|
||||
source.apply_status_effect(/datum/status_effect/choke, src, lit, choke_forever ? -1 : rand(25 SECONDS, choke_time_max))
|
||||
|
||||
/obj/item/clothing/mask/cigarette/suicide_act(mob/user)
|
||||
user.visible_message(span_suicide("[user] is huffing [src] as quickly as [user.p_they()] can! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer."))
|
||||
return (TOXLOSS|OXYLOSS)
|
||||
@@ -431,6 +449,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
|
||||
smoketime = 4 MINUTES
|
||||
chem_volume = 50
|
||||
list_reagents = null
|
||||
choke_time_max = 40 SECONDS
|
||||
|
||||
/obj/item/clothing/mask/cigarette/rollie/Initialize(mapload)
|
||||
name = pick(list(
|
||||
@@ -503,6 +522,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
|
||||
heat = 473.15 // Lowered so that the sugar can be carmalized, but not burnt.
|
||||
lung_harm = 0.5
|
||||
list_reagents = list(/datum/reagent/consumable/sugar = 20)
|
||||
choke_time_max = 70 SECONDS // This shit really is deadly
|
||||
|
||||
/obj/item/clothing/mask/cigarette/candy/nicotine
|
||||
desc = "For all ages*! Doesn't contain any* amount of nicotine. Health and safety risks can be read on the tip of the cigarette."
|
||||
@@ -538,6 +558,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
|
||||
smoketime = 11 MINUTES
|
||||
chem_volume = 40
|
||||
list_reagents = list(/datum/reagent/drug/nicotine = 25)
|
||||
choke_time_max = 40 SECONDS
|
||||
|
||||
/obj/item/clothing/mask/cigarette/cigar/cohiba
|
||||
name = "\improper Cohiba Robusto cigar"
|
||||
@@ -589,6 +610,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
|
||||
chem_volume = 200 // So we can fit densified chemicals plants
|
||||
list_reagents = null
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
choke_forever = TRUE
|
||||
///name of the stuff packed inside this pipe
|
||||
var/packeditem
|
||||
|
||||
|
||||
@@ -417,6 +417,8 @@
|
||||
if((HAS_TRAIT(src, TRAIT_NOHUNGER) || HAS_TRAIT(src, TRAIT_TOXINLOVER)) && !force)
|
||||
return TRUE
|
||||
|
||||
SEND_SIGNAL(src, COMSIG_CARBON_VOMITED, distance, force)
|
||||
var/starting_dir = dir
|
||||
if(nutrition < 100 && !blood && !force)
|
||||
if(message)
|
||||
visible_message(span_warning("[src] dry heaves!"), \
|
||||
@@ -455,7 +457,7 @@
|
||||
else
|
||||
if(T)
|
||||
T.add_vomit_floor(src, vomit_type, purge_ratio) //toxic barf looks different || call purge when doing detoxicfication to pump more chems out of the stomach.
|
||||
T = get_step(T, dir)
|
||||
T = get_step(T, starting_dir)
|
||||
if (T?.is_blocked_turf())
|
||||
break
|
||||
return TRUE
|
||||
|
||||
@@ -403,7 +403,7 @@
|
||||
to_chat(helper, span_warning("You can't put [p_them()] out with just your bare hands!"))
|
||||
return
|
||||
|
||||
if(SEND_SIGNAL(src, COMSIG_CARBON_PRE_HELP_ACT, helper) & COMPONENT_BLOCK_HELP_ACT)
|
||||
if(SEND_SIGNAL(src, COMSIG_CARBON_PRE_MISC_HELP, helper) & COMPONENT_BLOCK_MISC_HELP)
|
||||
return
|
||||
|
||||
if(helper == src)
|
||||
|
||||
@@ -1068,9 +1068,11 @@ GLOBAL_LIST_EMPTY(features_by_species)
|
||||
/datum/species/proc/spec_fully_heal(mob/living/carbon/human/H)
|
||||
return
|
||||
|
||||
|
||||
/datum/species/proc/help(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
|
||||
if(target.body_position == STANDING_UP || (target.health >= 0 && !HAS_TRAIT(target, TRAIT_FAKEDEATH)))
|
||||
if(SEND_SIGNAL(target, COMSIG_CARBON_PRE_HELP, user, attacker_style) & COMPONENT_BLOCK_HELP_ACT)
|
||||
return TRUE
|
||||
|
||||
if(target.body_position == STANDING_UP || target.appears_alive())
|
||||
target.help_shake_act(user)
|
||||
if(target != user)
|
||||
log_combat(user, target, "shaken")
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
//Second link in a breath chain, calls check_breath()
|
||||
/mob/living/carbon/proc/breathe(delta_time, times_fired)
|
||||
var/obj/item/organ/internal/lungs = getorganslot(ORGAN_SLOT_LUNGS)
|
||||
if(reagents.has_reagent(/datum/reagent/toxin/lexorin, needs_metabolizing = TRUE))
|
||||
if(SEND_SIGNAL(src, COMSIG_CARBON_ATTEMPT_BREATHE) & COMSIG_CARBON_BLOCK_BREATH)
|
||||
return
|
||||
|
||||
SEND_SIGNAL(src, COMSIG_CARBON_PRE_BREATHE)
|
||||
@@ -151,7 +151,7 @@
|
||||
failed_last_breath = TRUE
|
||||
throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy)
|
||||
return FALSE
|
||||
|
||||
|
||||
var/safe_oxy_min = 16
|
||||
var/safe_co2_max = 10
|
||||
var/safe_plas_max = 0.05
|
||||
|
||||
@@ -761,3 +761,7 @@
|
||||
/mob/living/proc/get_drunk_amount()
|
||||
var/datum/status_effect/inebriated/inebriation = has_status_effect(/datum/status_effect/inebriated)
|
||||
return inebriation?.drunk_value || 0
|
||||
|
||||
/// Helper to check if we seem to be alive or not
|
||||
/mob/living/proc/appears_alive()
|
||||
return health >= 0 && !HAS_TRAIT(src, TRAIT_FAKEDEATH)
|
||||
|
||||
@@ -176,6 +176,16 @@
|
||||
C.emote("gasp")
|
||||
..()
|
||||
|
||||
/datum/reagent/toxin/lexorin/on_mob_metabolize(mob/living/L)
|
||||
RegisterSignal(L, COMSIG_CARBON_ATTEMPT_BREATHE, .proc/block_breath)
|
||||
|
||||
/datum/reagent/toxin/lexorin/on_mob_end_metabolize(mob/living/L)
|
||||
UnregisterSignal(L, COMSIG_CARBON_ATTEMPT_BREATHE, .proc/block_breath)
|
||||
|
||||
/datum/reagent/toxin/lexorin/proc/block_breath(mob/living/source)
|
||||
SIGNAL_HANDLER
|
||||
return COMSIG_CARBON_BLOCK_BREATH
|
||||
|
||||
/datum/reagent/toxin/slimejelly
|
||||
name = "Slime Jelly"
|
||||
description = "A gooey semi-liquid produced from one of the deadliest lifeforms in existence. SO REAL."
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
log_speech_indicators("[key_name(client)] FORCED to stop typing, indicators enabled.")
|
||||
else
|
||||
log_speech_indicators("[key_name(client)] FORCED to stop typing, indicators DISABLED.")
|
||||
SEND_SIGNAL(src, COMSIG_HUMAN_FORCESAY)
|
||||
|
||||
/**
|
||||
* Handles text entry and forced speech.
|
||||
|
||||
Reference in New Issue
Block a user