local Debug = CS.UnityEngine.Debug
Object = CS.UnityEngine.Object
Sprite = CS.UnityEngine.Sprite
Texture = CS.UnityEngine.Texture
Color = CS.UnityEngine.Color
ColorUtility = CS.UnityEngine.ColorUtility
Transform = CS.UnityEngine.Transform
RectTransform = CS.UnityEngine.RectTransform
GameObject = CS.UnityEngine.GameObject
Vector2 = CS.UnityEngine.Vector2
Vector3 = CS.UnityEngine.Vector3
Quaternion = CS.UnityEngine.Quaternion
NovaAPI = CS.NovaAPI
CSTimerManager = CS.TimerManager
DOTween = CS.DG.Tweening.DOTween
Sequence = CS.DG.Tweening.DOTween.Sequence
Ease = CS.DG.Tweening.Ease
RotateMode = CS.DG.Tweening.RotateMode
TweenExtensions = CS.TweenExtensions
GameUIUtils = CS.GameUIUtils
local PB = require("pb")
require("functions")
local rapidjson = require("rapidjson")
GameEnum = require("Game.CodeGen.GAME_ENUM_DEFINE")
AllEnum = require("GameCore.Common.AllEnum")
Settings = require("GameCore.Common.Settings")
EventId = require("GameCore.Event.EventId")
EventManager = require("GameCore.Event.EventManager")
PlayerData = require("GameCore.Data.PlayerData")
NetMsgId = require("GameCore.Network.NetMsgId")
HttpNetHandler = require("GameCore.Network.HttpNetHandler")
PanelId = require("GameCore.UI.PanelId")
PanelManager = require("GameCore.UI.PanelManager")
BasePanel = require("GameCore.UI.BasePanel")
BaseCtrl = require("GameCore.UI.BaseCtrl")
RedDotDefine = require("GameCore.RedDot.RedDotDefine")
RedDotManager = require("GameCore.RedDot.RedDotManager")
PopUpManager = require("GameCore.Data.PopUpManager")
local util = require("xlua.util")
async_to_sync = util.async_to_sync
coroutine_call = util.coroutine_call
cs_generator = util.cs_generator
loadpackage = util.loadpackage
auto_id_map = util.auto_id_map
hotfix_ex = util.hotfix_ex
bind = util.bind
createdelegate = util.createdelegate
state = util.state
print_func_ref_by_csharp = util.print_func_ref_by_csharp
cs_coroutine = require("xlua.cs_coroutine")
local serpent = require("serpent")
function printLog(str)
	Debug.Log(str)
end
function printWarn(str)
	Debug.LogWarning(str)
end
function printError(str)
	Debug.LogError(str)
end
function printTable(tb)
	if type(tb) == "table" then
		print(serpent.block(tb))
	end
end
function traceback(str)
	Debug.LogError(debug.traceback(str))
end
function timeFormat_MS(value)
	local min = math.floor(value / 60)
	local sec = value - min * 60
	if min < 10 then
		min = "0" .. min
	end
	if sec < 10 then
		sec = "0" .. sec
	end
	return min .. ":" .. sec
end
function timeFormat_HMS(value)
	local hor = math.floor(value / 60 / 60)
	local min = math.floor((value - hor * 60 * 60) / 60)
	local sec = value - hor * 60 * 60 - min * 60
	if hor < 10 then
		hor = "0" .. hor
	end
	if min < 10 then
		min = "0" .. min
	end
	if sec < 10 then
		sec = "0" .. sec
	end
	return hor .. ":" .. min .. ":" .. sec
end
function timeFormat_DHMS(value)
	local day = math.floor(value / 60 / 60 / 24)
	value = value % 86400
	local hor = math.floor(value / 60 / 60)
	local min = math.floor((value - hor * 60 * 60) / 60)
	local sec = value - hor * 60 * 60 - min * 60
	if hor < 10 then
		hor = "0" .. hor
	end
	if min < 10 then
		min = "0" .. min
	end
	if sec < 10 then
		sec = "0" .. sec
	end
	if 0 < day then
		return string.format("%dd %s", day, hor .. ":" .. min .. ":" .. sec)
	else
		return hor .. ":" .. min .. ":" .. sec
	end
end
function timeFormat_Table(value)
	local tbTime = {}
	local day = math.floor(value / 60 / 60 / 24)
	value = value % 86400
	local hour = math.floor(value / 60 / 60)
	local min = math.floor((value - hour * 60 * 60) / 60)
	local sec = value - hour * 60 * 60 - min * 60
	tbTime.day = day
	tbTime.hour = hour
	tbTime.min = min
	tbTime.sec = sec
	return tbTime
end
function GetNextWeekRefreshTime()
	local nCurTimeWithTimeZone = CS.ClientManager.Instance.serverTimeStampWithTimeZone
	local nCurTime = CS.ClientManager.Instance.serverTimeStamp
	local tbCurDate = os.date("!*t", nCurTimeWithTimeZone)
	local nCurWeekday = tbCurDate.wday
	nCurWeekday = nCurWeekday == 1 and 7 or nCurWeekday - 1
	local nDays = 7 - nCurWeekday
	local nDailyRefreshOffsetHour = ConfigTable.GetConfigNumber("DailyRefreshOffsetHour")
	if nDailyRefreshOffsetHour > tbCurDate.hour then
		if nCurWeekday == 1 then
			nDays = 0
		else
			nDays = nDays + 1
		end
	end
	local nNextOpenTime = CS.ClientManager.Instance:GetNextRefreshTime(nCurTime)
	if 0 < nDays then
		for i = 1, nDays do
			nCurTime = nNextOpenTime
			nNextOpenTime = CS.ClientManager.Instance:GetNextRefreshTime(nCurTime)
		end
	end
	return nNextOpenTime
end
function FormatNum(num)
	if num <= 0 then
		return 0
	else
		local t1, t2 = math.modf(num)
		if 0 < t2 then
			return num
		else
			return t1
		end
	end
end
function FormatEffectValue(nValue, bPercent, nFormat)
	if bPercent then
		nValue = nValue * 100
	end
	if nValue == 0 and bPercent then
		return "0%"
	end
	local sValue
	if nFormat == GameEnum.ValueFormat.Int then
		nValue = math.floor(nValue)
		sValue = string.format("%d", nValue)
	elseif nFormat == GameEnum.ValueFormat.ODP then
		sValue = string.format("%.1f", nValue)
		sValue = sValue:gsub("0+$", "")
	elseif nFormat == GameEnum.ValueFormat.TDP then
		sValue = string.format("%.2f", nValue)
		sValue = sValue:gsub("0+$", "")
	end
	sValue = sValue:gsub("%.$", "")
	if bPercent then
		sValue = sValue .. "%"
	end
	return sValue
end
function FormatWithCommas(nValue)
	local s, integer, decimal = tostring(nValue), "", ""
	local dot = string.find(s, "%.")
	if dot then
		integer = string.sub(s, 1, dot - 1)
		decimal = string.sub(s, dot + 1)
	else
		integer = s
	end
	integer = string.reverse(integer)
	integer = string.gsub(integer, "(%d%d%d)", "%1,")
	integer = string.reverse(integer)
	if string.sub(integer, 1, 1) == "," then
		integer = string.sub(integer, 2)
	end
	if decimal ~= "" then
		return integer .. "." .. decimal
	else
		return integer
	end
end
function indexOfPose(sPose)
	local tbCharPose = {
		"a",
		"b",
		"c",
		"d",
		"e",
		"f",
		"g",
		"h",
		"i",
		"j",
		"k",
		"l",
		"m",
		"n",
		"o",
		"p",
		"q",
		"r",
		"s",
		"t",
		"u",
		"v",
		"w",
		"x",
		"y",
		"z",
		"aa",
		"ab",
		"ac",
		"ad",
		"ae",
		"af",
		"ag",
		"ah",
		"ai",
		"aj",
		"ak",
		"al",
		"am",
		"an",
		"ao",
		"ap",
		"aq",
		"ar",
		"as",
		"at",
		"au",
		"av",
		"aw",
		"ax",
		"ay",
		"az"
	}
	local nIdx = table.indexof(tbCharPose, sPose)
	if nIdx < 0 then
		nIdx = 0
	end
	return nIdx
end
function instantiate(...)
	return GameObject.Instantiate(...)
end
function destroy(obj)
	if not obj:IsNull() then
		GameObject.Destroy(obj)
	end
end
function destroyImmediate(obj)
	if not obj:IsNull() then
		GameObject.DestroyImmediate(obj)
	end
end
function luaCoroutineStart(runner, ...)
	return runner:StartCoroutine(util.cs_generator(...))
end
function luaCoroutineStop(runner, coroutine)
	runner:StopCoroutine(coroutine)
end
function delChildren(go)
	if go.transform.childCount > 0 then
		for i = go.transform.childCount, 1, -1 do
			local obj = go.transform:GetChild(i - 1).gameObject
			destroy(obj)
		end
	end
end
function ProcContentWord(sContentWord, bUsePresetColor)
	if bUsePresetColor == nil then
		bUsePresetColor = false
	end
	local tbContent = string.split(sContentWord, "$")
	local sRetContent = tbContent[1]
	for i, v in ipairs(tbContent) do
		if 1 < i then
			local sHeadFourChar = string.sub(v, 1, 4)
			local nWordId = tonumber(sHeadFourChar)
			local sWord
			if type(nWordId) == "number" then
				local data = ConfigTable.GetData("ContentWord", nWordId)
				if data ~= nil then
					sWord = data.Word
				end
			end
			if sWord == nil then
				sWord = "$" .. v
			else
				if bUsePresetColor == true then
					local sColor = ConfigTable.GetData("ContentWord", nWordId).PresetColor or ""
					if sColor ~= "" then
						sWord = string.format("<color=%s>%s</color>", sColor, sWord)
					end
				end
				sWord = string.gsub(v, sHeadFourChar, sWord)
			end
			sRetContent = sRetContent .. sWord
		end
	end
	return sRetContent
end
function GetLanguageIndex(sLan)
	for i, v in ipairs(AllEnum.LanguageInfo) do
		if v[1] == sLan then
			return i
		end
	end
	return 1
end
function GetLanguageByIndex(nIndex)
	local tbLanInfo = AllEnum.LanguageInfo[nIndex]
	if tbLanInfo ~= nil then
		return tbLanInfo[1]
	else
		return AllEnum.Language.CN
	end
end
function GetLanguageSurfixByIndex(nIndex)
	local tbLanInfo = AllEnum.LanguageInfo[nIndex]
	if tbLanInfo ~= nil then
		return tbLanInfo[3]
	else
		return "_cn"
	end
end
function GetAvgLuaRequireRoot(nIndex)
	local sFolderSurfix = GetLanguageSurfixByIndex(nIndex) .. "/"
	local sRootPath = "Game/UI/Avg/" .. sFolderSurfix
	if AVG_EDITOR == true and NovaAPI.IsRuntimeWindowsPlayer() == true then
		sRootPath = "/../../../../" .. sFolderSurfix
	end
	return sRootPath
end
function ProcAvgTextContent(sContent, nLanguageIndex)
	local nIndex = PlayerData.Base:GetPlayerSex() == true and 2 or 1
	if nLanguageIndex == nil then
		nLanguageIndex = GetLanguageIndex(Settings.sCurrentTxtLanguage)
	end
	if nLanguageIndex < 1 or nLanguageIndex > #AllEnum.LanguageInfo then
		nLanguageIndex = 1
	end
	sContent = ProcContentWord(sContent, true)
	sContent = string.gsub(sContent, "==RT==", "\n")
	sContent = string.gsub(sContent, "==PLAYER_NAME==", PlayerData.Base:GetPlayerNickName())
	local AvgUIText = require(GetAvgLuaRequireRoot(nLanguageIndex) .. "Preset/AvgUIText")
	local mapSex = AvgUIText.SEX
	for k, v in pairs(mapSex) do
		sContent = string.gsub(sContent, k, v[nIndex])
	end
	return sContent
end
function ProcAvgTextContentFallback(sTextLan, sVoLan, bIsMale, sCN_F, sCN_M, sJP_F, sJP_M)
	if sCN_F == nil then
		sCN_F = ""
	end
	if sCN_M == nil then
		sCN_M = ""
	end
	if sJP_F == nil then
		sJP_F = ""
	end
	if sJP_M == nil then
		sJP_M = ""
	end
	local sContent = ""
	local tbFallback
	if sTextLan == AllEnum.Language.CN then
		tbFallback = {sCN_F}
		if bIsMale == true then
			table.insert(tbFallback, sCN_M)
		end
		if sVoLan == AllEnum.Language.JP then
			table.insert(tbFallback, sJP_F)
			if bIsMale == true then
				table.insert(tbFallback, sJP_M)
			end
		end
	elseif sTextLan == AllEnum.Language.JP then
		tbFallback = {sJP_F}
		if MULTI_LANGUAGE_GENDER_TEXT_COMPATIBLE == true and sJP_F == "" then
			table.insert(tbFallback, sCN_F)
		end
		if bIsMale == true then
			table.insert(tbFallback, sJP_M)
			if MULTI_LANGUAGE_GENDER_TEXT_COMPATIBLE == true and sJP_M == "" then
				table.insert(tbFallback, sCN_M)
			end
		end
	elseif sTextLan == AllEnum.Language.TW or sTextLan == AllEnum.Language.EN or sTextLan == AllEnum.Language.KR then
		tbFallback = {sJP_F}
		if bIsMale == true then
			table.insert(tbFallback, sJP_M)
		end
		if sVoLan == AllEnum.Language.CN then
			table.insert(tbFallback, sCN_F)
			if bIsMale == true then
				table.insert(tbFallback, sCN_M)
			end
		end
		if MULTI_LANGUAGE_GENDER_TEXT_COMPATIBLE == true and sVoLan == AllEnum.Language.JP and sJP_F == "" and sJP_M == "" then
			table.insert(tbFallback, sCN_F)
			if bIsMale == true then
				table.insert(tbFallback, sCN_M)
			end
		end
	end
	if type(tbFallback) == "table" then
		local n = #tbFallback
		for i = n, 1, -1 do
			local s = tbFallback[i]
			if type(s) == "string" and s ~= "" then
				sContent = s
				break
			end
		end
	end
	return sContent
end
function AdjustMainRoleAvgCharId(sAvgCharId)
	if sAvgCharId == nil then
		sAvgCharId = "avg3_100"
	end
	local tbMainRoleAvgCharId = {"avg3_100", "avg3_101"}
	local nIdx = table.indexof(tbMainRoleAvgCharId, sAvgCharId)
	if 0 < nIdx then
		local nIndex = PlayerData.Base:GetPlayerSex() == true and 2 or 1
		return tbMainRoleAvgCharId[nIndex]
	else
		return sAvgCharId
	end
end
function CalcTextAnimDuration(sContent, nLanguageIndex, bIsBB)
	local tbInterval = {
		0.03,
		0.03,
		0.03,
		0.01,
		0.03
	}
	local tbIntervalForBB = {
		0.25,
		0.25,
		0.25,
		0.083,
		0.25
	}
	local nInterval = bIsBB == true and tbIntervalForBB[nLanguageIndex] or tbInterval[nLanguageIndex]
	if nInterval == nil then
		nInterval = bIsBB == true and tbIntervalForBB[1] or tbInterval[1]
	end
	local sPureContent = string.gsub(sContent, "<.->", "")
	sPureContent = string.gsub(sPureContent, "\n", "")
	local nDuration = string.utf8len(sPureContent) * nInterval
	return nDuration
end
function Avg_ProcRes_M_F(sName)
	local nLen = string.len(sName)
	local surfix = string.sub(sName, nLen - 2, nLen)
	if surfix == "_MP" or surfix == "_FP" then
		if PlayerData.Base:GetPlayerSex() == true then
			surfix = "_MP"
		else
			surfix = "_FP"
		end
		sName = string.sub(sName, 1, nLen - 3) .. surfix
		return sName
	else
		return sName
	end
end
function Avg_ProcEnquotes(s)
	s = string.gsub(s, "\\", "")
	s = string.gsub(s, "'", "\\'")
	s = string.gsub(s, "\"", "\\\"")
	return s
end
function decodeJson(sJson)
	local tbData = {}
	if type(sJson) == "string" and sJson ~= "" then
		tbData = rapidjson.decode(sJson)
		if tbData == nil then
			tbData = {}
			printError("json文本配置格式有误，该文本为：" .. sJson)
		end
	end
	return tbData
end
function IsStartsWith(str, start)
	return string.sub(str, 1, string.len(start)) == start
end
function UFT8ToUnicode(convertStr)
	if type(convertStr) ~= "string" then
		return convertStr
	end
	local resultDec = 0
	local i = 1
	local num1 = string.byte(convertStr, i)
	if num1 ~= nil then
		local tempVar1, tempVar2 = 0, 0
		if 0 <= num1 and num1 <= 127 then
			tempVar1 = num1
			tempVar2 = 0
		elseif num1 & 224 == 192 then
			local t1 = 0
			local t2 = 0
			t1 = num1 & 31
			i = i + 1
			num1 = string.byte(convertStr, i)
			t2 = num1 & 63
			tempVar1 = t2 | (t1 & 3) << 6
			tempVar2 = t1 >> 2
		elseif num1 & 240 == 224 then
			local t1 = 0
			local t2 = 0
			local t3 = 0
			t1 = num1 & 31
			i = i + 1
			num1 = string.byte(convertStr, i)
			t2 = num1 & 63
			i = i + 1
			num1 = string.byte(convertStr, i)
			t3 = num1 & 63
			tempVar1 = (t2 & 3) << 6 | t3
			tempVar2 = t1 << 4 | t2 >> 2
		end
		resultDec = tempVar2 * 256 + tempVar1
	end
	return resultDec
end
local DecodeChangeInfo = function(mapChangeInfo)
	local mapDecodedChangeInfo = {}
	if type(mapChangeInfo) ~= "table" then
		return mapDecodedChangeInfo
	end
	if mapChangeInfo.Props == nil then
		return mapDecodedChangeInfo
	end
	for nIndex, mapGoogleProtobufAny in ipairs(mapChangeInfo.Props) do
		if mapGoogleProtobufAny.type_url == "" or mapGoogleProtobufAny.type_url == nil then
			printError("ChangeInfo 格式错误")
			return mapDecodedChangeInfo
		end
		local tbSubUrl = string.split(mapGoogleProtobufAny.type_url, "/")
		local sProtoMsgName = tbSubUrl[2]
		local mapDecodedData = PB.decode(sProtoMsgName, mapGoogleProtobufAny.value)
		if mapDecodedChangeInfo[sProtoMsgName] == nil then
			mapDecodedChangeInfo[sProtoMsgName] = {}
		end
		table.insert(mapDecodedChangeInfo[sProtoMsgName], mapDecodedData)
	end
	return mapDecodedChangeInfo
end
local OpenReceiveByChangeInfo = function(mapChangeInfo, callback, sTip)
	local mapReward = PlayerData.Item:ProcessRewardChangeInfo(mapChangeInfo)
	UTILS.OpenReceiveByReward(mapReward, callback, sTip)
end
local OpenReceiveByDisplayItem = function(tbItem, mapChangeInfo, callback, sTip, nTitleType)
	local mapTrans = PlayerData.Item:ProcessTransChangeInfo(mapChangeInfo)
	local tbReward, tbSpReward = PlayerData.Item:ProcessRewardDisplayItem(tbItem, mapTrans)
	local mapReward = {
		tbReward = tbReward,
		tbSpReward = tbSpReward,
		tbSrc = mapTrans.tbSrc,
		tbDst = mapTrans.tbDst
	}
	UTILS.OpenReceiveByReward(mapReward, callback, sTip, nTitleType)
end
local OpenReceiveByReward = function(mapReward, callback, sTip, nTitleType)
	local bOverflow = PlayerData.State:GetMailOverflow()
	local open_mail = function()
		if bOverflow then
			EventManager.Hit(EventId.OpenMessageBox, {
				nType = AllEnum.MessageBox.Alert,
				sContent = ConfigTable.GetUIText("Mail_Overflow_Tip"),
				callbackConfirm = callback
			})
			PlayerData.State:SetMailOverflow(false)
		elseif callback then
			callback()
		end
	end
	local open_trans = function()
		local tbSrc, tbDst = {}, {}
		local mapOverTrans = PlayerData.Item:GetFragmentsOverflow()
		if mapOverTrans and mapOverTrans.tbSrc and #mapOverTrans.tbSrc > 0 then
			for _, v in ipairs(mapOverTrans.tbSrc) do
				table.insert(tbSrc, v)
			end
			for _, v in ipairs(mapOverTrans.tbDst) do
				table.insert(tbDst, v)
			end
		end
		if mapReward and mapReward.tbSrc and #mapReward.tbSrc > 0 then
			for _, v in ipairs(mapReward.tbSrc) do
				table.insert(tbSrc, v)
			end
			for _, v in ipairs(mapReward.tbDst) do
				table.insert(tbDst, v)
			end
		end
		if 0 < #tbDst and 0 < #tbSrc then
			EventManager.Hit(EventId.OpenPanel, PanelId.ReceiveAutoTrans, tbSrc, tbDst, open_mail)
		else
			open_mail()
		end
	end
	local open_normal = function()
		if mapReward and mapReward.tbReward and #mapReward.tbReward > 0 then
			EventManager.Hit(EventId.OpenPanel, PanelId.ReceivePropsTips, mapReward.tbReward, open_trans, sTip, nTitleType)
		else
			open_trans()
		end
	end
	if mapReward and mapReward.tbSpReward and #mapReward.tbSpReward > 0 then
		EventManager.Hit(EventId.OpenPanel, PanelId.ReceiveSpecialReward, mapReward.tbSpReward, open_normal)
	else
		open_normal()
	end
end
local strLengthConfig = {
	{
		tbRange = {
			{11904, 12255},
			{13312, 19903},
			{19968, 40959}
		},
		sType = "中文",
		nCount = 2
	},
	{
		tbRange = {
			{4352, 4607},
			{12592, 12687},
			{44032, 55215}
		},
		sType = "韩文",
		nCount = 2
	},
	{
		tbRange = {
			{12352, 12543},
			{12784, 12799}
		},
		sType = "日文",
		nCount = 2
	},
	{
		tbRange = {
			{65280, 65519}
		},
		sType = "全角符号",
		nCount = 2
	}
}
local GetParamStrLen = function(sParam)
	sParam = string.gsub(sParam, "</?[^>]+>", "")
	local nLength = 0
	local nIndex = 1
	while true do
		local curByte = string.byte(sParam, nIndex)
		local byteCount = 1
		if 239 < curByte then
			byteCount = 4
		elseif 223 < curByte then
			byteCount = 3
		elseif 128 < curByte then
			byteCount = 2
		elseif curByte == 10 then
			byteCount = 1
		else
			byteCount = 1
		end
		local subStr = string.sub(sParam, nIndex, nIndex + byteCount - 1)
		local charUnicodeNum = UFT8ToUnicode(subStr)
		local bContains = false
		local nAddCount = 1
		for _, v in ipairs(strLengthConfig) do
			local tbRange = v.tbRange
			for _, range in ipairs(tbRange) do
				if charUnicodeNum >= range[1] and charUnicodeNum <= range[2] then
					bContains = true
					nAddCount = v.nCount
					break
				end
			end
			if bContains then
				break
			end
		end
		nLength = nLength + nAddCount
		nIndex = nIndex + byteCount
		if nIndex > #sParam then
			break
		end
	end
	return nLength
end
local ParseByteString = function(sByte)
	return {
		string.byte(sByte, 1, -1)
	}
end
local IsBitSet = function(tbByte, nIndex)
	local nGroup64 = math.ceil(nIndex / 64) - 1
	local nIndexInGroup64 = nIndex - nGroup64 * 64
	local nGroup8 = math.ceil(nIndexInGroup64 / 8) - 1
	local nIndexInGroup8 = nIndexInGroup64 - nGroup8 * 8
	local nByteTableIndex = 8 - nGroup8 + nGroup64 * 8
	if not tbByte[nByteTableIndex] then
		return false
	end
	return 1 << nIndexInGroup8 - 1 & tbByte[nByteTableIndex] > 0
end
local GetBuildAttributeId = function(nGroupId, nLevel)
	return nGroupId * 100000 + nLevel
end
local GetCharacterAttributeId = function(nCharId, nAdvance, nLevel)
	return nCharId * 100000 + nAdvance * 1000 + nLevel
end
local GetDiscAttributeId = function(nGroupId, nPhase, nLevel)
	return nGroupId * 1000 + nPhase * 100 + nLevel
end
local GetDiscExtraAttributeId = function(nGroupId, nStar)
	return nGroupId * 10 + nStar
end
local GetPotentialId = function(nCharId, nIndex)
	return 500000 + nCharId * 100 + nIndex
end
local ParseNoBrokenDesc = function(sDesc)
	if Settings.sCurrentTxtLanguage == AllEnum.Language.EN or Settings.sCurrentTxtLanguage == AllEnum.Language.KR then
		return sDesc
	else
		return "<nobr>" .. sDesc .. "</nobr>"
	end
end
local SubDesc = function(str, nLevel, nCompareLevel, mapLinkParam)
	local ConfigData = require("GameCore.Data.ConfigData")
	local SubDescLink = function(originStr, mapParam)
		if originStr == nil or originStr == "" then
			return ""
		end
		local mapWord = {}
		for word in string.gmatch(originStr, "##.-#%d%d%d%d#") do
			if mapWord[word] == nil then
				local sWordId = string.match(word, "%d%d%d%d")
				local nWordId = tonumber(sWordId)
				if nWordId == nil then
					printError("词条 id 错误:" .. sWordId)
					mapWord[word] = sWordId
				else
					local mapWordData = ConfigTable.GetData("Word", nWordId)
					if mapWordData == nil or mapWordData.Icon == "" then
						printError("该词条 id 找不到数据:" .. sWordId)
						if mapWordData and mapWordData.Icon == "" then
							printError("该词条 id 找不到Icon:" .. sWordId)
						end
						mapWord[word] = string.format("<color=#FF0000>%s</color>", sWordId)
					elseif mapWordData.Type == GameEnum.wordLinkType.Word then
						mapWord[word] = string.format("<color=#%s><link=\"%d\"><u>%s</u>%s</link></color>", mapWordData.Color, mapWordData.Id, mapWordData.Title, mapWordData.TitleIcon)
					elseif mapWordData.Type == GameEnum.wordLinkType.Potential then
						if mapParam == nil or mapParam.nCharId == nil then
							printError("该<潜能>词条 id 找不到角色:" .. sWordId)
							mapWord[word] = string.format("<color=#FF0000>%s</color>", sWordId)
						else
							local nPotentialId = GetPotentialId(mapParam.nCharId, tonumber(mapWordData.Param1))
							local mapItemCfg = ConfigTable.GetData_Item(nPotentialId)
							local sTitle = mapItemCfg and mapItemCfg.Title or mapWordData.Title
							mapWord[word] = string.format("<color=#%s><link=\"%d\"><u>%s</u></link></color>", mapWordData.Color, mapWordData.Id, sTitle)
						end
					end
				end
			end
		end
		for word, finalStr in pairs(mapWord) do
			originStr = string.gsub(originStr, word, finalStr)
		end
		return originStr
	end
	local ParseHitDamageDesc = function(nHitDamageId, nHitDamageLevel)
		local mapDamage = ConfigTable.GetData_HitDamage(nHitDamageId)
		if not mapDamage then
			return string.format("<color=#BD3059>该 hit damage id 找不到数据:%s</color>", nHitDamageId)
		end
		local nPercent = mapDamage.SkillPercentAmend[nHitDamageLevel]
		local nAbs = mapDamage.SkillAbsAmend[nHitDamageLevel]
		if not nPercent or not nAbs then
			return string.format("<color=#BD3059>该技能等级在 HitDamage 表中找不到数据, hit damage id:%d, level:%d</color>", nHitDamageId, nHitDamageLevel)
		end
		nPercent = nPercent * ConfigData.IntFloatPrecision
		nPercent = FormatNum(nPercent)
		nAbs = FormatNum(nAbs)
		local sPercent = nPercent == 0 and "" or tostring(nPercent) .. "%%"
		local sAbs = nAbs == 0 and "" or tostring(nAbs)
		if nPercent ~= 0 and nAbs ~= 0 then
			return sPercent .. "+" .. sAbs
		else
			return sPercent .. sAbs
		end
	end
	local GetValueKey = function(nDataId, nType, nValueLevel)
		local ret = nDataId
		if nType == GameEnum.levelTypeData.Exclusive or nType == GameEnum.levelTypeData.SkillSlot then
			ret = nDataId + nValueLevel * 10
		elseif nType == GameEnum.levelTypeData.OutfitPromote then
		else
			if nType == GameEnum.levelTypeData.OutfifBreak then
			else
			end
		end
		return ret
	end
	local ParseEffectDesc = function(nEffectId, nEffectLevel, nShowType)
		local mapEffectCfgData = ConfigTable.GetData_Effect(nEffectId)
		if mapEffectCfgData == nil then
			return string.format("<color=#BD3059>该EffectId找不到数据:%s</color>", nEffectId)
		end
		local nValueKey = GetValueKey(nEffectId, mapEffectCfgData.levelTypeData, nEffectLevel)
		local mapEffectValueData = ConfigTable.GetData("EffectValue", nValueKey)
		if mapEffectValueData == nil then
			return string.format("<color=#BD3059>该EffectId和等级找不到Value数据:%s，%s</color>", nEffectId, nEffectLevel)
		end
		local sValue = mapEffectValueData.EffectTypeParam1
		local nValue = tonumber(sValue)
		if nValue == nil then
			return string.format("<color=#BD3059>该EffectValueId配置的数据不支持显示:%s</color>", nValueKey)
		end
		nValue = math.abs(nValue)
		if nShowType == 1 then
			nValue = nValue * 100
			return nValue .. "%%"
		end
		return tostring(nValue)
	end
	local ParseOnceDesc = function(nOnceId, nOnceLevel, nShowType)
		local mapCfgData = ConfigTable.GetData("OnceAdditionalAttribute", nOnceId)
		if mapCfgData == nil then
			return string.format("<color=#BD3059>该OnceAdditionalAttributeId找不到数据:%s</color>", nOnceId)
		end
		local nValueKey = GetValueKey(nOnceId, mapCfgData.levelTypeData, nOnceLevel)
		local mapValueData = ConfigTable.GetData("OnceAdditionalAttributeValue", nValueKey)
		if mapValueData == nil then
			return string.format("<color=#BD3059>该OnceAdditionalAttributeId和等级找不到Value数据:%s，%s，%s</color>", nOnceId, nOnceLevel, nValueKey)
		end
		local nValue = mapValueData.Value1 * ConfigData.IntFloatPrecision
		nValue = math.abs(nValue)
		if nShowType == 1 then
			nValue = nValue * 100
			return nValue .. "%%"
		end
		return tostring(nValue)
	end
	local ParseShieldDesc = function(nShieldId, nShieldLevel, nShowType)
		local mapCfgData = ConfigTable.GetData("Shield", nShieldId)
		if mapCfgData == nil then
			return string.format("<color=#BD3059>该ShieldId找不到数据:%s</color>", nShieldId)
		end
		local nValueKey = GetValueKey(nShieldId, mapCfgData.levelTypeData, nShieldLevel)
		local mapValueData = ConfigTable.GetData("ShieldValue", nValueKey)
		if mapValueData == nil then
			return string.format("<color=#BD3059>该ShieldId和等级找不到Value数据:%s，%s</color>", nShieldId, nShieldLevel)
		end
		local nValue = mapValueData.ReferenceScale
		nValue = nValue * ConfigData.IntFloatPrecision
		nValue = math.abs(nValue)
		if nShowType == 1 then
			nValue = nValue * 100
			return nValue .. "%%"
		end
		return tostring(nValue)
	end
	local ParseSriptDesc = function(nSriptId, nSriptLevel, nShowType)
		local mapCfgData = ConfigTable.GetData("ScriptParameter", nSriptId)
		if mapCfgData == nil then
			return string.format("<color=#BD3059>该SriptId找不到数据:%s</color>", nSriptId)
		end
		local nValueKey = GetValueKey(nSriptId, mapCfgData.levelTypeData, nSriptLevel)
		local mapValueData = ConfigTable.GetData("ScriptParameterValue", nValueKey)
		if mapValueData == nil then
			return string.format("<color=#BD3059>该SriptId和等级找不到Value数据:%s，%s</color>", nSriptId, nSriptLevel)
		end
		local nValue = mapValueData.CommonData
		nValue = nValue * ConfigData.IntFloatPrecision
		nValue = math.abs(nValue)
		if nShowType == 1 then
			nValue = nValue * 100
			return nValue .. "%%"
		end
		return tostring(nValue)
	end
	local linkStr = SubDescLink(str, mapLinkParam)
	if nLevel == nil then
		return linkStr
	end
	local mapWord = {}
	for word in string.gmatch(linkStr, "&.-&") do
		local paramStr = string.gsub(word, "&", "")
		print(paramStr)
		local tbParam = string.split(paramStr, ",")
		local sTable = tbParam[1]
		local sKey = tbParam[2]
		local sShowType = tbParam[3]
		local nShowType = tonumber(sShowType)
		if nShowType == nil then
			nShowType = 0
		end
		if sTable == nil or sKey == nil then
			mapWord[word] = string.format("<color=#BD3059>配置错误，无法读取对应表名和ID：%s</color>", word)
		else
			local nKey = tonumber(sKey)
			if nKey == nil then
				mapWord[word] = string.format("<color=#BD3059>表ID配置错误，无法转为number：%s</color>", word)
			elseif sTable == "HitDamage" then
				local subStr = ParseHitDamageDesc(nKey, nLevel)
				if nCompareLevel ~= nil then
					local sCompareStr = ParseHitDamageDesc(nKey, nCompareLevel)
					subStr = string.format("%s<color=#8cac59>(%s↑)</color>", subStr, sCompareStr)
				end
				mapWord[word] = subStr
			elseif sTable == "Effect" then
				local subStr = ParseEffectDesc(nKey, nLevel, nShowType)
				if nCompareLevel ~= nil then
					local sCompareStr = ParseEffectDesc(nKey, nCompareLevel, nShowType)
					subStr = string.format("%s<color=#8cac59>(%s↑)</color>", subStr, sCompareStr)
				end
				mapWord[word] = subStr
			elseif sTable == "Once" then
				local subStr = ParseOnceDesc(nKey, nLevel, nShowType)
				if nCompareLevel ~= nil then
					local sCompareStr = ParseOnceDesc(nKey, nCompareLevel, nShowType)
					subStr = string.format("%s<color=#8cac59>(%s↑)</color>", subStr, sCompareStr)
				end
				mapWord[word] = subStr
			elseif sTable == "Shield" then
				local subStr = ParseShieldDesc(nKey, nLevel, nShowType)
				if nCompareLevel ~= nil then
					local sCompareStr = ParseShieldDesc(nKey, nCompareLevel, nShowType)
					subStr = string.format("%s<color=#8cac59>(%s↑)</color>", subStr, sCompareStr)
				end
				mapWord[word] = subStr
			elseif sTable == "Script" then
				local subStr = ParseSriptDesc(nKey, nLevel, nShowType)
				if nCompareLevel ~= nil then
					local sCompareStr = ParseSriptDesc(nKey, nCompareLevel, nShowType)
					subStr = string.format("%s<color=#8cac59>(%s↑)</color>", subStr, sCompareStr)
				end
				mapWord[word] = subStr
			else
				mapWord[word] = string.format("<color=#BD3059>未支持的表名：%s</color>", word)
			end
		end
	end
	for word, finalStr in pairs(mapWord) do
		linkStr = string.gsub(linkStr, word, finalStr)
	end
	return ParseNoBrokenDesc(linkStr)
end
local ParseDesc = function(mapDescConfig, nCompareLevelType, nCompareLevel, bSimple, nOverrideLevel, mapLinkParam)
	if mapDescConfig == nil then
		printError("解析描述失败!")
		return ""
	end
	local ConfigData = require("GameCore.Data.ConfigData")
	local str = bSimple and mapDescConfig.BriefDesc or mapDescConfig.Desc
	local SubDescLink = function(originStr, mapParam)
		if originStr == nil or originStr == "" then
			return ""
		end
		local mapWord = {}
		for word in string.gmatch(originStr, "##.-#%d%d%d%d#") do
			if mapWord[word] == nil then
				local sWordId = string.match(word, "%d%d%d%d")
				local nWordId = tonumber(sWordId)
				if nWordId == nil then
					printError("词条 id 错误:" .. sWordId)
					mapWord[word] = sWordId
				else
					local mapWordData = ConfigTable.GetData("Word", nWordId)
					if mapWordData == nil or mapWordData.Icon == "" then
						printError("该词条 id 找不到数据:" .. sWordId)
						if mapWordData and mapWordData.Icon == "" then
							printError("该词条 id 找不到Icon:" .. sWordId)
						end
						mapWord[word] = string.format("<color=#FF0000>%s</color>", sWordId)
					elseif mapWordData.Type == GameEnum.wordLinkType.Word then
						mapWord[word] = string.format("<color=#%s><link=\"%d\"><u>%s</u>%s</link></color>", mapWordData.Color, mapWordData.Id, mapWordData.Title, mapWordData.TitleIcon)
					elseif mapWordData.Type == GameEnum.wordLinkType.Potential then
						if mapParam == nil or mapParam.nCharId == nil then
							printError("该<潜能>词条 id 找不到角色:" .. sWordId)
							mapWord[word] = string.format("<color=#FF0000>%s</color>", sWordId)
						else
							local nPotentialId = GetPotentialId(mapParam.nCharId, tonumber(mapWordData.Param1))
							local mapItemCfg = ConfigTable.GetData_Item(nPotentialId)
							local sTitle = mapItemCfg and mapItemCfg.Title or mapWordData.Title
							mapWord[word] = string.format("<color=#%s><link=\"%d\"><u>%s</u></link></color>", mapWordData.Color, mapWordData.Id, sTitle)
						end
					end
				end
			end
		end
		for word, finalStr in pairs(mapWord) do
			originStr = string.gsub(originStr, word, finalStr)
		end
		return originStr
	end
	local linkStr = SubDescLink(str, mapLinkParam)
	local ParseHitDamageDesc = function(nHitDamageId, nHitDamageLevel)
		local sDesc = ""
		local mapDamage = ConfigTable.GetData_HitDamage(nHitDamageId)
		if not mapDamage then
			sDesc = string.format("<color=#BD3059>该 hit damage id 找不到数据:%s</color>", nHitDamageId)
			return sDesc
		end
		local levelType = mapDamage.levelTypeData
		if levelType == GameEnum.levelTypeData.BreakCount then
			if nHitDamageLevel < 1 then
				nHitDamageLevel = 1 or nHitDamageLevel
			end
		elseif levelType == GameEnum.levelTypeData.None then
			nHitDamageLevel = 1
		end
		local nPercent = mapDamage.SkillPercentAmend[nHitDamageLevel]
		local nAbs = mapDamage.SkillAbsAmend[nHitDamageLevel]
		if not nPercent or not nAbs then
			sDesc = string.format("<color=#BD3059>该技能等级在 HitDamage 表中找不到数据, hit damage id:%d, level:%d</color>", nHitDamageId, nHitDamageLevel)
			return sDesc
		end
		nPercent = nPercent * ConfigData.IntFloatPrecision
		nPercent = FormatNum(nPercent)
		nAbs = FormatNum(nAbs)
		local sPercent = nPercent == 0 and "" or tostring(nPercent) .. "%%"
		local sAbs = nAbs == 0 and "" or tostring(nAbs)
		if nPercent ~= 0 and nAbs ~= 0 then
			sDesc = sPercent .. "+" .. sAbs
		else
			sDesc = sPercent .. sAbs
		end
		return sDesc
	end
	local FormatValueShow = function(sValue, sShowType, sEnumType)
		if sShowType == "Text" then
			return sValue
		elseif sShowType == "Enum" then
			sEnumType = tostring(sEnumType)
			if sEnumType ~= nil then
				local tbEnum = CacheTable.GetData("_EnumDesc", sEnumType)
				if tbEnum ~= nil then
					local nEnumValue = tonumber(sValue)
					if nEnumValue ~= nil then
						if tbEnum[nEnumValue] then
							return ConfigTable.GetUIText(tbEnum[nEnumValue])
						else
							printError(sEnumType .. "枚举未找到值:" .. nEnumValue)
							return nil
						end
					else
						printError("枚举值填写错误:" .. sValue)
						return nil
					end
				else
					printError("枚举类型填写错误:" .. sEnumType)
					return nil
				end
			else
				printError("枚举类型未填写")
				return nil
			end
		else
			local nValue = tonumber(sValue)
			if nValue ~= nil then
				local isIntFloat = sShowType == "10K" or sShowType == "10KPct" or sShowType == "10KHdPct"
				local isPercent = sShowType == "Pct" or sShowType == "HdPct" or sShowType == "10KPct" or sShowType == "10KHdPct"
				local multiHundred = sShowType == "HdPct" or sShowType == "10KHdPct"
				nValue = isIntFloat and nValue * ConfigData.IntFloatPrecision or nValue
				nValue = multiHundred and nValue * 100 or nValue
				nValue = math.abs(nValue)
				nValue = clearFloat(nValue)
				local integer, decimal = math.modf(nValue)
				sValue = decimal < 0.01 and tostring(integer) or tostring(nValue)
				sValue = isPercent and sValue .. "%%" or sValue
				return sValue
			else
				return nil
			end
		end
	end
	local ParseLevelUpDesc = function(sTable, nId, nLevel, sParameter, sShowType, sEnumType)
		local GetValueKey = function(nDataId, nType, nValueLevel)
			return nDataId + nValueLevel * 10
		end
		local sDesc, sErrorInfo
		local mapCfgData = DataTable[sTable]
		if mapCfgData ~= nil then
			local sValueTable = sTable .. "Value"
			local mapValueCfgData = DataTable[sValueTable]
			if mapValueCfgData ~= nil then
				local mapData = mapCfgData[nId]
				if mapData ~= nil then
					local nValueId = GetValueKey(nId, mapData.levelTypeData, nLevel)
					local mapValueData = mapValueCfgData[nValueId]
					if mapValueData ~= nil then
						local sValue = mapValueData[sParameter]
						if sValue ~= nil then
							sDesc = FormatValueShow(sValue, sShowType, sEnumType)
							if sDesc == nil then
								sErrorInfo = string.format("<color=#BD3059>%s表中该ValueId配置的数据解析失败:%s</color>", sValueTable, nValueId)
							end
						else
							sErrorInfo = string.format("<color=#BD3059>%s表中没有该字段:%s</color>", sValueTable, sParameter)
						end
					else
						sErrorInfo = string.format("<color=#BD3059>%s表中该Id找不到数据:%s</color>", sValueTable, nValueId)
					end
				else
					sErrorInfo = string.format("<color=#BD3059>%s表中该Id找不到数据:%s</color>", sTable, nId)
				end
			else
				sErrorInfo = string.format("<color=#BD3059>找不到该配置表:%s</color>", sValueTable)
			end
		else
			sErrorInfo = string.format("<color=#BD3059>找不到该配置表:%s</color>", sTable)
		end
		if sErrorInfo ~= nil and sErrorInfo ~= "" then
			printError(sErrorInfo)
			sDesc = sErrorInfo
		end
		if sDesc == nil then
			printError("描述解析失败")
			sDesc = ""
		end
		return sDesc
	end
	local ParseNoLevelUpDesc = function(sTable, nId, sParameter, sShowType, sEnumType)
		local sDesc, sErrorInfo
		local mapCfgData = DataTable[sTable]
		if mapCfgData ~= nil then
			local mapData = mapCfgData[nId]
			if mapData ~= nil then
				local sValue = mapData[sParameter]
				if sValue ~= nil then
					sDesc = FormatValueShow(sValue, sShowType, sEnumType)
					if sDesc == nil then
						sErrorInfo = string.format("<color=#BD3059>%s表中该Id配置的数据解析失败:%s</color>", sTable, nId)
					end
				else
					sErrorInfo = string.format("<color=#BD3059>%s表中没有该字段:%s</color>", sTable, sParameter)
				end
			else
				sErrorInfo = string.format("<color=#BD3059>%s表中该Id找不到数据:%s</color>", sTable, nId)
			end
		else
			sErrorInfo = string.format("<color=#BD3059>找不到该配置表:%s</color>", sTable)
		end
		if sErrorInfo ~= nil and sErrorInfo ~= "" then
			printError(sErrorInfo)
			sDesc = sErrorInfo
		end
		if sDesc == nil then
			printError("描述解析失败")
			sDesc = ""
		end
		return sDesc
	end
	local mapWord = {}
	for word in string.gmatch(linkStr, "&.-&") do
		local sParameterKey = string.gsub(word, "&", "")
		local paramStr = mapDescConfig[sParameterKey]
		local tbParam = string.split(paramStr, ",")
		local sTable = tbParam[1]
		local sParseType = tbParam[2]
		local sKey = tbParam[3]
		local sParameter = tbParam[4]
		local sShowType = tbParam[5]
		local sEnumType = tbParam[6]
		if sTable == nil or sKey == nil then
			mapWord[word] = string.format("<color=#BD3059>配置错误，无法读取对应表名和ID：%s</color>", word)
		else
			local mapCfgData = DataTable[sTable]
			if mapCfgData ~= nil then
				local nKey = tonumber(sKey)
				if nKey == nil then
					mapWord[word] = string.format("<color=#BD3059>表ID配置错误，无法转为number：%s</color>", word)
				else
					local mapData = mapCfgData[nKey]
					if mapData ~= nil then
						local nLevel = 1
						nOverrideLevel = tonumber(nOverrideLevel)
						if mapData.levelTypeData == GameEnum.levelTypeData.Exclusive then
							nLevel = UTILS.QueryLevelInfo(mapData.LevelData, mapData.levelTypeData, mapData.LevelData)
						else
							local nCharId = tonumber(string.sub(sKey, 1, 3))
							nLevel = UTILS.QueryLevelInfo(nCharId, mapData.levelTypeData, mapData.LevelData, mapData.MainOrSupport)
						end
						if (mapData.levelTypeData == GameEnum.levelTypeData.Exclusive or mapData.levelTypeData == GameEnum.levelTypeData.Note) and nOverrideLevel ~= nil then
							nLevel = nOverrideLevel
						end
						if sParseType == "DamageNum" and sTable == "HitDamage" then
							local subStr = ParseHitDamageDesc(nKey, nLevel)
							if nCompareLevelType ~= nil and mapData.levelTypeData == nCompareLevelType and nCompareLevel ~= nil then
								local sCompareStr = ParseHitDamageDesc(nKey, nCompareLevel)
								if subStr ~= sCompareStr then
									subStr = string.format("%s<color=#8cac59>(%s↑)</color>", subStr, sCompareStr)
								end
							end
							mapWord[word] = subStr
						elseif sParseType == "LevelUp" then
							local subStr = ParseLevelUpDesc(sTable, nKey, nLevel, sParameter, sShowType, sEnumType)
							if nCompareLevelType ~= nil and mapData.levelTypeData == nCompareLevelType and nCompareLevel ~= nil then
								local sCompareStr = ParseLevelUpDesc(sTable, nKey, nCompareLevel, sParameter, sShowType, sEnumType)
								if subStr ~= sCompareStr then
									subStr = string.format("%s<color=#8cac59>(%s↑)</color>", subStr, sCompareStr)
								end
							end
							mapWord[word] = subStr
						elseif sParseType == "NoLevel" then
							mapWord[word] = ParseNoLevelUpDesc(sTable, nKey, sParameter, sShowType, sEnumType)
						end
					else
						mapWord[word] = string.format("<color=#BD3059>%s表中该Id找不到数据:%s</color>", sTable, nKey)
					end
				end
			else
				mapWord[word] = string.format("<color=#BD3059>找不到该配置表:%s</color>", sTable)
			end
		end
	end
	for word, finalStr in pairs(mapWord) do
		linkStr = string.gsub(linkStr, word, finalStr)
	end
	return ParseNoBrokenDesc(linkStr)
end
local ParseDiscDesc = function(originStr, mapSkill, mapSkillNext, nLayer, mapLinkParam)
	originStr = UTILS.SubDesc(originStr, nil, nil, mapLinkParam)
	local mapWord = {}
	for word in string.gmatch(originStr, "{[0-9]*}") do
		if mapWord[word] == nil then
			local nCurLayer = nLayer
			local sWordId = string.sub(word, 2, #word - 1)
			local nWordId = tonumber(sWordId)
			if nWordId == nil then
				printError("wordId error:" .. sWordId)
				mapWord[word] = sWordId
			else
				local fieldName = "Param" .. nWordId
				if nCurLayer then
					local nMaxStringCount = #mapSkill[fieldName]
					if nCurLayer > nMaxStringCount then
						nCurLayer = nMaxStringCount
					end
					for k, v in ipairs(mapSkill[fieldName]) do
						if nCurLayer == k then
							mapWord[word] = v
						end
					end
				elseif mapSkillNext and mapSkill[fieldName] ~= mapSkillNext[fieldName] then
					mapWord[word] = mapSkill[fieldName] .. "<color=#8cac59>(" .. mapSkillNext[fieldName] .. "↑)</color>"
				else
					mapWord[word] = mapSkill[fieldName]
				end
				if not mapWord[word] then
					mapWord[word] = "{未找到参数配置}"
				end
				mapWord[word] = string.gsub(mapWord[word], "%%", "%%%%")
			end
		end
	end
	for word, finalStr in pairs(mapWord) do
		originStr = string.gsub(originStr, word, finalStr)
	end
	return ParseNoBrokenDesc(originStr)
end
local ParseParamDesc = function(originStr, mapCfg, mapCfgNext, mapLinkParam, sColor)
	originStr = UTILS.SubDesc(originStr, nil, nil, mapLinkParam)
	local mapWord = {}
	for word in string.gmatch(originStr, "{[0-9]*}") do
		if mapWord[word] == nil then
			local sWordId = string.sub(word, 2, #word - 1)
			local nWordId = tonumber(sWordId)
			if nWordId == nil then
				printError("wordId error:" .. sWordId)
				mapWord[word] = sWordId
			else
				local fieldName = "Param" .. nWordId
				if mapCfgNext and mapCfg[fieldName] ~= mapCfgNext[fieldName] then
					mapWord[word] = mapCfg[fieldName] .. "<color=#8cac59>(" .. mapCfgNext[fieldName] .. "↑)</color>"
				elseif sColor then
					mapWord[word] = "<color=" .. sColor .. ">" .. mapCfg[fieldName] .. "</color>"
				else
					mapWord[word] = mapCfg[fieldName]
				end
				if not mapWord[word] then
					mapWord[word] = "{未找到参数配置}"
				end
				mapWord[word] = string.gsub(mapWord[word], "%%", "%%%%")
			end
		end
	end
	for word, finalStr in pairs(mapWord) do
		originStr = string.gsub(originStr, word, finalStr)
	end
	return ParseNoBrokenDesc(originStr)
end
local ParseRewardItemCount = function(tbReward)
	if nil == tbReward then
		return -1
	end
	if #tbReward < 3 then
		printError("物品数量配置错误，应有至少3个参数")
		return -1
	end
	if #tbReward == 3 then
		return tbReward[2]
	elseif 3 < #tbReward then
		local countTxt = tbReward[2] .. "~" .. tbReward[3]
		return countTxt
	end
	return -1
end
local QueryLevelInfo = function(nId, nType, nParam1, nParam2)
	local ret
	ret = PlayerData.StarTower:QueryLevelInfo(nId, nType, nParam1, nParam2)
	if ret == nil then
		ret = PlayerData.Char:QueryLevelInfo(nId, nType, nParam1, nParam2)
	end
	return ret
end
local ParseLevelQuestTargetDesc = function(originStr, mapTarget)
	local mapSkillType = {
		[GameEnum.LevelQuestTargetType.CastSkill] = true,
		[GameEnum.LevelQuestTargetType.CastSkillEnd] = true
	}
	local mapWord = {}
	for word in string.gmatch(originStr, "{Param" .. "[0-9]*}") do
		if mapWord[word] == nil then
			local fieldName = string.sub(word, 2, #word - 1)
			local sParam = mapTarget[fieldName]
			if mapTarget.QuestType == GameEnum.LevelQuestTargetType.KillMonster and fieldName == "Param2" then
				local nMonsterId = tonumber(sParam)
				local sMonsterName = sParam
				if nMonsterId then
					local monsterData = ConfigTable.GetData("Monster", nMonsterId)
					if monsterData then
						local monsterSkin = ConfigTable.GetData("MonsterSkin", monsterData.FAId)
						if monsterSkin then
							local monsterManual = ConfigTable.GetData("MonsterManual", monsterSkin.MonsterManual)
							if monsterManual then
								sMonsterName = monsterManual.Name
							end
						end
					end
				end
				sParam = sMonsterName
			elseif mapSkillType[mapTarget.QuestType] and fieldName == "Param2" then
				local nSkillId = tonumber(sParam)
				local sSkillName = sParam
				if nSkillId then
					local mapCfg = ConfigTable.GetData("Skill", nSkillId)
					if mapCfg then
						sSkillName = mapCfg.Title
					end
				end
				sParam = sSkillName
			end
			mapWord[word] = "<color=#2be1f1>" .. sParam .. "</color>"
		end
		if not mapWord[word] then
			mapWord[word] = "{未找到参数配置}"
		end
		mapWord[word] = string.gsub(mapWord[word], "%%", "%%%%")
	end
	for word, finalStr in pairs(mapWord) do
		originStr = string.gsub(originStr, word, finalStr)
	end
	return originStr
end
local GetLevelQuestTargetProcess = function(mapTarget, nCur)
	local mapSkipProcess = {
		[GameEnum.LevelQuestTargetType.ReceiveTriggerOpId] = true,
		[GameEnum.LevelQuestTargetType.Null] = true,
		[GameEnum.LevelQuestTargetType.RecoverEnergy] = true,
		[GameEnum.LevelQuestTargetType.KillAllMonster] = true
	}
	if mapSkipProcess[mapTarget.QuestType] then
		return ""
	end
	if nCur then
		return "<color=#2be1f1>(" .. nCur .. "/" .. mapTarget.Param1 .. ")</color>"
	else
		return "<color=#2be1f1>(0/" .. mapTarget.Param1 .. ")</color>"
	end
end
local GetBezierPointByT = function(beginPos, handlePos, endPos, deltaTime)
	local pow = (1 - deltaTime) ^ 2
	local x = pow * beginPos.x + 2 * deltaTime * (1 - deltaTime) * handlePos.x + deltaTime * deltaTime * endPos.x
	local y = pow * beginPos.y + 2 * deltaTime * (1 - deltaTime) * handlePos.y + deltaTime * deltaTime * endPos.y
	local z = pow * beginPos.z + 2 * deltaTime * (1 - deltaTime) * handlePos.z + deltaTime * deltaTime * endPos.z
	return x, y, z
end
local _
_, Red_Unable = ColorUtility.TryParseHtmlString("#BD3059")
_, Blue_Normal = ColorUtility.TryParseHtmlString("#264278")
_, Blue_Dark = ColorUtility.TryParseHtmlString("#3B62AE")
_, White_Normal = ColorUtility.TryParseHtmlString("#FAFAFA")
_, White_Dark = ColorUtility.TryParseHtmlString("#2d4257")
_, Gray_Dark = ColorUtility.TryParseHtmlString("#505c67")
local AddEffect = function(nCharId, nEffectId, nLevel, nUseCount)
	if nUseCount == nil then
		nUseCount = 0
	end
	local mapEftCfgData = ConfigTable.GetData_Effect(nEffectId)
	if mapEftCfgData == nil then
		printError("Effect Id missing" .. nEffectId)
		return nil
	end
	local nEffectValueId = nEffectId
	if mapEftCfgData.levelTypeData ~= GameEnum.levelTypeData.None then
		nEffectValueId = nEffectValueId + nLevel * 10
	end
	local mapEftValueData = ConfigTable.GetData("EffectValue", nEffectValueId)
	if mapEftValueData == nil then
		printError("EffectValue Id missing" .. nEffectValueId)
		return nil
	end
	local nEftRemainTimes = -1
	if mapEftValueData.TakeEffectLimit ~= 0 then
		nEftRemainTimes = mapEftValueData.TakeEffectLimit - nUseCount
		if nEftRemainTimes <= 0 then
			printLog("效果次数已用完:" .. nEffectId)
			return nil
		end
	end
	local nEffectUid = safe_call_cs_func(CS.AdventureModuleHelper.SetActorEffect, nCharId, nEffectId, nEftRemainTimes, 0)
	return nEffectUid
end
local AddFateCardEft = function(nCharId, nEffectId, nRemainCount)
	if nRemainCount == 0 then
		printLog("效果次数已用完:" .. nEffectId)
		return nil
	end
	local nEffectUid = safe_call_cs_func(CS.AdventureModuleHelper.SetActorEffect, nCharId, nEffectId, nRemainCount, 0)
	return nEffectUid
end
local AddBuildEffect = function(mapCharEffect, mapDiscEffect, mapNoteEffect)
	local retCharEffect = {}
	local retDiscEffect = {}
	local retNoteEffect = {}
	for nCharId, mapEffect in pairs(mapCharEffect) do
		if mapEffect[AllEnum.EffectType.Affinity] ~= nil then
			for _, nEffectId in ipairs(mapEffect[AllEnum.EffectType.Affinity]) do
				if retCharEffect[AllEnum.EffectType.Affinity] == nil then
					retCharEffect[AllEnum.EffectType.Affinity] = {}
				end
				if retCharEffect[AllEnum.EffectType.Affinity][nEffectId] == nil then
					retCharEffect[AllEnum.EffectType.Affinity][nEffectId] = {}
				end
				local nEftUid = UTILS.AddEffect(nCharId, nEffectId, 0, 0)
				table.insert(retCharEffect[AllEnum.EffectType.Affinity][nEffectId], nEftUid)
			end
		end
		if mapEffect[AllEnum.EffectType.Talent] ~= nil then
			for _, nEffectId in ipairs(mapEffect[AllEnum.EffectType.Talent]) do
				if retCharEffect[AllEnum.EffectType.Talent] == nil then
					retCharEffect[AllEnum.EffectType.Talent] = {}
				end
				if retCharEffect[AllEnum.EffectType.Talent][nEffectId] == nil then
					retCharEffect[AllEnum.EffectType.Talent][nEffectId] = {}
				end
				local nEftUid = UTILS.AddEffect(nCharId, nEffectId, 0, 0)
				table.insert(retCharEffect[AllEnum.EffectType.Talent][nEffectId], nEftUid)
			end
		end
		if mapEffect[AllEnum.EffectType.Potential] ~= nil then
			for nPotentialId, tbPotentialData in pairs(mapEffect[AllEnum.EffectType.Potential]) do
				for _, nEffectId in ipairs(tbPotentialData[1]) do
					if retCharEffect[AllEnum.EffectType.Potential] == nil then
						retCharEffect[AllEnum.EffectType.Potential] = {}
					end
					if retCharEffect[AllEnum.EffectType.Potential][nEffectId] == nil then
						retCharEffect[AllEnum.EffectType.Potential][nEffectId] = {}
					end
					local nEftUid = UTILS.AddEffect(nCharId, nEffectId, tbPotentialData[2], 0)
					table.insert(retCharEffect[AllEnum.EffectType.Potential][nEffectId], nEftUid)
				end
			end
		end
		if mapEffect[AllEnum.EffectType.Equipment] ~= nil then
			for _, nEffectId in ipairs(mapEffect[AllEnum.EffectType.Equipment]) do
				if retCharEffect[AllEnum.EffectType.Equipment] == nil then
					retCharEffect[AllEnum.EffectType.Equipment] = {}
				end
				if retCharEffect[AllEnum.EffectType.Equipment][nEffectId] == nil then
					retCharEffect[AllEnum.EffectType.Equipment][nEffectId] = {}
				end
				local nEftUid = UTILS.AddEffect(nCharId, nEffectId, 0, 0)
				table.insert(retCharEffect[AllEnum.EffectType.Equipment][nEffectId], nEftUid)
			end
		end
		if mapDiscEffect ~= nil then
			for nDiscTid, tbDiscEffectId in pairs(mapDiscEffect) do
				if retDiscEffect[nDiscTid] == nil then
					retDiscEffect[nDiscTid] = {}
				end
				for _, mapEft in ipairs(tbDiscEffectId) do
					if retDiscEffect[nDiscTid][mapEft[1]] == nil then
						retDiscEffect[nDiscTid][mapEft[1]] = {}
					end
					local nEftUid = UTILS.AddEffect(nCharId, mapEft[1], mapEft[2], 0)
					table.insert(retDiscEffect[nDiscTid][mapEft[1]], nEftUid)
				end
			end
		end
		if mapNoteEffect ~= nil then
			for nNoteId, tbNoteEffectId in pairs(mapNoteEffect) do
				if retNoteEffect[nNoteId] == nil then
					retNoteEffect[nNoteId] = {}
				end
				for _, mapEft in ipairs(tbNoteEffectId) do
					if retNoteEffect[nNoteId][mapEft[1]] == nil then
						retNoteEffect[nNoteId][mapEft[1]] = {}
					end
					local nEftUid = UTILS.AddEffect(nCharId, mapEft[1], mapEft[2], 0)
					table.insert(retNoteEffect[nNoteId][mapEft[1]], nEftUid)
				end
			end
		end
	end
	return retCharEffect, retDiscEffect, retNoteEffect
end
local RemoveEffect = function(nEftUid, nCharId)
	safe_call_cs_func(CS.AdventureModuleHelper.RemoveActorEffect, nCharId, nEftUid)
end
local GetBattleSamples = function()
	local lstBattleSamples = CS.AdventureModuleHelper.GetBattleSamples()
	local tbSamples = {}
	if lstBattleSamples ~= nil then
		local nCount = lstBattleSamples.Count - 1
		local csList2Table = function(list)
			if list == nil then
				return {}
			end
			local nLstCount = list.Count - 1
			local ret = {}
			for i = 0, nLstCount do
				table.insert(ret, list[i])
			end
			return ret
		end
		for i = 0, nCount do
			local mapSample = {}
			mapSample.FromSrcAtk = lstBattleSamples[i].fromSrcAtk
			mapSample.FromPerkIntensityRatio = lstBattleSamples[i].fromPerkIntensityRatio
			mapSample.FromSlotDmgRatio = lstBattleSamples[i].fromSlotDmgRatio
			mapSample.FromEE = lstBattleSamples[i].fromEE
			mapSample.FromGenDmgRatio = lstBattleSamples[i].fromGenDmgRatio
			mapSample.FromDmgPlus = lstBattleSamples[i].fromDmgPlus
			mapSample.FromCritRatio = lstBattleSamples[i].fromCritRatio
			mapSample.FromFinalDmgRatio = lstBattleSamples[i].fromFinalDmgRatio
			mapSample.FromFinalDmgPlus = lstBattleSamples[i].fromFinalDmgPlus
			mapSample.ToErAmend = lstBattleSamples[i].toErAmend
			mapSample.ToDefAmend = lstBattleSamples[i].toDefAmend
			mapSample.ToRcdSlotDmgRatio = lstBattleSamples[i].toRcdSlotDmgRatio
			mapSample.ToEERCD = lstBattleSamples[i].toEERCD
			mapSample.ToGenDmgRcdRatio = lstBattleSamples[i].toGenDmgRcdRatio
			mapSample.ToDmgPlusRcd = lstBattleSamples[i].toDmgPlusRcd
			mapSample.Dmg = lstBattleSamples[i].dmg
			mapSample.CritRate = lstBattleSamples[i].critRate
			mapSample.Hp = lstBattleSamples[i].maxHP
			mapSample.Log = {}
			table.insert(tbSamples, mapSample)
		end
	end
	return tbSamples
end
local GetCharDamageResult = function(tbCharId)
	local tbResult = {}
	for i = 1, #tbCharId do
		local nCharId = tbCharId[i]
		local nDamage = CS.AdventureModuleHelper.GetCharacterDamage(nCharId, false)
		local actorInfo = {}
		actorInfo.nCharId = nCharId
		actorInfo.nDamage = nDamage
		table.insert(tbResult, actorInfo)
	end
	return tbResult
end
local ClickItemGridWithTips = function(nTid, transform, bOnlyItemTips, bShowDepot, bShowJumpto, nHasCount)
	local mapItemCfgData = ConfigTable.GetData_Item(nTid)
	if mapItemCfgData == nil then
		return
	end
	if mapItemCfgData.Type == GameEnum.itemType.Disc then
		EventManager.Hit(EventId.OpenPanel, PanelId.DiscSample, nTid)
		return
	elseif mapItemCfgData.Type == GameEnum.itemType.Char then
		EventManager.Hit(EventId.OpenPanel, PanelId.CharBgTrialPanel, PanelId.CharInfoTrial, nTid, {nTid}, true)
		return
	end
	if bOnlyItemTips then
		local mapData = {
			nTid = nTid,
			bShowDepot = bShowDepot,
			bShowJumpto = bShowJumpto,
			nHasCount = nHasCount
		}
		EventManager.Hit(EventId.OpenPanel, PanelId.ItemTips, transform, mapData)
	else
		local mapData = {
			nTid = nTid,
			bShowDepot = bShowDepot,
			bShowJumpto = bShowJumpto,
			nHasCount = nHasCount
		}
		EventManager.Hit(EventId.OpenPanel, PanelId.ItemTips, transform, mapData)
	end
end
local CheckIsTipsPanel = function(nPanelId)
	local tbAllTipsPanelId = {
		PanelId.ItemTips,
		PanelId.PerkTips,
		PanelId.SkillTips,
		PanelId.BtnTips,
		PanelId.MonsterTips,
		PanelId.EquipmentTips,
		PanelId.DiscSkillTips
	}
	return table.indexof(tbAllTipsPanelId, nPanelId) > 0
end
local ClickWordLink = function(link, sWordId, mapLinkParam)
	local nWordId = tonumber(sWordId)
	local mapWordData = ConfigTable.GetData("Word", nWordId)
	if mapWordData == nil then
		if sWordId == nil then
			printError("sWordId为空")
			return
		end
		printError("wordId error:" .. sWordId)
		return
	end
	if mapWordData.Type == GameEnum.wordLinkType.Word then
		local mapData = {
			nPerkId = 0,
			nCount = 0,
			bWordTip = true,
			sWordId = sWordId
		}
		EventManager.Hit(EventId.OpenPanel, PanelId.PerkTips, link, mapData)
	elseif mapWordData.Type == GameEnum.wordLinkType.Potential then
		if mapLinkParam == nil or mapLinkParam.nCharId == nil then
			printError("该<潜能>词条 id 找不到角色:" .. sWordId)
			return
		else
			local nPotentialId = GetPotentialId(mapLinkParam.nCharId, tonumber(mapWordData.Param1))
			EventManager.Hit(EventId.OpenPanel, PanelId.PotentialDetail, nPotentialId, mapLinkParam.nLevel, mapLinkParam.nAddLv)
		end
	end
end
local build_priority = function(selected, default_priority)
	local priority = {}
	local selected_map = {}
	for _, field in ipairs(selected) do
		table.insert(priority, field)
		selected_map[field] = true
	end
	for _, field in ipairs(default_priority) do
		if not selected_map[field] then
			table.insert(priority, field)
		end
	end
	return priority
end
local compare_roles = function(a, b, sort_priority, bOrder)
	for i, field in ipairs(sort_priority) do
		local va, vb = a[field], b[field]
		if va ~= nil and vb ~= nil and va ~= vb then
			if field == "Rare" or field == "nRarity" then
				if i == 1 and bOrder then
					return va > vb
				else
					return va < vb
				end
			elseif i == 1 and bOrder or field == "EET" or field == "nEET" then
				return va < vb
			else
				return va > vb
			end
		end
	end
	return false
end
local SortByPriority = function(items, selected_fields, default_priority, bOrder)
	local sort_priority = build_priority(selected_fields, default_priority)
	table.sort(items, function(a, b)
		return compare_roles(a, b, sort_priority, bOrder)
	end)
end
local GetDayRefreshTimeOffset = function()
	local nNewDayTime = ConfigTable.GetConfigNumber("DailyRefreshOffsetHour") or 5
	if 24 < nNewDayTime then
		nNewDayTime = nNewDayTime % 24
	end
	return nNewDayTime
end
local SDK_Logout = function()
	local SDKManager = CS.SDKManager.Instance
	if SDKManager:IsSDKInit() ~= true then
		return
	end
	SDKManager:SwitchAccount()
end
local SDK_ShowAgreement = function()
	local SDKManager = CS.SDKManager.Instance
	if SDKManager:IsSDKInit() ~= true then
		return
	end
	local agreements = {}
	local sChannel = NovaAPI.GetClientChannel()
	if sChannel == "CN_Bilibili" or sChannel == "CN" or sChannel == "KOL" then
		table.insert(agreements, "user_agreement")
		table.insert(agreements, "privacy_agreement")
		table.insert(agreements, "child_privacy_agreement")
	else
		table.insert(agreements, "user_agreement")
		table.insert(agreements, "privacy_agreement")
		table.insert(agreements, "minors_shop_agreement")
	end
	SDKManager.SDK:ShowAgreement(agreements)
end
local ServerChannel_CN = {
	[1] = "cn_android_official",
	[2] = "cn_ios_official",
	[4] = "cn_android_bilibili",
	[8] = "cn_harmony_official",
	[16] = "cn_pc_official",
	[32] = "cn_pc_bilibili"
}
local ServerChannel_JP = {
	[1] = "jp_android_official",
	[2] = "jp_ios_official",
	[4] = "jp_android_onestore",
	[8] = "jp_pc_official"
}
local ServerChannel_US = {
	[1] = "us_android_official",
	[2] = "us_ios_official",
	[4] = "us_android_onestore",
	[8] = "us_pc_official"
}
local ServerChannel_KR = {
	[1] = "kr_android_official",
	[2] = "kr_ios_official",
	[4] = "kr_android_onestore",
	[8] = "kr_pc_official"
}
local ServerChannel_TW = {
	[1] = "tw_android_official",
	[2] = "tw_ios_official",
	[4] = "tw_android_onestore",
	[8] = "tw_pc_official"
}
local GetChannelConfigList = function()
	local resultList
	local clientPublishRegion = CS.ClientConfig.ClientPublishRegion
	if clientPublishRegion == CS.ClientPublishRegion.CN then
		resultList = ServerChannel_CN
	elseif clientPublishRegion == CS.ClientPublishRegion.JP then
		resultList = ServerChannel_JP
	elseif clientPublishRegion == CS.ClientPublishRegion.US then
		resultList = ServerChannel_US
	elseif clientPublishRegion == CS.ClientPublishRegion.KR then
		resultList = ServerChannel_KR
	else
		if clientPublishRegion == CS.ClientPublishRegion.TW then
			resultList = ServerChannel_TW
		else
		end
	end
	return resultList
end
local CheckChannel = function(channel)
	local fullChannnel = CS.ClientConfig.FullClientPublishChannelName
	local channelList = GetChannelConfigList()
	if channelList == nil then
		return false
	end
	local strChannel = channelList[channel]
	if strChannel == nil then
		return false
	end
	local tbClient = string.split(fullChannnel, "_")
	local tbServer = string.split(strChannel, "_")
	if tbClient[2] == nil or tbServer[2] == nil or tbClient[2] ~= tbServer[2] then
		return false
	end
	if tbClient[3] == "taptap" then
		tbClient[3] = "official"
	end
	if string.match(tbClient[3], "test") then
		tbClient[3] = "official"
	end
	if tbClient[3] == nil or tbServer[3] == nil or tbClient[3] ~= tbServer[3] then
		return false
	end
	return true
end
local CheckChannelList = function(channelList)
	local sCurClientPublishRegion = CS.ClientConfig.ClientPublishRegion
	if sCurClientPublishRegion == CS.ClientPublishRegion.Other then
		return true
	end
	for i = 0, channelList.Count - 1 do
		if CheckChannel(channelList[i]) then
			return true
		end
	end
	return false
end
local CheckChannelList_Notice = function(channelList)
	local sCurClientPublishRegion = CS.ClientConfig.ClientPublishRegion
	if sCurClientPublishRegion == CS.ClientPublishRegion.Other then
		return true
	end
	for _, channel in ipairs(channelList) do
		if CheckChannel(channel) then
			return true
		end
	end
	return false
end
local VersionCompare = function(v1, v2, num)
	local v1List = string.split(v1, ".")
	local v2List = string.split(v2, ".")
	for i = 1, num do
		if i > #v1List or i > #v2List then
			if #v1List < #v2List then
				return -1
			elseif #v1List > #v2List then
				return 1
			else
				return 0
			end
		end
		local v1_num = tonumber(v1List[i]) or 0
		local v2_num = tonumber(v2List[i]) or 0
		if v1_num > v2_num then
			return 1
		elseif v1_num < v2_num then
			return -1
		end
	end
	return 0
end
local GetBBSUrl = function()
	local result = false
	local url = ""
	local sChannel = NovaAPI.GetClientChannel()
	if sChannel == "CN" or sChannel == "CN_Taptap" then
		result = true
		url = "https://bbs-stellasora.yostar.net/"
	elseif sChannel == "CN_TEST_1" then
		result = true
		url = "https://staging-bbs.yostar.net/"
	end
	return result, url
end
local GetToolBoxUrl = function()
	local result = false
	local url = ""
	local sChannel = NovaAPI.GetClientChannel()
	local clientPublishRegion = CS.ClientConfig.ClientPublishRegion
	if clientPublishRegion == CS.ClientPublishRegion.CN then
		if sChannel == "CN" or sChannel == "CN_Bilibili" or sChannel == "CN_Taptap" then
			result = true
			url = "https://toolbox-stellasora.yostar.cn"
		else
			result = true
			url = "https://staging-web-toolbox-stellasora.yostar.cn"
		end
	elseif clientPublishRegion == CS.ClientPublishRegion.TW then
		if sChannel == "TW" then
			result = true
			url = "https://toolbox-stellasora.stargazer-games.com"
		else
			result = true
			url = "https://staging-web-toolbox-stellasora.stargazer-games.com"
		end
	elseif clientPublishRegion == CS.ClientPublishRegion.JP then
		if sChannel == "JP" then
			result = true
			url = "https://toolbox.stellasora.jp"
		else
			result = true
			url = "https://staging-web-toolbox.stellasora.jp"
		end
	elseif clientPublishRegion == CS.ClientPublishRegion.US then
		if sChannel == "EN" then
			result = true
			url = "https://toolbox.stellasora.global"
		else
			result = true
			url = "https://staging-web-toolbox.stellasora.global"
		end
	elseif clientPublishRegion == CS.ClientPublishRegion.KR then
		if sChannel == "KR" then
			result = true
			url = "https://toolbox.stellasora.kr"
		else
			result = true
			url = "https://staging-web-toolbox.stellasora.kr"
		end
	end
	return result, url
end
local GetExchangeCodeUrl = function()
	local result = false
	local url = ""
	local sChannel = NovaAPI.GetClientChannel()
	local clientPublishRegion = CS.ClientConfig.ClientPublishRegion
	if clientPublishRegion == CS.ClientPublishRegion.CN then
	elseif clientPublishRegion == CS.ClientPublishRegion.TW then
		if sChannel == "TW" then
			result = true
			url = "https://stellasora.stargazer-games.com/redemption?type=webview"
		else
			result = true
			url = "https://staging-web-stellasora.stargazer-games.com/redemption?type=webview"
		end
	elseif clientPublishRegion == CS.ClientPublishRegion.JP then
		if sChannel == "JP" then
			result = true
			url = "https://stellasora.jp/serial_code?type=webview"
		else
			result = true
			url = "https://staging-web.stellasora.jp/serial_code?type=webview"
		end
	elseif clientPublishRegion == CS.ClientPublishRegion.US then
		if sChannel == "EN" then
			result = true
			url = "https://stellasora.global/gift?type=webview"
		else
			result = true
			url = "https://staging-web.stellasora.global/gift?type=webview"
		end
	elseif clientPublishRegion == CS.ClientPublishRegion.KR then
		if sChannel == "KR" then
			result = true
			url = "https://stellasora.kr/gift?type=webview"
		else
			result = true
			url = "https://staging-web.stellasora.kr/gift?type=webview"
		end
	end
	return result, url
end
_G.UTILS = {
	DecodeChangeInfo = DecodeChangeInfo,
	OpenReceiveByChangeInfo = OpenReceiveByChangeInfo,
	OpenReceiveByDisplayItem = OpenReceiveByDisplayItem,
	OpenReceiveByReward = OpenReceiveByReward,
	GetParamStrLen = GetParamStrLen,
	ParseByteString = ParseByteString,
	IsBitSet = IsBitSet,
	GetBuildAttributeId = GetBuildAttributeId,
	GetCharacterAttributeId = GetCharacterAttributeId,
	GetDiscAttributeId = GetDiscAttributeId,
	GetDiscExtraAttributeId = GetDiscExtraAttributeId,
	GetPotentialId = GetPotentialId,
	SubDesc = SubDesc,
	ParseDesc = ParseDesc,
	ParseDiscDesc = ParseDiscDesc,
	ParseParamDesc = ParseParamDesc,
	ParseLevelQuestTargetDesc = ParseLevelQuestTargetDesc,
	GetLevelQuestTargetProcess = GetLevelQuestTargetProcess,
	ParseRewardItemCount = ParseRewardItemCount,
	GetBezierPointByT = GetBezierPointByT,
	AddEffect = AddEffect,
	AddFateCardEft = AddFateCardEft,
	AddBuildEffect = AddBuildEffect,
	RemoveEffect = RemoveEffect,
	GetBattleSamples = GetBattleSamples,
	GetCharDamageResult = GetCharDamageResult,
	ClickItemGridWithTips = ClickItemGridWithTips,
	QueryLevelInfo = QueryLevelInfo,
	SDK_Logout = SDK_Logout,
	SDK_ShowAgreement = SDK_ShowAgreement,
	ParseNoBrokenDesc = ParseNoBrokenDesc,
	CheckIsTipsPanel = CheckIsTipsPanel,
	ClickWordLink = ClickWordLink,
	CheckChannelList = CheckChannelList,
	VersionCompare = VersionCompare,
	SortByPriority = SortByPriority,
	GetDayRefreshTimeOffset = GetDayRefreshTimeOffset,
	GetBBSUrl = GetBBSUrl,
	GetToolBoxUrl = GetToolBoxUrl,
	GetExchangeCodeUrl = GetExchangeCodeUrl,
	CheckChannelList_Notice = CheckChannelList_Notice
}
