using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using PhoenixLib.DAL.Redis.Locks; using PhoenixLib.Events; using PhoenixLib.Logging; using PhoenixLib.ServiceBus; using WingsAPI.Communication; using WingsAPI.Communication.DbServer.AccountService; using WingsAPI.Communication.DbServer.CharacterService; using WingsAPI.Communication.Player; using WingsAPI.Communication.ServerApi; using WingsAPI.Communication.ServerApi.Protocol; using WingsAPI.Communication.Sessions; using WingsAPI.Communication.Sessions.Model; using WingsAPI.Communication.Sessions.Request; using WingsAPI.Communication.Sessions.Response; using WingsAPI.Data.Account; using WingsAPI.Data.Character; using WingsAPI.Game.Extensions.Families; using WingsAPI.Game.Extensions.MinilandExtensions; using WingsAPI.Game.Extensions.Quests; using WingsAPI.Game.Extensions.Quicklist; using WingsAPI.Packets.Enums; using WingsEmu.DTOs.Bonus; using WingsEmu.DTOs.Buffs; using WingsEmu.DTOs.Inventory; using WingsEmu.DTOs.Items; using WingsEmu.DTOs.Maps; using WingsEmu.DTOs.Mates; using WingsEmu.DTOs.Quests; using WingsEmu.DTOs.Quicklist; using WingsEmu.DTOs.Skills; using WingsEmu.DTOs.Titles; using WingsEmu.Game; using WingsEmu.Game._enum; using WingsEmu.Game._i18n; using WingsEmu.Game.Algorithm; using WingsEmu.Game.Battle; using WingsEmu.Game.Buffs; using WingsEmu.Game.Characters; using WingsEmu.Game.Characters.Events; using WingsEmu.Game.Compliments; using WingsEmu.Game.Configurations; using WingsEmu.Game.Extensions; using WingsEmu.Game.Extensions.Mates; using WingsEmu.Game.Families; using WingsEmu.Game.Families.Configuration; using WingsEmu.Game.Helpers.Damages; using WingsEmu.Game.Inventory; using WingsEmu.Game.Items; using WingsEmu.Game.Managers; using WingsEmu.Game.Managers.StaticData; using WingsEmu.Game.Maps; using WingsEmu.Game.Mates; using WingsEmu.Game.Mates.Events; using WingsEmu.Game.Miniland; using WingsEmu.Game.Miniland.Minigames; using WingsEmu.Game.Networking; using WingsEmu.Game.Quests; using WingsEmu.Game.Quests.Event; using WingsEmu.Game.Raids.Events; using WingsEmu.Game.RainbowBattle.Event; using WingsEmu.Game.Revival; using WingsEmu.Game.Skills; using WingsEmu.Game.TimeSpaces; using WingsEmu.Packets.Enums; using WingsEmu.Packets.Enums.Chat; using WingsEmu.Plugins.DistributedGameEvents.PlayerEvents; namespace WingsEmu.Plugins.BasicImplementations.Event.Characters; public class CharacterLoadEventHandler : IAsyncEventProcessor { private static readonly SemaphoreSlim _semaphoreSlim = new(1, 1); private readonly IAccountService _accountService; private readonly IBCardEffectHandlerContainer _bcardHandlerContainer; private readonly IBuffFactory _buffFactory; private readonly ICharacterAlgorithm _characterAlgorithm; private readonly ICharacterService _characterService; private readonly FamilyConfiguration _familyConfiguration; private readonly IFamilyManager _familyManager; private readonly SerializableGameServer _gameServer; private readonly IGameItemInstanceFactory _itemInstanceFactory; private readonly IItemsManager _itemsManager; private readonly IGameLanguageService _language; private readonly IExpirableLockService _lock; private readonly IMapManager _mapManager; private readonly IMateEntityFactory _mateEntityFactory; private readonly IMessagePublisher _messagePublisher; private readonly IMinilandManager _minilandManager; private readonly IPlayerEntityFactory _playerEntityFactory; private readonly IQuestFactory _questFactory; private readonly IQuestManager _questManager; private readonly IRandomGenerator _randomGenerator; private readonly IRankingManager _rankingManager; private readonly IReputationConfiguration _reputationConfiguration; private readonly IRevivalManager _revivalManager; private readonly IServerApiService _serverApiService; private readonly IServerManager _serverManager; private readonly ISessionManager _sessionManager; private readonly ISessionService _sessionService; private readonly ISubActConfiguration _subActConfiguration; private readonly ITimeSpaceConfiguration _timeSpaceConfiguration; public CharacterLoadEventHandler(IServerManager serverManager, IGameLanguageService language, SerializableGameServer gameServer, IBuffFactory buffFactory, ISessionManager sessionManager, ICharacterAlgorithm characterAlgorithm, IMinilandManager minilandManager, IMessagePublisher messagePublisher, IRevivalManager revivalManager, IQuestManager questManager, FamilyConfiguration familyConfiguration, IServerApiService serverApiService, IRandomGenerator randomGenerator, IMapManager mapManager, IReputationConfiguration reputationConfiguration, IItemsManager itemsManager, ISubActConfiguration subActConfiguration, IRankingManager rankingManager, IExpirableLockService @lock, ITimeSpaceConfiguration timeSpaceConfiguration, ISessionService sessionService, IGameItemInstanceFactory itemInstanceFactory, ICharacterService characterService, IPlayerEntityFactory playerEntityFactory, IMateEntityFactory mateEntityFactory, IFamilyManager familyManager, IBCardEffectHandlerContainer bcardHandlerContainer, IQuestFactory questFactory, IAccountService accountService) { _serverManager = serverManager; _language = language; _gameServer = gameServer; _buffFactory = buffFactory; _sessionManager = sessionManager; _characterAlgorithm = characterAlgorithm; _minilandManager = minilandManager; _messagePublisher = messagePublisher; _revivalManager = revivalManager; _questManager = questManager; _familyConfiguration = familyConfiguration; _serverApiService = serverApiService; _randomGenerator = randomGenerator; _mapManager = mapManager; _reputationConfiguration = reputationConfiguration; _itemsManager = itemsManager; _subActConfiguration = subActConfiguration; _rankingManager = rankingManager; _lock = @lock; _timeSpaceConfiguration = timeSpaceConfiguration; _sessionService = sessionService; _itemInstanceFactory = itemInstanceFactory; _characterService = characterService; _playerEntityFactory = playerEntityFactory; _mateEntityFactory = mateEntityFactory; _familyManager = familyManager; _bcardHandlerContainer = bcardHandlerContainer; _questFactory = questFactory; _accountService = accountService; } public async Task HandleAsync(CharacterLoadEvent e, CancellationToken cancellation) { await _semaphoreSlim.WaitAsync(); try { IClientSession session = e.Sender; byte? characterSlot = session.SelectedCharacterSlot; if (characterSlot is null) { session.ForceDisconnect(); return; } if (session.HasCurrentMapInstance || session.HasSelectedCharacter) { return; } session.SelectedCharacterSlot = null; DbServerGetCharacterResponse response = await _characterService.GetCharacterBySlot(new DbServerGetCharacterFromSlotRequest { AccountId = session.Account.Id, Slot = characterSlot.Value }); if (response is not { RpcResponseType: RpcResponseType.SUCCESS } || response.CharacterDto is null) { session.ForceDisconnect(); return; } SessionResponse sessionResponse = await _sessionService.GetSessionByAccountId(new GetSessionByAccountIdRequest { AccountId = session.Account.Id }); if (sessionResponse is not { ResponseType: RpcResponseType.SUCCESS }) { await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER, $"[POTENTIAL_DUPE][LOAD] sessionResponse not success => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}"); session.ForceDisconnect(); return; } Session storedSession = sessionResponse.Session; if (storedSession.ChannelId != _serverManager.ChannelId) { await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER, $"[POTENTIAL_DUPE][LOAD] Wrong session ChannelId => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}"); session.ForceDisconnect(); return; } if (storedSession.EncryptionKey != session.SessionId) { await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER, $"[POTENTIAL_DUPE][LOAD] Wrong encryption key => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}"); session.ForceDisconnect(); return; } AccountPenaltyGetAllResponse penaltiesResponse = null; try { penaltiesResponse = await _accountService.GetAccountPenalties(new AccountPenaltyGetRequest { AccountId = session.Account.Id }); } catch (Exception ex) { Log.Error("[CHARACTER_PRELOAD] Unexpected error: ", ex); } if (penaltiesResponse?.ResponseType != RpcResponseType.SUCCESS) { Log.Error("penaltiesResponse.AccountPenaltyDtos", new Exception()); session.ForceDisconnect(); return; } session.Account.Logs = penaltiesResponse.AccountPenaltyDtos == null ? new List() : penaltiesResponse.AccountPenaltyDtos.ToList(); IPlayerEntity playerEntity = _playerEntityFactory.CreatePlayerEntity(response.CharacterDto); playerEntity.GameStartDate = DateTime.UtcNow; playerEntity.MapInstanceId = _mapManager.GetBaseMapInstanceIdByMapId(playerEntity.MapId); IMapInstance currentMap = _mapManager.GetMapInstance(playerEntity.MapInstanceId); if (currentMap != null && currentMap.IsBlockedZone(playerEntity.MapX, playerEntity.MapY)) { Position pos = currentMap.GetRandomPosition(); playerEntity.ChangePosition(pos); } else { playerEntity.ChangePosition(new Position(playerEntity.MapX, playerEntity.MapY)); } playerEntity.Authority = session.Account.Authority; if (response.CharacterDto.ReturnPoint != null) { if (response.CharacterDto.ReturnPoint.MapId == 10000 && !session.IsGameMaster()) { response.CharacterDto.ReturnPoint.MapId = 1; response.CharacterDto.ReturnPoint.MapX = 1; response.CharacterDto.ReturnPoint.MapY = 1; } } playerEntity.HomeComponent.ChangeReturn(response.CharacterDto.ReturnPoint); playerEntity.HomeComponent.ChangeAct5Respawn(response.CharacterDto.Act5RespawnType); playerEntity.HomeComponent.ChangeRespawn(response.CharacterDto.RespawnType); LoadQuicklist(playerEntity); LoadInventory(playerEntity); LoadSkills(playerEntity); LoadScripts(playerEntity); LoadQuests(playerEntity); LoadTitleBCards(playerEntity); sessionResponse = await _sessionService.GetSessionByAccountId(new GetSessionByAccountIdRequest { AccountId = session.Account.Id }); if (sessionResponse is not { ResponseType: RpcResponseType.SUCCESS }) { await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER, $"[POTENTIAL_DUPE][LOAD] 2nd check sessionResponse not success => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}"); session.ForceDisconnect(); return; } storedSession = sessionResponse.Session; if (storedSession.ChannelId != _serverManager.ChannelId) { await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER, $"[POTENTIAL_DUPE][LOAD] 2nd check Wrong session ChannelId => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}"); session.ForceDisconnect(); return; } if (storedSession.EncryptionKey != session.SessionId) { await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER, $"[POTENTIAL_DUPE][LOAD] 2nd check Wrong encryption key => characterName: {response.CharacterDto.Name} | master: {session.Account.MasterAccountId} | hardwareId: {session.HardwareId} on channel {_serverManager.ChannelId}"); session.ForceDisconnect(); return; } IClientSession tmp = _sessionManager.GetSessionByCharacterId(playerEntity.Id); if (tmp != null) { await session.NotifyStrangeBehavior(StrangeBehaviorSeverity.DANGER, $"[POTENTIAL_DUPE][LOAD] {playerEntity.Name} tried to connect twice on {_serverManager.ChannelId}!"); tmp.ForceDisconnect(); session.ForceDisconnect(); return; } // here everything starts session.InitializePlayerEntity(playerEntity); playerEntity.Miniland ??= _minilandManager.CreateMinilandByCharacterSession(session); FamilyMembership familyMember = _familyManager.GetFamilyMembershipByCharacterId(session.PlayerEntity.Id); playerEntity.SetFamilyMembership(familyMember); foreach (MateDTO mateDto in session.PlayerEntity.NosMates) { IMateEntity mate = _mateEntityFactory.CreateMateEntity(session.PlayerEntity, mateDto); await session.EmitEventAsync(new MateInitializeEvent { MateEntity = mate, IsOnCharacterEnter = true }); } LoadPartnerWarehouse(playerEntity); LoadPartnerInventory(playerEntity); LoadPartnerEqBCards(playerEntity); await session.EmitEventAsync(new MinigameRefreshProductionEvent(false)); await session.EmitEventAsync(new QuestDailyRefreshEvent { Force = false }); await session.EmitEventAsync(new ComplimentsMonthlyRefreshEvent { Force = false }); await session.EmitEventAsync(new RainbowBattleLeaverBusterRefreshEvent { Force = false }); await session.EmitEventAsync(new SpecialistRefreshEvent { Force = false }); await session.EmitEventAsync(new RaidResetRestrictionEvent()); session.CurrentMapInstance = session.PlayerEntity.MapInstance; // need to improve this logic if (await PreLoadLogicAndCheckChannelChange(session)) { Log.Warn($"[CHARACTER_LOAD] {session.PlayerEntity.Name} changed to another channel"); return; } IFamily family = session.PlayerEntity.Family; await ExecuteInitialMapJoin(session, family); await TryToSendSomeWarningMessages(session); // todo dump _sessionManager.AddOnline(new ClusterCharacterInfo { ChannelId = (byte?)_gameServer.ChannelId, Id = session.PlayerEntity.Id, Name = session.PlayerEntity.Name, Class = session.PlayerEntity.Class, Gender = session.PlayerEntity.Gender, HeroLevel = session.PlayerEntity.HeroLevel, Level = session.PlayerEntity.Level, HardwareId = session.HardwareId }); if (family != null) { FamilyPacketExtensions.SendFamilyMembersInfoToMembers(family, _sessionManager, _familyConfiguration); } await _messagePublisher.PublishAsync(new PlayerConnectedOnChannelMessage { ChannelId = _gameServer.ChannelId, CharacterId = session.PlayerEntity.Id, CharacterName = session.PlayerEntity.Name, Class = session.PlayerEntity.Class, Gender = session.PlayerEntity.Gender, HeroLevel = session.PlayerEntity.HeroLevel, Level = session.PlayerEntity.Level, FamilyId = family?.Id, HardwareId = session.HardwareId }, cancellation); } finally { _semaphoreSlim.Release(); } } private async Task PreLoadLogicAndCheckChannelChange(IClientSession session) { await RefreshRevivalInfo(session); await LoadBuffs(session); if (session.CurrentMapInstance != null && !session.CurrentMapInstance.HasMapFlag(MapFlags.ACT_4)) { return false; } if (session.PlayerEntity.Faction == FactionType.Neutral) { return false; } GetChannelInfoResponse response = null; try { response = await _serverApiService.GetAct4ChannelInfo(new GetAct4ChannelInfoRequest { WorldGroup = _serverManager.ServerGroup }); } catch (Exception e) { Log.Error("[CHARACTER_LOAD] Unexpected error while trying to obtain channel's Act4 information: ", e); } SerializableGameServer gameServer = response?.GameServer; if (response?.ResponseType != RpcResponseType.SUCCESS || gameServer == null) { session.ChangeMap(1, 149, 60); return false; } if (gameServer.ChannelId == _gameServer.ChannelId) { return false; } short mapId = session.PlayerEntity.Faction == FactionType.Angel ? (short)MapIds.ACT4_ANGEL_CITADEL : (short)MapIds.ACT4_DEMON_CITADEL; short mapX = (short)(12 + _randomGenerator.RandomNumber(-2, 3)); short mapY = (short)(40 + _randomGenerator.RandomNumber(-2, 3)); if (_mapManager.HasMapFlagByMapId(session.PlayerEntity.MapId, MapFlags.ACT_4)) { mapId = (short)session.PlayerEntity.MapId; mapX = session.PlayerEntity.MapX; mapY = session.PlayerEntity.MapY; } await session.EmitEventAsync(new PlayerChangeChannelEvent(gameServer, ItModeType.ToAct4, mapId, mapX, mapY)); return true; } private async Task LoadBuffs(IClientSession session) { var toAdd = new List(); var toRemove = new List(); foreach (CharacterStaticBuffDto buff in session.PlayerEntity.StaticBuffs) { if (buff.RemainingTime <= 0) { toRemove.Add(buff); continue; } toAdd.Add(_buffFactory.CreateBuff(buff.CardId, session.PlayerEntity, TimeSpan.FromMilliseconds(buff.RemainingTime), BuffFlag.BIG_AND_KEEP_ON_LOGOUT)); } await session.PlayerEntity.AddBuffsAsync(toAdd); foreach (CharacterStaticBuffDto removeBuff in toRemove) { session.PlayerEntity.StaticBuffs.Remove(removeBuff); } } private async Task RefreshRevivalInfo(IClientSession session) { foreach (IMateEntity mate in session.PlayerEntity.MateComponent.GetMates()) { _revivalManager.TryUnregisterRevival(mate.Id); } if (session.PlayerEntity.IsAlive()) { return; } await session.EmitEventAsync(new RevivalReviveEvent(RevivalType.DontPayRevival) { Forced = ForcedType.Reconnect }); } private async Task TryToSendSomeWarningMessages(IClientSession session) { await session.EmitEventAsync(new InventoryExpiredItemsEvent()); await session.EmitEventAsync(new CharacterBonusExpiredEvent()); if (session.PlayerEntity.HaveStaticBonus(StaticBonusType.BazaarMedalGold) || session.PlayerEntity.HaveStaticBonus(StaticBonusType.BazaarMedalSilver)) { session.SendChatMessage(_language.GetLanguage(GameDialogKey.INFORMATION_CHATMESSAGE_ON_LOGIN_BAZAAR_MEDAL, session.UserLanguage), ChatMessageColorType.Green); } if (!string.IsNullOrWhiteSpace(session.PlayerEntity.Family?.Message)) { session.SendInfo("--- Family Message ---\n" + session.PlayerEntity.Family.Message); } foreach (IMateEntity mate in session.PlayerEntity.MateComponent.GetMates()) { session.SendCondMate(mate); if (mate.IsTeamMember) { await session.EmitEventAsync(new MateJoinTeamEvent { MateEntity = mate, IsOnCharacterEnter = true }); continue; } await session.EmitEventAsync(new MateStayInsideMinilandEvent { MateEntity = mate, IsOnCharacterEnter = true }); } if (session.PlayerEntity.HaveStaticBonus(StaticBonusType.PetBasket)) { session.SendPetBasketPacket(true); } if (_gameServer.ChannelType == GameChannelType.ACT_4) { bool isLosingRep = await _lock.ExistsTemporaryLock($"game:locks:character:{session.PlayerEntity.Id}:act-4-less-rep"); if (isLosingRep) { session.PlayerEntity.IsGettingLosingReputation = true; session.SendChatMessage(session.GetLanguage(GameDialogKey.ACT4_CHATMESSAGE_LESS_REPUTATION), ChatMessageColorType.Red); } } AccountPenaltyDto mute = session.Account.Logs.FirstOrDefault(x => x.PenaltyType == PenaltyType.Muted && x.RemainingTime.HasValue); if (mute == null) { return; } DateTime now = DateTime.UtcNow; session.PlayerEntity.MuteRemainingTime = TimeSpan.FromSeconds(mute.RemainingTime ?? 0); session.PlayerEntity.LastChatMuteMessage = DateTime.MinValue; session.PlayerEntity.LastMuteTick = now; } private async Task ExecuteInitialMapJoin(IClientSession session, IFamily family) { session.RefreshSkillList(); session.SendRsfiPacket(_subActConfiguration, _timeSpaceConfiguration); IReadOnlyList topReputation = _rankingManager.TopReputation; session.RefreshReputation(_reputationConfiguration, topReputation); session.RefreshPassiveBCards(); session.SendTitlePacket(); session.SendIncreaseRange(); session.ShowInventoryExtensions(); session.SendStaticBonuses(); session.SendMinilandPrivateInformation(_minilandManager, _language); session.SendTitlePacket(); session.SendZzimPacket(); session.SendTwkPacket(); session.RefreshFaction(); session.SendMessageUnderChat(); session.SendScpStcPacket(); session.SendScpPackets(); session.SendScnPackets(); session.SendStartStartupInventory(); session.RefreshGold(); session.SendClinitPacket(_rankingManager.TopCompliment); session.SendFlinitPacket(topReputation); session.SendKdlinitPacket(_rankingManager.TopPoints); session.SendPacket("skyinit 0"); session.SendPacket("skyinit 1"); session.SendPacket("skyinit 2"); session.SendPacket("skyinit 3"); session.SendPacket("tbf 1.0 2.0 3.0 4.0 5.0"); session.SendScpPackets(); session.SendScnPackets(); session.RefreshSpPoint(); session.SendPacket("rage 0 250000"); session.SendPacket("rank_cool 0 0 18000"); bool changeMap = false; if (family != null) { if ((byte)session.PlayerEntity.Faction != family.Faction) { await session.EmitEventAsync(new ChangeFactionEvent { NewFaction = family.Faction == (byte)FactionType.Angel ? FactionType.Angel : FactionType.Demon }); if (session.CurrentMapInstance.HasMapFlag(MapFlags.ACT_4)) { changeMap = true; } } session.BroadcastGidx(family, _language); session.RefreshFamilyInfo(family, _familyConfiguration); session.SendFamilyLogsToMember(family); session.RefreshFamilyMembers(_sessionManager, family); session.RefreshFamilyMembersMessages(family); session.RefreshFamilyMembersExp(family); session.SendFmiPacket(); session.SendFmpPacket(_itemsManager); } session.RefreshQuicklist(); session.ChangeToLastBaseMap(); session.SendPacket("scr 0 0 0 0 0 0"); await ExecuteQuestLogic(session); if (!changeMap) { return; } await session.Respawn(); } private async Task ExecuteQuestLogic(IClientSession session) { CompletedScriptsDto lastCompletedScriptDto = session.PlayerEntity.GetLastCompletedScript(); if (lastCompletedScriptDto == null) { session.SendScriptPacket(1, 10); return; } TutorialDto lastCompletedTutorial = _questManager.GetScriptTutorialByIndex(lastCompletedScriptDto.ScriptId, lastCompletedScriptDto.ScriptIndex); if (lastCompletedTutorial == null) { return; } TutorialDto nextScript = _questManager.GetScriptTutorialById(lastCompletedTutorial.Id + 1); // We check if it was the last script from that sub-questline, for sending a QnpcPacket instead of a new script if (lastCompletedTutorial.ScriptIndex == _questManager.GetScriptsTutorialByScriptId(lastCompletedTutorial.ScriptId).Max(s => s.ScriptIndex)) { QuestNpcDto questNpc = _questManager.GetQuestNpcByScriptId(lastCompletedTutorial.ScriptId + 1); session.SendQnpcPacket(questNpc.Level, questNpc.NpcVnum, questNpc.MapId); await CheckCompletedQuests(session); return; } // We check if we have an active quest-script CompletedScriptsDto lastCompletedQuestStartScript = session.PlayerEntity.GetLastCompletedScriptByType(TutorialActionType.START_QUEST); if (lastCompletedQuestStartScript != null) { TutorialDto lastCompletedQuestStartTutorial = _questManager.GetScriptTutorialByIndex(lastCompletedQuestStartScript.ScriptId, lastCompletedQuestStartScript.ScriptIndex); TutorialDto waitQuestCompletionScript = _questManager.GetScriptsTutorialByType(TutorialActionType.WAIT_FOR_QUEST_COMPLETION) .First(s => s.Data == lastCompletedQuestStartTutorial.Data && s.Id > lastCompletedQuestStartTutorial.Id); if (!session.PlayerEntity.HasCompletedScriptByIndex(waitQuestCompletionScript.ScriptId, waitQuestCompletionScript.ScriptIndex)) { if (!session.PlayerEntity.HasQuestWithId(lastCompletedQuestStartTutorial.Data)) { session.PlayerEntity.RemoveCompletedScript(lastCompletedQuestStartTutorial.ScriptId, lastCompletedQuestStartTutorial.ScriptIndex); var completedScriptsToRemove = session.PlayerEntity.GetCompletedScripts().Where(s => s.ScriptId > lastCompletedQuestStartTutorial.ScriptId || s.ScriptId == lastCompletedQuestStartTutorial.ScriptId && s.ScriptIndex > lastCompletedQuestStartTutorial.ScriptIndex).ToList(); foreach (CompletedScriptsDto completedScript in completedScriptsToRemove) { session.PlayerEntity.RemoveCompletedScript(completedScript.ScriptId, completedScript.ScriptIndex); } nextScript = lastCompletedQuestStartTutorial; } else { nextScript = waitQuestCompletionScript; } } } session.SendScriptPacket(nextScript.ScriptId, nextScript.ScriptIndex); await CheckCompletedQuests(session); } private async Task CheckCompletedQuests(IClientSession session) { foreach (CharacterQuest characterQuest in session.PlayerEntity.GetCurrentQuests()) { if (session.PlayerEntity.IsQuestCompleted(characterQuest)) { await session.EmitEventAsync(new QuestCompletedEvent(characterQuest, true)); } } session.RefreshQuestList(_questManager, null); session.SendQuestsTargets(); session.SendSqstPackets(_questManager); } private void LoadSkills(IPlayerEntity session) { foreach (CharacterSkillDTO skillDto in session.LearnedSkills) { if (session.CharacterSkills.ContainsKey(skillDto.SkillVNum)) { continue; } var skill = new CharacterSkill { SkillVNum = skillDto.SkillVNum }; session.CharacterSkills[skill.SkillVNum] = skill; session.Skills.Add(skill); short upgradeSkill = skill.Skill?.UpgradeSkill ?? 0; if (upgradeSkill == 0) { continue; } if (!session.SkillComponent.SkillUpgrades.TryGetValue(upgradeSkill, out HashSet hashSet)) { hashSet = new HashSet(); session.SkillComponent.SkillUpgrades[upgradeSkill] = hashSet; } if (hashSet.Contains(skill)) { continue; } hashSet.Add(skill); } } private void LoadScripts(IPlayerEntity session) { IEnumerable completedScriptsDtos = session.CompletedScripts; foreach (CompletedScriptsDto completedScriptDto in completedScriptsDtos) { TutorialDto completedScript = _questManager.GetScriptTutorialByIndex(completedScriptDto.ScriptId, completedScriptDto.ScriptIndex); if (completedScript == null) { continue; } session.SaveScript(completedScript.ScriptId, completedScript.ScriptIndex, completedScript.Type, completedScriptDto.CompletedDate); } } private void LoadQuests(IPlayerEntity session) { IEnumerable characterQuests = session.ActiveQuests; IEnumerable completedPeriodicQuests = session.CompletedPeriodicQuests; IEnumerable completedQuests = session.CompletedQuests; foreach (CharacterQuestDto characterQuestDto in completedPeriodicQuests) { CharacterQuest characterQuest = _questFactory.NewQuest(session.Id, characterQuestDto.QuestId, characterQuestDto.SlotType); characterQuest.ObjectiveAmount = characterQuestDto.ObjectiveAmount; session.AddCompletedPeriodicQuest(characterQuest); } // This can be removed when the wipe of the server happens. // It's gonna contain only the MAIN quests, since we have a "backup" (the scripts), but // with new characters will also contain GENERAL and SECONDARY ones if (!completedQuests.Any()) { foreach (CompletedScriptsDto completedScript in session.GetCompletedScriptsByType(TutorialActionType.START_QUEST)) { TutorialDto script = _questManager.GetScriptTutorialByIndex(completedScript.ScriptId, completedScript.ScriptIndex); if (script == null) { continue; } if (characterQuests.Any(s => s.QuestId == script.Data)) // It's an active one { continue; } CharacterQuest characterQuest = _questFactory.NewQuest(session.Id, script.Data, QuestSlotType.MAIN); session.AddCompletedQuest(characterQuest); } } else { foreach (CharacterQuestDto characterQuestDto in completedQuests) { CharacterQuest characterQuest = _questFactory.NewQuest(session.Id, characterQuestDto.QuestId, characterQuestDto.SlotType); characterQuest.ObjectiveAmount = characterQuestDto.ObjectiveAmount; session.AddCompletedQuest(characterQuest); } } foreach (CharacterQuestDto characterQuestDto in characterQuests) { CharacterQuest characterQuest = _questFactory.NewQuest(session.Id, characterQuestDto.QuestId, characterQuestDto.SlotType); characterQuest.ObjectiveAmount = characterQuestDto.ObjectiveAmount; session.AddActiveQuest(characterQuest); } } private void LoadPartnerEqBCards(IPlayerEntity playerEntity) { foreach (IMateEntity mate in playerEntity.MateComponent.GetMates(x => x.MateType == MateType.Partner)) { foreach (PartnerInventoryItem partnerInventory in playerEntity.PartnerGetEquippedItems(mate.PetSlot)) { if (partnerInventory?.ItemInstance == null) { continue; } mate.RefreshEquipmentValues(partnerInventory.ItemInstance, false); } } } private void LoadTitleBCards(IPlayerEntity playerEntity) { CharacterTitleDto equippedTitle = playerEntity.Titles.FirstOrDefault(x => x.IsEquipped); if (equippedTitle == null) { return; } playerEntity.RefreshTitleBCards(_itemsManager, equippedTitle, _bcardHandlerContainer); } private void LoadPartnerInventory(IPlayerEntity playerEntity) { IEnumerable partnerItems = playerEntity.PartnerInventory; foreach (CharacterPartnerInventoryItemDto partnerItem in partnerItems) { if (partnerItem == null) { continue; } GameItemInstance itemInstance = _itemInstanceFactory.CreateItem(partnerItem.ItemInstance); playerEntity.PartnerEquipItem(itemInstance, partnerItem.PartnerSlot); } } private void LoadQuicklist(IPlayerEntity playerEntity) { List inventory = playerEntity.Quicklist; foreach (CharacterQuicklistEntryDto item in inventory) { if (item == null) { continue; } playerEntity.QuicklistComponent.AddQuicklist(item); } } private void LoadInventory(IPlayerEntity playerEntity) { foreach (CharacterInventoryItemDto item in playerEntity.Inventory) { if (item == null) { continue; } CharacterInventoryItemDto inventoryItem = item; GameItemInstance itemInstance = _itemInstanceFactory.CreateItem(inventoryItem.ItemInstance); var newItem = new InventoryItem { CharacterId = inventoryItem.CharacterId, InventoryType = inventoryItem.InventoryType, IsEquipped = inventoryItem.IsEquipped, Slot = inventoryItem.Slot, ItemInstance = itemInstance }; if (newItem.InventoryType == InventoryType.EquippedItems && newItem.ItemInstance != null) { playerEntity.EquipItem(newItem, newItem.ItemInstance.GameItem.EquipmentSlot, true); if (itemInstance.Type == ItemInstanceType.WearableInstance) { playerEntity.RefreshEquipmentValues(itemInstance); } continue; } playerEntity.AddItemToInventory(newItem); } // equipped items DateTime date = DateTime.UtcNow; foreach (CharacterInventoryItemDto item in playerEntity.EquippedStuffs) { if (item == null) { continue; } GameItemInstance itemInstance = _itemInstanceFactory.CreateItem(item.ItemInstance); var newItem = new InventoryItem { CharacterId = item.CharacterId, InventoryType = item.InventoryType, IsEquipped = item.IsEquipped, Slot = item.Slot, ItemInstance = itemInstance }; if (newItem.InventoryType != InventoryType.EquippedItems || newItem.ItemInstance == null) { continue; } if (newItem.ItemInstance.ItemDeleteTime <= date) { continue; } playerEntity.EquipItem(newItem, newItem.ItemInstance.GameItem.EquipmentSlot, true); if (itemInstance.Type == ItemInstanceType.WearableInstance) { playerEntity.RefreshEquipmentValues(itemInstance); } } } private void LoadPartnerWarehouse(IPlayerEntity playerEntity) { IEnumerable items = playerEntity.PartnerWarehouse; foreach (PartnerWarehouseItemDto item in items) { if (item == null) { continue; } playerEntity.AddPartnerWarehouseItem(_itemInstanceFactory.CreateItem(item.ItemInstance), item.Slot); } } }