diff --git a/Command/Command/Cmd/CommandGiveall.cs b/Command/Command/Cmd/CommandGiveall.cs index 6d3b17cc..60f40faf 100644 --- a/Command/Command/Cmd/CommandGiveall.cs +++ b/Command/Command/Cmd/CommandGiveall.cs @@ -23,7 +23,7 @@ public class CommandGiveall : ICommand arg.CharacterArgs.TryGetValue("r", out var rankStr); arg.CharacterArgs.TryGetValue("l", out var levelStr); - rankStr ??= "1"; + rankStr ??= "0"; levelStr ??= "1"; if (!int.TryParse(rankStr, out var rank) || !int.TryParse(levelStr, out var level)) { diff --git a/Command/Command/Cmd/CommandMission.cs b/Command/Command/Cmd/CommandMission.cs index 4132de84..9a806561 100644 --- a/Command/Command/Cmd/CommandMission.cs +++ b/Command/Command/Cmd/CommandMission.cs @@ -1,5 +1,7 @@ using System.Text; +using EggLink.DanhengServer.Data.Config; using EggLink.DanhengServer.Enums.Mission; +using EggLink.DanhengServer.GameServer.Game.Mission; using EggLink.DanhengServer.Internationalization; namespace EggLink.DanhengServer.Command.Command.Cmd; @@ -57,8 +59,8 @@ public class CommandMission : ICommand return; } - var mission = arg.Target!.Player!.MissionManager!; - var runningMissions = mission.GetRunningSubMissionList(); + MissionManager mission = arg.Target!.Player!.MissionManager!; + List runningMissions = mission.GetRunningSubMissionList(); if (runningMissions.Count == 0) { await arg.SendMsg(I18NManager.Translate("Game.Command.Mission.NoRunningMissions")); @@ -68,6 +70,7 @@ public class CommandMission : ICommand await arg.SendMsg(I18NManager.Translate("Game.Command.Mission.RunningMissions")); Dictionary> missionMap = []; + //build missionMap foreach (var m in runningMissions) { if (!missionMap.TryGetValue(m.MainMissionID, out var value)) @@ -78,7 +81,24 @@ public class CommandMission : ICommand value.Add(m.ID); } + + if ((arg.BasicArgs.Count == 1 && arg.BasicArgs[0] == "-all") || mission.Data.TrackingMainMissionId == 0) + { + //Show all the missions + await ShowMissionList(mission, missionMap, arg); + } + else + { + //Only show tracking missions + Dictionary> runningMissionMap = []; + runningMissionMap[mission.Data.TrackingMainMissionId] = missionMap[mission.Data.TrackingMainMissionId]; + await ShowMissionList(mission, runningMissionMap, arg); + } + await Task.CompletedTask; + } + public async ValueTask ShowMissionList(MissionManager mission, Dictionary> missionMap, CommandArg arg) + { var possibleStuckIds = new List(); var morePossibleStuckIds = new List(); @@ -124,8 +144,6 @@ public class CommandMission : ICommand await arg.SendMsg(sb.ToString()); } - - await Task.CompletedTask; } [CommandMethod("0 reaccept")] diff --git a/Command/Command/Cmd/CommandRelic.cs b/Command/Command/Cmd/CommandRelic.cs index 8002cc69..36fcb2f3 100644 --- a/Command/Command/Cmd/CommandRelic.cs +++ b/Command/Command/Cmd/CommandRelic.cs @@ -15,7 +15,7 @@ public class CommandRelic : ICommand return; } - if (arg.BasicArgs.Count < 3) + if (arg.BasicArgs.Count < 2) { await arg.SendMsg(I18NManager.Translate("Game.Command.Notice.InvalidArguments")); return; diff --git a/Common/Configuration/ConfigContainer.cs b/Common/Configuration/ConfigContainer.cs index e17a7f93..f6422ed5 100644 --- a/Common/Configuration/ConfigContainer.cs +++ b/Common/Configuration/ConfigContainer.cs @@ -85,6 +85,12 @@ public class ServerOption public ServerProfile ServerProfile { get; set; } = new(); public bool AutoCreateUser { get; set; } = true; public bool SavePersonalDebugFile { get; set; } = false; + public int FarmingDropRate { get; set; } = 1; + + public int ValidFarmingDropRate() + { + return Math.Max(Math.Min(FarmingDropRate, 999), 1); + } } public class ServerAnnounce diff --git a/Common/Data/Excel/MappingInfoExcel.cs b/Common/Data/Excel/MappingInfoExcel.cs index b8cc180f..930c3130 100644 --- a/Common/Data/Excel/MappingInfoExcel.cs +++ b/Common/Data/Excel/MappingInfoExcel.cs @@ -1,4 +1,7 @@ -using EggLink.DanhengServer.Enums.Item; +using EggLink.DanhengServer.Database.Inventory; +using EggLink.DanhengServer.Enums.Item; +using EggLink.DanhengServer.Util; +using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -183,6 +186,129 @@ public class MappingInfoExcel : ExcelResource } } } + + public List GenerateRelicDrops() + { + var relicsMap = new Dictionary>(); + foreach (var relic in DropRelicItemList) + { + GameData.ItemConfigData.TryGetValue(relic.ItemID, out var itemData); + if (itemData == null) continue; + switch (itemData.Rarity) + { + case ItemRarityEnum.NotNormal: + AddRelicToMap(relic, 2, relicsMap); + break; + case ItemRarityEnum.Rare: + AddRelicToMap(relic, 3, relicsMap); + break; + case ItemRarityEnum.VeryRare: + AddRelicToMap(relic, 4, relicsMap); + break; + case ItemRarityEnum.SuperRare: + AddRelicToMap(relic, 5, relicsMap); + break; + default: + continue; + } + } + + List drops = []; + // Add higher rarity relics first + for (var rarity = 5; rarity >= 2; rarity--) + { + var count = GetRelicCountByWorldLevel(rarity) * + ConfigManager.Config.ServerOption.ValidFarmingDropRate(); + if (count <= 0) continue; + if (!relicsMap.TryGetValue(rarity, out var value)) continue; + if (value.IsNullOrEmpty()) continue; + while (count > 0) + { + var relic = value.RandomElement(); + drops.Add(new ItemData + { + ItemId = relic.ItemID, + Count = 1 + }); + count--; + } + } + + return drops; + } + + private void AddRelicToMap(MappingInfoItem relic, int rarity, Dictionary> relicsMap) + { + if (relicsMap.TryGetValue(rarity, out var value)) + { + value.Add(relic); + } + else + { + relicsMap.Add(rarity, [relic]); + } + } + + private int GetRelicCountByWorldLevel(int rarity) + { + return WorldLevel switch + { + 1 => rarity switch + { + 2 => 6, + 3 => 3, + 4 => 1, + 5 => 0, + _ => 0 + }, + 2 => rarity switch + { + 2 => 2, + 3 => 4, + 4 => 2 + LuckyRelicDropped(), + 5 => 0, + _ => 0 + }, + 3 => rarity switch + { + 2 => 0, + 3 => 4, + 4 => 2, + 5 => 1, + _ => 0 + }, + 4 => rarity switch + { + 2 => 0, + 3 => 3, + 4 => 2 + LuckyRelicDropped(), + 5 => 1 + LuckyRelicDropped(), + _ => 0 + }, + 5 => rarity switch + { + 2 => 0, + 3 => 1 + LuckyRelicDropped(), + 4 => 3, + 5 => 2, + _ => 0 + }, + 6 => rarity switch + { + 2 => 0, + 3 => 0, + 4 => 5, + 5 => 2 + LuckyRelicDropped(), + _ => 0 + }, + _ => 0 + }; + } + + private int LuckyRelicDropped() + { + return Random.Shared.Next(100) < 25 ? 1 : 0; + } } public class MappingInfoItem diff --git a/Common/Data/Excel/MonsterDropExcel.cs b/Common/Data/Excel/MonsterDropExcel.cs index a39688fd..4a95647a 100644 --- a/Common/Data/Excel/MonsterDropExcel.cs +++ b/Common/Data/Excel/MonsterDropExcel.cs @@ -1,4 +1,5 @@ using EggLink.DanhengServer.Database.Inventory; +using EggLink.DanhengServer.Util; using Newtonsoft.Json; namespace EggLink.DanhengServer.Data.Excel; @@ -54,7 +55,7 @@ public class MonsterDropExcel : ExcelResource result.Add(new ItemData { ItemId = item.ItemID, - Count = count + Count = count * ConfigManager.Config.ServerOption.ValidFarmingDropRate() }); } diff --git a/Common/Data/Excel/RelicMainAffixConfigExcel.cs b/Common/Data/Excel/RelicMainAffixConfigExcel.cs index a3225de8..aa92c2c9 100644 --- a/Common/Data/Excel/RelicMainAffixConfigExcel.cs +++ b/Common/Data/Excel/RelicMainAffixConfigExcel.cs @@ -6,7 +6,6 @@ public class RelicMainAffixConfigExcel : ExcelResource public int GroupID { get; set; } public int AffixID { get; set; } - public bool IsAvailable { get; set; } public string? Property { get; set; } public override int GetId() diff --git a/Common/Database/Inventory/InventoryData.cs b/Common/Database/Inventory/InventoryData.cs index 46948dcc..2fa78031 100644 --- a/Common/Database/Inventory/InventoryData.cs +++ b/Common/Database/Inventory/InventoryData.cs @@ -1,5 +1,6 @@ using EggLink.DanhengServer.Data; using EggLink.DanhengServer.Data.Excel; +using EggLink.DanhengServer.Enums.Item; using EggLink.DanhengServer.Proto; using EggLink.DanhengServer.Util; using SqlSugar; @@ -60,15 +61,22 @@ public class ItemData public void AddRandomRelicSubAffix(int count = 1) { + // Avoid illegal count of relic sub affixes + if (count is < 1 or > 4 || SubAffixes.Count >= 4) return; GameData.RelicConfigData.TryGetValue(ItemId, out var config); if (config == null) return; GameData.RelicSubAffixData.TryGetValue(config.SubAffixGroup, out var affixes); if (affixes == null) return; + // Avoid same property on both main affix and sub affixes + GameData.RelicMainAffixData.TryGetValue(config.MainAffixGroup, out var mainAffixes); + if (mainAffixes == null) return; + var mainProperty = mainAffixes[MainAffix].Property; + var rollPool = new List(); foreach (var affix in affixes.Values) - if (SubAffixes.Find(x => x.Id == affix.AffixID) == null) + if (affix.Property != mainProperty & SubAffixes.Find(x => x.Id == affix.AffixID) == null) rollPool.Add(affix); for (var i = 0; i < count; i++) @@ -77,11 +85,68 @@ public class ItemData ItemSubAffix subAffix = new(affixConfig, 1); SubAffixes.Add(subAffix); rollPool.Remove(affixConfig); + if (SubAffixes.Count >= 4) break; } } + /** + * Init relic sub affixes based on rarity + * 20% chance to get one more affix + * r3 1-2 + * r4 2-3 + * r5 3-4 + */ + public void InitRandomRelicSubAffixesByRarity(ItemRarityEnum rarity = ItemRarityEnum.Unknown) + { + if (rarity == ItemRarityEnum.Unknown) + { + GameData.ItemConfigData.TryGetValue(ItemId, out var config); + if (config == null) return; + rarity = config.Rarity; + } + + int initSubAffixesCount; + switch (rarity) + { + case ItemRarityEnum.Rare: + initSubAffixesCount = 1 + LuckyRelicSubAffixCount(); + break; + case ItemRarityEnum.VeryRare: + initSubAffixesCount = 2 + LuckyRelicSubAffixCount(); + break; + case ItemRarityEnum.SuperRare: + initSubAffixesCount = 3 + LuckyRelicSubAffixCount(); + break; + default: + return; + } + + AddRandomRelicSubAffix(initSubAffixesCount); + } + + public int LuckyRelicSubAffixCount() + { + return Random.Shared.Next(100) < 20 ? 1 : 0; + } + #endregion + public int CalcTotalExpGained() + { + if (Level <= 0) return Exp; + GameData.RelicConfigData.TryGetValue(ItemId, out var costExcel); + if (costExcel == null) return 0; + var exp = 0; + for (var i = 0; i < Level; i++) + { + GameData.RelicExpTypeData.TryGetValue(costExcel.ExpType * 100 + i, out var typeExcel); + if (typeExcel != null) + exp += typeExcel.Exp; + } + + return exp + Exp; + } + #region Serialization public Material ToMaterialProto() diff --git a/Common/Enums/Avatar/MultiPathAvatarTypeEnum.cs b/Common/Enums/Avatar/MultiPathAvatarTypeEnum.cs index 20008266..b869c1c0 100644 --- a/Common/Enums/Avatar/MultiPathAvatarTypeEnum.cs +++ b/Common/Enums/Avatar/MultiPathAvatarTypeEnum.cs @@ -5,6 +5,7 @@ public enum MultiPathAvatarTypeEnum Warrior = 8001, Knight = 8003, Shaman = 8005, + Memory = 8007, Mar_7thKnight = 1001, Mar_7thRogue = 1224 -} \ No newline at end of file +} diff --git a/Common/Internationalization/Message/LanguageCHS.cs b/Common/Internationalization/Message/LanguageCHS.cs index e72d95fe..fbf8d6d5 100644 --- a/Common/Internationalization/Message/LanguageCHS.cs +++ b/Common/Internationalization/Message/LanguageCHS.cs @@ -314,11 +314,13 @@ public class MissionTextCHS public string Desc => "管理玩家的任务\n" + "使用 pass 完成当前正在进行的所有任务,此命令易造成严重卡顿,请尽量使用 /mission finish 替代\n" + - "使用 running 获取正在进行的任务以及可能卡住的任务,使用后可能会出现较长任务列表,请注意甄别\n" + - "使用 reaccept 可重新进行指定主任务,请浏览 handbook 来获取主任务ID"; + "使用 finish [子任务ID] 完成指定子任务,请浏览 handbook 来获取子任务ID\n" + + "使用 finishmain [主任务ID] 完成指定主任务,请浏览 handbook 来获取主任务ID\n" + + "使用 running <-all> 获取正在追踪的任务,增加'-all'则显示所有正在进行的任务以及可能卡住的任务,使用后可能会出现较长任务列表,请注意甄别\n" + + "使用 reaccept [主任务ID] 可重新进行指定主任务,请浏览 handbook 来获取主任务ID"; public string Usage => - "用法:/mission pass\n\n用法:/mission finish [子任务ID]\n\n用法:/mission running\n\n用法:/mission reaccept [主任务ID]"; + "用法:/mission pass\n\n用法:/mission finish [子任务ID]\n\n用法:/mission running <-all>\n\n用法:/mission reaccept [主任务ID]\n\n用法:/mission finishmain [主任务ID]"; public string AllMissionsFinished => "所有任务已完成!"; public string AllRunningMissionsFinished => "共 {0} 个进行中的任务已完成!"; @@ -435,7 +437,7 @@ public class RaidTextCHS public class AccountTextCHS { public string Desc => "创建账号\n注意:此命令未经测试,请谨慎使用!"; - public string Usage => "用法:/account create <用户名>"; + public string Usage => "用法:/account create [用户名]"; public string InvalidUid => "无效UID参数!"; public string CreateError => "出现内部错误 {0} "; public string CreateSuccess => "新账号 {0} 创建成功!"; @@ -450,7 +452,7 @@ public class AccountTextCHS public class UnstuckTextCHS { public string Desc => "将玩家传送回默认场景"; - public string Usage => "用法:/unstuck "; + public string Usage => "用法:/unstuck [UID]"; public string UnstuckSuccess => "已成功将该玩家传送回默认场景"; public string UidNotExist => "该UID不存在!"; public string PlayerIsOnline => "该玩家目前在线上!"; @@ -462,7 +464,7 @@ public class UnstuckTextCHS public class SetlevelTextCHS { public string Desc => "设定玩家等级"; - public string Usage => "用法:/setlevel <等级>"; + public string Usage => "用法:/setlevel [等级]"; public string SetlevelSuccess => "等级设定成功!"; } diff --git a/Common/Internationalization/Message/LanguageEN.cs b/Common/Internationalization/Message/LanguageEN.cs index dafd7207..5a1da6a6 100644 --- a/Common/Internationalization/Message/LanguageEN.cs +++ b/Common/Internationalization/Message/LanguageEN.cs @@ -260,7 +260,7 @@ public class AvatarTextEN public class GiveTextEN { public string Desc => "Give player items, item id can be avatar id, but cant set level, talent, rank"; - public string Usage => "Usage: /give l x r"; + public string Usage => "Usage: /give [item ID] l x r"; public string ItemNotFound => "Item not found!"; public string GiveItem => "Gave @{0} {1} item(s) {2}"; } @@ -321,7 +321,9 @@ public class MissionTextEN public string Desc => "Manage player's missions\n" + "Use 'pass' to finish all running mission, this command will cause severe lagging, please use '/mission finish' instead\n" + - "Use 'running' to get the running mission and possible stuck missions, after use, a longer mission list may appear, please note that\n" + + "Use 'finish [SubMissionID]' to finish certain sub-mission,please find sub-mission id in handbook\n" + + "Use 'finishmain [MainMissionID]' to finish certain main mission,please find main mission id in handbook\n" + + "Use 'running <-all>' to get the tracking mission, adding '-all' shows all running mission and possible stuck missions, after use, a longer mission list may appear, please note that\n" + "Use 'reaccept' to re-accept given main mission, please find main mission id in handbook"; public string Usage => diff --git a/Common/Util/GameConstants.cs b/Common/Util/GameConstants.cs index 63c7bb25..b53759f2 100644 --- a/Common/Util/GameConstants.cs +++ b/Common/Util/GameConstants.cs @@ -3,7 +3,7 @@ public static class GameConstants { public const int INVENTORY_MAX_EQUIPMENT = 1500; - public const int INVENTORY_MAX_RELIC = 1500; + public const int INVENTORY_MAX_RELIC = 2000; public const int INVENTORY_MAX_MATERIAL = 2000; public const int MAX_LINEUP_COUNT = 9; diff --git a/Common/Util/UtilTools.cs b/Common/Util/UtilTools.cs index a0c0819a..3d77c5ac 100644 --- a/Common/Util/UtilTools.cs +++ b/Common/Util/UtilTools.cs @@ -10,7 +10,7 @@ public static class UtilTools GameData.RelicMainAffixData.TryGetValue(groupId, out var affixes); if (affixes == null) return 0; List affixList = []; - affixList.AddRange(from affix in affixes.Values where affix.IsAvailable select affix.AffixID); + affixList.AddRange(from affix in affixes.Values select affix.AffixID); return affixList.Count == 0 ? 0 : affixList.RandomElement(); } diff --git a/GameServer/Game/Challenge/ChallengeInstance.cs b/GameServer/Game/Challenge/ChallengeInstance.cs index 3958e5d4..461fd5fd 100644 --- a/GameServer/Game/Challenge/ChallengeInstance.cs +++ b/GameServer/Game/Challenge/ChallengeInstance.cs @@ -296,7 +296,7 @@ public class ChallengeInstance if (monsters == 0) await AdvanceStage(req); // Calculate rounds left - if (IsStory()) RoundsLeft = (int)Math.Min(Math.Max(RoundsLeft - req.Stt.RoundCnt, 1), RoundsLeft); + if (!(IsStory() && IsBoss())) RoundsLeft = (int)Math.Min(Math.Max(RoundsLeft - req.Stt.RoundCnt, 1), RoundsLeft); // Set saved technique points (This will be restored if the player resets the challenge) SavedMp = Player.LineupManager!.GetCurLineup()!.Mp; diff --git a/GameServer/Game/Inventory/InventoryManager.cs b/GameServer/Game/Inventory/InventoryManager.cs index d59ca7b9..e6e74bfe 100644 --- a/GameServer/Game/Inventory/InventoryManager.cs +++ b/GameServer/Game/Inventory/InventoryManager.cs @@ -1,4 +1,5 @@ -using EggLink.DanhengServer.Data; +using System.Collections.Frozen; +using EggLink.DanhengServer.Data; using EggLink.DanhengServer.Database; using EggLink.DanhengServer.Database.Inventory; using EggLink.DanhengServer.Enums.Item; @@ -11,6 +12,8 @@ using EggLink.DanhengServer.GameServer.Server.Packet.Send.PlayerSync; using EggLink.DanhengServer.GameServer.Server.Packet.Send.Scene; using EggLink.DanhengServer.Proto; using EggLink.DanhengServer.Util; +using Google.Protobuf.Collections; +using Microsoft.Net.Http.Headers; namespace EggLink.DanhengServer.GameServer.Game.Inventory; @@ -54,7 +57,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) switch (itemConfig.ItemMainType) { case ItemMainTypeEnum.Equipment: - if (Data.RelicItems.Count + 1 > GameConstants.INVENTORY_MAX_EQUIPMENT) // get the max equipment + if (Data.EquipmentItems.Count + 1 > GameConstants.INVENTORY_MAX_EQUIPMENT) // get the max equipment { await Player.SendPacket(new PacketRetcodeNotify(Retcode.RetEquipmentExceedLimit)); break; @@ -88,9 +91,8 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) }; break; case ItemMainTypeEnum.Relic: - if (Data.RelicItems.Count + 1 > - GameConstants - .INVENTORY_MAX_RELIC) // get the max relic, i dont think one player can have more than max count of relic until i see a player get 50000 relic and the client crashed :( + //I dont think one player can have more than max count of relic until i see a player get 50000 relic and the client crashed :( + if (Data.RelicItems.Count + 1 > GameConstants.INVENTORY_MAX_RELIC) // get the max relic { await Player.SendPacket(new PacketRetcodeNotify(Retcode.RetRelicExceedLimit)); break; @@ -98,7 +100,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) var item = await PutItem(itemId, 1, 1, level: 0, uniqueId: ++Data.NextUniqueId); item.AddRandomRelicMainAffix(); - item.AddRandomRelicSubAffix(3); + item.InitRandomRelicSubAffixesByRarity(); Data.RelicItems.Find(x => x.UniqueId == item.UniqueId)!.SubAffixes = item.SubAffixes; itemData = item; break; @@ -219,7 +221,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) Data.MaterialItems.Add(item); break; case ItemMainTypeEnum.Equipment: - if (Data.RelicItems.Count + 1 > GameConstants.INVENTORY_MAX_EQUIPMENT) + if (Data.EquipmentItems.Count + 1 > GameConstants.INVENTORY_MAX_EQUIPMENT) { await Player.SendPacket(new PacketRetcodeNotify(Retcode.RetEquipmentExceedLimit)); return item; @@ -458,43 +460,20 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) items.Add(new ItemData { ItemId = item.ItemID, - Count = amount + Count = amount * (item.ItemID == 22 + ? 1 + : ConfigManager.Config.ServerOption.ValidFarmingDropRate()) }); } } - // 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 - }); - } - } + // Generate relics + var relicDrops = mapping.GenerateRelicDrops(); + + // Let AddItem notify relics count exceeding limit + items.AddRange(Data.RelicItems.Count + relicDrops.Count - 1 > GameConstants.INVENTORY_MAX_RELIC + ? relicDrops[..(GameConstants.INVENTORY_MAX_RELIC - Data.RelicItems.Count + 1)] + : relicDrops); foreach (var item in items) { @@ -620,42 +599,73 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) await RemoveItem(2, (int)(composeConfig.CoinCost * req.Count)); + var relicId = (int)req.ComposeRelicId; + GameData.RelicConfigData.TryGetValue(relicId, out var itemConfig); + GameData.RelicSubAffixData.TryGetValue(itemConfig!.SubAffixGroup, out var subAffixConfig); + // Add relic - var subAffixes = req.SubAffixIdList.Select(subId => ((int)subId, 1)).ToList(); + var mainAffix = (int)req.MainAffixId; + var itemData = new ItemData + { + ItemId = relicId, + Level = 0, + UniqueId = ++Data.NextUniqueId, + MainAffix = mainAffix, + SubAffixes = req.SubAffixIdList.Select(subId => new ItemSubAffix(subAffixConfig![(int)subId], 1)).ToList(), + Count = 1 + }; + if (mainAffix == 0) itemData.AddRandomRelicMainAffix(); + itemData.AddRandomRelicSubAffix(3 - itemData.SubAffixes.Count + itemData.LuckyRelicSubAffixCount()); + await AddItem(itemData, notify: false); - var (_, relic) = await HandleRelic( - (int)req.ComposeRelicId, ++Data.NextUniqueId, 0, (int)req.MainAffixId, subAffixes); - - return relic; + return itemData; } public async ValueTask ReforgeRelic(int uniqueId) { - var relic = Data.RelicItems.FirstOrDefault(x => x.UniqueId == uniqueId); - await RemoveItem(relic!.ItemId, 1, uniqueId, false); + var relic = Data.RelicItems.First(x => x.UniqueId == uniqueId); var totalCount = 0; var subAffixes = new List<(int, int)>(); foreach (var sub in relic.SubAffixes) { - totalCount += sub.Count; - subAffixes.Add((sub.Id, 0)); + totalCount = totalCount + sub.Count - 1; + subAffixes.Add((sub.Id, 1)); } - var remainCount = totalCount; - for (var i = 0; i < subAffixes.Count - 1; i++) + while (totalCount > 0) { - var count = new Random().Next(1, remainCount - (subAffixes.Count - i - 1)); - subAffixes[i] = (subAffixes[i].Item1, count); - remainCount -= count; + var idx = Random.Shared.Next(subAffixes.Count); + var cur = subAffixes[idx]; + subAffixes[idx] = (cur.Item1, cur.Item2 + 1); + totalCount--; + } + + GameData.RelicConfigData.TryGetValue(relic.ItemId, out var itemConfig); + GameData.RelicSubAffixData.TryGetValue(itemConfig!.SubAffixGroup, out var subAffixConfig); + + for (var i = 0; i < subAffixes.Count; i++) + { + var (subId, subLevel) = subAffixes[i]; + subAffixConfig!.TryGetValue(subId, out var subAffix); + var aff = new ItemSubAffix(subAffix!, subLevel); + relic.SubAffixes[i] = aff; + } + + if (relic.EquipAvatar > 0) + { + var avatar = Player.AvatarManager!.GetAvatar(relic.EquipAvatar); + await Player.SendPacket(new PacketPlayerSyncScNotify(avatar!, relic)); + } + else + { + await Player.SendPacket(new PacketPlayerSyncScNotify(relic)); } - subAffixes[^1] = (subAffixes[^1].Item1, remainCount); - await HandleRelic(relic.ItemId, uniqueId, relic.Level, relic.MainAffix, subAffixes); await RemoveItem(238, 1); } - public async ValueTask> SellItem(ItemCostData costData) + public async ValueTask> SellItem(ItemCostData costData, bool toMaterial = false) { List items = []; Dictionary itemMap = []; @@ -682,10 +692,46 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) removeItems.Add((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 (itemConfig.Rarity != ItemRarityEnum.SuperRare || toMaterial) { - if (!itemMap.ContainsKey(returnItem.ItemID)) itemMap[returnItem.ItemID] = 0; - itemMap[returnItem.ItemID] += returnItem.ItemNum; + foreach (var returnItem in itemConfig.ReturnItemIDList) // basic return items + { + itemMap.TryAdd(returnItem.ItemID, 0); + itemMap[returnItem.ItemID] += returnItem.ItemNum; + } + + var expReturned = (int)(itemData.CalcTotalExpGained() * 0.8); + + var credit = (int)(expReturned * 1.5); + if (credit > 0) + { + itemMap.TryAdd(2, 0); + itemMap[2] += (int)(expReturned * 1.5); + } + + var lostGoldFragCnt = expReturned / 500; + if (lostGoldFragCnt > 0) + { + itemMap.TryAdd(232, 0); + itemMap[232] += lostGoldFragCnt; + } + + var lostGoldLightdust = expReturned % 500 / 100; + if (lostGoldLightdust > 0) + { + itemMap.TryAdd(231, 0); + itemMap[231] += lostGoldLightdust; + } + } + else + { + var expGained = itemData.CalcTotalExpGained(); + var remainsCnt = (int)(10 + expGained * 0.005144); + if (remainsCnt > 0) + { + itemMap.TryAdd(235, 0); + itemMap[235] += remainsCnt; + } } } else @@ -1294,4 +1340,80 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player) } #endregion + + #region Mark + public async ValueTask LockItems(RepeatedField ids, bool isLocked, ItemMainTypeEnum itemType = ItemMainTypeEnum.Unknown) + { + List targetItems; + switch (itemType) + { + case ItemMainTypeEnum.Equipment: + targetItems = Data.EquipmentItems; + break; + case ItemMainTypeEnum.Relic: + targetItems = Data.RelicItems; + break; + case ItemMainTypeEnum.Unknown: + case ItemMainTypeEnum.Virtual: + case ItemMainTypeEnum.AvatarCard: + case ItemMainTypeEnum.Usable: + case ItemMainTypeEnum.Material: + case ItemMainTypeEnum.Mission: + case ItemMainTypeEnum.Display: + case ItemMainTypeEnum.Pet: + default: + return false; + } + if (targetItems.Count == 0) return false; + var idPool = ids.ToList().ConvertAll(x => (int)x).ToFrozenSet(); + var items = new List(); + foreach (var x in targetItems) + { + if (x.Discarded || !idPool.Contains(x.UniqueId)) continue; + x.Locked = isLocked; + items.Add(x); + } + + if (items.Count <= 0) return false; + await player.SendPacket(new PacketPlayerSyncScNotify(items)); + return true; + } + + public async ValueTask DiscardItems(RepeatedField ids, bool discarded, ItemMainTypeEnum itemType = ItemMainTypeEnum.Unknown) + { + List targetItems; + switch (itemType) + { + case ItemMainTypeEnum.Equipment: + targetItems = Data.EquipmentItems; + break; + case ItemMainTypeEnum.Relic: + targetItems = Data.RelicItems; + break; + case ItemMainTypeEnum.Unknown: + case ItemMainTypeEnum.Virtual: + case ItemMainTypeEnum.AvatarCard: + case ItemMainTypeEnum.Usable: + case ItemMainTypeEnum.Material: + case ItemMainTypeEnum.Mission: + case ItemMainTypeEnum.Display: + case ItemMainTypeEnum.Pet: + default: + return false; + } + if (targetItems.Count == 0) return false; + var idPool = ids.ToList().ConvertAll(x => (int)x).ToFrozenSet(); + var items = new List(); + foreach (var x in targetItems) + { + if (x.Locked || !idPool.Contains(x.UniqueId)) continue; + x.Discarded = discarded; + items.Add(x); + } + + if (items.Count <= 0) return false; + await player.SendPacket(new PacketPlayerSyncScNotify(items)); + return true; + } + #endregion } \ No newline at end of file diff --git a/GameServer/Game/Mission/FinishType/Handler/MissionHandlerMatchThreeFinishLevel.cs b/GameServer/Game/Mission/FinishType/Handler/MissionHandlerMatchThreeFinishLevel.cs new file mode 100644 index 00000000..4bdb05f8 --- /dev/null +++ b/GameServer/Game/Mission/FinishType/Handler/MissionHandlerMatchThreeFinishLevel.cs @@ -0,0 +1,25 @@ +using EggLink.DanhengServer.Data.Config; +using EggLink.DanhengServer.Data.Excel; +using EggLink.DanhengServer.Enums.Mission; +using EggLink.DanhengServer.GameServer.Game.Player; +using EggLink.DanhengServer.Proto; +using EggLink.DanhengServer.Util; + +namespace EggLink.DanhengServer.GameServer.Game.Mission.FinishType.Handler; + +[MissionFinishType(MissionFinishTypeEnum.MatchThreeFinishLevel)] +public class MissionHandlerMatchThreeFinishLevel : MissionFinishTypeHandler +{ + public override async ValueTask HandleMissionFinishType(PlayerInstance player, SubMissionInfo info, object? arg) + { + if(arg is MatchThreeLevelEndCsReq req) + if(req.LevelId == info.ParamInt1) + await player.MissionManager!.FinishSubMission(info.ID); + } + + public override async ValueTask HandleQuestFinishType(PlayerInstance player, QuestDataExcel quest, + FinishWayExcel excel, object? arg) + { + await ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/GameServer/Game/Player/PlayerInstance.cs b/GameServer/Game/Player/PlayerInstance.cs index d54f3f5e..e90c1eef 100644 --- a/GameServer/Game/Player/PlayerInstance.cs +++ b/GameServer/Game/Player/PlayerInstance.cs @@ -245,6 +245,14 @@ public class PlayerInstance(PlayerData data) } } + foreach (var relic in InventoryManager.Data.RelicItems) + { + if (relic.MainAffix != 0) continue; // fix relic main affix + + var groupId = GameData.RelicConfigData.GetValueOrDefault(relic.ItemId)?.MainAffixGroup ?? 0; + relic.MainAffix = UtilTools.GetRandomRelicMainAffix(groupId); + } + foreach (var avatar in AvatarManager?.AvatarData.Avatars ?? []) foreach (var skill in avatar.GetSkillTree()) { diff --git a/GameServer/Server/Packet/Recv/Item/HandlerComposeSelectedRelicCsReq.cs b/GameServer/Server/Packet/Recv/Item/HandlerComposeSelectedRelicCsReq.cs index f8dacf98..b79d3b45 100644 --- a/GameServer/Server/Packet/Recv/Item/HandlerComposeSelectedRelicCsReq.cs +++ b/GameServer/Server/Packet/Recv/Item/HandlerComposeSelectedRelicCsReq.cs @@ -1,6 +1,7 @@ using EggLink.DanhengServer.GameServer.Server.Packet.Send.Item; using EggLink.DanhengServer.Kcp; using EggLink.DanhengServer.Proto; +using EggLink.DanhengServer.Util; namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Item; @@ -11,10 +12,15 @@ public class HandlerComposeSelectedRelicCsReq : Handler { var req = ComposeSelectedRelicCsReq.Parser.ParseFrom(data); var player = connection.Player!; - var item = await player.InventoryManager!.ComposeRelic(req); + if (player.InventoryManager!.Data.RelicItems.Count >= GameConstants.INVENTORY_MAX_RELIC) + { + await connection.SendPacket(new PacketComposeSelectedRelicScRsp(req.ComposeId, Retcode.RetRelicExceedLimit)); + return; + } + var item = await player.InventoryManager.ComposeRelic(req); if (item == null) { - await connection.SendPacket(new PacketComposeSelectedRelicScRsp()); + await connection.SendPacket(new PacketComposeSelectedRelicScRsp(req.ComposeId)); return; } diff --git a/GameServer/Server/Packet/Recv/Item/HandlerDestroyItemCsReq.cs b/GameServer/Server/Packet/Recv/Item/HandlerDestroyItemCsReq.cs index d4bb0249..b01ed3ca 100644 --- a/GameServer/Server/Packet/Recv/Item/HandlerDestroyItemCsReq.cs +++ b/GameServer/Server/Packet/Recv/Item/HandlerDestroyItemCsReq.cs @@ -10,7 +10,7 @@ public class HandlerDestroyItemCsReq : Handler { var req = DestroyItemCsReq.Parser.ParseFrom(data); - await connection.Player!.InventoryManager!.RemoveItem((int)req.ItemCount, (int)req.ItemCount); + await connection.Player!.InventoryManager!.RemoveItem((int)req.ItemId, (int)req.ItemCount); await connection.SendPacket(CmdIds.DestroyItemScRsp); } } \ No newline at end of file diff --git a/GameServer/Server/Packet/Recv/Item/HandlerDiscardRelicCsReq.cs b/GameServer/Server/Packet/Recv/Item/HandlerDiscardRelicCsReq.cs new file mode 100644 index 00000000..69dc5c6c --- /dev/null +++ b/GameServer/Server/Packet/Recv/Item/HandlerDiscardRelicCsReq.cs @@ -0,0 +1,19 @@ +using EggLink.DanhengServer.Enums.Item; +using EggLink.DanhengServer.GameServer.Server.Packet.Send.Item; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Item; + +[Opcode(CmdIds.DiscardRelicCsReq)] +public class HandlerDiscardRelicCsReq : Handler +{ + public override async Task OnHandle(Connection connection, byte[] header, byte[] data) + { + var req = DiscardRelicCsReq.Parser.ParseFrom(data); + var result = + await connection.Player!.InventoryManager!.DiscardItems(req.RelicUniqueIdList, req.IsDiscard, + ItemMainTypeEnum.Relic); + await connection.SendPacket(new PacketDiscardRelicScRsp(result, req.IsDiscard)); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Recv/Item/HandlerLockEquipmentCsReq.cs b/GameServer/Server/Packet/Recv/Item/HandlerLockEquipmentCsReq.cs new file mode 100644 index 00000000..c9d9cc14 --- /dev/null +++ b/GameServer/Server/Packet/Recv/Item/HandlerLockEquipmentCsReq.cs @@ -0,0 +1,19 @@ +using EggLink.DanhengServer.Enums.Item; +using EggLink.DanhengServer.GameServer.Server.Packet.Send.Item; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Item; + +[Opcode(CmdIds.LockEquipmentCsReq)] +public class HandlerLockEquipmentCsReq : Handler +{ + public override async Task OnHandle(Connection connection, byte[] header, byte[] data) + { + var req = LockEquipmentCsReq.Parser.ParseFrom(data); + var result = + await connection.Player!.InventoryManager!.LockItems(req.EquipmentIdList, req.IsProtected, + ItemMainTypeEnum.Equipment); + await connection.SendPacket(new PacketLockEquipmentScRsp(result)); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Recv/Item/HandlerLockRelicCsReq.cs b/GameServer/Server/Packet/Recv/Item/HandlerLockRelicCsReq.cs new file mode 100644 index 00000000..4d735d52 --- /dev/null +++ b/GameServer/Server/Packet/Recv/Item/HandlerLockRelicCsReq.cs @@ -0,0 +1,19 @@ +using EggLink.DanhengServer.Enums.Item; +using EggLink.DanhengServer.GameServer.Server.Packet.Send.Item; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Item; + +[Opcode(CmdIds.LockRelicCsReq)] +public class HandlerLockRelicCsReq : Handler +{ + public override async Task OnHandle(Connection connection, byte[] header, byte[] data) + { + var req = LockRelicCsReq.Parser.ParseFrom(data); + var result = + await connection.Player!.InventoryManager!.LockItems(req.RelicUniqueIdList, req.IsProtected, + ItemMainTypeEnum.Relic); + await connection.SendPacket(new PacketLockRelicScRsp(result)); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Recv/Item/HandlerSellItemCsReq.cs b/GameServer/Server/Packet/Recv/Item/HandlerSellItemCsReq.cs index 3475f8f0..7583ffc1 100644 --- a/GameServer/Server/Packet/Recv/Item/HandlerSellItemCsReq.cs +++ b/GameServer/Server/Packet/Recv/Item/HandlerSellItemCsReq.cs @@ -10,7 +10,7 @@ public class HandlerSellItemCsReq : Handler public override async Task OnHandle(Connection connection, byte[] header, byte[] data) { var req = SellItemCsReq.Parser.ParseFrom(data); - var items = await connection.Player!.InventoryManager!.SellItem(req.CostData); + var items = await connection.Player!.InventoryManager!.SellItem(req.CostData, req.ToMaterial); await connection.SendPacket(new PacketSellItemScRsp(items)); } } \ No newline at end of file diff --git a/GameServer/Server/Packet/Recv/MatchThreeModule/HandlerMatchThreeLevelEndCsReq.cs b/GameServer/Server/Packet/Recv/MatchThreeModule/HandlerMatchThreeLevelEndCsReq.cs index 3e80b515..b397dd5f 100644 --- a/GameServer/Server/Packet/Recv/MatchThreeModule/HandlerMatchThreeLevelEndCsReq.cs +++ b/GameServer/Server/Packet/Recv/MatchThreeModule/HandlerMatchThreeLevelEndCsReq.cs @@ -1,4 +1,5 @@ -using EggLink.DanhengServer.GameServer.Server.Packet.Send.MatchThreeModule; +using EggLink.DanhengServer.Enums.Mission; +using EggLink.DanhengServer.GameServer.Server.Packet.Send.MatchThreeModule; using EggLink.DanhengServer.Kcp; using EggLink.DanhengServer.Proto; @@ -11,6 +12,8 @@ public class HandlerMatchThreeLevelEndCsReq : Handler { var req = MatchThreeLevelEndCsReq.Parser.ParseFrom(data); + await connection.Player!.MissionManager!.HandleFinishType(MissionFinishTypeEnum.MatchThreeFinishLevel, req); + await connection.SendPacket(new PacketMatchThreeLevelEndScRsp(req.LevelId, req.ModeId)); } } \ No newline at end of file diff --git a/GameServer/Server/Packet/Recv/Player/HandlerReserveStaminaExchangeCsReq.cs b/GameServer/Server/Packet/Recv/Player/HandlerReserveStaminaExchangeCsReq.cs new file mode 100644 index 00000000..8357d163 --- /dev/null +++ b/GameServer/Server/Packet/Recv/Player/HandlerReserveStaminaExchangeCsReq.cs @@ -0,0 +1,29 @@ +using EggLink.DanhengServer.GameServer.Server.Packet.Send.Player; +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Player; + +[Opcode(CmdIds.ReserveStaminaExchangeCsReq)] +public class HandlerReserveStaminaExchangeCsReq : Handler +{ + public async override Task OnHandle(Connection connection, byte[] header, byte[] data) + { + var req = ReserveStaminaExchangeCsReq.Parser.ParseFrom(data); + var player = connection.Player; + if (player == null) return; + var amount = req.Num; + if (amount <= 0 || player.Data.StaminaReserve < amount) + { + await connection.SendPacket(new PacketReserveStaminaExchangeScRsp(0)); + } + else + { + player.Data.StaminaReserve -= amount; + player.Data.Stamina += (int)amount; + + await connection.SendPacket(new PacketStaminaInfoScNotify(player)); + await connection.SendPacket(new PacketReserveStaminaExchangeScRsp(amount)); + } + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Send/Item/PacketComposeSelectedRelicScRsp.cs b/GameServer/Server/Packet/Send/Item/PacketComposeSelectedRelicScRsp.cs index a8abcf42..6a340714 100644 --- a/GameServer/Server/Packet/Send/Item/PacketComposeSelectedRelicScRsp.cs +++ b/GameServer/Server/Packet/Send/Item/PacketComposeSelectedRelicScRsp.cs @@ -6,16 +6,28 @@ namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Item; public class PacketComposeSelectedRelicScRsp : BasePacket { - public PacketComposeSelectedRelicScRsp() : base(CmdIds.ComposeSelectedRelicScRsp) + public PacketComposeSelectedRelicScRsp(uint composeId) : base(CmdIds.ComposeSelectedRelicScRsp) { var proto = new ComposeSelectedRelicScRsp { + ComposeId = composeId, Retcode = 1 }; SetData(proto); } + public PacketComposeSelectedRelicScRsp(uint composeId, Retcode retcode) : base(CmdIds.ComposeSelectedRelicScRsp) + { + var proto = new ComposeSelectedRelicScRsp + { + ComposeId = composeId, + Retcode = (uint)retcode + }; + + SetData(proto); + } + public PacketComposeSelectedRelicScRsp(uint composeId, ItemData item) : base(CmdIds.ComposeSelectedRelicScRsp) { diff --git a/GameServer/Server/Packet/Send/Item/PacketDiscardRecliScRsp.cs b/GameServer/Server/Packet/Send/Item/PacketDiscardRecliScRsp.cs new file mode 100644 index 00000000..c9d41043 --- /dev/null +++ b/GameServer/Server/Packet/Send/Item/PacketDiscardRecliScRsp.cs @@ -0,0 +1,17 @@ +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Item; + +public class PacketDiscardRelicScRsp : BasePacket +{ + public PacketDiscardRelicScRsp(bool success, bool isDiscard) : base(CmdIds.DiscardRelicScRsp) + { + DiscardRelicScRsp proto = new(); + + if (success) proto.IsDiscard = isDiscard; + else proto.Retcode = (uint)Retcode.RetFail; + + SetData(proto); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Send/Item/PacketLockEquipmentScRsp.cs b/GameServer/Server/Packet/Send/Item/PacketLockEquipmentScRsp.cs new file mode 100644 index 00000000..c9e4fe5c --- /dev/null +++ b/GameServer/Server/Packet/Send/Item/PacketLockEquipmentScRsp.cs @@ -0,0 +1,16 @@ +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Item; + +public class PacketLockEquipmentScRsp : BasePacket +{ + public PacketLockEquipmentScRsp(bool success) : base(CmdIds.LockEquipmentScRsp) + { + LockEquipmentScRsp proto = new(); + + if (!success) proto.Retcode = (uint)Retcode.RetFail; + + SetData(proto); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Send/Item/PacketLockRecliScRsp.cs b/GameServer/Server/Packet/Send/Item/PacketLockRecliScRsp.cs new file mode 100644 index 00000000..fd9004df --- /dev/null +++ b/GameServer/Server/Packet/Send/Item/PacketLockRecliScRsp.cs @@ -0,0 +1,16 @@ +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Item; + +public class PacketLockRelicScRsp : BasePacket +{ + public PacketLockRelicScRsp(bool success) : base(CmdIds.LockRelicScRsp) + { + LockRelicScRsp proto = new(); + + if (!success) proto.Retcode = (uint)Retcode.RetFail; + + SetData(proto); + } +} \ No newline at end of file diff --git a/GameServer/Server/Packet/Send/Player/PacketReserveStaminaExchangeScRsp.cs b/GameServer/Server/Packet/Send/Player/PacketReserveStaminaExchangeScRsp.cs new file mode 100644 index 00000000..948983e7 --- /dev/null +++ b/GameServer/Server/Packet/Send/Player/PacketReserveStaminaExchangeScRsp.cs @@ -0,0 +1,17 @@ +using EggLink.DanhengServer.Kcp; +using EggLink.DanhengServer.Proto; + +namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Player; + +public class PacketReserveStaminaExchangeScRsp : BasePacket +{ + public PacketReserveStaminaExchangeScRsp(uint amount) : base(CmdIds.ReserveStaminaExchangeScRsp) + { + var proto = new ReserveStaminaExchangeScRsp(); + + if (amount > 0) proto.Num = amount; + else proto.Retcode = (uint)Retcode.RetFail; + + SetData(proto); + } +} \ No newline at end of file diff --git a/docs/MuipAPI.md b/docs/MuipAPI.md index 57c1ca81..86ae2601 100644 --- a/docs/MuipAPI.md +++ b/docs/MuipAPI.md @@ -12,17 +12,16 @@ ## 💡API Help -- Since version 2.3, external APIs are supported -- For example, your Dispatch is http://127.0.0.1:8080, and the request parameters and returns are in json format -- (1) Authorization interface: http://127.0.0.1:8080/muip/auth_admin (support POST) - - -Required parameter 1: admin_key (MuipServer/AdminKey configuration in config.php) - - -Required parameter 2: key_type (type, e.g. PEM or XML) - - -Return example: + +- External API call interfaces are supported starting from version 2.3. +- The main interface is the Dispatch interface with an entry point. For example, if your Dispatch is http://127.0.0.1:8080, the request parameters and responses are in JSON format. +- (1) Create Session Interface: http://127.0.0.1:8080/muip/create_session (supports POST) + - -Optional parameter: key_type (type, only supports PEM or default XML) + - -Response example: ```json { "code": 0, - //codeResponse: `code`: `0 -> Success` `1 -> Token incorrect or not enable` - "message": "Authorized admin key successfully!", + "message": "Created!", "data": { "rsaPublicKey": "***", "sessionId": "***", @@ -30,29 +29,41 @@ } } ``` -- (2)Submit command interface: http://127.0.0.1:8080/muip/exec_cmd (support POST/GET) - - -Required parameter 1: SessionId (obtained after authorization API request) - - -Required parameter 2: Command (the command to be executed is encrypted by RSA[pacs#1] under rsaPublicKey) +- (2) Authorization Interface: http://127.0.0.1:8080/muip/auth_admin (supports POST) + - -Required parameter 1: SessionId (obtained after requesting the Create Session Interface) + - -Required parameter 2: admin_key (configured in config.json's MuipServer.AdminKey and encrypted under rsaPublicKey [obtained from Create Session Interface] using RSA [pacs#1]) + - -Response example: + ```json + { + "code": 0, + "message": "Authorized admin key successfully!", + "data": { + "sessionId": "***", + "expireTimeStamp": *** + } + } + ``` +- (3) Command Submission Interface: http://127.0.0.1:8080/muip/exec_cmd (supports POST/GET) + - -Required parameter 1: SessionId (obtained after requesting the Create Session Interface) + - -Required parameter 2: Command (the command to be executed, encrypted under rsaPublicKey [obtained from Create Session Interface] using RSA [pacs#1]) - -Required parameter 3: TargetUid (UID of the player executing the command) - - -Return example: + - -Response example: ```json { "code": 0, - //codeResponse: `code`: `0 -> Success` `1 -> Session expired` `2 -> session not found` `3 -> encryption error` "message": "Success", "data": { "sessionId": "***", - "message": "*** //base64 + "message": "*** //after base64 encoding } } ``` -- (3)Interface to get server status: http://127.0.0.1:8080/muip/server_information (support POST/GET) - - -Required parameter 1: SessionId (obtained after authorization API request) - - -Return example: +- (4) Get Server Status Interface: http://127.0.0.1:8080/muip/server_information (supports POST/GET) + - -Required parameter 1: SessionId (obtained after requesting the Create Session Interface) + - -Response example: ```json { "code": 0, - //codeResponse: `code`: `0 -> Success` `1 -> Session expired` `2 -> session not found` "message": "Success", "data": { "onlinePlayers": [ @@ -70,14 +81,13 @@ } } ``` -- (4)Interface to get player information: http://127.0.0.1:8080/muip/player_information (support POST/GET) - - -Required parameter 1: SessionId (obtained after authorization API request) +- (5) Get Player Information Interface: http://127.0.0.1:8080/muip/player_information (supports POST/GET) + - -Required parameter 1: SessionId (obtained after requesting the Create Session Interface) - -Required parameter 2: Uid (player UID) - - -Return example: + - -Response example: ```json { "code": 0, - //Response: `code`: `0 -> Success` `1 -> Session expired` `2 -> player not exist` `3 -> session not found` "message": "Success", "data": { "uid": 10001, @@ -97,4 +107,4 @@ "acceptedSubMissionIdList": Array[169] } } - ``` + ``` \ No newline at end of file diff --git a/docs/MuipAPI_ja-JP.md b/docs/MuipAPI_ja-JP.md index 11067a4c..b7fed829 100644 --- a/docs/MuipAPI_ja-JP.md +++ b/docs/MuipAPI_ja-JP.md @@ -11,44 +11,57 @@ -##💡API支援です +## 💡APIヘルプ --バージョン2.3から、外部API呼び出しインタフェースをサポートします。 --全体のインタフェースはDispatchインタフェースに入口を加えます。例えば、Dispatchはhttp://127.0.0.1:8080、要求パラメータとリターンはjson形式です。 --(1)ライセンスインタフェース:http://127.0.0.1:8080/muip/auth_admin(支持ポスト/ get) -- -必須引数1:admin_key (config.phpでのMuipServer/AdminKey構成) -- -必須パラメータ2:key_type(タイプ、例えばPEM)です。 -- -リターン例です: -```json +- バージョン2.3以降、外部API呼び出しをサポート +- 総インターフェースはDispatchインターフェースにエントリを加えたもので、例えばあなたのDispatchが http://127.0.0.1:8080 の場合、リクエストパラメータと返り値はjson形式です +- (1)セッション作成インターフェース: http://127.0.0.1:8080/muip/create_session (POSTサポート) + - -オプションパラメータ:key_type (タイプ、PEMまたはデフォルトのXMLのみサポート) + - -返り値の例: + ```json { "code": 0, - "message": "Authorized admin key successfully!", + "message": "Created!", "data": { "rsaPublicKey": "***", "sessionId": "***", "expireTimeStamp": *** } } -``` -—(2)提出命令インタフェース:http://127.0.0.1:8080/muip/exec_cmd(支持ポスト/ get) -- -必伝パラメータ1:SessionId(ライセンスインターフェース要求後に取得します) -- -必須引数2:Command(実行するコマンドをrsaPublicKey[ライセンスインターフェース取得]でRSA[pacs#1]で暗号化します) -- -必伝パラメータ3:TargetUid(コマンドを実行するプレイヤーUID)です -- -リターン例です: -```json + ``` +- (2)認証インターフェース: http://127.0.0.1:8080/muip/auth_admin (POSTサポート) + - -必須パラメータ1:SessionId (セッション作成インターフェースのリクエスト後に取得) + - -必須パラメータ2:admin_key (config.jsonのMuipServer.AdminKey設定で、rsaPublicKey[セッション作成インターフェースで取得]下でRSA[pacs#1]暗号化) + - -返り値の例: + ```json + { + "code": 0, + "message": "Authorized admin key successfully!", + "data": { + "sessionId": "***", + "expireTimeStamp": *** + } + } + ``` +- (3)コマンド送信インターフェース: http://127.0.0.1:8080/muip/exec_cmd (POST/GETサポート) + - -必須パラメータ1:SessionId (セッション作成インターフェースのリクエスト後に取得) + - -必須パラメータ2:Command (実行するコマンドはrsaPublicKey[セッション作成インターフェースで取得]下でRSA[pacs#1]暗号化) + - -必須パラメータ3:TargetUid (コマンドを実行するプレイヤーのUID) + - -返り値の例: + ```json { "code": 0, "message": "Success", "data": { "sessionId": "***", - "message": "*** //base64编码后 + "message": "*** //base64エンコード後 } } -``` -—(3)サーバーの状態をインタフェース:http://127.0.0.1:8080/muip/server_information(支持get)だけ -- -必伝パラメータ1:SessionId(ライセンスインターフェース要求後に取得します) -- -リターン例です: -```json + ``` +- (4)サーバー状態取得インターフェース: http://127.0.0.1:8080/muip/server_information (POST/GETサポート) + - -必須パラメータ1:SessionId (セッション作成インターフェースのリクエスト後に取得) + - -返り値の例: + ```json { "code": 0, "message": "Success", @@ -67,12 +80,12 @@ "programUsedMemory": 323 } } -``` -—(4)プレイヤー情報を盗み出すインタフェース:http://127.0.0.1:8080/muip/player_information(支持get)だけ -- -必伝パラメータ1:SessionId(ライセンスインターフェース要求後に取得します) -- -必伝パラメーター2:Uid(プレイヤーUid) -- -リターン例です: -```json + ``` +- (5)プレイヤー情報取得インターフェース: http://127.0.0.1:8080/muip/player_information (POST/GETサポート) + - -必須パラメータ1:SessionId (セッション作成インターフェースのリクエスト後に取得) + - -必須パラメータ2:Uid (プレイヤーUID) + - -返り値の例: + ```json { "code": 0, "message": "Success", @@ -94,4 +107,4 @@ "acceptedSubMissionIdList": Array[169] } } -``` + ``` \ No newline at end of file diff --git a/docs/MuipAPI_zh-TW.md b/docs/MuipAPI_zh-TW.md index 06cd1d22..ac51793e 100644 --- a/docs/MuipAPI_zh-TW.md +++ b/docs/MuipAPI_zh-TW.md @@ -15,14 +15,13 @@ - 自2.3版本開始,支持外部API調用接口 - 總接口為Dispatch接口加上入口,比如你的Dispatch為 http://127.0.0.1:8080 ,請求參數和返回都為json格式 -- (1)授權接口: http://127.0.0.1:8080/muip/auth_admin (支持POST) - - -必傳參數1:admin_key (在config.php的MuipServer/AdminKey配置) - - -必傳參數2:key_type (類型,比如PEM) +- (1)創建會話接口: http://127.0.0.1:8080/muip/create_session (支持POST) + - -可選參數:key_type (類型,僅支持PEM或默認XML) - -返回示例: ```json { "code": 0, - "message": "Authorized admin key successfully!", + "message": "Created!", "data": { "rsaPublicKey": "***", "sessionId": "***", @@ -30,9 +29,23 @@ } } ``` -- (2)提交命令接口: http://127.0.0.1:8080/muip/exec_cmd (支持POST/GET) - - -必傳參數1:SessionId (在授權接口請求後獲得) - - -必傳參數2:Command (需要執行的命令經過rsaPublicKey[授權接口獲取]下RSA[pacs#1]加密) +- (2)授權接口: http://127.0.0.1:8080/muip/auth_admin (支持POST) + - -必傳參數1:SessionId (在創建會話接口請求後獲得) + - -必傳參數2:admin_key (在config.json的MuipServer.AdminKey配置,並且經過rsaPublicKey[創建會話接口獲取]下RSA[pacs#1]加密) + - -返回示例: + ```json + { + "code": 0, + "message": "Authorized admin key successfully!", + "data": { + "sessionId": "***", + "expireTimeStamp": *** + } + } + ``` +- (3)提交命令接口: http://127.0.0.1:8080/muip/exec_cmd (支持POST/GET) + - -必傳參數1:SessionId (在創建會話接口請求後獲得) + - -必傳參數2:Command (需要執行的命令經過rsaPublicKey[創建會話接口獲取]下RSA[pacs#1]加密) - -必傳參數3:TargetUid (執行命令的玩家UID) - -返回示例: ```json @@ -45,8 +58,8 @@ } } ``` -- (3)獲取服務器狀態接口: http://127.0.0.1:8080/muip/server_information (支持POST/GET) - - -必傳參數1:SessionId (在授權接口請求後獲得) +- (4)獲取伺服器狀態接口: http://127.0.0.1:8080/muip/server_information (支持POST/GET) + - -必傳參數1:SessionId (在創建會話接口請求後獲得) - -返回示例: ```json { @@ -68,10 +81,10 @@ } } ``` -- (4)獲取玩家信息接口: http://127.0.0.1:8080/muip/player_information (支持POST/GET) - - -必傳參數1:SessionId (在授權接口請求後獲得) +- (5)獲取玩家信息接口: http://127.0.0.1:8080/muip/player_information (支持POST/GET) + - -必傳參數1:SessionId (在創建會話接口請求後獲得) - -必傳參數2:Uid (玩家UID) - - - -返回示例: + - -返回示例: ```json { "code": 0, @@ -94,4 +107,4 @@ "acceptedSubMissionIdList": Array[169] } } - ``` + ``` \ No newline at end of file