local ____lualib = require("lualib_bundle") local __TS__Class = ____lualib.__TS__Class local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach local Map = ____lualib.Map local __TS__New = ____lualib.__TS__New local Set = ____lualib.Set local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries local __TS__ObjectValues = ____lualib.__TS__ObjectValues local __TS__ArrayEvery = ____lualib.__TS__ArrayEvery local __TS__ArrayPushArray = ____lualib.__TS__ArrayPushArray local __TS__Iterator = ____lualib.__TS__Iterator local __TS__ArrayFrom = ____lualib.__TS__ArrayFrom local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter local __TS__ArrayMap = ____lualib.__TS__ArrayMap local ____exports = {} local ____crystal_currency = require("crystal_currency") local CrystalCurrency = ____crystal_currency.CrystalCurrency local ____entity_radius = require("utils.entity_radius") local findNearestByClassname = ____entity_radius.findNearestByClassname local ____real_lobby_player = require("utils.real_lobby_player") local collectStatsEligiblePlayerIds = ____real_lobby_player.collectStatsEligiblePlayerIds local ____quest_reward_scaling = require("quests.quest_reward_scaling") local getQuestRewardSplitCount = ____quest_reward_scaling.getQuestRewardSplitCount local splitQuestTeamReward = ____quest_reward_scaling.splitQuestTeamReward ____exports.QuestType = QuestType or ({}) ____exports.QuestType.KILL_UNIT = "KILL_UNIT" ____exports.QuestType.COLLECT_ITEM = "COLLECT_ITEM" ____exports.QuestType.REACH_LOCATION = "REACH_LOCATION" ____exports.QuestType.CUSTOM = "CUSTOM" ____exports.QuestState = QuestState or ({}) ____exports.QuestState.AVAILABLE = 0 ____exports.QuestState[____exports.QuestState.AVAILABLE] = "AVAILABLE" ____exports.QuestState.IN_PROGRESS = 1 ____exports.QuestState[____exports.QuestState.IN_PROGRESS] = "IN_PROGRESS" ____exports.QuestState.COMPLETED = 2 ____exports.QuestState[____exports.QuestState.COMPLETED] = "COMPLETED" ____exports.QuestState.LOCKED = 3 ____exports.QuestState[____exports.QuestState.LOCKED] = "LOCKED" ____exports.QuestState.FAILED = 4 ____exports.QuestState[____exports.QuestState.FAILED] = "FAILED" ____exports.QuestEvents = __TS__Class() local QuestEvents = ____exports.QuestEvents QuestEvents.name = "QuestEvents" QuestEvents.____file_path = "scripts/vscripts/quests/QuestSystem.lua" function QuestEvents.prototype.____constructor(self) end function QuestEvents.OnQuestAccepted(self, callback) local ____self_onQuestAcceptedCallbacks_0 = self.onQuestAcceptedCallbacks ____self_onQuestAcceptedCallbacks_0[#____self_onQuestAcceptedCallbacks_0 + 1] = callback end function QuestEvents.OnQuestCompleted(self, callback) local ____self_onQuestCompletedCallbacks_1 = self.onQuestCompletedCallbacks ____self_onQuestCompletedCallbacks_1[#____self_onQuestCompletedCallbacks_1 + 1] = callback end function QuestEvents.FireQuestAccepted(self, data) __TS__ArrayForEach( self.onQuestAcceptedCallbacks, function(____, callback) return callback(nil, data) end ) end function QuestEvents.FireQuestCompleted(self, data) __TS__ArrayForEach( self.onQuestCompletedCallbacks, function(____, callback) return callback(nil, data) end ) end QuestEvents.onQuestAcceptedCallbacks = {} QuestEvents.onQuestCompletedCallbacks = {} local DOTA_ModifyGold_Unspecified = 0 local DOTA_ModifyXP_Unspecified = 0 ____exports.QuestSystem = __TS__Class() local QuestSystem = ____exports.QuestSystem QuestSystem.name = "QuestSystem" QuestSystem.____file_path = "scripts/vscripts/quests/QuestSystem.lua" function QuestSystem.prototype.____constructor(self) self.quests = __TS__New(Map) self.npcQuests = __TS__New(Map) self.processedItems = __TS__New(Set) self.questItems = __TS__New(Map) self.initialized = false self.questMenuOpenNpcByPlayer = __TS__New(Map) self:initializeEventListeners() end function QuestSystem.getInstance(self) if not ____exports.QuestSystem.instance then ____exports.QuestSystem.instance = __TS__New(____exports.QuestSystem) end return ____exports.QuestSystem.instance end function QuestSystem.prototype.registerQuest(self, quest) self.quests:set(quest.id, quest) self.initialized = true end function QuestSystem.prototype.linkQuestChains(self) self.quests:forEach(function(____, quest) if not quest.previousQuestId then return end local previousQuest = self.quests:get(quest.previousQuestId) if not previousQuest then return end local questNpc = self:getNpcForQuest(quest.id) local previousNpc = self:getNpcForQuest(previousQuest.id) if questNpc and questNpc == previousNpc then previousQuest.nextQuestId = quest.id end end) end function QuestSystem.prototype.initializeEventListeners(self) CustomGameEventManager:RegisterListener( "request_quests_data", function(source, event) local npcName = event.npcName local player = PlayerResource:GetPlayer(event.PlayerID) if not player then return end if npcName then local questsData = self:getQuestsForUI(npcName) CustomGameEventManager:Send_ServerToPlayer(player, "quests_update", {quests = questsData, npcName = npcName, playerId = event.PlayerID}) return end local acceptedQuestsData = self:getAcceptedQuestsForUI() CustomGameEventManager:Send_ServerToPlayer(player, "quests_update", {quests = acceptedQuestsData, playerId = event.PlayerID}) end ) CustomGameEventManager:RegisterListener( "quest_action", function(source, event) if event and event.questId and event.action == "accept" then local playerId = event.PlayerID or source if not PlayerResource:IsValidPlayerID(playerId) then return end self:startQuest(playerId, event.questId) local npcName = self:getNpcForQuest(event.questId) if npcName then local player = PlayerResource:GetPlayer(playerId) if player then local questsData = self:getQuestsForUI(npcName) CustomGameEventManager:Send_ServerToPlayer(player, "quests_update", {quests = questsData, npcName = npcName, playerId = playerId}) end end end end ) CustomGameEventManager:RegisterListener( "quests_client_ui_state", function(_source, event) local playerId = event.PlayerID if playerId == nil or playerId < 0 then return end local open = event.open == 1 or event.open == true local rawNpc = event.npcName or "" if open then self.questMenuOpenNpcByPlayer:set(playerId, rawNpc ~= "" and rawNpc or "*") else self.questMenuOpenNpcByPlayer:delete(playerId) end end ) ListenToGameEvent( "entity_killed", function(event) local unit = EntIndexToHScript(event.entindex_killed) if unit and unit:IsBaseNPC() then self:OnNpcKilled(unit) end end, nil ) ListenToGameEvent( "dota_item_picked_up", function(event) self:OnItemPickedUp(event) end, nil ) end function QuestSystem.prototype.startQuest(self, playerId, questId) local quest = self.quests:get(questId) if not quest then return end if not self:isQuestAvailable(questId) then return end quest.state = ____exports.QuestState.IN_PROGRESS ____exports.QuestEvents:FireQuestAccepted({questId = questId, playerId = playerId}) local ____opt_4 = quest.events local ____opt_2 = ____opt_4 and ____opt_4.onAccept if ____opt_2 ~= nil then ____opt_2(____opt_4) end local npcName = self:getNpcForQuest(questId) if npcName then self:updateUI(npcName, playerId) end end function QuestSystem.prototype.isPreviousQuestRequirementMet(self, quest) if not quest.previousQuestId then return true end local previousQuest = self.quests:get(quest.previousQuestId) if not previousQuest then return false end if previousQuest.state == ____exports.QuestState.COMPLETED then return true end local ____temp_8 = previousQuest.state == ____exports.QuestState.AVAILABLE if ____temp_8 then local ____opt_6 = previousQuest.unlocksQuestIds ____temp_8 = ____opt_6 and __TS__ArrayIncludes(previousQuest.unlocksQuestIds, quest.id) end if ____temp_8 then return true end return false end function QuestSystem.prototype.isQuestAvailable(self, questId) local quest = self.quests:get(questId) if not quest or quest.state ~= ____exports.QuestState.AVAILABLE then return false end return self:isPreviousQuestRequirementMet(quest) end function QuestSystem.prototype.updateQuestProgress(self, ____type, target, playerID, data) self.quests:forEach(function(____, quest, questId) if quest.state ~= ____exports.QuestState.IN_PROGRESS then return end local questDirty = false __TS__ArrayForEach( __TS__ObjectEntries(quest.objectives), function(____, ____bindingPattern0) local objective local objectiveId = ____bindingPattern0[1] objective = ____bindingPattern0[2] local objectiveChanged = false if objective.type == ____exports.QuestType.CUSTOM and objective.customHandler then objective.customHandler:onProgress(data) objectiveChanged = true elseif objective.type == ____type then local ____Array_isArray_result_9 if __TS__ArrayIsArray(objective.target) then ____Array_isArray_result_9 = __TS__ArrayIncludes(objective.target, target) else ____Array_isArray_result_9 = objective.target == target end local isTargetMatch = ____Array_isArray_result_9 if isTargetMatch and objective.current < objective.required then objective.current = objective.current + 1 objectiveChanged = true end end if objectiveChanged then questDirty = true self:checkQuestCompletion(quest) end end ) if questDirty then local npcName = self:getNpcForQuest(quest.id) or "" if npcName ~= nil then self:updateUI(npcName, playerID) end end end) end function QuestSystem.prototype.checkQuestCompletion(self, quest) local allCompleted = __TS__ArrayEvery( __TS__ObjectValues(quest.objectives), function(____, objective) return objective.current >= objective.required end ) if allCompleted and quest.state == ____exports.QuestState.IN_PROGRESS then quest.state = ____exports.QuestState.COMPLETED print("[QuestSystem] FireQuestCompleted: questId=" .. quest.id) ____exports.QuestEvents:FireQuestCompleted({questId = quest.id, playerId = 0}) local ____opt_12 = quest.events local ____opt_10 = ____opt_12 and ____opt_12.onComplete if ____opt_10 ~= nil then ____opt_10(____opt_12) end self:giveRewards(quest) self:clearCollectedItems(quest) self:unlockLinkedQuests(quest) end end function QuestSystem.prototype.unlockLinkedQuests(self, quest) local npcsToUpdate = __TS__New(Set) local questIdsToUnlock = {} if quest.nextQuestId then questIdsToUnlock[#questIdsToUnlock + 1] = quest.nextQuestId end if quest.unlocksQuestIds then __TS__ArrayPushArray(questIdsToUnlock, quest.unlocksQuestIds) end for ____, questId in ipairs(questIdsToUnlock) do local linkedQuest = self.quests:get(questId) if linkedQuest and linkedQuest.state == ____exports.QuestState.LOCKED then linkedQuest.state = ____exports.QuestState.AVAILABLE local npcName = self:getNpcForQuest(questId) if npcName then npcsToUpdate:add(npcName) end end end for ____, npcName in __TS__Iterator(npcsToUpdate) do self:updateUI(npcName, 0) end end function QuestSystem.prototype.repairStuckQuestUnlocks(self) self.quests:forEach(function(____, quest) if quest.state ~= ____exports.QuestState.LOCKED or not quest.previousQuestId then return end local previousQuest = self.quests:get(quest.previousQuestId) if (previousQuest and previousQuest.state) == ____exports.QuestState.COMPLETED then quest.state = ____exports.QuestState.AVAILABLE end end) end function QuestSystem.prototype.shouldShowQuestInNpcUI(self, quest) repeat local ____switch72 = quest.state local previousQuest local ____cond72 = ____switch72 == ____exports.QuestState.AVAILABLE if ____cond72 then return self:isPreviousQuestRequirementMet(quest) end ____cond72 = ____cond72 or (____switch72 == ____exports.QuestState.IN_PROGRESS or ____switch72 == ____exports.QuestState.COMPLETED or ____switch72 == ____exports.QuestState.FAILED) if ____cond72 then return true end ____cond72 = ____cond72 or ____switch72 == ____exports.QuestState.LOCKED if ____cond72 then if not quest.previousQuestId then return false end previousQuest = self.quests:get(quest.previousQuestId) return (previousQuest and previousQuest.state) == ____exports.QuestState.COMPLETED end do return false end until true end function QuestSystem.prototype.resetQuest(self, questId) local quest = self.quests:get(questId) if not quest then return end __TS__ArrayForEach( __TS__ObjectValues(quest.objectives), function(____, objective) objective.current = 0 end ) quest.state = ____exports.QuestState.AVAILABLE local npcName = self:getNpcForQuest(questId) or "" self:updateUI(npcName, 0) end function QuestSystem.prototype.getRewardPlayerIds(self) local ids = collectStatsEligiblePlayerIds(nil) if #ids > 0 then return ids end do local playerID = 0 while playerID < DOTA_MAX_TEAM_PLAYERS do do local pid = playerID if not PlayerResource:IsValidPlayerID(pid) then goto __continue79 end if PlayerResource:GetTeam(pid) ~= DOTA_TEAM_GOODGUYS then goto __continue79 end return {pid} end ::__continue79:: playerID = playerID + 1 end end return {} end function QuestSystem.prototype.giveRewards(self, quest) local playerIds = self:getRewardPlayerIds() local playerCount = math.max(1, #playerIds) local goldSplitCount = getQuestRewardSplitCount(nil, playerCount) local goldParts = splitQuestTeamReward(nil, quest.rewards.gold or 0, goldSplitCount) local xpParts = splitQuestTeamReward(nil, quest.rewards.experience or 0, playerCount) local crystalParts = splitQuestTeamReward(nil, quest.rewards.crystals or 0, playerCount) local crystalCurrency = CrystalCurrency:getInstance() do local i = 0 while i < #playerIds do local playerID = playerIds[i + 1] local hero = PlayerResource:GetSelectedHeroEntity(playerID) local gold = goldParts[i + 1] or 0 if gold > 0 and hero then hero:ModifyGold(gold, true, DOTA_ModifyGold_Unspecified) end local xp = xpParts[i + 1] or 0 if xp > 0 and hero then hero:AddExperience(xp, DOTA_ModifyXP_Unspecified, false, true) end local crystals = crystalParts[i + 1] or 0 if crystals > 0 then crystalCurrency:addCrystals(playerID, crystals) end if hero then local madstone = CreateItem("item_madstone_bundle", nil, hero) if madstone then hero:AddItem(madstone) end end i = i + 1 end end end function QuestSystem.prototype.getVisibleQuests(self) local visibleQuests = {} local startingQuests = __TS__ArrayFilter( __TS__ArrayFrom(self.quests:values()), function(____, quest) return not quest.previousQuestId end ) for ____, startQuest in ipairs(startingQuests) do local currentQuest = startQuest while currentQuest do visibleQuests[#visibleQuests + 1] = currentQuest if currentQuest.state ~= ____exports.QuestState.COMPLETED then break end if currentQuest.nextQuestId then currentQuest = self.quests:get(currentQuest.nextQuestId) else break end end end return visibleQuests end function QuestSystem.prototype.registerNpcQuests(self, npcName, questIds) self.npcQuests:set(npcName, questIds) end function QuestSystem.prototype.getQuestsForNpc(self, npcName) local questIds = self.npcQuests:get(npcName) or ({}) local quests = __TS__ArrayFilter( __TS__ArrayMap( questIds, function(____, id) local quest = self.quests:get(id) return quest end ), function(____, quest) return quest ~= nil and (quest.state == ____exports.QuestState.AVAILABLE or quest.state == ____exports.QuestState.LOCKED or quest.state == ____exports.QuestState.IN_PROGRESS or quest.state == ____exports.QuestState.COMPLETED) end ) return quests end function QuestSystem.prototype.getQuestsForUI(self, npcName) local questsData = {} if not self.initialized or not npcName then return questsData end self:repairStuckQuestUnlocks() local npcQuestIds = self.npcQuests:get(npcName) or ({}) for ____, questId in ipairs(npcQuestIds) do local quest = self.quests:get(questId) if quest and self:shouldShowQuestInNpcUI(quest) then questsData[questId] = quest end end return questsData end function QuestSystem.prototype.getAcceptedQuestsForUI(self) local questsData = {} if not self.initialized then return questsData end self.quests:forEach(function(____, quest, id) if quest.state == ____exports.QuestState.IN_PROGRESS or quest.state == ____exports.QuestState.COMPLETED then questsData[id] = quest end end) return questsData end function QuestSystem.prototype.getNpcForQuest(self, questId) for ____, ____value in __TS__Iterator(self.npcQuests:entries()) do local npcName = ____value[1] local questIds = ____value[2] if __TS__ArrayIncludes(questIds, questId) then return npcName end end return nil end function QuestSystem.prototype.updateUI(self, npcName, playerId) if not npcName then return end local questsData = self:getQuestsForUI(npcName) do local i = 0 while i < DOTA_MAX_TEAM_PLAYERS do do if not PlayerResource:IsValidPlayerID(i) or PlayerResource:IsFakeClient(i) then goto __continue116 end local openFor = self.questMenuOpenNpcByPlayer:get(i) if openFor == nil then goto __continue116 end if openFor ~= "*" and openFor ~= npcName then goto __continue116 end local player = PlayerResource:GetPlayer(i) if player then CustomGameEventManager:Send_ServerToPlayer(player, "quests_update", {quests = questsData, npcName = npcName, playerId = i}) end end ::__continue116:: i = i + 1 end end end function QuestSystem.prototype.OnNpcKilled(self, unit) local unitName = unit:GetUnitName() self:updateQuestProgress(____exports.QuestType.KILL_UNIT, unitName, 0) end function QuestSystem.prototype.OnItemPickedUp(self, event) local itemEntity = EntIndexToHScript(event.ItemEntityIndex) if not itemEntity then return end local itemName = itemEntity:GetAbilityName() local itemHandle = itemEntity:GetEntityHandle() if self.processedItems:has(itemHandle) then return end local targetQuestId self.quests:forEach(function(____, quest, questId) if quest.state == ____exports.QuestState.IN_PROGRESS then __TS__ArrayForEach( __TS__ObjectValues(quest.objectives), function(____, objective) if objective.type == ____exports.QuestType.COLLECT_ITEM and objective.target == itemName then targetQuestId = questId end end ) end end) if targetQuestId then if not self.questItems:has(targetQuestId) then self.questItems:set( targetQuestId, __TS__New(Set) ) end self.questItems:get(targetQuestId):add(itemEntity) end self:updateQuestProgress(____exports.QuestType.COLLECT_ITEM, itemName, 0) self.processedItems:add(itemHandle) end function QuestSystem.prototype.clearCollectedItems(self, quest) local items = self.questItems:get(quest.id) if items then items:forEach(function(____, item) do local ____try, ____hasReturned, ____returnValue = pcall(function() if not item or item:IsNull() then return true end local itemName do local function ____catch(e) return true end local ____try, ____hasReturned, ____returnValue = pcall(function() itemName = item:GetAbilityName() end) if not ____try then ____hasReturned, ____returnValue = ____catch(____hasReturned) end if ____hasReturned then return true, ____returnValue end end do pcall(function() local container = item:GetContainer() if container and not container:IsNull() then local hero = container:GetOwner() if hero and not hero:IsNull() then hero:RemoveItem(item) end end end) end do pcall(function() if not item:IsNull() then local itemPos = item:GetAbsOrigin() if itemPos ~= nil then local itemPhysical = findNearestByClassname("dota_item_drop", itemPos, 100) if itemPhysical and not itemPhysical:IsNull() then local containedItem = itemPhysical:GetContainedItem() if containedItem and not containedItem:IsNull() and containedItem:GetEntityHandle() == item:GetEntityHandle() then itemPhysical:RemoveSelf() end end end end end) end do pcall(function() if not item:IsNull() then item:RemoveSelf() end end) end end) if ____try and ____hasReturned then return ____returnValue end end end) self.questItems:delete(quest.id) end self.processedItems:clear() end function QuestSystem.prototype.triggerCustomProgress(self, questId, data) local quest = self.quests:get(questId) if not quest or quest.state ~= ____exports.QuestState.IN_PROGRESS then return end self:updateQuestProgress(____exports.QuestType.CUSTOM, "", 0, data) end function QuestSystem.prototype.addQuestProgress(self, questId, objectiveId, amount) if amount == nil then amount = 1 end local quest = self.quests:get(questId) if not quest or quest.state ~= ____exports.QuestState.IN_PROGRESS then return end local objective = quest.objectives[objectiveId] if not objective then return end local nextCurrent = math.min(objective.current + amount, objective.required) if nextCurrent == objective.current then return end objective.current = nextCurrent self:checkQuestCompletion(quest) self:updateUI( self:getNpcForQuest(questId) or "", 0 ) end function QuestSystem.prototype.setQuestProgress(self, questId, objectiveId, value, updateUI) if updateUI == nil then updateUI = true end local quest = self.quests:get(questId) if not quest or quest.state ~= ____exports.QuestState.IN_PROGRESS then return end local objective = quest.objectives[objectiveId] if not objective then return end local nextCurrent = math.min( math.max(0, value), objective.required ) if nextCurrent == objective.current then return end objective.current = nextCurrent self:checkQuestCompletion(quest) if updateUI then self:updateUI( self:getNpcForQuest(questId) or "", 0 ) end end function QuestSystem.prototype.setObjectiveRequired(self, questId, objectiveId, required, updateUI) if updateUI == nil then updateUI = true end local quest = self.quests:get(questId) if not quest then return end local objective = quest.objectives[objectiveId] if not objective then return end local nextRequired = math.max( 1, math.floor(required) ) objective.required = nextRequired if objective.current > nextRequired then objective.current = nextRequired end if updateUI then self:updateUI( self:getNpcForQuest(questId) or "", 0 ) end end function QuestSystem.prototype.failQuest(self, questId) local quest = self.quests:get(questId) if not quest or quest.state == ____exports.QuestState.COMPLETED then return end quest.state = ____exports.QuestState.FAILED local ____opt_18 = quest.events if ____opt_18 and ____opt_18.onFail then quest.events:onFail() end CustomGameEventManager:Send_ServerToAllClients("quest_failed", {questId = quest.id}) self:updateUI( self:getNpcForQuest(questId) or "", 0 ) end function QuestSystem.prototype.getQuestProgress(self, questId, objectiveId) local quest = self.quests:get(questId) if not quest then return 0 end local objective = quest.objectives[objectiveId] if not objective then return 0 end return objective.current end return ____exports