require "vl_quest_utils"

VLF = VLF or {}
--VLF.checkNPCSpawned = VLF.checkNPCSpawned or {}

local Func = {}
Func.CalculateDistanceXY = VLF.CalculateDistanceXY
Func.GetZombieOnlineIDorUID = VLF.GetZombieOnlineIDorUID
Func.GetZombieByOnlineIDOrUID = VLF.GetZombieByOnlineIDOrUID
Func.GetPlayerID = VLF.GetPlayerID
Func.GetPlayerGlobalModData = VLF.GetPlayerGlobalModData
Func.IsMultiplayerMode = VLF.IsMultiplayerMode
Func.GetQuestGlobalModData = VLF.GetQuestGlobalModData
Func.AddBarricade = VLF.AddBarricade
Func.GetStaticCoordOfBuilding = VLF.GetStaticCoordOfBuilding
Func.DelayFunction = VLF.DelayFunction
Func.GetOutsideRoom = VLF.GetOutsideRoom
Func.SetZombieDefault = VLF.SetZombieDefault

local function TransmitVLQuestModData()
    ModData.transmit("VL_Quest")
    --print("Server-----------TransmitVLQuestModData= ") 
end
local function TransmitGlobalPlayerModData(pID)
    ModData.transmit("VL_Player_"..tostring(pID))
end

local function IsZombieExisting(uid)
    if not uid then return nil end
    local zombies = getCell():getZombieList()
    for i = 0, zombies:size()-1 do
        local zombie = zombies:get(i)
        if zombie then
            local zUID = zombie:getUID()
             
            if zUID and zUID == uid then
                --print("Server-----------IsZombieExisting= ", uid, zUID)
                return true 
            end
        end
    end
    return false
end

local function DeactiveNPCSpawnedData(qStep, zID)
    if not qStep or not zID then return end
    --local questMD = ModData.getOrCreate("VL_Quest")
    local questMD = Func.GetQuestGlobalModData({qStep = qStep})
    if questMD.NPC and questMD.NPC[qStep] then
        questMD.NPC[qStep]["spawned"] = false
        questMD.NPC[qStep]["uid"] = nil
        questMD.NPC[qStep]["zID"] = nil
        questMD.NPC[qStep]["x"] = nil
        questMD.NPC[qStep]["y"] = nil
        questMD.NPC[qStep]["z"] = nil
        questMD.NPCqStep[zID] = nil
        questMD.NPC[qStep]["sameQStep"] = nil
        questMD.NPC[qStep] = nil
        TransmitVLQuestModData()
    end
end
                
--Xóa moddata spawned của NPC khi zombie NPC không còn tồn tại trên bộ nhớ (tất cả client unload)
--24h ingame = 1h -> 1 minute ingame = 2.5s
local function EveryOneMinute_CheckNPCExist()
    --local questMD = ModData.getOrCreate("VL_Quest")
    local questMD = Func.GetQuestGlobalModData({qStep = nil})
    for qStep, data in pairs(questMD.NPC) do
        if data.spawned == true then
            local uid = data.uid
            local zID = questMD.NPC[qStep]["zID"]
            if zID then
                --Nếu vị trí NPC không giống lúc spawn +-5 ô thì --Xử lý trường hợp player teleport nên NPC chưa kịp xóa
                local x, y = questMD.NPC[qStep]["x"], questMD.NPC[qStep]["y"]
                local zombie = Func.GetZombieByOnlineIDOrUID(zID)
                if zombie and x and y then
                    local _x, _y = zombie:getX(), zombie:getY()
                    --Nếu NPC nằm ngoài điểm spawn +- 5 ô 
                    if (_x > (x + 5) or (_x < (x - 5))) and (_y > (y + 5) or (_y < (y - 5))) then
                        DeactiveNPCSpawnedData(qStep, zID)
                        Func.SetZombieDefault(zombie)
                        sendServerCommand("NPCQuestCM_C", "SetDefaultZombie_C", {zID = zID})
                        --print("Server------EveryOneMinute_CheckNPCExist= NPC Khong Dung Toa Do - ", qStep) 
                    end
                end
                
                --Nếu ID của NPC không tồn tại
                if not IsZombieExisting(uid) then
                    DeactiveNPCSpawnedData(qStep, zID)
                    --print("Server------EveryOneMinute_CheckNPCExist= RemoveNPC - ", qStep) 
                end
            end
        end
    end
end
Events.EveryOneMinute.Remove(EveryOneMinute_CheckNPCExist)
Events.EveryOneMinute.Add(EveryOneMinute_CheckNPCExist)

--SPAWN ZOMBIE NPCQUEST
local function SpawnZombieNPCQuest(args) --SpawnZombieNPCQuest({qStep = "q1s3", x = getPlayer():getX(), y = getPlayer():getY(), z = getPlayer():getZ(), 1, outfit = "Naked", femaleChance = 0, crawler = false, isFallOnFront = false, isFakeDead = false, knockedDown = false, isInvulnerable = true, isSitting = false, health = 1})
    local x, y , z = args.x, args.y, args.z
    local qStep = args.qStep
    --local pID = args.pID
    if not x or not y or not z or not qStep then return end
    local outfit = args.outfit or ""
    local femaleChance = args.femaleChance or 0
    local crawler = args.crawler or false
    local isFallOnFront = args.isFallOnFront or false
    local isFakeDead = args.isFakeDead or false
    local knockedDown = args.knockedDown or false
    local isInvulnerable = args.isInvulnerable or true
    local isSitting = args.isSitting or false
    local health = args.health or 1
    
    --spawn zombie NPC
    local zombieList = addZombiesInOutfit(x, y, z, 1, outfit, femaleChance, crawler, isFallOnFront, isFakeDead, knockedDown, isInvulnerable, isSitting, health)
    local zombie = zombieList:get(0)
    zombie:setPosition(x + 0.5, y + 0.5, z) --di chuyển vào giữa ô
    local zID = Func.GetZombieOnlineIDorUID(zombie)
    
    --zombie moddata sử dụng local server
    local zData = zombie:getModData()
    --zData.UID = zombie:getUID()
    zData.isNPCQuest = true --Cần send tới Client
    --zData.startPoint = getCell():getGridSquare(x, y, z)
    zData.spawnPoint = getCell():getGridSquare(zombie:getX(), zombie:getY(), zombie:getZ()) --Cần send tới Client
    --zData.qStep = qStep
    
    --sử dụng global moddata để tránh npc spawn trước mặt người chơi khác. Chỉ cần có 1 người có quest thì NPC spawn với tất cả ngươi chơi. Nhưng để active quest thì cần biến riêng
    local questMD = Func.GetQuestGlobalModData({qStep = qStep})
    questMD.NPC[qStep]["spawned"] = true --kích hoạt NPC hiện hay mất
    questMD.NPC[qStep]["uid"] = zombie:getUID() --dùng cho server kiểm tra zombie tồn tại không thì xóa spawned, chỉ dùng UID vì tính duy nhất
    questMD.NPC[qStep]["zID"] = zID --dùng xác định zombie nào là NPC
    questMD.NPC[qStep]["x"] = x
    questMD.NPC[qStep]["y"] = y
    questMD.NPC[qStep]["z"] = z
    questMD.NPCqStep[zID] = qStep --dùng lấy qStep
    TransmitVLQuestModData()
    
    --print("Server-----------SpawnZombieNPCQuest= ", qStep) 
    zombie:setUseless(true)
    if isServer() then
        --sendServerCommand('NPCQuestCM_C', 'GetZNPCQuest_C', {zID = zID})
        zombie:setNoTeeth(true)
        zombie:setInvulnerable(true)
        zombie:getEmitter():stopAll()
        zombie:setPrimaryHandItem(nil)
        zombie:setSecondaryHandItem(nil)
        zombie:resetEquippedHandsModels()
        zombie:clearAttachedItems()
        zombie:getDescriptor():setVoicePrefix("")
        zombie:setCanWalk(false)
        --zombie:setUseless(true)
        zombie:setDir(IsoDirections.E)
        --zombie:setVariable("npcQuestIdle", true)
    end
    return zombie
end

local function RemoveZombie(zombie)
    if not zombie then return end
    zombie:setUseless(true)
    zombie:removeFromSquare()
    zombie:removeFromWorld()
end

local function RemoveZombieFromSquares(sq, args) --RemoveZombieFromSquares(sq, {range = 5})
    if not sq then return end
    --print("Server-----------RemoveZombieFromSquares= ") 
    local range = args.range or 5
    local x, y, z = sq:getX(), sq:getY(), math.floor(sq:getZ())
    local zombies = getCell():getZombieList()
    local tab = {}
    for i = 0, zombies:size()-1 do
        local zombie = zombies:get(i)
        if zombie then
            local zX, zY, zZ = math.floor(zombie:getX()), math.floor(zombie:getY()), math.floor(zombie:getZ())
            if (zZ == z) and (zX > x - range and zX < x + range) and (zY > y - range and zY < y + range) then
                table.insert(tab, zombie)
            end
        end
    end
    --Lấy tất cả npcID đang spawned
    local npcIDTab = {}
    local questMD = Func.GetQuestGlobalModData({qStep = nil})
    for qStep, data in pairs(questMD.NPC) do
        if data["spawned"] == true then
            table.insert(npcIDTab, data["zID"])
        end
    end
    for _, zombie in ipairs(tab) do
        local isNPC = false
        local zID = Func.GetZombieOnlineIDorUID(zombie)
        --Nếu zombie không phải bất kì NPC nào thì xóa
        for _, _npcID in ipairs(npcIDTab) do
            if zID == _npcID then
                isNPC = true
                break
            end
        end
        if isNPC == false then
            RemoveZombie(zombie)
        end
    end
end

local function RemoveZombieAndSpawnedData(args)
    if args.zID then
        local zID = args.zID
        local zombie = Func.GetZombieByOnlineIDOrUID(zID)
        if zombie then
            RemoveZombie(zombie)
            local qStep = args.qStep
            DeactiveNPCSpawnedData(qStep, zID)
            --print("Server-----------RemoveZombieAndSpawnedData= ", qStep) 
        end
    end
end

local function RemoveZombiesInAreaExceptNPC(args) --RemoveZombiesInAreaExceptNPC({x1 = 10630, y1 = 10394, x2 = 10651, y2 = 10415, z = -1})
    local x1, y1, x2, y2, z = args.x1, args.y1, args.x2, args.y2, args.z
    local zombies = getCell():getZombieList()
    --print("Server-----------RemoveZombiesInAreaExceptNPC")
    local zTab, npcIDTab = {}, {}
    for i = 0, zombies:size()-1 do
        local zombie = zombies:get(i)
        if zombie then
            local zX, zY, zZ = zombie:getX(), zombie:getY(), zombie:getZ()
            if (zX >= x1 and zX <= x2) and (zY >= y1 and zY <= y2) and (zZ == z) then
                table.insert(zTab, zombie)
            end
        end
    end
    local questMD = Func.GetQuestGlobalModData({qStep = nil})
    for qStep, data in pairs(questMD.NPC) do
        if data["spawned"] == true then
            table.insert(npcIDTab, data["zID"])
        end
    end
    --zombie không phải NPC thì xóa
    for _, zombie in ipairs(zTab) do
        local zID = Func.GetZombieOnlineIDorUID(zombie)
        local isNPC = false
        if #npcIDTab > 0 then
            for _, npcID in ipairs(npcIDTab) do
                if zID == npcID then
                    isNPC = true
                    break
                end
            end
        end
        if not isNPC then
            RemoveZombie(zombie)
        end
    end
end
                        
--Spawn zombies trong room
local function AddZombiesInRoom(args) --AddZombiesInRoom({x = x, y = y, z = z, roomName = roomName, zMaxNumber = 10})
    local x, y, z, roomName, zMaxNumber = args.x, args.y, args.z, args.roomName, args.zMaxNumber or 1
	local square = getCell():getGridSquare(x, y, z)
    if square then
        local building = square:getBuilding()
        if building then
            local room = building:getRandomRoom(roomName)
            if room then
                local number = ZombRand(math.floor(zMaxNumber / 2 + 0.5), zMaxNumber + 1)
                for i = 1, number do
                    room:spawnZombies()
                end
            end
        end
    end
end

--Kiểm tra có player nào gần object hoặc gần tọa độ đã cho không
local function IsAnyPlayerNearObjectByXY(args) --zID, sqX, sqY, maxDist
    if Func.IsMultiplayerMode() then
        local players = getOnlinePlayers()
        local x1, y1
        if args.zID then --nếu chỉ khai báo zID --zombie
            local zID = args.zID
            local zombie = Func.GetZombieByOnlineIDOrUID(zID)
            x1, y1 = zombie:getX(), zombie:getY()
        elseif args.sqX and args. sqY then
            x1, y1 = args.sqX, args. sqY
        end
        local maxDist = args.maxDist or 70
        if players and x1 and y1 then
            for i = 0, players:size() - 1 do
                local player = players:get(i)
                local x2, y2 = player:getX(), player:getY()
                if x2 and y2 then
                    local fDistance = Func.CalculateDistanceXY({x1 = x1, y1 = y1, x2 = x2, y2 = y2})
                    if fDistance and fDistance < maxDist then
                        return true
                    end
                end
            end
        end
        return false
    end
end

function VLF.AddVehicle(args) --VLF.AddVehicle({vehicleName = vehicleName, x = x, y = y, z = z, angleX = 0, angleY = 90, angleZ = 0, damage = 100, crashFont = true, crashBack = true, colorH = 38, colorS = 59, colorV = 80})
	local x, y, z = args.x, args.y, args.z
	local square = getCell():getGridSquare(x, y, z)
	if not square or not square:isFree(true) or not square:TreatAsSolidFloor() then return end
	local vehicleName = args.vehicleName
	local vehicle = addVehicle(vehicleName, x, y, z)
    --print("Server-----------VLF.AddVehicle", qStep, vehicle) 
	if vehicle then
		local angleX, angleY, angleZ = args.angleX or 0, args.angleY or 0, args.angleZ or 0
		vehicle:setAngles(angleX, angleY, angleZ) --(thẳng, ngang, nghiêng)
		local crashFont = args.crashFont
		local damage = args.damage or 100.0
		if crashFont and crashFont == true then
			vehicle:crash(damage, true) --(float delta, boolean front) --damage 100 = vỡ cửa 50/50
		end
		if crashBack and crashBack == true then
			vehicle:crash(damage, false)
		end
		local colorH, colorS, colorV = args.colorH, args.colorS, args.colorV
		if colorH and colorS and colorV then
			vehicle:setColorHSV(colorH, colorS, colorV) --max: 100
		end
	end
end
local AddVehicle = VLF.AddVehicle

--Cập nhật data spawn cho NPC để không cần unload rồi load lại
local function UpdateNPC(args) --UpdateNPC({qStep_Next = qStep_Next, zID = zID})
    local qStep_Next, zID = args.qStep_Next, args.zID
	local questMD = Func.GetQuestGlobalModData({qStep = qStep_Next})
	local zombie = Func.GetZombieByOnlineIDOrUID(zID)
    if zombie and not zombie:isDead() then
        questMD.NPC[qStep_Next]["spawned"] = true
        questMD.NPC[qStep_Next]["uid"] = zombie:getUID() --dùng cho server kiểm tra zombie tồn tại không thì xóa spawned, chỉ dùng UID vì tính duy nhất
        questMD.NPC[qStep_Next]["zID"] = zID
        questMD.NPCqStep[zID] = qStep_Next
        TransmitVLQuestModData()
    end
end

--Lấy building từ tọa độ thuộc trong building
local function GetBuildingFromCoord(args) --GetBuildingFromCoord({x = x, y = y, z = z})
    local x, y, z = args.x, args.y, args.z
    local square = getCell():getGridSquare(x, y, z)
    if square then
        local building = square:getBuilding()
        if building then
            --print("Server-----------GetBuildingFromCoord= ", building)
            return building
        end
    end
    return nil
end

--Check ModData của Building
local function CheckBuildingCooldownEvent(building, args) --CheckBuildingCooldownEvent(building, {event == "Survivalist_InHouse", hoursToNextActive = 168})
	local buildingDef = building:getDef()
	if buildingDef then
		local x, y, z = Func.GetStaticCoordOfBuilding(buildingDef)
		local bSquare = getCell():getGridSquare(x, y, z)
		if bSquare then
			local sqData = bSquare:getModData()
			local event = args.event
            local hoursToNextActive = args.hoursToNextActive or 24
            if event == "Survivalist_InHouse" then
                --building chưa từng kích hoạt event Survivalist_InHouse
                if not sqData.event_Survivalist_InHouse then
                    return true
                elseif sqData.event_Survivalist_InHouse and sqData.event_Survivalist_InHouse.lastActive then
                    local nowHour = getGameTime():getWorldAgeHours()
                    --đã quá thời gian giới hạn, có thể kích hoạt tiếp
                    if (nowHour - sqData.event_Survivalist_InHouse.lastActive) >= args.hoursToNextActive then
                       return true
                    --else
                        --print("Server-----------CheckBuildingCooldownEvent= In Cooldown Time")
                    end
                    
                end
            end
        end
    end
    return false
end

--Set ModData cho building
local function SetBuildingModDataEvent(building, args) --SetBuildingModDataEvent(building, {event = "Survivalist_InHouse"})
	local buildingDef = building:getDef()
	if buildingDef then
		local x, y, z = Func.GetStaticCoordOfBuilding(buildingDef)
		local bSquare = getCell():getGridSquare(x, y, z)
		if bSquare then
			local sqData = bSquare:getModData()
			local event = args.event
            if event == "Survivalist_InHouse" then
                if not sqData.event_Survivalist_InHouse then sqData.event_Survivalist_InHouse = {} end
                sqData.event_Survivalist_InHouse.lastActive = getGameTime():getWorldAgeHours()
                bSquare:transmitModdata()
                --print("Server-----------SetBuildingModDataEvent: 4444= "..tostring(sqData.event_Survivalist_InHouse.lastActive))
                return true
            end
        end
    end
    return false
end

--Lấy IsoDoor hoặc IsoWindow từ room
local function GetDoorOrWindowOfRoom(room, doorOrWindow) --GetDoorOrWindowOfRoom(room, "door") --"window"
	local roomDef = room:getRoomDef()
	local x, y, x2, y2, z = roomDef:getX(), roomDef:getY(), roomDef:getX2(), roomDef:getY2(), roomDef:getZ()
    local objTab, sideTab = {}, {}
	for _x = x - 1, x2 + 1 do
		for _y = y - 1, y2 + 1 do
			local square = getCell():getGridSquare(_x, _y, z)
			if square then
				local obj
				if doorOrWindow == "door" then
					obj = square:getIsoDoor()
				elseif doorOrWindow == "window" then
					obj = square:getWindow()
				end
				if obj then
					--nếu obj nằm trong room
					local sq = obj:getSquare()
					if sq and room:isInside(sq:getX(), sq:getY(), sq:getZ()) then
						--return obj, "inside"
                        table.insert(objTab, obj)
                        table.insert(sideTab, "inside")
					end
					--nếu obj nằm ngoài room
					local sq = obj:getOppositeSquare()
					if sq and room:isInside(sq:getX(), sq:getY(), sq:getZ()) then
						--return obj, "outside"
                        table.insert(objTab, obj)
                        table.insert(sideTab, "outside")
					end
				end
			end
		end
	end
	return objTab, sideTab
end

--Xóa gia cố barricade
local function RemoveBarricade(obj)
    local barricade = obj:getBarricadeOnSameSquare()
    
    function DoRemove()
        if barricade:isMetalBar() then barricade:removeMetalBar(nil)
        elseif barricade:isMetal() then barricade:removeMetal(nil)
        elseif barricade:getNumPlanks() > 0 then
            for i = 1, barricade:getNumPlanks() do
                barricade:removePlank(nil)
            end
        end
    end
    
    if barricade then
        DoRemove()
    end
    barricade = obj:getBarricadeOnOppositeSquare()
    if barricade then
        DoRemove()
    end
end
        
--Gia cố Object
local function BarricadeOnObject(args) --BarricadeOnObject({x = door:getSquare():getX(), y = door:getSquare():getY(), z = door:getSquare():getZ(), objFlag = "door", side = "inside", barItem = "Base.Plank", number = 1})
    local x, y, z, objFlag, side, barItem = args.x, args.y, args.z, args.objFlag, args.side or "outside", args.barItem or "Base.Plank"
    if x and y and z and objFlag then
        local square = getCell():getGridSquare(x, y, z)
        if square then
            local addOpposite = false
            local obj
            if side == "outside" then addOpposite = true end
            
            if objFlag == "door" then
                obj = square:getIsoDoor()
            elseif objFlag == "window" then
                obj = square:getWindow()
            end
            if obj and (instanceof(obj, "IsoDoor") or instanceof(obj, "IsoWindow"))then
                if obj:IsOpen() then
                    if objFlag == "door" then
                        obj:ToggleDoorSilent()
                        obj:sendObjectChange('state')
                    elseif objFlag == "window" then
                        obj:ToggleWindow(nil)
                        obj:sendObjectChange('state')
                    end
                end
                --nếu đã barricade thì không thêm nữa
                local barricaded = obj:getBarricadeOnSameSquare()
                if not barricaded then
                    barricaded = obj:getBarricadeOnOppositeSquare()
                end
                if not barricaded then
                    local number = args.number or ZombRand(2,4)
                    for i = 1, number do
                        Func.AddBarricade(obj, {barItem = barItem, addOpposite = addOpposite})
                    end
                end
                
                if objFlag == "window" then
                    --nếu có rèm thì đóng rèm
                    local curtains = obj:HasCurtains()
                    if curtains then
                        if curtains:isCurtainOpen() then
                            curtains:ToggleDoorSilent()
                            curtains:sendObjectChange('state')
                        end
                    end
                end
            end
        end
    end
end

--Đập vỡ kính cửa sổ
local function SmashWindows(room, number) --SmashWindows(room, 1)
    local windowTab, sideTab = GetDoorOrWindowOfRoom(room, "window")
    local num = 0
    for i, window in ipairs(windowTab) do
        if num >= number then return end
        if window:isBarricaded() then
            RemoveBarricade(window)
        end        
        window:smashWindow(true)
        num = num + 1
    end
end
        
local soundEvent = {}
soundEvent.pistol = {"pistoloutside1", "pistoloutside2"}
soundEvent.shotgun = {"shotgun", "shotgun2"}
soundEvent.assaultrifle = {"assaultrifledistant1", "assaultrifledistant2", "assaultrifledistant3", "assaultrifledistant4", "assaultrifledistant5", "assaultrifledistant8"}
soundEvent.scream_f = {"distscream2_female", "distscream4_female", "distscream5_female"}
soundEvent.scream_m = {"distscream6_male", "distscream7_male", "distscream8_male"}

local function ActiveEvent_Survivalist_InHouse(player, building, args) --ActiveEvent_Survivalist_InHouse(player, building, {_ = nil})
    local bedroom = building:getRandomRoom("bedroom")
	if bedroom then
		--yêu cầu server xóa zombie trong room
		local roomDef = bedroom:getRoomDef()
		local x1, y1, x2, y2, z = roomDef:getX(), roomDef:getY(), roomDef:getX2(), roomDef:getY2(), roomDef:getZ()
        RemoveZombiesInAreaExceptNPC({x1 = x1, y1 = y1, x2 = x2, y2 = y2, z = z})
		
		local female = ZombRand(2)
		local ghoul = ZombRand(2)
		--spawn npc trong bedroom
		local square = bedroom:getRandomFreeSquare()
		if square then
			local pID = Func.GetPlayerID(player)
			local x, y, z = square:getX(), square:getY(), square:getZ()
            local sameQStep, femaleChance
            --Các bước đẻ tạo và update FreeNPC
            --1, tạo ngẫu nhiên qStep, không có trong list qStep. qStep này chưa định nghĩa NPC nào cả
            --2, yêu cầu các client trỏ qStep này tới qStep của 1 trong 4 qStep dưới, tùy server đã chọn được qStep nào. sendClientCommand, qStep = sameQStep
            --3, các client đăng nhập sau thì: check tất cả qStep trong questMD.NPC, nếu có questMD.NPC[qStep].sameQStep thì cập nhật VLF.npcData[qStep] = VLF.npcData[sameQStep]
            --4, trường hợp thoát game và login chỗ có FreeNPC. nếu VLF.npcData[sameQStep] thì trỏ VLF.npcData[qStep] = VLF.npcData[sameQStep]
            
            --set outfit
			local outfit
			local outfitRate = ZombRand(100)
			if outfitRate < 50 then outfit = "Survivalist0"..tostring(ZombRand(2,6))
			elseif outfitRate < 80 then outfit = "Survivalist0"..tostring(ZombRand(2,6)).."_Mid"
			else outfit = "Survivalist0"..tostring(ZombRand(2,6)).."_Late"
			end
            
            --set sameQStep để client update qStep thành sameQStep
			if female == 1 and ghoul == 1 then sameQStep, femaleChance = "freeNPC_female_ghoul", 100
			elseif female == 1 and ghoul == 0 then sameQStep, femaleChance = "freeNPC_female", 100
			elseif female == 0 and ghoul == 0 then sameQStep, femaleChance = "freeNPC_male", 0
			else sameQStep, femaleChance = "freeNPC_male_ghoul", 0
			end
            
            --set qStep
            local questMD = Func.GetQuestGlobalModData({qStep = nil})
            local qStep
            for i = 1, 10 do
                qStep = "freeNPC_"..tostring(ZombRand(10000))
                local exist = false
                --kiểm tra qStep có tồn tại không
                for _qStep, tab in pairs(questMD.NPC) do
                    if _qStep == qStep then
                        exist = true
                    end                    
                end
                if exist == false then
                    break
                end
            end
            
			if qStep then
                --spawn NPC và NPC qStep cố định có sẵn VLF.npcData[sameQStep]
                local zNPC = SpawnZombieNPCQuest({qStep = qStep, x = x, y = y, z = z, 1, outfit = outfit, femaleChance = femaleChance, crawler = false, isFallOnFront = false, isFakeDead = false, knockedDown = false, isInvulnerable = true, isSitting = false, health = 5})
                if zNPC then
                    local questMD = Func.GetQuestGlobalModData({qStep = qStep})
                    --update data trường hợp không phải Client-Server
                    questMD.NPC[qStep]["sameQStep"] = sameQStep
                    if not isServer() then
                        VLF.npcData[qStep] = VLF.npcData[sameQStep]
                    end
                    --yêu cầu client update qStep NPC này
                    sendServerCommand("NPCQuestCM_C", "FreeNPCSameQStep_C", {qStep = qStep, sameQStep = sameQStep})
                end
            end
        end
        
		--Nếu có door thì đóng lại và barricade
		local doorTab, sideTab = GetDoorOrWindowOfRoom(bedroom, "door")
        for i, door in ipairs(doorTab) do		
            BarricadeOnObject({x = door:getSquare():getX(), y = door:getSquare():getY(), z = door:getSquare():getZ(), objFlag = "door", side = sideTab[i], barItem = "Base.Plank"})
		end
		--Nếu có window thì đóng lại và barricade
		local windowTab, sideTab = GetDoorOrWindowOfRoom(bedroom, "window")
        for i, window in ipairs(windowTab) do
			--barricade
            BarricadeOnObject({x = window:getSquare():getX(), y = window:getSquare():getY(), z = window:getSquare():getZ(), objFlag = "window", side = sideTab[i], barItem = "Base.Plank"})
		end	
        
        local outsideRoom = Func.GetOutsideRoom(building)
        if outsideRoom then
            --Đập vỡ kính cửa sổ
            SmashWindows(outsideRoom, ZombRand(1,3))
            
            --Spawn zombies trong livingroom
            AddZombiesInRoom({x = x1, y = y1, z = z, roomName = outsideRoom:getName(), zMaxNumber = 10})

            local soundGun
            local r = ZombRand(2)
            if r == 0 then soundGun = soundEvent.pistol[ZombRand(1,3)]
            elseif r == 1 then soundGun = soundEvent.shotgun[ZombRand(1,3)]
            end
            
			local sq = outsideRoom:getRandomFreeSquare()
			if sq then
				local sound
				if female == 1 then sound = soundEvent.scream_f[ZombRand(1,4)]
				else sound = soundEvent.scream_m[ZombRand(1,4)]
				end
				if sound then
                    --print("Server-----------PlayWorldSound: 2222")
                    addSound(sq:getFloor(), sq:getX(), sq:getY(), sq:getZ(), 30, 100) --thu hút zombie 30 ô
					getSoundManager():PlayWorldSound(soundGun, false, sq, 200, 0, 1, false)
                    if ZombRand(100) < 75 then
                        Func.DelayFunction(function()
                            getSoundManager():PlayWorldSound(soundGun, false, sq, 200, 0, 1, false)
                        end, ZombRand(15,25))
                    end
                    if ZombRand(100) < 70 then
                        Func.DelayFunction(function()
                            getSoundManager():PlayWorldSound(soundGun, false, sq, 200, 0, 1, false)
                        end, ZombRand(30,40))
                    end
                    if ZombRand(100) < 65 then
                        Func.DelayFunction(function()
                            getSoundManager():PlayWorldSound(soundGun, false, sq, 200, 0, 1, false)
                        end, ZombRand(45,55))
                    end
                    if ZombRand(100) < 60 then
                        Func.DelayFunction(function()
                            getSoundManager():PlayWorldSound(soundGun, false, sq, 200, 0, 1, false)
                        end, ZombRand(60,70))
                    end
                    if ZombRand(100) < 55 then
                        Func.DelayFunction(function()
                            getSoundManager():PlayWorldSound(soundGun, false, sq, 200, 0, 1, false)
                        end, ZombRand(80,90))
                    end
                    if ZombRand(100) < 50 then
                        Func.DelayFunction(function()
                            getSoundManager():PlayWorldSound(soundGun, false, sq, 200, 0, 1, false)
                        end, ZombRand(100,110))
                    end
                    if ZombRand(100) < 45 then
                        Func.DelayFunction(function()
                            getSoundManager():PlayWorldSound(soundGun, false, sq, 200, 0, 1, false)
                        end, ZombRand(120,130))
                    end
                    if ZombRand(100) < 40 then
                        Func.DelayFunction(function()
                            getSoundManager():PlayWorldSound(soundGun, false, sq, 200, 0, 1, false)
                        end, ZombRand(140,150))
                    end
                    if ZombRand(100) < 35 then
                        Func.DelayFunction(function()
                            getSoundManager():PlayWorldSound(soundGun, false, sq, 200, 0, 1, false)
                        end, ZombRand(160,170))
                    end
                    if ZombRand(100) < 30 then
                        Func.DelayFunction(function()
                            getSoundManager():PlayWorldSound(soundGun, false, sq, 200, 0, 1, false)
                        end, ZombRand(180,190))
                    end
                    if ghoul == 1 then
                        Func.DelayFunction(function()
                            getSoundManager():PlayWorldSound(sound, false, sq, 200, 0, 1, false)
                        end, ZombRand(25,100))
                    end
				end
			end
		end
        --Set ModData
        SetBuildingModDataEvent(building, {event = "Survivalist_InHouse"})
	end
end

local function OnClientCommandNPC(module, command, player, args)
    if module == "NPCQuestCM" then
        if command == "RemoveNPC" then
            --xóa zombie NPC
            RemoveZombieAndSpawnedData(args)
        elseif command == "RemoveNPCWhenFarAwayPlayers" then
            --xóa zombie NPC nếu không có player nào trong phạm vi maxDist
            if Func.IsMultiplayerMode() then
                local hasPlayerNearNPC = IsAnyPlayerNearObjectByXY(args)
                if not hasPlayerNearNPC then
                    RemoveZombieAndSpawnedData(args)
                end
            else
                RemoveZombieAndSpawnedData(args)
            end
		elseif command == "GetOrCreatePlayerGMD" then
            local playerGMD = Func.GetPlayerGlobalModData(player)
            if playerGMD then
                sendServerCommand(player, "NPCQuestCM_C", "GetOrCreatePlayerGMD_C", playerGMD)
            end
        elseif command == "RequestSpawnNPC" then
            local qStep = args.qStep
            local questMD = Func.GetQuestGlobalModData({qStep = qStep})
            if not questMD.NPC[qStep]["spawned"] then
                local range = args.removeZRange or 5
                local femaleChance = args.femaleChance or 0
                local sq = getCell():getGridSquare(args.x, args.y, args.z)
                --print("Server-----------RequestSpawnNPC= ", sq) 
                if sq then
                    RemoveZombieFromSquares(sq, {range = range})
                    SpawnZombieNPCQuest({pID = args.pID, qStep = qStep, x = args.x, y = args.y, z = args.z, 1, outfit = args.outfit, femaleChance = femaleChance, crawler = false, isFallOnFront = false, isFakeDead = false, knockedDown = false, isInvulnerable = true, isSitting = false, health = 5})
                end
            end
        elseif command == "ActiveNPCInQueueSpawnCM" then
            local qStep = args.qStep
            local active = args.active
            if not qStep then return end
            local questMD = Func.GetQuestGlobalModData({qStep = qStep})            
            --kiểm tra nếu qStep của tất cả moddata player online đều false thì mới gán false, nếu có 1 true thì NPCsQueueToSpawn[qStep] luôn là true
            --để NPC luôn spawn khi có ít nhất 1 player là true
            if Func.IsMultiplayerMode() then
                if active == true then
                    questMD.NPCsQueueToSpawn[qStep] = active
                else
                    local players = getOnlinePlayers()
                    for i = 0, players:size() - 1 do
                        local player = players:get(i)
                        local playerGMD = Func.GetPlayerGlobalModData(player)
                        if playerGMD.Quest then
                            for q, data in pairs(playerGMD.Quest) do
                                local active = data[qStep].active
                                --nếu bước nhiệm vụ đang kích hoạt
                                if active and active == true then
                                    local isQStepNPC = data[qStep].isNPC
                                    --nếu bước nhiệm vụ là loại có NPC
                                    if isQStepNPC and isQStepNPC == true then
                                        questMD.NPCsQueueToSpawn[qStep] = true
                                        TransmitVLQuestModData()
                                        return
                                    end
                                end
                            end
                        end
                    end
                    --khi tất cả moddata player đều false/nil thì nil
                    questMD.NPCsQueueToSpawn[qStep] = nil
                end
            else    
                questMD.NPCsQueueToSpawn[qStep] = active
                --print("Server-----------questMD.NPCsQueueToSpawn[qStep]= ", qStep, questMD.NPCsQueueToSpawn[qStep]) 
            end
            TransmitVLQuestModData()
        elseif command == "UpdateQuestModData" then
            local qStep = args.qStep
            local questMD = Func.GetQuestGlobalModData({qStep = qStep})
            local modData = args.modData
            local val = args.val
            if modData == "turnToZombie" then
                if questMD.NPC[qStep][modData] ~= val then
                    questMD.NPC[qStep][modData] = val
                end
            elseif modData == "AddedEventObjects" then
                if not questMD.Object[qStep] then questMD.Object[qStep] = {} end
                if questMD.Object[qStep][modData] ~= val then
                    questMD.Object[qStep][modData] = val
                end
            end
            sendServerCommand(player, "NPCQuestCM_C", "UpdateQuestModData_C", args)
        elseif command == "SpawnVehicle" then
            AddVehicle(args)
        elseif command == "RemoveZombiesInAreaExceptNPC" then
            RemoveZombiesInAreaExceptNPC(args)
        elseif command == "UpdateNPCToNextQStep" then
            UpdateNPC(args)
        elseif command == "RequestEvents" then
            local event = args.event
            if event == "Survivalist_InHouse" then
                local building = GetBuildingFromCoord(args)
                if building then
                    --local canActive = false
                    local countFalse = 0
                    --check ModData có thể active không
                    local canActive = CheckBuildingCooldownEvent(building, args)
                    if not canActive then countFalse = countFalse + 1 end
                    --Multiplayer mode: check Player có ở gần xung quanh
                    if Func.IsMultiplayerMode() then
                        --Kiểm tra có player nào trong bán kính maxDist không
                        local playerNearby = IsAnyPlayerNearObjectByXY({sqX = building:getDef():getX(), sqY = building:getDef():getY(), maxDist = 35})
                        if playerNearby then countFalse = countFalse + 1 end
                       
                        --Kiểm tra building có phải SafeHouse không
                        local square = getCell():getGridSquare(args.x, args.y, args.z)
                        if square then
                            local isSH = SafeHouse.getSafeHouse(square)
                            if isSH then countFalse = countFalse + 1 end
                        end
                    end
                    --print("-----------countFalse= ", countFalse)
                    if countFalse == 0 then
                        --active event
                        ActiveEvent_Survivalist_InHouse(player, building, {_ = nil})
                    end
                end
            end
        end
    end
end
Events.OnClientCommand.Remove(OnClientCommandNPC)
Events.OnClientCommand.Add(OnClientCommandNPC)



