Save/Load System Guide¶
This guide explains how the save/load system works in Barebones RPG Framework, including automatic callback serialization and best practices for managing game state.
Overview¶
The framework provides a comprehensive save/load system that handles:
Automatic serialization of entities, items, quests, and parties
Callback serialization via symbolic names
Save file management with versioning support
Custom data through metadata dictionaries
Key Components¶
SaveManager¶
The SaveManager handles file I/O, directory management, and save versioning:
from barebones_rpg.core import Game, GameConfig
# Configure save directory
config = GameConfig(save_directory="my_saves")
game = Game(config)
# Save and load
game.save_to_file("save_001")
game.load_from_file("save_001")
# List all saves
saves = game.list_saves()
for save_info in saves:
print(f"{save_info['save_name']}: {save_info['timestamp']}")
CallbackRegistry¶
The CallbackRegistry enables serialization of callback functions by mapping them to
symbolic names. This allows functions like on_use callbacks to be saved and restored.
Important: Callbacks must be registered before saving. The framework provides automatic registration in specific cases (see below).
Automatic Callback Registration¶
The framework automatically registers callbacks in two scenarios:
1. Items Registered with LootManager¶
When you register an item with LootManager, its on_use callback is automatically
registered for serialization:
from barebones_rpg.items import create_consumable, LootManager
def heal_50(entity, context):
entity.heal(50)
return 50
# Create item with callback
potion = create_consumable("Health Potion", on_use=heal_50)
# Register with LootManager - callback is auto-registered!
LootManager().register("health_potion", potion)
# Now the callback can be serialized when saving
What gets auto-registered: Only the on_use callback from items registered with
LootManager().register().
2. Quests Added to QuestManager¶
When you add a quest to QuestManager, all its callbacks are automatically registered:
from barebones_rpg.quests import Quest, QuestObjective, QuestManager
def quest_started(quest):
print(f"Started: {quest.name}")
def quest_completed(quest):
print(f"Completed: {quest.name}")
quest = Quest(
name="My Quest",
on_start=quest_started,
on_complete=quest_completed
)
# Add to QuestManager - all callbacks auto-registered!
QuestManager().add_quest(quest)
What gets auto-registered:
- Quest callbacks: on_start, on_complete, on_fail
- Objective callbacks: condition, on_progress, on_complete
Manual Callback Registration¶
For items NOT registered with LootManager, you must manually register callbacks:
from barebones_rpg.items import create_consumable
from barebones_rpg.core.serialization import CallbackRegistry
def poison_effect(entity, context):
entity.take_damage(10)
return -10
# Create item directly (not via LootManager)
poison = create_consumable("Poison Vial", on_use=poison_effect)
# Manually register the callback for serialization
CallbackRegistry.register("poison_effect", poison_effect)
# Now add to inventory
hero.inventory.add_item(poison)
When to Use Manual Registration¶
You need manual registration when:
Creating items directly without
LootManagerUsing custom callbacks not covered by auto-registration
Building procedural/dynamic items at runtime
Best Practices¶
Recommended Workflow¶
Use LootManager for reusable items:
# Register templates with LootManager LootManager().register("health_potion", health_potion_template) LootManager().register("sword", sword_template) # Get instances when needed drop = LootManager().get("health_potion") hero.inventory.add_item(drop)
Use QuestManager for all quests:
quest = Quest(name="My Quest", on_complete=callback) QuestManager().add_quest(quest) # Auto-registers callbacks
Manual registration for edge cases:
# Only when you can't use LootManager or QuestManager CallbackRegistry.register("unique_callback", my_callback)
Common Patterns¶
Pattern 1: Reusable Items
# Define callbacks
def heal_effect(entity, context):
entity.heal(50)
# Create and register with LootManager
potion = create_consumable("Health Potion", on_use=heal_effect)
LootManager().register("health_potion", potion)
# Use throughout game
loot_table = [
{"item": "health_potion", "chance": 0.3}
]
drops = roll_loot_table(loot_table)
Pattern 2: Unique Items
# For one-off items, still use LootManager
def legendary_effect(entity, context):
entity.stats.atk += 10
legendary_sword = create_weapon(
"Excalibur",
base_damage=50,
on_use=legendary_effect,
unique=True
)
LootManager().register("excalibur", legendary_sword)
# Get the unique instance
sword = LootManager().get("excalibur")
hero.inventory.add_item(sword)
Pattern 3: Procedural Items
# When generating items dynamically
def random_potion_effect(entity, context):
heal_amount = random.randint(30, 70)
entity.heal(heal_amount)
return heal_amount
# Must manually register
CallbackRegistry.register("random_potion_effect", random_potion_effect)
# Create items at runtime
for i in range(10):
potion = create_consumable(
f"Potion #{i}",
on_use=random_potion_effect
)
hero.inventory.add_item(potion)
Complete Example¶
Here’s a full example showing proper save/load setup:
from barebones_rpg.core import Game, GameConfig
from barebones_rpg.items import create_consumable, create_weapon, LootManager
from barebones_rpg.quests import Quest, QuestManager
from barebones_rpg.entities import Character, Stats
# 1. Define callbacks
def heal_50(entity, context):
entity.heal(50)
return 50
def quest_complete(quest):
print(f"Completed: {quest.name}")
# 2. Register items with LootManager (auto-registers callbacks)
health_potion = create_consumable("Health Potion", on_use=heal_50, value=50)
LootManager().register("health_potion", health_potion)
iron_sword = create_weapon("Iron Sword", base_damage=10, value=100)
LootManager().register("iron_sword", iron_sword)
# 3. Create game
config = GameConfig(save_directory="saves")
game = Game(config)
# 4. Create and register entities
hero = Character(name="Hero", stats=Stats(hp=100, base_max_hp=50))
hero.init_inventory()
hero.inventory.add_item(LootManager().get("health_potion"))
hero.inventory.add_item(LootManager().get("iron_sword"))
game.register_entity(hero)
# 5. Create and add quests (auto-registers callbacks)
quest = Quest(name="First Quest", on_complete=quest_complete)
QuestManager().add_quest(quest)
# 6. Save and load
game.save_to_file("my_save")
print("Game saved!")
# Later...
game.load_from_file("my_save")
print("Game loaded!")
# Verify callback works
restored_hero = game.get_entity_by_name("Hero")
potion = restored_hero.inventory.items[0]
potion.on_use(restored_hero, {}) # Callback still works!
Troubleshooting¶
Callback Not Restored After Load¶
Problem: Item callbacks are None after loading.
Solution: Ensure callbacks are registered before saving:
# Option 1: Use LootManager (recommended)
LootManager().register("my_item", item)
# Option 2: Manual registration
CallbackRegistry.register("my_callback", my_callback)
Save File Format¶
Save files are JSON with this structure:
{
"version": "1.0",
"timestamp": "2024-01-01T12:00:00",
"entities": [...],
"items": [...],
"quests": [...],
"parties": [...]
}
Callbacks are stored as symbolic names:
{
"name": "Health Potion",
"on_use": "health_potion.on_use"
}
On load, the CallbackRegistry maps "health_potion.on_use" back to the actual function.
Advanced Topics¶
Custom System Serialization¶
To add save/load to custom systems:
class CustomSystem:
def save(self) -> dict:
"""Return serializable state."""
return {
"my_data": self.my_data,
"my_counter": self.counter
}
def load(self, data: dict) -> None:
"""Restore state from data."""
self.my_data = data["my_data"]
self.counter = data["my_counter"]
# Register with game
game.register_system("custom", custom_system)
# Automatically saved/loaded with game
Migration Between Versions¶
Handle save file version changes:
def migrate_save_v1_to_v2(data: dict) -> dict:
"""Migrate old save format to new."""
# Add new fields with defaults
for entity in data["entities"]:
if "action_points" not in entity:
entity["action_points"] = 10
return data
# Apply migration when loading
if save_data["version"] == "1.0":
save_data = migrate_save_v1_to_v2(save_data)
Summary¶
Key Takeaways:
✅ Use ``LootManager`` for items with callbacks - auto-registers
on_use✅ Use ``QuestManager.add_quest()`` for quests - auto-registers all callbacks
⚠️ Manual registration required for items created directly without
LootManager🎯 Best practice: Use the managers whenever possible to avoid manual registration
Quick Reference:
# ✅ Good: Auto-registration
LootManager().register("potion", potion)
QuestManager().add_quest(quest)
# ⚠️ Requires manual registration
item = create_consumable("Custom", on_use=callback)
CallbackRegistry.register("callback", callback)
hero.inventory.add_item(item)