Files
SyncIt21andnevimer f640a0e972 Enhances algorithm for finding an atom mount (#94076)
## About The Pull Request
Depends on #94064 for the unit test but offers a better method for
finding an atom to mount on
- Finding a mount now takes into consideration the objects pixel x & y
offsets meaning diagonal mounting is now supported. Gives great
flexibility for mappers
- If you don't want to use pixel offsets but default back to using the
objects direction that behaviour is still preserved. Useful if your
object uses directional icon states(lights & cameras for now) AND don't
use offsets
- If no direction could be specified then as the last resort it defaults
back to the objects local turf for mounting

## Changelog
🆑
fix: all mounted objects on tables, fences, windows & walls should fall
of correctly when the atom it is placed on is destroyed
fix: security telescreen now falls off when their mounted wall is
destroyed
fix: defib wall mount falls off when their mounted wall is destroyed
fix: floor lights are mounted to the ground/catwalk/tram floor they are
sitting on meaning destroying it will destroy the light
fix: wall mounted plaques now fall off when their mounted wall is
destroyed
/🆑
2025-12-12 12:23:22 -05:00

229 lines
7.0 KiB
Plaintext

// It is a gizmo that flashes a small area
/obj/machinery/flasher
name = "mounted flash"
desc = "A wall-mounted flashbulb device."
icon = 'icons/obj/wallmounts.dmi'
icon_state = "mflash1"
base_icon_state = "mflash"
max_integrity = 250
integrity_failure = 0.4
damage_deflection = 10
///The contained flash. Mostly just handles the bulb burning out & needing placement.
var/obj/item/assembly/flash/handheld/bulb
var/id = null
/// How far this flash reaches. Affects both proximity distance and the actual stun effect.
var/flash_range = 2 //this is roughly the size of a brig cell.
/// How strong Paralyze()'d targets are when flashed.
var/strength = 5 SECONDS
COOLDOWN_DECLARE(flash_cooldown)
/// Duration of time between flashes.
var/flash_cooldown_duration = 15 SECONDS
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/flasher, 26)
/obj/machinery/flasher/Initialize(mapload)
. = ..()
if(mapload)
bulb = new(src)
find_and_mount_on_atom()
/obj/machinery/flasher/vv_edit_var(vname, vval)
. = ..()
if(vname == NAMEOF(src, flash_cooldown_duration) && (COOLDOWN_TIMELEFT(src, flash_cooldown) > flash_cooldown_duration))
COOLDOWN_START(src, flash_cooldown, flash_cooldown_duration)
/obj/machinery/flasher/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
id = "[port.shuttle_id]_[id]"
/obj/machinery/flasher/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
if(istype(arrived, /obj/item/assembly/flash/handheld))
bulb = arrived
return ..()
/obj/machinery/flasher/Exited(atom/movable/gone, direction)
if(gone == bulb)
bulb = null
return ..()
/obj/machinery/flasher/Destroy()
QDEL_NULL(bulb)
return ..()
/obj/machinery/flasher/powered()
if(!anchored || !bulb)
return FALSE
return ..()
/obj/machinery/flasher/update_icon_state()
icon_state = "[base_icon_state]1[(bulb?.burnt_out || !powered()) ? "-p" : null]"
return ..()
//Don't want to render prison breaks impossible
/obj/machinery/flasher/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers)
add_fingerprint(user)
if (attacking_item.tool_behaviour == TOOL_WIRECUTTER)
if (bulb)
user.visible_message(span_notice("[user] begins to disconnect [src]'s flashbulb."), span_notice("You begin to disconnect [src]'s flashbulb..."))
if(attacking_item.use_tool(src, user, 30, volume=50) && bulb)
user.visible_message(span_notice("[user] disconnects [src]'s flashbulb!"), span_notice("You disconnect [src]'s flashbulb."))
bulb.forceMove(loc)
power_change()
else if (istype(attacking_item, /obj/item/assembly/flash/handheld))
if (!bulb)
if(!user.transferItemToLoc(attacking_item, src))
return
user.visible_message(span_notice("[user] installs [attacking_item] into [src]."), span_notice("You install [attacking_item] into [src]."))
power_change()
else
to_chat(user, span_warning("A flashbulb is already installed in [src]!"))
else if (attacking_item.tool_behaviour == TOOL_WRENCH)
if(!bulb)
to_chat(user, span_notice("You start unsecuring the flasher frame..."))
if(attacking_item.use_tool(src, user, 40, volume=50))
to_chat(user, span_notice("You unsecure the flasher frame."))
deconstruct(TRUE)
else
to_chat(user, span_warning("Remove a flashbulb from [src] first!"))
else
return ..()
//Let the AI trigger them directly.
/obj/machinery/flasher/attack_ai()
if (anchored)
return flash()
/obj/machinery/flasher/proc/flash()
if (!powered() || !bulb)
return
if (bulb.burnt_out || !COOLDOWN_FINISHED(src, flash_cooldown))
return
if(!bulb.flash_recharge(30)) //Bulb can burn out if it's used too often too fast
power_change()
return
playsound(src, 'sound/items/weapons/flash.ogg', 100, TRUE)
flick("[base_icon_state]_flash", src)
flash_lighting_fx()
COOLDOWN_START(src, flash_cooldown, flash_cooldown_duration)
use_energy(1 KILO JOULES)
var/flashed = FALSE
for(var/mob/living/living_mob in viewers(src, null))
if (get_dist(src, living_mob) > flash_range)
continue
if(bulb.flash_mob(living_mob, confusion_duration = strength * 1.5, extra_log = "by [src]"))
living_mob.Paralyze(strength)
flashed = TRUE
if(flashed)
bulb.times_used++
return TRUE
/obj/machinery/flasher/emp_act(severity)
. = ..()
if(!(machine_stat & (BROKEN|NOPOWER)) && !(. & EMP_PROTECT_SELF))
if(bulb && prob(75/severity))
flash()
bulb.burn_out()
power_change()
/obj/machinery/flasher/atom_break(damage_flag)
. = ..()
if(. && bulb)
bulb.burn_out()
power_change()
/obj/machinery/flasher/on_deconstruction(disassembled)
if(bulb)
bulb.forceMove(loc)
if(disassembled)
var/obj/item/wallframe/flasher/flasher_obj = new(get_turf(src))
transfer_fingerprints_to(flasher_obj)
flasher_obj.id = id
playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE)
else
new /obj/item/stack/sheet/iron (loc, 2)
/obj/machinery/flasher/portable //Portable version of the flasher. Only flashes when anchored
name = "portable flasher"
desc = "A portable flashing device. Wrench to activate and deactivate. Cannot detect slow movements."
icon = 'icons/obj/machines/sec.dmi'
icon_state = "pflash1-p"
base_icon_state = "pflash"
strength = 4 SECONDS
anchored = FALSE
density = TRUE
///Proximity monitor associated with this atom, needed for proximity checks.
var/datum/proximity_monitor/proximity_monitor
/obj/machinery/flasher/portable/Initialize(mapload)
. = ..()
proximity_monitor = new(src, 0)
/obj/machinery/flasher/portable/find_and_mount_on_atom(mark_for_late_init, late_init)
return //its meant to be carried and mobile
/obj/machinery/flasher/portable/HasProximity(atom/movable/proximity_check_mob)
if(!COOLDOWN_FINISHED(src, flash_cooldown))
return
if(!isliving(proximity_check_mob))
return
var/mob/living/proximity_living = proximity_check_mob
if (proximity_living.move_intent != MOVE_INTENT_WALK && anchored)
flash()
/obj/machinery/flasher/portable/vv_edit_var(vname, vval)
. = ..()
if(vname == NAMEOF(src, flash_range))
proximity_monitor?.set_range(flash_range)
/obj/machinery/flasher/portable/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers)
if (attacking_item.tool_behaviour == TOOL_WRENCH)
attacking_item.play_tool_sound(src, 100)
if (!anchored && !isinspace())
to_chat(user, span_notice("[src] is now secured."))
add_overlay("[base_icon_state]-s")
set_anchored(TRUE)
power_change()
proximity_monitor.set_range(flash_range)
else
to_chat(user, span_notice("[src] can now be moved."))
cut_overlays()
set_anchored(FALSE)
power_change()
proximity_monitor.set_range(0)
else
return ..()
/obj/item/wallframe/flasher
name = "mounted flash frame"
desc = "Used for building wall-mounted flashers."
icon = 'icons/obj/wallmounts.dmi'
icon_state = "mflash_frame"
result_path = /obj/machinery/flasher
var/id = null
pixel_shift = 28
/obj/item/wallframe/flasher/examine(mob/user)
. = ..()
. += span_notice("Its channel ID is '[id]'.")
/obj/item/wallframe/flasher/after_attach(obj/attached_to)
..()
var/obj/machinery/flasher/flasher_obj = attached_to
flasher_obj.id = id