Modding:Хранилище данных SaveGame
Эта страница проверялась в последний раз для версии Vintage Story 1.15.
Перед запуском у вас должна быть настроена среда разработки. Если у вас его еще нет, вам следует прочитать руководство Настройка среды разработки. Кроме того, мы предполагаем, что у вас есть базовые знания языка 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:
- for VS v1.12.4: LookingForGroup_vs1.12.4_v1.0.0.zip
- for VS v1.6: LookingForGroup_vs1.6_v1.0.0.zip
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
Item • Entity • Entity Behaviors • Block • Block Behaviors • Block Classes • Block Entities • Block Entity Behaviors • Collectible Behaviors • World properties
Workflows & Infrastructure
Modding Efficiency Tips • Mod-engine compatibility • Mod Extensibility • VS Engine
Additional Resources
Community Resources • Modding API Updates • Programming Languages • List of server commands • List of client commands • Client startup parameters • Server startup parameters
Example Mods • API Docs • GitHub Repository