Core/Scripts: Save instance data in JSON format
Port From (https://github.com/TrinityCore/TrinityCore/commit/76be303351ae398b7f9e69e4c472cb5b05fce45e)
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.DataStorage;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
class InstanceScriptDataReader
|
||||
{
|
||||
public enum Result
|
||||
{
|
||||
Ok,
|
||||
MalformedJson,
|
||||
RootIsNotAnObject,
|
||||
MissingHeader,
|
||||
UnexpectedHeader,
|
||||
MissingBossStates,
|
||||
BossStatesIsNotAnObject,
|
||||
UnknownBoss,
|
||||
BossStateIsNotAnObject,
|
||||
MissingBossState,
|
||||
BossStateValueIsNotANumber,
|
||||
AdditionalDataIsNotAnObject,
|
||||
AdditionalDataUnexpectedValueType
|
||||
}
|
||||
|
||||
InstanceScript _instance;
|
||||
JsonDocument _doc;
|
||||
|
||||
public InstanceScriptDataReader(InstanceScript instance)
|
||||
{
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
public Result Load(string data)
|
||||
{
|
||||
/*
|
||||
Expected JSON
|
||||
|
||||
{
|
||||
"Header": "HEADER_STRING_SET_BY_SCRIPT",
|
||||
"BossStates": [0,2,0,...] // indexes are boss ids, values are EncounterState
|
||||
"AdditionalData: { // optional
|
||||
"ExtraKey1": 123
|
||||
"AnotherExtraKey": 2.0
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
try
|
||||
{
|
||||
_doc = JsonDocument.Parse(data);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, $"JSON parser error {ex.Message} at {ex.LineNumber} while loading data for instance {GetInstanceId()} [{GetMapId()}-{GetMapName()} | {GetDifficultyId()}-{GetDifficultyName()}]");
|
||||
return Result.MalformedJson;
|
||||
}
|
||||
|
||||
if (_doc.RootElement.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, $"Root JSON value is not an object for instance {GetInstanceId()} [{GetMapId()}-{GetMapName()} | {GetDifficultyId()}-{GetDifficultyName()}]");
|
||||
return Result.RootIsNotAnObject;
|
||||
}
|
||||
|
||||
Result result = ParseHeader();
|
||||
if (result != Result.Ok)
|
||||
return result;
|
||||
|
||||
result = ParseBossStates();
|
||||
if (result != Result.Ok)
|
||||
return result;
|
||||
|
||||
result = ParseAdditionalData();
|
||||
if (result != Result.Ok)
|
||||
return result;
|
||||
|
||||
return Result.Ok;
|
||||
}
|
||||
|
||||
Result ParseHeader()
|
||||
{
|
||||
if (!_doc.RootElement.TryGetProperty("Header", out JsonElement header))
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, $"Missing data header for instance {GetInstanceId()} [{GetMapId()}-{GetMapName()} | {GetDifficultyId()}-{GetDifficultyName()}]");
|
||||
return Result.MissingHeader;
|
||||
}
|
||||
|
||||
if (header.GetString() != _instance.GetHeader())
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, $"Incorrect data header for instance {GetInstanceId()} [{GetMapId()}-{GetMapName()} | {GetDifficultyId()}-{GetDifficultyName()}], expected \"{_instance.GetHeader()}\" got \"{header.GetString()}\"");
|
||||
return Result.UnexpectedHeader;
|
||||
}
|
||||
|
||||
return Result.Ok;
|
||||
}
|
||||
|
||||
Result ParseBossStates()
|
||||
{
|
||||
if (!_doc.RootElement.TryGetProperty("BossStates", out JsonElement bossStates))
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, $"Missing boss states for instance {GetInstanceId()} [{GetMapId()}-{GetMapName()} | {GetDifficultyId()}-{GetDifficultyName()}]");
|
||||
return Result.MissingBossStates;
|
||||
}
|
||||
|
||||
if (bossStates.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, $"Boss states is not an array for instance {GetInstanceId()} [{GetMapId()}-{GetMapName()} | {GetDifficultyId()}-{GetDifficultyName()}]");
|
||||
return Result.BossStatesIsNotAnObject;
|
||||
}
|
||||
|
||||
for (int bossId = 0; bossId < bossStates.GetArrayLength(); ++bossId)
|
||||
{
|
||||
if (bossId >= _instance.GetEncounterCount())
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, $"Boss states has entry for boss with higher id ({bossId}) than number of bosses ({_instance.GetEncounterCount()}) for instance {GetInstanceId()} [{GetMapId()}-{GetMapName()} | {GetDifficultyId()}-{GetDifficultyName()}]");
|
||||
return Result.UnknownBoss;
|
||||
}
|
||||
|
||||
var bossState = bossStates[bossId];
|
||||
if (bossState.ValueKind != JsonValueKind.Number)
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, $"Boss state for boss ({bossId}) is not a number for instance {GetInstanceId()} [{GetMapId()}-{GetMapName()} | {GetDifficultyId()}-{GetDifficultyName()}]");
|
||||
return Result.BossStateIsNotAnObject;
|
||||
}
|
||||
|
||||
EncounterState state = (EncounterState)bossState.GetInt32();
|
||||
if (state == EncounterState.InProgress || state == EncounterState.Fail || state == EncounterState.Special)
|
||||
state = EncounterState.NotStarted;
|
||||
|
||||
if (state < EncounterState.ToBeDecided)
|
||||
_instance.SetBossState((uint)bossId, state);
|
||||
}
|
||||
|
||||
return Result.Ok;
|
||||
}
|
||||
|
||||
Result ParseAdditionalData()
|
||||
{
|
||||
if (!_doc.RootElement.TryGetProperty("AdditionalData", out JsonElement moreData))
|
||||
return Result.Ok;
|
||||
|
||||
if (moreData.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, $"Additional data is not an object for instance {GetInstanceId()} [{GetMapId()}-{GetMapName()} | {GetDifficultyId()}-{GetDifficultyName()}]");
|
||||
return Result.AdditionalDataIsNotAnObject;
|
||||
}
|
||||
|
||||
foreach (PersistentInstanceScriptValueBase valueBase in _instance.GetPersistentScriptValues())
|
||||
{
|
||||
if (moreData.TryGetProperty(valueBase.GetName(), out JsonElement value) && value.ValueKind != JsonValueKind.Null)
|
||||
{
|
||||
if (value.ValueKind != JsonValueKind.Number)
|
||||
{
|
||||
Log.outError(LogFilter.Scripts, $"Additional data value for key {valueBase.GetName()} is not a number for instance {GetInstanceId()} [{GetMapId()}-{GetMapName()} | {GetDifficultyId()}-{GetDifficultyName()}]");
|
||||
return Result.AdditionalDataUnexpectedValueType;
|
||||
}
|
||||
|
||||
if (value.TryGetDouble(out double doubleValue))
|
||||
valueBase.LoadValue(doubleValue);
|
||||
else
|
||||
valueBase.LoadValue(value.GetInt64());
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Ok;
|
||||
}
|
||||
|
||||
uint GetInstanceId() { return _instance.instance.GetInstanceId(); }
|
||||
|
||||
uint GetMapId() { return _instance.instance.GetId(); }
|
||||
|
||||
string GetMapName() { return _instance.instance.GetMapName(); }
|
||||
|
||||
uint GetDifficultyId() { return (uint)_instance.instance.GetDifficultyID(); }
|
||||
|
||||
string GetDifficultyName() { return CliDB.DifficultyStorage.LookupByKey(_instance.instance.GetDifficultyID()).Name; }
|
||||
}
|
||||
|
||||
class InstanceScriptDataWriter
|
||||
{
|
||||
InstanceScript _instance;
|
||||
JsonObject _doc = new();
|
||||
|
||||
public InstanceScriptDataWriter(InstanceScript instance)
|
||||
{
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
public string GetString()
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
using (var writer = new Utf8JsonWriter(stream))
|
||||
{
|
||||
_doc.WriteTo(writer);
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(stream.ToArray());
|
||||
}
|
||||
|
||||
public void FillData(bool withValues = true)
|
||||
{
|
||||
_doc.Add("Header", _instance.GetHeader());
|
||||
|
||||
JsonArray bossStates = new();
|
||||
for (uint bossId = 0; bossId < _instance.GetEncounterCount(); ++bossId)
|
||||
bossStates.Add(JsonValue.Create((int)(withValues ? _instance.GetBossState(bossId) : EncounterState.NotStarted)));
|
||||
|
||||
_doc.Add("BossStates", bossStates);
|
||||
|
||||
if (!_instance.GetPersistentScriptValues().Empty())
|
||||
{
|
||||
JsonObject moreData = new();
|
||||
foreach (PersistentInstanceScriptValueBase additionalValue in _instance.GetPersistentScriptValues())
|
||||
{
|
||||
if (withValues)
|
||||
{
|
||||
UpdateAdditionalSaveDataEvent data = additionalValue.CreateEvent();
|
||||
if (data.Value is double)
|
||||
moreData.Add(data.Key, (double)data.Value);
|
||||
else
|
||||
moreData.Add(data.Key, (long)data.Value);
|
||||
}
|
||||
else
|
||||
moreData.Add(additionalValue.GetName(), null);
|
||||
}
|
||||
|
||||
_doc.Add("AdditionalData", moreData);
|
||||
}
|
||||
}
|
||||
|
||||
public void FillDataFrom(string data)
|
||||
{
|
||||
try
|
||||
{
|
||||
_doc = JsonNode.Parse(data).AsObject();
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
FillData(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetBossState(UpdateBossStateSaveDataEvent data)
|
||||
{
|
||||
var array = _doc["BossStates"].AsArray();
|
||||
array[(int)data.BossId] = (int)data.NewState;
|
||||
}
|
||||
|
||||
public void SetAdditionalData(UpdateAdditionalSaveDataEvent data)
|
||||
{
|
||||
var jObject = _doc["AdditionalData"].AsObject();
|
||||
if (data.Value is double)
|
||||
jObject[data.Key] = (double)data.Value;
|
||||
else
|
||||
jObject[data.Key] = (long)data.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user