From 53bf29a9ccf4e91d58f70972a2ed05ffc42e5bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Thu, 13 Mar 2025 22:34:18 -0300 Subject: [PATCH 1/6] Fix: Receiving multiple copies of equipments Fix: #366 --- Maple2.Server.Game/Commands/ItemCommand.cs | 11 --- Maple2.Server.Game/Commands/PlayerCommand.cs | 76 +++++++++++++++++++ .../Commands/TutorialCommand.cs | 5 -- Maple2.Server.Game/Manager/BeautyManager.cs | 1 + .../Manager/Items/FurnishingManager.cs | 7 +- .../Manager/Items/InventoryManager.cs | 74 ++++++++++++------ Maple2.Server.Game/Manager/QuestManager.cs | 21 +++-- .../PacketHandlers/BeautyHandler.cs | 11 ++- .../PacketHandlers/RequestCubeHandler.cs | 5 -- .../PacketHandlers/UgcHandler.cs | 7 -- dev.bat | 2 + 11 files changed, 159 insertions(+), 61 deletions(-) diff --git a/Maple2.Server.Game/Commands/ItemCommand.cs b/Maple2.Server.Game/Commands/ItemCommand.cs index 32c91885e..034e3648c 100644 --- a/Maple2.Server.Game/Commands/ItemCommand.cs +++ b/Maple2.Server.Game/Commands/ItemCommand.cs @@ -51,21 +51,10 @@ private void Handle(InvocationContext ctx, int itemId, int amount, int rarity, b if (item.Metadata.Property.SlotMax == 0) { ctx.Console.Error.WriteLine($"{itemId} has SlotMax of 0, ignoring..."); amount = Math.Clamp(amount, 1, int.MaxValue); - } else { - amount = Math.Clamp(amount, 1, item.Metadata.Property.SlotMax); } } item.Amount = amount; - using (GameStorage.Request db = session.GameStorage.Context()) { - item = db.CreateItem(session.CharacterId, item); - if (item == null) { - ctx.Console.Error.WriteLine($"Failed to create item:{itemId} in database"); - ctx.ExitCode = 1; - return; - } - } - if (drop && session.Field != null) { FieldItem fieldItem = session.Field.SpawnItem(session.Player, item); session.Field.Broadcast(FieldPacket.DropItem(fieldItem)); diff --git a/Maple2.Server.Game/Commands/PlayerCommand.cs b/Maple2.Server.Game/Commands/PlayerCommand.cs index 4d43773a2..0d0915fd3 100644 --- a/Maple2.Server.Game/Commands/PlayerCommand.cs +++ b/Maple2.Server.Game/Commands/PlayerCommand.cs @@ -291,6 +291,8 @@ public InventoryCommand(GameSession session) : base("inventory", "Manage player this.session = session; AddCommand(new ClearInventoryCommand(session)); + AddCommand(new SlotsInventoryCommand(session)); + AddCommand(new ExpandInventoryCommand(this.session)); } private class ClearInventoryCommand : Command { @@ -317,6 +319,80 @@ private void Handle(InvocationContext ctx, string tab) { ctx.ExitCode = 0; } } + + private class SlotsInventoryCommand : Command { + private readonly GameSession session; + + public SlotsInventoryCommand(GameSession session) : base("slots", "Get inventory slots information.") { + this.session = session; + + var tabArg = new Argument("tab", $"Inventory tab to get information for. Use 'all' for all tabs or one of: {string.Join(", ", Enum.GetNames(typeof(InventoryType)))}"); + AddArgument(tabArg); + this.SetHandler(Handle, tabArg); + } + + private void Handle(InvocationContext ctx, string tab) { + try { + if (tab.Equals("all", StringComparison.OrdinalIgnoreCase)) { + foreach (InventoryType inventoryTab in Enum.GetValues(typeof(InventoryType))) { + DisplayInventoryInfo(ctx, inventoryTab); + } + ctx.ExitCode = 0; + return; + } + + if (!Enum.TryParse(tab, true, out InventoryType inventoryType)) { + ctx.Console.Error.WriteLine($"Invalid inventory tab: {tab}. Must be one of: {string.Join(", ", Enum.GetNames(typeof(InventoryType)))}"); + ctx.ExitCode = 1; + return; + } + + DisplayInventoryInfo(ctx, inventoryType); + ctx.ExitCode = 0; + } catch (SystemException ex) { + ctx.Console.Error.WriteLine(ex.Message); + ctx.ExitCode = 1; + } + } + + private void DisplayInventoryInfo(InvocationContext ctx, InventoryType inventoryType) { + int totalSlots = session.Item.Inventory.TotalSlots(inventoryType); + int freeSlots = session.Item.Inventory.FreeSlots(inventoryType); + int usedSlots = totalSlots - freeSlots; + + ctx.Console.Out.WriteLine($"{inventoryType} Inventory: {usedSlots}/{totalSlots} slots used."); + } + } + + private class ExpandInventoryCommand : Command { + private readonly GameSession session; + + public ExpandInventoryCommand(GameSession session) : base("expand", "Expand player inventory.") { + this.session = session; + + var tab = new Argument("tab", $"Inventory tab to expand. One of: {string.Join(", ", Enum.GetNames(typeof(InventoryType)))}"); + + AddArgument(tab); + this.SetHandler(Handle, tab); + } + + private void Handle(InvocationContext ctx, string tab) { + try { + if (!Enum.TryParse(tab, true, out InventoryType inventoryType)) { + ctx.Console.Error.WriteLine($"Invalid inventory tab: {tab}. Must be one of: {string.Join(", ", Enum.GetNames(typeof(InventoryType)))}"); + ctx.ExitCode = 1; + return; + } + + session.Item.Inventory.Expand(inventoryType); + ctx.Console.Out.WriteLine($"Expanded {inventoryType} inventory"); + ctx.ExitCode = 0; + } catch (SystemException ex) { + ctx.Console.Error.WriteLine(ex.Message); + ctx.ExitCode = 1; + } + } + } } private class TrophyCommand : Command { diff --git a/Maple2.Server.Game/Commands/TutorialCommand.cs b/Maple2.Server.Game/Commands/TutorialCommand.cs index db58facae..57cbf4f1c 100644 --- a/Maple2.Server.Game/Commands/TutorialCommand.cs +++ b/Maple2.Server.Game/Commands/TutorialCommand.cs @@ -54,13 +54,8 @@ private void Handle(InvocationContext ctx, Type type) { switch (type) { case Type.Item: { - using GameStorage.Request db = session.GameStorage.Context(); foreach (JobTable.Item rewardItem in tutorial.StartItem.Concat(tutorial.Reward)) { Item? item = session.Field.ItemDrop.CreateItem(rewardItem.Id, rewardItem.Rarity, rewardItem.Count); - if (item == null) { - return; - } - item = db.CreateItem(player.Character.Id, item); if (item == null) { ctx.Console.Error.WriteLine($"Failed to create item: {rewardItem.Id}"); ctx.ExitCode = 1; diff --git a/Maple2.Server.Game/Manager/BeautyManager.cs b/Maple2.Server.Game/Manager/BeautyManager.cs index 3f3df3ee9..cec17a796 100644 --- a/Maple2.Server.Game/Manager/BeautyManager.cs +++ b/Maple2.Server.Game/Manager/BeautyManager.cs @@ -106,6 +106,7 @@ public bool EquipSavedCosmetic(long uid) { } if (!session.Item.Equips.EquipCosmetic(copy, copy.Metadata.SlotNames[0])) { + db.SaveItems(0, copy); return false; } session.Send(BeautyPacket.ApplySavedHair()); diff --git a/Maple2.Server.Game/Manager/Items/FurnishingManager.cs b/Maple2.Server.Game/Manager/Items/FurnishingManager.cs index e0612affd..90ed79e9a 100644 --- a/Maple2.Server.Game/Manager/Items/FurnishingManager.cs +++ b/Maple2.Server.Game/Manager/Items/FurnishingManager.cs @@ -223,7 +223,12 @@ public long AddStorage(Item? item) { item.Group = ItemGroup.Furnishing; using GameStorage.Request db = session.GameStorage.Context(); item = db.CreateItem(session.AccountId, item); - if (item == null || storage.Add(item).Count <= 0) { + if (item == null) { + return 0; + } + + if (storage.Add(item).Count <= 0) { + db.SaveItems(0, item); return 0; } diff --git a/Maple2.Server.Game/Manager/Items/InventoryManager.cs b/Maple2.Server.Game/Manager/Items/InventoryManager.cs index ceceec518..599729fc0 100644 --- a/Maple2.Server.Game/Manager/Items/InventoryManager.cs +++ b/Maple2.Server.Game/Manager/Items/InventoryManager.cs @@ -22,6 +22,8 @@ public class InventoryManager { private readonly Dictionary tabs; private readonly List delete; + private readonly ILogger logger = Log.Logger.ForContext(); + public InventoryManager(GameStorage.Request db, GameSession session) { this.session = session; tabs = new Dictionary(); @@ -169,47 +171,69 @@ public bool Add(Item add, bool notifyNew = false, bool commit = false) { return false; } - // If we are adding an item without a Uid, it may need to be created in db. - if (add.Uid == 0) { - // Slot MUST be -1 so we don't add directly to a slot. - add.Slot = -1; - int remainStack = items.GetStackResult(add); - if (remainStack > 0) { - if (items.OpenSlots <= 0) { - return false; - } + if (add.Metadata.Property.SlotMax == 1 && add.Amount > 1) { + if (items.OpenSlots < add.Amount) { + session.Send(ItemInventoryPacket.Error(s_err_inventory)); + return false; + } + int totalAmount = add.Amount; + add.Amount = 1; - if (add.Metadata.Property.SlotMax == 1 && add.Amount > 1) { - add.Amount--; - if (!Add(add, notifyNew, commit)) { - return false; - } - } + if (!Add(add, notifyNew, commit)) { + return false; + } - using GameStorage.Request db = session.GameStorage.Context(); - Item? newAdd = db.CreateItem(session.CharacterId, add); - if (newAdd == null) { + // Create and add individual copies for remaining items + for (int i = 1; i < totalAmount; i++) { + Item? copy = session.Field.ItemDrop.CreateItem(add.Id, add.Rarity); + if (copy is null) { return false; } - add = newAdd; + if (!Add(copy, notifyNew, commit)) { + return false; + } } + + return true; } if (add.Metadata.Limit.TransferType is TransferType.BindOnLoot) { add.Transfer?.Bind(session.Player.Value.Character); } + using GameStorage.Request db = session.GameStorage.Context(); + + bool justCreated = false; + + // If we are adding an item without a Uid, it needs to be created in db. + if (add.Uid == 0) { + // Slot MUST be -1 so we don't add directly to a slot. + add.Slot = -1; + + Item? newAdd = db.CreateItem(session.CharacterId, add); + if (newAdd == null) { + logger.Error("Failed to create item in database"); + return false; + } + + add = newAdd; + justCreated = true; + } + IList<(Item, int Added)> result = items.Add(add, stack: true); if (result.Count == 0) { + Discard(add, commit); session.Send(ItemInventoryPacket.Error(s_err_inventory)); return false; } if (add.Amount == 0) { Discard(add, commit); - } else if (commit) { - using GameStorage.Request db = session.GameStorage.Context(); + return false; + } + + if (commit && !justCreated) { db.SaveItems(session.CharacterId, add); } @@ -282,7 +306,7 @@ private void AddCurrency(Item add) { case 90000025: // StarPoint session.Currency[CurrencyType.StarPoint] += add.Amount; break; - // case 90000026: // Unknown (Blank) + // case 90000026: // Unknown (Blank) } } @@ -495,6 +519,12 @@ public short FreeSlots(InventoryType type) { } } + public short TotalSlots(InventoryType type) { + lock (session.Item) { + return !tabs.TryGetValue(type, out ItemCollection? items) ? (short) 0 : items.Size; + } + } + public Item? Get(long uid, InventoryType? type = null) { lock (session.Item) { if (type != null) { diff --git a/Maple2.Server.Game/Manager/QuestManager.cs b/Maple2.Server.Game/Manager/QuestManager.cs index 01d9e2948..22c9e5c40 100644 --- a/Maple2.Server.Game/Manager/QuestManager.cs +++ b/Maple2.Server.Game/Manager/QuestManager.cs @@ -311,8 +311,12 @@ public bool Complete(Quest quest, bool bypassConditions = false) { foreach (QuestMetadataReward.Item entry in reward.EssentialItem) { Item? item = session.Field.ItemDrop.CreateItem(entry.Id, entry.Rarity, entry.Amount); - if (item != null) { - session.Item.Inventory.Add(item, true); + if (item is null) { + continue; + } + + if (!session.Item.Inventory.Add(item, true)) { + session.Item.MailItem(item); } } @@ -320,13 +324,18 @@ public bool Complete(Quest quest, bool bypassConditions = false) { if (!session.ItemMetadata.TryGet(entry.Id, out ItemMetadata? metadata)) { continue; } - if (metadata.Limit.JobRecommends.Length > 0 && !metadata.Limit.JobRecommends.Contains(JobCode.None) - && !metadata.Limit.JobRecommends.Contains(session.Player.Value.Character.Job.Code())) { + + if (metadata.Limit.JobRecommends.Length > 0 && !metadata.Limit.JobRecommends.Contains(JobCode.None) && !metadata.Limit.JobRecommends.Contains(session.Player.Value.Character.Job.Code())) { continue; } + Item? item = session.Field.ItemDrop.CreateItem(entry.Id, entry.Rarity, entry.Amount); - if (item != null) { - session.Item.Inventory.Add(item, true); + if (item is null) { + continue; + } + + if (!session.Item.Inventory.Add(item, true)) { + session.Item.MailItem(item); } } diff --git a/Maple2.Server.Game/PacketHandlers/BeautyHandler.cs b/Maple2.Server.Game/PacketHandlers/BeautyHandler.cs index 8b60f99c6..6f08d6e50 100644 --- a/Maple2.Server.Game/PacketHandlers/BeautyHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/BeautyHandler.cs @@ -409,9 +409,7 @@ private void HandleDeleteHair(GameSession session, IByteReader packet) { } } - private void HandleAskAddSlots(GameSession session, IByteReader packet) { - - } + private void HandleAskAddSlots(GameSession session, IByteReader packet) { } private static void HandleApplySavedHair(GameSession session, IByteReader packet) { long uid = packet.ReadLong(); @@ -583,6 +581,11 @@ private static bool ModifyBeauty(GameSession session, IByteReader packet, Beauty return false; } - return session.Item.Equips.EquipCosmetic(newCosmetic, newCosmetic.Metadata.SlotNames.First()); + if (!session.Item.Equips.EquipCosmetic(newCosmetic, newCosmetic.Metadata.SlotNames.First())) { + db.SaveItems(0, newCosmetic); + return false; + } + + return true; } } diff --git a/Maple2.Server.Game/PacketHandlers/RequestCubeHandler.cs b/Maple2.Server.Game/PacketHandlers/RequestCubeHandler.cs index dea94c1d4..30930a636 100644 --- a/Maple2.Server.Game/PacketHandlers/RequestCubeHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/RequestCubeHandler.cs @@ -673,11 +673,6 @@ private void HandleCreateBlueprint(GameSession session) { CharacterName = session.PlayerName, }; - item = db.CreateItem(session.CharacterId, item); - if (item == null) { - return; - } - session.Item.Inventory.Add(item, notifyNew: true); session.StagedUgcItem = item; diff --git a/Maple2.Server.Game/PacketHandlers/UgcHandler.cs b/Maple2.Server.Game/PacketHandlers/UgcHandler.cs index 47d4ab1a2..a971f8922 100644 --- a/Maple2.Server.Game/PacketHandlers/UgcHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/UgcHandler.cs @@ -453,13 +453,6 @@ void ConfirmItem() { if (info.Type is UgcType.Furniture) { session.Item.Furnishing.AddCube(item); } else { - using GameStorage.Request gameRequest = session.GameStorage.Context(); - item = gameRequest.CreateItem(session.CharacterId, item); - if (item == null) { - Logger.Fatal("Failed to create UGC Item {ugcUid}", ugcUid); - throw new InvalidOperationException($"Fatal: UGC Item creation: {ugcUid}"); - } - session.Item.Inventory.Add(item, notifyNew: true); } diff --git a/dev.bat b/dev.bat index e8f91ddc1..13b70b949 100644 --- a/dev.bat +++ b/dev.bat @@ -1,5 +1,7 @@ @echo off +dotnet build + wt -d "Maple2.Server.World" --title "World Server" dotnet run ; ^ sp -d "Maple2.Server.Login" --title "Login Server" dotnet run ; ^ sp -d "Maple2.Server.Web" --title "Web Server" dotnet run From 26cd824283f52f81176be66df43d2c2b1e36ac77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Thu, 13 Mar 2025 22:43:09 -0300 Subject: [PATCH 2/6] format --- Maple2.Server.Game/Manager/Items/InventoryManager.cs | 2 +- .../Migrations/20250306081311_CubeCleanUp.cs | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Maple2.Server.Game/Manager/Items/InventoryManager.cs b/Maple2.Server.Game/Manager/Items/InventoryManager.cs index 599729fc0..d9095ca84 100644 --- a/Maple2.Server.Game/Manager/Items/InventoryManager.cs +++ b/Maple2.Server.Game/Manager/Items/InventoryManager.cs @@ -306,7 +306,7 @@ private void AddCurrency(Item add) { case 90000025: // StarPoint session.Currency[CurrencyType.StarPoint] += add.Amount; break; - // case 90000026: // Unknown (Blank) + // case 90000026: // Unknown (Blank) } } diff --git a/Maple2.Server.World/Migrations/20250306081311_CubeCleanUp.cs b/Maple2.Server.World/Migrations/20250306081311_CubeCleanUp.cs index 2bc9e80bb..6fc38cfdb 100644 --- a/Maple2.Server.World/Migrations/20250306081311_CubeCleanUp.cs +++ b/Maple2.Server.World/Migrations/20250306081311_CubeCleanUp.cs @@ -2,14 +2,11 @@ #nullable disable -namespace Maple2.Server.World.Migrations -{ +namespace Maple2.Server.World.Migrations { /// - public partial class CubeCleanUp : Migration - { + public partial class CubeCleanUp : Migration { /// - protected override void Up(MigrationBuilder migrationBuilder) - { + protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( name: "HousingCategory", table: "ugcmap-cube"); @@ -25,8 +22,7 @@ protected override void Up(MigrationBuilder migrationBuilder) } /// - protected override void Down(MigrationBuilder migrationBuilder) - { + protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.RenameColumn( name: "InteractId", table: "nurturing", From d99cc3077f504ee2df853cba6cf9e1aee53be6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Fri, 14 Mar 2025 20:45:32 -0300 Subject: [PATCH 3/6] check if inventory can hold all quest rewards items --- Maple2.Model/Metadata/Constants.cs | 2 + .../Manager/Items/InventoryManager.cs | 1 - Maple2.Server.Game/Manager/QuestManager.cs | 53 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Maple2.Model/Metadata/Constants.cs b/Maple2.Model/Metadata/Constants.cs index 6ca1b3389..fec65f37e 100644 --- a/Maple2.Model/Metadata/Constants.cs +++ b/Maple2.Model/Metadata/Constants.cs @@ -112,6 +112,8 @@ public static class Constant { public const bool EnableRollEverywhere = false; public const bool HideHomeCommands = true; + public const bool MailQuestItems = false; // Mail quest item rewards if inventory is full + #region Field public static readonly TimeSpan FieldUgcBannerRemoveAfter = TimeSpan.FromHours(4); public static readonly TimeSpan FieldDisposeLoopInterval = TimeSpan.FromMinutes(1); diff --git a/Maple2.Server.Game/Manager/Items/InventoryManager.cs b/Maple2.Server.Game/Manager/Items/InventoryManager.cs index d9095ca84..5c5fb3b40 100644 --- a/Maple2.Server.Game/Manager/Items/InventoryManager.cs +++ b/Maple2.Server.Game/Manager/Items/InventoryManager.cs @@ -230,7 +230,6 @@ public bool Add(Item add, bool notifyNew = false, bool commit = false) { if (add.Amount == 0) { Discard(add, commit); - return false; } if (commit && !justCreated) { diff --git a/Maple2.Server.Game/Manager/QuestManager.cs b/Maple2.Server.Game/Manager/QuestManager.cs index 22c9e5c40..03ddae908 100644 --- a/Maple2.Server.Game/Manager/QuestManager.cs +++ b/Maple2.Server.Game/Manager/QuestManager.cs @@ -293,6 +293,12 @@ public bool Complete(Quest quest, bool bypassConditions = false) { } QuestMetadataReward reward = quest.Metadata.CompleteReward; + + if (!CanHoldQuestRewards(reward) && !Constant.MailQuestItems) { + session.Send(ItemInventoryPacket.Error(ItemInventoryError.s_err_inventory)); + return false; + } + if (reward.Exp > 0) { session.Exp.AddExp(reward.Exp); } @@ -355,6 +361,53 @@ public bool Complete(Quest quest, bool bypassConditions = false) { return true; } + private bool CanHoldQuestRewards(QuestMetadataReward reward) { + // Check if inventory has enough space for all reward items + var requiredSlots = new Dictionary(); // Track required slots by inventory type + + // Count slots needed for EssentialItems + foreach (QuestMetadataReward.Item entry in reward.EssentialItem) { + if (!session.ItemMetadata.TryGet(entry.Id, out ItemMetadata? metadata)) { + continue; + } + + InventoryType invType = metadata.Inventory(); + requiredSlots.TryAdd(invType, 0); + requiredSlots[invType]++; + } + + // Count slots needed for EssentialJobItems + foreach (QuestMetadataReward.Item entry in reward.EssentialJobItem) { + if (!session.ItemMetadata.TryGet(entry.Id, out ItemMetadata? metadata)) { + continue; + } + + // Skip items that don't match the player's job + if (metadata.Limit.JobRecommends.Length > 0 && + !metadata.Limit.JobRecommends.Contains(JobCode.None) && + !metadata.Limit.JobRecommends.Contains(session.Player.Value.Character.Job.Code())) { + continue; + } + + InventoryType invType = metadata.Inventory(); + requiredSlots.TryAdd(invType, 0); + requiredSlots[invType]++; + } + + // Check if each inventory has enough free slots + foreach (KeyValuePair kvp in requiredSlots) { + InventoryType invType = kvp.Key; + int requiredSlot = kvp.Value; + int freeSlots = session.Item.Inventory.FreeSlots(invType); + + if (freeSlots < requiredSlot) { + return false; + } + } + + return true; + } + /// /// Gets available quests that the npc can give or can be completed. /// From 7bfe7121506a1c422e274a607fb2291bfe6dee44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Sat, 15 Mar 2025 00:56:00 -0300 Subject: [PATCH 4/6] . --- .../Manager/Items/InventoryManager.cs | 12 +++ Maple2.Server.Game/Manager/QuestManager.cs | 99 +++++-------------- 2 files changed, 39 insertions(+), 72 deletions(-) diff --git a/Maple2.Server.Game/Manager/Items/InventoryManager.cs b/Maple2.Server.Game/Manager/Items/InventoryManager.cs index 5c5fb3b40..0305802bd 100644 --- a/Maple2.Server.Game/Manager/Items/InventoryManager.cs +++ b/Maple2.Server.Game/Manager/Items/InventoryManager.cs @@ -319,6 +319,18 @@ public bool CanAdd(Item item) { } } + public bool CanAdd(ICollection items) { + lock (session.Item) { + foreach (Item item in items) { + if (!CanAdd(item)) { + return false; + } + } + + return true; + } + } + public bool Remove(long uid, [NotNullWhen(true)] out Item? removed, int amount = -1) { lock (session.Item) { return RemoveInternal(uid, amount, out removed); diff --git a/Maple2.Server.Game/Manager/QuestManager.cs b/Maple2.Server.Game/Manager/QuestManager.cs index 03ddae908..c398e59e5 100644 --- a/Maple2.Server.Game/Manager/QuestManager.cs +++ b/Maple2.Server.Game/Manager/QuestManager.cs @@ -294,36 +294,14 @@ public bool Complete(Quest quest, bool bypassConditions = false) { QuestMetadataReward reward = quest.Metadata.CompleteReward; - if (!CanHoldQuestRewards(reward) && !Constant.MailQuestItems) { - session.Send(ItemInventoryPacket.Error(ItemInventoryError.s_err_inventory)); - return false; - } - - if (reward.Exp > 0) { - session.Exp.AddExp(reward.Exp); - } - - if (reward.Meso > 0) { - session.Currency.Meso += reward.Meso; - } - - if (reward.Treva > 0) { - session.Currency[CurrencyType.Treva] += reward.Treva; - } - - if (reward.Rue > 0) { - session.Currency[CurrencyType.Rue] += reward.Rue; - } - + List rewards = []; foreach (QuestMetadataReward.Item entry in reward.EssentialItem) { Item? item = session.Field.ItemDrop.CreateItem(entry.Id, entry.Rarity, entry.Amount); if (item is null) { continue; } - if (!session.Item.Inventory.Add(item, true)) { - session.Item.MailItem(item); - } + rewards.Add(item); } foreach (QuestMetadataReward.Item entry in reward.EssentialJobItem) { @@ -340,12 +318,36 @@ public bool Complete(Quest quest, bool bypassConditions = false) { continue; } + rewards.Add(item); + } + + if (!session.Item.Inventory.CanAdd(rewards) && !Constant.MailQuestItems) { + session.Send(ItemInventoryPacket.Error(ItemInventoryError.s_err_inventory)); + return false; + } + + if (reward.Exp > 0) { + session.Exp.AddExp(reward.Exp); + } + + if (reward.Meso > 0) { + session.Currency.Meso += reward.Meso; + } + + if (reward.Treva > 0) { + session.Currency[CurrencyType.Treva] += reward.Treva; + } + + if (reward.Rue > 0) { + session.Currency[CurrencyType.Rue] += reward.Rue; + } + + foreach (Item item in rewards) { if (!session.Item.Inventory.Add(item, true)) { session.Item.MailItem(item); } } - // TODO: Guild rewards, mission points? session.ConditionUpdate(ConditionType.quest_clear_by_chapter, codeLong: quest.Metadata.Basic.ChapterId); @@ -361,53 +363,6 @@ public bool Complete(Quest quest, bool bypassConditions = false) { return true; } - private bool CanHoldQuestRewards(QuestMetadataReward reward) { - // Check if inventory has enough space for all reward items - var requiredSlots = new Dictionary(); // Track required slots by inventory type - - // Count slots needed for EssentialItems - foreach (QuestMetadataReward.Item entry in reward.EssentialItem) { - if (!session.ItemMetadata.TryGet(entry.Id, out ItemMetadata? metadata)) { - continue; - } - - InventoryType invType = metadata.Inventory(); - requiredSlots.TryAdd(invType, 0); - requiredSlots[invType]++; - } - - // Count slots needed for EssentialJobItems - foreach (QuestMetadataReward.Item entry in reward.EssentialJobItem) { - if (!session.ItemMetadata.TryGet(entry.Id, out ItemMetadata? metadata)) { - continue; - } - - // Skip items that don't match the player's job - if (metadata.Limit.JobRecommends.Length > 0 && - !metadata.Limit.JobRecommends.Contains(JobCode.None) && - !metadata.Limit.JobRecommends.Contains(session.Player.Value.Character.Job.Code())) { - continue; - } - - InventoryType invType = metadata.Inventory(); - requiredSlots.TryAdd(invType, 0); - requiredSlots[invType]++; - } - - // Check if each inventory has enough free slots - foreach (KeyValuePair kvp in requiredSlots) { - InventoryType invType = kvp.Key; - int requiredSlot = kvp.Value; - int freeSlots = session.Item.Inventory.FreeSlots(invType); - - if (freeSlots < requiredSlot) { - return false; - } - } - - return true; - } - /// /// Gets available quests that the npc can give or can be completed. /// From aa4517f18cff000f0343ee553a0340fecb69215c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Sat, 15 Mar 2025 01:22:40 -0300 Subject: [PATCH 5/6] Update InventoryManager.cs --- .../Manager/Items/InventoryManager.cs | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/Maple2.Server.Game/Manager/Items/InventoryManager.cs b/Maple2.Server.Game/Manager/Items/InventoryManager.cs index 0305802bd..40f9dbc70 100644 --- a/Maple2.Server.Game/Manager/Items/InventoryManager.cs +++ b/Maple2.Server.Game/Manager/Items/InventoryManager.cs @@ -319,17 +319,38 @@ public bool CanAdd(Item item) { } } - public bool CanAdd(ICollection items) { - lock (session.Item) { - foreach (Item item in items) { - if (!CanAdd(item)) { - return false; - } - } - - return true; - } - } + public bool CanAdd(ICollection items) { + lock (session.Item) { + // Group items by inventory type + Dictionary> itemsByType = items.GroupBy(item => item.Inventory) + .ToDictionary(g => g.Key, g => g.ToList()); + + foreach ((InventoryType inventoryType, List typeItems) in itemsByType) { + if (!tabs.TryGetValue(inventoryType, out ItemCollection? collection)) { + return false; + } + + short availableSlots = collection.OpenSlots; + + foreach (Item item in typeItems) { + int stackResult = collection.GetStackResult(item); + + if (stackResult == 0) { + // Item can be fully stacked, no slots needed + continue; + } + + // Need a new slot + if (availableSlots <= 0) { + return false; + } + availableSlots--; + } + } + + return true; + } + } public bool Remove(long uid, [NotNullWhen(true)] out Item? removed, int amount = -1) { lock (session.Item) { From 861b91680c989e9f8a200bc73600da3bef502f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Sat, 15 Mar 2025 01:28:00 -0300 Subject: [PATCH 6/6] Update InventoryManager.cs --- .../Manager/Items/InventoryManager.cs | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Maple2.Server.Game/Manager/Items/InventoryManager.cs b/Maple2.Server.Game/Manager/Items/InventoryManager.cs index 40f9dbc70..656d427a7 100644 --- a/Maple2.Server.Game/Manager/Items/InventoryManager.cs +++ b/Maple2.Server.Game/Manager/Items/InventoryManager.cs @@ -319,38 +319,38 @@ public bool CanAdd(Item item) { } } - public bool CanAdd(ICollection items) { - lock (session.Item) { - // Group items by inventory type - Dictionary> itemsByType = items.GroupBy(item => item.Inventory) - .ToDictionary(g => g.Key, g => g.ToList()); - - foreach ((InventoryType inventoryType, List typeItems) in itemsByType) { - if (!tabs.TryGetValue(inventoryType, out ItemCollection? collection)) { - return false; - } - - short availableSlots = collection.OpenSlots; - - foreach (Item item in typeItems) { - int stackResult = collection.GetStackResult(item); - - if (stackResult == 0) { - // Item can be fully stacked, no slots needed - continue; - } - - // Need a new slot - if (availableSlots <= 0) { - return false; - } - availableSlots--; - } - } - - return true; - } - } + public bool CanAdd(ICollection items) { + lock (session.Item) { + // Group items by inventory type + Dictionary> itemsByType = items.GroupBy(item => item.Inventory) + .ToDictionary(g => g.Key, g => g.ToList()); + + foreach ((InventoryType inventoryType, List typeItems) in itemsByType) { + if (!tabs.TryGetValue(inventoryType, out ItemCollection? collection)) { + return false; + } + + short availableSlots = collection.OpenSlots; + + foreach (Item item in typeItems) { + int stackResult = collection.GetStackResult(item); + + if (stackResult == 0) { + // Item can be fully stacked, no slots needed + continue; + } + + // Need a new slot + if (availableSlots <= 0) { + return false; + } + availableSlots--; + } + } + + return true; + } + } public bool Remove(long uid, [NotNullWhen(true)] out Item? removed, int amount = -1) { lock (session.Item) {