/obj/machinery/mecha_part_fabricator
icon = 'icons/obj/robotics_vr.dmi' //VOREStation Edit - New icon
icon_state = "mechfab"
name = "Exosuit Fabricator"
desc = "A machine used for the construction of mechas."
density = TRUE
anchored = TRUE
use_power = USE_POWER_IDLE
idle_power_usage = 20
active_power_usage = 5000
req_access = list(access_robotics)
circuit = /obj/item/circuitboard/mechfab
/// Current items in the build queue.
var/list/queue = list()
/// Whether or not the machine is building the entire queue automagically.
var/process_queue = FALSE
/// The current design datum that the machine is building.
var/datum/design/being_built
/// World time when the build will finish.
var/build_finish = 0
/// World time when the build started.
var/build_start = 0
/// Reference to all materials used in the creation of the item being_built.
var/list/build_materials
/// Part currently stored in the Exofab.
var/obj/item/stored_part
/// Coefficient for the speed of item building. Based on the installed parts.
var/time_coeff = 1
/// Coefficient for the efficiency of material usage in item building. Based on the installed parts.
var/component_coeff = 1
var/loading_icon_state = "mechfab-idle"
var/list/materials = list(
MAT_STEEL = 0,
MAT_GLASS = 0,
MAT_PLASTIC = 0,
MAT_GRAPHITE = 0,
MAT_PLASTEEL = 0,
MAT_GOLD = 0,
MAT_SILVER = 0,
MAT_LEAD = 0,
MAT_OSMIUM = 0,
MAT_DIAMOND = 0,
MAT_DURASTEEL = 0,
MAT_PHORON = 0,
MAT_URANIUM = 0,
MAT_VERDANTIUM = 0,
MAT_MORPHIUM = 0,
MAT_METALHYDROGEN = 0,
MAT_SUPERMATTER = 0)
var/res_max_amount = 200000
var/datum/research/files
var/valid_buildtype = MECHFAB
/// A list of categories that valid MECHFAB design datums will broadly categorise themselves under.
var/list/part_sets = list(
"Cyborg",
"Ripley",
"Scarab", //CHOMPedit adds Scarab Light Combat Mech to TGui fabricator.
"Odysseus",
"Gygax",
"Durand",
"Janus",
"Vehicle",
"Rigsuit",
"Phazon",
//"Gopher", // VOREStation Add //CHOMPedit commented micromech stuff, because fuck this trash
//"Polecat", // VOREStation Add //CHOMPedit commented micromech stuff, because fuck this trash
//"Weasel", // VOREStation Add //CHOMPedit commented micromech stuff, because fuck this trash
"Exosuit Equipment",
"Exosuit Internals",
"Exosuit Ammunition",
"Cyborg Upgrade Modules",
"Cybernetics",
"Implants",
"Control Interfaces",
"Other",
"Misc",
)
/obj/machinery/mecha_part_fabricator/Initialize()
. = ..()
// Go through all materials, and add them to the possible storage, but hide them unless we contain them.
for(var/Name in name_to_material)
if(Name in materials)
continue
materials[Name] = 0
default_apply_parts()
files = new /datum/research(src) //Setup the research data holder.
/obj/machinery/mecha_part_fabricator/dismantle()
for(var/f in materials)
eject_materials(f, -1)
..()
/obj/machinery/mecha_part_fabricator/RefreshParts()
res_max_amount = 0
for(var/obj/item/stock_parts/matter_bin/M in component_parts)
res_max_amount += M.rating * 100000 // 200k -> 600k
var/T = 0
for(var/obj/item/stock_parts/manipulator/M in component_parts)
T += M.rating
component_coeff = max(1 - (T - 1) / 4, 0.2) // 1 -> 0.2
for(var/obj/item/stock_parts/micro_laser/M in component_parts) // Not resetting T is intended; time_coeff is affected by both
T += M.rating
time_coeff = T / 2 // 1 -> 3
update_tgui_static_data(usr)
/**
* Generates an info list for a given part.
*
* Returns a list of part information.
* * D - Design datum to get information on.
* * categories - Boolean, whether or not to parse snowflake categories into the part information list.
*/
/obj/machinery/mecha_part_fabricator/proc/output_part_info(datum/design/D, var/categories = FALSE)
var/cost = list()
for(var/c in D.materials)
cost[c] = get_resource_cost_w_coeff(D, D.materials[c])
var/obj/built_item = D.build_path
var/list/category_override = null
var/list/sub_category = null
if(categories)
// Handle some special cases to build up sub-categories for the fab interface.
// Start with checking if this design builds a cyborg module.
if(built_item in typesof(/obj/item/borg/upgrade))
var/obj/item/borg/upgrade/U = built_item
var/module_types = initial(U.module_flags)
sub_category = list()
if(module_types)
if(module_types & BORG_UTILITY)
sub_category += "All Cyborgs - Utility"
if(module_types & BORG_BASIC)
sub_category += "All Cyborgs - Basic"
if(module_types & BORG_ADVANCED)
sub_category += "All Cyborgs - Advanced"
if(module_types & BORG_MODULE_SECURITY)
sub_category += "Security"
if(module_types & BORG_MODULE_MINER)
sub_category += "Mining"
if(module_types & BORG_MODULE_JANITOR)
sub_category += "Janitor"
if(module_types & BORG_MODULE_MEDICAL)
sub_category += "Medical"
if(module_types & BORG_MODULE_ENGINEERING)
sub_category += "Engineering"
if(module_types & BORG_MODULE_SCIENCE)
sub_category += "Science"
if(module_types & BORG_MODULE_SERVICE)
sub_category += "Service"
if(module_types & BORG_MODULE_CLERIC)
sub_category += "Cleric"
if(module_types & BORG_MODULE_COMBAT)
sub_category += "Combat"
if(module_types & BORG_MODULE_EXPLO)
sub_category += "Exploration"
else
sub_category += "This shouldn't be here, bother a dev!"
// Else check if this design builds a piece of exosuit equipment.
else if(built_item in typesof(/obj/item/mecha_parts/mecha_equipment))
var/obj/item/mecha_parts/mecha_equipment/E = built_item
var/mech_types = initial(E.mech_flags)
sub_category = "Equipment"
if(mech_types)
category_override = list()
if(mech_types & EXOSUIT_MODULE_RIPLEY)
category_override += "Ripley"
if(mech_types & EXOSUIT_MODULE_ODYSSEUS)
category_override += "Odysseus"
if(mech_types & EXOSUIT_MODULE_GYGAX)
category_override += "Gygax"
if(mech_types & EXOSUIT_MODULE_DURAND)
category_override += "Durand"
if(mech_types & EXOSUIT_MODULE_PHAZON)
category_override += "Phazon"
var/list/part = list(
"name" = D.name,
"desc" = initial(built_item.desc),
"printTime" = get_construction_time_w_coeff(initial(D.time))/10,
"cost" = cost,
"id" = D.id,
"subCategory" = sub_category,
"categoryOverride" = category_override,
"searchMeta" = D.search_metadata
)
return part
/**
* Generates a list of resources / materials available to this Exosuit Fab
*
* Returns null if there is no material container available.
* List format is list(material_name = list(amount = ..., ref = ..., etc.))
*/
/obj/machinery/mecha_part_fabricator/proc/output_available_resources()
var/list/material_data = list()
for(var/mat_id in materials)
var/amount = materials[mat_id]
var/list/material_info = list(
"name" = mat_id,
"amount" = amount,
"sheets" = round(amount / SHEET_MATERIAL_AMOUNT),
"removable" = amount >= SHEET_MATERIAL_AMOUNT
)
material_data += list(material_info)
return material_data
/**
* Intended to be called when an item starts printing.
*
* Adds the overlay to show the fab working and sets active power usage settings.
*/
/obj/machinery/mecha_part_fabricator/proc/on_start_printing()
add_overlay("[icon_state]-active")
use_power = USE_POWER_ACTIVE
/**
* Intended to be called when the exofab has stopped working and is no longer printing items.
*
* Removes the overlay to show the fab working and sets idle power usage settings. Additionally resets the description and turns off queue processing.
*/
/obj/machinery/mecha_part_fabricator/proc/on_finish_printing()
cut_overlay("[icon_state]-active")
use_power = USE_POWER_IDLE
desc = initial(desc)
process_queue = FALSE
/**
* Calculates resource/material costs for printing an item based on the machine's resource coefficient.
*
* Returns a list of k,v resources with their amounts.
* * D - Design datum to calculate the modified resource cost of.
*/
/obj/machinery/mecha_part_fabricator/proc/get_resources_w_coeff(datum/design/D)
var/list/resources = list()
for(var/mat_id in D.materials)
resources[mat_id] = get_resource_cost_w_coeff(D, D.materials[mat_id])
return resources
/**
* Checks if the Exofab has enough resources to print a given item.
*
* Returns FALSE if the design has no reagents used in its construction (?) or if there are insufficient resources.
* Returns TRUE if there are sufficient resources to print the item.
* * D - Design datum to calculate the modified resource cost of.
*/
/obj/machinery/mecha_part_fabricator/proc/check_resources(datum/design/D)
if(length(D.chemicals)) // No reagents storage - no reagent designs.
return FALSE
. = TRUE
var/list/coeff_required = get_resources_w_coeff(D)
for(var/mat_id in coeff_required)
if(materials[mat_id] < coeff_required[mat_id])
return FALSE
/**
* Attempts to build the next item in the build queue.
*
* Returns FALSE if either there are no more parts to build or the next part is not buildable.
* Returns TRUE if the next part has started building.
* * verbose - Whether the machine should use say() procs. Set to FALSE to disable the machine saying reasons for failure to build.
*/
/obj/machinery/mecha_part_fabricator/proc/build_next_in_queue(verbose = TRUE)
if(!length(queue))
return FALSE
var/datum/design/D = queue[1]
if(build_part(D, verbose))
remove_from_queue(1)
return TRUE
return FALSE
/**
* Starts the build process for a given design datum.
*
* Returns FALSE if the procedure fails. Returns TRUE when being_built is set.
* Uses materials.
* * D - Design datum to attempt to print.
* * verbose - Whether the machine should use say() procs. Set to FALSE to disable the machine saying reasons for failure to build.
*/
/obj/machinery/mecha_part_fabricator/proc/build_part(datum/design/D, verbose = TRUE)
if(!D)
return FALSE
if(!check_resources(D))
if(verbose)
atom_say("Not enough resources. Processing stopped.")
return FALSE
build_materials = get_resources_w_coeff(D)
for(var/mat_id in build_materials)
materials[mat_id] -= build_materials[mat_id]
being_built = D
build_finish = world.time + get_construction_time_w_coeff(initial(D.time))
build_start = world.time
desc = "It's building \a [D.name]."
return TRUE
/obj/machinery/mecha_part_fabricator/process()
..()
// If there's a stored part to dispense due to an obstruction, try to dispense it.
if(stored_part)
var/turf/exit = get_step(src,(dir))
if(exit.density)
return TRUE
atom_say("Obstruction cleared. \The [stored_part] is complete.")
stored_part.forceMove(exit)
stored_part = null
// If there's nothing being built, try to build something
if(!being_built)
// If we're not processing the queue anymore or there's nothing to build, end processing.
if(!process_queue || !build_next_in_queue())
on_finish_printing()
return PROCESS_KILL
on_start_printing()
// If there's an item being built, check if it is complete.
if(being_built && (build_finish < world.time))
// Then attempt to dispense it and if appropriate build the next item.
dispense_built_part(being_built)
if(process_queue)
build_next_in_queue(FALSE)
return TRUE
/**
* Dispenses a part to the tile infront of the Exosuit Fab.
*
* Returns FALSE is the machine cannot dispense the part on the appropriate turf.
* Return TRUE if the part was successfully dispensed.
* * D - Design datum to attempt to dispense.
*/
/obj/machinery/mecha_part_fabricator/proc/dispense_built_part(datum/design/D)
var/obj/item/I = D.Fabricate(src, src)
// I.material_flags |= MATERIAL_NO_EFFECTS //Find a better way to do this.
// I.set_custom_materials(build_materials)
being_built = null
var/turf/exit = get_step(src,(dir))
if(exit.density)
atom_say("Error! Part outlet is obstructed.")
desc = "It's trying to dispense \a [D.name], but the part outlet is obstructed."
stored_part = I
return FALSE
atom_say("\The [I] is complete.")
I.forceMove(exit)
return I
/**
* Adds a list of datum designs to the build queue.
*
* Will only add designs that are in this machine's stored techweb.
* Does final checks for datum IDs and makes sure this machine can build the designs.
* * part_list - List of datum design ids for designs to add to the queue.
*/
/obj/machinery/mecha_part_fabricator/proc/add_part_set_to_queue(list/part_list)
for(var/datum/design/D in files.known_designs)
if((D.build_type & valid_buildtype) && (D.id in part_list))
add_to_queue(D)
/**
* Adds a datum design to the build queue.
*
* Returns TRUE if successful and FALSE if the design was not added to the queue.
* * D - Datum design to add to the queue.
*/
/obj/machinery/mecha_part_fabricator/proc/add_to_queue(datum/design/D)
if(!istype(queue))
queue = list()
if(D)
queue[++queue.len] = D
return TRUE
return FALSE
/**
* Removes datum design from the build queue based on index.
*
* Returns TRUE if successful and FALSE if a design was not removed from the queue.
* * index - Index in the build queue of the element to remove.
*/
/obj/machinery/mecha_part_fabricator/proc/remove_from_queue(index)
if(!isnum(index) || !ISINTEGER(index) || !istype(queue) || (index<1 || index>length(queue)))
return FALSE
queue.Cut(index,++index)
return TRUE
/**
* Generates a list of parts formatted for tgui based on the current build queue.
*
* Returns a formatted list of lists containing formatted part information for every part in the build queue.
*/
/obj/machinery/mecha_part_fabricator/proc/list_queue()
if(!istype(queue) || !length(queue))
return null
var/list/queued_parts = list()
for(var/datum/design/D in queue)
var/list/part = output_part_info(D)
queued_parts += list(part)
return queued_parts
/obj/machinery/mecha_part_fabricator/proc/sync()
for(var/obj/machinery/computer/rdconsole/RDC in get_area_all_atoms(get_area(src)))
if(!RDC.sync)
continue
for(var/datum/tech/T in RDC.files.known_tech)
files.AddTech2Known(T)
for(var/datum/design/D in RDC.files.known_designs)
files.AddDesign2Known(D)
files.RefreshResearch()
update_tgui_static_data(usr)
atom_say("Successfully synchronized with R&D server.")
return
atom_say("Unable to connect to local R&D server.")
return
/**
* Calculates the coefficient-modified resource cost of a single material component of a design's recipe.
*
* Returns coefficient-modified resource cost for the given material component.
* * D - Design datum to pull the resource cost from.
* * resource - Material datum reference to the resource to calculate the cost of.
* * roundto - Rounding value for round() proc
*/
/obj/machinery/mecha_part_fabricator/proc/get_resource_cost_w_coeff(datum/design/D, var/amt, roundto = 1)
return round(amt * component_coeff, roundto)
/**
* Calculates the coefficient-modified build time of a design.
*
* Returns coefficient-modified build time of a given design.
* * D - Design datum to calculate the modified build time of.
* * roundto - Rounding value for round() proc
*/
/obj/machinery/mecha_part_fabricator/proc/get_construction_time_w_coeff(construction_time, roundto = 1) //aran
return round(construction_time * time_coeff, roundto)
/obj/machinery/mecha_part_fabricator/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/sheetmaterials)
)
/obj/machinery/mecha_part_fabricator/attack_hand(var/mob/user)
if(..())
return
if(!allowed(user))
to_chat(user, span_warning("\The [src] rejects your use due to lack of access!"))
return
tgui_interact(user)
/obj/machinery/mecha_part_fabricator/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ExosuitFabricator", name)
ui.open()
/obj/machinery/mecha_part_fabricator/tgui_static_data(mob/user)
var/list/data = list()
var/list/final_sets = list()
var/list/buildable_parts = list()
for(var/part_set in part_sets)
final_sets += part_set
for(var/datum/design/D in files.known_designs)
if((D.build_type & valid_buildtype) && D.id != "id") // bugfix for weird null entries
// This is for us.
var/list/part = output_part_info(D, TRUE)
if(part["categoryOverride"])
for(var/cat in part["categoryOverride"])
buildable_parts[cat] += list(part)
if(!(cat in part_sets))
final_sets += cat
continue
for(var/cat in part_sets)
// Find all matching categories.
if(!(cat in D.category))
continue
buildable_parts[cat] += list(part)
data["partSets"] = final_sets
data["buildableParts"] = buildable_parts
return data
/obj/machinery/mecha_part_fabricator/tgui_data(mob/user)
var/list/data = list()
data["materials"] = output_available_resources()
if(being_built)
var/list/part = list(
"name" = being_built.name,
"duration" = build_finish - world.time,
"printTime" = get_construction_time_w_coeff(initial(being_built.time))
)
data["buildingPart"] = part
else
data["buildingPart"] = null
data["queue"] = list_queue()
if(stored_part)
data["storedPart"] = stored_part.name
else
data["storedPart"] = null
data["isProcessingQueue"] = process_queue
return data
/obj/machinery/mecha_part_fabricator/tgui_act(action, list/params, datum/tgui/ui)
if(..())
return TRUE
. = TRUE
add_fingerprint(ui.user)
ui.user.set_machine(src)
switch(action)
if("sync_rnd")
// Sync with R&D Servers
sync()
return
if("add_queue_set")
// Add all parts of a set to queue
var/part_list = params["part_list"]
add_part_set_to_queue(part_list)
return
if("add_queue_part")
// Add a specific part to queue
var/T = params["id"]
for(var/datum/design/D in files.known_designs)
if((D.build_type & valid_buildtype) && (D.id == T))
add_to_queue(D)
break
return
if("del_queue_part")
// Delete a specific from from the queue
var/index = text2num(params["index"])
remove_from_queue(index)
return
if("clear_queue")
// Delete everything from queue
queue.Cut()
return
if("build_queue")
// Build everything in queue
if(process_queue)
return
process_queue = TRUE
if(!being_built)
START_PROCESSING(SSobj, src)
return
if("stop_queue")
// Pause queue building. Also known as stop.
process_queue = FALSE
return
if("build_part")
// Build a single part
if(being_built || process_queue)
return
var/id = params["id"]
var/datum/design/D = null
for(var/datum/design/D_new in files.known_designs)
if((D_new.build_type == valid_buildtype) && (D_new.id == id))
D = D_new
break
if(!D)
return
if(build_part(D))
on_start_printing()
START_PROCESSING(SSobj, src)
return
if("move_queue_part")
// Moves a part up or down in the queue.
var/index = text2num(params["index"])
var/new_index = index + text2num(params["newindex"])
if(isnum(index) && isnum(new_index) && ISINTEGER(index) && ISINTEGER(new_index))
if(ISINRANGE(new_index,1,length(queue)))
queue.Swap(index,new_index)
return
if("remove_mat")
// Remove a material from the fab
var/mat_id = params["id"]
var/amount = text2num(params["amount"])
eject_materials(mat_id, amount)
return
return FALSE
/obj/machinery/mecha_part_fabricator/attackby(var/obj/item/I, var/mob/user)
if(being_built)
to_chat(user, span_notice("\The [src] is busy. Please wait for completion of previous operation."))
return 1
if(default_deconstruction_screwdriver(user, I))
return
if(default_deconstruction_crowbar(user, I))
return
if(default_part_replacement(user, I))
return
if(istype(I,/obj/item/stack/material))
var/obj/item/stack/material/S = I
if(!(S.material.name in materials))
to_chat(user, span_warning("The [src] doesn't accept [S.material]!"))
return
var/sname = "[S.name]"
var/amnt = S.perunit
if(materials[S.material.name] + amnt <= res_max_amount)
if(S && S.get_amount() >= 1)
var/count = 0
flick("[loading_icon_state]", src)
// yess hacky but whatever //even more hacky now, but at least it works
if(loading_icon_state == "mechfab-idle")
flick("mechfab-load-metal", src)
while(materials[S.material.name] + amnt <= res_max_amount && S.get_amount() >= 1)
materials[S.material.name] += amnt
S.use(1)
count++
to_chat(user, "You insert [count] [sname] into the fabricator.")
else
to_chat(user, "The fabricator cannot hold more [sname].")
return
..()
/obj/machinery/mecha_part_fabricator/emag_act(var/remaining_charges, var/mob/user)
switch(emagged)
if(0)
emagged = 0.5
visible_message("[icon2html(src,viewers(src))] [src] beeps: \"DB error \[Code 0x00F1\]\"")
sleep(10)
visible_message("[icon2html(src,viewers(src))] [src] beeps: \"Attempting auto-repair\"")
sleep(15)
visible_message("[icon2html(src,viewers(src))] [src] beeps: \"User DB corrupted \[Code 0x00FA\]. Truncating data structure...\"")
sleep(30)
visible_message("[icon2html(src,viewers(src))] [src] beeps: \"User DB truncated. Please contact your [using_map.company_name] system operator for future assistance.\"")
req_access = null
emagged = 1
return 1
if(0.5)
visible_message("[icon2html(src,viewers(src))] [src] beeps: \"DB not responding \[Code 0x0003\]...\"")
if(1)
visible_message("[icon2html(src,viewers(src))] [src] beeps: \"No records in User DB\"")
/obj/machinery/mecha_part_fabricator/proc/eject_materials(var/material, var/amount) // 0 amount = 0 means ejecting a full stack; -1 means eject everything
var/recursive = amount == -1 ? TRUE : FALSE
var/matstring = lowertext(material)
// 0 or null, nothing to eject
if(!materials[matstring])
return
// Problem, fix problem and abort
if(materials[matstring] < 0)
warning("[src] tried to eject material '[material]', which it has 'materials[matstring]' of!")
materials[matstring] = 0
return
// Find the material datum for our material
var/datum/material/M = get_material_by_name(matstring)
if(!M)
warning("[src] tried to eject material '[matstring]', which didn't match any known material datum!")
return
// Find what type of sheets it makes
var/obj/item/stack/material/S = M.stack_type
if(!S)
warning("[src] tried to eject material '[matstring]', which didn't have a stack_type!")
return
// If we were passed -1, then it's recursive ejection and we should eject all we can
if(amount <= 0)
amount = initial(S.max_amount)
// Smaller of what we have left, or the desired amount (note the amount is in sheets, but the array stores perunit values)
var/ejected = min(round(materials[matstring] / initial(S.perunit)), amount)
// Place a sheet
S = M.place_sheet(get_turf(src), ejected)
if(!istype(S))
warning("[src] tried to eject material '[material]', which didn't generate a proper stack when asked!")
return
// Reduce our amount stored
materials[matstring] -= ejected * S.perunit
// Recurse if we have enough left for more sheets
if(recursive && materials[matstring] >= S.perunit)
eject_materials(matstring, -1)