Files
CHOMPStation2/code/modules/integrated_electronics/core/assemblies.dm
2025-08-30 04:45:41 +02:00

486 lines
15 KiB
Plaintext

// Here is where the base definition lives.
// Specific subtypes are in their own folder.
/obj/item/electronic_assembly
name = "electronic assembly"
desc = "It's a case, for building small electronics with."
w_class = ITEMSIZE_SMALL
icon = 'icons/obj/integrated_electronics/electronic_setups.dmi'
icon_state = "setup_small"
show_messages = TRUE
var/max_components = IC_COMPONENTS_BASE
var/max_complexity = IC_COMPLEXITY_BASE
var/opened = FALSE
var/can_anchor = FALSE // If true, wrenching it will anchor it.
var/obj/item/cell/device/battery = null // Internal cell which most circuits need to work.
var/net_power = 0 // Set every tick, to display how much power is being drawn in total.
var/detail_color = COLOR_ASSEMBLY_BLACK
var/locked = FALSE // If true, the assembly cannot be opened with a crowbar
var/obj/item/card/id/locked_by = null // The ID that locked this assembly
var/obj/item/card/id/access_card = null // ID card for door access
var/list/component_positions = list() // Stores circuit positions as list of lists: list("ref" = ref, "x" = x, "y" = y)
/obj/item/electronic_assembly/Initialize(mapload)
battery = new(src)
START_PROCESSING(SSobj, src)
return ..()
/obj/item/electronic_assembly/Destroy()
STOP_PROCESSING(SSobj, src)
battery = null // It will be qdel'd by ..() if still in our contents
return ..()
/obj/item/electronic_assembly/process()
handle_idle_power()
/obj/item/electronic_assembly/proc/handle_idle_power()
net_power = 0 // Reset this. This gets increased/decreased with [give/draw]_power() outside of this loop.
// First we handle passive sources. Most of these make power so they go first.
for(var/obj/item/integrated_circuit/passive/power/P in contents)
P.handle_passive_energy()
// Now we handle idle power draw.
for(var/obj/item/integrated_circuit/IC in contents)
if(IC.power_draw_idle)
if(!draw_power(IC.power_draw_idle))
IC.power_fail()
/obj/item/electronic_assembly/proc/check_interactivity(mob/user)
return tgui_status(user, GLOB.tgui_physical_state) == STATUS_INTERACTIVE
/obj/item/electronic_assembly/get_cell()
return battery
// TGUI
/obj/item/electronic_assembly/tgui_state(mob/user)
return GLOB.tgui_physical_state
/obj/item/electronic_assembly/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ICAssembly", name, parent_ui)
ui.open()
/obj/item/electronic_assembly/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/simple/circuit_assets)
)
/obj/item/electronic_assembly/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
var/list/data = ..()
var/total_parts = 0
var/total_complexity = 0
for(var/obj/item/integrated_circuit/part in contents)
total_parts += part.size
total_complexity = total_complexity + part.complexity
data["total_parts"] = total_parts
data["max_components"] = max_components
data["total_complexity"] = total_complexity
data["max_complexity"] = max_complexity
data["battery_charge"] = round(battery?.charge, 0.1)
data["battery_max"] = round(battery?.maxcharge, 0.1)
data["net_power"] = net_power / CELLRATE
// Include export data - the UI component will handle displaying it if needed
data["export_data"] = serialize_electronic_assembly()
data["assembly_name"] = name
var/list/circuits = list()
for(var/obj/item/integrated_circuit/circuit in contents)
UNTYPED_LIST_ADD(circuits, circuit.tgui_data(user, ui, state))
data["circuits"] = circuits
// Include component positions for UI restoration
data["component_positions"] = component_positions
return data
/obj/item/electronic_assembly/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
if(..())
return TRUE
switch(action)
if("export_circuit")
if(!LAZYLEN(contents))
to_chat(ui.user, span_warning("There's nothing in the [src] to export!"))
return TRUE
var/datum/tgui/window = new(ui.user, src, "ICExport", "Circuit Export")
window.open()
return TRUE
// Actual assembly actions
if("rename")
rename(ui.user)
return TRUE
if("remove_cell")
if(!battery)
to_chat(ui.user, span_warning("There's no power cell to remove from \the [src]."))
return FALSE
var/turf/T = get_turf(src)
battery.forceMove(T)
playsound(T, 'sound/items/Crowbar.ogg', 50, 1)
to_chat(ui.user, span_notice("You pull \the [battery] out of \the [src]'s power supplier."))
battery = null
return TRUE
// Circuit actions
if("wire_internal")
var/datum/integrated_io/pin1 = locate(params["pin1"])
if(!istype(pin1))
return
var/datum/integrated_io/pin2 = locate(params["pin2"])
if(!istype(pin2))
return
var/obj/item/integrated_circuit/holder1 = pin1.holder
if(!istype(holder1) || holder1.loc != src || holder1.assembly != src)
return
var/obj/item/integrated_circuit/holder2 = pin2.holder
if(!istype(holder2) || holder2.loc != src || holder2.assembly != src)
return
// Wiring the same pin will unwire it
if(pin2 in pin1.linked)
pin1.linked -= pin2
pin2.linked -= pin1
else
pin1.linked |= pin2
pin2.linked |= pin1
return TRUE
if("remove_all_wires")
var/datum/integrated_io/pin1 = locate(params["pin"])
if(!istype(pin1))
return
var/obj/item/integrated_circuit/holder1 = pin1.holder
if(!istype(holder1) || holder1.loc != src || holder1.assembly != src)
return
for(var/datum/integrated_io/other as anything in pin1.linked)
other.linked -= pin1
pin1.linked.Cut()
return TRUE
if("open_circuit")
var/obj/item/integrated_circuit/C = locate(params["ref"]) in contents
if(!istype(C))
return
C.tgui_interact(ui.user, null, ui)
return TRUE
if("remove_circuit")
var/obj/item/integrated_circuit/C = locate(params["ref"]) in contents
if(!istype(C))
return
C.remove(ui.user)
return TRUE
if("update_component_position")
var/obj/item/integrated_circuit/C = locate(params["ref"]) in contents
if(!istype(C))
return FALSE
var/new_x = params["x"]
var/new_y = params["y"]
if(!isnum(new_x) || !isnum(new_y))
return FALSE
// Find existing position entry or create new one
var/found = FALSE
for(var/list/pos_data in component_positions)
if(pos_data["ref"] == REF(C))
pos_data["x"] = new_x
pos_data["y"] = new_y
found = TRUE
break
if(!found)
UNTYPED_LIST_ADD(component_positions, list("ref" = REF(C), "x" = new_x, "y" = new_y))
return TRUE
return FALSE
// End TGUI
/obj/item/electronic_assembly/verb/rename()
set name = "Rename Circuit"
set category = "Object"
set desc = "Rename your circuit, useful to stay organized."
var/mob/M = usr
if(!check_interactivity(M))
return
var/input = sanitizeSafe(tgui_input_text(usr, "What do you want to name this?", "Rename", src.name, MAX_NAME_LEN, encode = FALSE), MAX_NAME_LEN)
if(src && input)
to_chat(M, span_notice("The machine now has a label reading '[input]'."))
name = input
/obj/item/electronic_assembly/proc/can_move()
return FALSE
/obj/item/electronic_assembly/update_icon()
if(opened)
icon_state = initial(icon_state) + "-open"
// else if(locked)
// For when I finish the locked assembly sprites.
// icon_state = initial(icon_state) // + "-locked" would be added once sprites are made
else
icon_state = initial(icon_state)
cut_overlays()
if(detail_color == COLOR_ASSEMBLY_BLACK) //Black colored overlay looks almost but not exactly like the base sprite, so just cut the overlay and avoid it looking kinda off.
return
var/mutable_appearance/detail_overlay = mutable_appearance('icons/obj/integrated_electronics/electronic_setups.dmi', "[icon_state]-color")
detail_overlay.color = detail_color
add_overlay(detail_overlay)
/obj/item/electronic_assembly/examine(mob/user)
. = ..()
if(Adjacent(user))
for(var/obj/item/integrated_circuit/IC in contents)
// Make sure there's actually examine text to prevent empty lines being printed for EVERY component!
var/examine_text = IC.external_examine(user)
if (length(examine_text))
. += examine_text
if(opened)
tgui_interact(user)
/obj/item/electronic_assembly/proc/get_part_complexity()
. = 0
for(var/obj/item/integrated_circuit/part in contents)
. += part.complexity
/obj/item/electronic_assembly/proc/get_part_size()
. = 0
for(var/obj/item/integrated_circuit/part in contents)
. += part.size
// Returns true if the circuit made it inside.
/obj/item/electronic_assembly/proc/add_circuit(var/obj/item/integrated_circuit/IC, var/mob/user)
if(!opened)
to_chat(user, span_warning("\The [src] isn't opened, so you can't put anything inside. Try using a crowbar."))
return FALSE
if(IC.w_class > src.w_class)
to_chat(user, span_warning("\The [IC] is way too big to fit into \the [src]."))
return FALSE
var/total_part_size = get_part_size()
var/total_complexity = get_part_complexity()
if((total_part_size + IC.size) > max_components)
to_chat(user, span_warning("You can't seem to add the '[IC.name]', as there's insufficient space."))
return FALSE
if((total_complexity + IC.complexity) > max_complexity)
to_chat(user, span_warning("You can't seem to add the '[IC.name]', since this setup's too complicated for the case."))
return FALSE
if(!IC.forceMove(src))
return FALSE
IC.assembly = src
return TRUE
// Non-interactive version of above that always succeeds, intended for build-in circuits that get added on assembly initialization.
/obj/item/electronic_assembly/proc/force_add_circuit(var/obj/item/integrated_circuit/IC)
IC.forceMove(src)
IC.assembly = src
/obj/item/electronic_assembly/afterattack(atom/target, mob/user, proximity)
var/scanned = FALSE
if(proximity)
// Existing sensor support
for(var/obj/item/integrated_circuit/input/sensor/S in contents)
if(S.scan(target))
scanned = TRUE
if(scanned)
visible_message(span_infoplain(span_bold("\The [user]") + " waves \the [src] around [target]."))
// Support for reference grabber + future ranged circuitry.
for(var/obj/item/integrated_circuit/input/reference_grabber/G in contents)
G.afterattack(target, user, proximity, null)
/obj/item/electronic_assembly/attackby(var/obj/item/I, var/mob/user)
if(can_anchor && I.has_tool_quality(TOOL_WRENCH))
anchored = !anchored
to_chat(user, span_notice("You've [anchored ? "" : "un"]secured \the [src] to \the [get_turf(src)]."))
if(anchored)
on_anchored()
else
on_unanchored()
playsound(src, I.usesound, 50, 1)
return TRUE
else if(istype(I, /obj/item/integrated_circuit))
if(!user.unEquip(I) && !isrobot(user)) //Robots cannot de-equip items in grippers.
return FALSE
if(add_circuit(I, user))
to_chat(user, span_notice("You slide \the [I] inside \the [src]."))
playsound(src, 'sound/items/Deconstruct.ogg', 50, 1)
tgui_interact(user)
return TRUE
else if(I.has_tool_quality(TOOL_CROWBAR))
if(locked)
to_chat(user, span_warning("\The [src] is locked! You cannot open it with a crowbar."))
return FALSE
playsound(src, 'sound/items/Crowbar.ogg', 50, 1)
opened = !opened
to_chat(user, span_notice("You [opened ? "opened" : "closed"] \the [src]."))
update_icon()
return TRUE
else if((istype(I, /obj/item/card/id) || istype(I, /obj/item/pda)) && !opened)
var/obj/item/card/id/id_card = null
if(istype(I, /obj/item/card/id))
id_card = I
else
var/obj/item/pda/pda = I
id_card = pda.id
if(!id_card)
to_chat(user, span_warning("You need an ID card to lock this assembly!"))
return FALSE
if(locked)
// Trying to unlock
if(locked_by && id_card.registered_name == locked_by.registered_name)
locked = FALSE
locked_by = null
to_chat(user, span_notice("You unlock \the [src]."))
update_icon()
else
to_chat(user, span_warning("Access denied. This assembly was locked by [locked_by ? locked_by.registered_name : "someone else"]."))
return TRUE
else
// Trying to lock
locked = TRUE
locked_by = id_card
to_chat(user, span_notice("You lock \the [src]. Now only your ID card can unlock it."))
update_icon()
return TRUE
else if(istype(I, /obj/item/integrated_electronics/wirer) || istype(I, /obj/item/integrated_electronics/debugger) || I.has_tool_quality(TOOL_SCREWDRIVER))
if(opened)
tgui_interact(user)
return TRUE
else
to_chat(user, span_warning("\The [src] isn't opened, so you can't fiddle with the internal components. \
Try using a crowbar."))
return FALSE
else if(istype(I, /obj/item/integrated_electronics/detailer))
var/obj/item/integrated_electronics/detailer/D = I
detail_color = D.detail_color
update_icon()
else if(istype(I, /obj/item/cell/device))
if(!opened)
to_chat(user, span_warning("\The [src] isn't opened, so you can't put anything inside. Try using a crowbar."))
return FALSE
if(battery)
to_chat(user, span_warning("\The [src] already has \a [battery] inside. Remove it first if you want to replace it."))
return FALSE
var/obj/item/cell/device/cell = I
user.drop_item(cell)
cell.forceMove(src)
battery = cell
playsound(src, 'sound/items/Deconstruct.ogg', 50, 1)
to_chat(user, span_notice("You slot \the [cell] inside \the [src]'s power supplier."))
tgui_interact(user)
return TRUE
else
return ..()
/obj/item/electronic_assembly/attack_self(mob/user)
if(!check_interactivity(user))
return
if(opened)
tgui_interact(user)
var/list/input_selection = list()
var/list/available_inputs = list()
for(var/obj/item/integrated_circuit/input/input in contents)
if(input.can_be_asked_input)
available_inputs.Add(input)
var/i = 0
for(var/obj/item/integrated_circuit/s in available_inputs)
if(s.name == input.name && s.displayed_name == input.displayed_name && s != input)
i++
var/disp_name= "[input.displayed_name] \[[input.name]\]"
if(i)
disp_name += " ([i+1])"
input_selection.Add(disp_name)
var/obj/item/integrated_circuit/input/choice
if(available_inputs)
var/selection = tgui_input_list(user, "What do you want to interact with?", "Interaction", input_selection)
if(selection)
var/index = input_selection.Find(selection)
choice = available_inputs[index]
if(choice)
choice.ask_for_input(user)
/obj/item/electronic_assembly/attack_robot(mob/user as mob)
if(Adjacent(user))
return attack_self(user)
else
return ..()
/obj/item/electronic_assembly/emp_act(severity)
..()
for(var/atom/movable/AM in contents)
AM.emp_act(severity)
// Returns true if power was successfully drawn.
/obj/item/electronic_assembly/proc/draw_power(amount)
if(battery)
var/lost = battery.use(amount * CELLRATE)
net_power -= lost
return lost
return FALSE
// Ditto for giving.
/obj/item/electronic_assembly/proc/give_power(amount)
if(battery)
var/gained = battery.give(amount * CELLRATE)
net_power += gained
return TRUE
return FALSE
/obj/item/electronic_assembly/proc/on_anchored()
for(var/obj/item/integrated_circuit/IC in contents)
IC.on_anchored()
/obj/item/electronic_assembly/proc/on_unanchored()
for(var/obj/item/integrated_circuit/IC in contents)
IC.on_unanchored()
// Bump functionality, for pathfinding circuits. (Droid circuit assembly types)
/obj/item/electronic_assembly/Bump(atom/AM)
..()
if(istype(AM, /obj/machinery/door) && can_move())
var/obj/machinery/door/D = AM
if(D.check_access(src))
D.open()
// Returns TRUE if I is something that could/should have a valid interaction. Used to tell circuitclothes to hit the circuit with something instead of the clothes
/obj/item/electronic_assembly/proc/is_valid_tool(var/obj/item/I)
return I.has_tool_quality(TOOL_CROWBAR) || I.has_tool_quality(TOOL_SCREWDRIVER) || istype(I, /obj/item/integrated_circuit) || istype(I, /obj/item/cell/device) || istype(I, /obj/item/integrated_electronics)