local BattleScheduler = require "app/module/battle/helper/battle_scheduler"
local BattlePassive = require "app/module/battle/helper/battle_passive"
local BattlePool = require "app/module/battle/helper/battle_pool"
local BattleHelper = require "app/module/battle/helper/battle_helper"
local BattleBuffEntity = require "app/userdata/battle/skill/battle_buff_entity"
local BattleTeam = require "app/module/battle/team/battle_team"
local BATTLE_BOARD_SKILL_HANDLE = require "app/module/battle/skill/battle_board_skill_handle"
local BATTLE_ROGUE_SKILL_HANDLE = require "app/module/battle/skill/battle_rogue_skill_handle"
local BATTLE_GRID_EFFECT_HANDLE = require "app/module/battle/skill/battle_grid_effect_handle"
local BATTLE_INSTRUCTIONS_HELPER = require "app/module/battle/helper/battle_instructions_helper"
local BattleBoardTouchHelper = require "app/module/battle/helper/battle_board_touch_helper"
local BattleBuffHandle = require "app/module/battle/helper/battle_buff_handle"
local BATTLE_SNAPSHOT_HELPER = require "app/module/battle/helper/battle_snapshot_helper"
local BOARD_HELER = require "app/module/battle/helper/board_helper"
local BattleFormula = require "app/module/battle/helper/battle_formula"
local BattleBaseController = class("BattleBaseController")
local BattleConst = GConst.BattleConst
local BUFF_NAME_TO_ATTR = BattleConst.BUFF_NAME_TO_ATTR
local GRID_BREAK_CONDITION = BattleConst.GRID_BREAK_CONDITION
local SIDE_ATK = BattleConst.SIDE_ATK
local SIDE_DEF = BattleConst.SIDE_DEF

function BattleBaseController:getPosInfo(posId)
    return ModuleManager.BattleManager:getPosInfo(posId, self:getRowCount())
end

function BattleBaseController:getRowCount()
    return self.battleData:getRowCount()
end

function BattleBaseController:getSkillEntityBySkillId(skillId)
    return self.battleData:getSkillEntityBySkillId(skillId, self.curActionSide)
end

function BattleBaseController:getSkillEntityByElement(elementType)
    return self.battleData:getSkillEntityByElement(elementType, self.curActionSide)
end

function BattleBaseController:isUnlockedSkillElementType(elementType)
    return self.battleData:isUnlockedSkillElementType(elementType, self.curActionSide)
end

function BattleBaseController:getSkillCount(skillId)
    return self.battleData:getSkillCount(skillId, self.curActionSide)
end

function BattleBaseController:getSkillPool()
    return self.battleData:getSkillPool(self.curActionSide)
end

function BattleBaseController:getSkillEntities(side)
    side = side or self.curActionSide
    return self.battleData:getSkillEntities(side)
end

function BattleBaseController:addSkillEnergy(elementMap, side)
    side = side or self.curActionSide
    self.battleData:addSkillEnergy(elementMap, side)
end

-- *************各个子模块的战斗需要重写的方法 START*************
function BattleBaseController:getBoardConfig()
    return {}
end

function BattleBaseController:getChapterConfig()
    return {}
end

function BattleBaseController:getChapterId()
    return 0
end

function BattleBaseController:getBuffs()
    return {}
end

function BattleBaseController:getBattleUIPath()
	return "app/ui/battle/battle_ui"
end

---- 障碍格子图片
function BattleBaseController:getBlockIcon()
    local chapterInfo = self:getChapterConfig()[self.chapterId]
    if not chapterInfo then
        return "battle_hinder_4"
    end
    return chapterInfo.block_icon
end

function BattleBaseController:getChessBoardBgName()
    local chapterInfo = self:getChapterConfig()[self.chapterId]
    if not chapterInfo then
        return "chessboard_1"
    end
    return chapterInfo.chess_board
end

function BattleBaseController:getScene()
    local chapterInfo = self:getChapterConfig()[self.chapterId]
    if not chapterInfo then
        return "bg_1"
    end
    return chapterInfo.scene
end

function BattleBaseController:refreshWave()
    if not self.battleUI then
        return
    end
    self.battleUI:refreshWave(self:getWaveIndex())
end

-- 战斗结束
function BattleBaseController:controllBattleEnd()
end

-- 不同模块的战斗需要初始化的东西
function BattleBaseController:initOther()
end

-- 怪物攻击力加成
function BattleBaseController:getMonsterAtkAddition()
	return 0
end

-- 怪物血量加成
function BattleBaseController:getMonsterHpAddition()
	return 0
end

-- 怪物攻击力固定
function BattleBaseController:getMonsterAtkFixed()
	return 0
end

-- 怪物血量固定
function BattleBaseController:getMonsterHpFixed()
	return 0
end

-- 需要额外加载的资源
function BattleBaseController:loadOtherRes(callback)
	return callback()
end

function BattleBaseController:tick(dt)
end

-- 一共有多少波
function BattleBaseController:getMaxWave()
    local config = self:getChapterConfig()[self.chapterId]
    if not config or not config.monster then
        return 0
    end
    return #config.monster
end

function BattleBaseController:getMinEliminationCount()
    if not self.minEliminationCount then
        self.minEliminationCount = GFunc.getConstIntValue("element_combo")
    end
    return self.minEliminationCount
end

function BattleBaseController:getNextMonsterId(waveIndex)
    waveIndex = waveIndex or self:getWaveIndex() + 1
    local config = self:getChapterConfig()[self.chapterId]
    local monsterId = config.monster[waveIndex]
    return monsterId
end

function BattleBaseController:showBossEnterAni(bornTime, bossName, monsterComp, callback)
    self.battleUI:showBossEnterAni(bornTime, bossName, monsterComp, callback)
end

function BattleBaseController:generateNextMonster()
    local monsterId = self:getNextMonsterId()
    if monsterId == nil then
        return self:enterNextWave()
    end
    local isBoss = self.defTeam:getIsBoss()
    local unitEntity = self.battleData:addMonster(monsterId, true, self)
    local modelId = unitEntity:getModelId()
	Logger.logHighlight("=========================xxxx2  modelId = %s isBoss = %s", modelId, isBoss)
	if isBoss then
		modelId = "p0003"
	end
    BattleHelper:loadBattleHeroModel(modelId, self.battleUI:getBattleNode(), function(spineObject)
        self.defTeam:removeAllUnits()
        local monsterComp = spineObject:addLuaComponent(GConst.BattleConst.TYPEOF_LUA_COMP.BATTLE_MONSTER_COMPONENT)
        monsterComp:initWithEntity(modelId, unitEntity, self)
        self.defTeam:addUnit(monsterComp, true)
        self:handleBuffs(GConst.BattleConst.SIDE_DEF)
        self.battleUI:refreshDefHp(unitEntity:getHp(), unitEntity:getHpPercent())
        local bornTime = monsterComp:getAnimationDuration(GConst.BattleConst.SPINE_ANIMATION_NAME.BORN)
        if isBoss then -- 如果是boss就跑过去
            local count = 0
            local function onFinish()
                count = count + 1
                if count == 2 then
                    self.atkTeam:stopRunAction()
                    self:onRoundEnd(true)
					if isBoss then
						self:refreshBossSkill(unitEntity)
					end
                end
            end
            self.atkTeam:playRunAction()
            self.atkTeam:recoverHpOnWaveOver(onFinish)

            isBoss = self.defTeam:getIsBoss()
			Logger.logHighlight("=========================xxxx21  modelId = %s isBoss = %s", modelId, isBoss)
            if isBoss then
                local monsterInfo = ConfigManager:getConfig("monster")[monsterId]
                self:showBossEnterAni(bornTime, ModuleManager.HeroManager:getMonsterName(monsterInfo.monster_base), monsterComp, function()
                    monsterComp:playEnterBattlefield(true, onFinish)
                end)
            else
                monsterComp:playEnterBattlefield(true, onFinish)
            end
        else
			self:clearBossSkill()
            local count = 0
            local function onFinish()
                count = count + 1
                if count == 2 then
                    self:onRoundEnd(true)
					if isBoss then
						self:refreshBossSkill(unitEntity)
					end
                end
            end
            self.atkTeam:recoverHpOnWaveOver(onFinish)

            isBoss = self.defTeam:getIsBoss()
            if isBoss then
                local monsterInfo = ConfigManager:getConfig("monster")[monsterId]
                self:showBossEnterAni(bornTime, ModuleManager.HeroManager:getMonsterName(monsterInfo.monster_base), monsterComp, function()
                    monsterComp:playEnterBattlefield(false, onFinish)
                end)
            else
                monsterComp:playEnterBattlefield(false, onFinish)
            end
        end
    end)
end

function BattleBaseController:findNextDefUnit()
    self:generateNextMonster()
end

function BattleBaseController:onDefDead(callback)
    self.defTeam:getMainUnit():playDead(function()
        local monsterId = self.defTeam:getMainUnit().unitEntity:getDeathSummon()
        if monsterId > 0 then
            self:generateMonsterById(monsterId, function()
                self.curWaveMonsterDead = false
                if callback then
                    callback()
                end
                self:handleBuffs(BattleConst.SIDE_DEF)
            end)
        else
            if callback then
                callback()
            end
        end
    end)
end

function BattleBaseController:onLinkChange()
    self.battleUI:hideAllSfxLine()
    self.linkChangeNeedFalsePosMap = table.clearOrCreate(self.linkChangeNeedFalsePosMap)
    for posId, entity in pairs(self.battleData:getGridEnties()) do
        if entity:getCell() then
            self.linkChangeNeedFalsePosMap[posId] = entity:getCell()
        end
    end

    local sequence = self.battleData:getGridSequence()
    local mainElementType
    local dmgSkillId
    for index, info in ipairs(sequence) do
        local entity = self.battleData:getGridEntity(info.posId)
        if not mainElementType or not dmgSkillId then
            local skillId = entity:getSkillId()
            if not skillId and not mainElementType then
                mainElementType = entity:getElementType()
            end
            if skillId and not dmgSkillId then
                dmgSkillId = skillId
            end
            if mainElementType and dmgSkillId then
                break
            end
        end
    end

    local count = #sequence

    for index, info in ipairs(sequence) do
        local entity = self.battleData:getGridEntity(info.posId)
        if entity:getCell() then
            entity:getCell():showHighLight(true, mainElementType, self.battleUI)
        end
        if self.linkChangeNeedFalsePosMap[info.posId] then
            self.linkChangeNeedFalsePosMap[info.posId] = nil
        end
        if index < count then
            local nextPosId = sequence[index + 1].posId
            self.battleUI:getSfxLine(index, function(obj)
                local curPos = self:getPosInfo(info.posId)
                local nextPos = self:getPosInfo(nextPosId)
                local pos, z = ModuleManager.BattleManager:getPosCenterAndDir(curPos, nextPos)
                obj:setLocalScale(25, 25, 25)
                obj:setLocalPosition(pos.x, pos.y, 0)
                obj:setEulerAngles(0, 0, z)
            end)
        end
        if index == count then
            if entity:getCell() then
                entity:getCell():showAni()
            end
        end
    end

    for posId, cell in pairs(self.linkChangeNeedFalsePosMap) do
        cell:showHighLight(false)
    end

    self.teampSkillBreakEnergyMap = table.clearOrCreate(self.teampSkillBreakEnergyMap)
    for posId, info in pairs(self.battleData:getSkillInfluenceGrids()) do
        local entity = self.battleData:getGridEntity(posId)
        if info.direction ~= BattleConst.BOARD_RANGE_TYPE.RANDOM then
            if entity:getCell() then
                entity:getCell():showCircle(true)
                if dmgSkillId then
                    local skillEntity = self.battleData:getSkillEntityBySkillId(dmgSkillId)
                    local multiple = skillEntity and skillEntity:getSkillBreakEnergyMultiple() or 1
                    if multiple > 1 then
                        entity:getCell():setCountStr(skillEntity:getSkillBreakEnergyMultipleStr())
                        local elementType = entity:getElementType()
                        if entity:canLink() and elementType and elementType > 0 then
                            self.teampSkillBreakEnergyMap[elementType] = (self.teampSkillBreakEnergyMap[elementType] or 0) + (multiple - 1)
                        end
                    end
                end
            end
        end
    end

    local dmgElementTypeMap
    local aniSequence, influenceElementType, lineCount, elementTypeMap, linkElementType, effectGridMap, randomPosList = self:calculateCurElimination(true)
    dmgElementTypeMap = elementTypeMap
    if not self:getCurActionUnitComp():getActiveSkillLimit() then
        if randomPosList then
            for _, posId in ipairs(randomPosList) do
                local gridEntity = self.battleData:getGridEntity(posId)
                if gridEntity and gridEntity:canLink() then
                    local elementType = gridEntity:getElementType()
                    if elementTypeMap[elementType] and elementTypeMap[elementType] > 0 then
                        elementTypeMap[elementType] = elementTypeMap[elementType] - 1
                    end
                end
            end
        end
        for elementType, count in pairs(elementTypeMap) do
            self.teampSkillBreakEnergyMap[elementType] = (self.teampSkillBreakEnergyMap[elementType] or 0) + count
        end
        self.battleUI:refreshSkill(elementTypeMap, count > 0, self.curActionSide)
    end

    if mainElementType then
        self:getSideTeam(self.curActionSide):changeMainUnit(mainElementType)
    end

    local elementTypeCount = 0
    if dmgElementTypeMap then
        for element, count in pairs(dmgElementTypeMap) do
            elementTypeCount = elementTypeCount + count
        end
    end
    local dmg = self:calExpectedInjury(mainElementType, elementTypeCount, dmgSkillId)
    local defMainUnitcomp = self:getCurOtherActionUnitComp()
    if defMainUnitcomp then
        local hp = defMainUnitcomp.unitEntity:getHp()
        local hpPercent = (hp - dmg) / defMainUnitcomp.unitEntity:getMaxHp()
        if self.curActionSide == SIDE_ATK then
            self.battleUI:setDefHp(hp, hpPercent)
        else
            self.battleUI:setAtkHp(hp, hpPercent)
        end
    end
end

function BattleBaseController:getInitBoard()
    if not self.boradList then
        self.boradList = {}
        self.fixedRandomGrid = {}
        self.mysteryBoxIndexMap = {}

        local config = self:getChapterConfig()[self.chapterId]
        local boardCfg = self:getBoardConfig()
        for idx, boardId in ipairs(config.board) do
            local cfg = boardCfg[boardId]
            if cfg then
				local tab = {
					board = GFunc.getTable(cfg.board),
					mysteryBoard = GFunc.getTable(cfg.mystery_box_board),
					gridEdge = GFunc.getTable(cfg.grid_edge)
				}
				if config.mystery_box and config.mystery_box[idx] and math.random(1, BattleConst.DEFAULT_FACTOR) <= config.mystery_box[idx] then
					self.mysteryBoxIndexMap[idx] = GFunc.randomDrop(config.mystery_box_reward)
				end
                table.insert(self.boradList, tab)
                table.insert(self.fixedRandomGrid, GFunc.getTable(cfg.control_element))
            end
        end
    end

    return self.boradList, self.fixedRandomGrid, self.mysteryBoxIndexMap
end

function BattleBaseController:getNotInvolvedSkills()
    if not self.notInvolvedSkills then
        self.notInvolvedSkills = {}
        local config = self:getChapterConfig()[self.chapterId]
        if config.not_involved_skill then
            for _, skillId in ipairs(config.not_involved_skill) do
                self.notInvolvedSkills[skillId] = true
            end
        end
    end

    return self.notInvolvedSkills
end

function BattleBaseController:getFixedRogueSkill()
    if not self.fixedRogueSkill then
        self.fixedRogueSkill = {}
    end

    return self.fixedRogueSkill
end

function BattleBaseController:getSealElementType()
    if not self.sealElementType then
        self.sealElementType = {}
        local config = self:getChapterConfig()[self.chapterId]
        if config.seal_element then
            for _, elementType in ipairs(config.seal_element) do
                self.sealElementType[elementType] = true
            end
        end
    end

    return self.sealElementType
end

function BattleBaseController:getAtkMinRow()
    return 0
end

-- *************各个子模块的战斗需要重写的方法 end*************

function BattleBaseController:handleBuffs(side)
    for _, buffInfo in ipairs(self:getBuffs()) do
        local buffEntity = buffInfo.buffEntity
        local buffTargetSide = buffEntity:getTargetSide()
        local target
        if buffTargetSide == BattleConst.SIDE_DEF then
            target = self.defTeam:getMainUnit()
        elseif buffTargetSide == BattleConst.SIDE_ATK then
            target = self.atkTeam:getMainUnit()
        elseif buffTargetSide == BattleConst.SIDE_ATK_ALL then
            if not side or side == buffTargetSide then
                for matchtype, comp in pairs(self.atkTeam:getUnitComp()) do
                    local buffEntity = BattleBuffEntity:create()
                    buffEntity:init(buffInfo.effect, comp.unitEntity)
                    buffEntity:setTargetSide(buffTargetSide)
                    comp:takeEffect(buffEntity, comp)
                end
            end
        else
            local matchType = BattleConst.SIDE_OBJ_TO_MATCH_TYPE[buffTargetSide]
            if matchType then
                target = self.atkTeam:getUnitComp()[matchType]
            end
        end

        if target then
            if side then
                if buffTargetSide == side then
                    buffEntity:setOwner(target)
                    target:takeEffect(buffEntity, target)
                end
            else
                buffEntity:setOwner(target)
                target:takeEffect(buffEntity, target)
            end
        end
    end
end

function BattleBaseController:ctor()
    self.battleData = DataManager.BattleData
end

function BattleBaseController:init(params, snapshot)
    params = params or {}
    self.snapshot = snapshot
    self.params = params
    self.battleType = params.battleType or GConst.BattleConst.BATTLE_TYPE.STAGE
    self.waveDurationTime = 0
    self.totalDurationTime = 0
    self.eliminateCount = 0
    self.eliminateTotalCount = 0
    self.maxLinkCount = 0
    self.realTime = 0
    self.taskProgress = {}
    self.waveRoundCount = {}
    self.lastRoundBreakedGridType = {}
    self.waitingFillCount = 0
    self.needWaitingBoardOver = nil
    self.curWaveMonsterDead = false
    self.totalBreakedGridType = {}
    self.totalEliminateCountMap = {}

    self.curActionSide = SIDE_ATK
    self:resetSideActionCount()
    self.chapterId = self:getChapterId()
    self.waveIndex = 0
    self.maxWaveIndex = self:getMaxWave()
    self.victory = false
    self.roundStep = BattleConst.BATTLE_ROUND_STEP.WAIT_BEGIN
    self.effectTexts = {}
    self.instructions = {}
    self.gotMysteryBoxIndexs = {}
    self.showMysteryBoxIndexs = {}
    self.delayEffectTextList = {}
	self.delayEffectTextCount = 0
    self.time = 0
    self.battleData:setTimeScaleBase(nil) -- 还原
    if snapshot then -- 处理战斗快照
        self:dealSnapshotBattleBaseInfo(snapshot)
        self.battleData:init(params, snapshot.battledataShopInfo)
    else
        self.battleData:init(params)
    end
    BattleScheduler:init()
    BattlePool:init()
    BattleHelper:init()
    BattlePassive:init()
    self:setTimeScale(self.battleData:getTimeScale())
    self:bindData()
    self:initBattleTeam()
    self:initOther()
    self:prepareFight()
end

function BattleBaseController:dealSnapshotBattleExtraInfo(snapshot)
    BATTLE_SNAPSHOT_HELPER:dealSnapshotBattleExtraInfo(self, snapshot)
end

function BattleBaseController:dealSnapshotBattleBaseInfo(snapInfo)
    BATTLE_SNAPSHOT_HELPER:dealSnapshotBattleBaseInfo(self, snapInfo)
end

function BattleBaseController:snapshotBattleInfo()
    BATTLE_SNAPSHOT_HELPER:snapshotBattleInfo(self)
end

function BattleBaseController:bindData()
    self.battleData:bind("timeSpeed", ModuleManager.BattleManager, function()
		self:setTimeScale( self.battleData:getTimeScale())
	end, false)
end

function BattleBaseController:unBindAll()
    self.battleData:unBind("timeSpeed", ModuleManager.BattleManager)
end

function BattleBaseController:initBattleTeam()
    self.atkTeam = BattleTeam:create()
    self.atkTeam:init(BattleConst.SIDE_ATK, self)
    self.defTeam = BattleTeam:create()
    self.defTeam:init(BattleConst.SIDE_DEF, self)
end

function BattleBaseController:resetSideActionCount()
    if not self.sideActionCount then
        self.sideActionCount = {
            [SIDE_ATK] = 1,
            [SIDE_DEF] = 1,
        }
    else
        self.sideActionCount[SIDE_ATK] = 1
        self.sideActionCount[SIDE_DEF] = 1
    end
end

function BattleBaseController:resduceSideActionCount(side)
    if not self.sideActionCount or not self.sideActionCount[side] then
        return false
    end
    if self.sideActionCount[side] > 0 then
        self.sideActionCount[side] = self.sideActionCount[side] - 1
        return true
    else
        return false
    end
end

function BattleBaseController:getSideActionCount(side)
    if not self.sideActionCount or not self.sideActionCount[side] then
        return 0
    end
    return self.sideActionCount[side]
end

function BattleBaseController:getControllerParams()
    return self.params
end

function BattleBaseController:setCurActionSide(side)
    self.curActionSide = side or SIDE_ATK
end

function BattleBaseController:setIsPauseHpProgress(value)
    self.battleUI:setIsPauseHpProgress(value)
end

function BattleBaseController:getIsPvpBattle()
    return BattleConst.IS_PVP_BATTLE[self.battleType] == true
end

function BattleBaseController:moveBattlefield(time)
    self.battleUI:moveBattlefield(time)
end

function BattleBaseController:refreshBuff(side, buffList)
    self.battleUI:refreshBuff(side, buffList)
end

function BattleBaseController:refreshBossSkill(unitEntity)
	local skillList = unitEntity:getSkillList()
	self.battleUI:refreshBossSkill(skillList)
end

function BattleBaseController:clearBossSkill()
	self.battleUI:clearBossSkill()
end

function BattleBaseController:clearBuff(side)
    self.battleUI:clearBuff(side)
end

function BattleBaseController:showBuffTips(side, autoClose)
    if side == BattleConst.SIDE_ATK then
        local buffList = self.atkTeam:getBuffList()
        if #buffList <= 0 then
            return
        end
        self.battleUI:showLeftBuffTips(buffList, autoClose)
    else
        local buffList = self.defTeam:getBuffList()
        if #buffList <= 0 then
            return
        end
        self.battleUI:showRightBuffTips(buffList, autoClose)
    end
end

function BattleBaseController:showCombo(count, side)
    self.battleUI:showCombo(count, side)
end

function BattleBaseController:hideCombo()
    self.battleUI:hideCombo()
end

function BattleBaseController:showCounterAttack(count, side)
    self.battleUI:showCounterAttack(count, side)
end

function BattleBaseController:hideCounterAttack()
    self.battleUI:hideCounterAttack()
end

function BattleBaseController:prepareFight()
    local count = 0
	local totalCount = 3
	local function onPreloadFinished()
		count = count + 1
		if count == totalCount then
			self:onLoadComplete()
		end
	end
    UIManager:closeAllUI()
    self.battleUI = UIManager:showUI(self:getBattleUIPath(), {battleController = self})
	self.battleUI:addLoadUICompleteListener(function()
        BattleHelper:setEffectTextCache(self.battleUI:getBattleNumberRed(),
                                        self.battleUI:getBattleNumberGreen(),
                                        self.battleUI:getBattleNumberBlue(),
                                        self.battleUI:getBattleNumberWhite(),
                                        self.battleUI:getBattleNumberSpecial())
        self:initAtkUnits(onPreloadFinished)
        self:initDefUnits(onPreloadFinished)
        self.battleUI:refreshChessBoard(self:getChessBoardBgName())
        self.battleUI:loadBg(self:getScene())
	end)
    BattleHelper:setBaseOrder(self.battleUI:getUIOrder())
    BattleHelper:setBattleData(self.battleData)
    BattleBoardTouchHelper:init(self)
    self:loadOtherRes(onPreloadFinished)
end

function BattleBaseController:initAtkUnits(callback)
    local atkTeamEntity = self.battleData:getAtkTeam()
    local count = 0
    local totalCount = atkTeamEntity:getMembersCount()
    local function onloadFinished()
		count = count + 1
		if count == totalCount then
            self.battleUI:refreshAtkHp(atkTeamEntity:getHp(), atkTeamEntity:getHpPercent())
			callback()
		end
	end
    local members = atkTeamEntity:getAllMembers()
    for k, v in pairs(members) do
        local modelId = v:getModelId()
        BattleHelper:loadBattleHeroModel(modelId, self.battleUI:getBattleNode(), function(spineObject)
			local heroComp = spineObject:addLuaComponent(BattleConst.TYPEOF_LUA_COMP.BATTLE_HERO_COMPONENT)
			heroComp:initWithEntity(modelId, v, self)
            if v:getIsMainUnit() then
                self.atkTeam:addUnit(heroComp, true)
            else
                self.atkTeam:addUnit(heroComp)
                heroComp:hideOutsideScreen()
            end
			onloadFinished()
		end)
    end
    if totalCount == 0 then
        callback()
    end
end

function BattleBaseController:initDefUnits(callback)
    local initIndex = self:getWaveIndex()
    if initIndex <= 0 then
        initIndex = 1
    end
    local monsterId = self:getNextMonsterId(initIndex)
    local unitEntity = self.battleData:addMonster(monsterId, nil, self)
    local modelId = unitEntity:getModelId()
    BattleHelper:loadBattleHeroModel(modelId, self.battleUI:getBattleNode(), function(spineObject)
        local monsterComp = spineObject:addLuaComponent(BattleConst.TYPEOF_LUA_COMP.BATTLE_MONSTER_COMPONENT)
        monsterComp:initWithEntity(modelId, unitEntity, self)
        self.defTeam:addUnit(monsterComp, true)
        self.battleUI:refreshDefHp(unitEntity:getHp(), unitEntity:getHpPercent())
		self:refreshBossSkill(unitEntity)
        callback()
    end)
end

function BattleBaseController:refreshHp(side, num, percent)
    if side == BattleConst.SIDE_ATK then
        self.battleUI:refreshAtkHp(num, percent)
    else
        self.battleUI:refreshDefHp(num, percent)
    end
end

function BattleBaseController:getOtherSideMainUnit(side)
    if side == BattleConst.SIDE_ATK then
        return self.defTeam:getMainUnit()
    else
        return self.atkTeam:getMainUnit()
    end
end

function BattleBaseController:getOtherSideTeam(side)
    if side == BattleConst.SIDE_ATK then
        return self.defTeam
    else
        return self.atkTeam
    end
end

function BattleBaseController:getSideTeam(side)
    if side == BattleConst.SIDE_ATK then
        return self.atkTeam
    else
        return self.defTeam
    end
end

function BattleBaseController:getCurActionTeam(side)
    side = side or self.curActionSide
    local isAtkAction = side == SIDE_ATK
    local team = isAtkAction and self.atkTeam or self.defTeam
    return team
end

function BattleBaseController:getCurActionSide()
    return self.curActionSide
end

function BattleBaseController:getCurActionOtherSide()
    if self.curActionSide == SIDE_ATK then
        return SIDE_DEF
    else
        return SIDE_ATK
    end
end

function BattleBaseController:getCurActionUnitComp()
    return self:getCurActionTeam():getMainUnit()
end

function BattleBaseController:getCurOtherActionUnitComp()
    local side = self:getCurActionOtherSide()
    if not side then
        return
    end
    local team = self:getCurActionTeam(side)
    if not team then
        return
    end
    return team:getMainUnit()
end

function BattleBaseController:getCurActionBoardRowRange(side)
    side = side or self.curActionSide
    local isAtkAction = side == SIDE_ATK
    if isAtkAction then
        return self:getAtkMinRow() + 1, self:getRowCount()
    else
        return 1, self:getAtkMinRow()
    end
end

function BattleBaseController:getPosIdInCurActionBoardRowRange(posId)
    local min, max = self:getCurActionBoardRowRange()
    local r = ModuleManager.BattleManager:getPosRC(posId).r
    if r < min or r > max then
        return false
    end

    return true
end

function BattleBaseController:getPosIdInActionBoardRowRange(side, posId)
    local min, max = self:getCurActionBoardRowRange(side)
    local r = ModuleManager.BattleManager:getPosRC(posId).r
    if r < min or r > max then
        return false
    end

    return true
end

function BattleBaseController:onLoadComplete()
    UIManager:closeLoading()
    self:handleBuffs()
    if self.snapshot then
        self:dealSnapshotBattleExtraInfo(self.snapshot)
    end
    self:battleStart()
end

function BattleBaseController:battleStart()
    self.atkTeam:prepare()
    self.defTeam:prepare()
    self.isBattleStart = true
	self.tickSid = BattleScheduler:scheduleGlobal(function(dt, originDt)
		self:_tick(dt, originDt)
	end, 0)

    if self.snapshot then
        self:enterNextWaveBySnapshot(self.snapshot)
    else
        self:enterNextWave()
    end
end

function BattleBaseController:addTimeSpeed()
    if self.skillSlowDownDuration then
        return
    end
    self.battleData:addTimeSpeed()
end

function BattleBaseController:resetTimeSpeed(isDeadReset)
    if isDeadReset then
        if self.skillSlowDownDuration then
            return
        end
    end

    self.battleData:resetTimeSpeed()
    if self.skillSlowDownDuration then
        self.skillSlowDownDuration = nil
    end
end

function BattleBaseController:setSkillSlowDown(timeScale, duration)
    self.skillSlowDownDuration = duration
    self.battleData:setSkillTimeSpeed(timeScale)
end

function BattleBaseController:setTimeScale(timeScale)
	GFunc.setDOTweenTimeScale(GConst.DOTWEEN_IDS.BATTLE, timeScale)
	BattleScheduler:setTimeScale(timeScale)
	BattleHelper:setTimeScale(timeScale)
end

function BattleBaseController:addWaveIndex(wave)
    local origin = self:getWaveIndex()
    self.waveIndex = GFunc.encryptNumber(origin + wave)
end

function BattleBaseController:setWaveIndex(wave)
    self.waveIndex = GFunc.encryptNumber(wave)
end

function BattleBaseController:getWaveIndex()
    if self.waveIndex <= 0 then
        return 0
    end
    return GFunc.decryptNumber(self.waveIndex)
end

function BattleBaseController:_findNextDefUnit()
    if self.isBossWave then -- 如果上一波是boss波次，则重新生成棋盘
        self:putBoardCacheSkill(function()
            self.atkTeam:onRoundEnd()
            self:findNextDefUnit()
            self:generateBoard()
        end)
    else
        self.atkTeam:onRoundEnd()
        self:findNextDefUnit()
    end
end

---- start 回合步骤
function BattleBaseController:enterNextWave()
    local waveIndex = self:getWaveIndex()
    if waveIndex ~= 0 and self.curWaveMonsterDead then
        if self.isBossWave then
            self:addTaskProgress(BattleConst.BATTLE_TASK_FIELD.KILL_BOSS, 1)
        else
            self:addTaskProgress(BattleConst.BATTLE_TASK_FIELD.KILL_NORMAL_MONSTER, 1)
        end
        self:setTaskProgress(BattleConst.BATTLE_TASK_FIELD.PASS_WAVE, waveIndex)
    end

    if waveIndex >= self.maxWaveIndex then
        self.victory = self.curWaveMonsterDead
        self:postWaveOver(not self.curWaveMonsterDead)
        self:battleEnd()
        return
    end

    local atkTeam = self.battleData:getAtkTeam()
    if not atkTeam or atkTeam:getIsDead() then
        self.victory = false
        self:postWaveOver(true)
        self:battleEnd()
        return
    end

    if waveIndex ~= 0 then -- 第一波
        self:postWaveOver(false)
    end

    self.curWaveMonsterDead = false
    self:addWaveIndex(1)
    waveIndex = self:getWaveIndex()
    self:refreshWave(waveIndex)
    if waveIndex == 1 then -- 第一波
        self.needWaitingBoardOver = true
        self:generateBoard(true)
    else
        -- 上报关卡结束
        self.defTeam:prepare()
    end

    self.waveDurationTime = 0
    self.eliminateCount = 0

    self:snapshotBattleInfo()

    self.isBossWave = self.defTeam:getMainUnit().unitEntity:getIsBoss()
    self:postFightStart()
    if not self.needWaitingBoardOver then
        self:enterRoundBegin()
    end
    if not self.isBossWave then
        self.battleUI:hideBuffTips()
    end
end

function BattleBaseController:enterNextWaveBySnapshot(snapShot)
    local atkTeam = self.battleData:getAtkTeam()
    if not atkTeam or atkTeam:getIsDead() then
        self.victory = false
        self:battleEnd()
        return
    end

    self.curWaveMonsterDead = false
    self:refreshWave(self:getWaveIndex())
    self.needWaitingBoardOver = true
    self:generateBoard(true, snapShot)

    self.waveDurationTime = 0
    self.eliminateCount = 0

    self:snapshotBattleInfo()

    self.isBossWave = self.defTeam:getMainUnit().unitEntity:getIsBoss()
    self:postFightStart()
    if not self.needWaitingBoardOver then
        self:enterRoundBegin()
    end
    if not self.isBossWave then
        self.battleUI:hideBuffTips()
    end
end

function BattleBaseController:enterRoundBegin()
    self.battleData:clearBreakGridEdgeTag()
    self:hideTouchCancel()
    self:resetSideActionCount()
    self:setCurActionSide(SIDE_ATK)
    self.needWaitingBoardOver = nil
    local waveIndex = self:getWaveIndex()
    self.waveRoundCount[waveIndex] = (self.waveRoundCount[waveIndex] or 0) + 1
    self.battleUI:enterShowBoardAni(function()
        self:enterEliminationBegin()
    end)
    table.clear(self.lastRoundBreakedGridType)
end

function BattleBaseController:takeGridEffect()
    local haveGridEffectSucc = false
    local gridEntities = self.battleData:getGridEnties()
    local effectGrid = {}
    for posId, entity in pairs(gridEntities) do
        if entity:getEffectType() then
            table.insert(effectGrid, entity)
        end
    end

    effectGrid = table.shuffle(effectGrid)
    local availableEffectTypeMap
    for _, entity in ipairs(effectGrid) do
        local effectType = entity:getEffectType()
        if effectType ~= BattleConst.GRID_EFFECT_TYPE.CROSS_MOVE_NOT_BREAK and (not availableEffectTypeMap or not availableEffectTypeMap[effectType]) then
            local succ = BATTLE_GRID_EFFECT_HANDLE.gridEffectOn(entity:getPosId(), gridEntities, BattleConst.GRID_EFFECT_TRIGGER_TYPE.ON_ROUND_BEGIN, self)
            if succ and (effectType == BattleConst.GRID_EFFECT_TYPE.CROSS_SPREAD or
                        effectType == BattleConst.GRID_EFFECT_TYPE.CROSS_SPREAD_NOT_BREAK) then
                if not availableEffectTypeMap then
                    availableEffectTypeMap = {}
                end
                availableEffectTypeMap[effectType] = true
                haveGridEffectSucc = succ
            end
        end
    end

    for _, entity in ipairs(effectGrid) do -- gridEffect == 6的最后处理
        local effectType = entity:getEffectType()
        if effectType == BattleConst.GRID_EFFECT_TYPE.CROSS_MOVE_NOT_BREAK and (not availableEffectTypeMap or not availableEffectTypeMap[effectType]) then
            local succ = BATTLE_GRID_EFFECT_HANDLE.gridEffectOn(entity:getPosId(), gridEntities, BattleConst.GRID_EFFECT_TRIGGER_TYPE.ON_ROUND_BEGIN, self)
            if succ then
                if not availableEffectTypeMap then
                    availableEffectTypeMap = {}
                end
                availableEffectTypeMap[effectType] = true
                haveGridEffectSucc = succ
            end
        end
    end

    return haveGridEffectSucc
end

function BattleBaseController:enterEliminationBegin()
    self.roundStep = BattleConst.BATTLE_ROUND_STEP.ON_BEGIN
    self:enterRefreshBoard(true, function()
        local haveGridEffectSucc = self:takeGridEffect()
        if haveGridEffectSucc then -- 如果成功，则等待效果播放完成后，进入消除逻辑
            if self.haveGridEffectSuccSid then
                ModuleManager.BattleManager:unscheduleGlobal(self.haveGridEffectSuccSid)
                self.haveGridEffectSuccSid = nil
            end
            ModuleManager.BattleManager:performWithDelayGlobal(function()
                self:enterElimination(true)
            end, 1.1)
        else
            self:enterElimination(true)
        end
    end)
end

function BattleBaseController:enterElimination(needDelay)
    if self.haveGridEffectSuccSid then
        ModuleManager.BattleManager:unscheduleGlobal(self.haveGridEffectSuccSid)
        self.haveGridEffectSuccSid = nil
    end

    if self.showSelectSkillSid then
        ModuleManager.BattleManager:unscheduleGlobal(self.showSelectSkillSid)
        self.showSelectSkillSid = nil
    end

    if self.battleData:useAddlvCount() then
        self:tryShowSelectSkillComp(needDelay)
        return
    end

    self.battleUI:hideAllBoardSfxs()

    -- 检查棋盘
    local find, pathList = self:findAttention()
    if not find then -- 如果没找到，就要打乱棋盘
        self:shuffleBoard(function()
            self.roundStep = BattleConst.BATTLE_ROUND_STEP.ON_ELIMINATION
        end)
    else
        self.attentionList = pathList
        -- ModuleManager.BattleManager:performWithDelayGlobal(function()
        --     for _, posId in ipairs(pathList) do
        --         local entity = self.battleData:getGridEntity(posId)
        --         if entity and entity:getCell() then
        --             entity:getCell():showAni()
        --         end
        --     end
        -- end, 2)
        self.roundStep = BattleConst.BATTLE_ROUND_STEP.ON_ELIMINATION
    end
end

function BattleBaseController:enterBattleStep()
    self.roundStep = BattleConst.BATTLE_ROUND_STEP.ON_TEAM_ACTION
    self.battleTeamActionList = table.clearOrCreate(self.battleTeamActionList)

    local atkAction = function()
        self.roundStep = BattleConst.BATTLE_ROUND_STEP.ON_ATK_STEP
        self:exeInstructions(function()
            self:enterNextTeamAction()
        end, self.curActionSide)
    end

    local defAction = function()
        self.roundStep = BattleConst.BATTLE_ROUND_STEP.ON_DEF_STEP
        self.defTeam:mainUnitUseAllSkills(BattleConst.ATTACK_ACTION_STATE.NORMAL, function()
            self:enterNextTeamAction()
        end)
    end

    if self.battleData:getAtkTeam():getFirstHand() < self.battleData:getDefTeam():getFirstHand() then
        self:addTeamActionList(defAction, 1)
        self:addTeamActionList(atkAction, 2)
    else
        self:addTeamActionList(atkAction, 1)
        self:addTeamActionList(defAction, 2)
    end

    self:enterNextTeamAction()
end


function BattleBaseController:addTeamActionList(func, index)
    if not self.battleTeamActionList then
        self.battleTeamActionList = {}
    end

    index = index or 1
    table.insert(self.battleTeamActionList, index, func)
end

function BattleBaseController:enterNextTeamAction()
    self.atkTeam:onActionOver()
    self.defTeam:onActionOver()

    self.roundStep = BattleConst.BATTLE_ROUND_STEP.ON_TEAM_ACTION_OVER
    self:hideCombo()
    self:hideCounterAttack()

    self:checkTeamIsDead(function() self:enterRoundEnd() end, function()
        if not self.battleTeamActionList or not self.battleTeamActionList[1] then
            self:enterRoundEnd()
            return
        end
    
        local action = table.remove(self.battleTeamActionList, 1)
        action()
    end)
end

function BattleBaseController:checkTeamIsDead(callback, falseCallback)
    local defTeam = self.battleData:getDefTeam()
    local atkTeam = self.battleData:getAtkTeam()
    if atkTeam:getIsDead() and defTeam:getIsDead() then -- 都死了，直接结算
        self.curWaveMonsterDead = true
        self:enterNextWave()
        return true
    end

    if not defTeam or defTeam:getIsDead() then -- 怪物死了， 直接进入刷新逻辑
        self.curWaveMonsterDead = true
        self:onDefDead(function()
            self.defTeam:removeAllBuff()
            if self.battleData:getDefTeam():getIsDead() then
                if self:getWaveIndex() >= self.maxWaveIndex then
                    if callback() then
                        callback()
                    end
                else
                    self:_findNextDefUnit()
                end
            else
                if callback() then
                    callback()
                end
            end
        end)
        return true
    end

    if not atkTeam or atkTeam:getIsDead() then -- 英雄死了， 直接结算
        self:enterNextWave()
        return true
    end

    if falseCallback then
        falseCallback()
    end
end

function BattleBaseController:getIsAtkStep()
    return self.roundStep == BattleConst.BATTLE_ROUND_STEP.ON_ATK_STEP
end

function BattleBaseController:enterRefreshBoard(isRoundBeginCheck, callback)
    if not isRoundBeginCheck then
        self.roundStep = BattleConst.BATTLE_ROUND_STEP.ON_REFRESH_BOARD
    end
    self.onFillBoardOverCallback = callback
    self.isRoundBeginCheck = isRoundBeginCheck
    if self.isRoundBeginCheck and self.waitingFillCount > 0 then
        self.isWaitingFill = true
        return
    end

    self:fillBoard(self.isRoundBeginCheck)
end

function BattleBaseController:enterRoundEnd()
    self.roundStep = BattleConst.BATTLE_ROUND_STEP.ON_END
    local defTeam = self.battleData:getDefTeam()
    if not defTeam or defTeam:getIsDead() then -- 怪物死了， 直接进入刷新逻辑
        self.curWaveMonsterDead = true
        self.atkTeam:onRoundEnd()
        self:enterNextWave()
        return
    end

    local atkTeam = self.battleData:getAtkTeam()
    if not atkTeam or atkTeam:getIsDead() then -- 英雄死了， 直接结算
        self.defTeam:onRoundEnd()
        self:enterNextWave()
        return
    end

    self.atkTeam:onRoundEnd()
    self.defTeam:onRoundEnd()

    self:checkTeamIsDead(function() self:enterNextWave() end, function()
        self:onRoundEnd()
    end)
end

function BattleBaseController:onRoundEnd(toNextWave)
    if toNextWave then
        self:enterNextWave()
    else
        self:enterRoundBegin()
    end
end

---- end回合步骤
function BattleBaseController:onTouchEvent(eventType, posId, isVirtual)
    BattleBoardTouchHelper:onTouchEvent(eventType, posId, isVirtual)
end

function BattleBaseController:showTouchCancel()
    self.battleUI:showTouchCancel()
end

function BattleBaseController:hideTouchCancel()
    self.battleUI:hideTouchCancel()
end

function BattleBaseController:clearGridSequence()
    self:hideTouchCancel()
    local sequence = self.battleData:getGridSequence()
    local count = #sequence
    if count <= 0 then
        self.battleData:clearGridSequence()
        self:onLinkChange()
        return
    end

    local snapshot = self.battleData:getFirstSequenceSnapshot()
    if snapshot then -- 如果有快照，则恢复一次
        for posId, info in pairs(snapshot) do
            self.battleData:setInfoBySnapshop(posId, info)
        end
    end

    self.battleData:clearGridSequence()
    self:onLinkChange()
end

function BattleBaseController:onLinkOver()
    local hp = self.atkTeam:getMainUnit().unitEntity:getHp()
    local hpPercent = self.atkTeam:getMainUnit().unitEntity:getHpPercent()
    self.battleUI:setAtkHp(hp, hpPercent)

    local hp = self.defTeam:getMainUnit().unitEntity:getHp()
    local hpPercent = self.defTeam:getMainUnit().unitEntity:getHpPercent()
    self.battleUI:setDefHp(hp, hpPercent)

    self.battleUI:hideAllSfxLine()
    local sequence = self.battleData:getGridSequence()
    local count = #sequence
    if count < self:getMinEliminationCount() then
        return
    end

    local isAtkAction = self.curActionSide == SIDE_ATK
    local skillId = self.battleData:getSequenceHadSkill()
    local skillEntity
    if skillId then
        skillEntity = self:getSkillEntityBySkillId(skillId)
        if isAtkAction then
            self:addTaskProgress(BattleConst.BATTLE_TASK_FIELD.BOARD_SKILL_RELEASE_COUNT, 1)
        end
    end

    self.teampSkillBreakEnergyMap = table.clearOrCreate(self.teampSkillBreakEnergyMap)
    if skillEntity then
        for posId, info in pairs(self.battleData:getSkillInfluenceGrids()) do
            local entity = self.battleData:getGridEntity(posId)
            if entity then
                local multiple = skillEntity and skillEntity:getSkillBreakEnergyMultiple() or 1
                if multiple > 1 then
                    entity:getCell():setCountStr(skillEntity:getSkillBreakEnergyMultipleStr())
                    local elementType = entity:getElementType()
                    if entity:canLink() and elementType and elementType > 0 then
                        self.teampSkillBreakEnergyMap[elementType] = (self.teampSkillBreakEnergyMap[elementType] or 0) + (multiple - 1)
                    end
                end
            end
        end
    end

    local aniSequence, influenceElementTypeMap, lineCount, elementTypeMap, linkElementType, effectGridMap = self:calculateCurElimination()

    if not self:getCurActionUnitComp():getActiveSkillLimit() then
        for elementType, count in pairs(elementTypeMap) do
            self.teampSkillBreakEnergyMap[elementType] = (self.teampSkillBreakEnergyMap[elementType] or 0) + count
        end
        self:addSkillEnergy(self.teampSkillBreakEnergyMap)
    end
    self.battleData:clearGridSequence()
    self.battleUI:disableUITouch()
    self.battleUI:eliminationAni(aniSequence, effectGridMap, function()
        self:enterRefreshBoard(nil, function()
            self:onLinkOverDone(skillEntity, linkElementType, lineCount, influenceElementTypeMap, elementTypeMap)
        end)
    end, self.curActionSide)

    for element, count in pairs(elementTypeMap) do
        self.totalEliminateCountMap[element] = (self.totalEliminateCountMap[element] or 0) + count
    end

    self.eliminateCount = self.eliminateCount + 1
    self.eliminateTotalCount = self.eliminateTotalCount + 1
    if count > self.maxLinkCount then
        self.maxLinkCount = count
    end
end

function BattleBaseController:onLinkOverDone(skillEntity, linkElementType, lineCount, influenceElementTypeMap, elementTypeMap)
    self.battleUI:enterHideBoardAni(function()
        self:generateInstructions(skillEntity, linkElementType, lineCount, influenceElementTypeMap, elementTypeMap)
        self:enterBattleStep()
    end)
end

function BattleBaseController:calculateCurElimination(onlyCheck)
    local sequence = self.battleData:getGridSequence()
    local skillId = self.battleData:getSequenceHadSkill()
    local skillEntity
    if skillId then
        skillEntity = self:getSkillEntityBySkillId(skillId)
    end
    self.breakedMap = table.clearOrCreate(self.breakedMap)
    self.boomGridIds = table.clearOrCreate(self.boomGridIds)
    self.sequenceMap = table.clearOrCreate(self.sequenceMap)
    self.aniSequence = table.clearOrCreate(self.aniSequence)
    for idx, info in ipairs(sequence) do
        self.sequenceMap[info.posId] = idx
    end

    local time = 0
    local skillTime = BattleConst.SKILL_ELIMINATION_TIME
    for idx, info in ipairs(sequence) do
        local posId = info.posId
        self:dealGridBreak(posId, GRID_BREAK_CONDITION.LINE, time, self.breakedMap, self.sequenceMap, self.aniSequence, self.boomGridIds, onlyCheck)
        time = time + BattleConst.ELIMINATION_INTERVAL
    end

    local randomPosList, influenceElementTypeMap
    for i, info in ipairs(self.aniSequence) do
        if info.isSkill and skillEntity then
            randomPosList, influenceElementTypeMap = self:dealSkillElement(info.timeIdx + skillTime, self.breakedMap, self.sequenceMap, self.aniSequence, self.boomGridIds, onlyCheck)
            local aniUnit = self.aniSequence[i]
            if not BattleConst.NO_EFFECT_GRID_SKILL_TYPE[skillEntity:getSkillType()] then
                aniUnit.rangeList = skillEntity:getBoardRange()
                aniUnit.randomPosList = randomPosList
            end
            break
        end
    end

    self.elementTypeMap = table.clearOrCreate(self.elementTypeMap)
    self.effectGridMap = table.clearOrCreate(self.effectGridMap)
    local linkElementType
    local lineCount = 0
    local gridEntities = self.battleData:getGridEnties()
    for _, info in ipairs(self.aniSequence) do
        local entity = gridEntities[info.posId]
        local elementType = entity:getElementType()
        if not info.noAni and info.isIdle then
            if not info.isSkill then
                if not entity:isElmentTypeInvalid() then
                    self.elementTypeMap[elementType] = (self.elementTypeMap[elementType] or 0) + 1
                end
                if self.boomGridIds[info.posId] and self.boomGridIds[info.posId][GRID_BREAK_CONDITION.LINE] then
                    lineCount = lineCount + 1
                    linkElementType = elementType
                end
            end
        end
        if entity:getEffectType() and info.isIdle then
            self.effectGridMap[info.posId] = true
        end
    end

    return self.aniSequence, influenceElementTypeMap, lineCount, self.elementTypeMap, linkElementType, self.effectGridMap, randomPosList
end

function BattleBaseController:dealGridBreak(posId, condition, time, breakedMap, sequenceMap, aniSequence, gridMap, onlyCheck)
    self:setGridBreakCondition(gridMap, posId, condition)
    if breakedMap[posId] or (sequenceMap[posId] and condition ~= GRID_BREAK_CONDITION.LINE) then
        return
    end

    local gridEntities = self.battleData:getGridEnties()
    local entity = gridEntities[posId]
    local breaked, isIdle = entity:tryBreakGrid(condition, true)
    local aniUnit = self:getEliminattionAniInfo(posId, time)
    aniUnit.noAni = entity:isElmentTypeInvalid()
    aniUnit.isIdle = isIdle
    if entity:getSkillId() then
        aniUnit.isSkill = true
    end

    if breaked or entity:canBreakByThisCondition(condition) then
        breakedMap[posId] = true

        aniUnit.breakSfxName = entity:getBreakSfx()
        aniUnit.callback = function()
            local gridType = entity:getGridType()
            self.lastRoundBreakedGridType[gridType] = true
            self.totalBreakedGridType[gridType] = (self.totalBreakedGridType[gridType] or 0) + 1
            entity:tryBreakGrid(condition)
            if entity:getIsIdle() then
                self:dealGridEdge(posId)
            end
        end

        table.insert(aniSequence, aniUnit)
    end

    if isIdle then
        if condition == GRID_BREAK_CONDITION.LINE then
            local outline = BattleConst.UP_DOWN_LEFT_RIGHT[posId]
            for _, id in ipairs(outline) do
                if self.battleData:getGridEntity(id) then
                    if not BOARD_HELER:hasGridEdgeBetween(self, posId, id) then
                        self:dealGridBreak(id, GRID_BREAK_CONDITION.AROUND, time, breakedMap, sequenceMap, aniSequence, gridMap, onlyCheck)
                    end
                end
            end
        end
        if entity:getEffectType() then
            local succ, list = BATTLE_GRID_EFFECT_HANDLE.gridEffectOn(posId, gridEntities, BattleConst.GRID_EFFECT_TRIGGER_TYPE.ON_GRID_BREAK, self, onlyCheck)
            if list then
                aniUnit.aniPosList = table.clearOrCreate(aniUnit.aniPosList)
                aniUnit.overCallback = aniUnit.callback
                aniUnit.callback = nil
                for index, id in ipairs(list) do
                    if id ~= posId then
                        local timeIdx = time + BattleConst.GRID_BREAK_EFFECT_INTERVAL * (index - 1) -- 因为第一个是自己，所以-1
                        self:dealGridBreak(id, GRID_BREAK_CONDITION.SKILL, timeIdx, breakedMap, sequenceMap, aniSequence, gridMap, onlyCheck)
                        table.insert(aniUnit.aniPosList, id)
                    end
                end

                ---- 技能效果特效
                aniUnit.effectSfxName = entity:getEffectSfx()
                aniUnit.effectSfxFlow = entity:getEffectSfxFlow()
                aniUnit.effectSfxDir = entity:getEffectParams()[1]
            end
        end
        aniUnit.bftcTime = entity:getBftcTime()
        if entity:getBreakFlyToCharacter() then
            aniUnit.breakFlyToCharacter = true
            aniUnit.noAni = false
            aniUnit.overCallback = aniUnit.callback
            aniUnit.callback = nil
            if entity:getBreakFlyToCharacterIcon() then
                aniUnit.callback = function()
                    local cell = entity:getCell()
                    if not cell then
                        return
                    end
                    cell:setGridTypeIcon(entity:getBreakFlyToCharacterIcon())
                    self:dealGridEdge(posId)
                end
            end
        end
    end

    return isIdle
end

function BattleBaseController:dealGridEdge(posId)
    -- 能进来一定已经可以消除了
    if not self.upDownLeftRightList then
        self.upDownLeftRightList = {BattleConst.BOARD_RANGE_TYPE.UP, BattleConst.BOARD_RANGE_TYPE.DOWN, BattleConst.BOARD_RANGE_TYPE.LEFT, BattleConst.BOARD_RANGE_TYPE.RIGHT}
        self.upDownLeftRightOppositeMap = {
            [BattleConst.BOARD_RANGE_TYPE.UP] = BattleConst.BOARD_RANGE_TYPE.DOWN,
            [BattleConst.BOARD_RANGE_TYPE.DOWN] = BattleConst.BOARD_RANGE_TYPE.UP,
            [BattleConst.BOARD_RANGE_TYPE.LEFT] = BattleConst.BOARD_RANGE_TYPE.RIGHT,
            [BattleConst.BOARD_RANGE_TYPE.RIGHT] = BattleConst.BOARD_RANGE_TYPE.LEFT
        }
    end

    for _, direction in ipairs(self.upDownLeftRightList) do
        -- 周围的
        local aroundId = ModuleManager.BattleManager:getPosByDirection(posId, direction, self.battleData:getRowCount())
        if aroundId then
            local breaked, breakSfx, breakCount, cell = self.battleData:breakGridEdge(aroundId, self.upDownLeftRightOppositeMap[direction])
            if breaked and breakSfx and breakCount and cell then
                if self.battleUI then
                    self.battleUI:getSfxGridEdgeBreak(breakSfx, breakCount, function(obj)
                        local posX, posY = cell:getBaseObject():fastGetAnchoredPosition()
                        obj:setLocalPosition(posX, posY, 0)
                        obj:play()
                    end)
                end
            end
        end

        -- 本身
        local breaked, breakSfx, breakCount, cell = self.battleData:breakGridEdge(posId, direction)
        if breaked and breakSfx and breakCount and cell then
            if self.battleUI then
                self.battleUI:getSfxGridEdgeBreak(breakSfx, breakCount, function(obj)
                    local posX, posY = cell:getBaseObject():fastGetAnchoredPosition()
                    obj:setLocalPosition(posX, posY, 0)
                    obj:play()
                end)
            end
        end
    end
end

function BattleBaseController:dealSkillElement(time, breakedMap, sequenceMap, aniSequence, boomGridIds, onlyCheck)
    local randomPosList
    local influenceElementTypeMap = {}
    for posId, info in pairs(self.battleData:getSkillInfluenceGrids()) do
        local entity = self.battleData:getGridEntity(posId)
        if not entity:getIsIdle() then
            if entity:isEmptyIdle() then
                if info.direction == BattleConst.BOARD_RANGE_TYPE.RANDOM then
                    if not randomPosList then
                        randomPosList = {}
                    end
                    table.insert(randomPosList, posId)
                end
                influenceElementTypeMap[entity:getElementType()] = (influenceElementTypeMap[entity:getElementType()] or 0) + 1
            end
            self:dealGridBreak(posId, GRID_BREAK_CONDITION.SKILL, time, breakedMap, sequenceMap, aniSequence, boomGridIds, onlyCheck)
        end
    end

    return randomPosList, influenceElementTypeMap
end

function BattleBaseController:getEliminattionAniInfo(posId, timeIdx)
    return {
        posId = posId,
        noAni = nil,
        aniPosList = nil,
        breakSfxName = nil,
        timeIdx = timeIdx,
        conditions = nil,
        isSkill = false,
        isIdle = false,
        callback = nil,
        overCallback = nil,
        bftcTime = 0,
        effectSfxName = nil,
        effectSfxFlow = nil,
        effectSfxDir = nil,
    }
end

function BattleBaseController:setGridBreakCondition(gridMap, posId, condition)
    if not gridMap[posId] then
        gridMap[posId] = {}
        gridMap[posId][condition] = true
    end
    gridMap[posId][condition] = true
end

function BattleBaseController:tryShowSelectSkillComp(needDelay, onlyCommonSkill)
    self.canChoseSkillCount = 1
    local skillList = self:getRandomSkillList(nil, onlyCommonSkill)
    if needDelay then
        self.showSelectSkillSid = ModuleManager.BattleManager:performWithDelayGlobal(function()
            self.battleUI:showSelectSkillComp(skillList, onlyCommonSkill)
            self.showSelectSkillSid = nil
        end, 0.3)
    else
        self.battleUI:showSelectSkillComp(skillList, onlyCommonSkill)
    end

    BIReport:postShowFightSkillSelect(self.battleType, self.battleData, skillList, self.chapterId, self.totalDurationTime, self:getWaveIndex())
end


function BattleBaseController:fillBoard(isRoundBeginCheck)
    local reverse = self.curActionSide == SIDE_DEF
    self.fillBoardPathMap = table.clearOrCreate(self.fillBoardPathMap)
    self.fillColumnCount = table.clearOrCreate(self.fillColumnCount)
    self.fillGridMap = table.clearOrCreate(self.fillGridMap)

    if reverse then
        for c = 1, BattleConst.COLUMN_COUNT do
            for r = 1, self:getRowCount() do
                local posId = ModuleManager.BattleManager:getPosId(r, c)
                local entity = self.battleData:getGridEntity(posId)
                if entity and entity:getIsIdle() then
                    self:fillThisPos(posId, self.fillColumnCount, self.fillGridMap, reverse)
                end
            end
        end
    else
        for c = 1, BattleConst.COLUMN_COUNT do
            for r = self:getRowCount(), 1, -1 do
                local posId = ModuleManager.BattleManager:getPosId(r, c)
                local entity = self.battleData:getGridEntity(posId)
                if entity and entity:getIsIdle() then
                    self:fillThisPos(posId, self.fillColumnCount, self.fillGridMap, reverse)
                end
            end
        end
    end

    while true do
        local find = false
        for c = 1, BattleConst.COLUMN_COUNT do
            local list = self.fillGridMap[c]
            if list and list[1] then -- 此列有需要填充的元素
                local entity = table.remove(list, 1)
                if entity then
                    self.battleData:setGridInfo(entity:getPosId(), self:getRandomGridInfo())
                end
                find = true
            end
        end

        if not find then
            break
        end
    end

    for posId, entity in pairs(self.battleData:getGridEnties()) do
        if #entity:getPath() > 0 then
            self.fillBoardPathMap[posId] = entity:getPath()
            entity:clearPath()
        end
    end

    self.battleUI:fallGrid(self.fillBoardPathMap, isRoundBeginCheck, function()
        self:onFillBoardOver(isRoundBeginCheck)
        self.isRoundBeginCheck = nil
        self.isWaitingFill = false
    end)
end

function BattleBaseController:onFillBoardOver(isRoundBeginCheck)
    self.battleUI:enableUITouch()

    if not isRoundBeginCheck then -- 检查一些表现
        if self.showMysteryBoxIndexs[1] then -- 检查宝箱
            local index = table.remove(self.showMysteryBoxIndexs, 1)
            local boardList, _, mysteryBoxIndexMap = self:getInitBoard()
            local rewards = mysteryBoxIndexMap[index]
            if rewards then
                ModuleManager.BattleManager:showBoxOpenUI(rewards,  function()
                    self:onFillBoardOver()
                end)
            else
                self:onFillBoardOver()
            end
            return
        end

        if self.battleData:useCommonSelectSkillCount() then -- 检查神灯
            self:tryShowSelectSkillComp(false, true)
            return
        end
    end

    if isRoundBeginCheck then
        EventManager:dispatchEvent(EventManager.CUSTOM_EVENT.BOARD_FILL_OVER)
        self:popBoardCacheSkill(function()
            self:generateSkill(function()
                if self.onFillBoardOverCallback then
                    local callback = self.onFillBoardOverCallback
                    self.onFillBoardOverCallback = nil
                    callback()
                else
                    self:enterElimination(true)
                end
            end)
        end)
    else
        self:generateSkill(function()
            self.battleUI:refreshSkill(nil, nil, self:getCurActionSide())
            if self.onFillBoardOverCallback then
                local callback = self.onFillBoardOverCallback
                self.onFillBoardOverCallback = nil
                callback()
            end
        end)
    end
end

function BattleBaseController:generateInstructions(skillEntity, elementType, lineCount, influenceElementTypeMap, elementTypeMap)
    self.instructions = BATTLE_INSTRUCTIONS_HELPER.generateInstructions(skillEntity, elementType, lineCount, influenceElementTypeMap, elementTypeMap, self)
end

function BattleBaseController:exeInstructions(callback, side)
    side = side or self:getCurActionSide()
    if not self.instructions or #self.instructions <= 0 then
        callback()
        return
    end
    local instruction = table.remove(self.instructions)
    local func = BattleBaseController._doInstruction[instruction.name]
    if func then
        func(self, side, instruction, function()
            self.battleData:resetTimeSpeed()
            self:exeInstructions(callback, side)
        end)
    else
        self:exeInstructions(callback, side)
    end
end

function BattleBaseController:getIsLastInstruction()
    if not self.instructions or not self.instructions[1] then
        return true
    end

    return false
end

function BattleBaseController:generateBoard(isFirst, snapshot)
    local boardList, _, mysteryBoxIndexMap = self:getInitBoard()
    if self.curBoardIndex and self.curBoardIndex >= #boardList then
        if isFirst then
            self.needWaitingBoardOver = false
        end
        return
    end

    if not self.battleUI then
        if isFirst then
            self.needWaitingBoardOver = false
        end
        return
    end

    self.curBoardIndex = (self.curBoardIndex or 0) + 1
    if snapshot and snapshot.curBoardIndex then
        self.curBoardIndex = snapshot.curBoardIndex
    end
    if not boardList[self.curBoardIndex] then -- 容错
        if isFirst then
            self.needWaitingBoardOver = false
        end
        return
    end
    local board = boardList[self.curBoardIndex].board
    local mysteryBoard = boardList[self.curBoardIndex].mysteryBoard
    if mysteryBoard and mysteryBoxIndexMap and mysteryBoxIndexMap[self.curBoardIndex] then
        board = mysteryBoard
    end
    local gridEdge = boardList[self.curBoardIndex].gridEdge
    self.battleUI:switchBoard(function()
        self.battleData:refreshBoard(board, gridEdge, self:getBlockIcon(), snapshot)
        self.battleUI:initGridCell(function()
            if isFirst then
                self.needWaitingBoardOver = false
            end
        end)
    end, function()
    end, isFirst)
end

function BattleBaseController:onGotMysteryBoxIndexs()
    self.gotMysteryBoxIndexs[self.curBoardIndex] = true
    table.insert(self.showMysteryBoxIndexs, self.curBoardIndex)
end


function BattleBaseController:putBoardCacheSkill(callback)
    if not self.battleUI then
        return
    end
    self.battleData:cacheBoardSkill()
    local skillList, skillCount = self.battleData:getCacheBoardSkill()
    self.battleUI:cacheSkillAni(skillList, false, callback)
end

function BattleBaseController:popBoardCacheSkill(callback)
    local skillList, skillCount = self.battleData:getCacheBoardSkill()
    self.battleData:clearCacheBoardSkill()
    if self.battleUI and skillCount > 0 then
        self.popSkillPosIdList = table.clearOrCreate(self.popSkillPosIdList)
        for posId, entity in pairs(self.battleData:getGridEnties()) do
            if entity:isEmptyIdle() and not entity:getIsIdle() then
                table.insert(self.popSkillPosIdList, posId)
            end
        end

        self.popSkillPosIdList = table.shuffle(self.popSkillPosIdList)
        self.popNewSkillId = table.clearOrCreate(self.popNewSkillId)
        for index, info in ipairs(skillList) do
            if self.popSkillPosIdList[1] then
                self.popNewSkillId[index] = info
                self.popNewSkillId[index].posId = table.remove(self.popSkillPosIdList)
                self:setGridSkillId(self.popNewSkillId[index].posId, info.skillId, true)
            else
                break
            end
        end

        self.battleUI:cacheSkillAni(self.popNewSkillId, true, function()
            for index, info in ipairs(self.popNewSkillId) do
                self.battleData:setGridDirty(info.posId)
            end

            if callback then
                callback()
            end
        end)
    else
        if callback then
            callback()
        end
    end
end

function BattleBaseController:generateSkill(callback)
    self.generateSkillMap = table.clearOrCreate(self.generateSkillMap)
    self.generateExcludeMap = table.clearOrCreate(self.generateExcludeMap)
    for _, skillEntity in pairs(self:getSkillEntities()) do
        if skillEntity:getEnergyEnough() then
            local list = self:getSkillElementList(skillEntity:getPosition(), 1, true, self.generateExcludeMap)
            if list[1] then
                self.generateSkillMap[skillEntity:getPosition()] =
                {
                    skillId = skillEntity:getSkillId(),
                    posId = list[1]
                }
                self.generateExcludeMap[list[1]] = true
            end
        end
    end

    if not self.battleUI then
        if callback then
            callback()
        end
        return
    end

    self.battleUI:generateSkillAni(self.generateSkillMap, function()
        for elementType, info in pairs(self.generateSkillMap) do
            self:setGridSkillId(info.posId, info.skillId)
        end

        if callback then
            callback()
        end
    end, self.curActionSide)
end

function BattleBaseController:setGridSkillId(posId, skillId, noDirty)
    local entity = self.battleData:getGridEntity(posId)
    if entity then
        entity:setSkilId(skillId, noDirty, self.curActionSide)
        local skillEntity = self:getSkillEntityBySkillId(skillId)
        if skillEntity and entity:getElementType() ~= skillEntity:getPosition() then
            entity:setElementType(skillEntity:getPosition(), noDirty)
        end
    end
end


function BattleBaseController:generateGridType(skillTypeParameter, monsterPos)
    if not skillTypeParameter then
        return
    end

    self.generateGridTypeList = table.clearOrCreate(self.generateGridTypeList)
    local count = 0
    for posId, entity in pairs(self.battleData:getGridEnties()) do
        if entity:isEmptyIdle() then
            table.insert(self.generateGridTypeList, entity)
            count = count + 1
        end
    end
    self.generateGridTypeList = table.shuffle(self.generateGridTypeList)
    if count > 0 then
        self.generateGridTypeMap = table.clearOrCreate(self.generateGridTypeMap)
        local minCount = math.min(skillTypeParameter[2], count)
        for i = minCount, 1, -1 do
            local entity = table.remove(self.generateGridTypeList, math.random(1, i))
            self.generateGridTypeMap[entity:getPosId()] = {
                gridType = skillTypeParameter[1],
                elementType = entity:getElementType()
            }
        end

        self.battleUI:showMonsterSkillAni(self.generateGridTypeMap, monsterPos, function()
            for posId, info in pairs(self.generateGridTypeMap) do
                self.battleData:setGridInfo(posId, info)
            end
        end)
    end
end


function BattleBaseController:lockElement(lcokElementType, isUnlock)
    if not self.lockElementElementTypeMap then
        self.lockElementElementTypeMap = table.clearOrCreate(self.lockElementElementTypeMap)
    else
        for k, v in pairs(self.lockElementElementTypeMap) do
            self.lockElementElementTypeMap[k] = table.clearOrCreate(self.lockElementElementTypeMap[k])
        end
    end
    if not self.lockElementMap then
        self.lockElementMap = table.clearOrCreate(self.lockElementMap)
    else
        for k, v in pairs(self.lockElementMap) do
            self.lockElementMap[k] = table.clearOrCreate(self.lockElementMap[k])
        end
    end
    self.lockElementList = table.clearOrCreate(self.lockElementList)
    local locked = false
    for posId, entity in pairs(self.battleData:getGridEnties()) do
        local elementType = entity:getElementType()
        if not isUnlock then
            if entity:isEmptyIdle() then
                local elementTypeMapUnit = self.lockElementElementTypeMap[elementType]
                if not elementTypeMapUnit or not elementTypeMapUnit[1] then
                    self.lockElementElementTypeMap[elementType] = table.clearOrCreate(self.lockElementElementTypeMap[elementType])
                    elementTypeMapUnit = self.lockElementElementTypeMap[elementType]
                    if not isUnlock then
                        table.insert(self.lockElementList, elementType)
                    end
                end

                table.insert(elementTypeMapUnit, posId)
            end
            if entity:isLock() then
                locked = true
            end
        else
            if entity:isLock() then
                if not self.lockElementMap[elementType] then
                    self.lockElementMap[elementType] = table.clearOrCreate(self.lockElementMap[elementType])
                end
                table.insert(self.lockElementMap[elementType], posId)
            end
        end
    end

    if isUnlock then
        self.battleData:clearAllCacheLockElement(lcokElementType, nil)
        for elementType, list in pairs(self.lockElementMap) do
            for _, posId in ipairs(list) do
                self.battleData:setGridType(posId, BattleConst.GRID_TYPE.EMPTY)
            end
        end
    else
        if locked then -- 锁定过就不新锁了
            return
        end
        local elementType = lcokElementType
        if elementType == BattleConst.ELEMENT_TYPE.NONE then
            if not self.lockElementList[1] then
                return
            end
            elementType = self.lockElementList[math.random(1, #self.lockElementList)]
        end
        self.battleData:cacheLockElement(elementType, true)
        local list = self.lockElementElementTypeMap[elementType]
        if list then
            for _, posId in ipairs(list) do
                self.battleData:setGridType(posId, BattleConst.GRID_TYPE.LOCK)
            end
        end
    end
end

function BattleBaseController:getSkillElementListRow()
    self.skillElementListRow = table.clearOrCreate(self.skillElementListRow)
    local min, max = self:getCurActionBoardRowRange()
    for row = min, max do
        table.insert(self.skillElementListRow, row)
    end
    return self.skillElementListRow
end

function BattleBaseController:getSkillElementList(elementType, count, useAlternate, excludeMap)
    self.getSkillElementListResult = table.clearOrCreate(self.getSkillElementListResult)
    local gridEntities = self.battleData:getGridEnties()
    if not gridEntities then
        return self.getSkillElementListResult
    end

    self.getSkillElementListSameElementList = table.clearOrCreate(self.getSkillElementListSameElementList)
    self.getSkillElementListAlternateList = table.clearOrCreate(self.getSkillElementListAlternateList)
    local addCount = 0

    for _, row in ipairs(self:getSkillElementListRow()) do
        for column = 1, BattleConst.COLUMN_COUNT do
            local posId = ModuleManager.BattleManager:getPosId(row, column)
            local entity = gridEntities[posId]
            if entity and entity:canChangeInfo() and not excludeMap[posId] then
                if entity:getElementType() == elementType then
                    table.insert(self.getSkillElementListSameElementList, posId)
                else
                    table.insert(self.getSkillElementListAlternateList, posId)
                end
            end
        end

        local remainCount = count - addCount
        if remainCount <= 0 then
            return self.getSkillElementListResult
        end

        for i = 1, remainCount do
            if not self.getSkillElementListSameElementList[i] then
                break
            end
            table.insert(self.getSkillElementListResult, table.remove(self.getSkillElementListSameElementList, math.random(1, #self.getSkillElementListSameElementList)))
            addCount = addCount + 1
            if addCount >= count then
                return self.getSkillElementListResult
            end
        end

        if addCount < count and useAlternate then
            for i = 1, count - addCount do
                if not self.getSkillElementListAlternateList[1] then
                    break
                end
                table.insert(self.getSkillElementListResult, table.remove(self.getSkillElementListAlternateList, math.random(1, #self.getSkillElementListAlternateList)))
                addCount = addCount + 1
                if addCount >= count then
                    return self.getSkillElementListResult
                end
            end
        end

        if addCount >= count then
            return self.getSkillElementListResult
        end
    end

    return self.getSkillElementListResult
end


---- 从一个点直接遍历所有相关的路径
function BattleBaseController:fillThisPos(posId, columnCount, gridMap, reverse)
    local entity = self.battleData:getGridEntity(posId)
    if not entity or not entity:getIsIdle() then
        return
    end

    local list
    if reverse then
        list = BattleConst.DOWN_LINE_FILL_LIST[posId]
    else
        list = BattleConst.UP_LINE_FILL_LIST[posId]
    end
    if not list[1] then -- 第一排
        if reverse then
            local gridEdgeEntity = self.battleData:getGridEdgeEntity(posId, BattleConst.BOARD_RANGE_TYPE.DOWN)
            if gridEdgeEntity and not gridEdgeEntity:getIsIdle() then
                return
            end
        else
            local gridEdgeEntity = self.battleData:getGridEdgeEntity(posId, BattleConst.BOARD_RANGE_TYPE.UP)
            if gridEdgeEntity and not gridEdgeEntity:getIsIdle() then
                return
            end
        end

        local rc = ModuleManager.BattleManager:getPosRC(posId)
        local c = rc.c
        if not columnCount[c] then
            columnCount[c] = 0
        end
        columnCount[c] = columnCount[c] + 1

        local fallPosId = posId
        local fallEntity = self.battleData:getGridEntity(fallPosId)
        if not fallEntity then -- 异常情况，理论上不可能不存在
            return
        end

        local newStartPosId
        for i = columnCount[c], 1, -1 do
            if reverse then
                newStartPosId = ModuleManager.BattleManager:getLastLineLastRowPosId(i, c, self:getRowCount())
            else
                newStartPosId = ModuleManager.BattleManager:getFirstLineLastRowPosId(i, c)
            end

            local curPos = self:getPosInfo(newStartPosId)
            fallEntity:addPath({x = curPos.x, y = curPos.y})
        end

        local curPos = self:getPosInfo(posId)
        fallEntity:addPath({x = curPos.x, y = curPos.y})

        self.battleData:exchangeGridEntities(posId, fallPosId)
        if not gridMap[c] then
            gridMap[c] = {}
        end
        table.insert(gridMap[c], fallEntity)
    else
        for index, fallPosId in ipairs(list) do
            local fallEntity = self.battleData:getGridEntity(fallPosId)
            if not fallEntity then -- 异常情况，理论上不可能不存在
                return
            end
            if self:judgeGridEdgeCanFall(posId, fallPosId, reverse) then
                if not fallEntity:isCantFallType() then
                    if fallEntity:getIsIdle() then
                        self:fillThisPos(fallPosId, columnCount, gridMap, reverse)
                    end
                    fallEntity = self.battleData:getGridEntity(fallPosId)
                    if not fallEntity:getIsIdle() then
                        if not fallEntity:getPath()[1] then
                            local curPos = self:getPosInfo(fallPosId)
                            fallEntity:addPath({x = curPos.x, y = curPos.y})
                        end

                        local curPos = self:getPosInfo(posId)
                        fallEntity:addPath({x = curPos.x, y = curPos.y})

                        self.battleData:exchangeGridEntities(posId, fallPosId)
                        self:fillThisPos(fallPosId, columnCount, gridMap, reverse)
                        return
                    end
                end
            end
        end
    end
end

function BattleBaseController:judgeGridEdgeCanFall(posId, fallPosId, reverse)
    return BattleBoardTouchHelper:judgeGridEdgeCanLink(fallPosId, posId)
end


function BattleBaseController:getRandomGridInfo()
    local list, fixedRandomGrid = self:getInitBoard()
    local fixedRandomList = fixedRandomGrid[self.curBoardIndex]

    local gridType = 0
    local elementType
    if fixedRandomList and fixedRandomList[1] then
        elementType = table.remove(fixedRandomList, 1)
    else
        local map = self.battleData:getElementTypeMap()
        self.getRandomGridInfoIndexs = table.clearOrCreate(self.getRandomGridInfoIndexs)
        self.getRandomGridInfoTypeList = table.clearOrCreate(self.getRandomGridInfoTypeList)
        for typeName, typeNum in pairs(BattleConst.ELEMENT_TYPE) do
            if not self:getSealElementType()[typeNum] and self:getSkillEntityByElement(typeNum) then
                local weight = ((map[typeNum] or 0) + 1) * BattleConst.ELEMENT_WIGHT
                if self.elementWeightMap and self.elementWeightMap[typeNum] then
                    weight = weight + self.elementWeightMap[typeNum]
                end
                if weight > BattleConst.MAX_ELEMENT_WIGHT then
                    weight = BattleConst.MAX_ELEMENT_WIGHT
                end
                table.insert(self.getRandomGridInfoIndexs, weight)
                table.insert(self.getRandomGridInfoTypeList, typeNum)
            end
        end

        local index = GFunc.getRandomIndex(self.getRandomGridInfoIndexs)
        elementType = self.getRandomGridInfoTypeList[index]
    end

    return {gridType = gridType, elementType = elementType}
end


function BattleBaseController:findSkillInfluenceGrids()
    local girds = self.battleData:clearSkillInfluenceGrids()
    for posId, _ in pairs(girds) do
        local entity = self.battleData:getGridEntity(posId)
        if entity:getCell() then
            entity:getCell():showCircle(false)
        end
    end

    local sequence = self.battleData:getGridSequence()
    self.skillInfluenceGridsSequenceEntities = table.clearOrCreate(self.skillInfluenceGridsSequenceEntities)
    for _, info in ipairs(sequence) do
        local entity = self.battleData:getGridEntity(info.posId)
        table.insert(self.skillInfluenceGridsSequenceEntities, entity)
    end

    for _, info in ipairs(sequence) do
        local entity = self.battleData:getGridEntity(info.posId)
        local skillId = entity:getSkillId()
        if skillId then
            local skillEntity = self:getSkillEntityBySkillId(skillId)
            if skillEntity then
                BATTLE_BOARD_SKILL_HANDLE.activeBoardSkill(info.posId, skillEntity, self.battleData:getGridEnties(), self.skillInfluenceGridsSequenceEntities, self)
            end
        end
    end
end

function BattleBaseController:getRandomSkillList(getCount, onlyCommonSkill, excludeMap)
    local fixedList = self:getFixedRogueSkill()
    if fixedList[1] then
        return table.remove(fixedList, 1)
    end

    getCount = getCount or BattleConst.SKILL_SELECT_COUNT
    self.randomSkillList = table.clearOrCreate(self.randomSkillList)
    self.randomSkillMap = table.clearOrCreate(self.randomSkillMap)
    self.randomSkillNewSkillPool = table.clearOrCreate(self.randomSkillNewSkillPool)
    self.randomSkillSkillWeight = table.clearOrCreate(self.randomSkillSkillWeight)
    local cfg = ConfigManager:getConfig("skill_rogue")
	local skillPool = self:getSkillPool()
    local count = 0

	if not onlyCommonSkill then
		for elementType, list in pairs(skillPool) do -- 先遍历一下未解锁的技能
			if not self:isUnlockedSkillElementType(elementType) then
				local skillEntity = self:getSkillEntityByElement(elementType)
				if skillEntity then
					local skillId = skillEntity:getUnlockId()
					if skillId and not self.randomSkillMap[skillId] then
						local skillCfg = cfg[skillId]
						table.insert(self.randomSkillNewSkillPool, skillId)
						table.insert(self.randomSkillSkillWeight, skillCfg.weight)
						count = count + 1
						self.randomSkillMap[skillId] = true
					end
				end
			end
		end

		if count >= 3 then -- 如果未解锁的技能大于等于3，则直接返回三个解锁技能
			for i = 1, 3 do
				local index = GFunc.getRandomIndex(self.randomSkillSkillWeight)
				local skillId = table.remove(self.randomSkillNewSkillPool, index)
				table.remove(self.randomSkillSkillWeight, index)
				count = count - 1
				table.insert(self.randomSkillList, skillId)
			end

			return table.shuffle(self.randomSkillList)
		end

		if excludeMap then
			for skillId, _ in pairs(excludeMap) do
				self.randomSkillMap[skillId] = true
			end
		end

		for elementType, list in pairs(skillPool) do
			if self:isUnlockedSkillElementType(elementType) then
				for _, skillId in ipairs(list) do
					local skillCfg = cfg[skillId]
					if skillCfg and (not skillCfg.limit_times or self:getSkillCount(skillId) < skillCfg.limit_times) then

						if not self.randomSkillMap[skillId] and (not skillCfg.unlock or self:getSkillCount(skillCfg.unlock) > 0) then
							table.insert(self.randomSkillNewSkillPool, skillId)
							table.insert(self.randomSkillSkillWeight, skillCfg.weight)
							count = count + 1
							self.randomSkillMap[skillId] = true
						end
					end
				end
			end
		end

		if count > 0 then
			local index = GFunc.getRandomIndex(self.randomSkillSkillWeight)
			local skillId = table.remove(self.randomSkillNewSkillPool, index)
			table.remove(self.randomSkillSkillWeight, index)
			count = count - 1
			table.insert(self.randomSkillList, skillId)
			getCount = getCount - 1
		end
	end

    for skillId, info in pairs(cfg) do
        if not self:getNotInvolvedSkills()[skillId] then
            if info.universal then
                if not info.limit_times or self:getSkillCount(skillId) < info.limit_times then
                    if not self.randomSkillMap[skillId] then
                        table.insert(self.randomSkillNewSkillPool, skillId)
                        table.insert(self.randomSkillSkillWeight, info.weight)
                        count = count + 1
                    end
                end
            end
        end
    end

    for i = 1, getCount do
        local index = GFunc.getRandomIndex(self.randomSkillSkillWeight)
        local skillId = table.remove(self.randomSkillNewSkillPool, index)
        table.remove(self.randomSkillSkillWeight, index)
        count = count - 1
        table.insert(self.randomSkillList, skillId)
        if count <= 0 then
            break
        end
    end

    local result = table.shuffle(self.randomSkillList)
    return result
end

function BattleBaseController:dealSelectSkill(skillId, value, side, isSnapshot)
    side = side or self:getCurActionSide()
    self.battleData:addSkillCount(skillId, value, side)
    BATTLE_ROGUE_SKILL_HANDLE.takeEffect(skillId, self.battleData, self, value, side, isSnapshot)

    local skillEntities = self:getSkillEntities(side)
    for _, entity in pairs(skillEntities) do
        entity:gotUpSKill(skillId)
    end
end

function BattleBaseController:onSelectSkill(skillId, value, pos, side)
    -- if not self.canChoseSkillCount or self.canChoseSkillCount <= 0 then
    --     return
    -- end
    -- self.canChoseSkillCount = self.canChoseSkillCount - 1
    side = side or self:getCurActionSide()
    self:dealSelectSkill(skillId, value, side)

    BIReport:postFightSkillSelect(self.battleType, self.battleData, {skillId}, self.chapterId, self.totalDurationTime, self:getWaveIndex())

    local elementType = ModuleManager.HeroManager:getSkillRoguePosition(skillId)
    if elementType and pos then
        if self.battleUI then
            self.battleUI:gotOneSkillAni(skillId, elementType, function()
                self:selectSKillNextToStep()
                self.battleUI:refreshBoard()
                self.battleUI:refreshSkill(nil, nil, self:getCurActionSide())
            end, pos)
            return
        end
    end

    if not self.battleUI then
        return
    end

    self:selectSKillNextToStep()

    self.battleUI:showCommonSkillTips(skillId)
end

function BattleBaseController:selectSKillNextToStep()
    if self.roundStep == BattleConst.BATTLE_ROUND_STEP.ON_REFRESH_BOARD then
        self:onFillBoardOver()
    else
        self:enterElimination()
    end
end

function BattleBaseController:changeElementType(count, elementType, side)
    self.changeElementTypeMap = table.clearOrCreate(self.changeElementTypeMap)
    self.changeElementTypeList = table.clearOrCreate(self.changeElementTypeList)
    local listCount = 0
    for _, entity in pairs(self.battleData:getGridEnties()) do
        if entity:canChangeInfo() and entity:getElementType() ~= elementType then
            if side then
                if self:getPosIdInActionBoardRowRange(side, entity:getPosId()) then
                    table.insert(self.changeElementTypeList, entity)
                    listCount = listCount + 1
                end
            else
                table.insert(self.changeElementTypeList, entity)
                listCount = listCount + 1
            end
        end
    end

    count = math.min(count, listCount)
    for i = 1, count do
        if listCount <= 0 then
            break
        end
        local index = math.random(1, listCount)
        self.changeElementTypeList[index], self.changeElementTypeList[listCount] = self.changeElementTypeList[listCount], self.changeElementTypeList[index]
        local entity = table.remove(self.changeElementTypeList)
        listCount = listCount - 1
        self.changeElementTypeMap[entity] = elementType
    end

    self:changeElementTypeByMap(self.changeElementTypeMap)
end

function BattleBaseController:changeElementTypeByMap(changeMap)
    local index = 1
    for entity, elementType in pairs(changeMap) do
        self.battleData:setGridInfo(entity:getPosId(), {gridType = entity:getGridType(), elementType = elementType})
        self.battleUI:playChangeElementSfx(entity:getPosId(), index)
        index = index + 1
    end
end

function BattleBaseController:killGrids(posIdList)
    if not posIdList[1] then
        return
    end

    for _, posId in ipairs(posIdList) do
        local entity = self.battleData:getGridEntity(posId)
        entity:setIsIdle(true)
    end

    self.battleUI:removeGridOutOfScreen(posIdList)
end

function BattleBaseController:killRowOrColumn(infoList)
    self.killRowOrColumnMap = table.clearOrCreate(self.killRowOrColumnMap)
    for _, info in ipairs(infoList) do
        if info.posList then
            for i, posId in ipairs(info.posList) do
                if not self.killRowOrColumnMap[posId] then
                    self.killRowOrColumnMap[posId] = true
                    local entity = self.battleData:getGridEntity(posId)
                    entity:setIsIdle(true)
                end
            end
            self.battleUI:removeGridOutOfScreen(info.posList)
        end
    end
end

function BattleBaseController:addHeroAttr(attrName, value, side)
    if not self.battleData or not self.battleData.atkTeam then
        return
    end

    local attr = BUFF_NAME_TO_ATTR[attrName]
    if attr then
        self.battleData:getTeamBySide(side):addAttr(attr[1], value, attr[2])
    else
        local func = BattleBuffHandle.addAttribute[attrName]
        if func then
            local unitComp
            for matchType, comp in pairs(self:getCurActionTeam(side):getUnitComp()) do
                unitComp = comp
                break
            end
            if unitComp then
                func(unitComp, value)
            end
        end
    end
end

---- 快照一次棋盘
function BattleBaseController:snapshotBoard()
    local snapshot = {}
    for _, entity in pairs(self.battleData:getGridEnties()) do
        snapshot[entity:getPosId()] = entity:getSnapshoptInfo()
    end
    return snapshot
end

function BattleBaseController:snapshotGridEdge()
    local snapshot = {}
    for posId, info in pairs(self.battleData:getGridEdgeEntities()) do
        for direction, entity in pairs(info) do
            if not entity:getIsIdle() then
                table.insert(snapshot, {pos = posId, type = entity:getType(), direction = direction})
            end
        end
    end
    return snapshot
end

function BattleBaseController:showAttention()
    if not self.attentionList then
        return
    end

    if self.attentionSid then
        ModuleManager.BattleManager:unscheduleGlobal(self.attentionSid)
        self.attentionSid = nil
    end

    self.attentionSid = ModuleManager.BattleManager:performWithDelayGlobal(function()
        for _, posId in ipairs(self.attentionList) do
            local entity = self.battleData:getGridEntity(posId)
            if entity and entity:getCell() then
                entity:getCell():showAni()
            end
        end
    end, 2)
end


function BattleBaseController:shuffleBoard(callback)
    local changeInfo = self:getShuffleBoardInfo()
    if changeInfo then
        self.battleData:setGridEntitiesPosId(changeInfo)
        self.battleUI:shuffleBoard(changeInfo, callback)
    else
        self.resetList = table.clearOrCreate(self.resetList)
        for posId, entity in pairs(self.battleData:getGridEnties()) do
            if self:getPosIdInCurActionBoardRowRange(posId) and entity:isCanFallStatus() and not entity:getCantUpset() then
                table.insert(self.resetList, entity)
            end
        end
        local resetInfo = self:resetGrids(self.resetList)
        if not resetInfo then
            if EDITOR_MODE then
                Logger.logHighlight("----- 处于无法消除状态 -----")
            end
            if callback then
                callback()
            end
        else
            if self.resetGridSid then
                ModuleManager.BattleManager:unscheduleGlobal(self.resetGridSid)
                self.resetGridSid = nil
            end

            self.resetGridSid = ModuleManager.BattleManager:performWithDelayGlobal(function()
                local count = 1
                for posId, info in pairs(resetInfo) do
                    self.battleData:setInfoBySnapshop(posId, info)
                    self.battleUI:playChangeElementSfx(posId, count)
                    count = count + 1
                end
                self.resetGridSid = nil
                if callback then
                    callback()
                end
            end, 0.5)
        end
    end
end

function BattleBaseController:getShuffleBoardInfo()
    self.shuffleBoardPosList = table.clearOrCreate(self.shuffleBoardPosList)
    self.shuffleBoardAnySkillList = table.clearOrCreate(self.shuffleBoardAnySkillList)
    self.shuffleBoardPosMap = table.clearOrCreate(self.shuffleBoardPosMap)
    self.shuffleBoardCountMap = table.clearOrCreate(self.shuffleBoardCountMap)
    self.shuffleBoardCantLinkMap = table.clearOrCreate(self.shuffleBoardCantLinkMap)
    if not self.shuffleBoardList then
        self.shuffleBoardList = table.clearOrCreate(self.shuffleBoardList)
    else
        for elementType, _ in pairs(self.shuffleBoardList) do
            self.shuffleBoardList[elementType] = table.clearOrCreate(self.shuffleBoardList[elementType])
        end
    end
    local anySkillCount = 0
    for posId, entity in pairs(self.battleData:getGridEnties()) do
        if self:getPosIdInCurActionBoardRowRange(posId) and entity:isCanFallStatus() and not entity:getCantUpset() then
            if entity:getSkillId() then
                local skillEntity = self:getSkillEntityBySkillId(entity:getSkillId())
                local elementType = skillEntity:getPosition()
                if skillEntity:getIgnoreElementType() then
                    table.insert(self.shuffleBoardAnySkillList, entity)
                    anySkillCount = anySkillCount + 1
                else
                    if not self.shuffleBoardList[elementType] then
                        self.shuffleBoardList[elementType] = table.clearOrCreate(self.shuffleBoardList[elementType])
                    end
                    table.insert(self.shuffleBoardList[elementType], entity)
                    self.shuffleBoardCountMap[elementType] = (self.shuffleBoardCountMap[elementType] or 0) + 1
                end
            else
                local elementType = entity:getElementType()
                if not self.shuffleBoardList[elementType] then
                    self.shuffleBoardList[elementType] = table.clearOrCreate(self.shuffleBoardList[elementType])
                end
                table.insert(self.shuffleBoardList[elementType], entity)
                self.shuffleBoardCountMap[elementType] = (self.shuffleBoardCountMap[elementType] or 0) + 1
                if not entity:canLink() then
                    self.shuffleBoardCantLinkMap[elementType] = (self.shuffleBoardCantLinkMap[elementType] or 0) + 1
                end
            end
            table.insert(self.shuffleBoardPosList, posId)
            self.shuffleBoardPosMap[posId] = true
        end
    end

    for elementType, list in pairs(self.shuffleBoardList) do
        table.sort(self.shuffleBoardList[elementType], function(a, b)
            if a:getSkillId() and not b:getSkillId() then
                return false
            elseif not a:getSkillId() and b:getSkillId() then
                return true
            else
                return a:getPosId() < b:getPosId()
            end
        end)
    end


    local needCount = self:getMinEliminationCount()
    local find = false
    for elementType, list in pairs(self.shuffleBoardList) do
        local count = self.shuffleBoardCountMap[elementType] or 0
        local cantLinkCount = self.shuffleBoardCantLinkMap[elementType] or 0
        local skillCount = 0
        if anySkillCount > 0 then
            skillCount = 1
        end
        if count + skillCount - cantLinkCount >= needCount then
            local haveSkill = false
            local listCount = 0
            self.shuffleBoardTempList = table.clearOrCreate(self.shuffleBoardTempList)
            find = false
            local add = false
            for _, entity in ipairs(list) do
                add = false
                if haveSkill then
                    if not entity:getSkillId() then
                        add = entity:canLink()
                    end
                else
                    if entity:getSkillId() then
                        haveSkill = true
                    end
                    add = entity:canLink()
                end
                if add then
                    table.insert(self.shuffleBoardTempList, entity)
                    listCount = listCount + 1

                    if listCount >= needCount then -- 可连接的 普通的元素+元素技能
                        find = true
                        break
                    end
                end
            end

            if listCount >= needCount then -- 可连接的 普通的元素+元素技能
                find = true
                break
            elseif not haveSkill and skillCount > 0 then -- 可连接的 普通元素 + 任意元素技能
                table.insert(self.shuffleBoardTempList, self.shuffleBoardAnySkillList[1])
                listCount = listCount + 1
                if listCount >= needCount then
                    find = true
                    break
                end
            end
        end
    end

    if find then
        return self:shufflePos(self.shuffleBoardTempList, self.shuffleBoardPosMap, needCount, self.shuffleBoardPosList)
    end
end

function BattleBaseController:shufflePos(tempList, posMap, needCount, posList)
    local gotPos, useMap, list = self:findCanLinkPosList(posMap, needCount)

    if gotPos and list[1] then
        self.shufflePosInfo = table.clearOrCreate(self.shufflePosInfo)
        self.shufflerPosUsedPos = table.clearOrCreate(self.shufflerPosUsedPos)
        self.shufflerSetedPos = table.clearOrCreate(self.shufflerSetedPos)
        for index, entity in ipairs(tempList) do
            self.shufflePosInfo[entity:getPosId()] = list[index]
            self.shufflerPosUsedPos[list[index]] = true
            self.shufflerSetedPos[entity:getPosId()] = true
        end

        self.shufflePosList1 = table.clearOrCreate(self.shufflePosList1)
        self.shufflePosList2 = table.clearOrCreate(self.shufflePosList2)
        for index = #posList, 1, -1 do
            local posId = posList[index]
            if not self.shufflerPosUsedPos[posId] then
                table.insert(self.shufflePosList2, posId)
            end
            if not self.shufflerSetedPos[posId] then
                table.insert(self.shufflePosList1, posId)
            end
        end

        self.shufflePosList2 = table.shuffle(self.shufflePosList2)
        for index, posId in ipairs(self.shufflePosList1) do
            if not self.shufflePosList2[index] then
                break
            end
            self.shufflePosInfo[posId] = self.shufflePosList2[index]
        end

        return self.shufflePosInfo
    end
end

function BattleBaseController:findAttention()
    local find = false
    local min, max = self:getCurActionBoardRowRange()
    for r = min, max do
        for c = 1, GConst.BattleConst.COLUMN_COUNT  do
            local posId = ModuleManager.BattleManager:getPosId(r, c)
            local gridEntity = self.battleData:getGridEntity(posId)
            local mainElementType = gridEntity:getElementType()
            if gridEntity:canChangeInfo() then
                self.findAttentionPathList = table.clearOrCreate(self.findAttentionPathList)
                self.findAttentionPosIdMap = table.clearOrCreate(self.findAttentionPosIdMap)
                self:findLinkLine(posId, self.findAttentionPosIdMap, false, mainElementType, self.findAttentionPathList)
                if table.nums(self.findAttentionPathList) >= self:getMinEliminationCount() then
                    find = true
                    break
                end
            end
        end
        if find then
            break
        end
    end

    return find, self.findAttentionPathList
end

function BattleBaseController:_dealResetGridsDataFunc(posList)
    self.dealResetFunc1PosMap = table.clearOrCreate(self.dealResetFunc1PosMap)
    self.dealResetFunc1LockElementMap = table.clearOrCreate(self.dealResetFunc1LockElementMap)
    self.dealResetFunc1KeepSnapList = table.clearOrCreate(self.dealResetFunc1KeepSnapList)
    self.dealResetFunc1KeepSnapSkillList = table.clearOrCreate(self.dealResetFunc1KeepSnapSkillList)
    self.dealResetFunc1EmptySnapList = table.clearOrCreate(self.dealResetFunc1EmptySnapList)
    local emptyCount = 0
    for _, entity in ipairs(posList) do
        local posId = entity:getPosId()
        self.dealResetFunc1PosMap[posId] = entity
        if entity:isEmptyIdle() then
            emptyCount = emptyCount + 1
            table.insert(self.dealResetFunc1EmptySnapList, entity:getSnapshoptInfo())
        elseif entity:isLock() and not entity:getSkillId() then
            self.dealResetFunc1LockElementMap[entity:getElementType()] = true
            emptyCount = emptyCount + 1
            local snapInfo = entity:getSnapshoptInfo()
            snapInfo.gridType = BattleConst.GRID_TYPE.EMPTY
            table.insert(self.dealResetFunc1EmptySnapList, snapInfo)
        else
            if entity:getSkillId() then
                table.insert(self.dealResetFunc1KeepSnapSkillList, entity:getSnapshoptInfo())
            else
                table.insert(self.dealResetFunc1KeepSnapList, entity:getSnapshoptInfo())
            end
        end
    end

    return self.dealResetFunc1PosMap, self.dealResetFunc1LockElementMap, emptyCount, self.dealResetFunc1KeepSnapList, self.dealResetFunc1KeepSnapSkillList, self.dealResetFunc1EmptySnapList
end


function BattleBaseController:_dealResetGridsDataFunc2(useMap, emptySnapList, mainElement, backupSkill, keepSnapList, posMap, canRandomElmentList, elementCount, lockElementMap)
    self.dealResetFunc2ResetPosInfo = table.clearOrCreate(self.dealResetFunc2ResetPosInfo)
    for posId, _ in pairs(useMap) do
        local snapInfo
        if emptySnapList[1] then
            snapInfo = table.remove(emptySnapList)
            snapInfo.elementType = mainElement
            snapInfo.posId = posId
            snapInfo.gridType = BattleConst.GRID_TYPE.EMPTY
        else
            snapInfo = backupSkill
        end
        self.dealResetFunc2ResetPosInfo[posId] = snapInfo
    end

    for _, snap in ipairs(emptySnapList) do
        table.insert(keepSnapList, snap)
    end

    keepSnapList = table.shuffle(keepSnapList)

    for posId, entity in pairs(posMap) do
        if not useMap[posId] then
            local snapInfo
            if keepSnapList[1] then
                snapInfo = table.remove(keepSnapList)
                snapInfo.posId = posId
                if not snapInfo.skillId or snapInfo.skillId <= 0 then
                    if snapInfo.gridType == BattleConst.GRID_TYPE.EMPTY then
                        snapInfo.elementType = canRandomElmentList[math.random(1, elementCount)]
                        if lockElementMap[snapInfo.elementType] then
                            snapInfo.gridType = BattleConst.GRID_TYPE.LOCK
                        end
                    end
                end
            end

            if snapInfo then
                self.dealResetFunc2ResetPosInfo[posId] = snapInfo
            end
        end
    end

    return self.dealResetFunc2ResetPosInfo
end

function BattleBaseController:resetGrids(posList)
    local needCount = self:getMinEliminationCount()
    if not posList[needCount] then
        return
    end

    local posMap, lockElementMap, emptyCount, keepSnapList, keepSnapSkillList, emptySnapList = self:_dealResetGridsDataFunc(posList)

    local canUseSkillCount = 0
    if keepSnapSkillList[1] then -- 技能可以直接链接， 不判断元素技能了
        canUseSkillCount = 1
    end

    if emptyCount + canUseSkillCount < needCount then -- 格子不够
        return
    end

    local backupSkill
    if emptyCount < needCount then
        backupSkill = table.remove(keepSnapSkillList)
    end

    for _, snap in ipairs(keepSnapSkillList) do
        table.insert(keepSnapList, snap)
    end

    local gotPos, useMap = self:findCanLinkPosList(posMap, needCount)
    if not gotPos then
        return
    end

    self.canRandomElmentList = table.clearOrCreate(self.canRandomElmentList)

    local elementCount = 0
    for typeName, typeNum in pairs(BattleConst.ELEMENT_TYPE) do
        if not lockElementMap[typeNum] and  not self:getSealElementType()[typeNum] and self:getSkillEntityByElement(typeNum) then
            table.insert(self.canRandomElmentList, typeNum)
            elementCount = elementCount + 1
        end
    end

    if elementCount <= 0 then
        return
    end

    local mainElement = self.canRandomElmentList[math.random(1, elementCount)]

    for typeNum, _ in pairs(lockElementMap) do
        table.insert(self.canRandomElmentList, typeNum)
        elementCount = elementCount + 1
    end

    local resetPosInfo = self:_dealResetGridsDataFunc2(useMap, emptySnapList, mainElement, backupSkill, keepSnapList, posMap, self.canRandomElmentList, elementCount, lockElementMap)

    return resetPosInfo
end

function BattleBaseController:findCanLinkPosList(posMap, needCount)
    self.findCanLinkList = table.clearOrCreate(self.findCanLinkList)
    self.findCanUseMap = table.clearOrCreate(self.findCanUseMap)
    local gotPos = false
    for posId, _ in pairs(posMap) do
        table.clear(self.findCanLinkList)
        table.clear(self.findCanUseMap)
        local count = 0
        local findPodId = posId
        gotPos = false
        while true do
            local aounrdList = BattleConst.GRID_OUT_LINE_POS_ID[findPodId]
            if not aounrdList then
                break
            end
            local got = false
            for id, status in pairs(aounrdList) do
                if posMap[id] and not self.findCanUseMap[id] and BattleBoardTouchHelper:judgeGridEdgeCanLink(findPodId, id) then
                    table.insert(self.findCanLinkList, id)
                    count = count + 1
                    self.findCanUseMap[id] = true
                    findPodId = id
                    got = true
                    break
                end
            end
            if not got then
                break
            end
            if count >= needCount then
                gotPos = true
                break
            end
        end

        if gotPos then
            break
        end
    end

    return gotPos, self.findCanUseMap, self.findCanLinkList
end

function BattleBaseController:findLinkLine(posId, posIdMap, hadSkill, mainElementType, lineList)
    posIdMap[posId] = true
    table.insert(lineList, posId)
    local list = BattleConst.GRID_OUT_LINE_POS_ID[posId]
    if not list then
        return hadSkill
    end

    if #lineList >= self:getMinEliminationCount() then
        return hadSkill
    end

    local lineCount = 0
    local maxLineList
    local maxPosIdMap
    local maxGotSkill
    for aroundposId, _ in pairs(list) do
        if self:getPosIdInCurActionBoardRowRange(aroundposId) and not posIdMap[aroundposId] and BattleBoardTouchHelper:judgeGridEdgeCanLink(posId, aroundposId) then
            local gridEntity = self.battleData:getGridEntity(aroundposId)
            if gridEntity then
                if gridEntity:canLink() and not gridEntity:getSkillId() then
                    if not mainElementType or gridEntity:getElementType() == mainElementType then
                        mainElementType = mainElementType or gridEntity:getElementType()
                        local tempIdMap = GFunc.getTable(posIdMap)
                        local newList = GFunc.getTable(lineList)
                        local gotSkill = self:findLinkLine(aroundposId, tempIdMap, hadSkill, mainElementType, newList)
                        local count = #newList
                        if count > lineCount then
                            lineCount = count
                            maxLineList = newList
                            maxPosIdMap = tempIdMap
                            maxGotSkill = gotSkill
                        end
                    end
                elseif gridEntity:canLink() and gridEntity:getSkillId() and not hadSkill and mainElementType then
                    local skillId = gridEntity:getSkillId()
                    local skillEntity = self:getSkillEntityBySkillId(skillId)
                    if skillEntity then
                        if skillEntity:getIgnoreElementType() or skillEntity:getPosition() == mainElementType then
                            local tempIdMap = GFunc.getTable(posIdMap)
                            local newList = GFunc.getTable(lineList)
                            local gotSkill = self:findLinkLine(aroundposId, tempIdMap, true, mainElementType, newList)
                            local count = #newList
                            if count > lineCount then
                                lineCount = count
                                maxLineList = newList
                                maxPosIdMap = tempIdMap
                                maxGotSkill = gotSkill
                            end
                        end
                    end
                end
            end
        end
    end

    if lineCount > 0 then
        for index, id in ipairs(maxLineList) do
            lineList[index] = id
        end

        for _, id in pairs(maxPosIdMap) do
            posIdMap[id] = true
        end

        hadSkill = maxGotSkill
    end

    return hadSkill
end

function BattleBaseController:generateMonsterById(monsterId, callback)
    local unitEntity = self.battleData:addMonster(monsterId, true, self)
    local modelId = unitEntity:getModelId()
    BattleHelper:loadBattleHeroModel(modelId, self.battleUI:getBattleNode(), function(spineObject)
        self.defTeam:removeAllUnits()
        local monsterComp = spineObject:addLuaComponent(GConst.BattleConst.TYPEOF_LUA_COMP.BATTLE_MONSTER_COMPONENT)
        monsterComp:initWithEntity(modelId, unitEntity, self)
        self.defTeam:addUnit(monsterComp, true)
        self.battleUI:refreshDefHp(unitEntity:getHp(), unitEntity:getHpPercent())
        monsterComp:playEnterBattlefield(false, callback)
    end)
end

function BattleBaseController:addBattleExp(exp)
    if not self.battleData or not exp then
        return
    end
    self.battleData:addExp(exp)
end

function BattleBaseController:addTaskProgress(fieldName, count)
    if self:getCurActionSide() ~= SIDE_ATK then
        return
    end
    if not count then
        return
    end
    self.taskProgress[fieldName] = (self.taskProgress[fieldName] or 0) + count
end

function BattleBaseController:setTaskProgress(fieldName, count)
    if not count then
        return
    end
    self.taskProgress[fieldName] = count
end


function BattleBaseController:getTaskProgress()
    return self.taskProgress
end

function BattleBaseController:addFillWaitingCount()
    self.waitingFillCount = self.waitingFillCount + 1
end

function BattleBaseController:reduceFillWaitingCount()
    self.waitingFillCount = self.waitingFillCount - 1
end

function BattleBaseController:_tick(dt, originDt)
    self.realTime = self.realTime + dt
    self.waveDurationTime = self.waveDurationTime + originDt
    self.totalDurationTime = self.totalDurationTime + originDt
	if self.isPause then
		return
	end
    if self.needWaitingBoardOver == false then
        self:enterRoundBegin()
    end
    if self.waitingFillCount <= 0 and self.isWaitingFill and self.isRoundBeginCheck then
        self:fillBoard(self.isRoundBeginCheck)
    end
	self.time = self.time + dt
    self.atkTeam:tick(dt)
    self.defTeam:tick(dt)
    local count = #self.effectTexts
    for i = count, 1, -1 do
        self.effectTexts[i]:tick(dt)
        if self.effectTexts[i]:getDuration() < 0 then
            BattleHelper:recycleEffectText(self.effectTexts[i])
            table.remove(self.effectTexts, i)
        end
    end
    self:checkDelayEffectText(dt)
    self:checkSkillSlowDown(dt)
	self:tick(dt)
    BattleBoardTouchHelper:tick(dt)
end

function BattleBaseController:battleEnd()
    if self.battleEndSid then
        ModuleManager.BattleManager:unscheduleGlobal(self.battleEndSid)
        self.battleEndSid = nil
    end
    self.battleUI:disableUITouch()
    self.battleEndSid = ModuleManager.BattleManager:performWithDelayGlobal(function()
        if self.battleUI then
            self.battleUI:enableUITouch()
        end

        -- 处理战斗任务
        local curWaveIndex = self:getWaveIndex()
        if self.victory then
            self:setTaskProgress(GConst.BattleConst.BATTLE_TASK_FIELD.KILLS_BOSS_TURN, self.waveRoundCount[curWaveIndex])
        end

        local teamEntity = self.battleData:getAtkTeam()
        local hpp = math.floor(teamEntity:getHpPercent() * 100 + 0.0001)
        self:setTaskProgress(GConst.BattleConst.BATTLE_TASK_FIELD.SPARE_HP, hpp)

        local totalRound = 0
        for wave, round in pairs(self.waveRoundCount) do
            if wave == curWaveIndex then
                if self.victory then
                    totalRound = totalRound + round
                end
            else
                totalRound = totalRound + round
            end
        end
        self:setTaskProgress(GConst.BattleConst.BATTLE_TASK_FIELD.TOTAL_TURN, totalRound)
        -- end处理战斗任务

        self:controllBattleEnd()
        if self.victory then
            ModuleManager.TaskManager:addTaskProgress(GConst.TaskConst.TASK_TYPE.X_BATTLE_VICTORY)
        end
    end, 1)
end

function BattleBaseController:clear()
    if self.alreadyClear then
		return
	end
	self.alreadyClear = true
    if self.tickSid then
		BattleScheduler:unscheduleGlobal(self.tickSid)
		self.tickSid = nil
	end
    BattleScheduler:clear()
    BattleHelper:clear()
    BattlePassive:clear()
    BattlePool:clear()
    self:unBindAll()
    self.battleData:clear()
    self.battleData:resetTimeSpeed()
    ModuleManager.BattleManager:unscheduleAll()

    if DataManager.TutorialData:getIsInTutorial() then
        ModuleManager.TutorialManager:stopTutorial()
    end
end

function BattleBaseController:endBattleAndExit()
    BATTLE_SNAPSHOT_HELPER:clearSnap(self)
	ModuleManager.BattleManager:exitBattle()
end

function BattleBaseController:checkSkillSlowDown(dt)
    if self.skillSlowDownDuration == nil then
        return
    end
    self.skillSlowDownDuration = self.skillSlowDownDuration - dt
    if self.skillSlowDownDuration >= 0 then
        return
    end
    self.skillSlowDownDuration = nil
    self:resetTimeSpeed()
end

function BattleBaseController:checkDelayEffectText(dt)
	if self.delayEffectTextCount <= 0 then
		return
	end
	for i = self.delayEffectTextCount, 1, -1 do
		local delayEffectText = self.delayEffectTextList[i]
		delayEffectText.time = delayEffectText.time - dt
		if delayEffectText.time <= 0 then
            local effectTextComp = BattleHelper:getEffectText(self.battleUI:getNumberNode(), delayEffectText.colorType)
            effectTextComp:showEffectNumber(delayEffectText.effectType, delayEffectText.num, delayEffectText.x, delayEffectText.y, delayEffectText.showCombo)
            table.insert(self.effectTexts, effectTextComp)
			local tailDelayEffectText = self.delayEffectTextList[self.delayEffectTextCount]
			delayEffectText.colorType = tailDelayEffectText.colorType
			delayEffectText.effectType = tailDelayEffectText.effectType
			delayEffectText.num = tailDelayEffectText.num
			delayEffectText.x = tailDelayEffectText.x
			delayEffectText.y = tailDelayEffectText.y
			delayEffectText.time = tailDelayEffectText.time
            delayEffectText.showCombo = tailDelayEffectText.showCombo
			self.delayEffectTextCount = self.delayEffectTextCount - 1
		end
	end
end

function BattleBaseController:showEffectNumber(colorType, effectType, num, x, y, delayTime, showCombo)
    if delayTime > 0 then
        self.delayEffectTextCount = self.delayEffectTextCount + 1
        local obj = self.delayEffectTextList[self.delayEffectTextCount]
        if obj == nil then
            obj = {}
            self.delayEffectTextList[self.delayEffectTextCount] = obj
        end
        obj.colorType = colorType
        obj.effectType = effectType
        obj.num = num
        obj.x = x
        obj.y = y
        obj.time = delayTime
        obj.showCombo = showCombo
    else
        local effectTextComp = BattleHelper:getEffectText(self.battleUI:getNumberNode(), colorType)
        effectTextComp:showEffectNumber(effectType, num, x, y, showCombo)
        table.insert(self.effectTexts, effectTextComp)
    end
end

function BattleBaseController:getFxNode()
    return self.battleUI:getFxNode()
end

function BattleBaseController:shakeScreen(shakeType, duration)
    self.battleUI:shakeScreen(shakeType, duration)
end

local function _addCurRoundAttr(self, side, instruction, callback)
    if instruction.effectList then
        local defComp = self.defTeam:getMainUnit()
        local atkComp = self.atkTeam:getMainUnit()
        local isAtkSide = side == SIDE_ATK
        local actionComp = isAtkSide and atkComp or defComp
        for _, effect in ipairs(instruction.effectList) do
            local target
            if effect:getTargetSide() == BattleConst.SIDE_DEF then
                target = isAtkSide and defComp or atkComp
            elseif effect:getTargetSide() == BattleConst.SIDE_ATK then
                target = isAtkSide and atkComp or defComp
            else
                local matchType = BattleConst.SIDE_OBJ_TO_MATCH_TYPE[effect:getTargetSide()]
                if matchType then
                    if isAtkSide then
                        target = self.atkTeam:getUnitComp()[matchType]
                    else
                        target = self.defTeam:getUnitComp()[matchType]
                    end
                end
            end
            if target then
                actionComp:takeEffect(effect, target)
            end
        end
    end

    callback()
end

-- local function _assisting(self, side, instruction, callback)
--     local actionTeam = self.atkTeam
--     if side == SIDE_DEF then
--         actionTeam = self.defTeam
--     end
--     actionTeam:useAssistingSkill(instruction.assistingList, #self.instructions == 0, callback)
-- end

local function _generalAttack(self, side, instruction, callback)
    local actionTeam = self.atkTeam
    if side == SIDE_DEF then
        actionTeam = self.defTeam
    end
    actionTeam:useNormalSkill(instruction.skillMatch, instruction.count, #self.instructions == 0, BattleConst.EFFECT_TYPE.DIRECT, BattleConst.ATTACK_ACTION_STATE.NORMAL, callback)
end

local function _playSkill(self, side, instruction, callback)
    local actionTeam = self.atkTeam
    if side == SIDE_DEF then
        actionTeam = self.defTeam
    end
    actionTeam:useSkill(instruction.skillMatch, instruction.count, #self.instructions == 0, BattleConst.EFFECT_TYPE.DIRECT, BattleConst.ATTACK_ACTION_STATE.NORMAL, callback)
end

BattleBaseController._doInstruction = {
    [BattleConst.INSTRUCTION_NAME.ADD_CUR_ROUND_ATTR] = _addCurRoundAttr,
    -- [BattleConst.INSTRUCTION_NAME.ASSISTING] = _assisting,
    [BattleConst.INSTRUCTION_NAME.GENERAL_ATTACK] = _generalAttack,
    [BattleConst.INSTRUCTION_NAME.PLAY_SKILL] = _playSkill,
}

function BattleBaseController:calExpectedInjury(mainElementType, count, skillId)
    local atkCompMap = self:getCurActionTeam():getUnitComp()
    if not atkCompMap then
        return 0
    end
    local atkUnitComp = atkCompMap[mainElementType]
    if mainElementType and not atkUnitComp then
        return 0
    end
    if not mainElementType or not atkUnitComp then
        count = 0
        atkUnitComp = self:getCurActionTeam():getMainUnit()
    end
    local dmg = BattleFormula:getExpectedDamageResult(atkUnitComp, count, self:getCurOtherActionUnitComp(), skillId, atkCompMap)
    return dmg
end

return BattleBaseController