diff --git a/Maple2.Database/Storage/Game/GameStorage.Nurturing.cs b/Maple2.Database/Storage/Game/GameStorage.Nurturing.cs index faa054ec4..b1d0f6377 100644 --- a/Maple2.Database/Storage/Game/GameStorage.Nurturing.cs +++ b/Maple2.Database/Storage/Game/GameStorage.Nurturing.cs @@ -19,7 +19,7 @@ public partial class Request { Exp = 0, ClaimedGiftForStage = 1, CreationTime = DateTime.Now, - LastFeedTime = DateTime.MinValue, + LastFeedTime = DateTime.Now, PlayedBy = [], }; Context.Nurturing.Add(nurturing); diff --git a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs index 4bd29977a..65506f8c7 100644 --- a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs +++ b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs @@ -276,6 +276,12 @@ private void ProcessPackets() { continue; } packet.handler.Handle(packet.session, packet.reader); + } catch (Exception ex) { + if (ex is IndexOutOfRangeException) { + logger.Error("Error processing packet {OpCode} for account ID {AccountId}. Packet: {Packet}", packet.handler.OpCode, packet.session.AccountId, packet.ToString()); + } else { + logger.Error(ex, "Error processing handler {Handler} for account ID {AccountId}. Packet: {Packet}", packet.handler.OpCode, packet.session.AccountId, packet.ToString()); + } } finally { ArrayPool.Shared.Return(packet.reader.Buffer); } diff --git a/Maple2.Server.Game/Manager/HousingManager.cs b/Maple2.Server.Game/Manager/HousingManager.cs index 2734f6aa1..560a80541 100644 --- a/Maple2.Server.Game/Manager/HousingManager.cs +++ b/Maple2.Server.Game/Manager/HousingManager.cs @@ -714,7 +714,8 @@ public bool TryRemoveCube(Plot plot, in Vector3B position, [NotNullWhen(true)] o } if (!session.Item.Furnishing.RetrieveCube(cube.Id)) { - throw new InvalidOperationException($"Failed to deposit cube {cube.Id} back into storage."); + logger.Error("Failed to deposit cube {CubeId} back into storage.", cube.Id); + return false; } DeleteCube(cube); diff --git a/Maple2.Server.Game/Manager/Items/InventoryManager.cs b/Maple2.Server.Game/Manager/Items/InventoryManager.cs index 83ff7e94b..ebd0b31eb 100644 --- a/Maple2.Server.Game/Manager/Items/InventoryManager.cs +++ b/Maple2.Server.Game/Manager/Items/InventoryManager.cs @@ -460,8 +460,8 @@ public bool ConsumeItemComponents(IReadOnlyList components, int q foreach (Item material in materialsByTag[ingredient.Tag]) { int consume = Math.Min(remainingIngredients, material.Amount); if (!session.Item.Inventory.Consume(material.Uid, consume)) { - Log.Fatal("Failed to consume item {ItemUid}", material.Uid); - throw new InvalidOperationException($"Fatal: Consuming item: {material.Uid}"); + Log.Fatal("Failed to consume item uid: {ItemUid}, item id: {ItemId}", material.Uid, material.Id); + throw new InvalidOperationException($"Fatal: Consuming item uid: {material.Uid}, item id: {material.Id}"); } remainingIngredients -= consume; @@ -473,8 +473,8 @@ public bool ConsumeItemComponents(IReadOnlyList components, int q foreach (Item material in materialsById[ingredient.ItemId]) { int consume = Math.Min(remainingIngredients, material.Amount); if (!session.Item.Inventory.Consume(material.Uid, consume)) { - Log.Fatal("Failed to consume item {ItemUid}", material.Uid); - throw new InvalidOperationException($"Fatal: Consuming item: {material.Uid}"); + Log.Fatal("Failed to consume item uid: {ItemUid}, item id: {ItemId}", material.Uid, material.Id); + throw new InvalidOperationException($"Fatal: Consuming item uid: {material.Uid}, item id: {material.Id}"); } remainingIngredients -= consume; diff --git a/Maple2.Server.Game/Manager/MailManager.cs b/Maple2.Server.Game/Manager/MailManager.cs index f463d9b48..bb40ea779 100644 --- a/Maple2.Server.Game/Manager/MailManager.cs +++ b/Maple2.Server.Game/Manager/MailManager.cs @@ -6,6 +6,7 @@ using Maple2.Server.Game.Packets; using Maple2.Server.Game.Session; using Maple2.Tools.Extensions; +using Serilog; namespace Maple2.Server.Game.Manager; @@ -20,6 +21,8 @@ public sealed class MailManager { private readonly SortedList inbox; + private readonly ILogger logger = Log.ForContext(); + public MailManager(GameSession session) { this.session = session; @@ -205,7 +208,8 @@ private MailError CollectInternal(GameStorage.Request db, Mail mail) { } if (!session.Item.Inventory.Add(item, notifyNew: true, commit: true)) { - throw new InvalidOperationException($"Mail {mail.Id} was collected but items could not be added to inventory."); + logger.Error("Mail {MailId} was collected but items could not be added to inventory. Item: {Item}", mail.Id, item); + return MailError.s_mail_error_receiveitem_to_inven; } mail.Items.RemoveAt(i); diff --git a/Maple2.Server.World/Containers/ChannelClientLookup.cs b/Maple2.Server.World/Containers/ChannelClientLookup.cs index 6fcb98b79..7d6fb2354 100644 --- a/Maple2.Server.World/Containers/ChannelClientLookup.cs +++ b/Maple2.Server.World/Containers/ChannelClientLookup.cs @@ -26,6 +26,7 @@ public class ChannelClientLookup : IEnumerable<(int, ChannelClient)> { private enum ChannelStatus { Active, Inactive, + Pending, } public void InjectDependencies(WorldServer worldSv, PlayerInfoLookup playerInfo) { @@ -33,31 +34,6 @@ public void InjectDependencies(WorldServer worldSv, PlayerInfoLookup playerInfo) playerInfoLookup = playerInfo; } - private class Channel { - public ChannelStatus Status { get; set; } - - public readonly int Id; - public readonly bool InstancedContent; - - public readonly IPEndPoint Endpoint; - public readonly ushort GamePort; - public readonly int GrpcPort; - - public readonly ChannelClient Client; - public readonly Health.HealthClient Health; - - public Channel(ChannelStatus status, int id, bool instancedContent, IPEndPoint endpoint, ChannelClient client, Health.HealthClient health, ushort gamePort, int grpcPort) { - Status = status; - Id = id; - InstancedContent = instancedContent; - Endpoint = endpoint; - Client = client; - Health = health; - GamePort = gamePort; - GrpcPort = grpcPort; - } - } - private readonly ConcurrentDictionary channels = []; private readonly ILogger logger = Log.ForContext(); @@ -73,13 +49,15 @@ public IEnumerable Keys { } public (ushort gamePort, int grpcPort, int channel) FindOrCreateChannelByIp(string gameIp, string grpcGameIp, bool instancedContent) { - // find the first channel that matches the ip, status and instanced content + // find the first channel that matches the ip, status and instanced content, but only if not Pending Channel? activeChannel = channels.Values.FirstOrDefault(channel => channel.Endpoint.Address.ToString() == gameIp && channel.Status is ChannelStatus.Inactive && channel.InstancedContent == instancedContent); if (activeChannel is not null) { + // Mark as pending before returning + activeChannel.Status = ChannelStatus.Pending; return (activeChannel.GamePort, activeChannel.GrpcPort, activeChannel.Id); } @@ -98,7 +76,7 @@ public int FirstChannel() { return -1; } - public bool TryGetInstancedChannelId([NotNullWhen(true)] out int channelId) { + public bool TryGetInstancedChannelId(out int channelId) { foreach (Channel channel in channels.Values.Where(ch => ch.Status is ChannelStatus.Active && ch.InstancedContent)) { channelId = channel.Id; return true; @@ -136,7 +114,7 @@ public bool TryGetActiveEndpoint(int channelId, [NotNullWhen(true)] out IPEndPoi int channelId = 1; if (!instancedContent) { // Find the smallest positive integer not used as a channel ID (excluding 0) - var usedIds = channels.Keys.Where(id => id > 0).ToHashSet(); + HashSet usedIds = channels.Keys.Where(id => id > 0).ToHashSet(); while (usedIds.Contains(channelId)) { channelId++; } @@ -165,7 +143,7 @@ public bool TryGetActiveEndpoint(int channelId, [NotNullWhen(true)] out IPEndPoi GrpcChannel grpcChannel = GrpcChannel.ForAddress(grpcUri); var client = new ChannelClient(grpcChannel); var healthClient = new Health.HealthClient(grpcChannel); - var activeChannel = new Channel(ChannelStatus.Inactive, channelId, instancedContent, gameEndpoint, client, healthClient, (ushort) newGamePort, newGrpcChannelPort); + var activeChannel = new Channel(ChannelStatus.Pending, channelId, instancedContent, gameEndpoint, client, healthClient, (ushort) newGamePort, newGrpcChannelPort); if (!channels.TryAdd(channelId, activeChannel)) { logger.Error("Failed to add channel {Channel}", channelId); return (0, 0, -1); @@ -187,13 +165,13 @@ private async Task MonitorChannel(Channel channel, CancellationTokenSource cance cancellationToken: cancellationToken); switch (response.Status) { case HealthCheckResponse.Types.ServingStatus.Serving: - if (channel.Status is ChannelStatus.Inactive) { + if (channel.Status is ChannelStatus.Inactive || channel.Status is ChannelStatus.Pending) { logger.Information("Channel {Channel} has become active", channel.Id); Active(channel); } break; default: - if (channel.Status is ChannelStatus.Active) { + if (channel.Status is ChannelStatus.Active || channel.Status is ChannelStatus.Pending) { Inactive(channel); logger.Information("Channel {Channel} has become inactive due to {Status}", channel.Id, response.Status); #if !DEBUG @@ -284,8 +262,35 @@ private void UpdateChannels(int exclueChannelBroadcast = -1) { List channelList = channels.Values.Where(ch => ch.Status is ChannelStatus.Active && !ch.InstancedContent).Select(ch => ch.Id).ToList(); channelClient.UpdateChannels(new Maple2.Server.Channel.Service.ChannelsUpdateRequest { - Channels = { channelList }, + Channels = { + channelList, + }, }); } } + + private class Channel { + public ChannelStatus Status { get; set; } + + public readonly int Id; + public readonly bool InstancedContent; + + public readonly IPEndPoint Endpoint; + public readonly ushort GamePort; + public readonly int GrpcPort; + + public readonly ChannelClient Client; + public readonly Health.HealthClient Health; + + public Channel(ChannelStatus status, int id, bool instancedContent, IPEndPoint endpoint, ChannelClient client, Health.HealthClient health, ushort gamePort, int grpcPort) { + Status = status; + Id = id; + InstancedContent = instancedContent; + Endpoint = endpoint; + Client = client; + Health = health; + GamePort = gamePort; + GrpcPort = grpcPort; + } + } }