diff --git a/.run/Server_Dockerfile.run.xml b/.run/Server_Dockerfile.run.xml new file mode 100644 index 0000000..a509d2d --- /dev/null +++ b/.run/Server_Dockerfile.run.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 70b1290..948de68 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # What is this project about? This is a continuation and rewrite of the original Project Infinity for Wonderking. + +## Important notes for developers + +* Avoid using statements with TcpSession, AuthSession or any type that inherits Session outside the session itself. diff --git a/Server/AuthSession.cs b/Server/AuthSession.cs index ae0f4c9..788c4c3 100644 --- a/Server/AuthSession.cs +++ b/Server/AuthSession.cs @@ -29,14 +29,14 @@ public class AuthSession : TcpSession return base.Send(buffer); } - public void Send(IPacket packet) + public Task SendAsync(IPacket packet) { var type = packet.GetType(); this._logger.LogInformation("Packet of type {Type} is being serialized", type.Name); var packetIdAttribute = type.GetCustomAttribute(); if (packetIdAttribute == null) { - return; + return Task.CompletedTask; } var opcode = packetIdAttribute.Code; @@ -63,7 +63,8 @@ public class AuthSession : TcpSession this._logger.LogInformation("Packet data being parsed is: {Data}", BitConverter.ToString(packetData.ToArray())); this._logger.LogInformation("Packet being parsed is: {Data}", BitConverter.ToString(buffer.ToArray())); - this.Send(buffer); + this.SendAsync(buffer); + return Task.CompletedTask; } protected override void OnReceived(byte[] buffer, long offset, long size) diff --git a/Server/PacketHandlers/ChannelSelectionHandler.cs b/Server/PacketHandlers/ChannelSelectionHandler.cs index 9e474ff..0a399a9 100644 --- a/Server/PacketHandlers/ChannelSelectionHandler.cs +++ b/Server/PacketHandlers/ChannelSelectionHandler.cs @@ -39,7 +39,7 @@ public class ChannelSelectionHandler : IPacketHandler { ChannelIsFullFlag = 0, Endpoint = "127.0.0.1", - Port = 12345, + Port = 2000, Characters = await _wonderkingContext.Characters.AsNoTracking() .Where(c => c.Account.Id == authSession.AccountId) .Select(c => @@ -54,20 +54,19 @@ public class ChannelSelectionHandler : IPacketHandler Stats = c.BaseStats, Health = c.Health, Mana = c.Mana, - EquippedItems = - c.InventoryItems.Where(item => item.InventoryTab == InventoryTab.WornEquipment) - .Select(item => item.ItemId) - .ToArray(), - EquippedCashItems = c.InventoryItems + EquippedItems = GetItemIDsByInventoryTab(c.InventoryItems + .Where(item => item.InventoryTab == InventoryTab.WornEquipment) + .Select(item => new Tuple(item.ItemId, item.Slot)).ToArray()), + EquippedCashItems = GetItemIDsByInventoryTab(c.InventoryItems .Where(item => item.InventoryTab == InventoryTab.WornCashEquipment) - .Select(item => item.ItemId) - .ToArray() + .Select(item => new Tuple(item.ItemId, item.Slot)).ToArray()), }) .ToArrayAsync().ConfigureAwait(true), }; guildNameResponsePacket.GuildNames = await _wonderkingContext.Characters .Where(c => c.Account.Id == authSession.AccountId) + .Where(c => c.Guild != null) .Select(character => character.Guild.Name).ToArrayAsync().ConfigureAwait(true); } else @@ -81,11 +80,23 @@ public class ChannelSelectionHandler : IPacketHandler }; } - authSession.Send(responsePacket); + await authSession.SendAsync(responsePacket).ConfigureAwait(false); if (guildNameResponsePacket.GuildNames.Length > 0 && guildNameResponsePacket.GuildNames.Select(n => n != string.Empty).Any()) { - authSession.Send(guildNameResponsePacket); + await authSession.SendAsync(guildNameResponsePacket).ConfigureAwait(false); } } + + private static ushort[] GetItemIDsByInventoryTab(Tuple[] items) + { + var ids = new ushort[20]; + + for (var i = 0; i < 20; i++) + { + ids[i] = items.FirstOrDefault(item => item.Item2 == i)?.Item1 ?? 0; + } + + return ids; + } } diff --git a/Server/PacketHandlers/CharacterCreationHandler.cs b/Server/PacketHandlers/CharacterCreationHandler.cs index 7e9dcb4..9a1cbf1 100644 --- a/Server/PacketHandlers/CharacterCreationHandler.cs +++ b/Server/PacketHandlers/CharacterCreationHandler.cs @@ -28,8 +28,13 @@ public class CharacterCreationHandler : IPacketHandler public async Task HandleAsync(CharacterCreationPacket packet, TcpSession session) { var authSession = session as AuthSession; + if (authSession is null) + { + return; + } + var account = - _wonderkingContext.Accounts.FirstOrDefault(a => authSession != null && a.Id == authSession.AccountId); + _wonderkingContext.Accounts.FirstOrDefault(a => a.Id == authSession.AccountId); var mappedDefaultItems = _characterStatsMapping.DefaultCharacterMapping.Items .Select(i => _itemObjectPoolService.GetBaseInventoryItem(i.Id, i.Quantity)).ToArray(); @@ -83,10 +88,10 @@ public class CharacterCreationHandler : IPacketHandler await _wonderkingContext.SaveChangesAsync().ConfigureAwait(true); var amountOfCharacters = await _wonderkingContext.Characters.AsNoTrackingWithIdentityResolution() - .CountAsync(c => authSession != null && c.Account.Id == authSession.AccountId).ConfigureAwait(true); + .CountAsync(c => c.Account.Id == authSession.AccountId).ConfigureAwait(true); var character = await _wonderkingContext.Characters.AsNoTrackingWithIdentityResolution() - .Where(c => authSession != null && c.Account.Id == authSession.AccountId && c.Name == packet.Name) + .Where(c => c.Account.Id == authSession.AccountId && c.Name == packet.Name) .Select(c => new CharacterData { @@ -99,20 +104,19 @@ public class CharacterCreationHandler : IPacketHandler Health = c.Health, Mana = c.Mana, EquippedItems = - c.InventoryItems.Where(item => item.InventoryTab == InventoryTab.WornEquipment) - .Select(item => item.ItemId) - .ToArray(), - EquippedCashItems = c.InventoryItems + GetItemIDsByInventoryTab(c.InventoryItems + .Where(item => item.InventoryTab == InventoryTab.WornEquipment) + .Select(item => new Tuple(item.ItemId, item.Slot)).ToArray()), + EquippedCashItems = GetItemIDsByInventoryTab(c.InventoryItems .Where(item => item.InventoryTab == InventoryTab.WornCashEquipment) - .Select(item => item.ItemId) - .ToArray(), + .Select(item => new Tuple(item.ItemId, item.Slot)).ToArray()), }).FirstAsync().ConfigureAwait(true); - authSession?.Send(new CharacterCreationResponsePacket + await authSession.SendAsync(new CharacterCreationResponsePacket { Character = character, - Slot = amountOfCharacters - 1, + Slot = packet.Slot, isDuplicate = false, - }); + }).ConfigureAwait(false); } private static int CalculateCurrentHealth(ushort level, JobSpecificMapping firstJobConfig) @@ -126,4 +130,16 @@ public class CharacterCreationHandler : IPacketHandler return (int)((level - 1) * firstJobConfig.DynamicStats.ManaPerLevel + firstJobConfig.BaseStats.Wisdom * firstJobConfig.DynamicStats.ManaPerWisdom); } + + private static ushort[] GetItemIDsByInventoryTab(Tuple[] items) + { + var ids = new ushort[20]; + + for (var i = 0; i < 20; i++) + { + ids[i] = items.FirstOrDefault(item => item.Item2 == i)?.Item1 ?? 0; + } + + return ids; + } } diff --git a/Server/PacketHandlers/CharacterDeletionHandler.cs b/Server/PacketHandlers/CharacterDeletionHandler.cs index 920131a..3807d46 100644 --- a/Server/PacketHandlers/CharacterDeletionHandler.cs +++ b/Server/PacketHandlers/CharacterDeletionHandler.cs @@ -17,8 +17,7 @@ public class CharacterDeletionHandler : IPacketHandler public async Task HandleAsync(CharacterDeletePacket packet, TcpSession session) { - using var authSession = session as AuthSession; - if (authSession == null) + if (session is not AuthSession authSession) { session.Disconnect(); return; @@ -30,13 +29,13 @@ public class CharacterDeletionHandler : IPacketHandler var response = new CharacterDeleteResponsePacket { IsDeleted = 0 }; if (character == null) { - authSession.Send(response); + await authSession.SendAsync(response).ConfigureAwait(false); return; } _wonderkingContext.Characters.Remove(character); await _wonderkingContext.SaveChangesAsync().ConfigureAwait(false); - authSession.Send(response); + await authSession.SendAsync(response).ConfigureAwait(false); } } diff --git a/Server/PacketHandlers/CharacterNameCheckHandler.cs b/Server/PacketHandlers/CharacterNameCheckHandler.cs index c97af26..7804da6 100644 --- a/Server/PacketHandlers/CharacterNameCheckHandler.cs +++ b/Server/PacketHandlers/CharacterNameCheckHandler.cs @@ -18,8 +18,11 @@ public class CharacterNameCheckHandler : IPacketHandler c.Name == packet.Name); var responsePacket = new CharacterNameCheckPacketResponse { IsTaken = isTaken }; - var authSession = session as AuthSession; - authSession?.Send(responsePacket); + if (session is AuthSession authSession) + { + return authSession.SendAsync(responsePacket); + } + return Task.CompletedTask; } } diff --git a/Server/PacketHandlers/LoginHandler.cs b/Server/PacketHandlers/LoginHandler.cs index 1d60f1b..86f57ae 100644 --- a/Server/PacketHandlers/LoginHandler.cs +++ b/Server/PacketHandlers/LoginHandler.cs @@ -119,6 +119,6 @@ public class LoginHandler : IPacketHandler } _logger.LogInformation("LoginResponsePacket: {@LoginResponsePacket}", loginResponsePacket); - sess?.Send(loginResponsePacket); + sess?.SendAsync(loginResponsePacket); } } diff --git a/Server/Program.cs b/Server/Program.cs index e203b3b..0c67d9b 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -42,7 +42,8 @@ builder.Services.AddDbContextPool(o => builder.Services.AddSingleton(loggerFactory); builder.Services.AddSingleton(); builder.Services.AddSingleton(); -builder.Services.AddHostedService(); +builder.Services.AddHostedService(provider => + provider.GetService() ?? throw new InvalidOperationException()); builder.Services.AddHostedService(provider => provider.GetService() ?? throw new InvalidOperationException()); builder.Services.AddMassTransit(x => diff --git a/Server/Server.csproj b/Server/Server.csproj index 5745021..95af43f 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -8,7 +8,6 @@ Server default true - net8.0 true strict Timothy (RaiNote) Schenk @@ -20,6 +19,7 @@ True LICENSE latest-recommended + net8.0 @@ -82,7 +82,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Server/Services/ItemObjectPoolService.cs b/Server/Services/ItemObjectPoolService.cs index 6b190e5..69cdfbe 100644 --- a/Server/Services/ItemObjectPoolService.cs +++ b/Server/Services/ItemObjectPoolService.cs @@ -71,8 +71,18 @@ public class ItemObjectPoolService : IHostedService { ItemId = itemId, Count = count, - Slot = 0, - InventoryTab = InventoryTab.WornEquipment, + Slot = (byte)item.SlotNo1, + InventoryTab = + item.ItemType switch + { + 1 => InventoryTab.WornCashEquipment, + 2 => isWorn ? InventoryTab.WornEquipment : InventoryTab.Equipment, + 3 => InventoryTab.Etc, + 4 => isWorn ? InventoryTab.WornCashEquipment : InventoryTab.Cash, + 5 => InventoryTab.Warehouse, + 0 => InventoryTab.WornEquipment, + _ => 0 + }, Level = item.MinimumLevelRequirement, Rarity = 0, AddOption = 0, diff --git a/Server/Services/WonderkingAuthServer.cs b/Server/Services/WonderkingAuthServer.cs index 30ae4e8..7c79c80 100644 --- a/Server/Services/WonderkingAuthServer.cs +++ b/Server/Services/WonderkingAuthServer.cs @@ -27,6 +27,7 @@ public class WonderkingAuthServer : TcpServer, IHostedService public Task StopAsync(CancellationToken cancellationToken) { + this.DisconnectAll(); this.Stop(); return Task.CompletedTask; } diff --git a/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs b/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs index 12b93fc..3245cb2 100644 --- a/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs +++ b/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs @@ -29,39 +29,41 @@ public class ChannelSelectionResponsePacket : IPacket // Character Data for (var i = 0; i < Characters.Length; i++) { + int offset = 20 + (i * 132); var character = Characters[i]; - BinaryPrimitives.WriteInt32LittleEndian(data.Slice(20 + (i * 132), 4), i); - Encoding.ASCII.GetBytes(character.Name, data.Slice(24 + (i * 132), 20)); + // Character Data + BinaryPrimitives.WriteInt32LittleEndian(data.Slice(offset, 4), i); + Encoding.ASCII.GetBytes(character.Name, data.Slice(offset + 4, 20)); // Job Data - data[44 + (i * 132)] = character.Job.FirstJob; - data[45 + (i * 132)] = character.Job.SecondJob; - data[46 + (i * 132)] = character.Job.ThirdJob; - data[47 + (i * 132)] = character.Job.FourthJob; + data[offset + 24] = character.Job.FirstJob; + data[offset + 25] = character.Job.SecondJob; + data[offset + 26] = character.Job.ThirdJob; + data[offset + 27] = character.Job.FourthJob; - data[48 + (i * 132)] = (byte)character.Gender; - BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(49 + (i * 132), 2), character.Level); - data[51 + (i * 132)] = (byte)character.Experience; + data[offset + 28] = (byte)character.Gender; + BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(offset + 29, 2), character.Level); + data[offset + 31] = (byte)character.Experience; // Stats - BinaryPrimitives.WriteInt16LittleEndian(data.Slice(52 + (i * 132), 2), character.Stats.Strength); - BinaryPrimitives.WriteInt16LittleEndian(data.Slice(54 + (i * 132), 2), character.Stats.Dexterity); - BinaryPrimitives.WriteInt16LittleEndian(data.Slice(56 + (i * 132), 2), character.Stats.Intelligence); - BinaryPrimitives.WriteInt16LittleEndian(data.Slice(58 + (i * 132), 2), character.Stats.Vitality); - BinaryPrimitives.WriteInt16LittleEndian(data.Slice(60 + (i * 132), 2), character.Stats.Luck); - BinaryPrimitives.WriteInt16LittleEndian(data.Slice(62 + (i * 132), 2), character.Stats.Wisdom); + BinaryPrimitives.WriteInt16LittleEndian(data.Slice(offset + 32, 2), character.Stats.Strength); + BinaryPrimitives.WriteInt16LittleEndian(data.Slice(offset + 34, 2), character.Stats.Dexterity); + BinaryPrimitives.WriteInt16LittleEndian(data.Slice(offset + 36, 2), character.Stats.Intelligence); + BinaryPrimitives.WriteInt16LittleEndian(data.Slice(offset + 38, 2), character.Stats.Vitality); + BinaryPrimitives.WriteInt16LittleEndian(data.Slice(offset + 40, 2), character.Stats.Luck); + BinaryPrimitives.WriteInt16LittleEndian(data.Slice(offset + 42, 2), character.Stats.Wisdom); - BinaryPrimitives.WriteInt32LittleEndian(data.Slice(64 + (i * 132), 4), character.Health); - BinaryPrimitives.WriteInt32LittleEndian(data.Slice(68 + (i * 132), 4), character.Mana); + BinaryPrimitives.WriteInt32LittleEndian(data.Slice(offset + 44, 4), character.Health); + BinaryPrimitives.WriteInt32LittleEndian(data.Slice(offset + 48, 4), character.Mana); for (var j = 0; j < 20; j++) { // Equipped Items - BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(72 + (i * 132) + (j * 2), 2), + BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(offset + 52 + j * 2, 2), character.EquippedItems.Length > j ? character.EquippedItems[j] : (ushort)0); // Equipped Cash Items - BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(112 + (i * 132) + (j * 2), 2), + BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(offset + 92 + j * 2, 2), character.EquippedCashItems.Length > j ? character.EquippedCashItems[j] : (ushort)0); } } diff --git a/Wonderking/Packets/Outgoing/CharacterCreationResponsePacket.cs b/Wonderking/Packets/Outgoing/CharacterCreationResponsePacket.cs index 6419417..cce221c 100644 --- a/Wonderking/Packets/Outgoing/CharacterCreationResponsePacket.cs +++ b/Wonderking/Packets/Outgoing/CharacterCreationResponsePacket.cs @@ -20,6 +20,8 @@ public class CharacterCreationResponsePacket : IPacket { Span data = stackalloc byte[1 + 132]; data[0] = isDuplicate ? (byte)1 : (byte)0; + + // Character Data BinaryPrimitives.WriteInt32LittleEndian(data.Slice(1, 4), Slot); Encoding.ASCII.GetBytes(Character.Name, data.Slice(5, 20));