The standard library comes with an easy to use API to save, load and sync player data. Import the SaveLoadData
package to use the following code examples.
Internally the save load packages make use of the preload exploit, which is now officially endorsed by blizzard to save and load files from the CustomMapData
folder in your Warcraft III Document root. Data loaded from a specific player is therefore only loaded on that player’s machine and needs to be synchronized with all other players before it can be used in a synchronous environment. Wurst wraps all this behind a simple API, so you don’t need to worry about the details.
To save data for a specific player, simplye invoke saveData
with the data you want to save. Existing file contents will be overriden. Since saving does not require synchronizing, it is a simple, blocking operation.
function savePlayerData()
players[0].saveData("MyFileName", "someDataString")
This will save someDataString
into the file MyFileName.pld
in the CustomMapData folder.
To load data for a specific player, invoke the loadData
function with the same file name you used to save. Because loading does require synchronizing, loadData
expects a closure as second parameter which will be invoked once the loading and synchronizing has finished. Loading may also fail if the file is empty, corrupted, or the player disconnects during loading - so you should always check the status
parameter for validity first. If the status is SUCCESS
, the data
parameter will contain the synchronized version of the file’s contents, which you can immediately use in a synchronous context.
function loadPlayerData()
players[0].loadData("MyFileName") (status, data) ->
if status == LoadStatus.SUCCESS
Log.info("Loaded: " + data.getUnsafeString())
else
// some error handling
As you can see we used .getUnsafeString()
in the example above. It is unsafe because the maximum length of a string
in Jass is capped at 1024 bytes without and 4099 characters with concatenation. This would not only limit how much data we can load/save, but also complicate the usage in general. 1024 bytes also doesn’t equal to 1024 characters, because certain characters (unicode) take up more than 1 byte.
Thus it is generally recommended to use a ChunkedString
for any data above around 500 characters. The ChunkedString
splits big strings into smaller chunks, which can then be acceessed seperately. The save functions are overloaded to allow string or ChunkedString input.
Of course usually we don’t want to save just strings, but data that is stored in some object. For this purpose there is the Serializable
class for simple saving and loading of primitive attribute values. The serialize()
function returns a ChunkedString
, which then can be passed to the data save function from above.
Import Serializable
, make your data class extend Serializable
and implement the required functions.
import Serializable
class MyClass extends Serializable
var amount = 0
override function serializeProperties()
override function deserializeProperties()
Inside the serializeProperties
register all attributes that should be serialized using addProperty
.
override function serializeProperties()
addProperty("amount", amount)
Every serialized attribute should also be deserialized again in the deserializeProperties
function using the appropriate getXXProperty
function and assigned to the attribute. Make sure to use the same name used for saving.
override function deserializeProperties()
amount = getIntProperty("amount")
Serialize the data class object to a ChunkedString
using serialize
. You can pass this chunked string directly to the save functions from above.
let saveData = new MyClass()
saveData.amount = 1337
let result = saveData.serialize()
To load from a ChunkedString
, which could be obtained by loading using the function above, use deserialize
.
let loadedData = new MyClass()
loadedData.deserialize(inputChunkedString)
Find a full working example below. The first time you run the map a new save file will be created saving the value 1337
in the amount
attribute. The second time you run the map it will load the existing save file and assign 1337
to the loaded object’s amount
variable, which is printed on the screen.
package SerTest
import Serializable
import SaveLoadData
import ClosureTimers
constant FILE_NAME = "MyFileName"
class MyClass extends Serializable
var amount = 0
override function serializeProperties()
addProperty("amount", amount)
override function deserializeProperties()
amount = getIntProperty("amount")
init
doAfter(1.) ->
players[0].loadData(FILE_NAME) (status, data) ->
if status == LoadStatus.SUCCESS
let loadedData = new MyClass()..deserialize(data)
print("loaded: " + loadedData.amount.toString())
else
let saveData = new MyClass()
saveData.amount = 1337
let saveString = saveData.serialize()
players[0].saveData(FILE_NAME, saveString)
print("created new save file")