bugfix/87-client-dc-on-char-deletion #90

Merged
rainote merged 12 commits from bugfix/87-client-dc-on-char-deletion into master 2023-11-19 14:10:19 +00:00
14 changed files with 122 additions and 56 deletions

View file

@ -0,0 +1,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Server/Dockerfile" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="continuity" />
<option name="buildCliOptions" value="--platform linux/amd64" />
<option name="buildKitEnabled" value="true" />
<option name="buildOnly" value="true" />
<option name="contextFolderPath" value="." />
<option name="sourceFilePath" value="Server/Dockerfile" />
</settings>
</deployment>
<EXTENSION ID="com.jetbrains.rider.docker.debug" isFastModeEnabled="true" isSslEnabled="false" />
<method v="2" />
</configuration>
</component>

View file

@ -1,2 +1,6 @@
# What is this project about? # What is this project about?
This is a continuation and rewrite of the original Project Infinity for Wonderking. 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.

View file

@ -29,14 +29,14 @@ public class AuthSession : TcpSession
return base.Send(buffer); return base.Send(buffer);
} }
public void Send(IPacket packet) public Task SendAsync(IPacket packet)
{ {
var type = packet.GetType(); var type = packet.GetType();
this._logger.LogInformation("Packet of type {Type} is being serialized", type.Name); this._logger.LogInformation("Packet of type {Type} is being serialized", type.Name);
var packetIdAttribute = type.GetCustomAttribute<PacketIdAttribute>(); var packetIdAttribute = type.GetCustomAttribute<PacketIdAttribute>();
if (packetIdAttribute == null) if (packetIdAttribute == null)
{ {
return; return Task.CompletedTask;
} }
var opcode = packetIdAttribute.Code; 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 data being parsed is: {Data}", BitConverter.ToString(packetData.ToArray()));
this._logger.LogInformation("Packet being parsed is: {Data}", BitConverter.ToString(buffer.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) protected override void OnReceived(byte[] buffer, long offset, long size)

View file

@ -39,7 +39,7 @@ public class ChannelSelectionHandler : IPacketHandler<ChannelSelectionPacket>
{ {
ChannelIsFullFlag = 0, ChannelIsFullFlag = 0,
Endpoint = "127.0.0.1", Endpoint = "127.0.0.1",
Port = 12345, Port = 2000,
Characters = await _wonderkingContext.Characters.AsNoTracking() Characters = await _wonderkingContext.Characters.AsNoTracking()
.Where(c => c.Account.Id == authSession.AccountId) .Where(c => c.Account.Id == authSession.AccountId)
.Select(c => .Select(c =>
@ -54,20 +54,19 @@ public class ChannelSelectionHandler : IPacketHandler<ChannelSelectionPacket>
Stats = c.BaseStats, Stats = c.BaseStats,
Health = c.Health, Health = c.Health,
Mana = c.Mana, Mana = c.Mana,
EquippedItems = EquippedItems = GetItemIDsByInventoryTab(c.InventoryItems
c.InventoryItems.Where(item => item.InventoryTab == InventoryTab.WornEquipment) .Where(item => item.InventoryTab == InventoryTab.WornEquipment)
.Select(item => item.ItemId) .Select(item => new Tuple<ushort, byte>(item.ItemId, item.Slot)).ToArray()),
.ToArray(), EquippedCashItems = GetItemIDsByInventoryTab(c.InventoryItems
EquippedCashItems = c.InventoryItems
.Where(item => item.InventoryTab == InventoryTab.WornCashEquipment) .Where(item => item.InventoryTab == InventoryTab.WornCashEquipment)
.Select(item => item.ItemId) .Select(item => new Tuple<ushort, byte>(item.ItemId, item.Slot)).ToArray()),
.ToArray()
}) })
.ToArrayAsync().ConfigureAwait(true), .ToArrayAsync().ConfigureAwait(true),
}; };
guildNameResponsePacket.GuildNames = await _wonderkingContext.Characters guildNameResponsePacket.GuildNames = await _wonderkingContext.Characters
.Where(c => c.Account.Id == authSession.AccountId) .Where(c => c.Account.Id == authSession.AccountId)
.Where(c => c.Guild != null)
.Select(character => character.Guild.Name).ToArrayAsync().ConfigureAwait(true); .Select(character => character.Guild.Name).ToArrayAsync().ConfigureAwait(true);
} }
else else
@ -81,11 +80,23 @@ public class ChannelSelectionHandler : IPacketHandler<ChannelSelectionPacket>
}; };
} }
authSession.Send(responsePacket); await authSession.SendAsync(responsePacket).ConfigureAwait(false);
if (guildNameResponsePacket.GuildNames.Length > 0 && if (guildNameResponsePacket.GuildNames.Length > 0 &&
guildNameResponsePacket.GuildNames.Select(n => n != string.Empty).Any()) guildNameResponsePacket.GuildNames.Select(n => n != string.Empty).Any())
{ {
authSession.Send(guildNameResponsePacket); await authSession.SendAsync(guildNameResponsePacket).ConfigureAwait(false);
} }
} }
private static ushort[] GetItemIDsByInventoryTab(Tuple<ushort, byte>[] 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;
}
} }

View file

@ -28,8 +28,13 @@ public class CharacterCreationHandler : IPacketHandler<CharacterCreationPacket>
public async Task HandleAsync(CharacterCreationPacket packet, TcpSession session) public async Task HandleAsync(CharacterCreationPacket packet, TcpSession session)
{ {
var authSession = session as AuthSession; var authSession = session as AuthSession;
if (authSession is null)
{
return;
}
var account = 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 var mappedDefaultItems = _characterStatsMapping.DefaultCharacterMapping.Items
.Select(i => _itemObjectPoolService.GetBaseInventoryItem(i.Id, i.Quantity)).ToArray(); .Select(i => _itemObjectPoolService.GetBaseInventoryItem(i.Id, i.Quantity)).ToArray();
@ -83,10 +88,10 @@ public class CharacterCreationHandler : IPacketHandler<CharacterCreationPacket>
await _wonderkingContext.SaveChangesAsync().ConfigureAwait(true); await _wonderkingContext.SaveChangesAsync().ConfigureAwait(true);
var amountOfCharacters = await _wonderkingContext.Characters.AsNoTrackingWithIdentityResolution() 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() 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 => .Select(c =>
new CharacterData new CharacterData
{ {
@ -99,20 +104,19 @@ public class CharacterCreationHandler : IPacketHandler<CharacterCreationPacket>
Health = c.Health, Health = c.Health,
Mana = c.Mana, Mana = c.Mana,
EquippedItems = EquippedItems =
c.InventoryItems.Where(item => item.InventoryTab == InventoryTab.WornEquipment) GetItemIDsByInventoryTab(c.InventoryItems
.Select(item => item.ItemId) .Where(item => item.InventoryTab == InventoryTab.WornEquipment)
.ToArray(), .Select(item => new Tuple<ushort, byte>(item.ItemId, item.Slot)).ToArray()),
EquippedCashItems = c.InventoryItems EquippedCashItems = GetItemIDsByInventoryTab(c.InventoryItems
.Where(item => item.InventoryTab == InventoryTab.WornCashEquipment) .Where(item => item.InventoryTab == InventoryTab.WornCashEquipment)
.Select(item => item.ItemId) .Select(item => new Tuple<ushort, byte>(item.ItemId, item.Slot)).ToArray()),
.ToArray(),
}).FirstAsync().ConfigureAwait(true); }).FirstAsync().ConfigureAwait(true);
authSession?.Send(new CharacterCreationResponsePacket await authSession.SendAsync(new CharacterCreationResponsePacket
{ {
Character = character, Character = character,
Slot = amountOfCharacters - 1, Slot = packet.Slot,
isDuplicate = false, isDuplicate = false,
}); }).ConfigureAwait(false);
} }
private static int CalculateCurrentHealth(ushort level, JobSpecificMapping firstJobConfig) private static int CalculateCurrentHealth(ushort level, JobSpecificMapping firstJobConfig)
@ -126,4 +130,16 @@ public class CharacterCreationHandler : IPacketHandler<CharacterCreationPacket>
return (int)((level - 1) * firstJobConfig.DynamicStats.ManaPerLevel + return (int)((level - 1) * firstJobConfig.DynamicStats.ManaPerLevel +
firstJobConfig.BaseStats.Wisdom * firstJobConfig.DynamicStats.ManaPerWisdom); firstJobConfig.BaseStats.Wisdom * firstJobConfig.DynamicStats.ManaPerWisdom);
} }
private static ushort[] GetItemIDsByInventoryTab(Tuple<ushort, byte>[] 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;
}
} }

View file

@ -17,8 +17,7 @@ public class CharacterDeletionHandler : IPacketHandler<CharacterDeletePacket>
public async Task HandleAsync(CharacterDeletePacket packet, TcpSession session) public async Task HandleAsync(CharacterDeletePacket packet, TcpSession session)
{ {
using var authSession = session as AuthSession; if (session is not AuthSession authSession)
if (authSession == null)
{ {
session.Disconnect(); session.Disconnect();
return; return;
@ -30,13 +29,13 @@ public class CharacterDeletionHandler : IPacketHandler<CharacterDeletePacket>
var response = new CharacterDeleteResponsePacket { IsDeleted = 0 }; var response = new CharacterDeleteResponsePacket { IsDeleted = 0 };
if (character == null) if (character == null)
{ {
authSession.Send(response); await authSession.SendAsync(response).ConfigureAwait(false);
return; return;
} }
_wonderkingContext.Characters.Remove(character); _wonderkingContext.Characters.Remove(character);
await _wonderkingContext.SaveChangesAsync().ConfigureAwait(false); await _wonderkingContext.SaveChangesAsync().ConfigureAwait(false);
authSession.Send(response); await authSession.SendAsync(response).ConfigureAwait(false);
} }
} }

View file

@ -18,8 +18,11 @@ public class CharacterNameCheckHandler : IPacketHandler<CharacterNameCheckPacket
{ {
var isTaken = _wonderkingContext.Characters.Any(c => c.Name == packet.Name); var isTaken = _wonderkingContext.Characters.Any(c => c.Name == packet.Name);
var responsePacket = new CharacterNameCheckPacketResponse { IsTaken = isTaken }; var responsePacket = new CharacterNameCheckPacketResponse { IsTaken = isTaken };
var authSession = session as AuthSession; if (session is AuthSession authSession)
authSession?.Send(responsePacket); {
return authSession.SendAsync(responsePacket);
}
return Task.CompletedTask; return Task.CompletedTask;
} }
} }

View file

@ -119,6 +119,6 @@ public class LoginHandler : IPacketHandler<LoginInfoPacket>
} }
_logger.LogInformation("LoginResponsePacket: {@LoginResponsePacket}", loginResponsePacket); _logger.LogInformation("LoginResponsePacket: {@LoginResponsePacket}", loginResponsePacket);
sess?.Send(loginResponsePacket); sess?.SendAsync(loginResponsePacket);
} }
} }

View file

@ -42,7 +42,8 @@ builder.Services.AddDbContextPool<WonderkingContext>(o =>
builder.Services.AddSingleton<ILoggerFactory>(loggerFactory); builder.Services.AddSingleton<ILoggerFactory>(loggerFactory);
builder.Services.AddSingleton<PacketDistributorService>(); builder.Services.AddSingleton<PacketDistributorService>();
builder.Services.AddSingleton<ItemObjectPoolService>(); builder.Services.AddSingleton<ItemObjectPoolService>();
builder.Services.AddHostedService<ItemObjectPoolService>(); builder.Services.AddHostedService(provider =>
provider.GetService<ItemObjectPoolService>() ?? throw new InvalidOperationException());
builder.Services.AddHostedService(provider => builder.Services.AddHostedService(provider =>
provider.GetService<PacketDistributorService>() ?? throw new InvalidOperationException()); provider.GetService<PacketDistributorService>() ?? throw new InvalidOperationException());
builder.Services.AddMassTransit(x => builder.Services.AddMassTransit(x =>

View file

@ -8,7 +8,6 @@
<RootNamespace>Server</RootNamespace> <RootNamespace>Server</RootNamespace>
<LangVersion>default</LangVersion> <LangVersion>default</LangVersion>
<ServerGarbageCollection>true</ServerGarbageCollection> <ServerGarbageCollection>true</ServerGarbageCollection>
<TargetFramework>net8.0</TargetFramework>
<EnableNETAnalyzers>true</EnableNETAnalyzers> <EnableNETAnalyzers>true</EnableNETAnalyzers>
<Features>strict</Features> <Features>strict</Features>
<Authors>Timothy (RaiNote) Schenk</Authors> <Authors>Timothy (RaiNote) Schenk</Authors>
@ -20,6 +19,7 @@
<ProduceReferenceAssembly>True</ProduceReferenceAssembly> <ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<AnalysisLevel>latest-recommended</AnalysisLevel> <AnalysisLevel>latest-recommended</AnalysisLevel>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0|AnyCPU'">

View file

@ -71,8 +71,18 @@ public class ItemObjectPoolService : IHostedService
{ {
ItemId = itemId, ItemId = itemId,
Count = count, Count = count,
Slot = 0, Slot = (byte)item.SlotNo1,
InventoryTab = InventoryTab.WornEquipment, 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, Level = item.MinimumLevelRequirement,
Rarity = 0, Rarity = 0,
AddOption = 0, AddOption = 0,

View file

@ -27,6 +27,7 @@ public class WonderkingAuthServer : TcpServer, IHostedService
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
{ {
this.DisconnectAll();
this.Stop(); this.Stop();
return Task.CompletedTask; return Task.CompletedTask;
} }

View file

@ -29,39 +29,41 @@ public class ChannelSelectionResponsePacket : IPacket
// Character Data // Character Data
for (var i = 0; i < Characters.Length; i++) for (var i = 0; i < Characters.Length; i++)
{ {
int offset = 20 + (i * 132);
var character = Characters[i]; var character = Characters[i];
BinaryPrimitives.WriteInt32LittleEndian(data.Slice(20 + (i * 132), 4), i); // Character Data
Encoding.ASCII.GetBytes(character.Name, data.Slice(24 + (i * 132), 20)); BinaryPrimitives.WriteInt32LittleEndian(data.Slice(offset, 4), i);
Encoding.ASCII.GetBytes(character.Name, data.Slice(offset + 4, 20));
// Job Data // Job Data
data[44 + (i * 132)] = character.Job.FirstJob; data[offset + 24] = character.Job.FirstJob;
data[45 + (i * 132)] = character.Job.SecondJob; data[offset + 25] = character.Job.SecondJob;
data[46 + (i * 132)] = character.Job.ThirdJob; data[offset + 26] = character.Job.ThirdJob;
data[47 + (i * 132)] = character.Job.FourthJob; data[offset + 27] = character.Job.FourthJob;
data[48 + (i * 132)] = (byte)character.Gender; data[offset + 28] = (byte)character.Gender;
BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(49 + (i * 132), 2), character.Level); BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(offset + 29, 2), character.Level);
data[51 + (i * 132)] = (byte)character.Experience; data[offset + 31] = (byte)character.Experience;
// Stats // Stats
BinaryPrimitives.WriteInt16LittleEndian(data.Slice(52 + (i * 132), 2), character.Stats.Strength); BinaryPrimitives.WriteInt16LittleEndian(data.Slice(offset + 32, 2), character.Stats.Strength);
BinaryPrimitives.WriteInt16LittleEndian(data.Slice(54 + (i * 132), 2), character.Stats.Dexterity); BinaryPrimitives.WriteInt16LittleEndian(data.Slice(offset + 34, 2), character.Stats.Dexterity);
BinaryPrimitives.WriteInt16LittleEndian(data.Slice(56 + (i * 132), 2), character.Stats.Intelligence); BinaryPrimitives.WriteInt16LittleEndian(data.Slice(offset + 36, 2), character.Stats.Intelligence);
BinaryPrimitives.WriteInt16LittleEndian(data.Slice(58 + (i * 132), 2), character.Stats.Vitality); BinaryPrimitives.WriteInt16LittleEndian(data.Slice(offset + 38, 2), character.Stats.Vitality);
BinaryPrimitives.WriteInt16LittleEndian(data.Slice(60 + (i * 132), 2), character.Stats.Luck); BinaryPrimitives.WriteInt16LittleEndian(data.Slice(offset + 40, 2), character.Stats.Luck);
BinaryPrimitives.WriteInt16LittleEndian(data.Slice(62 + (i * 132), 2), character.Stats.Wisdom); BinaryPrimitives.WriteInt16LittleEndian(data.Slice(offset + 42, 2), character.Stats.Wisdom);
BinaryPrimitives.WriteInt32LittleEndian(data.Slice(64 + (i * 132), 4), character.Health); BinaryPrimitives.WriteInt32LittleEndian(data.Slice(offset + 44, 4), character.Health);
BinaryPrimitives.WriteInt32LittleEndian(data.Slice(68 + (i * 132), 4), character.Mana); BinaryPrimitives.WriteInt32LittleEndian(data.Slice(offset + 48, 4), character.Mana);
for (var j = 0; j < 20; j++) for (var j = 0; j < 20; j++)
{ {
// Equipped Items // 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); character.EquippedItems.Length > j ? character.EquippedItems[j] : (ushort)0);
// Equipped Cash Items // 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); character.EquippedCashItems.Length > j ? character.EquippedCashItems[j] : (ushort)0);
} }
} }

View file

@ -20,6 +20,8 @@ public class CharacterCreationResponsePacket : IPacket
{ {
Span<byte> data = stackalloc byte[1 + 132]; Span<byte> data = stackalloc byte[1 + 132];
data[0] = isDuplicate ? (byte)1 : (byte)0; data[0] = isDuplicate ? (byte)1 : (byte)0;
// Character Data
BinaryPrimitives.WriteInt32LittleEndian(data.Slice(1, 4), Slot); BinaryPrimitives.WriteInt32LittleEndian(data.Slice(1, 4), Slot);
Encoding.ASCII.GetBytes(Character.Name, data.Slice(5, 20)); Encoding.ASCII.GetBytes(Character.Name, data.Slice(5, 20));