Saving and Loading
I’ve tried to set up Fleshcult so that modders shouldn’t have to think about loading and saving, but it helps to have some background if things go wrong.
Broadly speaking there are two approaches to writing save/load code:
- Opt-in: Manually write every relevant variable into the save.
- Opt-out: Write every variable except ones that have been explicitly disallowed.
Fleshcult is using Python’s Pickle system, which is opt-out. I give Pickle a root object to save and it rushes off writing out everything connected to it. But obviously I don’t want everything in the save file: I don’t want levels and NPCs. I want to be able to patch those, rather than having old saves preserve old values.
There are things Pickle won’t touch, like class variables, but for the sake of clarity I’ve been using separate classes for saveable objects and non-saveable objects. If you make a new class and you’re not already inheriting from one of mine, you should pick one:
- Saveable objects inherit from UnpicklePolicyMixin. This automatically gives the object a version number so you have the opportunity in later versions to write ‘migrations’ that convert old versions of the object into new versions.
- Non-saveable objects inherit from ConstantMixin. This gives the object a unique internal name called a constant_id, and instead of writing the object into the save, it just writes the constant id. On load it looks the constant up in the constant registry, which will be the latest patched version. As a modder you’ll mostly be making Constants: new transformations and archetypes and such which only exist in the save as an ID. If you change the ID, it’ll break old saves.
- Neither. If the object is part of a Constant it doesn’t matter because Pickle will never look inside Constants for things to save.
You may be curious what the root object is that I’m handing to Pickle:
- It’s GameState in GameState.py. You’ll see this referred to variously as Globals.game_state or current_game_state(), they’re both the same. There’s some bookkeeping junk like this save’s random seed, but it’s not that interesting by itself.
- Contained inside GameState is Player, defined in Game.py. Player directly or indirectly contains most of the game logic variables a modder will be interested in.
- doms is the list of player characters. In practice I only ever had one, so the protagonist is
doms[0]
. It’s defined in Dom.py.
- doms is the list of player characters. In practice I only ever had one, so the protagonist is
- Where’s the current encounter that our dom is on? For dumb historical reasons, this lives in GameState rather than inside Player.
- Contained inside GameState is Player, defined in Game.py. Player directly or indirectly contains most of the game logic variables a modder will be interested in.