When creating more advanced mods you will find the need to persist data between play sessions. This is easily done anywhere there are save and write functions provided. Even when they are not there are means to ensure data is persisted.
Regardless of where the data is being saved and loaded you will always be working with a DataStream
.
A DataStream works by saving/loading data piece by piece. Such as DataStream.writeUint32()
is used to save a unsigned integer.
Note
Always be sure to save and load the exact same data in the same order!
Here is an example of saving and loading some data.
def write(self, stream):
stream.writeUint8(int(self.location))
stream.writeUint32(self.entityId)
stream.writeUint8(self.bagIndex)
stream.writeUint8(self.index)
stream.writeUint8(self.toolSlot)
stream.writeString(self.slot)
stream.writeString(self.reserve)
stream.writeString(self.customLocation)
def read(self, stream):
self.location = stream.readUint8()
self.entityId = stream.readUint32()
self.bagIndex = stream.readUint8()
self.index = stream.readUint8()
self.toolSlot = stream.readUint8()
self.slot = stream.readString()
self.reserve = stream.readString()
self.customLocation = stream.readString()
There are several ways to persist data. Components and plants provide read and write functions that are given a DataStream
.
Sometimes you will want to save data outside of these. The world_saved()
event can be listened to anytime you need to add a new subsystem or system to persist data.
Using the world_save will require you to create your own DataStream
as well as save it with FileManager.asyncWrite()
or File.save()
. This will save the data into a new file.
Use the world_created()
event to load this saved data. You should always check that the file exists with File.exists()
and load it with File.load()
.
Note
Always be sure to save a version with the data to support backwards compability!
def _getPath(self, world):
return os.path.join(world.path, "merchant")
def _saveData(self, world, asynchronous):
contentStash = game.content
entityManager = game.entity
stream = DataStream()
stream.writeUint16(MerchantSystem.FILE_VERSION)
self.wares.write(stream, contentStash, entityManager)
self.daily.write(stream, contentStash, entityManager)
if asynchronous:
game.file.asyncWrite(self._getPath(world), FileOnCompleteHandler.create(), stream, useCompression=False)
else:
File.save(self._getPath(world), stream)
def _loadData(self, world):
if File.exists(self._getPath(world)):
stream = DataStream()
File.load(self._getPath(world), stream)
version = stream.readUint16()
contentStash = game.content
entityManager = game.entity
self.wares.read(stream, contentStash, entityManager)
self.daily.read(stream, contentStash, entityManager)
@staticmethod
def register():
merchant = MerchantSystem()
game.registerSystem(MerchantSystem.NAME, merchant)
if NetworkManager.isHost():
# We only want to save the data on the host (server)
game.events['world_saved'].listen(merchant._saveData)
game.events['world_created'].listen(merchant._loadData)
@staticmethod
def unregister():
if NetworkManager.isHost():
game.events['world_saved'].remove(game.merchant._saveData)
game.events['world_created'].remove(game.merchant._loadData)
Note
This will work even without a system or subsystem.
Sometimes you will want data to be saved with characters but not in a component.
This is accomplished using World.addPlayerDataHandlers()
.
The provided reader and writer callbacks are called when a player is being saved or loaded.
Each reader/writer pair is associated with a key and if that key cannot be found when loading then it skips the saved data.
def testReader(player, stream):
value = stream.readString()
def testWriter(player, stream):
stream.writeString("something")
def register():
World.get().addPlayerDataHandlers("test", testReader, testWriter)