Skip to content

Persistence

The following script is a short demonstration of a common use case for storing persistence data: A hypothetical RPG game! (We've stripped out the actual RPG code to make things simpler to understand.)

For more information on the Storage API, please read our Storage API Reference.

This is stored on the user's computer, so it's not entirely secure!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
-- This is a world script, but you can use persistence on worlds, avatars, and props!

-- A table of the players in our game. In our case, we'll use a {user guid => data} structure.
PLAYERS = {}

-- Version of our game's public storage scheme, explanation below
LATEST_PUBLIC_VERSION = 5

-- Same for private storage
LATEST_PRIVATE_VERSION = 13

-- Game difficulty (you'd use constants here, like DIFFICULTY_EASY=1, _MEDIUM=2, _HARD=3; but let's be short and sweet for this example)
DIFFICULTY=2

-- Turn off Krampus mobs by defaults because they scared the American playtesters
KRAMPUS_ENABLED=false
function Start()
    print("HYPOTHETICAL-RPG V1.0")

    -- Print out some helpful debug info
    print("[Public Storage]")
    print("Path..........: " .. Storage.Public.Path)
    print("Bytes Allowed.: " .. tostring(Storage.Public.BytesAllowed))
    print("Bytes In Use..: " .. tostring(Storage.Public.CurrentSize))
    print("[Private Storage]")
    print("Path..........: " .. Storage.Private.Path)
    print("Bytes Allowed.: " .. tostring(Storage.Private.BytesAllowed))
    print("Bytes In Use..: " .. tostring(Storage.Private.CurrentSize))

    -- First, we'll load up our public configuration data.
    local public = Storage.Public
    --[[ WALL OF TEXT WARNING
    At this point, if there's an existing file, it's already Load()'d, so we don't need to call it here. It should already be loaded into memory if it's there on disk. If it's NOT present on disk, we're just basically working with a blank file with no data.  We can add this later.

    So, a good way to check for file presence while also tracking changes to the data structure we make over time (like adding new stats or renaming our 'spel' list to 'spell'), we try to access a field called version. 

    Version is a number because we can just increment it one to say we're on a new version. If the save is less than the latest version, we know it's old and can throw it out, or manually upgrade it.
    ]]--
    local publicVersion = public.GetNumber("version")

    -- If we're starting a new save, it's simply nil, because the file is blank.
    if publicVersion == nil then
      -- Cool, new save! Let's fill in some defaults for the user.
      -- Tell the game this is the latest structure of our save file, since we're making it from scratch.
      public.SetNumber("version", LATEST_PUBLIC_VERSION)
      -- Set our defaults
      public.SetBoolean("krampus_enabled", KRAMPUS_ENABLED)
      public.SetNumber("difficulty", DIFFICULTY)
      -- And any other stuff you'd like can go here.
      -- Now we need to Save() to actually save these changes to disk.
      public.Save()
    elseif publicVersion < LATEST_PUBLIC_VERSION then
      -- We could either reset here as above, or make an if-tree handling and upgrading every prior version of this save.  This is outside the scope of this example.
      print('ERROR: Public data is out of date! FIXME!')
    end

    -- So we have data.  Let's load it into our game configuration:
    DIFFICULTY = public.GetNumber("difficulty")
    KRAMPUS_ENABLED = public.GetBoolean("krampus")

    -- Let's load up our private storage now.
    -- This is where we're going to store more sensitive things, like player stats, save state, monster positions, quests, etc.
    local private = Storage.Private
    -- Check our version info
    local privateVersion = private.GetNumber("version")
    if privateVersion == nil then
        -- New save! Build defaults and save. Don't do class selection or anything here, this is just scaffolding for later.
        -- Ideally, this would be player GUID instead of name, and a full-featured "class" with functions, and then you'd just simplify it in SaveGame(), but we're getting ahead of ourselves.
        PLAYER["Example"] = {
            level=0,
            hp=0,
            mana=0,
            inventory=["sword", "rat flail"],
            gold=10
        }
        SaveGame()
    elseif privateVersion < LATEST_PRIVATE_VERSION then
        -- Upgrade saves here
      print('ERROR: Private data is out of date! FIXME!')
    end

    -- And load it all into memory.
    PLAYERS = private.GetTable("players")

    -- Other game startup stuff
end

-- ... Other game stuff ...

-- A function to just dump the game state into our save.
function SaveGame()
    private.SetNumber("version", LATEST_PRIVATE_VERSION)
    -- Remember that if you feed SetTable a table with functions/coroutines/etc, it will silently eat them and you won't get them back when it loads the table later. Allowing Lua to store arbitrary code to a user's computer would be... bad, so we had to make some compromises.
    private.SetTable("players", PLAYERS)
end