Files
Endfield-Data/LuaScripts/Common/Utils/Utils.lua
2026-01-31 21:42:01 +07:00

1330 lines
40 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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