using Yuanshen.Common.Data.Excel; using Yuanshen.Gameserver.Database; namespace Yuanshen.Gameserver.Game; internal sealed class Player { public uint Uid { get; private set; } = 1; public const uint PeerId = 1; public const uint TeamEntityId = 0x09000001; private const uint StarterReliquaryRank = 5; public const uint MpLevelEntityId = 0x0B000001; public static readonly uint[] Flycloaks = { 140001, 140002, 140003, 140004, 140005 }; public string Nickname { get; } = "Traveler"; public List Avatars { get; } = new(); public List Weapons { get; } = new(); public List Reliquaries { get; } = new(); public List Monsters { get; } = new(); public List Gadgets { get; } = new(); public HashSet KilledNodeIds { get; } = new(); public HashSet OpenedGadgetIds { get; } = new(); public Dictionary Materials { get; } = new(); public bool IsInitialized { get; private set; } public uint CurrentTeamId { get; set; } = 1; public List TeamAvatarGuids { get; } = new(); public ulong CurrentAvatarGuid { get; set; } private readonly ExcelTableCollection _excel; private uint _guidSeq; private uint _defaultGuidCeiling; private uint _avatarEntitySeq; private uint _weaponEntitySeq; private uint _monsterEntitySeq; private uint _gadgetEntitySeq; public Player(ExcelTableCollection excel) => _excel = excel; public void AssignUid(uint uid) => Uid = uid; public const uint StartingHcoin = 1_600_000; public const uint StartingScoin = 9_999_999; public const uint CurrentSpawnDataVersion = 2; public ulong NextGuid() => ((ulong)Uid << 32) | ++_guidSeq; public GameAvatar? ActiveAvatar => FindAvatar(CurrentAvatarGuid); public GameAvatar? FindAvatar(ulong guid) => Avatars.FirstOrDefault(avatar => avatar.Guid == guid); public GameWeapon? FindWeapon(ulong guid) => Weapons.FirstOrDefault(weapon => weapon.Guid == guid); public GameReliquary? FindReliquary(ulong guid) => Reliquaries.FirstOrDefault(item => item.Guid == guid); public GameReliquary? FindReliquaryByItemId(uint itemId) => Reliquaries.FirstOrDefault(item => item.ItemId == itemId); public IEnumerable TeamAvatars => TeamAvatarGuids.Select(FindAvatar).OfType(); public void InitDefault() { if (IsInitialized) return; List depots = _excel.GetTable(ExcelType.AvatarSkillDepot) .Items.OfType().ToList(); List abilities = _excel.GetTable(ExcelType.AvatarAbilities) .Items.OfType().ToList(); foreach (AvatarExcel excel in _excel.GetTable(ExcelType.Avatar).Items.OfType()) { AvatarAbilitiesExcel? avatarAbilities = abilities.FirstOrDefault(a => a.Id == excel.Id); if (avatarAbilities is null) continue; AvatarSkillDepotExcel? depot = depots.FirstOrDefault(d => d.Id == excel.SkillDepotId); GameAvatar avatar = new(excel, NextGuid(), 0x01000000 + ++_avatarEntitySeq, 0x06000000 + ++_weaponEntitySeq, depot, avatarAbilities); GameWeapon weapon = CreateWeapon(excel.InitialWeapon); avatar.WeaponGuid = weapon.Guid; avatar.OwnerUid = Uid; Avatars.Add(avatar); } foreach (WeaponExcel excel in _excel.GetTable(ExcelType.Weapon).Items.OfType()) CreateWeapon(excel.Id); CreateReliquaries(); CurrentAvatarGuid = Avatars[0].Guid; TeamAvatarGuids.Add(Avatars[0].Guid); _defaultGuidCeiling = _guidSeq; IsInitialized = true; } public GameAvatar? GrantAvatar(uint avatarId) { if (Avatars.Any(existing => existing.AvatarId == avatarId)) return null; AvatarExcel? excel = _excel.GetTable(ExcelType.Avatar).Items.OfType() .FirstOrDefault(candidate => candidate.Id == avatarId); AvatarAbilitiesExcel? abilities = _excel.GetTable(ExcelType.AvatarAbilities).Items.OfType() .FirstOrDefault(candidate => candidate.Id == avatarId); if (excel is null) return null; AvatarSkillDepotExcel? depot = _excel.GetTable(ExcelType.AvatarSkillDepot).Items.OfType() .FirstOrDefault(candidate => candidate.Id == excel.SkillDepotId); GameAvatar avatar = new(excel, NextGuid(), 0x01000000 + ++_avatarEntitySeq, 0x06000000 + ++_weaponEntitySeq, depot, abilities); GameWeapon weapon = CreateWeapon(excel.InitialWeapon); avatar.WeaponGuid = weapon.Guid; avatar.OwnerUid = Uid; Avatars.Add(avatar); return avatar; } public bool HasAvatarData(uint avatarId) => _excel.GetTable(ExcelType.Avatar).Items.OfType().Any(candidate => candidate.Id == avatarId); public bool HasAbilities(uint avatarId) => _excel.GetTable(ExcelType.AvatarAbilities).Items.OfType().Any(candidate => candidate.Id == avatarId); public void ResetTeam() { TeamAvatarGuids.Clear(); TeamAvatarGuids.Add(Avatars[0].Guid); CurrentAvatarGuid = Avatars[0].Guid; } public ulong MaterialGuid(uint itemId) => ((ulong)Uid << 32) | (0x80000000u | itemId); public void MarkNodeKilled(uint nodeId) => KilledNodeIds.Add(nodeId); public Dictionary GachaPity { get; } = new(); public Dictionary GachaGuarantee { get; } = new(); public List GachaLog { get; } = new(); public Dictionary BoughtGoods { get; } = new(); public Dictionary ClientGadgets { get; } = new(); public uint Hcoin { get; set; } = StartingHcoin; public uint Scoin { get; set; } = StartingScoin; public Yuanshen.Protocol.PlayerPropNotify BuildCurrencyProps() { Yuanshen.Protocol.PlayerPropNotify notify = new(); notify.PropMap.Add(10015u, new Yuanshen.Protocol.PropValue { Type = 10015, Ival = Hcoin, Val = Hcoin }); notify.PropMap.Add(10016u, new Yuanshen.Protocol.PropValue { Type = 10016, Ival = Scoin, Val = Scoin }); return notify; } public void TakeMaterial(uint itemId, uint count) { uint left = Materials.GetValueOrDefault(itemId); Materials[itemId] = left > count ? left - count : 0; } public void GiveMaterial(uint itemId, uint count) => Materials[itemId] = Materials.GetValueOrDefault(itemId) + count; public GameMonster SpawnMonster(uint monsterId, Yuanshen.Protocol.Vector pos, uint level, float maxHp, float attack, float defense, uint nodeId = 0, uint configId = 0, uint groupId = 0) { GameMonster monster = new(monsterId, 0x02000000 + ++_monsterEntitySeq, pos, level, maxHp, attack, defense) { NodeId = nodeId, ConfigId = configId, GroupId = groupId }; Monsters.Add(monster); return monster; } public GameGadget SpawnGadget(GadgetNode node) { GameGadget gadget = new(node, 0x04000000 + ++_gadgetEntitySeq); Gadgets.Add(gadget); return gadget; } public PlayerRecord ToRecord(Yuanshen.Protocol.Vector pos) => new() { Uid = Uid, Nickname = Nickname, CurrentTeamId = CurrentTeamId, CurrentAvatarGuid = CurrentAvatarGuid, TeamAvatarGuids = TeamAvatarGuids.ToList(), TeamAvatarIds = TeamAvatars.Select(avatar => avatar.AvatarId).ToList(), CurrentAvatarId = ActiveAvatar?.AvatarId ?? 0, EquippedWeapons = Avatars .GroupBy(a => a.AvatarId) .ToDictionary(group => group.Key, group => FindWeapon(group.First().WeaponGuid)?.ItemId ?? 0UL), EquippedReliquaries = Avatars .Where(a => a.ReliquaryGuids.Count != 0) .GroupBy(a => a.AvatarId) .ToDictionary(group => group.Key, group => group.First().ReliquaryGuids .ToDictionary(slot => slot.Key, slot => (ulong)(FindReliquary(slot.Value)?.ItemId ?? 0))), AvatarProgress = Avatars .GroupBy(a => a.AvatarId) .ToDictionary(group => group.Key, group => BuildProgress(group.First())), Materials = new Dictionary(Materials), KilledNodes = KilledNodeIds.ToList(), OpenedGadgets = OpenedGadgetIds.ToList(), DroppedWeapons = Weapons.Where(IsDropped).Select(weapon => new DroppedWeapon { ItemId = weapon.ItemId, Level = weapon.Level, PromoteLevel = weapon.PromoteLevel, Refinement = weapon.Refinement }).ToList(), DroppedReliquaries = Reliquaries.Where(IsDropped).Select(reliquary => new DroppedReliquary { ItemId = reliquary.ItemId, EquipType = reliquary.EquipType, MainPropId = reliquary.MainPropId, Level = reliquary.Level, PromoteLevel = reliquary.PromoteLevel, AppendPropIds = reliquary.AppendPropIds.ToList() }).ToList(), SpawnDataVersion = CurrentSpawnDataVersion, GachaPity = GachaPity.ToDictionary(pair => pair.Key, pair => pair.Value), GachaGuarantee = GachaGuarantee.ToDictionary(pair => pair.Key, pair => pair.Value), GachaLog = GachaLog.ToList(), BoughtGoods = BoughtGoods.ToDictionary(pair => pair.Key, pair => pair.Value), Hcoin = Hcoin, Scoin = Scoin, PosX = pos.X, PosY = pos.Y, PosZ = pos.Z }; public void ApplyRecord(PlayerRecord record) { CurrentTeamId = record.CurrentTeamId; RestoreDropped(record); Hcoin = record.Hcoin != 0 || record.Scoin != 0 ? record.Hcoin : StartingHcoin; Scoin = record.Hcoin != 0 || record.Scoin != 0 ? record.Scoin : StartingScoin; BoughtGoods.Clear(); foreach ((uint goodsId, uint bought) in record.BoughtGoods) BoughtGoods[goodsId] = bought; GachaPity.Clear(); foreach ((uint gachaType, uint counter) in record.GachaPity) GachaPity[gachaType] = counter; GachaGuarantee.Clear(); foreach ((uint gachaType, uint flag) in record.GachaGuarantee) GachaGuarantee[gachaType] = flag; GachaLog.Clear(); GachaLog.AddRange(record.GachaLog); KilledNodeIds.Clear(); foreach (uint nodeId in record.SpawnDataVersion == CurrentSpawnDataVersion ? record.KilledNodes : new List()) KilledNodeIds.Add(nodeId); OpenedGadgetIds.Clear(); foreach (uint gadgetNodeId in record.OpenedGadgets) OpenedGadgetIds.Add(gadgetNodeId); HashSet takenWeapons = new(); foreach ((uint avatarId, ulong itemId) in record.EquippedWeapons) { GameAvatar? avatar = Avatars.FirstOrDefault(a => a.AvatarId == avatarId); GameWeapon? weapon = Weapons.FirstOrDefault(w => w.ItemId == itemId && !takenWeapons.Contains(w.Guid)); if (avatar is null || weapon is null) continue; avatar.WeaponGuid = weapon.Guid; takenWeapons.Add(weapon.Guid); } HashSet takenReliquaries = new(); foreach ((uint avatarId, Dictionary slots) in record.EquippedReliquaries) { GameAvatar? avatar = Avatars.FirstOrDefault(a => a.AvatarId == avatarId); if (avatar is null) continue; foreach ((uint slot, ulong itemId) in slots) { GameReliquary? reliquary = Reliquaries .FirstOrDefault(r => r.ItemId == itemId && !takenReliquaries.Contains(r.Guid)); if (reliquary is null) continue; avatar.ReliquaryGuids[slot] = reliquary.Guid; takenReliquaries.Add(reliquary.Guid); } } foreach ((uint avatarId, AvatarProgress progress) in record.AvatarProgress) { GameAvatar? avatar = Avatars.FirstOrDefault(a => a.AvatarId == avatarId); if (avatar is null) continue; avatar.Level = progress.Level; avatar.PromoteLevel = progress.PromoteLevel; avatar.TalentLevel = progress.TalentLevel; avatar.Constellation = progress.Constellation; GameWeapon? weapon = FindWeapon(avatar.WeaponGuid); if (weapon is not null) { weapon.Level = progress.WeaponLevel; weapon.PromoteLevel = progress.WeaponPromote; weapon.Refinement = progress.WeaponRefinement; } } Materials.Clear(); foreach ((uint itemId, uint count) in record.Materials) Materials[itemId] = count; List team = record.TeamAvatarIds .Select(avatarId => Avatars.FirstOrDefault(a => a.AvatarId == avatarId)?.Guid ?? 0) .Where(guid => guid != 0) .ToList(); if (team.Count != 0) { TeamAvatarGuids.Clear(); TeamAvatarGuids.AddRange(team); ulong active = Avatars.FirstOrDefault(a => a.AvatarId == record.CurrentAvatarId)?.Guid ?? 0; CurrentAvatarGuid = team.Contains(active) ? active : team[0]; } } private AvatarProgress BuildProgress(GameAvatar avatar) { GameWeapon? weapon = FindWeapon(avatar.WeaponGuid); return new AvatarProgress { Level = avatar.Level, PromoteLevel = avatar.PromoteLevel, TalentLevel = avatar.TalentLevel, Constellation = avatar.Constellation, WeaponLevel = weapon?.Level ?? 90, WeaponPromote = weapon?.PromoteLevel ?? 6, WeaponRefinement = weapon?.Refinement ?? 0 }; } private bool IsDropped(GameWeapon weapon) => (uint)(weapon.Guid & 0xFFFFFFFF) > _defaultGuidCeiling; private bool IsDropped(GameReliquary reliquary) => (uint)(reliquary.Guid & 0xFFFFFFFF) > _defaultGuidCeiling; private void RestoreDropped(PlayerRecord record) { foreach (DroppedWeapon saved in record.DroppedWeapons) { GameWeapon weapon = CreateWeapon(saved.ItemId); weapon.Level = saved.Level; weapon.PromoteLevel = saved.PromoteLevel; weapon.Refinement = saved.Refinement; } foreach (DroppedReliquary saved in record.DroppedReliquaries) { GameReliquary reliquary = new(saved.ItemId, NextGuid(), saved.EquipType, saved.MainPropId) { Level = saved.Level, PromoteLevel = saved.PromoteLevel }; foreach (uint appendPropId in saved.AppendPropIds) reliquary.AppendPropIds.Add(appendPropId); Reliquaries.Add(reliquary); } } public GameWeapon CreateWeapon(uint itemId) { GameWeapon weapon = new(itemId, NextGuid()); Weapons.Add(weapon); return weapon; } public GameReliquary? CreateReliquary(uint itemId, Random random) { ReliquaryExcel? excel = _excel.GetTable(ExcelType.Reliquary).Items.OfType() .FirstOrDefault(candidate => candidate.Id == itemId); if (excel is null) return null; List pool = _excel.GetTable(ExcelType.ReliquaryMainProp) .Items.OfType() .Where(candidate => candidate.PropDepotId == excel.MainPropDepotId) .ToList(); if (pool.Count == 0) return null; ReliquaryMainPropExcel main = pool[random.Next(pool.Count)]; GameReliquary reliquary = new(excel.Id, NextGuid(), EquipTypeId(excel.EquipType), main.Id) { Level = 1, PromoteLevel = 0 }; List> groups = _excel.GetTable(ExcelType.ReliquaryAffix) .Items.OfType() .Where(candidate => candidate.DepotId == excel.AppendPropDepotId && candidate.PropType != main.PropType) .GroupBy(candidate => candidate.GroupId) .ToList(); int wanted = (int)Math.Clamp(excel.AppendPropNum, 1u, (uint)groups.Count); for (int taken = 0; taken < wanted && groups.Count != 0; taken++) { int index = random.Next(groups.Count); List tiers = groups[index].ToList(); groups.RemoveAt(index); reliquary.AppendPropIds.Add(tiers[random.Next(tiers.Count)].Id); } Reliquaries.Add(reliquary); return reliquary; } private void CreateReliquaries() { List mainProps = _excel.GetTable(ExcelType.ReliquaryMainProp) .Items.OfType().ToList(); List affixes = _excel.GetTable(ExcelType.ReliquaryAffix) .Items.OfType().ToList(); foreach (ReliquaryExcel excel in _excel.GetTable(ExcelType.Reliquary).Items.OfType() .Where(candidate => candidate.RankLevel == StarterReliquaryRank)) { ReliquaryMainPropExcel? main = mainProps.FirstOrDefault(p => p.PropDepotId == excel.MainPropDepotId); if (main is null) continue; GameReliquary reliquary = new(excel.Id, NextGuid(), EquipTypeId(excel.EquipType), main.Id); foreach (ReliquaryAffixExcel affix in affixes.Where(a => a.DepotId == excel.AppendPropDepotId).Take(4)) reliquary.AppendPropIds.Add(affix.Id); Reliquaries.Add(reliquary); } } public static uint EquipTypeId(string equipType) => equipType switch { "EQUIP_BRACER" => 1, "EQUIP_NECKLACE" => 2, "EQUIP_SHOES" => 3, "EQUIP_RING" => 4, "EQUIP_DRESS" => 5, _ => 0 }; }