From c88321f0c0cd0b4f7357827279f6408c539d90d3 Mon Sep 17 00:00:00 2001 From: NovaBot <154629622+NovaBot13@users.noreply.github.com> Date: Tue, 19 Mar 2024 22:06:45 -0400 Subject: [PATCH] [MIRROR] Infrared Emitters use beam datums (As God intended) (#1539) * Infrared Emitters use beam datums (As God intended) (#82094) ## About The Pull Request Fixes #64459 Fixes #82052 , probably Fixes #79747 , probably Fixes #81443 Infrared emitters now use beam datums instead of making their own effects and trying to `Move` it out until it hits something This means 1. Infrared emitters are (probably) more responsive 2. Infrared emitters (probably) react to less things they shouldn't react to (such as projectiles) This also means - Infrared emitters (when visible) are emissive (and glow in the dark slightly). Kinda neat? If you don't want it you can obviously just make it invisible - You can limbo under beams? Maybe you can do that already? IDK Other notes - Beams no longer set their beam component's `x`, `y` directly, now using `forceMove` ![image](https://github.com/tgstation/tgstation/assets/51863163/1d516703-1f95-4c8e-a83b-89acaf20e5af) ![image](https://github.com/tgstation/tgstation/assets/51863163/ddb8eb51-f787-4def-82bd-8c2b878327f6) https://github.com/tgstation/tgstation/assets/51863163/29b76b58-ef36-4c4a-a3b2-017b625389dd ## Changelog :cl: Melbert refactor: Infrared emitters now function better (or at least more how you would expect them) (hopefully). Report any oddities /:cl: * Infrared Emitters use beam datums (As God intended) --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- code/__DEFINES/dcs/signals/signals_beam.dm | 9 + code/datums/beam.dm | 73 ++- .../antagonists/heretic/magic/fire_blast.dm | 20 +- code/modules/assembly/infrared.dm | 420 +++++++++++------- 4 files changed, 329 insertions(+), 193 deletions(-) diff --git a/code/__DEFINES/dcs/signals/signals_beam.dm b/code/__DEFINES/dcs/signals/signals_beam.dm index edfbc7c4371..12d58b70c9a 100644 --- a/code/__DEFINES/dcs/signals/signals_beam.dm +++ b/code/__DEFINES/dcs/signals/signals_beam.dm @@ -1,3 +1,12 @@ /// Called before beam is redrawn #define COMSIG_BEAM_BEFORE_DRAW "beam_before_draw" #define BEAM_CANCEL_DRAW (1 << 0) + +/// Sent to a beam when an atom enters any turf the beam covers: (obj/effect/ebeam/hit_beam, atom/movable/entered) +#define COMSIG_BEAM_ENTERED "beam_entered" + +/// Sent to a beam when an atom exits any turf the beam covers: (obj/effect/ebeam/hit_beam, atom/movable/exited) +#define COMSIG_BEAM_EXITED "beam_exited" + +/// Sent to a beam when any turf the beam covers changes: (list/datum/callback/post_change_callbacks) +#define COMSIG_BEAM_TURFS_CHANGED "beam_turfs_changed" diff --git a/code/datums/beam.dm b/code/datums/beam.dm index 152de6ec1e1..52b649990d7 100644 --- a/code/datums/beam.dm +++ b/code/datums/beam.dm @@ -163,16 +163,16 @@ Pixel_y = round(cos(Angle)+32*cos(Angle)*(N+16)/32) //Position the effect so the beam is one continous line - var/a + var/final_x = segment.x + var/final_y = segment.y if(abs(Pixel_x)>32) - a = Pixel_x > 0 ? round(Pixel_x/32) : CEILING(Pixel_x/32, 1) - segment.x += a + final_x += Pixel_x > 0 ? round(Pixel_x/32) : CEILING(Pixel_x/32, 1) Pixel_x %= 32 if(abs(Pixel_y)>32) - a = Pixel_y > 0 ? round(Pixel_y/32) : CEILING(Pixel_y/32, 1) - segment.y += a + final_y += Pixel_y > 0 ? round(Pixel_y/32) : CEILING(Pixel_y/32, 1) Pixel_y %= 32 + 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 @@ -194,6 +194,7 @@ return var/mutable_appearance/emissive_overlay = emissive_appearance(icon, icon_state, src) emissive_overlay.transform = transform + emissive_overlay.alpha = alpha . += emissive_overlay /obj/effect/ebeam/Destroy() @@ -202,9 +203,69 @@ /obj/effect/ebeam/singularity_pull() return + /obj/effect/ebeam/singularity_act() return +/// A beam subtype used for advanced beams, to react to atoms entering the beam +/obj/effect/ebeam/reacting + /// If TRUE, atoms that exist in the beam's loc when inited count as "entering" the beam + var/react_on_init = FALSE + +/obj/effect/ebeam/reacting/Initialize(mapload, beam_owner) + . = ..() + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + COMSIG_ATOM_EXITED = PROC_REF(on_exited), + COMSIG_TURF_CHANGE = PROC_REF(on_turf_change), + ) + AddElement(/datum/element/connect_loc, loc_connections) + + if(!isturf(loc) || isnull(owner) || mapload || !react_on_init) + return + + for(var/atom/movable/existing as anything in loc) + beam_entered(existing) + +/obj/effect/ebeam/reacting/proc/on_entered(datum/source, atom/movable/entered) + SIGNAL_HANDLER + + if(isnull(owner)) + return + + beam_entered(entered) + +/obj/effect/ebeam/reacting/proc/on_exited(datum/source, atom/movable/exited) + SIGNAL_HANDLER + + if(isnull(owner)) + return + + beam_exited(exited) + +/obj/effect/ebeam/reacting/proc/on_turf_change(datum/source, path, new_baseturfs, flags, list/datum/callback/post_change_callbacks) + SIGNAL_HANDLER + + if(isnull(owner)) + return + + beam_turfs_changed(post_change_callbacks) + +/// Some atom entered the beam's line +/obj/effect/ebeam/reacting/proc/beam_entered(atom/movable/entered) + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(owner, COMSIG_BEAM_ENTERED, src, entered) + +/// Some atom exited the beam's line +/obj/effect/ebeam/reacting/proc/beam_exited(atom/movable/exited) + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(owner, COMSIG_BEAM_EXITED, src, exited) + +/// Some turf the beam covers has changed to a new turf type +/obj/effect/ebeam/reacting/proc/beam_turfs_changed(list/datum/callback/post_change_callbacks) + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(owner, COMSIG_BEAM_TURFS_CHANGED, post_change_callbacks) + /** * This is what you use to start a beam. Example: origin.Beam(target, args). **Store the return of this proc if you don't set maxdist or time, you need it to delete the beam.** * @@ -221,5 +282,3 @@ var/datum/beam/newbeam = new(src,BeamTarget,icon,icon_state,time,maxdistance,beam_type, beam_color, emissive, override_origin_pixel_x, override_origin_pixel_y, override_target_pixel_x, override_target_pixel_y ) INVOKE_ASYNC(newbeam, TYPE_PROC_REF(/datum/beam/, Start)) return newbeam - - diff --git a/code/modules/antagonists/heretic/magic/fire_blast.dm b/code/modules/antagonists/heretic/magic/fire_blast.dm index 1d2d7daaceb..f76a1f18d17 100644 --- a/code/modules/antagonists/heretic/magic/fire_blast.dm +++ b/code/modules/antagonists/heretic/magic/fire_blast.dm @@ -31,7 +31,7 @@ /datum/action/cooldown/spell/charged/beam/fire_blast/send_beam(atom/origin, mob/living/carbon/to_beam, bounces = 4) // Send a beam from the origin to the hit mob - origin.Beam(to_beam, icon_state = "solar_beam", time = beam_duration, beam_type = /obj/effect/ebeam/fire) + origin.Beam(to_beam, icon_state = "solar_beam", time = beam_duration, beam_type = /obj/effect/ebeam/reacting/fire) // If they block the magic, the chain wont necessarily stop, // but likely will (due to them not catching on fire) @@ -141,25 +141,11 @@ owner.adjustStaminaLoss(2 * tick_damage * seconds_between_ticks) // The beam fireblast spits out, causes people to walk through it to be on fire -/obj/effect/ebeam/fire +/obj/effect/ebeam/reacting/fire name = "fire beam" -/obj/effect/ebeam/fire/Initialize(mapload) +/obj/effect/ebeam/reacting/fire/beam_entered(atom/movable/entered) . = ..() - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = PROC_REF(on_entered), - ) - AddElement(/datum/element/connect_loc, loc_connections) - - if(!isturf(loc) || mapload) // idk if this would ever be maploaded but you never know - return - - for(var/mob/living/living_mob in loc) - on_entered(entered = living_mob) - -/obj/effect/ebeam/fire/proc/on_entered(datum/source, atom/movable/entered) - SIGNAL_HANDLER - if(!isliving(entered)) return var/mob/living/living_entered = entered diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm index 478d5000721..b5c847a78ab 100644 --- a/code/modules/assembly/infrared.dm +++ b/code/modules/assembly/infrared.dm @@ -2,189 +2,298 @@ name = "infrared emitter" desc = "Emits a visible or invisible beam and is triggered when the beam is interrupted." icon_state = "infrared" - custom_materials = list(/datum/material/iron=HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass=SMALL_MATERIAL_AMOUNT*5) + base_icon_state = "infrared" + custom_materials = list( + /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/glass = SMALL_MATERIAL_AMOUNT * 5, + ) is_position_sensitive = TRUE drop_sound = 'sound/items/handling/component_drop.ogg' pickup_sound = 'sound/items/handling/component_pickup.ogg' + set_dir_on_move = FALSE + /// Whether the beam is beaming var/on = FALSE + /// Whether the beam is visible var/visible = FALSE - var/maxlength = 8 - var/list/obj/effect/beam/i_beam/beams - var/olddir = 0 - var/turf/listeningTo + /// The length the beam can go + var/max_beam_length = 8 + /// The radius of which people can hear triggers var/hearing_range = 3 + /// Pass flags the beam uses to determine what it can pass through + var/beam_pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE + /// The current active beam datum + VAR_FINAL/datum/beam/active_beam + /// A reference to the turf at the END of our active beam + VAR_FINAL/turf/buffer_turf /obj/item/assembly/infra/Initialize(mapload) . = ..() - beams = list() - START_PROCESSING(SSobj, src) - AddComponent(/datum/component/simple_rotation, post_rotation = CALLBACK(src, PROC_REF(post_rotation))) + AddComponent(/datum/component/simple_rotation) -/obj/item/assembly/infra/proc/post_rotation(mob/user, degrees) - refreshBeam() +/obj/item/assembly/infra/Destroy() + QDEL_NULL(active_beam) + buffer_turf = null + return ..() /obj/item/assembly/infra/AltClick(mob/user) return ..() // This hotkey is BLACKLISTED since it's used by /datum/component/simple_rotation -/obj/item/assembly/infra/Destroy() - STOP_PROCESSING(SSobj, src) - listeningTo = null - QDEL_LIST(beams) - . = ..() - /obj/item/assembly/infra/examine(mob/user) . = ..() - . += span_notice("The infrared trigger is [on?"on":"off"].") + . += span_notice("The infrared trigger is [on ? "on" : "off"].") + +/// Checks if the passed movable can block the beam. +/obj/item/assembly/infra/proc/atom_blocks_beam(atom/movable/beam_atom) + if(isnull(beam_atom)) + return FALSE + if(beam_atom == src || beam_atom == holder) + return FALSE + // Blocks beams from triggering themselves, important to avoid infinite loops + if(istype(beam_atom, /obj/effect/ebeam)) + return FALSE + // Anti-revenant / anti-ghost guard + if(beam_atom.invisibility) + return FALSE + // In general non-dense items should not block beams, but make special cases for things being thrown + if(!beam_atom.density && !beam_atom.throwing) + return FALSE + // The actually important check. Ensures stuff like mobs trip it but stuff like laser projectiles don't + if(beam_atom.pass_flags_self & beam_pass_flags) + return FALSE + if(isitem(beam_atom)) + var/obj/item/beam_item = beam_atom + if(beam_item.item_flags & ABSTRACT) + return FALSE -/obj/item/assembly/infra/activate() - if(!..()) - return FALSE //Cooldown check - on = !on - refreshBeam() - update_appearance() return TRUE -/obj/item/assembly/infra/toggle_secure() - secured = !secured - if(secured) - START_PROCESSING(SSobj, src) - refreshBeam() - else - QDEL_LIST(beams) - STOP_PROCESSING(SSobj, src) - update_appearance() - return secured +/// Checks if the passed turf (or something on it) can block the beam. +/obj/item/assembly/infra/proc/turf_blocks_beam(turf/beam_turf) + if(beam_turf.density) + return TRUE + for(var/atom/movable/blocker as anything in beam_turf) + if(atom_blocks_beam(blocker)) + return TRUE + return FALSE -/obj/item/assembly/infra/update_appearance(updates=ALL) - . = ..() - holder?.update_appearance(updates) +/// Used to refresh the beam in whatever context. +/obj/item/assembly/infra/proc/make_beam() + SHOULD_NOT_SLEEP(TRUE) -/obj/item/assembly/infra/update_overlays() - . = ..() - attached_overlays = list() - if(!on) + if(!isnull(buffer_turf)) + UnregisterSignal(buffer_turf, list(COMSIG_ATOM_EXITED, COMSIG_TURF_CHANGE)) + buffer_turf = null + + QDEL_NULL(active_beam) + if(!on || !secured) return - . += "infrared_on" - attached_overlays += "infrared_on" - if(visible && secured) - . += "infrared_visible" - attached_overlays += "infrared_visible" -/obj/item/assembly/infra/dropped() - . = ..() - if(holder) - holder_movement() //sync the dir of the device as well if it's contained in a TTV or an assembly holder - else - INVOKE_ASYNC(src, PROC_REF(refreshBeam)) + var/atom/start_loc = holder || src + var/turf/start_turf = start_loc.loc + if(!istype(start_turf)) + return + // One extra turf is added to max length to get an extra buffer + var/list/turf/potential_turfs = get_line(start_turf, get_ranged_target_turf(start_turf, dir, max_beam_length + 1)) + if(!length(potential_turfs)) + return -/obj/item/assembly/infra/process() - if(!on || !secured) - refreshBeam() + var/list/turf/final_turfs = list() + for(var/turf/target_turf as anything in potential_turfs) + if(target_turf != start_turf && turf_blocks_beam(target_turf)) + break + final_turfs += target_turf + + if(!length(final_turfs)) return -/obj/item/assembly/infra/proc/refreshBeam() - QDEL_LIST(beams) - if(throwing || !on || !secured) + var/turf/last_turf = final_turfs[length(final_turfs)] + buffer_turf = get_step(last_turf, dir) + + var/beam_target_x = pixel_x + var/beam_target_y = pixel_y + // The beam by default will go to middle of turf (because items are in the middle of turfs) + // So we need to offset it + if(dir & NORTH) + beam_target_y += 16 + else if(dir & SOUTH) + beam_target_y -= 16 + if(dir & WEST) + beam_target_x -= 16 + else if(dir & EAST) + beam_target_x += 16 + + active_beam = start_loc.Beam( + BeamTarget = last_turf, + beam_type = /obj/effect/ebeam/reacting/infrared, + icon = 'icons/effects/beam.dmi', + icon_state = "1-full", + beam_color = COLOR_RED, + emissive = TRUE, + override_target_pixel_x = beam_target_x, + override_target_pixel_y = beam_target_y, + ) + RegisterSignal(active_beam, COMSIG_BEAM_ENTERED, PROC_REF(beam_entered)) + RegisterSignal(active_beam, COMSIG_BEAM_TURFS_CHANGED, PROC_REF(beam_turfs_changed)) + update_visible() + // Buffer can be null (if we're at map edge for an example) but this fine + if(!isnull(buffer_turf)) + // We need to check the state of the turf at the end of the beam, to determine when we need to re-grow (if blocked) + RegisterSignal(buffer_turf, COMSIG_ATOM_EXITED, PROC_REF(buffer_exited)) + RegisterSignal(buffer_turf, COMSIG_TURF_CHANGE, PROC_REF(buffer_changed)) + +/obj/item/assembly/infra/proc/beam_entered(datum/beam/source, obj/effect/ebeam/hit, atom/movable/entered) + SIGNAL_HANDLER + + // First doesn't count + if(hit == active_beam.elements[1]) return - if(holder) - if(holder.master) //incase the sensor is part of an assembly that's contained in another item, such as a single tank bomb - if(!holder.master.IsSpecialAssembly() || !isturf(holder.master.loc)) - return - else if(!isturf(holder.loc)) //else just check where the holder is - return - else if(!isturf(loc)) //or just where the fuck we are in general + if(!atom_blocks_beam(entered)) return - var/turf/T = get_turf(src) - var/_dir = dir - var/turf/_T = get_step(T, _dir) - if(_T) - for(var/i in 1 to maxlength) - var/obj/effect/beam/i_beam/I = new(T) - if(istype(holder, /obj/item/assembly_holder)) - I.icon_state = "[initial(I.icon_state)]_l" //Sync the offset of the beam with the position of the sensor. - else if(istype(holder, /obj/item/transfer_valve)) - I.icon_state = "[initial(I.icon_state)]_ttv" - I.set_density(TRUE) - if(!I.Move(_T)) - qdel(I) - switchListener(_T) - break - I.set_density(FALSE) - beams += I - I.master = src - I.setDir(_dir) - if(!visible) - I.SetInvisibility(INVISIBILITY_ABSTRACT) - T = _T - _T = get_step(_T, _dir) - CHECK_TICK -/obj/item/assembly/infra/on_detach() + beam_trigger(hit, entered) + +/obj/item/assembly/infra/proc/beam_turfs_changed(datum/beam/source, list/datum/callback/post_change_callbacks) + SIGNAL_HANDLER + // If the turfs changed it's possible something is now blocking it, remake when done + post_change_callbacks += CALLBACK(src, PROC_REF(make_beam)) + +/obj/item/assembly/infra/proc/buffer_exited(turf/source, atom/movable/exited, ...) + SIGNAL_HANDLER + + if(!atom_blocks_beam(exited)) + return + + make_beam() + +/obj/item/assembly/infra/proc/buffer_changed(turf/source, path, list/new_baseturfs, flags, list/datum/callback/post_change_callbacks) + SIGNAL_HANDLER + + post_change_callbacks += CALLBACK(src, PROC_REF(make_beam)) + +/obj/item/assembly/infra/proc/beam_trigger(obj/effect/ebeam/hit, atom/movable/entered) + make_beam() + if(!COOLDOWN_FINISHED(src, next_activate)) + return + + pulse() + audible_message( + message = span_infoplain("[icon2html(src, hearers(holder || src))] *beep* *beep* *beep*"), + hearing_distance = hearing_range, + ) + playsound(src, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE, extrarange = hearing_range - SOUND_RANGE + 1, falloff_distance = hearing_range) + COOLDOWN_START(src, next_activate, 3 SECONDS) + +/obj/item/assembly/infra/activate() . = ..() if(!.) return - refreshBeam() -/obj/item/assembly/infra/attack_hand(mob/user, list/modifiers) + toggle_on() + +/obj/item/assembly/infra/toggle_secure() . = ..() - refreshBeam() + make_beam() + +/// Toggles the beam on or off. +/obj/item/assembly/infra/proc/toggle_on() + on = !on + make_beam() + update_appearance() + +/// Toggles the visibility of the beam. +/obj/item/assembly/infra/proc/toggle_visible() + visible = !visible + update_visible() + update_appearance() -/obj/item/assembly/infra/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - var/t = dir +/// Updates the visibility of the beam (if active). +/obj/item/assembly/infra/proc/update_visible() + if(visible) + for(var/obj/effect/ebeam/beam as anything in active_beam?.elements) + beam.RemoveInvisibility(REF(src)) + else + for(var/obj/effect/ebeam/beam as anything in active_beam?.elements) + beam.SetInvisibility(INVISIBILITY_ABSTRACT, REF(src)) + +/obj/item/assembly/infra/vv_edit_var(var_name, var_value) . = ..() - setDir(t) + if(!.) + return + switch(var_name) + if(NAMEOF(src, visible)) + update_visible() + update_appearance() + + if(NAMEOF(src, on), NAMEOF(src, max_beam_length), NAMEOF(src, beam_pass_flags)) + make_beam() + update_appearance() -/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) +/obj/item/assembly/infra/update_appearance(updates) . = ..() - olddir = dir + holder?.update_appearance(updates) -/obj/item/assembly/infra/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) +/obj/item/assembly/infra/update_overlays() . = ..() - if(!olddir) - return - setDir(olddir) - olddir = null + attached_overlays = list() + if(on) + attached_overlays += "[base_icon_state]_on" -/obj/item/assembly/infra/proc/trigger_beam(atom/movable/AM, turf/location) - refreshBeam() - switchListener(location) - if(!secured || !on || next_activate > world.time) - return FALSE - pulse() - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) - for(var/mob/hearing_mob in get_hearers_in_view(hearing_range, src)) - hearing_mob.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - next_activate = world.time + 30 + . += attached_overlays -/obj/item/assembly/infra/proc/switchListener(turf/newloc) - if(listeningTo == newloc) - return - if(listeningTo) - UnregisterSignal(listeningTo, COMSIG_ATOM_EXITED) - RegisterSignal(newloc, COMSIG_ATOM_EXITED, PROC_REF(check_exit)) - listeningTo = newloc +/obj/item/assembly/infra/dropped() + . = ..() + if(holder) + holder_movement() //sync the dir of the device as well if it's contained in a TTV or an assembly holder + else + make_beam() -/obj/item/assembly/infra/proc/check_exit(datum/source, atom/movable/gone, direction) - SIGNAL_HANDLER +/obj/item/assembly/infra/on_attach() + . = ..() + make_beam() + holder.set_dir_on_move = set_dir_on_move - if(QDELETED(src)) - return - if(src == gone || istype(gone, /obj/effect/beam/i_beam)) +/obj/item/assembly/infra/on_detach() + holder.set_dir_on_move = initial(holder.set_dir_on_move) + . = ..() + if(!.) return - if(isitem(gone)) - var/obj/item/I = gone - if (I.item_flags & ABSTRACT) - return - INVOKE_ASYNC(src, PROC_REF(refreshBeam)) + make_beam() -/obj/item/assembly/infra/setDir() +/obj/item/assembly/infra/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + if(loc == old_loc) + return + make_beam() + if(!visible || forced || !movement_dir || !Adjacent(old_loc)) + return + // Because the new beam is made in the new loc, it "jumps" from one turf to another + // We can do an animate to pretend we're gliding between turfs rather than making a whole new beam + var/x_move = 0 + var/y_move = 0 + if(movement_dir & NORTH) + y_move = -32 + else if(movement_dir & SOUTH) + y_move = 32 + if(movement_dir & WEST) + x_move = 32 + else if(movement_dir & EAST) + x_move = -32 + + var/fake_glide_time = round(world.icon_size / glide_size * world.tick_lag, world.tick_lag) + for(var/obj/effect/ebeam/beam as anything in active_beam?.elements) + var/matrix/base_transform = matrix(beam.transform) + beam.transform = beam.transform.Translate(x_move, y_move) + animate(beam, transform = base_transform, time = fake_glide_time) + +/obj/item/assembly/infra/setDir(newdir) + var/prev_dir = dir . = ..() - refreshBeam() + if(dir == prev_dir) + return + make_beam() /obj/item/assembly/infra/ui_status(mob/user, datum/ui_state/state) - if(is_secured(user)) - return ..() - return UI_CLOSE + return is_secured(user) ? ..() : UI_CLOSE /obj/item/assembly/infra/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) @@ -201,44 +310,17 @@ /obj/item/assembly/infra/ui_act(action, params) . = ..() if(.) - return + return . switch(action) if("power") - on = !on - . = TRUE + toggle_on() + return TRUE if("visibility") - visible = !visible - . = TRUE - - update_appearance() - refreshBeam() + toggle_visible() + return TRUE -/***************************IBeam*********************************/ - -/obj/effect/beam/i_beam +// Beam subtype for the infrared emitter +/obj/effect/ebeam/reacting/infrared name = "infrared beam" - icon = 'icons/obj/weapons/guns/projectiles.dmi' - icon_state = "ibeam" - anchored = TRUE - density = FALSE - pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE - pass_flags_self = LETPASSTHROW - var/obj/item/assembly/infra/master - -/obj/effect/beam/i_beam/Initialize(mapload) - . = ..() - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = PROC_REF(on_entered), - ) - AddElement(/datum/element/connect_loc, loc_connections) - -/obj/effect/beam/i_beam/proc/on_entered(datum/source, atom/movable/AM as mob|obj) - SIGNAL_HANDLER - if(istype(AM, /obj/effect/beam)) - return - if (isitem(AM)) - var/obj/item/I = AM - if (I.item_flags & ABSTRACT) - return - INVOKE_ASYNC(master, TYPE_PROC_REF(/obj/item/assembly/infra, trigger_beam), AM, get_turf(src)) + alpha = 175