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 00000000000..95bbd8fccb0 Binary files /dev/null and b/icons/obj/machines/manufactorio.dmi differ diff --git a/tgstation.dme b/tgstation.dme index ed7d09bb8fe..9d5d9d705a1 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -758,6 +758,7 @@ #include "code\controllers\subsystem\processing\fishing.dm" #include "code\controllers\subsystem\processing\greyscale.dm" #include "code\controllers\subsystem\processing\instruments.dm" +#include "code\controllers\subsystem\processing\manufacturing.dm" #include "code\controllers\subsystem\processing\obj.dm" #include "code\controllers\subsystem\processing\plumbing.dm" #include "code\controllers\subsystem\processing\processing.dm" @@ -4576,6 +4577,17 @@ #include "code\modules\mafia\roles\town\town_killing.dm" #include "code\modules\mafia\roles\town\town_protective.dm" #include "code\modules\mafia\roles\town\town_support.dm" +#include "code\modules\manufactorio\_manufacturing.dm" +#include "code\modules\manufactorio\machines\crafter.dm" +#include "code\modules\manufactorio\machines\crusher.dm" +#include "code\modules\manufactorio\machines\debug.dm" +#include "code\modules\manufactorio\machines\lathe.dm" +#include "code\modules\manufactorio\machines\router.dm" +#include "code\modules\manufactorio\machines\smelter.dm" +#include "code\modules\manufactorio\machines\sorter.dm" +#include "code\modules\manufactorio\machines\sorter_filters.dm" +#include "code\modules\manufactorio\machines\storagebox.dm" +#include "code\modules\manufactorio\machines\unloader.dm" #include "code\modules\mapfluff\centcom\nuke_ops.dm" #include "code\modules\mapfluff\ruins\generic.dm" #include "code\modules\mapfluff\ruins\lavaland_ruin_code.dm" diff --git a/tgui/packages/tgui/interfaces/ManufacturingSorter.tsx b/tgui/packages/tgui/interfaces/ManufacturingSorter.tsx new file mode 100644 index 00000000000..9ae38a3e5b6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ManufacturingSorter.tsx @@ -0,0 +1,113 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../backend'; +import { Box, Button, Icon, LabeledList, Section, Stack } from '../components'; +import { Window } from '../layouts'; + +type Data = { + filters: Filter[]; + unmet_dir: number; +}; + +function dir2icon(dir) { + switch (dir) { + case 1: + return 'arrow-up'; + case 2: + return 'arrow-down'; + case 4: + return 'arrow-right'; + case 8: + return 'arrow-left'; + default: + return 'arrow-up'; + } +} + +type Filter = { + name: string; + ref: string; + inverted: BooleanLike; + dir: number; +}; + +export function ManufacturingSorter(props) { + const { act, data } = useBackend(); + const { filters, unmet_dir } = data; + + return ( + + + + +
act('new_filter')} + > + New filter + + } + > + + {filters.map((filter, i) => ( + + + + + + + + ))} + +
+
+ + + + If no criteria is met, outputting to: + + + + + + +
+
+
+ ); +}