--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

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}

local Func = {}
Func.GetPlayerGlobalModDataQ1 = VLF.GetPlayerGlobalModDataQ1
Func.CalculateDistanceXY = VLF.CalculateDistanceXY
Func.GetZombieOnlineIDorUID = VLF.GetZombieOnlineIDorUID
Func.GetPlayerID = VLF.GetPlayerID
Func.SetZombieDefault = VLF.SetZombieDefault
Func.Caldistance = VLF.Caldistance
Func.ActiveQuestIntro_Free = VLF.ActiveQuestIntro_Free
Func.GetPlayerGlobalModData = VLF.GetPlayerGlobalModData
Func.DelayFunction = VLF.DelayFunction
Func.GetQuestGlobalModData = VLF.GetQuestGlobalModData
Func.PlaySound = VLF.PlaySound
Func.GetAllnpcIDSpawning = VLF.GetAllnpcIDSpawning

local npcUID = {}
local 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

--Chỉnh giá trị isRagdoll về lúc bắt đầu load NPC
local function SetRagdollToStartOption()
    local isRagdoll = ragdollOptionStart
    if isRagdoll then getCore():setOptionUsePhysicsHitReaction(isRagdoll) end
end

--Trỏ qStep ngẫu nhiên của Free NPC tới qStep cố định
local function FreeNPCToSameQStep(qStep, sameQStep)
    local questMD = Func.GetQuestGlobalModData({qStep = qStep})
    questMD.NPC[qStep]["sameQStep"] = sameQStep
    VLF.npcData[qStep] = VLF.npcData[sameQStep]
end

--Cập nhật qStep của FreeNPC khi vào game
local function UpdateFreeNPCOnStart(playerNum, player)
    local questMD = Func.GetQuestGlobalModData({qStep = nil})
    for qStep, data in pairs(questMD.NPC) do
        if data["sameQStep"] then
            FreeNPCToSameQStep(qStep, data["sameQStep"])
        end
    end            
end
Events.OnCreatePlayer.Remove(UpdateFreeNPCOnStart)
Events.OnCreatePlayer.Add(UpdateFreeNPCOnStart)

--Kiểm tra có bất kì NPC nào đang chờ spawn không
local function IsAnyNPCQueueToSpawn()
    local questMD = Func.GetQuestGlobalModData({qStep = nil})
    for qStep, val in pairs(questMD.NPCsQueueToSpawn) do
        if val == true then return true end
    end
    return false
end

--Kiểm tra điểm spawn có thể spawn, nếu không thì tìm điểm spawn mới
local function CheckNPCSpawnPoint(square, args) -- CheckNPCSpawnPoint(square, {qStep = qStep, range = 3})
    --if not sq then return nil end
    --print("Client-----------CheckNPCSpawnPoint: 11111111") 
    local x, y, z = square:getX(), square:getY(), square:getZ() --z = 0 vì square luôn lấy load tầng 1
    if not x or not y or not z then return end
    local range = args.range or 3
    local npcZ = VLF.npcData[args.qStep].startZ or 0
    local sq = getCell():getGridSquare(x, y, npcZ)
    --kiểm tra square này có spawn được ko
    if sq and sq:TreatAsSolidFloor() and sq:isFree(true) then return sq end

    for h = 0, npcZ, npcZ >=0 and 1 or -1 do
        for i = -range, range do
            for j = -range, range do
                local newX, newY, newZ = x + i, y + j, npcZ - h
                local newSq = getCell():getGridSquare(newX, newY, newZ)
                if newSq and newSq:TreatAsSolidFloor() and newSq:isFree(true) then return newSq end
            end
        end
    end
    --print("Client-----------CheckNPCSpawnPoint: no square for NPC spawn") 
    return nil
end

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"
    }
  
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]
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
    
    zNPC:getModData().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 isHole = args.isHole or false
    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
        --print("Client-----------TurnZombieToNPC: outfit= ", outfit)
        --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)
                    --print("--------------------------colorTab")
                    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

    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()
	if not zData.NotUpdateVariable then --điều kiện để không update Variable,
		local questMD = Func.GetQuestGlobalModData({qStep = qStep})
		--print("Client-----------NPCTurnToZombie= ", zNPC, qStep)

		local popup
		if args.isPopUp and args.isPopUp == true then
			local qID = args.qID or 1
			popup = Func.ActiveQuestIntro_Free(getText("IGUI_VLQ_"..tostring(qID).."_NPCTalk_"..tostring(qStep)), (getCore():getScreenWidth() - 800) / 2, (getCore():getScreenHeight() - 200) / 16, 800, 200, false, "")
		end
		
		Func.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

		Func.DelayFunction(function()
            local sq = zNPC:getCurrentSquare()
            if sq then getSoundManager():PlayWorldSound("zombie_agg03", false, sq, 20, 0, 1, false) end
			questMD.NPC[qStep]["turnToZombie"] = true
			sendClientCommand(player, 'NPCQuestCM', 'UpdateQuestModData', {qStep = qStep, modData = "turnToZombie", val = true})
			if popup then popup:removeFromUIManager() end
		end, 480) -- ~8s
		--sendClientCommand(player, 'NPCQuestCM', 'UpdateQuestModData', questMD)
		
		zData.NotUpdateVariable = true
	end
end
Func.NPCTurnToZombie = VLF.NPCTurnToZombie

--Xóa NPC
function VLF.RemoveNPCQuest(zNPC)
    if not zNPC then return end
    local zID = Func.GetZombieOnlineIDorUID(zNPC)
    local questMD = Func.GetQuestGlobalModData({qStep = nil})
    local qStep = questMD.NPCqStep[zID]
    --print("Client-----------RemoveNPCQuest= ", zNPC:getUID())
    sendClientCommand(getPlayer(), 'NPCQuestCM', 'RemoveNPC', {zID = zID, qStep = qStep})
    Func.SetZombieDefault(zNPC)
    zNPC:removeFromSquare()
    zNPC:removeFromWorld()
end
local RemoveNPCQuest = VLF.RemoveNPCQuest

--Xóa NPC khi có zombies xung quanh
local function ZombiesAroundNPC(zNPC, args) --ZombiesAroundNPC(zNPC, {minDist = 1})
    if not zNPC then return end
    local minDist = args.minDist or 2
    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 = Func.Caldistance(zombie,zNPC)
                    if dist and dist < minDist then
                        --Lấy tất cả npcID đang spawned
                        local npcIDTab = Func.GetAllnpcIDSpawning()
                        --Nếu có 2+ NPC đang spawned thì
                        local isNPC = false
                        if #npcIDTab > 1 then
                            --local npcID = Func.GetZombieOnlineIDorUID(zNPC)
                            local zID = Func.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= ", dist, minDist)
                            RemoveNPCQuest(zNPC)
                            return
                        end
                        
                    end
                end
            end
        end
    end
end

--Xóa NPC khi cách xa khỏi điểm spawn
local function NPCFarAwaySpawnPoint(zNPC, squareSP, args) --NPCFarAwaySpawnPoint(zNPC, squareSP, {maxDist = 2})
    if not zNPC or not squareSP then return nil end
    local maxDist = args.maxDist or 2
    local dist = Func.Caldistance(zNPC,squareSP)
    if dist and dist > 2 then
        --print("Client-----------NPCFarAwaySpawnPoint= ", dist, maxDist)
        RemoveNPCQuest(zNPC)
        
        local popup = Func.ActiveQuestIntro_Free(getText("IGUI_VLQ_NPCFlee_1"), (getCore():getScreenWidth() - 800) / 2, (getCore():getScreenHeight() - 200) / 16, 800, 200, false, "")
        Func.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, args) --NPCOnFire(zNPC, {minDist = 1})
    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
        RemoveNPCQuest(zNPC)
        
        local popup = Func.ActiveQuestIntro_Free(getText("IGUI_VLQ_NPCFlee_2"), (getCore():getScreenWidth() - 800) / 2, (getCore():getScreenHeight() - 200) / 16, 800, 200, false, "")
        Func.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 = args.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
                RemoveNPCQuest(zNPC)
                local popup = Func.ActiveQuestIntro_Free(getText("IGUI_VLQ_NPCFlee_2"), (getCore():getScreenWidth() - 800) / 2, (getCore():getScreenHeight() - 200) / 16, 800, 200, false, "")
                Func.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, args) --NPCFarAwayPlayerCondition(zNPC, player, {zID = zID, qStep = qStep, maxDist = 70})
    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 fDistance = Func.CalculateDistanceXY({x1 = x1, y1 = y1, x2 = x2, y2 = y2})
    if fDistance then
        local maxDist = args.maxDist or 70
        --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
                sendClientCommand(getPlayer(), 'NPCQuestCM', 'RemoveNPCWhenFarAwayPlayers', {zID = args.zID, qStep = args.qStep, maxDist = maxDist})
                
                SetRagdollToStartOption()
                --print("Client-----------NPCFarAwayPlayerCondition= ", fDistance, maxDist)
            end
        end
    end
end

--UPDATE NPC
local function OnZombieNPCUpdate(zombie)
    local zData = zombie:getModData()
    if not zData.UID then zData.UID = zombie:getUID() return end --để đảm bảo zombie không dùng lại moddata của z cũ
    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
        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 = Func.GetZombieOnlineIDorUID(zombie)
    local questMD = Func.GetQuestGlobalModData({qStep = nil})
    local qStep = questMD.NPCqStep[zID]
    if not qStep then return end
    
    local questMD_NPC = questMD.NPC
    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 npcUID khi NPC spawn dùng để check mỗi phút NPC này còn tồn tại không
    if not npcUID[qStep] then 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
            zombie:setNoTeeth(true)
            zombie:setInvulnerable(true)
            zombie:getEmitter():stopAll()
            zombie:setPrimaryHandItem(nil)
            zombie:setSecondaryHandItem(nil)
            zombie:resetEquippedHandsModels()
            zombie:clearAttachedItems()
            zombie:setDir(IsoDirections.E)
            zombie:setVariable(npcData.variable, true)
            zombie:setCanWalk(false)
            zombie:setUseless(true)
            --Xóa âm thanh zombie
            zombie:getDescriptor():setVoicePrefix("")
            TurnZombieToNPC(zombie, qStep, npcData)
            --lấy giá trị isRagdoll khi bắt đầu NPC quest để làm backup
            ragdollOptionStart = getCore():getOptionUsePhysicsHitReaction()
        end    

        if not zombie:getVariableBoolean(npcData.variable) then return end
        
        --npc có data infected biến thành Zombie
        local infectedNPC = false
        if npcData.infected and npcData.infected == true then
            infectedNPC = true
        end
        if not zData.becomeZ then
            if infectedNPC == true then
                Func.DelayFunction(function()
                    Func.NPCTurnToZombie(getPlayer(), zombie, qStep, {isPopUp = false})
                end, 36000) -- ~10 phút
            end
            zData.becomeZ = true
        end
        
        --đổi state zombie thành StaggerBackState để không bị đẩy, sẽ khiến anim npc XOAY CHẬM
        --if not zombie:isCurrentState(StaggerBackState.instance()) then zombie:changeState(StaggerBackState.instance()) end
            
        if not zData.zTick then zData.zTick = 0 end
        local zTick = zData.zTick
        if zTick % 7 == 0 then
            local player = getPlayer()
            zombie:setUseless(true)
            --NPC xoay mặt về phía player
            if zombie:getActionStateName() == "idle" and not npcData.isGhoul then
                zombie:faceThisObject(player)
            end
            --kiểm tra khoảng cách NPC với player
            NPCFarAwayPlayerCondition(zombie, player, {zID = zID, qStep = qStep, maxDist = 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(zombie, spawnPoint, {maxDist = 2}) end
            --print("Client-----------OnZombieNPCUpdate= ", zombie) 
            --Nếu NPC không phải infected thì check
            if infectedNPC == false then
                ZombiesAroundNPC(zombie, {minDist = 0.3})
                NPCOnFire(zombie, {minDist = 1})
            end
        end
        zData.zTick = zTick + 1
        --zombie:resetModelNextFrame()
        --zombie:resetModel()
        
    elseif questMD_NPC[qStep]["turnToZombie"] == true then
        if not zData.npcTurnToZombie then
            Func.SetZombieDefault(zombie)
            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, args)
    if zNPC and instanceof(zNPC, "IsoZombie") then
        if args.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)
    --if not zombie:getModData().isNPCQuest then return end
    local questMD = Func.GetQuestGlobalModData({qStep = nil})
    local qStep = questMD.NPCqStep[Func.GetZombieOnlineIDorUID(zombie)]
    if not qStep then return end
    if zombie:getVariableBoolean(VLF.npcData[qStep].variable) ~= true then return end
    
    --print("Client-----------OnHitZNPCQuest= ", zombie:getUID())
    local zData = zombie:getModData()
    NPCQuestSay(zombie, {isAttacked = 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
        --print("Client-----------OnHitZNPCQuest= ", zData.countHit)
        RemoveNPCQuest(zombie)
        local popup = Func.ActiveQuestIntro_Free(getText("IGUI_VLQ_NPCFlee_1"), (getCore():getScreenWidth() - 800) / 2, (getCore():getScreenHeight() - 200) / 16, 800, 200, false, "")
        Func.DelayFunction(function() 
            if popup then popup:removeFromUIManager() end
        end, 600)
        zData.countHit = 0
    end
end
--Events.OnHitZombie.Remove(OnHitZNPCQuest)
--Events.OnHitZombie.Add(OnHitZNPCQuest)

--Thêm lựa chọn Talk với NPC khi chuột phải vào ô npc đứng
local function OnPreFillWorldObjectContextMenu(playerIndex, context, worldobjects, test)
    if IsAnyNPCQueueToSpawn() == false then return end
    
    local player = getSpecificPlayer(playerIndex)
    if not player then return end
    local mx, my = ISCoordConversion.ToWorld(getMouseXScaled(), getMouseYScaled(), player:getZ())
    local x, y, z = math.floor(mx), math.floor(my), player:getZ()
    --local zNPC = square:getZombie()
    for _x = x, x + 1 do
        for _y = y, y + 1 do
            local square = getCell():getGridSquare(_x, _y, z)
            if square then
                local objs = square:getMovingObjects()
                for i = 0, objs:size() - 1 do
                    local zNPC = objs:get(i)
                    if zNPC and zNPC:isZombie() then
                        local questMD = Func.GetQuestGlobalModData({qStep = nil})
                        local zID = Func.GetZombieOnlineIDorUID(zNPC)
                        local qStep = questMD.NPCqStep[zID]
                        if qStep and zNPC:getVariableBoolean(VLF.npcData[qStep].variable) and not VLF.npcData[qStep].infected then
                            if zNPC:getModData().name then
                                context:addOption(getText("IGUI_VLQ_TalkTo")..zNPC:getModData().name, player, VLF.TalkToNPC[qStep], square, zNPC, qStep)
                            else
                                context:addOption(getText("IGUI_VLQ_Talk"), player, VLF.TalkToNPC[qStep], square, zNPC, qStep)
                            end
                        end
                    end
                end
            end
        end
    end
end
Events.OnPreFillWorldObjectContextMenu.Remove(OnPreFillWorldObjectContextMenu)
Events.OnPreFillWorldObjectContextMenu.Add(OnPreFillWorldObjectContextMenu)

--Tìm NPC có điểm startpoint chính là square đang load
--Có thể lỗi khi các NPC cùng tọa độ x, y và khác z. --FIXME
local function GetNPCStartPointSameAsSquare(square)
    local sqX, sqY = square:getX(), square:getY()
    
    --lấy điểm start trực tiếp từ bảng
    local npcData = VLF.npcData
    for qStep, data in pairs(npcData) do
        if data.startX == sqX and data.startY == sqY then --startPoint cùng tọa độ với square
            local playerGMD_Quest_Q1 = Func.GetPlayerGlobalModDataQ1(getPlayer(), {qStep = qStep})
            if qStep == "q1s014_2" then --NPC Dr. Bassler luôn spawn cùng Dr. Venter khi qStep q1s011 == true
                if playerGMD_Quest_Q1.q1s011.active == true then
                    return qStep
                end
            end
            --local _qStep = string.sub(qStep, 1, 6) --lấy 6 ký tự đầu của qStep --để các NPC phụ cùng spawn có qStep "q1s014_2" cũng có thể check được qStep q1s014 có đang active
            if playerGMD_Quest_Q1[qStep].active == true then --NPC có qStep đang được kích hoạt mới spawn. --để phân biệt NPC cùng vị trí startPoint
                return qStep
            end
        end
    end
    return nil
end

--CALL FUNCTION 
--Khi client load square thì spawn NPC
--Tầng 1 luôn có sàn, tầng 2 phá sàn thì LoadGridsquare không load square này nữa
local function LoadGridsquare(square)
    if square:getZ() ~= 0 then return end --Chỉ check square tầng 1, không thể phá sàn
    if IsAnyNPCQueueToSpawn() == false then return end 

    --Tìm NPC có startPoint là square này
    local qStep = GetNPCStartPointSameAsSquare(square)
    if qStep then        
        --print("Client-----------NPC Start Point= "..tostring(qStep).." - "..tostring(square:getX())..", "..tostring(square:getY())..", "..tostring(square:getZ()))
        local questMD = Func.GetQuestGlobalModData({qStep = qStep})
        --Nếu NPC có qStep này đang được kích hoạt = true VÀ turnToZombie chưa kích hoạt
        if questMD.NPCsQueueToSpawn[qStep] == true and not questMD.NPC[qStep]["turnToZombie"] then
            local tick = 0
            local function OnTick()
                tick = tick + 1    
                if tick % 30 == 0 then
                    --tìm điểm spawn cho NPC
                    local range = VLF.npcData[qStep].spawnRange or 1
                    local spawnPoint = CheckNPCSpawnPoint(square, {qStep = qStep, range = range})
                    --print("Client-----------spawnPoint= ", spawnPoint)
                    if spawnPoint then
                        local x, y, z = spawnPoint:getX(), spawnPoint:getY(), spawnPoint:getZ()
                        local chunk = spawnPoint:getChunk()
                        if x and y and z and chunk then
                            
                            local pID = Func.GetPlayerID(getPlayer())
                            local femaleChance = VLF.npcData[qStep].femaleChance
                            local outfit = VLF.npcData[qStep].outfit
                            --print("Client-----------LoadGridsquare= ", pID, qStep, femaleChance, x, y, z ) 
                            --yêu cầu server spawn NPC
                            sendClientCommand(getPlayer(), 'NPCQuestCM', 'RequestSpawnNPC', {qStep = qStep, pID = pID, outfit = outfit, x = x, y = y, z = z, removeZRange = 5, femaleChance = femaleChance})
                            Events.OnTick.Remove(OnTick)
                        end
                    end
                end
                if tick >= 180 then Events.OnTick.Remove(OnTick) end
            end
            Events.OnTick.Add(OnTick) 
        end
    end
end
Events.LoadGridsquare.Remove(LoadGridsquare)
Events.LoadGridsquare.Add(LoadGridsquare)

--YÊU CẦU GLOBAL MODDATA PLAYER TỪ SERVER KHI ĐĂNG NHẬP VÀO GAME
--Nếu player đang active quest có NPC thì yêu cầu server kiểm tra và update NPCsQueueToSpawn
local function CreateModDataPlayer(playerNum, player)
    local playerGMD = Func.GetPlayerGlobalModData(player)
    sendClientCommand(player, 'NPCQuestCM', 'GetOrCreatePlayerGMD', {pID = pID})
    
    --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 = Func.GetQuestGlobalModData({qStep = qStep})
                    if questMD.NPCsQueueToSpawn and not questMD.NPCsQueueToSpawn[qStep] == true then
                        sendClientCommand(player, 'NPCQuestCM', 'ActiveNPCInQueueSpawnCM', {qStep = qStep, active = active})
                    end
                end
            end
        end
    end    
end
Events.OnCreatePlayer.Remove(CreateModDataPlayer)
Events.OnCreatePlayer.Add(CreateModDataPlayer)

--OnServerCommand KHÔNG CHẠY TRONG SINGLE PLAYER
local function OnServerCommand(module, command, args)
    if module == "NPCQuestCM_C" then
        if command == "GetZNPCQuest_C" then
        --HỦY vì client không có zombie này sẽ không update được, đến khi load zombie thì chưa có data
        --[[
            local zombie = VLF.GetZombieByOnlineIDOrUID(args.zID)
            local zData = zombie:getModData()
            zombie:setUseless(true)
            zData.UID = zombie:getUID()
            zData.isNPCQuest = true
            zData.spawnPoint = getCell():getGridSquare(zombie:getX(), zombie:getY(), zombie:getZ())
            ]]
            
        elseif command == "GetOrCreatePlayerGMD_C" then
            --cập nhật moddata global player từ server
            local player = getPlayer()
            if not player then return end
            local playerGMD = Func.GetPlayerGlobalModData(player)
            for k, v in pairs(args) do
                playerGMD[k] = v
            end

        elseif command == "UpdateQuestModData_C" then
            local qStep = args.qStep
            local questMD = Func.GetQuestGlobalModData({qStep = qStep})
            local modData = args.modData
            local val = args.val
            if modData == "turnToZombie" then
                questMD.NPC[qStep][modData] = val
            elseif modData == "AddedObj1" then
                if not questMD.Object[qStep] then questMD.Object[qStep] = {} end
                questMD.Object[qStep][modData] = val
            end
            
        elseif command == "FreeNPCSameQStep_C" then
            FreeNPCToSameQStep(args.qStep, args.sameQStep)
            
        elseif command == "SetDefaultZombie_C" then
            local zombie = Func.GetZombieByOnlineIDOrUID(args.zID)
            if zombie then Func.SetZombieDefault(zombie) end
            
        end
    end
end
Events.OnServerCommand.Remove(OnServerCommand)
Events.OnServerCommand.Add(OnServerCommand)

--kiểm tra NPC còn tồn tại trên CLient không để set lại giá trị Ragdoll
local function EveryOneMinute_CheckRagdoll()
    local zombies = getCell():getZombieList()
    local flag = 0
    for i = 0, zombies:size()-1 do
        local zombie = zombies:get(i)
        if zombie then
            local zUID = zombie:getUID()
            for qStep, uid in ipairs(npcUID) do
                if uid == zUID then
                    flag = 1
                end
            end
        end
    end
    --nếu tất cả NPC không tồn tại thì chỉnh lại Ragdoll
    if flag == 0 then
        SetRagdollToStartOption()
    end
end
Events.EveryOneMinute.Remove(EveryOneMinute_CheckRagdoll)
Events.EveryOneMinute.Add(EveryOneMinute_CheckRagdoll)

local function OnGameTimeLoaded_CheckRagdoll()
    ragdollOptionStart = getCore():getOptionUsePhysicsHitReaction()
    --print("--------------OnGameTimeLoaded_CheckRagdoll----------------= ", VLF.isRagdoll)
end
Events.OnGameTimeLoaded.Remove(OnGameTimeLoaded_CheckRagdoll)
Events.OnGameTimeLoaded.Add(OnGameTimeLoaded_CheckRagdoll)




