From f6c2f4d2b6736aac92e337c78a80e1a454749be1 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Wed, 8 Nov 2023 15:33:04 +0100 Subject: [PATCH 01/34] feat: add inventory/item data --- Server/DB/Documents/Character.cs | 1 + Server/DB/Documents/InventoryItem.cs | 20 ++++++++++++++++++++ Server/DB/Documents/ItemType.cs | 12 ++++++++++++ Server/DB/WonderkingContext.cs | 7 +++++-- Wonderking/Wonderking.csproj | 2 +- 5 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 Server/DB/Documents/InventoryItem.cs create mode 100644 Server/DB/Documents/ItemType.cs diff --git a/Server/DB/Documents/Character.cs b/Server/DB/Documents/Character.cs index ee9be2c..2c737c8 100644 --- a/Server/DB/Documents/Character.cs +++ b/Server/DB/Documents/Character.cs @@ -14,4 +14,5 @@ public class Character public Gender Gender { get; set; } public long Experience { get; set; } public byte Level { get; set; } + public ICollection InventoryItems { get; set; } } diff --git a/Server/DB/Documents/InventoryItem.cs b/Server/DB/Documents/InventoryItem.cs new file mode 100644 index 0000000..f85c981 --- /dev/null +++ b/Server/DB/Documents/InventoryItem.cs @@ -0,0 +1,20 @@ +namespace Server.DB.Documents; + +public class InventoryItem +{ + public Guid CharacterId { get; set; } + public Character Character { get; set; } + public Guid Id { get; set; } + public short ItemId { get; set; } + public ushort Count { get; set; } + public byte Slot { get; set; } + public ItemType ItemType { get; set; } + public byte Level { get; set; } + public byte Rarity { get; set; } + public byte AddOption { get; set; } + public byte AddOption2 { get; set; } + public byte AddOption3 { get; set; } + public short Option { get; set; } + public short Option2 { get; set; } + public short Option3 { get; set; } +} diff --git a/Server/DB/Documents/ItemType.cs b/Server/DB/Documents/ItemType.cs new file mode 100644 index 0000000..2b12c16 --- /dev/null +++ b/Server/DB/Documents/ItemType.cs @@ -0,0 +1,12 @@ +namespace Server.DB.Documents; + +public enum ItemType : byte +{ + WornEquipment = 0, + WornCashEquipment = 1, + Equipment = 2, + Etc = 3, + Cash = 4, + Warehouse = 5, + GiftBox = 6 +} diff --git a/Server/DB/WonderkingContext.cs b/Server/DB/WonderkingContext.cs index 2cc9720..807dfd8 100644 --- a/Server/DB/WonderkingContext.cs +++ b/Server/DB/WonderkingContext.cs @@ -32,10 +32,13 @@ public class WonderkingContext : DbContext builder.Property(b => b.Password).HasColumnType("bytea"); builder.Property(b => b.Salt).HasColumnType("bytea"); builder.HasKey(b => b.Id); - builder.HasMany(e => e.Characters).WithOne(e => e.Account).HasForeignKey(e => e.AccountId).IsRequired(); + builder.HasMany(e => e.Characters).WithOne(e => e.Account).HasForeignKey(e => e.AccountId) + .IsRequired(); }).Entity(builder => { builder.HasKey(c => c.Id); builder.Property(c => c.Name).HasColumnType("varchar(20)"); - }); + builder.HasMany(e => e.InventoryItems).WithOne(e => e.Character) + .HasForeignKey(e => e.CharacterId).IsRequired(); + }).Entity(builder => { builder.HasKey(i => i.Id); }); } diff --git a/Wonderking/Wonderking.csproj b/Wonderking/Wonderking.csproj index e8fbd2c..78f67e5 100644 --- a/Wonderking/Wonderking.csproj +++ b/Wonderking/Wonderking.csproj @@ -13,7 +13,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 1135605d27fbfcfc5fea48c3924cd1eeecb15008 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Wed, 8 Nov 2023 15:33:31 +0100 Subject: [PATCH 02/34] chore: update dependencies --- Server/Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/Server.csproj b/Server/Server.csproj index cfa8679..0aec4a7 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -55,7 +55,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive From b7a33d0a3f220cdba0741923b99f6ab74055a75b Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Wed, 8 Nov 2023 15:33:45 +0100 Subject: [PATCH 03/34] chore: add analyzers to Benchmark --- Benchmarks/Benchmarks.csproj | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Benchmarks/Benchmarks.csproj b/Benchmarks/Benchmarks.csproj index 8b71371..10403a3 100644 --- a/Benchmarks/Benchmarks.csproj +++ b/Benchmarks/Benchmarks.csproj @@ -5,12 +5,20 @@ enable enable preview - net6.0;net7.0 + net6.0;net7.0;net8.0 + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + all runtime; build; native; contentfiles; analyzers; buildtransitive From d90dcf696d5043ad9cbfa1de5151a70d18be90b9 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Wed, 8 Nov 2023 15:34:09 +0100 Subject: [PATCH 04/34] feat: add tests for data caching --- Benchmarks/BinaryConversionBenchmarks.cs | 8 +-- Benchmarks/DataCacheBenchmark.cs | 85 ++++++++++++++++++++++++ Benchmarks/GenericConfig.cs | 38 +++++++++++ 3 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 Benchmarks/DataCacheBenchmark.cs create mode 100644 Benchmarks/GenericConfig.cs diff --git a/Benchmarks/BinaryConversionBenchmarks.cs b/Benchmarks/BinaryConversionBenchmarks.cs index 74cef4c..3252171 100644 --- a/Benchmarks/BinaryConversionBenchmarks.cs +++ b/Benchmarks/BinaryConversionBenchmarks.cs @@ -2,16 +2,10 @@ namespace Benchmarks; using System.Security.Cryptography; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Order; -[SimpleJob(RuntimeMoniker.Net80)] -[SimpleJob(RuntimeMoniker.Net70)] -[SimpleJob(RuntimeMoniker.Net60)] [Orderer(SummaryOrderPolicy.FastestToSlowest)] -[RankColumn] -[MemoryDiagnoser] -[ThreadingDiagnoser] +[Config(typeof(GenericConfig))] public class BinaryConversionBenchmarks { private byte[] _data = null!; diff --git a/Benchmarks/DataCacheBenchmark.cs b/Benchmarks/DataCacheBenchmark.cs new file mode 100644 index 0000000..27e78b4 --- /dev/null +++ b/Benchmarks/DataCacheBenchmark.cs @@ -0,0 +1,85 @@ +using System.Collections.Concurrent; +using System.Collections.Immutable; + +namespace Benchmarks; + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; + +[Config(typeof(GenericConfig))] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] +public class DataCacheBenchmark +{ + [Params(1000, 100000, 1000000)] public int N; + private HashSet _hashSet; + private Dictionary _dictionary; + private ConcurrentDictionary _concurrentDictionary; + private ImmutableHashSet _immutableHashSet; + + [GlobalSetup] + public void Setup() + { + _hashSet = new HashSet(); + _dictionary = new Dictionary(); + _concurrentDictionary = new ConcurrentDictionary(); + _immutableHashSet = ImmutableHashSet.Empty; + + _hashSet.Clear(); + _dictionary.Clear(); + _concurrentDictionary.Clear(); + _immutableHashSet = _immutableHashSet.Clear(); + _hashSet.EnsureCapacity(N); + _dictionary.EnsureCapacity(N); + + for (var i = 0; i < N; i++) + { + _immutableHashSet = _immutableHashSet.Add(i); + _hashSet.Add(i); + _dictionary.Add(i, i); + _concurrentDictionary.TryAdd(i, i); + } + } + + [Benchmark] + public void HashSetAdd() + { + ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => _hashSet.Add(i)); + } + + [Benchmark] + public void DictionaryAdd() + { + ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => _dictionary.Add(N + i, i)); + } + + [Benchmark] + public void ConcurrentDictionaryAddOrUpdate() + { + ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => + _concurrentDictionary.AddOrUpdate(N + i, i, (key, oldValue) => oldValue + i)); + } + + [Benchmark] + public void ImmutableHashSetLookup() + { + ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => _immutableHashSet.Contains(i)); + } + + [Benchmark] + public void HashSetLookup() + { + ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => _hashSet.Contains(i)); + } + + [Benchmark] + public void DictionaryLookup() + { + ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => _dictionary.ContainsKey(i)); + } + + [Benchmark] + public void ConcurrentDictionaryLookup() + { + ParallelEnumerable.Range(0, N).AsParallel().ForAll(i => _concurrentDictionary.ContainsKey(i)); + } +} diff --git a/Benchmarks/GenericConfig.cs b/Benchmarks/GenericConfig.cs new file mode 100644 index 0000000..0dd812e --- /dev/null +++ b/Benchmarks/GenericConfig.cs @@ -0,0 +1,38 @@ +using BenchmarkDotNet.Analysers; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Exporters.Csv; +using BenchmarkDotNet.Jobs; + +namespace Benchmarks; + +public class GenericConfig : ManualConfig +{ + public GenericConfig() + { + AddJob(Job.Default + .WithRuntime(CoreRuntime.Core80)) + .AddDiagnoser(ThreadingDiagnoser.Default, MemoryDiagnoser.Default, + EventPipeProfiler.Default) + .AddAnalyser(MinIterationTimeAnalyser.Default, OutliersAnalyser.Default, + RuntimeErrorAnalyser.Default, EnvironmentAnalyser.Default) + .AddColumn(RankColumn.Arabic).AddExporter(CsvExporter.Default, MarkdownExporter.Default); + AddJob(Job.Default + .WithRuntime(CoreRuntime.Core70)) + .AddDiagnoser(ThreadingDiagnoser.Default, MemoryDiagnoser.Default, + EventPipeProfiler.Default) + .AddAnalyser(MinIterationTimeAnalyser.Default, OutliersAnalyser.Default, + RuntimeErrorAnalyser.Default, EnvironmentAnalyser.Default) + .AddColumn(RankColumn.Arabic).AddExporter(CsvExporter.Default, MarkdownExporter.Default); + AddJob(Job.Default + .WithRuntime(CoreRuntime.Core60)) + .AddDiagnoser(ThreadingDiagnoser.Default, MemoryDiagnoser.Default, + EventPipeProfiler.Default) + .AddAnalyser(MinIterationTimeAnalyser.Default, OutliersAnalyser.Default, + RuntimeErrorAnalyser.Default, EnvironmentAnalyser.Default) + .AddColumn(RankColumn.Arabic).AddExporter(CsvExporter.Default, MarkdownExporter.Default); + } +} From cb087f53e940e6c041a9e55cb80961506d7115d4 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Wed, 8 Nov 2023 15:39:25 +0100 Subject: [PATCH 05/34] chore: add migration for inventory data --- ...143729_AddInventoryToCharacter.Designer.cs | 183 ++++++++++++++++++ .../20231108143729_AddInventoryToCharacter.cs | 55 ++++++ .../WonderkingContextModelSnapshot.cs | 70 ++++++- Server/Server.csproj | 4 + 4 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 Server/DB/Migrations/20231108143729_AddInventoryToCharacter.Designer.cs create mode 100644 Server/DB/Migrations/20231108143729_AddInventoryToCharacter.cs diff --git a/Server/DB/Migrations/20231108143729_AddInventoryToCharacter.Designer.cs b/Server/DB/Migrations/20231108143729_AddInventoryToCharacter.Designer.cs new file mode 100644 index 0000000..86952dc --- /dev/null +++ b/Server/DB/Migrations/20231108143729_AddInventoryToCharacter.Designer.cs @@ -0,0 +1,183 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Server.DB; + +#nullable disable + +namespace Server.DB.Migrations +{ + [DbContext(typeof(WonderkingContext))] + [Migration("20231108143729_AddInventoryToCharacter")] + partial class AddInventoryToCharacter + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Server.DB.Documents.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Password") + .HasColumnType("bytea"); + + b.Property("PermissionLevel") + .HasColumnType("smallint"); + + b.Property("Salt") + .HasColumnType("bytea"); + + b.Property("Username") + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.DB.Documents.Character", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("Experience") + .HasColumnType("bigint"); + + b.Property("Gender") + .HasColumnType("smallint"); + + b.Property("LastXCoordinate") + .HasColumnType("smallint"); + + b.Property("LastYCoordinate") + .HasColumnType("smallint"); + + b.Property("Level") + .HasColumnType("smallint"); + + b.Property("MapId") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("varchar(20)"); + + b.Property("PvPLevel") + .HasColumnType("smallint"); + + b.Property("ServerId") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Characters"); + }); + + modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddOption") + .HasColumnType("smallint"); + + b.Property("AddOption2") + .HasColumnType("smallint"); + + b.Property("AddOption3") + .HasColumnType("smallint"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("Count") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("smallint"); + + b.Property("ItemType") + .HasColumnType("smallint"); + + b.Property("Level") + .HasColumnType("smallint"); + + b.Property("Option") + .HasColumnType("smallint"); + + b.Property("Option2") + .HasColumnType("smallint"); + + b.Property("Option3") + .HasColumnType("smallint"); + + b.Property("Rarity") + .HasColumnType("smallint"); + + b.Property("Slot") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.ToTable("InventoryItem"); + }); + + modelBuilder.Entity("Server.DB.Documents.Character", b => + { + b.HasOne("Server.DB.Documents.Account", "Account") + .WithMany("Characters") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => + { + b.HasOne("Server.DB.Documents.Character", "Character") + .WithMany("InventoryItems") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Character"); + }); + + modelBuilder.Entity("Server.DB.Documents.Account", b => + { + b.Navigation("Characters"); + }); + + modelBuilder.Entity("Server.DB.Documents.Character", b => + { + b.Navigation("InventoryItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/DB/Migrations/20231108143729_AddInventoryToCharacter.cs b/Server/DB/Migrations/20231108143729_AddInventoryToCharacter.cs new file mode 100644 index 0000000..4e365ba --- /dev/null +++ b/Server/DB/Migrations/20231108143729_AddInventoryToCharacter.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Server.DB.Migrations; + +/// +public partial class AddInventoryToCharacter : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "InventoryItem", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CharacterId = table.Column(type: "uuid", nullable: false), + ItemId = table.Column(type: "smallint", nullable: false), + Count = table.Column(type: "integer", nullable: false), + Slot = table.Column(type: "smallint", nullable: false), + ItemType = table.Column(type: "smallint", nullable: false), + Level = table.Column(type: "smallint", nullable: false), + Rarity = table.Column(type: "smallint", nullable: false), + AddOption = table.Column(type: "smallint", nullable: false), + AddOption2 = table.Column(type: "smallint", nullable: false), + AddOption3 = table.Column(type: "smallint", nullable: false), + Option = table.Column(type: "smallint", nullable: false), + Option2 = table.Column(type: "smallint", nullable: false), + Option3 = table.Column(type: "smallint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_InventoryItem", x => x.Id); + table.ForeignKey( + name: "FK_InventoryItem_Characters_CharacterId", + column: x => x.CharacterId, + principalTable: "Characters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_InventoryItem_CharacterId", + table: "InventoryItem", + column: "CharacterId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "InventoryItem"); + } +} diff --git a/Server/DB/Migrations/WonderkingContextModelSnapshot.cs b/Server/DB/Migrations/WonderkingContextModelSnapshot.cs index 51f1e73..ab1f034 100644 --- a/Server/DB/Migrations/WonderkingContextModelSnapshot.cs +++ b/Server/DB/Migrations/WonderkingContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Server.DB.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.10") + .HasAnnotation("ProductVersion", "7.0.13") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -91,6 +91,58 @@ namespace Server.DB.Migrations b.ToTable("Characters"); }); + modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddOption") + .HasColumnType("smallint"); + + b.Property("AddOption2") + .HasColumnType("smallint"); + + b.Property("AddOption3") + .HasColumnType("smallint"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("Count") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("smallint"); + + b.Property("ItemType") + .HasColumnType("smallint"); + + b.Property("Level") + .HasColumnType("smallint"); + + b.Property("Option") + .HasColumnType("smallint"); + + b.Property("Option2") + .HasColumnType("smallint"); + + b.Property("Option3") + .HasColumnType("smallint"); + + b.Property("Rarity") + .HasColumnType("smallint"); + + b.Property("Slot") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.ToTable("InventoryItem"); + }); + modelBuilder.Entity("Server.DB.Documents.Character", b => { b.HasOne("Server.DB.Documents.Account", "Account") @@ -102,10 +154,26 @@ namespace Server.DB.Migrations b.Navigation("Account"); }); + modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => + { + b.HasOne("Server.DB.Documents.Character", "Character") + .WithMany("InventoryItems") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Character"); + }); + modelBuilder.Entity("Server.DB.Documents.Account", b => { b.Navigation("Characters"); }); + + modelBuilder.Entity("Server.DB.Documents.Character", b => + { + b.Navigation("InventoryItems"); + }); #pragma warning restore 612, 618 } } diff --git a/Server/Server.csproj b/Server/Server.csproj index 0aec4a7..05c9822 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -66,6 +66,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From f5cd6c380e317efea334ce5603a49fc63d9f1e12 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Wed, 8 Nov 2023 19:04:37 +0100 Subject: [PATCH 06/34] feat: Itemdata pooling & benchmarks for storage options --- Benchmarks/Benchmarks.csproj | 2 +- Server/Server.csproj | 2 +- Server/Services/ItemObjectPoolService.cs | 51 ++++++ Wonderking/Game/Data/Item/ContainedItem.cs | 10 + Wonderking/Game/Data/Item/CraftMaterial.cs | 11 ++ Wonderking/Game/Data/Item/ElementalStats.cs | 55 ++++++ Wonderking/Game/Data/Item/ItemOptions.cs | 7 + Wonderking/Game/Data/Item/Stats.cs | 25 +++ Wonderking/Game/Data/ItemObject.cs | 171 ++++++++++++++++++ Wonderking/Game/DataReader.cs | 50 +++++ Wonderking/Game/GameDataMetadataAttribute.cs | 12 ++ Wonderking/Game/Reader/BinaryReader.cs | 75 ++++++++ .../Game/Reader/GenericReaderExtensions.cs | 37 ++++ Wonderking/Game/Reader/ItemReader.cs | 107 +++++++++++ .../Game/Reader/ItemReaderExtensions.cs | 96 ++++++++++ Wonderking/Utils/ByteArrayConverter.cs | 36 ++++ Wonderking/Wonderking.csproj | 11 +- 17 files changed, 755 insertions(+), 3 deletions(-) create mode 100644 Server/Services/ItemObjectPoolService.cs create mode 100644 Wonderking/Game/Data/Item/ContainedItem.cs create mode 100644 Wonderking/Game/Data/Item/CraftMaterial.cs create mode 100644 Wonderking/Game/Data/Item/ElementalStats.cs create mode 100644 Wonderking/Game/Data/Item/ItemOptions.cs create mode 100644 Wonderking/Game/Data/Item/Stats.cs create mode 100644 Wonderking/Game/Data/ItemObject.cs create mode 100644 Wonderking/Game/DataReader.cs create mode 100644 Wonderking/Game/GameDataMetadataAttribute.cs create mode 100644 Wonderking/Game/Reader/BinaryReader.cs create mode 100644 Wonderking/Game/Reader/GenericReaderExtensions.cs create mode 100644 Wonderking/Game/Reader/ItemReader.cs create mode 100644 Wonderking/Game/Reader/ItemReaderExtensions.cs create mode 100644 Wonderking/Utils/ByteArrayConverter.cs diff --git a/Benchmarks/Benchmarks.csproj b/Benchmarks/Benchmarks.csproj index 10403a3..737b096 100644 --- a/Benchmarks/Benchmarks.csproj +++ b/Benchmarks/Benchmarks.csproj @@ -4,7 +4,7 @@ Exe enable enable - preview + 12 net6.0;net7.0;net8.0 diff --git a/Server/Server.csproj b/Server/Server.csproj index 05c9822..f0d9ddb 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -6,7 +6,7 @@ warnings Linux Server - default + 12 true net8.0;net7.0 true diff --git a/Server/Services/ItemObjectPoolService.cs b/Server/Services/ItemObjectPoolService.cs new file mode 100644 index 0000000..3a8ac45 --- /dev/null +++ b/Server/Services/ItemObjectPoolService.cs @@ -0,0 +1,51 @@ +using System.Collections.Concurrent; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Wonderking.Game.Data; +using Wonderking.Game.Reader; + +namespace Server.Services; + +public class ItemObjectPoolService : IHostedService +{ + readonly ConcurrentDictionary _itemObjectPool = new(); + private readonly ItemReader _itemReader; + + public ItemObjectPoolService(IConfiguration configuration) + { + _itemReader = new ItemReader(configuration.GetSection("Game").GetSection("Data").GetValue("Path") ?? + string.Empty); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + var amountOfEntries = _itemReader.GetAmountOfEntries(); + ParallelEnumerable.Range(0, (int)amountOfEntries).AsParallel().ForAll(i => + { + var itemObject = _itemReader.GetEntry((uint)i); + _itemObjectPool.TryAdd(itemObject.ItemID, itemObject); + }); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _itemReader.Dispose(); + return Task.CompletedTask; + } + + public ItemObject GetItem(ushort itemId) + { + return _itemObjectPool[itemId]; + } + + public bool ContainsItem(ushort itemId) + { + return _itemObjectPool.ContainsKey(itemId); + } + + public IQueryable QueryItems() + { + return _itemObjectPool.AsReadOnly().Values.AsQueryable(); + } +} diff --git a/Wonderking/Game/Data/Item/ContainedItem.cs b/Wonderking/Game/Data/Item/ContainedItem.cs new file mode 100644 index 0000000..3f819f4 --- /dev/null +++ b/Wonderking/Game/Data/Item/ContainedItem.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Wonderking.Game.Data.Item; + +[StructLayout(LayoutKind.Sequential)] +public struct ContainedItem +{ + public short ID { get; internal set; } + public float ObtainChance { get; internal set; } +} diff --git a/Wonderking/Game/Data/Item/CraftMaterial.cs b/Wonderking/Game/Data/Item/CraftMaterial.cs new file mode 100644 index 0000000..b548e36 --- /dev/null +++ b/Wonderking/Game/Data/Item/CraftMaterial.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Wonderking.Game.Data.Item; + +[StructLayout(LayoutKind.Sequential)] +public struct CraftMaterial +{ + public uint ID; + public uint Amount; +} + diff --git a/Wonderking/Game/Data/Item/ElementalStats.cs b/Wonderking/Game/Data/Item/ElementalStats.cs new file mode 100644 index 0000000..1599a5f --- /dev/null +++ b/Wonderking/Game/Data/Item/ElementalStats.cs @@ -0,0 +1,55 @@ +using System.Runtime.InteropServices; + +namespace Wonderking.GameData.Item; + +[StructLayout(LayoutKind.Explicit, Size = 64)] +public struct ElementalStats +{ + [FieldOffset(0), MarshalAs(UnmanagedType.I4)] + public int MinimumFireDamage; + + [FieldOffset(4), MarshalAs(UnmanagedType.I4)] + public int MinimumWaterDamage; + + [FieldOffset(8), MarshalAs(UnmanagedType.I4)] + public int MinimumDarkDamage; + + [FieldOffset(12), MarshalAs(UnmanagedType.I4)] + public int MinimumHolyDamage; + + [FieldOffset(16), MarshalAs(UnmanagedType.I4)] + public int MaximumFireDamage; + + [FieldOffset(20), MarshalAs(UnmanagedType.I4)] + public int MaximumWaterDamage; + + [FieldOffset(24), MarshalAs(UnmanagedType.I4)] + public int MaximumDarkDamage; + + [FieldOffset(28), MarshalAs(UnmanagedType.I4)] + public int MaximumHolyDamage; + + [FieldOffset(32), MarshalAs(UnmanagedType.U4)] + public uint ElementFire; + + [FieldOffset(36), MarshalAs(UnmanagedType.U4)] + public uint ElementWater; + + [FieldOffset(40), MarshalAs(UnmanagedType.U4)] + public uint ElementDark; + + [FieldOffset(44), MarshalAs(UnmanagedType.U4)] + public uint ElementHoly; + + [FieldOffset(48), MarshalAs(UnmanagedType.I4)] + public int FireResistance; + + [FieldOffset(52), MarshalAs(UnmanagedType.I4)] + public int WaterResistance; + + [FieldOffset(56), MarshalAs(UnmanagedType.I4)] + public int DarkResistance; + + [FieldOffset(60), MarshalAs(UnmanagedType.I4)] + public int HolyResistance; +} diff --git a/Wonderking/Game/Data/Item/ItemOptions.cs b/Wonderking/Game/Data/Item/ItemOptions.cs new file mode 100644 index 0000000..3b52fb1 --- /dev/null +++ b/Wonderking/Game/Data/Item/ItemOptions.cs @@ -0,0 +1,7 @@ +namespace Wonderking.Game.Data.Item; + +public struct ItemOptions +{ + public ICollection OptionIDs { get; internal set; } + public bool OptionAvailable { get; internal set; } +} diff --git a/Wonderking/Game/Data/Item/Stats.cs b/Wonderking/Game/Data/Item/Stats.cs new file mode 100644 index 0000000..ab60dd0 --- /dev/null +++ b/Wonderking/Game/Data/Item/Stats.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; + +namespace Wonderking.GameData.Item; + +[StructLayout(LayoutKind.Explicit, Size = 24)] +public struct Stats +{ + [FieldOffset(0), MarshalAs(UnmanagedType.I4)] + public int Strength; + + [FieldOffset(4), MarshalAs(UnmanagedType.I4)] + public int Dexterity; + + [FieldOffset(8), MarshalAs(UnmanagedType.I4)] + public int Intelligence; + + [FieldOffset(12), MarshalAs(UnmanagedType.I4)] + public int Vitality; + + [FieldOffset(16), MarshalAs(UnmanagedType.I4)] + public int Luck; + + [FieldOffset(20), MarshalAs(UnmanagedType.I4)] + public int Wisdom; +} diff --git a/Wonderking/Game/Data/ItemObject.cs b/Wonderking/Game/Data/ItemObject.cs new file mode 100644 index 0000000..2f1bf2e --- /dev/null +++ b/Wonderking/Game/Data/ItemObject.cs @@ -0,0 +1,171 @@ +using System.Text.Json.Serialization; +using Wonderking.Game.Data.Item; +using Wonderking.GameData.Item; +using Wonderking.Utils; + +namespace Wonderking.Game.Data; + +[GameDataMetadata(932, "baseitemdata.dat", 197)] +public struct ItemObject +{ + public uint ItemID { get; set; } + public bool Disabled { get; set; } + public uint ItemType { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown2 { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown3 { get; set; } + + public uint ClassNo1 { get; set; } + public uint ClassNo2 { get; set; } + public uint ClassNo3 { get; set; } + public uint ClassNo4 { get; set; } + public uint SlotNo1 { get; set; } + public uint SlotNo2 { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown4 { get; set; } + + public uint IsCash { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown5 { get; set; } + + public uint Price { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown7 { get; set; } + + public uint MaxNumber { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown17 { get; set; } + + public uint MaximumLevelRequirement { get; set; } + public uint SexNo { get; set; } + public uint WeaponSomething { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown8 { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R2C { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown9 { get; set; } + + public Stats Stats { get; set; } + public ElementalStats ElementalStats { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R7C { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R8C { get; set; } + + public float Speed { get; set; } + + public float Jump { get; set; } + public int StatDefense { get; set; } + public uint MagicID { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown13 { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown14 { get; set; } + + public int AdditionalHealthRecoveryVolume { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R9C_1 { get; set; } + + public int AdditionalManaRecoveryVolume { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R9C_2 { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R10C { get; set; } + + public int AdditionalHealthPoints { get; set; } + public int AdditionalManaPoints { get; set; } + public bool IsArrow { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown18 { get; set; } + + public int AdditionalEvasionRate { get; set; } + public int HitRate { get; set; } + + public int ChanceToHit { get; set; } + public int MagicalDamage { get; set; } + public int CriticalHitChance { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R12C { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown16 { get; set; } + + public int MinimalAttackDamage { get; set; } + public int MaximalAttackDamage { get; set; } + public int PhysicalDamage { get; set; } + public CraftMaterial[] CraftMaterial { get; set; } + public uint CraftResultAmount { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R14C { get; set; } + + public uint CraftResultItem { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R15C { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] R16C { get; set; } + + public int InventoryX { get; set; } + public int InventoryY { get; set; } + public int InventoryWidth { get; set; } + public int InventoryHeight { get; set; } + public int SheetID { get; set; } + public string Name { get; set; } + public string Description { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown1 { get; set; } + + public bool IsEnchantable { get; set; } + + public uint SetID { get; set; } + public uint[] SetItems { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown1_2 { get; set; } + + public ItemOptions Options { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown19 { get; set; } + + public byte PetID { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown20 { get; set; } + + public byte HitBoxScaling { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown20_2 { get; set; } + + public ContainedItem[] ContainedItems { get; set; } + + public bool IsQuestItem { get; set; } + public byte MinimumLevelRequirement { get; set; } + + [JsonConverter(typeof(ByteArrayConverter))] + public byte[] Unknown21_2 { get; set; } +} diff --git a/Wonderking/Game/DataReader.cs b/Wonderking/Game/DataReader.cs new file mode 100644 index 0000000..e67c8a4 --- /dev/null +++ b/Wonderking/Game/DataReader.cs @@ -0,0 +1,50 @@ +using System.Reflection; + +namespace Wonderking.Game; + +public abstract class DataReader +{ + protected DataReader(string path) + { + Path = path; + DatFileContent = new(GetDatFileContent(path).ToArray()); + } + + private protected string Path { get; init; } + + public abstract uint GetAmountOfEntries(); + public abstract T GetEntry(uint entryId); + + protected ushort GetSizeOfEntry() + { + return typeof(T).GetCustomAttribute()?.DataEntrySize ?? + throw new NotSupportedException("DataEntrySize is null"); + } + + private static string GetDatFileName() + { + return typeof(T).GetCustomAttribute()?.DatFileName ?? + throw new NotSupportedException("DatFileName is null"); + } + + private static byte GetXorKey() + { + return typeof(T).GetCustomAttribute()?.XorKey ?? + throw new NotSupportedException("XorKey is null"); + } + + protected MemoryStream DatFileContent { get; } + + private static Span GetDatFileContent(string path) + { + var fileData = File.ReadAllBytes(path + GetDatFileName()); + var data = new byte[fileData.Length]; + + for (var i = 0; i < fileData.Length; i++) + { + data[i] = (byte)(fileData[i] ^ GetXorKey()); + } + + return data; + } +} diff --git a/Wonderking/Game/GameDataMetadataAttribute.cs b/Wonderking/Game/GameDataMetadataAttribute.cs new file mode 100644 index 0000000..b9db18c --- /dev/null +++ b/Wonderking/Game/GameDataMetadataAttribute.cs @@ -0,0 +1,12 @@ +using JetBrains.Annotations; + +namespace Wonderking.Game; + +[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)] +public class GameDataMetadataAttribute(ushort dataEntrySize, string datFileName, byte xorKey) : Attribute +{ + [UsedImplicitly] public byte XorKey { get; init; } = xorKey; + [UsedImplicitly] public ushort DataEntrySize { get; init; } = dataEntrySize; + + [UsedImplicitly] internal string DatFileName { get; init; } = datFileName; +} diff --git a/Wonderking/Game/Reader/BinaryReader.cs b/Wonderking/Game/Reader/BinaryReader.cs new file mode 100644 index 0000000..62c683d --- /dev/null +++ b/Wonderking/Game/Reader/BinaryReader.cs @@ -0,0 +1,75 @@ +using System.Runtime.InteropServices; + +namespace Wonderking.Game.Reader; + +public static class BinaryReader where T : new() +{ + public static readonly Func Read; + + static BinaryReader() + { + var type = typeof(T); + + if (type == typeof(bool)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadBoolean()); + } + else if (type == typeof(char)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadChar()); + } + else if (type == typeof(string)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadString()); + } + else if (type == typeof(sbyte)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadSByte()); + } + else if (type == typeof(short)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadInt16()); + } + else if (type == typeof(int)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadInt32()); + } + else if (type == typeof(long)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadInt64()); + } + else if (type == typeof(byte)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadByte()); + } + else if (type == typeof(ushort)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadUInt16()); + } + else if (type == typeof(uint)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadUInt32()); + } + else if (type == typeof(ulong)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadUInt64()); + } + else if (type == typeof(float)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadSingle()); + } + else if (type == typeof(double)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadDouble()); + } + else if (type == typeof(decimal)) + { + Read = (Func)(Delegate)(Func)(p => p.ReadDecimal()); + } + else + { + Read = (Func)(p => + (T)(object)p.ReadBytes(Marshal.SizeOf(new T()))); + } + } +} diff --git a/Wonderking/Game/Reader/GenericReaderExtensions.cs b/Wonderking/Game/Reader/GenericReaderExtensions.cs new file mode 100644 index 0000000..17e888e --- /dev/null +++ b/Wonderking/Game/Reader/GenericReaderExtensions.cs @@ -0,0 +1,37 @@ + +/* Nicht gemergte Änderung aus Projekt "Wonderking(net7.0)" +Vor: +using System.Runtime.InteropServices; +using System.Text; +Nach: +using System.Text; +*/ + +using System.Text; + +namespace Wonderking.Game.Reader; + +public static class GenericReaderExtensions +{ + public static string ReadString(this BinaryReader reader, int length) + { + var ret = Encoding.ASCII.GetString(reader.ReadBytes(length)).Replace("\0", ""); + return ret; + } + + public static T[] ReadArray(this BinaryReader pReader, int pLength) where T : new() + { + var array = new T[pLength]; + for (var index = 0; index < pLength; ++index) + { + array[index] = pReader.Read(); + } + + return array; + } + + public static T Read(this BinaryReader br) where T : new() + { + return BinaryReader.Read(br); + } +} diff --git a/Wonderking/Game/Reader/ItemReader.cs b/Wonderking/Game/Reader/ItemReader.cs new file mode 100644 index 0000000..38ae8a3 --- /dev/null +++ b/Wonderking/Game/Reader/ItemReader.cs @@ -0,0 +1,107 @@ +using Wonderking.Game.Data; +using Wonderking.Game.Data.Item; + +namespace Wonderking.Game.Reader; + +public class ItemReader(string path) : DataReader(path), IDisposable +{ + public override uint GetAmountOfEntries() + { + return (uint)((this.DatFileContent.Length - 9) / this.GetSizeOfEntry()); + } + + public override ItemObject GetEntry(uint entryId) + { + var item = new ItemObject(); + this.DatFileContent.Position = 9 + entryId * this.GetSizeOfEntry(); + var reader = new BinaryReader(this.DatFileContent); + item.ItemID = reader.ReadUInt32(); //9 + item.Disabled = reader.ReadUInt32() == 1; //13 + item.ItemType = reader.ReadUInt32(); //17 + item.Unknown2 = reader.ReadBytes(4); //21 + item.Unknown3 = reader.ReadBytes(4); //25 + item.ClassNo1 = reader.ReadUInt32(); //29 + item.ClassNo2 = reader.ReadUInt32(); //33 + item.ClassNo3 = reader.ReadUInt32(); //37 + item.ClassNo4 = reader.ReadUInt32(); //41 + item.SlotNo1 = reader.ReadUInt32(); //45 + item.SlotNo2 = reader.ReadUInt32(); //49 + item.Unknown4 = reader.ReadBytes(4); //53 + item.IsCash = reader.ReadUInt32(); //57 + item.Unknown5 = reader.ReadBytes(4); //61 + item.Price = reader.ReadUInt32(); //65 + item.Unknown7 = reader.ReadBytes(4); //69 + item.MaxNumber = reader.ReadUInt32(); //73 + item.Unknown17 = reader.ReadBytes(12); //77 + item.MaximumLevelRequirement = reader.ReadUInt32(); //89 + item.SexNo = reader.ReadUInt32(); //93 + item.WeaponSomething = reader.ReadUInt32(); //97 + item.Unknown8 = reader.ReadBytes(4); //101 + item.R2C = reader.ReadBytes(16); //105 + item.Unknown9 = reader.ReadBytes(4); //121 + item.Stats = reader.ReadStats(); //125 + item.ElementalStats = reader.ReadElementalStats(); //149 + item.R7C = reader.ReadBytes(4); //213 + item.R8C = reader.ReadBytes(8); //217 + item.Speed = reader.ReadSingle(); //225 + item.Jump = reader.ReadSingle(); //229 + item.StatDefense = reader.ReadInt32(); //233 + item.MagicID = reader.ReadUInt32(); //237 + item.Unknown13 = reader.ReadBytes(4); //241 + item.Unknown14 = reader.ReadBytes(4); //245 + item.AdditionalHealthRecoveryVolume = reader.ReadInt32(); //249 + item.R9C_1 = reader.ReadBytes(4); //253 + item.AdditionalManaRecoveryVolume = reader.ReadInt32(); //257 + item.R9C_2 = reader.ReadBytes(4); //261 + item.R10C = reader.ReadBytes(8); //265 + item.AdditionalHealthPoints = reader.ReadInt32(); //273 + item.AdditionalManaPoints = reader.ReadInt32(); //277 + item.IsArrow = reader.ReadBoolean(); //281 + item.Unknown18 = reader.ReadBytes(7); //282 + item.AdditionalEvasionRate = reader.ReadInt32(); //289 + item.HitRate = reader.ReadInt32(); //293 + item.ChanceToHit = reader.ReadInt32(); //297 + item.MagicalDamage = reader.ReadInt32(); //301 + item.CriticalHitChance = reader.ReadInt32(); //305 + item.R12C = reader.ReadBytes(4); //309 + item.Unknown16 = reader.ReadBytes(4); //313 + item.MinimalAttackDamage = reader.ReadInt32(); //317 + item.MaximalAttackDamage = reader.ReadInt32(); //321 + item.PhysicalDamage = reader.ReadInt32(); //325 + item.CraftMaterial = reader.ReadCraftMaterial(); //329 + item.CraftResultAmount = reader.ReadUInt32(); //361 + item.R14C = reader.ReadBytes(4); //365 + item.CraftResultItem = reader.ReadUInt32(); //369 + item.R15C = reader.ReadBytes(4); //373 + item.R16C = reader.ReadBytes(20); //377 + item.InventoryX = reader.ReadInt32(); //397 + item.InventoryY = reader.ReadInt32(); //401 + item.InventoryWidth = reader.ReadInt32(); //405 + item.InventoryHeight = reader.ReadInt32(); //409 + item.SheetID = reader.ReadInt32(); //413 + item.Name = reader.ReadString(20); //417 + item.Description = reader.ReadString(85); //427 + item.Unknown1 = reader.ReadBytes(175); //493 + item.IsEnchantable = reader.ReadUInt32() == 1; //687 + item.Unknown1_2 = reader.ReadBytes(104); //687 + item.SetItems = reader.ReadArray(5); + item.SetID = reader.ReadUInt32(); //691 + item.Options = reader.ReadItemOptions(); //819 + item.Unknown19 = reader.ReadBytes(23); //835 + item.PetID = reader.ReadByte(); //858 + item.Unknown20 = reader.ReadBytes(20); //859 + item.HitBoxScaling = reader.ReadByte(); //879 + item.Unknown20_2 = reader.ReadBytes(13); //880 + item.ContainedItems = reader.ReadContainedItems(); //893 + item.IsQuestItem = reader.ReadBoolean(); //923 + item.MinimumLevelRequirement = reader.ReadByte(); //924 + item.Unknown21_2 = reader.ReadBytes(6); //925 + reader.Dispose(); //931 + return item; + } + + public void Dispose() + { + this.DatFileContent.Dispose(); + } +} diff --git a/Wonderking/Game/Reader/ItemReaderExtensions.cs b/Wonderking/Game/Reader/ItemReaderExtensions.cs new file mode 100644 index 0000000..df9d71f --- /dev/null +++ b/Wonderking/Game/Reader/ItemReaderExtensions.cs @@ -0,0 +1,96 @@ +using Wonderking.GameData.Item; + +namespace Wonderking.Game.Data.Item; + +public static class ItemReaderExtensions +{ + public static Stats ReadStats(this BinaryReader reader) + { + return new Stats() + { + Strength = reader.ReadInt32(), //125 + Dexterity = reader.ReadInt32(), //129 + Intelligence = reader.ReadInt32(), //133 + Vitality = reader.ReadInt32(), //137 + Luck = reader.ReadInt32(), //141 + Wisdom = reader.ReadInt32(), //145 + }; + } + + public static ElementalStats ReadElementalStats(this BinaryReader reader) + { + return new ElementalStats() + { + MinimumFireDamage = reader.ReadInt32(), //149 + MinimumWaterDamage = reader.ReadInt32(), //153 + MinimumDarkDamage = reader.ReadInt32(), //157 + MinimumHolyDamage = reader.ReadInt32(), //161 + MaximumFireDamage = reader.ReadInt32(), //165 + MaximumWaterDamage = reader.ReadInt32(), //169 + MaximumDarkDamage = reader.ReadInt32(), //173 + MaximumHolyDamage = reader.ReadInt32(), //177 + ElementFire = reader.ReadUInt32(), //181 + ElementWater = reader.ReadUInt32(), //185 + ElementDark = reader.ReadUInt32(), //189 + ElementHoly = reader.ReadUInt32(), //193 + FireResistance = reader.ReadInt32(), //197 + WaterResistance = reader.ReadInt32(), //201 + DarkResistance = reader.ReadInt32(), //205 + HolyResistance = reader.ReadInt32(), //209 + }; + } + + public static ContainedItem[] ReadContainedItems(this BinaryReader reader) + { + var list = new ContainedItem[5]; + //893 + for (var i = 0; i < 5; i++) + { + list[i].ID = reader.ReadInt16(); + } + + //903 + for (var i = 0; i < 5; i++) + { + list[i].ObtainChance = reader.ReadSingle(); + } + + return list; + } + + public static CraftMaterial[] ReadCraftMaterial(this BinaryReader reader) + { + var mats = new CraftMaterial[4]; + //329 + for (var i = 0; i < 4; ++i) + { + mats[i].ID = reader.ReadUInt32(); + } + + //345 + for (var i = 0; i < 4; ++i) + { + mats[i].ID = reader.ReadUInt32(); + } + + return mats; + } + + public static ItemOptions ReadItemOptions(this BinaryReader reader) + { + var options = new ItemOptions(); + + options.OptionAvailable = reader.ReadInt32() == 1; //819 + + var optionIDs = new List(4); + //823 + for (var i = 0; i < 3; i++) + { + optionIDs.Add((byte)reader.ReadUInt32()); + } + + options.OptionIDs = optionIDs; + + return options; + } +} diff --git a/Wonderking/Utils/ByteArrayConverter.cs b/Wonderking/Utils/ByteArrayConverter.cs new file mode 100644 index 0000000..76259f5 --- /dev/null +++ b/Wonderking/Utils/ByteArrayConverter.cs @@ -0,0 +1,36 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Wonderking.Utils; + +public class ByteArrayConverter : JsonConverter +{ + public override byte[] Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + var hexData = reader.GetString(); + if (hexData != null) + { + return hexData.Split('-').Select(b => Convert.ToByte(b, 16)).ToArray(); + } + throw new JsonException("Hex string is null."); + } + + public override void Write( + Utf8JsonWriter writer, + byte[]? value, + JsonSerializerOptions options) + { + if (value == null) + { + writer.WriteNullValue(); + } + else + { + var hexData = BitConverter.ToString(value).Replace("-", string.Empty); + writer.WriteStringValue(hexData); + } + } +} diff --git a/Wonderking/Wonderking.csproj b/Wonderking/Wonderking.csproj index 78f67e5..f5b8158 100644 --- a/Wonderking/Wonderking.csproj +++ b/Wonderking/Wonderking.csproj @@ -5,6 +5,7 @@ enable net8.0;net7.0 strict + 12 @@ -30,5 +31,13 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + + + True + \ + + + + + From 1a6e1e4cccc628c6d83a0a6f7b79fa026ba81f62 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Wed, 8 Nov 2023 19:10:23 +0100 Subject: [PATCH 07/34] chore: upgrade dependencies for Serilog.Extensions.Logging.File --- Server/Server.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/Server.csproj b/Server/Server.csproj index f0d9ddb..a4a8d6a 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -93,6 +93,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + From e4d6f153450b00d71e5997c167e8147c1cf214ee Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Wed, 8 Nov 2023 19:48:58 +0100 Subject: [PATCH 08/34] fix: Packets not found --- Server/Services/PacketDistributorService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Server/Services/PacketDistributorService.cs b/Server/Services/PacketDistributorService.cs index 79f9c34..1c53f8c 100644 --- a/Server/Services/PacketDistributorService.cs +++ b/Server/Services/PacketDistributorService.cs @@ -36,9 +36,9 @@ public class PacketDistributorService : IHostedService var tempDeserializationMap = new Dictionary>(); - var executingAssembly = Assembly.GetExecutingAssembly(); - var packetsTypes = this.GetPacketsWithId(executingAssembly); - var packetHandlers = this.GetAllPacketHandlersWithId(executingAssembly); + var wonderkingAssembly = Assembly.GetAssembly(typeof(IPacket)); + var packetsTypes = this.GetPacketsWithId(wonderkingAssembly); + var packetHandlers = this.GetAllPacketHandlersWithId(Assembly.GetExecutingAssembly()); this._packetHandlersInstantiation = new ConcurrentDictionary(); packetHandlers.ForEach(x => { From 497d415fb6b3e0b756126fbef512feb61cd14425 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Wed, 8 Nov 2023 19:49:23 +0100 Subject: [PATCH 09/34] feat: Item Pool service --- .gitignore | 2 ++ Server/Program.cs | 1 + Server/docker-compose.yml | 13 +++++++++++++ Server/settings.Development.json | 5 +++++ 4 files changed, 21 insertions(+) diff --git a/.gitignore b/.gitignore index d0af085..4789088 100644 --- a/.gitignore +++ b/.gitignore @@ -482,3 +482,5 @@ $RECYCLE.BIN/ .vscode .nuke + +wk-data diff --git a/Server/Program.cs b/Server/Program.cs index 3241823..70ff82c 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -24,6 +24,7 @@ builder.Services.AddDbContext(); builder.Services.AddSingleton(); builder.Services.AddHostedService(provider => provider.GetService() ?? throw new InvalidOperationException()); +builder.Services.AddSingleton(); builder.Services.AddMassTransit(x => { x.UsingInMemory((context, configurator) => configurator.ConfigureEndpoints(context)); diff --git a/Server/docker-compose.yml b/Server/docker-compose.yml index 1efa332..6ba37d3 100644 --- a/Server/docker-compose.yml +++ b/Server/docker-compose.yml @@ -11,10 +11,16 @@ - DB:Port=5432 - DB:Username=continuity - DB:Password=continuity + - Game:Data:Path=/app/data networks: - continuity ports: - "10001:10001" + volumes: + - type: bind + source: game-data + target: /app/data + read_only: true db: container_name: continuity-db @@ -37,3 +43,10 @@ networks: continuity: volumes: db-data: + + game-data: + driver: local + driver_opts: + type: none + device: ../wk-data + o: bind diff --git a/Server/settings.Development.json b/Server/settings.Development.json index 1b64c70..ded1040 100644 --- a/Server/settings.Development.json +++ b/Server/settings.Development.json @@ -8,5 +8,10 @@ }, "Testing": { "CreateAccountOnLogin": true + }, + "Game":{ + "Data":{ + "Path": "../wk-data" + } } } From dc7daaef5cf2f2d904190aa44431aeedbc393d56 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Sun, 12 Nov 2023 13:18:36 +0100 Subject: [PATCH 10/34] fix: add transaction to avoid not updating the password --- .editorconfig | 2 ++ Server/PacketHandlers/LoginHandler.cs | 36 +++++++++++++++++++-------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/.editorconfig b/.editorconfig index e270271..6389e64 100644 --- a/.editorconfig +++ b/.editorconfig @@ -28,6 +28,8 @@ insert_final_newline = true indent_size = 4 dotnet_sort_system_directives_first = true +MA0004.report = DetectContext # (default) Try to detect the current context and report only if it considers ConfigureAwait is needed +MA0004.report = Always # Always report missing ConfigureAwait whatever the context # Don't use this. qualifier dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion diff --git a/Server/PacketHandlers/LoginHandler.cs b/Server/PacketHandlers/LoginHandler.cs index 35dd997..e1ec019 100644 --- a/Server/PacketHandlers/LoginHandler.cs +++ b/Server/PacketHandlers/LoginHandler.cs @@ -44,16 +44,32 @@ public class LoginHandler : IPacketHandler { if (this._configuration.GetSection("Testing").GetValue("CreateAccountOnLogin")) { - argon2Id.Salt = RandomNumberGenerator.GetBytes(16); - var finalAccount = - await this._wonderkingContext.Accounts.AddAsync(new Account(packet.Username, Array.Empty(), "", - 0, argon2Id.Salt)).ConfigureAwait(true); - await this._wonderkingContext.SaveChangesAsync().ConfigureAwait(true); - argon2Id.AssociatedData = finalAccount.Entity.Id.ToByteArray(); - finalAccount.Entity.Password = await argon2Id.GetBytesAsync(16).ConfigureAwait(true); - this._wonderkingContext.Accounts.Update(finalAccount.Entity); - loginResponseReason = LoginResponseReason.Ok; - await this._wonderkingContext.SaveChangesAsync().ConfigureAwait(true); + var transaction = + await _wonderkingContext.Database.BeginTransactionAsync().ConfigureAwait(true); + await using (transaction.ConfigureAwait(true)) + { + try + { + argon2Id.Salt = RandomNumberGenerator.GetBytes(16); + var finalAccount = + await this._wonderkingContext.Accounts.AddAsync(new Account(packet.Username, + Array.Empty(), "", + 0, argon2Id.Salt)).ConfigureAwait(true); + await this._wonderkingContext.SaveChangesAsync().ConfigureAwait(true); + argon2Id.AssociatedData = finalAccount.Entity.Id.ToByteArray(); + finalAccount.Entity.Password = await argon2Id.GetBytesAsync(16).ConfigureAwait(true); + this._wonderkingContext.Accounts.Update(finalAccount.Entity); + loginResponseReason = LoginResponseReason.Ok; + await this._wonderkingContext.SaveChangesAsync().ConfigureAwait(true); + + await transaction.CommitAsync().ConfigureAwait(true); + } + catch (Exception) + { + await transaction.RollbackAsync().ConfigureAwait(true); // Rollback the transaction on error + throw; + } + } } else { From 24a118d52a44f36d7aef1eec34d5d2954fd8c296 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Sun, 12 Nov 2023 15:27:59 +0100 Subject: [PATCH 11/34] fix: for game-data folder --- Server/docker-compose.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Server/docker-compose.yml b/Server/docker-compose.yml index 6ba37d3..8659b0b 100644 --- a/Server/docker-compose.yml +++ b/Server/docker-compose.yml @@ -1,7 +1,8 @@ services: server: container_name: continuity-server - image: server:latest + image: continuity:latest + restart: always depends_on: - db environment: @@ -25,6 +26,7 @@ db: container_name: continuity-db image: postgres:16.0-alpine + restart: always environment: - POSTGRES_USER=continuity - POSTGRES_DB=continuity @@ -34,19 +36,13 @@ volumes: - db-data:/var/lib/postgresql/data healthcheck: - test: [ "CMD-SHELL", "sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'" ] + test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" ] interval: 10s timeout: 3s retries: 3 networks: continuity: + volumes: db-data: - - game-data: - driver: local - driver_opts: - type: none - device: ../wk-data - o: bind From 9356a729b0ba6b1db7e466e44e95faae50253580 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Sun, 12 Nov 2023 15:28:17 +0100 Subject: [PATCH 12/34] chore: build script for images (JB Issue) --- build-image.ps1 | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 build-image.ps1 diff --git a/build-image.ps1 b/build-image.ps1 new file mode 100644 index 0000000..c46b322 --- /dev/null +++ b/build-image.ps1 @@ -0,0 +1,2 @@ +#!sh +docker build --platform linux/arm64,linux/amd64 -f Server/Dockerfile -t continuity . From 1a13c3fb1b1cadbcfce2b5057a55ccd2ac952f12 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Sun, 12 Nov 2023 15:29:20 +0100 Subject: [PATCH 13/34] refactor: Complexity & Method length --- .../LoginHandlerLoggerMessages.cs | 15 +++ Server/PacketHandlers/LoginHandler.cs | 94 +++++++++++-------- 2 files changed, 71 insertions(+), 38 deletions(-) create mode 100644 Server/LoggerMessages/LoginHandlerLoggerMessages.cs diff --git a/Server/LoggerMessages/LoginHandlerLoggerMessages.cs b/Server/LoggerMessages/LoginHandlerLoggerMessages.cs new file mode 100644 index 0000000..adefe33 --- /dev/null +++ b/Server/LoggerMessages/LoginHandlerLoggerMessages.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.Logging; +using Server.PacketHandlers; + +namespace Server.LoggerMessages; + +public static partial class LoginHandlerLoggerMessages +{ + [LoggerMessage(EventId = 0, Level = LogLevel.Information, + Message = "Login data: Username {Username} & Password {Password}")] + public static partial void LoginData(this ILogger logger, string username, string password); + + [LoggerMessage(EventId = 1, Level = LogLevel.Information, + Message = "Requested account for user: {Username} does not exist")] + public static partial void RequestedAccountDoesNotExist(this ILogger logger, string username); +} diff --git a/Server/PacketHandlers/LoginHandler.cs b/Server/PacketHandlers/LoginHandler.cs index e1ec019..c56c977 100644 --- a/Server/PacketHandlers/LoginHandler.cs +++ b/Server/PacketHandlers/LoginHandler.cs @@ -1,5 +1,6 @@ using System.Security.Cryptography; using System.Text; +using Server.LoggerMessages; using Wonderking.Packets.Incoming; using Wonderking.Packets.Outgoing; @@ -25,63 +26,79 @@ public class LoginHandler : IPacketHandler this._configuration = configuration; } - public async Task HandleAsync(LoginInfoPacket packet, TcpSession session) + private static Task GetPasswordHashAsync(string password, byte[] salt, Guid userId) { - LoginResponseReason loginResponseReason; - this._logger.LogInformation("Login data: Username {Username} & Password {Password}", packet.Username, - packet.Password); - var account = this._wonderkingContext.Accounts.FirstOrDefault(a => a.Username == packet.Username); - // https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Chea1t_Sheet.html#argon2id // "Use Argon2id with a minimum configuration of 19 MiB of memory, an iteration count of 2, and 1 degree of parallelism." - var argon2Id = new Argon2id(Encoding.ASCII.GetBytes(packet.Password)) + var argon2Id = new Argon2id(Encoding.ASCII.GetBytes(password)) { MemorySize = 1024 * 19, Iterations = 2, - DegreeOfParallelism = 1 + DegreeOfParallelism = 1, + Salt = salt, + AssociatedData = userId.ToByteArray() }; + return argon2Id.GetBytesAsync(16); + } + + private async Task CreateAccountOnLoginAsync(string username, string password) + { + LoginResponseReason loginResponseReason; + var transaction = + await _wonderkingContext.Database.BeginTransactionAsync().ConfigureAwait(true); + await using (transaction.ConfigureAwait(false)) + { + try + { + var salt = RandomNumberGenerator.GetBytes(16); + var finalAccount = + await this._wonderkingContext.Accounts.AddAsync(new Account(username, + Array.Empty(), "", + 0, salt)).ConfigureAwait(true); + await this._wonderkingContext.SaveChangesAsync().ConfigureAwait(true); + finalAccount.Entity.Password = + await LoginHandler.GetPasswordHashAsync(password, salt, finalAccount.Entity.Id) + .ConfigureAwait(true); + this._wonderkingContext.Accounts.Update(finalAccount.Entity); + loginResponseReason = LoginResponseReason.Ok; + await this._wonderkingContext.SaveChangesAsync().ConfigureAwait(true); + + await transaction.CommitAsync().ConfigureAwait(true); + } + catch (Exception) + { + await transaction.RollbackAsync().ConfigureAwait(true); // Rollback the transaction on error + throw; + } + } + + return loginResponseReason; + } + + public async Task HandleAsync(LoginInfoPacket packet, TcpSession session) + { + LoginResponseReason loginResponseReason; + this._logger.LoginData(packet.Username, packet.Password); + var account = this._wonderkingContext.Accounts.FirstOrDefault(a => a.Username == packet.Username); + if (account == null) { if (this._configuration.GetSection("Testing").GetValue("CreateAccountOnLogin")) { - var transaction = - await _wonderkingContext.Database.BeginTransactionAsync().ConfigureAwait(true); - await using (transaction.ConfigureAwait(true)) - { - try - { - argon2Id.Salt = RandomNumberGenerator.GetBytes(16); - var finalAccount = - await this._wonderkingContext.Accounts.AddAsync(new Account(packet.Username, - Array.Empty(), "", - 0, argon2Id.Salt)).ConfigureAwait(true); - await this._wonderkingContext.SaveChangesAsync().ConfigureAwait(true); - argon2Id.AssociatedData = finalAccount.Entity.Id.ToByteArray(); - finalAccount.Entity.Password = await argon2Id.GetBytesAsync(16).ConfigureAwait(true); - this._wonderkingContext.Accounts.Update(finalAccount.Entity); - loginResponseReason = LoginResponseReason.Ok; - await this._wonderkingContext.SaveChangesAsync().ConfigureAwait(true); - - await transaction.CommitAsync().ConfigureAwait(true); - } - catch (Exception) - { - await transaction.RollbackAsync().ConfigureAwait(true); // Rollback the transaction on error - throw; - } - } + loginResponseReason = await CreateAccountOnLoginAsync(packet.Username, packet.Password) + .ConfigureAwait(true); } else { - this._logger.LogInformation("Requested account for user: {Username} does not exist", packet.Username); + this._logger.RequestedAccountDoesNotExist(packet.Username); loginResponseReason = LoginResponseReason.AccountDoesNotExit; } } else { - argon2Id.Salt = account.Salt; - argon2Id.AssociatedData = account.Id.ToByteArray(); - var tempPasswordBytes = await argon2Id.GetBytesAsync(16).ConfigureAwait(true); + var salt = account.Salt; + var tempPasswordBytes = await LoginHandler.GetPasswordHashAsync(packet.Password, salt, account.Id) + .ConfigureAwait(false); loginResponseReason = tempPasswordBytes.SequenceEqual(account.Password) ? LoginResponseReason.Ok : LoginResponseReason.WrongPassword; @@ -100,6 +117,7 @@ public class LoginHandler : IPacketHandler sess.AccountId = account.Id; } + _logger.LogInformation("LoginResponsePacket: {@LoginResponsePacket}", loginResponsePacket); sess?.Send(loginResponsePacket); } } From 72cacab0ed4bb5d7cb9fbd92b9a4059c0934ecf8 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Sun, 12 Nov 2023 15:31:02 +0100 Subject: [PATCH 14/34] fix: "\n" was being sent but not trimmed. --- Wonderking/Packets/Incoming/LoginInfoPacket.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Wonderking/Packets/Incoming/LoginInfoPacket.cs b/Wonderking/Packets/Incoming/LoginInfoPacket.cs index 4758ef7..0005eb1 100644 --- a/Wonderking/Packets/Incoming/LoginInfoPacket.cs +++ b/Wonderking/Packets/Incoming/LoginInfoPacket.cs @@ -12,7 +12,8 @@ public class LoginInfoPacket : IPacket public void Deserialize(byte[] data) { this.Username = Encoding.ASCII.GetString(data, 0, 20).TrimEnd('\0'); - this.Password = Encoding.ASCII.GetString(data, 20, 31).TrimEnd('\0'); + // Remove unnecessary Symbols + this.Password = Encoding.ASCII.GetString(data, 20, 31).TrimEnd('\n').TrimEnd('\0'); } public byte[] Serialize() From 9a827214fa09c3d333390668fccf9f4f55e3a5bd Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Mon, 13 Nov 2023 18:23:29 +0100 Subject: [PATCH 15/34] feat: implement ChannelSelectionResponse packet data --- Wonderking/Packets/OperationCode.cs | 3 +- Wonderking/Packets/Outgoing/BaseStats.cs | 14 ++++ .../ChannelSelectionResponsePacket.cs | 71 +++++++++++++++++++ Wonderking/Packets/Outgoing/CharacterData.cs | 16 +++++ Wonderking/Packets/Outgoing/JobData.cs | 12 ++++ 5 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 Wonderking/Packets/Outgoing/BaseStats.cs create mode 100644 Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs create mode 100644 Wonderking/Packets/Outgoing/CharacterData.cs create mode 100644 Wonderking/Packets/Outgoing/JobData.cs diff --git a/Wonderking/Packets/OperationCode.cs b/Wonderking/Packets/OperationCode.cs index f8b9eea..c866557 100644 --- a/Wonderking/Packets/OperationCode.cs +++ b/Wonderking/Packets/OperationCode.cs @@ -4,5 +4,6 @@ public enum OperationCode : ushort { LoginInfo = 11, LoginResponse = 12, - ChannelSelection + ChannelSelection = 13, + ChannelSelectionResponse = 13 } diff --git a/Wonderking/Packets/Outgoing/BaseStats.cs b/Wonderking/Packets/Outgoing/BaseStats.cs new file mode 100644 index 0000000..61b9bf9 --- /dev/null +++ b/Wonderking/Packets/Outgoing/BaseStats.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Wonderking.Packets.Outgoing; + +[StructLayout(LayoutKind.Auto)] +public struct BaseStats +{ + public required short Strength { get; set; } + public required short Dexterity { get; set; } + public required short Intelligence { get; set; } + public required short Vitality { get; set; } + public required short Luck { get; set; } + public required short Wisdom { get; set; } +} diff --git a/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs b/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs new file mode 100644 index 0000000..100f7dc --- /dev/null +++ b/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs @@ -0,0 +1,71 @@ +using System.Buffers.Binary; +using System.Text; + +namespace Wonderking.Packets.Outgoing; + +[PacketId(OperationCode.ChannelSelectionResponse)] +public class ChannelSelectionResponsePacket : IPacket +{ + public required byte UnknownFlag { get; set; } + public required string Endpoint { get; set; } + public required ushort Port { get; set; } + public required CharacterData[] Characters { get; set; } + + public void Deserialize(byte[] data) + { + throw new NotSupportedException(); + } + + public byte[] Serialize() + { + Span data = stackalloc byte[1 + 16 + 2 + 2 + 132 * this.Characters.Length]; + data.Clear(); + data[0] = this.UnknownFlag; + Encoding.ASCII.GetBytes(this.Endpoint, data.Slice(1, 16)); + BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(17, 2), this.Port); + BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(19, 2), (ushort)this.Characters.Length); + + // Character Data + for (var i = 0; i < Characters.Length; i++) + { + var character = Characters[i]; + BinaryPrimitives.WriteInt32LittleEndian(data.Slice(21, 4), i); + Encoding.ASCII.GetBytes(character.Name, data.Slice(25 + (i * 132), 16)); + + // Job Data + data[41 + (i * 132)] = character.Job.FirstJob; + data[42 + (i * 132)] = character.Job.SecondJob; + data[43 + (i * 132)] = character.Job.ThirdJob; + data[44 + (i * 132)] = character.Job.FourthJob; + + data[45 + (i * 132)] = character.Gender; + data[46 + (i * 132)] = character.Level; + data[47 + (i * 132)] = character.Unknown; + data[48 + (i * 132)] = (byte)character.Experience; + + // Stats + BinaryPrimitives.WriteInt16LittleEndian(data.Slice(49 + (i * 132), 2), character.Stats.Strength); + BinaryPrimitives.WriteInt16LittleEndian(data.Slice(51 + (i * 132), 2), character.Stats.Dexterity); + BinaryPrimitives.WriteInt16LittleEndian(data.Slice(53 + (i * 132), 2), character.Stats.Intelligence); + BinaryPrimitives.WriteInt16LittleEndian(data.Slice(55 + (i * 132), 2), character.Stats.Vitality); + BinaryPrimitives.WriteInt16LittleEndian(data.Slice(57 + (i * 132), 2), character.Stats.Luck); + BinaryPrimitives.WriteInt16LittleEndian(data.Slice(59 + (i * 132), 2), character.Stats.Wisdom); + + BinaryPrimitives.WriteInt32LittleEndian(data.Slice(61 + (i * 132), 4), character.Health); + BinaryPrimitives.WriteInt32LittleEndian(data.Slice(65 + (i * 132), 4), character.Mana); + + for (var j = 0; j < 20; j++) + { + // Equipped Items + BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(69 + (i * 132) + (j * 2), 2), + character.EquippedItems[j] ?? 0); + + // Equipped Cash Items + BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(109 + (i * 132) + (j * 2), 2), + character.EquippedCashItems[j] ?? 0); + } + } + + return data.ToArray(); + } +} diff --git a/Wonderking/Packets/Outgoing/CharacterData.cs b/Wonderking/Packets/Outgoing/CharacterData.cs new file mode 100644 index 0000000..6e401b6 --- /dev/null +++ b/Wonderking/Packets/Outgoing/CharacterData.cs @@ -0,0 +1,16 @@ +namespace Wonderking.Packets.Outgoing; + +public struct CharacterData +{ + public required string Name { get; set; } + public required JobData Job { get; set; } + public required byte Gender { get; set; } + public required byte Level { get; set; } + public required byte Unknown { get; set; } + public required float Experience { get; set; } + public required BaseStats Stats { get; set; } + public required int Health { get; set; } + public required int Mana { get; set; } + public required ushort?[] EquippedItems { get; set; } + public required ushort?[] EquippedCashItems { get; set; } +} diff --git a/Wonderking/Packets/Outgoing/JobData.cs b/Wonderking/Packets/Outgoing/JobData.cs new file mode 100644 index 0000000..89d0b0a --- /dev/null +++ b/Wonderking/Packets/Outgoing/JobData.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Wonderking.Packets.Outgoing; + +[StructLayout(LayoutKind.Auto)] +public struct JobData +{ + public required byte FirstJob { get; set; } + public required byte SecondJob { get; set; } + public required byte ThirdJob { get; set; } + public required byte FourthJob { get; set; } +} From 293b65a8562fe5cbb465ebdfd3600c84be990740 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Mon, 13 Nov 2023 19:32:47 +0100 Subject: [PATCH 16/34] feat: add benchmark for writing to span/array of bytes --- Benchmarks/BinaryConversionBenchmarks.cs | 29 ++++++++++++++++++++++-- Benchmarks/GenericConfig.cs | 7 ------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Benchmarks/BinaryConversionBenchmarks.cs b/Benchmarks/BinaryConversionBenchmarks.cs index 3252171..ff14eee 100644 --- a/Benchmarks/BinaryConversionBenchmarks.cs +++ b/Benchmarks/BinaryConversionBenchmarks.cs @@ -10,16 +10,18 @@ public class BinaryConversionBenchmarks { private byte[] _data = null!; private int _offset; + private int _writeBuffer; [GlobalSetup] public void Setup() { this._data = RandomNumberGenerator.GetBytes(4000); this._offset = RandomNumberGenerator.GetInt32(0, 3500); + this._writeBuffer = RandomNumberGenerator.GetInt32(int.MinValue, int.MaxValue); } [Benchmark] - public short BitConverterTest() => BitConverter.ToInt16(this._data, this._offset); + public short BitConverterParseTest() => BitConverter.ToInt16(this._data, this._offset); [Benchmark] public short BinaryReader() @@ -31,7 +33,30 @@ public class BinaryConversionBenchmarks } [Benchmark] - public short BinaryPrimitives() => + public short BinaryPrimitivesRead() => System.Buffers.Binary.BinaryPrimitives.ReadInt16LittleEndian( new ArraySegment(this._data, this._offset, sizeof(short))); + + [Benchmark] + public void BinaryPrimitivesWrite() + { + System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian(this._data.AsSpan(_offset, 4), + this._writeBuffer); + } + + [Benchmark] + public void BitConverterCopy() + { + BitConverter.GetBytes(this._writeBuffer).CopyTo(this._data, this._offset); + } + + [Benchmark] + public void BitConverterAssignment() + { + var bytes = BitConverter.GetBytes(this._writeBuffer); + this._data[this._offset] = bytes[0]; + this._data[this._offset + 1] = bytes[1]; + this._data[this._offset + 2] = bytes[2]; + this._data[this._offset + 3] = bytes[3]; + } } diff --git a/Benchmarks/GenericConfig.cs b/Benchmarks/GenericConfig.cs index 0dd812e..903dbf4 100644 --- a/Benchmarks/GenericConfig.cs +++ b/Benchmarks/GenericConfig.cs @@ -27,12 +27,5 @@ public class GenericConfig : ManualConfig .AddAnalyser(MinIterationTimeAnalyser.Default, OutliersAnalyser.Default, RuntimeErrorAnalyser.Default, EnvironmentAnalyser.Default) .AddColumn(RankColumn.Arabic).AddExporter(CsvExporter.Default, MarkdownExporter.Default); - AddJob(Job.Default - .WithRuntime(CoreRuntime.Core60)) - .AddDiagnoser(ThreadingDiagnoser.Default, MemoryDiagnoser.Default, - EventPipeProfiler.Default) - .AddAnalyser(MinIterationTimeAnalyser.Default, OutliersAnalyser.Default, - RuntimeErrorAnalyser.Default, EnvironmentAnalyser.Default) - .AddColumn(RankColumn.Arabic).AddExporter(CsvExporter.Default, MarkdownExporter.Default); } } From 7fec462a1dd5105affcac0177b97ecba82c79f8b Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Mon, 13 Nov 2023 19:45:52 +0100 Subject: [PATCH 17/34] fix: duplicate packet ids --- Server/Services/PacketDistributorService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/Services/PacketDistributorService.cs b/Server/Services/PacketDistributorService.cs index 1c53f8c..5bc2d43 100644 --- a/Server/Services/PacketDistributorService.cs +++ b/Server/Services/PacketDistributorService.cs @@ -76,6 +76,7 @@ public class PacketDistributorService : IHostedService // ! : We are filtering if types that don't have an instance of the required Attribute var packetsWithId = executingAssembly.GetTypes().AsParallel() .Where(type => type.HasInterface(typeof(IPacket)) && type is { IsInterface: false, IsAbstract: false }) + .Where(type => type.Namespace?.Contains("Incoming") ?? false) .Select(type => new { Type = type, Attribute = type.GetCustomAttribute() }) .Where(item => item.Attribute is not null) .ToDictionary(item => item.Attribute!.Code, item => item.Type); From b2373d15569e685409046303c14c0a04317f9d5b Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Mon, 13 Nov 2023 21:12:12 +0100 Subject: [PATCH 18/34] feat: channel selection working --- Server/DB/Documents/Character.cs | 9 + Server/DB/Documents/InventoryItem.cs | 2 +- ...192405_AdditionalCharacterData.Designer.cs | 249 ++++++++++++++++++ .../20231113192405_AdditionalCharacterData.cs | 155 +++++++++++ .../WonderkingContextModelSnapshot.cs | 70 ++++- Server/DB/WonderkingContext.cs | 2 + .../PacketHandlers/ChannelSelectionHandler.cs | 79 ++++++ Server/docker-compose.yml | 2 + .../Game/Data/Character}/Gender.cs | 2 +- .../Game/Data/Character}/PvPLevel.cs | 2 +- Wonderking/Game/Data/Item/ElementalStats.cs | 2 +- Wonderking/Game/Data/Item/Stats.cs | 2 +- Wonderking/Game/Data/ItemObject.cs | 1 - .../Game/Reader/ItemReaderExtensions.cs | 4 +- Wonderking/Packets/Outgoing/BaseStats.cs | 6 +- .../ChannelSelectionResponsePacket.cs | 53 ++-- Wonderking/Packets/Outgoing/CharacterData.cs | 11 +- Wonderking/Packets/Outgoing/JobData.cs | 6 +- 18 files changed, 609 insertions(+), 48 deletions(-) create mode 100644 Server/DB/Migrations/20231113192405_AdditionalCharacterData.Designer.cs create mode 100644 Server/DB/Migrations/20231113192405_AdditionalCharacterData.cs rename {Server/DB/Documents => Wonderking/Game/Data/Character}/Gender.cs (63%) rename {Server/DB/Documents => Wonderking/Game/Data/Character}/PvPLevel.cs (92%) diff --git a/Server/DB/Documents/Character.cs b/Server/DB/Documents/Character.cs index 2c737c8..49293fd 100644 --- a/Server/DB/Documents/Character.cs +++ b/Server/DB/Documents/Character.cs @@ -1,3 +1,6 @@ +using Wonderking.Game.Data.Character; +using Wonderking.Packets.Outgoing; + namespace Server.DB.Documents; public class Character @@ -15,4 +18,10 @@ public class Character public long Experience { get; set; } public byte Level { get; set; } public ICollection InventoryItems { get; set; } + + public BaseStats BaseStats { get; set; } + + public JobData JobData { get; set; } + public int Health { get; set; } + public int Mana { get; set; } } diff --git a/Server/DB/Documents/InventoryItem.cs b/Server/DB/Documents/InventoryItem.cs index f85c981..58ddd55 100644 --- a/Server/DB/Documents/InventoryItem.cs +++ b/Server/DB/Documents/InventoryItem.cs @@ -5,7 +5,7 @@ public class InventoryItem public Guid CharacterId { get; set; } public Character Character { get; set; } public Guid Id { get; set; } - public short ItemId { get; set; } + public ushort ItemId { get; set; } public ushort Count { get; set; } public byte Slot { get; set; } public ItemType ItemType { get; set; } diff --git a/Server/DB/Migrations/20231113192405_AdditionalCharacterData.Designer.cs b/Server/DB/Migrations/20231113192405_AdditionalCharacterData.Designer.cs new file mode 100644 index 0000000..345b7b3 --- /dev/null +++ b/Server/DB/Migrations/20231113192405_AdditionalCharacterData.Designer.cs @@ -0,0 +1,249 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Server.DB; + +#nullable disable + +namespace Server.DB.Migrations +{ + [DbContext(typeof(WonderkingContext))] + [Migration("20231113192405_AdditionalCharacterData")] + partial class AdditionalCharacterData + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Server.DB.Documents.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Password") + .HasColumnType("bytea"); + + b.Property("PermissionLevel") + .HasColumnType("smallint"); + + b.Property("Salt") + .HasColumnType("bytea"); + + b.Property("Username") + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.DB.Documents.Character", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("Experience") + .HasColumnType("bigint"); + + b.Property("Gender") + .HasColumnType("smallint"); + + b.Property("Health") + .HasColumnType("integer"); + + b.Property("LastXCoordinate") + .HasColumnType("smallint"); + + b.Property("LastYCoordinate") + .HasColumnType("smallint"); + + b.Property("Level") + .HasColumnType("smallint"); + + b.Property("Mana") + .HasColumnType("integer"); + + b.Property("MapId") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("varchar(20)"); + + b.Property("PvPLevel") + .HasColumnType("smallint"); + + b.Property("ServerId") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.ToTable("Characters"); + }); + + modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddOption") + .HasColumnType("smallint"); + + b.Property("AddOption2") + .HasColumnType("smallint"); + + b.Property("AddOption3") + .HasColumnType("smallint"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("Count") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("ItemType") + .HasColumnType("smallint"); + + b.Property("Level") + .HasColumnType("smallint"); + + b.Property("Option") + .HasColumnType("smallint"); + + b.Property("Option2") + .HasColumnType("smallint"); + + b.Property("Option3") + .HasColumnType("smallint"); + + b.Property("Rarity") + .HasColumnType("smallint"); + + b.Property("Slot") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.ToTable("InventoryItem"); + }); + + modelBuilder.Entity("Server.DB.Documents.Character", b => + { + b.HasOne("Server.DB.Documents.Account", "Account") + .WithMany("Characters") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Wonderking.Packets.Outgoing.BaseStats", "BaseStats", b1 => + { + b1.Property("CharacterId") + .HasColumnType("uuid"); + + b1.Property("Dexterity") + .HasColumnType("smallint"); + + b1.Property("Intelligence") + .HasColumnType("smallint"); + + b1.Property("Luck") + .HasColumnType("smallint"); + + b1.Property("Strength") + .HasColumnType("smallint"); + + b1.Property("Vitality") + .HasColumnType("smallint"); + + b1.Property("Wisdom") + .HasColumnType("smallint"); + + b1.HasKey("CharacterId"); + + b1.ToTable("Characters"); + + b1.WithOwner() + .HasForeignKey("CharacterId"); + }); + + b.OwnsOne("Wonderking.Packets.Outgoing.JobData", "JobData", b1 => + { + b1.Property("CharacterId") + .HasColumnType("uuid"); + + b1.Property("FirstJob") + .HasColumnType("smallint"); + + b1.Property("FourthJob") + .HasColumnType("smallint"); + + b1.Property("SecondJob") + .HasColumnType("smallint"); + + b1.Property("ThirdJob") + .HasColumnType("smallint"); + + b1.HasKey("CharacterId"); + + b1.ToTable("Characters"); + + b1.WithOwner() + .HasForeignKey("CharacterId"); + }); + + b.Navigation("Account"); + + b.Navigation("BaseStats"); + + b.Navigation("JobData"); + }); + + modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => + { + b.HasOne("Server.DB.Documents.Character", "Character") + .WithMany("InventoryItems") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Character"); + }); + + modelBuilder.Entity("Server.DB.Documents.Account", b => + { + b.Navigation("Characters"); + }); + + modelBuilder.Entity("Server.DB.Documents.Character", b => + { + b.Navigation("InventoryItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/DB/Migrations/20231113192405_AdditionalCharacterData.cs b/Server/DB/Migrations/20231113192405_AdditionalCharacterData.cs new file mode 100644 index 0000000..49c9278 --- /dev/null +++ b/Server/DB/Migrations/20231113192405_AdditionalCharacterData.cs @@ -0,0 +1,155 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Server.DB.Migrations; + +/// +public partial class AdditionalCharacterData : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ItemId", + table: "InventoryItem", + type: "integer", + nullable: false, + oldClrType: typeof(short), + oldType: "smallint"); + + migrationBuilder.AddColumn( + name: "BaseStats_Dexterity", + table: "Characters", + type: "smallint", + nullable: true); + + migrationBuilder.AddColumn( + name: "BaseStats_Intelligence", + table: "Characters", + type: "smallint", + nullable: true); + + migrationBuilder.AddColumn( + name: "BaseStats_Luck", + table: "Characters", + type: "smallint", + nullable: true); + + migrationBuilder.AddColumn( + name: "BaseStats_Strength", + table: "Characters", + type: "smallint", + nullable: true); + + migrationBuilder.AddColumn( + name: "BaseStats_Vitality", + table: "Characters", + type: "smallint", + nullable: true); + + migrationBuilder.AddColumn( + name: "BaseStats_Wisdom", + table: "Characters", + type: "smallint", + nullable: true); + + migrationBuilder.AddColumn( + name: "Health", + table: "Characters", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "JobData_FirstJob", + table: "Characters", + type: "smallint", + nullable: true); + + migrationBuilder.AddColumn( + name: "JobData_FourthJob", + table: "Characters", + type: "smallint", + nullable: true); + + migrationBuilder.AddColumn( + name: "JobData_SecondJob", + table: "Characters", + type: "smallint", + nullable: true); + + migrationBuilder.AddColumn( + name: "JobData_ThirdJob", + table: "Characters", + type: "smallint", + nullable: true); + + migrationBuilder.AddColumn( + name: "Mana", + table: "Characters", + type: "integer", + nullable: false, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BaseStats_Dexterity", + table: "Characters"); + + migrationBuilder.DropColumn( + name: "BaseStats_Intelligence", + table: "Characters"); + + migrationBuilder.DropColumn( + name: "BaseStats_Luck", + table: "Characters"); + + migrationBuilder.DropColumn( + name: "BaseStats_Strength", + table: "Characters"); + + migrationBuilder.DropColumn( + name: "BaseStats_Vitality", + table: "Characters"); + + migrationBuilder.DropColumn( + name: "BaseStats_Wisdom", + table: "Characters"); + + migrationBuilder.DropColumn( + name: "Health", + table: "Characters"); + + migrationBuilder.DropColumn( + name: "JobData_FirstJob", + table: "Characters"); + + migrationBuilder.DropColumn( + name: "JobData_FourthJob", + table: "Characters"); + + migrationBuilder.DropColumn( + name: "JobData_SecondJob", + table: "Characters"); + + migrationBuilder.DropColumn( + name: "JobData_ThirdJob", + table: "Characters"); + + migrationBuilder.DropColumn( + name: "Mana", + table: "Characters"); + + migrationBuilder.AlterColumn( + name: "ItemId", + table: "InventoryItem", + type: "smallint", + nullable: false, + oldClrType: typeof(int), + oldType: "integer"); + } +} diff --git a/Server/DB/Migrations/WonderkingContextModelSnapshot.cs b/Server/DB/Migrations/WonderkingContextModelSnapshot.cs index ab1f034..933467c 100644 --- a/Server/DB/Migrations/WonderkingContextModelSnapshot.cs +++ b/Server/DB/Migrations/WonderkingContextModelSnapshot.cs @@ -63,6 +63,9 @@ namespace Server.DB.Migrations b.Property("Gender") .HasColumnType("smallint"); + b.Property("Health") + .HasColumnType("integer"); + b.Property("LastXCoordinate") .HasColumnType("smallint"); @@ -72,6 +75,9 @@ namespace Server.DB.Migrations b.Property("Level") .HasColumnType("smallint"); + b.Property("Mana") + .HasColumnType("integer"); + b.Property("MapId") .HasColumnType("integer"); @@ -112,8 +118,8 @@ namespace Server.DB.Migrations b.Property("Count") .HasColumnType("integer"); - b.Property("ItemId") - .HasColumnType("smallint"); + b.Property("ItemId") + .HasColumnType("integer"); b.Property("ItemType") .HasColumnType("smallint"); @@ -151,7 +157,67 @@ namespace Server.DB.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.OwnsOne("Wonderking.Packets.Outgoing.BaseStats", "BaseStats", b1 => + { + b1.Property("CharacterId") + .HasColumnType("uuid"); + + b1.Property("Dexterity") + .HasColumnType("smallint"); + + b1.Property("Intelligence") + .HasColumnType("smallint"); + + b1.Property("Luck") + .HasColumnType("smallint"); + + b1.Property("Strength") + .HasColumnType("smallint"); + + b1.Property("Vitality") + .HasColumnType("smallint"); + + b1.Property("Wisdom") + .HasColumnType("smallint"); + + b1.HasKey("CharacterId"); + + b1.ToTable("Characters"); + + b1.WithOwner() + .HasForeignKey("CharacterId"); + }); + + b.OwnsOne("Wonderking.Packets.Outgoing.JobData", "JobData", b1 => + { + b1.Property("CharacterId") + .HasColumnType("uuid"); + + b1.Property("FirstJob") + .HasColumnType("smallint"); + + b1.Property("FourthJob") + .HasColumnType("smallint"); + + b1.Property("SecondJob") + .HasColumnType("smallint"); + + b1.Property("ThirdJob") + .HasColumnType("smallint"); + + b1.HasKey("CharacterId"); + + b1.ToTable("Characters"); + + b1.WithOwner() + .HasForeignKey("CharacterId"); + }); + b.Navigation("Account"); + + b.Navigation("BaseStats"); + + b.Navigation("JobData"); }); modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => diff --git a/Server/DB/WonderkingContext.cs b/Server/DB/WonderkingContext.cs index 807dfd8..b18817b 100644 --- a/Server/DB/WonderkingContext.cs +++ b/Server/DB/WonderkingContext.cs @@ -40,5 +40,7 @@ public class WonderkingContext : DbContext builder.Property(c => c.Name).HasColumnType("varchar(20)"); builder.HasMany(e => e.InventoryItems).WithOne(e => e.Character) .HasForeignKey(e => e.CharacterId).IsRequired(); + builder.OwnsOne(p => p.BaseStats); + builder.OwnsOne(p => p.JobData); }).Entity(builder => { builder.HasKey(i => i.Id); }); } diff --git a/Server/PacketHandlers/ChannelSelectionHandler.cs b/Server/PacketHandlers/ChannelSelectionHandler.cs index 12c2030..74364d5 100644 --- a/Server/PacketHandlers/ChannelSelectionHandler.cs +++ b/Server/PacketHandlers/ChannelSelectionHandler.cs @@ -1,5 +1,16 @@ + +/* Nicht gemergte Änderung aus Projekt "Server(net7.0)" +Vor: +using System.Net; using Microsoft.EntityFrameworkCore; +Nach: +using Microsoft.EntityFrameworkCore; +*/ +using Microsoft.EntityFrameworkCore; +using Server.DB.Documents; +using Wonderking.Game.Data.Character; using Wonderking.Packets.Incoming; +using Wonderking.Packets.Outgoing; namespace Server.PacketHandlers; @@ -30,8 +41,76 @@ public class ChannelSelectionHandler : IPacketHandler { var authSession = (AuthSession)session; var charactersOfAccount = this._wonderkingContext.Accounts.Include(account => account.Characters) + .ThenInclude(character => character.InventoryItems) .FirstOrDefault(a => a.Id == authSession.AccountId) ?.Characters; + ChannelSelectionResponsePacket responsePacket; + + if (charactersOfAccount != null && false) + { + responsePacket = new ChannelSelectionResponsePacket + { + ChannelIsFullFlag = 0, + Endpoint = "127.0.0.1", + Port = 12345, + Characters = charactersOfAccount.Select((character, + _) => new CharacterData + { + Name = character.Name, + Job = character.JobData, + Gender = character.Gender, + Level = character.Level, + Experience = 0, + Stats = character.BaseStats, + Health = character.Health, + Mana = character.Mana, + EquippedItems = + character.InventoryItems.Where(item => item.ItemType == ItemType.WornEquipment) + .Select(item => item.ItemId) + .ToArray(), + EquippedCashItems = character.InventoryItems + .Where(item => item.ItemType == ItemType.WornCashEquipment) + .Select(item => item.ItemId) + .ToArray(), + }) + .ToArray(), + }; + } + else + { + responsePacket = new ChannelSelectionResponsePacket + { + ChannelIsFullFlag = 0, + Endpoint = "127.0.0.1", + Port = 12345, + Characters = new[] + { + new CharacterData + { + Name = "Test243", + Job = new JobData { FirstJob = 1, SecondJob = 0, ThirdJob = 0, FourthJob = 0 }, + Gender = Gender.None, + Level = ushort.MaxValue - 1, + Experience = 255, + Stats = new BaseStats + { + Strength = 5, + Dexterity = 5, + Intelligence = 5, + Vitality = 5, + Luck = 5, + Wisdom = 5 + }, + Health = int.MaxValue - 1, + Mana = int.MaxValue - 1, + EquippedItems = Enumerable.Repeat((ushort)25,20).ToArray(), + EquippedCashItems = Enumerable.Repeat((ushort)25,20).ToArray() + }, + }, + }; + } + + authSession.Send(responsePacket); return Task.CompletedTask; } } diff --git a/Server/docker-compose.yml b/Server/docker-compose.yml index 8659b0b..1f8fa76 100644 --- a/Server/docker-compose.yml +++ b/Server/docker-compose.yml @@ -33,6 +33,8 @@ - POSTGRES_PASSWORD=continuity networks: - continuity + ports: + - "5432:5432" volumes: - db-data:/var/lib/postgresql/data healthcheck: diff --git a/Server/DB/Documents/Gender.cs b/Wonderking/Game/Data/Character/Gender.cs similarity index 63% rename from Server/DB/Documents/Gender.cs rename to Wonderking/Game/Data/Character/Gender.cs index 441de51..49f9949 100644 --- a/Server/DB/Documents/Gender.cs +++ b/Wonderking/Game/Data/Character/Gender.cs @@ -1,4 +1,4 @@ -namespace Server.DB.Documents; +namespace Wonderking.Game.Data.Character; public enum Gender : byte { diff --git a/Server/DB/Documents/PvPLevel.cs b/Wonderking/Game/Data/Character/PvPLevel.cs similarity index 92% rename from Server/DB/Documents/PvPLevel.cs rename to Wonderking/Game/Data/Character/PvPLevel.cs index d1bb1ec..8fbaca4 100644 --- a/Server/DB/Documents/PvPLevel.cs +++ b/Wonderking/Game/Data/Character/PvPLevel.cs @@ -1,4 +1,4 @@ -namespace Server.DB.Documents; +namespace Wonderking.Game.Data.Character; public enum PvPLevel : byte { diff --git a/Wonderking/Game/Data/Item/ElementalStats.cs b/Wonderking/Game/Data/Item/ElementalStats.cs index 1599a5f..e56739b 100644 --- a/Wonderking/Game/Data/Item/ElementalStats.cs +++ b/Wonderking/Game/Data/Item/ElementalStats.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Wonderking.GameData.Item; +namespace Wonderking.Game.Data.Item; [StructLayout(LayoutKind.Explicit, Size = 64)] public struct ElementalStats diff --git a/Wonderking/Game/Data/Item/Stats.cs b/Wonderking/Game/Data/Item/Stats.cs index ab60dd0..009fa3d 100644 --- a/Wonderking/Game/Data/Item/Stats.cs +++ b/Wonderking/Game/Data/Item/Stats.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace Wonderking.GameData.Item; +namespace Wonderking.Game.Data.Item; [StructLayout(LayoutKind.Explicit, Size = 24)] public struct Stats diff --git a/Wonderking/Game/Data/ItemObject.cs b/Wonderking/Game/Data/ItemObject.cs index 2f1bf2e..4284d2f 100644 --- a/Wonderking/Game/Data/ItemObject.cs +++ b/Wonderking/Game/Data/ItemObject.cs @@ -1,6 +1,5 @@ using System.Text.Json.Serialization; using Wonderking.Game.Data.Item; -using Wonderking.GameData.Item; using Wonderking.Utils; namespace Wonderking.Game.Data; diff --git a/Wonderking/Game/Reader/ItemReaderExtensions.cs b/Wonderking/Game/Reader/ItemReaderExtensions.cs index df9d71f..97a8b72 100644 --- a/Wonderking/Game/Reader/ItemReaderExtensions.cs +++ b/Wonderking/Game/Reader/ItemReaderExtensions.cs @@ -1,6 +1,6 @@ -using Wonderking.GameData.Item; +using Wonderking.Game.Data.Item; -namespace Wonderking.Game.Data.Item; +namespace Wonderking.Game.Reader; public static class ItemReaderExtensions { diff --git a/Wonderking/Packets/Outgoing/BaseStats.cs b/Wonderking/Packets/Outgoing/BaseStats.cs index 61b9bf9..b525c87 100644 --- a/Wonderking/Packets/Outgoing/BaseStats.cs +++ b/Wonderking/Packets/Outgoing/BaseStats.cs @@ -1,9 +1,9 @@ -using System.Runtime.InteropServices; +using JetBrains.Annotations; namespace Wonderking.Packets.Outgoing; -[StructLayout(LayoutKind.Auto)] -public struct BaseStats +[UsedImplicitly] +public class BaseStats { public required short Strength { get; set; } public required short Dexterity { get; set; } diff --git a/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs b/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs index 100f7dc..1293949 100644 --- a/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs +++ b/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs @@ -6,10 +6,11 @@ namespace Wonderking.Packets.Outgoing; [PacketId(OperationCode.ChannelSelectionResponse)] public class ChannelSelectionResponsePacket : IPacket { - public required byte UnknownFlag { get; set; } + public required byte ChannelIsFullFlag { get; set; } public required string Endpoint { get; set; } public required ushort Port { get; set; } public required CharacterData[] Characters { get; set; } + public required string GuildName { get; set; } public void Deserialize(byte[] data) { @@ -18,54 +19,52 @@ public class ChannelSelectionResponsePacket : IPacket public byte[] Serialize() { - Span data = stackalloc byte[1 + 16 + 2 + 2 + 132 * this.Characters.Length]; + Span data = stackalloc byte[1 + 16 + 2 + 2 + 132 * this.Characters.Length + 20]; data.Clear(); - data[0] = this.UnknownFlag; + data[0] = this.ChannelIsFullFlag; Encoding.ASCII.GetBytes(this.Endpoint, data.Slice(1, 16)); BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(17, 2), this.Port); - BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(19, 2), (ushort)this.Characters.Length); + data[19] = (byte)this.Characters.Length; // Character Data for (var i = 0; i < Characters.Length; i++) { var character = Characters[i]; - BinaryPrimitives.WriteInt32LittleEndian(data.Slice(21, 4), i); - Encoding.ASCII.GetBytes(character.Name, data.Slice(25 + (i * 132), 16)); + BinaryPrimitives.WriteInt32LittleEndian(data.Slice(20, 4), i); + Encoding.ASCII.GetBytes(character.Name, data.Slice(24 + (i * 132), 20)); // Job Data - data[41 + (i * 132)] = character.Job.FirstJob; - data[42 + (i * 132)] = character.Job.SecondJob; - data[43 + (i * 132)] = character.Job.ThirdJob; - data[44 + (i * 132)] = character.Job.FourthJob; + 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[45 + (i * 132)] = character.Gender; - data[46 + (i * 132)] = character.Level; - data[47 + (i * 132)] = character.Unknown; - data[48 + (i * 132)] = (byte)character.Experience; + data[48 + (i * 132)] = (byte)character.Gender; + BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(49 + (i * 132), 2), character.Level); + data[51 + (i * 132)] = (byte)character.Experience; // Stats - BinaryPrimitives.WriteInt16LittleEndian(data.Slice(49 + (i * 132), 2), character.Stats.Strength); - BinaryPrimitives.WriteInt16LittleEndian(data.Slice(51 + (i * 132), 2), character.Stats.Dexterity); - BinaryPrimitives.WriteInt16LittleEndian(data.Slice(53 + (i * 132), 2), character.Stats.Intelligence); - BinaryPrimitives.WriteInt16LittleEndian(data.Slice(55 + (i * 132), 2), character.Stats.Vitality); - BinaryPrimitives.WriteInt16LittleEndian(data.Slice(57 + (i * 132), 2), character.Stats.Luck); - BinaryPrimitives.WriteInt16LittleEndian(data.Slice(59 + (i * 132), 2), character.Stats.Wisdom); + 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.WriteInt32LittleEndian(data.Slice(61 + (i * 132), 4), character.Health); - BinaryPrimitives.WriteInt32LittleEndian(data.Slice(65 + (i * 132), 4), character.Mana); + BinaryPrimitives.WriteInt32LittleEndian(data.Slice(64 + (i * 132), 4), character.Health); + BinaryPrimitives.WriteInt32LittleEndian(data.Slice(68 + (i * 132), 4), character.Mana); for (var j = 0; j < 20; j++) { // Equipped Items - BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(69 + (i * 132) + (j * 2), 2), - character.EquippedItems[j] ?? 0); + BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(72 + (i * 132) + (j * 2), 2), + character.EquippedItems.Length > j ? character.EquippedItems[j] : (ushort)0); // Equipped Cash Items - BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(109 + (i * 132) + (j * 2), 2), - character.EquippedCashItems[j] ?? 0); + BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(112 + (i * 132) + (j * 2), 2), + character.EquippedCashItems.Length > j ? character.EquippedCashItems[j] : (ushort)0); } } - return data.ToArray(); } } diff --git a/Wonderking/Packets/Outgoing/CharacterData.cs b/Wonderking/Packets/Outgoing/CharacterData.cs index 6e401b6..b6d95af 100644 --- a/Wonderking/Packets/Outgoing/CharacterData.cs +++ b/Wonderking/Packets/Outgoing/CharacterData.cs @@ -1,16 +1,17 @@ +using Wonderking.Game.Data.Character; + namespace Wonderking.Packets.Outgoing; public struct CharacterData { public required string Name { get; set; } public required JobData Job { get; set; } - public required byte Gender { get; set; } - public required byte Level { get; set; } - public required byte Unknown { get; set; } + public required Gender Gender { get; set; } + public required ushort Level { get; set; } public required float Experience { get; set; } public required BaseStats Stats { get; set; } public required int Health { get; set; } public required int Mana { get; set; } - public required ushort?[] EquippedItems { get; set; } - public required ushort?[] EquippedCashItems { get; set; } + public required ushort[] EquippedItems { get; set; } + public required ushort[] EquippedCashItems { get; set; } } diff --git a/Wonderking/Packets/Outgoing/JobData.cs b/Wonderking/Packets/Outgoing/JobData.cs index 89d0b0a..6c2c9fe 100644 --- a/Wonderking/Packets/Outgoing/JobData.cs +++ b/Wonderking/Packets/Outgoing/JobData.cs @@ -1,9 +1,9 @@ -using System.Runtime.InteropServices; +using JetBrains.Annotations; namespace Wonderking.Packets.Outgoing; -[StructLayout(LayoutKind.Auto)] -public struct JobData +[UsedImplicitly] +public class JobData { public required byte FirstJob { get; set; } public required byte SecondJob { get; set; } From 98aa813f65dc10ddda270ade19a9462abd6d44e0 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Mon, 13 Nov 2023 21:14:38 +0100 Subject: [PATCH 19/34] fix: remove non working guild name --- Server/PacketHandlers/ChannelSelectionHandler.cs | 15 ++++----------- Server/Server.csproj | 2 +- .../Outgoing/ChannelSelectionResponsePacket.cs | 1 - Wonderking/Wonderking.csproj | 2 +- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/Server/PacketHandlers/ChannelSelectionHandler.cs b/Server/PacketHandlers/ChannelSelectionHandler.cs index 74364d5..e1592cf 100644 --- a/Server/PacketHandlers/ChannelSelectionHandler.cs +++ b/Server/PacketHandlers/ChannelSelectionHandler.cs @@ -1,11 +1,3 @@ - -/* Nicht gemergte Änderung aus Projekt "Server(net7.0)" -Vor: -using System.Net; -using Microsoft.EntityFrameworkCore; -Nach: -using Microsoft.EntityFrameworkCore; -*/ using Microsoft.EntityFrameworkCore; using Server.DB.Documents; using Wonderking.Game.Data.Character; @@ -46,7 +38,8 @@ public class ChannelSelectionHandler : IPacketHandler ?.Characters; ChannelSelectionResponsePacket responsePacket; - if (charactersOfAccount != null && false) + var testingChars = true; + if (charactersOfAccount != null && testingChars) { responsePacket = new ChannelSelectionResponsePacket { @@ -103,8 +96,8 @@ public class ChannelSelectionHandler : IPacketHandler }, Health = int.MaxValue - 1, Mana = int.MaxValue - 1, - EquippedItems = Enumerable.Repeat((ushort)25,20).ToArray(), - EquippedCashItems = Enumerable.Repeat((ushort)25,20).ToArray() + EquippedItems = Enumerable.Repeat((ushort)25, 20).ToArray(), + EquippedCashItems = Enumerable.Repeat((ushort)25, 20).ToArray() }, }, }; diff --git a/Server/Server.csproj b/Server/Server.csproj index a4a8d6a..854ccf5 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -8,7 +8,6 @@ Server 12 true - net8.0;net7.0 true strict Timothy (RaiNote) Schenk @@ -20,6 +19,7 @@ True LICENSE latest-recommended + net8.0 diff --git a/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs b/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs index 1293949..7e4d135 100644 --- a/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs +++ b/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs @@ -10,7 +10,6 @@ public class ChannelSelectionResponsePacket : IPacket public required string Endpoint { get; set; } public required ushort Port { get; set; } public required CharacterData[] Characters { get; set; } - public required string GuildName { get; set; } public void Deserialize(byte[] data) { diff --git a/Wonderking/Wonderking.csproj b/Wonderking/Wonderking.csproj index f5b8158..c54168d 100644 --- a/Wonderking/Wonderking.csproj +++ b/Wonderking/Wonderking.csproj @@ -3,9 +3,9 @@ enable enable - net8.0;net7.0 strict 12 + net8.0 From a9f382963b3005943f2912257d3dc47e287305c6 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Mon, 13 Nov 2023 21:16:02 +0100 Subject: [PATCH 20/34] refactor: channel response test packet --- .../PacketHandlers/ChannelSelectionHandler.cs | 65 ++++++++++--------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/Server/PacketHandlers/ChannelSelectionHandler.cs b/Server/PacketHandlers/ChannelSelectionHandler.cs index e1592cf..eedeb90 100644 --- a/Server/PacketHandlers/ChannelSelectionHandler.cs +++ b/Server/PacketHandlers/ChannelSelectionHandler.cs @@ -71,39 +71,44 @@ public class ChannelSelectionHandler : IPacketHandler } else { - responsePacket = new ChannelSelectionResponsePacket - { - ChannelIsFullFlag = 0, - Endpoint = "127.0.0.1", - Port = 12345, - Characters = new[] - { - new CharacterData - { - Name = "Test243", - Job = new JobData { FirstJob = 1, SecondJob = 0, ThirdJob = 0, FourthJob = 0 }, - Gender = Gender.None, - Level = ushort.MaxValue - 1, - Experience = 255, - Stats = new BaseStats - { - Strength = 5, - Dexterity = 5, - Intelligence = 5, - Vitality = 5, - Luck = 5, - Wisdom = 5 - }, - Health = int.MaxValue - 1, - Mana = int.MaxValue - 1, - EquippedItems = Enumerable.Repeat((ushort)25, 20).ToArray(), - EquippedCashItems = Enumerable.Repeat((ushort)25, 20).ToArray() - }, - }, - }; + responsePacket = CreateTestChannelSelectionResponsePacket(); } authSession.Send(responsePacket); return Task.CompletedTask; } + + private static ChannelSelectionResponsePacket CreateTestChannelSelectionResponsePacket() + { + return new ChannelSelectionResponsePacket + { + ChannelIsFullFlag = 0, + Endpoint = "127.0.0.1", + Port = 12345, + Characters = new[] + { + new CharacterData + { + Name = "Test243", + Job = new JobData { FirstJob = 1, SecondJob = 0, ThirdJob = 0, FourthJob = 0 }, + Gender = Gender.None, + Level = ushort.MaxValue - 1, + Experience = 255, + Stats = new BaseStats + { + Strength = 5, + Dexterity = 5, + Intelligence = 5, + Vitality = 5, + Luck = 5, + Wisdom = 5 + }, + Health = int.MaxValue - 1, + Mana = int.MaxValue - 1, + EquippedItems = Enumerable.Repeat((ushort)25, 20).ToArray(), + EquippedCashItems = Enumerable.Repeat((ushort)25, 20).ToArray() + }, + }, + }; + } } From 64ba76e41a453ccab1c414839d4bc7c40eaf88d2 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Mon, 13 Nov 2023 21:17:44 +0100 Subject: [PATCH 21/34] fix: add default empty response packet --- Server/PacketHandlers/ChannelSelectionHandler.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Server/PacketHandlers/ChannelSelectionHandler.cs b/Server/PacketHandlers/ChannelSelectionHandler.cs index eedeb90..1818578 100644 --- a/Server/PacketHandlers/ChannelSelectionHandler.cs +++ b/Server/PacketHandlers/ChannelSelectionHandler.cs @@ -33,7 +33,9 @@ public class ChannelSelectionHandler : IPacketHandler { var authSession = (AuthSession)session; var charactersOfAccount = this._wonderkingContext.Accounts.Include(account => account.Characters) - .ThenInclude(character => character.InventoryItems) + .ThenInclude(character => character.InventoryItems).Include(account => account.Characters) + .ThenInclude(character => character.JobData).Include(account => account.Characters) + .ThenInclude(character => character.BaseStats) .FirstOrDefault(a => a.Id == authSession.AccountId) ?.Characters; ChannelSelectionResponsePacket responsePacket; @@ -71,7 +73,15 @@ public class ChannelSelectionHandler : IPacketHandler } else { - responsePacket = CreateTestChannelSelectionResponsePacket(); + responsePacket = testingChars + ? CreateTestChannelSelectionResponsePacket() + : new ChannelSelectionResponsePacket + { + ChannelIsFullFlag = 0, + Endpoint = "127.0.0.1", + Port = 12345, + Characters = Array.Empty() + }; } authSession.Send(responsePacket); From 8bbd36eb89c59f8038bf1d28b8596755a8e3b56d Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Mon, 13 Nov 2023 22:42:31 +0100 Subject: [PATCH 22/34] fix: for incorrect characters --- Wonderking/Packets/Incoming/LoginInfoPacket.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Wonderking/Packets/Incoming/LoginInfoPacket.cs b/Wonderking/Packets/Incoming/LoginInfoPacket.cs index 0005eb1..dd9ecdd 100644 --- a/Wonderking/Packets/Incoming/LoginInfoPacket.cs +++ b/Wonderking/Packets/Incoming/LoginInfoPacket.cs @@ -11,9 +11,9 @@ public class LoginInfoPacket : IPacket public void Deserialize(byte[] data) { - this.Username = Encoding.ASCII.GetString(data, 0, 20).TrimEnd('\0'); + this.Username = Encoding.ASCII.GetString(data, 0, 20).TrimEnd('\0').TrimEnd('\n').TrimEnd('\0'); // Remove unnecessary Symbols - this.Password = Encoding.ASCII.GetString(data, 20, 31).TrimEnd('\n').TrimEnd('\0'); + this.Password = Encoding.ASCII.GetString(data, 20, 31).TrimEnd('\0').TrimEnd('\n').TrimEnd('\0'); } public byte[] Serialize() From 5381d78cfc0ee873b22a48856ef9dbc8523429ac Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Mon, 13 Nov 2023 22:43:37 +0100 Subject: [PATCH 23/34] fix: incorrect char slot --- .../PacketHandlers/ChannelSelectionHandler.cs | 50 +++++++++++++++++-- .../ChannelSelectionResponsePacket.cs | 5 +- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/Server/PacketHandlers/ChannelSelectionHandler.cs b/Server/PacketHandlers/ChannelSelectionHandler.cs index 1818578..2e0130c 100644 --- a/Server/PacketHandlers/ChannelSelectionHandler.cs +++ b/Server/PacketHandlers/ChannelSelectionHandler.cs @@ -41,7 +41,7 @@ public class ChannelSelectionHandler : IPacketHandler ChannelSelectionResponsePacket responsePacket; var testingChars = true; - if (charactersOfAccount != null && testingChars) + if (charactersOfAccount != null && !testingChars) { responsePacket = new ChannelSelectionResponsePacket { @@ -99,9 +99,9 @@ public class ChannelSelectionHandler : IPacketHandler { new CharacterData { - Name = "Test243", + Name = "1", Job = new JobData { FirstJob = 1, SecondJob = 0, ThirdJob = 0, FourthJob = 0 }, - Gender = Gender.None, + Gender = Gender.Female, Level = ushort.MaxValue - 1, Experience = 255, Stats = new BaseStats @@ -116,8 +116,50 @@ public class ChannelSelectionHandler : IPacketHandler Health = int.MaxValue - 1, Mana = int.MaxValue - 1, EquippedItems = Enumerable.Repeat((ushort)25, 20).ToArray(), - EquippedCashItems = Enumerable.Repeat((ushort)25, 20).ToArray() + EquippedCashItems = Enumerable.Repeat((ushort)70, 20).ToArray() }, + new CharacterData + { + Name = "2", + Job = new JobData { FirstJob = 1, SecondJob = 0, ThirdJob = 0, FourthJob = 0 }, + Gender = Gender.Female, + Level = ushort.MaxValue - 1, + Experience = 255, + Stats = new BaseStats + { + Strength = 5, + Dexterity = 5, + Intelligence = 5, + Vitality = 5, + Luck = 5, + Wisdom = 5 + }, + Health = int.MaxValue - 1, + Mana = int.MaxValue - 1, + EquippedItems = Enumerable.Repeat((ushort)25, 20).ToArray(), + EquippedCashItems = Enumerable.Repeat((ushort)70, 20).ToArray() + }, + new CharacterData + { + Name = "3", + Job = new JobData { FirstJob = 1, SecondJob = 0, ThirdJob = 0, FourthJob = 0 }, + Gender = Gender.Female, + Level = ushort.MaxValue - 1, + Experience = 255, + Stats = new BaseStats + { + Strength = 5, + Dexterity = 5, + Intelligence = 5, + Vitality = 5, + Luck = 5, + Wisdom = 5 + }, + Health = int.MaxValue - 1, + Mana = int.MaxValue - 1, + EquippedItems = Enumerable.Repeat((ushort)25, 20).ToArray(), + EquippedCashItems = Enumerable.Repeat((ushort)65, 20).ToArray() + } }, }; } diff --git a/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs b/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs index 7e4d135..f803786 100644 --- a/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs +++ b/Wonderking/Packets/Outgoing/ChannelSelectionResponsePacket.cs @@ -18,7 +18,7 @@ public class ChannelSelectionResponsePacket : IPacket public byte[] Serialize() { - Span data = stackalloc byte[1 + 16 + 2 + 2 + 132 * this.Characters.Length + 20]; + Span data = stackalloc byte[1 + 16 + 2 + 1 + 132 * this.Characters.Length]; data.Clear(); data[0] = this.ChannelIsFullFlag; Encoding.ASCII.GetBytes(this.Endpoint, data.Slice(1, 16)); @@ -29,7 +29,7 @@ public class ChannelSelectionResponsePacket : IPacket for (var i = 0; i < Characters.Length; i++) { var character = Characters[i]; - BinaryPrimitives.WriteInt32LittleEndian(data.Slice(20, 4), i); + BinaryPrimitives.WriteInt32LittleEndian(data.Slice(20 + (i * 132), 4), i); Encoding.ASCII.GetBytes(character.Name, data.Slice(24 + (i * 132), 20)); // Job Data @@ -64,6 +64,7 @@ public class ChannelSelectionResponsePacket : IPacket character.EquippedCashItems.Length > j ? character.EquippedCashItems[j] : (ushort)0); } } + return data.ToArray(); } } From 4b0840b5b91b8bbccce309b659e437faee64e08d Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Tue, 14 Nov 2023 20:07:45 +0100 Subject: [PATCH 24/34] feat: add guild data to character selection and db --- Server/DB/Documents/Character.cs | 2 + Server/DB/Documents/Guild.cs | 9 + Server/DB/Documents/GuildMember.cs | 11 + Server/DB/Documents/GuildRank.cs | 11 + .../20231114184404_AddGuildData.Designer.cs | 327 ++++++++++++++++++ .../Migrations/20231114184404_AddGuildData.cs | 104 ++++++ .../WonderkingContextModelSnapshot.cs | 78 +++++ Server/DB/WonderkingContext.cs | 7 +- .../PacketHandlers/ChannelSelectionHandler.cs | 63 ++-- Wonderking/Packets/OperationCode.cs | 3 +- .../Packets/Outgoing/CharacterGuildInfo.cs | 5 + .../CharacterSelectionSetGuildNamePacket.cs | 30 ++ 12 files changed, 623 insertions(+), 27 deletions(-) create mode 100644 Server/DB/Documents/Guild.cs create mode 100644 Server/DB/Documents/GuildMember.cs create mode 100644 Server/DB/Documents/GuildRank.cs create mode 100644 Server/DB/Migrations/20231114184404_AddGuildData.Designer.cs create mode 100644 Server/DB/Migrations/20231114184404_AddGuildData.cs create mode 100644 Wonderking/Packets/Outgoing/CharacterGuildInfo.cs create mode 100644 Wonderking/Packets/Outgoing/CharacterSelectionSetGuildNamePacket.cs diff --git a/Server/DB/Documents/Character.cs b/Server/DB/Documents/Character.cs index 49293fd..ad9c06e 100644 --- a/Server/DB/Documents/Character.cs +++ b/Server/DB/Documents/Character.cs @@ -24,4 +24,6 @@ public class Character public JobData JobData { get; set; } public int Health { get; set; } public int Mana { get; set; } + public Guid GuildId { get; set; } + public Guild Guild { get; set; } } diff --git a/Server/DB/Documents/Guild.cs b/Server/DB/Documents/Guild.cs new file mode 100644 index 0000000..a907566 --- /dev/null +++ b/Server/DB/Documents/Guild.cs @@ -0,0 +1,9 @@ +namespace Server.DB.Documents; + +public class Guild +{ + public Guid Id { get; set; } + public string Name { get; set; } + public string Notice { get; set; } + public ICollection GuildMembers { get; set; } +} diff --git a/Server/DB/Documents/GuildMember.cs b/Server/DB/Documents/GuildMember.cs new file mode 100644 index 0000000..44cb91b --- /dev/null +++ b/Server/DB/Documents/GuildMember.cs @@ -0,0 +1,11 @@ +namespace Server.DB.Documents; + +public class GuildMember +{ + public Guid Id { get; set; } + public Guid CharacterId { get; set; } + public Character Character { get; set; } + public Guid GuildId { get; set; } + public Guild Guild { get; set; } + public GuildRank Rank { get; set; } +} diff --git a/Server/DB/Documents/GuildRank.cs b/Server/DB/Documents/GuildRank.cs new file mode 100644 index 0000000..bf36540 --- /dev/null +++ b/Server/DB/Documents/GuildRank.cs @@ -0,0 +1,11 @@ +namespace Server.DB.Documents; + +public enum GuildRank : byte +{ + Initiate = 0, + Member = 1, + Veteran = 2, + Elite = 3, + Officer = 4, + Master = 5 +} diff --git a/Server/DB/Migrations/20231114184404_AddGuildData.Designer.cs b/Server/DB/Migrations/20231114184404_AddGuildData.Designer.cs new file mode 100644 index 0000000..3c4b5d6 --- /dev/null +++ b/Server/DB/Migrations/20231114184404_AddGuildData.Designer.cs @@ -0,0 +1,327 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Server.DB; + +#nullable disable + +namespace Server.DB.Migrations +{ + [DbContext(typeof(WonderkingContext))] + [Migration("20231114184404_AddGuildData")] + partial class AddGuildData + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Server.DB.Documents.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Password") + .HasColumnType("bytea"); + + b.Property("PermissionLevel") + .HasColumnType("smallint"); + + b.Property("Salt") + .HasColumnType("bytea"); + + b.Property("Username") + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Server.DB.Documents.Character", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("Experience") + .HasColumnType("bigint"); + + b.Property("Gender") + .HasColumnType("smallint"); + + b.Property("GuildId") + .HasColumnType("uuid"); + + b.Property("Health") + .HasColumnType("integer"); + + b.Property("LastXCoordinate") + .HasColumnType("smallint"); + + b.Property("LastYCoordinate") + .HasColumnType("smallint"); + + b.Property("Level") + .HasColumnType("smallint"); + + b.Property("Mana") + .HasColumnType("integer"); + + b.Property("MapId") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("varchar(20)"); + + b.Property("PvPLevel") + .HasColumnType("smallint"); + + b.Property("ServerId") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("GuildId"); + + b.ToTable("Characters"); + }); + + modelBuilder.Entity("Server.DB.Documents.Guild", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Notice") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Guild"); + }); + + modelBuilder.Entity("Server.DB.Documents.GuildMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("GuildId") + .HasColumnType("uuid"); + + b.Property("Rank") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.HasIndex("GuildId"); + + b.ToTable("GuildMember"); + }); + + modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddOption") + .HasColumnType("smallint"); + + b.Property("AddOption2") + .HasColumnType("smallint"); + + b.Property("AddOption3") + .HasColumnType("smallint"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("Count") + .HasColumnType("integer"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("ItemType") + .HasColumnType("smallint"); + + b.Property("Level") + .HasColumnType("smallint"); + + b.Property("Option") + .HasColumnType("smallint"); + + b.Property("Option2") + .HasColumnType("smallint"); + + b.Property("Option3") + .HasColumnType("smallint"); + + b.Property("Rarity") + .HasColumnType("smallint"); + + b.Property("Slot") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.ToTable("InventoryItem"); + }); + + modelBuilder.Entity("Server.DB.Documents.Character", b => + { + b.HasOne("Server.DB.Documents.Account", "Account") + .WithMany("Characters") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.DB.Documents.Guild", "Guild") + .WithMany() + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Wonderking.Packets.Outgoing.BaseStats", "BaseStats", b1 => + { + b1.Property("CharacterId") + .HasColumnType("uuid"); + + b1.Property("Dexterity") + .HasColumnType("smallint"); + + b1.Property("Intelligence") + .HasColumnType("smallint"); + + b1.Property("Luck") + .HasColumnType("smallint"); + + b1.Property("Strength") + .HasColumnType("smallint"); + + b1.Property("Vitality") + .HasColumnType("smallint"); + + b1.Property("Wisdom") + .HasColumnType("smallint"); + + b1.HasKey("CharacterId"); + + b1.ToTable("Characters"); + + b1.WithOwner() + .HasForeignKey("CharacterId"); + }); + + b.OwnsOne("Wonderking.Packets.Outgoing.JobData", "JobData", b1 => + { + b1.Property("CharacterId") + .HasColumnType("uuid"); + + b1.Property("FirstJob") + .HasColumnType("smallint"); + + b1.Property("FourthJob") + .HasColumnType("smallint"); + + b1.Property("SecondJob") + .HasColumnType("smallint"); + + b1.Property("ThirdJob") + .HasColumnType("smallint"); + + b1.HasKey("CharacterId"); + + b1.ToTable("Characters"); + + b1.WithOwner() + .HasForeignKey("CharacterId"); + }); + + b.Navigation("Account"); + + b.Navigation("BaseStats"); + + b.Navigation("Guild"); + + b.Navigation("JobData"); + }); + + modelBuilder.Entity("Server.DB.Documents.GuildMember", b => + { + b.HasOne("Server.DB.Documents.Character", "Character") + .WithMany() + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.DB.Documents.Guild", "Guild") + .WithMany("GuildMembers") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Character"); + + b.Navigation("Guild"); + }); + + modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => + { + b.HasOne("Server.DB.Documents.Character", "Character") + .WithMany("InventoryItems") + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Character"); + }); + + modelBuilder.Entity("Server.DB.Documents.Account", b => + { + b.Navigation("Characters"); + }); + + modelBuilder.Entity("Server.DB.Documents.Character", b => + { + b.Navigation("InventoryItems"); + }); + + modelBuilder.Entity("Server.DB.Documents.Guild", b => + { + b.Navigation("GuildMembers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Server/DB/Migrations/20231114184404_AddGuildData.cs b/Server/DB/Migrations/20231114184404_AddGuildData.cs new file mode 100644 index 0000000..1f93d59 --- /dev/null +++ b/Server/DB/Migrations/20231114184404_AddGuildData.cs @@ -0,0 +1,104 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Server.DB.Migrations; + +/// +public partial class AddGuildData : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "GuildId", + table: "Characters", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.CreateTable( + name: "Guild", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: true), + Notice = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Guild", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "GuildMember", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CharacterId = table.Column(type: "uuid", nullable: false), + GuildId = table.Column(type: "uuid", nullable: false), + Rank = table.Column(type: "smallint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GuildMember", x => x.Id); + table.ForeignKey( + name: "FK_GuildMember_Characters_CharacterId", + column: x => x.CharacterId, + principalTable: "Characters", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_GuildMember_Guild_GuildId", + column: x => x.GuildId, + principalTable: "Guild", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Characters_GuildId", + table: "Characters", + column: "GuildId"); + + migrationBuilder.CreateIndex( + name: "IX_GuildMember_CharacterId", + table: "GuildMember", + column: "CharacterId"); + + migrationBuilder.CreateIndex( + name: "IX_GuildMember_GuildId", + table: "GuildMember", + column: "GuildId"); + + migrationBuilder.AddForeignKey( + name: "FK_Characters_Guild_GuildId", + table: "Characters", + column: "GuildId", + principalTable: "Guild", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Characters_Guild_GuildId", + table: "Characters"); + + migrationBuilder.DropTable( + name: "GuildMember"); + + migrationBuilder.DropTable( + name: "Guild"); + + migrationBuilder.DropIndex( + name: "IX_Characters_GuildId", + table: "Characters"); + + migrationBuilder.DropColumn( + name: "GuildId", + table: "Characters"); + } +} diff --git a/Server/DB/Migrations/WonderkingContextModelSnapshot.cs b/Server/DB/Migrations/WonderkingContextModelSnapshot.cs index 933467c..a1f3412 100644 --- a/Server/DB/Migrations/WonderkingContextModelSnapshot.cs +++ b/Server/DB/Migrations/WonderkingContextModelSnapshot.cs @@ -63,6 +63,9 @@ namespace Server.DB.Migrations b.Property("Gender") .HasColumnType("smallint"); + b.Property("GuildId") + .HasColumnType("uuid"); + b.Property("Health") .HasColumnType("integer"); @@ -94,9 +97,52 @@ namespace Server.DB.Migrations b.HasIndex("AccountId"); + b.HasIndex("GuildId"); + b.ToTable("Characters"); }); + modelBuilder.Entity("Server.DB.Documents.Guild", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Notice") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Guild"); + }); + + modelBuilder.Entity("Server.DB.Documents.GuildMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CharacterId") + .HasColumnType("uuid"); + + b.Property("GuildId") + .HasColumnType("uuid"); + + b.Property("Rank") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("CharacterId"); + + b.HasIndex("GuildId"); + + b.ToTable("GuildMember"); + }); + modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => { b.Property("Id") @@ -157,6 +203,12 @@ namespace Server.DB.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("Server.DB.Documents.Guild", "Guild") + .WithMany() + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.OwnsOne("Wonderking.Packets.Outgoing.BaseStats", "BaseStats", b1 => { b1.Property("CharacterId") @@ -217,9 +269,30 @@ namespace Server.DB.Migrations b.Navigation("BaseStats"); + b.Navigation("Guild"); + b.Navigation("JobData"); }); + modelBuilder.Entity("Server.DB.Documents.GuildMember", b => + { + b.HasOne("Server.DB.Documents.Character", "Character") + .WithMany() + .HasForeignKey("CharacterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Server.DB.Documents.Guild", "Guild") + .WithMany("GuildMembers") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Character"); + + b.Navigation("Guild"); + }); + modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => { b.HasOne("Server.DB.Documents.Character", "Character") @@ -240,6 +313,11 @@ namespace Server.DB.Migrations { b.Navigation("InventoryItems"); }); + + modelBuilder.Entity("Server.DB.Documents.Guild", b => + { + b.Navigation("GuildMembers"); + }); #pragma warning restore 612, 618 } } diff --git a/Server/DB/WonderkingContext.cs b/Server/DB/WonderkingContext.cs index b18817b..94efe19 100644 --- a/Server/DB/WonderkingContext.cs +++ b/Server/DB/WonderkingContext.cs @@ -42,5 +42,10 @@ public class WonderkingContext : DbContext .HasForeignKey(e => e.CharacterId).IsRequired(); builder.OwnsOne(p => p.BaseStats); builder.OwnsOne(p => p.JobData); - }).Entity(builder => { builder.HasKey(i => i.Id); }); + }).Entity(builder => { builder.HasKey(i => i.Id); }).Entity(builder => + { + builder.HasKey(g => g.Id); + builder.HasMany(g => g.GuildMembers).WithOne(g => g.Guild).HasForeignKey(g => g.GuildId) + .IsRequired(); + }); } diff --git a/Server/PacketHandlers/ChannelSelectionHandler.cs b/Server/PacketHandlers/ChannelSelectionHandler.cs index 2e0130c..de92f14 100644 --- a/Server/PacketHandlers/ChannelSelectionHandler.cs +++ b/Server/PacketHandlers/ChannelSelectionHandler.cs @@ -32,44 +32,48 @@ public class ChannelSelectionHandler : IPacketHandler public Task HandleAsync(ChannelSelectionPacket packet, TcpSession session) { var authSession = (AuthSession)session; - var charactersOfAccount = this._wonderkingContext.Accounts.Include(account => account.Characters) - .ThenInclude(character => character.InventoryItems).Include(account => account.Characters) - .ThenInclude(character => character.JobData).Include(account => account.Characters) - .ThenInclude(character => character.BaseStats) - .FirstOrDefault(a => a.Id == authSession.AccountId) - ?.Characters; ChannelSelectionResponsePacket responsePacket; + CharacterSelectionSetGuildNamePacket guildNameResponsePacket; - var testingChars = true; - if (charactersOfAccount != null && !testingChars) + var hasCharacters = this._wonderkingContext.Accounts.Include(account => account.Characters) + .FirstOrDefault(a => a.Id == authSession.AccountId)?.Characters.Count != 0; + var testingChars = false; + if (hasCharacters && !testingChars) { responsePacket = new ChannelSelectionResponsePacket { ChannelIsFullFlag = 0, Endpoint = "127.0.0.1", Port = 12345, - Characters = charactersOfAccount.Select((character, - _) => new CharacterData + Characters = this._wonderkingContext.Characters.Where(c => c.AccountId == authSession.AccountId) + .Select(c => + new CharacterData { - Name = character.Name, - Job = character.JobData, - Gender = character.Gender, - Level = character.Level, + Name = c.Name, + Job = c.JobData, + Gender = c.Gender, + Level = c.Level, Experience = 0, - Stats = character.BaseStats, - Health = character.Health, - Mana = character.Mana, + Stats = c.BaseStats, + Health = c.Health, + Mana = c.Mana, EquippedItems = - character.InventoryItems.Where(item => item.ItemType == ItemType.WornEquipment) + c.InventoryItems.Where(item => item.ItemType == ItemType.WornEquipment) + .Select(item => item.ItemId) + .ToArray(), + EquippedCashItems = c.InventoryItems + .Where(item => item.ItemType == ItemType.WornCashEquipment) .Select(item => item.ItemId) .ToArray(), - EquippedCashItems = character.InventoryItems - .Where(item => item.ItemType == ItemType.WornCashEquipment) - .Select(item => item.ItemId) - .ToArray(), }) .ToArray(), }; + + guildNameResponsePacket = new CharacterSelectionSetGuildNamePacket + { + GuildNames = this._wonderkingContext.Characters.Where(c => c.AccountId == authSession.AccountId) + .Select(character => character.Guild.Name).ToArray() + }; } else { @@ -82,9 +86,18 @@ public class ChannelSelectionHandler : IPacketHandler Port = 12345, Characters = Array.Empty() }; + guildNameResponsePacket = new CharacterSelectionSetGuildNamePacket + { + GuildNames = new[] { "ABCDEFGHIJKLMNOP", "QRSTUVWXYZ123456", "A Guild Name For" } + }; } authSession.Send(responsePacket); + if (guildNameResponsePacket.GuildNames.Length > 0) + { + authSession.Send(guildNameResponsePacket); + } + return Task.CompletedTask; } @@ -136,8 +149,8 @@ public class ChannelSelectionHandler : IPacketHandler }, Health = int.MaxValue - 1, Mana = int.MaxValue - 1, - EquippedItems = Enumerable.Repeat((ushort)25, 20).ToArray(), - EquippedCashItems = Enumerable.Repeat((ushort)70, 20).ToArray() + EquippedItems = Enumerable.Repeat((ushort)35, 20).ToArray(), + EquippedCashItems = Enumerable.Repeat((ushort)55, 20).ToArray() }, new CharacterData { @@ -157,7 +170,7 @@ public class ChannelSelectionHandler : IPacketHandler }, Health = int.MaxValue - 1, Mana = int.MaxValue - 1, - EquippedItems = Enumerable.Repeat((ushort)25, 20).ToArray(), + EquippedItems = Enumerable.Repeat((ushort)45, 20).ToArray(), EquippedCashItems = Enumerable.Repeat((ushort)65, 20).ToArray() } }, diff --git a/Wonderking/Packets/OperationCode.cs b/Wonderking/Packets/OperationCode.cs index c866557..97e5584 100644 --- a/Wonderking/Packets/OperationCode.cs +++ b/Wonderking/Packets/OperationCode.cs @@ -5,5 +5,6 @@ public enum OperationCode : ushort LoginInfo = 11, LoginResponse = 12, ChannelSelection = 13, - ChannelSelectionResponse = 13 + ChannelSelectionResponse = 13, + CharacterSelectionSetGuildName = 19, } diff --git a/Wonderking/Packets/Outgoing/CharacterGuildInfo.cs b/Wonderking/Packets/Outgoing/CharacterGuildInfo.cs new file mode 100644 index 0000000..7f41eb0 --- /dev/null +++ b/Wonderking/Packets/Outgoing/CharacterGuildInfo.cs @@ -0,0 +1,5 @@ +namespace Wonderking.Packets.Outgoing; + +public struct CharacterGuildInfo +{ +} diff --git a/Wonderking/Packets/Outgoing/CharacterSelectionSetGuildNamePacket.cs b/Wonderking/Packets/Outgoing/CharacterSelectionSetGuildNamePacket.cs new file mode 100644 index 0000000..bc6bf2f --- /dev/null +++ b/Wonderking/Packets/Outgoing/CharacterSelectionSetGuildNamePacket.cs @@ -0,0 +1,30 @@ +using System.Text; + +namespace Wonderking.Packets.Outgoing; + +[PacketId(OperationCode.CharacterSelectionSetGuildName)] +public class CharacterSelectionSetGuildNamePacket : IPacket +{ + public required string[] GuildNames { get; set; } + + public void Deserialize(byte[] data) + { + throw new NotSupportedException(); + } + + public byte[] Serialize() + { + Span data = stackalloc byte[1 + (16 + 1 + 1) * this.GuildNames.Length]; + data.Clear(); + data[0] = (byte)this.GuildNames.Length; + for (var i = 0; i < this.GuildNames.Length; i++) + { + data[1 + (i * (16 + 1 + 1))] = (byte)i; + Encoding.ASCII.GetBytes(this.GuildNames[i], data.Slice(2 + (i * (16 + 1 + 1)), 16)); + // Null terminator + data[18 + (i * (16 + 1 + 1))] = 0; + } + + return data.ToArray(); + } +} From d5a311fce70931b0f8c9a0d8c795d01c690bc12b Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Tue, 14 Nov 2023 20:09:09 +0100 Subject: [PATCH 25/34] chore: .net8 released --- global.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/global.json b/global.json index ad3eb2c..f7fb55b 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "7.0.403", - "rollForward": "latestMajor", - "allowPrerelease": true + "version": "8.0.100", + "rollForward": "latestMinor", + "allowPrerelease": false } -} \ No newline at end of file +} From 90846c04b8588d72a6224b4d426821fefb80b3fc Mon Sep 17 00:00:00 2001 From: renovate-bot Date: Tue, 14 Nov 2023 17:59:02 +0000 Subject: [PATCH 26/34] chore(deps): update dotnet monorepo Signed-off-by: noreply@rainote.dev --- Server/Server.csproj | 17 ++++++----------- global.json | 8 ++++---- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Server/Server.csproj b/Server/Server.csproj index 854ccf5..0dfbb16 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -6,8 +6,9 @@ warnings Linux Server - 12 + default true + net8.0;net7.0 true strict Timothy (RaiNote) Schenk @@ -19,7 +20,6 @@ True LICENSE latest-recommended - net8.0 @@ -55,7 +55,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -63,13 +63,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + @@ -93,7 +89,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/global.json b/global.json index f7fb55b..7fd6a2a 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "8.0.100", - "rollForward": "latestMinor", - "allowPrerelease": false + "version": "7.0.404", + "rollForward": "latestMajor", + "allowPrerelease": true } -} +} \ No newline at end of file From 5b143c1ef3f7b897beff80eb467d6020a7507526 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Wed, 8 Nov 2023 15:39:25 +0100 Subject: [PATCH 27/34] chore: add migration for inventory data --- .../WonderkingContextModelSnapshot.cs | 148 +----------------- Server/Server.csproj | 4 + 2 files changed, 6 insertions(+), 146 deletions(-) diff --git a/Server/DB/Migrations/WonderkingContextModelSnapshot.cs b/Server/DB/Migrations/WonderkingContextModelSnapshot.cs index a1f3412..ab1f034 100644 --- a/Server/DB/Migrations/WonderkingContextModelSnapshot.cs +++ b/Server/DB/Migrations/WonderkingContextModelSnapshot.cs @@ -63,12 +63,6 @@ namespace Server.DB.Migrations b.Property("Gender") .HasColumnType("smallint"); - b.Property("GuildId") - .HasColumnType("uuid"); - - b.Property("Health") - .HasColumnType("integer"); - b.Property("LastXCoordinate") .HasColumnType("smallint"); @@ -78,9 +72,6 @@ namespace Server.DB.Migrations b.Property("Level") .HasColumnType("smallint"); - b.Property("Mana") - .HasColumnType("integer"); - b.Property("MapId") .HasColumnType("integer"); @@ -97,52 +88,9 @@ namespace Server.DB.Migrations b.HasIndex("AccountId"); - b.HasIndex("GuildId"); - b.ToTable("Characters"); }); - modelBuilder.Entity("Server.DB.Documents.Guild", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Notice") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Guild"); - }); - - modelBuilder.Entity("Server.DB.Documents.GuildMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CharacterId") - .HasColumnType("uuid"); - - b.Property("GuildId") - .HasColumnType("uuid"); - - b.Property("Rank") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.HasIndex("CharacterId"); - - b.HasIndex("GuildId"); - - b.ToTable("GuildMember"); - }); - modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => { b.Property("Id") @@ -164,8 +112,8 @@ namespace Server.DB.Migrations b.Property("Count") .HasColumnType("integer"); - b.Property("ItemId") - .HasColumnType("integer"); + b.Property("ItemId") + .HasColumnType("smallint"); b.Property("ItemType") .HasColumnType("smallint"); @@ -203,94 +151,7 @@ namespace Server.DB.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Server.DB.Documents.Guild", "Guild") - .WithMany() - .HasForeignKey("GuildId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.OwnsOne("Wonderking.Packets.Outgoing.BaseStats", "BaseStats", b1 => - { - b1.Property("CharacterId") - .HasColumnType("uuid"); - - b1.Property("Dexterity") - .HasColumnType("smallint"); - - b1.Property("Intelligence") - .HasColumnType("smallint"); - - b1.Property("Luck") - .HasColumnType("smallint"); - - b1.Property("Strength") - .HasColumnType("smallint"); - - b1.Property("Vitality") - .HasColumnType("smallint"); - - b1.Property("Wisdom") - .HasColumnType("smallint"); - - b1.HasKey("CharacterId"); - - b1.ToTable("Characters"); - - b1.WithOwner() - .HasForeignKey("CharacterId"); - }); - - b.OwnsOne("Wonderking.Packets.Outgoing.JobData", "JobData", b1 => - { - b1.Property("CharacterId") - .HasColumnType("uuid"); - - b1.Property("FirstJob") - .HasColumnType("smallint"); - - b1.Property("FourthJob") - .HasColumnType("smallint"); - - b1.Property("SecondJob") - .HasColumnType("smallint"); - - b1.Property("ThirdJob") - .HasColumnType("smallint"); - - b1.HasKey("CharacterId"); - - b1.ToTable("Characters"); - - b1.WithOwner() - .HasForeignKey("CharacterId"); - }); - b.Navigation("Account"); - - b.Navigation("BaseStats"); - - b.Navigation("Guild"); - - b.Navigation("JobData"); - }); - - modelBuilder.Entity("Server.DB.Documents.GuildMember", b => - { - b.HasOne("Server.DB.Documents.Character", "Character") - .WithMany() - .HasForeignKey("CharacterId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Server.DB.Documents.Guild", "Guild") - .WithMany("GuildMembers") - .HasForeignKey("GuildId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Character"); - - b.Navigation("Guild"); }); modelBuilder.Entity("Server.DB.Documents.InventoryItem", b => @@ -313,11 +174,6 @@ namespace Server.DB.Migrations { b.Navigation("InventoryItems"); }); - - modelBuilder.Entity("Server.DB.Documents.Guild", b => - { - b.Navigation("GuildMembers"); - }); #pragma warning restore 612, 618 } } diff --git a/Server/Server.csproj b/Server/Server.csproj index 0dfbb16..0cfbc67 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -66,6 +66,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From d42ed882be2d79697e8a13f1d368b6f7ec235af5 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Tue, 14 Nov 2023 20:09:09 +0100 Subject: [PATCH 28/34] chore: .net8 released --- global.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/global.json b/global.json index 7fd6a2a..fe2a068 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,8 @@ { "sdk": { - "version": "7.0.404", - "rollForward": "latestMajor", - "allowPrerelease": true + "version": "8.0.100", + "rollForward": "latestMinor", + "allowPrerelease": false } -} \ No newline at end of file +} + From 818f12e7712f91ab7dce98408abfcde5b2388dcd Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Tue, 14 Nov 2023 20:24:02 +0100 Subject: [PATCH 29/34] chore: deps upgrade --- Benchmarks/Benchmarks.csproj | 2 +- Server/Server.csproj | 39 ++++++++++++++++++------------------ Wonderking/Wonderking.csproj | 4 ++-- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Benchmarks/Benchmarks.csproj b/Benchmarks/Benchmarks.csproj index 737b096..eb55e9f 100644 --- a/Benchmarks/Benchmarks.csproj +++ b/Benchmarks/Benchmarks.csproj @@ -14,7 +14,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Server/Server.csproj b/Server/Server.csproj index 0cfbc67..1a94494 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -6,9 +6,8 @@ warnings Linux Server - default + 12 true - net8.0;net7.0 true strict Timothy (RaiNote) Schenk @@ -20,6 +19,7 @@ True LICENSE latest-recommended + net8.0 @@ -41,21 +41,21 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -63,20 +63,20 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -93,6 +93,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Wonderking/Wonderking.csproj b/Wonderking/Wonderking.csproj index c54168d..f41aaad 100644 --- a/Wonderking/Wonderking.csproj +++ b/Wonderking/Wonderking.csproj @@ -13,8 +13,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 97a171e4ed5732f061ac9b0700d5a7685a8a5c76 Mon Sep 17 00:00:00 2001 From: renovate-bot Date: Tue, 14 Nov 2023 17:59:02 +0000 Subject: [PATCH 30/34] chore(deps): update dotnet monorepo Signed-off-by: noreply@rainote.dev --- Server/Server.csproj | 41 ++++++++++++++++++----------------------- global.json | 9 ++++----- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/Server/Server.csproj b/Server/Server.csproj index 1a94494..0dfbb16 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -6,8 +6,9 @@ warnings Linux Server - 12 + default true + net8.0;net7.0 true strict Timothy (RaiNote) Schenk @@ -19,7 +20,6 @@ True LICENSE latest-recommended - net8.0 @@ -41,21 +41,21 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -63,20 +63,16 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -93,7 +89,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/global.json b/global.json index fe2a068..7fd6a2a 100644 --- a/global.json +++ b/global.json @@ -1,8 +1,7 @@ { "sdk": { - "version": "8.0.100", - "rollForward": "latestMinor", - "allowPrerelease": false + "version": "7.0.404", + "rollForward": "latestMajor", + "allowPrerelease": true } -} - +} \ No newline at end of file From 6de1c0b97f18a7d819152b2b20d26a652002167a Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Wed, 8 Nov 2023 15:39:25 +0100 Subject: [PATCH 31/34] chore: add migration for inventory data --- Server/Server.csproj | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Server/Server.csproj b/Server/Server.csproj index 0dfbb16..8d1be11 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -63,9 +63,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From a984fcd001c36f43e75c63f70efb6d3254cfad50 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Tue, 14 Nov 2023 20:09:09 +0100 Subject: [PATCH 32/34] chore: .net8 released --- global.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/global.json b/global.json index 7fd6a2a..f7fb55b 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "7.0.404", - "rollForward": "latestMajor", - "allowPrerelease": true + "version": "8.0.100", + "rollForward": "latestMinor", + "allowPrerelease": false } -} \ No newline at end of file +} From 109b628a4a72d5bd2c0fda6f7b3e27a73b156d58 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Tue, 14 Nov 2023 20:34:26 +0100 Subject: [PATCH 33/34] chore: .net8 update --- Server/Server.csproj | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Server/Server.csproj b/Server/Server.csproj index 8d1be11..c96f480 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -8,7 +8,7 @@ Server default true - net8.0;net7.0 + net8.0 true strict Timothy (RaiNote) Schenk @@ -41,17 +41,17 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -63,20 +63,20 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 4c1b948df145f530bb691a127bb561431747d9e1 Mon Sep 17 00:00:00 2001 From: Timothy Schenk Date: Tue, 14 Nov 2023 20:45:53 +0100 Subject: [PATCH 34/34] fix: incompatible nuget package --- Server/Server.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/Server.csproj b/Server/Server.csproj index c96f480..ba239ce 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -93,6 +93,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive +