From 03e64bd97fc1980bce58ff620bcd3075f8e27672 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Mon, 25 Sep 2023 23:06:54 -0400 Subject: [PATCH 01/37] foundation --- code/__HELPERS/global_lists.dm | 8 + code/_onclick/item_attack.dm | 13 + code/game/atoms.dm | 6 + code/modules/grab/grab_datum.dm | 357 ++++++++++++++++++ code/modules/grab/grab_object.dm | 330 ++++++++++++++++ .../mob/living/carbon/human/human_defines.dm | 3 + code/modules/mob/living/carbon/inventory.dm | 8 + code/modules/mob/mob_helpers.dm | 4 + daedalus.dme | 2 + 9 files changed, 731 insertions(+) create mode 100644 code/modules/grab/grab_datum.dm create mode 100644 code/modules/grab/grab_object.dm diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index cfba25e00440..551a6fb2bbfd 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -87,6 +87,14 @@ GLOB.emote_list = init_emote_list() + for(var/datum/grab/G as anything in subtypesof(/datum/grab)) + if(isabstract(G)) + continue + GLOB.all_grabstates[G.type] = G + for(var/path in GLOB.all_grabstates) + var/datum/grab/G = GLOB.all_grabstates[path] + G.refresh_updown() + init_crafting_recipes(GLOB.crafting_recipes) init_loadout_references() init_augment_references() diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index dc9a43b16b2b..d3e3cdb0b7b2 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -348,3 +348,16 @@ visible_message(span_danger("[attack_message_spectator]"), vision_distance = COMBAT_MESSAGE_RANGE) return 1 + +/** + * Interaction handler for being clicked on with a grab. This is called regardless of user intent. + * + * **Parameters**: + * - `grab` - The grab item being used. + * - `click_params` - List of click parameters. + * + * Returns boolean to indicate whether the attack call was handled or not. If `FALSE`, the next `use_*` proc in the + * resolve chain will be called. + */ +/atom/proc/use_grab(obj/item/hand_item/grab/grab, list/params) + return FALSE diff --git a/code/game/atoms.dm b/code/game/atoms.dm index da32f9f269f6..3842224ec6dc 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -2218,3 +2218,9 @@ //Currently only changed by Observers to be hearing through their orbit target. /atom/proc/hear_location() return src + +///Reset plane and layer values to their defaults. +/atom/proc/reset_plane_and_layer() + plane = initial(plane) + layer = initial(layer) + diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm new file mode 100644 index 000000000000..7d4f00fd7ad0 --- /dev/null +++ b/code/modules/grab/grab_datum.dm @@ -0,0 +1,357 @@ +/// An associative list of type:instance for grab datums +GLOBAL_LIST_EMPTY(all_grabstates) +/datum/grab + abstract_type = /datum/grab + var/icon + var/icon_state + + var/type_name + var/state_name + var/fancy_desc + + /// The grab that this will upgrade to if it upgrades, null means no upgrade + var/datum/grab/upgrab + /// The grab that this will downgrade to if it downgrades, null means break grab on downgrade + var/datum/grab/downgrab + + var/datum/time_counter // For things that need to be timed + + // Whether or not the grabbed person can move out of the grab + var/stop_move = FALSE + /// Whether or not the grabbed person is forced to be standing + var/force_stand = FALSE + // Whether the person being grabbed is facing forwards or backwards. + var/reverse_facing = FALSE + /// Whether this grab state is strong enough to, as a changeling, absorb the person you're grabbing. + var/can_absorb = FALSE + /// Whether the person you're grabbing will shield you from bullets. + var/shield_assailant = FALSE + /// How much the grab increases point blank damage. + var/point_blank_mult = 1 + /// Affects how much damage is being dealt using certain actions. + var/damage_stage = 1 + /// If the grabbed person and the grabbing person are on the same tile. + var/same_tile = FALSE + /// If the grabber can carry the grabbed person up or down ladders. + var/ladder_carry = FALSE + /// If the grabber can throw the person grabbed. + var/can_throw = FALSE + /// If the grab needs to be downgraded when the grabber does stuff. + var/downgrade_on_action = FALSE + /// If the grab needs to be downgraded when the grabber moves. + var/downgrade_on_move = FALSE + /// If the grab is strong enough to be able to force someone to do something harmful to them. + var/force_danger = FALSE + /// If the grab acts like cuffs and prevents action from the victim. + var/restrains = FALSE + + var/grab_slowdown = 7 + + var/shift = 0 + + var/success_up = "You upgrade the grab." + var/success_down = "You downgrade the grab." + + var/fail_up = "You fail to upgrade the grab." + var/fail_down = "You fail to downgrade the grab." + + var/upgrade_cooldown = 40 + var/action_cooldown = 40 + + var/can_downgrade_on_resist = TRUE + var/list/break_chance_table = list(100) + var/breakability = 2 + + var/can_grab_self = TRUE + + // The names of different intents for use in attack logs + var/help_action = "help intent" + var/disarm_action = "disarm intent" + var/grab_action = "grab intent" + var/harm_action = "harm intent" + +/* + These procs shouldn't be overriden in the children unless you know what you're doing with them; they handle important core functions. + Even if you do override them, you should likely be using ..() if you want the behaviour to function properly. That is, of course, + unless you're writing your own custom handling of things. +*/ +/// Called during world/New to setup references. +/datum/grab/proc/refresh_updown() + if(upgrab) + upgrab = GLOB.all_grabstates[upgrab] + + if(downgrab) + downgrab = GLOB.all_grabstates[upgrab] + +// This is for the strings defined as datum variables. It takes them and swaps out keywords for relevent ones from the grab +// object involved. +/datum/grab/proc/string_process(obj/item/hand_item/grab/G, to_write, obj/item/used_item) + to_write = replacetext(to_write, "rep_affecting", G.affecting) + to_write = replacetext(to_write, "rep_assailant", G.assailant) + if(used_item) + to_write = replacetext(to_write, "rep_item", used_item) + return to_write + +/datum/grab/proc/upgrade(obj/item/hand_item/grab/G) + if(!upgrab) + return + + if (can_upgrade(G)) + upgrade_effect(G) + log_combat(G.assailant, G.affecting, "tightens their grip on their victim to [upgrab.state_name]") + return upgrab + else + to_chat(G.assailant, span_warning("[string_process(G, fail_up)]")) + return + +/datum/grab/proc/downgrade(obj/item/hand_item/grab/G) + // Starts the process of letting go if there's no downgrade grab + if(can_downgrade()) + downgrade_effect(G) + return downgrab + else + to_chat(G.assailant, span_warning("[string_process(G, fail_down)]")) + return + +/datum/grab/proc/let_go(obj/item/hand_item/grab/G) + if (G) + let_go_effect(G) + qdel(G) + +/datum/grab/proc/on_target_change(obj/item/hand_item/grab/G, old_zone, new_zone) + G.special_target_functional = check_special_target(G) + if(G.special_target_functional) + special_target_change(G, old_zone, new_zone) + special_target_effect(G) + +/datum/grab/process(obj/item/hand_item/grab/G) + special_target_effect(G) + process_effect(G) + +/datum/grab/proc/throw_held(obj/item/hand_item/grab/G) + if(G.assailant == G.affecting) + return + + var/mob/living/carbon/human/affecting = G.affecting + + if(can_throw) + . = affecting + var/mob/thrower = G.loc + + animate(affecting, pixel_x = 0, pixel_y = 0, 4, 1) + qdel(G) + + // check if we're grabbing with our inactive hand + G = thrower.get_inactive_hand() + if(!istype(G)) return + qdel(G) + return + +/datum/grab/proc/hit_with_grab(obj/item/hand_item/grab/G, params) + if(downgrade_on_action) + G.downgrade() + + if(G.check_action_cooldown() && !G.attacking) + var/combat_mode = G.assailant.combat_mode + if(params[RIGHT_CLICK]) + if(on_hit_disarm(G)) + G.action_used() + make_log(G, disarm_action) + return TRUE + + else if(params[CTRL_CLICK]) + if(on_hit_grab(G)) + G.action_used() + make_log(G, grab_action) + return TRUE + + + else if(combat_mode) + if(on_hit_harm(G)) + G.action_used() + make_log(G, harm_action) + return TRUE + else + if(on_hit_help(G)) + G.action_used() + make_log(G, help_action) + return TRUE + + else + to_chat(G.assailant, span_warning("You must wait before you can do that.")) + return FALSE + +/datum/grab/proc/make_log(obj/item/hand_item/grab/G, action) + log_combat(G.assailant, G.affecting, "[action]s their victim") + +/datum/grab/proc/adjust_position(obj/item/hand_item/grab/G) + var/mob/living/carbon/human/affecting = G.affecting + var/mob/living/carbon/human/assailant = G.assailant + var/adir = get_dir(assailant, affecting) + + if(same_tile) + affecting.forceMove(assailant.loc) + adir = assailant.dir + affecting.setDir(assailant.dir) + + switch(adir) + if(NORTH) + animate(affecting, pixel_x = 0, pixel_y =-shift, 5, 1, LINEAR_EASING) + G.draw_affecting_under() + if(SOUTH) + animate(affecting, pixel_x = 0, pixel_y = shift, 5, 1, LINEAR_EASING) + G.draw_affecting_over() + if(WEST) + animate(affecting, pixel_x = shift, pixel_y = 0, 5, 1, LINEAR_EASING) + G.draw_affecting_under() + if(EAST) + animate(affecting, pixel_x =-shift, pixel_y = 0, 5, 1, LINEAR_EASING) + G.draw_affecting_under() + + affecting.reset_plane_and_layer() + +/datum/grab/proc/reset_position(obj/item/hand_item/grab/G) + var/mob/living/carbon/human/affecting = G.affecting + + if(!affecting.buckled) + animate(affecting, pixel_x = 0, pixel_y = 0, 4, 1, LINEAR_EASING) + affecting.reset_plane_and_layer() + +// This is called whenever the assailant moves. +/datum/grab/proc/assailant_moved(obj/item/hand_item/grab/G) + adjust_position(G) + moved_effect(G) + if(downgrade_on_move) + G.downgrade() + +/* + Override these procs to set how the grab state will work. Some of them are best + overriden in the parent of the grab set (for example, the behaviour for on_hit_intent(var/obj/item/hand_item/grab/G) + procs is determined in /datum/grab/normal and then inherited by each intent). +*/ + +// What happens when you upgrade from one grab state to the next. +/datum/grab/proc/upgrade_effect(obj/item/hand_item/grab/G) + +// Conditions to see if upgrading is possible +/datum/grab/proc/can_upgrade(obj/item/hand_item/grab/G) + return 1 + +// What happens when you downgrade from one grab state to the next. +/datum/grab/proc/downgrade_effect(obj/item/hand_item/grab/G) + +// Conditions to see if downgrading is possible +/datum/grab/proc/can_downgrade(obj/item/hand_item/grab/G) + return 1 + +// What happens when you let go of someone by either dropping the grab +// or by downgrading from the lowest grab state. +/datum/grab/proc/let_go_effect(obj/item/hand_item/grab/G) + +// What happens each tic when process is called. +/datum/grab/proc/process_effect(obj/item/hand_item/grab/G) + +// Handles special targeting like eyes and mouth being covered. +/datum/grab/proc/special_target_effect(obj/item/hand_item/grab/G) + +// Handles when they change targeted areas and something is supposed to happen. +/datum/grab/proc/special_target_change(obj/item/hand_item/grab/G, diff_zone) + +// Checks if the special target works on the grabbed humanoid. +/datum/grab/proc/check_special_target(obj/item/hand_item/grab/G) + +// What happens when you hit the grabbed person with the grab on help intent. +/datum/grab/proc/on_hit_help(obj/item/hand_item/grab/G) + return FALSE + +// What happens when you hit the grabbed person with the grab on disarm intent. +/datum/grab/proc/on_hit_disarm(obj/item/hand_item/grab/G) + return FALSE + +// What happens when you hit the grabbed person with the grab on grab intent. +/datum/grab/proc/on_hit_grab(obj/item/hand_item/grab/G) + return FALSE + +// What happens when you hit the grabbed person with the grab on harm intent. +/datum/grab/proc/on_hit_harm(obj/item/hand_item/grab/G) + return FALSE + +// What happens when you hit the grabbed person with an open hand and you want it +// to do some special snowflake action based on some other factor such as +// intent. +/datum/grab/proc/resolve_openhand_attack(obj/item/hand_item/grab/G) + return FALSE + +// Used when you want an effect to happen when the grab enters this state as an upgrade +/datum/grab/proc/enter_as_up(obj/item/hand_item/grab/G) + +/datum/grab/proc/item_attack(obj/item/hand_item/grab/G, obj/item) + +/datum/grab/proc/resolve_item_attack(obj/item/hand_item/grab/G, mob/living/carbon/human/user, obj/item/I, target_zone) + return FALSE + +/datum/grab/proc/handle_resist(obj/item/hand_item/grab/G) + var/mob/living/carbon/human/affecting = G.affecting + var/mob/living/carbon/human/assailant = G.assailant + + if(affecting.incapacitated()) + to_chat(G.affecting, span_warning("You can't resist in your current state!")) + return + + var/break_strength = breakability + size_difference(affecting, assailant) + var/affecting_shock = affecting.getPain() + var/assailant_shock = assailant.getPain() + + // Target modifiers + if(affecting.incapacitated()) + break_strength-- + if(affecting.has_status_effect(/datum/status_effect/confusion)) + break_strength-- + if(affecting.eye_blind || HAS_TRAIT(affecting, TRAIT_BLIND)) + break_strength-- + if(affecting.eye_blurry) + break_strength-- + if(affecting_shock >= 10) + break_strength-- + if(affecting_shock >= 30) + break_strength-- + if(affecting_shock >= 50) + break_strength-- + + // User modifiers + if(assailant.has_status_effect(/datum/status_effect/confusion)) + break_strength++ + if(assailant.eye_blind || HAS_TRAIT(assailant, TRAIT_BLIND)) + break_strength++ + if(assailant.eye_blurry) + break_strength++ + if(assailant_shock >= 10) + break_strength++ + if(assailant_shock >= 30) + break_strength++ + if(assailant_shock >= 50) + break_strength++ + + if(break_strength < 1) + to_chat(G.affecting, span_warning("You try to break free but feel that unless something changes, you'll never escape!")) + return + + var/break_chance = break_chance_table[clamp(break_strength, 1, length(break_chance_table))] + + if (assailant.incapacitated()) + let_go(G) + + if(prob(break_chance)) + if(can_downgrade_on_resist && !prob((break_chance+100)/2)) + affecting.visible_message(span_danger("[affecting] has loosened [assailant]'s grip!")) + G.downgrade() + return + else + affecting.visible_message(span_danger("[affecting] has broken free of [assailant]'s grip!")) + let_go(G) + +/datum/grab/proc/size_difference(mob/living/A, mob/living/B) + return mob_size_difference(A.mob_size, B.mob_size) + +/datum/grab/proc/moved_effect(obj/item/hand_item/grab/G) + return diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm new file mode 100644 index 000000000000..62485e26ba6d --- /dev/null +++ b/code/modules/grab/grab_object.dm @@ -0,0 +1,330 @@ +/obj/item/hand_item/grab + name = "grab" + item_flags = DROPDEL | ABSTRACT | HAND_ITEM | NOBLUDGEON + + var/mob/living/carbon/human/affecting = null + var/mob/living/carbon/human/assailant = null + + var/datum/grab/current_grab + var/type_name + + var/last_action + var/last_upgrade + + var/special_target_functional = 1 + + var/attacking = 0 + var/target_zone + var/done_struggle = FALSE // Used by struggle grab datum to keep track of state. +/* + This section is for overrides of existing procs. +*/ +/obj/item/hand_item/grab/Initialize(mapload, mob/living/carbon/human/victim, datum/grab/grab_type) + . = ..() + current_grab = GLOB.all_grabstates[grab_type] + + assailant = loc + if(!istype(assailant)) + return INITIALIZE_HINT_QDEL + + affecting = victim + if(!istype(affecting)) + return INITIALIZE_HINT_QDEL + target_zone = assailant.zone_selected + + if(!can_grab()) + return INITIALIZE_HINT_QDEL + if(!init()) + return INITIALIZE_HINT_QDEL + + var/obj/item/bodypart/BP = get_targeted_bodypart() + name = "[initial(name)] ([BP.plaintext_zone])" + + RegisterSignal(assailant, COMSIG_MOB_SELECTED_ZONE_SET, PROC_REF(on_target_change)) + RegisterSignal(assailant, COMSIG_MOVABLE_MOVED, PROC_REF(relay_user_move)) + + RegisterSignal(affecting, COMSIG_CARBON_REMOVED_LIMB, PROC_REF(on_limb_loss)) + RegisterSignal(affecting, COMSIG_PARENT_QDELETING, PROC_REF(target_del)) + +/obj/item/hand_item/grab/examine(mob/user) + . = ..() + var/obj/item/bodypart/BP = get_targeted_bodypart() + to_chat(user, "A grab on \the [affecting]'s [BP.plaintext_zone].") + +/obj/item/hand_item/grab/update_icon_state() + . = ..() + if(current_grab.icon_state) + icon_state = current_grab.icon_state + +/obj/item/hand_item/grab/process() + current_grab.process(src) + +/obj/item/hand_item/grab/attack_self(mob/user) + if (!assailant) + return + + if(assailant.combat_mode) + upgrade() + else + downgrade() + + +/obj/item/hand_item/grab/pre_attack(atom/A, mob/living/user, params) + // End workaround + if (QDELETED(src) || !assailant) + return TRUE + if (A.use_grab(src, params)) + user.changeNext_move(CLICK_CD_MELEE) + action_used() + if (current_grab.downgrade_on_action) + downgrade() + return TRUE + if(current_grab.hit_with_grab(src, params)) //If there is no use_grab override or if it returns FALSE; then will behave according to intent. + return TRUE + return ..() //To cover for legacy behavior. Should not reach here normally. Have all grabs be handled by use_grab or hit_with_grab. + +/obj/item/hand_item/grab/Destroy() + if(affecting) + reset_position() + affecting.grabbed_by -= src + affecting.reset_plane_and_layer() + affecting = null + assailant = null + return ..() + +/* + This section is for newly defined useful procs. +*/ + +/obj/item/hand_item/grab/proc/on_target_change(datum/source, old_sel, new_sel) + SIGNAL_HANDLER + + if(src != assailant.get_active_hand()) + return // Note that because of this condition, there's no guarantee that target_zone = old_sel + if(target_zone == new_sel) + return + + var/old_zone = target_zone + target_zone = new_sel + var/obj/item/bodypart/BP = get_targeted_bodypart() + + if (!BP) + to_chat(assailant, span_warning("You fail to grab \the [affecting] there as they do not have that bodypart!")) + return + + name = "[initial(name)] ([BP.plaintext_zone])" + to_chat(assailant, span_notice("You are now holding \the [affecting] by \the [BP.plaintext_zone].")) + + if(!isbodypart(get_targeted_bodypart())) + current_grab.let_go(src) + return + current_grab.on_target_change(src, old_zone, target_zone) + +/obj/item/hand_item/grab/proc/on_limb_loss(mob/victim, obj/item/bodypart/lost) + SIGNAL_HANDLER + + if(affecting != victim) + stack_trace("A grab switched affecting targets without properly re-registering for dismemberment updates.") + return + var/obj/item/bodypart/BP = get_targeted_bodypart() + if(!istype(BP)) + current_grab.let_go(src) + return // Sanity check in case the lost organ was improperly removed elsewhere in the code. + if(lost != BP) + return + current_grab.let_go(src) + +/obj/item/hand_item/grab/proc/can_grab() + if(!assailant.Adjacent(affecting)) + return FALSE + + if(assailant.anchored || affecting.anchored) + return FALSE + + if(assailant.get_active_hand()) + to_chat(assailant, span_warning("You can't grab someone if your hand is full.")) + return FALSE + + if(length(assailant.grabbed_by)) + to_chat(assailant, span_warning("You can't grab someone if you're being grabbed.")) + return FALSE + + var/obj/item/bodypart/BP = get_targeted_bodypart() + if(!istype(BP)) + to_chat(assailant, span_warning("\The [affecting] is missing that body part!")) + return FALSE + + if(assailant == affecting) + if(!current_grab.can_grab_self) //let's not nab ourselves + to_chat(assailant, span_warning("You can't grab yourself!")) + return FALSE + + var/active_hand = assailant.get_active_hand() + if(BP == active_hand) + to_chat(assailant, span_warning("You can't grab your own [BP.plaintext_zone] with itself!")) + return FALSE + + for(var/obj/item/hand_item/grab/G in affecting.grabbed_by) + if(G.assailant == assailant && G.target_zone == target_zone) + var/obj/item/bodypart/targeted = G.get_targeted_bodypart() + to_chat(assailant, span_warning("You already grabbed [affecting]'s [targeted.plaintext_zone].")) + return FALSE + return TRUE + +// This will run from Initialize, after can_grab and other checks have succeeded. Must call parent; returning FALSE means failure and qdels the grab. +/obj/item/hand_item/grab/proc/init() + if(!assailant.put_in_active_hand(src)) + return FALSE // This should succeed as we checked the hand, but if not we abort here. + + affecting.grabbed_by += src // This is how we handle affecting being deleted. + + adjust_position() + action_used() + if(affecting.w_uniform) + affecting.w_uniform.add_fingerprint(assailant) + assailant.do_attack_animation(affecting) + playsound(affecting.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + update_icon() + return TRUE + +// Returns the bodypart of the grabbed person that the grabber is targeting +/obj/item/hand_item/grab/proc/get_targeted_bodypart() + return (affecting?.get_bodypart(target_zone)) + +/obj/item/hand_item/grab/proc/resolve_item_attack(mob/living/M, obj/item/I, target_zone) + if((M && ishuman(M)) && I) + return current_grab.resolve_item_attack(src, M, I, target_zone) + else + return 0 + +/obj/item/hand_item/grab/proc/action_used() + last_action = world.time + leave_forensic_traces() + +/obj/item/hand_item/grab/proc/check_action_cooldown() + return (world.time >= last_action + current_grab.action_cooldown) + +/obj/item/hand_item/grab/proc/check_upgrade_cooldown() + return (world.time >= last_upgrade + current_grab.upgrade_cooldown) + +/obj/item/hand_item/grab/proc/leave_forensic_traces() + if (!affecting) + return + + var/obj/item/clothing/C = affecting.get_item_covering_zone(target_zone) + if(istype(C)) + C.add_fingerprint(assailant) + else + affecting.add_fingerprint(assailant) //If no clothing; add fingerprint to mob proper. + +/obj/item/hand_item/grab/proc/upgrade(bypass_cooldown = FALSE) + if(!check_upgrade_cooldown() && !bypass_cooldown) + to_chat(assailant, span_warning("It's too soon to upgrade.")) + return + + var/datum/grab/upgrab = current_grab.upgrade(src) + if(upgrab) + current_grab = upgrab + last_upgrade = world.time + adjust_position() + update_appearance() + leave_forensic_traces() + current_grab.enter_as_up(src) + +/obj/item/hand_item/grab/proc/downgrade() + var/datum/grab/downgrab = current_grab.downgrade(src) + if(downgrab) + current_grab = downgrab + update_appearance() + +/obj/item/hand_item/grab/proc/draw_affecting_over() + affecting.plane = assailant.plane + affecting.layer = assailant.layer + 0.01 + +/obj/item/hand_item/grab/proc/draw_affecting_under() + affecting.plane = assailant.plane + affecting.layer = assailant.layer - 0.01 + + +/obj/item/hand_item/grab/proc/throw_held() + return current_grab.throw_held(src) + +/obj/item/hand_item/grab/proc/handle_resist() + current_grab.handle_resist(src) + +/obj/item/hand_item/grab/proc/adjust_position(force = 0) + if(force) affecting.forceMove(assailant.loc) + + if(!assailant || !affecting || !assailant.Adjacent(affecting)) + qdel(src) + return 0 + else + current_grab.adjust_position(src) + +/obj/item/hand_item/grab/proc/reset_position() + current_grab.reset_position(src) + +/obj/item/hand_item/grab/proc/has_hold_on_bodypart(obj/item/bodypart/BP) + if (!BP) + return FALSE + + if (get_targeted_bodypart() == BP) + return TRUE + + return FALSE + +/// Relay when the assailant moves to the grab datum +/obj/item/hand_item/grab/proc/relay_user_move(datum/source) + SIGNAL_HANDLER + current_grab.assailant_moved(src) + +/// Target deleted, ABORT +/obj/item/hand_item/grab/proc/target_del(datum/source) + SIGNAL_HANDLER + qdel(src) + +/* + This section is for the simple procs used to return things from current_grab. +*/ +/obj/item/hand_item/grab/proc/stop_move() + return current_grab.stop_move + +/obj/item/hand_item/grab/proc/force_stand() + return current_grab.force_stand + +/obj/item/hand_item/grab/attackby(obj/W, mob/user) + if(user == assailant) + current_grab.item_attack(src, W) + +/obj/item/hand_item/grab/proc/can_absorb() + return current_grab.can_absorb + +/obj/item/hand_item/grab/proc/assailant_reverse_facing() + return current_grab.reverse_facing + +/obj/item/hand_item/grab/proc/shield_assailant() + return current_grab.shield_assailant + +/obj/item/hand_item/grab/proc/point_blank_mult() + return current_grab.point_blank_mult + +/obj/item/hand_item/grab/proc/damage_stage() + return current_grab.damage_stage + +/obj/item/hand_item/grab/proc/force_danger() + return current_grab.force_danger + +/obj/item/hand_item/grab/proc/grab_slowdown() + return current_grab.grab_slowdown + +/obj/item/hand_item/grab/proc/ladder_carry() + return current_grab.ladder_carry + +/obj/item/hand_item/grab/proc/assailant_moved() + current_grab.assailant_moved(src) + +/obj/item/hand_item/grab/proc/restrains() + return current_grab.restrains + +/obj/item/hand_item/grab/proc/resolve_openhand_attack() + return current_grab.resolve_openhand_attack(src) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 005128446a4a..3281d13b8c6c 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -88,3 +88,6 @@ ///A lazylist of preference-applied appearance mods present on this human. Type:Instance var/list/appearance_mods + + ///A lazylist of grab objects gripping us + var/list/grabbed_by diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index 4c932b08c75f..4fb4e37cd9d5 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -299,3 +299,11 @@ covered_flags |= worn_item.body_parts_covered return covered_flags + +///Returns an item that is covering a bodypart. +/mob/living/carbon/proc/get_item_covering_bodypart(obj/item/bodypart/BP) + return get_item_covering_zone(body_zone2cover_flags(BP.body_zone)) + +///Returns an item that is covering a body_zone (BODY_ZONE_CHEST, etc) +/mob/living/carbon/proc/get_item_covering_zone(zone) + for(var/obj/item in get_all_worn_items()) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 6832cae61937..2d8295a867dc 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -516,3 +516,7 @@ GLOBAL_LIST_INIT(bodyzone_miss_chance, list( /// Can this mob SMELL THE SMELLY SMELLS? /mob/proc/can_smell(intensity) return FALSE + +//returns the number of size categories between two mob_sizes, rounded. Positive means A is larger than B +/proc/mob_size_difference(mob_size_A, mob_size_B) + return round(log(2, mob_size_A/mob_size_B), 1) diff --git a/daedalus.dme b/daedalus.dme index 8c670b34a50d..ba658cc65e94 100644 --- a/daedalus.dme +++ b/daedalus.dme @@ -2979,6 +2979,8 @@ #include "code\modules\food_and_drinks\restaurant\custom_order.dm" #include "code\modules\food_and_drinks\restaurant\generic_venues.dm" #include "code\modules\food_and_drinks\restaurant\customers\_customer.dm" +#include "code\modules\grab\grab_datum.dm" +#include "code\modules\grab\grab_object.dm" #include "code\modules\holiday\easter.dm" #include "code\modules\holiday\foreign_calendar.dm" #include "code\modules\holiday\holidays.dm" From ada0ca62b1fac198c4367600ac9eab2f5c23a760 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Tue, 26 Sep 2023 02:08:57 -0400 Subject: [PATCH 02/37] refactor what i just wrote lol lmao --- code/__DEFINES/bodyparts.dm | 12 +- code/_compile_options.dm | 2 +- code/_onclick/item_attack.dm | 2 +- code/game/atoms_movable.dm | 3 + code/modules/grab/grab_datum.dm | 70 ++-- code/modules/grab/grab_object.dm | 98 ++++-- code/modules/grab/grabs/grab_normal.dm | 316 ++++++++++++++++++ code/modules/mob/living/carbon/human/human.dm | 15 + .../mob/living/carbon/human/human_defines.dm | 3 - code/modules/mob/living/living.dm | 37 +- code/modules/mob/mob_movement.dm | 12 +- code/modules/surgery/bodyparts/_bodyparts.dm | 46 ++- code/modules/surgery/bodyparts/head.dm | 1 + code/modules/surgery/bodyparts/injuries.dm | 36 ++ code/modules/surgery/bodyparts/parts.dm | 5 + daedalus.dme | 1 + goon/icons/items/grab.dmi | Bin 0 -> 686 bytes 17 files changed, 552 insertions(+), 107 deletions(-) create mode 100644 code/modules/grab/grabs/grab_normal.dm create mode 100644 goon/icons/items/grab.dmi diff --git a/code/__DEFINES/bodyparts.dm b/code/__DEFINES/bodyparts.dm index 4618a751494a..0a6f0128f943 100644 --- a/code/__DEFINES/bodyparts.dm +++ b/code/__DEFINES/bodyparts.dm @@ -39,15 +39,19 @@ #define BP_CUT_AWAY (1<<10) /// Limb cannot feel pain #define BP_NO_PAIN (1<<11) +/// Limb can be dislocated +#define BP_CAN_BE_DISLOCATED (1<<12) +/// Limb is dislocated +#define BP_DISLOCATED (1<<13) #define HATCH_CLOSED 1 #define HATCH_UNSCREWED 2 #define HATCH_OPENED 3 -#define STOCK_BP_FLAGS_CHEST (BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_ARTERY) -#define STOCK_BP_FLAGS_HEAD (BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_ARTERY) -#define STOCK_BP_FLAGS_ARMS (BP_IS_GRABBY_LIMB | BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_TENDON | BP_HAS_ARTERY) -#define STOCK_BP_FLAGS_LEGS (BP_IS_MOVEMENT_LIMB | BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_TENDON | BP_HAS_ARTERY) +#define STOCK_BP_FLAGS_CHEST (BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_ARTERY | BP_CAN_BE_DISLOCATED) +#define STOCK_BP_FLAGS_HEAD (BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_ARTERY | BP_CAN_BE_DISLOCATED) +#define STOCK_BP_FLAGS_ARMS (BP_IS_GRABBY_LIMB | BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_TENDON | BP_HAS_ARTERY | BP_CAN_BE_DISLOCATED) +#define STOCK_BP_FLAGS_LEGS (BP_IS_MOVEMENT_LIMB | BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_TENDON | BP_HAS_ARTERY| BP_CAN_BE_DISLOCATED) //check_bones() return values #define CHECKBONES_NONE (1<<0) diff --git a/code/_compile_options.dm b/code/_compile_options.dm index 73b9ce646e61..bbd9b98a8c73 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -148,7 +148,7 @@ /////////////////////// MISC PERFORMANCE //uncomment this to load centcom and runtime station and thats it. -// #define LOWMEMORYMODE +#define LOWMEMORYMODE //uncomment to enable the spatial grid debug proc. // #define SPATIAL_GRID_ZLEVEL_STATS diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index d3e3cdb0b7b2..151a26512d0a 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -359,5 +359,5 @@ * Returns boolean to indicate whether the attack call was handled or not. If `FALSE`, the next `use_*` proc in the * resolve chain will be called. */ -/atom/proc/use_grab(obj/item/hand_item/grab/grab, list/params) +/atom/proc/attack_grab(obj/item/hand_item/grab/grab, list/params) return FALSE diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 004b99dbb652..c458e9519776 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -95,6 +95,9 @@ ///For storing what do_after's someone has, key = string, value = amount of interactions of that type happening. var/list/do_afters + ///A lazylist of grab objects gripping us + var/list/grabbed_by + /mutable_appearance/emissive_blocker /mutable_appearance/emissive_blocker/New() diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index 7d4f00fd7ad0..76af09fdf940 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -83,6 +83,11 @@ GLOBAL_LIST_EMPTY(all_grabstates) if(downgrab) downgrab = GLOB.all_grabstates[upgrab] +/// Called by the grab item's setup() proc. May return FALSE to interrupt, otherwise the grab has succeeded. +/datum/grab/proc/setup(obj/item/hand_item/grab) + SHOULD_CALL_PARENT(TRUE) + return TRUE + // This is for the strings defined as datum variables. It takes them and swaps out keywords for relevent ones from the grab // object involved. /datum/grab/proc/string_process(obj/item/hand_item/grab/G, to_write, obj/item/used_item) @@ -147,40 +152,49 @@ GLOBAL_LIST_EMPTY(all_grabstates) qdel(G) return -/datum/grab/proc/hit_with_grab(obj/item/hand_item/grab/G, params) +/datum/grab/proc/hit_with_grab(obj/item/hand_item/grab/G, atom/target, params) if(downgrade_on_action) G.downgrade() - if(G.check_action_cooldown() && !G.attacking) - var/combat_mode = G.assailant.combat_mode - if(params[RIGHT_CLICK]) - if(on_hit_disarm(G)) - G.action_used() - make_log(G, disarm_action) - return TRUE - - else if(params[CTRL_CLICK]) - if(on_hit_grab(G)) - G.action_used() - make_log(G, grab_action) - return TRUE - - - else if(combat_mode) - if(on_hit_harm(G)) - G.action_used() - make_log(G, harm_action) - return TRUE - else - if(on_hit_help(G)) - G.action_used() - make_log(G, help_action) - return TRUE - - else + if(!G.check_action_cooldown() || G.is_currently_resolving_hit) to_chat(G.assailant, span_warning("You must wait before you can do that.")) return FALSE + G.is_currently_resolving_hit = TRUE + var/combat_mode = G.assailant.combat_mode + if(params[RIGHT_CLICK]) + if(on_hit_disarm(G)) + . = disarm_action || TRUE + + else if(params[CTRL_CLICK]) + if(on_hit_grab(G)) + . = grab_action || TRUE + + + else if(combat_mode) + if(on_hit_harm(G)) + . = harm_action || TRUE + else + if(on_hit_help(G)) + . = help_action || TRUE + + if(QDELETED(src)) + return + + G.is_currently_resolving_hit = FALSE + + if(!.) + return + + G.action_used() + if(G.assailant) + G.assailant.changeNext_move(CLICK_CD_MELEE) + if(istext(.) && G.affecting) + make_log(G, "used [.] on") + + if(downgrade_on_action) + G.downgrade() + /datum/grab/proc/make_log(obj/item/hand_item/grab/G, action) log_combat(G.assailant, G.affecting, "[action]s their victim") diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 62485e26ba6d..e70d89b0a475 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -2,24 +2,30 @@ name = "grab" item_flags = DROPDEL | ABSTRACT | HAND_ITEM | NOBLUDGEON - var/mob/living/carbon/human/affecting = null - var/mob/living/carbon/human/assailant = null + /// The initiator of the grab + var/mob/living/assailant = null + /// The thing being grabbed + var/atom/movable/affecting = null + /// The grab datum currently being used var/datum/grab/current_grab - var/type_name + /// world.time of the last action var/last_action + /// world.time of the last upgrade var/last_upgrade - - var/special_target_functional = 1 - - var/attacking = 0 + /// Indicates if the current grab has special interactions applied to the target organ (eyes and mouth at time of writing) + var/special_target_functional = TRUE + /// Used to avoid stacking interactions that sleep during /decl/grab/proc/on_hit_foo() (ie. do_after() is used) + var/is_currently_resolving_hit = FALSE + /// Records a specific bodypart that was targetted by this grab. var/target_zone - var/done_struggle = FALSE // Used by struggle grab datum to keep track of state. + /// Used by struggle grab datum to keep track of state. + var/done_struggle = FALSE /* This section is for overrides of existing procs. */ -/obj/item/hand_item/grab/Initialize(mapload, mob/living/carbon/human/victim, datum/grab/grab_type) +/obj/item/hand_item/grab/Initialize(mapload, atom/movable/target, datum/grab/grab_type) . = ..() current_grab = GLOB.all_grabstates[grab_type] @@ -27,29 +33,37 @@ if(!istype(assailant)) return INITIALIZE_HINT_QDEL - affecting = victim + affecting = target if(!istype(affecting)) return INITIALIZE_HINT_QDEL target_zone = assailant.zone_selected if(!can_grab()) return INITIALIZE_HINT_QDEL - if(!init()) + if(!setup()) return INITIALIZE_HINT_QDEL var/obj/item/bodypart/BP = get_targeted_bodypart() - name = "[initial(name)] ([BP.plaintext_zone])" + if(BP) + name = "[initial(name)] ([BP.plaintext_zone])" + RegisterSignal(affecting, COMSIG_CARBON_REMOVED_LIMB, PROC_REF(on_limb_loss)) + + RegisterSignal(affecting, COMSIG_PARENT_QDELETING, PROC_REF(target_del)) RegisterSignal(assailant, COMSIG_MOB_SELECTED_ZONE_SET, PROC_REF(on_target_change)) RegisterSignal(assailant, COMSIG_MOVABLE_MOVED, PROC_REF(relay_user_move)) - RegisterSignal(affecting, COMSIG_CARBON_REMOVED_LIMB, PROC_REF(on_limb_loss)) - RegisterSignal(affecting, COMSIG_PARENT_QDELETING, PROC_REF(target_del)) +/obj/item/hand_item/grab/Destroy() + assailant = null + affecting = null + return ..() /obj/item/hand_item/grab/examine(mob/user) . = ..() + var/mob/living/L = get_affecting_mob() var/obj/item/bodypart/BP = get_targeted_bodypart() - to_chat(user, "A grab on \the [affecting]'s [BP.plaintext_zone].") + if(L && BP) + to_chat(user, "A grab on \the [L]'s [BP.plaintext_zone].") /obj/item/hand_item/grab/update_icon_state() . = ..() @@ -71,17 +85,11 @@ /obj/item/hand_item/grab/pre_attack(atom/A, mob/living/user, params) // End workaround - if (QDELETED(src) || !assailant) - return TRUE - if (A.use_grab(src, params)) - user.changeNext_move(CLICK_CD_MELEE) - action_used() - if (current_grab.downgrade_on_action) - downgrade() + if (QDELETED(src) || !assailant || !current_grab) return TRUE - if(current_grab.hit_with_grab(src, params)) //If there is no use_grab override or if it returns FALSE; then will behave according to intent. + if(A.attack_grab(src, params) || current_grab.hit_with_grab(src, A, params)) //If there is no use_grab override or if it returns FALSE; then will behave according to intent. return TRUE - return ..() //To cover for legacy behavior. Should not reach here normally. Have all grabs be handled by use_grab or hit_with_grab. + return ..() /obj/item/hand_item/grab/Destroy() if(affecting) @@ -99,7 +107,7 @@ /obj/item/hand_item/grab/proc/on_target_change(datum/source, old_sel, new_sel) SIGNAL_HANDLER - if(src != assailant.get_active_hand()) + if(src != assailant.get_active_held_item()) return // Note that because of this condition, there's no guarantee that target_zone = old_sel if(target_zone == new_sel) return @@ -172,24 +180,26 @@ return TRUE // This will run from Initialize, after can_grab and other checks have succeeded. Must call parent; returning FALSE means failure and qdels the grab. -/obj/item/hand_item/grab/proc/init() +/obj/item/hand_item/grab/proc/setup() if(!assailant.put_in_active_hand(src)) return FALSE // This should succeed as we checked the hand, but if not we abort here. - + if(!current_grab.setup(src)) + return FALSE affecting.grabbed_by += src // This is how we handle affecting being deleted. adjust_position() action_used() - if(affecting.w_uniform) - affecting.w_uniform.add_fingerprint(assailant) + assailant.do_attack_animation(affecting) + playsound(affecting.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - update_icon() + update_appearance() return TRUE // Returns the bodypart of the grabbed person that the grabber is targeting /obj/item/hand_item/grab/proc/get_targeted_bodypart() - return (affecting?.get_bodypart(target_zone)) + var/mob/living/L = get_affecting_mob() + return (L?.get_bodypart(target_zone)) /obj/item/hand_item/grab/proc/resolve_item_attack(mob/living/M, obj/item/I, target_zone) if((M && ishuman(M)) && I) @@ -210,12 +220,14 @@ /obj/item/hand_item/grab/proc/leave_forensic_traces() if (!affecting) return + var/mob/living/carbon/carbo = get_affecting_mob() + if(istype(carbo)) + var/obj/item/clothing/C = carbo.get_item_covering_zone(target_zone) + if(istype(C)) + C.add_fingerprint(assailant) + return - var/obj/item/clothing/C = affecting.get_item_covering_zone(target_zone) - if(istype(C)) - C.add_fingerprint(assailant) - else - affecting.add_fingerprint(assailant) //If no clothing; add fingerprint to mob proper. + affecting.add_fingerprint(assailant) //If no clothing; add fingerprint to mob proper. /obj/item/hand_item/grab/proc/upgrade(bypass_cooldown = FALSE) if(!check_upgrade_cooldown() && !bypass_cooldown) @@ -273,6 +285,20 @@ return FALSE +/obj/item/hand_item/grab/proc/get_affecting_mob() + RETURN_TYPE(/mob/living) + if(isobj(affecting)) + return affecting.buckled_mobs?[1] + + if(isliving(affecting)) + return affecting +/// Primarily used for do_after() callbacks, checks if the grab item is still holding onto something +/obj/item/hand_item/grab/proc/is_grabbing(atom/movable/AM) + return affecting == AM +/* + * This section is for component signal relays/hooks +*/ + /// Relay when the assailant moves to the grab datum /obj/item/hand_item/grab/proc/relay_user_move(datum/source) SIGNAL_HANDLER diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm new file mode 100644 index 000000000000..c1b27c54dcb4 --- /dev/null +++ b/code/modules/grab/grabs/grab_normal.dm @@ -0,0 +1,316 @@ +/datum/grab/normal + icon = 'goon/icons/items/grab.dmi' + icon_state = "1" + + help_action = "inspect" + disarm_action = "pin" + grab_action = "jointlock" + harm_action = "dislocate" + + var/drop_headbutt = 1 + +/datum/grab/normal/setup(obj/item/hand_item/grab/G) + if(!(. = ..())) + return + + var/obj/item/bodypart/BP = G.get_targeted_bodypart() + if(!BP) + return + + if(G.affecting != G.assailant) + G.assailant.visible_message(span_warning("[G.assailant] has grabbed [G.affecting]'s [BP.plaintext_zone]!")) + else + G.assailant.visible_message(span_notice("[G.assailant] has grabbed [G.assailant.p_their()] [BP.plaintext_zone]!")) + +/datum/grab/normal/on_hit_help(var/obj/item/hand_item/grab/G, var/atom/A, var/proximity) + + var/obj/item/bodypart/BP = G.get_targeted_bodypart() + if(!BP || !proximity || (A && A != G.get_affecting_mob())) + return FALSE + return BP.inspect(G.assailant) + +/datum/grab/normal/on_hit_disarm(var/obj/item/hand_item/grab/G, var/atom/A, var/proximity) + + if(!proximity) + return FALSE + + var/mob/living/affecting = G.get_affecting_mob() + var/mob/living/assailant = G.assailant + if(affecting && A && A == affecting && !affecting.body_position == STANDING_UP) + + affecting.visible_message(span_danger("\The [assailant] is trying to pin \the [affecting] to the ground!")) + if(do_after(assailant, affecting, action_cooldown - 1, DO_PUBLIC, display = image('icons/hud/do_after.dmi', "harm"))) + G.action_used() + affecting.visible_message(span_danger("\The [assailant] pins \the [affecting] to the ground!")) + return TRUE + affecting.visible_message(span_warning("\The [assailant] fails to pin \the [affecting] to the ground.")) + + return FALSE + +/datum/grab/normal/on_hit_grab(var/obj/item/hand_item/grab/G, var/atom/A, var/proximity) + + if(!proximity) + return FALSE + + var/mob/living/affecting = G.get_affecting_mob() + if(!affecting || (A && A != affecting)) + return FALSE + + var/mob/living/assailant = G.assailant + if(!assailant) + return FALSE + + var/obj/item/bodypart/BP = G.get_targeted_bodypart() + if(!BP) + to_chat(assailant, span_warning("\The [affecting] is missing that body part!")) + return FALSE + + assailant.visible_message(span_danger("\The [assailant] begins to [pick("bend", "twist")] \the [affecting]'s [BP.plaintext_zone] into a jointlock!")) + if(do_after(assailant, affecting, action_cooldown - 1, DO_PUBLIC, display = image('icons/hud/do_after.dmi', "harm"))) + G.action_used() + BP.jointlock(assailant) + assailant.visible_message(span_danger("\The [affecting]'s [BP.plaintext_zone] is twisted!")) + playsound(assailant.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + return TRUE + + affecting.visible_message(span_warning("\The [assailant] fails to jointlock \the [affecting]'s [BP.plaintext_zone].")) + return FALSE + +/datum/grab/normal/on_hit_harm(var/obj/item/hand_item/grab/G, var/atom/A, var/proximity) + + if(!proximity) + return FALSE + + var/mob/living/affecting = G.get_affecting_mob() + if(!affecting || (A && A != affecting)) + return FALSE + + var/mob/living/assailant = G.assailant + if(!assailant) + return FALSE + + var/obj/item/bodypart/BP = G.get_targeted_bodypart() + if(!BP) + to_chat(assailant, span_warning("\The [affecting] is missing that body part!")) + return FALSE + + if(BP.can_be_dislocated()) + assailant.visible_message(span_danger("\The [assailant] begins to dislocate \the [affecting]'s [BP.joint_name]!")) + if(do_after(assailant, affecting, action_cooldown - 1, DO_PUBLIC)) + G.action_used() + BP.set_dislocated() + assailant.visible_message(span_danger("\The [affecting]'s [BP.joint_name] [pick("gives way","caves in","crumbles","collapses")]!")) + playsound(assailant.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + return TRUE + + affecting.visible_message(span_warning("\The [assailant] fails to dislocate \the [affecting]'s [BP.joint_name].")) + return FALSE + + if(BP.can_be_dislocated()) + to_chat(assailant, span_warning("\The [affecting]'s [BP.joint_name] is already dislocated!")) + else + to_chat(assailant, span_warning("You can't dislocate \the [affecting]'s [BP.joint_name]!")) + return FALSE + +/datum/grab/normal/resolve_openhand_attack(var/obj/item/hand_item/grab/G) + if(!G.assailant.combat_mode) + return FALSE + if(G.target_zone == BODY_ZONE_HEAD) + if(G.assailant.zone_selected == BODY_ZONE_PRECISE_EYES) + if(attack_eye(G)) + return TRUE + else + if(headbutt(G)) + if(drop_headbutt) + let_go() + return TRUE + return FALSE + +/datum/grab/normal/proc/attack_eye(var/obj/item/hand_item/grab/G) + var/mob/living/carbon/human/target = G.get_affecting_mob() + var/mob/living/carbon/human/attacker = G.assailant + if(!istype(target) || !istype(attacker)) + return + + if(target.is_eyes_covered()) + to_chat(attacker, "You're going to need to remove the eye covering first.") + return + + var/obj/item/organ/eyes/E = target.getorganslot(ORGAN_SLOT_EYES) + if(E) + to_chat(attacker, "You cannot locate any eyes on [target]!") + return + + log_combat(attacker, target, "attacked the eyes of (grab)") + + if(E) + E.applyOrganDamage(rand(3,4)) + attacker.visible_message(span_danger("\The [attacker] jams [G.p_their()] fingers into \the [target]'s [E.name]!")) + if(!(E.organ_flags & ORGAN_SYNTHETIC)) + to_chat(target, span_danger("You experience immense pain as fingers are jammed into your [E.name]!")) + else + to_chat(target, span_danger("You experience fingers being jammed into your [E.name].")) + else + attacker.visible_message(span_danger("\The [attacker] attempts to press [G.p_their()] fingers into \the [target]'s [E.name], but [target.p_they()] doesn't have any!")) + return TRUE + +/datum/grab/normal/proc/headbutt(var/obj/item/hand_item/grab/G) + var/mob/living/carbon/human/target = G.get_affecting_mob() + var/mob/living/carbon/human/attacker = G.assailant + if(!istype(target) || !istype(attacker)) + return + + if(target.body_position == LYING_DOWN) + return + + var/damage = 20 + var/obj/item/clothing/hat = attacker.head + var/sharpness + if(istype(hat)) + damage += hat.force * 3 + sharpness = hat.sharpness + + if(sharpness & SHARP_POINTY) + if(istype(hat)) + attacker.visible_message(span_danger("\The [attacker] gores \the [target] with \the [hat]!")) + else + attacker.visible_message(span_danger("\The [attacker] gores \the [target]!")) + else + attacker.visible_message(span_danger("\The [attacker] thrusts [attacker.p_their()] head into \the [target]'s skull!")) + + var/armor = target.run_armor_check(BODY_ZONE_HEAD, MELEE) + target.apply_damage(damage, BRUTE, BODY_ZONE_HEAD, armor, sharpness = sharpness) + attacker.apply_damage(10, BRUTE, BODY_ZONE_HEAD) + + if(armor < 0.5 && target.can_head_trauma_ko() && prob(damage)) + target.Unconscious(20 SECONDS) + target.visible_message(span_danger("\The [target] collapses, now fast asleep.")) + + playsound(attacker.loc, "swing_hit", 25, 1, -1) + log_combat(attacker, target, "headbutted") + return 1 + +// Handles special targeting like eyes and mouth being covered. +/datum/grab/normal/special_target_effect(var/obj/item/hand_item/grab/G) + var/mob/living/affecting_mob = G.get_affecting_mob() + if(istype(affecting_mob) && G.special_target_functional) + switch(G.target_zone) + if(BODY_ZONE_PRECISE_MOUTH) + if(iscarbon(affecting_mob)) + var/mob/living/carbon/C = affecting_mob + C.silent = max(C.silent, 2) + if(BODY_ZONE_PRECISE_EYES) + affecting_mob.blind_eyes(2 SECONDS) + +// Handles when they change targeted areas and something is supposed to happen. +/datum/grab/normal/special_target_change(var/obj/item/hand_item/grab/G, old_zone, new_zone) + if((old_zone != BODY_ZONE_HEAD && old_zone != BODY_ZONE_CHEST) || !G.get_affecting_mob()) + return + switch(new_zone) + if(BODY_ZONE_PRECISE_MOUTH) + G.assailant.visible_message("\The [G.assailant] covers [G.affecting]'s mouth!") + if(BODY_ZONE_PRECISE_EYES) + G.assailant.visible_message("\The [G.assailant] covers [G.affecting]'s eyes!") + +/datum/grab/normal/check_special_target(var/obj/item/hand_item/grab/G) + var/mob/living/affecting_mob = G.get_affecting_mob() + if(!istype(affecting_mob)) + return FALSE + switch(G.target_zone) + if(BODY_ZONE_PRECISE_MOUTH) + if(!affecting_mob.has_mouth()) + to_chat(G.assailant, "You cannot locate a mouth on [G.affecting]!") + return FALSE + if(BODY_ZONE_PRECISE_EYES) + if(!affecting_mob.getorganslot(ORGAN_SLOT_EYES)) + to_chat(G.assailant, "You cannot locate any eyes on [G.affecting]!") + return FALSE + return TRUE + +/datum/grab/normal/resolve_item_attack(var/obj/item/hand_item/grab/G, var/mob/living/carbon/human/user, var/obj/item/I) + switch(G.target_zone) + if(BODY_ZONE_HEAD) + return attack_throat(G, I, user) + else + return attack_tendons(G, I, user, G.target_zone) + +/datum/grab/normal/proc/attack_throat(var/obj/item/hand_item/grab/G, var/obj/item/W, mob/living/user) + var/mob/living/carbon/affecting = G.get_affecting_mob() + if(!istype(affecting)) + return + if(!user.combat_mode) + return FALSE // Not trying to hurt them. + + if(!(W.sharpness & SHARP_EDGED) || !W.force || W.damtype != BRUTE) + return FALSE //unsuitable weapon + + if(DOING_INTERACTION(user, "throat slit")) + return FALSE + + user.visible_message("\The [user] begins to slit [affecting]'s throat with \the [W]!") + + user.changeNext_move(CLICK_CD_MELEE) + if(!do_after(user, affecting, 2 SECONDS, DO_PUBLIC, extra_checks = CALLBACK(G, TYPE_PROC_REF(/obj/item/hand_item/grab, is_grabbing), affecting), interaction_key = "throat slit", display = W)) + return FALSE + + if(!(G && G.affecting == affecting)) //check that we still have a grab + return FALSE + + var/armor + + //presumably, if they are wearing a helmet that stops pressure effects, then it probably covers the throat as well + for(var/obj/item/clothing/equipped in affecting.get_equipped_items()) + if((equipped.body_parts_covered & HEAD) && (equipped.clothing_flags & STOPSPRESSUREDAMAGE)) + armor = affecting.run_armor_check(BODY_ZONE_HEAD, MELEE, silent = TRUE) + break + + var/total_damage = 0 + for(var/i in 1 to 3) + var/damage = min(W.force*1.5, 20) + affecting.apply_damage(damage, BRUTE, BODY_ZONE_HEAD, armor, sharpness = W.sharpness) + total_damage += damage + + if(total_damage) + user.visible_message("\The [user] slit [affecting]'s throat open with \the [W]!") + + if(W.hitsound) + playsound(affecting.loc, W.hitsound, 50, 1, -1) + + G.last_action = world.time + + log_combat(user, affecting, "slit throat (grab)") + return 1 + +/datum/grab/normal/proc/attack_tendons(var/obj/item/hand_item/grab/G, var/obj/item/W, mob/living/user, var/target_zone) + var/mob/living/affecting = G.get_affecting_mob() + if(!affecting) + return + if(!user.combat_mode) + return FALSE // Not trying to hurt them. + + if(!(W.sharpness & SHARP_EDGED) || !W.force || W.damtype != BRUTE) + return FALSE //unsuitable weapon + + var/obj/item/bodypart/BP = G.get_targeted_bodypart() + if(!BP || !(BP.check_tendon() == CHECKTENDON_OK)) + return FALSE + + if(DOING_INTERACTION(user, "slice tendon")) + return FALSE + + user.visible_message(span_danger("\The [user] begins to cut \the [affecting]'s [BP.tendon_name] with \the [W]!")) + user.changeNext_move(CLICK_CD_MELEE) + + if(!do_after(user, affecting, 2 SECONDS, DO_PUBLIC, extra_checks = CALLBACK(G, TYPE_PROC_REF(/obj/item/hand_item/grab, is_grabbing), affecting), interaction_key = "slice tendon", display = W)) + return FALSE + + if(!BP || !BP.set_sever_tendon(TRUE)) + return FALSE + + user.visible_message(span_danger("\The [user] cut \the [affecting]'s [BP.tendon_name] with \the [W]!")) + + if(W.hitsound) + playsound(affecting.loc, W.hitsound, 50, 1, -1) + G.last_action = world.time + log_combat(user, affecting, "hamstrung (grab)") + return TRUE diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 4b9f33232d49..afc9877e9561 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1131,3 +1131,18 @@ return FALSE return dna.species.organs[slot] + +//Used by various things that knock people out by applying blunt trauma to the head. +//Checks that the species has a "head" (brain containing organ) and that hit_zone refers to it. +/mob/living/carbon/human/proc/can_head_trauma_ko() + var/obj/item/organ/brain = getorganslot(ORGAN_SLOT_BRAIN) + if(!brain || !needs_organ(ORGAN_SLOT_BRAIN)) + return FALSE + + //if the parent organ is significantly larger than the brain organ, then hitting it is not guaranteed + var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD) + if(!head) + return FALSE + if(head.w_class > brain.w_class + 1) + return prob(100 / 2**(head.w_class - brain.w_class - 1)) + return TRUE diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 3281d13b8c6c..005128446a4a 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -88,6 +88,3 @@ ///A lazylist of preference-applied appearance mods present on this human. Type:Instance var/list/appearance_mods - - ///A lazylist of grab objects gripping us - var/list/grabbed_by diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index cd7b49d8e5e3..940a96d4b8c1 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1054,34 +1054,21 @@ else if(last_special <= world.time) resist_restraints() //trying to remove cuffs. +/// Attempt to break free of grabs. Returning TRUE means the user broke free and can move. /mob/proc/resist_grab(moving_resist) - return 1 //returning 0 means we successfully broke free + return TRUE /mob/living/resist_grab(moving_resist) - . = TRUE - if(pulledby.grab_state || body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_GRABWEAKNESS)) - var/altered_grab_state = pulledby.grab_state - if((body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_GRABWEAKNESS)) && pulledby.grab_state < GRAB_KILL) //If prone, resisting out of a grab is equivalent to 1 grab state higher. won't make the grab state exceed the normal max, however - altered_grab_state++ - var/resist_chance = BASE_GRAB_RESIST_CHANCE /// see defines/combat.dm, this should be baseline 60% - resist_chance = (resist_chance/altered_grab_state) ///Resist chance divided by the value imparted by your grab state. It isn't until you reach neckgrab that you gain a penalty to escaping a grab. - if(prob(resist_chance)) - visible_message(span_danger("[src] breaks free of [pulledby]'s grip!"), \ - span_danger("You break free of [pulledby]'s grip!"), null, null, pulledby) - to_chat(pulledby, span_warning("[src] breaks free of your grip!")) - log_combat(pulledby, src, "broke grab") - pulledby.stop_pulling() - return FALSE - else - stamina.adjust(-rand(15,20)) //failure to escape still imparts a pretty serious penalty - visible_message(span_danger("[src] struggles as they fail to break free of [pulledby]'s grip!"), \ - span_warning("You struggle as you fail to break free of [pulledby]'s grip!"), null, null, pulledby) - to_chat(pulledby, span_danger("[src] struggles as they fail to break free of your grip!")) - if(moving_resist && client) //we resisted by trying to move - client.move_delay = world.time + 4 SECONDS - else - pulledby.stop_pulling() - return FALSE + if(!LAZYLEN(grabbed_by)) + return TRUE + + if(moving_resist && client) //we resisted by trying to move + client.move_delay = world.time + 4 SECONDS + + visible_message(span_danger("\The [src] struggles to break free!")) + + for(var/obj/item/hand_item/grab/G as anything in grabbed_by) + . = G.handle_resist() || . /mob/living/proc/resist_buckle() buckled.user_unbuckle_mob(src,src) diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 9500c1abc49b..68703e071b04 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -104,7 +104,7 @@ if(isAI(mob)) return AIMove(new_loc,direct,mob) - if(Process_Grab()) //are we restrained by someone's grip? + if(Process_Grabs()) //are we restrained by someone's grip? return if(mob.buckled) //if we're buckled to something, tell it we moved. @@ -171,18 +171,14 @@ * * Called by client/Move() */ -/client/proc/Process_Grab() - if(!mob.pulledby) - return FALSE - if(mob.pulledby == mob.pulling && mob.pulledby.grab_state == GRAB_PASSIVE) //Don't autoresist passive grabs if we're grabbing them too. - return FALSE +/client/proc/Process_Grabs() if(HAS_TRAIT(mob, TRAIT_INCAPACITATED)) COOLDOWN_START(src, move_delay, 1 SECONDS) - return TRUE + return FALSE else if(HAS_TRAIT(mob, TRAIT_RESTRAINED)) COOLDOWN_START(src, move_delay, 1 SECONDS) to_chat(src, span_warning("You're restrained! You can't move!")) - return TRUE + return FALSE return mob.resist_grab(TRUE) diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index d5240011597d..1f7f2761dc53 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -124,6 +124,8 @@ var/artery_name = "artery" /// The name of the tendon this limb has var/tendon_name = "tendon" + /// The name of the joint you can dislocate + var/joint_name = "joint" /// The name for the amputation point of the limb var/amputation_point /// Surgical stage. Magic BS. Do not touch @@ -262,7 +264,7 @@ . = ..() . += mob_examine() -/obj/item/bodypart/proc/mob_examine(hallucinating, covered) +/obj/item/bodypart/proc/mob_examine(hallucinating, covered, just_wounds_please) . = list() if(covered) @@ -330,6 +332,9 @@ if(6 to INFINITY) flavor_text += "a ton of [wound]\s" + if(just_wounds_please) + return english_list(flavor_text) + if(owner) if(current_damage) . += "[owner.p_they(TRUE)] [owner.p_have()] [english_list(flavor_text)] on [owner.p_their()] [plaintext_zone]." @@ -1371,3 +1376,42 @@ else user.visible_message(span_notice("[user] removes [removed] from [owner]'s [plaintext_zone].")) return + +/obj/item/bodypart/proc/inspect(mob/user) + if(is_stump) + to_chat(user, span_notice("[owner] is missing that bodypart.")) + return + + user.visible_message(span_notice("[user] starts inspecting [owner]'s [plaintext_zone] carefully.")) + if(LAZYLEN(wounds)) + to_chat(user, span_warning("You find [mob_examine(just_wounds_please = TRUE)].")) + var/list/stuff = list() + for(var/datum/wound/wound as anything in wounds) + if(LAZYLEN(wound.embedded_objects)) + stuff |= wound.embedded_objects + + if(length(stuff)) + to_chat(user, span_warning("There's [english_list(stuff)] sticking out of [owner]'s [plaintext_zone].")) + else + to_chat(user, span_notice("You find no visible wounds.")) + + to_chat(user, span_notice("Checking skin now...")) + + if(!do_after(user, owner, 1 SECOND, DO_PUBLIC)) + return + + to_chat(user, span_notice("Checking bones now...")) + if(!do_after(user, owner, 1 SECOND, DO_PUBLIC)) + return + + if(bodypart_flags & BP_BROKEN_BONES) + to_chat(user, span_warning("The [encased ? encased : "bone in the [plaintext_zone]"] moves slightly when you poke it!")) + owner.apply_pain(40, body_zone, "Your [plaintext_zone] hurts where it's poked.") + else + to_chat(user, span_notice("The [encased ? encased : "bones in the [plaintext_zone]"] seem to be fine.")) + + if(bodypart_flags & BP_TENDON_CUT) + to_chat(user, span_warning("The tendons in the [plaintext_zone] are severed!")) + if(bodypart_flags & BP_DISLOCATED) + to_chat(user, span_warning("The [joint_name] is dislocated!")) + return TRUE diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index 42fa7dc72fae..1d26ff3f667c 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -31,6 +31,7 @@ encased = "skull" artery_name = "carotid artery" cavity_name = "cranial" + joint_name = "jaw" minimum_break_damage = 30 diff --git a/code/modules/surgery/bodyparts/injuries.dm b/code/modules/surgery/bodyparts/injuries.dm index ad7bebc09729..e9ab90d5475c 100644 --- a/code/modules/surgery/bodyparts/injuries.dm +++ b/code/modules/surgery/bodyparts/injuries.dm @@ -155,6 +155,31 @@ update_disabled() return TRUE +/obj/item/bodypart/proc/can_be_dislocated() + if(!(bodypart_flags & BP_CAN_BE_DISLOCATED)) + return FALSE + if(bodypart_flags & BP_DISLOCATED) + return FALSE + return TRUE + +/obj/item/bodypart/proc/set_dislocated(val, painless) + if(val) + if(!can_be_dislocated()) + return FALSE + else + if(can_be_dislocated()) + return FALSE + + if(val) + bodypart_flags |= BP_DISLOCATED + if(!painless) + owner?.apply_pain(20, body_zone, "A surge of pain shoots through your [plaintext_zone].") + else + bodypart_flags &= BP_DISLOCATED + + return TRUE + + /obj/item/bodypart/proc/clamp_wounds() for(var/datum/wound/W as anything in wounds) . ||= !W.clamped @@ -223,3 +248,14 @@ if(!W) return W.open_wound(min(W.damage * 2, W.damage_list[1] - W.damage)) + +/obj/item/bodypart/proc/jointlock(mob/living/user) + if(!IS_ORGANIC_LIMB(src)) + return + + var/armor = owner.run_armor_check(body_zone, MELEE, silent = TRUE) + if(armor > 70) + return + + var/max_halloss = round(owner.maxHealth * 0.8 * ((100 - armor) / 100)) //up to 80% of passing out, further reduced by armour + owner.apply_pain(max(30, max_halloss - owner.getPain()), body_zone, "your [plaintext_zone] is in excruciating pain") diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm index d73430c4efaa..04be40662950 100644 --- a/code/modules/surgery/bodyparts/parts.dm +++ b/code/modules/surgery/bodyparts/parts.dm @@ -16,6 +16,7 @@ encased = "ribcage" artery_name = "aorta" cavity_name = "thoracic" + joint_name = "neck" minimum_break_damage = 35 @@ -105,6 +106,7 @@ px_y = 0 bodypart_trait_source = LEFT_ARM_TRAIT amputation_point = "left shoulder" + joint_name = "left elbow" /obj/item/bodypart/arm/left/set_owner(new_owner) @@ -188,6 +190,7 @@ bodypart_trait_source = RIGHT_ARM_TRAIT can_be_disabled = TRUE amputation_point = "right shoulder" + joint_name = "right elbow" /obj/item/bodypart/arm/right/set_owner(new_owner) . = ..() @@ -297,6 +300,7 @@ can_be_disabled = TRUE bodypart_trait_source = LEFT_LEG_TRAIT amputation_point = "left hip" + joint_name = "left knee" /obj/item/bodypart/leg/left/set_owner(new_owner) @@ -377,6 +381,7 @@ bodypart_trait_source = RIGHT_LEG_TRAIT can_be_disabled = TRUE amputation_point = "right hip" + joint_name = "right knee" /obj/item/bodypart/leg/right/set_owner(new_owner) . = ..() diff --git a/daedalus.dme b/daedalus.dme index ba658cc65e94..2c1a7fe42095 100644 --- a/daedalus.dme +++ b/daedalus.dme @@ -2981,6 +2981,7 @@ #include "code\modules\food_and_drinks\restaurant\customers\_customer.dm" #include "code\modules\grab\grab_datum.dm" #include "code\modules\grab\grab_object.dm" +#include "code\modules\grab\grabs\grab_normal.dm" #include "code\modules\holiday\easter.dm" #include "code\modules\holiday\foreign_calendar.dm" #include "code\modules\holiday\holidays.dm" diff --git a/goon/icons/items/grab.dmi b/goon/icons/items/grab.dmi new file mode 100644 index 0000000000000000000000000000000000000000..eb4b9699b4c29fc5ab4bd8aa5b13f2b9135fc934 GIT binary patch literal 686 zcmV;f0#W^mP);<7#M&Z z9sd9TH1}CY00001bW%=J06^y0W&i*Hf_hX~bVOxyV{&P5bZKvH004NLQ&wBN&B3t_^&Hr&0CIo?mmK)40CIo?MGj)GDyVFtadaPcQJAKwP{8k^%070^eLM#t zg!XgL`y_Y&76}9bfj}T=!?*w3nMIrZx2j=&FAUyui#=2S`&|Fe%d%FnEOU!}z5XZc z1RUlTd#pVPsRE6~Cxws*AlePux@|bDYZ}&_0Bz~&Kv#$~_^X1W{x215(3ae3I4e-; z4nci)QCHxr3Uc6#f*klq1*Wrtw3c6TkOw9Qc_w0(0G~`d U-ASniO#lD@07*qoM6N<$f>#|Nng9R* literal 0 HcmV?d00001 From 3b1902134737f29d2a825e5fc43bfa39440ca7e9 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Tue, 26 Sep 2023 04:56:18 -0400 Subject: [PATCH 03/37] saving progress --- .../signals_atom/signals_atom_movement.dm | 27 +++-- code/__DEFINES/is_helpers.dm | 3 + code/_onclick/hud/screen_objects.dm | 15 --- code/_onclick/item_attack.dm | 2 +- code/datums/ai/idle_behaviors/idle_dog.dm | 2 +- code/datums/ai/idle_behaviors/idle_monkey.dm | 2 +- .../ai/idle_behaviors/idle_random_walk.dm | 2 +- code/datums/ai/monkey/monkey_behaviors.dm | 8 +- code/datums/ai/monkey/monkey_controller.dm | 9 +- code/datums/ai/movement/_ai_movement.dm | 2 +- .../robot_customer_controller.dm | 4 +- code/datums/brain_damage/special.dm | 11 +- code/datums/components/butchering.dm | 47 -------- code/datums/components/drift.dm | 4 +- code/datums/components/jetpack.dm | 2 +- code/datums/components/riding/riding_mob.dm | 2 +- code/datums/components/rot.dm | 2 +- code/datums/components/shy.dm | 2 +- code/datums/components/strong_pull.dm | 2 +- code/datums/components/tackle.dm | 2 +- code/datums/elements/atmos_requirements.dm | 3 +- code/datums/keybinding/mob.dm | 10 +- code/datums/martial/cqc.dm | 3 +- code/datums/martial/wrestling.dm | 9 +- code/game/atoms_movable.dm | 64 ++--------- code/game/machinery/washing_machine.dm | 27 +++-- code/game/objects/buckling.dm | 7 +- code/game/objects/items/hand_items.dm | 5 +- code/game/objects/items/storage/bags.dm | 5 +- .../structures/crates_lockers/closets.dm | 2 +- code/game/turfs/open/_open.dm | 2 +- code/game/turfs/turf.dm | 3 +- .../antagonists/changeling/powers/absorb.dm | 5 +- code/modules/antagonists/cult/cult_items.dm | 7 +- code/modules/awaymissions/signpost.dm | 7 +- code/modules/cargo/supplypod.dm | 2 +- .../kitchen_machinery/deep_fryer.dm | 57 ++++++---- .../kitchen_machinery/processor.dm | 3 +- code/modules/grab/grab_datum.dm | 72 ++++++++++-- code/modules/grab/grab_helpers.dm | 40 +++++++ code/modules/grab/grab_living.dm | 77 +++++++++++++ code/modules/grab/grab_movable.dm | 30 +++++ code/modules/grab/grab_object.dm | 105 ++++-------------- code/modules/grab/grabs/grab_carbon.dm | 7 ++ code/modules/grab/grabs/grab_normal.dm | 2 +- code/modules/grab/grabs/grab_simple.dm | 25 +++++ code/modules/grab/human_grab.dm | 33 ++++++ code/modules/holodeck/items.dm | 23 ++-- code/modules/mob/inventory.dm | 13 ++- .../living/carbon/alien/humanoid/humanoid.dm | 4 +- code/modules/mob/living/carbon/carbon.dm | 4 +- .../mob/living/carbon/carbon_defense.dm | 4 +- .../mob/living/carbon/human/examine.dm | 4 +- code/modules/mob/living/carbon/human/human.dm | 3 +- .../mob/living/carbon/human/human_context.dm | 13 +-- .../mob/living/carbon/human/inventory.dm | 2 +- code/modules/mob/living/carbon/inventory.dm | 4 +- code/modules/mob/living/carbon/life.dm | 2 +- code/modules/mob/living/death.dm | 2 +- code/modules/mob/living/init_signals.dm | 3 +- code/modules/mob/living/living.dm | 32 +++--- code/modules/mob/living/living_defense.dm | 11 +- code/modules/mob/living/living_stripping.dm | 2 +- .../mob/living/silicon/pai/pai_shell.dm | 4 +- .../simple_animal/friendly/drone/inventory.dm | 4 +- .../living/simple_animal/guardian/guardian.dm | 4 +- .../mob/living/simple_animal/hostile/mimic.dm | 2 +- .../mob/living/simple_animal/simple_animal.dm | 2 +- code/modules/mod/mod_ai.dm | 2 +- code/modules/mod/modules/modules_supply.dm | 5 +- code/modules/projectiles/aiming.dm | 2 +- code/modules/shuttle/on_move.dm | 4 +- code/modules/surgery/organs/external/wings.dm | 2 +- code/modules/tables/tables_racks.dm | 36 +++--- daedalus.dme | 6 + 75 files changed, 550 insertions(+), 411 deletions(-) create mode 100644 code/modules/grab/grab_helpers.dm create mode 100644 code/modules/grab/grab_living.dm create mode 100644 code/modules/grab/grab_movable.dm create mode 100644 code/modules/grab/grabs/grab_carbon.dm create mode 100644 code/modules/grab/grabs/grab_simple.dm create mode 100644 code/modules/grab/human_grab.dm diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm index bdc082f9c759..9b36207ac0a8 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm @@ -2,26 +2,25 @@ // When the signal is called: (signal arguments) // All signals send the source datum of the signal as the first argument -///signal sent out by an atom when it checks if it can be pulled, for additional checks -#define COMSIG_ATOM_CAN_BE_PULLED "movable_can_be_pulled" - #define COMSIG_ATOM_CANT_PULL (1 << 0) +///signal sent out by an atom when it checks if it can be grabbed, for additional checks +#define COMSIG_ATOM_CAN_BE_GRABBED "movable_can_be_grabbed" + #define COMSIG_ATOM_NO_GRAB (1 << 0) ///signal sent out by an atom when it is no longer being pulled by something else : (atom/puller) -#define COMSIG_ATOM_NO_LONGER_PULLED "movable_no_longer_pulled" +#define COMSIG_ATOM_NO_LONGER_GRABBED "movable_no_longer_grabbed" ///signal sent out by an atom when it is no longer pulling something : (atom/pulling) -#define COMSIG_ATOM_NO_LONGER_PULLING "movable_no_longer_pulling" +#define COMSIG_ATOM_NO_LONGER_GRABBING "movable_no_longer_grabbing" ///called for each movable in a turf contents on /turf/zImpact(): (atom/movable/A, levels) #define COMSIG_ATOM_INTERCEPT_Z_FALL "movable_intercept_z_impact" -///called on a movable (NOT living) when it starts pulling (atom/movable/pulled, state, force) -#define COMSIG_ATOM_START_PULL "movable_start_pull" -///called on /living when someone starts pulling (atom/movable/pulled, state, force) -#define COMSIG_LIVING_START_PULL "living_start_pull" -///called on /living when someone is pulled (mob/living/puller) -#define COMSIG_LIVING_GET_PULLED "living_start_pulled" -///called on /living, when pull is attempted, but before it completes, from base of [/mob/living/start_pulling]: (atom/movable/thing, force) -#define COMSIG_LIVING_TRY_PULL "living_try_pull" - #define COMSIG_LIVING_CANCEL_PULL (1 << 0) +///called on a movable when it starts pulling (atom/movable/pulled, state, force) +#define COMSIG_ATOM_START_GRAB "movable_start_grab" +///called on a movable when it has been grabbed +#define COMSIG_ATOM_GET_GRABBED "movable_start_grabbed" +///called on /living, when a grab is attempted, but before it completes, from base of [/mob/living/make_grab]: (atom/movable/thing, grab_type) +#define COMSIG_LIVING_TRY_GRAB "living_try_pull" + #define COMSIG_LIVING_CANCEL_GRAB (1 << 0) /// Called from /mob/living/update_pull_movespeed #define COMSIG_LIVING_UPDATING_PULL_MOVESPEED "living_updating_pull_movespeed" + /// Called from /mob/living/PushAM -- Called when this mob is about to push a movable, but before it moves /// (aotm/movable/being_pushed) #define COMSIG_LIVING_PUSHING_MOVABLE "living_pushing_movable" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index e4c1945cf50a..053da27efe1a 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -224,6 +224,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list( #define isfalsewall(A) (istype(A, /obj/structure/falsewall)) +#define isgrab(A) (istype(A, /obj/item/hand_item/grab)) + //Assemblies #define isassembly(O) (istype(O, /obj/item/assembly)) @@ -271,3 +273,4 @@ GLOBAL_LIST_INIT(book_types, typecacheof(list( #define is_security_officer_job(job_type) (istype(job_type, /datum/job/security_officer)) #define is_research_director_job(job_type) (istype(job_type, /datum/job/research_director)) #define is_unassigned_job(job_type) (istype(job_type, /datum/job/unassigned)) + diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 2509fc9ec2db..1f443513c783 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -386,21 +386,6 @@ else user.set_move_intent(MOVE_INTENT_RUN) -/atom/movable/screen/pull - name = "stop pulling" - icon = 'icons/hud/screen_midnight.dmi' - icon_state = "pull" - base_icon_state = "pull" - -/atom/movable/screen/pull/Click() - if(isobserver(usr)) - return - usr.stop_pulling() - -/atom/movable/screen/pull/update_icon_state() - icon_state = "[base_icon_state][hud?.mymob?.pulling ? null : 0]" - return ..() - /atom/movable/screen/resist name = "resist" icon = 'icons/hud/screen_midnight.dmi' diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 151a26512d0a..24e9850bbe2e 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -359,5 +359,5 @@ * Returns boolean to indicate whether the attack call was handled or not. If `FALSE`, the next `use_*` proc in the * resolve chain will be called. */ -/atom/proc/attack_grab(obj/item/hand_item/grab/grab, list/params) +/atom/proc/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) return FALSE diff --git a/code/datums/ai/idle_behaviors/idle_dog.dm b/code/datums/ai/idle_behaviors/idle_dog.dm index fd27bc6968e3..6e570e301143 100644 --- a/code/datums/ai/idle_behaviors/idle_dog.dm +++ b/code/datums/ai/idle_behaviors/idle_dog.dm @@ -1,7 +1,7 @@ ///Dog specific idle behavior. /datum/idle_behavior/idle_dog/perform_idle_behavior(delta_time, datum/ai_controller/dog/controller) var/mob/living/living_pawn = controller.pawn - if(!isturf(living_pawn.loc) || living_pawn.pulledby) + if(!isturf(living_pawn.loc) || LAZYLEN(living_pawn.grabbed_by)) return // if we were just ordered to heel, chill out for a bit diff --git a/code/datums/ai/idle_behaviors/idle_monkey.dm b/code/datums/ai/idle_behaviors/idle_monkey.dm index 66b630c35e12..c1c5f2d72b70 100644 --- a/code/datums/ai/idle_behaviors/idle_monkey.dm +++ b/code/datums/ai/idle_behaviors/idle_monkey.dm @@ -1,7 +1,7 @@ /datum/idle_behavior/idle_monkey/perform_idle_behavior(delta_time, datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn - if(DT_PROB(25, delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby) + if(DT_PROB(25, delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !LAZYLEN(living_pawn.grabbed_by)) var/move_dir = pick(GLOB.alldirs) living_pawn.Move(get_step(living_pawn, move_dir), move_dir) else if(DT_PROB(5, delta_time)) diff --git a/code/datums/ai/idle_behaviors/idle_random_walk.dm b/code/datums/ai/idle_behaviors/idle_random_walk.dm index 1187d8786c1d..8adb8b6e01a8 100644 --- a/code/datums/ai/idle_behaviors/idle_random_walk.dm +++ b/code/datums/ai/idle_behaviors/idle_random_walk.dm @@ -6,7 +6,7 @@ . = ..() var/mob/living/living_pawn = controller.pawn - if(DT_PROB(walk_chance, delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby) + if(DT_PROB(walk_chance, delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !LAZYLEN(living_pawn.grabbed_by)) var/move_dir = pick(GLOB.alldirs) living_pawn.Move(get_step(living_pawn, move_dir), move_dir) diff --git a/code/datums/ai/monkey/monkey_behaviors.dm b/code/datums/ai/monkey/monkey_behaviors.dm index 9132106dd88a..02910ee1779c 100644 --- a/code/datums/ai/monkey/monkey_behaviors.dm +++ b/code/datums/ai/monkey/monkey_behaviors.dm @@ -259,7 +259,13 @@ controller.current_movement_target = target - if(target.pulledby != living_pawn && !HAS_AI_CONTROLLER_TYPE(target.pulledby, /datum/ai_controller/monkey)) //Dont steal from my fellow monkeys. + var/monkey_is_grabbing_target = FALSE + for(var/obj/item/hand_item/grab/G as anything in target.grabbed_by) + if(!HAS_AI_CONTROLLER_TYPE(G.assailant, /datum/ai_controller/monkey)) + monkey_is_grabbing_target = TRUE + break + + if(!living_pawn.is_grabbing(target) && !monkey_is_grabbing_target) //Dont steal from my fellow monkeys. if(living_pawn.Adjacent(target) && isturf(target.loc)) target.grabbedby(living_pawn) return //Do the rest next turn diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm index 98566a77a36f..266b9d107ae9 100644 --- a/code/datums/ai/monkey/monkey_controller.dm +++ b/code/datums/ai/monkey/monkey_controller.dm @@ -54,7 +54,7 @@ have ways of interacting with a specific mob and control it. RegisterSignal(new_pawn, COMSIG_MOB_ATTACK_ALIEN, PROC_REF(on_attack_alien)) RegisterSignal(new_pawn, COMSIG_ATOM_BULLET_ACT, PROC_REF(on_bullet_act)) RegisterSignal(new_pawn, COMSIG_ATOM_HITBY, PROC_REF(on_hitby)) - RegisterSignal(new_pawn, COMSIG_LIVING_START_PULL, PROC_REF(on_startpulling)) + RegisterSignal(new_pawn, COMSIG_LIVING_START_GRAB, PROC_REF(on_startpulling)) RegisterSignal(new_pawn, COMSIG_LIVING_TRY_SYRINGE, PROC_REF(on_try_syringe)) RegisterSignal(new_pawn, COMSIG_ATOM_HULK_ATTACK, PROC_REF(on_attack_hulk)) RegisterSignal(new_pawn, COMSIG_CARBON_CUFF_ATTEMPTED, PROC_REF(on_attempt_cuff)) @@ -70,7 +70,7 @@ have ways of interacting with a specific mob and control it. COMSIG_ATOM_ATTACK_PAW, COMSIG_ATOM_BULLET_ACT, COMSIG_ATOM_HITBY, - COMSIG_LIVING_START_PULL, + COMSIG_LIVING_START_GRAB, COMSIG_LIVING_TRY_SYRINGE, COMSIG_ATOM_HULK_ATTACK, COMSIG_CARBON_CUFF_ATTEMPTED, @@ -189,11 +189,12 @@ have ways of interacting with a specific mob and control it. in_the_way_mob.knockOver(living_pawn) return -/datum/ai_controller/monkey/proc/on_startpulling(datum/source, atom/movable/puller, state, force) +/datum/ai_controller/monkey/proc/on_grabbed(datum/source, atom/movable/puller) SIGNAL_HANDLER + var/mob/living/living_pawn = pawn if(!IS_DEAD_OR_INCAP(living_pawn) && prob(MONKEY_PULL_AGGRO_PROB)) // nuh uh you don't pull me! - retaliate(living_pawn.pulledby) + retaliate(puller) return TRUE /datum/ai_controller/monkey/proc/on_try_syringe(datum/source, mob/user) diff --git a/code/datums/ai/movement/_ai_movement.dm b/code/datums/ai/movement/_ai_movement.dm index fa83d64e7986..c383b15769a7 100644 --- a/code/datums/ai/movement/_ai_movement.dm +++ b/code/datums/ai/movement/_ai_movement.dm @@ -29,7 +29,7 @@ source.delay = controller.movement_delay var/can_move = TRUE - if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && pawn.pulledby) //Need to store more state. Annoying. + if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && LAZYLEN(pawn.grabbed_by)) //Need to store more state. Annoying. can_move = FALSE if(!isturf(pawn.loc)) //No moving if not on a turf diff --git a/code/datums/ai/robot_customer/robot_customer_controller.dm b/code/datums/ai/robot_customer/robot_customer_controller.dm index 901c0f2f69c6..22fc2e267074 100644 --- a/code/datums/ai/robot_customer/robot_customer_controller.dm +++ b/code/datums/ai/robot_customer/robot_customer_controller.dm @@ -21,12 +21,12 @@ if(!istype(new_pawn, /mob/living/simple_animal/robot_customer)) return AI_CONTROLLER_INCOMPATIBLE RegisterSignal(new_pawn, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby)) - RegisterSignal(new_pawn, COMSIG_LIVING_GET_PULLED, PROC_REF(on_get_pulled)) + RegisterSignal(new_pawn, COMSIG_ATOM_GET_GRABBED, PROC_REF(on_get_pulled)) RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_get_punched)) return ..() //Run parent at end /datum/ai_controller/robot_customer/UnpossessPawn(destroy) - UnregisterSignal(pawn, list(COMSIG_PARENT_ATTACKBY, COMSIG_LIVING_GET_PULLED, COMSIG_ATOM_ATTACK_HAND)) + UnregisterSignal(pawn, list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_GET_GRABBED, COMSIG_ATOM_ATTACK_HAND)) return ..() //Run parent at end /datum/ai_controller/robot_customer/proc/on_attackby(datum/source, obj/item/I, mob/living/user) diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm index 52c418bab37c..e7f3e112663c 100644 --- a/code/datums/brain_damage/special.dm +++ b/code/datums/brain_damage/special.dm @@ -170,18 +170,17 @@ /datum/brain_trauma/special/quantum_alignment/proc/try_entangle() //Check for pulled mobs - if(ismob(owner.pulling)) - entangle(owner.pulling) + var/list/grabs = owner.get_active_grabs() + if(length(grabs)) + for(var/obj/item/hand_item/grab/G in grabs) + entangle(G.affecting) return + //Check for adjacent mobs for(var/mob/living/L in oview(1, owner)) if(owner.Adjacent(L)) entangle(L) return - //Check for pulled objects - if(isobj(owner.pulling)) - entangle(owner.pulling) - return //Check main hand var/obj/item/held_item = owner.get_active_held_item() diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm index 7f93831b78a2..bcbbf3eacda7 100644 --- a/code/datums/components/butchering.dm +++ b/code/datums/components/butchering.dm @@ -29,26 +29,6 @@ can_be_blunt = _can_be_blunt if(_butcher_callback) butcher_callback = _butcher_callback - if(isitem(parent)) - RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(onItemAttack)) - -/datum/component/butchering/proc/onItemAttack(obj/item/source, mob/living/M, mob/living/user) - SIGNAL_HANDLER - - if(M.stat == DEAD && (M.butcher_results || M.guaranteed_butcher_results)) //can we butcher it? - if(butchering_enabled && (can_be_blunt || (source.sharpness & SHARP_EDGED))) - INVOKE_ASYNC(src, PROC_REF(startButcher), source, M, user) - return COMPONENT_CANCEL_ATTACK_CHAIN - - if(ishuman(M) && source.force && (source.sharpness & SHARP_EDGED)) - var/mob/living/carbon/human/H = M - if((user.pulling == H && user.grab_state >= GRAB_AGGRESSIVE) && user.zone_selected == BODY_ZONE_HEAD) // Only aggressive grabbed can be sliced. - if(H.has_status_effect(/datum/status_effect/neck_slice)) - user.show_message(span_warning("[H]'s neck has already been already cut, you can't make the bleeding any worse!"), MSG_VISUAL, \ - span_warning("Their neck has already been already cut, you can't make the bleeding any worse!")) - return COMPONENT_CANCEL_ATTACK_CHAIN - INVOKE_ASYNC(src, PROC_REF(startNeckSlice), source, H, user) - return COMPONENT_CANCEL_ATTACK_CHAIN /datum/component/butchering/proc/startButcher(obj/item/source, mob/living/M, mob/living/user) to_chat(user, span_notice("You begin to butcher [M]...")) @@ -56,33 +36,6 @@ if(do_after(user, M, speed, DO_PUBLIC, display = parent) && M.Adjacent(source)) Butcher(user, M) -/datum/component/butchering/proc/startNeckSlice(obj/item/source, mob/living/carbon/human/H, mob/living/user) - if(DOING_INTERACTION_WITH_TARGET(user, H)) - to_chat(user, span_warning("You're already interacting with [H]!")) - return - - user.visible_message(span_danger("[user] is slitting [H]'s throat!"), \ - span_danger("You start slicing [H]'s throat!"), \ - span_hear("You hear a cutting noise!"), ignored_mobs = H) - H.show_message(span_userdanger("Your throat is being slit by [user]!"), MSG_VISUAL, \ - span_userdanger("Something is cutting into your neck!"), NONE) - log_combat(user, H, "attempted throat slitting", source) - - playsound(H.loc, butcher_sound, 50, TRUE, -1) - if(do_after(user, H, clamp(500 / source.force, 30, 100), DO_PUBLIC, display = parent) && H.Adjacent(source)) - if(H.has_status_effect(/datum/status_effect/neck_slice)) - user.show_message(span_warning("[H]'s neck has already been already cut, you can't make the bleeding any worse!"), MSG_VISUAL, \ - span_warning("Their neck has already been already cut, you can't make the bleeding any worse!")) - return - - H.visible_message(span_danger("[user] slits [H]'s throat!"), \ - span_userdanger("[user] slits your throat...")) - log_combat(user, H, "wounded via throat slitting", source) - var/obj/item/bodypart/slit_throat = H.get_bodypart(BODY_ZONE_HEAD) - - slit_throat.create_wound_easy(/datum/wound/cut/flesh, 30) - H.apply_status_effect(/datum/status_effect/neck_slice) - /** * Handles a user butchering a target * diff --git a/code/datums/components/drift.dm b/code/datums/components/drift.dm index 58bcff8acdf6..a2ae04783089 100644 --- a/code/datums/components/drift.dm +++ b/code/datums/components/drift.dm @@ -92,14 +92,14 @@ // This way you can't ride two movements at once while drifting, since that'd be dumb as fuck RegisterSignal(movable_parent, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(handle_glidesize_update)) // If you stop pulling something mid drift, I want it to retain that momentum - RegisterSignal(movable_parent, COMSIG_ATOM_NO_LONGER_PULLING, PROC_REF(stopped_pulling)) + RegisterSignal(movable_parent, COMSIG_ATOM_NO_LONGER_GRABBING, PROC_REF(stopped_pulling)) /datum/component/drift/proc/drifting_stop() SIGNAL_HANDLER var/atom/movable/movable_parent = parent movable_parent.inertia_moving = FALSE ignore_next_glide = FALSE - UnregisterSignal(movable_parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, COMSIG_ATOM_NO_LONGER_PULLING)) + UnregisterSignal(movable_parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, COMSIG_ATOM_NO_LONGER_GRABBING)) /datum/component/drift/proc/before_move(datum/source) SIGNAL_HANDLER diff --git a/code/datums/components/jetpack.dm b/code/datums/components/jetpack.dm index 73a26b986fd3..1f9f2dda3820 100644 --- a/code/datums/components/jetpack.dm +++ b/code/datums/components/jetpack.dm @@ -109,7 +109,7 @@ return if(!(user.movement_type & FLOATING) || user.buckled)//You don't want use jet in gravity or while buckled. return - if(user.pulledby)//You don't must use jet if someone pull you + if(LAZYLEN(user.grabbed_by))//You don't must use jet if someone pull you return if(user.throwing)//You don't must use jet if you thrown return diff --git a/code/datums/components/riding/riding_mob.dm b/code/datums/components/riding/riding_mob.dm index f6945d215cc1..3a112c079bf3 100644 --- a/code/datums/components/riding/riding_mob.dm +++ b/code/datums/components/riding/riding_mob.dm @@ -13,7 +13,7 @@ . = ..() var/mob/living/living_parent = parent - living_parent.stop_pulling() // was only used on humans previously, may change some other behavior + living_parent.release_all_grabs() // was only used on humans previously, may change some other behavior log_riding(living_parent, riding_mob) riding_mob.set_glide_size(living_parent.glide_size) handle_vehicle_offsets(living_parent.dir) diff --git a/code/datums/components/rot.dm b/code/datums/components/rot.dm index 9e1b186dc8e7..8ec182019770 100644 --- a/code/datums/components/rot.dm +++ b/code/datums/components/rot.dm @@ -43,7 +43,7 @@ RegisterSignal(parent, COMSIG_MOVABLE_BUMP, PROC_REF(rot_react)) if(isliving(parent)) RegisterSignal(parent, COMSIG_LIVING_REVIVE, PROC_REF(react_to_revive)) //mobs stop this when they come to life - RegisterSignal(parent, COMSIG_LIVING_GET_PULLED, PROC_REF(rot_react_touch)) + RegisterSignal(parent, COMSIG_ATOM_GET_GRABBED, PROC_REF(rot_react_touch)) if(iscarbon(parent)) var/mob/living/carbon/carbon_parent = parent RegisterSignal(carbon_parent.reagents, list(COMSIG_REAGENTS_ADD_REAGENT, diff --git a/code/datums/components/shy.dm b/code/datums/components/shy.dm index 433b723e9923..b8f7b5896b01 100644 --- a/code/datums/components/shy.dm +++ b/code/datums/components/shy.dm @@ -45,7 +45,7 @@ /datum/component/shy/RegisterWithParent() RegisterSignal(parent, COMSIG_MOB_CLICKON, PROC_REF(on_clickon)) - RegisterSignal(parent, COMSIG_LIVING_TRY_PULL, PROC_REF(on_try_pull)) + RegisterSignal(parent, COMSIG_LIVING_TRY_GRAB, PROC_REF(on_try_pull)) RegisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK), PROC_REF(on_unarmed_attack)) RegisterSignal(parent, COMSIG_TRY_STRIP, PROC_REF(on_try_strip)) RegisterSignal(parent, COMSIG_TRY_ALT_ACTION, PROC_REF(on_try_alt_action)) diff --git a/code/datums/components/strong_pull.dm b/code/datums/components/strong_pull.dm index f0cb206a650d..353d6e361ede 100644 --- a/code/datums/components/strong_pull.dm +++ b/code/datums/components/strong_pull.dm @@ -17,7 +17,7 @@ Basically, the items they pull cannot be pulled (except by the puller) /datum/component/strong_pull/RegisterWithParent() . = ..() - RegisterSignal(parent, COMSIG_LIVING_START_PULL, PROC_REF(on_pull)) + RegisterSignal(parent, COMSIG_ATOM_START_GRAB, PROC_REF(on_pull)) /** * Called when the parent grabs something, adds signals to the object to reject interactions diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm index 68da2882c5d9..ad042989ef58 100644 --- a/code/datums/components/tackle.dm +++ b/code/datums/components/tackle.dm @@ -74,7 +74,7 @@ if(modifiers[ALT_CLICK] || modifiers[SHIFT_CLICK] || modifiers[CTRL_CLICK] || modifiers[MIDDLE_CLICK]) return - if(!user.throw_mode || user.get_active_held_item() || user.pulling || user.buckled || user.incapacitated()) + if(!user.throw_mode || user.get_active_held_item() || user.get_active_grabs() || user.buckled || user.incapacitated()) return if(!A || !(isturf(A) || isturf(A.loc))) diff --git a/code/datums/elements/atmos_requirements.dm b/code/datums/elements/atmos_requirements.dm index ff2ede8401b2..4f074dc2ec6e 100644 --- a/code/datums/elements/atmos_requirements.dm +++ b/code/datums/elements/atmos_requirements.dm @@ -36,9 +36,10 @@ target.throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy) /datum/element/atmos_requirements/proc/is_breathable_atmos(mob/living/target) - if(target.pulledby && target.pulledby.grab_state >= GRAB_KILL && atmos_requirements["min_oxy"]) + if(target.check_grab_severity(GRAB_KILL)) && atmos_requirements["min_oxy"]) return FALSE + if(!isopenturf(target.loc)) return TRUE diff --git a/code/datums/keybinding/mob.dm b/code/datums/keybinding/mob.dm index e34142ffd72f..e3bbdc8e4f2d 100644 --- a/code/datums/keybinding/mob.dm +++ b/code/datums/keybinding/mob.dm @@ -12,11 +12,13 @@ . = ..() if(.) return - var/mob/M = user.mob - if(!M.pulling) - to_chat(user, span_notice("You are not pulling anything.")) + var/mob/living/M = user.mob + if(!istype(M)) + return + if(!LAZYLEN(M.get_active_grabs())) + to_chat(user, span_notice("You are not grabbing anything.")) else - M.stop_pulling() + M.release_all_grabs() return TRUE /datum/keybinding/mob/swap_hands diff --git a/code/datums/martial/cqc.dm b/code/datums/martial/cqc.dm index 262d299c4578..ff01d6c4a8f7 100644 --- a/code/datums/martial/cqc.dm +++ b/code/datums/martial/cqc.dm @@ -191,7 +191,8 @@ to_chat(A, span_warning("You fail to disarm [D]!")) playsound(D, 'sound/weapons/punchmiss.ogg', 25, TRUE, -1) log_combat(A, D, "disarmed (CQC)", "[I ? " grabbing \the [I]" : ""]") - if(restraining && A.pulling == D) + + if(restraining && A.is_grabbing(D)) log_combat(A, D, "knocked out (Chokehold)(CQC)") D.visible_message(span_danger("[A] puts [D] into a chokehold!"), \ span_userdanger("You're put into a chokehold by [A]!"), span_hear("You hear shuffling and a muffled groan!"), null, A) diff --git a/code/datums/martial/wrestling.dm b/code/datums/martial/wrestling.dm index 0ca18ae84110..26f649ed2544 100644 --- a/code/datums/martial/wrestling.dm +++ b/code/datums/martial/wrestling.dm @@ -130,7 +130,7 @@ If you make a derivative work from this code, you must include this notification /datum/martial_art/wrestling/proc/throw_wrassle(mob/living/A, mob/living/D) if(!D) return - if(!A.pulling || A.pulling != D) + if(!A.is_grabbing(D)) to_chat(A, span_warning("You need to have [D] in a cinch!")) return D.forceMove(A.loc) @@ -214,7 +214,7 @@ If you make a derivative work from this code, you must include this notification /datum/martial_art/wrestling/proc/slam(mob/living/A, mob/living/D) if(!D) return - if(!A.pulling || A.pulling != D) + if(!A.is_grabbing(D)) to_chat(A, span_warning("You need to have [D] in a cinch!")) return D.forceMove(A.loc) @@ -452,9 +452,10 @@ If you make a derivative work from this code, you must include this notification /datum/martial_art/wrestling/grab_act(mob/living/A, mob/living/D) if(check_streak(A,D)) return 1 - if(A.pulling == D) + if(A.is_grabbing(D)) + return 1 + if(!A.try_make_grab(D)) return 1 - A.start_pulling(D) D.visible_message(span_danger("[A] gets [D] in a cinch!"), \ span_userdanger("You're put into a cinch by [A]!"), span_hear("You hear aggressive shuffling!"), COMBAT_MESSAGE_RANGE, A) to_chat(A, span_danger("You get [D] in a cinch!")) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index c458e9519776..497fb540aa2b 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -13,7 +13,6 @@ var/throw_range = 7 ///Max range this atom can be thrown via telekinesis var/tk_throw_range = 10 - var/mob/pulledby = null var/initial_language_holder = /datum/language_holder var/datum/language_holder/language_holder // Mindless mobs and objects need language too, some times. Mind holder takes prescedence. var/verb_say = "says" @@ -64,7 +63,6 @@ var/movement_type = GROUND var/atom/movable/pulling - var/grab_state = 0 var/throwforce = 0 var/datum/component/orbiter/orbiting @@ -170,11 +168,6 @@ invisibility = INVISIBILITY_ABSTRACT - if(pulledby) - pulledby.stop_pulling() - if(pulling) - stop_pulling() - if(orbiting) orbiting.end_orbit(src) orbiting = null @@ -566,9 +559,6 @@ if(NAMEOF(src, anchored)) set_anchored(var_value) . = TRUE - if(NAMEOF(src, pulledby)) - set_pulledby(var_value) - . = TRUE if(NAMEOF(src, glide_size)) set_glide_size(var_value) . = TRUE @@ -637,12 +627,12 @@ if(!pulling) return FALSE if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src, src, pulling)) - stop_pulling() + release_grab(moving_atom) return FALSE if(isliving(pulling)) var/mob/living/pulling_mob = pulling if(pulling_mob.buckled && pulling_mob.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it - stop_pulling() + release_grab(moving_atom) return FALSE if(moving_atom == loc && pulling.density) return FALSE @@ -664,6 +654,7 @@ * If z_allowed is TRUE, the z level of the pulling will be ignored.This is to allow things to be dragged up and down stairs. */ /atom/movable/proc/check_pulling(only_pulling = FALSE, z_allowed = FALSE) + #warn this probably needs a fat rewrite to use grab datums if(pulling) if(get_dist(src, pulling) > 1 || (z != pulling.z && !z_allowed)) stop_pulling() @@ -857,7 +848,8 @@ set_currently_z_moving(FALSE, TRUE) return - if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move. + #warn FUUUUCK + if(. && LAZYLEN && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move. if(pulling.anchored) stop_pulling() else @@ -1376,9 +1368,6 @@ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, args) & COMPONENT_CANCEL_THROW) return - if (pulledby) - pulledby.stop_pulling() - //They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw? if (thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag*2) var/user_momentum = thrower.cached_multiplicative_slowdown @@ -1435,8 +1424,9 @@ thrown_thing.diagonal_error = dist_x/2 - dist_y thrown_thing.start_time = world.time - if(pulledby) - pulledby.stop_pulling() + if(LAZYLEN(grabbed_by)) + free_from_all_grabs() + if (quickstart && (throwing || SSthrowing.state == SS_RUNNING)) //Avoid stack overflow edgecases. quickstart = FALSE throwing = thrown_thing @@ -1641,44 +1631,6 @@ /atom/movable/proc/get_cell() return -/atom/movable/proc/can_be_pulled(user, grab_state, force) - if(src == user || !isturf(loc)) - return FALSE - if(SEND_SIGNAL(src, COMSIG_ATOM_CAN_BE_PULLED, user) & COMSIG_ATOM_CANT_PULL) - return FALSE - if(anchored || throwing) - return FALSE - if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) - return FALSE - return TRUE - -/** - * Updates the grab state of the movable - * - * This exists to act as a hook for behaviour - */ -/atom/movable/proc/setGrabState(newstate) - if(newstate == grab_state) - return - SEND_SIGNAL(src, COMSIG_MOVABLE_SET_GRAB_STATE, newstate) - . = grab_state - grab_state = newstate - switch(grab_state) // Current state. - if(GRAB_PASSIVE) - REMOVE_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) - REMOVE_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT) - if(. >= GRAB_NECK) // Previous state was a a neck-grab or higher. - REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) - if(GRAB_AGGRESSIVE) - if(. >= GRAB_NECK) // Grab got downgraded. - REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) - else // Grab got upgraded from a passive one. - ADD_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) - ADD_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT) - if(GRAB_NECK, GRAB_KILL) - if(. <= GRAB_AGGRESSIVE) - ADD_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) - /** * Adds the deadchat_plays component to this atom with simple movement commands. * diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index 6ff1fd03d62d..5867f95043ab 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -369,22 +369,29 @@ GLOBAL_LIST_INIT(dye_registry, list( to_chat(user, span_warning("[src] is busy!")) return - if(user.pulling && isliving(user.pulling)) - var/mob/living/L = user.pulling - if(L.buckled || L.has_buckled_mobs()) - return - if(state_open) - if(istype(L, /mob/living/simple_animal/pet)) - L.forceMove(src) - update_appearance() - return - if(!state_open) open_machine() else state_open = FALSE //close the door update_appearance() +/obj/machinery/washing_machine/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(busy) + to_chat(user, span_warning("[src] is busy!")) + return + + if(!isliving(victim)) + return + + var/mob/living/L = victim + if(L.buckled || L.has_buckled_mobs()) + return + if(state_open) + if(istype(L, /mob/living/simple_animal/pet)) + L.forceMove(src) + update_appearance() + /obj/machinery/washing_machine/attack_hand_secondary(mob/user, modifiers) . = ..() if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm index 7bf7b875e938..4039e98965a3 100644 --- a/code/game/objects/buckling.dm +++ b/code/game/objects/buckling.dm @@ -102,12 +102,15 @@ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PREBUCKLE, M, force, buckle_mob_flags) & COMPONENT_BLOCK_BUCKLE) return FALSE - if(M.pulledby) + if(LAZYLEN(M.grabbed_by)) if(buckle_prevents_pull) - M.pulledby.stop_pulling() + M.free_from_all_grabs() + #warn pull offsets + /* else if(isliving(M.pulledby)) var/mob/living/L = M.pulledby L.reset_pull_offsets(M, TRUE) + */ if(anchored) ADD_TRAIT(M, TRAIT_NO_FLOATING_ANIM, BUCKLED_TRAIT) diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index f24aa5103144..6ff9e40d25d0 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -123,7 +123,8 @@ to_chat(user, span_warning("You can't bring yourself to noogie [target]! You don't want to risk harming anyone...")) return - if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || user.pulling != target || user.grab_state < GRAB_AGGRESSIVE || HAS_TRAIT(user, TRAIT_EXHAUSTED)) + var/obj/item/hand_item/grab/G = user.is_grabbing(target) + if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || !G || !(G.damage_stage >= GRAB_AGGRESSIVE)|| HAS_TRAIT(user, TRAIT_EXHAUSTED)) return FALSE // [user] gives [target] a [prefix_desc] noogie[affix_desc]! @@ -153,7 +154,7 @@ /// The actual meat and bones of the noogie'ing /obj/item/hand_item/noogie/proc/noogie_loop(mob/living/carbon/human/user, mob/living/carbon/target, iteration) - if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || user.pulling != target) + if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || !user.is_grabbing(target)) return FALSE if(HAS_TRAIT(user, TRAIT_EXHAUSTED)) diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm index bd8330ea4744..ac13babf7b6f 100644 --- a/code/game/objects/items/storage/bags.dm +++ b/code/game/objects/items/storage/bags.dm @@ -141,8 +141,9 @@ if(!isturf(tile)) return - if(istype(user.pulling, /obj/structure/ore_box)) - box = user.pulling + var/obj/item/hand_item/grab/G = user.get_active_held_item() + if(isgrab(G) && istype(G.affecting, /obj/structure/ore_box)) + box = G.affecting if(atom_storage) for(var/thing in tile) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index e837fe600101..2edbe79f700f 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -309,7 +309,7 @@ for(var/mob/living/M in contents) if(++mobs_stored >= mob_storage_capacity) return FALSE - L.stop_pulling() + L.release_all_grabs() else if(istype(AM, /obj/structure/closet)) return FALSE diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm index ab15cefc3351..d190e4c0fb7b 100644 --- a/code/game/turfs/open/_open.dm +++ b/code/game/turfs/open/_open.dm @@ -207,7 +207,7 @@ else slipper.Knockdown(knockdown_amount) slipper.Paralyze(paralyze_amount) - slipper.stop_pulling() + slipper.release_all_grabs() if(buckled_obj) buckled_obj.unbuckle_mob(slipper) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 50207ec1bc75..3ac29d7cbd12 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -305,7 +305,7 @@ GLOBAL_LIST_EMPTY(station_turfs) for(var/atom/movable/falling_mob as anything in falling_movables) if(!(flags & FALL_RETAIN_PULL)) - falling_mob.stop_pulling() + falling_mob.release_all_grabs() if(!(flags & FALL_INTERCEPTED)) falling_mob.onZImpact(src, levels) @@ -315,6 +315,7 @@ GLOBAL_LIST_EMPTY(station_turfs) prev_turf.audible_message(span_hear("You hear something slam into the deck below.")) #endif + #warn FUUUUUCk if(falling_mob.pulledby && (falling_mob.z != falling_mob.pulledby.z || get_dist(falling_mob, falling_mob.pulledby) > 1)) falling_mob.pulledby.stop_pulling() return TRUE diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm index 8ce7d4eb0aa9..2846e693ca99 100644 --- a/code/modules/antagonists/changeling/powers/absorb.dm +++ b/code/modules/antagonists/changeling/powers/absorb.dm @@ -16,10 +16,11 @@ to_chat(owner, span_warning("We are already absorbing!")) return - if(!owner.pulling || !iscarbon(owner.pulling)) + var/obj/item/hand_item/grab/G = owner.get_active_grabs()?[1] + if(!G) to_chat(owner, span_warning("We must be grabbing a creature to absorb them!")) return - if(owner.grab_state <= GRAB_NECK) + if(!G.can_absorb) to_chat(owner, span_warning("We must have a tighter grip to absorb this creature!")) return diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index d4b71b770bce..5fe52da90f39 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -607,8 +607,9 @@ Striking a noncultist, however, will tear their flesh."} /obj/item/cult_shift/proc/handle_teleport_grab(turf/T, mob/user) var/mob/living/carbon/C = user - if(C.pulling) - var/atom/movable/pulled = C.pulling + var/list/obj/item/hand_item/grab/grabs = C.get_active_grabs() + if(length(grabs)) + var/atom/movable/pulled = grabs[1].affecting do_teleport(pulled, T, channel = TELEPORT_CHANNEL_CULT) . = pulled @@ -636,7 +637,7 @@ Striking a noncultist, however, will tear their flesh."} var/atom/movable/pulled = handle_teleport_grab(destination, C) if(do_teleport(C, destination, channel = TELEPORT_CHANNEL_CULT)) if(pulled) - C.start_pulling(pulled) //forcemove resets pulls, so we need to re-pull + C.try_make_grab(pulled) //forcemove resets pulls, so we need to re-pull new /obj/effect/temp_visual/dir_setting/cult/phase(destination, C.dir) playsound(destination, 'sound/effects/phasein.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) playsound(destination, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) diff --git a/code/modules/awaymissions/signpost.dm b/code/modules/awaymissions/signpost.dm index ea0d22ac39f6..8acdc5076472 100644 --- a/code/modules/awaymissions/signpost.dm +++ b/code/modules/awaymissions/signpost.dm @@ -20,12 +20,13 @@ var/turf/T = find_safe_turf(zlevels=zlevels) if(T) - var/atom/movable/AM = user.pulling - if(AM) + var/list/grabbing= user.get_all_grabbed_movables() + for(var/atom/movable/AM as anything in grabbing) AM.forceMove(T) user.forceMove(T) if(AM) - user.start_pulling(AM) + user.try_make_grab(AM) + to_chat(user, span_notice("You blink and find yourself in [get_area_name(T)].")) else to_chat(user, "Nothing happens. You feel that this is a bad sign.") diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 7d337767d90c..0404781ad769 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -369,7 +369,7 @@ var/mob/living/mob_to_insert = to_insert if(mob_to_insert.anchored || mob_to_insert.incorporeal_move) return FALSE - mob_to_insert.stop_pulling() + mob_to_insert.release_all_grabs() else if(isobj(to_insert)) var/obj/obj_to_insert = to_insert diff --git a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm index d83125e7e4cc..e73f1cc40fb6 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm @@ -129,31 +129,42 @@ GLOBAL_LIST_INIT(oilfry_blacklisted_items, typecacheof(list( frying_burnt = FALSE fry_loop.stop() return - else if(user.pulling && iscarbon(user.pulling) && reagents.total_volume) - if(user.grab_state < GRAB_AGGRESSIVE) - to_chat(user, span_warning("You need a better grip to do that!")) - return - var/mob/living/carbon/dunking_target = user.pulling - log_combat(user, dunking_target, "dunked", null, "into [src]") - user.visible_message(span_danger("[user] dunks [dunking_target]'s face in [src]!")) - reagents.expose(dunking_target, TOUCH) - var/permeability = 1 - dunking_target.get_permeability_protection(list(HEAD)) - var/target_temp = dunking_target.bodytemperature - var/cold_multiplier = 1 - if(target_temp < TCMB + 10) // a tiny bit of leeway - dunking_target.visible_message(span_userdanger("[dunking_target] explodes from the entropic difference! Holy fuck!")) - dunking_target.gib() - log_combat(user, dunking_target, "blew up", null, "by dunking them into [src]") - return - else if(target_temp < T0C) - cold_multiplier += round(target_temp * 1.5 / T0C, 0.01) - dunking_target.apply_damage(min(30 * permeability * cold_multiplier, reagents.total_volume), BURN, BODY_ZONE_HEAD) - if(reagents.reagent_list) //This can runtime if reagents has nothing in it. - reagents.remove_any((reagents.total_volume/2)) - dunking_target.Paralyze(60) - user.changeNext_move(CLICK_CD_MELEE) return ..() +/obj/machinery/deepfryer/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!iscarbon(victim) || !reagents.total_volume) + return + + if(grab.current_grab.damage_stage < GRAB_AGGRESSIVE) + to_chat(user, span_warning("You need a better grip to do that!")) + return + + var/mob/living/carbon/dunking_target = victim + + log_combat(user, dunking_target, "dunked", null, "into [src]") + user.visible_message(span_danger("[user] dunks [dunking_target]'s face in [src]!")) + reagents.expose(dunking_target, TOUCH) + + var/permeability = 1 - dunking_target.get_permeability_protection(list(HEAD)) + var/target_temp = dunking_target.bodytemperature + var/cold_multiplier = 1 + if(target_temp < TCMB + 10) // a tiny bit of leeway + dunking_target.visible_message(span_userdanger("[dunking_target] explodes from the entropic difference! Holy fuck!")) + dunking_target.gib() + log_combat(user, dunking_target, "blew up", null, "by dunking them into [src]") + return + + else if(target_temp < T0C) + cold_multiplier += round(target_temp * 1.5 / T0C, 0.01) + + dunking_target.apply_damage(min(30 * permeability * cold_multiplier, reagents.total_volume), BURN, BODY_ZONE_HEAD) + if(reagents.reagent_list) //This can runtime if reagents has nothing in it. + reagents.remove_any((reagents.total_volume/2)) + + dunking_target.Paralyze(60) + user.changeNext_move(CLICK_CD_MELEE) + /obj/machinery/deepfryer/proc/fry(obj/item/frying_item, mob/user) to_chat(user, span_notice("You put [frying_item] into [src].")) if(istype(frying_item, /obj/item/freeze_cube)) diff --git a/code/modules/food_and_drinks/kitchen_machinery/processor.dm b/code/modules/food_and_drinks/kitchen_machinery/processor.dm index 616d4700adfa..0606ab7d7b86 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/processor.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/processor.dm @@ -126,8 +126,9 @@ visible_message(span_warning("[user] stuffs [pushed_mob] into [src]!")) pushed_mob.forceMove(src) LAZYADD(processor_contents, pushed_mob) - user.stop_pulling() + user.release_all_grabs() return + if(!LAZYLEN(processor_contents)) to_chat(user, span_warning("[src] is empty!")) return TRUE diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index 76af09fdf940..96ca9ae35446 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -29,7 +29,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) /// How much the grab increases point blank damage. var/point_blank_mult = 1 /// Affects how much damage is being dealt using certain actions. - var/damage_stage = 1 + var/damage_stage = GRAB_PASSIVE /// If the grabbed person and the grabbing person are on the same tile. var/same_tile = FALSE /// If the grabber can carry the grabbed person up or down ladders. @@ -65,10 +65,10 @@ GLOBAL_LIST_EMPTY(all_grabstates) var/can_grab_self = TRUE // The names of different intents for use in attack logs - var/help_action = "help intent" - var/disarm_action = "disarm intent" - var/grab_action = "grab intent" - var/harm_action = "harm intent" + var/help_action = "" + var/disarm_action = "" + var/grab_action = "" + var/harm_action = "" /* These procs shouldn't be overriden in the children unless you know what you're doing with them; they handle important core functions. @@ -121,7 +121,8 @@ GLOBAL_LIST_EMPTY(all_grabstates) /datum/grab/proc/let_go(obj/item/hand_item/grab/G) if (G) let_go_effect(G) - qdel(G) + if(!QDELETED(G)) + qdel(G) /datum/grab/proc/on_target_change(obj/item/hand_item/grab/G, old_zone, new_zone) G.special_target_functional = check_special_target(G) @@ -245,22 +246,58 @@ GLOBAL_LIST_EMPTY(all_grabstates) */ // What happens when you upgrade from one grab state to the next. -/datum/grab/proc/upgrade_effect(obj/item/hand_item/grab/G) +/datum/grab/proc/upgrade_effect(obj/item/hand_item/grab/G, datum/grab/old_grab) + update_grab_effects(old_grab) // Conditions to see if upgrading is possible /datum/grab/proc/can_upgrade(obj/item/hand_item/grab/G) - return 1 + if(!upgrab) + return FALSE + + if(!(G.affecting.status_flags & CANPUSH) || HAS_TRAIT(G.affecting, TRAIT_PUSHIMMUNE)) + to_chat(user, span_warning("[src] can't be grabbed more aggressively!")) + return FALSE + + if(upgrab.damage_stage >= GRAB_AGGRESSIVE && HAS_TRAIT(G.assailant, TRAIT_PACIFISM)) + to_chat(user, span_warning("You don't want to risk hurting [src]!")) + return FALSE + return TRUE // What happens when you downgrade from one grab state to the next. -/datum/grab/proc/downgrade_effect(obj/item/hand_item/grab/G) +/datum/grab/proc/downgrade_effect(obj/item/hand_item/grab/G, datum/grab/old_grab) + update_grab_effects(old_grab) // Conditions to see if downgrading is possible /datum/grab/proc/can_downgrade(obj/item/hand_item/grab/G) - return 1 + return TRUE // What happens when you let go of someone by either dropping the grab // or by downgrading from the lowest grab state. /datum/grab/proc/let_go_effect(obj/item/hand_item/grab/G) + SEND_SIGNAL(G.affecting, COMSIG_ATOM_NO_LONGER_GRABBED, G.assailant) + SEND_SIGNAL(G.assailant, COMSIG_ATOM_NO_LONGER_GRABBING, G.affecting) + update_grab_effects(null) + +/datum/grab/proc/update_grab_effects(datum/grab/old_grab) + var/old_damage_stage = old_grab?.damage_stage || 0 + + switch(damage_stage) // Current state. + if(GRAB_PASSIVE) + REMOVE_TRAIT(G.affecting, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) + REMOVE_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT) + if(old_damage_stage >= GRAB_NECK) // Previous state was a a neck-grab or higher. + REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + + if(GRAB_AGGRESSIVE) + if(old_damage_stage >= GRAB_NECK) // Grab got downgraded. + REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + else // Grab got upgraded from a passive one. + ADD_TRAIT(G.affecting, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) + ADD_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT) + + if(GRAB_NECK, GRAB_KILL) + if(old_damage_stage <= GRAB_AGGRESSIVE) + ADD_TRAIT(G.affecting, TRAIT_FLOORED, CHOKEHOLD_TRAIT) // What happens each tic when process is called. /datum/grab/proc/process_effect(obj/item/hand_item/grab/G) @@ -369,3 +406,18 @@ GLOBAL_LIST_EMPTY(all_grabstates) /datum/grab/proc/moved_effect(obj/item/hand_item/grab/G) return + +/// Add screentip context, user will always be assailant. +/datum/grab/proc/add_context(list/context, mob/living/user) + if(disarm_action) + context[SCREENTIP_CONTEXT_RMB] = capitalize(disarm_action) + + if(grab_action) + context[SCREENTIP_CONTEXT_CTRL_LMB] = capitalize(grab_action) + + if(user.combat_mode) + if(harm_action) + context[SCREENTIP_CONTEXT_RMB] = capitalize(harm_action) + + else if(help_action) + context[SCREENTIP_CONTEXT_LMB] = capitalize(help_action) diff --git a/code/modules/grab/grab_helpers.dm b/code/modules/grab/grab_helpers.dm new file mode 100644 index 000000000000..890a4f396758 --- /dev/null +++ b/code/modules/grab/grab_helpers.dm @@ -0,0 +1,40 @@ +/mob/living/proc/get_active_grabs() + . = list() + for(var/obj/item/hand_item/grab/G in held_items) + . += G + +/// Returns TRUE if src is grabbing hold of the target +/mob/living/proc/is_grabbing(atom/movable/AM) + RETURN_TYPE(/obj/item/hand_item/grab) + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + if(G.affecting == AM) + return G + +/// Release all grabs we have +/mob/living/proc/release_all_grabs() + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + qdel(G) + +/// Release the given movable from a grab +/mob/living/proc/release_grab(atom/movable/AM) + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + if(G.affecting == AM) + qdel(G) + +/// Returns a list of every movable we are grabbing +/mob/living/proc/get_all_grabbed_movables() + . = list() + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + . |= G.affecting + +/// Checks to see if we have a grab with atleast the given damage_stage +/atom/movable/proc/check_grab_severities(stage) + for(var/obj/item/hand_item/grab/G in grabbed_by) + if(G.current_grab.damage_stage >= stage) + return G + +/// Frees src from all grabs. +/atom/movable/proc/free_from_all_grabs() + for(var/obj/item/hand_item/grab/G in grabbed_by) + qdel(G) + diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm new file mode 100644 index 000000000000..31dad89f7b97 --- /dev/null +++ b/code/modules/grab/grab_living.dm @@ -0,0 +1,77 @@ +/mob/living/proc/can_grab(atom/movable/target, vtarget_zone) + if(!ismob(target) && target.anchored) + to_chat(src, span_warning("\The [target] won't budge!")) + return FALSE + + if(defer_hand) + if(!get_empty_held_index()) + to_chat(src, SPAN_WARNING("Your hands are full!")) + return FALSE + else if(get_active_hand()) + to_chat(src, span_warning("Your [get_active_hand().plaintext_zone] is full!")) + return FALSE + + if(LAZYLEN(grabbed_by)) + to_chat(src, span_warning("You cannot start grappling while already being grappled!")) + return FALSE + + for(var/obj/item/hand_item/grab/G in target.grabbed_by) + if(G.assailant != src) + continue + if(!target_zone || !ismob(target)) + to_chat(src, span_warning("You already have a grip on \the [target]!")) + return FALSE + if(G.target_zone == target_zone) + var/obj/item/bodypart/BP = G.get_targeted_bodypart() + if(BP) + to_chat(src, span_warning("You already have a grip on \the [target]'s [BP.plaintext_zone].")) + return FALSE + return TRUE + +/mob/living/proc/make_grab(atom/movable/target, grab_type = /datum/grab/simple) + if(SEND_SIGNAL(src, COMSIG_LIVING_TRY_GRAB, target, grab_type) & COMSIG_LIVING_CANCEL_GRAB) + return + + // Resolve to the 'topmost' atom in the buckle chain, as grabbing someone buckled to something tends to prevent further interaction. + var/atom/movable/original_target = target + var/mob/grabbing_mob = (ismob(target) && target) + + while(istype(grabbing_mob) && grabbing_mob.buckled) + grabbing_mob = grabbing_mob.buckled + + if(grabbing_mob && grabbing_mob != original_target) + target = grabbing_mob + to_chat(src, span_warning("As \the [original_target] is buckled to \the [target], you try to grab that instead!")) + + if(!istype(target)) + return + + face_atom(target) + + var/obj/item/hand_item/grab/grab + if(ispath(grab_type, /datum/grab) && can_grab(target, get_target_zone(), defer_hand = defer_hand) && target.can_be_grabbed(src, get_target_zone(), defer_hand)) + grab = new /obj/item/hand_item/grab(src, target, grab_tag, defer_hand) + + + if(QDELETED(grab)) + if(original_target != src && ismob(original_target)) + to_chat(original_target, span_warning("\The [src] tries to grab you, but fails!")) + to_chat(src, span_warning("You try to grab \the [target], but fail!")) + + return null + + SEND_SIGNAL(src, COMSIG_LIVING_START_GRAB, src, grab) + SEND_SIGNAL(target, COMSIG_ATOM_GET_GRABBED, src, grab) + + return grab + +/mob/living/add_grab(obj/item/hand_item/grab/grab) + for(var/obj/item/hand_item/grab/other_grab in contents) + if(other_grab != grab) + return FALSE + grab.forceMove(src) + return TRUE + +/mob/living/ProcessGrabs() + if(LAZYLEN(grabbed_by)) + resist() diff --git a/code/modules/grab/grab_movable.dm b/code/modules/grab/grab_movable.dm new file mode 100644 index 000000000000..71755217595d --- /dev/null +++ b/code/modules/grab/grab_movable.dm @@ -0,0 +1,30 @@ +/// Attempt to create a grab, returns TRUE on success +/atom/movable/proc/try_make_grab(atom/movable/target, grab_type) + return canUseTopic(src, USE_IGNORE_TK|USE_CLOSE) && make_grab(target, grab_type) + +/atom/movable/proc/can_be_grabbed(mob/living/grabber, target_zone, force) + if(!istype(grabber) || !isturf(loc) || !isturf(grabber.loc)) + return FALSE + if(SEND_SIGNAL(src, COMSIG_ATOM_CAN_BE_GRABBED, grabber) & COMSIG_ATOM_CANT_PULL) + return FALSE + if(!grabber.canUseTopic(src, USE_CLOSE|USE_IGNORE_TK)) + return FALSE + if(!buckled_grab_check(grabber)) + return FALSE + if(anchored) + to_chat(grabber, span_warning("\The [src] won't budge!")) + return FALSE + if(throwing) + return FALSE + if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) + return FALSE + return TRUE + +/atom/movable/proc/buckled_grab_check(var/mob/grabber) + if(grabber.buckled == src && buckled_mob == grabber) + return TRUE + if(grabber.anchored) + return FALSE + if(grabber.buckled) + return FALSE + return TRUE diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index e70d89b0a475..8967276fd66b 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -25,7 +25,7 @@ /* This section is for overrides of existing procs. */ -/obj/item/hand_item/grab/Initialize(mapload, atom/movable/target, datum/grab/grab_type) +/obj/item/hand_item/grab/Initialize(mapload, atom/movable/target, datum/grab/grab_type, defer_hand) . = ..() current_grab = GLOB.all_grabstates[grab_type] @@ -34,12 +34,10 @@ return INITIALIZE_HINT_QDEL affecting = target - if(!istype(affecting)) + if(!istype(assailant) || !assailant.add_grab(src, defer_hand = defer_hand)) return INITIALIZE_HINT_QDEL target_zone = assailant.zone_selected - if(!can_grab()) - return INITIALIZE_HINT_QDEL if(!setup()) return INITIALIZE_HINT_QDEL @@ -48,7 +46,9 @@ name = "[initial(name)] ([BP.plaintext_zone])" RegisterSignal(affecting, COMSIG_CARBON_REMOVED_LIMB, PROC_REF(on_limb_loss)) - RegisterSignal(affecting, COMSIG_PARENT_QDELETING, PROC_REF(target_del)) + RegisterSignal(assailant, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) + RegisterSignal(affecting, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) + RegisterSignal(affecting, COMSIG_MOVABLE_PRE_THROW, PROC_REF(target_thrown)) RegisterSignal(assailant, COMSIG_MOB_SELECTED_ZONE_SET, PROC_REF(on_target_change)) RegisterSignal(assailant, COMSIG_MOVABLE_MOVED, PROC_REF(relay_user_move)) @@ -87,14 +87,16 @@ // End workaround if (QDELETED(src) || !assailant || !current_grab) return TRUE - if(A.attack_grab(src, params) || current_grab.hit_with_grab(src, A, params)) //If there is no use_grab override or if it returns FALSE; then will behave according to intent. + if(A.attack_grab(assailant, affecting, src, params) || current_grab.hit_with_grab(src, A, params)) //If there is no use_grab override or if it returns FALSE; then will behave according to intent. return TRUE return ..() /obj/item/hand_item/grab/Destroy() + if(affecting && assailant) + current_grab.let_go(src) if(affecting) reset_position() - affecting.grabbed_by -= src + LAZYREMOVE(affecting.grabbed_by, src) affecting.reset_plane_and_layer() affecting = null assailant = null @@ -142,50 +144,13 @@ return current_grab.let_go(src) -/obj/item/hand_item/grab/proc/can_grab() - if(!assailant.Adjacent(affecting)) - return FALSE - - if(assailant.anchored || affecting.anchored) - return FALSE - - if(assailant.get_active_hand()) - to_chat(assailant, span_warning("You can't grab someone if your hand is full.")) - return FALSE - - if(length(assailant.grabbed_by)) - to_chat(assailant, span_warning("You can't grab someone if you're being grabbed.")) - return FALSE - - var/obj/item/bodypart/BP = get_targeted_bodypart() - if(!istype(BP)) - to_chat(assailant, span_warning("\The [affecting] is missing that body part!")) - return FALSE - - if(assailant == affecting) - if(!current_grab.can_grab_self) //let's not nab ourselves - to_chat(assailant, span_warning("You can't grab yourself!")) - return FALSE - - var/active_hand = assailant.get_active_hand() - if(BP == active_hand) - to_chat(assailant, span_warning("You can't grab your own [BP.plaintext_zone] with itself!")) - return FALSE - - for(var/obj/item/hand_item/grab/G in affecting.grabbed_by) - if(G.assailant == assailant && G.target_zone == target_zone) - var/obj/item/bodypart/targeted = G.get_targeted_bodypart() - to_chat(assailant, span_warning("You already grabbed [affecting]'s [targeted.plaintext_zone].")) - return FALSE - return TRUE - // This will run from Initialize, after can_grab and other checks have succeeded. Must call parent; returning FALSE means failure and qdels the grab. /obj/item/hand_item/grab/proc/setup() if(!assailant.put_in_active_hand(src)) return FALSE // This should succeed as we checked the hand, but if not we abort here. if(!current_grab.setup(src)) return FALSE - affecting.grabbed_by += src // This is how we handle affecting being deleted. + LAZYADD(affecting.grabbed_by, src) // This is how we handle affecting being deleted. adjust_position() action_used() @@ -194,6 +159,7 @@ playsound(affecting.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) update_appearance() + current_grab.update_grab_effects(null) return TRUE // Returns the bodypart of the grabbed person that the grabber is targeting @@ -292,6 +258,7 @@ if(isliving(affecting)) return affecting + /// Primarily used for do_after() callbacks, checks if the grab item is still holding onto something /obj/item/hand_item/grab/proc/is_grabbing(atom/movable/AM) return affecting == AM @@ -305,52 +272,24 @@ current_grab.assailant_moved(src) /// Target deleted, ABORT -/obj/item/hand_item/grab/proc/target_del(datum/source) +/obj/item/hand_item/grab/proc/target_or_owner_del(datum/source) SIGNAL_HANDLER qdel(src) -/* - This section is for the simple procs used to return things from current_grab. -*/ -/obj/item/hand_item/grab/proc/stop_move() - return current_grab.stop_move +/// If something tries to throw the target. +/obj/item/hand_item/grab/proc/target_thrown(atom/movable/source, list/arguments) + SIGNAL_HANDLER -/obj/item/hand_item/grab/proc/force_stand() - return current_grab.force_stand + if(!current_grab.stop_move) + return + if(arguments[4] == assailant && current_grab.can_throw) + return + + return COMPONENT_CANCEL_THROW /obj/item/hand_item/grab/attackby(obj/W, mob/user) if(user == assailant) current_grab.item_attack(src, W) -/obj/item/hand_item/grab/proc/can_absorb() - return current_grab.can_absorb - -/obj/item/hand_item/grab/proc/assailant_reverse_facing() - return current_grab.reverse_facing - -/obj/item/hand_item/grab/proc/shield_assailant() - return current_grab.shield_assailant - -/obj/item/hand_item/grab/proc/point_blank_mult() - return current_grab.point_blank_mult - -/obj/item/hand_item/grab/proc/damage_stage() - return current_grab.damage_stage - -/obj/item/hand_item/grab/proc/force_danger() - return current_grab.force_danger - -/obj/item/hand_item/grab/proc/grab_slowdown() - return current_grab.grab_slowdown - -/obj/item/hand_item/grab/proc/ladder_carry() - return current_grab.ladder_carry - -/obj/item/hand_item/grab/proc/assailant_moved() - current_grab.assailant_moved(src) - -/obj/item/hand_item/grab/proc/restrains() - return current_grab.restrains - /obj/item/hand_item/grab/proc/resolve_openhand_attack() return current_grab.resolve_openhand_attack(src) diff --git a/code/modules/grab/grabs/grab_carbon.dm b/code/modules/grab/grabs/grab_carbon.dm new file mode 100644 index 000000000000..c05ee4719251 --- /dev/null +++ b/code/modules/grab/grabs/grab_carbon.dm @@ -0,0 +1,7 @@ +/mob/living/carbon/get_active_grabs() + . = list() + for(var/obj/item/hand_item/grab/grab in get_held_items()) + . += grab + +/mob/living/carbon/make_grab(atom/movable/target,grab_type) + . = ..(target, species?.grab_type || grab_type) diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index c1b27c54dcb4..581ec33cd171 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -272,7 +272,7 @@ if(total_damage) user.visible_message("\The [user] slit [affecting]'s throat open with \the [W]!") - + affecting.apply_status_effect(/datum/status_effect/neck_slice) if(W.hitsound) playsound(affecting.loc, W.hitsound, 50, 1, -1) diff --git a/code/modules/grab/grabs/grab_simple.dm b/code/modules/grab/grabs/grab_simple.dm new file mode 100644 index 000000000000..912d672209ab --- /dev/null +++ b/code/modules/grab/grabs/grab_simple.dm @@ -0,0 +1,25 @@ +/datum/grab/simple + name = "simple grab" + shift = 8 + stop_move = FALSE + reverse_facing = FALSE + shield_assailant = FALSE + point_blank_mult = 1 + same_tile = FALSE + icon_state = "1" + break_chance_table = list(15, 60, 100) + +/datum/grab/simple/upgrade(obj/item/grab/G) + return + +/datum/grab/simple/on_hit_disarm(var/obj/item/grab/G, var/atom/A) + return FALSE + +/datum/grab/simple/on_hit_grab(var/obj/item/grab/G, var/atom/A) + return FALSE + +/datum/grab/simple/on_hit_harm(var/obj/item/grab/G, var/atom/A) + return FALSE + +/decl/grab/simple/resolve_openhand_attack(var/obj/item/grab/G) + return FALSE diff --git a/code/modules/grab/human_grab.dm b/code/modules/grab/human_grab.dm new file mode 100644 index 000000000000..b715128c44c2 --- /dev/null +++ b/code/modules/grab/human_grab.dm @@ -0,0 +1,33 @@ +/mob/living/carbon/human/add_grab(var/obj/item/grab/grab, defer_hand = FALSE) + if(defer_hand) + . = put_in_inactive_hand(grab) + else + . = put_in_active_hand(grab) + +/mob/living/carbon/human/can_be_grabbed(mob/living/grabber, target_zone, defer_hand = FALSE) + . = ..() + if(!.) + return + + var/obj/item/bodypart/BP = get_bodypart(deprecise_zone(target_zone)) + if(!istype(organ)) + to_chat(grabber, span_warning("\The [src] is missing that body part!")) + return FALSE + + if(grabber == src) + var/using_slot = defer_hand ? get_inactive_hand() : get_active_hand() + if(!using_slot) + to_chat(src, span_warning("You cannot grab yourself without a usable hand!")) + return FALSE + + if(using_slot == BP) + to_chat(src, span_warning("You can't grab your own [organ.name] with itself!")) + return FALSE + + if(pull_damage()) + to_chat(grabber, span_warning("Pulling \the [src] in their current condition would probably be a bad idea.")) + + var/obj/item/clothing/C = get_covering_equipped_item_by_zone(target_zone) + if(istype(C)) + C.add_fingerprint(grabber) + diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm index 202c7ea00139..401884915f04 100644 --- a/code/modules/holodeck/items.dm +++ b/code/modules/holodeck/items.dm @@ -93,21 +93,18 @@ if(user.transferItemToLoc(W, drop_location())) visible_message(span_warning("[user] dunks [W] into \the [src]!")) -/obj/structure/holohoop/attack_hand(mob/living/user, list/modifiers) +/obj/structure/holohoop/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) . = ..() - if(.) + if(!isliving(victim)) return - if(user.pulling && isliving(user.pulling)) - var/mob/living/L = user.pulling - if(user.grab_state < GRAB_AGGRESSIVE) - to_chat(user, span_warning("You need a better grip to do that!")) - return - L.forceMove(loc) - L.Paralyze(100) - visible_message(span_danger("[user] dunks [L] into \the [src]!")) - user.stop_pulling() - else - ..() + var/mob/living/L = victim + if(grab.current_grab.damage_stage < 1) + to_chat(user, span_warning("You need a better grip to do that!")) + return + L.forceMove(loc) + L.Paralyze(100) + visible_message(span_danger("[user] dunks [L] into \the [src]!")) + user.release_grab(L) /obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) if (isitem(AM) && !istype(AM,/obj/projectile)) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 0987fd3f546b..29889b3f30cf 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -47,6 +47,12 @@ return has_hand_for_held_index(active_hand_index) +/// Returns the first available empty held index +/mob/proc/get_empty_held_index() + for(var/i in 1 to length(held_items)) + if(isnull(held_items[i])) + return i + //Finds the first available (null) index OR all available (null) indexes in held_items based on a side. //Lefts: 1, 3, 5, 7... //Rights:2, 4, 6, 8... @@ -162,11 +168,14 @@ held_items[hand_index] = I I.plane = ABOVE_HUD_PLANE I.equipped(src, ITEM_SLOT_HANDS) + if(QDELETED(I)) // this is here because some ABSTRACT items like slappers and circle hands could be moved from hand to hand then delete, which meant you'd have a null in your hand until you cleared it (say, by dropping it) held_items[hand_index] = null return FALSE - if(I.pulledby) - I.pulledby.stop_pulling() + + if(LAZYLEN(I.grabbed_by)) + I.free_from_all_grabs() + update_held_items() I.pixel_x = I.base_pixel_x I.pixel_y = I.base_pixel_y diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm index eed38cba076a..5e9f39259835 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm @@ -39,10 +39,10 @@ GLOBAL_LIST_INIT(strippable_alien_humanoid_items, create_strippable_list(list( ..(I, cuff_break = INSTANT_CUFFBREAK) /mob/living/carbon/alien/humanoid/resist_grab(moving_resist) - if(pulledby.grab_state) + if(LAZYLEN(grabbed_by)) visible_message(span_danger("[src] breaks free of [pulledby]'s grip!"), \ span_danger("You break free of [pulledby]'s grip!")) - pulledby.stop_pulling() + free_from_all_grabs() . = 0 /mob/living/carbon/alien/humanoid/get_permeability_protection(list/target_zones) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 0a0ac287cafe..90e6080bacca 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -160,7 +160,7 @@ thrown_thing = throwable_mob if(grab_state >= GRAB_NECK) neckgrab_throw = TRUE - stop_pulling() + release_all_grabs() if(HAS_TRAIT(src, TRAIT_PACIFISM)) to_chat(src, span_notice("You gently let go of [throwable_mob].")) return @@ -776,7 +776,7 @@ /mob/living/carbon/proc/update_handcuffed() if(handcuffed) drop_all_held_items() - stop_pulling() + release_all_grabs() throw_alert(ALERT_HANDCUFFED, /atom/movable/screen/alert/restrained/handcuffed, new_master = src.handcuffed) else clear_alert(ALERT_HANDCUFFED) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index ca447b99bb32..14b8c07cdfbe 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -290,8 +290,8 @@ COMBAT_MESSAGE_RANGE, ) log_combat(src, target, "shoved", "knocking them down") - target.pulledby?.stop_pulling() - target.stop_pulling() + target.free_from_all_grabs() + target.release_all_grabs() return if(target.IsKnockdown()) //KICK HIM IN THE NUTS //That is harm intent. diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 613d36df97ae..aa4aef23d6fa 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -208,7 +208,9 @@ msg += "[t_He] look[p_s()] a little soaked.\n" - if(pulledby?.grab_state) + for(var/obj/item/hand_item/grab/G in grabbed_by) + if(G.current_grab.can_move) + continue msg += "[t_He] [t_is] restrained by [pulledby]'s grip.\n" if(nutrition < NUTRITION_LEVEL_STARVING - 50) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index afc9877e9561..b568108a3319 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -852,7 +852,8 @@ set_species(newtype) /mob/living/carbon/human/mouse_buckle_handling(mob/living/M, mob/living/user) - if(pulling != M || grab_state != GRAB_AGGRESSIVE || stat != CONSCIOUS) + var/obj/item/hand_item/grab/G = is_grabbing(M) + if(!G || G.current_grab.damage_stage != GRAB_AGGRESSIVE || stat != CONSCIOUS) return FALSE //If they dragged themselves to you and you're currently aggressively grabbing them try to piggyback diff --git a/code/modules/mob/living/carbon/human/human_context.dm b/code/modules/mob/living/carbon/human/human_context.dm index 56dc9040bbd8..adaeee86dc87 100644 --- a/code/modules/mob/living/carbon/human/human_context.dm +++ b/code/modules/mob/living/carbon/human/human_context.dm @@ -7,16 +7,9 @@ if (user == src) return . - if (pulledby == user) - switch (user.grab_state) - if (GRAB_PASSIVE) - context[SCREENTIP_CONTEXT_CTRL_LMB] = "Grip" - if (GRAB_AGGRESSIVE) - context[SCREENTIP_CONTEXT_CTRL_LMB] = "Choke" - if (GRAB_NECK) - context[SCREENTIP_CONTEXT_CTRL_LMB] = "Strangle" - else - return . + var/obj/item/hand_item/grab/G = user.is_grabbing(src) + if (G) + G.current_grab.add_context(context, user) else context[SCREENTIP_CONTEXT_CTRL_LMB] = "Pull" diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index c5f0afa51615..7fc1eef67643 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -184,7 +184,7 @@ update_worn_undersuit() if(wear_suit.breakouttime) //when equipping a straightjacket ADD_TRAIT(src, TRAIT_RESTRAINED, SUIT_TRAIT) - stop_pulling() //can't pull if restrained + release_all_grabs() //can't pull if restrained update_mob_action_buttons() //certain action buttons will no longer be usable. update_worn_oversuit() if(ITEM_SLOT_ICLOTHING) diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index 4fb4e37cd9d5..c9b50fb3f94c 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -67,8 +67,8 @@ if(index) held_items[index] = null - if(I.pulledby) - I.pulledby.stop_pulling() + if(LAZYLEN(I.grabbed_by)) + I.free_from_all_grabs() I.screen_loc = null if(client) diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 41d28aab82c1..ef85fa68fa9c 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -83,7 +83,7 @@ losebreath = max(2, losebreath + 1) else if(!getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - if((pulledby?.grab_state >= GRAB_KILL) || (lungs?.organ_flags & ORGAN_DEAD)) + if((check_grab_severity(GRAB_NECK)) || (lungs?.organ_flags & ORGAN_DEAD)) losebreath++ //You can't breath at all when in critical or when being choked, so you're going to miss a breath // Recover from breath loss diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 09423e5001a9..ba82fda8f6c1 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -88,7 +88,7 @@ update_health_hud() med_hud_set_health() med_hud_set_status() - stop_pulling() + release_all_grabs() set_ssd_indicator(FALSE) set_typing_indicator(FALSE) diff --git a/code/modules/mob/living/init_signals.dm b/code/modules/mob/living/init_signals.dm index e832d5d770ff..ca539e53f0a1 100644 --- a/code/modules/mob/living/init_signals.dm +++ b/code/modules/mob/living/init_signals.dm @@ -154,8 +154,7 @@ /mob/living/proc/on_pull_blocked_trait_gain(datum/source) SIGNAL_HANDLER mobility_flags &= ~(MOBILITY_PULL) - if(pulling) - stop_pulling() + release_all_grabs() /// Called when [TRAIT_PULL_BLOCKED] is removed from the mob. /mob/living/proc/on_pull_blocked_trait_loss(datum/source) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 940a96d4b8c1..f3125a53d4b0 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -133,18 +133,20 @@ ContactContractDisease(D) //Should stop you pushing a restrained person out of the way - if(L.pulledby && L.pulledby != src && HAS_TRAIT(L, TRAIT_RESTRAINED)) + if(LAZYLEN(L.grabbed_by) && !is_grabbing(L) && HAS_TRAIT(L, TRAIT_RESTRAINED)) if(!(world.time % 5)) to_chat(src, span_warning("[L] is restrained, you cannot push past.")) return TRUE - if(L.pulling) - if(ismob(L.pulling)) - var/mob/P = L.pulling - if(HAS_TRAIT(P, TRAIT_RESTRAINED)) - if(!(world.time % 5)) - to_chat(src, span_warning("[L] is restraining [P], you cannot push past.")) - return TRUE + var/list/grabs = L.get_active_grabs() + if(length(grabs)) + for(var/obj/item/hand_item/grab/G in grabs) + if(ismob(G.affecting)) + var/mob/P = L.pulling + if(HAS_TRAIT(P, TRAIT_RESTRAINED)) + if(!(world.time % 5)) + to_chat(src, span_warning("[L] is restraining [P], you cannot push past.")) + return TRUE if(moving_diagonally)//no mob swap during diagonal moves. return TRUE @@ -283,8 +285,9 @@ for(var/obj/structure/window/win in get_step(W, dir_to_target)) now_pushing = FALSE return - if(pulling == AM) - stop_pulling() + + release_grab(AM) + var/current_dir if(isliving(AM)) current_dir = AM.dir @@ -426,8 +429,6 @@ if(istype(AM) && Adjacent(AM)) start_pulling(AM) - else if(!combat_mode) //Don;'t cancel pulls if misclicking in combat mode. - stop_pulling() /mob/living/stop_pulling() animate_interact(pulling, INTERACT_UNPULL) @@ -440,7 +441,7 @@ /mob/living/verb/stop_pulling1() set name = "Stop Pulling" set category = "IC" - stop_pulling() + release_all_grabs() //same as above /mob/living/pointed(atom/A as mob|obj|turf in view(client.view, src)) @@ -1240,7 +1241,7 @@ return /mob/living/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) - stop_pulling() + release_all_grabs() . = ..() // Used in polymorph code to shapeshift mobs into other creatures @@ -1599,7 +1600,8 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/forceMove(atom/destination) if(!currently_z_moving) - stop_pulling() + release_all_grabs() + free_from_all_grabs() if(buckled && !HAS_TRAIT(src, TRAIT_CANNOT_BE_UNBUCKLED)) buckled.unbuckle_mob(src, force = TRUE) if(has_buckled_mobs()) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index b69dd1b1860d..090872f5aac8 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -144,15 +144,6 @@ user.start_pulling(src, supress_message = supress_message) return - if(!(status_flags & CANPUSH) || HAS_TRAIT(src, TRAIT_PUSHIMMUNE)) - to_chat(user, span_warning("[src] can't be grabbed more aggressively!")) - return FALSE - - if(user.grab_state >= GRAB_AGGRESSIVE && HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, span_warning("You don't want to risk hurting [src]!")) - return FALSE - grippedby(user) - //proc to upgrade a simple pull into a more aggressive grab. /mob/living/proc/grippedby(mob/living/carbon/user, instant = FALSE) if(user.grab_state < GRAB_KILL) @@ -193,7 +184,7 @@ span_userdanger("[user] grabs you aggressively!"), span_hear("You hear aggressive shuffling!"), null, user) to_chat(user, span_danger("You grab [src] aggressively!")) drop_all_held_items() - stop_pulling() + release_all_grabs() log_combat(user, src, "grabbed", addition="aggressive grab[add_log]") if(GRAB_NECK) log_combat(user, src, "grabbed", addition="neck grab") diff --git a/code/modules/mob/living/living_stripping.dm b/code/modules/mob/living/living_stripping.dm index a0956eb26724..8725add6bbb0 100644 --- a/code/modules/mob/living/living_stripping.dm +++ b/code/modules/mob/living/living_stripping.dm @@ -1,7 +1,7 @@ /mob/living/proc/should_strip(mob/user) . = TRUE - if(user.pulling == src && isliving(user)) + if(user.is_grabbing(src) && !user.combat_mode && isliving(user)) var/mob/living/living_user = user if(mob_size < living_user.mob_size) // If we're smaller than user return !mob_pickup_checks(user, FALSE) diff --git a/code/modules/mob/living/silicon/pai/pai_shell.dm b/code/modules/mob/living/silicon/pai/pai_shell.dm index 8036bd1c0d88..5ec8d31fbda4 100644 --- a/code/modules/mob/living/silicon/pai/pai_shell.dm +++ b/code/modules/mob/living/silicon/pai/pai_shell.dm @@ -55,7 +55,9 @@ . = fold_out(force) return visible_message(span_notice("[src] deactivates its holochassis emitter and folds back into a compact card!")) - stop_pulling() + + release_all_grabs() + if(ismobholder(loc)) var/obj/item/mob_holder/MH = loc MH.release_mob(display_messages = FALSE) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm index e32880794724..504d25e598b1 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm @@ -61,8 +61,8 @@ held_items[index] = null update_held_items() - if(I.pulledby) - I.pulledby.stop_pulling() + if(LAZYLEN(I.grabbed_by)) + I.free_from_all_grabs() I.screen_loc = null // will get moved if inventory is visible I.forceMove(src) diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm index ae0f67eb6b85..eb39e577788d 100644 --- a/code/modules/mob/living/simple_animal/guardian/guardian.dm +++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm @@ -300,8 +300,8 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians held_items[index] = null update_held_items() - if(I.pulledby) - I.pulledby.stop_pulling() + if(LAZYLEN(I.grabbed_by)) + I.free_from_all_grabs() I.screen_loc = null // will get moved if inventory is visible I.forceMove(src) diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm index b0512edbe159..42a3050fb213 100644 --- a/code/modules/mob/living/simple_animal/hostile/mimic.dm +++ b/code/modules/mob/living/simple_animal/hostile/mimic.dm @@ -383,7 +383,7 @@ GLOBAL_LIST_INIT(mimic_blacklist, list(/obj/structure/table, /obj/structure/cabl for(var/mob/living/M in contents) if(++mobs_stored >= mob_storage_capacity) return FALSE - L.stop_pulling() + L.release_all_grabs() else if(istype(AM, /obj/structure/closet)) return FALSE diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 69a9ef18b6a2..dd99f589cccd 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -309,7 +309,7 @@ /mob/living/simple_animal/proc/environment_air_is_safe() . = TRUE - if(pulledby && pulledby.grab_state >= GRAB_KILL && atmos_requirements["min_oxy"]) + if(check_grab_severities(GRAB_KILL) && atmos_requirements["min_oxy"]) . = FALSE //getting choked if(isturf(loc) && isopenturf(loc)) diff --git a/code/modules/mod/mod_ai.dm b/code/modules/mod/mod_ai.dm index 61ffeca5e997..e238f135ca30 100644 --- a/code/modules/mod/mod_ai.dm +++ b/code/modules/mod/mod_ai.dm @@ -74,7 +74,7 @@ #define AI_FALL_TIME 1 SECONDS /obj/item/mod/control/relaymove(mob/user, direction) - if((!active && wearer) || get_charge() < CHARGE_PER_STEP || user != ai || !COOLDOWN_FINISHED(src, cooldown_mod_move) || (wearer?.pulledby?.grab_state > GRAB_PASSIVE)) + if((!active && wearer) || get_charge() < CHARGE_PER_STEP || user != ai || !COOLDOWN_FINISHED(src, cooldown_mod_move) || (wearer.check_grab_severity(GRAB_AGGRESSIVE))) return FALSE var/timemodifier = MOVE_DELAY * (ISDIAGONALDIR(direction) ? 2 : 1) * (wearer ? WEARER_DELAY : LONE_DELAY) if(wearer && !wearer.Process_Spacemove(direction)) diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm index 698dbde97131..f382d2c903cd 100644 --- a/code/modules/mod/modules/modules_supply.dm +++ b/code/modules/mod/modules/modules_supply.dm @@ -337,8 +337,9 @@ . = ..() if(!.) return - if(istype(mod.wearer.pulling, /obj/structure/closet)) - mod.wearer.stop_pulling() + for(var/obj/item/hand_item/grab/G in mod.wearer.get_active_grabs()) + if(istype(G.affecting, /obj/structure/closet)) + qdel(G) /obj/item/mod/module/magnet/proc/check_locker(obj/structure/closet/locker) if(!mod?.wearer) diff --git a/code/modules/projectiles/aiming.dm b/code/modules/projectiles/aiming.dm index 8b40dc161b4c..968ca42c5552 100644 --- a/code/modules/projectiles/aiming.dm +++ b/code/modules/projectiles/aiming.dm @@ -34,7 +34,7 @@ RegisterSignal(user, COMSIG_PARENT_QDELETING, PROC_REF(target_del)) RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(check_sight)) RegisterSignal(user, COMSIG_MOB_FIRED_GUN, PROC_REF(user_shot)) - RegisterSignal(user, list(COMSIG_PARENT_ATTACKBY, COMSIG_LIVING_GET_PULLED, COMSIG_HUMAN_DISARM_HIT), PROC_REF(trigger)) + RegisterSignal(user, list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_GET_GRABBED, COMSIG_HUMAN_DISARM_HIT), PROC_REF(trigger)) RegisterSignal( user, list( diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm index 9e3d2289b2ea..fe5df4134cc6 100644 --- a/code/modules/shuttle/on_move.dm +++ b/code/modules/shuttle/on_move.dm @@ -28,9 +28,7 @@ All ShuttleMove procs go here var/mob/living/M = thing if(M.buckled) M.buckled.unbuckle_mob(M, 1) - if(M.pulledby) - M.pulledby.stop_pulling() - M.stop_pulling() + M.release_all_grabs() M.visible_message(span_warning("[shuttle] slams into [M]!")) SSblackbox.record_feedback("tally", "shuttle_gib", 1, M.type) log_attack("[key_name(M)] was shuttle gibbed by [shuttle].") diff --git a/code/modules/surgery/organs/external/wings.dm b/code/modules/surgery/organs/external/wings.dm index d07c14dffd15..4e7b89dc6fa9 100644 --- a/code/modules/surgery/organs/external/wings.dm +++ b/code/modules/surgery/organs/external/wings.dm @@ -106,7 +106,7 @@ var/olddir = human.dir - human.stop_pulling() + human.release_all_grabs() if(buckled_obj) buckled_obj.unbuckle_mob(human) step(buckled_obj, olddir) diff --git a/code/modules/tables/tables_racks.dm b/code/modules/tables/tables_racks.dm index a43f8b267c16..b83667842520 100644 --- a/code/modules/tables/tables_racks.dm +++ b/code/modules/tables/tables_racks.dm @@ -98,10 +98,6 @@ /obj/structure/table/attack_paw(mob/user, list/modifiers) return attack_hand(user, modifiers) -/obj/structure/table/attack_hand(mob/living/user, list/modifiers) - try_place_pulled_onto_table(user) - return ..() - /obj/structure/table/attack_tk(mob/user) return @@ -238,23 +234,29 @@ else layer = TABLE_LAYER -/obj/structure/table/proc/try_place_pulled_onto_table(mob/living/user) - if(!Adjacent(user) || !user.pulling) +/obj/structure/table/attack_grab(mob/living/user, obj/item/hand_item/grab/grab, list/params) + try_place_pulled_onto_table(user, G.affecting) + +/obj/structure/table/proc/try_place_pulled_onto_table(mob/living/user, atom/movable/target, obj/item/hand_item/grab/grab) + if(!Adjacent(user)) return - if(isliving(user.pulling)) - var/mob/living/pushed_mob = user.pulling + if(isliving(target)) + var/mob/living/pushed_mob = target if(pushed_mob.buckled) to_chat(user, span_warning("[pushed_mob] is buckled to [pushed_mob.buckled]!")) return if(user.combat_mode) - switch(user.grab_state) - if(GRAB_PASSIVE) + switch(grab.current_grab.type) + if(/datum/grab/simple, /datum/grab/normal) to_chat(user, span_warning("You need a better grip to do that!")) return - if(GRAB_AGGRESSIVE) + #warn aggro grab + /* + if(/datum/grab/aggressive) tablepush(user, pushed_mob) - if(GRAB_NECK to GRAB_KILL) + */ + else tablelimbsmash(user, pushed_mob) else pushed_mob.visible_message(span_notice("[user] begins to place [pushed_mob] onto [src]..."), \ @@ -263,13 +265,15 @@ tableplace(user, pushed_mob) else return - user.stop_pulling() - else if(user.pulling.pass_flags & PASSTABLE) + user.release_all_grabs() + + else if(target.pass_flags & PASSTABLE) user.Move_Pulled(src) - if (user.pulling.loc == loc) + if (target.loc == loc) user.visible_message(span_notice("[user] places [user.pulling] onto [src]."), span_notice("You place [user.pulling] onto [src].")) - user.stop_pulling() + + user.release_grab(target) /obj/structure/table/proc/tableplace(mob/living/user, mob/living/pushed_mob) pushed_mob.forceMove(loc) diff --git a/daedalus.dme b/daedalus.dme index 2c1a7fe42095..9fe65fa5dfc9 100644 --- a/daedalus.dme +++ b/daedalus.dme @@ -2980,8 +2980,14 @@ #include "code\modules\food_and_drinks\restaurant\generic_venues.dm" #include "code\modules\food_and_drinks\restaurant\customers\_customer.dm" #include "code\modules\grab\grab_datum.dm" +#include "code\modules\grab\grab_helpers.dm" +#include "code\modules\grab\grab_living.dm" +#include "code\modules\grab\grab_movable.dm" #include "code\modules\grab\grab_object.dm" +#include "code\modules\grab\human_grab.dm" +#include "code\modules\grab\grabs\grab_carbon.dm" #include "code\modules\grab\grabs\grab_normal.dm" +#include "code\modules\grab\grabs\grab_simple.dm" #include "code\modules\holiday\easter.dm" #include "code\modules\holiday\foreign_calendar.dm" #include "code\modules\holiday\holidays.dm" From f85204927ae1d0fdda3da91983ef8246cade6e65 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Tue, 26 Sep 2023 17:55:41 -0400 Subject: [PATCH 04/37] IM DYING INSIDE --- _maps/shuttles/emergency_luxury.dmm | 1324 ----------------- code/__DEFINES/ai.dm | 2 +- .../signals_atom/signals_atom_movement.dm | 2 +- code/__DEFINES/movement.dm | 2 +- code/_onclick/hud/screen_objects.dm | 17 + code/_onclick/other_mobs.dm | 2 +- code/datums/ai/monkey/monkey_behaviors.dm | 2 +- code/datums/ai/monkey/monkey_controller.dm | 4 +- code/datums/components/shy.dm | 4 +- code/datums/components/shy_in_room.dm | 6 +- code/datums/components/strong_pull.dm | 2 +- code/datums/elements/atmos_requirements.dm | 2 +- code/datums/martial/cqc.dm | 12 +- code/datums/mutations/hulk.dm | 5 +- code/game/area/areas/shuttles.dm | 4 +- code/game/atoms_movable.dm | 75 +- code/game/objects/buckling.dm | 1 + .../game/objects/items/devices/radio/radio.dm | 2 +- code/game/objects/items/hand_items.dm | 2 +- code/game/objects/items/stacks/stack.dm | 8 +- code/game/objects/structures/plasticflaps.dm | 8 +- .../structures/transit_tubes/station.dm | 67 +- code/game/objects/structures/watercloset.dm | 101 +- code/game/turfs/closed/wall/misc_walls.dm | 4 +- .../game/turfs/open/floor/reinforced_floor.dm | 2 +- code/game/turfs/open/space/space.dm | 13 +- code/game/turfs/turf.dm | 19 +- .../antagonists/changeling/powers/absorb.dm | 10 +- .../changeling/powers/mutations.dm | 4 +- code/modules/antagonists/cult/runes.dm | 18 +- .../nukeop/equipment/nuclearbomb.dm | 2 +- code/modules/atmospherics/ZAS/Airflow.dm | 2 - code/modules/awaymissions/signpost.dm | 11 +- .../kitchen_machinery/processor.dm | 27 +- code/modules/grab/grab_datum.dm | 24 +- code/modules/grab/grab_helpers.dm | 45 +- code/modules/grab/grab_living.dm | 43 +- code/modules/grab/grab_movable.dm | 50 +- code/modules/grab/grab_object.dm | 3 +- code/modules/grab/grabs/grab_carbon.dm | 6 +- code/modules/grab/grabs/grab_simple.dm | 11 +- code/modules/grab/human_grab.dm | 13 +- .../living/carbon/alien/humanoid/humanoid.dm | 7 +- .../mob/living/carbon/carbon_defense.dm | 7 +- .../living/carbon/human/human_stripping.dm | 3 +- .../carbon/human/species_types/jellypeople.dm | 14 +- .../carbon/human/species_types/vampire.dm | 73 +- code/modules/mob/living/carbon/life.dm | 2 +- code/modules/mob/living/living.dm | 35 +- code/modules/mob/living/living_defense.dm | 4 +- code/modules/mob/living/living_stripping.dm | 2 +- .../simple_animal/friendly/farm_animals.dm | 2 +- .../living/simple_animal/heretic_monsters.dm | 2 +- .../hostile/megafauna/hierophant.dm | 2 +- .../mob/living/simple_animal/hostile/ooze.dm | 16 +- .../mob/living/simple_animal/parrot.dm | 2 +- .../mob/living/simple_animal/simple_animal.dm | 4 +- .../mob/living/simple_animal/slime/life.dm | 2 +- code/modules/mob/mob_helpers.dm | 5 +- code/modules/mod/mod_ai.dm | 2 +- code/modules/mod/modules/modules_supply.dm | 8 +- .../chemistry/reagents/drug_reagents.dm | 17 +- code/modules/recycling/conveyor.dm | 2 +- code/modules/religion/religion_structures.dm | 17 +- .../crossbreeding/_status_effects.dm | 7 +- code/modules/shuttle/special.dm | 177 --- .../spells/spell_types/jaunt/bloodcrawl.dm | 2 +- .../modules/spells/spell_types/self/charge.dm | 4 +- code/modules/tables/tables_racks.dm | 20 +- 69 files changed, 507 insertions(+), 1892 deletions(-) delete mode 100644 _maps/shuttles/emergency_luxury.dmm diff --git a/_maps/shuttles/emergency_luxury.dmm b/_maps/shuttles/emergency_luxury.dmm deleted file mode 100644 index 042202d52b57..000000000000 --- a/_maps/shuttles/emergency_luxury.dmm +++ /dev/null @@ -1,1324 +0,0 @@ -//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE -"aa" = ( -/turf/template_noop, -/area/template_noop) -"ab" = ( -/turf/closed/wall/mineral/titanium, -/area/shuttle/escape/luxury) -"ac" = ( -/obj/machinery/door/airlock/external/ruin{ - name = "Economy-Class" - }, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"ad" = ( -/obj/machinery/scanner_gate/luxury_shuttle{ - layer = 2.6 - }, -/obj/machinery/door/airlock/silver{ - name = "First Class" - }, -/turf/open/floor/carpet/blue, -/area/shuttle/escape/luxury) -"ae" = ( -/obj/docking_port/mobile/emergency{ - dir = 2; - dwidth = 5; - height = 14; - name = "Luxurious Emergency Shuttle"; - width = 25 - }, -/obj/machinery/scanner_gate/luxury_shuttle{ - layer = 2.6 - }, -/obj/machinery/door/airlock/silver{ - name = "First Class" - }, -/turf/open/floor/carpet/blue, -/area/shuttle/escape/luxury) -"af" = ( -/obj/machinery/door/airlock/security/glass{ - name = "Escape Shuttle Cell" - }, -/obj/effect/mapping_helpers/airlock/access/all/security/brig, -/turf/open/floor/mineral/plastitanium/red/brig, -/area/shuttle/escape/brig) -"ag" = ( -/turf/closed/indestructible/riveted/plastinum, -/area/shuttle/escape/luxury) -"ah" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/ore_box, -/obj/effect/decal/cleanable/cobweb, -/obj/machinery/light/small/directional/north, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"aj" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/closet/crate/large, -/obj/effect/spawner/random/maintenance/eight, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"ak" = ( -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"al" = ( -/turf/closed/wall/mineral/plastitanium/nodiagonal, -/area/shuttle/escape/luxury) -"ao" = ( -/turf/open/floor/carpet/blue, -/area/shuttle/escape/luxury) -"aq" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/reagent_dispensers/fueltank, -/obj/structure/window{ - dir = 4 - }, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"ar" = ( -/obj/structure/shuttle/engine/propulsion{ - dir = 8 - }, -/turf/open/floor/plating/airless, -/area/shuttle/escape/luxury) -"as" = ( -/obj/structure/shuttle/engine/heater{ - dir = 8 - }, -/obj/structure/window/reinforced{ - dir = 4 - }, -/turf/open/floor/plating/airless, -/area/shuttle/escape/luxury) -"at" = ( -/turf/open/floor/mineral/plastitanium/red/brig, -/area/shuttle/escape/brig) -"au" = ( -/obj/effect/spawner/structure/window, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"av" = ( -/obj/machinery/door/poddoor/shutters/indestructible{ - id = "ohnopoors" - }, -/turf/closed/indestructible/opsglass{ - desc = "A durable looking window made of an alloy of of plasma and titanium."; - name = "plastitanium window" - }, -/area/shuttle/escape/luxury) -"aw" = ( -/turf/open/floor/mineral/titanium/blue, -/area/shuttle/escape/luxury) -"ax" = ( -/obj/effect/decal/cleanable/cobweb/cobweb2, -/obj/machinery/light/small/directional/east, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"ay" = ( -/turf/closed/indestructible/syndicate, -/area/shuttle/escape/luxury) -"az" = ( -/obj/machinery/computer/communications{ - dir = 8 - }, -/turf/open/floor/mineral/diamond, -/area/shuttle/escape/luxury) -"aA" = ( -/obj/machinery/button/door{ - id = "ohnopoors"; - name = "window shutters"; - pixel_y = 26 - }, -/turf/open/floor/carpet/blue, -/area/shuttle/escape/luxury) -"aB" = ( -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"aC" = ( -/obj/item/food/enchiladas{ - pixel_y = 3 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aD" = ( -/obj/item/food/candiedapple{ - pixel_y = 6 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aE" = ( -/obj/item/food/benedict{ - pixel_y = 1 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aF" = ( -/obj/item/food/cakeslice/chocolate{ - pixel_y = 1 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aG" = ( -/obj/item/food/eggplantparm{ - pixel_y = 3 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aH" = ( -/obj/item/food/spaghetti/copypasta{ - pixel_y = 5 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aI" = ( -/obj/item/food/spaghetti/boiledspaghetti{ - pixel_y = 5 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aJ" = ( -/obj/item/food/cherrycupcake{ - pixel_y = 2 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aK" = ( -/obj/item/food/spaghetti/meatballspaghetti{ - pixel_y = 5 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aL" = ( -/obj/item/food/notasandwich{ - pixel_y = 11 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aM" = ( -/obj/structure/chair/comfy/teal{ - dir = 4 - }, -/turf/open/floor/mineral/diamond, -/area/shuttle/escape/luxury) -"aN" = ( -/obj/item/food/burger/baconburger{ - pixel_y = 2 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aO" = ( -/obj/item/food/melonfruitbowl{ - pixel_y = 4 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aP" = ( -/obj/item/food/khachapuri{ - pixel_y = 6 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aQ" = ( -/obj/item/food/bearsteak{ - pixel_y = 6 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aR" = ( -/obj/structure/mirror/directional/west, -/obj/structure/sink/greyscale{ - dir = 4; - pixel_x = -12 - }, -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"aS" = ( -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"aT" = ( -/obj/item/food/spaghetti/pastatomato{ - pixel_y = 5 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aU" = ( -/obj/item/food/kebab/tofu{ - pixel_y = 6 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aV" = ( -/obj/item/food/honkdae{ - pixel_y = 8 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aW" = ( -/obj/item/food/spaghetti/chowmein{ - pixel_y = 5 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aX" = ( -/obj/item/food/grilled_cheese_sandwich{ - pixel_y = 11 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aY" = ( -/obj/item/food/jelliedtoast/cherry{ - pixel_y = 6 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aZ" = ( -/obj/item/food/honeybun{ - pixel_y = 1 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"ba" = ( -/obj/item/food/pizza/dank{ - pixel_y = 5 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"bb" = ( -/obj/machinery/door/airlock/silver{ - name = "Restroom" - }, -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"bc" = ( -/turf/closed/indestructible/opsglass{ - desc = "A durable looking window made of an alloy of of plasma and titanium."; - name = "plastitanium window" - }, -/area/shuttle/escape/luxury) -"be" = ( -/obj/structure/girder, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bf" = ( -/obj/structure/closet/crate/trashcart, -/obj/effect/turf_decal/loading_area, -/obj/effect/spawner/random/maintenance/six, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bg" = ( -/obj/structure/closet/crate/trashcart, -/obj/effect/turf_decal/loading_area, -/obj/effect/spawner/random/maintenance/three, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bi" = ( -/obj/machinery/computer/emergency_shuttle{ - dir = 8 - }, -/turf/open/floor/mineral/diamond, -/area/shuttle/escape/luxury) -"bj" = ( -/obj/structure/chair/comfy/shuttle, -/obj/machinery/light/directional/north, -/turf/open/floor/mineral/plastitanium/red/brig, -/area/shuttle/escape/brig) -"bk" = ( -/obj/machinery/light/directional/north, -/turf/open/floor/carpet/blue, -/area/shuttle/escape/luxury) -"bl" = ( -/obj/machinery/computer/station_alert{ - dir = 8 - }, -/turf/open/floor/mineral/diamond, -/area/shuttle/escape/luxury) -"bm" = ( -/obj/structure/table/wood/fancy/black, -/obj/item/storage/fancy/cigarettes/cigars/havana{ - pixel_y = 7 - }, -/turf/open/floor/mineral/titanium/blue, -/area/shuttle/escape/luxury) -"bn" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/closet/crate/engineering/electrical, -/obj/effect/decal/cleanable/robot_debris, -/obj/effect/spawner/random/maintenance/eight, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bo" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/closet/crate/engineering, -/obj/effect/spawner/random/maintenance/eight, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bp" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/window{ - dir = 4 - }, -/obj/structure/closet/crate{ - icon_state = "crateopen" - }, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bq" = ( -/obj/item/stack/rods/ten, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"br" = ( -/obj/effect/decal/cleanable/robot_debris, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bs" = ( -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bt" = ( -/obj/structure/window/reinforced{ - dir = 1; - pixel_y = 2 - }, -/obj/item/clothing/head/collectable/paper, -/turf/open/floor/holofloor/beach/water, -/area/shuttle/escape/luxury) -"bu" = ( -/obj/structure/window/reinforced{ - dir = 1; - pixel_y = 2 - }, -/turf/open/floor/holofloor/beach/water, -/area/shuttle/escape/luxury) -"bv" = ( -/obj/machinery/computer/crew{ - dir = 8 - }, -/turf/open/floor/mineral/diamond, -/area/shuttle/escape/luxury) -"bw" = ( -/obj/structure/window/reinforced{ - dir = 1; - pixel_y = 2 - }, -/obj/structure/window/reinforced{ - dir = 4; - layer = 2.9 - }, -/turf/open/floor/holofloor/beach/coast_t{ - dir = 8 - }, -/area/shuttle/escape/luxury) -"bx" = ( -/obj/machinery/door/airlock/silver{ - name = "Flight Control" - }, -/turf/open/floor/mineral/titanium/blue, -/area/shuttle/escape/luxury) -"by" = ( -/obj/structure/closet/crate/large, -/obj/effect/turf_decal/delivery, -/obj/effect/decal/cleanable/robot_debris, -/obj/effect/spawner/random/maintenance/eight, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bz" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/closet/crate{ - icon_state = "crateopen" - }, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bA" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/window{ - dir = 4 - }, -/obj/structure/closet/crate{ - icon_state = "crateopen" - }, -/obj/effect/decal/cleanable/oil, -/obj/effect/spawner/random/maintenance/two, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bB" = ( -/obj/item/stack/ore/glass, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bC" = ( -/obj/structure/chair/comfy/shuttle{ - dir = 1 - }, -/obj/effect/turf_decal/stripes/line, -/obj/structure/window, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bD" = ( -/obj/structure/chair/comfy/shuttle{ - dir = 1 - }, -/turf/open/floor/mineral/plastitanium/red/brig, -/area/shuttle/escape/brig) -"bE" = ( -/obj/machinery/light/directional/north, -/turf/open/floor/mineral/titanium/blue, -/area/shuttle/escape/luxury) -"bF" = ( -/obj/item/bikehorn/rubberducky, -/obj/item/bikehorn/rubberducky, -/turf/open/floor/holofloor/beach/water, -/area/shuttle/escape/luxury) -"bG" = ( -/obj/machinery/door/window/left/directional/east, -/turf/open/floor/holofloor/beach/coast_t{ - dir = 8 - }, -/area/shuttle/escape/luxury) -"bH" = ( -/obj/structure/door_assembly, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bI" = ( -/obj/item/stack/tile/iron/base, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bJ" = ( -/obj/structure/chair/comfy/shuttle, -/obj/effect/turf_decal/stripes/line{ - dir = 1 - }, -/obj/structure/window{ - dir = 1 - }, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bK" = ( -/obj/structure/chair/comfy/shuttle, -/obj/effect/turf_decal/stripes/line{ - dir = 1 - }, -/obj/structure/window{ - dir = 1 - }, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bL" = ( -/obj/structure/window/reinforced, -/turf/open/floor/holofloor/beach/water, -/area/shuttle/escape/luxury) -"bM" = ( -/obj/structure/window/reinforced, -/obj/machinery/door/window/right/directional/east, -/turf/open/floor/holofloor/beach/coast_t{ - dir = 8 - }, -/area/shuttle/escape/luxury) -"bN" = ( -/obj/machinery/chem_dispenser/drinks/beer{ - name = "aperitif fountain"; - pixel_y = 8 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"bO" = ( -/obj/machinery/chem_dispenser/drinks{ - name = "soda fountain"; - pixel_y = 8 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"bP" = ( -/obj/structure/table/wood/fancy/black, -/obj/machinery/recharger, -/turf/open/floor/mineral/titanium/blue, -/area/shuttle/escape/luxury) -"bQ" = ( -/obj/machinery/light/directional/west, -/turf/open/floor/holofloor/beach/water, -/area/shuttle/escape/luxury) -"bR" = ( -/obj/effect/decal/cleanable/glass, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bS" = ( -/obj/item/stack/tile/iron/base, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bT" = ( -/obj/effect/decal/cleanable/oil, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bU" = ( -/obj/effect/decal/cleanable/generic, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bV" = ( -/obj/structure/chair/comfy/brown, -/turf/open/floor/carpet/orange, -/area/shuttle/escape/luxury) -"bW" = ( -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"bX" = ( -/obj/structure/chair/comfy/shuttle{ - dir = 1 - }, -/obj/effect/turf_decal/stripes/line, -/obj/structure/window, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bY" = ( -/obj/structure/table/wood/fancy/black, -/obj/item/reagent_containers/food/drinks/bottle/champagne{ - pixel_x = 7; - pixel_y = 11 - }, -/obj/item/reagent_containers/food/drinks/shaker{ - pixel_x = -7; - pixel_y = 11 - }, -/obj/item/reagent_containers/glass/rag{ - pixel_x = -6; - pixel_y = 4 - }, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"bZ" = ( -/obj/machinery/vending/boozeomat, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"cb" = ( -/obj/machinery/stasis, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"cc" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/closet/crate/large, -/obj/effect/decal/cleanable/robot_debris, -/obj/effect/spawner/random/maintenance/eight, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cd" = ( -/obj/machinery/door/airlock/public/glass{ - name = "Emergency Shuttle Infirmary" - }, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"cf" = ( -/obj/effect/decal/cleanable/vomit/old, -/obj/item/stack/ore/glass, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cg" = ( -/obj/effect/decal/cleanable/glass, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"ch" = ( -/obj/structure/chair/comfy/brown{ - dir = 1 - }, -/turf/open/floor/carpet/orange, -/area/shuttle/escape/luxury) -"ci" = ( -/obj/structure/musician/piano{ - icon_state = "piano" - }, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"cj" = ( -/obj/structure/closet/crate/freezer, -/obj/item/reagent_containers/blood, -/obj/item/reagent_containers/blood, -/obj/item/reagent_containers/blood/a_minus, -/obj/item/reagent_containers/blood/b_minus{ - pixel_x = -4; - pixel_y = 4 - }, -/obj/item/reagent_containers/blood/b_plus{ - pixel_x = 1; - pixel_y = 2 - }, -/obj/item/reagent_containers/blood/o_minus, -/obj/item/reagent_containers/blood/o_plus{ - pixel_x = -2; - pixel_y = -1 - }, -/obj/item/reagent_containers/blood/random, -/obj/item/reagent_containers/blood/random, -/obj/item/reagent_containers/blood/a_plus, -/obj/item/reagent_containers/blood/random, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"ck" = ( -/obj/item/stack/tile/iron/base, -/obj/machinery/light/small/directional/west, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cl" = ( -/mob/living/simple_animal/bot/medbot{ - name = "\improper emergency medibot"; - pixel_x = -3; - pixel_y = 2 - }, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"cm" = ( -/obj/structure/table, -/obj/item/scalpel{ - pixel_y = 12 - }, -/obj/item/circular_saw, -/obj/item/retractor{ - pixel_x = 4 - }, -/obj/item/hemostat{ - pixel_x = -4 - }, -/obj/item/clothing/gloves/color/latex, -/obj/item/clothing/mask/surgical, -/obj/item/surgicaldrill, -/obj/item/cautery, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"cn" = ( -/obj/machinery/door/airlock{ - name = "Economy Medical" - }, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"co" = ( -/obj/item/shard, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"cp" = ( -/obj/structure/closet/crate/freezer, -/obj/effect/decal/cleanable/blood/old, -/obj/item/bot_assembly/medbot, -/obj/item/stack/gauze, -/obj/item/reagent_containers/food/drinks/bottle/whiskey, -/obj/item/reagent_containers/glass/bottle/ethanol, -/obj/item/organ/stomach, -/obj/item/clothing/mask/surgical, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cq" = ( -/obj/effect/decal/cleanable/chem_pile, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"cr" = ( -/obj/structure/frame/machine, -/obj/effect/decal/cleanable/cobweb/cobweb2, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cs" = ( -/obj/effect/decal/cleanable/robot_debris, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"ct" = ( -/obj/machinery/shower{ - dir = 8 - }, -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"cu" = ( -/obj/structure/toilet{ - pixel_y = 8 - }, -/obj/machinery/light/directional/east, -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"cv" = ( -/obj/structure/table, -/obj/item/reagent_containers/pill/maintenance{ - pixel_x = -6; - pixel_y = -3 - }, -/obj/item/reagent_containers/pill/maintenance{ - pixel_x = 8; - pixel_y = -3 - }, -/obj/item/reagent_containers/pill/maintenance, -/obj/item/healthanalyzer{ - pixel_y = 9 - }, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"cw" = ( -/obj/structure/bed, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cx" = ( -/obj/effect/decal/cleanable/vomit/old, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cy" = ( -/obj/item/shard, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cz" = ( -/obj/machinery/light/directional/south, -/turf/open/floor/mineral/titanium/blue, -/area/shuttle/escape/luxury) -"cA" = ( -/obj/effect/decal/cleanable/oil, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"cB" = ( -/obj/structure/bed, -/obj/effect/decal/cleanable/blood/old, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cC" = ( -/obj/structure/grille/broken, -/obj/effect/spawner/random/maintenance/two, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cD" = ( -/obj/structure/frame/machine, -/obj/effect/turf_decal/delivery, -/obj/effect/decal/cleanable/cobweb, -/obj/machinery/light/small/directional/west, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cE" = ( -/obj/structure/chair/comfy/brown, -/obj/machinery/light/directional/north, -/turf/open/floor/carpet/orange, -/area/shuttle/escape/luxury) -"cF" = ( -/obj/machinery/light/directional/east, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"cG" = ( -/obj/structure/table, -/obj/item/storage/medkit/advanced, -/obj/item/storage/medkit/regular, -/obj/item/storage/medkit/fire, -/obj/item/reagent_containers/glass/bottle/epinephrine{ - pixel_x = 6 - }, -/obj/item/reagent_containers/glass/bottle/dylovene, -/obj/item/reagent_containers/glass/bottle/epinephrine{ - pixel_x = -3; - pixel_y = 8 - }, -/obj/item/reagent_containers/glass/bottle/dylovene, -/obj/item/reagent_containers/syringe/epinephrine{ - pixel_x = 3; - pixel_y = -2 - }, -/obj/item/reagent_containers/syringe/epinephrine{ - pixel_x = 3; - pixel_y = -2 - }, -/obj/item/reagent_containers/syringe/epinephrine{ - pixel_x = 3; - pixel_y = -2 - }, -/obj/machinery/light/directional/north, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"cH" = ( -/obj/machinery/light/directional/south, -/obj/structure/table/optable, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"cI" = ( -/obj/machinery/light/directional/east, -/turf/open/floor/carpet/blue, -/area/shuttle/escape/luxury) -"cJ" = ( -/obj/item/stack/tile/iron/base, -/obj/machinery/light/small/directional/west, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"cK" = ( -/obj/structure/table, -/obj/item/wirecutters{ - pixel_y = 6 - }, -/obj/item/hatchet, -/obj/item/knife/kitchen, -/obj/machinery/light/small/directional/west, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"cL" = ( -/obj/structure/chair/comfy/brown{ - dir = 1 - }, -/obj/machinery/light/directional/south, -/turf/open/floor/carpet/orange, -/area/shuttle/escape/luxury) -"cM" = ( -/obj/machinery/light/directional/south, -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"cN" = ( -/obj/structure/chair/wood{ - dir = 8 - }, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"qa" = ( -/obj/structure/toilet{ - pixel_y = 8 - }, -/obj/machinery/light/directional/west, -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"zh" = ( -/turf/open/floor/mineral/diamond, -/area/shuttle/escape/luxury) -"Eg" = ( -/obj/machinery/vending/wallmed/directional/north, -/obj/machinery/iv_drip, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"Lt" = ( -/obj/machinery/door/airlock/security/glass{ - name = "Escape Shuttle Cell" - }, -/obj/machinery/scanner_gate/luxury_shuttle, -/obj/effect/mapping_helpers/airlock/access/all/security/brig, -/turf/open/floor/mineral/plastitanium/red/brig, -/area/shuttle/escape/brig) - -(1,1,1) = {" -aa -al -ar -ar -al -ar -ar -ar -ar -al -ar -ar -al -aa -"} -(2,1,1) = {" -ab -al -as -as -al -as -as -as -as -al -as -as -al -ab -"} -(3,1,1) = {" -ab -ah -bn -by -ab -cD -aj -cc -aj -ab -cp -cv -cK -ab -"} -(4,1,1) = {" -ab -aj -bo -bz -bH -bR -bC -bJ -ak -cn -cq -bs -cA -ab -"} -(5,1,1) = {" -ab -aq -bp -bA -ab -bS -bC -bJ -cf -ab -cr -cw -cB -ab -"} -(6,1,1) = {" -ab -be -bq -bB -ck -bT -ak -bU -cg -ab -ab -ab -ab -ab -"} -(7,1,1) = {" -ab -bf -br -bC -bJ -bI -bC -bK -bs -co -cs -cJ -cC -ab -"} -(8,1,1) = {" -ab -bg -ak -bC -bJ -bU -bC -bJ -bS -bC -bJ -cx -bC -ab -"} -(9,1,1) = {" -ac -ax -bs -bC -bK -bS -bX -bJ -ak -bC -bJ -cy -bC -ab -"} -(10,1,1) = {" -ag -ag -af -ag -ag -ay -av -av -av -ay -av -av -av -ay -"} -(11,1,1) = {" -af -at -at -bD -ag -aA -ao -ao -ao -ao -ao -ao -ao -ag -"} -(12,1,1) = {" -ag -bj -at -bD -ag -cE -aG -aK -ch -bV -aO -aT -cL -ag -"} -(13,1,1) = {" -ag -at -at -bD -ag -bV -aH -aL -ch -bV -aP -aU -ch -ag -"} -(14,1,1) = {" -ag -Lt -ag -ag -ag -bV -aC -aE -ch -bV -aX -aZ -ch -ag -"} -(15,1,1) = {" -ag -ao -bt -bQ -bL -bV -aD -aF -ch -bV -aY -ba -ch -ag -"} -(16,1,1) = {" -ag -ao -bu -bF -bL -bV -aI -aD -ch -bV -aH -aV -ch -ag -"} -(17,1,1) = {" -ae -ao -bw -bG -bM -bV -aJ -aN -ch -bV -aQ -aW -cL -ag -"} -(18,1,1) = {" -ag -bk -ao -ao -ao -ao -ao -ao -ao -ao -cI -ao -ao -ag -"} -(19,1,1) = {" -ad -ao -ao -ao -bN -bW -bY -ao -ci -ag -ag -ag -bb -ag -"} -(20,1,1) = {" -ag -ao -ao -ao -bO -cF -bZ -ao -cN -ag -aR -aR -aS -ag -"} -(21,1,1) = {" -ag -au -bx -bx -au -ag -au -cd -au -ag -ct -ct -cM -ag -"} -(22,1,1) = {" -ag -bm -aw -aw -bP -ag -cj -aB -cm -ag -ag -ag -aS -ag -"} -(23,1,1) = {" -ag -bE -aw -aw -cz -ag -cG -aB -cH -ag -cu -bb -aS -ag -"} -(24,1,1) = {" -ag -aM -aM -aM -zh -ag -Eg -aB -cl -ag -ag -ag -cM -ag -"} -(25,1,1) = {" -ag -az -bi -bl -bv -ag -cb -cb -cb -ag -qa -bb -aS -ag -"} -(26,1,1) = {" -ag -bc -bc -bc -bc -ag -ag -ag -ag -ag -ag -ag -ag -ag -"} diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index 076efefc7d5f..ce4a13e18d29 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -36,7 +36,7 @@ /// probability that the pawn should try resisting out of restraints #define RESIST_SUBTREE_PROB 50 ///macro for whether it's appropriate to resist right now, used by resist subtree -#define SHOULD_RESIST(source) (source.on_fire || source.buckled || HAS_TRAIT(source, TRAIT_RESTRAINED) || (source.pulledby && source.pulledby.grab_state > GRAB_PASSIVE)) +#define SHOULD_RESIST(source) (source.on_fire || source.buckled || HAS_TRAIT(source, TRAIT_RESTRAINED) || (source.check_grab_severities(GRAB_AGGRESSIVE))) ///macro for whether the pawn can act, used generally to prevent some horrifying ai disasters #define IS_DEAD_OR_INCAP(source) (source.incapacitated() || source.stat) diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm index 9b36207ac0a8..5c3ae41912b7 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm @@ -12,7 +12,7 @@ ///called for each movable in a turf contents on /turf/zImpact(): (atom/movable/A, levels) #define COMSIG_ATOM_INTERCEPT_Z_FALL "movable_intercept_z_impact" ///called on a movable when it starts pulling (atom/movable/pulled, state, force) -#define COMSIG_ATOM_START_GRAB "movable_start_grab" +#define COMSIG_LIVING_START_GRAB "movable_start_grab" ///called on a movable when it has been grabbed #define COMSIG_ATOM_GET_GRABBED "movable_start_grabbed" ///called on /living, when a grab is attempted, but before it completes, from base of [/mob/living/make_grab]: (atom/movable/thing, grab_type) diff --git a/code/__DEFINES/movement.dm b/code/__DEFINES/movement.dm index 8dd637fb8784..caaceadd332d 100644 --- a/code/__DEFINES/movement.dm +++ b/code/__DEFINES/movement.dm @@ -61,7 +61,7 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0) /// Used when the grip on a pulled object shouldn't be broken. #define FALL_RETAIN_PULL (1<<3) -/// Runs check_pulling() by the end of [/atom/movable/proc/zMove] for every movable that's pulling something. Should be kept enabled unless you know what you are doing. +/// Runs recheck_grabs() by the end of [/atom/movable/proc/zMove] for every movable that's pulling something. Should be kept enabled unless you know what you are doing. #define ZMOVE_CHECK_PULLING (1<<0) /// Checks if pulledby is nearby. if not, stop being pulled. #define ZMOVE_CHECK_PULLEDBY (1<<1) diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 1f443513c783..73d27b255468 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -386,6 +386,23 @@ else user.set_move_intent(MOVE_INTENT_RUN) +/atom/movable/screen/pull + name = "stop pulling" + icon = 'icons/hud/screen_midnight.dmi' + icon_state = "pull" + base_icon_state = "pull" + +/atom/movable/screen/pull/Click() + if(isobserver(usr)) + return + if(isliving(usr) && usr == hud.mymob) + var/mob/living/L = usr + L.release_all_grabs() + +/atom/movable/screen/pull/update_icon_state() + icon_state = "[base_icon_state][hud?.mymob?.pulling ? null : 0]" + return ..() + /atom/movable/screen/resist name = "resist" icon = 'icons/hud/screen_midnight.dmi' diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index 7c68a2ca584a..9085ab8e8687 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -122,7 +122,7 @@ return TRUE if(isturf(A) && get_dist(src,A) <= 1) - Move_Pulled(A) + move_grabbed_atoms_towards(A) return TRUE /* diff --git a/code/datums/ai/monkey/monkey_behaviors.dm b/code/datums/ai/monkey/monkey_behaviors.dm index 02910ee1779c..7c8a4499d0a7 100644 --- a/code/datums/ai/monkey/monkey_behaviors.dm +++ b/code/datums/ai/monkey/monkey_behaviors.dm @@ -267,7 +267,7 @@ if(!living_pawn.is_grabbing(target) && !monkey_is_grabbing_target) //Dont steal from my fellow monkeys. if(living_pawn.Adjacent(target) && isturf(target.loc)) - target.grabbedby(living_pawn) + living_pawn.try_make_grab(target) return //Do the rest next turn var/datum/weakref/disposal_ref = controller.blackboard[disposal_target_key] diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm index 266b9d107ae9..08a77309ca3b 100644 --- a/code/datums/ai/monkey/monkey_controller.dm +++ b/code/datums/ai/monkey/monkey_controller.dm @@ -54,7 +54,7 @@ have ways of interacting with a specific mob and control it. RegisterSignal(new_pawn, COMSIG_MOB_ATTACK_ALIEN, PROC_REF(on_attack_alien)) RegisterSignal(new_pawn, COMSIG_ATOM_BULLET_ACT, PROC_REF(on_bullet_act)) RegisterSignal(new_pawn, COMSIG_ATOM_HITBY, PROC_REF(on_hitby)) - RegisterSignal(new_pawn, COMSIG_LIVING_START_GRAB, PROC_REF(on_startpulling)) + RegisterSignal(new_pawn, COMSIG_ATOM_GET_GRABBED, PROC_REF(on_grabbed)) RegisterSignal(new_pawn, COMSIG_LIVING_TRY_SYRINGE, PROC_REF(on_try_syringe)) RegisterSignal(new_pawn, COMSIG_ATOM_HULK_ATTACK, PROC_REF(on_attack_hulk)) RegisterSignal(new_pawn, COMSIG_CARBON_CUFF_ATTEMPTED, PROC_REF(on_attempt_cuff)) @@ -70,7 +70,7 @@ have ways of interacting with a specific mob and control it. COMSIG_ATOM_ATTACK_PAW, COMSIG_ATOM_BULLET_ACT, COMSIG_ATOM_HITBY, - COMSIG_LIVING_START_GRAB, + COMSIG_ATOM_GET_GRABBED, COMSIG_LIVING_TRY_SYRINGE, COMSIG_ATOM_HULK_ATTACK, COMSIG_CARBON_CUFF_ATTEMPTED, diff --git a/code/datums/components/shy.dm b/code/datums/components/shy.dm index b8f7b5896b01..b3823031a3b0 100644 --- a/code/datums/components/shy.dm +++ b/code/datums/components/shy.dm @@ -53,7 +53,7 @@ /datum/component/shy/UnregisterFromParent() UnregisterSignal(parent, list( COMSIG_MOB_CLICKON, - COMSIG_LIVING_TRY_PULL, + COMSIG_LIVING_TRY_GRAB, COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, COMSIG_TRY_STRIP, COMSIG_TRY_ALT_ACTION, @@ -118,7 +118,7 @@ /datum/component/shy/proc/on_try_pull(datum/source, atom/movable/target, force) SIGNAL_HANDLER - return is_shy(target) && COMSIG_LIVING_CANCEL_PULL + return is_shy(target) && COMSIG_LIVING_CANCEL_GRAB /datum/component/shy/proc/on_unarmed_attack(datum/source, atom/target, proximity, modifiers) SIGNAL_HANDLER diff --git a/code/datums/components/shy_in_room.dm b/code/datums/components/shy_in_room.dm index cf4319339630..a6a7933a0b4b 100644 --- a/code/datums/components/shy_in_room.dm +++ b/code/datums/components/shy_in_room.dm @@ -16,7 +16,7 @@ /datum/component/shy_in_room/RegisterWithParent() RegisterSignal(parent, COMSIG_MOB_CLICKON, PROC_REF(on_clickon)) - RegisterSignal(parent, COMSIG_LIVING_TRY_PULL, PROC_REF(on_try_pull)) + RegisterSignal(parent, COMSIG_LIVING_TRY_GRAB, PROC_REF(on_try_pull)) RegisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK), PROC_REF(on_unarmed_attack)) RegisterSignal(parent, COMSIG_TRY_STRIP, PROC_REF(on_try_strip)) RegisterSignal(parent, COMSIG_TRY_ALT_ACTION, PROC_REF(on_try_alt_action)) @@ -25,7 +25,7 @@ /datum/component/shy_in_room/UnregisterFromParent() UnregisterSignal(parent, list( COMSIG_MOB_CLICKON, - COMSIG_LIVING_TRY_PULL, + COMSIG_LIVING_TRY_GRAB, COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, COMSIG_TRY_STRIP, @@ -60,7 +60,7 @@ /datum/component/shy_in_room/proc/on_try_pull(datum/source, atom/movable/target, force) SIGNAL_HANDLER - return is_shy(target) && COMSIG_LIVING_CANCEL_PULL + return is_shy(target) && COMSIG_LIVING_CANCEL_GRAB /datum/component/shy_in_room/proc/on_unarmed_attack(datum/source, atom/target, proximity, modifiers) SIGNAL_HANDLER diff --git a/code/datums/components/strong_pull.dm b/code/datums/components/strong_pull.dm index 353d6e361ede..f9b8188e0c60 100644 --- a/code/datums/components/strong_pull.dm +++ b/code/datums/components/strong_pull.dm @@ -17,7 +17,7 @@ Basically, the items they pull cannot be pulled (except by the puller) /datum/component/strong_pull/RegisterWithParent() . = ..() - RegisterSignal(parent, COMSIG_ATOM_START_GRAB, PROC_REF(on_pull)) + RegisterSignal(parent, COMSIG_LIVING_START_GRAB, PROC_REF(on_pull)) /** * Called when the parent grabs something, adds signals to the object to reject interactions diff --git a/code/datums/elements/atmos_requirements.dm b/code/datums/elements/atmos_requirements.dm index 4f074dc2ec6e..749b017e4b5d 100644 --- a/code/datums/elements/atmos_requirements.dm +++ b/code/datums/elements/atmos_requirements.dm @@ -36,7 +36,7 @@ target.throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy) /datum/element/atmos_requirements/proc/is_breathable_atmos(mob/living/target) - if(target.check_grab_severity(GRAB_KILL)) && atmos_requirements["min_oxy"]) + if(target.check_grab_severity(GRAB_KILL) && atmos_requirements["min_oxy"]) return FALSE diff --git a/code/datums/martial/cqc.dm b/code/datums/martial/cqc.dm index ff01d6c4a8f7..4909ba92f4fe 100644 --- a/code/datums/martial/cqc.dm +++ b/code/datums/martial/cqc.dm @@ -122,11 +122,17 @@ add_to_streak("G",D) if(check_streak(A,D)) //if a combo is made no grab upgrade is done return TRUE - old_grab_state = A.grab_state - D.grabbedby(A, 1) + var/obj/item/hand_item/grab/G = A.is_grabbing(D) + if(G) + old_grab_state = G?.current_grab.damage_stage + if(old_grab_state == GRAB_PASSIVE) + G.upgrade() + + else + A.try_make_grab(D, /datum/grab/normal/aggressive) + if(old_grab_state == GRAB_PASSIVE) D.drop_all_held_items() - A.setGrabState(GRAB_AGGRESSIVE) //Instant aggressive grab if on grab intent log_combat(A, D, "grabbed", addition="aggressively") D.visible_message(span_warning("[A] violently grabs [D]!"), \ span_userdanger("You're grabbed violently by [A]!"), span_hear("You hear sounds of aggressive fondling!"), COMBAT_MESSAGE_RANGE, A) diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm index 2c28e969a08b..32111d8c27af 100644 --- a/code/datums/mutations/hulk.dm +++ b/code/datums/mutations/hulk.dm @@ -102,10 +102,11 @@ return if(!user.throw_mode || user.get_active_held_item() || user.zone_selected != BODY_ZONE_PRECISE_GROIN) return - if(user.grab_state < GRAB_NECK || !iscarbon(user.pulling) || user.buckled || user.incapacitated()) + var/obj/item/hand_item/grab/G = user.get_active_grab() + if(G.current_grab.damage_stage < GRAB_NECK || !iscarbon(G.affecting) || user.buckled || user.incapacitated()) return - var/mob/living/carbon/possible_throwable = user.pulling + var/mob/living/carbon/possible_throwable = G.affecting if(!possible_throwable.getorganslot(ORGAN_SLOT_EXTERNAL_TAIL)) return diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm index 551260b4a11e..b5b8bfd7eec3 100644 --- a/code/game/area/areas/shuttles.dm +++ b/code/game/area/areas/shuttles.dm @@ -254,9 +254,9 @@ return var/mob/living/L = AM - if(L.pulling && istype(L.pulling, /obj/item/bodypart/head)) + if(istype(L.get_active_grab()?.affecting, /obj/item/bodypart/head)) to_chat(L, span_notice("Your offering is accepted. You may pass."), confidential = TRUE) - qdel(L.pulling) + qdel(L.get_active_grab()?.affecting) var/turf/LA = get_turf(pick(warp_points)) L.forceMove(LA) L.hallucination = 0 diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 497fb540aa2b..428047997671 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -272,10 +272,13 @@ // This is run after ALL movables have been moved, so pulls don't get broken unless they are actually out of range. if(z_move_flags & ZMOVE_CHECK_PULLS) for(var/atom/movable/moved_mov as anything in moving_movs) - if(z_move_flags & ZMOVE_CHECK_PULLEDBY && moved_mov.pulledby && (moved_mov.z != moved_mov.pulledby.z || get_dist(moved_mov, moved_mov.pulledby) > 1)) - moved_mov.pulledby.stop_pulling() + if(z_move_flags & ZMOVE_CHECK_PULLEDBY) + for(var/obj/item/hand_item/grab/G in grabbed_by) + if(G.assailant.z != moved_mov.z || get_dist(moved_mov, G.assailant) > 1) + qdel(G) + if(z_move_flags & ZMOVE_CHECK_PULLING) - moved_mov.check_pulling(TRUE) + moved_mov.recheck_grabs(TRUE) return TRUE /// Returns a list of movables that should also be affected when src moves through zlevels, and src. @@ -605,68 +608,6 @@ span_danger("[src] grabs you passively.")) return TRUE -/atom/movable/proc/stop_pulling() - if(!pulling) - return - pulling.set_pulledby(null) - setGrabState(GRAB_PASSIVE) - var/atom/movable/old_pulling = pulling - pulling = null - SEND_SIGNAL(old_pulling, COMSIG_ATOM_NO_LONGER_PULLED, src) - SEND_SIGNAL(src, COMSIG_ATOM_NO_LONGER_PULLING, old_pulling) - -///Reports the event of the change in value of the pulledby variable. -/atom/movable/proc/set_pulledby(new_pulledby) - if(new_pulledby == pulledby) - return FALSE //null signals there was a change, be sure to return FALSE if none happened here. - . = pulledby - pulledby = new_pulledby - - -/atom/movable/proc/Move_Pulled(atom/moving_atom) - if(!pulling) - return FALSE - if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src, src, pulling)) - release_grab(moving_atom) - return FALSE - if(isliving(pulling)) - var/mob/living/pulling_mob = pulling - if(pulling_mob.buckled && pulling_mob.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it - release_grab(moving_atom) - return FALSE - if(moving_atom == loc && pulling.density) - return FALSE - var/move_dir = get_dir(pulling.loc, moving_atom) - if(!Process_Spacemove(move_dir)) - return FALSE - pulling.Move(get_step(pulling.loc, move_dir), move_dir, glide_size) - return TRUE - -/mob/living/Move_Pulled(atom/moving_atom) - . = ..() - if(!. || !isliving(moving_atom)) - return - var/mob/living/pulled_mob = moving_atom - set_pull_offsets(pulled_mob, grab_state) - -/** - * Checks if the pulling and pulledby should be stopped because they're out of reach. - * If z_allowed is TRUE, the z level of the pulling will be ignored.This is to allow things to be dragged up and down stairs. - */ -/atom/movable/proc/check_pulling(only_pulling = FALSE, z_allowed = FALSE) - #warn this probably needs a fat rewrite to use grab datums - if(pulling) - if(get_dist(src, pulling) > 1 || (z != pulling.z && !z_allowed)) - stop_pulling() - else if(!isturf(loc)) - stop_pulling() - else if(pulling && !isturf(pulling.loc) && pulling.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). - log_game("DEBUG:[src]'s pull on [pulling] wasn't broken despite [pulling] being in [pulling.loc]. Pull stopped manually.") - stop_pulling() - else if(pulling.anchored || pulling.move_resist > move_force) - stop_pulling() - if(!only_pulling && pulledby && moving_diagonally != FIRST_DIAG_STEP && (get_dist(src, pulledby) > 1 || z != pulledby.z)) //separated from our puller and not in the middle of a diagonal move. - pulledby.stop_pulling() /atom/movable/proc/set_glide_size(target = 8) SEND_SIGNAL(src, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, target) @@ -777,7 +718,7 @@ var/atom/movable/pullee = pulling var/turf/current_turf = loc if(!moving_from_pull) - check_pulling(z_allowed = TRUE) + recheck_grabs(z_allowed = TRUE) if(!loc || !newloc) return FALSE var/atom/oldloc = loc @@ -866,7 +807,7 @@ if(target_turf != current_turf || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1) pulling.move_from_pull(src, target_turf, glide_size) - check_pulling() + recheck_grabs() //glide_size strangely enough can change mid movement animation and update correctly while the animation is playing //This means that if you don't override it late like this, it will just be set back by the movement update that's called when you move turfs. diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm index 4039e98965a3..7acabe89e443 100644 --- a/code/game/objects/buckling.dm +++ b/code/game/objects/buckling.dm @@ -349,6 +349,7 @@ span_notice("You unbuckle yourself from [src]."),\ span_hear("You hear metal clanking.")) add_fingerprint(user) + if(isliving(M.pulledby)) var/mob/living/L = M.pulledby L.set_pull_offsets(M, L.grab_state) diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 102d93618f8a..323db9f5c4ab 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -228,7 +228,7 @@ if(talking_carbon.handcuffed)// If we're handcuffed, we can't press the button to_chat(talking_carbon, span_warning("You can't use the radio while handcuffed!")) return ITALICS | REDUCE_RANGE - if(talking_carbon.pulledby?.grab_state) + if(talking_carbon.check_grab_severities(GRAB_AGGRESSIVE)) to_chat(talking_carbon, span_warning("You can't use the radio while aggressively grabbed!")) return ITALICS | REDUCE_RANGE diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index 6ff9e40d25d0..120eb826ab88 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -124,7 +124,7 @@ return var/obj/item/hand_item/grab/G = user.is_grabbing(target) - if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || !G || !(G.damage_stage >= GRAB_AGGRESSIVE)|| HAS_TRAIT(user, TRAIT_EXHAUSTED)) + if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || !G || !(G.current_grab.damage_stage >= GRAB_AGGRESSIVE)|| HAS_TRAIT(user, TRAIT_EXHAUSTED)) return FALSE // [user] gives [target] a [prefix_desc] noogie[affix_desc]! diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 029f9bb9825d..aea74a3ee574 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -490,8 +490,12 @@ transfer = min(transfer, round((target_stack.source.max_energy - target_stack.source.energy) / target_stack.cost)) else transfer = min(transfer, (limit ? limit : target_stack.max_amount) - target_stack.amount) - if(pulledby) - pulledby.start_pulling(target_stack) + if(LAZYLEN(grabbed_by)) + for(var/obj/item/hand_item/grab/G in grabbed_by) + var/mob/living/grabber = G.assailant + qdel(G) + grabber.try_make_grab(target_stack) + target_stack.copy_evidences(src) use(transfer, transfer = TRUE, check = FALSE) target_stack.add(transfer) diff --git a/code/game/objects/structures/plasticflaps.dm b/code/game/objects/structures/plasticflaps.dm index f446c0dbf474..273dc249229e 100644 --- a/code/game/objects/structures/plasticflaps.dm +++ b/code/game/objects/structures/plasticflaps.dm @@ -73,8 +73,12 @@ if(!ventcrawler && living_caller.mob_size != MOB_SIZE_TINY) return FALSE - if(caller?.pulling) - return CanAStarPass(ID, to_dir, caller.pulling, no_id = no_id) + if(isliving(caller)) + var/mob/living/L = caller + var/list/grabs = L.get_active_grabs() + for(var/obj/item/hand_item/grab/G in grabs) + if(!CanAStarPass(ID, to_dir, G.affecting, no_id = no_id)) + return FALSE return TRUE //diseases, stings, etc can pass diff --git a/code/game/objects/structures/transit_tubes/station.dm b/code/game/objects/structures/transit_tubes/station.dm index 134b381f6867..1d5f877b306d 100644 --- a/code/game/objects/structures/transit_tubes/station.dm +++ b/code/game/objects/structures/transit_tubes/station.dm @@ -64,38 +64,43 @@ if(.) return if(!pod_moving) - if(user.pulling && isliving(user.pulling)) - if(open_status == STATION_TUBE_OPEN) - var/mob/living/GM = user.pulling - if(user.grab_state >= GRAB_AGGRESSIVE) - if(GM.buckled || GM.has_buckled_mobs()) - to_chat(user, span_warning("[GM] is attached to something!")) - return - for(var/obj/structure/transit_tube_pod/pod in loc) - pod.visible_message(span_warning("[user] starts putting [GM] into the [pod]!")) - if(do_after(user, src, 15)) - if(open_status == STATION_TUBE_OPEN && GM && user.grab_state >= GRAB_AGGRESSIVE && user.pulling == GM && !GM.buckled && !GM.has_buckled_mobs()) - GM.Paralyze(100) - src.BumpedBy(GM) - break - else - for(var/obj/structure/transit_tube_pod/pod in loc) - if(!pod.moving && (pod.dir in tube_dirs)) - if(open_status == STATION_TUBE_CLOSED) - open_animation() - - else if(open_status == STATION_TUBE_OPEN) - if(pod.contents.len && user.loc != pod) - user.visible_message(span_notice("[user] starts emptying [pod]'s contents onto the floor."), span_notice("You start emptying [pod]'s contents onto the floor...")) - if(do_after(user, src, 10)) //So it doesn't default to close_animation() on fail - if(pod && pod.loc == loc) - for(var/atom/movable/AM in pod) - AM.forceMove(get_turf(user)) - - else - close_animation() - break + for(var/obj/structure/transit_tube_pod/pod in loc) + if(!pod.moving && (pod.dir in tube_dirs)) + if(open_status == STATION_TUBE_CLOSED) + open_animation() + + else if(open_status == STATION_TUBE_OPEN) + if(pod.contents.len && user.loc != pod) + user.visible_message(span_notice("[user] starts emptying [pod]'s contents onto the floor."), span_notice("You start emptying [pod]'s contents onto the floor...")) + if(do_after(user, src, 10)) //So it doesn't default to close_animation() on fail + if(pod && pod.loc == loc) + for(var/atom/movable/AM in pod) + AM.forceMove(get_turf(user)) + + else + close_animation() + break + +/obj/structure/transit_tube/station/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!isliving(victim) || pod_moving) + return + if(!(open_status == STATION_TUBE_OPEN)) + return + + var/mob/living/GM = victim + if(grab.current_grab.damage_level >= GRAB_AGGRESSIVE) + if(GM.buckled || GM.has_buckled_mobs()) + to_chat(user, span_warning("[GM] is attached to something!")) + return + for(var/obj/structure/transit_tube_pod/pod in loc) + pod.visible_message(span_warning("[user] starts putting [GM] into the [pod]!")) + if(do_after(user, src, 15)) + if(open_status == STATION_TUBE_OPEN && GM && grab.current_grab?.damage_level >= GRAB_AGGRESSIVE && G.is_grabbing(victim) && !GM.buckled && !GM.has_buckled_mobs()) + GM.Paralyze(100) + src.BumpedBy(GM) + break /obj/structure/transit_tube/station/attackby(obj/item/W, mob/user, params) if(W.tool_behaviour == TOOL_CROWBAR) diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index a3f945e18a62..cb8ff6d00b46 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -29,39 +29,6 @@ log_combat(user, swirlie, "swirlied (brute)") swirlie.adjustBruteLoss(5) - else if(user.pulling && isliving(user.pulling)) - user.changeNext_move(CLICK_CD_MELEE) - var/mob/living/GM = user.pulling - if(user.grab_state >= GRAB_AGGRESSIVE) - if(GM.loc != get_turf(src)) - to_chat(user, span_warning("[GM] needs to be on [src]!")) - return - if(!swirlie) - if(open) - GM.visible_message(span_danger("[user] starts to give [GM] a swirlie!"), span_userdanger("[user] starts to give you a swirlie...")) - swirlie = GM - var/was_alive = (swirlie.stat != DEAD) - if(do_after(user, src, 3 SECONDS, timed_action_flags = IGNORE_HELD_ITEM)) - GM.visible_message(span_danger("[user] gives [GM] a swirlie!"), span_userdanger("[user] gives you a swirlie!"), span_hear("You hear a toilet flushing.")) - if(iscarbon(GM)) - var/mob/living/carbon/C = GM - if(!C.internal) - log_combat(user, C, "swirlied (oxy)") - C.adjustOxyLoss(5) - else - log_combat(user, GM, "swirlied (oxy)") - GM.adjustOxyLoss(5) - if(was_alive && swirlie.stat == DEAD && swirlie.client) - swirlie.client.give_award(/datum/award/achievement/misc/swirlie, swirlie) // just like space high school all over again! - swirlie = null - else - playsound(src.loc, 'sound/effects/bang.ogg', 25, TRUE) - GM.visible_message(span_danger("[user] slams [GM.name] into [src]!"), span_userdanger("[user] slams you into [src]!")) - log_combat(user, GM, "toilet slammed") - GM.adjustBruteLoss(5) - else - to_chat(user, span_warning("You need a tighter grip!")) - else if(cistern && !open && user.CanReach(src)) if(!contents.len) to_chat(user, span_notice("The cistern is empty.")) @@ -78,6 +45,44 @@ update_appearance() +/obj/structure/toilet/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!isliving(victim)) + return + + user.changeNext_move(CLICK_CD_MELEE) + var/mob/living/GM = victim + if(grab.current_grab.damage_level >= GRAB_AGGRESSIVE) + if(GM.loc != get_turf(src)) + to_chat(user, span_warning("[GM] needs to be on [src]!")) + return + + if(!swirlie) + if(open) + GM.visible_message(span_danger("[user] starts to give [GM] a swirlie!"), span_userdanger("[user] starts to give you a swirlie...")) + swirlie = GM + var/was_alive = (swirlie.stat != DEAD) + if(do_after(user, src, 3 SECONDS, timed_action_flags = IGNORE_HELD_ITEM)) + GM.visible_message(span_danger("[user] gives [GM] a swirlie!"), span_userdanger("[user] gives you a swirlie!"), span_hear("You hear a toilet flushing.")) + if(iscarbon(GM)) + var/mob/living/carbon/C = GM + if(!C.internal) + log_combat(user, C, "swirlied (oxy)") + C.adjustOxyLoss(5) + else + log_combat(user, GM, "swirlied (oxy)") + GM.adjustOxyLoss(5) + if(was_alive && swirlie.stat == DEAD && swirlie.client) + swirlie.client.give_award(/datum/award/achievement/misc/swirlie, swirlie) // just like space high school all over again! + swirlie = null + else + playsound(src.loc, 'sound/effects/bang.ogg', 25, TRUE) + GM.visible_message(span_danger("[user] slams [GM.name] into [src]!"), span_userdanger("[user] slams you into [src]!")) + log_combat(user, GM, "toilet slammed") + GM.adjustBruteLoss(5) + else + to_chat(user, span_warning("You need a tighter grip!")) + /obj/structure/toilet/update_icon_state() icon_state = "toilet[open][cistern]" return ..() @@ -169,19 +174,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/urinal, 32) . = ..() if(.) return - if(user.pulling && isliving(user.pulling)) - var/mob/living/GM = user.pulling - if(user.grab_state >= GRAB_AGGRESSIVE) - if(GM.loc != get_turf(src)) - to_chat(user, span_notice("[GM.name] needs to be on [src].")) - return - user.changeNext_move(CLICK_CD_MELEE) - user.visible_message(span_danger("[user] slams [GM] into [src]!"), span_danger("You slam [GM] into [src]!")) - GM.adjustBruteLoss(8) - else - to_chat(user, span_warning("You need a tighter grip!")) - else if(exposed) + if(exposed) if(!hiddenitem) to_chat(user, span_warning("There is nothing in the drain holder!")) else @@ -194,6 +188,23 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/urinal, 32) else ..() +/obj/structure/urinal/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!isliving(victim)) + return + + var/mob/living/GM = victim + + if(grab.current_grab.damage_level >= GRAB_AGGRESSIVE) + if(GM.loc != get_turf(src)) + to_chat(user, span_notice("[GM.name] needs to be on [src].")) + return + user.changeNext_move(CLICK_CD_MELEE) + user.visible_message(span_danger("[user] slams [GM] into [src]!"), span_danger("You slam [GM] into [src]!")) + GM.adjustBruteLoss(8) + else + to_chat(user, span_warning("You need a tighter grip!")) + /obj/structure/urinal/attackby(obj/item/I, mob/living/user, params) if(exposed) if (hiddenitem) diff --git a/code/game/turfs/closed/wall/misc_walls.dm b/code/game/turfs/closed/wall/misc_walls.dm index 4d1f430736ed..0aff692c94c4 100644 --- a/code/game/turfs/closed/wall/misc_walls.dm +++ b/code/game/turfs/closed/wall/misc_walls.dm @@ -16,11 +16,11 @@ . = ..() if(istype(gone, /mob/living/simple_animal/hostile/construct/harvester)) //harvesters can go through cult walls, dragging something with var/mob/living/simple_animal/hostile/construct/harvester/H = gone - var/atom/movable/stored_pulling = H.pulling + var/atom/movable/stored_pulling = H.get_active_grab() if(stored_pulling) stored_pulling.setDir(direction) stored_pulling.forceMove(src) - H.start_pulling(stored_pulling, supress_message = TRUE) + H.try_make_grab(stored_pulling, supress_message = TRUE) /turf/closed/wall/mineral/cult/artificer name = "runed stone wall" diff --git a/code/game/turfs/open/floor/reinforced_floor.dm b/code/game/turfs/open/floor/reinforced_floor.dm index 92f717979c58..94efec82cb20 100644 --- a/code/game/turfs/open/floor/reinforced_floor.dm +++ b/code/game/turfs/open/floor/reinforced_floor.dm @@ -92,7 +92,7 @@ . = ..() if(.) return - user.Move_Pulled(src) + user.move_grabbed_atoms_towards(src) //air filled floors; used in atmos pressure chambers diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm index 21ecdc3ae84f..1292ab514b7c 100644 --- a/code/game/turfs/open/space/space.dm +++ b/code/game/turfs/open/space/space.dm @@ -123,7 +123,7 @@ GLOBAL_REAL_VAR(space_appearances) = make_space_appearances() if(!arrived || src != arrived.loc) return - if(destination_z && destination_x && destination_y && !arrived.pulledby && !arrived.currently_z_moving) + if(destination_z && destination_x && destination_y && !LAZYLEN(arrived.grabbed_by) && !arrived.currently_z_moving) var/tx = destination_x var/ty = destination_y var/turf/DT = locate(tx, ty, destination_z) @@ -146,11 +146,12 @@ GLOBAL_REAL_VAR(space_appearances) = make_space_appearances() return arrived.zMove(null, DT, ZMOVE_ALLOW_BUCKLED) - var/atom/movable/current_pull = arrived.pulling - while (current_pull) - var/turf/target_turf = get_step(current_pull.pulledby.loc, REVERSE_DIR(current_pull.pulledby.dir)) || current_pull.pulledby.loc - current_pull.zMove(null, target_turf, ZMOVE_ALLOW_BUCKLED) - current_pull = current_pull.pulling + if(isliving(arrived)) + var/mob/living/L = arrived + for(var/obj/item/hand_item/grab/G as anything in L.get_active_grabs()) + var/atom/movable/current_pull = G.affecting + var/turf/target_turf = get_step(G.assailant.loc, REVERSE_DIR(G.assailant.dir)) || G.assailant.loc + current_pull.zMove(null, target_turf, ZMOVE_ALLOW_BUCKLED) /turf/open/space/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 3ac29d7cbd12..fcd9878af2f7 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -205,7 +205,7 @@ GLOBAL_LIST_EMPTY(station_turfs) . = ..() if(.) return - user.Move_Pulled(src) + user.move_grabbed_atoms_towards(src) /** * Check whether the specified turf is blocked by something dense inside it with respect to a specific atom. @@ -303,21 +303,26 @@ GLOBAL_LIST_EMPTY(station_turfs) if(!(flags & FALL_INTERCEPTED) && falling.zFall(levels + 1)) return FALSE - for(var/atom/movable/falling_mob as anything in falling_movables) + for(var/atom/movable/falling_movable as anything in falling_movables) + var/mob/living/L = falling_movable + if(!isliving(L)) + L = null if(!(flags & FALL_RETAIN_PULL)) - falling_mob.release_all_grabs() + L?.release_all_grabs() if(!(flags & FALL_INTERCEPTED)) - falling_mob.onZImpact(src, levels) + falling_movable.onZImpact(src, levels) #ifndef ZMIMIC_MULTIZ_SPEECH //Multiz speech handles this otherwise if(!(flags & FALL_NO_MESSAGE)) prev_turf.audible_message(span_hear("You hear something slam into the deck below.")) #endif - #warn FUUUUUCk - if(falling_mob.pulledby && (falling_mob.z != falling_mob.pulledby.z || get_dist(falling_mob, falling_mob.pulledby) > 1)) - falling_mob.pulledby.stop_pulling() + if(L) + if(LAZYLEN(L.grabbed_by)) + for(var/obj/item/hand_item/grab/G in L.grabbed_by) + if(L.z != G.assailant.z || get_dist(L, G.assailant.z > 1)) + qdel(G) return TRUE /turf/proc/handleRCL(obj/item/rcl/C, mob/user) diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm index 2846e693ca99..83efdc1e59a6 100644 --- a/code/modules/antagonists/changeling/powers/absorb.dm +++ b/code/modules/antagonists/changeling/powers/absorb.dm @@ -16,21 +16,21 @@ to_chat(owner, span_warning("We are already absorbing!")) return - var/obj/item/hand_item/grab/G = owner.get_active_grabs()?[1] + var/obj/item/hand_item/grab/G = owner.get_active_grab() if(!G) to_chat(owner, span_warning("We must be grabbing a creature to absorb them!")) return - if(!G.can_absorb) + if(!G.current_grab.can_absorb) to_chat(owner, span_warning("We must have a tighter grip to absorb this creature!")) return - var/mob/living/carbon/target = owner.pulling + var/mob/living/carbon/target = G.affecting var/datum/antagonist/changeling/changeling = owner.mind.has_antag_datum(/datum/antagonist/changeling) return changeling.can_absorb_dna(target) -/datum/action/changeling/absorb_dna/sting_action(mob/owner) +/datum/action/changeling/absorb_dna/sting_action(mob/living/owner) var/datum/antagonist/changeling/changeling = owner.mind.has_antag_datum(/datum/antagonist/changeling) - var/mob/living/carbon/human/target = owner.pulling + var/mob/living/carbon/human/target = owner.get_active_grab()?.affecting is_absorbing = TRUE if(!attempt_absorb(target)) diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm index b15ef9498ffd..dc120f31da77 100644 --- a/code/modules/antagonists/changeling/powers/mutations.dm +++ b/code/modules/antagonists/changeling/powers/mutations.dm @@ -326,8 +326,8 @@ H.swap_hand() if(H.get_active_held_item()) return - C.grabbedby(H) - C.grippedby(H, instant = TRUE) //instant aggro grab + + H.try_make_grab(C, /datum/grab/normal/aggressive) /obj/projectile/tentacle/proc/tentacle_stab(mob/living/carbon/human/H, mob/living/carbon/C) if(H.Adjacent(C)) diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 892d8a408090..1cb5eeb90353 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -702,11 +702,19 @@ structure_check() searches for nearby cultist structures required for the invoca fail_invoke() log_game("Summon Cultist rune failed - target died") return - if(cultist_to_summon.pulledby || cultist_to_summon.buckled) - to_chat(user, "[cultist_to_summon] is being held in place!") - fail_invoke() - log_game("Summon Cultist rune failed - target restrained") - return + if(LAZYLEN(cultist_to_summon.grabbed_by) || cultist_to_summon.buckled) + var/grab_check = 0 + if(!cultist_to_summon.buckled) + for(var/obj/item/hand_item/grab/G in cultist_to_summon.grabbed_by) + if(G.current_grab.stop_move) + grab_check++ + + if(grab_check == 0) + to_chat(user, "[cultist_to_summon] is being held in place!") + fail_invoke() + log_game("Summon Cultist rune failed - target restrained") + return + if(!IS_CULTIST(cultist_to_summon)) to_chat(user, "[cultist_to_summon] is not a follower of the Geometer!") fail_invoke() diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm index 27f372762cb0..e57a962d4644 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm @@ -740,7 +740,7 @@ This is here to make the tiles around the station mininuke change when it's arme if (last_secured_location == get_turf(src)) return FALSE - var/mob/holder = pulledby || get(src, /mob) + var/mob/holder = get(src, /mob) if (isnull(holder?.client)) return FALSE diff --git a/code/modules/atmospherics/ZAS/Airflow.dm b/code/modules/atmospherics/ZAS/Airflow.dm index 9accdb2b38aa..b97dbf3a3798 100644 --- a/code/modules/atmospherics/ZAS/Airflow.dm +++ b/code/modules/atmospherics/ZAS/Airflow.dm @@ -36,8 +36,6 @@ This entire system is an absolute mess. /mob/living/airflow_stun(delta_p) if(stat == 2) return FALSE - if(pulledby || pulling) - return FALSE if(last_airflow_stun > world.time - zas_settings.airflow_stun_cooldown) return FALSE if(!(status_flags & CANSTUN) && !(status_flags & CANKNOCKDOWN)) diff --git a/code/modules/awaymissions/signpost.dm b/code/modules/awaymissions/signpost.dm index 8acdc5076472..3ee162c24e4d 100644 --- a/code/modules/awaymissions/signpost.dm +++ b/code/modules/awaymissions/signpost.dm @@ -20,9 +20,14 @@ var/turf/T = find_safe_turf(zlevels=zlevels) if(T) - var/list/grabbing= user.get_all_grabbed_movables() - for(var/atom/movable/AM as anything in grabbing) - AM.forceMove(T) + var/atom/movable/preserve_grab + if(isliving(user)) + var/mob/living/L = user + var/list/grabbing= user.get_all_grabbed_movables() + for(var/atom/movable/AM as anything in grabbing) + preserve_grab ||= AM + AM.forceMove(T) + user.forceMove(T) if(AM) user.try_make_grab(AM) diff --git a/code/modules/food_and_drinks/kitchen_machinery/processor.dm b/code/modules/food_and_drinks/kitchen_machinery/processor.dm index 0606ab7d7b86..ef18baef5a6f 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/processor.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/processor.dm @@ -118,16 +118,6 @@ if(processing) to_chat(user, span_warning("[src] is in the process of processing!")) return TRUE - if(ismob(user.pulling) && PROCESSOR_SELECT_RECIPE(user.pulling)) - if(user.grab_state < GRAB_AGGRESSIVE) - to_chat(user, span_warning("You need a better grip to do that!")) - return - var/mob/living/pushed_mob = user.pulling - visible_message(span_warning("[user] stuffs [pushed_mob] into [src]!")) - pushed_mob.forceMove(src) - LAZYADD(processor_contents, pushed_mob) - user.release_all_grabs() - return if(!LAZYLEN(processor_contents)) to_chat(user, span_warning("[src] is empty!")) @@ -158,6 +148,23 @@ processing = FALSE visible_message(span_notice("\The [src] finishes processing.")) +/obj/machinery/processor/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(processing) + to_chat(user, span_warning("[src] is in the process of processing!")) + return TRUE + + if(ismobvictim && PROCESSOR_SELECT_RECIPE(victim)) + if(grab.current_grab.damage_stage < GRAB_AGGRESSIVE) + to_chat(user, span_warning("You need a better grip to do that!")) + return + var/mob/living/pushed_mob = victim + visible_message(span_warning("[user] stuffs [pushed_mob] into [src]!")) + qdel(grab) + pushed_mob.forceMove(src) + LAZYADD(processor_contents, pushed_mob) + return + /obj/machinery/processor/verb/eject() set category = "Object" set name = "Eject Contents" diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index 96ca9ae35446..19eb47a4b1b9 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -121,8 +121,8 @@ GLOBAL_LIST_EMPTY(all_grabstates) /datum/grab/proc/let_go(obj/item/hand_item/grab/G) if (G) let_go_effect(G) - if(!QDELETED(G)) - qdel(G) + G.current_grab = null + qdel(G) /datum/grab/proc/on_target_change(obj/item/hand_item/grab/G, old_zone, new_zone) G.special_target_functional = check_special_target(G) @@ -247,25 +247,27 @@ GLOBAL_LIST_EMPTY(all_grabstates) // What happens when you upgrade from one grab state to the next. /datum/grab/proc/upgrade_effect(obj/item/hand_item/grab/G, datum/grab/old_grab) - update_grab_effects(old_grab) + update_grab_effects(G, old_grab) // Conditions to see if upgrading is possible /datum/grab/proc/can_upgrade(obj/item/hand_item/grab/G) if(!upgrab) return FALSE - if(!(G.affecting.status_flags & CANPUSH) || HAS_TRAIT(G.affecting, TRAIT_PUSHIMMUNE)) - to_chat(user, span_warning("[src] can't be grabbed more aggressively!")) - return FALSE + if(isliving(G.affecting)) + var/mob/living/L = G.affecting + if(!(L.status_flags & CANPUSH) || HAS_TRAIT(L, TRAIT_PUSHIMMUNE)) + to_chat(G.assailant, span_warning("[src] can't be grabbed more aggressively!")) + return FALSE if(upgrab.damage_stage >= GRAB_AGGRESSIVE && HAS_TRAIT(G.assailant, TRAIT_PACIFISM)) - to_chat(user, span_warning("You don't want to risk hurting [src]!")) + to_chat(G.assailant, span_warning("You don't want to risk hurting [src]!")) return FALSE return TRUE // What happens when you downgrade from one grab state to the next. /datum/grab/proc/downgrade_effect(obj/item/hand_item/grab/G, datum/grab/old_grab) - update_grab_effects(old_grab) + update_grab_effects(G, old_grab) // Conditions to see if downgrading is possible /datum/grab/proc/can_downgrade(obj/item/hand_item/grab/G) @@ -276,12 +278,12 @@ GLOBAL_LIST_EMPTY(all_grabstates) /datum/grab/proc/let_go_effect(obj/item/hand_item/grab/G) SEND_SIGNAL(G.affecting, COMSIG_ATOM_NO_LONGER_GRABBED, G.assailant) SEND_SIGNAL(G.assailant, COMSIG_ATOM_NO_LONGER_GRABBING, G.affecting) - update_grab_effects(null) + update_grab_effects(G, null, TRUE) -/datum/grab/proc/update_grab_effects(datum/grab/old_grab) +/datum/grab/proc/update_grab_effects(obj/item/hand_item/grab/G, datum/grab/old_grab, dropping_grab) var/old_damage_stage = old_grab?.damage_stage || 0 - switch(damage_stage) // Current state. + switch(!dropping_grab || damage_stage) // Current state. if(GRAB_PASSIVE) REMOVE_TRAIT(G.affecting, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) REMOVE_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT) diff --git a/code/modules/grab/grab_helpers.dm b/code/modules/grab/grab_helpers.dm index 890a4f396758..30723334d2bd 100644 --- a/code/modules/grab/grab_helpers.dm +++ b/code/modules/grab/grab_helpers.dm @@ -21,6 +21,23 @@ if(G.affecting == AM) qdel(G) +/// Returns the currently selected grab item +/mob/living/proc/get_active_grab() + RETURN_TYPE(/obj/item/hand_item/grab) + var/obj/item/hand_item/grab/G = locate() in src + return G + +/mob/living/carbon/get_active_grab() + RETURN_TYPE(/obj/item/hand_item/grab) + var/item = get_active_held_item() + if(isgrab(item)) + return item + return ..() + +/// Releases the currently selected grab item +/mob/living/proc/release_active_grab() + qdel(get_active_grab()) + /// Returns a list of every movable we are grabbing /mob/living/proc/get_all_grabbed_movables() . = list() @@ -35,6 +52,32 @@ /// Frees src from all grabs. /atom/movable/proc/free_from_all_grabs() + QDEL_LIST(grabbed_by) + grabbed_by = null + +/// Gets every grabber of this atom, and every grabber of those grabbers, repeat +/atom/movable/proc/recursively_get_all_grabbers() + RETURN_TYPE(/list) + . = list() for(var/obj/item/hand_item/grab/G in grabbed_by) - qdel(G) + . |= G.assailant + . |= G.assailant.recursively_get_all_grabbers() + +/// Gets every grabbed atom of this mob, and every grabbed atom of that grabber, repeat +/mob/living/proc/recursively_get_all_grabbed_movables() + RETURN_TYPE(/list) + . = list() + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + . |= G.affecting + if(isliving(G.affecting)) + var/mob/living/L = G.affecting + . |= L.recursively_get_all_grabbed_movables() + +/// Get every single member of a grab chain +/atom/movable/proc/get_all_grab_chain_members() + RETURN_TYPE(/list) + return recursively_get_all_grabbers() +/mob/living/get_all_grab_chain_members() + . = ..() + . |= recursively_get_all_grabbed_movables() diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index 31dad89f7b97..4d2b550e4655 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -1,13 +1,14 @@ -/mob/living/proc/can_grab(atom/movable/target, vtarget_zone) +/mob/living/proc/can_grab(atom/movable/target, target_zone, defer_hand) if(!ismob(target) && target.anchored) to_chat(src, span_warning("\The [target] won't budge!")) return FALSE if(defer_hand) if(!get_empty_held_index()) - to_chat(src, SPAN_WARNING("Your hands are full!")) + to_chat(src, span_warning("Your hands are full!")) return FALSE - else if(get_active_hand()) + + else if(get_active_held_item()) to_chat(src, span_warning("Your [get_active_hand().plaintext_zone] is full!")) return FALSE @@ -28,7 +29,18 @@ return FALSE return TRUE -/mob/living/proc/make_grab(atom/movable/target, grab_type = /datum/grab/simple) +/mob/living/proc/can_be_grabbed(mob/living/grabber, target_zone, force) + . = ..() + if(!.) + return + if((buckled?.buckle_prevents_pull)) + return FALSE + +/// Attempt to create a grab, returns TRUE on success +/mob/living/proc/try_make_grab(atom/movable/target, grab_type) + return canUseTopic(src, USE_IGNORE_TK|USE_CLOSE) && make_grab(target, grab_type) + +/mob/living/proc/make_grab(atom/movable/target, grab_type = /datum/grab/simple, defer_hand) if(SEND_SIGNAL(src, COMSIG_LIVING_TRY_GRAB, target, grab_type) & COMSIG_LIVING_CANCEL_GRAB) return @@ -49,8 +61,8 @@ face_atom(target) var/obj/item/hand_item/grab/grab - if(ispath(grab_type, /datum/grab) && can_grab(target, get_target_zone(), defer_hand = defer_hand) && target.can_be_grabbed(src, get_target_zone(), defer_hand)) - grab = new /obj/item/hand_item/grab(src, target, grab_tag, defer_hand) + if(ispath(grab_type, /datum/grab) && can_grab(target, zone_selected, defer_hand = defer_hand) && target.can_be_grabbed(src, zone_selected, defer_hand)) + grab = new /obj/item/hand_item/grab(src, target, grab_type, defer_hand) if(QDELETED(grab)) @@ -60,12 +72,12 @@ return null - SEND_SIGNAL(src, COMSIG_LIVING_START_GRAB, src, grab) + SEND_SIGNAL(src, COMSIG_LIVING_START_GRAB, target, grab) SEND_SIGNAL(target, COMSIG_ATOM_GET_GRABBED, src, grab) return grab -/mob/living/add_grab(obj/item/hand_item/grab/grab) +/mob/living/add_grab(obj/item/hand_item/grab/grab, defer_hand) for(var/obj/item/hand_item/grab/other_grab in contents) if(other_grab != grab) return FALSE @@ -75,3 +87,18 @@ /mob/living/ProcessGrabs() if(LAZYLEN(grabbed_by)) resist() + +/mob/living/recheck_grabs(only_pulling = FALSE, z_allowed = FALSE) + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + var/atom/movable/pulling = G.affecting + if(get_dist(src, pulling) > 1 || (z != pulling.z && !z_allowed)) + qdel(G) + else if(!isturf(loc)) + qdel(G) + else if(pulling && !isturf(pulling.loc) && pulling.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). + log_game("DEBUG:[src]'s pull on [pulling] wasn't broken despite [pulling] being in [pulling.loc]. Pull stopped manually.") + qdel(G) + else if(pulling.anchored || pulling.move_resist > move_force) + qdel(G) + + return ..() diff --git a/code/modules/grab/grab_movable.dm b/code/modules/grab/grab_movable.dm index 71755217595d..fd24ddd701d2 100644 --- a/code/modules/grab/grab_movable.dm +++ b/code/modules/grab/grab_movable.dm @@ -1,11 +1,7 @@ -/// Attempt to create a grab, returns TRUE on success -/atom/movable/proc/try_make_grab(atom/movable/target, grab_type) - return canUseTopic(src, USE_IGNORE_TK|USE_CLOSE) && make_grab(target, grab_type) - /atom/movable/proc/can_be_grabbed(mob/living/grabber, target_zone, force) if(!istype(grabber) || !isturf(loc) || !isturf(grabber.loc)) return FALSE - if(SEND_SIGNAL(src, COMSIG_ATOM_CAN_BE_GRABBED, grabber) & COMSIG_ATOM_CANT_PULL) + if(SEND_SIGNAL(src, COMSIG_ATOM_CAN_BE_GRABBED, grabber) & COMSIG_ATOM_NO_GRAB) return FALSE if(!grabber.canUseTopic(src, USE_CLOSE|USE_IGNORE_TK)) return FALSE @@ -21,10 +17,52 @@ return TRUE /atom/movable/proc/buckled_grab_check(var/mob/grabber) - if(grabber.buckled == src && buckled_mob == grabber) + if(grabber.buckled == src && (grabber in buckled_mobs)) return TRUE if(grabber.anchored) return FALSE if(grabber.buckled) return FALSE return TRUE + +/** + * Checks if the pulling and pulledby should be stopped because they're out of reach. + * If z_allowed is TRUE, the z level of the pulling will be ignored.This is to allow things to be dragged up and down stairs. + */ +/atom/movable/proc/recheck_grabs(only_pulling = FALSE, z_allowed = FALSE) + if(only_pulling) + return + + for(var/obj/item/hand_item/grab/G in grabbed_by) + if(moving_diagonally != FIRST_DIAG_STEP && (get_dist(src, G.assailant) > 1 || z != G.assailant.z)) //separated from our puller and not in the middle of a diagonal move. + qdel(G) + +/// Move grabbed atoms towards a destination +/mob/living/proc/move_grabbed_atoms_towards(atom/destination) + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + var/atom/movable/pulling = G.affecting + if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src, src, pulling)) + qdel(G) + continue + + if(isliving(pulling)) + var/mob/living/pulling_mob = pulling + if(pulling_mob.buckled && pulling_mob.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it + qdel(G) + continue + + if(destination == loc && pulling.density) + continue + + var/move_dir = get_dir(pulling.loc, destination) + if(!Process_Spacemove(move_dir)) + continue + + // At this point the move was successful + pulling.Move(get_step(pulling.loc, move_dir), move_dir, glide_size) + + if(!isliving(pulling)) + continue + + var/mob/living/pulled_mob = pulling + set_pull_offsets(pulled_mob) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 8967276fd66b..f8a607d1540f 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -54,6 +54,7 @@ RegisterSignal(assailant, COMSIG_MOVABLE_MOVED, PROC_REF(relay_user_move)) /obj/item/hand_item/grab/Destroy() + current_grab?.let_go(src) assailant = null affecting = null return ..() @@ -159,7 +160,7 @@ playsound(affecting.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) update_appearance() - current_grab.update_grab_effects(null) + current_grab.update_grab_effects(src, null) return TRUE // Returns the bodypart of the grabbed person that the grabber is targeting diff --git a/code/modules/grab/grabs/grab_carbon.dm b/code/modules/grab/grabs/grab_carbon.dm index c05ee4719251..a604143c71c9 100644 --- a/code/modules/grab/grabs/grab_carbon.dm +++ b/code/modules/grab/grabs/grab_carbon.dm @@ -1,7 +1,7 @@ /mob/living/carbon/get_active_grabs() . = list() - for(var/obj/item/hand_item/grab/grab in get_held_items()) + for(var/obj/item/hand_item/grab/grab in held_items) . += grab -/mob/living/carbon/make_grab(atom/movable/target,grab_type) - . = ..(target, species?.grab_type || grab_type) +/mob/living/carbon/make_grab(atom/movable/target, grab_type = /datum/grab/normal) + . = ..() diff --git a/code/modules/grab/grabs/grab_simple.dm b/code/modules/grab/grabs/grab_simple.dm index 912d672209ab..6c6ff193022c 100644 --- a/code/modules/grab/grabs/grab_simple.dm +++ b/code/modules/grab/grabs/grab_simple.dm @@ -1,5 +1,4 @@ /datum/grab/simple - name = "simple grab" shift = 8 stop_move = FALSE reverse_facing = FALSE @@ -9,17 +8,17 @@ icon_state = "1" break_chance_table = list(15, 60, 100) -/datum/grab/simple/upgrade(obj/item/grab/G) +/datum/grab/simple/upgrade(obj/item/hand_item/grab/G) return -/datum/grab/simple/on_hit_disarm(var/obj/item/grab/G, var/atom/A) +/datum/grab/simple/on_hit_disarm(var/obj/item/hand_item/grab/G, var/atom/A) return FALSE -/datum/grab/simple/on_hit_grab(var/obj/item/grab/G, var/atom/A) +/datum/grab/simple/on_hit_grab(var/obj/item/hand_item/grab/G, var/atom/A) return FALSE -/datum/grab/simple/on_hit_harm(var/obj/item/grab/G, var/atom/A) +/datum/grab/simple/on_hit_harm(var/obj/item/hand_item/grab/G, var/atom/A) return FALSE -/decl/grab/simple/resolve_openhand_attack(var/obj/item/grab/G) +/datum/grab/simple/resolve_openhand_attack(var/obj/item/hand_item/grab/G) return FALSE diff --git a/code/modules/grab/human_grab.dm b/code/modules/grab/human_grab.dm index b715128c44c2..6cb029f25874 100644 --- a/code/modules/grab/human_grab.dm +++ b/code/modules/grab/human_grab.dm @@ -1,4 +1,4 @@ -/mob/living/carbon/human/add_grab(var/obj/item/grab/grab, defer_hand = FALSE) +/mob/living/carbon/human/add_grab(var/obj/item/hand_item/grab/grab, defer_hand = FALSE) if(defer_hand) . = put_in_inactive_hand(grab) else @@ -10,7 +10,7 @@ return var/obj/item/bodypart/BP = get_bodypart(deprecise_zone(target_zone)) - if(!istype(organ)) + if(!istype(BP)) to_chat(grabber, span_warning("\The [src] is missing that body part!")) return FALSE @@ -21,13 +21,16 @@ return FALSE if(using_slot == BP) - to_chat(src, span_warning("You can't grab your own [organ.name] with itself!")) + to_chat(src, span_warning("You can't grab your own [BP.plaintext_zone] with itself!")) return FALSE - + /* if(pull_damage()) to_chat(grabber, span_warning("Pulling \the [src] in their current condition would probably be a bad idea.")) + */ - var/obj/item/clothing/C = get_covering_equipped_item_by_zone(target_zone) + var/obj/item/clothing/C = get_item_covering_zone(target_zone) if(istype(C)) C.add_fingerprint(grabber) + else + C.add_fingerprint(grabber) diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm index 5e9f39259835..3469d4d706c8 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm @@ -40,8 +40,11 @@ GLOBAL_LIST_INIT(strippable_alien_humanoid_items, create_strippable_list(list( /mob/living/carbon/alien/humanoid/resist_grab(moving_resist) if(LAZYLEN(grabbed_by)) - visible_message(span_danger("[src] breaks free of [pulledby]'s grip!"), \ - span_danger("You break free of [pulledby]'s grip!")) + for(var/obj/item/hand_item/grab/G in grabbed_by) + visible_message( + span_danger("[src] breaks free of [G.assailant]'s grip!"), + span_danger("You break free of [G.assailant]'s grip!") + ) free_from_all_grabs() . = 0 diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 14b8c07cdfbe..5ed5a786fd29 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -363,10 +363,9 @@ if(undergoing_cardiac_arrest()) set_heartattack(FALSE) var/list/shocking_queue = list() - if(iscarbon(pulling) && source != pulling) - shocking_queue += pulling - if(iscarbon(pulledby) && source != pulledby) - shocking_queue += pulledby + shocking_queue += get_all_grabbed_movables() + shocking_queue -= source + if(iscarbon(buckled) && source != buckled) shocking_queue += buckled for(var/mob/living/carbon/carried in buckled_mobs) diff --git a/code/modules/mob/living/carbon/human/human_stripping.dm b/code/modules/mob/living/carbon/human/human_stripping.dm index b43915faf779..b0dd29eefbd6 100644 --- a/code/modules/mob/living/carbon/human/human_stripping.dm +++ b/code/modules/mob/living/carbon/human/human_stripping.dm @@ -28,7 +28,8 @@ GLOBAL_LIST_INIT(strippable_human_items, create_strippable_list(list( if (!.) return FALSE - if (user.grab_state != GRAB_AGGRESSIVE) + var/obj/item/hand_item/grab/G = user.is_grabbing(src) + if (G && G.current_grab.damage_stage != GRAB_AGGRESSIVE) return TRUE if (ishuman(user)) diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index 6e1930f90e96..da26471014dc 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -746,11 +746,13 @@ return TRUE /datum/action/innate/link_minds/Activate() - if(!isliving(owner.pulling) || owner.grab_state < GRAB_AGGRESSIVE) + var/mob/living/L = owner + var/obj/item/hand_item/grab/G = L.get_active_grab() + if(!isliving(G?.affecting) || G.current_grab.damage_stage < GRAB_AGGRESSIVE) to_chat(owner, span_warning("You need to aggressively grab someone to link minds!")) return - var/mob/living/living_target = owner.pulling + var/mob/living/living_target = G.affecting if(living_target.stat == DEAD) to_chat(owner, span_warning("They're dead!")) return @@ -779,11 +781,9 @@ /datum/action/innate/link_minds/proc/while_link_callback(mob/living/linkee) if(!is_species(owner, req_species)) return FALSE - if(!owner.pulling) - return FALSE - if(owner.pulling != linkee) - return FALSE - if(owner.grab_state < GRAB_AGGRESSIVE) + var/mob/living/L = owner + var/obj/item/hand_item/grab/G = L.is_grabbing(linkee) + if(!G || G.current_grab.damage_stage < GRAB_AGGRESSIVE) return FALSE if(linkee.stat == DEAD) return FALSE diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index 92a96b6c7e7e..650b8ea0edb7 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -176,37 +176,48 @@ if(!COOLDOWN_FINISHED(V, drain_cooldown)) to_chat(H, span_warning("You just drained blood, wait a few seconds!")) return - if(H.pulling && iscarbon(H.pulling)) - var/mob/living/carbon/victim = H.pulling - if(H.blood_volume >= BLOOD_VOLUME_MAXIMUM) - to_chat(H, span_warning("You're already full!")) - return - if(victim.stat == DEAD) - to_chat(H, span_warning("You need a living victim!")) - return - if(!victim.blood_volume || (victim.dna && ((NOBLOOD in victim.dna.species.species_traits) || victim.dna.species.exotic_blood))) - to_chat(H, span_warning("[victim] doesn't have blood!")) - return - COOLDOWN_START(V, drain_cooldown, 3 SECONDS) - if(victim.can_block_magic(MAGIC_RESISTANCE_HOLY, charge_cost = 0)) - victim.show_message(span_warning("[H] tries to bite you, but stops before touching you!")) - to_chat(H, span_warning("[victim] is blessed! You stop just in time to avoid catching fire.")) - return - if(victim.has_reagent(/datum/reagent/consumable/garlic)) - victim.show_message(span_warning("[H] tries to bite you, but recoils in disgust!")) - to_chat(H, span_warning("[victim] reeks of garlic! you can't bring yourself to drain such tainted blood.")) - return - if(!do_after(H, victim, 3 SECONDS)) - return - var/blood_volume_difference = BLOOD_VOLUME_MAXIMUM - H.blood_volume //How much capacity we have left to absorb blood - var/drained_blood = min(victim.blood_volume, VAMP_DRAIN_AMOUNT, blood_volume_difference) - victim.show_message(span_danger("[H] is draining your blood!")) - to_chat(H, span_notice("You drain some blood!")) - playsound(H, 'sound/items/drink.ogg', 30, TRUE, -2) - victim.blood_volume = clamp(victim.blood_volume - drained_blood, 0, BLOOD_VOLUME_MAXIMUM) - H.blood_volume = clamp(H.blood_volume + drained_blood, 0, BLOOD_VOLUME_MAXIMUM) - if(!victim.blood_volume) - to_chat(H, span_notice("You finish off [victim]'s blood supply.")) + var/obj/item/hand_item/grab/G = H.get_active_grab() + if(!iscarbon(G?.affecting)) + return + + var/mob/living/carbon/victim = G.affecting + if(H.blood_volume >= BLOOD_VOLUME_MAXIMUM) + to_chat(H, span_warning("You're already full!")) + return + + if(victim.stat == DEAD) + to_chat(H, span_warning("You need a living victim!")) + return + + if(!victim.blood_volume || (victim.dna && ((NOBLOOD in victim.dna.species.species_traits) || victim.dna.species.exotic_blood))) + to_chat(H, span_warning("[victim] doesn't have blood!")) + return + + COOLDOWN_START(V, drain_cooldown, 3 SECONDS) + if(victim.can_block_magic(MAGIC_RESISTANCE_HOLY, charge_cost = 0)) + victim.show_message(span_warning("[H] tries to bite you, but stops before touching you!")) + to_chat(H, span_warning("[victim] is blessed! You stop just in time to avoid catching fire.")) + return + + if(victim.has_reagent(/datum/reagent/consumable/garlic)) + victim.show_message(span_warning("[H] tries to bite you, but recoils in disgust!")) + to_chat(H, span_warning("[victim] reeks of garlic! you can't bring yourself to drain such tainted blood.")) + return + + if(!do_after(H, victim, 3 SECONDS)) + return + + var/blood_volume_difference = BLOOD_VOLUME_MAXIMUM - H.blood_volume //How much capacity we have left to absorb blood + var/drained_blood = min(victim.blood_volume, VAMP_DRAIN_AMOUNT, blood_volume_difference) + + victim.show_message(span_danger("[H] is draining your blood!")) + to_chat(H, span_notice("You drain some blood!")) + playsound(H, 'sound/items/drink.ogg', 30, TRUE, -2) + + victim.blood_volume = clamp(victim.blood_volume - drained_blood, 0, BLOOD_VOLUME_MAXIMUM) + H.blood_volume = clamp(H.blood_volume + drained_blood, 0, BLOOD_VOLUME_MAXIMUM) + if(!victim.blood_volume) + to_chat(H, span_notice("You finish off [victim]'s blood supply.")) /obj/item/organ/heart/vampire name = "vampire heart" diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index ef85fa68fa9c..60ba0d7ad520 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -83,7 +83,7 @@ losebreath = max(2, losebreath + 1) else if(!getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - if((check_grab_severity(GRAB_NECK)) || (lungs?.organ_flags & ORGAN_DEAD)) + if((check_grab_severities(GRAB_NECK)) || (lungs?.organ_flags & ORGAN_DEAD)) losebreath++ //You can't breath at all when in critical or when being choked, so you're going to miss a breath // Recover from breath loss diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index f3125a53d4b0..3e341ba8bc28 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -142,7 +142,7 @@ if(length(grabs)) for(var/obj/item/hand_item/grab/G in grabs) if(ismob(G.affecting)) - var/mob/P = L.pulling + var/mob/P = G.affecting if(HAS_TRAIT(P, TRAIT_RESTRAINED)) if(!(world.time % 5)) to_chat(src, span_warning("[L] is restraining [P], you cannot push past.")) @@ -159,7 +159,7 @@ mob_swap = TRUE else //You can swap with the person you are dragging on grab intent, and restrained people in most cases - if(M.pulledby == src && !too_strong) + if(is_grabbing(M) && !too_strong) mob_swap = TRUE else if( !(HAS_TRAIT(M, TRAIT_NOMOBSWAP) || HAS_TRAIT(src, TRAIT_NOMOBSWAP))&&\ @@ -1590,10 +1590,6 @@ GLOBAL_LIST_EMPTY(fire_appearances) "[C] leaps out of [src]'s way!")]") C.Paralyze(40) -/mob/living/can_be_pulled() - return ..() && !(buckled?.buckle_prevents_pull) - - /// Called when mob changes from a standing position into a prone while lacking the ability to stand up at the moment. /mob/living/proc/on_fall() return @@ -1647,7 +1643,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) var/mob/living/U = user if(isliving(dropping)) var/mob/living/M = dropping - if(U.pulling == M && mob_size > M.mob_size) + if(U.is_grabbing(M) && !U.combat_mode && mob_size > M.mob_size) M.mob_try_pickup(U)//blame kevinz return//dont open the mobs inventory if you are picking them up . = ..() @@ -1976,31 +1972,6 @@ GLOBAL_LIST_EMPTY(fire_appearances) if(old_buckled.buckle_lying == 0 && (resting || HAS_TRAIT(src, TRAIT_FLOORED))) // The buckle forced us to stay up (like a chair) set_lying_down() // We want to rest or are otherwise floored, so let's drop on the ground. -/mob/living/set_pulledby(new_pulledby) - . = ..() - if(. == FALSE) //null is a valid value here, we only want to return if FALSE is explicitly passed. - return - if(pulledby) - if(!. && HAS_TRAIT(src, TRAIT_SOFT_CRITICAL_CONDITION)) - ADD_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT) - else if(. && HAS_TRAIT(src, TRAIT_SOFT_CRITICAL_CONDITION)) - REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT) - - -/// Updates the grab state of the mob and updates movespeed -/mob/living/setGrabState(newstate) - . = ..() - switch(grab_state) - if(GRAB_PASSIVE) - remove_movespeed_modifier(MOVESPEED_ID_MOB_GRAB_STATE) - if(GRAB_AGGRESSIVE) - add_movespeed_modifier(/datum/movespeed_modifier/grab_slowdown/aggressive) - if(GRAB_NECK) - add_movespeed_modifier(/datum/movespeed_modifier/grab_slowdown/neck) - if(GRAB_KILL) - add_movespeed_modifier(/datum/movespeed_modifier/grab_slowdown/kill) - - /// Only defined for carbons who can wear masks and helmets, we just assume other mobs have visible faces /mob/living/proc/is_face_visible() return TRUE diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 090872f5aac8..7180ff404d7e 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -137,6 +137,8 @@ adjust_fire_stacks(3) ignite_mob() +#warn replace +/* /mob/living/proc/grabbedby(mob/living/carbon/user, supress_message = FALSE) if(user == src || anchored || !isturf(user.loc)) return FALSE @@ -202,7 +204,7 @@ Move(user.loc) user.set_pull_offsets(src, grab_state) return TRUE - +*/ /mob/living/attack_slime(mob/living/simple_animal/slime/M) if(!SSticker.HasRoundStarted()) diff --git a/code/modules/mob/living/living_stripping.dm b/code/modules/mob/living/living_stripping.dm index 8725add6bbb0..793745cf3a4c 100644 --- a/code/modules/mob/living/living_stripping.dm +++ b/code/modules/mob/living/living_stripping.dm @@ -1,4 +1,4 @@ -/mob/living/proc/should_strip(mob/user) +/mob/living/proc/should_strip(mob/living/user) . = TRUE if(user.is_grabbing(src) && !user.combat_mode && isliving(user)) diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm index 5d79eca81b66..120e7061df63 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -56,7 +56,7 @@ return eat_plants() - if(pulledby) + if(LAZYLEN(grabbed_by)) return for(var/direction in shuffle(list(1,2,4,8,5,6,9,10))) diff --git a/code/modules/mob/living/simple_animal/heretic_monsters.dm b/code/modules/mob/living/simple_animal/heretic_monsters.dm index 60b060679590..1cc62a77421f 100644 --- a/code/modules/mob/living/simple_animal/heretic_monsters.dm +++ b/code/modules/mob/living/simple_animal/heretic_monsters.dm @@ -219,7 +219,7 @@ /mob/living/simple_animal/hostile/heretic_summon/armsy/has_gravity(turf/T) return TRUE -/mob/living/simple_animal/hostile/heretic_summon/armsy/can_be_pulled() +/mob/living/simple_animal/hostile/heretic_summon/armsy/can_be_grabbed(mob/living/grabber, target_zone, force) return FALSE /// Updates every body in the chain to force move onto a single tile. diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index c3fa6d9cf43d..fc22a1e781f6 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -556,7 +556,7 @@ Difficulty: Hard . = ..() if(QDELETED(caster)) return FALSE - if(mover == caster.pulledby) + if(caster.is_grabbing(mover)) return if(istype(mover, /obj/projectile)) var/obj/projectile/P = mover diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm index fb6918f2bf81..25e7c53da6ea 100644 --- a/code/modules/mob/living/simple_animal/hostile/ooze.dm +++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm @@ -209,16 +209,17 @@ if(!.) return var/mob/living/simple_animal/hostile/ooze/gelatinous/ooze = owner - if(!isliving(ooze.pulling)) - to_chat(src, span_warning("You need to be pulling a creature for this to work!")) + var/mob/living/target = ooze.get_active_grab()?.affecting + if(!isliving(target)) + to_chat(src, span_warning("You need to be gripping a creature for this to work!")) return FALSE if(vored_mob) to_chat(src, span_warning("You are already consuming another creature!")) return FALSE owner.visible_message(span_warning("[ooze] starts attempting to devour [target]!"), span_notice("You start attempting to devour [target].")) - if(!do_after(ooze, ooze.pulling, 1.5 SECONDS)) + if(!do_after(ooze, target, 1.5 SECONDS)) return FALSE - var/mob/living/eat_target = ooze.pulling + var/mob/living/eat_target = target if(!(eat_target.mob_biotypes & MOB_ORGANIC) || eat_target.stat == DEAD) to_chat(src, span_warning("This creature isn't to my tastes!")) @@ -416,14 +417,15 @@ ///Try to put the pulled mob in a cocoon /datum/action/cooldown/gel_cocoon/proc/gel_cocoon() var/mob/living/simple_animal/hostile/ooze/grapes/ooze = owner - if(!iscarbon(ooze.pulling)) + var/mob/living/carbon/target = ooze.get_active_grab()?.affecting + if(!iscarbon(target)) to_chat(src, span_warning("You need to be pulling an intelligent enough creature to assist it with a cocoon!")) return FALSE owner.visible_message(span_nicegreen("[ooze] starts attempting to put [target] into a gel cocoon!"), span_notice("You start attempting to put [target] into a gel cocoon.")) - if(!do_after(ooze, ooze.pulling, 1.5 SECONDS)) + if(!do_after(ooze, target, 1.5 SECONDS)) return FALSE - put_in_cocoon(ooze.pulling) + put_in_cocoon(target) ooze.adjust_ooze_nutrition(-30) ///Mob needs to have enough nutrition diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 1a1670c19318..c9be88c0b46b 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -377,7 +377,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list( ..() //Sprite update for when a parrot gets pulled - if(pulledby && !stat && parrot_state != PARROT_WANDER) + if(LAZYLEN(grabbed_by) && !stat && parrot_state != PARROT_WANDER) if(buckled) buckled.unbuckle_mob(src, TRUE) buckled = null diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index dd99f589cccd..1f87ea21bb1f 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -263,7 +263,7 @@ turns_since_move++ if(turns_since_move < turns_per_move) return TRUE - if(stop_automated_movement_when_pulled && pulledby) //Some animals don't move when pulled + if(stop_automated_movement_when_pulled && LAZYLEN(grabbed_by)) //Some animals don't move when pulled return TRUE var/anydir = pick(GLOB.cardinals) if(Process_Spacemove(anydir)) @@ -648,7 +648,7 @@ stack_trace("Something attempted to set simple animals AI to an invalid state: [togglestatus]") /mob/living/simple_animal/proc/consider_wakeup() - if (pulledby || shouldwakeup) + if (LAZYLEN(grabbed_by) || shouldwakeup) toggle_ai(AI_ON) /mob/living/simple_animal/on_changed_z_level(turf/old_turf, turf/new_turf) diff --git a/code/modules/mob/living/simple_animal/slime/life.dm b/code/modules/mob/living/simple_animal/slime/life.dm index d02608454d5c..89e5f1a0e8ce 100644 --- a/code/modules/mob/living/simple_animal/slime/life.dm +++ b/code/modules/mob/living/simple_animal/slime/life.dm @@ -357,7 +357,7 @@ else if(holding_still) holding_still = max(holding_still - (0.5 * delta_time), 0) - else if (docile && pulledby) + else if (docile && LAZYLEN(grabbed_by)) holding_still = 10 else if(!HAS_TRAIT(src, TRAIT_IMMOBILIZED) && isturf(loc) && prob(33)) step(src, pick(GLOB.cardinals)) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 2d8295a867dc..368bbe87ade8 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -80,8 +80,9 @@ GLOBAL_LIST_INIT(bodyzone_miss_chance, list( if(target.buckled || target.body_position == LYING_DOWN) return zone // if your target is being grabbed aggressively by someone you cannot miss either - if(target.pulledby) - return zone + for(var/obj/item/hand_item/grab/G in target.grabbed_by) + if(G.current_grab.stop_move) + return zone var/miss_chance = GLOB.bodyzone_miss_chance[zone] diff --git a/code/modules/mod/mod_ai.dm b/code/modules/mod/mod_ai.dm index e238f135ca30..37caddb1c4d4 100644 --- a/code/modules/mod/mod_ai.dm +++ b/code/modules/mod/mod_ai.dm @@ -74,7 +74,7 @@ #define AI_FALL_TIME 1 SECONDS /obj/item/mod/control/relaymove(mob/user, direction) - if((!active && wearer) || get_charge() < CHARGE_PER_STEP || user != ai || !COOLDOWN_FINISHED(src, cooldown_mod_move) || (wearer.check_grab_severity(GRAB_AGGRESSIVE))) + if((!active && wearer) || get_charge() < CHARGE_PER_STEP || user != ai || !COOLDOWN_FINISHED(src, cooldown_mod_move) || (wearer.check_grab_severities(GRAB_AGGRESSIVE))) return FALSE var/timemodifier = MOVE_DELAY * (ISDIAGONALDIR(direction) ? 2 : 1) * (wearer ? WEARER_DELAY : LONE_DELAY) if(wearer && !wearer.Process_Spacemove(direction)) diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm index f382d2c903cd..aef30e4e1e92 100644 --- a/code/modules/mod/modules/modules_supply.dm +++ b/code/modules/mod/modules/modules_supply.dm @@ -315,8 +315,8 @@ . = ..() if(!.) return - if(istype(mod.wearer.pulling, /obj/structure/closet)) - var/obj/structure/closet/locker = mod.wearer.pulling + var/obj/structure/closet/locker = mod.wearer.get_active_grab()?.affecting + if(istype(locker, /obj/structure/closet)) playsound(locker, 'sound/effects/gravhit.ogg', 75, TRUE) locker.forceMove(mod.wearer.loc) locker.throw_at(target, range = 7, speed = 4, thrower = mod.wearer) @@ -348,13 +348,13 @@ return mod.wearer.start_pulling(locker) locker.strong_grab = TRUE - RegisterSignal(locker, COMSIG_ATOM_NO_LONGER_PULLED, PROC_REF(on_stop_pull)) + RegisterSignal(locker, COMSIG_ATOM_NO_LONGER_GRABBED, PROC_REF(on_stop_pull)) /obj/item/mod/module/magnet/proc/on_stop_pull(obj/structure/closet/locker, atom/movable/last_puller) SIGNAL_HANDLER locker.strong_grab = FALSE - UnregisterSignal(locker, COMSIG_ATOM_NO_LONGER_PULLED) + UnregisterSignal(locker, COMSIG_ATOM_NO_LONGER_GRABBED) /obj/item/mod/module/ash_accretion name = "MOD ash accretion module" diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index da949c73599a..195039bdd985 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -549,16 +549,17 @@ dancer.spin(30, 2) if(dancer.disgust < 40) dancer.adjust_disgust(10) - if(!dancer.pulledby) + if(!LAZYLEN(dancer.grabbed_by)) return var/dancer_turf = get_turf(dancer) - var/atom/movable/dance_partner = dancer.pulledby - dance_partner.visible_message(span_danger("[dance_partner] tries to hold onto [dancer], but is thrown back!"), span_danger("You try to hold onto [dancer], but you are thrown back!"), null, COMBAT_MESSAGE_RANGE) - var/throwtarget = get_edge_target_turf(dancer_turf, get_dir(dancer_turf, get_step_away(dance_partner, dancer_turf))) - if(overdosed) - dance_partner.throw_at(target = throwtarget, range = 7, speed = 4) - else - dance_partner.throw_at(target = throwtarget, range = 4, speed = 1) //superspeed + for(var/obj/item/hand_item/grab/G in dancer.grabbed_by) + var/atom/movable/dance_partner = G.assailant + dance_partner.visible_message(span_danger("[dance_partner] tries to hold onto [dancer], but is thrown back!"), span_danger("You try to hold onto [dancer], but you are thrown back!"), null, COMBAT_MESSAGE_RANGE) + var/throwtarget = get_edge_target_turf(dancer_turf, get_dir(dancer_turf, get_step_away(dance_partner, dancer_turf))) + if(overdosed) + dance_partner.throw_at(target = throwtarget, range = 7, speed = 4) + else + dance_partner.throw_at(target = throwtarget, range = 4, speed = 1) //superspeed /datum/reagent/drug/saturnx name = "SaturnX" diff --git a/code/modules/recycling/conveyor.dm b/code/modules/recycling/conveyor.dm index b16c91286531..8d1724e735d5 100644 --- a/code/modules/recycling/conveyor.dm +++ b/code/modules/recycling/conveyor.dm @@ -299,7 +299,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) . = ..() if(.) return - user.Move_Pulled(src) + user.move_grabbed_atoms_towards(src) /obj/machinery/conveyor/power_change() . = ..() diff --git a/code/modules/religion/religion_structures.dm b/code/modules/religion/religion_structures.dm index adc4c2e53b9e..58c6e8633e5f 100644 --- a/code/modules/religion/religion_structures.dm +++ b/code/modules/religion/religion_structures.dm @@ -30,20 +30,19 @@ new_overlays += "convertaltarcandle" return new_overlays -/obj/structure/altar_of_gods/attack_hand(mob/living/user, list/modifiers) - if(!Adjacent(user) || !user.pulling) - return ..() +/obj/structure/altar_of_gods/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() if(!isliving(user.pulling)) - return ..() - var/mob/living/pushed_mob = user.pulling + return + var/mob/living/pushed_mob = victim if(pushed_mob.buckled) to_chat(user, span_warning("[pushed_mob] is buckled to [pushed_mob.buckled]!")) - return ..() + return + to_chat(user, span_notice("You try to coax [pushed_mob] onto [src]...")) if(!do_after(user,(5 SECONDS),target = pushed_mob)) - return ..() + return pushed_mob.forceMove(loc) - return ..() /obj/structure/altar_of_gods/examine_more(mob/user) if(!isobserver(user)) @@ -104,7 +103,7 @@ new /obj/effect/decal/cleanable/ash(drop_location()) qdel(src) -/obj/item/ritual_totem/can_be_pulled(user, grab_state, force) +/obj/item/ritual_totem/can_be_grabbed(mob/living/grabber, target_zone, force) . = ..() return FALSE //no diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm index 2f0872a673e0..e59677a3674c 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -882,11 +882,11 @@ /datum/status_effect/stabilized/black/proc/on_grab(mob/living/source, new_state) SIGNAL_HANDLER - if(new_state < GRAB_KILL || !isliving(source.pulling)) + if(new_state < GRAB_KILL || !isliving(source.get_active_grab()?.affecting)) draining_ref = null return - var/mob/living/draining = source.pulling + var/mob/living/draining = source.get_active_grab()?.affecting if(draining.stat == DEAD) return @@ -902,7 +902,8 @@ return span_warning("[owner.p_they(TRUE)] [owner.p_are()] draining health from [draining]!") /datum/status_effect/stabilized/black/tick() - if(owner.grab_state < GRAB_KILL || !IS_WEAKREF_OF(owner.pulling, draining_ref)) + var/obj/item/hand_item/grab/G = owner.get_active_grab() + if(G.current_grab.damage_stage < GRAB_KILL || !IS_WEAKREF_OF(G.affecting, draining_ref)) return var/mob/living/drained = draining_ref.resolve() diff --git a/code/modules/shuttle/special.dm b/code/modules/shuttle/special.dm index b33cee2b2dc6..d695e9e7c42b 100644 --- a/code/modules/shuttle/special.dm +++ b/code/modules/shuttle/special.dm @@ -226,183 +226,6 @@ if(ID && (ACCESS_CENT_BAR in ID.access)) return TRUE -//Luxury Shuttle Blockers - -/obj/machinery/scanner_gate/luxury_shuttle - name = "luxury shuttle ticket field" - density = FALSE //allows shuttle airlocks to close, nothing but an approved passenger gets past CanPass - locked = TRUE - use_power = NO_POWER_USE - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - speech_span = SPAN_ROBOT - var/threshold = 500 - var/static/list/approved_passengers = list() - var/static/list/check_times = list() - var/list/payees = list() - -/obj/machinery/scanner_gate/luxury_shuttle/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - - if(mover in approved_passengers) - set_scanline("scanning", 10) - if(isvehicle(mover)) - var/obj/vehicle/vehicle = mover - for(var/mob/living/rat in vehicle.occupants) - if(!(rat in approved_passengers)) - say("Stowaway detected. Please exit the vehicle first.") - return FALSE - return TRUE - if(isitem(mover)) - return TRUE - if(isstructure(mover)) - var/obj/structure/struct = mover - for(var/mob/living/rat in struct.contents) - say("Stowaway detected. Please exit the structure first.") - return FALSE - return TRUE - - return FALSE - -/obj/machinery/scanner_gate/luxury_shuttle/auto_scan(atom/movable/AM) - return - -/obj/machinery/scanner_gate/luxury_shuttle/attackby(obj/item/W, mob/user, params) - return - -/obj/machinery/scanner_gate/luxury_shuttle/emag_act(mob/user) - return - -#define LUXURY_MESSAGE_COOLDOWN 100 -/obj/machinery/scanner_gate/luxury_shuttle/BumpedBy(atom/movable/AM) - ///If the atom entering the gate is a vehicle, we store it here to add to the approved list to enter/leave the scanner gate. - var/obj/vehicle/vehicle - ///We store the driver of vehicles separately so that we can add them to the approved list once payment is fully processed. - var/mob/living/driver_holdout - if(!isliving(AM) && !isvehicle(AM)) - alarm_beep() - return ..() - - var/datum/bank_account/account - if(istype(AM.pulling, /obj/item/card/id)) - var/obj/item/card/id/I = AM.pulling - if(I.registered_account) - account = I.registered_account - else if(!check_times[AM] || check_times[AM] < world.time) //Let's not spam the message - to_chat(AM, span_notice("This ID card doesn't have an owner associated with it!")) - check_times[AM] = world.time + LUXURY_MESSAGE_COOLDOWN - else if(isliving(AM)) - var/mob/living/L = AM - account = L.get_bank_account() - - else if(isvehicle(AM)) - vehicle = AM - for(var/passenger in vehicle.occupants) - if(!isliving(passenger)) - continue - var/mob/living/rider = passenger - if(vehicle.is_driver(rider)) - driver_holdout = rider - var/obj/item/card/id/id = rider.get_idcard(TRUE) - account = id?.registered_account - break - - if(account) - if(account.account_balance < threshold - payees[AM]) - account.adjust_money(-account.account_balance) - payees[AM] += account.account_balance - else - var/money_owed = threshold - payees[AM] - account.adjust_money(-money_owed) - payees[AM] += money_owed - - //Here is all the possible paygate payment methods. - var/list/counted_money = list() - for(var/obj/item/coin/C in AM.get_all_contents()) //Coins. - if(payees[AM] >= threshold) - break - payees[AM] += C.value - counted_money += C - for(var/obj/item/stack/spacecash/S in AM.get_all_contents()) //Paper Cash - if(payees[AM] >= threshold) - break - payees[AM] += S.value * S.amount - counted_money += S - for(var/obj/item/holochip/H in AM.get_all_contents()) //Holocredits - if(payees[AM] >= threshold) - break - payees[AM] += H.credits - counted_money += H - - if(payees[AM] < threshold && istype(AM.pulling, /obj/item/coin)) //Coins(Pulled). - var/obj/item/coin/C = AM.pulling - payees[AM] += C.value - counted_money += C - - else if(payees[AM] < threshold && istype(AM.pulling, /obj/item/stack/spacecash)) //Cash(Pulled). - var/obj/item/stack/spacecash/S = AM.pulling - payees[AM] += S.value * S.amount - counted_money += S - - else if(payees[AM] < threshold && istype(AM.pulling, /obj/item/holochip)) //Holocredits(pulled). - var/obj/item/holochip/H = AM.pulling - payees[AM] += H.credits - counted_money += H - - if(payees[AM] < threshold) //Suggestions for those with no arms/simple animals. - var/armless - if(!ishuman(AM) && !istype(AM, /mob/living/simple_animal/slime)) - armless = TRUE - else - var/mob/living/carbon/human/H = AM - if(!H.get_bodypart(BODY_ZONE_L_ARM) && !H.get_bodypart(BODY_ZONE_R_ARM)) - armless = TRUE - - if(armless) - if(!AM.pulling || !iscash(AM.pulling) && !istype(AM.pulling, /obj/item/card/id)) - if(!check_times[AM] || check_times[AM] < world.time) //Let's not spam the message - to_chat(AM, span_notice("Try pulling a valid ID, space cash, holochip or coin into \the [src]!")) - check_times[AM] = world.time + LUXURY_MESSAGE_COOLDOWN - - if(payees[AM] >= threshold) - for(var/obj/I in counted_money) - qdel(I) - payees[AM] -= threshold - - var/change = FALSE - if(payees[AM] > 0) - change = TRUE - var/obj/item/holochip/HC = new /obj/item/holochip(AM.loc) //Change is made in holocredits exclusively. - HC.credits = payees[AM] - HC.name = "[HC.credits] credit holochip" - if(istype(AM, /mob/living/carbon/human)) - var/mob/living/carbon/human/H = AM - if(!H.put_in_hands(HC)) - AM.pulling = HC - else - AM.pulling = HC - payees[AM] -= payees[AM] - - say("Welcome to first class, [driver_holdout ? "[driver_holdout]" : "[AM]" ]![change ? " Here is your change." : ""]") - approved_passengers |= AM - if(vehicle) - approved_passengers |= vehicle - if(driver_holdout) - approved_passengers |= driver_holdout - - check_times -= AM - return - else if (payees[AM] > 0) - for(var/obj/I in counted_money) - qdel(I) - if(!check_times[AM] || check_times[AM] < world.time) //Let's not spam the message - to_chat(AM, span_notice("[payees[AM]] cr received. You need [threshold-payees[AM]] cr more.")) - check_times[AM] = world.time + LUXURY_MESSAGE_COOLDOWN - alarm_beep() - return ..() - else - alarm_beep() - return ..() - /mob/living/simple_animal/hostile/bear/fightpit name = "fight pit bear" desc = "This bear's trained through ancient Russian secrets to fear the walls of its glass prison." diff --git a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm index 4cbbca3d9990..0216650f04f1 100644 --- a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm +++ b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm @@ -163,7 +163,7 @@ /datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/try_enter_jaunt(obj/effect/decal/cleanable/blood, mob/living/jaunter) // Save this before the actual jaunt - var/atom/coming_with = jaunter.pulling + var/atom/coming_with = jaunter.get_active_grab()?.affecting // Does the actual jaunt . = ..() diff --git a/code/modules/spells/spell_types/self/charge.dm b/code/modules/spells/spell_types/self/charge.dm index 87d7ae287d33..d8ee8db134f8 100644 --- a/code/modules/spells/spell_types/self/charge.dm +++ b/code/modules/spells/spell_types/self/charge.dm @@ -21,8 +21,8 @@ . = ..() // Charge people we're pulling first and foremost - if(isliving(cast_on.pulling)) - var/mob/living/pulled_living = cast_on.pulling + if(isliving(cast_on.get_active_grab()?.affecting)) + var/mob/living/pulled_living = cast_on.get_active_grab()?.affecting var/pulled_has_spells = FALSE for(var/datum/action/cooldown/spell/spell in pulled_living.actions) diff --git a/code/modules/tables/tables_racks.dm b/code/modules/tables/tables_racks.dm index b83667842520..f954561c467a 100644 --- a/code/modules/tables/tables_racks.dm +++ b/code/modules/tables/tables_racks.dm @@ -235,7 +235,7 @@ layer = TABLE_LAYER /obj/structure/table/attack_grab(mob/living/user, obj/item/hand_item/grab/grab, list/params) - try_place_pulled_onto_table(user, G.affecting) + try_place_pulled_onto_table(user, grab.affecting, grab) /obj/structure/table/proc/try_place_pulled_onto_table(mob/living/user, atom/movable/target, obj/item/hand_item/grab/grab) if(!Adjacent(user)) @@ -247,15 +247,13 @@ to_chat(user, span_warning("[pushed_mob] is buckled to [pushed_mob.buckled]!")) return if(user.combat_mode) - switch(grab.current_grab.type) - if(/datum/grab/simple, /datum/grab/normal) + switch(grab.current_grab.damage_stage) + if(GRAB_PASSIVE) to_chat(user, span_warning("You need a better grip to do that!")) return - #warn aggro grab - /* - if(/datum/grab/aggressive) + + if(GRAB_AGGRESSIVE) tablepush(user, pushed_mob) - */ else tablelimbsmash(user, pushed_mob) else @@ -265,13 +263,13 @@ tableplace(user, pushed_mob) else return - user.release_all_grabs() + user.release_grab(pushed_mob) else if(target.pass_flags & PASSTABLE) - user.Move_Pulled(src) + user.move_grabbed_atoms_towards(src) if (target.loc == loc) - user.visible_message(span_notice("[user] places [user.pulling] onto [src]."), - span_notice("You place [user.pulling] onto [src].")) + user.visible_message(span_notice("[user] places [target] onto [src]."), + span_notice("You place [target] onto [src].")) user.release_grab(target) From edbb6368500ee82f5b40ce180348ce634541030c Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Tue, 26 Sep 2023 21:06:52 -0400 Subject: [PATCH 05/37] it mf compiles --- .../signals_atom/signals_atom_movement.dm | 4 +- code/__HELPERS/global_lists.dm | 3 +- code/_onclick/hud/screen_objects.dm | 2 +- code/datums/ai/generic/generic_behaviors.dm | 2 +- code/datums/components/drift.dm | 4 +- code/datums/components/strong_pull.dm | 8 +- code/datums/components/tackle.dm | 9 +-- code/datums/elements/atmos_requirements.dm | 3 +- code/datums/martial/cqc.dm | 7 +- code/datums/martial/psychotic_brawl.dm | 16 ++-- code/game/atoms_movable.dm | 79 +++++++------------ code/game/objects/buckling.dm | 13 +-- code/game/objects/effects/spiderwebs.dm | 7 +- code/game/objects/items/defib.dm | 3 +- code/game/objects/items/theft_tools.dm | 3 +- .../structures/crates_lockers/closets.dm | 2 +- .../structures/transit_tubes/station.dm | 4 +- code/game/objects/structures/watercloset.dm | 4 +- code/game/turfs/closed/wall/misc_walls.dm | 2 +- .../game/turfs/open/floor/reinforced_floor.dm | 4 +- code/game/turfs/turf.dm | 4 +- code/modules/antagonists/cult/cult_items.dm | 2 +- code/modules/awaymissions/signpost.dm | 7 +- .../kitchen_machinery/processor.dm | 2 +- code/modules/grab/{grabs => }/grab_carbon.dm | 2 +- code/modules/grab/grab_datum.dm | 29 +++---- code/modules/grab/grab_living.dm | 18 +++-- code/modules/grab/grab_movable.dm | 31 +++++++- code/modules/grab/grab_object.dm | 30 ++++--- code/modules/grab/grab_silicon.dm | 2 + code/modules/grab/grabs/grab_aggressive.dm | 43 ++++++++++ code/modules/grab/grabs/grab_normal.dm | 22 ++---- code/modules/grab/grabs/grab_passive.dm | 25 ++++++ code/modules/grab/grabs/grab_simple.dm | 8 +- code/modules/grab/human_grab.dm | 2 +- .../mob/living/carbon/alien/larva/larva.dm | 4 +- code/modules/mob/living/carbon/carbon.dm | 25 +++--- .../mob/living/carbon/carbon_defense.dm | 24 ------ code/modules/mob/living/carbon/examine.dm | 3 - .../mob/living/carbon/human/examine.dm | 4 +- .../mob/living/carbon/human/human_defense.dm | 6 -- .../living/carbon/human/human_stripping.dm | 2 +- .../mob/living/carbon/human/species.dm | 2 +- code/modules/mob/living/living.dm | 65 ++++----------- code/modules/mob/living/living_movement.dm | 5 +- .../mob/living/silicon/pai/pai_shell.dm | 2 +- .../mob/living/silicon/silicon_defense.dm | 4 - .../mob/living/simple_animal/constructs.dm | 5 +- .../mob/living/simple_animal/slime/slime.dm | 2 +- code/modules/mob/mob_movement.dm | 2 +- code/modules/mod/modules/modules_supply.dm | 4 +- code/modules/recycling/conveyor.dm | 6 +- .../research/designs/wiremod_designs.dm | 5 -- .../unit_tests/chain_pull_through_space.dm | 4 +- .../modules/wiremod/components/action/pull.dm | 27 ------- daedalus.dme | 6 +- 56 files changed, 300 insertions(+), 313 deletions(-) rename code/modules/grab/{grabs => }/grab_carbon.dm (87%) create mode 100644 code/modules/grab/grab_silicon.dm create mode 100644 code/modules/grab/grabs/grab_aggressive.dm create mode 100644 code/modules/grab/grabs/grab_passive.dm delete mode 100644 code/modules/wiremod/components/action/pull.dm diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm index 5c3ae41912b7..540415bf48bb 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm @@ -7,8 +7,8 @@ #define COMSIG_ATOM_NO_GRAB (1 << 0) ///signal sent out by an atom when it is no longer being pulled by something else : (atom/puller) #define COMSIG_ATOM_NO_LONGER_GRABBED "movable_no_longer_grabbed" -///signal sent out by an atom when it is no longer pulling something : (atom/pulling) -#define COMSIG_ATOM_NO_LONGER_GRABBING "movable_no_longer_grabbing" +///signal sent out by a living mob when it is no longer pulling something : (atom/pulling) +#define COMSIG_LIVING_NO_LONGER_GRABBING "living_no_longer_grabbing" ///called for each movable in a turf contents on /turf/zImpact(): (atom/movable/A, levels) #define COMSIG_ATOM_INTERCEPT_Z_FALL "movable_intercept_z_impact" ///called on a movable when it starts pulling (atom/movable/pulled, state, force) diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index 551a6fb2bbfd..f81755d0a9f3 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -90,7 +90,8 @@ for(var/datum/grab/G as anything in subtypesof(/datum/grab)) if(isabstract(G)) continue - GLOB.all_grabstates[G.type] = G + GLOB.all_grabstates[G] = new G + for(var/path in GLOB.all_grabstates) var/datum/grab/G = GLOB.all_grabstates[path] G.refresh_updown() diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 73d27b255468..5d4fda7800fb 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -400,7 +400,7 @@ L.release_all_grabs() /atom/movable/screen/pull/update_icon_state() - icon_state = "[base_icon_state][hud?.mymob?.pulling ? null : 0]" + icon_state = "[base_icon_state][LAZYLEN(hud?.mymob?:get_active_grabs()) ? null : 0]" return ..() /atom/movable/screen/resist diff --git a/code/datums/ai/generic/generic_behaviors.dm b/code/datums/ai/generic/generic_behaviors.dm index 08f6dc16b908..d3d1a18da088 100644 --- a/code/datums/ai/generic/generic_behaviors.dm +++ b/code/datums/ai/generic/generic_behaviors.dm @@ -43,7 +43,7 @@ if(get_dist(batman, big_guy) >= give_up_distance) finish_action(controller, FALSE, target_key) - big_guy.start_pulling(batman) + big_guy.try_make_grab(batman) big_guy.setDir(get_dir(big_guy, batman)) batman.visible_message(span_warning("[batman] gets a slightly too tight hug from [big_guy]!"), span_userdanger("You feel your body break as [big_guy] embraces you!")) diff --git a/code/datums/components/drift.dm b/code/datums/components/drift.dm index a2ae04783089..a649b3947f9c 100644 --- a/code/datums/components/drift.dm +++ b/code/datums/components/drift.dm @@ -92,14 +92,14 @@ // This way you can't ride two movements at once while drifting, since that'd be dumb as fuck RegisterSignal(movable_parent, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(handle_glidesize_update)) // If you stop pulling something mid drift, I want it to retain that momentum - RegisterSignal(movable_parent, COMSIG_ATOM_NO_LONGER_GRABBING, PROC_REF(stopped_pulling)) + RegisterSignal(movable_parent, COMSIG_LIVING_NO_LONGER_GRABBING, PROC_REF(stopped_pulling)) /datum/component/drift/proc/drifting_stop() SIGNAL_HANDLER var/atom/movable/movable_parent = parent movable_parent.inertia_moving = FALSE ignore_next_glide = FALSE - UnregisterSignal(movable_parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, COMSIG_ATOM_NO_LONGER_GRABBING)) + UnregisterSignal(movable_parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, COMSIG_LIVING_NO_LONGER_GRABBING)) /datum/component/drift/proc/before_move(datum/source) SIGNAL_HANDLER diff --git a/code/datums/components/strong_pull.dm b/code/datums/components/strong_pull.dm index f9b8188e0c60..85e251c234ae 100644 --- a/code/datums/components/strong_pull.dm +++ b/code/datums/components/strong_pull.dm @@ -25,8 +25,8 @@ Basically, the items they pull cannot be pulled (except by the puller) /datum/component/strong_pull/proc/on_pull(datum/source, atom/movable/pulled, state, force) SIGNAL_HANDLER strongpulling = pulled - RegisterSignal(strongpulling, COMSIG_ATOM_CAN_BE_PULLED, PROC_REF(reject_further_pulls)) - RegisterSignal(strongpulling, COMSIG_ATOM_NO_LONGER_PULLED, PROC_REF(on_no_longer_pulled)) + RegisterSignal(strongpulling, COMSIG_ATOM_CAN_BE_GRABBED, PROC_REF(reject_further_pulls)) + RegisterSignal(strongpulling, COMSIG_ATOM_NO_LONGER_GRABBED, PROC_REF(on_no_longer_pulled)) if(istype(strongpulling, /obj/structure/closet) && !istype(strongpulling, /obj/structure/closet/body_bag)) var/obj/structure/closet/grabbed_closet = strongpulling grabbed_closet.strong_grab = TRUE @@ -37,13 +37,13 @@ Basically, the items they pull cannot be pulled (except by the puller) /datum/component/strong_pull/proc/reject_further_pulls(datum/source, mob/living/puller) SIGNAL_HANDLER if(puller != parent) //for increasing grabs, you need to have a valid pull. thus, parent should be able to pull the same object again - return COMSIG_ATOM_CANT_PULL + return COMSIG_ATOM_NO_GRAB /* * Unregisters signals and stops any buffs to pulling. */ /datum/component/strong_pull/proc/lose_strong_grip() - UnregisterSignal(strongpulling, list(COMSIG_ATOM_CAN_BE_PULLED, COMSIG_ATOM_NO_LONGER_PULLED)) + UnregisterSignal(strongpulling, list(COMSIG_ATOM_CAN_BE_GRABBED, COMSIG_ATOM_NO_LONGER_GRABBED)) if(istype(strongpulling, /obj/structure/closet)) var/obj/structure/closet/ungrabbed_closet = strongpulling ungrabbed_closet.strong_grab = FALSE diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm index ad042989ef58..c665ee56bddc 100644 --- a/code/datums/components/tackle.dm +++ b/code/datums/components/tackle.dm @@ -211,9 +211,7 @@ target.stamina.adjust(-40) target.Paralyze(5) target.Knockdown(30) - if(ishuman(target) && ishuman(user)) - INVOKE_ASYNC(S.dna.species, TYPE_PROC_REF(/datum/species, grab), S, T) - S.setGrabState(GRAB_PASSIVE) + S.try_make_grab(target, /datum/grab/normal/aggressive) if(5 to INFINITY) // absolutely BODIED user.visible_message(span_warning("[user] lands a monster [tackle_word] on [target], knocking [target.p_them()] senseless and applying an aggressive pin!"), span_userdanger("You land a monster [tackle_word] on [target], knocking [target.p_them()] senseless and applying an aggressive pin!"), ignored_mobs = target) @@ -225,10 +223,7 @@ target.stamina.adjust(-40) target.Paralyze(5) target.Knockdown(30) - if(ishuman(target) && ishuman(user)) - INVOKE_ASYNC(S.dna.species, TYPE_PROC_REF(/datum/species, grab), S, T) - S.setGrabState(GRAB_AGGRESSIVE) - + S.try_make_grab(target, /datum/grab/normal/aggressive) return COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH diff --git a/code/datums/elements/atmos_requirements.dm b/code/datums/elements/atmos_requirements.dm index 749b017e4b5d..f06573618221 100644 --- a/code/datums/elements/atmos_requirements.dm +++ b/code/datums/elements/atmos_requirements.dm @@ -36,10 +36,9 @@ target.throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy) /datum/element/atmos_requirements/proc/is_breathable_atmos(mob/living/target) - if(target.check_grab_severity(GRAB_KILL) && atmos_requirements["min_oxy"]) + if(target.check_grab_severities(GRAB_KILL) && atmos_requirements["min_oxy"]) return FALSE - if(!isopenturf(target.loc)) return TRUE diff --git a/code/datums/martial/cqc.dm b/code/datums/martial/cqc.dm index 4909ba92f4fe..6b9702fc67e3 100644 --- a/code/datums/martial/cqc.dm +++ b/code/datums/martial/cqc.dm @@ -198,15 +198,16 @@ playsound(D, 'sound/weapons/punchmiss.ogg', 25, TRUE, -1) log_combat(A, D, "disarmed (CQC)", "[I ? " grabbing \the [I]" : ""]") - if(restraining && A.is_grabbing(D)) + var/obj/item/hand_item/grab/G = A.is_grabbing(D) + if(restraining && G) log_combat(A, D, "knocked out (Chokehold)(CQC)") D.visible_message(span_danger("[A] puts [D] into a chokehold!"), \ span_userdanger("You're put into a chokehold by [A]!"), span_hear("You hear shuffling and a muffled groan!"), null, A) to_chat(A, span_danger("You put [D] into a chokehold!")) D.SetSleeping(400) restraining = FALSE - if(A.grab_state < GRAB_NECK && !HAS_TRAIT(A, TRAIT_PACIFISM)) - A.setGrabState(GRAB_NECK) + if(G.current_grab.damage_stage < GRAB_NECK && !HAS_TRAIT(A, TRAIT_PACIFISM)) + G.upgrade() else restraining = FALSE return FALSE diff --git a/code/datums/martial/psychotic_brawl.dm b/code/datums/martial/psychotic_brawl.dm index 329144feebb9..71cea7c1d96e 100644 --- a/code/datums/martial/psychotic_brawl.dm +++ b/code/datums/martial/psychotic_brawl.dm @@ -24,22 +24,24 @@ A.Stun(20) atk_verb = "cried looking at" if(3) - if(A.grab_state >= GRAB_AGGRESSIVE) - D.grabbedby(A, 1) + var/obj/item/hand_item/grab/G = A.is_grabbing(D) + if(G?.current_grab.damage_stage == GRAB_AGGRESSIVE) + G.upgrade(TRUE) else - A.start_pulling(D, supress_message = TRUE) - if(A.pulling) + A.try_make_grab(D) + G = A.is_grabbing(D) + if(G) D.drop_all_held_items() - D.stop_pulling() + D.release_all_grabs() if(grab_attack) log_combat(A, D, "grabbed", addition="aggressively") D.visible_message(span_warning("[A] violently grabs [D]!"), \ span_userdanger("You're violently grabbed by [A]!"), span_hear("You hear sounds of aggressive fondling!"), null, A) to_chat(A, span_danger("You violently grab [D]!")) - A.setGrabState(GRAB_AGGRESSIVE) //Instant aggressive grab + G.upgrade(TRUE) else log_combat(A, D, "grabbed", addition="passively") - A.setGrabState(GRAB_PASSIVE) + if(4) A.do_attack_animation(D, ATTACK_EFFECT_PUNCH) atk_verb = "headbutt" diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 428047997671..66926f4e6c08 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -572,43 +572,6 @@ return ..() - -/atom/movable/proc/start_pulling(atom/movable/pulled_atom, state, force = move_force, supress_message = FALSE) - if(QDELETED(pulled_atom)) - return FALSE - if(!(pulled_atom.can_be_pulled(src, state, force))) - return FALSE - - // If we're pulling something then drop what we're currently pulling and pull this instead. - if(pulling) - if(state == 0) - stop_pulling() - return FALSE - // Are we trying to pull something we are already pulling? Then enter grab cycle and end. - if(pulled_atom == pulling) - setGrabState(state) - if(istype(pulled_atom,/mob/living)) - var/mob/living/pulled_mob = pulled_atom - pulled_mob.grabbedby(src) - return TRUE - stop_pulling() - - if(pulled_atom.pulledby) - log_combat(pulled_atom, pulled_atom.pulledby, "pulled from", src) - pulled_atom.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. - pulling = pulled_atom - pulled_atom.set_pulledby(src) - SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, pulled_atom, state, force) - setGrabState(state) - if(ismob(pulled_atom)) - var/mob/pulled_mob = pulled_atom - log_combat(src, pulled_mob, "grabbed", addition="passive grab") - if(!supress_message) - pulled_mob.visible_message(span_warning("[src] grabs [pulled_mob] passively."), \ - span_danger("[src] grabs you passively.")) - return TRUE - - /atom/movable/proc/set_glide_size(target = 8) SEND_SIGNAL(src, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, target) glide_size = target @@ -715,12 +678,13 @@ if(QDELING(src)) CRASH("Illegal Move()! on [type]") - var/atom/movable/pullee = pulling var/turf/current_turf = loc if(!moving_from_pull) recheck_grabs(z_allowed = TRUE) + if(!loc || !newloc) return FALSE + var/atom/oldloc = loc //Early override for some cases like diagonal movement if(glide_size_override && glide_size != glide_size_override) @@ -789,13 +753,18 @@ set_currently_z_moving(FALSE, TRUE) return - #warn FUUUUCK - if(. && LAZYLEN && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move. - if(pulling.anchored) - stop_pulling() - else - //puller and pullee more than one tile away or in diagonal position and whatever the pullee is pulling isn't already moving from a pull as it'll most likely result in an infinite loop a la ouroborus. - if(!pulling.pulling?.moving_from_pull) + if(. && isliving(src)) + var/mob/living/L = src + var/list/grabs = L.get_active_grabs() + if(LAZYLEN(grabs)) + for(var/obj/item/hand_item/grab/G in grabs) + var/atom/movable/pulling = G.affecting + if(pulling.anchored) + qdel(G) + continue + //puller and pullee more than one tile away or in diagonal position and whatever the pullee is pulling isn't already moving from a pull as it'll most likely result in an infinite loop a la ouroborus. + if(G.assailant.moving_from_pull) + continue var/pull_dir = get_dir(pulling, src) var/target_turf = current_turf @@ -807,6 +776,7 @@ if(target_turf != current_turf || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1) pulling.move_from_pull(src, target_turf, glide_size) + recheck_grabs() //glide_size strangely enough can change mid movement animation and update correctly while the animation is playing @@ -1147,8 +1117,8 @@ var/is_multi_tile = bound_width > world.icon_size || bound_height > world.icon_size if(destination) ///zMove already handles whether a pull from another movable should be broken. - if(pulledby && !currently_z_moving) - pulledby.stop_pulling() + if(LAZYLEN(grabbed_by) && !currently_z_moving) + free_from_all_grabs() var/same_loc = oldloc == destination var/area/old_area = get_area(oldloc) @@ -1248,8 +1218,19 @@ if(SEND_SIGNAL(src, COMSIG_MOVABLE_SPACEMOVE, movement_dir, continuous_move) & COMSIG_MOVABLE_STOP_SPACEMOVE) return TRUE - if(pulledby && (pulledby.pulledby != src || moving_from_pull)) - return TRUE + // If we are being pulled by something AND (we are NOT pulling them OR we are moving from a pull), do not drift + if(LAZYLEN(grabbed_by)) + var/can_drift = TRUE + if(isliving(src)) + var/mob/living/L = src + for(var/obj/item/hand_item/grab/G in grabbed_by) + if(!L.is_grabbing(G.assailant)) + can_drift = FALSE // Something is grabbing us and we're not grabbing them + else + can_drift = FALSE + + if(!can_drift || moving_from_pull) + return TRUE if(throwing) return TRUE diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm index 7acabe89e443..dfff619d5f42 100644 --- a/code/game/objects/buckling.dm +++ b/code/game/objects/buckling.dm @@ -105,12 +105,9 @@ if(LAZYLEN(M.grabbed_by)) if(buckle_prevents_pull) M.free_from_all_grabs() - #warn pull offsets - /* - else if(isliving(M.pulledby)) - var/mob/living/L = M.pulledby - L.reset_pull_offsets(M, TRUE) - */ + + else if(LAZYLEN(grabbed_by)) + M.reset_pull_offsets(TRUE) if(anchored) ADD_TRAIT(M, TRAIT_NO_FLOATING_ANIM, BUCKLED_TRAIT) @@ -350,7 +347,5 @@ span_hear("You hear metal clanking.")) add_fingerprint(user) - if(isliving(M.pulledby)) - var/mob/living/L = M.pulledby - L.set_pull_offsets(M, L.grab_state) + update_offsets() return M diff --git a/code/game/objects/effects/spiderwebs.dm b/code/game/objects/effects/spiderwebs.dm index 709127a37596..d81e1d5086cd 100644 --- a/code/game/objects/effects/spiderwebs.dm +++ b/code/game/objects/effects/spiderwebs.dm @@ -67,8 +67,9 @@ if(istype(mover, /mob/living/simple_animal/hostile/giant_spider)) return TRUE else if(isliving(mover)) - if(istype(mover.pulledby, /mob/living/simple_animal/hostile/giant_spider)) - return TRUE + for(var/obj/item/hand_item/grab/G in mover.grabbed_by) + if(istype(G.assailant, /mob/living/simple_animal/hostile/giant_spider)) + return TRUE if(prob(50)) to_chat(mover, span_danger("You get stuck in \the [src] for a moment.")) return FALSE @@ -95,7 +96,7 @@ if(mover == allowed_mob) return TRUE else if(isliving(mover)) //we change the spider to not be able to go through here - if(mover.pulledby == allowed_mob) + if(allowed_mob.is_grabbing(mover)) return TRUE if(prob(50)) to_chat(mover, span_danger("You get stuck in \the [src] for a moment.")) diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm index b95c96aa1408..235c97615852 100644 --- a/code/game/objects/items/defib.dm +++ b/code/game/objects/items/defib.dm @@ -514,8 +514,7 @@ update_appearance() /obj/item/shockpaddles/proc/shock_pulling(dmg, mob/H) - if(isliving(H.pulledby)) //CLEAR! - var/mob/living/M = H.pulledby + for(var/mob/living/M in H.recursively_get_all_grabbers()) if(M.electrocute_act(dmg, H)) M.visible_message(span_danger("[M] is electrocuted by [M.p_their()] contact with [H]!")) M.emote("scream") diff --git a/code/game/objects/items/theft_tools.dm b/code/game/objects/items/theft_tools.dm index dfe6df5024d4..5a286cd37ca8 100644 --- a/code/game/objects/items/theft_tools.dm +++ b/code/game/objects/items/theft_tools.dm @@ -169,7 +169,8 @@ return -/obj/item/nuke_core/supermatter_sliver/can_be_pulled(user) // no drag memes +/obj/item/nuke_core/supermatter_sliver/can_be_grabbed(mob/living/grabber, target_zone, force) + // no drag memes return FALSE /obj/item/nuke_core/supermatter_sliver/attackby(obj/item/W, mob/living/user, params) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 2edbe79f700f..2153a8dfb36a 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -212,7 +212,7 @@ if(welded || locked) return FALSE if(strong_grab) - to_chat(user, span_danger("[pulledby] has an incredibly strong grip on [src], preventing it from opening.")) + to_chat(user, span_danger("[grabbed_by[1].assailant] has an incredibly strong grip on [src], preventing it from opening.")) return FALSE var/turf/T = get_turf(src) for(var/mob/living/L in T) diff --git a/code/game/objects/structures/transit_tubes/station.dm b/code/game/objects/structures/transit_tubes/station.dm index 1d5f877b306d..fb6ea8be1733 100644 --- a/code/game/objects/structures/transit_tubes/station.dm +++ b/code/game/objects/structures/transit_tubes/station.dm @@ -90,14 +90,14 @@ return var/mob/living/GM = victim - if(grab.current_grab.damage_level >= GRAB_AGGRESSIVE) + if(grab.current_grab.damage_stage >= GRAB_AGGRESSIVE) if(GM.buckled || GM.has_buckled_mobs()) to_chat(user, span_warning("[GM] is attached to something!")) return for(var/obj/structure/transit_tube_pod/pod in loc) pod.visible_message(span_warning("[user] starts putting [GM] into the [pod]!")) if(do_after(user, src, 15)) - if(open_status == STATION_TUBE_OPEN && GM && grab.current_grab?.damage_level >= GRAB_AGGRESSIVE && G.is_grabbing(victim) && !GM.buckled && !GM.has_buckled_mobs()) + if(open_status == STATION_TUBE_OPEN && GM && grab.current_grab?.damage_stage >= GRAB_AGGRESSIVE && user.is_grabbing(victim) && !GM.buckled && !GM.has_buckled_mobs()) GM.Paralyze(100) src.BumpedBy(GM) break diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index cb8ff6d00b46..21a5c23dd18f 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -52,7 +52,7 @@ user.changeNext_move(CLICK_CD_MELEE) var/mob/living/GM = victim - if(grab.current_grab.damage_level >= GRAB_AGGRESSIVE) + if(grab.current_grab.damage_stage >= GRAB_AGGRESSIVE) if(GM.loc != get_turf(src)) to_chat(user, span_warning("[GM] needs to be on [src]!")) return @@ -195,7 +195,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/urinal, 32) var/mob/living/GM = victim - if(grab.current_grab.damage_level >= GRAB_AGGRESSIVE) + if(grab.current_grab.damage_stage >= GRAB_AGGRESSIVE) if(GM.loc != get_turf(src)) to_chat(user, span_notice("[GM.name] needs to be on [src].")) return diff --git a/code/game/turfs/closed/wall/misc_walls.dm b/code/game/turfs/closed/wall/misc_walls.dm index 0aff692c94c4..9b6d7683cb51 100644 --- a/code/game/turfs/closed/wall/misc_walls.dm +++ b/code/game/turfs/closed/wall/misc_walls.dm @@ -20,7 +20,7 @@ if(stored_pulling) stored_pulling.setDir(direction) stored_pulling.forceMove(src) - H.try_make_grab(stored_pulling, supress_message = TRUE) + H.try_make_grab(stored_pulling) /turf/closed/wall/mineral/cult/artificer name = "runed stone wall" diff --git a/code/game/turfs/open/floor/reinforced_floor.dm b/code/game/turfs/open/floor/reinforced_floor.dm index 94efec82cb20..71d98b5aa58e 100644 --- a/code/game/turfs/open/floor/reinforced_floor.dm +++ b/code/game/turfs/open/floor/reinforced_floor.dm @@ -88,10 +88,12 @@ /turf/open/floor/engine/attack_paw(mob/user, list/modifiers) return attack_hand(user, modifiers) -/turf/open/floor/engine/attack_hand(mob/user, list/modifiers) +/turf/open/floor/engine/attack_hand(mob/living/user, list/modifiers) . = ..() if(.) return + if(!isliving(user)) + return user.move_grabbed_atoms_towards(src) //air filled floors; used in atmos pressure chambers diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index fcd9878af2f7..0a84330b8c7c 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -201,10 +201,12 @@ GLOBAL_LIST_EMPTY(station_turfs) /turf/clear_signal_refs() return -/turf/attack_hand(mob/user, list/modifiers) +/turf/attack_hand(mob/living/user, list/modifiers) . = ..() if(.) return + if(!isliving(user)) + return user.move_grabbed_atoms_towards(src) /** diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index 5fe52da90f39..8a7845ea49f0 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -166,7 +166,7 @@ Striking a noncultist, however, will tear their flesh."} else . += "The sword appears to be quite lifeless." -/obj/item/cult_bastard/can_be_pulled(user) +/obj/item/cult_bastard/can_be_grabbed(mob/living/grabber, target_zone, force) return FALSE /obj/item/cult_bastard/attack_self(mob/user) diff --git a/code/modules/awaymissions/signpost.dm b/code/modules/awaymissions/signpost.dm index 3ee162c24e4d..12e2e832e510 100644 --- a/code/modules/awaymissions/signpost.dm +++ b/code/modules/awaymissions/signpost.dm @@ -23,14 +23,15 @@ var/atom/movable/preserve_grab if(isliving(user)) var/mob/living/L = user - var/list/grabbing= user.get_all_grabbed_movables() + var/list/grabbing= L.get_all_grabbed_movables() for(var/atom/movable/AM as anything in grabbing) preserve_grab ||= AM AM.forceMove(T) user.forceMove(T) - if(AM) - user.try_make_grab(AM) + if(preserve_grab && isliving(user)) + var/mob/living/L = user + L.try_make_grab(preserve_grab) to_chat(user, span_notice("You blink and find yourself in [get_area_name(T)].")) else diff --git a/code/modules/food_and_drinks/kitchen_machinery/processor.dm b/code/modules/food_and_drinks/kitchen_machinery/processor.dm index ef18baef5a6f..52c275e7de17 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/processor.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/processor.dm @@ -154,7 +154,7 @@ to_chat(user, span_warning("[src] is in the process of processing!")) return TRUE - if(ismobvictim && PROCESSOR_SELECT_RECIPE(victim)) + if(ismob(victim) && PROCESSOR_SELECT_RECIPE(victim)) if(grab.current_grab.damage_stage < GRAB_AGGRESSIVE) to_chat(user, span_warning("You need a better grip to do that!")) return diff --git a/code/modules/grab/grabs/grab_carbon.dm b/code/modules/grab/grab_carbon.dm similarity index 87% rename from code/modules/grab/grabs/grab_carbon.dm rename to code/modules/grab/grab_carbon.dm index a604143c71c9..aaafa736b094 100644 --- a/code/modules/grab/grabs/grab_carbon.dm +++ b/code/modules/grab/grab_carbon.dm @@ -3,5 +3,5 @@ for(var/obj/item/hand_item/grab/grab in held_items) . += grab -/mob/living/carbon/make_grab(atom/movable/target, grab_type = /datum/grab/normal) +/mob/living/carbon/make_grab(atom/movable/target, grab_type = /datum/grab/normal/passive) . = ..() diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index 19eb47a4b1b9..575f888c4352 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -2,7 +2,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) /datum/grab abstract_type = /datum/grab - var/icon + var/icon = 'goon/icons/items/grab.dmi' var/icon_state var/type_name @@ -122,7 +122,8 @@ GLOBAL_LIST_EMPTY(all_grabstates) if (G) let_go_effect(G) G.current_grab = null - qdel(G) + if(!QDELETED(G)) + qdel(G) /datum/grab/proc/on_target_change(obj/item/hand_item/grab/G, old_zone, new_zone) G.special_target_functional = check_special_target(G) @@ -164,22 +165,22 @@ GLOBAL_LIST_EMPTY(all_grabstates) G.is_currently_resolving_hit = TRUE var/combat_mode = G.assailant.combat_mode if(params[RIGHT_CLICK]) - if(on_hit_disarm(G)) + if(on_hit_disarm(G, target)) . = disarm_action || TRUE else if(params[CTRL_CLICK]) - if(on_hit_grab(G)) + if(on_hit_grab(G, target)) . = grab_action || TRUE else if(combat_mode) - if(on_hit_harm(G)) + if(on_hit_harm(G, target)) . = harm_action || TRUE else - if(on_hit_help(G)) + if(on_hit_help(G, target)) . = help_action || TRUE - if(QDELETED(src)) + if(QDELETED(G)) return G.is_currently_resolving_hit = FALSE @@ -225,13 +226,6 @@ GLOBAL_LIST_EMPTY(all_grabstates) affecting.reset_plane_and_layer() -/datum/grab/proc/reset_position(obj/item/hand_item/grab/G) - var/mob/living/carbon/human/affecting = G.affecting - - if(!affecting.buckled) - animate(affecting, pixel_x = 0, pixel_y = 0, 4, 1, LINEAR_EASING) - affecting.reset_plane_and_layer() - // This is called whenever the assailant moves. /datum/grab/proc/assailant_moved(obj/item/hand_item/grab/G) adjust_position(G) @@ -277,7 +271,10 @@ GLOBAL_LIST_EMPTY(all_grabstates) // or by downgrading from the lowest grab state. /datum/grab/proc/let_go_effect(obj/item/hand_item/grab/G) SEND_SIGNAL(G.affecting, COMSIG_ATOM_NO_LONGER_GRABBED, G.assailant) - SEND_SIGNAL(G.assailant, COMSIG_ATOM_NO_LONGER_GRABBING, G.affecting) + SEND_SIGNAL(G.assailant, COMSIG_LIVING_NO_LONGER_GRABBING, G.affecting) + if(G.assailant) + G.assailant.after_grab_release(G.affecting) + update_grab_effects(G, null, TRUE) /datum/grab/proc/update_grab_effects(obj/item/hand_item/grab/G, datum/grab/old_grab, dropping_grab) @@ -419,7 +416,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) if(user.combat_mode) if(harm_action) - context[SCREENTIP_CONTEXT_RMB] = capitalize(harm_action) + context[SCREENTIP_CONTEXT_LMB] = capitalize(harm_action) else if(help_action) context[SCREENTIP_CONTEXT_LMB] = capitalize(help_action) diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index 4d2b550e4655..682cb32645a7 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -1,4 +1,6 @@ /mob/living/proc/can_grab(atom/movable/target, target_zone, defer_hand) + if(throwing || !(mobility_flags & MOBILITY_PULL)) + return FALSE if(!ismob(target) && target.anchored) to_chat(src, span_warning("\The [target] won't budge!")) return FALSE @@ -29,7 +31,7 @@ return FALSE return TRUE -/mob/living/proc/can_be_grabbed(mob/living/grabber, target_zone, force) +/mob/living/can_be_grabbed(mob/living/grabber, target_zone, force) . = ..() if(!.) return @@ -77,17 +79,13 @@ return grab -/mob/living/add_grab(obj/item/hand_item/grab/grab, defer_hand) +/mob/living/proc/add_grab(obj/item/hand_item/grab/grab, defer_hand) for(var/obj/item/hand_item/grab/other_grab in contents) if(other_grab != grab) return FALSE grab.forceMove(src) return TRUE -/mob/living/ProcessGrabs() - if(LAZYLEN(grabbed_by)) - resist() - /mob/living/recheck_grabs(only_pulling = FALSE, z_allowed = FALSE) for(var/obj/item/hand_item/grab/G in get_active_grabs()) var/atom/movable/pulling = G.affecting @@ -102,3 +100,11 @@ qdel(G) return ..() + +/// Called by grab objects when a grab has been released +/mob/living/proc/after_grab_release(atom/movable/old_target) + animate_interact(old_target, INTERACT_UNPULL) + if(ismob(pulling)) + var/mob/living/L = pulling + L.reset_pull_offsets() + update_pull_hud_icon() diff --git a/code/modules/grab/grab_movable.dm b/code/modules/grab/grab_movable.dm index fd24ddd701d2..2bc912416790 100644 --- a/code/modules/grab/grab_movable.dm +++ b/code/modules/grab/grab_movable.dm @@ -12,8 +12,11 @@ return FALSE if(throwing) return FALSE + #warn Impliment pull force + /* Impliment pull force if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) return FALSE + */ return TRUE /atom/movable/proc/buckled_grab_check(var/mob/grabber) @@ -65,4 +68,30 @@ continue var/mob/living/pulled_mob = pulling - set_pull_offsets(pulled_mob) + update_offsets(pulled_mob) + + +/atom/movable/proc/update_offsets() + var/last_pixel_x = pixel_x + var/last_pixel_y = pixel_y + + var/new_pixel_x = base_pixel_x + var/new_pixel_y = base_pixel_y + + if(isturf(loc)) + // Update offsets from grabs. + if(length(grabbed_by)) + for(var/obj/item/hand_item/grab/G as anything in grabbed_by) + var/grab_dir = get_dir(G.assailant, src) + if(grab_dir && G.current_grab.shift > 0) + if(grab_dir & WEST) + new_pixel_x = min(new_pixel_x+G.current_grab.shift, base_pixel_x+G.current_grab.shift) + else if(grab_dir & EAST) + new_pixel_x = max(new_pixel_x-G.current_grab.shift, base_pixel_x-G.current_grab.shift) + if(grab_dir & NORTH) + new_pixel_y = max(new_pixel_y-G.current_grab.shift, base_pixel_y-G.current_grab.shift) + else if(grab_dir & SOUTH) + new_pixel_y = min(new_pixel_y+G.current_grab.shift, base_pixel_y+G.current_grab.shift) + + if(last_pixel_x != new_pixel_x || last_pixel_y != new_pixel_y) + animate(src, pixel_x = new_pixel_x, pixel_y = new_pixel_y, 3, 1, (LINEAR_EASING|EASE_IN)) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index f8a607d1540f..e9c13071103d 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -41,6 +41,8 @@ if(!setup()) return INITIALIZE_HINT_QDEL + update_appearance(UPDATE_ICON_STATE) + var/obj/item/bodypart/BP = get_targeted_bodypart() if(BP) name = "[initial(name)] ([BP.plaintext_zone])" @@ -68,6 +70,7 @@ /obj/item/hand_item/grab/update_icon_state() . = ..() + icon = current_grab.icon if(current_grab.icon_state) icon_state = current_grab.icon_state @@ -88,18 +91,17 @@ // End workaround if (QDELETED(src) || !assailant || !current_grab) return TRUE - if(A.attack_grab(assailant, affecting, src, params) || current_grab.hit_with_grab(src, A, params)) //If there is no use_grab override or if it returns FALSE; then will behave according to intent. + if(A.attack_grab(assailant, affecting, src, params2list(params)) || current_grab.hit_with_grab(src, A, params2list(params))) //If there is no use_grab override or if it returns FALSE; then will behave according to intent. return TRUE return ..() /obj/item/hand_item/grab/Destroy() - if(affecting && assailant) - current_grab.let_go(src) if(affecting) - reset_position() LAZYREMOVE(affecting.grabbed_by, src) - affecting.reset_plane_and_layer() - affecting = null + affecting.update_offsets() + if(affecting && assailant) + current_grab.let_go(src) + affecting = null assailant = null return ..() @@ -147,16 +149,23 @@ // This will run from Initialize, after can_grab and other checks have succeeded. Must call parent; returning FALSE means failure and qdels the grab. /obj/item/hand_item/grab/proc/setup() - if(!assailant.put_in_active_hand(src)) - return FALSE // This should succeed as we checked the hand, but if not we abort here. if(!current_grab.setup(src)) return FALSE + + assailant.update_pull_hud_icon() + LAZYADD(affecting.grabbed_by, src) // This is how we handle affecting being deleted. adjust_position() action_used() - assailant.do_attack_animation(affecting) + assailant.animate_interact(affecting, INTERACT_GRAB) + + var/sound = 'sound/weapons/thudswoosh.ogg' + if(iscarbon(assailant)) + var/mob/living/carbon/C = assailant + if(C.dna.species.grab_sound) + sound = C.dna.species.grab_sound playsound(affecting.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) update_appearance() @@ -240,9 +249,6 @@ else current_grab.adjust_position(src) -/obj/item/hand_item/grab/proc/reset_position() - current_grab.reset_position(src) - /obj/item/hand_item/grab/proc/has_hold_on_bodypart(obj/item/bodypart/BP) if (!BP) return FALSE diff --git a/code/modules/grab/grab_silicon.dm b/code/modules/grab/grab_silicon.dm new file mode 100644 index 000000000000..f13f7d448607 --- /dev/null +++ b/code/modules/grab/grab_silicon.dm @@ -0,0 +1,2 @@ +/mob/living/silcion/try_make_grab(atom/movable/target, grab_type = /datum/grab/simple) + . = ..() diff --git a/code/modules/grab/grabs/grab_aggressive.dm b/code/modules/grab/grabs/grab_aggressive.dm new file mode 100644 index 000000000000..cc8ffbd55a7d --- /dev/null +++ b/code/modules/grab/grabs/grab_aggressive.dm @@ -0,0 +1,43 @@ +/datum/grab/normal/aggressive + //upgrab = /datum/grab/normal/neck + downgrab = /datum/grab/normal/passive + shift = 12 + stop_move = 1 + reverse_facing = 0 + shield_assailant = 0 + point_blank_mult = 1.5 + damage_stage = 1 + same_tile = 0 + can_throw = 1 + force_danger = 1 + breakability = 3 + icon_state = "2" + + break_chance_table = list(5, 20, 40, 80, 100) + +/datum/grab/normal/aggressive/process_effect(obj/item/hand_item/grab/G) + var/mob/living/affecting_mob = G.get_affecting_mob() + if(istype(affecting_mob)) + if(G.target_zone in list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM)) + affecting_mob.drop_all_held_items() + // Keeps those who are on the ground down + if(affecting_mob.body_position == LYING_DOWN) + affecting_mob.Knockdown(4 SECONDS) + +/datum/grab/normal/aggressive/can_upgrade(obj/item/hand_item/grab/G) + . = ..() + if(.) + if(!ishuman(G.affecting)) + to_chat(G.assailant, span_warning("You can only upgrade an aggressive grab when grappling a human!")) + return FALSE + if(!(G.target_zone in list(BODY_ZONE_CHEST, BODY_ZONE_HEAD))) + to_chat(G.assailant, span_warning("You need to be grabbing their torso or head for this!")) + return FALSE + + var/mob/living/carbon/human/affecting_mob = G.get_affecting_mob() + if(istype(affecting_mob)) + var/obj/item/clothing/C = affecting_mob.head + if(istype(C)) //hardsuit helmets etc + if((C.clothing_flags & STOPSPRESSUREDAMAGE) && C.returnArmor().getRating(MELEE) > 20) + to_chat(G.assailant, span_warning("\The [C] is in the way!")) + return FALSE diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index 581ec33cd171..935e30b08648 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -1,4 +1,5 @@ /datum/grab/normal + abstract_type = /datum/grab/normal icon = 'goon/icons/items/grab.dmi' icon_state = "1" @@ -22,17 +23,14 @@ else G.assailant.visible_message(span_notice("[G.assailant] has grabbed [G.assailant.p_their()] [BP.plaintext_zone]!")) -/datum/grab/normal/on_hit_help(var/obj/item/hand_item/grab/G, var/atom/A, var/proximity) +/datum/grab/normal/on_hit_help(obj/item/hand_item/grab/G, atom/A) var/obj/item/bodypart/BP = G.get_targeted_bodypart() - if(!BP || !proximity || (A && A != G.get_affecting_mob())) + if(!BP || (A && A != G.get_affecting_mob())) return FALSE return BP.inspect(G.assailant) -/datum/grab/normal/on_hit_disarm(var/obj/item/hand_item/grab/G, var/atom/A, var/proximity) - - if(!proximity) - return FALSE +/datum/grab/normal/on_hit_disarm(obj/item/hand_item/grab/G, atom/A) var/mob/living/affecting = G.get_affecting_mob() var/mob/living/assailant = G.assailant @@ -47,11 +45,7 @@ return FALSE -/datum/grab/normal/on_hit_grab(var/obj/item/hand_item/grab/G, var/atom/A, var/proximity) - - if(!proximity) - return FALSE - +/datum/grab/normal/on_hit_grab(var/obj/item/hand_item/grab/G, atom/A) var/mob/living/affecting = G.get_affecting_mob() if(!affecting || (A && A != affecting)) return FALSE @@ -76,11 +70,7 @@ affecting.visible_message(span_warning("\The [assailant] fails to jointlock \the [affecting]'s [BP.plaintext_zone].")) return FALSE -/datum/grab/normal/on_hit_harm(var/obj/item/hand_item/grab/G, var/atom/A, var/proximity) - - if(!proximity) - return FALSE - +/datum/grab/normal/on_hit_harm(obj/item/hand_item/grab/G, atom/A) var/mob/living/affecting = G.get_affecting_mob() if(!affecting || (A && A != affecting)) return FALSE diff --git a/code/modules/grab/grabs/grab_passive.dm b/code/modules/grab/grabs/grab_passive.dm new file mode 100644 index 000000000000..8c2d1c1b3b28 --- /dev/null +++ b/code/modules/grab/grabs/grab_passive.dm @@ -0,0 +1,25 @@ +/datum/grab/normal/passive + upgrab = /datum/grab/normal/aggressive + shift = 8 + stop_move = 0 + reverse_facing = 0 + shield_assailant = 0 + point_blank_mult = 1.1 + same_tile = 0 + icon_state = "1" + break_chance_table = list(15, 60, 100) + +/datum/grab/normal/passive/on_hit_disarm(var/obj/item/hand_item/grab/G, var/atom/A) + to_chat(G.assailant, span_warning("Your grip isn't strong enough to pin.")) + return FALSE + +/datum/grab/normal/passive/on_hit_grab(var/obj/item/hand_item/grab/G, var/atom/A) + to_chat(G.assailant, span_warning("Your grip isn't strong enough to jointlock.")) + return FALSE + +/datum/grab/normal/passive/on_hit_harm(var/obj/item/hand_item/grab/G, var/atom/A) + to_chat(G.assailant, span_warning("Your grip isn't strong enough to dislocate.")) + return FALSE + +/datum/grab/normal/passive/resolve_openhand_attack(var/obj/item/hand_item/grab/G) + return FALSE diff --git a/code/modules/grab/grabs/grab_simple.dm b/code/modules/grab/grabs/grab_simple.dm index 6c6ff193022c..83763c2f1db5 100644 --- a/code/modules/grab/grabs/grab_simple.dm +++ b/code/modules/grab/grabs/grab_simple.dm @@ -11,14 +11,14 @@ /datum/grab/simple/upgrade(obj/item/hand_item/grab/G) return -/datum/grab/simple/on_hit_disarm(var/obj/item/hand_item/grab/G, var/atom/A) +/datum/grab/simple/on_hit_disarm(obj/item/hand_item/grab/G, atom/A) return FALSE -/datum/grab/simple/on_hit_grab(var/obj/item/hand_item/grab/G, var/atom/A) +/datum/grab/simple/on_hit_grab(obj/item/hand_item/grab/G, atom/A) return FALSE -/datum/grab/simple/on_hit_harm(var/obj/item/hand_item/grab/G, var/atom/A) +/datum/grab/simple/on_hit_harm(obj/item/hand_item/grab/G, atom/A) return FALSE -/datum/grab/simple/resolve_openhand_attack(var/obj/item/hand_item/grab/G) +/datum/grab/simple/resolve_openhand_attack(obj/item/hand_item/grab/G) return FALSE diff --git a/code/modules/grab/human_grab.dm b/code/modules/grab/human_grab.dm index 6cb029f25874..adf8c3fbfdcb 100644 --- a/code/modules/grab/human_grab.dm +++ b/code/modules/grab/human_grab.dm @@ -32,5 +32,5 @@ if(istype(C)) C.add_fingerprint(grabber) else - C.add_fingerprint(grabber) + add_fingerprint(grabber) diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm index afe80cc9a4f6..2c8238cd69dc 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva.dm @@ -69,8 +69,8 @@ /mob/living/carbon/alien/larva/toggle_throw_mode() return -/mob/living/carbon/alien/larva/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) - return +/mob/living/carbon/alien/larva/can_grab(atom/movable/target, target_zone, defer_hand) + return FALSE /mob/living/carbon/alien/larva/canBeHandcuffed() return TRUE diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 90e6080bacca..d666cfce50e0 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -153,17 +153,20 @@ var/obj/item/I = get_active_held_item() var/neckgrab_throw = FALSE // we can't check for if it's a neckgrab throw when totaling up power_throw since we've already stopped pulling them by then, so get it early - if(!I) - if(pulling && isliving(pulling) && grab_state >= GRAB_AGGRESSIVE) - var/mob/living/throwable_mob = pulling - if(!throwable_mob.buckled) - thrown_thing = throwable_mob - if(grab_state >= GRAB_NECK) - neckgrab_throw = TRUE - release_all_grabs() - if(HAS_TRAIT(src, TRAIT_PACIFISM)) - to_chat(src, span_notice("You gently let go of [throwable_mob].")) - return + if(isgrab(I)) + var/obj/item/hand_item/grab/G = I + if(!G.current_grab.can_throw) + return + + var/mob/living/throwable_mob = G.affecting + if(!throwable_mob.buckled) + thrown_thing = throwable_mob + if(G.current_grab.damage_stage >= GRAB_NECK) + neckgrab_throw = TRUE + release_all_grabs() + if(HAS_TRAIT(src, TRAIT_PACIFISM)) + to_chat(src, span_notice("You gently let go of [throwable_mob].")) + return else thrown_thing = I.on_thrown(src, target) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 5ed5a786fd29..6549bbb0c0b2 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -603,30 +603,6 @@ if (!IS_ORGANIC_LIMB(limb)) . += (limb.brute_dam) + (limb.burn_dam) -/mob/living/carbon/grabbedby(mob/living/carbon/user, supress_message = FALSE) - if(user != src) - return ..() - - var/obj/item/bodypart/grasped_part = get_bodypart(zone_selected) - if(!grasped_part?.get_modified_bleed_rate()) - return - var/starting_hand_index = active_hand_index - if(starting_hand_index == grasped_part.held_index) - to_chat(src, span_danger("You can't grasp your [grasped_part.name] with itself!")) - return - - to_chat(src, span_warning("You try grasping at your [grasped_part.name], trying to stop the bleeding...")) - if(!do_after(src, time = 0.75 SECONDS)) - to_chat(src, span_danger("You fail to grasp your [grasped_part.name].")) - return - - var/obj/item/hand_item/self_grasp/grasp = new - if(starting_hand_index != active_hand_index || !put_in_active_hand(grasp)) - to_chat(src, span_danger("You fail to grasp your [grasped_part.name].")) - QDEL_NULL(grasp) - return - grasp.grasp_limb(grasped_part) - /// an abstract item representing you holding your own limb to staunch the bleeding, see [/mob/living/carbon/proc/grabbedby] will probably need to find somewhere else to put this. /obj/item/hand_item/self_grasp name = "self-grasp" diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index dd7e7053b9e8..03e406ec651c 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -98,9 +98,6 @@ if(has_status_effect(/datum/status_effect/fire_handler/wet_stacks)) msg += "[t_He] look[p_s()] a little soaked.\n" - if(pulledby?.grab_state) - msg += "[t_He] [t_is] restrained by [pulledby]'s grip.\n" - msg += "" . += msg.Join("") diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index aa4aef23d6fa..2cfb28654ca9 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -209,9 +209,9 @@ for(var/obj/item/hand_item/grab/G in grabbed_by) - if(G.current_grab.can_move) + if(!G.current_grab.stop_move) continue - msg += "[t_He] [t_is] restrained by [pulledby]'s grip.\n" + msg += "[t_He] [t_is] restrained by [G.assailant]'s grip.\n" if(nutrition < NUTRITION_LEVEL_STARVING - 50) msg += "[t_He] [t_is] severely malnourished.\n" diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index d4ac1cb22c0f..cb19b53434b0 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -163,12 +163,6 @@ return ..() -/mob/living/carbon/human/grippedby(mob/living/user, instant = FALSE) - if(w_uniform) - w_uniform.add_fingerprint(user) - ..() - - /mob/living/carbon/human/attacked_by(obj/item/I, mob/living/user) if(!I || !user) return MOB_ATTACKEDBY_FAIL diff --git a/code/modules/mob/living/carbon/human/human_stripping.dm b/code/modules/mob/living/carbon/human/human_stripping.dm index b0dd29eefbd6..bbbbf0835669 100644 --- a/code/modules/mob/living/carbon/human/human_stripping.dm +++ b/code/modules/mob/living/carbon/human/human_stripping.dm @@ -23,7 +23,7 @@ GLOBAL_LIST_INIT(strippable_human_items, create_strippable_list(list( /datum/strippable_item/mob_item_slot/legcuffs, ))) -/mob/living/carbon/human/should_strip(mob/user) +/mob/living/carbon/human/should_strip(mob/living/user) . = ..() if (!.) return FALSE diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 0036cb65704e..578b2ccd24e8 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -961,7 +961,7 @@ GLOBAL_LIST_EMPTY(features_by_species) if(attacker_style?.grab_act(user,target) == MARTIAL_ATTACK_SUCCESS) return TRUE else - target.grabbedby(user) + user.try_make_grab(target) return TRUE ///This proc handles punching damage. diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 3e341ba8bc28..45f8830564a5 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -297,7 +297,8 @@ if(current_dir) AM.setDir(current_dir) now_pushing = FALSE - +#warn old start_pulling stuff +/* /mob/living/start_pulling(atom/movable/AM, state, force = pull_force, supress_message = FALSE) if(!AM || !src) return FALSE @@ -387,39 +388,12 @@ update_pull_movespeed() set_pull_offsets(M, state) +*/ -/mob/living/proc/set_pull_offsets(mob/living/M, grab_state = GRAB_PASSIVE) - if(M.buckled) - return //don't make them change direction or offset them if they're buckled into something. - var/offset = 0 - switch(grab_state) - if(GRAB_PASSIVE) - offset = GRAB_PIXEL_SHIFT_PASSIVE - if(GRAB_AGGRESSIVE) - offset = GRAB_PIXEL_SHIFT_AGGRESSIVE - if(GRAB_NECK) - offset = GRAB_PIXEL_SHIFT_NECK - if(GRAB_KILL) - offset = GRAB_PIXEL_SHIFT_NECK - M.setDir(get_dir(M, src)) - switch(M.dir) - if(NORTH) - animate(M, pixel_x = M.base_pixel_x, pixel_y = M.base_pixel_y + offset, 3) - if(SOUTH) - animate(M, pixel_x = M.base_pixel_x, pixel_y = M.base_pixel_y - offset, 3) - if(EAST) - if(M.lying_angle == 270) //update the dragged dude's direction if we've turned - M.set_lying_angle(90) - animate(M, pixel_x = M.base_pixel_x + offset, pixel_y = M.base_pixel_y, 3) - if(WEST) - if(M.lying_angle == 90) - M.set_lying_angle(270) - animate(M, pixel_x = M.base_pixel_x - offset, pixel_y = M.base_pixel_y, 3) - -/mob/living/proc/reset_pull_offsets(mob/living/M, override) - if(!override && M.buckled) +/mob/living/proc/reset_pull_offsets(override) + if(!override && buckled) return - animate(M, pixel_x = M.base_pixel_x, pixel_y = M.base_pixel_y, 1) + animate(src, pixel_x = src.base_pixel_x, pixel_y = src.base_pixel_y, 1) //mob verbs are a lot faster than object verbs //for more info on why this is not atom/pull, see examinate() in mob.dm @@ -428,15 +402,7 @@ set category = "Object" if(istype(AM) && Adjacent(AM)) - start_pulling(AM) - -/mob/living/stop_pulling() - animate_interact(pulling, INTERACT_UNPULL) - if(ismob(pulling)) - reset_pull_offsets(pulling) - ..() - update_pull_movespeed() - update_pull_hud_icon() + try_make_grab(AM) /mob/living/verb/stop_pulling1() set name = "Stop Pulling" @@ -488,8 +454,12 @@ if(!(flags & IGNORE_RESTRAINTS) && HAS_TRAIT(src, TRAIT_RESTRAINED)) return TRUE - if(!(flags & IGNORE_GRAB) && pulledby && pulledby.grab_state >= GRAB_AGGRESSIVE) - return TRUE + + if(!(flags & IGNORE_GRAB)) + for(var/obj/item/hand_item/grab/G in grabbed_by) + if(G.current_grab.restrains) + return TRUE + if(!(flags & IGNORE_STASIS) && IS_IN_HARD_STASIS(src)) return TRUE return FALSE @@ -908,9 +878,8 @@ . = ..() - if(moving_diagonally != FIRST_DIAG_STEP && isliving(pulledby)) - var/mob/living/L = pulledby - L.set_pull_offsets(src, pulledby.grab_state) + if(moving_diagonally != FIRST_DIAG_STEP) + update_offsets() if(active_storage && !((active_storage.parent?.resolve() in important_recursive_contents?[RECURSIVE_CONTENTS_ACTIVE_STORAGE]) || CanReach(active_storage.parent?.resolve(),view_only = TRUE))) active_storage.hide_contents(src) @@ -1036,8 +1005,7 @@ SEND_SIGNAL(src, COMSIG_LIVING_RESIST, src) //resisting grabs (as if it helps anyone...) - if(!HAS_TRAIT(src, TRAIT_RESTRAINED) && pulledby) - log_combat(src, pulledby, "resisted grab") + if(!HAS_TRAIT(src, TRAIT_RESTRAINED) && LAZYLEN(grabbed_by)) resist_grab() return @@ -1069,6 +1037,7 @@ visible_message(span_danger("\The [src] struggles to break free!")) for(var/obj/item/hand_item/grab/G as anything in grabbed_by) + log_combat(src, G.assailant, "resisted grab") . = G.handle_resist() || . /mob/living/proc/resist_buckle() diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm index cfe982af22bc..f8849c360c19 100644 --- a/code/modules/mob/living/living_movement.dm +++ b/code/modules/mob/living/living_movement.dm @@ -96,10 +96,10 @@ remove_movespeed_modifier(/datum/movespeed_modifier/turf_slowdown) current_turf_slowdown = 0 - /mob/living/proc/update_pull_movespeed() SEND_SIGNAL(src, COMSIG_LIVING_UPDATING_PULL_MOVESPEED) - +#warn drag movespeed +/* if(pulling) if(isliving(pulling)) var/mob/living/L = pulling @@ -116,6 +116,7 @@ add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/bulky_drag, multiplicative_slowdown = S.drag_slowdown) return remove_movespeed_modifier(/datum/movespeed_modifier/bulky_drag) +*/ /** * We want to relay the zmovement to the buckled atom when possible diff --git a/code/modules/mob/living/silicon/pai/pai_shell.dm b/code/modules/mob/living/silicon/pai/pai_shell.dm index 5ec8d31fbda4..deac77c2ce66 100644 --- a/code/modules/mob/living/silicon/pai/pai_shell.dm +++ b/code/modules/mob/living/silicon/pai/pai_shell.dm @@ -139,7 +139,7 @@ if(loc != card) visible_message(span_notice("[src] [resting? "lays down for a moment..." : "perks up from the ground."]")) -/mob/living/silicon/pai/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) +/mob/living/silicon/pai/try_make_grab(atom/movable/target, grab_type) return FALSE /mob/living/silicon/pai/proc/toggle_integrated_light() diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm index cde9424d34f8..65145599894e 100644 --- a/code/modules/mob/living/silicon/silicon_defense.dm +++ b/code/modules/mob/living/silicon/silicon_defense.dm @@ -1,7 +1,3 @@ - -/mob/living/silicon/grippedby(mob/living/user, instant = FALSE) - return //can't upgrade a simple pull into a more aggressive grab. - /mob/living/silicon/get_ear_protection()//no ears return 2 diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index 4ca684cda8d5..cfb20848fe2c 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -422,13 +422,14 @@ /mob/living/simple_animal/hostile/construct/harvester/Bump(atom/AM) . = ..() if(istype(AM, /turf/closed/wall/mineral/cult) && AM != loc) //we can go through cult walls - var/atom/movable/stored_pulling = pulling + var/atom/movable/stored_pulling = get_active_grab()?.affecting + var/datum/grab/stored_tier = get_active_grab()?.current_grab.type if(stored_pulling) stored_pulling.setDir(get_dir(stored_pulling.loc, loc)) stored_pulling.forceMove(loc) forceMove(AM) if(stored_pulling) - start_pulling(stored_pulling, supress_message = TRUE) //drag anything we're pulling through the wall with us by magic + try_make_grab(stored_pulling, stored_pulling) /mob/living/simple_animal/hostile/construct/harvester/AttackingTarget() if(iscarbon(target)) diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index 05c1e3fbd938..fd1e6d7d548c 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -270,7 +270,7 @@ /mob/living/simple_animal/slime/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE) return -/mob/living/simple_animal/slime/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) +/mob/living/simple_animal/slime/try_make_grab(atom/movable/target, grab_type) return /mob/living/simple_animal/slime/attack_ui(slot, params) diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 68703e071b04..9051809edfe7 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -179,7 +179,7 @@ COOLDOWN_START(src, move_delay, 1 SECONDS) to_chat(src, span_warning("You're restrained! You can't move!")) return FALSE - return mob.resist_grab(TRUE) + return !mob.resist_grab(TRUE) /** diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm index aef30e4e1e92..12fd3b3d0206 100644 --- a/code/modules/mod/modules/modules_supply.dm +++ b/code/modules/mod/modules/modules_supply.dm @@ -324,7 +324,7 @@ if(!istype(target, /obj/structure/closet) || !(target in view(mod.wearer))) balloon_alert(mod.wearer, "invalid target!") return - var/obj/structure/closet/locker = target + if(locker.anchored || locker.move_resist >= MOVE_FORCE_OVERPOWERING) balloon_alert(mod.wearer, "target anchored!") return @@ -346,7 +346,7 @@ return if(!locker.Adjacent(mod.wearer) || !isturf(locker.loc) || !isturf(mod.wearer.loc)) return - mod.wearer.start_pulling(locker) + mod.wearer.try_make_grab(locker) locker.strong_grab = TRUE RegisterSignal(locker, COMSIG_ATOM_NO_LONGER_GRABBED, PROC_REF(on_stop_pull)) diff --git a/code/modules/recycling/conveyor.dm b/code/modules/recycling/conveyor.dm index 8d1724e735d5..b87445232e99 100644 --- a/code/modules/recycling/conveyor.dm +++ b/code/modules/recycling/conveyor.dm @@ -295,11 +295,13 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) // attack with hand, move pulled object onto conveyor -/obj/machinery/conveyor/attack_hand(mob/user, list/modifiers) +/obj/machinery/conveyor/attack_hand(mob/living/user, list/modifiers) . = ..() if(.) return - user.move_grabbed_atoms_towards(src) + + if(!istype(user)) + user.move_grabbed_atoms_towards(src) /obj/machinery/conveyor/power_change() . = ..() diff --git a/code/modules/research/designs/wiremod_designs.dm b/code/modules/research/designs/wiremod_designs.dm index dc5443e58006..4ae96e9a1b21 100644 --- a/code/modules/research/designs/wiremod_designs.dm +++ b/code/modules/research/designs/wiremod_designs.dm @@ -218,11 +218,6 @@ id = "comp_split" build_path = /obj/item/circuit_component/split -/datum/design/component/pull - name = "Pull Component" - id = "comp_pull" - build_path = /obj/item/circuit_component/pull - /datum/design/component/soundemitter name = "Sound Emitter Component" id = "comp_soundemitter" diff --git a/code/modules/unit_tests/chain_pull_through_space.dm b/code/modules/unit_tests/chain_pull_through_space.dm index b718da91c2c6..ccbed364ca06 100644 --- a/code/modules/unit_tests/chain_pull_through_space.dm +++ b/code/modules/unit_tests/chain_pull_through_space.dm @@ -42,8 +42,8 @@ /datum/unit_test/chain_pull_through_space/Run() // Alice pulls Bob, who pulls Charlie // Normally, when Alice moves forward, the rest follow - alice.start_pulling(bob) - bob.start_pulling(charlie) + alice.try_make_grab(bob) + bob.try_make_grab(charlie) // Walk normally to the left, make sure we're still a chain alice.Move(locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) diff --git a/code/modules/wiremod/components/action/pull.dm b/code/modules/wiremod/components/action/pull.dm deleted file mode 100644 index 8c83e938b00a..000000000000 --- a/code/modules/wiremod/components/action/pull.dm +++ /dev/null @@ -1,27 +0,0 @@ -/** - * # Pull Component - * - * Tells the shell to start pulling on a designated atom. Only works on movable shells. - */ -/obj/item/circuit_component/pull - display_name = "Start Pulling" - desc = "A component that can force the shell to pull entities. Only works for drone shells." - category = "Action" - - /// Frequency input - var/datum/port/input/target - circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL - -/obj/item/circuit_component/pull/populate_ports() - target = add_input_port("Target", PORT_TYPE_ATOM) - -/obj/item/circuit_component/pull/input_received(datum/port/input/port) - var/atom/target_atom = target.value - if(!target_atom) - return - - var/mob/shell = parent.shell - if(!istype(shell) || get_dist(shell, target_atom) > 1 || shell.z != target_atom.z) - return - - INVOKE_ASYNC(shell, TYPE_PROC_REF(/atom/movable, start_pulling), target_atom) diff --git a/daedalus.dme b/daedalus.dme index 9fe65fa5dfc9..16470751e071 100644 --- a/daedalus.dme +++ b/daedalus.dme @@ -2979,14 +2979,17 @@ #include "code\modules\food_and_drinks\restaurant\custom_order.dm" #include "code\modules\food_and_drinks\restaurant\generic_venues.dm" #include "code\modules\food_and_drinks\restaurant\customers\_customer.dm" +#include "code\modules\grab\grab_carbon.dm" #include "code\modules\grab\grab_datum.dm" #include "code\modules\grab\grab_helpers.dm" #include "code\modules\grab\grab_living.dm" #include "code\modules\grab\grab_movable.dm" #include "code\modules\grab\grab_object.dm" +#include "code\modules\grab\grab_silicon.dm" #include "code\modules\grab\human_grab.dm" -#include "code\modules\grab\grabs\grab_carbon.dm" +#include "code\modules\grab\grabs\grab_aggressive.dm" #include "code\modules\grab\grabs\grab_normal.dm" +#include "code\modules\grab\grabs\grab_passive.dm" #include "code\modules\grab\grabs\grab_simple.dm" #include "code\modules\holiday\easter.dm" #include "code\modules\holiday\foreign_calendar.dm" @@ -4517,7 +4520,6 @@ #include "code\modules\wiremod\components\action\mmi.dm" #include "code\modules\wiremod\components\action\pathfind.dm" #include "code\modules\wiremod\components\action\printer.dm" -#include "code\modules\wiremod\components\action\pull.dm" #include "code\modules\wiremod\components\action\radio.dm" #include "code\modules\wiremod\components\action\soundemitter.dm" #include "code\modules\wiremod\components\action\speech.dm" From f2dea1c2758198a5063da7f04871afbd1c5e535a Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Tue, 26 Sep 2023 21:35:40 -0400 Subject: [PATCH 06/37] tweaks and stuff --- .../structures/crates_lockers/closets.dm | 3 ++- code/modules/grab/grab_datum.dm | 5 +++- code/modules/grab/grab_living.dm | 2 +- code/modules/grab/grab_object.dm | 2 +- code/modules/grab/grabs/grab_normal.dm | 26 +++++++++++++------ code/modules/grab/grabs/grab_passive.dm | 8 +++--- .../mob/living/carbon/carbon_context.dm | 7 ++--- .../mob/living/carbon/human/human_context.dm | 6 ++--- 8 files changed, 35 insertions(+), 24 deletions(-) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 2153a8dfb36a..d1672fb5462a 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -212,7 +212,8 @@ if(welded || locked) return FALSE if(strong_grab) - to_chat(user, span_danger("[grabbed_by[1].assailant] has an incredibly strong grip on [src], preventing it from opening.")) + var/obj/item/hand_item/grab/G = grabbed_by[1] + to_chat(user, span_danger("[G.assailant] has an incredibly strong grip on [src], preventing it from opening.")) return FALSE var/turf/T = get_turf(src) for(var/mob/living/L in T) diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index 575f888c4352..775cac49b30a 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -407,7 +407,10 @@ GLOBAL_LIST_EMPTY(all_grabstates) return /// Add screentip context, user will always be assailant. -/datum/grab/proc/add_context(list/context, mob/living/user) +/datum/grab/proc/add_context(list/context, obj/item/held_item, mob/living/user, atom/movable/target) + if(!(isgrab(held_item) && held_item:current_grab == src)) + return + if(disarm_action) context[SCREENTIP_CONTEXT_RMB] = capitalize(disarm_action) diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index 682cb32645a7..070a4946b17e 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -11,7 +11,7 @@ return FALSE else if(get_active_held_item()) - to_chat(src, span_warning("Your [get_active_hand().plaintext_zone] is full!")) + to_chat(src, span_warning("Your [active_hand_index % 2 ? "right" : "left"] hand is full!")) return FALSE if(LAZYLEN(grabbed_by)) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index e9c13071103d..243dc616cd96 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -167,7 +167,7 @@ if(C.dna.species.grab_sound) sound = C.dna.species.grab_sound - playsound(affecting.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + playsound(affecting.loc, sound, 50, 1, -1) update_appearance() current_grab.update_grab_effects(src, null) return TRUE diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index 935e30b08648..388d99f06603 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -45,7 +45,7 @@ return FALSE -/datum/grab/normal/on_hit_grab(var/obj/item/hand_item/grab/G, atom/A) +/datum/grab/normal/on_hit_grab(obj/item/hand_item/grab/G, atom/A) var/mob/living/affecting = G.get_affecting_mob() if(!affecting || (A && A != affecting)) return FALSE @@ -102,7 +102,7 @@ to_chat(assailant, span_warning("You can't dislocate \the [affecting]'s [BP.joint_name]!")) return FALSE -/datum/grab/normal/resolve_openhand_attack(var/obj/item/hand_item/grab/G) +/datum/grab/normal/resolve_openhand_attack(obj/item/hand_item/grab/G) if(!G.assailant.combat_mode) return FALSE if(G.target_zone == BODY_ZONE_HEAD) @@ -116,7 +116,7 @@ return TRUE return FALSE -/datum/grab/normal/proc/attack_eye(var/obj/item/hand_item/grab/G) +/datum/grab/normal/proc/attack_eye(obj/item/hand_item/grab/G) var/mob/living/carbon/human/target = G.get_affecting_mob() var/mob/living/carbon/human/attacker = G.assailant if(!istype(target) || !istype(attacker)) @@ -144,7 +144,7 @@ attacker.visible_message(span_danger("\The [attacker] attempts to press [G.p_their()] fingers into \the [target]'s [E.name], but [target.p_they()] doesn't have any!")) return TRUE -/datum/grab/normal/proc/headbutt(var/obj/item/hand_item/grab/G) +/datum/grab/normal/proc/headbutt(obj/item/hand_item/grab/G) var/mob/living/carbon/human/target = G.get_affecting_mob() var/mob/living/carbon/human/attacker = G.assailant if(!istype(target) || !istype(attacker)) @@ -193,7 +193,7 @@ affecting_mob.blind_eyes(2 SECONDS) // Handles when they change targeted areas and something is supposed to happen. -/datum/grab/normal/special_target_change(var/obj/item/hand_item/grab/G, old_zone, new_zone) +/datum/grab/normal/special_target_change(obj/item/hand_item/grab/G, old_zone, new_zone) if((old_zone != BODY_ZONE_HEAD && old_zone != BODY_ZONE_CHEST) || !G.get_affecting_mob()) return switch(new_zone) @@ -217,14 +217,14 @@ return FALSE return TRUE -/datum/grab/normal/resolve_item_attack(var/obj/item/hand_item/grab/G, var/mob/living/carbon/human/user, var/obj/item/I) +/datum/grab/normal/resolve_item_attack(obj/item/hand_item/grab/G, mob/living/carbon/human/user, obj/item/I) switch(G.target_zone) if(BODY_ZONE_HEAD) return attack_throat(G, I, user) else return attack_tendons(G, I, user, G.target_zone) -/datum/grab/normal/proc/attack_throat(var/obj/item/hand_item/grab/G, var/obj/item/W, mob/living/user) +/datum/grab/normal/proc/attack_throat(obj/item/hand_item/grab/G, obj/item/W, mob/living/user) var/mob/living/carbon/affecting = G.get_affecting_mob() if(!istype(affecting)) return @@ -271,7 +271,7 @@ log_combat(user, affecting, "slit throat (grab)") return 1 -/datum/grab/normal/proc/attack_tendons(var/obj/item/hand_item/grab/G, var/obj/item/W, mob/living/user, var/target_zone) +/datum/grab/normal/proc/attack_tendons(obj/item/hand_item/grab/G, obj/item/W, mob/living/user, target_zone) var/mob/living/affecting = G.get_affecting_mob() if(!affecting) return @@ -304,3 +304,13 @@ G.last_action = world.time log_combat(user, affecting, "hamstrung (grab)") return TRUE + +/datum/grab/normal/add_context(list/context, obj/item/held_item, mob/living/user, atom/movable/target) + . = ..() + if(held_item.sharpness & SHARP_EDGED) + var/obj/item/hand_item/grab/G = user.is_grabbing(target) + switch(G.target_zone) + if(BODY_ZONE_HEAD) + context[SCREENTIP_CONTEXT_LMB] = "Slice neck" + else + context[SCREENTIP_CONTEXT_LMB] = "Attack tendons" diff --git a/code/modules/grab/grabs/grab_passive.dm b/code/modules/grab/grabs/grab_passive.dm index 8c2d1c1b3b28..db468d7fdfb0 100644 --- a/code/modules/grab/grabs/grab_passive.dm +++ b/code/modules/grab/grabs/grab_passive.dm @@ -9,17 +9,17 @@ icon_state = "1" break_chance_table = list(15, 60, 100) -/datum/grab/normal/passive/on_hit_disarm(var/obj/item/hand_item/grab/G, var/atom/A) +/datum/grab/normal/passive/on_hit_disarm(obj/item/hand_item/grab/G, atom/A) to_chat(G.assailant, span_warning("Your grip isn't strong enough to pin.")) return FALSE -/datum/grab/normal/passive/on_hit_grab(var/obj/item/hand_item/grab/G, var/atom/A) +/datum/grab/normal/passive/on_hit_grab(obj/item/hand_item/grab/G, atom/A) to_chat(G.assailant, span_warning("Your grip isn't strong enough to jointlock.")) return FALSE -/datum/grab/normal/passive/on_hit_harm(var/obj/item/hand_item/grab/G, var/atom/A) +/datum/grab/normal/passive/on_hit_harm(obj/item/hand_item/grab/G, atom/A) to_chat(G.assailant, span_warning("Your grip isn't strong enough to dislocate.")) return FALSE -/datum/grab/normal/passive/resolve_openhand_attack(var/obj/item/hand_item/grab/G) +/datum/grab/normal/passive/resolve_openhand_attack(obj/item/hand_item/grab/G) return FALSE diff --git a/code/modules/mob/living/carbon/carbon_context.dm b/code/modules/mob/living/carbon/carbon_context.dm index 3ea764aa043f..2577f15a55dd 100644 --- a/code/modules/mob/living/carbon/carbon_context.dm +++ b/code/modules/mob/living/carbon/carbon_context.dm @@ -1,7 +1,7 @@ /mob/living/carbon/add_context(atom/source, list/context, obj/item/held_item, mob/user) . = ..() - if (!isnull(held_item)) + if (!isnull(held_item) && !(held_item.item_flags & (ABSTRACT|HAND_ITEM))) context[SCREENTIP_CONTEXT_CTRL_SHIFT_LMB] = "Offer item" return CONTEXTUAL_SCREENTIP_SET @@ -15,11 +15,8 @@ else if (human_user == src) context[SCREENTIP_CONTEXT_LMB] = "Check injuries" - if (get_bodypart(human_user.zone_selected)?.get_modified_bleed_rate()) - context[SCREENTIP_CONTEXT_CTRL_LMB] = "Grab limb" - if (human_user != src) - context[SCREENTIP_CONTEXT_RMB] = "Shove" + context[SCREENTIP_CONTEXT_RMB] = "Disarm" if (!human_user.combat_mode) if (body_position == STANDING_UP) diff --git a/code/modules/mob/living/carbon/human/human_context.dm b/code/modules/mob/living/carbon/human/human_context.dm index adaeee86dc87..eaaac631a911 100644 --- a/code/modules/mob/living/carbon/human/human_context.dm +++ b/code/modules/mob/living/carbon/human/human_context.dm @@ -7,10 +7,10 @@ if (user == src) return . + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Grab" + var/obj/item/hand_item/grab/G = user.is_grabbing(src) if (G) - G.current_grab.add_context(context, user) - else - context[SCREENTIP_CONTEXT_CTRL_LMB] = "Pull" + G.current_grab.add_context(context, held_item, user, src) return CONTEXTUAL_SCREENTIP_SET From 44bba23c3ae89421b2a894e4106371cf448de4f2 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Tue, 26 Sep 2023 22:39:55 -0400 Subject: [PATCH 07/37] make these things work without processing --- code/__DEFINES/grab_defines.dm | 2 + code/modules/grab/grab_datum.dm | 55 +++++++++++++--------- code/modules/grab/grab_object.dm | 35 +++++++++++--- code/modules/grab/grabs/grab_aggressive.dm | 27 +++++++---- code/modules/grab/grabs/grab_normal.dm | 17 ++++--- daedalus.dme | 1 + 6 files changed, 93 insertions(+), 44 deletions(-) create mode 100644 code/__DEFINES/grab_defines.dm diff --git a/code/__DEFINES/grab_defines.dm b/code/__DEFINES/grab_defines.dm new file mode 100644 index 000000000000..902a501d1fe3 --- /dev/null +++ b/code/__DEFINES/grab_defines.dm @@ -0,0 +1,2 @@ +/// Trait source for aggressive grabs +#define AGGRESSIVE_GRAB "trait_aggressive_grab" diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index 775cac49b30a..dcda28294080 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -119,6 +119,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) return /datum/grab/proc/let_go(obj/item/hand_item/grab/G) + SHOULD_NOT_OVERRIDE(TRUE) if (G) let_go_effect(G) G.current_grab = null @@ -126,14 +127,11 @@ GLOBAL_LIST_EMPTY(all_grabstates) qdel(G) /datum/grab/proc/on_target_change(obj/item/hand_item/grab/G, old_zone, new_zone) + remove_bodyzone_effects(G) G.special_target_functional = check_special_target(G) if(G.special_target_functional) - special_target_change(G, old_zone, new_zone) - special_target_effect(G) - -/datum/grab/process(obj/item/hand_item/grab/G) - special_target_effect(G) - process_effect(G) + special_bodyzone_change(G, old_zone, new_zone) + special_bodyzone_effects(G) /datum/grab/proc/throw_held(obj/item/hand_item/grab/G) if(G.assailant == G.affecting) @@ -241,7 +239,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) // What happens when you upgrade from one grab state to the next. /datum/grab/proc/upgrade_effect(obj/item/hand_item/grab/G, datum/grab/old_grab) - update_grab_effects(G, old_grab) + update_stage_effects(G, old_grab) // Conditions to see if upgrading is possible /datum/grab/proc/can_upgrade(obj/item/hand_item/grab/G) @@ -261,7 +259,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) // What happens when you downgrade from one grab state to the next. /datum/grab/proc/downgrade_effect(obj/item/hand_item/grab/G, datum/grab/old_grab) - update_grab_effects(G, old_grab) + update_stage_effects(G, old_grab) // Conditions to see if downgrading is possible /datum/grab/proc/can_downgrade(obj/item/hand_item/grab/G) @@ -272,40 +270,51 @@ GLOBAL_LIST_EMPTY(all_grabstates) /datum/grab/proc/let_go_effect(obj/item/hand_item/grab/G) SEND_SIGNAL(G.affecting, COMSIG_ATOM_NO_LONGER_GRABBED, G.assailant) SEND_SIGNAL(G.assailant, COMSIG_LIVING_NO_LONGER_GRABBING, G.affecting) + + remove_bodyzone_effects(G) + if(G.is_grab_unique(src)) + remove_grab_effects(G) + update_stage_effects(G, null, TRUE) + if(G.assailant) G.assailant.after_grab_release(G.affecting) - update_grab_effects(G, null, TRUE) - -/datum/grab/proc/update_grab_effects(obj/item/hand_item/grab/G, datum/grab/old_grab, dropping_grab) +/// Add effects that apply based on damage_stage here +/datum/grab/proc/update_stage_effects(obj/item/hand_item/grab/G, datum/grab/old_grab, dropping_grab) var/old_damage_stage = old_grab?.damage_stage || 0 switch(!dropping_grab || damage_stage) // Current state. if(GRAB_PASSIVE) - REMOVE_TRAIT(G.affecting, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) - REMOVE_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT) + REMOVE_TRAIT(G.affecting, TRAIT_IMMOBILIZED, REF(G)) + REMOVE_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, REF(G)) if(old_damage_stage >= GRAB_NECK) // Previous state was a a neck-grab or higher. - REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, REF(G)) if(GRAB_AGGRESSIVE) if(old_damage_stage >= GRAB_NECK) // Grab got downgraded. - REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, REF(G)) else // Grab got upgraded from a passive one. - ADD_TRAIT(G.affecting, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) - ADD_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT) + ADD_TRAIT(G.affecting, TRAIT_IMMOBILIZED, REF(G)) + ADD_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, REF(G)) if(GRAB_NECK, GRAB_KILL) if(old_damage_stage <= GRAB_AGGRESSIVE) - ADD_TRAIT(G.affecting, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + ADD_TRAIT(G.affecting, TRAIT_FLOORED, REF(G)) + +/// Apply effects to people here. Remove them in remove_grab_effects() +/datum/grab/proc/apply_grab_effects(obj/item/hand_item/grab/G) + +/datum/grab/proc/remove_grab_effects(obj/item/hand_item/grab/G) -// What happens each tic when process is called. -/datum/grab/proc/process_effect(obj/item/hand_item/grab/G) +/// Handles special targeting like eyes and mouth being covered. +/// CLEAR OUT ANY EFFECTS USING remove_bodyzone_effects() +/datum/grab/proc/special_bodyzone_effects(obj/item/hand_item/grab/G) -// Handles special targeting like eyes and mouth being covered. -/datum/grab/proc/special_target_effect(obj/item/hand_item/grab/G) +/// Clear out any effects from special_bodyzone_effects() +/datum/grab/proc/remove_bodyzone_effects(obj/item/hand_item/grab/G) // Handles when they change targeted areas and something is supposed to happen. -/datum/grab/proc/special_target_change(obj/item/hand_item/grab/G, diff_zone) +/datum/grab/proc/special_bodyzone_change(obj/item/hand_item/grab/G, diff_zone) // Checks if the special target works on the grabbed humanoid. /datum/grab/proc/check_special_target(obj/item/hand_item/grab/G) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 243dc616cd96..288210142b29 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -74,9 +74,6 @@ if(current_grab.icon_state) icon_state = current_grab.icon_state -/obj/item/hand_item/grab/process() - current_grab.process(src) - /obj/item/hand_item/grab/attack_self(mob/user) if (!assailant) return @@ -109,7 +106,7 @@ This section is for newly defined useful procs. */ -/obj/item/hand_item/grab/proc/on_target_change(datum/source, old_sel, new_sel) +/obj/item/hand_item/grab/proc/on_target_change(datum/source, new_sel) SIGNAL_HANDLER if(src != assailant.get_active_held_item()) @@ -131,6 +128,7 @@ if(!isbodypart(get_targeted_bodypart())) current_grab.let_go(src) return + current_grab.on_target_change(src, old_zone, target_zone) /obj/item/hand_item/grab/proc/on_limb_loss(mob/victim, obj/item/bodypart/lost) @@ -169,13 +167,13 @@ playsound(affecting.loc, sound, 50, 1, -1) update_appearance() - current_grab.update_grab_effects(src, null) + current_grab.update_stage_effects(src, null) return TRUE // Returns the bodypart of the grabbed person that the grabber is targeting /obj/item/hand_item/grab/proc/get_targeted_bodypart() var/mob/living/L = get_affecting_mob() - return (L?.get_bodypart(target_zone)) + return (L?.get_bodypart(deprecise_zone(target_zone))) /obj/item/hand_item/grab/proc/resolve_item_attack(mob/living/M, obj/item/I, target_zone) if((M && ishuman(M)) && I) @@ -212,7 +210,15 @@ var/datum/grab/upgrab = current_grab.upgrade(src) if(upgrab) + if(is_grab_unique(current_grab)) + current_grab.remove_grab_effects(src) + var/apply_effects = is_grab_unique(upgrab) + current_grab = upgrab + + if(apply_effects) + current_grab.apply_grab_effects(src) + last_upgrade = world.time adjust_position() update_appearance() @@ -222,9 +228,26 @@ /obj/item/hand_item/grab/proc/downgrade() var/datum/grab/downgrab = current_grab.downgrade(src) if(downgrab) + if(is_grab_unique(current_grab)) + current_grab.remove_grab_effects(src) + var/apply_effects = is_grab_unique(downgrab) + current_grab = downgrab + + if(apply_effects) + current_grab.apply_grab_effects(src) update_appearance() +/// Used to prevent repeated effect application or early effect removal +/obj/item/hand_item/grab/proc/is_grab_unique(datum/grab/grab_datum) + var/count = 0 + for(var/obj/item/hand_item/grab/other as anything in affecting.grabbed_by) + if(other.current_grab == grab_datum) + count++ + + if(count >= 2) + return FALSE + /obj/item/hand_item/grab/proc/draw_affecting_over() affecting.plane = assailant.plane affecting.layer = assailant.layer + 0.01 diff --git a/code/modules/grab/grabs/grab_aggressive.dm b/code/modules/grab/grabs/grab_aggressive.dm index cc8ffbd55a7d..0b1adbf9c495 100644 --- a/code/modules/grab/grabs/grab_aggressive.dm +++ b/code/modules/grab/grabs/grab_aggressive.dm @@ -15,14 +15,25 @@ break_chance_table = list(5, 20, 40, 80, 100) -/datum/grab/normal/aggressive/process_effect(obj/item/hand_item/grab/G) - var/mob/living/affecting_mob = G.get_affecting_mob() - if(istype(affecting_mob)) - if(G.target_zone in list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM)) - affecting_mob.drop_all_held_items() - // Keeps those who are on the ground down - if(affecting_mob.body_position == LYING_DOWN) - affecting_mob.Knockdown(4 SECONDS) +/datum/grab/normal/aggressive/apply_grab_effects(obj/item/hand_item/grab/G) + if(!isliving(G.affecting)) + return + + var/mob/living/L = G.affecting + RegisterSignal(G.affecting, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(target_bodyposition_change)) + + if(L.body_position == LYING_DOWN) + ADD_TRAIT(L, TRAIT_FLOORED, AGGRESSIVE_GRAB) + ADD_TRAIT(L, TRAIT_HANDS_BLOCKED, AGGRESSIVE_GRAB) + +/datum/grab/normal/aggressive/remove_grab_effects(obj/item/hand_item/grab/G) + UnregisterSignal(G.affecting, COMSIG_LIVING_SET_BODY_POSITION) + REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, AGGRESSIVE_GRAB) + REMOVE_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, AGGRESSIVE_GRAB) + +/datum/grab/normal/aggressive/proc/target_bodyposition_change(mob/living/source) + if(source.body_position == LYING_DOWN) + ADD_TRAIT(source, TRAIT_FLOORED, AGGRESSIVE_GRAB) /datum/grab/normal/aggressive/can_upgrade(obj/item/hand_item/grab/G) . = ..() diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index 388d99f06603..0fd1542a1cda 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -181,20 +181,23 @@ return 1 // Handles special targeting like eyes and mouth being covered. -/datum/grab/normal/special_target_effect(var/obj/item/hand_item/grab/G) +/datum/grab/normal/special_bodyzone_effects(obj/item/hand_item/grab/G) var/mob/living/affecting_mob = G.get_affecting_mob() if(istype(affecting_mob) && G.special_target_functional) switch(G.target_zone) if(BODY_ZONE_PRECISE_MOUTH) - if(iscarbon(affecting_mob)) - var/mob/living/carbon/C = affecting_mob - C.silent = max(C.silent, 2) + ADD_TRAIT(affecting_mob, TRAIT_MUTE, REF(G)) if(BODY_ZONE_PRECISE_EYES) - affecting_mob.blind_eyes(2 SECONDS) + ADD_TRAIT(affecting_mob, TRAIT_BLIND, REF(G)) + +/datum/grab/normal/remove_bodyzone_effects(obj/item/hand_item/grab/G) + REMOVE_TRAIT(G.affecting, TRAIT_MUTE, REF(G)) + REMOVE_TRAIT(G.affecting, TRAIT_BLIND, REF(G)) // Handles when they change targeted areas and something is supposed to happen. -/datum/grab/normal/special_target_change(obj/item/hand_item/grab/G, old_zone, new_zone) - if((old_zone != BODY_ZONE_HEAD && old_zone != BODY_ZONE_CHEST) || !G.get_affecting_mob()) +/datum/grab/normal/special_bodyzone_change(obj/item/hand_item/grab/G, old_zone, new_zone) + old_zone = parse_zone(old_zone) + if(old_zone != BODY_ZONE_HEAD && old_zone != BODY_ZONE_CHEST) || !G.get_affecting_mob() return switch(new_zone) if(BODY_ZONE_PRECISE_MOUTH) diff --git a/daedalus.dme b/daedalus.dme index 16470751e071..44ae20e74af2 100644 --- a/daedalus.dme +++ b/daedalus.dme @@ -94,6 +94,7 @@ #include "code\__DEFINES\food.dm" #include "code\__DEFINES\footsteps.dm" #include "code\__DEFINES\ghost.dm" +#include "code\__DEFINES\grab_defines.dm" #include "code\__DEFINES\gravity.dm" #include "code\__DEFINES\greyscale.dm" #include "code\__DEFINES\hud.dm" From 6a1265c3259f9c4ee5ccc4ad885e25b5ec1774c5 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Wed, 27 Sep 2023 19:52:41 -0400 Subject: [PATCH 08/37] tweaks --- code/__DEFINES/grab_defines.dm | 1 + code/_compile_options.dm | 2 +- code/game/atoms_movable.dm | 11 +++++++- code/modules/grab/grab_datum.dm | 33 ---------------------- code/modules/grab/grab_living.dm | 5 +++- code/modules/grab/grab_movable.dm | 4 +-- code/modules/grab/grab_object.dm | 32 +++++++++++---------- code/modules/grab/grabs/grab_aggressive.dm | 2 +- code/modules/grab/grabs/grab_neck.dm | 30 ++++++++++++++++++++ code/modules/grab/grabs/grab_normal.dm | 2 +- daedalus.dme | 1 + 11 files changed, 68 insertions(+), 55 deletions(-) create mode 100644 code/modules/grab/grabs/grab_neck.dm diff --git a/code/__DEFINES/grab_defines.dm b/code/__DEFINES/grab_defines.dm index 902a501d1fe3..d75aab5a55f3 100644 --- a/code/__DEFINES/grab_defines.dm +++ b/code/__DEFINES/grab_defines.dm @@ -1,2 +1,3 @@ /// Trait source for aggressive grabs #define AGGRESSIVE_GRAB "trait_aggressive_grab" +#define NECK_GRAB "trait_neck_grab" diff --git a/code/_compile_options.dm b/code/_compile_options.dm index bbd9b98a8c73..17767297fa83 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -217,7 +217,7 @@ #endif #ifdef LOWMEMORYMODE -#define FORCE_MAP "runtimestation" +#define FORCE_MAP "multiz_debug" #define FORCE_MAP_DIRECTORY "_maps" #endif diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 66926f4e6c08..f1c0e34fb1f3 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -777,7 +777,15 @@ if(target_turf != current_turf || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1) pulling.move_from_pull(src, target_turf, glide_size) - recheck_grabs() + if(get_dist(src, pulling) > 1) + qdel(G) + + if(!QDELETED(G)) + G.current_grab.moved_effect(G) + if(G.current_grab.downgrade_on_move) + G.downgrade() + + recheck_grabs(only_pulled = TRUE) //glide_size strangely enough can change mid movement animation and update correctly while the animation is playing //This means that if you don't override it late like this, it will just be set back by the movement update that's called when you move turfs. @@ -788,6 +796,7 @@ if(set_dir_on_move && dir != direct) setDir(direct) + if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s) . = FALSE diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index dcda28294080..dc538a04eec2 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -198,39 +198,6 @@ GLOBAL_LIST_EMPTY(all_grabstates) /datum/grab/proc/make_log(obj/item/hand_item/grab/G, action) log_combat(G.assailant, G.affecting, "[action]s their victim") -/datum/grab/proc/adjust_position(obj/item/hand_item/grab/G) - var/mob/living/carbon/human/affecting = G.affecting - var/mob/living/carbon/human/assailant = G.assailant - var/adir = get_dir(assailant, affecting) - - if(same_tile) - affecting.forceMove(assailant.loc) - adir = assailant.dir - affecting.setDir(assailant.dir) - - switch(adir) - if(NORTH) - animate(affecting, pixel_x = 0, pixel_y =-shift, 5, 1, LINEAR_EASING) - G.draw_affecting_under() - if(SOUTH) - animate(affecting, pixel_x = 0, pixel_y = shift, 5, 1, LINEAR_EASING) - G.draw_affecting_over() - if(WEST) - animate(affecting, pixel_x = shift, pixel_y = 0, 5, 1, LINEAR_EASING) - G.draw_affecting_under() - if(EAST) - animate(affecting, pixel_x =-shift, pixel_y = 0, 5, 1, LINEAR_EASING) - G.draw_affecting_under() - - affecting.reset_plane_and_layer() - -// This is called whenever the assailant moves. -/datum/grab/proc/assailant_moved(obj/item/hand_item/grab/G) - adjust_position(G) - moved_effect(G) - if(downgrade_on_move) - G.downgrade() - /* Override these procs to set how the grab state will work. Some of them are best overriden in the parent of the grab set (for example, the behaviour for on_hit_intent(var/obj/item/hand_item/grab/G) diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index 070a4946b17e..317cfd1f359f 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -86,7 +86,10 @@ grab.forceMove(src) return TRUE -/mob/living/recheck_grabs(only_pulling = FALSE, z_allowed = FALSE) +/mob/living/recheck_grabs(only_pulling = FALSE, only_pulled = FALSE, z_allowed = FALSE) + if(only_pulled) + return ..() + for(var/obj/item/hand_item/grab/G in get_active_grabs()) var/atom/movable/pulling = G.affecting if(get_dist(src, pulling) > 1 || (z != pulling.z && !z_allowed)) diff --git a/code/modules/grab/grab_movable.dm b/code/modules/grab/grab_movable.dm index 2bc912416790..2a14aa189535 100644 --- a/code/modules/grab/grab_movable.dm +++ b/code/modules/grab/grab_movable.dm @@ -32,7 +32,7 @@ * Checks if the pulling and pulledby should be stopped because they're out of reach. * If z_allowed is TRUE, the z level of the pulling will be ignored.This is to allow things to be dragged up and down stairs. */ -/atom/movable/proc/recheck_grabs(only_pulling = FALSE, z_allowed = FALSE) +/atom/movable/proc/recheck_grabs(only_pulling = FALSE, only_pulled = FALSE, z_allowed = FALSE) if(only_pulling) return @@ -83,7 +83,7 @@ if(length(grabbed_by)) for(var/obj/item/hand_item/grab/G as anything in grabbed_by) var/grab_dir = get_dir(G.assailant, src) - if(grab_dir && G.current_grab.shift > 0) + if(grab_dir && G.current_grab.shift != 0) if(grab_dir & WEST) new_pixel_x = min(new_pixel_x+G.current_grab.shift, base_pixel_x+G.current_grab.shift) else if(grab_dir & EAST) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 288210142b29..30e8ed8d4acc 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -53,7 +53,6 @@ RegisterSignal(affecting, COMSIG_MOVABLE_PRE_THROW, PROC_REF(target_thrown)) RegisterSignal(assailant, COMSIG_MOB_SELECTED_ZONE_SET, PROC_REF(on_target_change)) - RegisterSignal(assailant, COMSIG_MOVABLE_MOVED, PROC_REF(relay_user_move)) /obj/item/hand_item/grab/Destroy() current_grab?.let_go(src) @@ -236,6 +235,8 @@ if(apply_effects) current_grab.apply_grab_effects(src) + + adjust_position() update_appearance() /// Used to prevent repeated effect application or early effect removal @@ -263,15 +264,6 @@ /obj/item/hand_item/grab/proc/handle_resist() current_grab.handle_resist(src) -/obj/item/hand_item/grab/proc/adjust_position(force = 0) - if(force) affecting.forceMove(assailant.loc) - - if(!assailant || !affecting || !assailant.Adjacent(affecting)) - qdel(src) - return 0 - else - current_grab.adjust_position(src) - /obj/item/hand_item/grab/proc/has_hold_on_bodypart(obj/item/bodypart/BP) if (!BP) return FALSE @@ -296,11 +288,6 @@ * This section is for component signal relays/hooks */ -/// Relay when the assailant moves to the grab datum -/obj/item/hand_item/grab/proc/relay_user_move(datum/source) - SIGNAL_HANDLER - current_grab.assailant_moved(src) - /// Target deleted, ABORT /obj/item/hand_item/grab/proc/target_or_owner_del(datum/source) SIGNAL_HANDLER @@ -323,3 +310,18 @@ /obj/item/hand_item/grab/proc/resolve_openhand_attack() return current_grab.resolve_openhand_attack(src) + +/obj/item/hand_item/grab/proc/adjust_position() + if(QDELETED(assailant) || QDELETED(affecting) || !assailant.Adjacent(affecting)) + qdel(src) + return FALSE + + if(assailant) + assailant.setDir(get_dir(assailant, affecting)) + + if(current_grab.same_tile) + affecting.move_from_pull(assailant, get_turf(assailant)) + affecting.setDir(assailant.dir) + + affecting.update_offsets() + affecting.reset_plane_and_layer() diff --git a/code/modules/grab/grabs/grab_aggressive.dm b/code/modules/grab/grabs/grab_aggressive.dm index 0b1adbf9c495..7288c2b9548e 100644 --- a/code/modules/grab/grabs/grab_aggressive.dm +++ b/code/modules/grab/grabs/grab_aggressive.dm @@ -1,5 +1,5 @@ /datum/grab/normal/aggressive - //upgrab = /datum/grab/normal/neck + upgrab = /datum/grab/normal/neck downgrab = /datum/grab/normal/passive shift = 12 stop_move = 1 diff --git a/code/modules/grab/grabs/grab_neck.dm b/code/modules/grab/grabs/grab_neck.dm new file mode 100644 index 000000000000..446d252dde4b --- /dev/null +++ b/code/modules/grab/grabs/grab_neck.dm @@ -0,0 +1,30 @@ +/datum/grab/normal/neck + //upgrab = /datum/grab/normal/kill + downgrab = /datum/grab/normal/aggressive + + drop_headbutt = 0 + + shift = -10 + stop_move = 1 + reverse_facing = 1 + shield_assailant = 1 + point_blank_mult = 2 + damage_stage = GRAB_NECK + same_tile = 1 + can_throw = 1 + force_danger = 1 + restrains = 1 + icon_state = "3" + break_chance_table = list(3, 18, 45, 100) + +/datum/grab/normal/neck/apply_grab_effects(obj/item/hand_item/grab/G) + if(!isliving(G.affecting)) + return + + var/mob/living/L = G.affecting + ADD_TRAIT(L, TRAIT_FORCED_STANDING, NECK_GRAB) + ADD_TRAIT(L, TRAIT_HANDS_BLOCKED, NECK_GRAB) + +/datum/grab/normal/neck/remove_grab_effects(obj/item/hand_item/grab/G) + REMOVE_TRAIT(G.affecting, TRAIT_FORCED_STANDING, NECK_GRAB) + REMOVE_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, NECK_GRAB) diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index 0fd1542a1cda..5dee686b58b8 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -197,7 +197,7 @@ // Handles when they change targeted areas and something is supposed to happen. /datum/grab/normal/special_bodyzone_change(obj/item/hand_item/grab/G, old_zone, new_zone) old_zone = parse_zone(old_zone) - if(old_zone != BODY_ZONE_HEAD && old_zone != BODY_ZONE_CHEST) || !G.get_affecting_mob() + if((old_zone != BODY_ZONE_HEAD && old_zone != BODY_ZONE_CHEST) || !G.get_affecting_mob()) return switch(new_zone) if(BODY_ZONE_PRECISE_MOUTH) diff --git a/daedalus.dme b/daedalus.dme index 44ae20e74af2..e66fa87f16ba 100644 --- a/daedalus.dme +++ b/daedalus.dme @@ -2989,6 +2989,7 @@ #include "code\modules\grab\grab_silicon.dm" #include "code\modules\grab\human_grab.dm" #include "code\modules\grab\grabs\grab_aggressive.dm" +#include "code\modules\grab\grabs\grab_neck.dm" #include "code\modules\grab\grabs\grab_normal.dm" #include "code\modules\grab\grabs\grab_passive.dm" #include "code\modules\grab\grabs\grab_simple.dm" From dd7ab30e0d827ee61376805a9cbe08e3806ed6cc Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Thu, 28 Sep 2023 02:38:39 -0400 Subject: [PATCH 09/37] pain --- code/__DEFINES/movement.dm | 2 + code/_onclick/adjacent.dm | 29 ++ code/game/atoms.dm | 4 +- code/game/atoms_movable.dm | 356 +----------------- .../machinery/computer/camera_advanced.dm | 4 +- code/game/objects/structures/ladders.dm | 4 +- code/game/objects/structures/stairs.dm | 10 +- code/game/turfs/open/openspace.dm | 21 +- code/game/turfs/open/space/space.dm | 12 +- code/game/turfs/turf.dm | 6 +- code/modules/grab/grab_living.dm | 37 +- code/modules/grab/grab_object.dm | 1 + code/modules/mob/camera/camera.dm | 4 +- code/modules/mob/dead/observer/observer.dm | 2 +- .../mob/dead/observer/observer_movement.dm | 2 +- code/modules/mob/living/carbon/carbon.dm | 3 + .../mob/living/carbon/carbon_movement.dm | 2 +- code/modules/mob/living/carbon/human/human.dm | 11 + .../mob/living/carbon/human/human_movement.dm | 2 +- code/modules/mob/living/living.dm | 40 +- code/modules/mob/living/living_movement.dm | 21 +- code/modules/mob/living/silicon/ai/ai.dm | 4 +- .../mob/living/silicon/ai/freelook/eye.dm | 5 - .../living/simple_animal/hostile/hostile.dm | 2 +- code/modules/mob/mob_movement.dm | 8 +- code/modules/mod/modules/modules_maint.dm | 2 +- code/modules/multiz/movement.dm | 270 +++++++++++++ daedalus.dme | 1 + 28 files changed, 423 insertions(+), 442 deletions(-) create mode 100644 code/modules/multiz/movement.dm diff --git a/code/__DEFINES/movement.dm b/code/__DEFINES/movement.dm index caaceadd332d..c0e4adc594b1 100644 --- a/code/__DEFINES/movement.dm +++ b/code/__DEFINES/movement.dm @@ -79,6 +79,8 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0) #define ZMOVE_INCLUDE_PULLED (1<<8) /// Skips check for whether the moving atom is anchored or not. #define ZMOVE_ALLOW_ANCHORED (1<<9) +/// Skip CanMoveOnto() checks +#define ZMOVE_SKIP_CANMOVEONTO (1<<10) #define ZMOVE_CHECK_PULLS (ZMOVE_CHECK_PULLING|ZMOVE_CHECK_PULLEDBY) diff --git a/code/_onclick/adjacent.dm b/code/_onclick/adjacent.dm index 0329134a9f2d..c6d96d7c9b11 100644 --- a/code/_onclick/adjacent.dm +++ b/code/_onclick/adjacent.dm @@ -145,3 +145,32 @@ else if( !border_only ) // dense, not on border, cannot pass over return FALSE return TRUE + +/atom/proc/MultiZAdjacent(atom/neighbor) + var/turf/T = get_turf(src) + var/turf/N = get_turf(neighbor) + + // Not on valid turfs. + if(QDELETED(src) || QDELETED(neighbor) || !istype(T) || !istype(N)) + return FALSE + + // On the same z-level, we don't need to care about multiz. + if(N.z == T.z) + return Adjacent(neighbor) + + // More than one z-level away from each other. + if(abs(N.x - T.x) > 1 || abs(N.y - T.y) > 1 || abs(N.z - T.z) > 1) + return FALSE + + + // Are they below us? + if(N.z < T.z && HasBelow(T.z)) + var/turf/B = GetBelow(T) + return TURF_IS_MIMICKING(B) && neighbor.Adjacent(B) + + // Are they above us? + if(HasAbove(T.z)) + var/turf/A = GetAbove(T) + return TURF_IS_MIMICKING(A) && neighbor.Adjacent(A) + + return FALSE diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 3842224ec6dc..6e95c092b8b7 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1389,11 +1389,11 @@ * * Default behaviour is to send the [COMSIG_ATOM_EXIT] */ -/atom/Exit(atom/movable/leaving, direction) +/atom/Exit(atom/movable/leaving, direction, no_side_effects) // Don't call `..()` here, otherwise `Uncross()` gets called. // See the doc comment on `Uncross()` to learn why this is bad. - if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, leaving, direction) & COMPONENT_ATOM_BLOCK_EXIT) + if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, leaving, direction, no_side_effects) & COMPONENT_ATOM_BLOCK_EXIT) return FALSE return TRUE diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index f1c0e34fb1f3..1941faec1a25 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -217,300 +217,6 @@ if(emissive_block) . += emissive_block -/atom/movable/proc/onZImpact(turf/impacted_turf, levels, message = TRUE) - if(message) - visible_message( - span_danger("[src] slams into [impacted_turf]!"), - blind_message = span_hear("You hear something slam into the deck.") - ) - - INVOKE_ASYNC(src, PROC_REF(SpinAnimation), 5, 2) - return TRUE - -/* - * The core multi-z movement proc. Used to move a movable through z levels. - * If target is null, it'll be determined by the can_z_move proc, which can potentially return null if - * conditions aren't met (see z_move_flags defines in __DEFINES/movement.dm for info) or if dir isn't set. - * Bear in mind you don't need to set both target and dir when calling this proc, but at least one or two. - * This will set the currently_z_moving to CURRENTLY_Z_MOVING_GENERIC if unset, and then clear it after - * Forcemove(). - * - * - * Args: - * * dir: the direction to go, UP or DOWN, only relevant if target is null. - * * target: The target turf to move the src to. Set by can_z_move() if null. - * * z_move_flags: bitflags used for various checks in both this proc and can_z_move(). See __DEFINES/movement.dm. - */ -/atom/movable/proc/zMove(dir, turf/target, z_move_flags = ZMOVE_FLIGHT_FLAGS) - var/able_to_climb = (!currently_z_moving && (dir == UP) && isliving(src)) - if(!target) - // We remove feedback if the mover is able to climb to prevent feedback from playing if they end up climbing. - target = can_z_move(dir, get_turf(src), able_to_climb ? z_move_flags & ~ZMOVE_FEEDBACK : z_move_flags) - if(!target) - set_currently_z_moving(FALSE, TRUE) - - if(!target && able_to_climb) - var/obj/climbable = check_zclimb() - if(!climbable) - if(z_move_flags & ZMOVE_FEEDBACK) - // This is kind of stupid, but we do this *again* to properly get feedback - can_z_move(dir, get_turf(src), z_move_flags) - return FALSE - - return ClimbUp(climbable) - - if(!target) - return FALSE - - var/list/moving_movs = get_z_move_affected(z_move_flags) - - for(var/atom/movable/movable as anything in moving_movs) - movable.currently_z_moving = currently_z_moving || CURRENTLY_Z_MOVING_GENERIC - movable.forceMove(target) - movable.set_currently_z_moving(FALSE, TRUE) - - // This is run after ALL movables have been moved, so pulls don't get broken unless they are actually out of range. - if(z_move_flags & ZMOVE_CHECK_PULLS) - for(var/atom/movable/moved_mov as anything in moving_movs) - if(z_move_flags & ZMOVE_CHECK_PULLEDBY) - for(var/obj/item/hand_item/grab/G in grabbed_by) - if(G.assailant.z != moved_mov.z || get_dist(moved_mov, G.assailant) > 1) - qdel(G) - - if(z_move_flags & ZMOVE_CHECK_PULLING) - moved_mov.recheck_grabs(TRUE) - return TRUE - -/// Returns a list of movables that should also be affected when src moves through zlevels, and src. -/atom/movable/proc/get_z_move_affected(z_move_flags) - . = list(src) - if(buckled_mobs) - . |= buckled_mobs - if(!(z_move_flags & ZMOVE_INCLUDE_PULLED)) - return - for(var/mob/living/buckled as anything in buckled_mobs) - if(buckled.pulling) - . |= buckled.pulling - if(pulling) - . |= pulling - -/** - * Checks if the destination turf is elegible for z movement from the start turf to a given direction and returns it if so. - * Args: - * * direction: the direction to go, UP or DOWN, only relevant if target is null. - * * start: Each destination has a starting point on the other end. This is it. Most of the times the location of the source. - * * z_move_flags: bitflags used for various checks. See __DEFINES/movement.dm. - * * rider: A living mob in control of the movable. Only non-null when a mob is riding a vehicle through z-levels. - */ -/atom/movable/proc/can_z_move(direction, turf/start, z_move_flags = ZMOVE_FLIGHT_FLAGS, mob/living/rider) - if(!start) - start = get_turf(src) - if(!start) - CRASH("Something tried to zMove from nullspace.") - - if(!direction) - CRASH("can_z_move() called with no direction") - - if(direction != UP && direction != DOWN) - CRASH("Tried to zMove on the same Z Level.") - - var/turf/destination = get_step_multiz(start, direction) - if(!destination) - if(z_move_flags & ZMOVE_FEEDBACK) - to_chat(rider || src, span_notice("There is nothing of interest in this direction.")) - return FALSE - - // Check flight - if(z_move_flags & ZMOVE_CAN_FLY_CHECKS) - if(!(movement_type & (FLYING|FLOATING)) && has_gravity(start) && (direction == UP)) - if(z_move_flags & ZMOVE_FEEDBACK) - if(rider) - to_chat(rider, span_warning("[src] is is not capable of flight.")) - else - to_chat(src, span_warning("You stare at [destination].")) - return FALSE - - // Check CanZPass - if(!(z_move_flags & ZMOVE_IGNORE_OBSTACLES)) - // Check exit - if(!start.CanZPass(src, direction, z_move_flags)) - if(z_move_flags & ZMOVE_FEEDBACK) - to_chat(rider || src, span_warning("[start] is in the way.")) - return FALSE - - // Check enter - if(!destination.CanZPass(src, direction, z_move_flags)) - if(z_move_flags & ZMOVE_FEEDBACK) - to_chat(rider || src, span_warning("You bump against [destination].")) - return FALSE - - // Check destination movable CanPass - for(var/atom/movable/A as anything in destination) - if(!A.CanMoveOnto(src, direction)) - if(z_move_flags & ZMOVE_FEEDBACK) - to_chat(rider || src, span_warning("You are blocked by [A].")) - return FALSE - - // Check if we would fall. - if(z_move_flags & ZMOVE_FALL_CHECKS) - if((throwing || (movement_type & (FLYING|FLOATING)) || !has_gravity(start))) - if(z_move_flags & ZMOVE_FEEDBACK) - to_chat(rider || src, span_warning("You see nothing to hold onto.")) - return FALSE - - return destination //used by some child types checks and zMove() - -/// Precipitates a movable (plus whatever buckled to it) to lower z levels if possible and then calls zImpact() -/atom/movable/proc/zFall(levels = 1, force = FALSE, falling_from_move = FALSE) - if(QDELETED(src)) - return FALSE - - var/direction = DOWN - if(has_gravity() == NEGATIVE_GRAVITY) - direction = UP - - var/turf/target = direction == UP ? GetAbove(src) : GetBelow(src) - if(!target) - return FALSE - - if(!CanZFall(get_turf(src), direction)) - set_currently_z_moving(FALSE, TRUE) - return FALSE - - var/isliving = isliving(src) - if(!isliving && !isobj(src)) - return - - if(isliving) - var/mob/living/falling_living = src - //relay this mess to whatever the mob is buckled to. - if(falling_living.buckled) - return falling_living.buckled.zFall() - - if(!falling_from_move && currently_z_moving) - return - - if(!force && !can_z_move(direction, get_turf(src), ZMOVE_FALL_FLAGS)) - set_currently_z_moving(FALSE, TRUE) - return FALSE - - spawn(-1) - _doZFall(target, levels, get_turf(src)) - return TRUE - -/atom/movable/proc/_doZFall(turf/destination, levels, turf/prev_turf) - PRIVATE_PROC(TRUE) - - // So it doesn't trigger other zFall calls. Cleared on zMove. - set_currently_z_moving(CURRENTLY_Z_FALLING) - - zMove(null, destination, ZMOVE_CHECK_PULLEDBY) - destination.zImpact(src, levels, prev_turf) - -/atom/movable/proc/CanZFall(turf/from, direction, anchor_bypass) - if(anchored && !anchor_bypass) - return FALSE - - if(from) - for(var/obj/O in from) - if(O.obj_flags & BLOCK_Z_FALL) - return FALSE - - var/turf/other = direction == UP ? GetAbove(from) : GetBelow(from) - if(!from.CanZPass(from, direction) || !other?.CanZPass(from, direction)) //Kinda hacky but it does work. - return FALSE - - return TRUE - -/// Returns an object we can climb onto -/atom/movable/proc/check_zclimb() - var/turf/above = GetAbove(src) - if(!above?.CanZPass(src, UP)) - return - - var/list/all_turfs_above = get_adjacent_open_turfs(above) - - //Check directly above first - . = get_climbable_surface(above) - if(.) - return - - //Next, try the direction the mob is facing - . = get_climbable_surface(get_step(above, dir)) - if(.) - return - - for(var/turf/T as turf in all_turfs_above) - . = get_climbable_surface(T) - if(.) - return - -/atom/movable/proc/get_climbable_surface(turf/T) - var/climb_target - if(!T.Enter(src)) - return - if(!isopenspaceturf(T) && isfloorturf(T)) - climb_target = T - else - for(var/obj/I in T) - if(I.obj_flags & BLOCK_Z_FALL) - climb_target = I - break - - if(climb_target) - return climb_target - -/atom/movable/proc/ClimbUp(atom/onto) - if(!isturf(loc)) - return FALSE - - var/turf/above = GetAbove(src) - var/turf/destination = get_turf(onto) - if(!above || !above.Adjacent(destination, mover = src)) - return FALSE - - if(has_gravity() > 0) - var/can_overcome - for(var/atom/A in loc) - if(HAS_TRAIT(A, TRAIT_CLIMBABLE)) - can_overcome = TRUE - break; - - if(!can_overcome) - var/list/objects_to_stand_on = list( - /obj/structure/chair, - /obj/structure/bed, - /obj/structure/lattice - ) - for(var/path in objects_to_stand_on) - if(locate(path) in loc) - can_overcome = TRUE - break; - if(!can_overcome) - to_chat(src, span_warning("You cannot reach [onto] from here!")) - return FALSE - - visible_message( - span_notice("[src] starts climbing onto \the [onto]."), - span_notice("You start climbing onto \the [onto].") - ) - - if(!do_after(src, time = 5 SECONDS, timed_action_flags = DO_PUBLIC, display = image('icons/hud/do_after.dmi', "help"))) - return FALSE - - visible_message( - span_notice("[src] climbs onto \the [onto]."), - span_notice("You climb onto \the [onto].") - ) - - var/oldloc = loc - setDir(get_dir(above, destination)) - set_currently_z_moving(CURRENTLY_Z_ASCENDING) - . = zMove(UP, destination, ZMOVE_CHECK_PULLS|ZMOVE_INCAPACITATED_CHECKS) - if(.) - playsound(oldloc, 'sound/effects/stairs_step.ogg', 50) - playsound(destination, 'sound/effects/stairs_step.ogg', 50) - /atom/movable/vv_edit_var(var_name, var_value) var/static/list/banned_edits = list( NAMEOF_STATIC(src, step_x) = TRUE, @@ -597,7 +303,7 @@ // Here's where we rewrite how byond handles movement except slightly different // To be removed on step_ conversion // All this work to prevent a second bump -/atom/movable/Move(atom/newloc, direction, glide_size_override = 0) +/atom/movable/Move(atom/newloc, direction, glide_size_override = 0, z_movement_flags) . = FALSE if(!newloc || newloc == loc) @@ -606,7 +312,7 @@ if(!direction) direction = get_dir(src, newloc) - if(set_dir_on_move && dir != direction) + if(set_dir_on_move && dir != direction && !(dir & (UP|DOWN))) setDir(direction) var/is_multi_tile_object = bound_width > 32 || bound_height > 32 @@ -674,7 +380,7 @@ //////////////////////////////////////// -/atom/movable/Move(atom/newloc, direct, glide_size_override = 0) +/atom/movable/Move(atom/newloc, direct, glide_size_override = 0, z_movement_flags) if(QDELING(src)) CRASH("Illegal Move()! on [type]") @@ -685,6 +391,11 @@ if(!loc || !newloc) return FALSE + if(direct & (UP|DOWN)) + if(!can_z_move(direct, null, z_movement_flags)) + return FALSE + set_currently_z_moving(TRUE) + var/atom/oldloc = loc //Early override for some cases like diagonal movement if(glide_size_override && glide_size != glide_size_override) @@ -750,40 +461,12 @@ if(!loc || (loc == oldloc && oldloc != newloc)) last_move = 0 - set_currently_z_moving(FALSE, TRUE) + set_currently_z_moving(FALSE) return if(. && isliving(src)) var/mob/living/L = src - var/list/grabs = L.get_active_grabs() - if(LAZYLEN(grabs)) - for(var/obj/item/hand_item/grab/G in grabs) - var/atom/movable/pulling = G.affecting - if(pulling.anchored) - qdel(G) - continue - //puller and pullee more than one tile away or in diagonal position and whatever the pullee is pulling isn't already moving from a pull as it'll most likely result in an infinite loop a la ouroborus. - if(G.assailant.moving_from_pull) - continue - var/pull_dir = get_dir(pulling, src) - var/target_turf = current_turf - - // Pulling things down/up stairs. zMove() has flags for check_pulling and stop_pulling calls. - // You may wonder why we're not just forcemoving the pulling movable and regrabbing it. - // The answer is simple. forcemoving and regrabbing is ugly and breaks conga lines. - if(pulling.z != z) - target_turf = get_step(pulling, get_dir(pulling, current_turf)) - - if(target_turf != current_turf || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1) - pulling.move_from_pull(src, target_turf, glide_size) - - if(get_dist(src, pulling) > 1) - qdel(G) - - if(!QDELETED(G)) - G.current_grab.moved_effect(G) - if(G.current_grab.downgrade_on_move) - G.downgrade() + L.handle_grabs_during_movement(oldloc, direct) recheck_grabs(only_pulled = TRUE) @@ -801,10 +484,7 @@ . = FALSE if(currently_z_moving) - if(. && loc == newloc) - zFall(falling_from_move = TRUE) - else - set_currently_z_moving(FALSE, TRUE) + set_currently_z_moving(FALSE) /// Called when src is being moved to a target turf because another movable (puller) is moving around. /atom/movable/proc/move_from_pull(atom/movable/puller, turf/target_turf, glide_size_override) @@ -1099,13 +779,11 @@ SEND_SIGNAL(src, COMSIG_MOVABLE_SET_ANCHORED, anchorvalue) /// Sets the currently_z_moving variable to a new value. Used to allow some zMovement sources to have precedence over others. -/atom/movable/proc/set_currently_z_moving(new_z_moving_value, forced = FALSE) - if(forced) - currently_z_moving = new_z_moving_value - return TRUE - var/old_z_moving_value = currently_z_moving - currently_z_moving = max(currently_z_moving, new_z_moving_value) - return currently_z_moving > old_z_moving_value +/atom/movable/proc/set_currently_z_moving(new_z_moving_value) + if(new_z_moving_value == currently_z_moving) + return FALSE + currently_z_moving = new_z_moving_value + return TRUE /atom/movable/proc/forceMove(atom/destination) if(QDELING(src)) @@ -1621,7 +1299,7 @@ var/current_loc = get_turf(src) var/direction = get_dir(current_loc, destination) if(loc) - if(!loc.Exit(src, direction)) + if(!loc.Exit(src, direction, TRUE)) return FALSE return destination.Enter(src, TRUE) diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm index b8b93da05fa7..052c146469d5 100644 --- a/code/game/machinery/computer/camera_advanced.dm +++ b/code/game/machinery/computer/camera_advanced.dm @@ -310,7 +310,7 @@ if(!owner || !isliving(owner)) return var/mob/camera/ai_eye/remote/remote_eye = owner.remote_control - if(remote_eye.zMove(UP)) + if(step(remote_eye, UP)) to_chat(owner, span_notice("You move upwards.")) else to_chat(owner, span_notice("You couldn't move upwards!")) @@ -324,7 +324,7 @@ if(!owner || !isliving(owner)) return var/mob/camera/ai_eye/remote/remote_eye = owner.remote_control - if(remote_eye.zMove(DOWN)) + if(step(remote_eye, DOWN)) to_chat(owner, span_notice("You move downwards.")) else to_chat(owner, span_notice("You couldn't move downwards!")) diff --git a/code/game/objects/structures/ladders.dm b/code/game/objects/structures/ladders.dm index cd5d558f2317..12db214d0175 100644 --- a/code/game/objects/structures/ladders.dm +++ b/code/game/objects/structures/ladders.dm @@ -5,7 +5,7 @@ icon = 'icons/obj/structures.dmi' icon_state = "ladder11" anchored = TRUE - obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN + obj_flags = CAN_BE_HIT var/obj/structure/ladder/down //the ladder below this one var/obj/structure/ladder/up //the ladder above this one var/crafted = FALSE @@ -89,7 +89,7 @@ if(!do_after(user, src, travel_time, DO_PUBLIC)) return - if(!user.zMove(target = target, z_move_flags = ZMOVE_CHECK_PULLEDBY|ZMOVE_ALLOW_BUCKLED|ZMOVE_INCLUDE_PULLED|ZMOVE_IGNORE_OBSTACLES)) + if(!zstep(user, going_up ? UP : DOWN, ZMOVE_INCAPACITATED_CHECKS)) return if(!is_ghost) diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm index 2d5856c23db6..b881e41498eb 100644 --- a/code/game/objects/structures/stairs.dm +++ b/code/game/objects/structures/stairs.dm @@ -55,15 +55,15 @@ if(S) S.update_appearance() -/obj/structure/stairs/proc/on_exit(datum/source, atom/movable/leaving, direction) +/obj/structure/stairs/proc/on_exit(datum/source, atom/movable/leaving, direction, no_side_effects) SIGNAL_HANDLER if(leaving == src) return //Let's not block ourselves. if(!isobserver(leaving) && isTerminator() && direction == dir) - leaving.set_currently_z_moving(CURRENTLY_Z_ASCENDING) - INVOKE_ASYNC(src, PROC_REF(stair_ascend), leaving) + if(!no_side_effects) + INVOKE_ASYNC(src, PROC_REF(stair_ascend), leaving) leaving.Bump(src) return COMPONENT_ATOM_BLOCK_EXIT @@ -91,10 +91,10 @@ to_chat(climber, span_warning("Something blocks the path.")) return - if(!target.Enter(climber)) + if(!target.Enter(climber, FALSE)) to_chat(climber, span_warning("Something blocks the path.")) return - + climber.set_currently_z_moving(TRUE) climber.forceMove(target) if(!(climber.throwing || (climber.movement_type & (VENTCRAWLING | FLYING)) || HAS_TRAIT(climber, TRAIT_IMMOBILIZED))) playsound(my_turf, 'sound/effects/stairs_step.ogg', 50) diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm index 70499f599b6a..75c0ddcc2cce 100644 --- a/code/game/turfs/open/openspace.dm +++ b/code/game/turfs/open/openspace.dm @@ -31,28 +31,15 @@ SHOULD_CALL_PARENT(FALSE) return below.examine(user) -/** - * Prepares a moving movable to be precipitated if Move() is successful. - * This is done in Enter() and not Entered() because there's no easy way to tell - * if the latter was called by Move() or forceMove() while the former is only called by Move(). - */ -/turf/open/openspace/Enter(atom/movable/movable, atom/oldloc) - . = ..() - if(.) - //higher priority than CURRENTLY_Z_FALLING so the movable doesn't fall on Entered() - movable.set_currently_z_moving(CURRENTLY_Z_FALLING_FROM_MOVE) - ///Makes movables fall when forceMove()'d to this turf. /turf/open/openspace/Entered(atom/movable/movable) . = ..() - if(movable.set_currently_z_moving(CURRENTLY_Z_FALLING)) - movable.zFall(falling_from_move = TRUE) - -/turf/open/openspace/proc/zfall_if_on_turf(atom/movable/movable) - if(QDELETED(movable) || movable.loc != src) - return movable.zFall() +/turf/open/openspace/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + . = ..() + hitting_atom.zFall() + /turf/open/openspace/can_have_cabling() if(locate(/obj/structure/lattice/catwalk, src)) return TRUE diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm index 1292ab514b7c..6b9592af72a0 100644 --- a/code/game/turfs/open/space/space.dm +++ b/code/game/turfs/open/space/space.dm @@ -123,7 +123,7 @@ GLOBAL_REAL_VAR(space_appearances) = make_space_appearances() if(!arrived || src != arrived.loc) return - if(destination_z && destination_x && destination_y && !LAZYLEN(arrived.grabbed_by) && !arrived.currently_z_moving) + if(destination_z && destination_x && destination_y && !LAZYLEN(arrived.grabbed_by)) var/tx = destination_x var/ty = destination_y var/turf/DT = locate(tx, ty, destination_z) @@ -144,14 +144,8 @@ GLOBAL_REAL_VAR(space_appearances) = make_space_appearances() if(SEND_SIGNAL(arrived, COMSIG_MOVABLE_LATERAL_Z_MOVE) & COMPONENT_BLOCK_MOVEMENT) return - arrived.zMove(null, DT, ZMOVE_ALLOW_BUCKLED) - - if(isliving(arrived)) - var/mob/living/L = arrived - for(var/obj/item/hand_item/grab/G as anything in L.get_active_grabs()) - var/atom/movable/current_pull = G.affecting - var/turf/target_turf = get_step(G.assailant.loc, REVERSE_DIR(G.assailant.dir)) || G.assailant.loc - current_pull.zMove(null, target_turf, ZMOVE_ALLOW_BUCKLED) + + arrived.Move(null, DT) /turf/open/space/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 0a84330b8c7c..a951dd73b7a9 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -262,7 +262,7 @@ GLOBAL_LIST_EMPTY(station_turfs) else if(O.obj_flags & BLOCK_Z_OUT_DOWN) return FALSE - return direction == UP //can't go below + return direction & UP //can't go below else if(density) //No fuck off return FALSE @@ -278,7 +278,7 @@ GLOBAL_LIST_EMPTY(station_turfs) ///Called each time the target falls down a z level possibly making their trajectory come to a halt. see __DEFINES/movement.dm. /turf/proc/zImpact(atom/movable/falling, levels = 1, turf/prev_turf) - var/flags = NONE + var/flags = FALL_RETAIN_PULL var/list/falling_movables = falling.get_z_move_affected() var/list/falling_mob_names @@ -383,7 +383,7 @@ GLOBAL_LIST_EMPTY(station_turfs) return FALSE continue else - if(!firstbump || ((thing.layer > firstbump.layer || (thing.flags_1 & ON_BORDER_1|BUMP_PRIORITY_1)) && !(firstbump.flags_1 & ON_BORDER_1))) + if(!firstbump || ((thing.layer < firstbump.layer || (thing.flags_1 & ON_BORDER_1|BUMP_PRIORITY_1)) && !(firstbump.flags_1 & ON_BORDER_1))) firstbump = thing if(QDELETED(mover)) //Mover deleted from Cross/CanPass/Bump, do not proceed. diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index 317cfd1f359f..0b1236dbd9d0 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -96,8 +96,7 @@ qdel(G) else if(!isturf(loc)) qdel(G) - else if(pulling && !isturf(pulling.loc) && pulling.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). - log_game("DEBUG:[src]'s pull on [pulling] wasn't broken despite [pulling] being in [pulling.loc]. Pull stopped manually.") + else if(!isturf(pulling.loc)) //to be removed once all code that changes an object's loc uses forceMove(). qdel(G) else if(pulling.anchored || pulling.move_resist > move_force) qdel(G) @@ -111,3 +110,37 @@ var/mob/living/L = pulling L.reset_pull_offsets() update_pull_hud_icon() + +/mob/living/proc/handle_grabs_during_movement(turf/old_loc, direction) + var/list/grabs = recursively_get_all_grabbed_movables() + if(LAZYLEN(grabs)) + for(var/obj/item/hand_item/grab/G in grabs) + var/atom/movable/pulling = G.affecting + if(pulling == src || pulling.loc == loc || !old_loc.Adjacent(AM)) + continue + if(pulling.anchored) + qdel(G) + continue + var/pull_dir = get_dir(pulling, src) + var/target_turf = current_turf + + // Pulling things down/up stairs. zMove() has flags for check_pulling and stop_pulling calls. + // You may wonder why we're not just forcemoving the pulling movable and regrabbing it. + // The answer is simple. forcemoving and regrabbing is ugly and breaks conga lines. + if(pulling.z != z) + target_turf = get_step(pulling, get_dir(pulling, current_turf)) + + if(target_turf != current_turf || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1) + pulling.move_from_pull(src, target_turf, glide_size) + if(QDELETED(G)) + continue + + if(!MultiZAdjacent(pulling)) + qdel(G) + continue + + if(!QDELETED(G)) + G.update_offsets() + G.current_grab.moved_effect(G) + if(G.current_grab.downgrade_on_move) + G.downgrade() diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 30e8ed8d4acc..3dca74bd5d9b 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -248,6 +248,7 @@ if(count >= 2) return FALSE + return TRUE /obj/item/hand_item/grab/proc/draw_affecting_over() affecting.plane = assailant.plane diff --git a/code/modules/mob/camera/camera.dm b/code/modules/mob/camera/camera.dm index 77bc82db77b9..4b7bbbff0d84 100644 --- a/code/modules/mob/camera/camera.dm +++ b/code/modules/mob/camera/camera.dm @@ -29,14 +29,14 @@ set name = "Move Upwards" set category = "IC" - if(zMove(UP, z_move_flags = ZMOVE_FEEDBACK)) + if(zstep(src, UP, ZMOVE_FEEDBACK)) to_chat(src, span_notice("You move upwards.")) /mob/camera/down() set name = "Move Down" set category = "IC" - if(zMove(DOWN, z_move_flags = ZMOVE_FEEDBACK)) + if(zstep(src, UP, ZMOVE_FEEDBACK)) to_chat(src, span_notice("You move down.")) /mob/camera/can_z_move(direction, turf/start, z_move_flags = NONE, mob/living/rider) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index ad68a81c6c42..a9218dd53829 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -242,7 +242,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp return ghostize(FALSE) -/mob/dead/observer/Move(NewLoc, direct, glide_size_override = 32) +/mob/dead/observer/Move(NewLoc, direct, glide_size_override = 32, z_movement_flags) setDir(direct) if(glide_size_override) diff --git a/code/modules/mob/dead/observer/observer_movement.dm b/code/modules/mob/dead/observer/observer_movement.dm index a0338903bfac..138f33d0f2d7 100644 --- a/code/modules/mob/dead/observer/observer_movement.dm +++ b/code/modules/mob/dead/observer/observer_movement.dm @@ -2,7 +2,7 @@ set name = "Move Upwards" set category = "IC" - if(zMove(UP, z_move_flags = ZMOVE_FEEDBACK)) + if(zstep(src, UP, ZMOVE_FEEDBACK)) to_chat(src, "You move upwards.") /mob/dead/observer/can_z_move(direction, turf/start, z_move_flags = NONE, mob/living/rider) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index d666cfce50e0..42a19fe4d1cc 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -151,6 +151,9 @@ var/atom/movable/thrown_thing var/obj/item/I = get_active_held_item() + if(isnull(I)) + return + var/neckgrab_throw = FALSE // we can't check for if it's a neckgrab throw when totaling up power_throw since we've already stopped pulling them by then, so get it early if(isgrab(I)) diff --git a/code/modules/mob/living/carbon/carbon_movement.dm b/code/modules/mob/living/carbon/carbon_movement.dm index 980a01980117..343c96b813a1 100644 --- a/code/modules/mob/living/carbon/carbon_movement.dm +++ b/code/modules/mob/living/carbon/carbon_movement.dm @@ -6,7 +6,7 @@ ..() return loc.handle_slip(src, knockdown_amount, slipped_on, lube_flags, paralyze, force_drop) -/mob/living/carbon/Move(NewLoc, direct) +/mob/living/carbon/Move(NewLoc, direct, glide_size_override, z_movement_flags) . = ..() if(!(usr == src)) return diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index b568108a3319..339771a85cd3 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1147,3 +1147,14 @@ if(head.w_class > brain.w_class + 1) return prob(100 / 2**(head.w_class - brain.w_class - 1)) return TRUE + +/mob/living/carbon/human/up() + . = ..() + if(.) + return + var/obj/climbable = check_zclimb() + if(!climbable) + can_z_move(UP, get_turf(src), ZMOVE_FEEDBACK|ZMOVE_FLIGHT_FLAGS) + return FALSE + + return ClimbUp(climbable) diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index eba4d1e9145f..4dcd5a3b3a95 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -25,7 +25,7 @@ if(dna.species.negates_gravity(src) || ..()) return TRUE -/mob/living/carbon/human/Move(NewLoc, direct) +/mob/living/carbon/human/Move(NewLoc, direct, glide_size_override, z_movement_flags) . = ..() if(shoes && body_position == STANDING_UP && loc == NewLoc && has_gravity(loc)) SEND_SIGNAL(shoes, COMSIG_SHOES_STEP_ACTION) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 45f8830564a5..dac6ef50454f 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -169,32 +169,18 @@ mob_swap = TRUE if(mob_swap) //switch our position with M - if(loc && !loc.Adjacent(M.loc)) + if(loc && !loc.MultiZAdjacent(M.loc)) return TRUE + now_pushing = TRUE + var/oldloc = loc var/oldMloc = M.loc - - - var/M_passmob = (M.pass_flags & PASSMOB) // we give PASSMOB to both mobs to avoid bumping other mobs during swap. - var/src_passmob = (pass_flags & PASSMOB) - M.pass_flags |= PASSMOB - pass_flags |= PASSMOB - - var/move_failed = FALSE - if(!M.Move(oldloc) || !Move(oldMloc)) - M.forceMove(oldMloc) - forceMove(oldloc) - move_failed = TRUE - if(!src_passmob) - pass_flags &= ~PASSMOB - if(!M_passmob) - M.pass_flags &= ~PASSMOB + forceMove(oldMloc) + M.forceMove(oldloc) now_pushing = FALSE - - if(!move_failed) - return TRUE + return TRUE //okay, so we didn't switch. but should we push? //not if he's not CANPUSH of course @@ -860,7 +846,7 @@ /mob/living/proc/update_wound_overlays() return -/mob/living/Move(atom/newloc, direct, glide_size_override) +/mob/living/Move(atom/newloc, direct, glide_size_override, z_movement_flags) if(lying_angle != 0) lying_angle_on_movement(direct) if (buckled && buckled.loc != newloc) //not updating position @@ -1565,17 +1551,23 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/forceMove(atom/destination) if(!currently_z_moving) - release_all_grabs() - free_from_all_grabs() if(buckled && !HAS_TRAIT(src, TRAIT_CANNOT_BE_UNBUCKLED)) buckled.unbuckle_mob(src, force = TRUE) if(has_buckled_mobs()) unbuckle_all_mobs(force = TRUE) + . = ..() - if(. && client) + if(!.) + return + if(!QDELETED(src) && (LAZYLEN(grabbed_by) || get_active_grabs())) + recheck_grabs() + + if(client) reset_perspective() + + /mob/living/proc/update_z(new_z) // 1+ to register, null to unregister if (registered_z != new_z) if (registered_z) diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm index f8849c360c19..a4b7baba3b20 100644 --- a/code/modules/mob/living/living_movement.dm +++ b/code/modules/mob/living/living_movement.dm @@ -118,26 +118,7 @@ remove_movespeed_modifier(/datum/movespeed_modifier/bulky_drag) */ -/** - * We want to relay the zmovement to the buckled atom when possible - * and only run what we can't have on buckled.zMove() or buckled.can_z_move() here. - * This way we can avoid esoteric bugs, copypasta and inconsistencies. - */ -/mob/living/zMove(dir, turf/target, z_move_flags = ZMOVE_FLIGHT_FLAGS) - if(buckled) - if(buckled.currently_z_moving) - return FALSE - if(!(z_move_flags & ZMOVE_ALLOW_BUCKLED)) - buckled.unbuckle_mob(src, force = TRUE, can_fall = FALSE) - else - if(!target) - target = can_z_move(dir, get_turf(src), z_move_flags, src) - if(!target) - return FALSE - return buckled.zMove(dir, target, z_move_flags) // Return value is a loc. - return ..() - -/mob/living/can_z_move(direction, turf/start, z_move_flags = ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK, mob/living/rider) +/mob/living/can_z_move(direction, turf/start, z_move_flags, mob/living/rider) // Check physical climbing ability if((z_move_flags & ZMOVE_INCAPACITATED_CHECKS)) if(incapacitated()) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 5cd18ca61fd1..bf02f88c7c64 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -1016,14 +1016,14 @@ set name = "Move Upwards" set category = "IC" - if(eyeobj.zMove(UP, z_move_flags = ZMOVE_FEEDBACK)) + if(zstep(eyeobj, UP, ZMOVE_FEEDBACK)) to_chat(src, span_notice("You move upwards.")) /mob/living/silicon/ai/down() set name = "Move Down" set category = "IC" - if(eyeobj.zMove(DOWN, z_move_flags = ZMOVE_FEEDBACK)) + if(zstep(eyeobj, DOWN, ZMOVE_FEEDBACK)) to_chat(src, span_notice("You move down.")) /// Proc to hook behavior to the changes of the value of [aiRestorePowerRoutine]. diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index d0246f3b7b5b..77cb0adf204e 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -101,11 +101,6 @@ if(ai.master_multicam) ai.master_multicam.refresh_view() -/mob/camera/ai_eye/zMove(dir, turf/target, z_move_flags = NONE, recursions_left = 1, list/falling_movs) - . = ..() - if(.) - setLoc(loc, force_update = TRUE) - /mob/camera/ai_eye/Move() return diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 998ab16d6e65..2263216ecd8b 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -437,7 +437,7 @@ return iswallturf(T) || ismineralturf(T) -/mob/living/simple_animal/hostile/Move(atom/newloc, dir , step_x , step_y) +/mob/living/simple_animal/hostile/Move(atom/newloc, dir, glide_size_override, z_movement_flags) if(dodging && approaching_target && prob(dodge_prob) && moving_diagonally == 0 && isturf(loc) && isturf(newloc)) return dodge(newloc,dir) else diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 9051809edfe7..356f5b903172 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -535,7 +535,10 @@ var/atom/loc_atom = loc return loc_atom.relaymove(src, UP) - if(zMove(UP, z_move_flags = ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK)) + //Human's up() override has its own feedback + var/flags = ishuman(src) ? ZMOVE_FLIGHT_FLAGS : ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK + . = zstep(src, UP, flags) + if(.) to_chat(src, span_notice("You move upwards.")) ///Moves a mob down a z level @@ -547,8 +550,9 @@ var/atom/loc_atom = loc return loc_atom.relaymove(src, DOWN) - if(zMove(DOWN, z_move_flags = ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK)) + if(zstep(src, DOWN, ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK)) to_chat(src, span_notice("You move down.")) + return FALSE /mob/abstract_move(atom/destination) diff --git a/code/modules/mod/modules/modules_maint.dm b/code/modules/mod/modules/modules_maint.dm index 418c9b49671a..dbf71dc5cdb4 100644 --- a/code/modules/mod/modules/modules_maint.dm +++ b/code/modules/mod/modules/modules_maint.dm @@ -268,7 +268,7 @@ qdel(mod.wearer.RemoveElement(/datum/element/forced_gravity, NEGATIVE_GRAVITY)) UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED) REMOVE_TRAIT(mod.wearer, TRAIT_SILENT_FOOTSTEPS, MOD_TRAIT) - mod.wearer.zFall(falling_from_move = TRUE) + mod.wearer.zFall() /obj/item/mod/module/atrocinator/proc/check_upstairs() SIGNAL_HANDLER diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm new file mode 100644 index 000000000000..c23559211ef7 --- /dev/null +++ b/code/modules/multiz/movement.dm @@ -0,0 +1,270 @@ +/// A step() variant that allows passing z_movement_flags. Normal step() is fine if you do not need special movement flags. +/proc/zstep(atom/movable/mover, dir, z_movement_flags) + if(!istype(mover)) + return + + var/turf/destination = get_step_multiz(mover, dir) + if(!mover.can_z_move(dir, z_move_flags = z_movement_flags)) + return FALSE + + if(!destination) + if(z_movement_flags & ZMOVE_FEEDBACK) + to_chat(mover, span_warning("There is nothing of interest in that direction.")) + return FALSE + return mover.Move(destination, dir, null, z_movement_flags) + +/atom/movable/proc/onZImpact(turf/impacted_turf, levels, message = TRUE) + if(message) + visible_message( + span_danger("[src] slams into [impacted_turf]!"), + blind_message = span_hear("You hear something slam into the deck.") + ) + + INVOKE_ASYNC(src, PROC_REF(SpinAnimation), 5, 2) + return TRUE + +/// Returns a list of movables that should also be affected when src moves through zlevels, and src. +/atom/movable/proc/get_z_move_affected(z_move_flags) + . = list(src) + if(buckled_mobs) + . |= buckled_mobs + if(!(z_move_flags & ZMOVE_INCLUDE_PULLED)) + return + for(var/mob/living/buckled as anything in buckled_mobs) + if(buckled.pulling) + . |= buckled.pulling + if(pulling) + . |= pulling + +/** + * Checks if the destination turf is elegible for z movement from the start turf to a given direction and returns it if so. + * Args: + * * direction: the direction to go, UP or DOWN, only relevant if target is null. + * * start: Each destination has a starting point on the other end. This is it. Most of the times the location of the source. + * * z_move_flags: bitflags used for various checks. See __DEFINES/movement.dm. + * * rider: A living mob in control of the movable. Only non-null when a mob is riding a vehicle through z-levels. + */ +/atom/movable/proc/can_z_move(direction, turf/start, z_move_flags = ZMOVE_FLIGHT_FLAGS, mob/living/rider) + if(!start) + start = get_turf(src) + if(!start) + CRASH("Something tried to zMove from nullspace.") + + if(!(direction & (UP|DOWN))) + CRASH("can_z_move() received an invalid direction ([direction || "null"])") + + var/turf/destination = get_step_multiz(start, direction) + if(!destination) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, span_notice("There is nothing of interest in this direction.")) + return FALSE + + // Check flight + if(z_move_flags & ZMOVE_CAN_FLY_CHECKS) + if(!(movement_type & (FLYING|FLOATING)) && has_gravity(start) && (direction == UP)) + if(z_move_flags & ZMOVE_FEEDBACK) + if(rider) + to_chat(rider, span_warning("[src] is is not capable of flight.")) + else + to_chat(src, span_warning("You stare at [destination].")) + return FALSE + + // Check CanZPass + if(!(z_move_flags & ZMOVE_IGNORE_OBSTACLES)) + // Check exit + if(!start.CanZPass(src, direction, z_move_flags)) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, span_warning("[start] is in the way.")) + return FALSE + + // Check enter + if(!destination.CanZPass(src, direction, z_move_flags)) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, span_warning("You bump against [destination].")) + return FALSE + + if(!(z_move_flags & ZMOVE_SKIP_CANMOVEONTO)) + // Check destination movable CanPass + for(var/atom/movable/A as anything in destination) + if(!A.CanMoveOnto(src, direction)) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, span_warning("You are blocked by [A].")) + return FALSE + + // Check if we would fall. + if(z_move_flags & ZMOVE_FALL_CHECKS) + if((throwing || (movement_type & (FLYING|FLOATING)) || !has_gravity(start))) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, span_warning("You see nothing to hold onto.")) + return FALSE + + return destination //used by some child types checks and zMove() + +/// Precipitates a movable (plus whatever buckled to it) to lower z levels if possible and then calls zImpact() +/atom/movable/proc/zFall(force = FALSE) + if(QDELETED(src)) + return FALSE + + var/direction = DOWN + if(has_gravity() == NEGATIVE_GRAVITY) + direction = UP + + var/turf/target = direction == UP ? GetAbove(src) : GetBelow(src) + if(!target) + return FALSE + + var/isliving = isliving(src) + if(!isliving && !isobj(src)) + return + + if(isliving) + var/mob/living/falling_living = src + //relay this mess to whatever the mob is buckled to. + if(falling_living.buckled) + return falling_living.buckled.zFall(force) + + if(!force && !can_z_move(direction, get_turf(src), direction == DOWN ? ZMOVE_SKIP_CANMOVEONTO|ZMOVE_FALL_FLAGS : ZMOVE_FALL_FLAGS)) + return FALSE + + if(!CanZFall(get_turf(src), direction)) + return FALSE + + spawn(0) + _doZFall(target, get_turf(src)) + return TRUE + +/atom/movable/proc/_doZFall(turf/destination, turf/prev_turf) + PRIVATE_PROC(TRUE) + SHOULD_NOT_OVERRIDE(TRUE) + + if(QDELETED(src)) + return + + var/turf/falling_from = get_turf(loc) + forceMove(destination) + destination.zImpact(src, 1, prev_turf) + +/atom/movable/proc/CanZFall(turf/from, direction, anchor_bypass) + if(anchored && !anchor_bypass) + return FALSE + + if(from) + for(var/obj/O in from) + if(O.obj_flags & BLOCK_Z_FALL) + return FALSE + + var/turf/other = direction == UP ? GetAbove(from) : GetBelow(from) + if(!from.CanZPass(from, direction) || !other?.CanZPass(from, direction)) //Kinda hacky but it does work. + return FALSE + + return TRUE + +/// Returns an object we can climb onto +/atom/movable/proc/check_zclimb() + var/turf/above = GetAbove(src) + if(!above?.CanZPass(src, UP)) + return + + var/list/all_turfs_above = get_adjacent_open_turfs(above) + + //Check directly above first + . = get_climbable_surface(above) + if(.) + return + + //Next, try the direction the mob is facing + . = get_climbable_surface(get_step(above, dir)) + if(.) + return + + for(var/turf/T as turf in all_turfs_above) + . = get_climbable_surface(T) + if(.) + return + +/atom/movable/proc/get_climbable_surface(turf/T) + var/climb_target + if(!T.Enter(src)) + return + if(!isopenspaceturf(T) && isfloorturf(T)) + climb_target = T + else + for(var/obj/I in T) + if(I.obj_flags & BLOCK_Z_FALL) + climb_target = I + break + + if(climb_target) + return climb_target + +/atom/movable/proc/ClimbUp(atom/onto) + if(!isturf(loc)) + return FALSE + + var/turf/above = GetAbove(src) + var/turf/destination = get_turf(onto) + if(!above || !above.Adjacent(destination, mover = src)) + return FALSE + + if(has_gravity() > 0) + var/can_overcome + for(var/atom/A in loc) + if(HAS_TRAIT(A, TRAIT_CLIMBABLE)) + can_overcome = TRUE + break; + + if(!can_overcome) + var/list/objects_to_stand_on = list( + /obj/structure/chair, + /obj/structure/bed, + /obj/structure/lattice + ) + for(var/path in objects_to_stand_on) + if(locate(path) in loc) + can_overcome = TRUE + break; + if(!can_overcome) + to_chat(src, span_warning("You cannot reach [onto] from here!")) + return FALSE + + visible_message( + span_notice("[src] starts climbing onto \the [onto]."), + span_notice("You start climbing onto \the [onto].") + ) + + if(!do_after(src, time = 5 SECONDS, timed_action_flags = DO_PUBLIC, display = image('icons/hud/do_after.dmi', "help"))) + return FALSE + + visible_message( + span_notice("[src] climbs onto \the [onto]."), + span_notice("You climb onto \the [onto].") + ) + + var/oldloc = loc + setDir(get_dir(above, destination)) + . = Move(destination, null, null, ZMOVE_INCAPACITATED_CHECKS) + if(.) + playsound(oldloc, 'sound/effects/stairs_step.ogg', 50) + playsound(destination, 'sound/effects/stairs_step.ogg', 50) + +#warn TODO +/* +/** + * We want to relay the zmovement to the buckled atom when possible + * and only run what we can't have on buckled.zMove() or buckled.can_z_move() here. + * This way we can avoid esoteric bugs, copypasta and inconsistencies. + */ +/mob/living/zMove(dir, turf/target, z_move_flags = ZMOVE_FLIGHT_FLAGS) + if(buckled) + if(buckled.currently_z_moving) + return FALSE + if(!(z_move_flags & ZMOVE_ALLOW_BUCKLED)) + buckled.unbuckle_mob(src, force = TRUE, can_fall = FALSE) + else + if(!target) + target = can_z_move(dir, get_turf(src), z_move_flags, src) + if(!target) + return FALSE + return buckled.zMove(dir, target, z_move_flags) // Return value is a loc. + return ..() +*/ diff --git a/daedalus.dme b/daedalus.dme index e66fa87f16ba..1dd3e69bf042 100644 --- a/daedalus.dme +++ b/daedalus.dme @@ -3838,6 +3838,7 @@ #include "code\modules\movespeed\modifiers\mobs.dm" #include "code\modules\movespeed\modifiers\reagent.dm" #include "code\modules\movespeed\modifiers\status_effects.dm" +#include "code\modules\multiz\movement.dm" #include "code\modules\ninja\__ninjaDefines.dm" #include "code\modules\ninja\energy_katana.dm" #include "code\modules\ninja\ninja_explosive.dm" From 7070449c6bc2b6f2518568cad57500d0031c21e2 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Thu, 28 Sep 2023 03:53:11 -0400 Subject: [PATCH 10/37] NICE --- code/game/atoms_movable.dm | 11 ++-- code/modules/grab/grab_datum.dm | 3 +- code/modules/grab/grab_helpers.dm | 11 ++++ code/modules/grab/grab_living.dm | 71 +++++++++++++++----------- code/modules/grab/grabs/grab_normal.dm | 2 +- 5 files changed, 58 insertions(+), 40 deletions(-) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 1941faec1a25..ade6251be109 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -384,7 +384,6 @@ if(QDELING(src)) CRASH("Illegal Move()! on [type]") - var/turf/current_turf = loc if(!moving_from_pull) recheck_grabs(z_allowed = TRUE) @@ -464,6 +463,9 @@ set_currently_z_moving(FALSE) return + if(set_dir_on_move && dir != direct) + setDir(direct) + if(. && isliving(src)) var/mob/living/L = src L.handle_grabs_during_movement(oldloc, direct) @@ -477,9 +479,6 @@ last_move = direct - if(set_dir_on_move && dir != direct) - setDir(direct) - if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s) . = FALSE @@ -803,10 +802,6 @@ var/atom/oldloc = loc var/is_multi_tile = bound_width > world.icon_size || bound_height > world.icon_size if(destination) - ///zMove already handles whether a pull from another movable should be broken. - if(LAZYLEN(grabbed_by) && !currently_z_moving) - free_from_all_grabs() - var/same_loc = oldloc == destination var/area/old_area = get_area(oldloc) var/area/destarea = get_area(destination) diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index dc538a04eec2..90d59d363a43 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -249,8 +249,9 @@ GLOBAL_LIST_EMPTY(all_grabstates) /// Add effects that apply based on damage_stage here /datum/grab/proc/update_stage_effects(obj/item/hand_item/grab/G, datum/grab/old_grab, dropping_grab) var/old_damage_stage = old_grab?.damage_stage || 0 + var/new_stage = dropping_grab ? 0 : damage_stage - switch(!dropping_grab || damage_stage) // Current state. + switch(new_stage) // Current state. if(GRAB_PASSIVE) REMOVE_TRAIT(G.affecting, TRAIT_IMMOBILIZED, REF(G)) REMOVE_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, REF(G)) diff --git a/code/modules/grab/grab_helpers.dm b/code/modules/grab/grab_helpers.dm index 30723334d2bd..103ffae62a13 100644 --- a/code/modules/grab/grab_helpers.dm +++ b/code/modules/grab/grab_helpers.dm @@ -55,6 +55,7 @@ QDEL_LIST(grabbed_by) grabbed_by = null + /// Gets every grabber of this atom, and every grabber of those grabbers, repeat /atom/movable/proc/recursively_get_all_grabbers() RETURN_TYPE(/list) @@ -73,6 +74,16 @@ var/mob/living/L = G.affecting . |= L.recursively_get_all_grabbed_movables() +/// Gets every grab object owned by this mob, and every grabbed atom of those grabbed mobs +/mob/living/proc/recursively_get_conga_line() + RETURN_TYPE(/list) + . = list() + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + . |= G + if(isliving(G.affecting)) + var/mob/living/L = G.affecting + . |= L.recursively_get_conga_line() + /// Get every single member of a grab chain /atom/movable/proc/get_all_grab_chain_members() RETURN_TYPE(/list) diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index 0b1236dbd9d0..f923cffd6c54 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -112,35 +112,46 @@ update_pull_hud_icon() /mob/living/proc/handle_grabs_during_movement(turf/old_loc, direction) - var/list/grabs = recursively_get_all_grabbed_movables() - if(LAZYLEN(grabs)) - for(var/obj/item/hand_item/grab/G in grabs) - var/atom/movable/pulling = G.affecting - if(pulling == src || pulling.loc == loc || !old_loc.Adjacent(AM)) - continue - if(pulling.anchored) - qdel(G) - continue - var/pull_dir = get_dir(pulling, src) - var/target_turf = current_turf - - // Pulling things down/up stairs. zMove() has flags for check_pulling and stop_pulling calls. - // You may wonder why we're not just forcemoving the pulling movable and regrabbing it. - // The answer is simple. forcemoving and regrabbing is ugly and breaks conga lines. - if(pulling.z != z) - target_turf = get_step(pulling, get_dir(pulling, current_turf)) - - if(target_turf != current_turf || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1) - pulling.move_from_pull(src, target_turf, glide_size) - if(QDELETED(G)) - continue - - if(!MultiZAdjacent(pulling)) - qdel(G) + var/list/grabs_in_grab_chain = get_active_grabs() //recursively_get_conga_line() + if(!LAZYLEN(grabs_in_grab_chain)) + return + + for(var/obj/item/hand_item/grab/G in grabs_in_grab_chain) + var/atom/movable/pulling = G.affecting + if(pulling == src || pulling.loc == loc) + continue + if(pulling.anchored) + qdel(G) + continue + + var/pull_dir = get_dir(pulling, src) + var/target_turf = old_loc + + // Pulling things down/up stairs. zMove() has flags for check_pulling and stop_pulling calls. + // You may wonder why we're not just forcemoving the pulling movable and regrabbing it. + // The answer is simple. forcemoving and regrabbing is ugly and breaks conga lines. + if(pulling.z != z) + target_turf = get_step(pulling, get_dir(pulling, old_loc)) + + + if(target_turf != old_loc || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1 && old_loc.Adjacent(pulling)) + pulling.move_from_pull(G.assailant, get_step(pulling, get_dir(pulling, target_turf)), glide_size) + if(QDELETED(G)) continue - if(!QDELETED(G)) - G.update_offsets() - G.current_grab.moved_effect(G) - if(G.current_grab.downgrade_on_move) - G.downgrade() + if(!MultiZAdjacent(pulling)) + qdel(G) + continue + + if(!QDELETED(G)) + G.update_offsets() + G.current_grab.moved_effect(G) + if(G.current_grab.downgrade_on_move) + G.downgrade() + + var/list/my_grabs = get_active_grabs() + for(var/obj/item/hand_item/grab/G in my_grabs) + if(G.current_grab.reverse_facing) + setDir(global.reverse_dir[direction]) + + diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index 5dee686b58b8..19d2acc52c8b 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -310,7 +310,7 @@ /datum/grab/normal/add_context(list/context, obj/item/held_item, mob/living/user, atom/movable/target) . = ..() - if(held_item.sharpness & SHARP_EDGED) + if(held_item?.sharpness & SHARP_EDGED) var/obj/item/hand_item/grab/G = user.is_grabbing(target) switch(G.target_zone) if(BODY_ZONE_HEAD) From 97a62bc085f573a634e3ad7921aa5a828e138aee Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Thu, 28 Sep 2023 18:06:01 -0400 Subject: [PATCH 11/37] LET'S GO MULTIZ MOVEMENT WORKS --- code/_onclick/adjacent.dm | 13 +++++++++++-- code/game/turfs/turf.dm | 2 +- code/modules/grab/grab_datum.dm | 3 --- code/modules/grab/grab_living.dm | 8 ++++---- code/modules/grab/grab_movable.dm | 4 +++- code/modules/grab/grab_object.dm | 2 ++ code/modules/mob/living/living.dm | 5 ++++- 7 files changed, 25 insertions(+), 12 deletions(-) diff --git a/code/_onclick/adjacent.dm b/code/_onclick/adjacent.dm index c6d96d7c9b11..c1a5905e58e8 100644 --- a/code/_onclick/adjacent.dm +++ b/code/_onclick/adjacent.dm @@ -166,11 +166,20 @@ // Are they below us? if(N.z < T.z && HasBelow(T.z)) var/turf/B = GetBelow(T) - return TURF_IS_MIMICKING(B) && neighbor.Adjacent(B) + . = TURF_IS_MIMICKING(T) && neighbor.Adjacent(B) + if(!.) + B = GetAbove(N) + . = TURF_IS_MIMICKING(B) && src.Adjacent(B) + return + // Are they above us? if(HasAbove(T.z)) var/turf/A = GetAbove(T) - return TURF_IS_MIMICKING(A) && neighbor.Adjacent(A) + . = TURF_IS_MIMICKING(A) && neighbor.Adjacent(A) + if(!.) + A = GetBelow(N) + . = TURF_IS_MIMICKING(N) && src.Adjacent(A) + return return FALSE diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index a951dd73b7a9..e8270c03e9dd 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -323,7 +323,7 @@ GLOBAL_LIST_EMPTY(station_turfs) if(L) if(LAZYLEN(L.grabbed_by)) for(var/obj/item/hand_item/grab/G in L.grabbed_by) - if(L.z != G.assailant.z || get_dist(L, G.assailant.z > 1)) + if(L.z != G.assailant.z || get_dist(L, G.assailant) > 1) qdel(G) return TRUE diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index 90d59d363a43..6e6869f1f0d6 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -243,9 +243,6 @@ GLOBAL_LIST_EMPTY(all_grabstates) remove_grab_effects(G) update_stage_effects(G, null, TRUE) - if(G.assailant) - G.assailant.after_grab_release(G.affecting) - /// Add effects that apply based on damage_stage here /datum/grab/proc/update_stage_effects(obj/item/hand_item/grab/G, datum/grab/old_grab, dropping_grab) var/old_damage_stage = old_grab?.damage_stage || 0 diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index f923cffd6c54..eeb21439dfef 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -92,7 +92,7 @@ for(var/obj/item/hand_item/grab/G in get_active_grabs()) var/atom/movable/pulling = G.affecting - if(get_dist(src, pulling) > 1 || (z != pulling.z && !z_allowed)) + if(!MultiZAdjacent(src, pulling)) qdel(G) else if(!isturf(loc)) qdel(G) @@ -120,7 +120,7 @@ var/atom/movable/pulling = G.affecting if(pulling == src || pulling.loc == loc) continue - if(pulling.anchored) + if(pulling.anchored || !isturf(loc)) qdel(G) continue @@ -134,12 +134,12 @@ target_turf = get_step(pulling, get_dir(pulling, old_loc)) - if(target_turf != old_loc || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1 && old_loc.Adjacent(pulling)) + if(target_turf != old_loc || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1) pulling.move_from_pull(G.assailant, get_step(pulling, get_dir(pulling, target_turf)), glide_size) if(QDELETED(G)) continue - if(!MultiZAdjacent(pulling)) + if(!pulling.MultiZAdjacent(src)) qdel(G) continue diff --git a/code/modules/grab/grab_movable.dm b/code/modules/grab/grab_movable.dm index 2a14aa189535..abd1ce703702 100644 --- a/code/modules/grab/grab_movable.dm +++ b/code/modules/grab/grab_movable.dm @@ -37,7 +37,7 @@ return for(var/obj/item/hand_item/grab/G in grabbed_by) - if(moving_diagonally != FIRST_DIAG_STEP && (get_dist(src, G.assailant) > 1 || z != G.assailant.z)) //separated from our puller and not in the middle of a diagonal move. + if(moving_diagonally != FIRST_DIAG_STEP && !MultiZAdjacent(G.assailant)) //separated from our puller and not in the middle of a diagonal move. qdel(G) /// Move grabbed atoms towards a destination @@ -95,3 +95,5 @@ if(last_pixel_x != new_pixel_x || last_pixel_y != new_pixel_y) animate(src, pixel_x = new_pixel_x, pixel_y = new_pixel_y, 3, 1, (LINEAR_EASING|EASE_IN)) + + UPDATE_OO_IF_PRESENT diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 3dca74bd5d9b..4919b1f16f20 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -56,6 +56,8 @@ /obj/item/hand_item/grab/Destroy() current_grab?.let_go(src) + if(assailant) + assailant.after_grab_release(affecting) assailant = null affecting = null return ..() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index dac6ef50454f..39885b4c24ba 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1550,6 +1550,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) return /mob/living/forceMove(atom/destination) + var/old_loc = loc if(!currently_z_moving) if(buckled && !HAS_TRAIT(src, TRAIT_CANNOT_BE_UNBUCKLED)) buckled.unbuckle_mob(src, force = TRUE) @@ -1559,7 +1560,9 @@ GLOBAL_LIST_EMPTY(fire_appearances) . = ..() if(!.) return - if(!QDELETED(src) && (LAZYLEN(grabbed_by) || get_active_grabs())) + + if(!QDELETED(src)) + handle_grabs_during_movement(old_loc, get_dir(old_loc, src)) recheck_grabs() if(client) From b2b207cb435882631535d57e3a7d74e172f22634 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Thu, 28 Sep 2023 18:10:26 -0400 Subject: [PATCH 12/37] minor fix --- code/modules/mob/living/simple_animal/constructs.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index cfb20848fe2c..d32c28f24a2f 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -429,7 +429,7 @@ stored_pulling.forceMove(loc) forceMove(AM) if(stored_pulling) - try_make_grab(stored_pulling, stored_pulling) + try_make_grab(stored_pulling, stored_tier) /mob/living/simple_animal/hostile/construct/harvester/AttackingTarget() if(iscarbon(target)) From 6adb88dd27ce1daff6e2b168738f9a12a8c6e590 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Thu, 28 Sep 2023 18:37:39 -0400 Subject: [PATCH 13/37] improvements --- code/_onclick/adjacent.dm | 8 +-- code/modules/grab/grab_datum.dm | 9 ++- code/modules/grab/grab_object.dm | 1 + code/modules/grab/grabs/grab_aggressive.dm | 13 ++++ code/modules/grab/grabs/grab_normal.dm | 6 ++ code/modules/mob/living/living_defense.dm | 69 ---------------------- 6 files changed, 30 insertions(+), 76 deletions(-) diff --git a/code/_onclick/adjacent.dm b/code/_onclick/adjacent.dm index c1a5905e58e8..39c5ee4ff32e 100644 --- a/code/_onclick/adjacent.dm +++ b/code/_onclick/adjacent.dm @@ -166,20 +166,20 @@ // Are they below us? if(N.z < T.z && HasBelow(T.z)) var/turf/B = GetBelow(T) - . = TURF_IS_MIMICKING(T) && neighbor.Adjacent(B) + . = isopenspaceturf(T) && neighbor.Adjacent(B) if(!.) B = GetAbove(N) - . = TURF_IS_MIMICKING(B) && src.Adjacent(B) + . = isopenspaceturf(B) && src.Adjacent(B) return // Are they above us? if(HasAbove(T.z)) var/turf/A = GetAbove(T) - . = TURF_IS_MIMICKING(A) && neighbor.Adjacent(A) + . = isopenspaceturf(A) && neighbor.Adjacent(A) if(!.) A = GetBelow(N) - . = TURF_IS_MIMICKING(N) && src.Adjacent(A) + . = isopenspaceturf(N) && src.Adjacent(A) return return FALSE diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index 6e6869f1f0d6..234988b19d1b 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -103,7 +103,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) if (can_upgrade(G)) upgrade_effect(G) - log_combat(G.assailant, G.affecting, "tightens their grip on their victim to [upgrab.state_name]") + log_combat(G.assailant, G.affecting, "tightens their grip [upgrab.state_name] on") return upgrab else to_chat(G.assailant, span_warning("[string_process(G, fail_up)]")) @@ -188,7 +188,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) G.action_used() if(G.assailant) - G.assailant.changeNext_move(CLICK_CD_MELEE) + G.assailant.changeNext_move(CLICK_CD_GRABBING) if(istext(.) && G.affecting) make_log(G, "used [.] on") @@ -196,7 +196,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) G.downgrade() /datum/grab/proc/make_log(obj/item/hand_item/grab/G, action) - log_combat(G.assailant, G.affecting, "[action]s their victim") + log_combat(G.assailant, G.affecting, "[action] their victim") /* Override these procs to set how the grab state will work. Some of them are best @@ -309,6 +309,9 @@ GLOBAL_LIST_EMPTY(all_grabstates) // Used when you want an effect to happen when the grab enters this state as an upgrade /datum/grab/proc/enter_as_up(obj/item/hand_item/grab/G) +// Used when you want an effect to happen when the grab enters this state as a downgrade +/datum/grab/proc/enter_as_down(obj/item/hand_item/grab/G) + /datum/grab/proc/item_attack(obj/item/hand_item/grab/G, obj/item) /datum/grab/proc/resolve_item_attack(obj/item/hand_item/grab/G, mob/living/carbon/human/user, obj/item/I, target_zone) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 4919b1f16f20..46790ee2aa4c 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -238,6 +238,7 @@ if(apply_effects) current_grab.apply_grab_effects(src) + current_grab.enter_as_down(src) adjust_position() update_appearance() diff --git a/code/modules/grab/grabs/grab_aggressive.dm b/code/modules/grab/grabs/grab_aggressive.dm index 7288c2b9548e..508442ef2ab2 100644 --- a/code/modules/grab/grabs/grab_aggressive.dm +++ b/code/modules/grab/grabs/grab_aggressive.dm @@ -52,3 +52,16 @@ if((C.clothing_flags & STOPSPRESSUREDAMAGE) && C.returnArmor().getRating(MELEE) > 20) to_chat(G.assailant, span_warning("\The [C] is in the way!")) return FALSE + +/datum/grab/normal/aggressive/enter_as_up(obj/item/hand_item/grab/G) + . = ..() + G.assailant.visible_message( + span_danger("[G.assailant] tightens their grip on [G.affecting] (now hands)!"), + blind_message = span_hear("You hear aggressive shuffling.") + ) + +/datum/grab/normal/aggressive/enter_as_down(obj/item/hand_item/grab/G) + . = ..() + G.assailant.visible_message( + span_danger("[G.assailant] loosens their grip on [G.affecting], allowing them to breathe!"), + ) diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index 19d2acc52c8b..153428affd09 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -308,6 +308,12 @@ log_combat(user, affecting, "hamstrung (grab)") return TRUE +/datum/grab/normal/enter_as_down(obj/item/hand_item/grab/G) + . = ..() + G.assailant.visible_message( + span_warning("[G.assailant] loosens their grip on [G.affecting]."), + ) + /datum/grab/normal/add_context(list/context, obj/item/held_item, mob/living/user, atom/movable/target) . = ..() if(held_item?.sharpness & SHARP_EDGED) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 7180ff404d7e..f03eedef5a6c 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -137,75 +137,6 @@ adjust_fire_stacks(3) ignite_mob() -#warn replace -/* -/mob/living/proc/grabbedby(mob/living/carbon/user, supress_message = FALSE) - if(user == src || anchored || !isturf(user.loc)) - return FALSE - if(!user.pulling || user.pulling != src) - user.start_pulling(src, supress_message = supress_message) - return - -//proc to upgrade a simple pull into a more aggressive grab. -/mob/living/proc/grippedby(mob/living/carbon/user, instant = FALSE) - if(user.grab_state < GRAB_KILL) - user.changeNext_move(CLICK_CD_GRABBING) - var/sound_to_play = 'sound/weapons/thudswoosh.ogg' - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.dna.species.grab_sound) - sound_to_play = H.dna.species.grab_sound - playsound(src.loc, sound_to_play, 50, TRUE, -1) - - if(user.grab_state) //only the first upgrade is instantaneous - var/old_grab_state = user.grab_state - var/grab_upgrade_time = instant ? 0 : 30 - visible_message(span_danger("[user] starts to tighten [user.p_their()] grip on [src]!"), \ - span_userdanger("[user] starts to tighten [user.p_their()] grip on you!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You start to tighten your grip on [src]!")) - switch(user.grab_state) - if(GRAB_AGGRESSIVE) - log_combat(user, src, "attempted to neck grab", addition="neck grab") - if(GRAB_NECK) - log_combat(user, src, "attempted to strangle", addition="kill grab") - if(!do_after(user, src, grab_upgrade_time)) - return FALSE - if(!user.pulling || user.pulling != src || user.grab_state != old_grab_state) - return FALSE - user.setGrabState(user.grab_state + 1) - switch(user.grab_state) - if(GRAB_AGGRESSIVE) - var/add_log = "" - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - visible_message(span_danger("[user] firmly grips [src]!"), - span_danger("[user] firmly grips you!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You firmly grip [src]!")) - add_log = " (pacifist)" - else - visible_message(span_danger("[user] grabs [src] aggressively!"), \ - span_userdanger("[user] grabs you aggressively!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You grab [src] aggressively!")) - drop_all_held_items() - release_all_grabs() - log_combat(user, src, "grabbed", addition="aggressive grab[add_log]") - if(GRAB_NECK) - log_combat(user, src, "grabbed", addition="neck grab") - visible_message(span_danger("[user] grabs [src] by the neck!"),\ - span_userdanger("[user] grabs you by the neck!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You grab [src] by the neck!")) - if(!buckled && !density) - Move(user.loc) - if(GRAB_KILL) - log_combat(user, src, "strangled", addition="kill grab") - visible_message(span_danger("[user] is strangling [src]!"), \ - span_userdanger("[user] is strangling you!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You're strangling [src]!")) - if(!buckled && !density) - Move(user.loc) - user.set_pull_offsets(src, grab_state) - return TRUE -*/ - /mob/living/attack_slime(mob/living/simple_animal/slime/M) if(!SSticker.HasRoundStarted()) to_chat(M, "You cannot attack people before the game has started.") From 94116e896b927185849d690247b08df7df613daa Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Fri, 29 Sep 2023 02:03:40 -0400 Subject: [PATCH 14/37] bunch of work --- code/__DEFINES/ai.dm | 2 +- .../signals_atom/signals_atom_movement.dm | 2 - code/__DEFINES/grab_defines.dm | 12 ++- code/datums/elements/atmos_requirements.dm | 2 +- code/datums/elements/nerfed_pulling.dm | 53 ---------- code/game/atoms_movable.dm | 1 - code/game/machinery/washing_machine.dm | 1 + .../game/objects/items/devices/radio/radio.dm | 2 +- code/game/objects/structures/stairs.dm | 3 +- code/game/objects/structures/watercloset.dm | 2 + code/modules/aquarium/aquarium.dm | 47 +++++---- .../kitchen_machinery/gibber.dm | 61 ++++++------ .../kitchen_machinery/processor.dm | 2 +- code/modules/grab/grab_datum.dm | 28 ++++-- code/modules/grab/grab_helpers.dm | 6 -- code/modules/grab/grab_living.dm | 7 +- code/modules/grab/grab_object.dm | 12 ++- code/modules/grab/grabs/grab_aggressive.dm | 20 ++-- code/modules/grab/grabs/grab_neck.dm | 40 +++++--- code/modules/grab/grabs/grab_strangle.dm | 36 +++++++ code/modules/holodeck/items.dm | 3 +- code/modules/mob/living/carbon/life.dm | 2 +- code/modules/mob/living/living.dm | 98 +------------------ code/modules/mob/living/living_defines.dm | 2 - code/modules/mob/living/living_movement.dm | 30 +++--- .../simple_animal/hostile/giant_spider.dm | 1 - .../simple_animal/hostile/space_dragon.dm | 1 - .../mob/living/simple_animal/simple_animal.dm | 2 +- code/modules/mob/mob_movement.dm | 16 +-- code/modules/mod/mod_ai.dm | 2 +- code/modules/movespeed/modifiers/mobs.dm | 2 +- code/modules/multiz/movement.dm | 7 -- code/modules/religion/religion_structures.dm | 8 +- code/modules/tables/tables_racks.dm | 1 + daedalus.dme | 2 +- 35 files changed, 223 insertions(+), 293 deletions(-) delete mode 100644 code/datums/elements/nerfed_pulling.dm create mode 100644 code/modules/grab/grabs/grab_strangle.dm diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index ce4a13e18d29..ed88474afdd1 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -36,7 +36,7 @@ /// probability that the pawn should try resisting out of restraints #define RESIST_SUBTREE_PROB 50 ///macro for whether it's appropriate to resist right now, used by resist subtree -#define SHOULD_RESIST(source) (source.on_fire || source.buckled || HAS_TRAIT(source, TRAIT_RESTRAINED) || (source.check_grab_severities(GRAB_AGGRESSIVE))) +#define SHOULD_RESIST(source) (source.on_fire || source.buckled || HAS_TRAIT(source, TRAIT_RESTRAINED) || HAS_TRAIT(source, TRAIT_AGGRESSIVE_GRAB)) ///macro for whether the pawn can act, used generally to prevent some horrifying ai disasters #define IS_DEAD_OR_INCAP(source) (source.incapacitated() || source.stat) diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm index 540415bf48bb..eb3fccdbf931 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm @@ -18,8 +18,6 @@ ///called on /living, when a grab is attempted, but before it completes, from base of [/mob/living/make_grab]: (atom/movable/thing, grab_type) #define COMSIG_LIVING_TRY_GRAB "living_try_pull" #define COMSIG_LIVING_CANCEL_GRAB (1 << 0) -/// Called from /mob/living/update_pull_movespeed -#define COMSIG_LIVING_UPDATING_PULL_MOVESPEED "living_updating_pull_movespeed" /// Called from /mob/living/PushAM -- Called when this mob is about to push a movable, but before it moves /// (aotm/movable/being_pushed) diff --git a/code/__DEFINES/grab_defines.dm b/code/__DEFINES/grab_defines.dm index d75aab5a55f3..1b3437a83073 100644 --- a/code/__DEFINES/grab_defines.dm +++ b/code/__DEFINES/grab_defines.dm @@ -1,3 +1,9 @@ -/// Trait source for aggressive grabs -#define AGGRESSIVE_GRAB "trait_aggressive_grab" -#define NECK_GRAB "trait_neck_grab" +// Trait sources for grabs +#define AGGRESSIVE_GRAB "source_aggressive_grab" +#define NECK_GRAB "source_neck_grab" +#define KILL_GRAB "source_kill_grab" + +/// Applied to movables that are aggressively grabbed OR HIGHER +#define TRAIT_AGGRESSIVE_GRAB "trait_aggressive_grab" +/// Applied to movables that are being strangled +#define TRAIT_KILL_GRAB "trait_strangle_grab" diff --git a/code/datums/elements/atmos_requirements.dm b/code/datums/elements/atmos_requirements.dm index f06573618221..71f074978291 100644 --- a/code/datums/elements/atmos_requirements.dm +++ b/code/datums/elements/atmos_requirements.dm @@ -36,7 +36,7 @@ target.throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy) /datum/element/atmos_requirements/proc/is_breathable_atmos(mob/living/target) - if(target.check_grab_severities(GRAB_KILL) && atmos_requirements["min_oxy"]) + if(HAS_TRAIT(target, TRAIT_KILL_GRAB) && atmos_requirements["min_oxy"]) return FALSE if(!isopenturf(target.loc)) diff --git a/code/datums/elements/nerfed_pulling.dm b/code/datums/elements/nerfed_pulling.dm deleted file mode 100644 index c192e04f5c2d..000000000000 --- a/code/datums/elements/nerfed_pulling.dm +++ /dev/null @@ -1,53 +0,0 @@ -/// This living will be slower when pulling/moving anything in the given typecache -/datum/element/nerfed_pulling - element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH - id_arg_index = 2 - - /// The typecache of things that shouldn't be easily movable - var/list/typecache - -/datum/element/nerfed_pulling/Attach(datum/target, list/typecache) - . = ..() - - if (!isliving(target)) - return ELEMENT_INCOMPATIBLE - - src.typecache = typecache - - RegisterSignal(target, COMSIG_LIVING_PUSHING_MOVABLE, PROC_REF(on_push_movable)) - RegisterSignal(target, COMSIG_LIVING_UPDATING_PULL_MOVESPEED, PROC_REF(on_updating_pull_movespeed)) - -/datum/element/nerfed_pulling/Detach(mob/living/source) - source.remove_movespeed_modifier(/datum/movespeed_modifier/nerfed_bump) - source.remove_movespeed_modifier(/datum/movespeed_modifier/nerfed_pull) - - UnregisterSignal(source, list(COMSIG_LIVING_PUSHING_MOVABLE, COMSIG_LIVING_UPDATING_PULL_MOVESPEED)) - - return ..() - -/datum/element/nerfed_pulling/proc/on_push_movable(mob/living/source, atom/movable/being_pushed) - SIGNAL_HANDLER - - if (!will_slow_down(being_pushed)) - return - - source.add_movespeed_modifier(/datum/movespeed_modifier/nerfed_bump) - addtimer(CALLBACK(source, TYPE_PROC_REF(/mob, remove_movespeed_modifier), /datum/movespeed_modifier/nerfed_bump), 1 SECONDS, TIMER_OVERRIDE | TIMER_UNIQUE) - -/datum/element/nerfed_pulling/proc/on_updating_pull_movespeed(mob/living/source) - SIGNAL_HANDLER - - if (!will_slow_down(source.pulling)) - source.remove_movespeed_modifier(/datum/movespeed_modifier/nerfed_pull) - return - - source.add_movespeed_modifier(/datum/movespeed_modifier/nerfed_pull) - -/datum/element/nerfed_pulling/proc/will_slow_down(datum/input) - return !isnull(input) && typecache[input.type] - -/datum/movespeed_modifier/nerfed_pull - multiplicative_slowdown = 5.5 - -/datum/movespeed_modifier/nerfed_bump - multiplicative_slowdown = 5.5 diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index ade6251be109..4203282f7cc6 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -62,7 +62,6 @@ */ var/movement_type = GROUND - var/atom/movable/pulling var/throwforce = 0 var/datum/component/orbiter/orbiting diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index 5867f95043ab..c5a5c28378f0 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -391,6 +391,7 @@ GLOBAL_LIST_INIT(dye_registry, list( if(istype(L, /mob/living/simple_animal/pet)) L.forceMove(src) update_appearance() + return TRUE /obj/machinery/washing_machine/attack_hand_secondary(mob/user, modifiers) . = ..() diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 323db9f5c4ab..f2ce888af395 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -228,7 +228,7 @@ if(talking_carbon.handcuffed)// If we're handcuffed, we can't press the button to_chat(talking_carbon, span_warning("You can't use the radio while handcuffed!")) return ITALICS | REDUCE_RANGE - if(talking_carbon.check_grab_severities(GRAB_AGGRESSIVE)) + if(HAS_TRAIT(talking_carbon, TRAIT_AGGRESSIVE_GRAB)) to_chat(talking_carbon, span_warning("You can't use the radio while aggressively grabbed!")) return ITALICS | REDUCE_RANGE diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm index b881e41498eb..085e5670c3c8 100644 --- a/code/game/objects/structures/stairs.dm +++ b/code/game/objects/structures/stairs.dm @@ -101,9 +101,8 @@ playsound(my_turf, 'sound/effects/stairs_step.ogg', 50) /// Moves anything that's being dragged by src or anything buckled to it to the stairs turf. - climber.pulling?.move_from_pull(climber, loc, climber.glide_size) for(var/mob/living/buckled as anything in climber.buckled_mobs) - buckled.pulling?.move_from_pull(buckled, loc, buckled.glide_size) + buckled.handle_grabs_during_movement(my_turf, get_dir(my_turf, target)) /obj/structure/stairs/intercept_zImpact(list/falling_movables, levels = 1) . = ..() diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index 21a5c23dd18f..0ff0ab0aa2e1 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -75,11 +75,13 @@ if(was_alive && swirlie.stat == DEAD && swirlie.client) swirlie.client.give_award(/datum/award/achievement/misc/swirlie, swirlie) // just like space high school all over again! swirlie = null + return TRUE else playsound(src.loc, 'sound/effects/bang.ogg', 25, TRUE) GM.visible_message(span_danger("[user] slams [GM.name] into [src]!"), span_userdanger("[user] slams you into [src]!")) log_combat(user, GM, "toilet slammed") GM.adjustBruteLoss(5) + return TRUE else to_chat(user, span_warning("You need a tighter grip!")) diff --git a/code/modules/aquarium/aquarium.dm b/code/modules/aquarium/aquarium.dm index fff606f84db9..3f6c8f05b9b3 100644 --- a/code/modules/aquarium/aquarium.dm +++ b/code/modules/aquarium/aquarium.dm @@ -142,31 +142,36 @@ return NONE /obj/structure/aquarium/interact(mob/user) - if(!broken && user.pulling && isliving(user.pulling)) - var/mob/living/living_pulled = user.pulling - var/datum/component/aquarium_content/content_component = living_pulled.GetComponent(/datum/component/aquarium_content) - if(content_component && content_component.is_ready_to_insert(src)) - try_to_put_mob_in(user) - else if(panel_open) + if(panel_open) . = ..() //call base ui_interact +/obj/structure/aquarium/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(broken || !isliving(victim)) + return + + var/mob/living/living_pulled = victim + var/datum/component/aquarium_content/content_component = living_pulled.GetComponent(/datum/component/aquarium_content) + if(content_component && content_component.is_ready_to_insert(src)) + try_to_put_mob_in(user, grab) + /// Tries to put mob pulled by the user in the aquarium after a delay -/obj/structure/aquarium/proc/try_to_put_mob_in(mob/user) - if(user.pulling && isliving(user.pulling)) - var/mob/living/living_pulled = user.pulling - if(living_pulled.buckled || living_pulled.has_buckled_mobs()) - to_chat(user, span_warning("[living_pulled] is attached to something!")) +/obj/structure/aquarium/proc/try_to_put_mob_in(mob/living/user, obj/item/hand_item/grab/G) + var/mob/living/living_pulled = G.affecting + if(living_pulled.buckled || living_pulled.has_buckled_mobs()) + to_chat(user, span_warning("[living_pulled] is attached to something!")) + return + user.visible_message(span_danger("[user] starts to put [living_pulled] into [src]!")) + + if(do_after(user, src, 10 SECONDS)) + if(QDELETED(living_pulled) || !user.is_grabbing(living_pulled) || living_pulled.buckled || living_pulled.has_buckled_mobs()) return - user.visible_message(span_danger("[user] starts to put [living_pulled] into [src]!")) - if(do_after(user, src, 10 SECONDS)) - if(QDELETED(living_pulled) || user.pulling != living_pulled || living_pulled.buckled || living_pulled.has_buckled_mobs()) - return - var/datum/component/aquarium_content/content_component = living_pulled.GetComponent(/datum/component/aquarium_content) - if(content_component || content_component.is_ready_to_insert(src)) - return - user.visible_message(span_danger("[user] stuffs [living_pulled] into [src]!")) - living_pulled.forceMove(src) - update_appearance() + var/datum/component/aquarium_content/content_component = living_pulled.GetComponent(/datum/component/aquarium_content) + if(content_component || content_component.is_ready_to_insert(src)) + return + user.visible_message(span_danger("[user] stuffs [living_pulled] into [src]!")) + living_pulled.forceMove(src) + update_appearance() /obj/structure/aquarium/ui_data(mob/user) . = ..() diff --git a/code/modules/food_and_drinks/kitchen_machinery/gibber.dm b/code/modules/food_and_drinks/kitchen_machinery/gibber.dm index 2b7af865200a..e3448d3a25be 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/gibber.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/gibber.dm @@ -73,34 +73,39 @@ to_chat(user, span_warning("[src] cannot be used unless bolted to the ground!")) return - if(user.pulling && isliving(user.pulling)) - var/mob/living/L = user.pulling - if(!iscarbon(L)) - to_chat(user, span_warning("This item is not suitable for the gibber!")) - return - var/mob/living/carbon/C = L - if(C.buckled ||C.has_buckled_mobs()) - to_chat(user, span_warning("[C] is attached to something!")) - return - - if(!ignore_clothing) - for(var/obj/item/I in C.held_items + C.get_equipped_items()) - if(!HAS_TRAIT(I, TRAIT_NODROP)) - to_chat(user, span_warning("Subject may not have abiotic items on!")) - return - - user.visible_message(span_danger("[user] starts to put [C] into the gibber!")) - - add_fingerprint(user) - - if(do_after(user, src, gibtime)) - if(C && user.pulling == C && !C.buckled && !C.has_buckled_mobs() && !occupant) - user.visible_message(span_danger("[user] stuffs [C] into the gibber!")) - C.forceMove(src) - set_occupant(C) - update_appearance() - else - startgibbing(user) + startgibbing(user) + +/obj/machinery/gibber/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!isliving(victim)) + return + + var/mob/living/L = victim + if(!iscarbon(L)) + to_chat(user, span_warning("This item is not suitable for the gibber!")) + return + + var/mob/living/carbon/C = L + if(C.buckled ||C.has_buckled_mobs()) + to_chat(user, span_warning("[C] is attached to something!")) + return + + if(!ignore_clothing) + for(var/obj/item/I in C.held_items + C.get_equipped_items()) + if(!HAS_TRAIT(I, TRAIT_NODROP)) + to_chat(user, span_warning("Subject may not have abiotic items on!")) + return + + user.visible_message(span_danger("[user] starts to put [C] into the gibber!")) + + add_fingerprint(user) + + if(do_after(user, src, gibtime)) + if(C && user.is_grabbing(C) && !C.buckled && !C.has_buckled_mobs() && !occupant) + user.visible_message(span_danger("[user] stuffs [C] into the gibber!")) + C.forceMove(src) + set_occupant(C) + update_appearance() /obj/machinery/gibber/wrench_act(mob/living/user, obj/item/tool) . = ..() diff --git a/code/modules/food_and_drinks/kitchen_machinery/processor.dm b/code/modules/food_and_drinks/kitchen_machinery/processor.dm index 52c275e7de17..871fa54ed9b9 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/processor.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/processor.dm @@ -163,7 +163,7 @@ qdel(grab) pushed_mob.forceMove(src) LAZYADD(processor_contents, pushed_mob) - return + return TRUE /obj/machinery/processor/verb/eject() set category = "Object" diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index 234988b19d1b..2773b3ea81b9 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -18,8 +18,6 @@ GLOBAL_LIST_EMPTY(all_grabstates) // Whether or not the grabbed person can move out of the grab var/stop_move = FALSE - /// Whether or not the grabbed person is forced to be standing - var/force_stand = FALSE // Whether the person being grabbed is facing forwards or backwards. var/reverse_facing = FALSE /// Whether this grab state is strong enough to, as a changeling, absorb the person you're grabbing. @@ -40,12 +38,12 @@ GLOBAL_LIST_EMPTY(all_grabstates) var/downgrade_on_action = FALSE /// If the grab needs to be downgraded when the grabber moves. var/downgrade_on_move = FALSE - /// If the grab is strong enough to be able to force someone to do something harmful to them. - var/force_danger = FALSE + /// If the grab is strong enough to be able to force someone to do something harmful to them, like slam their head into glass. + var/enable_violent_interactions = FALSE /// If the grab acts like cuffs and prevents action from the victim. var/restrains = FALSE - var/grab_slowdown = 7 + var/grab_slowdown = 0 var/shift = 0 @@ -245,8 +243,8 @@ GLOBAL_LIST_EMPTY(all_grabstates) /// Add effects that apply based on damage_stage here /datum/grab/proc/update_stage_effects(obj/item/hand_item/grab/G, datum/grab/old_grab, dropping_grab) - var/old_damage_stage = old_grab?.damage_stage || 0 - var/new_stage = dropping_grab ? 0 : damage_stage + var/old_damage_stage = old_grab?.damage_stage || GRAB_PASSIVE + var/new_stage = dropping_grab ? GRAB_PASSIVE : damage_stage switch(new_stage) // Current state. if(GRAB_PASSIVE) @@ -254,6 +252,8 @@ GLOBAL_LIST_EMPTY(all_grabstates) REMOVE_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, REF(G)) if(old_damage_stage >= GRAB_NECK) // Previous state was a a neck-grab or higher. REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, REF(G)) + if(old_damage_stage >= GRAB_AGGRESSIVE) + REMOVE_TRAIT(G.affecting, TRAIT_AGGRESSIVE_GRAB, REF(G)) if(GRAB_AGGRESSIVE) if(old_damage_stage >= GRAB_NECK) // Grab got downgraded. @@ -261,15 +261,25 @@ GLOBAL_LIST_EMPTY(all_grabstates) else // Grab got upgraded from a passive one. ADD_TRAIT(G.affecting, TRAIT_IMMOBILIZED, REF(G)) ADD_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, REF(G)) + ADD_TRAIT(G.affecting, TRAIT_AGGRESSIVE_GRAB, REF(G)) if(GRAB_NECK, GRAB_KILL) + if(old_damage_stage < GRAB_AGGRESSIVE) + ADD_TRAIT(G.affecting, TRAIT_AGGRESSIVE_GRAB, REF(G)) if(old_damage_stage <= GRAB_AGGRESSIVE) ADD_TRAIT(G.affecting, TRAIT_FLOORED, REF(G)) + ADD_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, REF(G)) + ADD_TRAIT(G.affecting, TRAIT_IMMOBILIZED, REF(G)) + /// Apply effects to people here. Remove them in remove_grab_effects() /datum/grab/proc/apply_grab_effects(obj/item/hand_item/grab/G) + SHOULD_CALL_PARENT(TRUE) + if(G.loc != G.assailant.loc && same_tile) + G.affecting.move_from_pull(G.assailant, get_turf(G.assailant)) /datum/grab/proc/remove_grab_effects(obj/item/hand_item/grab/G) + SHOULD_CALL_PARENT(TRUE) /// Handles special targeting like eyes and mouth being covered. /// CLEAR OUT ANY EFFECTS USING remove_bodyzone_effects() @@ -363,11 +373,13 @@ GLOBAL_LIST_EMPTY(all_grabstates) to_chat(G.affecting, span_warning("You try to break free but feel that unless something changes, you'll never escape!")) return - var/break_chance = break_chance_table[clamp(break_strength, 1, length(break_chance_table))] if (assailant.incapacitated()) let_go(G) + stack_trace("Someone resisted a grab while the assailant was incapacitated. This shouldn't ever happen.") + return + var/break_chance = break_chance_table[clamp(break_strength, 1, length(break_chance_table))] if(prob(break_chance)) if(can_downgrade_on_resist && !prob((break_chance+100)/2)) affecting.visible_message(span_danger("[affecting] has loosened [assailant]'s grip!")) diff --git a/code/modules/grab/grab_helpers.dm b/code/modules/grab/grab_helpers.dm index 103ffae62a13..fa4c7413fc74 100644 --- a/code/modules/grab/grab_helpers.dm +++ b/code/modules/grab/grab_helpers.dm @@ -44,12 +44,6 @@ for(var/obj/item/hand_item/grab/G in get_active_grabs()) . |= G.affecting -/// Checks to see if we have a grab with atleast the given damage_stage -/atom/movable/proc/check_grab_severities(stage) - for(var/obj/item/hand_item/grab/G in grabbed_by) - if(G.current_grab.damage_stage >= stage) - return G - /// Frees src from all grabs. /atom/movable/proc/free_from_all_grabs() QDEL_LIST(grabbed_by) diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index eeb21439dfef..f2f5ba8ee122 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -106,9 +106,6 @@ /// Called by grab objects when a grab has been released /mob/living/proc/after_grab_release(atom/movable/old_target) animate_interact(old_target, INTERACT_UNPULL) - if(ismob(pulling)) - var/mob/living/L = pulling - L.reset_pull_offsets() update_pull_hud_icon() /mob/living/proc/handle_grabs_during_movement(turf/old_loc, direction) @@ -125,7 +122,7 @@ continue var/pull_dir = get_dir(pulling, src) - var/target_turf = old_loc + var/target_turf = G.current_grab.same_tile ? loc : old_loc // Pulling things down/up stairs. zMove() has flags for check_pulling and stop_pulling calls. // You may wonder why we're not just forcemoving the pulling movable and regrabbing it. @@ -151,7 +148,7 @@ var/list/my_grabs = get_active_grabs() for(var/obj/item/hand_item/grab/G in my_grabs) - if(G.current_grab.reverse_facing) + if(G.current_grab.reverse_facing || HAS_TRAIT(G.affecting, TRAIT_KEEP_DIRECTION_WHILE_PULLING)) setDir(global.reverse_dir[direction]) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 46790ee2aa4c..71765001efcf 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -166,6 +166,16 @@ if(C.dna.species.grab_sound) sound = C.dna.species.grab_sound + if(isliving(affecting)) + var/mob/living/affecting_mob = affecting + for(var/datum/disease/D as anything in assailant.diseases) + if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + affecting_mob.ContactContractDisease(D) + + for(var/datum/disease/D as anything in affecting_mob.diseases) + if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + assailant.ContactContractDisease(D) + playsound(affecting.loc, sound, 50, 1, -1) update_appearance() current_grab.update_stage_effects(src, null) @@ -243,7 +253,7 @@ update_appearance() /// Used to prevent repeated effect application or early effect removal -/obj/item/hand_item/grab/proc/is_grab_unique(datum/grab/grab_datum) +/obj/item/hand_item/grab/proc/is_grab_unique (datum/grab/grab_datum) var/count = 0 for(var/obj/item/hand_item/grab/other as anything in affecting.grabbed_by) if(other.current_grab == grab_datum) diff --git a/code/modules/grab/grabs/grab_aggressive.dm b/code/modules/grab/grabs/grab_aggressive.dm index 508442ef2ab2..04fc25ab144a 100644 --- a/code/modules/grab/grabs/grab_aggressive.dm +++ b/code/modules/grab/grabs/grab_aggressive.dm @@ -1,21 +1,24 @@ /datum/grab/normal/aggressive upgrab = /datum/grab/normal/neck downgrab = /datum/grab/normal/passive + + grab_slowdown = 0.7 shift = 12 - stop_move = 1 - reverse_facing = 0 - shield_assailant = 0 + stop_move = TRUE + reverse_facing = FALSE + shield_assailant = FALSE point_blank_mult = 1.5 - damage_stage = 1 - same_tile = 0 - can_throw = 1 - force_danger = 1 + damage_stage = GRAB_AGGRESSIVE + same_tile = FALSE + can_throw = TRUE + enable_violent_interactions = TRUE breakability = 3 icon_state = "2" break_chance_table = list(5, 20, 40, 80, 100) /datum/grab/normal/aggressive/apply_grab_effects(obj/item/hand_item/grab/G) + . = ..() if(!isliving(G.affecting)) return @@ -24,12 +27,11 @@ if(L.body_position == LYING_DOWN) ADD_TRAIT(L, TRAIT_FLOORED, AGGRESSIVE_GRAB) - ADD_TRAIT(L, TRAIT_HANDS_BLOCKED, AGGRESSIVE_GRAB) /datum/grab/normal/aggressive/remove_grab_effects(obj/item/hand_item/grab/G) + . = ..() UnregisterSignal(G.affecting, COMSIG_LIVING_SET_BODY_POSITION) REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, AGGRESSIVE_GRAB) - REMOVE_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, AGGRESSIVE_GRAB) /datum/grab/normal/aggressive/proc/target_bodyposition_change(mob/living/source) if(source.body_position == LYING_DOWN) diff --git a/code/modules/grab/grabs/grab_neck.dm b/code/modules/grab/grabs/grab_neck.dm index 446d252dde4b..9731cffefbf6 100644 --- a/code/modules/grab/grabs/grab_neck.dm +++ b/code/modules/grab/grabs/grab_neck.dm @@ -1,30 +1,44 @@ /datum/grab/normal/neck - //upgrab = /datum/grab/normal/kill + upgrab = /datum/grab/normal/kill downgrab = /datum/grab/normal/aggressive - drop_headbutt = 0 + grab_slowdown = 4 + drop_headbutt = FALSE shift = -10 - stop_move = 1 - reverse_facing = 1 - shield_assailant = 1 + stop_move = TRUE + reverse_facing = TRUE + shield_assailant = TRUE point_blank_mult = 2 damage_stage = GRAB_NECK - same_tile = 1 - can_throw = 1 - force_danger = 1 - restrains = 1 + same_tile = TRUE + can_throw = TRUE + enable_violent_interactions = TRUE + restrains = TRUE icon_state = "3" break_chance_table = list(3, 18, 45, 100) /datum/grab/normal/neck/apply_grab_effects(obj/item/hand_item/grab/G) + . = ..() if(!isliving(G.affecting)) return var/mob/living/L = G.affecting - ADD_TRAIT(L, TRAIT_FORCED_STANDING, NECK_GRAB) - ADD_TRAIT(L, TRAIT_HANDS_BLOCKED, NECK_GRAB) + ADD_TRAIT(L, TRAIT_FLOORED, NECK_GRAB) /datum/grab/normal/neck/remove_grab_effects(obj/item/hand_item/grab/G) - REMOVE_TRAIT(G.affecting, TRAIT_FORCED_STANDING, NECK_GRAB) - REMOVE_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, NECK_GRAB) + . = ..() + ADD_TRAIT(G.affecting, TRAIT_FLOORED, NECK_GRAB) + +/datum/grab/normal/neck/enter_as_up(obj/item/hand_item/grab/G) + . = ..() + G.assailant.visible_message( + span_danger("[G.assailant] places their arm around [G.affecting]'s neck!"), + blind_message = span_hear("You hear aggressive shuffling.") + ) + +/datum/grab/normal/neck/enter_as_down(obj/item/hand_item/grab/G) + . = ..() + G.assailant.visible_message( + span_danger("[G.assailant] stops strangling [G.affecting]."), + ) diff --git a/code/modules/grab/grabs/grab_strangle.dm b/code/modules/grab/grabs/grab_strangle.dm new file mode 100644 index 000000000000..5337d2adc554 --- /dev/null +++ b/code/modules/grab/grabs/grab_strangle.dm @@ -0,0 +1,36 @@ +/datum/grab/normal/kill + downgrab = /datum/grab/normal/neck + + grab_slowdown = 1.4 + + shift = 0 + stop_move = TRUE + reverse_facing = TRUE + shield_assailant = FALSE + point_blank_mult = 2 + damage_stage = GRAB_KILL + same_tile = TRUE + enable_violent_interactions = TRUE + restrains = TRUE + downgrade_on_action = TRUE + downgrade_on_move = TRUE + icon_state = "3" + break_chance_table = list(5, 20, 40, 80, 100) + +/datum/grab/normal/kill/apply_grab_effects(obj/item/hand_item/grab/G) + . = ..() + if(!isliving(G.affecting)) + return + + ADD_TRAIT(G.affecting, TRAIT_KILL_GRAB, REF(G)) + +/datum/grab/normal/kill/remove_grab_effects(obj/item/hand_item/grab/G) + . = ..() + REMOVE_TRAIT(G.affecting, TRAIT_KILL_GRAB, REF(G)) + +/datum/grab/normal/kill/enter_as_up(obj/item/hand_item/grab/G) + . = ..() + G.assailant.visible_message( + span_danger("[G.assailant] begins to strangle [G.affecting]!"), + blind_message = span_hear("You hear aggressive shuffling.") + ) diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm index 401884915f04..b747dc4a6df8 100644 --- a/code/modules/holodeck/items.dm +++ b/code/modules/holodeck/items.dm @@ -100,11 +100,12 @@ var/mob/living/L = victim if(grab.current_grab.damage_stage < 1) to_chat(user, span_warning("You need a better grip to do that!")) - return + return TRUE L.forceMove(loc) L.Paralyze(100) visible_message(span_danger("[user] dunks [L] into \the [src]!")) user.release_grab(L) + return TRUE /obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) if (isitem(AM) && !istype(AM,/obj/projectile)) diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 60ba0d7ad520..5c0e3cf571c8 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -83,7 +83,7 @@ losebreath = max(2, losebreath + 1) else if(!getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - if((check_grab_severities(GRAB_NECK)) || (lungs?.organ_flags & ORGAN_DEAD)) + if(HAS_TRAIT(src, TRAIT_KILL_GRAB) || (lungs?.organ_flags & ORGAN_DEAD)) losebreath++ //You can't breath at all when in critical or when being choked, so you're going to miss a breath // Recover from breath loss diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 39885b4c24ba..86a08b4e7fb6 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -283,98 +283,6 @@ if(current_dir) AM.setDir(current_dir) now_pushing = FALSE -#warn old start_pulling stuff -/* -/mob/living/start_pulling(atom/movable/AM, state, force = pull_force, supress_message = FALSE) - if(!AM || !src) - return FALSE - if(!(AM.can_be_pulled(src, state, force))) - return FALSE - if(throwing || !(mobility_flags & MOBILITY_PULL)) - return FALSE - if(SEND_SIGNAL(src, COMSIG_LIVING_TRY_PULL, AM, force) & COMSIG_LIVING_CANCEL_PULL) - return FALSE - - AM.add_fingerprint(src) - - // If we're pulling something then drop what we're currently pulling and pull this instead. - if(pulling) - // Are we trying to pull something we are already pulling? Then just stop here, no need to continue. - if(AM == pulling) - return - stop_pulling() - - changeNext_move(CLICK_CD_GRABBING) - animate_interact(AM, INTERACT_PULL) - - if(AM.pulledby) - if(!supress_message) - AM.visible_message(span_danger("[src] pulls [AM] from [AM.pulledby]'s grip."), \ - span_danger("[src] pulls you from [AM.pulledby]'s grip."), null, null, src) - to_chat(src, span_notice("You pull [AM] from [AM.pulledby]'s grip!")) - log_combat(AM, AM.pulledby, "pulled from", src) - AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. - - pulling = AM - AM.set_pulledby(src) - - SEND_SIGNAL(src, COMSIG_LIVING_START_PULL, AM, state, force) - - if(!supress_message) - var/sound_to_play = 'sound/weapons/thudswoosh.ogg' - if(ishuman(src)) - var/mob/living/carbon/human/H = src - if(H.dna.species.grab_sound) - sound_to_play = H.dna.species.grab_sound - if(HAS_TRAIT(H, TRAIT_STRONG_GRABBER)) - sound_to_play = null - playsound(src.loc, sound_to_play, 50, TRUE, -1) - update_pull_hud_icon() - - if(ismob(AM)) - var/mob/M = AM - - log_combat(src, M, "grabbed", addition="passive grab") - if(!supress_message && !(iscarbon(AM) && HAS_TRAIT(src, TRAIT_STRONG_GRABBER))) - if(ishuman(M)) - var/mob/living/carbon/human/grabbed_human = M - var/grabbed_by_hands = (zone_selected == "l_arm" || zone_selected == "r_arm") && grabbed_human.usable_hands > 0 - M.visible_message(span_warning("[src] grabs [M] [grabbed_by_hands ? "by their hands":"passively"]!"), \ - span_warning("[src] grabs you [grabbed_by_hands ? "by your hands":"passively"]!"), null, null, src) - to_chat(src, span_notice("You grab [M] [grabbed_by_hands ? "by their hands":"passively"]!")) - else - M.visible_message(span_warning("[src] grabs [M] passively!"), \ - span_warning("[src] grabs you passively!"), null, null, src) - to_chat(src, span_notice("You grab [M] passively!")) - - if(!iscarbon(src)) - M.LAssailant = null - else - M.LAssailant = WEAKREF(usr) - if(isliving(M)) - var/mob/living/L = M - - SEND_SIGNAL(M, COMSIG_LIVING_GET_PULLED, src) - //Share diseases that are spread by touch - for(var/thing in diseases) - var/datum/disease/D = thing - if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) - L.ContactContractDisease(D) - - for(var/thing in L.diseases) - var/datum/disease/D = thing - if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) - ContactContractDisease(D) - - if(iscarbon(L)) - var/mob/living/carbon/C = L - if(HAS_TRAIT(src, TRAIT_STRONG_GRABBER)) - C.grippedby(src) - - update_pull_movespeed() - - set_pull_offsets(M, state) -*/ /mob/living/proc/reset_pull_offsets(override) if(!override && buckled) @@ -859,8 +767,7 @@ var/old_direction = dir var/turf/T = loc - if(pulling) - update_pull_movespeed() + update_pull_movespeed() . = ..() @@ -1020,7 +927,8 @@ if(moving_resist && client) //we resisted by trying to move client.move_delay = world.time + 4 SECONDS - visible_message(span_danger("\The [src] struggles to break free!")) + if(!moving_resist) + visible_message(span_danger("\The [src] struggles to break free!")) for(var/obj/item/hand_item/grab/G as anything in grabbed_by) log_combat(src, G.assailant, "resisted grab") diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index b0387aad0e10..d9b29efeb9d3 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -147,8 +147,6 @@ var/list/diseases /// list of all diseases in a mob var/list/disease_resistances - var/slowed_by_drag = TRUE ///Whether the mob is slowed down when dragging another prone mob - /// List of changes to body temperature, used by desease symtoms like fever var/list/body_temp_changes = list() diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm index a4b7baba3b20..4f78cf9da99f 100644 --- a/code/modules/mob/living/living_movement.dm +++ b/code/modules/mob/living/living_movement.dm @@ -97,26 +97,24 @@ current_turf_slowdown = 0 /mob/living/proc/update_pull_movespeed() - SEND_SIGNAL(src, COMSIG_LIVING_UPDATING_PULL_MOVESPEED) -#warn drag movespeed -/* - if(pulling) + var/list/obj/item/hand_item/grab/grabs = get_active_grabs() + if(!length(grabs)) + remove_movespeed_modifier(/datum/movespeed_modifier/grabbing) + return + + var/slowdown_total = 0 + for(var/obj/item/hand_item/grab/G as anything in grabs) + var/atom/movable/pulling = G.affecting if(isliving(pulling)) var/mob/living/L = pulling - if(!slowed_by_drag || L.body_position == STANDING_UP || L.buckled || grab_state >= GRAB_AGGRESSIVE) - remove_movespeed_modifier(/datum/movespeed_modifier/bulky_drag) - return - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/bulky_drag, multiplicative_slowdown = PULL_PRONE_SLOWDOWN) - return + if(G.current_grab.grab_slowdown || L.body_position == LYING_DOWN) + slowdown_total += max(G.current_grab.grab_slowdown, PULL_PRONE_SLOWDOWN) + if(isobj(pulling)) var/obj/structure/S = pulling - if(!slowed_by_drag || !S.drag_slowdown) - remove_movespeed_modifier(/datum/movespeed_modifier/bulky_drag) - return - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/bulky_drag, multiplicative_slowdown = S.drag_slowdown) - return - remove_movespeed_modifier(/datum/movespeed_modifier/bulky_drag) -*/ + slowdown_total += S.drag_slowdown + + add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/grabbing, multiplicative_slowdown = slowdown_total) /mob/living/can_z_move(direction, turf/start, z_move_flags, mob/living/rider) // Check physical climbing ability diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index 0747ca686f1e..da17fbd2fb18 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -67,7 +67,6 @@ if(poison_per_bite) AddElement(/datum/element/venomous, poison_type, poison_per_bite) - AddElement(/datum/element/nerfed_pulling, GLOB.typecache_general_bad_things_to_easily_move) AddElement(/datum/element/prevent_attacking_of_types, GLOB.typecache_general_bad_hostile_attack_targets, "this tastes awful!") /mob/living/simple_animal/hostile/giant_spider/Login() diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm index 60fa7abe30a0..25a930ddc5a9 100644 --- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm +++ b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm @@ -715,7 +715,6 @@ return FALSE var/mob/living/simple_animal/hostile/carp/newcarp = new /mob/living/simple_animal/hostile/carp(loc) - newcarp.AddElement(/datum/element/nerfed_pulling, GLOB.typecache_general_bad_things_to_easily_move) newcarp.AddElement(/datum/element/prevent_attacking_of_types, GLOB.typecache_general_bad_hostile_attack_targets, "this tastes awful!") if(!is_listed) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 1f87ea21bb1f..4cbf4746e625 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -309,7 +309,7 @@ /mob/living/simple_animal/proc/environment_air_is_safe() . = TRUE - if(check_grab_severities(GRAB_KILL) && atmos_requirements["min_oxy"]) + if(HAS_TRAIT(src, TRAIT_KILL_GRAB) && atmos_requirements["min_oxy"]) . = FALSE //getting choked if(isturf(loc) && isopenturf(loc)) diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 356f5b903172..d97407867a9a 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -162,10 +162,6 @@ // as a result of player input and not because they were pulled or any other magic. SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_MOVED) - var/atom/movable/P = mob.pulling - if(P && !ismob(P) && P.density && !HAS_TRAIT(P, TRAIT_KEEP_DIRECTION_WHILE_PULLING)) - mob.setDir(turn(mob.dir, 180)) - /** * Checks to see if you're being grabbed and if so attempts to break it * @@ -344,8 +340,16 @@ continue if(rebound.anchored) return rebound - if(pulling == rebound) - continue + if(isliving(rebound)) + var/mob/living/L = rebound + var/_continue = FALSE + if(LAZYLEN(L.grabbed_by)) + for(var/obj/item/hand_item/grab/G in L.grabbed_by) + if(G.assailant == src) + _continue = TRUE + break + if(_continue) + continue return rebound /mob/has_gravity() diff --git a/code/modules/mod/mod_ai.dm b/code/modules/mod/mod_ai.dm index 37caddb1c4d4..cd6052039021 100644 --- a/code/modules/mod/mod_ai.dm +++ b/code/modules/mod/mod_ai.dm @@ -74,7 +74,7 @@ #define AI_FALL_TIME 1 SECONDS /obj/item/mod/control/relaymove(mob/user, direction) - if((!active && wearer) || get_charge() < CHARGE_PER_STEP || user != ai || !COOLDOWN_FINISHED(src, cooldown_mod_move) || (wearer.check_grab_severities(GRAB_AGGRESSIVE))) + if((!active && wearer) || get_charge() < CHARGE_PER_STEP || user != ai || !COOLDOWN_FINISHED(src, cooldown_mod_move) || HAS_TRAIT(wearer, TRAIT_AGGRESSIVE_GRAB)) return FALSE var/timemodifier = MOVE_DELAY * (ISDIAGONALDIR(direction) ? 2 : 1) * (wearer ? WEARER_DELAY : LONE_DELAY) if(wearer && !wearer.Process_Spacemove(direction)) diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm index 38fe0bfc3833..50883711c836 100644 --- a/code/modules/movespeed/modifiers/mobs.dm +++ b/code/modules/movespeed/modifiers/mobs.dm @@ -83,7 +83,7 @@ blacklisted_movetypes = (FLYING|FLOATING) variable = TRUE -/datum/movespeed_modifier/bulky_drag +/datum/movespeed_modifier/grabbing variable = TRUE /datum/movespeed_modifier/cold diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index c23559211ef7..36b0331f5862 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -28,13 +28,6 @@ . = list(src) if(buckled_mobs) . |= buckled_mobs - if(!(z_move_flags & ZMOVE_INCLUDE_PULLED)) - return - for(var/mob/living/buckled as anything in buckled_mobs) - if(buckled.pulling) - . |= buckled.pulling - if(pulling) - . |= pulling /** * Checks if the destination turf is elegible for z movement from the start turf to a given direction and returns it if so. diff --git a/code/modules/religion/religion_structures.dm b/code/modules/religion/religion_structures.dm index 58c6e8633e5f..c9c41175cb3b 100644 --- a/code/modules/religion/religion_structures.dm +++ b/code/modules/religion/religion_structures.dm @@ -32,17 +32,19 @@ /obj/structure/altar_of_gods/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) . = ..() - if(!isliving(user.pulling)) + if(!isliving(victim)) return + var/mob/living/pushed_mob = victim if(pushed_mob.buckled) to_chat(user, span_warning("[pushed_mob] is buckled to [pushed_mob.buckled]!")) - return + return TRUE to_chat(user, span_notice("You try to coax [pushed_mob] onto [src]...")) if(!do_after(user,(5 SECONDS),target = pushed_mob)) - return + return TRUE pushed_mob.forceMove(loc) + return TRUE /obj/structure/altar_of_gods/examine_more(mob/user) if(!isobserver(user)) diff --git a/code/modules/tables/tables_racks.dm b/code/modules/tables/tables_racks.dm index f954561c467a..05b6b8c682ad 100644 --- a/code/modules/tables/tables_racks.dm +++ b/code/modules/tables/tables_racks.dm @@ -236,6 +236,7 @@ /obj/structure/table/attack_grab(mob/living/user, obj/item/hand_item/grab/grab, list/params) try_place_pulled_onto_table(user, grab.affecting, grab) + return TRUE /obj/structure/table/proc/try_place_pulled_onto_table(mob/living/user, atom/movable/target, obj/item/hand_item/grab/grab) if(!Adjacent(user)) diff --git a/daedalus.dme b/daedalus.dme index 1dd3e69bf042..24bc3306174a 100644 --- a/daedalus.dme +++ b/daedalus.dme @@ -1010,7 +1010,6 @@ #include "code\datums\elements\light_eater.dm" #include "code\datums\elements\movement_turf_changer.dm" #include "code\datums\elements\movetype_handler.dm" -#include "code\datums\elements\nerfed_pulling.dm" #include "code\datums\elements\obj_regen.dm" #include "code\datums\elements\openspace_item_click_handler.dm" #include "code\datums\elements\pet_bonus.dm" @@ -2993,6 +2992,7 @@ #include "code\modules\grab\grabs\grab_normal.dm" #include "code\modules\grab\grabs\grab_passive.dm" #include "code\modules\grab\grabs\grab_simple.dm" +#include "code\modules\grab\grabs\grab_strangle.dm" #include "code\modules\holiday\easter.dm" #include "code\modules\holiday\foreign_calendar.dm" #include "code\modules\holiday\holidays.dm" From 7cb156364ea60382612bf295642b3a22fc72a17f Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Fri, 29 Sep 2023 19:30:33 -0400 Subject: [PATCH 15/37] more grab things --- .../signals_atom/signals_atom_mouse.dm | 2 +- code/_onclick/ai.dm | 2 +- code/_onclick/click.dm | 18 ++-- code/_onclick/cyborg.dm | 2 +- code/_onclick/overmind.dm | 2 +- .../test_equipment/test_equipment_wired.dm | 2 +- .../objects/items/devices/pressureplates.dm | 2 +- code/game/objects/items/his_grace.dm | 2 +- code/game/objects/items/storage/bags.dm | 2 +- code/game/objects/structures.dm | 30 ++++++ code/game/objects/structures/railings.dm | 24 +++++ code/game/objects/structures/window.dm | 33 +++++++ code/game/turfs/turf.dm | 9 ++ .../components/binary_devices/passive_gate.dm | 2 +- .../binary_devices/pressure_valve.dm | 2 +- .../components/binary_devices/pump.dm | 2 +- .../binary_devices/temperature_gate.dm | 2 +- .../binary_devices/temperature_pump.dm | 2 +- .../components/binary_devices/volume_pump.dm | 2 +- .../components/trinary_devices/filter.dm | 2 +- .../components/trinary_devices/mixer.dm | 2 +- .../components/unary_devices/cryo.dm | 2 +- .../unary_devices/outlet_injector.dm | 2 +- code/modules/grab/grab_datum.dm | 20 +--- code/modules/grab/grab_living.dm | 1 + code/modules/grab/grab_object.dm | 91 +++++++++++-------- code/modules/grab/grabs/grab_aggressive.dm | 1 - code/modules/grab/grabs/grab_neck.dm | 3 +- code/modules/grab/grabs/grab_normal.dm | 19 ++-- code/modules/grab/grabs/grab_passive.dm | 1 - code/modules/grab/grabs/grab_simple.dm | 1 - code/modules/grab/grabs/grab_strangle.dm | 1 - code/modules/hydroponics/hydroponics.dm | 4 +- .../mob/living/carbon/carbon_defense.dm | 2 +- code/modules/mob/living/carbon/human/human.dm | 2 + code/modules/mob/living/living_defense.dm | 9 +- .../simple_animal/hostile/jungle/leaper.dm | 2 +- .../computers/item/tablet.dm | 2 +- code/modules/projectiles/gun.dm | 53 +++++------ code/modules/recycling/sortingmachinery.dm | 2 +- .../research/xenobiology/xenobio_camera.dm | 6 +- code/modules/surgery/bodyparts/_bodyparts.dm | 3 + code/modules/surgery/bodyparts/pain.dm | 5 +- .../modules/clothing/glasses/glasses.dm | 2 +- 44 files changed, 247 insertions(+), 133 deletions(-) diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm index 2f8ebc836650..91ebec57f7f9 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm @@ -9,7 +9,7 @@ ///from base of atom/ShiftClick(): (/mob) #define COMSIG_CLICK_SHIFT "shift_click" #define COMPONENT_ALLOW_EXAMINATE (1<<0) //Allows the user to examinate regardless of client.eye. -///from base of atom/CtrlClickOn(): (/mob) +///from base of atom/CtrlClickOn(): (/mob, list/params) #define COMSIG_CLICK_CTRL "ctrl_click" ///from base of atom/AltClick(): (/mob) #define COMSIG_CLICK_ALT "alt_click" diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index 94399b1b8c62..e587a08ac1ce 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -123,7 +123,7 @@ A.AICtrlShiftClick(src) /mob/living/silicon/ai/ShiftClickOn(atom/A) A.AIShiftClick(src) -/mob/living/silicon/ai/CtrlClickOn(atom/A) +/mob/living/silicon/ai/CtrlClickOn(atom/A, list/params) A.AICtrlClick(src) /mob/living/silicon/ai/AltClickOn(atom/A) A.AIAltClick(src) diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index f80f121fbab6..815462eef778 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -99,7 +99,7 @@ AltClickOn(A) return if(LAZYACCESS(modifiers, CTRL_CLICK)) - CtrlClickOn(A) + CtrlClickOn(A, modifiers) return //PARIAH EDIT ADDITION @@ -351,11 +351,11 @@ * Ctrl click * For most objects, pull */ -/mob/proc/CtrlClickOn(atom/A) - A.CtrlClick(src) +/mob/proc/CtrlClickOn(atom/A, list/params) + A.CtrlClick(src, params) return -/atom/proc/CtrlClick(mob/user) +/atom/proc/CtrlClick(mob/user, list/params) SEND_SIGNAL(src, COMSIG_CLICK_CTRL, user) SEND_SIGNAL(user, COMSIG_MOB_CTRL_CLICKED, src) var/mob/living/ML = user @@ -364,7 +364,7 @@ if(!can_interact(user)) return FALSE -/mob/living/CtrlClick(mob/user) +/mob/living/CtrlClick(mob/user, list/params) if(!isliving(user) || !user.CanReach(src) || user.incapacitated()) return ..() @@ -379,7 +379,7 @@ return ..() -/mob/living/carbon/human/CtrlClick(mob/user) +/mob/living/carbon/human/CtrlClick(mob/user, list/params) if(!ishuman(user) || !user.CanReach(src) || user.incapacitated()) return ..() @@ -388,6 +388,12 @@ return FALSE var/mob/living/carbon/human/human_user = user + // If they're wielding a grab item, do the normal click chain. + var/obj/item/hand_item/grab/G = user.get_active_held_item() + if(isgrab(G)) + G.current_grab.hit_with_grab(G, src, params) + return TRUE + if(human_user.dna.species.grab(human_user, src, human_user.mind.martial_art)) human_user.changeNext_move(CLICK_CD_MELEE) human_user.animate_interact(src, INTERACT_GRAB) diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm index 7f12d938471c..774072aace91 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -99,7 +99,7 @@ A.BorgCtrlShiftClick(src) /mob/living/silicon/robot/ShiftClickOn(atom/A) A.BorgShiftClick(src) -/mob/living/silicon/robot/CtrlClickOn(atom/A) +/mob/living/silicon/robot/CtrlClickOn(atom/A, list/params) A.BorgCtrlClick(src) /mob/living/silicon/robot/AltClickOn(atom/A) A.BorgAltClick(src) diff --git a/code/_onclick/overmind.dm b/code/_onclick/overmind.dm index d6b8994f82f0..555ba4b87d76 100644 --- a/code/_onclick/overmind.dm +++ b/code/_onclick/overmind.dm @@ -25,7 +25,7 @@ if(T) rally_spores(T) -/mob/camera/blob/CtrlClickOn(atom/A) //Create a shield +/mob/camera/blob/CtrlClickOn(atom/A, list/params) //Create a shield var/turf/T = get_turf(A) if(T) create_shield(T) diff --git a/code/game/machinery/datanet/test_equipment/test_equipment_wired.dm b/code/game/machinery/datanet/test_equipment/test_equipment_wired.dm index 48d4f7d4e348..137cb13d9c38 100644 --- a/code/game/machinery/datanet/test_equipment/test_equipment_wired.dm +++ b/code/game/machinery/datanet/test_equipment/test_equipment_wired.dm @@ -8,7 +8,7 @@ // This equipment is *incredibly* basic, and there's probably a subtype of machinery that you should use instead. // I should probably remove this before pushing, but if not, here it is! -/obj/machinery/test_equipment/wired/CtrlClick(mob/user) +/obj/machinery/test_equipment/wired/CtrlClick(mob/user, list/params) . = ..() reconnect_dataterm() diff --git a/code/game/objects/items/devices/pressureplates.dm b/code/game/objects/items/devices/pressureplates.dm index c46b42b0c58f..b40e82716182 100644 --- a/code/game/objects/items/devices/pressureplates.dm +++ b/code/game/objects/items/devices/pressureplates.dm @@ -74,7 +74,7 @@ sigdev = null return ..() -/obj/item/pressure_plate/CtrlClick(mob/user) +/obj/item/pressure_plate/CtrlClick(mob/user, list/params) if(protected) to_chat(user, span_warning("You can't quite seem to turn this pressure plate off...")) return diff --git a/code/game/objects/items/his_grace.dm b/code/game/objects/items/his_grace.dm index feccb047f5d6..e2651dad4373 100644 --- a/code/game/objects/items/his_grace.dm +++ b/code/game/objects/items/his_grace.dm @@ -64,7 +64,7 @@ else ..() -/obj/item/his_grace/CtrlClick(mob/user) //you can't pull his grace +/obj/item/his_grace/CtrlClick(mob/user, list/params) //you can't pull his grace return /obj/item/his_grace/examine(mob/user) diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm index ac13babf7b6f..4b2a2bdbb5dc 100644 --- a/code/game/objects/items/storage/bags.dm +++ b/code/game/objects/items/storage/bags.dm @@ -234,7 +234,7 @@ . = ..() . += span_notice("Ctrl-click to activate seed extraction.") -/obj/item/storage/bag/plants/portaseeder/CtrlClick(mob/user) +/obj/item/storage/bag/plants/portaseeder/CtrlClick(mob/user, list/params) if(user.incapacitated()) return for(var/obj/item/plant in contents) diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 5732aac3ed14..88846bcc185c 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -96,3 +96,33 @@ L.Paralyze(10 SECONDS) visible_message(span_warning("[src] slams into [highest] from above!")) + +/obj/structure/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!user.combat_mode) + return + if (!grab.current_grab.enable_violent_interactions) + to_chat(user, span_warning("You need a better grip to do that!")) + return TRUE + + var/mob/living/affecting_mob = grab.get_affecting_mob() + if(!istype(affecting_mob)) + to_chat(user, span_warning("You need to be grabbing a living creature to do that!")) + return TRUE + + // Slam their face against the table. + var/blocked = affecting_mob.run_armor_check(BODY_ZONE_HEAD, MELEE) + if (prob(30 * ((100-blocked)/100))) + affecting_mob.Knockdown(10 SECONDS) + + affecting_mob.apply_damage(8, BRUTE, BODY_ZONE_HEAD, blocked) + visible_message(span_danger("[user] slams [affecting_mob]'s face against \the [src]!")) + playsound(loc, 'sound/items/trayhit1.ogg', 50, 1) + + take_damage(rand(1,5), BRUTE) + for(var/obj/item/shard/S in loc) + if(prob(50)) + affecting_mob.visible_message(span_danger("\The [S] slices into [affecting_mob]'s face!"), span_danger("\The [S] slices into your face!")) + S.melee_attack_chain(user, victim, params) + qdel(grab) + return TRUE diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm index 4cd10b042c51..04df2e167525 100644 --- a/code/game/objects/structures/railings.dm +++ b/code/game/objects/structures/railings.dm @@ -117,3 +117,27 @@ /obj/structure/railing/proc/check_anchored(checked_anchored) if(anchored == checked_anchored) return TRUE + +/obj/structure/railing/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + var/mob/living/L = grab.get_affecting_mob() + if(!grab.current_grab.enable_violent_interactions || !isliving(L)) + return ..() + + if(!Adjacent(L)) + user.move_grabbed_atoms_towards(get_turf(src)) + return ..() + + if(user.combat_mode) + visible_message(span_danger("[user] slams [L]'s face against \the [src]!")) + playsound(loc, 'sound/effects/grillehit.ogg', 50, 1) + var/blocked = L.run_armor_check(BODY_ZONE_HEAD, MELEE) + if (prob(30 * ((100 - blocked)/100))) + L.Knockdown(10 SECONDS) + L.apply_damage(8, BRUTE, BODY_ZONE_HEAD) + else + if (get_turf(L) == get_turf(src)) + L.forceMove(get_step(src, src.dir)) + else + L.forceMove(get_turf(src)) + L.Knockdown(10 SECONDS) + visible_message(span_danger("[user] throws \the [L] over \the [src].")) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 48233a4d8846..365894e47d9a 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -435,6 +435,39 @@ /obj/structure/window/GetExplosionBlock() return reinf && fulltile ? real_explosion_block : 0 +/obj/structure/window/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + if (!user.combat_mode || !grab.current_grab.enable_violent_interactions) + return ..() + + var/mob/living/affecting_mob = grab.get_affecting_mob() + if(!istype(affecting_mob)) + if(isitem(victim)) + var/obj/item/I = victim + I.melee_attack_chain(user, src, params) + return TRUE + + var/def_zone = ran_zone(BODY_ZONE_HEAD, 20) + var/blocked = affecting_mob.run_armor_check(def_zone, MELEE) + if(grab.current_grab.damage_stage < GRAB_NECK) + affecting_mob.visible_message(span_danger("[user] bashes [affecting_mob] against \the [src]!")) + if(prob(50 * ((100 - blocked/100)))) + affecting_mob.Knockdown(4 SECONDS) + affecting_mob.apply_damage(10, BRUTE, def_zone, blocked) + take_damage(10) + qdel(grab) + else + affecting_mob.visible_message(span_danger("[user] crushes [affecting_mob] against \the [src]!")) + affecting_mob.Knockdown(10 SECONDS) + affecting_mob.apply_damage(20, BRUTE, def_zone, blocked) + take_damage(20) + qdel(grab) + + var/obj/effect/decal/cleanable/blood/splatter/over_window/splatter = new(src) + splatter.transfer_mob_blood_dna(victim) + vis_contents += splatter + bloodied = TRUE + return TRUE + /obj/structure/window/spawner/east dir = EAST diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index e8270c03e9dd..a70521f06c54 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -209,6 +209,15 @@ GLOBAL_LIST_EMPTY(station_turfs) return user.move_grabbed_atoms_towards(src) +/turf/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(.) + return + if(!isliving(user)) + return + user.move_grabbed_atoms_towards(src) + + /** * Check whether the specified turf is blocked by something dense inside it with respect to a specific atom. * diff --git a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm index a52c80e0f49e..4852cc3cdae1 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm @@ -26,7 +26,7 @@ Passive gate is similar to the regular pump except: ///Stores the radio connection var/datum/radio_frequency/radio_connection -/obj/machinery/atmospherics/components/binary/passive_gate/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/binary/passive_gate/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm b/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm index 4b8d32a708f7..e091fc3d8059 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm @@ -23,7 +23,7 @@ ///Which side is the valve regulating? var/regulate_mode = REGULATE_OUTPUT -/obj/machinery/atmospherics/components/binary/pressure_valve/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/binary/pressure_valve/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm index cad775e3b114..27d95f7d7e17 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm @@ -39,7 +39,7 @@ /obj/item/circuit_component/atmos_pump, )) -/obj/machinery/atmospherics/components/binary/pump/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/binary/pump/CtrlClick(mob/user, list/params) if(can_interact(user)) set_on(!on) investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm b/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm index 98ef842b0e8e..bebadfd3c98a 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm @@ -17,7 +17,7 @@ ///Check if the gas is moving from one pipenet to the other var/is_gas_flowing = FALSE -/obj/machinery/atmospherics/components/binary/temperature_gate/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/binary/temperature_gate/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm index 425ed4b25bce..dc570ffcb1fd 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm @@ -12,7 +12,7 @@ ///Maximum allowed transfer percentage var/max_heat_transfer_rate = 100 -/obj/machinery/atmospherics/components/binary/temperature_pump/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/binary/temperature_pump/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm index e0c827d10dc7..1bab358d3aeb 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm @@ -39,7 +39,7 @@ /obj/item/circuit_component/atmos_volume_pump, )) -/obj/machinery/atmospherics/components/binary/volume_pump/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/binary/volume_pump/CtrlClick(mob/user, list/params) if(can_interact(user)) set_on(!on) investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm index 73b1a8d6f765..41267ae13e75 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm @@ -22,7 +22,7 @@ //Last power draw, for the progress bar in the UI var/last_power_draw = 0 -/obj/machinery/atmospherics/components/trinary/filter/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/trinary/filter/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm index 80fb796f509e..8ad21db4f41e 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm @@ -22,7 +22,7 @@ var/last_power_draw = 0 -/obj/machinery/atmospherics/components/trinary/mixer/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/trinary/mixer/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index c1cc24f86f28..5c52a6a8ac5d 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -459,7 +459,7 @@ GLOBAL_VAR_INIT(cryo_overlay_cover_off, mutable_appearance('icons/obj/cryogenics /obj/machinery/atmospherics/components/unary/cryo_cell/can_interact(mob/user) return ..() && user.loc != src -/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user, list/params) if(can_interact(user) && !state_open) set_on(!on) return ..() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm index 64f30e857671..c2f6056f1700 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm @@ -20,7 +20,7 @@ ///Rate of operation of the device var/volume_rate = 50 -/obj/machinery/atmospherics/components/unary/outlet_injector/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/unary/outlet_injector/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index 2773b3ea81b9..dc79156ae7af 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -5,33 +5,23 @@ GLOBAL_LIST_EMPTY(all_grabstates) var/icon = 'goon/icons/items/grab.dmi' var/icon_state - var/type_name - var/state_name - var/fancy_desc - /// The grab that this will upgrade to if it upgrades, null means no upgrade var/datum/grab/upgrab /// The grab that this will downgrade to if it downgrades, null means break grab on downgrade var/datum/grab/downgrab - var/datum/time_counter // For things that need to be timed - // Whether or not the grabbed person can move out of the grab var/stop_move = FALSE - // Whether the person being grabbed is facing forwards or backwards. + // Whether the assailant is facing forwards or backwards. var/reverse_facing = FALSE /// Whether this grab state is strong enough to, as a changeling, absorb the person you're grabbing. var/can_absorb = FALSE - /// Whether the person you're grabbing will shield you from bullets. - var/shield_assailant = FALSE /// How much the grab increases point blank damage. var/point_blank_mult = 1 /// Affects how much damage is being dealt using certain actions. var/damage_stage = GRAB_PASSIVE /// If the grabbed person and the grabbing person are on the same tile. var/same_tile = FALSE - /// If the grabber can carry the grabbed person up or down ladders. - var/ladder_carry = FALSE /// If the grabber can throw the person grabbed. var/can_throw = FALSE /// If the grab needs to be downgraded when the grabber does stuff. @@ -53,8 +43,8 @@ GLOBAL_LIST_EMPTY(all_grabstates) var/fail_up = "You fail to upgrade the grab." var/fail_down = "You fail to downgrade the grab." - var/upgrade_cooldown = 40 - var/action_cooldown = 40 + var/upgrade_cooldown = 4 SECONDS + var/action_cooldown = 4 SECONDS var/can_downgrade_on_resist = TRUE var/list/break_chance_table = list(100) @@ -101,7 +91,6 @@ GLOBAL_LIST_EMPTY(all_grabstates) if (can_upgrade(G)) upgrade_effect(G) - log_combat(G.assailant, G.affecting, "tightens their grip [upgrab.state_name] on") return upgrab else to_chat(G.assailant, span_warning("[string_process(G, fail_up)]")) @@ -250,10 +239,9 @@ GLOBAL_LIST_EMPTY(all_grabstates) if(GRAB_PASSIVE) REMOVE_TRAIT(G.affecting, TRAIT_IMMOBILIZED, REF(G)) REMOVE_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, REF(G)) - if(old_damage_stage >= GRAB_NECK) // Previous state was a a neck-grab or higher. - REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, REF(G)) if(old_damage_stage >= GRAB_AGGRESSIVE) REMOVE_TRAIT(G.affecting, TRAIT_AGGRESSIVE_GRAB, REF(G)) + REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, REF(G)) if(GRAB_AGGRESSIVE) if(old_damage_stage >= GRAB_NECK) // Grab got downgraded. diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index f2f5ba8ee122..0f67abafa59a 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -108,6 +108,7 @@ animate_interact(old_target, INTERACT_UNPULL) update_pull_hud_icon() +/// Called during or immediately after movement. Used to move grab targets around to ensure the grabs do not break during movement. /mob/living/proc/handle_grabs_during_movement(turf/old_loc, direction) var/list/grabs_in_grab_chain = get_active_grabs() //recursively_get_conga_line() if(!LAZYLEN(grabs_in_grab_chain)) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 71765001efcf..ddfc19b3699f 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -51,6 +51,7 @@ RegisterSignal(assailant, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) RegisterSignal(affecting, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) RegisterSignal(affecting, COMSIG_MOVABLE_PRE_THROW, PROC_REF(target_thrown)) + RegisterSignal(affecting, COMSIG_ATOM_ATTACK_HAND, PROC_REF(intercept_attack_hand)) RegisterSignal(assailant, COMSIG_MOB_SELECTED_ZONE_SET, PROC_REF(on_target_change)) @@ -62,6 +63,41 @@ affecting = null return ..() +// This will run from Initialize, after can_grab and other checks have succeeded. Must call parent; returning FALSE means failure and qdels the grab. +/obj/item/hand_item/grab/proc/setup() + if(!current_grab.setup(src)) + return FALSE + + assailant.update_pull_hud_icon() + + LAZYADD(affecting.grabbed_by, src) // This is how we handle affecting being deleted. + + adjust_position() + action_used() + + assailant.animate_interact(affecting, INTERACT_GRAB) + + var/sound = 'sound/weapons/thudswoosh.ogg' + if(iscarbon(assailant)) + var/mob/living/carbon/C = assailant + if(C.dna.species.grab_sound) + sound = C.dna.species.grab_sound + + if(isliving(affecting)) + var/mob/living/affecting_mob = affecting + for(var/datum/disease/D as anything in assailant.diseases) + if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + affecting_mob.ContactContractDisease(D) + + for(var/datum/disease/D as anything in affecting_mob.diseases) + if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + assailant.ContactContractDisease(D) + + playsound(affecting.loc, sound, 50, 1, -1) + update_appearance() + current_grab.update_stage_effects(src, null) + return TRUE + /obj/item/hand_item/grab/examine(mob/user) . = ..() var/mob/living/L = get_affecting_mob() @@ -85,13 +121,14 @@ downgrade() -/obj/item/hand_item/grab/pre_attack(atom/A, mob/living/user, params) - // End workaround +/obj/item/hand_item/grab/melee_attack_chain(mob/user, atom/target, params) if (QDELETED(src) || !assailant || !current_grab) - return TRUE - if(A.attack_grab(assailant, affecting, src, params2list(params)) || current_grab.hit_with_grab(src, A, params2list(params))) //If there is no use_grab override or if it returns FALSE; then will behave according to intent. - return TRUE - return ..() + return + + if(target.attack_grab(assailant, affecting, src, params2list(params))) + return + + current_grab.hit_with_grab(src, target, params2list(params)) /obj/item/hand_item/grab/Destroy() if(affecting) @@ -146,40 +183,14 @@ return current_grab.let_go(src) -// This will run from Initialize, after can_grab and other checks have succeeded. Must call parent; returning FALSE means failure and qdels the grab. -/obj/item/hand_item/grab/proc/setup() - if(!current_grab.setup(src)) - return FALSE - - assailant.update_pull_hud_icon() - - LAZYADD(affecting.grabbed_by, src) // This is how we handle affecting being deleted. - - adjust_position() - action_used() - - assailant.animate_interact(affecting, INTERACT_GRAB) - - var/sound = 'sound/weapons/thudswoosh.ogg' - if(iscarbon(assailant)) - var/mob/living/carbon/C = assailant - if(C.dna.species.grab_sound) - sound = C.dna.species.grab_sound - - if(isliving(affecting)) - var/mob/living/affecting_mob = affecting - for(var/datum/disease/D as anything in assailant.diseases) - if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) - affecting_mob.ContactContractDisease(D) - - for(var/datum/disease/D as anything in affecting_mob.diseases) - if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) - assailant.ContactContractDisease(D) +/// Intercepts attack_hand() calls on our target. +/obj/item/hand_item/grab/proc/intercept_attack_hand(atom/movable/source, user, list/modifiers) + SIGNAL_HANDLER + if(user != assailant) + return - playsound(affecting.loc, sound, 50, 1, -1) - update_appearance() - current_grab.update_stage_effects(src, null) - return TRUE + if(current_grab.resolve_openhand_attack(src)) + return COMPONENT_CANCEL_ATTACK_CHAIN // Returns the bodypart of the grabbed person that the grabber is targeting /obj/item/hand_item/grab/proc/get_targeted_bodypart() @@ -253,7 +264,7 @@ update_appearance() /// Used to prevent repeated effect application or early effect removal -/obj/item/hand_item/grab/proc/is_grab_unique (datum/grab/grab_datum) +/obj/item/hand_item/grab/proc/is_grab_unique(datum/grab/grab_datum) var/count = 0 for(var/obj/item/hand_item/grab/other as anything in affecting.grabbed_by) if(other.current_grab == grab_datum) diff --git a/code/modules/grab/grabs/grab_aggressive.dm b/code/modules/grab/grabs/grab_aggressive.dm index 04fc25ab144a..45ba0dd1ad6e 100644 --- a/code/modules/grab/grabs/grab_aggressive.dm +++ b/code/modules/grab/grabs/grab_aggressive.dm @@ -6,7 +6,6 @@ shift = 12 stop_move = TRUE reverse_facing = FALSE - shield_assailant = FALSE point_blank_mult = 1.5 damage_stage = GRAB_AGGRESSIVE same_tile = FALSE diff --git a/code/modules/grab/grabs/grab_neck.dm b/code/modules/grab/grabs/grab_neck.dm index 9731cffefbf6..de469b381763 100644 --- a/code/modules/grab/grabs/grab_neck.dm +++ b/code/modules/grab/grabs/grab_neck.dm @@ -8,7 +8,6 @@ shift = -10 stop_move = TRUE reverse_facing = TRUE - shield_assailant = TRUE point_blank_mult = 2 damage_stage = GRAB_NECK same_tile = TRUE @@ -28,7 +27,7 @@ /datum/grab/normal/neck/remove_grab_effects(obj/item/hand_item/grab/G) . = ..() - ADD_TRAIT(G.affecting, TRAIT_FLOORED, NECK_GRAB) + REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, NECK_GRAB) /datum/grab/normal/neck/enter_as_up(obj/item/hand_item/grab/G) . = ..() diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index 153428affd09..25e78c33bafb 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -34,15 +34,20 @@ var/mob/living/affecting = G.get_affecting_mob() var/mob/living/assailant = G.assailant - if(affecting && A && A == affecting && !affecting.body_position == STANDING_UP) + if(affecting && A && A == affecting) + if(affecting.body_position == LYING_DOWN) + to_chat(assailant, span_warning("They're already on the ground.")) + return FALSE affecting.visible_message(span_danger("\The [assailant] is trying to pin \the [affecting] to the ground!")) + if(do_after(assailant, affecting, action_cooldown - 1, DO_PUBLIC, display = image('icons/hud/do_after.dmi', "harm"))) G.action_used() affecting.visible_message(span_danger("\The [assailant] pins \the [affecting] to the ground!")) + affecting.Knockdown(1 SECOND) // This can only be performed with an aggressive grab, which ensures that once someone is knocked down, they stay down/ return TRUE - affecting.visible_message(span_warning("\The [assailant] fails to pin \the [affecting] to the ground.")) + affecting.visible_message(span_warning("\The [assailant] fails to pin \the [affecting] to the ground.")) return FALSE /datum/grab/normal/on_hit_grab(obj/item/hand_item/grab/G, atom/A) @@ -88,7 +93,7 @@ assailant.visible_message(span_danger("\The [assailant] begins to dislocate \the [affecting]'s [BP.joint_name]!")) if(do_after(assailant, affecting, action_cooldown - 1, DO_PUBLIC)) G.action_used() - BP.set_dislocated() + BP.set_dislocated(TRUE) assailant.visible_message(span_danger("\The [affecting]'s [BP.joint_name] [pick("gives way","caves in","crumbles","collapses")]!")) playsound(assailant.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) return TRUE @@ -147,7 +152,7 @@ /datum/grab/normal/proc/headbutt(obj/item/hand_item/grab/G) var/mob/living/carbon/human/target = G.get_affecting_mob() var/mob/living/carbon/human/attacker = G.assailant - if(!istype(target) || !istype(attacker)) + if(!istype(target) || !istype(attacker)) return if(target.body_position == LYING_DOWN) @@ -162,11 +167,11 @@ if(sharpness & SHARP_POINTY) if(istype(hat)) - attacker.visible_message(span_danger("\The [attacker] gores \the [target] with \the [hat]!")) + attacker.visible_message(span_danger("\The [attacker] gores \the [target] with \the [hat]!")) else - attacker.visible_message(span_danger("\The [attacker] gores \the [target]!")) + attacker.visible_message(span_danger("\The [attacker] gores \the [target]!")) else - attacker.visible_message(span_danger("\The [attacker] thrusts [attacker.p_their()] head into \the [target]'s skull!")) + attacker.visible_message(span_danger("\The [attacker] thrusts [attacker.p_their()] head into \the [target]'s skull!")) var/armor = target.run_armor_check(BODY_ZONE_HEAD, MELEE) target.apply_damage(damage, BRUTE, BODY_ZONE_HEAD, armor, sharpness = sharpness) diff --git a/code/modules/grab/grabs/grab_passive.dm b/code/modules/grab/grabs/grab_passive.dm index db468d7fdfb0..315d8d59e414 100644 --- a/code/modules/grab/grabs/grab_passive.dm +++ b/code/modules/grab/grabs/grab_passive.dm @@ -3,7 +3,6 @@ shift = 8 stop_move = 0 reverse_facing = 0 - shield_assailant = 0 point_blank_mult = 1.1 same_tile = 0 icon_state = "1" diff --git a/code/modules/grab/grabs/grab_simple.dm b/code/modules/grab/grabs/grab_simple.dm index 83763c2f1db5..1e32a62a9b80 100644 --- a/code/modules/grab/grabs/grab_simple.dm +++ b/code/modules/grab/grabs/grab_simple.dm @@ -2,7 +2,6 @@ shift = 8 stop_move = FALSE reverse_facing = FALSE - shield_assailant = FALSE point_blank_mult = 1 same_tile = FALSE icon_state = "1" diff --git a/code/modules/grab/grabs/grab_strangle.dm b/code/modules/grab/grabs/grab_strangle.dm index 5337d2adc554..3b8c7db9e1cf 100644 --- a/code/modules/grab/grabs/grab_strangle.dm +++ b/code/modules/grab/grabs/grab_strangle.dm @@ -6,7 +6,6 @@ shift = 0 stop_move = TRUE reverse_facing = TRUE - shield_assailant = FALSE point_blank_mult = 2 damage_stage = GRAB_KILL same_tile = TRUE diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 89b7d1d293d4..cffffe2e88ba 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -1032,7 +1032,7 @@ if(user) user.examinate(src) -/obj/machinery/hydroponics/CtrlClick(mob/user) +/obj/machinery/hydroponics/CtrlClick(mob/user, list/params) . = ..() if(!user.canUseTopic(src, USE_CLOSE|USE_IGNORE_TK)) return @@ -1130,7 +1130,7 @@ qdel(src) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN -/obj/machinery/hydroponics/soil/CtrlClick(mob/user) +/obj/machinery/hydroponics/soil/CtrlClick(mob/user, list/params) return //Soil has no electricity. diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 6549bbb0c0b2..028ae179c8a2 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -158,7 +158,7 @@ if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) ContactContractDisease(D) - return FALSE + return . || FALSE /mob/living/carbon/attack_paw(mob/living/carbon/human/user, list/modifiers) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 339771a85cd3..171d99721566 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -763,7 +763,9 @@ for(var/obj/item/bodypart/BP as anything in bodyparts) BP.set_sever_artery(FALSE) BP.set_sever_tendon(FALSE) + BP.set_dislocated(FALSE) BP.heal_bones() + BP.adjustPain(-INFINITY) remove_all_embedded_objects() set_heartattack(FALSE) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index f03eedef5a6c..09cba62bf2bc 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -61,7 +61,14 @@ // we need a second, silent armor check to actually know how much to reduce damage taken, as opposed to // on [/atom/proc/bullet_act] where it's just to pass it to the projectile's on_hit(). var/armor_check = check_projectile_armor(def_zone, P, is_silent = TRUE) - apply_damage(P.damage, P.damage_type, def_zone, armor_check, sharpness = P.sharpness, attack_direction = attack_direction) + + var/modifier = 1 + if(LAZYLEN(grabbed_by)) + for(var/obj/item/hand_item/grab/G in grabbed_by) + modifier = max(G.current_grab.point_blank_mult, modifier) + var/damage = P.damage * modifier + + apply_damage(damage, P.damage_type, def_zone, armor_check, sharpness = P.sharpness, attack_direction = attack_direction) apply_effects(P.stun, P.knockdown, P.unconscious, P.slur, P.stutter, P.eyeblur, P.drowsy, armor_check, P.stamina, P.jitter, P.paralyze, P.immobilize) if(P.disorient_length) var/stamina = P.disorient_damage * ((100-armor_check)/100) diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm b/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm index 31e792d02525..0ad9d727d85d 100644 --- a/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm +++ b/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm @@ -142,7 +142,7 @@ . = ..() remove_verb(src, /mob/living/verb/pulled) -/mob/living/simple_animal/hostile/jungle/leaper/CtrlClickOn(atom/A) +/mob/living/simple_animal/hostile/jungle/leaper/CtrlClickOn(atom/A, list/params) face_atom(A) GiveTarget(A) if(!isturf(loc)) diff --git a/code/modules/modular_computers/computers/item/tablet.dm b/code/modules/modular_computers/computers/item/tablet.dm index 6d80b4df1fbb..30273877b6bb 100644 --- a/code/modules/modular_computers/computers/item/tablet.dm +++ b/code/modules/modular_computers/computers/item/tablet.dm @@ -73,7 +73,7 @@ remove_pen(user) -/obj/item/modular_computer/tablet/CtrlClick(mob/user) +/obj/item/modular_computer/tablet/CtrlClick(mob/user, list/params) . = ..() if(.) return diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index bbc98258780d..ffee0e6687ea 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -161,32 +161,33 @@ if(recoil && !tk_firing(user)) shake_camera(user, recoil + 1, recoil) fire_sounds() - if(!suppressed) - if(message) - if(tk_firing(user)) - visible_message( - span_danger("[src] fires itself[pointblank ? " point blank at [pbtarget]!" : "!"]"), - blind_message = span_hear("You hear a gunshot!"), - vision_distance = COMBAT_MESSAGE_RANGE - ) - else if(pointblank) - user.visible_message( - span_danger("[user] fires [src] point blank at [pbtarget]!"), - span_danger("You fire [src] point blank at [pbtarget]!"), - span_hear("You hear a gunshot!"), COMBAT_MESSAGE_RANGE, pbtarget - ) - to_chat(pbtarget, span_userdanger("[user] fires [src] point blank at you!")) - if(pb_knockback > 0 && ismob(pbtarget)) - var/mob/PBT = pbtarget - var/atom/throw_target = get_edge_target_turf(PBT, user.dir) - PBT.throw_at(throw_target, pb_knockback, 2) - else if(!tk_firing(user)) - user.visible_message( - span_danger("[user] fires [src]!"), - blind_message = span_hear("You hear a gunshot!"), - vision_distance = COMBAT_MESSAGE_RANGE, - ignored_mobs = user - ) + if(suppressed || !message) + return + + if(tk_firing(user)) + visible_message( + span_danger("[src] fires itself[pointblank ? " point blank at [pbtarget]!" : "!"]"), + blind_message = span_hear("You hear a gunshot!"), + vision_distance = COMBAT_MESSAGE_RANGE + ) + else if(pointblank) + user.visible_message( + span_danger("[user] fires [src] point blank at [pbtarget]!"), + span_danger("You fire [src] point blank at [pbtarget]!"), + span_hear("You hear a gunshot!"), COMBAT_MESSAGE_RANGE, pbtarget + ) + to_chat(pbtarget, span_userdanger("[user] fires [src] point blank at you!")) + if(pb_knockback > 0 && ismob(pbtarget)) + var/mob/PBT = pbtarget + var/atom/throw_target = get_edge_target_turf(PBT, user.dir) + PBT.throw_at(throw_target, pb_knockback, 2) + else if(!tk_firing(user)) + user.visible_message( + span_danger("[user] fires [src]!"), + blind_message = span_hear("You hear a gunshot!"), + vision_distance = COMBAT_MESSAGE_RANGE, + ignored_mobs = user + ) /obj/item/gun/emp_act(severity) . = ..() diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm index 511b1d05759a..f2ede34647d7 100644 --- a/code/modules/recycling/sortingmachinery.dm +++ b/code/modules/recycling/sortingmachinery.dm @@ -381,7 +381,7 @@ new_barcode.cut_multiplier = cut_multiplier // Also the registered percent cut. user.put_in_hands(new_barcode) -/obj/item/sales_tagger/CtrlClick(mob/user) +/obj/item/sales_tagger/CtrlClick(mob/user, list/params) . = ..() payments_acc = null to_chat(user, span_notice("You clear the registered account.")) diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm index e7bd35a169ce..baaa3b87ee61 100644 --- a/code/modules/research/xenobiology/xenobio_camera.dm +++ b/code/modules/research/xenobiology/xenobio_camera.dm @@ -322,17 +322,17 @@ ..() //scans slimes -/mob/living/simple_animal/slime/CtrlClick(mob/user) +/mob/living/simple_animal/slime/CtrlClick(mob/user, list/params) SEND_SIGNAL(user, COMSIG_XENO_SLIME_CLICK_CTRL, src) ..() //picks up dead monkies -/mob/living/carbon/human/species/monkey/CtrlClick(mob/user) +/mob/living/carbon/human/species/monkey/CtrlClick(mob/user, list/params) SEND_SIGNAL(user, COMSIG_XENO_MONKEY_CLICK_CTRL, src) ..() //places monkies -/turf/open/CtrlClick(mob/user) +/turf/open/CtrlClick(mob/user, list/params) SEND_SIGNAL(user, COMSIG_XENO_TURF_CLICK_CTRL, src) ..() diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 1f7f2761dc53..55fafa63eb43 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -1308,6 +1308,9 @@ if(check_bones() & CHECKBONES_BROKEN) . += tag ? "Fractured" : "Fractured" + if(bodypart_flags & BP_DISLOCATED) + . += tag ? "Dislocated" : "Dislocated" + if (length(cavity_items) || length(embedded_objects)) var/unknown_body = 0 for(var/obj/item/I in cavity_items + embedded_objects) diff --git a/code/modules/surgery/bodyparts/pain.dm b/code/modules/surgery/bodyparts/pain.dm index 4cd293d6b0d7..2da6993e59fd 100644 --- a/code/modules/surgery/bodyparts/pain.dm +++ b/code/modules/surgery/bodyparts/pain.dm @@ -15,10 +15,9 @@ var/lasting_pain = 0 if(bodypart_flags & BP_BROKEN_BONES) lasting_pain += 10 - /* - else if(is_dislocated()) + + else if(bodypart_flags & BP_DISLOCATED) lasting_pain += 5 - */ var/organ_dam = 0 for(var/obj/item/organ/O as anything in contained_organs) diff --git a/modular_pariah/modules/customization/modules/clothing/glasses/glasses.dm b/modular_pariah/modules/customization/modules/clothing/glasses/glasses.dm index e8924f08ec1a..63fddf494caf 100644 --- a/modular_pariah/modules/customization/modules/clothing/glasses/glasses.dm +++ b/modular_pariah/modules/customization/modules/clothing/glasses/glasses.dm @@ -2,7 +2,7 @@ var/can_switch_eye = FALSE //Having this default to false means that its easy to make sure this doesnt apply to any pre-existing items var/current_eye = "_R" //Added to the end of the icon_state to make this easy code-wise, L and R being the wearer's Left and Right -/obj/item/clothing/glasses/CtrlClick(mob/user) +/obj/item/clothing/glasses/CtrlClick(mob/user, list/params) . = ..() if(.) return From ae87711eb681bbefb4649a375bc026cddccf5339 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Fri, 29 Sep 2023 23:43:10 -0400 Subject: [PATCH 16/37] fix buckled movement --- code/game/objects/structures/stairs.dm | 5 +++-- code/game/objects/structures/window.dm | 2 +- code/game/turfs/turf.dm | 2 +- code/modules/grab/grab_living.dm | 4 +++- code/modules/grab/grab_movable.dm | 17 ++++++++------ code/modules/grab/grab_object.dm | 4 +++- code/modules/mob/living/living_movement.dm | 5 ----- code/modules/multiz/movement.dm | 26 ++++++++++++++++------ 8 files changed, 40 insertions(+), 25 deletions(-) diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm index 085e5670c3c8..2dd5c93a9f2f 100644 --- a/code/game/objects/structures/stairs.dm +++ b/code/game/objects/structures/stairs.dm @@ -94,8 +94,9 @@ if(!target.Enter(climber, FALSE)) to_chat(climber, span_warning("Something blocks the path.")) return - climber.set_currently_z_moving(TRUE) - climber.forceMove(target) + + climber.forceMoveWithGroup(target, z_movement = TRUE) + if(!(climber.throwing || (climber.movement_type & (VENTCRAWLING | FLYING)) || HAS_TRAIT(climber, TRAIT_IMMOBILIZED))) playsound(my_turf, 'sound/effects/stairs_step.ogg', 50) playsound(my_turf, 'sound/effects/stairs_step.ogg', 50) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 365894e47d9a..f58719a6f7fd 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -452,7 +452,7 @@ affecting_mob.visible_message(span_danger("[user] bashes [affecting_mob] against \the [src]!")) if(prob(50 * ((100 - blocked/100)))) affecting_mob.Knockdown(4 SECONDS) - affecting_mob.apply_damage(10, BRUTE, def_zone, blocked) + affecting_mob.apply_damage(20, BRUTE, def_zone, blocked) take_damage(10) qdel(grab) else diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index a70521f06c54..d441ac2dd2b1 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -288,7 +288,7 @@ GLOBAL_LIST_EMPTY(station_turfs) ///Called each time the target falls down a z level possibly making their trajectory come to a halt. see __DEFINES/movement.dm. /turf/proc/zImpact(atom/movable/falling, levels = 1, turf/prev_turf) var/flags = FALL_RETAIN_PULL - var/list/falling_movables = falling.get_z_move_affected() + var/list/falling_movables = falling.get_move_group() var/list/falling_mob_names for(var/atom/movable/falling_mob as anything in falling_movables) diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index 0f67abafa59a..2bc3bd9797fb 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -142,7 +142,7 @@ continue if(!QDELETED(G)) - G.update_offsets() + G.affecting.update_offsets() G.current_grab.moved_effect(G) if(G.current_grab.downgrade_on_move) G.downgrade() @@ -150,6 +150,8 @@ var/list/my_grabs = get_active_grabs() for(var/obj/item/hand_item/grab/G in my_grabs) if(G.current_grab.reverse_facing || HAS_TRAIT(G.affecting, TRAIT_KEEP_DIRECTION_WHILE_PULLING)) + if(!direction) + direction = get_dir(src, G.affecting) setDir(global.reverse_dir[direction]) diff --git a/code/modules/grab/grab_movable.dm b/code/modules/grab/grab_movable.dm index abd1ce703702..e46676781fd6 100644 --- a/code/modules/grab/grab_movable.dm +++ b/code/modules/grab/grab_movable.dm @@ -64,11 +64,7 @@ // At this point the move was successful pulling.Move(get_step(pulling.loc, move_dir), move_dir, glide_size) - if(!isliving(pulling)) - continue - - var/mob/living/pulled_mob = pulling - update_offsets(pulled_mob) + pulling.update_offsets() /atom/movable/proc/update_offsets() @@ -78,10 +74,17 @@ var/new_pixel_x = base_pixel_x var/new_pixel_y = base_pixel_y + var/list/grabbed_by = list() + + grabbed_by += src.grabbed_by + if(isliving(src)) + var/mob/living/L = src + if(buckled) + grabbed_by += buckled.grabbed_by + if(isturf(loc)) - // Update offsets from grabs. if(length(grabbed_by)) - for(var/obj/item/hand_item/grab/G as anything in grabbed_by) + for(var/obj/item/hand_item/grab/G in grabbed_by) var/grab_dir = get_dir(G.assailant, src) if(grab_dir && G.current_grab.shift != 0) if(grab_dir & WEST) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index ddfc19b3699f..de346d8d1170 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -301,7 +301,9 @@ /obj/item/hand_item/grab/proc/get_affecting_mob() RETURN_TYPE(/mob/living) if(isobj(affecting)) - return affecting.buckled_mobs?[1] + if(length(affecting.buckled_mobs)) + return affecting.buckled_mobs?[1] + return if(isliving(affecting)) return affecting diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm index 4f78cf9da99f..b6fbadc0b781 100644 --- a/code/modules/mob/living/living_movement.dm +++ b/code/modules/mob/living/living_movement.dm @@ -151,11 +151,6 @@ to_chat(src, span_warning("Unbuckle from [buckled] first.")) return FALSE -/mob/set_currently_z_moving(value) - if(buckled) - return buckled.set_currently_z_moving(value) - return ..() - /mob/living/keybind_face_direction(direction) if(stat != CONSCIOUS) return diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index 36b0331f5862..8956f557b195 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -23,12 +23,6 @@ INVOKE_ASYNC(src, PROC_REF(SpinAnimation), 5, 2) return TRUE -/// Returns a list of movables that should also be affected when src moves through zlevels, and src. -/atom/movable/proc/get_z_move_affected(z_move_flags) - . = list(src) - if(buckled_mobs) - . |= buckled_mobs - /** * Checks if the destination turf is elegible for z movement from the start turf to a given direction and returns it if so. * Args: @@ -134,7 +128,7 @@ return var/turf/falling_from = get_turf(loc) - forceMove(destination) + forceMoveWithGroup(destination, z_movement = TRUE) destination.zImpact(src, 1, prev_turf) /atom/movable/proc/CanZFall(turf/from, direction, anchor_bypass) @@ -241,6 +235,24 @@ playsound(destination, 'sound/effects/stairs_step.ogg', 50) #warn TODO + +/// Returns a list of movables that should also be affected when calling forceMoveWithGroup() +/atom/movable/proc/get_move_group() + SHOULD_CALL_PARENT(TRUE) + RETURN_TYPE(/list) + . = list(src) + if(length(buckled_mobs)) + . |= buckled_mobs + +/// forceMove() wrapper to include things like buckled mobs. +/atom/movable/proc/forceMoveWithGroup(destination, z_movement) + var/list/movers = get_move_group() + for(var/atom/movable/AM as anything in movers) + if(z_movement) + AM.set_currently_z_moving(TRUE) + AM.forceMove(destination) + if(z_movement) + AM.set_currently_z_moving(FALSE) /* /** * We want to relay the zmovement to the buckled atom when possible From cf578c85ce6e5dc5c2e250240a89df276e1f5ba3 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sat, 30 Sep 2023 00:42:53 -0400 Subject: [PATCH 17/37] more window slam funnies --- code/game/objects/structures/window.dm | 32 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index f58719a6f7fd..6c9cb2aa8c26 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -446,18 +446,38 @@ I.melee_attack_chain(user, src, params) return TRUE - var/def_zone = ran_zone(BODY_ZONE_HEAD, 20) + + var/def_zone = grab.target_zone + var/obj/item/bodypart/BP = affecting.get_bodypart(def_zone) + if(!BP) + return var/blocked = affecting_mob.run_armor_check(def_zone, MELEE) if(grab.current_grab.damage_stage < GRAB_NECK) - affecting_mob.visible_message(span_danger("[user] bashes [affecting_mob] against \the [src]!")) - if(prob(50 * ((100 - blocked/100)))) - affecting_mob.Knockdown(4 SECONDS) + affecting_mob.visible_message(span_danger("[user] bashes [affecting_mob]'s [BP.plaintext_zone] against \the [src]!")) + switch(def_zone) + if(BODY_ZONE_HEAD, BODY_ZONE_CHEST) + if(prob(50 * ((100 - blocked/100)))) + affecting_mob.Knockdown(4 SECONDS) + if(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM) + if(prob(50 * ((100 - blocked/100)))) + var/side = def_zone == BODY_ZONE_L_ARM ? LEFT_HANDS : RIGHT_HANDS + var/obj/item/I = affecting.get_held_items_for_side(side) + if(I) + affecting.dropItemToGround(I) affecting_mob.apply_damage(20, BRUTE, def_zone, blocked) take_damage(10) qdel(grab) else - affecting_mob.visible_message(span_danger("[user] crushes [affecting_mob] against \the [src]!")) - affecting_mob.Knockdown(10 SECONDS) + affecting_mob.visible_message(span_danger("[user] crushes [affecting_mob]'s [BP.plaintext_zone] against \the [src]!")) + switch(def_zone) + if(BODY_ZONE_HEAD, BODY_ZONE_CHEST) + affecting_mob.Knockdown(10 SECONDS) + if(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM) + var/side = def_zone == BODY_ZONE_L_ARM ? LEFT_HANDS : RIGHT_HANDS + var/obj/item/I = affecting.get_held_items_for_side(side) + if(I) + affecting.dropItemToGround(I) + affecting_mob.apply_damage(20, BRUTE, def_zone, blocked) take_damage(20) qdel(grab) From 0a1691efc21e9e9706c1f333155b2bb53b1d98d5 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sat, 30 Sep 2023 00:43:22 -0400 Subject: [PATCH 18/37] tweak --- code/game/objects/structures/window.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 6c9cb2aa8c26..1431b7f94523 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -447,7 +447,7 @@ return TRUE - var/def_zone = grab.target_zone + var/def_zone = deprecise_zone(grab.target_zone) var/obj/item/bodypart/BP = affecting.get_bodypart(def_zone) if(!BP) return From 32d53851430a2a071ca96ce07c29929d3ba6453d Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sat, 30 Sep 2023 17:00:03 -0400 Subject: [PATCH 19/37] Fix alot of shit --- code/game/objects/structures/window.dm | 10 +-- code/modules/grab/grab_datum.dm | 42 ++++--------- code/modules/grab/grab_helpers.dm | 2 +- code/modules/grab/grab_living.dm | 2 + code/modules/grab/grab_movable.dm | 4 +- code/modules/grab/grab_object.dm | 9 +-- code/modules/grab/grabs/grab_normal.dm | 15 ++++- code/modules/mob/living/carbon/carbon.dm | 76 +++++++++++++---------- code/modules/mob/living/living.dm | 3 - icons/mob/species/vox/bodyparts.dmi | Bin 1817 -> 1804 bytes 10 files changed, 78 insertions(+), 85 deletions(-) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 1431b7f94523..12442a85f256 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -448,7 +448,7 @@ var/def_zone = deprecise_zone(grab.target_zone) - var/obj/item/bodypart/BP = affecting.get_bodypart(def_zone) + var/obj/item/bodypart/BP = affecting_mob.get_bodypart(def_zone) if(!BP) return var/blocked = affecting_mob.run_armor_check(def_zone, MELEE) @@ -461,9 +461,9 @@ if(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM) if(prob(50 * ((100 - blocked/100)))) var/side = def_zone == BODY_ZONE_L_ARM ? LEFT_HANDS : RIGHT_HANDS - var/obj/item/I = affecting.get_held_items_for_side(side) + var/obj/item/I = affecting_mob.get_held_items_for_side(side) if(I) - affecting.dropItemToGround(I) + affecting_mob.dropItemToGround(I) affecting_mob.apply_damage(20, BRUTE, def_zone, blocked) take_damage(10) qdel(grab) @@ -474,9 +474,9 @@ affecting_mob.Knockdown(10 SECONDS) if(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM) var/side = def_zone == BODY_ZONE_L_ARM ? LEFT_HANDS : RIGHT_HANDS - var/obj/item/I = affecting.get_held_items_for_side(side) + var/obj/item/I = affecting_mob.get_held_items_for_side(side) if(I) - affecting.dropItemToGround(I) + affecting_mob.dropItemToGround(I) affecting_mob.apply_damage(20, BRUTE, def_zone, blocked) take_damage(20) diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index dc79156ae7af..c8675a039bf2 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -47,8 +47,10 @@ GLOBAL_LIST_EMPTY(all_grabstates) var/action_cooldown = 4 SECONDS var/can_downgrade_on_resist = TRUE - var/list/break_chance_table = list(100) + + /// The baseline index of break_chance_table, before modifiers. This can be higher than the length of break_chance_table. var/breakability = 2 + var/list/break_chance_table = list(100) var/can_grab_self = TRUE @@ -120,25 +122,6 @@ GLOBAL_LIST_EMPTY(all_grabstates) special_bodyzone_change(G, old_zone, new_zone) special_bodyzone_effects(G) -/datum/grab/proc/throw_held(obj/item/hand_item/grab/G) - if(G.assailant == G.affecting) - return - - var/mob/living/carbon/human/affecting = G.affecting - - if(can_throw) - . = affecting - var/mob/thrower = G.loc - - animate(affecting, pixel_x = 0, pixel_y = 0, 4, 1) - qdel(G) - - // check if we're grabbing with our inactive hand - G = thrower.get_inactive_hand() - if(!istype(G)) return - qdel(G) - return - /datum/grab/proc/hit_with_grab(obj/item/hand_item/grab/G, atom/target, params) if(downgrade_on_action) G.downgrade() @@ -228,7 +211,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) remove_bodyzone_effects(G) if(G.is_grab_unique(src)) remove_grab_effects(G) - update_stage_effects(G, null, TRUE) + update_stage_effects(G, src, TRUE) /// Add effects that apply based on damage_stage here /datum/grab/proc/update_stage_effects(obj/item/hand_item/grab/G, datum/grab/old_grab, dropping_grab) @@ -315,20 +298,17 @@ GLOBAL_LIST_EMPTY(all_grabstates) /datum/grab/proc/resolve_item_attack(obj/item/hand_item/grab/G, mob/living/carbon/human/user, obj/item/I, target_zone) return FALSE +/// Handle resist actions from the affected mob. Returns TRUE if the grab was broken. /datum/grab/proc/handle_resist(obj/item/hand_item/grab/G) var/mob/living/carbon/human/affecting = G.affecting var/mob/living/carbon/human/assailant = G.assailant - if(affecting.incapacitated()) - to_chat(G.affecting, span_warning("You can't resist in your current state!")) - return - var/break_strength = breakability + size_difference(affecting, assailant) var/affecting_shock = affecting.getPain() var/assailant_shock = assailant.getPain() // Target modifiers - if(affecting.incapacitated()) + if(affecting.incapacitated(IGNORE_GRAB)) break_strength-- if(affecting.has_status_effect(/datum/status_effect/confusion)) break_strength-- @@ -359,23 +339,23 @@ GLOBAL_LIST_EMPTY(all_grabstates) if(break_strength < 1) to_chat(G.affecting, span_warning("You try to break free but feel that unless something changes, you'll never escape!")) - return - + return FALSE - if (assailant.incapacitated()) + if (assailant.incapacitated(IGNORE_GRAB)) let_go(G) stack_trace("Someone resisted a grab while the assailant was incapacitated. This shouldn't ever happen.") - return + return TRUE var/break_chance = break_chance_table[clamp(break_strength, 1, length(break_chance_table))] if(prob(break_chance)) if(can_downgrade_on_resist && !prob((break_chance+100)/2)) affecting.visible_message(span_danger("[affecting] has loosened [assailant]'s grip!")) G.downgrade() - return + return FALSE else affecting.visible_message(span_danger("[affecting] has broken free of [assailant]'s grip!")) let_go(G) + return TRUE /datum/grab/proc/size_difference(mob/living/A, mob/living/B) return mob_size_difference(A.mob_size, B.mob_size) diff --git a/code/modules/grab/grab_helpers.dm b/code/modules/grab/grab_helpers.dm index fa4c7413fc74..232b48a04061 100644 --- a/code/modules/grab/grab_helpers.dm +++ b/code/modules/grab/grab_helpers.dm @@ -46,7 +46,7 @@ /// Frees src from all grabs. /atom/movable/proc/free_from_all_grabs() - QDEL_LIST(grabbed_by) + grabbed_by?.len = 0 grabbed_by = null diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index 2bc3bd9797fb..f1990fea9f97 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -152,6 +152,8 @@ if(G.current_grab.reverse_facing || HAS_TRAIT(G.affecting, TRAIT_KEEP_DIRECTION_WHILE_PULLING)) if(!direction) direction = get_dir(src, G.affecting) + if(!direction) + continue setDir(global.reverse_dir[direction]) diff --git a/code/modules/grab/grab_movable.dm b/code/modules/grab/grab_movable.dm index e46676781fd6..bcb087a51c5f 100644 --- a/code/modules/grab/grab_movable.dm +++ b/code/modules/grab/grab_movable.dm @@ -79,8 +79,8 @@ grabbed_by += src.grabbed_by if(isliving(src)) var/mob/living/L = src - if(buckled) - grabbed_by += buckled.grabbed_by + if(L.buckled) + grabbed_by += L.buckled.grabbed_by if(isturf(loc)) if(length(grabbed_by)) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index de346d8d1170..9c94ff82645b 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -36,7 +36,7 @@ affecting = target if(!istype(assailant) || !assailant.add_grab(src, defer_hand = defer_hand)) return INITIALIZE_HINT_QDEL - target_zone = assailant.zone_selected + target_zone = deprecise_zone(assailant.zone_selected) if(!setup()) return INITIALIZE_HINT_QDEL @@ -135,7 +135,7 @@ LAZYREMOVE(affecting.grabbed_by, src) affecting.update_offsets() if(affecting && assailant) - current_grab.let_go(src) + current_grab?.let_go(src) affecting = null assailant = null return ..() @@ -149,6 +149,7 @@ if(src != assailant.get_active_held_item()) return // Note that because of this condition, there's no guarantee that target_zone = old_sel + new_sel = deprecise_zone(new_sel) if(target_zone == new_sel) return @@ -282,10 +283,6 @@ affecting.plane = assailant.plane affecting.layer = assailant.layer - 0.01 - -/obj/item/hand_item/grab/proc/throw_held() - return current_grab.throw_held(src) - /obj/item/hand_item/grab/proc/handle_resist() current_grab.handle_resist(src) diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index 25e78c33bafb..d6bc61bb449a 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -76,8 +76,8 @@ return FALSE /datum/grab/normal/on_hit_harm(obj/item/hand_item/grab/G, atom/A) - var/mob/living/affecting = G.get_affecting_mob() - if(!affecting || (A && A != affecting)) + var/mob/living/carbon/affecting = G.get_affecting_mob() + if(!istype(affecting) || (A && A != affecting)) return FALSE var/mob/living/assailant = G.assailant @@ -89,12 +89,21 @@ to_chat(assailant, span_warning("\The [affecting] is missing that body part!")) return FALSE + if(BP.bodypart_flags & BP_DISLOCATED) + assailant.visible_message(span_notice("\The [assailant] begins to place [affecting]'s [BP.joint_name] back in it's socket.")) + if(do_after(assailant, affecting, action_cooldown - 1, DO_PUBLIC)) + G.action_used() + BP.set_dislocated(FALSE) + assailant.visible_message(span_warning("\The [affecting]'s [BP.joint_name] pops back into place!")) + affecting.pain_message("AAAHHHHAAGGHHHH", 50, TRUE) + return TRUE + if(BP.can_be_dislocated()) assailant.visible_message(span_danger("\The [assailant] begins to dislocate \the [affecting]'s [BP.joint_name]!")) if(do_after(assailant, affecting, action_cooldown - 1, DO_PUBLIC)) G.action_used() BP.set_dislocated(TRUE) - assailant.visible_message(span_danger("\The [affecting]'s [BP.joint_name] [pick("gives way","caves in","crumbles","collapses")]!")) + assailant.visible_message(span_danger("\The [affecting]'s [BP.joint_name] [pick("gives way","caves in","collapses")]!")) playsound(assailant.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) return TRUE diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 42a19fe4d1cc..c72411948cff 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -158,44 +158,52 @@ if(isgrab(I)) var/obj/item/hand_item/grab/G = I - if(!G.current_grab.can_throw) - return - - var/mob/living/throwable_mob = G.affecting - if(!throwable_mob.buckled) - thrown_thing = throwable_mob - if(G.current_grab.damage_stage >= GRAB_NECK) - neckgrab_throw = TRUE - release_all_grabs() - if(HAS_TRAIT(src, TRAIT_PACIFISM)) - to_chat(src, span_notice("You gently let go of [throwable_mob].")) + if(isitem(G.affecting)) + I = G.affecting + thrown_thing = I.on_thrown(src, target) + if(!thrown_thing) + return + qdel(G) + else + if(!G.current_grab.can_throw || !isliving(G.affecting)) return + + var/mob/living/throwable_mob = G.affecting + if(!throwable_mob.buckled) + thrown_thing = throwable_mob + if(G.current_grab.damage_stage >= GRAB_NECK) + neckgrab_throw = TRUE + release_all_grabs() + if(HAS_TRAIT(src, TRAIT_PACIFISM)) + to_chat(src, span_notice("You gently let go of [throwable_mob].")) + return else thrown_thing = I.on_thrown(src, target) - if(thrown_thing) - - if(isliving(thrown_thing)) - var/turf/start_T = get_turf(loc) //Get the start and target tile for the descriptors - var/turf/end_T = get_turf(target) - if(start_T && end_T) - log_combat(src, thrown_thing, "thrown", addition="grab from tile in [AREACOORD(start_T)] towards tile at [AREACOORD(end_T)]") - var/power_throw = 0 - if(HAS_TRAIT(src, TRAIT_HULK)) - power_throw++ - if(HAS_TRAIT(src, TRAIT_DWARF)) - power_throw-- - if(HAS_TRAIT(thrown_thing, TRAIT_DWARF)) - power_throw++ - if(neckgrab_throw) - power_throw++ - do_attack_animation(target, no_effect = TRUE) //PARIAH EDIT ADDITION - AESTHETICS - playsound(loc, 'sound/weapons/punchmiss.ogg', 50, TRUE, -1) //PARIAH EDIT ADDITION - AESTHETICS - visible_message(span_danger("[src] throws [thrown_thing][power_throw ? " really hard!" : "."]"), \ - span_danger("You throw [thrown_thing][power_throw ? " really hard!" : "."]")) - log_message("has thrown [thrown_thing] [power_throw ? "really hard" : ""]", LOG_ATTACK) - newtonian_move(get_dir(target, src)) - thrown_thing.safe_throw_at(target, thrown_thing.throw_range, thrown_thing.throw_speed + power_throw, src, null, null, null, move_force) + if(!thrown_thing) + return + + if(isliving(thrown_thing)) + var/turf/start_T = get_turf(loc) //Get the start and target tile for the descriptors + var/turf/end_T = get_turf(target) + if(start_T && end_T) + log_combat(src, thrown_thing, "thrown", addition="grab from tile in [AREACOORD(start_T)] towards tile at [AREACOORD(end_T)]") + var/power_throw = 0 + if(HAS_TRAIT(src, TRAIT_HULK)) + power_throw++ + if(HAS_TRAIT(src, TRAIT_DWARF)) + power_throw-- + if(HAS_TRAIT(thrown_thing, TRAIT_DWARF)) + power_throw++ + if(neckgrab_throw) + power_throw++ + do_attack_animation(target, no_effect = TRUE) //PARIAH EDIT ADDITION - AESTHETICS + playsound(loc, 'sound/weapons/punchmiss.ogg', 50, TRUE, -1) //PARIAH EDIT ADDITION - AESTHETICS + visible_message(span_danger("[src] throws [thrown_thing][power_throw ? " really hard!" : "."]"), \ + span_danger("You throw [thrown_thing][power_throw ? " really hard!" : "."]")) + log_message("has thrown [thrown_thing] [power_throw ? "really hard" : ""]", LOG_ATTACK) + newtonian_move(get_dir(target, src)) + thrown_thing.safe_throw_at(target, thrown_thing.throw_range, thrown_thing.throw_speed + power_throw, src, null, null, null, move_force) /mob/living/carbon/proc/canBeHandcuffed() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 86a08b4e7fb6..df3e9503fb7c 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -924,9 +924,6 @@ if(!LAZYLEN(grabbed_by)) return TRUE - if(moving_resist && client) //we resisted by trying to move - client.move_delay = world.time + 4 SECONDS - if(!moving_resist) visible_message(span_danger("\The [src] struggles to break free!")) diff --git a/icons/mob/species/vox/bodyparts.dmi b/icons/mob/species/vox/bodyparts.dmi index f036392f63393734aaeaa4075b320f494c4d40d2..8663e45da8455737a5c1030962995193a9ed2083 100644 GIT binary patch delta 1502 zcmV<41tI#G4vY@4K>>fZNkl4RfO)5QfFCA~YW)XaE0Cy~T8e&Q6=}Qfk?YowX*Vgy`v~4R`*mtT`om7DDVtfO?JxPDC22|E>hHi1V8`$ls zIQo$)P_ZMRwyWRtgHoMR1J|PWhp3bLpjhCt2Ydmw^{D`FP{$WAEM1h1-wIID5m2%L zb@dYlnMj;U90s`nYVdsiwe_2k6jVPx9lv!_sQ|eL*VbPg5)6#SKZhx46qd)Z%KBsH zN724xEq)_;HN1cE1^5b7&;A)x{s?VXzZr+2qtCz=Fusw2Z$cOJmuX6fk4n0ZtF2EL zp;W>!@|U8NKuP&urMA9T80Gx2_}=va0000000000008)T=-LI;W?~&kwnlR;^^{#s zMkTCl9uwz4di9z~VeC}Kfna`4H9PraP3ZSJqj<(dGGKqbgOpRBh%R6Nv!lk^H4U&u zayQzB4VW?ntPPrFra}gUPNm5fGhhlEkWswm3@Fu01Nlpt>)j0*kT=9ItxdRd<@1=o z)DS2XnPmWzJG+{U{N*lSij@ff000000002|2;MzHe57z>v98c0zIAQ=!|_C)gAn)d zaW8W6jDdf>c>fG-T>mIkFfP&KQCg6HT53E#>hO`x=r;#y(@+zxfcN8agpNWdU1Wk? zZTV%yfXe#K``y2f1uUINDuDLkd^nO^pthzt9?tyk)z|k9(>L!h8R+$;E_gdl&*#%) z2G&&qk0e!0AbU1%(TAw!l_8m^{PeD1y(k?s-M>njk zf76zaQN;fjsjctFAL%+|eQN6i0000000000000000000000000004NJV$T@aE7tOl z>BxLSNca&Jxmo@B0RqSW59nd3~)?G*J%cbS0QExV}QH3OYl`!e=M^+ z@p~gn*J1ohr3-~OZ-BniCm8TVwl*HYfF=G?eEe zNgM+NmtvFCo9_Vy#(*6gp#9Q>cq=EIC_45ir7f?|&=+?}GGMZQ_%$p9f1PnPTV9^9 ztV>841chY^%kl%s04z@c00000004gg0002sKhl5x>|SJkayw()OSkqX>AouRY9Y?? zG2^UT-vC)es!t2Cf9h~TC6fQC2Zxy*5Ovc;e30K%Tgjepbxdp_@Pw4@A=4J>*tp+XpPP&exLGqjz%+k`)J1t6 z?*KD9`#Dc~PUxaWi-&;fqe6pGE6uxpp(k#zFn;PyntCz`2KeI;K1MEO{X|4WEK$#W ziondy;`=i!^85%BKZLqxPDDM~gfYOG$Qlu0jELVGy(sVk(fNIy@K=DZLi6L-7m@v6 z9NIdbn}SyI7a#E&xcn|{?%dje%kbeM`}bi}3}75^L&_Su%M2D@%^X#R7v31=X2T`)65$F0uO>oQl6$Ub^rhX07*qoM6N<$ Ef@6f$Q~&?~ delta 1515 zcmYk(c|6k%90%~tvanjBLyqPMPrZt{BFgZ%AGej5V~%n)(-czMZ<2c&;b9L(Vx@%8 z%m^JK%Bn4u*f1hXh4e7To_dbg)Az5>f8WpR{h`2V@CIuz@X*QD8l6}=Tec< zH((v1;50yzP?M};7?BV3a!0SuuAI;_<#rQAiZ$LIB~Z|hBTHp4qr3aZX2c^S39&0} zc;^Zci?!KkJ8y6&F1i!Lw$o=b6_3y=TEza4PU35?xgqgDITN=Fw&K=YDU^#1pu~(a zTNHpQaN%Of)dhG0r1jo`fpy_N`8!(b%v*x5De`6VkXMxn={JNc>X-@vgiRh2GeO}V z4m*)cdd`pW(lVc9WcXyZST@CFPt0$s&<@bM(Z$thk?FOg@@q?k)j_9Lsioci7=gtj zC8ZpR3WMcYjCiU z7E}+Vd|ze2+z0&X{XzTF*9!1w4$~oG=Ea3MfE7t>n;S_mYqgMGR_zE#+;%zUI7Y&F zhPaj?!Fyo@IjG^Yy<*V*Z?zDsfTv_qrHdfq7$J#M=n;2&8sp!G`lO?6c~6#l56I;HmS{v;JDL7?H8Q! zmd~i4Uw}ZQojeO$(q{IXezSPFMTJ7aL()`lOLA4vl%_DdG{kiR{Sjvnt+6@ODlxdP zMQ6nnTPdhMR#^+v=xmZAa~$6I8@UkRxf`sJMMAp-$Q*o@11 z+)dbe8y@^ID9)g0^j6(^M98^uq86_H;dG(%P8EJTcs>espRCn*mw<#WEnyb_Tlag< z25VARNeKNDhr>21HzBhY0nFi%^73;4N=Oc&S2k$umVoHz07){uhFB{|Y)7FM*4ODz z+NqN;s_8fo&M)SaO1UsDy$z55^fEldm7~k)KdhW99Sx%G51z>>Yug{)74WO^>-+Oo$p+XSsAG$<2EPxfn?*U|9N8~2 zn&|ewvZ?qhB58AWYR3TU<^g^8FCqmmsP#$O#UK*UwTnC{`xE!7gE+11;0U#Memvf} zq$Ot7{Uy<7V#J1WHMB3HsRP_D<5M0MBA7wCXqQTAHl`P=Vb5}H{d4)~r~!JLxlW_J z$WjRSZ9(#eUmI4Nb!Gh$q`tk-UVkn#v)hpkFQMnVe*hD%o|N&OG~PNn18A*~OG+rP zVmpN+RwjKno8}Gq;2nf-akPmm&+Q7swj|%?>`w(?y+CxVn@x@%J3QfT6_>^d~uItVEo Date: Sat, 30 Sep 2023 17:12:20 -0400 Subject: [PATCH 20/37] rename release_grab --- code/modules/grab/grab_helpers.dm | 2 +- code/modules/holodeck/items.dm | 2 +- code/modules/mob/living/carbon/carbon.dm | 4 ++-- code/modules/mob/living/living.dm | 2 +- code/modules/tables/tables_racks.dm | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/code/modules/grab/grab_helpers.dm b/code/modules/grab/grab_helpers.dm index 232b48a04061..5e8850edd900 100644 --- a/code/modules/grab/grab_helpers.dm +++ b/code/modules/grab/grab_helpers.dm @@ -16,7 +16,7 @@ qdel(G) /// Release the given movable from a grab -/mob/living/proc/release_grab(atom/movable/AM) +/mob/living/proc/release_grabs(atom/movable/AM) for(var/obj/item/hand_item/grab/G in get_active_grabs()) if(G.affecting == AM) qdel(G) diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm index b747dc4a6df8..80e4167689a1 100644 --- a/code/modules/holodeck/items.dm +++ b/code/modules/holodeck/items.dm @@ -104,7 +104,7 @@ L.forceMove(loc) L.Paralyze(100) visible_message(span_danger("[user] dunks [L] into \the [src]!")) - user.release_grab(L) + user.release_grabs(L) return TRUE /obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index c72411948cff..5f64d3354a02 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -163,7 +163,7 @@ thrown_thing = I.on_thrown(src, target) if(!thrown_thing) return - qdel(G) + release_grabs(I) else if(!G.current_grab.can_throw || !isliving(G.affecting)) return @@ -173,7 +173,7 @@ thrown_thing = throwable_mob if(G.current_grab.damage_stage >= GRAB_NECK) neckgrab_throw = TRUE - release_all_grabs() + release_grabs(throwable_mob) if(HAS_TRAIT(src, TRAIT_PACIFISM)) to_chat(src, span_notice("You gently let go of [throwable_mob].")) return diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index df3e9503fb7c..e6492d3ae4f3 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -272,7 +272,7 @@ now_pushing = FALSE return - release_grab(AM) + release_grabs(AM) var/current_dir if(isliving(AM)) diff --git a/code/modules/tables/tables_racks.dm b/code/modules/tables/tables_racks.dm index 05b6b8c682ad..eb1a5dd3da65 100644 --- a/code/modules/tables/tables_racks.dm +++ b/code/modules/tables/tables_racks.dm @@ -264,7 +264,7 @@ tableplace(user, pushed_mob) else return - user.release_grab(pushed_mob) + user.release_grabs(pushed_mob) else if(target.pass_flags & PASSTABLE) user.move_grabbed_atoms_towards(src) @@ -272,7 +272,7 @@ user.visible_message(span_notice("[user] places [target] onto [src]."), span_notice("You place [target] onto [src].")) - user.release_grab(target) + user.release_grabs(target) /obj/structure/table/proc/tableplace(mob/living/user, mob/living/pushed_mob) pushed_mob.forceMove(loc) From fbc69c1af4a04ac8f4ebf5e2de3974507580c69d Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sun, 1 Oct 2023 17:38:59 -0400 Subject: [PATCH 21/37] fix lateral Z movement --- code/__DEFINES/_multiz.dm | 6 ++++++ code/game/atoms_movable.dm | 6 +++--- code/game/objects/structures/stairs.dm | 2 +- code/game/turfs/open/space/space.dm | 9 +++++++-- code/modules/grab/grab_living.dm | 1 - code/modules/mob/living/living.dm | 10 ++++++---- code/modules/multiz/movement.dm | 6 ++---- code/modules/power/gravitygenerator.dm | 2 +- code/modules/unit_tests/_unit_tests.dm | 9 ++++++++- code/modules/unit_tests/chain_pull_through_space.dm | 2 +- 10 files changed, 35 insertions(+), 18 deletions(-) diff --git a/code/__DEFINES/_multiz.dm b/code/__DEFINES/_multiz.dm index b4c9d0de5774..02dd04549d43 100644 --- a/code/__DEFINES/_multiz.dm +++ b/code/__DEFINES/_multiz.dm @@ -5,3 +5,9 @@ #define GetAbove(A) (HasAbove(A:z) ? get_step(A, UP) : null) #define GetBelow(A) (HasBelow(A:z) ? get_step(A, DOWN) : null) + + +/// Vertical Z movement +#define ZMOVING_VERTICAL 1 +/// Laterial Z movement +#define ZMOVING_LATERAL 2 diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 4203282f7cc6..86bd47b393cd 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -392,7 +392,7 @@ if(direct & (UP|DOWN)) if(!can_z_move(direct, null, z_movement_flags)) return FALSE - set_currently_z_moving(TRUE) + set_currently_z_moving(ZMOVING_VERTICAL) var/atom/oldloc = loc //Early override for some cases like diagonal movement @@ -465,7 +465,7 @@ if(set_dir_on_move && dir != direct) setDir(direct) - if(. && isliving(src)) + if(. && isliving(src) && currently_z_moving != ZMOVING_LATERAL) var/mob/living/L = src L.handle_grabs_during_movement(oldloc, direct) @@ -776,7 +776,7 @@ anchored = anchorvalue SEND_SIGNAL(src, COMSIG_MOVABLE_SET_ANCHORED, anchorvalue) -/// Sets the currently_z_moving variable to a new value. Used to allow some zMovement sources to have precedence over others. +/// Sets the currently_z_moving variable to a new value. Used to temporarily disable some Move() side effects. /atom/movable/proc/set_currently_z_moving(new_z_moving_value) if(new_z_moving_value == currently_z_moving) return FALSE diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm index 2dd5c93a9f2f..5c350a2ae064 100644 --- a/code/game/objects/structures/stairs.dm +++ b/code/game/objects/structures/stairs.dm @@ -95,7 +95,7 @@ to_chat(climber, span_warning("Something blocks the path.")) return - climber.forceMoveWithGroup(target, z_movement = TRUE) + climber.forceMoveWithGroup(target, z_movement = ZMOVING_VERTICAL) if(!(climber.throwing || (climber.movement_type & (VENTCRAWLING | FLYING)) || HAS_TRAIT(climber, TRAIT_IMMOBILIZED))) playsound(my_turf, 'sound/effects/stairs_step.ogg', 50) diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm index 6b9592af72a0..1230ee74611f 100644 --- a/code/game/turfs/open/space/space.dm +++ b/code/game/turfs/open/space/space.dm @@ -123,7 +123,7 @@ GLOBAL_REAL_VAR(space_appearances) = make_space_appearances() if(!arrived || src != arrived.loc) return - if(destination_z && destination_x && destination_y && !LAZYLEN(arrived.grabbed_by)) + if(destination_z && destination_x && destination_y && !LAZYLEN(arrived.grabbed_by) && !arrived.currently_z_moving) var/tx = destination_x var/ty = destination_y var/turf/DT = locate(tx, ty, destination_z) @@ -145,7 +145,12 @@ GLOBAL_REAL_VAR(space_appearances) = make_space_appearances() if(SEND_SIGNAL(arrived, COMSIG_MOVABLE_LATERAL_Z_MOVE) & COMPONENT_BLOCK_MOVEMENT) return - arrived.Move(null, DT) + arrived.forceMoveWithGroup(DT, ZMOVING_LATERAL) + if(isliving(arrived)) + var/mob/living/L = arrived + for(var/obj/item/hand_item/grab/G as anything in L.recursively_get_conga_line()) + var/turf/pulled_dest = get_step(G.assailant.loc, REVERSE_DIR(G.assailant.dir)) || G.assailant.loc + G.affecting.forceMoveWithGroup(pulled_dest, ZMOVING_LATERAL) /turf/open/space/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent) diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index f1990fea9f97..2062f2e23a3d 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -142,7 +142,6 @@ continue if(!QDELETED(G)) - G.affecting.update_offsets() G.current_grab.moved_effect(G) if(G.current_grab.downgrade_on_move) G.downgrade() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index e6492d3ae4f3..2043364aca16 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -927,9 +927,13 @@ if(!moving_resist) visible_message(span_danger("\The [src] struggles to break free!")) + . = TRUE for(var/obj/item/hand_item/grab/G as anything in grabbed_by) + if(G.assailant == src) //Grabbing our own bodyparts + continue log_combat(src, G.assailant, "resisted grab") - . = G.handle_resist() || . + if(!G.handle_resist()) + . = FALSE /mob/living/proc/resist_buckle() buckled.user_unbuckle_mob(src,src) @@ -1466,7 +1470,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) if(!.) return - if(!QDELETED(src)) + if(!QDELETED(src) && currently_z_moving == ZMOVING_VERTICAL) // Lateral Z movement handles this on it's own handle_grabs_during_movement(old_loc, get_dir(old_loc, src)) recheck_grabs() @@ -1474,8 +1478,6 @@ GLOBAL_LIST_EMPTY(fire_appearances) reset_perspective() - - /mob/living/proc/update_z(new_z) // 1+ to register, null to unregister if (registered_z != new_z) if (registered_z) diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index 8956f557b195..b83bbe0ea985 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -128,7 +128,7 @@ return var/turf/falling_from = get_turf(loc) - forceMoveWithGroup(destination, z_movement = TRUE) + forceMoveWithGroup(destination, z_movement = ZMOVING_VERTICAL) destination.zImpact(src, 1, prev_turf) /atom/movable/proc/CanZFall(turf/from, direction, anchor_bypass) @@ -234,8 +234,6 @@ playsound(oldloc, 'sound/effects/stairs_step.ogg', 50) playsound(destination, 'sound/effects/stairs_step.ogg', 50) -#warn TODO - /// Returns a list of movables that should also be affected when calling forceMoveWithGroup() /atom/movable/proc/get_move_group() SHOULD_CALL_PARENT(TRUE) @@ -249,7 +247,7 @@ var/list/movers = get_move_group() for(var/atom/movable/AM as anything in movers) if(z_movement) - AM.set_currently_z_moving(TRUE) + AM.set_currently_z_moving(z_movement) AM.forceMove(destination) if(z_movement) AM.set_currently_z_moving(FALSE) diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm index 99d105185aa2..0aba2f8e45aa 100644 --- a/code/modules/power/gravitygenerator.dm +++ b/code/modules/power/gravitygenerator.dm @@ -318,7 +318,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) if(SSticker.current_state == GAME_STATE_PLAYING) investigate_log("was brought online and is now producing gravity for this level.", INVESTIGATE_GRAVITY) message_admins("The gravity generator was brought online [ADMIN_VERBOSEJMP(src)]") - shake_everyone() + shake_everyone() /obj/machinery/gravity_generator/main/proc/disable() diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index b494717a4319..22f19a305261 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -65,7 +65,7 @@ /// A trait source when adding traits through unit tests #define TRAIT_SOURCE_UNIT_TESTS "unit_tests" - +/* #include "achievements.dm" #include "anchored_mobs.dm" #include "anonymous_themes.dm" @@ -76,7 +76,9 @@ #include "bloody_footprints.dm" #include "breath.dm" #include "card_mismatch.dm" +*/ #include "chain_pull_through_space.dm" +/* #include "chat_filter.dm" #include "circuit_component_category.dm" #include "closets.dm" @@ -86,7 +88,9 @@ #include "confusion.dm" #include "connect_loc.dm" #include "crayons.dm" +*/ #include "create_and_destroy.dm" +/* #include "dcs_get_id_from_elements.dm" #include "designs.dm" #include "dummy_spawn.dm" @@ -153,11 +157,14 @@ #include "stomach.dm" #include "strippable.dm" #include "subsystem_init.dm" +*/ #include "surgeries.dm" +/* #include "teleporters.dm" #include "tgui_create_message.dm" #include "timer_sanity.dm" #include "traitor.dm" +*/ #include "unit_test.dm" #include "wizard_loadout.dm" #include "wounds.dm" diff --git a/code/modules/unit_tests/chain_pull_through_space.dm b/code/modules/unit_tests/chain_pull_through_space.dm index ccbed364ca06..4ec1f7d612c3 100644 --- a/code/modules/unit_tests/chain_pull_through_space.dm +++ b/code/modules/unit_tests/chain_pull_through_space.dm @@ -42,8 +42,8 @@ /datum/unit_test/chain_pull_through_space/Run() // Alice pulls Bob, who pulls Charlie // Normally, when Alice moves forward, the rest follow - alice.try_make_grab(bob) bob.try_make_grab(charlie) + alice.try_make_grab(bob) // Walk normally to the left, make sure we're still a chain alice.Move(locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) From 6b9f88c3cb47e4780d23e1416bc7c8a5da2b70e6 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sun, 1 Oct 2023 17:53:26 -0400 Subject: [PATCH 22/37] fix up defer_hand --- code/_onclick/click.dm | 2 +- code/modules/grab/grab_living.dm | 18 +++++++++--------- code/modules/grab/grab_movable.dm | 8 +++----- code/modules/grab/grab_object.dm | 4 ++-- code/modules/grab/human_grab.dm | 8 ++++---- .../mob/living/carbon/alien/larva/larva.dm | 2 +- .../modules/mob/living/carbon/human/species.dm | 4 ++-- code/modules/unit_tests/_unit_tests.dm | 9 +-------- 8 files changed, 23 insertions(+), 32 deletions(-) diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 815462eef778..58cb44fa436b 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -394,7 +394,7 @@ G.current_grab.hit_with_grab(G, src, params) return TRUE - if(human_user.dna.species.grab(human_user, src, human_user.mind.martial_art)) + if(human_user.dna.species.grab(human_user, src, human_user.mind.martial_art, params)) human_user.changeNext_move(CLICK_CD_MELEE) human_user.animate_interact(src, INTERACT_GRAB) return TRUE diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index 2062f2e23a3d..99f22d8b0409 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -1,11 +1,11 @@ -/mob/living/proc/can_grab(atom/movable/target, target_zone, defer_hand) +/mob/living/proc/can_grab(atom/movable/target, target_zone, use_offhand) if(throwing || !(mobility_flags & MOBILITY_PULL)) return FALSE if(!ismob(target) && target.anchored) to_chat(src, span_warning("\The [target] won't budge!")) return FALSE - if(defer_hand) + if(use_offhand) if(!get_empty_held_index()) to_chat(src, span_warning("Your hands are full!")) return FALSE @@ -31,7 +31,7 @@ return FALSE return TRUE -/mob/living/can_be_grabbed(mob/living/grabber, target_zone, force) +/mob/living/can_be_grabbed(mob/living/grabber, target_zone, use_offhand) . = ..() if(!.) return @@ -39,10 +39,10 @@ return FALSE /// Attempt to create a grab, returns TRUE on success -/mob/living/proc/try_make_grab(atom/movable/target, grab_type) - return canUseTopic(src, USE_IGNORE_TK|USE_CLOSE) && make_grab(target, grab_type) +/mob/living/proc/try_make_grab(atom/movable/target, grab_type, use_offhand) + return canUseTopic(src, USE_IGNORE_TK|USE_CLOSE) && make_grab(target, grab_type, use_offhand) -/mob/living/proc/make_grab(atom/movable/target, grab_type = /datum/grab/simple, defer_hand) +/mob/living/proc/make_grab(atom/movable/target, grab_type = /datum/grab/simple, use_offhand) if(SEND_SIGNAL(src, COMSIG_LIVING_TRY_GRAB, target, grab_type) & COMSIG_LIVING_CANCEL_GRAB) return @@ -63,8 +63,8 @@ face_atom(target) var/obj/item/hand_item/grab/grab - if(ispath(grab_type, /datum/grab) && can_grab(target, zone_selected, defer_hand = defer_hand) && target.can_be_grabbed(src, zone_selected, defer_hand)) - grab = new /obj/item/hand_item/grab(src, target, grab_type, defer_hand) + if(ispath(grab_type, /datum/grab) && can_grab(target, zone_selected, use_offhand) && target.can_be_grabbed(src, zone_selected, use_offhand)) + grab = new /obj/item/hand_item/grab(src, target, grab_type, use_offhand) if(QDELETED(grab)) @@ -79,7 +79,7 @@ return grab -/mob/living/proc/add_grab(obj/item/hand_item/grab/grab, defer_hand) +/mob/living/proc/add_grab(obj/item/hand_item/grab/grab, use_offhand) for(var/obj/item/hand_item/grab/other_grab in contents) if(other_grab != grab) return FALSE diff --git a/code/modules/grab/grab_movable.dm b/code/modules/grab/grab_movable.dm index bcb087a51c5f..a373b4607797 100644 --- a/code/modules/grab/grab_movable.dm +++ b/code/modules/grab/grab_movable.dm @@ -1,4 +1,4 @@ -/atom/movable/proc/can_be_grabbed(mob/living/grabber, target_zone, force) +/atom/movable/proc/can_be_grabbed(mob/living/grabber, target_zone, use_offhand) if(!istype(grabber) || !isturf(loc) || !isturf(grabber.loc)) return FALSE if(SEND_SIGNAL(src, COMSIG_ATOM_CAN_BE_GRABBED, grabber) & COMSIG_ATOM_NO_GRAB) @@ -12,11 +12,9 @@ return FALSE if(throwing) return FALSE - #warn Impliment pull force - /* Impliment pull force - if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) + if(pull_force < (move_resist * MOVE_FORCE_PULL_RATIO)) + to_chat(grabber, span_warning("You aren't strong enough to move [src]!")) return FALSE - */ return TRUE /atom/movable/proc/buckled_grab_check(var/mob/grabber) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 9c94ff82645b..87ebb3da7551 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -25,7 +25,7 @@ /* This section is for overrides of existing procs. */ -/obj/item/hand_item/grab/Initialize(mapload, atom/movable/target, datum/grab/grab_type, defer_hand) +/obj/item/hand_item/grab/Initialize(mapload, atom/movable/target, datum/grab/grab_type, use_offhand) . = ..() current_grab = GLOB.all_grabstates[grab_type] @@ -34,7 +34,7 @@ return INITIALIZE_HINT_QDEL affecting = target - if(!istype(assailant) || !assailant.add_grab(src, defer_hand = defer_hand)) + if(!istype(assailant) || !assailant.add_grab(src, use_offhand = use_offhand)) return INITIALIZE_HINT_QDEL target_zone = deprecise_zone(assailant.zone_selected) diff --git a/code/modules/grab/human_grab.dm b/code/modules/grab/human_grab.dm index adf8c3fbfdcb..c8d071af8b36 100644 --- a/code/modules/grab/human_grab.dm +++ b/code/modules/grab/human_grab.dm @@ -1,10 +1,10 @@ -/mob/living/carbon/human/add_grab(var/obj/item/hand_item/grab/grab, defer_hand = FALSE) - if(defer_hand) +/mob/living/carbon/human/add_grab(var/obj/item/hand_item/grab/grab, use_offhand = FALSE) + if(use_offhand) . = put_in_inactive_hand(grab) else . = put_in_active_hand(grab) -/mob/living/carbon/human/can_be_grabbed(mob/living/grabber, target_zone, defer_hand = FALSE) +/mob/living/carbon/human/can_be_grabbed(mob/living/grabber, target_zone, use_offhand) . = ..() if(!.) return @@ -15,7 +15,7 @@ return FALSE if(grabber == src) - var/using_slot = defer_hand ? get_inactive_hand() : get_active_hand() + var/using_slot = use_offhand ? get_inactive_hand() : get_active_hand() if(!using_slot) to_chat(src, span_warning("You cannot grab yourself without a usable hand!")) return FALSE diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm index 2c8238cd69dc..0135607736b3 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva.dm @@ -69,7 +69,7 @@ /mob/living/carbon/alien/larva/toggle_throw_mode() return -/mob/living/carbon/alien/larva/can_grab(atom/movable/target, target_zone, defer_hand) +/mob/living/carbon/alien/larva/can_grab(atom/movable/target, target_zone, use_offhand) return FALSE /mob/living/carbon/alien/larva/canBeHandcuffed() diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 578b2ccd24e8..28792e3b5035 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -952,7 +952,7 @@ GLOBAL_LIST_EMPTY(features_by_species) user.do_cpr(target) -/datum/species/proc/grab(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style) +/datum/species/proc/grab(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style, list/params) if(target.check_block()) target.visible_message(span_warning("[target] blocks [user]'s grab!"), \ span_userdanger("You block [user]'s grab!"), span_hear("You hear a swoosh!"), COMBAT_MESSAGE_RANGE, user) @@ -961,7 +961,7 @@ GLOBAL_LIST_EMPTY(features_by_species) if(attacker_style?.grab_act(user,target) == MARTIAL_ATTACK_SUCCESS) return TRUE else - user.try_make_grab(target) + user.try_make_grab(target, use_offhand = params?[RIGHT_CLICK]) return TRUE ///This proc handles punching damage. diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 22f19a305261..b494717a4319 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -65,7 +65,7 @@ /// A trait source when adding traits through unit tests #define TRAIT_SOURCE_UNIT_TESTS "unit_tests" -/* + #include "achievements.dm" #include "anchored_mobs.dm" #include "anonymous_themes.dm" @@ -76,9 +76,7 @@ #include "bloody_footprints.dm" #include "breath.dm" #include "card_mismatch.dm" -*/ #include "chain_pull_through_space.dm" -/* #include "chat_filter.dm" #include "circuit_component_category.dm" #include "closets.dm" @@ -88,9 +86,7 @@ #include "confusion.dm" #include "connect_loc.dm" #include "crayons.dm" -*/ #include "create_and_destroy.dm" -/* #include "dcs_get_id_from_elements.dm" #include "designs.dm" #include "dummy_spawn.dm" @@ -157,14 +153,11 @@ #include "stomach.dm" #include "strippable.dm" #include "subsystem_init.dm" -*/ #include "surgeries.dm" -/* #include "teleporters.dm" #include "tgui_create_message.dm" #include "timer_sanity.dm" #include "traitor.dm" -*/ #include "unit_test.dm" #include "wizard_loadout.dm" #include "wounds.dm" From 11788acda56d8804bce0a216bafc814f115217b8 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sun, 1 Oct 2023 18:06:31 -0400 Subject: [PATCH 23/37] Fix linters --- code/modules/grab/grab_movable.dm | 2 +- code/modules/grab/grabs/grab_normal.dm | 2 +- code/modules/grab/human_grab.dm | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/modules/grab/grab_movable.dm b/code/modules/grab/grab_movable.dm index a373b4607797..ab2afce93af6 100644 --- a/code/modules/grab/grab_movable.dm +++ b/code/modules/grab/grab_movable.dm @@ -17,7 +17,7 @@ return FALSE return TRUE -/atom/movable/proc/buckled_grab_check(var/mob/grabber) +/atom/movable/proc/buckled_grab_check(mob/grabber) if(grabber.buckled == src && (grabber in buckled_mobs)) return TRUE if(grabber.anchored) diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index d6bc61bb449a..be6bbfbf769a 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -219,7 +219,7 @@ if(BODY_ZONE_PRECISE_EYES) G.assailant.visible_message("\The [G.assailant] covers [G.affecting]'s eyes!") -/datum/grab/normal/check_special_target(var/obj/item/hand_item/grab/G) +/datum/grab/normal/check_special_target(obj/item/hand_item/grab/G) var/mob/living/affecting_mob = G.get_affecting_mob() if(!istype(affecting_mob)) return FALSE diff --git a/code/modules/grab/human_grab.dm b/code/modules/grab/human_grab.dm index c8d071af8b36..aa852d5419e4 100644 --- a/code/modules/grab/human_grab.dm +++ b/code/modules/grab/human_grab.dm @@ -1,4 +1,4 @@ -/mob/living/carbon/human/add_grab(var/obj/item/hand_item/grab/grab, use_offhand = FALSE) +/mob/living/carbon/human/add_grab(obj/item/hand_item/grab/grab, use_offhand = FALSE) if(use_offhand) . = put_in_inactive_hand(grab) else From 1c212edf435771e22b2ee2f56c1e32d0fbc0e179 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sun, 1 Oct 2023 18:42:19 -0400 Subject: [PATCH 24/37] fix tabling --- code/game/objects/structures.dm | 4 +++- code/modules/multiz/movement.dm | 1 - code/modules/tables/tables_racks.dm | 29 +++++++++++++++++++---------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 88846bcc185c..ebb02c17b238 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -101,6 +101,8 @@ . = ..() if(!user.combat_mode) return + if(!grab.target_zone == BODY_ZONE_HEAD) + return if (!grab.current_grab.enable_violent_interactions) to_chat(user, span_warning("You need a better grip to do that!")) return TRUE @@ -115,7 +117,7 @@ if (prob(30 * ((100-blocked)/100))) affecting_mob.Knockdown(10 SECONDS) - affecting_mob.apply_damage(8, BRUTE, BODY_ZONE_HEAD, blocked) + affecting_mob.apply_damage(30, BRUTE, BODY_ZONE_HEAD, blocked) visible_message(span_danger("[user] slams [affecting_mob]'s face against \the [src]!")) playsound(loc, 'sound/items/trayhit1.ogg', 50, 1) diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index b83bbe0ea985..991792d5cba2 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -127,7 +127,6 @@ if(QDELETED(src)) return - var/turf/falling_from = get_turf(loc) forceMoveWithGroup(destination, z_movement = ZMOVING_VERTICAL) destination.zImpact(src, 1, prev_turf) diff --git a/code/modules/tables/tables_racks.dm b/code/modules/tables/tables_racks.dm index eb1a5dd3da65..ad16f54b11c9 100644 --- a/code/modules/tables/tables_racks.dm +++ b/code/modules/tables/tables_racks.dm @@ -234,8 +234,8 @@ else layer = TABLE_LAYER -/obj/structure/table/attack_grab(mob/living/user, obj/item/hand_item/grab/grab, list/params) - try_place_pulled_onto_table(user, grab.affecting, grab) +/obj/structure/table/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + try_place_pulled_onto_table(user, victim, grab) return TRUE /obj/structure/table/proc/try_place_pulled_onto_table(mob/living/user, atom/movable/target, obj/item/hand_item/grab/grab) @@ -253,10 +253,11 @@ to_chat(user, span_warning("You need a better grip to do that!")) return - if(GRAB_AGGRESSIVE) + if(GRAB_NECK, GRAB_KILL) tablepush(user, pushed_mob) else - tablelimbsmash(user, pushed_mob) + if(grab.target_zone == BODY_ZONE_HEAD) + tablelimbsmash(user, pushed_mob) else pushed_mob.visible_message(span_notice("[user] begins to place [pushed_mob] onto [src]..."), \ span_userdanger("[user] begins to place [pushed_mob] onto [src]...")) @@ -308,16 +309,24 @@ log_combat(user, pushed_mob, "tabled", null, "onto [src]") /obj/structure/table/proc/tablelimbsmash(mob/living/user, mob/living/pushed_mob) - pushed_mob.Knockdown(30) - var/obj/item/bodypart/banged_limb = pushed_mob.get_bodypart(user.zone_selected) || pushed_mob.get_bodypart(BODY_ZONE_HEAD) - banged_limb?.receive_damage(30) + var/obj/item/bodypart/banged_limb = pushed_mob.get_bodypart(BODY_ZONE_HEAD) + if(!banged_limb) + return + + var/blocked = pushed_mob.run_armor_check(BODY_ZONE_HEAD, MELEE) + pushed_mob.apply_damage(30, BRUTE, BODY_ZONE_HEAD, blocked) + if (prob(30 * ((100-blocked)/100))) + pushed_mob.Knockdown(10 SECONDS) + pushed_mob.stamina.adjust(-60) take_damage(50) if(user.mind?.martial_art.smashes_tables && user.mind?.martial_art.can_use(user)) deconstruct(FALSE) - playsound(pushed_mob, 'sound/effects/bang.ogg', 90, TRUE) - pushed_mob.visible_message(span_danger("[user] smashes [pushed_mob]'s [banged_limb.name] against \the [src]!"), - span_userdanger("[user] smashes your [banged_limb.name] against \the [src]")) + + playsound(pushed_mob, 'sound/items/trayhit1.ogg', 70, TRUE) + pushed_mob.visible_message( + span_danger("[user] smashes [pushed_mob]'s [banged_limb.plaintext_zone] against \the [src]!"), + ) log_combat(user, pushed_mob, "head slammed", null, "against [src]") /obj/structure/table/screwdriver_act_secondary(mob/living/user, obj/item/tool) From 40b6c738b1123ff72dc5607a5529ca9cce7127b1 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sun, 1 Oct 2023 18:44:22 -0400 Subject: [PATCH 25/37] update vox test --- ...creenshot_humanoids__datum_species_vox.png | Bin 1387 -> 1370 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_vox.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_vox.png index 896167f6eaf54c8ff7ad1c8aefe5ffe0df563eb9..1917786c90cd2f3bbb5234df3c340441dac13a2b 100644 GIT binary patch delta 1052 zcmV+%1mpYb3fc;gBmugSC9e}yR8+CCv0GSHetv#jTwHHPm7bC6fq&geL_t(og`JiQ zTcbD>h6B~!Y`Daj84CfOD5YB+TRM#Hy8HkC)_ysyon8{^;$SkI1aaifxZ*KS zk_6u#LmZ_7?Ta*uJ)9@k*VhU6aT(o6p@h7Nq$?wqRBsI)F3|(|O`-?Vkpa&6HK&h@ zBSAzrLVlOxCK678;0et^!d;xjLPV*Qw^F8&5DtM5yCJwZQ-4Zf2;x>?2>9>fJAsQ& zvnksN=#(c)3E(7z^5Kg0%NDrFEEy8u}^>*s8|&{A5<$S z&1}YIvpVw#OkLMVz_*8&vkJVLc{no_8enLm@(Jd|=RQGIRWT%xfqQ`rviKNQ-;A9X^W?)L-h2t2!h`DGqczm^q!x?{_w!?H0+1Z z&)Y$C)Z-ZU#^W%Y9Oc<%KMco!{%48X5Ik{V=( z0=mSOFMkRt%?c~zY5jH5i1m8S)$*4$oK6p8zm|$4MKnm}C-tjF0ctKb3*V7}PL&c| zBS|%@BLiP0w)Z5^dVRWdQmD1o8v&A&)uDkWO`}k%RyM&WSg%(K8r3w({yZ9EXbP3k zYSV&Wpt;gQ(KH(Sj;8veC^mCo_W&A8w7Z@$tbg3vZVoncfHE=|yr#+`B}3lot{(hG z=r*VNw6Jy7P?7~}|Eg)TT-!PH3SM7->pW{Qf;6Sp%blQz(lpJpyuJT^`%St9^6mBY ztwj=2qhcq(o0<$IjvQmagW~JK+u#2{kW;GJm2SfrTz+UDP_KQ0TLe;&_;CXc0=MN+ zKY4ZuG@f;&7-zYSN4-8TSZl}DfTG2Hw)I^CYru2TdJJt|V+LzaJq)!H>pE{e^;li8 z>Qx;p-xhlo-;Dw5f5*yMV}sC9PsLTYpdv{Jf43hWe~10OtA&66vj@VBEuobCi~j?h WbboV*tB8jH0000Qz(wlCylmSO*?da^f`~QE|emNk+B`H3hK3v-GJ8zQH!U<#h z;nCraZXMbt9G-JJ`_R2F>fU~QLEkw1p5Li==U*Vd=Op||zi<&Qk>4jm_Y5u_MHoQq z_xqn%!oUvj2AIGVrZ7AMe;BxK(+K%jAdDRPx!cUHy)o3WckR?Y&bjB-gU}majlD3i zJFa=iqbS0=gAfL>!1y8#LWjpgDwNz!ExOcxk!xwu*~9d~+-WI=c?;?MR2MV@VbR$m4gfH^5C&k* zPjvv0FDyEp!Vs!le}$XJ5(+a|=MW-Nu{s5eYnSTy_>d*~ZS*8`0Z@UG zm7(=PHGo3TW^6XA5(hw6RRsgy9eOb7ES+)Vd9IX!tJgRGjRJAq+7ocg@3K&(_(S9AZ z)>J|zGPN1O4am64grYKP^B+z5d7f`-VDH2$WFJSIZg zW@ZMK-y+T67b0s@%8zrCX9xvR(EHbQo1~ekp%d`>`YTJ57AuHj8ok&7@*s}mG)de0 z@3-Hiogm*{U*B4oU=8ve0RPlPC~%}$10EFh+dhN0zyARsC9B@0ZbNHaerO+%pQ#r+ z;1&T3e~ceD;2>~Y9A?Rs9t~hYC8Re000002uVdwM6N<$g66UUb^rhX From c791e6c95788f7554862d4415d275fc01cb57e8a Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sun, 1 Oct 2023 22:12:24 -0400 Subject: [PATCH 26/37] Struggle grab --- code/__DEFINES/combat.dm | 2 +- code/_compile_options.dm | 2 +- code/modules/grab/grab_datum.dm | 35 ++-- code/modules/grab/grab_object.dm | 153 +++++++++--------- code/modules/grab/grabs/grab_aggressive.dm | 26 +-- code/modules/grab/grabs/grab_neck.dm | 26 +-- code/modules/grab/grabs/grab_normal.dm | 10 +- code/modules/grab/grabs/grab_passive.dm | 2 +- code/modules/grab/grabs/grab_strangle.dm | 16 +- code/modules/grab/grabs/grab_struggle.dm | 74 +++++++++ .../mob/living/carbon/carbon_defense.dm | 1 - daedalus.dme | 1 + 12 files changed, 218 insertions(+), 130 deletions(-) create mode 100644 code/modules/grab/grabs/grab_struggle.dm diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 9d3e5d3e8edf..7ea730b5e8ac 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -113,7 +113,7 @@ //the define for visible message range in combat #define SAMETILE_MESSAGE_RANGE 1 -#define COMBAT_MESSAGE_RANGE 3 +#define COMBAT_MESSAGE_RANGE 4 #define DEFAULT_MESSAGE_RANGE 7 //Shove knockdown lengths (deciseconds) diff --git a/code/_compile_options.dm b/code/_compile_options.dm index 17767297fa83..f3564aee3696 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -148,7 +148,7 @@ /////////////////////// MISC PERFORMANCE //uncomment this to load centcom and runtime station and thats it. -#define LOWMEMORYMODE +// #define LOWMEMORYMODE //uncomment to enable the spatial grid debug proc. // #define SPATIAL_GRID_ZLEVEL_STATS diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index c8675a039bf2..e8e67e7416b7 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -123,14 +123,18 @@ GLOBAL_LIST_EMPTY(all_grabstates) special_bodyzone_effects(G) /datum/grab/proc/hit_with_grab(obj/item/hand_item/grab/G, atom/target, params) - if(downgrade_on_action) - G.downgrade() + if(G.is_currently_resolving_hit) + return FALSE - if(!G.check_action_cooldown() || G.is_currently_resolving_hit) - to_chat(G.assailant, span_warning("You must wait before you can do that.")) + if(!COOLDOWN_FINISHED(G, action_cd)) + to_chat(G.assailant, span_warning("You must wait [round(COOLDOWN_TIMELEFT(G, action_cd) * 0.1, 0.1)] seconds before you can perform a grab action.")) return FALSE + if(downgrade_on_action) + G.downgrade() + G.is_currently_resolving_hit = TRUE + var/combat_mode = G.assailant.combat_mode if(params[RIGHT_CLICK]) if(on_hit_disarm(G, target)) @@ -210,7 +214,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) remove_bodyzone_effects(G) if(G.is_grab_unique(src)) - remove_grab_effects(G) + remove_unique_grab_effects(G) update_stage_effects(G, src, TRUE) /// Add effects that apply based on damage_stage here @@ -243,13 +247,14 @@ GLOBAL_LIST_EMPTY(all_grabstates) ADD_TRAIT(G.affecting, TRAIT_IMMOBILIZED, REF(G)) -/// Apply effects to people here. Remove them in remove_grab_effects() -/datum/grab/proc/apply_grab_effects(obj/item/hand_item/grab/G) +/// Apply effects that should only be applied when a grab type is first used on a mob. +/datum/grab/proc/apply_unique_grab_effects(obj/item/hand_item/grab/G) SHOULD_CALL_PARENT(TRUE) if(G.loc != G.assailant.loc && same_tile) G.affecting.move_from_pull(G.assailant, get_turf(G.assailant)) -/datum/grab/proc/remove_grab_effects(obj/item/hand_item/grab/G) +/// Remove effects added by apply_unique_grab_effects() +/datum/grab/proc/remove_unique_grab_effects(obj/item/hand_item/grab/G) SHOULD_CALL_PARENT(TRUE) /// Handles special targeting like eyes and mouth being covered. @@ -288,10 +293,16 @@ GLOBAL_LIST_EMPTY(all_grabstates) return FALSE // Used when you want an effect to happen when the grab enters this state as an upgrade -/datum/grab/proc/enter_as_up(obj/item/hand_item/grab/G) +/// Return TRUE unless the grab state is changing during this proc (for example, calling upgrade()) +/datum/grab/proc/enter_as_up(obj/item/hand_item/grab/G, silent) + SHOULD_CALL_PARENT(TRUE) + return TRUE // Used when you want an effect to happen when the grab enters this state as a downgrade -/datum/grab/proc/enter_as_down(obj/item/hand_item/grab/G) +/// Return TRUE unless the grab state is changing during this proc (for example, calling upgrade()) +/datum/grab/proc/enter_as_down(obj/item/hand_item/grab/G, silent) + SHOULD_CALL_PARENT(TRUE) + return TRUE /datum/grab/proc/item_attack(obj/item/hand_item/grab/G, obj/item) @@ -349,11 +360,11 @@ GLOBAL_LIST_EMPTY(all_grabstates) var/break_chance = break_chance_table[clamp(break_strength, 1, length(break_chance_table))] if(prob(break_chance)) if(can_downgrade_on_resist && !prob((break_chance+100)/2)) - affecting.visible_message(span_danger("[affecting] has loosened [assailant]'s grip!")) + affecting.visible_message(span_danger("[affecting] has loosened [assailant]'s grip!"), vision_distance = COMBAT_MESSAGE_RANGE) G.downgrade() return FALSE else - affecting.visible_message(span_danger("[affecting] has broken free of [assailant]'s grip!")) + affecting.visible_message(span_danger("[affecting] has broken free of [assailant]'s grip!"), vision_distance = COMBAT_MESSAGE_RANGE) let_go(G) return TRUE diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 87ebb3da7551..e832254d1a45 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -10,10 +10,10 @@ /// The grab datum currently being used var/datum/grab/current_grab - /// world.time of the last action - var/last_action - /// world.time of the last upgrade - var/last_upgrade + /// Cooldown for actions + COOLDOWN_DECLARE(action_cd) + /// Cooldown for upgrade times + COOLDOWN_DECLARE(upgrade_cd) /// Indicates if the current grab has special interactions applied to the target organ (eyes and mouth at time of writing) var/special_target_functional = TRUE /// Used to avoid stacking interactions that sleep during /decl/grab/proc/on_hit_foo() (ie. do_after() is used) @@ -22,9 +22,7 @@ var/target_zone /// Used by struggle grab datum to keep track of state. var/done_struggle = FALSE -/* - This section is for overrides of existing procs. -*/ + /obj/item/hand_item/grab/Initialize(mapload, atom/movable/target, datum/grab/grab_type, use_offhand) . = ..() current_grab = GLOB.all_grabstates[grab_type] @@ -38,35 +36,8 @@ return INITIALIZE_HINT_QDEL target_zone = deprecise_zone(assailant.zone_selected) - if(!setup()) - return INITIALIZE_HINT_QDEL - - update_appearance(UPDATE_ICON_STATE) - - var/obj/item/bodypart/BP = get_targeted_bodypart() - if(BP) - name = "[initial(name)] ([BP.plaintext_zone])" - RegisterSignal(affecting, COMSIG_CARBON_REMOVED_LIMB, PROC_REF(on_limb_loss)) - - RegisterSignal(assailant, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) - RegisterSignal(affecting, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) - RegisterSignal(affecting, COMSIG_MOVABLE_PRE_THROW, PROC_REF(target_thrown)) - RegisterSignal(affecting, COMSIG_ATOM_ATTACK_HAND, PROC_REF(intercept_attack_hand)) - - RegisterSignal(assailant, COMSIG_MOB_SELECTED_ZONE_SET, PROC_REF(on_target_change)) - -/obj/item/hand_item/grab/Destroy() - current_grab?.let_go(src) - if(assailant) - assailant.after_grab_release(affecting) - assailant = null - affecting = null - return ..() - -// This will run from Initialize, after can_grab and other checks have succeeded. Must call parent; returning FALSE means failure and qdels the grab. -/obj/item/hand_item/grab/proc/setup() if(!current_grab.setup(src)) - return FALSE + return INITIALIZE_HINT_QDEL assailant.update_pull_hud_icon() @@ -83,6 +54,8 @@ if(C.dna.species.grab_sound) sound = C.dna.species.grab_sound + playsound(affecting.loc, sound, 50, 1, -1) + if(isliving(affecting)) var/mob/living/affecting_mob = affecting for(var/datum/disease/D as anything in assailant.diseases) @@ -93,10 +66,37 @@ if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) assailant.ContactContractDisease(D) - playsound(affecting.loc, sound, 50, 1, -1) - update_appearance() current_grab.update_stage_effects(src, null) - return TRUE + + update_appearance(UPDATE_ICON_STATE) + + var/mob/living/L = get_affecting_mob() + if(L && assailant.combat_mode) + upgrade(TRUE) + + var/obj/item/bodypart/BP = get_targeted_bodypart() + if(BP) + name = "[initial(name)] ([BP.plaintext_zone])" + RegisterSignal(affecting, COMSIG_CARBON_REMOVED_LIMB, PROC_REF(on_limb_loss)) + + RegisterSignal(assailant, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) + RegisterSignal(affecting, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) + RegisterSignal(affecting, COMSIG_MOVABLE_PRE_THROW, PROC_REF(target_thrown)) + RegisterSignal(affecting, COMSIG_ATOM_ATTACK_HAND, PROC_REF(intercept_attack_hand)) + + RegisterSignal(assailant, COMSIG_MOB_SELECTED_ZONE_SET, PROC_REF(on_target_change)) + +/obj/item/hand_item/grab/Destroy() + if(assailant) + assailant.after_grab_release(affecting) + if(affecting) + LAZYREMOVE(affecting.grabbed_by, src) + affecting.update_offsets() + if(affecting && assailant && current_grab) + current_grab.let_go(src) + affecting = null + assailant = null + return ..() /obj/item/hand_item/grab/examine(mob/user) . = ..() @@ -130,16 +130,6 @@ current_grab.hit_with_grab(src, target, params2list(params)) -/obj/item/hand_item/grab/Destroy() - if(affecting) - LAZYREMOVE(affecting.grabbed_by, src) - affecting.update_offsets() - if(affecting && assailant) - current_grab?.let_go(src) - affecting = null - assailant = null - return ..() - /* This section is for newly defined useful procs. */ @@ -205,15 +195,9 @@ return 0 /obj/item/hand_item/grab/proc/action_used() - last_action = world.time + COOLDOWN_START(src, action_cd, current_grab.action_cooldown) leave_forensic_traces() -/obj/item/hand_item/grab/proc/check_action_cooldown() - return (world.time >= last_action + current_grab.action_cooldown) - -/obj/item/hand_item/grab/proc/check_upgrade_cooldown() - return (world.time >= last_upgrade + current_grab.upgrade_cooldown) - /obj/item/hand_item/grab/proc/leave_forensic_traces() if (!affecting) return @@ -226,43 +210,50 @@ affecting.add_fingerprint(assailant) //If no clothing; add fingerprint to mob proper. -/obj/item/hand_item/grab/proc/upgrade(bypass_cooldown = FALSE) - if(!check_upgrade_cooldown() && !bypass_cooldown) - to_chat(assailant, span_warning("It's too soon to upgrade.")) +/obj/item/hand_item/grab/proc/upgrade(bypass_cooldown, silent) + if(!COOLDOWN_FINISHED(src, upgrade_cd) && !bypass_cooldown) + if(!silent) + to_chat(assailant, span_warning("You must wait [round(COOLDOWN_TIMELEFT(src, upgrade_cd) * 0.1, 0.1)] seconds to upgrade.")) return var/datum/grab/upgrab = current_grab.upgrade(src) - if(upgrab) - if(is_grab_unique(current_grab)) - current_grab.remove_grab_effects(src) - var/apply_effects = is_grab_unique(upgrab) + if(!upgrab) + return + + if(is_grab_unique(current_grab)) + current_grab.remove_unique_grab_effects(src) - current_grab = upgrab + current_grab = upgrab - if(apply_effects) - current_grab.apply_grab_effects(src) + COOLDOWN_START(src, upgrade_cd, current_grab.upgrade_cooldown) - last_upgrade = world.time - adjust_position() - update_appearance() - leave_forensic_traces() - current_grab.enter_as_up(src) + adjust_position() + update_appearance() + leave_forensic_traces() + + if(!current_grab.enter_as_up(src, silent)) + return + if(is_grab_unique(current_grab)) + current_grab.apply_unique_grab_effects(src) -/obj/item/hand_item/grab/proc/downgrade() +/obj/item/hand_item/grab/proc/downgrade(silent) var/datum/grab/downgrab = current_grab.downgrade(src) - if(downgrab) - if(is_grab_unique(current_grab)) - current_grab.remove_grab_effects(src) - var/apply_effects = is_grab_unique(downgrab) + if(!downgrab) + return + if(is_grab_unique(current_grab)) + current_grab.remove_unique_grab_effects(src) + + current_grab = downgrab - current_grab = downgrab + if(!current_grab.enter_as_down(src)) + return - if(apply_effects) - current_grab.apply_grab_effects(src) + if(is_grab_unique(current_grab)) + current_grab.apply_unique_grab_effects(src) - current_grab.enter_as_down(src) - adjust_position() - update_appearance() + current_grab.enter_as_down(src, silent) + adjust_position() + update_appearance() /// Used to prevent repeated effect application or early effect removal /obj/item/hand_item/grab/proc/is_grab_unique(datum/grab/grab_datum) diff --git a/code/modules/grab/grabs/grab_aggressive.dm b/code/modules/grab/grabs/grab_aggressive.dm index 45ba0dd1ad6e..6b042a8fdcdd 100644 --- a/code/modules/grab/grabs/grab_aggressive.dm +++ b/code/modules/grab/grabs/grab_aggressive.dm @@ -16,7 +16,7 @@ break_chance_table = list(5, 20, 40, 80, 100) -/datum/grab/normal/aggressive/apply_grab_effects(obj/item/hand_item/grab/G) +/datum/grab/normal/aggressive/apply_unique_grab_effects(obj/item/hand_item/grab/G) . = ..() if(!isliving(G.affecting)) return @@ -27,7 +27,7 @@ if(L.body_position == LYING_DOWN) ADD_TRAIT(L, TRAIT_FLOORED, AGGRESSIVE_GRAB) -/datum/grab/normal/aggressive/remove_grab_effects(obj/item/hand_item/grab/G) +/datum/grab/normal/aggressive/remove_unique_grab_effects(obj/item/hand_item/grab/G) . = ..() UnregisterSignal(G.affecting, COMSIG_LIVING_SET_BODY_POSITION) REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, AGGRESSIVE_GRAB) @@ -54,15 +54,19 @@ to_chat(G.assailant, span_warning("\The [C] is in the way!")) return FALSE -/datum/grab/normal/aggressive/enter_as_up(obj/item/hand_item/grab/G) +/datum/grab/normal/aggressive/enter_as_up(obj/item/hand_item/grab/G, silent) . = ..() - G.assailant.visible_message( - span_danger("[G.assailant] tightens their grip on [G.affecting] (now hands)!"), - blind_message = span_hear("You hear aggressive shuffling.") - ) + if(!silent) + G.assailant.visible_message( + span_danger("[G.assailant] tightens their grip on [G.affecting] (now hands)!"), + blind_message = span_hear("You hear aggressive shuffling."), + vision_distance = COMBAT_MESSAGE_RANGE, + ) -/datum/grab/normal/aggressive/enter_as_down(obj/item/hand_item/grab/G) +/datum/grab/normal/aggressive/enter_as_down(obj/item/hand_item/grab/G, silent) . = ..() - G.assailant.visible_message( - span_danger("[G.assailant] loosens their grip on [G.affecting], allowing them to breathe!"), - ) + if(!silent) + G.assailant.visible_message( + span_danger("[G.assailant] loosens their grip on [G.affecting], allowing them to breathe!"), + vision_distance = COMBAT_MESSAGE_RANGE, + ) diff --git a/code/modules/grab/grabs/grab_neck.dm b/code/modules/grab/grabs/grab_neck.dm index de469b381763..56016beba1cb 100644 --- a/code/modules/grab/grabs/grab_neck.dm +++ b/code/modules/grab/grabs/grab_neck.dm @@ -17,7 +17,7 @@ icon_state = "3" break_chance_table = list(3, 18, 45, 100) -/datum/grab/normal/neck/apply_grab_effects(obj/item/hand_item/grab/G) +/datum/grab/normal/neck/apply_unique_grab_effects(obj/item/hand_item/grab/G) . = ..() if(!isliving(G.affecting)) return @@ -25,19 +25,23 @@ var/mob/living/L = G.affecting ADD_TRAIT(L, TRAIT_FLOORED, NECK_GRAB) -/datum/grab/normal/neck/remove_grab_effects(obj/item/hand_item/grab/G) +/datum/grab/normal/neck/remove_unique_grab_effects(obj/item/hand_item/grab/G) . = ..() REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, NECK_GRAB) -/datum/grab/normal/neck/enter_as_up(obj/item/hand_item/grab/G) +/datum/grab/normal/neck/enter_as_up(obj/item/hand_item/grab/G, silent) . = ..() - G.assailant.visible_message( - span_danger("[G.assailant] places their arm around [G.affecting]'s neck!"), - blind_message = span_hear("You hear aggressive shuffling.") - ) + if(!silent) + G.assailant.visible_message( + span_danger("[G.assailant] places their arm around [G.affecting]'s neck!"), + blind_message = span_hear("You hear aggressive shuffling."), + vision_distance = COMBAT_MESSAGE_RANGE + ) -/datum/grab/normal/neck/enter_as_down(obj/item/hand_item/grab/G) +/datum/grab/normal/neck/enter_as_down(obj/item/hand_item/grab/G, silent) . = ..() - G.assailant.visible_message( - span_danger("[G.assailant] stops strangling [G.affecting]."), - ) + if(!silent) + G.assailant.visible_message( + span_danger("[G.assailant] stops strangling [G.affecting]."), + vision_distance = COMBAT_MESSAGE_RANGE + ) diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index be6bbfbf769a..de6bd6656dfc 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -283,7 +283,7 @@ if(W.hitsound) playsound(affecting.loc, W.hitsound, 50, 1, -1) - G.last_action = world.time + COOLDOWN_START(G, action_cd, action_cooldown) log_combat(user, affecting, "slit throat (grab)") return 1 @@ -305,7 +305,7 @@ if(DOING_INTERACTION(user, "slice tendon")) return FALSE - user.visible_message(span_danger("\The [user] begins to cut \the [affecting]'s [BP.tendon_name] with \the [W]!")) + user.visible_message(span_danger("\The [user] begins to cut \the [affecting]'s [BP.tendon_name] with \the [W]!"), vision_distance = COMBAT_MESSAGE_RANGE) user.changeNext_move(CLICK_CD_MELEE) if(!do_after(user, affecting, 2 SECONDS, DO_PUBLIC, extra_checks = CALLBACK(G, TYPE_PROC_REF(/obj/item/hand_item/grab, is_grabbing), affecting), interaction_key = "slice tendon", display = W)) @@ -318,11 +318,13 @@ if(W.hitsound) playsound(affecting.loc, W.hitsound, 50, 1, -1) - G.last_action = world.time + + COOLDOWN_START(G, action_cd, action_cooldown) + log_combat(user, affecting, "hamstrung (grab)") return TRUE -/datum/grab/normal/enter_as_down(obj/item/hand_item/grab/G) +/datum/grab/normal/enter_as_down(obj/item/hand_item/grab/G, silent) . = ..() G.assailant.visible_message( span_warning("[G.assailant] loosens their grip on [G.affecting]."), diff --git a/code/modules/grab/grabs/grab_passive.dm b/code/modules/grab/grabs/grab_passive.dm index 315d8d59e414..e8735274d5e1 100644 --- a/code/modules/grab/grabs/grab_passive.dm +++ b/code/modules/grab/grabs/grab_passive.dm @@ -1,5 +1,5 @@ /datum/grab/normal/passive - upgrab = /datum/grab/normal/aggressive + upgrab = /datum/grab/normal/struggle shift = 8 stop_move = 0 reverse_facing = 0 diff --git a/code/modules/grab/grabs/grab_strangle.dm b/code/modules/grab/grabs/grab_strangle.dm index 3b8c7db9e1cf..f7d7dd0e6dbe 100644 --- a/code/modules/grab/grabs/grab_strangle.dm +++ b/code/modules/grab/grabs/grab_strangle.dm @@ -16,20 +16,22 @@ icon_state = "3" break_chance_table = list(5, 20, 40, 80, 100) -/datum/grab/normal/kill/apply_grab_effects(obj/item/hand_item/grab/G) +/datum/grab/normal/kill/apply_unique_grab_effects(obj/item/hand_item/grab/G) . = ..() if(!isliving(G.affecting)) return ADD_TRAIT(G.affecting, TRAIT_KILL_GRAB, REF(G)) -/datum/grab/normal/kill/remove_grab_effects(obj/item/hand_item/grab/G) +/datum/grab/normal/kill/remove_unique_grab_effects(obj/item/hand_item/grab/G) . = ..() REMOVE_TRAIT(G.affecting, TRAIT_KILL_GRAB, REF(G)) -/datum/grab/normal/kill/enter_as_up(obj/item/hand_item/grab/G) +/datum/grab/normal/kill/enter_as_up(obj/item/hand_item/grab/G, silent) . = ..() - G.assailant.visible_message( - span_danger("[G.assailant] begins to strangle [G.affecting]!"), - blind_message = span_hear("You hear aggressive shuffling.") - ) + if(!silent) + G.assailant.visible_message( + span_danger("[G.assailant] begins to strangle [G.affecting]!"), + blind_message = span_hear("You hear aggressive shuffling."), + vision_distance = COMBAT_MESSAGE_RANGE + ) diff --git a/code/modules/grab/grabs/grab_struggle.dm b/code/modules/grab/grabs/grab_struggle.dm new file mode 100644 index 000000000000..0123c3944955 --- /dev/null +++ b/code/modules/grab/grabs/grab_struggle.dm @@ -0,0 +1,74 @@ +/datum/grab/normal/struggle + upgrab = /datum/grab/normal/aggressive + downgrab = /datum/grab/normal/passive + shift = 8 + stop_move = TRUE + reverse_facing = FALSE + same_tile = FALSE + breakability = 3 + grab_slowdown = 0.7 + upgrade_cooldown = 2 SECONDS + can_downgrade_on_resist = 0 + icon_state = "reinforce" + break_chance_table = list(5, 20, 30, 80, 100) + +/datum/grab/normal/struggle/enter_as_up(obj/item/hand_item/grab/G, silent) + var/mob/living/affecting = G.get_affecting_mob() + var/mob/living/assailant = G.assailant + if(!affecting) + return TRUE + + if(affecting == assailant) + G.done_struggle = TRUE + G.upgrade(TRUE) + return FALSE + + if(!affecting.can_resist() || !affecting.combat_mode) + affecting.visible_message( + span_danger("\The [affecting] isn't prepared to fight back as [assailant] tightens [assailant.p_their()] grip!"), + vision_distance = COMBAT_MESSAGE_RANGE, + ) + G.done_struggle = TRUE + G.upgrade(TRUE, FALSE) + return FALSE + + affecting.visible_message("[affecting] struggles against [assailant]!", vision_distance = COMBAT_MESSAGE_RANGE) + G.done_struggle = FALSE + addtimer(CALLBACK(G, TYPE_PROC_REF(/obj/item/hand_item/grab, handle_resist)), 1 SECOND) + resolve_struggle(G) + return ..() + +/datum/grab/normal/struggle/proc/resolve_struggle(obj/item/hand_item/grab/G) + set waitfor = FALSE + + var/datum/callback/user_incapacitated_callback = CALLBACK(src, PROC_REF(resolve_struggle_check), G) + if(do_after(G.assailant, G.affecting, upgrade_cooldown, DO_PUBLIC|IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE, extra_checks = user_incapacitated_callback)) + G.done_struggle = TRUE + G.upgrade(TRUE) + else + if(!QDELETED(G)) + G.downgrade() + +/// Callback for the above proc +/datum/grab/normal/struggle/proc/resolve_struggle_check(obj/item/hand_item/grab/G) + if(QDELETED(G)) + return FALSE + return TRUE + +/datum/grab/normal/struggle/can_upgrade(obj/item/hand_item/grab/G) + . = ..() && G.done_struggle + +/datum/grab/normal/struggle/on_hit_disarm(obj/item/hand_item/grab/G, atom/A) + to_chat(G.assailant, span_warning("Your grip isn't strong enough to pin.")) + return FALSE + +/datum/grab/normal/struggle/on_hit_grab(obj/item/hand_item/grab/G, atom/A) + to_chat(G.assailant, span_warning("Your grip isn't strong enough to jointlock.")) + return FALSE + +/datum/grab/normal/struggle/on_hit_harm(obj/item/hand_item/grab/G, atom/A) + to_chat(G.assailant, span_warning("Your grip isn't strong enough to dislocate.")) + return FALSE + +/datum/grab/normal/struggle/resolve_openhand_attack(obj/item/hand_item/grab/G) + return FALSE diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 028ae179c8a2..e9a67de8d042 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -290,7 +290,6 @@ COMBAT_MESSAGE_RANGE, ) log_combat(src, target, "shoved", "knocking them down") - target.free_from_all_grabs() target.release_all_grabs() return diff --git a/daedalus.dme b/daedalus.dme index 24bc3306174a..5fb691b5850d 100644 --- a/daedalus.dme +++ b/daedalus.dme @@ -2993,6 +2993,7 @@ #include "code\modules\grab\grabs\grab_passive.dm" #include "code\modules\grab\grabs\grab_simple.dm" #include "code\modules\grab\grabs\grab_strangle.dm" +#include "code\modules\grab\grabs\grab_struggle.dm" #include "code\modules\holiday\easter.dm" #include "code\modules\holiday\foreign_calendar.dm" #include "code\modules\holiday\holidays.dm" From a69a01ed3891bcae56faf5bc150e4f1d8ec35a8b Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sun, 1 Oct 2023 22:43:22 -0400 Subject: [PATCH 27/37] clean up --- code/modules/grab/grab_object.dm | 1 + .../mob/living/carbon/carbon_defense.dm | 50 ------------------- code/modules/mob/living/living.dm | 2 +- code/modules/surgery/bodyparts/_bodyparts.dm | 2 - 4 files changed, 2 insertions(+), 53 deletions(-) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index e832254d1a45..68fabd274290 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -29,6 +29,7 @@ assailant = loc if(!istype(assailant)) + assailant = null return INITIALIZE_HINT_QDEL affecting = target diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index e9a67de8d042..0a9de6b65671 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -601,53 +601,3 @@ var/obj/item/bodypart/limb = _limb if (!IS_ORGANIC_LIMB(limb)) . += (limb.brute_dam) + (limb.burn_dam) - -/// an abstract item representing you holding your own limb to staunch the bleeding, see [/mob/living/carbon/proc/grabbedby] will probably need to find somewhere else to put this. -/obj/item/hand_item/self_grasp - name = "self-grasp" - desc = "Sometimes all you can do is slow the bleeding." - icon_state = "latexballon" - inhand_icon_state = "nothing" - slowdown = 0.5 - item_flags = DROPDEL | ABSTRACT | NOBLUDGEON | SLOWS_WHILE_IN_HAND | HAND_ITEM - /// The bodypart we're staunching bleeding on, which also has a reference to us in [/obj/item/bodypart/var/grasped_by] - var/obj/item/bodypart/grasped_part - /// The carbon who owns all of this mess - var/mob/living/carbon/user - -/obj/item/hand_item/self_grasp/Destroy() - if(user) - to_chat(user, span_warning("You stop holding onto your[grasped_part ? " [grasped_part.name]" : "self"].")) - UnregisterSignal(user, COMSIG_PARENT_QDELETING) - if(grasped_part) - UnregisterSignal(grasped_part, list(COMSIG_CARBON_REMOVED_LIMB, COMSIG_PARENT_QDELETING)) - grasped_part.grasped_by = null - grasped_part.refresh_bleed_rate() - grasped_part = null - user = null - return ..() - -/// The limb or the whole damn person we were grasping got deleted or dismembered, so we don't care anymore -/obj/item/hand_item/self_grasp/proc/qdel_void() - SIGNAL_HANDLER - qdel(src) - -/// We've already cleared that the bodypart in question is bleeding in [the place we create this][/mob/living/carbon/proc/grabbedby], so set up the connections -/obj/item/hand_item/self_grasp/proc/grasp_limb(obj/item/bodypart/grasping_part) - user = grasping_part.owner - if(!istype(user)) - stack_trace("[src] attempted to try_grasp() with [istype(user, /datum) ? user.type : isnull(user) ? "null" : user] user") - qdel(src) - return - - grasped_part = grasping_part - grasped_part.grasped_by = src - grasped_part.refresh_bleed_rate() - RegisterSignal(user, COMSIG_PARENT_QDELETING, PROC_REF(qdel_void)) - RegisterSignal(grasped_part, list(COMSIG_CARBON_REMOVED_LIMB, COMSIG_PARENT_QDELETING), PROC_REF(qdel_void)) - - user.visible_message(span_danger("[user] grasps at [user.p_their()] [grasped_part.name], trying to stop the bleeding."), span_notice("You grab hold of your [grasped_part.name] tightly."), vision_distance=COMBAT_MESSAGE_RANGE) - playsound(get_turf(src), 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) - return TRUE - -#undef SHAKE_ANIMATION_OFFSET diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 2043364aca16..7ae01ac8374b 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -351,7 +351,7 @@ if(!(flags & IGNORE_GRAB)) for(var/obj/item/hand_item/grab/G in grabbed_by) - if(G.current_grab.restrains) + if(G.current_grab.restrains && !G.assailant == src) return TRUE if(!(flags & IGNORE_STASIS) && IS_IN_HARD_STASIS(src)) diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 55fafa63eb43..2eb559d193fb 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -189,8 +189,6 @@ /// How much generic bleedstacks we have on this bodypart var/generic_bleedstacks - /// If something is currently grasping this bodypart and trying to staunch bleeding (see [/obj/item/hand_item/self_grasp]) - var/obj/item/hand_item/self_grasp/grasped_by /// If something is currently supporting this limb as a splint var/obj/item/splint /// The bandage that may-or-may-not be absorbing our blood From e397df8511139add2ab6b1d955a9bd9b632c3785 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sun, 1 Oct 2023 22:55:59 -0400 Subject: [PATCH 28/37] oops --- code/modules/mob/living/carbon/human/examine.dm | 11 ----------- code/modules/surgery/bodyparts/_bodyparts.dm | 5 ----- 2 files changed, 16 deletions(-) diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 2cfb28654ca9..52fe6b256f98 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -239,17 +239,6 @@ if(-INFINITY to BLOOD_VOLUME_BAD) msg += "[span_deadsay("[t_He] resemble[p_s()] a crushed, empty juice pouch.")]\n" - if(is_bleeding()) - var/list/obj/item/bodypart/grasped_limbs = list() - - for(var/obj/item/bodypart/body_part as anything in bodyparts) - if(body_part.grasped_by) - grasped_limbs += body_part - - for(var/i in grasped_limbs) - var/obj/item/bodypart/grasped_part = i - msg += "[t_He] [t_is] holding [t_his] [grasped_part.name] to slow the bleeding!\n" - if(islist(stun_absorption)) for(var/i in stun_absorption) if(stun_absorption[i]["end_time"] > world.time && stun_absorption[i]["examine_message"]) diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 2eb559d193fb..b86f11ab4e9d 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -1017,9 +1017,6 @@ cached_bleed_rate += round(iter_wound.damage / 40, DAMAGE_PRECISION) bodypart_flags |= BP_BLEEDING - if(!cached_bleed_rate) - QDEL_NULL(grasped_by) - // Our bleed overlay is based directly off bleed_rate, so go aheead and update that would you? if(cached_bleed_rate != old_bleed_rate) update_part_wound_overlay() @@ -1031,8 +1028,6 @@ var/bleed_rate = cached_bleed_rate if(owner.body_position == LYING_DOWN) bleed_rate *= 0.75 - if(grasped_by) - bleed_rate *= 0.7 if(bandage) bleed_rate *= bandage.absorption_rate_modifier From e7b09371ebcfbb0f8dcc40cb42581670d859cb4b Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:16:04 -0400 Subject: [PATCH 29/37] fixes and tweaks --- code/modules/grab/grab_carbon.dm | 1 + code/modules/grab/grab_datum.dm | 16 +++++++++------- code/modules/grab/grabs/grab_aggressive.dm | 2 +- code/modules/grab/grabs/grab_neck.dm | 2 +- code/modules/grab/grabs/grab_normal.dm | 2 -- code/modules/grab/grabs/grab_passive.dm | 2 +- code/modules/grab/grabs/grab_simple.dm | 2 +- code/modules/grab/grabs/grab_strangle.dm | 3 +-- icons/hud/screen1.dmi | Bin 0 -> 167621 bytes icons/hud/screen_gen.dmi | Bin 110775 -> 124483 bytes 10 files changed, 15 insertions(+), 15 deletions(-) create mode 100644 icons/hud/screen1.dmi diff --git a/code/modules/grab/grab_carbon.dm b/code/modules/grab/grab_carbon.dm index aaafa736b094..a81f042b942b 100644 --- a/code/modules/grab/grab_carbon.dm +++ b/code/modules/grab/grab_carbon.dm @@ -3,5 +3,6 @@ for(var/obj/item/hand_item/grab/grab in held_items) . += grab +// We're changing the default grab type here. /mob/living/carbon/make_grab(atom/movable/target, grab_type = /datum/grab/normal/passive) . = ..() diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index e8e67e7416b7..6650b9f99967 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -2,7 +2,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) /datum/grab abstract_type = /datum/grab - var/icon = 'goon/icons/items/grab.dmi' + var/icon = 'icons/hud/screen_gen.dmi' var/icon_state /// The grab that this will upgrade to if it upgrades, null means no upgrade @@ -71,7 +71,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) upgrab = GLOB.all_grabstates[upgrab] if(downgrab) - downgrab = GLOB.all_grabstates[upgrab] + downgrab = GLOB.all_grabstates[downgrab] /// Called by the grab item's setup() proc. May return FALSE to interrupt, otherwise the grab has succeeded. /datum/grab/proc/setup(obj/item/hand_item/grab) @@ -187,11 +187,13 @@ GLOBAL_LIST_EMPTY(all_grabstates) if(!upgrab) return FALSE - if(isliving(G.affecting)) - var/mob/living/L = G.affecting - if(!(L.status_flags & CANPUSH) || HAS_TRAIT(L, TRAIT_PUSHIMMUNE)) - to_chat(G.assailant, span_warning("[src] can't be grabbed more aggressively!")) - return FALSE + var/mob/living/L = G.get_affecting_mob() + if(!isliving(L)) + return FALSE + + if(!(L.status_flags & CANPUSH) || HAS_TRAIT(L, TRAIT_PUSHIMMUNE)) + to_chat(G.assailant, span_warning("[src] can't be grabbed more aggressively!")) + return FALSE if(upgrab.damage_stage >= GRAB_AGGRESSIVE && HAS_TRAIT(G.assailant, TRAIT_PACIFISM)) to_chat(G.assailant, span_warning("You don't want to risk hurting [src]!")) diff --git a/code/modules/grab/grabs/grab_aggressive.dm b/code/modules/grab/grabs/grab_aggressive.dm index 6b042a8fdcdd..6ef2a8f3dc39 100644 --- a/code/modules/grab/grabs/grab_aggressive.dm +++ b/code/modules/grab/grabs/grab_aggressive.dm @@ -12,7 +12,7 @@ can_throw = TRUE enable_violent_interactions = TRUE breakability = 3 - icon_state = "2" + icon_state = "reinforce1" break_chance_table = list(5, 20, 40, 80, 100) diff --git a/code/modules/grab/grabs/grab_neck.dm b/code/modules/grab/grabs/grab_neck.dm index 56016beba1cb..8f1597cb4c39 100644 --- a/code/modules/grab/grabs/grab_neck.dm +++ b/code/modules/grab/grabs/grab_neck.dm @@ -14,7 +14,7 @@ can_throw = TRUE enable_violent_interactions = TRUE restrains = TRUE - icon_state = "3" + icon_state = "kill" break_chance_table = list(3, 18, 45, 100) /datum/grab/normal/neck/apply_unique_grab_effects(obj/item/hand_item/grab/G) diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index de6bd6656dfc..2ba217165af8 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -1,7 +1,5 @@ /datum/grab/normal abstract_type = /datum/grab/normal - icon = 'goon/icons/items/grab.dmi' - icon_state = "1" help_action = "inspect" disarm_action = "pin" diff --git a/code/modules/grab/grabs/grab_passive.dm b/code/modules/grab/grabs/grab_passive.dm index e8735274d5e1..09cca4c4e6b7 100644 --- a/code/modules/grab/grabs/grab_passive.dm +++ b/code/modules/grab/grabs/grab_passive.dm @@ -5,7 +5,7 @@ reverse_facing = 0 point_blank_mult = 1.1 same_tile = 0 - icon_state = "1" + icon_state = "!reinforce" break_chance_table = list(15, 60, 100) /datum/grab/normal/passive/on_hit_disarm(obj/item/hand_item/grab/G, atom/A) diff --git a/code/modules/grab/grabs/grab_simple.dm b/code/modules/grab/grabs/grab_simple.dm index 1e32a62a9b80..147b95eabf0b 100644 --- a/code/modules/grab/grabs/grab_simple.dm +++ b/code/modules/grab/grabs/grab_simple.dm @@ -4,7 +4,7 @@ reverse_facing = FALSE point_blank_mult = 1 same_tile = FALSE - icon_state = "1" + icon_state = "!reinforce" break_chance_table = list(15, 60, 100) /datum/grab/simple/upgrade(obj/item/hand_item/grab/G) diff --git a/code/modules/grab/grabs/grab_strangle.dm b/code/modules/grab/grabs/grab_strangle.dm index f7d7dd0e6dbe..0de3afae24dd 100644 --- a/code/modules/grab/grabs/grab_strangle.dm +++ b/code/modules/grab/grabs/grab_strangle.dm @@ -1,6 +1,5 @@ /datum/grab/normal/kill downgrab = /datum/grab/normal/neck - grab_slowdown = 1.4 shift = 0 @@ -13,7 +12,7 @@ restrains = TRUE downgrade_on_action = TRUE downgrade_on_move = TRUE - icon_state = "3" + icon_state = "kill1" break_chance_table = list(5, 20, 40, 80, 100) /datum/grab/normal/kill/apply_unique_grab_effects(obj/item/hand_item/grab/G) diff --git a/icons/hud/screen1.dmi b/icons/hud/screen1.dmi new file mode 100644 index 0000000000000000000000000000000000000000..7f8db4256da59272124e6ff6c99ad0bd45a64c07 GIT binary patch literal 167621 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 z zl*p|z*Kb~|ReE1I9d24c;CDZDLP3wkzsv9wX9SpTc18Pv=tya;8tkyqG}sbSnT6M*mJrODkYxWW?56zJ{MVecbN%u|_aC*4u%d z-~9Kqx5&yURhd(WrG1!%Zl?rSX|y#M5vtSF?0FZoopoa%B+Zk^1Nru2 zILdS{w#5By9%kLc4;leH!C&7hsz2)QAwWOp%kf%$aP5a2KT!gy{WsQ4U0Tc>s&Q69(m7!Y=!5 z70i>K{9Ng>!hPjuDlsJ$KhvwBg6Z@W$s;~iW2lA-5*t>ehz2U%ghe`i2nDeS-qx)foy zI7Od1?@l{;w9K%Q=5^(8FR-418#TG#PP>g%d_xHNMY6S{-TOjc4?~9@OylLH3pvKt zHn*AC2&~#-S=b1`pgtqEu%6-CC3mG3l+EjGT9WD7MVf)U&zks2|47Hbr*~$hobV;RJ)9&#uaqpgxw$))I&r)IwbCL=)#Z=yzG}knhduTEDrIxb z50U)7Iq{(##F|_A&;pdF<}gmNG!LdrMQXsFnuQB$iMhEp{~({4AN69s{@8X~?d@IM zx)9jcXxY*5jHZf>R@ z)UVC*N&^n{^K|sKov+|)WCvmQ-3$WM`Fn0U^DoXq(7m3r-p03k?-UVP4yHe%#}Irh z|I{b9qJ?;T&+LZ7j$Uf2KUo3qFSnVb=YI5iPqRr}@keHDH*d}|%8#b*BEmG_wKs)v*3))=9=@?Ydp~0-{&U2ujWI_?#eFc z@Gycdog_=Yz79+y#@j<_H$G3L9@|MV_-0s`#A{ajk^hbR-sB0}_%uJM>D(Q~^@O7B z^h4>H7&?302@w{ix<&W&_lGS5mvhTE=XKn}_|D?9-g#q{LifeF(vH8LRPSc!)L*Vf zVu41NPD(4piStOSf+V`iHBBmv6DQg>f7$qMc+o_Mkvlp%(n$K$6MtFQ>SE=3w?akg z>r2D`xWi3=%B=3=zc)E`ACJdL4Ptfqf&&31V%qD?*<;6kQ55V#b2&`uz4r~9Z+Av^;$vhh9Tq`g1wPm$U#8%&+ve)`0N!RPQIyE z01)T7Q#BPa=2?kq%X~+xd*)}0YEVgF {7*PM!+@|!7p$CFX=_3O{Mx!tm#t};40 zlmHz5El^Kd3?}#EVJA?V2-OmSb|3Mz3qOP$t8W| zv0Wm^h!u{C|K4=joLkJJi%`202GRErat`$<1q=YpfuO-FLB%+=(HN;4&jSf(@zq8u zAe>>EKL)8|ec7-+`9zuROyq|`zvmfLId%Ed)^IxYuM5ws<6g#!8s%#Mf!|e_uRwwD zE>ZrhR~UFE3dBi}c#8W1f(OUk50*Ch2nToALRNbQNV=obWxuxV;`84#xQuVL?aUZ`a=1|eJ>}5llE4<6SckwN|mSO_ZG%?t|BYl zw3^Y1zO6?}l4__QeI;&0s`f=#zPK0b{U_Y5PlP!#Ud8LZD%v~F8C!nO#BECF zBwHrPd`#-YX_2)oC$=mpN!%(IcDxVkgRWfXhM)W@QK~(!Z)nu|q`5Jw&&8W~UtDPZ zxxNZMmmPfb?xGggPK6xmRc`KC3S)Lh>$#vcM7L*L(RS)>wYm+zb=7~W`(w>?e`uJ% zO&?vH-#bO`D5(5;Nai|Tl_asgR@2h(2R=e)L3l)+0DswDvLZN@zh;OWt3850r|7bvBnYo& zvcABqN8~#I_j1e5sI71m?M4kxT;b?In>fy@tt8Lbs9|dCMvZ{A&tEe~zk?P5s(dzu zg@P;!4rGUtUAbr99AYwUyAWrWS9x!SS9v}q7KK6=y_@zBfHvQ}*}3e+qo6bs5*wq}w81iyuRLO3-V-76tlq;nX z&qonKz^U!afqZMOGy?xq`KuKmXwTS7AzkY4!tKH+=WJ4$1UEbg)u%eTy-7|_Rxr=) z)z9ZIS?8aA^C08SvK9F$y3R>(28tB9_8I)raQ&CtA7mORwmoziY_8vX`B@-(XWhxc_{FIaw4~n7@`(rt=VZ1VoFQ&pWI;A8} zQ$vNW!8-LqcC_*EHKibj&)i5!V~#e1{5M!&gpwy6d&t3-&U9kycpkKTI1jAzHw&~` zvTfm!&&sIk_^+QztTZu(+fqxRXVIHDAocxiK4J19pQy6pu>!Y>X4#&g?Fq{Qo7iFN zv4w@G%}r|u?>hd`SWpB?zgb_3mM9#Wq}DehLPsXe4^{NdV?OQzlLJL~{QiOuP-0Oa zwVK59(#P)(MaN&{2R-~V)va<8N9>Md1PF2bHRo-ms_{4G>Lj=#HKD{UC+nkTR*aAS zj)ly7w9k5%HfG1Y+2aq%GJ42IU*W}NUubbF*P2>C<~VDlB~9VQeUBV}1L(8|jvzGa zvYX?m10QNz0Z0b-+vZTXb~;lolb}RUYWQpBb6Yu_fe>w5c7%d#3M!Trj`BUYe!)Yw z8fQgbdY0@fkat_pH(}B!L``r!0H(ht$Qttei11_+)2qPT6SJa0MewdU%l_0&{8~4MbPgSx`age|Wwv7pLd6CJR)|LW)K3}3tE#U1ISyM5A zM4-pN9Qma3fMxHuXb+s#Tz?PH?6-GKf7ltOhi49+h~-10Ta9j%8PM)1795#nBAYDl zaw=RiqvwaeS_}tqn8RB8wn|EBE@B=F%{slI!`e|ow`rOzJFdrP1ux~HM?58 z6uFPb=V!~~ShS<*Y)rPxuG0DH7y42JRqEF#tAb`2%-bBj-M`RaXgW+$ni zTdd{3PxFP1Fk1wc&Rg=6Ox?@3Z8L~1jqdLQ)cQ|YrThJ6(qF79uR0%89M1Su%4=9i zu}x{JVr7e9BUx3RU#_2BQ{CjA8JrXuknP?(YZ-f;;rpkd9&_`z1_dlwg9#yHg>T6N z^j#(4U!L8^GdbcVip7`}I8z4LHfWCtOkAX6I>KPsq_wR{dsVNUCre%B7>L5`_NYHU zcb%tIgG7QWy`)lV{8m)n-9A=8yM1U3v-BNpgqWKFY__7fJjNk5GCSdbyf;9ecTM<))VvK7i3l(w?lr`90uH>!_&Qmp&>-dPWl2 z7H$2s;A_PUebT6*x$!nM&`Tp<{893@MFAC##=L+>__`q!#IAAkiK9g;1Qiler&{;M z*?Hy+@r=Y5m-i`2Qb-=D^JUgUfiI@^;j5cYJPgi7x0KA z`6uCZ&+gE?GINr>%-05@&1--FN7EFg>+@9w$;}t0V*sq}Y%i;N+LA`hi+0aUKnX+l zzge>*G@c0yGK-fF<9oX#gZdV{X*R8#p0?dGj_PtFoyPd@gu*dU{Q zm%MG&oD%a?xizQp zN9mQIZ|PKOjLl^KJwN)$k^ZZ+7~cxc1jeh!y8n*ko%_+ezMJ~yjL+qm$X>=!1xo)+ zMnu=wba_LxEm~*G(rGC4-lHJ1tM3k;HSLZo6$jIurZnUO`d)|HU8_1-S{y2A4W>xx z5g(eF;krIg7QmR?3UDq(+)h-bk)+yEMJUMg<%-*j{>`ro1Ud@o$s2{bYi6h2oI?EB zx8h06MW?50PL0;F_sN92Q5n6y)5MQ5YNXj!#su&tVEcx<-uzJU63K0MhQr{u774(s z4w9aiC)$B|V|-oCE5zH;xKPsY=gVP<9hE^t_+!vn5Zmct1DY9(B$byUM3RO8N=zEk zYJj3f9hLYEE2W^#GOn`G#=}bbm%>mp(mo#=Gg1;_E0>gDsYtjdKeO2`{aXt~fUnoM zCQnJ2Q;FNqz&bq}ZT87u=Co`;${WXAE7M%jctjbZuTz*}HbhH+GgxJ;%C{MX_%fRI zOZ1DE?a8+$J>>(zOlW%FFO-m-re4GFm1^qABA5BQoI6TE z^TwKT9ZKJEj!?AU7B9oxK`ZoBl4Fw&OHPJ?+Zw0AetgQy^aXhGduN~gotM6jo(RgA zwtPN0ocHkjt4fkIvnxhp7>jrQ=j=R{snS@Z=E7U7vhc^eOM<;+$h6Mp1AMU^<)z(OY|mXwSu+qV_CT_!*)uO_FZ1% z9IpD+yP)iJlj|RkPD~mc%hFcJLeD- zZ&I0P)VjCFHs`UPCGT5j5d@COM=@GdF@NIuH7rRE&I`X>*WB~jV4unW^WPU&M7De2 zS%*}SS7y@;%PpYcOVUr=6s@P&#AHtK1ttWGR9RK;w)KNPKVyyUUsvoVO1C1JMZ{(@CFx0L?w*&KMpgPrf94vI@p35{74c$JMwdcK zrssZqeOFy%DiK#MI7Ce08%Se2n@SvFEn5&O#hIRo;@jm-CuwcHz0nAhUIw)L?AyDR zJ#Su_&sEN@^uncfB_YIQAjeK1CZmu$G_mGpfS4l@_ zR_ng;_eds)EcjTmlMD=Ov&z5;A-4qyHWQ94P*Sjl6A@Sn(mMWB5u^jXzOO~LTQ&lyb*A3`6c6Q=$}4v2>gqxp z8D&m|J;}#S1r)3#X_^VD#$&$X0y7V@E?TPwj&;i3{cJe~1!53|y<2SBVQV<(a){Wg z^>|quy{KI8=Q7{)V7|wR^|e&}!Qv-J3lXw%=)$?vKy4L0!3?qbunDb0dS!Z7#I%RK z-=OLIKB(v^&lst|J%(xFbgkz+k^mn?W6_VX3VqNURi^ z*k-fkgI7)B_HFN^xP_z8K5vq@2)Ru@zYF_-$QeECs&8v-JUWZ+TEZW@n0U97{!@f^ zs>U59-FBd{EV)|GTfu~XJU$J&9NWwVZ%lMeieJ={> zr`UlDM?Kyb0i=8g<*BWy@qz!}H3aYkbyBMlisQ?R2acPK zO7S@{4jVswO7%%wTay#o+_Wyy#zVaT*13bZlt_u8?1zlBg?4&qfzify{KwFGMs7OW zX!T7N+7A8;{>OKwoheGCUgUcMP&2kgLs9d_lONg-8G!O`S}-N~ivpU}-5y8?p#^YU zGZr)6{V^dUCP*jcyUiglnS8$ii!&0fCk|>n@f-#Bf#BDv!FxQ`2$E)6T{9GAzXJbG zI>s*H(dw9nk>i&(82dEk)h>-Fi*^zj;>H`u;;;b&@I-wQpLtYfF{PhNaS`I((u_SEeA!<5@U z6!e?Xq_DAR6^@?>Kwn z=EcsM8il$f3LeWWwZkYpB7a#7wuSxy2*ZD%8A^s{~%e!XH`O5Y?d(7O1{&(8|2Py(AI1p!1AS1GKaQH4P zGQ>IQij+|cOZMH2lI4b@l-c*Nm1LXuUkcu-0gcz>$a8lta6r%ac%=Mfy-P#jFg|&j z^)tk-rKI|R<@1;d37<6C^J_Lf zZuaCHjjHMwq?hWS)QK*xyJJSss)xk z;C~HXv>dR}(5}7yQ`M+v)i8moGcl49DM=qn5whonU#(>c*;j1bTmafLcXN%e9y#Cx zil{OZ?Qv_?`d!F0Rc(A;43Hu{!gpCgg{;?emuGLHW%M#&>-M&wM%4m@#+`!TI zN4zRgP%6`KS=fkYwTZ-+TtlUZ!%Cn?m2)ggtll>$wHbFDm>R(&}Uypai7d{SgBr{hX=q|cHztIi-5lI9!eih1=j2C%e z$*#&bmWAl9bqqM)d*xpqezTyWT)NQXn3q&});_cMjub>igrZfnC>RadO#MK9jaE?} zT0h;RyavO&FNoJ+O!h+HWeKYA)SJ`2Eo&rjQSTf|Ok8*j%4R^$?By_G^0l6_n6m-V zladmY<=-1!9K+TVjFrmPaM#1oab zS(v|${mdoa1veP$QLZKe%z2R&gRoVUi*B{mqIsHEa_8ikb*I3PD(ewy*Z#GLmjEc+ zK^eEUWxgm9!G~mi>rbG9O~o4`Nw#X>9~j*lqa3Dp-S8Ownst5e3|;7tmu|Bsu{Sfn z{VpyRU;^j-w<7$%&7z~z-d})y`P;q_Z?DYKi3@#=GoPv0an{I1C@9PE6W+j>tvBVk zLu$fBgd#|bUQSmMeJA#gL8PP+AN(HY+YIDXlpZVBVUWu%3bq3x@gDHkvA5IJ6->MB&PBwdLz=MKc_;e`MMy)Kv>+V5dZbApt&}z)k+rc9Jn^}ozqwe_ z+lT|HuQ^@vFikP9V)av}bD`2acW!T7Wpc-RrQDhL1k1S$*|wh7Rjz3f#Qv7#n9St!a> zRaJtiWm=Qr&3e$z3$S7UjIV=07<;uKf<;+4%mUo4MyUa}XR072QF&1iK!Md@JxOD^ zIc=|TEzLt0n`&6XR;2hP25>j3zK%Q>I1R63Rej5nvgs0U0AHgeMP1NqA|;ZX{i^6v zm|12RzpH|Pz+RHZPfa;vGl==&1<)Nu06`?M>uz5jI!&jT*{Lk+mRGUyp{UIGyDmR7 zYfhsVwz2Du50EI)oV<~N(r=^)0QOs@0-DC^2HYwKq9KOM4`;d_im*CdbH+&VG^fQ@ z4&ir|5D*Od@HMBAkZ#HeQ3Uk!YqIc}{Vt{)!s9H{VBmlLgveI1c{`mg15X$VKLKa( z+kJ4b?Eiuc_~M2dG#`(-BZ3Z3w0kXRPw01{6*&#tv=LNdsS+TXeDoAnQ|k>Wk`gSF ze|l3T_l7h<%dnLd-u{$H9WAK$Q0rq)jA9A5`KL7-FXrGh9P{;EU@uiovCLZbl-uBw z+e#|2;zPHZPpZ=odID<}AKUfkazRFY`0l5q#8Ww{4Jgm#*49$(eD``zbr4C!gzO-p z3sB*SA5G(8!j>zGmHm-`l{(Vm@scQSFHF_whW(0Y?2m31ZB}v7)Gc__%PQe&vZz}_ zPEE~u7uu6N?~2p?T*@WJd{<>T{ym+wL{3QYD480{y57Odk*?sMN7ayW+Va}h5`NI& zctc-nfzw{8R|cQ)EFM`9%|$o!|`ngjt*KV(kcH;qnMd zeREEED1_aWD-9Bqd0QO%T!@r}(t-jOhC0e~j_L&SL!}|4VE~R4_x0cGin%$IwC_07 z-F(#o{Ni_d9K)!*4ZD9c918WFhT;AGAalU78MM-akT;X08DR&W@(n8$Der#oNZn=A~zn9Z)DLt$+4NgwbOvArbr-h~XZ z!{ssa7|==?L4SX!lO&BhJ7R_d$?rnbN1DPHE`;3)>GA=O5&!RHh8V~L!T>)FDUnprkJoxRpfg3v=ZqCn5W?ys`YNn`WlzH;2f7$mpW484 zx}{iTQn4LVR^C&{t}$J}lE9I-Eok!)eKxi;Gde7~i)2|nr(QU98PNEt8s-&KZ2Wej z%Pu8vn+_-+Ox&}eFanU#6FL$xWJlOI0)R+)eiZE8w{R$rkJ){b#YE~#c6P*PeMiX@ z2wTpA*VVkxq$xmb63C0-AYZNZKC4t56aK$Swy=P(!YWfp;5NpSah}bAeAUe%xl%1` zrpl-fL4uI#gOh-BRy<4_0Cp`0Y;+1R`{5_C0DM|vzC}#%-+6O*fQM45A+nbZ{AmB2 z9l_MRVk^D$oHcgXl5fg79S@XjBjCv!SNUrIni!s1e)fo5(6RPJ$$Y-7{EU3wDO%b; z9sk;KWv}8iyG;Q5I`#gkO-O~*JF72mu2Ua9lADQ%%mx&D1_2g^61K6k7NJaI378=l z^$~DB@=jYmRjpKFJV9WmLo0JZw=eOuHQ!gpFZ!_0S2i;Wa_)zq?iuhmc3RNFrXF&#+-!K0^B|@;66)% z`UP{NQ}n#UY2G6r;poK2Xwo!dHH(7W(ei7R5H~HscUEk>+^1av(CFysLbWs@ySpTS z>C^RCS+6w%@eRLoJJFFy4djYV7El2OI3D3kChyNO3U4O({XR0fg)Ume*C$7mb5z#a zQ<%<8@-$8zXP0kY5r9#TUs>Ct@85j|Mc3>sT#ITf=ZzJey}fMc)gkvCRqPr?)qT2f z5)$u=p?SMcCG9NRSx{AVEL338ZA%lFLM-)nNQ4k@7g@e^L~=BW%>VS;DTKY+`$T`m zhDp?`;62@wThxTQY@sq|9{5WeHp2Cc6u;RF zVlR2M|I&s=mTqc#P|~Z5BuOjGJPSN#5(}imJSNYdk$u#>(5=f#t&n}#dZbYfFJ`~MaK>9>41 z!|id?B4ppP%IaT__=%r@tRCfT)GT!RMF4|lVlhKt#wl2&!KuJqU>J@{lGQ*D)u2oV zdN}Y*A?8o*tIC^u9cX9Bv@?0>GE6^<36E=%l=dIcUnPf!V1fiPh0Xq>%YdudkG*Y$ z2?BG!)qCJ2QZY;E+ls(2MggXOA_m>}Xi07>n9M`}CwB1vhh{#q4pwuapObV;TOZ8T zj04%`mRd+yThO-N*m7_i9uOxUjvQAAh*f3Z(0<4k?wM%y6z2;APduv%cD7nIS4pHR zkQbs7A7xc*d2C#p@?lD^)Trq^g+$|v4ZunH0H#hb=ve^MuGrC_JS|+7Pd3;TB<~+H zFfgD$kn8Zc#4bVz3M5aZ@muut{#Fg`w+u?d2erT zF-wEmv^gSjLqGjY(W&s^Z=(z^gsZ<=_jod{eg?K!NpP_hq&cxW{j=&f6?Upo0|U>z zb|;JHoI5UCIuY0Bj~(X_4v!ykopdNj3+(C%M*7f<20M5Ln~61z_G=Onmvowu#v%?A zXt6&5LF+(%x_~EJ3brT6+`Bije2lP)Hf5u{Utx1TDRyB(Q2Tpf=SGtkX;;(03BndiNlpdkvPKTP5k2;9gJCXS(vUo%*II0AF9L; zvA6Da+bUje{7fe{It2p8oPci2^1<%HWjU|p=|E$6%BN@4#e9mR$w)bw zzDPJB!rXh^LQ;Q2-!A5XC&K(+gudV;B`IP@Yy=N>Q1S&tEOeE-U5+*RJ#+t(ze zJTT+W$e9lQlnMT%25!Al%iamdZ{F~}i3Vah|2eX5zK(~?Q+KF-@ugonUS;I9NIe^v z8Pbcw*H_RY)qH%f{*QP2%puOwU$Yl}?}|NG{*zC*Bv@l#l}v5Fm|IDDvY16Vq^LEh zUH_G^<2+dm1ZUHzB=1+W3p@KRF={mREH0q2@iB5-#YGpXZyAd)SpgKzp6_V5c}VpxkIP zYujF@VEm!!@S60@-}Y!J(Bq{Q2U);fAWY?{K*5@q;Xw@hc8Lt(78%P;2Q3n|1!_^^dwGH~3ZsI?6I zQ;poW{`h{WoH$H#h@<6TjO49O)5ZnajVBq@$)t*@_|YwZWMiK?fBf5lS#r@`j|#-O~Imn_Vng6sCo zo!|eerkQxDs`LUf*F?de`i3q{;8wQEIM`ujA#qkuBYoDG0|Y6gjW719SA^?28!slC zE%iY9z~K}AjuC6VqB|i>;4W&!+tJ9M>st!xv9f&9|GHHIs(@_>Wayp1_X{oz4oc$7 z$>8^Ce&9U74;p~J^;ZTw95QtK^InK7%}ABfbB;iqDZzjCr3~&S`_{3U4J}otCgQLC zxN@E_L)SBO)J^oCe~|`v3z3->DfZKE1#@q05#jx)eseAhgpy?-ik4WKkl-w3K!Bj| z)*G1HFL9Q8a^{nq2nYlk{yRbCAeZqTdA;7+-%eR}NG_A|mlt8)#)tw3I5+w6!x!`W zO<^h2Uj|2=7wX3@zlB9b(~HSPoShF> zmX?;(4U)vH4E|K7h!Rbhx$SBey1V(~>qN)s4C4SV^ZjM)eySyPJBvv&@R}R1dyrf| zL^mDHwEsI@^JxO-e4ZP0qcNtCKltkw>#k2CCN-6oSm#d<%}zf1C+76fva0L~b9F`X zwt6}!7fw+Ft~*zn#0(qctN$t=MQR1Q(*dCj#NzyPNXn7>PTG1XWeo3!B?Yt1lgkTh zork*Xw8RvRSXx~&|E$(Xs$^7({;zdgq5LT|(<#?$PmlbkPl+YoH#r)u8WvY5e#I28 zS2&z0K-=`Li3^O@>ec>kKNv_qoxl9cXkk9Hg}Ocy&euTWmS8F{O=9C1|JmNd#L7b{ z6SQg5+T+4HC5%`L z)akWbG@<;A0`K7JXZNu~)!S~N*F#TDYK)e31l7C}{P2ys)O!dp7baD^$Vwz;GCGUZ zcYJiM^YAdKDdcn1ZnVhVv9_xf4~qs2!t17m!=M-1Sy+|=RXc_< zc=>SBgZ3Q))(j5KZ+-ZaeM%XmG=g_f1R1Bedv&eRa#>G^o~#Lda`x%fVMV~0mcm%D z&FN5KTFdtYm~f+P@zph$IWaVp@F~zw>!@+e*HXvx!YVYBNiYBZ+`}vg|?- zI_oSvz8u)}vBCX#4UjL}KS;$tp|$s0X4(9O^c8;`OaJ|=e7zMnd$#eD!((;?Z)dp{c_cVkKE7f=!5f95Qr1s2v*KBj0t-(aZ!HwH=9`G<#Y^@1QDP(Z$>n_)`-BMPG9zhKiZS&15058)?WVva1(xLt2g zI>LX66S={llp#w`OSz)7vI^ZiU+_bFb=RqqKCy^hnpix=hzOSk{JNGJYhHRO)P0Cq ze%8i4A7}=8H4r+lD49EX2(HW-SiW9P#4^ZOcw`d!#)WL)d-+Q?(3)n*{9DEkATITr z3Llctqpr!F9Qpvu#c+Y1#9jX@kc$tg%awk%V1T6&y8ugdZ?_Mq-39aw^qrZ3#e74$ z4K>W03(#u=oXu-)W&qBn+!qxU6=GSXAU!6>N|k8DB`+-63=(UMw}5N-Q^5%dU(F*} zjM_`^_Zh3Q0I4D|Anji!n2-n)?1MuwMfodntl8Z>vh4%KUd$OtuE#)85;k?nqpj~+X8#9nqnc6n^s9H|G z+XHM#li%z&&&D}+2?H(noNeQ|7@nA+K6ARt4Jgi*(&J?atMbF7K8VHJr`}0-yCSZV zl&*Gn`^&=P#VR)5`J}YHGved9^qSb?bpUWiUD*36S-jaVWN_I0cJ# zqNB`nl*^!FbfMHIUB00(Wze7Y=u2JK1|O5%$ysuFt;d8?W0T>%z$5z%N#w>xJ>>T6I z4fh3=Yi_MgZ_$E((|`ci{%72*U|w1CvlZ=U!Z5<7dFo1HqbGNMi|NA+` z^V@YYTd0E2Ugt}RW<=I+=S!QR9uF6m*0`&xE2(0;05xg*n|luo@M&rCrp4$yO(g7k zsj(^!u=w!mf2*?kpMKzEw{&>OCd{570E!Ye80ERr2aV#SfH#zm^OeW}qxC;YkXlV& zzJh_)Hv&LU*R)=~3`#cfqXoJ2vn7KxVaz{%{Mg>v!TQaMU2T0xV=8x-T^;8cS)U;D z;=#Zhm8VC4O4=`9Qjm{dza}>xj0(`!1{+B5NNF%l@fsCR26;E%aFMN?U;6HtxD@DL z#}IEGDi2fd(+G^n?kIbrd7JMM(gG@+JK2BOk4=LY1&y3$-%t^%-ote6wexqu`wKad zuv6YAFS#!R_mc0=IT@_Fv#H}gd!ESof7pBPe=h&Oef();H4s@@Ns$UkDLdIIk|^0D z(l8>Cl~E}(B*{*a3W-$qmV_jG@09G#^Lw1{>$*PI?fM_S@890NJfF|=d_3;sIF9>q zK2HDeu(`JzV z{bgRJ)L(wWsMSJmQB5UK%~*8ap1WaJc16Rw z=!=}e!ArUn1*^|2WaZtqWzDPfz0jt7{N_(i{wq2;(Ho>&)d_8DMIxxEv9 z+Gr^%wR#x z^UH*{w#CsKS1(XyE|Rl0$N{e#lEuC)W&P9LnnnO&@?=AS} z9o#y%K*^$QU|1fMKqq-bc<<1Z`MSItEw3TZo4SdA!%G^yY4Y|;-&Z0ZXTRJyBT%?O zGWaV6Uu`w_raqfXDNn+dX zN7Xv_6VL{u{TvZ4s*M*J3TRUU4fjh)PJMr=zDg6bsZT>#$YX!xUQLhl7pV%QYUh72 zj1lYHTP4ehbyV+-6C#g1OqFck&1$B&!{Ns78i%+;KMYS75+&%Z5%H$4> zqFAG+&jjC)c%uuex;^PAFK6fviA43sjTw_t>e~LFU#qjXCd^kO_cRoI;3JWeB*@v}#Vf|Z?O=P!!p2Aa>8?&clk_at42w$=!V`?HyZ{5ex?y8a`|_Fjh& zW%D97Ci#-=)uf4KRBAK0Qz3rm#D}hm(?WDzQt5dfU)#r)K`QrXr!~^&G zy@)5tYxUPlKAdax9N>J__T^%y?A(N@B!>>Ia`TFJ7H5K>y7r4V=g(HBkBV&KOAI=d zT*UbL-w4?N1tQAi1m_FBXGO8Sy>fx=`RV4TClA+jlP_klOn944kxjju7h4!>W_}*O zmvOA4;f6kri2nBg59`(6el|JFz;AT}J{@d*BN?X>% z-AHCt9^vuM2?P5A&iZCvq%Wt|-ECAo6L+xtmjT*sW^SGqwmFvrR92SIeSDO7JW&c)A-G?>1L zwW<64`^j&U_M_K0iw~Bcq8@s=KT@bOLE*TGRKt^QXO)P_sO#lVraL7HWbaCTGq@oc zF(LY+KF50Ntikwyed=v2B4}zZR_qvyu^4IT)t2FuFS+XKPg{#Q6&*F`rC+Bd6<8CL zx6$yEs(Gs}7{ zZ;Q4z|G&j#_sUPU`7S)KqvPJzQ~hpUSS`fVv1GN-_Gz(Z=e>i?*Cz%LHCCQSw*P1@ zFn;78dBmPUn@>^ORjP4Misd)f$Uhett*rNKe;jr8)35<)PP;1J%Ui4VKWC%w-WmRQ zWVbqXvci9E_Z#zZ`9>$x^}XcyQ7Xp z{jkqShUwXO9~OE^*GaWmDFe$HO9Q(JmfthAE~ZN5Et#aa)m-a(gJR!W@8XL2=?H{a z)pMqgmQDFhVxs#f+(VNKQkmPEix`|;g^PEGGYp?)!sKR8y>g#r?67&Ne}1LGcIzwq1#(^d!uetP__^J!A0LtaT%pdCoA(vN6TQzn%9l*{|<DluJ-ES^6 z$TaS}kxy0kWIa0XfGst(aS8cnXsOiYqy65$qim}X2UG`>o1!vY|NZ_`L6()F>3!Os zeDItA|BAaCq9Jbe?0Q;5QvYUemH7mDTGFeH+UH#?o}*sf(*~Kx*y~M~GVv+CmW+KVSb-(JG*>9yCleWGg@^MW>Osu5-U+A!hbJQ8t)HxbM z?dSKuy7C$DuC{nP0J^)*uOeY}l{|3s$gpm4> zqQ>5cQ`Fu8k!I_00+oyh$$t+dt#F9w#tZ!Sn3)l(@PEe4LYWMg`^&z_<=$QSLE9-u z?=`Ym{Qf$P@(R@@a@3UYW9Ujeql2ir2or4gc-^H{5!Y=SJw-Y_ald zg{6Pqc`XSQ!OlFJf+^I#?R~J}|BiFgrL}u66&p|Zg(>?Oxyj0(XUx8P|8{-ptpvvm z`A$* zUgj-3eY0+s&zf(GkJHvS-@U5?qE+5(Qzv~*xYMDhU^iPTEXbmCoIjFEV7oDO#XGUv z`@c#rx^?u}o36d!E8N($XU~O8vfLk!WL7sER6QJ|Tl_}S`@n-N$Vhim+g=KlJ$#rH zW+|?y|3IF+XV0Do9e3MmSg4r~OH&OaAT8OMF!=}65P95$EI@^5mo>Nv^d0METx zu3z~N{r%H>+gtX1AkE#}DAYB&cAeDG$++t`ow{Rq+pOu()as+%loH1*w)*mSonU7A z(RzFTA%=6?cuKTLVo$z2^4FoR*vU$fM2BYu7+5KGd3G0>h;jOMyz_gvd)N9QN&yPt z$37=iN)j!syPJ6XY{eS)kN(;!O7n0}q8d$z!-O0CUk5>w1b;gRiH*X1rEPm?{Cb4CWpA9{I^o`}pn)@XqWyLl&E!vu1sY_|R zPh_s|c!+VjdEaqLr*q|8f+Q=_n#ataw%w!`d~x!3D7`X1Y=IUVqe_T{mhM+szLK0N z+jNffMC3&jO|PBOeJ6f^Mn_~Kw0QDE``4rIb(x|+3sd|RU~*$-I#WzXvg*4j)6Bup z$K+EScOhf^)Ngm8KU;KqmZRq`liqKvrKtrQ1d(LN?gaWgld1jF- zMcXyam)@7mDtJHoBFo7uLp2ad<>Yd8_Zhuj1B!fSHLm@y_qzRL{I}NX#X7m{`jsMm zT>1gSyyrfjroY$QjHSQD$`ix;0FcO<(_62KbCxLIXcs13Z)eliPVpiZrLbt4{wbc8 z>yaP$IU3W`;eVE->QpB}ilmn(2ae1eO4e*e#&IWf##9B8?DDP4DYjg{Ua^bh&0{d3 z*?W%Ke|N~NeKeKC%apAWm4V(BT`p)0dGrVuLs32 z%`xth|CX${6CSL|-hJ#PyK-5(Br|$TX;eF#7HNvcM~K3RIYplI?Q34*_LVY5(mfIf zUvg2xtioo+oj(6h45O=A`=zRP!d{+a#^GZ~qNUi<9aFWdgF%P2>pcCxuW7kK3M<>f>N0!jyFzjtHx7R7$u!B_&{W=~}`)4>?@Qffar2lIx@p zT5+lp!?DwNsMN+7y zttUEd=~OU`aiHsBxMxF``15S%&w90XMyhez=8tt0W4^loZdfZN|8n&kn^oXO>SSFx z&MHV-5ok_&Nl|iUqAQFaUQcQ{@`+W7|5B8)zHj}ni_K#W6VSNQbMLaNOx^2DA<7k@ zUBC3V^~5XEKHK&ms9D?aku%i`3Kol(ZKj{6(oBk+IH;Q-bih-g*qh%*wu7Qt`!*Qpb6s8(5!+72JdNTYC0A2#}Zq9RJ6?nYh${El(n8FcFB z#>KJ4bR}zx4MA%ABJHxAUX5x5`>TiXXWV)DpG|&uskAHNX>cdkFI%ZTIh`B%28#P0 zBy4gx6#2PtFG=m^->;f=I(kO_J-nkk!wfuWzDzq;zJl%$_GMZiabwdhxf4$q)lRGJ zyzuvE#&-I1y`r>z!>^~0W#($^_jzBlLv>xHx8WaO4JV^w?It%~MVXHcC;T)W=4_=% zx#CL#=}_OuHgLbB}G0fCcjH$ex@H&_7po#TK##&O5w-i z>MGynRMRk3;i}y!-*Y^-@ygPvg>Y|TGTByh!{zl|3&qzzm&D>8JO4>2G zyg0jRbGB61rHEEj$okuJ=SAm~R|ZO%TFgwo!jc8^{$E=H8hmw`K2o(kd`^FHvzc_H zNbA<~gJ)?t;uY8A?i!9S^1JRHSQZxWWLthpF}&{s?WdiNPda~wZd?jHo42WC>X)A( zwbe17k4bYX%ukAAq=ye5=}2D)R`i!-(_$U%t-Byz9==M>CVR7gEaK7Z<=>-|%&QWK znf0doX>>JzTWm;n33Nd;!iAPM&X3BF*7BS=Ht0*z-oI7fIblX6d-vJ6t3zho?-$m6 z8H17jsR?(tJ1zyiuD-GKjhec})t%DXW!gh-O%2@AtFhNxHSPnOcudgC@xAmd`zXtt zPAtiW@#s93cRd;CF}6Z^#a^mOtZVikD~0(deh*%CnaI6Acyt8#J{Kvve?Gr%B=#3= z^FdkZ>;>^f!OK^>ov0KkznHnYzzn zIm;el@#2?$TH5`yZT_al85!%qv2r6`(excx==j^y9kS#1)A)Y`x5B$N0cw*5YP1v4|*rE_pjfnf@I6*Me7^%z7H5p zkMln%lzXl1f!X3+bgq;hCVGmpbU&LF0*QqDFz;YVj}5=LKGyrIfcML(`~3;-pPRP! zHRu=0(foA1L~GyPDL791gK=WfUX6qJuDHC9v0|;`?C4mrR2J=qTEVy5e&f_e>a)UQ zllKCZ%beU(+&p{k4DK)9z}PMYgIbe=q9-&Q>A(Mx4|@MYA~MQQfzPdlHuAtCFoy6x z(WV6jh0f$csn~-5T*SfQ^67-vRhOd9cWeY_o|ykDU;Zy*E~SZacG3Q$*nSa_dlt4T z0plMEau5A0oNl>xX;AO1IS(JR=f)|`$U6b;6&r(VcLp=s^W4q_8hiNjKR7ncN>OVi zaXZCLPX3%HO<1j2=;tk6`-|UR6tuF8xu{LKF-NhdF0OEfnJL5eL9~#W{0xQgU^1$h zlJPJ{Vg}2_Gu5v~)q2P2KjdVv%;p??!58k;R_uM)S6Oje?ng_vtGn6sPE&StHoX?5 zTKsB%^_^+{XzxDCAKlHtxI<0J+_as>jbw6g=d=G_sQm8~?af=(KDo(VPLEk9HTJym zxE#9j)xK`T``xdYQ70wo8P~~keD#{tFE%#{@cTRtwx547e3aH_*yEdCt2M>9`n^MG8y?1)WIGpVTqaV%OSY@cJOt1;L@Hhy7Vx`+tVY-}w>K<-(M9i>HF`r`DQSy0Kc08@Sn& zZ+*kK0H3}7me%+sG?1zLucw!EuSVpVF1(PE>hwMFJf6=pPb@>p2RJ))X0gHHp${pG zo7vS?Wz!S%1YG$6PJ@hteq22Js6GrYW*C1II}{W&!Ae0<5$F9R!FeAqn?>Lz_p zyI!ShzSeAN(Dqy5PQUg0=ykVBjsK7ozaVwZmC`vC@`krZ%W0=DX2-at1_fE8#?j)z zvN{1hT9NoW9s1t75}c{BT<6k#-z(NyIR*A*hHZaSeD1|k(r|Iy9=+bYCa<}n|IjZB zGesKzEf3V+3}*kAorL+s|5%2@doC4?Toou*l=&9Bxhi0cDROL({jEY{gM!HOJ7U3+ zN%t#$Ka%%qT5;Z}{LMHuE4DHRI7u+N|KD!>zaHu|tGeOSO2vvQUv%91SE_loXNavV zsZ22P^o{bhekeW$?Lt-T{j#0@!wSJ?Zt5H+UxTf4%3zF zgP%s@PhPsTb7aGNp+{2qx6D=Y#vG00CD$CWtj07IY{*=FQFC z+Ai12>Cc|6DjasTU=w;)VY_|NGgx)gj*H67JD8cRn3bvZs81-fDqsA(>SjLe8DHS> zI$k6}#;l>4PT4*2@y~C(VcvJqif5v`L*3O4Gq=Z-8_NHN=iz;HN}+{{W#|Q zTE~Y#;%D*UH~RZ&8F{TNEPMktvp*bNXfZIIo~aWX93Kyih=^GF(qfmIm-i$;|Cp+p z+Va37v88H-jT0wMP~_M&jAUdM4HT1Ci^DZj0jCdN?~ue%X5Sz0Ifc7{NJ@JB`t^ec z4@yc)DbQrIEvs*1w>Wq1JSiG|SX|ua(WA|to}Tc*b#-+Ki;C_&awO{e_cO^UDd{+^ zw!VI=Un$9_#ZE;aTpWL_nQGDg-nw9Q@(a$t4vvguWxga3ZkB5p^Ym%ox0u65h3-+V zy`Iq-2ASKpZ!en}uA^sQ5IBB3=CI9oahKnEKkbejJ$m$7Z&6%(j!n+xdM+G*SDbyV zU1DQnvDh-#@M59+5u9Awzp=hL<}Cf}*|VoduXC|I3K$u=EFd62S{jPbR@cxtk)$Io zEBoz~SYbt58%JxVX|=7SgrsCeNeRilFg~+r`pCz#5GqbZXQue&KSp2Yr^;gyp zhHq~(I{IAi%wy)|J)@*Vsh<(``LlF#ax#8?K&~V2`cC_TX`0c7gtQkg{1wQ{4MqjG z!%IpO3knMGbjOj!f4!sL8>8OG90#by3Z^JYwA;ANdJ5bIj0f5*47xFJMceVfLf%1B-hsMf&Opa9Pse;Z2xeDrM7CN2Hr$B(j#3hI6P_Ib>=7<7GhdDPWq)X~v#%;h($mzNjb z2@v)4DTlK(4-##1@`i_p2ke3>>3YO+6B)TQwTqsf9v2E9u1!Py9$DGvckfdCXl@R# z8zzm-yPu83txnI*4t|Z=|Ek3~t!--e=%jnKx%2KR9%|Dq!Kwe|JmxT&{q-#*pai-#sEs`jC1WNwaa~PQOWS+z{Rq$F=0PHzvnWpA-KX2?&C^2I!yBN^0K$5NNQ?o z2-DKi(g7Y3JOP%fo1~xN=-^P&)D&J-rHcE$a^uEJxSCvDT@htS{x;c8ExSwNv2mi&DXkM4>gr||7L@onvATbATOT}p zn4Fs%#d-L$nWbg;5VQ&k3aHw5 z7ALL8f27KKEQKvB+`>5!P1neQqKFq6|fJv{Qg$?^#1 z8msrF6g7uK$)xPvl?M=F6|666O$sY z4~Yu^fR>c+G|ces-8*!{WZm?oQ3Y~sidrb$)~%c3+3uoDJB@zld83~pT)-84?{w@@ zW)>Dd>>_030UbUL4l^969T^oheP3aXqR^yaWqty8nDYAdL!56p)uhJP^z)}O;u9hH z;^oWtc;pDlQn~B6q@=QxLNE2lIuadRIa zC1+-e;q!vKcHP~~E}Gan;dtxT-ebp}Uix@rdbUAfaA?RMKgTX+@*+2PhhHhb{Ot5# zHT|}2s_4d7ui_5N>*`LCZumTW$S^Q4F!tvU@lEimV1abf^`0wu^TwU2#!QO1L*$v& z<;%D=2Uk}$k*FW-?HbC;)ZiINkp91a>(S7wQQ~xjS#g<%EvvTRhhbTHIqc3Tz?!E{p&FMX>!+t3k6vq2 zL{oHeaS`0J=MXCTwQJYtwrml^XAl8s=l=Q`03BvWV>x+vD%;yRv5G~jlQO!xy2`G) zB2gBWmM2|ZrEu{`Q5IHK|H4AKOL^CJiB9hOaLjE+@b2Ba&t=^nrID8Fa}Xmh9= zzI#qRp)(`)H7DmV;=064Jj@6a=8gA0E< zNq;(WA3cB0{ZcE&7hjF)ny&X12jVu_W8~ zi+0J$@yN)?kkAi~I}Op{ffdCh9sY3jnz}kFv)&?4D;pb28yl396%~`W`Wa&b*f)kg z!H8NE1#3_O*jP{n%4=%q@K^&1@OCyf)l;W7o0ymk+#$fvjvWE$qP^Z^Ig}No*;ySO zMH3TVz_z5!%urkl_M4NB4^=)QAwk>2L)P5foMQ_OdW4#%r`*4yRZ#@7%3FOF4i4dt zWjx4Yf$T(X4UCSeSzGT>3+1hBXb8pMBsGr%a09@K8s+;KiB{Lv(#L4s(@j?vi6YP* z3QF4-SFx>Ix1tI>a8xof;uaMZMH2V@`J<(wL65@RmG9_BMY}C5GE#8H;QaYL(k#o% z%Sce&3m0mVRUYYNv}kE*(Uo{i^_@VWxN_x6X-mrv6l0Uy{hNNhxme!U#}A~EpP!FY zf;AEGNN*Ho0zlRc%eu`dcZ~^^*1+^dq&C(V8O+CLgXcsG7JKge=_%ZaeFJpbbb*(T zPl%tNggQp_AYR_RXq)lzyhBdMZ{4~D{BrAWMUxHEAlwdoAaG zw-hXo#KNcLG(Dj5{{4G(ZEc%>J%tBM3Z9<4zbzctx5du7?#aPddLr1cLmXQGRj5;% z9_Wdr;(3Ey!+K!onzDe+j?T_Q&Dsi;ot?3AcNPHki4KX4{@eMHg^No>TC2XkzM`T6 zz^(>F5N7}(cv!y0$*=*xZnQ9jm!1V zju4lRQg3F)kAgRZPFJwdaTSaRumBr`#CR4Pt8wPc-UA1M(BrqzP+?=KH*GS{HQa~y z!yG!qjBajZWHdE1Ghj+yVdv%L{r3EL1aLG~sPav5Q4w#dt_E@(@Eb^sP0ZvQxCgk6 zprj<&68qP>I=9uoANL(NfPaVt!2&Ws`#W$U%jatAyItYxPZhl7vC0*;4+7}f(cN* zHlOLeM^yAokl?M!E)CpXUS1v|20bO~!hzfF?x@0k?bk+@rU#MYXQx{1j%cPn1}Vno zW##4$R|K-y<{I+K-u6jL6FPF_2uPi*tn7o};2nwI?Q$)yUabSDd7#VKM;!&vz#AgOYU}C- za~kA#3klt?s8IG=>ZgTElarShjS+$2qKv&2euZ?9J6Sv_V+&ny2u3iO5 zZft3(#3PlPw5=bDL8qbHwr#J3#ADzUcXxL>div5|zn(NTouyDmyijw#hieSDTUU2G znzN;una}Z?BYmiO2uRhG@We!Z@S-&B1Rx|1oOs@pqDH@UYh_OlueY~13OmtXA?mEI zesZ4|foM{4F8OtA>`vkZURDyjqBOH}at3<}@A&4s19GG1*L?k&oShvGE{O;`?mWf> zvJL1nyEJXtn|`qy(BPYGVUc+pmpi$Xw1&4?jjfSqScl%@&}uivz3(;BptVbCxo zY)p$C_S7QCT9S(pi(b8cyhFOpLh2wy z($ZW~Qc?gk%GF}>^1O&5d=zv6Z1$|7p(a9f=h16~+Eg%IwUYz{f@b+0+7BYHvby>{ zf;UKYHyZ2nV~)Q5{?rITVd6z%N1_tERy2aF1_uTJf!`Q?dOSDh{LZ9M6F3Wb{<4PF z2l(rDJlYqb%V8ZfH8pR81=oE0#=5(d-P9xe=ID1+df)#`pn1%GT8{EdSIwU+StOUR?Ff`Q0*7iY7jrz6g*CE^y1jMMw zBW7vo_QGU0Ay9t2(Om|~4iXA<7(gwwnWm18ubuYA=(p5p1*lZGQSvBxjqjalamfAq zw9PM152ht}2}nq62T>L^DX2ksrl+Tug0{ZZuS09uwr$&+i#as-Qi=z}?`Uc=pom@T z%sc&|i+CG?T`mw2wk`-T0Gu7ghQMuBR)V-01jEgVUk5)J9wbApbDMbk@l|GKwV5~q zx(pQ*ma%0j68WTz@L;!J)EgZqB z1nvzER!P+jfBFF3gj_C3*>X|pmnPE_CKaSl$-eyQT+t=N+C z-rfXl`DK6TooHm(k~C0%{On8Zgpl@Ys_A+FiipS{o!p?%&=ehEVKFf^2q(*<35Rcv zXdUYKd~}Z3{^2~6-+Dsj^w6o{WtDsYWOn5rcd-oDmCMk&m{dZt+5II9A8k;t=nMuTe*dj&y(8U3?rEYX7zSZA6wN5C! zKsGGE_5hVcSwhvI%-7e~cN+h(r^juShhhl}iedvuG1*%j8XUaUb+U^&P5`2_yZdJj zsg>pBY@{f{DU?s)G5{_XqzP;f^lfr-(*OQ_7CycPtek(x8niVx)J%PT{N_D?;{M@b6(k;L*3jtaVb2w3L@=~qOXMl| zUt*K(*zeyc<~#snQTq*Q>g)HQr~qXU!ukIFgk+2|jdFwX0(E>VK(4cNoK6E&Ek^8e z2(O%`ySp?rml3R#-ud&)e0*Y&ikhk0(dBW&D62}RBLF6lrYR{Y*hJ(KOvi|p8@)xR z)zoOgQc;1JckX_uq&*tDgU72WgaD^bXSsR2iF+~@5;jz4*> z9+i&U2A%m($Jg50en3q$f@zGO4jns2SJGd8KN~{3ySqS?W_o5O`(SEwnl>7+#YZQ5-EfHDJLh1>FMcX$B*|94xWUnOkQa%CcrT0 zN5Rb3{lp{*jTB>w?~oRoCgzcA#6*KZH@p4XONekl2oO!ck%_6#)n5mQJyuF0$QdMT zJfU>I(gK7w3XgdWAhJ)Ls>>t|5z5ErK0=2a?d4(@BGK{d@srRMur6WY;ibrM#Nmg$ zy!SUp&me8`T_)HNY1i7a?!JC4g57T#cYa!b8Mp`tD>Xa&EVnx-_0aGzU@cN*H|jeS zO1wt(foe)pQBl0x?8x+ZS^}g;fYd7BV<1+Uagc&|QCyEz%j@$6E59?Ktyze35ZW&8;=slC%0Ue%y?zl9ELirowzj_E;Q(k? z&^f{3aJv}Ftj#8n1*N2-QKHb{k=$08`;?Vi;PhZHmiwqO6c0&w0~^#dme+w;aMWXn z2Lylyz=ZIPU6OU5dw}8q4C8R~W=d*m0Ad35Y1-Wi5DG0$bK2u$v#XO zm69+Mz$nOHDFu30QqnFEkB6u;s5ry-`Mk^=9KxLTI`XCe;8|8 zV=*G1z&CoIkLb3Vnh*JZRaI9v_wNqBJx#M7@Hn7&umy`ts!fi7^f zKvgh@5d!pJ$_QZj@^tLIw{H(ZuGKRzIE?8o00JR_qmA|fb`i9GeRYwAlasy#v%A2M z5Vr16yc0_4(^hRS7E3Da0Jv~cl9dBmr9b3j;^i}@E<@98$Y&@9h?v&jaL~N<=}TMEqg}l!Cz4~ zAS6vqPn$y&ghGMH!znXh(Rk~vgQFu%6O@*g@B4%B-lUbc3#SOI^*9z+1{h8U@WLr?eI`G@99?IB>v?g(~LeN&GW7Yi!|9 zdpWeK+k?SU6}OdBb?GhmWaCn zisQR?@Akb9Ih&Yz3Y%65G|-A;M=pSz2tb8iC;j3#L!me)*rL! zfl9o1ObECDQkx2v8YCA+2VXzGO@VCjGunG=6QSywp`5>XA&BAVLBU)gA_9*iHV6TI zd^{;VJrLCZ4f-Ln6*~@WT(q%r)x>lDJRm#(9Lh85Y$aF>LXxdO2tci>s_N;}r)CIo zY%E0w6dKIKEu5WMfd?>OLTLew%z=1>Kjc991X9Aws?ojo$xqy+qNQVGtVR|!D3G6n z3uDRw@(S{I@BaO9AQ3bbfJ}?*3&*_Hj$w$36PA;kY^P>tPht#fZ?6s1PBgsAN@g~; zdzdF&&bj#NBZ?DP4z32M0Z@bppa7*4NMlTi1qB7oVCj$+gN}-LgA4*$hL{L6HJyOE zN)T+Qn8;qDo;viAkog}zemo2yk1K-z0`px55QL`lF&>M1w$meblLw`)7)jB;Bve4@ z|5X9sHNTA6yZLpC-7o}3LehpJm!h8$@G+p)s;s%01(FOv5UT0W$jA&>H)sF~Ls@UH zDeE`H_K7WVf|RPNs@qvvE1)_M2p_5wQTcR)$HvC`Fye$j*o2h8oXP2v5%&vRdX<%g zdG-a2I7a8jn{nA>U!SMZ(Q24utH_b@i3y@ug8OgGDv(+HN`2a|Re>e| z=0`k#u4QX045m%sMc_UF&qt5|K~2k+G)1DY_^&Wo&1}s9kB9nBOhM4-Vx{b%w|zqk zU#_?JD*xE)RR@JgIxb{w%>(*p!2SF8Np}_&7Pf5JLgxx>pouNu3Z_A$L{N-PP7;$Q zqDBKFoIA$?j0|M>I3i+GzYCORVs|lYEH&eomp7@v{P!|?1dx=|@{Bd)Wo%?uF`jp% zhnRLD=kiuu4j!D3LC1i;117~S=dM>1IPXrxGXTns?$0U~76L@~9ZotS|36*;5s`;L zmM?VEr>ceb?K8*B6p4flvAx#hjys|O?y#qj>U_M~Hz~|^``4C<} z3!xuz01E=M5rx4gYfJzWyM!3i6N&?Bq^Xsa4<|jOTb4#grHPX+{!fmat~rBY6nmQ z+%MZU1{F5^fSpiJp~RKI;sS1r9Q%e!i(A-(nI+2l+5GjjwcB%J;rvHB+uDed4h;^) za%p)Pk`ZJLCT_-H)!japKygqDPp0er`1!K}W*=NpSCMBd^zqFG1Na%d)PA3sSXxHL zBLm`HTkTrT&XOljo&=*DgzOF2hpV>Gd!?Vzo^5&mHg9TbsxQ^y@f|EEoREpKv$L_- zKuVUDmOoos*8eXRigR+(ilF`q5jx~}q=sz}sKsP~#5XyC&#UM40VOlLt^ckbLtcoC2- zFp$6?0EG+udkszxXJ=;^gA_sE6xQb=o{mE4xjELj3*euc{-*ABX{e>5rbSwgj@eUE zgkTRbIZXDXk`gpNbEy9)XV?cI^%LoOPJh}C&AH1Vk%zF3YvbD9$xufzlR%PP&WQn< zK^P;!%@E^&gP6P#{vY7|H1OzL!(a?d>~e`><>^_d)2gMpue!P#i%JW4PwWV&Bvy%_ zZp3_W*REZtagM79m>s{`iOC_vRm_wKc!)p2l%}eoff0}pjV~+>BPbp9kaN1aa2!|+ ze~lu59Wh=+6~LlGodIFTjP2N+g##$0TCqn@Uq}gsd0}!=^hQ4V81fWRgCcE?Rs_i^ z31h72Ycm%C`ELI0I0F3ER^%y@Sun#u+N(nZC?0^X*|IVT)kg7E>x5rZdeI>6#jAt>|N_aL*q>SVLss|q> zC7<5tDX7In+rimc5FK}Q@gI>rp`nbJr#_eWdiBU&Qpb3<7B|-hMCs<{h6ppaw4{=C z>Gy!ti`|=BSa6yd(tuP-xZ|K9K(a)R9N6XzxCf+oo4mdV`Ic}jpbr7!g5n__!s6oU zQ975$QbLzN15qro4%84M2sj3KybRaGGMIxFuqRkJ<{}T{BoX^avFUXL%APJ!9&T>X z3*Y8x^0gDym6eXryRk@Tg;`OJ2+9uQBhgLapXe%Zi}?N94mxdjcQ<%22GZXf8=2sj z;)h`cX`!MLz|YFY%E}*63+6R6)|7$)I40;q9{i{jzDQYcTM#GWjoK8EdzTsA3r(h& z3G@gB6ZXulm~HnX&4CdgW`|+%VR8Zq&GJ}w0@`x}_pN+t?iAV7++6V zpLBANn!&?$(aoq5(COgaAVemZg8+7bA%N*f=t$USV$Pd=p`s@msyQh;Z4?xUAJzoe zfCm2k3&q`O;@I8$F48>kej)@3aR-_JJSxPpH?^RhXq`Ms1to=`_kaJM2IfSO^OY>P z_1EwOX?}4LLLx2`V|XReXmau@Ksn*XKoMG6T?Gv(M`eTHY6C5qn7W|Zp^ZYSXEIL7 zz1m7R5h^X16$cRy#IzFvCkr$4u7Tq+GC4Q8r3rHu@H7NNavkJe;`PXAQI;_C5aA7_ z-&~x7piLm8Kti)H@|Yiut+ey6olY?lCGYT&C3H3lj}gp`)~eSgU0-S(B@gWyo}B!S zz6va2g(3!vgd)ejE0SCSyLS^s_SBOD{dhI5QK9>eh+09m^mzn1$ReZwVYYkAtE=$0 zOwZ55_j0N2FrU1~Q#jY9lzgw?ci(@C5YlG&GDS?fkz`o*{9Avy!0SQ9U|oZZID{|8 zz~OOBOf8t6=H<(Kv2g@x1U7skYWxt+3aGi5$os>z0aqah4-a@|EJlP_3FWI-_rq#h z28$hxrod$vbM1EEYLi~RyoXphbo8hyA^|EmVZQ|22Ve5HUL4AQSzOGA+5;jL2rmaB zm2g|ZOnMmpN0QFx9Q^0PukSS#0=H4jqkQ6-;;0p+BdcuL< z6BI%L!NJcGp^Biu0NWuG0olPWd=1~S`FCLt3C$6x4MtA_&dJt+sYX10O!V;OK0ikI z&(;GT)5N=LVPxLa+Z(E%k(!cnGRSXcW=8$gDPl|vC=ap);LW;Y2Qy@O6lg*>f}?|( zxfH^C#g~6Yr%X-xQ0VX%(_i;zB7JcklwpViVw_)-6*2e(01jLO`6wwR#lpjL@Wnew zF1|lymR$h@(5)f;SkI2s!*5l3;+aSB-n*?BbP?)#YO2a@mv8ZMmu`2!yoordK&nln zI>7G8O4=nYZTR%LCk7pa)w6ef(iQRy6Tn&sj|_$&7_B##K8Aos5wV42f-iv8_y{lzq)j-D z%?GQ3l~q-L^!Ac}axTF>6!gFLA^YG*hfnt;W28n*x^XTi)2U(1=z?|AUwr%+jgbQE zrLe$4NNO^~7qYW;V=4>?>Nr@nm9WXZ$%pM1@dowSpDXpv8?6ia<;|Vh5HCOjap_>m z;D-ZM9zatNm0=)H`~3MCq*y`_WK>uy0e3o^(Sq43LR169gmBJSNf55_S#-2-CSb=cD4uRj+(bH3gEeLlB!s!6?1K|Yz6Qc1O8GY;POU(KZ z3YTGi{?o=r9l}U(;Q-Q zMd&CN>G5bOaAg_gJKV*H5!LYo6k*hX?XNL)*@dd13C4nUhS?_tY#`Y?ZNdJ5oe3of z>K+mXg_uxw(SITJSK8&mNKlSm3)+NH99+->c|&LdfPe7G?KyBjEB}q*0XB@A;Yq;t z66bwD+K>aouDD?7>`b`n=&er?(=8d?ci8ji-)d?ekUCJfQW6t=iplH$h@O*c2;x*# zUhW4vHu61gzlRIyMpynW!6KkEpgF<{Rce-qKmbL7v_({L81C?UQ{Aq;#NKaSz6BKq zy%&Ljxj{W5h46!7KVd_IDIc;7FLq~>$Es&Jk1T@ z2>-7INT!vwHAW+qxOdEVST?gj97mtxKIWjf^bog_sGDwk^=e9C;rjpe*NJJUi=Pf* z*n2RCPtINN@?Y?ItpxcvG}K#v+DefyfY!Uf3bo>-&%cu0dp#McPIO)s6~g@l3nLsN z7_0K}slgi>I}BD2&DIJIHry@Ud=Rur^a#)o2&k3ua(8Yc9tkG|rhjk;K%{?wRDy8_ z&;~8Ve*(2o3T`e`A+7+WyD2HV!Mjin;Nuat%7QeE2>>M}C4!B3e>*%ya5ixM~DAUfj2zI$W;gp(D6DtJ7eVT ztdWrxDha9qR9C`qka{W_NO2b+H8J!6P&3IS{CH5^ z{Tt+0gAf;V+qNn~7{lr_$?~9`>Ay45fGx4MwiaO+1w&k5c=OQepYicp0CCtGK$e+y z?mPt%7&KuNPvulvWKZ>#Qo(lP;NoHpM;-dZ!#J3|&SDC)IVKn0UJ(%nuu;t8!PyaE z5{C}$;(h`_%=KSS>W2?=|2In%cz0MEesbD_sRC@d@gS6xR8*VpUVqPYrsp{dG5iwH zmd(3i)x4YbqyqyJIPh7($FiL~p&WlQ5>@wrjclOtv1TgPI(tq8fMbaPpucZi^B+9m z(37DFmSgEK!Qq8S3l9`XzpdmmQ&uZQ=UoUXIO;6!98`q71i>&UD5$Z$o#01ca;Zf{ zCShFY4s@G0pA?C5n*V(sNdn@;+7+&mBMPAoCgLT46A;c}lze!3;RofPalv4%DG5@D zQLz^f^c*M|kX$jz=o=la23H#Y_fJS#`dMk|3Bsaf;=S&YZE3Te14{@iukw{E`%uI2 zT}`d6LW<8@>~{3#V%Bu62SU&RWCY>QfKUrHK=|N67z`XIyN+WV0P@D#VbPLuOIuYa!us*@_?eAU=mnnLdo=DV%Yc@w?fzEfqhK|uSpc~xQ zBUAn57F}WJqS$*Ft*tNs!SoW(dnf{fpq8Popm_N~42BRt0{_ZoVD*yb>_!N;n2HYE z(f+D{9vAS!ZN|98E*H%ZPyzn|%>%$6=Z1@+h!P$%AchZHPeZ(D?y-#kqpv(uN!HV!p#Jwnv-b zyve)K{SY}+`EUo}X?E8X7O*$-%>X7qiSLz%yQC zWrg7%jEs#xIS$gGFj^F0(pClobmf2~N;F7YhW=XtxM=}%Am9d}L||88Q9B`WT0?_* zrlhuf9Hrl}yG*AQ<&;lWc;Mxe2kcUTs4$bn?l9`PS z@wfotAcJrSr_t|;34$O3kVDbf1Skyg5N_mAvcq6k@%P|1ttxngD?-=!hY|1kOs?`V!GbLTk+_NfFAtA{~uTH z9oO^z|Nkc>X$kZ^ zOisr$lQ^x^HAjF>6_AOT9XoR9P~!djKBR(qh*a^xq>U4dinGX!3;MCvye~*CuK@=R zTwk(uX}2yt!JUm9Y!=J#gO94plA5^7tIsICy+JS%&*XZtcBehsYzitZ)T%gOYtje8CGyI633mn(luiK?Cm<6js#Q z?%v@yj6n}9r*46dV`vJhWKWyBdphQydV+ct0s;2B z070dQd3Z*=e;OGEAyg#C1qPx(B;=_i+OJy|Yqs|5$B$By?H}WT=*4X`cvWwQ*Td8% zhjv$L*{s>bnKOMZUFr`8g{Y+OuwmT-)v2T3f_19aYfAW)8bL6}MT_!4phOTz(L#6S zF?W*Q-W8vobOzo6LK0$4h!5@{>Cn^H_asXZfw3XvL=7}$%9Q-wCE!x@%tmwOwBd8| z>zL{4^yRwrlCBjQL7Zp99tWpbT6)sQb4!AqL@oqz#>W=Oks|s0s*jU-W*C`XXF!o}ap&JGmvC@0RGj)ZLHfB3LZ>Ta~{Xk&T6BO@$U(jr1a zi=;R!%fI!|HFOKU{8}w7Eul2p`s~~(#9hERJ*TN){ZT%N8Ml&{=L|#D{+FR+&VY>F z9>fZj;G7L#`>$|Ix0uqcTemuU8M)kg_bw#L7=ij=glH>Pl%J(+LQB77$r2HcoIc$d zm{!q0WOb)S;dMxMpas;Cn?pi6(sYOh0SJRap&1iP*49BH6_Z*|o&oR^5V*)*QE*YZ z0#3v*r@+xPst0#1xgA!D>?O}zC|bZzzLzwpg@up6*F-5nSYkY8@4kIWG$xch%seRb zlFptz%eT69?_L9Et6zK`wXHJ=l3k{QzI0+Lf=|uGU;r%GE!~73;e0gWs*)e{aFZ@I z)G6b)DbJtJ=w%qKSfZ`_q{hJa|G5t1%*|5}AJZ-H`;8eSAuHRUbc09> zVjT7Ox3zzZ137GNfXx8DO3TQYM@>uPFN7q}EckUU zb>v$xaf(CrmMyym>Nv%=qM}30BbdYd`NmUi=+p+SeAkdTtu@@0AWBgUK1t>wQHhKh zbjBze)O$GxoCNTm0!Oet3fNla^FFX-4_r&D&e&xyk?rOohoWYo4j2_-kwF6G4^x`C zFFQUy=7_()YHWY{;JH*Uv~+HWsfE&>R1rik!bRYT3gEycLq)8Mz6U1sqGO(sjnWNJ zI_iMqOeGQ?L@)#p?;jW_ViorZ>*%)gI<)s;!nQsg3?nS%U zo05kVdmBO*=2PpNADW_s_dsFu*tsoi$I6u}i)Sp)imC;f3a-Y1M2yqm-F->|nVn(2 zziCj=<+tY-K59e9L%@xRjXlp4qcjCneWFKzX6EM7Xh?05nK{jK+|VII0DLrC#+sh6 z0Wtt2r#=y0hWA5Itd+ebv~iE_O_)YEELuh`5GsXW%#ZfcIyJS6%tR1e-V_%0w7&$$ zSzcZq#qXz#TJdW`-E_ic@z0-33%N9MSi@f%PVTVN^xcM*L3{P~`;`yKN*^t_I= zCZ(mNGOvdX1f?r)r55zkDXUwCMg<{2F?DW`xDX)t@cY)M^CzMgG1#XZpEW?I`oWU*McS#4X8KA08eq z)68jUyLsotRf4>R>Hu0m3GiS~Nt_}-w{rxhBq}Gm7ncz0h&dzkjiry5!;H15f9?M?{3JSWDLiz>>*iQY% z2Y!BOi8gW8|Ii^t>7V&^w+QCH@C4Bv(U-aly+T*@$$;;72mk_dQL(!_ciqo3|EHaf zj^V!Vt5>Z8($h%Y31}*HvX76CvUBZlm7rJu(*lUJrFjR+2~lV+xUWSI20)+mclKD^ zG}3QgLzV}NP5Z`$lX-KpKa|&FIwv|z1U|qUE9uy{ax};QC2$|cn)Whw$uJ|82kOFD z_&i=Q@j}7V`z~WijCUcO_JjvP3_=V+TcAp5R6N*#IlPJE8#QcbTx7%9+0|ya6w_dr zlm^T@3d{l*P?O|&jZ97k~lB<{A_10vIh zr_B|Kx_nt0LYKEjGL9#90EFmC@PmMow48YW^3o5{vaDc~c5-b+MZs2;+S{Q0<_g6}+|5gO(AZ zPDp1&j(Cm07(qFLcB(YmO7`ao1`gj~kv6=Lxe5ANPNVcBJg55G@5tN$ND6{=J!$~( z1`!gAEyj1QzSwh!TFZDJDfdfGoOMipz-eO-MiiAP4$%VM7Wk94hE`j5+4Ps6(k?HL z_N-=jZru3s&zT-YJum$)rQn?PA3Jiad-dyA89IK#;Vmb|n_{%lo1jj+66(MF&p0%) zsz(I{CzZ$SGwXohzbB-;Oj07oBU7UEN`n^W^l)J4mG&^h%YjGc#$7ov`Lv+O$;oen z>O8gD0Y7@peg}CV1G*5#0KL7mb*)V&PTZ~V_3iujdw1{F@$W!>pr91}mgxy}vVBI3 zm;g6|!$}uv^p{s0L?ptPA#fa$9zpIY6DZt>0N$gXk#o5yS|hzh{RE;+@dC6YswBR( zB(eesf@c@bEYI5QTdyI=&&?@)U~x_SJttFHW|(6h15pT48E%R88q5`%Ar zZ5bR%ru$;Z;P&g+M=3vKRBS|fZtdJf>q8jRqWlxD92s|G{1+<-nWfRyb*Gjm#P{ys zU-MHXxsq=#EFYo=Jdt)EK75~1Xc<_5NC+<5*B;sVRRV`}sY@qURA> zOawg;3p14G1Jp8(1M~F;rvR=VIzU*u3(m#1H*2alL>s+CfF{B(t`uV_YTdjzVX9un zLU;qFP$y$1@_-%^P*|<_)Aq)#ZHdFSC7G8a-~b;9aoEdo@nUbveWZAN6^*Oz1S^DK zGPagn_t4(Gqnw;BgjFzWu!qLL=s2`L^N1JMBo zKjVW@Y(rx(FLII3o4Y8+3apO-aMj2%DkikAm|qW>#?p4W^`~`qDN-Cqj@>+SMfvoQ;S`&H;&)>5(l&3&NL>y0o+6 zSOoV#``xfnqddS^*nAQbKV8evadz|!Q{IYM2lFZZLK$4TBnl-Cquh6Sl|1>KY7q;r zlF29=M3xd)^)k+D%i+u3)S06xg?Uqwkx*@;Ykd0jDaxuzOtDaBpyI)l;3KkOfGHOLSWa!kngMPnlV z=yzckL`zDwqjn(d!Kd`cHWKRS6yPnR7xC^pE`31aMga=*zXeJY-2m(-Q>K;wCy}fv zd8hOm_7tvM>JlhMdM9Fy6QhKjkY7PHzuLl6F`miM8cKY}@`Wre(=_0YYMPqKkgd$8 z@Tn=Rw_Ln<(d^)~@JwEa?Scg!;8H|rBBvF;Iq(g+T_Y}K#>dMbwAaZAZuo;pi#u}l zbp+L5p-p=iqJ0!HmkbKN;ZZx8Fhd5M$g_mIt~JP|ycZ2Flx&|3Kfbt*sZDh|kXL8h z{zGA5D0ztxCcOkzaUl&WxMS7`ztO9GT@`yf~Qp5b$z?D|kg z(>P7SM}|y8%;9|_8t4c;QE&-(sp#vK9ANtdZ_Tdo35?He)QlirMyLuezgohOSgrCqT%=)0`XQf?yAJn?j}$%*#`1*$Dt2+K`t zsg|=Bizs=nyqRr85dj=v6F_3Qn6!4x*$T8tk&nEY0#m%3=!)OHdnYqz6!m=>f21Wp z9~Cv13zOI`ny!!*Jo&isHMQ`}#q@QzXn4e&N_@(!tkC&^Z)<(k<_Z#nKoZD9Dvh@d zXcS8;2f;ki+S(jR1ph%)Ktu>Ge&_Dp7bLhVanvrXE3uQ(RW3dBzO*;ui(qV=OPvou z0Ow7s4$A~T@)mNa-+%$~D4D+USgoz<+af||6$(-=Is&~OS=68SeeH*LUm^ruE3 zl~W+1|9{9Z3J@cmN-s1T=)vIx_$l@HZNx9)qwUhA4f$1#+m09q2vI)!`XIMqpV6!| zDiVb2QCt_MKb)QYY2$VP8PDSmqiQET5(o{uSz5I#Fi^Y@Jx--wy?P02{QECCP=4+5 zxO2(p!l!o0TblM_0rQXyIBI|TO=={A3HgZf&;7~izZ2Y4Ia!UdGkSzkg8uhzRI?lg z0SUO~bT|w~BR|{@Hhcj@oSI9FfS4H>0wt+Cuz;R5_zNN90j3Fz(&Zt02xj1tIWr#P z1Jbg9CIlZYY6ff!KgJ$$FGwXX(3UFX$&;pBw5Iw$c|8=fQoGUOfd;A*VJ%!&qW=}+ zpmwu({I>y2sI-xorSjYUHAm4cc^h%%wfZcTWjD83>;;;#$09H>(Oo2vsJNDg<+zkI z;(7}bjT;22L2io(LbSCB<_*a+&l2U{mMbu&|`aXMopkCd&(q-%aB%9>v zz)76Aem#)84LQJ&g?QniS@iPWr`6)=t5*+zlDbEIwp#D7Xwm7pj-qcsT5D{)_0HhO z7Ae)|HBV-C{QByeHnLlC4$?XfG$sjVlxo~501fEHWW@dFFJIQ{+gHdn(wngO zJ0S<>*wQIbfs5}BC5dRw)M|r-$xJ^?8`Sb>1YVR5VpxStiat0Qd`GEC3Yzt)O0@`% zRJ;`AAQ}&m^9aAev1b%jjjs9T#^1u@lS>IbbD3@@7ZL&DGY;&&+)>1|%%e`AEhI(2 zlOCJerT4&rOnq#Js1kY3oyZsQw;Ht^z_qRUKj~t6y?V8I^SrtEj2+#E51$>?8oV&A zjsih@Z1!$6+&~B;dmLQJ1nGIdIavDPX#^mhhJ-KQw3UfW%N+b$Q9N9&lZ_4xPMsaf)XyBP1qB5a6cO4$Ou+ z7^f#Mlbj(pcl`Jrb6e5YQca9zewXTj{|mWZx9nP0o$gY@dLk9iTSSSAGFCt!)Q*-s zK0=)c^|_l=fhNhfka3W>h@rO`324@$h0*beo)+R4!NgNOcp{HhIqblJibeMJW~6cu zT->ChoZgO+$vtLApeNx+K_d`rYb%;`Uz`n2%F~eXL7@nls2osS+qw1YiMJWxehLx;pP;B7E>NSDDr(ABQyvTlf^UUMPKx+3p@T8w)gbfKT{nH^0^kSr zto1}Blocb1l>GnvO|STPYGE{)qbW5Q+=Z$laZrzQ?wHpzQ9d@n`~!hVX5mqv)$HB$ zj(?MJRror<4NnbbmT-xjw`^IXVGpb^g&Tzqh?0tPAX5F<*Z@>2qV*?7!n=V~h|EBN zhe*p$6M2YfFzZ>0)Nl-VG$FIQ7p&*RAcz$cK`;Y^f41}IZ$+vTSM}qtqbAcA^Z0iv zMCx49G7{MUC>QcCgkk-;R-|9LUvNXQ0E?o=oisA~!UfPS#7%mQUvra3lL3CpYTWm7!P&G9?|Pc=Oy|=sJN5p3jUjHdTEG+x zcO_6c6Tz^cdWZ&2G%FlrP8AQ0GBpA25gb5VIMMk#4ve3od}vIF(TQ|X)YI2drV?F5 z-bDvaru$cjL+t0a$rYsU5k{Y;nSM|}0sf(Xb4F)O2??%>e9#EUfKx_`eE=K`k$NwH z^F`MPCV*^{00b!yq5Ua75iBYx&i;)M>%y{^1EeLPFEep`MMFT*2C)La4B|f@+DTMn zif+@atsjw@k`hv0zMNV56|_j?y4;&*b2c;raM**L2p`pNn*x^s*Ah5}02ktFTHS43jM^@O+w2s_)vhEBy_$Yc3Z9U52ui zJtdTs${~|i{C@qf4w#qq?<~Fz{M)IrvFI<=$H80wCH}8Se zS&p8XhS`=%0eL}DLcO*{_j|@M3wDP{LtM6l_!sR~3KGHQ)*H(-892~>k_py~ZjH|< z_;*aqAc{_pHj9ziRu2XeXNtyOhL~Bu2nV z_yWo0MjWIE=|f#DT2j4Eov^-

KE<^*_TLB2?7*%Tn_(ylEo+8ltS!^|Kl~<8pL# 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 z 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 V@oiN5{~+G>CcXVKmcpwkeh)#5Q#Z{`^DDh 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

(yS|whSza%xpIzvk>1Ki2 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

d43YowL6ooh)y`%LQXFUPYV8DlU&G?5 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 literal 110775 zcmeFZ_dl2Y`#&yJw1iU0$ksritc;MAO)^4>NLEI&+d}qANKx6lvPsIGS=m_$iHvMM z_w%}5uiNeY!|R9dAMm|yx9dvQ^E@BtaopE&JTD)0Rr%dJ=y#BiknC1ekkuq1A(O(t zM<}-8Gdm(l50a3O7rAQdI?9?kT(iGn=Xk@`hJ@r!OheQK+a9^ScbmRuUI|Tot{*na zzMWa~n+lmAWwXq^7j!cA^@es4b&b4lmIfAIvPbY_UQc@YNa|qLU0bHr=TWg9dTXw$ z8x@H|KZd=Rw}})sAL#rtn(DV~HTm4sZ_?yqqdA@7&DP)Y%0XHAyhiyFEQ?YH_iez5%`FAmp`&xAB5|h`?2IoTnEbMGE{!!wOk?>#RQ0z$c^!WHqpTkp zJ~U}fdY|GwP?Q3>DS$?e!Yz6-ic0_GsG*%v8QPT?Y<<)Kj3#T zVRD+ZlRL!U@8d>fBelv^fg*>6ET+-;`tj+m=la_W zcaQvFPLH{$-ADJZ?C<=Nb2)6(q{*K7a(&w8(Jxf=5<_2|SDEq;kE(o={<6nsMCDrW zwo#klbbjrebN+9;O_X=Il`qV0;iva|9sh0bhaHCWJEk>^6qqa7<+=QtxrbDe#I)^M z7eXa?ce9N)Dlh*StiN2W&$m%kCZ8)`bN%b`A(v;AZRR6aGu%a(sRS<840-KdP~m^SRvvvS+WSsff!f6IlQFZ~m@S-3tI=40366_Dj6t9~`CRSTysX zXfW7sc4F6q$y0KzkB{U9zo^!JzGuJS-UOe7-nQIV(^;oVCY~lRQ-ti|mmBeqKTmga zkL>j@s-;Uglm;%!6-gGop*{Jf;R7#j#|%9mIg@dzcjxVxcm?OfoySwYG77!_>u|VJ zEnVU4hrQcx`|jSmJ?T^c@1aTkf~l3eZi(Ln2X54s+!Mc$z#Ous{BHWf(~PG1xImQ< z7AwP`#TymP$FeNbj(xfvRy5JU;$1Gy$HhABdY!D{HXP9&kO~1eF zkLu$q6H{z|His?>a?WIU_df1-duGU-q{p08L;f(;gJ-mJT3s|!X)#8ZQ8-&vJ+$r} zZTkq#AKBAUXJe;zm@TTq-&Zj=EIyF_ot)RJrNJBEL1k&a@6!ys`4LA>TQl3FNWW2& z^_W0|hkLGx#=q%xb`tT@>-8)=Y9rzKY;z}v>DOk3wxb#(Bu7XTWzT8fiTPFHqPy=> z>D_f5zF*C0q&a*IrPF>CmL`Iy{50fqxtfF$qM{3gqN0227wd%<`=d^Tj$V&qe}3q6 zDm|?~C-v!!Q^(}kpX`ih9SVq-(;z?avS@OZEXCr9lYDHOlx@>ehnnA6lXQQm(*I6lx7ET@%xIfcw^1vZXzHSV{~?ybe*WJUZ2P z_iSRRZKiczZP?qoYKN7-&OQG8T4P5dRi=C9rd6X_-%QOih@{(QdND_e7$}8LdpaFc zprE9oATPOI)yPO@FL07W! z{i(>G+&Y|ZVkl{UPt>(e@*meplWpUhU{-!e(sx&zQja>$qAn_q`Q;xvnl>tBp(AR} z+g8-?7hZfTX!v+MJvh5Gj0Ewxrf6-M6|CPHUDhmH5V5AkR8Bxcl$T8~fQA(=`*G1Zuw6 zJ=MZ;J}d0n#chQG-36^Y6U^NURCIxW`$p);W^uXXJ)dXJ&OEt3)OjyW^ep z<=NSe&TR_!x2>*F?ek4~-Tle&CoScKPLyv_kW!qYx=^jQ>?7@$X}0W1VrIwUY0Pb2 zv$iLzJ$%Dg_bymQA-8)80 zaY2pXLmWBRc+S85u+H;t&!gM`)^lzn6y2+C*Hb-?ZEj4LO|};}g)jC;yY#I;Fxqs`;{yjy(^scWuY1-2H)6c(Ap>yRq*T>zeuU*T{cKY;td_ESR|DrS5 z#`Wndvl(gchAjW2M1QhGldph-$(3#!>U%U48|TMumo+AWf=Blhb!$9MJ>8;lci&*} zjROod69>;*=v(+NaaUDaXC4hP%|2FT;uPasHZa5VLa#GP#AV`bCe`irT*ax*6?1jY ziOsFA3R-qj`cT>%88NxIxM*M0Nm)%jc3k7HbAa+Z#{&&7^W}nz>v$r2twkz?{TH1= zIbJJO$L3{tMwFLskSUsL+-uXhn*C_JF+FqfW>oC?^IdI84fDT^$85s7^t98wKI}1^ z+*o!_tZ$4D*^!dPEz?t|jC(jHbP50d=zL2ukH)Ai2K(Ehf}YVEOfv45V#%V%1Ff{9ydpWkiX zDRrnM_~m3RmDyg`dpVrXTATF~SGKtXkUQuH9=(}aT1De1zN}IEX4kU^PUK0a>09-k zEmM={mF{P7Nix>H-8D(k%pjr?zw6jLRl%sk?XAb<%XpefJ;Nov@=R8mknTLz_Q4`N}wc?}LkFPV1)SQr_%@ic--0NM)RvH^h)4#MM87?Wo@xRAsOdI%P!Z+#_f}S zE8-nLO(&=1stnZ>3JuCw&-ku=)^D6ydf3kR;hw~1|7Bcx9!=XdTyi@3g1YBE*W>5J z!X}12Y%XBpxmwVim|ymP&4z1)O!L3&Shpt(3*KL(WVc5m3G@#t?w8%Lg~A& zi}dzgkDhC0hDRpr?RV6kW$T{!#UIMqH;_(COW`@brhGT7fQzK?xu1}G!fdzykNBUz zjd(8H{;j~Im>wM~W;p(E@!~quO~;%{L+Qx2!W+FDHk{{T$2J@jJtt4x@?ASCdi8W{ z)8-$_RNF&a>3>a1ew)|6@5&>&Lp=PYQEi~K8b?wdj$ezCUSV0|U>Z3ID=7zYwal;T z`^HZk=hPonh^lIJ*%K6T`_CEp<rUR@pY*bJ(#<>^NkR+9O|J1sc8YCg$o>PwXZpflN`x{$`mLnS zToWkm%#oBiBLAw0M)2UQz_%w9+F~-NNjdh;Z`t!s&T9UhlFxls2_@|$pTE^XrVC7~ z9~$%@{;~+9=xRPDdd9k{N4F%=fK>3t*Y&ljDWRIf!F2=uo?3n6_q}vHBeoT}I~Rln zJv$PYoIZL>Mtic6a?8F02l6ChPQG$_&ZK_PAh}eYCrfMW@XM8Ap26QL-w#rBPS~v6 zb2R{FZ4tIKP_fgxGg%s~U1mJoFgH3{{31xodBUr-n870aW#g#Hi}sRPZXHn$OD{7>OE?>|q&V+q-t$zK=qE`L}&?N};|=+2zZZ>zC)oM7%bN zyQwo%(q5+&7Rrpd)n>k}R+G=r5*rv~vX)UE?NAlI`^A28I~71X_| z1Envqo|C!gx$Q9ZJxlW=Y((8A)OTvgDg-ld_(g~0zkFk@MXqpYk2&L@M*LadU0u52 z*5cmPl@nu5*#R@$)N#es1s<+LhF1rs<3`5^_HRu^PXg5 zc%N?5#Nr|sIR0?-aeHKqf`Yke_YYEWW245&j|~%f5ph*qX>uXkr`J|jRyH;BFS1ko zWM0eqDL3DQcM>py$oW$2VOVYfG}M&q&2*x9!SC$*AO{ zdtK*5R$6{Sow252x^YzJwd7Ej+GQ4!exA5Zf#vMBn#%`!7WP)7L3)!m8fRPE=i>5a z12S|Ze|Bz0erjMnw|-1=hmarnLc$t%?+Kc=xY-1|T!o;ZxK}mm0aBFY9#2?LD%q2c zzJ2$eJj$Yp!n{N(g;g|GXsa1_%BNGn>j0pa^pc zin}LYt-5(jYm26^mO*NUjKkXxA1ZDtjTQY=`@L_yhVkyNz^~^X^Vt}D<%*q-ZqKZ4 z+Of#0lb^>)p3fzIIOtiyh@VYOApbs_vdZW>!!+aIxQv+1;&A6IG`wY9RL2u7=9o>X z%bUieLSZjABA6dVE2>M@27dBqPbwRgAC#kEA2I*Q-05;$n!!`8 zyb`qxOlG+O3|HR|*vGDSc!-Zt&WYw*X1*UTo3tO1*Q?-aw%;cIeeiHwrmPo}?S(td;d9f$YYNpuVw?B!H%RYE z&0M&a^$$5DB%Hn$-9M4YDy`&5Au8t{Hl`HvFhVt&-%Ej-tD8xtO8c9*>I3VN%;Uj~ zH_uJ*=Jq^}S!geQ@jFE6n3;!gW?~VyOh36t)9x`6y@Yys4d&MV<%rDo;6=@u~*+)J~iOm%Ad z`t)~2nH5}iP0hCAs*~5a7pOWGHaTupXm@^G$f;Ox+4oZ}Iin|KyXbZA$zkqhe@(;V z3(k#uIy*I99)443le%HvH7L7Jp?QE}nR;#Kcd67iZs`Qrcv*@a9joD;VNY#*8II0* ziBh>e&`Hke&m5)`xW7MJgH;k_t=9jfch4t>5|2(9k&GWB$X}_clDr-9lmZt^F(Cfkh?H50N&JqMez$+>V>(ivChL=d7N)EXDBUhnbcg8pTy&%{mTI(Zi6>RJXCbcsqD>TxAS_FBcp{XP-V zt!49}QO%j|!hQ;J(sSE?Sr|8K>lMk@OuUtS7`Dr{WA1kSJKnHv<(qHwQ>~+OY=T%7 zXc%)#yBt%-99m_7ZQP%snK5w}_*(7>Q`+E5P z->yw=eop&-x^@lx=p8EfzT$Y4v~6&=b+C6s)2ls2nxcKR4qY7yYL5+%#(Lcht5#!) zZog{lJD*hX{p^Sh*V%j~-?cOwgOPpRY?6i|n=cvxV7Rdn+?ZYJdDX!mljIAcG>&!W zRSVAD&M);L?{f{-vME$mw`Dt*cX?zVJ`8$7PDxH$zFUJoPfy-FZ19F}HFxH52j?&a z4S}i2?TYUbTROhJ&@C4ozqhd4cJgO)lWyUBDpMxe>*7w=#-N<(g0aF#3YH&JLN0*! zgWaJlj-xKN&(b}U4J1{k9NW@gEXIs?y7Y>$d`f?@(?&UOD}{xjdg!oZ504A`?&^nb z4GXFAk?afHJqCH>pOzPOJY5Yo)q~RnHSTX0($vn4eCo&n<8FKNEZpmAA(Jaq~-f@@h z83&fLRUf}B_DuEYJg&Cu9_?Dl&nS`>dJpx-#-kPjDV`>rLKID|UgcRg{=Fqrwg6>*A9>`BLG6P`4vbW%>dB6m9Pxi1@K zbWZv{IqALck1xNysPpb!)AEgYahH1w#mq}Cazdl0N7sF4xJ_Qm2I>D4R#Q-Xc?52! z@a%Sla}j3w?lIroZ?bA zvX{c9`t8x$v{^}lDc`NJW_^xT7rM2{6O3slKr*5mstru0^HBoEwe>SvEO-N3+ zxp$@dE6*0C3u>X8l7WiVofE>E?|0U)B@c)W)UF-4NvVLZcr6z2M2JRJ6a~Dz5VCUK zla8b(&vtt^hk}cH?i^_L7QJodjbg>{pv=6!d9rE+rS;e*ua_B{S6U3A_yztoDZ%7U zmpu2aDBUkSF1h2M^LyFzuK5^i#vRR;$s?hdoE?I3*}*3)|< zw~-&{AX0cjN=e>*wBKUmr>ErXd(nEysgG-|zda}25fC`7tMR-m*W_v~u262ZnR6OV z%pZ|2$RDZVbkvd&$BhSrp>Q#m7=~@Rf4luN-cZw*(~ zbSxMjEc@|znum8;qp`6*D2>^m?(VEUG#&eE0e*Vhcph_IlC!;>EW?%llXe?(9lguB ziw1Q~zA;5x+Nx+j@LmekGAiQW|N8gCo@-+(wvOv=4jT^?ZAayyku?KAroaSzIUt79M}%mqX0xzYHoEKDBV%$tRAFr$N)M|MKRAhW+<)`BMWUZ;te5+EoqjsbIxoI%=$il2hfhrny}Vv*UTMArF*>E7ZVW#n#CFbuLvlx- zKlwuV8uycb&zjQg&f#}{K41BABbdz$nM2#vLfa>mRwf%+Ia*#=_K2FieiABm(sW#h zF8|^{#)QW96*-MpFgR+H)4WRT3VN$x<;PtlsXANc^+F!lu&||@w14ank-98o*($R< zt`XJg*FCT8>8k!?A^wbbc-)IHmy#qEwb4Y$x_xlXUZ?iewCIWF868%r>3D0^F|CnP ztj8s(&aPBLkystpF_FmMrrP_EX4ehZm~BO3j&!lVebt<^nP%Htle|S4_OzW>-)|Gv zuMg=aK9Vl45oh%Ez?fvSBV9y)wOU~9T2$7&G+z9=`PFg%wfWceji0_Ah(~s`0|exC zgsqN7v+RQkzrFeR&tj64S>3KQT2^jF>NMl&v+k;0Y4VG@RBpMtjGMXa6c0*No33<9 zCM;agY?GAzskJ4CX{BQz#NoK8^IG^=A2sPk`)=8v=Bt^`?gk@Cxl7zlmqvQ~GAGH7 z&VN~^672Z&)qpsTfB!u9pFb6eKQI0Ff{cShVpylX709Tns+PWZIWHpS{F|LaEHc21u+ChGw#rNsiZ&L#=-%5Kf4SH|Sb(Juf>3V78b!hoU_W`l? zW(|w5Wywy9rta$>M2!@^(_fc=pgVf=Xify%)z&`l$l@yJ^M3pGZP~8t z>on>opD~jNi#`h%YBaLzdD#)zifr19!P{|tXvJ?*GD+BosdpuoetX_Vxx{99hB0!G zbgw7IUbmLLZ?DU&uQ-eB{s4r5{J4id)^gd%2?+o9Z@UgXBmr#`UqE59JlK2W-w*Mb zlcsw~+5h`}6pwdF-~I3Rk(LLYqaZ-*-(PZ&pJEGQ>Hqu^nQbgm|M`#uU!=rT%^uz;NnO*mOG|7J1V(~54oT}pS?uwqC=tv`1A9-ev zu#X?5lsRcdV*?fkyyCdx+-dIC)+(8pnA{nE6RDu2WYU{|D@GE&!PfTunz-BF?^9C^ z^P0W{-JlHSvXgH#^d6;yPgP*p(Rf(>Q5-_*BefxTQ%Eg2Jax^qFB;Sgh`7|^%EZ*y%vF%qjF=6$i z<1ty8AAhFIdA;d{%DHn}I5;?B#N8r_rc0yqpB*&vT6fx*@s=dHawa;M-6z+)$^K_E z3$J0}XY0Ja$3~gvQ{C@-Jy)j`LdZWBIE-0XSR6TdaxY1`PF?u%ODCfngvG?z>iAEe zKE1TMI@f(O^UvB`Q$ou-QLhUZcJ_R*u4vVgzj~F$$jAs3sjvUsb)AQ9Czoo>_k=)+ zAwI9=ojE7)t!jT||YGiiwSFU(Y#_>oBIOsi|pUX*tw> z$#~RjbJ0t$)Lq1Rz(dHk_|LY@<+x2BxrD2@(x;4K5y)BQs@Vc@KP{H;Ny5Uy_8va$ zmyo~~!v1F}Z@_*m*7<91?^D&pgp7ENzyMxM)N*Xy5^dJXUwtdgZ zYn8k9FbGS@%93Z6ZHQjIde!v$^&GQ0I-8=YGnWkwv$M0WZmcc$dMx(UKR@j!x$!5k zcx|?BZCuSuI>PLFV@%DnQ`hqC21(1y%YXG1MWP-|vUR_ec}e0YNs5<-{7@*bIwx;f zS(&0J1cihg@UU3f*lt)@yv)c52nyQUe`k323y;5g66^C?OkQiZ`aM^zhlYoba&v#r zZIWOU5TH$Hd7_j0CMoGjZ|~(3bUWX?d1GpBel1(q;>L~gjt)A#h#NLG*Vk7Th)Tl| zN1DHIdF<=EgOrTy)~#DxUgqaN%g+~9jgwHZw&tk{WUi{Hc!2BF)YYy2>_eNad%}HX zJn`uvfo)!1UJKKE8KQ)2nNFQL_4>^lmOB2T+f%G8EL+lbQa2bIqJ;v9#VBR?L>Cw)$MXybv0gpw-ya7)!k_(Z%W<#HSCI*K7PW33N zsa=|EwqOW0i_%I|9ULC6_|=uWoaePF6u=~|WM`MPQZ`$@oq48bV1q8S=5wU6XAK$E z*}Es|PUvQlxVgD;s=ZKFRHTfGiuyHB7Wea~1xdsSy-!`a<}O1%Tv>T}=F|Npzk2cl zk7~W0Sssg<`&dGCGJk5I;z9Sum(QlVZB`TKXs6;VHn(N2JDA$_NZK~M{NR-*e)3&U zSx{Q})aH%2=-bXa9z0Q*yV9}e49^3D%}qMG%1b&rIX7B4zqYpi`SYjT>ddv9H|u^h zL?05eVW9Va{Nu;wX_{QP$I_VGD6G@&)JIy!VjC!sN~v?Xb1U%HfgYgmqylyqsnRZUSziB(wm zAUYoXc~HV#*t{{Oyubei9(vC0DSbc%8k;_Z{Z)$vXqNejkIyTVB8tmVl#zsmg=Her z>Bh>(rDpFVveZhv86L0ClOYPK!~m2H`A z*42t_IOU9t3}I2xp^*{U^XIn~6ci98r4oH+aCG#%v^44U+(TkwVxDV%2MBS((h6><0x6dZgnFHdOq?%m&d zdgi*!9d7|eEMU)zpn z%FfQNe(91oiWznCtl>HxkN)Rtf#Q{KE<_5uFP*#)abj?Fy3G9Z6Y3Z-=Kv5Z4hAFL zrIi&{#gM{S@T#PwB+i9`lJe?Eb@0yx*R}O^f+KuY;oL`-$H0=`Wz z$CMI}Sdy=JhBSWtAJmXtD#-G?|o~e08AIdt}b^#0@^3VUt30o?A*C?#mhgUM}GVu zfFpv}uoka{`q_nF3&9efkU$U{bk*lCUvTu?+}t1g`{I&FJ4%ey932IbV3^}Xu@G_c zX2%Hz1_q!8*r-2+iYcX#VUi&5;_^|`N) zF}EwNe{g1YHXf+h+^kL@|IEzy3MxtM!=H+a&tf!$hXUB%f=VLk?d$uqxX2|b$*dP4 z;yRzy-5e1Xrfy^uAeS&W^u;f~pn#f*DdUW~r>7@zv|!ag!-0}`vHO00JMq|Kgm2x? zEM3`QZf=ei`Bv&7cJU%14HpD5jY`n6cI=9uK=SE2eu063-QUUf?cX0967q@AJQpq` zfZM@?fHNpJHo(&U<2uA`qH$~L>SRLL@hi~}1f+iZrYlIld#ow0swgKL`ukcl&oQ z^wuu@bUKhjN2alwloUy0W8;M3=XdV}Z%=ifmy_EDvink5`54EC7S=7W=K$6~a6*?| z&y9NfmJ^3)ck9+*$zng-@#7at#1dO9va+)W+mf}wWy6z`7j$(G?%A`)eYS?{_3L+M zrUtw=#b3R8<-WPTh_PiZG7LF?`Zp0;y1J0+JBW%vQG*Ls7AEz|JjJ=xUZ9A#<#rK* z8WP`Q^>1)=bX_o;eA~_8-@hBtWl{WQJH&4PIwB@^nB?%`!}Rp@`NIKXxTse-IUx|v z{E&vg=ma@`>33fL2LsCP*=#-}*_}helbY;9MMaeuA5V%xK)1`|N`ixfp;qp$thj>z z4uO1dFnDBtpl_2l4~~zI<4|XQMBADO(i0fdB(Zt|7ojdQ(%#+<)>p^a5e>S$km;pj zXLk~}L4IZM(<3|@LL5LhLb_TA(u2YumX%5R`}^0&NyecjQ33S++e(a@P#0iLs*UWX=eshhfMD8{tQ1tFkT0+=AIq;B0hwY?6(xL^BG+ zIt)tu*44Eo2&abqhl5Z%t|d&0AqT}>1xY}?oG5rOB!sHi&UO+tpX^FeNdzVBtM)(|#5^|^Al29!zr!2j>dH&!MyZ`s+sgib?s#KgqpumA0dI(?H4+Jv2r zjSLP3)B~s_^gn0<{e}X)*_n~-JRjC5<>4WYx(6SkTX}eR>R*UQ!<&YHQyAps<)JFu zDhuDdc?2>Bg~&mwpa`Bah=c&aAm47^z75)d3>Ufm>mbe@v_WFmY3SU7b5u>NsH^jX z|D63ytCj_igC_9)V#0Fb#8ZF+8uA;c9ghdp{(Wq$YOul^rww3-aB8-EUmqh9TE*-Y z17q>QcA&93n5_o&)e?XH?94|p0zNyB2|D~qJ2QHk_*x0yqX=taZ-gQDJz~aIQ((d2?rvGX`926QF%6Im6(KNx`uq{_Xq$sXQ;~yW8~c zK5c#bP$qb1r&-xb^t>cm+j8xRhKNH29@@_+Eqr zilLmaTMxdN5R`;XJv+cC?V&@8kiKANsH+@^V&dDkGrLQnrif4k6tT2)2db#zg}9r7 zs%j;81JpnBE4L}d_#mC%BP0(w=1okswY5(I0^}mhbkgMl*Rr!FloXW#i_f_@Qpn%IY54_?Dx zn#Ud6Iy#6Vn1y~x9$bUKjPIO`5_LLuSG2g|%a^ShhW3lxwz)W z6XRxqx$58tQ&V=rDtRqcFym}Ayk*`0cA2BdffRn_SB`LSZ1wwcA8Po_nKMu#I+;fP zEf(5kc7|~Od3j^C;m0AVufp`fT6+U@;U@05xoPR>$N^O#eb_`q^sKc_^(s3$A|Uso zd}z6dK<4Y$uh6!zT}*Imo}Lo8QLFyqC`1(tBy?kdfJ&#BaaNdOXn40oOlj8Rf zeLziJelxSA5{6-U(s)w|IS|-mZGGB{OH3@fvQiG>1?&$TfdAaTfBzj<*LVQjkRKnM za3T=m!FH;Y-9_Hs+Y{si@pKyFBm)x@xx7|;1`eJwA)gy-;yQZNCnSUh-mNPC*6`BO zaNsRR$Iq>;S|u{ENL5DbB9l{6K2=q1PiUz|bOd@nm)Lr1Vd7$jQHc^Bw6Xi2t!iig zkb}AM3kyRcA`m_$CMQ!u6u{5>rl<3Shlj_AIBZL3QMh<9e1&5bSr8OYIdZDEw`XVP z=JcQoV4Ft9#-Iz{zkjd%T#)j>fdjvO{i?37e_dGk90mBnxR6RkQ~Q}wRtIn0i4!`fqn`XE|94oo`jG>VGz;} zeFp`N%$l%EC=Lk4Q;@l(>wgUpes6!P8e8kl?D+(zh+Btp0?I8-q-N%CZmixy3Q$-` z50$KaAv8gG67 zxCP$&ZikQkix)3WT+R=|g92ZHeUO+M#R})y4oEzvVkTGuzy+e)gKV&L`Nv_v9e{ZZ z{a*j<)B|u99-9dI9iYJ=>iFc-C+Ur)DoGsCr%q!p6^P8kXRN*>qqsg=8*VjF8jGJ` za-TT|w1}(XeQfj(T3Hvg~A8&K|l$)8Ex#9+AE)~KqEiH|*`_kTikx__Gkp>6N z0k+ERs((VwMi|A(%(AyQN;m~j|6`B~A)e$Cf@eq-qT*|o2f^NXKm#+R6!{e#0uD;Un{4{dNxzR}D6d6{W3LJRxLWco#fIaFX zg3q`SMkm9FiUdEluy}(%ipaw(;1~M}oz5FN?niJ;%u|ca|9JR+h@Gr&gK`VK>6Wi%*DJ|jxNy6O*FJT1 zMYSfX5t$~tVpwFPfY*k*)18kntdzTVkM`J>aq#f?1D->lKUac5M!@>5yE`618Zf%{ zcV7_zfDlCx>~=$+DH#|UiO{g2VK;!_jMXUePev&t>4Vx;LECN4Np4oWlJP!x z#_Aw!(`X%E89Og8(J_S3!WX!YAFrsaB;r|Kqv9tB#vvSl%kt~qm!Vqq^z?xJ{I>mP zPmdUDYTie=BAJJAL_-qkt)ygJxPh0qoJ>FE)cmf z^qt#uN!3G2TH+S);sBtbp`o^*`2)u8_i9W{kjws4mb%&Z(U3VgIc-*)FiOY;;kr)J z`^(D7*^g9HhlGYAQhbd}1>9#~Tk-|MFTnnTt3V9A#-(Yh zl^&zvmk0p?xm}NW04OENIZ<3(9OR2S`93=OX2YfvqLDC|AQ~bP{`;#-R-TaQ1Ox*U zu77>4D0Q=H9K#(QFR$%;7(QZ_gPJK_nK+KP71%;pBj`W$K91yE2>S=CE+X-O9zpya z^7QF0Ko}t&V5vrM=nuWU5gF8>RqPxbWHLM9As&HZL7a#;+5WebfBo*=9gg`V)BK}6 zJRAVLIL{Ry$m$?Or7zrmi@>5mdcu*3-1V$rD(AUooLz=fpo8*~X$c2Q2EG;eJ&xuGE zR0%P2{`T!N(0Ax$LfsIceEm8nZU8W`hv{rJ90v+sC)Zm!Mwl8Urjv15(Z+_Cn7K5+ zk|FB!`SXs^5qp8~4^P@5H`d$6drgBdY-b367TqaZkS~Ml}h5$c*+bd>x)lo1wMBmW( zcujwa+aa{QbH580D2N1cI?U$E_BZZb$7oH%6+gfMB_(mAqAP*n%D_xQ=*5bLS9nut zUB0aB>MDvjnS_Y_P=y2@L^t4f{6RAlAute>parA>#Ka^mjmQCsktVV(BDaGMhFNNY zC`3%0zB#X-F8bq6XNDn>d8VWsgT&@OdX$)Mz~Q3oqeL7y@oF(=T|&^o?j9cP&Gr!1 z;FC(oIVeE8KSSq9@Q5FusDsE)K%~jXcgZ^s%KZ=!oCv;fy_icjicJ$njhH_ncSHpB zsi{dVmLw`JF7x_=h|4T{2z&O;PF@5yTQCePdrp6bmsd5GeSCU45WS9UW#YgOM(5sp za2i~l(0n};58J`ipDHRu?#vtkot-fF@D!cp970`>Op+xO7le>ZLdu`huu1Q{4*5|b< zD0cFd@`t}OGwRbR+qQ29eBZ-BkjTy^$_)KIZS-K}s_k{0dS0tPSAaubGSQ0i|6)1qj5EGIp zakn!dWrznH#4FVoJtb~J;0{8R;oy)9$RY}WEI{&0diQF@W8@D+p4`)OSTSS?M%T{S z*}VMzRwTt>mTC;65lfpw|AN4FKosCImoel+zRPw5Qy@!~doI6wMTmGDiv(a*L}@cX z))ZO|@v~=zy*AEDuFuoj*w}y!ozA*nyT*o>o`@B|8~2dFSIJO`zdkdMECLql^qP^L zUJbI6tlZoR3_9_iFG4^;Me=IBRc7Jiqk;iN(cRDrKbxhYJHsrgEm?$_5Vzt07Eir0n#%E=LIPge^;Z1s>QV0V*28Tuf1j6#9;e_}4Jav(LgsBEr!<4$L zFLb)hx5$IgFactQA4P(A57rs)YkeT9h^Mb_s5S^I`vvZbm}CF^nG{gb|Nds=$|7$a zzjL3{0pvVmk{iR4y6-LbqY@@Q|A6Y=!z}R>J%vyXDFei2{#>IN=AqABg^nSDhNxdq zjf0GUss^3?qpBz6aFukJQvepgQlqGi9}w}xh#NZb*6fHP5!r-rTmY|c%Er0|+t=YX`V$A{GFP zd9J%U@!tR63R_>>kdYa`vi~A<@2}p1P#`lx3p`OaLBTMjZONPDh}l0`2~KYt$SS7>x}4~tWhT*9m5WWxR< zD?57P1iOR;#N%Jckw*XoVj6&<7|z&Hw$IJuXtnC*c%pqJzE^c;V=O_3y!@CV8*`Qt}7;y}QKpDgs z{>7akN`zr=RA0AQ2)h6Ia)3eX_EU_mwHaSrJ$bOrC%joS;2R(YW#@p_1*#xlh0GiJ z@uL!9GBJ+_pdgA1x@6??2&N`!I7>!a`dj`jHcXL-oRrvB0$XC+#s$DHjDZ}oLdzu& zOvT8zZq?Azs=#370(36n^H&$A%_rKDF?vOC)-E?2>)a2pDXL&RgRuuju;7g}jGBx$ zS32vX1iV2**c{Qzy0V4p>qxU-rA8L8_4WVG`*0 zb1#Gs)GuZUpKvmm>3(Wy3C8pVQ&c77fy9gr83>jEhOiXEt_U`eE=f&Uo4ODJ4$E|{ zx}YblIbOfAyi5%;2K-fXt&^vh9@Rcew@KPvI=mE#T>$xVwQs*9)d0;oH!&pim0704HH2pljr=@ z0ytGXgX;~?zr0oqB_hhwQYOr+FmWQr4v3z>$a@(X5rAqP&cUV;_%`s1NjY2${^EeG zl0CFi@-WO+Rwn>kOG`_8F;>lZcv(+R(a?|)?=0Vhjs0wOhW6mWY6OD_5ZN(^LWr3Q zO^(=U8Hq44)6LG-FubFh(DMDq4>=7DY7jGS{u!2Ezl@%qH2VxTm! zyh9kiojVIqlKKv1d8f}{M$^{bo}B7;Q;@zfM&toJB3h8x??PcBeG~Cm5ro1eR+6BY zX!Ifo$Hmp#fN>7c1Z;`f3Q93$f!I62GaJUl1@QrfQ@}I_0FcvJC)_IG^g)Bzt_w6Q zx~-wFuY|w=h#(uH-zsu!IPmzfW5O6Yz&Q|uqK6O33Dqu_pwa2An`grqDE{{#nioK5 zx3+Zaw0|nnM4YtMx2${2o=aPhk3w&wLE-^7nABo!@&r-K$9rVZ3Pi{Y8in6s1FOWU zN2A~n3lh?m@cnEej|I@0S(+C6ogQ^p1;5aBQp! z5^PcJ8a#`wJL!_j0Od0qUK0zW#JV4>&qO5Lpa8K4f&d62$EbvCnb@za7idZf zy@eIAYw)u!KVNx6+i;yZ69G!oLiNIb5QhwPg-$|PDK5^4zJNsoWDGqzU~~o8Ju!ym zMZg#nRsq=+VG>#YwRsHoZ|PObWQYjo2-gpfN-EN%Z?|)ul8=+ug!T# zmYVz=b$CHa>2BlS(+!eAMu$f^w=+qJH#zh*dGvKoiy{F?&d88s4SvE_4S0NV`*|pr z8Vv)3G9Cb!5gudaSC>G2Lqm3M?l7X6;yZsx-)PnVjqU(YPC;K+z0yn$D_ftk1uDw) z>H>Hb-e|U2p8oOU2SIsHo=^aJF=YMMo&evtEN^ z5Ha%8P!vUg0h^75Et>Vej~+cj+;qzHFfIvY4J}6&5D7oPRSM3-`rE zYZ@6f#%;`VF`d05g|#lcB8IVe%4m(3>FNH^O)oW*^IhgR6A}`Lyc0KrRC0TLeLcwP z-TU_%1_sBw&vT}nt*WdfDg#;g4>UMZ^1aZKgz&?%Fpfl5Uw_BqQ7kD$DL;n8fULom zCb5f(G#1f(vFpN)H=2sL0_W?@eP1G(7Q$suhp zEDKU#zg*@Si(nb1RPM%&qr`0Z>C^8M6UP{Mks4qcf-~NEfH%vuh6eNu7(T$;^7(y$ zukX;t`YJd!8)*dH3Eht2`Hl0t}bHohi@HD&+8{%2NH#NowsUnjo2iBasL>- zyam<*u#L<`f!(${4u^k)^%(7`;Lc2AwqOP9uz-1uO5J@xgy1=1rwq@yMOyIW$-D5S zXeDH@Lmb!d>xh>(=X6K2Z2!=Er9Ub?dlwf9y>BB==)~lNv z>*O%IhYugOpGB1NEGjBEGO`K@U6hzJ?_nXEk+~+x0}Ev!6k^>7&J8zp7d#Pt#+vo? z>2T<*ztaQ6oEH_2MZs*0Z&BfpBeHKav$5z$tXnND2?N`S#kO`!uPupT3hcXf?K@U!`**Iu0K0^@Oto60MTHz(t6kj?Y;lOPdIa9NsR zL5)`;N(7v~kn{@pcNB#r!5$GJj0RS)&$|763W-gu^bwI7hJqMRKPV?caIh9)`)tQ? z&F9h41#XMH(U9fL`4ry zAA#j2=*1nN0X9K^`&5}Tp`;|12!jZPBDuNT1iykwp8umqTk#JuXTl~KJP`LrDQ2QO zx{*Jf)=btm?cN+(voIa6p@{M3c8WYWDl)|Z$<_$ z4kIZe<2n`zb#-+~PV)1!Zj6LAW=cD+tWAj6}k zjwN;?oPYPS2nz0lmqnwYMp+~zjCNUXLdcqsbar+gz-)t4<@rOb_1D+`I`BJDmUR&8 zsAw)j1$o+_c!TtO9fo+5_=;+WP7E;iL#4eDis6NAH zbW{{eWF3+zRwM+CjjHgML_J`51_K5ihje`>ZV20ECReYXv$Q;R_sIoaJw1(oCou?o z%&n|;0iLN}Sz1`^<=YAii53oe@j?}93a?WBe|pvYYnidZrt;yJUVpZ6uOtKt(pvM< zvJOs6)PDPR5IOmknh*{Ur}W)=Ags^Rr>1})Vpxf1+w$$(6S)Ky%#DC-XfF}0P!Yim z7(n{jYt5tm{rz}ngFObM!8{)DjIvvkFs&~tI*dm`EQC8d3xWP1wdblNH>lC<7%CEb zznHm!g|NB`VkKr{KrL)Ys2~M}umngve*9QB+hi+bG=s3+-NC`D|8AgMH8p*R!^*f^ zfH#_z$YV1bqTsWI;4E^J;OEb4v3f)-`(kx~fby0W4Tw0*(q5&c_~I|bim!=De07V` z8%jX6B3FhVZ)E6KX`llYEUL#q%x5zmV3Wi9*!6|_BR}i$Yq2SuP%A7Jd`UCe-eApuTa&lI- z7jIvRM6!r@h0u!F^u_;hvE9 zNRkHaB`XxwlS+FK4Ncm#Xd)$)b{Q@0UFsntq-nHOghE3`yNv(qdV0S9|L^!6|L=1g zzvK6Odn%vL`+eW{bzbLrUg!Py@^-uK+9~@K)a>=HxSf$9xRUapbYl0wl~-I_iXerS z3ZzAU60W~MetV+$QRXTN!t-jTMJw^dMq28YjMmsr)j}7We%zE&A zr%s=)_d@D1y$@}aR8y+BU=${uNC=#1$#NcKw&{( zg~7qCHI&L$e*cv9^5s2f1d%=HcdM9rDzLRS3GZ%hzJ+evO9>nIY){YT3l2-ooRw|4 zy=IS;(S$2CdwN^dHkkO@XmV3*UY$F4#-C7QjW|P@z_OqM%ffc^;YaV>xf8jm@Z-nM z!QXD)ycs&gkQxT!Rc+8fDik6}Xm~tkAI>rw`GDTRm~W=WuMoFvd7AFUbLUvo>T~DL zbvDuF`z3c9fG-01OE4;|Cq&5*v}Ia_IJ&_6UIwqW*5zg@fa>^vEt zpnTvF#v`?nr6p$gJ9m;FJZQ`bO~anfj@lUT_51h5w1!yV#l05lc!YBl*QV2PXV1v7 z-COV-pFMk~GT8N2ur4sW9FDO&CY7dY^?*~+8)p?fd%{PXyq)rcv?)^%oHqd5%`HcP zbve-uyI)vzjV=Bug!U1#1c|X>V#}E&b4pmjRbBYMVRPb2_Qo_TL`tOyOo52&& zj*mv!KuWe_VC3q8S8kNg=rM2EU8m5ZdHy`rb62jU-M+1gNeOP8l_tI;nqdgQq5L7} zT8?Y6ks;8*Ln04~3dA>+gS>I$0uBwo#d7@hTeJa!!+Js=orawM(`L@J=a2JX-a;cz zTeWI(iiy3weaVLpTKDZ?=?%Wo4GiWhv1@EfJb|Tv2+`z+!^rS+c&zYMu^=pLAlj4b z`F;Dg?7VS{t@ix~48T&ON59H%fy<={YKP7k6{XL!dUk2H7ESmdlhvc!zI^dwC=3EV z9uTOIAoSr-eK=7x@B*Njz|_;a2d*u~|LBhxg?omj0g>w6lD8qoFA5QUnUN;pljB!tvw9xQx$G6Q z)xI4M1HL$PcAr0Z_TRl(_VkJ6L*)`*#rhrvF%Mq8Y)#J$?QZvEe3Vo9lS!T$>gtKK z8bUUb;bdM|SU7A?ce_iIX(PlS_VsJ3M^F56En2l2WVC%+&z975HwrZ^cz);8tPSd% z45iuHZt!R_(zq!PD(lIUP5%D%g}1{LNxfghBlvf&11B&}TeIH1>EzTOmGXPSka>8M zi>|eXWM%dSQa7Jc5WT4!s3)Ty#l`+miX2HiA`EZrxG!J7j+{KXDya81p3B3$ylq(? zZ)kI=1O5B=XVxztw!(SrSd4b|$RFd*PMrbQ0s`Z-47Q1~>bgsKehx=!5)d(9^MkwR$J%q@Bd>U`0hSZ z{XIB;rWa*g1T-A7fB!HZ4TDET)!*|5M>&~USo{Uu1@vbDE#fuGFf1^Y74_d5(5Xr~7 z`~sylVZ6HH`39WR_zd)M=e{c81e8Gq&SG=#a+$h&_~?=C{Q0MKA}?OKV$`{F!{g45 zR0nVp}Kv<=zw;ll^M7d&y4 zvkA7xV^^>40tNi~{acy_)cqr|vE#qgwrZ%%3*=oh6(P1_{MXc78(X7iiseTQvgvYK zzv4u`h@U{rjnGg`I@2)0Wk8iSVPYkT67O5{If533;%!ip!=IPhhYrMx+`@PoOPWqQ zEBBDI$-7UV($FYq&3GEvJy`cRvZxFV8#NMt62QyuB`Biq<%y7<+SS$7xSr&!1N8;j zM7dHU_#>xIo${{w(M^yz4y43=;~lZ1pt)AP()xNx>RXe63(x-g@xyiY>_GZAeh$?Z zKp>3<1!EQGGM`q)FPZ2?2s;)NQ*!_O*IMnbs%bZA@|He_#6=LK(lHr298+b*xxYoz1EyGc~7-sB?powJ;cB9Q&NDfBZKn(ds z!xKN;xqFw60K1b0ExYs@RC89`eoJ2&pKiWB$q(v))(lcZ=A12lzR~Kv;N=i{9*U`u z&N$Je-vWnQ)>eHsZ9U5r@On)*qkY+i^hDhG%&x4LUbO>H(idjpD06r z#&GS)C64jR<6>@tz8xYwQW{WYF_51pGYU=0+U*LEojF-~w5p!>b?>lx#nda*1 zhnGj*L34u5;Y^4(+G(tu%tt zFYy*3_4-20@+n1PL$LuEQXC)!>mvU=prA4?nh(wefOLly6VQeZ0m4q`+xP|rfs!NI z;!OxFb_@g$Y$GF@Q!>}F{@Dpc zeQ~JU4!F+;{>h;v9I1a}-RNV-{BPW_0N4Rb+SBKJ`m}%pVMCAp3-6tAGRBlz08F5K z0KnGSL`6nMDqNe~Rk`Qq%1UoOVV?yr^_W`fc?h8*%E@@QxleZKbf}Me zN>fXVXQ$C^uWBVU`DL#{cpBT>m$)c+mfcPbtf z*lOZIre?!NaMF~PmUfyO z-*%%$tCNbh+S9>|d}6V>Bj&N&_$NAd6j)^J$)CUTYLOZj`}G-63dv3sh3`3r+yRFh z3VZ8s(dwF*lE5L0m&$m5*p(~2Mf4;F!G~j-LrZ^Kn}JNb3eOJRL0JMhy3L#^c_d2H zzOy2E+6%vZoFIn?Z3%!GqN9QLq*=2%K;76HZP(DzF%FK_R;!0NLI0Zh__5u*d8a%a zpoC>Elu5>6<}AeCBGxcDke!or2lm(-gBc}TG{lBmXv`PVZHoHYr%wmc9fh^!)Li`g zn?zn5KY4P{>9G$VKTeId3!)$u=&yvXrV$hYmQG5x7k{(ufdU+QVlNs-i~;(L@CdYC zJmC$CVUq6w{b*+1d{3wyh;}jyH4bu@@3k>pQE@#9cUfHEcJ`&>nWn%$YMOHLjkX zBRJtlL|7cbV;}CgF+P6aS(gMOyaOD7aq;myGK3(C?po^E{Q2|8PnvYm=krX~9}WJ^ zq@=ZgEyY@nAF&2-JpXmjyd$GNOOKS#>=pqR%aUgg!|_VK0OT1|h|psL%HrBh)iZkO z3~)=Dj+A{v~ga6|NP-4r0j(D^XLd`mL>Lg4*MGc;}D6gh8Pi$eR$SIs_w3j3+Y#U}&2r-3i zU0q#WuSgG!BEvrnQ$y*su~;R=kDUVT5VC96U>aLEF=i2h#r0-pCPrTv4+eA?fjNKq zGS)*v5&Y>5%F}*crp|8o&BMu`+_v8$B?c<+p+-4*;e1hCza=P0>*jkFTfyR$6$Kl; zN-0^?K7()7?h#SnyKIdPkEy2QeoS7U*qmXfM zU|o(!KWso$ECt(-#c0{cej{)9Ps)X6KX&L4{^lQkpja$s$snD2M{@sv{A+E!R*|gw z*OV$elVV856D^ZR%pD)JSnW7!RG7!|iqD@5`TOH=R9?DrWfX)H%-Qm?+|;~07uy!H zkASjhJFP8N?Zz?#?0faiJjdQnCePPfM8)W2jU!M`S zO`w{6zLaP{nF|xe{u3<7Yr<^@yM*2rNovW##g3--^hI=+L9bXs=GwySJc!gqD>?cJ}P!>1ls89P}4f zVUG2j7cVNleVa)!!$T@Q2#^49opWBc=H0hqu_Ek3k^%&)(aa*BTWqreUGoyPX=Evkm5nr#_urLXghAT#++{fTeciN zc`}F(ip}=?g$o6FP)0aG-3daG!{znsMBIovZ6aPf@7}i$wmg_q3`3PcLN%jLnJ_}^ zQ~;C=0x*3U0N`?ms9LQb8u%;TL1b;}f*sHT{Nz;4n~0`9u^ z?VG-?UV{zo=DEACr+EcjvvJ=nx)#uTc;*9yt$OfftS{_Lii9;7Q@ZVplLieFwTH4Z~6H$|Gcy z2S4@Nzm5%J(2-yo_#WCen%&p<+>vs4;%?~JSU|H8wK)YP28#pK&LEV*{VjkLnt!U1 zE`KQtK(cfoP_K4AtW~S_=T(1N&To+pQwH>yTre=O z>8WuoHkK2xYfN5kacxyw*coQ73uxfU1wiZ#rYl$5xQ-AbC+L2}EOr1L7R8-Pi6|{I z=FXjmHQaLpDMv&nZ#FJP-2T@YWGzH;o!I zMi_Qn{{k3cdFhbYc*0V?dNrKRPlPS_A_!F05)O*wdW?nlFr6CbI(@pWnJyF$q_C2D z@N8rq&Re}uB%g=bUyhC8Bj zDD*W*UM5@2eJVHP=1!$Q3Ay{0nn|Hj+S5wC?lrh*h+(k=sH*B?9R_t$Yyq=ku*JCY zqb^e!;?TfeMm2*LM%)113=%cdZ$#pK0fi;G5UY=#KHZLImhHfx!+8&jpOtq_C0dZ} z#R{Xs%h4h^dABk7j7KVEow)^EzH#FqeWvR9`|0WGjf23m9LUoBBg|xjj$mNXgOcCv z_OOH5)>^vv&wSzqs>l8V2Re4`YJYm{XV~!yh&9l!@A>p$&R#2^hY7*pD5j6YhP((g z&W8u#rD3?2*5248bcjqBFtiFPVv!~@H33x3hkv3R&v9(wOk=5OQX*pAG!aJf2gpgdD)RH28sbB3}aMFbAejVBeGO zk{;*tnX~WZ^`${@t}>rkQ4z&c$g3*q0~BK34ZNvsI#7bp;2oH5I zVCm_&&Yw3AAzauB!d23*yTo~mD;PhI;e`?{(>W9`JEQH4P$f%~^Je>q6u&FOjOyk# zOf@A1&&ule|CA?`#=U>YCUl;6gPF51`GM!{R@ z)$Rl9QeBTGxklm~;2;AnwqNuZ;aMikKqG8DxoBW`>{9MVi zvvgTZ?7>DcA99a0MMlJmnZ?6Hl>h0|UmVqRtQ;0WDk(Qs|Avq6(5sg=Y_N18wCUK1 zV3oj=u)rEFwFC?ODE0IWq9|rWSRUhJSk}?Y z1m6hy6m;;}^XEe`)f1!RhY!g5{b?H{q6oNB&Y2@~Uhtza2$-0&vEA{k-<6hfjx#`D zJ!z77sOi^45JCQnsT&O0P0t429eHfu_U(hXKCF!bda2__Oun>K0xzuppWPr7^2Fv&uap?i)OtK-h1U~q)O2HH^5o+i|U^CfvFY$ko4 z6pg%iT=*Sm-3amLARf>{MxJ~OnZb}F>Y-cTp8fi@U@PHf0*0Vd;7UP)-k^{%+_`~C zQ#CQ^Z{NQCS-daYJ6LFl(*e`Tv57l6j^1#w8$CRq2N@1-D^NL@14+~cijgQ<3RsV< zj*RNoyLZ~9*$-2kUgAo`UkO*!K>t%|=@Ix#%yQjkJ!s8BOtEPOEewTCiI+$R5DuiL z8GH}3T}TQzq$p7oRYXtQRRDlBulX^u!|6WBp6DE0P7`RS-0HQ=gmmjBHY>IbXq^-& zb$Dc?J_^E;B}-hX_mvqGTzO>_2g-_ks*{i1*qO-kv~b{f{cuNdN=el0mM!9YcZqqzHGkBT^0vOpTNs>6j@LpO*#x{r-T)dkw)$kWJZ8VbacJuaahq$K0 zI&VNuq0$Luj|?kfHUcem3V7pg}}2 zXb0Rw@H}ozXv) zo@S_*8omljhg{wCmh)Zf1Q(ZL_|mYM($u_tn@UsJ z$20dZAB*njkdrY=hIlL~lmU4jgy@=T*qgooShwAu+9=%(;x?+Mlv3 zi*5C%_wOBDT-p*{Aql78J}Ne&=|X0rN%8=d6B+CfngL%aZAH|>cUY=`+8a2M#4Px~ z%IW3*l+(0B(LL!w%~$;h!SOHbAj^&2$DG9^m9!MS_q7dT_LcPfURkL*Z#@Aoo6nAD z{m_NT5qd7RrrpK+wo~$PEYVu3mv&+^2zvqyWgQp|dBqFlz~md)mc9ieJVOusV`5}- zoXsqHEh9`g6)+`r!01IOtH9BQ{pmIYyASSHe5S}5${TI07c4l_cNxy4>4d$(K36;( zVaeEV1~fJ(HJ|2OSUo14Ty%76#1ZpVhf^nZApwqO_Imqg@e1`+8>N)p4^g2$rQyBLJ>q)fXXXLVB)+QMl{ zm<>B)!=SG+h=kT+!9XqsOI*-4up`;oqANh>v!T_SHcg?+#x4gn<%SfQnCO>&R+t&Q z-h94mh<;R~qn9s-12REODNM_W2a2>&#hTixUnw?y^P8f->fkvLGXXwqc8%79mm4NM zdHU3!NH;borc(SVCM^%cVX*`aQP&V$a&mHFPM&mU%r-f6kKZ>q?h2w%uwI(Hcj0lc zNi-X7hFxbM0Zh$|PSEcOr^nVBZ0;U9G#8lx2mmSW3Yo$(fp`NJBW8<>4X;onAcvAD zzF62j%KabLF{MIG#^7;@fR*_pW)+cM+(NOyU{T&74;90TDC-n|>49;Gah8awpqslg z&0HHBPI#|W1>y<-#;|xWR$Z07-Y)6~TT3G$UL}q#3@HLSP(p#m9=iP(7HkOEAeKKU z2fn$Aii%`}9~l)0T}1sB#RG-Gdd-MG;|Ab`&{UW+LjNMbi!9r|%(o#1H4WOcYu7HX zobMl>O9(3aMaGaQ*XKPrH!0HcF(JypPV~TpAT&S^T$dqMaq1563pnQff6aE{S{j3{YGMybY47y{GS?itjT?eu6x`mc?HxF9wK96}d(Br?*f^K?nq zLxRT(LXS*=U?+&@Z*ATBWS7|=871C)Y$JrK@s~>eG*gn|ScKPeC&epzTs-2)Mw=u! zD_%-__8b-zw3QU~0;V^_L&U~w9v*06VS&?sAzd+Yob=-0JuTf@m=F#}aj}vi<7$RA zv186OqrDs8ZuMWg!&Jj2$^;AztqfLh7SBW$W}S-z5fnFI4ec#wwxmH3n-`gofFjpc zXJI$38_g18=kT{u zd0eT0ohMvSf&qaJL7^839?H**DH$*m%%Zr75zx`08^8x_EbI^3)juIov$Q{t2BK!E z06ej85?mP=le!896Fm=`YxLNY&o9m1o*uMq%NESy;7IX{wtN9(4>UT6*}X77f9>S1 z#mM?N$IdbO!E<6;B;uif!V{8UP3`ZzhrBMlU=oW7LQih-zO3xly?YG-YfQu-Hy1&m zp{efS=r`01^iw{1_^@C~PE1%EobR72-wvhALg5AKqkeGaQU)aUlP46r;_G|UqWW~} zk^zCG6%r4e5KL)g5Rux+s!@nt`eCQn4;<<;T+hrpDz?z&UN`u5<08$| z_xB_|plOjV0?2JjGN>K97;6mLau&JuHtUTFdcZ<*xRS%Vj`4HhLzuu8#t(ci6(1ai z`=O}Jw{`$O1?QF+@j2y@xL~0xi5r%x{NKvtqq4X||7KhjMDrL$HXS)K%XH%Wnr~MV z&d+<^op2{dULbREn#0NOc^Q?E5J7Xe=J$&{T!)Mx$(%n8<;XE(f(0U1&g+rK+!>sHudLnGS`AvL()J2_TUxkE(B=U~LC5$bSBkk9(U%Y^3 zrM=6QlY8+187Dt`b{j~Pa>LOeS^>uxDTK&_k_xdtDG`d5bK)rEIt5ZvH?lmYzso)X zjCOT*ze&}>z!!yohyIWj7FtL$L^$1p93A%zGcs{l+Fuuo5_E^~Wl#wC7&(3`e%C=t zmYHzLb0hX5FgUo`&7E^Dw?rVML-MJq>c@=6>sQP3v=y#n+{O>wzD*pPHgr-~Q`1}c z8gbtVD*+9O`HQ_k(?>Tji(QIUNr6HK(Un?9BEJ!8QZElcbH=lPW#!KEHd=HFeQtAeVI+ivLUfKo^_fjLV-TLz+C&2tj7}iQ4r}&oP2nb zEBd8EUNfG`Z)33u^N&hED~;1Y`=g(#y||Zv)-t05u;3q=sF5fFwDMH;J6IMhl1iCJ zmp<=tihvsTKS5#*-mhRxfWnJ8&1ur4p}+#ri9r*3&UB`FM##7rGKe>lF!L7Jzcb-) zp`B^b@m`995%5iUaWh`NZl=Y^wv(nz*~)ri8KvQu0g14~Mf`8^1J?pggm@BAU%5Bi|QM2K}on8iUUUBx$no*=Y2 zdIB%@kHPdUY^-E)aOhjNSiWeHW|u*5;6@|CvcBygY8I{)5+2OH?Bg?G zjKm}yyqGx$jE{7~GH=0=O)E(64ml&D{shVbz4DJ*7pqrFjm)WNv~2(1J?fIE2<{*h zDQNEqBof?0K1lMMQ(v;jIXe@iO8YRQ^384ZBItC(Ho<`{Q^@ie+;FX~xpxRPhwhEy z(A1#IuDehqg$(t=PgM|r<5gR;nrA9~xXkJ(E35rFkCH`CF2^q9CQ6`;k?>B0gO&&= zcrljMyOI+17OtoqP7g>%Q(*oPwhFoln~Fi`kROm3N+pg=l8y~U-xRpW5CY=Dev8$6 zB{u{)o-^lff(?bs7-E#GH`u#&Taq!zv0^AwG-Su9#ULkmX-s1D+vKWI&uNrL zjvsH_dN?g9?~Tcy_oi)XP%4oh(@8~>Aw-TB0x<(w`W3u0EiQ9lSdDHOY{d~pdp3h) zccBkB55=2FDy`RP3~7F2PikK(Vbb&*gP!a(yUV9QR~+3$w@OC5NUR)*BMzGDljmPk zvxeQgVe~_=G$R@`OVb?e?#p17}J+0jq|2=qf<;34LmZOcc z_xbgFrxHD+L{Ck}PGi!?S1Im~43F;7dDyF4mf=pVUOPQ{ew(Us-tADpX1y^Q=k{{7em*3ZKZ~heCA#yL4Y?H)pZPF6oe)Rsq2@LtUAkFaUMaRv@mm3plaASjvYFLDw&{{ z51h-sJa?STP9hMyr)|kcWdkTG>W0T39JrE+h~}_xl72aG#g{}Z9o!or*;vS=bZ7Y- z_cHKHbLN~lMF==LWQjHx1@O4DTD?5H^dRwMz%ZF%1xt;TR{YJ2-JOGMaF@V2IUw zGm?3gtcMQ|Vb3F9&bsn(jYW>*#VZ$Hwy5o2=rXJ7!xrBI6=!?YRG%z#DON6N@9B4A zz|s@r{t!q~*1x3^J?IE@Gp&Hwa*pred_`g= zo7zvYF7y<@SCj3)uTsL@x(Vt9`EYJ1N&*+Y7z3}T%qPfP!TM_mN!B*?QYu=tXd%8U zuP5jy20!v(0VHd;=w1u!6*QZcSnfDEd$^^j@bl%puq(h)pi?0t=~lIihe8!#OjtYj zdwF>za~{Np0p)0=n~iA+_J^7jnF9kBCZ@5kvoX+0gowy5v~8PGDxSG{P(g(SQ@Y?&>kk?tAR^O3)U4&2ttYxoGAES%tJIN$j*FFVU;BK4H=oD zhZS6HTjTlb7V-6QBQo&(rS!!vmIC|HP9b}&b#+x0dBPgS`3P(}ty&dA7(%G9qRrP- z?dF70{gf!(Fct^s4+-fa`lS(gJxkMb{(NmQ2?X)G7UGJgJk_)BXKXyg_`Qd|A){yD z33vDn76*@zbFG4u$2z*&whm!Ry$8?;0W3*tw}RU~8&|2uYk|X&E&&A>bC%prL17Yd z9)N?P>Oy&~BvwM_>2$I1F1NDkamn1DI>0Drr!$YKf!SwWET{Us)uw?`+NQ^f$2`xm zE14;5v&je|z=H-ebWitl=i#sxB~WHf^{*`UE`?+;)(?EQJ<8%fHwFoaa!yzmES`8* zQF7U)q@!%4N8%+hfTpdhtBch`647NM624l%5d4`4A=IgIjdwYhJBh8;&~O7Am5Y&v z5ZkeFve)X>@p_NpP%{&JeMi3Q(Wg%_M})M^PhETuaHkit%DX40Td^GR7mo-nf&V1y#!P1WS`}$Idn& zFczNV>Pk)}1yRdrm>y7w%NL+58Ig8lY^{!oh_U+l?_a-$m+SyVKqDnJ8s8O66W@vC z2Dtxr@Qj#M)Zuh_Y<#3tz&G%kV{Q}QgAoYE@|gs7APJq+afo*PDV-d``qt->p^BlD z7;Em9ST1%LpjI;WP`sd6xo#pDbq!!5(%im%LnAC!6I*=AiI;qv9UvbJ+?N=QTm)3qvCTUc{AvTer<|9wD8+m$Q!U3Wcv_G}28K3_uu;sGD( zEnAvQ=2U$}`_upg3)O#pSt*UiINP^<`}83@rrq=n5Jopqc42R3z$Onn&|g~|;*U=N zj-XKp@B<6VF@dyd;v9VGua^^hMbINMRhLGm_CsbB4Fh>e-H=g-4I4(-&4JhEG9eDT zPn6x!#+vW)9<2nnm+-_u&Wr_;fz%KW9v834r4ABs%B=!4#Rk7xj692y=aFS1bC%8Y9=X!R3zsV1vdOR@rzi zqLSo$642qo^F3WXJY($f(2I;5VP${Tlta}Migqe}veL8NkC-A3O znmc=Yj9)kT(`Mu|VE=V^@8$Qj;OgZDK|ABRIB0S?4TXuN4G+fWCD^*L6|O^cRCZ3F z60lK*XF2ns@}qjx{ImC-{#!eL@4A&>86Lkp*mB?v!WnQYG?K@x>;a~zuO5^M#S0JX z7m|U9*d1712wy9e5;>0C$UiR3MfmL?N)SvH08c?D!eP+Z7CbH}5(T+&0=i4wP84zd zi{ml0&75hZw$VmsF4ufSFsKiGn2Om~xLdY6B^9Fth?Q%!l)&V)gy_;d7nzRYv;7$A z5GM23lv{3#G$p5UGvp3L?6a$$Z7BmqjP!LTrjC?696-|kO2ffqg;-d*Fiz3`2UB*0 zwl~#0m6>01D$Bz!c&zyn*L`Ksk$gQx=u8t_1#;a&xFd%0b$)elUweIc(nX=7ALKHC z^KFw&i`b}E^%ursl)zOhZW_XZv&bZb`c+_c$b4N^|FOnRIoK~?{;Q~E&wAmECtRrE zlHJcyb{lwX;6UaCCFD=#LLp|P^t*QpNf$hXNoy6C+VR&sJT$loZ+{Q-qyV?(Q*|j7 z(87!OH2Sxm<2%!9PkePG@Duz`V|F_|J&|5Uf$0tAuQ!}3k(h9j{ivR(eYy%rCHmbO z=mx#8Tr-PRBj+0h{%-zF|Bcro3>{?t-D>cK@SQu2R8o*R;K8hZv=&3vjvXQi=nxss z+vIfQe3lVtxoZUBhlmG>5ae`I0O!gCJ=KJIA#uHI1BjPR$O5Xh{=5|qC`okROxq1Y zly}H6#xV^gRK-OZ6w^kO$rax|c5L<;kS-ZK7cU-S8}T87OqOwA;-M{w^Vl|KJWZ77la`M!XWdZOrfOg#^a+mu?`pdWL}&#V(m4IDTXvwyi=Ts_zP1Z!Qp{;4lDpsRyjRr zjQvfiOcEpwqHJs!FjF z(xpgu2D?Ltp5Oc9+qdoaFPbuWvUHPdlUbZpl1&5<$vQdCl%DSy;lptC$tpt|%+0(~ zi;mCVhi_gOBSQzi`a(t~HZ^RKj6+d4JX$&o0je+o3gUbH`t=BoUpk>S16N4dSg+iu zfcGI8xz68|8|vt}HqH^67`?=Hqw#~dj^IA`m4akEXndS?esnL1-x+>2U$#9gzO<^275=#B%<~sDaG(PhMEt zWx|CGtk(QDZ#Fm{K{vjDaKW%a-9#Ky^o-N+64HM71CbjfsFh~2RRa%B{E^7TLhb&LZgIwLjB($FVDxeto|23I6c7q4|W1Tef zdTn*yW6n=inrMaOfs=!_?L6Fgu|4QSWOBk_^v?VZI}PgHjArx?gF`9YrB!P*e1FrM zwUto`I`1NU@`BEWDsHfA^y^q|#F~mN4fV`>e~eh+oAd5VuvP2xB@rtuj|bIED)N1P zt9Dj#S!+|z^qwt;S8~$r;Vb%iRXskjbW59odjb9YpgY|C!)QzgdJcYpvtcvmJ?Doc zP0?Cw0VrW0yWu)hYu%)+#dM6Wa*q-FsjiCZ(D|C@xp~FnS4lhccKYuladG=O;l=Kr zwdU-ZGtB~3_0qlN@`Af}8^Cm(kC{8e_~M@-L*5(Qo5((_R+Ke-4$g5?gJwT5$>NgC zAZ8RC_Zyem9M%O8!qU@#4&vCNq{+M&PH{OE(F(aPTh_V6>4i5h(d%`TqE+{_@9&>s zhJv7RaLI+0aHZ4uMheos1N9ZrjQoJ^sJZYb<73-XB6Um5bXP+6u?(AFwD+1|AqvkDQWDZo!!3{Ur;F!zl)^ixrh4OCLvgeT4A#>Yn z(#m7&a^txPQv>~h67Go7f&b(Uh*-RH9Fb95K5si+0VzS(Cm4Fl^(7jGEf!UGLz zBv@wkuP;d{DN0O#K6qzoba-OGrf=W>%#8<~*s-hz1Lw%+JMO-PE@_1?gk~E8L?&ao z6D3sd&h+kt!mK4>^7!MiRa{1r$eZ5~^_)E;sm}l}E|cB~!}}Q;m{V37B@R!n@+-gH zAp#qos9*r%1_@P_K~9R8;@cykFlji4mD zvELc;`&@R$uGAgofWazBtyi%9usYFC5krY|+Y;ta6UEhX66_%N6=`T#)3X57u^bn0 z$pa8mj1U|nq6Y|`HGE={1X=dIiNoG+e|yB6(e;&o`C`b5r3-`6i0Qi;yc0*WjmEK< z_XVl?ob4OoThouKBzy%9AB+>hqmCc`7khqRR1k3{_*3osMja@_syN0eel*I*XpOWkAB~JFlAV5);r5y36JJf zjoMb5@gVP->WXWF&Wt%Xp4FO(~`!wjb}>S5?0@y%!g>O?)kT}a=Bpre4JOW-U6 zH6al|&*_8?u=?9FK#L=U11Ne$L)|;Z^qQ!aq}-N~h?@qTV-L?qw8WvrVYI9yR*3pO z=ki`Q9Oyu9#i9!fCe)sfjTX;dYPe7ywg(rn%fuLCo zzZBfoGMLtx$uNV!ubkHes^B%IUlxBDw@6z@IWfmGjGV0@N9#A#NfZfQ#(pPG+}pQ4 zp|wB6YcEmfSth#z4vac~g$ZfW?9&*J!$T4(?5tk|= zWb!1~u>4TD$xpa5o(Q@IzaXkR=!K9XP35(;CS|z;c{sewHzE$tT$-7+Vgc)(p@k)7 zxd$NWA60KneSfZ>Fa-(LWzEm_j2SjJybDcfhBK4>uBZ7Hgk3?T z0?s|mzcla{84O?(6YC=+97Vp3)eM_YA`X*SdCL|#D+Ty|UmWdoq za)^Qi4+|v3uv+zVF|0CLL1UJa)E1Nw`D2`n&xj=A!VFYUx#fvG8S)SN0$-}1)%-(SU52MT@fhty@*UyO-7 zbB>gD%mF(4LvTLhdIJUI7nv>P(1*pp%Oh!F?rnwx=oYded@}cS+n{{VX0%2nwO+3b zGO>j~=fk>k0?M_uRX})~KsVqNco>7_AdWG>j&lFVdU$>l)6-?NQ)B$)H;J6Nf=w9& zTH9D<7?T9l**8$k5}nSYQe8#i>H!}&i%v21|LKVpy)vAp=|es zpCBp+w_~^I>FP+d1QRv8oknXS@t-`YBMehR?eHNZ&VdL)k9d2VU4M_>&M$XIK>-Jt zm}PM4#w6TcJm-Ga^zM87;=rO}7@3)IkQnQgurLhi$Q^15p(zZLQ=OL35Zx9$@z3R^ zjUC>XWMk5in_%&$VFAX^u-`7IS>WacqMYGz<4l*FIrcmqGzH&;1)2(P!)89O$B1Ll z+!@ImR15mRinvG~D;P%18y2g7ougrr2BP00LuLV!($l%&?HTrKN#$zUGk^du{%cCv zgfV04iML}n1Dkvs=>#YlqyXLJ;3|21vN<|z6)BWrxP&&Awhyxd2mmfXxD$?B87O1C zM!CQ9C>$Ft_<`aA-P@~yLm2F-Tm=4?L0r|e3UYHGqAVC04ekO<0IKyIgx?g!(oCZO zGNbtG8mziH>iBdn&YB$SwU(sLtQLRN%auMAXBSu9H2a`z;d@5k?0c!tJ*ut3>Q%M5 zSue_ex2dS|?fvCTqjQ(eSB?I>?ZDr=&o3SjJITGeW=&`J<<6UnYdaMG1AdFjTG#&4 z-(@H_Kai#PPgAK_rwaDCp^lj!*Zl+>PA7bn&@X~Rf(#ud$FB&-;6szBh@f$dn?7O| z#Z@}`r@v(HWIcW?J_$B6b>=V9+Q`&NUhWEQX|r@t&~2VlGpOu#OiqUMW=Y;=o@zH% zGe|(*?K%!hMO|$Wa0gVU_C=e5d)ET%6jRoQq71hIY+6+NPrS2v8K|zPCXQjg?xM_L$7x1%=oDps)d5_E^ zFfD0>l#hFf89TN{`gC0*FGMnR+eEs5QEapNWiIOv$7TokkanC2crv#_4<795-7Lqp z1Qu0{*c2GGBat$^42Pyk01R}NUG{l|b^SnLOd2?hJFVn4ckW52e$Wtt4`~4FsO*}C zwoChI+x|O5!y7vHbrFP68J$r)ot(}QRDz&{Np;;IJqJyhh^4Ap`9CUx?%M;0CyXLx zO9t56wl$6IkU8?2n5}WULm|E9R$U4({Ss+uW6D)!(&XM(D9ZczTTyumnLifo!8K7K z=@^k^XPnNp`1c0wqs+>Iy+6}l1pT%RtNCPd0IMRHg;58oX$<;=vnL;dz?lC;Y@Sh& z5(rpr#+RWUKxN7oWXueV7EA|4JB5A@!)42o!^zl8QsDXP$k}MZz1>{*LRfShS53*) zE&Gev)UO#Ahkm4nijy4J$;V;nrKatPw|Lp;XBHEVyD)xEnGdvxV(m5GS_^8AM#a(p z2a;WifB09NYOPlDGA+~8c>mM1;hMEijb2?nb1c7k(;Al>SHIfz=oQ|}cg~vR;>@{s z^7I$FJ=}gpF}b*ETSn~#u<^aTel=eQb=`j3tXHqMJS>HEeNT-&IU&|dpNU;wJL$6> z&8pBlckkX^p4Q3}Z^u?_#5C%N6_P{BhWJTSPtQ@zYETeI>#84KAU#P3lFm;u7U;7$ zViZeMk|p2c{CN=usPtr2L!E7+$kUiSzv>y0*uAc8_5D&Gp0qS1SgrX>Cd99}91_0c zaN~DzG-?Xhm%Lkto`-vry+G&$a=Vat5DDeHHDR5p;7nM=ayQNH-2o(K0lXz20fm_b zCKv)wQhLKRG$qmz(sfgIq(-nXrO^~P00+yF3QZ(8`NI4Q2;qOFo`3P;1>@Zvn=Y~V z{jmoc0BBN_0_1!6Cg}9!>BGYgTPAADq!kO8TPL`|aeU_|*ev8z%HPd+Cui|)OWzw_ zkyO&2B8szQ>c|88)mfjNTZk}J;Kw|L8J)V>0OXNc#~x2CGessP_%-ZQ$lpvi!U7eCC2Qb z2S6wv!h2=#iz5(593S>=ngzLjogPL6Lv|uhmWVyf9pV$YJSW_&{eacKF7@Tcy_pi| zPJjVL8v$I3ycmr7Vrc37x;qrT`k1i*Vob(LtsTi*BrSoc1L%o$#XZ15VhI9=@fHL+ z0(2_))J*Va;MGMnKLi1ePe3I)c4wgkn^BR3u7%cAfZWq}&@J-dDX|B8EG)2k*4 zE|*`yloo0@XKN_bu-)%t)4Uev9rWK9bkV!& zp=m;Ge%XSq4{B#OFDPx2o0OfVIkeSX-$0?w;s``pAq8RtX&%%+J_bLNBXr5T?2T+dR(`t^D|0sI)AyhU&xEmP z*rL0wj@=lJ6aMZsRTYEf+A$^+nUUk6*GIdMt5!IQB+%m4Eq{`EAx&hu)5OG4$s2V5 zuPvK}6lnn%w6Sbe;kD4AGa-Bt6BrNIY0)C;?AZ*CX^17c&78x%Pu?og9zh`DW~4r; zS*+bT%L`v|?yAqjNlaUZZHkow)jRU*6MRg;ZG@3R{N#()vNqv)a z6Qb&g)5GteJ7Aeq31k~tA^6WhN;qKB!7$9iFs?K5hk-|@Vj4pO43^+7tO2ROqHc(r zgjw+NnV!c;ag-=f-0y)(43#Tm@RGq*DuzEKgV66Bz>>0sFp7~-l1UI{OWwbim=-WS zO6eOi>4kN}?SYwUsHaRd(B#IO}rc!Ke%!CZi^bW3m!hbEOh~XND zuD`_4e^eXksL_F$W50<;&Vhu69!N5k{TMm(=g%*7JDECouESL}9pXwknRGJL!1)Af zl-mrM3!?!eLReGHnr;gDN#Y@yx1-M%trfe4%0)vqpPW(DY5K38z2)Aodsz0onE~J= z(|9~&e!YQUHC{aap%!#z0u-DO^9iw1 zY0ohGWy`KO$vt{av(F*sk~%E$R!mD_AZF?)pdKFhW)S3%{un!ir(Abct||Nb@4sJb zONS05pjqZNwaEB*$)F59ht#l_tQa|i>18)g$g%vTC_|{2VDUK^_HBqFBr$9V=%fVU zSC(tJMjo=4OuDHnOM8hx^6OivWO9p~fQ1K+Nis3X(zmY;h)1YZ9xnqS zR314_@q9?q9U~*WSCy{occ#EHb_!cgGy{N-gY_n-bBq(KZwsUZQuCunO)U*qv_nmG=3D>T4!y0`TCW|I80cqb zrxjFlMD=*3W{|U5za`;I=dC#yesgr<;I!O!^;6x|42|q-id#i;aCm?JX!k1bQ<7bT zsoolo)^~k>X-ui>-u2+XKo_UJ&5C9=Eh?^=R#$KP-EL;xBTtX9`w%p9;@*jH1aDqP z4JueTd`6=|^>0;AtMPrQpgSaP!|<}0;QLLyUSEvj;Be#oaBkrieo^CWFEF$)bm`1hZ-7u}EU*`)|j(X}TMG!)dNf7QTxXXN}cz^bwm@3HC zEL;zP4FP={o~l`m5g<`K2lQw}BVyJDIw`93GI9u0Y1ByjoJOZkCoN+hkZFXyz!7s9 zK~>RM%MgLs9pV$AG@}zsEhfa7zf5Qyj0GGtoO5D*q-yn;maK%-33kxY(NWjZYG-l> zBc2G@G)R0Bmi8@J%LHg<^l!>P4$EX3bv}A``T+_5fqfT^ky)>3+|`iR0gKVS?KpoF zB(0j7SjQC<05!JRcEY~>3QXBch9^jgK#+l)cdT@>wp8`P)Jk6WV~JKkQ9|?Igq*)k zOaPWc=#yRu{>m^Zm^{&oo7=8rFp0UATL3}qqzYsvStLGwJZ4FMhWVXVUMJW&Gc<7( z7a&^s`;13>rX$qKtKhR@^pgB-2_Ff&!za+?7(fm1Ypn7F0491o&7GD1yD^x7=;-Pg z_4Y1zWuns>Vpgth?Rya0p3~T|%1hGq$%<@){xW^~VXr%+OEkSRhH616w$AbWe7}#P zTs_FunQ#}5u|2IBK|^75^c@L*whPd1amPX))2+I%C8FwY?$X3 zCP0i;ED8D<@W{p$JLz0OfY=PP$OIr62liM2(?SeU3!~e7=8TEEBfgnOu%6&gCBNa*cN$%l^_N&65=x8{RJw45{CwTr zb9q3hqa2CU;>La?&<*i&`=4jSh;50HR`2pG4w@}o_r)_JVPW;A&O~VOi?fqcR7qmX zpU_X?mc^?VN%ejuG_{RgUaOdyL}u{un;}b)<#zXQj}Dph{}f&_v8VN?F9?| z5)qSY5i!FGkBWf~w<%}sGhPBUH2$CD-i!SlBo7!c5azKL)b+Z!24)_adVi+5r-_ijs^z*y z>s1lZqup?Z;mqO=I5kNli`&2hD3*V!fjm zV=me*OBE zawPo{r-5?g)&cO3=(C2r9@JBnCP6-*i$dx3<@8~uul#k=y7h>4_a!0+=^A|yD9jDF zn+TMm*ooT;@`1_04GorYKJX*gojU7e?9DTbg=nH%ho2Sm4~>#<^#_-FaWQ14?RKY^ z9-DreIF9{9chI-3C-Xk|Dar)|5%dZVk<&|VhQU;aj)E6)Lg$^hzZ4iNG+SIWEXu1M zJ9g8=(=#(8t!c*tigB$fJs-C(VEEpi4$eQ1k-35K2iKQCBe9ueL}b9_(!jUt-+wQw z0LzqEn`x~p{dF8FhkY8WB*6rQ5&qG z)71Fq*NW}Y?$Jj|c6)R*Gj>vIsJ3Ip@2@)-Ois{W?6oAMU{jhypEG$O^4s90VmS}y zJu9DfXD`^@|pe5Y|m z-EsRGpRcR8yx2GU*^d`_wFCYU6!N6&pH8Yo=$<_ev=9hIwrB<<(M#rck6Deey~*uKKPxFrq(mD5tYIj_6ab-rBUV<$}?xF;RirN7q$|3d{ z)?Ptn_5`d*V$~i!dV5Iw-av42nxRV=&a+GA!L)BS8=5*_Lq=0Y=c4`Mt<8Vj-!_S^;+f<0w~tc+g~s-j1IngrB{baTfheV*8UM1pc@W+$yv~N92Y&Q;%+3 zwH0r(4C$X(j2?~~UHnWaKx(6|V6Wn!$GMgLaKMRq7)~TN6w`TMuCG~G_4TZ8`+ofh z$iKl5jXFrPB476#yX$K^&XwRe`_07#sQ+S zt;_t*8|&RLBq2!52A4OPYt5?dChUGV6#@OzsYT0#2W^)*hX7{dXU3h`7;f$ z%mPA%UQd&zK>CAbP##n0(p|CG(4@#3wMOuR;1atJvH@EQ0c{$i=|rH4RYC02oIIi# zt8mL9ZcT1p62l_ya>I9!<}?9>40mYX-Y%vAfS+cjgSmc9<*tNCCu43X>NdYR%*Y)bm2l}M>{m`d$hv6=igL(tf4jmoFO_rHQe;4oT?7nEh7JaFVkF~fxJ7Md z7`=>~xm27ObfO?A5j2}as`-)jm~&}1$qbt814J{s%HNDBGJUj}+5Qq6kHE_lYjr4o zl-eH!L8koBx^nDrCIN%8E(d_AU0fp2EE&k{c;X9GG8R#$00ivfX-Gs?r|pm_X%1-G zA+b!uvWPhyafbQDkXU6HP)QG*+C1m*VtGZ-uuI=NM;$&grj;Z4U;m_r-Npz@49U?S z)wCWcMCz-yQ%XpN+GOZJc36m5Wr-*<`|TP3wHd+)e8p(&q(T;beLN1VAv!zHOM@g% zY_@i(&;JaZ3e+SmV5^~n;6|{$Twg-;`vW#)34@9#6)dmeA>~ZKV8ao(KXq!?-fb9@ z<4QUmEKEcpm^}E6b}84)VDl!h4W>~pw7~GIe)t1JESn8|3CIQ0h=&^-0PR5Auc`aW zx{@^aK@jrb+f1CkkLG->omLiiGy3|K@3nhc^v@1AiZ*WMWF>m7$ykFqX5Vaw*}9qP z4BxWRqo@7(S@Tyb`f&%fQf|DLm8k*8bOhq7GL4?acH zbT(bgV9KHN@{=`Be~YPDycFErKoJ!u18GNG4=#Bqc4ZPp4%Makw<#`UG3Uxy5EQ%@ z+y{Eda3C0=LRPu-x3r%aoDt2X}RwM$Dzo=9kd zoGy2qphMDm52iYB(y_w=4UH|jP7ON{-sR_v=tFC&?_&*|F_a)pkq0n)qduvrsio*p z?J;&n6ITMe-L=b<8fVR>y{*2k)=wCZ2UCx=8w=A*KRH!QYE8TSa!9Jx!Mm=~@=F_gluajxMc~5K4 z6xLc#`eE9p_TsnY?U!fcWfzkVMOToHEF6X&nEDeR5aPEZhlOAQdS3>1M~)q9&jgzg zuIxs#g}7}=>0=5;YDrY2Ibb<4O|CU5S4KUrUvCNX$c<%@F{>-z1o|lr=ZaQp>B!qJ zF#v)M#F?(ZnSl}`F~S)X4hcvtd492jkQS|4$%VSG9SF9)Kr78#>LyMKp7N?E>9sI+ z)A7KtsNi6%=}{2P8gXuE=NPfsW=a6mbxLR|H-;#Rqs?l#fQ>hTCU+f76X=}uc-~-&AYzFsCnf7>V@^D+kDu0(-xCZq zohLS9TtVKu_*U<)z>>&ax)R#3V#Nyh=x#V6aF!WF_Ikhg2GSt6$MM1mPB-i+N#R{r zsL@C8*NLf|U~boTu!>!vWP8rfaaPJecSPFPAP+^_@O5b%#Ffa5EFNq-%JADIB_(G3 zgppr!d7e_kBeT!Hdv?AZ%qu*d%;(~!lm-%ogf>gq zB6vuCu!3@vHupFLiF@L)Qc#o`5t2)XF#^>&dp0V{8mft}3$K`Yd~^q)Dfpt0IL8>G zhl_?bRhm)?pN)6=(TOn)czlw7BKNw#xY7mmr`_{mdshA~J4?i2+f3pv@IHBTbb$ zLeI%1a@^cZe{4}(wU_{%evG*wp)yEFCZVKzaeoU^-g*QhNxi{EX+^WjvX@&mai{XF z?sF`kyX4&EBSuP0=-Kfrmy5p)S!Os5yf_({os@@gY(!hRsEXEokHzZ#902ggldM+b z_4T)jvKl%xhy_>ogx*W!C^?`ofF^~F=x4VvrmH z&6{@&+!nO!<+as=%|!?=NyC7UnVtRXabZ>W3ZI9RAJjg5#)z0;s3dV9w{<%Xm_?=U zd!Zhv^7LySI;h8J?mO4dGO*ZB&&uh0{px08mSvpLt$BMsuVJijC#MLr`34`xozcor zv{s1xwC!h?6D2+eYVN1Thn823js7z0pJQU&78mz~1-``(b~1*zf1F7;)h7x%-NX8^ z_Bi&em_MQIQA z9i&IY$?imZ28!yW`bzpc5N@o+kQ(UrqO>sk*)08~mT(Tpua~3c%((Oc*n3*@N%u%> zPVCA2`nuPBj^{^e_+m+g#JP~diAB_I3cjJ zgjEI12Ha@Vw{Ic-28VJU40P}b5Ih%Q&avvbb8#{6v=1I6voc7Ch(iyjj~@6ZUMs;> z1Y87={tRPc=(Hln1`H!`L@LX@BnfCdR0c5p=(hewpi4g3w*bnVp&pfKS>KM314JK$ zxSvSb{+OA&57eCC1RhZVCoZ_lQ|=E4!_6={jue%*bt9ck=rikCdt~yNloN4Miu!IP^7m zthmN==#jVT^iq)q8%9xw&ZFIn)iNW#tSk6n#k0GDeXByJbVzjh*dpy!V|4m1x~GrB z3&$y)fV3Ce_|po4`q{5L%zWiW^190^ec#fWdc&lT=namq9HhN#ebYtjY?9E58_sXq z>t(6@<@4t!FbL3IB1$=^)n91M-;27P${FQ?yVvJUkx>(5`Qg2tF-3?!2%6(hu@RAQ z+M#X+4mCD#P(oBB$V!L`F$A%AJFGtmJqi{BpCN@+ME$}tmLKp&awuWT0m&Nd3Mjlb z#DyX{@ye+X`MJ{hc_|QW^7IidJjpR>x#I?622g-K5MGOvb8JUIIQr{b2x^C5XF*p& za8EBzEr#%D1Bgev5q9JbP!DiT&XdFIK##>PorR$wIC|?cR-#Hu`tecY*bjdgfk!(H zg}i0Ci~3kR$bqo2k!zm?=k&>|1#=s9As&dham}74(CE$ENzVZSmvs<~1~-Bb{c(`} zRoB2t5{)c^#KREbYSiJyv5LbW7wZyXI- zke89>@&)iiW{aZRR~d8$A`T3uzne6lni2IdGT}cle`DLB#k;Lk8(BYJRgF|A(P?NGO$1{{$Y}1sDog>_kex z-gls>%N0%HH1IIWVZc#FE`Ilp6SE6JXLiO(2eBQQWg!Rub)3hA2um?y0vsF&Y_~zU z2gSbaAa^0oxvrOpj#pB0L*wLaI5G5!0xAJ*?>0)Q2zGbbG(SQ{_zH^Ec#5R#jd)HH zzzlXMiF3hhC7PXxZ`wt%=cIaqLbcW*g(1XlR`-)?6Tf(EDA3c^q<%eYahO z(eHq3L?|nZ)h8J`+-bhrC*yyuhAV}YmUZGcC4z%_(*CNQAx5^{i;ia73O=F@Q`xr* zthi?Xsa7#FGX3MyE#^ZB;oz8kXkObD%`_^n`}DED76PlrCO*%*G48^oUS&L<-DCEh z9>sx(D)QKC)x^kg;yibpy?vYm%Z)+2;d2G*4v%WzGQ#v6SntaYNaX>{@s=;vHJC-e z^b)P<=&vBcv{3BEryzykO$c&1LPT}|RjkoPYqAnjc95@T0Mhl~9*}5`I8+c`fw+Mg zLnJLw?E(;qt+NeUD@gw@)Fmv&EH#83BA%p`FxyrK`vi*VLZMD$?z;OAz!`BnKmbJ$ zwZ$VXcaRO+nk_YR14s&646c&eu0#u@&omErly*J>IVT+k&|4)}0=rN6AxcV~Z?e%4 z5`%yl%B$Y!5+PFjz+*x97Ton7X)0+f=z>cn0~65m5WDzk77~}*B!f*rs}c>8AD9Ol z%3Oi`30bBGG8VZR#)@qHh9Ma!Lx^!*DXn=RoVhBE{^u|Km@xOvt z@c$Da$)dWP`iQS@@-M!&9gS2%x}FG${;m5$KJx=a53!Z;kS2Pb1_2(qF_#N<%?eVC zG%<-t*p(DyhT2JLs@CL5F;z_o0b-6}6+TR2mjj9?s=!zSWK8dBB+yt6K?}s_v1oDL zb*WKT-w(P$qyq(tCT*yb0iOyWP%|D8+M*;~X2S##t{P!;!IG2qL3q)5>+epT1nHi% zeC< zb&>1=4KYe_KL}qIP8uXi$e#t`VC?|Bj%WZ_#}mv1;Ak#%7l8@^nHPaF*8-8<2x^IC zBa}a+H;qK5l|XD{ZZyiQt3tIvm|uc$hqH~ebQZdY<6$33i~3v%FJT9tff@-29a0!3 z2$Kj^C2thbz;PjQ0G>O6Xkc{KgX4|p6}8bo{17sCrkBFCo6!PCZK%8xH z-tup@q!OGtW*a}|>y~E3N|Y4U?#B?t>Ab5{){9;g8QSbS`SdCB4r5af2dB3j<)MqY zI$nz(l8!!dud2%KoqT}KFE83z#<)8?P zffN!7B!uB9j6bsw)pj7tM&IQzeF~EMO-bJW>W<;*qlQi(BgC3c+WJO-q=l|_(ggYbMV+i~&e0{Nkh%sXYPIDR-Ee6v{71HK0HUT(%NaaoskqOwa zT~B~4VE7aCScuXKNWf2i{Do13RO?G93qVJ4gO`S`L8)bZ646#ec@=pt7?C%Dlz@TS z&d$aNDm*2%Dtm;w@Tg!V2*?8Ne}piSxS;e24ljW$Y+CF(1bZLId(|e$l)7!(bgNz= zRiCHfGsOl089CK+q$W?ExM81hQZOeTc)VWTRck=17#ClHh#D~}F)m0N|1FV#?!fs@ zm&Xs?BZ$IYUK=_MDMs)1{J_8p<7nU9&k-0}4zQn)3t=}%qGXt2#QY6+Z)Iw*K1Rk#ai%_*~4OiqGA^$n344e~Cm1m-tNtT0CtM#WO%4loU}fXoCyc+8(0uX`~^-20CaX3Xbywy0$4`O2 zB6fc$V$Wi%2^gVaJI>8REdvBAXr21J&zo|MiAn}=E3w47DPytyb1-C4(7J7}$MZ`s zadeBNt&hs;q>{dI$fOWyB%^Gb00{gbcp&$W-~nwk60PxyM5q&|4%IaCe;L7L4Y++U zz*O59NR3u6Z-S`?@KB0qN_Bs}Q3NzNgOP8}1mOQ5ewud~DZIL1#GobPtGsfXQPw5G zWMTiE{V};CkGsvnibQ()1of`VP&!S$;vKSGfj4a!pe7w6FaTC;68}prCza5!{{Ob&AN0)yg5+R2l z1{!MFhk@uI;zC$Ggfj~lMVcc21gRYn`Cyt<2OI{(ch^Mkywqx@WPHFbh>*eS6IWI?7 z0mQbTRF2!$jXx|#`iga*R1zL?-g!z>iID*uzA-~m*>I(Lxp1-z;e zc|wR`|KY=do=_fc&JhGAfZfhLAIJC91xrgkfURI0BI-q$gG1wp+;FRK6KDrB)sNC6 zJl>Wm;UMze4Go3CaJ!ClcJ10(h;Q(*7r(t)hjWYSE0KCa#H0fAf>TFm8mKYb;e-Rx zR;^SqFbMTrnAm;mvC+52MgjpK%eep$1!xj(Y#YXJ66qIo;z0ZC*{PzNw!fqDSIjNQ zG3L6>!KYu|g))LUa9l92uHX_0C(nuiXK*PTTPzKH&Q5z=^2!u+IT48)6vH&8~jr8ZV`Y-Tv5dR#7w}r|(_)&z$ z`afZ=&c=JJ6kfEa2;wF8c(GNPbf&!EsB&}6(oe{#aG`T3?*$^2M*M{JhdOdM51ZlZglsbg*PRZ9tj1}^#nx?_&F%=uOZ>h zK`5gT=R;!`3HAZhj=(@aO-?2yC$x^EJp?MStB+qqjgL&|f(D`zD&(jt)Y7O_5^N&W zAg#fjR}kPJB1U9_VH7+Gjo0vR(zgGDw9DX#%rb9#dmVw=U6?q+K*G3HB$}j(hy<9l z9q4Z)%FjqE)0b~G&YGX7uZ3U$oB{NJZbMds(^~|L=QhM`@q2JJw>S17w!K&t!->9U zp=*7p(vU__)Z?^(`XO>@XTqJI{&0Ko6CO-T)zj6*wPwAMcI>M=g>Wz9LNyl=e+0#5 zqfjn1(K%vm@qCF)+5{k8pk+UhX}v{{1e|PNLs}3tG+`i+0Qn%(#2u*CLp>o64vI8o z;jFKwsN}&9K+BCN@0M8kgr<-UgVQ@L#fWA}J8jiUM)u_{k0hmYIIS2aA zHFURjcXncmB2hU-zhB{_V-WtwT97$!E~7oG9keiCVXkFzQc~+Y%x`)Ht$=vMamb(r zAVUFb2+Cj^fG-ljLmCaYQ3QK+0wag`sk(zl{A=bkP3nTeoKY`gnfq%&F$Tf-^Q{ZF*Vgjxf}e>Zk%(%ghH$WPfzq# zq2x!t8qIj`VjN+z!EFQRN$7R}iO6E1I)#l5axGuo1WlNItb^T3V^1e*7ffT!GUHZ= zxF_Oe4QTvbY)BQW*jhnN>FFKJ(6sH@&O9;}>3he13b8A6~gr8qwiYq#LAnI#K6?mVDaDOEQT&TY2?JH0 z030Kp2$zXCXSRF@J_}{L1E`sxFmsz5o-iE95RMJz+w6se1g6T)N1qt-6<>4`nb&cB z$Pp11CiM@y=(^CifP9dVJb8RyZv!~P&w+VCc`_8GMmQG^QlcR6U(OY7l<*MK&h9@7 zek|yIph$u`DEa6vdY;sF7NF!8tIKPcG;N4~qmK48p(klW5Qy`-1v zJy$*zjfr@Cd_O)SVv#dD3KS&R+RMnWl`@fvcb+ff54FeqjH`2i9>Cqt!NQaF5o|4T=>(8P(TL18EKr^)fYndX z#=F@p%l_|aX*D1{puRWf@FHfW21_unS$ zo?gE4a#L{G9&Wu5%~KGh4cX@i4@{aVMSsPX0O&{FCL zR?(Ul6nomvD4J%s1~QzZ2zGFkmo}QpY{txBb$#1S8#gYOzq%Pm66usE2Y@#Sw5<_N zQ}&rO+!#t^xN){&hdDyq$N-)Vn>L|duJ;n*n>~*wB$RI=Vg$`#_3S?c!xKsaYK8F_ zKEQ|8IH8UVSS5DhNn5PWUz2*f&jy5dZ_tY0+-OyoE6e8~(!uKoT2=90@q*thp_V5fUOa z0NV-?FX0!No3BC|^#d0F$wdPcLvc`WOBbr2aKqma@pG;^2HR^w=FHE`bqFC zn}pwd&$POQatK1n2#749{k0lpOJslE=%PS`gD#5flB1hkr6eWSJp2MT$UmL5KM#s z0NNfvPy$$&v`HV~U4+TH1aZPpPz2MpDaK^l4muzB5P!S}A%xWaoCZuJ4?Xf!U2_W8 zOZun4D?#0lOc#gxD#`=M47{VXkpjWXp{Wi;g6p_Cply;E9f-qD5Xd7NpchI(rmT{2 zMC`8Nv17F;eWR%&L{b|xCVy;(2U|`3!1&3PaQ&N57aP6Ld5za4L|B!aNd8Wh;XjL)@#3hXc~?V}$&m zy`fr%2lyJyERe~-(pfV>lrf1OD(1-}!G}>ru?o|(IDq+KSw4OK+>Yu9!Y(g}epNlw z6jUmHANO@X^p45qK=j774b)#`3t%D&2K*K9osGgSqF55)5otKY^(eAWrL@MPrb49d zj;uBu-@g=)h>?*NvdJA`hx9WsnhaV_?O^SI1Aq?U_jqKWYbxQ=v74U4Ujg9Y9!t-a zeNw{4aTjKfvlw*xDM)rAX(+6%sFeQwev&U3sX0&_y}5)Fv=FlaY)z+B=89^ zEU@dJ&?0h|TJ1@PBN zhdThwj};Z?ft(ThitwFt9SSf@mj42DM4G1wFHFv9pbY5S9yI(Tj%Y_vz@EUlQC=ixmR}fKs$WOzmlAZ;?Wv~le-?swYg^Y??$Tfr-Fl>VJ zwJM_$gX!Qi*`=b|IoemxhYjq>Im_FR=|p$uVVtqkvw8ud3Wl!+f{vdcoD`M|ayZEy z$@Rd|lWPa$Rh?=ok6YF}fBt#G+?zf=L{NRT3&bZp;K^2C2V-A@aE5Gw48P1lcMl@c zS^60j$gDs**p3twZ$xGUKm&RMs<2+LAlPgO4%b!KgTw`5C8iau^~wR|vg*0E2r?%W z!DFykM5gXi(=&nNN2M0RcLJITC=j1jjXdi> zfS!RB0!kpZ=g|59NLCn06BrpvKwSy)aFFLenoQ1GiUgzcc6vc561RY_B14yv3!)8*RD#GXs;?;r5uBiS zgc>!X4Kl|FDK-WX-NZ-^6fwZ;B&tgw_7*PviQy&@fDvkP>@9?MUGPLYg2NB0B%UD| zS%3fl?;|B=nDT5}`pkwxhn6Sm&AyciKvsz8KgxWlUnxT(K{dn$ry5FyNZy7!ADV%V zb`S=Y^t_=~Lt0bOR||%t29^r38T6|~`-(#1BwE-&l^`snr%#Ij1aLb*tc}dzK*1LP4^D1j{c;f` zW3Uei2FXGc=BOa*VLiRQLxB3KT`yzdiJA_S?5a)^=0B__0gG`wkr(4P9?bQsyNWl# zGfWW61fe2O3O(m}K=-x;XA#rF`CmT3i6J`uElqXZ0#!)=I~Yys1Kr*?ff3 zAvEQY@GnHj$<@x8WcdiiYXq*vbUHibz_qdM^;}MPx+r^AvR7ASU`^~UX zArpck6CrJZS4oIxc*6%eJ(^@aC@tYCk%yN8e20%lPmB449$zpkSfUMFEj%8=nZqL? zQAA0}O&I^3aF8fCfiKqv>rKoB#s?vU<-kOgGlAH(r4gG0*uUsL5X^FDj2>eqm@mg?s8L^}zM@ zOtv{Kt9FFmM3ry{!57a>K$#~YL-?_*?P1tFYzD7$TCuuSK=sks^9_|2^!Y6{F4Dr( zuQb38dM8eNLQ}-hpFeN0M{wJ(p<{y+1u?egZ{9otly_SVl~cvjK<7z|En=H_>3L`X zeuW-;Z2#WooSn$;h-wSW0+D?oLZl!f5E&hWkgM-$s5%a!3jlGHFe*i)Bxy?r_`Vy? z4A|FlOcfxtpSPZzK-zAia*fewDAPd^Iq}7fc}|$oc>!eA00i9tpGdv{Xbcq|)XU>M zzmQT5&OCw>7#D&oB^z!5-lGg74l)gD>m=WVjs&Sg0z4u8ZxHw8IC2hS+LrPMA+R}` zZDR^O(KpfvYhsXblW)LDsD)W? zNEoz2o&l7aEH#LAv52N%nH#sDz(C~b3A2W9+5ufcR|AFY18eRfTtaDWA&zQ1Kn>Vl zBFjvAxh)$~1JE8qq@oiNG?cVGAo)T_3dk5}S?Hz-o&d^L@9A_XseVF* z0?(dIrbaiXuZK4h;1Mv#KzCbxJR5PEw^C2+{#zs=hEcUB_FIaQJy?qy6>q^g95pc^ z5f;JTiEboPd5E^@RgDFT$dZ~gSCZm#05t@WQ!Ii#ICbgLA;{OD^bFbL#jq=I)_9Oy zYr3Fyt~i3d2I|;;R5R~t#Ub~Gw!uN5_2vQK=%k@#oWOTbj_6vP&}K^1g-5^G*=_7} z9qTs0LnUhja2jsH%h3|+29tFYbzf++68na=ECy2q=Hi`j{J=Dk^aK#OW_7}g7Z`AJ z3r}(coI?M?Fa%3vL&3!Z-6c)Sc(i|}#;jm?VNkb&M}>sli~EVL~UK2O%WGQmW4o z-ReTv`i6Mzm>~*Dap77+tmPo^onZbYJP9jLd8s*7fK|)HW%l!)E5EOhgw`C_*L0 zi&0sLq4iC%MSi)z9b|0{c@P>}jVujSy+`2Xa^TLeLu+IA^WdtXv2+T!HX)&a`iUDQ z9q5SPK@8{6{o&GIpDeX)5}*aPJkmn|U||V2v4PWZ+_8B!?b0EHClm+Cj>HZ{+D{(* zERxyYy2XRDjnF0t$R?qpBAld?5w zmPt-dZcHTOdn6=?Cib5W(DLyhVl$#pLkDV1Tqo)pV9OH$93TrrWLUc%LQygZ8RaHY zBgghex%mkyMw?M^gas!GbFfl9#UPoUg?)Je;S!KD+A+on)DQkNIEHm-dPbulQ6IT_ z^{?^NCKy{XIEL_U;Va2ldF0@qA?~AzsZ?N|AX0D2vi<^uDUMCIVT#G< zW(b;)DFawOz>Shg1(q6`xbEV7k{qE20QFChL5lkknk?abAeW=tUyYi=ETx|8gybS1 zrlN8Vw+CtLDCBJrd6<>3{(%KFCO;?vUnc-wN2ULLg!*tV#P8rntJBOVgtY{K4@P4l zxD}ls1eKsr1?-qe7-8+yv0U6ECg3d~s{MzJtBhb1<>8$K;EId%LIsr1kfrYc ziHKA=F@~MUIv@u?!NLwLk{FEcT?EC1IIyq~JKuZsNCQ|Bp*$g1zat{M5PjQd#~>nM z(7NBHgT|OJbO3{GKzOHWOPxS!K%$D%Iy?Lx24;f?g6x85CoAurf+{i@x`zYAJzW8= zJLb)jahEXb#GsN)qC1*;7}@~DW+4ZKrS)6&l=!j>B$>p{B6b@p7X@#Xfzm&QX$Fj@ zYQIm(**T|b3liovRTu*30@Aj&OwR}gnWsO0?$*80x>WOr~^uF-5Q9G zp1V5+b{~K7#xBEwBLFUPrMVM_QJ{yl186Mp=QvE2nUhqCmb^56WY8=PL4>TPmP_$ws9@lMF5FP^=bB@hbj7*3q;%(h}cy>HY3`(q| zA9?Z1oV+}~s~ce5ATKhBQZa^&l739D0a>`3MT!%iNDN8OIJkg4_5>dI_ARWf&3yIS zZ$SL8^$6t)0p~vGt&(?Rr;(r$l@UA?R4?WM*1_|`b^-kX9+iT?iD3C?hyQP=KSGt6 z^W}!uhl*RmQ<>d=R<~JSYYeBd*Qp*7yFg`py1-eWC;NkKw%jbT2i3KuRkAX??`f~+ znwh1KJiS@C<&gfmO|}B75?B?+l={CGskD;TXw9*)G0j7VQ2O*FJ&0;wz`BK?79g+v zIAY`#)Q~`q1h_)B7F;sn1tUR3M0N{MDF8g>jiF?F0n>#MJ`Y3XrJ%9q-etf`aNRxV zgF=8?+SmOl4ei$GfFy&nkvhH~f~b|hy%9t}t8OBU!mvgT0OTm25!NnzJrUYPj^!sg zOe_gypl(@&lb<|wJPWS-o`m8@f0~D~( zu|G&_2(W721q;q0VFWNja9Y#|VpelM0_uk}AKO6;z1=9jla2+{+Aq-yBSB|LUyfA3 z@5Sb&KZl8uDp4Lo6&}qBEB0Jo1$GBvnIhjNgd5aN@Ey0RI#KOALl{`ckMoO&Y-)T4 z<}7!LHJbK-?!xwg^g*xz1eNarxbCt{P9H<_HXIcKVsFTeAa3P=(!dRLn-HWxdGIj` z0l?#6O37SM>~Aum0B07H&K8dkAqXZEXuK1ygETFm@C>U0Ei67P6s~3entS{hLc8%D zDNOd&`QmbY=L{hjK(Ad6ju6gYH;O}8a{=`J;&Tc51ZX5USOuXmY32ou^(5q#GXuy6@v(RI&E5R94;|oQIXJ9qL_QToCNm&0_OjML0 zx`+Zm9pD8Om4px8yn^_}r!g@~Z_82a;7kaX9qX1y%Lvx)vV#LT8`xFKqiaAC@jqZW zw(tfD4RyHukxoBG_VN#b4p({0EK~OeG#-L_h(;Jnl{;gv9Wnh(hL< z0`!7aM(7QjjlC0(-AJH*a2|l)kTDZ5J%Y=pp@~AKBqFFlkR1ay5n+mfa0-V6iBB|& z0$|1r31Nhf7||F7Z!`k>obc{T8aTp8ON9bzU`I%H3(=4Kxa&|8(MG{&+m0RWxE=&w z{xLg<+PkSKhR$(kzCf7EADa)=7Gg?J=|h}{Ckw>?CkRADln#xFaEKfU>kpv3PKMio zbrFaC1>|fQ$QJ~@MLa|r#UQ!~Z)1nnMo<-`M|Z-69l)k@Mo>!T?&4;#sr{9R$Q+Cr z>imaKoCwohZwvZ7jxH(~HNf>qS0gMB3EPpTb|Rf4Cx~bpX_76I2ES*7)O&kP8rS2M+{oH5Mpb z0QG?Wuopy$kdAmYO(3N~rxx+CWJIJ&ERggAZi>Kc3B3f{0zIOMW;8`qFJY?j55#gr zk!-%WB65}YFioUS4lz4i5mqY@1wte=LU%K8uCIXnPpZRhmr#R?Mf3^&rXTF+WhfkC zgH=Jf8~M_&IZ&SUM!8YBRuq+B$Ufk1RjSB<2ABbePm@Mh0;6u(qP_lOe{XL(3gy5~ z!N^$pt_iS#>PP{YOrJl`gODVVK2TlrBX6)!a91H77!rBj=ZQd={bP0k!-H512Fpkb zTu1SlRNnAHv2&1^ybuT#sJ(-Sw3rxNysDzFk2VXqQ^dSC(ZdEi@!u#ui2Z%=4P}(F zxQD^E49KcC=4h+Q-y3~6;$vcn)b`!mVmc~6tc^b$^(-OAa|2Q$G$xbb@jomK8X46^4{`*i~HK zIlge@T1yVCOJ;Ce-oD2ei+DP2~x(hBu)8k#imM}wR4Bnr7yJ76caeN zEb8lYinBEt-Lhb-yRK~OH|H|i)u|gB_jVaKjZznB8F!3p3vX8m@vw{EtuSF6xk^3$ zYQ`RGmp{sx15I(hixd--@!y$eTZ0N5Kl?f>up7?ji+yJ7ZVOstzqIT2+Q2I00|t6} z?TQoQRQ9+FM!>ZUaCTX~?$|S+>dMB?;;mA14(RL~@`ki_Dgw8>V~?q;apfI>T3(G-wG^$Cl} zxYreO2levv2YAHYVo+hs?yJNhcEl{aepG%8U0w60xlwuLA3rz&0$LrS^U?Caca~t^ z1k2?u6H|0MPMNBP+(E5QKTNL8_|xN?pIbz`8v-fL#dwponz=ns{4>x*?*4<%tZGAi zM7!boDKz&(tL6z1?vugI&RYS%?gF2I*+6AJ1d$<@PdZ;$&+SSH*bBWGwTICv)-l>- zm6n*3a{`$|9U!SR=qZ6q(EGYlv6fdVqOGK^wKWxxWM71RMK#=MbB@uWZ>K_)h%h2t z-f9x%0~sK$rq_P>*q?tU}47b59^T@)A&F zF@=s((JM1ty+Dy}_}I zFqk&UzMAV{^Pf`~KNjIi8pyBU=;*48Io?Ot^b7I}NabtAoSI|#w!Tq9^TAiLGw>OH zgOoz{1nc!UlgF;mrPbMaaBc1Kkj^iLJ$_9qTo~h(JvCqYrXnb{%cEVFDI{#NpdOWN z-O!S^{>M9Sy8}6QD@Sab`W9Nkv^#p-)ygk>hDiFEm9q zP=ka%1MK9;Ywf;M_^`B1sc+u@&QrtzS+IPY5(d%d6Xs0|whw~SD_muqg~xt;bOZW4t$<&Uik}E<4nW80bneCoajpVsEKJ zHB>6l%EMwFhnY3&r=fR{`cDy&PxktCs}zJblSE!)M`(a z+j>03)CGm{W_z3Y=#muPckN{$g>?-Pz(MSRsc9P96=tM7ARWYgqu1vHT>*dXgJSZui+0!!HGl`tT!VMvkdgJlaUR$q$b=1a zfSREtNW4=hWn{RH+h87m`_u__$54<+5>TxO?C7y&|Fv~|$Gz+VvZkp|Iv!83Q;1$C z1kC*3ClwTXpM{Km5Y*{;7IL@-$6@mZk73*Mma?BdU}#JlM!gy1zr57KDN8&h*jnZY zh5Z`30s|G9b~v+ENbD>@kbrz*bDw(p@k5yXB24kfC&wE7RAyx35i?V%6b~(3vIoS|3&0=E(1B;8Gsj=H1;?fDEunoTBK==S11Jc z<*s!5qg;R~v*gLZ%&^o^TATWOtndloH7gV(2j-86iQ6!Hb*MFnYsWA(p$D7gp@f+8 z&wB&)Q)bM*W4Qyl{msJ#=eK2rJ^O<`iMqYr5o=5BM!~_dLbwm!#NCz7KNl-Z{tPjC zb=G%U;Z(^7(yEv{_SJY@v6N8(w0&Cy-(jGh4vJJwc}{7>^8&mx1$p8*j6PI-rV^h$ z`|x{?)0ZvTJ-$*=RWw#VEh#_WggidrbPn%%rO568Kcq%&nd#5Kp&SgMtZ#090sW#A zWYHiXmw<5^mZ)Tt)^F^z=ZM{`5V>CwCCDvcJ~#Fd(_(OXQF~eM`m<|_^SoP_ucLFu zO9L)P@GMFH{y#mSxnRqschYwhaGPX6=OfPE1+vUCXgpyKIMHf}uOa3dEd30kN}fG^Ln7N)?aY~{tiQHwL!{ra z9uO<-C(#=CJS|%_h!e5N?Uih45925^x$cUgYh#&d?%I&{dcc|}rQ{0oB8@VHG?(1p z#u-aPHX}OHB2QlX!96~&TT?3X8OXEs4Gm-ofeBT|7>uky#iO5qsG#Z1KT%W#e4SU5Ell=foqS_A3|j=$1Nb4xFy&LM|U>u?q=1FB}zO%2WB zKX7eupE@MsBF@Yzt>9~lm zLx&aow6S6S_VAi4y?p-$1clF!ope?U+BbZgyp`BGGxk)&7y>FI*vMMc&|28baG4>| z3{{|lh*tCvev;LK7NHjQ(G{sW$hRjKP83V`VKf_x_5>~NippT&;0H^%PktG?9(|q_ zMfMO`!|#^qAU`Zvx$ihsrD%wq2LeAGhYv_fPId&UPn=}~&@vgo?2(x42RY!{Xg0%w z1UJM%fEkg3XqIQ?H6?@{E8`bmSnVi_@=i z)_uNK=WAuWj!A--Y-M(gT)X&bmD9?o);`%0jvT?L=Ngyu>)XmYig><6@~mH$Kf=*D z#c=J|r{wPYsAG=^YI(o@hKcXF978Od|JGa$Vz+RsW7stP;_M;uJbkw&_z+j_Qae$F zvAUy=x1I8mf0PHw{HgjhkGMgP@w|c1ZJI9azETdKH^lcK&cB+c2l-NM$Pz!1=Np5c z{B3wNxA%FtL#4v_jQ4?CYj);=K0*TgIG$DGZ>=LHCe7!(_8jdkHHni0$cpJdj^IWI z0Mk#)ANC{OlVmM8S~e>THh9Qff$ZSGs^279fcAkMv+IMpUuC-o39;eh>hSaD@IJYv zL|Sp+gS(Ex*nP#xpMuO5Ix|wf>jyTnOWSpx=HPTV_^+@4&&Vgi&@G})+`!;kX?O|D z2~WmtZj_d806y;sWTT(FN=!w0r2H`iDYZ!AGBJED645>}Eo0z|^zl$8u9L4K&pjzI zv0B<=h>yIVjkqKk6!D9w4(9%O&*z9R9mmRqMD=J2O#>93R8U}wk+PRd1@|E&d92`? zMZ7DtWO@Q*TdQOmOlmvN8U%X``J45SZ$X_Xj`(d*@2j?#iav!98AcPj4!rfafiyCx z9dLPlV`D;pYTn`OR+r?%3S;`F4%KQ?Fer|AT)819Td}=t=%Z-+&uuX*`&34v;|uU> zE?r?3(9e?yy?_4#*dxUBC^AQnrkpHj`YoIR%vp0tg@gD&vS;DA5pEHbs|I0t_f___ z-nfSjB`YjI!LqiYQ%6LR?CG1ByohFv#t(dW-c@joYy?=cdO%{1_^Lhx@p63$Pe&oQ zk?ZTXH)n^GS=Uvx>AGM5RI^}_RJ5BOBu*a_-w4ds9d@@V{md<*Yk2_?Q!3qf_zjj0 zVHDY&qNUX$w)wFZQO*ZQq&td-VhLlDl$O?Do!#;-l(xuI@>e9^x@+EACdd@#+%X9z6U@l@`KJu{CFip|0 zzCLf(*t+lCIPMo>7Lwng+~wdrUJ}l@CK$5KwCJYTh*!h8;CoqFn`sxqvx@^8} zsb$FTo!fi4@b2|XB$TQ)*`0Rz!Vt4}cv61c3w_*c?thH|j+*f)R8#+%mgeiauac&& zzuWR7vgw6s-xY6Yy&zw7<@)ToE@EPzD$85PTBBx%eSP#YSp2nZ$n14jbNYiB`dUrG zI>&8j?!H3J&Mt=wohfeXdKJGnNo;7`dX@aXN9Eejdai0+OC5=NK`&7Mq2_o3~6tnDgeZl)ee6X^8~m;gGY@vv{_~ z&n5S=B*G(WmgleSno94jqor0)=4|+pRVjKaCM5dM^qpl=$Jbo6vIssM{2(yZYRJ1p z+#@*J&42S~`1$dg1s;rvr~UL)YeS~JL#P4!ZP4$P)bA`#<0rqRjap?H4%hpAt{Hn= z%H$aH+`dOvs##^_e9<@gPpz6>1g2W{X?Xs;Y8pOXw9ZdTy4G>P+dg=p)b>k|>f+fP zJ&_EpgO_e`a5$@Q)g9cfd8^aLOg8y7x1__N_wlJIN&|EsIibqpjop%Y)H|=oQdK63 z+MBF+`u~1^xZYG?iIj7HK=jXU{KV?V`@2!NNu`e;HeOQaP*&oaJ*oThXK9e8ENe}f7c-^$Pp z0uiqA0x{jEL+xGd1thXv)u$e;I=cLw?eHuPj>+_&9j?2qJ{n{dalG`ZU1Gv#(x%2g z^;yY*<3{w)(&i)U#r&j{!}!)VYI!V7Gbrl{HHRKpM|P)Kg6rws#$HV~ zKjzZUAG~kPewXg|ZL@ckhY$I&*YK?xt|gf@W&8_j)={31b!%Dln~wi&|5?hf<8?n> zKEhRM3kB;_b#QL-{HxI$H0M{BWU$U^d2CeZKPG29*!Md{>E<=AkkoazfAVn5D;00B zc`c?A)Rs5Ey`JMEcNZtu$5|navs%F}f+HT+0`e|-^!1Kv>iYduH+oX8qtIea%{iSL zJ{m6B{l{osduB7G+>y(Bd5(hGHRe*W^%T+`-e2c==P5s^WY7KLy3+y5!K0joo zTC$iPB@v@)w7tERySjcJGdf)_?Gxht=vk4(YK3pn)&ff=md8lgXL5}0ow_nCI7f2D z)ca+HRh=Fb%JMEh`*zKQ6XUH@E%85stg;>Rf{irTzcPoCAJOYy+_&@YrJN4a-~DqW z_?f>!=&rz*%$xdlOFj<6jL2uN^k1w|Y%}$vZ=IZ2slNaHP`qDJ#vTdD zQ>P{cJGQYtNPNg&$>*q3!TK<6{HpIHPultj%C|Gmi;J{XN=gQ0({62;o31#E|uk02P+C@9fLn>wcr-Da(_P2YCtk5107xJbt{Zc(f_G%w`UsJ5S z;*#fPU#;OYxi1Wk=^V>%Qx3MY)vIy#s)(#|G^?+lIsLiiv+gI!Yj0#+iY`~I{Mz%P zYh-Q{O?TX(b?Wxo3)Jvucm^W-pYM79M}+ZV@pHP!{)c;1M#TYhjUR9PBUN%rqn`Of zm!;oQd1XxeZ{8`M#9XRcadgu3u~gP2%}#blll8D<*QrVE9rqW0XT6q@6Z$G@keD0o zJY=$s*DdwMB}UZPeY@FB+!emPTXGAO%_KK|3aUH7Yctk&CiGRE<70tuK5JUu%qGXK zl;gj+pUULCI&P!$S&Tvh#dnBqcJ{mYcTO2=MZc zDh~u0RFv2C)n<2beBSrzVnp4@?DFX!!CQ+L3FN)yzFwXjL@q|$fbAr6kguW1%Ka|G z(f`*MkMI;}g?EGT#^*P3S#$dBl}kUvRTnPNvSjpZfj?u z%zKP=6uyCdEv5UEe_XxgKm3UYmL13R`!5&CZ#s&XrmdyKvzU_-6g>?!ey_uB0&l+g z_NLqwJMkmY#VO?X@l~QFdyEHn2!=n`^4h5IU1;-5{y*W2K!799qgQh!2WYs|z*WeJp z@3!=KlVI^1(GOz2+nPPOKC8%O4tYyhZ^{-W4*pfv*2mVvOt!_ZTR%XY*~?kH{7Q?? zlvZT(RKen;{=z5smO*ERWtfx`ceyu-ELlF+AHYHEx&IFb#=`f$aCxt09RUm)ovnW@<+A z9^7{%=Wo-yy_GHYzcX(*PXBI|SYKFqeb&Q+x$EQI$wmDmo?+8iqk(?jz#1v<} z%lP;qugKuopyB0|U8jBt;x|^wp1yMF*4;4u6pF&?Pd%T~l`AdYzUAza=zl}Vsym0yPc>VVpFr+B$tWO7>F4&cyPWCXqPhMDND-&{`bRUN?*UJKRnFs9uy%Mv!7z{;zCZrrr28^Nsr1mRTG!lV}`#zh$*sK`npMA z*Bj;X>An<$j+yT?W-ZvMZaG{d_BVLJWmF_BT4>eG#s0Ha3Zj%_ zx$pCZZ;4Y_Q9tN~N!3Y|Xlt+0hQzN)l!w-T#y8UjXMc2X>SdqVi!VR^%6iTv-n4@n zwtsGYTUQlCDchVxAYEXX~#1B+gZ8ox$h?{2M2VTOWY3ZAB#3i8y*v5`K5mUS+g$vW95OaXQh?A z6`otJEDY54@VdLngLy0b8Rx6+hwYlW(@Xy{dJpRkbqt2G0-u%Yc!g|L{=76~bVBoT z%{q-we@1+)59c!Usq;G2`6(XZ9`-f1wYDV@yBe0X(wus^LMtMqDD{;p&P@5>z%HM$ z$1ie6zu){ZxTp8;QTHwSw4go<_Zs>=hqFnAS;xQB<=dvq(3Y@YY~-Wfiw+UCR|&Z$ zQ0JkR&#yAQ)X!Nk`ntH2%}&Hi10IFRC#F7URGa=48#$u0FO>cw+-tt%(6%kgzSFzY zuXW7*OoD(WYEyl5-(HuF1cqHh$Gsr>rH73I_0sQb+rwmjZfs$8xN?n$TMI-xz1q{1 zXLe|AS@eWkNU>c{>>p-t`}${;6QYNY{$QFm{w|{5tCEmZKHuWH>0E=r&xt?b-jdsS zcl5U!Z^(NPbN(DlPg=Y=rfccgc3WPPL6`Q>-bv{$G< z^4Jpc+3$;lra_XHuzI7%{-izgI>$@e0y>O$?jH@;7AzDgC_PnQQ{Ff&?E$5a-uL&Gh zTSVc|5@(y&&X+P+<9SlGKyr^7Lt?Gj|hTS|#NS2gGpe5t*sokA%v z3to9#ZbOIdCa<)WQw>=uu|9;+{d%$?7tmN;S zqwCo>!^E<+#IO+MLmz_I1gcCPEk1IAdRjDgYFQlh+6@&C)(Xb?=&rxU518|sXo|)3 z4UFsJ?6Vc#)AI&&u5H$5%dhzAJ|!qXQLNmm#BHbs+p`tH+c_*dT$x?aYi z3eN+3CkLb&;;01?PtL)P^utfu7PA%hGHDi-^fax*w2ktt;V&Yh2A__EM&K{=R?Z?8 zPW_u%gONoG`Jcgw!6RvZk8;oCha7GlTRxckyGfRe8O&ZSsJu+<kbsl$Pz8`N035kHL#dXFu-c+~~l&Z)V|w%z{f=4WSW5 z?z3BYVO+;&Y1_*ghpz89$LIJTEQ3I76F}e)Zh5Ggqi!jX277fx+9ZgUz0dbjML71qYyXqpHJ=hoEew?%}8bqx%BK#dY1W( z1C`;I1}CMrW)INy90SWIMa}sbE&qLQ7Eu#=O6l#RqM^@{M48y{kuaR4$SRTf9X$EE%;2o zs8Lw{{&McwN7rcs)*IQsxF&hVb*UPrT<+8AZP|T`5)Uq6U6?K>?#IOWU-#qMo$~S@ z_d}1!ZV2f3%)N6S+)oOw{Dn2&?j^01#GIXh#X}v`tL1OrPfIyY#N8?NLGLwG+@`}fPsiz&sGaLvw3_#Av=7BQ z#GK*YKphQZo0V9XX+DhZ(>cYMi(_1Cdw2>7+M|#yGQDT!7O}hX3*?-Y6B+sxzRs~h z4Y{m38LNz%Qhq;L$1}RlSf(yhF)3|qH}%M8lotCde>gg}dQ`ZC}62C8BEqE}??%7!QrlPfS#&b+*t1I`oP0Jf~<%VkNh;A~dUU4h> z%7YWr>Jk)|W`otvKIY{3x}o?H;asz_$`;BVABw5ysfqEpJ*-dd*Ve02<lX>rwXXR40X@&knoKUCBFQ z;3WP3CwC0xJMY~sD<0}SIA<`uG0S}agvb6@d*m3o+7;M8ErIGW@D^}P4#Qr04Yt8C zZy5$Hk-m0!Oq(ZJ#wOXv&ls!>q^OCWk#%u%( zW1QUmCi`3Qy{~4mHiMyuj(NTgSUcEFQ*?wwple>+c0%;V&b|>U3+~vPEYOso`=|Do zlMKG!Izs+is62&6o~gEhVc*GNJ+Dxo_05Dc|H`{N&rDtBb;`C}Os%^~lIn-uQ%TpU>$4Sz1D;pJYC??^4y%cWdrN)hsAwEI$ej} zq_wfUOI60EZ^I^R-##wwaK((+qq_C#t|Z=V`}sO8pAjI${IYqvzw<)aIck9#)nL(| zV?SVrs#xg@I#XesFuehXT{?yY#JTvNtrk?)xtAM?=kNZ(1pmigAgGbdD;K9x2Tf>y*F^rcF&>`<@x;jNICO`!i+c8!3-H z@cSa$=KPOEz56?v`aiEB;o$zyYlggc$NQoKHY)wEm)kBs!vx=W#_*i@%*^`j)bMG+ zh7e}t;6JzgwATWi*QZWx82ulg@_&0teEa{Cx8Nr;K1JBoIH&&q@b=zuQFYC_V1r0d z5CjAzCjk*e$%4>;B3VR|Btc1%1<6P^Spfwk=bVusSyC%VMskqYO3oRY&|y~V`UZkVw6TNP{Rh=XEXjOfLHyI2nH5H27unx48Qn-e_Zf7N)fk6P?C-W@57c?hcW!t1 zAENc~n>*pcvWi}UUh6-2&wGk7S|#7N;RjuxzxN+BCjKvtq27&!crK@cQPMXPkK>tH zn)SRXe{y+cPSAGjXWS*Y!ni#E0H!p!2IcOy_1MqMt_Z+fsC~}vuSNZ|MYFq-9c%(P zp@yNyFw8SEH6o(6b_y2&)XX*RW9=p;KYfk`#K$!RtVyUaL+XBDGD>t~L>~tn{ilbp zK6ks$Y2-(W@he0k$`U(NW`pguy12zE?p@5-QFM2A9-3=u^bIp_>3&L6+tp^WuzYbU z<-n>)2C#{$+&jmt1PGWigS2CXOS1o=J9@=&VwgWx|Hx`mERc)$CY)6Hog;WL2-@zt zf=_7nl)$+T$5G_RgX2Oh-9u{gC>H1_vO?Xzd1v|Cwc0y8KCmKfbYOW+J=ZD?r{X7 zP@iFK*FEKxW1(#v-S=8n&^4~YN7acyt0H)QpLnCq=;PgY8Hd_GmO+(!e#O=IjpH%c zhw0>$#&F31FuDq#jVY!F;=Bbxb)e_xD~L=P+|E&W~@rSy$@_ z8cQ1QTi3;vAIFcI1v?+nUtjP0HMBclg~!z|%s^k5jYU7N*=6>ltT;6BoFcc{s1xbG zX^yZasDH8>I1q=7bYwbd#7Xb6RXHqe~@)xUjv=pqPMdS75HSe!&}vG}id6fpTl#c#P?qTUpb~z{VYR z;f~q|M_D)Ukkwc=fc@Ula-jNXtLJC1Gr*~l>M^`+ociWy)E4u>xk}Lu^Y4fSGG-i? z9NR7yjAh}Hoe$Kdut_~PQ=N32+jY7Mzcfh4WqKU_xDK0Tsr1Da119%KQ}HAY;^{-e<~_u>o1 zG~j7J9*ZSm|EOa?JI?G~r13r&Z1b;(uFHQ~mE|JMxybZ))A2v-b>QLqbLfBRy8>6q znzIq$J#Y};P7wL|^Rtg2{`O~s-OrrfRw6!(3qpwbNwl8-}%NY9D#~bBpJyX>@h;ZM@M=0X6>h z=PB5P4kn3Sng?zEeW;AukFJE~_3()-p5vZAVCw#thLX1g@lB@-Hr1Rb-57kajREKk zfke{Z<&iC3(gqrzXbK2QY=u5)riD+g9UVvc=D5@mMvO!rPipG1nQ!?lDydqW{#^3= zJ;9ZVzd+K2{7+K&hi^y&zR~iBZva}{zrC#Dn4pJ`<|0veda>OoK5yx$GoPiR{6xXY zINsiE<6^$@wAA+d@$|YMbGc#isQ6Tbj==gn%<+1C(zVz=bgl`-5661LpGUUe$2?FG zTvLIg*9Xg>pKgUTwXy2P%ObGl^+dlrSY1K(fhl--N-7) zY&Uf6Tz$MR#cKy@5P{r6?EVH6UzIy(PH=BO$Rg?zbacE$$-h?_p0{QuU|=~asJtH! z(@DxSMG`G3jw>FrPhpdS40AK!M3p-c$@cw-?529LR%!KmdbK8gJ8AOZj`_Nv3i^ql zsu1E47@HD<&!TzPQm+@xuRHgWp~8I(MV()vy9dI3us!B^$^9MQhbm;YBH*9BFuS@W zu2rz!^pj5yFO`z_I_{jRQ^#}*Dg(P?C??6t@8s}NKH~cClVqAL@bIE=%QyQcst7)1 zJ}vupiHgHyRG2>MRPRUO#+-Og82H?@BJNvKP-+>XHdaXgAJsqB)ATs9&XFceJUj!< zFgPKU;E~B;%;dW7)2QKd`XhGt`^EDiu*zqeM)@cE^dwv2Q(Un4>tSX(_bq)&VfX7L z7+BZNur5xtL+SjNOs)u=JMx|wj`B>-d@G6JCfh6JsUf$};y$aa>78QI8(9@sj&zs) zY{8o>G*Ln#?D)Ff_rgql;f*B$zjb4cMjTeLuUYmTaq-D0&#As|TQ5`o zjcR9zK{oW;p_A@R-Ra6;20GqL_u4$^sG|~%A}?%v9cOn6Voc(0-Us@qu@9UF%jq(g zZtS0lAp1>vPD`g;+G$FmyAAxVOuvtf9fKuK>!C;qe=Jtxh*3`o5uX`{;j37OTF5TZ zWf+Q!w)JjO(A)%!$Yv^|O!n<60`vvXQkK%IKhKwaxG_&UjH@(kvSo~J*Zeo7|5nd! zIxi%X3g#}|CNnb?QtfwYcH$COwEFTSGUr4%U8i2K{dU3XOV0w{(y&kP>5Y=zn-j13 zN=~k8+e)vz zy;ZzAMhZ4Hlh@RSMGGVu__)lIk(n45{83i2Ph`m=Ka54BEz}O44n?EI3{1SDkPp{2 z==qI&08fGU?@YbK?7ql8>LkF)D|*Q#=Q=kNkvoAz6G z*^>WN=dzAj;}L(AHDu@F|F1e8ALsyoe+WbU^A|xh=4^Ns`&ac1SME*&Ewj6<{D^D- zyuXzAOJe$G)OFyx*YmAC8>jJV6>Eqs;)V`n4Q{RnEPY^Qwa5Mbjc>uR1JUq34L<+n zdY``@%Ka`G{{qD}&Xbw+4UPQK0_S4!6})@SZ%Vff!i zn?}2QFHX+|7C#97;{zCKh;$IZ7z8dUVO>^ReamsdHYg{}l>#Js<*X29J_VnlP=E5Z z1ImbNi=WsR-z;B`$5RbE^z%PdV;Iqp)5W|$Z#89yre}dk&5JGN2u>ut)#eG5>-*sk z#`%ywSF%u4FFx#T+-lr<@Y; zWk*k9e5U;A69rzZw$r`Fq4kJuDK+3_e}?dEs&8bWiLHe0e&P0}xUi~m^mJc1wXAmG z(7ll*Ely60iIWuZ4R0ayA-HCacPeg`1e~0nZLS@N7b;`!!CtrcfIih*=)Unr$t&pp zMW1M@%B{oXxGo2Jm7(u|c2_(;s4q%~O02RVaFd)?Ob0KN-v9)#iWM(HaU} z8JK4>lgXaRW+-=nl?%q=wRwNQ<{~!1Gubmg?&Z)D=8j`q@%O`+GfLA8j|8j^rD#ze zAIAN00k$Z7yJd0`?;0{0zn!`I*Q7{TY<^(!RM2L%0RC;1`SRR2O@MwnoS!I6oZ4Zs-XWK>7* zX8UVZfH8IIrN%*ar;KGyZ1ZlJrzVYp*p6yc?zD^=?nvu;;XD_n?F@9c85wymyyZk1 zKr1Nh9b3a6dgEINZ)OGzbsFc zo=5d(%GGfVdax{D#ha^v!J!n!<4q2(NF|Dbq~6JpUrh>XE2PG zFtQzfIjjKr{&MA_xEUAlV3>IHUS&6gK{{jaC1==)Vt^j~CpmkcIPP}JZBd3fz$fm6<(vtX8cPm&2Wz2!xd3oO!)CY(9m*xn*bgn zymf@DT*nQ$e>&}lb#LtowmXg@S}Sl)Kua6$;gnr&R646>wp<}xOzw341YzKY?gVx{H%izP_qqChdX`8NbMLNLjQzs;?Rid6>+^Y z&?EmwLtig>d0#|1cC0P6OOG}ko|PUL6G(-SMr_i^1A}CPhrI=9hygri|B=AQA2b@k z>GnZzmM58Kc8VwFDQfE8F6y+2%sTUB>SK=Nkd96=JQUeaK`rD%?9z_GC<1n)${o{b zIrnS?uu;zE=Wbs8?Ye=9x{-5#Mfk)m9?KQFLv`vYea!l*c3O;0=R zSXvr!Hsn5XJm+&Bm0a9o1+6xj;`tr{S0AnOKMRwF#nWE-*|Jy5eL9RrWT2(c$28PK zx6x8*lVu<2(s1)K=nyiwtJn?!D6S)YvJ5P#12aJuVLfxQsdeV6?1z~Z>UbS7S=?|b z<`fJ3u=&}Y{mCOK2Rw|gqxjhGbg59W622aSZmA+aKHHR>&1A#ssg8K8VROtLUHC1o z+%ciC8Q-k!0TAwy*itTV^_V?+ay`(e3xJ&ay=ugiGVF!C&9ks_bchE{#az8);rvsg zRdMKPBKn!6;G_{%0#rZ!Oq)!9St0rC(@DTuR6<|+T41mDAX^bOqkiE~7yj6|VrS|w zIfrK=Azhp7qqwz7?Y350{~Lrdav3Obv5qL+Unb0J}2 zUE{!OoKa|Ue;=B*bfP*0JZwDsNr&>M(QUu;&t46{1YzA*jplZ7V&BpWspRuIkhgj^ z5bLsqk4`-tdkuGQI!RxVBafjRMyzGFzn8X_Kh$%@J-U?x*H;}k;?xHrld#^=;msMA zHII2Wu!w6pdG?d{ZW>jxA=G%(Kx4B$jUT|+MfpSb2+l#cNe0XlU29u`g%XUZ)=IC(YNmN*j`hj>xg-O^a~9FIe>&c9~M;R6ga~~ zet=GuI)i0AfBH4O#q?w?;qbe?^Cq9gFb*3;zjiAZc}pAD9lHzFEmZ?cDX>ksL$l{d zrmse|UCu-BP9)!Dp4}YwVT-b7B|6@9KjhI+%AT+FLH{-8u{)UAXrv3z_pTZcnUQ+* zst2gnM>>fqMM8HOd155ww($ORX(ZA>&uSE*=xngQ_URPWIlJ$;L4bW2hP8;Q?9D#5)e|Mz!;KFXu319sCg9yZJY{t8KR)6+< zzE@IjVo~w(U<>)hp50R5akQ3 z0(yvhf9V}A4x`2dyJdiaV=aPKa4>- zIjhKX%;DG5FGTz&NYch>^BjlRb1u7e zU=DM0`ON3cgnzH}V86CQ5^07~?{CDAfF%duBsY8YqBvQi07AKQXKFVY_`z{&EWZ$p zQ0%)?b$QwrvT-%L8?sib$06)`8E^N5I3$F+@>D-HTWR|b2LA+?1yJzvOOAK)F1 zj)^m06gNkWNmP8K-)78;NPlzeMjPzA$DC9zLDzUUnK+h@dv5LfR50ftjf|n#ik^e! z{v1vv#Fu)bdiS!UxOP0gO3dVrlqp=r8hfKua>@Gs;4G!aLH-F`?nL}}mxn9o`LX!M zOmP>$*%=x1m8c_b0Kpjd?U~|Y0U0=6oCHBuHPTa%;lU`Yc6{UP8fB3x9jpNseZa-IAI- zy*@ftlxLHYLMQara{yjZcjobC}qh_fd>d zZG)>OtDjDN8XL5p0!Rq$={~^vohwCh$;q7*-@}?48}QMxnmm7F(D;;Z6AL46N#_gy z&60l77`Td$xFAQ{5JG0>tJJWe$$qHm-UFH(Xm2K`IhNzrlonnA81--Y{&#@IAP)R{;cT{;cwu61+kpiT}9i z_=efz?Ok!MW8o|Eod?IoWzB1x{yRTE+a*368=KsZV7i7g!@jxcM7w!tUgk~7^bYsg zIk(C=*IRT{WImBzTE8+46*w?`x>{5%{+vdfSOdVVI%VGX+CtP*+Lmpv=FF1GpF5B*&Hvi??5(2nZbo*{J&*j;<(xNcq{A-|U# zcXu0gGW%cQ6se)7j;g)>0Fhq$#QVB&>L5#+$+74}1(I^&xN?&Zg)Yn|rZy`z+KEnI z(GI&~WQr}PRn)AY7slgb6Z>C$oJXBT^W+TfPRU^ZQO>up`TiF#Ie#T{n9w&^(~j-> zhf9z-Bk@-thcP9tuDTd+uKeoKz9M+UhBU67<(FgCiV64K9{$sHZ(lqYQN=tZwl4Isc%GVpo6Ezb!KRZ!HM;uO#gMSup5-!sar0 zH?zg19?b?PzxbSj6NDARmMs@IBWAdO##omBj)3D;axLZrpm;Y-xv1IP>)D-8*v%wz z7*CrJ(kbV7PQn=X$OtvN?W5+*^us+RCfwyBXvM0TsblqN%#Qn9JqosM7S3eV3}|oq zrV!%HY8uTPw9ShDSj8R=;%E^SCgIw~(lcN5oT1|=l^f$)(z?dd(j`zcdWnvYFFqfa zush96FE>$K0K_Wv2G(+SOZ?9P5Tb+6{=}j|z{&!#*$;;IMI zJeQB1?i~Gt3vVI__F$hA1asy6k)?R(m&US8@;fFu?ROi22lwzN2oZ(E*CYzY=nlH-@5|Gn>jKE7%DsmD0RAvyf%BoE!L zP=Y3E>Q(@V?8IPdpg8<8Psg);(Crw^lVgkWVIJ8(QVQ{T_cM@;M#+z(x7|?! z?X-nd2w-TvK*IJ!%_iAYipaNf4uztG_%A2t4u3i|>yh+AjlN%(_?8Ma;k32ANGE$= zRW<%lzT-|=t4Z7|c;;L}{0>^H?T&Lt_+zw=fB-|{0@lIl9@gVtxNn3)?DEN3DCe*7 zT)yAO^pfGBW4byU6VV5E9GOC}8FK|M?Kja=icE!bU80xDfak001t~|kIq?1DgkB$) z7&^Il8Ib(?t9u_>%a9uUU%r0~gLX>fiHI(4P;!J@+<&{Y+0PTshWYY+*Mk&mH%B_W z%@ZzvJlr;@Eeq72-EmVDExqAkK2|q^xC)}cRO(CcGXSh|_ue@pF{NA=kbVD8^t2#y z_`%;00Ho>i|KgK@{~aUrfB5#`f2W~PA^XQZT`oMV-`0?&A&dLCzZBpfD-$Y#i1sQh zP1~Bjc{kWQH)g$%r5Nnxy#6;&3=ob?baTU3fsi(l9;f z6L_DQuRqNH-&7Nj;TmWp2dU4Z>AyFKa6EerRt3>$&mn^d|L8Ehxrg7VY;z|xKb`ah z0?P=9%fAkTjr1~T9(jqP4}zTKpBw)9LH1mK!r!0;bnX9VO8dXn5Fkd8H?#W~784NZxZ(ZJ@@E<;ZcXcO+fDoP zC=G;sDg1}?m1#=T=xd{?+qf&mp1Au}2~KIt7#z_y{2iTe^NJ@9(SFA2oUO+#i*F~z zZPGf`J>`0Sr1C(>aH)GZ!?Rh7Ca`mGb0I~1^mWG-XC;3&zTSd3;lq3V%%gwzBsBU) z2GgYaqVH1^X+CeKUsPk>`Z6DeKS}RpOfTWMCk(z^S01v?PaVru;8knv(!kAqi^|Ck zj(4}de1Qg>->5s@^wesoAtgA_Y3%BEYMraq>R+GaAR5ry<%V{&!C@kE%eJzA3lu!@XqkDi=Tn(mK>8$faQ# z`)k&~+i>H%^|;iz;J7a+=IP-lZ`?1+ z>hTG?zGH8k6SG(hD!*INh0hk(*u8yeqng_^Na-kQ_ZbjCu9BB zXJgR7wZ9FBb$+>!QhS(BB_Nk~It3&k4^+~1HS9Het`i_UiUa~yifs#M5Lo&wuM<$& z=BYIwL{A-WuKe-C&z7R#c)s0$mv;^uLB_T3*Y%&6bJmm>{mxZEvl*x{GYd9G)^VwZ z+I^?f6>ZcGVSNx**!q9|_4?-VBVX!<#$4M&vU+c$ilOn4Kz(W{{nhmcb}`>;{0e23 zQCNAJhtv=D&Jk<*^lf8;xZiVu4~oei*ToMap)1OfkX;fP)|>y7*Lb)yJl zn&IoisYsJ2%!q8SdakYp?3rp&1JbMJM=hbzTuweH7pOeM)oDok8>HF0Xy-igU0H*& zdP_Bl$TN&1fvX@gI-4$(5~`H8agdWmzO1fe8r>R&_Kl~MQTuyHt6%G1hmNjmxOqL% z3@*&R@^hh_3;Js@AfPKcHBCF_`P!@VVt4IK9>KU+&3x_R%8$6A@RJ@BuH37*VL$g| zwkS5oz1OCCl{s7U@J5f?)gI-?8jB$vbG6Ir6kufpH`Ee&nvZ@#L>ktS_73?-S}=jD zGZ~%Wvwn8cMoL8&VIPKk#LTQly`T>3Z6@&Ii`WbW2Uz|&aK@l8oTpJaX>7ZhRDI1P z1A$%?^ulWNcc5MDg>~kJuB%KqgPR*-1cqxY8(c!3I9i-uagNCo{qFV=GS2*}SY3m6 z2Xd6CDWGU=aNmxe?9~iG?c_lqA*E^hwdF3&q%o=~1wteDuO(vAnP1QSv;yjdSKapA zCI>;O)GyuSw8y{L`9o{$=ZF4$dGo`AR(N zPokN_bLAd{S46t-7lCZ%s`7bC$OUx`J`c#r<<18M&!4yNCm4BjtbaNxw8fvfFfd>IKb0bB1I2p+*g-Qh(@ZXkj+FTeE@~(sCvR{q~&RR%zqe)x&YvsNmwr8|Y-mSFvK)#0i$N z;pzPssFni6Q88Bk(U1OYA9B0OwRGuiTx6Iimz&`GQ(MHE(OuExzbB0P&!BH#P&D(% zZz?Bv`l?N=Ye#1X2PKkp#xy+=y=!CAAwWT)d*gtsO4?$`aH=jh^BZ4KT3}7SnwKr3 z%6%011!*#Sjl_d7F`sG?7e~%?<(J+Tgj}vQM_?ihB#f~{6DeG-51Q!ErB>Nftu0UU zE#Zn;hol547X9J2AMUqL9ab0cJ0A{2yBv(nqHzEy{7YqgmrDTam7d;cX^&Qa z*-`1XRY{ZA_N2~XNfGd1?Ux{P8~MFE9^>i{aZ^Bi3S4Y3`;x|Ir-4*Yv=11cB{wb` zYNv4LXKgoc?xE7DxrosuPh5 zq>7Smb$D-o+&*6YDv7BUeCV}T-%vP}OU4lxxpCvijV}lzN#5We-eH6o-NwlEe^@9KMNi%FgBO@U)&5I#WgVHIht4Wrl%G9^8paUTOaIrs;vzNrlDn@6$sFG5NkuO3CugUU#yd#4}~ z(*}_uDLWl67^LyIo;nOjQ3cVHKIN5(eT6oTCobqDU-uR7#5hje@oSa*Md2*Ip#Dp! zX9m*bVmVDwK8T8-qV6ld4euZ{t(GW90Jy>CRyccEs{MspJ-=^QF`0&HQ7iK;|al7+)lB4sOT7KgW$e{V2 zMcV6UYd@`poS`8j$Q~-S@!KlY&a9F#gH_+{9Jx?)4XIFb;E1q@tn0(a<5Gq*JAl{h z4^fm|R;T(w$xcMNlZ!D0@vRpPM=Uygj&4G3e~8~EiP$!~gr2`^Wq z>^ZjwARmLXF56l%6y}9c$yfYqGxFXy+B(Vax~&0hdm}r(RPv#LCBYBOz zwkmJ+lL|Oz#?cv}cnRaLEE4uJ6XS0=!{3rTYkk(Rt-ihBZW5QvYPjZby2|~z-xTYA zvNgt2v&Y2tf*%mLqlFuo{GZR2^9h#F{_|80aX|DDDc&QVbPYuF!%$@a{) zGO|<4?|oRHF!S4RxH?sEra;P`7uoon-?#VA?sfH?Gl=Ju35~qcY(dpK7>Q8`F?%fC z;3cDI3i-y(GW&UZxH>7jXZ8?>*CqV9A_W(Btyx}GyOVG#-{GYM=-sOv zq=V&t+hA}>V9!q z%$PdeF7s~4gU9;#$dg(>0QvkT&Oty9!2S{Pks)#Q5zObp^a3(@?;Z)D&M7cfDSmqt3T0zb(N$R;ubsw)-f-WiDI|a|UwJs%L$cpYyxm&5I z9zP(i{u8UT+7S-9s~$dubR#aEsK4TF{EC1j4a#aVTYm*Ny>PYZOaLfXo0t-qL75Z=htF!_dOoakq_*HuG)>j^J$99n4 zc6SGT61xieSOM ze<#DnB}KmTeP=0PPz&j)MsRyWbfWuhvRR+W?raqA3D7pyl4|Q<9pztA;0b$*3SJt*&m#l z5pW<>eXUaZ!Hnv=&ZX%oWAi-0Cio>rpGYP){j+nONvY*7P(yV#!owhS)3mh~A$@qj zHY>Y=#ce_zYyX|Fv#kh=#sklJ{$$?skD|bxL(?5513`Y)`7w(gzJ5?J+(7{W#6!dA z6#@RGg5nG%_Z)3)E3;VJHy4FeUaPz~-=)!)9#Je*U%i!j$;dGMGi%YI6#E0Zrt@{! z>7?c{LL+z@iQRYr#c+QC`@`%ldUgdxg^cdSJw8+3DBA0(eNv7o7JiZ<__WgPdlo%A zt%vIb8GCO)PGH>w41?@6$T<9^AfW#F>QJzV9NsX9SB;J`M|PtyP~w-aODyh*y8sQUZqdwaz2X4brU81y(=(BzRDi-ivehd zbuj;o-CkM{=FaI@%0qj?k%$qPJM*e;L`_~>)uwv?8aR&Zw4%~Q@^ zqSe8xKGzVz7c~#F83Yj)81knx*D`>;#7u|L9Jc@c0b8&<^u~Oc8vwmlFA=74-ZV{) zCTD2Y;p%%V9{fAq9xCKE`M6`>y!XU<0wch1lqZl}aR?1L+49d157FjQVy2<_nDKCu zUOwy9X~u~sGEsNSNAkV%mkrz7Rm3DZ5xlFGvx`RK3Xa>ePr2J|v*r>qZlBZDBVOtI zMo?{XfyuX*1P%HZock3_jO6pt zm)T}W6Q5e~W4`%x8J5sn-^0HZ9bu$9_pHyP_k)9`!BnB?j#s)?Yz?JSA>F}SQk`uu_sEkmxBY4!#QyJrXE3>k+tgMmO+Qd!PAO4!uZ z!q<0)w(b*2w+$Op3?T|AlEgJdIZ^U*3(_W|#jp0ZwymxE%k#qQJkGSGqc(6Zo1~HD z8F>4(xj|`pB@f!pP?!>JXE02F;q!A@PBAyO?>(Rp?ZNP7zjQ#niItIF^yggF-IK$0Cm{IUui3!Y#dyVppXqo_c7#58TJHIJ(US$FEx6yI z#Bu&oK{z%+$uC;S`Rij~lW?&H=6=;5q*V5+%w&yyXw5RfdnTtO0CMXi6caO+(^dvUj#ix>T>Q( zAmF$(kfHDlV#MDhAeSI=&uQmj@E=L2x||7y2uNaIJ_Hg$2+4b6S9rFfA%6G1R>cR8 zxv_JB=~ci zU$G<`?7>&wK9k!>48REw59}K-r>tyffj->=)d!ts^3>~|uRgJ3KyOWDHCd!gfj0~x zHfS~xH0=(1?@ndsZMwlswCrj+t>h~Fp-NEymnfF{@RiQ#HI}lhjrGo@%Yu#Qy_+bD z&ki&m>WTvQMScc;Ra#53-q+E)@#>jft-?`DP3MCopaR-7NQQ}Wlb+pszWVShU+%tc zn7W3G_61Nm(d_kWq@cXg=Z|ZVdV{q;*~@s(Br80>Uj((7V{a1gz3OTU8!NnIPK6$CdD0sO zTru1QyE_c!5=%W&(l7O)=XA$!yb7dh5GOp!8k)7s|EfGHwJI*p9T<7Yd`4JP`D8JknJ{b_e!TSmAiS*P-z=BX(aY8MGX zpPxksHKzw-y>{yP*mGDkFQ*sxEPi9i@LRsgKa<^V?aYT@116{89b#@NuG!VEIyW2v zv5h?tH{{k(fR8j6E^iPjlYZptB>gEQEBEkg6~W%G_B7 zMzrkV*J(-%@`~HCng{AMmIrog!~a;>FD_fiW<|vk@bNu*{rWQ{ggq(&D+kKz=^aqN z3s@1SH1M7`pof%V=Bht6fjdPdjVWuLU3Wokd)vPcEM;|ykV~s!R%*t?-g>1#;is8? z>N!AveV#FaIO?nSkAyp%0Tg(vUc`V$eKTFcDfIm@*TM2PrK(3slgJ?<_F4J66K-rX zM-dS7&`55zt4L0LA_bqdWd$hx&r3wtMU17~HXwA2vvhzQ1TB5_oBb%}0#f8V_XaQupdH5gw2?-E9Rt8>Fy!P@e)jQa{QzknH@xXN_JTs^LKqdah$vSir_B(&>V?;0->)oHak%nsxsI z4RC=T?&3|v<6QW@x;VWG;<9Il)U4+QfTvdS@Ef3v;ZC@F%&6+j+j zh8d>S{o|5WB9Qs{>m=iN{j5@C2{D<<+tZPJ*zQ5>ZXJ5Brx^M4?tq54E$5u5%8N`*;y_r4vvfjHaDkyc`JwAc`I(F-cO@kJ1Bm>@8Fk4i53N6cCRYvhz?@5{P`Of zP!-p&EevzfZ)~=1?1v3i4r5tc81{*-i5IR=1LMC6f4)d>WbtOHFLfO{tWu|+NHv3= z1~ocHD{kuPE@hIbJd7J;RY-BUJLQqhYgmJ(D=E?1?G>JB&64$ufJh(x%+4O&2kGp$ z3$V{ji2!?ocw2Jw+3e7W>ytU_-#MPgU8~TG57!O}Vh~H~y@6Wfm>e!^Hgb0Ih zi$MTuDyX}uH?$x$>W+p>(hHudN}Uosor14vA=plni9?n?_w3TV}P&ENhRxTb2u;l#dpsqTGbCuN?s8VpyAVG!(bXS9j8Kg!u{tn zg#34#^R=!JJUKVu{DmA+hgb79=Wn`hY%#Ar)82DFg!^f6_VZL)kw>howa{$ ztbMEXVJM`Kpa2_L*J6AtUTVDrtu|_JSI1AB@jm8lij#A^5tWTr#o#*3<$oT!bqf zAy{6uhkkPp;I!`vuX8$HW(yJ$0-^h`gU3rex2ierQ7{rbA^y7iUhhB-CAigqT#I$^ zF|24DDpg=9%A1-VzoErlwhoB6H^^u%U9VjJg+i`F?&Fd004F#8QIDFTFNx^aBqxPh z`xJa8GVJ$+1#+L3=w(+IJhQk=VQw68(5F>;Y7PliDi&|vUNo}W}`FqGUMIJDhFFhInzU_ZZ( zojn^oXquAq*>{x%CDu!>MrMeO=wGGs!Q?H4`3s4HLX5|tQV$^n1as*Jrl!Ku$fQe! zB<6YEy9X>0taXqsiV_l$KM;oe%87n>_RxPsMAU@(K&cChDSM8?j>|81(ZDXV9g6GH zON<7oH62}ghMB!sbVYO9dztY)WQ5ClCP?P$C~N6@d(r(}!-&cB4AfjNLy~*SJ zx=(}9DT8fe=iMyV*~vU0ulYhm@kWg=jcK(qmm2UBJ1PfZ@x`8tDHaFyKC+AHS>%h( z1SMM`j}v9ww~S(XEPB(VrCgbPpTRQ@Vs#bZx976+zs7jOta2`O8sdJic645vAwhs-K>g80HhoS{O)= zLO>Ota!ZQGG`AlIWDzeu#?mqT_vx1kcdq;tL zcWs@)YZ9d3pAu3H*q_32?T-{U%Y0QAraWBNgM-p%nKvx;*OtjPRa{cC3>gYUl$*XEFGaNBHA z=J`cERUe;uE2gVunc!-QbH9$LzzE1WkX4`tVL3du^T zC4M3tHH^__&c^hhxVX-_bWj}I_54$Knb9q)7yh71`6UG1ZapkcRQBV6@Xzggt2~|& zryt53=g|!S??EXC_mMZVN=zG4EB8cxv%7=UgZGCov3wE|)m*ys11ec?nvQa%FRb2s zc8(~6dQr1KU{=`p9IdWmGzr?qcZAcKB9eh=Dc9I3^794T@Qi3NFLQ?Isd=m8#t#=< zI5UWGMWfa85{0&(b9Rb1cf(rlhR@T7s2O=L-c3{1eV#I=44(rP!K%z>2Drc6IB&3pftbs4CdDB|r*}P6QMbgPcgCpGXw0SaZ6|gpY zC5%MRV}!)1ej1#wS_{!B1mql)v|(;dVmZ$3N|IOb@?U#n)07?);6uW!c2$T8*3R91 zFYj29WNWQ0Z1>Cra{*Fr_R7G3Mw?Pp_P%CXvkG%$5l(aa`t?t`?b}V5vuBLaPkX7( zP)%Zau0ta*+!^G00f7xyF!IayJ4ya=Zr*lRsia+1#i7==uW|-$z5TOds%IU^r&S!B z;5c}j*Fvf-!lNXcUZ_7!dY}PRGydkp1PY!|fR`4#`s`etS;r-#vo>2AoYukG&4YSQ z3l}s8RDbm*5LG&x1H7TgGmbdPOJ=?@p-Ghc>qH$fA1?I@*o^r@di?=UX3jf^gx6NoQShQA_I%p3cpUbiF0cGguBn8M|)k59oL8F5U1l7j<(zW&nulwy8HzmHV zm!Vpm9w*Xyu(Fx52^9Sw?R{w=)Lk3*FhU_jBnjD4mSjy?v+wIIia{Yuma=P%Es^Z9 z7Gi8!%f8KMm1JK_j3~^=K4TeX<~`$n?&o>l?Zf;1{WRN|^FQZ0*M6>Zez&B=E_|sy znP_e^lU{XIk~q5q!YM;9&6BT~zQr7!4+EL%PpdkabUwd|9E8-A&D&UAU-D)+xLv;* zmu{k5fg^yl44cwggS(qm{-Welz7L?-+YU=tHu0EdOkR!Xwu!5(Jw39vy83DEV*(r# zw*Hpk^}}ys@T}J_`K`8q$84zyYcE7P9UWF4OyFFg<`#rtd@MV82_PLhKRi~2w4Z+5T! zNZx{9il0|{R%sxlBgfEGYt_(X@^N=#?NzG-cg?b6u*r*{wggA9rRImXsIgB!)vpG2 z=gXz7dBt@zaS!)eawuWy`}bGFzW5s7-}x98Lqx9~KO+h|2e_FD%$P%y^l4;AR^3Tn zqZIFh!n!hCWUy%OSPV1UiUiiFB#FF4gx%OX5(Jbk3S|9$tf^QI6?AL_a34Q*1B%2f zc1wRL#)y@d%d5Xyb(?Lqle%R`t- zdDstpci{?SsC56%Th5wgX8>31YtIevdNj-TXy}Ri&44ms(MJzbpLzruoqC}Zd{qUj zsL^P?Q|29u6X~UAoT;28%1k!hyE*1~DWMV*Zu2u57wLt4+?Q8^!t^N++_eXHgbd-< z3|5uBQ=7kg6UQmRsV(g88TZz63w63*bg)D3u?2fX7y_qGyJ&KFxt zvls4k`m7GQ6-+DGu|TcxZsS$0x=_>Zpag$as;3v!zTd>Qfei- zP1QRl?3+uL696DW1%=tH)b6ZKCA075)DexI6rzE< za)7fzzXRB($AJ^nK4Yx}&+Fztb=eW;U=?1zhOEXbPHD+pDdiR5jPKBiIDTg7BOr}0 zh(=d(BENL_8Jh`dML>Kw?*@ytbV>P!2v+WI$FM}aXLcNY8(ckcftLL*D3aMA+jX>D zYp~neJL#_(!lxs!EPDPR7EG|H$cU#I8JPuGX{BcAPM1l>6UkxMM|6x4 zy=m8UBK(Cox(7eV7(LM`2cB~q8aoi(qy~2Smqsr~hjtebTesiC;wxZglnX{W5njkHkZ6Fu60!pO@&Ncd#-%sYdIr<%!%qbbJOcpW?lbIG z6{&lySJy;I!?t_A7OAzN+A2E>j+#XeNYtPhu*+uXU}EU8JLANz6q+r=POF9PPe)P& zI6&~K=n^XASg3pq>2qE3wvI=7Gh>R!#+ixF!4vEOGdl5*7Z3VFbOCnVkbzjsVeG0} z+sJ5kqalhd?W{EvV90ODFC!pUI7GxTbBt%#!Rmf`3C&2cde^#L!Ly4tuHPT3C)0{^ zAbAC+4=xeYp8`61&3Jb6iugS*9ZFW&K7Xd{C}Q`0o-UND_lW))^p|G@NDnsq@R^;+ z*o(JBuv@Doz5Sx8b{wP#xahPa4$Syw=X4MrIsCkR>{NwkvNq!|;TgmEWgYlJP=LJ0 zQ)-l-_D!8x@g0E|;(BGbYM7yrzGwyuIw-~&@~YP!J@#DeI((Y$+qjSaCKt_{M8z-j ziO-jQT>&1mcI3gf1}3nqwKb&PA-M?W_(rot4Vw%8Yfea}Z0sI!AD>Wme-%c=uD#8a zq`(OH@rw5gV{0^Y%s-^>Vo)kj>!=#f>byLr3FB8GnA+6`K=lG3+t^oklP{|R25kx6 z`1sfMb^FvN`CZ`{=0a51u_=s(k8#qydBJMTd1|^z?S{1M{qGI3Ni?6o(ur?@0BuW1 z45X6&bVWK;vY%C>W~lg=q$ap4Kb>ctsxd2gnMGAPp+MiL(wB346kKF41PQqv2o%~- zMIZTU7qg}U;{DG={SBRnjExOhbrOlDyV0&|O+6kzm0FI;D@(uglhm5Hj z#CEZJN=O24D!0Srl}-CK50!--hclL(T)#s|1d<^a0!+;4R&1x6W=>(x!!?cB2#T~N=ec29A&$? z68d&_qK*LSY&b79TWM-$`0~rmAMW1XGtA8PG|i8ip*(~Nm=*~y?u(c+d9KvD{Zti1 z_E$4##Uej8Tm_wKiJgakp%2j)vUKq<%`w)TOGUQIZGV@ARozP+9@h#)g#Jec!)&&02h)<{n4G zS;75=uHsCVnTifAKbZTl#w|TnoX@8pi{p;&(V-?O`ck-cwNe7TlIK+07Q>iFd#cp}GI?tE{6 zm_33^R4|&R{Pn%_X-AJeVdiq7uvo~bD|pjgd9d*HEJ-KD7E65wvj6kDgQaDY%X;|I z{n1>p#LS?O#+mZ&k5>y&Npc^CYRe*9{P_%3_%7;SR;IilgO>%AdZ(1%iq!lX4(Rdv6Fv_xMkly_>ZfsKyLo6g8g(@^-}yi? za{j6&K@0LBHYxz4q;{n21)*%s{s?3Si|aI$a%fluGpd^M?-`+&s8nos!9 zmSnkD)tMKmyQ?O|xsE6T<$3BP;V|(>-P}_q*dbtw${yb>(fcH2l@my5P%U>y2>|*f zFhNvIRUS<^z)yB{6|zI9IocPOoM>J9avuT6NPuWU&#y#zDA9fZE~q5RO4z=kgVf#W zIYIq!>&nk_w?eU=ldTWL?Ck6=d5%jMWnZH4^tr{mYx;;d$`Fx!CAi z%XZ68E=vPqJ{v%gJ+5u8p+5>y$Tz)O!mhgz0Z!^AP+`~5&;anH);@sH2m+xDY{?E` zI@$Ar6rm7>HSd}Za5(ZBgxWyM(2xlfwk6rO2R>S+%!1w)d^SGkp+Cm*jY{W|7i&;$ zo40ZV@`$YhRZV5%iDuCY`e}h#NBrbF_UDctUyPoql~)nhZ=d~Wi{PqA2w#3$FYTUa z*~$CzDUWLe)!k4b-qzHD!78kZ*g?(asqzeA5~x*FD^=M>+%~$&pep0TOmx5`Fqx<( z$=u^xTAMl5a)waBucH{DpaC~8;v(tha0N`Ch#pnSbxIXLYcyr->0d^pNZw;-ue_g0 zZf9|y@VsFE8eHH9XSBs!0>NCwoYp<7(C2nKC7(hTU+xLU{5s|~i7s1z{F`8$ed*<9 z5U6dL>RJ&H{F={Nk7Foab5dq=TnWqfBPzUCkIt#@ zpIU&h7SMUUa z(@aN5Fu*uug=EXQpMcD+srrkO7cwbKl)W{zx4ZMwKL^MMKCfmFpiOef)`AnjMwDOc zne(m=pB#tDRS-4c?L-G8;lU%-&xuJ*iSynpu#dH`{(R(?QJlIQ!>tU;rL>H8c6B20|_#RS_Shpm|f@T z-2_tzgo@|ORbp|J0fu-%9IXu_#R`2k+T+QV>tiay{fcjh-fh{pxp+T4@TQkkS!UnX zVjh$>PXH+I9&TOCLyq6~c>wS|1LQe*aJuDpb$s^bdi`!6;^3_1*>JAr4te%6HGi-V z3*dUsXZ0!(7xI9YGrjC)aD!98AV#-9%z&Y>Mawwn+;y#r`lFYh`i&IKOYn+l_i1rH zPAxQx)s6tBY;tk-<@NWaSa}ZK)tT8@4qqzd`85dx7#bkMXL8eVc6m$)`{ zfN*r(bAOKCsGU0o0fU3A7xR-C8&}_I!f}SUMXpkTF~!Dnf}uFO$$h^HunAp%;kXbe zq)W5xu&=iRfekf(2Oux0%(A+-LEq6V#{jvYy~w1xzSy$Z&ASdX?i_&2{?On)GWh+w z0US5+0ww>BzE#x2x5(pBU;6L+jd7vOo66;^%Jhw2O{;g(Bsfa)&sTVJE#Gx~$TA3$ z@rY;Sb=2a{kF|hmYB8GJbKK)*&+#_O>;Bfo{4fY42(tl|*zGESQZ1P6-adfuc6`e~ zzV=PUCWe3rKoH(Hkx~1V{MsWs&eoVuj*{`QR&;tW-5;D1=~FMu1O6BV<;fZkTQx{H zW;Hn)@n@wF#=N4dF^mLiHJU-^ctIFuWP)4n1u)x6;XB5*J8I)sy1JfiE6xNLU*5Ii zYZ?{6SJn3ec<$#BVXLiWZ@(xc+Ygh^T9{98Ld*YP$O$Rr+aY#j8 zp-v|^wPcOYkK#9_cQZ~M2IDtT4Jp65m??Sl!U#Y$z4bL$o(Zx(c%9Y7B20tyY0L-e z+n#}%@fGlRWj5J{|6PCAC?%s@UclW$TWOsyNjabBmq^Sz+sp)$+%mst+&{Y5t!F6^ zNgwy=F`MR}O;gM+JsHDr76LI+`JK_V2Uc$bpp8QKiGz>pK-6Q9sO{-_vB~ROl+5i< zvp*m6C)tOY`;!crYAA<){pksVLi_=Wb^z&u5$BVtT*O$1dF~(^HlfjjqQyV1WfLS5AmR}?&CZSUYrlSh-dou-KuXB_u1Z1 z-k(zkdlB|GMs;%pg-UN~HMmTd=&?$K5N3;$z7QZo47z*(@37azRPx_Dtt zUQuyAXEFah0Dq60*#7f&HxPh2dUuC?N{##jOLuc*8952nQXPaEEN$*Oj6`n0qR6C!7*c(@C=d(GGKfX=) zIcDSV*3wjs1)}6T3lIiC`jP5<aJs<>(b7OW0#c- zWz$@zXI$R5Wsg1Jpws3OEqBoW{wQhm12q)6My=pQp`z<)9I5+pw#TSR6$QxoKG~YR z=0~=;u{qBKik-#T^zHaB(5XiR#7!^Kqiz>Mmh?^0R{(?PtU0NV)u*OcVKPQeu9RWj z3DbMtJyBHULU7kq~Hd5H+5uRJ9zjkR6ji>Q+VH}au#-> zTFRIsR!4Ki^UGSC&#~uEhN*si%#H4CxS7Xv`bVu}7S%Lf*|zbw8={)te-QZA&S!hP zW6L{AxZKT>_fcIOZ1CVo_7J$FdvGGC?nKV(lP}H1gMe0K6hsO0&~+yggQqwik4=t- zVx$B5q$MT$1G_r~U5W$ag|>T$``pj$J}JC?lx9E|7kl`pRv`o3Cb{UE!sEW?4N>%Q zp^7bX>>NJIx2jF<<609xKlMkJ#4Xfh3i?=itiWiC{{lOFal(XAyJ5Lu!MpD87ghby za?6sNt<)GQ#q4f`-es@jUBwP6_0O^->|@SX^EdH{HoqKS0lEFdp+i@R&! z9`=f_6k!Pwx{h^*Adbw+@+>0;iq!^Wd#~NQ`Tg=B;xeuyy4zJI0)2lrj;to%DJiGk zcwY0ajFbYyta*bMZ&YDM%sHOILe8gE%qcbVaA}jP-y5b*XcwTlW7tj{wx_vVO;R7Z7-~Yxcfd)fk*S-X z9!V8@(p4u=s}2^#^m;S*jt;+Z*SY&JHqJXDid$&GhWUkQuYeoD0dj4Yg zi-dy(*%BU?7Y8Weh3NTDZ~cB!D>{SA_WJho-ap=W>o-q3u?60}hk5*~icfOUgQRszxUpc*; zG`o7bG3#8QQ$pJea*89U^>m{K`pUyoHBg({JoAyc&-9X(p7s;`s!t`)!sR8C!Ur$w zkApKJF?X~ZI9rhBaUFps9;0$H(zbuxs^fSBkEzgRWXKIk_lO?z*khbpv+8>=>6RNr zbQ*YLp2?$X?^lydZl&pczC1Jmdo0ayIMK<{Ksr6;V}cr)Lj~o_3bs)5T0K_B=y7cG zOcm2qaSa9^$T>DDy0XAk5_ZcxSsbuc8V9IY7Qn`R_Y(9e6~S5uk3cuhFZaH>Xa~0P zed@gK^LlAH8hhlu)8ba(nBot*D%?gpE^b+B63ALTnRoYF=9FmhPGe_9e`?t4A^ZDl zE4(<>>Y4ws> z(pY}@XZ{G|S+##7xbfyjnhAObKj1u%t}$uw>RyBt%03J4w%@PYNKZc@oxp3+bNi$2 zwljgyqlzdL`aJ^$BZP80DnfEecaA`-Shd--N~6+J7YzUd#2ScbI*B8w>!-T z5}9vo_9fr$QB-rd0l)m=N#)hLIb4#{Ktd5K#PfuX>uK$Rl&T&yjll^0_i;xheM<;> zCzzmwpnGBHB<`!wujW!%|J2`L>?PKiIc-!fBun;Nq7*iCbI|(o+S5GRuP5*c!$gR( zYBN1w!A;rqhy8<1{y!RPFL$OAPBu?p;^lv#i2tErLG)BsO_Jw>ir&(W&0x138A@#x zPaPNgAo`R3^TM-zfnk6D%K_3E!pr@ZFtbS`EaqivQZ)_R`x*3cxmArHDTW0RB z)@mw_d+S^N>>z}hx3dvdcD$J?%w+g$jn8IZ@|4bexHaFhQ>k|LuH1SR)7Sea6fb^m zSG{ysiR>*O8pslGXL+wV(j7)&W$sYiZJU4Kc|y_Tb0umH@E&t^;&SLKi-EI01}$X- zo2Szn+%>}q6aEj+RC0fPpDULC;R@vJ`=ifx&hkD*LAtad>(xFq2+9siiD?|&dzO^M zwnsW5UKo#)!OJ7>=?(lC%=ME(I9b-z2dr@BXXA&v&SBxVH=kPZV(pg8UG%m>SAPez zZ+`eW6S;l#;ndU}@xnxoUSkelU$b_&*vi)KD^25xhi?WYT z3E#u6z^m2XEy!y>7lkC6q#bt=zS7w5$BUuiuxAWOih0n&4e?#GLEMUO}NjZg;tDZHP|`z_12u{piv*7p1{Vl(~ancrA|NiU3dzr5>ckJ_o$}vFEMJ_-`L|L@# zE~SJo^v>=lB3YSv5>MI$Sy>ENanSPW;nW6)%;P)CN9*NPC`Sw1Xx?`1mlLNLi7|TY z)hvN&kydFn`nJro@5kMX9Ll)#g+xkEGiRTrD@%mr4!?Q*aFF=!oVsmxk^~yzM8}g{ z$7#Y5E8OGrIDT)A&Fw|FczU9iijUgcPmdcSPCXi@`9h3!0#9t5EqlBD_R#6>|BhQtz)JB@vO; zjbjEZRY66o=@szg;o0}Rdn~!qci{xBv?agP5v|*WxlC5fkDE^tMmw*I(p~b_L=sLq zB_3G~afK1C@n|_C3D#SYOtn95SU-3$zSpHTql6E|Y<(Lo&|AJZ^7D73SbkSjBJXR) z&kZb%fUBA+k|28}^(7yr?@3)KwVZt;@Wt9EUL!;(M?e4;pr~Lu+cEjHE99tw*fRsk zXQE5Nx~+_wu`iWtD;6QKr`edt7w>#~WoJR*$G~pW_xau#iZr(4FSoy4JUr?zJA8>a zY(Iu19A)o9vjjRYzREdzn%VHULPnC-{G$$*@{R)SAK%5$ZFNf8OW|9KE2n2@@>*X(HT|-EZ!X|OdNn?r9L~VEE1?j6e<}Z(dgI0<;N`@b zbg|TMdUxOOrP%qVn+nb!$M|me1u&H6-_=$%!`yLek>}%k*6^m=msZr@f0q1qN9uId zdzv?fD(T}vKWFBnw_ zSXB{yOF4G?iU3$SQp-5(3-L^y%ld&oh-JPfu+2OWHRcVnB!q{!A2GS`V}Fc)-e>%C z<6QL*q0bP9+w9xnTzbpU?<~3u#S*V(K3%m}QG{Ds=)3b+$WZF7KR}fQ;Fdjw=Y;jw9KP?_&hX3tG;=&scqVGK@oV>i*OnJgP}? z%n!xQJB`#?m=WQvqvNZR z779%f!`ajH#=uRL6ax=b88d%9ELxeYKBWWQu?BzWTc`;;sKUB0bGuyY4VF zWsBDGjOOcdoLoQ4UDJ{{*W`N>xOU*DiX%emNMGg!qEwT$|KS#z<|a6#AucR+YeJ*# zAb=XzvRNRI29ZKY0B@qEaJB!i!xX{05sax$ukrMUA352<8H{rf%k{%v?@&v(X^?%a ze}A^H0&T@bjou1TeQ!Z0aKz$BjCe5h@!}VrLf9?T|K0T55wMl0MO!*cJ^!mk2RhZP zde3={ItsnFjBJ7*l!q4{AO2O(?$vN0Pub4uAKmbzBD|TR&X>r|A=B>Q*Kv>tR!I38P4%n3Sl$d)1C*2ODDI zr~YXwOnp9^Gw)$j_Gs$K7tN}7hXY8XK40*|g?}}*@B;G(y|Pf7#$A%S_4=CJ;n@uB z`C6nRIPcCFE+t|_J;s4FyL2R(P4oWST4ann^*?Ne%%u;h->e3T&xNYQ9h&FQ7dr>f@ZxuaieMa|-46SSR#op`+t*G^N<>9CNV+L^^E9X%h2 z;D>PYhC4Iha4|SEA|b{9a9wcEmtnU-yVEppIB%n8*}mttF16^JXp!7ssSb+&|DrrG z>>h1YwVY*4U0hTUx?I}eD$zc}C*yaT`YQh#B}^1DeIfQrjo7(TM3k(jXj)Mk9&nws z16!?z1&CpcS`Vh6|D>-CB3wb)M%{$c=JJLJyJ_zQ$IeR!lkGQ#WL8fdHY^TT5ZGU& zxopUfz;2lm_r>=1i)&B`I9RjMUlh;EglcK{!I1&Alcdy93fa1>xy+swP_S#D2IrTF$(@wNOKOnPfh9X9Jsy` z>A&*Ger=>+CTgckeXfu^vQfYB^*^wH+&{Ml0*N=dz+%xk z9y?tTi65J|b_rCDvlKBG6mG5dZwL3rEbE*>?eaO3o&^;&+9` zWi##hvsK;aaT0_IP!4H`4XRNuL?u>kfqW&Be3GA!qufVGIpZ(PQPqbZxTJoBol(j% z@L@=O`)J>NN>nb%b-1#&)WRyw0j}_@C>@V$uiz{}4l!7Y+vyiems*T&7XBBZO@}Mj zN-Z+dboh-*?usO$vJK38A5jT%jgqDVGb;J)QZc~U^013jZrDt3anW!>OmD(XZ=y_Z zV(~xo5hLvj*%t^p6r8g!5Tf#zEkCDz0X^fNC>=ev;ntmya+Q>t&XgLJ)SAxJ@hnyH z@L~aeC%I9rio`w!K30)>yhuioP|LwM2|@}j_ckgh!WpoiBzz6hhaX$CBhJvg!d9*| z;ya=KBSVkvhz`*pdz}_U6&W~LA3=FTCLOJ;hd|aKWB9S1Tw*>=8+PfsF+5pAh7aD; z2_`hKd%#p#PXnn1-;jDN3XIw4!6FIL#IT5Zu$WNmPA$>-g!lC6jr3(8Z`-;2I49n2 zL3oYWsuy`1G)#>2U+wh&KXTD9T5Y(Id#+JzxKVMKx~h$8ukjC@BD#3}PFMs1sx7`$ z)o4?_-lnSErmFiNJuuGjT2;>qsD*mf^JB`J&Jlp~^j-+T3D!hhJ$@_*yA4yOcoXVK=;3Q#c1$ntv%b88dc)(n59%vn0|+cI6ZCWbnd^|9+LkQH-*% z)y=J}6~&L-f-Af%SirR(CCLpja1h;#q!a(c4=YQ_iloomV?jQ|XDdt5ilncBcoA+Y z+piyNl@@#hhlYo3=;Ori#&D+GMqFFK4b;M}oY1JH@~I^{6@OIlqYE8%Ku^AW4odRJ zYv@&n_)QIV{X!(|6+njV;l{<3`gm3vy&%GsL~>`DH%-baF6=ak67VSTSJ+K?u9%IiTcbNDBnW z9~hi~n_9j0d#rgnq_@(ieJk4r6S&yeK^4(Mjd&=2k5tJ|$;ja{nsdSoExu~gadw9~ zBjk1rFyj{fXOu7xeujT9mC?7@DXT#R&d@JT@>2#P7nfJ;;YHzOJpY9jumokOuVZWd zQ?riR$rl3%aCJmy9XU6OD(J0SYYb}3LF+@@_7zLXSK#1|xZU%c^Wadf_oB6LGT+sO zSBoBPJB^qJRO>QYYXeu^oh{C0Wso|Y6i}pWb^o~q#Dcf*qN=ivTr9P27?#Auov(jr zl}15HTm{UHD89K^)8hYhnOys=vKGA2lUgk#`@|5Y566PtT6?GuC)KWWKjObOGDb02 zY7hFV3cG=5to!dqruh9bieypvKo&+Q^dJm%aCT^`cCmMjokS))+*2t+t6doc(pu54 zM)gGwhk5L}Gt~d1Zg&gSPMJhcrD>rJZ@>&xrHS_Mh%ep|SJH@SiURGvKXX>2(cwB) zlt>rjmBU}a?-rXOLu)6-*Eq+0vPQGWq||XrR(*0K({j+U<)EX>hCPegpf|MaO6aM7 zd^gTmE&B6fHkkGpLdmbKB8*5CM!X6mRRJuKWqVzCSzUNm9V+9Y10FQE`iL+kLMTBX zkOUqQd$OwDzxo^b(Dq;bO)6>Op{d~VmDGjgHZf$>Tl%zbSO#67&f;Xm$DB_iT<7-sSJ`0TgOB#2He^ddv0Ox~vH|P0G1Qz({#ooJPwLXeco4OZ|I3sUN$FIoH@_e+6oV@OzX3Zp#7!dgL;}t> zg_E@v)Q4>fsIo0f6gnidNR_lm0XIp?iDcqL3e<-KjpPp3L3}7?{bw!ZI%nA3ZF~Vs z&^4LY`32;HQc%%jaRj)j0-sk#Did~zj!1=FqJ%7Ib~f2#WJ#fo(j-Eptn z|NaV?FU4m?8FEC&3)&am@gJ?8*qPwlPb}qKBfmI`H_k__9K|QXDM;$_rfVU9r4s2R zdSB3%a>u{5dh*xsQbGl1u|twlm4PX#1IwE%sc5$XrG8k_Tb!X%y9||q^8a$i6Y;^*6zS$#cg_~8ZCu&Vo(*_cNG=;n62Am=hQcy%A>P(0D51Tb7HV8W7 zb*~Wx_~3^Y{q=xaYcF+gg37_pIePZ92kWJn$-n;vL@F=bc^`N2aPbXs%UIo}S`>9y zrnSIh`%de9&KZwWf`&Y0XXH!S0w`+G$7Z}x)izLoul`2eMsP1OAUi3=Dj6c3|;!v}Dg zDz|6{bEryJp)GG9+UihC0coBE$c=*uzuBX zDHlztTfwFKv$X|&NIXXI_M815Nu5cf$!ydqIWJ3x)uZiifQL|ZrWac-$KN(DKAa+q zKrmJWWrG_M4rwFK9M6*Sgna5p<|I(3jutksLw^OPiTIXB`oE~t1Ck(;{lB7}6FdSs zh+nm2u>C(OU4sH%)tOsxpnbFcwah?uwj))G)nbs3sj(gPxq41<_aZum3Zzw3-}Ji5 zec~m(=A*jtui^5sfb8L~fh#|-tec2EF}OC0zj_4UE=LNaA$?gU6n`p=Alm1mK5T#= zMEcQz#u(y`>E_Pk6rHI5>fI&c#=cO&{-N{GLIi7nrMz?Le;blYOtz3U-RD4J9{1^U z)MeKmYkmTP`Zk=^$bV|EtL` zt-l^;tAn(E3v`oz^h~|e4I}}q(S6`*^Gh5u`Jp($r-)Qm{|8q(KHP>Dp5Bd?jYRcq)v83IUQ?qR)68qwS#T@b zZbuy%C{pY@V$ptqEw|D*Lr>XPDoab3qWs~S$8oaAWlQApgMWoy-9!P+`zFTF58n%% zS&>v00J{H>dH&JM{PAU(D4X5}_hY^FM`9Ph=aPqt4z+|p z2UKpvratB3Sorr{`ivP`1WhWres8!F2L%USRHC6l6r|dVusnY z-JDAL8+yiU@4jkDK5(XWC)M$%+o_oxDzv{Lipx@eNT`rLv|vow z?;P0gyzYN9`s?k-DawM+Xq@3@uNiJq+u8C9R{@*;KO31`KFze-aR2UD4Njv8=)xKRg)jF1I}@mWe}_oJIV#euUoC=8l#Tar)y<~(e*wzC7L>9eq-W)b@D(#p^ib@a(gJAh&gBx;W|oxhO=>31koc g|Cj#{1c{~aJ8Xvql`|STQ*YhZ*EZ3r(R7UZA0SM)cmMzZ From fff8b3ae5c4a6af92aa3462b2e65a2cc4836414f Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:24:23 -0400 Subject: [PATCH 30/37] fix minor conga interaction --- code/modules/grab/grab_helpers.dm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/modules/grab/grab_helpers.dm b/code/modules/grab/grab_helpers.dm index 5e8850edd900..496bfdb99c26 100644 --- a/code/modules/grab/grab_helpers.dm +++ b/code/modules/grab/grab_helpers.dm @@ -64,8 +64,8 @@ . = list() for(var/obj/item/hand_item/grab/G in get_active_grabs()) . |= G.affecting - if(isliving(G.affecting)) - var/mob/living/L = G.affecting + var/mob/living/L = G.get_affecting_mob() + if(L) . |= L.recursively_get_all_grabbed_movables() /// Gets every grab object owned by this mob, and every grabbed atom of those grabbed mobs @@ -74,8 +74,8 @@ . = list() for(var/obj/item/hand_item/grab/G in get_active_grabs()) . |= G - if(isliving(G.affecting)) - var/mob/living/L = G.affecting + var/mob/living/L = G.get_affecting_mob() + if(L) . |= L.recursively_get_conga_line() /// Get every single member of a grab chain From 99ac81821986c064ea56fb68ba812e16fe310414 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Tue, 3 Oct 2023 17:09:56 -0400 Subject: [PATCH 31/37] grab to reduce bleeding --- code/__DEFINES/traits.dm | 7 ++-- code/datums/station_traits/negative_traits.dm | 10 ----- code/modules/grab/grab_datum.dm | 37 +++++++++++++++---- code/modules/grab/grab_living.dm | 2 - code/modules/grab/grab_object.dm | 31 ++++++++++++---- code/modules/grab/grabs/grab_normal.dm | 2 + code/modules/jobs/job_types/_job.dm | 2 - .../mob/living/carbon/human/examine.dm | 3 ++ code/modules/surgery/bodyparts/_bodyparts.dm | 3 ++ 9 files changed, 65 insertions(+), 32 deletions(-) diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index cd8e84dc99e2..4fe9b8993eba 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -408,8 +408,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_SPRAY_PAINTABLE "spray_paintable" /// This person is blushing #define TRAIT_BLUSHING "blushing" - -#define TRAIT_NOBLEED "nobleed" //This carbon doesn't bleed +/// This bodypart is being held in a grab, and reduces bleeding +#define TRAIT_BODYPART_GRABBED "bodypart_grabbed" +/// This carbon doesn't bleed +#define TRAIT_NOBLEED "nobleed" /// This atom can ignore the "is on a turf" check for simple AI datum attacks, allowing them to attack from bags or lockers as long as any other conditions are met #define TRAIT_AI_BAGATTACK "bagattack" /// This mobs bodyparts are invisible but still clickable. @@ -887,7 +889,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define STATION_TRAIT_UNIQUE_AI "station_trait_unique_ai" #define STATION_TRAIT_CARP_INFESTATION "station_trait_carp_infestation" #define STATION_TRAIT_PREMIUM_INTERNALS "station_trait_premium_internals" -#define STATION_TRAIT_RANDOM_ARRIVALS "station_trait_random_arrivals" #define STATION_TRAIT_HANGOVER "station_trait_hangover" #define STATION_TRAIT_FILLED_MAINT "station_trait_filled_maint" #define STATION_TRAIT_EMPTY_MAINT "station_trait_empty_maint" diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm index 610631947394..fce4ef1f1578 100644 --- a/code/datums/station_traits/negative_traits.dm +++ b/code/datums/station_traits/negative_traits.dm @@ -17,15 +17,6 @@ /datum/station_trait/distant_supply_lines/on_round_start() SSeconomy.pack_price_modifier *= 1.2 -/datum/station_trait/random_spawns - name = "Drive-by landing" - trait_type = STATION_TRAIT_NEGATIVE - weight = 2 - show_in_report = TRUE - report_message = "Sorry for that, we missed your station by a few miles, so we just launched you towards your station in pods. Hope you don't mind!" - trait_to_give = STATION_TRAIT_RANDOM_ARRIVALS - blacklist = list(/datum/station_trait/hangover) - /datum/station_trait/hangover name = "Hangover" trait_type = STATION_TRAIT_NEGATIVE @@ -33,7 +24,6 @@ show_in_report = TRUE report_message = "Ohh....Man....That mandatory office party from last shift...God that was awesome..I woke up in some random toilet 3 sectors away..." trait_to_give = STATION_TRAIT_HANGOVER - blacklist = list(/datum/station_trait/random_spawns) /datum/station_trait/hangover/New() . = ..() diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm index 6650b9f99967..d8cda6f98a2a 100644 --- a/code/modules/grab/grab_datum.dm +++ b/code/modules/grab/grab_datum.dm @@ -109,14 +109,15 @@ GLOBAL_LIST_EMPTY(all_grabstates) /datum/grab/proc/let_go(obj/item/hand_item/grab/G) SHOULD_NOT_OVERRIDE(TRUE) - if (G) - let_go_effect(G) - G.current_grab = null - if(!QDELETED(G)) - qdel(G) + if(!G) + return + let_go_effect(G) + G.current_grab = null + if(!QDELETED(G)) + qdel(G) /datum/grab/proc/on_target_change(obj/item/hand_item/grab/G, old_zone, new_zone) - remove_bodyzone_effects(G) + remove_bodyzone_effects(G, old_zone, new_zone) G.special_target_functional = check_special_target(G) if(G.special_target_functional) special_bodyzone_change(G, old_zone, new_zone) @@ -214,7 +215,7 @@ GLOBAL_LIST_EMPTY(all_grabstates) SEND_SIGNAL(G.affecting, COMSIG_ATOM_NO_LONGER_GRABBED, G.assailant) SEND_SIGNAL(G.assailant, COMSIG_LIVING_NO_LONGER_GRABBING, G.affecting) - remove_bodyzone_effects(G) + remove_bodyzone_effects(G, G.target_zone) if(G.is_grab_unique(src)) remove_unique_grab_effects(G) update_stage_effects(G, src, TRUE) @@ -262,9 +263,29 @@ GLOBAL_LIST_EMPTY(all_grabstates) /// Handles special targeting like eyes and mouth being covered. /// CLEAR OUT ANY EFFECTS USING remove_bodyzone_effects() /datum/grab/proc/special_bodyzone_effects(obj/item/hand_item/grab/G) + SHOULD_CALL_PARENT(TRUE) + var/mob/living/carbon/C = G.affecting + if(!istype(C)) + return + + var/obj/item/bodypart/BP = C.get_bodypart(deprecise_zone(G.target_zone)) + if(!BP) + return + + ADD_TRAIT(BP, TRAIT_BODYPART_GRABBED, REF(G)) /// Clear out any effects from special_bodyzone_effects() -/datum/grab/proc/remove_bodyzone_effects(obj/item/hand_item/grab/G) +/datum/grab/proc/remove_bodyzone_effects(obj/item/hand_item/grab/G, old_zone, new_zone) + SHOULD_CALL_PARENT(TRUE) + var/mob/living/carbon/C = G.affecting + if(!istype(C)) + return + + var/obj/item/bodypart/BP = C.get_bodypart(deprecise_zone(old_zone)) + if(!BP) + return + + REMOVE_TRAIT(BP, TRAIT_BODYPART_GRABBED, REF(G)) // Handles when they change targeted areas and something is supposed to happen. /datum/grab/proc/special_bodyzone_change(obj/item/hand_item/grab/G, diff_zone) diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index 99f22d8b0409..24e8160ee342 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -70,8 +70,6 @@ if(QDELETED(grab)) if(original_target != src && ismob(original_target)) to_chat(original_target, span_warning("\The [src] tries to grab you, but fails!")) - to_chat(src, span_warning("You try to grab \the [target], but fail!")) - return null SEND_SIGNAL(src, COMSIG_LIVING_START_GRAB, target, grab) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 68fabd274290..02ad43fe9a9d 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -35,18 +35,18 @@ affecting = target if(!istype(assailant) || !assailant.add_grab(src, use_offhand = use_offhand)) return INITIALIZE_HINT_QDEL + target_zone = deprecise_zone(assailant.zone_selected) if(!current_grab.setup(src)) return INITIALIZE_HINT_QDEL + /// Apply any needed updates to the assailant assailant.update_pull_hud_icon() - LAZYADD(affecting.grabbed_by, src) // This is how we handle affecting being deleted. + /// Do flavor things like pixel offsets, animation, sound adjust_position() - action_used() - assailant.animate_interact(affecting, INTERACT_GRAB) var/sound = 'sound/weapons/thudswoosh.ogg' @@ -57,6 +57,7 @@ playsound(affecting.loc, sound, 50, 1, -1) + /// Spread diseases if(isliving(affecting)) var/mob/living/affecting_mob = affecting for(var/datum/disease/D as anything in assailant.diseases) @@ -67,21 +68,26 @@ if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) assailant.ContactContractDisease(D) + /// Setup the effects applied by grab current_grab.update_stage_effects(src, null) - - update_appearance(UPDATE_ICON_STATE) + current_grab.special_bodyzone_effects(src) var/mob/living/L = get_affecting_mob() if(L && assailant.combat_mode) upgrade(TRUE) + /// Update appearance + update_appearance(UPDATE_ICON_STATE) + + /// Setup signals var/obj/item/bodypart/BP = get_targeted_bodypart() if(BP) name = "[initial(name)] ([BP.plaintext_zone])" RegisterSignal(affecting, COMSIG_CARBON_REMOVED_LIMB, PROC_REF(on_limb_loss)) RegisterSignal(assailant, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) - RegisterSignal(affecting, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) + if(affecting != assailant) + RegisterSignal(affecting, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) RegisterSignal(affecting, COMSIG_MOVABLE_PRE_THROW, PROC_REF(target_thrown)) RegisterSignal(affecting, COMSIG_ATOM_ATTACK_HAND, PROC_REF(intercept_attack_hand)) @@ -140,13 +146,23 @@ if(src != assailant.get_active_held_item()) return // Note that because of this condition, there's no guarantee that target_zone = old_sel + new_sel = deprecise_zone(new_sel) if(target_zone == new_sel) return + var/obj/item/bodypart/BP + + if(affecting == assailant && iscarbon(assailant)) + BP = assailant.get_bodypart(new_sel) + var/using_slot = assailant.get_held_index_of_item(src) + if(assailant.has_hand_for_held_index(using_slot) == BP) + to_chat(assailant, span_warning("You can't grab your own [BP.plaintext_zone] with itself!")) + return + var/old_zone = target_zone target_zone = new_sel - var/obj/item/bodypart/BP = get_targeted_bodypart() + BP = get_targeted_bodypart() if (!BP) to_chat(assailant, span_warning("You fail to grab \the [affecting] there as they do not have that bodypart!")) @@ -186,6 +202,7 @@ // Returns the bodypart of the grabbed person that the grabber is targeting /obj/item/hand_item/grab/proc/get_targeted_bodypart() + RETURN_TYPE(/obj/item/bodypart) var/mob/living/L = get_affecting_mob() return (L?.get_bodypart(deprecise_zone(target_zone))) diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm index 2ba217165af8..2f9ebd222b3e 100644 --- a/code/modules/grab/grabs/grab_normal.dm +++ b/code/modules/grab/grabs/grab_normal.dm @@ -194,6 +194,7 @@ // Handles special targeting like eyes and mouth being covered. /datum/grab/normal/special_bodyzone_effects(obj/item/hand_item/grab/G) + . = ..() var/mob/living/affecting_mob = G.get_affecting_mob() if(istype(affecting_mob) && G.special_target_functional) switch(G.target_zone) @@ -203,6 +204,7 @@ ADD_TRAIT(affecting_mob, TRAIT_BLIND, REF(G)) /datum/grab/normal/remove_bodyzone_effects(obj/item/hand_item/grab/G) + . = ..() REMOVE_TRAIT(G.affecting, TRAIT_MUTE, REF(G)) REMOVE_TRAIT(G.affecting, TRAIT_BLIND, REF(G)) diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 6ae574caed32..5d3081ebc631 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -436,8 +436,6 @@ GLOBAL_LIST_INIT(job_display_order, list( /// Returns an atom where the mob should spawn in. /datum/job/proc/get_roundstart_spawn_point() if(random_spawns_possible) - if(HAS_TRAIT(SSstation, STATION_TRAIT_RANDOM_ARRIVALS)) - return get_safe_random_station_turf(typesof(/area/station/hallway)) || get_latejoin_spawn_point() if(HAS_TRAIT(SSstation, STATION_TRAIT_HANGOVER)) var/obj/effect/landmark/start/hangover_spawn_point for(var/obj/effect/landmark/start/hangover/hangover_landmark in GLOB.start_landmarks_list) diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 52fe6b256f98..f84712ae80cc 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -209,6 +209,9 @@ for(var/obj/item/hand_item/grab/G in grabbed_by) + if(G.assailant == src) + msg += "[t_He] [t_is] gripping [t_His] [G.get_targeted_bodypart().plaintext_zone].\n" + continue if(!G.current_grab.stop_move) continue msg += "[t_He] [t_is] restrained by [G.assailant]'s grip.\n" diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index b86f11ab4e9d..99a4daed1980 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -1029,6 +1029,9 @@ if(owner.body_position == LYING_DOWN) bleed_rate *= 0.75 + if(HAS_TRAIT(src, TRAIT_BODYPART_GRABBED)) + bleed_rate *= 0.4 + if(bandage) bleed_rate *= bandage.absorption_rate_modifier From ae49e695a8f3cfe4db5a79763f1db9001aa140e6 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Wed, 4 Oct 2023 20:14:13 -0400 Subject: [PATCH 32/37] revert compile options change --- code/_compile_options.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/_compile_options.dm b/code/_compile_options.dm index f3564aee3696..73b9ce646e61 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -217,7 +217,7 @@ #endif #ifdef LOWMEMORYMODE -#define FORCE_MAP "multiz_debug" +#define FORCE_MAP "runtimestation" #define FORCE_MAP_DIRECTORY "_maps" #endif From dae045beb44eea56799cedcef7291db3143a7f74 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sun, 8 Oct 2023 19:13:04 -0400 Subject: [PATCH 33/37] fix pull icon for real for real --- code/modules/grab/grab_object.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm index 02ad43fe9a9d..5cb1e3b6e64d 100644 --- a/code/modules/grab/grab_object.dm +++ b/code/modules/grab/grab_object.dm @@ -94,13 +94,13 @@ RegisterSignal(assailant, COMSIG_MOB_SELECTED_ZONE_SET, PROC_REF(on_target_change)) /obj/item/hand_item/grab/Destroy() - if(assailant) - assailant.after_grab_release(affecting) if(affecting) LAZYREMOVE(affecting.grabbed_by, src) affecting.update_offsets() if(affecting && assailant && current_grab) current_grab.let_go(src) + if(assailant) + assailant.after_grab_release(affecting) affecting = null assailant = null return ..() From ae21750aba9de7cc6242484efdccb63dc14facdb Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sun, 8 Oct 2023 20:58:38 -0400 Subject: [PATCH 34/37] fix bugs --- code/modules/credits_roll/episode_name.dm | 2 +- code/modules/grab/grab_living.dm | 3 +++ code/modules/mob/living/living.dm | 3 --- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/code/modules/credits_roll/episode_name.dm b/code/modules/credits_roll/episode_name.dm index 8e312c961cff..44023c41332f 100644 --- a/code/modules/credits_roll/episode_name.dm +++ b/code/modules/credits_roll/episode_name.dm @@ -318,7 +318,7 @@ var/all_braindamaged = TRUE for(var/mob/living/carbon/human/H as anything in SSticker.popcount["human_escapees_list"]) var/obj/item/organ/brain/hbrain = H.getorganslot(ORGAN_SLOT_BRAIN) - if(hbrain.damage < 60) + if(hbrain?.damage < 60) all_braindamaged = FALSE braindamage_total += hbrain.damage var/average_braindamage = braindamage_total / human_escapees diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm index 24e8160ee342..08fec11b1379 100644 --- a/code/modules/grab/grab_living.dm +++ b/code/modules/grab/grab_living.dm @@ -144,6 +144,9 @@ if(G.current_grab.downgrade_on_move) G.downgrade() + if(moving_diagonally != FIRST_DIAG_STEP) + pulling.update_offsets() + var/list/my_grabs = get_active_grabs() for(var/obj/item/hand_item/grab/G in my_grabs) if(G.current_grab.reverse_facing || HAS_TRAIT(G.affecting, TRAIT_KEEP_DIRECTION_WHILE_PULLING)) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 7ae01ac8374b..9b911267e0f4 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -771,9 +771,6 @@ . = ..() - if(moving_diagonally != FIRST_DIAG_STEP) - update_offsets() - if(active_storage && !((active_storage.parent?.resolve() in important_recursive_contents?[RECURSIVE_CONTENTS_ACTIVE_STORAGE]) || CanReach(active_storage.parent?.resolve(),view_only = TRUE))) active_storage.hide_contents(src) From f469b575483dd90043c61d2255aa09f0ea1eacf7 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sun, 8 Oct 2023 23:13:50 -0400 Subject: [PATCH 35/37] Re-add recursive falling --- code/modules/multiz/movement.dm | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index 991792d5cba2..2bab8a8a5634 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -88,7 +88,7 @@ return destination //used by some child types checks and zMove() /// Precipitates a movable (plus whatever buckled to it) to lower z levels if possible and then calls zImpact() -/atom/movable/proc/zFall(force = FALSE) +/atom/movable/proc/zFall(levels = 1, force = FALSE) if(QDELETED(src)) return FALSE @@ -108,7 +108,7 @@ var/mob/living/falling_living = src //relay this mess to whatever the mob is buckled to. if(falling_living.buckled) - return falling_living.buckled.zFall(force) + return falling_living.buckled.zFall(levels, force) if(!force && !can_z_move(direction, get_turf(src), direction == DOWN ? ZMOVE_SKIP_CANMOVEONTO|ZMOVE_FALL_FLAGS : ZMOVE_FALL_FLAGS)) return FALSE @@ -116,11 +116,12 @@ if(!CanZFall(get_turf(src), direction)) return FALSE + . = TRUE + spawn(0) - _doZFall(target, get_turf(src)) - return TRUE + _doZFall(target, get_turf(src), levels) -/atom/movable/proc/_doZFall(turf/destination, turf/prev_turf) +/atom/movable/proc/_doZFall(turf/destination, turf/prev_turf, levels) PRIVATE_PROC(TRUE) SHOULD_NOT_OVERRIDE(TRUE) @@ -128,7 +129,7 @@ return forceMoveWithGroup(destination, z_movement = ZMOVING_VERTICAL) - destination.zImpact(src, 1, prev_turf) + destination.zImpact(src, levels, prev_turf) /atom/movable/proc/CanZFall(turf/from, direction, anchor_bypass) if(anchored && !anchor_bypass) From 02e34978193e91abd0327c2a518b1ad1b6ff2264 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Sun, 8 Oct 2023 23:59:29 -0400 Subject: [PATCH 36/37] hubris --- code/modules/power/gravitygenerator.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm index 0aba2f8e45aa..99d105185aa2 100644 --- a/code/modules/power/gravitygenerator.dm +++ b/code/modules/power/gravitygenerator.dm @@ -318,7 +318,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) if(SSticker.current_state == GAME_STATE_PLAYING) investigate_log("was brought online and is now producing gravity for this level.", INVESTIGATE_GRAVITY) message_admins("The gravity generator was brought online [ADMIN_VERBOSEJMP(src)]") - shake_everyone() + shake_everyone() /obj/machinery/gravity_generator/main/proc/disable() From a13ffcfed879fc53a18a11c6cbf9c1a888b3f2d0 Mon Sep 17 00:00:00 2001 From: Kapu1178 <75460809+Kapu1178@users.noreply.github.com> Date: Mon, 9 Oct 2023 01:04:03 -0400 Subject: [PATCH 37/37] fix butchering --- code/datums/components/butchering.dm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm index bcbbf3eacda7..6574667a4b2b 100644 --- a/code/datums/components/butchering.dm +++ b/code/datums/components/butchering.dm @@ -30,6 +30,17 @@ if(_butcher_callback) butcher_callback = _butcher_callback + if(isitem(parent)) + RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(onItemAttack)) + +/datum/component/butchering/proc/onItemAttack(obj/item/source, mob/living/M, mob/living/user) + SIGNAL_HANDLER + + if(M.stat == DEAD && (M.butcher_results || M.guaranteed_butcher_results)) //can we butcher it? + if(butchering_enabled && (can_be_blunt || (source.sharpness & SHARP_EDGED))) + INVOKE_ASYNC(src, PROC_REF(startButcher), source, M, user) + return COMPONENT_CANCEL_ATTACK_CHAIN + /datum/component/butchering/proc/startButcher(obj/item/source, mob/living/M, mob/living/user) to_chat(user, span_notice("You begin to butcher [M]...")) playsound(M.loc, butcher_sound, 50, TRUE, -1)