From 44c968f75e3111d4e4cc2645abbfadd4388cafb4 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Thu, 3 Apr 2025 08:05:08 +0200 Subject: [PATCH] Adds a unit test for techweb design presence, puts some missing designs into protolathes (#90219) ## About The Pull Request Adds a unit test that checks that all designs are accessible through some source, be it techweb, disks, or innate designs. Certain designs, like pocket extinguishers, entertainment screens, etc, that have been present in autolathes but not in the techweb despite having PROTOLATHE flag assigned have been put into protolathes (where it made sense, otherwise the flag was removed). An important change is that restaurant portals are now printable, and thus can be constructed and deconstructed. Indestructible portals have been a major gripe of mine for a while, and I don't see a solid reason for keeping them indestructible if they can easily be printed from the service protolathe. Closes #90212 ## Why It's Good For The Game Initial argument for keeping portals unbreakable was to prevent people from griefing the chef/bartender, but by that logic we can make most machinery unbreakable too. I don't think that having an unbreakable portal is good if its locking us out from allowing chefs/bartenders to reorder or even outright reposition their lunchroom/bar. ## Changelog :cl: balance: Restaurant portals can now be printed, constructed and deconstructed. They're also no longer completely invulnerable. /:cl: --- .../robot_customer_behaviors.dm | 3 +- .../machines/machine_circuitboards.dm | 35 ++++++ code/game/objects/objs.dm | 2 +- .../food_and_drinks/restaurant/_venue.dm | 108 ++++++++++++++---- .../restaurant/custom_order.dm | 12 +- .../restaurant/customers/_customer.dm | 6 +- .../autolathe/multi-department_designs.dm | 6 +- .../designs/autolathe/security_designs.dm | 6 +- .../designs/autolathe/service_designs.dm | 8 +- .../research/designs/limbgrower_designs.dm | 2 - .../research/designs/machine_designs.dm | 22 ---- .../research/designs/medical_designs.dm | 1 - .../research/designs/weapon_designs.dm | 12 -- .../research/techweb/nodes/atmos_nodes.dm | 2 + .../research/techweb/nodes/bepis_nodes.dm | 1 + .../research/techweb/nodes/circuit_nodes.dm | 1 - .../research/techweb/nodes/engi_nodes.dm | 4 +- .../research/techweb/nodes/service_nodes.dm | 12 ++ code/modules/unit_tests/designs.dm | 55 +++++++++ icons/obj/machines/restaurant_portal.dmi | Bin 3266 -> 3440 bytes 20 files changed, 217 insertions(+), 81 deletions(-) diff --git a/code/datums/ai/robot_customer/robot_customer_behaviors.dm b/code/datums/ai/robot_customer/robot_customer_behaviors.dm index 87120499013..42c2188bc4c 100644 --- a/code/datums/ai/robot_customer/robot_customer_behaviors.dm +++ b/code/datums/ai/robot_customer/robot_customer_behaviors.dm @@ -112,7 +112,8 @@ /datum/ai_behavior/leave_venue/setup(datum/ai_controller/controller, venue_key) . = ..() var/datum/venue/attending_venue = controller.blackboard[venue_key] - set_movement_target(controller, attending_venue.restaurant_portal) + var/datum/weakref/portal_ref = attending_venue.current_visitors[controller.pawn] + set_movement_target(controller, portal_ref.resolve()) /datum/ai_behavior/leave_venue/perform(seconds_per_tick, datum/ai_controller/controller, venue_key) qdel(controller.pawn) //save the world, my final message, goodbye. diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index a1a82bbdce6..74c05110a34 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -1505,7 +1505,42 @@ name = "Restaurant Portal" greyscale_colors = CIRCUIT_COLOR_SERVICE build_path = /obj/machinery/restaurant_portal + req_components = list( + /datum/stock_part/scanning_module = 2, + /obj/item/stack/sheet/glass = 1) needs_anchored = TRUE + /// Type of the venue that we're linked to + var/venue_type = /datum/venue/restaurant + +/obj/item/circuitboard/machine/restaurant_portal/multitool_act(mob/living/user) + var/list/radial_items = list() + var/list/radial_results = list() + + for(var/type_key in SSrestaurant.all_venues) + var/datum/venue/venue = SSrestaurant.all_venues[type_key] + radial_items[venue.name] = image('icons/obj/machines/restaurant_portal.dmi', venue.name) + radial_results[venue.name] = type_key + + var/choice = show_radial_menu(user, src, radial_items, null, require_near = TRUE) + + if(!choice) + return ITEM_INTERACT_BLOCKING + + venue_type = radial_results[choice] + to_chat(user, span_notice("You change [src]'s linked venue.")) + return ITEM_INTERACT_SUCCESS + +/obj/item/circuitboard/machine/restaurant_portal/examine(mob/user) + . = ..() + if (venue_type) + var/datum/venue/as_venue = venue_type + . += span_notice("[src] is linked to \a [initial(as_venue.name)] venue.") + +/obj/item/circuitboard/machine/restaurant_portal/configure_machine(obj/machinery/restaurant_portal/machine) + if(!istype(machine)) + CRASH("Cargo board attempted to configure incorrect machine type: [machine] ([machine?.type])") + machine.linked_venue = SSrestaurant.all_venues[venue_type] + machine.linked_venue.restaurant_portals += machine /obj/item/circuitboard/machine/abductor name = "alien board (Report This)" diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 46cf3987381..e1ba5e81535 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -261,7 +261,7 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag) return SUCCESSFUL_UNFASTEN /// Try to unwrench an object in a WONDERFUL DYNAMIC WAY -/obj/proc/default_unfasten_wrench(mob/user, obj/item/wrench, time = 20) +/obj/proc/default_unfasten_wrench(mob/user, obj/item/wrench, time = 2 SECONDS) if(wrench.tool_behaviour != TOOL_WRENCH) return CANT_UNFASTEN diff --git a/code/modules/food_and_drinks/restaurant/_venue.dm b/code/modules/food_and_drinks/restaurant/_venue.dm index 5cca645cefe..59d82878b46 100644 --- a/code/modules/food_and_drinks/restaurant/_venue.dm +++ b/code/modules/food_and_drinks/restaurant/_venue.dm @@ -10,8 +10,8 @@ var/list/customer_types ///Is the venue open at the moment? var/open - ///Portal linked to this venue at the moment - var/obj/machinery/restaurant_portal/restaurant_portal + ///List of portals linked to this venue at the moment + var/list/obj/machinery/restaurant_portal/restaurant_portals = list() ///Lists the current visitors of a venue var/list/current_visitors = list() ///Cooldown for next guest to arrive @@ -42,13 +42,14 @@ /datum/venue/proc/create_new_customer() var/list/customer_types_to_choose = customer_types var/datum/customer_data/customer_type + var/obj/machinery/restaurant_portal/chosen_portal = pick(restaurant_portals) // In practice, the list will never run out, but this is for sanity. while (customer_types_to_choose.len) customer_type = pick_weight(customer_types_to_choose) var/datum/customer_data/customer = SSrestaurant.all_customers[customer_type] - if (customer.can_use(src)) + if (customer.can_use(src, chosen_portal)) break // Only copy the list once, so that we're not mutating ourselves. @@ -60,8 +61,8 @@ if (initial(customer_type.is_unique)) customer_types -= customer_type - var/mob/living/basic/robot_customer/new_customer = new /mob/living/basic/robot_customer(get_turf(restaurant_portal), customer_type, src) - current_visitors += new_customer + var/mob/living/basic/robot_customer/new_customer = new /mob/living/basic/robot_customer(get_turf(chosen_portal), customer_type, src) + current_visitors[new_customer] = WEAKREF(chosen_portal) /datum/venue/proc/order_food(mob/living/basic/robot_customer/customer_pawn, datum/customer_data/customer_data) var/order = pick_weight(customer_data.orderable_objects[venue_type]) @@ -76,7 +77,7 @@ order = initial(reagent_order.restaurant_order) if(ispath(order, /datum/custom_order)) // generate the special order - var/datum/custom_order/custom_order = new order(arglist(order_args || list())) + var/datum/custom_order/custom_order = new order(arglist(list("customer" = customer_pawn) + (order_args || list()))) food_image = custom_order.get_order_appearance(src) food_line = custom_order.get_order_line(src) order = custom_order.dispense_order() @@ -144,13 +145,15 @@ /datum/venue/proc/open() open = TRUE - restaurant_portal.update_icon() + for (var/obj/machinery/restaurant_portal/portal as anything in restaurant_portals) + portal.update_icon() COOLDOWN_START(src, visit_cooldown, 4 SECONDS) //First one comes faster START_PROCESSING(SSobj, src) /datum/venue/proc/close() open = FALSE - restaurant_portal.update_icon() + for (var/obj/machinery/restaurant_portal/portal as anything in restaurant_portals) + portal.update_icon() STOP_PROCESSING(SSobj, src) for(var/mob/living/basic/robot_customer as anything in current_visitors) robot_customer.ai_controller.set_blackboard_key(BB_CUSTOMER_LEAVING, TRUE) //LEAVEEEEEE @@ -160,32 +163,51 @@ desc = "A robot-only gate into the wonders of Space Station cuisine!" icon = 'icons/obj/machines/restaurant_portal.dmi' icon_state = "portal" + base_icon_state = "portal" anchored = TRUE density = FALSE circuit = /obj/item/circuitboard/machine/restaurant_portal layer = BELOW_OBJ_LAYER - resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | UNACIDABLE | ACID_PROOF + armor_type = /datum/armor/restaurant_portal + resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF ///What venue is this portal for? Uses a typepath which is turned into an instance on Initialize - var/datum/venue/linked_venue = /datum/venue + var/datum/venue/linked_venue /// A weak reference to the mob who turned on the portal var/datum/weakref/turned_on_portal +/datum/armor/restaurant_portal + melee = 50 + bullet = 30 + laser = 50 + energy = 20 + bomb = 20 + fire = 100 + acid = 100 + /obj/machinery/restaurant_portal/Initialize(mapload) . = ..() - if(linked_venue) - linked_venue = SSrestaurant.all_venues[linked_venue] - linked_venue.restaurant_portal = src + register_context() + if (!linked_venue) + return + var/obj/item/circuitboard/machine/restaurant_portal/board = circuit + board.venue_type = linked_venue + linked_venue = SSrestaurant.all_venues[linked_venue] + linked_venue.restaurant_portals += src /obj/machinery/restaurant_portal/Destroy() - . = ..() turned_on_portal = null - linked_venue.restaurant_portal = null + linked_venue.restaurant_portals -= src linked_venue = null + return ..() + +/obj/machinery/restaurant_portal/on_construction(mob/user) + . = ..() + circuit.configure_machine(src) /obj/machinery/restaurant_portal/update_overlays() . = ..() - if(!linked_venue.open) //Any open venues + if(!linked_venue?.open) //Any open venues . += mutable_appearance(icon, "portal_door") /obj/machinery/restaurant_portal/attack_hand(mob/living/user) @@ -217,14 +239,17 @@ for(var/type_key in SSrestaurant.all_venues) var/datum/venue/venue = SSrestaurant.all_venues[type_key] radial_items[venue.name] = image('icons/obj/machines/restaurant_portal.dmi', venue.name) - radial_results[venue.name] = venue + radial_results[venue.name] = type_key var/choice = show_radial_menu(user, src, radial_items, null, require_near = TRUE) if(!choice) return - var/datum/venue/chosen_venue = radial_results[choice] + var/venue_type = radial_results[choice] + var/obj/item/circuitboard/machine/restaurant_portal/board = circuit + board.venue_type = venue_type + var/datum/venue/chosen_venue = SSrestaurant.all_venues[venue_type] turned_on_portal = WEAKREF(user) @@ -234,15 +259,54 @@ to_chat(user, span_notice("You change the portal's linked venue.")) - if(linked_venue && linked_venue.restaurant_portal) //We're already linked, unlink us. - if(linked_venue.open) + if(linked_venue && (src in linked_venue.restaurant_portals)) //We're already linked, unlink us. + linked_venue.restaurant_portals -= src + if(linked_venue.open && !length(linked_venue.restaurant_portals)) linked_venue.close() - linked_venue.restaurant_portal = null linked_venue = null linked_venue = chosen_venue - linked_venue.restaurant_portal = src + linked_venue.restaurant_portals += src +/obj/machinery/restaurant_portal/screwdriver_act(mob/user, obj/item/tool) + if (default_deconstruction_screwdriver(user, "[base_icon_state]-open", base_icon_state, tool)) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING + +/obj/machinery/restaurant_portal/crowbar_act(mob/user, obj/item/tool) + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING + +/obj/machinery/restaurant_portal/wrench_act(mob/living/user, obj/item/tool) + if(!panel_open) + balloon_alert(user, "open the panel first!") + return ITEM_INTERACT_BLOCKING + + if (default_unfasten_wrench(user, tool)) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING + +/obj/machinery/restaurant_portal/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" + return CONTEXTUAL_SCREENTIP_SET + + if(held_item.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = anchored ? "Unsecure" : "Secure" + return CONTEXTUAL_SCREENTIP_SET + + if(held_item.tool_behaviour == TOOL_CROWBAR && panel_open) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET + + if(isidcard(held_item)) + context[SCREENTIP_CONTEXT_LMB] = "Change Venue" + return CONTEXTUAL_SCREENTIP_SET /obj/item/holosign_creator/robot_seat name = "seating indicator placer" diff --git a/code/modules/food_and_drinks/restaurant/custom_order.dm b/code/modules/food_and_drinks/restaurant/custom_order.dm index 5374ee3482a..c4e8861ade5 100644 --- a/code/modules/food_and_drinks/restaurant/custom_order.dm +++ b/code/modules/food_and_drinks/restaurant/custom_order.dm @@ -41,8 +41,10 @@ /// The item type that we want to order, usually clothing var/wanted_clothing_type -/datum/custom_order/moth_clothing/New(datum/venue/our_venue) - var/mob/living/carbon/buffet = our_venue.restaurant_portal?.turned_on_portal?.resolve() +/datum/custom_order/moth_clothing/New(mob/living/basic/robot_customer/customer, datum/venue/our_venue) + var/datum/weakref/portal_ref = our_venue.current_visitors[customer] + var/obj/machinery/restaurant_portal/portal = portal_ref.resolve() + var/mob/living/carbon/buffet = portal?.turned_on_portal?.resolve() if (!istype(buffet)) // Always asks for the clothes that you have on, but this is a fallback. wanted_clothing_type = pick_weight(list( /obj/item/clothing/head/utility/chefhat = 3, @@ -90,7 +92,7 @@ /// stores tha name of our order generated on New() var/icecream_name -/datum/custom_order/icecream/New() +/datum/custom_order/icecream/New(mob/living/basic/robot_customer/customer) if(prob(33)) cone_type = /obj/item/food/icecream/chocolate var/static/list/possible_flavors = list() @@ -136,7 +138,7 @@ /// How many reagents is needed var/reagents_needed = VENUE_BAR_MINIMUM_REAGENTS -/datum/custom_order/reagent/New(reagent_type) +/datum/custom_order/reagent/New(mob/living/basic/robot_customer/customer, reagent_type) . = ..() src.reagent_type = reagent_type @@ -198,7 +200,7 @@ /// What serving we picked for the order var/picked_serving -/datum/custom_order/reagent/soup/New(reagent_type) +/datum/custom_order/reagent/soup/New(mob/living/basic/robot_customer/customer, reagent_type) . = ..() var/list/serving_sizes = list( "small serving (15u)" = 15, diff --git a/code/modules/food_and_drinks/restaurant/customers/_customer.dm b/code/modules/food_and_drinks/restaurant/customers/_customer.dm index b4f3ab2e055..97b5b0c00ec 100644 --- a/code/modules/food_and_drinks/restaurant/customers/_customer.dm +++ b/code/modules/food_and_drinks/restaurant/customers/_customer.dm @@ -53,7 +53,7 @@ orderable_restaurant[/datum/custom_order/icecream] *= 3 /// Can this customer be chosen for this venue? -/datum/customer_data/proc/can_use(datum/venue/venue) +/datum/customer_data/proc/can_use(datum/venue/venue, obj/machinery/restaurant_portal/portal) return TRUE /datum/customer_data/proc/get_overlays(mob/living/basic/robot_customer/customer) @@ -295,8 +295,8 @@ // The whole gag is taking off your hat and giving it to the customer. // If it takes any more effort, it loses a bit of the comedy. // Therefore, only show up if it's reasonable for that gag to happen. -/datum/customer_data/moth/can_use(datum/venue/venue) - var/mob/living/carbon/buffet = venue.restaurant_portal?.turned_on_portal?.resolve() +/datum/customer_data/moth/can_use(datum/venue/venue, obj/machinery/restaurant_portal/portal) + var/mob/living/carbon/buffet = portal.turned_on_portal?.resolve() if (!istype(buffet)) return FALSE if(QDELETED(buffet.head) && QDELETED(buffet.gloves) && QDELETED(buffet.shoes)) diff --git a/code/modules/research/designs/autolathe/multi-department_designs.dm b/code/modules/research/designs/autolathe/multi-department_designs.dm index ec672d6a07c..941dda328cd 100644 --- a/code/modules/research/designs/autolathe/multi-department_designs.dm +++ b/code/modules/research/designs/autolathe/multi-department_designs.dm @@ -168,7 +168,7 @@ /datum/design/toolbox name = "Toolbox" id = "tool_box" - build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + build_type = AUTOLATHE materials = list(MAT_CATEGORY_ITEM_MATERIAL =SMALL_MATERIAL_AMOUNT*5) build_path = /obj/item/storage/toolbox category = list( @@ -536,7 +536,7 @@ name = "Paper Biscuit" desc = "A paper biscuit which can seal paperwork inside. After sealing it the only way to open is through cracking it, cracking is irreversible and makes it permanently open. Not actually a biscuit." id = "biscuit" - build_type = PROTOLATHE | AWAY_LATHE | AUTOLATHE + build_type = AUTOLATHE materials = list(/datum/material/plastic =SMALL_MATERIAL_AMOUNT*0.2) build_path = /obj/item/folder/biscuit/unsealed category = list( @@ -549,7 +549,7 @@ name = "Confidential Paper Biscuit" desc = "A paper biscuit which can seal paperwork inside, this one is used for confidential Nanotrasen documents. After sealing it the only way to open is through cracking it, cracking is irreversible and makes it permanently open. Not actually a biscuit." id = "confidential_biscuit" - build_type = PROTOLATHE | AWAY_LATHE | AUTOLATHE + build_type = AUTOLATHE materials = list(/datum/material/plastic = SMALL_MATERIAL_AMOUNT*0.3) build_path = /obj/item/folder/biscuit/unsealed/confidential category = list( diff --git a/code/modules/research/designs/autolathe/security_designs.dm b/code/modules/research/designs/autolathe/security_designs.dm index 7fbf06e122f..15b4e59abdc 100644 --- a/code/modules/research/designs/autolathe/security_designs.dm +++ b/code/modules/research/designs/autolathe/security_designs.dm @@ -1,7 +1,7 @@ /datum/design/beanbag_slug name = "Beanbag Slug (Less Lethal)" id = "beanbag_slug" - build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + build_type = AUTOLATHE materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT) build_path = /obj/item/ammo_casing/shotgun/beanbag category = list( @@ -13,7 +13,7 @@ /datum/design/rubbershot name = "Rubber Shot (Less Lethal)" id = "rubber_shot" - build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + build_type = AUTOLATHE materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*2) build_path = /obj/item/ammo_casing/shotgun/rubbershot category = list( @@ -25,7 +25,7 @@ /datum/design/c38 name = "Speed Loader (.38) (Lethal)" id = "c38" - build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + build_type = AUTOLATHE materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*10) build_path = /obj/item/ammo_box/c38 category = list( diff --git a/code/modules/research/designs/autolathe/service_designs.dm b/code/modules/research/designs/autolathe/service_designs.dm index 38e36d26914..2f60366a7e5 100644 --- a/code/modules/research/designs/autolathe/service_designs.dm +++ b/code/modules/research/designs/autolathe/service_designs.dm @@ -422,7 +422,7 @@ /datum/design/plastic_tree name = "Plastic Potted Plant" id = "plastic_trees" - build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + build_type = AUTOLATHE materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT*4) build_path = /obj/item/kirbyplants/random/fullysynthetic category = list( @@ -434,7 +434,7 @@ /datum/design/beads name = "Plastic Bead Necklace" id = "plastic_necklace" - build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + build_type = AUTOLATHE materials = list(/datum/material/plastic =SMALL_MATERIAL_AMOUNT*5) build_path = /obj/item/clothing/neck/beads category = list( @@ -446,7 +446,7 @@ /datum/design/plastic_ring name = "Plastic Can Rings" id = "ring_holder" - build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + build_type = AUTOLATHE materials = list(/datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT*1.2) build_path = /obj/item/storage/cans category = list( @@ -458,7 +458,7 @@ /datum/design/plastic_box name = "Plastic Box" id = "plastic_box" - build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + build_type = AUTOLATHE materials = list(/datum/material/plastic =HALF_SHEET_MATERIAL_AMOUNT) build_path = /obj/item/storage/box/plastic category = list( diff --git a/code/modules/research/designs/limbgrower_designs.dm b/code/modules/research/designs/limbgrower_designs.dm index 53309d39c7d..9e594b4af8e 100644 --- a/code/modules/research/designs/limbgrower_designs.dm +++ b/code/modules/research/designs/limbgrower_designs.dm @@ -200,7 +200,6 @@ // Intentionally not growable by normal means - for balance conerns. /datum/design/ethereal_heart name = "Crystal Core" - id = "etherealheart" build_type = LIMBGROWER reagents_list = list(/datum/reagent/medicine/c2/synthflesh = 10, /datum/reagent/consumable/liquidelectricity/enriched = 20) build_path = /obj/item/organ/heart/ethereal @@ -231,7 +230,6 @@ /datum/design/limb_disk name = "Limb Design Disk" desc = "Contains designs for various limbs." - id = "limbdesign_parent" build_type = PROTOLATHE materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT * 3, /datum/material/glass =SMALL_MATERIAL_AMOUNT) build_path = /obj/item/disk/design_disk/limbs diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index 5fb67b7356e..3f86067e3e0 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -398,28 +398,6 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE -/datum/design/board/protolathe - name = "Protolathe Board" - desc = "The circuit board for a protolathe." - id = "protolathe" - build_type = IMPRINTER - build_path = /obj/item/circuitboard/machine/protolathe - category = list( - RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_FAB - ) - departmental_flags = DEPARTMENT_BITFLAG_SCIENCE - -/datum/design/board/protolathe/offstation - name = "Ancient Protolathe Board" - desc = "The circuit board for an ancient protolathe." - id = "protolathe_offstation" - build_type = AWAY_IMPRINTER - build_path = /obj/item/circuitboard/machine/protolathe/offstation - category = list( - RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_FAB - ) - departmental_flags = DEPARTMENT_BITFLAG_SCIENCE - /datum/design/board/circuit_imprinter name = "Circuit Imprinter Board" desc = "The circuit board for a circuit imprinter." diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm index 353dd787806..9c59c741320 100644 --- a/code/modules/research/designs/medical_designs.dm +++ b/code/modules/research/designs/medical_designs.dm @@ -1101,7 +1101,6 @@ /datum/design/surgery name = "Surgery Design" desc = "what" - id = "surgery_parent" research_icon = 'icons/obj/medical/surgery_ui.dmi' research_icon_state = "surgery_any" var/surgery diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm index a836814094d..ae848a63238 100644 --- a/code/modules/research/designs/weapon_designs.dm +++ b/code/modules/research/designs/weapon_designs.dm @@ -475,18 +475,6 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE -/datum/design/stunshell - name = "Stun Shell" - desc = "A stunning shell for a shotgun." - id = "stunshell" - build_type = PROTOLATHE | AWAY_LATHE - materials = list(/datum/material/iron =SMALL_MATERIAL_AMOUNT * 2) - build_path = /obj/item/ammo_casing/shotgun/stunslug - category = list( - RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO - ) - departmental_flags = DEPARTMENT_BITFLAG_SECURITY - /datum/design/lasershell name = "Scatter Laser Shotgun Shell (Lethal)" desc = "A high-tech shotgun shell which houses an internal capacitor and laser focusing crystal inside of a shell casing. \ diff --git a/code/modules/research/techweb/nodes/atmos_nodes.dm b/code/modules/research/techweb/nodes/atmos_nodes.dm index cf36ff06161..e063629af97 100644 --- a/code/modules/research/techweb/nodes/atmos_nodes.dm +++ b/code/modules/research/techweb/nodes/atmos_nodes.dm @@ -15,6 +15,7 @@ "plasmaman_tank_belt", "plasmarefiller", "extinguisher", + "pocketfireextinguisher", "gas_filter", "plasmaman_gas_filter", "analyzer", @@ -40,6 +41,7 @@ "turbine_stator", "atmos_thermal", "pneumatic_seal", + "large_welding_tool", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS) experiments_to_unlock = list( diff --git a/code/modules/research/techweb/nodes/bepis_nodes.dm b/code/modules/research/techweb/nodes/bepis_nodes.dm index a9fe5f48814..997af89e930 100644 --- a/code/modules/research/techweb/nodes/bepis_nodes.dm +++ b/code/modules/research/techweb/nodes/bepis_nodes.dm @@ -21,6 +21,7 @@ design_ids = list( "mauna_mug", "rolling_table", + "plasticducky", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS) hidden = TRUE diff --git a/code/modules/research/techweb/nodes/circuit_nodes.dm b/code/modules/research/techweb/nodes/circuit_nodes.dm index 9ef3b929ac4..44f38afd800 100644 --- a/code/modules/research/techweb/nodes/circuit_nodes.dm +++ b/code/modules/research/techweb/nodes/circuit_nodes.dm @@ -73,7 +73,6 @@ "comp_soundemitter", "comp_species", "comp_speech", - "comp_speech", "comp_split", "comp_string_contains", "comp_tempsensor", diff --git a/code/modules/research/techweb/nodes/engi_nodes.dm b/code/modules/research/techweb/nodes/engi_nodes.dm index 69f653d8db8..638905edd9b 100644 --- a/code/modules/research/techweb/nodes/engi_nodes.dm +++ b/code/modules/research/techweb/nodes/engi_nodes.dm @@ -129,6 +129,7 @@ "firelock_board", "trapdoor_electronics", "blast", + "ignition", "big_manipulator", "tile_sprayer", "airlock_painter", @@ -137,8 +138,8 @@ "cable_coil", "welding_helmet", "welding_tool", + "mini_welding_tool", "tscanner", - "analyzer", "multitool", "wrench", "crowbar", @@ -178,6 +179,7 @@ "inducerengi", "welding_goggles", "tray_goggles", + "geigercounter", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS) announce_channels = list(RADIO_CHANNEL_ENGINEERING) diff --git a/code/modules/research/techweb/nodes/service_nodes.dm b/code/modules/research/techweb/nodes/service_nodes.dm index 6786c2f9e1e..3a0d86d1e9e 100644 --- a/code/modules/research/techweb/nodes/service_nodes.dm +++ b/code/modules/research/techweb/nodes/service_nodes.dm @@ -33,6 +33,9 @@ "normtrash", "wirebrush", "flashlight", + "water_balloon", + "ticket_machine", + "radio_entertainment", ) /datum/techweb_node/sanitation @@ -77,6 +80,13 @@ "custom_vendor_refill", "bounty_pad_control", "bounty_pad", + "digital_clock_frame", + "telescreen_research", + "telescreen_ordnance", + "telescreen_interrogation", + "telescreen_prison", + "telescreen_bar", + "telescreen_entertainment", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS) announce_channels = list(RADIO_CHANNEL_SERVICE) @@ -121,6 +131,8 @@ "coffeemaker", "coffeepot", "syrup_bottle", + "foodtray", + "restaurant_portal", ) /datum/techweb_node/food_proc diff --git a/code/modules/unit_tests/designs.dm b/code/modules/unit_tests/designs.dm index 729f2facf81..52c3cdb36a7 100644 --- a/code/modules/unit_tests/designs.dm +++ b/code/modules/unit_tests/designs.dm @@ -32,3 +32,58 @@ if (isnull(current_design.surgery) || current_design.surgery == default_design_surgery.surgery) //Check if surgery was not set TEST_FAIL("Surgery Design [current_design.type] has default or null surgery var") +/datum/unit_test/design_source + +/datum/unit_test/design_source/Run() + var/list/all_designs = list() + var/list/exceptions = list( + /datum/design/surgery/healing, // Ignored due to the above test + ) + + for (var/datum/design/design as anything in subtypesof(/datum/design)) + var/design_id = design::id + if (design_id == DESIGN_ID_IGNORE || (design in exceptions)) + continue + if (design_id in all_designs) + TEST_FAIL("Design [design] shares an ID \"[design_id]\" with another design") + continue + all_designs[design_id] = design + + for (var/datum/techweb_node/node as anything in subtypesof(/datum/techweb_node)) + node = new node() + for (var/design_id in node.design_ids) + if (!all_designs[design_id]) + TEST_FAIL("Techweb node [node.display_name] ([node.id]) has a design_id \"[design_id]\" which doesn't correspond to any existing design!") + continue + all_designs -= design_id + qdel(node) + + // Designs can also be disk-exclusive + for (var/obj/item/disk/design_disk/design_disk as anything in subtypesof(/obj/item/disk/design_disk)) + design_disk = new design_disk() + for (var/datum/design/design as anything in design_disk.blueprints) + all_designs -= design.id + qdel(design_disk) + + for (var/obj/item/disk/surgery/design_disk as anything in subtypesof(/obj/item/disk/surgery)) + design_disk = new design_disk() + for (var/surgery_type as anything in design_disk.surgeries) + for (var/design_id in all_designs) + var/datum/design/surgery/design = all_designs[design_id] + if (ispath(design, /datum/design/surgery) && design::surgery == surgery_type) + all_designs -= design::id + qdel(design_disk) + + // Or machine-exclusive + for (var/datum/techweb/autounlocking/techweb as anything in subtypesof(/datum/techweb/autounlocking)) + techweb = new techweb() + for (var/design_id in techweb.researched_designs + techweb.hacked_designs) + var/datum/design/design = SSresearch.techweb_design_by_id(design_id) + // If we have a design thats supposed to be printable from a protolathe and an autolathe, but only autolathes can print it + // then we still should error because then we either have a missing design_id or redundant build flags + if (!(design.build_type & (~techweb.allowed_buildtypes))) + all_designs -= design_id + qdel(techweb) + + for (var/missing_id in all_designs) + TEST_FAIL("Design [all_designs[missing_id]] has an ID \"[missing_id]\" which is not in any of the techweb nodes or tech disks, or it is possibly misconfigured!") diff --git a/icons/obj/machines/restaurant_portal.dmi b/icons/obj/machines/restaurant_portal.dmi index ea5c06175094cea746ac197d6958fcf0b373b843..847aae13d3e8211bbfa246f7a239ac91271c19c8 100644 GIT binary patch literal 3440 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