// Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. using DotNext.Collections.Generic; using Microsoft.EntityFrameworkCore; using NetCoreServer; using Server.DB; using Server.DB.Documents; using Wonderking.Game.Data.Character; using Wonderking.Packets.Incoming; using Wonderking.Packets.Outgoing; using Wonderking.Packets.Outgoing.Data; namespace Server.PacketHandlers; public class ChannelSelectionHandler : IPacketHandler { private readonly WonderkingContext _wonderkingContext; public ChannelSelectionHandler(WonderkingContext wonderkingContext) { _wonderkingContext = wonderkingContext; } public async Task HandleAsync(ChannelSelectionPacket packet, TcpSession session) { var authSession = (AuthSession)session; ChannelSelectionResponsePacket responsePacket; var guildNameResponsePacket = new CharacterSelectionSetGuildNamePacket { GuildNames = Array.Empty() }; var account = await _wonderkingContext.Accounts .FirstOrDefaultAsync(a => a.Id == authSession.AccountId); var amountOfCharacter = await _wonderkingContext.Characters .Where(c => c.Account.Id == authSession.AccountId) .CountAsync(); if (account != null && amountOfCharacter > 0) { responsePacket = new ChannelSelectionResponsePacket { ChannelIsFullFlag = 0, Endpoint = "127.0.0.1", Port = 2000, Characters = await GetCharacterDataAsync(account.Id).ToArrayAsync() }; guildNameResponsePacket.GuildNames = await _wonderkingContext.Characters .Where(c => c.Account.Id == authSession.AccountId) .Where(c => c.Guild != null) .Select(character => character.Guild.Name).ToArrayAsync(); } else { responsePacket = new ChannelSelectionResponsePacket { ChannelIsFullFlag = 0, Endpoint = "127.0.0.1", Port = 2000, Characters = Array.Empty() }; } await authSession.SendAsync(responsePacket); if (guildNameResponsePacket.GuildNames.Length > 0 && guildNameResponsePacket.GuildNames.Select(n => n != string.Empty).Any()) { await authSession.SendAsync(guildNameResponsePacket); } } private static ushort[] GetItemIDsByInventoryTab(IEnumerable items) { var ids = new ushort[20]; ids.AsSpan().Clear(); foreach (var item in items) { if (item.Slot > 20) { continue; } ids[item.Slot] = item.ItemId; } return ids; } private async IAsyncEnumerable GetCharacterDataAsync(Guid accountId) { await foreach (var c in _getCharacters(_wonderkingContext, accountId)) { yield return new CharacterData { Name = c.Name, Job = c.JobData, Gender = c.Gender, Level = c.Level, // TODO: Calculate instead of clamping based on max experience for level Experience = Math.Clamp(c.Experience, 0, 100), Stats = c.BaseStats, Health = c.Health, Mana = c.Mana, EquippedItems = GetItemIDsByInventoryTab(c.InventoryItems.Where(i => i.InventoryTab == InventoryTab.WornEquipment)), EquippedCashItems = GetItemIDsByInventoryTab(c.InventoryItems.Where(i => i.InventoryTab == InventoryTab.WornCashEquipment)) }; } } private static readonly Func> _getCharacters = EF.CompileAsyncQuery((WonderkingContext context, Guid accountId) => context.Characters.AsNoTracking().AsSplitQuery() .Where(c => c.Account.Id == accountId) .Select(c => new CharacterDataProjection( c.Name, c.JobData, c.Gender, c.Level, c.Experience, c.BaseStats, c.Health, c.Mana, c.InventoryItems.Select(i => new InventoryItemProjection(i.ItemId, i.Slot, i.InventoryTab)) )).Take(3)); private sealed record InventoryItemProjection(ushort ItemId, byte Slot, InventoryTab InventoryTab); private sealed record CharacterDataProjection( string Name, JobData JobData, Gender Gender, ushort Level, long Experience, BaseStats BaseStats, int Health, int Mana, IEnumerable InventoryItems); }