[MDB Ignore] Adds a unit test for typepaths that are required to be mapped onto each station map (#74985)

## About The Pull Request

Inspired by #74967 and #68459 , and the fact that Tramstation regresses
very often -

Adds a unit test, `required_map_items`, which ensures that certain
typepaths which should definitely be mapped onto every map is mapped
onto every map

It can also be used to ensure that items which should not be mapped in
multiple times are not, among other things.

I included a few examples - 
- Min 1, max inf of each head of staff stamps
- Min 1, max 1 departmental order consoles
- Min 1, max inf comms console
- Min 1, max 1 Pun Pun
- Min 1, max 1 Poly
- Min 1, max 1 Ian

If, in the future, a mapper decides they (for some reason) do not want a
certain previously-required item on their map, the test can be adjusted
such that it allows excluding or something, but currently it should be
for items which require conscious thought about.

#### QA: Why not make this a linter?

I attempted to make this a linter before realizing two things
1. Someone might make a spawner which spawns the items, or they might
get placed in a locker, in any case this accounts for everything on init
2. Linters run on every map, non-station maps included

So I went with a test

## Why It's Good For The Game

#50468
#61013
#74967

Why is it always the CMO stamp?

## Changelog

Not necessary (unless I find a map missing something, then this will be
updated)
This commit is contained in:
MrMelbert
2023-05-04 16:20:25 -05:00
committed by GitHub
parent 7aef07fc85
commit 4de3df461e
36 changed files with 202388 additions and 202275 deletions
+3
View File
@@ -24,6 +24,9 @@ GLOBAL_LIST_INIT(department_order_cooldowns, list(
/obj/machinery/computer/department_orders/Initialize(mapload, obj/item/circuitboard/board)
. = ..()
// All maps should have ONLY ONE of each order console roundstart
REGISTER_REQUIRED_MAP_ITEM(1, 1)
if(mapload) //check for mapping errors
for(var/delivery_area_type in department_delivery_areas)
if(GLOB.areas_by_type[delivery_area_type])
+1 -1
View File
@@ -79,7 +79,7 @@
accessory = /obj/item/clothing/accessory/medal/gold/captain
chameleon_extras = list(
/obj/item/gun/energy/e_gun,
/obj/item/stamp/captain,
/obj/item/stamp/head/captain,
)
implants = list(/obj/item/implant/mindshield)
skillchips = list(/obj/item/skillchip/disk_verifier)
@@ -74,7 +74,7 @@
duffelbag = /obj/item/storage/backpack/duffelbag/engineering
box = /obj/item/storage/box/survival/engineer
chameleon_extras = /obj/item/stamp/ce
chameleon_extras = /obj/item/stamp/head/ce
skillchips = list(/obj/item/skillchip/job/engineer)
pda_slot = ITEM_SLOT_LPOCKET
@@ -76,7 +76,7 @@
box = /obj/item/storage/box/survival/medical
chameleon_extras = list(
/obj/item/gun/syringe,
/obj/item/stamp/cmo,
/obj/item/stamp/head/cmo,
)
skillchips = list(/obj/item/skillchip/entrails_reader)
@@ -66,7 +66,7 @@
chameleon_extras = list(
/obj/item/gun/energy/e_gun,
/obj/item/stamp/hop,
/obj/item/stamp/head/hop,
)
/datum/outfit/job/hop/pre_equip(mob/living/carbon/human/H)
@@ -71,7 +71,7 @@
box = /obj/item/storage/box/survival/security
chameleon_extras = list(
/obj/item/gun/energy/e_gun/hos,
/obj/item/stamp/hos,
/obj/item/stamp/head/hos,
)
implants = list(/obj/item/implant/mindshield)
+1 -1
View File
@@ -51,4 +51,4 @@
shoes = /obj/item/clothing/shoes/sneakers/brown
l_hand = /obj/item/clipboard
chameleon_extras = /obj/item/stamp/qm
chameleon_extras = /obj/item/stamp/head/qm
@@ -73,7 +73,7 @@
satchel = /obj/item/storage/backpack/satchel/science
duffelbag = /obj/item/storage/backpack/duffelbag/science
chameleon_extras = /obj/item/stamp/rd
chameleon_extras = /obj/item/stamp/head/rd
skillchips = list(/obj/item/skillchip/job/research_director)
/datum/outfit/job/rd/mod
@@ -541,6 +541,9 @@ GLOBAL_LIST_INIT(strippable_corgi_items, create_strippable_list(list(
/mob/living/basic/pet/dog/corgi/ian/Initialize(mapload)
. = ..()
// Ensure Ian exists
REGISTER_REQUIRED_MAP_ITEM(1, 1)
//parent call must happen first to ensure IAN
//is not in nullspace when child puppies spawn
Read_Memory()
@@ -65,6 +65,8 @@ GLOBAL_DATUM(the_one_and_only_punpun, /mob/living/carbon/human/species/monkey/pu
if(!GLOB.the_one_and_only_punpun && mapload)
GLOB.the_one_and_only_punpun = src
// 1 Pun Pun should exist
REGISTER_REQUIRED_MAP_ITEM(1, 1)
fully_replace_character_name(real_name, name_to_use)
@@ -924,6 +924,9 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
. = ..()
// Ensure 1 Poly exists
REGISTER_REQUIRED_MAP_ITEM(1, 1)
/mob/living/simple_animal/parrot/poly/Life(seconds_per_tick = SSMOBS_DT, times_fired)
if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved)
Write_Memory(FALSE)
+7 -7
View File
@@ -113,7 +113,7 @@
//HEAD OF STAFF DOCUMENTS
/obj/item/paperwork/cargo
stamp_requested = /obj/item/stamp/qm
stamp_requested = /obj/item/stamp/head/qm
stamp_job = /datum/job/quartermaster
stamp_icon = "paper_stamp-qm"
@@ -126,7 +126,7 @@
detailed_desc += span_info(" Despite how disorganized the documents are, they're all appropriately filled in. You should probably stamp this.")
/obj/item/paperwork/security
stamp_requested = /obj/item/stamp/hos
stamp_requested = /obj/item/stamp/head/hos
stamp_job = /datum/job/head_of_security
stamp_icon = "paper_stamp-hos"
@@ -139,7 +139,7 @@
detailed_desc += span_info(" What a bunch of crap, the security team were clearly just doing what they had to. You should probably stamp this.")
/obj/item/paperwork/service
stamp_requested = /obj/item/stamp/hop
stamp_requested = /obj/item/stamp/head/hop
stamp_job = /datum/job/head_of_personnel
stamp_icon = "paper_stamp-hop"
@@ -152,7 +152,7 @@
detailed_desc += span_info(" A MAXIMUM priority request like this is nothing to balk at. You should probably stamp this.")
/obj/item/paperwork/medical
stamp_requested = /obj/item/stamp/cmo
stamp_requested = /obj/item/stamp/head/cmo
stamp_job = /datum/job/chief_medical_officer
stamp_icon = "paper_stamp-cmo"
@@ -166,7 +166,7 @@
/obj/item/paperwork/engineering
stamp_requested = /obj/item/stamp/ce
stamp_requested = /obj/item/stamp/head/ce
stamp_job = /datum/job/chief_engineer
stamp_icon = "paper_stamp-ce"
@@ -179,7 +179,7 @@
detailed_desc += span_info(" Damn, that's impressive stuff. You should probably stamp this.")
/obj/item/paperwork/research
stamp_requested = /obj/item/stamp/rd
stamp_requested = /obj/item/stamp/head/rd
stamp_job = /datum/job/research_director
stamp_icon = "paper_stamp-rd"
@@ -192,7 +192,7 @@
detailed_desc += span_info(" Regardless, they're still perfectly usable test results. You should probably stamp this.")
/obj/item/paperwork/captain
stamp_requested = /obj/item/stamp/captain
stamp_requested = /obj/item/stamp/head/captain
stamp_job = /datum/job/captain
stamp_icon = "paper_stamp-cap"
+18 -11
View File
@@ -26,46 +26,53 @@
stamp_class = sheet.icon_class_name(icon_state)
)
/obj/item/stamp/qm
name = "quartermaster's rubber stamp"
icon_state = "stamp-qm"
dye_color = DYE_QM
/obj/item/stamp/law
name = "law office's rubber stamp"
icon_state = "stamp-law"
dye_color = DYE_LAW
/obj/item/stamp/captain
/obj/item/stamp/head
/obj/item/stamp/head/Initialize(mapload)
. = ..()
// All maps should have at least 1 of each head of staff stamp
REGISTER_REQUIRED_MAP_ITEM(1, INFINITY)
/obj/item/stamp/head/captain
name = "captain's rubber stamp"
icon_state = "stamp-cap"
dye_color = DYE_CAPTAIN
/obj/item/stamp/hop
/obj/item/stamp/head/hop
name = "head of personnel's rubber stamp"
icon_state = "stamp-hop"
dye_color = DYE_HOP
/obj/item/stamp/hos
/obj/item/stamp/head/hos
name = "head of security's rubber stamp"
icon_state = "stamp-hos"
dye_color = DYE_HOS
/obj/item/stamp/ce
/obj/item/stamp/head/ce
name = "chief engineer's rubber stamp"
icon_state = "stamp-ce"
dye_color = DYE_CE
/obj/item/stamp/rd
/obj/item/stamp/head/rd
name = "research director's rubber stamp"
icon_state = "stamp-rd"
dye_color = DYE_RD
/obj/item/stamp/cmo
/obj/item/stamp/head/cmo
name = "chief medical officer's rubber stamp"
icon_state = "stamp-cmo"
dye_color = DYE_CMO
/obj/item/stamp/head/qm
name = "quartermaster's rubber stamp"
icon_state = "stamp-qm"
dye_color = DYE_QM
/obj/item/stamp/denied
name = "\improper DENIED rubber stamp"
icon_state = "stamp-deny"
+1
View File
@@ -189,6 +189,7 @@
#include "reagent_names.dm"
#include "reagent_recipe_collisions.dm"
#include "reagent_transfer.dm"
#include "required_map_items.dm"
#include "resist.dm"
#include "say.dm"
#include "screenshot_antag_icons.dm"
@@ -0,0 +1,62 @@
/**
* Tests that all expected items are mapped in roundstart.
*
* How to add an item to this test:
* - Add the typepath(s) to setup_expected_types
* - In the type's initialize, REGISTER_REQUIRED_MAP_ITEM() a minimum and maximum
*/
/datum/unit_test/required_map_items
/// A list of all typepaths that we expect to be in the required items list
var/list/expected_types = list()
/// Used to fill the expected types list with all the types we look for on the map.
/// This list will just be full of typepaths that we expect.
/// More detailed information about each item (mainly, how much of each should exist) is set on a per item basis
/datum/unit_test/required_map_items/proc/setup_expected_types()
expected_types += subtypesof(/obj/item/stamp/head)
expected_types += subtypesof(/obj/machinery/computer/department_orders)
expected_types += /obj/machinery/computer/communications
expected_types += /mob/living/carbon/human/species/monkey/punpun
expected_types += /mob/living/basic/pet/dog/corgi/ian
expected_types += /mob/living/simple_animal/parrot/poly
/datum/unit_test/required_map_items/Run()
setup_expected_types()
var/list/required_map_items = GLOB.required_map_items.Copy()
for(var/got_type in expected_types)
var/datum/required_item/item = required_map_items[got_type]
var/items_found = item?.total_amount || 0
required_map_items -= got_type
if(items_found <= 0)
TEST_FAIL("Item [got_type] was not found, but is expected to be mapped in on mapload!")
continue
if(items_found < item.minimum_amount)
TEST_FAIL("Item [got_type] should have at least [item.minimum_amount] mapped in but only had [items_found] on mapload!")
continue
if(items_found > item.maximum_amount)
TEST_FAIL("Item [got_type] should have at most [item.maximum_amount] mapped in but had [items_found] on mapload!")
continue
// This primarily serves as a reminder to include the typepath in the expected types list above.
// However we can easily delete this line in the future if it runs into false positives.
TEST_ASSERT(length(required_map_items) == 0, "The following paths were found in required map items, but weren't checked: [english_list(required_map_items)]")
/// Datum for tracking required map items
/datum/required_item
/// Type (exact) being tracked
var/tracked_type
/// How many exist in the world
var/total_amount = 0
/// Min. amount of this type that should exist roundstart (inclusive)
var/minimum_amount = 1
/// Max. amount of this type that should exist roundstart (inclusive)
var/maximum_amount = 1
/datum/required_item/New(tracked_type, minimum_amount = 1, maximum_amount = 1)
src.tracked_type = tracked_type
src.minimum_amount = minimum_amount
src.maximum_amount = maximum_amount
total_amount += 1
+2
View File
@@ -15,6 +15,8 @@ GLOBAL_DATUM(current_test, /datum/unit_test)
GLOBAL_VAR_INIT(failed_any_test, FALSE)
/// When unit testing, all logs sent to log_mapping are stored here and retrieved in log_mapping unit test.
GLOBAL_LIST_EMPTY(unit_test_mapping_logs)
/// Global assoc list of required mapping items, [item typepath] to [required item datum].
GLOBAL_LIST_EMPTY(required_map_items)
/// A list of every test that is currently focused.
/// Use the PERFORM_ALL_TESTS macro instead.