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(); + } +}