diff --git a/code/datums/elements/climbable.dm b/code/datums/elements/climbable.dm index b26990c5911..56c16f303c4 100644 --- a/code/datums/elements/climbable.dm +++ b/code/datums/elements/climbable.dm @@ -23,7 +23,6 @@ RegisterSignal(target, COMSIG_ATOM_ATTACK_HAND, PROC_REF(attack_hand)) RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) RegisterSignal(target, COMSIG_MOUSEDROPPED_ONTO, PROC_REF(mousedrop_receive)) - RegisterSignal(target, COMSIG_ATOM_BUMPED, PROC_REF(try_speedrun)) ADD_TRAIT(target, TRAIT_CLIMBABLE, ELEMENT_TRAIT(type)) /datum/element/climbable/Detach(datum/target) @@ -104,8 +103,6 @@ var/x_dist = (text2num(LAZYACCESS(modifiers, ICON_X)) - world.icon_size/2) * (climbed_thing.dir & WEST ? -1 : 1) var/y_dist = (text2num(LAZYACCESS(modifiers, ICON_Y)) - world.icon_size/2) * (climbed_thing.dir & SOUTH ? -1 : 1) dir_step = (x_dist >= y_dist ? (EAST|WEST) : (NORTH|SOUTH)) & climbed_thing.dir - else //user is being moved by a forced_movement datum. dir_step will be the direction to the forced movement target. - dir_step = get_dir(user, user.force_moving.target) else dir_step = get_dir(user, get_step(climbed_thing, climbed_thing.dir)) . = step(user, dir_step) @@ -121,11 +118,3 @@ var/mob/living/living_target = dropped_atom if(living_target.mobility_flags & MOBILITY_MOVE) INVOKE_ASYNC(src, PROC_REF(climb_structure), climbed_thing, living_target, params) - -///Tries to climb onto the target if the forced movement of the mob allows it -/datum/element/climbable/proc/try_speedrun(datum/source, mob/bumpee) - SIGNAL_HANDLER - if(!istype(bumpee)) - return - if(bumpee.force_moving?.allow_climbing) - do_climb(source, bumpee) diff --git a/code/datums/forced_movement.dm b/code/datums/forced_movement.dm deleted file mode 100644 index 407f258281c..00000000000 --- a/code/datums/forced_movement.dm +++ /dev/null @@ -1,89 +0,0 @@ -//Just new and forget -//Depricated, use movement loops instead. Exists to support things that want to move more then 10 times a second -/datum/forced_movement - var/atom/movable/victim - var/atom/target - var/last_processed - var/steps_per_tick - var/allow_climbing - var/datum/callback/on_step - var/moved_at_all = FALSE - //as fast as ssfastprocess -/datum/forced_movement/New(atom/movable/_victim, atom/_target, _steps_per_tick = 0.5, _allow_climbing = FALSE, datum/callback/_on_step = null) - victim = _victim - target = _target - steps_per_tick = _steps_per_tick - allow_climbing = _allow_climbing - on_step = _on_step - - . = ..() - - if(_victim && _target && _steps_per_tick && !_victim.force_moving) - last_processed = world.time - _victim.force_moving = src - START_PROCESSING(SSfastprocess, src) - else - qdel(src) //if you want to overwrite the current forced movement, call qdel(victim.force_moving) before creating this - -/datum/forced_movement/Destroy() - if(victim.force_moving == src) - victim.force_moving = null - if(moved_at_all) - victim.forceMove(victim.loc) //get the side effects of moving here that require us to currently not be force_moving aka reslipping on ice - STOP_PROCESSING(SSfastprocess, src) - victim = null - target = null - on_step = null - return ..() - -//Todo: convert -/datum/forced_movement/process() - if(QDELETED(victim) || !victim.loc || QDELETED(target) || !target.loc) - qdel(src) - return - var/steps_to_take = round(steps_per_tick * (world.time - last_processed)) - if(steps_to_take) - for(var/i in 1 to steps_to_take) - if(TryMove()) - moved_at_all = TRUE - if(on_step) - on_step.InvokeAsync() - else - qdel(src) - return - last_processed = world.time - -/datum/forced_movement/proc/TryMove(recursive = FALSE) - if(QDELETED(src)) //Our previous step caused deletion of this datum - return - - var/atom/movable/vic = victim //sanic - var/atom/tar = target - - if(!recursive) - . = step_towards(vic, tar) - - //shit way for getting around corners - if(!.) //If stepping towards the target failed - if(tar.x > vic.x) //If we're going x, step x - if(step(vic, EAST)) - . = TRUE - else if(tar.x < vic.x) - if(step(vic, WEST)) - . = TRUE - - if(!.) //If the x step failed, go y - if(tar.y > vic.y) - if(step(vic, NORTH)) - . = TRUE - else if(tar.y < vic.y) - if(step(vic, SOUTH)) - . = TRUE - - if(!.) //If both failed, try again for some reason - if(recursive) - return FALSE - else - . = TryMove(TRUE) - - . = . && (vic.loc != tar.loc) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 11d01fb827e..54f315e6a37 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -45,7 +45,6 @@ var/atom/movable/moving_from_pull ///Holds information about any movement loops currently running/waiting to run on the movable. Lazy, will be null if nothing's going on var/datum/movement_packet/move_packet - var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm /** * an associative lazylist of relevant nested contents by "channel", the list is of the form: list(channel = list(important nested contents of that type)) * each channel has a specific purpose and is meant to replace potentially expensive nested contents iteration. diff --git a/code/modules/projectiles/guns/special/meat_hook.dm b/code/modules/projectiles/guns/special/meat_hook.dm index dd83bbd030f..cfa932a6541 100644 --- a/code/modules/projectiles/guns/special/meat_hook.dm +++ b/code/modules/projectiles/guns/special/meat_hook.dm @@ -1,5 +1,7 @@ -//Meat Hook +#define TRAIT_HOOKED "hooked" +#define IMMOBILIZATION_TIMER (0.25 SECONDS) //! How long we immobilize the firer after firing - we do cancel the immobilization early if nothing is hit. +/// Meat Hook /obj/item/gun/magic/hook name = "meat hook" desc = "Mid or feed." @@ -22,8 +24,22 @@ /obj/item/gun/magic/hook/can_trigger_gun(mob/living/user, akimbo_usage) // This isn't really a gun, so it shouldn't be checking for TRAIT_NOGUNS, a firing pin (pinless), or a trigger guard (guardless) if(akimbo_usage) return FALSE //this would be kinda weird while shooting someone down. + if(HAS_TRAIT(user, TRAIT_IMMOBILIZED)) + return FALSE return TRUE +/obj/item/gun/magic/hook/suicide_act(mob/living/user) + var/obj/item/bodypart/head/removable = user.get_bodypart(BODY_ZONE_HEAD) + if(isnull(removable)) + user.visible_message(span_suicide("[user] stuffs the chain of the [src] down the hole where their head should be! It looks like [user.p_theyre()] trying to commit suicide!")) + return OXYLOSS + + playsound(get_turf(src), fire_sound, 50, TRUE, -1) + user.visible_message(span_suicide("[user] is using the [src] on their [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!")) + playsound(get_turf(src), 'sound/weapons/bladeslice.ogg', 70) + removable.dismember(silent = FALSE) + return BRUTELOSS + /obj/item/ammo_casing/magic/hook name = "hook" desc = "A hook." @@ -41,37 +57,154 @@ armour_penetration = 60 damage_type = BRUTE hitsound = 'sound/effects/splat.ogg' - var/chain - var/knockdown_time = (0.5 SECONDS) - var/chain_icon = 'icons/effects/beam.dmi' //SKYRAT ADDITION + /// The chain we send out while we are in motion, referred to as "initial" to not get confused with the chain we use to reel the victim in. + var/datum/beam/initial_chain + var/chain_icon = 'icons/effects/beam.dmi' // SKYRAT EDIT ADDITION /obj/projectile/hook/fire(setAngle) if(firer) - chain = firer.Beam(src, icon_state = "chain", icon = chain_icon, emissive = FALSE) //SKYRAT EDIT, original: chain = firer.Beam(src, icon_state = "chain", emissive = FALSE) - ..() - //TODO: root the firer until the chain returns + initial_chain = firer.Beam(src, icon_state = "chain", icon = chain_icon, emissive = FALSE) // SKYRAT EDIT CHANGE - Adds icon arg - ORIGINAL: chain = firer.Beam(src, icon_state = "chain", emissive = FALSE) + ADD_TRAIT(firer, TRAIT_IMMOBILIZED, REF(src)) + addtimer(TRAIT_CALLBACK_REMOVE(firer, TRAIT_IMMOBILIZED, REF(src)), IMMOBILIZATION_TIMER) // safety if we miss, if we get a hit we stay immobilized + return ..() /obj/projectile/hook/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() - if(ismovable(target)) - var/atom/movable/A = target - if(A.anchored) - return - A.visible_message(span_danger("[A] is snagged by [firer]'s hook!")) - //Should really be a movement loop, but I don't want to support moving 5 tiles a tick - //It just looks bad - new /datum/forced_movement(A, get_turf(firer), 5, TRUE) - if (isliving(target)) - var/mob/living/fresh_meat = target - fresh_meat.Knockdown(knockdown_time) - return - //TODO: keep the chain beamed to A - //TODO: needs a callback to delete the chain - -/obj/projectile/hook/Destroy() - qdel(chain) + if(!ismovable(target)) + return + + var/atom/movable/victim = target + if(victim.anchored || HAS_TRAIT_FROM(victim, TRAIT_HOOKED, REF(firer))) + return + + victim.visible_message(span_danger("[victim] is snagged by [firer]'s hook!")) + + var/datum/hook_and_move/puller = new + puller.begin_pulling(firer, victim, get_turf(firer)) + REMOVE_TRAIT(firer, TRAIT_IMMOBILIZED, REF(src)) + +/obj/projectile/hook/Destroy(force) + QDEL_NULL(initial_chain) + return ..() + +/// Lightweight datum that just handles moving a target for the hook. +/// For the love of God, do not use this outside this file. +/datum/hook_and_move + /// Weakref to the victim we are dragging + var/datum/weakref/victim_ref = null + /// Weakref of the destination that the victim is heading towards. + var/datum/weakref/destination_ref = null + /// Weakref to the firer of the hook + var/datum/weakref/firer_ref = null + /// String to the REF() of the dude that fired us so we can ensure we always cleanup our traits + var/firer_ref_string = null + + /// The last time our movement fired. + var/last_movement = 0 + /// The chain beam we currently own. + var/datum/beam/return_chain = null + + /// How many steps we force the victim to take per tick + var/steps_per_tick = 5 + /// How long we knockdown the victim for. + var/knockdown_time = (0.5 SECONDS) + + /// List of traits that prevent the user from moving. More restrictive than attempting to fire the hook by design. + var/static/list/prevent_movement_traits = list( + TRAIT_IMMOBILIZED, + TRAIT_UI_BLOCKED, + ) + +/datum/hook_and_move/Destroy(force) + STOP_PROCESSING(SSfastprocess, src) + QDEL_NULL(return_chain) return ..() +/// Uses fastprocessing to move our victim to the destination at a rather fast speed. +/datum/hook_and_move/proc/begin_pulling(atom/movable/firer, atom/movable/victim, atom/destination) + return_chain = firer.Beam(victim, icon_state = "chain", emissive = FALSE) + + firer_ref_string = REF(firer) + ADD_TRAIT(victim, TRAIT_HOOKED, firer_ref_string) + firer.add_traits(prevent_movement_traits, REF(src)) + if(isliving(victim)) + var/mob/living/fresh_meat = victim + fresh_meat.Knockdown(knockdown_time) + + destination_ref = WEAKREF(destination) + victim_ref = WEAKREF(victim) + firer_ref = WEAKREF(firer) + + START_PROCESSING(SSfastprocess, src) + +/// Cancels processing and removes the trait from the victim. +/datum/hook_and_move/proc/end_movement() + var/atom/movable/firer = firer_ref?.resolve() + if(!QDELETED(firer)) + firer.remove_traits(prevent_movement_traits, REF(src)) + + var/atom/movable/victim = victim_ref?.resolve() + if(!QDELETED(victim)) + REMOVE_TRAIT(victim, TRAIT_HOOKED, firer_ref_string) + + qdel(src) + +/datum/hook_and_move/process(seconds_per_tick) + var/atom/movable/victim = victim_ref?.resolve() + var/atom/destination = destination_ref?.resolve() + if(QDELETED(victim) || QDELETED(destination)) + end_movement() + return + + var/steps_to_take = round(steps_per_tick * (world.time - last_movement)) + if(steps_to_take <= 0) + return + + var/movement_result = attempt_movement(victim, destination) + if(!movement_result || (victim.loc == destination.loc)) // either we failed our movement or our mission is complete + end_movement() + +/// Attempts to move the victim towards the destination. Returns TRUE if we do a successful movement, FALSE otherwise. +/// second_attempt is a boolean to prevent infinite recursion. +/// If this whole series of events wasn't reliant on SSfastprocess firing as fast as it does, it would have been more useful to make this a move loop datum. But, we need the speed. +/datum/hook_and_move/proc/attempt_movement(atom/movable/subject, atom/target, second_attempt = FALSE) + var/actually_moved = FALSE + if(!second_attempt) + actually_moved = step_towards(subject, target) + + if(actually_moved) + return TRUE + + // alright now the code fucking sucks + var/subject_x = subject.x + var/subject_y = subject.y + var/target_x = target.x + var/target_y = target.y + + //If we're going x, step x + if((target_x > subject_x) && step(subject, EAST)) + actually_moved = TRUE + else if((target_x < subject_x) && step(subject, WEST)) + actually_moved = TRUE + + if(actually_moved) + return TRUE + + //If the x step failed, go y + if((target_y > subject_y) && step(subject, NORTH)) + actually_moved = TRUE + else if((target_y < subject_y) && step(subject, SOUTH)) + actually_moved = TRUE + + if(actually_moved) + return TRUE + + // if we fail twice, abort. otherwise queue up the second attempt. + if(second_attempt) + return FALSE + + return attempt_movement(subject, target, second_attempt = TRUE) + //just a nerfed version of the real thing for the bounty hunters. /obj/item/gun/magic/hook/bounty name = "hook" @@ -83,3 +216,12 @@ /obj/projectile/hook/bounty damage = 0 stamina = 40 + +/// Debug hook for fun (AKA admin abuse). doesn't do any more damage or anything just lets you wildfire it. +/obj/item/gun/magic/hook/debug + name = "super meat hook" + max_charges = 100 + recharge_rate = 1 + +#undef TRAIT_HOOKED +#undef IMMOBILIZATION_TIMER diff --git a/tgstation.dme b/tgstation.dme index 5bb9f921fb7..6542ef67643 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -842,7 +842,6 @@ #include "code\datums\ductnet.dm" #include "code\datums\emotes.dm" #include "code\datums\ert.dm" -#include "code\datums\forced_movement.dm" #include "code\datums\hailer_phrase.dm" #include "code\datums\holocall.dm" #include "code\datums\hotkeys_help.dm"