Files
StellaSora_DataLua/lua/game/ui/guide/guidesteprunner.lua
T
SL1900 08b515afd9 Major Update - 2.0.0.143
EN: 2.0.0.143
CN: 2.0.0.142
JP: 2.0.0.145
KR: 2.0.0.150
2026-10-20 13:00:00 +09:00

1098 lines
28 KiB
Lua

local TimerManager = require("GameCore.Timer.TimerManager")
local GuideStepRunner = {}
GuideStepRunner.__index = GuideStepRunner
local nDefaultSystemTimeout = 3
local nPlayerSkipDelay = 20
local nEventPollInterval = 0.1
local Guard = function(fn, ...)
if fn == nil then
return true
end
return xpcall(fn, debug.traceback, ...)
end
function GuideStepRunner.Start(objParent, objGroup)
local obj = setmetatable({}, GuideStepRunner)
objParent._objStepRunner = obj
obj:_Start(objParent, objGroup)
return obj
end
function GuideStepRunner:_Start(objParent, objGroup)
self._objParent = objParent
self._objGroup = objGroup
self._nGroupId = objGroup.nGroupId
self._nGuideType = objGroup.nGuideType or AllEnum.GuideType.Dorm
self._bNeedCheckpoint = objGroup.bNeedCheckpoint == true
self._nGroupGen = 1
self._nStepGen = 0
self._nPresentGen = 0
self._nConfirmGen = 0
self._bStopped = false
self._bExiting = false
self._bReported = false
self._bInTick = false
self._bDirty = false
self._tbListen = {}
self._mapCtx = {
mapData = {},
nGroupId = self._nGroupId
}
function self._mapCtx.Set(mapCtx, sKey, anyVal)
mapCtx.mapData[sKey] = anyVal
end
function self._mapCtx.Get(mapCtx, sKey)
return mapCtx.mapData[sKey]
end
local ok, tbSteps = Guard(objGroup.GetSteps, objGroup)
if not ok or type(tbSteps) ~= "table" or #tbSteps == 0 then
printError("[DormGuide] GetSteps 失败 group=" .. tostring(self._nGroupId))
self:Stop("abort", false)
return
end
self._tbSteps = tbSteps
self._nIndex = 1
if objGroup.OnStart ~= nil then
local bOk = Guard(objGroup.OnStart, objGroup, self._mapCtx)
if not bOk or self._bStopped then
if not self._bStopped then
self:Stop("abort", false)
end
return
end
end
self:_BeginStep()
end
function GuideStepRunner:_Alive(nGroup, nStep, nPresent)
if self._bStopped or self._bExiting then
return false
end
if nGroup ~= nil and nGroup ~= self._nGroupGen then
return false
end
if nStep ~= nil and nStep ~= self._nStepGen then
return false
end
if nPresent ~= nil and nPresent ~= self._nPresentGen then
return false
end
return true
end
function GuideStepRunner:_Call(fn, ...)
local ok, a, b = Guard(fn, ...)
if not ok then
printError("[DormGuide] group=" .. tostring(self._nGroupId) .. " step=" .. tostring(self._nIndex) .. " phase=" .. tostring(self._sPhase) .. "\n" .. tostring(a))
return false
end
return true, a, b
end
function GuideStepRunner:_CancelTick()
if self._objTick ~= nil then
TimerManager.Remove(self._objTick)
self._objTick = nil
end
end
function GuideStepRunner:_ArmTick(nInterval, fn)
if self._bStopped or self._bCompleting and fn == nil then
return
end
self:_CancelTick()
local nGroup = self._nGroupGen
local nStep = self._nStepGen
local timer
timer = TimerManager.Add(1, nInterval, self, function()
timer:Cancel(false)
if self._objTick == timer then
self._objTick = nil
end
if self._bStopped or self._nGroupGen ~= nGroup or self._nStepGen ~= nStep then
return
end
if fn ~= nil then
fn()
else
self:_Tick()
end
end, true, true, true, nil)
self._objTick = timer
end
function GuideStepRunner:_Schedule(fn)
self:_ArmTick(0, fn)
end
function GuideStepRunner:_Wake()
if self._bStopped or self._bExiting or self._bCompleting or self._bActionRunning then
return
end
if self._bInTick then
self._bDirty = true
return
end
self:_Tick()
end
function GuideStepRunner:_UnbindStep()
for sName, fn in pairs(self._tbListen) do
EventManager.Remove(sName, self, fn)
end
self._tbListen = {}
end
function GuideStepRunner:_Bind(sName)
if sName == nil or self._tbListen[sName] ~= nil then
return
end
local nGroup, nStep = self._nGroupGen, self._nStepGen
local fn = function(_, ...)
if self:_Alive(nGroup, nStep) then
self:_OnEvent(sName, ...)
end
end
self._tbListen[sName] = fn
EventManager.Add(sName, self, fn)
end
function GuideStepRunner:_BindList(tbName)
if tbName == nil then
return
end
for _, sName in ipairs(tbName) do
self:_Bind(sName)
end
end
function GuideStepRunner:_OnEvent(sName, ...)
if self._bStopped or self._bExiting then
return
end
local step = self._mapStep
if step == nil then
return
end
local bCare = false
local tbArg = table.pack(...)
local Match = function(mapWait)
if mapWait == nil or mapWait.Events == nil then
return false
end
for _, sWait in ipairs(mapWait.Events) do
if sWait == sName then
if mapWait.Match ~= nil then
local ok, bHit = self:_Call(mapWait.Match, self._mapCtx, table.unpack(tbArg, 1, tbArg.n))
return ok and bHit == true
end
return true
end
end
return false
end
if Match(step.WaitTrigger) or Match(step.Present) or Match(step.End) or Match(step.Prepare) or Match(step.Start) then
bCare = true
end
if step.tbHint ~= nil then
for _, sHint in ipairs(step.tbHint) do
if sHint == sName then
bCare = true
end
end
end
if not bCare then
return
end
self:_Wake()
end
function GuideStepRunner:_Now()
return TimerManager.GetUnscaledTime()
end
function GuideStepRunner:_Frozen()
if self._objParent ~= nil and self._objParent.IsRunnerBudgetFrozen ~= nil then
return self._objParent:IsRunnerBudgetFrozen()
end
return false
end
function GuideStepRunner:_TimeoutReached(nStart, nLimit)
if nLimit == nil or nLimit <= 0 then
return false
end
if not self:_IsPlayerWait() and self:_Frozen() then
if not self._bFrozen then
self._bFrozen = true
self._nFreezeAt = self:_Now()
end
return false
end
if self._bFrozen then
self._nStartShift = (self._nStartShift or 0) + (self:_Now() - (self._nFreezeAt or self:_Now()))
self._bFrozen = false
end
local nStartUse = nStart + (self._nStartShift or 0)
return nLimit <= self:_Now() - nStartUse
end
function GuideStepRunner:_SetPhase(sPhase)
if self._sPhase == sPhase then
return
end
if not self._bCompleting then
self:_CancelTick()
end
self._sPhase = sPhase
self._nConfirmGen = self._nConfirmGen + 1
self._bSkipAsking = false
self._objParent:CloseRunnerSkip()
self._nMiss = 0
self:_ResetBudget()
end
function GuideStepRunner:_IsPlayerWait()
local step = self._mapStep
if step == nil then
return false
end
if self._sPhase == "trigger" then
return step.WaitTrigger ~= nil and step.WaitTrigger.bPlayerWait == true
end
if self._sPhase ~= "end" then
return false
end
local sKind = step.Present ~= nil and step.Present.sKind or "None"
if sKind == "Mask" then
return not self._bClickUsed
end
return step.bPlayerWait == true
end
function GuideStepRunner:_NextMissInterval()
self._nMiss = (self._nMiss or 0) + 1
if self._nMiss <= 3 then
return 0
end
local nInterval = 0.05
local nPow = self._nMiss - 3
for _ = 2, nPow do
nInterval = nInterval * 2
if nInterval >= nEventPollInterval then
return nEventPollInterval
end
end
return nInterval
end
function GuideStepRunner:_HoldForEvent(mapWait)
local nInterval = nEventPollInterval
if mapWait == nil then
nInterval = math.min(self:_NextMissInterval(), nEventPollInterval)
elseif mapWait.Interval ~= nil then
nInterval = mapWait.Interval
end
self:_ArmTick(nInterval, nil)
end
function GuideStepRunner:_ResetBudget()
self._nBudgetStart = self:_Now()
self._nStartShift = 0
self._bFrozen = false
self._bSkipAvailable = false
if self._objParent ~= nil then
self._objParent:SetRunnerSkipVisible(false)
end
end
function GuideStepRunner:_BudgetHit(nLimit)
if self:_IsPlayerWait() then
if not self._bSkipAvailable and self:_TimeoutReached(self._nBudgetStart or self:_Now(), nPlayerSkipDelay) then
self._bSkipAvailable = true
self._objParent:SetRunnerSkipVisible(true)
end
return false
end
return self:_TimeoutReached(self._nBudgetStart or self:_Now(), nLimit)
end
function GuideStepRunner:_ApplyPolicy(sStage)
local step = self._mapStep
if step == nil or step.InputPolicy == nil or self._objParent == nil then
return
end
local mapPolicy = step.InputPolicy
if type(mapPolicy) == "function" then
local ok, mapRet = self:_Call(mapPolicy, self._mapCtx, sStage)
mapPolicy = ok and mapRet or nil
elseif mapPolicy.enter ~= nil or mapPolicy.click ~= nil or mapPolicy.present ~= nil or mapPolicy.request ~= nil then
mapPolicy = mapPolicy[sStage]
end
if mapPolicy ~= nil and self._objParent.ApplyRunnerPolicy ~= nil then
self._objParent:ApplyRunnerPolicy(mapPolicy)
end
end
function GuideStepRunner:_Decide()
local step = self._mapStep
if step.ShouldRun == nil then
return "run"
end
local ok, sRet = self:_Call(step.ShouldRun, self._mapCtx)
if not ok then
return "abort"
end
if sRet ~= "run" and sRet ~= "skip" and sRet ~= "abort" and sRet ~= "finish" then
return "run"
end
return sRet
end
function GuideStepRunner:_LeaveByDecide(sDecide)
if sDecide == "skip" then
self:_FinishStep("skip")
return true
end
if sDecide == "abort" then
self:_FinishStep("abort")
return true
end
if sDecide == "finish" then
self:_FinishStep("finish")
return true
end
return false
end
function GuideStepRunner:_CacheValid()
local tr = self._trCache
if tr == nil or tr == false then
return false
end
if tr.gameObject == nil then
return false
end
local step = self._mapStep
if step.CacheValid ~= nil then
local ok, bOk = self:_Call(step.CacheValid, self._mapCtx, tr, self._mapCacheId)
if not ok or bOk ~= true then
return false
end
end
return true
end
function GuideStepRunner:_DropCache()
self._trCache = nil
self._mapCacheId = nil
end
function GuideStepRunner:_StorePoll(anyRet)
if anyRet == false then
return "fail"
end
if anyRet == nil then
self:_DropCache()
return "miss"
end
self._trCache = anyRet
local step = self._mapStep
if step.CacheId ~= nil then
local ok, mapId = self:_Call(step.CacheId, self._mapCtx, anyRet)
self._mapCacheId = ok and mapId or nil
else
self._mapCacheId = {
nInst = anyRet.gameObject ~= nil and anyRet.gameObject:GetInstanceID() or 0
}
end
if not self:_CacheValid() then
self:_DropCache()
return "miss"
end
return "hit"
end
function GuideStepRunner:_PollOnce()
local step = self._mapStep
if step.Poll == nil then
return "none"
end
local ok, anyRet = self:_Call(step.Poll, self._mapCtx)
if not ok then
return "fail"
end
return self:_StorePoll(anyRet)
end
function GuideStepRunner:_BeginStep()
if self._bStopped then
return
end
if self._nIndex > #self._tbSteps then
local bReport = not self._bNeedCheckpoint or self._bReported
self:Stop("finish", bReport)
return
end
self._bExiting = false
self._bCompleting = false
self._nStepGen = self._nStepGen + 1
self._nPresentGen = 0
self._nOpId = 0
self._bPreFindDone = false
self._bEntered = false
self._bPrepared = false
self._bPreparing = false
self._sPrepareVer = nil
self._bPresented = false
self._bStepExited = false
self._bActionRunning = false
self._bSkipAsking = false
self._bClickUsed = false
self._nMiss = 0
self:_DropCache()
self:_ResetBudget()
self._mapStep = self._tbSteps[self._nIndex]
local mapEnd = self._mapStep.End
if mapEnd ~= nil and (mapEnd.sMode == "Event" or mapEnd.sMode == "Condition") and type(mapEnd.Ready) ~= "function" then
printError("[DormGuide] End.Ready 未配置 group=" .. tostring(self._nGroupId) .. " step=" .. tostring(self._nIndex))
self:Stop("abort", false)
return
end
self._sPhase = "decide"
local step = self._mapStep
self:_UnbindStep()
self:_BindList(step.tbHint)
if step.WaitTrigger ~= nil then
self:_BindList(step.WaitTrigger.Events)
end
if step.Present ~= nil then
self:_BindList(step.Present.Events)
end
if step.End ~= nil then
self:_BindList(step.End.Events)
end
if step.Prepare ~= nil then
self:_BindList(step.Prepare.Events)
end
if step.Start ~= nil then
self:_BindList(step.Start.Events)
self._sPhase = "start"
print("[DormGuide] " .. tostring(self._nGroupId) .. " wait start " .. tostring(step.sName))
else
print("[DormGuide] " .. tostring(self._nGroupId) .. " step " .. tostring(self._nIndex) .. " " .. tostring(step.sName))
end
self:_Wake()
end
function GuideStepRunner:_SystemLimit(mapWait)
if mapWait ~= nil and mapWait.Timeout ~= nil then
return mapWait.Timeout
end
local step = self._mapStep
if step ~= nil and step.nSystemTimeout ~= nil then
return step.nSystemTimeout
end
return nDefaultSystemTimeout
end
function GuideStepRunner:_OnWaitTimeout(mapWait)
printError("[DormGuide] timeout group=" .. tostring(self._nGroupId) .. " step=" .. tostring(self._nIndex) .. " name=" .. tostring(self._mapStep.sName) .. " phase=" .. tostring(self._sPhase) .. " limit=" .. tostring(self:_SystemLimit(mapWait)) .. " reported=" .. tostring(self._bReported))
self:_FinishStep("abort")
end
function GuideStepRunner:_Tick()
if not self:_Alive() or self._bCompleting then
return
end
self._bInTick = true
local nGuard = 0
while self:_Alive() and not self._bCompleting and nGuard < 16 do
nGuard = nGuard + 1
local sBefore = self._sPhase
local ok, bWait = self:_Call(self._RunPhase, self)
if not ok and self:_Alive() then
self:_FinishStep("abort")
end
if not self:_Alive() or bWait or self._sPhase == sBefore then
break
end
end
self._bInTick = false
if 16 <= nGuard and self:_Alive() and not self._bCompleting then
self._bDirty = true
end
if self._bDirty and self:_Alive() then
self._bDirty = false
if not self._bCompleting then
self:_Schedule()
end
end
end
function GuideStepRunner:_RunPhase()
local sPhase = self._sPhase
if sPhase == "decide" or sPhase == "show" then
if self:_LeaveByDecide(self:_Decide()) then
return true
end
if sPhase == "decide" then
if self._mapStep.Poll ~= nil and not self._bPreFindDone then
self._sPhase = "prefind"
else
self._sPhase = "enter"
end
else
self:_SetPhase("presentReady")
end
return false
end
if sPhase == "prefind" then
self._bPreFindDone = true
local sRet = self:_PollOnce()
if sRet == "fail" then
self:_OnFail("poll")
return true
end
self._sPhase = "enter"
return false
end
if sPhase == "enter" then
if not self._bEntered then
self:_ApplyPolicy("enter")
end
if not self._bEntered and self._mapStep.OnEnter ~= nil then
local ok = self:_Call(self._mapStep.OnEnter, self._mapCtx)
if not ok or not self:_Alive() then
if self:_Alive() then
self:_FinishStep("abort")
end
return true
end
end
self._bEntered = true
if self._mapStep.Prepare ~= nil then
self:_SetPhase("prepare")
else
self:_SetPhase("trigger")
end
return false
end
if sPhase == "prepare" then
return self:_RunPrepare()
end
if sPhase == "start" then
return self:_RunStart()
end
if sPhase == "trigger" then
return self:_RunTrigger()
end
if sPhase == "find" then
return self:_RunFind()
end
if sPhase == "presentReady" then
return self:_RunPresentReady()
end
if sPhase == "present" then
return self:_RunPresent()
end
if sPhase == "end" or sPhase == "request" then
return self:_RunEnd()
end
return true
end
function GuideStepRunner:_RunStart()
local mapStart = self._mapStep.Start
local bReady = true
if mapStart ~= nil and mapStart.Ready ~= nil then
local ok, bRet = self:_Call(mapStart.Ready, self._mapCtx)
if not ok then
self:_FinishStep("abort")
return true
end
bReady = bRet == true
end
if bReady then
self:_SetPhase("decide")
print("[DormGuide] " .. tostring(self._nGroupId) .. " step " .. tostring(self._nIndex) .. " " .. tostring(self._mapStep.sName))
return false
end
if self:_BudgetHit(self:_SystemLimit(mapStart)) then
self:_OnWaitTimeout(mapStart)
return true
end
self:_HoldForEvent(mapStart)
return true
end
function GuideStepRunner:_RunPrepare()
if self:_LeaveByDecide(self:_Decide()) then
return true
end
local mapPrepare = self._mapStep.Prepare
if mapPrepare.Ready ~= nil then
local ok, bReady = self:_Call(mapPrepare.Ready, self._mapCtx)
if not ok then
self:_FinishStep("abort")
return true
end
if bReady ~= true then
if self:_BudgetHit(self:_SystemLimit(mapPrepare)) then
self:_OnWaitTimeout(mapPrepare)
return true
end
self:_Schedule()
return true
end
end
if self._bPrepared then
local sNow
if mapPrepare.Version ~= nil then
local bOkVer, anyNow = self:_Call(mapPrepare.Version, self._mapCtx)
if bOkVer then
sNow = anyNow
end
end
if mapPrepare.Version == nil or sNow == self._sPrepareVer then
self:_SetPhase("trigger")
return false
end
self._bPrepared = false
end
if self._bPreparing then
return true
end
local sVer
if mapPrepare.Version ~= nil then
local ok, anyVer = self:_Call(mapPrepare.Version, self._mapCtx)
if ok then
sVer = anyVer
end
end
if self._sPrepareVer ~= nil and sVer == self._sPrepareVer then
self._bPrepared = true
self:_SetPhase("trigger")
return false
end
self._bPreparing = true
local ok, bRan = self:_Call(mapPrepare.Run, self._mapCtx)
self._bPreparing = false
if not self:_Alive() then
return true
end
if not ok then
self:_FinishStep("abort")
return true
end
if bRan == false then
if self:_BudgetHit(self:_SystemLimit(mapPrepare)) then
self:_OnWaitTimeout(mapPrepare)
else
self:_Schedule()
end
return true
end
self._bPrepared = true
self._sPrepareVer = sVer
if mapPrepare.Version ~= nil then
local ok2, anyVer = self:_Call(mapPrepare.Version, self._mapCtx)
if ok2 then
self._sPrepareVer = anyVer
end
end
self:_SetPhase("trigger")
return false
end
function GuideStepRunner:_RunTrigger()
if self:_LeaveByDecide(self:_Decide()) then
return true
end
local mapWait = self._mapStep.WaitTrigger
if mapWait == nil or mapWait.Ready == nil then
self:_SetPhase("find")
return false
end
local ok, bReady = self:_Call(mapWait.Ready, self._mapCtx)
if not ok then
self:_FinishStep("abort")
return true
end
if bReady == true then
self._nMiss = 0
self:_SetPhase("find")
return false
end
if self:_BudgetHit(self:_SystemLimit(mapWait)) then
self:_OnWaitTimeout(mapWait)
return true
end
self:_HoldForEvent(mapWait)
return true
end
function GuideStepRunner:_RunFind()
if self:_LeaveByDecide(self:_Decide()) then
return true
end
if self._mapStep.Poll == nil then
self:_SetPhase("show")
return false
end
if self:_CacheValid() then
self:_SetPhase("show")
return false
end
self:_DropCache()
local sRet = self:_PollOnce()
if sRet == "fail" then
self:_OnFail("poll")
return true
end
if sRet == "hit" then
self._nMiss = 0
self:_SetPhase("show")
return false
end
if self:_BudgetHit(self:_SystemLimit(self._mapStep.Find)) then
self:_OnWaitTimeout(self._mapStep.Find)
return true
end
self:_HoldForEvent(nil)
return true
end
function GuideStepRunner:_RunPresentReady()
local mapPresent = self._mapStep.Present
if mapPresent == nil or mapPresent.sKind == "None" or mapPresent.sKind == "Avg" or mapPresent.sKind == "Dict" then
self:_SetPhase("present")
return false
end
if not self:_CacheValid() then
self:_DropCache()
self._bPresented = false
self:_SetPhase("find")
return false
end
if self:_LeaveByDecide(self:_Decide()) then
return true
end
if not self._bShown then
if mapPresent.Show ~= nil then
local ok = self:_Call(mapPresent.Show, self._mapCtx, self._trCache)
if not ok or not self:_Alive() then
if self:_Alive() then
self:_FinishStep("abort")
end
return true
end
end
self._bShown = true
if not self:_CacheValid() then
self:_DropCache()
self._bShown = false
self:_SetPhase("find")
return false
end
end
if not self._trCache.gameObject.activeInHierarchy then
if self:_BudgetHit(self:_SystemLimit(mapPresent)) then
self:_OnWaitTimeout(mapPresent)
else
self:_HoldForEvent(mapPresent)
end
return true
end
if mapPresent.Ready ~= nil then
local ok, bReady = self:_Call(mapPresent.Ready, self._mapCtx, self._trCache)
if not ok then
self:_FinishStep("abort")
return true
end
if bReady ~= true then
if self:_BudgetHit(self:_SystemLimit(mapPresent)) then
self:_OnWaitTimeout(mapPresent)
return true
end
self:_Schedule()
return true
end
end
self:_SetPhase("present")
return false
end
function GuideStepRunner:_RunPresent()
if self._bPresented then
self:_SetPhase("end")
return false
end
local mapPresent = self._mapStep.Present or {sKind = "None"}
local sKind = mapPresent.sKind or "None"
if sKind == "None" then
self._bPresented = true
self:_SetPhase("end")
return false
end
if sKind == "Avg" or sKind == "Dict" then
self._nPresentGen = self._nPresentGen + 1
local nGroup, nStep, nPresent = self._nGroupGen, self._nStepGen, self._nPresentGen
local fnEnd = function(bNatural)
if not self:_Alive(nGroup, nStep, nPresent) then
return
end
if bNatural ~= true then
self:_FinishStep("abort")
return
end
self:_RequestComplete("present")
end
local ok, bStart
if sKind == "Avg" then
ok, bStart = self:_Call(self._objParent.PlayRunnerAvg, self._objParent, mapPresent, fnEnd)
else
ok, bStart = self:_Call(self._objParent.PlayRunnerDict, self._objParent, mapPresent, fnEnd)
end
if not self:_Alive() then
return true
end
if not ok or bStart ~= true then
printError("[DormGuide] 呈现启动失败 group=" .. tostring(self._nGroupId) .. " kind=" .. sKind)
self:_FinishStep("abort")
return true
end
self._bPresented = true
self:_SetPhase("end")
self:_ApplyPolicy("present")
if not self._bCompleting then
self:_HoldForEvent(mapPresent)
end
return true
end
if not self:_CacheValid() then
self:_DropCache()
self._bShown = false
self:_SetPhase("find")
return false
end
self._nPresentGen = self._nPresentGen + 1
self._bClickUsed = false
local nGroup, nStep, nPresent = self._nGroupGen, self._nStepGen, self._nPresentGen
local fnClick = function()
self:_OnClick(nGroup, nStep, nPresent)
end
local ok, bShown = self:_Call(self._objParent.PlayStepMask, self._objParent, self._trCache, mapPresent.mapMask or {}, fnClick)
if not ok or bShown ~= true then
self:_FinishStep("abort")
return true
end
self._bPresented = true
self:_SetPhase("end")
self:_ApplyPolicy("click")
self:_HoldForEvent(self._mapStep.End)
return true
end
function GuideStepRunner:_OnClick(nGroup, nStep, nPresent)
if not self:_Alive(nGroup, nStep, nPresent) then
return
end
if self._bClickUsed or self._bCompleting or self._bSkipAsking or self._sPhase == "request" then
return
end
if not self:_CacheValid() then
self._objParent:ReleaseStepVisual(true)
self._bPresented = false
self._bShown = false
self:_DropCache()
self:_SetPhase("find")
self:_Wake()
return
end
if self:_LeaveByDecide(self:_Decide()) then
return
end
self._bClickUsed = true
self._bActionRunning = true
self._nPresentGen = self._nPresentGen + 1
local mapEnd = self._mapStep.End or {sMode = "Click"}
if mapEnd.sMode ~= "Click" then
self:_SetPhase("request")
self:_ApplyPolicy("request")
end
self._objParent:ReleaseStepVisual(true)
local ok, mapRet
if self._mapStep.Action ~= nil then
ok, mapRet = self:_Call(self._mapStep.Action, self._mapCtx, self._trCache)
else
ok, mapRet = self:_Call(self._objParent.InvokeRunnerTarget, self._objParent, self._trCache)
end
self._bActionRunning = false
if not self:_Alive() then
return
end
if not ok or type(mapRet) ~= "table" then
self:_FinishStep("abort")
return
end
self._nOpId = mapRet.nOpId or 0
self._mapCtx:Set("nOpId", self._nOpId)
if mapRet.sState ~= "accepted" and mapRet.sState ~= "pending" and mapRet.sState ~= "done" then
self:_FinishStep("abort")
return
end
if mapEnd.sMode == "Click" and (mapRet.sState == "accepted" or mapRet.sState == "done") then
self:_RequestComplete("click")
return
end
if mapRet.sState == "done" or mapEnd.Ready ~= nil and self:_EndReady() then
self:_RequestComplete("click")
return
end
if not self:_Alive() then
return
end
self:_SetPhase("request")
self:_ApplyPolicy("request")
self:_Schedule()
end
function GuideStepRunner:_EndReady()
local mapEnd = self._mapStep.End
if mapEnd == nil or mapEnd.Ready == nil then
return false
end
local ok, bReady = self:_Call(mapEnd.Ready, self._mapCtx)
if not ok then
self:_FinishStep("abort")
return false
end
return bReady == true
end
function GuideStepRunner:_RunEnd()
local mapEnd = self._mapStep.End
local mapPresent = self._mapStep.Present or {}
if mapPresent.sKind == "Mask" and self._sPhase == "end" and not self._bClickUsed and (not self:_CacheValid() or not self._trCache.gameObject.activeInHierarchy) then
self._objParent:ReleaseStepVisual(true)
self._bPresented = false
self._bShown = false
self:_DropCache()
self:_SetPhase("find")
return false
end
local bPresentWait = mapPresent.sKind == "Avg" or mapPresent.sKind == "Dict"
local bWaitClick = mapPresent.sKind == "Mask" and not self._bClickUsed
if not bPresentWait and not bWaitClick and self:_EndReady() then
self:_RequestComplete("end")
return true
end
if not self:_Alive() then
return true
end
local mapWait = mapEnd or mapPresent
if self:_BudgetHit(self:_SystemLimit(mapWait)) then
self:_OnWaitTimeout(mapWait)
return true
end
self:_HoldForEvent(mapWait)
return true
end
function GuideStepRunner:_RequestComplete(sWhy)
if self._bStopped or self._bExiting or self._bCompleting then
return
end
self._bCompleting = true
local nGroup, nStep = self._nGroupGen, self._nStepGen
self:_Schedule(function()
if self._bStopped or self._nGroupGen ~= nGroup or self._nStepGen ~= nStep then
return
end
self._bExiting = false
self:_FinishStep("success")
end)
end
function GuideStepRunner:_OnFail(sWhy)
local step = self._mapStep
local sHow = "abort"
if step ~= nil and step.OnFail ~= nil then
sHow = step.OnFail
end
printError("[DormGuide] fail group=" .. tostring(self._nGroupId) .. " step=" .. tostring(self._nIndex) .. " " .. tostring(sWhy))
if sHow == "continue" then
self:_FinishStep("skip")
return
end
self:_FinishStep("abort")
end
function GuideStepRunner:AskSkip()
if not self:_Alive() or self._bCompleting or self._bSkipAsking or not self._bSkipAvailable then
return
end
self._bSkipAsking = true
self._nConfirmGen = self._nConfirmGen + 1
local nGroup, nStep, nConfirm = self._nGroupGen, self._nStepGen, self._nConfirmGen
local IsCurrent = function()
return self:_Alive(nGroup, nStep) and self._nConfirmGen == nConfirm and not self._bCompleting
end
self._objParent:OpenRunnerSkip(nGroup, nStep, nConfirm, function()
if IsCurrent() then
self:Stop("jump", true)
end
end, function()
if IsCurrent() then
self._bSkipAsking = false
self:_Wake()
end
end)
end
function GuideStepRunner:_ExitStep(sReason, bKeepBarrier)
if self._bStepExited then
return
end
self._bStepExited = true
self:_CancelTick()
self:_UnbindStep()
self._nConfirmGen = self._nConfirmGen + 1
if self._objParent ~= nil then
self._objParent:ReleaseStepVisual(bKeepBarrier)
self._objParent:CancelRunnerPresent()
self._objParent:CloseRunnerSkip()
self._objParent:SetRunnerSkipVisible(false)
end
local step = self._mapStep
if step ~= nil and step.OnExit ~= nil then
self:_Call(step.OnExit, self._mapCtx, sReason)
end
end
function GuideStepRunner:_FinishStep(sReason)
if self._bStopped or self._bExiting then
return
end
self._bExiting = true
local step = self._mapStep
if sReason == "success" and step ~= nil and step.bReportDone then
self:_Report()
end
if sReason == "skip" and step ~= nil and step.bReportOnSkip then
self:_Report()
end
if sReason == "finish" then
self:_Report()
end
self:_ExitStep(sReason, sReason ~= "abort" and sReason ~= "finish")
if self._bStopped then
return
end
if sReason == "abort" then
self._bExiting = false
self:Stop("abort", false)
return
end
if sReason == "finish" then
self._bExiting = false
self:Stop("finish", false)
return
end
self._nIndex = self._nIndex + 1
self._bExiting = false
self._bShown = false
self:_BeginStep()
end
function GuideStepRunner:_Report()
if self._bReported or self._objParent == nil then
return
end
if self._objParent.isDebugTest then
self._bReported = true
return
end
self._bReported = true
self._objParent:SendGuideStep(self._nGroupId, -1, self._nGuideType)
end
function GuideStepRunner:Stop(sReason, bReport)
if self._bStopped then
return
end
self._bStopped = true
self._bExiting = true
self._nGroupGen = self._nGroupGen + 1
self._nPresentGen = self._nPresentGen + 1
self:_ExitStep(sReason, false)
local objParent = self._objParent
if self._objGroup ~= nil and self._objGroup.OnClear ~= nil then
Guard(self._objGroup.OnClear, self._objGroup, self._mapCtx, sReason)
end
if sReason == "jump" or bReport then
self:_Report()
end
if sReason == "abort" and PlayerData.Guide ~= nil and PlayerData.Guide.NoteDormAbort ~= nil then
PlayerData.Guide:NoteDormAbort(self._nGroupId)
end
self._objParent = nil
if objParent ~= nil then
objParent._objStepRunner = nil
objParent:FinishRunnerGroup(sReason == "finish" or sReason == "jump")
end
end
return GuideStepRunner