Using Chronicles

What Are Chronicles?

Sometimes a puzzle requires the storage of personal player progress which is not tied to an age state. Each avatar has its own location in the vault to store personal variables: the ChronicleFolder.


User Age Chronicles

In order to keep the ChronicleFolder organized this model uses ONE base chronicle for ALL fan made ages. This base chronicle is called 'UserAges'. Within the base chronicle age builders can add unique subchronicles for their ages.

Each user age subchronicle in turn contains 3rd level subchronicles where the actual player variables are stored. Note that this model uses sub-subchronicles which go one level deeper than Cyan's subchronicles:

  ChronicleFolder
    |
    |
    |__Cyan Chronicle-1
    |
    |__Cyan Chronicle-2
    |     |
    |     |__Cyan Subchronicle-1
    |     |
    |     |__Cyan Subchronicle-2
    |
    |__Cyan Chronicle-3
    |
    |
    |__UserAges
    |     |
          |
          |__Age-1
          |     |
          |     |__Variable-1
          |     |
          |     |__Variable-2
          |
          |
          |__Age-2
          |     |
                |__Variable-1
                |

    L1    L2    L3

This method was tested successfully in the following Uru versions: Uru CC (offline), Until Uru (online). It is expected to be fully compatible with Alcugs and MOULa, because the Python routines used for vault navigation are the same in all Uru versions.

Level 1 - Base Chronicle

A separate function takes care of creating the base chronicle 'UserAges'. The best time of creation for the base chronicle is when the player enters the starting age of the puzzle for the first time. The base chronicle may already exist if another user age has created it previously.

Level 2 - Age SubChronicle

In order to avoid confusion as to which user age uses which subchronicle, the L2 subchronicle should have the same name as the age (unless the puzzle spans multiple ages). The best time of creation for the age subchronicle is when the player enters the starting age of the puzzle for the first time.

Level 3 - Player Variables

This is where we store our variables. Creation and modification of these chronicles is determined by game events and by the player's progress.


Global Module xChronHandler

Navigating through 3 levels of vault nodes by Python code is rather elaborate. Therefore a global Python module is provided which contains all the required functions: xChronHandler.py

This module is included in the Offline KI. You can import it into your own Python files by adding this statement at the beginning:

import xChronHandler

For people who do not have the Offline KI installed a global addon pak containing xChronHandler can be downloaded from here.

Age writers, please DO NOT add xChronHandler.py or the GlobalAddon.pak file to your age distribution! You can provide links to Offline KI/Drizzle or the download address above, so that users of your age may obtain the module from there.

Setting Up Levels 1 & 2

Before you can store variables in the level 3 chronicles you must set up the levels 1 and 2 above. Unfortunately multiple chronicle levels cannot be created in a single step. They must be created one by one using for example a timer callback or an OnVaultEvent.

Timer Callback Method

Place this code in one of your age's Python files. Make sure this is a file which loads at link-in. An example Python file can be downloaded from here.

# Python code by D'Lanor
from Plasma import *
from PlasmaTypes import *
import xChronHandler
kTime = 5.0
# Change ExampleTimer to the name of your age
# both in the filename AND the classname below!
class ExampleTimer(ptResponder,):

    def __init__(self):
        ptResponder.__init__(self)
        self.version = 1



    def OnServerInitComplete(self):
        if (not xChronHandler.ISetBaseChron()):
            xChronHandler.ISetSubChron(PtGetAgeName())
            return 
        PtAtTimeCallback(self.key, kTime, 1)


    def OnTimer(self, id):
        if (id == 1):
            xChronHandler.ISetSubChron(PtGetAgeName())



# Add glue section here

This method has a disadvantage: If the level 1 base chronicle does not get saved within the time interval, the level 2 age subchronicle will not be created. So in high latency situations this method fails miserably. Therefore the OnVaultEvent method is the preferred method.

OnVaultEvent Method

Place this code in one of your age's Python files. Make sure this is a file which loads at link-in. An example Python file can be downloaded from here.

# Python code by D'Lanor
from Plasma import *
from PlasmaTypes import *
from PlasmaVaultConstants import *
import xChronHandler
baseChron = 'UserAges'
# Change ExampleVaultEvent to the name of your age
# both in the filename AND the classname below!
class ExampleVaultEvent(ptResponder,):

    def __init__(self):
        ptResponder.__init__(self)
        self.version = 1



    def OnServerInitComplete(self):
        if (not xChronHandler.ISetBaseChron()):
            xChronHandler.ISetSubChron(PtGetAgeName())



    def OnVaultEvent(self, event, tupdata):
        if ((event == PtVaultCallbackTypes.kVaultNodeSaved) and (tupdata[0].getType() == PtVaultNodeTypes.kChronicleNode)):
            chronName = tupdata[0].upcastToChronicleNode().chronicleGetName()
            if (chronName == baseChron):
                print ('%s: OnVaultEvent: Chronicle %s saved, continue...' % (self.__class__.__name__,
                 chronName))
                xChronHandler.ISetSubChron(PtGetAgeName())
            elif (chronName == PtGetAgeName()):
                print ('%s: OnVaultEvent: Subchronicle %s saved' % (self.__class__.__name__,
                 chronName))



# Add glue section here

This is the recommended method. It is better than the timer callback method, because it waits for a confirmation message from the vault to make sure that the level 1 base chronicle has been created before it attempts to add the level 2 age subchronicle.


xChronHandler In Detail

When you call xChronHandler it executes its vault routines in the background. For full documentation please refer to the text file included in this download.

If you just need a quick guide on how to use this module you can skip to the examples.


How To Use xChronHandler

Example 1

Write the age subchronicle 'Prad' within the base chronicle 'UserAges':

    xChronHandler.ISetSubChron('Prad')

Result:

  ChronicleFolder
    |
    |
    |__UserAges
         |
         |
         |__Prad

Note that the base chronicle 'UserAges' must already exists for this function to work. See Using Chronicles#Setting Up Levels 1 & 2 for setting it up.


Example 2

Write the player variable called 'JournalSeen' within the 'Walker' age subchronicle and give it value 1. Note that the 'UserAges' and 'Walker' chronicles must already exists for this function to work (see Using Chronicles#Setting Up Levels 1 & 2 for setting them up):

    xChronHandler.IWriteAgeChron('Walker', 'JournalSeen', '1')

Result:

  ChronicleFolder
    |
    |
    |__UserAges
         |
         |
         |__Walker
              |
              |__JournalSeen (value = 1)

The function 'IWriteAgeChron' handles all possible situations:

  • It verifies if the chronicle exists (this verification is a must because the vault will happily create multiple chronicles with the same name when asked, which could result in an ugly mess). If chronicle does not exists, function creates chronicle with the specified value.
  • If the chronicle exists but has a different value its value is changed.
  • If the chronicle exists and already has the correct value nothing is done.

Example 3

Read the value of the player variable 'JournalSeen' and then increment its value by 1

    jrnlLevel = xChronHandler.IReadAgeChron('Walker', 'JournalSeen')  # The function IReadAgeChron returns integer 0 when it does not find the chronicle. So we can use an "if not" statement to check. Then we can either abort (return), or we can do other stuff such as creating the chronicle (see example)
    if (not jrnlLevel):    
        xChronHandler.IWriteAgeChron('Walker', 'JournalSeen', '1')
        return
    nextLevel = int(jrnlLevel) + 1      # In order to do calculations with chronicles we have to convert the datatype to integer. Chronicles are always stored as strings
    xChronHandler.IWriteAgeChron('Walker', 'JournalSeen', nextLevel)