From 524bf9c417ebf75a26268fdc84fda8278d33f9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Barou=C5=A1?= Date: Fri, 30 Oct 2020 12:20:59 +0100 Subject: [PATCH] Mitigates (autolathe) recycling exploits by minimizing what can be recycled, adding unit tests and fixing the recipes (#10375) * Mitigates research recycling exploits by minimizing what can be recycled * Moves the research design check into a new unit test * Adds unit test for stack material recipes costs and fixes these costs * Instead of changing recipes the resulting materials are worth less * Crap fix --- aurorastation.dme | 1 + code/game/machinery/autolathe.dm | 6 +- code/game/machinery/floor_light.dm | 1 + code/game/objects/items/devices/debugger.dm | 3 +- code/game/objects/items/glassjar.dm | 1 + code/game/objects/items/stacks/rods.dm | 3 +- code/game/objects/items/stacks/stack.dm | 44 +++++++----- .../objects/items/stacks/tiles/tile_types.dm | 33 +++++---- code/game/objects/items/weapons/RFD.dm | 3 +- code/game/objects/items/weapons/handcuffs.dm | 1 + .../objects/items/weapons/material/shards.dm | 1 + .../game/objects/items/weapons/power_cells.dm | 3 +- code/game/objects/items/weapons/shields.dm | 1 + .../objects/items/weapons/surgery_tools.dm | 3 +- .../objects/items/weapons/teleportation.dm | 2 +- code/game/objects/objs.dm | 8 +-- code/game/objects/structures/therapy.dm | 2 + code/modules/assembly/assembly.dm | 3 +- code/modules/cooking/trays.dm | 1 + code/modules/materials/material_recipes.dm | 4 +- code/modules/materials/material_sheets.dm | 1 + code/modules/materials/material_synth.dm | 7 +- code/modules/organs/internal/stomach.dm | 4 +- code/modules/power/cable.dm | 1 + code/modules/reagents/reagent_containers.dm | 1 + .../reagents/reagent_containers/glass.dm | 2 +- .../designs/protolathe/medical_designs.dm | 4 +- code/modules/research/rdconsole.dm | 6 +- code/modules/research/research.dm | 40 ++++++----- code/unit_tests/recipe_tests.dm | 71 +++++++++++++++++++ code/unit_tests/unit_test.dm | 14 ++-- html/changelogs/amunak-research-exploits.yml | 11 +++ 32 files changed, 205 insertions(+), 81 deletions(-) create mode 100755 code/unit_tests/recipe_tests.dm create mode 100755 html/changelogs/amunak-research-exploits.yml diff --git a/aurorastation.dme b/aurorastation.dme index 22f86286ce7..d0de7e69b64 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -2875,6 +2875,7 @@ #include "code\unit_tests\object_tests.dm" #include "code\unit_tests\observation_tests.dm" #include "code\unit_tests\overmap_tests.dm" +#include "code\unit_tests\recipe_tests.dm" #include "code\unit_tests\spawner_tests.dm" #include "code\unit_tests\sql_tests.dm" #include "code\unit_tests\ss_test.dm" diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index e25b3f00041..5cfcd344000 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -160,8 +160,8 @@ //Resources are being loaded. var/obj/item/eating = O - if(!eating.matter) - to_chat(user, "\The [eating] does not contain significant amounts of useful materials and cannot be accepted.") + if(!eating.matter || !eating.recyclable) + to_chat(user, SPAN_WARNING("\The [eating] cannot be recycled by \the [src].")) return var/filltype = 0 // Used to determine message. @@ -192,7 +192,7 @@ mass_per_sheet += eating.matter[material] if(!filltype) - to_chat(user, SPAN_NOTICE("\The [src] is full. Please remove material from the autolathe in order to insert more.")) + to_chat(user, SPAN_WARNING("\The [src] is full. Please remove some material in order to insert more.")) return else if(filltype == 1) to_chat(user, SPAN_NOTICE("You fill \the [src] to capacity with \the [eating].")) diff --git a/code/game/machinery/floor_light.dm b/code/game/machinery/floor_light.dm index 1e04f4c5e61..75f5337001b 100644 --- a/code/game/machinery/floor_light.dm +++ b/code/game/machinery/floor_light.dm @@ -12,6 +12,7 @@ var/list/floor_light_cache = list() active_power_usage = 20 power_channel = LIGHT matter = list(DEFAULT_WALL_MATERIAL = 2500, MATERIAL_GLASS = 2750) + recyclable = TRUE var/on var/on_state = "on" diff --git a/code/game/objects/items/devices/debugger.dm b/code/game/objects/items/devices/debugger.dm index 19c264c3b92..6ec507d6ba1 100644 --- a/code/game/objects/items/devices/debugger.dm +++ b/code/game/objects/items/devices/debugger.dm @@ -12,5 +12,4 @@ throw_speed = 3 matter = list(DEFAULT_WALL_MATERIAL = 50, MATERIAL_GLASS = 20) - - origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1) \ No newline at end of file + origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1) diff --git a/code/game/objects/items/glassjar.dm b/code/game/objects/items/glassjar.dm index 087866a9f62..efef7ad3b22 100644 --- a/code/game/objects/items/glassjar.dm +++ b/code/game/objects/items/glassjar.dm @@ -5,6 +5,7 @@ icon_state = "jar_lid" w_class = ITEMSIZE_SMALL matter = list(MATERIAL_GLASS = 200) + recyclable = TRUE flags = NOBLUDGEON var/list/accept_mobs = list(/mob/living/simple_animal/lizard, /mob/living/simple_animal/rat) var/contains = 0 // 0 = nothing, 1 = money, 2 = animal, 3 = spiderling diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 691a110a7ac..1805d0c1b95 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -29,7 +29,8 @@ var/global/list/datum/stack_recipe/rod_recipes = list( throw_range = 20 drop_sound = 'sound/items/drop/metalweapon.ogg' pickup_sound = 'sound/items/pickup/metalweapon.ogg' - matter = list(DEFAULT_WALL_MATERIAL = 1875) + matter = list(DEFAULT_WALL_MATERIAL = 937.5) + recyclable = TRUE max_amount = 60 attack_verb = list("hit", "bludgeoned", "whacked") lock_picking_level = 3 diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index be6d8a87cfc..92a30bdc31b 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -155,23 +155,7 @@ return if (use(required)) - var/atom/O - if(recipe.use_material) - O = new recipe.result_type(user.loc, recipe.use_material) - else - O = new recipe.result_type(user.loc) - O.set_dir(user.dir) - O.add_fingerprint(user) - - if (istype(O, /obj/item/stack)) - var/obj/item/stack/S = O - S.amount = produced - S.update_icon() - S.add_to_stacks(user) - - if (istype(O, /obj/item/storage)) //BubbleWrap - so newly formed boxes are empty - for (var/obj/item/I in O) - qdel(I) + recipe.Produce(produced, user.loc, user.dir, user) /obj/item/stack/Topic(href, href_list) ..() @@ -388,6 +372,30 @@ src.on_floor = on_floor src.use_material = supplied_material +/datum/stack_recipe/proc/Produce(var/amount = 1, var/loc = null, var/dir = NORTH, var/user = null) + if(amount < 1) + return null + + var/atom/O + if(use_material) + O = new result_type(loc, use_material) + else + O = new result_type(loc) + O.set_dir(dir) + O.add_fingerprint(user) + + if (istype(O, /obj/item/stack)) + var/obj/item/stack/S = O + S.amount = amount + S.update_icon() + if(user) + S.add_to_stacks(user) + + if (istype(O, /obj/item/storage)) //BubbleWrap - so newly formed boxes are empty + for (var/obj/item/I in O) + qdel(I) + return O + /* * Recipe list datum */ @@ -397,4 +405,4 @@ /datum/stack_recipe_list/New(new_title, new_recipes) src.title = new_title - src.recipes = new_recipes \ No newline at end of file + src.recipes = new_recipes diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm index 08037a32805..e7e2e4d0d51 100644 --- a/code/game/objects/items/stacks/tiles/tile_types.dm +++ b/code/game/objects/items/stacks/tiles/tile_types.dm @@ -6,12 +6,15 @@ * Carpet */ +#define TILE_MATERIAL_AMOUNT 468 + /obj/item/stack/tile name = "tile" singular_name = "tile" desc = "A non-descript floor tile" w_class = ITEMSIZE_NORMAL max_amount = 60 + recyclable = TRUE icon = 'icons/obj/stacks/tiles.dmi' item_icons = list( slot_l_hand_str = 'icons/mob/items/stacks/lefthand_tiles.dmi', @@ -139,7 +142,7 @@ flags = 0 drop_sound = 'sound/items/drop/cloth.ogg' pickup_sound = 'sound/items/pickup/cloth.ogg' - matter = list(MATERIAL_PLASTIC = 937.5) + matter = list(MATERIAL_PLASTIC = TILE_MATERIAL_AMOUNT) /obj/item/stack/tile/lino_grey name = "linoleum" @@ -153,7 +156,7 @@ flags = 0 drop_sound = 'sound/items/drop/cloth.ogg' pickup_sound = 'sound/items/pickup/cloth.ogg' - matter = list(MATERIAL_PLASTIC = 937.5) + matter = list(MATERIAL_PLASTIC = TILE_MATERIAL_AMOUNT) /* * Circuits @@ -165,7 +168,7 @@ desc = "An advanced tile covered in various circuitry and wiring." icon_state = "tile_bcircuit" force = 6.0 - matter = list(DEFAULT_WALL_MATERIAL = 937.5, MATERIAL_GLASS = 937.5) + matter = list(DEFAULT_WALL_MATERIAL = TILE_MATERIAL_AMOUNT, MATERIAL_GLASS = TILE_MATERIAL_AMOUNT) throwforce = 15.0 throw_speed = 5 throw_range = 20 @@ -177,7 +180,7 @@ desc = "An advanced tile covered in various circuitry and wiring." icon_state = "tile_gcircuit" force = 6.0 - matter = list(DEFAULT_WALL_MATERIAL = 937.5, MATERIAL_GLASS = 937.5) + matter = list(DEFAULT_WALL_MATERIAL = TILE_MATERIAL_AMOUNT, MATERIAL_GLASS = TILE_MATERIAL_AMOUNT) throwforce = 15.0 throw_speed = 5 throw_range = 20 @@ -193,7 +196,7 @@ desc = "Those could work as a pretty decent throwing weapon" //why? icon_state = "tile" force = 6.0 - matter = list(DEFAULT_WALL_MATERIAL = 937.5) + matter = list(DEFAULT_WALL_MATERIAL = TILE_MATERIAL_AMOUNT) throwforce = 15.0 throw_speed = 5 throw_range = 20 @@ -209,13 +212,13 @@ name = "steel floor tile" singular_name = "steel floor tile" icon_state = "tile_steel" - matter = list(MATERIAL_PLASTEEL = 937.5) + matter = list(MATERIAL_PLASTEEL = TILE_MATERIAL_AMOUNT) /obj/item/stack/tile/floor_white name = "white floor tile" singular_name = "white floor tile" icon_state = "tile_white" - matter = list(MATERIAL_PLASTIC = 937.5) + matter = list(MATERIAL_PLASTIC = TILE_MATERIAL_AMOUNT) /obj/item/stack/tile/floor_yellow name = "yellow floor tile" @@ -227,43 +230,43 @@ name = "dark floor tile" singular_name = "dark floor tile" icon_state = "fr_tile" - matter = list(MATERIAL_PLASTEEL = 937.5) + matter = list(MATERIAL_PLASTEEL = TILE_MATERIAL_AMOUNT) /obj/item/stack/tile/floor_freezer name = "freezer floor tile" singular_name = "freezer floor tile" icon_state = "tile_freezer" - matter = list(MATERIAL_PLASTIC = 937.5) + matter = list(MATERIAL_PLASTIC = TILE_MATERIAL_AMOUNT) /obj/item/stack/tile/silver name = "silver floor tile" singular_name = "silver floor tile" icon_state = "tile_silver" - matter = list(MATERIAL_SILVER = 937.5) + matter = list(MATERIAL_SILVER = TILE_MATERIAL_AMOUNT) /obj/item/stack/tile/gold name = "golden floor tile" singular_name = "golden floor tile" icon_state = "tile_gold" - matter = list(MATERIAL_GOLD = 937.5) + matter = list(MATERIAL_GOLD = TILE_MATERIAL_AMOUNT) /obj/item/stack/tile/uranium name = "uranium floor tile" singular_name = "uranium floor tile" icon_state = "tile_uranium" - matter = list(MATERIAL_URANIUM = 937.5) + matter = list(MATERIAL_URANIUM = TILE_MATERIAL_AMOUNT) /obj/item/stack/tile/phoron name = "phoron floor tile" singular_name = "phoron floor tile" icon_state = "tile_plasma" - matter = list(MATERIAL_PHORON = 937.5) + matter = list(MATERIAL_PHORON = TILE_MATERIAL_AMOUNT) /obj/item/stack/tile/diamond name = "diamond floor tile" singular_name = "diamond floor tile" icon_state = "tile_diamond" - matter = list(MATERIAL_DIAMOND = 937.5) + matter = list(MATERIAL_DIAMOND = TILE_MATERIAL_AMOUNT) /* * Cyborg modules @@ -320,3 +323,5 @@ charge_costs = list(250) stacktype = /obj/item/stack/tile/carpet build_type = /obj/item/stack/tile/carpet + +#undef TILE_MATERIAL_AMOUNT diff --git a/code/game/objects/items/weapons/RFD.dm b/code/game/objects/items/weapons/RFD.dm index fcafdf35df1..4768ede225c 100644 --- a/code/game/objects/items/weapons/RFD.dm +++ b/code/game/objects/items/weapons/RFD.dm @@ -141,7 +141,8 @@ item_state = "rfdammo" w_class = ITEMSIZE_SMALL origin_tech = list(TECH_MATERIAL = 2) - matter = list(DEFAULT_WALL_MATERIAL = 30000, MATERIAL_GLASS = 15000) + matter = list(DEFAULT_WALL_MATERIAL = 2000, MATERIAL_GLASS = 2000) + recyclable = TRUE /* RFD Construction-Class diff --git a/code/game/objects/items/weapons/handcuffs.dm b/code/game/objects/items/weapons/handcuffs.dm index 86d4cbc2f5b..ea7947c5533 100644 --- a/code/game/objects/items/weapons/handcuffs.dm +++ b/code/game/objects/items/weapons/handcuffs.dm @@ -12,6 +12,7 @@ throw_range = 5 origin_tech = list(TECH_MATERIAL = 1) matter = list(DEFAULT_WALL_MATERIAL = 500) + recyclable = TRUE var/elastic var/dispenser = 0 var/breakouttime = 1200 //Deciseconds = 120s = 2 minutes diff --git a/code/game/objects/items/weapons/material/shards.dm b/code/game/objects/items/weapons/material/shards.dm index e034b528047..e748105aa95 100644 --- a/code/game/objects/items/weapons/material/shards.dm +++ b/code/game/objects/items/weapons/material/shards.dm @@ -8,6 +8,7 @@ randpixel = 8 sharp = 1 edge = 1 + recyclable = TRUE w_class = ITEMSIZE_SMALL force_divisor = 0.2 // 6 with hardness 30 (glass) thrown_force_divisor = 0.4 // 4 with weight 15 (glass) diff --git a/code/game/objects/items/weapons/power_cells.dm b/code/game/objects/items/weapons/power_cells.dm index 8432056462a..26d309c5fd5 100644 --- a/code/game/objects/items/weapons/power_cells.dm +++ b/code/game/objects/items/weapons/power_cells.dm @@ -15,6 +15,7 @@ var/rigged = 0 // true if rigged to explode var/minor_fault = 0 //If not 100% reliable, it will build up faults. matter = list(DEFAULT_WALL_MATERIAL = 700, MATERIAL_GLASS = 50) + recyclable = TRUE //currently only used by energy-type guns, that may change in the future. /obj/item/cell/device @@ -94,7 +95,7 @@ origin_tech = list(TECH_POWER = 6) icon_state = "hpcell" maxcharge = 30000 - matter = list(DEFAULT_WALL_MATERIAL = 700, MATERIAL_GLASS = 80) + matter = list(DEFAULT_WALL_MATERIAL = 200, MATERIAL_GOLD = 50, MATERIAL_SILVER = 50, MATERIAL_GLASS = 40) /obj/item/cell/hyper/empty/Initialize() . = ..() diff --git a/code/game/objects/items/weapons/shields.dm b/code/game/objects/items/weapons/shields.dm index 97b84612fc4..0ba75c8ca3c 100644 --- a/code/game/objects/items/weapons/shields.dm +++ b/code/game/objects/items/weapons/shields.dm @@ -37,6 +37,7 @@ slot_r_hand_str = 'icons/mob/items/weapons/righthand_shield.dmi' ) var/base_block_chance = 50 + recyclable = TRUE /obj/item/shield/handle_shield(mob/user, var/on_back, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") if(user.incapacitated()) diff --git a/code/game/objects/items/weapons/surgery_tools.dm b/code/game/objects/items/weapons/surgery_tools.dm index 55af2e5a2ed..242a5a4770e 100644 --- a/code/game/objects/items/weapons/surgery_tools.dm +++ b/code/game/objects/items/weapons/surgery_tools.dm @@ -8,7 +8,7 @@ * Circular Saw * Tray */ -/obj/item/surgery/ +/obj/item/surgery name = "surgery tool" desc = "hey, you aren't supposed to have this" icon = 'icons/obj/surgery.dmi' @@ -19,6 +19,7 @@ slot_l_hand_str = 'icons/mob/items/lefthand_medical.dmi', slot_r_hand_str = 'icons/mob/items/righthand_medical.dmi', ) + recyclable = TRUE /* * Retractor diff --git a/code/game/objects/items/weapons/teleportation.dm b/code/game/objects/items/weapons/teleportation.dm index 2eba0834109..e44989ebd09 100644 --- a/code/game/objects/items/weapons/teleportation.dm +++ b/code/game/objects/items/weapons/teleportation.dm @@ -140,7 +140,7 @@ Frequency: /obj/item/hand_tele/Initialize() . = ..() - if(ismob(loc) || ismob(loc.loc)) + if(get(loc, /mob)) maptext = held_maptext /obj/item/hand_tele/attack_self(mob/user) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index a0227dd7c95..b28c574eeb5 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -1,12 +1,12 @@ /obj animate_movement = 2 - var/list/matter //Used to store information about the contents of the object. + var/recyclable = FALSE //Whether the object can be recycled (eaten) by something like the Autolathe var/w_class // Size of the object. var/list/origin_tech = null //Used by R&D to determine what research bonuses it grants. var/unacidable = 0 //universal "unacidabliness" var, here so you can use it in any obj. - + var/obj_flags //Special flags such as whether or not this object can be rotated. var/throwforce = 1 var/list/attack_verb //Used in attackby() to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]" @@ -221,11 +221,11 @@ /obj/proc/rotate(var/mob/user, var/anchored_ignore = FALSE) if(use_check_and_message(user)) return - + if(anchored && !anchored_ignore) to_chat(user, SPAN_WARNING("\The [src] is bolted down to the floor!")) return - + set_dir(turn(dir, 90)) update_icon() diff --git a/code/game/objects/structures/therapy.dm b/code/game/objects/structures/therapy.dm index 0f13ee5bdf7..79c830e796f 100644 --- a/code/game/objects/structures/therapy.dm +++ b/code/game/objects/structures/therapy.dm @@ -81,6 +81,7 @@ drop_sound = 'sound/items/drop/accessory.ogg' pickup_sound = 'sound/items/pickup/accessory.ogg' matter = list(MATERIAL_GLASS = 150, MATERIAL_GOLD = 50) + recyclable = TRUE w_class = ITEMSIZE_TINY var/closed = FALSE @@ -128,6 +129,7 @@ drop_sound = 'sound/items/drop/accessory.ogg' pickup_sound = 'sound/items/pickup/accessory.ogg' matter = list(MATERIAL_GLASS = 150, MATERIAL_GOLD = 50) + recyclable = TRUE w_class = ITEMSIZE_TINY var/datum/weakref/thrall = null var/time_counter = 0 diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm index 54ac19494fd..627206c30e8 100644 --- a/code/modules/assembly/assembly.dm +++ b/code/modules/assembly/assembly.dm @@ -6,6 +6,7 @@ flags = CONDUCT w_class = ITEMSIZE_SMALL matter = list(DEFAULT_WALL_MATERIAL = 100) + recyclable = TRUE throwforce = 2 throw_speed = 3 throw_range = 10 @@ -108,4 +109,4 @@ /obj/item/device/assembly/interact(mob/user) - return \ No newline at end of file + return diff --git a/code/modules/cooking/trays.dm b/code/modules/cooking/trays.dm index a64d792faaa..7575c024547 100644 --- a/code/modules/cooking/trays.dm +++ b/code/modules/cooking/trays.dm @@ -17,6 +17,7 @@ w_class = 3.0 flags = CONDUCT matter = list(DEFAULT_WALL_MATERIAL = 3000) + recyclable = TRUE hitsound = /decl/sound_category/bottle_hit_broken drop_sound = /decl/sound_category/bottle_hit_broken var/max_carry = 20 diff --git a/code/modules/materials/material_recipes.dm b/code/modules/materials/material_recipes.dm index eef4890f466..224caeb6952 100644 --- a/code/modules/materials/material_recipes.dm +++ b/code/modules/materials/material_recipes.dm @@ -267,14 +267,14 @@ ..() recipes += new /datum/stack_recipe_list("[display_name] construction", list( - new /datum/stack_recipe("phoron floor tile", /turf/simulated/floor/phoron, 1, 4, 20) + new /datum/stack_recipe("phoron floor tile", /obj/item/stack/tile/phoron, 1, 4, 20) )) /material/diamond/generate_recipes() ..() recipes += new /datum/stack_recipe_list("[display_name] construction", list( - new /datum/stack_recipe("diamond floor tile", /turf/simulated/floor/diamond, 1, 4, 20) + new /datum/stack_recipe("diamond floor tile", /obj/item/stack/tile/diamond, 1, 4, 20) )) /material/leather/generate_recipes() diff --git a/code/modules/materials/material_sheets.dm b/code/modules/materials/material_sheets.dm index de834a9e9ad..0059823a7b1 100644 --- a/code/modules/materials/material_sheets.dm +++ b/code/modules/materials/material_sheets.dm @@ -7,6 +7,7 @@ throw_speed = 3 throw_range = 3 max_amount = 50 + recyclable = TRUE // Pretty much all materials should be recyclable var/default_type = DEFAULT_WALL_MATERIAL var/material/material diff --git a/code/modules/materials/material_synth.dm b/code/modules/materials/material_synth.dm index cd78506e979..fdace5a2769 100644 --- a/code/modules/materials/material_synth.dm +++ b/code/modules/materials/material_synth.dm @@ -4,7 +4,10 @@ uses_charge = 1 charge_costs = list(1000) gender = NEUTER - matter = null // Don't shove it in the autholathe. + + // Don't shove it in the autholathe + recyclable = FALSE + matter = null /obj/item/stack/material/cyborg/New() if(..()) @@ -39,4 +42,4 @@ As a synthetic, you can gain more reinforced glass by recharging." icon_state = "sheet-rglass" default_type = MATERIAL_GLASS_REINFORCED - charge_costs = list(500, 1000) \ No newline at end of file + charge_costs = list(500, 1000) diff --git a/code/modules/organs/internal/stomach.dm b/code/modules/organs/internal/stomach.dm index 1d0a4d5aacb..552e087779a 100644 --- a/code/modules/organs/internal/stomach.dm +++ b/code/modules/organs/internal/stomach.dm @@ -26,7 +26,7 @@ ingested = new /datum/reagents/metabolism(240, owner, CHEM_INGEST) if(!ingested.my_atom) ingested.my_atom = src - if(species.gluttonous) + if(species && species.gluttonous) action_button_name = PUKE_ACTION_NAME /obj/item/organ/internal/stomach/removed() @@ -140,4 +140,4 @@ if(!QDELETED(M)) qdel(M) -#undef PUKE_ACTION_NAME \ No newline at end of file +#undef PUKE_ACTION_NAME diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index 7bf387110b1..b4714d435b9 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -475,6 +475,7 @@ obj/structure/cable/proc/cableColor(var/colorC) throw_speed = 2 throw_range = 5 matter = list(DEFAULT_WALL_MATERIAL = 50, MATERIAL_GLASS = 20) + recyclable = TRUE flags = CONDUCT slot_flags = SLOT_BELT attack_verb = list("whipped", "lashed", "disciplined", "flogged") diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm index de75456de51..d6cddf1d910 100644 --- a/code/modules/reagents/reagent_containers.dm +++ b/code/modules/reagents/reagent_containers.dm @@ -4,6 +4,7 @@ icon = 'icons/obj/chemical.dmi' icon_state = null w_class = ITEMSIZE_SMALL + recyclable = TRUE var/amount_per_transfer_from_this = 5 var/possible_transfer_amounts = list(5,10,15,25,30) var/volume = 30 diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm index 4a2aefc4228..21069146325 100644 --- a/code/modules/reagents/reagent_containers/glass.dm +++ b/code/modules/reagents/reagent_containers/glass.dm @@ -172,7 +172,7 @@ desc = "A bluespace beaker, powered by experimental bluespace technology." icon_state = "beakerbluespace" center_of_mass = list("x" = 16,"y" = 11) - matter = list(MATERIAL_GLASS = 5000) + matter = list(MATERIAL_PHORON = 1000, MATERIAL_DIAMOND = 100) volume = 300 amount_per_transfer_from_this = 10 possible_transfer_amounts = list(5,10,15,25,30,60,120,300) diff --git a/code/modules/research/designs/protolathe/medical_designs.dm b/code/modules/research/designs/protolathe/medical_designs.dm index bd570a83ce6..838746ce860 100644 --- a/code/modules/research/designs/protolathe/medical_designs.dm +++ b/code/modules/research/designs/protolathe/medical_designs.dm @@ -116,11 +116,11 @@ /datum/design/item/beaker/noreact desc = "A cryostasis beaker that allows for chemical storage without reactions. Can hold up to 50 units." req_tech = list(TECH_MATERIAL = 2) - materials = list(DEFAULT_WALL_MATERIAL = 3000) + materials = list(DEFAULT_WALL_MATERIAL = 2000, MATERIAL_GLASS = 1000) build_path = /obj/item/reagent_containers/glass/beaker/noreact /datum/design/item/beaker/bluespace desc = "A bluespace beaker, powered by experimental bluespace technology and Element Cuban combined with the Compound Pete. Can hold up to 300 units." req_tech = list(TECH_BLUESPACE = 2, TECH_MATERIAL = 6) materials = list(DEFAULT_WALL_MATERIAL = 3000, MATERIAL_PHORON = 3000, MATERIAL_DIAMOND = 500) - build_path = /obj/item/reagent_containers/glass/beaker/bluespace \ No newline at end of file + build_path = /obj/item/reagent_containers/glass/beaker/bluespace diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index c7169c0b9d9..e529619925f 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -309,14 +309,14 @@ won't update every console in existence) but it's more of a hassle to do. Also, sync = !sync else if(href_list["protolathe_category"]) - var/choice = input("Which category do you wish to display?") as null|anything in files.protolathe_categories+"All" + var/choice = input("Which category do you wish to display?") as null|anything in designs_protolathe_categories+"All" if(!choice) return protolathe_category = choice updateUsrDialog() else if(href_list["imprinter_category"]) - var/choice = input("Which category do you wish to display?") as null|anything in files.imprinter_categories+"All" + var/choice = input("Which category do you wish to display?") as null|anything in designs_protolathe_categories+"All" if(!choice) return imprinter_category = choice @@ -867,4 +867,4 @@ won't update every console in existence) but it's more of a hassle to do. Also, /obj/machinery/computer/rdconsole/core name = "core R&D console" desc = "A console which is used to operate various research devices. It is the backbone of any megacorporate research division." - id = 1 \ No newline at end of file + id = 1 diff --git a/code/modules/research/research.dm b/code/modules/research/research.dm index 9037351dab6..ce9a51bc9c8 100644 --- a/code/modules/research/research.dm +++ b/code/modules/research/research.dm @@ -11,7 +11,6 @@ with these since they should be the default version of the datums. They're actua refer to them since it makes it a bit easier to search through them for specific information. - know_tech is the companion list to possible_tech. It's the tech you can actually research and improve. Until it's added to this list, it can't be improved. All the tech in this list are visible to the player. -- possible_designs is functionally identical to possbile_tech except it's for /datum/design. - known_designs is functionally identical to known_tech except it's for /datum/design Procs: @@ -23,7 +22,7 @@ it doesn't have it, it adds it. Note: It does NOT check possible_tech at all. So a player made tech?) you can. - AddDesign2Known: Same as AddTech2Known except for /datum/design and known_designs. - RefreshResearch: This is the workhorse of the R&D system. It updates the /datum/research holder and adds any unlocked tech paths -and designs you have reached the requirements for. It only checks through possible_tech and possible_designs, however, so it won't +and designs you have reached the requirements for. It only checks through possible_tech and designs, however, so it won't accidentally add "secret" tech to it. - UpdateTech is used as part of the actual researching process. It takes an ID and finds techs with that same ID in known_tech. When it finds it, it checks to see whether it can improve it at all. If the known_tech's level is less then or equal to @@ -44,11 +43,13 @@ research holder datum. ** Includes all the helper procs and basic tech processing. ** ***************************************************************/ +// Global design lists +var/global/list/designs = null +var/global/list/designs_protolathe_categories = list() +var/global/list/designs_imprinter_categories = list() + /datum/research //Holder for all the existing, archived, and known tech. Individual to console. var/list/known_tech = list() //List of locally known tech. Datum/tech go here. - var/list/possible_designs = list() //List of all designs. - var/list/protolathe_categories = list() - var/list/imprinter_categories = list() var/list/known_designs = list() //List of available designs. var/standard_start_level // The level non-antag techs are set at @@ -67,14 +68,8 @@ research holder datum. else if(antag_start_level) T.level = antag_start_level - if(load_designs) - for(var/design_path in subtypesof(/datum/design)) - var/datum/design/D = new design_path(src) - if(D.build_type & PROTOLATHE) - protolathe_categories |= D.p_category - if(D.build_type & IMPRINTER) - imprinter_categories |= D.p_category - possible_designs[D.type] = D + if(load_designs && isnull(designs)) + InitializeDesigns() RefreshResearch() /datum/research/techonly @@ -83,6 +78,16 @@ research holder datum. /datum/research/hightech standard_start_level = 3 +/datum/research/proc/InitializeDesigns() + designs = list() + for(var/T in subtypesof(/datum/design)) + var/datum/design/D = new T + designs[D.type] = D + if(D.build_type & PROTOLATHE) + designs_protolathe_categories |= D.p_category + if(D.build_type & IMPRINTER) + designs_imprinter_categories |= D.p_category + //Checks to see if design has all the required pre-reqs. //Input: datum/design; Output: 0/1 (false/true) /datum/research/proc/DesignHasReqs(var/datum/design/D) @@ -123,10 +128,11 @@ research holder datum. //Input/Output: n/a /datum/research/proc/RefreshResearch() known_designs.Cut() // this is to refresh the ordering of the designs, the alternative is an expensive insertion or sorting proc - for(var/path in possible_designs) - var/datum/design/PD = possible_designs[path] - if(DesignHasReqs(PD)) - AddDesign2Known(PD) + if(load_designs) + for(var/path in designs) + var/datum/design/PD = designs[path] + if(DesignHasReqs(PD)) + AddDesign2Known(PD) for(var/id in known_tech) var/datum/tech/T = known_tech[id] T.level = between(0, T.level, MAX_TECH_LEVEL) diff --git a/code/unit_tests/recipe_tests.dm b/code/unit_tests/recipe_tests.dm new file mode 100755 index 00000000000..b7410517822 --- /dev/null +++ b/code/unit_tests/recipe_tests.dm @@ -0,0 +1,71 @@ +/* + * Unit Tests for various recipes. + * + */ + +/datum/unit_test/research_design_cost + name = "RECIPES: Design Cost" + +/datum/unit_test/research_design_cost/start_test() + var/tested_count = 0 + var/error_count = 0 + for(var/T in subtypesof(/datum/design)) + var/datum/design/D = new T + if(ispath(D.build_path, /obj/item)) + var/obj/item/I = D.Fabricate() + if(I.matter && D.materials && I.recyclable) // non-recyclable items can't be exploited + for(var/mat in I.matter) + tested_count++ + if(mat in D.materials) + if(I.matter[mat] > D.materials[mat]) + fail("Design '[D.name]' costs less material '[mat]' ([D.materials[mat]]) than the product is worth ([I.matter[mat]]).") + error_count++ + else + fail("Design '[D.name]' does not require material '[mat]' even though the product is worth [I.matter[mat]].") + error_count++ + qdel(I) + qdel(D) + + if(error_count) + fail("[error_count] design error(s) found. Every research design should cost more than what its product is worth when recycled.") + else + pass("All [tested_count] research designs with recyclable products have correct material costs.") + + return 1 + + +/datum/unit_test/stack_recipe_cost + name = "RECIPES: Stack Recipes" + +/datum/unit_test/stack_recipe_cost/start_test() + var/tested_count = 0 + var/error_count = 0 + for(var/T in subtypesof(/material)) + var/material/D = new T + var/list/datum/stack_recipe_list/recipe_lists = D.get_recipes() + var/list/temp_matter = D.get_matter() + for(var/datum/stack_recipe_list/L in recipe_lists) + for(var/datum/stack_recipe/R in L.recipes) + if(!ispath(R.result_type, /obj/item)) + continue + var/obj/item/I = R.Produce() + if(I.matter && I.recyclable) // non-recyclable items can't be exploited + tested_count++ + for(var/mat in I.matter) + if(mat in temp_matter) + var/item_matter_value = I.matter[mat] * R.res_amount + var/consumed_matter_value = temp_matter[mat] * R.req_amount + if(item_matter_value > consumed_matter_value) + fail("Recipe '[R.title]' on material '[D.name]' consumes less material '[mat]' ([R.req_amount] × [temp_matter[mat]] = [consumed_matter_value]) than the product is worth ([R.res_amount] × [I.matter[mat]] = [item_matter_value]).") + error_count++ + else + warn("Recipe '[R.title]' on material '[D.name]' creates product with material '[mat]', but that material is not required by the recipe.") + qdel(I) + qdel(D) + + if(error_count) + fail("[error_count] stack recipe error(s) found. Every stack recipe should cost more than what its product is worth when recycled.") + else + pass("All [tested_count] stack recipes with recyclable /obj/item products have correct material costs.") + + return 1 diff --git a/code/unit_tests/unit_test.dm b/code/unit_tests/unit_test.dm index e44da99a335..17412be038f 100644 --- a/code/unit_tests/unit_test.dm +++ b/code/unit_tests/unit_test.dm @@ -1,11 +1,11 @@ /* Unit Tests originally designed by Ccomp5950 - * + * * Tests are created to prevent changes that would create bugs or change expected behaviour. * For the most part I think any test can be created that doesn't require a client in a mob or require a game mode other then extended - * + * * The easiest way to make effective tests is to create a "template" if you intend to run the same test over and over and make your actual * tests be a "child object" of those templates. Be sure and name your templates with the word "template" somewhere in var/name. - * + * * The goal is to have all sorts of tests that run and to run them as quickly as possible. * * Tests that require time to run we instead just check back on their results later instead of waiting around in a sleep(1) for each test. @@ -16,7 +16,7 @@ * * If your test requires a significant amount of time...cheat on the timers. Either speed up the process/life runs or do as we did in the timers for the shuttle * transfers in zas_tests.dm we move a shuttle but instead of waiting 3 minutes we set the travel time to a very low number. - * + * * At the same time, Unit tests are intended to reflect standard usage so avoid changing to much about how stuff is processed. * * @@ -34,6 +34,7 @@ var/total_unit_tests = 0 var/ascii_esc = ascii2text(27) var/ascii_red = "[ascii_esc]\[31m" var/ascii_green = "[ascii_esc]\[32m" +var/ascii_yellow = "[ascii_esc]\[33m" var/ascii_reset = "[ascii_esc]\[0m" @@ -58,13 +59,16 @@ datum/unit_test/proc/pass(var/message) reported = 1 log_unit_test("[ascii_green]*** SUCCESS *** \[[name]\]: [message][ascii_reset]") +datum/unit_test/proc/warn(var/message) + log_unit_test("[ascii_yellow]=== WARNING === \[[name]\]: [message][ascii_reset]") + datum/unit_test/proc/start_test() fail("No test proc.") datum/unit_test/proc/check_result() fail("No check results proc") return 1 - + proc/load_unit_test_changes() /* diff --git a/html/changelogs/amunak-research-exploits.yml b/html/changelogs/amunak-research-exploits.yml new file mode 100755 index 00000000000..df84e8cca2f --- /dev/null +++ b/html/changelogs/amunak-research-exploits.yml @@ -0,0 +1,11 @@ +author: Amunak +delete-after: True +changes: + - rscdel: "It is no longer possible to recycle any random crap in the Autolathe. It is now limited to 'reasonable' items such as stack materials, shards, tiles, reagent containers, power cells, shields and a handful of other things. Complex devices, guns, stuff that clearly wouldn't fit or doesn't make much sense to recycle (returns almost no useful materials) is no longer possible to recycle." + - bugfix: "It should be no longer possible to 'cheat' in the Autolathe by inserting items that give more materials than they cost to make in the Protolathe." + - bugfix: "Fixed a handful of the remaining problematic autolathe recipes / recyclation materials to not cost less than they're worth." + - bugfix: "Fixed stack material recipes to not create more product matter than they consume: metal rods and all floor tiles are now worth about half of their previous materials." + - bugfix: "Fixes minor instatiation issues with hand teleporter and an organ when initialized in a non-typical way." + - refactor: "Moved the list of research designs to global level. This allows only single initialization of it." + - backend: "Added unit test to check if material costs of all research designs are greater than what can be gained from recycling their products." + - backend: "Added unit test to check if material costs of stack recipes are greater than what can be gained from recycling their products."