using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using PhoenixLib.Events; using PhoenixLib.Logging; using PhoenixLib.Scheduler; using WingsAPI.Game.Extensions.Quests; using WingsEmu.DTOs.Quests; using WingsEmu.Game._i18n; using WingsEmu.Game.Characters.Events; using WingsEmu.Game.Extensions; using WingsEmu.Game.Networking; using WingsEmu.Game.Quests; using WingsEmu.Game.Quests.Event; using WingsEmu.Packets.Enums; using WingsEmu.Packets.Enums.Chat; namespace Plugin.QuestImpl.Handlers { public class AddMainQuestEventHandler : IAsyncEventProcessor { private readonly IGameLanguageService _gameLanguageService; private readonly IQuestFactory _questFactory; private readonly IQuestManager _questManager; private readonly IScheduler _scheduler; public AddMainQuestEventHandler(IQuestManager questManager, IQuestFactory questFactory, IScheduler scheduler, IGameLanguageService gameLanguageService) { _questManager = questManager; _questFactory = questFactory; _scheduler = scheduler; _gameLanguageService = gameLanguageService; } public async Task HandleAsync(AddQuestEvent e, CancellationToken cancellation) { IClientSession session = e.Sender; int questId = e.QuestId; if (e.QuestSlotType != QuestSlotType.MAIN) { return; } QuestDto quest = _questManager.GetQuestById(questId); if (quest == null) { Log.Debug($"[ERROR] Quest not found: {questId.ToString()}"); return; } if (session.PlayerEntity.HasQuestWithId(quest.Id)) { session.SendMsg(_gameLanguageService.GetLanguage(GameDialogKey.QUEST_SHOUTMESSAGE_ALREADY_HAVE_QUEST, session.UserLanguage), MsgMessageType.Middle); return; } // Check for possible script exploit if (IsPotentialMainQuestExploit(session, quest)) { return; } if (session.GetEmptyQuestSlot(e.QuestSlotType) == -1) { session.SendMsg(_gameLanguageService.GetLanguage(GameDialogKey.QUEST_SHOUTMESSAGE_SLOT_FULL, session.UserLanguage), MsgMessageType.Middle); return; } CharacterQuest characterQuest = _questFactory.NewQuest(session.PlayerEntity.Id, questId, e.QuestSlotType); session.PlayerEntity.AddActiveQuest(characterQuest); session.RefreshQuestList(_questManager, characterQuest.QuestId); await session.EmitEventAsync(new QuestAddedEvent { QuestId = characterQuest.QuestId, QuestSlotType = characterQuest.SlotType }); if (characterQuest.Quest.QuestType != QuestType.COMPLETE_TIMESPACE && characterQuest.Quest.QuestType != QuestType.DROP_IN_TIMESPACE && quest.TargetMapId != 0) { session.SendTargetQuest(quest.TargetMapX, quest.TargetMapY, quest.TargetMapId, quest.Id); } // Still gotta be present for NOTHING type quests _scheduler.Schedule(TimeSpan.FromSeconds(1), s => session.EmitEvent(new QuestCompletedEvent(characterQuest))); } private bool IsPotentialMainQuestExploit(IClientSession session, QuestDto quest) { CompletedScriptsDto lastCompletedScript = session.PlayerEntity.GetLastCompletedScript(); TutorialDto lastCompletedTutorial = _questManager.GetScriptTutorialByIndex(lastCompletedScript.ScriptId, lastCompletedScript.ScriptIndex); IReadOnlyCollection questScripts = _questManager.GetScriptsTutorialByType(TutorialActionType.START_QUEST); TutorialDto questScript = _questManager.GetScriptsTutorialByType(TutorialActionType.START_QUEST).FirstOrDefault(s => s.Data == quest.Id && s.Id > lastCompletedTutorial.Id); if (questScript == null) { return false; } if (lastCompletedTutorial != null && lastCompletedTutorial.Id > questScript.Id) { session.NotifyStrangeBehavior(StrangeBehaviorSeverity.SEVERE_ABUSE, $"Tried to send an already completed quest script! | ScriptId: {questScript.ScriptId}, ScriptIndex: {questScript.ScriptIndex}"); return true; } IEnumerable mustBeCompletedScripts = questScripts.Where(s => s.ScriptId < questScript.ScriptId || s.ScriptId == questScript.ScriptId && s.ScriptIndex < questScript.ScriptIndex); foreach (TutorialDto tutorialDto in mustBeCompletedScripts) { if (!session.PlayerEntity.HasCompletedScriptByIndex(tutorialDto.ScriptId, tutorialDto.ScriptIndex)) { session.NotifyStrangeBehavior(StrangeBehaviorSeverity.SEVERE_ABUSE, $"Tried to send a quest script without having completed previous ones! | ScriptId: {questScript.ScriptId}, ScriptIndex: {questScript.ScriptIndex} | " + $"The script that was incompleted and triggered this message was: ScriptId: {tutorialDto.ScriptId} ; ScriptIndex: {tutorialDto.ScriptIndex}"); return true; // Let's hope doesn't explode } } return false; } } }