mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-06-25 08:05:31 +01:00
442ad835bc
## About The Pull Request Reverts space movement being affected by inertia What is kept: - Items have a varying force on your drift speed, ie heavier items will move you faster through space, and smaller items, slower. - Jetpacks can have varying force of impulse - the effect is applied directly to the mob's `inertia_move_multiplier` - Tethers are unreverted - they still stop you from drifting too far from the tether point, however you can no longer 'swing' with them. What is removed: - Multiple impulses in the same angle/direction no longer speeds you up. Only the fastest impulse in 1 direction is accounted for. - An impulse in a different angle/direction will completely override any existing impulses, even if they are faster. - Jetpack stabilizers are once again perfectly capable of immediately stopping any active impulses. TL;DR If you point yourself in a direction you will now go that direction ## Why It's Good For The Game The concept was fun and had potential but the fight between impulses vs tiles was very, very clunky and janky. Multiple fixes were attempted to reduce the jank but it ultimately nograv still acts very cumbersome and jetpacks are still very unappealing to use. Smartkar gave the go-ahead to revert this a while back so, o7. ## Changelog 🆑 Melbert del: Zero-gravity drifting is no longer affected by inertia, ie it has been reverted to what it once was. /🆑
342 lines
14 KiB
Plaintext
342 lines
14 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
|
|
/// Ref of 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
|
|
/// Are we currently attempting to forcefully shorten the tether?
|
|
var/force_moving_target = FALSE
|
|
|
|
/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
|
|
if(isatom(tether_trait_source))
|
|
stack_trace("Tried to add a [src.type] with a tether_trait_source that is a hard ref! Use REF() first before passing!")
|
|
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_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))
|
|
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)
|
|
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(is_moving = TRUE))
|
|
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)
|
|
|
|
// Ignore distance limitations if we're attempting to move the other part of the tether
|
|
if (get_dist(anchor, new_loc) > cur_dist && !force_moving_target)
|
|
if (!istype(anchor) || anchor.anchored || anchor.move_resist > movable_source.move_force)
|
|
to_chat(source, span_warning("[tether_name] runs out of slack and prevents you from moving!"))
|
|
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
|
|
|
|
force_moving_target = TRUE
|
|
if (!try_adjust_position(anchor, new_loc, source))
|
|
force_moving_target = FALSE
|
|
to_chat(source, span_warning("[tether_name] runs out of slack and prevents you from moving!"))
|
|
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
|
|
|
|
force_moving_target = FALSE
|
|
|
|
var/atom/blocker = check_line(anchor, new_loc, list(source))
|
|
if (blocker)
|
|
if (!istype(anchor) || anchor.anchored || anchor.move_resist > movable_source.move_force)
|
|
to_chat(source, span_warning("[tether_name] runs out of slack and prevents you from moving!"))
|
|
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
|
|
|
|
// If the tether would snag on something when we move, see if we could move to the side to get LOS back
|
|
if (!try_adjust_position(anchor, new_loc, source))
|
|
to_chat(source, span_warning("[tether_name] catches on [blocker] and prevents you from moving!"))
|
|
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
|
|
|
|
/// Try adjust the anchor's position to move closer to the target or regain LOS
|
|
/// true_source is an optional argument in case we're looking for a LOS/closer turf to a new location rather than the actual owner, and need to ignore them
|
|
/datum/component/tether/proc/try_adjust_position(atom/movable/anchor, atom/target, atom/true_source)
|
|
if (!istype(anchor) || anchor.anchored)
|
|
return FALSE
|
|
|
|
if (anchor.x == target.x && anchor.y == target.y)
|
|
return TRUE
|
|
|
|
var/datum/can_pass_info/pass_info = new(no_id = TRUE)
|
|
pass_info.pass_flags = anchor.pass_flags
|
|
pass_info.movement_type = anchor.movement_type
|
|
if (isliving(anchor))
|
|
var/mob/living/living_anchor = anchor
|
|
pass_info.is_living = TRUE
|
|
pass_info.mob_size = living_anchor.mob_size
|
|
pass_info.incorporeal_move = living_anchor.incorporeal_move
|
|
pass_info.is_bot = isbot(living_anchor)
|
|
|
|
var/list/pass_turfs = list()
|
|
var/turf/anchor_turf = get_turf(anchor)
|
|
|
|
var/primary_cardinal = null
|
|
if (abs(anchor.x - target.x) > abs(anchor.y - target.y))
|
|
primary_cardinal = anchor.x > target.x ? WEST : EAST
|
|
else
|
|
primary_cardinal = anchor.y > target.y ? SOUTH : NORTH
|
|
|
|
var/anchor_dir = get_dir(anchor, target)
|
|
if (primary_cardinal == anchor_dir)
|
|
pass_turfs += get_step(anchor, primary_cardinal)
|
|
else if (get_dist(anchor, get_step(target, REVERSE_DIR(primary_cardinal))) >= get_dist(anchor, get_step(target, REVERSE_DIR(anchor_dir))))
|
|
pass_turfs += get_step(anchor, anchor_dir)
|
|
pass_turfs += get_step(anchor, primary_cardinal)
|
|
else
|
|
pass_turfs += get_step(anchor, primary_cardinal)
|
|
pass_turfs += get_step(anchor, anchor_dir)
|
|
|
|
// Make a list of secondary dirs to try and sidestep into if we cannot go in our main direction
|
|
var/list/match_dirs = null
|
|
if (primary_cardinal == NORTH || primary_cardinal == SOUTH)
|
|
match_dirs = list(EAST, WEST)
|
|
else
|
|
match_dirs = list(NORTH, SOUTH)
|
|
|
|
for (var/match_dir in match_dirs)
|
|
if ((match_dir & primary_cardinal) != anchor_dir)
|
|
pass_turfs += get_step(anchor, match_dir | primary_cardinal)
|
|
|
|
for (var/match_dir in match_dirs)
|
|
pass_turfs += get_step(anchor, match_dir)
|
|
|
|
// The final list is something like (direct path, main cardinal, diagonals to main cardinal, 90* dirs to the main cardinal)
|
|
// Whichever one we manage to move onto first is our pick
|
|
|
|
var/list/turf_cache = list()
|
|
for (var/turf/pass_turf in pass_turfs) // keep the typecheck in case we accidentally go out of map bounds
|
|
if (pass_turf.density || get_dist(pass_turf, target) > cur_dist)
|
|
continue
|
|
if (anchor_turf.LinkBlockedWithAccess(pass_turf, pass_info))
|
|
continue
|
|
if (check_line(pass_turf, target, list(anchor, true_source), turf_cache))
|
|
continue
|
|
if (anchor.Move(pass_turf))
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/// Check LOS availibility of a tile, returns a blocking atom, if any
|
|
/// turf_cache could be used to reduce the amount of calculations if multiple lines are cast and expected to have multiple shared turfs
|
|
/// by sharing located results
|
|
/datum/component/tether/proc/check_line(atom/start, atom/end, list/to_ignore, list/turf_cache = list())
|
|
var/turf/start_loc = get_turf(start)
|
|
var/turf/end_loc = get_turf(end)
|
|
var/start_dir = get_dir(start_loc, end_loc)
|
|
var/end_dir = REVERSE_DIR(start_dir)
|
|
var/list/turf/turf_line = get_line(start_loc, end_loc)
|
|
for (var/turf/line_turf in turf_line)
|
|
if (turf_cache[line_turf])
|
|
return turf_cache[line_turf]
|
|
|
|
if (line_turf.density && line_turf != start_loc && line_turf != end_loc)
|
|
turf_cache[line_turf] = line_turf
|
|
return line_turf
|
|
|
|
if (line_turf == start_loc)
|
|
for (var/atom/in_turf in line_turf)
|
|
if (in_turf.density && (in_turf.flags_1 & ON_BORDER_1) && (in_turf.dir & start_dir) && in_turf != start && !(in_turf in to_ignore))
|
|
turf_cache[line_turf] = in_turf
|
|
return in_turf
|
|
continue
|
|
|
|
if (line_turf == end_loc)
|
|
for (var/atom/in_turf in line_turf)
|
|
if (in_turf.density && (in_turf.flags_1 & ON_BORDER_1) && (in_turf.dir & end_dir) && in_turf != end && !(in_turf in to_ignore))
|
|
turf_cache[line_turf] = in_turf
|
|
return in_turf
|
|
continue
|
|
|
|
for (var/atom/in_turf in line_turf)
|
|
if (!in_turf.density || (in_turf in to_ignore))
|
|
continue
|
|
if ((in_turf.flags_1 & ON_BORDER_1))
|
|
// If the tether is in a straight line, we can ignore border objects parallel to us
|
|
if (!(in_turf.dir & start_dir) && !(in_turf.dir & end_dir))
|
|
continue
|
|
// Also ignore objects that we don't intersect with
|
|
if (!(get_step(in_turf, in_turf.dir) in turf_line))
|
|
continue
|
|
|
|
turf_cache[line_turf] = in_turf
|
|
return in_turf
|
|
|
|
turf_cache[line_turf] = null
|
|
|
|
/datum/component/tether/proc/check_snap(atom/movable/source, atom/old_loc, dir, forced, list/old_locs, is_moving = FALSE)
|
|
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()
|
|
else if (!is_moving && check_line(atom_target, tether_target) && !(try_adjust_position(atom_target, tether_target) || try_adjust_position(tether_target, atom_target)))
|
|
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 (line_turf.IsReachableBy(user))
|
|
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
|