local localIndex = {} local locals = {} setmetatable(locals, { ["__index"] = localIndex, ["__newindex"] = function(t, k, v) localIndex[k] = v end, ["__mode"] = "kv" }) setmetatable(localIndex, { ["__index"] = setmetatable({}, { ["__index"] = _G }), ["__mode"] = "kv" }) localIndex["__locals"] = locals local Utils = {} function Utils.printDoString(str, noPrintOriCode) local logs = {} local localPrint = function(...) print(...) logs = lume.concat(logs, { ..., "\n" }) end local localError = function(...) logger.error(...) logs = lume.concat(logs, { ..., "\n" }) end if not lume.find({ "\r\n", "\n", "\r" }, str) then if not noPrintOriCode then localPrint("[Run Script]>>>>---- " .. str .. "---- <<<<") end local hasPrefix = (#str > 1 and str:sub(1, 1) == "=") if hasPrefix then str = string.sub(str, 2) end local retStr = "return " .. str local retFunc, errMsg = loadstring(retStr) if not retFunc and not hasPrefix then retFunc, errMsg = loadstring(str) end if not retFunc then localError(errMsg) else retFunc = setfenv(retFunc, locals) local function collectLocals() if debug.getinfo(2, "f").func ~= retFunc then return end local __debug_idx = 1 while true do local name, value = debug.getlocal(2, __debug_idx) if not name then break end rawset(locals, name, value) __debug_idx = __debug_idx + 1 end end local function traceback(msg) msg = debug.traceback(msg, 2) localError(msg) return msg end local function getReturnValue(status, ...) local retNum = select("#", ...) return status, retNum, { ... } end local status, retNum, retVals = getReturnValue(xpcall(retFunc, traceback)) if status and retNum > 0 then local outputStr = "" for i = 1, retNum do outputStr = outputStr .. inspect(retVals[i], { ['depth'] = 3 }) if i < retNum then outputStr = outputStr .. ", " end end localPrint(outputStr) return logs end end localPrint("> ") else localPrint("> ") end return logs end function Utils.bindLuaRef(item) if type(item) ~= "table" then item = { gameObject = item.gameObject, transform = item.transform, } end local luaRef = item.gameObject:GetComponent("LuaReference") if luaRef then luaRef:BindToLua(item) end return item end function Utils.wrapLuaNode(item) local luaWidget = item.transform:GetComponent("LuaUIWidget") local wrapResult if luaWidget then wrapResult = UIWidgetManager:Wrap(item) else wrapResult = Utils.bindLuaRef(item) if wrapResult then UIUtils.initLuaCustomConfig(wrapResult) end end return wrapResult end function Utils.genSortFunction(keyList, isIncremental) if isIncremental then return function(a, b) for _, key in ipairs(keyList) do local valueA = a[key] local valueB = b[key] if valueA ~= valueB then if valueA == nil or valueB == nil then return valueA == nil end return valueA < valueB end end return false end else return function(a, b) for _, key in ipairs(keyList) do local valueA = a[key] local valueB = b[key] if valueA ~= valueB then if valueA == nil or valueB == nil then return valueA ~= nil end return valueA > valueB end end return false end end end function Utils.genSortFunctionWithIgnore(keyList, isIncremental, ignoreKeyList) return function(a, b) for _, key in ipairs(keyList) do local valueA = a[key] local valueB = b[key] if type(valueA) == "function" then valueA = valueA(a) end if type(valueB) == "function" then valueB = valueB(b) end if valueA ~= valueB then if valueA == nil or valueB == nil then if isIncremental or lume.find(ignoreKeyList, key) then return valueA == nil else return valueA ~= nil end end if isIncremental or lume.find(ignoreKeyList, key) then return valueA < valueB else return valueA > valueB end end end return false end end function Utils.isInclude(table, value) for index, v in pairs(table) do if v == value then return index end end return nil end function Utils.transferToCameraCoordinate(v) local camera = CameraManager.mainCamera local forward = camera.transform.forward forward.y = 0 forward = forward.normalized local left = camera.transform.right left.y = 0 left = left.normalized return v.x * left + v.y * Vector3.up + v.z * forward end function Utils.tobool(v) if type(v) == "number" then return not (v == 0) elseif type(v) == "string" then return not (string.lower(v) == "false") end return false end function Utils.syncFreeLookCamWithMain(ctrl, setPitch) local angles = GameInstance.cameraManager.mainCamera.transform.rotation.eulerAngles; local pitch = angles.x; if pitch > 180 then pitch = pitch - 360 end local horizontalValue = angles.y; if setPitch then ctrl:SetCameraVerticalDegrees(pitch, false) end ctrl:SetCameraHorizontalAngle(horizontalValue, false) ctrl:ForceFlush() end function Utils.getUnlockedCustomObtainWay(itemId) local unlockedObtainWayList = {} local hasUnlockedObtainWay = false local itemCfg = Tables.itemTable:GetValue(itemId) for _, obtainWayId in pairs(itemCfg.obtainWayIds) do local _, obtainWayCfg = Tables.systemJumpTable:TryGetValue(obtainWayId) if obtainWayCfg then local isUnlock = (not obtainWayCfg.bindSystem) or Utils.isSystemUnlocked(obtainWayCfg.bindSystem) if isUnlock then hasUnlockedObtainWay = true table.insert(unlockedObtainWayList, obtainWayCfg) end end end return hasUnlockedObtainWay, unlockedObtainWayList end function Utils.getItemValuableDepotType(itemId) local itemData = Tables.itemTable[itemId] return itemData.valuableTabType end function Utils.isItemInstType(itemId) return GameInstance.player.inventory:IsInstItem(itemId) end function Utils.getItemCount(itemId, forceIncludeCurDepot, allDepot) if string.isEmpty(itemId) then return 0, 0, 0 end local inventory = GameInstance.player.inventory local _, itemData = Tables.itemTable:TryGetValue(itemId) if not itemData then return 0, 0, 0 end if inventory:IsMoneyType(itemData.type) then return inventory:GetItemCountInWallet(itemId) end local valuableDepotType = itemData.valuableTabType local isValuableItem = valuableDepotType ~= GEnums.ItemValuableDepotType.Factory local bagCount, depotCount, walletCount = 0, 0, 0 if isValuableItem then local scope = CS.Beyond.Gameplay.Scope.Create(GEnums.ScopeName.Main) depotCount = inventory:GetItemCountInDepot(scope, 0, itemId) else local isMoney = inventory:IsMoneyType(itemData.type) if isMoney then walletCount = inventory:GetItemCountInWallet(itemId) else bagCount = inventory:GetItemCountInBag(Utils.getCurrentScope(), itemId) if forceIncludeCurDepot or Utils.isInSafeZone() then if allDepot or itemData.showAllDepotCount then depotCount = inventory:GetItemCountInAllFacDepot(Utils.getCurrentScope(), itemId) else depotCount = inventory:GetItemCountInDepot(Utils.getCurrentScope(), Utils.getCurrentChapterId(), itemId) end end end end local count = depotCount + bagCount + walletCount return count, bagCount, depotCount end function Utils.getDepotItemStackLimitCountInCurrentDomain() return Utils.getDepotItemStackLimitCount(Utils.getCurDomainId()) end function Utils.getDepotItemStackLimitCount(domainId) local domainSuccess, domainCfg = Tables.domainDataTable:TryGetValue(domainId) if not domainSuccess then return Tables.domainDepotConst.nonConfigDepotBaseItemLimit or 0 end local stackLimitCount = domainCfg.baseItemStackCount for domainDepotId, domainDepotCfg in pairs(Tables.domainDepotTable) do if domainDepotCfg.domainId == domainId then local domainDepotData = GameInstance.player.domainDepotSystem:GetDomainDepotDataById(domainDepotId) if domainDepotData ~= nil and domainDepotData.level > 0 then stackLimitCount = stackLimitCount + domainDepotData.extraItemStackLimitCount end end end return stackLimitCount end function Utils.getDepotItemCount(itemId, scope, domainId) if string.isEmpty(itemId) then return 0 end scope = scope or Utils.getCurrentScope() local chapterId = domainId and ScopeUtil.ChapterIdStr2Int(domainId) or Utils.getCurrentChapterId() return GameInstance.player.inventory:GetItemCountInDepot(scope, chapterId, itemId) end function Utils.getAllFacDepotItemCount(itemId, scope) if string.isEmpty(itemId) then return 0 end scope = scope or Utils.getCurrentScope() return GameInstance.player.inventory:GetItemCountInAllFacDepot(scope, itemId) end function Utils.getBagItemCount(itemId) if string.isEmpty(itemId) then return 0 end return GameInstance.player.inventory:GetItemCountInBag(Utils.getCurrentScope(), itemId) end function Utils.isInFactoryMode() return GameWorld.worldInfo.inFactoryMode end function Utils.isInSafeZone() if Utils.isInFacMainRegion() then return true end return GameInstance.playerController.isInSaveZone and not Utils.isInFight() end function Utils.isInFacMainRegionAndGetIndex() local inMainRegion, panel = GameInstance.remoteFactoryManager:IsPlayerPositionInMainRegionAndGetIndex() local panelIndex = -1 if inMainRegion and panel then panelIndex = panel.index end return inMainRegion, panelIndex end function Utils.isInFacMainRegion() return GameInstance.remoteFactoryManager:IsPlayerPositionInMainRegion() end function Utils.stringJsonToTable(jsonString) local value = Json.decode(jsonString) return value end function Utils.enableCameraDOF(data) CS.Beyond.Gameplay.View.CameraUtils.EnableDOF(data) end function Utils.disableCameraDOF() CS.Beyond.Gameplay.View.CameraUtils.DisableDOF() end function Utils.isSystemUnlocked(t) if not t or t == GEnums.UnlockSystemType.None then return true end return GameInstance.player.systemUnlockManager:IsSystemUnlockByType(t) end function Utils.round(num, numDecimalPlaces) local mult = 10 ^ (numDecimalPlaces or 0) return math.floor(num * mult + 0.5) / mult end function Utils.timestampToDate(timestamp) local date = os.date("!*t", timestamp + Utils.getServerTimeZoneOffsetSeconds()) return string.format(Language.DATE_FORMAT_MONTH_DAY, date.month, date.day) end function Utils.timestampToDateYMD(timestamp) local date = os.date("!*t", timestamp + Utils.getServerTimeZoneOffsetSeconds()) return string.format(Language.DATE_FORMAT_YEAR_MONTH_DAY, date.year, date.month, date.day) end function Utils.timestampToDateMDHM(timestamp) local date = os.date("!*t", timestamp + Utils.getServerTimeZoneOffsetSeconds()) return string.format(Language.DATE_FORMAT_MONTH_DAY_HOUR_MIN, date.month, date.day, date.hour, date.min) end function Utils.timestampToDateYMDHM(timestamp) local date = os.date("!*t", timestamp + Utils.getServerTimeZoneOffsetSeconds()) return string.format(Language.DATE_FORMAT_YEAR_MONTH_DAY_HOUR_MIN, date.year, date.month, date.day, date.hour, date.min) end function Utils.getTimestampToday0AM() local curTime = DateTimeUtils.GetCurrentTimestampBySeconds() local curDate = os.date("!*t", curTime) local today0AM = { year = curDate.year, month = curDate.month, day = curDate.day, hour = 0, } return os.time(today0AM) end function Utils.getTimestampNowYear1M1Day() local curTime = DateTimeUtils.GetCurrentTimestampBySeconds() local curDate = os.date("!*t", curTime) local today0AM = { year = curDate.year, month = 1, day = 1, hour = 0, } return os.time(today0AM) end function Utils.triggerVoice(triggerKey, speakerId) if not speakerId then speakerId = GameInstance.player.squadManager:GetLeaderId() end return VoiceManager:Response(triggerKey, nil, speakerId, GEnums.VoSpeakerType.Characters) end function Utils.stopDefaultChannelVoice() VoiceManager:StopVoiceOnEntity(nil) end function Utils.checkCGCanSkip(cgId) local res, data = DataManager.cgConfig.data:TryGetValue(cgId) if not res then return false end local skipType = data.skipType local skipTypeInt = skipType:ToInt() if skipTypeInt == CS.Beyond.Gameplay.CutsceneSkipType.NoneSkip:ToInt() then return false elseif skipTypeInt == CS.Beyond.Gameplay.CutsceneSkipType.CanSkip:ToInt() then return true else return GameInstance.player.cinematic:CheckFMVWatched(cgId) end end function Utils.checkCinematicCanSkip(data) local skipType = data.skipType local key = data.cutsceneName local skipTypeInt = skipType:ToInt() if skipTypeInt == CS.Beyond.Gameplay.CutsceneSkipType.NoneSkip:ToInt() then return false elseif skipTypeInt == CS.Beyond.Gameplay.CutsceneSkipType.CanSkip:ToInt() then return true else return GameInstance.player.cinematic:CheckTimelineWatched(key) end end Utils.SkillUtil = CS.Beyond.Gameplay.SkillUtil function Utils.isGameSystemUnlocked(systemId) if string.isEmpty(systemId) then return true end local success, sysData = Tables.gameSystemConfigTable:TryGetValue(systemId) if success and sysData.unlockSystemType ~= GEnums.UnlockSystemType.None then return Utils.isSystemUnlocked(sysData.unlockSystemType) end return true end function Utils.isInFight() return GlobalTagUtils.IsInFight() end function Utils.isInThrowMode() return GameWorld.battle.inThrowMode end function Utils.isInCustomAbility() return GameInstance.playerController.mainCharacter.customAbilityCom:IsInCustomAbility() end function Utils.isInNarrative() return GameWorld.narrativeManager.inNarrative end function Utils.isNarrativeTopPhase() local topPhaseId = PhaseManager:GetTopPhaseId() return topPhaseId == PhaseId.Cinematic or topPhaseId == PhaseId.Dialog or topPhaseId == PhaseId.DialogTimeline end function Utils.isRadioPlaying() local show, _ = UIManager:IsShow(PanelId.Radio) return show end function Utils.getCurrentScope() if UNITY_EDITOR then local callerInfo = debug.getinfo(2, "Sl") return CS.Beyond.Gameplay.Scope.Create( ScopeUtil.GetCurrentScope(), CS.Beyond.Gameplay.Scope.CreateReason.Query, callerInfo.name, callerInfo.source, callerInfo.currentline ) else return CS.Beyond.Gameplay.Scope.Create(ScopeUtil.GetCurrentScope()) end end function Utils.getCurrentChapterId() return ScopeUtil.GetCurrentChapterId() end function Utils.isInMainScope() return ScopeUtil.IsMainScope() end function Utils.isInRpgDungeon() return ScopeUtil.IsPlayerInRpgDungeon() end function Utils.isInBlackbox() return ScopeUtil.IsPlayerInBlackbox() end function Utils.isInDungeonTrain() local dungeonId = GameInstance.dungeonManager.curDungeonId if not dungeonId then return false end return DungeonUtils.isDungeonTrain(dungeonId) end function Utils.isInDungeon() return GameInstance.dungeonManager.inDungeon end function Utils.isInWeekRaid() return GameInstance.mode.modeType == GEnums.GameModeType.WeekRaid or GameInstance.mode.modeType == GEnums.GameModeType.WeekRaidIntro end function Utils.isInDungeonFactory() local dungeonId = GameInstance.dungeonManager.curDungeonId local success, dungeonInfo = Tables.gameMechanicTable:TryGetValue(dungeonId or "") if success then return dungeonInfo.gameCategory == Tables.dungeonConst.dungeonFactoryCategory end return false end function Utils.isDepotManualInOutLocked() local bData = GameWorld.worldInfo.curLevel.levelData.blackbox if not bData then return false end return bData.inventory.depotManualInOutLocked end function Utils.isCurrentMapHasFactoryGrid() local mapId = GameWorld.worldInfo.curMapIdStr local regionMap = GameInstance.remoteFactoryManager:GetVoxelSpaceQuery(mapId) return regionMap ~= nil end function Utils.isForbidden(forbidType) return GameInstance.player.forbidSystem:IsForbidden(forbidType) end function Utils.getForbiddenReason(forbidType) return GameInstance.player.forbidSystem:GetForbidParams(forbidType) end function Utils.isForbiddenWithReason(forbidType) if not Utils.isForbidden(forbidType) then return false end return true, Utils.getForbiddenReason(forbidType) end function Utils.isForbiddenMapTeleport() return Utils.isForbidden(ForbidType.ForbidMapTeleport) or Utils.isForbidden(ForbidType.ForbidMapTeleportButCanGetUnstuck) end function Utils.isSwitchModeDisabled() if GameInstance.player.forbidSystem:IsForbidden(ForbidType.ForbidFactoryMode) then return true end if GameInstance.player.forbidSystem:IsForbidden(ForbidType.DisableSwitchMode) then return true end if not Utils.isCurrentMapHasFactoryGrid() then return true end return false end function Utils.shouldShowSwitchModeBtn() if GameInstance.player.forbidSystem:IsForbidden(ForbidType.DisableSwitchMode) then return false end if not Utils.isCurrentMapHasFactoryGrid() then return false end return true end function Utils.getPlayerName() local playerInfoSystem = GameInstance.player.playerInfoSystem return playerInfoSystem.playerName end function Utils.getPlayerGender() local playerInfoSystem = GameInstance.player.playerInfoSystem return playerInfoSystem.gender end function Utils.getServerAreaType() local playerInfoSystem = GameInstance.player.playerInfoSystem local serverIdType = GEnums.ServerAreaType.__CastFrom(playerInfoSystem.serverAreaType) return serverIdType end function Utils.csList2Table(list) local t = {} for _, v in pairs(list) do t[v] = true end return t end function Utils.teleportToPosition(sceneId, position, rotation, teleportReason, callback, uiType,hubNodeId) if string.isEmpty(sceneId) or position == nil then logger.error("teleportToPosition failed, invalid sceneId or position") return end if Utils.isCurSquadAllDead() then Notify(MessageConst.SHOW_TOAST, Language.LUA_GAME_MODE_FORBID_FACTORY_WATCH) return end teleportReason = teleportReason or GEnums.C2STeleportReason.ServerTpGM uiType = uiType or CS.Beyond.Gameplay.TeleportUIType.Default hubNodeId = hubNodeId or 0 if rotation ~= nil then GameAction.TeleportToPosition(teleportReason, sceneId, position, rotation, uiType, callback,hubNodeId) else GameAction.TeleportToPosition(teleportReason, sceneId, position, Vector3.zero, uiType, callback,hubNodeId) end logger.important(CS.Beyond.EnableLogType.DevOnly, "[LuaTeleport] teleportToPosition", sceneId, position, teleportReason, uiType) end function Utils.teleportToEntity(teleportValidationId, callback) if string.isEmpty(teleportValidationId) then logger.error("teleportToEntity failed, invalid teleportValidationId") return end if Utils.isCurSquadAllDead() then Notify(MessageConst.SHOW_TOAST, Language.LUA_GAME_MODE_FORBID_FACTORY_WATCH) return end GameAction.TeleportToPositionByValidationId(teleportValidationId, callback) logger.important(CS.Beyond.EnableLogType.DevOnly, "[LuaTeleport] teleportToEntity", teleportValidationId) end function Utils.getCurDomainId() return ScopeUtil.GetCurrentChapterIdAsStr() end function Utils.getCurDomainName() return Utils.getDomainName(Utils.getCurDomainId()) end function Utils.getDomainName(domainId) local succ, data = Tables.domainDataTable:TryGetValue(domainId) if succ then return data.domainName else logger.error("No Domain Data", domainId) return "" end end function Utils.isInFocusMode() return FocusModeUtils.isInFocusMode and not string.isEmpty(GameInstance.mode.instId) end function Utils.isInSettlementDefenseDefending() local towerDefenseGame = GameInstance.player.towerDefenseSystem.towerDefenseGame return towerDefenseGame ~= nil and towerDefenseGame.phase == CS.Beyond.Gameplay.Core.TowerDefenseGame.Phase.Defending end function Utils.isInSettlementDefense() return GameInstance.player.towerDefenseSystem.systemInDefense end function Utils.isInSpaceShip() return GameUtil.SpaceshipUtils.IsInSpaceShip() end function Utils.getServerTimeZoneOffsetHours() return CS.Beyond.DateTimeUtils.SERVER_TIME_ZONE.BaseUtcOffset.TotalHours end function Utils.getServerTimeZoneOffsetSeconds() return CS.Beyond.DateTimeUtils.SERVER_TIME_ZONE.BaseUtcOffset.TotalSeconds end function Utils.getClientTimeZoneOffsetSeconds() return CS.System.TimeZoneInfo.Local.BaseUtcOffset.TotalSeconds end function Utils.getCurrentCommonServerRefreshTime() return Utils._getCommonServerRefreshTime(0) end function Utils.getNextCommonServerRefreshTime() return Utils._getCommonServerRefreshTime(1) end function Utils._getCommonServerRefreshTime(offsetDays) local timePerDay = 24 * 60 * 60 local curTime = DateTimeUtils.GetCurrentTimestampBySeconds() + Utils.getServerTimeZoneOffsetSeconds() local curDate = os.date("!*t", curTime) local today4AM = { year = curDate.year, month = curDate.month, day = curDate.day, hour = UIConst.COMMON_SERVER_UPDATE_TIME, } offsetDays = offsetDays or 0 if curDate.hour < UIConst.COMMON_SERVER_UPDATE_TIME then offsetDays = offsetDays - 1 end return os.time(today4AM) + (timePerDay * offsetDays) + Utils._getTimeZoneDiffOfClientAndServer() end function Utils.getNextWeeklyServerRefreshTime() local timePerDay = 24 * 60 * 60 local curTime = DateTimeUtils.GetCurrentTimestampBySeconds() + Utils.getServerTimeZoneOffsetSeconds() local curDate = os.date("!*t", curTime) local today4AM = { year = curDate.year, month = curDate.month, day = curDate.day, hour = UIConst.COMMON_SERVER_UPDATE_TIME, } local weekDay = curDate.wday - 1 if weekDay == 0 then weekDay = 7 end if weekDay == 1 and curDate.hour < UIConst.COMMON_SERVER_UPDATE_TIME then return os.time(today4AM) + Utils._getTimeZoneDiffOfClientAndServer() end local deltaDays = 8 - weekDay return os.time(today4AM) + timePerDay * deltaDays + Utils._getTimeZoneDiffOfClientAndServer() end function Utils._getTimeZoneDiffOfClientAndServer() return CS.System.TimeZoneInfo.Local:GetUtcOffset(CS.System.DateTime.Now).TotalSeconds - Utils.getServerTimeZoneOffsetSeconds() end function Utils.getNextMonthlyServerRefreshTime() local timePerDay = 24 * 60 * 60 local curTime = DateTimeUtils.GetCurrentTimestampBySeconds() + Utils.getServerTimeZoneOffsetSeconds() local curDate = os.date("!*t", curTime) local today4AM = { year = curDate.year, month = curDate.month, day = curDate.day, hour = UIConst.COMMON_SERVER_UPDATE_TIME, } local monthDay = curDate.day if monthDay == 1 and curDate.hour < UIConst.COMMON_SERVER_UPDATE_TIME then return os.time(today4AM) + Utils._getTimeZoneDiffOfClientAndServer() end local monthTotalDays = os.date("%d", os.time({ year = curDate.year, month = curDate.month + 1, day = 0, })) local deltaDays = monthTotalDays + 1 - monthDay return os.time(today4AM) + timePerDay * deltaDays + Utils._getTimeZoneDiffOfClientAndServer() end function Utils.appendUTC(timeStr) local hour = Utils.getServerTimeZoneOffsetHours() if hour >= 0 then return string.format("%s (UTC+%d)", timeStr, math.abs(hour)) else return string.format("%s (UTC-%d)", timeStr, math.abs(hour)) end end function Utils.timeStr2TimeStamp(timeStr, timeZoneSeconds) local pattern = "(%d+)/(%d+)/(%d+) (%d+):(%d+):(%d+)" local year, month, day, hour, min, sec = timeStr:match(pattern) local timeTable = { year = tonumber(year), month = tonumber(month), day = tonumber(day), hour = tonumber(hour), min = tonumber(min), sec = tonumber(sec) } local localTimestamp = os.time(timeTable) local localTimeZoneOffset = os.difftime(os.time(), os.time(os.date("!*t"))) local timestamp = localTimestamp - timeZoneSeconds + localTimeZoneOffset return math.floor(timestamp) end function Utils.isCurTimeInTimeIdRange(timeId) local hasCfg, timeCfg = Tables.timeRangeTable:TryGetValue(timeId) if not hasCfg then logger.error("时间区间表不存在该timeId:" .. timeId) return false end local serverAreaTypeInt = Utils.getServerAreaType():GetHashCode() local timeRange = timeCfg.timeRangeList[CSIndex(serverAreaTypeInt)] local timeZoneSeconds = Utils.getServerTimeZoneOffsetSeconds() local openTs = Utils.timeStr2TimeStamp(timeRange.openTime, timeZoneSeconds) local closeTs = nil if not string.isEmpty(timeRange.closeTime) then closeTs = Utils.timeStr2TimeStamp(timeRange.closeTime, timeZoneSeconds) end local curTs = DateTimeUtils.GetCurrentTimestampBySeconds() if closeTs == nil then return curTs >= openTs else return curTs >= openTs and curTs <= closeTs end end function Utils.isNotObtainCanShow(isNotObtainShow, notObtainShowTimeId) if isNotObtainShow then return true end if string.isEmpty(notObtainShowTimeId) then return false end return Utils.isCurTimeInTimeIdRange(notObtainShowTimeId) end function Utils.checkSettlementOrderCanSubmit(settlementId, domainId, context) local orderId = GameInstance.player.settlementSystem:GetSettlementOrderId(settlementId) if orderId == nil then return CS.Beyond.Gameplay.SettlementSystem.EOrderSubmitState.None end local itemConsumeDic = {} local orderData = Tables.settlementOrderDataTable[orderId] for _, costItem in pairs(orderData.costItems) do if context[costItem.id] == nil then context[costItem.id] = Utils.getDepotItemCount(costItem.id, nil, domainId) end itemConsumeDic[costItem.id] = itemConsumeDic[costItem.id] and itemConsumeDic[costItem.id] + costItem.count or costItem.count end local canSubmit = true for id, count in pairs(itemConsumeDic) do if context[id] < count then canSubmit = false break end end if canSubmit then for id, count in pairs(itemConsumeDic) do context[id] = context[id] - count end end return canSubmit end function Utils.getOrderSubmitStateBySettlementId(domainId, settlementId, itemContext) local oneCanSubmit = false local oneCantSubmit = false local orderId = GameInstance.player.settlementSystem:GetSettlementOrderId(settlementId) if orderId ~= nil then local canSubmit = Utils.checkSettlementOrderCanSubmit(settlementId, domainId, itemContext) if canSubmit then oneCanSubmit = true else oneCantSubmit = true end end if oneCanSubmit and not oneCantSubmit then return CS.Beyond.Gameplay.SettlementSystem.EOrderSubmitState.All elseif oneCanSubmit and oneCantSubmit then return CS.Beyond.Gameplay.SettlementSystem.EOrderSubmitState.Part elseif oneCantSubmit then return CS.Beyond.Gameplay.SettlementSystem.EOrderSubmitState.Zero else return CS.Beyond.Gameplay.SettlementSystem.EOrderSubmitState.None end end function Utils.getOrderSubmitStateByDomainId(domainId, itemContext) local oneCanSubmit = false local oneCantSubmit = false for i, settlementId in pairs(Tables.domainDataTable[domainId].settlementGroup) do local orderId = GameInstance.player.settlementSystem:GetSettlementOrderId(settlementId) if orderId ~= nil then local canSubmit = Utils.checkSettlementOrderCanSubmit(settlementId, domainId, itemContext) if canSubmit then oneCanSubmit = true else oneCantSubmit = true end end end if oneCanSubmit and not oneCantSubmit then return CS.Beyond.Gameplay.SettlementSystem.EOrderSubmitState.All elseif oneCanSubmit and oneCantSubmit then return CS.Beyond.Gameplay.SettlementSystem.EOrderSubmitState.Part elseif oneCantSubmit then return CS.Beyond.Gameplay.SettlementSystem.EOrderSubmitState.Zero else return CS.Beyond.Gameplay.SettlementSystem.EOrderSubmitState.None end end function Utils.intToEnum(enumType, value) return CS.System.Enum.ToObject(enumType, value) end function Utils.needMissionHud() if GameInstance.mode.hideMissionHud then return false end if GameInstance.player.towerDefenseSystem.hudState == CS.Beyond.Gameplay.TowerDefenseSystem.HUDState.WaitingFinished then return false end return true end function Utils.canJumpToSystem(jumpId) local cfg = Tables.systemJumpTable[jumpId] local isUnlock = Utils.isSystemUnlocked(cfg.bindSystem) if isUnlock then local phaseId = PhaseId[cfg.phaseId] local phaseArgs if not string.isEmpty(cfg.phaseArgs) then phaseArgs = Json.decode(cfg.phaseArgs) end if not phaseId or PhaseManager:CheckCanOpenPhase(phaseId, phaseArgs) then return true end end return false end function Utils.jumpToSystem(jumpId) local cfg = Tables.systemJumpTable[jumpId] local isUnlock = Utils.isSystemUnlocked(cfg.bindSystem) if not isUnlock then Notify(MessageConst.SHOW_TOAST, Language.LUA_SYSTEM_LOCK) return end local phaseId = PhaseId[cfg.phaseId] local phaseArgs if not string.isEmpty(cfg.phaseArgs) then phaseArgs = Json.decode(cfg.phaseArgs) end if phaseId == PhaseId.CharInfo then CharInfoUtils.openCharInfoBestWay(phaseArgs) else PhaseManager:GoToPhase(phaseId, phaseArgs) end end function Utils.unlockCraft(itemId) local hasCraft, craftIds = Tables.factoryItemAsManualCraftOutcomeTable:TryGetValue(itemId) local unlock = true if hasCraft then unlock = false for _, craftId in pairs(craftIds.list) do unlock = GameInstance.player.facManualCraft:IsCraftUnlocked(craftId) if unlock then break end end end return hasCraft, unlock end function Utils.nextStaminaRecoverLeftTime() local nextRecoverTime = GameInstance.player.inventory.staminaNextRecoverTime local curTime = DateTimeUtils.GetCurrentTimestampBySeconds() local nextLeftTime = nextRecoverTime - curTime if (nextLeftTime <= 0) then return 0 else return nextLeftTime end end function Utils.fullStaminaRecoverLeftTime() local nextLeftTime = Utils.nextStaminaRecoverLeftTime() local curStamina = GameInstance.player.inventory.curStamina local maxStamina = GameInstance.player.inventory.maxStamina local fullLeftTime = (maxStamina - curStamina - 1) * Tables.dungeonConst.staminaRecoverDuration + nextLeftTime if (fullLeftTime <= 0) then return 0 else return fullLeftTime end end function Utils.tryGetTableCfg(table, id) local hasCfg, cfg = table:TryGetValue(id) if not hasCfg then logger.error(ELogChannel.Cfg, "[Utils.tryGetTableCfg] missing cfg, id = "..id) return nil end return cfg end function Utils.getImgGenderDiffPath(imgText) local path = string.match(imgText, UIConst.UI_RICH_CONTENT_IMG_GENDER_DIFF_MATCH) if not path then path = imgText else local isMale = Utils.getPlayerGender() == CS.Proto.GENDER.GenMale if isMale then path = string.format(UIConst.UI_RICH_CONTENT_IMG_GENDER_DIFF_FORMAT_MALE, path) else path = string.format(UIConst.UI_RICH_CONTENT_IMG_GENDER_DIFF_FORMAT_FEMALE, path) end end return path end function Utils.isCurSquadAllDead() return GameInstance.player.squadManager:IsCurSquadAllDead() end function Utils.zoomCamera(delta) if LuaSystemManager.factory.inTopView then LuaSystemManager.factory.m_topViewCamCtrl:ZoomCamera(delta, true, function(pos) return FactoryUtils.clampTopViewCamTargetPosition(pos) end) else CameraManager:Zoom(delta) end end function Utils.getClientVar(key, default_value) local globalVar = GameInstance.player.globalVar if globalVar then local res, data = globalVar:TryGetClientVar(key) if res then return data end end return default_value end function Utils.getGuideText(key) local find, textInfo = Tables.textTable:TryGetValue(key) if find then return textInfo end return "" end function Utils.compareInt(a,b,compareOperator) if compareOperator == GEnums.CompareOperator.Equal then return a == b; end if compareOperator == GEnums.CompareOperator.NotEqual then return a ~= b; end if compareOperator == GEnums.CompareOperator.GreaterThan then return a > b; end if compareOperator == GEnums.CompareOperator.GreaterEqual then return a >= b; end if compareOperator == GEnums.CompareOperator.LessThan then return a < b; end if compareOperator == GEnums.CompareOperator.LessEqual then return a <= b; end return false end function Utils.openURL(url) CS.Beyond.UI.WebApplication.Start(url) end function Utils.getLTItemExpireInfo(itemId, instId) local info = { almostExpireTime = 0, isLTItem = false, expireTime = 0, isExpire = true, } local inventory = GameInstance.player.inventory local itemCfg = Tables.itemTable[itemId] local hasCfg, ltItemCfg = Tables.lTItemTypeTable:TryGetValue(itemCfg.type) if hasCfg then info.almostExpireTime = ltItemCfg.daysBeforeExpireToNotify * Const.SEC_PER_DAY end local valuableDepotType = itemCfg.valuableTabType; local contains = inventory.valuableDepots:ContainsKey(valuableDepotType) if contains then local depot = inventory.valuableDepots[valuableDepotType]:GetOrFallback(CS.Beyond.Gameplay.Scope.Create(GEnums.ScopeName.Main)) if depot then local hasData, itemBundle = depot.instItems:TryGetValue(instId) if hasData and itemBundle.isLimitTimeItem then info.isLTItem = true info.expireTime = itemBundle.instData.expireTs info.isExpire = itemBundle.isExpire end end end return info end function Utils.isSettlementDefenseGuideCompleted() if string.isEmpty(Tables.settlementConst.defenseGuideMissionId) then return true end return GameInstance.player.mission:GetMissionState(Tables.settlementConst.defenseGuideMissionId) == CS.Beyond.Gameplay.MissionSystem.MissionState.Completed end function Utils.getCurMissionIdAndDesc(descType) local missionSystem = GameInstance.player.mission local csMissionType = CS.Beyond.Gameplay.MissionSystem.MissionType local curMissionId = "" local curMissionDesc = Language.LUA_GACHA_STARTER_ALL_MISSION_COMPLETE for missionId, _ in pairs(missionSystem.missions) do local missionInfo = missionSystem:GetMissionInfo(missionId) if missionInfo.missionType == csMissionType.Main then local curMissionState = missionSystem:GetMissionState(missionId) if curMissionState == CS.Beyond.Gameplay.MissionSystem.MissionState.Processing then curMissionId = missionId local chapterId = missionSystem:GetChapterIdByMissionId(missionId) local chapterInfo = missionSystem:GetChapterInfo(chapterId) if descType == "gacha" then curMissionDesc = string.format(Language.LUA_GACHA_STARTER_CUR_MISSION_DESC, chapterInfo.chapterNum:GetText(), chapterInfo.episodeNum:GetText()) elseif descType == "activity" then curMissionDesc = string.format(Language.LUA_ACTIVITY_UNLOCK_CUR_MISSION_DESC, chapterInfo.chapterNum:GetText(), chapterInfo.episodeNum:GetText(), missionSystem:GetMissionInfo(curMissionId).missionName:GetText()) end break end end end return curMissionId, curMissionDesc end function Utils.checkIsPSDevice() return UNITY_PS4 or UNITY_PS5 end function Utils.tryGetItemFirstObtainWay(itemId) local itemCfg = Tables.itemTable:GetValue(itemId) if itemCfg.obtainWayIds then for k, obtainWayId in pairs(itemCfg.obtainWayIds) do local _, obtainWayCfg = Tables.systemJumpTable:TryGetValue(obtainWayId) if obtainWayCfg then local isUnlock = Utils.isSystemUnlocked(obtainWayCfg.bindSystem) if isUnlock then local phaseId = PhaseId[obtainWayCfg.phaseId] local phaseArgs if not string.isEmpty(obtainWayCfg.phaseArgs) then phaseArgs = Json.decode(obtainWayCfg.phaseArgs) end if not phaseId or PhaseManager:CheckCanOpenPhase(phaseId, phaseArgs) then return { name = obtainWayCfg.desc, iconFolder = UIConst.UI_SPRITE_ITEM_TIPS, iconId = obtainWayCfg.iconId, phaseId = phaseId, phaseArgs = phaseArgs, } end end end end end return nil end function Utils.reportPlacementEvent(eventType) if GameInstance.player.gameDataReportSystem:IsPlacementEventReported(eventType) then return end GameInstance.player.gameDataReportSystem:ReportPlacementEvent(eventType) end _G.Utils = Utils return Utils