diff --git a/code/datums/components/boomerang.dm b/code/datums/components/boomerang.dm new file mode 100644 index 00000000000..844baf475fc --- /dev/null +++ b/code/datums/components/boomerang.dm @@ -0,0 +1,85 @@ +///The cooldown period between last_boomerang_throw and it's methods of implementing a rebound proc. +#define BOOMERANG_REBOUND_INTERVAL (1 SECONDS) +/** + * If an ojvect is given the boomerang component, it should be thrown back to the thrower after either hitting it's target, or landing on the thrown tile. + * Thrown objects should be thrown back to the original thrower with this component, a number of tiles defined by boomerang_throw_range. + */ +/datum/component/boomerang + ///How far should the boomerang try to travel to return to the thrower? + var/boomerang_throw_range = 3 + ///If this boomerang is thrown, does it re-enable the throwers throw mode? + var/thrower_easy_catch_enabled = FALSE + ///This cooldown prevents our 2 throwing signals from firing too often based on how we implement those signals within thrown impacts. + COOLDOWN_DECLARE(last_boomerang_throw) + +/datum/component/boomerang/Initialize(boomerang_throw_range, thrower_easy_catch_enabled) + . = ..() + if(!isitem(parent)) //Only items support being thrown around like a boomerang, feel free to make this apply to humans later on. + return COMPONENT_INCOMPATIBLE + + //Assignments + if(boomerang_throw_range) + src.boomerang_throw_range = boomerang_throw_range + if(thrower_easy_catch_enabled) + src.thrower_easy_catch_enabled = thrower_easy_catch_enabled + +/datum/component/boomerang/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/prepare_throw) //Collect data on current thrower and the throwing datum + RegisterSignal(parent, COMSIG_MOVABLE_THROW_LANDED, .proc/return_missed_throw) + RegisterSignal(parent, COMSIG_MOVABLE_IMPACT, .proc/return_hit_throw) + +/datum/component/boomerang/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_MOVABLE_POST_THROW, COMSIG_MOVABLE_THROW_LANDED, COMSIG_MOVABLE_IMPACT)) + +/** + * Proc'd before the first thrown is performed in order to gather information regarding each throw as well as handle throw_mode as necessary. + * * source: Datum src from original signal call. + * * thrown_thing: The thrownthing datum from the parent object's latest throw. Updates thrown_boomerang. + * * spin: Carry over from POST_THROW, the speed of rotation on the boomerang when thrown. + */ +/datum/component/boomerang/proc/prepare_throw(datum/source, datum/thrownthing/thrown_thing, spin) + SIGNAL_HANDLER + if(thrower_easy_catch_enabled && thrown_thing?.thrower) + if(iscarbon(thrown_thing.thrower)) + var/mob/living/carbon/Carbon = thrown_thing.thrower + Carbon.throw_mode_on(THROW_MODE_TOGGLE) + return + +/** + * Proc that triggers when the thrown boomerang hits an object. + * * source: Datum src from original signal call. + * * hit_atom: The atom that has been hit by the boomerang component. + * * init_throwing_datum: The thrownthing datum that originally impacted the object, that we use to build the new throwing datum for the rebound. + */ +/datum/component/boomerang/proc/return_hit_throw(datum/source, atom/hit_atom, datum/thrownthing/init_throwing_datum) + SIGNAL_HANDLER + if (!COOLDOWN_FINISHED(src, last_boomerang_throw)) + return + var/obj/item/true_parent = parent + aerodynamic_swing(init_throwing_datum, true_parent) + +/** + * Proc that triggers when the thrown boomerang does not hit a target. + * * source: Datum src from original signal call. + * * throwing_datum: The thrownthing datum that originally impacted the object, that we use to build the new throwing datum for the rebound. + */ +/datum/component/boomerang/proc/return_missed_throw(datum/source, datum/thrownthing/throwing_datum) + SIGNAL_HANDLER + if(!COOLDOWN_FINISHED(src, last_boomerang_throw)) + return + var/obj/item/true_parent = parent + aerodynamic_swing(throwing_datum, true_parent) + +/** + * Proc that triggers when the thrown boomerang has been fully thrown, rethrowing the boomerang back to the thrower, and producing visible feedback. + * * throwing_datum: The thrownthing datum that originally impacted the object, that we use to build the new throwing datum for the rebound. + * * hit_atom: The atom that has been hit by the boomerang'd object. + */ +/datum/component/boomerang/proc/aerodynamic_swing(datum/thrownthing/throwing_datum, obj/item/true_parent) + var/mob/thrown_by = true_parent.thrownby?.resolve() + if(thrown_by) + addtimer(CALLBACK(true_parent, /atom/movable.proc/throw_at, thrown_by, boomerang_throw_range, throwing_datum.speed, null, TRUE), 1) + COOLDOWN_START(src, last_boomerang_throw, BOOMERANG_REBOUND_INTERVAL) + true_parent.visible_message(span_danger("[true_parent] is flying back at [throwing_datum.thrower]!"), \ + span_danger("You see [true_parent] fly back at you!"), \ + span_hear("You hear an aerodynamic woosh!")) diff --git a/code/game/objects/items/melee/baton.dm b/code/game/objects/items/melee/baton.dm index 888df747047..bb0bb18083d 100644 --- a/code/game/objects/items/melee/baton.dm +++ b/code/game/objects/items/melee/baton.dm @@ -626,12 +626,9 @@ convertible = FALSE custom_materials = list(/datum/material/iron = 10000, /datum/material/glass = 4000, /datum/material/silver = 10000, /datum/material/gold = 2000) -/obj/item/melee/baton/security/boomerang/throw_at(atom/target, range, speed, mob/thrower, spin = 1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) - if(active) - if(ishuman(thrower)) - var/mob/living/carbon/human/human_thrower = thrower - human_thrower.throw_mode_off(THROW_MODE_TOGGLE) //so they can catch it on the return. - return ..() +/obj/item/melee/baton/security/boomerang/Initialize(mapload) + . = ..() + AddComponent(/datum/component/boomerang, throw_range+2, TRUE) /obj/item/melee/baton/security/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) if(!active) @@ -640,8 +637,6 @@ var/mob/thrown_by = thrownby?.resolve() if(isliving(hit_atom) && !iscyborg(hit_atom) && !caught && prob(throw_stun_chance))//if they are a living creature and they didn't catch it finalize_baton_attack(hit_atom, thrown_by, in_attack_chain = FALSE) - if(thrown_by && !caught) - addtimer(CALLBACK(src, /atom/movable.proc/throw_at, thrown_by, throw_range+2, throw_speed, null, TRUE), 1) /obj/item/melee/baton/security/boomerang/loaded //Same as above, comes with a cell. preload_cell_type = /obj/item/stock_parts/cell/high diff --git a/code/modules/antagonists/clown_ops/clown_weapons.dm b/code/modules/antagonists/clown_ops/clown_weapons.dm index ea4a2133e6c..c3d13ae06ae 100644 --- a/code/modules/antagonists/clown_ops/clown_weapons.dm +++ b/code/modules/antagonists/clown_ops/clown_weapons.dm @@ -141,23 +141,18 @@ /obj/item/shield/energy/bananium/on_transform(obj/item/source, mob/user, active) . = ..() - adjust_slipperiness() + adjust_comedy() /* - * Adds or removes a slippery component, depending on whether the shield is active or not. + * Adds or removes a slippery and boomerang component, depending on whether the shield is active or not. */ -/obj/item/shield/energy/bananium/proc/adjust_slipperiness() +/obj/item/shield/energy/bananium/proc/adjust_comedy() if(enabled) AddComponent(/datum/component/slippery, 60, GALOSHES_DONT_HELP) + AddComponent(/datum/component/boomerang, throw_range+2, TRUE) else qdel(GetComponent(/datum/component/slippery)) - -/obj/item/shield/energy/bananium/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) - if(enabled) - if(iscarbon(thrower)) - var/mob/living/carbon/C = thrower - C.throw_mode_on(THROW_MODE_TOGGLE) //so they can catch it on the return. - return ..() + qdel(GetComponent(/datum/component/boomerang)) /obj/item/shield/energy/bananium/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) if(enabled) @@ -165,9 +160,6 @@ if(iscarbon(hit_atom) && !caught)//if they are a carbon and they didn't catch it var/datum/component/slippery/slipper = GetComponent(/datum/component/slippery) slipper.Slip(src, hit_atom) - var/mob/thrown_by = thrownby?.resolve() - if(thrown_by && !caught) - addtimer(CALLBACK(src, /atom/movable.proc/throw_at, thrown_by, throw_range+2, throw_speed, null, TRUE), 1) else return ..() diff --git a/tgstation.dme b/tgstation.dme index 430b2ff5aa9..22952675d5e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -651,6 +651,7 @@ #include "code\datums\components\bakeable.dm" #include "code\datums\components\beetlejuice.dm" #include "code\datums\components\bloodysoles.dm" +#include "code\datums\components\boomerang.dm" #include "code\datums\components\butchering.dm" #include "code\datums\components\caltrop.dm" #include "code\datums\components\chasm.dm"