Security camera attackby to item_interaction (#96361)

## About The Pull Request

Rewrite the big `attackby` for security cameras to use
`item_interaction` and distinct `_act` procs, also turns the big
clusterfuck for pressing notes up to cameras into a slightly less
clusterfuck proc

## Why It's Good For The Game

Clean code

## Changelog
🆑
refactor: security camera construction/item interaction has been
rewritten, report any weirdness
/🆑

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
This commit is contained in:
Roxy
2026-06-11 03:28:05 +02:00
committed by GitHub
co-authored by MrMelbert
parent c433d10e13
commit 2b0da7a1ce
+159 -165
View File
@@ -71,177 +71,171 @@
return ..()
/obj/machinery/camera/wrench_act(mob/user, obj/item/tool)
if(camera_construction_state == CAMERA_STATE_WRENCHED)
tool.play_tool_sound(src)
to_chat(user, span_notice("You detach [src] from its place."))
deconstruct(TRUE)
return ITEM_INTERACT_SUCCESS
return ..()
if(camera_construction_state != CAMERA_STATE_WRENCHED)
return NONE
tool.play_tool_sound(src)
to_chat(user, span_notice("You detach [src] from its place."))
deconstruct(TRUE)
return ITEM_INTERACT_SUCCESS
/obj/machinery/camera/crowbar_act(mob/living/user, obj/item/tool)
if(camera_construction_state == CAMERA_STATE_FINISHED)
if(!panel_open)
return ITEM_INTERACT_BLOCKING
var/list/droppable_parts = list()
if(xray_module)
droppable_parts += xray_module
if(emp_module)
droppable_parts += emp_module
if(proximity_monitor)
droppable_parts += proximity_monitor
if(!length(droppable_parts))
return ITEM_INTERACT_BLOCKING
var/obj/item/choice = tgui_input_list(user, "Select a part to remove", "Part Removal", sort_names(droppable_parts))
if(isnull(choice))
return ITEM_INTERACT_BLOCKING
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return ITEM_INTERACT_BLOCKING
to_chat(user, span_notice("You remove [choice] from [src]."))
if(choice == xray_module)
drop_upgrade(xray_module)
removeXRay()
if(choice == emp_module)
drop_upgrade(emp_module)
removeEmpProof()
if(choice == proximity_monitor)
drop_upgrade(proximity_monitor)
removeMotion()
tool.play_tool_sound(src)
return ITEM_INTERACT_SUCCESS
return ..()
if(camera_construction_state != CAMERA_STATE_FINISHED || !panel_open)
return NONE
var/list/droppable_parts = list()
if(xray_module)
droppable_parts += xray_module
if(emp_module)
droppable_parts += emp_module
if(proximity_monitor)
droppable_parts += proximity_monitor
if(!length(droppable_parts))
return ITEM_INTERACT_BLOCKING
var/obj/item/choice = tgui_input_list(user, "Select a part to remove", "Part Removal", sort_names(droppable_parts))
if(isnull(choice))
return ITEM_INTERACT_BLOCKING
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
return ITEM_INTERACT_BLOCKING
to_chat(user, span_notice("You remove [choice] from [src]."))
if(choice == xray_module)
drop_upgrade(xray_module)
removeXRay()
if(choice == emp_module)
drop_upgrade(emp_module)
removeEmpProof()
if(choice == proximity_monitor)
drop_upgrade(proximity_monitor)
removeMotion()
tool.play_tool_sound(src)
return ITEM_INTERACT_SUCCESS
/obj/machinery/camera/multitool_act(mob/living/user, obj/item/tool)
if(camera_construction_state == CAMERA_STATE_FINISHED)
if(!panel_open)
return ITEM_INTERACT_BLOCKING
setViewRange((view_range == initial(view_range)) ? short_range : initial(view_range))
to_chat(user, span_notice("You [(view_range == initial(view_range)) ? "restore" : "mess up"] the camera's focus."))
return ITEM_INTERACT_SUCCESS
return ..()
if(camera_construction_state != CAMERA_STATE_FINISHED || !panel_open)
return NONE
setViewRange((view_range == initial(view_range)) ? short_range : initial(view_range))
to_chat(user, span_notice("You [(view_range == initial(view_range)) ? "restore" : "mess up"] the camera's focus."))
return ITEM_INTERACT_SUCCESS
/obj/machinery/camera/attackby(obj/item/attacking_item, mob/living/user, list/modifiers, list/attack_modifiers)
if(camera_construction_state != CAMERA_STATE_FINISHED || panel_open)
if(attacking_item.tool_behaviour == TOOL_ANALYZER)
if(!isXRay(TRUE)) //don't reveal it was already upgraded if was done via MALF AI Upgrade Camera Network ability
if(!user.temporarilyRemoveItemFromInventory(attacking_item, newloc = src))
return
upgradeXRay(FALSE, TRUE)
to_chat(user, span_notice("You attach [attacking_item] into [name]'s inner circuits."))
qdel(attacking_item)
/obj/machinery/camera/proc/gas_analyzer_act(mob/living/user, obj/item/tool)
if(camera_construction_state == CAMERA_STATE_FINISHED && !panel_open)
return NONE
if(isXRay(TRUE))
to_chat(user, span_warning("[src] already has that upgrade!"))
return ITEM_INTERACT_BLOCKING
if(!user.temporarilyRemoveItemFromInventory(tool, newloc = src))
return ITEM_INTERACT_BLOCKING
upgradeXRay(FALSE, TRUE)
to_chat(user, span_notice("You attach [tool] into [src]'s inner circuits."))
qdel(tool)
return ITEM_INTERACT_SUCCESS
/obj/machinery/camera/proc/plasma_act(mob/living/user, obj/item/tool)
if(camera_construction_state == CAMERA_STATE_FINISHED && !panel_open)
return NONE
if(isEmpProof(TRUE))
to_chat(user, span_warning("[src] already has that upgrade!"))
return ITEM_INTERACT_BLOCKING
if(!tool.use_tool(src, user, 0, amount = 1))
return ITEM_INTERACT_BLOCKING
upgradeEmpProof(FALSE, TRUE)
to_chat(user, span_notice("You attach [tool] into [src]'s inner circuits."))
return ITEM_INTERACT_SUCCESS
/obj/machinery/camera/proc/prox_act(mob/living/user, obj/item/tool)
if(camera_construction_state == CAMERA_STATE_FINISHED && !panel_open)
return NONE
if(isMotion())
to_chat(user, span_warning("[src] already has that upgrade!"))
return ITEM_INTERACT_BLOCKING
if(!user.temporarilyRemoveItemFromInventory(tool, newloc = src))
return ITEM_INTERACT_BLOCKING
upgradeMotion()
to_chat(user, span_notice("You attach [tool] into [src]'s inner circuits."))
qdel(tool)
return ITEM_INTERACT_SUCCESS
/obj/machinery/camera/proc/cable_act(mob/living/user, obj/item/tool)
if(camera_construction_state != CAMERA_STATE_WELDED)
return NONE
if(!astype(tool, /obj/item/stack/cable_coil)?.use(2))
to_chat(user, span_warning("You need two lengths of cable to wire [src]!"))
return ITEM_INTERACT_BLOCKING
to_chat(user, span_notice("You add wires to [src]."))
camera_construction_state = CAMERA_STATE_WIRED
return ITEM_INTERACT_SUCCESS
/obj/machinery/camera/proc/computer_act(mob/living/user, obj/item/tool)
if(camera_construction_state != CAMERA_STATE_FINISHED)
return NONE
var/obj/item/modular_computer/computer = tool
var/note_name = sanitize(computer.name)
var/datum/computer_file/program/notepad/notepad_app = locate() in computer.stored_files
if(!notepad_app)
return ITEM_INTERACT_BLOCKING
var/note_text = sanitize(notepad_app.written_note)
if(!note_text)
return ITEM_INTERACT_BLOCKING
display_note(user, note_name, note_text, TRUE)
return ITEM_INTERACT_SUCCESS
/obj/machinery/camera/proc/paper_act(mob/living/user, obj/item/tool)
if(camera_construction_state != CAMERA_STATE_FINISHED)
return NONE
var/obj/item/paper/paper = tool
last_shown_paper = paper.copy(paper.type, null)
var/note_name = sanitize(last_shown_paper.name)
last_shown_paper.camera_holder = WEAKREF(src)
display_note(user, note_name, null, FALSE)
return ITEM_INTERACT_SUCCESS
/obj/machinery/camera/proc/display_note(mob/living/user, title, text, is_computer)
to_chat(user, span_notice("You hold up \the [title] up to the camera..."))
user.log_talk(title, LOG_GAME, "Pressed to camera", TRUE)
user.changeNext_move(CLICK_CD_MELEE)
// Iterate over all living mobs and check if anyone is elibile to view the paper.
// This is backwards, but cameras don't store a list of people that are looking through them,
// and we'll have to iterate this list anyway so we can use it to pull out AIs too.
for(var/mob/potential_viewer as anything in GLOB.player_list)
// All AIs view through cameras, so we need to check them regardless.
if(isAI(potential_viewer))
var/mob/living/silicon/ai/ai = potential_viewer
if(ai.control_disabled || ai.stat == DEAD)
continue
ai.log_talk(title, LOG_VICTIM, "Pressed to camera from [key_name(user)]", FALSE)
if(is_computer)
ai.last_tablet_note_seen = "<HTML><HEAD><TITLE>[title]</TITLE></HEAD><BODY><TT>[text]</TT></BODY></HTML>"
else
to_chat(user, span_warning("[src] already has that upgrade!"))
return
else if(istype(attacking_item, /obj/item/stack/sheet/mineral/plasma))
if(!isEmpProof(TRUE)) //don't reveal it was already upgraded if was done via MALF AI Upgrade Camera Network ability
if(attacking_item.use_tool(src, user, 0, amount=1))
upgradeEmpProof(FALSE, TRUE)
to_chat(user, span_notice("You attach [attacking_item] into [name]'s inner circuits."))
log_paper("[key_name(user)] held [last_shown_paper] up to [src], requesting [key_name(ai)] read it.")
var/href_string = is_computer ? "show_tablet_note=1" : "show_paper_note=[REF(last_shown_paper)]"
if(user.name == "Unknown")
to_chat(ai, "[span_name("[user]")] holds <a href='byond://?_src_=usr;[href_string];'>\a [title]</a> up to one of your cameras...")
else
to_chat(user, span_warning("[src] already has that upgrade!"))
return
else if(isprox(attacking_item))
if(!isMotion())
if(!user.temporarilyRemoveItemFromInventory(attacking_item, newloc = src))
return
upgradeMotion()
to_chat(user, span_notice("You attach [attacking_item] into [name]'s inner circuits."))
qdel(attacking_item)
to_chat(ai, "[span_name("<a href='byond://?src=[REF(ai)];track=[html_encode(user.name)]'>[user]</a>")] holds <a href='byond://?_src_=usr;[href_string];'>\a [title]</a> up to one of your cameras...")
// If it's not an AI, eye if the client's eye is set to the camera. I wonder if this even works anymore with tgui camera apps and stuff?
else if(potential_viewer.client?.eye == src)
potential_viewer.log_talk(title, LOG_VICTIM, "Pressed to camera from [key_name(user)]", FALSE)
if(!is_computer)
log_paper("[key_name(user)] held [last_shown_paper] up to [src], and [key_name(potential_viewer)] may read it.")
to_chat(potential_viewer, "[span_name("[user]")] holds <a href='byond://?_src_=usr;show_paper_note=[REF(last_shown_paper)];'>\a [title]</a> up to your camera...")
else
to_chat(user, span_warning("[src] already has that upgrade!"))
return
switch(camera_construction_state)
if(CAMERA_STATE_WELDED)
if(istype(attacking_item, /obj/item/stack/cable_coil))
var/obj/item/stack/cable_coil/attacking_cable = attacking_item
if(attacking_cable.use(2))
to_chat(user, span_notice("You add wires to [src]."))
camera_construction_state = CAMERA_STATE_WIRED
else
to_chat(user, span_warning("You need two lengths of cable to wire a camera!"))
return
if(CAMERA_STATE_FINISHED)
if(istype(attacking_item, /obj/item/modular_computer))
var/itemname = ""
var/info = ""
potential_viewer << browse("<HTML><HEAD><TITLE>[title]</TITLE></HEAD><BODY><TT>[text]</TT></BODY></HTML>", "window=[title]")
var/obj/item/modular_computer/computer = attacking_item
for(var/datum/computer_file/program/notepad/notepad_app in computer.stored_files)
info = notepad_app.written_note
break
if(!info)
return
itemname = computer.name
itemname = sanitize(itemname)
info = sanitize(info)
to_chat(user, span_notice("You hold \the [itemname] up to the camera..."))
user.log_talk(itemname, LOG_GAME, log_globally=TRUE, tag="Pressed to camera")
user.changeNext_move(CLICK_CD_MELEE)
for(var/mob/potential_viewer as anything in GLOB.player_list)
if(isAI(potential_viewer))
var/mob/living/silicon/ai/ai = potential_viewer
if(ai.control_disabled || (ai.stat == DEAD))
continue
ai.log_talk(itemname, LOG_VICTIM, tag="Pressed to camera from [key_name(user)]", log_globally=FALSE)
ai.last_tablet_note_seen = "<HTML><HEAD><TITLE>[itemname]</TITLE></HEAD><BODY><TT>[info]</TT></BODY></HTML>"
if(user.name == "Unknown")
to_chat(ai, "[span_name(user)] holds <a href='byond://?_src_=usr;show_tablet_note=1;'>\a [itemname]</a> up to one of your cameras ...")
else
to_chat(ai, "<b><a href='byond://?src=[REF(ai)];track=[html_encode(user.name)]'>[user]</a></b> holds <a href='byond://?_src_=usr;show_tablet_note=1;'>\a [itemname]</a> up to one of your cameras ...")
continue
if (potential_viewer.client?.eye == src)
to_chat(potential_viewer, "[span_name("[user]")] holds \a [itemname] up to one of the cameras ...")
potential_viewer.log_talk(itemname, LOG_VICTIM, tag="Pressed to camera from [key_name(user)]", log_globally=FALSE)
potential_viewer << browse("<HTML><HEAD><TITLE>[itemname]</TITLE></HEAD><BODY><TT>[info]</TT></BODY></HTML>", "window=[itemname]")
return
if(istype(attacking_item, /obj/item/paper))
// Grab the paper, sanitise the name as we're about to just throw it into chat wrapped in HTML tags.
var/obj/item/paper/paper = attacking_item
// Make a complete copy of the paper, store a ref to it locally on the camera.
last_shown_paper = paper.copy(paper.type, null)
// Then sanitise the name because we're putting it directly in chat later.
var/item_name = sanitize(last_shown_paper.name)
// Start the process of holding it up to the camera.
to_chat(user, span_notice("You hold \the [item_name] up to the camera..."))
user.log_talk(item_name, LOG_GAME, log_globally=TRUE, tag="Pressed to camera")
user.changeNext_move(CLICK_CD_MELEE)
// And make a weakref we can throw around to all potential viewers.
last_shown_paper.camera_holder = WEAKREF(src)
// Iterate over all living mobs and check if anyone is elibile to view the paper.
// This is backwards, but cameras don't store a list of people that are looking through them,
// and we'll have to iterate this list anyway so we can use it to pull out AIs too.
for(var/mob/potential_viewer in GLOB.player_list)
// All AIs view through cameras, so we need to check them regardless.
if(isAI(potential_viewer))
var/mob/living/silicon/ai/ai = potential_viewer
if(ai.control_disabled || (ai.stat == DEAD))
continue
ai.log_talk(item_name, LOG_VICTIM, tag="Pressed to camera from [key_name(user)]", log_globally=FALSE)
log_paper("[key_name(user)] held [last_shown_paper] up to [src], requesting [key_name(ai)] read it.")
if(user.name == "Unknown")
to_chat(ai, "[span_name(user.name)] holds <a href='byond://?_src_=usr;show_paper_note=[REF(last_shown_paper)];'>\a [item_name]</a> up to one of your cameras ...")
else
to_chat(ai, "<b><a href='byond://?src=[REF(ai)];track=[html_encode(user.name)]'>[user]</a></b> holds <a href='byond://?_src_=usr;show_paper_note=[REF(last_shown_paper)];'>\a [item_name]</a> up to one of your cameras ...")
continue
// If it's not an AI, eye if the client's eye is set to the camera. I wonder if this even works anymore with tgui camera apps and stuff?
if (potential_viewer.client?.eye == src)
log_paper("[key_name(user)] held [last_shown_paper] up to [src], and [key_name(potential_viewer)] may read it.")
potential_viewer.log_talk(item_name, LOG_VICTIM, tag="Pressed to camera from [key_name(user)]", log_globally=FALSE)
to_chat(potential_viewer, "[span_name(user)] holds <a href='byond://?_src_=usr;show_paper_note=[REF(last_shown_paper)];'>\a [item_name]</a> up to your camera...")
return
return ..()
/obj/machinery/camera/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if(user.combat_mode)
return ITEM_INTERACT_SKIP_TO_ATTACK
if(istype(tool, /obj/item/stack/sheet/mineral/plasma))
return plasma_act(user, tool)
if(istype(tool, /obj/item/analyzer))
return gas_analyzer_act(user, tool)
if(isprox(tool))
return prox_act(user, tool)
if(istype(tool, /obj/item/stack/cable_coil))
return cable_act(user, tool)
if(istype(tool, /obj/item/modular_computer))
return computer_act(user, tool)
if(istype(tool, /obj/item/paper))
return paper_act(user, tool)
return NONE