local SceneUtils = {}
local isInCityScene = false
local Sound = CS.GameEntry.Sound
local Resource = CS.GameEntry.Resource
local SVC_CITY_PATH = "Assets/Main/Shaders2019/SVC_City.shadervariants"
local SVC_WORLD_PATH = "Assets/Main/Shaders2019/SVC_World.shadervariants"
local NinePalacesWorldBasePosX = {
  0,
  2000,
  4000,
  0,
  2000,
  4000,
  0,
  2000,
  4000
}
local NinePalacesWorldBasePosZ = {
  0,
  0,
  0,
  2000,
  2000,
  2000,
  4000,
  4000,
  4000
}
local NinePalacesTileBasePosX = {
  0,
  1000,
  2000,
  0,
  1000,
  2000,
  0,
  1000,
  2000
}
local NinePalacesTileBasePosZ = {
  0,
  0,
  0,
  1000,
  1000,
  1000,
  2000,
  2000,
  2000
}
local SetIsInCity = function(value)
  isInCityScene = value
end

function SceneUtils.GetNinePalacesOffsetByIndex(mapIndex)
  return NinePalacesWorldBasePosX[mapIndex], 0, NinePalacesWorldBasePosZ[mapIndex]
end

function SceneUtils.GetNinePalacesOffset(theServerId)
  local serverId = toInt(theServerId)
  if 0 < serverId then
    local info = SeasonUtil.GetSeasonInfo(serverId)
    if info ~= nil and info:GetServerType() == SeasonMapType.NineNation then
      local mapIndex = info:GetNinePalacesIndex(serverId)
      if 1 < mapIndex then
        return NinePalacesWorldBasePosX[mapIndex], 0, NinePalacesWorldBasePosZ[mapIndex]
      end
    end
  end
  return 0, 0, 0
end

local TileToWorld = function(tilePos, forceType, theServerId)
  local v3 = {}
  v3.x = (tilePos.x + 0.5) * TileSize
  v3.y = 0
  v3.z = (tilePos.y + 0.5) * TileSize
  if forceType == ForceChangeScene.World and theServerId ~= nil and theServerId ~= 0 then
    local x, y, z = SceneUtils.GetNinePalacesOffset(theServerId)
    if x ~= nil and z ~= nil then
      v3.x = v3.x + x
      v3.z = v3.z + z
    end
  end
  return v3
end

function SceneUtils.TileToUniqueTile(tilePos, serverId)
  local v2 = {
    x = tilePos.x,
    y = tilePos.y
  }
  if serverId ~= nil and serverId ~= 0 then
    local mapIndex = DataCenter.SeasonDataManager:GetNinePalacesIndex(serverId)
    if 1 < mapIndex then
      v2.x = tilePos.x + NinePalacesTileBasePosX[mapIndex]
      v2.y = tilePos.y + NinePalacesTileBasePosZ[mapIndex]
    end
  end
  return v2
end

function SceneUtils.WorldToUniqueTile(worldPos)
  local v2 = {}
  v2.x = math.floor(worldPos.x / TileSize)
  v2.y = math.floor(worldPos.z / TileSize)
  return v2
end

function SceneUtils.UniqueTileToWorld(uniqueTile)
  return SceneUtils.TileToWorld(uniqueTile)
end

function SceneUtils.WorldXYToUniqueTileXY(x, y)
  return math.floor(x / TileSize), math.floor(y / TileSize)
end

local WorldToTileFloat = function(worldPos)
  local v2 = {}
  if SeasonUtil.GetSeasonType() == SeasonMapType.NineNation then
    v2.x = Mathf.Mod(worldPos.x / TileSize, WORLD_TILE_COUNT_MAX) - 0.5
    v2.y = Mathf.Mod(worldPos.z / TileSize, WORLD_TILE_COUNT_MAX) - 0.5
  else
    v2.x = worldPos.x / TileSize - 0.5
    v2.y = worldPos.z / TileSize - 0.5
  end
  return v2
end
local WorldToTileFloatXY = function(worldPos)
  local x = 0
  local y = 0
  if SeasonUtil.GetSeasonType() == SeasonMapType.NineNation then
    x = math.floor((Mathf.Mod(worldPos.x / TileSize, WORLD_TILE_COUNT_MAX) - 0.5) * 100) / 100
    y = math.floor((Mathf.Mod(worldPos.z / TileSize, WORLD_TILE_COUNT_MAX) - 0.5) * 100) / 100
  else
    x = math.floor((worldPos.x / TileSize - 0.5) * 100) / 100
    y = math.floor((worldPos.z / TileSize - 0.5) * 100) / 100
  end
  return x, y
end
local WorldToTile = function(worldPos)
  local v2 = {}
  if SeasonUtil.GetSeasonType() == SeasonMapType.NineNation then
    v2.x = Mathf.Mod(math.floor(worldPos.x / TileSize), WORLD_TILE_COUNT_MAX)
    v2.y = Mathf.Mod(math.floor(worldPos.z / TileSize), WORLD_TILE_COUNT_MAX)
  else
    v2.x = math.floor(worldPos.x / TileSize)
    v2.y = math.floor(worldPos.z / TileSize)
  end
  return v2
end
local WorldToTileXZ = function(x, z)
  if SeasonUtil.GetSeasonType() == SeasonMapType.NineNation then
    x = Mathf.Mod(math.floor(x / TileSize), WORLD_TILE_COUNT_MAX)
    z = Mathf.Mod(math.floor(z / TileSize), WORLD_TILE_COUNT_MAX)
  else
    x = math.floor(x / TileSize)
    z = math.floor(z / TileSize)
  end
  return x, z
end
local TileDistanceToMyHome = function(pointIndex, serverId)
  local myCityPos = SceneUtils.IndexToTilePos(LuaEntry.Player:GetMainWorldPos(), ForceChangeScene.World)
  local targetPos = SceneUtils.IndexToTilePos(pointIndex, ForceChangeScene.World)
  if serverId and 0 < serverId then
    local distance = math.ceil(SceneUtils.TileDistance(targetPos, myCityPos, serverId, LuaEntry.Player:GetSelfServerId()))
    return distance
  else
    local distance = math.ceil(SceneUtils.TileDistance(targetPos, myCityPos))
    return distance
  end
end
local TileDistance = function(v2a, v2b, server1, server2)
  if server1 and server2 and 0 < server1 and 0 < server2 and server1 ~= server2 then
    local world1 = SceneUtils.TileToWorld(v2a, ForceChangeScene.World, server1)
    local world2 = SceneUtils.TileToWorld(v2b, ForceChangeScene.World, server2)
    local num1 = world1.x - world2.x
    local num2 = world1.z - world2.z
    return Mathf.Sqrt(num1 * num1 + num2 * num2) / TileSize
  else
    local num1 = v2a.x - v2b.x
    local num2 = v2a.y - v2b.y
    return Mathf.Sqrt(num1 * num1 + num2 * num2)
  end
end
local IndexToTilePos = function(index, forceType)
  local tileCount = WorldTileCount
  if forceType ~= nil then
    if forceType == ForceChangeScene.World then
      tileCount = WorldTileCount
    else
      tileCount = CityTileCount
    end
  elseif isInCityScene == true then
    tileCount = CityTileCount
  end
  local square = tileCount * tileCount
  if index < 1 then
    Logger.LogInfo(string.format("ERR -> forceType : %s, idx : %s, tile : %s", forceType or "nil", index, tileCount))
    return {x = 0, y = 0}
  elseif index > square then
    if forceType == nil and tileCount == CityTileCount then
      tileCount = WorldTileCount
    else
      Logger.LogInfo(string.format("ERR -> forceType : %s, idx : %s, tile : %s", forceType or "nil", index, tileCount))
      return {x = 0, y = 0}
    end
  end
  index = index - 1
  local v2 = {}
  v2.x = index % tileCount
  v2.y = math.floor(index / tileCount)
  return v2
end
local BigIndexToStandardIndex = function(index)
  return index // 10
end
local BigIndexToTilePos = function(index, forceType)
  return SceneUtils.IndexToTilePos(SceneUtils.BigIndexToStandardIndex(index), forceType)
end
local TilePosToIndex = function(tilePos, forceType)
  return SceneUtils.TileXYToIndex(tilePos.x, tilePos.y, forceType)
end
local TileXYToIndex = function(x, y, forceType)
  local tileCount = WorldTileCount
  if forceType ~= nil then
    if forceType == ForceChangeScene.World then
      tileCount = WorldTileCount
    else
      tileCount = CityTileCount
    end
  elseif isInCityScene == true then
    tileCount = CityTileCount
  end
  if x < 0 or y < 0 or x > tileCount - 1 or y > tileCount - 1 then
    return 0
  end
  return x + y * tileCount + 1
end
local TileIndexToWorld = function(index, forceType, theServerId)
  return SceneUtils.TileToWorld(SceneUtils.IndexToTilePos(index, forceType), forceType, theServerId)
end
local WorldToTileIndex = function(pos, forceType)
  return SceneUtils.TilePosToIndex(SceneUtils.WorldToTile(pos), forceType)
end
local GetIndexByOffsetX = function(index, offset, forceType)
  local temp = index - 1
  local tileCount = WorldTileCount
  if forceType ~= nil then
    if forceType ~= ForceChangeScene.World then
      tileCount = CityTileCount
    end
  elseif isInCityScene == true then
    tileCount = CityTileCount
  end
  temp = temp % tileCount
  temp = temp + offset
  if 0 <= temp and tileCount > temp then
    return index + offset
  end
  return 0
end
local GetIndexByOffsetY = function(index, offset, forceType)
  local temp = index - 1
  local tileCount = WorldTileCount
  if forceType ~= nil then
    if forceType ~= ForceChangeScene.World then
      tileCount = CityTileCount
    end
  elseif isInCityScene == true then
    tileCount = CityTileCount
  end
  local _floor = math.floor
  temp = _floor(temp / tileCount)
  temp = temp + offset
  if 0 <= temp and tileCount > temp then
    return index + offset * tileCount
  end
  return 0
end
local GetIndexByOffset = function(index, x, y, forceType)
  local temp = GetIndexByOffsetX(index, x, forceType)
  if 0 < temp then
    temp = GetIndexByOffsetY(temp, y, forceType)
    return temp
  end
  return 0
end
local GetMarchCurPos = function(march)
  local position
  local status = march:GetMarchStatus()
  local path = string.split(march.path, ";")
  local curServerId = march.pathStartServerId
  if status == MarchStatus.MOVING or status == MarchStatus.BACK_HOME or status == MarchStatus.CHASING or status == MarchStatus.IN_WORM_HOLE then
    if 1 < #path then
      local serverNow = UITimeManager:GetInstance():GetServerTime()
      local pathLen = 0
      local BlackLandSpeed = DataCenter.BirthPointTemplateManager:GetBlackLandSpeedByServerId(curServerId)
      local blackEndTime = march.blackEndTime
      local blackStartTime = march.blackStartTime
      if blackEndTime ~= nil and blackStartTime ~= nil and 0 < blackStartTime and 0 < blackEndTime then
        local BlackLandMaxSpeed = DataCenter.BirthPointTemplateManager:GetBlackLandMaxSpeed()
        local BlackLandRealSpeed = math.min(BlackLandMaxSpeed, march.speed * BlackLandSpeed)
        if serverNow <= blackStartTime then
          pathLen = march.speed * (serverNow - march.startTime) * 0.001 * TileSize
        elseif serverNow <= blackEndTime then
          pathLen = march.speed * (blackStartTime - march.startTime) * 0.001 * TileSize
          pathLen = pathLen + (serverNow - blackStartTime) * 0.001 * TileSize * BlackLandRealSpeed
        else
          pathLen = march.speed * (blackStartTime - march.startTime) * 0.001 * TileSize
          pathLen = pathLen + (blackEndTime - blackStartTime) * 0.001 * TileSize * BlackLandRealSpeed
          pathLen = pathLen + march.speed * (serverNow - blackEndTime) * 0.001 * TileSize
        end
      else
        pathLen = march.speed * (serverNow - march.startTime) * 0.001 * TileSize
      end
      local pathSegment = SceneUtils.CreatePathSegment(march)
      position = SceneUtils.CalcMoveOnPath(pathSegment, 1, pathLen)
    else
      position = SceneUtils.TileIndexToWorld(march.targetPos, ForceChangeScene.World, march.targetServer)
    end
  elseif status == MarchStatus.TRANSPORT_BACK_HOME then
    position = SceneUtils.TileIndexToWorld(march.startPos, ForceChangeScene.World, march.srcServer)
  else
    position = SceneUtils.TileIndexToWorld(march.targetPos, ForceChangeScene.World, march.targetServer)
  end
  return position
end
local CreatePathSegment = function(march)
  local path = string.split(march.path, ";")
  if #path < 2 then
    return nil
  end
  for i = 1, #path do
    path[i] = tonumber(path[i])
  end
  local pathList = {}
  for i = 1, #path do
    pathList[i] = {}
    if i < #path then
      local curPos = SceneUtils.TileIndexToWorld(path[i], ForceChangeScene.World, march.pathStartServerId)
      local nextPos = SceneUtils.TileIndexToWorld(path[i + 1], ForceChangeScene.World, march.targetServer)
      local pathVec = Vector3.New(nextPos.x - curPos.x, nextPos.y - curPos.y, nextPos.z - curPos.z)
      pathList[i].pos = curPos
      pathList[i].dir = Vector3.Normalize(pathVec)
      pathList[i].dist = Vector3.Magnitude(pathVec)
    else
      pathList[i].pos = SceneUtils.TileIndexToWorld(path[i], ForceChangeScene.World, march.targetServer)
      pathList[i].dir = pathList[i - 1].dir
      pathList[i].dist = LongMaxValue
    end
  end
  return pathList
end
local CalcMoveOnPath = function(path, startIndex, startPathLen)
  local pathIdx = startIndex
  local pathLen = startPathLen
  local pos
  while pathIdx < #path and pathLen > path[pathIdx].dist do
    pathLen = path[pathIdx].dist - 1
    pathIdx = pathIdx + 1
  end
  if pathIdx < #path then
    pos = path[pathIdx].pos + path[pathIdx].dir * pathLen
  else
    pos = path[#path].pos
  end
  return pos
end
local GetIsInCity = function()
  local curScene = CS.SceneManager.CurrSceneID
  return curScene == SceneManagerSceneID.City
end
local GetIsInWorld = function()
  local curScene = CS.SceneManager.CurrSceneID
  return curScene == SceneManagerSceneID.World
end
local GetIsInPve = function()
  local curScene = CS.SceneManager.CurrSceneID
  return curScene == SceneManagerSceneID.PVE
end
local ChangeToCity = function(createAction)
  local action = createAction
  if SceneUtils.GetIsInCity() then
    if action ~= nil then
      action()
    end
  elseif SceneUtils.GetIsInWorld() then
    PostEventLog.Track(PostEventLog.Defines.change_to_city_start, {})
    EventManager:GetInstance():Broadcast(EventId.BeforeLeaveWorld)
    local scene = CS.SceneManager.World
    GoToUtil.CloseAllWindows()
    if BattleFieldUtil.InBattleField() then
      CrossServerUtil.OnBackSelfServerFromDragonWorld()
    end
    LuaEntry.Player:SetCrossServerId(-1)
    CrossServerUtil.SetLastJumpToParam(nil)
    WorldMarchTileUIManager:GetInstance():OnExitWorld()
    DataCenter.BuildBubbleManager:ClearAll()
    DataCenter.WorldBuildBubbleManager:ClearAll()
    DataCenter.RoadBubbleManager:ClearAll()
    DataCenter.AllianceCityTipManager:RemoveAllAllianceCityTip()
    DataCenter.SurpriseBuildingTipManager:RemoveAllSurpriseBuildingTip()
    AllianceBuildBloodManager:GetInstance():RemoveAllEffect()
    DataCenter.WarningBallManager:DeleteTimer()
    DataCenter.WorldFavoDataManager:ClearAll()
    DataCenter.ArrowManager:CloseAllArrow()
    DataCenter.LWWorldZoneChangeTipManager:RemoveTimer()
    SceneUtils.SetIsInCity(true)
    SFSNetwork.SendMessage(MsgDefines.LeaveWorld)
    CS.SceneManager.DestroyScene(scene)
    DataCenter.SeasonDataManager:SetViewServerSkinMeta(nil)
    DataCenter.WorldTroopLineManager:Destroy()
    SceneUtils.CreateCity()
    DataCenter.WorldBattleManager:OnExitWorld()
    DataCenter.WorldFakeBattleManager:OnExitWorld()
    DataCenter.LWTrainManager:ExitWorld()
    DataCenter.FlowerTrainGroupManager:ExitWorld()
    DataCenter.AllyDrillBaseManager:ExitWorld()
    DataCenter.ZombieBusTrainEntityManager:ExitWorld()
    DataCenter.AllianceWarDataManager:OnExitWorld()
    TileBubbleManager:GetInstance():ExitWorld()
    DataCenter.MonsterProtectionManager:ExitWorld()
    DataCenter.ActMeteoriteBattleManager:ExitWorld()
    collectgarbage("collect")
    CS.SceneManager.World:CreateScene(function()
      DataCenter.WarningBallManager:AddTimer()
      DataCenter.NpcQABubbleManager:SetQuestionBubbleFlag(true)
      EventManager:GetInstance():Broadcast(EventId.SetCityPeopleAndCarVisible, CityPeopleAndCarVisibleType.AllShow)
      DataCenter.CityNpcManager:SetNpcVisible(true)
      EventManager:GetInstance():Broadcast(EventId.UIMAIN_VISIBLE, true)
      DataCenter.GuideManager:DoWaitTriggerAfterBack()
      if action ~= nil then
        action()
      end
      EventManager:GetInstance():Broadcast(EventId.OnEnterCity)
      PostEventLog.Track(PostEventLog.Defines.change_to_city_finish, {})
    end)
  end
end
local ChangeToWorld = function(createAction, noSendReq, noSendGoToWorld)
  local action = createAction
  if SceneUtils.GetIsInWorld() then
    if action ~= nil then
      action()
    end
  elseif SceneUtils.GetIsInCity() then
    PostEventLog.Track(PostEventLog.Defines.change_to_world_start, {})
    if not noSendGoToWorld then
      SFSNetwork.SendMessage(MsgDefines.GoToWorld)
    end
    local scene = CS.SceneManager.World
    EventManager:GetInstance():Broadcast(EventId.BeforeReleaseCity)
    GoToUtil.CloseAllWindows()
    DataCenter.LandLockManager:DestroyAll()
    DataCenter.BuildBubbleManager:ClearAll()
    DataCenter.WorldBuildBubbleManager:ClearAll()
    DataCenter.RoadBubbleManager:ClearAll()
    DataCenter.ArrowManager:CloseAllArrow()
    DataCenter.SeasonPowerWorkerManager:DestroyWorkerMan()
    DataCenter.AllianceCityTipManager:RemoveAllAllianceCityTip()
    DataCenter.SurpriseBuildingTipManager:RemoveAllSurpriseBuildingTip()
    AllianceBuildBloodManager:GetInstance():RemoveAllEffect()
    DataCenter.WarningBallManager:DeleteTimer()
    DataCenter.WorldFavoDataManager:ClearAll()
    DataCenter.NpcQABubbleManager:SetQuestionBubbleFlag(false)
    EventManager:GetInstance():Broadcast(EventId.SetCityPeopleAndCarVisible, CityPeopleAndCarVisibleType.AllHide)
    DataCenter.CityNpcManager:SetNpcVisible(false)
    DataCenter.ArmyFormationDataManager:FetchFormationSoldier()
    scene:UninitSubModulesAndCameraUpdate_withoutCameraMove()
    SceneUtils.CreateWorld()
    DataCenter.LWTrainManager:EnterWorld()
    DataCenter.FlowerTrainGroupManager:EnterWorld()
    DataCenter.AllyDrillBaseManager:EnterWorld()
    DataCenter.ZombieBusTrainEntityManager:EnterWorld()
    DataCenter.MonsterProtectionManager:EnterWorld()
    DataCenter.BirthPointTemplateManager:EnterWorld()
    EventManager:GetInstance():Broadcast(EventId.OnEnterWorldFromCity)
    SceneUtils.SetIsInCity(false)
    collectgarbage("collect")
    CS.SceneManager.World:CreateScene(function()
      scene:UninitCameraMovement()
      CS.SceneManager.DestroyScene(scene)
      if not noSendReq then
        CS.SceneManager.World:UpdateViewRequest(true)
      end
      DataCenter.WarningBallManager:AddTimer()
      DataCenter.LWWorldZoneChangeTipManager:AddTimer()
      EventManager:GetInstance():Broadcast(EventId.UIMAIN_VISIBLE, true)
      if action ~= nil then
        action()
      end
      EventManager:GetInstance():Broadcast(EventId.OnEnterWorld)
      PostEventLog.Track(PostEventLog.Defines.change_to_world_finish, {})
    end)
    if SeasonUtil.GetSeasonType() == SeasonMapType.Snow then
      CS.UnityEngine.Shader.DisableKeyword("HEATMAP_ON")
    end
  end
end
local CheckCanGotoWorld = function(error_tip, enable_guide)
  local unlock = DataCenter.LWFunctionUnlockManager:CheckCanShow(LWFunctionUnlockType.MainUI_WorldBtn)
  if not unlock then
    if error_tip ~= nil and (type(error_tip) == "string" or type(error_tip) == "number") then
      UIUtil.ShowTipsId(error_tip)
    else
      UIUtil.ShowTipsId("E100101")
    end
    return false
  end
  return true
end
local TryJoinAlliance = function(callback)
  local allianceId = LuaEntry.Player:GetAllianceUid()
  if not string.IsNullOrEmpty(allianceId) then
    if callback ~= nil and type(callback) == "function" then
      callback(true)
    end
    return
  end
  local param = {
    agree_close = true,
    agree_callback = function()
      local params = {
        guide = false,
        al_success_callback = function()
          UIManager:GetInstance():DestroyWindow(UIWindowNames.UILWAlCreateJoin)
          if callback ~= nil and type(callback) == "function" then
            callback(true)
          end
        end,
        al_lose_callback = function()
          if callback ~= nil and type(callback) == "function" then
            callback(false)
          end
        end
      }
      if LuaEntry.Player:IsFirstJoinAlliance() == true then
        UIManager:GetInstance():OpenWindow(UIWindowNames.UILWAllianceFirstJoin, {anim = true}, params)
      else
        UIManager:GetInstance():OpenWindow(UIWindowNames.UILWAlCreateJoin, {anim = true}, params)
      end
    end,
    dialog_id = 1001
  }
  UIManager:GetInstance():OpenWindow(UIWindowNames.UILWAlGuide, {
    anim = false,
    UIMainAnim = UIMainAnimType.AllHide
  }, param)
end

function SceneUtils:TryFastJoinAlliance(callback)
  local params = {
    guide = false,
    al_success_callback = function()
      UIManager:GetInstance():DestroyWindow(UIWindowNames.UILWAlCreateJoin)
      if callback ~= nil and type(callback) == "function" then
        callback(true)
      end
    end,
    al_lose_callback = function()
      if callback ~= nil and type(callback) == "function" then
        callback(false)
      end
    end
  }
  if LuaEntry.Player:IsFirstJoinAlliance() == true then
    UIManager:GetInstance():OpenWindow(UIWindowNames.UILWAllianceFirstJoin, {anim = true}, params)
  else
    UIManager:GetInstance():OpenWindow(UIWindowNames.UILWAlCreateJoin, {anim = true}, params)
  end
end

function SceneUtils.IsInBlackOrYellowLand(pointId, serverId)
  return SceneUtils.IsInBlackLand(pointId, serverId) or SceneUtils.IsInYellowLand(pointId, serverId)
end

function SceneUtils.IsInBlackLand(pointId, serverId)
  return SceneUtils.IsInBlackRange(pointId, serverId) or DataCenter.AllianceSkillManager:GetBlackAreaOverTime(pointId, serverId) > 0
end

function SceneUtils.IsInYellowLand(pointId, serverId)
  return DataCenter.BirthPointTemplateManager:IsInAllianceCityField(pointId, serverId)
end

local IsInBlackRange = function(pointId, serverId)
  if pointId == nil or pointId == 0 or pointId == -1 or BattleFieldUtil.InBattleField() and not BattleFieldUtil.isObserve then
    return false
  end
  local v2 = SceneUtils.IndexToTilePos(pointId, ForceChangeScene.World)
  local a, b, c, d = DataCenter.BirthPointTemplateManager:GetBlackLandRange(serverId)
  if v2.x >= a.x and v2.x <= b.x and v2.y <= a.y and v2.y >= d.y then
    return true
  end
  return false
end
local IsInCityField = function(pointId)
  if SeasonUtil.GetSeason() == 0 then
    return false
  end
  return DataCenter.BirthPointTemplateManager:IsInAllianceCityField(pointId)
end
local CreateWorld = function()
  if nil == SceneUtils.svc_world then
    SceneUtils.svc_world = Resource:LoadAsset(SVC_WORLD_PATH, typeof(CS.UnityEngine.ShaderVariantCollection)).asset
  end
  if DataCenter.FunctionOnManager:IsServerSwitchOn(ServerSwitch.ShaderWarmUp) then
    SceneUtils.svc_world:WarmUp()
  end
  CS.SceneManager.CreateWorld()
end
local CreateCity = function()
  if nil == SceneUtils.svc_city then
    SceneUtils.svc_city = Resource:LoadAsset(SVC_CITY_PATH, typeof(CS.UnityEngine.ShaderVariantCollection)).asset
  end
  if DataCenter.FunctionOnManager:IsServerSwitchOn(ServerSwitch.ShaderWarmUp) then
    SceneUtils.svc_city:WarmUp()
  end
  CS.SceneManager.CreateCity()
end
local IsIndexInWorld = function(index)
  if index <= 0 then
    return false
  end
  local tileCount = WorldTileCount
  index = index - 1
  local x = index % tileCount
  local y = math.floor(index / tileCount)
  return 0 <= x and x <= 999 and 0 <= y and y <= 999
end

function SceneUtils.SceneDescription(name, argsArray)
  local world = CS.SceneManager.World
  if world then
    local desc = world:Description(name, argsArray)
    if desc then
      Logger.LogWarning(desc)
    else
      Logger.LogWarning("Scene Description failed. ???")
    end
  else
    Logger.LogWarning("Scene Description failed. World is null.")
  end
end

function SceneUtils.GetZoneIdByPosId(pointId, serverId)
  if SceneUtils.GetIsInWorld() and CS.SceneManager.World ~= nil and serverId == LuaEntry.Player:GetCurServerId() then
    local worldPos = SceneUtils.TileIndexToWorld(pointId, ForceChangeScene.World, serverId)
    local zoneId = CS.SceneManager.World:GetZoneIdByWorldPos(worldPos)
    if zoneId ~= 0 then
      return zoneId
    end
  end
  local theServerId = serverId or LuaEntry.Player:GetSelfServerId()
  local data = SeasonUtil.GetSeasonInfo(theServerId)
  if data and (data:ServerInReady() and data:InNormalMode() or data:InHaltMode()) then
    local theServerType = data:GetServerType(false)
    local worldPos = SceneUtils.TileIndexToWorld(pointId, ForceChangeScene.World, theServerId)
    return CS.WorldZoneMapData.GetZoneIdByWorldPos(worldPos, theServerType)
  end
  return CS.WorldZoneMapData.GetZoneIdByPosId(pointId, 0)
end

function SceneUtils.GetBlackLengthByStartEnd(startTilePos, endTilePos, fromServer, toServer)
  if SceneUtils.IsBlackLandActive() then
    return DataCenter.BirthPointTemplateManager:GetBlackLandIntersectionWithSegment(startTilePos, endTilePos, fromServer, toServer)
  end
  return 0
end

function SceneUtils.AxisAlignRectIntersectSegment(rectMinX, rectMinY, rectMaxX, rectMaxY, startX, startY, endX, endY)
  local num1 = math.min(startX, endX)
  local num2 = math.max(startX, endX)
  if rectMaxX < num2 then
    num2 = rectMaxX
  end
  if rectMinX > num1 then
    num1 = rectMinX
  end
  if num2 < num1 then
    return false
  end
  local num3 = math.min(startY, endY)
  local num4 = math.max(startY, endY)
  local f = endX - startX
  if math.abs(f) > 1.0E-6 then
    local num5 = (endY - startY) / f
    local num6 = startY - num5 * startX
    num3 = num5 * num1 + num6
    num4 = num5 * num2 + num6
  end
  if num3 > num4 then
    num3, num4 = num4, num3
  end
  if rectMaxY < num4 then
    num4 = rectMaxY
  end
  if rectMinY > num3 then
    num3 = rectMinY
  end
  return num4 >= num3
end

function SceneUtils.AxisAlignRectIntersectAxisAlignSegment(rectMinX, rectMinY, rectMaxX, rectMaxY, startX, startY, endX, endY)
  if startX == endX then
    if startX < rectMinX or rectMaxX < startX then
      return false
    end
    local segMinY = math.min(startY, endY)
    local segMaxY = math.max(startY, endY)
    return rectMinY <= segMaxY and rectMaxY >= segMinY
  elseif startY == endY then
    if startY < rectMinY or rectMaxY < startY then
      return false
    end
    local segMinX = math.min(startX, endX)
    local segMaxX = math.max(startX, endX)
    return rectMinX <= segMaxX and rectMaxX >= segMinX
  else
    return SceneUtils.AxisAlignRectIntersectSegment(rectMinX, rectMinY, rectMaxX, rectMaxY, startX, startY, endX, endY)
  end
end

function SceneUtils.IsBlackLandActive()
  local myServerKing = DataCenter.GovernmentManager.activityServerData
  local myServerKingFight = myServerKing and myServerKing.actFightStep == 1
  if myServerKingFight or DataCenter.ZoneWarManager:IsBattleDay() then
    return true
  end
  return false
end

local PlayWorldEffect = function(pointId, serverId, prefabPath, lifeTime)
  local theWorld = CS.SceneManager.World
  if theWorld ~= nil and SceneUtils.GetIsInWorld() then
    local curServerId = serverId or LuaEntry.Player:GetCurServerId()
    local worldPos = SceneUtils.TileToWorld(SceneUtils.IndexToTilePos(pointId, ForceChangeScene.World), ForceChangeScene.World, curServerId)
    theWorld:CreateBattleVFX(prefabPath, lifeTime, function(go)
      if IsNotNull(go) and SceneUtils.GetIsInWorld() then
        go.transform:Set_position(worldPos.x, worldPos.y, worldPos.z)
      end
    end)
  end
end

function SceneUtils.PlayWorldEffect(pointId, serverId, prefabPath, lifeTime, delay)
  if delay then
    local _pointId = pointId
    local _prefabPath = prefabPath
    local _lifeTime = lifeTime
    local _serverId = serverId
    TimerManager:GetInstance():DelayInvoke(function()
      PlayWorldEffect(_pointId, _serverId, _prefabPath, _lifeTime)
    end, tonumber(delay) or 0.1)
  else
    PlayWorldEffect(pointId, serverId, prefabPath, lifeTime)
  end
end

function SceneUtils.PlayWorldSceneBGMusic()
  Sound:PlayMainSceneBGMusic()
end

function SceneUtils.PlayGuideSceneBgMusic()
  Sound:PlayGuideSceneBgMusic()
end

function SceneUtils.PlayCityBGM()
  local seasonType = SeasonUtil.GetSeasonType()
  if seasonType == SeasonMapType.Darkness then
    SceneUtils.TryPlayDarkneesSeasonBloodyNightBGM()
    return
  end
  local soundId = DataCenter.SeasonDataManager:GetCityBGMId()
  if not string.IsNullOrEmpty(soundId) then
    DataCenter.LWSoundManager:PlaySound(soundId, false, true)
  end
end

function SceneUtils.PlayWorldBGM()
  local seasonType = SeasonUtil.GetSeasonType()
  if seasonType == SeasonMapType.Darkness then
    SceneUtils.TryPlayDarkneesSeasonBloodyNightBGM()
    return
  end
  if SceneUtils.TryPlayQueenOfBloodWorldBgm() then
    return
  end
  local soundId = DataCenter.SeasonDataManager:GetWorldBGMId()
  if not string.IsNullOrEmpty(soundId) then
    DataCenter.LWSoundManager:PlaySound(soundId, false, true)
  end
end

function SceneUtils.PlayWorldAMBSound()
  local soundId = DataCenter.SeasonDataManager:GetWorldAMBSoundId()
  if soundId ~= 0 then
    return DataCenter.LWSoundManager:PlayAMBSound(soundId), soundId
  end
end

function SceneUtils.PlayCityAMBSound()
  local soundId = DataCenter.SeasonDataManager:GetCityAMBSoundId()
  if soundId ~= 0 then
    return DataCenter.LWSoundManager:PlayAMBSound(soundId), soundId
  end
end

function SceneUtils.TryPlayDarkneesSeasonBloodyNightBGM()
  local isBloodyNight = DataCenter.BloodyNightDataManager:IsBloodyNight(LuaEntry.Player:GetSelfServerId())
  if isBloodyNight then
    DataCenter.LWSoundManager:PlaySound(70029, false, true)
    return
  else
    local state, BNTemplate, startTime, endTime = DataCenter.BloodyNightDataManager:GetBloodyNightState()
    if state == BloodyNightState.Silent then
      local curTime = UITimeManager:GetInstance():GetServerTime()
      local remainTime = endTime - curTime
      if 0 <= remainTime and remainTime <= 22000 then
        local musicStartTime = 22000 - remainTime
        DataCenter.LWSoundManager:PlayBloodyNightTransitionBGM(musicStartTime)
        return
      end
    end
    local curScene = CS.SceneManager.CurrSceneID
    if curScene == SceneManagerSceneID.World then
      local soundId = DataCenter.SeasonDataManager:GetWorldBGMId()
      if not string.IsNullOrEmpty(soundId) then
        DataCenter.LWSoundManager:PlaySound(soundId, false, true)
      end
    else
      local soundId = DataCenter.SeasonDataManager:GetCityBGMId()
      if not string.IsNullOrEmpty(soundId) then
        DataCenter.LWSoundManager:PlaySound(soundId, false, true)
      end
    end
  end
  return
end

function SceneUtils.WorldToClosestGridWorld(worldPos)
  if SeasonUtil.InSeasonBigMapMode(LuaEntry.Player:GetCurServerId()) then
    local serverId = DataCenter.SeasonDataManager:GetNinePalacesServerByWorldPos(worldPos)
    local tilePos = SceneUtils.WorldToTile(worldPos)
    return SceneUtils.TileToWorld(tilePos, ForceChangeScene.World, serverId)
  else
    local tilePos = SceneUtils.WorldToTile(worldPos)
    return SceneUtils.TileToWorld(tilePos, ForceChangeScene.World)
  end
end

function SceneUtils.GetCityMetaByPointIndex(pointId, serverId)
  local zoneId = SceneUtils.GetZoneIdByPosId(pointId, serverId)
  local cityMeta = DataCenter.AllianceCityTemplateManager:GetTemplate(zoneId, serverId)
  return cityMeta
end

function SceneUtils.TryPlayQueenOfBloodWorldBgm()
  local bgmId = DataCenter.OffSeason1QueenOfBloodManager:GetNeedPlayBgmId()
  if bgmId then
    DataCenter.LWSoundManager:PlaySound(bgmId, true)
    return true
  end
  return false
end

SceneUtils.IsInBlackRange = IsInBlackRange
SceneUtils.IsInCityField = IsInCityField
SceneUtils.SetIsInCity = SetIsInCity
SceneUtils.TileToWorld = TileToWorld
SceneUtils.TileDistance = TileDistance
SceneUtils.TileDistanceToMyHome = TileDistanceToMyHome
SceneUtils.WorldToTile = WorldToTile
SceneUtils.WorldToTileXZ = WorldToTileXZ
SceneUtils.IndexToTilePos = IndexToTilePos
SceneUtils.BigIndexToStandardIndex = BigIndexToStandardIndex
SceneUtils.BigIndexToTilePos = BigIndexToTilePos
SceneUtils.TilePosToIndex = TilePosToIndex
SceneUtils.TileXYToIndex = TileXYToIndex
SceneUtils.TileIndexToWorld = TileIndexToWorld
SceneUtils.WorldToTileIndex = WorldToTileIndex
SceneUtils.GetIndexByOffset = GetIndexByOffset
SceneUtils.GetIndexByOffsetX = GetIndexByOffsetX
SceneUtils.GetIndexByOffsetY = GetIndexByOffsetY
SceneUtils.GetMarchCurPos = GetMarchCurPos
SceneUtils.CreatePathSegment = CreatePathSegment
SceneUtils.CalcMoveOnPath = CalcMoveOnPath
SceneUtils.WorldToTileFloat = WorldToTileFloat
SceneUtils.WorldToTileFloatXY = WorldToTileFloatXY
SceneUtils.GetIsInCity = GetIsInCity
SceneUtils.GetIsInWorld = GetIsInWorld
SceneUtils.GetIsInPve = GetIsInPve
SceneUtils.ChangeToWorld = ChangeToWorld
SceneUtils.ChangeToCity = ChangeToCity
SceneUtils.CreateWorld = CreateWorld
SceneUtils.CreateCity = CreateCity
SceneUtils.IsIndexInWorld = IsIndexInWorld
SceneUtils.CheckCanGotoWorld = CheckCanGotoWorld
SceneUtils.TryJoinAlliance = TryJoinAlliance
return ConstClass("SceneUtils", SceneUtils)
