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?
|
# 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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 =>
|
||||||
|
|
|
@ -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'">
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</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="Newtonsoft.Json" Version="13.0.3"/>
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0-rc.2" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0-rc.2" />
|
||||||
<PackageReference Include="Nullable.Extended.Analyzer" Version="1.10.4539">
|
<PackageReference Include="Nullable.Extended.Analyzer" Version="1.10.4539">
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue