Skip to main content

Saving and Loading Snippets

This page provides reusable code snippets for saving and loading data in Don't Starve Together mods.

Basic Saving and Loading

Saving Component Data

-- Basic component with save/load functionality
local MyComponent = Class(function(self, inst)
self.inst = inst
self.value = 0
self.items = {}
self.enabled = true
end)

-- Save component data
function MyComponent:OnSave()
return {
value = self.value,
items = self.items,
enabled = self.enabled
}
end

-- Load component data
function MyComponent:OnLoad(data)
if data then
self.value = data.value or self.value
self.items = data.items or self.items
self.enabled = data.enabled ~= nil and data.enabled or self.enabled
end
end

-- Usage in a prefab
local function fn()
local inst = CreateEntity()
-- ... other entity setup ...

inst:AddComponent("mycomponent")

return inst
end

Entity Save/Load Handlers

-- Entity with save/load handlers
local function fn()
local inst = CreateEntity()
-- ... other entity setup ...

-- Custom data to save
inst.persistent_data = {
counter = 0,
last_used = nil,
settings = {
power_level = 1,
mode = "normal"
}
}

-- Save handler
inst.OnSave = function(inst)
local data = {
counter = inst.persistent_data.counter,
last_used = inst.persistent_data.last_used,
settings = inst.persistent_data.settings
}
return data
end

-- Load handler
inst.OnLoad = function(inst, data)
if data then
if data.counter then inst.persistent_data.counter = data.counter end
if data.last_used then inst.persistent_data.last_used = data.last_used end
if data.settings then inst.persistent_data.settings = data.settings end
end
end

return inst
end

Advanced Saving and Loading

Saving References to Other Entities

-- Saving references to other entities
local function fn()
local inst = CreateEntity()
-- ... other entity setup ...

-- References to other entities
inst.linked_entities = {}

-- Function to link to another entity
inst.LinkEntity = function(inst, other)
if other and other.GUID then
table.insert(inst.linked_entities, other)
end
end

-- Save handler with references
inst.OnSave = function(inst)
local references = {}
local data = {
linked_entity_guids = {}
}

-- Save GUIDs of linked entities
for i, entity in ipairs(inst.linked_entities) do
if entity:IsValid() then
table.insert(data.linked_entity_guids, entity.GUID)
table.insert(references, entity.GUID)
end
end

return data, references
end

-- Load handler with references
inst.OnLoad = function(inst, data)
if data and data.linked_entity_guids then
inst.linked_entity_guids = data.linked_entity_guids
end
end

-- Resolve references after world loads
inst.OnLoadPostPass = function(inst)
if inst.linked_entity_guids then
for i, guid in ipairs(inst.linked_entity_guids) do
local entity = Ents[guid]
if entity then
table.insert(inst.linked_entities, entity)
end
end
inst.linked_entity_guids = nil
end
end

return inst
end

Saving Binary Data

-- Saving binary or complex data with string encoding
local function fn()
local inst = CreateEntity()
-- ... other entity setup ...

-- Complex data structure
inst.map_data = {
{1, 0, 1, 0, 1},
{0, 1, 0, 1, 0},
{1, 0, 1, 0, 1}
}

-- Encode map data to string
local function EncodeMapData(map_data)
local result = {}
table.insert(result, #map_data) -- Height
table.insert(result, #map_data[1]) -- Width

for y, row in ipairs(map_data) do
for x, cell in ipairs(row) do
table.insert(result, cell)
end
end

return table.concat(result, ",")
end

-- Decode map data from string
local function DecodeMapData(encoded)
local values = {}
for value in string.gmatch(encoded, "[^,]+") do
table.insert(values, tonumber(value))
end

local height = values[1]
local width = values[2]
local map = {}

local index = 3
for y = 1, height do
map[y] = {}
for x = 1, width do
map[y][x] = values[index]
index = index + 1
end
end

return map
end

-- Save handler with encoding
inst.OnSave = function(inst)
return {
encoded_map = EncodeMapData(inst.map_data)
}
end

-- Load handler with decoding
inst.OnLoad = function(inst, data)
if data and data.encoded_map then
inst.map_data = DecodeMapData(data.encoded_map)
end
end

return inst
end

Saving Player-Specific Data

-- Saving player-specific data
local function fn()
local inst = CreateEntity()
-- ... other entity setup ...

-- Player data storage
inst.player_data = {}

-- Add data for a player
inst.SetPlayerData = function(inst, player, key, value)
local userid = player and player.userid
if userid then
inst.player_data[userid] = inst.player_data[userid] or {}
inst.player_data[userid][key] = value
end
end

-- Get data for a player
inst.GetPlayerData = function(inst, player, key)
local userid = player and player.userid
if userid and inst.player_data[userid] then
return inst.player_data[userid][key]
end
return nil
end

-- Save handler
inst.OnSave = function(inst)
return {
player_data = inst.player_data
}
end

-- Load handler
inst.OnLoad = function(inst, data)
if data and data.player_data then
inst.player_data = data.player_data
end
end

return inst
end

Mod Configuration Saving

Basic Mod Configuration

-- Basic mod configuration system
local ModConfig = {
config = {
difficulty = "normal",
spawn_rate = 0.5,
enable_feature = true
},

-- Default values
defaults = {
difficulty = "normal",
spawn_rate = 0.5,
enable_feature = true
}
}

-- Save configuration to mod settings
function ModConfig:Save()
local config_str = json.encode(self.config)
TheSim:SetPersistentString("my_mod_config", config_str, false)
end

-- Load configuration from mod settings
function ModConfig:Load()
TheSim:GetPersistentString("my_mod_config", function(success, config_str)
if success and config_str and #config_str > 0 then
local success, config = pcall(function() return json.decode(config_str) end)
if success and config then
-- Merge with defaults for any missing values
for k, v in pairs(self.defaults) do
if config[k] == nil then
config[k] = v
end
end
self.config = config
end
end
end)
end

-- Set a configuration value
function ModConfig:Set(key, value)
self.config[key] = value
self:Save()
end

-- Get a configuration value
function ModConfig:Get(key)
return self.config[key]
end

-- Reset to defaults
function ModConfig:Reset()
self.config = deepcopy(self.defaults)
self:Save()
end

-- Initialize on mod load
function ModConfig:Init()
self:Load()
end

-- Usage
-- ModConfig:Init()
-- local difficulty = ModConfig:Get("difficulty")
-- ModConfig:Set("spawn_rate", 0.75)

Per-World Configuration

-- Per-world configuration system
local WorldConfig = {
world_configs = {},
current_config = nil,

-- Default values
defaults = {
structures = {},
resources_harvested = 0,
events_triggered = {}
}
}

-- Get current world ID
function WorldConfig:GetWorldID()
if TheWorld and TheWorld.meta then
return TheWorld.meta.session_identifier
end
return nil
end

-- Save configuration for current world
function WorldConfig:Save()
local world_id = self:GetWorldID()
if world_id and self.current_config then
self.world_configs[world_id] = deepcopy(self.current_config)

local config_str = json.encode(self.world_configs)
TheSim:SetPersistentString("my_mod_world_configs", config_str, false)
end
end

-- Load configurations for all worlds
function WorldConfig:LoadAll()
TheSim:GetPersistentString("my_mod_world_configs", function(success, config_str)
if success and config_str and #config_str > 0 then
local success, configs = pcall(function() return json.decode(config_str) end)
if success and configs then
self.world_configs = configs
end
end
end)
end

-- Initialize configuration for current world
function WorldConfig:InitForCurrentWorld()
local world_id = self:GetWorldID()
if world_id then
if self.world_configs[world_id] then
self.current_config = deepcopy(self.world_configs[world_id])
else
self.current_config = deepcopy(self.defaults)
end
else
self.current_config = deepcopy(self.defaults)
end
end

-- Set a configuration value for current world
function WorldConfig:Set(key, value)
if self.current_config then
self.current_config[key] = value
self:Save()
end
end

-- Get a configuration value for current world
function WorldConfig:Get(key)
if self.current_config then
return self.current_config[key]
end
return nil
end

-- Initialize on mod load and world load
function WorldConfig:Init()
self:LoadAll()

-- Initialize when world is ready
if TheWorld then
self:InitForCurrentWorld()
else
-- Wait for world to be ready
self.world_ready_task = TheWorld:DoTaskInTime(0, function()
self:InitForCurrentWorld()
self.world_ready_task = nil
end)
end
end

-- Usage
-- WorldConfig:Init()
-- local resources = WorldConfig:Get("resources_harvested")
-- WorldConfig:Set("resources_harvested", resources + 1)

Saving Large Data Structures

Chunked Data Saving

-- Saving large data structures in chunks
local ChunkedStorage = {
chunk_size = 10000, -- Maximum size of each chunk
prefix = "my_mod_data_chunk_"
}

-- Save large data in chunks
function ChunkedStorage:Save(key, data)
-- Convert data to string
local data_str = json.encode(data)

-- Calculate number of chunks needed
local num_chunks = math.ceil(#data_str / self.chunk_size)

-- Save metadata
TheSim:SetPersistentString(self.prefix .. key .. "_meta", json.encode({
num_chunks = num_chunks,
total_size = #data_str
}), false)

-- Save each chunk
for i = 1, num_chunks do
local start_pos = (i - 1) * self.chunk_size + 1
local end_pos = math.min(i * self.chunk_size, #data_str)
local chunk = string.sub(data_str, start_pos, end_pos)

TheSim:SetPersistentString(self.prefix .. key .. "_" .. i, chunk, false)
end

return true
end

-- Load large data from chunks
function ChunkedStorage:Load(key, callback)
-- Load metadata first
TheSim:GetPersistentString(self.prefix .. key .. "_meta", function(success, meta_str)
if success and meta_str and #meta_str > 0 then
local success, meta = pcall(function() return json.decode(meta_str) end)

if success and meta and meta.num_chunks then
local chunks = {}
local chunks_loaded = 0

-- Function to check if all chunks are loaded
local function CheckAllChunksLoaded()
if chunks_loaded == meta.num_chunks then
-- Combine all chunks
local full_data_str = table.concat(chunks)

-- Decode the data
local success, data = pcall(function() return json.decode(full_data_str) end)

if success and data then
callback(true, data)
else
callback(false)
end
end
end

-- Load each chunk
for i = 1, meta.num_chunks do
TheSim:GetPersistentString(self.prefix .. key .. "_" .. i, function(success, chunk)
if success and chunk then
chunks[i] = chunk
chunks_loaded = chunks_loaded + 1
CheckAllChunksLoaded()
else
callback(false)
end
end)
end
else
callback(false)
end
else
callback(false)
end
end)
end

-- Delete saved data
function ChunkedStorage:Delete(key)
-- Load metadata to know how many chunks to delete
TheSim:GetPersistentString(self.prefix .. key .. "_meta", function(success, meta_str)
if success and meta_str and #meta_str > 0 then
local success, meta = pcall(function() return json.decode(meta_str) end)

if success and meta and meta.num_chunks then
-- Delete each chunk
for i = 1, meta.num_chunks do
TheSim:ErasePersistentString(self.prefix .. key .. "_" .. i)
end
end

-- Delete metadata
TheSim:ErasePersistentString(self.prefix .. key .. "_meta")
end
end)
end

-- Usage
-- local large_data = {/* ... large data structure ... */}
-- ChunkedStorage:Save("world_map", large_data)
--
-- ChunkedStorage:Load("world_map", function(success, data)
-- if success then
-- print("Loaded large data structure!")
-- else
-- print("Failed to load data")
-- end
-- end)

Versioned Data Saving

-- Versioned data saving system
local VersionedStorage = {
current_version = 1, -- Increment when data format changes
prefix = "my_mod_versioned_"
}

-- Upgrade functions for each version
VersionedStorage.upgraders = {
-- Upgrade from v1 to v2
[1] = function(data_v1)
-- Example: Convert old format to new format
local data_v2 = {
settings = data_v1.config or {},
player_stats = {},
version = 2
}

-- Move player stats from old location to new
if data_v1.player_data then
for player_id, stats in pairs(data_v1.player_data) do
data_v2.player_stats[player_id] = {
score = stats.score or 0,
level = stats.level or 1
}
end
end

return data_v2
end,

-- Add more upgraders as needed for future versions
}

-- Save data with version
function VersionedStorage:Save(key, data)
-- Ensure data has current version
data.version = self.current_version

-- Convert to string and save
local data_str = json.encode(data)
TheSim:SetPersistentString(self.prefix .. key, data_str, false)

return true
end

-- Load data and upgrade if needed
function VersionedStorage:Load(key, callback)
TheSim:GetPersistentString(self.prefix .. key, function(success, data_str)
if success and data_str and #data_str > 0 then
local success, data = pcall(function() return json.decode(data_str) end)

if success and data then
-- Check if data needs upgrading
local version = data.version or 1

-- Apply upgraders sequentially
while version < self.current_version do
if self.upgraders[version] then
data = self.upgraders[version](data)
version = data.version
else
-- Missing upgrader, can't proceed
callback(false)
return
end
end

callback(true, data)
else
callback(false)
end
else
callback(false)
end
end)
end

-- Usage
-- local data = {
-- config = {difficulty = "hard"},
-- player_data = {
-- ["KU_12345"] = {score = 100, level = 5}
-- },
-- version = 1
-- }
--
-- VersionedStorage:Save("game_state", data)
--
-- VersionedStorage:Load("game_state", function(success, loaded_data)
-- if success then
-- -- Data will be automatically upgraded to current version
-- print("Loaded data version: " .. loaded_data.version)
-- end
-- end)

These snippets provide a foundation for various saving and loading scenarios in Don't Starve Together mods. Adapt them to your specific needs and combine them for more complex data persistence requirements.