// Copyright (c) 2023 Timothy Schenk. Subject to the GNU AGPL Version 3 License. using Continuity.AuthServer.DB; using Continuity.AuthServer.DB.Documents; using Continuity.AuthServer.Services; using Microsoft.EntityFrameworkCore; using NetCoreServer; using Wonderking.Game.Data.Character; using Wonderking.Game.Mapping; using Wonderking.Packets.Incoming; using Wonderking.Packets.Outgoing; using Wonderking.Packets.Outgoing.Data; namespace Continuity.AuthServer.PacketHandlers; public class CharacterCreationHandler : IPacketHandler { private readonly CharacterStatsMappingConfiguration _characterStatsMapping; private readonly ItemObjectPoolService _itemObjectPoolService; private readonly WonderkingContext _wonderkingContext; public CharacterCreationHandler(WonderkingContext wonderkingContext, ItemObjectPoolService itemObjectPoolService, CharacterStatsMappingConfiguration characterStatsMappingConfiguration) { _wonderkingContext = wonderkingContext; _itemObjectPoolService = itemObjectPoolService; _characterStatsMapping = characterStatsMappingConfiguration; } public async Task HandleAsync(CharacterCreationPacket packet, TcpSession session) { if (session is not AuthSession authSession) { return; } var account = await _wonderkingContext.Accounts.FirstOrDefaultAsync(a => a.Id == authSession.AccountId); if (account is null) { return; } var firstJobConfig = SelectFirstJobConfig(packet.FirstJob); var items = CreateDefaultItems(packet, firstJobConfig); var toBeAddedCharacter = CreateDefaultCharacter(packet, account, items, firstJobConfig); account.Characters.Add(toBeAddedCharacter); await _wonderkingContext.SaveChangesAsync(); var character = new CharacterData { Name = toBeAddedCharacter.Name, Job = toBeAddedCharacter.JobData, Gender = toBeAddedCharacter.Gender, Level = toBeAddedCharacter.Level, Experience = toBeAddedCharacter.Experience, Stats = toBeAddedCharacter.BaseStats, Health = toBeAddedCharacter.Health, Mana = toBeAddedCharacter.Mana, EquippedItems = GetItemIDsByInventoryTab(toBeAddedCharacter.InventoryItems .Where(item => item.InventoryTab == InventoryTab.WornEquipment) .Select(item => new Tuple(item.ItemId, item.Slot)).AsEnumerable()), EquippedCashItems = GetItemIDsByInventoryTab(toBeAddedCharacter.InventoryItems .Where(item => item.InventoryTab == InventoryTab.WornCashEquipment) .Select(item => new Tuple(item.ItemId, item.Slot)).AsEnumerable()) }; await authSession.SendAsync(new CharacterCreationResponsePacket { Character = character, Slot = packet.Slot, isDuplicate = false }); } private InventoryItem[] CreateDefaultItems(CharacterCreationPacket packet, JobSpecificMapping firstJobConfig) { var mappedDefaultItems = _characterStatsMapping.DefaultCharacterMapping.Items .Select(i => _itemObjectPoolService.GetBaseInventoryItem(i.Id, i.Quantity)).ToArray(); var mappedJobItems = firstJobConfig.Items .Select(i => _itemObjectPoolService.GetBaseInventoryItem(i.Id, i.Quantity)).ToArray(); var defaultItems = CreateChosenItems(packet); InventoryItem[] items = [ .. mappedDefaultItems, .. mappedJobItems, .. defaultItems ]; return items; } private static Character CreateDefaultCharacter(CharacterCreationPacket packet, Account account, InventoryItem[] items, JobSpecificMapping firstJobConfig) { return new Character() { Account = account, MapId = 300, Name = packet.Name, LastXCoordinate = 113, LastYCoordinate = 0, PvPLevel = PvPLevel.None, Gender = packet.Gender, Experience = 0, Level = 1, InventoryItems = items, BaseStats = firstJobConfig.BaseStats, JobData = new JobData { FirstJob = packet.FirstJob, SecondJob = 0, ThirdJob = 0, FourthJob = 0 }, Health = CalculateCurrentHealth(1, firstJobConfig), Mana = CalculateCurrentMana(1, firstJobConfig) }; } private JobSpecificMapping SelectFirstJobConfig(byte firstJob) { return firstJob switch { 1 => _characterStatsMapping.Swordsman, 2 => _characterStatsMapping.Mage, 3 => _characterStatsMapping.Thief, 4 => _characterStatsMapping.Scout, _ => _characterStatsMapping.Swordsman }; } private InventoryItem[] CreateChosenItems(CharacterCreationPacket packet) { return new[] { _itemObjectPoolService.GetBaseInventoryItem((ushort)((packet.FirstJob - 1) * 6 + ((byte)packet.Gender - 1) * 3 + packet.Hair + 1)), _itemObjectPoolService.GetBaseInventoryItem((ushort)((packet.FirstJob - 1) * 6 + ((byte)packet.Gender - 1) * 3 + packet.Eyes + 25)), _itemObjectPoolService.GetBaseInventoryItem((ushort)(((byte)packet.Gender - 1) * 3 + packet.Shirt + 49)), _itemObjectPoolService.GetBaseInventoryItem((ushort)(((byte)packet.Gender - 1) * 3 + packet.Pants + 58)) }; } private static int CalculateCurrentHealth(ushort level, JobSpecificMapping firstJobConfig) { return (int)((level - 1) * firstJobConfig.DynamicStats.HealthPerLevel + firstJobConfig.BaseStats.Vitality * firstJobConfig.DynamicStats.HealthPerVitality); } private static int CalculateCurrentMana(ushort level, JobSpecificMapping firstJobConfig) { return (int)((level - 1) * firstJobConfig.DynamicStats.ManaPerLevel + firstJobConfig.BaseStats.Wisdom * firstJobConfig.DynamicStats.ManaPerWisdom); } private static ushort[] GetItemIDsByInventoryTab(IEnumerable> items) { Span ids = stackalloc ushort[20]; ids.Clear(); foreach (var item in items) { if (item.Item2 > 20) { continue; } ids[item.Item2] = item.Item1; } return ids.ToArray(); } }