Skip to content

Commit

Permalink
[MIRROR] Meat Hook Rework (Accidental Features) [MDB IGNORE] (#25343) (
Browse files Browse the repository at this point in the history
…#936)

* Meat Hook Rework (Accidental Features)

* Update meat_hook.dm

* Update meat_hook.dm

---------

Co-authored-by: SkyratBot <[email protected]>
Co-authored-by: san7890 <[email protected]>
Co-authored-by: Bloop <[email protected]>
  • Loading branch information
4 people authored Dec 3, 2023
1 parent 6f0e0d2 commit 17a7832
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 126 deletions.
11 changes: 0 additions & 11 deletions code/datums/elements/climbable.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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 changes: 0 additions & 89 deletions code/datums/forced_movement.dm

This file was deleted.

1 change: 0 additions & 1 deletion code/game/atoms_movable.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
190 changes: 166 additions & 24 deletions code/modules/projectiles/guns/special/meat_hook.dm
Original file line number Diff line number Diff line change
@@ -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."
Expand All @@ -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."
Expand All @@ -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"
Expand All @@ -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 change: 0 additions & 1 deletion tgstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 17a7832

Please sign in to comment.