feat: character creation works
All checks were successful
Build, Package and Push Images / preprocess (push) Successful in 2s
Build, Package and Push Images / build (push) Successful in 23s
Build, Package and Push Images / sonarqube (push) Has been skipped
Build, Package and Push Images / sbom-scan (push) Successful in 32s
Build, Package and Push Images / container-build (push) Successful in 3m16s
Build, Package and Push Images / container-sbom-scan (push) Successful in 32s

requires base stats and parsing of values provided by user
This commit is contained in:
Timothy Schenk 2023-11-15 20:00:08 +01:00
parent 4781a5c12a
commit 46649adfd8
29 changed files with 1276 additions and 91 deletions

View file

@ -1,5 +1,10 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Server.DB.Documents; namespace Server.DB.Documents;
[Index(nameof(Username), IsUnique = true), Index(nameof(Id), IsUnique = true)]
public class Account public class Account
{ {
public Account(string username, byte[] password, string email, byte permissionLevel, byte[] salt) public Account(string username, byte[] password, string email, byte permissionLevel, byte[] salt)
@ -11,12 +16,15 @@ public class Account
this.Salt = salt; this.Salt = salt;
} }
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; } public Guid Id { get; set; }
public string Username { get; set; } [Column(TypeName = "varchar(20)")] public string Username { get; set; }
public byte[] Password { get; set; } [Column(TypeName = "bytea")] public byte[] Password { get; set; }
public string Email { get; set; }
[EmailAddress] public string Email { get; set; }
public byte PermissionLevel { get; set; } public byte PermissionLevel { get; set; }
public byte[] Salt { get; set; } [Column(TypeName = "bytea")] public byte[] Salt { get; set; }
public ICollection<Character> Characters { get; } = new List<Character>(); public virtual ICollection<Character> Characters { get; } = new List<Character>();
} }

View file

@ -1,3 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Wonderking.Game.Data.Character; using Wonderking.Game.Data.Character;
using Wonderking.Packets.Outgoing.Data; using Wonderking.Packets.Outgoing.Data;
@ -5,10 +7,12 @@ namespace Server.DB.Documents;
public class Character public class Character
{ {
public byte ServerId { get; set; } public virtual Account Account { get; set; }
public Guid AccountId { get; set; }
public Account Account { get; set; } [Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; } public Guid Id { get; set; }
public ushort MapId { get; set; } public ushort MapId { get; set; }
public string Name { get; set; } public string Name { get; set; }
public short LastXCoordinate { get; set; } public short LastXCoordinate { get; set; }
@ -17,13 +21,12 @@ public class Character
public Gender Gender { get; set; } public Gender Gender { get; set; }
public long Experience { get; set; } public long Experience { get; set; }
public byte Level { get; set; } public byte Level { get; set; }
public ICollection<InventoryItem> InventoryItems { get; set; } public virtual ICollection<InventoryItem> InventoryItems { get; set; }
public BaseStats BaseStats { get; set; } public BaseStats BaseStats { get; set; }
public JobData JobData { get; set; } public JobData JobData { get; set; }
public int Health { get; set; } public int Health { get; set; }
public int Mana { get; set; } public int Mana { get; set; }
public Guid GuildId { get; set; } public virtual Guild Guild { get; set; }
public Guild Guild { get; set; }
} }

View file

@ -1,9 +1,15 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Server.DB.Documents; namespace Server.DB.Documents;
public class Guild public class Guild
{ {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; } public Guid Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Notice { get; set; } public string Notice { get; set; }
public ICollection<GuildMember> GuildMembers { get; set; } public virtual ICollection<GuildMember> GuildMembers { get; set; }
} }

View file

@ -1,11 +1,15 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Server.DB.Documents; namespace Server.DB.Documents;
public class GuildMember public class GuildMember
{ {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid CharacterId { get; set; }
public Character Character { get; set; } public Character Character { get; set; }
public Guid GuildId { get; set; }
public Guild Guild { get; set; } public Guild Guild { get; set; }
public GuildRank Rank { get; set; } public GuildRank Rank { get; set; }
} }

View file

@ -8,7 +8,7 @@ public class InventoryItem
public ushort ItemId { get; set; } public ushort ItemId { get; set; }
public ushort Count { get; set; } public ushort Count { get; set; }
public byte Slot { get; set; } public byte Slot { get; set; }
public ItemType ItemType { get; set; } public InventoryTab InventoryTab { get; set; }
public byte Level { get; set; } public byte Level { get; set; }
public byte Rarity { get; set; } public byte Rarity { get; set; }
public byte AddOption { get; set; } public byte AddOption { get; set; }

View file

@ -1,6 +1,6 @@
namespace Server.DB.Documents; namespace Server.DB.Documents;
public enum ItemType : byte public enum InventoryTab : byte
{ {
WornEquipment = 0, WornEquipment = 0,
WornCashEquipment = 1, WornCashEquipment = 1,

View file

@ -0,0 +1,333 @@
// <auto-generated />
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("20231115174714_GuildIsNotRequired")]
partial class GuildIsNotRequired
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Server.DB.Documents.Account", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Email")
.HasColumnType("text");
b.Property<byte[]>("Password")
.HasColumnType("bytea");
b.Property<byte>("PermissionLevel")
.HasColumnType("smallint");
b.Property<byte[]>("Salt")
.HasColumnType("bytea");
b.Property<string>("Username")
.HasColumnType("varchar(20)");
b.HasKey("Id");
b.HasIndex("Username")
.IsUnique();
b.ToTable("Accounts");
});
modelBuilder.Entity("Server.DB.Documents.Character", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("AccountId")
.HasColumnType("uuid");
b.Property<long>("Experience")
.HasColumnType("bigint");
b.Property<byte>("Gender")
.HasColumnType("smallint");
b.Property<Guid>("GuildId")
.HasColumnType("uuid");
b.Property<int>("Health")
.HasColumnType("integer");
b.Property<short>("LastXCoordinate")
.HasColumnType("smallint");
b.Property<short>("LastYCoordinate")
.HasColumnType("smallint");
b.Property<byte>("Level")
.HasColumnType("smallint");
b.Property<int>("Mana")
.HasColumnType("integer");
b.Property<int>("MapId")
.HasColumnType("integer");
b.Property<string>("Name")
.HasColumnType("varchar(20)");
b.Property<byte>("PvPLevel")
.HasColumnType("smallint");
b.Property<byte>("ServerId")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("AccountId");
b.HasIndex("GuildId");
b.HasIndex("Name")
.IsUnique();
b.ToTable("Characters");
});
modelBuilder.Entity("Server.DB.Documents.Guild", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Notice")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Guild");
});
modelBuilder.Entity("Server.DB.Documents.GuildMember", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("CharacterId")
.HasColumnType("uuid");
b.Property<Guid>("GuildId")
.HasColumnType("uuid");
b.Property<byte>("Rank")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("CharacterId");
b.HasIndex("GuildId");
b.ToTable("GuildMember");
});
modelBuilder.Entity("Server.DB.Documents.InventoryItem", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<byte>("AddOption")
.HasColumnType("smallint");
b.Property<byte>("AddOption2")
.HasColumnType("smallint");
b.Property<byte>("AddOption3")
.HasColumnType("smallint");
b.Property<Guid>("CharacterId")
.HasColumnType("uuid");
b.Property<int>("Count")
.HasColumnType("integer");
b.Property<byte>("InventoryTab")
.HasColumnType("smallint");
b.Property<int>("ItemId")
.HasColumnType("integer");
b.Property<byte>("Level")
.HasColumnType("smallint");
b.Property<short>("Option")
.HasColumnType("smallint");
b.Property<short>("Option2")
.HasColumnType("smallint");
b.Property<short>("Option3")
.HasColumnType("smallint");
b.Property<byte>("Rarity")
.HasColumnType("smallint");
b.Property<byte>("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.Data.BaseStats", "BaseStats", b1 =>
{
b1.Property<Guid>("CharacterId")
.HasColumnType("uuid");
b1.Property<short>("Dexterity")
.HasColumnType("smallint");
b1.Property<short>("Intelligence")
.HasColumnType("smallint");
b1.Property<short>("Luck")
.HasColumnType("smallint");
b1.Property<short>("Strength")
.HasColumnType("smallint");
b1.Property<short>("Vitality")
.HasColumnType("smallint");
b1.Property<short>("Wisdom")
.HasColumnType("smallint");
b1.HasKey("CharacterId");
b1.ToTable("Characters");
b1.WithOwner()
.HasForeignKey("CharacterId");
});
b.OwnsOne("Wonderking.Packets.Outgoing.Data.JobData", "JobData", b1 =>
{
b1.Property<Guid>("CharacterId")
.HasColumnType("uuid");
b1.Property<byte>("FirstJob")
.HasColumnType("smallint");
b1.Property<byte>("FourthJob")
.HasColumnType("smallint");
b1.Property<byte>("SecondJob")
.HasColumnType("smallint");
b1.Property<byte>("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
}
}
}

View file

@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Server.DB.Migrations;
/// <inheritdoc />
public partial class GuildIsNotRequired : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "ItemType",
table: "InventoryItem",
newName: "InventoryTab");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "InventoryTab",
table: "InventoryItem",
newName: "ItemType");
}
}

View file

@ -0,0 +1,322 @@
// <auto-generated />
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("20231115183824_SwitchToDataAnnotations")]
partial class SwitchToDataAnnotations
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Server.DB.Documents.Account", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Email")
.HasColumnType("text");
b.Property<byte[]>("Password")
.HasColumnType("bytea");
b.Property<byte>("PermissionLevel")
.HasColumnType("smallint");
b.Property<byte[]>("Salt")
.HasColumnType("bytea");
b.Property<string>("Username")
.HasColumnType("varchar(20)");
b.HasKey("Id");
b.HasIndex("Id")
.IsUnique();
b.HasIndex("Username")
.IsUnique();
b.ToTable("Accounts");
});
modelBuilder.Entity("Server.DB.Documents.Character", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid?>("AccountId")
.HasColumnType("uuid");
b.Property<long>("Experience")
.HasColumnType("bigint");
b.Property<byte>("Gender")
.HasColumnType("smallint");
b.Property<Guid?>("GuildId")
.HasColumnType("uuid");
b.Property<int>("Health")
.HasColumnType("integer");
b.Property<short>("LastXCoordinate")
.HasColumnType("smallint");
b.Property<short>("LastYCoordinate")
.HasColumnType("smallint");
b.Property<byte>("Level")
.HasColumnType("smallint");
b.Property<int>("Mana")
.HasColumnType("integer");
b.Property<int>("MapId")
.HasColumnType("integer");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<byte>("PvPLevel")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("AccountId");
b.HasIndex("GuildId");
b.ToTable("Characters");
});
modelBuilder.Entity("Server.DB.Documents.Guild", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Notice")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Guild");
});
modelBuilder.Entity("Server.DB.Documents.GuildMember", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid?>("CharacterId")
.HasColumnType("uuid");
b.Property<Guid?>("GuildId")
.HasColumnType("uuid");
b.Property<byte>("Rank")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("CharacterId");
b.HasIndex("GuildId");
b.ToTable("GuildMember");
});
modelBuilder.Entity("Server.DB.Documents.InventoryItem", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<byte>("AddOption")
.HasColumnType("smallint");
b.Property<byte>("AddOption2")
.HasColumnType("smallint");
b.Property<byte>("AddOption3")
.HasColumnType("smallint");
b.Property<Guid>("CharacterId")
.HasColumnType("uuid");
b.Property<int>("Count")
.HasColumnType("integer");
b.Property<byte>("InventoryTab")
.HasColumnType("smallint");
b.Property<int>("ItemId")
.HasColumnType("integer");
b.Property<byte>("Level")
.HasColumnType("smallint");
b.Property<short>("Option")
.HasColumnType("smallint");
b.Property<short>("Option2")
.HasColumnType("smallint");
b.Property<short>("Option3")
.HasColumnType("smallint");
b.Property<byte>("Rarity")
.HasColumnType("smallint");
b.Property<byte>("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");
b.HasOne("Server.DB.Documents.Guild", "Guild")
.WithMany()
.HasForeignKey("GuildId");
b.OwnsOne("Wonderking.Packets.Outgoing.Data.BaseStats", "BaseStats", b1 =>
{
b1.Property<Guid>("CharacterId")
.HasColumnType("uuid");
b1.Property<short>("Dexterity")
.HasColumnType("smallint");
b1.Property<short>("Intelligence")
.HasColumnType("smallint");
b1.Property<short>("Luck")
.HasColumnType("smallint");
b1.Property<short>("Strength")
.HasColumnType("smallint");
b1.Property<short>("Vitality")
.HasColumnType("smallint");
b1.Property<short>("Wisdom")
.HasColumnType("smallint");
b1.HasKey("CharacterId");
b1.ToTable("Characters");
b1.WithOwner()
.HasForeignKey("CharacterId");
});
b.OwnsOne("Wonderking.Packets.Outgoing.Data.JobData", "JobData", b1 =>
{
b1.Property<Guid>("CharacterId")
.HasColumnType("uuid");
b1.Property<byte>("FirstJob")
.HasColumnType("smallint");
b1.Property<byte>("FourthJob")
.HasColumnType("smallint");
b1.Property<byte>("SecondJob")
.HasColumnType("smallint");
b1.Property<byte>("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");
b.HasOne("Server.DB.Documents.Guild", "Guild")
.WithMany("GuildMembers")
.HasForeignKey("GuildId");
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
}
}
}

View file

@ -0,0 +1,230 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Server.DB.Migrations;
/// <inheritdoc />
public partial class SwitchToDataAnnotations : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Characters_Accounts_AccountId",
table: "Characters");
migrationBuilder.DropForeignKey(
name: "FK_Characters_Guild_GuildId",
table: "Characters");
migrationBuilder.DropForeignKey(
name: "FK_GuildMember_Characters_CharacterId",
table: "GuildMember");
migrationBuilder.DropForeignKey(
name: "FK_GuildMember_Guild_GuildId",
table: "GuildMember");
migrationBuilder.DropIndex(
name: "IX_Characters_Name",
table: "Characters");
migrationBuilder.DropColumn(
name: "ServerId",
table: "Characters");
migrationBuilder.AlterColumn<Guid>(
name: "GuildId",
table: "GuildMember",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<Guid>(
name: "CharacterId",
table: "GuildMember",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Characters",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "varchar(20)",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "GuildId",
table: "Characters",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<Guid>(
name: "AccountId",
table: "Characters",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.CreateIndex(
name: "IX_Accounts_Id",
table: "Accounts",
column: "Id",
unique: true);
migrationBuilder.AddForeignKey(
name: "FK_Characters_Accounts_AccountId",
table: "Characters",
column: "AccountId",
principalTable: "Accounts",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Characters_Guild_GuildId",
table: "Characters",
column: "GuildId",
principalTable: "Guild",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_GuildMember_Characters_CharacterId",
table: "GuildMember",
column: "CharacterId",
principalTable: "Characters",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_GuildMember_Guild_GuildId",
table: "GuildMember",
column: "GuildId",
principalTable: "Guild",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Characters_Accounts_AccountId",
table: "Characters");
migrationBuilder.DropForeignKey(
name: "FK_Characters_Guild_GuildId",
table: "Characters");
migrationBuilder.DropForeignKey(
name: "FK_GuildMember_Characters_CharacterId",
table: "GuildMember");
migrationBuilder.DropForeignKey(
name: "FK_GuildMember_Guild_GuildId",
table: "GuildMember");
migrationBuilder.DropIndex(
name: "IX_Accounts_Id",
table: "Accounts");
migrationBuilder.AlterColumn<Guid>(
name: "GuildId",
table: "GuildMember",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "CharacterId",
table: "GuildMember",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Characters",
type: "varchar(20)",
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "GuildId",
table: "Characters",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "AccountId",
table: "Characters",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AddColumn<byte>(
name: "ServerId",
table: "Characters",
type: "smallint",
nullable: false,
defaultValue: (byte)0);
migrationBuilder.CreateIndex(
name: "IX_Characters_Name",
table: "Characters",
column: "Name",
unique: true);
migrationBuilder.AddForeignKey(
name: "FK_Characters_Accounts_AccountId",
table: "Characters",
column: "AccountId",
principalTable: "Accounts",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Characters_Guild_GuildId",
table: "Characters",
column: "GuildId",
principalTable: "Guild",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_GuildMember_Characters_CharacterId",
table: "GuildMember",
column: "CharacterId",
principalTable: "Characters",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_GuildMember_Guild_GuildId",
table: "GuildMember",
column: "GuildId",
principalTable: "Guild",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}

View file

@ -45,6 +45,9 @@ namespace Server.DB.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("Id")
.IsUnique();
b.HasIndex("Username") b.HasIndex("Username")
.IsUnique(); .IsUnique();
@ -57,7 +60,7 @@ namespace Server.DB.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<Guid>("AccountId") b.Property<Guid?>("AccountId")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<long>("Experience") b.Property<long>("Experience")
@ -66,7 +69,7 @@ namespace Server.DB.Migrations
b.Property<byte>("Gender") b.Property<byte>("Gender")
.HasColumnType("smallint"); .HasColumnType("smallint");
b.Property<Guid>("GuildId") b.Property<Guid?>("GuildId")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<int>("Health") b.Property<int>("Health")
@ -88,23 +91,17 @@ namespace Server.DB.Migrations
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<string>("Name") b.Property<string>("Name")
.HasColumnType("varchar(20)"); .HasColumnType("text");
b.Property<byte>("PvPLevel") b.Property<byte>("PvPLevel")
.HasColumnType("smallint"); .HasColumnType("smallint");
b.Property<byte>("ServerId")
.HasColumnType("smallint");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("AccountId"); b.HasIndex("AccountId");
b.HasIndex("GuildId"); b.HasIndex("GuildId");
b.HasIndex("Name")
.IsUnique();
b.ToTable("Characters"); b.ToTable("Characters");
}); });
@ -131,10 +128,10 @@ namespace Server.DB.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<Guid>("CharacterId") b.Property<Guid?>("CharacterId")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<Guid>("GuildId") b.Property<Guid?>("GuildId")
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<byte>("Rank") b.Property<byte>("Rank")
@ -170,12 +167,12 @@ namespace Server.DB.Migrations
b.Property<int>("Count") b.Property<int>("Count")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<byte>("InventoryTab")
.HasColumnType("smallint");
b.Property<int>("ItemId") b.Property<int>("ItemId")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<byte>("ItemType")
.HasColumnType("smallint");
b.Property<byte>("Level") b.Property<byte>("Level")
.HasColumnType("smallint"); .HasColumnType("smallint");
@ -205,15 +202,11 @@ namespace Server.DB.Migrations
{ {
b.HasOne("Server.DB.Documents.Account", "Account") b.HasOne("Server.DB.Documents.Account", "Account")
.WithMany("Characters") .WithMany("Characters")
.HasForeignKey("AccountId") .HasForeignKey("AccountId");
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Server.DB.Documents.Guild", "Guild") b.HasOne("Server.DB.Documents.Guild", "Guild")
.WithMany() .WithMany()
.HasForeignKey("GuildId") .HasForeignKey("GuildId");
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.OwnsOne("Wonderking.Packets.Outgoing.Data.BaseStats", "BaseStats", b1 => b.OwnsOne("Wonderking.Packets.Outgoing.Data.BaseStats", "BaseStats", b1 =>
{ {
@ -284,15 +277,11 @@ namespace Server.DB.Migrations
{ {
b.HasOne("Server.DB.Documents.Character", "Character") b.HasOne("Server.DB.Documents.Character", "Character")
.WithMany() .WithMany()
.HasForeignKey("CharacterId") .HasForeignKey("CharacterId");
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Server.DB.Documents.Guild", "Guild") b.HasOne("Server.DB.Documents.Guild", "Guild")
.WithMany("GuildMembers") .WithMany("GuildMembers")
.HasForeignKey("GuildId") .HasForeignKey("GuildId");
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Character"); b.Navigation("Character");

View file

@ -24,30 +24,4 @@ public class WonderkingContext : DbContext
.UseNpgsql( .UseNpgsql(
$"Host={this._configuration["DB:Host"]};Username={this._configuration["DB:Username"]};Password={this._configuration["DB:Password"]};Database={this._configuration["DB:Database"]};Port={this._configuration["DB:Port"]}") $"Host={this._configuration["DB:Host"]};Username={this._configuration["DB:Username"]};Password={this._configuration["DB:Password"]};Database={this._configuration["DB:Database"]};Port={this._configuration["DB:Port"]}")
.EnableSensitiveDataLogging().UseLoggerFactory(this._loggerFactory); .EnableSensitiveDataLogging().UseLoggerFactory(this._loggerFactory);
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
modelBuilder.Entity<Account>(builder =>
{
builder.Property(b => b.Username).HasColumnType("varchar(20)");
builder.HasIndex(b => b.Username).IsUnique();
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();
}).Entity<Character>(builder =>
{
builder.HasKey(c => c.Id);
builder.Property(c => c.Name).HasColumnType("varchar(20)");
builder.HasIndex(c => c.Name).IsUnique();
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<InventoryItem>(builder => { builder.HasKey(i => i.Id); }).Entity<Guild>(builder =>
{
builder.HasKey(g => g.Id);
builder.HasMany(g => g.GuildMembers).WithOne(g => g.Guild).HasForeignKey(g => g.GuildId)
.IsRequired();
});
} }

View file

@ -46,7 +46,7 @@ public class ChannelSelectionHandler : IPacketHandler<ChannelSelectionPacket>
ChannelIsFullFlag = 0, ChannelIsFullFlag = 0,
Endpoint = "127.0.0.1", Endpoint = "127.0.0.1",
Port = 12345, Port = 12345,
Characters = this._wonderkingContext.Characters.Where(c => c.AccountId == authSession.AccountId) Characters = this._wonderkingContext.Characters.AsNoTracking().Where(c => c.Account.Id == authSession.AccountId)
.Select(c => .Select(c =>
new CharacterData new CharacterData
{ {
@ -59,11 +59,11 @@ public class ChannelSelectionHandler : IPacketHandler<ChannelSelectionPacket>
Health = c.Health, Health = c.Health,
Mana = c.Mana, Mana = c.Mana,
EquippedItems = EquippedItems =
c.InventoryItems.Where(item => item.ItemType == ItemType.WornEquipment) c.InventoryItems.Where(item => item.InventoryTab == InventoryTab.WornEquipment)
.Select(item => item.ItemId) .Select(item => item.ItemId)
.ToArray(), .ToArray(),
EquippedCashItems = c.InventoryItems EquippedCashItems = c.InventoryItems
.Where(item => item.ItemType == ItemType.WornCashEquipment) .Where(item => item.InventoryTab == InventoryTab.WornCashEquipment)
.Select(item => item.ItemId) .Select(item => item.ItemId)
.ToArray(), .ToArray(),
}) })
@ -72,7 +72,7 @@ public class ChannelSelectionHandler : IPacketHandler<ChannelSelectionPacket>
guildNameResponsePacket = new CharacterSelectionSetGuildNamePacket guildNameResponsePacket = new CharacterSelectionSetGuildNamePacket
{ {
GuildNames = this._wonderkingContext.Characters.Where(c => c.AccountId == authSession.AccountId) GuildNames = this._wonderkingContext.Characters.Where(c => c.Account.Id == authSession.AccountId)
.Select(character => character.Guild.Name).ToArray() .Select(character => character.Guild.Name).ToArray()
}; };
} }

View file

@ -0,0 +1,89 @@
using Microsoft.EntityFrameworkCore;
using NetCoreServer;
using Server.DB;
using Server.DB.Documents;
using Server.Services;
using Wonderking.Game.Data.Character;
using Wonderking.Packets.Incoming;
using Wonderking.Packets.Outgoing;
using Wonderking.Packets.Outgoing.Data;
namespace Server.PacketHandlers;
public class CharacterCreationHandler : IPacketHandler<CharacterCreationPacket>
{
private readonly WonderkingContext _wonderkingContext;
private readonly ItemObjectPoolService _itemObjectPoolService;
public CharacterCreationHandler(WonderkingContext wonderkingContext, ItemObjectPoolService itemObjectPoolService)
{
_wonderkingContext = wonderkingContext;
_itemObjectPoolService = itemObjectPoolService;
}
public Task HandleAsync(CharacterCreationPacket packet, TcpSession session)
{
var authSession = session as AuthSession;
var account =
_wonderkingContext.Accounts.FirstOrDefault(a => authSession != null && a.Id == authSession.AccountId);
var items = new List<InventoryItem>
{
_itemObjectPoolService.GetBaseInventoryItem(25),
_itemObjectPoolService.GetBaseInventoryItem(764),
_itemObjectPoolService.GetBaseInventoryItem(766),
_itemObjectPoolService.GetBaseInventoryItem(763),
_itemObjectPoolService.GetBaseInventoryItem(767)
};
account?.Characters.Add(new Character
{
Account = account,
MapId = 300,
Name = packet.Name,
LastXCoordinate = 113,
LastYCoordinate = 0,
PvPLevel = PvPLevel.None,
Gender = Gender.None,
Experience = 0,
Level = 1,
InventoryItems = items,
BaseStats = new BaseStats
{
Strength = 5,
Dexterity = 5,
Intelligence = 5,
Vitality = 5,
Luck = 5,
Wisdom = 5
},
JobData = new JobData { FirstJob = packet.FirstJob, SecondJob = 0, ThirdJob = 0, FourthJob = 0 },
Health = 250,
Mana = 250,
});
_wonderkingContext.SaveChanges();
var character = this._wonderkingContext.Characters.AsNoTracking()
.Where(c => authSession != null && c.Account.Id == authSession.AccountId && c.Name == packet.Name)
.Select(c =>
new CharacterData
{
Name = c.Name,
Job = c.JobData,
Gender = c.Gender,
Level = c.Level,
Experience = 0,
Stats = c.BaseStats,
Health = c.Health,
Mana = c.Mana,
EquippedItems =
c.InventoryItems.Where(item => item.InventoryTab == InventoryTab.WornEquipment)
.Select(item => item.ItemId)
.ToArray(),
EquippedCashItems = c.InventoryItems
.Where(item => item.InventoryTab == InventoryTab.WornCashEquipment)
.Select(item => item.ItemId)
.ToArray(),
}).FirstOrDefault();
authSession?.Send(new CharacterCreationResponsePacket { Character = character });
return Task.CompletedTask;
}
}

View file

@ -0,0 +1,25 @@
using NetCoreServer;
using Server.DB;
using Wonderking.Packets.Incoming;
using Wonderking.Packets.Outgoing;
namespace Server.PacketHandlers;
public class CharacterNameCheckHandler : IPacketHandler<CharacterNameCheckPacket>
{
private readonly WonderkingContext _wonderkingContext;
public CharacterNameCheckHandler(WonderkingContext wonderkingContext)
{
_wonderkingContext = wonderkingContext;
}
public Task HandleAsync(CharacterNameCheckPacket packet, TcpSession session)
{
var isTaken = _wonderkingContext.Characters.Any(c => c.Name == packet.Name);
var responsePacket = new CharacterNameCheckPacketResponse { IsTaken = isTaken };
var authSession = session as AuthSession;
authSession?.Send(responsePacket);
return Task.CompletedTask;
}
}

View file

@ -22,9 +22,10 @@ builder.Logging.AddFile("Logs/Server-{Date}.json.log", LogLevel.Trace, isJson: t
builder.Services.AddEntityFrameworkNpgsql(); builder.Services.AddEntityFrameworkNpgsql();
builder.Services.AddDbContext<WonderkingContext>(); builder.Services.AddDbContext<WonderkingContext>();
builder.Services.AddSingleton<PacketDistributorService>(); builder.Services.AddSingleton<PacketDistributorService>();
builder.Services.AddSingleton<ItemObjectPoolService>();
builder.Services.AddHostedService<ItemObjectPoolService>();
builder.Services.AddHostedService(provider => builder.Services.AddHostedService(provider =>
provider.GetService<PacketDistributorService>() ?? throw new InvalidOperationException()); provider.GetService<PacketDistributorService>() ?? throw new InvalidOperationException());
builder.Services.AddSingleton<ItemObjectPoolService>();
builder.Services.AddMassTransit(x => builder.Services.AddMassTransit(x =>
{ {
x.UsingInMemory((context, configurator) => configurator.ConfigureEndpoints(context)); x.UsingInMemory((context, configurator) => configurator.ConfigureEndpoints(context));

View file

@ -1,6 +1,8 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Server.DB.Documents;
using Wonderking.Game.Data; using Wonderking.Game.Data;
using Wonderking.Game.Reader; using Wonderking.Game.Reader;
@ -10,9 +12,11 @@ public class ItemObjectPoolService : IHostedService
{ {
readonly ConcurrentDictionary<uint, ItemObject> _itemObjectPool = new(); readonly ConcurrentDictionary<uint, ItemObject> _itemObjectPool = new();
private readonly ItemReader _itemReader; private readonly ItemReader _itemReader;
private readonly ILogger<ItemObjectPoolService> _logger;
public ItemObjectPoolService(IConfiguration configuration) public ItemObjectPoolService(IConfiguration configuration, ILogger<ItemObjectPoolService> logger)
{ {
_logger = logger;
_itemReader = new ItemReader(configuration.GetSection("Game").GetSection("Data").GetValue<string>("Path") ?? _itemReader = new ItemReader(configuration.GetSection("Game").GetSection("Data").GetValue<string>("Path") ??
string.Empty); string.Empty);
} }
@ -20,23 +24,31 @@ public class ItemObjectPoolService : IHostedService
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
var amountOfEntries = _itemReader.GetAmountOfEntries(); var amountOfEntries = _itemReader.GetAmountOfEntries();
ParallelEnumerable.Range(0, (int)amountOfEntries).AsParallel().ForAll(i => Parallel.For(0, (int)amountOfEntries, i =>
{ {
var itemObject = _itemReader.GetEntry((uint)i); var itemObject = _itemReader.GetEntry((uint)i);
_itemObjectPool.TryAdd(itemObject.ItemID, itemObject); var result = _itemObjectPool.TryAdd(itemObject.ItemID, itemObject);
if (!result)
{
throw new Exception($"Failed to add item {itemObject.ItemID} to the item object pool");
}
_logger.LogTrace("Item with {ID} has been added", itemObject.ItemID);
}); });
_logger.LogInformation("A total of {AmountOfEntries} items have been added to the item object pool",
_itemObjectPool.Count);
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
{ {
_itemReader.Dispose();
return Task.CompletedTask; return Task.CompletedTask;
} }
public ItemObject GetItem(ushort itemId) public ItemObject GetItem(ushort itemId)
{ {
return _itemObjectPool[itemId]; _ = _itemObjectPool.TryGetValue(itemId, out var itemObject);
return itemObject;
} }
public bool ContainsItem(ushort itemId) public bool ContainsItem(ushort itemId)
@ -48,4 +60,24 @@ public class ItemObjectPoolService : IHostedService
{ {
return _itemObjectPool.AsReadOnly().Values.AsQueryable(); return _itemObjectPool.AsReadOnly().Values.AsQueryable();
} }
public InventoryItem GetBaseInventoryItem(ushort itemId, ushort count = 1, bool isWorn = false)
{
var item = this.GetItem(itemId);
return new InventoryItem
{
ItemId = itemId,
Count = count,
Slot = 0,
InventoryTab = InventoryTab.WornEquipment,
Level = item.MinimumLevelRequirement,
Rarity = 0,
AddOption = 0,
AddOption2 = 0,
AddOption3 = 0,
Option = 0,
Option2 = 0,
Option3 = 0
};
}
} }

View file

@ -12,14 +12,14 @@
- DB:Port=5432 - DB:Port=5432
- DB:Username=continuity - DB:Username=continuity
- DB:Password=continuity - DB:Password=continuity
- Game:Data:Path=/app/data - Game:Data:Path=/app/data/
networks: networks:
- continuity - continuity
ports: ports:
- "10001:10001" - "10001:10001"
volumes: volumes:
- type: bind - type: bind
source: game-data source: ../wk-data
target: /app/data target: /app/data
read_only: true read_only: true

View file

@ -11,7 +11,7 @@
}, },
"Game":{ "Game":{
"Data":{ "Data":{
"Path": "../wk-data" "Path": "../wk-data/"
} }
} }
} }

View file

@ -7,7 +7,7 @@ public abstract class DataReader<T>
protected DataReader(string path) protected DataReader(string path)
{ {
Path = path; Path = path;
DatFileContent = new(GetDatFileContent(path).ToArray()); DatFileContent = GetDatFileContent(path).ToArray();
} }
private protected string Path { get; init; } private protected string Path { get; init; }
@ -33,7 +33,7 @@ public abstract class DataReader<T>
throw new NotSupportedException("XorKey is null"); throw new NotSupportedException("XorKey is null");
} }
protected MemoryStream DatFileContent { get; } protected byte[] DatFileContent { get; }
private static Span<byte> GetDatFileContent(string path) private static Span<byte> GetDatFileContent(string path)
{ {

View file

@ -1,9 +1,8 @@
using Wonderking.Game.Data; using Wonderking.Game.Data;
using Wonderking.Game.Data.Item;
namespace Wonderking.Game.Reader; namespace Wonderking.Game.Reader;
public class ItemReader(string path) : DataReader<ItemObject>(path), IDisposable public class ItemReader(string path) : DataReader<ItemObject>(path)
{ {
public override uint GetAmountOfEntries() public override uint GetAmountOfEntries()
{ {
@ -13,8 +12,9 @@ public class ItemReader(string path) : DataReader<ItemObject>(path), IDisposable
public override ItemObject GetEntry(uint entryId) public override ItemObject GetEntry(uint entryId)
{ {
var item = new ItemObject(); var item = new ItemObject();
this.DatFileContent.Position = 9 + entryId * this.GetSizeOfEntry(); var arraySegment = new ArraySegment<byte>(DatFileContent,
var reader = new BinaryReader(this.DatFileContent); 9 + (int)entryId * this.GetSizeOfEntry(), this.GetSizeOfEntry());
var reader = new BinaryReader(new MemoryStream(arraySegment.ToArray()));
item.ItemID = reader.ReadUInt32(); //9 item.ItemID = reader.ReadUInt32(); //9
item.Disabled = reader.ReadUInt32() == 1; //13 item.Disabled = reader.ReadUInt32() == 1; //13
item.ItemType = reader.ReadUInt32(); //17 item.ItemType = reader.ReadUInt32(); //17
@ -100,8 +100,4 @@ public class ItemReader(string path) : DataReader<ItemObject>(path), IDisposable
return item; return item;
} }
public void Dispose()
{
this.DatFileContent.Dispose();
}
} }

View file

@ -0,0 +1,39 @@
using System.Text;
using Wonderking.Game.Data.Character;
namespace Wonderking.Packets.Incoming;
[PacketId(OperationCode.CharacterCreation)]
public class CharacterCreationPacket : IPacket
{
public byte Slot { get; set; }
public byte Unknown { get; set; }
public ushort Id { get; set; }
public string Name { get; set; }
public byte FirstJob { get; set; }
public Gender Gender { get; set; }
public byte Hair { get; set; }
public byte Eyes { get; set; }
public byte Shirt { get; set; }
public byte Pants { get; set; }
public void Deserialize(byte[] data)
{
Slot = data[0];
Unknown = data[1];
Id = BitConverter.ToUInt16(data, 2);
Name = Encoding.ASCII.GetString(data, 4, 20).TrimEnd('\0').TrimEnd('\n').TrimEnd('\0');
FirstJob = data[24];
Gender = (Gender)data[25];
Hair = data[26];
Eyes = data[27];
Shirt = data[28];
Pants = data[29];
}
public byte[] Serialize()
{
throw new NotSupportedException();
}
}

View file

@ -0,0 +1,19 @@
using System.Text;
namespace Wonderking.Packets.Incoming;
[PacketId(OperationCode.CharacterNameCheck)]
public class CharacterNameCheckPacket : IPacket
{
public required string Name { get; set; }
public void Deserialize(byte[] data)
{
Encoding.ASCII.GetString(data, 0, 20);
}
public byte[] Serialize()
{
throw new NotSupportedException();
}
}

View file

@ -6,5 +6,11 @@ public enum OperationCode : ushort
LoginResponse = 12, LoginResponse = 12,
ChannelSelection = 13, ChannelSelection = 13,
ChannelSelectionResponse = 13, ChannelSelectionResponse = 13,
CharacterNameCheck = 14,
CharacterNameCheckResponse = 14,
CharacterCreation = 15,
CharacterCreationResponse = 15,
CharacterDeletion = 16,
CharacterSelection = 17,
CharacterSelectionSetGuildName = 19, CharacterSelectionSetGuildName = 19,
} }

View file

@ -0,0 +1,58 @@
using System.Buffers.Binary;
using System.Text;
using Wonderking.Packets.Outgoing.Data;
namespace Wonderking.Packets.Outgoing;
[PacketId(OperationCode.CharacterCreationResponse)]
public class CharacterCreationResponsePacket : IPacket
{
public required CharacterData Character { get; set; }
public void Deserialize(byte[] data)
{
throw new NotSupportedException();
}
public byte[] Serialize()
{
Span<byte> data = stackalloc byte[132];
BinaryPrimitives.WriteInt32LittleEndian(data.Slice(0 + 132, 4), 0);
Encoding.ASCII.GetBytes(Character.Name, data.Slice(4 + 132, 20));
// Job Data
data[24 + 132] = Character.Job.FirstJob;
data[25 + 132] = Character.Job.SecondJob;
data[26 + 132] = Character.Job.ThirdJob;
data[27 + 132] = Character.Job.FourthJob;
data[28 + 132] = (byte)Character.Gender;
BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(49 + 132, 2), Character.Level);
data[31 + 132] = (byte)Character.Experience;
// Stats
BinaryPrimitives.WriteInt16LittleEndian(data.Slice(32 + 132, 2), Character.Stats.Strength);
BinaryPrimitives.WriteInt16LittleEndian(data.Slice(34 + 132, 2), Character.Stats.Dexterity);
BinaryPrimitives.WriteInt16LittleEndian(data.Slice(36 + 132, 2), Character.Stats.Intelligence);
BinaryPrimitives.WriteInt16LittleEndian(data.Slice(38 + 132, 2), Character.Stats.Vitality);
BinaryPrimitives.WriteInt16LittleEndian(data.Slice(40 + 132, 2), Character.Stats.Luck);
BinaryPrimitives.WriteInt16LittleEndian(data.Slice(42 + 132, 2), Character.Stats.Wisdom);
BinaryPrimitives.WriteInt32LittleEndian(data.Slice(44 + 132, 4), Character.Health);
BinaryPrimitives.WriteInt32LittleEndian(data.Slice(48 + 132, 4), Character.Mana);
for (var i = 0; i < 20; i++)
{
// Equipped Items
BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(52 + 132 + i * 2, 2),
Character.EquippedItems.Length > i ? Character.EquippedItems[i] : (ushort)0);
// Equipped Cash Items
BinaryPrimitives.WriteUInt16LittleEndian(data.Slice(92 + 132 + i * 2, 2),
Character.EquippedCashItems.Length > i ? Character.EquippedCashItems[i] : (ushort)0);
}
return data.ToArray();
}
}

View file

@ -0,0 +1,19 @@
namespace Wonderking.Packets.Outgoing;
[PacketId(OperationCode.CharacterNameCheckResponse)]
public class CharacterNameCheckPacketResponse : IPacket
{
public required bool IsTaken { get; set; }
public void Deserialize(byte[] data)
{
throw new NotSupportedException();
}
public byte[] Serialize()
{
Span<byte> data = stackalloc byte[1];
data[0] = this.IsTaken ? (byte)1 : (byte)0;
return data.ToArray();
}
}

View file

@ -1,8 +1,10 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
namespace Wonderking.Packets.Outgoing.Data; namespace Wonderking.Packets.Outgoing.Data;
[UsedImplicitly] [UsedImplicitly]
[Owned]
public class BaseStats public class BaseStats
{ {
public required short Strength { get; set; } public required short Strength { get; set; }

View file

@ -1,8 +1,10 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
namespace Wonderking.Packets.Outgoing.Data; namespace Wonderking.Packets.Outgoing.Data;
[UsedImplicitly] [UsedImplicitly]
[Owned]
public class JobData public class JobData
{ {
public required byte FirstJob { get; set; } public required byte FirstJob { get; set; }

View file

@ -22,6 +22,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.8.14"> <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.8.14">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>