mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-11 02:01:22 +00:00
Changes job config format to use TOML, and add tools for migrating existing format (#70199)
This commit is contained in:
@@ -88,6 +88,8 @@
|
||||
LoadMOTD()
|
||||
LoadPolicy()
|
||||
LoadChatFilter()
|
||||
if(CONFIG_GET(flag/load_jobs_from_txt))
|
||||
validate_job_config()
|
||||
|
||||
loaded = TRUE
|
||||
|
||||
@@ -468,6 +470,31 @@ Example config:
|
||||
var/regex_filter = whitespace_split != "" ? "([whitespace_split]|[word_bounds])" : word_bounds
|
||||
return regex(regex_filter, "i")
|
||||
|
||||
/// Check to ensure that the jobconfig is valid/in-date.
|
||||
/datum/controller/configuration/proc/validate_job_config()
|
||||
var/config_toml = "[directory]/jobconfig.toml"
|
||||
var/config_txt = "[directory]/jobs.txt"
|
||||
var/message = "Notify Server Operators: "
|
||||
log_config("Validating config file jobconfig.toml...")
|
||||
|
||||
if(!fexists(file(config_toml)))
|
||||
SSjob.legacy_mode = TRUE
|
||||
message += "jobconfig.toml not found, falling back to legacy mode (using jobs.txt). To surpress this warning, generate a jobconfig.toml by running the verb 'Generate Job Configuration' in the Server tab.\n\
|
||||
From there, you can then add it to the /config folder of your server to have it take effect for future rounds."
|
||||
|
||||
if(!fexists(file(config_txt)))
|
||||
message += "\n\nFailed to set up legacy mode, jobs.txt not found! Codebase defaults will be used. If you do not wish to use this system, please disable it by commenting out the LOAD_JOBS_FROM_TXT config flag."
|
||||
|
||||
log_config(message)
|
||||
DelayedMessageAdmins(span_notice(message))
|
||||
return
|
||||
|
||||
var/list/result = rustg_raw_read_toml_file(config_toml)
|
||||
if(!result["success"])
|
||||
message += "The job config (jobconfig.toml) is not configured correctly! [result["content"]]"
|
||||
log_config(message)
|
||||
DelayedMessageAdmins(span_notice(message))
|
||||
|
||||
//Message admins when you can.
|
||||
/datum/controller/configuration/proc/DelayedMessageAdmins(text)
|
||||
addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, text), 0)
|
||||
|
||||
@@ -62,12 +62,26 @@ SUBSYSTEM_DEF(job)
|
||||
/// Dictionary that maps job priorities to low/medium/high. Keys have to be number-strings as assoc lists cannot be indexed by integers. Set in setup_job_lists.
|
||||
var/list/job_priorities_to_strings
|
||||
|
||||
/// Are we using the old job config system (txt) or the new job config system (TOML)? IF we are going to use the txt file, then we are in "legacy mode", and this will flip to TRUE.
|
||||
var/legacy_mode = FALSE
|
||||
|
||||
/// This is just the message we prepen and put into all of the config files to ensure documentation. We use this in more than one place, so let's put it in the SS to make life a bit easier.
|
||||
var/config_documentation = "## This is the configuration file for the job system.\n## This will only be enabled when the config flag LOAD_JOBS_FROM_TXT is enabled.\n\
|
||||
## We use a system of keys here that directly correlate to the job, just to ensure they don't desync if we choose to change the name of a job.\n## You are able to change (as of now) four different variables in this file.\n\
|
||||
## Total Positions are how many job slots you get in a shift, Spawn Positions are how many you get that load in at spawn. If you set this to -1, it is unrestricted.\n## Playtime Requirements is in minutes, and the job will unlock when a player reaches that amount of time.\n\
|
||||
## However, that can be superseded by Required Account Age, which is a time in days that you need to have had an account on the server for.\n## As time goes on, more config options may be added to this file.\n\
|
||||
## You can use the admin verb 'Generate Job Configuration' in-game to auto-regenerate this config as a downloadable file without having to manually edit this file if we add more jobs or more things you can edit here.\n\
|
||||
## It will always respect prior-existing values in the config, but will appropriately add more fields when they generate.\n## It's strongly advised you create your own version of this file rather than use the one provisioned on the codebase.\n\n\
|
||||
## The game will not read any line that is commented out with a '#', as to allow you to defer to codebase defaults.\n## If you want to override the codebase values, add the value and then uncomment that line by removing the # from the job key's name.\n\
|
||||
## Ensure that the key is flush, do not introduce any whitespaces when you uncomment a key. For example:\n## \"# Total Positions\" should always be changed to \"Total Positions\", no additional spacing. \n\
|
||||
## Best of luck editing!\n"
|
||||
|
||||
/datum/controller/subsystem/job/Initialize()
|
||||
setup_job_lists()
|
||||
if(!length(all_occupations))
|
||||
SetupOccupations()
|
||||
if(CONFIG_GET(flag/load_jobs_from_txt))
|
||||
LoadJobs()
|
||||
load_jobs_from_config()
|
||||
set_overflow_role(CONFIG_GET(string/overflow_job))
|
||||
return SS_INIT_SUCCESS
|
||||
|
||||
@@ -590,14 +604,203 @@ SUBSYSTEM_DEF(job)
|
||||
else //We ran out of spare locker spawns!
|
||||
break
|
||||
|
||||
#define TOTAL_POSITIONS "Total Positions"
|
||||
#define SPAWN_POSITIONS "Spawn Positions"
|
||||
#define PLAYTIME_REQUIREMENTS "Playtime Requirements"
|
||||
#define REQUIRED_ACCOUNT_AGE "Required Account Age"
|
||||
|
||||
/datum/controller/subsystem/job/proc/LoadJobs()
|
||||
var/jobstext = file2text("[global.config.directory]/jobs.txt")
|
||||
for(var/datum/job/job as anything in joinable_occupations)
|
||||
var/regex/jobs = new("[job.title]=(-1|\\d+),(-1|\\d+)")
|
||||
jobs.Find(jobstext)
|
||||
job.total_positions = text2num(jobs.group[1])
|
||||
job.spawn_positions = text2num(jobs.group[2])
|
||||
/// Called in jobs subsystem initialize if LOAD_JOBS_FROM_TXT config flag is set: reads jobconfig.toml (or if in legacy mode, jobs.txt) to set all of the datum's values to what the server operator wants.
|
||||
/datum/controller/subsystem/job/proc/load_jobs_from_config()
|
||||
var/toml_file = "[global.config.directory]/jobconfig.toml"
|
||||
|
||||
if(!legacy_mode) // this flag is set during the setup of SSconfig, and all warnings were handled there.
|
||||
var/job_config = rustg_read_toml_file(toml_file)
|
||||
|
||||
for(var/datum/job/occupation as anything in joinable_occupations)
|
||||
var/job_title = occupation.title
|
||||
var/job_key = occupation.config_tag
|
||||
if(!job_config[job_key]) // Job isn't listed, skip it.
|
||||
message_admins(span_notice("[job_title] (with config key [job_key]) is missing from jobconfig.toml! Using codebase defaults.")) // List both job_title and job_key in case they de-sync over time.
|
||||
continue
|
||||
|
||||
// If the value is commented out, we assume that the server operate did not want to override the codebase default values, so we skip it.
|
||||
var/default_positions = job_config[job_key][TOTAL_POSITIONS]
|
||||
var/starting_positions = job_config[job_key][SPAWN_POSITIONS]
|
||||
var/playtime_requirements = job_config[job_key][PLAYTIME_REQUIREMENTS]
|
||||
var/required_account_age = job_config[job_key][REQUIRED_ACCOUNT_AGE]
|
||||
|
||||
if(default_positions)
|
||||
occupation.total_positions = default_positions
|
||||
if(starting_positions)
|
||||
occupation.spawn_positions = starting_positions
|
||||
if(playtime_requirements)
|
||||
occupation.exp_requirements = playtime_requirements
|
||||
if(required_account_age)
|
||||
occupation.minimal_player_age = required_account_age
|
||||
|
||||
return
|
||||
|
||||
else // legacy mode, so just run the old parser.
|
||||
var/jobsfile = file("[global.config.directory]/jobs.txt")
|
||||
if(!fexists(jobsfile)) // sanity with a trace
|
||||
stack_trace("Despite SSconfig setting SSjob.legacy_mode to TRUE, jobs.txt was not found in the config directory! Something has gone terribly wrong!")
|
||||
return
|
||||
var/jobstext = file2text(jobsfile)
|
||||
for(var/datum/job/occupation as anything in joinable_occupations)
|
||||
var/regex/parser = new("[occupation.title]=(-1|\\d+),(-1|\\d+)")
|
||||
parser.Find(jobstext)
|
||||
occupation.total_positions = text2num(parser.group[1])
|
||||
occupation.spawn_positions = text2num(parser.group[2])
|
||||
|
||||
/// Called from an admin debug verb that generates the jobconfig.toml file and then allows the end user to download it to their machine. Returns TRUE if a file is successfully generated, FALSE otherwise.
|
||||
/datum/controller/subsystem/job/proc/generate_config(mob/user)
|
||||
var/toml_file = "[global.config.directory]/jobconfig.toml"
|
||||
var/jobstext = "[global.config.directory]/jobs.txt"
|
||||
var/list/file_data = list()
|
||||
config_documentation = initial(config_documentation) // Reset to default juuuuust in case.
|
||||
|
||||
if(fexists(file(toml_file)))
|
||||
to_chat(src, span_notice("Generating new jobconfig.toml, pulling from the old config settings."))
|
||||
if(!regenerate_job_config(user))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
if(fexists(file(jobstext))) // Generate the new TOML format, migrating from the text format.
|
||||
to_chat(user, span_notice("Found jobs.txt in config directory! Generating jobconfig.toml from it."))
|
||||
jobstext = file2text(file(jobstext)) // walter i'm dying (get the file from the string, then parse it into a larger text string)
|
||||
config_documentation += "\n\n## This TOML was migrated from jobs.txt. All variables are COMMENTED and will not load by default! Please verify to ensure that they are correct, and uncomment the key as you want, comparing it to the old config.\n\n" // small warning
|
||||
for(var/datum/job/occupation as anything in joinable_occupations)
|
||||
var/job_key = occupation.config_tag
|
||||
var/regex/parser = new("[occupation.title]=(-1|\\d+),(-1|\\d+)") // TXT system used the occupation's name, we convert it to the new config_key system here.
|
||||
parser.Find(jobstext)
|
||||
|
||||
var/default_positions = text2num(parser.group[1])
|
||||
var/starting_positions = text2num(parser.group[2])
|
||||
|
||||
// Playtime Requirements and Required Account Age are new and we want to see it migrated, so we will just pull codebase defaults for them.
|
||||
// Remember, every time we write the TOML from scratch, we want to have it commented out by default to ensure that the server operator is knows that they codebase defaults when they remove the comment.
|
||||
file_data["[job_key]"] = list(
|
||||
"# [PLAYTIME_REQUIREMENTS]" = occupation.exp_requirements,
|
||||
"# [REQUIRED_ACCOUNT_AGE]" = occupation.minimal_player_age,
|
||||
"# [TOTAL_POSITIONS]" = default_positions,
|
||||
"# [SPAWN_POSITIONS]" = starting_positions,
|
||||
)
|
||||
|
||||
if(!export_toml(user, file_data))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
else // Generate the new TOML format, using codebase defaults.
|
||||
to_chat(user, span_notice("Generating new jobconfig.toml, using codebase defaults."))
|
||||
for(var/datum/job/occupation as anything in joinable_occupations)
|
||||
var/job_key = occupation.config_tag
|
||||
// Remember, every time we write the TOML from scratch, we want to have it commented out by default to ensure that the server operator is knows that they override codebase defaults when they remove the comment.
|
||||
// Having comments mean that we allow server operators to defer to codebase standards when they deem acceptable. They must uncomment to override the codebase default.
|
||||
if(is_assistant_job(occupation)) // there's a concession made in jobs.txt that we should just rapidly account for here I KNOW I KNOW.
|
||||
file_data["[job_key]"] = list(
|
||||
"# [TOTAL_POSITIONS]" = -1,
|
||||
"# [SPAWN_POSITIONS]" = -1,
|
||||
"# [PLAYTIME_REQUIREMENTS]" = occupation.exp_requirements,
|
||||
"# [REQUIRED_ACCOUNT_AGE]" = occupation.minimal_player_age,
|
||||
)
|
||||
continue
|
||||
// Generate new config from codebase defaults.
|
||||
file_data["[job_key]"] = list(
|
||||
"# [TOTAL_POSITIONS]" = occupation.total_positions,
|
||||
"# [SPAWN_POSITIONS]" = occupation.spawn_positions,
|
||||
"# [PLAYTIME_REQUIREMENTS]" = occupation.exp_requirements,
|
||||
"# [REQUIRED_ACCOUNT_AGE]" = occupation.minimal_player_age,
|
||||
)
|
||||
if(!export_toml(user, file_data))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/// If we add a new job or more fields to config a job with, quickly spin up a brand new config that inherits all of your old settings, but adds the new job with codebase defaults. Returns TRUE if a file is successfully generated, FALSE otherwise.
|
||||
/datum/controller/subsystem/job/proc/regenerate_job_config(mob/user)
|
||||
var/toml_file = "[global.config.directory]/jobconfig.toml"
|
||||
var/list/file_data = list()
|
||||
|
||||
if(!fexists(file(toml_file))) // You need an existing (valid) TOML for this to work. Sanity check if someone calls this directly instead of through 'Generate Job Configuration' verb.
|
||||
to_chat(user, span_notice("No jobconfig.toml found in the config folder! If this is not expected, please notify a server operator or coders. You may need to generate a new config file by running 'Generate Job Configuration' from the Server tab."))
|
||||
return FALSE
|
||||
|
||||
var/job_config = rustg_read_toml_file(toml_file)
|
||||
for(var/datum/job/occupation as anything in joinable_occupations)
|
||||
var/job_name = occupation.title
|
||||
var/job_key = occupation.config_tag
|
||||
|
||||
var/default_positions = job_config[job_key][TOTAL_POSITIONS]
|
||||
var/starting_positions = job_config[job_key][SPAWN_POSITIONS]
|
||||
var/playtime_requirements = job_config[job_key][PLAYTIME_REQUIREMENTS]
|
||||
var/required_account_age = job_config[job_key][REQUIRED_ACCOUNT_AGE]
|
||||
|
||||
// When we regenerate, we want to make sure commented stuff stays commented, but we also want to migrate information that remains uncommented. So, let's make sure we keep that pattern.
|
||||
if(job_config["[job_key]"]) // Let's see if any data for this job exists.
|
||||
if(file_data["[job_key]"]) // Sanity, let's just make sure we don't overwrite anything or add any dupe keys. We also unit test for this, but eh, you never know sometimes.
|
||||
stack_trace("We were about to over-write a job key that already exists in file_data while generating a new jobconfig.toml! This should not happen! Verify you do not have any duplicate job keys in your codebase!")
|
||||
continue
|
||||
if(default_positions) // If the variable exists, we want to ensure it migrated into the new TOML uncommented, to allow for flush migration.
|
||||
file_data["[job_key]"] += list(
|
||||
TOTAL_POSITIONS = default_positions,
|
||||
)
|
||||
else // If we can't find anything for this variable, then we just throw in the codebase default with it commented out.
|
||||
file_data["[job_key]"] += list(
|
||||
"# [TOTAL_POSITIONS]" = occupation.total_positions,
|
||||
)
|
||||
|
||||
if(starting_positions) // Same pattern as above.
|
||||
file_data["[job_key]"] += list(
|
||||
SPAWN_POSITIONS = starting_positions,
|
||||
)
|
||||
else
|
||||
file_data["[job_key]"] += list(
|
||||
"# [SPAWN_POSITIONS]" = occupation.spawn_positions,
|
||||
)
|
||||
|
||||
if(playtime_requirements) // Same pattern as above.
|
||||
file_data["[job_key]"] += list(
|
||||
PLAYTIME_REQUIREMENTS = playtime_requirements,
|
||||
)
|
||||
else
|
||||
file_data["[job_key]"] += list(
|
||||
"# [PLAYTIME_REQUIREMENTS]" = occupation.exp_requirements,
|
||||
)
|
||||
|
||||
if(required_account_age) // Same pattern as above.
|
||||
file_data["[job_key]"] += list(
|
||||
REQUIRED_ACCOUNT_AGE = required_account_age,
|
||||
)
|
||||
else
|
||||
file_data["[job_key]"] += list(
|
||||
"# [REQUIRED_ACCOUNT_AGE]" = occupation.minimal_player_age,
|
||||
)
|
||||
continue
|
||||
else
|
||||
to_chat(user, span_notice("New job [job_name] (using key [job_key]) detected! Adding to jobconfig.toml using default codebase values..."))
|
||||
// Commented out keys here in case server operators wish to defer to codebase defaults.
|
||||
file_data["[job_key]"] = list(
|
||||
"# [TOTAL_POSITIONS]" = occupation.total_positions,
|
||||
"# [SPAWN_POSITIONS]" = occupation.spawn_positions,
|
||||
"# [PLAYTIME_REQUIREMENTS]" = occupation.exp_requirements,
|
||||
"# [REQUIRED_ACCOUNT_AGE]" = occupation.minimal_player_age,
|
||||
)
|
||||
|
||||
if(!export_toml(user, file_data))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/// Proc that we call to generate a new jobconfig.toml file and send it to the requesting client. Returns TRUE if a file is successfully generated.
|
||||
/datum/controller/subsystem/job/proc/export_toml(mob/user, data)
|
||||
var/file_location = "data/jobconfig.toml" // store it in the data folder server-side so we can FTP it to the client.
|
||||
var/payload = "[config_documentation]\n[rustg_toml_encode(data)]"
|
||||
rustg_file_write(payload, file_location)
|
||||
DIRECT_OUTPUT(user, ftp(file(file_location), "jobconfig.toml"))
|
||||
return TRUE
|
||||
|
||||
#undef TOTAL_POSITIONS
|
||||
#undef SPAWN_POSITIONS
|
||||
#undef PLAYTIME_REQUIREMENTS
|
||||
#undef REQUIRED_ACCOUNT_AGE
|
||||
|
||||
/datum/controller/subsystem/job/proc/HandleFeedbackGathering()
|
||||
for(var/datum/job/job as anything in joinable_occupations)
|
||||
|
||||
Reference in New Issue
Block a user