Modding:SaveGame Data Storage: Difference between revisions

From Vintage Story Wiki
no edit summary
No edit summary
Line 38: Line 38:
             serverApi = api;
             serverApi = api;


            api.Event.SaveGameLoaded += OnSaveGameLoading;
             api.Event.GameWorldSave += OnSaveGameSaving;
             api.Event.GameWorldSave += OnSaveGameSaving;
            api.Event.SaveGameLoaded += OnSaveGameLoading;


             api.RegisterCommand("lfg", "List or join the lfg list", "[list|join|leave]", OnLfg);
             api.RegisterCommand("lfg", "List or join the lfg list", "[list|join|leave]", OnLfg);
Line 53: Line 53:
Let's create these event delegates!
Let's create these event delegates!


== Storing and Retrieving the List ==
== Retrieving and Storing the List ==


Right after our <code>StartServerSide</code> override, let's create our <code>/lfg</code> command delegate:
When the game loads, <code>OnSaveGameLoading</code> gets called and attempts to get our list of players looking for group. The Core Server Api has a member called <code>WorldManager</code> which we use to access everything World related, including our <code>SaveGame</code>.


<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
         private void OnLfg(IServerPlayer player, int groupId, CmdArgs args)
         private void OnSaveGameLoading()
         {
         {
             if (args.Length < 1)
             byte[] data = serverApi.WorldManager.SaveGame.GetData("lfg");
            {
                player.SendMessage(groupId, "/lfg [list|join|leave]", EnumChatType.CommandError);
                return;
            }


            lfgList = data == null ? new List<string>() : SerializerUtil.Deserialize<List<string>>(data);
         }
         }
</syntaxhighlight>
</syntaxhighlight>


This has three parameters: the player issuing the command, the group in which this command was entered, and the arguments sent for the command.
We attempt to get our list by exposing the <code>GetData</code> method and passing it an identifier for our custom data, in this case our "lfg" list.


The first thing we do is check that there is at least an argument given, with <code>if (args.Length < 1)</code>. If there isn't, we show the player the list of possible arguments and we go no further.
'''Hint''': If nothing is found stored under the key we provide, the <code>GetData</code> method returns null. In our mod example, this will happen until at least one player enters the list!


Next, we'll attempt to get our list of players looking for group. The Core Server Api has a member called <code>WorldManager</code> which we use to access everything World related, including our <code>SaveGame</code>.
<code>byte[] data = serverApi.WorldManager.SaveGame.GetData("lfg");</code>
 
As you can see, we're retrieving an array of bytes, which is the data type we actually store on the <code>SaveGame</code>. Let's convert it to a <code>List</code> of strings:


<syntaxhighlight lang="c#">
<code>lfgList = data == null ? new List<string>() : SerializerUtil.Deserialize<List<string>>(data);</code>
        private void OnLfg(IServerPlayer player, int groupId, CmdArgs args)
        {
            if (args.Length < 1)...


            byte[] data = serverApi.WorldManager.SaveGame.GetData("lfg");
Here we use a ternary operator to assign our <code>lfgList</code> field a new <code>List<string></code> if there was nothing stored under the "lfg" key.


            List<string> players = data == null ? new List<string>() : SerializerUtil.Deserialize<List<string>>(data);
If <code>data</code> is not null, we expose the <code>Deserialize<T></code> method of the <code>SerializerUtil</code> class. This method will deserialize an array of bytes into an instance of the type argument we pass it. Now that we have a delegate that fetches our list of players "lfg", let's create the delegate that stores this list when the Game World is saved:


<syntaxhighlight lang="c#">
        private void OnSaveGameSaving()
        {
            serverApi.WorldManager.SaveGame.StoreData("lfg", SerializerUtil.Serialize(lfgList));
         }
         }
</syntaxhighlight>
</syntaxhighlight>


We attempt to get our list by exposing the <code>GetData</code> method and passing it an identifier for our custom data, in this case our "lfg" list.
Here we call the <code>StoreData</code> method to save our list under the "lfg" key for later retrieval! Because we can only store data in the form of <code>byte[]</code>, we use the <code>Serialize</code> method of the <code>SerializerUtil</code> class to turn <code>lfgList</code> back to an array of bytes, which we pass as the second argument.
 
'''Hint''': If nothing is found stored under the key we provide, the <code>GetData</code> method returns null. In our mod example, this will happen until at least one player enters the list!
 
<code>byte[] data = serverApi.WorldManager.SaveGame.GetData("lfg");</code>
 
As you can see, we're retrieving an array of bytes, which is a data type we actually store on the <code>SaveGame</code>. Let's convert it to a <code>List</code> of strings:


<code>List<string> players = data == null ? new List<string>() : SerializerUtil.Deserialize<List<string>>(data);</code>
We now have implemented a way to assign our list from storageto <code>lfgList</code> when the <code>SaveGame</code> is loaded, and a way to store this list once the game is saved. In between these two events, we want players to be added or removed from <code>lfgList</code> via our command. Let's create our command delegate!


Here we use a ternary operator to assign our <code>players</code> list a new <code>List<string></code> if there was nothing stored under the "lfg" key.
== Handling the Command ==


If <code>data</code> is not null, we expose the <code>Deserialize<T></code> method of the <code>SerializerUtil</code> class. This method will deserialize an array of bytes into an instance of the type argument we pass it.
Server chat commands have three parameters: the player issuing the command, the group in which this command was entered, and the arguments sent for the command.
 
Now that we have our list of players "lfg", let's handle the possible arguments of the command.


<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
         private void OnLfg(IServerPlayer player, int groupId, CmdArgs args)
         private void OnLfg(IServerPlayer player, int groupId, CmdArgs args)
         {
         {
            if (args.Length < 1)...
            byte[] data = serverApi.WorldManager.SaveGame.GetData("lfg");
            List<string> players = data == null ? new List<string>() : SerializerUtil.Deserialize<List<string>>(data);
             string cmd = args.PopWord();
             string cmd = args.PopWord();
             switch (cmd)
             switch (cmd)
             {
             {
Line 132: Line 117:
</syntaxhighlight>
</syntaxhighlight>


We use the <code>PopWord</code> of our <code>CmdArgs</code> parameter to collect the first argument passed (ignoring anything subsequent). We then start a switch statement for our valid arguments, and default to showing these to the player if none of them match.
We use the <code>PopWord</code> of our <code>CmdArgs</code> parameter to collect the first argument passed (ignoring anything subsequent). We then start a switch statement for our valid arguments, and default to showing these to the player if none of them match or <code>cmd</code> is null.


Let's handle each of these:
Let's handle each of these:
Line 138: Line 123:
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
                 case "join":
                 case "join":
                     if (players.Contains(player.PlayerUID))
                     if (lfgList.Contains(player.PlayerUID))
                     {
                     {
                         player.SendMessage(groupId, "You're already in this list!", EnumChatType.Notification);
                         player.SendMessage(groupId, "You're already in the list!", EnumChatType.CommandError);
                     }
                     }
                     else
                     else
                     {
                     {
                         players.Add(player.PlayerUID);
                         lfgList.Add(player.PlayerUID);
                        data = SerializerUtil.Serialize(players);
                         player.SendMessage(groupId, "Successfully joined.", EnumChatType.CommandSuccess);
 
                        serverApi.WorldManager.SaveGame.StoreData("lfg", data);
 
                         player.SendMessage(groupId, "Successfully joined!", EnumChatType.Notification);
                     }
                     }
                     break;
                     break;
</syntaxhighlight>
</syntaxhighlight>


If <code>/lfg join</code> was entered, we'll first check if the player is already on the "lfg" list, letting the player know if so. Alternatively, we add the player's unique identifier to the list, and use the <code>Serialize</code> method to turn the updated players <code>List<string></code> back to an array of byte.
If <code>/lfg join</code> was entered, we'll first check if the player is already on the "lfg" list, letting the player know if so. Alternatively, we add the player's unique identifier to the list and show a success message.  


After, we use the <code>StoreData</code> method to save our new serialized list under the "lfg" key for later retrieval! Upon completion, we let the player know that they are now in the list.
'''Hint:''' We do not want to store player names directly. This is because player names may change, therefore our list could become inaccurate.


Now let's handle <code>/lfg leave</code>:
Next we handle <code>/lfg leave</code>:


<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
                 case "leave":
                 case "leave":
                     if (!players.Remove(player.PlayerUID))
                     if (!lfgList.Remove(player.PlayerUID))
                     {
                     {
                         player.SendMessage(groupId, "You're not in the list!", EnumChatType.Notification);
                         player.SendMessage(groupId, "You're not in the list!", EnumChatType.CommandError);
                     }
                     }
                     else
                     else
                     {
                     {
                        data = SerializerUtil.Serialize(players);
                         player.SendMessage(groupId, "Successfully left.", EnumChatType.CommandSuccess);
 
                        serverApi.WorldManager.SaveGame.StoreData("lfg", data);
 
                         player.SendMessage(groupId, "Successfully left!", EnumChatType.Notification);
                     }
                     }
                     break;
                     break;
</syntaxhighlight>
</syntaxhighlight>


The <code>Remove</code> method returns false if nothing matching the argument passed to it was found to be removed, and true if it was. If the player was in the list, we serialize the updated list, and store the data to the <code>SaveGame</code>, letting the player know their request was successful.
The <code>Remove</code> method returns false if nothing matching the argument passed to it was found and removed, and true if it was. We let the player know if they were not on the list, or if they were and got successfully taken out of it.


Finally, we handle <code>/lfg list</code>:
Finally, we handle <code>/lfg list</code>:
Line 183: Line 160:
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
                 case "list":
                 case "list":
                     if (players.Count == 0)
                     if (lfgList.Count == 0)
                     {
                     {
                         player.SendMessage(groupId, "Noone is looking for group!", EnumChatType.Notification);
                         player.SendMessage(groupId, "Noone is looking for a group.", EnumChatType.CommandSuccess);
                        break;
                     }
                     }
 
                     else
                     string lfgList = "Players looking for group:";
                    players.ForEach((playerUid) =>
                     {
                     {
                         lfgList += "\n" + serverApi.World.PlayerByUid(playerUid).PlayerName;
                        string response = "Players looking for group:";
                    });
                         lfgList.ForEach((playerUid) =>
                        {
                            response += "\n" + serverApi.World.PlayerByUid(playerUid).PlayerName;
                        });


                    player.SendMessage(groupId, lfgList, EnumChatType.Notification);
                        player.SendMessage(groupId, response, EnumChatType.CommandSuccess);
                    }
                     break;
                     break;
</syntaxhighlight>
</syntaxhighlight>


In this case we simply let the player know if are no players in the "lfg" list, and if there are then we build a string with all the player names on the list!
In this case we simply let the player know if there is noone on the list, but if there are we build a string with all the player names on the list.


== Conclusion ==
== Conclusion ==
Line 216: Line 194:
     {
     {
         ICoreServerAPI serverApi;
         ICoreServerAPI serverApi;
        List<string> lfgList;
         public override void StartServerSide(ICoreServerAPI api)
         public override void StartServerSide(ICoreServerAPI api)
         {
         {
             serverApi = api;
             serverApi = api;


             api.RegisterCommand("lfg", "List or join the lfg list", "[list|join|leave]", OnLfg);
            api.Event.SaveGameLoaded += OnSaveGameLoading;
            api.Event.GameWorldSave += OnSaveGameSaving;
 
             api.RegisterCommand("lfg", "Access the lfg list.", "[list|join|leave]", OnLfg);
         }
         }


         private void OnLfg(IServerPlayer player, int groupId, CmdArgs args)
         private void OnSaveGameLoading()
         {
         {
            if (args.Length < 1)
            {
                player.SendMessage(groupId, "/lfg [list|join|leave]", EnumChatType.CommandError);
                return;
            }
             byte[] data = serverApi.WorldManager.SaveGame.GetData("lfg");
             byte[] data = serverApi.WorldManager.SaveGame.GetData("lfg");


             List<string> players = data == null ? new List<string>() : SerializerUtil.Deserialize<List<string>>(data);
             lfgList = data == null ? new List<string>() : SerializerUtil.Deserialize<List<string>>(data);
        }
        private void OnSaveGameSaving()
        {
            serverApi.WorldManager.SaveGame.StoreData("lfg", SerializerUtil.Serialize(lfgList));
        }


        private void OnLfg(IServerPlayer player, int groupId, CmdArgs args)
        {
             string cmd = args.PopWord();
             string cmd = args.PopWord();
             switch (cmd)
             switch (cmd)
             {
             {
                 case "join":
                 case "join":
                     if (players.Contains(player.PlayerUID))
                     if (lfgList.Contains(player.PlayerUID))
                     {
                     {
                         player.SendMessage(groupId, "You're already in this list!", EnumChatType.Notification);
                         player.SendMessage(groupId, "You're already in the list!", EnumChatType.CommandError);
                     }
                     }
                     else
                     else
                     {
                     {
                         players.Add(player.PlayerUID);
                         lfgList.Add(player.PlayerUID);
                        data = SerializerUtil.Serialize(players);
                         player.SendMessage(groupId, "Successfully joined.", EnumChatType.CommandSuccess);
 
                        serverApi.WorldManager.SaveGame.StoreData("lfg", data);
 
                         player.SendMessage(groupId, "Successfully joined!", EnumChatType.Notification);
                     }
                     }
                     break;
                     break;


                 case "leave":
                 case "leave":
                     if (!players.Remove(player.PlayerUID))
                     if (!lfgList.Remove(player.PlayerUID))
                     {
                     {
                         player.SendMessage(groupId, "You're not in the list!", EnumChatType.Notification);
                         player.SendMessage(groupId, "You're not in the list!", EnumChatType.CommandError);
                     }
                     }
                     else
                     else
                     {
                     {
                        data = SerializerUtil.Serialize(players);
                         player.SendMessage(groupId, "Successfully left.", EnumChatType.CommandSuccess);
 
                        serverApi.WorldManager.SaveGame.StoreData("lfg", data);
 
                         player.SendMessage(groupId, "Successfully left!", EnumChatType.Notification);
                     }
                     }
                     break;
                     break;


                 case "list":
                 case "list":
                     if (players.Count == 0)
                     if (lfgList.Count == 0)
                     {
                     {
                         player.SendMessage(groupId, "Noone is looking for group!", EnumChatType.Notification);
                         player.SendMessage(groupId, "Noone is looking for a group.", EnumChatType.CommandSuccess);
                        break;
                     }
                     }
 
                     else
                     string lfgList = "Players looking for group:";
                    players.ForEach((playerUid) =>
                     {
                     {
                         lfgList += "\n" + serverApi.World.PlayerByUid(playerUid).PlayerName;
                        string response = "Players looking for group:";
                    });
                         lfgList.ForEach((playerUid) =>
                        {
                            response += "\n" + serverApi.World.PlayerByUid(playerUid).PlayerName;
                        });


                    player.SendMessage(groupId, lfgList, EnumChatType.Notification);
                        player.SendMessage(groupId, response, EnumChatType.CommandSuccess);
                    }
                     break;
                     break;
 
                   
                case null:
                 default:
                 default:
                     player.SendMessage(groupId, "/lfg [list|join|leave]", EnumChatType.CommandError);
                     player.SendMessage(groupId, "/lfg [list|join|leave]", EnumChatType.CommandError);
                     break;
                     return;
             }
             }
         }
         }
     }
     }
}
}
</syntaxhighlight>
</syntaxhighlight>