Files
Bubberstation/code/controllers/subsystem/persistence.dm
san7890 ccef887efe Lints Against Unmanaged Local Defines (#74333)
# MAINTAINER - USE THE BUTTON THAT SAYS "MERGE MASTER" THEN SET THE PR
TO AUTO-MERGE! IT'S MUCH EASIER FOR ME TO FIX THINGS BEFORE THEY SKEW
RATHER THAN AFTER THE FACT.

## About The Pull Request

Hey there,

This took a while to do, but here's the gist:

Python file now regexes every file in `/code` except for those that have
some valid reason to be tacking on more global defines. Some of those
reasons are simply just that I don't have the time right now (doing what
you see in this PR took a few hours) to refactor and parse what should
belong and what should be thrown out. For the time being though, this PR
will at least _halt_ people making the mistake of not `#undef`ing any
files they `#define` "locally", or within the scope of a file.

Most people forget to do this and this leads to a lot of mess later on
due to how many variables can be unmanaged on the global level. I've
made this mistake, you've made this mistake, it's a common thing. Let's
automatically check for it so it can be fixed no-stress.

Scenarios this PR corrects:

* Forgetting to undef a define but undeffing others.
* Not undeffing any defines in your file.
* Earmarking a define as a "file local" define, but not defining it.
* Having a define be a "file local" define, but having it be used
elsewhere.
* Having a "local" define not even be in the file that it only shows up
in.
* Having a completely unused define*

(* I kept some of these because they seemed important... Others were
junked.)
## Why It's Good For The Game

If you wanna use it across multiple files, no reason to not make it a
global define (maybe there's a few reasons but let's assume that this is
the 95% case).

Let me know if you don't like how I re-arranged some of the defines and
how you'd rather see it be implemented, and I'd be happy to do that.
This was mostly just "eh does it need it or not" sorta stuff.

I used a pretty cool way to detect if we should use the standardized
GitHub "error" output, you can see the results of that here
https://github.com/san7890/bruhstation/actions/runs/4549766579/jobs/8022186846#step:7:792
## Changelog
Nothing that really concerns players.

(I fixed up all this stuff using vscode, no regexes beyond what you see
in the python script. sorry downstreams)
2023-03-29 10:17:03 -07:00

550 lines
18 KiB
Plaintext

#define FILE_RECENT_MAPS "data/RecentMaps.json"
#define KEEP_ROUNDS_MAP 3
SUBSYSTEM_DEF(persistence)
name = "Persistence"
init_order = INIT_ORDER_PERSISTENCE
flags = SS_NO_FIRE
///instantiated wall engraving components
var/list/wall_engravings = list()
///tattoo stories that we're saving.
var/list/prison_tattoos_to_save = list()
///tattoo stories that have been selected for this round.
var/list/prison_tattoos_to_use = list()
var/list/saved_messages = list()
var/list/saved_modes = list(1,2,3)
var/list/saved_maps = list()
var/list/blocked_maps = list()
var/list/saved_trophies = list()
var/list/picture_logging_information = list()
var/list/obj/structure/sign/picture_frame/photo_frames
var/list/obj/item/storage/photo_album/photo_albums
var/rounds_since_engine_exploded = 0
/datum/controller/subsystem/persistence/Initialize()
load_poly()
load_wall_engravings()
load_prisoner_tattoos()
load_trophies()
load_recent_maps()
load_photo_persistence()
load_randomized_recipes()
load_custom_outfits()
load_delamination_counter()
load_adventures()
return SS_INIT_SUCCESS
///Collects all data to persist.
/datum/controller/subsystem/persistence/proc/collect_data()
save_wall_engravings()
save_prisoner_tattoos()
collect_trophies()
collect_maps()
save_photo_persistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION.
save_randomized_recipes()
save_scars()
save_custom_outfits()
save_delamination_counter()
///Loads up Poly's speech buffer.
/datum/controller/subsystem/persistence/proc/load_poly()
for(var/mob/living/simple_animal/parrot/poly/P in GLOB.alive_mob_list)
twitterize(P.speech_buffer, "polytalk")
break //Who's been duping the bird?!
///Loads all engravings, and places a select amount in maintenance and the prison.
/datum/controller/subsystem/persistence/proc/load_wall_engravings()
var/json_file = file(ENGRAVING_SAVE_FILE)
if(!fexists(json_file))
return
var/list/json = json_decode(file2text(json_file))
if(!json)
return
if(json["version"] < ENGRAVING_PERSISTENCE_VERSION)
update_wall_engravings(json)
var/successfully_loaded_engravings = 0
var/list/viable_turfs = get_area_turfs(/area/station/maintenance, subtypes = TRUE) + get_area_turfs(/area/station/security/prison, subtypes = TRUE)
var/list/turfs_to_pick_from = list()
for(var/turf/T as anything in viable_turfs)
if(!isclosedturf(T))
continue
turfs_to_pick_from += T
var/list/engraving_entries = json["entries"]
if(engraving_entries.len)
for(var/iteration in 1 to rand(MIN_PERSISTENT_ENGRAVINGS, MAX_PERSISTENT_ENGRAVINGS))
var/engraving = engraving_entries[rand(1, engraving_entries.len)] //This means repeats will happen for now, but its something I can live with. Just make more engravings!
if(!islist(engraving))
stack_trace("something's wrong with the engraving data! one of the saved engravings wasn't a list!")
continue
var/turf/closed/engraved_wall = pick(turfs_to_pick_from)
if(HAS_TRAIT(engraved_wall, TRAIT_NOT_ENGRAVABLE))
continue
engraved_wall.AddComponent(/datum/component/engraved, engraving["story"], FALSE, engraving["story_value"])
successfully_loaded_engravings++
turfs_to_pick_from -= engraved_wall
log_world("Loaded [successfully_loaded_engravings] engraved messages on map [SSmapping.config.map_name]")
///Saves all new engravings in the world.
/datum/controller/subsystem/persistence/proc/save_wall_engravings()
var/list/saved_data = list()
saved_data["version"] = ENGRAVING_PERSISTENCE_VERSION
saved_data["entries"] = list()
var/json_file = file(ENGRAVING_SAVE_FILE)
if(fexists(json_file))
var/list/old_json = json_decode(file2text(json_file))
if(old_json)
saved_data["entries"] = old_json["entries"]
for(var/datum/component/engraved/engraving in wall_engravings)
if(!engraving.persistent_save)
continue
var/area/engraved_area = get_area(engraving.parent)
if(!(engraved_area.area_flags & PERSISTENT_ENGRAVINGS))
continue
saved_data["entries"] += engraving.save_persistent()
fdel(json_file)
WRITE_FILE(json_file, json_encode(saved_data))
///This proc can update entries if the format has changed at some point.
/datum/controller/subsystem/persistence/proc/update_wall_engravings(json)
for(var/engraving_entry in json["entries"])
continue //no versioning yet
//Save it to the file
var/json_file = file(ENGRAVING_SAVE_FILE)
fdel(json_file)
WRITE_FILE(json_file, json_encode(json))
return json
///Loads all tattoos, and select a few based on the amount of prisoner spawn positions.
/datum/controller/subsystem/persistence/proc/load_prisoner_tattoos()
var/json_file = file(PRISONER_TATTOO_SAVE_FILE)
if(!fexists(json_file))
return
var/list/json = json_decode(file2text(json_file))
if(!json)
return
if(json["version"] < TATTOO_PERSISTENCE_VERSION)
update_prisoner_tattoos(json)
var/datum/job/prisoner_datum = SSjob.name_occupations[JOB_PRISONER]
if(!prisoner_datum)
return
var/iterations_allowed = prisoner_datum.spawn_positions
var/list/entries = json["entries"]
if(entries.len)
for(var/index in 1 to iterations_allowed)
prison_tattoos_to_use += list(entries[rand(1, entries.len)])
log_world("Loaded [prison_tattoos_to_use.len] prison tattoos")
///Saves all tattoos, so they can appear on prisoners in future rounds
/datum/controller/subsystem/persistence/proc/save_prisoner_tattoos()
var/json_file = file(PRISONER_TATTOO_SAVE_FILE)
var/list/saved_data = list()
var/list/entries = list()
if(fexists(json_file))
var/list/old_json = json_decode(file2text(json_file))
if(old_json)
entries += old_json["entries"] //Save the old if its there
entries += prison_tattoos_to_save
saved_data["version"] = ENGRAVING_PERSISTENCE_VERSION
saved_data["entries"] = entries
fdel(json_file)
WRITE_FILE(json_file, json_encode(saved_data))
///This proc can update entries if the format has changed at some point.
/datum/controller/subsystem/persistence/proc/update_prisoner_tattoos(json)
for(var/tattoo_entry in json["entries"])
continue //no versioning yet
//Save it to the file
var/json_file = file(PRISONER_TATTOO_SAVE_FILE)
fdel(json_file)
WRITE_FILE(json_file, json_encode(json))
return json
/// Loads the trophies from the source file, and places a few in trophy display cases.
/datum/controller/subsystem/persistence/proc/load_trophies()
var/list/raw_saved_trophies = list()
if(fexists("data/npc_saves/TrophyItems.json"))
var/json_file = file("data/npc_saves/TrophyItems.json")
if(!fexists(json_file))
return
var/list/json = json_decode(file2text(json_file))
if(!json)
return
raw_saved_trophies = json["data"]
fdel("data/npc_saves/TrophyItems.json")
else
var/json_file = file("data/trophy_items.json")
if(!fexists(json_file))
return
var/list/json = json_decode(file2text(json_file))
if(!json)
return
raw_saved_trophies = json["data"]
for(var/raw_json in raw_saved_trophies)
var/datum/trophy_data/parsed_trophy_data = new
parsed_trophy_data.load_from_json(raw_json)
saved_trophies += parsed_trophy_data
set_up_trophies()
///trophy data datum, for admin manipulation
/datum/trophy_data
///path of the item the trophy will try to mimic, null if path_string is invalid
var/path
///the message that appears under the item
var/message
///the key of the one who placed the item in the trophy case
var/placer_key
/datum/trophy_data/proc/load_from_json(list/json_data)
path = json_data["path"]
message = json_data["message"]
placer_key = json_data["placer_key"]
/datum/trophy_data/proc/to_json()
var/list/new_data = list()
new_data["path"] = path
new_data["message"] = message
new_data["placer_key"] = placer_key
new_data["is_valid"] = text2path(path) ? TRUE : FALSE
return new_data
/// Returns a list for the admin trophy panel.
/datum/controller/subsystem/persistence/proc/trophy_ui_data()
var/list/ui_data = list()
for(var/datum/trophy_data/data in saved_trophies)
var/list/pdata = data.to_json()
pdata["ref"] = REF(data)
ui_data += list(pdata)
return ui_data
/// Loads up the amount of times maps appeared to alter their appearance in voting and rotation.
/datum/controller/subsystem/persistence/proc/load_recent_maps()
var/map_sav = FILE_RECENT_MAPS
if(!fexists(FILE_RECENT_MAPS))
return
var/list/json = json_decode(file2text(map_sav))
if(!json)
return
saved_maps = json["data"]
//Convert the mapping data to a shared blocking list, saves us doing this in several places later.
for(var/map in config.maplist)
var/datum/map_config/VM = config.maplist[map]
var/run = 0
if(VM.map_name == SSmapping.config.map_name)
run++
for(var/name in SSpersistence.saved_maps)
if(VM.map_name == name)
run++
if(run >= 2) //If run twice in the last KEEP_ROUNDS_MAP + 1 (including current) rounds, disable map for voting and rotation.
blocked_maps += VM.map_name
/// Puts trophies into trophy cases.
/datum/controller/subsystem/persistence/proc/set_up_trophies()
var/list/valid_trophies = list()
for(var/datum/trophy_data/data in saved_trophies)
if(!data) //sanity for incorrect deserialization
continue
var/path = text2path(data.path)
if(!path) //If the item no longer exist, ignore it
continue
valid_trophies += data
for(var/obj/structure/displaycase/trophy/trophy_case in GLOB.trophy_cases)
if(!valid_trophies.len)
break
if(trophy_case.showpiece)
continue
trophy_case.set_up_trophy(pick_n_take(valid_trophies))
///Loads up the photo album source file.
/datum/controller/subsystem/persistence/proc/get_photo_albums()
var/album_path = file("data/photo_albums.json")
if(fexists(album_path))
return json_decode(file2text(album_path))
///Loads up the photo frames source file.
/datum/controller/subsystem/persistence/proc/get_photo_frames()
var/frame_path = file("data/photo_frames.json")
if(fexists(frame_path))
return json_decode(file2text(frame_path))
/// Removes the identifier of a persistent photo frame from the json.
/datum/controller/subsystem/persistence/proc/remove_photo_frames(identifier)
var/frame_path = file("data/photo_frames.json")
if(!fexists(frame_path))
return
var/frame_json = json_decode(file2text(frame_path))
frame_json -= identifier
frame_json = json_encode(frame_json)
fdel(frame_path)
WRITE_FILE(frame_path, frame_json)
///Loads photo albums, and populates them; also loads and applies frames to picture frames.
/datum/controller/subsystem/persistence/proc/load_photo_persistence()
var/album_path = file("data/photo_albums.json")
var/frame_path = file("data/photo_frames.json")
if(fexists(album_path))
var/list/json = json_decode(file2text(album_path))
if(json.len)
for(var/i in photo_albums)
var/obj/item/storage/photo_album/A = i
if(!A.persistence_id)
continue
if(json[A.persistence_id])
A.populate_from_id_list(json[A.persistence_id])
if(fexists(frame_path))
var/list/json = json_decode(file2text(frame_path))
if(json.len)
for(var/i in photo_frames)
var/obj/structure/sign/picture_frame/PF = i
if(!PF.persistence_id)
continue
if(json[PF.persistence_id])
PF.load_from_id(json[PF.persistence_id])
///Saves the contents of photo albums and the picture frames.
/datum/controller/subsystem/persistence/proc/save_photo_persistence()
var/album_path = file("data/photo_albums.json")
var/frame_path = file("data/photo_frames.json")
var/list/frame_json = list()
var/list/album_json = list()
if(fexists(album_path))
album_json = json_decode(file2text(album_path))
fdel(album_path)
for(var/i in photo_albums)
var/obj/item/storage/photo_album/A = i
if(!istype(A) || !A.persistence_id)
continue
var/list/L = A.get_picture_id_list()
album_json[A.persistence_id] = L
album_json = json_encode(album_json)
WRITE_FILE(album_path, album_json)
if(fexists(frame_path))
frame_json = json_decode(file2text(frame_path))
fdel(frame_path)
for(var/i in photo_frames)
var/obj/structure/sign/picture_frame/F = i
if(!istype(F) || !F.persistence_id)
continue
frame_json[F.persistence_id] = F.get_photo_id()
frame_json = json_encode(frame_json)
WRITE_FILE(frame_path, frame_json)
///Collects trophies from all existing trophy cases.
/datum/controller/subsystem/persistence/proc/collect_trophies()
for(var/trophy_case in GLOB.trophy_cases)
save_trophy(trophy_case)
var/json_file = file("data/trophy_items.json")
var/list/file_data = list()
var/list/converted_data = list()
for(var/datum/trophy_data/data in saved_trophies)
converted_data += list(data.to_json())
converted_data = remove_duplicate_trophies(converted_data)
file_data["data"] = converted_data
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
///gets the list of json trophies, and deletes the ones with an identical path and message
/datum/controller/subsystem/persistence/proc/remove_duplicate_trophies(list/trophies)
var/list/ukeys = list()
. = list()
for(var/trophy in trophies)
var/tkey = "[trophy["path"]]-[trophy["message"]]"
if(ukeys[tkey])
continue
else
. += list(trophy)
ukeys[tkey] = TRUE
///If there is a trophy in the trophy case, saved it, if the trophy was not a holo trophy and has a message attached.
/datum/controller/subsystem/persistence/proc/save_trophy(obj/structure/displaycase/trophy/trophy_case)
if(!trophy_case.holographic_showpiece && trophy_case.showpiece && trophy_case.trophy_message)
var/datum/trophy_data/data = new
data.path = trophy_case.showpiece.type
data.message = trophy_case.trophy_message
data.placer_key = trophy_case.placer_key
saved_trophies += data
///Updates the list of the most recent maps.
/datum/controller/subsystem/persistence/proc/collect_maps()
if(length(saved_maps) > KEEP_ROUNDS_MAP) //Get rid of extras from old configs.
saved_maps.Cut(KEEP_ROUNDS_MAP+1)
var/mapstosave = min(length(saved_maps)+1, KEEP_ROUNDS_MAP)
if(length(saved_maps) < mapstosave) //Add extras if too short, one per round.
saved_maps += mapstosave
for(var/i = mapstosave; i > 1; i--)
saved_maps[i] = saved_maps[i-1]
saved_maps[1] = SSmapping.config.map_name
var/json_file = file(FILE_RECENT_MAPS)
var/list/file_data = list()
file_data["data"] = saved_maps
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
///Loads all randomized recipes.
/datum/controller/subsystem/persistence/proc/load_randomized_recipes()
var/json_file = file("data/RandomizedChemRecipes.json")
var/json
if(fexists(json_file))
json = json_decode(file2text(json_file))
for(var/randomized_type in subtypesof(/datum/chemical_reaction/randomized))
var/datum/chemical_reaction/randomized/R = new randomized_type
var/loaded = FALSE
if(R.persistent && json)
var/list/recipe_data = json["[R.type]"]
if(recipe_data)
if(R.LoadOldRecipe(recipe_data) && (daysSince(R.created) <= R.persistence_period))
loaded = TRUE
if(!loaded) //We do not have information for whatever reason, just generate new one
if(R.persistent)
log_game("Resetting persistent [randomized_type] random recipe.")
R.GenerateRecipe()
if(!R.HasConflicts()) //Might want to try again if conflicts happened in the future.
add_chemical_reaction(R)
else
log_game("Randomized recipe [randomized_type] resulted in conflicting recipes.")
///Saves all randomized recipes.
/datum/controller/subsystem/persistence/proc/save_randomized_recipes()
var/json_file = file("data/RandomizedChemRecipes.json")
var/list/file_data = list()
//asert globchems done
for(var/randomized_type in subtypesof(/datum/chemical_reaction/randomized))
var/datum/chemical_reaction/randomized/R = get_chemical_reaction(randomized_type) //ew, would be nice to add some simple tracking
if(R?.persistent)
var/list/recipe_data = R.SaveOldRecipe()
file_data["[R.type]"] = recipe_data
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
///Saves all scars for everyone's original characters
/datum/controller/subsystem/persistence/proc/save_scars()
for(var/i in GLOB.joined_player_list)
var/mob/living/carbon/human/ending_human = get_mob_by_ckey(i)
if(!istype(ending_human) || !ending_human.mind?.original_character_slot_index || !ending_human.client?.prefs.read_preference(/datum/preference/toggle/persistent_scars))
continue
var/mob/living/carbon/human/original_human = ending_human.mind.original_character.resolve()
if(!original_human)
continue
if(original_human.stat == DEAD || !original_human.all_scars || original_human != ending_human)
original_human.save_persistent_scars(TRUE)
else
original_human.save_persistent_scars()
///Loads the custom outfits of every admin.
/datum/controller/subsystem/persistence/proc/load_custom_outfits()
var/file = file("data/custom_outfits.json")
if(!fexists(file))
return
var/outfits_json = file2text(file)
var/list/outfits = json_decode(outfits_json)
if(!islist(outfits))
return
for(var/outfit_data in outfits)
if(!islist(outfit_data))
continue
var/outfittype = text2path(outfit_data["outfit_type"])
if(!ispath(outfittype, /datum/outfit))
continue
var/datum/outfit/outfit = new outfittype
if(!outfit.load_from(outfit_data))
continue
GLOB.custom_outfits += outfit
///Saves each admin's custom outfit list
/datum/controller/subsystem/persistence/proc/save_custom_outfits()
var/file = file("data/custom_outfits.json")
fdel(file)
var/list/data = list()
for(var/datum/outfit/outfit in GLOB.custom_outfits)
data += list(outfit.get_json_data())
WRITE_FILE(file, json_encode(data))
/// Location where we save the information about how many rounds it has been since the engine blew up
#define DELAMINATION_COUNT_FILEPATH "data/rounds_since_delamination.txt"
/datum/controller/subsystem/persistence/proc/load_delamination_counter()
if (!fexists(DELAMINATION_COUNT_FILEPATH))
return
rounds_since_engine_exploded = text2num(file2text(DELAMINATION_COUNT_FILEPATH))
for (var/obj/structure/sign/delamination_counter/sign as anything in GLOB.map_delamination_counters)
sign.update_count(rounds_since_engine_exploded)
/datum/controller/subsystem/persistence/proc/save_delamination_counter()
rustg_file_write("[rounds_since_engine_exploded + 1]", DELAMINATION_COUNT_FILEPATH)
#undef DELAMINATION_COUNT_FILEPATH
#undef FILE_RECENT_MAPS
#undef KEEP_ROUNDS_MAP