local LuaSystemBase = require_ex('LuaSystem/LuaSystemBase') RadioSystem = HL.Class('RadioSystem', LuaSystemBase.LuaSystemBase) RADIO_DATA_SORT_KEYS = { "priorityKey", "needResumeKey", "addTimeKey" } CONTINUE_RADIO_PRIORITY = -1000 TIMEOUT_TIME = 60 RadioSystem.s_radioIndexCache = HL.StaticField(HL.Table) << {} RadioSystem.m_curShow = HL.Field(HL.Any) RadioSystem.m_waitingQueue = HL.Field(HL.Table) RadioSystem.m_queueSortFunc = HL.Field(HL.Function) RadioSystem.m_audioEndFunc = HL.Field(HL.Function) RadioSystem.m_pauseRefCount = HL.Field(HL.Number) << 0 RadioSystem.inMainHud = HL.Field(HL.Boolean) << true RadioSystem.m_forcePlayRadio = HL.Field(HL.Boolean) << false RadioSystem.m_radioOnlySound = HL.Field(HL.Boolean) << false RadioSystem.m_enableDebugLog = HL.Field(HL.Boolean) << false RadioSystem.m_globalTagHandle = HL.Field(CS.Beyond.Gameplay.Core.GlobalTagHandle) RadioSystem.RadioSystem = HL.Constructor() << function(self) self:RegisterMessage(MessageConst.ON_SCENE_LOAD_START, function(arg) self:_FlushAll() end) self:RegisterMessage(MessageConst.ALL_CHARACTER_DEAD, function(arg) self:_FlushAll() end) self:RegisterMessage(MessageConst.ON_TELEPORT_SQUAD, function(arg) self:_FlushAll() end) self:RegisterMessage(MessageConst.PLAY_CG, function(arg) self:_FlushAll() end) self:RegisterMessage(MessageConst.ON_PLAY_CUTSCENE, function(arg) if not self.m_forcePlayRadio then self:_FlushAll() end end) self:RegisterMessage(MessageConst.REMOTE_COMM_START, function(arg) self:_FlushAll() end) self:RegisterMessage(MessageConst.ON_SNS_FORCE_DIALOG_START, function(arg) self:_FlushAll() end) self:RegisterMessage(MessageConst.ON_DO_CLOSE_MAP, function(arg) self:_FlushAll() end) self:RegisterMessage(MessageConst.ON_GUIDE_STOPPED, function(arg) if arg then local isForceGuide = unpack(arg) if isForceGuide then self:_TryResumeAndShow() end end end) self:RegisterMessage(MessageConst.START_GUIDE_GROUP, function(arg) local info = unpack(arg) if info.type == CS.Beyond.Gameplay.GuideGroupType.Force then self:_TryPauseAndHide() end end) self:RegisterMessage(MessageConst.ON_ULTIMATE_SKILL_START, function(arg) local info = unpack(arg) if info.type == CS.Beyond.Gameplay.GuideGroupType.Force then self:_TryPauseAndHide() end end) self:RegisterMessage(MessageConst.ON_ULTIMATE_SKILL_END, function(arg) self:_TryResumeAndShow() end) self:RegisterMessage(MessageConst.ON_NARRATIVE_BLACK_SCREEN_END, function(arg) self:_TryResumeAndShow() end) self:RegisterMessage(MessageConst.ON_DIALOG_START, function(arg) self:_OnDialogStart(arg) end) self:RegisterMessage(MessageConst.ON_FORCE_PLAY_RADIO_CHANGED, function(arg) local forcePlayRadio = unpack(arg) if self.m_forcePlayRadio ~= forcePlayRadio then self.m_forcePlayRadio = forcePlayRadio if not forcePlayRadio and not self:_CheckCanPlay() then self:_TryPauseAndHide() end end end) self:RegisterMessage(MessageConst.ON_RADIO_ONLY_SOUND_CHANGED, function(arg) local onlySound = unpack(arg) if self.m_radioOnlySound ~= onlySound then self.m_radioOnlySound = onlySound if self.m_curShow then local panel = self:_GetUICtrl() if onlySound then panel:HideSelf() else panel:ShowSelf() panel.animationWrapper:ClearTween(false) panel:PlayAnimationIn() end end end end) self:RegisterMessage(MessageConst.ON_RADIO_EMPTY_SHOW, function(arg) self:_TryResumeAndShow() end) self:RegisterMessage(MessageConst.ON_RADIO_EMPTY_HIDE, function(arg) if not self.m_forcePlayRadio then self:_TryPauseAndHide() end end) self:RegisterMessage(MessageConst.ON_IN_MAIN_HUD_CHANGED, function(arg) local inMainHud = unpack(arg) self.inMainHud = inMainHud if inMainHud then self:_TryResumeAndShow() else if not self.m_forcePlayRadio then self:_TryPauseAndHide() end end end) self:RegisterMessage(MessageConst.ON_NARRATIVE_STATE_CHANGED, function(arg) local inNarrative = unpack(arg) if not inNarrative then self:_TryResumeAndShow() end end) self:RegisterMessage(MessageConst.SHOW_RADIO, function(arg) local data = unpack(arg) self:_TryPlayRadio(data) end) self:RegisterMessage(MessageConst.MANUAL_STOP_RADIO, function(arg) local radioId = unpack(arg) self:ManualStopRadio(radioId) end) self:RegisterMessage(MessageConst.FLUSH_RADIO, function(arg) local radioId = unpack(arg) self:_DoFlushRadio() if not string.isEmpty(radioId) then self:_TryPlayRadio(radioId) else self:_Exit() end end) self:RegisterMessage(MessageConst.ENABLE_RADIO_QUEUE_LOG, function(enable) self.m_enableDebugLog = enable end) end RadioSystem.ManualStopRadio = HL.Method(HL.Any) << function(self, radioId) if self.m_curShow == nil then return end if self.m_curShow.radioId ~= radioId then return end RadioSystem.s_radioIndexCache[radioId] = self.m_curShow.curIndex - 1 self:_CutCurRadio(true) if not self.m_curShow then if not self:_TryShowNextRadio() then self:_Exit() end end end RadioSystem._ClearCurTimer = HL.Method() << function(self) if self.m_curShow and self.m_curShow.timerId and self.m_curShow.timerId > 0 then self:_ClearTimer(self.m_curShow.timerId) end end RadioSystem._AddTimeoutTimer = HL.Method() << function(self) self:_ClearCurTimer() local timerId = self:_StartTimer(TIMEOUT_TIME, function() self:_ShowSingleRadio() end) self.m_curShow.timerId = timerId end RadioSystem._TryPauseAndHide = HL.Method() << function(self) self:_RemoveGlobalTag() if self.m_curShow then if self.m_pauseRefCount == 0 then if self.m_curShow.voiceHandleId and self.m_curShow.voiceHandleId > 0 then VoiceManager:PauseVoice(self.m_curShow.voiceHandleId) self:_ClearCurTimer() elseif self.m_curShow.timerId then local triggerTime = TimerManager:GetTimerTriggerTime(self.m_curShow.timerId) if triggerTime > 0 then self.m_curShow.resumeTime = triggerTime - Time.time end self:_ClearCurTimer() end end self.m_pauseRefCount = self.m_pauseRefCount + 1 end NarrativeUtils.SetRadioId("") self:_Exit() end RadioSystem._IsSoundOnly = HL.Method().Return(HL.Boolean) << function(self) return GameWorld.narrativeManager.radioOnlySound end RadioSystem._TryResumeAndShow = HL.Method() << function(self) local panel = self:_GetUICtrl() if not panel then return end local showRadio = true local resume = false local isShow = panel:IsShow() if self.m_curShow and self.m_pauseRefCount > 0 then resume = true self.m_pauseRefCount = self.m_pauseRefCount - 1 if self.m_curShow.cacheCallbackVoiceHandleId > 0 and self.m_curShow and self.m_curShow.voiceHandleId and self.m_pauseRefCount == 0 then if self.m_curShow.cacheCallbackVoiceHandleId == self.m_curShow.voiceHandleId then self.m_curShow.voiceHandleId = -1 self.m_curShow.cacheCallbackVoiceHandleId = -1 else VoiceManager:ResumeVoice(self.m_curShow.voiceHandleId) self:_AddTimeoutTimer() end end end if self:_CheckCanPlay() then local needShow = not self:_IsSoundOnly() if needShow then panel:ShowSelf() if not isShow then panel.animationWrapper:ClearTween(false) panel:PlayAnimationIn() end end if resume then self:_ContinueCurRadio() elseif not self.m_curShow then if not self:_TryShowNextRadio() then showRadio = false self:_Exit() end elseif not isShow then showRadio = false self:_Exit() end else showRadio = false self:_Exit() end if showRadio then self:_AddGlobalTag() end end RadioSystem._Exit = HL.Method(HL.Opt(HL.Boolean)) << function(self, useAnim) self:_RemoveGlobalTag() self:_HideUI(useAnim) end RadioSystem._HideUI = HL.Method(HL.Opt(HL.Boolean)) << function(self, useAnim) local panel = self:_GetUICtrl() if panel then panel:HideSelf(useAnim) end end RadioSystem._TryPlayRadio = HL.Method(CS.Beyond.Gameplay.Actions.GameAction.RadioRuntimeData) << function(self, data) local radioId = data.radioId local fromBegin = data.fromBegin local index = data.index local callback = data.callback local entity = data.entity local res, radioData = Tables.radioTable:TryGetValue(radioId) if not res then logger.error("Radio play fail, no radioId in table: " .. radioId) return end local extraData = { callback = callback, entity = entity, } local tmpIndex index = index == nil and 0 or index if not fromBegin then if index >= 0 then tmpIndex = index elseif RadioSystem.s_radioIndexCache[radioId] then tmpIndex = RadioSystem.s_radioIndexCache[radioId] RadioSystem.s_radioIndexCache[radioId] = -1 end end if tmpIndex and tmpIndex >= 0 then extraData.curIndex = tmpIndex end if not self.m_curShow then self:_TryAddRadio2Queue(radioId, extraData) self:_TryResumeAndShow() else local curExtraData = { callback = self.m_curShow.callback, entity = self.m_curShow.entity, curIndex = self.m_curShow.curIndex - 1, needResume = true, } if self.m_curShow.priority > radioData.priority then if radioData.continueAfterRadio then self:_TryAddRadio2Queue(radioId, extraData) end elseif self.m_curShow.priority < radioData.priority then self:_TryAddRadio2Queue(radioId, extraData) if self.m_curShow.continueAfterRadio then self:_TryAddRadio2Queue(self.m_curShow.radioId, curExtraData) end self:_CutCurRadio(true) if not self.m_curShow then self:_TryShowNextRadio() end else self:_TryAddRadio2Queue(radioId, extraData) if self.m_curShow.radioId ~= radioId and self.m_curShow.continueAfterRadio then self:_TryAddRadio2Queue(self.m_curShow.radioId, curExtraData, true) end self:_CutCurRadio(true) if not self.m_curShow then self:_TryShowNextRadio() end end end end RadioSystem._ContinueCurRadio = HL.Method() << function(self) if self.m_pauseRefCount == 0 then local panel = self:_GetUICtrl() local needShow = not self:_IsSoundOnly() if self.m_curShow then if self.m_curShow.voiceHandleId then VoiceManager:ResumeVoice(self.m_curShow.voiceHandleId) self:_AddTimeoutTimer() if self.m_curShow.cacheCallbackVoiceHandleId <= 0 and self.m_curShow.cacheCallbackVoiceHandleId == self.m_curShow.voiceHandleId then self:_ShowSingleRadio() end elseif self.m_curShow.resumeTime > 0 then local timerId = self:_StartTimer(self.m_curShow.resumeTime, function() self:_ShowSingleRadio() end) self.m_curShow.timerId = timerId else self:_ShowSingleRadio() end if needShow then panel.view.textTalk:Play() panel.view.textTalkCenter:Play() end if self.m_curShow then NarrativeUtils.SetRadioId(self.m_curShow.radioId) end end end end RadioSystem._TryGetContinueExRadio = HL.Method(HL.Table).Return(HL.Any) << function(self, curData) local interruptedVoiceId = curData.interruptedVoiceId local data if not string.isEmpty(interruptedVoiceId) then local res, continueRadioId = VoiceUtils.TryGetRadioContinueId(interruptedVoiceId, VoiceUtils.GetDefaultContinueRadioSpeakers()) if res then data = { radioId = continueRadioId, curIndex = 0, priority = CONTINUE_RADIO_PRIORITY, addTime = TimeManagerInst.unscaledTime, isContinueExRadio = true, } end end return data end RadioSystem._TryShowNextRadio = HL.Method().Return(HL.Boolean) << function(self) if #self.m_waitingQueue <= 0 then return false end local data = self.m_waitingQueue[1] local radioId = data.radioId if not self:_CheckCanPlay(radioId) then return false end if data.isContinueExRadio then table.remove(self.m_waitingQueue, 1) data = self.m_waitingQueue[1] radioId = data.radioId end local continueRadioData = self:_TryGetContinueExRadio(data) if continueRadioData then self:_DoShowRadio(continueRadioData) self.m_curShow.interruptedVoiceId = self.m_waitingQueue[1].interruptedVoiceId self.m_waitingQueue[1].interruptedVoiceId = nil self.m_waitingQueue[1].continuedRadio = continueRadioData.radioId return true end table.remove(self.m_waitingQueue, 1) self:_DoShowRadio(data) if BEYOND_DEBUG then self:_UpdateLog() end return true end RadioSystem._CheckRadioInQueue = HL.Method(HL.String).Return(HL.Number) << function(self, radioId) for index, v in pairs(self.m_waitingQueue) do if v.radioId == radioId then return index end end return -1 end RadioSystem._TryAddRadio2Queue = HL.Method(HL.String, HL.Opt(HL.Table, HL.Boolean)) << function(self, radioId, extraData, resume) if self:_CheckRadioInQueue(radioId) <= 0 then local res, radioData = Tables.radioTable:TryGetValue(radioId) if res then local data = { radioId = radioId, curIndex = 0, priority = radioData.priority, addTime = TimeManagerInst.unscaledTime, } if extraData then for k, v in pairs(extraData) do data[k] = v end end data["priorityKey"] = -radioData.priority data["needResumeKey"] = resume and 0 or -1 data["addTimeKey"] = resume and data.addTime or -data.addTime table.insert(self.m_waitingQueue, data) table.sort(self.m_waitingQueue, self.m_queueSortFunc) if BEYOND_DEBUG then self:_UpdateLog() end end end end RadioSystem._CutCurRadio = HL.Method(HL.Opt(HL.Boolean)) << function(self, doCallback) local curShow = self.m_curShow local callback local radioId if curShow then callback = curShow.callback radioId = curShow.radioId if curShow.timerId then self:_ClearTimer(curShow.timerId) end if not string.isEmpty(curShow.voiceId) then if curShow.voiceHandleId then VoiceManager:StopVoice(curShow.voiceHandleId) end VoiceCallbackUtil.UnsubscribeOnVoiceEndEvent(self.m_audioEndFunc) end end self.m_curShow = nil self.m_pauseRefCount = 0 if doCallback and callback and not string.isEmpty(radioId) then callback(radioId) end NarrativeUtils.RadioFinish({ radioId }) NarrativeUtils.SetRadioId("") end RadioSystem._UpdateVoiceInfo = HL.Method() << function(self) self.m_curShow.voiceDurations = {} self.m_curShow.voiceTotalDuration = 0 local radioSingleDataList = self.m_curShow.radioSingleDataList for i = 1, radioSingleDataList.Count do local singleData = radioSingleDataList[CSIndex(i)] local voiceId = singleData.audioOverride local res, duration = VoiceUtils.TryGetVoiceDuration(voiceId) if not res then voiceId = "" end if string.isEmpty(voiceId) then self.m_curShow.voiceDurations = {} self.m_curShow.voiceTotalDuration = 0 break end table.insert(self.m_curShow.voiceDurations, duration) self.m_curShow.voiceTotalDuration = self.m_curShow.voiceTotalDuration + duration end end RadioSystem._DoShowRadio = HL.Method(HL.Table) << function(self, data) local panel = self:_GetUICtrl() local needShow = not self:_IsSoundOnly() if needShow then panel.animationWrapper:ClearTween() panel:PlayAnimationIn() else self:_HideUI() end local radioId = data.radioId local curIndex = data.curIndex local callback = data.callback local entity = data.entity local isContinueExRadio = data.isContinueExRadio local res, radioData = Tables.radioTable:TryGetValue(radioId) if res then local canStop = false local radioType = radioData.radioType if radioType == GEnums.RadioType.Wireless then self.m_curShow = { radioId = radioId, priority = radioData.priority, continueAfterRadio = radioData.continueAfterRadio, continueAfterDialog = radioData.continueAfterDialog, radioSingleDataList = radioData.radioSingleDataList, callback = callback, curIndex = curIndex, timerId = nil, voiceHandleId = nil, entity = entity, icon = nil, spriteName = nil, cacheCallbackVoiceHandleId = -1, resumeTime = -1, isContinueExRadio = isContinueExRadio, } self:_UpdateVoiceInfo() self:_ShowSingleRadio() NarrativeUtils.SetRadioId(self.m_curShow.radioId) else logger.error("_DoShowRadio radioType error, only wireless supported, radioId: %s!!!", radioId) end else logger.error("_DoShowRadio data error, radioId: %s!!!", radioId) end end RadioSystem._ShowSingleRadio = HL.Method() << function(self) local panel = self:_GetUICtrl() local isShow = panel:IsShow() local needShow = not self:_IsSoundOnly() if isShow and not needShow then self:_HideUI() elseif not isShow and needShow then panel:ShowSelf() end if self.m_curShow then local nextIndex = self.m_curShow.curIndex + 1 if nextIndex <= self.m_curShow.radioSingleDataList.Count then panel.view.infoNode:ClearTween() local nextSingleData = self.m_curShow.radioSingleDataList[CSIndex(nextIndex)] local num = panel:ShowRadioUI(self.m_curShow, nextSingleData, nextIndex) num = num / I18nUtils.GetTextSpeedFactor() local voiceId = nextSingleData.audioOverride local res, _ = VoiceUtils.TryGetVoiceDuration(voiceId) if not res then voiceId = "" end local audioEffect = nextSingleData.audioEffect local is3D = nextSingleData.is3D self:_ClearCurTimer() local durationOnText = lume.clamp(num * Tables.cinematicConst.textShowDurationPerWord, Tables.cinematicConst.radioMinWaitTime, Tables.cinematicConst.radioMaxWaitTime) if not string.isEmpty(voiceId) then local cfg = CS.Beyond.Gameplay.Audio.NarrativeVoiceConfig(audioEffect, 1) local voiceHandleId local entity = self.m_curShow.entity if is3D and entity then voiceHandleId = VoiceManager:SpeakNarrative(voiceId, entity, cfg) else voiceHandleId = VoiceManager:SpeakNarrative(voiceId, nil, cfg) end if voiceHandleId > 0 and VoiceManager:IsVoicePlaying(voiceHandleId) then self.m_curShow.voiceHandleId = voiceHandleId VoiceCallbackUtil.SubscribeOnVoiceEndEvent(self.m_audioEndFunc) self.m_curShow.voiceId = voiceId self:_AddTimeoutTimer() else local duration = durationOnText self:_ClearCurTimer() local timerId = self:_StartTimer(duration, function() self:_ShowSingleRadio() end) self.m_curShow.timerId = timerId end else local duration = durationOnText local minDuration = Tables.cinematicConst.radioTextMinDuration local finalTime = math.max(minDuration, duration) local timerId = self:_StartTimer(finalTime, function() self:_ShowSingleRadio() end) self.m_curShow.timerId = timerId end self.m_curShow.curIndex = nextIndex self.m_curShow.cacheCallbackVoiceHandleId = -1 else GameWorld.narrativeManager:SetLastFinishRadio(self.m_curShow.radioId) self:_CutCurRadio(true) if not self.m_curShow then if not self:_TryShowNextRadio() then local panel = self:_GetUICtrl() panel:TryPlayInfoNodeOut() self:_Exit() end end end end end RadioSystem._CheckCanPlay = HL.Method(HL.Opt(HL.String)).Return(HL.Boolean) << function(self, radioId) if GameInstance.player.guide.isInForceGuide and not GameInstance.player.guide.isInHelperGuideStep then return false end if self.m_forcePlayRadio then return true end if Utils.isInNarrative() then return false end return self.inMainHud end RadioSystem._UpdateLog = HL.Method() << function(self) if self.m_enableDebugLog then local text = "" for i, debugData in pairs(self.m_waitingQueue) do if i ~= 1 then text = text .. "\n" end text = text .. string.format("{%s : %d}", debugData.radioId, debugData.curIndex) end Notify(MessageConst.UPDATE_DEBUG_TEXT, text) end end RadioSystem._GetUICtrl = HL.Method().Return(HL.Forward("UICtrl")) << function(self) local _, panel = UIManager:IsOpen(PanelId.Radio) return panel end RadioSystem._AddGlobalTag = HL.Method() << function(self) self:_RemoveGlobalTag() self.m_globalTagHandle = GameInstance.player.globalTagsSystem:AddGlobalTag(CS.Beyond.Gameplay.Core.GameplayTag(CS.Beyond.GlobalTagConsts.TAG_RADIO_PATH)) end RadioSystem._RemoveGlobalTag = HL.Method() << function(self) if self.m_globalTagHandle then self.m_globalTagHandle:RemoveTag() end end RadioSystem._OnDialogStart = HL.Method(HL.Opt(HL.Table)) << function(self, data) local _, dialogType = unpack(data) if dialogType == Const.DialogType.Cinematic then self:_FlushAll() else if self.m_curShow then if self.m_curShow.continueAfterDialog then local needContinue, nextIndex = self:_TryGetContinueIndex() if needContinue then local extraData = { curIndex = nextIndex - 1, interruptedVoiceId = self.m_curShow.voiceId, } self:_TryAddRadio2Queue(self.m_curShow.radioId, extraData) end elseif self.m_curShow.isContinueExRadio then if #self.m_waitingQueue > 0 and self.m_waitingQueue[1].continuedRadio == self.m_curShow.radioId then self.m_waitingQueue[1].interruptedVoiceId = self.m_curShow.interruptedVoiceId end end end self:_CutCurRadio(true) end end RadioSystem._TryGetContinueIndex = HL.Method().Return(HL.Boolean, HL.Number) << function(self) local nextIndex = self.m_curShow.curIndex if self.m_curShow and self.m_curShow.continueAfterDialog then if self.m_curShow.voiceTotalDuration <= 0 then return true, nextIndex end local playedTime = 0 if self.m_curShow.curIndex - 1 > 1 then for i = 1, self.m_curShow.curIndex - 1 do playedTime = playedTime + self.m_curShow.voiceDurations[i] end end local res, positionMs = VoiceUtils.TryGetVoicePlayPosition(self.m_curShow.voiceHandleId) local curVoiceDuration = self.m_curShow.voiceDurations[self.m_curShow.curIndex] if res then curVoiceDuration = positionMs / 1000 playedTime = playedTime + curVoiceDuration end local totalPercent = playedTime / self.m_curShow.voiceTotalDuration if totalPercent > 0.9 then return false, -1 end local singlePercent = curVoiceDuration / self.m_curShow.voiceDurations[self.m_curShow.curIndex] if singlePercent > 0.9 then nextIndex = self.m_curShow.curIndex + 1 end if nextIndex > self.m_curShow.radioSingleDataList.Count then return false, -1 end return true, nextIndex else return false, -1 end end RadioSystem._FlushAll = HL.Method(HL.Opt(HL.Any)) << function(self, _) self:_DoFlushRadio() self:_Exit(true) end RadioSystem._DoFlushRadio = HL.Method() << function(self) local radioIds = {} for _, data in pairs(self.m_waitingQueue) do table.insert(radioIds, data.radioId) if data.callback then data.callback(data.radioId) end end if #radioIds > 0 then NarrativeUtils.RadioFinish(radioIds) end self.m_waitingQueue = {} if self.m_curShow then self:_CutCurRadio(true) end if BEYOND_DEBUG then self:_UpdateLog() end end RadioSystem._OnAudioEnd = HL.Method(HL.Number) << function(self, voiceHandleId) if self.m_curShow and self.m_curShow.voiceHandleId == voiceHandleId then if self.m_pauseRefCount > 0 then self.m_curShow.cacheCallbackVoiceHandleId = voiceHandleId else if self.m_curShow.timerId and self.m_curShow.timerId > 0 then self:_ClearTimer(self.m_curShow.timerId) end local timerId = self:_StartTimer(Tables.cinematicConst.radioAudioWaitTime, function() self:_ShowSingleRadio() end) self.m_curShow.timerId = timerId self.m_curShow.cacheCallbackVoiceHandleId = -1 self.m_curShow.voiceHandleId = nil end VoiceCallbackUtil.UnsubscribeOnVoiceEndEvent(self.m_audioEndFunc) end end RadioSystem.OnInit = HL.Override() << function(self) RadioSystem.s_radioIndexCache = {} self.m_waitingQueue = {} self.m_queueSortFunc = Utils.genSortFunction(RADIO_DATA_SORT_KEYS, true) self.m_curShow = nil self.m_audioEndFunc = function(handleId) self:_OnAudioEnd(handleId) end end RadioSystem.OnRelease = HL.Override() << function(self) self:_CutCurRadio(true) RadioSystem.s_radioIndexCache = {} self.m_waitingQueue = {} self.m_curShow = nil self.m_queueSortFunc = nil self.m_audioEndFunc = nil end HL.Commit(RadioSystem) return RadioSystem