Difference between revisions of "PersistenceWML"
m (→Overview) |
(Link to issue 1280 about causing duplicate underlying ids) |
||
(8 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
− | + | ||
{{WML Tags}} | {{WML Tags}} | ||
− | |||
− | |||
== Purpose == | == Purpose == | ||
Line 14: | Line 12: | ||
Persistent Variables work like [[VariablesWML|normal Variables]], with a few differences. | Persistent Variables work like [[VariablesWML|normal Variables]], with a few differences. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
* Persistent Variables may be assigned, retrieved, or cleared, but not directly queried. | * Persistent Variables may be assigned, retrieved, or cleared, but not directly queried. | ||
* Persistent Variables may only be assigned from an existing variable, not directly from a constant | * Persistent Variables may only be assigned from an existing variable, not directly from a constant | ||
Line 46: | Line 38: | ||
to_global=my_variable_name | to_global=my_variable_name | ||
side=1 | side=1 | ||
+ | immediate=no | ||
[/set_global_variable] | [/set_global_variable] | ||
Assigns a persistent variable with the contents of a standard variable | Assigns a persistent variable with the contents of a standard variable | ||
Line 54: | Line 47: | ||
to_local=foo | to_local=foo | ||
side=1 | side=1 | ||
− | |||
[/get_global_variable] | [/get_global_variable] | ||
Retrieves the contents of a persistent variable and stores them in a standard variable. | Retrieves the contents of a persistent variable and stores them in a standard variable. | ||
Line 93: | Line 85: | ||
This attribute specifies which player client's persistence data should read from or written to in multiplayer.<br/> | This attribute specifies which player client's persistence data should read from or written to in multiplayer.<br/> | ||
This attribute is not used in single player, and may be omitted in single-player only scenarios.<br/> | This attribute is not used in single player, and may be omitted in single-player only scenarios.<br/> | ||
− | If this attribute is equal to "global", the currently active player's data will be read in [get_global_variable] operations, and all player's data will be written to in [set_global_variable] and [clear_global_variable] operations. | + | If this attribute is equal to "global", the currently active player's data will be read in [get_global_variable] operations, and all player's data will be written to in [set_global_variable] and [clear_global_variable] operations.<br/> |
+ | Calling [get_global_variable] with side not beeing "global" in networked mp might result making the currently active player wait while the variable is retrieved on another client and then sent to other players via network.<br/> | ||
+ | If the given side has controller=null, then the game will choose another side for you. | ||
==== immediate ==== | ==== immediate ==== | ||
Line 146: | Line 140: | ||
If campaigns are intended to have a low level of interaction, it is recommended that the later campaign(s) has its own separate namespace using the "Single Standalone Campaign" guideline format, and explicitly name the prior campaign(s)'s namespace(s) for the few times it needs to access them. | If campaigns are intended to have a low level of interaction, it is recommended that the later campaign(s) has its own separate namespace using the "Single Standalone Campaign" guideline format, and explicitly name the prior campaign(s)'s namespace(s) for the few times it needs to access them. | ||
Working from the above examples, upthorn's "bar" campaign would use "upthorn_bar" as its default namespace, and occasionally access the "foo" campaign's information in "upthorn_foo", by explicitly naming "upthorn_foo" as the namespace. | Working from the above examples, upthorn's "bar" campaign would use "upthorn_bar" as its default namespace, and occasionally access the "foo" campaign's information in "upthorn_foo", by explicitly naming "upthorn_foo" as the namespace. | ||
+ | |||
+ | == Copying Units Between Campaigns == | ||
+ | |||
+ | PersistenceWML can be used to copy units between campaigns, by using '''[store_unit]''' in the source and then '''[unstore_unit]''' in the destination. If you do this, ensure that it does not cause a duplicate underlying id, as explained in [https://github.com/wesnoth/wesnoth/issues/1280 Issue 1280]. |
Latest revision as of 21:11, 28 November 2022
Contents
Purpose
Persistent Variables are used to record information that can be accessed across savegames and session instances.
For instance, assume that completing a campaign on hard mode should unlock a special item in subsequent playthroughs of this campaign. With a persistent variable, that can be recorded, and made available to the campaign on later plays.
This can also be used to allow two or more related campaigns to communicate with each other about what has happened in each, allowing the player's choices in one campaign to influence the state of another.
Overview
Persistent Variables work like normal Variables, with a few differences.
- Persistent Variables may be assigned, retrieved, or cleared, but not directly queried.
- Persistent Variables may only be assigned from an existing variable, not directly from a constant
- In addition to a name, each Persistent Variable must have a namespace
- In multiplayer campaigns, each Persistent Variable also requires a side
- Persistent Variables will retain their values after wesnoth is closed, until they are cleared or the file containing their namespace is erased or lost.
Transactions
For the purposes of efficiency, and gameplay consistency, persistent variables are usually not written out to file immediately. Instead, they are grouped into transactions which are sets of changes that are either all written at once, or not written at all.
The policy regarding persistent variables will be written is as follows:
- Transactions will be committed (written to permanent file) whenever the game autosaves, whenever the player saves the game manually, or whenever a scenario is completed in either victory or defeat.
- Transactions will be cancelled (ignored and wiped from memory) whenever the player loads an earlier savegame, or quits without saving.
- A WML author can mark a change as an exception to a transaction, to be committed immediately by itself, by using setting the attribute "immediate" to "yes" in a [set_global_variable] or [clear_global_variable] tag. Such operations will be written immediately, regardless of the transaction status, without committing any waiting transactions.
NOTE: Because of the interaction between persistent variable transactions and user loading/saving of games, it is suggested that any variables a WML author wants to remain consistent for a single playthrough of a scenario should be read in immediately at the beginning of the scenario (side 1 turn 1), and set or cleared only in the victory and/or defeat events. |
WML Syntax
The following WML Tags are provided.
[set_global_variable] namespace=my_addon from_local=foo to_global=my_variable_name side=1 immediate=no [/set_global_variable]
Assigns a persistent variable with the contents of a standard variable
[get_global_variable] namespace=my_addon from_global=my_variable_name to_local=foo side=1 [/get_global_variable]
Retrieves the contents of a persistent variable and stores them in a standard variable. and
[clear_global_variable] namespace=my_addon global=my_variable_name side=1 immediate=no [/clear_global_variable]
Clears a persistent variable entirely.
Attribute information
namespace
This atribute specifies the name of the namespace that the persistent variable resides in.
- In this attribute, the character "." means "child of",
- A namespace equal to "foo.bar" will access a "bar" namespace inside "foo", creating it if it doesn't already exist, and store the peristent variable inside "bar".
- If this attribute begins with "." it will access a child namespace of the default namespace.
- A namespace equal to ".bar" with the default namespace of "foo" will access a "bar" namespace inside the "foo" namespace.
- In this attribute, the character "^" means "parent of", must follow a namespace and may only be directly followed by a "." or another "^". And is provided so that content creators can easily use macros to eliminate data repitition for the most frequently used namespaces.
- a namespace equal to "{def}^" with the def macro "foo" will access the "foo" namespace.
- a namespace equal to "{def}^" with the def macro "foo.bar" will access the "foo" namespace.
- a namespace equal to "{def}^" with the def macro "foo.bar.test" will access the "bar" namespace. inside the "foo" namespace.
- a namespace equal to "{def}^^" with the def macro "foo.bar.test" will access the "foo" namespace.
- a namespace equal to "{def}^.rod" with the def macro of "foo.bar" will access the "rod" namespace within the "foo" namespace.
- a namespace equal to "{def}^^.rod" with the def macro "foo.bar.test" will access the "rod" namespace within the "foo" namespace.
- a namespace equal to "{def}^.quiz" with the def macro "foo.bar.test" will access the "quiz" namespace within the "rod" namespace within the "foo" namespace.
- a namespace beginning with "^" is invalid and will generate a WML error, because it is impossible to determine the parent of nothing.
*global
These attributes (from_global, to_global, and global) specify the name of the persistent variable to be worked with.
The name of the persistent variable must be a valid variable name, as defined in VariablesWML
*local
These attributes (from_local, to_local) specify the name of the standard variable to be worked with.
side
This attribute specifies which player client's persistence data should read from or written to in multiplayer.
This attribute is not used in single player, and may be omitted in single-player only scenarios.
If this attribute is equal to "global", the currently active player's data will be read in [get_global_variable] operations, and all player's data will be written to in [set_global_variable] and [clear_global_variable] operations.
Calling [get_global_variable] with side not beeing "global" in networked mp might result making the currently active player wait while the variable is retrieved on another client and then sent to other players via network.
If the given side has controller=null, then the game will choose another side for you.
immediate
This attribute is an optional boolean value that specifies whether the changes made by the [set_global_variable] or [clear_global_variable] should be stored permanently at the moment they occur.
If it is set to yes, the namespace's permanent storage will be updated with the change that occurred in the marked tag immediately.
The default value is no.
This attribute does not apply to [get_global_variable], which always occurs immediately.
About Namespaces
Namespaces exist in order to prevent variable collisions from occuring when two or more unrelated campaigns use the same name for some of their persistent variables. A namespace is simply a name that identifies one set of variables. A namespace may contain only alphabetic characters, digits, and underscores.
Internal Separation
Just as variables can contain other variables, namespaces may contain other namespaces. The syntax for this works just like container variables: to refer to a namespace "bar" contained within a namespace "foo", use namespace="foo.bar"
Uniqueness
Because situations may arise where persistent variables from campaigns that are not installed concurrently have to coexist, a namespace should be unique per-context. This means that two unrelated campaigns with the same name should use different namespaces, but two or more related campaigns with different names may use the same namespace.
The following guidelines are provided in order to facilitate this:
Single Standalone Campaign
For a single campaign which is not intended to interact with other campaigns, the format "(author_handle)_(campaign_name)" is suggested for namespaces. As an example, if an author going by "upthorn" in the community were to create a campaign named "foo", the suggested namespace would be "upthorn_foo".
In cases where the author wishes to remain anonymous, or there are many contributing authors (more than say, two or three), the alternate format "(campaign_name)_(month)(year)" is suggested. Using the above example, assuming that upthorn began work on the campaign on the day this wiki page was created, the resulting namespace would be "foo_may2010"
Multiple Related Campaigns
For multiple related campaigns, there are a few different suggested namespace options depending on order of development and the intended level of interaction.
Developed or Conceived Concurrently
If a campaign is intended from the beginning to be part of a larger world of related campaigns, it is recommended that a namespace be created for the overall world, using the format "(author_handle)_(world_name)" for a named author, or "(world_name)_(month)(year)" for an unnamed author. As an example, if an author going by "upthorn" were to begin development on a "hypothetical" world the day this page was created, this would result in a namespace of either "upthorn_hypothetical" or "hypothetical_may2010"
If a campaign in this world is intended to have a high level of interaction, it is recommended that the campaign simply uses the world's namespace as default, and stores any information that it needs to be kept separate in a child namespace of the format "(campaign_name)". Building on the above example, if upthorn made a "foo" campaign in the "hypothetical" world, with high level of intended interactivity with the world, it would use "upthorn_hypothetical" as its default namespace, perhaps occasionally storing information in "upthorn_hypothetical.foo".
If a campaign in this world is intended to have a medium level of interaction, it is recommended that the campaign creates a child namespace of the format "(campaign_name)" in the world's overall namespace, and uses it as default, accessing the parent namespace when it needs to get information from the overall world. Continuing with the above examples, if upthorn made a "bar" campaign in the "hypothetical" world, with mid level of intended interactivity with the world, it would use "upthorn_hypothetical.bar" as its default namespace, and somewhat frequently access information in "upthorn_hypothetical", by using the namespace "^" or explicitly naming "upthorn_hypothetical".
If a campaign in this world is intended to have a low level of interaction, it is recommended that the campaign uses an entirely separate namespace, of the format "(world_namespace)_(campaign)" as its default, and accesses the overall world's namespace when it needs to communicate with the world. Continuing with the above examples, if upthorn made a "fnord" campaign in the "hypothetical" world, with a low level of intended interactivity with the world, it would use "upthorn_hypothetical_fnord" as its default namespace, and occasionally access information in "upthorn_hypothetical", by explicitly naming "upthorn_hypothetical".
Developed Sequentially
In cases where the first campaign is intended as a standalone, but some time after its release the same author, or other authors decide to make other campaigns in the same overall continuity, the first campaign's namespace cannot be changed.
If campaigns are intended to have a high level of interaction, it is recommended that the later campaign uses the namespace of the prior campaign, and makes a child namespace for itself 'if' it needs to save any of its variables separately. As an example, if an author going by "upthorn" developed a campaign "foo" and later developed a related campaign "bar", the "bar" campaign would use "upthorn_foo" as its default namespace, and perhaps occasionally store information in "upthorn_foo.bar"
If campaigns are intended to have a medium level of interaction, it is recommended that the later campaign makes its default namespace a child inside the namespace of the prior campaign that it is most heavily realted to, and accesses the parent namespace when it needs to work with variables from the prior campaign(s). Working from the above example, upthorn's "bar" campaign would use "upthorn_foo.bar" as its default namespace, and somewhat frequently access the "foo" campaign's information in "upthorn_foo", either by setting the namespace attribute to "upthorn_foo" explicitly, or by using "^" to get "upthorn_foo.bar"'s parent.
If campaigns are intended to have a low level of interaction, it is recommended that the later campaign(s) has its own separate namespace using the "Single Standalone Campaign" guideline format, and explicitly name the prior campaign(s)'s namespace(s) for the few times it needs to access them. Working from the above examples, upthorn's "bar" campaign would use "upthorn_bar" as its default namespace, and occasionally access the "foo" campaign's information in "upthorn_foo", by explicitly naming "upthorn_foo" as the namespace.
Copying Units Between Campaigns
PersistenceWML can be used to copy units between campaigns, by using [store_unit] in the source and then [unstore_unit] in the destination. If you do this, ensure that it does not cause a duplicate underlying id, as explained in Issue 1280.