/** * returns everyone we're grabbing associated to state */ /mob/proc/get_grabbing() RETURN_TYPE(/list) . = list() for(var/obj/item/grab/G in get_held_items()) .[G.affecting] = G.state /** * returns everyone we're grabbing that are at least the given grab state */ /mob/proc/get_grabbing_of_state(state) RETURN_TYPE(/list) . = list() for(var/obj/item/grab/G in get_held_items()) if(G.state < state) continue . += G.affecting /** * returns everyone we're grabbing, recursively; this can include ourselves! * * @return grabbed mobs associated to states */ /mob/proc/get_grabbing_recursive(list/L = list(), safety = 15, list/processed = list()) RETURN_TYPE(/list) if(processed[src]) return processed[src] = TRUE if(safety <= 0) CRASH("infinite loop guard tripped") . = L for(var/obj/item/grab/G in get_held_items()) .[G.affecting] = max(.[G.affecting], G.state) G.affecting.get_grabbing_recursive(., --safety, processed) /** * check the grab state of us to someone * * @return grab state, or null */ /mob/proc/check_grab(mob/M) for(var/obj/item/grab/G in get_held_items()) if(G.affecting == M) return G.state /** * drops our grab to someone immediately */ /mob/proc/drop_grab(mob/M) for(var/obj/item/grab/G in get_held_items()) if(G.affecting == M) qdel(G) /** * drops all grabs immediately */ /mob/proc/drop_grabs() for(var/obj/item/grab/G in get_held_items()) qdel(G) /** * are we being grabbed * * @return null, or highest state */ /mob/proc/is_grabbed() for(var/mob/M as anything in grabbed_by) . = max(., M.check_grab(src)) /** * are we being grabbed by someone * * @return null, or state */ /mob/proc/is_grabbed_by(mob/M) return (M in grabbed_by)? M.check_grab(src) : null #define UPGRADE_COOLDOWN 40 #define UPGRADE_KILL_TIMER 100 ///Process_Grab() ///Called by client/Move() ///Checks to see if you are grabbing or being grabbed by anything and if moving will affect your grab. /client/proc/Process_Grab() //if we are being grabbed if(isliving(mob)) var/mob/living/L = mob if(!CHECK_MOBILITY(L, MOBILITY_CAN_MOVE) && L.grabbed_by.len) L.resist() //shortcut for resisting grabs //if we are grabbing someone for(var/obj/item/grab/G in L.get_held_items_of_type(/obj/item/grab)) G.reset_kill_state() //no wandering across the station/asteroid while choking someone // TODO: refactor this and perform logging in attackchain instead of here /obj/item/grab name = "grab" icon = 'icons/mob/screen1.dmi' icon_state = "reinforce" item_flags = ITEM_ABSTRACT | ITEM_DROPDEL | ITEM_ENCUMBERS_WHILE_HELD | ITEM_NO_BLUDGEON atom_flags = ATOM_ABSTRACT drop_sound = null pickup_sound = null equip_sound = null unequip_sound = null var/atom/movable/screen/grab/hud = null var/mob/living/affecting = null var/mob/living/assailant = null var/state = GRAB_PASSIVE var/allow_upgrade = 1 var/last_action = 0 var/last_hit_zone = 0 var/force_down //determines if the affecting mob will be pinned to the ground var/dancing //determines if assailant and affecting keep looking at each other. Basically a wrestling position item_state = "nothing" w_class = WEIGHT_CLASS_HUGE /obj/item/grab/Initialize(mapload, mob/victim) . = ..() var/mob/user = loc assailant = user affecting = victim add_attack_logs(assailant,affecting,"Grab Initialized") if(affecting.anchored || !assailant.Adjacent(victim) || affecting.buckled) affecting = null assailant = null return INITIALIZE_HINT_QDEL affecting.grabbed_by += src affecting.reveal("You are revealed as [assailant] grabs you.") assailant.reveal("You reveal yourself as you grab [affecting].") hud = new /atom/movable/screen/grab(src) hud.icon_state = "reinforce" icon_state = "grabbed" hud.name = "reinforce grab" hud.master = src vis_contents += hud //check if assailant is grabbed by victim as well if(assailant.grabbed_by) for (var/obj/item/grab/G in assailant.grabbed_by) if(G.assailant == affecting && G.affecting == assailant) G.dancing = 1 G.adjust_position() dancing = 1 //stop pulling the affected if(assailant.pulling == affecting) assailant.stop_pulling() adjust_position() /obj/item/grab/process(delta_time) if(QDELETED(src)) // GC is trying to delete us, we'll kill our processing so we can cleanly GC return PROCESS_KILL confirm() if(!assailant) qdel(src) // Same here, except we're trying to delete ourselves. return PROCESS_KILL if(assailant.client) assailant.client.screen -= hud assailant.client.screen += hud if(state <= GRAB_AGGRESSIVE) allow_upgrade = 1 //disallow upgrading if we're grabbing more than one person if(length(assailant.get_grabbing()) > 1) allow_upgrade = 0 //disallow upgrading past aggressive if we're being grabbed aggressively for(var/obj/item/grab/G in affecting.grabbed_by) if(G == src) continue if(G.state >= GRAB_AGGRESSIVE) allow_upgrade = 0 if(allow_upgrade) if(state < GRAB_AGGRESSIVE) hud.icon_state = "reinforce" else hud.icon_state = "reinforce1" else hud.icon_state = "!reinforce" if(state >= GRAB_AGGRESSIVE) affecting.drop_held_items() if(iscarbon(affecting)) handle_eye_mouth_covering(affecting, assailant, assailant.zone_sel.selecting) if(force_down) if(affecting.loc != assailant.loc || size_difference(affecting, assailant) > 0) force_down = 0 else affecting.afflict_knockdown(3 SECONDS) affecting.afflict_root(3 SECONDS) affecting.afflict_daze(3 SECONDS) if(state >= GRAB_NECK) affecting.afflict_stun(20 * 3) if(state >= GRAB_KILL) //affecting.apply_effect(STUTTER, 5) //would do this, but affecting isn't declared as mob/living for some stupid reason. affecting.stuttering = max(affecting.stuttering, 5) //It will hamper your voice, being choked and all. affecting.afflict_paralyze(20 * 5) //Should keep you down unless you get help. affecting.losebreath = max(affecting.losebreath + 2, 3) adjust_position() /obj/item/grab/proc/handle_eye_mouth_covering(mob/living/carbon/target, mob/user, var/target_zone) var/announce = (target_zone != last_hit_zone) //only display messages when switching between different target zones last_hit_zone = target_zone switch(target_zone) if(O_MOUTH) if(announce) user.visible_message("\The [user] covers [target]'s mouth!") if(target.silent < 3) target.silent = 3 if(O_EYES) if(announce) assailant.visible_message("[assailant] covers [affecting]'s eyes!") if(!affecting.has_status_effect(/datum/status_effect/sight/blindness)) affecting.apply_status_effect(/datum/status_effect/sight/blindness, 3 SECONDS) /obj/item/grab/attack_self(mob/user, datum/event_args/actor/actor) . = ..() if(.) return return s_click(hud) /obj/item/grab/throw_resolve_actual(mob/user) if(affecting.buckled) return if(state < GRAB_AGGRESSIVE) return animate(affecting, pixel_x = initial(affecting.pixel_x), pixel_y = initial(affecting.pixel_y), 4, 1) return affecting /obj/item/grab/throw_resolve_finalize(atom/movable/resolver, mob/user) qdel(src) /obj/item/grab/throw_resolve_override(atom/movable/resolved, mob/user) return TRUE // TODO: should this maybe use melee_attack/melee_impact? /obj/item/grab/using_as_item(atom/target, datum/event_args/actor/clickchain/clickchain, clickchain_flags) . = ..() if(. & CLICKCHAIN_FLAGS_INTERACT_ABORT) return if(QDELETED(src)) . |= CLICKCHAIN_DO_NOT_PROPAGATE return if(!affecting) return if(world.time < (last_action + 20)) return last_action = world.time reset_kill_state() //using special grab moves will interrupt choking them //clicking on the victim while grabbing them if(target == affecting) if(ishuman(affecting)) var/mob/living/carbon/human/H = affecting var/hit_zone = clickchain.target_zone flick(hud.icon_state, hud) switch(clickchain.using_intent) if(INTENT_HELP) if(force_down) to_chat(assailant, "You are no longer pinning [affecting] to the ground.") force_down = 0 return if(state >= GRAB_AGGRESSIVE) H.apply_pressure(assailant, hit_zone) else inspect_organ(affecting, assailant, hit_zone) if(INTENT_DISARM) pin_down(affecting, assailant) if(INTENT_GRAB) jointlock(affecting, assailant, hit_zone) if(INTENT_HARM) if(hit_zone == O_EYES) attack_eye(affecting, assailant) else if(hit_zone == BP_HEAD) headbutt(affecting, assailant) else dislocate(affecting, assailant, hit_zone) . |= CLICKCHAIN_DID_SOMETHING else if(clickchain.using_intent != INTENT_HARM) return // TODO: don't do hard stuns, maybe have this differ for each object? switch(state) if(GRAB_PASSIVE) clickchain.visible_feedback( target = target, range = MESSAGE_RANGE_COMBAT_LOUD, visible = SPAN_DANGER("[clickchain.performer] shoves [affecting] against \the [target]!") ) affecting.take_random_targeted_damage(brute = 10) affecting.afflict_knockdown(0.5 SECONDS) if(GRAB_AGGRESSIVE) clickchain.visible_feedback( target = target, range = MESSAGE_RANGE_COMBAT_LOUD, visible = SPAN_DANGER("[clickchain.performer] slams [affecting] against \the [target]!") ) affecting.take_random_targeted_damage(brute = 20) affecting.afflict_paralyze(1 SECONDS) affecting.afflict_knockdown(2 SECONDS) if(GRAB_NECK, GRAB_KILL) clickchain.visible_feedback( target = target, range = MESSAGE_RANGE_COMBAT_LOUD, visible = SPAN_DANGER("[clickchain.performer] smashes [affecting] against \the [target]!") ) affecting.take_random_targeted_damage(brute = 30) affecting.afflict_paralyze(2.75 SECONDS) affecting.afflict_knockdown(3.5 SECONDS) qdel(src) clickchain.data[ACTOR_DATA_GRAB_LOG] = "slam [key_name(affecting)] ([ref(affecting)]) into [target] ([target.type]) ([ref(target)])" . |= CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING //clicking on yourself while grabbing them if(target == assailant && state >= GRAB_AGGRESSIVE) devour(affecting, assailant) //Updating pixelshift, position and direction //Gets called on process, when the grab gets upgraded or the assailant moves /obj/item/grab/proc/adjust_position() if(QDELETED(src)) return if(!affecting) qdel(src) return if(affecting.buckled) animate(affecting, pixel_x = initial(affecting.pixel_x), pixel_y = initial(affecting.pixel_y), 4, 1, LINEAR_EASING) return if(affecting.lying && state != GRAB_KILL) animate(affecting, pixel_x = initial(affecting.pixel_x), pixel_y = initial(affecting.pixel_y), 5, 1, LINEAR_EASING) if(force_down) affecting.setDir(SOUTH) //face up return var/shift = 0 var/adir = get_dir(assailant, affecting) affecting.set_base_layer(MOB_LAYER) switch(state) if(GRAB_PASSIVE) shift = 8 if(dancing) //look at partner shift = 10 assailant.setDir(get_dir(assailant, affecting)) if(GRAB_AGGRESSIVE) shift = 12 if(GRAB_NECK) shift = -10 adir = assailant.dir affecting.forceMove(assailant.loc) affecting.setDir(assailant.dir) if(GRAB_KILL) shift = 0 adir = 1 affecting.forceMove(assailant.loc) affecting.setDir(SOUTH) //face up switch(adir) if(NORTH) animate(affecting, pixel_x = initial(affecting.pixel_x), pixel_y =-shift, 5, 1, LINEAR_EASING) affecting.set_base_layer(BELOW_MOB_LAYER) if(SOUTH) animate(affecting, pixel_x = initial(affecting.pixel_x), pixel_y = shift, 5, 1, LINEAR_EASING) if(WEST) animate(affecting, pixel_x = shift, pixel_y = initial(affecting.pixel_y), 5, 1, LINEAR_EASING) if(EAST) animate(affecting, pixel_x =-shift, pixel_y = initial(affecting.pixel_y), 5, 1, LINEAR_EASING) /obj/item/grab/proc/s_click(atom/movable/screen/S) if(QDELETED(src)) return if(!affecting) return if(!assailant.canClick()) return if(world.time < (last_action + UPGRADE_COOLDOWN)) return if(!CHECK_ALL_MOBILITY(assailant, MOBILITY_CAN_USE | MOBILITY_IS_STANDING)) qdel(src) return var/datum/gender/TU = GLOB.gender_datums[assailant.get_visible_gender()] last_action = world.time if(state < GRAB_AGGRESSIVE) if(!allow_upgrade) return if(!affecting.lying || size_difference(affecting, assailant) > 0) assailant.visible_message("[assailant] has grabbed [affecting] aggressively (now hands)!") else assailant.visible_message("[assailant] pins [affecting] down to the ground (now hands)!") apply_pinning(affecting, assailant) state = GRAB_AGGRESSIVE add_attack_logs(assailant,affecting,"Aggressively grabbed") icon_state = "grabbed1" hud.icon_state = "reinforce1" else if(state < GRAB_NECK) if(isslime(affecting)) to_chat(assailant, "You squeeze [affecting], but nothing interesting happens.") return assailant.visible_message("[assailant] has reinforced [TU.his] grip on [affecting] (now neck)!") state = GRAB_NECK icon_state = "grabbed+1" assailant.setDir(get_dir(assailant, affecting)) add_attack_logs(assailant,affecting,"Neck grabbed") hud.icon_state = "kill" hud.name = "kill" affecting.afflict_stun(20 * 10) //10 ticks of ensured grab else if(state < GRAB_KILL) assailant.visible_message("[assailant] starts to tighten [TU.his] grip on [affecting]'s neck!") hud.icon_state = "kill1" state = GRAB_KILL assailant.visible_message("[assailant] has tightened [TU.his] grip on [affecting]'s neck!") add_attack_logs(assailant,affecting,"Strangled") affecting.setClickCooldownLegacy(10) affecting.AdjustLosebreath(1) affecting.setDir(WEST) adjust_position() //This is used to make sure the victim hasn't managed to yackety sax away before using the grab. /obj/item/grab/proc/confirm() if(!assailant || !affecting) qdel(src) return 0 if(affecting) if(!isturf(assailant.loc) || ( !isturf(affecting.loc) || assailant.loc != affecting.loc && get_dist(assailant, affecting) > 1) ) qdel(src) return 0 return 1 /obj/item/grab/proc/reset_kill_state() if(state == GRAB_KILL) var/datum/gender/T = GLOB.gender_datums[assailant.get_visible_gender()] assailant.visible_message("[assailant] lost [T.his] tight grip on [affecting]'s neck!") hud.icon_state = "kill" state = GRAB_NECK /obj/item/grab/proc/handle_resist() var/grab_name var/break_strength = 1 var/list/break_chance_table = list(100) switch(state) //if(GRAB_PASSIVE) if(GRAB_AGGRESSIVE) grab_name = "grip" //Being knocked down makes it harder to break a grab, so it is easier to cuff someone who is down without forcing them into unconsciousness. if(!affecting.incapacitated(INCAPACITATION_KNOCKDOWN)) break_strength++ break_chance_table = list(15, 60, 100) if(GRAB_NECK) grab_name = "headlock" //If the you move when grabbing someone then it's easier for them to break free. Same if the affected mob is immune to stun. if(world.time - assailant.l_move_time < 30 || !affecting.is_stunned()) break_strength++ break_chance_table = list(3, 18, 45, 100) if(GRAB_KILL) grab_name = "stranglehold" break_chance_table = list(5, 20, 40, 80, 100) //It's easier to break out of a grab by a smaller mob break_strength += max(size_difference(affecting, assailant), 0) var/break_chance = break_chance_table[clamp(break_strength, 1, break_chance_table.len)] if(prob(break_chance)) if(state == GRAB_KILL) reset_kill_state() return else if(grab_name) affecting.visible_message("[affecting] has broken free of [assailant]'s [grab_name]!") qdel(src) //returns the number of size categories between affecting and assailant, rounded. Positive means A is larger than B /obj/item/grab/proc/size_difference(mob/A, mob/B) return mob_size_difference(A.mob_size, B.mob_size) /obj/item/grab/Destroy() if(affecting) animate(affecting, pixel_x = initial(affecting.pixel_x), pixel_y = initial(affecting.pixel_y), 4, 1, LINEAR_EASING) affecting.reset_plane_and_layer() affecting.grabbed_by -= src affecting = null if(assailant) if(assailant.client) assailant.client.screen -= hud assailant = null qdel(hud) hud = null return ..() /obj/item/grab/proc/inspect_organ(mob/living/carbon/human/H, mob/user, var/target_zone) var/obj/item/organ/external/E = H.get_organ(target_zone) if(!E || E.is_stump()) to_chat(user, "[H] is missing that bodypart.") return user.visible_message("[user] starts inspecting [affecting]'s [E.name] carefully.") if(!do_mob(user,H, 10)) to_chat(user, "You must stand still to inspect [E] for wounds.") else if(length(E.wounds)) to_chat(user, "You find [E.get_wounds_desc()]") else to_chat(user, "You find no visible wounds.") to_chat(user, "Checking bones now...") if(!do_mob(user, H, 20)) to_chat(user, "You must stand still to feel [E] for fractures.") else if(E.status & ORGAN_BROKEN) to_chat(user, "The [E.encased ? E.encased : "bone in the [E.name]"] moves slightly when you poke it!") H.custom_pain("Your [E.name] hurts where it's poked.", 40) else to_chat(user, "The [E.encased ? E.encased : "bones in the [E.name]"] seem to be fine.") to_chat(user, "Checking skin now...") if(!do_mob(user, H, 10)) to_chat(user, "You must stand still to check [H]'s skin for abnormalities.") else var/bad = 0 if(H.getToxLoss() >= 40) to_chat(user, "[H] has an unhealthy skin discoloration.") bad = 1 if(H.getOxyLoss() >= 20) to_chat(user, "[H]'s skin is unusaly pale.") bad = 1 if(E.status & ORGAN_DEAD) to_chat(user, "[E] is decaying!") bad = 1 if(!bad) to_chat(user, "[H]'s skin is normal.") /obj/item/grab/proc/jointlock(mob/living/carbon/human/target, mob/attacker, var/target_zone) if(state < GRAB_AGGRESSIVE) to_chat(attacker, "You require a better grab to do this.") return var/obj/item/organ/external/organ = target.get_organ(check_zone(target_zone)) if(!organ || organ.dislocated == -1) return attacker.visible_message("[attacker] [pick("bent", "twisted")] [target]'s [organ.name] into a jointlock!") if(target.species.species_flags & NO_PAIN) return var/armor = target.run_armor_check(target, "melee") var/soaked = target.get_armor_soak(target, "melee") if(armor + soaked < 60) to_chat(target, "You feel extreme pain!") var/max_halloss = round(target.species.total_health * 0.8) //up to 80% of passing out affecting.adjustHalLoss(clamp(0, max_halloss - affecting.halloss, 30)) /obj/item/grab/proc/attack_eye(mob/living/carbon/human/target, mob/living/carbon/human/attacker) if(!istype(attacker)) return var/datum/melee_attack/unarmed/attack = attacker.get_unarmed_attack(target, O_EYES) if(!attack) return if(state < GRAB_NECK) to_chat(attacker, "You require a better grab to do this.") return for(var/obj/item/protection in list(target.head, target.wear_mask, target.glasses)) if(protection && (protection.body_cover_flags & EYES)) to_chat(attacker, "You're going to need to remove the eye covering first.") return if(!target.has_eyes()) to_chat(attacker, "You cannot locate any eyes on [target]!") return add_attack_logs(attacker,target,"Eye gouge using grab") attack.handle_eye_attack(attacker, target) /obj/item/grab/proc/headbutt(mob/living/carbon/human/target, mob/living/carbon/human/attacker) if(!istype(attacker)) return if(target.lying) return var/datum/gender/T = GLOB.gender_datums[attacker.get_visible_gender()] attacker.visible_message("[attacker] thrusts [T.his] head into [target]'s skull!") var/damage = 20 var/obj/item/clothing/hat = attacker.head if(istype(hat)) damage += hat.damage_force * 3 var/armor = target.run_armor_check(BP_HEAD, "melee") var/soaked = target.get_armor_soak(BP_HEAD, "melee") target.apply_damage(damage, DAMAGE_TYPE_BRUTE, BP_HEAD, armor, soaked) attacker.apply_damage(10, DAMAGE_TYPE_BRUTE, BP_HEAD, attacker.run_armor_check(BP_HEAD), attacker.get_armor_soak(BP_HEAD), "melee") if(!armor && target.headcheck(BP_HEAD) && prob(damage)) target.apply_effect(20, PARALYZE) target.visible_message("[target] [target.species.get_knockout_message(target)]") playsound(attacker.loc, "swing_hit", 25, 1, -1) add_attack_logs(attacker,target,"Headbutted using grab") qdel(src) /obj/item/grab/proc/dislocate(mob/living/carbon/human/target, mob/living/attacker, var/target_zone) if(state < GRAB_NECK) to_chat(attacker, "You require a better grab to do this.") return if(target.grab_joint(attacker, target_zone)) playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) return /obj/item/grab/proc/pin_down(mob/target, mob/attacker) if(state < GRAB_AGGRESSIVE) to_chat(attacker, "You require a better grab to do this.") return if(force_down) to_chat(attacker, "You are already pinning [target] to the ground.") return if(size_difference(affecting, assailant) > 10) to_chat(attacker, "You are too small to do that!") return attacker.visible_message("[attacker] starts forcing [target] to the ground!") if(do_after(attacker, 20) && target) last_action = world.time attacker.visible_message("[attacker] forces [target] to the ground!") apply_pinning(target, attacker) /obj/item/grab/proc/apply_pinning(mob/target, mob/attacker) force_down = 1 target.afflict_knockdown(4 SECONDS) target.afflict_root(4 SECONDS) target.afflict_daze(4 SECONDS) step_to(attacker, target) attacker.setDir(EAST) //face the victim target.setDir(SOUTH) //face up /obj/item/grab/proc/devour(mob/target, mob/user) var/can_eat if((MUTATION_FAT in user.mutations) && ismini(target)) can_eat = 1 else var/mob/living/carbon/human/H = user if(istype(H) && H.species.gluttonous) if(H.species.gluttonous == 2) can_eat = 2 else if((H.mob_size > target.mob_size) && !ishuman(target) && ismini(target)) can_eat = 1 if(can_eat) var/mob/living/carbon/attacker = user user.visible_message("[user] is attempting to devour [target]!") if(can_eat == 2) if(!do_mob(user, target)||!do_after(user, 30)) return else if(!do_mob(user, target)||!do_after(user, 70)) return user.visible_message("[user] devours [target]!") target.loc = user attacker.stomach_contents.Add(target) qdel(src)