Files
Dota-Zombie-Invasion/scripts/vscripts/quests/questsystem.lua
T
2026-05-29 15:11:31 +07:00

754 lines
28 KiB
Lua

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