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
This commit is contained in:
Jiří Barouš
2020-10-30 12:20:59 +01:00
committed by GitHub
parent 821d790332
commit 524bf9c417
32 changed files with 205 additions and 81 deletions
+3 -3
View File
@@ -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]."))
+1
View File
@@ -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"
+1 -2
View File
@@ -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)
origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1)
+1
View File
@@ -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
+2 -1
View File
@@ -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
+26 -18
View File
@@ -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
src.recipes = new_recipes
@@ -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
+2 -1
View File
@@ -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
@@ -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
@@ -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)
@@ -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()
. = ..()
@@ -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())
@@ -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
@@ -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)
+4 -4
View File
@@ -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()
+2
View File
@@ -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
+2 -1
View File
@@ -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
return
+1
View File
@@ -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
+2 -2
View File
@@ -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()
@@ -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
+5 -2
View File
@@ -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)
charge_costs = list(500, 1000)
+2 -2
View File
@@ -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
#undef PUKE_ACTION_NAME
+1
View File
@@ -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")
@@ -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
@@ -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)
@@ -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
build_path = /obj/item/reagent_containers/glass/beaker/bluespace
+3 -3
View File
@@ -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
id = 1
+23 -17
View File
@@ -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)
+71
View File
@@ -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
+9 -5
View File
@@ -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()
/*