bugfix/87-client-dc-on-char-deletion #90
14 changed files with 122 additions and 56 deletions
16
.run/Server_Dockerfile.run.xml
Normal file
16
.run/Server_Dockerfile.run.xml
Normal 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>
|
|
@ -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.
|
||||
|
|
|
@ -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<PacketIdAttribute>();
|
||||
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)
|
||||
|
|
|
@ -39,7 +39,7 @@ public class ChannelSelectionHandler : IPacketHandler<ChannelSelectionPacket>
|
|||
{
|
||||
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<ChannelSelectionPacket>
|
|||
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<ushort, byte>(item.ItemId, item.Slot)).ToArray()),
|
||||
EquippedCashItems = GetItemIDsByInventoryTab(c.InventoryItems
|
||||
.Where(item => item.InventoryTab == InventoryTab.WornCashEquipment)
|
||||
.Select(item => item.ItemId)
|
||||
.ToArray()
|
||||
.Select(item => new Tuple<ushort, byte>(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<ChannelSelectionPacket>
|
|||
};
|
||||
}
|
||||
|
||||
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<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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,13 @@ public class CharacterCreationHandler : IPacketHandler<CharacterCreationPacket>
|
|||
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<CharacterCreationPacket>
|
|||
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<CharacterCreationPacket>
|
|||
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<ushort, byte>(item.ItemId, item.Slot)).ToArray()),
|
||||
EquippedCashItems = GetItemIDsByInventoryTab(c.InventoryItems
|
||||
.Where(item => item.InventoryTab == InventoryTab.WornCashEquipment)
|
||||
.Select(item => item.ItemId)
|
||||
.ToArray(),
|
||||
.Select(item => new Tuple<ushort, byte>(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<CharacterCreationPacket>
|
|||
return (int)((level - 1) * firstJobConfig.DynamicStats.ManaPerLevel +
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,7 @@ public class CharacterDeletionHandler : IPacketHandler<CharacterDeletePacket>
|
|||
|
||||
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<CharacterDeletePacket>
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,11 @@ public class CharacterNameCheckHandler : IPacketHandler<CharacterNameCheckPacket
|
|||
{
|
||||
var isTaken = _wonderkingContext.Characters.Any(c => 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,6 +119,6 @@ public class LoginHandler : IPacketHandler<LoginInfoPacket>
|
|||
}
|
||||
|
||||
_logger.LogInformation("LoginResponsePacket: {@LoginResponsePacket}", loginResponsePacket);
|
||||
sess?.Send(loginResponsePacket);
|
||||
sess?.SendAsync(loginResponsePacket);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,8 @@ builder.Services.AddDbContextPool<WonderkingContext>(o =>
|
|||
builder.Services.AddSingleton<ILoggerFactory>(loggerFactory);
|
||||
builder.Services.AddSingleton<PacketDistributorService>();
|
||||
builder.Services.AddSingleton<ItemObjectPoolService>();
|
||||
builder.Services.AddHostedService<ItemObjectPoolService>();
|
||||
builder.Services.AddHostedService(provider =>
|
||||
provider.GetService<ItemObjectPoolService>() ?? throw new InvalidOperationException());
|
||||
builder.Services.AddHostedService(provider =>
|
||||
provider.GetService<PacketDistributorService>() ?? throw new InvalidOperationException());
|
||||
builder.Services.AddMassTransit(x =>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<RootNamespace>Server</RootNamespace>
|
||||
<LangVersion>default</LangVersion>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<Features>strict</Features>
|
||||
<Authors>Timothy (RaiNote) Schenk</Authors>
|
||||
|
@ -20,6 +19,7 @@
|
|||
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<AnalysisLevel>latest-recommended</AnalysisLevel>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0|AnyCPU'">
|
||||
|
@ -82,7 +82,7 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NetCoreServer" Version="7.0.0"/>
|
||||
<PackageReference Include="NetCoreServer" Version="7.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0-rc.2" />
|
||||
<PackageReference Include="Nullable.Extended.Analyzer" Version="1.10.4539">
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -27,6 +27,7 @@ public class WonderkingAuthServer : TcpServer, IHostedService
|
|||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
this.DisconnectAll();
|
||||
this.Stop();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ public class CharacterCreationResponsePacket : IPacket
|
|||
{
|
||||
Span<byte> 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));
|
||||
|
||||
|
|
Loading…
Reference in a new issue