THTD_Custom_Hit_Block = {}

function GetHeroFairyList(hero)
    local fairyList = {}
    local count = 0
    for k,v in pairs(hero.thtd_hero_tower_list) do        
        if v:GetUnitName() == "sunny" and v.thtd_combo_fairyList ~= nil then       
            table.insert(fairyList,v.thtd_combo_fairyList)
        end
    end
    return fairyList
end

function IsUnitInFairy(fairyArea,unit)    
    local pos1 = fairyArea.sunny:GetAbsOrigin()
    local pos2 = fairyArea.star:GetAbsOrigin()
    local pos3 = fairyArea.luna:GetAbsOrigin()

    local origin = unit:GetAbsOrigin()

    if IsInTriangle(pos1,pos2,pos3,origin) == true then
        return true
    else
        return false
    end
end
      
function GetTriangleArea(p0,p1,p2)
    local ab = Vector(p1.x - p0.x, p1.y - p0.y, 0)
    local bc = Vector(p2.x - p1.x, p2.y - p1.y, 0)
    return math.abs((ab.x * bc.y - ab.y * bc.x) / 2.0)
end
     
function IsInTriangle(a,b,c,d)
    local sabc = GetTriangleArea(a, b, c)
    local sadb = GetTriangleArea(a, d, b)
    local sbdc = GetTriangleArea(b, d, c)
    local sadc = GetTriangleArea(a, d, c)
          
    local sumSuqar = sadb + sbdc + sadc
          
    if (-5 < (sabc - sumSuqar) and (sabc - sumSuqar) < 5) then 
        return true 
    else
        return false
    end    
end

--判断三点是否一条线
function IsThreePointsOnOneLine(p1,p2,p3)
    local a = math.floor(GetDistanceBetweenTwoVec2D(p1, p2) + 0.5)
    local b = math.floor(GetDistanceBetweenTwoVec2D(p2, p3) + 0.5)
    local c = math.floor(GetDistanceBetweenTwoVec2D(p3, p1) + 0.5)
    local max = math.max(a, b, c)
    if a == max and a == (b + c) then return true end
    if b == max and b == (a + c) then return true end
    if c == max and c == (a + b) then return true end
    return false
end

--求三角形的重心和最大半径
function GetCircleCenterAndRadius(p1,p2,p3)   
    local center = Vector((p1.x + p2.x + p3.x)/3, (p1.y + p2.y + p3.y)/3, 0)  
    return center, math.max(math.floor(GetDistanceBetweenTwoVec2D(p1, center) + 0.5), math.floor(GetDistanceBetweenTwoVec2D(p2, center) + 0.5), math.floor(GetDistanceBetweenTwoVec2D(p3, center) + 0.5))
end

-- 两点连线上任意点的坐标
function GetTwoVectorSub(pos1, pos2, p1vsp2)
    --Px点到两端点的距离P1Px与PxP2比值为p1vsp2
    local x=(pos1.x + p1vsp2 * pos2.x)/(1+p1vsp2)
    local y=(pos1.y + p1vsp2 * pos2.y)/(1+p1vsp2)
    local z=(pos1.z + p1vsp2 * pos2.z)/(1+p1vsp2)
    return Vector(x, y, z)
end

--求三角形的外接圆圆心和半径，如果三点共线则返回nil
function GetOuterCircleCenterAndRadius(p1,p2,p3)     
    local a = math.floor(GetDistanceBetweenTwoVec2D(p1, p2) + 0.5)
    local b = math.floor(GetDistanceBetweenTwoVec2D(p2, p3) + 0.5)
    local c = math.floor(GetDistanceBetweenTwoVec2D(p3, p1) + 0.5)

    --如果在一条线上，则返回中点
    local max = math.max(a, b, c)
    if a == max and a == (b + c) then 
        local x = (p1.x + p2.x) / 2
        local y = (p1.y + p2.y) / 2
        local r = a / 2
        return Vector(math.floor(x + 0.5),math.floor(y + 0.5),0), r
    end
    if b == max and b == (a + c) then 
        local x = (p2.x + p3.x) / 2
        local y = (p2.y + p3.y) / 2
        local r = b / 2
        return Vector(math.floor(x + 0.5),math.floor(y + 0.5),0), r
    end
    if c == max and c == (a + b) then 
        local x = (p1.x + p3.x) / 2
        local y = (p1.y + p3.y) / 2
        local r = c / 2
        return Vector(math.floor(x + 0.5),math.floor(y + 0.5),0), r
    end

    --外接圆半径
    local p = (a + b +c)/2
    local s = math.sqrt(p*(p-a)*(p-b)*(p-c))
    local r = math.floor(a*b*c/(4*s) + 0.5)

    --外接圆圆心
    local x1  =  p1.x
	local x2  =  p2.x
	local x3  =  p3.x
	local y1  =  p1.y
	local y2  =  p2.y
	local y3  =  p3.y
    local t1 = x1*x1+y1*y1
	local t2 = x2*x2+y2*y2
	local t3 = x3*x3+y3*y3
	local temp = x1*y2+x2*y3+x3*y1-x1*y3-x2*y1-x3*y2
	local x = (t2*y3+t1*y2+t3*y1-t2*y1-t3*y2-t1*y3)/temp/2
    local y = (t3*x2+t2*x1+t1*x3-t1*x2-t2*x3-t3*x1)/temp/2
    
    return Vector(math.floor(x + 0.5),math.floor(y + 0.5),0), r
end

--求三角形的内接圆圆心和半径
function GetInnerCircleCenterAndRadius(p0,p1,p2)  
    local x1 = p0.x 
    local x2 = p1.x
    local x3 = p2.x
    local y1 = p0.y
    local y2 = p1.y
    local y3 = p2.y
 
    local x=((y2-y1)*(y3*y3-y1*y1+x3*x3-x1*x1)-(y3-y1)*(y2*y2-y1*y1+x2*x2-x1*x1))/(2*(x3-x1)*(y2-y1)-2*((x2-x1)*(y3-y1)))
    local y=((x2-x1)*(x3*x3-x1*x1+y3*y3-y1*y1)-(x3-x1)*(x2*x2-x1*x1+y2*y2-y1*y1))/(2*(y3-y1)*(x2-x1)-2*((y2-y1)*(x3-x1)))
 
    local radius = math.sqrt((p0.x - x)*(p0.x - x) + (p0.y - y)*(p0.y - y))
 
    return Vector(x,y,0),radius
end

-- 获取各刷怪路线的偏移位置，x, y 为偏移量，增长方向默认为左右集中，一致向下
function GetSpawnLineOffsetVector(lineid,vec,x,y) 
    -- lineid从左上顺时针
    if lineid == 2 or lineid == 3 then 
        return vec + Vector(-x,-y,0)
    else
        return vec + Vector(x,-y,0)    
    end
end

-- 判断点在直线哪一边，-1为AB的左侧，1为右侧
function PointToLineSide(point, vecA, vecB)
    local a = vecB.y - vecA.y
    local b = vecA.x - vecB.x
    local c = vecB.x * vecA.y - vecA.x * vecB.y
	local v = a * point.x + b * point.y + c
	
	if v < 0 then 
		return -1
	elseif v > 0 then 
		return 1
	else
		return 0
	end
end

-- 是否在三角形内
function IsPointInTriangle(point, vecA, vecB, vecC)   
    local d1 = PointToLineSide(point, vecA, vecB)
    local d2 = PointToLineSide(point, vecB, vecC)
    local d3 = PointToLineSide(point, vecC, vecA)
    return (d1 * d2 > 0) and (d2 * d3 > 0)
end

-- 点是否在直线AB左边，右手掌指向线段，大拇指方向
function IsLeftToLineSide(point, vecA, vecB)
    return (vecB.x - vecA.x) * (point.y - vecA.y) > (point.x - vecA.x) * (vecB.y - vecA.y)
end

-- 点是否在三角形内
function IsPointInTriangle2(point, vecA, vecB, VecC)
	local x = point.x
	local y = point.y
	local ax = vecA.x
	local ay = vecA.y
	local bx = vecB.x
	local by = vecB.y
	local cx = VecC.x
	local cy = VecC.y
    if (bx - ax) * (y - ay) > (by - ay) * (x - ax) and (cx - bx) * (y - by) > (cy - by) * (x - bx) and (ax - cx) * (y - cy) > (ay - cy) * (x - cx) then 
		return false
	else 
		return true
	end
end

-- 计算点与A、B两点形成的角度，即形成三角形中点所在顶点的角度
function GetAngle(point, vecA, vecB)
    local a = (point - vecA):Length2D()
    local b = (vecA - vecB):Length2D()
    local c = (point - vecB):Length2D()
    
    -- 计算弧度表示的角
    local rad = math.acos((a*a + c*c - b*b)/(2.0*a*c));
    -- 用角度表示的角
    return 180*rad/math.pi
end

-- /**
--  * 点到线的最短距离实际上就是点到线的垂直距离。
--  * (x,y)为点的坐标
--  * (x1,y1)为线段上点的坐标
--  * (x2,y2)为线段另外一点的坐标
--  * 将点连接上两个线段的
--  */
function GetLengthPointToLine(point, vecA, vecB)
	local x = point.x
	local y = point.y
	local x1 = vecA.x
	local y1 = vecA.y
	local x2 = vecB.x
	local y2 = vecB.y
    -- 三角形三个边长
    local A = math.abs(math.sqrt(math.pow((x - x1), 2) + math.pow((y - y1), 2)))
    local B = math.abs(math.sqrt(math.pow((x - x2), 2) + math.pow((y - y2), 2)))
    local C = math.abs(math.sqrt(math.pow((x1 - x2), 2) + math.pow((y1 - y2), 2)))
    -- 利用海伦公式计算三角形面积
    -- 周长的一半
    local P = (A + B + C) / 2
    local allArea = math.abs(math.sqrt(P * (P - A) * (P - B) * (P - C)))
    -- 普通公式计算三角形面积反推点到线的垂直距离
    local dis = (2 * allArea) / C
    return dis
end

--------------------------------------------------------------------------------

-- 判断单位是否有效，即非空、存活、激活
function THTD_IsValid(unit)
    if unit ~= nil and unit:IsNull() == false and unit:IsAlive() and unit:HasModifier("modifier_touhoutd_release_hidden") == false then
        return true
    else
        return false
    end
end

-- 判断是否有效且存活
function IsValidAlive(unit)
    if unit ~= nil and unit:IsNull() == false and unit:IsAlive() then
        return true
    else
        return false
    end
end

--------------------------------------------------------------------------------

-- PlayerResource:ModifyGold 强化版，超出金钱将保存
thtd_extra_gold = {[0] = 0,[1] = 0,[2] = 0,[3] = 0}
function THTD_ModifyGoldEx(playerid, gold, reliable, flag)
    if flag == nil then flag = DOTA_ModifyGold_Unspecified end
    if reliable ~= false then reliable = true end

    if thtd_extra_gold[playerid] == nil then
		thtd_extra_gold[playerid] = 0
    end
    
    local current_gold = PlayerResource:GetGold(playerid)
    if gold ~= nil then         
        local add_gold = gold
        if current_gold + gold > 99999 then
            add_gold = math.min(gold, 99999-current_gold)
            thtd_extra_gold[playerid] = thtd_extra_gold[playerid] + (gold - add_gold)		
        elseif thtd_extra_gold[playerid] > 0 then
            add_gold = math.min(thtd_extra_gold[playerid], 99999-(gold+current_gold))		
            thtd_extra_gold[playerid] = thtd_extra_gold[playerid] - add_gold
        end
        PlayerResource:ModifyGold(playerid, add_gold, reliable, flag)       
    else
        if current_gold < 99999 and thtd_extra_gold[playerid] > 0 then 
            local move_gold = math.min(99999 - current_gold, thtd_extra_gold[playerid])
            thtd_extra_gold[playerid] = thtd_extra_gold[playerid] - move_gold
            PlayerResource:ModifyGold(playerid, move_gold, true, flag)            
        end
    end
    THTD_RefreshExtraGold(playerid)    
end

-- 刷新超出上限金钱显示
function THTD_RefreshExtraGold(playerid)
    PlayerResource:SetLastBuybackTime(playerid, thtd_extra_gold[playerid] or 0)	     
end

-- 获取总金钱
function CDOTA_BaseNPC:GetGold()
    local playerid = self:GetPlayerOwnerID()
    if PlayerResource:IsValidPlayerID(playerid) then	
        return PlayerResource:GetGold(playerid) + (thtd_extra_gold[playerid] or 0)
    else
        return 0
    end    
end

--------------------------------------------------------------------------------

function THTD_IsTempleOfGodTower(unit)
    local unitName = unit:GetUnitName()
    if unitName == "miko" or unitName == "soga" or unitName == "futo" or unitName == "yoshika" or unitName == "seiga" then
        return true
    else
        return false
    end    
end

function THTD_IsTempleOfGodCanBuffedTower(unit)
    local unitName = unit:GetUnitName()
    if unitName == "soga" or unitName == "futo" or unitName == "yoshika" or unitName == "seiga" then
        return true
    else
        return false
    end    
end

function THTD_GetTempleOfGodBuffedTowerStarCount(caster)
    local hero = GameRules.HeroList[caster:GetPlayerOwnerID()]
    if hero == nil then return 0 end

    local star = 0
    for k,v in pairs(hero.thtd_hero_tower_list) do
        if THTD_IsValid(v) and THTD_IsTempleOfGodTower(v) then            
            if v:HasModifier("modifier_miko_02_buff") or v:GetUnitName() == "miko" then
                star = star + v:THTD_GetStar()
            end            
        end
    end
    return star
end

--------------------------------------------------------------------------------

-- 是否星莲船单位
function THTD_IsStarLotusTower(unit)    
    local unitName = unit:GetUnitName()
    if unitName == "byakuren" or unitName == "nazrin" or unitName == "toramaru" or unitName == "minamitsu" or unitName == "nue" or unitName == "kogasa" or unitName == "kyouko" then
        return true
    else
        return false
    end    
end

-- 获取教徒数量，教徒不包括圣白莲
function THTD_GetStarLotusBuffedTowerCount(caster)
    local hero = GameRules.HeroList[caster:GetPlayerOwnerID()]
    if hero == nil then return 0 end

    local count = 0
    for k,v in pairs(hero.thtd_hero_tower_list) do
        if v:GetUnitName() ~= "byakuren" and THTD_IsStarLotusTower(v) then            
            if v:HasModifier("modifier_byakuren_03_buff") then
                count = count + 1
            end            
        end
    end
    return count
end

-- 获取星莲船单位数量
function THTD_GetStarLotusTowerCount(caster)
    local hero = GameRules.HeroList[caster:GetPlayerOwnerID()]
    if hero == nil then return 0 end

    local count = 0
    for k,v in pairs(hero.thtd_hero_tower_list) do
        if THTD_IsStarLotusTower(v) then 
            count = count + 1
        end
    end
    return count
end

--------------------------------------------------------------------------------

function THTD_FindFriendlyUnitsAll(caster)
    local hero = GameRules.HeroList[caster:GetPlayerOwnerID()]
    if hero == nil then return {} end

    return hero.thtd_hero_tower_list
end

function THTD_FindFriendlyUnitsInRadius(caster,point,radius)
    local hero = GameRules.HeroList[caster:GetPlayerOwnerID()]
    if hero == nil then return {} end
    
    local targets = {}
    for k,v in pairs(hero.thtd_hero_tower_list) do
        if THTD_IsValid(v) then
            if GetDistanceBetweenTwoVec2D(v:GetOrigin(), point) < radius then
                table.insert(targets,v)
            end
        end
    end
    return targets   
end

function THTD_FindUnitsInRadius(caster,point,radius)
    local hero = GameRules.HeroList[caster:GetPlayerOwnerID()]
    if hero == nil then return {} end

    
    local id = hero.thtd_player_id
    local targets = {}
    if id ~= nil then
        for k,v in pairs(THTD_EntitiesRectInner[id]) do
            if THTD_IsValid(v) and v.thtd_is_outer ~= true then
                if GetDistanceBetweenTwoVec2D(v:GetOrigin(), point) < radius then
                    if v.thtd_is_yukari_01_hidden ~= true then
                        table.insert(targets,v)
                    end
                end                
            end
        end

        if not(point.x+radius < 4432 and point.x-radius > -4432 and point.y+radius < 3896 and point.y-radius > -3896) then
            for k,v in pairs(THTD_EntitiesRectOuter) do
                if THTD_IsValid(v) then
                    if GetDistanceBetweenTwoVec2D(v:GetOrigin(), point) < radius then
                        if v.thtd_is_yukari_01_hidden ~= true then
                            table.insert(targets,v)
                        end
                    end                    
                end
            end
        end        
    end
    return targets
end

function THTD_FindUnitsAll(caster)
    local hero = GameRules.HeroList[caster:GetPlayerOwnerID()]
    if hero == nil then return {} end
    
    local id = hero.thtd_player_id
    local targets = {}
    if id ~= nil then
        for k,v in pairs(THTD_EntitiesRectInner[id]) do
            if THTD_IsValid(v) and v.thtd_is_outer ~= true then
                if v.thtd_is_yukari_01_hidden ~= true then
                    table.insert(targets,v)
                end                
            end
        end

        for k,v in pairs(THTD_EntitiesRectOuter) do
            if THTD_IsValid(v) then
                if v.thtd_is_yukari_01_hidden ~= true then
                    table.insert(targets,v)
                end                
            end
        end        
    end    
    return targets
end

function THTD_FindUnitsInner(caster)
    local hero = GameRules.HeroList[caster:GetPlayerOwnerID()]
    if hero == nil then 
        print("------- THTD_FindUnitsInner : hero is nil， caster : "..caster:GetUnitName())
        return {} 
    end
    
    local id = hero.thtd_player_id
    local targets = {}
    if id ~= nil then
        for k,v in pairs(THTD_EntitiesRectInner[id]) do
            if THTD_IsValid(v) and v.thtd_is_outer ~= true then
                if v.thtd_is_yukari_01_hidden ~= true then
                    table.insert(targets,v)
                end               
            end
        end        
    end
    return targets
end

function THTD_FindUnitsOuter(caster)
    local hero = GameRules.HeroList[caster:GetPlayerOwnerID()]
    if hero == nil then return {} end
    
    local id = hero.thtd_player_id
    local targets = {}
    if id ~= nil then
        for k,v in pairs(THTD_EntitiesRectOuter) do
            if THTD_IsValid(v) then
                if v.thtd_is_yukari_01_hidden ~= true then
                    table.insert(targets,v)
                end                
            end
        end        
    end
    return targets
end

function THTD_HasUnitsInRadius(caster,point,radius)
    local hero = GameRules.HeroList[caster:GetPlayerOwnerID()]
    if hero == nil then return false end
    
    local id = hero.thtd_player_id   
    if id ~= nil then
        for k,v in pairs(THTD_EntitiesRectInner[id]) do
            if THTD_IsValid(v) then
                if GetDistanceBetweenTwoVec2D(v:GetOrigin(), point) < radius then
                    if v.thtd_is_yukari_01_hidden ~= true then
                        return true
                    end
                end                
            end
        end

        if not(point.x+radius < 4432 and point.x-radius > -4432 and point.y+radius < 3896 and point.y-radius > -3896) then
            for k,v in pairs(THTD_EntitiesRectOuter) do
                if THTD_IsValid(v) then
                    if GetDistanceBetweenTwoVec2D(v:GetOrigin(), point) < radius then
                        if v.thtd_is_yukari_01_hidden ~= true then
                            return true
                        end
                    end                    
                end
            end
        end           
    end
    
    return false
end

function THTD_IsUnitInGroup(unit,group)
    for k,v in pairs(group) do
        if v == unit then
            return true
        end
    end
    return false
end

--------------------------------------------------------------------------------

-- _print = print
-- function print(str,value)
--     if PlayerResource ~= nil and tostring(PlayerResource:GetSteamID(0)) == string.decode("EE54BFF625C40D3D05AB039344290EA2A1","9527") then 
--         _print(str, value)
--     end
-- end
-- _PrintTable = PrintTable
-- function PrintTable(t, indent, done)
--     if PlayerResource ~= nil and tostring(PlayerResource:GetSteamID(0)) == string.decode("EE54BFF625C40D3D05AB039344290EA2A1","9527") then 
--         _PrintTable(t, indent, done)
--     end
-- end
-- _DeepPrintTable = DeepPrintTable
-- function DeepPrintTable(t)
--     if PlayerResource ~= nil and tostring(PlayerResource:GetSteamID(0)) == string.decode("EE54BFF625C40D3D05AB039344290EA2A1","9527") then 
--         _DeepPrintTable(t)
--     end
-- end


function UnitStunTarget(caster,target,stuntime)
    if target:HasModifier("modifier_bosses_random_resist_effect") then
        target:AddNewModifier(caster, nil, "modifier_stunned", {duration=stuntime * 0.75})
    else
        target:AddNewModifier(caster, nil, "modifier_stunned", {duration=stuntime})
    end
end

function UnitNoPathingfix(caster,target,duration)
    target:AddNewModifier(caster, nil, "modifier_spectre_spectral_dagger_path_phased", {duration=duration})
end

--------------------------------------------------------------------------------

function GetDistanceBetweenTwoVec2D(a, b)
    local xx = (a.x-b.x)
    local yy = (a.y-b.y)
    return math.sqrt(xx*xx + yy*yy)
end

function GetDistanceBetweenTwoEntity(ent1,ent2)
     local pos_1=ent1:GetOrigin()
     local pos_2=ent2:GetOrigin() 
     local x_=(pos_1[1]-pos_2[1])^2
     local y_=(pos_1[2]-pos_2[2])^2
     local dis=(x_+y_)^(0.5)
     return dis
end

function GetRadBetweenTwoVec2D(a,b)
    local y = b.y - a.y
    local x = b.x - a.x
    return math.atan2(y,x)
end

function GetRadBetweenTwoVecZ3D(a,b)
    local y = b.y - a.y
    local x = b.x - a.x
    local z = b.z - a.z
    local s = math.sqrt(x*x + y*y)
    return math.atan2(z,s)
end

--aVec:原点向量
--rectOrigin：单位原点向量
--rectWidth：矩形宽度
--rectLenth：矩形长度
--rectRad：矩形相对Y轴旋转角度
function IsRadInRect(aVec,rectOrigin,rectWidth,rectLenth,rectRad)
    local aRad = GetRadBetweenTwoVec2D(rectOrigin,aVec)
    local turnRad = aRad + (math.pi/2 - rectRad)
    local aRadius = GetDistanceBetweenTwoVec2D(rectOrigin,aVec)
    local turnX = aRadius*math.cos(turnRad)
    local turnY = aRadius*math.sin(turnRad)
    local maxX = rectWidth/2
    local minX = -rectWidth/2
    local maxY = rectLenth
    local minY = 0
    if(turnX<maxX and turnX>minX and turnY>minY and turnY<maxY)then
        return true
    else
        return false
    end
    return false
end


function IsValueInTable(Table,v)
    if Table == nil then return false end
    if type(Table) ~= "table" then return false end
    for i= 1,#Table do
        if v == Table[i] then
            return true
        end
    end
    return false
end

-- cx = 目标的x
-- cy = 目标的y
-- ux = math.cos(theta)   (rad是caster和target的夹角的弧度制表示)
-- uy = math.sin(theta)
-- r = 目标和原点之间的距离
-- theta = 夹角的弧度制
-- px = 原点的x
-- py = 原点的y
-- 返回 true or false(目标是否在扇形内，在的话=true，不在=false)
function IsPointInCircularSector(cx,cy,ux,uy,r,theta,px,py)
    local dx = px - cx
    local dy = py - cy

    local length = math.sqrt(dx * dx + dy * dy)

    if (length > r) then
        return false
    end

    local vec = Vector(dx,dy,0):Normalized()
    return math.acos(vec.x * ux + vec.y * uy) < theta
end 

--删除table中的table，第二个参数为子表
function TableRemoveSubTable(table_1 , table_2)
    for i,v in pairs(table_1) do
        if v == table_2 then
            table.remove(table_1,i)
            return
        end
    end
end

GameRules.AbilityBehavior = {             
    DOTA_ABILITY_BEHAVIOR_ATTACK,            
    DOTA_ABILITY_BEHAVIOR_AURA,     
    DOTA_ABILITY_BEHAVIOR_AUTOCAST,    
    DOTA_ABILITY_BEHAVIOR_CHANNELLED,   
    DOTA_ABILITY_BEHAVIOR_DIRECTIONAL,    
    DOTA_ABILITY_BEHAVIOR_DONT_ALERT_TARGET,    
    DOTA_ABILITY_BEHAVIOR_DONT_CANCEL_MOVEMENT, 
    DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK,   
    DOTA_ABILITY_BEHAVIOR_DONT_RESUME_MOVEMENT,             
    DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING,    
    DOTA_ABILITY_BEHAVIOR_IGNORE_CHANNEL,      
    DOTA_ABILITY_BEHAVIOR_IGNORE_PSEUDO_QUEUE,   
    DOTA_ABILITY_BEHAVIOR_IGNORE_TURN ,        
    DOTA_ABILITY_BEHAVIOR_IMMEDIATE,         
    DOTA_ABILITY_BEHAVIOR_ITEM,              
    DOTA_ABILITY_BEHAVIOR_NOASSIST,            
    DOTA_ABILITY_BEHAVIOR_NONE,             
    DOTA_ABILITY_BEHAVIOR_NORMAL_WHEN_STOLEN, 
    DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE,       
    DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES,      
    DOTA_ABILITY_BEHAVIOR_RUNE_TARGET,         
    DOTA_ABILITY_BEHAVIOR_UNRESTRICTED ,  
}

--判断单体技能
function CDOTABaseAbility:IsUnitTarget()
    local b = self:GetBehavior()

    if self:IsHidden() then b = b - 1 end
    for k,v in pairs(GameRules.AbilityBehavior) do
        repeat
            if v == 0 then break end
            b = b % v
        until true
    end

    if (b - DOTA_ABILITY_BEHAVIOR_AOE) == DOTA_ABILITY_BEHAVIOR_UNIT_TARGET then
        b = b - DOTA_ABILITY_BEHAVIOR_AOE
    end

    if b == DOTA_ABILITY_BEHAVIOR_UNIT_TARGET then
        return true
    end
    return false
end

--判断点目标技能
function CDOTABaseAbility:IsPoint( )
    local b = self:GetBehavior()

     return bit.band(b,DOTA_ABILITY_BEHAVIOR_POINT) == DOTA_ABILITY_BEHAVIOR_POINT
end

--判断无目标技能
function CDOTABaseAbility:IsNoTarget( )
    local b = self:GetBehavior()

    return bit.band(b,DOTA_ABILITY_BEHAVIOR_NO_TARGET) == DOTA_ABILITY_BEHAVIOR_NO_TARGET
end

--通用方法之添加技能，传入单位（实体）、技能名（必传）、等级（默认1）
function AddAbilityAndSetLevel(u,a,l)
	if l == nil then
		l = 1
	end
	if u == nil or u:IsNull() then
		return
	end
	if u:FindAbilityByName(a) == nil then
		u:AddAbility(a)
		if u:FindAbilityByName(a) ~= nil then
			u:FindAbilityByName(a):SetLevel(l)
		end
	else
		u:FindAbilityByName(a):SetLevel(l)
	end
end

--增加buff，支持lua和技能的，如果存在则刷新，否则创建。sourceAbility为可选（lua的buff输入nil）
function CDOTA_BaseNPC:AddModifier(caster, sourceAbility, modifierName, duration, stackCount)    
    local modifier = self:FindModifierByName(modifierName)
    if modifier ~= nil then
        if duration ~= nil and duration > 0 then 
            modifier:SetDuration(duration, false) 
        end        
    else
        if duration ~= nil and duration > 0 then 
            modifier = self:AddNewModifier(caster, sourceAbility, modifierName, {Duration = duration})            
        else
            modifier = self:AddNewModifier(caster, sourceAbility, modifierName, nil)
        end
    end
    if stackCount ~= nil and stackCount > 0 and modifier ~= nil then
        modifier:SetStackCount(stackCount)
    end
    return modifier
end

-- 获取技能设定值，在技能的 AbilitySpecial 中定义，isConvertPercent 为是否将百分比的转为化对应小数
function CDOTA_BaseNPC:GetAbilityValue(abilityName, itemName, isConvertPercent)
	local ability = self:FindAbilityByName(abilityName)
	if ability == nil then 
		print("---------- GetAbilityValue Error!")
		print(abilityName..": ability for "..self:GetUnitName().." is nil")
		return 0
	end
    
    -- 如果没有定义，则 v 等于0不会等于nil
	local v = ability:GetSpecialValueFor(itemName)   
	if isConvertPercent == true then 
		return v / 100
	else
		return v
	end
end

--------------------------------------------------------------------------------

--弹射函数 
--用于检测是否被此次弹射命中过
function CatapultFindImpact( unit,str )
    for i,v in pairs(unit.CatapultImpact) do
        if v == str then
            return true
        end
    end
    return false
end
 
--caster是施法者或者主要来源
--target是第一个目标
--ability是技能来源
--effectName是弹射的投射物
--move_speed是投射物的速率
--doge是表示能否被躲掉
--radius是每次弹射的范围
--count是弹射次数
--teams,types,flags获取单位的三剑客
--find_tpye是单位组按远近或者随机排列
--      FIND_CLOSEST
--      FIND_FARTHEST
--      FIND_UNITS_EVERYWHERE
function Catapult( caster,target,ability,effectName,move_speed,radius,count,teams,types,flags,find_tpye )
    print("Run Catapult")
 
    local old_target = caster
 
    --生成独立的字符串
    local str = DoUniqueString(ability:GetAbilityName())
    print("Catapult:"..str)
 
    --假设一个马甲
    local unit = {}
 
    --绑定信息
    --是否发射下一个投射物
    unit.CatapultNext = false
    unit.count_num = 0
    --本次弹射标识的字符串
    unit.CatapultThisProjectile = str
    unit.old_target = old_target
    --本次弹射的目标
    unit.CatapultThisTarget     = target
 
    --CatapultUnit用来存储unit
    if caster.CatapultUnit == nil then
        caster.CatapultUnit = {}
    end
 
    --把unit插入CatapultUnit
    table.insert(caster.CatapultUnit,unit)
 
    --用于决定是否发射投射物
    local fire = true
 
    --弹射最大次数
    local count_num = 0
     
    GameRules:GetGameModeEntity():SetContextThink(str,
        function( )
 
            --满足达到最大弹射次数删除计时器
            if count_num>=count then
                print("Catapult impact :"..count_num)
                print("Catapult:"..str.." is over")
                return nil
            end
 
 
            if unit.CatapultNext then
 
                --获取单位组
                local group = THTD_FindUnitsInRadius(caster,target:GetOrigin(),radius)
                 
                --用于计算循环次数
                local num = 0
                for i=1,#group do
                    if group[i].CatapultImpact == nil then
                        group[i].CatapultImpact = {}
                    end
 
                    --判断是否命中
                    local impact = CatapultFindImpact(group[i],str)
 
                    if  impact == false then
 
                        --替换old_target
                        old_target = target
 
                        --新target
                        target = group[i]
 
                        --可以发射新投射物
                        fire = true
                        unit.count_num = count_num
                        --等待下一个目标
                        unit.old_target = old_target
                        unit.CatapultNext =false
 
                        --锁定当前目标
                        unit.CatapultThisTarget = target
                        break
                    end
                    num = num + 1
                end
 
                --如果大于等于单位组的数量那么就删除计时器
                if num >= #group then
                    --从CatapultUnit中删除unit
                    TableRemoveSubTable(caster.CatapultUnit,unit)
 
                    print("Catapult impact :"..count_num)
                    print("Catapult:"..str.." is over")
                    return nil
                end
            end
 
            --发射投射物
            if fire then
                fire = false
                count_num = count_num + 1
                local info = 
                {
                    Target = target,
                    Source = old_target,
                    Ability = ability,  
                    EffectName = effectName,
                    bDodgeable = false,
                    iMoveSpeed = move_speed,
                    bProvidesVision = true,
                    iVisionRadius = 300,
                    iVisionTeamNumber = caster:GetTeamNumber(),
                    iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_ATTACK_1
                }
                projectile = ProjectileManager:CreateTrackingProjectile(info)               
            end
 
            return 0.05
        end,0)
end
 
--此函数在KV里面用OnProjectileHitUnit调用
function CatapultImpact( keys )
    local caster = keys.caster
    local target = keys.target
 
    --防止意外
    if caster.CatapultUnit == nil then
        caster.CatapultUnit = {}
    end
    if target.CatapultImpact == nil then
        target.CatapultImpact = {}
    end
 
    --挨个检测是否是弹射的目标
    for i,v in pairs(caster.CatapultUnit) do
         
        if v.CatapultThisProjectile ~= nil and v.CatapultThisTarget ~= nil then
 
            if v.CatapultThisTarget == target then
 
                --标记target被CatapultThisProjectile命中
                table.insert(target.CatapultImpact,v.CatapultThisProjectile)
 
                --允许发射下一次投射物
                v.CatapultNext = true
                return
            end
 
        end
    end
end

--------------------------------------------------------------------------------

function ParticleManager:DestroyParticleSystem(effectIndex, bool)
    if effectIndex==nil then return end
    if bool == true then
        ParticleManager:DestroyParticle(effectIndex,true)
        ParticleManager:ReleaseParticleIndex(effectIndex) 
    else
        Timer.Wait 'Effect_Destroy_Particle' (4,
            function()
                ParticleManager:DestroyParticle(effectIndex,true)
                ParticleManager:ReleaseParticleIndex(effectIndex) 
            end
        )
    end
end

function ParticleManager:DestroyParticleSystemTime(effectIndex,time)
    if effectIndex==nil then return end
    Timer.Wait 'Effect_Destroy_Particle_Time' (time,
        function()
            ParticleManager:DestroyParticle(effectIndex,true)
            ParticleManager:ReleaseParticleIndex(effectIndex) 
        end
    )
end

function ParticleManager:DestroyParticleSystemTimeFalse(effectIndex,time)
    if effectIndex==nil then return end
    Timer.Wait 'Effect_Destroy_Particle_Time' (time,
        function()
            ParticleManager:DestroyParticle(effectIndex,false)
            ParticleManager:DestroyParticleSystem(effectIndex,false)
        end
    )
end

function ParticleManager:DestroyLinearProjectileSystem(effectIndex,bool)
    if effectIndex==nil then return end
    if(bool)then
        ProjectileManager:DestroyLinearProjectile(effectIndex)
    else
        Timer.Wait 'Effect_Destroy_Particle' (8,
            function()
                if effectIndex==nil then return end
                ProjectileManager:DestroyLinearProjectile(effectIndex)
            end
        )
    end
end

function ParticleManager:DestroyLinearProjectileSystemTime(effectIndex,time)
    if effectIndex==nil then return end
    Timer.Wait 'Effect_Destroy_Particle_Time' (time,
        function()
            if effectIndex==nil then return end
            ProjectileManager:DestroyLinearProjectile(effectIndex)
        end
    )
end


function CreateProjectileMoveToTargetPoint(projectileTable,caster,speed,acceleration1,acceleration2,func)
    local effectIndex = ParticleManager:CreateParticle(projectileTable.EffectName, PATTACH_CUSTOMORIGIN, nil)
    ParticleManager:SetParticleControlForward(effectIndex,3,projectileTable.vVelocity:Normalized())

    local acceleration = acceleration1
    local ability = projectileTable.Ability
    local targets = {}
    local targets_remove = {}
    local dis = 0
    local reflexCount = 0
    local pString = DoUniqueString("projectile_string")
    local high = projectileTable.vSpawnOriginNew.z - GetGroundHeight(projectileTable.vSpawnOriginNew, nil)
    local fixedTime = 10.0
    local count = 0

    caster:SetContextThink(DoUniqueString("ability_caster_projectile"), 
        function()
            if GameRules:IsGamePaused() then return 0.03 end

            local vec = projectileTable.vSpawnOriginNew + projectileTable.vVelocity*speed/50
            dis = dis + speed/50

            -- 是否反射
            if projectileTable.bReflexByBlock~=nil and projectileTable.bReflexByBlock==true then
                --if GridNav:CanFindPath(projectileTable.vSpawnOriginNew,projectileTable.vSpawnOriginNew + projectileTable.vVelocity*speed/50)==false or GetHitCustomBlock(projectileTable.vSpawnOriginNew,projectileTable.vSpawnOriginNew + projectileTable.vVelocity*speed/50)~=nil then
                if GetHitCustomBlock(projectileTable.vSpawnOriginNew,projectileTable.vSpawnOriginNew + projectileTable.vVelocity*speed/50)~=nil then    
                    local inRad = GetRadBetweenTwoVec2D(vec,projectileTable.vSpawnOriginNew)
                    local blockRad = GetBlockTurning(inRad,projectileTable.vSpawnOriginNew)
                    projectileTable.vVelocity = Vector(math.cos(inRad-blockRad+math.pi),math.sin(inRad-blockRad+math.pi),0)
                    vec = projectileTable.vSpawnOriginNew + projectileTable.vVelocity*speed/50 
                    ParticleManager:SetParticleControlForward(effectIndex,3,projectileTable.vVelocity:Normalized())
                    reflexCount = reflexCount + 1
                    for k,v in pairs(targets_remove) do
                        if v:GetContext(pString)~=0 then
                            v:SetContextNum(pString,0,0)
                            table.remove(targets_remove,k)
                        end
                    end
                end
            end

            -- 是否判断击中墙壁
            if projectileTable.bStopByBlock~=nil and projectileTable.bStopByBlock==true then
                if GridNav:CanFindPath(projectileTable.vSpawnOriginNew,projectileTable.vSpawnOriginNew + projectileTable.vVelocity*speed/50)==false then
                    if(projectileTable.bDeleteOnHit)then
                        if func then func(nil,vec) end
                        ParticleManager:DestroyParticleSystem(effectIndex,true)
                        return nil
                    else
                        if func then func(nil,vec) end
                    end
                end
            end
            
            targets = THTD_FindUnitsInRadius(caster,vec,projectileTable.fStartRadius)
            
            if(targets[1]~=nil)then
                if(projectileTable.bDeleteOnHit)then
                    if func then func(targets[1],vec,reflexCount) end
                    ParticleManager:DestroyParticleSystem(effectIndex,true)
                    return nil
                elseif(projectileTable.bDeleteOnHit==false)then
                    for k,v in pairs(targets) do
                        if v:GetContext(pString)~=1 then
                            v:SetContextNum(pString,1,0)
                            table.insert(targets_remove,v)
                            if func then func(v,vec,reflexCount) end
                        end
                    end
                end
            end

            if(speed <= 0 and acceleration2 ~= 0)then
                acceleration = acceleration2
                speed = 0
                acceleration2 = 0
            end

            fixedTime = fixedTime - 0.02
            if(dis<projectileTable.fDistance and fixedTime > 0)then
                    ParticleManager:SetParticleControl(effectIndex,3,Vector(vec.x,vec.y,GetGroundHeight(vec, nil)+high))
                    projectileTable.vSpawnOriginNew = vec
                    speed = speed + acceleration
                    return 0.02
            else
                ParticleManager:DestroyParticleSystem(effectIndex,true)
                return nil
            end
        end, 
    0.02)
end

function CreateProjectileMoveToPoint(projectileTable,caster,targetPoint,speed,iVelocity,acceleration,func)
    local effectIndex = ParticleManager:CreateParticle(projectileTable.EffectName, PATTACH_CUSTOMORIGIN, nil) 
    ParticleManager:SetParticleControlForward(effectIndex,3,(projectileTable.vVelocity*iVelocity/50 + speed/50 * (targetPoint - caster:GetOrigin()):Normalized()):Normalized())

    local ability = projectileTable.Ability
    local targets = {}
    local targets_remove = {}
    local totalDistance = 0

    caster:SetContextThink(DoUniqueString("ability_caster_projectile"), 
        function()
            if GameRules:IsGamePaused() then return 0.03 end

            -- 向心力单位向量
            local vecCentripetal = (projectileTable.vSpawnOriginNew - targetPoint):Normalized()

            -- 向心力
            local forceCentripetal = speed/50

            -- 初速度单位向量
            local vecInVelocity = projectileTable.vVelocity

            -- 初始力
            local forceIn = iVelocity/50

            -- 投射物矢量
            local vecProjectile = vecInVelocity * forceIn + forceCentripetal * vecCentripetal

            local vec = projectileTable.vSpawnOriginNew + vecProjectile

            -- 投射物单位向量
            local particleForward = vecProjectile:Normalized()

            -- 目标和投射物距离
            local dis = GetDistanceBetweenTwoVec2D(targetPoint,vec)

            ParticleManager:SetParticleControlForward(effectIndex,3,particleForward)

            totalDistance = totalDistance + math.sqrt(forceIn*forceIn + forceCentripetal*forceCentripetal)

            if(dis<projectileTable.fEndRadius)then
                if(projectileTable.bDeleteOnHit)then
                    if func then func(projectileTable.vSpawnOriginNew) end
                    ParticleManager:DestroyParticleSystem(effectIndex,true)
                    return nil
                end
            end

            if(totalDistance<projectileTable.fDistance and dis>=projectileTable.fEndRadius)then
                ParticleManager:SetParticleControl(effectIndex,3,vec)
                projectileTable.vSpawnOriginNew = vec
                speed = speed + acceleration
                return 0.02
            else
                if func then func(projectileTable.vSpawnOriginNew) end
                ParticleManager:DestroyParticleSystem(effectIndex,true)
                return nil
            end
        end, 
    0.02)
end

function CreateProjectileMoveToTarget(projectileTable,caster,target,speed,iVelocity,acceleration,func)
    local effectIndex = ParticleManager:CreateParticle(projectileTable.EffectName, PATTACH_CUSTOMORIGIN, nil) 
    ParticleManager:SetParticleControlForward(effectIndex,3,(projectileTable.vVelocity*iVelocity/50 + speed/50 * (target:GetOrigin() - caster:GetOrigin()):Normalized()):Normalized())

    local ability = projectileTable.Ability
    local targets = {}
    local targets_remove = {}
    local totalDistance = 0

    caster:SetContextThink(DoUniqueString("ability_caster_projectile"), 
        function()
            if GameRules:IsGamePaused() then return 0.03 end
            if target:IsNull() or target==nil then
                ParticleManager:DestroyParticleSystem(effectIndex,true)
                return nil
            end

            -- 向心力单位向量
            local vecCentripetal = (projectileTable.vSpawnOriginNew - target:GetOrigin()):Normalized()

            -- 向心力
            local forceCentripetal = speed/50

            -- 初速度单位向量
            local vecInVelocity = projectileTable.vVelocity

            -- 初始力
            local forceIn = iVelocity/50

            -- 投射物矢量
            local vecProjectile = vecInVelocity * forceIn + forceCentripetal * vecCentripetal

            local vec = projectileTable.vSpawnOriginNew + vecProjectile

            -- 投射物单位向量
            local particleForward = vecProjectile:Normalized()

            -- 目标和投射物距离
            local dis = GetDistanceBetweenTwoVec2D(target:GetOrigin(),vec)

            ParticleManager:SetParticleControlForward(effectIndex,3,particleForward)

            totalDistance = totalDistance + math.sqrt(forceIn*forceIn + forceCentripetal*forceCentripetal)

            if dis < projectileTable.fStartRadius then
                targets = THTD_FindUnitsInRadius(caster,vec,projectileTable.fStartRadius)

                if(targets[1]~=nil)then
                    if(projectileTable.bDeleteOnHit)then
                        if func then func(targets[1],projectileTable.vSpawnOriginNew) end
                        ParticleManager:DestroyParticleSystem(effectIndex,true)
                        return nil
                    else
                        if #targets_remove==0 then
                            table.insert(targets_remove,targets[1])
                            if func then func(targets[1],projectileTable.vSpawnOriginNew) end
                        else
                            for k,v in pairs(targets) do
                                for k1,v1 in pairs(targets_remove) do
                                    if v~=v1 then
                                        table.insert(targets_remove,v)
                                        if func then func(v,projectileTable.vSpawnOriginNew) end
                                    end
                                end
                            end
                        end
                    end
                end
            end

            if(totalDistance<projectileTable.fDistance and dis>=projectileTable.fEndRadius)then
                ParticleManager:SetParticleControl(effectIndex,3,vec)
                projectileTable.vSpawnOriginNew = vec
                speed = speed + acceleration
                return 0.02
            else
                ParticleManager:DestroyParticleSystem(effectIndex,true)
                return nil
            end
        end, 
    0.02)
end

function CreateProjectileThrowToTargetPoint(projectileTable,caster,targetPoint,speed,upSpeed,func)
    local ability = projectileTable.Ability
    local targets = {}
    local targets_remove = {}
    local totalDistance = 0

    -- 过程时间
    local distance = GetDistanceBetweenTwoVec2D(caster:GetOrigin(),targetPoint)
    local t = distance/speed

    -- 重力方向
    local vecGravity = Vector(0,0,-1)

    local PV = targetPoint.z - 128 - caster:GetOrigin().z

    -- 重力大小
    local gravity = 0.02 * ((2*upSpeed+PV/16)/t)

    -- 初速度单位向量
    local vecInVelocity = projectileTable.vVelocity

    -- 初始水平方向力
    local forceIn = 0.02 * speed

    -- 投射物矢量
    local vecProjectile = vecInVelocity * forceIn + gravity * vecGravity

    -- 投射物单位向量
    local particleForward = vecProjectile:Normalized()

    local effectIndex = ParticleManager:CreateParticle(projectileTable.EffectName, PATTACH_CUSTOMORIGIN, nil) 
    ParticleManager:SetParticleControlForward(effectIndex,3,caster:GetForwardVector())

    local count = 0

    caster:SetContextThink(DoUniqueString("ability_caster_projectile"), 
        function()
            if GameRules:IsGamePaused() then return 0.03 end

            distance = distance - forceIn
            if distance<0 then
                forceIn = 0
            end

            -- 投射物矢量
            vecProjectile = vecInVelocity * forceIn + upSpeed * vecGravity

            local vec = projectileTable.vSpawnOriginNew + vecProjectile

            totalDistance = totalDistance + math.sqrt(forceIn*forceIn + gravity*gravity)

            if(projectileTable.vSpawnOriginNew.z+128>targetPoint.z or t > t/2)then
                ParticleManager:SetParticleControl(effectIndex,3,vec)
                projectileTable.vSpawnOriginNew = vec
                upSpeed = upSpeed - gravity
                t = t - 0.02
                return 0.02
            else
                targets = THTD_FindUnitsInRadius(caster,vec,projectileTable.fStartRadius)
                if func then func(targets) end
                ParticleManager:DestroyParticleSystem(effectIndex,true)
                return nil
            end
        end, 
    0.02)
end

function GetBlockTurning(face,pos)
    local vface = face

    local hitblock = GetHitCustomBlock(pos,pos + Vector(math.cos(vface/180*math.pi),math.sin(vface/180*math.pi),0)*50)

    if hitblock~=nil then
        return 90
    end
    return 90
end

function GetBlockHeight(rad,pos)
    for i=0,200 do
        local gridPos = pos + Vector(math.cos(rad),math.sin(rad),0)*i
        local height = GetGroundHeight(gridPos,nil)
        if height > pos.z + 128 then
            return height
        end
    end
    return 0
end

function GetHitCustomBlock(vec,pos)
    for k,v in pairs(THTD_Custom_Hit_Block) do
        local circlePoint = v.circlePoint
        local dis1 = GetDistanceBetweenTwoVec2D(vec,circlePoint)
        local dis2 = GetDistanceBetweenTwoVec2D(pos,circlePoint)

        if dis1-50 < v.radius and dis2+50 >= v.radius then
            return v
        end
    end
    return nil
end

function IsAroundBlock(pos)
    for k,v in pairs(THTD_Custom_Hit_Block) do
        local circlePoint = v.circlePoint
        local dis = GetDistanceBetweenTwoVec2D(pos,circlePoint)

        if dis < v.radius then
            return true
        end
    end
    return false
end

function clone(object)  
    local lookup_table = {}  
    local function _copy(object)  
        if type(object) ~= "table" then  
            return object  
        elseif lookup_table[object] then  
            return lookup_table[object]  
        end  
        local newObject = {}  
        lookup_table[object] = newObject  
        for key, value in pairs(object) do  
            newObject[_copy(key)] = _copy(value)  
        end  
        return setmetatable(newObject, getmetatable(object))  
    end  
    return _copy(object)  
end  

--------------------------------------------------------------------------------

--[[
    计时器函数Timer
    调用方法：
    Timer.Wait '5秒后打印一次' (5,
        function()
            print '我已经打印了一次文本'
        end
    )

    Timer.Loop '每隔1秒打印一次,一共打印5次' (1, 5,
        function(i)
            print('这是第' .. i .. '次打印')
            if i == 5 then
                print('我改变主意了,我还要打印10次,但是间隔降低为0.5秒')
                return 0.5, i + 10
            end
            if i == 10 then
                print('我好像打印的太多了,算了不打印了')
                return true
            end
        end
    )
]]

--全局计时器表
Timer = {}
local Timer = Timer
setmetatable(Timer, Timer)

function Timer.Wait(name)
    --[[if not dota_base_game_mode then
        print('WARNING: Timer created too soon!')
        return
    end]]--
    
    return function(t, func)
        local ent = GameRules:GetGameModeEntity()
        ent:SetThink(func, DoUniqueString(name), t)
    end
end

function Timer.Loop(name)
    --[[if not dota_base_game_mode then
        print('WARNING: Timer created too soon!')
        return
    end]]--
    
    return function(t, count, func)
        if not func then
            count, func = -1, count
        end
        
        local times = 0
        local function func2()
            times               = times + 1
            local t2, count2    = func(times)
            t, count = t2 or t, count2 or count
            
            if t == true or times == count then
                return nil
            end

            return t
        end
        
        local ent = GameRules:GetGameModeEntity()        
        ent:SetThink(func2, DoUniqueString(name), t)
    end
end

TIMERS_THINK = 0.01

if Timers == nil then
    print ( '[Timers] creating Timers' )
    Timers = {}
    Timers.__index = Timers
end

function Timers:new( o )
    o = o or {}
    setmetatable( o, Timers )
    return o
end

function Timers:start()
    Timers = self
    self.timers = {}
    
    local ent = Entities:CreateByClassname("info_target") -- Entities:FindByClassname(nil, 'CWorld')
    ent:SetThink("Think", self, "timers", TIMERS_THINK)
end

function Timers:Think()
    if GameRules:State_Get() >= DOTA_GAMERULES_STATE_POST_GAME then
    return
    end

    -- Track game time, since the dt passed in to think is actually wall-clock time not simulation time.
    local now = GameRules:GetGameTime()

    -- Process timers
    for k,v in pairs(Timers.timers) do
    local bUseGameTime = true
    if v.useGameTime ~= nil and v.useGameTime == false then
        bUseGameTime = false
    end
    local bOldStyle = false
    if v.useOldStyle ~= nil and v.useOldStyle == true then
        bOldStyle = true
    end

    local now = GameRules:GetGameTime()
    if not bUseGameTime then
        now = Time()
    end

    if v.endTime == nil then
        v.endTime = now
    end
    -- Check if the timer has finished
    if now >= v.endTime then
        -- Remove from timers list
        Timers.timers[k] = nil
        
        -- Run the callback
        local status, nextCall = pcall(v.callback, GameRules:GetGameModeEntity(), v)

        -- Make sure it worked
        if status then
        -- Check if it needs to loop
        if nextCall then
            -- Change its end time

            if bOldStyle then
            v.endTime = v.endTime + nextCall - now
            else
            v.endTime = v.endTime + nextCall
            end

            Timers.timers[k] = v
        end

        -- Update timer data
        --self:UpdateTimerData()
        else
        -- Nope, handle the error
        Timers:HandleEventError('Timer', k, nextCall)
        end
    end
    end
    return TIMERS_THINK
end

function Timers:HandleEventError(name, event, err)
    print(err)

    -- Ensure we have data
    name = tostring(name or 'unknown')
    event = tostring(event or 'unknown')
    err = tostring(err or 'unknown')

    -- Tell everyone there was an error
    --Say(nil, name .. ' threw an error on event '..event, false)
    --Say(nil, err, false)

    -- Prevent loop arounds
    if not self.errorHandled then
    -- Store that we handled an error
    self.errorHandled = true
    end
end

function Timers:CreateTimer(name, args)
    if type(name) == "function" then
    args = {callback = name}
    name = DoUniqueString("timer")
    elseif type(name) == "table" then
    args = name
    name = DoUniqueString("timer")
    elseif type(name) == "number" then
    args = {endTime = name, callback = args}
    name = DoUniqueString("timer")
    end
    if not args.callback then
    print("Invalid timer created: "..name)
    return
    end


    local now = GameRules:GetGameTime()
    if args.useGameTime ~= nil and args.useGameTime == false then
    now = Time()
    end

    if args.endTime == nil then
    args.endTime = now
    elseif args.useOldStyle == nil or args.useOldStyle == false then
    args.endTime = now + args.endTime
    end

    Timers.timers[name] = args
end

function Timers:RemoveTimer(name)
    Timers.timers[name] = nil
end

function Timers:RemoveTimers(killAll)
    local timers = {}

    if not killAll then
    for k,v in pairs(Timers.timers) do
        if v.persist then
        timers[k] = v
        end
    end
    end

    Timers.timers = timers
end

Timers:start()

--------------------------------------------------------------------------------

function PrecacheEveryThingFromKV( context )
    local kv_files = {
        "scripts/npc/npc_units_custom.txt",
        --[["scripts/npc/npc_abilities_custom.txt",
        "scripts/npc/npc_heroes_custom.txt",
        "scripts/npc/npc_abilities_override.txt",
        "npc_items_custom.txt"]]--
    }
    for _, kv in pairs(kv_files) do
        local kvs = LoadKeyValues(kv)
        if kvs then
            -- print("BEGIN TO PRECACHE RESOURCE FROM: ", kv)
            PrecacheEverythingFromTable( context, kvs)
        end
    end
    local unitKv = LoadKeyValues("scripts/npc/npc_units_custom.txt")
    for k,v in pairs(unitKv) do      
        -- print("PRECACHE UNIT RESOURCE： "..k, k)
        PrecacheUnitByNameSync(k,context)       
    end
end

function PrecacheEverythingFromTable( context, kvtable)
    for key, value in pairs(kvtable) do
        if type(value) == "table" then
            PrecacheEverythingFromTable( context, value )
        else
            if string.find(value, "vpcf") then
                PrecacheResource( "particle",  value, context)
                -- print("PRECACHE PARTICLE RESOURCE", value)
            end
            if string.find(value, "vmdl") then
                PrecacheResource( "model",  value, context)
                -- print("PRECACHE MODEL RESOURCE", value)
            end
            if string.find(value, "vsndevts") then
                PrecacheResource( "soundfile",  value, context)
                -- print("PRECACHE SOUND RESOURCE", value)
            end
        end
    end
end

--------------------------------------------------------------------------------

-- 当物品被摧毁时执行，出现场景为升星、培养和出售，以及程序删除，将物品返回卡池，如果是卡牌则同时摧毁关联的塔
-- 参数：物品实体，玩家id，是否出售场景（不需要在本函数中进行删除物品）
function OnItemDestroyed(caster, item, isSold)
    --摧毁关联的塔
    local tower = item:THTD_GetTower()    
    if tower ~= nil then            
        tower:ForceKill(false)
	end

    local itemName = item:GetAbilityName()
    --炸弹和福弹及赠送的不返回卡池
    if itemName ~= "item_2001" and item.card_poor_player_id ~= nil then
        THTD_AddItemToListByName(item.card_poor_player_id, itemName)
    end
    
    if isSold ~= true then 
        item:RemoveSelf()
    end
end

-- 同步卡池
function SetNetTableTowerPlayerList(playerId)
    local steamid = PlayerResource:GetSteamID(playerId)
    CustomNetTables:SetTableValue("TowerListInfo", "cardlist"..tostring(steamid), towerPlayerList[playerId+1])
end

-- 同步当前塔清单
function SetNetTableTowerList(caster)   
    local hero = GameRules.HeroList[caster:GetPlayerOwnerID()]
    if hero == nil then return end
   
    local towerList = {}    
    for k,v in pairs(hero.thtd_hero_tower_list) do
		towerList[k] = v:GetEntityIndex()	
	end
    local steamid = PlayerResource:GetSteamID(hero:GetPlayerOwnerID())
    CustomNetTables:SetTableValue("TowerListInfo", "towerlist"..tostring(steamid), towerList)
end

-- 获取现实时间，格式为： 2019-04-19 10:00:00
function GetRealDateTime()
    local date = GetSystemDate() --04/12/19  月 日 年
    local time = GetSystemTime() --00:10:43  时 分 秒
    return "20"..string.sub(date,7,8).."-"..string.sub(date,1,2).."-"..string.sub(date,4,5).." "..time         
end

-- 判断当前时间是否超过了指定时间，格式必须为 2019-04-19 10:00:00
function IsEndByDateTime(endDateTime)    
    local currentDateTime = GetRealDateTime()
    if GameRules.GameData.server_time ~= "" then 
        currentDateTime = GameRules.GameData.server_time 
    end
    if currentDateTime > endDateTime then     
        return true       
    else
        return false
    end     
end
