--TẠO NPC ĐỨNG NGUYÊN TẠI CHỖ SỬ DỤNG ĐỂ TƯƠNG TÁC NHIỆM VỤ

-- CALL để thêm NPC vào danh sách chờ spawn
------------CÁC BƯỚC ĐỂ THÊM NPC------------
-- 1, SendCommand tới server để active = true cho bảng questMD.NPCsQueueToSpawn[qStep] --sendClientCommand(player, "NPCQuestCM", "ActiveNPCInQueueSpawnCM", {qStep = qStep, active = true})
-- 2, Thêm outfit NPC: Thêm trực tiếp outfit data vào bảng VLF.outfits.clothingSet[qStep] và VLF.outfits.clothingTINT[qStep] và VLF.outfits.weaponsAttachment[qStep]
-- 3, Thêm NPC data: Thêm trực tiếp data của NPC vào bảng VLF.npcData[qStep]
-- 4, Thêm function VLF.TalkToNPC[qStep] để có thể nói chuyện với NPC
--------------------------------------------
--  NPC Quest:
--NPC có sẵn data cố định VLF.npcData[qStep]
--NPC được thêm vào danh sách chờ spawn questMD.NPCsQueueToSpawn[qStep]
--NPC tự động spawn khi load square trùng với tọa độ cố định của NPC
--RELOGIN, square được load lại nên NPC tự động spawn. zombie cũ bị xóa
--Xóa khi không có player trong bán kính 70 ô
--Xóa khi có zombie ở gần
--Xóa khi xung quanh có lửa hoặc bị cháy
--Xóa khi bị đẩy xa khỏi điểm spawn spawnPoint

--  FREE NPC:
--Client tìm được building phù hợp, gửi cho server để yêu cầu Event. Server check cooldown, active event, spawn NPC với qStep ngẫu nhiên, trỏ qStep NPC tới qStep có sẵn, rồi yêu cầu tất cả Client cũng trỏ tới qStep sẵn này
--NPC KHÔNG có data cố định. Tạo qStep ngẫu nhiên FreeNPC_xxxx rồi trỏ tới 1 data cố định cho trước: VLF.npcData["freeNPC_...
--KHÔNG có trong questMD.NPCsQueueToSpawn[qStep] nên sẽ không spawn khi load square
--KHÔNG có tọa độ cố định trong NPC data
--RELOGIN sẽ XÓA NPC, chỉ có zombie
--NPC được spawn trực tiếp tới square đã được load ở client
--NPC có data infected biến thành Zombie sau x phút

require "vl_quest_utils"
require "questui/vl_quest_introwindow_ui"

--VLF = VLF or {}
VLF.TalkToNPC = VLF.TalkToNPC or {}
VLF.outfits = VLF.outfits or {}
VLF.outfits.clothingSet = VLF.outfits.clothingSet or {}
VLF.outfits.clothingTINT = VLF.outfits.clothingTINT or {}
VLF.outfits.weaponsAttachment = VLF.outfits.weaponsAttachment or {}
VLF.npcData = VLF.npcData or {}

--Free NPC --Survivalist
VLF.npcData["freeNPC_female"] = {name = nil, variable = "npcQuestIdle", outfit = "", femaleChance = 100, isGhoul = false, hasHoles = 1, isDirt = true, isBlood = false, textureID = 2}
VLF.npcData["freeNPC_male"] = {name = nil, variable = "npcQuestIdle", outfit = "", femaleChance = 0, isGhoul = false, hasHoles = 1, isDirt = true, isBlood = false, textureID = 2}
VLF.npcData["freeNPC_female_ghoul"] = {name = nil, variable = "npcQuestIdle", outfit = "", femaleChance = 100, isGhoul = true, infected = true, hasHoles = 3, isDirt = true, isBlood = true, textureID = 2}
VLF.npcData["freeNPC_male_ghoul"] = {name = nil, variable = "npcQuestIdle", outfit = "", femaleChance = 0, isGhoul = true, infected = true, hasHoles = 3, isDirt = true, isBlood = true, textureID = 2}

assert(VLPL, "Stack trace: Missing dependency: VLPL - vl_quest_npc_client.lua")
assert(VLZB, "Stack trace: Missing dependency: VLZB - vl_quest_npc_client.lua")
 
local GetPlayerGlobalModDataQ1 = VLGMD.GetPlayerGlobalModDataQ1
local CalculateDistanceXY = VLF.CalculateDistanceXY
local GetZombieOnlineIDorUID = VLZB.GetZombieOnlineIDorUID
local GetPlayerID = VLPL.GetPlayerID
local SetZombieDefault = VLZB.SetZombieDefault
local Caldistance = VLF.Caldistance
local ActiveQuestIntro_Free = VLF.ActiveQuestIntro_Free
local GetPlayerGlobalModData = VLGMD.GetPlayerGlobalModData
local DelayFunction = VLF.DelayFunction
local GetQuestGlobalModData = VLGMD.GetQuestGlobalModData
local GetAllnpcIDSpawning = VLZB.GetAllnpcIDSpawning
local GetZombieByOnlineIDOrUID = VLZB.GetZombieByOnlineIDOrUID
local GetOrCreateSquare = VLF.GetOrCreateSquare
local AddObjectToSquare = VLF.AddObjectToSquare
local RemoveObjectOnSquare = VLF.RemoveObjectOnSquare
local GetZombieModDataQuest = VLZB.GetZombieModDataQuest
local RemoveNPCQuest = VLZB.RemoveNPCQuest
local DoDamageOnZombie = VLZB.DoDamageOnZombie
local GetZombiesNotNPCInRange = VLZB.GetZombiesNotNPCInRange
local NpcDoRifleShot = VLZB.NpcDoRifleShot
local IsCharExistAndAlive = VLZB.IsCharExistAndAlive
local SetZombieInactiveFakeNPC = VLZB.SetZombieInactiveFakeNPC
local DeactiveNPCSpawnedData = VLZB.DeactiveNPCSpawnedData
local NPCSetHandItem = VLZB.NPCSetHandItem
local UpdateRifleNPC = VLF.UpdateRifleNPC
local UpdateNPCSleepOnBed = VLF.UpdateNPCSleepOnBed

VLF.var.npcUID = {}
VLF.var.ragdollOptionStart = nil --giá trị isRagdoll lúc bắt đầu game. dùng bật lại Ragdoll nếu có sau khi xóa NPC

local itemVisualsTable = {
    ["Base.ZedDmg_CHEST_Slash"] = true,
    ["Base.Bandage_LeftUpperArm"] = true,
    ["Base.Bandage_LeftUpperArm_Blood"] = true,
    ["Base.Bandage_RightUpperArm"] = true,
    ["Base.Bandage_RightUpperArm_Blood"] = true,
    ["Base.Bandage_Abdomen"] = true,
    ["Base.Bandage_Abdomen_Blood"] = true,
    ["Base.Bandage_Chest"] = true,
    ["Base.Bandage_Chest_Blood"] = true,
    ["Base.Bandage_Groin"] = true,
    ["Base.Bandage_Groin_Blood"] = true,
    ["Base.Bandage_LeftLowerLeg"] = true,
    ["Base.Bandage_LeftLowerLeg_Blood"] = true,
    ["Base.Bandage_RightLowerLeg"] = true,
    ["Base.Bandage_RightLowerLeg_Blood"] = true,
    ["Base.Bandage_LeftFoot"] = true,
    ["Base.Bandage_LeftFoot_Blood"] = true,
    ["Base.Bandage_RightFoot"] = true,
    ["Base.Bandage_RightFoot_Blood"] = true,
    ["Base.Bandage_LeftHand"] = true,
    ["Base.Bandage_LeftHand_Blood"] = true,
    ["Base.Bandage_RightHand"] = true,
    ["Base.Bandage_RightHand_Blood"] = true,
    ["Base.Bandage_LeftUpperLeg"] = true,
    ["Base.Bandage_LeftUpperLeg_Blood"] = true,
    ["Base.Bandage_RightUpperLeg"] = true,
    ["Base.Bandage_RightUpperLeg_Blood"] = true,
    ["Base.Bandage_LeftLowerArm"] = true,
    ["Base.Bandage_LeftLowerArm_Blood"] = true,
    ["Base.Bandage_RightLowerArm"] = true,
    ["Base.Bandage_RightLowerArm_Blood"] = true,
    ["Base.Bandage_Neck"] = true,
    ["Base.Bandage_Neck_Blood"] = true,
    ["Base.Bandage_Head"] = true,
    ["Base.Bandage_Head_Blood"] = true,
    ["Base.ZedDmg_BACK_Slash"] = true,
    ["Base.ZedDmg_BulletBelly01"] = true,
    ["Base.ZedDmg_BulletBelly03"] = true,
    ["Base.ZedDmg_BulletBelly02"] = true,
    ["Base.ZedDmg_FaceSkullLeft"] = true,
    ["Base.ZedDmg_NoNose"] = true,
    ["Base.ZedDmg_ShoulderSlashLeft"] = true,
    ["Base.ZedDmg_HEAD_Slash"] = true,
    ["Base.ZedDmg_MouthLeft"] = true,
    ["Base.ZedDmg_NoChin"] = true,
    ["Base.ZedDmg_NoEarLeft"] = true,
    ["Base.ZedDmg_RibsLeft"] = true,
    ["Base.ZedDmg_BellySlashRight"] = true,
    ["Base.ZedDmg_BulletForehead01"] = true,
    ["Base.ZedDmg_BulletForehead03"] = true,
    ["Base.ZedDmg_BulletForehead02"] = true,
    ["Base.ZedDmg_ShotgunChestLeft"] = true,
    ["Base.ZedDmg_ShotgunChestRight"] = true,
    ["Base.ZedDmg_FaceSkullRight"] = true,
    ["Base.M_Beard_Stubble"] = true,
    ["Base.ZedDmg_ChestSlashLeft"] = true,
    ["Base.ZedDmg_ShotgunFaceLeft"] = true,
    ["Base.ZedDmg_HeadSlashLeft02"] = true,
    ["Base.ZedDmg_HeadSlashLeft03"] = true,
    ["Base.ZedDmg_HeadSlashLeft01"] = true,
    ["Base.ZedDmg_HEAD_Bullet"] = true,
    ["Base.ZedDmg_BACK_Spine"] = true,
    ["Base.ZedDmg_ShotgunLeft"] = true,
    ["Base.ZedDmg_BELLY_Bullet"] = true,
    ["Base.ZedDmg_ShotgunFaceFull"] = true,
    ["Base.ZedDmg_HEAD_Shotgun"] = true,
    ["Base.ZedDmg_HeadSlashCentre01"] = true,
    ["Base.ZedDmg_HeadSlashCentre02"] = true,
    ["Base.ZedDmg_HeadSlashCentre03"] = true,
    ["Base.ZedDmg_ShotgunFaceRight"] = true,
    ["Base.ZedDmg_ShotgunChestCentre"] = true,
    ["Base.ZedDmg_RibsRight"] = true,
    ["Base.ZedDmg_BELLY_Slash"] = true,
    ["Base.ZedDmg_SkullUpRight"] = true,
    ["Base.ZedDmg_NeckBiteBackRight"] = true,
    ["Base.ZedDmg_Mouth01"] = true,
    ["Base.M_Hair_Stubble"] = true,
    ["Base.ZedDmg_Mouth02"] = true,
    ["Base.ZedDmg_NoEarRight"] = true,
    ["Base.ZedDmg_HeadSlashRight03"] = true,
    ["Base.ZedDmg_HeadSlashRight02"] = true,
    ["Base.ZedDmg_HeadSlashRight01"] = true,
    ["Base.ZedDmg_BELLY_Shotgun"] = true,
    ["Base.ZedDmg_NeckBiteBackLeft"] = true,
    ["Base.ZedDmg_BulletFace02"] = true,
    ["Base.ZedDmg_BulletFace01"] = true,
    ["Base.ZedDmg_ShotgunBelly"] = true,
    ["Base.ZedDmg_NeckBiteFrontRight"] = true,
    ["Base.ZedDmg_ShoulderSlashRight"] = true,
    ["Base.ZedDmg_CHEST_Shotgun"] = true,
    ["Base.ZedDmg_HEAD_Skin"] = true,
    ["Base.ZedDmg_CHEST_Bullet"] = true,
    ["Base.ZedDmg_MouthRight"] = true,
    ["Base.ZedDmg_NeckBiteFrontLeft"] = true,
    ["Base.ZedDmg_BulletRightTemple"] = true,
    ["Base.ZedDmg_HeadSlashRightBack02"] = true,
    ["Base.ZedDmg_HeadSlashRightBack01"] = true,
    ["Base.ZedDmg_BELLY_Skin"] = true,
    ["Base.ZedDmg_NECK_Bite"] = true,
    ["Base.ZedDmg_SkullUpLeft"] = true,
    ["Base.F_Hair_Stubble"] = true,
    ["Base.ZedDmg_HeadSlashLeftBack01"] = true,
    ["Base.ZedDmg_HeadSlashLeftBack02"] = true,
    ["Base.ZedDmg_BulletChest01"] = true,
    ["Base.ZedDmg_BulletChest02"] = true,
    ["Base.ZedDmg_BulletChest03"] = true,
    ["Base.ZedDmg_BulletChest04"] = true,
    ["Base.ZedDmg_SkullCap"] = true,
    ["Base.ZedDmg_ShotgunRight"] = true,
    ["Base.ZedDmg_BulletLeftTemple"] = true,
    ["Base.ZedDmg_BellySlashLeft"] = true,
}
local body_Locations = {
    "Ears", "EarTop", "Nose", "Hat", "FullHat", "Mask", "MaskEyes", "Eyes", "RightEye", "LeftEye", "Neck", "Necklace", "Gorget", "Scarf",
    "TankTop", "Tshirt", "ShortSleeveShirt", "Shirt", "VestTexture", "Sweater", "SweaterHat", "TorsoExtraVest", "Cuirass", "TorsoExtra",
    "Jacket", "JacketHat", "Jacket_Down", "JacketHat_Bulky", "Jacket_Bulky", "JacketSuit", "FullTop",
    "BathRobe", "FullSuit", "FullSuitHead", "Boilersuit", "Tail", "TorsoExtraVestBullet",
    "Pants", "PantsExtra", "ShortPants", "ShortsShort", "LongSkirt", "Skirt", "Dress", "LongDress", "Pants_Skinny",
    "UnderwearBottom", "UnderwearTop", "UnderwearExtra1", "UnderwearExtra2", "Underwear", "Torso1Legs1", "Legs1", "Socks", "Shoes",
    "RightWrist", "Right_MiddleFinger", "Right_RingFinger", "LeftWrist", "Left_MiddleFinger", "Left_RingFinger", "Hands", "HandsRight", "HandsLeft",
    "ShoulderpadRight", "ShoulderpadLeft", "Elbow_Right", "Elbow_Left", "ForeArm_Right", "ForeArm_Left",
    "Thigh_Right", "Thigh_Left", "Knee_Right", "Knee_Left", "Calf_Right", "Calf_Left",
    "FannyPackFront", "FannyPackBack", "Webbing", "AmmoStrap", "AnkleHolster", "BeltExtra", "ShoulderHolster", "Belt"
    }

local function getHairStyle(isFemale, id)
    local hairStyles = getAllHairStyles(isFemale)
    local hs = {}
    for i=0, hairStyles:size()-1 do
        local hairStyle = hairStyles:get(i)
        local hairStyleInst
        if isFemale then hairStyleInst = getHairStylesInstance():FindFemaleStyle(hairStyle) 
        else hairStyleInst = getHairStylesInstance():FindMaleStyle(hairStyle) end
        if not hairStyleInst:isNoChoose() then
            table.insert(hs, hairStyle)
        end
    end
    return hs[id] or hs[1]
end

--Update zombie thành NPC Quest
local function TurnZombieToNPC(zNPC, qStep, args) --TurnZombieToNPC(zNPC, qStep, VLF.npcData[qStep])
    if not zNPC or not qStep then return end
    local npcVisuals = zNPC:getHumanVisual()
    if not npcVisuals then return end
    local skin = npcVisuals:getSkinTexture()
    if not skin or skin:find("^FemaleBody") or skin:find("^MaleBody") then return end
    
    local zData = GetZombieModDataQuest(zNPC)
    zData.name = args.name or nil
    local textureID = args.textureID or 1
    local hairStypeID = args.hairStypeID or 1
    local beardStypeID = args.beardStypeID or 1
    local r, g, b = args.hairColorR or 0, args.hairColorG or 0, args.hairColorB or 0
    local isDirt = args.isDirt or false
    local isBlood = args.isBlood or false
    local hasHoles = args.hasHoles or 0
    local isGhoul = args.isGhoul or false
    local isHasBeard = args.isHasBeard or true
    local itemVisuals = zNPC:getItemVisuals()
    local isFemale = zNPC:isFemale()
    local outfit = args.outfit
    
    --Đổi màu da giống player
    local textureName
    if isGhoul ~= true then
        if isFemale then textureName = "FemaleBody0"..tostring(textureID)
        else textureName = "MaleBody0"..tostring(textureID) end
        npcVisuals:setSkinTextureName(textureName)
    end
    
    --nếu không có OUTFIT mặc định thì thêm outfit thủ công
    if not outfit then
        --màu cho tóc và râu
        local icolor = ImmutableColor.new(r, g, b)    
        --kiểu tóc
        npcVisuals:setHairModel(getHairStyle(isFemale, hairStypeID)) 
        npcVisuals:setHairColor(icolor)     
        --kiểu râu
        if not isFemale and isHasBeard then
            local beardModel = getAllBeardStyles():get(beardStypeID)
            if beardModel then
                npcVisuals:setBeardModel(beardModel) 
                npcVisuals:setBeardColor(icolor) 
            end
        end
        
        --XÓA Visuals mặc định trên client
        itemVisuals:clear()
         
        --thêm quần áo phụ kiện + thêm BAG
        local _clothingSet = VLF.outfits.clothingSet[qStep]
        if _clothingSet then
            --thêm quần áo phụ kiện, cần xóa quần áo cũ của zombie tránh đè lên nhau
            for i, bodyLoc in pairs(body_Locations) do
                for _bodyLoc, itemType in pairs(_clothingSet) do
                    if _bodyLoc == bodyLoc then
                        local item = instanceItem(itemType)
                        if item then
                            local itemVisual = ItemVisual.new()
                            itemVisual:setItemType(itemType)
                            itemVisual:setClothingItemName(itemType)
                            --Chỉnh màu cho quần áo phụ kiện
                            local _clothingTint = VLF.outfits.clothingTINT[qStep]
                            if _clothingTint then
                                local colorTab = VLF.outfits.clothingTINT[qStep][_bodyLoc]
                                local immutableColor
                                if colorTab then
                                    immutableColor = ImmutableColor.new(colorTab.r, colorTab.g, colorTab.b, colorTab.a)
                                else
                                    immutableColor = ImmutableColor.new(0.12, 0.12, 0.12, 1)
                                end
                                itemVisual:setTint(immutableColor)
                            end
                            itemVisuals:add(itemVisual)
                        end
                    end
                end
            end
            
            --thêm Bag
            local bagName = VLF.outfits.clothingSet[qStep].Bag
            if bagName then
                --local item = instanceItem("Base.Bag_Satchel")
                local item = instanceItem(bagName)
                if item then
                    local itemVisual = ItemVisual.new()
                    itemVisual:setItemType(bagName)
                    itemVisual:setClothingItemName(bagName)
                    local colorTab = VLF.outfits.clothingTINT[qStep].Bag
                    local immutableColor
                    if colorTab then
                        immutableColor = ImmutableColor.new(colorTab.r, colorTab.g, colorTab.b, colorTab.a)
                    else
                        immutableColor = ImmutableColor.new(0.12, 0.12, 0.12, 1)
                    end
                    itemVisual:setTint(immutableColor)
                    itemVisuals:add(itemVisual)
                end
            end
        end    
        
        --vũ khí, dụng cụ gán trên cơ thể
        local _attachments = VLF.outfits.weaponsAttachment[qStep]
        if _attachments then
            for loc, itemType in pairs(_attachments) do
                local item = instanceItem(itemType)
                if item then
                    zNPC:setAttachedItem(loc, item)
                end
            end
        end        
    end
    
    -----------------------
    npcVisuals:randomDirt()
    
    -- Xóa blood và dirt các bộ phận cơ thể
    local maxIndex = BloodBodyPartType.MAX:index()
    for i = 0, maxIndex - 1 do
        local part = BloodBodyPartType.FromIndex(i)
        if not isDirt then npcVisuals:setDirt(part, 0) end
        if not isBlood then npcVisuals:setBlood(part, 0) end
       
    end

    -- Lỗ rách, blood và dirt trên phụ kiện
    for i = 0, itemVisuals:size() - 1 do
        local item = itemVisuals:get(i)
        if item then
            for j = 0, maxIndex - 1 do
                local part = BloodBodyPartType.FromIndex(j)
                if not hasHoles or hasHoles == 0 then
                    item:removeHole(j)
                else
                    if hasHoles == 1 then
                        if j % 13 == 0  then item:setHole(part) end --13
                    elseif hasHoles == 2 then
                        if j % 6 == 0  then item:setHole(part) end --6, 12
                    elseif hasHoles == 3 then
                        if j % 5 == 0  then item:setHole(part) end --5, 10, 15
                    elseif hasHoles == 4 then
                        if j % 4 == 0  then item:setHole(part) end --4, 8, 12, 16?
                    elseif hasHoles == 5 then
                        if j % 3 == 0  then item:setHole(part) end --3, 6, 9, 12, 15
                    else
                        if j % 2 == 0  then item:setHole(part) end --2, 4, 6, 8, 10, 12, 14, 16
                    end
                end                
                if not isDirt then item:setDirt(part, 0) else item:setDirt(part, 0.2) end
                if not isBlood then item:setBlood(part, 0) else item:setBlood(part, 0.2) end
            end
            item:setInventoryItem(nil)
        end
    end

    --Xóa các Visuals gắn trên cơ thể: miệng, mặt thối rữa... của zombie
    local bodyVisuals = npcVisuals:getBodyVisuals()
    local tab = {}
    local count = 0
    for i = 0, bodyVisuals:size() - 1 do
        local item = bodyVisuals:get(i)
        if item and itemVisualsTable[item:getItemType()] then
            count = count + 1
            tab[count] = item:getItemType()
        end
    end
    for i = 1, count do
        npcVisuals:removeBodyVisualFromItemType(tab[i])
    end
    
    --Trang bị vũ khí lên tay NPC nếu Variable là "npcQuestRifleAiming" 
    local variable = args.variable
    if variable and variable == "npcQuestRifleAiming" then           
        local item = instanceItem(args.rifleType)
        if item then
            NPCSetHandItem(zNPC, true, item)                    
            --zNPC:setVariable("npcQuestAimType", "rifle")
        end
    end            
    
    --quấn bandage lên bộ phận cơ thể
    local _bandage = VLF.outfits.bandageds[qStep]
    if _bandage then
        for _, data in pairs(_bandage) do
            zNPC:addVisualBandage(data.bodyPart, data.bloody)
        end
    end
    
    zNPC:resetModelNextFrame()
    zNPC:resetModel()
    zNPC:setUseless(true)
end

--NPC biến thành zombie
function VLF.NPCTurnToZombie(player, zNPC, qStep, args) --VLF.NPCTurnToZombie(player, zNPC, qStep, {isPopUp = true, qID = 1})
	--local questMD = ModData.getOrCreate("VL_Quest")
    if not zNPC or not qStep then return end 
	--local zData = zNPC:getModData()
    local zData = GetZombieModDataQuest(zNPC)
	if not zData.NotUpdateVariable then --điều kiện để không update Variable,
		local questMD = GetQuestGlobalModData(qStep)
		
        --Hiện thông báo
		local popup
		if args.isPopUp and args.isPopUp == true then
			local qID = args.qID or 1
			popup = ActiveQuestIntro_Free(getText("IGUI_VLQ_"..tostring(qID).."_NPCTalk_"..tostring(qStep)), (getCore():getScreenWidth() - 800) / 2, (getCore():getScreenHeight() - 200) / 16, 800, 200, false, "")
		end
		
        --cho dáng đứng NPC thành của zombie, bật tiếng zombie báo động, nhưng chưa cho zombie cử động
		DelayFunction(function()
			zNPC:setVariable(VLF.npcData[qStep].variable, false) --khiến NPC dáng đứng thành zombie
			if zNPC:isFemale() then
				zNPC:getDescriptor():setVoicePrefix("FemaleZombie")
			else
				zNPC:getDescriptor():setVoicePrefix("MaleZombie")
			end
            local sq = zNPC:getCurrentSquare()
            if sq then getSoundManager():PlayWorldSound("zombie_agg02", false, sq, 20, 0, 1, false) end
		end, 60) -- ~1s
        
        --cho zombie cử động lại bình thường
		DelayFunction(function()
            local sq = zNPC:getCurrentSquare()
            if sq then getSoundManager():PlayWorldSound("zombie_agg03", false, sq, 20, 0, 1, false) end
            
            local questMD = GetQuestGlobalModData(qStep)
			questMD.NPC[qStep]["turnToZombie"] = true
            
			sendClientCommand(player, "NPCQuestCM", "UpdateQuestModData", {qStep = qStep, modDataKey = "turnToZombie", val = true})
            print("Client---NPCTurnToZombie= ", qStep, zNPC:getX(), zNPC:getY(), zNPC:getZ())
			if popup then popup:removeFromUIManager() end
		end, 480) -- ~8s
		--sendClientCommand(player, "NPCQuestCM", "UpdateQuestModData", questMD)
		
		zData.NotUpdateVariable = true
	end
end
local NPCTurnToZombie = VLF.NPCTurnToZombie

--Xóa NPC khi có zombies xung quanh
local function ZombiesAroundNPC(zNPC, minDist, qStep)
    if not zNPC then return end
    if not minDist then minDist = 2 end
    local zombies = getCell():getZombieList()
    
    if zombies:size() > 1 then
        for i = 0, zombies:size()-1 do
            local zombie = zombies:get(i)
            if zombie ~= zNPC and not zombie:isDead() then
                local z, _z = math.floor(zombie:getZ()), math.floor(zNPC:getZ())
                if z and _z and z == _z then
                    local dist = (math.floor(Caldistance(zombie,zNPC) * 100 + 0.5) / 100)
                    if dist and dist < minDist then
                        --Lấy tất cả npcID đang spawned
                        local npcIDTab = GetAllnpcIDSpawning()
                        --Nếu có 2+ NPC đang spawned thì
                        local isNPC = false
                        
                        if #npcIDTab > 1 then
                            --local npcID = GetZombieOnlineIDorUID(zNPC)
                            local zID = GetZombieOnlineIDorUID(zombie)
                            
                            --Nếu zombie không phải bất kì NPC nào thì xóa NPC
                            for _, _npcID in ipairs(npcIDTab) do
                                if zID == _npcID then
                                    isNPC = true
                                    break
                                end
                            end
                        end
                        
                        if isNPC == false then
                            print("Client---ZombiesAroundNPC---Remove NPC= ", qStep, dist, minDist, math.floor(zNPC:getX()), math.floor(zNPC:getY()), zNPC:getZ())
                            RemoveNPCQuest(zNPC)
                            return
                        end
                        
                    end
                end
            end
        end
    end
end

--Xóa NPC khi cách xa khỏi điểm spawn --squareSP
local function NPCFarAwaySpawnPoint(zNPC, squareSP, maxDist, qStep)
    if not zNPC or not squareSP then return false end
    if not maxDist then maxDist = 2 end
    local dist = Caldistance(zNPC,squareSP)
    if dist and dist > maxDist then
        print("Client---NPCFarAwaySpawnPoint---Remove NPC= ", qStep, math.floor(dist*100)/100, maxDist, math.floor(zNPC:getX()), math.floor(zNPC:getY()), zNPC:getZ())
        RemoveNPCQuest(zNPC)
        
        --local popup = ActiveQuestIntro_Free(getText("IGUI_VLQ_NPCFlee_1"), (getCore():getScreenWidth() - 800) / 2, (getCore():getScreenHeight() - 200) / 16, 800, 200, false, "")
        --DelayFunction(function() 
        --    if popup then popup:removeFromUIManager() end
        --end, 600)
        return true
    end
    return false
end

--Xóa NPC khi có lửa ở gần
local function NPCOnFire(zNPC, minDist, qStep)
    if not zNPC then return end
    local sq = zNPC:getCurrentSquare()
    if not sq then return end
    
    --khi npc bị cháy
    if zNPC:isOnFire() then
        print("Client---NPCOnFire-1--Remove NPC= ", qStep, zNPC:getX(), zNPC:getY(), zNPC:getZ())
        RemoveNPCQuest(zNPC)
        
        local popup = ActiveQuestIntro_Free(getText("IGUI_VLQ_NPCFlee_2"), (getCore():getScreenWidth() - 800) / 2, (getCore():getScreenHeight() - 200) / 16, 800, 200, false, "")
        DelayFunction(function() 
            if popup then popup:removeFromUIManager() end
        end, 600)
        return
    end
    
    --khi có lửa quanh ô đứng
    local x, y, z = sq:getX(), sq:getY(), sq:getZ()
    local dist = minDist or 1
    for i = 0, 1 do
        for j = 0, 1 do
            local _x, _y = x + i, y + j
            local _sq = getCell():getGridSquare(_x, _y, z)
            if _sq and _sq:getFire() then
                print("Client---NPCOnFire-2--Remove NPC= ", qStep, _x, _y, z)
                RemoveNPCQuest(zNPC)
                local popup = ActiveQuestIntro_Free(getText("IGUI_VLQ_NPCFlee_2"), (getCore():getScreenWidth() - 800) / 2, (getCore():getScreenHeight() - 200) / 16, 800, 200, false, "")
                DelayFunction(function() 
                    if popup then popup:removeFromUIManager() end
                end, 600)
                return
            end
        end
    end
end

--Kiểm tra khoảng cách giữa NPC và player để xóa NPC
local function NPCFarAwayPlayerCondition(zNPC, player, zID, qStep, maxDist)
    if not zNPC or not player then return end
    local x1, y1 = zNPC:getX(), zNPC:getY()
    local x2, y2 = player:getX(), player:getY()
    --local zData = zNPC:getModData()
    local zData = GetZombieModDataQuest(zNPC)
    local fDistance = CalculateDistanceXY(x1, y1, x2, y2)
    if fDistance then
        if not maxDist then maxDist = 70 end
        --player phải đến gần NPC mới kích hoạt
        if not zData.goToNextNPC then
            if fDistance < maxDist then
                zData.goToNextNPC = true
            end
        elseif zData.goToNextNPC == true then
        --khi khoảng cách giữa npc và player vượt quá 70 ô thì yêu cầu remove NPC
            if fDistance > maxDist then
                if not zData.requestedRemove then
                    zData.requestedRemove = true
                    
                    RemoveNPCQuest(zNPC)
                    print("Client---NPCFarAwayPlayerCondition---Remove NPC= ", zID, qStep, maxDist, x1, y1, zNPC:getZ())
                    sendClientCommand(getPlayer(), "NPCQuestCM", "RemoveNPCWhenFarAwayPlayers", {zID = zID, qStep = qStep, maxDist = maxDist})
                    --SetRagdollToStartOption()
                    --Chỉnh giá trị isRagdoll về lúc bắt đầu load NPC
                    if VLF.var.ragdollOptionStart then 
                        getCore():setOptionUsePhysicsHitReaction(VLF.var.ragdollOptionStart)
                    end
                end
            end
        end
    end
end


--UPDATE NPC
local function OnZombieNPCUpdate(zombie)
    if not IsCharExistAndAlive(zombie) then return end
    
    local zData = GetZombieModDataQuest(zombie)
    local zUID = zombie:getUID()
    
    --để đảm bảo zombie không dùng lại moddata của z cũ
    if not zData.UID then   --modData mới
        zData.UID = zUID
        return  --return to skip this tick
    else
        if zData.UID ~= zUID then    --sử dụng lại modData
            --reset zData
            for k in pairs(zData) do
                zData[k] = nil
            end
            zData.UID = zUID
            return  --return to skip this tick
        end
    end
     
    if not zData.spawnPoint then
        local x, y, z = zombie:getX(), zombie:getY(), zombie:getZ()
        if x and y and z then
            zData.spawnPoint = getCell():getGridSquare(x, y, z)
            return  --skip thêm 1 tick nữa
        end
    end
    --đã qua 2 tick
    
    --if not zData.isNPCQuest then return end --không dùng vì client chưa có zombie này sẽ không gán được ModData zombie
    local zID = GetZombieOnlineIDorUID(zombie)
    local questMD = GetQuestGlobalModData(nil)
    local qStep = questMD.NPCqStep[zID]
    if not qStep then return end
    
    local _zID = questMD.NPC[qStep]["zID"]
    --xác nhận chính để phân biệt NPC
    if not zID or not _zID or zID ~= _zID then return end
    --khởi tạo VLF.var.npcUID khi NPC spawn dùng để client check mỗi phút NPC này còn tồn tại không --dùng set Ragdoll
    if not VLF.var.npcUID[qStep] then VLF.var.npcUID[qStep] = zData.UID end
    
    local npcData = VLF.npcData[qStep]
    if not questMD.NPC[qStep]["turnToZombie"] then
        if zombie:getVariableBoolean(npcData.variable) ~= true and not zData.NotUpdateVariable then
        --if zombie:getVariableBoolean(npcData.variable) ~= true and not zData.NotUpdateVariable and not zData.preventZombieUselessthen then
             
           --zData.preventZombieUselessthen = true
           
            SetZombieInactiveFakeNPC(zombie)
            zombie:setVariable(npcData.variable, true)
            TurnZombieToNPC(zombie, qStep, npcData)
            --lấy giá trị isRagdoll khi bắt đầu NPC quest để làm backup
            VLF.var.ragdollOptionStart = getCore():getOptionUsePhysicsHitReaction()
        end    

        if not zombie:getVariableBoolean(npcData.variable) then return end
        local zNPC = zombie
        if not zData.zTick then zData.zTick = 0 end
        local zTick = zData.zTick
        
        if zNPC:getVariableBoolean("npcQuestIdle") then        
            --đổi state zombie thành StaggerBackState để không bị đẩy, không bị đổi anim
            --if not zNPC:isCurrentState(StaggerBackState.instance()) then zNPC:changeState(StaggerBackState.instance()) end
            
            --npc có data infected biến thành Zombie sau x phút
            local infectedNPC = false
            if npcData.infected and npcData.infected == true then
                infectedNPC = true
            end
            if not zData.becomeZ then
                if infectedNPC == true then
                    DelayFunction(function()
                        --sau delay time thì kiểm tra còn zNPC này không thì mới chạy func
                        local zNPC = GetZombieByOnlineIDOrUID(zID)
                        if zNPC then
                            NPCTurnToZombie(getPlayer(), zNPC, qStep, {isPopUp = false})
                        end
                    end, 36000) -- ~10 phút
                end
                zData.becomeZ = true
            end
            
            if zTick % 7 == 0 then
                local player = getPlayer()
                zNPC:setUseless(true)
                --NPC xoay mặt về phía player
                if zNPC:getActionStateName() == "idle" and not npcData.isGhoul then
                    zNPC:faceThisObject(player)
                end
                --kiểm tra khoảng cách NPC với player
                NPCFarAwayPlayerCondition(zNPC, player, zID, qStep, 70)
                --tắt Ragdoll
                local core = getCore() 
                if core:getOptionUsePhysicsHitReaction() then core:setOptionUsePhysicsHitReaction(false) end
            end
            if zTick % 19 == 0 then
                --kiểm tra khoảng cách NPC với spawn point
                local spawnPoint = zData.spawnPoint
                if spawnPoint then NPCFarAwaySpawnPoint(zNPC, spawnPoint, 2, qStep) end
                --Nếu NPC không phải infected thì check
                if infectedNPC == false then
                    ZombiesAroundNPC(zNPC, 0.7, qStep)
                    NPCOnFire(zNPC, 1, qStep)
                end
            end

        elseif zNPC:getVariableBoolean("npcQuestRifleAiming") then
            if zTick % 7 == 0 then
                UpdateRifleNPC(zNPC, zID, qStep, zData)
            end   
        
        elseif zNPC:getVariableBoolean("npcQuestSleepOnBed") then
            if zTick % 7 == 0 then
                UpdateNPCSleepOnBed(zNPC, zID, qStep, zData)
            end
        end
       
        zData.zTick = zTick + 1       
        
         
    elseif questMD.NPC[qStep]["turnToZombie"] == true then
        if not zData.npcTurnToZombie then
            SetZombieDefault(zombie)
            DeactiveNPCSpawnedData(qStep, zID)
            zData.npcTurnToZombie = true
            if not zombie:getTarget() then zombie:setTarget(getPlayer()) end
        end
    end
end
Events.OnZombieUpdate.Remove(OnZombieNPCUpdate)
Events.OnZombieUpdate.Add(OnZombieNPCUpdate)


local function NPCQuestSay(zNPC, isAttacked)
    if zNPC and instanceof(zNPC, "IsoZombie") then
        if isAttacked == true then zNPC:addLineChatElement("Ouch!", 0.8, 0.1, 0.1) end
    end    
end

--NPC khi bị player đánh
local function OnHitZNPCQuest(zombie, attacker, bodyPart, weapon)
    local questMD = GetQuestGlobalModData(nil)
    local qStep = questMD.NPCqStep[GetZombieOnlineIDorUID(zombie)]
    if not qStep then return end
    if zombie:getVariableBoolean(VLF.npcData[qStep].variable) ~= true then return end
    
    local zData = GetZombieModDataQuest(zombie)
    NPCQuestSay(zombie, true) --NPC say
    if not zData.countHit then zData.countHit = 0 end
    if zData.countHit < 3 then zData.countHit = zData.countHit + 1 end
    if zData.countHit >= 3 then
        --xoa zombie
        RemoveNPCQuest(zombie)
        local popup = ActiveQuestIntro_Free(getText("IGUI_VLQ_NPCFlee_1"), (getCore():getScreenWidth() - 800) / 2, (getCore():getScreenHeight() - 200) / 16, 800, 200, false, "")
        DelayFunction(function() 
            if popup then popup:removeFromUIManager() end
        end, 600)
        zData.countHit = 0
    end
end
--Events.OnHitZombie.Remove(OnHitZNPCQuest)
--Events.OnHitZombie.Add(OnHitZNPCQuest)

--Multiplayer --Client online --Nếu player đang active quest có NPC login thì yêu cầu server kiểm tra và update NPCsQueueToSpawn
--Để nếu NPC này đang false trên server thì sẽ active true khi player này online
local function OnCreatePlayer_ActiveNPCInQueueSpawn(playerNum, player)
    local playerGMD = GetPlayerGlobalModData(player)

    --Nếu player đang active quest có NPC thì yêu cầu server kiểm tra và update NPCsQueueToSpawn
    for q, data in pairs(playerGMD.Quest) do
        for qStep, _data in pairs(data) do
            local active = _data.active
            --nếu bước nhiệm vụ đang kích hoạt
            if active and active == true then
                local isQStepNPC = _data.isNPC
                
                --nếu bước nhiệm vụ là loại có NPC
                if isQStepNPC and isQStepNPC == true then
                    --gửi command set NPCsQueueToSpawn khi qStep active và qStep này là có NPC và NPCsQueueToSpawn[qStep] đang là false
                    local questMD = GetQuestGlobalModData(qStep)
                    
					if questMD.NPCsQueueToSpawn and questMD.NPCsQueueToSpawn[qStep] ~= true then
                        sendClientCommand(player, 'NPCQuestCM', 'ActiveNPCInQueueSpawnCM', {qStep = qStep, active = active})
                    end
                end
            end
        end
    end    
end
Events.OnCreatePlayer.Remove(OnCreatePlayer_ActiveNPCInQueueSpawn)
Events.OnCreatePlayer.Add(OnCreatePlayer_ActiveNPCInQueueSpawn)
















