feat: support resources cache

This commit is contained in:
letheriver2007
2024-12-22 21:02:26 +08:00
committed by EggLink
parent f65c2933a5
commit 6484e28df8
20 changed files with 348 additions and 60 deletions

View File

@@ -91,6 +91,7 @@ public class ServerOption
{
return Math.Max(Math.Min(FarmingDropRate, 999), 1);
}
public bool UseCache { get; set; } = true;
}
public class ServerAnnounce

View File

@@ -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; }

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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()
{

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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; } =

View 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);
}
}
}

View File

@@ -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);
}

View File

@@ -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}。";

View File

@@ -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}。";

View File

@@ -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}.";

View 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>();
}
}

View File

@@ -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);
}

View File

@@ -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)
{