
local HasTimePassedInHours = VLF.HasTimePassedInHours
local GetWeatherCondition = VLF.GetWeatherCondition
local AddObjectToSquare = VLF.AddObjectToSquare
local UpdatePlayerGMDKnewCarpetBombing = VLECB.UpdatePlayerGMDKnewCarpetBombing
local GetModDataCarpetBombingEvent = VLECB.GetModDataCarpetBombingEvent
local PlayBombEffect = VLECB.PlayBombEffect
local CaldistanceCoordinates = VLF.CaldistanceCoordinates
local RegisterCarpetBombingEvent = VLECB.RegisterCarpetBombingEvent
local UpdateDataBombsToPlan = VLECB.UpdateDataBombsToPlan
local PlaySound_B52Appeared = VLECB.PlaySound_B52Appeared
local BroadCastAboutCarpetBombing = VLECB.BroadCastAboutCarpetBombing
local SetNormalGameSpeed = VLF.SetNormalGameSpeed
local ClearDataCarpetBombingEvent = VLECB.ClearDataCarpetBombingEvent
local EventCB_RemoveQuestList = VLECB.EventCB_RemoveQuestList
local KillZombie = VLZB.KillZombie
local IsMultiplayerMode = VLF.IsMultiplayerMode
local IsAnyPlayerNearXY = VLPL.IsAnyPlayerNearXY
local CalculateDistanceXY = VLF.CalculateDistanceXY
local RemoveDataBombs = VLECB.RemoveDataBombs
local HelicopterBombWarning = VLECB.HelicopterBombWarning

local needHoursToStartCBEvent = VLECB.needHoursToStartCBEvent
--local minBombCount = 100		--FIXME: 50
--local maxBombCount = 200		--FIXME: 70
local notSpam = {}
local bombEffectRadius = VLECB.bombEffectRadius
local bomb_DropAfterHours = VLECB.bomb_DropAfterHours

local ashSprites = {"floors_burnt_01_1", "floors_burnt_01_2"}

--Thêm đống tro
local function AddAshToSquare(square)
	local spriteName = ashSprites[ZombRand(2) + 1]
	AddObjectToSquare(square, spriteName, {isTransmit = true, x = -1, y = -1, z = 0, objType = "IsoObject"})
end

local carpetBombingRules = {
	{
		minDay = 0,        	-- từ ngày 0	--FIXME: 0
		cooldownDays = 2,	--FIXME: 2
		chance = 1 / 72	--FIXME: 1 / 72		-- ~1% mỗi tiếng	~trung bình 3 + 2 ngày 1 lần	~3 lần/ 15 ngày
	},
	{
		minDay = 30,      	-- sau 1 tháng	--FIXME: 30
		cooldownDays = 3,	--FIXME: 3
		chance = 1 / 144		--FIXME: 1 / 144		-- ~0.7% mỗi tiếng	~trung bình 6 + 3 ngày 1 lần	~6.67 lần/ 60 ngày
	},
	{
		minDay = 90,       -- sau 3 tháng
		cooldownDays = 4,
		chance = 1 / 192	-- ~0.5% mỗi tiếng	~trung bình 8 + 4 ngày 1 lần	~7.5 lần/ 90 ngày
	},
	{
		minDay = 180,       -- sau 6 tháng
		cooldownDays = 6,
		chance = 1 / 240	-- ~0.4% mỗi tiếng	~trung bình 10 + 6 ngày 1 lần	~11.6 lần/ 185 ngày
	},
	{
		minDay = 365,      -- sau 1 năm
		cooldownDays = 30,
		chance = 1 / 3600	-- ~1.66% mỗi tiếng	~trung bình 150 + 30 ngày 1 lần
	}
}

--Chọn tỉ lệ và cooldown theo thời gian tồn tại của World
local function GetCarpetBombingRule(worldAgeDays)
	local selected = carpetBombingRules[1]

	for _, rule in ipairs(carpetBombingRules) do
		if worldAgeDays >= rule.minDay then
			selected = rule
		else
			break
		end
	end

	return selected
end

--Danh sách các khu vực thả bomb
local carpetBombingAreas = {
	{ x = 3474, y = 10959, x2 = 3859, y2 = 11339, minBombCount = 50, maxBombCount = 100, weight = 100 },		--1		--Echo Creek	--100
	{ x = 11296, y = 6748, x2 = 12185, y2 = 7160, minBombCount = 100, maxBombCount = 200, weight = 200 },		--2		--West Point	--200
	{ x = 10671, y = 9511, x2 = 10919, y2 = 10684, minBombCount = 100, maxBombCount = 200, weight = 200 },		--3		--Muldraugh		--200
	{ x = 9896, y = 12697, x2 = 10584, y2 = 13220, minBombCount = 100, maxBombCount = 200, weight = 200 },		--4		--March Ridge	--200	
	{ x = 8086, y = 11487, x2 = 8511, y2 = 11844, minBombCount = 70, maxBombCount = 140, weight = 140 },		--5		--Rosewood		--140	
	{ x = 7171, y = 8274, x2 = 7515, y2 = 8557, minBombCount = 50, maxBombCount = 100, weight = 100 },			--6		--Fallas Lake	--100	
	{ x = 6137, y = 5288, x2 = 6899, y2 = 5637, minBombCount = 100, maxBombCount = 200, weight = 200 },			--7		--Riverside		--200	
	{ x = 1740, y = 5825, x2 = 2361, y2 = 6646, minBombCount = 100, maxBombCount = 200, weight = 200 },			--8		--Brandenburg	--200	
	{ x = 360, y = 9688, x2 = 947, y2 = 9974, minBombCount = 75, maxBombCount = 150, weight = 150 },			--9		--Ekron			--150	
	{ x = 12011, y = 1204, x2 = 13030, y2 = 1952, minBombCount = 180, maxBombCount = 270, weight = 200 },		--10	--Louisville	NW	--200
	{ x = 12011, y = 1952, x2 = 13030, y2 = 2676, minBombCount = 180, maxBombCount = 270, weight = 200 },		--11	--Louisville	W	--200
	{ x = 12011, y = 2676, x2 = 13030, y2 = 3398, minBombCount = 180, maxBombCount = 270, weight = 200 },		--12	--Louisville	SW	--200
	{ x = 13030, y = 1204, x2 = 13795, y2 = 2334, minBombCount = 180, maxBombCount = 270, weight = 200 },		--13	--Louisville	NE	--200
	{ x = 13030, y = 2334, x2 = 13974, y2 = 3420, minBombCount = 180, maxBombCount = 270, weight = 200 },		--14	--Louisville	WE	--200
	{ x = 1826, y = 13908, x2 = 3217, y2 = 14982, minBombCount = 180, maxBombCount = 270, weight = 200 }		--15	--Irvington		--200
	--{ x = 12748, y = 1509, x2 = 12794, y2 = 1545, weight = 100 }		--11	--Irvington		--100	--TEST
}

--Chọn khu vực thả bomb
local function GetRandomAreaByWeight()
	local questMD_Events_CarpetBombing = GetModDataCarpetBombingEvent()
	local lastSelectedAreaIndex = questMD_Events_CarpetBombing["lastSelectedAreaIndex"] or nil
	
	local totalWeight = 0

	for _, area in ipairs(carpetBombingAreas) do
		totalWeight = totalWeight + (area.weight or 0)
	end

	if totalWeight <= 0 then return nil end
	
	--nếu chỉ có 1 area thì bắt buộc chọn
    if #carpetBombingAreas == 1 then
        questMD_Events_CarpetBombing["lastSelectedAreaIndex"] = 1
        return 1
    end

	--thử 10 lần RANDOM để lấy area khác area đã chọn lần trước 
	for attempt = 1, 10 do
		local roll = ZombRand(totalWeight)
		local current = 0

		for i, area in ipairs(carpetBombingAreas) do
			current = current + (area.weight or 0)
			if roll < current then
				if i ~= lastSelectedAreaIndex then
					questMD_Events_CarpetBombing["lastSelectedAreaIndex"] = i	--không cần transmit vì chỉ dùng ở server
					return i
				end
				break
			end
		end
	end
	
	--fallback: Random không có kết quả thì chọn area bằng cách duyệt tất cả area
    for i = 1, #carpetBombingAreas do
        if i ~= lastSelectedAreaIndex then
            questMD_Events_CarpetBombing["lastSelectedAreaIndex"] = i
            return i
        end
    end
	
	return nil
end

--Gửi, nhận tín hiệu Radio
local function SendBroadCast(bombPlanCode)
	if not isServer() then	--SP
		BroadCastAboutCarpetBombing(getPlayer(), bombPlanCode)
	end
	
	if isServer() then
		sendServerCommand("Event_CarpetBombing_C", "BroadCastBombingPlan_C", {bombPlanCode = bombPlanCode})
	end
end

local function GetBombPlanCode()	
	local questMD_Events_CarpetBombing = GetModDataCarpetBombingEvent()
    local plans = questMD_Events_CarpetBombing["Plan"]
	local gameTime = getGameTime()
    local bombPlanCode
    local isExisting = true
	local rNumber = 0
	local tries = 0
    local maxTries = 100

    while isExisting do
        bombPlanCode = math.floor(gameTime:getMinutesStamp() / 10) + rNumber
        isExisting = false
		
		if plans[bombPlanCode] then
			rNumber = ZombRand(1000, 2000)
			tries = tries + 1
			isExisting = true
        end
		
		if tries >= maxTries then
			return nil
		end
    end

    return bombPlanCode
end
	

--CALL EVENT --Tính tỉ lệ để kích hoạt event
function VLECB.TryTriggerCarpetBombing(alwaysTrigger)	--local isTrigger, bombPlanCode = TryTriggerCarpetBombing()
	local gameTime = getGameTime()
	local worldAgeDays = gameTime:getWorldAgeDaysSinceBegin()
	alwaysTrigger = alwaysTrigger or false
	
	local rule = GetCarpetBombingRule(worldAgeDays)

	--ModData để lưu cooldown
	local questMD_Events_CarpetBombing = GetModDataCarpetBombingEvent()
	
	local lastTriggerHour = questMD_Events_CarpetBombing["lastTriggerHour"] or -999999
	local cooldownHours = rule.cooldownDays * 24
	local currentHour = gameTime:getWorldAgeHours()

	--đang cooldown
	if not alwaysTrigger then
		if currentHour - lastTriggerHour < cooldownHours then
			return false, nil
		end
	end

	--tính tỉ lệ để kích hoạt event
	local r = ZombRandFloat(0, 1)

	if r <= rule.chance or alwaysTrigger == true then
		questMD_Events_CarpetBombing["lastTriggerHour"] = currentHour

		local gameTime = getGameTime()
		local bombPlanCode = GetBombPlanCode()
		if not bombPlanCode then return false, nil end
		
		questMD_Events_CarpetBombing["Plan"][bombPlanCode] = questMD_Events_CarpetBombing["Plan"][bombPlanCode] or {}
		
		--local bombDropAfterHours = 24
		local activateStartHour = currentHour
		
		--chọn địa điểm và lấy vị trí trung tâm
		local areaID = GetRandomAreaByWeight()
		local x, y, distAreaCheck
		if areaID then
			local area = carpetBombingAreas[areaID]
			x = math.floor((area.x + area.x2) / 2)
			y = math.floor((area.y + area.y2) / 2)
			
			distAreaCheck = CaldistanceCoordinates(x, y, area.x, area.y) + 60 --khoảng cách player kích hoạt load các địa điểm bomb nổ
		
			--print("Server---TryTriggerCarpetBombing---Triggered!---bombPlanCode, x, y, activateStartHour= ", bombPlanCode, x, y, activateStartHour)
			--print("------- getPlayer():teleportTo("..tostring(x)..","..tostring(y)..",0)")
			
			RegisterCarpetBombingEvent(areaID, x, y, bombPlanCode, activateStartHour, bomb_DropAfterHours, distAreaCheck)
			 
			--gửi tín hiệu qua radio thông báo ném bomb sau 24 giờ. 
			SendBroadCast(bombPlanCode)
		
			return true, bombPlanCode
		end
	end

	return false, nil
end
local TryTriggerCarpetBombing = VLECB.TryTriggerCarpetBombing

--Tạo chi tiết kế hoạch các vị trí nổ
local function CreateCarpetBombingPlan(bombPlanCode, bombCount, areaID, weatherCondition)
    if not bombPlanCode or not bombCount or bombCount <= 0 then
        return false
    end

	local bombs = {}
    local usedXY = {}  -- để tránh trùng tọa độ

    local gameTime = getGameTime()
    local startMinute = gameTime:getMinutesStamp() + 5  --5 phút ingame sau bắt đầu thả bomb

    local bombsPerMinute = 5

    local function isUsed(x, y)
        return usedXY[x .. "_" .. y] == true
    end

    local function markUsed(x, y)
        usedXY[x .. "_" .. y] = true
    end
	
	local area = carpetBombingAreas[areaID]
    local created = 0
    local tryLimit = bombCount * 10
	
	--tạo danh sách các điểm thả bomb: bombs
    for i = 1, tryLimit do
        if created >= bombCount then
            break
        end

        local rx = ZombRand(area.x, area.x2 + 1)
        local ry = ZombRand(area.y, area.y2 + 1)

        if not isUsed(rx, ry) then
            markUsed(rx, ry)

            local index = created
            local explodeMinute = startMinute + math.floor(index / bombsPerMinute)
			local bombID = tostring(startMinute) .. "_" .. tostring(created + 1)
			
			bombs[bombID] = {
                x = rx,
                y = ry,
                explodeMinute = explodeMinute,
				--exploded = false
            }
			
            created = created + 1
        end
    end

    if created < bombCount then
        print("[CarpetBombing] WARNING: NOT CREATE ENOUGH BOMBS", created)
    end
	
	--tính thời gian kết thúc chiến dịch
	local endBombingMinute = startMinute + math.floor(created / bombsPerMinute)

	--cập nhật danh sách vị trí thả bomb
	UpdateDataBombsToPlan(bombPlanCode, bombCount, bombs, weatherCondition, endBombingMinute)
	
	--yêu cầu các client play sound tiếng máy bay nếu đang ở gần khu vực chiến dịch
	if not isServer() then
		PlaySound_B52Appeared(getPlayer(), bombPlanCode)
	else
		sendServerCommand("Event_CarpetBombing_C", "PlaySound_B52Appeared_C", {bombPlanCode = bombPlanCode})
	end
	
    return true
end

local hour = 1

local function EveryHours_TriggerNSendBroadCast()
	--Sau 7 ngày mới kích hoạt event
	if HasTimePassedInHours(needHoursToStartCBEvent) ~= true then return end
	
	if hour % 3 == 0 then	
		local questMD_Events_CarpetBombing = GetModDataCarpetBombingEvent()
		
		--Gửi Broadcast thông báo Tất cả Bombing Plan mỗi 3 tiếng
		for bombPlanCode, data in pairs(questMD_Events_CarpetBombing["Plan"]) do
			if type(data) == "table" and data.activateStartHour then
				--nếu chưa đến thời gian thả Bomb thì gửi thông báo
				local currentHour = getGameTime():getWorldAgeHours()
				local elapsedHours = currentHour - (data.activateStartHour or currentHour)
				
				if elapsedHours < data.bombDropAfterHours then
					SendBroadCast(bombPlanCode)
				end
			end
		end
	end
	
	hour = hour + 1

	--Nếu đến giờ thả bomb
	local questMD_Events_CarpetBombing = GetModDataCarpetBombingEvent()
	local weatherCondition = GetWeatherCondition()
	
	for bombPlanCode, data in pairs(questMD_Events_CarpetBombing["Plan"]) do
		local areaID = data.areaID
		if type(data) == "table" and areaID and not data.dropped then
			local passedHour = getGameTime():getWorldAgeHours() - data.activateStartHour
			local bombDropInHours = data.bombDropAfterHours - passedHour
			
			--Thông báo trực tiếp cho player đang ở thành phố trước khi thả bomb 3 tiếng	
			if math.floor(passedHour + 0.5) == math.floor(data.bombDropAfterHours - 3) then	--FIXME: 3
				if not isServer() then
					HelicopterBombWarning(getPlayer(), bombPlanCode)
				else
					sendServerCommand("Event_CarpetBombing_C", "HelicopterBombWarning_C", {bombPlanCode = bombPlanCode})
				end
			end
			
			--Kiểm tra đã đến thời gian thả bomb chưa
			if passedHour >= data.bombDropAfterHours then
				SetNormalGameSpeed()

				--Kiểm tra thời tiết cực xấu -> Hủy
				if weatherCondition == "verybad" then
					print("Server---EveryHours_TriggerNSendBroadCast---bombPlanCode, weatherCondition= ", bombPlanCode, weatherCondition)
					ClearDataCarpetBombingEvent(bombPlanCode)
					
					if not isServer() then	--SP
						HaloTextHelper.addBadText(getPlayer(), getText("IGUI_VLQ_CarpetBombing_Cancel", bombPlanCode))
					end
					
				--Kiểm tra thời tiết trung bình -> Giảm 50% bomb count
				elseif weatherCondition == "bad" then
					local minBombCount, maxBombCount = carpetBombingAreas[areaID].minBombCount, carpetBombingAreas[areaID].maxBombCount
					local bombCount = math.floor(ZombRand(minBombCount, maxBombCount + 1) / 2)
					print("Server---EveryHours_TriggerNSendBroadCast---bombPlanCode, bombCount, weatherCondition= ", bombPlanCode, bombCount, weatherCondition)
					CreateCarpetBombingPlan(bombPlanCode, bombCount, areaID, weatherCondition)
					
					if not isServer() then	--SP
						HaloTextHelper.addGoodText(getPlayer(), getText("IGUI_VLQ_CarpetBombing_Start"))
					end
					
				--Thời tiết bình thường -> Thả 100% bomb count
				else
					local minBombCount, maxBombCount = carpetBombingAreas[areaID].minBombCount, carpetBombingAreas[areaID].maxBombCount
					local bombCount = ZombRand(minBombCount, maxBombCount + 1)
					print("Server---EveryHours_TriggerNSendBroadCast---bombPlanCode, bombCount, weatherCondition= ", bombPlanCode, bombCount, weatherCondition)
					CreateCarpetBombingPlan(bombPlanCode, bombCount, areaID, weatherCondition)
					
					if not isServer() then	--SP
						HaloTextHelper.addGoodText(getPlayer(), getText("IGUI_VLQ_CarpetBombing_Start"))
					end
				end
				
				EventCB_RemoveQuestList(bombPlanCode, weatherCondition)
			end
		end
	end
	
	--Kích hoạt event
	TryTriggerCarpetBombing()

end
Events.EveryHours.Remove(EveryHours_TriggerNSendBroadCast)
Events.EveryHours.Add(EveryHours_TriggerNSendBroadCast)

--============================================--

--Nổ bomb tại 1 square
local function BombDamageSquare(square, isBorder, isCenter, isSurroundingCenter, explodeNow, activateStartHour)
	local z = square:getZ()
	local objects = square:getObjects()
	explodeNow = explodeNow or false
	
	--print("Server---BombDamageSquare---isBorder, isCenter, isSurroundingCenter= ", isBorder, isCenter, isSurroundingCenter)
	--nếu square là ô nước thì bỏ qua
	if square:isWaterSquare() then return end	
	
	--Nếu load square khi trôi qua thời gian đủ lâu thì không add xác VÀ không kill zombie mà remove
	local function isHourPassed(number)
		if not activateStartHour or not number then return false end
		local currentHour = getGameTime():getWorldAgeHours()
		local h = currentHour - activateStartHour
		
		return h >= number and true or false
	end
	
	local passed30Days = isHourPassed(720)
	
	--Xóa xác có trước đó ở các square giữa
	if isCenter then
		local bodies = square:getDeadBodys()
		if bodies then
			for i = bodies:size() - 1, 0, -1 do
				local body = bodies:get(i)
				if body then
					square:removeCorpse(body, false)
				end
			end
		end
	end
	
	--Kill zombies NẾU chưa trôi qua 30 ngày từ khi thả bomb. Đã qua 30 ngày thì giả định xác zombie đã mất, zombie mới thay thế
	if not passed30Days then
		local zombies = square:getMovingObjects()
		for i = zombies:size() - 1, 0, -1 do
			local mo = zombies:get(i)
			if instanceof(mo, "IsoZombie") then
				KillZombie(mo)
			end
		end
	end
	
	--Phá hủy object:
	--Khi không phải viền bán kính nổ: Tầng 0 phá hết ngoại trừ sàn. Tầng 1+ phá hết
	if not isBorder then
		if z == 0 then
			square:ClearTileObjectsExceptFloor()
		else
			square:ClearTileObjects()
		end
	end
	
	--Burn floor tầng 0, VÀ burn quanh viền bán kính nổ
	if isBorder or (square:getFloor() and z == 0) then
		--square:Burn()
		square:Burn(true)
	end

	if z == 0 then
		--Add đống tro: ở giữa 100%, +-1 ô giữa 70%, còn lại 10%
		if isCenter == true then
			AddAshToSquare(square)
		elseif isSurroundingCenter == true then
			if ZombRand(100) < 70 then
				AddAshToSquare(square)
			end
		else
			if ZombRand(100) < 10 then
				AddAshToSquare(square)
			end
		end

		--Add xác zombie 2% NẾU chưa trôi qua 30 ngày từ lúc nổ
		--Nếu là nổ trực tiếp thì chỉ add xác khi cách player 40+ ô
		if not passed30Days then
			if ZombRand(100) < 2 then
				if explodeNow == true then
					local x, y = square:getX(), square:getY()
					if IsMultiplayerMode() then
						if not IsAnyPlayerNearXY(x, y, 40) then
							square:addCorpse(false)
						end
					else
						local player = getPlayer()
						if CalculateDistanceXY(x, y, player:getX(), player:getY()) >= 40 then
							square:addCorpse(false)
						end
					end
				else
					square:addCorpse(false)
				end
			end
		end
	end

	--Clear items
	square:removeAllWorldObjects()
end

--Lấy các square trong phạm vi ảnh hưởng của bomb để tiến hành nổ
local function DoExplosionDamage(centerSq, bombEffectRadius, explodeNow, activateStartHour)
    if not centerSq then return end
	
	explodeNow = explodeNow or false
    local cx, cy, cz = centerSq:getX(), centerSq:getY(), centerSq:getZ()
    local squares = {}
	
	--print("Server---DoExplosionDamage---cx, cy, cz= ", cx, cy, cz)
	
    for dz = -1, 1 do
        local targetZ = cz + dz

        --bỏ qua tầng < 0
        if targetZ >= 0 then
            local effectiveRadius = bombEffectRadius - math.abs(dz) * 3	--khác mỗi tầng thì giảm 3 ô bán kính ảnh hưởng
			
			--tầng trên 1 tầng thì giữ nguyên bán kính nổ để phá mái nhà
			if targetZ == (cz + 1) then
				effectiveRadius = bombEffectRadius
			end
			
            if effectiveRadius > 0 then
                for dx = -effectiveRadius, effectiveRadius do
                    for dy = -effectiveRadius, effectiveRadius do
						--tính khoảng cách từ dx, dy tới tâm 0, 0
                        local dist2D = math.sqrt(dx * dx + dy * dy)
						
						--chỉ lấy các sq trong bán kính ảnh hưởng --theo vòng tròn
                        if dist2D < effectiveRadius then
                            local sq = getCell():getGridSquare(cx + dx, cy + dy, targetZ)
                            if sq then
								--kiểm tra sq có phải nằm ở viền
                                --local isBorder = (dist2D >= (effectiveRadius - 1.5))	--là sq ở viền bán kính ảnh hưởng	
								local isBorder = (dist2D >= (effectiveRadius * 0.7))	--là sq ở viền bán kính ảnh hưởng	
								
								--kiểm tra sq có phải là ô trung tâm, hoặc vây quanh ô trung tâm
								local isCenter, isSurroundingCenter = false, false
								if sq == centerSq then
									isCenter = true
								else
									local surroundingSquares = centerSq:getSurroundingSquares()
									for i = 1, #surroundingSquares do
										local _sq = surroundingSquares[i]
										if sq == _sq then
											isSurroundingCenter = true
											break
										end
									end
								end
								
                                table.insert(squares, {
                                    sq = sq,
                                    dist = dist2D + math.abs(dz) * 10,	--thêm * 10 để khác tầng thì trọng số dist lớn hơn để xử lý sau. dist này chỉ dùng để so sánh khoảng cách sq dùng xử lý trước sau
                                    isBorder = isBorder,
									isCenter = isCenter,
									isSurroundingCenter = isSurroundingCenter
                                })
                            end
                        end
                    end
                end
            end
        end
    end

    -- Gần → xa
    table.sort(squares, function(a, b)
        return a.dist < b.dist
    end)

    for _, data in ipairs(squares) do
        BombDamageSquare(data.sq, data.isBorder, data.isCenter, data.isSurroundingCenter, explodeNow, activateStartHour)
    end
end

--Nổ Bomb
local function TryExplodeBomb(bombPlanCode, bombID, x, y, z, explodeNow, activateStartHour)
	if not bombPlanCode or not bombID then return end
	explodeNow = explodeNow or false

	local sq = getCell():getGridSquare(x, y, z)
	if not sq then return end

	if explodeNow == true then
		--tiếng thu hút zombie
		addSound(sq:getFloor(), sq:getX(), sq:getY(), sq:getZ(), 250, 2000)
		
		--tiếng nổ	--chuyển play ở các clients 
		--getSoundManager():PlayWorldSound("vl_explode", false, sq, 0.0, 200, 1, false)
	end
	
	--SERVER DAMAGE
	DoExplosionDamage(sq, bombEffectRadius, explodeNow, activateStartHour)
	
	--remove data bombID
	RemoveDataBombs(bombPlanCode, bombID)
end


--================= ONCLIENTCOMMAND =================--
VLOCC["Event_CarpetBombing"] = VLOCC["Event_CarpetBombing"] or {}

VLOCC["Event_CarpetBombing"]["RequestExplode"] = function(player, args)		--{bombPlanCode = bombPlanCode, bombID = bombID, x = _x, y = _y, z = _z, explodeNow = explodeNow, activateStartHour = data.activateStartHour}
	local bombID = args.bombID
	local x, y, z = args.x, args.y, args.z
	local var = tostring(getGameTime():getMinutesStamp()) .. tostring(x) .. tostring(y) .. tostring(z)
	if not notSpam[var] then
		notSpam[var] = true
		TryExplodeBomb(args.bombPlanCode, bombID, x, y, z, args.explodeNow, args.activateStartHour)
	end
end
	
VLOCC["Event_CarpetBombing"]["UpdatePlayerGMDCB"] = function(player, args)		--{bombPlanCode = bombPlanCode}
	UpdatePlayerGMDKnewCarpetBombing(player, args.bombPlanCode)
end
		
VLOCC["Event_CarpetBombing"]["RequestPlayBombEffect"] = function(player, args)		--{x = _x, y = _y, z = _z}
	if not isServer() then	--SP
		PlayBombEffect(args.x, args.y, args.z)
	else
		sendServerCommand("Event_CarpetBombing_C", "RequestPlayBombEffect_C", args)
	end
end








