diff --git a/Common/Data/Excel/ItemConfigExcel.cs b/Common/Data/Excel/ItemConfigExcel.cs index 918a9262..4c7966ef 100644 --- a/Common/Data/Excel/ItemConfigExcel.cs +++ b/Common/Data/Excel/ItemConfigExcel.cs @@ -25,7 +25,7 @@ namespace EggLink.DanhengServer.Data.Excel [JsonConverter(typeof(StringEnumConverter))] public ItemUseMethodEnum UseMethod { get; set; } - //public List ReturnItemIDList{ get; set; } + public List ReturnItemIDList { get; set; } = []; [JsonIgnore] public int Exp { get; set; } diff --git a/Common/Data/Excel/MappingInfoExcel.cs b/Common/Data/Excel/MappingInfoExcel.cs index 70dc14ca..078b64fe 100644 --- a/Common/Data/Excel/MappingInfoExcel.cs +++ b/Common/Data/Excel/MappingInfoExcel.cs @@ -17,6 +17,8 @@ namespace EggLink.DanhengServer.Data.Excel [JsonIgnore] public List DropItemList { get; set; } = []; + [JsonIgnore] + public List DropRelicItemList { get; set; } = []; public override int GetId() { @@ -182,7 +184,7 @@ namespace EggLink.DanhengServer.Data.Excel if (amount > 0) { drop.ItemNum = (int)amount; - DropItemList.Add(drop); + DropRelicItemList.Add(drop); } } } diff --git a/Common/Data/Excel/PlaneEventExcel.cs b/Common/Data/Excel/PlaneEventExcel.cs index 97491f06..6c0cd447 100644 --- a/Common/Data/Excel/PlaneEventExcel.cs +++ b/Common/Data/Excel/PlaneEventExcel.cs @@ -12,6 +12,7 @@ namespace EggLink.DanhengServer.Data.Excel public int EventID { get; set; } public int WorldLevel { get; set; } public int Reward { get; set; } + public List DropList { get; set; } = []; public int StageID { get; set; } public override int GetId() diff --git a/Common/Data/Excel/SpecialAvatarExcel.cs b/Common/Data/Excel/SpecialAvatarExcel.cs index e3686042..79a34a27 100644 --- a/Common/Data/Excel/SpecialAvatarExcel.cs +++ b/Common/Data/Excel/SpecialAvatarExcel.cs @@ -77,7 +77,7 @@ namespace EggLink.DanhengServer.Data.Excel CurrentHp = hp == 0 ? 10000 : hp, CurrentSp = sp, InternalEntityId = Id, - PlayerData = DatabaseHelper.Instance!.GetInstance(uid), + PlayerData = DatabaseHelper.GetInstance(uid), }; } } diff --git a/Common/Database/Account/AccountData.cs b/Common/Database/Account/AccountData.cs index 686a1f08..7c68c424 100644 --- a/Common/Database/Account/AccountData.cs +++ b/Common/Database/Account/AccountData.cs @@ -33,7 +33,7 @@ namespace EggLink.DanhengServer.Database.Account public static AccountData? GetAccountByUid(long uid) { - AccountData? result = DatabaseHelper.Instance?.GetInstance(uid); + AccountData? result = DatabaseHelper.Instance?.GetInstance((int)uid); return result; } diff --git a/Common/Database/Avatar/AvatarData.cs b/Common/Database/Avatar/AvatarData.cs index 9b8307e3..a4651aa6 100644 --- a/Common/Database/Avatar/AvatarData.cs +++ b/Common/Database/Avatar/AvatarData.cs @@ -393,7 +393,7 @@ namespace EggLink.DanhengServer.Database.Avatar Pos = (uint)pos, }; - var inventory = DatabaseHelper.Instance!.GetInstance(PlayerData!.Uid)!; + var inventory = DatabaseHelper.GetInstance(PlayerData!.Uid)!; foreach (var item in Relic) { var relic = inventory.RelicItems.Find(x => x.UniqueId == item.Value)!; diff --git a/Common/Database/DatabaseHelper.cs b/Common/Database/DatabaseHelper.cs index 1fa67b7e..71e50445 100644 --- a/Common/Database/DatabaseHelper.cs +++ b/Common/Database/DatabaseHelper.cs @@ -1,20 +1,19 @@ -using EggLink.DanhengServer.Configuration; -using EggLink.DanhengServer.Database.Account; -using EggLink.DanhengServer.Database.Inventory; +using EggLink.DanhengServer.Database.Inventory; using EggLink.DanhengServer.Database.Mission; using EggLink.DanhengServer.Util; -using Microsoft.Data.Sqlite; using SqlSugar; -using System.Reflection; namespace EggLink.DanhengServer.Database { public class DatabaseHelper { - public Logger logger = new("Database"); + public static Logger logger = new("Database"); public static SqlSugarScope? sqlSugarScope; public static DatabaseHelper? Instance; - private static readonly Dictionary _lock = []; + public readonly static Dictionary> UidInstanceMap = []; + public readonly static List ToSaveUidList = []; + public static long LastSaveTick = DateTime.UtcNow.Ticks; + public static Thread? SaveThread; public DatabaseHelper() { @@ -56,6 +55,7 @@ namespace EggLink.DanhengServer.Database SerializeService = new CustomSerializeService() } }); + switch (config.Database.DatabaseType) { case "sqlite": @@ -68,6 +68,44 @@ namespace EggLink.DanhengServer.Database logger.Error("Unsupported database type"); break; } + + var baseType = typeof(BaseDatabaseDataHelper); + var assembly = typeof(BaseDatabaseDataHelper).Assembly; + var types = assembly.GetTypes().Where(t => t.IsSubclassOf(baseType)); + foreach (var t in types) + { + typeof(DatabaseHelper).GetMethod("InitializeTable")?.MakeGenericMethod(t).Invoke(null, null); // cache the data + } + + LastSaveTick = DateTime.UtcNow.Ticks; + + SaveThread = new(() => + { + while (true) + { + CalcSaveDatabase(); + } + }); + SaveThread.Start(); + } + + public static void InitializeTable() where T : class, new() + { + var list = sqlSugarScope?.Queryable() + .Select(x => x) + .ToList(); + + foreach (var instance in list!) + { + var inst = (instance as BaseDatabaseDataHelper)!; + if (!UidInstanceMap.TryGetValue(inst.Uid, out List? value)) + { + value = []; + UidInstanceMap[inst.Uid] = value; + } + + value.Add(inst); // add to the map + } } public void UpgradeDatabase() @@ -85,7 +123,7 @@ namespace EggLink.DanhengServer.Database } } - public void MoveFromSqlite() + public static void MoveFromSqlite() { logger.Info("Moving from sqlite..."); @@ -130,16 +168,6 @@ namespace EggLink.DanhengServer.Database } } - public object GetLock(int uid) - { - if (!_lock.TryGetValue(uid, out object? value)) - { - value = new(); - _lock[uid] = value; - } - return value; - } - public static void InitializeSqlite() { var baseType = typeof(BaseDatabaseDataHelper); @@ -150,7 +178,7 @@ namespace EggLink.DanhengServer.Database typeof(DatabaseHelper).GetMethod("InitializeSqliteTable")?.MakeGenericMethod(type).Invoke(null, null); } } - + public static void InitializeMysql() { sqlSugarScope?.DbMaintenance.CreateDatabase(); @@ -161,21 +189,35 @@ namespace EggLink.DanhengServer.Database { try { - sqlSugarScope?.Queryable().ToList(); - } catch + sqlSugarScope?.Queryable() + .Select(x => x) + .ToList(); + } + catch { sqlSugarScope?.CodeFirst.InitTables(); } } - public T? GetInstance(long uid) where T : class, new() + public static T? GetInstance(int uid) where T : class, new() { try { - lock (GetLock((int)uid)) + if (!UidInstanceMap.TryGetValue(uid, out List? value)) { - return sqlSugarScope?.Queryable().Where(it => (it as BaseDatabaseDataHelper)!.Uid == uid).First(); + value = []; + UidInstanceMap[uid] = value; } + + foreach (var instance in value) + { + if (instance is T) + { + return instance as T; // found + } + } + + return null; // not found } catch (Exception e) { @@ -196,40 +238,91 @@ namespace EggLink.DanhengServer.Database return instance; } - public List? GetAllInstance() where T : class, new() + public static List? GetAllInstance() where T : class, new() { try { - return sqlSugarScope?.Queryable().ToList(); - } catch(Exception e) + return sqlSugarScope?.Queryable() + .Select(x => x) + .ToList(); + } + catch (Exception e) { logger.Error("Unsupported type", e); return null; } } - public void SaveInstance(T instance) where T : class, new() + public static void SaveInstance(T instance) where T : class, new() { - lock (GetLock((instance as BaseDatabaseDataHelper)!.Uid)) - { - sqlSugarScope?.Insertable(instance).ExecuteCommand(); - } + sqlSugarScope?.Insertable(instance).ExecuteCommand(); + UidInstanceMap[(instance as BaseDatabaseDataHelper)!.Uid].Add((instance as BaseDatabaseDataHelper)!); // add to the map } public void UpdateInstance(T instance) where T : class, new() { - lock (GetLock((instance as BaseDatabaseDataHelper)!.Uid)) + //lock (GetLock((instance as BaseDatabaseDataHelper)!.Uid)) + //{ + // sqlSugarScope?.Updateable(instance).ExecuteCommand(); + //} + } + + public void CalcSaveDatabase() // per 5 min + { + if (LastSaveTick + TimeSpan.TicksPerMinute * 5 > DateTime.UtcNow.Ticks) return; + SaveDatabase(); + } + + public static void SaveDatabase() // per 5 min + { + try + { + var prev = DateTime.Now; + foreach (var uid in ToSaveUidList) + { + var value = UidInstanceMap[uid]; + var baseType = typeof(BaseDatabaseDataHelper); + var assembly = typeof(BaseDatabaseDataHelper).Assembly; + var types = assembly.GetTypes().Where(t => t.IsSubclassOf(baseType)); + foreach (var type in types) + { + var instance = value.Find(x => x.GetType() == type); + if (instance != null) + { + typeof(DatabaseHelper).GetMethod("SaveDatabaseType")?.MakeGenericMethod(type).Invoke(null, [instance]); + } + } + } + + logger.Info($"Save database. Using {(DateTime.Now - prev).TotalSeconds.ToString()[..4]} seconds."); + + ToSaveUidList.Clear(); + } + catch (Exception e) + { + logger.Error("An error occurred while saving the database", e); + } + + LastSaveTick = DateTime.UtcNow.Ticks; + } + + public static void SaveDatabaseType(T instance) where T : class, new() + { + try { sqlSugarScope?.Updateable(instance).ExecuteCommand(); } + catch (Exception e) + { + logger.Error("An error occurred while saving the database", e); + } } - public void DeleteInstance(T instance) where T : class, new() + public static void DeleteInstance(T instance) where T : class, new() { - lock (GetLock((instance as BaseDatabaseDataHelper)!.Uid)) - { - sqlSugarScope?.Deleteable(instance).ExecuteCommand(); - } + sqlSugarScope?.Deleteable(instance).ExecuteCommand(); + UidInstanceMap[(instance as BaseDatabaseDataHelper)!.Uid].Remove((instance as BaseDatabaseDataHelper)!); // remove from the map + ToSaveUidList.Remove((instance as BaseDatabaseDataHelper)!.Uid); // remove from the save list } } } diff --git a/Common/Database/Inventory/InventoryData.cs b/Common/Database/Inventory/InventoryData.cs index 6c4f10b4..a1da5f25 100644 --- a/Common/Database/Inventory/InventoryData.cs +++ b/Common/Database/Inventory/InventoryData.cs @@ -198,6 +198,26 @@ namespace EggLink.DanhengServer.Database.Inventory return relic; } + public ItemData Clone() + { + return new() + { + UniqueId = UniqueId, + ItemId = ItemId, + Count = Count, + Level = Level, + Exp = Exp, + TotalExp = TotalExp, + Promotion = Promotion, + Rank = Rank, + Locked = Locked, + Discarded = Discarded, + MainAffix = MainAffix, + SubAffixes = SubAffixes.Select(x => x.Clone()).ToList(), + EquipAvatar = EquipAvatar + }; + } + #endregion } @@ -236,5 +256,15 @@ namespace EggLink.DanhengServer.Database.Inventory Cnt = (uint)Count, Step = (uint)Step }; + + public ItemSubAffix Clone() + { + return new() + { + Id = Id, + Count = Count, + Step = Step + }; + } } } diff --git a/Common/Database/Player/PlayerData.cs b/Common/Database/Player/PlayerData.cs index 6b5d8dca..ade81fa0 100644 --- a/Common/Database/Player/PlayerData.cs +++ b/Common/Database/Player/PlayerData.cs @@ -48,7 +48,7 @@ namespace EggLink.DanhengServer.Database.Player public static PlayerData? GetPlayerByUid(long uid) { - PlayerData? result = DatabaseHelper.Instance?.GetInstance(uid); + PlayerData? result = DatabaseHelper.Instance?.GetInstance((int)uid); return result; } @@ -69,7 +69,7 @@ namespace EggLink.DanhengServer.Database.Player public PlayerSimpleInfo ToSimpleProto(FriendOnlineStatus status) { - var AvatarInfo = DatabaseHelper.Instance!.GetInstance(Uid)!; + var AvatarInfo = DatabaseHelper.GetInstance(Uid)!; foreach (var avatar in AvatarInfo.Avatars) { @@ -120,7 +120,7 @@ namespace EggLink.DanhengServer.Database.Player RecordInfo = new(), }; - var AvatarInfo = DatabaseHelper.Instance!.GetInstance(Uid); + var AvatarInfo = DatabaseHelper.GetInstance(Uid); if (AvatarInfo != null) { diff --git a/GameServer/Command/CommandSender.cs b/GameServer/Command/CommandSender.cs index 5044b8c7..be1708b1 100644 --- a/GameServer/Command/CommandSender.cs +++ b/GameServer/Command/CommandSender.cs @@ -42,7 +42,7 @@ namespace EggLink.DanhengServer.Command public bool HasPermission(string permission) { - var account = DatabaseHelper.Instance!.GetInstance(Player.Uid)!; + var account = DatabaseHelper.GetInstance(Player.Uid)!; return account.Permissions!.Contains(permission); } } diff --git a/GameServer/Game/Battle/BattleInstance.cs b/GameServer/Game/Battle/BattleInstance.cs index ec2f744f..42335d8d 100644 --- a/GameServer/Game/Battle/BattleInstance.cs +++ b/GameServer/Game/Battle/BattleInstance.cs @@ -20,6 +20,7 @@ namespace EggLink.DanhengServer.Game.Battle public int MappingInfoId { get; set; } public int RoundLimit { get; set; } public int StageId { get; set; } = stages.Count > 0 ? stages[0].StageID : 0; // Set to 0 when hit monster + public int EventId { get; set; } public int CustomLevel { get; set; } public BattleEndStatus BattleEndStatus { get; set; } @@ -49,6 +50,7 @@ namespace EggLink.DanhengServer.Game.Battle public ItemList GetDropItemList() { + if (BattleEndStatus != BattleEndStatus.BattleEndWin) return new(); var list = new ItemList(); foreach (var item in MonsterDropItems) @@ -56,52 +58,9 @@ namespace EggLink.DanhengServer.Game.Battle list.ItemList_.Add(item.ToProto()); } - // calculate drops - GameData.MappingInfoData.TryGetValue(MappingInfoId * 10 + WorldLevel, out var mapping); - if (mapping != null) + foreach (var item in Player.InventoryManager!.HandleMappingInfo(MappingInfoId, WorldLevel)) { - List items = []; - foreach (var item in mapping.DropItemList) - { - var random = Random.Shared.Next(0, 101); - if (random <= item.Chance) - { - var amount = item.ItemNum > 0 ? item.ItemNum : Random.Shared.Next(item.MinCount, item.MaxCount + 1); - - GameData.ItemConfigData.TryGetValue(item.ItemID, out var itemData); - if (itemData == null) continue; - - if (itemData.ItemMainType == ItemMainTypeEnum.Relic || itemData.ItemMainType == ItemMainTypeEnum.Equipment) - { - for (int i = 0; i < amount; i++) - { - items.Add(new ItemData() - { - ItemId = item.ItemID, - Count = 1, - }); - } - } - else - { - items.Add(new ItemData() - { - ItemId = item.ItemID, - Count = amount, - }); - } - } - } - - - foreach (var item in items) - { - var i = Player.InventoryManager!.AddItem(item.ItemId, item.Count, false, false)!; - i.Count = item.Count; // return the all thing - list.ItemList_.Add(i.ToProto()); - } - - DatabaseHelper.Instance!.UpdateInstance(Player.InventoryManager!.Data); + list.ItemList_.Add(item.ToProto()); } return list; @@ -137,7 +96,7 @@ namespace EggLink.DanhengServer.Game.Battle var avatarType = AvatarType.AvatarFormalType; if (avatar.AssistUid != 0) { - var player = DatabaseHelper.Instance!.GetInstance(avatar.AssistUid); + var player = DatabaseHelper.GetInstance(avatar.AssistUid); if (player != null) { avatarInstance = player.Avatars!.Find(item => item.GetAvatarId() == avatar.BaseAvatarId); diff --git a/GameServer/Game/Battle/BattleManager.cs b/GameServer/Game/Battle/BattleManager.cs index 30c6ec1f..e1a835df 100644 --- a/GameServer/Game/Battle/BattleManager.cs +++ b/GameServer/Game/Battle/BattleManager.cs @@ -44,6 +44,7 @@ namespace EggLink.DanhengServer.Game.Battle Player.SceneInstance!.Entities.TryGetValue((int)entity, out var entityInstance); if (entityInstance is EntityMonster monster) { + if (targetList.Contains(monster)) continue; // avoid adding the same monster twice targetList.Add(monster); } } @@ -86,16 +87,15 @@ namespace EggLink.DanhengServer.Game.Battle { Player.LineupManager!.GetCurLineup()!.Heal(2000, false); Player.SendPacket(new PacketSyncLineupNotify(Player.LineupManager!.GetCurLineup()!)); + } else + { + Player.InventoryManager!.HandlePlaneEvent(prop.PropInfo.EventID); } Player.RogueManager!.GetRogueInstance()?.OnPropDestruct(prop); } if (targetList.Count > 0) { - if (castAvatar != null && req.SkillIndex > 0) - { - skill.OnCast(castAvatar); - } // Skill handle if (!skill.TriggerBattle) { @@ -106,6 +106,7 @@ namespace EggLink.DanhengServer.Game.Battle if (castAvatar != null) { skill.OnAttack(Player.SceneInstance!.AvatarInfo[(int)req.AttackedByEntityId], targetList); + skill.OnCast(castAvatar); } var triggerBattle = false; @@ -187,6 +188,7 @@ namespace EggLink.DanhengServer.Game.Battle BattleInstance battleInstance = new(Player, Player.LineupManager!.GetCurLineup()!, [stageConfig]) { WorldLevel = Player.Data.WorldLevel, + EventId = eventId, }; var avatarList = new List(); @@ -305,6 +307,7 @@ namespace EggLink.DanhengServer.Game.Battle break; default: teleportToAnchor = true; + if (battle.CocoonWave > 0) teleportToAnchor = false; updateStatus = false; break; } diff --git a/GameServer/Game/Battle/Skill/Action/MazeAddMazeBuff.cs b/GameServer/Game/Battle/Skill/Action/MazeAddMazeBuff.cs index 7096bfa1..41d820b8 100644 --- a/GameServer/Game/Battle/Skill/Action/MazeAddMazeBuff.cs +++ b/GameServer/Game/Battle/Skill/Action/MazeAddMazeBuff.cs @@ -24,14 +24,14 @@ namespace EggLink.DanhengServer.Game.Battle.Skill.Action public void OnCast(AvatarSceneInfo avatar) { - avatar.BuffList.Add(new SceneBuff(BuffId, 1, avatar.AvatarInfo.AvatarId, duration)); + avatar.AddBuff(new SceneBuff(BuffId, 1, avatar.AvatarInfo.AvatarId, duration)); } public void OnHitTarget(AvatarSceneInfo avatar, List entities) { foreach (var entity in entities) { - entity.BuffList.Add(new SceneBuff(BuffId, 1, avatar.AvatarInfo.AvatarId, duration)); + entity.AddBuff(new SceneBuff(BuffId, 1, avatar.AvatarInfo.AvatarId, duration)); } } } diff --git a/GameServer/Game/Friend/FriendManager.cs b/GameServer/Game/Friend/FriendManager.cs index 6ea9288b..67e49441 100644 --- a/GameServer/Game/Friend/FriendManager.cs +++ b/GameServer/Game/Friend/FriendManager.cs @@ -18,7 +18,7 @@ namespace EggLink.DanhengServer.Game.Friend public void AddFriend(int targetUid) { - var target = DatabaseHelper.Instance!.GetInstance(targetUid); + var target = DatabaseHelper.GetInstance(targetUid); if (target == null) { return; @@ -50,6 +50,7 @@ namespace EggLink.DanhengServer.Game.Friend var targetPlayer = Listener.GetActiveConnection(targetUid); targetPlayer?.SendPacket(new PacketSyncApplyFriendScNotify(Player.Data)); targetPlayer?.Player!.FriendManager!.FriendData.ReceiveApplyList.Add(Player.Uid); + DatabaseHelper.ToSaveUidList.Add(targetUid); DatabaseHelper.Instance!.UpdateInstance(FriendData); DatabaseHelper.Instance!.UpdateInstance(target); @@ -57,7 +58,7 @@ namespace EggLink.DanhengServer.Game.Friend public PlayerData? ConfirmAddFriend(int targetUid) { - var target = DatabaseHelper.Instance!.GetInstance(targetUid); + var target = DatabaseHelper.GetInstance(targetUid); if (target == null) { return null; @@ -99,7 +100,7 @@ namespace EggLink.DanhengServer.Game.Friend public void RefuseAddFriend(int targetUid) { - var target = DatabaseHelper.Instance!.GetInstance(targetUid); + var target = DatabaseHelper.GetInstance(targetUid); if (target == null) { return; @@ -115,6 +116,7 @@ namespace EggLink.DanhengServer.Game.Friend var targetPlayer = Listener.GetActiveConnection(targetUid); targetPlayer?.Player!.FriendManager!.FriendData.SendApplyList.Remove(Player.Uid); + DatabaseHelper.ToSaveUidList.Add(targetUid); DatabaseHelper.Instance!.UpdateInstance(FriendData); DatabaseHelper.Instance!.UpdateInstance(target); @@ -164,8 +166,6 @@ namespace EggLink.DanhengServer.Game.Friend } } - DatabaseHelper.Instance!.UpdateInstance(FriendData); - // receive message var recvPlayer = Listener.GetActiveConnection(recvUid)?.Player!; if (recvPlayer != null) @@ -174,7 +174,7 @@ namespace EggLink.DanhengServer.Game.Friend } else { // offline - var friendData = DatabaseHelper.Instance!.GetInstance(recvUid); + var friendData = DatabaseHelper.GetInstance(recvUid); if (friendData == null) return; // not exist maybe server profile if (!friendData.ChatHistory.TryGetValue(sendUid, out FriendChatHistory? history)) { @@ -183,7 +183,7 @@ namespace EggLink.DanhengServer.Game.Friend } history.MessageList.Add(data); - DatabaseHelper.Instance!.UpdateInstance(friendData); + DatabaseHelper.ToSaveUidList.Add(recvUid); } } @@ -216,8 +216,6 @@ namespace EggLink.DanhengServer.Game.Friend } Player.SendPacket(proto); - - DatabaseHelper.Instance!.UpdateInstance(FriendData); } public List GetHistoryInfo(int uid) diff --git a/GameServer/Game/Inventory/InventoryManager.cs b/GameServer/Game/Inventory/InventoryManager.cs index 6674c208..ccb856b7 100644 --- a/GameServer/Game/Inventory/InventoryManager.cs +++ b/GameServer/Game/Inventory/InventoryManager.cs @@ -8,6 +8,7 @@ using EggLink.DanhengServer.Server.Packet.Send.Avatar; using EggLink.DanhengServer.Server.Packet.Send.Player; using EggLink.DanhengServer.Server.Packet.Send.Scene; using EggLink.DanhengServer.Util; +using System.Collections.Generic; using System.Text.RegularExpressions; namespace EggLink.DanhengServer.Game.Inventory @@ -142,20 +143,22 @@ namespace EggLink.DanhengServer.Game.Inventory break; } + ItemData? clone = null; if (itemData != null) { + clone = itemData.Clone(); Player.SendPacket(new PacketPlayerSyncScNotify(itemData)); + clone.Count = count; if (notify) { - itemData.Count = count; // only notify the increase count - Player.SendPacket(new PacketScenePlaneEventScNotify(itemData)); + Player.SendPacket(new PacketScenePlaneEventScNotify(clone)); } } if (save) DatabaseHelper.Instance?.UpdateInstance(Data); - return itemData; + return clone ?? itemData; } public ItemData PutItem(int itemId, int count, int rank = 0, int promotion = 0, int level = 0, int exp = 0, int totalExp = 0, int mainAffix = 0, List? subAffixes = null, int uniqueId = 0) @@ -322,8 +325,88 @@ namespace EggLink.DanhengServer.Game.Inventory GameData.PlaneEventData.TryGetValue(eventId * 10 + Player.Data.WorldLevel, out var planeEvent); if (planeEvent == null) return; GameData.RewardDataData.TryGetValue(planeEvent.Reward, out var rewardData); - if (rewardData == null) return; - rewardData.GetItems().ForEach(x => AddItem(x.Item1, x.Item2)); + rewardData?.GetItems().ForEach(x => AddItem(x.Item1, x.Item2)); + + foreach (var id in planeEvent.DropList) + { + GameData.RewardDataData.TryGetValue(id, out var reward); + reward?.GetItems().ForEach(x => AddItem(x.Item1, x.Item2)); + } + } + + public List HandleMappingInfo(int mappingId, int worldLevel) + { + // calculate drops + List items = []; + GameData.MappingInfoData.TryGetValue(mappingId * 10 + worldLevel, out var mapping); + if (mapping != null) + { + foreach (var item in mapping.DropItemList) + { + var random = Random.Shared.Next(0, 101); + + if (random <= item.Chance) + { + var amount = item.ItemNum > 0 ? item.ItemNum : Random.Shared.Next(item.MinCount, item.MaxCount + 1); + + GameData.ItemConfigData.TryGetValue(item.ItemID, out var itemData); + if (itemData == null) continue; + + items.Add(new ItemData() + { + ItemId = item.ItemID, + Count = amount, + }); + } + } + + // randomize the order of the relics + var relics = mapping.DropRelicItemList.OrderBy(x => Random.Shared.Next()).ToList(); + + var relic5Count = Random.Shared.Next(worldLevel - 4, worldLevel - 2); + var relic4Count = worldLevel - 2; + foreach (var relic in relics) + { + var random = Random.Shared.Next(0, 101); + + if (random <= relic.Chance) + { + var amount = relic.ItemNum > 0 ? relic.ItemNum : Random.Shared.Next(relic.MinCount, relic.MaxCount + 1); + + GameData.ItemConfigData.TryGetValue(relic.ItemID, out var itemData); + if (itemData == null) continue; + + if (itemData.Rarity == ItemRarityEnum.SuperRare && relic5Count > 0) + { + relic5Count--; + } + else if (itemData.Rarity == ItemRarityEnum.VeryRare && relic4Count > 0) + { + relic4Count--; + } + else + { + continue; + } + + items.Add(new ItemData() + { + ItemId = relic.ItemID, + Count = 1, + }); + } + } + + foreach (var item in items) + { + var i = Player.InventoryManager!.AddItem(item.ItemId, item.Count, false, false)!; + i.Count = item.Count; // return the all thing + } + + DatabaseHelper.Instance!.UpdateInstance(Player.InventoryManager!.Data); + } + + return items; } public ItemData? ComposeItem(int composeId, int count) @@ -340,6 +423,63 @@ namespace EggLink.DanhengServer.Game.Inventory return AddItem(composeConfig.ItemID, count, false); } + public List SellItem(ItemCostData costData) + { + List items = []; + Dictionary ItemMap = []; + foreach (var cost in costData.ItemList) + { + if (cost.EquipmentUniqueId != 0) // equipment + { + var itemData = Data.EquipmentItems.Find(x => x.UniqueId == cost.EquipmentUniqueId); + if (itemData == null) continue; + RemoveItem(itemData.ItemId, 1, (int)cost.EquipmentUniqueId); + GameData.ItemConfigData.TryGetValue(itemData.ItemId, out var itemConfig); + if (itemConfig == null) continue; + foreach (var returnItem in itemConfig.ReturnItemIDList) // return items + { + if (!ItemMap.ContainsKey(returnItem.ItemID)) + { + ItemMap[returnItem.ItemID] = 0; + } + ItemMap[returnItem.ItemID] += returnItem.ItemNum; + } + } + else if (cost.RelicUniqueId != 0) // relic + { + var itemData = Data.RelicItems.Find(x => x.UniqueId == cost.RelicUniqueId); + if (itemData == null) continue; + RemoveItem(itemData.ItemId, 1, (int)cost.RelicUniqueId); + GameData.ItemConfigData.TryGetValue(itemData.ItemId, out var itemConfig); + if (itemConfig == null) continue; + foreach (var returnItem in itemConfig.ReturnItemIDList) // return items + { + if (!ItemMap.ContainsKey(returnItem.ItemID)) + { + ItemMap[returnItem.ItemID] = 0; + } + ItemMap[returnItem.ItemID] += returnItem.ItemNum; + } + } + else + { + RemoveItem((int)cost.PileItem.ItemId, (int)cost.PileItem.ItemNum); + } + } + + foreach (var itemInfo in ItemMap) + { + var item = AddItem(itemInfo.Key, itemInfo.Value, false, false); + + if (item != null) + { + items.Add(item); + } + } + + return items; + } + #region Equip public void EquipAvatar(int baseAvatarId, int equipmentUniqueId) @@ -420,6 +560,19 @@ namespace EggLink.DanhengServer.Game.Inventory Player.SendPacket(new PacketPlayerSyncScNotify(avatarData, itemData)); } + public void UnequipEquipment(int baseAvatarId) + { + var avatarData = Player.AvatarManager!.GetAvatar(baseAvatarId); + if (avatarData == null) return; + var itemData = Data.EquipmentItems.Find(x => x.UniqueId == avatarData.EquipId); + if (itemData == null) return; + itemData.EquipAvatar = 0; + avatarData.EquipId = 0; + DatabaseHelper.Instance!.UpdateInstance(Data); + DatabaseHelper.Instance!.UpdateInstance(Player.AvatarManager.AvatarData!); + Player.SendPacket(new PacketPlayerSyncScNotify(avatarData, itemData)); + } + public List LevelUpAvatar(int baseAvatarId, ItemCostData item) { var avatarData = Player.AvatarManager!.GetAvatar(baseAvatarId); diff --git a/GameServer/Game/Lineup/LineupManager.cs b/GameServer/Game/Lineup/LineupManager.cs index 871a71ff..0cfbc24a 100644 --- a/GameServer/Game/Lineup/LineupManager.cs +++ b/GameServer/Game/Lineup/LineupManager.cs @@ -323,6 +323,7 @@ namespace EggLink.DanhengServer.Game.Lineup public void GainMp(int count, bool sendPacket = true) { + count = Math.Min(Math.Max(0, count), 2); var curLineup = GetCurLineup()!; curLineup.Mp += count; curLineup.Mp = Math.Min(Math.Max(0, curLineup.Mp), 5); diff --git a/GameServer/Game/Mission/MissionManager.cs b/GameServer/Game/Mission/MissionManager.cs index 98aba49a..7b58baa8 100644 --- a/GameServer/Game/Mission/MissionManager.cs +++ b/GameServer/Game/Mission/MissionManager.cs @@ -486,25 +486,6 @@ namespace EggLink.DanhengServer.Game.Mission if (req.StageId.ToString().StartsWith(subMission.ParamInt1.ToString())) { FinishSubMission(mission); - } else - { - // may be a boss stage - foreach (var id in subMission.StageList) - { - if (req.StageId.ToString().StartsWith(id.ToString())) - { - var nextStageId = subMission.StageList.Find(x => x > id); - if (nextStageId == 0) - { - FinishSubMission(mission); - } else - { - Player.BattleManager!.StartStage(nextStageId); // need to improve - } - - break; - } - } } } } diff --git a/GameServer/Game/Player/PlayerInstance.cs b/GameServer/Game/Player/PlayerInstance.cs index 9d2cb1f1..e39ee44c 100644 --- a/GameServer/Game/Player/PlayerInstance.cs +++ b/GameServer/Game/Player/PlayerInstance.cs @@ -299,6 +299,13 @@ namespace EggLink.DanhengServer.Game.Player } } + public void OnHeartBeat() + { + OnStaminaRecover(); + + DatabaseHelper.ToSaveUidList.SafeAdd(Uid); + } + #endregion #region Scene Actions @@ -319,8 +326,6 @@ namespace EggLink.DanhengServer.Game.Player } } } - - OnStaminaRecover(); } public EntityProp? InteractProp(int propEntityId, int interactId) diff --git a/GameServer/Game/Rogue/Scene/RogueEntityLoader.cs b/GameServer/Game/Rogue/Scene/RogueEntityLoader.cs index 1d12d9bd..329a62c8 100644 --- a/GameServer/Game/Rogue/Scene/RogueEntityLoader.cs +++ b/GameServer/Game/Rogue/Scene/RogueEntityLoader.cs @@ -35,7 +35,7 @@ namespace EggLink.DanhengServer.Game.Rogue.Scene Scene.IsLoaded = true; } - public override List? LoadGroup(GroupInfo info) + public override List? LoadGroup(GroupInfo info, bool forceLoad = false) { var entityList = new List(); foreach (var npc in info.NPCList) diff --git a/GameServer/Game/Scene/Entity/EntityMonster.cs b/GameServer/Game/Scene/Entity/EntityMonster.cs index 5414910f..96f0b49b 100644 --- a/GameServer/Game/Scene/Entity/EntityMonster.cs +++ b/GameServer/Game/Scene/Entity/EntityMonster.cs @@ -53,6 +53,9 @@ namespace EggLink.DanhengServer.Game.Scene.Entity } instance.Buffs.Add(new MazeBuff(buff)); } + Scene.Player.SendPacket(new PacketSyncEntityBuffChangeListScNotify(this, BuffList)); + + BuffList.Clear(); } public int GetStageId() diff --git a/GameServer/Game/Scene/SceneBuff.cs b/GameServer/Game/Scene/SceneBuff.cs index 32efa8ba..40a86b8a 100644 --- a/GameServer/Game/Scene/SceneBuff.cs +++ b/GameServer/Game/Scene/SceneBuff.cs @@ -14,13 +14,13 @@ namespace EggLink.DanhengServer.Game.Scene public int BuffLevel { get; private set; } = buffLevel; public int OwnerAvatarId { get; private set; } = owner; - public int Duration { get; private set; } = duration * 1000; // in milliseconds + public int Duration { get; set; } = duration * 1000; // in milliseconds public long CreatedTime { get; private set; } = Extensions.GetUnixMs(); public Dictionary DynamicValues = []; public bool IsExpired() { - if (Duration == -1) + if (Duration < 0) return false; // Permanent buff return Extensions.GetUnixMs() - CreatedTime >= Duration; } diff --git a/GameServer/Game/Scene/SceneEntityLoader.cs b/GameServer/Game/Scene/SceneEntityLoader.cs index 35f5017b..8651ba0f 100644 --- a/GameServer/Game/Scene/SceneEntityLoader.cs +++ b/GameServer/Game/Scene/SceneEntityLoader.cs @@ -58,7 +58,7 @@ namespace EggLink.DanhengServer.Game.Scene if (oldGroupId.Contains(group.Id)) // check if it should be unloaded { - if (group.ForceUnloadCondition.IsTrue(Scene.Player.MissionManager!.Data, false)) + if (group.ForceUnloadCondition.IsTrue(Scene.Player.MissionManager!.Data, false) || group.UnloadCondition.IsTrue(Scene.Player.MissionManager!.Data, false)) { foreach (var entity in Scene.Entities.Values) { @@ -83,13 +83,19 @@ namespace EggLink.DanhengServer.Game.Scene } } - public virtual List? LoadGroup(GroupInfo info) + public virtual List? LoadGroup(GroupInfo info, bool forceLoad = false) { var missionData = Scene.Player.MissionManager!.Data; - if (!info.LoadCondition.IsTrue(missionData) || info.UnloadCondition.IsTrue(missionData, false) || info.ForceUnloadCondition.IsTrue(missionData, false)) + if ((!info.LoadCondition.IsTrue(missionData) || info.UnloadCondition.IsTrue(missionData, false) || info.ForceUnloadCondition.IsTrue(missionData, false)) && !forceLoad) { return null; } + + if (Scene.Entities.Values.ToList().FindIndex(x => x.GroupID == info.Id) != -1) // check if group is already loaded + { + return null; + } + var entityList = new List(); foreach (var npc in info.NPCList) { @@ -132,7 +138,7 @@ namespace EggLink.DanhengServer.Game.Scene { var group = Scene.FloorInfo?.Groups.TryGetValue(groupId, out GroupInfo? v1) == true ? v1 : null; if (group == null) { return null; } - var entities = LoadGroup(group); + var entities = LoadGroup(group, true); if (sendPacket && entities != null && entities.Count > 0) { diff --git a/GameServer/Game/Scene/SceneInstance.cs b/GameServer/Game/Scene/SceneInstance.cs index 15356722..a2aab324 100644 --- a/GameServer/Game/Scene/SceneInstance.cs +++ b/GameServer/Game/Scene/SceneInstance.cs @@ -318,6 +318,7 @@ namespace EggLink.DanhengServer.Game.Scene public List BuffList = []; public void AddBuff(SceneBuff buff) { + if (BuffList.FindIndex(x => x.BuffID == buff.BuffID) != -1) return; // already have buff BuffList.Add(buff); Player.SendPacket(new PacketSyncEntityBuffChangeListScNotify(this, buff)); } @@ -332,6 +333,9 @@ namespace EggLink.DanhengServer.Game.Scene } instance.Buffs.Add(new MazeBuff(buff)); } + Player.SendPacket(new PacketSyncEntityBuffChangeListScNotify(this, BuffList)); + + BuffList.Clear(); } public SceneEntityInfo ToProto() diff --git a/GameServer/Program/EntryPoint.cs b/GameServer/Program/EntryPoint.cs index d2515a48..3759e967 100644 --- a/GameServer/Program/EntryPoint.cs +++ b/GameServer/Program/EntryPoint.cs @@ -135,6 +135,9 @@ namespace EggLink.DanhengServer.Program private static void PerformCleanup() { Listener.Connections.Values.ToList().ForEach(x => x.Stop()); + + DatabaseHelper.SaveThread?.Interrupt(); + DatabaseHelper.SaveDatabase(); } } } diff --git a/GameServer/Server/Packet/Recv/Avatar/HandlerTakeOffEquipmentCsReq.cs b/GameServer/Server/Packet/Recv/Avatar/HandlerTakeOffEquipmentCsReq.cs new file mode 100644 index 00000000..aa6cee00 --- /dev/null +++ b/GameServer/Server/Packet/Recv/Avatar/HandlerTakeOffEquipmentCsReq.cs @@ -0,0 +1,21 @@ +using EggLink.DanhengServer.Proto; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.Server.Packet.Recv.Avatar +{ + [Opcode(CmdIds.TakeOffEquipmentCsReq)] + public class HandlerTakeOffEquipmentCsReq : Handler + { + public override void OnHandle(Connection connection, byte[] header, byte[] data) + { + var req = TakeOffEquipmentCsReq.Parser.ParseFrom(data); + connection.Player!.InventoryManager!.UnequipEquipment((int)req.DressAvatarId); + + connection.SendPacket(CmdIds.TakeOffEquipmentScRsp); + } + } +} diff --git a/GameServer/Server/Packet/Recv/Avatar/HandlerTakeOffRelicCsReq.cs b/GameServer/Server/Packet/Recv/Avatar/HandlerTakeOffRelicCsReq.cs index 83f91238..10477052 100644 --- a/GameServer/Server/Packet/Recv/Avatar/HandlerTakeOffRelicCsReq.cs +++ b/GameServer/Server/Packet/Recv/Avatar/HandlerTakeOffRelicCsReq.cs @@ -10,7 +10,7 @@ namespace EggLink.DanhengServer.Server.Packet.Recv.Avatar var req = TakeOffRelicCsReq.Parser.ParseFrom(data); foreach (var param in req.RelicTypeList) { - connection.Player!.InventoryManager!.UnequipRelic((int)req.BaseAvatarId, (int)param); + connection.Player!.InventoryManager!.UnequipRelic((int)req.DressAvatarId, (int)param); } connection.SendPacket(CmdIds.TakeOffRelicScRsp); } diff --git a/GameServer/Server/Packet/Recv/Battle/HandlerSceneCastSkillCsReq.cs b/GameServer/Server/Packet/Recv/Battle/HandlerSceneCastSkillCsReq.cs index f1f5df2f..6ed3bf40 100644 --- a/GameServer/Server/Packet/Recv/Battle/HandlerSceneCastSkillCsReq.cs +++ b/GameServer/Server/Packet/Recv/Battle/HandlerSceneCastSkillCsReq.cs @@ -31,6 +31,10 @@ namespace EggLink.DanhengServer.Server.Packet.Recv.Battle if (req.HitTargetEntityIdList.Count == 0) { // didnt hit any target + if (info != null && req.SkillIndex > 0) + { + mazeSkill.OnCast(info); + } connection.SendPacket(new PacketSceneCastSkillScRsp(req.CastEntityId)); } else diff --git a/GameServer/Server/Packet/Recv/Player/HandlerPlayerGetTokenCsReq.cs b/GameServer/Server/Packet/Recv/Player/HandlerPlayerGetTokenCsReq.cs index 34689192..d8d6b729 100644 --- a/GameServer/Server/Packet/Recv/Player/HandlerPlayerGetTokenCsReq.cs +++ b/GameServer/Server/Packet/Recv/Player/HandlerPlayerGetTokenCsReq.cs @@ -15,7 +15,7 @@ namespace EggLink.DanhengServer.Server.Packet.Recv.Player { var req = PlayerGetTokenCsReq.Parser.ParseFrom(data); - var account = DatabaseHelper.Instance?.GetInstance(long.Parse(req.AccountUid)); + var account = DatabaseHelper.Instance?.GetInstance(int.Parse(req.AccountUid)); if (account == null) { connection.SendPacket(new PacketPlayerGetTokenScRsp()); @@ -31,7 +31,7 @@ namespace EggLink.DanhengServer.Server.Packet.Recv.Player } connection.State = SessionState.WAITING_FOR_LOGIN; - var pd = DatabaseHelper.Instance?.GetInstance(long.Parse(req.AccountUid)); + var pd = DatabaseHelper.Instance?.GetInstance(int.Parse(req.AccountUid)); if (pd == null) connection.Player = new PlayerInstance(int.Parse(req.AccountUid)); else diff --git a/GameServer/Server/Packet/Recv/Player/HandlerPlayerHeartBeatCsReq.cs b/GameServer/Server/Packet/Recv/Player/HandlerPlayerHeartBeatCsReq.cs index 2cc2fadc..5a55971f 100644 --- a/GameServer/Server/Packet/Recv/Player/HandlerPlayerHeartBeatCsReq.cs +++ b/GameServer/Server/Packet/Recv/Player/HandlerPlayerHeartBeatCsReq.cs @@ -18,6 +18,8 @@ namespace EggLink.DanhengServer.Server.Packet.Recv.Player { connection.SendPacket(new PacketPlayerHeartBeatScRsp((long)req.ClientTimeMs)); } + + connection.Player?.OnHeartBeat(); } } } diff --git a/GameServer/Server/Packet/Recv/Shop/HandlerSellItemCsReq.cs b/GameServer/Server/Packet/Recv/Shop/HandlerSellItemCsReq.cs new file mode 100644 index 00000000..a0328301 --- /dev/null +++ b/GameServer/Server/Packet/Recv/Shop/HandlerSellItemCsReq.cs @@ -0,0 +1,21 @@ +using EggLink.DanhengServer.Proto; +using EggLink.DanhengServer.Server.Packet.Send.Shop; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.Server.Packet.Recv.Shop +{ + [Opcode(CmdIds.SellItemCsReq)] + public class HandlerSellItemCsReq : Handler + { + public override void OnHandle(Connection connection, byte[] header, byte[] data) + { + var req = SellItemCsReq.Parser.ParseFrom(data); + var items = connection.Player!.InventoryManager!.SellItem(req.CostData); + connection.SendPacket(new PacketSellItemScRsp(items)); + } + } +} diff --git a/GameServer/Server/Packet/Send/Battle/PacketPVEBattleResultScRsp.cs b/GameServer/Server/Packet/Send/Battle/PacketPVEBattleResultScRsp.cs index 50d5c13e..f6c48467 100644 --- a/GameServer/Server/Packet/Send/Battle/PacketPVEBattleResultScRsp.cs +++ b/GameServer/Server/Packet/Send/Battle/PacketPVEBattleResultScRsp.cs @@ -30,6 +30,7 @@ namespace EggLink.DanhengServer.Server.Packet.Send.Battle Unk1 = new(), Unk2 = new(), Unk3 = new(), + EventId = (uint)battle.EventId, }; SetData(proto); diff --git a/GameServer/Server/Packet/Send/Scene/PacketSyncEntityBuffChangeListScNotify.cs b/GameServer/Server/Packet/Send/Scene/PacketSyncEntityBuffChangeListScNotify.cs index 9d40ad70..e6c982d8 100644 --- a/GameServer/Server/Packet/Send/Scene/PacketSyncEntityBuffChangeListScNotify.cs +++ b/GameServer/Server/Packet/Send/Scene/PacketSyncEntityBuffChangeListScNotify.cs @@ -23,5 +23,23 @@ namespace EggLink.DanhengServer.Server.Packet.Send.Scene SetData(proto); } + + public PacketSyncEntityBuffChangeListScNotify(IGameEntity entity, List buffs) : base(CmdIds.SyncEntityBuffChangeListScNotify) + { + var proto = new SyncEntityBuffChangeListScNotify(); + + foreach (var buff in buffs) + { + buff.Duration = 0; + var change = new EntityBuffChange() + { + EntityId = (uint)entity.EntityID, + BuffInfo = buff.ToProto(), + }; + proto.EntityBuffChangeList.Add(change); + } + + SetData(proto); + } } } diff --git a/GameServer/Server/Packet/Send/Shop/PacketSellItemScRsp.cs b/GameServer/Server/Packet/Send/Shop/PacketSellItemScRsp.cs new file mode 100644 index 00000000..76dd4437 --- /dev/null +++ b/GameServer/Server/Packet/Send/Shop/PacketSellItemScRsp.cs @@ -0,0 +1,26 @@ +using EggLink.DanhengServer.Database.Inventory; +using EggLink.DanhengServer.Proto; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EggLink.DanhengServer.Server.Packet.Send.Shop +{ + public class PacketSellItemScRsp : BasePacket + { + public PacketSellItemScRsp(List items) : base(CmdIds.SellItemScRsp) + { + var proto = new SellItemScRsp() + { + ReturnItemList = new() + { + ItemList_ = { items.Select(x => x.ToProto())} + } + }; + + SetData(proto); + } + } +}