diff --git a/code/__DEFINES/dcs/movable_signals.dm b/code/__DEFINES/dcs/movable_signals.dm index d4b07c5e9b1..24cad49c6b2 100644 --- a/code/__DEFINES/dcs/movable_signals.dm +++ b/code/__DEFINES/dcs/movable_signals.dm @@ -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" diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 96bf3eacf4b..d42a77fa1d9 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -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) /* diff --git a/code/datums/beam.dm b/code/datums/beam.dm index 19ac0049481..23776d1d8d0 100644 --- a/code/datums/beam.dm +++ b/code/datums/beam.dm @@ -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) + 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 diff --git a/code/datums/components/tether.dm b/code/datums/components/tether.dm new file mode 100644 index 00000000000..e5fde3c0ba9 --- /dev/null +++ b/code/datums/components/tether.dm @@ -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, "You can't move in that direction with the [tethered_to] tethered to you!") + 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, "You can't move in that direction with the [tethered_to] tethered to you!") + 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, "You try to pick up [tethered_to], you can't pull it's [tether_name] any farther!") + 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("[tethered_to]'s [tether_name] wears out and snaps back!") + terminate() + return TRUE // it breaks, let the parent move outside of the range again + return FALSE diff --git a/code/datums/movement_detector.dm b/code/datums/movement_detector.dm index 0de904dde2a..899ef7b0398 100644 --- a/code/datums/movement_detector.dm +++ b/code/datums/movement_detector.dm @@ -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 diff --git a/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm b/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm index fad169146e8..ed9e02690b6 100644 --- a/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm +++ b/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm @@ -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) diff --git a/code/game/machinery/iv_drip.dm b/code/game/machinery/iv_drip.dm index f969e8daaf5..a3e2b884527 100644 --- a/code/game/machinery/iv_drip.dm +++ b/code/game/machinery/iv_drip.dm @@ -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, "You attach [used] to [src].") 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) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 905bcf838f9..013999cbba1 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -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) diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm index 3de868857ac..74948f38762 100644 --- a/code/modules/antagonists/changeling/powers/mutations.dm +++ b/code/modules/antagonists/changeling/powers/mutations.dm @@ -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) diff --git a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm index 2d79c322411..023b12691f9 100644 --- a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm +++ b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm @@ -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 diff --git a/code/modules/mod/modules/module_kinesis.dm b/code/modules/mod/modules/module_kinesis.dm index 070c1122d83..968e9a573eb 100644 --- a/code/modules/mod/modules/module_kinesis.dm +++ b/code/modules/mod/modules/module_kinesis.dm @@ -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 diff --git a/code/modules/projectiles/guns/medbeam.dm b/code/modules/projectiles/guns/medbeam.dm index e2cce3d1dda..08950eab8d8 100644 --- a/code/modules/projectiles/guns/medbeam.dm +++ b/code/modules/projectiles/guns/medbeam.dm @@ -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("The medbeams cross and EXPLODE!") explosion(B.loc,0,3,5,8, cause = "Crossed beams") qdel(dummy) diff --git a/code/modules/reagents/reagent_containers/iv_bag.dm b/code/modules/reagents/reagent_containers/iv_bag.dm index c5e3245b424..df7ee4f5a25 100644 --- a/code/modules/reagents/reagent_containers/iv_bag.dm +++ b/code/modules/reagents/reagent_containers/iv_bag.dm @@ -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, "[src]'s needle is ripped out of you!") + 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, "[src]'s needle is ripped out of you!") - 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 diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi index 7bd1e539713..ab11d3de653 100644 Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ diff --git a/paradise.dme b/paradise.dme index ed3cb27b1fb..9db5919be35 100644 --- a/paradise.dme +++ b/paradise.dme @@ -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"