[MIRROR] Meat Hook Rework (Accidental Features) [MDB IGNORE] (#25343)

* Meat Hook Rework (Accidental Features)

* Update meat_hook.dm

* Update meat_hook.dm

---------

Co-authored-by: san7890 <the@san7890.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-12-03 01:07:56 -05:00
committed by GitHub
co-authored by san7890 Bloop
parent 6c11df99a4
commit 1f9fc79c7d
5 changed files with 165 additions and 125 deletions
-11
View File
@@ -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)
-89
View File
@@ -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)
-1
View File
@@ -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.
@@ -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
if(!ismovable(target))
return
/obj/projectile/hook/Destroy()
qdel(chain)
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
-1
View File
@@ -834,7 +834,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"