mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-24 04:27:35 +01:00
Refactors beams. Adds tethering. IV drips/bags are now tethered. (#29570)
* refactor * tether * tetherrrs * logic fix (i hope) * ci fix * ok please work
This commit is contained in:
@@ -89,3 +89,8 @@
|
||||
|
||||
///from base of /atom/movable/point_at: (atom/A, obj/effect/temp_visual/point/point)
|
||||
#define COMSIG_MOVABLE_POINTED "movable_pointed"
|
||||
|
||||
///signal sent out by /datum/component/tether when durability/line of sight fails : (atom/tethered)
|
||||
#define COMSIG_TETHER_DESTROYED "tether_snap"
|
||||
///a signal sent to /datum/component/tether by the tethered object to stop the tethering : (atom/tethered)
|
||||
#define COMSIG_TETHER_STOP "tether_stop"
|
||||
|
||||
@@ -61,6 +61,18 @@
|
||||
else if(dx < 0)
|
||||
. += 360
|
||||
|
||||
/// Angle between two arbitrary points and horizontal line same as [/proc/get_angle]
|
||||
/proc/get_angle_raw(start_x, start_y, start_pixel_x, start_pixel_y, end_x, end_y, end_pixel_x, end_pixel_y)
|
||||
var/dy = (32 * end_y + end_pixel_y) - (32 * start_y + start_pixel_y)
|
||||
var/dx = (32 * end_x + end_pixel_x) - (32 * start_x + start_pixel_x)
|
||||
if(!dy)
|
||||
return (dx >= 0) ? 90 : 270
|
||||
. = arctan(dx/dy)
|
||||
if(dy < 0)
|
||||
. += 180
|
||||
else if(dx < 0)
|
||||
. += 360
|
||||
|
||||
// Returns location. Returns null if no location was found.
|
||||
/proc/get_teleport_loc(turf/location, mob/target, distance = 1, density = TRUE, errorx = 0, errory = 0, eoffsetx = 0, eoffsety = 0)
|
||||
/*
|
||||
|
||||
+150
-80
@@ -1,91 +1,153 @@
|
||||
//Beam Datum and effect
|
||||
/** # Beam Datum and Effect
|
||||
* **IF YOU ARE LAZY AND DO NOT WANT TO READ, GO TO THE BOTTOM OF THE FILE AND USE THAT PROC!**
|
||||
*
|
||||
* This is the beam datum! It's a really neat effect for the game in drawing a line from one atom to another.
|
||||
* It has two parts:
|
||||
* The datum itself which manages redrawing the beam to constantly keep it pointing from the origin to the target.
|
||||
* The effect which is what the beams are made out of. They're placed in a line from the origin to target, rotated towards the target and snipped off at the end.
|
||||
* These effects are kept in a list and constantly created and destroyed (hence the proc names draw and reset, reset destroying all effects and draw creating more.)
|
||||
*
|
||||
* You can add more special effects to the beam itself by changing what the drawn beam effects do. For example you can make a vine that pricks people by making the beam_type
|
||||
* include a crossed proc that damages the crosser. Examples in venus_human_trap.dm
|
||||
*/
|
||||
/datum/beam
|
||||
///where the beam goes from
|
||||
var/atom/origin = null
|
||||
///where the beam goes to
|
||||
var/atom/target = null
|
||||
///list of beam objects. These have their visuals set by the visuals var which is created on starting
|
||||
var/list/elements = list()
|
||||
var/icon/base_icon = null
|
||||
///icon used by the beam.
|
||||
var/icon
|
||||
var/icon_state = "" //icon state of the main segments of the beam
|
||||
///icon state of the main segments of the beam
|
||||
var/icon_state = ""
|
||||
///The beam will qdel if it's longer than this many tiles.
|
||||
var/max_distance = 0
|
||||
var/endtime = 0
|
||||
var/sleep_time = 3
|
||||
var/finished = FALSE
|
||||
var/target_oldloc = null
|
||||
var/origin_oldloc = null
|
||||
var/static_beam = FALSE
|
||||
var/beam_type = /obj/effect/ebeam //must be subtype
|
||||
var/beamcolor
|
||||
///the objects placed in the elements list
|
||||
var/beam_type = /obj/effect/ebeam
|
||||
///This is used as the visual_contents of beams, so you can apply one effect to this and the whole beam will look like that. never gets deleted on redrawing.
|
||||
var/obj/effect/ebeam/visuals
|
||||
///The color of the beam we're drawing.
|
||||
var/beam_color
|
||||
///Should we use a turf for origin/target's x and y values instead
|
||||
var/use_get_turf
|
||||
|
||||
/datum/beam/New(beam_origin, beam_target,beam_icon = 'icons/effects/beam.dmi', beam_icon_state = "b_beam", time = 50, maxdistance = 10, btype = /obj/effect/ebeam, beam_sleep_time = 3, beam_color)
|
||||
endtime = world.time+time
|
||||
origin = beam_origin
|
||||
origin_oldloc = get_turf(origin)
|
||||
target = beam_target
|
||||
target_oldloc = get_turf(target)
|
||||
sleep_time = beam_sleep_time
|
||||
if(origin_oldloc == origin && target_oldloc == target)
|
||||
static_beam = TRUE
|
||||
max_distance = maxdistance
|
||||
base_icon = new(beam_icon,beam_icon_state)
|
||||
icon = beam_icon
|
||||
icon_state = beam_icon_state
|
||||
beam_type = btype
|
||||
beamcolor = beam_color
|
||||
/datum/beam/New(
|
||||
origin,
|
||||
target,
|
||||
icon = 'icons/effects/beam.dmi',
|
||||
icon_state = "b_beam",
|
||||
time = INFINITY,
|
||||
max_distance = INFINITY,
|
||||
beam_type = /obj/effect/ebeam,
|
||||
beam_color,
|
||||
use_get_turf = FALSE
|
||||
)
|
||||
src.origin = origin
|
||||
src.target = target
|
||||
src.icon = icon
|
||||
src.icon_state = icon_state
|
||||
src.max_distance = max_distance
|
||||
src.beam_type = beam_type
|
||||
src.beam_color = beam_color
|
||||
src.use_get_turf = use_get_turf
|
||||
if(time < INFINITY)
|
||||
QDEL_IN(src, time)
|
||||
|
||||
/**
|
||||
* Proc called by the atom Beam() proc. Sets up signals, and draws the beam for the first time.
|
||||
*/
|
||||
/datum/beam/proc/Start()
|
||||
visuals = new beam_type()
|
||||
visuals.icon = icon
|
||||
visuals.icon_state = icon_state
|
||||
visuals.color = beam_color
|
||||
visuals.vis_flags = VIS_INHERIT_PLANE|VIS_INHERIT_LAYER
|
||||
visuals.update_appearance()
|
||||
Draw()
|
||||
while(!finished && origin && target && world.time < endtime && get_dist(origin,target)<max_distance && origin.z == target.z)
|
||||
var/origin_turf = get_turf(origin)
|
||||
var/target_turf = get_turf(target)
|
||||
if(!static_beam && (origin_turf != origin_oldloc || target_turf != target_oldloc))
|
||||
origin_oldloc = origin_turf //so we don't keep checking against their initial positions, leading to endless Reset()+Draw() calls
|
||||
target_oldloc = target_turf
|
||||
Reset()
|
||||
Draw()
|
||||
sleep(sleep_time)
|
||||
RegisterSignal(origin, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING), PROC_REF(redrawing), TRUE)
|
||||
RegisterSignal(target, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING), PROC_REF(redrawing), TRUE)
|
||||
|
||||
qdel(src)
|
||||
|
||||
/datum/beam/proc/End()
|
||||
finished = TRUE
|
||||
|
||||
/datum/beam/proc/Reset()
|
||||
/**
|
||||
* Triggered by signals set up when the beam is set up. If it's still sane to create a beam, it removes the old beam, creates a new one. Otherwise it kills the beam.
|
||||
*
|
||||
* Arguments:
|
||||
* mover: either the origin of the beam or the target of the beam that moved.
|
||||
* oldloc: from where mover moved.
|
||||
* direction: in what direction mover moved from.
|
||||
*/
|
||||
/datum/beam/proc/redrawing(atom/movable/mover, atom/oldloc, direction)
|
||||
SIGNAL_HANDLER
|
||||
if(QDELING(src))
|
||||
return
|
||||
if(QDELETED(origin) || QDELETED(target) || get_dist(origin,target) > max_distance)
|
||||
qdel(src)
|
||||
return
|
||||
if(origin.z != target.z)
|
||||
if(!use_get_turf || !atoms_share_level(get_turf(origin), get_turf(target)))
|
||||
qdel(src)
|
||||
return
|
||||
QDEL_LIST_CONTENTS(elements)
|
||||
INVOKE_ASYNC(src, PROC_REF(Draw))
|
||||
|
||||
/datum/beam/Destroy()
|
||||
Reset()
|
||||
target = null
|
||||
QDEL_LIST_CONTENTS(elements)
|
||||
QDEL_NULL(visuals)
|
||||
UnregisterSignal(origin, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
|
||||
UnregisterSignal(target, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
|
||||
origin = null
|
||||
target = null
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Creates the beam effects and places them in a line from the origin to the target. Sets their rotation to make the beams face the target, too.
|
||||
*/
|
||||
/datum/beam/proc/Draw()
|
||||
var/Angle = round(get_angle(origin, target))
|
||||
var/origin_px = origin.pixel_x + origin.pixel_w
|
||||
var/origin_py = origin.pixel_y + origin.pixel_z
|
||||
var/target_px = target.pixel_x + target.pixel_w
|
||||
var/target_py = target.pixel_y + target.pixel_z
|
||||
|
||||
var/origin_x = origin.x
|
||||
var/origin_y = origin.y
|
||||
var/target_x = target.x
|
||||
var/target_y = target.y
|
||||
if(use_get_turf)
|
||||
var/turf/T = get_turf(origin)
|
||||
origin_x = T.x
|
||||
origin_y = T.y
|
||||
T = get_turf(target)
|
||||
target_x = T.x
|
||||
target_y = T.y
|
||||
|
||||
var/Angle = get_angle_raw(origin_x, origin_y, origin_px, origin_py, target_x, target_y, target_px, target_py)
|
||||
///var/Angle = round(get_angle(origin,target))
|
||||
var/matrix/rot_matrix = matrix()
|
||||
var/turf/origin_turf = get_turf(origin)
|
||||
rot_matrix.Turn(Angle)
|
||||
|
||||
//Translation vector for origin and target
|
||||
var/DX = (32*target.x+target.pixel_x)-(32*origin.x+origin.pixel_x)
|
||||
var/DY = (32*target.y+target.pixel_y)-(32*origin.y+origin.pixel_y)
|
||||
var/DX = (32 * target_x + target_px) - (32 * origin_x + origin_px)
|
||||
var/DY = (32 * target_y + target_py) - (32 * origin_y + origin_py)
|
||||
var/N = 0
|
||||
var/length = round(sqrt((DX)**2+(DY)**2)) //hypotenuse of the triangle formed by target and origin's displacement
|
||||
var/length = round(sqrt((DX)**2 + (DY)**2)) // hypotenuse of the triangle formed by target and origin's displacement
|
||||
|
||||
for(N in 0 to length-1 step 32)//-1 as we want < not <=, but we want the speed of X in Y to Z and step X
|
||||
var/obj/effect/ebeam/X = new beam_type(origin_oldloc)
|
||||
X.owner = src
|
||||
elements |= X
|
||||
for(N in 0 to length-1 step 32) // -1 as we want < not <=, but we want the speed of X in Y to Z and step X
|
||||
if(QDELETED(src))
|
||||
break
|
||||
var/obj/effect/ebeam/segment = new beam_type(origin_turf, src)
|
||||
elements += segment
|
||||
|
||||
//Assign icon, for main segments it's base_icon, for the end, it's icon+icon_state
|
||||
//cropped by a transparent box of length-N pixel size
|
||||
if(N+32>length)
|
||||
var/icon/II = new(icon, icon_state)
|
||||
II.DrawBox(null, 1, (length-N), 32, 32)
|
||||
X.icon = II
|
||||
//Assign our single visual ebeam to each ebeam's vis_contents
|
||||
//ends are cropped by a transparent box icon of length-N pixel size laid over the visuals obj
|
||||
if(N + 32 > length) //went past the target, we draw a box of space to cut away from the beam sprite so the icon actually ends at the center of the target sprite
|
||||
var/icon/II = new(icon, icon_state)//this means we exclude the overshooting object from the visual contents which does mean those visuals don't show up for the final bit of the beam...
|
||||
II.DrawBox(null, 1, (length-N), 32, 32)//in the future if you want to improve this, remove the drawbox and instead use a 513 filter to cut away at the final object's icon
|
||||
segment.icon = II
|
||||
segment.color = beam_color
|
||||
else
|
||||
X.icon = base_icon
|
||||
if(beamcolor)
|
||||
X.color = beamcolor
|
||||
X.transform = rot_matrix
|
||||
segment.vis_contents += visuals
|
||||
segment.transform = rot_matrix
|
||||
|
||||
//Calculate pixel offsets (If necessary)
|
||||
var/Pixel_x
|
||||
@@ -93,40 +155,44 @@
|
||||
if(DX == 0)
|
||||
Pixel_x = 0
|
||||
else
|
||||
Pixel_x = round(sin(Angle)+32*sin(Angle)*(N+16)/32)
|
||||
Pixel_x = round(sin(Angle) + 32 * sin(Angle) * (N + 16) / 32)
|
||||
if(DY == 0)
|
||||
Pixel_y = 0
|
||||
else
|
||||
Pixel_y = round(cos(Angle) + 32 * cos(Angle) * (N + 16) / 32)
|
||||
|
||||
//Position the effect so the beam is one continous line
|
||||
var/final_x = X.x
|
||||
var/final_y = X.y
|
||||
if(abs(Pixel_x)>32)
|
||||
final_x += Pixel_x > 0 ? round(Pixel_x / 32) : CEILING(Pixel_x / 32, 1)
|
||||
var/final_x = segment.x
|
||||
var/final_y = segment.y
|
||||
if(abs(Pixel_x) > 32)
|
||||
final_x += Pixel_x > 0 ? round(Pixel_x/32) : CEILING(Pixel_x/32, 1)
|
||||
Pixel_x %= 32
|
||||
if(abs(Pixel_y)>32)
|
||||
final_y += Pixel_y > 0 ? round(Pixel_y / 32) : CEILING(Pixel_y / 32, 1)
|
||||
if(abs(Pixel_y) > 32)
|
||||
final_y += Pixel_y > 0 ? round(Pixel_y/32) : CEILING(Pixel_y/32, 1)
|
||||
Pixel_y %= 32
|
||||
|
||||
var/turf/T = locate(final_x, final_y, X.z)
|
||||
if(T)
|
||||
X.forceMove(locate(final_x, final_y, X.z))
|
||||
X.pixel_x = origin.pixel_x + Pixel_x
|
||||
X.pixel_y = origin.pixel_y + Pixel_y
|
||||
segment.forceMove(locate(final_x, final_y, segment.z))
|
||||
segment.pixel_x = origin_px + Pixel_x
|
||||
segment.pixel_y = origin_py + Pixel_y
|
||||
CHECK_TICK
|
||||
|
||||
/obj/effect/ebeam
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
anchored = TRUE
|
||||
var/datum/beam/owner
|
||||
|
||||
/obj/effect/ebeam/Initialize(mapload)
|
||||
/obj/effect/ebeam/Initialize(mapload, beam_owner)
|
||||
. = ..()
|
||||
owner = beam_owner
|
||||
var/static/list/loc_connections = list(
|
||||
COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered),
|
||||
)
|
||||
AddElement(/datum/element/connect_loc, loc_connections)
|
||||
|
||||
/obj/effect/ebeam/Destroy()
|
||||
owner = null
|
||||
return ..()
|
||||
|
||||
/obj/effect/ebeam/proc/on_atom_entered(datum/source, atom/movable/entered)
|
||||
SIGNAL_HANDLER // ON_ATOM_ENTERED
|
||||
return
|
||||
@@ -134,10 +200,6 @@
|
||||
/obj/effect/ebeam/ex_act(severity)
|
||||
return
|
||||
|
||||
/obj/effect/ebeam/Destroy()
|
||||
owner = null
|
||||
return ..()
|
||||
|
||||
/obj/effect/ebeam/singularity_pull()
|
||||
return
|
||||
|
||||
@@ -178,7 +240,15 @@
|
||||
var/armor = L.run_armor_check(limb_to_hit, LASER)
|
||||
L.apply_damage(damage, BURN, limb_to_hit, armor)
|
||||
|
||||
/atom/proc/Beam(atom/BeamTarget, icon_state="b_beam", icon='icons/effects/beam.dmi', time = 5 SECONDS, maxdistance = 10, beam_type = /obj/effect/ebeam, beam_sleep_time = 3, beam_color)
|
||||
var/datum/beam/newbeam = new(src, BeamTarget, icon, icon_state, time, maxdistance, beam_type, beam_sleep_time, beam_color)
|
||||
/atom/proc/Beam(atom/BeamTarget,
|
||||
icon_state = "b_beam",
|
||||
icon = 'icons/effects/beam.dmi',
|
||||
time = 5 SECONDS,
|
||||
maxdistance = 10,
|
||||
beam_type = /obj/effect/ebeam,
|
||||
beam_color,
|
||||
use_get_turf = FALSE
|
||||
)
|
||||
var/datum/beam/newbeam = new(src, BeamTarget, icon, icon_state, time, maxdistance, beam_type, beam_color, use_get_turf)
|
||||
INVOKE_ASYNC(newbeam, TYPE_PROC_REF(/datum/beam, Start))
|
||||
return newbeam
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/datum/component/tether
|
||||
dupe_mode = COMPONENT_DUPE_ALLOWED
|
||||
var/atom/movable/tethered_to
|
||||
// Durability decreases with every on_range. Snaps when durability = 0.
|
||||
var/durability = INFINITY
|
||||
var/max_range = 2
|
||||
var/tether_icon_state
|
||||
|
||||
var/datum/movement_detector/tether_tracker
|
||||
var/datum/movement_detector/parent_tracker
|
||||
var/datum/beam/beam
|
||||
var/atom/movable/tether_visual_target
|
||||
var/tether_name
|
||||
var/last_movement = 0
|
||||
|
||||
/datum/component/tether/Initialize(tethered_atom_movable, range = 2, durability = INFINITY, tether_icon_state = "tether", tether_name = "tether")
|
||||
. = ..()
|
||||
if(!isatom(parent) || !ismovable(tethered_atom_movable))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.tethered_to = tethered_atom_movable
|
||||
src.max_range = range
|
||||
src.durability = durability // movement is bugged right now. Durability wears out 3 times faster than expected. 1 durability should last 1 tile.
|
||||
src.tether_icon_state = tether_icon_state
|
||||
src.tether_name = tether_name
|
||||
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_movement))
|
||||
RegisterSignal(tethered_atom_movable, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(pre_tethered_movement))
|
||||
RegisterSignal(tethered_atom_movable, COMSIG_ATOM_ATTACK_HAND, PROC_REF(can_equip))
|
||||
parent_tracker = new /datum/movement_detector(parent, CALLBACK(src, PROC_REF(parent_check_bounds)))
|
||||
tether_tracker = new /datum/movement_detector(tethered_atom_movable, CALLBACK(src, PROC_REF(check_bounds)))
|
||||
parent_tracker.fix_signal()
|
||||
make_beam(tether_tracker.fix_signal() || tethered_to)
|
||||
|
||||
RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(terminate))
|
||||
RegisterSignal(tethered_atom_movable, list(COMSIG_PARENT_QDELETING, COMSIG_TETHER_STOP), PROC_REF(terminate))
|
||||
|
||||
if(get_dist(get_turf(parent), get_turf(tethered_to)) <= max_range)
|
||||
return
|
||||
tether_move_towards_parent()
|
||||
|
||||
/datum/component/tether/Destroy(force, silent)
|
||||
QDEL_NULL(tether_tracker)
|
||||
QDEL_NULL(parent_tracker)
|
||||
if(!QDELETED(beam))
|
||||
qdel(beam)
|
||||
beam = null
|
||||
SEND_SIGNAL(tethered_to, COMSIG_TETHER_DESTROYED)
|
||||
tether_visual_target = null
|
||||
tethered_to = null
|
||||
return ..()
|
||||
|
||||
/datum/component/tether/proc/make_beam(target)
|
||||
if(target == tether_visual_target)
|
||||
return
|
||||
tether_visual_target = target
|
||||
if(beam)
|
||||
UnregisterSignal(beam, COMSIG_PARENT_QDELETING)
|
||||
QDEL_NULL(beam)
|
||||
var/atom/atom = parent
|
||||
beam = atom.Beam(tether_visual_target, icon_state = tether_icon_state, time = INFINITY, use_get_turf = TRUE)
|
||||
RegisterSignal(beam, COMSIG_PARENT_QDELETING, PROC_REF(terminate))
|
||||
|
||||
// /datum/component/tether/proc/on_movement(atom/source, atom/oldloc, dir, forced)
|
||||
/datum/component/tether/proc/on_movement(atom/movable/source, newloc)
|
||||
// check if the item is in range. If not, try to move it towards the parent.
|
||||
if(get_dist(get_turf(tethered_to), newloc) <= max_range)
|
||||
return
|
||||
if(source.pulledby == tethered_to.loc)
|
||||
return
|
||||
if(tether_move_towards_parent(newloc))
|
||||
return
|
||||
if(get_dir(parent, newloc) & get_dir(parent, tethered_to))
|
||||
return
|
||||
var/atom/atom = parent
|
||||
atom?.setDir(get_dir(parent, newloc))
|
||||
to_chat(atom, "<span class='warning'>You can't move in that direction with the [tethered_to] tethered to you!</span>")
|
||||
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
|
||||
|
||||
/datum/component/tether/proc/pre_tethered_movement(atom/movable/source, newloc)
|
||||
// check if the item would be in range at its new tile. If not, return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
|
||||
if(get_dist(get_turf(parent), newloc) <= max_range)
|
||||
return
|
||||
if(get_dir(tethered_to, newloc) & get_dir(tethered_to, parent))
|
||||
return
|
||||
durability_cost()
|
||||
var/atom/atom = parent
|
||||
atom?.setDir(get_dir(parent, newloc))
|
||||
to_chat(atom, "<span class='warning'>You can't move in that direction with the [tethered_to] tethered to you!</span>")
|
||||
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
|
||||
|
||||
/datum/component/tether/proc/can_equip(atom/source, mob/user)
|
||||
// Check to make sure we dont go into an inventory outside of our range
|
||||
if(get_dist(get_turf(parent), get_turf(user)) > max_range)
|
||||
to_chat(user, "<span class='warning'>You try to pick up [tethered_to], you can't pull it's [tether_name] any farther!</span>")
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/datum/component/tether/proc/parent_check_bounds(atom/movable/source, atom/mover, atom/old_loc, direction)
|
||||
// check to see if the parent has moved out of range (while inside another objects contents). If it has, pull the tether towards the parent.
|
||||
if(get_dist(get_turf(parent), get_turf(tethered_to)) <= max_range)
|
||||
return
|
||||
if(source.pulledby == tethered_to.loc)
|
||||
return
|
||||
tether_move_towards_parent()
|
||||
|
||||
/datum/component/tether/proc/check_bounds(atom/movable/source, atom/mover, atom/old_loc, direction)
|
||||
// check to see if the tethered object is out of range.
|
||||
// If it is, make it be dropped from a mob's hand, or open a locker its in.
|
||||
make_beam(mover)
|
||||
if(get_dist(get_turf(parent), get_turf(tethered_to)) <= max_range)
|
||||
return
|
||||
if(ismovable(mover))
|
||||
var/atom/movable/a_mover = mover
|
||||
if(a_mover.pulledby == parent || a_mover.pulling == parent)
|
||||
return
|
||||
tether_move_towards_parent()
|
||||
|
||||
/datum/component/tether/proc/tether_move_towards_parent(parent_newloc)
|
||||
if(QDELETED(tethered_to) || !istype(tethered_to) || last_movement == world.time)
|
||||
return TRUE
|
||||
var/atom/current_loc = tethered_to.loc
|
||||
var/i = 0
|
||||
while(!isturf(current_loc) && current_loc != null && current_loc != current_loc.loc)
|
||||
if(i++ > 100)
|
||||
return TRUE
|
||||
if(ismob(current_loc))
|
||||
var/mob/M = current_loc
|
||||
M.drop_item_to_ground(tethered_to)
|
||||
current_loc = tethered_to.loc
|
||||
break
|
||||
if(istype(current_loc, /obj/item/storage))
|
||||
var/obj/item/storage/S = current_loc
|
||||
S.remove_from_storage(tethered_to, get_turf(tethered_to))
|
||||
current_loc = tethered_to.loc
|
||||
break
|
||||
if(istype(current_loc, /obj/structure/closet))
|
||||
var/obj/structure/closet/C = current_loc
|
||||
if(C.can_open())
|
||||
C.open()
|
||||
current_loc = tethered_to.loc
|
||||
break
|
||||
if(ismovable(current_loc))
|
||||
var/atom/movable/AM = current_loc
|
||||
if(AM.anchored)
|
||||
return FALSE
|
||||
if(isturf(AM.loc))
|
||||
last_movement = world.time
|
||||
durability_cost()
|
||||
step_towards(AM, get_turf(parent)) // holy shitcode
|
||||
return get_dist(get_turf(parent_newloc || parent), get_turf(tethered_to)) <= max_range
|
||||
current_loc = current_loc.loc
|
||||
if(get_dist(get_turf(parent), get_turf(tethered_to)) <= max_range)
|
||||
return TRUE
|
||||
last_movement = world.time
|
||||
durability_cost()
|
||||
if(!step_towards(tethered_to, get_turf(parent))) // shitcode
|
||||
return durability_cost(2) // use 3 total durability
|
||||
return TRUE
|
||||
|
||||
/datum/component/tether/proc/terminate(source, force)
|
||||
if(QDELETED(src))
|
||||
return
|
||||
qdel(src)
|
||||
|
||||
/datum/component/tether/proc/durability_cost(cost = 1)
|
||||
if(durability == INFINITY)
|
||||
return
|
||||
durability -= cost
|
||||
if(durability <= 0 && !QDELETED(tethered_to))
|
||||
tethered_to.visible_message("<span class='warning'>[tethered_to]'s [tether_name] wears out and snaps back!</span>")
|
||||
terminate()
|
||||
return TRUE // it breaks, let the parent move outside of the range again
|
||||
return FALSE
|
||||
@@ -33,6 +33,24 @@
|
||||
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
|
||||
target = target.loc
|
||||
|
||||
|
||||
/**
|
||||
* Movement detectors don't work by default unless the item starts on a turf.
|
||||
* Run this proc to fix that.
|
||||
*/
|
||||
/datum/movement_detector/proc/fix_signal()
|
||||
// we're already inside something
|
||||
var/atom/current_target = tracked.loc
|
||||
var/turf/newturf = get_turf(tracked)
|
||||
var/i = 0
|
||||
while(current_target.loc != newturf && current_target != null)
|
||||
current_target = current_target.loc
|
||||
if(i++ <= 100)
|
||||
return
|
||||
if(ismovable(current_target))
|
||||
RegisterSignal(current_target, COMSIG_MOVABLE_MOVED, PROC_REF(move_react), TRUE)
|
||||
return current_target
|
||||
|
||||
/**
|
||||
* Reacts to any movement that would cause a change in coordinates of the tracked movable atom
|
||||
* This works by detecting movement of either the tracked object, or anything it is inside, recursively
|
||||
|
||||
@@ -232,7 +232,7 @@
|
||||
var/mob/living/simple_animal/demon/shadow/current_demon = firer
|
||||
if(istype(current_demon))
|
||||
current_demon.block_shadow_crawl()
|
||||
firer.Beam(src, icon_state = "grabber_beam", time = INFINITY, maxdistance = INFINITY, beam_sleep_time = 1, beam_type = /obj/effect/ebeam/floor)
|
||||
firer.Beam(src, icon_state = "grabber_beam", time = INFINITY, maxdistance = INFINITY, beam_type = /obj/effect/ebeam/floor)
|
||||
return ..()
|
||||
|
||||
/obj/item/projectile/magic/shadow_hand/on_hit(atom/target, blocked, hit_zone)
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
if(bag)
|
||||
user.put_in_hands(bag)
|
||||
bag.update_icon(UPDATE_OVERLAYS)
|
||||
bag.update_iv_type()
|
||||
bag = null
|
||||
update_icon(UPDATE_OVERLAYS)
|
||||
|
||||
@@ -72,6 +73,7 @@
|
||||
|
||||
used.forceMove(src)
|
||||
bag = used
|
||||
bag.update_iv_type()
|
||||
to_chat(user, "<span class='notice'>You attach [used] to [src].</span>")
|
||||
update_icon(UPDATE_OVERLAYS)
|
||||
START_PROCESSING(SSmachines, src)
|
||||
@@ -95,8 +97,9 @@
|
||||
. += bag.examine(user)
|
||||
|
||||
/obj/machinery/iv_drip/Move(NewLoc, direct)
|
||||
var/oldloc = loc
|
||||
. = ..()
|
||||
if(!.) // ..() will return 0 if we didn't actually move anywhere.
|
||||
if(oldloc == loc) // ..() will return 0 if we didn't actually move anywhere, except for some diagonal cases.
|
||||
return
|
||||
playsound(loc, pick('sound/items/cartwheel1.ogg', 'sound/items/cartwheel2.ogg'), 75, TRUE, ignore_walls = FALSE)
|
||||
|
||||
|
||||
@@ -840,10 +840,9 @@
|
||||
held_items += held.UID()
|
||||
|
||||
/obj/structure/table/tray/Move(NewLoc, direct)
|
||||
var/atom/OldLoc = loc
|
||||
|
||||
var/atom/oldloc = loc
|
||||
. = ..()
|
||||
if(!.) // ..() will return 0 if we didn't actually move anywhere.
|
||||
if(oldloc == loc) // ..() will return 0 if we didn't actually move anywhere, except for some diagonal cases.
|
||||
return
|
||||
|
||||
if(direct & (direct - 1)) // This represents a diagonal movement, which is split into multiple cardinal movements. We'll handle moving the items on the cardinals only.
|
||||
@@ -857,7 +856,7 @@
|
||||
if(!held)
|
||||
held_items -= held_uid
|
||||
continue
|
||||
if(OldLoc != held.loc)
|
||||
if(oldloc != held.loc)
|
||||
held_items -= held_uid
|
||||
continue
|
||||
held.forceMove(NewLoc)
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
|
||||
/obj/item/projectile/tentacle/fire(setAngle)
|
||||
if(firer)
|
||||
chain = firer.Beam(src, icon_state = "tentacle", time = INFINITY, maxdistance = INFINITY, beam_sleep_time = 1)
|
||||
chain = firer.Beam(src, icon_state = "tentacle", time = INFINITY, maxdistance = INFINITY)
|
||||
..()
|
||||
|
||||
/mob/proc/tentacle_stab(mob/living/carbon/C)
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
anchors += locate(x + 2, y - 2, z)
|
||||
|
||||
for(var/turf/T in anchors)
|
||||
var/datum/beam/B = Beam(T, "vine", time=INFINITY, maxdistance=5, beam_type=/obj/effect/ebeam/vine)
|
||||
B.sleep_time = 10 //these shouldn't move, so let's slow down updates to 1 second (any slower and the deletion of the vines would be too slow)
|
||||
Beam(T, "vine", time = INFINITY, maxdistance = 5, beam_type = /obj/effect/ebeam/vine)
|
||||
addtimer(CALLBACK(src, PROC_REF(bear_fruit)), growth_time)
|
||||
|
||||
/obj/structure/alien/resin/flower_bud_enemy/proc/bear_fruit()
|
||||
@@ -76,8 +75,7 @@
|
||||
for(var/mob/living/L in grasping)
|
||||
if(L.stat == DEAD)
|
||||
var/datum/beam/B = grasping[L]
|
||||
if(B)
|
||||
B.End()
|
||||
qdel(B)
|
||||
grasping -= L
|
||||
|
||||
//Can attack+pull multiple times per cycle
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
grabbed_atom.add_overlay(kinesis_icon)
|
||||
pre_pixel_x = grabbed_atom.pixel_x
|
||||
pre_pixel_y = grabbed_atom.pixel_y
|
||||
beam.chain = beam.Beam(grabbed_atom, icon_state = "kinesis", icon='icons/effects/beam.dmi', time = 100 SECONDS, maxdistance = 15, beam_type = /obj/effect/ebeam, beam_sleep_time = 3)
|
||||
beam.chain = beam.Beam(grabbed_atom, icon_state = "kinesis", icon='icons/effects/beam.dmi', time = 100 SECONDS, maxdistance = 15, beam_type = /obj/effect/ebeam)
|
||||
kinesis_catcher = mod.wearer.overlay_fullscreen("kinesis", /atom/movable/screen/fullscreen/stretch/cursor_catcher/kinesis, 0)
|
||||
kinesis_catcher.assign_to_mob(mod.wearer)
|
||||
soundloop.start()
|
||||
@@ -113,8 +113,7 @@
|
||||
if(grabbed_atom.pixel_x == kinesis_catcher.given_x - world.icon_size/2 && grabbed_atom.pixel_y == kinesis_catcher.given_y - world.icon_size/2)
|
||||
return //spare us redrawing if we are standing still
|
||||
animate(grabbed_atom, 0.2 SECONDS, pixel_x = pre_pixel_x + kinesis_catcher.given_x - world.icon_size/2, pixel_y = pre_pixel_y + kinesis_catcher.given_y - world.icon_size/2)
|
||||
beam.chain.Reset()
|
||||
beam.chain.Draw()
|
||||
beam.chain.redrawing()
|
||||
return
|
||||
animate(grabbed_atom, 0.2 SECONDS, pixel_x = pre_pixel_x + kinesis_catcher.given_x - world.icon_size/2, pixel_y = pre_pixel_y + kinesis_catcher.given_y - world.icon_size/2)
|
||||
var/turf/next_turf = get_step_towards(grabbed_atom, kinesis_catcher.given_turf)
|
||||
@@ -137,8 +136,7 @@
|
||||
else if(direction & WEST)
|
||||
pixel_x_change = -world.icon_size / 2
|
||||
animate(grabbed_atom, 0.2 SECONDS, pixel_x = pre_pixel_x + pixel_x_change, pixel_y = pre_pixel_y + pixel_y_change) //Not as smooth as I would like, will look into this in the future
|
||||
beam.chain.Reset()
|
||||
beam.chain.Draw()
|
||||
beam.chain.redrawing()
|
||||
if(!isitem(grabbed_atom) || !COOLDOWN_FINISHED(src, hit_cooldown))
|
||||
return
|
||||
var/atom/hitting_atom
|
||||
|
||||
@@ -54,9 +54,8 @@
|
||||
|
||||
current_target = target
|
||||
active = TRUE
|
||||
var/datum/beam/current_beam = new(user,current_target,time=6000,beam_icon_state="medbeam",btype=/obj/effect/ebeam/medical)
|
||||
var/datum/beam/current_beam = user.Beam(current_target, "medbeam", time = 10 MINUTES, beam_type = /obj/effect/ebeam/medical)
|
||||
beam_UID = current_beam.UID()
|
||||
INVOKE_ASYNC(current_beam, TYPE_PROC_REF(/datum/beam, Start))
|
||||
|
||||
SSblackbox.record_feedback("tally", "gun_fired", 1, type)
|
||||
|
||||
@@ -87,6 +86,8 @@
|
||||
/obj/item/gun/medbeam/proc/los_check(atom/movable/user, mob/target)
|
||||
var/turf/user_turf = user.loc
|
||||
var/datum/beam/current_beam = locateUID(beam_UID)
|
||||
if(QDELETED(current_beam))
|
||||
return FALSE
|
||||
if(mounted)
|
||||
user_turf = get_turf(user)
|
||||
else if(!istype(user_turf))
|
||||
@@ -104,7 +105,7 @@
|
||||
qdel(dummy)
|
||||
return FALSE
|
||||
for(var/obj/effect/ebeam/medical/B in turf)// Don't cross the str-beams!
|
||||
if(B.owner != current_beam)
|
||||
if(B.owner && B.owner != current_beam && !QDELETED(B)) // only blow up if it has a CONFIRMED different owner than us. Don't want it blowing up on creation/deletion of beams.
|
||||
turf.visible_message("<span class='userdanger'>The medbeams cross and EXPLODE!</span>")
|
||||
explosion(B.loc,0,3,5,8, cause = "Crossed beams")
|
||||
qdel(dummy)
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
var/mob/living/carbon/human/injection_target
|
||||
var/injection_action_delay = 3 SECONDS
|
||||
|
||||
/obj/item/reagent_containers/iv_bag/Initialize(mapload)
|
||||
. = ..()
|
||||
RegisterSignal(src, COMSIG_TETHER_DESTROYED, PROC_REF(tether_snapped))
|
||||
|
||||
/obj/item/reagent_containers/iv_bag/Destroy()
|
||||
end_processing()
|
||||
return ..()
|
||||
@@ -52,12 +56,34 @@
|
||||
injection_target = target
|
||||
RegisterSignal(injection_target, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
|
||||
START_PROCESSING(SSobj, src)
|
||||
update_iv_type()
|
||||
|
||||
/obj/item/reagent_containers/iv_bag/proc/end_processing()
|
||||
/obj/item/reagent_containers/iv_bag/proc/update_iv_type()
|
||||
var/target = injection_target
|
||||
injection_target = null
|
||||
SEND_SIGNAL(src, COMSIG_TETHER_STOP)
|
||||
injection_target = target
|
||||
if(!injection_target)
|
||||
return
|
||||
if(istype(loc, /obj/machinery/iv_drip))
|
||||
injection_target.AddComponent(/datum/component/tether, src, 2, 50, "iv_tether", "tubing")
|
||||
else
|
||||
injection_target.AddComponent(/datum/component/tether, src, 1, 5, "iv_tether", "tubing")
|
||||
|
||||
/obj/item/reagent_containers/iv_bag/proc/end_processing(send_signal = TRUE)
|
||||
if(injection_target)
|
||||
UnregisterSignal(injection_target, COMSIG_PARENT_EXAMINE)
|
||||
injection_target = null
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
if(send_signal)
|
||||
SEND_SIGNAL(src, COMSIG_TETHER_STOP)
|
||||
|
||||
/obj/item/reagent_containers/iv_bag/proc/tether_snapped()
|
||||
if(!injection_target)
|
||||
return
|
||||
to_chat(injection_target, "<span class='userdanger'>[src]'s needle is ripped out of you!</span>")
|
||||
injection_target.apply_damage(3, BRUTE, pick("r_arm", "l_arm"))
|
||||
end_processing(FALSE)
|
||||
|
||||
/obj/item/reagent_containers/iv_bag/process()
|
||||
if(QDELETED(injection_target))
|
||||
@@ -69,12 +95,6 @@
|
||||
end_processing()
|
||||
return
|
||||
|
||||
if(get_dist(get_turf(src), get_turf(injection_target)) > 1)
|
||||
to_chat(injection_target, "<span class='userdanger'>[src]'s needle is ripped out of you!</span>")
|
||||
injection_target.apply_damage(3, BRUTE, pick("r_arm", "l_arm"))
|
||||
end_processing()
|
||||
return
|
||||
|
||||
if(mode) // Injecting
|
||||
if(reagents.total_volume)
|
||||
var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1) //The amount of reagents we'll transfer to the person
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
@@ -544,6 +544,7 @@
|
||||
#include "code\datums\components\sticky.dm"
|
||||
#include "code\datums\components\surgery_initiator.dm"
|
||||
#include "code\datums\components\swarming.dm"
|
||||
#include "code\datums\components\tether.dm"
|
||||
#include "code\datums\components\tilted.dm"
|
||||
#include "code\datums\components\tippable.dm"
|
||||
#include "code\datums\components\two_handed.dm"
|
||||
|
||||
Reference in New Issue
Block a user