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