From 666ab972ccb77e1e79aa4e56a76b0f709f08de20 Mon Sep 17 00:00:00 2001 From: NovaBot <154629622+NovaBot13@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:07:55 -0500 Subject: [PATCH] [MIRROR] General maintenance for Lathes (#839) * General maintenance for Lathes (#81244) ## About The Pull Request 1. **Qol Stuff** - Screentips & examines for screwdriver, crowbar acts, multiool & wirecutter Also for Alt click - Techfabs can now also use the Mouse drag functionality to set drop target for items - Lathe printing animation now plays on loop instead of just flicking once till printing is finished for more visual feedback 2. **Code Improvements** - Merged `start_making()` with `do_make_item()`. That proc was like only 3 lines long and used only in 1 place so let's just move that code to `ui_act()` - Merged `user_print_item_id()` with `ui_act()`. Again was used only in 1 place so let's just move that code in to save some proc overhead - Sets `processing_flags` for autolathe to `NONE` cause we don't use `process()` - Autodocs vars such as `hacked` , `shocked` etc & procs - `maxmult` is now computed client side saving backend bandwidth, `construction_time` is removed from lathes which did not use it - Removed all usages of lathe taxes and their related vars, removed engineering lathe no tax from ice moon, replaced with normal engineering lathe 3. **Fixes** - Lathe sheet insertion animations are now linked & work again for all material types inserted via remote silo/local storage, silver/titanium/plastic all play the same animation(that is `protolathe_shiny` overlay). Other materials have their own respective overlays - Fixes #81243. Calling `update_static_data_for_all_viewers()` is too expensive for the UI. We should instead use `SStgui.update_uis(src)` which will report the `busy` status to the UI more immediatly - Fixes #81236. Some problems with the params passed to the timer callback. It should now print the correct number of requested items - Fixes #81192. `design.materials` would runtime for custom material items as they were list of texts not materials. We have to pass our manually parsed list of materials for an specific item to ensure they are set & used correctly. Same fixes apply for techfabs as well ## Changelog :cl: qol: adds screentips & examines for screwdriver & crowbar acts & alt click. qol: techfabs can now use the mouse drop functionality to set drop target. qol: lathe printing animation plays on loop while printing rather than flicking once for more visual feedback fix: lathe sheet insertion animations are now linked & work again for all material types inserted via remote silo/local storage fix: printing custom materials items from autolathe works again. fix: printing multiple items from lathes will actually print that correct quantity of items requested. fix: printing items the 2nd time around from lathes won't cause the UI to reload each time. code: autodoc for some vars & procs, merges procs. refactor: Optimized code for autolathe & techfabs in general. Report bugs on github /:cl: --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> * General maintenance for Lathes * should fix all the stuff * yeah that one is on me * fixes dme * they gotta stop privating procs this shit sucks * ok * do not the register context --------- Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> Co-authored-by: Paxilmaniac --- .../IceRuins/icemoon_surface_engioutpost.dmm | 2 +- .../SpaceRuins/nova/cargodiselost.dmm | 2 +- .../components/material/remote_materials.dm | 18 +- code/game/machinery/autolathe.dm | 328 ++++++++------ .../machines/machine_circuitboards.dm | 3 - .../oldstation/oldstation_rnd.dm | 13 +- code/modules/research/destructive_analyzer.dm | 21 +- .../modules/research/machinery/_production.dm | 426 +++++++++++------- .../research/machinery/circuit_imprinter.dm | 15 +- .../machinery/departmental_protolathe.dm | 4 - code/modules/research/machinery/protolathe.dm | 1 - code/modules/research/machinery/techfab.dm | 1 - code/modules/research/rdmachines.dm | 97 ++-- .../modules/wiremod/core/component_printer.dm | 3 +- icons/obj/machines/research.dmi | Bin 26907 -> 28134 bytes .../departmental_circuit_imprinter.dm | 5 - .../code/colony_fabricator.dm | 14 +- .../tarkon/code/misc_fluff/research.dm | 9 +- tgstation.dme | 1 - tgui/packages/tgui/interfaces/Autolathe.tsx | 41 +- .../tgui/interfaces/ExosuitFabricator.tsx | 5 + .../tgui/interfaces/Fabrication/Types.ts | 10 - tgui/packages/tgui/interfaces/Fabricator.tsx | 12 +- 23 files changed, 617 insertions(+), 414 deletions(-) delete mode 100644 modular_nova/master_files/code/modules/research/machinery/departmental_circuit_imprinter.dm diff --git a/_maps/RandomRuins/IceRuins/icemoon_surface_engioutpost.dmm b/_maps/RandomRuins/IceRuins/icemoon_surface_engioutpost.dmm index 5c9336c5c5c..6f86429720a 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_surface_engioutpost.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_surface_engioutpost.dmm @@ -88,7 +88,7 @@ /turf/open/floor/iron, /area/ruin/planetengi) "aA" = ( -/obj/machinery/rnd/production/protolathe/department/engineering/no_tax, +/obj/machinery/rnd/production/protolathe/department/engineering, /obj/effect/turf_decal/trimline/yellow/filled/warning{ dir = 9 }, diff --git a/_maps/RandomRuins/SpaceRuins/nova/cargodiselost.dmm b/_maps/RandomRuins/SpaceRuins/nova/cargodiselost.dmm index 801a11da4fe..3592016d918 100644 --- a/_maps/RandomRuins/SpaceRuins/nova/cargodiselost.dmm +++ b/_maps/RandomRuins/SpaceRuins/nova/cargodiselost.dmm @@ -1162,7 +1162,7 @@ /obj/item/stack/sheet/glass/fifty, /obj/item/circuitboard/machine/protolathe/offstation, /obj/effect/spawner/random/engineering/toolbox, -/obj/item/circuitboard/machine/circuit_imprinter/department/no_tax, +/obj/item/circuitboard/machine/circuit_imprinter/department, /obj/item/circuitboard/machine/rtg/advanced, /obj/item/circuitboard/machine/rtg/advanced, /obj/item/circuitboard/machine/rtg/advanced, diff --git a/code/datums/components/material/remote_materials.dm b/code/datums/components/material/remote_materials.dm index 97c6b4e6282..e418d4276be 100644 --- a/code/datums/components/material/remote_materials.dm +++ b/code/datums/components/material/remote_materials.dm @@ -118,6 +118,12 @@ handles linking back and forth. else silo.holds -= src +/** + * Sets the storage size for local materials when not linked with silo + * Arguments + * + * * size - the new size for local storage. measured in SHEET_MATERIAL_SIZE units + */ /datum/component/remote_materials/proc/set_local_size(size) local_size = size if (!silo && mat_container) @@ -209,7 +215,7 @@ handles linking back and forth. return check_z_level() ? silo.holds[src] : FALSE /** - * Internal proc to check if this connection can use any materials from the silo + * Check if this connection can use any materials from the silo * Returns true only if * - The parent is of type movable atom * - A mat container is actually present @@ -217,9 +223,7 @@ handles linking back and forth. * Arguments * * check_hold - should we check if the silo is on hold */ -/datum/component/remote_materials/proc/_can_use_resource(check_hold = TRUE) - PRIVATE_PROC(TRUE) - +/datum/component/remote_materials/proc/can_use_resource(check_hold = TRUE) var/atom/movable/movable_parent = parent if (!istype(movable_parent)) return FALSE @@ -243,7 +247,7 @@ handles linking back and forth. * name- For logging only. the design you are trying to build e.g. matter bin, etc. */ /datum/component/remote_materials/proc/use_materials(list/mats, coefficient = 1, multiplier = 1, action = "build", name = "design") - if(!_can_use_resource()) + if(!can_use_resource()) return 0 var/amount_consumed = mat_container.use_materials(mats, coefficient, multiplier) @@ -265,7 +269,7 @@ handles linking back and forth. * [drop_target][atom]- optional where to drop the sheets. null means it is dropped at this components parent location */ /datum/component/remote_materials/proc/eject_sheets(datum/material/material_ref, eject_amount, atom/drop_target = null) - if(!_can_use_resource()) + if(!can_use_resource()) return 0 var/atom/movable/movable_parent = parent @@ -282,7 +286,7 @@ handles linking back and forth. * * multiplier - the multiplier applied on the materials consumed */ /datum/component/remote_materials/proc/insert_item(obj/item/weapon, multiplier = 1) - if(!_can_use_resource(FALSE)) + if(!can_use_resource(FALSE)) return MATERIAL_INSERT_ITEM_FAILURE return mat_container.insert_item(weapon, multiplier, parent) diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index 03473ed5d39..f6600942cde 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -7,24 +7,24 @@ active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.5 circuit = /obj/item/circuitboard/machine/autolathe layer = BELOW_OBJ_LAYER + processing_flags = NONE + ///Is the autolathe hacked via wiring var/hacked = FALSE + ///Is the autolathe disabled via wiring var/disabled = FALSE + ///Did we recently shock a mob who medled with the wiring var/shocked = FALSE + ///Are we currently printing something var/busy = FALSE - - /// Coefficient applied to consumed materials. Lower values result in lower material consumption. + ///Coefficient applied to consumed materials. Lower values result in lower material consumption. var/creation_efficiency = 1.6 - - var/datum/design/being_built + ///Designs related to the autolathe var/datum/techweb/autounlocking/stored_research - ///Designs imported from technology disks that we can print. var/list/imported_designs = list() - ///The container to hold materials var/datum/component/material_container/materials - ///direction we output onto (if 0, on top of us) var/drop_direction = 0 @@ -43,17 +43,69 @@ GLOB.autounlock_techwebs[/datum/techweb/autounlocking/autolathe] = new /datum/techweb/autounlocking/autolathe stored_research = GLOB.autounlock_techwebs[/datum/techweb/autounlocking/autolathe] + register_context() + /obj/machinery/autolathe/Destroy() materials = null QDEL_NULL(wires) return ..() +/obj/machinery/autolathe/examine(mob/user) + . = ..() + if(!in_range(user, src) && !isobserver(user)) + return + + . += span_notice("Material usage cost at [creation_efficiency * 100]%") + if(drop_direction) + . += span_notice("Currently configured to drop printed objects [dir2text(drop_direction)].") + . += span_notice("[EXAMINE_HINT("Alt-click")] to reset.") + else + . += span_notice("[EXAMINE_HINT("Drag")] towards a direction (while next to it) to change drop direction.") + + . += span_notice("Its maintainence panel can be [EXAMINE_HINT("screwed")] [panel_open ? "closed" : "open"].") + if(panel_open) + . += span_notice("The machine can be [EXAMINE_HINT("pried")] apart.") + +/obj/machinery/autolathe/add_context(atom/source, list/context, obj/item/held_item, mob/user) + if(drop_direction) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Reset Drop" + return CONTEXTUAL_SCREENTIP_SET + + if(isnull(held_item)) + return NONE + + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_RMB] = "[panel_open ? "Close" : "Open"] Panel" + return CONTEXTUAL_SCREENTIP_SET + + if(panel_open && held_item.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/autolathe/crowbar_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/autolathe/screwdriver_act_secondary(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING + if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", tool)) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/autolathe/proc/AfterMaterialInsert(container, obj/item/item_inserted, last_inserted_id, mats_consumed, amount_inserted, atom/context) + SIGNAL_HANDLER + + flick("autolathe_[item_inserted.has_material_type(/datum/material/glass) ? "r" : "o"]", src) + + directly_use_power(round((amount_inserted / SHEET_MATERIAL_AMOUNT) * active_power_usage * 0.0025)) + /obj/machinery/autolathe/ui_interact(mob/user, datum/tgui/ui) if(!is_operational) return if(shocked && !(machine_stat & NOPOWER)) shock(user, 50) + return ui = SStgui.try_update_ui(user, src, ui) @@ -61,66 +113,40 @@ ui = new(user, src, "Autolathe") ui.open() -/obj/machinery/autolathe/ui_static_data(mob/user) - var/list/data = materials.ui_static_data() - - var/max_available = materials.total_amount() - for(var/datum/material/container_mat as anything in materials.materials) - var/available = materials.materials[container_mat] - if(available) - max_available = max(max_available, available) - - data["designs"] = handle_designs(stored_research.researched_designs, max_available) - if(imported_designs.len) - data["designs"] += handle_designs(imported_designs, max_available) - if(hacked) - data["designs"] += handle_designs(stored_research.hacked_designs, max_available) - - return data - -/obj/machinery/autolathe/ui_data(mob/user) - var/list/data = list() - - data["materials"] = list() - data["materialtotal"] = materials.total_amount() - data["materialsmax"] = materials.max_amount - data["active"] = busy - data["materials"] = materials.ui_data() - return data +/** + * Converts all the designs supported by this autolathe into UI data + * Arguments + * + * * list/designs - the list of techweb designs we are trying to send to the UI + */ +/obj/machinery/autolathe/proc/handle_designs(list/designs) + PRIVATE_PROC(TRUE) -/obj/machinery/autolathe/proc/handle_designs(list/designs, max_available) var/list/output = list() var/datum/asset/spritesheet/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet/research_designs) var/size32x32 = "[spritesheet.name]32x32" - var/max_multiplier = INFINITY for(var/design_id in designs) var/datum/design/design = SSresearch.techweb_design_by_id(design_id) if(design.make_reagent) continue //compute cost & maximum number of printable items - max_multiplier = INFINITY var/coeff = (ispath(design.build_path, /obj/item/stack) ? 1 : creation_efficiency) var/list/cost = list() + var/customMaterials = FALSE for(var/i in design.materials) var/datum/material/mat = i var/design_cost = OPTIMAL_COST(design.materials[i] * coeff) if(istype(mat)) cost[mat.name] = design_cost + customMaterials = FALSE else cost[i] = design_cost - - var/mat_available - if(istype(mat)) //regular mat - mat_available = materials.get_material_amount(mat) - else //category mat means we can make it from any mat, use largest available mat - mat_available = max_available - - max_multiplier = min(max_multiplier, 50, round(mat_available / design_cost)) + customMaterials = TRUE //create & send ui data var/icon_size = spritesheet.icon_size_id(design.id) @@ -131,65 +157,87 @@ "id" = design.id, "categories" = design.category, "icon" = "[icon_size == size32x32 ? "" : "[icon_size] "][design.id]", - "constructionTime" = -1, - "maxmult" = max_multiplier + "customMaterials" = customMaterials ) output += list(design_data) return output +/obj/machinery/autolathe/ui_static_data(mob/user) + var/list/data = materials.ui_static_data() + + data["designs"] = handle_designs(stored_research.researched_designs) + if(imported_designs.len) + data["designs"] += handle_designs(imported_designs) + if(hacked) + data["designs"] += handle_designs(stored_research.hacked_designs) + + return data + + /obj/machinery/autolathe/ui_assets(mob/user) return list( get_asset_datum(/datum/asset/spritesheet/sheetmaterials), get_asset_datum(/datum/asset/spritesheet/research_designs), ) +/obj/machinery/autolathe/ui_data(mob/user) + var/list/data = list() + + data["materials"] = list() + data["materialtotal"] = materials.total_amount() + data["materialsmax"] = materials.max_amount + data["active"] = busy + data["materials"] = materials.ui_data() + + return data + /obj/machinery/autolathe/ui_act(action, list/params, datum/tgui/ui) . = ..() if(.) return + //sanity checks to start printing if(action != "make") stack_trace("unknown autolathe ui_act: [action]") return - if(disabled) say("Unable to print, voltage mismatch in internal wiring.") return - if(busy) - balloon_alert(ui.user, "busy!") - return - - var/turf/target_location = get_step(src, drop_direction) - if(isclosedturf(target_location)) - say("Output path is obstructed by a large object.") + say("currently printing.") return + //validate design var/design_id = params["id"] - + if(!design_id) + return var/valid_design = stored_research.researched_designs[design_id] valid_design ||= stored_research.hacked_designs[design_id] valid_design ||= imported_designs[design_id] if(!valid_design) return - var/datum/design/design = SSresearch.techweb_design_by_id(design_id) if(isnull(design)) stack_trace("got passed an invalid design id: [design_id] and somehow made it past all checks") return - if(!(design.build_type & AUTOLATHE)) + say("This fabricator does not have the necessary keys to decrypt this design.") return - var/build_count = text2num(params["multiplier"]) - if(!build_count) + //validate print quantity + var/build_count = params["multiplier"] + if(isnull(build_count)) + return + build_count = text2num(build_count) + if(isnull(build_count)) return build_count = clamp(build_count, 1, 50) + //check for materials required. For custom material items decode their required materials var/list/materials_needed = list() - for(var/datum/material/material as anything in design.materials) + for(var/material in design.materials) var/amount_needed = design.materials[material] if(istext(material)) // category var/list/choices = list() @@ -215,64 +263,70 @@ return materials_needed[material] = amount_needed + //checks for available materials var/material_cost_coefficient = ispath(design.build_path, /obj/item/stack) ? 1 : creation_efficiency if(!materials.has_materials(materials_needed, material_cost_coefficient, build_count)) say("Not enough materials to begin production.") return - //use power - var/total_charge = 0 + //compute power & time to print 1 item + var/charge_per_item = 0 for(var/material in design.materials) - total_charge += round(design.materials[material] * material_cost_coefficient * build_count) - var/charge_per_item = total_charge / build_count - - var/total_time = (design.construction_time * design.lathe_time_factor * build_count) ** 0.8 - var/time_per_item = total_time / build_count - start_making(design, build_count, time_per_item, material_cost_coefficient, charge_per_item) - return TRUE - -/// Begins the act of making the given design the given number of items -/// Does not check or use materials/power/etc -/obj/machinery/autolathe/proc/start_making(datum/design/design, build_count, build_time_per_item, material_cost_coefficient, charge_per_item) - PROTECTED_PROC(TRUE) + charge_per_item += design.materials[material] + charge_per_item = min(active_power_usage, round(charge_per_item * material_cost_coefficient)) + var/build_time_per_item = (design.construction_time * design.lathe_time_factor) ** 0.8 + //do the printing sequentially busy = TRUE icon_state = "autolathe_n" - update_static_data_for_all_viewers() + SStgui.update_uis(src) + var/turf/target_location + if(drop_direction) + target_location = get_step(src, drop_direction) + if(isclosedturf(target_location)) + target_location = get_turf(src) + else + target_location = get_turf(src) + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, build_count, build_time_per_item, material_cost_coefficient, charge_per_item, materials_needed, target_location), build_time_per_item) - addtimer(CALLBACK(src, PROC_REF(do_make_item), design, material_cost_coefficient, build_time_per_item, charge_per_item, build_count), build_time_per_item) + return TRUE -/// Callback for start_making, actually makes the item -/// Called using timers started by start_making -/obj/machinery/autolathe/proc/do_make_item(datum/design/design, material_cost_coefficient, time_per_item, charge_per_item, items_remaining) +/** + * Callback for start_making, actually makes the item + * Arguments + * + * * datum/design/design - the design we are trying to print + * * items_remaining - the number of designs left out to print + * * build_time_per_item - the time taken to print 1 item + * * material_cost_coefficient - the cost efficiency to print 1 design + * * charge_per_item - the amount of power to print 1 item + * * list/materials_needed - the list of materials to print 1 item + * * turf/target - the location to drop the printed item on +*/ +/obj/machinery/autolathe/proc/do_make_item(datum/design/design, items_remaining, build_time_per_item, material_cost_coefficient, charge_per_item, list/materials_needed, turf/target) PROTECTED_PROC(TRUE) - if(!items_remaining) // how + if(items_remaining <= 0) // how finalize_build() return - if(!directly_use_power(charge_per_item)) + if(!is_operational || !directly_use_power(charge_per_item)) say("Unable to continue production, power failure.") finalize_build() return - var/list/design_materials = design.materials var/is_stack = ispath(design.build_path, /obj/item/stack) - if(!materials.has_materials(design_materials, material_cost_coefficient, is_stack ? items_remaining : 1)) + if(!materials.has_materials(materials_needed, material_cost_coefficient, is_stack ? items_remaining : 1)) say("Unable to continue production, missing materials.") return - materials.use_materials(design_materials, material_cost_coefficient, is_stack ? items_remaining : 1) - - var/turf/target = get_step(src, drop_direction) - if(isclosedturf(target)) - target = get_turf(src) + materials.use_materials(materials_needed, material_cost_coefficient, is_stack ? items_remaining : 1) var/atom/movable/created if(is_stack) created = new design.build_path(target, items_remaining) else created = new design.build_path(target) - split_materials_uniformly(design_materials, material_cost_coefficient, created) + split_materials_uniformly(materials_needed, material_cost_coefficient, created) created.pixel_x = created.base_pixel_x + rand(-6, 6) created.pixel_y = created.base_pixel_y + rand(-6, 6) @@ -283,26 +337,44 @@ else items_remaining -= 1 - if(!items_remaining) + if(items_remaining <= 0) finalize_build() return - addtimer(CALLBACK(src, PROC_REF(do_make_item), design, material_cost_coefficient, time_per_item, items_remaining), time_per_item) + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, items_remaining, build_time_per_item, material_cost_coefficient, charge_per_item, materials_needed, target), build_time_per_item) -/// Resets the icon state and busy flag -/// Called at the end of do_make_item's timer loop +/** + * Resets the icon state and busy flag + * Called at the end of do_make_item's timer loop +*/ /obj/machinery/autolathe/proc/finalize_build() PROTECTED_PROC(TRUE) + icon_state = initial(icon_state) busy = FALSE - update_static_data_for_all_viewers() + SStgui.update_uis(src) -/obj/machinery/autolathe/crowbar_act(mob/living/user, obj/item/tool) - if(default_deconstruction_crowbar(tool)) - return ITEM_INTERACT_SUCCESS +/obj/machinery/autolathe/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) + . = ..() + if((!issilicon(usr) && !isAdminGhostAI(usr)) && !Adjacent(usr)) + return + if(busy) + balloon_alert(usr, "printing started!") + return + var/direction = get_dir(src, over_location) + if(!direction) + return + drop_direction = direction + balloon_alert(usr, "dropping [dir2text(drop_direction)]") -/obj/machinery/autolathe/screwdriver_act_secondary(mob/living/user, obj/item/tool) - if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", tool)) - return ITEM_INTERACT_SUCCESS +/obj/machinery/autolathe/AltClick(mob/user) + . = ..() + if(!drop_direction || !can_interact(user)) + return + if(busy) + balloon_alert(user, "busy printing!") + return + balloon_alert(user, "drop direction reset") + drop_direction = 0 /obj/machinery/autolathe/attackby(obj/item/attacking_item, mob/living/user, params) if(user.combat_mode) //so we can hit the machine @@ -346,25 +418,6 @@ return ..() -/obj/machinery/autolathe/proc/AfterMaterialInsert(container, obj/item/item_inserted, last_inserted_id, mats_consumed, amount_inserted, atom/context) - SIGNAL_HANDLER - - flick("autolathe_[item_inserted.has_material_type(/datum/material/glass) ? "r" : "o"]", src) - - use_power(min(active_power_usage * 0.25, amount_inserted / SHEET_MATERIAL_AMOUNT)) - - update_static_data_for_all_viewers() - -/obj/machinery/autolathe/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) - . = ..() - if((!issilicon(usr) && !isAdminGhostAI(usr)) && !Adjacent(usr)) - return - var/direction = get_dir(src, over_location) - if(!direction) - return - drop_direction = direction - balloon_alert(usr, "dropping [dir2text(drop_direction)]") - /obj/machinery/autolathe/RefreshParts() . = ..() var/mat_capacity = 0 @@ -377,24 +430,12 @@ efficiency -= new_servo.tier * 0.2 creation_efficiency = max(1,efficiency) // creation_efficiency goes 1.6 -> 1.4 -> 1.2 -> 1 per level of servo efficiency -/obj/machinery/autolathe/examine(mob/user) - . += ..() - if(in_range(user, src) || isobserver(user)) - . += span_notice("The status display reads: Storing up to [materials.max_amount] material units.
Material consumption at [creation_efficiency*100]%.") - if(drop_direction) - . += span_notice("Currently configured to drop printed objects [dir2text(drop_direction)].") - . += span_notice("Alt-click to reset.") - else - . += span_notice("Drag towards a direction (while next to it) to change drop direction.") - -/obj/machinery/autolathe/AltClick(mob/user) - . = ..() - if(!can_interact(user)) - return - if(drop_direction) - balloon_alert(user, "drop direction reset") - drop_direction = 0 - +/** + * Cut a wire in the autolathe + * Arguments + * + * * wire - the wire we are trying to cut + */ /obj/machinery/autolathe/proc/reset(wire) switch(wire) if(WIRE_HACK) @@ -407,6 +448,13 @@ if(!wires.is_cut(wire)) disabled = FALSE +/** + * Shock a mob who is trying to interact with the autolathe + * Arguments + * + * * mob/user - the mob we are trying to shock + * * prb - the probability of getting shocked + */ /obj/machinery/autolathe/proc/shock(mob/user, prb) if(machine_stat & (BROKEN|NOPOWER)) // unpowered, no shock return FALSE @@ -417,6 +465,12 @@ s.start() return electrocute_mob(user, get_area(src), src, 0.7, TRUE) +/** + * Is the autolathe hacked. Allowing us to acess hidden designs + * Arguments + * + * state - TRUE/FALSE for is the autolathe hacked + */ /obj/machinery/autolathe/proc/adjust_hacked(state) hacked = state update_static_data_for_all_viewers() diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index 24dd69e2297..851e08beb15 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -309,9 +309,6 @@ greyscale_colors = CIRCUIT_COLOR_ENGINEERING build_path = /obj/machinery/rnd/production/protolathe/department/engineering -/obj/item/circuitboard/machine/protolathe/department/engineering/no_tax - build_path = /obj/machinery/rnd/production/protolathe/department/engineering/no_tax - /obj/item/circuitboard/machine/rtg name = "RTG" greyscale_colors = CIRCUIT_COLOR_ENGINEERING diff --git a/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_rnd.dm b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_rnd.dm index 58d14e0d041..5f0935bb8bd 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_rnd.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/oldstation/oldstation_rnd.dm @@ -4,15 +4,24 @@ req_access = list(ACCESS_AWAY_SCIENCE) /obj/machinery/rnd/server/oldstation/Initialize(mapload) - register_context() var/datum/techweb/oldstation_web = locate(/datum/techweb/oldstation) in SSresearch.techwebs stored_research = oldstation_web return ..() /obj/machinery/rnd/server/oldstation/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(held_item && istype(held_item, /obj/item/research_notes)) context[SCREENTIP_CONTEXT_LMB] = "Generate research points" - return CONTEXTUAL_SCREENTIP_SET + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/rnd/server/oldstation/examine(mob/user) + . = ..() + + if(!in_range(user, src) && !isobserver(user)) + return + + . += span_notice("Insert [EXAMINE_HINT("Research Notes")] to generate points.") /obj/machinery/rnd/server/oldstation/attackby(obj/item/attacking_item, mob/user, params) if(istype(attacking_item, /obj/item/research_notes) && stored_research) diff --git a/code/modules/research/destructive_analyzer.dm b/code/modules/research/destructive_analyzer.dm index 72fd12ced70..e9e34bfd806 100644 --- a/code/modules/research/destructive_analyzer.dm +++ b/code/modules/research/destructive_analyzer.dm @@ -14,16 +14,29 @@ base_icon_state = "d_analyzer" circuit = /obj/item/circuitboard/machine/destructive_analyzer -/obj/machinery/rnd/destructive_analyzer/Initialize(mapload) +/obj/machinery/rnd/destructive_analyzer/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) . = ..() - register_context() -/obj/machinery/rnd/destructive_analyzer/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + var/screentip_set = FALSE if(loaded_item) context[SCREENTIP_CONTEXT_ALT_LMB] = "Remove Item" + screentip_set = TRUE else if(!isnull(held_item)) context[SCREENTIP_CONTEXT_LMB] = "Insert Item" - return CONTEXTUAL_SCREENTIP_SET + screentip_set = TRUE + + if(screentip_set) + . = CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/rnd/destructive_analyzer/examine(mob/user) + . = ..() + if(!in_range(user, src) && !isobserver(user)) + return + + if(loaded_item) + . += span_notice("[EXAMINE_HINT("Left-Click")] to remove loaded item inside.") + else + . += span_notice("An item can be loaded inside via [EXAMINE_HINT("Left-Click")].") /obj/machinery/rnd/destructive_analyzer/attackby(obj/item/weapon, mob/living/user, params) if(user.combat_mode) diff --git a/code/modules/research/machinery/_production.dm b/code/modules/research/machinery/_production.dm index dd282040ab0..0ae8df4d8a4 100644 --- a/code/modules/research/machinery/_production.dm +++ b/code/modules/research/machinery/_production.dm @@ -1,38 +1,37 @@ /obj/machinery/rnd/production name = "technology fabricator" desc = "Makes researched and prototype items with materials and energy." - layer = BELOW_OBJ_LAYER /// The efficiency coefficient. Material costs and print times are multiplied by this number; - /// better parts result in a higher efficiency (and lower value). var/efficiency_coeff = 1 - /// The material storage used by this fabricator. var/datum/component/remote_materials/materials - /// Which departments are allowed to process this design var/allowed_department_flags = ALL - - /// What's flick()'d on print. + /// Icon state when production has started var/production_animation - /// The types of designs this fabricator can print. var/allowed_buildtypes = NONE - /// All designs in the techweb that can be fabricated by this machine, since the last update. var/list/datum/design/cached_designs - /// What color is this machine's stripe? Leave null to not have a stripe. var/stripe_color = null - - /// Does this charge the user's ID on fabrication? - var/charges_tax = TRUE + ///direction we output onto (if 0, on top of us) + var/drop_direction = 0 /obj/machinery/rnd/production/Initialize(mapload) . = ..() cached_designs = list() - materials = AddComponent(/datum/component/remote_materials, mapload) + materials = AddComponent( + /datum/component/remote_materials, \ + mapload, \ + mat_container_signals = list( \ + COMSIG_MATCONTAINER_ITEM_CONSUMED = TYPE_PROC_REF(/obj/machinery/rnd, local_material_insert) + ) \ + ) + + RegisterSignal(src, COMSIG_SILO_ITEM_CONSUMED, TYPE_PROC_REF(/obj/machinery/rnd/production, silo_material_insert)) AddComponent( /datum/component/payment, \ @@ -42,9 +41,49 @@ TRUE, \ ) - RefreshParts() update_icon(UPDATE_OVERLAYS) +/obj/machinery/rnd/production/Destroy() + materials = null + cached_designs = null + return ..() + + +// Stuff for the stripe on the department machines +/obj/machinery/rnd/production/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/screwdriver) + . = ..() + + update_icon(UPDATE_OVERLAYS) + +/obj/machinery/rnd/production/update_overlays() + . = ..() + + if(!stripe_color) + return + + var/mutable_appearance/stripe = mutable_appearance('icons/obj/machines/research.dmi', "protolathe_stripe[panel_open ? "_t" : ""]") + stripe.color = stripe_color + . += stripe + + +/obj/machinery/rnd/production/examine(mob/user) + . = ..() + if(!in_range(user, src) && !isobserver(user)) + return + + . += span_notice("Material usage cost at [efficiency_coeff * 100]%") + if(drop_direction) + . += span_notice("Currently configured to drop printed objects [dir2text(drop_direction)].") + . += span_notice("[EXAMINE_HINT("Alt-click")] to reset.") + else + . += span_notice("[EXAMINE_HINT("Drag")] towards a direction (while next to it) to change drop direction.") + +/obj/machinery/rnd/production/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(drop_direction) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Reset Drop" + return CONTEXTUAL_SCREENTIP_SET + /obj/machinery/rnd/production/connect_techweb(datum/techweb/new_techweb) if(stored_research) UnregisterSignal(stored_research, list(COMSIG_TECHWEB_ADD_DESIGN, COMSIG_TECHWEB_REMOVE_DESIGN)) @@ -55,24 +94,14 @@ RegisterSignals( stored_research, list(COMSIG_TECHWEB_ADD_DESIGN, COMSIG_TECHWEB_REMOVE_DESIGN), - PROC_REF(on_techweb_update) + TYPE_PROC_REF(/obj/machinery/rnd/production, on_techweb_update) ) update_designs() -/obj/machinery/rnd/production/Destroy() - materials = null - cached_designs = null - return ..() - -/obj/machinery/rnd/production/proc/on_techweb_update() - SIGNAL_HANDLER - - // We're probably going to get more than one update (design) at a time, so batch - // them together. - addtimer(CALLBACK(src, PROC_REF(update_designs)), 2 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) - /// Updates the list of designs this fabricator can print. /obj/machinery/rnd/production/proc/update_designs() + PROTECTED_PROC(TRUE) + var/previous_design_count = cached_designs.len cached_designs.Cut() @@ -91,12 +120,98 @@ update_static_data_for_all_viewers() +/obj/machinery/rnd/production/proc/on_techweb_update() + SIGNAL_HANDLER + + // We're probably going to get more than one update (design) at a time, so batch + // them together. + addtimer(CALLBACK(src, PROC_REF(update_designs)), 2 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) + +///When materials are instered via silo link +/obj/machinery/rnd/proc/silo_material_insert(obj/machinery/rnd/machine, container, obj/item/item_inserted, last_inserted_id, list/mats_consumed, amount_inserted) + SIGNAL_HANDLER + + process_item(item_inserted, mats_consumed, amount_inserted) + +/** + * Consumes power for the item inserted either into silo or local storage. + * Arguments + * + * * obj/item/item_inserted - the item to process + * * list/mats_consumed - list of mats consumed + * * amount_inserted - amount of material actually processed + */ +/obj/machinery/rnd/proc/process_item(obj/item/item_inserted, list/mats_consumed, amount_inserted) + PRIVATE_PROC(TRUE) + + if(directly_use_power(round((amount_inserted / SHEET_MATERIAL_AMOUNT) * active_power_usage * 0.00025))) + var/mat_name = "iron" + + var/highest_mat = 0 + for(var/datum/material/mat as anything in mats_consumed) + var/present_mat = mats_consumed[mat] + if(present_mat > highest_mat) + mat_name = initial(mat.name) + if(mat_name == "silver" || mat_name == "titanium" || mat_name == "plastic") //these materials have similar appearances so use an common overlay for them + mat_name = "shiny" + highest_mat = present_mat + + flick_animation(mat_name) +/** + * Plays an visual animation when materials are inserted + * Arguments + * + * * mat_name - the name of the material we are trying to animate on the machine + */ +/obj/machinery/rnd/proc/flick_animation(mat_name) + PROTECTED_PROC(TRUE) + SHOULD_CALL_PARENT(FALSE) + + flick_overlay_view(mutable_appearance('icons/obj/machines/research.dmi', "protolathe_[mat_name]"), 1 SECONDS) + +///When materials are instered into local storage +/obj/machinery/rnd/proc/local_material_insert(container, obj/item/item_inserted, last_inserted_id, list/mats_consumed, amount_inserted, atom/context) + SIGNAL_HANDLER + + process_item(item_inserted, mats_consumed, amount_inserted) + /obj/machinery/rnd/production/RefreshParts() . = ..() - calculate_efficiency() + if(materials) + var/total_storage = 0 + for(var/datum/stock_part/matter_bin/bin in component_parts) + total_storage += bin.tier * 37.5 * SHEET_MATERIAL_AMOUNT + materials.set_local_size(total_storage) + + efficiency_coeff = compute_efficiency() + update_static_data_for_all_viewers() +///Computes this machines cost efficiency based on the available parts +/obj/machinery/rnd/production/proc/compute_efficiency() + PROTECTED_PROC(TRUE) + + var/efficiency = 1.2 + for(var/datum/stock_part/servo/servo in component_parts) + efficiency -= servo.tier * 0.1 + + return efficiency + +/** + * The cost efficiency for an particular design + * Arguments + * + * * path - the design path to check for + */ +/obj/machinery/rnd/production/proc/build_efficiency(path) + PROTECTED_PROC(TRUE) // NOVA EDIT CHANGE - ORIGINAL: PRIVATE_PROC(TRUE) + + if(ispath(path, /obj/item/stack/sheet) || ispath(path, /obj/item/stack/ore/bluespace_crystal)) + return 1 + else + return efficiency_coeff + /obj/machinery/rnd/production/ui_assets(mob/user) return list( get_asset_datum(/datum/asset/spritesheet/sheetmaterials), @@ -117,16 +232,13 @@ var/datum/asset/spritesheet/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet/research_designs) var/size32x32 = "[spritesheet.name]32x32" - var/max_multiplier = INFINITY var/coefficient for(var/datum/design/design in cached_designs) var/cost = list() - max_multiplier = INFINITY coefficient = build_efficiency(design.build_path) for(var/datum/material/mat in design.materials) cost[mat.name] = OPTIMAL_COST(design.materials[mat] * coefficient) - max_multiplier = min(max_multiplier, 50, round(materials.mat_container.get_material_amount(mat) / cost[mat.name])) var/icon_size = spritesheet.icon_size_id(design.id) designs[design.id] = list( @@ -135,9 +247,7 @@ "cost" = cost, "id" = design.id, "categories" = design.category, - "icon" = "[icon_size == size32x32 ? "" : "[icon_size] "][design.id]", - "constructionTime" = 0, - "maxmult" = max_multiplier + "icon" = "[icon_size == size32x32 ? "" : "[icon_size] "][design.id]" ) data["designs"] = designs @@ -158,141 +268,127 @@ /obj/machinery/rnd/production/ui_act(action, list/params, datum/tgui/ui) . = ..() - if(.) return - . = TRUE - switch (action) if("remove_mat") var/datum/material/material = locate(params["ref"]) - var/amount = text2num(params["amount"]) - // SAFETY: eject_sheets checks for valid mats - materials.eject_sheets(material, amount) - - if("build") - user_try_print_id(ui.user, params["ref"], params["amount"]) - -/// Updates the fabricator's efficiency coefficient based on the installed parts. -/obj/machinery/rnd/production/proc/calculate_efficiency() - efficiency_coeff = 1 - - if(materials) - var/total_storage = 0 - - for(var/datum/stock_part/matter_bin/bin in component_parts) - total_storage += bin.tier * (37.5*SHEET_MATERIAL_AMOUNT) - - materials.set_local_size(total_storage) - - var/total_rating = 1.2 - - for(var/datum/stock_part/servo/servo in component_parts) - total_rating -= servo.tier * 0.1 - - efficiency_coeff = max(total_rating, 0) - -/obj/machinery/rnd/production/proc/build_efficiency(path) - if(ispath(path, /obj/item/stack/sheet) || ispath(path, /obj/item/stack/ore/bluespace_crystal)) - return 1 - else - return efficiency_coeff - -/obj/machinery/rnd/production/proc/user_try_print_id(mob/user, design_id, print_quantity) - if(!design_id) - return FALSE - - if(istext(print_quantity)) - print_quantity = text2num(print_quantity) - - if(isnull(print_quantity)) - print_quantity = 1 - - var/datum/design/design = stored_research.researched_designs[design_id] ? SSresearch.techweb_design_by_id(design_id) : null - - if(!istype(design)) - return FALSE - - if(busy) - say("Warning: fabricator is busy!") - return FALSE - - if(!(isnull(allowed_department_flags) || (design.departmental_flags & allowed_department_flags))) - say("This fabricator does not have the necessary keys to decrypt this design.") - return FALSE - - if(design.build_type && !(design.build_type & allowed_buildtypes)) - say("This fabricator does not have the necessary manipulation systems for this design.") - return FALSE + if(!istype(material)) + return - if(!materials.mat_container) - say("No connection to material storage, please contact the quartermaster.") - return FALSE + var/amount = params["amount"] + if(isnull(amount)) + return - if(materials.on_hold()) - say("Mineral access is on hold, please contact the quartermaster.") - return FALSE + amount = text2num(amount) + if(isnull(amount)) + return - print_quantity = clamp(print_quantity, 1, 50) - var/coefficient = build_efficiency(design.build_path) - - // check if sufficient materials are available. - if(!materials.mat_container.has_materials(design.materials, coefficient, print_quantity)) - say("Not enough materials to complete prototype[print_quantity > 1 ? "s" : ""].") - return FALSE - - //use power - var/total_charge = 0 - for(var/material in design.materials) - total_charge += round(design.materials[material] * coefficient * print_quantity / 35) - var/charge_per_item = total_charge / print_quantity - - if(production_animation) - flick(production_animation, src) - - var/total_time = (design.construction_time * design.lathe_time_factor * print_quantity) ** 0.8 - var/time_per_item = total_time / print_quantity - start_making(design, print_quantity, time_per_item, coefficient, charge_per_item) - - return TRUE - -/// Begins the act of making the given design the given number of items -/// Does not check or use materials/power/etc -/obj/machinery/rnd/production/proc/start_making(datum/design/design, build_count, build_time_per_item, build_efficiency, charge_per_item) - PROTECTED_PROC(TRUE) - - busy = TRUE - update_static_data_for_all_viewers() - addtimer(CALLBACK(src, PROC_REF(do_make_item), design, build_efficiency, build_time_per_item, charge_per_item, build_count), build_time_per_item) + materials.eject_sheets(material, amount) + return TRUE -/// Callback for start_making, actually makes the item -/// Called using timers started by start_making -/obj/machinery/rnd/production/proc/do_make_item(datum/design/design, build_efficiency, time_per_item, charge_per_item, items_remaining) + if("build") + if(busy) + say("Warning: fabricator is busy!") + return + + //validate design + var/design_id = params["ref"] + if(!design_id) + return + var/datum/design/design = stored_research.researched_designs[design_id] ? SSresearch.techweb_design_by_id(design_id) : null + if(!istype(design)) + return FALSE + if(!(isnull(allowed_department_flags) || (design.departmental_flags & allowed_department_flags))) + say("This fabricator does not have the necessary keys to decrypt this design.") + return FALSE + if(design.build_type && !(design.build_type & allowed_buildtypes)) + say("This fabricator does not have the necessary manipulation systems for this design.") + return FALSE + + //validate print quantity + var/print_quantity = params["amount"] + if(isnull(print_quantity)) + return + print_quantity = text2num(print_quantity) + if(isnull(print_quantity)) + return + print_quantity = clamp(print_quantity, 1, 50) + + //efficiency for this design, stacks use exact materials + var/coefficient = build_efficiency(design.build_path) + + //check for materials + if(!materials.can_use_resource()) + return + if(!materials.mat_container.has_materials(design.materials, coefficient, print_quantity)) + say("Not enough materials to complete prototype[print_quantity > 1 ? "s" : ""].") + return FALSE + + //compute power & time to print 1 item + var/charge_per_item = 0 + for(var/material in design.materials) + charge_per_item += design.materials[material] + charge_per_item = min(active_power_usage, round(charge_per_item * coefficient)) + var/build_time_per_item = (design.construction_time * design.lathe_time_factor) ** 0.8 + + //start production + busy = TRUE + SStgui.update_uis(src) + if(production_animation) + icon_state = production_animation + start_printing_visuals() // NOVA EDIT ADDITION - COLONY FABRICATOR STUFF + var/turf/target_location + if(drop_direction) + target_location = get_step(src, drop_direction) + if(isclosedturf(target_location)) + target_location = get_turf(src) + else + target_location = get_turf(src) + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, print_quantity, build_time_per_item, coefficient, charge_per_item, target_location), build_time_per_item) + + return TRUE + +/** + * Callback for start_making, actually makes the item + * Arguments + * + * * datum/design/design - the design we are trying to print + * * items_remaining - the number of designs left out to print + * * build_time_per_item - the time taken to print 1 item + * * material_cost_coefficient - the cost efficiency to print 1 design + * * charge_per_item - the amount of power to print 1 item + * * turf/target - the location to drop the printed item on +*/ +/obj/machinery/rnd/production/proc/do_make_item(datum/design/design, items_remaining, build_time_per_item, material_cost_coefficient, charge_per_item, turf/target) PROTECTED_PROC(TRUE) if(!items_remaining) // how finalize_build() return - if(!directly_use_power(charge_per_item)) + if(!is_operational || !directly_use_power(charge_per_item)) say("Unable to continue production, power failure.") finalize_build() return + if(!materials.can_use_resource()) + finalize_build() + return var/is_stack = ispath(design.build_path, /obj/item/stack) var/list/design_materials = design.materials - if(!materials.mat_container.has_materials(design_materials, build_efficiency, is_stack ? items_remaining : 1)) + if(!materials.mat_container.has_materials(design_materials, material_cost_coefficient, is_stack ? items_remaining : 1)) say("Unable to continue production, missing materials.") return - materials.use_materials(design_materials, build_efficiency, is_stack ? items_remaining : 1, "built", "[design.name]") + materials.use_materials(design_materials, material_cost_coefficient, is_stack ? items_remaining : 1, "built", "[design.name]") var/atom/movable/created if(is_stack) - created = new design.build_path(get_turf(src), items_remaining) + created = new design.build_path(target, items_remaining) else - created = new design.build_path(get_turf(src)) - split_materials_uniformly(design_materials, build_efficiency, created) + created = new design.build_path(target) + split_materials_uniformly(design_materials, material_cost_coefficient, created) created.pixel_x = created.base_pixel_x + rand(-6, 6) created.pixel_y = created.base_pixel_y + rand(-6, 6) @@ -305,40 +401,36 @@ if(!items_remaining) finalize_build() return - addtimer(CALLBACK(src, PROC_REF(do_make_item), design, build_efficiency, time_per_item, items_remaining), time_per_item) + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, items_remaining, build_time_per_item, material_cost_coefficient, charge_per_item, target), build_time_per_item) /// Resets the busy flag /// Called at the end of do_make_item's timer loop /obj/machinery/rnd/production/proc/finalize_build() PROTECTED_PROC(TRUE) - busy = FALSE - update_static_data_for_all_viewers() -// Stuff for the stripe on the department machines -/obj/machinery/rnd/production/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/screwdriver) - . = ..() - - update_icon(UPDATE_OVERLAYS) + busy = FALSE + SStgui.update_uis(src) + icon_state = initial(icon_state) -/obj/machinery/rnd/production/update_overlays() +/obj/machinery/rnd/production/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) . = ..() - - if(!stripe_color) + if((!issilicon(usr) && !isAdminGhostAI(usr)) && !Adjacent(usr)) return + if(busy) + balloon_alert(usr, "busy printing!") + return + var/direction = get_dir(src, over_location) + if(!direction) + return + drop_direction = direction + balloon_alert(usr, "dropping [dir2text(drop_direction)]") - var/mutable_appearance/stripe = mutable_appearance('icons/obj/machines/research.dmi', "protolate_stripe") - - if(!panel_open) - stripe.icon_state = "protolathe_stripe" - else - stripe.icon_state = "protolathe_stripe_t" - - stripe.color = stripe_color - - . += stripe - -/obj/machinery/rnd/production/examine(mob/user) +/obj/machinery/rnd/production/AltClick(mob/user) . = ..() - - if(in_range(user, src) || isobserver(user)) - . += span_notice("The status display reads: Storing up to [materials.local_size] material units.
Material consumption at [efficiency_coeff * 100]%.
Build time reduced by [100 - efficiency_coeff * 100]%.") + if(!drop_direction || !can_interact(user)) + return + if(busy) + balloon_alert(user, "busy printing!") + return + balloon_alert(user, "drop direction reset") + drop_direction = 0 diff --git a/code/modules/research/machinery/circuit_imprinter.dm b/code/modules/research/machinery/circuit_imprinter.dm index 8127bc45d0a..2dcbde23663 100644 --- a/code/modules/research/machinery/circuit_imprinter.dm +++ b/code/modules/research/machinery/circuit_imprinter.dm @@ -2,23 +2,22 @@ name = "circuit imprinter" desc = "Manufactures circuit boards for the construction of machines." icon_state = "circuit_imprinter" - circuit = /obj/item/circuitboard/machine/circuit_imprinter production_animation = "circuit_imprinter_ani" + circuit = /obj/item/circuitboard/machine/circuit_imprinter allowed_buildtypes = IMPRINTER -/obj/machinery/rnd/production/circuit_imprinter/calculate_efficiency() - . = ..() - +/obj/machinery/rnd/production/circuit_imprinter/compute_efficiency() var/rating = 0 - for(var/datum/stock_part/servo/servo in component_parts) - rating += servo.tier // There is only one. + rating += servo.tier + + return 0.5 ** max(rating - 1, 0) // One sheet, half sheet, quarter sheet, eighth sheet. - efficiency_coeff = 0.5 ** max(rating - 1, 0) // One sheet, half sheet, quarter sheet, eighth sheet. +/obj/machinery/rnd/production/circuit_imprinter/flick_animation(mat_name) + return //we presently have no animation /obj/machinery/rnd/production/circuit_imprinter/offstation name = "ancient circuit imprinter" desc = "Manufactures circuit boards for the construction of machines. Its ancient construction may limit its ability to print all known technology." allowed_buildtypes = AWAY_IMPRINTER circuit = /obj/item/circuitboard/machine/circuit_imprinter/offstation - charges_tax = FALSE diff --git a/code/modules/research/machinery/departmental_protolathe.dm b/code/modules/research/machinery/departmental_protolathe.dm index a4874876ba2..70e3959f5f7 100644 --- a/code/modules/research/machinery/departmental_protolathe.dm +++ b/code/modules/research/machinery/departmental_protolathe.dm @@ -11,10 +11,6 @@ stripe_color = "#EFB341" payment_department = ACCOUNT_ENG -/obj/machinery/rnd/production/protolathe/department/engineering/no_tax - circuit = /obj/item/circuitboard/machine/protolathe/department/engineering/no_tax - charges_tax = FALSE - /obj/machinery/rnd/production/protolathe/department/service name = "department protolathe (Service)" allowed_department_flags = DEPARTMENT_BITFLAG_SERVICE diff --git a/code/modules/research/machinery/protolathe.dm b/code/modules/research/machinery/protolathe.dm index b48f64ca9d7..fb912bba67c 100644 --- a/code/modules/research/machinery/protolathe.dm +++ b/code/modules/research/machinery/protolathe.dm @@ -23,4 +23,3 @@ desc = "Converts raw materials into useful objects. Its ancient construction may limit its ability to print all known technology." circuit = /obj/item/circuitboard/machine/protolathe/offstation allowed_buildtypes = AWAY_LATHE - charges_tax = FALSE diff --git a/code/modules/research/machinery/techfab.dm b/code/modules/research/machinery/techfab.dm index 38df3fc038d..7f1428882e7 100644 --- a/code/modules/research/machinery/techfab.dm +++ b/code/modules/research/machinery/techfab.dm @@ -3,6 +3,5 @@ desc = "Produces researched prototypes with raw materials and energy." icon_state = "protolathe" circuit = /obj/item/circuitboard/machine/techfab - console_link = FALSE production_animation = "protolathe_n" allowed_buildtypes = PROTOLATHE | IMPRINTER diff --git a/code/modules/research/rdmachines.dm b/code/modules/research/rdmachines.dm index 4ab9d73dca7..a8fa3e36d47 100644 --- a/code/modules/research/rdmachines.dm +++ b/code/modules/research/rdmachines.dm @@ -7,21 +7,22 @@ icon = 'icons/obj/machines/research.dmi' density = TRUE use_power = IDLE_POWER_USE + + ///Are we currently printing a machine var/busy = FALSE + ///Is this machne hacked via wires var/hacked = FALSE - var/console_link = TRUE //allow console link. + ///Is this machine disabled via wires var/disabled = FALSE - /// Ref to global science techweb. + ///Ref to global science techweb. var/datum/techweb/stored_research ///The item loaded inside the machine, used by experimentors and destructive analyzers only. var/obj/item/loaded_item -/obj/machinery/rnd/proc/reset_busy() - busy = FALSE - /obj/machinery/rnd/Initialize(mapload) . = ..() set_wires(new /datum/wires/rnd(src)) + register_context() /obj/machinery/rnd/LateInitialize() . = ..() @@ -37,6 +38,46 @@ QDEL_NULL(wires) return ..() +/obj/machinery/rnd/examine(mob/user) + . = ..() + if(!in_range(user, src) && !isobserver(user)) + return + + . += span_notice("A [EXAMINE_HINT("multitool")] with techweb designs can be uploaded here.") + . += span_notice("Its maintainence panel can be [EXAMINE_HINT("screwed")] [panel_open ? "closed" : "open"].") + if(panel_open) + . += span_notice("Use a [EXAMINE_HINT("multitool")] or [EXAMINE_HINT("wirecutters")] to interact with wires.") + . += span_notice("The machine can be [EXAMINE_HINT("pried")] apart.") + +/obj/machinery/rnd/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = NONE + if(isnull(held_item)) + return + + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] Panel" + context[SCREENTIP_CONTEXT_RMB] = "[panel_open ? "Close" : "Open"] Panel" + return CONTEXTUAL_SCREENTIP_SET + + if(panel_open) + var/msg + if(held_item.tool_behaviour == TOOL_CROWBAR) + msg = "Deconstruct" + else if(is_wire_tool(held_item)) + msg = "Open Wires" + + if(msg) + context[SCREENTIP_CONTEXT_LMB] = msg + context[SCREENTIP_CONTEXT_RMB] = msg + return CONTEXTUAL_SCREENTIP_SET + else + if(held_item.tool_behaviour == TOOL_MULTITOOL) + var/obj/item/multitool/tool = held_item + if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb)) + context[SCREENTIP_CONTEXT_LMB] = "Upload Techweb" + context[SCREENTIP_CONTEXT_RMB] = "Upload Techweb" + return CONTEXTUAL_SCREENTIP_SET + ///Called when attempting to connect the machine to a techweb, forgetting the old. /obj/machinery/rnd/proc/connect_techweb(datum/techweb/new_techweb) if(stored_research) @@ -49,52 +90,42 @@ /obj/machinery/rnd/proc/on_connected_techweb() SHOULD_CALL_PARENT(FALSE) -/obj/machinery/rnd/proc/shock(mob/user, prb) - if(machine_stat & (BROKEN|NOPOWER)) // unpowered, no shock - return FALSE - if(!prob(prb)) - return FALSE - do_sparks(5, TRUE, src) - if (electrocute_mob(user, get_area(src), src, 0.7, TRUE)) - return TRUE - else - return FALSE +///Reset the state of this machine +/obj/machinery/rnd/proc/reset_busy() + busy = FALSE /obj/machinery/rnd/crowbar_act(mob/living/user, obj/item/tool) return default_deconstruction_crowbar(tool) /obj/machinery/rnd/crowbar_act_secondary(mob/living/user, obj/item/tool) - return default_deconstruction_crowbar(tool) + return crowbar_act(user, tool) /obj/machinery/rnd/screwdriver_act(mob/living/user, obj/item/tool) return default_deconstruction_screwdriver(user, "[initial(icon_state)]_t", initial(icon_state), tool) /obj/machinery/rnd/screwdriver_act_secondary(mob/living/user, obj/item/tool) - return default_deconstruction_screwdriver(user, "[initial(icon_state)]_t", initial(icon_state), tool) + return screwdriver_act(user, tool) /obj/machinery/rnd/multitool_act(mob/living/user, obj/item/multitool/tool) + . = ITEM_INTERACT_BLOCKING if(panel_open) wires.interact(user) - return TRUE + return ITEM_INTERACT_SUCCESS if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb)) connect_techweb(tool.buffer) - return TRUE - return FALSE + return ITEM_INTERACT_SUCCESS /obj/machinery/rnd/multitool_act_secondary(mob/living/user, obj/item/tool) - if(panel_open) - wires.interact(user) - return TRUE + return multitool_act(user, tool) /obj/machinery/rnd/wirecutter_act(mob/living/user, obj/item/tool) + . = ITEM_INTERACT_BLOCKING if(panel_open) wires.interact(user) - return TRUE + return ITEM_INTERACT_SUCCESS /obj/machinery/rnd/wirecutter_act_secondary(mob/living/user, obj/item/tool) - if(panel_open) - wires.interact(user) - return TRUE + return wirecutter_act(user, tool) //whether the machine can have an item inserted in its current state. /obj/machinery/rnd/proc/is_insertion_ready(mob/user) @@ -123,15 +154,3 @@ if(loaded_item) loaded_item.forceMove(drop_location()) ..() - -/obj/machinery/rnd/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) - var/stack_name - if(istype(item_inserted, /obj/item/stack/ore/bluespace_crystal)) - stack_name = "bluespace" - use_power(SHEET_MATERIAL_AMOUNT / 10) - else - var/obj/item/stack/S = item_inserted - stack_name = S.name - use_power(min(active_power_usage, (amount_inserted / 100))) - add_overlay("protolathe_[stack_name]") - addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, cut_overlay), "protolathe_[stack_name]"), 10) diff --git a/code/modules/wiremod/core/component_printer.dm b/code/modules/wiremod/core/component_printer.dm index 3fb736540ec..7c691e3a4c4 100644 --- a/code/modules/wiremod/core/component_printer.dm +++ b/code/modules/wiremod/core/component_printer.dm @@ -184,8 +184,7 @@ "cost" = cost, "id" = researched_design_id, "categories" = design.category, - "icon" = "[icon_size == size32x32 ? "" : "[icon_size] "][design.id]", - "constructionTime" = -1 + "icon" = "[icon_size == size32x32 ? "" : "[icon_size] "][design.id]" ) data["designs"] = designs diff --git a/icons/obj/machines/research.dmi b/icons/obj/machines/research.dmi index 02d848eb4e9dab872564abb8b9cac79bb5662a98..544054279e97af245675d35f021ae8328d8bda33 100644 GIT binary patch literal 28134 zcmb@uc|4ST`#*e+eJdhT*-J#C5ZOi{AzRjnQAEgAmMqPTs4R)HWgSH+LJP(|3JHlU zk!|cdV`rG<`HZgXy6^A(x}VqceLcV5ALd-ld49HIc^~iN_)MgUk?tP0Lu?QP?a{la zeHnu2z$qQX!UPU$ce}R1#myjdYhUdfcN}lKdi%P1c|y?L^lHn`o}+SH>Jo@+B|SO! zq5ieFmMFJ|7wYqz+Q(A3WS@BaW=bM&tBWW;yjyE779|j>NND}OdiQkO_|#5Ox5O3X zyHWL&+dQ!sO%Q%VH3RVc%np;ZN>bQ-NJL3E4d0hd~0R`-Ioqu zj&l`IO!fEPjya;gvxkHCdu%hGk2!s=?&Y{9gG{x%4f~QWXXxe^7e6!W|I%6?)Cutl zIEM1K4U8XX%8-7dAn^Q+DfX~Y%yK1CHD2AA4$-qNosx7hFLoLGv`VNG!UWahnd zas2fzfu0cN3qRS)_r&^Cbeg}nQ+jRC@OEbUP4nebMoE5pj1!HhEQ2Rb${A~mubYO; z8-hL&*S`ilXHu|ew06A$`-T5#W%UE=l+(@Zrb(cGMye-e26>COQ*W2Ldi0rw-QB>WD~xz~Ydf<} zWQf^QLz_RU=X`a04b8}WyXBy077!CBp7-@uDz!)C{>e3Q z=Yk2ocyE87S)rt;LZXLW&G)*fm7>v(_v#6W^9f9jM=$TJp1Wf2@|kxI&bDQe-M z-9?q;tSAHeZ?yq?F7&8n*~?xNyz%9J7Tw{^LWk}v);BkuA;v>QUM{rO>v74PRGkrv z^kmzQ_D=%jwN7I=_#mVs6d~)%!qZhw%dIRAY&h6MYOdbO?+ZIP5kK~8lX&|B=|y8* z6I&>9dnOkHJ_3Ff{RXO;hWPEK41;eCSv>4{LAy`;XTvrRP}lbq!Ae<;L@f+`;^qSnXW7i}n;Upf1Q{A}mjI`u-si1>N3z7q6;VvW6sOSMfvw8(tH zd|N1nr}XRLgs^6mN5Z7?li=v`>s|YVe>1_Cl1&Gd?CfRKdiWy8scd9VS}1arvTf{k zb+rdIik&|*mppiG8L2vWf$U!yvz6gJx2`(zYb>B6<2#{P^(i9UwncXAz3K}DTJ;6~ zv%)u6ZJ%G_$Q<8E2zqDV$+OPK$M^F6`}TvCbbkhA)+S3QgOjY9jdg>St>j6=pieG{ z2kG-CAWbtfp40L>bA8s=uDz(rJUES=WY(ljs#iXq#eXN}JnqB#=r%OI_j}Cty`jLV zH2sJx(@Im=ISTF|b#oH$9NqsI;g8`XVEaAl3E{Ry_~Te1T>PRFGIubNBsO4!EawpL z&nN0k*tVd4PuvMF?CTF5)p_FLeJiv{+5BT6sM6%gad0F#Ol{X9vwYDu2s?>{nT4NcdNcEc`1U z2a}0ib^m3$$LDE-p@~WKYJLr~$bPo7$GSdw5XHcvN{=PCvaN?3Br_MD^o9?u9oc?;aMthj}J7R zYA~$YXpH{i5=(vgYLEN!wS}F#lW%!haSxra)ZbP^b@yA^8a@?OJ&OM7JUqXQNRURA z$jZO6{ih|M1kA&AeVc-MX#4p!gu)s6H}~MDeLs6K{WTc@+}+S{cG!;deqKz8$M@&U z5W1qmX8+zF+V1YsHt?2k1-qE&z4I{WUD>IXmHTgPabj)x`S}e$k1#tQHAv{BxMbRx zCQjF}|7(7Yx6V3X+9YD9_6UVleSR7vnJdlu;Cyf3uW`>0+y0M-a}WhHh#Umx-&hdc9!wU?0acjs}-)h%L0FUiZ{7X-F13s-IUih7cRnUOwq$#NxlrRA8F$lmAi z^jE9f{o%tQ6yuE<_G0>28N7sX<-{-6r!#aU2ptGmq|D3QSoFXSAi(kQ)trG-y|D28Zv+=WEq&XEJXm)Qm4EmV2F`6gn)xQ`Jmc&E7 z&=QHNoTR>6m(*}qcMDWam4S@ZpJh!+hwO@y{f5LB{f6ZDj2jlhQ}~p4dgu*lsRoa%P)pCLvU)j8*@<(4JbQjJB&oC7E zVK!?+=^=EpjF<8(b@58{`$gB`N*A5i*wPRl2wbo%M}MP+&IXLV&Jb-EF6;|6`@kc! z74J_MGHi==tVMBXevZ2?P_E(AsS8|U(;1iN%r$DlX<4!J3 zZTE~*6&Sq;t7BhSUzxU+c7!MvtArk3Cf+s$rrsYV>3g-u@um?G7O;x9C7Z&~efNsc zEB;z?Bc(F&T?Fq#+}nxvk2>PM!>?^Jbk!2953Oy%l#`gMk+~fX^#tuhkGD4sXJ%q< z=^i>k^r(GDt&ik!9Z61gxmVM)QVg7BV8t)8qw1n`C&3UW0KK!bI#>gg7>Yw^LQq}w zC9i(2KT@;K90pUvBJX^;!L29?&h2tq?Jb^H=MoUod&J0Y?RZu43zLhVT=^jqB{iqNJvjC(KS#SU z97@!H%IUM=%HEsN220h%LAX)lNe{Ri68h4SHBkgXFxQLI2jU*)ARl#RwGbQ*`R)kn zhl%3oUFGJjUVoOMYCC+XPbu795aZ`?_-6cTtd}s=RQK%Jh)>JlcISjOu|HEo_%h?i zYAK(_hM5WZeQXb=35*ohwkq_!o*zD4!o1L}zA{$L9M%)EaN!>6RwWdqaXF6V_)6lU z)&2vV(a{g%)#=XBj5WamSFc-Sd@l1ke@g+}?#XY&;Rnq)9WW1azP&60QhAnxi__Wh z(>>=L+N|U>Px-ds+$S2I>`Xu+{2aqWJ4o@ii~ex2gEn*diYPF2@>=k3S7O_)bq2$YtXB7C7`ZoTL`Va zDf#ZDOaGr`UIR;eDV-#7RbJxp9$6;`LMEYMkUq(1J3jX-hIIJw?1x(v$Gm>z){C8Z zOSv^>?`|wLLfD?2G;+{kEE*rs01uRU?~=TVY&`(e>u``mSA-E9zxT?1-D9BtF#F@} zY-eX6C~Th{M5O40sO8f%bXq-~EQNEQ4F3>dyjJDZI@sCb;#O*ZQAS`*!g;0Z;souL z%Q^w1V1F3A>Q^8TQ?=EusK@T8DoHip-iWdXY!4rs2K~<%^=ux5fctqIOhe48^Z%8uhBGl>i<6B?EE3+R4?lcaEPN3>L#PPX^TxyEq zS29m#Agt2T{_$HZg@<+=iN{Cy&IY?PoUx?rvufoVORl=CQcN$++k%%%Ur6&u*VPJZ{&9xIA zBl1E53KFF7-@Nf9ca|Qx1pV^qC|c8>IlJ*o%>s47!onhbsZUsz4Wmwp&WkK=epEsj zOUbo7N;Ie*Mmp`n6ZbTP&%fY0ba-~GLldV&PQ3?dyt{|j z4?3}nGYH~18|DvSX*xRY#24-)8@6SCcwxUf%iUhHl7p{A{%`P+c96#|K6?B=@lgxM zZjTO-k*GcT1})<+v?YB7>U1^2YeyO&E+viX5TB2?U=ik$kt$*x1R2zqHkfo8&k9rQ zxuM~2=hEM#T${S`RQ8~uMb%RiccRlDA^wc8vWh0IO3S%k9*_Uhu{4pa)YL+g&qSP| zTHzyr-nI?5P|Nsh1I2PfZ_^iZ5G}6aR91ROTmoL81yCLOQtOTHlX?hNysIwxLS4H$=t(qVV-nGZZtptVt>l(PO_AQ5 z^7m~As6W{wO)laaWkD!-0yVwtJr5TpH<9yu)bh^~Dju-Yv?(6RO;68d%+*b|i@mx5 ztXe5%mq?(CqxvW)6@y^1Li(J{bk9bS{p!2dTKZ{2@gn54v~~FV8;5_WxTmX8YeUJ_ zD()9-w)EIOL)t7p_n@JnDP6yH&#PGPPywGdNzCI3PW8++1}9zdi?|BJXkK%ek5J}B|ipqn|oT)ND$Qwgn z%hNwpyS4mkh0tdMPqJ5Jhu#OZ-eJ8#!==6wms+S>PX_sw7H<(ZDP-Z%#TMCplg7Su zd?UK_OQWkh6Bv9=p@hs@a9xoFEP4s!x2&A5SSZoP-zT4XhVo;qmbpmhq336)zMq9n z^D&&!n`5i9NFxq~By49o3|BnBcUPS^+Gu;5c44?pb}b!^`}AIdpKH|E1je!)(CI&W zar5~mN!^n`Aulj4BGq(qX5wNI$%CIy_Q5%lI3Xv#I^=$HB7RgyvM%VMcXW_P}HTqQG7OQ$) zyXINa8=fpY;)tB0>t1@UHU}wa`YkCYK0YHcA}EOp+HcLS$vLsj|j z6+#ls!*n$c7N zTGWgfDM-p8mlP!Jkh_Tsw`4r=1vO`!?6}c$;l+yAvMX02>e<)3lF%suNTC12fyV0+ z{?stnK^Il;VF=2)-Euf@HKv^utDedm$A!AjW)hAFI-$QOm)rziN^bfU&{332$M=CM z-X-N=iY#EW4yW4ooX~Z>;#5_9Vr576a8fT}6X$h2Bk2XclD4#8bsxL|`U4P5`TZ0H zS2iG{jSqPAvkTvITWH#3cqeRyl!JJpKqZz^k1UupaINU1I7ygDkb|U#b1^{!%VgOX zmau4L%2BKh7z^eN9bY2#RGsdKZV&1)I9}^EqbB9rt{a$>+r|ntc7RWLc@^NVF15+k z^{W#T0K0X@TWDIofPcXMkiM9WtXu?((0F}A=)E9C+pyhehsUB;jD+@QJkczBR>HWX z%`oD?Q+hP8yR<;R%=;yqBtOS=9!p6Qcm=|kao(4b_{O64CCLVe2I?i7wK=8a@40}R zzISXe-hJLgGU2`Jr8eHoA?Yp$Im&UmZJr%R(|oiJ5qx_eMVt5i8A^Q@=7v4d{ zOX255TZx|?AeuMaDa<&+ITO19-YrIo1RW#xBSesE8G%YEaz{?$Rf$&j*hJn> zN%Lp%e|BF3EY56CAGM3@D_ObDVZzb6C7t;g8Sc2S&geQ~?9XYfP2m}& z=NmEhq4oHrM zVa~R8;W=YVnCe82E%->58m?)K9c|8^bZ}_V^d;9Ywk47ehOtF~!OpC3cA*|S>JsX& zxa_H?^tkzm`ErD@1r7D-pa>iNp+m3j0eRV9)W@v3J(Z@$+q-ogpg`VsKvSkx2j(WX zbtOtRQS01il6P$n=`v%w--@IPoSCU~eAL{N{Uh8xv}m0DHc%j<%Le@!j}XyO;m-R{ z+HA5&YJg6)UFR9)HQ#~0m)$)B4W}qV`!|P4ltTcOIZnUdx3bcVzsxygCoTK6%a$5R zJ1#YOX{+$mDAFC4rbgsDqu|%>&u zE^03(1yUmPOT-W#cD=S%eZ#8e8$jtyZEPMcSlEuN636vXJ>=OiTwS^6Ci6^Z z=r4r9x_k)ububMHNl8swktWm14`V}ELhGVoBAy{Z#OronXmNz$)qlRuy+U8?=U4I^ zSlY?9qKHFM#0J!6afh9}#5)x`PVk?+&c|CNUv~+=m~GZ&vD?CE!gpJKN=Nc*wx>0K z(IN$sQ{M!8|MNyMQ!F$TFOjPc?miDT9M(Qw8u+<-I!iJkKdM4v+k&_r=Kxf;kv0DPJ zCl!(mCjTwZAh zKuZd063)BQw#pfc^KHW&F^0X-dGB5%+inLLZNcWK)bpGbu}2K&2}dN9r%`$p%KS(MuHLKEHUlcvb0?58LexEE>`(C-zfK$csE`mTS_ zIP8G8Dq*d&=;0eG3JdJyFyp|FV6T-`bui24BbZ0b{v5B-s75d$O#kPXKI4v>;npuo zOD!p>-9P{ygq(}!Iu8wNdlrUJn!H^1Au0Q2T-YH!y<Bo8WpJ~fP9ioR3We3T} z4?{pbI5Y<~LHmyY_j&wbKP;jjc|L+)fgUXyShWsf0_IXpu2Ho-m}cu|HGzUQU2yn0 z^VCO`rYjLvdJp(mu=HR&^@ne2FuwQks{H1w4HSS=ks0Oyo!rR6)RqVhuXPHuFf2WS zlVFe>l61F?$hwr-(uk9!g}jMe`)F+TWoCc6qyJrWfzUczpWWX&V<79-jgcZIKYaQ7 z@~lyf_~lom>L60!AYq-{wi+Ao1O=qla%%E=>h+rm<+0x_47XOX@S?fj6&YvG7H*q~ zQ#(wGQAw!$S1ux*|M{qHCS63#@GpBrRth9x5-svaFyA8`HuhwpGtUEBIM+z{z;4;ktHunOs45@IsnMPgPBd?q5mHRg0!VB1h;agw?6CjP zOfF(iChFpe5Y9gd!>#>)j{mE~;r}knC_v=r60KL0KK!}`v0CBR>o1DnG7yK0YI6XV z|0pAI+uCb~RrE)8ADy?Dl2r}eH!s%_UPx4%|5m7oHb*KJx^O8yV!Jw6=e5p5V4Q{~ z=Nv<+s?$%>K!n70yq_7pYq9vzT(WT=Vd(xp#xkO3`8G6KiZ^)A;}CWF`lzdMt++~P|NMBUs+UaQn{bfl+nLhTU8okl7=W)OaLDLC?RUY-=TlV%jSJ}>e8*Xak2 zp(91UuKC4HfLGtm`dS+96UKNx>7Ov2SM3Wrj~1+xK%=z3FZ+yt)*27c#VZ(Q;&Tl6=b4=Dlt>SnW(PUt0W!-T}j@ZR)>1^m{u`A2Ak`DqZZ?7~>)}FcjZ-tHKfn_HUhoYPf z-l`IlE+(#QZZ9{Ia#0iu44rjGTC{7_3)4xB(^iTjZFovm;pQR2_a>k2Hj+H`Z5+x~ zvJc*Z>+aZTq?tA(=@fMag91kFy$xjf`?89B)y6iR6&d16WBN(+v?DYU9hbnQL`JVi zm0-7~G)^mBT^?V4<0{s&D{Kk4KuToR1{ITpp|{+b%ElnEMQhZhQ)8c&1ur`>_h;SI1iKmTugVG|1 z{o|}yM1R!TLdToySF0B!fM}fn_U<^dtq72$KpNDlZ^1f5Y|`p0(nF5#ym<~+oh^Hf zPeU?6hd6{s>>0HO8e3>0R`dZRyzVxQrtM=ZVOb`Ilv`8U$F9BQ&H_Yfs|IB2qAsBz zrYC-XPwfy^Obg4e@kKpAYODH5|2?^Zjh;oR&3WP@F4OmnB8;qOAVT+rhk@DEQxf58 zCY9rRJLjGtD01XA_b(7S=V%q9R@IZXDdGK0i<(ZRW^J`L~Q;Jr?i)0MR+UY_+{qpbLmz4u~Z(6c(|AIMJK#?glD3803+g zrO@|*6}vVKiaau}GFIa!AeqT;pFK1m?fu~R?Z*!zmHBD#J@T#)mtFE%ejncXk$G=- zNygs9y^bIHjV52>Rl!t(c^3opL0bX-ol23${`2m(((S+U+-+MWwC*b5b{OznwQ`z% z{huAs$lZK#L>h=^%&iCy@dzHpYQUM3`)x*AqSIfe+1kiSDk+WJ)y16X>gj3epYEB| zdLM+%>x@9eR+hhFG|YXX<59O4h1Xc97-$aZ&p^q0s*;@tf=GE8kTKvHXixz-=)xMt zcfQW`z@%D+!Xhoja~=E_*5LF(OeT)hGoNYUKi!UcOP&)%D>0P|c2scijD5mv8~( zCu^{Fm0p*@#qd_^f0R<248Crh@KF2BApMz^pV_)Fs6pIuOh9-$z#&`T`$jV2wk|2q z=gGbXdhdP@?AcMPiX)UQlIldPpDt;cNWgC%U92Q%*pl5ABj7$1K~`w(OvzbB8rhHX zS|jwGItY39d<-$L3Uk}UkST{kD8KhLl5I9?UJW~HFLN}KA{Q$S5)oqWw&Dj@lSzmC zO2l!Q6sF5du(cB{xe$x!(bj2KSVRq7L4H2Zq~&U~Z}i5TD~xOA*Vt}?-?jeiSR;Le znwWoubkNY_gYpW}?3IIIx9{vzAqIAl?zE{}JwL3Oj>xesAn854x{OAK%JWKqUqId)ph`uqEz{>DwY`#!_pl-4#gi$AUeSx{XH z`r2CEC-9Fel@j2zph<7vqD_+U`cB3*7dqw6wG9m>le{e%zY~>rW>%KMI8Q{*P*fPdT4E-65XHEo0+?n1UZr*6J#^=74RD%U}4*R{kc*r zj}r4h>%qN$beu)>WJj+FC#Ee|#hBQwEM4!J`{9L#TRPI~kDj!ISnt%lnDO_(AiZ|v z^YZd;?W2{RjEyCG*FqPY3ZhEId=a#Qs$l)w@e!DAN(^&U0|1@q@8uhNXI6@=M~$VuTzyk z&R~w>(PQ`M_3P}4p0iXEt0@j|e_Pf0o&BKusxc0UdKbJpDf5PR-0BWJ*J#h}=p~8U zstd-Gzs7pVGU$+)Sl)W>G?wZ)T6U+;>Bfy28_%gHN-vSIht$9F;rfZWaH4;g6jXU} z(y9gfw6-YBv1R$P+{k=G4Qr<>ww>m70agT*srNrf88X4?6;!Hha$Kn+i>_(TMQyVqE=MvVOBwb z%uM@nFv=DRB_(Bgo8;T^!mKOBIB0LgThjLqpxD zvsCU4PJul4FhQwJ!Ry$J?>b-+bd5XvnTO2{J{>_~SB+7(X|;UtUm5D&Z`4{{!Nu7@ zy}vqLgcF7s{$%;nL1l@SXmM!?okv05s-vsM!7r7>!TS%Php@cy~ZBD)p%W1wO$+2USV;3eO28Oldt4L8Vs) z2kt}4U|CRNSU}2qs#A~<7uy3++jbaU zjXNWJzq@km^`rei4Nl`^i!jHr8A$oXJ6~fPKg1U#kv~in_3WwT!+j*hlmVaN{Z6Ku zWJ6`{0Dj}$n;A>;*@XpVM{O8145}c$wY3?a9#V4KZc6eu&ow2^Eo0!<86XE}I0`N= zD?2KjAU=Tlm`$(6#%^kTIbt%2uejvgbE=arGnC~`y0WtJlJqRNHsU(Rw}mVA;fNGM z@9Oq%>=$sr^L`H$Wsp-Dkw{N%i^&L`Dh%!ZqZT`&O#$e8j z{j0zbpa$8wjR1aS%0j&du7AQ97YK(OsK_4D*-*&FIOBE$1~t21MOD=lfskd+dP%!| zT3&wnoQDj=vTi_38n{6Afz@{0_=Oj6W(16C{NC7|!ILOe(u2VRLM4}9N%q2DzS?;u z9md4EiRtcskeFI;g%7F9OR`CW8@8VbMW1sGcb3QFjW=v) z6M%BtR#jECTg9R_t~edlV@LB)0Q5N8RxXb`i$6y8!9xBE&pDtn_bW9oG@zh~BT>vp zuYP?4f2!)Qt4GnM!Y`063_Qc3@bOS_Pv@M{Y!b;o0e2AF;Z*|KOKSk_>6m>TNwg{^ zbZ#*1ws?PaK>|}Y*`{{;=ryk49Qf+DeG4%v^F3q!%|$&06Fp?9VQqd_*!#ozn^&h7 z?H0|Kf(5ZH7!YIuZ{^)ATPd>vlN(DOc)1bX;n?kQ7BbOT$=!zh%Dz~+O2<(g9m(dgXB1OzW3kx$NnBy!k2uT(MuU=3*{uqRj{=Q=&|kl1 z#@?G2Y-^qglqkD`@{*?ZR2YAkF*H0z!p#d6$}J$|@4k1ZBwo4lt+kc0qV0+pDDmOE zU|_(0o44ig2FBw@(TO_-jTlzct(54s{KWEosH-$&Kzf5GaH#U|`@izcxZ$jzs9}+_fNO=fd)M6RB}p4O_quJSPN*d%)P6A230dd}I62 z?|SP^>>-*<7)x2pQLh_Hr4`oV&*BE?mOy=~iI#hX=`)W<;NjTD=0w_E8$#=@_e+Oo z=jAo?!9?rr0e7$V&hLgpxQQe~iBKaTHtHZi^iBSF)lz4P{oIVj6Q`rT744WMw6(;)IsRUYVg z!*(G19~__uXJUX#3>M{N_;I9b|29KEw!jR5+D?DFO`dd?2cwgAyWvNgl9%b=1>orA z{MHGEie5K~>lGPGhkg1Nlx9miy}o)RQ>8mko}MUe0q)W48r7gIkd&qu#)CZK>fKzV zw8vAo8?itaq1cEe?-=*~&1AOO0pb8$(uA;_;1Vm9kD{dcl5ShIT-Tk*) zUyEjJpriz-+@2KbVZAVrV|4ZI!<^Uy5|AAt%|(e!Pmv`G)4*r8`{$i<)<>NP@;U%H zG?bfJOzI|mh@M`U^8pE}IsY$S^&UmS=yTX=#)px+tEaJ#)KHI6A3^6=xGPup+Vg+L1Ci zM1AW`%Kbt`Nu^WQlEQ#%n-BqsWdUtsN%OfJ{5#`+oO?^1(`u}ssf%FXtX=ae%od6@Z*egbMzPBQ? zTT(8cNDIDVQhWB@K^^h0xt-GMA#q21_20&hg(o?Ag3R{-SdG8u^h3X45)(-0cY^X{ z<3r%(24NuUtPru{{_0!~LS~Omza?dn8Tu=HtGInnGW=JmWmbWrThM9~7bQWS1zPgd zg2Jv(8jM4KVS)>!ph1v_EL+$(?AJe|G*;SzZ|9QxM>cZ5EX$5eQgJ^OlqY)9m_Ua* zhCz|R-?f2?dje~6{wowaZQ%>|Rb&G?@Z>=N*p0WW>?Nuw?1ab7VJ!R)3+;Az*C z{smcqD-quvBve#XOq4&$z3prwZ4&Up55ymQA!|BOT{Z++AbvSXgK0`~sf#b7cR@HI z&HW}qd4ze5zz(Kw^8t{`oiDENxVs2Y0or!}H2x5GdJ`xhZPB3NlaGG&u0hSe)H>MSi~5v*b4!f7>0p#Ou@fo>LvMu>u!&hB}NvO z4P2+^=j(M(5<|ZcMRQ*@EvOyp|yQCv{A<|>^->lb;Yik^L&cpo^e*XS1JHxAh zPcI|X?40hJoLL9(Am!Yrdq{a=X}=dAC{+IQ99)s)LxA;`IGzS5;jux@wqk1x2jI5@mXsd;Q{l#Wf;x$P#ROV<8$87exmj{o;!H zK_M&O+x9iT60tHhJ`P35-T_AL^nq$>#;U|uB1vMC-e?hZ&((WztEUkSqZinw_Qd|G z;fn05;WC`B@ur`s@n&~Llr^DYk+%HTBRQcTPY}~LH^=tK$jIFE@=9*(;Sdry=;aob zS|}iVRqpbT(I3#zg-XxD0RY+GtHXL2O^i z%s<|4m6DSIVL`-vBHdo2^H1!#TX#sNgu`k#@(Pl;z5G=F`MNx?DeZrv=?e5pzK&(Y zppm22y)NZ{1--IZh!#=LM%i@6JiGO;N0oPTQ8IM2ka|sM)SmGYIEh=MWyJuu_5ZTN zOB>1Iy-?Bo{-RF<>9;B2+(v~kG})RaadgjIPf_fhKVStEKY}zD@2|pqj@@uu^c3dy z+Mi8b6Z-9QNRf}LMP;LG0&wJjX~4O2urppm0s&lq@C-o)?gdhTV5`~DnMO2}<=-Uu znsF={ek;gsn-z4-s-JrO293SN_IFf&mM(nGyS@`69^f!Li%)a2->krZ$9>rB zWFRQ6X!*l^Yw-H(ZtA;F`%LeJ`ivB3uYQE{?FPG}18oPNXe`*988)PESoei8sF z5gF5q49ZU5xQa_k#6gJK15%i@n|q-rf~pVa=jU^@a+h{n`@low(*#UmSxU3(Z~8~G zeGZTGAC=QwRp3uqQxlV{ma+YRmMpjqKrpZ?D$^Es#V>T*gr&fvK+`tAaL*YG$B6OS zFbE=?VUXi68T6Oqk2r+w#6LqYKwIC$sUSk-tulf`=_=ik6%{IWd-K8BTa}nU5ei{z z2c{{x|2!yU+#)NQvL_`ay+GMYhXg)-cpn6J9L=qU%0s|Yq}*esgP1s)zkDdQWXKO_ z6c@`WyIk52XSuQuKKul6$}W~RdHW0p%pgDsGC(KnHdp`yH^62z=7$0dQsD#}JNs@O z1OSN&yCGSu_0V)g(qi%u$04V?b|4}vcsjY=mku@tSf-r5q5p+g8PJH8glg$I)#7~n z4U$j3qCrt$h^C>e;^Ir+EWB=+sgi$ob$5$qJ$*7#_o!v;vjbN_K>@$2{|RXL+tVY~ zAFeY(4nJS&&`$VdY{J{{AuLckp(WbyRoh8wVzOPNVUcH4>IFF2eicD%#)6H4C#me@ zy*oI+2@t8`ZuN~fQwScEH<0FnP}v}uq3J)QUrm{@SkPdRQ(v)bUT319j=sMB=%jSO z$07P3$EmNalzw99D0!c-I+N${tj{THeN_7u>J78lc*;1X%oeKj*A9AzFS+$#!Kx0_ z1Yg+CS^G1)uVudPRCzy!poa_36ngPtp9plekNzDaOVDIaO}nq;`uzzIzV z|M2eST)Vm)vC)`>J2-y=r9?tgq>U=cC#Wo*3Ap|#+|VQl%N)e^RtuhcrZdbPltx)t z@~R-o)=or^=0>j3o4R^8V4PC3kx8fy*C*RoD0;bPC#+Pd2MZ#q$6cVf>((=a#OdtV zzhVX4Nf21+Rvvkl1Ok36RzeVjJl;-KZ&}0GFEsZbmjRIrB5XSremdb|Hl?QmQy0)u zB%m~k|1>42HS_T>ZP`FY`&cYHv}jW*HoK`>_k+Rvwd?0bNS_z?>WLqy)fPf7g^K$K zjsQ-PpohA%`r%+0)1Kd!mrvIh8*qLeKBb@}oVHIj6}US2K<8pl5kh!#@1Jpjz9P~Qz*G)Z1&&A; zmeGL%mH=~m6BBf6no=V#-M@@;n=RmSK@rE_2}c^W`u}-3b3pY)R#t6{?`{n_&F0d9 z;!Cq!6;n_Is2wzGo;$1XXSzT?9>WV6!3e&~Ie&j1zM7%&nMNjlCHcU>M$-6`SHLFH zA5I5pL*GFTaK*q#>b1+*3Q(aGItP6$8T!L7bjOV8RvkA7v4e9Z14a(i%Xv%18~ zX*@FRTU2k9uyjd@6Ri&gJN)IRVwZac(L~ah(~mB-ssu9Ku`?R3L=XBvF*^CzHTVgx7{?uTzvG zpFMk4MnwHC))T_7&Au)p2A!I-%5<99EJXx7$j|!lVV~9257H}+J=&MjRrksIwWUSa z-DM5DIWQ3M<;$1iHG}6esFG&tclkCsuq&{EQyWA2o*B`)y%W-RTy;m1bb*db8X3}e z(1Mt{NBhzav&;3q@5rrnsxMub{PL=>(}k~l7(_r1!{mck-U%pip{j%f>7hWvyUj{^X2GA*9RP+>kW8Cc7MSM0TQgs= zH7*VzE!wRQoin*`UQ%FSU`1*6oe%a?N@f&b>9izdff53!9s)M4)^lGlvH>?~cXVjV zXK9(VzW)ly4bBkkC>#Ec>hEYiv1{oQP}b{_L;S@w-IjvY>I!$;>OUSU>mB;L7QkY6 zUlGk3PDQdoT`CfP8?fz}16s85qMH)Hf}Ov5f!t{djx2iaf7jd?17LO41=yr(plETf z$7Po*q8YN|pLwmWhK@DHs?JJEO4b5EM|=HWX?(qxHjuWWR{fbjTe*17*NZVjtCOwl z(QfyjD|y*lF9@*!gGMj}l{act|FZ^zR+|==0E`uFpa&QL=W|g%YyIwi+V?@dB8zC@ zomq2A3mI# zDb1O9eJSO-0qwm8NLw%1QTGb(Z3fyVqD@cg;$Y<|-)R%5_Nf#i!HoMW9Yv>sg`3(ue33>ou$rB%U3A7oT3M($2~Swu>` z4>17T;A%?&1#fnCf^?ziS+=@IoXL%0pG9nJZBO~n_m)i{{f#w{cL=P8J|NAR=CPHT z!i!34q1-yG@!@&{{M!wjU^}4@acpB87^?_JNoi?%F4u;wHzaNaMMa&$5Z_X_o7;|Q zS&H7@i+tV32TgI@Nhq5{oU<_6we$5`BdZ|;$>XN7EH@^+&TzdS#bAV+epT>b9Fa)) zJJscKInd=6eo@|p;D*rby%#< z-Ma7pz33Bb->E-S2dHOz~{nIozFO)@k!h z19jo9iLGs~NRnZS?7=Ot2s{#SC$id zIZ9kXm;;J6@}A$ME19!nFh$aXIoaAndqr#?)B?|F#wRJk4xM#n2y2`-p}|u7<+)pQ zg7VHBsi~<;MTfibI^Gni_%=r{Vxw`?!iC|DRm1IWxj1T%Cqqp|^LdI)0&bkCJ{i<4 zG!(UIJk1eulTP4eR1Hq`7$5#>o8h2KYhO+ zZH?TCwqB6`(9QOOHPDjfYMQ?^`B)47neB|6DJ5x8ANP3$+fcC9?@e;QX26b4iGP?u zvpcTSO1%tH>MLDATH^sf2x3|@SXbPSV>e&nKibap{ALi``Ig+_AqAbjLUHq~y^q%> zHdeeMUu*XIKr$>(M2a)DAOZ#Q62bB<{nTAQX68=&;RI0GpgF$p^$Rb1H0oY?CF0!u zW9_T4S2=w#Yt7BgFNLlt*b=`Ic@#jW3(QGANJZ{({N?r+lSfl0Gs7Rj=QMQ9XB0%w%1*_Sl+@Ha42MLLfZ(qgC^?$07xd*I#y_DsU7VSq$4oe)- zp+nIa2bTKF?6u7n(qCw`-Liz5o`jJJ?q+$Qw+b5!{#-S&Dq@PS-oHc zU6;DvgzAi8H?>hQDubVqQ5r1cf|fQkaBBCaexRCNPw|3HCmP&h#^-5$4@N%g3)%SX zk_W2={8PvFR@o-SVk9wEBf>#$<(q?Kmsp$h@#CLA1~1+z=BBK#W zX7r216Jbp`{>K$7R<}pDU<1L_JnxVB-k&Jq(FdA%7eQL(5eWY}I{u&1zB8(c?~8VV zbfif~ItqwX={2aJhyp4|krI%q(h-oBgbpGiq9R2CA|N77q?b@s1f+w2AffjfT0+{J z{Qm2Gd0*dJ$;!%_WaiG9JLm3m&OUoC$$=aRLff-Ed4@T;eZRAZdl|6+6q$jE-Hph$+Ilt6gkm21jfF2LpQtC`N-}aC5?^xO%07{|5KLDgr$n-1M8yj_y>sbUhd;GQBc=-_qJTfi>ug zWyCOiNHO7OpJP>?vd?`b{_k$pe6yg?LH*WSx``$~MXB=4M5@JrNS;a_C`Sn~nUt`* zbF;{0cb4+H_@?Qu8387u&AovNSHHUc+~)4xGoaeQ$}79wx;>pezzR?T1Kr5bK+BQu zx$B5)4pF*$yS|coVmr7JN$FV2eJDv38Zd@9o_SzRnwXkfBQ~{Aj6^9_A1!|DsG{vQ zI$3$d>(Yn9U(PsVNXps1GLC5k>>!SLFSp6}_kYv6w1TYuLN#`G)n#D5){b{}s4Mr) zMY!8ehzj{;N85%#5^059_}O8`t**xvzFpQlov`Wsmj&>o!^r9=t-ubo$m+fCva{=@ zXjZXdzS#t5Jw3Yu;xs$)QCtpN=X=1HT#>!B*PX8L^I3vuIwDnv;8l>Va!VGE__pw{ zS^aaL8uc2@4qnx(@a5a;`cB4?ud>+hH@!#H9iz{7pD^QlHU+RcI1N#@pf1j--L^Ol#*A}vD$T^zaMQ)sSH=~I1RCG z`AD~)ap`*T1EdrB@6KKp(tSF(wfJs<{=lBDpcosj*FF?H@K3{ia~f|>ee2eoI0J1K zrK0hweS?xuW#LP=d)WR$Z{1y?3g6UvY1@likA}-@zgv8PC_# ze`3JZ(@zSp7X)2}O#_j0x^XeM0q_5Be_$@YVLWT{HAv2(dY!dfR#v=LLI zFoUq-`y={iJv51`wDa+XMI~4_rt0>D3UZX3euJ*Cs|TnmJ@+3@Er}{Ob5IpstYM)L z(3taM){co;+utyrWY!uW2Qkwiqvi6ANFYUueo{C^{P)Y98lO4N9@F%6ecp^8;b@ia)*vyL96|pq5^K2 zcNPGS@-;(T6&F7#Zs_v{hLjAWK}N6`d_H|Re;!?82Mpi19*VVX5_595s8tRjm^dr^ zx7#XsS{GVy?MT(Y*B$g;pAjuTM8O@Qq?tM4o3tHj@9*&BTjCExCH8m@XX=P|u1Y@b z_O0j2`@{1ykF|}y;H*k}JgcJfK=>Q%j*$ihEI_keV{ACN5eIe#BIG{2K*$g8Jw(p* z%(R5xD^3pM%DacQoIDKuz#)@tnb?C7Fz5cTQ6%NmaI)bIahlICf&5+*5$wDPzY1wH z4I!#75HQUbP6MXz8$x7Pc-(7sKdJvSdks%Q6Ssc7_rd+gvr9fI)~?$@>jn5coiF;d z#=u6j7eU~cSRaahA}&fp{nb*fNLU`f<1wF~^7zka7h7-wjxuCb9?L_e;3byj#G}@w z>ydd(ppZcFrx(EoXMG{K1sH1ZZv%ShozF!Caf3@B*@vvC#fZbmqtlu@h=SFt#n3I% zg%ry@%mFPM@xVnFc5)AB`{zLtJofMjQg;5%SRl)A>aL{k((HErPhYwouy4Jk#x6*k zG-Pwai?mA&8@m7!7AqgUIruawYj6isK?4{rCxvmR@=HtWu;`=M%Zf+Q%Y1djHk_1S z$*ZQtS2rz-S6m+>QrnZsj{nI^$z6XT)c%YoEj35({x`;pR$pEw46C9BR@~3TDFrMV zkFjMYu#55r%(?zSWuQ{WYqwN4(`ODVh>Ke&*z}p2-yyAfELxMczj_CXVGwHI{kPI{ zNMg)iF@J2q(Nfw#h5Wy6ZS|$n@)_a7rjimS1SS05bU<8O+}C6>BQ-jI81tlt{iz0U zo3ObZ&w94w0}Y@;zAbak+Ep|nZ)d0>us`WZ01L>Nd*cME(t*)p!P~O(8F_1D{%vAc zZu5WYS2xa%%0^bu)3l_q>+kmR*j+M-XTPpA7^v11yts46A14hUb8>`Eg4)15>nmPd zgnlsFwZ%tGb%at!f(npFEX5=>O6afT+}$z>Al%3!oR6)tZo78TwF38_nFNPMM@KhE zKP3C#bR9Z!!qoj*b$yPQryu`E(H=Gh@zF$&2O{0n8t(P$0<~T3+m%nS#(C2}x{mht za;bJLG{?^{2P;S>@pHm!Tc}fJ4EV+iV7SJ#0{_3Ef$qQEaiyZP^bKtI<55n; zrPmu^Jlsy1DgT5AtL0#Y_@%?abth`y-ox*{@cpyasCcCf#73DzH{Y;n*XgB-RH%Oa z)>3oeC_#@4`{!|U%OM!DmLh1+IQVD9F}o!R_XZcIr^u+(m6<8|1dDui9Pto z%35Ff;YuP*o{}HK&^c1kdjB1?wu8r(9K4O_g&?0`QFrtOfZKQP(x4G&U6|{6KPr5^ zC>qHDmWDt&C|Ml%^#&b_LBpQf8oxom7Adi>y|+~* z&uCmoGB?&P(8VAB6DHBsug`U=MFdY-RpdXflQbbZRR4$2iC@!G~5{-f3 zd(gX{WJS=re#_gd16x+?94mE!lqsx49th0ufWrc?-LTKVP%>e0@50fF60N%BLI3#8@Erio#Y$RNn-J1@itc z<}b4c38Eq@KC-P=;ha(mN&3tp_GEsLgFPLve#O+zb)UA3@cwD^PP4m}1jVwf0DvHy==aJrmmxnJ65h59TXzNY4D}#kqSzeub9Jb| zAGhnw4F<&x4FYs@bc}IVGy@hH6l1%~BHPHjL0 zmAbgP3c|%G7cz@tGl#IS@alMlC_S4{VkF_n`CxU()5qtUwKd-hA+2c8AlHe8P~mZV z$!4teCHHy5i0xNRrSNn5=rRwX6USVSi^3!2kfNmH9{AZK^pOSN5_XM`6yQ0%$-^SJ zw1-Z?BqA=~X!0t`jn9KI+;Sf-@@Y$4) zEwW`ST-krva60I?0qK(gVicu0%IwC1QQBld;FgW?f!2??6+?(Ws??fAPT-+%pL3=0n*no)49cDa3i7 zw`hjsC1;Wegz$Yr9fo0+&!s~7&y$b^2XBo(PweP(-_t4HU`DjCHwBBf)sEMSDJZZx z+^1jZ`Run!>h%0l(bnCpd-P8A_nQc&lZFfq9Tvw|_Ki={j(4Db+-vNGE|jQ()!|Fv zt2`(o5KHVMCCosdzk0{7n_ENU(Vu(W`;ab2D_kViqm1L=qGa($EG>#P&no25wZ0{VIke!67IRVIb$=kc(dY|Ay29$Ki<9Y zRuQJ~2$EM_&BekihqPC?4ugr()6*-ds?s*tzi0~CxuTE}uKVwkB(I|5^SnGULn9+` zU0tD}p`paY#5T~(?gP0g+uJ_0bVD2xWF!lXhVP6k$lS$cD2C+)lGCJf4fa~XHQkkV z6=AxNHhf$^hR+<(#1gPOplTlpwznf^U2QY_<^5s^Z}Yj(4Qs;0lyDev1HCvKoFH=3 zLgX5FdVd5M?&$l949-1B>3ac9rZZWdfIiug*PQlx>pup>a?1Z2?pScMehUT9q%tBg1Dc~_yqR~jq3I;8Dr*w>wmDR}7 zl6x^g>mW?;!Gi}*O8jE!-F^!{S!3fGZ1RGl$Yi*xs;YrPhNP-0x9l5cOvFCx{QP{S zQ#;MK+FI;Fd~*AbWWKb_%%t30k<+ZK;+mTL%yBe*eSL|ksa-i*frG=t5?Ls}Th7kn zV9MRO${WP%AdO!dig)OMLy)DU6gnCOoF*HMQ-dRhIxoEb{q4|i38`3o2yEm!>{xb5 z6ccOZhuuS!)FL(hSc{443yQ)kGuWFI^j=8Ku3&_5<9c@OZwIm&h*x>VSQlc@#c-$j z{gsKPkP2F$r_RT%LD&ECWFar33VHy{W)1)S`|Q^*0SXEV}B$&d;&Yo~fs?!=48Fz5-~>vUUq90)%FP{1INs^> zRK1daTkPC9+T-niF@_SC0|Qmu`?Bcbv*%yzvVfQq0OFmAScjL0Fq}LyAQ~C*h$!KG z@046plJb~tS#&C1IVmY=b?ae!Oz&453%Su_yn3>9|0RcL-%iuqiaB#Ur#T6DiU22r zPwJR97Z;a*+0mCSGE=I+Lc5!-(_4s)-V1g%QKO@E-5dqI?ezT>a{u)7;53GEf6Mlq zl+=7ds(IA|ydH@x%|arPh|^mj2jwZ~;5pBniJqNx`kkZ2btZ|HKIXD2ddM@@q_899 ztVW)tEGG{SU71yKrizCI=!YUBBUf{0tzJsUCL|Q(=R=&GosCURKK;dH(6K2Fs z32|`_*%k4@P*n9~j*#fgYJbMg|Ynz*~;5-3qQUK`WZJW7hmGwB} zfNw2-QVEJ1;*)0}-NR1FZh+I->un;dM#ItP+6`G85vKs0)Kdk1O%{CjP7=oA*A5JN znWOb`CD5k+!s%ze=QK2rq1JH1o^j$-%RB#H{wdoghe2dLJ@2HOnG#UE-eEWmp0pwT zQ|sw&USCV@xl)f^;jL^GYUd8(EY{o??F2y*ss=Q@CmIDU_xPx#nL(TWG>%Ug`yOUyCc z#eMm8=&jLlkemhzb@1xx>B-2-er=XSiaDQ$>qsch1>! zRL)$3W1F6VI}qWFQdv=G$YIpzet~~o{R0Jet}@?->OpL6WEMF&gGa;>P6UV(rZp1I zO1pSmfvSvk=9hV6lD!2|^n=%JK#OulP}iVS2;C!ug8RVtahUb}FW}m3Uw_-ZT2!Od z&ch?2&ts9@3%uC1;Nb0BBPI)7_~WCl87f5n79HDyk#F!t}C-^0&-X_JnXT_3K_>!42x2kslmUc5-B zbG7j#d+w;tOi@y@bFKlL$wc+{Kz3I7KHpJNVsh$rkFKZQ+XW3q6N5gFviw3C?_wEh zQDVgIqiDTNy-x2@VkNyN!?SNj6ONS$hxfz{N-VLRa>s{H?u%2zBGt%E_& zx*&nu!A0*x`C3cXv?=p{=(L~;O8lpJ6|JczPHGeH>o>F`FM4Ew?V9Sv<`EV5yz2i7 zh3qjn`R_5b!H6T}j&wk5y!N)&hBtKOCLlntD5I0U(hHDHO zaTbcV60hX9)jk;6v_n>$P0Y#Yjj1603Ei#1{M&)~ z2?3Am-RF|B5?owd@NOITQPP)@#HcOlB8}m%m%2g7#2XsAnN{`hMI_#H7BuviK1$H3 z+i5=Q+yuXvw#yPjBO@{2zZ(LxI{wP1V;mY-@NVFQ27dLPt)Np^Q%g}yh;{l@H|`TQ zGKS$kL-TwoCcA%Xd}Uy%9R^om2U*Koyufb+$Mo03CN<`)0kJV`gf$!k36GOn6xX*IiKY9l9W|x=iSKbCE7JeEB3uC=)6Q4CctcxMp8#ZFAvJP8U)LlGT2KhWhh{;h#92&oXc_G@Zu zeFti4A~$v!|2Kb=s#DR=lCBlZvCL0<`Dnjq1N=7r$Y+du!@TLgPEz{(+*y#Z;U z+|p(_d6+it&M+%2iAuk9WrN2caDObk8(W)Xlt*B?8Gt>;##M1K)R_Qzt0>rVoj18t z{KSzlTI`Eo1CVD`IytM@YVdcuVe|P>Fg)DNl=ZFegjg8IBF&a$clTytk1!W5%|9MztDxdzL-|4{OMUmnkHPW|ls$SFeL$FD4#tu_^S-`Jd z(RIS1|K`c%6*02fnX4|z*d;Jh}|lR6U1Kh;JKW;q8X>t7KG z`>_HCa3b+~KP|D`!>pd>o%N+9yRuJUl6EtKtJOzjlqA04V z-LU%P`em^T!|(yIXxAVxQ08$b6q7h9@p0>xpTjpzUQBJ`zWrx33H*0#{nJsJN+mtv zf|;JAA1zX%EnI#YAM z??b_<4|h{^A1m;9q0Fw0lRg(rDL_^oScUnuv#JmF~B=z zYt+>~Z_5+C<~L<|s1p;qyRz>$us`XVs(-%yWWd%d$vXes;aTcl86ODFS`j?|A8dYX zQuJ#_$eyeDHdoU`-*$TYz#S}_;%%`Tw*Q^rc;UIcj{38<c*NHB`Fm?whIc>O5og zu8t2zOkPfo)+y^#Te?8%uO0r4{-;SS%e%c`aJk}(Mpp?Ku8Kq5V#OFcdg~p4+a;Dn zS30uTAW0~hw@bSQb>Srj6%3CCH|yzcNSG#eSUj%IVg9m1US>Bd4y`T}@kO_|nc$ef zU+m@6w4GZ-z^g@Bc#CPjpv1n5NlSN?IOeOCaB|ArhBz_%Redq^BNSvlj+n1#kkfsJ z!J?~$jxQE@6UB0jGoEQ?zrYIaE)=4uB^_7n66bfb%?#{wu19@t`DJ ztipmuUnKU$K9n*i2_S#SJ}(Ol$GN|TM(@%W78aUq)%h=6OACC!!iQt($fzGz*X9$vto@ybUAWM%+YCDA!OEXFFTo!CH z2XHjuaF{9fD(H<8yJG2plxs1d8*X|n)2Vb@hI`Ocb$dCPD6&gbbZkc@Qx^F&E`!co zt?~k3X=TOC_IB=(_uDCqcFXhUrxW`8qP8`Df0iqD?Sw;BG!xfHE#WQA%@G|3v3}zfcT41s z-hYM%{tlsc?RYcl*e&h(G~7Sh{GjHYA|NRsjd8WP}5^-)BAh zR%(r38XUY02GybG)BAB{nPzYx5q076?!<0$(?-)CEY-dZk!xBvu`={q&E`Iwrjvl_ zNCyN}T2#(p(o%|s>5n&|8e)T=HRM-VDz{Y(TmaBKw;bIytJA4SW!?%f0NYtWayS5V zrP_g+b2HN#8OEn99;63zicmfS3x2(5>!rP^47t9J?XWpJAc$U)f_;n8-eqB>afP2g zmH^i&DMfX807U7=w{Fn_tz2h2l_uUay@XlGa0AHZ=DquV78WD80At4Zu<)&-6-Hki zWVdFgWSQ>@xH=7i4+tx?t)K9o0;z|2v!-9JLn6F%SZpW0cX3jU!j(-!9Un2tnHTJ)NmtE=Yk+j)etP}M-zD{m=4uf29p_&be_KuL;! z6zUNk+>r%eLL^|nh9^HXVRlcES8S-FbyyA1V#Tsp*p1Bk3+5U`!LiPIHS(+hDtfcp zGw`Rbijk<;Rn31)ex9C;U^ajn^6Qu{GWb;&!sp)?RiP@)&CJ*y>(zLJA2;KWC0*eO zGq1cN;1^-<3fjzZXR>KPd_K41A6PxP_Cy zKH}Q1^@s6 literal 26907 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
    YW1M)!$&WC0>ZVCwTYLrS}!RH`T$WZho8v)qIDk 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

    #3O4 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 diff --git a/modular_nova/master_files/code/modules/research/machinery/departmental_circuit_imprinter.dm b/modular_nova/master_files/code/modules/research/machinery/departmental_circuit_imprinter.dm deleted file mode 100644 index dc66184c345..00000000000 --- a/modular_nova/master_files/code/modules/research/machinery/departmental_circuit_imprinter.dm +++ /dev/null @@ -1,5 +0,0 @@ -/obj/item/circuitboard/machine/circuit_imprinter/department/no_tax - build_path = /obj/machinery/rnd/production/circuit_imprinter/department/no_tax - -/obj/machinery/rnd/production/circuit_imprinter/department/no_tax - charges_tax = FALSE diff --git a/modular_nova/modules/colony_fabricator/code/colony_fabricator.dm b/modular_nova/modules/colony_fabricator/code/colony_fabricator.dm index 93aa68efc3e..f74cf23dda5 100644 --- a/modular_nova/modules/colony_fabricator/code/colony_fabricator.dm +++ b/modular_nova/modules/colony_fabricator/code/colony_fabricator.dm @@ -12,7 +12,6 @@ obj_flags = CAN_BE_HIT | NO_DECONSTRUCTION light_color = LIGHT_COLOR_BRIGHT_YELLOW light_power = 5 - charges_tax = FALSE allowed_buildtypes = COLONY_FABRICATOR /// The item we turn into when repacked var/repacked_type = /obj/item/flatpacked_machine @@ -33,12 +32,11 @@ QDEL_NULL(soundloop) return ..() -/obj/machinery/rnd/production/colony_lathe/user_try_print_id(design_id, print_quantity) - . = ..() - - if(!.) - return +/// Proc for starting extra printing visuals, because upstream removed any nice way to do this +/obj/machinery/rnd/production/proc/start_printing_visuals() + return +/obj/machinery/rnd/production/colony_lathe/start_printing_visuals() soundloop.start() set_light(l_range = 1.5) icon_state = "colony_lathe_working" @@ -52,8 +50,8 @@ update_appearance() flick("colony_lathe_finish_print", src) -/obj/machinery/rnd/production/colony_lathe/calculate_efficiency() - efficiency_coeff = 1 +/obj/machinery/rnd/production/colony_lathe/build_efficiency() + return 1 // We take from all nodes even unresearched ones /obj/machinery/rnd/production/colony_lathe/update_designs() diff --git a/modular_nova/modules/tarkon/code/misc_fluff/research.dm b/modular_nova/modules/tarkon/code/misc_fluff/research.dm index 1301c970a7a..4a4290b62c7 100644 --- a/modular_nova/modules/tarkon/code/misc_fluff/research.dm +++ b/modular_nova/modules/tarkon/code/misc_fluff/research.dm @@ -85,15 +85,13 @@ req_access = list(ACCESS_AWAY_SCIENCE) /obj/machinery/rnd/server/tarkon/Initialize(mapload) - register_context() var/datum/techweb/tarkon_techweb = locate(/datum/techweb/tarkon) in SSresearch.techwebs stored_research = tarkon_techweb return ..() -/obj/machinery/rnd/server/tarkon/add_context(atom/source, list/context, obj/item/held_item, mob/user) - if(held_item && istype(held_item, /obj/item/research_notes)) - context[SCREENTIP_CONTEXT_LMB] = "Generate research points" - return CONTEXTUAL_SCREENTIP_SET +/obj/machinery/rnd/server/tarkon/examine(mob/user) + . = ..() + . += span_notice("You can use research notes on this to generate research points.") /obj/machinery/rnd/server/tarkon/attackby(obj/item/attacking_item, mob/user, params) if(istype(attacking_item, /obj/item/research_notes) && stored_research) @@ -109,7 +107,6 @@ desc = "Converts raw materials into useful objects. Refurbished and updated from its previous, limited capabilities." circuit = /obj/item/circuitboard/machine/protolathe/tarkon stripe_color = "#350f04" - charges_tax = FALSE /obj/item/circuitboard/machine/protolathe/tarkon name = "Tarkon Industries Protolathe" diff --git a/tgstation.dme b/tgstation.dme index da0e385e0e9..2dd8544cb36 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -6528,7 +6528,6 @@ #include "modular_nova\master_files\code\modules\research\designs\medical_designs.dm" #include "modular_nova\master_files\code\modules\research\designs\misc_designs.dm" #include "modular_nova\master_files\code\modules\research\designs\tool_designs.dm" -#include "modular_nova\master_files\code\modules\research\machinery\departmental_circuit_imprinter.dm" #include "modular_nova\master_files\code\modules\research\techweb\all_nodes.dm" #include "modular_nova\master_files\code\modules\shuttle\shuttle.dm" #include "modular_nova\master_files\code\modules\shuttle\shuttle_events\meteors.dm" diff --git a/tgui/packages/tgui/interfaces/Autolathe.tsx b/tgui/packages/tgui/interfaces/Autolathe.tsx index 45d7051e437..8d5d5bafa16 100644 --- a/tgui/packages/tgui/interfaces/Autolathe.tsx +++ b/tgui/packages/tgui/interfaces/Autolathe.tsx @@ -19,12 +19,16 @@ import { MaterialCostSequence } from './Fabrication/MaterialCostSequence'; import { Design, MaterialMap } from './Fabrication/Types'; import { Material } from './Fabrication/Types'; +type AutolatheDesign = Design & { + customMaterials: BooleanLike; +}; + type AutolatheData = { materials: Material[]; materialtotal: number; materialsmax: number; SHEET_MATERIAL_AMOUNT: number; - designs: Design[]; + designs: AutolatheDesign[]; active: BooleanLike; }; @@ -174,7 +178,7 @@ const PrintButton = (props: PrintButtonProps) => { }; type AutolatheRecipeProps = { - design: Design; + design: AutolatheDesign; availableMaterials: MaterialMap; SHEET_MATERIAL_AMOUNT: number; }; @@ -183,7 +187,38 @@ const AutolatheRecipe = (props: AutolatheRecipeProps) => { const { act } = useBackend(); const { design, availableMaterials, SHEET_MATERIAL_AMOUNT } = props; - const maxmult = design.maxmult; + let maxmult = 0; + if (design.customMaterials) { + const smallest_mat = + Object.entries(availableMaterials).reduce( + (accumulator: number, [material, amount]) => { + return Math.min(accumulator, amount); + }, + Infinity, + ) || 0; + + if (smallest_mat > 0) { + maxmult = Object.entries(design.cost).reduce( + (accumulator: number, [material, required]) => { + return Math.min(accumulator, smallest_mat / required); + }, + Infinity, + ); + } else { + maxmult = 0; + } + } else { + maxmult = Object.entries(design.cost).reduce( + (accumulator: number, [material, required]) => { + return Math.min( + accumulator, + (availableMaterials[material] || 0) / required, + ); + }, + Infinity, + ); + } + maxmult = Math.min(Math.floor(maxmult), 50); const canPrint = maxmult > 0; return ( diff --git a/tgui/packages/tgui/interfaces/ExosuitFabricator.tsx b/tgui/packages/tgui/interfaces/ExosuitFabricator.tsx index 07c0daabe32..e78aae9591b 100644 --- a/tgui/packages/tgui/interfaces/ExosuitFabricator.tsx +++ b/tgui/packages/tgui/interfaces/ExosuitFabricator.tsx @@ -9,8 +9,13 @@ import { MaterialAccessBar } from './Fabrication/MaterialAccessBar'; import { MaterialCostSequence } from './Fabrication/MaterialCostSequence'; import { Design, FabricatorData, MaterialMap } from './Fabrication/Types'; +type ExosuitDesign = Design & { + constructionTime: number; +}; + type ExosuitFabricatorData = FabricatorData & { processing: BooleanLike; + designs: Record; }; export const ExosuitFabricator = (props) => { diff --git a/tgui/packages/tgui/interfaces/Fabrication/Types.ts b/tgui/packages/tgui/interfaces/Fabrication/Types.ts index e05b977dd7e..f214252ef70 100644 --- a/tgui/packages/tgui/interfaces/Fabrication/Types.ts +++ b/tgui/packages/tgui/interfaces/Fabrication/Types.ts @@ -68,16 +68,6 @@ export type Design = { * 32x32.** */ icon: string; - - /** - * The amount of time, in seconds, that this design takes to print. - */ - constructionTime: number; - - /** - * The maximum number of items than can be printed - */ - maxmult: number; }; /** diff --git a/tgui/packages/tgui/interfaces/Fabricator.tsx b/tgui/packages/tgui/interfaces/Fabricator.tsx index 1b02e9f8639..05119076180 100644 --- a/tgui/packages/tgui/interfaces/Fabricator.tsx +++ b/tgui/packages/tgui/interfaces/Fabricator.tsx @@ -116,10 +116,14 @@ type CustomPrintProps = { const CustomPrint = (props: CustomPrintProps) => { const { act } = useBackend(); const { design, available } = props; - const canPrint = !Object.entries(design.cost).some( - ([material, amount]) => - !available[material] || amount > (available[material] ?? 0), + let maxMult = Object.entries(design.cost).reduce( + (accumulator: number, [material, required]) => { + return Math.min(accumulator, (available[material] || 0) / required); + }, + Infinity, ); + maxMult = Math.min(Math.floor(maxMult), 50); + const canPrint = maxMult > 0; return (

    { }) } > - [Max: {design.maxmult}] + [Max: {maxMult}]
    );