From 0771b1b3a7941bc626c59429f0abd7757f5087d4 Mon Sep 17 00:00:00 2001 From: jimmyl <70376633+mc-oofert@users.noreply.github.com> Date: Sun, 29 Sep 2024 14:58:27 +0200 Subject: [PATCH] adds some "factory" machines (#86063) ## About The Pull Request https://github.com/user-attachments/assets/4ceb4c0f-d5ef-4fc0-8436-d7eec5b6f396 https://github.com/user-attachments/assets/56ddd387-7376-4c35-a067-1adccbddeecd https://github.com/user-attachments/assets/dda6cc2b-614a-4adb-a8f4-2c03b51162e0 https://github.com/user-attachments/assets/fa7697fb-f484-48a0-bb85-ee0c2f4867e2 https://github.com/user-attachments/assets/02de4b24-2fa0-4a1e-b147-df9500109b3c https://github.com/user-attachments/assets/b56c03ab-49c9-487f-a99f-fcba5ce038ac https://github.com/user-attachments/assets/52bae5a4-68b0-4f25-99c1-1b677b99790a i didnt feel like recording the lathe and crafter for a suitable file size again but essentially the crafter crafts and the lathe lathes all machines but the router and sorter are cable powered (suitable on lavaland) theyre researched roundstart they can receive any resource that bumps into it if that resource is on the conveyor ## Why It's Good For The Game more fun engineering stuff and perhaps mining given these are more efficient but require effort to set up https://hackmd.io/@jimmyl/S1dZRZosC ## Changelog :cl: add: added the manufacturing smelter,router,sorter,crafter,lathe,crusher,unloader /:cl: --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- .../subsystem/processing/manufacturing.dm | 4 + code/datums/components/crafting/crafting.dm | 31 +++- code/datums/components/crafting/structures.dm | 11 ++ .../machines/machine_circuitboards.dm | 62 ++++++++ code/game/turfs/turf.dm | 2 +- code/modules/manufactorio/_manufacturing.dm | 123 +++++++++++++++ code/modules/manufactorio/machines/crafter.dm | 139 ++++++++++++++++ code/modules/manufactorio/machines/crusher.dm | 83 ++++++++++ code/modules/manufactorio/machines/debug.dm | 18 +++ code/modules/manufactorio/machines/lathe.dm | 145 +++++++++++++++++ code/modules/manufactorio/machines/router.dm | 66 ++++++++ code/modules/manufactorio/machines/smelter.dm | 59 +++++++ code/modules/manufactorio/machines/sorter.dm | 149 ++++++++++++++++++ .../manufactorio/machines/sorter_filters.dm | 120 ++++++++++++++ .../manufactorio/machines/storagebox.dm | 46 ++++++ .../modules/manufactorio/machines/unloader.dm | 78 +++++++++ code/modules/recycling/conveyor.dm | 67 +++++++- .../research/designs/machine_designs.dm | 70 ++++++++ .../research/techweb/nodes/engi_nodes.dm | 7 + icons/obj/machines/manufactorio.dmi | Bin 0 -> 9114 bytes tgstation.dme | 12 ++ .../tgui/interfaces/ManufacturingSorter.tsx | 113 +++++++++++++ 22 files changed, 1395 insertions(+), 10 deletions(-) create mode 100644 code/controllers/subsystem/processing/manufacturing.dm create mode 100644 code/modules/manufactorio/_manufacturing.dm create mode 100644 code/modules/manufactorio/machines/crafter.dm create mode 100644 code/modules/manufactorio/machines/crusher.dm create mode 100644 code/modules/manufactorio/machines/debug.dm create mode 100644 code/modules/manufactorio/machines/lathe.dm create mode 100644 code/modules/manufactorio/machines/router.dm create mode 100644 code/modules/manufactorio/machines/smelter.dm create mode 100644 code/modules/manufactorio/machines/sorter.dm create mode 100644 code/modules/manufactorio/machines/sorter_filters.dm create mode 100644 code/modules/manufactorio/machines/storagebox.dm create mode 100644 code/modules/manufactorio/machines/unloader.dm create mode 100644 icons/obj/machines/manufactorio.dmi create mode 100644 tgui/packages/tgui/interfaces/ManufacturingSorter.tsx diff --git a/code/controllers/subsystem/processing/manufacturing.dm b/code/controllers/subsystem/processing/manufacturing.dm new file mode 100644 index 00000000000..8bc9c6af5d5 --- /dev/null +++ b/code/controllers/subsystem/processing/manufacturing.dm @@ -0,0 +1,4 @@ +PROCESSING_SUBSYSTEM_DEF(manufacturing) + name = "Manufacturing Processing" + wait = 1 SECONDS + stat_tag = "MN" diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm index 54f77d4579e..bf13b7cd5ba 100644 --- a/code/datums/components/crafting/crafting.dm +++ b/code/datums/components/crafting/crafting.dm @@ -21,6 +21,8 @@ var/display_craftable_only = FALSE var/display_compact = FALSE var/forced_mode = FALSE + /// crafting flags we ignore when considering a recipe + var/ignored_flags = NONE /* This is what procs do: get_environment - gets a list of things accessable for crafting by user @@ -205,16 +207,16 @@ if(!check_tools(crafter, recipe, contents)) return ", missing tool." + var/considered_flags = recipe.crafting_flags & ~(ignored_flags) - - if((recipe.crafting_flags & CRAFT_ONE_PER_TURF) && (locate(recipe.result) in dest_turf)) + if((considered_flags & CRAFT_ONE_PER_TURF) && (locate(recipe.result) in dest_turf)) return ", already one here!" - if(recipe.crafting_flags & CRAFT_CHECK_DIRECTION) - if(!valid_build_direction(dest_turf, crafter.dir, is_fulltile = (recipe.crafting_flags & CRAFT_IS_FULLTILE))) + if(considered_flags & CRAFT_CHECK_DIRECTION) + if(!valid_build_direction(dest_turf, crafter.dir, is_fulltile = (considered_flags & CRAFT_IS_FULLTILE))) return ", won't fit here!" - if(recipe.crafting_flags & CRAFT_ON_SOLID_GROUND) + if(considered_flags & CRAFT_ON_SOLID_GROUND) if(isclosedturf(dest_turf)) return ", cannot be made on a wall!" @@ -222,7 +224,7 @@ if(!locate(/obj/structure/thermoplastic) in dest_turf) // for tram construction return ", must be made on solid ground!" - if(recipe.crafting_flags & CRAFT_CHECK_DENSITY) + if(considered_flags & CRAFT_CHECK_DENSITY) for(var/obj/object in dest_turf) if(object.density && !(object.obj_flags & IGNORE_DENSITY) || object.obj_flags & BLOCKS_CONSTRUCTION) return ", something is in the way!" @@ -703,3 +705,20 @@ if(recipe == potential_recipe) return TRUE return FALSE + +/datum/component/personal_crafting/machine + ignored_flags = CRAFT_CHECK_DENSITY + +/datum/component/personal_crafting/machine/get_environment(atom/crafter, list/blacklist = null, radius_range = 1) + . = list() + for(var/atom/movable/content in crafter.contents) + if((content.flags_1 & HOLOGRAM_1) || (blacklist && (content.type in blacklist))) + continue + if(isitem(content)) + var/obj/item/item = content + if(item.item_flags & ABSTRACT) //let's not tempt fate, shall we? + continue + . += content + +/datum/component/personal_crafting/machine/check_tools(atom/source, datum/crafting_recipe/recipe, list/surroundings) + return TRUE diff --git a/code/datums/components/crafting/structures.dm b/code/datums/components/crafting/structures.dm index c4a9b48ec36..090ec31ce22 100644 --- a/code/datums/components/crafting/structures.dm +++ b/code/datums/components/crafting/structures.dm @@ -74,3 +74,14 @@ ) category = CAT_STRUCTURE crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED + +/datum/crafting_recipe/manucrate + name = "Manufacturing Storage Unit" + result = /obj/machinery/power/manufacturing/storagebox + tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WELDER) + time = 6 SECONDS + reqs = list( + /obj/item/stack/sheet/iron = 10, + ) + category = CAT_STRUCTURE + crafting_flags = CRAFT_CHECK_DENSITY diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index 80c71e2b859..4db8ffd123f 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -1715,3 +1715,65 @@ req_components = list( /datum/stock_part/servo = 1, ) + +/obj/item/circuitboard/machine/manucrafter + name = /obj/machinery/power/manufacturing/crafter::name + greyscale_colors = CIRCUIT_COLOR_ENGINEERING + build_path = /obj/machinery/power/manufacturing/crafter + req_components = list( + /obj/item/stack/sheet/iron = 5, + /datum/stock_part/servo = 1, + ) + +/obj/item/circuitboard/machine/manulathe + name = /obj/machinery/power/manufacturing/lathe::name + greyscale_colors = CIRCUIT_COLOR_ENGINEERING + build_path = /obj/machinery/power/manufacturing/lathe + req_components = list( + /obj/item/stack/sheet/iron = 5, + /datum/stock_part/servo = 1, + ) + +/obj/item/circuitboard/machine/manucrusher + name = /obj/machinery/power/manufacturing/crusher::name + greyscale_colors = CIRCUIT_COLOR_ENGINEERING + build_path = /obj/machinery/power/manufacturing/crusher + req_components = list( + /obj/item/stack/sheet/iron = 5, + /datum/stock_part/servo = 1, + ) + +/obj/item/circuitboard/machine/manuunloader + name = /obj/machinery/power/manufacturing/unloader::name + greyscale_colors = CIRCUIT_COLOR_ENGINEERING + build_path = /obj/machinery/power/manufacturing/unloader + req_components = list( + /obj/item/stack/sheet/iron = 5, + /datum/stock_part/servo = 1, + ) + +/obj/item/circuitboard/machine/manusorter + name = /obj/machinery/power/manufacturing/sorter::name + greyscale_colors = CIRCUIT_COLOR_ENGINEERING + build_path = /obj/machinery/power/manufacturing/sorter + req_components = list( + /obj/item/stack/sheet/iron = 5, + /datum/stock_part/scanning_module = 1, + ) + +/obj/item/circuitboard/machine/manusmelter + name = /obj/machinery/power/manufacturing/smelter::name + greyscale_colors = CIRCUIT_COLOR_ENGINEERING + build_path = /obj/machinery/power/manufacturing/smelter + req_components = list( + /obj/item/stack/sheet/iron = 5, + /datum/stock_part/micro_laser = 1, + ) + +/obj/item/circuitboard/machine/manurouter + name = /obj/machinery/power/manufacturing/router::name + greyscale_colors = CIRCUIT_COLOR_ENGINEERING + build_path = /obj/machinery/power/manufacturing/router + req_components = list( + /obj/item/stack/sheet/iron = 5, + ) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 38976bcfdd6..5c2dcbe34d6 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -270,7 +270,7 @@ GLOBAL_LIST_EMPTY(station_turfs) * * type_list - are we checking for types of atoms to ignore and not physical atoms */ /turf/proc/is_blocked_turf(exclude_mobs = FALSE, source_atom = null, list/ignore_atoms, type_list = FALSE) - if(density) + if((!isnull(source_atom) && !CanPass(source_atom, get_dir(src, source_atom))) || density) return TRUE for(var/atom/movable/movable_content as anything in contents) diff --git a/code/modules/manufactorio/_manufacturing.dm b/code/modules/manufactorio/_manufacturing.dm new file mode 100644 index 00000000000..236c05dd869 --- /dev/null +++ b/code/modules/manufactorio/_manufacturing.dm @@ -0,0 +1,123 @@ +#define MANUFACTURING_FAIL_FULL -1 +#define MANUFACTURING_FAIL 0 +#define MANUFACTURING_SUCCESS 1 + +#define POCKET_INPUT "Input" +#define POCKET_OUTPUT "Output" + +#define MANUFACTURING_TURF_LAG_LIMIT 10 // max items on a turf before we consider it full + +/obj/machinery/power/manufacturing + icon = 'icons/obj/machines/manufactorio.dmi' + name = "base manufacture receiving type" + desc = "this shouldnt exist" + density = TRUE + /// Do we add the simple_rotation component and a text that we are powered by cable? Also allows unwrenching + var/may_be_moved = TRUE + /// Allow taking in mobs from conveyors? + var/allow_mob_bump_intake = FALSE + +/obj/machinery/power/manufacturing/Initialize(mapload) + . = ..() + if(may_be_moved) + AddComponent(/datum/component/simple_rotation) + if(anchored) + connect_to_network() + +/obj/machinery/power/manufacturing/examine(mob/user) + . = ..() + if(may_be_moved) + . += "It receives power via cable, but certain buildings do not need power." + . += length(contents - circuit) ? "It contains:" : "Its empty." + for(var/atom/movable/thing as anything in contents - circuit) + var/text = thing.name + var/obj/item/stack/possible_stack = thing + if(istype(possible_stack)) + text = "[possible_stack.amount] [text]" + . += text + + +/obj/machinery/power/manufacturing/Bumped(atom/movable/bumped_atom) //attempt to put in whatever is pushed into us via conveyor + . = ..() + if((!allow_mob_bump_intake && ismob(bumped_atom)) || !anchored) //only uncomment if youre brave + return + var/conveyor = locate(/obj/machinery/conveyor) in bumped_atom.loc + if(isnull(conveyor)) + return + receive_resource(bumped_atom, bumped_atom.loc, get_dir(src, bumped_atom)) + +/obj/machinery/power/manufacturing/wrench_act(mob/living/user, obj/item/tool) + . = ..() + if(!may_be_moved) + return + default_unfasten_wrench(user, tool) + if(anchored) + connect_to_network() + else + disconnect_from_network() + return ITEM_INTERACT_SUCCESS + +/obj/machinery/power/manufacturing/screwdriver_act(mob/living/user, obj/item/tool) + if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool)) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING + +/obj/machinery/power/manufacturing/crowbar_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/power/manufacturing/proc/generate_io_overlays(direction, color, offsets_override) + var/list/dir_offset + if(islist(offsets_override)) + dir_offset = offsets_override + else + dir_offset = dir2offset(direction) + dir_offset[1] *= 32 + dir_offset[2] *= 32 + var/image/nonemissive = image(icon='icons/obj/doors/airlocks/station/overlays.dmi', icon_state="unres_[direction]") + nonemissive.pixel_x = dir_offset[1] + nonemissive.pixel_y = dir_offset[2] + nonemissive.color = color + var/mutable_appearance/emissive = emissive_appearance(nonemissive.icon, nonemissive.icon_state, offset_spokesman = src, alpha = nonemissive.alpha) + emissive.pixel_y = nonemissive.pixel_y + emissive.pixel_x = nonemissive.pixel_x + return list(nonemissive, emissive) + +/// Returns whatever object it may output, or null if it cant do that +/obj/machinery/power/manufacturing/proc/request_resource() + + +/obj/machinery/power/manufacturing/proc/receive_resource(atom/movable/receiving, atom/from, receive_dir) + CRASH("Unimplemented!") //check can_receive_resource here + +//use dir please +/obj/machinery/power/manufacturing/proc/send_resource(atom/movable/sending, atom/what_or_dir) + if(isobj(what_or_dir)) + var/obj/machinery/power/manufacturing/target = what_or_dir + return target.receive_resource(sending, src, get_step(src, what_or_dir)) + var/turf/next_turf = isturf(what_or_dir) ? what_or_dir : get_step(src, what_or_dir) + var/obj/machinery/power/manufacturing/manufactury = locate(/obj/machinery/power/manufacturing) in next_turf + if(!isnull(manufactury)) + if(!manufactury.anchored) + return MANUFACTURING_FAIL + return manufactury.receive_resource(sending, src, isturf(what_or_dir) ? get_dir(src, what_or_dir) : what_or_dir) + if(next_turf.is_blocked_turf(exclude_mobs = TRUE, source_atom = sending)) + return MANUFACTURING_FAIL + if(length(next_turf.contents) >= MANUFACTURING_TURF_LAG_LIMIT) + return MANUFACTURING_FAIL_FULL + if(isnull(sending)) + return MANUFACTURING_SUCCESS // for the sake of being used as a check + if(isnull(sending.loc) || !sending.Move(next_turf, get_dir(src, next_turf))) + sending.forceMove(next_turf) + return MANUFACTURING_SUCCESS + +/// Checks if this stack (if not a stack does not do anything) can merge WITHOUT creating two stacks in contents +/obj/machinery/power/manufacturing/proc/may_merge_in_contents(obj/item/stack/stack) + if(!istype(stack)) + return + for(var/obj/item/stack/other in contents - circuit) + if(!other.can_merge(stack)) + continue + if(other.amount + stack.amount <= other.max_amount) + return other diff --git a/code/modules/manufactorio/machines/crafter.dm b/code/modules/manufactorio/machines/crafter.dm new file mode 100644 index 00000000000..ee794d29301 --- /dev/null +++ b/code/modules/manufactorio/machines/crafter.dm @@ -0,0 +1,139 @@ +/obj/machinery/power/manufacturing/crafter + name = "manufacturing assembling machine" + desc = "Assembles (crafts) the set recipe until it runs out of resources. Inputs irrelevant to the recipe are ignored." + icon_state = "crafter" + circuit = /obj/item/circuitboard/machine/manucrafter + /// power used per process() spent crafting + var/power_cost = 5 KILO WATTS + /// our output, if the way out was blocked is held here + var/atom/movable/withheld + /// current recipe + var/datum/crafting_recipe/recipe + /// crafting component + var/datum/component/personal_crafting/machine/craftsman + /// current timer for our crafting + var/craft_timer + /// do we use cooking recipes instead + var/cooking = FALSE + +/obj/machinery/power/manufacturing/crafter/Initialize(mapload) + . = ..() + craftsman = AddComponent(/datum/component/personal_crafting/machine) + +/obj/machinery/power/manufacturing/crafter/examine(mob/user) + . = ..() + . += span_notice("It is currently manufacturing [isnull(recipe) ? "nothing. Use a multitool to set it" : recipe.name].") + if(isnull(recipe)) + return + . += span_notice("It needs:") + for(var/valid_type in recipe.reqs) + // Check if they're datums, specifically reagents. + var/datum/reagent/reagent_ingredient = valid_type + if(istype(reagent_ingredient)) + var/amount = recipe.reqs[reagent_ingredient] + . += "[amount] unit[amount > 1 ? "s" : ""] of [initial(reagent_ingredient.name)]" + + var/atom/ingredient = valid_type + var/amount = recipe.reqs[ingredient] + + . += "[amount > 1 ? ("[amount]" + " of") : "a"] [initial(ingredient.name)]" + +/obj/machinery/power/manufacturing/crafter/update_overlays() + . = ..() + . += generate_io_overlays(dir, COLOR_ORANGE) + for(var/target_dir in GLOB.cardinals - dir) + . += generate_io_overlays(target_dir, COLOR_MODERATE_BLUE) + +/obj/machinery/power/manufacturing/crafter/proc/valid_for_recipe(obj/item/checking) + . = FALSE + for(var/requirement_path in recipe.reqs) + if(!ispath(checking.type, requirement_path) || recipe.blacklist.Find(checking.type)) + continue + return TRUE + +/obj/machinery/power/manufacturing/crafter/proc/contains_type(path) + . = FALSE + for(var/content in contents - circuit) + if(!istype(content, path)) + continue + return TRUE + +/obj/machinery/power/manufacturing/crafter/receive_resource(obj/receiving, atom/from, receive_dir) + if(isnull(recipe) || !isitem(receiving) || surplus() < power_cost) + return MANUFACTURING_FAIL + if(receive_dir == dir || !valid_for_recipe(receiving)) + return MANUFACTURING_FAIL + if(!may_merge_in_contents(receiving) && contains_type(receiving.type)) + return MANUFACTURING_FAIL_FULL + receiving.Move(src, get_dir(receiving, src)) + START_PROCESSING(SSmanufacturing, src) + return MANUFACTURING_SUCCESS + +/obj/machinery/power/manufacturing/crafter/multitool_act(mob/living/user, obj/item/tool) + . = NONE + var/list/unavailable = list() + for(var/datum/crafting_recipe/potential_recipe as anything in cooking ? GLOB.cooking_recipes : GLOB.crafting_recipes) + if(craftsman.is_recipe_available(potential_recipe, user)) + continue + var/obj/result = initial(potential_recipe.result) + if(istype(result) && initial(result.anchored)) + continue + unavailable += potential_recipe + var/result = tgui_input_list(usr, "Recipe", "Select Recipe", (cooking ? GLOB.cooking_recipes : GLOB.crafting_recipes) - unavailable) + if(isnull(result) || result == recipe || !user.can_perform_action(src)) + return ITEM_INTERACT_FAILURE + var/dump_target = get_step(src, get_dir(src, user)) + for(var/atom/movable/thing as anything in contents - circuit) + thing.Move(dump_target) + recipe = result + return ITEM_INTERACT_SUCCESS + +/obj/machinery/power/manufacturing/crafter/Exited(atom/movable/gone, direction) + . = ..() + if(gone == withheld) + withheld = null + +/obj/machinery/power/manufacturing/crafter/atom_destruction(damage_flag) + . = ..() + withheld?.Move(drop_location(src)) + +/obj/machinery/power/manufacturing/crafter/Destroy() + . = ..() + recipe = null + craftsman = null + QDEL_NULL(withheld) + +/obj/machinery/power/manufacturing/crafter/process(seconds_per_tick) + if(!isnull(withheld) && !send_resource(withheld, dir)) + return + if(!isnull(craft_timer)) + if(surplus() >= power_cost) + add_load() + else + deltimer(craft_timer) + craft_timer = null + say("Power failure!") + return + if(isnull(recipe) || !craftsman.check_contents(src, recipe, craftsman.get_surroundings(src))) + return + flick_overlay_view(mutable_appearance(icon, "crafter_printing"), recipe.time) + craft_timer = addtimer(CALLBACK(src, PROC_REF(craft), recipe), recipe.time, TIMER_STOPPABLE) + +/obj/machinery/power/manufacturing/crafter/proc/craft(datum/crafting_recipe/recipe) + if(QDELETED(src)) + return + craft_timer = null + var/atom/movable/result = craftsman.construct_item(src, recipe) + if(istype(result)) + if(isitem(result)) + result.pixel_x += rand(-4, 4) + result.pixel_y += rand(-4, 4) + result.Move(src) + send_resource(result, dir) + else + say(result) + +/obj/machinery/power/manufacturing/crafter/cooker + name = "manufacturing cooking machine" // maybe this shouldnt be available dont wanna make chef useless, though otherwise it would need a sprite + desc = "Cooks the set recipe until it runs out of resources. Inputs irrelevant to the recipe are ignored." + cooking = TRUE diff --git a/code/modules/manufactorio/machines/crusher.dm b/code/modules/manufactorio/machines/crusher.dm new file mode 100644 index 00000000000..f0f18c10ae8 --- /dev/null +++ b/code/modules/manufactorio/machines/crusher.dm @@ -0,0 +1,83 @@ +/obj/machinery/power/manufacturing/crusher //todo make it work for other stuff + name = "manufacturing crusher" + desc = "Crushes any item put into it, boulders and such. Materials below a sheet are stored in the machine." + icon_state = "crusher" + circuit = /obj/item/circuitboard/machine/manucrusher + /// power used to crush + var/crush_cost = 3 KILO WATTS + /// how much can we hold + var/capacity = 5 + /// withheld output because output is either blocked or full + var/atom/movable/withholding + /// list of held mats + var/list/obj/item/stack/held_mats = list() + +/obj/machinery/power/manufacturing/crusher/update_overlays() + . = ..() + . += generate_io_overlays(dir, COLOR_ORANGE) // OUT - stuff in it + . += generate_io_overlays(REVERSE_DIR(dir), COLOR_MODERATE_BLUE) // IN - to crush + +/obj/machinery/power/manufacturing/crusher/Destroy() + . = ..() + QDEL_NULL(withholding) + +/obj/machinery/power/manufacturing/crusher/atom_destruction(damage_flag) + withholding?.Move(drop_location()) + return ..() + +/obj/machinery/power/manufacturing/crusher/receive_resource(obj/receiving, atom/from, receive_dir) + if(istype(receiving, /obj/item/stack/ore) || receiving.resistance_flags & INDESTRUCTIBLE || !isitem(receiving) || surplus() < crush_cost || receive_dir != REVERSE_DIR(dir)) + return MANUFACTURING_FAIL + if(!may_merge_in_contents(receiving) && length(contents - circuit) >= capacity) + return MANUFACTURING_FAIL_FULL + receiving.Move(src, get_dir(receiving, src)) + START_PROCESSING(SSmanufacturing, src) + return MANUFACTURING_SUCCESS + +/obj/machinery/power/manufacturing/crusher/Exited(atom/movable/gone, direction) + . = ..() + if(gone == withholding) + withholding = null + +/obj/machinery/power/manufacturing/crusher/process(seconds_per_tick) //noot functional + if(!isnull(withholding) && !send_resource(withholding, dir)) + return + for(var/material in held_mats) + if(held_mats[material] >= 1) + var/new_amount = floor(held_mats[material]) + held_mats[material] -= new_amount + if(held_mats[material] <= 0) + held_mats -= material + withholding = new material(null, new_amount) + return + var/list/poor_saps = contents - circuit + if(!length(poor_saps)) + return PROCESS_KILL + if(surplus() < crush_cost) + return + var/obj/victim = poor_saps[length(poor_saps)] + if(istype(victim)) //todo handling for other things + if(!length(victim.custom_materials)) + add_load(crush_cost) + victim.atom_destruction() + for(var/obj/object in victim.contents+victim) + for(var/datum/material/possible_mat as anything in object.custom_materials) + var/quantity = object.custom_materials[possible_mat] + object.set_custom_materials(object.custom_materials.Copy() - possible_mat, 1) + var/type_to_use = istype(victim, /obj/item/boulder) ? possible_mat.ore_type : possible_mat.sheet_type + if(quantity < SHEET_MATERIAL_AMOUNT) + if(!(type_to_use in held_mats)) + held_mats[type_to_use] = quantity / SHEET_MATERIAL_AMOUNT + continue + held_mats[type_to_use] += quantity / SHEET_MATERIAL_AMOUNT + continue + var/obj/item/stack/sheet/new_item = new type_to_use(src, quantity / SHEET_MATERIAL_AMOUNT) + if(!send_resource(new_item, dir)) + withholding = new_item + return + else if(isliving(victim)) + var/mob/living/poor_sap = victim + poor_sap.adjustBruteLoss(95, TRUE) + if(!send_resource(poor_sap, dir)) + withholding = poor_sap + return diff --git a/code/modules/manufactorio/machines/debug.dm b/code/modules/manufactorio/machines/debug.dm new file mode 100644 index 00000000000..7c21cf4e989 --- /dev/null +++ b/code/modules/manufactorio/machines/debug.dm @@ -0,0 +1,18 @@ +/obj/loop_spawner + name = "testing loop spawner" + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "unloader" + anchored = TRUE + color = COLOR_PURPLE + /// directions we can output to right now + var/to_spawn = /obj/item/screwdriver + /// the subsystem to process us + var/subsystem_to_process_us = /datum/controller/subsystem/processing/obj + +/obj/loop_spawner/Initialize(mapload) + . = ..() + var/datum/controller/subsystem/processing/subsystem = locate(subsystem_to_process_us) in Master.subsystems + START_PROCESSING(subsystem, src) + +/obj/loop_spawner/process(seconds_per_tick) + new to_spawn(get_step(src, dir)) diff --git a/code/modules/manufactorio/machines/lathe.dm b/code/modules/manufactorio/machines/lathe.dm new file mode 100644 index 00000000000..2669e851b93 --- /dev/null +++ b/code/modules/manufactorio/machines/lathe.dm @@ -0,0 +1,145 @@ +/obj/machinery/power/manufacturing/lathe // this is a heavily gutted autolathe + name = "manufacturing lathe" + desc = "Lathes the set recipe until it runs out of resources. Only accepts sheets or other kinds of material stacks." + icon_state = "lathe" + circuit = /obj/item/circuitboard/machine/manulathe + /// power cost for lathing + var/power_cost = 5 KILO WATTS + /// design id we print + var/design_id + ///The container to hold materials + var/datum/component/material_container/materials + //looping sound for printing items + var/datum/looping_sound/lathe_print/print_sound + ///Designs related to the autolathe + var/datum/techweb/autounlocking/stored_research + /// timer id of printing + var/busy = FALSE + /// our output, if the way out was blocked is held here + var/atom/movable/withheld + +/obj/machinery/power/manufacturing/lathe/Initialize(mapload) + . = ..() + print_sound = new(src, FALSE) + materials = AddComponent( \ + /datum/component/material_container, \ + SSmaterials.materials_by_category[MAT_CATEGORY_ITEM_MATERIAL], \ + SHEET_MATERIAL_AMOUNT * MAX_STACK_SIZE * 2, \ + MATCONTAINER_EXAMINE|MATCONTAINER_NO_INSERT, \ + ) + if(!GLOB.autounlock_techwebs[/datum/techweb/autounlocking/autolathe]) + GLOB.autounlock_techwebs[/datum/techweb/autounlocking/autolathe] = new /datum/techweb/autounlocking/autolathe + stored_research = GLOB.autounlock_techwebs[/datum/techweb/autounlocking/autolathe] + +/obj/machinery/power/manufacturing/lathe/examine(mob/user) + . = ..() + var/datum/design/design + if(!isnull(design_id)) + design = SSresearch.techweb_design_by_id(design_id) + . += span_notice("It is set to print [!isnull(design) ? design.name : "nothing, set with a multitool"].") + if(isnull(design)) + return + . += span_notice("It needs:") + for(var/valid_type in design.materials) + var/atom/ingredient = valid_type + var/amount = design.materials[ingredient] / SHEET_MATERIAL_AMOUNT + + . += "[amount] sheets of [initial(ingredient.name)]" + +/obj/machinery/power/manufacturing/lathe/update_overlays() + . = ..() + . += generate_io_overlays(dir, COLOR_ORANGE) // OUT - stuff in it + . += generate_io_overlays(REVERSE_DIR(dir), COLOR_MODERATE_BLUE) // IN - to crush + +/obj/machinery/power/manufacturing/lathe/Destroy() + . = ..() + stored_research = null + QDEL_NULL(print_sound) + materials = null + QDEL_NULL(withheld) + +/obj/machinery/power/manufacturing/lathe/atom_destruction(damage_flag) + withheld?.Move(drop_location()) + return ..() + +/obj/machinery/power/manufacturing/lathe/receive_resource(atom/movable/receiving, atom/from, receive_dir) + if(!isstack(receiving) || receiving.resistance_flags & INDESTRUCTIBLE || receive_dir != REVERSE_DIR(dir)) + return MANUFACTURING_FAIL + materials.insert_item(receiving) + return MANUFACTURING_SUCCESS + +/obj/machinery/power/manufacturing/lathe/multitool_act(mob/living/user, obj/item/tool) + . = ..() + var/list/name_to_id = list() + for(var/id in stored_research.researched_designs) + var/datum/design/design = SSresearch.techweb_design_by_id(id) + name_to_id[design.name] = id + var/result = tgui_input_list(user, "Select Design", "Select Design", sort_list(name_to_id)) + if(isnull(result)) + return ITEM_INTERACT_FAILURE + design_id = name_to_id[result] + return ITEM_INTERACT_SUCCESS + +/obj/machinery/power/manufacturing/lathe/process() + if(!isnull(withheld) && !send_resource(withheld, dir)) + return + + var/datum/design/design = SSresearch.techweb_design_by_id(design_id) + if(isnull(design) || !(design.build_type & AUTOLATHE)) + return + if(surplus() < power_cost) + finalize_build() + return + //check for materials required. For custom material items decode their required materials + var/list/materials_needed = list() + for(var/material in design.materials) + var/amount_needed = design.materials[material] + if(istext(material)) // category + for(var/datum/material/valid_candidate as anything in SSmaterials.materials_by_category[material]) + if(materials.get_material_amount(valid_candidate) < amount_needed) + continue + material = valid_candidate + break + if(isnull(material)) + return + materials_needed[material] = amount_needed + + if(!materials.has_materials(materials_needed)) + return + + var/craft_time = (design.construction_time * design.lathe_time_factor) ** 0.8 + flick_overlay_view(mutable_appearance(icon, "crafter_printing"), craft_time) + print_sound.start() + add_load(power_cost) + busy = addtimer(CALLBACK(src, PROC_REF(do_make_item), design, materials_needed), craft_time, TIMER_UNIQUE | TIMER_STOPPABLE | TIMER_DELETE_ME) + +/obj/machinery/power/manufacturing/lathe/proc/do_make_item(datum/design/design, list/materials_needed) + finalize_build() + if(surplus() < power_cost) + return + + var/is_stack = ispath(design.build_path, /obj/item/stack) + if(!materials.has_materials(materials_needed)) + return + materials.use_materials(materials_needed) + + var/atom/movable/created + if(is_stack) + var/obj/item/stack/stack_item = initial(design.build_path) + created = new stack_item(null, 1) + else + created = new design.build_path(null) + split_materials_uniformly(materials_needed, target_object = created) + if(isitem(created)) + created.pixel_x = created.base_pixel_x + rand(-6, 6) + created.pixel_y = created.base_pixel_y + rand(-6, 6) + SSblackbox.record_feedback("nested tally", "lathe_printed_items", 1, list("[type]", "[created.type]")) + + if(!send_resource(created, dir)) + withheld = created + + +/obj/machinery/power/manufacturing/lathe/proc/finalize_build() + print_sound.stop() + deltimer(busy) + busy = null diff --git a/code/modules/manufactorio/machines/router.dm b/code/modules/manufactorio/machines/router.dm new file mode 100644 index 00000000000..7c57a930bd3 --- /dev/null +++ b/code/modules/manufactorio/machines/router.dm @@ -0,0 +1,66 @@ +/obj/machinery/power/manufacturing/router // Basically a splitter + name = "manufacturing router" + desc = "Distributes input to 3 output directions equally. Stacks are split, and you may toggle outputs with a multitool. May not receive from other routers." + allow_mob_bump_intake = TRUE + icon_state = "splitter" + circuit = /obj/item/circuitboard/machine/manurouter + /// outputs disabled with a multitool + var/list/disabled_dirs = list() + /// directions we can output to right now + var/list/directions + +/obj/machinery/power/manufacturing/router/Initialize(mapload) + . = ..() + directions = GLOB.cardinals.Copy() + +/obj/machinery/power/manufacturing/router/multitool_act(mob/living/user, obj/item/tool) + . = ..() + var/to_toggle = get_dir(src, user) + if(!(to_toggle in GLOB.cardinals)) + balloon_alert(user, "stand inline!") + return ITEM_INTERACT_FAILURE + if(to_toggle in disabled_dirs) + disabled_dirs -= to_toggle + else + disabled_dirs += to_toggle + update_appearance(UPDATE_OVERLAYS) + balloon_alert(user, "toggled output") + return ITEM_INTERACT_SUCCESS + +/obj/machinery/power/manufacturing/router/update_overlays() + . = ..() + for(var/direction in GLOB.cardinals) + var/variant + if(disabled_dirs.Find(direction)) + variant = "bl" + else + variant = (direction == dir) ? "in" : "out" + var/image/new_overlay = image(icon, "splitter_[variant]", layer = layer+0.001, dir = direction) + . += new_overlay + +/obj/machinery/power/manufacturing/router/receive_resource(obj/receiving, atom/from, receive_dir) + if(istype(from, /obj/machinery/power/manufacturing/router)) + return MANUFACTURING_FAIL + var/list/filtered = directions - receive_dir - disabled_dirs + if(!length(filtered)) + directions = GLOB.cardinals.Copy() + for(var/target in filtered) + directions -= target + if(isstack(receiving)) + receiving = handle_stack(receiving, receive_dir) + if(send_resource(receiving, target)) + dir = receive_dir + update_appearance(UPDATE_OVERLAYS) // im sorry + return MANUFACTURING_SUCCESS + return MANUFACTURING_FAIL_FULL + +/obj/machinery/power/manufacturing/router/proc/handle_stack(obj/item/stack/stack, direction) + . = stack + var/potential_output_count = length(GLOB.cardinals - direction - disabled_dirs) + if(potential_output_count <= 1) + return + var/split_amount = round(stack.amount / potential_output_count, 1) + if(stack.amount == potential_output_count) + return + var/atom/movable/new_stack = stack.split_stack(amount = min(stack.amount, split_amount)) + return new_stack diff --git a/code/modules/manufactorio/machines/smelter.dm b/code/modules/manufactorio/machines/smelter.dm new file mode 100644 index 00000000000..1a7beca66f4 --- /dev/null +++ b/code/modules/manufactorio/machines/smelter.dm @@ -0,0 +1,59 @@ +/obj/machinery/power/manufacturing/smelter + name = "manufacturing smelter" + desc = "Pretty much incinerates whatever is put into it. Refines ore (not boulders)." + icon_state = "smelter" + circuit = /obj/item/circuitboard/machine/manusmelter + /// power used to smelt + var/power_cost = 4 KILO WATTS + /// our output, if the way out was blocked is held here + var/atom/movable/withheld + +/obj/machinery/power/manufacturing/smelter/update_overlays() + . = ..() + . += generate_io_overlays(dir, COLOR_ORANGE) // OUT - stuff in it + . += generate_io_overlays(REVERSE_DIR(dir), COLOR_MODERATE_BLUE) // IN - to crush + +/obj/machinery/power/manufacturing/smelter/receive_resource(obj/receiving, atom/from, receive_dir) + if(!isitem(receiving) || surplus() < power_cost || receive_dir != REVERSE_DIR(dir)) + return MANUFACTURING_FAIL + var/list/stacks = contents - circuit + if(!may_merge_in_contents(receiving) && length(stacks) >= 5) + return MANUFACTURING_FAIL_FULL + receiving.Move(src, get_dir(receiving, src)) + START_PROCESSING(SSmanufacturing, src) + return MANUFACTURING_SUCCESS + +/obj/machinery/power/manufacturing/smelter/Destroy() + . = ..() + QDEL_NULL(withheld) + +/obj/machinery/power/manufacturing/smelter/atom_destruction(damage_flag) + withheld?.Move(drop_location()) + return ..() + +/obj/machinery/power/manufacturing/smelter/process(seconds_per_tick) + var/list/stacks = contents - circuit + if(!length(stacks)) + return + + var/list/stacks_preprocess = contents - circuit + var/obj/item/stack/ore/ore = stacks_preprocess[length(stacks_preprocess)] + if(isnull(ore)) + return + if(isnull(withheld) && surplus() >= power_cost) + icon_state="smelter_on" + add_load(power_cost) + if(istype(ore)) + var/obj/item/stack/new_stack = new ore.refined_type(null, min(5, ore.amount), FALSE) + new_stack.moveToNullspace() + ore.use(min(5, ore.amount)) + ore = new_stack + else + ore.fire_act(1400) + withheld = ore + else if(surplus() < power_cost) + icon_state = "smelter" + if(send_resource(withheld, dir)) + withheld = null // nullspace thumbs down + if(!length(contents - circuit)) + return PROCESS_KILL //we finished diff --git a/code/modules/manufactorio/machines/sorter.dm b/code/modules/manufactorio/machines/sorter.dm new file mode 100644 index 00000000000..7d06f74e6fa --- /dev/null +++ b/code/modules/manufactorio/machines/sorter.dm @@ -0,0 +1,149 @@ +/obj/machinery/power/manufacturing/sorter + icon_state = "router" + name = "conveyor sort-router" + desc = "Pushes things on it to its sides following set criteria, set via multitool." + layer = BELOW_OPEN_DOOR_LAYER + density = FALSE + interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND + circuit = /obj/item/circuitboard/machine/manurouter + /// for mappers; filter path = list(direction, value), otherwise a list of initialized filters + var/list/sort_filters = list() + /// dir to push to if there is no criteria + var/dir_if_not_met + /// timer id of the thing that makes stuff move + var/delay_timerid + /// max filters + var/max_filters = 10 + +/obj/machinery/power/manufacturing/sorter/Initialize(mapload) + . = ..() + if(isnull(dir_if_not_met)) + dir_if_not_met = dir + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + ) + AddElement(/datum/element/connect_loc, loc_connections) + for(var/i in 1 to length(sort_filters)) + var/creating_type = sort_filters[i] + var/list/values = sort_filters[creating_type] + var/datum/sortrouter_filter/new_type = new creating_type(src) + new_type.dir_target = values[1] + new_type.value = values[2] + sort_filters[i] = new_type + START_PROCESSING(SSobj, src) + +/obj/machinery/power/manufacturing/sorter/Destroy() + . = ..() + QDEL_LIST(sort_filters) + +/obj/machinery/power/manufacturing/sorter/multitool_act(mob/living/user, obj/item/tool) + . = ..() + ui_interact(user) + +/obj/machinery/power/manufacturing/sorter/receive_resource(atom/movable/receiving, atom/from, receive_dir) + if(length(loc.contents) >= MANUFACTURING_TURF_LAG_LIMIT) + return MANUFACTURING_FAIL_FULL + receiving.Move(loc) + return MANUFACTURING_SUCCESS + + +/obj/machinery/power/manufacturing/sorter/ui_data(mob/user) + . = list() + .["unmet_dir"] = dir_if_not_met + .["filters"] = list() + for(var/datum/sortrouter_filter/sorting as anything in sort_filters) + .["filters"] += list(list( + "name" = sorting.return_name(), + "ref" = REF(sorting), + "inverted" = sorting.inverted, + "dir" = sorting.dir_target, + )) + +/obj/machinery/power/manufacturing/sorter/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + switch(action) + if("del_filter") + var/datum/sortrouter_filter/filter = locate(params["ref"]) + if(isnull(filter)) + return + sort_filters -= filter + qdel(filter) + return TRUE + if("new_filter") + if(length(sort_filters) >= max_filters) + return + var/static/list/filter_by_name + if(!length(filter_by_name)) + filter_by_name = list() + for(var/datum/sortrouter_filter/to_do as anything in subtypesof(/datum/sortrouter_filter)) + filter_by_name[initial(to_do.name)] = to_do + filter_by_name = sort_list(filter_by_name) + var/target_type = tgui_input_list(usr, "Select a filter", "New Filter", filter_by_name) + if(isnull(target_type)|| !usr.can_perform_action(src, ALLOW_SILICON_REACH)) + return + target_type = filter_by_name[target_type] + sort_filters += new target_type(src) + return TRUE + if("rotate") + var/datum/sortrouter_filter/filter = locate(params["ref"]) + if(isnull(filter)) + return + var/next_ind = GLOB.cardinals.Find(filter.dir_target) + 1 + filter.dir_target = GLOB.cardinals[WRAP(next_ind, 1, 5)] + return TRUE + if("rotate_unmet") + var/next_ind = GLOB.cardinals.Find(dir_if_not_met) + 1 + dir_if_not_met = GLOB.cardinals[WRAP(next_ind, 1, 5)] + return TRUE + if("edit") + var/datum/sortrouter_filter/filter = locate(params["ref"]) + if(isnull(filter)) + return + filter.edit(usr) + return TRUE + if("shift") + var/datum/sortrouter_filter/filter = locate(params["ref"]) + if(isnull(filter)) + return + var/next_ind = WRAP(sort_filters.Find(filter) + text2num(params["amount"]), 1, length(sort_filters)+1) + sort_filters -= filter + sort_filters.Insert(next_ind, filter) + return TRUE + +/obj/machinery/power/manufacturing/sorter/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ManufacturingSorter") + ui.open() + +/obj/machinery/power/manufacturing/sorter/proc/send_nomobs(atom/movable/moving, dir) + var/mutable_appearance/operate = mutable_appearance(icon, "router_operate") + operate.dir = dir + flick_overlay_view(operate, 1 SECONDS) + return ismob(moving) ? moving.Move(get_step(src,dir), dir) : send_resource(moving, dir) + +/obj/machinery/power/manufacturing/sorter/process() + if(delay_timerid || !length(loc?.contents - 1)) + return + launch_everything() + +/obj/machinery/power/manufacturing/sorter/proc/on_entered(datum/source, atom/movable/mover) + SIGNAL_HANDLER + if(mover == src || !istype(mover) || mover.anchored || delay_timerid) + return + delay_timerid = addtimer(CALLBACK(src, PROC_REF(launch_everything)), 0.2 SECONDS) + +/obj/machinery/power/manufacturing/sorter/proc/launch_everything() + delay_timerid = null + var/turf/where_we_at = get_turf(src) + for(var/atom/movable/mover as anything in where_we_at.contents) + if(mover.anchored) + continue + for(var/datum/sortrouter_filter/sorting as anything in sort_filters) + if(sorting.meets_conditions(mover) == sorting.inverted) + continue + send_nomobs(mover, sorting.dir_target) + return + send_nomobs(mover, dir_if_not_met) diff --git a/code/modules/manufactorio/machines/sorter_filters.dm b/code/modules/manufactorio/machines/sorter_filters.dm new file mode 100644 index 00000000000..cb7e31cc41e --- /dev/null +++ b/code/modules/manufactorio/machines/sorter_filters.dm @@ -0,0 +1,120 @@ +/datum/sortrouter_filter + /// name of the filter shown in UI + var/name + /// if it meets criteria, item is pushed to this direction + var/dir_target = NORTH + /// value of our filter, checked by us + var/value = "" + /// is our output inverted? checked by sorter + var/inverted = FALSE + /// the sorter we belong to + var/obj/machinery/power/manufacturing/sorter/sorter + +/datum/sortrouter_filter/New(sorter) + . = ..() + if(isnull(sorter)) + return + src.sorter = sorter + +/datum/sortrouter_filter/Destroy() + . = ..() + if(isnull(sorter)) + return + sorter = null + +/datum/sortrouter_filter + +/datum/sortrouter_filter/proc/return_name() + return name + +/datum/sortrouter_filter/proc/edit(mob/user) + to_chat(user, "This filter is not editable.") + +/datum/sortrouter_filter/proc/meets_conditions(atom/checking) + +/datum/sortrouter_filter/is_stack + name = "input is stack" + +/datum/sortrouter_filter/is_stack/meets_conditions(atom/checking) + return isstack(checking) + +/datum/sortrouter_filter/is_ore + name = "input is ore" + +/datum/sortrouter_filter/is_ore/meets_conditions(atom/checking) + return istype(checking, /obj/item/stack/ore) + +/datum/sortrouter_filter/is_mail + name = "input is mail" + +/datum/sortrouter_filter/is_mail/meets_conditions(atom/checking) + return istype(checking, /obj/item/mail) + +/datum/sortrouter_filter/is_tagged + name = "input is tagged X" + +/datum/sortrouter_filter/is_tagged/edit(mob/user) + var/target = tgui_input_list(user, "Select a tag", "Tag", sort_list(GLOB.TAGGERLOCATIONS)) + if(isnull(target) || !user.can_perform_action(sorter, ALLOW_SILICON_REACH)) + return + value = GLOB.TAGGERLOCATIONS.Find(target) + +/datum/sortrouter_filter/is_tagged/return_name() + return "input is tagged [value ? GLOB.TAGGERLOCATIONS[value] : ""]" + +/datum/sortrouter_filter/is_tagged/meets_conditions(checking) + var/obj/item/delivery/mail_or_delivery = checking + var/sort_tag + if(istype(checking, /obj/item/delivery) || istype(checking, /obj/item/mail)) + sort_tag = mail_or_delivery.sort_tag + + return value == sort_tag + +/datum/sortrouter_filter/name_contains + name = "input's name contains" + +/datum/sortrouter_filter/name_contains/edit(mob/user) + var/target = tgui_input_text(user, "What should it contain?", "Name", value, 12) + if(isnull(target)|| !user.can_perform_action(sorter, ALLOW_SILICON_REACH)) + return + value = target + +/datum/sortrouter_filter/name_contains/return_name() + return "input's name contains [value]" + +/datum/sortrouter_filter/name_contains/meets_conditions(atom/checking) + return findtext(LOWER_TEXT(checking.name), value) + +/datum/sortrouter_filter/is_path_specific + name = "input is specific item" + /// are we currently listening for an item to set as our filter? + var/currently_listening = FALSE + +/datum/sortrouter_filter/is_path_specific/edit(mob/user) + name = initial(name) + if(!currently_listening) + name = "awaiting item" + to_chat(user, "Hit the sorter with the item of choice to set the filter.") + sorter.balloon_alert(user, "awaiting item!") + currently_listening = TRUE + RegisterSignal(sorter, COMSIG_ATOM_ATTACKBY, PROC_REF(sorter_hit)) + else + currently_listening = FALSE + UnregisterSignal(sorter, COMSIG_ATOM_ATTACKBY) + +/datum/sortrouter_filter/is_path_specific/proc/sorter_hit(datum/source, obj/item/attacking_item, user, params) + currently_listening = FALSE + value = attacking_item.type + name = attacking_item.name + sorter.balloon_alert(user, "filter set") + UnregisterSignal(sorter, COMSIG_ATOM_ATTACKBY) + return COMPONENT_NO_AFTERATTACK + +/datum/sortrouter_filter/is_path_specific/meets_conditions(atom/checking) + return checking.type == value + +/datum/sortrouter_filter/is_path_specific/subtypes + name = "input is specific kind of item" + +/datum/sortrouter_filter/is_path_specific/subtypes/meets_conditions(atom/checking) + return istype(checking.type, value) diff --git a/code/modules/manufactorio/machines/storagebox.dm b/code/modules/manufactorio/machines/storagebox.dm new file mode 100644 index 00000000000..21957871cf8 --- /dev/null +++ b/code/modules/manufactorio/machines/storagebox.dm @@ -0,0 +1,46 @@ +/obj/machinery/power/manufacturing/storagebox + name = "manufacturing storage unit" + desc = "Its basically a box. Receives resources (if anchored). Needs a machine to take stuff out of without dumping everything out." + icon_state = "box" + /// how much can we hold + var/max_stuff = 16 + +/obj/machinery/power/manufacturing/request_resource() //returns last inserted item + var/list/real_contents = contents - circuit + if(!length(real_contents)) + return + return (real_contents)[length(real_contents)] + +/obj/machinery/power/manufacturing/storagebox/receive_resource(atom/movable/receiving, atom/from, receive_dir) + if(iscloset(receiving) && length(receiving.contents)) + return MANUFACTURING_FAIL + if(!may_merge_in_contents(receiving) && length(contents - circuit) >= max_stuff) + return MANUFACTURING_FAIL_FULL + receiving.Move(src,receive_dir) + return MANUFACTURING_SUCCESS + +/obj/machinery/power/manufacturing/storagebox/container_resist_act(mob/living/user) + . = ..() + user.Move(drop_location()) + +/obj/machinery/power/manufacturing/storagebox/screwdriver_act(mob/living/user, obj/item/tool) + . = NONE + balloon_alert(user, "disassembling...") + if(!do_after(user, 5 SECONDS, src)) + return ITEM_INTERACT_FAILURE + atom_destruction() + return ITEM_INTERACT_SUCCESS + +/obj/machinery/power/manufacturing/storagebox/atom_destruction(damage_flag) + new /obj/item/stack/sheet/iron(drop_location(), 10) + dump_inventory_contents() + return ..() + +/obj/machinery/power/manufacturing/storagebox/attack_hand(mob/living/user, list/modifiers) + . = ..() + if(user.combat_mode) + return + balloon_alert(user, "dumping..") + if(!do_after(user, 1.25 SECONDS, src)) + return + dump_inventory_contents() diff --git a/code/modules/manufactorio/machines/unloader.dm b/code/modules/manufactorio/machines/unloader.dm new file mode 100644 index 00000000000..982c3358268 --- /dev/null +++ b/code/modules/manufactorio/machines/unloader.dm @@ -0,0 +1,78 @@ +/obj/machinery/power/manufacturing/unloader + name = "manufacturing crate unloader" + desc = "Unloads crates (and ore boxes) passed into it, ejecting the empty crate to the side and its contents forwards. Use a multitool to flip the crate output." + icon = 'icons/obj/machines/mining_machines.dmi' + icon_state = "unloader-corner" + circuit = /obj/item/circuitboard/machine/manuunloader + /// power used per attempt to unload a crate + var/power_to_unload_crate = 2 KILO WATTS + /// whether the side we output unloaded crates is flipped + var/flip_side = FALSE + +/obj/machinery/power/manufacturing/unloader/update_overlays() + . = ..() + . += generate_io_overlays(dir, COLOR_ORANGE) // OUT - stuff in it + . += generate_io_overlays(REVERSE_DIR(dir), COLOR_MODERATE_BLUE) // IN - crate + . += generate_io_overlays(turn(dir, flip_side ? 90 : -90), COLOR_ORANGE) // OUT -- empty crate + +/obj/machinery/power/manufacturing/unloader/request_resource() //returns held crate if someone wants to do that for some reason + var/list/real_contents = contents - circuit + if(!length(real_contents)) + return + return (real_contents)[1] + +/obj/machinery/power/manufacturing/unloader/multitool_act(mob/living/user, obj/item/tool) + . = ..() + balloon_alert(user, "flipped") + flip_side = !flip_side + update_appearance() + +/obj/machinery/power/manufacturing/unloader/receive_resource(obj/receiving, atom/from, receive_dir) + if(surplus() < power_to_unload_crate || receive_dir != REVERSE_DIR(dir)) + return MANUFACTURING_FAIL + var/list/real_contents = contents - circuit + if(length(real_contents)) + return MANUFACTURING_FAIL_FULL + + var/obj/structure/closet/as_closet = receiving + var/obj/structure/ore_box/as_orebox = receiving + if(istype(as_closet)) + if(!as_closet.can_open()) + return MANUFACTURING_FAIL + else if(!istype(as_orebox)) + return MANUFACTURING_FAIL + receiving.Move(src, get_dir(receiving, src)) + START_PROCESSING(SSfastprocess, src) + return MANUFACTURING_SUCCESS + +/obj/machinery/power/manufacturing/unloader/process(seconds_per_tick) + var/list/real_contents = contents - circuit + if(!length(real_contents)) + return PROCESS_KILL + if(surplus() < power_to_unload_crate) + return + add_load(power_to_unload_crate) + var/obj/structure/closet/closet = real_contents[1] + if(istype(closet)) + return unload_crate(closet) + else + return unload_orebox(closet) + +/obj/machinery/power/manufacturing/unloader/proc/unload_crate(obj/structure/closet/closet) + if (!closet.contents_initialized) + closet.contents_initialized = TRUE + closet.PopulateContents() + SEND_SIGNAL(closet, COMSIG_CLOSET_CONTENTS_INITIALIZED) + for(var/atom/thing as anything in closet.contents) + if(ismob(thing)) + continue + send_resource(thing, dir) + if(!length(closet.contents) && send_resource(closet, turn(dir, flip_side ? 90 : -90))) + closet.open(force = TRUE) + return PROCESS_KILL + +/obj/machinery/power/manufacturing/unloader/proc/unload_orebox(obj/structure/ore_box/box) + for(var/atom/thing as anything in box.contents) + send_resource(thing, dir) + if(!length(box.contents) && send_resource(box, turn(dir, flip_side ? 90 : -90))) + return PROCESS_KILL diff --git a/code/modules/recycling/conveyor.dm b/code/modules/recycling/conveyor.dm index b4580a8bc23..44d9631a609 100644 --- a/code/modules/recycling/conveyor.dm +++ b/code/modules/recycling/conveyor.dm @@ -34,15 +34,18 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) var/flipped = FALSE /// Are we currently conveying items? var/conveying = FALSE - //Direction -> if we have a conveyor belt in that direction + ///Direction -> if we have a conveyor belt in that direction var/list/neighbors + /// are we operating in wire power mode + var/wire_mode = FALSE + /// weakref to attached cable if wire mode + var/datum/weakref/attached_wire_ref /obj/machinery/conveyor/Initialize(mapload, new_dir, new_id) . = ..() AddElement(/datum/element/footstep_override, priority = STEP_SOUND_CONVEYOR_PRIORITY) AddElement(/datum/element/give_turf_traits, string_list(list(TRAIT_TURF_IGNORE_SLOWDOWN))) register_context() - if(new_dir) setDir(new_dir) if(new_id) @@ -58,6 +61,9 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) AddElement(/datum/element/connect_loc, loc_connections) update_move_direction() LAZYADD(GLOB.conveyors_by_id[id], src) + if(wire_mode) + update_cable() + START_PROCESSING(SSmachines, src) /obj/machinery/conveyor/examine(mob/user) . = ..() @@ -66,6 +72,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) . += "\nLeft-click with a wrench to rotate." . += "Left-click with a screwdriver to invert its direction." . += "Right-click with a screwdriver to flip its belt around." + . += "Left-click with a multitool to toggle whether this conveyor receives power via cable. Toggling connects and disconnects." . += "Using another conveyor belt assembly on this will place a new conveyor belt in the direction this one is pointing." /obj/machinery/conveyor/add_context(atom/source, list/context, obj/item/held_item, mob/user) @@ -80,6 +87,9 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) context[SCREENTIP_CONTEXT_LMB] = "Invert conveyor belt" context[SCREENTIP_CONTEXT_RMB] = "Flip conveyor belt" return CONTEXTUAL_SCREENTIP_SET + if(held_item?.tool_behaviour == TOOL_MULTITOOL) + context[SCREENTIP_CONTEXT_LMB] = "Toggle conveyor belt wire mode" + return CONTEXTUAL_SCREENTIP_SET /obj/machinery/conveyor/centcom_auto id = "round_end_belt" @@ -118,6 +128,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) /obj/machinery/conveyor/Destroy() set_operating(FALSE) LAZYREMOVE(GLOB.conveyors_by_id[id], src) + attached_wire_ref = null return ..() /obj/machinery/conveyor/vv_edit_var(var_name, var_value) @@ -295,7 +306,16 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) inverted = !inverted update_move_direction() to_chat(user, span_notice("You set [src]'s direction [inverted ? "backwards" : "back to default"].")) - + else if(attacking_item.tool_behaviour == TOOL_MULTITOOL) + attacking_item.play_tool_sound(src) + wire_mode = !wire_mode + update_cable() + power_change() + if(wire_mode) + START_PROCESSING(SSmachines, src) + else + STOP_PROCESSING(SSmachines, src) + to_chat(user, span_notice("You set [src]'s wire mode [wire_mode ? "on" : "off"].")) else if(istype(attacking_item, /obj/item/stack/conveyor)) // We should place a new conveyor belt machine on the output turf the conveyor is pointing to. var/turf/target_turf = get_step(get_turf(src), forwards) @@ -334,10 +354,51 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) return user.Move_Pulled(src) +/obj/machinery/conveyor/powered(chan = power_channel, ignore_use_power = FALSE) + if(!wire_mode) + return ..() + var/datum/powernet/powernet = get_powernet() + if(!isnull(powernet)) + return clamp(powernet.avail-powernet.load, 0, powernet.avail) >= active_power_usage + return ..() + /obj/machinery/conveyor/power_change() . = ..() update() +/obj/machinery/conveyor/process() + if(!wire_mode) + return PROCESS_KILL + if(isnull(attached_wire_ref)) + update_cable() + return + var/datum/powernet/powernet = get_powernet() + if(isnull(powernet)) + return + if(powered()) + powernet.load += active_power_usage + else + power_change() + + +/obj/machinery/conveyor/proc/update_cable() + if(!wire_mode) + attached_wire_ref = null + return + var/turf/our_turf = get_turf(src) + attached_wire_ref = WEAKREF(locate(/obj/structure/cable) in our_turf) + if(attached_wire_ref) + return power_change() + +/obj/machinery/conveyor/proc/get_powernet() + if(!wire_mode) + return + var/obj/structure/cable/cable = attached_wire_ref.resolve() + if(isnull(cable)) + attached_wire_ref = null + return + return cable.powernet + // Conveyor switch /obj/machinery/conveyor_switch name = "conveyor switch" diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index d07deca2926..55f8436c453 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -1257,3 +1257,73 @@ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SERVICE + +/datum/design/board/manulathe + name = /obj/machinery/power/manufacturing/lathe::name + desc = "The circuit board for this machine." + id = "manulathe" + build_path = /obj/machinery/power/manufacturing/lathe + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO + +/datum/design/board/manucrafter + name = /obj/machinery/power/manufacturing/crafter::name + desc = "The circuit board for this machine." + id = "manucrafter" + build_path = /obj/machinery/power/manufacturing/crafter + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO + +/datum/design/board/manucrusher + name = /obj/machinery/power/manufacturing/crusher::name + desc = "The circuit board for this machine." + id = "manucrusher" + build_path = /obj/machinery/power/manufacturing/crusher + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO + +/datum/design/board/manurouter + name = /obj/machinery/power/manufacturing/router::name + desc = "The circuit board for this machine." + id = "manurouter" + build_path = /obj/machinery/power/manufacturing/router + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO + +/datum/design/board/manusorter + name = /obj/machinery/power/manufacturing/sorter::name + desc = "The circuit board for this machine." + id = "manusorter" + build_path = /obj/machinery/power/manufacturing/sorter + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO + +/datum/design/board/manuunloader + name = /obj/machinery/power/manufacturing/unloader::name + desc = "The circuit board for this machine." + id = "manuunloader" + build_path = /obj/machinery/power/manufacturing/unloader + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO + +/datum/design/board/manusmelter + name = /obj/machinery/power/manufacturing/smelter::name + desc = "The circuit board for this machine." + id = "manusmelter" + build_path = /obj/machinery/power/manufacturing/smelter + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO diff --git a/code/modules/research/techweb/nodes/engi_nodes.dm b/code/modules/research/techweb/nodes/engi_nodes.dm index 0d8572130fb..fa1be5bb155 100644 --- a/code/modules/research/techweb/nodes/engi_nodes.dm +++ b/code/modules/research/techweb/nodes/engi_nodes.dm @@ -148,6 +148,13 @@ "light_tube", "crossing_signal", "guideway_sensor", + "manuunloader", + "manusmelter", + "manucrusher", + "manucrafter", + "manulathe", + "manusorter", + "manurouter", ) /datum/techweb_node/energy_manipulation diff --git a/icons/obj/machines/manufactorio.dmi b/icons/obj/machines/manufactorio.dmi new file mode 100644 index 0000000000000000000000000000000000000000..95bbd8fccb0eb97aa302ec1e1f5e1e3bd2e9661a GIT binary patch literal 9114 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 + const { filters, unmet_dir } = data; + + return ( + + + + +
act('new_filter')} + > + New filter + + } + > + + {filters.map((filter, i) => ( + + + + + + + + ))} + +
+
+ + + + If no criteria is met, outputting to: + + + + + + +
+
+
+ ); +}