From 6ee44b3363bae9b81a3997f894d062cf82e15e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Mon, 10 Mar 2025 02:07:37 -0300 Subject: [PATCH 1/4] Multiple fixes --- .../Manager/Items/ItemCollection.cs | 2 +- .../PacketHandlers/HomeBankHandler.cs | 8 +- .../PacketHandlers/ItemInventoryHandler.cs | 2 +- .../PacketHandlers/MasteryHandler.cs | 8 +- .../PacketHandlers/QuestHandler.cs | 5 + Maple2.Server.Game/Program.cs | 6 +- .../Game/Manager/Item/ItemCollectionTest.cs | 101 +++++++++++++++++- .../Containers/ChannelClientLookup.cs | 2 +- .../Service/WorldService.Migrate.cs | 20 ++-- 9 files changed, 133 insertions(+), 21 deletions(-) diff --git a/Maple2.Server.Game/Manager/Items/ItemCollection.cs b/Maple2.Server.Game/Manager/Items/ItemCollection.cs index b4bc67f7f..ecefd0b87 100644 --- a/Maple2.Server.Game/Manager/Items/ItemCollection.cs +++ b/Maple2.Server.Game/Manager/Items/ItemCollection.cs @@ -225,7 +225,7 @@ public void Sort() { // Update the slot mapping uidToSlot.Clear(); short i = 0; - while (items[i] is { } item) { + while (i < items.Length && items[i] is { } item) { item.Slot = i; uidToSlot[items[i]!.Uid] = i; i++; diff --git a/Maple2.Server.Game/PacketHandlers/HomeBankHandler.cs b/Maple2.Server.Game/PacketHandlers/HomeBankHandler.cs index 79342fc9c..a576ae35c 100644 --- a/Maple2.Server.Game/PacketHandlers/HomeBankHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/HomeBankHandler.cs @@ -11,8 +11,8 @@ public class HomeBankHandler : PacketHandler { public override RecvOp OpCode => RecvOp.RequestHomeBank; private enum Command : byte { - Home = 0, - Premium = 1, + Home = 1, + Premium = 2, } public override void Handle(GameSession session, IByteReader packet) { @@ -28,12 +28,12 @@ public override void Handle(GameSession session, IByteReader packet) { session.Send(HomeBank(time)); return; case Command.Premium: - session.Send(HomeBank(DateTimeOffset.UtcNow.ToUnixTimeSeconds())); + session.Send(HomeBank()); return; } } - private static ByteWriter HomeBank(long time) { + private static ByteWriter HomeBank(long time = 0) { var pWriter = Packet.Of(SendOp.HomeBank); pWriter.WriteLong(time); diff --git a/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs b/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs index 4f319be6b..fa0912f4c 100644 --- a/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs @@ -108,7 +108,7 @@ private void DropItem(GameSession session, long uid, int amount = -1) { return; } - if (drop.Transfer == null || drop.IsExpired() || !drop.Transfer.Flag.HasFlag(TransferFlag.Trade) || !drop.Transfer.Flag.HasFlag(TransferFlag.Split)) { + if (drop.Transfer == null || drop.IsExpired() || !drop.Transfer.Flag.HasFlag(TransferFlag.Trade) || !drop.Transfer.Flag.HasFlag(TransferFlag.Split) || drop.Transfer.Flag.HasFlag(TransferFlag.Bind)) { session.Item.Inventory.Discard(drop); return; } diff --git a/Maple2.Server.Game/PacketHandlers/MasteryHandler.cs b/Maple2.Server.Game/PacketHandlers/MasteryHandler.cs index 0e776848f..34a480923 100644 --- a/Maple2.Server.Game/PacketHandlers/MasteryHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/MasteryHandler.cs @@ -82,10 +82,10 @@ private void HandleCraft(GameSession session, IByteReader packet) { } foreach (int questId in entry.RequiredQuests) { - if (!session.Quest.TryGetQuest(questId, out Quest? quest) || quest.State != QuestState.Completed) { - session.Send(MasteryPacket.Error(MasteryError.s_mastery_error_lack_quest)); - return; - } + if (session.Quest.TryGetQuest(questId, out _)) continue; + + session.Send(MasteryPacket.Error(MasteryError.s_mastery_error_lack_quest)); + return; } if (session.Mastery[entry.Type] < entry.RequiredMastery) { diff --git a/Maple2.Server.Game/PacketHandlers/QuestHandler.cs b/Maple2.Server.Game/PacketHandlers/QuestHandler.cs index f1ace4970..741ceb34d 100644 --- a/Maple2.Server.Game/PacketHandlers/QuestHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/QuestHandler.cs @@ -201,6 +201,11 @@ private void HandleMapleGuide(GameSession session, IByteReader packet) { return; } + if (metadata.GoToMapId is Constant.DefaultHomeMapId) { + session.MigrateToInstance(Constant.DefaultHomeMapId, session.AccountId); + return; + } + session.Send(session.PrepareField(metadata.GoToMapId, metadata.GoToPortalId, session.CharacterId) ? FieldEnterPacket.Request(session.Player) : FieldEnterPacket.Error(MigrationError.s_move_err_default)); diff --git a/Maple2.Server.Game/Program.cs b/Maple2.Server.Game/Program.cs index 9f1f23e3d..518fb74de 100644 --- a/Maple2.Server.Game/Program.cs +++ b/Maple2.Server.Game/Program.cs @@ -39,6 +39,9 @@ DotEnv.Load(); +// Check for the --instanced parameter +bool overrideInstanced = args.Contains("--instanced"); + AddChannelResponse? response = null; try { GrpcChannel channel = GrpcChannel.ForAddress(Target.GrpcWorldUri); @@ -46,9 +49,8 @@ response = worldClient.AddChannel(new AddChannelRequest { GameIp = Target.GameIp.ToString(), GrpcGameIp = Target.GrpcGameIp, - InstancedContent = Target.InstancedContent, + InstancedContent = overrideInstanced || Target.InstancedContent, }); - } catch (RpcException e) { Log.Error(e, "Failed to get port information from World Server. Is World Server running?"); return; diff --git a/Maple2.Server.Tests/Game/Manager/Item/ItemCollectionTest.cs b/Maple2.Server.Tests/Game/Manager/Item/ItemCollectionTest.cs index a0b5f43cc..c33be9025 100644 --- a/Maple2.Server.Tests/Game/Manager/Item/ItemCollectionTest.cs +++ b/Maple2.Server.Tests/Game/Manager/Item/ItemCollectionTest.cs @@ -257,7 +257,102 @@ public void TestEnumeration() { [5] = item3, }; - CollectionAssert.AreEqual(new[] { item1, item2, item3 }, collection.ToList()); + CollectionAssert.AreEqual(new[] { + item1, + item2, + item3 + }, collection.ToList()); + } + + [Test] + public void TestSortFullInventory() { + var item1 = CreateItem(3000, rarity: 1, amount: 5); + var item2 = CreateItem(1000, rarity: 3, amount: 10); + var item3 = CreateItem(1000, rarity: 2, amount: 5); + var item4 = CreateItem(1000, rarity: 3, amount: 1); + var item5 = CreateItem(2000, rarity: 4, amount: 20); + + // Create a full collection + var collection = new ItemCollection(5); + collection.Add(item1); // slot 0 + collection.Add(item2); // slot 1 + collection.Add(item3); // slot 2 + collection.Add(item4); // slot 3 + collection.Add(item5); // slot 4 + + // Verify it's full + Assert.That(collection.OpenSlots, Is.EqualTo(0)); + + // Sort the full inventory + collection.Sort(); + + // Verify items are sorted correctly + Assert.That(item3, Is.EqualTo(collection[0])); + Assert.That(item4, Is.EqualTo(collection[1])); + Assert.That(item2, Is.EqualTo(collection[2])); + Assert.That(item5, Is.EqualTo(collection[3])); + Assert.That(item1, Is.EqualTo(collection[4])); + + // Verify all items still exist + Assert.That(collection.Count, Is.EqualTo(5)); + } + + [Test] + public void TestSortWithNullItems() { + var item1 = CreateItem(3000, rarity: 1, amount: 5); + var item2 = CreateItem(1000, rarity: 3, amount: 10); + var item3 = CreateItem(1000, rarity: 2, amount: 5); + + // Create collection with gaps + var collection = new ItemCollection(10); + collection[2] = item1; // Insert at specific slots to create gaps + collection[5] = item2; + collection[8] = item3; + + // Sort the inventory with gaps + collection.Sort(); + + // Verify items are sorted correctly with no gaps at the beginning + Assert.That(item3, Is.EqualTo(collection[0])); + Assert.That(item2, Is.EqualTo(collection[1])); + Assert.That(item1, Is.EqualTo(collection[2])); + Assert.IsNull(collection[3]); + + // Verify all items still exist + Assert.That(collection.Count, Is.EqualTo(3)); + } + + [Test] + public void TestSortAfterRemoving() { + var item1 = CreateItem(3000, rarity: 1, amount: 5); + var item2 = CreateItem(1000, rarity: 3, amount: 10); + var item3 = CreateItem(1000, rarity: 2, amount: 5); + var item4 = CreateItem(1000, rarity: 3, amount: 1); + var item5 = CreateItem(2000, rarity: 4, amount: 20); + + // Create a full collection + var collection = new ItemCollection(5); + collection.Add(item1); + collection.Add(item2); + collection.Add(item3); + collection.Add(item4); + collection.Add(item5); + + // Remove an item, creating a gap + collection.RemoveSlot(2, out _); + + // Sort the inventory with a gap + collection.Sort(); + + // Verify items are sorted correctly + Assert.That(item4, Is.EqualTo(collection[0])); + Assert.That(item2, Is.EqualTo(collection[1])); + Assert.That(item5, Is.EqualTo(collection[2])); + Assert.That(item1, Is.EqualTo(collection[3])); + Assert.IsNull(collection[4]); + + // Verify item count + Assert.That(collection.Count, Is.EqualTo(4)); } private static Model.Game.Item CreateItem(int id, int rarity = 1, int amount = 1) { @@ -266,6 +361,8 @@ private static Model.Game.Item CreateItem(int id, int rarity = 1, int amount = 1 var fakeLimit = new ItemMetadataLimit(Gender.All, 0, 0, 4, true, true, true, true, true, false, false, 0, Array.Empty(), Array.Empty()); var fakeLife = new ItemMetadataLife(0, 0); var fakeMetadata = new ItemMetadata(id, $"{id}", Array.Empty(), "", Array.Empty(), fakeLife, fakeProperty, fakeCustomize, fakeLimit, null, null, Array.Empty(), null, null, null, null); - return new Model.Game.Item(fakeMetadata, rarity, amount) { Uid = Rng.NextInt64() }; + return new Model.Game.Item(fakeMetadata, rarity, amount) { + Uid = Rng.NextInt64() + }; } } diff --git a/Maple2.Server.World/Containers/ChannelClientLookup.cs b/Maple2.Server.World/Containers/ChannelClientLookup.cs index 8e0f73e92..7c6e47a17 100644 --- a/Maple2.Server.World/Containers/ChannelClientLookup.cs +++ b/Maple2.Server.World/Containers/ChannelClientLookup.cs @@ -67,7 +67,7 @@ public IEnumerable Keys { if (activeChannel is null) { continue; } - if (activeChannel.Endpoint.Address.ToString() == gameIp && activeChannel.Status is ChannelStatus.Inactive) { + if (activeChannel.Endpoint.Address.ToString() == gameIp && activeChannel.Status is ChannelStatus.Inactive && activeChannel.InstancedContent == instancedContent) { return (activeChannel.GamePort, activeChannel.GrpcPort, activeChannel.Id); } } diff --git a/Maple2.Server.World/Service/WorldService.Migrate.cs b/Maple2.Server.World/Service/WorldService.Migrate.cs index a77491584..3564c3f93 100644 --- a/Maple2.Server.World/Service/WorldService.Migrate.cs +++ b/Maple2.Server.World/Service/WorldService.Migrate.cs @@ -3,6 +3,7 @@ using System.Security.Cryptography; using Grpc.Core; using Maple2.Server.Core.Constants; +using Maple2.Server.Core.Helpers; using Microsoft.Extensions.Caching.Memory; namespace Maple2.Server.World.Service; @@ -32,16 +33,23 @@ public override Task MigrateOut(MigrateOutRequest request, S throw new RpcException(new Status(StatusCode.Unavailable, $"No available game channels")); } - int channel; - - if (request.InstancedContent && channelClients.TryGetInstancedChannelId(out int channelId)) { - channel = channelId; + // Try to use requested channel or instanced channel + if (request.InstancedContent && channelClients.TryGetInstancedChannelId(out int channel)) { + if (!channelClients.TryGetActiveEndpoint(channel, out _)) { + throw new RpcException(new Status(StatusCode.Unavailable, "No available instanced game channel")); + } + } else if (request.HasChannel && channelClients.TryGetActiveEndpoint(request.Channel, out _)) { + channel = request.Channel; } else { - channel = request.HasChannel ? request.Channel : channelClients.FirstChannel(); + // Fall back to first available channel + channel = channelClients.FirstChannel(); + if (channel == -1) { + throw new RpcException(new Status(StatusCode.Unavailable, "No available game channels")); + } } if (!channelClients.TryGetActiveEndpoint(channel, out IPEndPoint? endpoint)) { - throw new RpcException(new Status(StatusCode.Unavailable, $"No available game channels")); + throw new RpcException(new Status(StatusCode.Unavailable, $"Channel {channel} not found")); } var gameEntry = new TokenEntry(request.Server, request.AccountId, request.CharacterId, new Guid(request.MachineId), channel, request.MapId, request.PortalId, request.RoomId, request.OwnerId, request.Type); From ab238ddb3eece7718744f905f651649007b7dc3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Mon, 10 Mar 2025 02:29:54 -0300 Subject: [PATCH 2/4] fix discard items --- Maple2.Server.Game/Manager/Items/InventoryManager.cs | 4 ++++ Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs | 1 + 2 files changed, 5 insertions(+) diff --git a/Maple2.Server.Game/Manager/Items/InventoryManager.cs b/Maple2.Server.Game/Manager/Items/InventoryManager.cs index ceceec518..f3e13cb9e 100644 --- a/Maple2.Server.Game/Manager/Items/InventoryManager.cs +++ b/Maple2.Server.Game/Manager/Items/InventoryManager.cs @@ -650,6 +650,10 @@ public void Discard(Item item, bool commit = false) { } } + public void AddItemToDelete(Item item) { + delete.Add(item); + } + public void Save(GameStorage.Request db) { lock (session.Item) { db.SaveItems(0, delete.ToArray()); diff --git a/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs b/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs index fa0912f4c..67e05f8bc 100644 --- a/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs @@ -113,6 +113,7 @@ private void DropItem(GameSession session, long uid, int amount = -1) { return; } + session.Item.Inventory.AddItemToDelete(drop); FieldItem fieldItem = session.Field!.SpawnItem(session.Player, drop); session.Field.Broadcast(FieldPacket.DropItem(fieldItem)); } From 76ab5628b63bb89b9efb52da6013812b1f819c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Mon, 10 Mar 2025 20:39:59 -0300 Subject: [PATCH 3/4] remove owner of item added to field --- .../Manager/Field/FieldManager/FieldManager.State.cs | 6 ++++++ Maple2.Server.Game/Manager/Items/InventoryManager.cs | 4 ---- Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs index 5b8322fae..701982860 100644 --- a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs +++ b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; using DotRecast.Detour.Crowd; +using Maple2.Database.Storage; using Maple2.Model.Enum; using Maple2.Model.Game; using Maple2.Model.Metadata; @@ -195,6 +196,11 @@ public FieldPortal SpawnPortal(Portal portal, int roomId, Vector3 position = def } public FieldItem SpawnItem(IActor owner, Item item) { + lock (item) { + using GameStorage.Request db = GameStorage.Context(); + db.SaveItems(0, item); + } + var fieldItem = new FieldItem(this, NextLocalId(), item) { Owner = owner, Position = owner.Position, diff --git a/Maple2.Server.Game/Manager/Items/InventoryManager.cs b/Maple2.Server.Game/Manager/Items/InventoryManager.cs index f3e13cb9e..ceceec518 100644 --- a/Maple2.Server.Game/Manager/Items/InventoryManager.cs +++ b/Maple2.Server.Game/Manager/Items/InventoryManager.cs @@ -650,10 +650,6 @@ public void Discard(Item item, bool commit = false) { } } - public void AddItemToDelete(Item item) { - delete.Add(item); - } - public void Save(GameStorage.Request db) { lock (session.Item) { db.SaveItems(0, delete.ToArray()); diff --git a/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs b/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs index 67e05f8bc..fa0912f4c 100644 --- a/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs @@ -113,7 +113,6 @@ private void DropItem(GameSession session, long uid, int amount = -1) { return; } - session.Item.Inventory.AddItemToDelete(drop); FieldItem fieldItem = session.Field!.SpawnItem(session.Player, drop); session.Field.Broadcast(FieldPacket.DropItem(fieldItem)); } From f79d3ae3489c39849a13a9638079f81a97b1d8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Tue, 11 Mar 2025 01:57:35 -0300 Subject: [PATCH 4/4] Update Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs Co-authored-by: Zin <62830952+Zintixx@users.noreply.github.com> --- Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs b/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs index fa0912f4c..bc6ccd157 100644 --- a/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs @@ -108,7 +108,7 @@ private void DropItem(GameSession session, long uid, int amount = -1) { return; } - if (drop.Transfer == null || drop.IsExpired() || !drop.Transfer.Flag.HasFlag(TransferFlag.Trade) || !drop.Transfer.Flag.HasFlag(TransferFlag.Split) || drop.Transfer.Flag.HasFlag(TransferFlag.Bind)) { + if (drop.Transfer == null || drop.IsExpired() || !drop.Transfer.Flag.HasFlag(TransferFlag.Trade) || !drop.Transfer.Flag.HasFlag(TransferFlag.Split) || drop.Transfer.Binding != null) { session.Item.Inventory.Discard(drop); return; }