mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 17:52:36 +00:00
## About The Pull Request Fixes tether stacking via beacons, you can freely cut your tether while moving, and cutting/snapping MODsuit tethers now also snaps the beacon they're connected to if said beacon was generated by a MODsuit projectile. Retracting the gloves or deactivating your MODsuit also snaps MODtethers you've created using it. Closes #88869 Closes #88866 Closes #89170 ## Changelog 🆑 qol: Snapping tethers now also removes their beacons qol: You can now cut tethers that you're attached to while in motion qol: Tethers now snap when you retract your gloves or disable your MODsuit fix: Fixed tether stacking issues /🆑
232 lines
8.8 KiB
Plaintext
232 lines
8.8 KiB
Plaintext
/// Creates a tether between two objects that limits movement range. Tether requires LOS and can be adjusted by left/right clicking its
|
|
/datum/component/tether
|
|
dupe_mode = COMPONENT_DUPE_ALLOWED
|
|
/// Other side of the tether
|
|
var/atom/tether_target
|
|
/// Maximum (and initial) distance that this tether can be adjusted to
|
|
var/max_dist
|
|
/// What the tether is going to be called
|
|
var/tether_name
|
|
/// Current extension distance
|
|
var/cur_dist
|
|
/// Embedded item that the tether "should" originate from
|
|
var/atom/embed_target
|
|
/// Beam effect
|
|
var/datum/beam/tether_beam
|
|
/// Tether module if we were created by one
|
|
var/obj/item/mod/module/tether/parent_module
|
|
/// Source, if any, for TRAIT_TETHER_ATTACHED we add
|
|
var/tether_trait_source
|
|
/// If TRUE, only add TRAIT_TETHER_ATTACHED to our parent
|
|
var/no_target_trait
|
|
|
|
/datum/component/tether/Initialize(atom/tether_target, max_dist = 7, tether_name, atom/embed_target = null, start_distance = null, \
|
|
parent_module = null, tether_trait_source = null, no_target_trait = FALSE)
|
|
if(!ismovable(parent) || !istype(tether_target) || !tether_target.loc)
|
|
return COMPONENT_INCOMPATIBLE
|
|
|
|
src.tether_target = tether_target
|
|
src.embed_target = embed_target
|
|
src.max_dist = max_dist
|
|
src.parent_module = parent_module
|
|
src.tether_trait_source = tether_trait_source
|
|
src.no_target_trait = no_target_trait
|
|
cur_dist = max_dist
|
|
if (start_distance != null)
|
|
cur_dist = start_distance
|
|
var/datum/beam/beam = tether_target.Beam(parent, "line", 'icons/obj/clothing/modsuit/mod_modules.dmi', emissive = FALSE, beam_type = /obj/effect/ebeam/tether)
|
|
tether_beam = beam
|
|
if (ispath(tether_name, /atom))
|
|
var/atom/tmp = tether_name
|
|
src.tether_name = initial(tmp.name)
|
|
else
|
|
src.tether_name = tether_name
|
|
if (!isnull(tether_trait_source))
|
|
ADD_TRAIT(parent, TRAIT_TETHER_ATTACHED, tether_trait_source)
|
|
if (!no_target_trait)
|
|
ADD_TRAIT(tether_target, TRAIT_TETHER_ATTACHED, tether_trait_source)
|
|
|
|
/datum/component/tether/RegisterWithParent()
|
|
RegisterSignal(parent, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(check_tether))
|
|
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(check_snap))
|
|
RegisterSignal(tether_target, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(check_tether))
|
|
RegisterSignal(tether_target, COMSIG_MOVABLE_MOVED, PROC_REF(check_snap))
|
|
RegisterSignal(tether_target, COMSIG_QDELETING, PROC_REF(on_delete))
|
|
RegisterSignal(tether_beam.visuals, COMSIG_CLICK, PROC_REF(beam_click))
|
|
// Also snap if the beam gets deleted, more of a backup check than anything
|
|
RegisterSignal(tether_beam.visuals, COMSIG_QDELETING, PROC_REF(on_delete))
|
|
|
|
if (!isnull(embed_target))
|
|
RegisterSignal(embed_target, COMSIG_ITEM_UNEMBEDDED, PROC_REF(on_embedded_removed))
|
|
RegisterSignal(embed_target, COMSIG_QDELETING, PROC_REF(on_delete))
|
|
|
|
if (!isnull(parent_module))
|
|
RegisterSignals(parent_module, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED, COMSIG_MOD_TETHER_SNAP), PROC_REF(snap))
|
|
RegisterSignal(parent_module, COMSIG_MODULE_TRIGGERED, PROC_REF(on_parent_use))
|
|
|
|
/datum/component/tether/UnregisterFromParent()
|
|
UnregisterSignal(parent, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED))
|
|
if (!isnull(tether_trait_source))
|
|
REMOVE_TRAIT(parent, TRAIT_TETHER_ATTACHED, tether_trait_source)
|
|
if (!QDELETED(tether_target))
|
|
UnregisterSignal(tether_target, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
|
|
if (!isnull(tether_trait_source) && !no_target_trait)
|
|
REMOVE_TRAIT(tether_target, TRAIT_TETHER_ATTACHED, tether_trait_source)
|
|
SEND_SIGNAL(tether_target, COMSIG_ATOM_TETHER_SNAPPED, tether_trait_source)
|
|
if (!QDELETED(tether_beam))
|
|
UnregisterSignal(tether_beam.visuals, list(COMSIG_CLICK, COMSIG_QDELETING))
|
|
qdel(tether_beam)
|
|
if (!QDELETED(embed_target))
|
|
UnregisterSignal(embed_target, list(COMSIG_ITEM_UNEMBEDDED, COMSIG_QDELETING))
|
|
SEND_SIGNAL(parent, COMSIG_ATOM_TETHER_SNAPPED, tether_trait_source)
|
|
|
|
/datum/component/tether/proc/check_tether(atom/source, new_loc)
|
|
SIGNAL_HANDLER
|
|
|
|
if (check_snap())
|
|
return
|
|
|
|
if (!isturf(new_loc))
|
|
to_chat(source, span_warning("[tether_name] prevents you from entering [new_loc]!"))
|
|
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
|
|
|
|
// If this was called, we know its a movable
|
|
var/atom/movable/movable_source = source
|
|
var/atom/movable/anchor = (source == tether_target ? parent : tether_target)
|
|
if (get_dist(anchor, new_loc) > cur_dist)
|
|
if (!istype(anchor) || anchor.anchored || !(!anchor.anchored && anchor.move_resist <= movable_source.move_force && anchor.Move(get_step_towards(anchor, new_loc))))
|
|
to_chat(source, span_warning("[tether_name] runs out of slack and prevents you from moving!"))
|
|
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
|
|
|
|
var/atom/blocker
|
|
var/anchor_dir = get_dir(source, anchor)
|
|
for (var/turf/line_turf in get_line(anchor, new_loc))
|
|
if (line_turf.density && line_turf != anchor.loc && line_turf != source.loc)
|
|
blocker = line_turf
|
|
break
|
|
if (line_turf == anchor.loc || line_turf == source.loc)
|
|
for (var/atom/in_turf in line_turf)
|
|
if ((in_turf.flags_1 & ON_BORDER_1) && (in_turf.dir & anchor_dir))
|
|
blocker = in_turf
|
|
break
|
|
else
|
|
for (var/atom/in_turf in line_turf)
|
|
if (in_turf.density && in_turf != source && in_turf != tether_target)
|
|
blocker = in_turf
|
|
break
|
|
|
|
if (!isnull(blocker))
|
|
break
|
|
|
|
if (blocker)
|
|
to_chat(source, span_warning("[tether_name] catches on [blocker] and prevents you from moving!"))
|
|
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
|
|
|
|
if (get_dist(anchor, new_loc) != cur_dist || !ismovable(source))
|
|
return
|
|
|
|
var/datum/drift_handler/handler = movable_source.drift_handler
|
|
if (isnull(handler))
|
|
return
|
|
handler.remove_angle_force(get_angle(anchor, source))
|
|
|
|
/datum/component/tether/proc/check_snap()
|
|
SIGNAL_HANDLER
|
|
|
|
var/atom/atom_target = parent
|
|
// Something broke us out, snap the tether
|
|
if (get_dist(atom_target, tether_target) > cur_dist + 1 || !isturf(atom_target.loc) || !isturf(tether_target.loc) || atom_target.z != tether_target.z)
|
|
snap()
|
|
|
|
/datum/component/tether/proc/snap()
|
|
SIGNAL_HANDLER
|
|
|
|
var/atom/atom_target = parent
|
|
atom_target.visible_message(span_warning("[atom_target]'s [tether_name] snaps!"), span_userdanger("Your [tether_name] snaps!"), span_hear("You hear a cable snapping."))
|
|
playsound(atom_target, 'sound/effects/snap.ogg', 50, TRUE)
|
|
qdel(src)
|
|
|
|
/datum/component/tether/proc/on_parent_use(obj/item/mod/module/module, atom/target)
|
|
SIGNAL_HANDLER
|
|
|
|
if (get_turf(target) == get_turf(tether_target))
|
|
return MOD_ABORT_USE
|
|
|
|
/datum/component/tether/proc/on_delete()
|
|
SIGNAL_HANDLER
|
|
qdel(src)
|
|
|
|
/datum/component/tether/proc/on_embedded_removed(atom/source, mob/living/victim)
|
|
SIGNAL_HANDLER
|
|
parent.AddComponent(/datum/component/tether, source, max_dist, tether_name, cur_dist)
|
|
qdel(src)
|
|
|
|
/datum/component/tether/proc/beam_click(atom/source, atom/location, control, params, mob/user)
|
|
SIGNAL_HANDLER
|
|
|
|
INVOKE_ASYNC(src, PROC_REF(process_beam_click), source, location, params, user)
|
|
|
|
/datum/component/tether/proc/process_beam_click(atom/source, atom/location, params, mob/user)
|
|
var/turf/nearest_turf
|
|
for (var/turf/line_turf in get_line(get_turf(parent), get_turf(tether_target)))
|
|
if (user.CanReach(line_turf))
|
|
nearest_turf = line_turf
|
|
break
|
|
|
|
if (isnull(nearest_turf))
|
|
return
|
|
|
|
if (!user.can_perform_action(nearest_turf))
|
|
nearest_turf.balloon_alert(user, "cannot reach!")
|
|
return
|
|
|
|
var/list/modifiers = params2list(params)
|
|
if(LAZYACCESS(modifiers, CTRL_CLICK))
|
|
location.balloon_alert(user, "cutting the tether...")
|
|
if (!do_after(user, 2 SECONDS, user, (user == parent || user == tether_target) ? IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE : NONE))
|
|
return
|
|
|
|
qdel(src)
|
|
location.balloon_alert(user, "tether cut!")
|
|
to_chat(parent, span_danger("Your [tether_name] has been cut!"))
|
|
return
|
|
|
|
if (LAZYACCESS(modifiers, RIGHT_CLICK))
|
|
if (cur_dist >= max_dist)
|
|
location.balloon_alert(user, "no coil remaining!")
|
|
return
|
|
cur_dist += 1
|
|
location.balloon_alert(user, "tether extended")
|
|
return
|
|
|
|
if (cur_dist <= 0)
|
|
location.balloon_alert(user, "too short!")
|
|
return
|
|
|
|
if (cur_dist > CEILING(get_dist(parent, tether_target), 1))
|
|
cur_dist -= 1
|
|
location.balloon_alert(user, "tether shortened")
|
|
return
|
|
|
|
if (!ismovable(parent) && !ismovable(tether_target))
|
|
location.balloon_alert(user, "too short!")
|
|
return
|
|
|
|
var/atom/movable/movable_parent = parent
|
|
var/atom/movable/movable_target = tether_target
|
|
|
|
if (istype(movable_parent) && !movable_parent.anchored && movable_parent.move_resist <= movable_target.move_force && movable_parent.Move(get_step(movable_parent.loc, get_dir(movable_parent, movable_target))))
|
|
cur_dist -= 1
|
|
location.balloon_alert(user, "tether shortened")
|
|
return
|
|
|
|
if (istype(movable_target) && !movable_target.anchored && movable_target.move_resist <= movable_parent.move_force && movable_target.Move(get_step(movable_target.loc, get_dir(movable_target, movable_parent))))
|
|
cur_dist -= 1
|
|
location.balloon_alert(user, "tether shortened")
|
|
return
|
|
|
|
location.balloon_alert(user, "too short!")
|
|
|
|
/obj/effect/ebeam/tether
|
|
mouse_opacity = MOUSE_OPACITY_ICON
|