mirror of
https://github.com/EggLinks/DanhengServer-OpenSource.git
synced 2026-01-02 20:26:03 +08:00
feat: support resources cache
This commit is contained in:
@@ -91,6 +91,7 @@ public class ServerOption
|
||||
{
|
||||
return Math.Max(Math.Min(FarmingDropRate, 999), 1);
|
||||
}
|
||||
public bool UseCache { get; set; } = true;
|
||||
}
|
||||
|
||||
public class ServerAnnounce
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
using System.Collections.Concurrent;
|
||||
using EggLink.DanhengServer.Enums.Scene;
|
||||
using EggLink.DanhengServer.Util;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EggLink.DanhengServer.Data.Config.Scene;
|
||||
|
||||
public class FloorInfo
|
||||
{
|
||||
[JsonIgnore] public ConcurrentDictionary<int, PropInfo> CachedTeleports = [];
|
||||
[JsonConverter(typeof(ConcurrentDictionaryConverter<int, PropInfo>))]
|
||||
public ConcurrentDictionary<int, PropInfo> CachedTeleports = [];
|
||||
|
||||
[JsonIgnore] public ConcurrentDictionary<int, GroupInfo> Groups = [];
|
||||
[JsonConverter(typeof(ConcurrentDictionaryConverter<int, GroupInfo>))]
|
||||
public ConcurrentDictionary<int, GroupInfo> Groups = [];
|
||||
|
||||
[JsonIgnore] public bool Loaded;
|
||||
|
||||
[JsonIgnore] public ConcurrentBag<PropInfo> UnlockedCheckpoints = [];
|
||||
[JsonConverter(typeof(ConcurrentBagConverter<PropInfo>))]
|
||||
public ConcurrentBag<PropInfo> UnlockedCheckpoints = [];
|
||||
|
||||
public int FloorID { get; set; }
|
||||
public int StartGroupIndex { get; set; }
|
||||
|
||||
@@ -5,7 +5,7 @@ using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace EggLink.DanhengServer.Data.Custom;
|
||||
|
||||
public abstract class BaseRogueBuffExcel : ExcelResource
|
||||
public class BaseRogueBuffExcel : ExcelResource
|
||||
{
|
||||
public int MazeBuffID { get; set; }
|
||||
public int MazeBuffLevel { get; set; }
|
||||
@@ -16,6 +16,11 @@ public abstract class BaseRogueBuffExcel : ExcelResource
|
||||
|
||||
public int RogueBuffTag { get; set; }
|
||||
|
||||
public override int GetId()
|
||||
{
|
||||
return MazeBuffID * 100 + MazeBuffLevel;
|
||||
}
|
||||
|
||||
public RogueCommonBuff ToProto()
|
||||
{
|
||||
return new RogueCommonBuff
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
|
||||
namespace EggLink.DanhengServer.Data.Custom;
|
||||
|
||||
public abstract class BaseRogueBuffGroupExcel : ExcelResource
|
||||
public class BaseRogueBuffGroupExcel : ExcelResource
|
||||
{
|
||||
public int GroupId { get; set; }
|
||||
[JsonIgnore] public List<BaseRogueBuffExcel> BuffList { get; set; } = [];
|
||||
[JsonIgnore] public bool IsLoaded { get; set; }
|
||||
|
||||
public override int GetId()
|
||||
{
|
||||
return GroupId;
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,7 @@ public class ChallengeConfigExcel : ExcelResource
|
||||
GameData.ChallengeConfigData[ID] = this;
|
||||
}
|
||||
|
||||
[method: JsonConstructor]
|
||||
public class ChallengeMonsterInfo(int ConfigId, int NpcMonsterId, int EventId)
|
||||
{
|
||||
public int ConfigId = ConfigId;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using EggLink.DanhengServer.Enums.Scene;
|
||||
using EggLink.DanhengServer.Enums.Scene;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace EggLink.DanhengServer.Data.Excel;
|
||||
|
||||
@@ -25,33 +25,9 @@ public class MainMissionExcel : ExcelResource
|
||||
public int RewardID { get; set; }
|
||||
public List<int> SubRewardList { get; set; } = [];
|
||||
|
||||
[JsonIgnore] private MissionInfo? InnerMissionInfo { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public MissionInfo? MissionInfo
|
||||
{
|
||||
get => InnerMissionInfo;
|
||||
set
|
||||
{
|
||||
InnerMissionInfo = value;
|
||||
if (value != null)
|
||||
foreach (var sub in value.SubMissionList)
|
||||
{
|
||||
SubMissionIds.Add(sub.ID);
|
||||
GameData.SubMissionData.TryGetValue(sub.ID, out var subMission);
|
||||
if (subMission != null)
|
||||
{
|
||||
subMission.MainMissionID = MainMissionID;
|
||||
subMission.MainMissionInfo = InnerMissionInfo;
|
||||
subMission.SubMissionInfo = sub;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore] public MissionInfo MissionInfo { get; protected set; } = new();
|
||||
[JsonIgnore] public List<int> SubMissionIds { get; set; } = [];
|
||||
|
||||
|
||||
public override int GetId()
|
||||
{
|
||||
return MainMissionID;
|
||||
@@ -62,6 +38,23 @@ public class MainMissionExcel : ExcelResource
|
||||
GameData.MainMissionData[GetId()] = this;
|
||||
}
|
||||
|
||||
public void SetMissionInfo(MissionInfo missionInfo)
|
||||
{
|
||||
MissionInfo = missionInfo;
|
||||
if (missionInfo != null)
|
||||
foreach (var sub in missionInfo.SubMissionList)
|
||||
{
|
||||
SubMissionIds.Add(sub.ID);
|
||||
GameData.SubMissionData.TryGetValue(sub.ID, out var subMission);
|
||||
if (subMission != null)
|
||||
{
|
||||
subMission.MainMissionID = MainMissionID;
|
||||
subMission.MainMissionInfo = MissionInfo;
|
||||
subMission.SubMissionInfo = sub;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEqual(MissionData data)
|
||||
{
|
||||
var result = TakeOperation == OperationEnum.And;
|
||||
|
||||
@@ -15,11 +15,6 @@ public class RogueBuffExcel : BaseRogueBuffExcel
|
||||
|
||||
public bool IsAeonBuff => BattleEventBuffType != RogueBuffAeonTypeEnum.Normal;
|
||||
|
||||
public override int GetId()
|
||||
{
|
||||
return MazeBuffID * 100 + MazeBuffLevel;
|
||||
}
|
||||
|
||||
public override void Loaded()
|
||||
{
|
||||
GameData.RogueBuffData.Add(GetId(), this);
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
using EggLink.DanhengServer.Data.Custom;
|
||||
using EggLink.DanhengServer.Util;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EggLink.DanhengServer.Data.Excel;
|
||||
|
||||
[ResourceEntity("RogueBuffGroup.json")]
|
||||
public class RogueBuffGroupExcel : BaseRogueBuffGroupExcel
|
||||
{
|
||||
[JsonProperty("IDLBMIHBAPB")] public int GroupID { get; set; }
|
||||
|
||||
[JsonProperty("GNGDPDOMDFH")] public List<int> BuffTagList { get; set; } = [];
|
||||
public int GroupID { get; set; }
|
||||
public List<int> BuffTagList { get; set; } = [];
|
||||
|
||||
public override int GetId()
|
||||
{
|
||||
|
||||
@@ -7,11 +7,6 @@ public class RogueTournBuffExcel : BaseRogueBuffExcel
|
||||
{
|
||||
public bool IsInHandbook { get; set; }
|
||||
|
||||
public override int GetId()
|
||||
{
|
||||
return MazeBuffID * 100 + MazeBuffLevel;
|
||||
}
|
||||
|
||||
public override void Loaded()
|
||||
{
|
||||
GameData.RogueBuffData.TryAdd(GetId(), this);
|
||||
|
||||
@@ -9,13 +9,9 @@ public class RogueTournBuffGroupExcel : BaseRogueBuffGroupExcel
|
||||
public int RogueBuffGroupID { get; set; }
|
||||
public List<int> RogueBuffDrop { get; set; } = [];
|
||||
|
||||
public override int GetId()
|
||||
{
|
||||
return RogueBuffGroupID;
|
||||
}
|
||||
|
||||
public override void Loaded()
|
||||
{
|
||||
GroupId = RogueBuffGroupID;
|
||||
GameData.RogueBuffGroupData.Add(GetId(), this);
|
||||
LoadBuff();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ using EggLink.DanhengServer.Data.Custom;
|
||||
using EggLink.DanhengServer.Data.Excel;
|
||||
using EggLink.DanhengServer.Enums.Rogue;
|
||||
using EggLink.DanhengServer.Enums.TournRogue;
|
||||
using EggLink.DanhengServer.Util;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EggLink.DanhengServer.Data;
|
||||
|
||||
@@ -41,7 +43,7 @@ public static class GameData
|
||||
public static Dictionary<int, AvatarExpItemConfigExcel> AvatarExpItemConfigData { get; private set; } = [];
|
||||
public static Dictionary<int, AvatarSkillTreeConfigExcel> AvatarSkillTreeConfigData { get; private set; } = [];
|
||||
public static Dictionary<int, AvatarDemoConfigExcel> AvatarDemoConfigData { get; private set; } = [];
|
||||
public static Dictionary<int, ExpTypeExcel> ExpTypeData { get; } = [];
|
||||
public static Dictionary<int, ExpTypeExcel> ExpTypeData { get; private set; } = [];
|
||||
|
||||
public static Dictionary<int, MultiplePathAvatarConfigExcel> MultiplePathAvatarConfigData { get; private set; } =
|
||||
[];
|
||||
@@ -106,7 +108,7 @@ public static class GameData
|
||||
|
||||
public static Dictionary<int, QuestDataExcel> QuestDataData { get; private set; } = [];
|
||||
public static Dictionary<int, FinishWayExcel> FinishWayData { get; private set; } = [];
|
||||
public static Dictionary<int, PlayerLevelConfigExcel> PlayerLevelConfigData { get; } = [];
|
||||
public static Dictionary<int, PlayerLevelConfigExcel> PlayerLevelConfigData { get; private set; } = [];
|
||||
public static Dictionary<int, BackGroundMusicExcel> BackGroundMusicData { get; private set; } = [];
|
||||
public static Dictionary<int, ChatBubbleConfigExcel> ChatBubbleConfigData { get; private set; } = [];
|
||||
|
||||
@@ -121,8 +123,10 @@ public static class GameData
|
||||
|
||||
#region Maze
|
||||
|
||||
[JsonConverter(typeof(ConcurrentDictionaryConverter<string, FloorInfo>))]
|
||||
public static ConcurrentDictionary<string, FloorInfo> FloorInfoData { get; private set; } = [];
|
||||
|
||||
public static Dictionary<int, NPCDataExcel> NpcDataData { get; private set; } = [];
|
||||
public static ConcurrentDictionary<string, FloorInfo> FloorInfoData { get; } = [];
|
||||
public static Dictionary<int, MapEntranceExcel> MapEntranceData { get; private set; } = [];
|
||||
public static Dictionary<int, MazePlaneExcel> MazePlaneData { get; private set; } = [];
|
||||
public static Dictionary<int, MazeChestExcel> MazeChestData { get; private set; } = [];
|
||||
@@ -166,7 +170,7 @@ public static class GameData
|
||||
public static Dictionary<int, ItemConfigExcel> ItemConfigData { get; private set; } = [];
|
||||
public static Dictionary<int, ItemUseBuffDataExcel> ItemUseBuffDataData { get; private set; } = [];
|
||||
public static Dictionary<int, EquipmentConfigExcel> EquipmentConfigData { get; private set; } = [];
|
||||
public static Dictionary<int, EquipmentExpTypeExcel> EquipmentExpTypeData { get; } = [];
|
||||
public static Dictionary<int, EquipmentExpTypeExcel> EquipmentExpTypeData { get; private set; } = [];
|
||||
public static Dictionary<int, EquipmentExpItemConfigExcel> EquipmentExpItemConfigData { get; private set; } = [];
|
||||
|
||||
public static Dictionary<int, EquipmentPromotionConfigExcel> EquipmentPromotionConfigData { get; private set; } =
|
||||
|
||||
175
Common/Data/ResourceCache.cs
Normal file
175
Common/Data/ResourceCache.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using EggLink.DanhengServer.Util;
|
||||
using EggLink.DanhengServer.Data.Config.Scene;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using EggLink.DanhengServer.Internationalization;
|
||||
using System.IO.MemoryMappedFiles;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace EggLink.DanhengServer.Data;
|
||||
|
||||
public static class CompressionHelper
|
||||
{
|
||||
public static byte[] Compress(byte[] data)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(data);
|
||||
if (data.Length == 0) return [];
|
||||
|
||||
try
|
||||
{
|
||||
if (data.Length < 1024)
|
||||
{
|
||||
var result = new byte[data.Length + 1];
|
||||
result[0] = 0;
|
||||
Buffer.BlockCopy(data, 0, result, 1, data.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
using var output = new MemoryStream();
|
||||
output.WriteByte(1);
|
||||
using (var compressor = new DeflateStream(output, CompressionMode.Compress, true))
|
||||
{
|
||||
compressor.Write(data, 0, data.Length);
|
||||
}
|
||||
return output.ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
var result = new byte[data.Length + 1];
|
||||
result[0] = 0;
|
||||
Buffer.BlockCopy(data, 0, result, 1, data.Length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] Decompress(byte[] data)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(data);
|
||||
if (data.Length == 0) return [];
|
||||
|
||||
try
|
||||
{
|
||||
if (data[0] == 0)
|
||||
{
|
||||
var result = new byte[data.Length - 1];
|
||||
Buffer.BlockCopy(data, 1, result, 0, result.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
using var input = new MemoryStream(data, 1, data.Length - 1);
|
||||
using var decompressor = new DeflateStream(input, CompressionMode.Decompress);
|
||||
using var output = new MemoryStream();
|
||||
decompressor.CopyTo(output);
|
||||
return output.ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ResourceCacheData
|
||||
{
|
||||
public Dictionary<string, byte[]> GameDataValues { get; set; } = [];
|
||||
}
|
||||
|
||||
public class IgnoreJsonIgnoreContractResolver : DefaultContractResolver
|
||||
{
|
||||
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
|
||||
{
|
||||
var property = base.CreateProperty(member, memberSerialization);
|
||||
property.Ignored = false;
|
||||
return property;
|
||||
}
|
||||
}
|
||||
|
||||
public class ResourceCache
|
||||
{
|
||||
public static Logger Logger { get; } = new("ResourceCache");
|
||||
public static string CachePath { get; } = ConfigManager.Config.Path.ConfigPath + "/Resource.cache";
|
||||
public static bool IsComplete { get; set; } = true; // Custom in errors to ignore some error
|
||||
|
||||
public static readonly JsonSerializerSettings Serializer = new()
|
||||
{
|
||||
ContractResolver = new IgnoreJsonIgnoreContractResolver(),
|
||||
TypeNameHandling = TypeNameHandling.Auto,
|
||||
Converters =
|
||||
{
|
||||
new ConcurrentBagConverter<PropInfo>(),
|
||||
new ConcurrentDictionaryConverter<string, FloorInfo>()
|
||||
}
|
||||
};
|
||||
|
||||
public static Task SaveCache()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var cacheData = new ResourceCacheData
|
||||
{
|
||||
GameDataValues = typeof(GameData)
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(p => p.GetValue(null) != null)
|
||||
.ToDictionary(
|
||||
p => p.Name,
|
||||
p => CompressionHelper.Compress(
|
||||
Encoding.UTF8.GetBytes(
|
||||
JsonConvert.SerializeObject(p.GetValue(null), Serializer)
|
||||
)
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
File.WriteAllText(CachePath, JsonConvert.SerializeObject(cacheData));
|
||||
Logger.Info(I18NManager.Translate("Server.ServerInfo.GeneratedItem",
|
||||
I18NManager.Translate("Word.Cache")));
|
||||
});
|
||||
}
|
||||
|
||||
public static bool LoadCache()
|
||||
{
|
||||
var buffer = new byte[new FileInfo(CachePath).Length];
|
||||
var viewAccessor = MemoryMappedFile.CreateFromFile(CachePath, FileMode.Open).CreateViewAccessor();
|
||||
viewAccessor.ReadArray(0, buffer, 0, buffer.Length);
|
||||
|
||||
var cacheData = JsonConvert.DeserializeObject<ResourceCacheData>(Encoding.UTF8.GetString(buffer));
|
||||
if (cacheData == null) return false;
|
||||
|
||||
Parallel.ForEachAsync(
|
||||
typeof(GameData).GetProperties(BindingFlags.Public | BindingFlags.Static),
|
||||
async (prop, token) => {
|
||||
if (cacheData.GameDataValues.TryGetValue(prop.Name, out var valueBytes))
|
||||
prop.SetValue(null, await Task.Run(() => JsonConvert.DeserializeObject(
|
||||
Encoding.UTF8.GetString(
|
||||
CompressionHelper.Decompress(valueBytes)), prop.PropertyType, Serializer
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Logger.Info(I18NManager.Translate("Server.ServerInfo.LoadedItem",
|
||||
I18NManager.Translate("Word.Cache")));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void ClearGameData()
|
||||
{
|
||||
var properties = typeof(GameData).GetProperties(BindingFlags.Public | BindingFlags.Static);
|
||||
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
var propType = prop.PropertyType;
|
||||
var emptyValue = propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(Dictionary<,>)
|
||||
? Activator.CreateInstance(propType)
|
||||
: propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(List<>)
|
||||
? Activator.CreateInstance(propType) : propType.IsClass
|
||||
? Activator.CreateInstance(propType) : null;
|
||||
|
||||
prop.SetValue(null, emptyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,7 @@ public class ResourceManager
|
||||
var file = new FileInfo(path);
|
||||
if (!file.Exists)
|
||||
{
|
||||
// ResourceCache.IsComplete = false;
|
||||
Logger.Error(I18NManager.Translate("Server.ServerInfo.FailedToReadItem", fileName,
|
||||
I18NManager.Translate("Word.NotFound")));
|
||||
continue;
|
||||
@@ -154,6 +155,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error(
|
||||
I18NManager.Translate("Server.ServerInfo.FailedToReadItem", fileName,
|
||||
I18NManager.Translate("Word.Error")), ex);
|
||||
@@ -231,6 +233,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error(
|
||||
I18NManager.Translate("Server.ServerInfo.FailedToReadItem", groupFile.Name,
|
||||
I18NManager.Translate("Word.Error")), ex);
|
||||
@@ -243,6 +246,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error(
|
||||
I18NManager.Translate("Server.ServerInfo.FailedToReadItem", file.Name,
|
||||
I18NManager.Translate("Word.Error")), ex);
|
||||
@@ -262,7 +266,6 @@ public class ResourceManager
|
||||
I18NManager.Translate("Word.FloorInfo")));
|
||||
}
|
||||
|
||||
|
||||
public static void LoadMissionInfo()
|
||||
{
|
||||
Logger.Info(I18NManager.Translate("Server.ServerInfo.LoadingItem", I18NManager.Translate("Word.MissionInfo")));
|
||||
@@ -292,7 +295,7 @@ public class ResourceManager
|
||||
var missionInfo = JsonConvert.DeserializeObject<MissionInfo>(json);
|
||||
if (missionInfo != null)
|
||||
{
|
||||
GameData.MainMissionData[missionExcel.Key].MissionInfo = missionInfo;
|
||||
GameData.MainMissionData[missionExcel.Key].SetMissionInfo(missionInfo);
|
||||
count++;
|
||||
}
|
||||
else
|
||||
@@ -335,6 +338,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error("Error in reading " + file.Name, ex);
|
||||
}
|
||||
|
||||
@@ -394,6 +398,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error(
|
||||
I18NManager.Translate("Server.ServerInfo.FailedToReadItem", adventurePath,
|
||||
I18NManager.Translate("Word.Error")), ex);
|
||||
@@ -437,6 +442,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error(
|
||||
I18NManager.Translate("Server.ServerInfo.FailedToReadItem", summonUnit.JsonPath,
|
||||
I18NManager.Translate("Word.Error")), ex);
|
||||
@@ -478,6 +484,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error(
|
||||
I18NManager.Translate("Server.ServerInfo.FailedToReadItem", file.Name,
|
||||
I18NManager.Translate("Word.Error")), ex);
|
||||
@@ -526,6 +533,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error(
|
||||
I18NManager.Translate("Server.ServerInfo.FailedToReadItem", file.Name,
|
||||
I18NManager.Translate("Word.Error")), ex);
|
||||
@@ -555,6 +563,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error(
|
||||
I18NManager.Translate("Server.ServerInfo.FailedToReadItem", file.Name,
|
||||
I18NManager.Translate("Word.Error")), ex);
|
||||
@@ -598,6 +607,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error(
|
||||
I18NManager.Translate("Server.ServerInfo.FailedToReadItem", file.Name,
|
||||
I18NManager.Translate("Word.Error")), ex);
|
||||
@@ -652,6 +662,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error(
|
||||
I18NManager.Translate("Server.ServerInfo.FailedToReadItem", file.Name,
|
||||
I18NManager.Translate("Word.Error")), ex);
|
||||
@@ -705,6 +716,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error(
|
||||
I18NManager.Translate("Server.ServerInfo.FailedToReadItem", file.Name,
|
||||
I18NManager.Translate("Word.Error")), ex);
|
||||
@@ -782,6 +794,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error("Error in reading " + file.Name, ex);
|
||||
}
|
||||
|
||||
@@ -803,7 +816,6 @@ public class ResourceManager
|
||||
I18NManager.Translate("Word.RogueTournRoomInfo"),
|
||||
$"{ConfigManager.Config.Path.ConfigPath}/TournRogueRoomGen.json",
|
||||
I18NManager.Translate("Word.RogueTournRoom")));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -827,6 +839,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error("Error in reading " + file.Name, ex);
|
||||
}
|
||||
|
||||
@@ -872,6 +885,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error("Error in reading " + file.Name, ex);
|
||||
}
|
||||
|
||||
@@ -910,6 +924,7 @@ public class ResourceManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResourceCache.IsComplete = false;
|
||||
Logger.Error("Error in reading " + file.Name, ex);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ public class WordTextCHS
|
||||
public string Language => "语言";
|
||||
public string Log => "日志";
|
||||
public string GameData => "游戏数据";
|
||||
public string Cache => "资源缓存";
|
||||
public string Database => "数据库";
|
||||
public string Command => "命令";
|
||||
public string WebServer => "Web服务器";
|
||||
@@ -152,6 +153,8 @@ public class ServerInfoTextCHS
|
||||
public string CancelKeyPressed => "已按下取消键 (Ctrl + C),服务器即将关闭…";
|
||||
public string StartingServer => "正在启动 DanhengServer…";
|
||||
public string LoadingItem => "正在加载 {0}…";
|
||||
public string GeneratingItem => "正在生成 {0}…";
|
||||
public string WaitingItem => "正在等待进程 {0} 完成…";
|
||||
public string RegisterItem => "注册了 {0} 个 {1}。";
|
||||
public string FailedToLoadItem => "加载 {0} 失败。";
|
||||
public string NewClientSecretKey => "客户端密钥不存在,正在生成新的客户端密钥。";
|
||||
@@ -163,6 +166,7 @@ public class ServerInfoTextCHS
|
||||
public string ServerRunning => "{0} 服务器正在监听 {1}";
|
||||
public string ServerStarted => "启动完成!用时 {0}s,击败了99%的用户,输入 ‘help’ 来获取命令帮助"; // 玩梗,考虑英语版本将其本土化
|
||||
public string MissionEnabled => "任务系统已启用,此功能仍在开发中,且可能不会按预期工作,如果遇见任何bug,请汇报给开发者。";
|
||||
public string CacheLoadSkip => "已跳过缓存加载。";
|
||||
|
||||
public string ConfigMissing => "{0} 缺失,请检查你的资源文件夹:{1},{2} 可能不能使用。";
|
||||
public string UnloadedItems => "卸载了所有 {0}。";
|
||||
|
||||
@@ -54,6 +54,7 @@ public class WordTextCHT
|
||||
public string Language => "語言";
|
||||
public string Log => "日誌";
|
||||
public string GameData => "遊戲數據";
|
||||
public string Cache => "資源緩存";
|
||||
public string Database => "數據庫";
|
||||
public string Command => "命令";
|
||||
public string WebServer => "Web服務器";
|
||||
@@ -152,6 +153,8 @@ public class ServerInfoTextCHT
|
||||
public string CancelKeyPressed => "已按下取消鍵 (Ctrl + C),服務器即將關閉…";
|
||||
public string StartingServer => "正在啟動 DanhengServer…";
|
||||
public string LoadingItem => "正在加載 {0}…";
|
||||
public string GeneratingItem => "正在生成 {0}…";
|
||||
public string WaitingItem => "正在等待進程 {0} 完成…";
|
||||
public string RegisterItem => "註冊了 {0} 個 {1}。";
|
||||
public string FailedToLoadItem => "加載 {0} 失敗。";
|
||||
public string NewClientSecretKey => "客戶端密鑰不存在,正在生成新的客戶端密鑰。";
|
||||
@@ -163,6 +166,7 @@ public class ServerInfoTextCHT
|
||||
public string ServerRunning => "{0} 服務器正在監聽 {1}";
|
||||
public string ServerStarted => "啟動完成!用時 {0}s,擊敗了99%的用戶,輸入 『help』 來獲取命令幫助"; // 玩梗,考慮英語版本將其本土化
|
||||
public string MissionEnabled => "任務系統已啟用,此功能仍在開發中,且可能不會按預期工作,如果遇見任何bug,請匯報給開發者。";
|
||||
public string CacheLoadSkip => "已跳過緩存加載。";
|
||||
|
||||
public string ConfigMissing => "{0} 缺失,請檢查你的資源文件夾:{1},{2} 可能不能使用。";
|
||||
public string UnloadedItems => "卸載了所有 {0}。";
|
||||
|
||||
@@ -54,6 +54,7 @@ public class WordTextEN
|
||||
public string Language => "Language";
|
||||
public string Log => "Log";
|
||||
public string GameData => "Game Data";
|
||||
public string Cache => "Resource Cache";
|
||||
public string Database => "Database";
|
||||
public string Command => "Command";
|
||||
public string WebServer => "Web Server";
|
||||
@@ -152,6 +153,8 @@ public class ServerInfoTextEN
|
||||
public string CancelKeyPressed => "Cancel key pressed (Ctrl + C), server shutting down...";
|
||||
public string StartingServer => "Starting DanhengServer...";
|
||||
public string LoadingItem => "Loading {0}...";
|
||||
public string GeneratingItem => "Building {0}...";
|
||||
public string WaitingItem => "Waiting for process {0} to complete...";
|
||||
public string RegisterItem => "Registered {0} {1}(s).";
|
||||
public string FailedToLoadItem => "Failed to load {0}.";
|
||||
|
||||
@@ -170,6 +173,7 @@ public class ServerInfoTextEN
|
||||
|
||||
public string MissionEnabled =>
|
||||
"Mission system enabled. This feature is still in development and may not work as expected. Please report any bugs to the developers.";
|
||||
public string CacheLoadSkip => "Skipped cache loading.";
|
||||
|
||||
public string ConfigMissing => "{0} is missing. Please check your resource folder: {1}, {2} may not be available.";
|
||||
public string UnloadedItems => "Unloaded all {0}.";
|
||||
|
||||
48
Common/Util/CustomConverters.cs
Normal file
48
Common/Util/CustomConverters.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace EggLink.DanhengServer.Util;
|
||||
|
||||
public class ConcurrentBagConverter<T> : JsonConverter<ConcurrentBag<T>>
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, ConcurrentBag<T>? value, JsonSerializer serializer)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WriteNull();
|
||||
return;
|
||||
}
|
||||
serializer.Serialize(writer, value.ToArray());
|
||||
}
|
||||
|
||||
public override ConcurrentBag<T>? ReadJson(JsonReader reader, Type objectType, ConcurrentBag<T>? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
|
||||
var array = serializer.Deserialize<T[]>(reader);
|
||||
return array != null ? new ConcurrentBag<T>(array) : new ConcurrentBag<T>();
|
||||
}
|
||||
}
|
||||
|
||||
public class ConcurrentDictionaryConverter<TKey, TValue> : JsonConverter<ConcurrentDictionary<TKey, TValue>> where TKey : notnull
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, ConcurrentDictionary<TKey, TValue>? value, JsonSerializer serializer)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
writer.WriteNull();
|
||||
return;
|
||||
}
|
||||
serializer.Serialize(writer, value.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
|
||||
}
|
||||
|
||||
public override ConcurrentDictionary<TKey, TValue>? ReadJson(JsonReader reader, Type objectType, ConcurrentDictionary<TKey, TValue>? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
|
||||
var dictionary = serializer.Deserialize<Dictionary<TKey, TValue>>(reader);
|
||||
return dictionary != null ? new ConcurrentDictionary<TKey, TValue>(dictionary) : new ConcurrentDictionary<TKey, TValue>();
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,16 @@ public static class HandbookGenerator
|
||||
{
|
||||
if (langFile.Extension != ".json") return;
|
||||
var lang = langFile.Name.Replace("TextMap", "").Replace(".json", "");
|
||||
|
||||
// Check if handbook needs to regenerate
|
||||
var handbookPath = $"GM Handbook/GM Handbook {lang}.txt";
|
||||
if (File.Exists(handbookPath))
|
||||
{
|
||||
var handbookInfo = new FileInfo(handbookPath);
|
||||
if (handbookInfo.LastWriteTime >= langFile.LastWriteTime)
|
||||
continue; // Skip if handbook is newer than language file
|
||||
}
|
||||
|
||||
Generate(lang);
|
||||
}
|
||||
|
||||
|
||||
@@ -127,10 +127,40 @@ public class EntryPoint
|
||||
GenerateLogMap();
|
||||
|
||||
// Load the game data
|
||||
Logger.Info(I18NManager.Translate("Server.ServerInfo.LoadingItem", I18NManager.Translate("Word.GameData")));
|
||||
try
|
||||
{
|
||||
ResourceManager.LoadGameData();
|
||||
var isCache = false;
|
||||
if (File.Exists(ResourceCache.CachePath))
|
||||
if (ConfigManager.Config.ServerOption.UseCache)
|
||||
{
|
||||
Logger.Info(I18NManager.Translate("Server.ServerInfo.LoadingItem", I18NManager.Translate("Word.Cache")));
|
||||
isCache = ResourceCache.LoadCache();
|
||||
|
||||
// Clear all game data if cache loading fails
|
||||
if (!isCache)
|
||||
{
|
||||
ResourceCache.ClearGameData();
|
||||
Logger.Warn(I18NManager.Translate("Server.ServerInfo.CacheLoadFailed"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Delete(ResourceCache.CachePath);
|
||||
Logger.Warn(I18NManager.Translate("Server.ServerInfo.CacheLoadSkip"));
|
||||
}
|
||||
|
||||
if (!isCache)
|
||||
{
|
||||
Logger.Info(I18NManager.Translate("Server.ServerInfo.LoadingItem", I18NManager.Translate("Word.GameData")));
|
||||
ResourceManager.LoadGameData();
|
||||
|
||||
// Async process cache saving
|
||||
if (ConfigManager.Config.ServerOption.UseCache && ResourceCache.IsComplete)
|
||||
{
|
||||
Logger.Warn(I18NManager.Translate("Server.ServerInfo.WaitingItem", I18NManager.Translate("Word.Cache")));
|
||||
_ = ResourceCache.SaveCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user