From f1db5ba79c97cf1b78cda87ca8b7dff210a6eddb Mon Sep 17 00:00:00 2001 From: erwan-joly Date: Tue, 20 Jan 2026 19:10:42 +1300 Subject: [PATCH 1/4] start moving logic out of game objects --- .../ComponentEntities/Entities/Character.cs | 151 +---------------- .../Extensions/CharacterEntityExtension.cs | 155 ++++++++++++++++++ .../Interfaces/ICharacterEntity.cs | 29 +--- .../GroupDisconnectHandler.cs | 7 +- ...ssageChannelCommunicationMessageHandler.cs | 10 +- .../Handlers/SpRechargerHandler.cs | 2 +- .../Handlers/WearHandler.cs | 6 +- .../Handlers/SpChargerHandler.cs | 8 +- .../NRunService/Handlers/TeleporterHandler.cs | 1 + .../TransformationService.cs | 6 +- .../CharacterScreen/SelectPacketHandler.cs | 5 +- .../SetHeroLevelCommandPacketHandler.cs | 9 +- .../SetJobLevelCommandPacketHandler.cs | 9 +- .../Command/SetReputationPacketHandler.cs | 1 + .../Game/GameStartPacketHandler.cs | 2 +- .../Group/PjoinPacketHandler.cs | 3 +- .../Group/PleavePacketHandler.cs | 7 +- test/NosCore.GameObject.Tests/GroupTests.cs | 1 - .../Handlers/StatDataMessageHandlerTests.cs | 11 +- .../Handlers/VehicleHandlerTests.cs | 2 +- .../Handlers/WearHandlerTests.cs | 2 +- .../Handlers/SpChargerHandlerTests.cs | 2 +- .../MapItemGenerationServiceTests.cs | 2 +- .../TransformationServiceTests.cs | 3 +- .../SelectPacketHandlerTests.cs | 4 +- .../SetHeroLevelCommandPacketHandlerTests.cs | 12 +- .../SetJobLevelCommandPacketHandlerTests.cs | 12 +- .../Group/PleavePacketHandlerTests.cs | 2 +- .../SpTransformPacketHandlerTests.cs | 2 +- .../Inventory/WearPacketHandlerTests.cs | 24 +-- test/NosCore.Tests.Shared/TestHelpers.cs | 13 +- 31 files changed, 280 insertions(+), 223 deletions(-) diff --git a/src/NosCore.GameObject/ComponentEntities/Entities/Character.cs b/src/NosCore.GameObject/ComponentEntities/Entities/Character.cs index 5baaf6880..2b46970c1 100644 --- a/src/NosCore.GameObject/ComponentEntities/Entities/Character.cs +++ b/src/NosCore.GameObject/ComponentEntities/Entities/Character.cs @@ -64,7 +64,7 @@ public class Character(IInventoryService inventory, IExchangeService exchangeSer IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService, IReputationService reputationService, IDignityService dignityService, IOptions worldConfiguration, ISpeedCalculationService speedCalculationService, - ISessionGroupFactory sessionGroupFactory, ISessionRegistry sessionRegistry, + ISessionRegistry sessionRegistry, IGameLanguageLocalizer gameLanguageLocalizer) : CharacterDto, ICharacterEntity { @@ -131,17 +131,6 @@ public class Character(IInventoryService inventory, IExchangeService exchangeSer public Group? Group { get; set; } - public void InitializeGroup() - { - if (Group != null) - { - return; - } - - Group = new Group(GroupType.Group, sessionGroupFactory); - Group.JoinGroup(this); - } - public Instant? LastGroupRequest { get; set; } = null; public ReputationType ReputIcon => reputationService.GetLevelFromReputation(Reput); @@ -226,76 +215,6 @@ public Task SendPacketsAsync(IEnumerable packetDefinitions) return sender?.SendPacketsAsync(packetDefinitions) ?? Task.CompletedTask; } - public async Task SetHeroLevelAsync(byte level) - { - HeroLevel = level; - HeroXp = 0; - await GenerateLevelupPacketsAsync(); - await SendPacketAsync(new MsgiPacket - { - Type = MessageType.Default, - Message = Game18NConstString.HeroLevelIncreased - }); - } - - public async Task SetJobLevelAsync(byte jobLevel) - { - JobLevel = (byte)((Class == CharacterClassType.Adventurer) && (jobLevel > 20) ? 20 : jobLevel); - JobLevelXp = 0; - await SendPacketAsync(this.GenerateLev(experienceService, jobExperienceService, heroExperienceService)); - var mapSessions = sessionRegistry.GetCharacters(s => s.MapInstance == MapInstance); - await Task.WhenAll(mapSessions.Select(s => - { - //if (s.VisualId != VisualId) - //{ - // TODO: Generate GIDX - //} - - return s.SendPacketAsync(this.GenerateEff(8)); - })); - await SendPacketAsync(new MsgiPacket - { - Type = MessageType.Default, - Message = Game18NConstString.JobLevelIncreased - }); - } - - public void JoinGroup(Group group) - { - Group = group; - group.JoinGroup(this); - } - - public async Task LeaveGroupAsync() - { - Group!.LeaveGroup(this); - foreach (var member in Group.Keys.Where(s => (s.Item2 != CharacterId) || (s.Item1 != VisualType.Player))) - { - var groupMember = sessionRegistry.GetCharacter(s => - (s.VisualId == member.Item2) && (member.Item1 == VisualType.Player)); - - if (groupMember == null) - { - continue; - } - - if (Group.Count == 1) - { - await groupMember.LeaveGroupAsync(); - await groupMember.SendPacketAsync(Group.GeneratePidx(groupMember)); - await groupMember.SendPacketAsync(new MsgiPacket - { - Type = MessageType.Default, - Message = Game18NConstString.PartyDisbanded - }); - } - - await groupMember.SendPacketAsync(groupMember.Group!.GeneratePinit()); - } - - Group = new Group(GroupType.Group, sessionGroupFactory); - Group.JoinGroup(this); - } // todo move this public async Task ChangeClassAsync(CharacterClassType classType) @@ -351,7 +270,7 @@ await SendPacketsAsync( } await SendPacketAsync(this.GenerateTit()); - await SendPacketAsync(GenerateStat()); + await SendPacketAsync(this.GenerateStat()); await MapInstance.SendPacketAsync(this.GenerateEq()); await MapInstance.SendPacketAsync(this.GenerateEff(8)); //TODO: Faction @@ -384,17 +303,6 @@ await SendPacketAsync(new MsgiPacket await MapInstance.SendPacketAsync(this.GenerateEff(198)); } - public Task AddGoldAsync(long gold) - { - Gold += gold; - return SendPacketAsync(this.GenerateGold()); - } - - public Task RemoveGoldAsync(long gold) - { - Gold -= gold; - return SendPacketAsync(this.GenerateGold()); - } public void AddBankGold(long bankGold) { @@ -406,23 +314,6 @@ public void RemoveBankGold(long bankGold) Account.BankMoney -= bankGold; } - public async Task SetGoldAsync(long gold) - { - Gold = gold; - await SendPacketAsync(this.GenerateGold()); - await SendPacketAsync(this.GenerateSay( - GetMessageFromKey(LanguageKey.UPDATE_GOLD), - SayColorType.Red)); - } - - public async Task SetReputationAsync(long reput) - { - Reput = reput; - await SendPacketAsync(this.GenerateFd()); - await SendPacketAsync(this.GenerateSay( - GetMessageFromKey(LanguageKey.REPUTATION_CHANGED), - SayColorType.Red)); - } //todo move this public async Task GenerateMailAsync(IEnumerable mails) @@ -659,7 +550,7 @@ await SendPacketAsync(new SellListPacket private async Task GenerateLevelupPacketsAsync() { - await SendPacketAsync(GenerateStat()); + await SendPacketAsync(this.GenerateStat()); await SendPacketAsync(this.GenerateStatInfo()); await SendPacketAsync(this.GenerateLev(experienceService, jobExperienceService, heroExperienceService)); var mapSessions = sessionRegistry.GetCharacters(s => s.MapInstance == MapInstance); @@ -700,41 +591,5 @@ await SendPacketAsync(new MsgiPacket } - public SpPacket GenerateSpPoint() - { - return new SpPacket - { - AdditionalPoint = SpAdditionPoint, - MaxAdditionalPoint = worldConfiguration.Value.MaxAdditionalSpPoints, - SpPoint = SpPoint, - MaxSpPoint = worldConfiguration.Value.MaxSpPoints - }; - } - - public StatPacket GenerateStat() - { - return new StatPacket - { - Hp = Hp, - HpMaximum = MaxHp, - Mp = Mp, - MpMaximum = MaxMp, - Unknown = 0, - Option = 0 - }; - } - public Task AddSpPointsAsync(int spPointToAdd) - { - SpPoint = SpPoint + spPointToAdd > worldConfiguration.Value.MaxSpPoints - ? worldConfiguration.Value.MaxSpPoints : SpPoint + spPointToAdd; - return SendPacketAsync(GenerateSpPoint()); - } - - public Task AddAdditionalSpPointsAsync(int spPointToAdd) - { - SpAdditionPoint = SpAdditionPoint + spPointToAdd > worldConfiguration.Value.MaxAdditionalSpPoints - ? worldConfiguration.Value.MaxAdditionalSpPoints : SpAdditionPoint + spPointToAdd; - return SendPacketAsync(GenerateSpPoint()); - } } } diff --git a/src/NosCore.GameObject/ComponentEntities/Extensions/CharacterEntityExtension.cs b/src/NosCore.GameObject/ComponentEntities/Extensions/CharacterEntityExtension.cs index 5824480e4..5d1bdb939 100644 --- a/src/NosCore.GameObject/ComponentEntities/Extensions/CharacterEntityExtension.cs +++ b/src/NosCore.GameObject/ComponentEntities/Extensions/CharacterEntityExtension.cs @@ -12,6 +12,7 @@ using NosCore.Data; using NosCore.Data.Enumerations; using NosCore.Data.Enumerations.Buff; +using NosCore.Data.Enumerations.Group; using NosCore.Data.Enumerations.I18N; using NosCore.Data.Enumerations.Interaction; using NosCore.GameObject.ComponentEntities.Interfaces; @@ -20,7 +21,10 @@ using NosCore.GameObject.InterChannelCommunication.Hubs.FriendHub; using NosCore.GameObject.InterChannelCommunication.Hubs.PubSub; using NosCore.GameObject.Services.BattleService; +using NosCore.GameObject.Services.BroadcastService; +using NosCore.GameObject.Services.GroupService; using NosCore.GameObject.Services.ItemGenerationService.Item; +using NosCore.Networking.SessionGroup; using NosCore.Packets.ClientPackets.Player; using NosCore.Packets.Enumerations; using NosCore.Packets.Interfaces; @@ -31,6 +35,7 @@ using NosCore.Packets.ServerPackets.Miniland; using NosCore.Packets.ServerPackets.MiniMap; using NosCore.Packets.ServerPackets.Player; +using NosCore.Packets.ServerPackets.Specialists; using NosCore.Packets.ServerPackets.Quest; using NosCore.Packets.ServerPackets.Quicklist; using NosCore.Packets.ServerPackets.Relations; @@ -695,5 +700,155 @@ public static CInfoPacket GenerateCInfo(this ICharacterEntity visualEntity) ArenaWinner = false }; } + + public static Task AddGoldAsync(this ICharacterEntity characterEntity) + { + return characterEntity.SendPacketAsync(characterEntity.GenerateGold()); + } + + public static Task AddGoldAsync(this ICharacterEntity characterEntity, long gold) + { + characterEntity.Gold += gold; + return characterEntity.SendPacketAsync(characterEntity.GenerateGold()); + } + + public static Task RemoveGoldAsync(this ICharacterEntity characterEntity, long gold) + { + characterEntity.Gold -= gold; + return characterEntity.SendPacketAsync(characterEntity.GenerateGold()); + } + + public static async Task SetGoldAsync(this ICharacterEntity characterEntity, long gold) + { + characterEntity.Gold = gold; + await characterEntity.SendPacketAsync(characterEntity.GenerateGold()); + await characterEntity.SendPacketAsync(characterEntity.GenerateSay( + characterEntity.GetMessageFromKey(LanguageKey.UPDATE_GOLD), + SayColorType.Red)); + } + + public static async Task SetReputationAsync(this ICharacterEntity characterEntity, long reput) + { + characterEntity.Reput = reput; + await characterEntity.SendPacketAsync(characterEntity.GenerateFd()); + await characterEntity.SendPacketAsync(characterEntity.GenerateSay( + characterEntity.GetMessageFromKey(LanguageKey.REPUTATION_CHANGED), + SayColorType.Red)); + } + + public static SpPacket GenerateSpPoint(this ICharacterEntity characterEntity, IOptions worldConfiguration) + { + return new SpPacket + { + AdditionalPoint = characterEntity.SpAdditionPoint, + MaxAdditionalPoint = worldConfiguration.Value.MaxAdditionalSpPoints, + SpPoint = characterEntity.SpPoint, + MaxSpPoint = worldConfiguration.Value.MaxSpPoints + }; + } + + public static StatPacket GenerateStat(this ICharacterEntity characterEntity) + { + return new StatPacket + { + Hp = characterEntity.Hp, + HpMaximum = characterEntity.MaxHp, + Mp = characterEntity.Mp, + MpMaximum = characterEntity.MaxMp, + Unknown = 0, + Option = 0 + }; + } + + public static Task AddSpPointsAsync(this ICharacterEntity characterEntity, int spPointToAdd, IOptions worldConfiguration) + { + characterEntity.SpPoint = characterEntity.SpPoint + spPointToAdd > worldConfiguration.Value.MaxSpPoints + ? worldConfiguration.Value.MaxSpPoints : characterEntity.SpPoint + spPointToAdd; + return characterEntity.SendPacketAsync(characterEntity.GenerateSpPoint(worldConfiguration)); + } + + public static Task AddAdditionalSpPointsAsync(this ICharacterEntity characterEntity, int spPointToAdd, IOptions worldConfiguration) + { + characterEntity.SpAdditionPoint = characterEntity.SpAdditionPoint + spPointToAdd > worldConfiguration.Value.MaxAdditionalSpPoints + ? worldConfiguration.Value.MaxAdditionalSpPoints : characterEntity.SpAdditionPoint + spPointToAdd; + return characterEntity.SendPacketAsync(characterEntity.GenerateSpPoint(worldConfiguration)); + } + + public static async Task SetJobLevelAsync(this ICharacterEntity characterEntity, byte jobLevel, + IExperienceService experienceService, IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService) + { + characterEntity.JobLevel = (byte)((characterEntity.Class == CharacterClassType.Adventurer) && (jobLevel > 20) ? 20 : jobLevel); + characterEntity.JobLevelXp = 0; + await characterEntity.SendPacketAsync(characterEntity.GenerateLev(experienceService, jobExperienceService, heroExperienceService)); + await characterEntity.SendPacketAsync(new MsgiPacket + { + Type = MessageType.Default, + Message = Game18NConstString.JobLevelIncreased + }); + } + + public static async Task SetHeroLevelAsync(this ICharacterEntity characterEntity, byte level, + IExperienceService experienceService, IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService) + { + characterEntity.HeroLevel = level; + characterEntity.HeroXp = 0; + await characterEntity.SendPacketAsync(characterEntity.GenerateStat()); + await characterEntity.SendPacketAsync(characterEntity.GenerateStatInfo()); + await characterEntity.SendPacketAsync(characterEntity.GenerateLev(experienceService, jobExperienceService, heroExperienceService)); + await characterEntity.SendPacketAsync(new MsgiPacket + { + Type = MessageType.Default, + Message = Game18NConstString.HeroLevelIncreased + }); + } + + public static void InitializeGroup(this ICharacterEntity characterEntity, ISessionGroupFactory sessionGroupFactory) + { + if (characterEntity.Group != null) + { + return; + } + + characterEntity.Group = new Group(GroupType.Group, sessionGroupFactory); + characterEntity.Group.JoinGroup(characterEntity); + } + + public static void JoinGroup(this ICharacterEntity characterEntity, Group group) + { + characterEntity.Group = group; + group.JoinGroup(characterEntity); + } + + public static async Task LeaveGroupAsync(this ICharacterEntity characterEntity, + ISessionGroupFactory sessionGroupFactory, ISessionRegistry sessionRegistry) + { + characterEntity.Group!.LeaveGroup(characterEntity); + foreach (var member in characterEntity.Group.Keys.Where(s => (s.Item2 != characterEntity.VisualId) || (s.Item1 != VisualType.Player))) + { + var groupMember = sessionRegistry.GetCharacter(s => + (s.VisualId == member.Item2) && (member.Item1 == VisualType.Player)); + + if (groupMember == null) + { + continue; + } + + if (characterEntity.Group.Count == 1) + { + await groupMember.LeaveGroupAsync(sessionGroupFactory, sessionRegistry); + await groupMember.SendPacketAsync(characterEntity.Group.GeneratePidx(groupMember)); + await groupMember.SendPacketAsync(new MsgiPacket + { + Type = MessageType.Default, + Message = Game18NConstString.PartyDisbanded + }); + } + + await groupMember.SendPacketAsync(groupMember.Group!.GeneratePinit()); + } + + characterEntity.Group = new Group(GroupType.Group, sessionGroupFactory); + characterEntity.Group.JoinGroup(characterEntity); + } } } diff --git a/src/NosCore.GameObject/ComponentEntities/Interfaces/ICharacterEntity.cs b/src/NosCore.GameObject/ComponentEntities/Interfaces/ICharacterEntity.cs index 4c193ca70..bb469ed85 100644 --- a/src/NosCore.GameObject/ComponentEntities/Interfaces/ICharacterEntity.cs +++ b/src/NosCore.GameObject/ComponentEntities/Interfaces/ICharacterEntity.cs @@ -51,9 +51,12 @@ public interface ICharacterEntity : INamedEntity, IRequestableEntity DignityType DignityIcon { get; } bool Camouflage { get; } - long JobLevelXp { get; } - long HeroXp { get; } - byte JobLevel { get; } + long JobLevelXp { get; set; } + long HeroXp { get; set; } + byte JobLevel { get; set; } + new byte HeroLevel { get; set; } + int SpPoint { get; set; } + int SpAdditionPoint { get; set; } bool Invisible { get; } @@ -67,7 +70,7 @@ public interface ICharacterEntity : INamedEntity, IRequestableEntity ConcurrentDictionary Skills { get; } - long Gold { get; } + long Gold { get; set; } long BankGold { get; } @@ -84,7 +87,7 @@ public interface ICharacterEntity : INamedEntity, IRequestableEntity Guid? CurrentScriptId { get; } ConcurrentDictionary Quests { get; } short Compliment { get; } - long Reput { get; } + long Reput { get; set; } short Dignity { get; } Task GenerateMailAsync(IEnumerable data); @@ -93,22 +96,6 @@ public interface ICharacterEntity : INamedEntity, IRequestableEntity Task SendPacketsAsync(IEnumerable packetDefinitions); - Task LeaveGroupAsync(); - - void JoinGroup(Group group); - - Task SetJobLevelAsync(byte level); - - Task SetHeroLevelAsync(byte level); - - Task SetReputationAsync(long reput); - - Task SetGoldAsync(long gold); - - Task AddGoldAsync(long gold); - - Task RemoveGoldAsync(long gold); - void AddBankGold(long bankGold); void RemoveBankGold(long bankGold); diff --git a/src/NosCore.GameObject/Networking/ClientSession/DisconnectHandlers/GroupDisconnectHandler.cs b/src/NosCore.GameObject/Networking/ClientSession/DisconnectHandlers/GroupDisconnectHandler.cs index 2a8ef854f..621d2c5e7 100644 --- a/src/NosCore.GameObject/Networking/ClientSession/DisconnectHandlers/GroupDisconnectHandler.cs +++ b/src/NosCore.GameObject/Networking/ClientSession/DisconnectHandlers/GroupDisconnectHandler.cs @@ -4,11 +4,14 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // +using NosCore.GameObject.ComponentEntities.Extensions; +using NosCore.GameObject.Services.BroadcastService; +using NosCore.Networking.SessionGroup; using System.Threading.Tasks; namespace NosCore.GameObject.Networking.ClientSession.DisconnectHandlers; -public class GroupDisconnectHandler : ISessionDisconnectHandler +public class GroupDisconnectHandler(ISessionGroupFactory sessionGroupFactory, ISessionRegistry sessionRegistry) : ISessionDisconnectHandler { public async Task HandleDisconnectAsync(ClientSession session) { @@ -17,6 +20,6 @@ public async Task HandleDisconnectAsync(ClientSession session) return; } - await session.Character.LeaveGroupAsync(); + await session.Character.LeaveGroupAsync(sessionGroupFactory, sessionRegistry); } } diff --git a/src/NosCore.GameObject/Services/ChannelCommunicationService/Handlers/StatDataMessageChannelCommunicationMessageHandler.cs b/src/NosCore.GameObject/Services/ChannelCommunicationService/Handlers/StatDataMessageChannelCommunicationMessageHandler.cs index f7f57d812..bc9b8b43e 100644 --- a/src/NosCore.GameObject/Services/ChannelCommunicationService/Handlers/StatDataMessageChannelCommunicationMessageHandler.cs +++ b/src/NosCore.GameObject/Services/ChannelCommunicationService/Handlers/StatDataMessageChannelCommunicationMessageHandler.cs @@ -1,4 +1,7 @@ using Microsoft.Extensions.Options; +using NosCore.Algorithm.ExperienceService; +using NosCore.Algorithm.HeroExperienceService; +using NosCore.Algorithm.JobExperienceService; using NosCore.Core.Configuration; using NosCore.Data.Enumerations; using NosCore.Data.Enumerations.I18N; @@ -13,7 +16,8 @@ namespace NosCore.GameObject.Services.ChannelCommunicationService.Handlers { public class StatDataMessageChannelCommunicationMessageHandler(ILogger logger, - ILogLanguageLocalizer logLanguage, IOptions worldConfiguration, ISessionRegistry sessionRegistry) : ChannelCommunicationMessageHandler + ILogLanguageLocalizer logLanguage, IOptions worldConfiguration, ISessionRegistry sessionRegistry, + IExperienceService experienceService, IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService) : ChannelCommunicationMessageHandler { public override async Task Handle(StatData data) { @@ -30,10 +34,10 @@ public override async Task Handle(StatData data) session.SetLevel((byte)data.Data); break; case UpdateStatActionType.UpdateJobLevel: - await session.SetJobLevelAsync((byte)data.Data); + await session.SetJobLevelAsync((byte)data.Data, experienceService, jobExperienceService, heroExperienceService); break; case UpdateStatActionType.UpdateHeroLevel: - await session.SetHeroLevelAsync((byte)data.Data); + await session.SetHeroLevelAsync((byte)data.Data, experienceService, jobExperienceService, heroExperienceService); break; case UpdateStatActionType.UpdateReputation: await session.SetReputationAsync(data.Data); diff --git a/src/NosCore.GameObject/Services/ItemGenerationService/Handlers/SpRechargerHandler.cs b/src/NosCore.GameObject/Services/ItemGenerationService/Handlers/SpRechargerHandler.cs index 873d939c5..c3a6da14b 100644 --- a/src/NosCore.GameObject/Services/ItemGenerationService/Handlers/SpRechargerHandler.cs +++ b/src/NosCore.GameObject/Services/ItemGenerationService/Handlers/SpRechargerHandler.cs @@ -36,7 +36,7 @@ public async Task ExecuteAsync(RequestData logLanguage) + public class WearEventHandler(ILogger logger, IClock clock, ILogLanguageLocalizer logLanguage, IOptions worldConfiguration) : IUseItemEventHandler { public bool Condition(Item.Item item) @@ -166,7 +168,7 @@ await requestData.ClientSession.Character.MapInstance.SendPacketAsync(requestDat if (itemInstance.ItemInstance.Item.EquipmentSlot == EquipmentType.Sp) { - await requestData.ClientSession.SendPacketAsync(requestData.ClientSession.Character.GenerateSpPoint()); + await requestData.ClientSession.SendPacketAsync(requestData.ClientSession.Character.GenerateSpPoint(worldConfiguration)); } if (itemInstance.ItemInstance.Item.EquipmentSlot == EquipmentType.Fairy) diff --git a/src/NosCore.GameObject/Services/MapItemGenerationService/Handlers/SpChargerHandler.cs b/src/NosCore.GameObject/Services/MapItemGenerationService/Handlers/SpChargerHandler.cs index 610e0f3ea..778a4c27b 100644 --- a/src/NosCore.GameObject/Services/MapItemGenerationService/Handlers/SpChargerHandler.cs +++ b/src/NosCore.GameObject/Services/MapItemGenerationService/Handlers/SpChargerHandler.cs @@ -4,6 +4,8 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // +using Microsoft.Extensions.Options; +using NosCore.Core.Configuration; using NosCore.Data.Enumerations.Items; using NosCore.GameObject.ComponentEntities.Entities; using NosCore.GameObject.ComponentEntities.Extensions; @@ -15,7 +17,7 @@ namespace NosCore.GameObject.Services.MapItemGenerationService.Handlers { - public class SpChargerEventHandler : IGetMapItemEventHandler + public class SpChargerEventHandler(IOptions worldConfiguration) : IGetMapItemEventHandler { public bool Condition(MapItem item) { @@ -25,8 +27,8 @@ public bool Condition(MapItem item) public async Task ExecuteAsync(RequestData> requestData) { - await requestData.ClientSession.Character.AddSpPointsAsync(requestData.Data.Item1.ItemInstance!.Item.EffectValue); - await requestData.ClientSession.SendPacketAsync(requestData.ClientSession.Character.GenerateSpPoint()); + await requestData.ClientSession.Character.AddSpPointsAsync(requestData.Data.Item1.ItemInstance!.Item.EffectValue, worldConfiguration); + await requestData.ClientSession.SendPacketAsync(requestData.ClientSession.Character.GenerateSpPoint(worldConfiguration)); requestData.ClientSession.Character.MapInstance.MapItems.TryRemove(requestData.Data.Item1.VisualId, out _); await requestData.ClientSession.Character.MapInstance.SendPacketAsync( requestData.ClientSession.Character.GenerateGet(requestData.Data.Item1.VisualId)); diff --git a/src/NosCore.GameObject/Services/NRunService/Handlers/TeleporterHandler.cs b/src/NosCore.GameObject/Services/NRunService/Handlers/TeleporterHandler.cs index ad6b22162..abf59915e 100644 --- a/src/NosCore.GameObject/Services/NRunService/Handlers/TeleporterHandler.cs +++ b/src/NosCore.GameObject/Services/NRunService/Handlers/TeleporterHandler.cs @@ -5,6 +5,7 @@ // using NosCore.GameObject.ComponentEntities.Entities; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.ComponentEntities.Interfaces; using NosCore.GameObject.Networking.ClientSession; using NosCore.GameObject.Services.MapChangeService; diff --git a/src/NosCore.GameObject/Services/TransformationService/TransformationService.cs b/src/NosCore.GameObject/Services/TransformationService/TransformationService.cs index 6d7df9ecc..199062dc8 100644 --- a/src/NosCore.GameObject/Services/TransformationService/TransformationService.cs +++ b/src/NosCore.GameObject/Services/TransformationService/TransformationService.cs @@ -4,10 +4,12 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // +using Microsoft.Extensions.Options; using NodaTime; using NosCore.Algorithm.ExperienceService; using NosCore.Algorithm.HeroExperienceService; using NosCore.Algorithm.JobExperienceService; +using NosCore.Core.Configuration; using NosCore.Data.Enumerations; using NosCore.Data.Enumerations.I18N; using NosCore.GameObject.ComponentEntities.Entities; @@ -29,7 +31,7 @@ namespace NosCore.GameObject.Services.TransformationService { public class TransformationService(IClock clock, IExperienceService experienceService, IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService, ILogger logger, - ILogLanguageLocalizer logLanguage) + ILogLanguageLocalizer logLanguage, IOptions worldConfiguration) : ITransformationService { public async Task RemoveSpAsync(Character character) @@ -123,7 +125,7 @@ await character.MapInstance.SendPacketAsync(new GuriPacket Value = 1, EntityId = character.CharacterId }); - await character.SendPacketAsync(character.GenerateSpPoint()); + await character.SendPacketAsync(character.GenerateSpPoint(worldConfiguration)); await character.SendPacketAsync(character.GenerateCond()); await character.SendPacketAsync(character.GenerateStat()); } diff --git a/src/NosCore.PacketHandlers/CharacterScreen/SelectPacketHandler.cs b/src/NosCore.PacketHandlers/CharacterScreen/SelectPacketHandler.cs index 5bf9eb2fa..96b22dadd 100644 --- a/src/NosCore.PacketHandlers/CharacterScreen/SelectPacketHandler.cs +++ b/src/NosCore.PacketHandlers/CharacterScreen/SelectPacketHandler.cs @@ -23,6 +23,7 @@ using NosCore.GameObject.Services.QuestService; using NosCore.Packets.ClientPackets.CharacterSelectionScreen; using NosCore.Packets.ServerPackets.CharacterSelectionScreen; +using NosCore.Networking.SessionGroup; using NosCore.Shared.I18N; using Serilog; using System; @@ -40,7 +41,7 @@ public class SelectPacketHandler(IDao characterDao, ILogger IDao quickListEntriesDao, IDao titleDao, IDao characterQuestDao, IDao scriptDao, List quests, List questObjectives, - IOptions configuration, ILogLanguageLocalizer logLanguage, IPubSubHub pubSubHub) + IOptions configuration, ILogLanguageLocalizer logLanguage, IPubSubHub pubSubHub, ISessionGroupFactory sessionGroupFactory) : PacketHandler, IWorldPacketHandler { public override async Task ExecuteAsync(SelectPacket packet, ClientSession clientSession) @@ -62,7 +63,7 @@ public override async Task ExecuteAsync(SelectPacket packet, ClientSession clien } var character = characterDto.Adapt(); - character.InitializeGroup(); + character.InitializeGroup(sessionGroupFactory); await pubSubHub.SubscribeAsync(new Subscriber { Id = clientSession.SessionId, diff --git a/src/NosCore.PacketHandlers/Command/SetHeroLevelCommandPacketHandler.cs b/src/NosCore.PacketHandlers/Command/SetHeroLevelCommandPacketHandler.cs index fe38599c9..f881a068b 100644 --- a/src/NosCore.PacketHandlers/Command/SetHeroLevelCommandPacketHandler.cs +++ b/src/NosCore.PacketHandlers/Command/SetHeroLevelCommandPacketHandler.cs @@ -4,8 +4,12 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // +using NosCore.Algorithm.ExperienceService; +using NosCore.Algorithm.HeroExperienceService; +using NosCore.Algorithm.JobExperienceService; using NosCore.Data.CommandPackets; using NosCore.Data.Enumerations; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.Infastructure; using NosCore.GameObject.InterChannelCommunication.Hubs.ChannelHub; using NosCore.GameObject.InterChannelCommunication.Hubs.PubSub; @@ -19,14 +23,15 @@ namespace NosCore.PacketHandlers.Command { - public class SetHeroLevelCommandPacketHandler(IPubSubHub pubSubHub, IChannelHub channelHub) + public class SetHeroLevelCommandPacketHandler(IPubSubHub pubSubHub, IChannelHub channelHub, + IExperienceService experienceService, IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService) : PacketHandler, IWorldPacketHandler { public override async Task ExecuteAsync(SetHeroLevelCommandPacket levelPacket, ClientSession session) { if (string.IsNullOrEmpty(levelPacket.Name) || (levelPacket.Name == session.Character.Name)) { - await session.Character.SetHeroLevelAsync(levelPacket.Level); + await session.Character.SetHeroLevelAsync(levelPacket.Level, experienceService, jobExperienceService, heroExperienceService); return; } diff --git a/src/NosCore.PacketHandlers/Command/SetJobLevelCommandPacketHandler.cs b/src/NosCore.PacketHandlers/Command/SetJobLevelCommandPacketHandler.cs index 6393911b4..aebb1ecd7 100644 --- a/src/NosCore.PacketHandlers/Command/SetJobLevelCommandPacketHandler.cs +++ b/src/NosCore.PacketHandlers/Command/SetJobLevelCommandPacketHandler.cs @@ -4,8 +4,12 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // +using NosCore.Algorithm.ExperienceService; +using NosCore.Algorithm.HeroExperienceService; +using NosCore.Algorithm.JobExperienceService; using NosCore.Data.CommandPackets; using NosCore.Data.Enumerations; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.Infastructure; using NosCore.GameObject.InterChannelCommunication.Hubs.PubSub; using NosCore.GameObject.InterChannelCommunication.Messages; @@ -18,14 +22,15 @@ namespace NosCore.PacketHandlers.Command { - public class SetJobLevelCommandPacketHandler(IPubSubHub pubSubHub) + public class SetJobLevelCommandPacketHandler(IPubSubHub pubSubHub, + IExperienceService experienceService, IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService) : PacketHandler, IWorldPacketHandler { public override async Task ExecuteAsync(SetJobLevelCommandPacket levelPacket, ClientSession session) { if (string.IsNullOrEmpty(levelPacket.Name) || (levelPacket.Name == session.Character.Name)) { - await session.Character.SetJobLevelAsync(levelPacket.Level); + await session.Character.SetJobLevelAsync(levelPacket.Level, experienceService, jobExperienceService, heroExperienceService); return; } diff --git a/src/NosCore.PacketHandlers/Command/SetReputationPacketHandler.cs b/src/NosCore.PacketHandlers/Command/SetReputationPacketHandler.cs index 21ffad9a9..ebd89ccdd 100644 --- a/src/NosCore.PacketHandlers/Command/SetReputationPacketHandler.cs +++ b/src/NosCore.PacketHandlers/Command/SetReputationPacketHandler.cs @@ -6,6 +6,7 @@ using NosCore.Data.CommandPackets; using NosCore.Data.Enumerations; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.Infastructure; using NosCore.GameObject.InterChannelCommunication.Hubs.PubSub; using NosCore.GameObject.InterChannelCommunication.Messages; diff --git a/src/NosCore.PacketHandlers/Game/GameStartPacketHandler.cs b/src/NosCore.PacketHandlers/Game/GameStartPacketHandler.cs index 35990c470..1d4554dfb 100644 --- a/src/NosCore.PacketHandlers/Game/GameStartPacketHandler.cs +++ b/src/NosCore.PacketHandlers/Game/GameStartPacketHandler.cs @@ -65,7 +65,7 @@ await session.SendPacketAsync(session.Character.GenerateSay("------------------- await skillService.LoadSkill(session.Character); await session.SendPacketAsync(session.Character.GenerateTit()); - await session.SendPacketAsync(session.Character.GenerateSpPoint()); + await session.SendPacketAsync(session.Character.GenerateSpPoint(worldConfiguration)); await session.SendPacketAsync(session.Character.GenerateRsfi()); await session.SendPacketAsync(session.Character.GenerateQuestPacket()); diff --git a/src/NosCore.PacketHandlers/Group/PjoinPacketHandler.cs b/src/NosCore.PacketHandlers/Group/PjoinPacketHandler.cs index e9d52fd8c..ada60a842 100644 --- a/src/NosCore.PacketHandlers/Group/PjoinPacketHandler.cs +++ b/src/NosCore.PacketHandlers/Group/PjoinPacketHandler.cs @@ -16,6 +16,7 @@ using NosCore.GameObject.Networking.ClientSession; using NosCore.GameObject.Services.BroadcastService; using NosCore.Networking; +using NosCore.Networking.SessionGroup; using NosCore.Packets.Enumerations; using NosCore.Packets.ServerPackets.Chats; using NosCore.Packets.ServerPackets.Groups; @@ -221,7 +222,7 @@ await targetSession.SendPacketAsync(new InfoiPacket { if (targetSession.Group.Type == GroupType.Group) { - clientSession.Character.JoinGroup(targetSession.Group); + clientSession.Character.JoinGroup(targetSession.Group!); } } else diff --git a/src/NosCore.PacketHandlers/Group/PleavePacketHandler.cs b/src/NosCore.PacketHandlers/Group/PleavePacketHandler.cs index 6b791757d..13621f99d 100644 --- a/src/NosCore.PacketHandlers/Group/PleavePacketHandler.cs +++ b/src/NosCore.PacketHandlers/Group/PleavePacketHandler.cs @@ -12,6 +12,7 @@ using NosCore.GameObject.Networking.ClientSession; using NosCore.GameObject.Services.BroadcastService; using NosCore.Networking; +using NosCore.Networking.SessionGroup; using NosCore.Packets.ClientPackets.Groups; using NosCore.Packets.Enumerations; using NosCore.Packets.ServerPackets.UI; @@ -21,7 +22,7 @@ namespace NosCore.PacketHandlers.Group { - public class PleavePacketHandler(IIdService groupIdService, ISessionRegistry sessionRegistry) : PacketHandler, + public class PleavePacketHandler(IIdService groupIdService, ISessionRegistry sessionRegistry, ISessionGroupFactory sessionGroupFactory) : PacketHandler, IWorldPacketHandler { public override async Task ExecuteAsync(PleavePacket bIPacket, ClientSession clientSession) @@ -36,7 +37,7 @@ public override async Task ExecuteAsync(PleavePacket bIPacket, ClientSession cli if (group.Count > 2) { var isLeader = group.IsGroupLeader(clientSession.Character.CharacterId); - await clientSession.Character.LeaveGroupAsync(); + await clientSession.Character.LeaveGroupAsync(sessionGroupFactory, sessionRegistry); if (isLeader) { @@ -91,7 +92,7 @@ await targetsession.SendPacketAsync(new MsgiPacket Message = Game18NConstString.PartyDisbanded }); - await targetsession.LeaveGroupAsync(); + await targetsession.LeaveGroupAsync(sessionGroupFactory, sessionRegistry); await targetsession.SendPacketAsync(targetsession.Group!.GeneratePinit()); await targetsession.MapInstance.SendPacketAsync(targetsession.Group.GeneratePidx(targetsession)); } diff --git a/test/NosCore.GameObject.Tests/GroupTests.cs b/test/NosCore.GameObject.Tests/GroupTests.cs index 4a69222d7..543cfc8f7 100644 --- a/test/NosCore.GameObject.Tests/GroupTests.cs +++ b/test/NosCore.GameObject.Tests/GroupTests.cs @@ -61,7 +61,6 @@ private Character CreateCharacter(long id = 1, string name = "TestCharacter") new DignityService(), TestHelpers.Instance.WorldConfiguration, new Mock().Object, - new Mock().Object, TestHelpers.Instance.SessionRegistry, TestHelpers.Instance.GameLanguageLocalizer) { diff --git a/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/StatDataMessageHandlerTests.cs b/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/StatDataMessageHandlerTests.cs index 962604a33..0ca85bcc2 100644 --- a/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/StatDataMessageHandlerTests.cs +++ b/test/NosCore.GameObject.Tests/Services/ChannelCommunicationService/Handlers/StatDataMessageHandlerTests.cs @@ -7,6 +7,9 @@ using Microsoft.Extensions.Options; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using NosCore.Algorithm.ExperienceService; +using NosCore.Algorithm.HeroExperienceService; +using NosCore.Algorithm.JobExperienceService; using NosCore.Core.Configuration; using NosCore.Data.Enumerations; using NosCore.Data.Enumerations.I18N; @@ -33,6 +36,9 @@ public class StatDataMessageHandlerTests private Mock Logger = null!; private Mock> LogLanguage = null!; private IOptions WorldConfig = null!; + private Mock ExperienceService = null!; + private Mock JobExperienceService = null!; + private Mock HeroExperienceService = null!; [TestInitialize] public async Task SetupAsync() @@ -44,7 +50,10 @@ public async Task SetupAsync() Logger = new Mock(); LogLanguage = new Mock>(); WorldConfig = Options.Create(new WorldConfiguration { MaxGoldAmount = 999999999 }); - Handler = new StatDataMessageChannelCommunicationMessageHandler(Logger.Object, LogLanguage.Object, WorldConfig, SessionRegistry.Object); + ExperienceService = new Mock(); + JobExperienceService = new Mock(); + HeroExperienceService = new Mock(); + Handler = new StatDataMessageChannelCommunicationMessageHandler(Logger.Object, LogLanguage.Object, WorldConfig, SessionRegistry.Object, ExperienceService.Object, JobExperienceService.Object, HeroExperienceService.Object); } [TestMethod] diff --git a/test/NosCore.GameObject.Tests/Services/ItemGenerationService/Handlers/VehicleHandlerTests.cs b/test/NosCore.GameObject.Tests/Services/ItemGenerationService/Handlers/VehicleHandlerTests.cs index e5addadfd..2eb1718c6 100644 --- a/test/NosCore.GameObject.Tests/Services/ItemGenerationService/Handlers/VehicleHandlerTests.cs +++ b/test/NosCore.GameObject.Tests/Services/ItemGenerationService/Handlers/VehicleHandlerTests.cs @@ -42,7 +42,7 @@ public async Task SetupAsync() await TestHelpers.ResetAsync(); Session = await TestHelpers.Instance.GenerateSessionAsync(); Logger = new Mock(); - Handler = new VehicleEventHandler(Logger.Object, TestHelpers.Instance.LogLanguageLocalizer, new GameObject.Services.TransformationService.TransformationService(TestHelpers.Instance.Clock, new Mock().Object, new Mock().Object, new Mock().Object, Logger.Object, TestHelpers.Instance.LogLanguageLocalizer)); + Handler = new VehicleEventHandler(Logger.Object, TestHelpers.Instance.LogLanguageLocalizer, new GameObject.Services.TransformationService.TransformationService(TestHelpers.Instance.Clock, new Mock().Object, new Mock().Object, new Mock().Object, Logger.Object, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)); var items = new List { new Item {Type = NoscorePocketType.Equipment, VNum = 1, ItemType = ItemType.Weapon} diff --git a/test/NosCore.GameObject.Tests/Services/ItemGenerationService/Handlers/WearHandlerTests.cs b/test/NosCore.GameObject.Tests/Services/ItemGenerationService/Handlers/WearHandlerTests.cs index c5831b6f2..f8530c250 100644 --- a/test/NosCore.GameObject.Tests/Services/ItemGenerationService/Handlers/WearHandlerTests.cs +++ b/test/NosCore.GameObject.Tests/Services/ItemGenerationService/Handlers/WearHandlerTests.cs @@ -47,7 +47,7 @@ public async Task SetupAsync() Logger = new Mock(); TestHelpers.Instance.WorldConfiguration.Value.BackpackSize = 40; Session = await TestHelpers.Instance.GenerateSessionAsync(); - Handler = new WearEventHandler(Logger.Object, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer); + Handler = new WearEventHandler(Logger.Object, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration); var items = new List { new Item diff --git a/test/NosCore.GameObject.Tests/Services/MapItemGenerationService/Handlers/SpChargerHandlerTests.cs b/test/NosCore.GameObject.Tests/Services/MapItemGenerationService/Handlers/SpChargerHandlerTests.cs index c8ce18b4e..c6898ebb0 100644 --- a/test/NosCore.GameObject.Tests/Services/MapItemGenerationService/Handlers/SpChargerHandlerTests.cs +++ b/test/NosCore.GameObject.Tests/Services/MapItemGenerationService/Handlers/SpChargerHandlerTests.cs @@ -32,7 +32,7 @@ public async Task SetupAsync() await TestHelpers.ResetAsync(); Broadcaster.Reset(); Session = await TestHelpers.Instance.GenerateSessionAsync(); - Handler = new SpChargerEventHandler(); + Handler = new SpChargerEventHandler(TestHelpers.Instance.WorldConfiguration); ItemProvider = TestHelpers.Instance.GenerateItemProvider(); IdService = new IdService(1); } diff --git a/test/NosCore.GameObject.Tests/Services/MapItemGenerationService/MapItemGenerationServiceTests.cs b/test/NosCore.GameObject.Tests/Services/MapItemGenerationService/MapItemGenerationServiceTests.cs index 84ec9c6bd..aa2fd5ce0 100644 --- a/test/NosCore.GameObject.Tests/Services/MapItemGenerationService/MapItemGenerationServiceTests.cs +++ b/test/NosCore.GameObject.Tests/Services/MapItemGenerationService/MapItemGenerationServiceTests.cs @@ -45,7 +45,7 @@ public async Task SetupAsync() var handlers = new List>> { new DropEventHandler(), - new SpChargerEventHandler(), + new SpChargerEventHandler(TestHelpers.Instance.WorldConfiguration), new GoldDropEventHandler(TestHelpers.Instance.WorldConfiguration) }; diff --git a/test/NosCore.GameObject.Tests/Services/TransformationService/TransformationServiceTests.cs b/test/NosCore.GameObject.Tests/Services/TransformationService/TransformationServiceTests.cs index b81cb3c1e..2c1580b6c 100644 --- a/test/NosCore.GameObject.Tests/Services/TransformationService/TransformationServiceTests.cs +++ b/test/NosCore.GameObject.Tests/Services/TransformationService/TransformationServiceTests.cs @@ -38,7 +38,8 @@ public async Task SetupAsync() new Mock().Object, new Mock().Object, Logger, - TestHelpers.Instance.LogLanguageLocalizer); + TestHelpers.Instance.LogLanguageLocalizer, + TestHelpers.Instance.WorldConfiguration); } [TestMethod] diff --git a/test/NosCore.PacketHandlers.Tests/CharacterScreen/SelectPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/CharacterScreen/SelectPacketHandlerTests.cs index 6d3c1085b..fe5adac4f 100644 --- a/test/NosCore.PacketHandlers.Tests/CharacterScreen/SelectPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/CharacterScreen/SelectPacketHandlerTests.cs @@ -13,6 +13,7 @@ using NosCore.GameObject.Services.ItemGenerationService; using NosCore.PacketHandlers.CharacterScreen; using NosCore.Packets.ClientPackets.CharacterSelectionScreen; +using NosCore.Networking.SessionGroup; using NosCore.Tests.Shared; using Serilog; using SpecLight; @@ -51,7 +52,8 @@ public async Task SetupAsync() new List(), TestHelpers.Instance.WorldConfiguration, TestHelpers.Instance.LogLanguageLocalizer, - TestHelpers.Instance.PubSubHub.Object); + TestHelpers.Instance.PubSubHub.Object, + new Mock().Object); } [TestMethod] diff --git a/test/NosCore.PacketHandlers.Tests/Command/SetHeroLevelCommandPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Command/SetHeroLevelCommandPacketHandlerTests.cs index 744cd21de..9d47f90b0 100644 --- a/test/NosCore.PacketHandlers.Tests/Command/SetHeroLevelCommandPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Command/SetHeroLevelCommandPacketHandlerTests.cs @@ -6,6 +6,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using NosCore.Algorithm.ExperienceService; +using NosCore.Algorithm.HeroExperienceService; +using NosCore.Algorithm.JobExperienceService; using NosCore.Core; using NosCore.Data.CommandPackets; using NosCore.Data.WebApi; @@ -32,6 +35,9 @@ public class SetHeroLevelCommandPacketHandlerTests private ClientSession Session = null!; private Mock PubSubHub = null!; private Mock ChannelHub = null!; + private Mock ExperienceService = null!; + private Mock JobExperienceService = null!; + private Mock HeroExperienceService = null!; [TestInitialize] public async Task SetupAsync() @@ -41,6 +47,9 @@ public async Task SetupAsync() Session = await TestHelpers.Instance.GenerateSessionAsync(); PubSubHub = new Mock(); ChannelHub = new Mock(); + ExperienceService = new Mock(); + JobExperienceService = new Mock(); + HeroExperienceService = new Mock(); PubSubHub.Setup(x => x.GetSubscribersAsync()) .Returns(Task.FromResult(new List())); @@ -48,7 +57,8 @@ public async Task SetupAsync() ChannelHub.Setup(x => x.GetCommunicationChannels()) .Returns(Task.FromResult(new List())); - Handler = new SetHeroLevelCommandPacketHandler(PubSubHub.Object, ChannelHub.Object); + Handler = new SetHeroLevelCommandPacketHandler(PubSubHub.Object, ChannelHub.Object, + ExperienceService.Object, JobExperienceService.Object, HeroExperienceService.Object); } [TestMethod] diff --git a/test/NosCore.PacketHandlers.Tests/Command/SetJobLevelCommandPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Command/SetJobLevelCommandPacketHandlerTests.cs index 504484981..96b4a6b7f 100644 --- a/test/NosCore.PacketHandlers.Tests/Command/SetJobLevelCommandPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Command/SetJobLevelCommandPacketHandlerTests.cs @@ -6,6 +6,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using NosCore.Algorithm.ExperienceService; +using NosCore.Algorithm.HeroExperienceService; +using NosCore.Algorithm.JobExperienceService; using NosCore.Data.CommandPackets; using NosCore.Data.WebApi; using NosCore.GameObject.InterChannelCommunication.Hubs.PubSub; @@ -29,6 +32,9 @@ public class SetJobLevelCommandPacketHandlerTests private SetJobLevelCommandPacketHandler Handler = null!; private ClientSession Session = null!; private Mock PubSubHub = null!; + private Mock ExperienceService = null!; + private Mock JobExperienceService = null!; + private Mock HeroExperienceService = null!; [TestInitialize] public async Task SetupAsync() @@ -37,11 +43,15 @@ public async Task SetupAsync() Broadcaster.Reset(); Session = await TestHelpers.Instance.GenerateSessionAsync(); PubSubHub = new Mock(); + ExperienceService = new Mock(); + JobExperienceService = new Mock(); + HeroExperienceService = new Mock(); PubSubHub.Setup(x => x.GetSubscribersAsync()) .Returns(Task.FromResult(new List())); - Handler = new SetJobLevelCommandPacketHandler(PubSubHub.Object); + Handler = new SetJobLevelCommandPacketHandler(PubSubHub.Object, + ExperienceService.Object, JobExperienceService.Object, HeroExperienceService.Object); } [TestMethod] diff --git a/test/NosCore.PacketHandlers.Tests/Group/PleavePacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Group/PleavePacketHandlerTests.cs index 97b276a51..1be530068 100644 --- a/test/NosCore.PacketHandlers.Tests/Group/PleavePacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Group/PleavePacketHandlerTests.cs @@ -53,7 +53,7 @@ public async Task SetupAsync() session.Character.Group.JoinGroup(session.Character); } - PLeavePacketHandler = new PleavePacketHandler(idServer, TestHelpers.Instance.SessionRegistry); + PLeavePacketHandler = new PleavePacketHandler(idServer, TestHelpers.Instance.SessionRegistry, new Mock().Object); var mock = new Mock(); PJoinPacketHandler = new PjoinPacketHandler(Logger, mock.Object, TestHelpers.Instance.Clock, idServer, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.GameLanguageLocalizer, TestHelpers.Instance.SessionRegistry); diff --git a/test/NosCore.PacketHandlers.Tests/Inventory/SpTransformPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Inventory/SpTransformPacketHandlerTests.cs index 59d633d3e..0f1240ff0 100644 --- a/test/NosCore.PacketHandlers.Tests/Inventory/SpTransformPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Inventory/SpTransformPacketHandlerTests.cs @@ -47,7 +47,7 @@ public async Task SetupAsync() SpTransformPacketHandler = new SpTransformPacketHandler(TestHelpers.Instance.Clock, new TransformationService(TestHelpers.Instance.Clock, new Mock().Object, new Mock().Object, new Mock().Object, - new Mock().Object, TestHelpers.Instance.LogLanguageLocalizer), + new Mock().Object, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration), TestHelpers.Instance.GameLanguageLocalizer); } diff --git a/test/NosCore.PacketHandlers.Tests/Inventory/WearPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Inventory/WearPacketHandlerTests.cs index 0c2cd0262..d5b2aed9f 100644 --- a/test/NosCore.PacketHandlers.Tests/Inventory/WearPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Inventory/WearPacketHandlerTests.cs @@ -76,7 +76,7 @@ public async Task WearingItemShouldPutInCorrectSlot(int typeInt) }; Item = new ItemGenerationService(items, new EventLoaderService, IUseItemEventHandler>( new List>> - {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); + {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(1, 1), Session.Character.CharacterId)); await WearPacketHandler.ExecuteAsync(new WearPacket { InventorySlot = 0, Type = PocketType.Equipment }, Session); @@ -104,7 +104,7 @@ public async Task WearingClassRestrictedItemShouldFailForWrongClass(int characte }; Item = new ItemGenerationService(items, new EventLoaderService, IUseItemEventHandler>( new List>> - {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); + {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(1, 1), Session.Character.CharacterId)); await WearPacketHandler.ExecuteAsync(new WearPacket { InventorySlot = 0, Type = PocketType.Equipment }, Session); @@ -130,7 +130,7 @@ public async Task WearingGenderRestrictedItemShouldFailForWrongGender(int gender }; Item = new ItemGenerationService(items, new EventLoaderService, IUseItemEventHandler>( new List>> - {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); + {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(1, 1), Session.Character.CharacterId)); await WearPacketHandler.ExecuteAsync(new WearPacket { InventorySlot = 0, Type = PocketType.Equipment }, Session); @@ -306,7 +306,7 @@ private void CharacterHasItemRequiringJobLevel_(int value) }; Item = new ItemGenerationService(items, new EventLoaderService, IUseItemEventHandler>( new List>> - {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); + {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(1, 1), Session.Character.CharacterId)); } @@ -328,7 +328,7 @@ private void CharacterHasItemRequiringLevel_(int value) }; Item = new ItemGenerationService(items, new EventLoaderService, IUseItemEventHandler>( new List>> - {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); + {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(1, 1), Session.Character.CharacterId)); } @@ -350,7 +350,7 @@ private void CharacterHasHeroicItemRequiringLevel_(int value) }; Item = new ItemGenerationService(items, new EventLoaderService, IUseItemEventHandler>( new List>> - {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); + {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(1, 1), Session.Character.CharacterId)); } @@ -363,7 +363,7 @@ private void CharacterHasDestroyedSp() }; Item = new ItemGenerationService(items, new EventLoaderService, IUseItemEventHandler>( new List>> - {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); + {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(1, 1, -2), Session.Character.CharacterId)); } @@ -377,7 +377,7 @@ private void CharacterHasTwoSpItems() }; Item = new ItemGenerationService(items, new EventLoaderService, IUseItemEventHandler>( new List>> - {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); + {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(1, 1), Session.Character.CharacterId)); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(2, 1), Session.Character.CharacterId)); } @@ -404,7 +404,7 @@ private void CharacterHasSpWithFireElementAndLightFairy() }; Item = new ItemGenerationService(items, new EventLoaderService, IUseItemEventHandler>( new List>> - {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); + {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(1, 1), Session.Character.CharacterId)); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(2, 1), Session.Character.CharacterId)); @@ -419,7 +419,7 @@ private void CharacterHasSpWithFireElementAndFireFairy() }; Item = new ItemGenerationService(items, new EventLoaderService, IUseItemEventHandler>( new List>> - {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); + {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(1, 1), Session.Character.CharacterId)); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(2, 1), Session.Character.CharacterId)); @@ -434,7 +434,7 @@ private void CharacterHasSpWithFireAndWaterElementsAndWaterFairy() }; Item = new ItemGenerationService(items, new EventLoaderService, IUseItemEventHandler>( new List>> - {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); + {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(1, 1), Session.Character.CharacterId)); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(2, 1), Session.Character.CharacterId)); @@ -454,7 +454,7 @@ private void CharacterHasItemRequiringBinding() }; Item = new ItemGenerationService(items, new EventLoaderService, IUseItemEventHandler>( new List>> - {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); + {new WearEventHandler(Logger, TestHelpers.Instance.Clock, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration)}), Logger, TestHelpers.Instance.LogLanguageLocalizer); Session.Character.InventoryService.AddItemToPocket(InventoryItemInstance.Create(Item.Create(1, 1), Session.Character.CharacterId)); } diff --git a/test/NosCore.Tests.Shared/TestHelpers.cs b/test/NosCore.Tests.Shared/TestHelpers.cs index 3b7fd9844..642ae41b3 100644 --- a/test/NosCore.Tests.Shared/TestHelpers.cs +++ b/test/NosCore.Tests.Shared/TestHelpers.cs @@ -35,6 +35,7 @@ using NosCore.Database; using NosCore.Database.Entities; using NosCore.GameObject.ComponentEntities.Entities; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.Infastructure; using NosCore.GameObject.InterChannelCommunication.Hubs.BazaarHub; using NosCore.GameObject.InterChannelCommunication.Hubs.BlacklistHub; @@ -186,7 +187,7 @@ private TestHelpers() private async Task GenerateMapInstanceProviderAsync() { MapItemProvider = new MapItemGenerationService(new EventLoaderService, IGetMapItemEventHandler>(new List>> - {new DropEventHandler(), new SpChargerEventHandler(), new GoldDropEventHandler(Instance.WorldConfiguration)}), new IdService(1)); + {new DropEventHandler(), new SpChargerEventHandler(Instance.WorldConfiguration), new GoldDropEventHandler(Instance.WorldConfiguration)}), new IdService(1)); var map = new Map { MapId = 0, @@ -262,8 +263,8 @@ public IItemGenerationService GenerateItemProvider() Tuple>> { new SpRechargerEventHandler(WorldConfiguration), - new VehicleEventHandler(Logger, Instance.LogLanguageLocalizer, new TransformationService(Instance.Clock, new Mock().Object, new Mock().Object, new Mock().Object, new Mock().Object, Instance.LogLanguageLocalizer)), - new WearEventHandler(Logger, Instance.Clock, Instance.LogLanguageLocalizer) + new VehicleEventHandler(Logger, Instance.LogLanguageLocalizer, new TransformationService(Instance.Clock, new Mock().Object, new Mock().Object, new Mock().Object, new Mock().Object, Instance.LogLanguageLocalizer, WorldConfiguration)), + new WearEventHandler(Logger, Instance.Clock, Instance.LogLanguageLocalizer, WorldConfiguration) }), Logger, Instance.LogLanguageLocalizer); } @@ -310,7 +311,7 @@ public async Task GenerateSessionAsync(List? pack new FinsPacketHandler(FriendHttpClient.Object, ChannelHttpClient.Object, TestHelpers.Instance.PubSubHub.Object, Instance.SessionRegistry), new SelectPacketHandler(CharacterDao, Logger, new Mock().Object, MapInstanceAccessorService, ItemInstanceDao, InventoryItemInstanceDao, StaticBonusDao, new Mock>().Object, new Mock>().Object, new Mock>().Object, - new Mock>().Object, new List(), new List(),WorldConfiguration, Instance.LogLanguageLocalizer, Instance.PubSubHub.Object), + new Mock>().Object, new List(), new List(),WorldConfiguration, Instance.LogLanguageLocalizer, Instance.PubSubHub.Object, SessionGroupFactory), new CSkillPacketHandler(Instance.Clock), new CBuyPacketHandler(new Mock().Object, new Mock().Object, Logger, ItemInstanceDao, Instance.LogLanguageLocalizer), new CRegPacketHandler(WorldConfiguration, new Mock().Object, ItemInstanceDao, InventoryItemInstanceDao), @@ -338,7 +339,7 @@ public async Task GenerateSessionAsync(List? pack var chara = new GameObject.ComponentEntities.Entities.Character(new InventoryService(ItemList, WorldConfiguration, Logger), new ExchangeService(new Mock().Object, WorldConfiguration, Logger, new ExchangeRequestRegistry(), Instance.LogLanguageLocalizer, Instance.GameLanguageLocalizer), new Mock().Object, new HpService(), new MpService(), new ExperienceService(), new JobExperienceService(), new HeroExperienceService(), new ReputationService(), new DignityService(), - Instance.WorldConfiguration, new Mock().Object, Instance.SessionGroupFactory, Instance.SessionRegistry, Instance.GameLanguageLocalizer) + Instance.WorldConfiguration, new Mock().Object, Instance.SessionRegistry, Instance.GameLanguageLocalizer) { CharacterId = LastId, Name = "TestExistingCharacter" + LastId, @@ -358,7 +359,7 @@ public async Task GenerateSessionAsync(List? pack session.InitializeAccount(acc); chara.MapInstance = MapInstanceAccessorService.GetBaseMapById(0)!; await session.SetCharacterAsync(chara); - session.Character.InitializeGroup(); + session.Character.InitializeGroup(SessionGroupFactory); session.Account = acc; return session; } From 6d7046c425670eb2030f3ef1e64eee142b8ff422 Mon Sep 17 00:00:00 2001 From: erwan-joly Date: Tue, 20 Jan 2026 20:53:10 +1300 Subject: [PATCH 2/4] start moving logic out of game objects --- NosCore.sln | 160 ++++++- .../ComponentEntities/Entities/Character.cs | 404 +----------------- .../ComponentEntities/Entities/MapMonster.cs | 62 +-- .../ComponentEntities/Entities/MapNpc.cs | 88 +--- .../ComponentEntities/Entities/Pet.cs | 16 +- .../Extensions/CharacterEntityExtension.cs | 362 ++++++++++++++++ .../Interfaces/IAliveEntity.cs | 2 +- .../Interfaces/ICharacterEntity.cs | 23 +- .../Interfaces/INonPlayableEntity.cs | 9 +- ...ssageChannelCommunicationMessageHandler.cs | 2 +- .../MapInstance.cs | 13 +- .../MapInstanceGenerationService.cs | 9 +- .../Handlers/ChangeClassHandler.cs | 12 +- .../Command/ChangeClassPacketHandler.cs | 12 +- .../Command/SetLevelCommandPacketHandler.cs | 11 +- .../Shops/BuyPacketHandler.cs | 7 +- test/NosCore.GameObject.Tests/GroupTests.cs | 4 - .../NRunService/Handlers/ChangeClassTests.cs | 5 +- .../Handlers/OpenShopHandlerTests.cs | 10 +- .../Handlers/TeleporterHandlerTests.cs | 18 +- test/NosCore.GameObject.Tests/ShopTests.cs | 15 +- .../CharNewPacketHandlerTests.cs | 2 +- .../CharRenPacketHandlerTests.cs | 2 +- .../Command/ChangeClassPacketHandlerTests.cs | 6 +- .../SetLevelCommandPacketHandlerTests.cs | 6 +- .../Command/SizePacketHandlerTests.cs | 1 - .../Friend/FinsPacketHandlerTests.cs | 3 +- .../Group/PleavePacketHandlerTests.cs | 4 +- .../Miniland/AddobjPacketHandlerTests.cs | 2 +- .../Miniland/MJoinPacketHandlerTests.cs | 2 +- .../MinilandObjects/MgPacketHandlerTests.cs | 2 +- .../UseobjPacketHandlerTests.cs | 2 +- .../Miniland/MlEditPacketHandlerTests.cs | 2 +- .../Miniland/RmvobjPacketHandlerTests.cs | 2 +- .../Shops/BuyPacketHandlerTests.cs | 3 +- .../Shops/NrunPacketHandlerTests.cs | 9 +- .../Shops/RequestNpcPacketHandlerTests.cs | 9 +- test/NosCore.Tests.Shared/TestHelpers.cs | 14 +- 38 files changed, 670 insertions(+), 645 deletions(-) diff --git a/NosCore.sln b/NosCore.sln index 9fb27f031..3fa73970d 100644 --- a/NosCore.sln +++ b/NosCore.sln @@ -1,6 +1,7 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.7.34031.279 +# Visual Studio Version 18 +VisualStudioVersion = 18.2.11408.102 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosCore.LoginServer", "src\NosCore.LoginServer\NosCore.LoginServer.csproj", "{40663D90-0B3E-48A7-800E-CB5E1BED27B6}" EndProject @@ -42,80 +43,234 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosCore.DtoGenerator", "too EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosCore.WebApi", "src\NosCore.WebApi\NosCore.WebApi.csproj", "{4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosCore.Parser.Tests", "test\NosCore.Parser.Tests\NosCore.Parser.Tests.csproj", "{4E68313F-69F8-4125-B87E-665D6F7DFD5D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {40663D90-0B3E-48A7-800E-CB5E1BED27B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {40663D90-0B3E-48A7-800E-CB5E1BED27B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {40663D90-0B3E-48A7-800E-CB5E1BED27B6}.Debug|x64.ActiveCfg = Debug|Any CPU + {40663D90-0B3E-48A7-800E-CB5E1BED27B6}.Debug|x64.Build.0 = Debug|Any CPU + {40663D90-0B3E-48A7-800E-CB5E1BED27B6}.Debug|x86.ActiveCfg = Debug|Any CPU + {40663D90-0B3E-48A7-800E-CB5E1BED27B6}.Debug|x86.Build.0 = Debug|Any CPU {40663D90-0B3E-48A7-800E-CB5E1BED27B6}.Release|Any CPU.ActiveCfg = Release|Any CPU {40663D90-0B3E-48A7-800E-CB5E1BED27B6}.Release|Any CPU.Build.0 = Release|Any CPU + {40663D90-0B3E-48A7-800E-CB5E1BED27B6}.Release|x64.ActiveCfg = Release|Any CPU + {40663D90-0B3E-48A7-800E-CB5E1BED27B6}.Release|x64.Build.0 = Release|Any CPU + {40663D90-0B3E-48A7-800E-CB5E1BED27B6}.Release|x86.ActiveCfg = Release|Any CPU + {40663D90-0B3E-48A7-800E-CB5E1BED27B6}.Release|x86.Build.0 = Release|Any CPU {4EFCF709-5899-4C49-A368-E556AFA3931F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4EFCF709-5899-4C49-A368-E556AFA3931F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EFCF709-5899-4C49-A368-E556AFA3931F}.Debug|x64.ActiveCfg = Debug|Any CPU + {4EFCF709-5899-4C49-A368-E556AFA3931F}.Debug|x64.Build.0 = Debug|Any CPU + {4EFCF709-5899-4C49-A368-E556AFA3931F}.Debug|x86.ActiveCfg = Debug|Any CPU + {4EFCF709-5899-4C49-A368-E556AFA3931F}.Debug|x86.Build.0 = Debug|Any CPU {4EFCF709-5899-4C49-A368-E556AFA3931F}.Release|Any CPU.ActiveCfg = Release|Any CPU {4EFCF709-5899-4C49-A368-E556AFA3931F}.Release|Any CPU.Build.0 = Release|Any CPU + {4EFCF709-5899-4C49-A368-E556AFA3931F}.Release|x64.ActiveCfg = Release|Any CPU + {4EFCF709-5899-4C49-A368-E556AFA3931F}.Release|x64.Build.0 = Release|Any CPU + {4EFCF709-5899-4C49-A368-E556AFA3931F}.Release|x86.ActiveCfg = Release|Any CPU + {4EFCF709-5899-4C49-A368-E556AFA3931F}.Release|x86.Build.0 = Release|Any CPU {BBAF31CA-9D4F-4786-81B8-7FD56633C7B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BBAF31CA-9D4F-4786-81B8-7FD56633C7B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BBAF31CA-9D4F-4786-81B8-7FD56633C7B7}.Debug|x64.ActiveCfg = Debug|Any CPU + {BBAF31CA-9D4F-4786-81B8-7FD56633C7B7}.Debug|x64.Build.0 = Debug|Any CPU + {BBAF31CA-9D4F-4786-81B8-7FD56633C7B7}.Debug|x86.ActiveCfg = Debug|Any CPU + {BBAF31CA-9D4F-4786-81B8-7FD56633C7B7}.Debug|x86.Build.0 = Debug|Any CPU {BBAF31CA-9D4F-4786-81B8-7FD56633C7B7}.Release|Any CPU.ActiveCfg = Release|Any CPU {BBAF31CA-9D4F-4786-81B8-7FD56633C7B7}.Release|Any CPU.Build.0 = Release|Any CPU + {BBAF31CA-9D4F-4786-81B8-7FD56633C7B7}.Release|x64.ActiveCfg = Release|Any CPU + {BBAF31CA-9D4F-4786-81B8-7FD56633C7B7}.Release|x64.Build.0 = Release|Any CPU + {BBAF31CA-9D4F-4786-81B8-7FD56633C7B7}.Release|x86.ActiveCfg = Release|Any CPU + {BBAF31CA-9D4F-4786-81B8-7FD56633C7B7}.Release|x86.Build.0 = Release|Any CPU {756A8A86-A7EE-475D-B2E4-E98D79049F6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {756A8A86-A7EE-475D-B2E4-E98D79049F6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {756A8A86-A7EE-475D-B2E4-E98D79049F6B}.Debug|x64.ActiveCfg = Debug|Any CPU + {756A8A86-A7EE-475D-B2E4-E98D79049F6B}.Debug|x64.Build.0 = Debug|Any CPU + {756A8A86-A7EE-475D-B2E4-E98D79049F6B}.Debug|x86.ActiveCfg = Debug|Any CPU + {756A8A86-A7EE-475D-B2E4-E98D79049F6B}.Debug|x86.Build.0 = Debug|Any CPU {756A8A86-A7EE-475D-B2E4-E98D79049F6B}.Release|Any CPU.ActiveCfg = Release|Any CPU {756A8A86-A7EE-475D-B2E4-E98D79049F6B}.Release|Any CPU.Build.0 = Release|Any CPU + {756A8A86-A7EE-475D-B2E4-E98D79049F6B}.Release|x64.ActiveCfg = Release|Any CPU + {756A8A86-A7EE-475D-B2E4-E98D79049F6B}.Release|x64.Build.0 = Release|Any CPU + {756A8A86-A7EE-475D-B2E4-E98D79049F6B}.Release|x86.ActiveCfg = Release|Any CPU + {756A8A86-A7EE-475D-B2E4-E98D79049F6B}.Release|x86.Build.0 = Release|Any CPU {83F1DAD2-9E07-4D0B-B548-DEB03105F874}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {83F1DAD2-9E07-4D0B-B548-DEB03105F874}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83F1DAD2-9E07-4D0B-B548-DEB03105F874}.Debug|x64.ActiveCfg = Debug|Any CPU + {83F1DAD2-9E07-4D0B-B548-DEB03105F874}.Debug|x64.Build.0 = Debug|Any CPU + {83F1DAD2-9E07-4D0B-B548-DEB03105F874}.Debug|x86.ActiveCfg = Debug|Any CPU + {83F1DAD2-9E07-4D0B-B548-DEB03105F874}.Debug|x86.Build.0 = Debug|Any CPU {83F1DAD2-9E07-4D0B-B548-DEB03105F874}.Release|Any CPU.ActiveCfg = Release|Any CPU {83F1DAD2-9E07-4D0B-B548-DEB03105F874}.Release|Any CPU.Build.0 = Release|Any CPU + {83F1DAD2-9E07-4D0B-B548-DEB03105F874}.Release|x64.ActiveCfg = Release|Any CPU + {83F1DAD2-9E07-4D0B-B548-DEB03105F874}.Release|x64.Build.0 = Release|Any CPU + {83F1DAD2-9E07-4D0B-B548-DEB03105F874}.Release|x86.ActiveCfg = Release|Any CPU + {83F1DAD2-9E07-4D0B-B548-DEB03105F874}.Release|x86.Build.0 = Release|Any CPU {93487BBB-589C-4315-9E52-1279C9434B3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93487BBB-589C-4315-9E52-1279C9434B3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93487BBB-589C-4315-9E52-1279C9434B3C}.Debug|x64.ActiveCfg = Debug|Any CPU + {93487BBB-589C-4315-9E52-1279C9434B3C}.Debug|x64.Build.0 = Debug|Any CPU + {93487BBB-589C-4315-9E52-1279C9434B3C}.Debug|x86.ActiveCfg = Debug|Any CPU + {93487BBB-589C-4315-9E52-1279C9434B3C}.Debug|x86.Build.0 = Debug|Any CPU {93487BBB-589C-4315-9E52-1279C9434B3C}.Release|Any CPU.ActiveCfg = Release|Any CPU {93487BBB-589C-4315-9E52-1279C9434B3C}.Release|Any CPU.Build.0 = Release|Any CPU + {93487BBB-589C-4315-9E52-1279C9434B3C}.Release|x64.ActiveCfg = Release|Any CPU + {93487BBB-589C-4315-9E52-1279C9434B3C}.Release|x64.Build.0 = Release|Any CPU + {93487BBB-589C-4315-9E52-1279C9434B3C}.Release|x86.ActiveCfg = Release|Any CPU + {93487BBB-589C-4315-9E52-1279C9434B3C}.Release|x86.Build.0 = Release|Any CPU {77D6E5F6-9A08-436D-8A9D-7F5832322FAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {77D6E5F6-9A08-436D-8A9D-7F5832322FAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77D6E5F6-9A08-436D-8A9D-7F5832322FAE}.Debug|x64.ActiveCfg = Debug|Any CPU + {77D6E5F6-9A08-436D-8A9D-7F5832322FAE}.Debug|x64.Build.0 = Debug|Any CPU + {77D6E5F6-9A08-436D-8A9D-7F5832322FAE}.Debug|x86.ActiveCfg = Debug|Any CPU + {77D6E5F6-9A08-436D-8A9D-7F5832322FAE}.Debug|x86.Build.0 = Debug|Any CPU {77D6E5F6-9A08-436D-8A9D-7F5832322FAE}.Release|Any CPU.ActiveCfg = Release|Any CPU {77D6E5F6-9A08-436D-8A9D-7F5832322FAE}.Release|Any CPU.Build.0 = Release|Any CPU + {77D6E5F6-9A08-436D-8A9D-7F5832322FAE}.Release|x64.ActiveCfg = Release|Any CPU + {77D6E5F6-9A08-436D-8A9D-7F5832322FAE}.Release|x64.Build.0 = Release|Any CPU + {77D6E5F6-9A08-436D-8A9D-7F5832322FAE}.Release|x86.ActiveCfg = Release|Any CPU + {77D6E5F6-9A08-436D-8A9D-7F5832322FAE}.Release|x86.Build.0 = Release|Any CPU {AEDAAE2C-D615-4241-8006-240BB7FF1446}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AEDAAE2C-D615-4241-8006-240BB7FF1446}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEDAAE2C-D615-4241-8006-240BB7FF1446}.Debug|x64.ActiveCfg = Debug|Any CPU + {AEDAAE2C-D615-4241-8006-240BB7FF1446}.Debug|x64.Build.0 = Debug|Any CPU + {AEDAAE2C-D615-4241-8006-240BB7FF1446}.Debug|x86.ActiveCfg = Debug|Any CPU + {AEDAAE2C-D615-4241-8006-240BB7FF1446}.Debug|x86.Build.0 = Debug|Any CPU {AEDAAE2C-D615-4241-8006-240BB7FF1446}.Release|Any CPU.ActiveCfg = Release|Any CPU {AEDAAE2C-D615-4241-8006-240BB7FF1446}.Release|Any CPU.Build.0 = Release|Any CPU + {AEDAAE2C-D615-4241-8006-240BB7FF1446}.Release|x64.ActiveCfg = Release|Any CPU + {AEDAAE2C-D615-4241-8006-240BB7FF1446}.Release|x64.Build.0 = Release|Any CPU + {AEDAAE2C-D615-4241-8006-240BB7FF1446}.Release|x86.ActiveCfg = Release|Any CPU + {AEDAAE2C-D615-4241-8006-240BB7FF1446}.Release|x86.Build.0 = Release|Any CPU {8DD33CE5-B9B4-4CCA-B3D7-55DA387AB4AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8DD33CE5-B9B4-4CCA-B3D7-55DA387AB4AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DD33CE5-B9B4-4CCA-B3D7-55DA387AB4AA}.Debug|x64.ActiveCfg = Debug|Any CPU + {8DD33CE5-B9B4-4CCA-B3D7-55DA387AB4AA}.Debug|x64.Build.0 = Debug|Any CPU + {8DD33CE5-B9B4-4CCA-B3D7-55DA387AB4AA}.Debug|x86.ActiveCfg = Debug|Any CPU + {8DD33CE5-B9B4-4CCA-B3D7-55DA387AB4AA}.Debug|x86.Build.0 = Debug|Any CPU {8DD33CE5-B9B4-4CCA-B3D7-55DA387AB4AA}.Release|Any CPU.ActiveCfg = Release|Any CPU {8DD33CE5-B9B4-4CCA-B3D7-55DA387AB4AA}.Release|Any CPU.Build.0 = Release|Any CPU + {8DD33CE5-B9B4-4CCA-B3D7-55DA387AB4AA}.Release|x64.ActiveCfg = Release|Any CPU + {8DD33CE5-B9B4-4CCA-B3D7-55DA387AB4AA}.Release|x64.Build.0 = Release|Any CPU + {8DD33CE5-B9B4-4CCA-B3D7-55DA387AB4AA}.Release|x86.ActiveCfg = Release|Any CPU + {8DD33CE5-B9B4-4CCA-B3D7-55DA387AB4AA}.Release|x86.Build.0 = Release|Any CPU {B22C7ABE-2B56-42AF-8CF2-46058A5B6543}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B22C7ABE-2B56-42AF-8CF2-46058A5B6543}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B22C7ABE-2B56-42AF-8CF2-46058A5B6543}.Debug|x64.ActiveCfg = Debug|Any CPU + {B22C7ABE-2B56-42AF-8CF2-46058A5B6543}.Debug|x64.Build.0 = Debug|Any CPU + {B22C7ABE-2B56-42AF-8CF2-46058A5B6543}.Debug|x86.ActiveCfg = Debug|Any CPU + {B22C7ABE-2B56-42AF-8CF2-46058A5B6543}.Debug|x86.Build.0 = Debug|Any CPU {B22C7ABE-2B56-42AF-8CF2-46058A5B6543}.Release|Any CPU.ActiveCfg = Release|Any CPU {B22C7ABE-2B56-42AF-8CF2-46058A5B6543}.Release|Any CPU.Build.0 = Release|Any CPU + {B22C7ABE-2B56-42AF-8CF2-46058A5B6543}.Release|x64.ActiveCfg = Release|Any CPU + {B22C7ABE-2B56-42AF-8CF2-46058A5B6543}.Release|x64.Build.0 = Release|Any CPU + {B22C7ABE-2B56-42AF-8CF2-46058A5B6543}.Release|x86.ActiveCfg = Release|Any CPU + {B22C7ABE-2B56-42AF-8CF2-46058A5B6543}.Release|x86.Build.0 = Release|Any CPU {A4B32710-06C8-4222-BC2B-2341EF514D9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A4B32710-06C8-4222-BC2B-2341EF514D9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4B32710-06C8-4222-BC2B-2341EF514D9C}.Debug|x64.ActiveCfg = Debug|Any CPU + {A4B32710-06C8-4222-BC2B-2341EF514D9C}.Debug|x64.Build.0 = Debug|Any CPU + {A4B32710-06C8-4222-BC2B-2341EF514D9C}.Debug|x86.ActiveCfg = Debug|Any CPU + {A4B32710-06C8-4222-BC2B-2341EF514D9C}.Debug|x86.Build.0 = Debug|Any CPU {A4B32710-06C8-4222-BC2B-2341EF514D9C}.Release|Any CPU.ActiveCfg = Release|Any CPU {A4B32710-06C8-4222-BC2B-2341EF514D9C}.Release|Any CPU.Build.0 = Release|Any CPU + {A4B32710-06C8-4222-BC2B-2341EF514D9C}.Release|x64.ActiveCfg = Release|Any CPU + {A4B32710-06C8-4222-BC2B-2341EF514D9C}.Release|x64.Build.0 = Release|Any CPU + {A4B32710-06C8-4222-BC2B-2341EF514D9C}.Release|x86.ActiveCfg = Release|Any CPU + {A4B32710-06C8-4222-BC2B-2341EF514D9C}.Release|x86.Build.0 = Release|Any CPU {DCD71941-444F-4A8B-B2CF-AF1C95BAB824}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DCD71941-444F-4A8B-B2CF-AF1C95BAB824}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DCD71941-444F-4A8B-B2CF-AF1C95BAB824}.Debug|x64.ActiveCfg = Debug|Any CPU + {DCD71941-444F-4A8B-B2CF-AF1C95BAB824}.Debug|x64.Build.0 = Debug|Any CPU + {DCD71941-444F-4A8B-B2CF-AF1C95BAB824}.Debug|x86.ActiveCfg = Debug|Any CPU + {DCD71941-444F-4A8B-B2CF-AF1C95BAB824}.Debug|x86.Build.0 = Debug|Any CPU {DCD71941-444F-4A8B-B2CF-AF1C95BAB824}.Release|Any CPU.ActiveCfg = Release|Any CPU {DCD71941-444F-4A8B-B2CF-AF1C95BAB824}.Release|Any CPU.Build.0 = Release|Any CPU + {DCD71941-444F-4A8B-B2CF-AF1C95BAB824}.Release|x64.ActiveCfg = Release|Any CPU + {DCD71941-444F-4A8B-B2CF-AF1C95BAB824}.Release|x64.Build.0 = Release|Any CPU + {DCD71941-444F-4A8B-B2CF-AF1C95BAB824}.Release|x86.ActiveCfg = Release|Any CPU + {DCD71941-444F-4A8B-B2CF-AF1C95BAB824}.Release|x86.Build.0 = Release|Any CPU {07506CBB-A1CE-4FFA-B2C7-AC34FD9E5676}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {07506CBB-A1CE-4FFA-B2C7-AC34FD9E5676}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07506CBB-A1CE-4FFA-B2C7-AC34FD9E5676}.Debug|x64.ActiveCfg = Debug|Any CPU + {07506CBB-A1CE-4FFA-B2C7-AC34FD9E5676}.Debug|x64.Build.0 = Debug|Any CPU + {07506CBB-A1CE-4FFA-B2C7-AC34FD9E5676}.Debug|x86.ActiveCfg = Debug|Any CPU + {07506CBB-A1CE-4FFA-B2C7-AC34FD9E5676}.Debug|x86.Build.0 = Debug|Any CPU {07506CBB-A1CE-4FFA-B2C7-AC34FD9E5676}.Release|Any CPU.ActiveCfg = Release|Any CPU {07506CBB-A1CE-4FFA-B2C7-AC34FD9E5676}.Release|Any CPU.Build.0 = Release|Any CPU + {07506CBB-A1CE-4FFA-B2C7-AC34FD9E5676}.Release|x64.ActiveCfg = Release|Any CPU + {07506CBB-A1CE-4FFA-B2C7-AC34FD9E5676}.Release|x64.Build.0 = Release|Any CPU + {07506CBB-A1CE-4FFA-B2C7-AC34FD9E5676}.Release|x86.ActiveCfg = Release|Any CPU + {07506CBB-A1CE-4FFA-B2C7-AC34FD9E5676}.Release|x86.Build.0 = Release|Any CPU {DCE80DB0-6530-4599-9433-5601F55E4270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DCE80DB0-6530-4599-9433-5601F55E4270}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DCE80DB0-6530-4599-9433-5601F55E4270}.Debug|x64.ActiveCfg = Debug|Any CPU + {DCE80DB0-6530-4599-9433-5601F55E4270}.Debug|x64.Build.0 = Debug|Any CPU + {DCE80DB0-6530-4599-9433-5601F55E4270}.Debug|x86.ActiveCfg = Debug|Any CPU + {DCE80DB0-6530-4599-9433-5601F55E4270}.Debug|x86.Build.0 = Debug|Any CPU {DCE80DB0-6530-4599-9433-5601F55E4270}.Release|Any CPU.ActiveCfg = Release|Any CPU {DCE80DB0-6530-4599-9433-5601F55E4270}.Release|Any CPU.Build.0 = Release|Any CPU + {DCE80DB0-6530-4599-9433-5601F55E4270}.Release|x64.ActiveCfg = Release|Any CPU + {DCE80DB0-6530-4599-9433-5601F55E4270}.Release|x64.Build.0 = Release|Any CPU + {DCE80DB0-6530-4599-9433-5601F55E4270}.Release|x86.ActiveCfg = Release|Any CPU + {DCE80DB0-6530-4599-9433-5601F55E4270}.Release|x86.Build.0 = Release|Any CPU {5C60F99A-1BA8-4A82-9973-7150D4BE57A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5C60F99A-1BA8-4A82-9973-7150D4BE57A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C60F99A-1BA8-4A82-9973-7150D4BE57A3}.Debug|x64.ActiveCfg = Debug|Any CPU + {5C60F99A-1BA8-4A82-9973-7150D4BE57A3}.Debug|x64.Build.0 = Debug|Any CPU + {5C60F99A-1BA8-4A82-9973-7150D4BE57A3}.Debug|x86.ActiveCfg = Debug|Any CPU + {5C60F99A-1BA8-4A82-9973-7150D4BE57A3}.Debug|x86.Build.0 = Debug|Any CPU {5C60F99A-1BA8-4A82-9973-7150D4BE57A3}.Release|Any CPU.ActiveCfg = Release|Any CPU {5C60F99A-1BA8-4A82-9973-7150D4BE57A3}.Release|Any CPU.Build.0 = Release|Any CPU + {5C60F99A-1BA8-4A82-9973-7150D4BE57A3}.Release|x64.ActiveCfg = Release|Any CPU + {5C60F99A-1BA8-4A82-9973-7150D4BE57A3}.Release|x64.Build.0 = Release|Any CPU + {5C60F99A-1BA8-4A82-9973-7150D4BE57A3}.Release|x86.ActiveCfg = Release|Any CPU + {5C60F99A-1BA8-4A82-9973-7150D4BE57A3}.Release|x86.Build.0 = Release|Any CPU {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C}.Debug|x64.ActiveCfg = Debug|Any CPU + {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C}.Debug|x64.Build.0 = Debug|Any CPU + {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C}.Debug|x86.ActiveCfg = Debug|Any CPU + {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C}.Debug|x86.Build.0 = Debug|Any CPU {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C}.Release|Any CPU.ActiveCfg = Release|Any CPU {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C}.Release|Any CPU.Build.0 = Release|Any CPU + {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C}.Release|x64.ActiveCfg = Release|Any CPU + {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C}.Release|x64.Build.0 = Release|Any CPU + {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C}.Release|x86.ActiveCfg = Release|Any CPU + {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C}.Release|x86.Build.0 = Release|Any CPU {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}.Debug|x64.ActiveCfg = Debug|Any CPU + {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}.Debug|x64.Build.0 = Debug|Any CPU + {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}.Debug|x86.ActiveCfg = Debug|Any CPU + {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}.Debug|x86.Build.0 = Debug|Any CPU {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}.Release|Any CPU.ActiveCfg = Release|Any CPU {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}.Release|Any CPU.Build.0 = Release|Any CPU + {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}.Release|x64.ActiveCfg = Release|Any CPU + {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}.Release|x64.Build.0 = Release|Any CPU + {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}.Release|x86.ActiveCfg = Release|Any CPU + {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5}.Release|x86.Build.0 = Release|Any CPU + {4E68313F-69F8-4125-B87E-665D6F7DFD5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E68313F-69F8-4125-B87E-665D6F7DFD5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E68313F-69F8-4125-B87E-665D6F7DFD5D}.Debug|x64.ActiveCfg = Debug|Any CPU + {4E68313F-69F8-4125-B87E-665D6F7DFD5D}.Debug|x64.Build.0 = Debug|Any CPU + {4E68313F-69F8-4125-B87E-665D6F7DFD5D}.Debug|x86.ActiveCfg = Debug|Any CPU + {4E68313F-69F8-4125-B87E-665D6F7DFD5D}.Debug|x86.Build.0 = Debug|Any CPU + {4E68313F-69F8-4125-B87E-665D6F7DFD5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E68313F-69F8-4125-B87E-665D6F7DFD5D}.Release|Any CPU.Build.0 = Release|Any CPU + {4E68313F-69F8-4125-B87E-665D6F7DFD5D}.Release|x64.ActiveCfg = Release|Any CPU + {4E68313F-69F8-4125-B87E-665D6F7DFD5D}.Release|x64.Build.0 = Release|Any CPU + {4E68313F-69F8-4125-B87E-665D6F7DFD5D}.Release|x86.ActiveCfg = Release|Any CPU + {4E68313F-69F8-4125-B87E-665D6F7DFD5D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -138,6 +293,7 @@ Global {5C60F99A-1BA8-4A82-9973-7150D4BE57A3} = {EFA0F8A7-CBF2-4542-82A9-931BB0111FED} {3F7C2822-BF7B-4BC9-8AA3-3ED2E2F0351C} = {975CF226-2297-4B1B-88C6-5783C5359D18} {4A4D1876-74CC-46B7-BB93-B3E0E4D25AE5} = {D4E0F6CD-9F21-4140-AC42-CD021D98B980} + {4E68313F-69F8-4125-B87E-665D6F7DFD5D} = {EFA0F8A7-CBF2-4542-82A9-931BB0111FED} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4B673C11-52EF-455B-BFC6-8CFEBC340972} diff --git a/src/NosCore.GameObject/ComponentEntities/Entities/Character.cs b/src/NosCore.GameObject/ComponentEntities/Entities/Character.cs index 2b46970c1..7d9540fc6 100644 --- a/src/NosCore.GameObject/ComponentEntities/Entities/Character.cs +++ b/src/NosCore.GameObject/ComponentEntities/Entities/Character.cs @@ -4,66 +4,46 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // -using Microsoft.Extensions.Options; using NodaTime; using NosCore.Algorithm.DignityService; -using NosCore.Algorithm.ExperienceService; -using NosCore.Algorithm.HeroExperienceService; using NosCore.Algorithm.HpService; -using NosCore.Algorithm.JobExperienceService; using NosCore.Algorithm.MpService; using NosCore.Algorithm.ReputationService; -using NosCore.Core.Configuration; using NosCore.Core.I18N; using NosCore.Data.Dto; -using NosCore.Data.Enumerations; -using NosCore.Data.Enumerations.Group; using NosCore.Data.Enumerations.I18N; using NosCore.Data.StaticEntities; -using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.ComponentEntities.Interfaces; -using NosCore.GameObject.Networking.ClientSession; using NosCore.GameObject.Services.BattleService; using NosCore.GameObject.Services.BroadcastService; using NosCore.GameObject.Services.ExchangeService; using NosCore.GameObject.Services.GroupService; using NosCore.GameObject.Services.InventoryService; using NosCore.GameObject.Services.ItemGenerationService; -using NosCore.GameObject.Services.MapChangeService; using NosCore.GameObject.Services.MapInstanceGenerationService; using NosCore.GameObject.Services.NRunService; using NosCore.GameObject.Services.QuestService; +using NosCore.GameObject.Networking.ClientSession; using NosCore.GameObject.Services.ShopService; using NosCore.GameObject.Services.SpeedCalculationService; using NosCore.Networking; -using NosCore.Networking.SessionGroup; -using NosCore.Networking.SessionGroup.ChannelMatcher; -using NosCore.Packets.ClientPackets.Shops; using NosCore.Packets.Enumerations; using NosCore.Packets.Interfaces; -using NosCore.Packets.ServerPackets.Chats; -using NosCore.Packets.ServerPackets.Player; -using NosCore.Packets.ServerPackets.Shop; -using NosCore.Packets.ServerPackets.Specialists; -using NosCore.Packets.ServerPackets.UI; using NosCore.Shared.Enumerations; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Reactive.Subjects; using System.Threading; using System.Threading.Tasks; -using MailData = NosCore.GameObject.InterChannelCommunication.Messages.MailData; namespace NosCore.GameObject.ComponentEntities.Entities { public class Character(IInventoryService inventory, IExchangeService exchangeService, IItemGenerationService itemProvider, - IHpService hpService, IMpService mpService, IExperienceService experienceService, - IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService, + IHpService hpService, IMpService mpService, IReputationService reputationService, IDignityService dignityService, - IOptions worldConfiguration, ISpeedCalculationService speedCalculationService, + ISpeedCalculationService speedCalculationService, ISessionRegistry sessionRegistry, IGameLanguageLocalizer gameLanguageLocalizer) : CharacterDto, ICharacterEntity @@ -106,7 +86,11 @@ public class Character(IInventoryService inventory, IExchangeService exchangeSer public List QuicklistEntries { get; set; } = new(); - public long BankGold => Account.BankMoney; + public long BankGold + { + get => Account.BankMoney; + set => Account.BankMoney = value; + } public RegionType AccountLanguage => Account.Language; @@ -215,381 +199,9 @@ public Task SendPacketsAsync(IEnumerable packetDefinitions) return sender?.SendPacketsAsync(packetDefinitions) ?? Task.CompletedTask; } - - // todo move this - public async Task ChangeClassAsync(CharacterClassType classType) - { - if (InventoryService.Any(s => s.Value.Type == NoscorePocketType.Wear)) - { - await SendPacketAsync(new SayiPacket - { - VisualType = VisualType.Player, - VisualId = CharacterId, - Type = SayColorType.Yellow, - Message = Game18NConstString.RemoveEquipment - }); - return; - } - - JobLevel = 1; - JobLevelXp = 0; - await SendPacketAsync(new NpInfoPacket()); - await SendPacketAsync(new PclearPacket()); - - if (classType == CharacterClassType.Adventurer) - { - HairStyle = HairStyle > HairStyleType.HairStyleB ? 0 : HairStyle; - } - - Class = classType; - Hp = MaxHp; - Mp = MaxMp; - var itemsToAdd = new List(); - foreach (var (key, _) in worldConfiguration.Value.BasicEquipments) - { - switch (key) - { - case nameof(CharacterClassType.Adventurer) when Class == CharacterClassType.Adventurer: - case nameof(CharacterClassType.Archer) when Class == CharacterClassType.Archer: - case nameof(CharacterClassType.Mage) when Class == CharacterClassType.Mage: - case nameof(CharacterClassType.MartialArtist) when Class == CharacterClassType.MartialArtist: - case nameof(CharacterClassType.Swordsman) when Class == CharacterClassType.Swordsman: - itemsToAdd.AddRange(worldConfiguration.Value.BasicEquipments[key]); - break; - default: - break; - } - } - - foreach (var inv in itemsToAdd - .Select(itemToAdd => InventoryService.AddItemToPocket(InventoryItemInstance.Create(ItemProvider.Create(itemToAdd.VNum, itemToAdd.Amount), CharacterId), itemToAdd.NoscorePocketType)) - .Where(inv => inv != null)) - { - await SendPacketsAsync( - inv!.Select(invItem => invItem.GeneratePocketChange((PocketType)invItem.Type, invItem.Slot))); - } - - await SendPacketAsync(this.GenerateTit()); - await SendPacketAsync(this.GenerateStat()); - await MapInstance.SendPacketAsync(this.GenerateEq()); - await MapInstance.SendPacketAsync(this.GenerateEff(8)); - //TODO: Faction - await SendPacketAsync(this.GenerateCond()); - await SendPacketAsync(this.GenerateLev(experienceService, jobExperienceService, heroExperienceService)); - await SendPacketAsync(this.GenerateCMode()); - await SendPacketAsync(new MsgiPacket - { - Type = MessageType.Default, - Message = Game18NConstString.ClassChanged - }); - - QuicklistEntries = new List - { - new() - { - Id = Guid.NewGuid(), - CharacterId = CharacterId, - QuickListIndex = 0, - Slot = 9, - Type = 1, - IconType = 3, - IconVNum = 1 - } - }; - - await MapInstance.SendPacketAsync(this.GenerateIn(Prefix ?? ""), new EveryoneBut(Channel!.Id)); - await MapInstance.SendPacketAsync(Group!.GeneratePidx(this)); - await MapInstance.SendPacketAsync(this.GenerateEff(6)); - await MapInstance.SendPacketAsync(this.GenerateEff(198)); - } - - - public void AddBankGold(long bankGold) - { - Account.BankMoney += bankGold; - } - - public void RemoveBankGold(long bankGold) - { - Account.BankMoney -= bankGold; - } - - - //todo move this - public async Task GenerateMailAsync(IEnumerable mails) - { - foreach (var mail in mails) - { - if (!mail.MailDto.IsSenderCopy && (mail.ReceiverName == Name)) - { - if (mail.ItemInstance != null) - { - await SendPacketAsync(mail.GeneratePost(0)); - } - else - { - await SendPacketAsync(mail.GeneratePost(1)); - } - } - else - { - if (mail.ItemInstance != null) - { - await SendPacketAsync(mail.GeneratePost(3)); - } - else - { - await SendPacketAsync(mail.GeneratePost(2)); - } - } - } - } - - public Task ChangeMapAsync(IMapChangeService mapChangeService, short mapId, short mapX, short mapY) - { - return mapChangeService.ChangeMapByCharacterIdAsync(CharacterId, mapId, mapX, mapY); - } - public string GetMessageFromKey(LanguageKey languageKey) { return gameLanguageLocalizer[languageKey, AccountLanguage]; } - - public async Task CloseShopAsync() - { - Shop = null; - - await MapInstance.SendPacketAsync(this.GenerateShop(AccountLanguage)); - await MapInstance.SendPacketAsync(this.GeneratePFlag()); - - IsSitting = false; - await SendPacketAsync(this.GenerateCond()); - await MapInstance.SendPacketAsync(this.GenerateRest()); - } - - public async Task BuyAsync(Shop shop, short slot, short amount) - { - if (amount <= 0) - { - return; - } - - var item = shop.ShopItems.Values.FirstOrDefault(it => it.Slot == slot); - if (item == null) - { - return; - } - - var itemPrice = item.Price ?? item.ItemInstance!.Item.Price; - if (itemPrice < 0 || itemPrice > long.MaxValue / amount) - { - return; - } - var price = itemPrice * amount; - - var itemReputPrice = item.Price == null ? item.ItemInstance!.Item.ReputPrice : 0; - if (itemReputPrice < 0 || itemReputPrice > long.MaxValue / amount) - { - return; - } - var reputprice = itemReputPrice * amount; - - var percent = DignityIcon switch - { - DignityType.Dreadful => 1.1, - DignityType.Unqualified => 1.2, - DignityType.Failed => 1.5, - DignityType.Useless => 1.5, - _ => 1.0, - }; - if (amount > item.Amount) - { - //todo LOG - return; - } - - if ((reputprice == 0) && (price * percent > Gold)) - { - await SendPacketAsync(new SMemoiPacket - { - Type = SMemoType.FailNpc, - Message = Game18NConstString.NotEnoughGold5 - }); - return; - } - - if (reputprice > Reput) - { - await SendPacketAsync(new SMemoiPacket - { - Type = SMemoType.FailNpc, - Message = Game18NConstString.ReputationNotHighEnough - }); - return; - } - - short slotChar = item.Slot; - List? inv; - if (shop.OwnerCharacter == null) - { - inv = InventoryService.AddItemToPocket(InventoryItemInstance.Create( - ItemProvider.Create(item.ItemInstance!.ItemVNum, amount), CharacterId)); - } - else - { - if (price + shop.OwnerCharacter.Gold > worldConfiguration.Value.MaxGoldAmount) - { - await SendPacketAsync(new SMemoPacket - { - Type = SMemoType.FailPlayer, - Message = GetMessageFromKey(LanguageKey.TOO_RICH_SELLER) - }); - return; - } - - if (amount == item.ItemInstance?.Amount) - { - inv = InventoryService.AddItemToPocket(InventoryItemInstance.Create(item.ItemInstance, - CharacterId)); - } - else - { - inv = InventoryService.AddItemToPocket(InventoryItemInstance.Create( - ItemProvider.Create(item.ItemInstance?.ItemVNum ?? 0, amount), CharacterId)); - } - } - - if (inv?.Count > 0) - { - inv.ForEach(it => it.CharacterId = CharacterId); - var packet = await (shop.OwnerCharacter == null ? Task.FromResult((NInvPacket?)null) : shop.OwnerCharacter.BuyFromAsync(item, amount, slotChar)); - if (packet != null) - { - await SendPacketAsync(packet); - } - - await SendPacketsAsync(inv.Select(invItem => invItem.GeneratePocketChange((PocketType)invItem.Type, invItem.Slot))); - await SendPacketAsync(new SMemoiPacket - { - Type = SMemoType.SuccessNpc, - Message = Game18NConstString.TradeSuccessfull - }); - - if (reputprice == 0) - { - Gold -= (long)(price * percent); - await SendPacketAsync(this.GenerateGold()); - } - else - { - Reput -= reputprice; - await SendPacketAsync(this.GenerateFd()); - await SendPacketAsync(new SayiPacket - { - VisualType = VisualType.Player, - VisualId = CharacterId, - Type = SayColorType.Red, - Message = Game18NConstString.ReputationReduced, - ArgumentType = 4, - Game18NArguments = { reputprice } - }); - } - } - else - { - await SendPacketAsync(new MsgiPacket - { - Type = MessageType.Default, - Message = Game18NConstString.NotEnoughSpace - }); - } - } - - private async Task BuyFromAsync(ShopItem item, short amount, short slotChar) - { - var type = item.Type; - var itemInstance = amount == item.ItemInstance?.Amount - ? InventoryService.DeleteById(item.ItemInstance.Id) - : InventoryService.RemoveItemAmountFromInventory(amount, item.ItemInstance!.Id); - var slot = item.Slot; - item.Amount = (short)((item.Amount ?? 0) - amount); - if ((item?.Amount ?? 0) == 0) - { - Shop!.ShopItems.TryRemove(slot, out _); - } - - await SendPacketAsync(itemInstance.GeneratePocketChange((PocketType)type, slotChar)); - var sellAmount = (item?.Price ?? 0) * amount; - Gold += sellAmount; - await SendPacketAsync(this.GenerateGold()); - Shop!.Sell += sellAmount; - - await SendPacketAsync(new SellListPacket - { - ValueSold = Shop.Sell, - SellListSubPacket = new List - { - new() - { - Amount = item?.Amount ?? 0, - Slot = slot, - SellAmount = item?.Amount ?? 0 - } - } - }); - - if (!Shop.ShopItems.IsEmpty) - { - return this.GenerateNInv(1, 0); - } - - await CloseShopAsync(); - return null; - - } - - private async Task GenerateLevelupPacketsAsync() - { - await SendPacketAsync(this.GenerateStat()); - await SendPacketAsync(this.GenerateStatInfo()); - await SendPacketAsync(this.GenerateLev(experienceService, jobExperienceService, heroExperienceService)); - var mapSessions = sessionRegistry.GetCharacters(s => s.MapInstance == MapInstance); - - await Task.WhenAll(mapSessions.Select(async s => - { - if (s.VisualId != VisualId) - { - await s.SendPacketAsync(this.GenerateIn(Authority == AuthorityType.Moderator - ? GetMessageFromKey(LanguageKey.SUPPORT) : string.Empty)); - //TODO: Generate GIDX - } - - await s.SendPacketAsync(this.GenerateEff(6)); - await s.SendPacketAsync(this.GenerateEff(198)); - })); - - foreach (var member in Group!.Keys) - { - var groupMember = sessionRegistry.GetCharacter(s => - (s.VisualId == member.Item2) && (member.Item1 == VisualType.Player)); - - groupMember?.SendPacketAsync(groupMember.Group!.GeneratePinit()); - } - - await SendPacketAsync(Group.GeneratePinit()); - } - - public async Task SetLevelAsync(byte level) - { - this.SetLevel(level); - await GenerateLevelupPacketsAsync(); - await SendPacketAsync(new MsgiPacket - { - Type = MessageType.Default, - Message = Game18NConstString.LevelIncreased - }); - } - - } } diff --git a/src/NosCore.GameObject/ComponentEntities/Entities/MapMonster.cs b/src/NosCore.GameObject/ComponentEntities/Entities/MapMonster.cs index 83be3c12f..2c4b5575e 100644 --- a/src/NosCore.GameObject/ComponentEntities/Entities/MapMonster.cs +++ b/src/NosCore.GameObject/ComponentEntities/Entities/MapMonster.cs @@ -7,44 +7,31 @@ using NodaTime; using NosCore.Data.Dto; using NosCore.Data.StaticEntities; -using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.ComponentEntities.Interfaces; using NosCore.GameObject.Services.MapInstanceGenerationService; using NosCore.GameObject.Services.ShopService; using NosCore.GameObject.Services.SpeedCalculationService; -using NosCore.PathFinder.Interfaces; using NosCore.Shared.Enumerations; -using Serilog; using System; using System.Collections.Concurrent; -using System.Reactive.Linq; using System.Threading; -using System.Threading.Tasks; namespace NosCore.GameObject.ComponentEntities.Entities { - public class MapMonster(ILogger logger, IHeuristic distanceCalculator, IClock clock, - ISpeedCalculationService speedCalculationService) + public class MapMonster(ISpeedCalculationService speedCalculationService) : MapMonsterDto, INonPlayableEntity { - public NpcMonsterDto NpcMonster { get; private set; } = null!; + public NpcMonsterDto NpcMonster { get; set; } = null!; - public IDisposable? Life { get; private set; } + public IDisposable? Life { get; set; } public ConcurrentDictionary HitList => new(); - public void Initialize(NpcMonsterDto npcMonster) + public bool IsSitting { get; set; } + public byte Speed { - NpcMonster = npcMonster; - Mp = NpcMonster?.MaxMp ?? 0; - Hp = NpcMonster?.MaxHp ?? 0; - PositionX = MapX; - PositionY = MapY; - IsAlive = true; - Level = NpcMonster?.Level ?? 0; + get => speedCalculationService.CalculateSpeed(this); + set { } } - - public bool IsSitting { get; set; } - public byte Speed => speedCalculationService.CalculateSpeed(this); public byte Size { get; set; } = 10; public int Mp { get; set; } public int Hp { get; set; } @@ -73,43 +60,10 @@ public void Initialize(NpcMonsterDto npcMonster) public int MaxMp => NpcMonster.MaxMp; public short Race => NpcMonster.Race; - public Shop? Shop => null; + public Shop? Shop { get; set; } public byte Level { get; set; } public byte HeroLevel { get; set; } - - internal void StopLife() - { - Life?.Dispose(); - Life = null; - } - - public Task StartLifeAsync() - { - Life?.Dispose(); - - async Task LifeAsync() - { - try - { - if (!MapInstance.IsSleeping) - { - await MonsterLifeAsync(); - } - } - catch (Exception e) - { - logger.Error(e.Message, e); - } - } - Life = Observable.Interval(TimeSpan.FromMilliseconds(400)).Select(_ => LifeAsync()).Subscribe(); - return Task.CompletedTask; - } - - private Task MonsterLifeAsync() - { - return this.MoveAsync(distanceCalculator, clock); - } } } diff --git a/src/NosCore.GameObject/ComponentEntities/Entities/MapNpc.cs b/src/NosCore.GameObject/ComponentEntities/Entities/MapNpc.cs index f873ed7ff..213b0f746 100644 --- a/src/NosCore.GameObject/ComponentEntities/Entities/MapNpc.cs +++ b/src/NosCore.GameObject/ComponentEntities/Entities/MapNpc.cs @@ -4,72 +4,30 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // -using Mapster; using NodaTime; using NosCore.Data.Dto; using NosCore.Data.StaticEntities; -using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.ComponentEntities.Interfaces; using NosCore.GameObject.Networking.ClientSession; -using NosCore.GameObject.Services.ItemGenerationService; using NosCore.GameObject.Services.MapInstanceGenerationService; using NosCore.GameObject.Services.NRunService; using NosCore.GameObject.Services.ShopService; -using NosCore.PathFinder.Interfaces; using NosCore.Shared.Enumerations; -using Serilog; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading; -using System.Threading.Tasks; namespace NosCore.GameObject.ComponentEntities.Entities { - public class MapNpc(IItemGenerationService? itemProvider, ILogger logger, IHeuristic distanceCalculator, - IClock clock) - : MapNpcDto, INonPlayableEntity, IRequestableEntity + public class MapNpc : MapNpcDto, INonPlayableEntity, IRequestableEntity { - public NpcMonsterDto NpcMonster { get; private set; } = null!; + public NpcMonsterDto NpcMonster { get; set; } = null!; - public IDisposable? Life { get; private set; } + public IDisposable? Life { get; set; } public ConcurrentDictionary HitList => new(); - public void Initialize(NpcMonsterDto npcMonster, ShopDto? shopDto, NpcTalkDto? npcTalkDto, List shopItemsDto) - { - NpcMonster = npcMonster; - Mp = NpcMonster?.MaxMp ?? 0; - Hp = NpcMonster?.MaxHp ?? 0; - Speed = NpcMonster?.Speed ?? 0; - PositionX = MapX; - PositionY = MapY; - IsAlive = true; - - Task RequestExecAsync(RequestData request) - { - return ShowDialogAsync(request); - } - Requests[typeof(INrunEventHandler)]?.Select(RequestExecAsync).Subscribe(); - var shopObj = shopDto; - if (shopObj == null) - { - return; - } - - var shopItemsList = new ConcurrentDictionary(); - Parallel.ForEach(shopItemsDto, shopItemGrouping => - { - var shopItem = shopItemGrouping.Adapt(); - shopItem.ItemInstance = itemProvider!.Create(shopItemGrouping.ItemVNum, -1); - shopItemsList[shopItemGrouping.ShopItemId] = shopItem; - }); - Shop = shopObj.Adapt(); - Shop.Name = npcTalkDto?.Name ?? new I18NString(); - Shop.OwnerCharacter = null; - Shop.ShopItems = shopItemsList; - } public SemaphoreSlim HitSemaphore { get; } = new SemaphoreSlim(1, 1); public byte Speed { get; set; } @@ -101,49 +59,11 @@ Task RequestExecAsync(RequestData request) public byte Level { get; set; } public byte HeroLevel { get; set; } - public Shop? Shop { get; private set; } + public Shop? Shop { get; set; } public Dictionary> Requests { get; set; } = new() { [typeof(INrunEventHandler)] = new() }; - - private Task ShowDialogAsync(RequestData requestData) - { - return requestData.ClientSession.SendPacketAsync(this.GenerateNpcReq(Dialog ?? 0)); - } - - internal void StopLife() - { - Life?.Dispose(); - Life = null; - } - - public Task StartLifeAsync() - { - Life?.Dispose(); - - async Task LifeAsync() - { - try - { - if (!MapInstance.IsSleeping) - { - await MonsterLifeAsync(); - } - } - catch (Exception e) - { - logger.Error(e.Message, e); - } - } - Life = Observable.Interval(TimeSpan.FromMilliseconds(400)).Select(_ => LifeAsync()).Subscribe(); - return Task.CompletedTask; - } - - private Task MonsterLifeAsync() - { - return this.MoveAsync(distanceCalculator, clock); - } } } diff --git a/src/NosCore.GameObject/ComponentEntities/Entities/Pet.cs b/src/NosCore.GameObject/ComponentEntities/Entities/Pet.cs index b82007694..2540fa56d 100644 --- a/src/NosCore.GameObject/ComponentEntities/Entities/Pet.cs +++ b/src/NosCore.GameObject/ComponentEntities/Entities/Pet.cs @@ -20,8 +20,8 @@ namespace NosCore.GameObject.ComponentEntities.Entities { public class Pet : MapMonsterDto, INamedEntity //TODO replace MapMonsterDTO by the correct PetDTO { - public IDisposable? Life { get; private set; } - public NpcMonsterDto NpcMonster { get; private set; } = null!; + public IDisposable? Life { get; set; } + public NpcMonsterDto NpcMonster { get; set; } = null!; public short Effect { get; set; } public short EffectDelay { get; set; } public Instant LastMove { get; set; } @@ -62,16 +62,6 @@ public class Pet : MapMonsterDto, INamedEntity //TODO replace MapMonsterDTO by t public short Race => NpcMonster.Race; - public Shop? Shop => null; - - - internal void Initialize(NpcMonsterDto npcMonster) - { - NpcMonster = npcMonster; - Mp = NpcMonster.MaxMp; - Hp = NpcMonster.MaxHp; - Speed = NpcMonster.Speed; - IsAlive = true; - } + public Shop? Shop { get; set; } } } diff --git a/src/NosCore.GameObject/ComponentEntities/Extensions/CharacterEntityExtension.cs b/src/NosCore.GameObject/ComponentEntities/Extensions/CharacterEntityExtension.cs index 5d1bdb939..4bc3374b4 100644 --- a/src/NosCore.GameObject/ComponentEntities/Extensions/CharacterEntityExtension.cs +++ b/src/NosCore.GameObject/ComponentEntities/Extensions/CharacterEntityExtension.cs @@ -23,10 +23,17 @@ using NosCore.GameObject.Services.BattleService; using NosCore.GameObject.Services.BroadcastService; using NosCore.GameObject.Services.GroupService; +using NosCore.GameObject.Services.InventoryService; using NosCore.GameObject.Services.ItemGenerationService.Item; +using NosCore.Data.Dto; +using NosCore.GameObject.Services.MapChangeService; +using NosCore.GameObject.Services.ShopService; +using NosCore.Networking; using NosCore.Networking.SessionGroup; +using NosCore.Networking.SessionGroup.ChannelMatcher; using NosCore.Packets.ClientPackets.Player; using NosCore.Packets.Enumerations; +using NosCore.Packets.ServerPackets.Shop; using NosCore.Packets.Interfaces; using NosCore.Packets.ServerPackets.Chats; using NosCore.Packets.ServerPackets.Entities; @@ -49,6 +56,7 @@ using System.Linq; using System.Threading.Tasks; using PostedPacket = NosCore.GameObject.InterChannelCommunication.Messages.PostedPacket; +using MailData = NosCore.GameObject.InterChannelCommunication.Messages.MailData; namespace NosCore.GameObject.ComponentEntities.Extensions { @@ -850,5 +858,359 @@ await groupMember.SendPacketAsync(new MsgiPacket characterEntity.Group = new Group(GroupType.Group, sessionGroupFactory); characterEntity.Group.JoinGroup(characterEntity); } + + public static void AddBankGold(this ICharacterEntity characterEntity, long bankGold) + { + characterEntity.BankGold += bankGold; + } + + public static void RemoveBankGold(this ICharacterEntity characterEntity, long bankGold) + { + characterEntity.BankGold -= bankGold; + } + + public static async Task GenerateMailAsync(this ICharacterEntity characterEntity, IEnumerable mails) + { + foreach (var mail in mails) + { + if (!mail.MailDto.IsSenderCopy && (mail.ReceiverName == characterEntity.Name)) + { + if (mail.ItemInstance != null) + { + await characterEntity.SendPacketAsync(mail.GeneratePost(0)!); + } + else + { + await characterEntity.SendPacketAsync(mail.GeneratePost(1)!); + } + } + else + { + if (mail.ItemInstance != null) + { + await characterEntity.SendPacketAsync(mail.GeneratePost(3)!); + } + else + { + await characterEntity.SendPacketAsync(mail.GeneratePost(2)!); + } + } + } + } + + public static Task ChangeMapAsync(this ICharacterEntity characterEntity, IMapChangeService mapChangeService, short mapId, short mapX, short mapY) + { + return mapChangeService.ChangeMapByCharacterIdAsync(characterEntity.CharacterId, mapId, mapX, mapY); + } + + public static async Task CloseShopAsync(this ICharacterEntity characterEntity) + { + characterEntity.Shop = null; + + await characterEntity.MapInstance.SendPacketAsync(characterEntity.GenerateShop(characterEntity.AccountLanguage)); + await characterEntity.MapInstance.SendPacketAsync(characterEntity.GeneratePFlag()); + + characterEntity.IsSitting = false; + await characterEntity.SendPacketAsync(characterEntity.GenerateCond()); + await characterEntity.MapInstance.SendPacketAsync(characterEntity.GenerateRest()); + } + + public static async Task ChangeClassAsync(this ICharacterEntity characterEntity, CharacterClassType classType, + IOptions worldConfiguration, + IExperienceService experienceService, IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService) + { + if (characterEntity.InventoryService.Any(s => s.Value.Type == NoscorePocketType.Wear)) + { + await characterEntity.SendPacketAsync(new SayiPacket + { + VisualType = VisualType.Player, + VisualId = characterEntity.CharacterId, + Type = SayColorType.Yellow, + Message = Game18NConstString.RemoveEquipment + }); + return; + } + + characterEntity.JobLevel = 1; + characterEntity.JobLevelXp = 0; + await characterEntity.SendPacketAsync(new NpInfoPacket()); + await characterEntity.SendPacketAsync(new PclearPacket()); + + if (classType == CharacterClassType.Adventurer) + { + characterEntity.HairStyle = characterEntity.HairStyle > HairStyleType.HairStyleB ? 0 : characterEntity.HairStyle; + } + + characterEntity.Class = classType; + characterEntity.Hp = characterEntity.MaxHp; + characterEntity.Mp = characterEntity.MaxMp; + var itemsToAdd = new List(); + foreach (var (key, _) in worldConfiguration.Value.BasicEquipments) + { + switch (key) + { + case nameof(CharacterClassType.Adventurer) when characterEntity.Class == CharacterClassType.Adventurer: + case nameof(CharacterClassType.Archer) when characterEntity.Class == CharacterClassType.Archer: + case nameof(CharacterClassType.Mage) when characterEntity.Class == CharacterClassType.Mage: + case nameof(CharacterClassType.MartialArtist) when characterEntity.Class == CharacterClassType.MartialArtist: + case nameof(CharacterClassType.Swordsman) when characterEntity.Class == CharacterClassType.Swordsman: + itemsToAdd.AddRange(worldConfiguration.Value.BasicEquipments[key]); + break; + default: + break; + } + } + + foreach (var inv in itemsToAdd + .Select(itemToAdd => characterEntity.InventoryService.AddItemToPocket(InventoryItemInstance.Create(characterEntity.ItemProvider.Create(itemToAdd.VNum, itemToAdd.Amount), characterEntity.CharacterId), itemToAdd.NoscorePocketType)) + .Where(inv => inv != null)) + { + await characterEntity.SendPacketsAsync( + inv!.Select(invItem => invItem.GeneratePocketChange((PocketType)invItem.Type, invItem.Slot))); + } + + await characterEntity.SendPacketAsync(characterEntity.GenerateTit()); + await characterEntity.SendPacketAsync(characterEntity.GenerateStat()); + await characterEntity.MapInstance.SendPacketAsync(characterEntity.GenerateEq()); + await characterEntity.MapInstance.SendPacketAsync(characterEntity.GenerateEff(8)); + await characterEntity.SendPacketAsync(characterEntity.GenerateCond()); + await characterEntity.SendPacketAsync(characterEntity.GenerateLev(experienceService, jobExperienceService, heroExperienceService)); + await characterEntity.SendPacketAsync(characterEntity.GenerateCMode()); + await characterEntity.SendPacketAsync(new MsgiPacket + { + Type = MessageType.Default, + Message = Game18NConstString.ClassChanged + }); + + characterEntity.QuicklistEntries = new List + { + new() + { + Id = Guid.NewGuid(), + CharacterId = characterEntity.CharacterId, + QuickListIndex = 0, + Slot = 9, + Type = 1, + IconType = 3, + IconVNum = 1 + } + }; + + await characterEntity.MapInstance.SendPacketAsync(characterEntity.GenerateIn(characterEntity.Prefix ?? ""), new EveryoneBut(characterEntity.Channel!.Id)); + await characterEntity.MapInstance.SendPacketAsync(characterEntity.Group!.GeneratePidx(characterEntity)); + await characterEntity.MapInstance.SendPacketAsync(characterEntity.GenerateEff(6)); + await characterEntity.MapInstance.SendPacketAsync(characterEntity.GenerateEff(198)); + } + + public static async Task BuyAsync(this ICharacterEntity characterEntity, Shop shop, short slot, short amount, + IOptions worldConfiguration) + { + if (amount <= 0) + { + return; + } + + var item = shop.ShopItems.Values.FirstOrDefault(it => it.Slot == slot); + if (item == null) + { + return; + } + + var itemPrice = item.Price ?? item.ItemInstance!.Item.Price; + if (itemPrice < 0 || itemPrice > long.MaxValue / amount) + { + return; + } + var price = itemPrice * amount; + + var itemReputPrice = item.Price == null ? item.ItemInstance!.Item.ReputPrice : 0; + if (itemReputPrice < 0 || itemReputPrice > long.MaxValue / amount) + { + return; + } + var reputprice = itemReputPrice * amount; + + var percent = characterEntity.DignityIcon switch + { + DignityType.Dreadful => 1.1, + DignityType.Unqualified => 1.2, + DignityType.Failed => 1.5, + DignityType.Useless => 1.5, + _ => 1.0, + }; + if (amount > item.Amount) + { + return; + } + + if ((reputprice == 0) && (price * percent > characterEntity.Gold)) + { + await characterEntity.SendPacketAsync(new SMemoiPacket + { + Type = SMemoType.FailNpc, + Message = Game18NConstString.NotEnoughGold5 + }); + return; + } + + if (reputprice > characterEntity.Reput) + { + await characterEntity.SendPacketAsync(new SMemoiPacket + { + Type = SMemoType.FailNpc, + Message = Game18NConstString.ReputationNotHighEnough + }); + return; + } + + short slotChar = item.Slot; + List? inv; + if (shop.OwnerCharacter == null) + { + inv = characterEntity.InventoryService.AddItemToPocket(InventoryItemInstance.Create( + characterEntity.ItemProvider.Create(item.ItemInstance!.ItemVNum, amount), characterEntity.CharacterId)); + } + else + { + if (price + shop.OwnerCharacter.Gold > worldConfiguration.Value.MaxGoldAmount) + { + await characterEntity.SendPacketAsync(new SMemoPacket + { + Type = SMemoType.FailPlayer, + Message = characterEntity.GetMessageFromKey(LanguageKey.TOO_RICH_SELLER) + }); + return; + } + + if (amount == item.ItemInstance?.Amount) + { + inv = characterEntity.InventoryService.AddItemToPocket(InventoryItemInstance.Create(item.ItemInstance, + characterEntity.CharacterId)); + } + else + { + inv = characterEntity.InventoryService.AddItemToPocket(InventoryItemInstance.Create( + characterEntity.ItemProvider.Create(item.ItemInstance?.ItemVNum ?? 0, amount), characterEntity.CharacterId)); + } + } + + if (inv?.Count > 0) + { + inv.ForEach(it => it.CharacterId = characterEntity.CharacterId); + var packet = await (shop.OwnerCharacter == null ? Task.FromResult((NInvPacket?)null) : shop.OwnerCharacter.BuyFromAsync(item, amount, slotChar)); + if (packet != null) + { + await characterEntity.SendPacketAsync(packet); + } + + await characterEntity.SendPacketsAsync(inv.Select(invItem => invItem.GeneratePocketChange((PocketType)invItem.Type, invItem.Slot))); + await characterEntity.SendPacketAsync(new SMemoiPacket + { + Type = SMemoType.SuccessNpc, + Message = Game18NConstString.TradeSuccessfull + }); + + if (reputprice == 0) + { + characterEntity.Gold -= (long)(price * percent); + await characterEntity.SendPacketAsync(characterEntity.GenerateGold()); + } + else + { + characterEntity.Reput -= reputprice; + await characterEntity.SendPacketAsync(characterEntity.GenerateFd()); + await characterEntity.SendPacketAsync(new SayiPacket + { + VisualType = VisualType.Player, + VisualId = characterEntity.CharacterId, + Type = SayColorType.Red, + Message = Game18NConstString.ReputationReduced, + ArgumentType = 4, + Game18NArguments = { reputprice } + }); + } + } + else + { + await characterEntity.SendPacketAsync(new MsgiPacket + { + Type = MessageType.Default, + Message = Game18NConstString.NotEnoughSpace + }); + } + } + + public static async Task BuyFromAsync(this ICharacterEntity characterEntity, ShopItem item, short amount, short slotChar) + { + var type = item.Type; + var itemInstance = amount == item.ItemInstance?.Amount + ? characterEntity.InventoryService.DeleteById(item.ItemInstance.Id) + : characterEntity.InventoryService.RemoveItemAmountFromInventory(amount, item.ItemInstance!.Id); + var slot = item.Slot; + item.Amount = (short)((item.Amount ?? 0) - amount); + if ((item?.Amount ?? 0) == 0) + { + characterEntity.Shop!.ShopItems.TryRemove(slot, out _); + } + + await characterEntity.SendPacketAsync(itemInstance.GeneratePocketChange((PocketType)type, slotChar)); + var sellAmount = (item?.Price ?? 0) * amount; + characterEntity.Gold += sellAmount; + await characterEntity.SendPacketAsync(characterEntity.GenerateGold()); + characterEntity.Shop!.Sell += sellAmount; + + if (!characterEntity.Shop.ShopItems.IsEmpty) + { + return characterEntity.GenerateNInv(1, 0); + } + + await characterEntity.CloseShopAsync(); + return null; + } + + public static async Task SetLevelAsync(this ICharacterEntity characterEntity, byte level, + IExperienceService experienceService, IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService, + ISessionRegistry sessionRegistry) + { + characterEntity.SetLevel(level); + await characterEntity.GenerateLevelupPacketsAsync(experienceService, jobExperienceService, heroExperienceService, sessionRegistry); + await characterEntity.SendPacketAsync(new MsgiPacket + { + Type = MessageType.Default, + Message = Game18NConstString.LevelIncreased + }); + } + + public static async Task GenerateLevelupPacketsAsync(this ICharacterEntity characterEntity, + IExperienceService experienceService, IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService, + ISessionRegistry sessionRegistry) + { + await characterEntity.SendPacketAsync(characterEntity.GenerateStat()); + await characterEntity.SendPacketAsync(characterEntity.GenerateStatInfo()); + await characterEntity.SendPacketAsync(characterEntity.GenerateLev(experienceService, jobExperienceService, heroExperienceService)); + var mapSessions = sessionRegistry.GetCharacters(s => s.MapInstance == characterEntity.MapInstance); + + await Task.WhenAll(mapSessions.Select(async s => + { + if (s.VisualId != characterEntity.VisualId) + { + await s.SendPacketAsync(characterEntity.GenerateIn(characterEntity.Authority == AuthorityType.Moderator + ? characterEntity.GetMessageFromKey(LanguageKey.SUPPORT) : string.Empty)); + } + + await s.SendPacketAsync(characterEntity.GenerateEff(6)); + await s.SendPacketAsync(characterEntity.GenerateEff(198)); + })); + + foreach (var member in characterEntity.Group!.Keys) + { + var groupMember = sessionRegistry.GetCharacter(s => + (s.VisualId == member.Item2) && (member.Item1 == VisualType.Player)); + + groupMember?.SendPacketAsync(groupMember.Group!.GeneratePinit()); + } + + await characterEntity.SendPacketAsync(characterEntity.Group.GeneratePinit()); + } } } diff --git a/src/NosCore.GameObject/ComponentEntities/Interfaces/IAliveEntity.cs b/src/NosCore.GameObject/ComponentEntities/Interfaces/IAliveEntity.cs index c4837f262..69fa3d754 100644 --- a/src/NosCore.GameObject/ComponentEntities/Interfaces/IAliveEntity.cs +++ b/src/NosCore.GameObject/ComponentEntities/Interfaces/IAliveEntity.cs @@ -50,7 +50,7 @@ public interface IAliveEntity : IVisualEntity short Race { get; } - Shop? Shop { get; } + Shop? Shop { get; set; } SemaphoreSlim HitSemaphore { get; } diff --git a/src/NosCore.GameObject/ComponentEntities/Interfaces/ICharacterEntity.cs b/src/NosCore.GameObject/ComponentEntities/Interfaces/ICharacterEntity.cs index bb469ed85..8c6d9cc77 100644 --- a/src/NosCore.GameObject/ComponentEntities/Interfaces/ICharacterEntity.cs +++ b/src/NosCore.GameObject/ComponentEntities/Interfaces/ICharacterEntity.cs @@ -10,7 +10,7 @@ using NosCore.GameObject.Services.BattleService; using NosCore.GameObject.Services.GroupService; using NosCore.GameObject.Services.InventoryService; -using NosCore.GameObject.Services.MapChangeService; +using NosCore.GameObject.Services.ItemGenerationService; using NosCore.GameObject.Services.QuestService; using NosCore.Networking; using NosCore.Packets.Enumerations; @@ -40,11 +40,11 @@ public interface ICharacterEntity : INamedEntity, IRequestableEntity GenderType Gender { get; } - HairStyleType HairStyle { get; } + HairStyleType HairStyle { get; set; } HairColorType HairColor { get; } - CharacterClassType Class { get; } + CharacterClassType Class { get; set; } ReputationType ReputIcon { get; } @@ -66,14 +66,12 @@ public interface ICharacterEntity : INamedEntity, IRequestableEntity ConcurrentDictionary GroupRequestCharacterIds { get; } - List QuicklistEntries { get; } + List QuicklistEntries { get; set; } ConcurrentDictionary Skills { get; } long Gold { get; set; } - long BankGold { get; } - IInventoryService InventoryService { get; } RegionType AccountLanguage { get; } @@ -90,19 +88,18 @@ public interface ICharacterEntity : INamedEntity, IRequestableEntity long Reput { get; set; } short Dignity { get; } - Task GenerateMailAsync(IEnumerable data); + long CharacterId { get; } - Task SendPacketAsync(IPacket packetDefinition); + IItemGenerationService ItemProvider { get; } - Task SendPacketsAsync(IEnumerable packetDefinitions); + long BankGold { get; set; } - void AddBankGold(long bankGold); + string? Prefix { get; } - void RemoveBankGold(long bankGold); + Task SendPacketAsync(IPacket packetDefinition); - Task ChangeClassAsync(CharacterClassType classType); + Task SendPacketsAsync(IEnumerable packetDefinitions); - Task ChangeMapAsync(IMapChangeService mapChangeService, short mapId, short mapX, short mapY); string GetMessageFromKey(LanguageKey support); } } diff --git a/src/NosCore.GameObject/ComponentEntities/Interfaces/INonPlayableEntity.cs b/src/NosCore.GameObject/ComponentEntities/Interfaces/INonPlayableEntity.cs index 53897cf1f..75967d27c 100644 --- a/src/NosCore.GameObject/ComponentEntities/Interfaces/INonPlayableEntity.cs +++ b/src/NosCore.GameObject/ComponentEntities/Interfaces/INonPlayableEntity.cs @@ -6,6 +6,7 @@ using NodaTime; using NosCore.Data.StaticEntities; +using System; namespace NosCore.GameObject.ComponentEntities.Interfaces { @@ -19,8 +20,14 @@ public interface INonPlayableEntity : IAliveEntity bool IsDisabled { get; } - NpcMonsterDto NpcMonster { get; } + NpcMonsterDto NpcMonster { get; set; } Instant LastMove { get; set; } + + IDisposable? Life { get; set; } + + new bool IsAlive { get; set; } + + new byte Speed { get; set; } } } diff --git a/src/NosCore.GameObject/Services/ChannelCommunicationService/Handlers/StatDataMessageChannelCommunicationMessageHandler.cs b/src/NosCore.GameObject/Services/ChannelCommunicationService/Handlers/StatDataMessageChannelCommunicationMessageHandler.cs index bc9b8b43e..d09458567 100644 --- a/src/NosCore.GameObject/Services/ChannelCommunicationService/Handlers/StatDataMessageChannelCommunicationMessageHandler.cs +++ b/src/NosCore.GameObject/Services/ChannelCommunicationService/Handlers/StatDataMessageChannelCommunicationMessageHandler.cs @@ -51,7 +51,7 @@ public override async Task Handle(StatData data) await session.SetGoldAsync(data.Data); break; case UpdateStatActionType.UpdateClass: - await session.ChangeClassAsync((CharacterClassType)data.Data); + await session.ChangeClassAsync((CharacterClassType)data.Data, worldConfiguration, experienceService, jobExperienceService, heroExperienceService); break; default: logger.Error(logLanguage[LogLanguageKey.UNKWNOWN_RECEIVERTYPE]); diff --git a/src/NosCore.GameObject/Services/MapInstanceGenerationService/MapInstance.cs b/src/NosCore.GameObject/Services/MapInstanceGenerationService/MapInstance.cs index baa8d0fa6..ec27c5d7a 100644 --- a/src/NosCore.GameObject/Services/MapInstanceGenerationService/MapInstance.cs +++ b/src/NosCore.GameObject/Services/MapInstanceGenerationService/MapInstance.cs @@ -16,6 +16,7 @@ using NosCore.GameObject.Services.MapItemGenerationService; using NosCore.Networking.SessionGroup; using NosCore.Packets.Interfaces; +using NosCore.PathFinder.Interfaces; using NosCore.Packets.ServerPackets.MiniMap; using NosCore.Shared.Enumerations; using NosCore.Shared.Helpers; @@ -48,10 +49,11 @@ public class MapInstance : IBroadcastable, IDisposable, IRequestableEntity(); XpRate = 1; @@ -72,6 +74,7 @@ public MapInstance(Map.Map map, Guid guid, bool shopAllowed, MapInstanceType typ _logger = logger; _mapChangeService = mapChangeService; _sessionRegistry = sessionRegistry; + _distanceCalculator = distanceCalculator; Requests = new Dictionary>> { [typeof(IMapInstanceEntranceEventHandler)] = new() @@ -93,8 +96,8 @@ public bool IsSleeping _isSleeping = true; _isSleepingRequest = false; - Parallel.ForEach(Monsters.Where(s => s.Life != null), monster => monster.StopLife()); - Parallel.ForEach(Npcs.Where(s => s.Life != null), npc => npc.StopLife()); + Parallel.ForEach(Monsters.Where(s => s.Life != null), monster => NonPlayableEntityExtension.StopLife(monster)); + Parallel.ForEach(Npcs.Where(s => s.Life != null), npc => NonPlayableEntityExtension.StopLife(npc)); return true; } @@ -295,8 +298,8 @@ async Task LifeAsync() return; } - await Task.WhenAll(Monsters.Where(s => s.Life == null).Select(monster => monster.StartLifeAsync())); - await Task.WhenAll(Npcs.Where(s => s.Life == null).Select(npc => npc.StartLifeAsync())); + await Task.WhenAll(Monsters.Where(s => s.Life == null).Select(monster => monster.StartLifeAsync(_distanceCalculator, _clock, _logger))); + await Task.WhenAll(Npcs.Where(s => s.Life == null).Select(npc => npc.StartLifeAsync(_distanceCalculator, _clock, _logger))); } catch (Exception e) { diff --git a/src/NosCore.GameObject/Services/MapInstanceGenerationService/MapInstanceGenerationService.cs b/src/NosCore.GameObject/Services/MapInstanceGenerationService/MapInstanceGenerationService.cs index fc9201f5c..72ffc3e25 100644 --- a/src/NosCore.GameObject/Services/MapInstanceGenerationService/MapInstanceGenerationService.cs +++ b/src/NosCore.GameObject/Services/MapInstanceGenerationService/MapInstanceGenerationService.cs @@ -12,12 +12,15 @@ using NosCore.Data.Enumerations.Map; using NosCore.Data.StaticEntities; using NosCore.GameObject.ComponentEntities.Entities; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.Services.BroadcastService; +using NosCore.GameObject.Services.ItemGenerationService; using NosCore.GameObject.Services.EventLoaderService; using NosCore.GameObject.Services.MapChangeService; using NosCore.GameObject.Services.MapInstanceAccessService; using NosCore.GameObject.Services.MapItemGenerationService; using NosCore.Networking.SessionGroup; +using NosCore.PathFinder.Interfaces; using NosCore.Shared.I18N; using Serilog; using System; @@ -35,7 +38,7 @@ public class MapInstanceGeneratorService(List maps, List MapInstance, IMapInstanceEntranceEventHandler> entranceRunnerService, IMapInstanceRegistry mapInstanceRegistry, IMapInstanceAccessorService mapInstanceAccessorService, IClock clock, ILogLanguageLocalizer logLanguage, IMapChangeService mapChangeService, - ISessionGroupFactory sessionGroupFactory, ISessionRegistry sessionRegistry) + ISessionGroupFactory sessionGroupFactory, ISessionRegistry sessionRegistry, IItemGenerationService itemProvider, IHeuristic distanceCalculator) : IMapInstanceGeneratorService { public Task AddMapInstanceAsync(MapInstance mapInstance) @@ -98,7 +101,7 @@ public async Task InitializeAsync() dtoShopItems = shopItems!.Where(o => o.ShopId == shop.ShopId)!.ToList(); dialog = npcTalks.Find(o => o.DialogId == s.Dialog); } - s.Initialize(npcMonsters.Find(o => o.NpcMonsterVNum == s.VNum)!, shop, dialog, dtoShopItems); + s.Initialize(npcMonsters.Find(o => o.NpcMonsterVNum == s.VNum)!, shop, dialog, dtoShopItems, itemProvider); }); mapinstance.LoadNpcs(npc); } @@ -114,7 +117,7 @@ public async Task InitializeAsync() public MapInstance CreateMapInstance(Map.Map map, Guid guid, bool shopAllowed, MapInstanceType normalInstance) { - return new MapInstance(map, guid, shopAllowed, normalInstance, mapItemGenerationService, logger, clock, mapChangeService, sessionGroupFactory, sessionRegistry); + return new MapInstance(map, guid, shopAllowed, normalInstance, mapItemGenerationService, logger, clock, mapChangeService, sessionGroupFactory, sessionRegistry, distanceCalculator); } private async Task LoadPortalsAsync(MapInstance mapInstance, List portals) diff --git a/src/NosCore.GameObject/Services/NRunService/Handlers/ChangeClassHandler.cs b/src/NosCore.GameObject/Services/NRunService/Handlers/ChangeClassHandler.cs index 7482e85f0..c39f162ce 100644 --- a/src/NosCore.GameObject/Services/NRunService/Handlers/ChangeClassHandler.cs +++ b/src/NosCore.GameObject/Services/NRunService/Handlers/ChangeClassHandler.cs @@ -4,7 +4,13 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // +using Microsoft.Extensions.Options; +using NosCore.Algorithm.ExperienceService; +using NosCore.Algorithm.HeroExperienceService; +using NosCore.Algorithm.JobExperienceService; +using NosCore.Core.Configuration; using NosCore.Data.Enumerations.I18N; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.ComponentEntities.Interfaces; using NosCore.GameObject.Networking.ClientSession; using NosCore.Packets.ClientPackets.Npcs; @@ -19,7 +25,9 @@ namespace NosCore.GameObject.Services.NRunService.Handlers { - public class ChangeClassEventHandler(ILogger logger, ILogLanguageLocalizer languageLocalizer) + public class ChangeClassEventHandler(ILogger logger, ILogLanguageLocalizer languageLocalizer, + IOptions worldConfiguration, IExperienceService experienceService, + IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService) : INrunEventHandler { public bool Condition(Tuple item) @@ -72,7 +80,7 @@ await requestData.ClientSession.SendPacketAsync(new SayiPacket return; } - await requestData.ClientSession.Character.ChangeClassAsync(classType); + await requestData.ClientSession.Character.ChangeClassAsync(classType, worldConfiguration, experienceService, jobExperienceService, heroExperienceService); } } } diff --git a/src/NosCore.PacketHandlers/Command/ChangeClassPacketHandler.cs b/src/NosCore.PacketHandlers/Command/ChangeClassPacketHandler.cs index 63c1be28c..d8c1c481c 100644 --- a/src/NosCore.PacketHandlers/Command/ChangeClassPacketHandler.cs +++ b/src/NosCore.PacketHandlers/Command/ChangeClassPacketHandler.cs @@ -4,8 +4,14 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // +using Microsoft.Extensions.Options; +using NosCore.Algorithm.ExperienceService; +using NosCore.Algorithm.HeroExperienceService; +using NosCore.Algorithm.JobExperienceService; +using NosCore.Core.Configuration; using NosCore.Data.CommandPackets; using NosCore.Data.Enumerations; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.Infastructure; using NosCore.GameObject.InterChannelCommunication.Hubs.PubSub; using NosCore.GameObject.InterChannelCommunication.Messages; @@ -18,14 +24,16 @@ namespace NosCore.PacketHandlers.Command { - public class ChangeClassPacketHandler(IPubSubHub pubSubHub) + public class ChangeClassPacketHandler(IPubSubHub pubSubHub, + IOptions worldConfiguration, IExperienceService experienceService, + IJobExperienceService jobExperienceService, IHeroExperienceService heroExperienceService) : PacketHandler, IWorldPacketHandler { public override async Task ExecuteAsync(ChangeClassPacket changeClassPacket, ClientSession session) { if ((changeClassPacket.Name == session.Character.Name) || string.IsNullOrEmpty(changeClassPacket.Name)) { - await session.Character.ChangeClassAsync(changeClassPacket.ClassType); + await session.Character.ChangeClassAsync(changeClassPacket.ClassType, worldConfiguration, experienceService, jobExperienceService, heroExperienceService); return; } diff --git a/src/NosCore.PacketHandlers/Command/SetLevelCommandPacketHandler.cs b/src/NosCore.PacketHandlers/Command/SetLevelCommandPacketHandler.cs index 66f19692b..0c03305e0 100644 --- a/src/NosCore.PacketHandlers/Command/SetLevelCommandPacketHandler.cs +++ b/src/NosCore.PacketHandlers/Command/SetLevelCommandPacketHandler.cs @@ -4,13 +4,18 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // +using NosCore.Algorithm.ExperienceService; +using NosCore.Algorithm.HeroExperienceService; +using NosCore.Algorithm.JobExperienceService; using NosCore.Data.CommandPackets; using NosCore.Data.Enumerations; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.Infastructure; using NosCore.GameObject.InterChannelCommunication.Hubs.ChannelHub; using NosCore.GameObject.InterChannelCommunication.Hubs.PubSub; using NosCore.GameObject.InterChannelCommunication.Messages; using NosCore.GameObject.Networking.ClientSession; +using NosCore.GameObject.Services.BroadcastService; using NosCore.Packets.Enumerations; using NosCore.Packets.ServerPackets.UI; using System.Linq; @@ -19,14 +24,16 @@ namespace NosCore.PacketHandlers.Command { - public class SetLevelCommandPacketHandler(IPubSubHub pubSubHub, IChannelHub channelHub) + public class SetLevelCommandPacketHandler(IPubSubHub pubSubHub, IChannelHub channelHub, + IExperienceService experienceService, IJobExperienceService jobExperienceService, + IHeroExperienceService heroExperienceService, ISessionRegistry sessionRegistry) : PacketHandler, IWorldPacketHandler { public override async Task ExecuteAsync(SetLevelCommandPacket levelPacket, ClientSession session) { if (string.IsNullOrEmpty(levelPacket.Name) || (levelPacket.Name == session.Character.Name)) { - await session.Character.SetLevelAsync(levelPacket.Level); + await session.Character.SetLevelAsync(levelPacket.Level, experienceService, jobExperienceService, heroExperienceService, sessionRegistry); return; } diff --git a/src/NosCore.PacketHandlers/Shops/BuyPacketHandler.cs b/src/NosCore.PacketHandlers/Shops/BuyPacketHandler.cs index 2fa97eebd..d1793bb11 100644 --- a/src/NosCore.PacketHandlers/Shops/BuyPacketHandler.cs +++ b/src/NosCore.PacketHandlers/Shops/BuyPacketHandler.cs @@ -4,7 +4,10 @@ // |_|\__|\__/ |___/ \__/\__/|_|_\___| // +using Microsoft.Extensions.Options; +using NosCore.Core.Configuration; using NosCore.Data.Enumerations.I18N; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.ComponentEntities.Interfaces; using NosCore.GameObject.Infastructure; using NosCore.GameObject.Networking.ClientSession; @@ -18,7 +21,7 @@ namespace NosCore.PacketHandlers.Shops { public class BuyPacketHandler(ILogger logger, ILogLanguageLocalizer logLanguage, - ISessionRegistry sessionRegistry) + ISessionRegistry sessionRegistry, IOptions worldConfiguration) : PacketHandler, IWorldPacketHandler { public override Task ExecuteAsync(BuyPacket buyPacket, ClientSession clientSession) @@ -41,7 +44,7 @@ public override Task ExecuteAsync(BuyPacket buyPacket, ClientSession clientSessi if (aliveEntity != null) { - return clientSession.Character.BuyAsync(aliveEntity.Shop!, buyPacket.Slot, buyPacket.Amount); + return clientSession.Character.BuyAsync(aliveEntity.Shop!, buyPacket.Slot, buyPacket.Amount, worldConfiguration); } logger.Error(logLanguage[LogLanguageKey.VISUALENTITY_DOES_NOT_EXIST]); diff --git a/test/NosCore.GameObject.Tests/GroupTests.cs b/test/NosCore.GameObject.Tests/GroupTests.cs index 543cfc8f7..fe59f4e48 100644 --- a/test/NosCore.GameObject.Tests/GroupTests.cs +++ b/test/NosCore.GameObject.Tests/GroupTests.cs @@ -54,12 +54,8 @@ private Character CreateCharacter(long id = 1, string name = "TestCharacter") new Mock().Object, new HpService(), new MpService(), - new ExperienceService(), - new JobExperienceService(), - new HeroExperienceService(), new ReputationService(), new DignityService(), - TestHelpers.Instance.WorldConfiguration, new Mock().Object, TestHelpers.Instance.SessionRegistry, TestHelpers.Instance.GameLanguageLocalizer) diff --git a/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/ChangeClassTests.cs b/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/ChangeClassTests.cs index ed1ec955e..cf2be9d2d 100644 --- a/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/ChangeClassTests.cs +++ b/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/ChangeClassTests.cs @@ -6,6 +6,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using NosCore.Algorithm.ExperienceService; +using NosCore.Algorithm.HeroExperienceService; +using NosCore.Algorithm.JobExperienceService; using NosCore.Data.Enumerations; using NosCore.GameObject.ComponentEntities.Interfaces; using NosCore.GameObject.Infastructure; @@ -48,7 +51,7 @@ public async Task SetupAsync() Item = TestHelpers.Instance.GenerateItemProvider(); NrRunService = new NrunService( new List, Tuple>> - {new ChangeClassEventHandler(Logger, TestHelpers.Instance.LogLanguageLocalizer)}); + {new ChangeClassEventHandler(Logger, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.WorldConfiguration, new ExperienceService(), new JobExperienceService(), new HeroExperienceService())}); } [DataTestMethod] diff --git a/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/OpenShopHandlerTests.cs b/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/OpenShopHandlerTests.cs index 54b01547a..978feadc5 100644 --- a/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/OpenShopHandlerTests.cs +++ b/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/OpenShopHandlerTests.cs @@ -6,8 +6,10 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using NosCore.Data.Dto; using NosCore.Data.StaticEntities; using NosCore.GameObject.ComponentEntities.Entities; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.ComponentEntities.Interfaces; using NosCore.GameObject.Networking; using NosCore.GameObject.Networking.ClientSession; @@ -84,13 +86,9 @@ public async Task ExecutingShouldNotThrow() private void NpcExists() { - Npc = new MapNpc( - TestHelpers.Instance.GenerateItemProvider(), - Logger, - TestHelpers.Instance.DistanceCalculator, - TestHelpers.Instance.Clock); + Npc = new MapNpc(); Npc.MapNpcId = 1; - Npc.Initialize(new NpcMonsterDto { NpcMonsterVNum = 1 }, null, null, new List()); + Npc.Initialize(new NpcMonsterDto { NpcMonsterVNum = 1 }, null, null, new List(), TestHelpers.Instance.GenerateItemProvider()); } private void CheckingConditionWithOpenShopRunner() diff --git a/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/TeleporterHandlerTests.cs b/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/TeleporterHandlerTests.cs index 4e3484004..1fd005f74 100644 --- a/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/TeleporterHandlerTests.cs +++ b/test/NosCore.GameObject.Tests/Services/NRunService/Handlers/TeleporterHandlerTests.cs @@ -6,8 +6,10 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using NosCore.Data.Dto; using NosCore.Data.StaticEntities; using NosCore.GameObject.ComponentEntities.Entities; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.ComponentEntities.Interfaces; using NosCore.GameObject.Networking; using NosCore.GameObject.Networking.ClientSession; @@ -114,26 +116,18 @@ public async Task TeleportWithoutEnoughGoldShouldNotChangeMap() private void NpcWithTeleportDialog() { - Npc = new MapNpc( - TestHelpers.Instance.GenerateItemProvider(), - Logger, - TestHelpers.Instance.DistanceCalculator, - TestHelpers.Instance.Clock); + Npc = new MapNpc(); Npc.MapNpcId = 1; Npc.Dialog = 439; - Npc.Initialize(new NpcMonsterDto { NpcMonsterVNum = 1 }, null, null, new List()); + Npc.Initialize(new NpcMonsterDto { NpcMonsterVNum = 1 }, null, null, new List(), TestHelpers.Instance.GenerateItemProvider()); } private void NpcWithInvalidDialog() { - Npc = new MapNpc( - TestHelpers.Instance.GenerateItemProvider(), - Logger, - TestHelpers.Instance.DistanceCalculator, - TestHelpers.Instance.Clock); + Npc = new MapNpc(); Npc.MapNpcId = 1; Npc.Dialog = 1; - Npc.Initialize(new NpcMonsterDto { NpcMonsterVNum = 1 }, null, null, new List()); + Npc.Initialize(new NpcMonsterDto { NpcMonsterVNum = 1 }, null, null, new List(), TestHelpers.Instance.GenerateItemProvider()); } private void CharacterHasEnoughGold() diff --git a/test/NosCore.GameObject.Tests/ShopTests.cs b/test/NosCore.GameObject.Tests/ShopTests.cs index 3bece10c4..07f0e44ea 100644 --- a/test/NosCore.GameObject.Tests/ShopTests.cs +++ b/test/NosCore.GameObject.Tests/ShopTests.cs @@ -7,6 +7,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using NosCore.Data.Enumerations; using NosCore.Data.StaticEntities; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.Infastructure; using NosCore.GameObject.Services.EventLoaderService; using NosCore.GameObject.Services.InventoryService; @@ -162,7 +163,7 @@ private async Task AttemptingToBuyFromWrongSlotAsync() { var itemBuilder = CreateItemBuilder(); var shop = CreateShop(itemBuilder); - await Session.Character.BuyAsync(shop, 1, 99); + await Session.Character.BuyAsync(shop, 1, 99, TestHelpers.Instance.WorldConfiguration); } @@ -178,7 +179,7 @@ private async Task AttemptingToBuyMoreThanAvailableAsync() Amount = 98 }); var shop = new Shop { ShopItems = list }; - await Session.Character.BuyAsync(shop, 0, 99); + await Session.Character.BuyAsync(shop, 0, 99, TestHelpers.Instance.WorldConfiguration); } private async Task AttemptingToBuy_ItemsAsync(int value) @@ -186,7 +187,7 @@ private async Task AttemptingToBuy_ItemsAsync(int value) var itemBuilder = CreateItemBuilder(); var shop = CreateShop(itemBuilder); - await Session.Character.BuyAsync(shop, 0, 99); + await Session.Character.BuyAsync(shop, 0, 99, TestHelpers.Instance.WorldConfiguration); } private void ShouldReceiveNotEnoughGoldMessage() @@ -200,7 +201,7 @@ private async Task AttemptingToBuyReputationItemAsync() { var itemBuilder = CreateItemBuilder(0, 500000); var shop = CreateShop(itemBuilder); - await Session.Character.BuyAsync(shop, 0, 99); + await Session.Character.BuyAsync(shop, 0, 99, TestHelpers.Instance.WorldConfiguration); } private void ShouldReceiveReputationError() @@ -229,7 +230,7 @@ private void CharacterHasGoldButFullInventory() private async Task AttemptingToBuyWithFullInventoryAsync() { var shop = CreateShop(ItemBuilder, -1, 1); - await Session.Character.BuyAsync(shop, 0, 999); + await Session.Character.BuyAsync(shop, 0, 999, TestHelpers.Instance.WorldConfiguration); } private void ShouldReceiveNotEnoughSpaceMessage() @@ -256,7 +257,7 @@ private void CharacterHasGoldAndPartialInventory() private async Task Buying998ItemsAt1GoldEachAsync() { var shop = CreateShop(ItemBuilder); - await Session.Character.BuyAsync(shop, 0, 998); + await Session.Character.BuyAsync(shop, 0, 998, TestHelpers.Instance.WorldConfiguration); } private void AllInventorySlotsShouldHave_Items(int value) @@ -290,7 +291,7 @@ private async Task Buying998ItemsAt1ReputationEachAsync() var list = new ConcurrentDictionary(); list.TryAdd(0, new ShopItem { Slot = 0, ItemInstance = ItemBuilder.Create(1), Type = 0 }); var shop = new Shop { ShopItems = list }; - await Session.Character.BuyAsync(shop, 0, 998); + await Session.Character.BuyAsync(shop, 0, 998, TestHelpers.Instance.WorldConfiguration); } private void ReputationShouldBeDeducted() diff --git a/test/NosCore.PacketHandlers.Tests/CharacterScreen/CharNewPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/CharacterScreen/CharNewPacketHandlerTests.cs index 96f81722a..6952655cd 100644 --- a/test/NosCore.PacketHandlers.Tests/CharacterScreen/CharNewPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/CharacterScreen/CharNewPacketHandlerTests.cs @@ -113,7 +113,7 @@ private async Task CharacterIsInGame() Session.Character.MapInstance = new MapInstance(new Map(), new Guid(), true, MapInstanceType.BaseMapInstance, new MapItemGenerationService(new EventLoaderService, IGetMapItemEventHandler>(new List>>()), idServer), - Logger, TestHelpers.Instance.Clock, MapChangeService.Object, new Mock().Object, TestHelpers.Instance.SessionRegistry); + Logger, TestHelpers.Instance.Clock, MapChangeService.Object, new Mock().Object, TestHelpers.Instance.SessionRegistry, TestHelpers.Instance.DistanceCalculator); } private async Task CreatingCharacterViaPacket() diff --git a/test/NosCore.PacketHandlers.Tests/CharacterScreen/CharRenPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/CharacterScreen/CharRenPacketHandlerTests.cs index 3994ebb29..5d948cdb0 100644 --- a/test/NosCore.PacketHandlers.Tests/CharacterScreen/CharRenPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/CharacterScreen/CharRenPacketHandlerTests.cs @@ -102,7 +102,7 @@ private async Task CharacterIsInGame() Session.Character.MapInstance = new MapInstance(new Map(), new Guid(), true, MapInstanceType.BaseMapInstance, new MapItemGenerationService(new EventLoaderService, IGetMapItemEventHandler>(new List>>()), idServer), - Logger, TestHelpers.Instance.Clock, MapChangeService.Object, new Mock().Object, TestHelpers.Instance.SessionRegistry); + Logger, TestHelpers.Instance.Clock, MapChangeService.Object, new Mock().Object, TestHelpers.Instance.SessionRegistry, TestHelpers.Instance.DistanceCalculator); } private async Task CharacterIsNotFlaggedForRename() diff --git a/test/NosCore.PacketHandlers.Tests/Command/ChangeClassPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Command/ChangeClassPacketHandlerTests.cs index e9d562ffd..e38c4982d 100644 --- a/test/NosCore.PacketHandlers.Tests/Command/ChangeClassPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Command/ChangeClassPacketHandlerTests.cs @@ -6,6 +6,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using NosCore.Algorithm.ExperienceService; +using NosCore.Algorithm.HeroExperienceService; +using NosCore.Algorithm.JobExperienceService; using NosCore.Data.CommandPackets; using NosCore.Data.WebApi; using NosCore.GameObject.InterChannelCommunication.Hubs.PubSub; @@ -42,7 +45,8 @@ public async Task SetupAsync() PubSubHub.Setup(x => x.GetSubscribersAsync()) .Returns(Task.FromResult(new List())); - Handler = new ChangeClassPacketHandler(PubSubHub.Object); + Handler = new ChangeClassPacketHandler(PubSubHub.Object, + TestHelpers.Instance.WorldConfiguration, new ExperienceService(), new JobExperienceService(), new HeroExperienceService()); } [TestMethod] diff --git a/test/NosCore.PacketHandlers.Tests/Command/SetLevelCommandPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Command/SetLevelCommandPacketHandlerTests.cs index 9cff52767..6c5699741 100644 --- a/test/NosCore.PacketHandlers.Tests/Command/SetLevelCommandPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Command/SetLevelCommandPacketHandlerTests.cs @@ -6,6 +6,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using NosCore.Algorithm.ExperienceService; +using NosCore.Algorithm.HeroExperienceService; +using NosCore.Algorithm.JobExperienceService; using NosCore.Core; using NosCore.Data.CommandPackets; using NosCore.Data.WebApi; @@ -48,7 +51,8 @@ public async Task SetupAsync() ChannelHub.Setup(x => x.GetCommunicationChannels()) .Returns(Task.FromResult(new List())); - Handler = new SetLevelCommandPacketHandler(PubSubHub.Object, ChannelHub.Object); + Handler = new SetLevelCommandPacketHandler(PubSubHub.Object, ChannelHub.Object, + new ExperienceService(), new JobExperienceService(), new HeroExperienceService(), TestHelpers.Instance.SessionRegistry); } [TestMethod] diff --git a/test/NosCore.PacketHandlers.Tests/Command/SizePacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Command/SizePacketHandlerTests.cs index 0b99e6aae..fd945b02f 100644 --- a/test/NosCore.PacketHandlers.Tests/Command/SizePacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Command/SizePacketHandlerTests.cs @@ -72,7 +72,6 @@ public async Task SizeOnNonExistentEntityShouldLogError() private void CharacterIsOnMap() { Session.Character.MapInstance = TestHelpers.Instance.MapInstanceAccessorService.GetBaseMapById(1)!; - Session.Character.Size = 10; Session.LastPackets.Clear(); } diff --git a/test/NosCore.PacketHandlers.Tests/Friend/FinsPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Friend/FinsPacketHandlerTests.cs index dfd9d6f9a..4f7cc78e2 100644 --- a/test/NosCore.PacketHandlers.Tests/Friend/FinsPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Friend/FinsPacketHandlerTests.cs @@ -53,8 +53,7 @@ public class FinsPacketHandlerTests public async Task SetupAsync() { TypeAdapterConfig.NewConfig() - .ConstructUsing(src => new MapNpc(null, new Mock().Object, - TestHelpers.Instance.DistanceCalculator, TestHelpers.Instance.Clock)); + .ConstructUsing(src => new MapNpc()); Broadcaster.Reset(); await TestHelpers.ResetAsync(); Fixture = new NosCoreFixture(); diff --git a/test/NosCore.PacketHandlers.Tests/Group/PleavePacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Group/PleavePacketHandlerTests.cs index 1be530068..fc920100a 100644 --- a/test/NosCore.PacketHandlers.Tests/Group/PleavePacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Group/PleavePacketHandlerTests.cs @@ -53,7 +53,9 @@ public async Task SetupAsync() session.Character.Group.JoinGroup(session.Character); } - PLeavePacketHandler = new PleavePacketHandler(idServer, TestHelpers.Instance.SessionRegistry, new Mock().Object); + var sessionGroupFactoryForHandler = new Mock(); + sessionGroupFactoryForHandler.Setup(x => x.Create()).Returns(new Mock().Object); + PLeavePacketHandler = new PleavePacketHandler(idServer, TestHelpers.Instance.SessionRegistry, sessionGroupFactoryForHandler.Object); var mock = new Mock(); PJoinPacketHandler = new PjoinPacketHandler(Logger, mock.Object, TestHelpers.Instance.Clock, idServer, TestHelpers.Instance.LogLanguageLocalizer, TestHelpers.Instance.GameLanguageLocalizer, TestHelpers.Instance.SessionRegistry); diff --git a/test/NosCore.PacketHandlers.Tests/Miniland/AddobjPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Miniland/AddobjPacketHandlerTests.cs index 2ba704cc4..9fe028356 100644 --- a/test/NosCore.PacketHandlers.Tests/Miniland/AddobjPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Miniland/AddobjPacketHandlerTests.cs @@ -54,7 +54,7 @@ public class AddobjPacketHandlerTests public async Task SetupAsync() { TypeAdapterConfig.NewConfig() - .ConstructUsing(src => new MapNpc(null, Logger, TestHelpers.Instance.DistanceCalculator, TestHelpers.Instance.Clock)); + .ConstructUsing(src => new MapNpc()); await TestHelpers.ResetAsync(); _session = await TestHelpers.Instance.GenerateSessionAsync(); await TestHelpers.Instance.MinilandDao.TryInsertOrUpdateAsync(new MinilandDto() diff --git a/test/NosCore.PacketHandlers.Tests/Miniland/MJoinPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Miniland/MJoinPacketHandlerTests.cs index f4a6311ff..2a6c5a529 100644 --- a/test/NosCore.PacketHandlers.Tests/Miniland/MJoinPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Miniland/MJoinPacketHandlerTests.cs @@ -47,7 +47,7 @@ public class MJoinPacketHandlerTests public async Task SetupAsync() { TypeAdapterConfig.NewConfig() - .ConstructUsing(src => new MapNpc(null, Logger, TestHelpers.Instance.DistanceCalculator, TestHelpers.Instance.Clock)); + .ConstructUsing(src => new MapNpc()); Broadcaster.Reset(); await TestHelpers.ResetAsync(); Session = await TestHelpers.Instance.GenerateSessionAsync(); diff --git a/test/NosCore.PacketHandlers.Tests/Miniland/MinilandObjects/MgPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Miniland/MinilandObjects/MgPacketHandlerTests.cs index 14d9ee8a4..990074680 100644 --- a/test/NosCore.PacketHandlers.Tests/Miniland/MinilandObjects/MgPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Miniland/MinilandObjects/MgPacketHandlerTests.cs @@ -57,7 +57,7 @@ public class MgPacketHandlerTests public async Task SetupAsync() { TypeAdapterConfig.NewConfig() - .ConstructUsing(src => new MapNpc(null, Logger, TestHelpers.Instance.DistanceCalculator, TestHelpers.Instance.Clock)); + .ConstructUsing(src => new MapNpc()); await TestHelpers.ResetAsync(); _session = await TestHelpers.Instance.GenerateSessionAsync(); await TestHelpers.Instance.MinilandDao.TryInsertOrUpdateAsync(new MinilandDto() diff --git a/test/NosCore.PacketHandlers.Tests/Miniland/MinilandObjects/UseobjPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Miniland/MinilandObjects/UseobjPacketHandlerTests.cs index 9a6014472..7b2588490 100644 --- a/test/NosCore.PacketHandlers.Tests/Miniland/MinilandObjects/UseobjPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Miniland/MinilandObjects/UseobjPacketHandlerTests.cs @@ -62,7 +62,7 @@ public class UseobjPacketHandlerTests public async Task SetupAsync() { TypeAdapterConfig.NewConfig() - .ConstructUsing(src => new MapNpc(null, Logger, TestHelpers.Instance.DistanceCalculator, TestHelpers.Instance.Clock)); + .ConstructUsing(src => new MapNpc()); await TestHelpers.ResetAsync(); _session = await TestHelpers.Instance.GenerateSessionAsync(); await TestHelpers.Instance.MinilandDao.TryInsertOrUpdateAsync(new MinilandDto() diff --git a/test/NosCore.PacketHandlers.Tests/Miniland/MlEditPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Miniland/MlEditPacketHandlerTests.cs index e947cfd4c..1bc95d2b9 100644 --- a/test/NosCore.PacketHandlers.Tests/Miniland/MlEditPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Miniland/MlEditPacketHandlerTests.cs @@ -42,7 +42,7 @@ public class MlEditPacketHandlerTests public async Task SetupAsync() { TypeAdapterConfig.NewConfig() - .ConstructUsing(src => new MapNpc(null, Logger, TestHelpers.Instance.DistanceCalculator, TestHelpers.Instance.Clock)); + .ConstructUsing(src => new MapNpc()); await TestHelpers.ResetAsync(); Session = await TestHelpers.Instance.GenerateSessionAsync(); Session2 = await TestHelpers.Instance.GenerateSessionAsync(); diff --git a/test/NosCore.PacketHandlers.Tests/Miniland/RmvobjPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Miniland/RmvobjPacketHandlerTests.cs index ea0eb31cf..01312f9b8 100644 --- a/test/NosCore.PacketHandlers.Tests/Miniland/RmvobjPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Miniland/RmvobjPacketHandlerTests.cs @@ -54,7 +54,7 @@ public class RmvobjPacketHandlerTests public async Task SetupAsync() { TypeAdapterConfig.NewConfig() - .ConstructUsing(src => new MapNpc(null, Logger, TestHelpers.Instance.DistanceCalculator, TestHelpers.Instance.Clock)); + .ConstructUsing(src => new MapNpc()); await TestHelpers.ResetAsync(); _session = await TestHelpers.Instance.GenerateSessionAsync(); await TestHelpers.Instance.MinilandDao.TryInsertOrUpdateAsync(new MinilandDto() diff --git a/test/NosCore.PacketHandlers.Tests/Shops/BuyPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Shops/BuyPacketHandlerTests.cs index 3ee7bef67..aa458f461 100644 --- a/test/NosCore.PacketHandlers.Tests/Shops/BuyPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Shops/BuyPacketHandlerTests.cs @@ -34,7 +34,8 @@ public async Task SetupAsync() Handler = new BuyPacketHandler( Logger, TestHelpers.Instance.LogLanguageLocalizer, - TestHelpers.Instance.SessionRegistry); + TestHelpers.Instance.SessionRegistry, + TestHelpers.Instance.WorldConfiguration); } [TestMethod] diff --git a/test/NosCore.PacketHandlers.Tests/Shops/NrunPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Shops/NrunPacketHandlerTests.cs index 3d6cb9218..ce9d962f0 100644 --- a/test/NosCore.PacketHandlers.Tests/Shops/NrunPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Shops/NrunPacketHandlerTests.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.ComponentEntities.Interfaces; using NosCore.GameObject.Networking; using NosCore.GameObject.Networking.ClientSession; @@ -94,16 +95,12 @@ public async Task NrunWithExistingPlayerShouldCallNrunService() private void NpcExistsOnMap() { - var npc = new NosCore.GameObject.ComponentEntities.Entities.MapNpc( - TestHelpers.Instance.GenerateItemProvider(), - Logger, - TestHelpers.Instance.DistanceCalculator, - TestHelpers.Instance.Clock); + var npc = new NosCore.GameObject.ComponentEntities.Entities.MapNpc(); npc.MapNpcId = 100; npc.MapId = _session.Character.MapInstance.Map.MapId; npc.MapX = 1; npc.MapY = 1; - npc.Initialize(new NosCore.Data.StaticEntities.NpcMonsterDto { NpcMonsterVNum = 1 }, null, null, new List()); + npc.Initialize(new NosCore.Data.StaticEntities.NpcMonsterDto { NpcMonsterVNum = 1 }, null, null, new List(), TestHelpers.Instance.GenerateItemProvider()); _session.Character.MapInstance.LoadNpcs(new List { npc }); } diff --git a/test/NosCore.PacketHandlers.Tests/Shops/RequestNpcPacketHandlerTests.cs b/test/NosCore.PacketHandlers.Tests/Shops/RequestNpcPacketHandlerTests.cs index 902c4abb7..d9b2909b3 100644 --- a/test/NosCore.PacketHandlers.Tests/Shops/RequestNpcPacketHandlerTests.cs +++ b/test/NosCore.PacketHandlers.Tests/Shops/RequestNpcPacketHandlerTests.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using NosCore.GameObject.ComponentEntities.Extensions; using NosCore.GameObject.Networking; using NosCore.GameObject.Networking.ClientSession; using NosCore.PacketHandlers.Shops; @@ -68,17 +69,13 @@ public async Task RequestNpcWithExistingNpcShouldNotThrow() private void NpcWithDialogExistsOnMap() { - var npc = new NosCore.GameObject.ComponentEntities.Entities.MapNpc( - TestHelpers.Instance.GenerateItemProvider(), - Logger, - TestHelpers.Instance.DistanceCalculator, - TestHelpers.Instance.Clock); + var npc = new NosCore.GameObject.ComponentEntities.Entities.MapNpc(); npc.MapNpcId = 100; npc.Dialog = 100; npc.MapId = _session.Character.MapInstance.Map.MapId; npc.MapX = 1; npc.MapY = 1; - npc.Initialize(new NosCore.Data.StaticEntities.NpcMonsterDto { NpcMonsterVNum = 1 }, null, null, new List()); + npc.Initialize(new NosCore.Data.StaticEntities.NpcMonsterDto { NpcMonsterVNum = 1 }, null, null, new List(), TestHelpers.Instance.GenerateItemProvider()); _session.Character.MapInstance.Npcs.Add(npc); } diff --git a/test/NosCore.Tests.Shared/TestHelpers.cs b/test/NosCore.Tests.Shared/TestHelpers.cs index 642ae41b3..45a857935 100644 --- a/test/NosCore.Tests.Shared/TestHelpers.cs +++ b/test/NosCore.Tests.Shared/TestHelpers.cs @@ -248,10 +248,10 @@ private async Task GenerateMapInstanceProviderAsync() MapItemProvider, MapNpcDao, MapMonsterDao, PortalDao, ShopItemDao, Logger, new EventLoaderService(new List>()), - mapInstanceRegistry, MapInstanceAccessorService, Instance.Clock, Instance.LogLanguageLocalizer, mapChangeService, sessionGroupFactory, SessionRegistry); + mapInstanceRegistry, MapInstanceAccessorService, Instance.Clock, Instance.LogLanguageLocalizer, mapChangeService, sessionGroupFactory, SessionRegistry, GenerateItemProvider(), Instance.DistanceCalculator); await instanceGeneratorService.InitializeAsync(); await instanceGeneratorService.AddMapInstanceAsync(new MapInstance(miniland, MinilandId, false, - MapInstanceType.NormalInstance, MapItemProvider, Logger, Clock, mapChangeService, sessionGroupFactory, SessionRegistry)); + MapInstanceType.NormalInstance, MapItemProvider, Logger, Clock, mapChangeService, sessionGroupFactory, SessionRegistry, Instance.DistanceCalculator)); MapInstanceGeneratorService = instanceGeneratorService; } @@ -289,9 +289,9 @@ public void InitDatabase() TypeAdapterConfig.GlobalSettings.AllowImplicitSourceInheritance = false; TypeAdapterConfig.GlobalSettings.ForDestinationType().Ignore(s => s.ValidationResult); TypeAdapterConfig.NewConfig() - .ConstructUsing(src => new GameObject.ComponentEntities.Entities.MapNpc(GenerateItemProvider(), Logger, Instance.DistanceCalculator, Instance.Clock)); + .ConstructUsing(src => new GameObject.ComponentEntities.Entities.MapNpc()); TypeAdapterConfig.NewConfig() - .ConstructUsing(src => new GameObject.ComponentEntities.Entities.MapMonster(Logger, Instance.DistanceCalculator, Instance.Clock, new Mock().Object)); + .ConstructUsing(src => new GameObject.ComponentEntities.Entities.MapMonster(new Mock().Object)); } @@ -337,9 +337,9 @@ public async Task GenerateSessionAsync(List? pack }; var chara = new GameObject.ComponentEntities.Entities.Character(new InventoryService(ItemList, WorldConfiguration, Logger), - new ExchangeService(new Mock().Object, WorldConfiguration, Logger, new ExchangeRequestRegistry(), Instance.LogLanguageLocalizer, Instance.GameLanguageLocalizer), new Mock().Object, new HpService(), new MpService(), new ExperienceService(), new JobExperienceService(), - new HeroExperienceService(), new ReputationService(), new DignityService(), - Instance.WorldConfiguration, new Mock().Object, Instance.SessionRegistry, Instance.GameLanguageLocalizer) + new ExchangeService(new Mock().Object, WorldConfiguration, Logger, new ExchangeRequestRegistry(), Instance.LogLanguageLocalizer, Instance.GameLanguageLocalizer), new Mock().Object, new HpService(), new MpService(), + new ReputationService(), new DignityService(), + new Mock().Object, Instance.SessionRegistry, Instance.GameLanguageLocalizer) { CharacterId = LastId, Name = "TestExistingCharacter" + LastId, From 1f31d5020fe5fc5d02d9e34d781554918d136238 Mon Sep 17 00:00:00 2001 From: erwan-joly Date: Tue, 20 Jan 2026 20:53:57 +1300 Subject: [PATCH 3/4] add tests on parser and improve --- .../Extensions/NonPlayableEntityExtension.cs | 112 +++++++ src/NosCore.Parser/Parsers/CardParser.cs | 29 +- .../Parsers/Generic/FluentParserBuilder.cs | 136 ++++++++ src/NosCore.Parser/Parsers/ItemParser.cs | 137 ++++----- .../Parsers/NpcMonsterParser.cs | 109 ++++--- src/NosCore.Parser/Parsers/QuestParser.cs | 44 ++- src/NosCore.Parser/Parsers/SkillParser.cs | 69 ++--- test/NosCore.Parser.Tests/ActParserTests.cs | 172 +++++++++++ test/NosCore.Parser.Tests/CardParserTests.cs | 163 ++++++++++ .../FluentParserBuilderTests.cs | 212 +++++++++++++ .../GenericParserTests.cs | 159 ++++++++++ test/NosCore.Parser.Tests/ItemParserTests.cs | 291 ++++++++++++++++++ .../MapMonsterParserTests.cs | 154 +++++++++ .../NosCore.Parser.Tests/MapNpcParserTests.cs | 163 ++++++++++ .../NosCore.Parser.Tests.csproj | 39 +++ .../NosCore.Parser.Tests/PortalParserTests.cs | 134 ++++++++ test/NosCore.Parser.Tests/QuestParserTests.cs | 198 ++++++++++++ test/NosCore.Parser.Tests/ShopParserTests.cs | 146 +++++++++ test/NosCore.Parser.Tests/SkillParserTests.cs | 196 ++++++++++++ 19 files changed, 2461 insertions(+), 202 deletions(-) create mode 100644 src/NosCore.GameObject/ComponentEntities/Extensions/NonPlayableEntityExtension.cs create mode 100644 src/NosCore.Parser/Parsers/Generic/FluentParserBuilder.cs create mode 100644 test/NosCore.Parser.Tests/ActParserTests.cs create mode 100644 test/NosCore.Parser.Tests/CardParserTests.cs create mode 100644 test/NosCore.Parser.Tests/FluentParserBuilderTests.cs create mode 100644 test/NosCore.Parser.Tests/GenericParserTests.cs create mode 100644 test/NosCore.Parser.Tests/ItemParserTests.cs create mode 100644 test/NosCore.Parser.Tests/MapMonsterParserTests.cs create mode 100644 test/NosCore.Parser.Tests/MapNpcParserTests.cs create mode 100644 test/NosCore.Parser.Tests/NosCore.Parser.Tests.csproj create mode 100644 test/NosCore.Parser.Tests/PortalParserTests.cs create mode 100644 test/NosCore.Parser.Tests/QuestParserTests.cs create mode 100644 test/NosCore.Parser.Tests/ShopParserTests.cs create mode 100644 test/NosCore.Parser.Tests/SkillParserTests.cs diff --git a/src/NosCore.GameObject/ComponentEntities/Extensions/NonPlayableEntityExtension.cs b/src/NosCore.GameObject/ComponentEntities/Extensions/NonPlayableEntityExtension.cs new file mode 100644 index 000000000..a2ce2af8f --- /dev/null +++ b/src/NosCore.GameObject/ComponentEntities/Extensions/NonPlayableEntityExtension.cs @@ -0,0 +1,112 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using Mapster; +using NodaTime; +using NosCore.Data.Dto; +using NosCore.Data.StaticEntities; +using NosCore.GameObject.ComponentEntities.Entities; +using NosCore.GameObject.ComponentEntities.Interfaces; +using NosCore.GameObject.Networking.ClientSession; +using NosCore.GameObject.Services.ItemGenerationService; +using NosCore.GameObject.Services.NRunService; +using NosCore.GameObject.Services.ShopService; +using NosCore.PathFinder.Interfaces; +using Serilog; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Threading.Tasks; + +namespace NosCore.GameObject.ComponentEntities.Extensions +{ + public static class NonPlayableEntityExtension + { + public static void Initialize(this INonPlayableEntity entity, NpcMonsterDto npcMonster) + { + entity.NpcMonster = npcMonster; + entity.Mp = npcMonster?.MaxMp ?? 0; + entity.Hp = npcMonster?.MaxHp ?? 0; + entity.PositionX = entity.MapX; + entity.PositionY = entity.MapY; + entity.IsAlive = true; + entity.Level = npcMonster?.Level ?? 0; + } + + public static void Initialize(this INonPlayableEntity entity, NpcMonsterDto npcMonster, + ShopDto? shopDto, NpcTalkDto? npcTalkDto, List shopItemsDto, + IItemGenerationService itemProvider) + { + entity.NpcMonster = npcMonster; + entity.Mp = npcMonster?.MaxMp ?? 0; + entity.Hp = npcMonster?.MaxHp ?? 0; + entity.Speed = npcMonster?.Speed ?? 0; + entity.PositionX = entity.MapX; + entity.PositionY = entity.MapY; + entity.IsAlive = true; + + if (entity is IRequestableEntity requestableEntity && entity is MapNpc mapNpc) + { + Task RequestExecAsync(RequestData request) + { + return entity.ShowDialogAsync(request, mapNpc.Dialog ?? 0); + } + requestableEntity.Requests[typeof(INrunEventHandler)]?.Select(RequestExecAsync).Subscribe(); + } + + if (shopDto == null) + { + return; + } + + var shopItemsList = new ConcurrentDictionary(); + Parallel.ForEach(shopItemsDto, shopItemGrouping => + { + var shopItem = shopItemGrouping.Adapt(); + shopItem.ItemInstance = itemProvider.Create(shopItemGrouping.ItemVNum, -1); + shopItemsList[shopItemGrouping.ShopItemId] = shopItem; + }); + entity.Shop = shopDto.Adapt(); + entity.Shop.Name = npcTalkDto?.Name ?? new I18NString(); + entity.Shop.OwnerCharacter = null; + entity.Shop.ShopItems = shopItemsList; + } + + public static void StopLife(this INonPlayableEntity entity) + { + entity.Life?.Dispose(); + entity.Life = null; + } + + public static Task StartLifeAsync(this INonPlayableEntity entity, IHeuristic distanceCalculator, IClock clock, ILogger logger) + { + entity.Life?.Dispose(); + + async Task LifeAsync() + { + try + { + if (!entity.MapInstance.IsSleeping) + { + await entity.MoveAsync(distanceCalculator, clock); + } + } + catch (Exception e) + { + logger.Error(e.Message, e); + } + } + entity.Life = Observable.Interval(TimeSpan.FromMilliseconds(400)).Select(_ => LifeAsync()).Subscribe(); + return Task.CompletedTask; + } + + public static Task ShowDialogAsync(this INonPlayableEntity entity, RequestData requestData, long dialog) + { + return requestData.ClientSession.SendPacketAsync(AliveEntityExtension.GenerateNpcReq(entity, dialog)); + } + } +} diff --git a/src/NosCore.Parser/Parsers/CardParser.cs b/src/NosCore.Parser/Parsers/CardParser.cs index eede1d379..e1540df4b 100644 --- a/src/NosCore.Parser/Parsers/CardParser.cs +++ b/src/NosCore.Parser/Parsers/CardParser.cs @@ -39,22 +39,19 @@ public class CardParser(IDao cardDao, IDao bcar public async Task InsertCardsAsync(string folder) { - var actionList = new Dictionary, object?>> - { - {nameof(CardDto.CardId), chunk => Convert.ToInt16(chunk["VNUM"][0][2])}, - {nameof(CardDto.NameI18NKey), chunk => chunk["NAME"][0][2]}, - {nameof(CardDto.Level), chunk => Convert.ToByte(chunk["GROUP"][0][3])}, - {nameof(CardDto.EffectId), chunk => Convert.ToInt32(chunk["EFFECT"][0][2])}, - {nameof(CardDto.BuffType), chunk => (BCardType.CardType) Convert.ToByte(chunk["STYLE"][0][3])}, - {nameof(CardDto.Duration), chunk => Convert.ToInt32(chunk["TIME"][0][2])}, - {nameof(CardDto.Delay), chunk => Convert.ToInt32(chunk["TIME"][0][3])}, - {nameof(CardDto.BCards), AddBCards}, - {nameof(CardDto.TimeoutBuff), chunk => Convert.ToInt16(chunk["LAST"][0][2])}, - {nameof(CardDto.TimeoutBuffChance), chunk => Convert.ToByte(chunk["LAST"][0][3])} - }; - var genericParser = new GenericParser(folder + _fileCardDat, - "END", 1, actionList, logger, logLanguage); - var cards = (await genericParser.GetDtosAsync()).GroupBy(p => p.CardId).Select(g => g.First()).ToList(); + var parser = FluentParserBuilder.Create(folder + _fileCardDat, "END", 1) + .Field(x => x.CardId, chunk => Convert.ToInt16(chunk["VNUM"][0][2])) + .Field(x => x.NameI18NKey, chunk => chunk["NAME"][0][2]) + .Field(x => x.Level, chunk => Convert.ToByte(chunk["GROUP"][0][3])) + .Field(x => x.EffectId, chunk => Convert.ToInt32(chunk["EFFECT"][0][2])) + .Field(x => x.BuffType, chunk => (BCardType.CardType)Convert.ToByte(chunk["STYLE"][0][3])) + .Field(x => x.Duration, chunk => Convert.ToInt32(chunk["TIME"][0][2])) + .Field(x => x.Delay, chunk => Convert.ToInt32(chunk["TIME"][0][3])) + .Field(x => x.BCards, chunk => AddBCards(chunk)) + .Field(x => x.TimeoutBuff, chunk => Convert.ToInt16(chunk["LAST"][0][2])) + .Field(x => x.TimeoutBuffChance, chunk => Convert.ToByte(chunk["LAST"][0][3])) + .Build(logger, logLanguage); + var cards = (await parser.GetDtosAsync()).GroupBy(p => p.CardId).Select(g => g.First()).ToList(); await cardDao.TryInsertOrUpdateAsync(cards); await bcardDao.TryInsertOrUpdateAsync(cards.Where(s => s.BCards != null).SelectMany(s => s.BCards)); diff --git a/src/NosCore.Parser/Parsers/Generic/FluentParserBuilder.cs b/src/NosCore.Parser/Parsers/Generic/FluentParserBuilder.cs new file mode 100644 index 000000000..89534eb5c --- /dev/null +++ b/src/NosCore.Parser/Parsers/Generic/FluentParserBuilder.cs @@ -0,0 +1,136 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using NosCore.Data.Enumerations.I18N; +using NosCore.Shared.I18N; +using Serilog; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace NosCore.Parser.Parsers.Generic +{ + public class FluentParserBuilder where T : new() + { + private readonly string _fileAddress; + private readonly string _endPattern; + private readonly int _firstIndex; + private readonly Dictionary, object?>> _actionList = new(); + private string _splitter = "\t"; + + private FluentParserBuilder(string fileAddress, string endPattern, int firstIndex = 1) + { + _fileAddress = fileAddress; + _endPattern = endPattern; + _firstIndex = firstIndex; + } + + public static FluentParserBuilder Create(string fileAddress, string endPattern, int firstIndex = 1) + { + return new FluentParserBuilder(fileAddress, endPattern, firstIndex); + } + + public FluentParserBuilder WithSplitter(string splitter) + { + _splitter = splitter; + return this; + } + + public FluentParserBuilder Field( + Expression> propertyExpression, + Func, object?> extractor) + { + var propertyName = GetPropertyName(propertyExpression); + _actionList[propertyName] = extractor; + return this; + } + + public FluentParserBuilder Field( + Expression> propertyExpression, + string section, + int row, + int column) + { + var propertyName = GetPropertyName(propertyExpression); + _actionList[propertyName] = chunk => ConvertValue(chunk[section][row][column]); + return this; + } + + public FluentParserBuilder Field( + Expression> propertyExpression, + string section, + int row, + int column, + Func converter) + { + var propertyName = GetPropertyName(propertyExpression); + _actionList[propertyName] = chunk => converter(chunk[section][row][column]); + return this; + } + + public FluentParser Build(ILogger logger, ILogLanguageLocalizer logLanguage) + { + return new FluentParser(_fileAddress, _endPattern, _firstIndex, _actionList, _splitter, logger, logLanguage); + } + + private static string GetPropertyName(Expression> expression) + { + if (expression.Body is MemberExpression memberExpression) + { + return memberExpression.Member.Name; + } + throw new ArgumentException("Expression must be a member expression", nameof(expression)); + } + + private static object? ConvertValue(string value) + { + var targetType = typeof(TProperty); + var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; + + if (underlyingType == typeof(string)) + return value; + if (underlyingType == typeof(short)) + return Convert.ToInt16(value); + if (underlyingType == typeof(int)) + return Convert.ToInt32(value); + if (underlyingType == typeof(long)) + return Convert.ToInt64(value); + if (underlyingType == typeof(byte)) + return Convert.ToByte(value); + if (underlyingType == typeof(bool)) + return value == "1"; + if (underlyingType.IsEnum) + return Enum.Parse(underlyingType, value); + + return Convert.ChangeType(value, underlyingType); + } + } + + public class FluentParser where T : new() + { + private readonly GenericParser _parser; + private readonly string _splitter; + + internal FluentParser( + string fileAddress, + string endPattern, + int firstIndex, + Dictionary, object?>> actionList, + string splitter, + ILogger logger, + ILogLanguageLocalizer logLanguage) + { + _parser = new GenericParser(fileAddress, endPattern, firstIndex, actionList, logger, logLanguage); + _splitter = splitter; + } + + public Task> GetDtosAsync() + { + return _parser.GetDtosAsync(_splitter); + } + } +} diff --git a/src/NosCore.Parser/Parsers/ItemParser.cs b/src/NosCore.Parser/Parsers/ItemParser.cs index 09563ca63..048611883 100644 --- a/src/NosCore.Parser/Parsers/ItemParser.cs +++ b/src/NosCore.Parser/Parsers/ItemParser.cs @@ -39,76 +39,73 @@ public class ItemParser(IDao itemDao, IDao bCar public async Task ParseAsync(string folder) { - var actionList = new Dictionary, object?>> - { - {nameof(ItemDto.VNum), chunk => Convert.ToInt16(chunk["VNUM"][0][2])}, - {nameof(ItemDto.Price), chunk => Convert.ToInt64(chunk["VNUM"][0][3])}, - {nameof(ItemDto.ReputPrice), chunk => chunk["FLAG"][0][21] == "1" ? Convert.ToInt64(chunk["VNUM"][0][3]) : 0}, - {nameof(ItemDto.NameI18NKey), chunk => chunk["NAME"][0][2]}, - {nameof(ItemDto.Type), chunk => ImportType(chunk)}, - {nameof(ItemDto.ItemType), chunk => ImportItemType(chunk)}, - {nameof(ItemDto.ItemSubType), chunk => Convert.ToByte(chunk["INDEX"][0][4])}, - {nameof(ItemDto.EquipmentSlot), chunk => ImportEquipmentType(chunk)}, - {nameof(ItemDto.Morph), chunk => ImportEffect(chunk) == ItemEffectType.ApplySkinPartner ?Convert.ToInt16(chunk["INDEX"][0][5]) : - ImportEquipmentType(chunk) != EquipmentType.Amulet ? Convert.ToInt16(chunk["INDEX"][0][7]) : default}, - {nameof(ItemDto.Class), chunk => ImportEquipmentType(chunk) == EquipmentType.Fairy ? (byte)15 : Convert.ToByte(chunk["TYPE"][0][3])}, - {nameof(ItemDto.Flag8), chunk => chunk["FLAG"][0][24] == "1"}, - {nameof(ItemDto.Flag7), chunk => chunk["FLAG"][0][23] == "1"}, - {nameof(ItemDto.IsHeroic), chunk => chunk["FLAG"][0][22] == "1"}, - {nameof(ItemDto.Flag6), chunk => chunk["FLAG"][0][20] == "1"}, - {nameof(ItemDto.Sex), chunk => chunk["FLAG"][0][18] == "1" ? (byte)1 : chunk["FLAG"][0][17] == "1" ? (byte)2 : (byte)0}, - {nameof(ItemDto.IsColored), chunk => chunk["FLAG"][0][16] == "1"}, - {nameof(ItemDto.RequireBinding), chunk => chunk["FLAG"][0][15] == "1"}, - {nameof(ItemDto.Flag4), chunk => chunk["FLAG"][0][14] == "1"}, - {nameof(ItemDto.Flag3), chunk => chunk["FLAG"][0][13] == "1"}, - {nameof(ItemDto.Flag2), chunk => chunk["FLAG"][0][12] == "1"}, - {nameof(ItemDto.Flag1), chunk => chunk["FLAG"][0][11] == "1"}, - {nameof(ItemDto.Flag9), chunk => chunk["FLAG"][0][10] == "1"}, - {nameof(ItemDto.IsWarehouse), chunk => chunk["FLAG"][0][9] == "1"}, - {nameof(ItemDto.IsMinilandActionable), chunk => chunk["FLAG"][0][8] == "1"}, - {nameof(ItemDto.IsTradable), chunk => chunk["FLAG"][0][7] == "0"}, - {nameof(ItemDto.IsDroppable), chunk => chunk["FLAG"][0][6] == "0"}, - {nameof(ItemDto.IsSoldable), chunk => chunk["FLAG"][0][5] == "0"}, - {nameof(ItemDto.LevelMinimum), chunk => ImportLevelMinimum(chunk)}, - {nameof(ItemDto.BCards), ImportBCards}, - {nameof(ItemDto.Effect), chunk => ImportEffect(chunk)}, - {nameof(ItemDto.EffectValue), chunk => ImportEffectValue(chunk)}, - {nameof(ItemDto.FireResistance), chunk => ImportResistance(chunk, ElementType.Fire)}, - {nameof(ItemDto.DarkResistance), chunk => ImportResistance(chunk, ElementType.Dark)}, - {nameof(ItemDto.LightResistance), chunk => ImportResistance(chunk, ElementType.Light)}, - {nameof(ItemDto.WaterResistance), chunk => ImportResistance(chunk, ElementType.Water)}, - {nameof(ItemDto.Hp), chunk => ImportHp(chunk)}, - {nameof(ItemDto.Mp), chunk => ImportMp(chunk)}, - {nameof(ItemDto.MinilandObjectPoint), chunk => ImportMinilandObjectPoint(chunk)}, - {nameof(ItemDto.Width), chunk => ImportWidth(chunk)}, - {nameof(ItemDto.Height), chunk => ImportHeight(chunk)}, - {nameof(ItemDto.DefenceDodge), chunk => ImportDefenceDodge(chunk)}, - {nameof(ItemDto.CloseDefence), chunk => ImportCloseDefence(chunk)}, - {nameof(ItemDto.DistanceDefence), chunk => ImportDistanceDefence(chunk)}, - {nameof(ItemDto.MagicDefence), chunk => ImportMagicDefence(chunk)}, - {nameof(ItemDto.BasicUpgrade), chunk => ImportBasicUpgrade(chunk)}, - {nameof(ItemDto.WaitDelay), chunk => ImportWaitDelay(chunk)}, - {nameof(ItemDto.ElementRate), chunk => ImportElementRate(chunk)}, - {nameof(ItemDto.Speed), chunk => ImportSpeed(chunk)}, - {nameof(ItemDto.SpType), chunk => ImportSpType(chunk)}, - {nameof(ItemDto.LevelJobMinimum), chunk => ImportLevelJobMinimum(chunk)}, - {nameof(ItemDto.ReputationMinimum), chunk => ImportReputationMinimum(chunk)}, - {nameof(ItemDto.ItemValidTime), chunk => ImportItemValidTime(chunk)}, - {nameof(ItemDto.Element), chunk => ImportElement(chunk)}, - {nameof(ItemDto.MaxCellonLvl), chunk => ImportMaxCellonLvl(chunk)}, - {nameof(ItemDto.MaxCellon), chunk => ImportMaxCellon(chunk)}, - {nameof(ItemDto.DistanceDefenceDodge), chunk => ImportDistanceDefenceDodge(chunk)}, - {nameof(ItemDto.MaximumAmmo), chunk => ImportMaximumAmmo(chunk)}, - {nameof(ItemDto.CriticalRate), chunk => ImportCriticalRate(chunk)}, - {nameof(ItemDto.CriticalLuckRate), chunk => ImportCriticalLuckRate(chunk)}, - {nameof(ItemDto.HitRate), chunk => ImportHitRate(chunk)}, - {nameof(ItemDto.DamageMaximum), chunk => ImportDamageMaximum(chunk)}, - {nameof(ItemDto.DamageMinimum), chunk => ImportDamageMinimum(chunk)}, - }; - - var genericParser = new GenericParser(folder + _itemCardDto, - "END", 1, actionList, logger, logLanguage); - var items = (await genericParser.GetDtosAsync()).GroupBy(p => p.VNum).Select(g => g.First()).ToList(); + var parser = FluentParserBuilder.Create(folder + _itemCardDto, "END", 1) + .Field(x => x.VNum, chunk => Convert.ToInt16(chunk["VNUM"][0][2])) + .Field(x => x.Price, chunk => Convert.ToInt64(chunk["VNUM"][0][3])) + .Field(x => x.ReputPrice, chunk => chunk["FLAG"][0][21] == "1" ? Convert.ToInt64(chunk["VNUM"][0][3]) : 0) + .Field(x => x.NameI18NKey, chunk => chunk["NAME"][0][2]) + .Field(x => x.Type, chunk => ImportType(chunk)) + .Field(x => x.ItemType, chunk => ImportItemType(chunk)) + .Field(x => x.ItemSubType, chunk => Convert.ToByte(chunk["INDEX"][0][4])) + .Field(x => x.EquipmentSlot, chunk => ImportEquipmentType(chunk)) + .Field(x => x.Morph, chunk => ImportEffect(chunk) == ItemEffectType.ApplySkinPartner ? Convert.ToInt16(chunk["INDEX"][0][5]) : + ImportEquipmentType(chunk) != EquipmentType.Amulet ? Convert.ToInt16(chunk["INDEX"][0][7]) : default) + .Field(x => x.Class, chunk => ImportEquipmentType(chunk) == EquipmentType.Fairy ? (byte)15 : Convert.ToByte(chunk["TYPE"][0][3])) + .Field(x => x.Flag8, chunk => chunk["FLAG"][0][24] == "1") + .Field(x => x.Flag7, chunk => chunk["FLAG"][0][23] == "1") + .Field(x => x.IsHeroic, chunk => chunk["FLAG"][0][22] == "1") + .Field(x => x.Flag6, chunk => chunk["FLAG"][0][20] == "1") + .Field(x => x.Sex, chunk => chunk["FLAG"][0][18] == "1" ? (byte)1 : chunk["FLAG"][0][17] == "1" ? (byte)2 : (byte)0) + .Field(x => x.IsColored, chunk => chunk["FLAG"][0][16] == "1") + .Field(x => x.RequireBinding, chunk => chunk["FLAG"][0][15] == "1") + .Field(x => x.Flag4, chunk => chunk["FLAG"][0][14] == "1") + .Field(x => x.Flag3, chunk => chunk["FLAG"][0][13] == "1") + .Field(x => x.Flag2, chunk => chunk["FLAG"][0][12] == "1") + .Field(x => x.Flag1, chunk => chunk["FLAG"][0][11] == "1") + .Field(x => x.Flag9, chunk => chunk["FLAG"][0][10] == "1") + .Field(x => x.IsWarehouse, chunk => chunk["FLAG"][0][9] == "1") + .Field(x => x.IsMinilandActionable, chunk => chunk["FLAG"][0][8] == "1") + .Field(x => x.IsTradable, chunk => chunk["FLAG"][0][7] == "0") + .Field(x => x.IsDroppable, chunk => chunk["FLAG"][0][6] == "0") + .Field(x => x.IsSoldable, chunk => chunk["FLAG"][0][5] == "0") + .Field(x => x.LevelMinimum, chunk => ImportLevelMinimum(chunk)) + .Field(x => x.BCards, chunk => ImportBCards(chunk)) + .Field(x => x.Effect, chunk => ImportEffect(chunk)) + .Field(x => x.EffectValue, chunk => ImportEffectValue(chunk)) + .Field(x => x.FireResistance, chunk => ImportResistance(chunk, ElementType.Fire)) + .Field(x => x.DarkResistance, chunk => ImportResistance(chunk, ElementType.Dark)) + .Field(x => x.LightResistance, chunk => ImportResistance(chunk, ElementType.Light)) + .Field(x => x.WaterResistance, chunk => ImportResistance(chunk, ElementType.Water)) + .Field(x => x.Hp, chunk => ImportHp(chunk)) + .Field(x => x.Mp, chunk => ImportMp(chunk)) + .Field(x => x.MinilandObjectPoint, chunk => ImportMinilandObjectPoint(chunk)) + .Field(x => x.Width, chunk => ImportWidth(chunk)) + .Field(x => x.Height, chunk => ImportHeight(chunk)) + .Field(x => x.DefenceDodge, chunk => ImportDefenceDodge(chunk)) + .Field(x => x.CloseDefence, chunk => ImportCloseDefence(chunk)) + .Field(x => x.DistanceDefence, chunk => ImportDistanceDefence(chunk)) + .Field(x => x.MagicDefence, chunk => ImportMagicDefence(chunk)) + .Field(x => x.BasicUpgrade, chunk => ImportBasicUpgrade(chunk)) + .Field(x => x.WaitDelay, chunk => ImportWaitDelay(chunk)) + .Field(x => x.ElementRate, chunk => ImportElementRate(chunk)) + .Field(x => x.Speed, chunk => ImportSpeed(chunk)) + .Field(x => x.SpType, chunk => ImportSpType(chunk)) + .Field(x => x.LevelJobMinimum, chunk => ImportLevelJobMinimum(chunk)) + .Field(x => x.ReputationMinimum, chunk => ImportReputationMinimum(chunk)) + .Field(x => x.ItemValidTime, chunk => ImportItemValidTime(chunk)) + .Field(x => x.Element, chunk => ImportElement(chunk)) + .Field(x => x.MaxCellonLvl, chunk => ImportMaxCellonLvl(chunk)) + .Field(x => x.MaxCellon, chunk => ImportMaxCellon(chunk)) + .Field(x => x.DistanceDefenceDodge, chunk => ImportDistanceDefenceDodge(chunk)) + .Field(x => x.MaximumAmmo, chunk => ImportMaximumAmmo(chunk)) + .Field(x => x.CriticalRate, chunk => ImportCriticalRate(chunk)) + .Field(x => x.CriticalLuckRate, chunk => ImportCriticalLuckRate(chunk)) + .Field(x => x.HitRate, chunk => ImportHitRate(chunk)) + .Field(x => x.DamageMaximum, chunk => ImportDamageMaximum(chunk)) + .Field(x => x.DamageMinimum, chunk => ImportDamageMinimum(chunk)) + .Build(logger, logLanguage); + + var items = (await parser.GetDtosAsync()).GroupBy(p => p.VNum).Select(g => g.First()).ToList(); foreach (var item in items) { HardcodeItem(item); diff --git a/src/NosCore.Parser/Parsers/NpcMonsterParser.cs b/src/NosCore.Parser/Parsers/NpcMonsterParser.cs index 0a0b89828..fe80e1f2f 100644 --- a/src/NosCore.Parser/Parsers/NpcMonsterParser.cs +++ b/src/NosCore.Parser/Parsers/NpcMonsterParser.cs @@ -80,63 +80,58 @@ public async Task InsertNpcMonstersAsync(string folder) { _skilldb = _skillDao.LoadAll().ToDictionary(x => x.SkillVNum, x => x); _dropdb = _dropDao.LoadAll().Where(x => x.MonsterVNum != null).GroupBy(x => x.MonsterVNum).ToDictionary(x => x.Key ?? 0, x => x.ToList()); - var actionList = new Dictionary, object?>> - { - {nameof(NpcMonsterDto.NpcMonsterVNum), chunk => Convert.ToInt16(chunk["VNUM"][0][2])}, - {nameof(NpcMonsterDto.NameI18NKey), chunk => chunk["NAME"][0][2]}, - {nameof(NpcMonsterDto.Level), chunk => Level(chunk)}, - {nameof(NpcMonsterDto.HeroXp), chunk => ImportXp(chunk) / 25}, - {nameof(NpcMonsterDto.Race), chunk => Convert.ToByte(chunk["RACE"][0][2])}, - {nameof(NpcMonsterDto.RaceType), chunk => Convert.ToByte(chunk["RACE"][0][3])}, - {nameof(NpcMonsterDto.Element), chunk => Convert.ToByte(chunk["ATTRIB"][0][2])}, - {nameof(NpcMonsterDto.ElementRate), chunk => Convert.ToInt16(chunk["ATTRIB"][0][3])}, - {nameof(NpcMonsterDto.FireResistance), chunk => Convert.ToInt16(chunk["ATTRIB"][0][4])}, - {nameof(NpcMonsterDto.WaterResistance), chunk => Convert.ToInt16(chunk["ATTRIB"][0][5])}, - {nameof(NpcMonsterDto.LightResistance), chunk => Convert.ToInt16(chunk["ATTRIB"][0][6])}, - {nameof(NpcMonsterDto.DarkResistance), chunk => Convert.ToInt16(chunk["ATTRIB"][0][7])}, - {nameof(NpcMonsterDto.MaxHp), chunk => Convert.ToInt32(chunk["HP/MP"][0][2]) + _basicHp[Level(chunk)]}, - {nameof(NpcMonsterDto.MaxMp), chunk => Convert.ToInt32(chunk["HP/MP"][0][3]) + Convert.ToByte(chunk["RACE"][0][2]) == 0 ? _basicPrimaryMp[Level(chunk)] : _basicSecondaryMp[Level(chunk)]}, - {nameof(NpcMonsterDto.Xp), chunk => ImportXp(chunk)}, - {nameof(NpcMonsterDto.JobXp), chunk => ImportJxp(chunk)}, - {nameof(NpcMonsterDto.IsHostile), chunk => chunk["PREATT"][0][2] != "0" }, - {nameof(NpcMonsterDto.NoticeRange), chunk => Convert.ToByte(chunk["PREATT"][0][4])}, - {nameof(NpcMonsterDto.Speed), chunk => Convert.ToByte(chunk["PREATT"][0][5])}, - {nameof(NpcMonsterDto.RespawnTime), chunk => Convert.ToInt32(chunk["PREATT"][0][6])}, - {nameof(NpcMonsterDto.CloseDefence), chunk => Convert.ToInt16((Convert.ToInt16(chunk["ARMOR"][0][2]) - 1) * 2 + 18)}, - {nameof(NpcMonsterDto.DistanceDefence), chunk => Convert.ToInt16((Convert.ToInt16(chunk["ARMOR"][0][2])- 1) * 3 + 17)}, - {nameof(NpcMonsterDto.MagicDefence), chunk => Convert.ToInt16((Convert.ToInt16(chunk["ARMOR"][0][2]) - 1) * 2 + 13)}, - {nameof(NpcMonsterDto.DefenceDodge), chunk => Convert.ToInt16((Convert.ToInt16(chunk["ARMOR"][0][2])- 1) * 5 + 31)}, - {nameof(NpcMonsterDto.DistanceDefenceDodge), chunk => Convert.ToInt16((Convert.ToInt16(chunk["ARMOR"][0][2]) - 1) * 5 + 31)}, - {nameof(NpcMonsterDto.AttackClass), chunk => Convert.ToByte(chunk["ZSKILL"][0][2])}, - {nameof(NpcMonsterDto.BasicRange), chunk => Convert.ToByte(chunk["ZSKILL"][0][3])}, - {nameof(NpcMonsterDto.BasicArea), chunk => Convert.ToByte(chunk["ZSKILL"][0][5])}, - {nameof(NpcMonsterDto.BasicCooldown), chunk => Convert.ToInt16(chunk["ZSKILL"][0][6])}, - {nameof(NpcMonsterDto.AttackUpgrade), chunk => Convert.ToByte(LoadUnknownData(chunk) == 1?chunk["WINFO"][0][2]:chunk["WINFO"][0][4])}, - {nameof(NpcMonsterDto.DefenceUpgrade), chunk => Convert.ToByte(LoadUnknownData(chunk) == 1? chunk["WINFO"][0][2]:chunk["AINFO"][0][3])}, - {nameof(NpcMonsterDto.BasicSkill), chunk => Convert.ToInt16(chunk["EFF"][0][2])}, - {nameof(NpcMonsterDto.VNumRequired), chunk => Convert.ToInt16(chunk["SETTING"][0][4] != "0" && ShouldLoadPetinfo(chunk) ? chunk["PETINFO"][0][2] : chunk["SETTING"][0][4])}, - {nameof(NpcMonsterDto.AmountRequired), chunk => Convert.ToByte(chunk["SETTING"][0][4] == "0" ? "1" : ShouldLoadPetinfo(chunk) ? chunk["PETINFO"][0][3] : "0")}, - {nameof(NpcMonsterDto.DamageMinimum), chunk => ImportDamageMinimum(chunk)}, - {nameof(NpcMonsterDto.DamageMaximum), chunk => ImportDamageMaximum(chunk)}, - {nameof(NpcMonsterDto.Concentrate), chunk => ImportConcentrate(chunk)}, - {nameof(NpcMonsterDto.CriticalChance), chunk => ImportCriticalChance(chunk)}, - {nameof(NpcMonsterDto.CriticalRate), chunk => ImportCriticalRate(chunk)}, - {nameof(NpcMonsterDto.NpcMonsterSkill), ImportNpcMonsterSkill}, - {nameof(NpcMonsterDto.BCards), ImportBCards}, - {nameof(NpcMonsterDto.Drop), ImportDrops}, - {nameof(NpcMonsterDto.MonsterType), chunk => ImportMonsterType(chunk)}, - {nameof(NpcMonsterDto.NoAggresiveIcon), chunk => { - var unknowndata = LoadUnknownData(chunk); - return (unknowndata == -2147483616 - || unknowndata == -2147483647 - || unknowndata == -2147483646) && ((Convert.ToByte(chunk["RACE"][0][2]) == 8) && (Convert.ToByte(chunk["RACE"][0][3]) == 0)); - } - } - }; - - var genericParser = new GenericParser(folder + _fileNpcId, - "#========================================================", 1, actionList, _logger, _logLanguage); - var monsters = (await genericParser.GetDtosAsync()).GroupBy(p => p.NpcMonsterVNum).Select(g => g.First()).ToList(); + var parser = FluentParserBuilder.Create(folder + _fileNpcId, "#========================================================", 1) + .Field(x => x.NpcMonsterVNum, chunk => Convert.ToInt16(chunk["VNUM"][0][2])) + .Field(x => x.NameI18NKey, chunk => chunk["NAME"][0][2]) + .Field(x => x.Level, chunk => Level(chunk)) + .Field(x => x.HeroXp, chunk => ImportXp(chunk) / 25) + .Field(x => x.Race, chunk => Convert.ToByte(chunk["RACE"][0][2])) + .Field(x => x.RaceType, chunk => Convert.ToByte(chunk["RACE"][0][3])) + .Field(x => x.Element, chunk => Convert.ToByte(chunk["ATTRIB"][0][2])) + .Field(x => x.ElementRate, chunk => Convert.ToInt16(chunk["ATTRIB"][0][3])) + .Field(x => x.FireResistance, chunk => Convert.ToInt16(chunk["ATTRIB"][0][4])) + .Field(x => x.WaterResistance, chunk => Convert.ToInt16(chunk["ATTRIB"][0][5])) + .Field(x => x.LightResistance, chunk => Convert.ToInt16(chunk["ATTRIB"][0][6])) + .Field(x => x.DarkResistance, chunk => Convert.ToInt16(chunk["ATTRIB"][0][7])) + .Field(x => x.MaxHp, chunk => Convert.ToInt32(chunk["HP/MP"][0][2]) + _basicHp[Level(chunk)]) + .Field(x => x.MaxMp, chunk => Convert.ToInt32(chunk["HP/MP"][0][3]) + (Convert.ToByte(chunk["RACE"][0][2]) == 0 ? _basicPrimaryMp[Level(chunk)] : _basicSecondaryMp[Level(chunk)])) + .Field(x => x.Xp, chunk => ImportXp(chunk)) + .Field(x => x.JobXp, chunk => ImportJxp(chunk)) + .Field(x => x.IsHostile, chunk => chunk["PREATT"][0][2] != "0") + .Field(x => x.NoticeRange, chunk => Convert.ToByte(chunk["PREATT"][0][4])) + .Field(x => x.Speed, chunk => Convert.ToByte(chunk["PREATT"][0][5])) + .Field(x => x.RespawnTime, chunk => Convert.ToInt32(chunk["PREATT"][0][6])) + .Field(x => x.CloseDefence, chunk => Convert.ToInt16((Convert.ToInt16(chunk["ARMOR"][0][2]) - 1) * 2 + 18)) + .Field(x => x.DistanceDefence, chunk => Convert.ToInt16((Convert.ToInt16(chunk["ARMOR"][0][2]) - 1) * 3 + 17)) + .Field(x => x.MagicDefence, chunk => Convert.ToInt16((Convert.ToInt16(chunk["ARMOR"][0][2]) - 1) * 2 + 13)) + .Field(x => x.DefenceDodge, chunk => Convert.ToInt16((Convert.ToInt16(chunk["ARMOR"][0][2]) - 1) * 5 + 31)) + .Field(x => x.DistanceDefenceDodge, chunk => Convert.ToInt16((Convert.ToInt16(chunk["ARMOR"][0][2]) - 1) * 5 + 31)) + .Field(x => x.AttackClass, chunk => Convert.ToByte(chunk["ZSKILL"][0][2])) + .Field(x => x.BasicRange, chunk => Convert.ToByte(chunk["ZSKILL"][0][3])) + .Field(x => x.BasicArea, chunk => Convert.ToByte(chunk["ZSKILL"][0][5])) + .Field(x => x.BasicCooldown, chunk => Convert.ToInt16(chunk["ZSKILL"][0][6])) + .Field(x => x.AttackUpgrade, chunk => Convert.ToByte(LoadUnknownData(chunk) == 1 ? chunk["WINFO"][0][2] : chunk["WINFO"][0][4])) + .Field(x => x.DefenceUpgrade, chunk => Convert.ToByte(LoadUnknownData(chunk) == 1 ? chunk["WINFO"][0][2] : chunk["AINFO"][0][3])) + .Field(x => x.BasicSkill, chunk => Convert.ToInt16(chunk["EFF"][0][2])) + .Field(x => x.VNumRequired, chunk => Convert.ToInt16(chunk["SETTING"][0][4] != "0" && ShouldLoadPetinfo(chunk) ? chunk["PETINFO"][0][2] : chunk["SETTING"][0][4])) + .Field(x => x.AmountRequired, chunk => Convert.ToByte(chunk["SETTING"][0][4] == "0" ? "1" : ShouldLoadPetinfo(chunk) ? chunk["PETINFO"][0][3] : "0")) + .Field(x => x.DamageMinimum, chunk => ImportDamageMinimum(chunk)) + .Field(x => x.DamageMaximum, chunk => ImportDamageMaximum(chunk)) + .Field(x => x.Concentrate, chunk => ImportConcentrate(chunk)) + .Field(x => x.CriticalChance, chunk => ImportCriticalChance(chunk)) + .Field(x => x.CriticalRate, chunk => ImportCriticalRate(chunk)) + .Field(x => x.NpcMonsterSkill, chunk => ImportNpcMonsterSkill(chunk)) + .Field(x => x.BCards, chunk => ImportBCards(chunk)) + .Field(x => x.Drop, chunk => ImportDrops(chunk)) + .Field(x => x.MonsterType, chunk => ImportMonsterType(chunk)) + .Field(x => x.NoAggresiveIcon, chunk => + { + var unknowndata = LoadUnknownData(chunk); + return (unknowndata == -2147483616 || unknowndata == -2147483647 || unknowndata == -2147483646) + && (Convert.ToByte(chunk["RACE"][0][2]) == 8) && (Convert.ToByte(chunk["RACE"][0][3]) == 0); + }) + .Build(_logger, _logLanguage); + var monsters = (await parser.GetDtosAsync()).GroupBy(p => p.NpcMonsterVNum).Select(g => g.First()).ToList(); await _npcMonsterDao.TryInsertOrUpdateAsync(monsters); await _bCardDao.TryInsertOrUpdateAsync(monsters.Where(s => s.BCards != null).SelectMany(s => s.BCards)); await _dropDao.TryInsertOrUpdateAsync(monsters.Where(s => s.Drop != null).SelectMany(s => s.Drop)); diff --git a/src/NosCore.Parser/Parsers/QuestParser.cs b/src/NosCore.Parser/Parsers/QuestParser.cs index a23bd8ef3..3fc4ae518 100644 --- a/src/NosCore.Parser/Parsers/QuestParser.cs +++ b/src/NosCore.Parser/Parsers/QuestParser.cs @@ -45,29 +45,27 @@ public async Task ImportQuestsAsync(string folder) { _questRewards = questRewardDao.LoadAll().ToDictionary(x => x.QuestRewardId, x => x); - var actionList = new Dictionary, object?>> - { - {nameof(QuestDto.QuestId), chunk => Convert.ToInt16(chunk["VNUM"][0][1])}, - {nameof(QuestDto.QuestType), chunk => (QuestType)Enum.Parse(typeof(QuestType), chunk["VNUM"][0][2])}, - {nameof(QuestDto.AutoFinish), chunk => chunk["VNUM"][0][3] == "1"}, - {nameof(QuestDto.IsDaily), chunk => chunk["VNUM"][0][4] == "-1"}, - {nameof(QuestDto.RequiredQuestId), chunk => chunk["VNUM"][0][5] != "-1" ? short.Parse(chunk["VNUM"][0][5]) : (short?)null }, - {nameof(QuestDto.IsSecondary), chunk => chunk["VNUM"][0][6] != "-1"}, - {nameof(QuestDto.LevelMin), chunk => Convert.ToByte(chunk["LEVEL"][0][1])}, - {nameof(QuestDto.LevelMax), chunk => Convert.ToByte(chunk["LEVEL"][0][2])}, - {nameof(QuestDto.TitleI18NKey), chunk => chunk["TITLE"][0][1]}, - {nameof(QuestDto.DescI18NKey), chunk => chunk["DESC"][0][1]}, - {nameof(QuestDto.TargetX), chunk => chunk["TARGET"][0][1] == "-1" ? (short?)null : Convert.ToInt16(chunk["TARGET"][0][1])}, - {nameof(QuestDto.TargetY), chunk => chunk["TARGET"][0][2] == "-1" ? (short?)null : Convert.ToInt16(chunk["TARGET"][0][2])}, - {nameof(QuestDto.TargetMap), chunk => chunk["TARGET"][0][3] == "-1" ? (short?)null : Convert.ToInt16(chunk["TARGET"][0][3])}, - {nameof(QuestDto.StartDialogId), chunk => chunk["TARGET"][0][1] == "-1" ? (int?)null : Convert.ToInt32(chunk["TALK"][0][1])}, - {nameof(QuestDto.EndDialogId), chunk => chunk["TARGET"][0][2] == "-1" ? (int?)null : Convert.ToInt32(chunk["TALK"][0][2])}, - {nameof(QuestDto.NextQuestId), chunk => chunk["LINK"][0][1] == "-1" ? (short?)null : Convert.ToInt16(chunk["LINK"][0][1])}, - {nameof(QuestDto.QuestQuestReward), ImportQuestQuestRewards}, - {nameof(QuestDto.QuestObjective), ImportQuestObjectives}, - }; - var genericParser = new GenericParser(folder + _fileQuestDat, "END", 0, actionList, logger, logLanguage); - var quests = await genericParser.GetDtosAsync(); + var parser = FluentParserBuilder.Create(folder + _fileQuestDat, "END", 0) + .Field(x => x.QuestId, chunk => Convert.ToInt16(chunk["VNUM"][0][1])) + .Field(x => x.QuestType, chunk => (QuestType)Enum.Parse(typeof(QuestType), chunk["VNUM"][0][2])) + .Field(x => x.AutoFinish, chunk => chunk["VNUM"][0][3] == "1") + .Field(x => x.IsDaily, chunk => chunk["VNUM"][0][4] == "-1") + .Field(x => x.RequiredQuestId, chunk => chunk["VNUM"][0][5] != "-1" ? short.Parse(chunk["VNUM"][0][5]) : (short?)null) + .Field(x => x.IsSecondary, chunk => chunk["VNUM"][0][6] != "-1") + .Field(x => x.LevelMin, chunk => Convert.ToByte(chunk["LEVEL"][0][1])) + .Field(x => x.LevelMax, chunk => Convert.ToByte(chunk["LEVEL"][0][2])) + .Field(x => x.TitleI18NKey, chunk => chunk["TITLE"][0][1]) + .Field(x => x.DescI18NKey, chunk => chunk["DESC"][0][1]) + .Field(x => x.TargetX, chunk => chunk["TARGET"][0][1] == "-1" ? (short?)null : Convert.ToInt16(chunk["TARGET"][0][1])) + .Field(x => x.TargetY, chunk => chunk["TARGET"][0][2] == "-1" ? (short?)null : Convert.ToInt16(chunk["TARGET"][0][2])) + .Field(x => x.TargetMap, chunk => chunk["TARGET"][0][3] == "-1" ? (short?)null : Convert.ToInt16(chunk["TARGET"][0][3])) + .Field(x => x.StartDialogId, chunk => chunk["TARGET"][0][1] == "-1" ? (int?)null : Convert.ToInt32(chunk["TALK"][0][1])) + .Field(x => x.EndDialogId, chunk => chunk["TARGET"][0][2] == "-1" ? (int?)null : Convert.ToInt32(chunk["TALK"][0][2])) + .Field(x => x.NextQuestId, chunk => chunk["LINK"][0][1] == "-1" ? (short?)null : Convert.ToInt16(chunk["LINK"][0][1])) + .Field(x => x.QuestQuestReward, chunk => ImportQuestQuestRewards(chunk)) + .Field(x => x.QuestObjective, chunk => ImportQuestObjectives(chunk)) + .Build(logger, logLanguage); + var quests = await parser.GetDtosAsync(); await questDao.TryInsertOrUpdateAsync(quests); await questQuestRewardDao.TryInsertOrUpdateAsync(quests.Where(s => s.QuestQuestReward != null).SelectMany(s => s.QuestQuestReward)); diff --git a/src/NosCore.Parser/Parsers/SkillParser.cs b/src/NosCore.Parser/Parsers/SkillParser.cs index 0eca5d71b..d4e22ba15 100644 --- a/src/NosCore.Parser/Parsers/SkillParser.cs +++ b/src/NosCore.Parser/Parsers/SkillParser.cs @@ -44,42 +44,39 @@ public class SkillParser(IDao bCardDao, IDao com public async Task InsertSkillsAsync(string folder) { - var actionList = new Dictionary, object?>> - { - {nameof(SkillDto.SkillVNum), chunk => Convert.ToInt16(chunk["VNUM"][0][2])}, - {nameof(SkillDto.NameI18NKey), chunk => chunk["NAME"][0][2]}, - {nameof(SkillDto.SkillType), chunk => Convert.ToByte(chunk["TYPE"][0][2])}, - {nameof(SkillDto.CastId), chunk => Convert.ToInt16(chunk["TYPE"][0][3])}, - {nameof(SkillDto.Class), chunk => Convert.ToByte(chunk["TYPE"][0][4])}, - {nameof(SkillDto.Type), chunk => Convert.ToByte(chunk["TYPE"][0][5])}, - {nameof(SkillDto.Element), chunk => Convert.ToByte(chunk["TYPE"][0][7])}, - {nameof(SkillDto.Combo), AddCombos}, - {nameof(SkillDto.CpCost), chunk => chunk["COST"][0][2] == "-1" ? (byte)0 : byte.Parse(chunk["COST"][0][2])}, - {nameof(SkillDto.Price), chunk => Convert.ToInt32(chunk["COST"][0][3])}, - {nameof(SkillDto.CastEffect), chunk => Convert.ToInt16(chunk["EFFECT"][0][3])}, - {nameof(SkillDto.CastAnimation), chunk => Convert.ToInt16(chunk["EFFECT"][0][4])}, - {nameof(SkillDto.Effect), chunk => Convert.ToInt16(chunk["EFFECT"][0][5])}, - {nameof(SkillDto.AttackAnimation), chunk => Convert.ToInt16(chunk["EFFECT"][0][6])}, - {nameof(SkillDto.TargetType), chunk => Convert.ToByte(chunk["TARGET"][0][2])}, - {nameof(SkillDto.HitType), chunk => Convert.ToByte(chunk["TARGET"][0][3])}, - {nameof(SkillDto.Range), chunk => Convert.ToByte(chunk["TARGET"][0][4])}, - {nameof(SkillDto.TargetRange), chunk => Convert.ToByte(chunk["TARGET"][0][5])}, - {nameof(SkillDto.UpgradeSkill), chunk => Convert.ToInt16(chunk["DATA"][0][2])}, - {nameof(SkillDto.UpgradeType), chunk => Convert.ToInt16(chunk["DATA"][0][3])}, - {nameof(SkillDto.CastTime), chunk => Convert.ToInt16(chunk["DATA"][0][6])}, - {nameof(SkillDto.Cooldown), chunk => Convert.ToInt16(chunk["DATA"][0][7])}, - {nameof(SkillDto.MpCost), chunk => Convert.ToInt16(chunk["DATA"][0][10])}, - {nameof(SkillDto.ItemVNum), chunk => Convert.ToInt16(chunk["DATA"][0][12])}, - {nameof(SkillDto.BCards), AddBCards}, - {nameof(SkillDto.MinimumAdventurerLevel), chunk => chunk["LEVEL"][0][3] != "-1" ? byte.Parse(chunk["LEVEL"][0][3]) : (byte)0}, - {nameof(SkillDto.MinimumSwordmanLevel), chunk => chunk["LEVEL"][0][4] != "-1" ? byte.Parse(chunk["LEVEL"][0][4]) : (byte)0}, - {nameof(SkillDto.MinimumArcherLevel), chunk => chunk["LEVEL"][0][5] != "-1" ? byte.Parse(chunk["LEVEL"][0][5]) : (byte)0}, - {nameof(SkillDto.MinimumMagicianLevel), chunk => chunk["LEVEL"][0][6] != "-1" ? byte.Parse(chunk["LEVEL"][0][6]) : (byte)0}, - {nameof(SkillDto.LevelMinimum), chunk => chunk["LEVEL"][0][2] != "-1" ? byte.Parse(chunk["LEVEL"][0][2]) : (byte)0 }, - }; - var genericParser = new GenericParser(folder + _fileCardDat, - "#=========================================================", 1, actionList, logger, logLanguage); - var skills = await genericParser.GetDtosAsync(); + var parser = FluentParserBuilder.Create(folder + _fileCardDat, "#=========================================================", 1) + .Field(x => x.SkillVNum, chunk => Convert.ToInt16(chunk["VNUM"][0][2])) + .Field(x => x.NameI18NKey, chunk => chunk["NAME"][0][2]) + .Field(x => x.SkillType, chunk => Convert.ToByte(chunk["TYPE"][0][2])) + .Field(x => x.CastId, chunk => Convert.ToInt16(chunk["TYPE"][0][3])) + .Field(x => x.Class, chunk => Convert.ToByte(chunk["TYPE"][0][4])) + .Field(x => x.Type, chunk => Convert.ToByte(chunk["TYPE"][0][5])) + .Field(x => x.Element, chunk => Convert.ToByte(chunk["TYPE"][0][7])) + .Field(x => x.Combo, chunk => AddCombos(chunk)) + .Field(x => x.CpCost, chunk => chunk["COST"][0][2] == "-1" ? (byte)0 : byte.Parse(chunk["COST"][0][2])) + .Field(x => x.Price, chunk => Convert.ToInt32(chunk["COST"][0][3])) + .Field(x => x.CastEffect, chunk => Convert.ToInt16(chunk["EFFECT"][0][3])) + .Field(x => x.CastAnimation, chunk => Convert.ToInt16(chunk["EFFECT"][0][4])) + .Field(x => x.Effect, chunk => Convert.ToInt16(chunk["EFFECT"][0][5])) + .Field(x => x.AttackAnimation, chunk => Convert.ToInt16(chunk["EFFECT"][0][6])) + .Field(x => x.TargetType, chunk => Convert.ToByte(chunk["TARGET"][0][2])) + .Field(x => x.HitType, chunk => Convert.ToByte(chunk["TARGET"][0][3])) + .Field(x => x.Range, chunk => Convert.ToByte(chunk["TARGET"][0][4])) + .Field(x => x.TargetRange, chunk => Convert.ToByte(chunk["TARGET"][0][5])) + .Field(x => x.UpgradeSkill, chunk => Convert.ToInt16(chunk["DATA"][0][2])) + .Field(x => x.UpgradeType, chunk => Convert.ToInt16(chunk["DATA"][0][3])) + .Field(x => x.CastTime, chunk => Convert.ToInt16(chunk["DATA"][0][6])) + .Field(x => x.Cooldown, chunk => Convert.ToInt16(chunk["DATA"][0][7])) + .Field(x => x.MpCost, chunk => Convert.ToInt16(chunk["DATA"][0][10])) + .Field(x => x.ItemVNum, chunk => Convert.ToInt16(chunk["DATA"][0][12])) + .Field(x => x.BCards, chunk => AddBCards(chunk)) + .Field(x => x.MinimumAdventurerLevel, chunk => chunk["LEVEL"][0][3] != "-1" ? byte.Parse(chunk["LEVEL"][0][3]) : (byte)0) + .Field(x => x.MinimumSwordmanLevel, chunk => chunk["LEVEL"][0][4] != "-1" ? byte.Parse(chunk["LEVEL"][0][4]) : (byte)0) + .Field(x => x.MinimumArcherLevel, chunk => chunk["LEVEL"][0][5] != "-1" ? byte.Parse(chunk["LEVEL"][0][5]) : (byte)0) + .Field(x => x.MinimumMagicianLevel, chunk => chunk["LEVEL"][0][6] != "-1" ? byte.Parse(chunk["LEVEL"][0][6]) : (byte)0) + .Field(x => x.LevelMinimum, chunk => chunk["LEVEL"][0][2] != "-1" ? byte.Parse(chunk["LEVEL"][0][2]) : (byte)0) + .Build(logger, logLanguage); + var skills = await parser.GetDtosAsync(); foreach (var skill in skills.Where(s => s.Class > 31)) { diff --git a/test/NosCore.Parser.Tests/ActParserTests.cs b/test/NosCore.Parser.Tests/ActParserTests.cs new file mode 100644 index 000000000..c19f8dda6 --- /dev/null +++ b/test/NosCore.Parser.Tests/ActParserTests.cs @@ -0,0 +1,172 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using NosCore.Dao.Interfaces; +using NosCore.Data.Enumerations.I18N; +using NosCore.Data.StaticEntities; +using NosCore.Parser.Parsers; +using NosCore.Shared.I18N; +using Serilog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace NosCore.Parser.Tests +{ + [TestClass] + public class ActParserTests + { + private Mock _loggerMock = null!; + private Mock> _logLanguageMock = null!; + private Mock> _actDaoMock = null!; + private Mock> _actPartDaoMock = null!; + private string _tempFolder = null!; + private List _savedActs = null!; + private List _savedActParts = null!; + + [TestInitialize] + public void Setup() + { + _loggerMock = new Mock(); + _logLanguageMock = new Mock>(); + _actDaoMock = new Mock>(); + _actPartDaoMock = new Mock>(); + _savedActs = []; + _savedActParts = []; + + _actDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(acts => _savedActs.AddRange(acts)) + .ReturnsAsync(true); + + _actPartDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(parts => _savedActParts.AddRange(parts)) + .ReturnsAsync(true); + + _tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(_tempFolder); + } + + [TestCleanup] + public void Cleanup() + { + if (Directory.Exists(_tempFolder)) + { + Directory.Delete(_tempFolder, true); + } + } + + private void CreateTestFile(string content) + { + File.WriteAllText(Path.Combine(_tempFolder, "act_desc.dat"), content); + } + + [TestMethod] + public async Task ActParser_ParsesActLines() + { + var content = "A\t1\tzts1e\nA\t2\tzts2e\n~"; + CreateTestFile(content); + + var parser = new ActParser(_actDaoMock.Object, _actPartDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ImportActAsync(_tempFolder); + + Assert.AreEqual(2, _savedActs.Count); + Assert.AreEqual(1, _savedActs[0].ActId); + Assert.AreEqual("zts1e", _savedActs[0].TitleI18NKey); + Assert.AreEqual(40, _savedActs[0].Scene); + Assert.AreEqual(2, _savedActs[1].ActId); + Assert.AreEqual("zts2e", _savedActs[1].TitleI18NKey); + Assert.AreEqual(41, _savedActs[1].Scene); + } + + [TestMethod] + public async Task ActParser_ParsesDataLines() + { + var content = "Data 1 1 1 10\nData 2 1 2 6\nend\n~"; + CreateTestFile(content); + + var parser = new ActParser(_actDaoMock.Object, _actPartDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ImportActAsync(_tempFolder); + + Assert.AreEqual(2, _savedActParts.Count); + Assert.AreEqual(1, _savedActParts[0].ActPartId); + Assert.AreEqual(1, _savedActParts[0].ActId); + Assert.AreEqual(1, _savedActParts[0].ActPartNumber); + Assert.AreEqual(10, _savedActParts[0].MaxTs); + Assert.AreEqual(2, _savedActParts[1].ActPartId); + Assert.AreEqual(1, _savedActParts[1].ActId); + Assert.AreEqual(2, _savedActParts[1].ActPartNumber); + Assert.AreEqual(6, _savedActParts[1].MaxTs); + } + + [TestMethod] + public async Task ActParser_ParsesMixedContent() + { + var content = @"# Act Data +#=================================== +Data 1 1 1 10 +Data 2 1 2 6 +Data 7 2 1 3 +end +#==================================# +# Title +A 1 zts1e +A 2 zts2e +~"; + CreateTestFile(content); + + var parser = new ActParser(_actDaoMock.Object, _actPartDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ImportActAsync(_tempFolder); + + Assert.AreEqual(2, _savedActs.Count); + Assert.AreEqual(3, _savedActParts.Count); + } + + [TestMethod] + public async Task ActParser_IgnoresCommentLines() + { + var content = "# This is a comment\nA\t1\tzts1e\n# Another comment\n~"; + CreateTestFile(content); + + var parser = new ActParser(_actDaoMock.Object, _actPartDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ImportActAsync(_tempFolder); + + Assert.AreEqual(1, _savedActs.Count); + Assert.AreEqual(0, _savedActParts.Count); + } + + [TestMethod] + public async Task ActParser_HandlesEmptyFile() + { + CreateTestFile(""); + + var parser = new ActParser(_actDaoMock.Object, _actPartDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ImportActAsync(_tempFolder); + + Assert.AreEqual(0, _savedActs.Count); + Assert.AreEqual(0, _savedActParts.Count); + } + + [TestMethod] + public async Task ActParser_SceneIsCalculatedFromActId() + { + var content = "A\t5\tzts5e\n~"; + CreateTestFile(content); + + var parser = new ActParser(_actDaoMock.Object, _actPartDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ImportActAsync(_tempFolder); + + Assert.AreEqual(1, _savedActs.Count); + Assert.AreEqual(5, _savedActs[0].ActId); + Assert.AreEqual(44, _savedActs[0].Scene); // 39 + 5 + } + } +} diff --git a/test/NosCore.Parser.Tests/CardParserTests.cs b/test/NosCore.Parser.Tests/CardParserTests.cs new file mode 100644 index 000000000..984830bbd --- /dev/null +++ b/test/NosCore.Parser.Tests/CardParserTests.cs @@ -0,0 +1,163 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using NosCore.Dao.Interfaces; +using NosCore.Data.Enumerations.I18N; +using NosCore.Data.StaticEntities; +using NosCore.Parser.Parsers; +using NosCore.Shared.I18N; +using Serilog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace NosCore.Parser.Tests +{ + [TestClass] + public class CardParserTests + { + private Mock _loggerMock = null!; + private Mock> _logLanguageMock = null!; + private Mock> _cardDaoMock = null!; + private Mock> _bCardDaoMock = null!; + private string _tempFolder = null!; + private List _savedCards = null!; + private List _savedBCards = null!; + + [TestInitialize] + public void Setup() + { + _loggerMock = new Mock(); + _logLanguageMock = new Mock>(); + _cardDaoMock = new Mock>(); + _bCardDaoMock = new Mock>(); + _savedCards = []; + _savedBCards = []; + + _cardDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(cards => _savedCards.AddRange(cards)) + .ReturnsAsync(true); + + _bCardDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(cards => _savedBCards.AddRange(cards)) + .ReturnsAsync(true); + + _tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(_tempFolder); + } + + [TestCleanup] + public void Cleanup() + { + if (Directory.Exists(_tempFolder)) + { + Directory.Delete(_tempFolder, true); + } + } + + private void CreateTestFile(string content) + { + File.WriteAllText(Path.Combine(_tempFolder, "Card.dat"), content); + } + + private static string CreateCardData( + short cardId = 1, + string name = "TestCard", + byte level = 1, + int effectId = 0, + byte buffType = 0, + int duration = 0, + int delay = 0, + short timeoutBuff = 0, + byte timeoutBuffChance = 0) + { + return $"\tVNUM\t{cardId}\r\n" + + $"\tNAME\t{name}\r\n" + + $"\tGROUP\t0\t{level}\t0\r\n" + + $"\tSTYLE\t0\t{buffType}\t0\t0\r\n" + + $"\tEFFECT\t{effectId}\t0\r\n" + + $"\tTIME\t{duration}\t{delay}\r\n" + + "\t1ST\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\r\n" + + "\t2ST\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\r\n" + + $"\tLAST\t{timeoutBuff}\t{timeoutBuffChance}\r\n" + + "\tDESC\tTest Description\r\n" + + "END"; + } + + [TestMethod] + public async Task CardParser_ParsesSingleCard() + { + var content = CreateCardData(cardId: 1, name: "Buff1", level: 5, duration: 100); + CreateTestFile(content); + + var parser = new CardParser(_cardDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertCardsAsync(_tempFolder); + + Assert.AreEqual(1, _savedCards.Count); + Assert.AreEqual(1, _savedCards[0].CardId); + Assert.AreEqual("Buff1", _savedCards[0].NameI18NKey); + Assert.AreEqual(5, _savedCards[0].Level); + Assert.AreEqual(100, _savedCards[0].Duration); + } + + [TestMethod] + public async Task CardParser_ParsesMultipleCards() + { + var content = CreateCardData(cardId: 1, name: "Buff1") + "\n#========================================================\n" + + CreateCardData(cardId: 2, name: "Buff2") + "\n#========================================================\n" + + CreateCardData(cardId: 3, name: "Buff3"); + CreateTestFile(content); + + var parser = new CardParser(_cardDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertCardsAsync(_tempFolder); + + Assert.AreEqual(3, _savedCards.Count); + } + + [TestMethod] + public async Task CardParser_DeduplicatesByCardId() + { + var content = CreateCardData(cardId: 1, name: "Buff1") + "\n#========================================================\n" + + CreateCardData(cardId: 1, name: "Buff1Duplicate"); + CreateTestFile(content); + + var parser = new CardParser(_cardDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertCardsAsync(_tempFolder); + + Assert.AreEqual(1, _savedCards.Count); + } + + [TestMethod] + public async Task CardParser_ParsesDelayField() + { + var content = CreateCardData(cardId: 1, delay: 500); + CreateTestFile(content); + + var parser = new CardParser(_cardDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertCardsAsync(_tempFolder); + + Assert.AreEqual(1, _savedCards.Count); + Assert.AreEqual(500, _savedCards[0].Delay); + } + + [TestMethod] + public async Task CardParser_HandlesEmptyFile() + { + CreateTestFile(""); + + var parser = new CardParser(_cardDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertCardsAsync(_tempFolder); + + Assert.AreEqual(0, _savedCards.Count); + } + } +} diff --git a/test/NosCore.Parser.Tests/FluentParserBuilderTests.cs b/test/NosCore.Parser.Tests/FluentParserBuilderTests.cs new file mode 100644 index 000000000..e47be1707 --- /dev/null +++ b/test/NosCore.Parser.Tests/FluentParserBuilderTests.cs @@ -0,0 +1,212 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using NosCore.Data.Enumerations.I18N; +using NosCore.Parser.Parsers.Generic; +using NosCore.Shared.I18N; +using Serilog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace NosCore.Parser.Tests +{ + [TestClass] + public class FluentParserBuilderTests + { + private Mock _loggerMock = null!; + private Mock> _logLanguageMock = null!; + private string _tempFolder = null!; + + [TestInitialize] + public void Setup() + { + _loggerMock = new Mock(); + _logLanguageMock = new Mock>(); + _tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(_tempFolder); + } + + [TestCleanup] + public void Cleanup() + { + if (Directory.Exists(_tempFolder)) + { + Directory.Delete(_tempFolder, true); + } + } + + private string CreateTestFile(string fileName, string content) + { + var filePath = Path.Combine(_tempFolder, fileName); + File.WriteAllText(filePath, content); + return filePath; + } + + [TestMethod] + public async Task FluentParser_WithExpressionExtractor_ParsesCorrectly() + { + var content = "VNUM\t1\t100\nNAME\tTestItem\nEND"; + var filePath = CreateTestFile("test.dat", content); + + var parser = FluentParserBuilder.Create(filePath, "END", 0) + .Field(x => x.Id, chunk => Convert.ToInt32(chunk["VNUM"][0][1])) + .Field(x => x.Price, chunk => Convert.ToInt64(chunk["VNUM"][0][2])) + .Field(x => x.Name, chunk => chunk["NAME"][0][1]) + .Build(_loggerMock.Object, _logLanguageMock.Object); + + var results = await parser.GetDtosAsync(); + + Assert.AreEqual(1, results.Count); + Assert.AreEqual(1, results[0].Id); + Assert.AreEqual(100, results[0].Price); + Assert.AreEqual("TestItem", results[0].Name); + } + + [TestMethod] + public async Task FluentParser_WithSimpleFieldMapping_ParsesCorrectly() + { + var content = "VNUM\t42\t999\nEND"; + var filePath = CreateTestFile("test.dat", content); + + var parser = FluentParserBuilder.Create(filePath, "END", 0) + .Field(x => x.Id, "VNUM", 0, 1) + .Field(x => x.Price, "VNUM", 0, 2) + .Build(_loggerMock.Object, _logLanguageMock.Object); + + var results = await parser.GetDtosAsync(); + + Assert.AreEqual(1, results.Count); + Assert.AreEqual(42, results[0].Id); + Assert.AreEqual(999, results[0].Price); + } + + [TestMethod] + public async Task FluentParser_WithCustomConverter_ParsesCorrectly() + { + var content = "DATA\thello_world\nEND"; + var filePath = CreateTestFile("test.dat", content); + + var parser = FluentParserBuilder.Create(filePath, "END", 0) + .Field(x => x.Name, "DATA", 0, 1, s => s.Replace("_", " ")) + .Build(_loggerMock.Object, _logLanguageMock.Object); + + var results = await parser.GetDtosAsync(); + + Assert.AreEqual(1, results.Count); + Assert.AreEqual("hello world", results[0].Name); + } + + [TestMethod] + public async Task FluentParser_WithCustomSplitter_ParsesCorrectly() + { + var content = "VNUM 1 100\nNAME Test Item\nEND"; + var filePath = CreateTestFile("test.dat", content); + + var parser = FluentParserBuilder.Create(filePath, "END", 0) + .WithSplitter(" ") + .Field(x => x.Id, "VNUM", 0, 1) + .Field(x => x.Price, "VNUM", 0, 2) + .Build(_loggerMock.Object, _logLanguageMock.Object); + + var results = await parser.GetDtosAsync(); + + Assert.AreEqual(1, results.Count); + Assert.AreEqual(1, results[0].Id); + Assert.AreEqual(100, results[0].Price); + } + + [TestMethod] + public async Task FluentParser_WithBooleanField_ParsesCorrectly() + { + var content = "DATA\t1\nEND"; + var filePath = CreateTestFile("test.dat", content); + + var parser = FluentParserBuilder.Create(filePath, "END", 0) + .Field(x => x.IsActive, "DATA", 0, 1) + .Build(_loggerMock.Object, _logLanguageMock.Object); + + var results = await parser.GetDtosAsync(); + + Assert.AreEqual(1, results.Count); + Assert.IsTrue(results[0].IsActive); + } + + [TestMethod] + public async Task FluentParser_WithEnumField_ParsesCorrectly() + { + var content = "DATA\t2\nEND"; + var filePath = CreateTestFile("test.dat", content); + + var parser = FluentParserBuilder.Create(filePath, "END", 0) + .Field(x => x.Status, "DATA", 0, 1) + .Build(_loggerMock.Object, _logLanguageMock.Object); + + var results = await parser.GetDtosAsync(); + + Assert.AreEqual(1, results.Count); + Assert.AreEqual(TestStatus.Completed, results[0].Status); + } + + [TestMethod] + public async Task FluentParser_ParsesMultipleRecords() + { + var content = "VNUM\t1\nEND\nVNUM\t2\nEND\nVNUM\t3\nEND"; + var filePath = CreateTestFile("test.dat", content); + + var parser = FluentParserBuilder.Create(filePath, "END", 0) + .Field(x => x.Id, "VNUM", 0, 1) + .Build(_loggerMock.Object, _logLanguageMock.Object); + + var results = await parser.GetDtosAsync(); + + Assert.AreEqual(3, results.Count); + } + + [TestMethod] + public async Task FluentParser_WithMethodReference_ParsesCorrectly() + { + var content = "DATA\t10\t20\t30\nEND"; + var filePath = CreateTestFile("test.dat", content); + + var parser = FluentParserBuilder.Create(filePath, "END", 0) + .Field(x => x.Id, CalculateSum) + .Build(_loggerMock.Object, _logLanguageMock.Object); + + var results = await parser.GetDtosAsync(); + + Assert.AreEqual(1, results.Count); + Assert.AreEqual(60, results[0].Id); + } + + private static object? CalculateSum(Dictionary chunk) + { + return Convert.ToInt32(chunk["DATA"][0][1]) + + Convert.ToInt32(chunk["DATA"][0][2]) + + Convert.ToInt32(chunk["DATA"][0][3]); + } + } + + public class TestDtoWithBool + { + public bool IsActive { get; set; } + } + + public enum TestStatus + { + Pending = 0, + InProgress = 1, + Completed = 2 + } + + public class TestDtoWithEnum + { + public TestStatus Status { get; set; } + } +} diff --git a/test/NosCore.Parser.Tests/GenericParserTests.cs b/test/NosCore.Parser.Tests/GenericParserTests.cs new file mode 100644 index 000000000..20e54e30f --- /dev/null +++ b/test/NosCore.Parser.Tests/GenericParserTests.cs @@ -0,0 +1,159 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using NosCore.Data.Enumerations.I18N; +using NosCore.Parser.Parsers.Generic; +using NosCore.Shared.I18N; +using Serilog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace NosCore.Parser.Tests +{ + [TestClass] + public class GenericParserTests + { + private Mock _loggerMock = null!; + private Mock> _logLanguageMock = null!; + private string _tempFolder = null!; + + [TestInitialize] + public void Setup() + { + _loggerMock = new Mock(); + _logLanguageMock = new Mock>(); + _tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(_tempFolder); + } + + [TestCleanup] + public void Cleanup() + { + if (Directory.Exists(_tempFolder)) + { + Directory.Delete(_tempFolder, true); + } + } + + private string CreateTestFile(string fileName, string content) + { + var filePath = Path.Combine(_tempFolder, fileName); + File.WriteAllText(filePath, content); + return filePath; + } + + [TestMethod] + public async Task GenericParser_ParsesSingleRecord() + { + var content = "VNUM\t1\t100\nNAME\tTestItem\nEND"; + var filePath = CreateTestFile("test.dat", content); + + var actionList = new Dictionary, object?>> + { + { nameof(TestDto.Id), chunk => Convert.ToInt32(chunk["VNUM"][0][1]) }, + { nameof(TestDto.Price), chunk => Convert.ToInt64(chunk["VNUM"][0][2]) }, + { nameof(TestDto.Name), chunk => chunk["NAME"][0][1] } + }; + + var parser = new GenericParser(filePath, "END", 0, actionList, _loggerMock.Object, _logLanguageMock.Object); + var results = await parser.GetDtosAsync(); + + Assert.AreEqual(1, results.Count); + Assert.AreEqual(1, results[0].Id); + Assert.AreEqual(100, results[0].Price); + Assert.AreEqual("TestItem", results[0].Name); + } + + [TestMethod] + public async Task GenericParser_ParsesMultipleRecords() + { + var content = "VNUM\t1\t100\nNAME\tItem1\nEND\nVNUM\t2\t200\nNAME\tItem2\nEND\nVNUM\t3\t300\nNAME\tItem3\nEND"; + var filePath = CreateTestFile("test.dat", content); + + var actionList = new Dictionary, object?>> + { + { nameof(TestDto.Id), chunk => Convert.ToInt32(chunk["VNUM"][0][1]) }, + { nameof(TestDto.Price), chunk => Convert.ToInt64(chunk["VNUM"][0][2]) }, + { nameof(TestDto.Name), chunk => chunk["NAME"][0][1] } + }; + + var parser = new GenericParser(filePath, "END", 0, actionList, _loggerMock.Object, _logLanguageMock.Object); + var results = await parser.GetDtosAsync(); + + Assert.AreEqual(3, results.Count); + } + + [TestMethod] + public async Task GenericParser_HandlesEmptyFile() + { + var content = ""; + var filePath = CreateTestFile("test.dat", content); + + var actionList = new Dictionary, object?>> + { + { nameof(TestDto.Id), chunk => Convert.ToInt32(chunk["VNUM"][0][1]) } + }; + + var parser = new GenericParser(filePath, "END", 0, actionList, _loggerMock.Object, _logLanguageMock.Object); + var results = await parser.GetDtosAsync(); + + Assert.AreEqual(0, results.Count); + } + + [TestMethod] + public async Task GenericParser_HandlesMultipleLinesWithSameKey() + { + var content = "DATA\t1\t100\nDATA\t2\t200\nEND"; + var filePath = CreateTestFile("test.dat", content); + + var actionList = new Dictionary, object?>> + { + { nameof(TestDto.Id), chunk => Convert.ToInt32(chunk["DATA"][0][1]) }, + { nameof(TestDto.Price), chunk => Convert.ToInt64(chunk["DATA"][1][2]) } + }; + + var parser = new GenericParser(filePath, "END", 0, actionList, _loggerMock.Object, _logLanguageMock.Object); + var results = await parser.GetDtosAsync(); + + Assert.AreEqual(1, results.Count); + Assert.AreEqual(1, results[0].Id); + Assert.AreEqual(200, results[0].Price); + } + + [TestMethod] + public async Task GenericParser_UsesCustomSplitter() + { + var content = "VNUM 1 100\nNAME TestItem\nEND"; + var filePath = CreateTestFile("test.dat", content); + + var actionList = new Dictionary, object?>> + { + { nameof(TestDto.Id), chunk => Convert.ToInt32(chunk["VNUM"][0][1]) }, + { nameof(TestDto.Price), chunk => Convert.ToInt64(chunk["VNUM"][0][2]) }, + { nameof(TestDto.Name), chunk => chunk["NAME"][0][1] } + }; + + var parser = new GenericParser(filePath, "END", 0, actionList, _loggerMock.Object, _logLanguageMock.Object); + var results = await parser.GetDtosAsync(" "); + + Assert.AreEqual(1, results.Count); + Assert.AreEqual(1, results[0].Id); + Assert.AreEqual(100, results[0].Price); + Assert.AreEqual("TestItem", results[0].Name); + } + } + + public class TestDto + { + public int Id { get; set; } + public long Price { get; set; } + public string Name { get; set; } = string.Empty; + } +} diff --git a/test/NosCore.Parser.Tests/ItemParserTests.cs b/test/NosCore.Parser.Tests/ItemParserTests.cs new file mode 100644 index 000000000..2fe14d9a7 --- /dev/null +++ b/test/NosCore.Parser.Tests/ItemParserTests.cs @@ -0,0 +1,291 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using NosCore.Dao.Interfaces; +using NosCore.Data.Enumerations.I18N; +using NosCore.Data.Enumerations; +using NosCore.Data.Enumerations.Items; +using NosCore.Data.StaticEntities; +using NosCore.Packets.Enumerations; +using NosCore.Parser.Parsers; +using NosCore.Shared.I18N; +using Serilog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace NosCore.Parser.Tests +{ + [TestClass] + public class ItemParserTests + { + private Mock _loggerMock = null!; + private Mock> _logLanguageMock = null!; + private Mock> _itemDaoMock = null!; + private Mock> _bCardDaoMock = null!; + private string _tempFolder = null!; + private List _savedItems = null!; + private List _savedBCards = null!; + + [TestInitialize] + public void Setup() + { + _loggerMock = new Mock(); + _logLanguageMock = new Mock>(); + _itemDaoMock = new Mock>(); + _bCardDaoMock = new Mock>(); + _savedItems = []; + _savedBCards = []; + + _itemDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(items => _savedItems.AddRange(items)) + .ReturnsAsync(true); + + _bCardDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(cards => _savedBCards.AddRange(cards)) + .ReturnsAsync(true); + + _tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(_tempFolder); + } + + [TestCleanup] + public void Cleanup() + { + if (Directory.Exists(_tempFolder)) + { + Directory.Delete(_tempFolder, true); + } + } + + private void CreateTestFile(string content) + { + File.WriteAllText(Path.Combine(_tempFolder, "Item.dat"), content); + } + + private static string CreateItemData( + short vnum = 1, + long price = 100, + string name = "TestItem", + int indexType = 0, + int indexSubType = 0, + int indexItemType = 0, + int equipmentSlot = -1, + int morph = 0, + int typeValue = 0, + int typeClass = 0, + string flags = "0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0", + string data = "0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0", + string buff = "0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0") + { + // Format: lines have a leading tab so keyword is at index 1 after splitting + return $"\tVNUM\t{vnum}\t{price}\r\n" + + $"\tNAME\t{name}\r\n" + + $"\tINDEX\t{indexType}\t{indexSubType}\t{indexItemType}\t{equipmentSlot}\t{morph}\t0\t0\r\n" + + $"\tTYPE\t{typeValue}\t{typeClass}\r\n" + + $"\tFLAG\t{flags}\r\n" + + $"\tDATA\t{data}\r\n" + + $"\tBUFF\t{buff}\r\n" + + "\tLINEDESC\t0\r\n" + + "Test Description\r\n" + + "END"; + } + + [TestMethod] + public async Task ItemParser_ParsesSingleItem() + { + var content = CreateItemData(vnum: 1, price: 500, name: "Sword"); + CreateTestFile(content); + + var parser = new ItemParser(_itemDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ParseAsync(_tempFolder); + + Assert.AreEqual(1, _savedItems.Count); + Assert.AreEqual(1, _savedItems[0].VNum); + Assert.AreEqual(500, _savedItems[0].Price); + Assert.AreEqual("Sword", _savedItems[0].NameI18NKey); + } + + [TestMethod] + public async Task ItemParser_ParsesMultipleItems() + { + var content = CreateItemData(vnum: 1, name: "Item1") + "\n#========================================================\n" + + CreateItemData(vnum: 2, name: "Item2") + "\n#========================================================\n" + + CreateItemData(vnum: 3, name: "Item3"); + CreateTestFile(content); + + var parser = new ItemParser(_itemDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ParseAsync(_tempFolder); + + Assert.AreEqual(3, _savedItems.Count); + } + + [TestMethod] + public async Task ItemParser_DeduplicatesByVNum() + { + var content = CreateItemData(vnum: 1, name: "Item1") + "\n#========================================================\n" + + CreateItemData(vnum: 1, name: "Item1Duplicate"); + CreateTestFile(content); + + var parser = new ItemParser(_itemDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ParseAsync(_tempFolder); + + Assert.AreEqual(1, _savedItems.Count); + } + + [TestMethod] + public async Task ItemParser_ParsesWeaponType() + { + // Equipment type is determined by INDEX field + // indexType=4 (Equipment), indexItemType=0 (Weapon subtype), equipmentSlot=0 (MainWeapon) + var content = CreateItemData( + vnum: 1, + indexType: 4, + indexSubType: 0, + indexItemType: 0, + equipmentSlot: 0, + typeClass: 1 + ); + CreateTestFile(content); + + var parser = new ItemParser(_itemDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ParseAsync(_tempFolder); + + Assert.AreEqual(1, _savedItems.Count); + Assert.AreEqual(ItemType.Weapon, _savedItems[0].ItemType); + Assert.AreEqual(EquipmentType.MainWeapon, _savedItems[0].EquipmentSlot); + } + + [TestMethod] + public async Task ItemParser_ParsesArmorType() + { + // INDEX[0][2]=4 (Equipment pocket), INDEX[0][3]=1 (Armor suffix) → ItemType = "01" = Armor + // Equipment slot at INDEX[0][5] = 1 (Armor) + var content = CreateItemData( + vnum: 100, + indexType: 4, + indexSubType: 1, // This is the itemType suffix (1 = Armor when combined with Equipment) + indexItemType: 0, + equipmentSlot: 1, + typeClass: 1 + ); + CreateTestFile(content); + + var parser = new ItemParser(_itemDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ParseAsync(_tempFolder); + + Assert.AreEqual(1, _savedItems.Count); + Assert.AreEqual(ItemType.Armor, _savedItems[0].ItemType); + Assert.AreEqual(EquipmentType.Armor, _savedItems[0].EquipmentSlot); + } + + [TestMethod] + public async Task ItemParser_ParsesNonDroppableFlag() + { + // IsDroppable = FLAG[0][6] == "0", so FLAG[0][6] = "1" means NOT droppable + // FLAG[0][6] is position 4 in the flags string (6 - 2 for "" and "FLAG") + var content = CreateItemData( + vnum: 1, + flags: "0\t0\t0\t0\t1\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0" + ); + CreateTestFile(content); + + var parser = new ItemParser(_itemDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ParseAsync(_tempFolder); + + Assert.AreEqual(1, _savedItems.Count); + Assert.IsFalse(_savedItems[0].IsDroppable); + } + + [TestMethod] + public async Task ItemParser_ParsesTradableItem() + { + var content = CreateItemData( + vnum: 1, + flags: "0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0" + ); + CreateTestFile(content); + + var parser = new ItemParser(_itemDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ParseAsync(_tempFolder); + + Assert.AreEqual(1, _savedItems.Count); + Assert.IsTrue(_savedItems[0].IsDroppable); + Assert.IsTrue(_savedItems[0].IsTradable); + Assert.IsTrue(_savedItems[0].IsSoldable); + } + + [TestMethod] + public async Task ItemParser_ParsesBCards() + { + var content = CreateItemData( + vnum: 1, + buff: "0\t1\t10\t100\t50\t1\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0" + ); + CreateTestFile(content); + + var parser = new ItemParser(_itemDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ParseAsync(_tempFolder); + + Assert.AreEqual(1, _savedItems.Count); + Assert.IsTrue(_savedBCards.Count > 0); + } + + [TestMethod] + public async Task ItemParser_HandlesEmptyFile() + { + CreateTestFile(""); + + var parser = new ItemParser(_itemDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ParseAsync(_tempFolder); + + Assert.AreEqual(0, _savedItems.Count); + } + + [TestMethod] + public async Task ItemParser_ParsesHeroicFlag() + { + // IsHeroic is at FLAG[0][22], which is the 20th position in flags string (22 - 2 for "" and "FLAG") + var content = CreateItemData( + vnum: 1, + flags: "0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t1\t0\t0\t0" + ); + CreateTestFile(content); + + var parser = new ItemParser(_itemDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ParseAsync(_tempFolder); + + Assert.AreEqual(1, _savedItems.Count); + Assert.IsTrue(_savedItems[0].IsHeroic); + } + + [TestMethod] + public async Task ItemParser_ParsesConsumableItem() + { + var content = CreateItemData( + vnum: 2000, + indexType: 9, + indexSubType: 0, + indexItemType: 0, + equipmentSlot: -1, + data: "0\t0\t100\t0\t50\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0" + ); + CreateTestFile(content); + + var parser = new ItemParser(_itemDaoMock.Object, _bCardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ParseAsync(_tempFolder); + + Assert.AreEqual(1, _savedItems.Count); + Assert.AreEqual(NoscorePocketType.Main, _savedItems[0].Type); + } + } +} diff --git a/test/NosCore.Parser.Tests/MapMonsterParserTests.cs b/test/NosCore.Parser.Tests/MapMonsterParserTests.cs new file mode 100644 index 000000000..58775358b --- /dev/null +++ b/test/NosCore.Parser.Tests/MapMonsterParserTests.cs @@ -0,0 +1,154 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using NosCore.Dao.Interfaces; +using NosCore.Data.Dto; +using NosCore.Data.Enumerations.I18N; +using NosCore.Data.StaticEntities; +using NosCore.Parser.Parsers; +using NosCore.Shared.I18N; +using Serilog; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace NosCore.Parser.Tests +{ + [TestClass] + public class MapMonsterParserTests + { + private Mock _loggerMock = null!; + private Mock> _logLanguageMock = null!; + private Mock> _mapMonsterDaoMock = null!; + private Mock> _npcMonsterDaoMock = null!; + private List _savedMonsters = null!; + + [TestInitialize] + public void Setup() + { + _loggerMock = new Mock(); + _logLanguageMock = new Mock>(); + _mapMonsterDaoMock = new Mock>(); + _npcMonsterDaoMock = new Mock>(); + _savedMonsters = []; + + _mapMonsterDaoMock + .Setup(x => x.LoadAll()) + .Returns(new List()); + + _mapMonsterDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(monsters => _savedMonsters.AddRange(monsters)) + .ReturnsAsync(true); + + _npcMonsterDaoMock + .Setup(x => x.LoadAll()) + .Returns(new List + { + new() { NpcMonsterVNum = 1 }, + new() { NpcMonsterVNum = 2 }, + new() { NpcMonsterVNum = 100 } + }); + } + + [TestMethod] + public async Task MapMonsterParser_ParsesSingleMonster() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "in", "3", "1", "1001", "100", "100", "2", "100", "0" } + }; + + var parser = new MapMonsterParser(_mapMonsterDaoMock.Object, _npcMonsterDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertMapMonsterAsync(packets); + + Assert.AreEqual(1, _savedMonsters.Count); + Assert.AreEqual(1, _savedMonsters[0].MapId); + Assert.AreEqual(1, _savedMonsters[0].VNum); + Assert.AreEqual(1001, _savedMonsters[0].MapMonsterId); + Assert.AreEqual(100, _savedMonsters[0].MapX); + Assert.AreEqual(100, _savedMonsters[0].MapY); + } + + [TestMethod] + public async Task MapMonsterParser_ParsesMultipleMonsters() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "in", "3", "1", "1001", "100", "100", "2", "100", "0" }, + new[] { "in", "3", "2", "1002", "110", "110", "3", "100", "0" } + }; + + var parser = new MapMonsterParser(_mapMonsterDaoMock.Object, _npcMonsterDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertMapMonsterAsync(packets); + + Assert.AreEqual(2, _savedMonsters.Count); + } + + [TestMethod] + public async Task MapMonsterParser_SkipsDuplicateMapMonsterId() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "in", "3", "1", "1001", "100", "100", "2", "100", "0" }, + new[] { "in", "3", "1", "1001", "110", "110", "3", "100", "0" } + }; + + var parser = new MapMonsterParser(_mapMonsterDaoMock.Object, _npcMonsterDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertMapMonsterAsync(packets); + + Assert.AreEqual(1, _savedMonsters.Count); + } + + [TestMethod] + public async Task MapMonsterParser_SkipsUnknownMonsterVNum() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "in", "3", "999", "1001", "100", "100", "2", "100", "0" } + }; + + var parser = new MapMonsterParser(_mapMonsterDaoMock.Object, _npcMonsterDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertMapMonsterAsync(packets); + + Assert.AreEqual(0, _savedMonsters.Count); + } + + [TestMethod] + public async Task MapMonsterParser_SetsIsMovingFromMvPackets() + { + var packets = new List + { + new[] { "mv", "3", "1001", "100", "100", "5" }, + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "in", "3", "1", "1001", "100", "100", "2", "100", "0" } + }; + + var parser = new MapMonsterParser(_mapMonsterDaoMock.Object, _npcMonsterDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertMapMonsterAsync(packets); + + Assert.AreEqual(1, _savedMonsters.Count); + Assert.IsTrue(_savedMonsters[0].IsMoving); + } + + [TestMethod] + public async Task MapMonsterParser_HandlesEmptyPacketList() + { + var packets = new List(); + + var parser = new MapMonsterParser(_mapMonsterDaoMock.Object, _npcMonsterDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertMapMonsterAsync(packets); + + Assert.AreEqual(0, _savedMonsters.Count); + } + } +} diff --git a/test/NosCore.Parser.Tests/MapNpcParserTests.cs b/test/NosCore.Parser.Tests/MapNpcParserTests.cs new file mode 100644 index 000000000..ffe02ea32 --- /dev/null +++ b/test/NosCore.Parser.Tests/MapNpcParserTests.cs @@ -0,0 +1,163 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using NosCore.Dao.Interfaces; +using NosCore.Data.Dto; +using NosCore.Data.Enumerations.I18N; +using NosCore.Data.StaticEntities; +using NosCore.Parser.Parsers; +using NosCore.Shared.I18N; +using Serilog; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace NosCore.Parser.Tests +{ + [TestClass] + public class MapNpcParserTests + { + private Mock _loggerMock = null!; + private Mock> _logLanguageMock = null!; + private Mock> _mapNpcDaoMock = null!; + private Mock> _npcMonsterDaoMock = null!; + private Mock> _npcTalkDaoMock = null!; + private List _savedNpcs = null!; + + [TestInitialize] + public void Setup() + { + _loggerMock = new Mock(); + _logLanguageMock = new Mock>(); + _mapNpcDaoMock = new Mock>(); + _npcMonsterDaoMock = new Mock>(); + _npcTalkDaoMock = new Mock>(); + _savedNpcs = []; + + _mapNpcDaoMock + .Setup(x => x.LoadAll()) + .Returns(new List()); + + _mapNpcDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(npcs => _savedNpcs.AddRange(npcs)) + .ReturnsAsync(true); + + _npcMonsterDaoMock + .Setup(x => x.LoadAll()) + .Returns(new List + { + new() { NpcMonsterVNum = 1 }, + new() { NpcMonsterVNum = 2 }, + new() { NpcMonsterVNum = 100 } + }); + + _npcTalkDaoMock + .Setup(x => x.LoadAll()) + .Returns(new List + { + new() { DialogId = 1 }, + new() { DialogId = 2 } + }); + } + + [TestMethod] + public async Task MapNpcParser_ParsesSingleNpc() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "in", "2", "1", "100", "50", "60", "2", "100", "0", "1", "0", "0", "0", "1", "0" } + }; + + var parser = new MapNpcParser(_mapNpcDaoMock.Object, _npcMonsterDaoMock.Object, _npcTalkDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertMapNpcsAsync(packets); + + Assert.AreEqual(1, _savedNpcs.Count); + Assert.AreEqual(1, _savedNpcs[0].MapId); + Assert.AreEqual(1, _savedNpcs[0].VNum); + Assert.AreEqual(100, _savedNpcs[0].MapNpcId); + Assert.AreEqual(50, _savedNpcs[0].MapX); + Assert.AreEqual(60, _savedNpcs[0].MapY); + } + + [TestMethod] + public async Task MapNpcParser_ParsesMultipleNpcs() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "in", "2", "1", "100", "50", "60", "2", "100", "0", "1", "0", "0", "0", "1", "0" }, + new[] { "in", "2", "2", "101", "70", "80", "3", "100", "0", "2", "0", "0", "0", "1", "0" } + }; + + var parser = new MapNpcParser(_mapNpcDaoMock.Object, _npcMonsterDaoMock.Object, _npcTalkDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertMapNpcsAsync(packets); + + Assert.AreEqual(2, _savedNpcs.Count); + } + + [TestMethod] + public async Task MapNpcParser_SkipsDuplicateMapNpcId() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "in", "2", "1", "100", "50", "60", "2", "100", "0", "1", "0", "0", "0", "1", "0" }, + new[] { "in", "2", "1", "100", "70", "80", "3", "100", "0", "2", "0", "0", "0", "1", "0" } + }; + + var parser = new MapNpcParser(_mapNpcDaoMock.Object, _npcMonsterDaoMock.Object, _npcTalkDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertMapNpcsAsync(packets); + + Assert.AreEqual(1, _savedNpcs.Count); + } + + [TestMethod] + public async Task MapNpcParser_SkipsUnknownNpcVNum() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "in", "2", "999", "100", "50", "60", "2", "100", "0", "1", "0", "0", "0", "1", "0" } + }; + + var parser = new MapNpcParser(_mapNpcDaoMock.Object, _npcMonsterDaoMock.Object, _npcTalkDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertMapNpcsAsync(packets); + + Assert.AreEqual(0, _savedNpcs.Count); + } + + [TestMethod] + public async Task MapNpcParser_SetsDialogFromNpcTalk() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "in", "2", "1", "100", "50", "60", "2", "100", "0", "1", "0", "0", "0", "1", "0" } + }; + + var parser = new MapNpcParser(_mapNpcDaoMock.Object, _npcMonsterDaoMock.Object, _npcTalkDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertMapNpcsAsync(packets); + + Assert.AreEqual(1, _savedNpcs.Count); + Assert.AreEqual((short)1, _savedNpcs[0].Dialog); + } + + [TestMethod] + public async Task MapNpcParser_HandlesEmptyPacketList() + { + var packets = new List(); + + var parser = new MapNpcParser(_mapNpcDaoMock.Object, _npcMonsterDaoMock.Object, _npcTalkDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertMapNpcsAsync(packets); + + Assert.AreEqual(0, _savedNpcs.Count); + } + } +} diff --git a/test/NosCore.Parser.Tests/NosCore.Parser.Tests.csproj b/test/NosCore.Parser.Tests/NosCore.Parser.Tests.csproj new file mode 100644 index 000000000..ac2465066 --- /dev/null +++ b/test/NosCore.Parser.Tests/NosCore.Parser.Tests.csproj @@ -0,0 +1,39 @@ + + + + net10.0 + false + preview + enable + true + MSTEST0001;MSTEST0037;MSTEST0044;NU1701 + + + + ..\..\build\ + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/test/NosCore.Parser.Tests/PortalParserTests.cs b/test/NosCore.Parser.Tests/PortalParserTests.cs new file mode 100644 index 000000000..a72b81f29 --- /dev/null +++ b/test/NosCore.Parser.Tests/PortalParserTests.cs @@ -0,0 +1,134 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using NosCore.Dao.Interfaces; +using NosCore.Data.Enumerations.I18N; +using NosCore.Data.StaticEntities; +using NosCore.Parser.Parsers; +using NosCore.Shared.I18N; +using Serilog; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace NosCore.Parser.Tests +{ + [TestClass] + public class PortalParserTests + { + private Mock _loggerMock = null!; + private Mock> _logLanguageMock = null!; + private Mock> _portalDaoMock = null!; + private Mock> _mapDaoMock = null!; + private List _savedPortals = null!; + + [TestInitialize] + public void Setup() + { + _loggerMock = new Mock(); + _logLanguageMock = new Mock>(); + _portalDaoMock = new Mock>(); + _mapDaoMock = new Mock>(); + _savedPortals = []; + + _portalDaoMock + .Setup(x => x.LoadAll()) + .Returns(new List()); + + _portalDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny())) + .Callback(portal => _savedPortals.Add(portal)) + .ReturnsAsync((PortalDto p) => p); + + _portalDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(portals => _savedPortals.AddRange(portals)) + .ReturnsAsync(true); + + _mapDaoMock + .Setup(x => x.LoadAll()) + .Returns(new List + { + new() { MapId = 1 }, + new() { MapId = 2 }, + new() { MapId = 3 }, + new() { MapId = 150 }, + new() { MapId = 98 }, + new() { MapId = 20001 }, + new() { MapId = 2586 }, + new() { MapId = 145 }, + new() { MapId = 2587 }, + new() { MapId = 189 } + }); + } + + [TestMethod] + public async Task PortalParser_ParsesPortalsBetweenMaps() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "gp", "100", "100", "2", "0" }, + new[] { "at", "1", "2", "50", "50", "2", "0", "0", "0" }, + new[] { "gp", "100", "100", "1", "0" } + }; + + var parser = new PortalParser(_loggerMock.Object, _mapDaoMock.Object, _portalDaoMock.Object, _logLanguageMock.Object); + await parser.InsertPortalsAsync(packets); + + var portalCount = _savedPortals.Count(p => p.SourceMapId == 1 || p.SourceMapId == 2); + Assert.IsTrue(portalCount >= 2); + } + + [TestMethod] + public async Task PortalParser_CreatesHardcodedPortals() + { + var packets = new List(); + + var parser = new PortalParser(_loggerMock.Object, _mapDaoMock.Object, _portalDaoMock.Object, _logLanguageMock.Object); + await parser.InsertPortalsAsync(packets); + + Assert.IsTrue(_savedPortals.Any(p => p.SourceMapId == 150)); + Assert.IsTrue(_savedPortals.Any(p => p.SourceMapId == 20001)); + Assert.IsTrue(_savedPortals.Any(p => p.SourceMapId == 2586)); + Assert.IsTrue(_savedPortals.Any(p => p.SourceMapId == 2587)); + } + + [TestMethod] + public async Task PortalParser_SkipsPortalsToUnknownMaps() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "gp", "100", "100", "999", "0" } + }; + + var parser = new PortalParser(_loggerMock.Object, _mapDaoMock.Object, _portalDaoMock.Object, _logLanguageMock.Object); + await parser.InsertPortalsAsync(packets); + + Assert.IsFalse(_savedPortals.Any(p => p.DestinationMapId == 999)); + } + + [TestMethod] + public async Task PortalParser_SkipsDuplicatePortals() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "gp", "100", "100", "2", "0" }, + new[] { "gp", "100", "100", "2", "0" } + }; + + var parser = new PortalParser(_loggerMock.Object, _mapDaoMock.Object, _portalDaoMock.Object, _logLanguageMock.Object); + await parser.InsertPortalsAsync(packets); + + var duplicatePortals = _savedPortals.Count(p => p.SourceMapId == 1 && p.SourceX == 100 && p.SourceY == 100 && p.DestinationMapId == 2); + Assert.IsTrue(duplicatePortals <= 1); + } + } +} diff --git a/test/NosCore.Parser.Tests/QuestParserTests.cs b/test/NosCore.Parser.Tests/QuestParserTests.cs new file mode 100644 index 000000000..67e966af4 --- /dev/null +++ b/test/NosCore.Parser.Tests/QuestParserTests.cs @@ -0,0 +1,198 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using NosCore.Dao.Interfaces; +using NosCore.Data.Enumerations.I18N; +using NosCore.Data.StaticEntities; +using NosCore.Parser.Parsers; +using NosCore.Shared.I18N; +using Serilog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace NosCore.Parser.Tests +{ + [TestClass] + public class QuestParserTests + { + private Mock _loggerMock = null!; + private Mock> _logLanguageMock = null!; + private Mock> _questDaoMock = null!; + private Mock> _questObjectiveDaoMock = null!; + private Mock> _questRewardDaoMock = null!; + private Mock> _questQuestRewardDaoMock = null!; + private string _tempFolder = null!; + private List _savedQuests = null!; + private List _savedObjectives = null!; + private List _savedQuestRewards = null!; + + [TestInitialize] + public void Setup() + { + _loggerMock = new Mock(); + _logLanguageMock = new Mock>(); + _questDaoMock = new Mock>(); + _questObjectiveDaoMock = new Mock>(); + _questRewardDaoMock = new Mock>(); + _questQuestRewardDaoMock = new Mock>(); + _savedQuests = []; + _savedObjectives = []; + _savedQuestRewards = []; + + _questDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(quests => _savedQuests.AddRange(quests)) + .ReturnsAsync(true); + + _questObjectiveDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(objectives => _savedObjectives.AddRange(objectives)) + .ReturnsAsync(true); + + _questQuestRewardDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(rewards => _savedQuestRewards.AddRange(rewards)) + .ReturnsAsync(true); + + _questRewardDaoMock + .Setup(x => x.LoadAll()) + .Returns(new List()); + + _tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(_tempFolder); + } + + [TestCleanup] + public void Cleanup() + { + if (Directory.Exists(_tempFolder)) + { + Directory.Delete(_tempFolder, true); + } + } + + private void CreateTestFile(string content) + { + File.WriteAllText(Path.Combine(_tempFolder, "quest.dat"), content); + } + + private static string CreateQuestData( + short questId = 1, + int questType = 1, + bool autoFinish = false, + bool isDaily = false, + short? requiredQuestId = null, + bool isSecondary = false, + byte levelMin = 1, + byte levelMax = 99, + string title = "TestQuest", + string desc = "TestDescription", + short? targetX = null, + short? targetY = null, + short? targetMap = null, + int? startDialogId = null, + int? endDialogId = null, + short? nextQuestId = null) + { + return $"BEGIN\r\n" + + $"VNUM\t{questId}\t{questType}\t{(autoFinish ? "1" : "0")}\t{(isDaily ? "-1" : "0")}\t{requiredQuestId?.ToString() ?? "-1"}\t{(isSecondary ? "1" : "-1")}\r\n" + + $"LEVEL\t{levelMin}\t{levelMax}\r\n" + + $"TITLE\t{title}\r\n" + + $"DESC\t{desc}\r\n" + + $"TALK\t{startDialogId?.ToString() ?? "-1"}\t{endDialogId?.ToString() ?? "-1"}\t0\t0\r\n" + + $"TARGET\t{targetX?.ToString() ?? "-1"}\t{targetY?.ToString() ?? "-1"}\t{targetMap?.ToString() ?? "-1"}\r\n" + + $"DATA\t-1\t-1\t-1\t-1\r\n" + + $"PRIZE\t-1\t-1\t-1\t-1\r\n" + + $"LINK\t{nextQuestId?.ToString() ?? "-1"}\r\n" + + "END"; + } + + [TestMethod] + public async Task QuestParser_ParsesSingleQuest() + { + var content = CreateQuestData(questId: 1, title: "FirstQuest", levelMin: 10, levelMax: 50); + CreateTestFile(content); + + var parser = new QuestParser(_questDaoMock.Object, _questObjectiveDaoMock.Object, _questRewardDaoMock.Object, _questQuestRewardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ImportQuestsAsync(_tempFolder); + + Assert.AreEqual(1, _savedQuests.Count); + Assert.AreEqual(1, _savedQuests[0].QuestId); + Assert.AreEqual("FirstQuest", _savedQuests[0].TitleI18NKey); + Assert.AreEqual(10, _savedQuests[0].LevelMin); + Assert.AreEqual(50, _savedQuests[0].LevelMax); + } + + [TestMethod] + public async Task QuestParser_ParsesMultipleQuests() + { + var content = CreateQuestData(questId: 1, title: "Quest1") + "\n#=======\n" + + CreateQuestData(questId: 2, title: "Quest2") + "\n#=======\n" + + CreateQuestData(questId: 3, title: "Quest3"); + CreateTestFile(content); + + var parser = new QuestParser(_questDaoMock.Object, _questObjectiveDaoMock.Object, _questRewardDaoMock.Object, _questQuestRewardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ImportQuestsAsync(_tempFolder); + + Assert.AreEqual(3, _savedQuests.Count); + } + + [TestMethod] + public async Task QuestParser_ParsesAutoFinishFlag() + { + var content = CreateQuestData(questId: 1, autoFinish: true); + CreateTestFile(content); + + var parser = new QuestParser(_questDaoMock.Object, _questObjectiveDaoMock.Object, _questRewardDaoMock.Object, _questQuestRewardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ImportQuestsAsync(_tempFolder); + + Assert.AreEqual(1, _savedQuests.Count); + Assert.IsTrue(_savedQuests[0].AutoFinish); + } + + [TestMethod] + public async Task QuestParser_ParsesDailyFlag() + { + var content = CreateQuestData(questId: 1, isDaily: true); + CreateTestFile(content); + + var parser = new QuestParser(_questDaoMock.Object, _questObjectiveDaoMock.Object, _questRewardDaoMock.Object, _questQuestRewardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ImportQuestsAsync(_tempFolder); + + Assert.AreEqual(1, _savedQuests.Count); + Assert.IsTrue(_savedQuests[0].IsDaily); + } + + [TestMethod] + public async Task QuestParser_ParsesNextQuestId() + { + var content = CreateQuestData(questId: 1, nextQuestId: 2); + CreateTestFile(content); + + var parser = new QuestParser(_questDaoMock.Object, _questObjectiveDaoMock.Object, _questRewardDaoMock.Object, _questQuestRewardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ImportQuestsAsync(_tempFolder); + + Assert.AreEqual(1, _savedQuests.Count); + Assert.AreEqual((short)2, _savedQuests[0].NextQuestId); + } + + [TestMethod] + public async Task QuestParser_HandlesEmptyFile() + { + CreateTestFile(""); + + var parser = new QuestParser(_questDaoMock.Object, _questObjectiveDaoMock.Object, _questRewardDaoMock.Object, _questQuestRewardDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.ImportQuestsAsync(_tempFolder); + + Assert.AreEqual(0, _savedQuests.Count); + } + } +} diff --git a/test/NosCore.Parser.Tests/ShopParserTests.cs b/test/NosCore.Parser.Tests/ShopParserTests.cs new file mode 100644 index 000000000..71d4a820d --- /dev/null +++ b/test/NosCore.Parser.Tests/ShopParserTests.cs @@ -0,0 +1,146 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using NosCore.Dao.Interfaces; +using NosCore.Data.Dto; +using NosCore.Data.Enumerations.I18N; +using NosCore.Data.StaticEntities; +using NosCore.Parser.Parsers; +using NosCore.Shared.I18N; +using Serilog; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace NosCore.Parser.Tests +{ + [TestClass] + public class ShopParserTests + { + private Mock _loggerMock = null!; + private Mock> _logLanguageMock = null!; + private Mock> _shopDaoMock = null!; + private Mock> _mapNpcDaoMock = null!; + private List _savedShops = null!; + + [TestInitialize] + public void Setup() + { + _loggerMock = new Mock(); + _logLanguageMock = new Mock>(); + _shopDaoMock = new Mock>(); + _mapNpcDaoMock = new Mock>(); + _savedShops = []; + + _shopDaoMock + .Setup(x => x.LoadAll()) + .Returns(new List()); + + _shopDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(shops => _savedShops.AddRange(shops)) + .ReturnsAsync(true); + + _mapNpcDaoMock + .Setup(x => x.LoadAll()) + .Returns(new List + { + new() { MapNpcId = 100, VNum = 1 }, + new() { MapNpcId = 101, VNum = 2 }, + new() { MapNpcId = 102, VNum = 3 } + }); + } + + [TestMethod] + public async Task ShopParser_ParsesSingleShop() + { + var packets = new List + { + new[] { "shop", "2", "100", "0", "1", "0", "Shop", "Name" } + }; + + var parser = new ShopParser(_shopDaoMock.Object, _mapNpcDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertShopsAsync(packets); + + Assert.AreEqual(1, _savedShops.Count); + Assert.AreEqual(100, _savedShops[0].MapNpcId); + Assert.AreEqual(1, _savedShops[0].MenuType); + Assert.AreEqual(0, _savedShops[0].ShopType); + } + + [TestMethod] + public async Task ShopParser_ParsesMultipleShops() + { + var packets = new List + { + new[] { "shop", "2", "100", "0", "1", "0", "Shop1" }, + new[] { "shop", "2", "101", "0", "2", "1", "Shop2" } + }; + + var parser = new ShopParser(_shopDaoMock.Object, _mapNpcDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertShopsAsync(packets); + + Assert.AreEqual(2, _savedShops.Count); + } + + [TestMethod] + public async Task ShopParser_SkipsDuplicateMapNpcId() + { + var packets = new List + { + new[] { "shop", "2", "100", "0", "1", "0", "Shop1" }, + new[] { "shop", "2", "100", "0", "2", "1", "Shop1Duplicate" } + }; + + var parser = new ShopParser(_shopDaoMock.Object, _mapNpcDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertShopsAsync(packets); + + Assert.AreEqual(1, _savedShops.Count); + } + + [TestMethod] + public async Task ShopParser_SkipsUnknownNpcId() + { + var packets = new List + { + new[] { "shop", "2", "999", "0", "1", "0", "UnknownShop" } + }; + + var parser = new ShopParser(_shopDaoMock.Object, _mapNpcDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertShopsAsync(packets); + + Assert.AreEqual(0, _savedShops.Count); + } + + [TestMethod] + public async Task ShopParser_HandlesEmptyPacketList() + { + var packets = new List(); + + var parser = new ShopParser(_shopDaoMock.Object, _mapNpcDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertShopsAsync(packets); + + Assert.AreEqual(0, _savedShops.Count); + } + + [TestMethod] + public async Task ShopParser_IgnoresNonShopPackets() + { + var packets = new List + { + new[] { "at", "1", "1", "50", "50", "2", "0", "0", "0" }, + new[] { "shop", "2", "100", "0", "1", "0", "Shop1" } + }; + + var parser = new ShopParser(_shopDaoMock.Object, _mapNpcDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertShopsAsync(packets); + + Assert.AreEqual(1, _savedShops.Count); + } + } +} diff --git a/test/NosCore.Parser.Tests/SkillParserTests.cs b/test/NosCore.Parser.Tests/SkillParserTests.cs new file mode 100644 index 000000000..035ce1731 --- /dev/null +++ b/test/NosCore.Parser.Tests/SkillParserTests.cs @@ -0,0 +1,196 @@ +// __ _ __ __ ___ __ ___ ___ +// | \| |/__\ /' _/ / _//__\| _ \ __| +// | | ' | \/ |`._`.| \_| \/ | v / _| +// |_|\__|\__/ |___/ \__/\__/|_|_\___| +// + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using NosCore.Dao.Interfaces; +using NosCore.Data.Enumerations.I18N; +using NosCore.Data.StaticEntities; +using NosCore.Parser.Parsers; +using NosCore.Shared.I18N; +using Serilog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace NosCore.Parser.Tests +{ + [TestClass] + public class SkillParserTests + { + private Mock _loggerMock = null!; + private Mock> _logLanguageMock = null!; + private Mock> _skillDaoMock = null!; + private Mock> _bCardDaoMock = null!; + private Mock> _comboDaoMock = null!; + private string _tempFolder = null!; + private List _savedSkills = null!; + private List _savedBCards = null!; + private List _savedCombos = null!; + + [TestInitialize] + public void Setup() + { + _loggerMock = new Mock(); + _logLanguageMock = new Mock>(); + _skillDaoMock = new Mock>(); + _bCardDaoMock = new Mock>(); + _comboDaoMock = new Mock>(); + _savedSkills = []; + _savedBCards = []; + _savedCombos = []; + + _skillDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(skills => _savedSkills.AddRange(skills)) + .ReturnsAsync(true); + + _bCardDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(cards => _savedBCards.AddRange(cards)) + .ReturnsAsync(true); + + _comboDaoMock + .Setup(x => x.TryInsertOrUpdateAsync(It.IsAny>())) + .Callback>(combos => _savedCombos.AddRange(combos)) + .ReturnsAsync(true); + + _tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(_tempFolder); + } + + [TestCleanup] + public void Cleanup() + { + if (Directory.Exists(_tempFolder)) + { + Directory.Delete(_tempFolder, true); + } + } + + private void CreateTestFile(string content) + { + File.WriteAllText(Path.Combine(_tempFolder, "Skill.dat"), content); + } + + private static string CreateSkillData( + short skillVNum = 1, + string name = "TestSkill", + byte skillType = 0, + short castId = 0, + byte classType = 0, + byte type = 0, + byte element = 0, + byte cpCost = 0, + int price = 0, + short castEffect = 0, + short castAnimation = 0, + short effect = 0, + short attackAnimation = 0, + byte targetType = 0, + byte hitType = 0, + byte range = 0, + byte targetRange = 0, + short upgradeSkill = 0, + short upgradeType = 0, + short castTime = 0, + short cooldown = 0, + short mpCost = 0, + short itemVNum = 0, + byte levelMinimum = 0) + { + return $"\tVNUM\t{skillVNum}\r\n" + + $"\tNAME\t{name}\r\n" + + $"\tTYPE\t{skillType}\t{castId}\t{classType}\t{type}\t0\t{element}\t0\r\n" + + $"\tCOST\t{cpCost}\t{price}\t0\r\n" + + $"\tLEVEL\t{levelMinimum}\t-1\t-1\t-1\t-1\r\n" + + $"\tEFFECT\t0\t{castEffect}\t{castAnimation}\t{effect}\t{attackAnimation}\t0\t0\t0\t0\r\n" + + $"\tTARGET\t{targetType}\t{hitType}\t{range}\t{targetRange}\t0\r\n" + + $"\tDATA\t{upgradeSkill}\t{upgradeType}\t0\t0\t{castTime}\t{cooldown}\t0\t0\t{mpCost}\t0\t{itemVNum}\t0\t0\t0\t0\r\n" + + "\tBASIC\t0\t0\t0\t0\t0\t0\r\n" + + "\tBASIC\t1\t0\t0\t0\t0\t0\r\n" + + "\tBASIC\t2\t0\t0\t0\t0\t0\r\n" + + "\tBASIC\t3\t0\t0\t0\t0\t0\r\n" + + "\tBASIC\t4\t0\t0\t0\t0\t0\r\n" + + "\tFCOMBO\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\r\n" + + "\tCELL\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\r\n" + + "\tZ_DESC\t0\r\n" + + "#========================================================="; + } + + [TestMethod] + public async Task SkillParser_ParsesSingleSkill() + { + var content = CreateSkillData(skillVNum: 1, name: "Fireball", mpCost: 50, cooldown: 10); + CreateTestFile(content); + + var parser = new SkillParser(_bCardDaoMock.Object, _comboDaoMock.Object, _skillDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertSkillsAsync(_tempFolder); + + Assert.AreEqual(1, _savedSkills.Count); + Assert.AreEqual(1, _savedSkills[0].SkillVNum); + Assert.AreEqual("Fireball", _savedSkills[0].NameI18NKey); + Assert.AreEqual(50, _savedSkills[0].MpCost); + Assert.AreEqual(10, _savedSkills[0].Cooldown); + } + + [TestMethod] + public async Task SkillParser_ParsesMultipleSkills() + { + var content = CreateSkillData(skillVNum: 1, name: "Skill1") + "\n" + + CreateSkillData(skillVNum: 2, name: "Skill2") + "\n" + + CreateSkillData(skillVNum: 3, name: "Skill3"); + CreateTestFile(content); + + var parser = new SkillParser(_bCardDaoMock.Object, _comboDaoMock.Object, _skillDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertSkillsAsync(_tempFolder); + + Assert.AreEqual(3, _savedSkills.Count); + } + + [TestMethod] + public async Task SkillParser_ParsesTargetFields() + { + var content = CreateSkillData(skillVNum: 1, targetType: 1, hitType: 2, range: 5, targetRange: 3); + CreateTestFile(content); + + var parser = new SkillParser(_bCardDaoMock.Object, _comboDaoMock.Object, _skillDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertSkillsAsync(_tempFolder); + + Assert.AreEqual(1, _savedSkills.Count); + Assert.AreEqual(1, _savedSkills[0].TargetType); + Assert.AreEqual(2, _savedSkills[0].HitType); + Assert.AreEqual(5, _savedSkills[0].Range); + Assert.AreEqual(3, _savedSkills[0].TargetRange); + } + + [TestMethod] + public async Task SkillParser_ParsesElementField() + { + var content = CreateSkillData(skillVNum: 1, element: 2); + CreateTestFile(content); + + var parser = new SkillParser(_bCardDaoMock.Object, _comboDaoMock.Object, _skillDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertSkillsAsync(_tempFolder); + + Assert.AreEqual(1, _savedSkills.Count); + Assert.AreEqual(2, _savedSkills[0].Element); + } + + [TestMethod] + public async Task SkillParser_HandlesEmptyFile() + { + CreateTestFile(""); + + var parser = new SkillParser(_bCardDaoMock.Object, _comboDaoMock.Object, _skillDaoMock.Object, _loggerMock.Object, _logLanguageMock.Object); + await parser.InsertSkillsAsync(_tempFolder); + + Assert.AreEqual(0, _savedSkills.Count); + } + } +} From 9c1bcff6a3082d7d61ef4dcc55652f3609ad46b3 Mon Sep 17 00:00:00 2001 From: erwan-joly Date: Tue, 20 Jan 2026 21:00:32 +1300 Subject: [PATCH 4/4] fix login --- src/NosCore.GameObject/Services/LoginService/LoginService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NosCore.GameObject/Services/LoginService/LoginService.cs b/src/NosCore.GameObject/Services/LoginService/LoginService.cs index c3f396775..f9e205beb 100644 --- a/src/NosCore.GameObject/Services/LoginService/LoginService.cs +++ b/src/NosCore.GameObject/Services/LoginService/LoginService.cs @@ -70,7 +70,7 @@ await clientSession.SendPacketAsync(new FailcPacket } if ((acc == null) - || (!useApiAuth && !string.Equals(acc.Password, passwordToken, StringComparison.Ordinal))) + || (!useApiAuth && !string.Equals(acc.Password?.ToUpper(), passwordToken, StringComparison.Ordinal))) { await clientSession.SendPacketAsync(new FailcPacket {