Files
QuiteLiterallyAnything c7e7257d9a Fixes floor igniters by refactoring wall button control assemblies (#96516)
## About The Pull Request
Constructable atmospheric floor igniters (as introduced in PR #75722 by
@SyncIt21) seem to have been broken last year with PR #92494. All
button-bound controllers (igniter, airlock, crematorium, curtain, et
cetera) are currently subtypes of blast door controllers. Accordingly,
the `multitool_act()` proc from the latter PR both broke and erroneously
introduced blast door ID-setting functionality to every subtype.

<details><summary>Faulty <code>examine()</code> for example:</summary>

<img width="415" height="121" alt="Ignition_Controller_Desc"
src="https://github.com/user-attachments/assets/ce7b038a-035a-45b1-85f9-1c78ad9b8b63"
/>

</details>

This PR fixes floor igniters by refactoring blast door controllers into
their own subtype of a generic parent type. A few minor grammar
corrections have also been included.
## Why It's Good For The Game
There are a lot of different wall buttons in-game that do a lot of
different things. Many of those things are far removed from blast doors.
Having blast door controllers be the parent type for all other
controllers is then likely to cause future problems. Nothing more
exemplifies this than the problem fixed here.
## Changelog
🆑
fix: Fixed floor igniters and ensured that they are linkable to
constructed buttons.
fix: All wall-mounted button electronics are no longer arbitrarily
alterable with blast doors.
refactor: Refactored blast doors controllers into their own subtype of a
new abstract controller type.
/🆑
2026-06-20 16:45:56 -06:00

256 lines
8.7 KiB
Plaintext

/obj/item/assembly/control
name = "controller assembly"
desc = "An assembly that controls something. It's so vaguely designed that it probably shouldn't exist."
icon_state = "control"
/// The ID of the electronics to match to the ID of the machine being used.
var/id = -1
/// Cooldown of the controller. Updates when pressed (activate())
var/cooldown = FALSE
/// Should we toggle open/close of doors based on their current state
/// Also used for curtains
var/sync_doors = TRUE
/// If this controller's ID should be adjustable by players through multitools.
var/generically_adjustable = FALSE
/// If this controller's ID should be copyable by hitting with an identical controller.
var/copyable = FALSE
/obj/item/assembly/control/Initialize(mapload)
. = ..()
register_context()
/obj/item/assembly/control/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = NONE
if(istype(held_item, type) && copyable)
context[SCREENTIP_CONTEXT_LMB] = "Copy ID"
return CONTEXTUAL_SCREENTIP_SET
/obj/item/assembly/control/examine(mob/user)
. = ..()
if(generically_adjustable)
. += span_notice("You can use a [EXAMINE_HINT("multitool")] to adjust its ID.")
if(copyable)
. += span_notice("You can interact with another controller to copy its ID.")
if(!id)
return
if(id != -1)
. += span_notice("Its channel ID is '[id]'.")
/obj/item/assembly/control/multitool_act(mob/living/user)
if(!generically_adjustable)
return
var/change_id = tgui_input_number(user, "Set the [name]'s ID", "Controller ID", id, 100)
if(!change_id || QDELETED(user) || QDELETED(src) || !usr.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return
id = change_id
to_chat(user, span_notice("You change the ID to [id]."))
/obj/item/assembly/control/interact_with_atom(obj/item/assembly/control/interacting_with, mob/living/user, list/modifiers)
. = NONE
if(!copyable)
return
if(istype(interacting_with))
id = interacting_with.id
balloon_alert(user, "id changed")
return ITEM_INTERACT_SUCCESS
/obj/item/assembly/control/blast_door
name = "blast door controller"
desc = "A small electronic device able to control blast doors or shutters remotely."
copyable = TRUE
/obj/item/assembly/control/blast_door/examine(mob/user)
. = ..()
if(id && id == -1)
. += span_notice("Interact with a blast door or shutter to generate a new ID")
/obj/item/assembly/control/blast_door/multitool_act(mob/living/user)
var/list/door_ids = list()
var/list/display_ids = list("UNIQUE")
for(var/obj/machinery/door/poddoor/M as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor))
if(!M.id || (M.id in door_ids))
continue
door_ids += "[M.id]"
if(M.owner?.resolve() != user)
continue
var/area/door_area = get_area(M)
display_ids += "[door_area.name]([M.id])"
var/change_id = tgui_input_list(user, "Set Controller ID", "Controller ID", display_ids)
if(!change_id || QDELETED(user) || QDELETED(src) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return
if(change_id == "UNIQUE")
id = -1
else
var/start = findtext(change_id, "(") + 1
var/end = length(change_id) - 1
if(start == end)
id = "[change_id[start]]"
else
id = copytext(change_id, start, end)
balloon_alert(user, "id changed")
if(id != -1)
to_chat(user, span_notice("You change the ID to [id]."))
else
to_chat(user, span_notice("You now must interact with a pod door to generate a unique ID."))
/obj/item/assembly/control/blast_door/activate()
var/openclose
if(cooldown)
return
cooldown = TRUE
for(var/obj/machinery/door/poddoor/M as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor))
if(M.id == src.id)
if(openclose == null || !sync_doors)
openclose = M.density
INVOKE_ASYNC(M, openclose ? TYPE_PROC_REF(/obj/machinery/door/poddoor, open) : TYPE_PROC_REF(/obj/machinery/door/poddoor, close))
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 1 SECONDS)
/obj/item/assembly/control/curtain
name = "curtain controller"
desc = "A small electronic device able to control a mechanical curtain remotely."
/obj/item/assembly/control/curtain/activate()
var/openclose
if(cooldown)
return
cooldown = TRUE
for(var/obj/structure/curtain/cloth/fancy/mechanical/M in GLOB.curtains)
if(M.id == src.id)
if(openclose == null || !sync_doors)
openclose = M.density
INVOKE_ASYNC(M, openclose ? TYPE_PROC_REF(/obj/structure/curtain/cloth/fancy/mechanical, open) : TYPE_PROC_REF(/obj/structure/curtain/cloth/fancy/mechanical, close))
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 0.5 SECONDS)
/obj/item/assembly/control/airlock
name = "airlock controller"
desc = "A small electronic device able to control an airlock remotely."
id = "badmin" // Set it to null for MEGAFUN.
var/specialfunctions = OPEN
/*
Bitflag, 1= open (OPEN)
2= idscan (IDSCAN)
4= bolts (BOLTS)
8= shock (SHOCK)
16= door safties (SAFE)
*/
/obj/item/assembly/control/airlock/activate()
if(cooldown)
return
cooldown = TRUE
var/doors_need_closing = FALSE
var/list/obj/machinery/door/airlock/open_or_close = list()
for(var/obj/machinery/door/airlock/D as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/airlock))
if(D.id_tag == src.id)
if(specialfunctions & OPEN)
open_or_close += D
if(!D.density)
doors_need_closing = TRUE
if(specialfunctions & IDSCAN)
D.aiDisabledIdScanner = !D.aiDisabledIdScanner
if(specialfunctions & BOLTS)
if(!D.wires.is_cut(WIRE_BOLTS) && D.hasPower())
if(D.locked)
D.unlock()
else
D.lock()
D.update_appearance()
if(specialfunctions & SHOCK)
if(D.secondsElectrified)
D.set_electrified(MACHINE_ELECTRIFIED_PERMANENT, usr)
else
D.set_electrified(MACHINE_NOT_ELECTRIFIED, usr)
if(specialfunctions & SAFE)
D.safe = !D.safe
for(var/D in open_or_close)
INVOKE_ASYNC(D, doors_need_closing ? TYPE_PROC_REF(/obj/machinery/door/airlock, close) : TYPE_PROC_REF(/obj/machinery/door/airlock, open))
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 1 SECONDS)
/obj/item/assembly/control/massdriver
name = "mass driver controller"
desc = "A small electronic device able to control a mass driver."
/obj/item/assembly/control/massdriver/activate()
if(cooldown)
return
cooldown = TRUE
for(var/obj/machinery/door/poddoor/M as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor))
if (M.id == src.id)
INVOKE_ASYNC(M, TYPE_PROC_REF(/obj/machinery/door/poddoor, open))
addtimer(CALLBACK(src, PROC_REF(activate_stage2)), 1 SECONDS)
/obj/item/assembly/control/massdriver/proc/activate_stage2()
for(var/obj/machinery/mass_driver/M as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/mass_driver))
if(M.id == src.id)
M.drive()
addtimer(CALLBACK(src, PROC_REF(activate_stage3)), 6 SECONDS)
/obj/item/assembly/control/massdriver/proc/activate_stage3()
for(var/obj/machinery/door/poddoor/M as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor))
if (M.id == src.id)
INVOKE_ASYNC(M, TYPE_PROC_REF(/obj/machinery/door/poddoor, close))
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 1 SECONDS)
/obj/item/assembly/control/igniter
name = "ignition controller"
desc = "A remote controller for a floor igniter or wall sparker."
generically_adjustable = TRUE
copyable = TRUE
/obj/item/assembly/control/igniter/activate()
if(cooldown)
return
cooldown = TRUE
for(var/obj/machinery/sparker/M as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/sparker))
if (M.id == src.id)
INVOKE_ASYNC(M, TYPE_PROC_REF(/obj/machinery/sparker, ignite))
for(var/obj/machinery/igniter/M as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/igniter))
if(M.id == src.id)
INVOKE_ASYNC(M, TYPE_PROC_REF(/obj/machinery/igniter, toggle))
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 3 SECONDS)
/obj/item/assembly/control/flasher
name = "flasher controller"
desc = "A remote controller for a mounted flasher."
/obj/item/assembly/control/flasher/activate()
if(cooldown)
return
cooldown = TRUE
for(var/obj/machinery/flasher/M as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/flasher))
if(M.id == src.id)
INVOKE_ASYNC(M, TYPE_PROC_REF(/obj/machinery/flasher, flash))
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 5 SECONDS)
/obj/item/assembly/control/crematorium
name = "crematorium controller"
desc = "An evil-looking remote controller for a crematorium."
/obj/item/assembly/control/crematorium/activate()
if(cooldown)
return
cooldown = TRUE
for (var/obj/structure/bodycontainer/crematorium/C in GLOB.crematoriums)
if (C.id == id)
C.cremate(usr)
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 5 SECONDS)