Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using DotRecast.Detour.Crowd;
using Maple2.Database.Storage;
using Maple2.Model.Common;
using Maple2.Model.Enum;
using Maple2.Model.Game;
Expand Down Expand Up @@ -200,6 +201,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,
Expand Down
2 changes: 1 addition & 1 deletion Maple2.Server.Game/Manager/Items/ItemCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Comment thread
AngeloTadeucci marked this conversation as resolved.
item.Slot = i;
uidToSlot[items[i]!.Uid] = i;
i++;
Expand Down
8 changes: 4 additions & 4 deletions Maple2.Server.Game/PacketHandlers/HomeBankHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public class HomeBankHandler : PacketHandler<GameSession> {
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) {
Expand All @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion Maple2.Server.Game/PacketHandlers/ItemInventoryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.Binding != null) {
session.Item.Inventory.Discard(drop);
return;
}
Expand Down
8 changes: 4 additions & 4 deletions Maple2.Server.Game/PacketHandlers/MasteryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isnt this supposed to be the reverse?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it found a quest, then it's fine. If not send error

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then it should break, ya? instead of continue

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we break we won't check the next quests. If it loops through all quests and they are found we can keep going, if any of them are not found then we send the error and exit early


session.Send(MasteryPacket.Error(MasteryError.s_mastery_error_lack_quest));
return;
}

if (session.Mastery[entry.Type] < entry.RequiredMastery) {
Expand Down
5 changes: 5 additions & 0 deletions Maple2.Server.Game/PacketHandlers/QuestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
6 changes: 4 additions & 2 deletions Maple2.Server.Game/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,18 @@

DotEnv.Load();

// Check for the --instanced parameter
bool overrideInstanced = args.Contains("--instanced");

AddChannelResponse? response = null;
try {
GrpcChannel channel = GrpcChannel.ForAddress(Target.GrpcWorldUri);
var worldClient = new WorldClient(channel);
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;
Expand Down
101 changes: 99 additions & 2 deletions Maple2.Server.Tests/Game/Manager/Item/ItemCollectionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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<JobCode>(), Array.Empty<JobCode>());
var fakeLife = new ItemMetadataLife(0, 0);
var fakeMetadata = new ItemMetadata(id, $"{id}", Array.Empty<EquipSlot>(), "", Array.Empty<DefaultHairMetadata>(), fakeLife, fakeProperty, fakeCustomize, fakeLimit, null, null, Array.Empty<ItemMetadataAdditionalEffect>(), 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()
};
}
}
2 changes: 1 addition & 1 deletion Maple2.Server.World/Containers/ChannelClientLookup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public IEnumerable<int> 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);
}
}
Expand Down
20 changes: 14 additions & 6 deletions Maple2.Server.World/Service/WorldService.Migrate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -32,16 +33,23 @@ public override Task<MigrateOutResponse> 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);
Expand Down