We do perform a save when we onExitWorld, but we also call it at certain points when we grant items. (we are using a playerVar as our persistence layer)
We have alot going on. But suffice it to say that we call "SetPlayervars" in the path both during the test, AND on exit.
The interesting part to me is why does it work fine reading/ writing in editor.. and reading seems fine in the published world. (if you go do something in editor.. then load that same player in the published world on device.. you have your data as expected) But saving just doesn't seem to happen.
Is there anywhere we can go to look for error logs or anything for the published world ?
Here is a bunch of our code for context:
export type PlayerVar = {
version: number;
name: string;
visits: number;
usingUI: boolean;
inventory: Array<{itemId: string, quantity: number}>;
equippedBowItemId: string; //DEPRECATED
equippedArtifactItemId: string;
equippedWingsItemId: string;
playerXP: number;
equippedPetItemId: string;
completedObjectiveIDs: string[];
}
export const allPlayerVarData = new Map<Player, PlayerVar>();
const playerVarName = `PlayerVarsObj`;
const varGroupName: string = "PlayerData_Live"; //group name for variable group
const varKey: string = `${varGroupName}:${playerVarName}`;
preStart() {
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER)
console.log(`PlayerDataManager: Prestart PlayerDataManager`);
this.connectCodeBlockEvent(this.entity, CodeBlockEvents.OnPlayerEnterWorld, this.playerEnterWorld.bind(this));
this.connectCodeBlockEvent(this.entity, CodeBlockEvents.OnPlayerExitWorld, this.playerExitWorld.bind(this));
}
start(){
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER)
console.log("PlayerDataManager: PlayerDataManager started");
}
playerEnterWorld(player: Player) {
let newPlayerVar = this.GetPlayerVars(player);
if (newPlayerVar == null){
console.error(`PlayerDataManager: newPlayerVar is null after calling GetPlayerVar`);
return;
}
newPlayerVar.visits++;
allPlayerVarData.set(player, newPlayerVar);
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER)
console.log(`PlayerDataManager: Player ${player.name} has entered the world. and visited ${newPlayerVar.visits} times.`);
}
playerExitWorld(player: Player){
const playerVar = allPlayerVarData.get(player);
if (playerVar){
this.world.persistentStorage.setPlayerVariable(player, varKey, playerVar);
}
else {
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER)
console.log(`PlayerDataManager: playerVar returned undefined on PlayerVarManager: playerExitWorld`);
}
allPlayerVarData.delete(player);
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER)
console.log(`PlayerDataManager: Player ${player.name} has exited the world.`);
}
// A method to get the player variable for external scripts
public GetPlayerVars(player: Player): PlayerVar | null{
let prevPlayerVar : PlayerVar | null = null;
const valueJson : string | null = this.world.persistentStorage.getPlayerVariable(player, varKey);
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER)
console.log(`PlayerDataManager: valueJSON for ${player.name} is: ${valueJson}`);
prevPlayerVar = valueJson as unknown as PlayerVar;
if (prevPlayerVar == null || prevPlayerVar === undefined || prevPlayerVar.version == null || prevPlayerVar.version === undefined){
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER)
console.log(`PlayerDataManager: prevPlayerVar is null or undefined, or its version is null or undefined after retrieving from storage and converting`);
}
const newPlayerVar = initializeOrMigratePlayerVars(player, prevPlayerVar);
return newPlayerVar;
}
//A method to set ALL the player vars via a "player var" object.
public SetPlayerVars(player: Player, playerVar: PlayerVar){
if (playerVar){
this.world.persistentStorage.setPlayerVariable(player, varKey, playerVar);
this.sendLocalBroadcastEvent(Events.onPlayerDataChanged, { player: player});
}
else {
console.log(`PlayerDataManager: playerVar returned undefined on PlayerVarManager: playerExitWorld`);
}
}
//This method will check an existing playerVar object, and migrate it to the latest version if needed
//If no previous object is found, it will initialize a new one
function initializeOrMigratePlayerVars(player: Player, prevPlayerVar: PlayerVar | null): PlayerVar {
//Init a new player variable
const newPlayerVar: PlayerVar = {
version: playerVariableVersion,
name: player.name.get(), //v1
visits: 1, //v1
usingUI: true, //v2
inventory: PlayerDataManager.convertInventoryItemEntries(STARTING_INVENTORY), //v3
equippedBowItemId: "" , //v4 //DEPRECATED
equippedWingsItemId: "" , //v4
equippedPetItemId: "" , //v4
equippedArtifactItemId: "" , //v5
playerXP: 0, //v6,
completedObjectiveIDs: [] //v7
}
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER){
console.log(`PlayerDataManager: initializeOrMigratePlayerVars PrevPlayerVar Object: ${JSON.stringify(prevPlayerVar)}`);
}
// Check if the player has a previous variable
// If so migrate the data
if (prevPlayerVar != null && prevPlayerVar !== undefined && prevPlayerVar.version != null){
if (prevPlayerVar.version < playerVariableVersion){
//PERFORM MIGRATION
// Carry over existing data from 1st version
newPlayerVar.visits = prevPlayerVar.visits;
newPlayerVar.name = prevPlayerVar.name;
// Migrate to version 2
if (prevPlayerVar.version === 1){
newPlayerVar.usingUI = false; // New field in version 2, default to false
}
else{
newPlayerVar.usingUI = prevPlayerVar.usingUI;
}
// Migrate to version 3
if (prevPlayerVar.version < 3){
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER){
console.log(`PlayerDataManager: Migrating player inventory to version 3 for player ${player.name}`);
}
newPlayerVar.inventory = PlayerDataManager.convertInventoryItemEntries(STARTING_INVENTORY);
}
else{
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER){
console.log(`PlayerDataManager: Carrying over existing inventory for player ${player.name}`);
}
newPlayerVar.inventory = prevPlayerVar.inventory;
}
// Migrate to version 4
if (prevPlayerVar.version < 4){
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER){
console.log(`PlayerDataManager: Initializing equipped items to empty for player ${player.name}`);
}
newPlayerVar.equippedArtifactItemId = "" ;
newPlayerVar.equippedWingsItemId = "" ;
newPlayerVar.equippedPetItemId = "" ;
}
else{
// No migration needed, use existing data
newPlayerVar.equippedArtifactItemId = prevPlayerVar.equippedArtifactItemId;
newPlayerVar.equippedWingsItemId = prevPlayerVar.equippedWingsItemId;
newPlayerVar.equippedPetItemId = prevPlayerVar.equippedPetItemId;
}
// Migrate to version 5
if (prevPlayerVar.version < 5){
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER){
console.log(`PlayerDataManager: Initializing equipped artifact item to empty for player ${player.name}`);
}
newPlayerVar.equippedArtifactItemId = "" ;
if (prevPlayerVar.equippedBowItemId != null && prevPlayerVar.equippedBowItemId !== undefined && prevPlayerVar.equippedBowItemId !== ""){
// Carry over the equipped bow item to the new artifact slot
newPlayerVar.equippedArtifactItemId = prevPlayerVar.equippedBowItemId;
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER){
console.log(`PlayerDataManager: Migrated equipped bow item ${prevPlayerVar.equippedBowItemId} to equipped artifact slot for player ${player.name}`);
}
}
}
else{
// No migration needed, use existing data
newPlayerVar.equippedArtifactItemId = prevPlayerVar.equippedArtifactItemId;
}
// Migrate to version 6
if (prevPlayerVar.version < 6){
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER){
console.log(`PlayerDataManager: playerXP for player ${player.name}`);
}
newPlayerVar.playerXP = 0;
}
else{
// No migration needed, use existing data
newPlayerVar.playerXP = prevPlayerVar.playerXP;
}
// Migrate to version 7
if (prevPlayerVar.version < 7){
if (DISPLAY_CONSOLE_PLAYER_DATA_MANAGER){
console.log(`PlayerDataManager: completedObjectiveIDs for player ${player.name}`);
}
newPlayerVar.completedObjectiveIDs = [];
}
else{
// No migration needed, use existing data
newPlayerVar.completedObjectiveIDs = prevPlayerVar.completedObjectiveIDs;
}
}
else{
//no migration needed at all.
return prevPlayerVar;
}
}
// the new player var has been setup and migrated if needed
//return it!
return newPlayerVar;
}