Difference between revisions of "Using Chronicles"
(→Example 2) |
(→Example 3) |
||
Line 223: | Line 223: | ||
=== Example 3 === | === Example 3 === | ||
− | Read the value of the player variable 'JournalSeen' and then increment its value by 1: | + | Read the value of the player variable 'JournalSeen' and then increment its value by 1 [[#note3|<span style="color:#cc9900" id="note3back">[3]</span>]] [[#note4|<span style="color:#cc9900" id="note4back">[4]</span>]]: |
<pre> | <pre> | ||
jrnlLevel = xChronHandler.IReadAgeChron('Walker', 'JournalSeen') | jrnlLevel = xChronHandler.IReadAgeChron('Walker', 'JournalSeen') | ||
Line 233: | Line 233: | ||
</pre> | </pre> | ||
− | + | [[#note3back|<span style="color:#cc9900" id="note3">[3]</span>]] In order to do calculations with chronicles we have to convert the datatype to integer. Chronicles are always stored as strings. | |
− | + | ||
+ | [[#note4back|<span style="color:#cc9900" id="note4">[4]</span>]] 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). |
Revision as of 10:18, 27 April 2010
Contents
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.
Please DO NOT add xChronHandler.py or this pak file to your age distribution! You can provide links to the Offline KI and the download address above, so that users of your age may obtain the module 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 [1]:
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 [2]. If not, it creates the 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.
[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.
[2] 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.
Example 3
Read the value of the player variable 'JournalSeen' and then increment its value by 1 [3] [4]:
jrnlLevel = xChronHandler.IReadAgeChron('Walker', 'JournalSeen') if (not jrnlLevel): xChronHandler.IWriteAgeChron('Walker', 'JournalSeen', '1') return nextLevel = int(jrnlLevel) + 1 xChronHandler.IWriteAgeChron('Walker', 'JournalSeen', nextLevel)
[3] In order to do calculations with chronicles we have to convert the datatype to integer. Chronicles are always stored as strings.
[4] 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).