Files
VOREStation/code/modules/integrated_electronics/core/printer.dm
Cameron Lennox d5849910e5 Begin clickcode attack_self fix (#18797)
* Begin clickcode attack_self fix

Begins the work to make everything call back to parent for attack_self so that signals are sacred.

* Makes MORE things call the attack_self() parent

Yes, I could make special_handling a var on obj/item HOWEVER i want it to be specific so it can be tracked down later and ONLY the objects that use it can be refactored instead of sitting there literally forever and it just becoming 'a thing'.

* Finishes making the rest of attack_self call parent.

As mentioned, things such as 'specialty_goggles' 'special_handling' and the such are only there to help with attack_self until the attack_self is recoded for those items.

* begone foul demon

* some more cleanup

* These

* GOD this was annoying

* yeh

* Fix this

* fLARES

* Thesee too

* toys!

* Even more!

* More fixes

* Even more

* rest of em

* these too

* Update syndie.dm

* hardref clear

* Update code/game/gamemodes/nuclear/pinpointer.dm

* Update code/game/objects/effects/mines.dm

* Update code/game/objects/items/blueprints_vr.dm

* Update code/game/objects/items/blueprints_vr.dm

* Update code/game/objects/items/contraband_vr.dm

* Update code/game/objects/items/crayons.dm

* Update code/game/objects/items/crayons.dm

* Update code/game/objects/items/gunbox.dm

* Update code/game/objects/items/gunbox.dm

* Update code/game/objects/items/gunbox_vr.dm

* Update code/game/objects/items/gunbox_vr.dm

* Update code/game/objects/items/weapons/gift_wrappaper.dm

* Update code/game/objects/items/crayons.dm

* Update code/game/objects/items/crayons.dm

* Update code/game/objects/items/gunbox.dm

* these too

* Update maintpanel_stack.dm

* angry warning

* Fixes packaged snacks.

Fixes improper var default.

* Special handling for these

* proper poly types

* Fixes magclaws

Makes the 'features' it had just part  of base magboots that can be adjusted via varswap.

* Fixes jackets

Fixes https://github.com/VOREStation/VOREStation/issues/18941

* Small bugfix

Makes p_Theyre properly capitialize
Makes examine show proper wording

* Update gift_wrappaper.dm
2025-12-29 13:21:10 -05:00

523 lines
18 KiB
Plaintext

/obj/item/integrated_circuit_printer
name = "integrated circuit printer"
desc = "A portable(ish) machine made to print tiny modular circuitry out of metal."
icon = 'icons/obj/integrated_electronics/electronic_tools.dmi'
icon_state = "circuit_printer"
w_class = ITEMSIZE_LARGE
var/metal = 0
var/max_metal = 100
var/metal_per_sheet = 10 // One sheet equals this much metal.
var/debug = FALSE // If true, metal is infinite.
var/upgraded = FALSE // When hit with an upgrade disk, will turn true, allowing it to print the higher tier circuits.
var/illegal_upgraded = FALSE // When hit with an illegal upgrade disk, will turn true, allowing it to print the illegal circuits.
var/can_clone = FALSE // Same for above, but will allow the printer to duplicate a specific assembly.
var/dirty_items = FALSE
// Printing state variables
var/is_printing = FALSE // If true, printer is busy cloning.
var/print_end_time = 0 // World time when printing will finish
var/obj/item/electronic_assembly/queued_assembly = null // The assembly being cloned.
/obj/item/integrated_circuit_printer/proc/finish_printing()
if(!queued_assembly)
is_printing = FALSE
return
// Drop the assembly on the ground
queued_assembly.forceMove(get_turf(src))
playsound(src, 'sound/machines/ding.ogg', 50, TRUE)
visible_message(span_notice("[src] beeps as it finishes printing '[queued_assembly.name]'."))
// Clear printing state
queued_assembly = null
is_printing = FALSE
print_end_time = 0
/obj/item/integrated_circuit_printer/all_upgrades
upgraded = TRUE
illegal_upgraded = TRUE
can_clone = TRUE
/obj/item/integrated_circuit_printer/illegal
illegal_upgraded = TRUE
can_clone = TRUE
/obj/item/integrated_circuit_printer/upgraded
upgraded = TRUE
can_clone = TRUE
/obj/item/integrated_circuit_printer/debug
name = "fractal integrated circuit printer"
desc = "A portable(ish) machine that makes modular circuitry seemingly out of thin air."
upgraded = TRUE
illegal_upgraded = TRUE
can_clone = TRUE
debug = TRUE
/obj/item/integrated_circuit_printer/attack_robot(mob/user as mob)
if(Adjacent(user))
return tgui_interact(user)
else
return ..()
/obj/item/integrated_circuit_printer/attackby(var/obj/item/O, var/mob/user)
if(istype(O,/obj/item/stack/material))
var/obj/item/stack/material/stack = O
if(stack.material.name == MAT_STEEL)
if(debug)
to_chat(user, span_warning("\The [src] does not need any material."))
return
var/num = min((max_metal - metal) / metal_per_sheet, stack.get_amount())
if(num < 1)
to_chat(user, span_warning("\The [src] is too full to add more metal."))
return
if(stack.use(max(1, round(num)))) // We don't want to create stacks that aren't whole numbers
to_chat(user, span_notice("You add [num] sheet\s to \the [src]."))
metal += num * metal_per_sheet
attack_self(user)
return TRUE
if(istype(O,/obj/item/integrated_circuit))
to_chat(user, span_notice("You insert the circuit into \the [src]."))
user.unEquip(O)
metal = min(metal + O.w_class, max_metal)
qdel(O)
attack_self(user)
return TRUE
if(istype(O,/obj/item/disk/integrated_circuit/upgrade/advanced))
if(upgraded)
to_chat(user, span_warning("\The [src] already has this upgrade."))
return TRUE
to_chat(user, span_notice("You install \the [O] into \the [src]."))
upgraded = TRUE
dirty_items = TRUE
attack_self(user)
return TRUE
if(istype(O,/obj/item/disk/integrated_circuit/upgrade/illegal))
if(illegal_upgraded)
to_chat(user, span_warning("\The [src] already has this upgrade."))
return TRUE
to_chat(user, span_notice("You install \the [O] into \the [src]."))
illegal_upgraded = TRUE
dirty_items = TRUE
attack_self(user)
return TRUE
if(istype(O,/obj/item/disk/integrated_circuit/upgrade/clone))
if(can_clone)
to_chat(user, span_warning("\The [src] already has this upgrade."))
return TRUE
to_chat(user, span_notice("You install \the [O] into \the [src]."))
can_clone = TRUE
attack_self(user)
return TRUE
return ..()
/obj/item/integrated_circuit_printer/vv_edit_var(var_name, var_value)
// Gotta update the static data in case an admin VV's the upgraded var for some reason..!
if(var_name == "upgraded")
dirty_items = TRUE
return ..()
/obj/item/integrated_circuit_printer/attack_self(mob/user)
. = ..(user)
if(.)
return TRUE
tgui_interact(user)
/obj/item/integrated_circuit_printer/tgui_state(mob/user)
return GLOB.tgui_physical_state
/obj/item/integrated_circuit_printer/tgui_interact(mob/user, datum/tgui/ui)
if(dirty_items)
update_tgui_static_data(user, ui)
dirty_items = FALSE
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ICPrinter", name) // 500, 600
ui.open()
/obj/item/integrated_circuit_printer/tgui_static_data(mob/user)
var/list/data = ..()
var/list/categories = list()
for(var/category in SScircuit.circuit_fabricator_recipe_list)
var/list/cat_obj = list(
"name" = category,
"items" = null
)
if(cat_obj["name"] == "Illegal Parts" && !illegal_upgraded)
continue
var/list/circuit_list = SScircuit.circuit_fabricator_recipe_list[category]
var/list/items = list()
for(var/path in circuit_list)
var/obj/O = path
var/can_build = TRUE
if(ispath(path, /obj/item/integrated_circuit))
var/obj/item/integrated_circuit/IC = path
if((IC::spawn_flags & IC_SPAWN_RESEARCH) && (!(IC::spawn_flags & IC_SPAWN_DEFAULT)) && !upgraded)
can_build = FALSE
var/cost = 1
if(ispath(path, /obj/item/electronic_assembly))
var/obj/item/electronic_assembly/E = path
cost = round((E::max_complexity + E::max_components) / 4)
else
var/obj/item/I = path
cost = I::w_class
items.Add(list(list(
"name" = O::name,
"desc" = O::desc,
"can_build" = can_build,
"cost" = cost,
"path" = path,
)))
cat_obj["items"] = items
categories.Add(list(cat_obj))
data["categories"] = categories
return data
/obj/item/integrated_circuit_printer/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
var/list/data = ..()
data["metal"] = metal
data["max_metal"] = max_metal
data["metal_per_sheet"] = metal_per_sheet
data["debug"] = debug
data["upgraded"] = upgraded
data["can_clone"] = can_clone && !is_printing // Can not clone while printing
data["is_printing"] = is_printing
data["print_time_remaining"] = is_printing ? max(0, print_end_time - world.time) : 0
return data
/obj/item/integrated_circuit_printer/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
if(..())
return TRUE
add_fingerprint(ui.user)
switch(action)
if("import_circuit")
if(!can_clone)
to_chat(ui.user, span_warning("This printer requires a clone upgrade disk to import circuit designs!"))
return TRUE
if(is_printing) // Should not be possible to reach here.
to_chat(ui.user, span_warning("The printer is busy! Please wait for the current print job to finish."))
return TRUE
handle_circuit_import(ui.user)
return TRUE
if("build")
var/build_type = text2path(params["build"])
if(!build_type || !ispath(build_type))
return 1
var/cost = 1
if(ispath(build_type, /obj/item/electronic_assembly))
var/obj/item/electronic_assembly/E = build_type
cost = round( (E::max_complexity + E::max_components ) / 4)
else
var/obj/item/I = build_type
cost = I::w_class
var/in_some_category = FALSE
for(var/category in SScircuit.circuit_fabricator_recipe_list)
if(build_type in SScircuit.circuit_fabricator_recipe_list[category])
in_some_category = TRUE
break
if(!in_some_category)
return
if(!debug)
if(!Adjacent(ui.user))
to_chat(ui.user, span_notice("You are too far away from \the [src]."))
if(metal - cost < 0)
to_chat(ui.user, span_warning("You need [cost] metal to build that!."))
return 1
metal -= cost
var/obj/item/built = new build_type(get_turf(loc))
ui.user.put_in_hands(built)
to_chat(ui.user, span_notice("[capitalize(built.name)] printed."))
playsound(src, 'sound/items/jaws_pry.ogg', 50, TRUE)
return TRUE
/**
* Imports a circuit design from JSON data
* This uses the same logic as the vrdb/html vore belly imports
*
* @param user The user importing the circuit
* @param circuit_data JSON string containing the circuit data
*/
/obj/item/integrated_circuit_printer/proc/check_interactivity(mob/user)
return user.Adjacent(src)
/obj/item/integrated_circuit_printer/proc/handle_circuit_import(mob/user)
if(!user || user.stat || user.restrained() || !Adjacent(user))
return
var/input_file = input(user, "Please choose a circuit JSON file to import.", "Import Circuit") as file
if(!input_file)
return
var/file_size = length(file2text(input_file))
if(file_size > 1048576 / 4) // quarter of a megabyte.
to_chat(user, span_warning("File too large! Circuit files must be smaller than 1MB. Your file is [num2text(file_size)] bytes."))
return
if(file_size < 10)
to_chat(user, span_warning("This doesn't appear to be a valid circuit file."))
return
var/input_data
try
input_data = file2text(input_file)
catch(var/exception/e)
to_chat(user, span_warning("Failed to read file: [e]. Please ensure you selected a valid text/JSON file."))
return
if(!input_data || length(input_data) < 10)
to_chat(user, span_warning("The selected file is empty or unreadable. Please select a valid circuit JSON file."))
return
// Basic JSON validation.
if(!findtext(input_data, "{"))
// If it doesn't contain basic JSON characters, it's likely not a JSON file
to_chat(user, span_warning("Invalid file format! Please select a JSON file containing circuit data. (File appears to be binary or non-text format)"))
return
// Additional validation to prevent binary files..
if(findtext(input_data, "\xFF\xD8\xFF") || findtext(input_data, "\x89PNG") || findtext(input_data, "GIF89a") || findtext(input_data, "GIF87a"))
to_chat(user, span_warning("Invalid file type! You selected an image file. Please select a JSON text file containing circuit data."))
return
// Check if the input is Base64 encoded and decode it
if(length(input_data) > 0 && !findtext(input_data, "{"))
// If it doesn't contain '{' it's likely Base64 encoded JSON
var/decoded_data = rustg_decode_base64(input_data)
if(decoded_data && length(decoded_data) > 0)
input_data = decoded_data
else
to_chat(user, span_warning("Unable to decode file data. Please select a valid circuit JSON file."))
return
import_circuit(user, input_data, FALSE, null)
/obj/item/integrated_circuit_printer/proc/import_circuit(mob/user, circuit_data, override_type = FALSE, custom_type = null)
if(!circuit_data)
to_chat(user, span_warning("No circuit data provided!"))
return
// Add safety check before deserializing
if(length(circuit_data) > 100000) // Reduced from 50KB to be more conservative
to_chat(user, span_warning("Circuit data is too large to process!"))
return
// Additional safety checks for malformed data
if(length(circuit_data) < 20) // Increase minimum size
to_chat(user, span_warning("Circuit data is too small to be valid."))
return
// Validate that this looks like circuit JSON data
if(!findtext(circuit_data, "components") && !findtext(circuit_data, "assembly"))
to_chat(user, span_warning("This doesn't appear to be valid circuit data."))
return
// Deserialize the circuit data with enhanced error handling
var/list/assembly_data = null
try
assembly_data = deserialize_electronic_assembly(circuit_data)
catch(var/exception/e)
to_chat(user, span_warning("Failed to process circuit data: [e]. The file may be corrupted or not a valid circuit export."))
return
if(!assembly_data)
to_chat(user, span_warning("Invalid circuit data! Please select a valid circuit export file (.json) created by the circuit export system."))
return
// Validate that the assembly data has required fields
if(!islist(assembly_data) || !assembly_data["components"])
to_chat(user, span_warning("Invalid circuit format!"))
return
if(assembly_data["t"])
var/restored_path = src.restore_assembly_prefix(assembly_data["t"])
var/assembly_path = text2path(restored_path)
if(assembly_path && ispath(assembly_path, /obj/item/electronic_assembly/clothing))
to_chat(user, span_warning("Cannot import wearable electronic assemblies!"))
return
// Check if we have enough metal to build all components
var/total_cost = 0
var/total_complexity = 0
var/list/available_components = list()
var/list/components_to_create = list()
// Build list of available components
for(var/category in SScircuit.circuit_fabricator_recipe_list)
if(category == "Illegal Parts" && !illegal_upgraded)
continue
var/list/circuit_list = SScircuit.circuit_fabricator_recipe_list[category]
for(var/path in circuit_list)
available_components += path
// Check each component and calculate costs
for(var/list/component_data in assembly_data["components"])
// Support both old "type" and new "t" format for component type
var/component_type = component_data["type"] || component_data["t"]
// Support both old "name" and new "n" format for component name
var/component_name = component_data["name"] || component_data["n"] || "Unknown Component"
if(!component_type)
to_chat(user, span_warning("Component missing type information. Skipping."))
continue
// Handle both shortened and full paths flexibly
var/build_type = null
build_type = text2path(component_type)
if(!build_type || !ispath(build_type, /obj/item/integrated_circuit))
// Try with circuit prefix (for new shortened paths)
var/full_path = "/obj/item/integrated_circuit/[component_type]"
build_type = text2path(full_path)
if(!build_type || !ispath(build_type, /obj/item/integrated_circuit))
to_chat(user, span_warning("Unknown component type: [component_type]. Skipping."))
continue
// Check if this component is available
if(!(build_type in available_components))
to_chat(user, span_warning("Component '[component_name]' ([build_type]) is not available in this printer. Skipping."))
continue
// Check if component requires upgrades
if(ispath(build_type, /obj/item/integrated_circuit))
var/obj/item/integrated_circuit/IC = build_type
var/spawn_flags = IC::spawn_flags
// Component requires upgrades only if it has IC_SPAWN_RESEARCH but NOT IC_SPAWN_DEFAULT
if((spawn_flags & IC_SPAWN_RESEARCH) && !(spawn_flags & IC_SPAWN_DEFAULT) && !upgraded)
to_chat(user, span_warning("Component '[component_name]' requires printer upgrades. Skipping."))
continue
// Calculate cost
var/cost = 1
if(ispath(build_type, /obj/item/electronic_assembly))
var/obj/item/electronic_assembly/E = build_type
cost = round((E::max_complexity + E::max_components) / 4)
else
var/obj/item/I = build_type
cost = I::w_class
// Calculate complexity for printing time
var/complexity = 1
if(ispath(build_type, /obj/item/integrated_circuit))
var/obj/item/integrated_circuit/IC = build_type
complexity = IC::complexity
total_cost += cost
total_complexity += complexity
UNTYPED_LIST_ADD(components_to_create, list(
"type" = build_type,
"data" = component_data,
"cost" = cost
))
if(!LAZYLEN(components_to_create))
to_chat(user, span_warning("No valid components found in the circuit data!"))
return
// Check if we have enough metal
if(!debug && (total_cost / 2) > metal)
to_chat(user, span_warning("Not enough metal! Need [total_cost / 2] units, have [metal] units."))
return
// Calculate assembly cost based on w_class (1 metal per size level)
var/assembly_w_class = assembly_data["w_class"] || ITEMSIZE_SMALL // Default to SMALL if not specified
var/assembly_cost = assembly_w_class * 10
total_cost += assembly_cost
// Final metal check with assembly cost
if(!debug && (total_cost / 2) > metal)
to_chat(user, span_warning("Not enough metal! Need [total_cost / 2] units (including assembly), have [metal] units."))
return
if(!debug)
metal = max(0, metal - (total_cost / 2))
// Calculate printing time based on actual complexity (1 minute per 120 complexity = 5 deciseconds per complexity)
var/print_time = total_complexity * 5
// Create the assembly
var/obj/item/electronic_assembly/assembly = create_assembly_from_data(assembly_data, override_type, custom_type)
if(!assembly)
to_chat(user, span_warning("Failed to create assembly!"))
if(!debug)
metal += total_cost
return
// Add components to assembly
var/list/created_components = add_components_to_assembly(assembly, assembly_data, available_components)
if(!created_components || !LAZYLEN(created_components))
to_chat(user, span_warning("Failed to add components to assembly! No components were created."))
qdel(assembly)
if(!debug)
metal += total_cost
return
// Restore wiring, and appearance
restore_component_wiring(assembly_data, created_components)
assembly.update_icon()
// Start the printing process
queued_assembly = assembly
is_printing = TRUE
print_end_time = world.time + print_time
// Use addtimer instead of processing for efficiency
addtimer(CALLBACK(src, PROC_REF(finish_printing)), print_time, TIMER_DELETE_ME)
var/print_minutes = round(print_time / 600, 0.1) // Convert to minutes for display
to_chat(user, span_notice("Printing '[assembly.name]' with [LAZYLEN(created_components)] component\s. Estimated completion time: [print_minutes] minute\s."))
playsound(src, 'sound/machines/click.ogg', 50, TRUE)
return TRUE
// FUKKEN UPGRADE DISKS
/obj/item/disk/integrated_circuit/upgrade
name = "integrated circuit printer upgrade disk"
desc = "Install this into your integrated circuit printer to enhance it."
icon = 'icons/obj/integrated_electronics/electronic_tools.dmi'
icon_state = "upgrade_disk"
item_state = "card-id"
w_class = ITEMSIZE_SMALL
origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 4)
/obj/item/disk/integrated_circuit/upgrade/advanced
name = "integrated circuit printer upgrade disk - advanced designs"
desc = "Install this into your integrated circuit printer to enhance it. This one adds new, advanced designs to the printer."
/obj/item/disk/integrated_circuit/upgrade/illegal
name = "integrated circuit printer upgrade disk - illegal designs"
desc = "Install this into your integrated circuit printer to enhance it. This one adds new, but illegal designs to the printer."
icon_state = "upgrade_disk_illegal"
origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 4, TECH_ILLEGAL = 1)
/obj/item/disk/integrated_circuit/upgrade/clone
name = "integrated circuit printer upgrade disk - circuit cloner"
desc = "Install this into your integrated circuit printer to enhance it. This one allows the printer to duplicate assemblies."
icon_state = "upgrade_disk_clone"
origin_tech = list(TECH_ENGINEERING = 5, TECH_DATA = 6)