Modding:Хранилище данных SaveGame

From Vintage Story Wiki
Revision as of 05:47, 21 September 2022 by Mirotworez (talk | contribs) (Created page with "Давайте начнем с создания нового файла .cs для этого мода и добавления нашего импорта и простран...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Эта страница проверялась в последний раз для версии Vintage Story 1.15.

Other languages:

Перед запуском у вас должна быть настроена среда разработки. Если у вас его еще нет, вам следует прочитать руководство Настройка среды разработки. Кроме того, мы предполагаем, что у вас есть базовые знания языка C# и объектно-ориентированного программирования. Давайте начнем!

Введение

VintageStory позволяет вам устанавливать и извлекать пользовательские данные для сохранения игры в мире, а также для отдельных фрагментов. На самом деле это работает очень просто!

Пользовательские данные хранятся в виде массива байтов для экономии места, но мы можем легко преобразовать любой допустимый тип C# в byte[] с помощью очень удобного класса в API VintageStory: vintagestory.at/api/Vintagestory.API.Util.SerializerUtil.html SerializerUtil. Этот класс предоставляет нам два статических метода, которые позволяют нам быстро Serialize в байтовые массивы и Deserialize<T> их в другие типы.

Вы можете ознакомиться с методами хранения и извлечения этих массивов байтов для фрагментов и для at/api/Vintagestory.API.Server.ISaveGame.html#methods SaveGame

Пользовательские данные в SaveGame

Давайте покажем вам этот мощный набор инструментов, создав пример мода, в котором мы реализуем открытый список игроков, которые в настоящее время ищут группу для игры в игре.

Этот список будет List<string> игроков, который будет сохранен в SaveGame мира. Любой может присоединиться к ней, выйти из нее или посмотреть, кому нужна группа. Мы сохраним этот список в виде массива байтов, доступ к которому можно получить по ключу «lfg».

Подготовка

Давайте начнем с создания нового файла .cs для этого мода и добавления нашего импорта и пространства имен VintageStory.ServerMods, чтобы обернуть наш класс. Кроме того, мы объявим класс LookingForGroup</ code>, который будет унаследован от ModSystem, который является базовым классом для систем модов. Подробнее об этом можно прочитать здесь.

using System.Collections.Generic;
using Vintagestory.API.Common;
using Vintagestory.API.Server;
using Vintagestory.API.Util;

namespace Vintagestory.ServerMods
{
    class LookingForGroup : ModSystem
    {
        ICoreServerAPI serverApi;
        List<string> lfgList;

        public override void StartServerSide(ICoreServerAPI api)
        {
            serverApi = api;

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

            api.RegisterCommand("lfg", "List or join the lfg list", "[list|join|leave]", OnLfg);
        }
    }
}

In this class, we create an override for the StartServerSide method. This method of the ModSystem is called for all mods on the Server side by the game.

The Core Server API contains an additional API for Event registration; this, in turn, contains two very important events (among others): SaveGameLoaded and GameWorldSave. These events are fired when the game is loaded, and when the game saves, respectively. We can assign delegates that will be called when the events fire by assigning these to the event with the += operator.

When the Server side starts, we add two event delegates that will retrieve our list from the SaveGame when the we game loads, and that will save the list when the game saves. We also register a command that players will use to access our list of those looking for group. This will be /lfg with the arguments list, join or leave.

Let us now define the event delegates!

Retrieving and Storing the List

When the game loads, OnSaveGameLoading gets called and attempts to get our list of players looking for group. The Core Server Api has a member called WorldManager which we use to access everything World related, including our SaveGame.

        private void OnSaveGameLoading()
        {
            byte[] data = serverApi.WorldManager.SaveGame.GetData("lfg");

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

We attempt to get our list by exposing the GetData method and passing it an identifier for our custom data, in this case our "lfg" list.

Hint: If nothing is found stored under the key we provide, the GetData method returns null. In our mod example, this will happen until at least one player enters the list!

byte[] data = serverApi.WorldManager.SaveGame.GetData("lfg");

As you can see, we're retrieving an array of bytes, which is the data type we actually store on the SaveGame. Let's convert it to a List of strings:

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

Here we use a ternary operator to assign our lfgList field a new List<string> if there was nothing stored under the "lfg" key.

If data is not null, we expose the Deserialize<T> method of the SerializerUtil 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:

        private void OnSaveGameSaving()
        {
            serverApi.WorldManager.SaveGame.StoreData("lfg", SerializerUtil.Serialize(lfgList));
        }

Here we call the StoreData method to save our list under the "lfg" key for later retrieval! Because we can only store data in the form of byte[], we use the Serialize method of the SerializerUtil class to turn lfgList back to an array of bytes, which we pass as the second argument.

We now have implemented a way to assign our list from storageto lfgList when the SaveGame 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 lfgList via our command. Let's create our command delegate!

Handling the Command

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.

        private void OnLfg(IServerPlayer player, int groupId, CmdArgs args)
        {
            string cmd = args.PopWord();
            switch (cmd)
            {
                case "join":
                    break;

                case "leave":
                    break;

                case "list":
                    break;

                default:
                    player.SendMessage(groupId, "/lfg [list|join|leave]", EnumChatType.CommandError);
                    break;
            }
        }

We use the PopWord of our CmdArgs 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 cmd is null.

Let's handle each of these:

                case "join":
                    if (lfgList.Contains(player.PlayerUID))
                    {
                        player.SendMessage(groupId, "You're already in the list!", EnumChatType.CommandError);
                    }
                    else
                    {
                        lfgList.Add(player.PlayerUID);
                        player.SendMessage(groupId, "Successfully joined.", EnumChatType.CommandSuccess);
                    }
                    break;

If /lfg join 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.

Hint: We do not want to store player names directly. This is because player names may change, therefore our list could become inaccurate.

Next we handle /lfg leave:

                case "leave":
                    if (!lfgList.Remove(player.PlayerUID))
                    {
                        player.SendMessage(groupId, "You're not in the list!", EnumChatType.CommandError);
                    }
                    else
                    {
                        player.SendMessage(groupId, "Successfully left.", EnumChatType.CommandSuccess);
                    }
                    break;

The Remove 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 /lfg list:

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

                        player.SendMessage(groupId, response, EnumChatType.CommandSuccess);
                    }
                    break;

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

If you followed the steps correctly, you should have the following code:

using System.Collections.Generic;
using Vintagestory.API.Common;
using Vintagestory.API.Server;
using Vintagestory.API.Util;

namespace Vintagestory.ServerMods
{
    class LookingForGroup : ModSystem
    {
        ICoreServerAPI serverApi;
        List<string> lfgList;
        public override void StartServerSide(ICoreServerAPI api)
        {
            serverApi = api;

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

            api.RegisterCommand("lfg", "Access the lfg list.", "[list|join|leave]", OnLfg);
        }

        private void OnSaveGameLoading()
        {
            byte[] data = serverApi.WorldManager.SaveGame.GetData("lfg");

            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();
            switch (cmd)
            {
                case "join":
                    if (lfgList.Contains(player.PlayerUID))
                    {
                        player.SendMessage(groupId, "You're already in the list!", EnumChatType.CommandError);
                    }
                    else
                    {
                        lfgList.Add(player.PlayerUID);
                        player.SendMessage(groupId, "Successfully joined.", EnumChatType.CommandSuccess);
                    }
                    break;

                case "leave":
                    if (!lfgList.Remove(player.PlayerUID))
                    {
                        player.SendMessage(groupId, "You're not in the list!", EnumChatType.CommandError);
                    }
                    else
                    {
                        player.SendMessage(groupId, "Successfully left.", EnumChatType.CommandSuccess);
                    }
                    break;

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

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

Testing

Let's test our mod. Once you're ingame, try entering /lfg join. Now quit the game and join back in. Upon entering /lfg list, you should see your name on the list; this means that your custom data has persisted in the SaveGame!

Distribution

To distribute this mod, you may run the following command in the modtools cli pack <your mod id>, then copy the .zip file into your VintageStory mods folder.

Here are the official versions:

Icon Sign.png

Wondering where some links have gone?
The modding navbox is going through some changes! Check out Navigation Box Updates for more info and help finding specific pages.

Modding
Modding Introduction Getting Started Пакет тем
Content Modding Content Mods Developing a Content Mod Basic Tutorials Intermediate Tutorials Advanced Tutorials Content Mod Concepts
Code Modding Code Mods Setting up your Development Environment
Property Overview ItemEntityBlockBlock BehaviorsBlock ClassesBlock EntitiesBlock Entity BehaviorsWorld properties
Workflows & Infrastructure Modding Efficiency TipsMod-engine compatibilityMod ExtensibilityVS Engine
Additional Resources Community Resources Modding API Updates Programming Languages List of server commandsList of client commandsClient startup parametersServer startup parameters
Example ModsAPI DocsGitHub Repository