Files
Bubberstation/code/datums/map_config.dm
san7890 cb20ec99f9 [MDB Ignore] Unit Tests for Invalid Space Turfs (Area Bullshit Edition) (#70967)
## About The Pull Request

So, there's some bullshit with the map loader(?) sometimes where it'll
let space turfs spawn in spots where we REALLY don't want space turfs.
Or, it could also just be a mapper screwing up. Anyways, we might miss
these, so let's set up a broad Unit Test that checks and verifies that
these round-ruining snagglers do _not_ exist.

In order to help me to do this, I standardized and fixed the
nomenclature such that `/area/ruin/space` is default for any map file in
`_maps/RandomRuins/SpaceRuins`, as well as it's subtypes. I also touched
up how we handle shuttle areas in these scenarios. This got a lot of
Unit Test noise filtered out, and is crucial for its functioning. It
should also be how we did it from the start anyways. I added in an
UpdatePaths for any compatible change, but it was completely
non-workable for some of the area type updates.

I also fixed any organic bugs that didn't require an areas type update.
Cool.

Placing space turfs on IceBox:

![image](https://user-images.githubusercontent.com/34697715/199177940-21c64964-1808-41b0-9a92-bf5b82eee2fa.png)

Organically found issues:

![image](https://user-images.githubusercontent.com/34697715/199177972-b27a89de-0e1a-41e5-8fa4-3bee1763b9da.png)

I also added a `planetary` variable to `/datum/map_config` because I
didn't like the hack I was using to see if we had a planetary map, and
I'd rather it just be an explicit variable in the map's JSON.

## Why It's Good For The Game

The less times we get Space Turfs showing up on IceBoxStation, the
better. It also standardizes areas a bit more, which I like (we were
using some incorrect ones in the wrong spots, so those were touched up
in this PR as well). Like, if it's a space ruin, we don't need to use
the lengthy `/area/ruin/unpowered/no_grav` when `/area/ruin/space` does
the same thing.
## Changelog
Nothing in here should concern a player (unless I broke something)

Expect a few commits as I spam unit tests a few times and play
whack-a-mole with bugs.
2022-11-21 07:59:54 +00:00

215 lines
6.6 KiB
Plaintext

//This file is used to contain unique properties of every map, and how we wish to alter them on a per-map basis.
//Use JSON files that match the datum layout and you should be set from there.
//Right now, we default to MetaStation to ensure something does indeed load by default.
// -san7890 (with regards to Cyberboss)
/datum/map_config
// Metadata
var/config_filename = "_maps/metastation.json"
var/defaulted = TRUE // set to FALSE by LoadConfig() succeeding
// Config from maps.txt
var/config_max_users = 0
var/config_min_users = 0
var/voteweight = 1
var/votable = FALSE
// Config actually from the JSON - should default to Meta
var/map_name = "Meta Station"
var/map_path = "map_files/MetaStation"
var/map_file = "MetaStation.dmm"
var/traits = null
var/space_ruin_levels = 7
var/space_empty_levels = 1
/// Boolean that tells us if this is a planetary station. (like IceBoxStation)
var/planetary = FALSE
///The type of mining Z-level that should be loaded.
var/minetype = "lavaland"
///If no minetype is set, this will be the blacklist file used
var/blacklist_file
var/allow_custom_shuttles = TRUE
var/shuttles = list(
"cargo" = "cargo_box",
"ferry" = "ferry_fancy",
"whiteship" = "whiteship_meta",
"emergency" = "emergency_meta")
/// Dictionary of job sub-typepath to template changes dictionary
var/job_changes = list()
/// List of additional areas that count as a part of the library
var/library_areas = list()
/**
* Proc that simply loads the default map config, which should always be functional.
*/
/proc/load_default_map_config()
return new /datum/map_config
/**
* Proc handling the loading of map configs. Will return the default map config using [/proc/load_default_map_config] if the loading of said file fails for any reason whatsoever, so we always have a working map for the server to run.
* Arguments:
* * filename - Name of the config file for the map we want to load. The .json file extension is added during the proc, so do not specify filenames with the extension.
* * directory - Name of the directory containing our .json - Must be in MAP_DIRECTORY_WHITELIST. We default this to MAP_DIRECTORY_MAPS as it will likely be the most common usecase. If no filename is set, we ignore this.
* * error_if_missing - Bool that says whether failing to load the config for the map will be logged in log_world or not as it's passed to LoadConfig().
*
* Returns the config for the map to load.
*/
/proc/load_map_config(filename = null, directory = null, error_if_missing = TRUE)
var/datum/map_config/config = load_default_map_config()
if(filename) // If none is specified, then go to look for next_map.json, for map rotation purposes.
//Default to MAP_DIRECTORY_MAPS if no directory is passed
if(directory)
if(!(directory in MAP_DIRECTORY_WHITELIST))
log_world("map directory not in whitelist: [directory] for map [filename]")
return config
else
directory = MAP_DIRECTORY_MAPS
filename = "[directory]/[filename].json"
else
filename = PATH_TO_NEXT_MAP_JSON
if (!config.LoadConfig(filename, error_if_missing))
qdel(config)
return load_default_map_config()
return config
#define CHECK_EXISTS(X) if(!istext(json[X])) { log_world("[##X] missing from json!"); return; }
/datum/map_config/proc/LoadConfig(filename, error_if_missing)
if(!fexists(filename))
if(error_if_missing)
log_world("map_config not found: [filename]")
return
var/json = file(filename)
if(!json)
log_world("Could not open map_config: [filename]")
return
json = file2text(json)
if(!json)
log_world("map_config is not text: [filename]")
return
json = json_decode(json)
if(!json)
log_world("map_config is not json: [filename]")
return
config_filename = filename
if(!json["version"])
log_world("map_config missing version!")
return
if(json["version"] != MAP_CURRENT_VERSION)
log_world("map_config has invalid version [json["version"]]!")
return
CHECK_EXISTS("map_name")
map_name = json["map_name"]
CHECK_EXISTS("map_path")
map_path = json["map_path"]
map_file = json["map_file"]
// "map_file": "MetaStation.dmm"
if (istext(map_file))
if (!fexists("_maps/[map_path]/[map_file]"))
log_world("Map file ([map_path]/[map_file]) does not exist!")
return
// "map_file": ["Lower.dmm", "Upper.dmm"]
else if (islist(map_file))
for (var/file in map_file)
if (!fexists("_maps/[map_path]/[file]"))
log_world("Map file ([map_path]/[file]) does not exist!")
return
else
log_world("map_file missing from json!")
return
if (islist(json["shuttles"]))
var/list/L = json["shuttles"]
for(var/key in L)
var/value = L[key]
shuttles[key] = value
else if ("shuttles" in json)
log_world("map_config shuttles is not a list!")
return
traits = json["traits"]
// "traits": [{"Linkage": "Cross"}, {"Space Ruins": true}]
if (islist(traits))
// "Station" is set by default, but it's assumed if you're setting
// traits you want to customize which level is cross-linked
for (var/level in traits)
if (!(ZTRAIT_STATION in level))
level[ZTRAIT_STATION] = TRUE
// "traits": null or absent -> default
else if (!isnull(traits))
log_world("map_config traits is not a list!")
return
var/temp = json["space_ruin_levels"]
if (isnum(temp))
space_ruin_levels = temp
else if (!isnull(temp))
log_world("map_config space_ruin_levels is not a number!")
return
temp = json["space_empty_levels"]
if (isnum(temp))
space_empty_levels = temp
else if (!isnull(temp))
log_world("map_config space_empty_levels is not a number!")
return
if ("minetype" in json)
minetype = json["minetype"]
if ("planetary" in json)
planetary = json["planetary"]
if ("blacklist_file" in json)
blacklist_file = json["blacklist_file"]
allow_custom_shuttles = json["allow_custom_shuttles"] != FALSE
if ("job_changes" in json)
if(!islist(json["job_changes"]))
log_world("map_config \"job_changes\" field is missing or invalid!")
return
job_changes = json["job_changes"]
if("library_areas" in json)
if(!islist(json["library_areas"]))
log_world("map_config \"library_areas\" field is missing or invalid!")
return
for(var/path_as_text in json["library_areas"])
var/path = text2path(path_as_text)
if(!ispath(path, /area))
stack_trace("Invalid path in mapping config for additional library areas: \[[path_as_text]\]")
continue
library_areas += path
defaulted = FALSE
return TRUE
#undef CHECK_EXISTS
/datum/map_config/proc/GetFullMapPaths()
if (istext(map_file))
return list("_maps/[map_path]/[map_file]")
. = list()
for (var/file in map_file)
. += "_maps/[map_path]/[file]"
/datum/map_config/proc/MakeNextMap()
return config_filename == PATH_TO_NEXT_MAP_JSON || fcopy(config_filename, PATH_TO_NEXT_MAP_JSON)