--HÀM TIỆN ÍCH, SỬ DỤNG CHUNG

VLF = VLF or {}
VLF.zombieStats = VLF.zombieStats or {}

local function OnInitGlobalModData(newGame)
	local questMD = ModData.getOrCreate("VL_Quest")
	if not questMD.NPC then questMD.NPC = {} end
    if not questMD.NPCsQueueToSpawn then questMD.NPCsQueueToSpawn = {} end
    if not questMD.NPCqStep then questMD.NPCqStep = {} end
    if not questMD.Object then questMD.Object = {} end
    
    if isClient() then
        ModData.request("VL_Quest")
    end
end
Events.OnInitGlobalModData.Add(OnInitGlobalModData)

--Lấy các thiết lập của ZombieLore lúc ban đầu
local function GetZombieStatsSetting()
		local zombieStats = VLF.zombieStats
        local sandboxOptions = getSandboxOptions()
		zombieStats.oldSpeed = sandboxOptions:getOptionByName("ZombieLore.Speed"):getValue()
		zombieStats.oldCognition = sandboxOptions:getOptionByName("ZombieLore.Cognition"):getValue()
		zombieStats.oldMemory = sandboxOptions:getOptionByName("ZombieLore.Memory"):getValue()
		zombieStats.oldSight = sandboxOptions:getOptionByName("ZombieLore.Sight"):getValue()
		zombieStats.oldHearing = sandboxOptions:getOptionByName("ZombieLore.Hearing"):getValue()
		zombieStats.oldStrength = sandboxOptions:getOptionByName("ZombieLore.Strength"):getValue()
        zombieStats.oldToughness = sandboxOptions:getOptionByName("ZombieLore.Toughness"):getValue()
end
if not isServer() then
    Events.OnGameStart.Add(GetZombieStatsSetting)
else
    Events.OnServerStarted.Add(GetZombieStatsSetting)
end

--Thiết lập chỉ số của zombie
function VLF.SetNewZombieStats(zombie, args) --VLF.SetNewZombieStats(zombie, {speed = 2, strength = 2, toughness = 2, cognition = 3, memory = 2, sight = 2, hearing = 2})
    local speed, cognition, memory, sight, hearing, strength, toughness = args.speed, args.cognition, args.memory, args.sight, args.hearing, args.strength, args.toughness
    local sandboxOptions = getSandboxOptions()
    local zombieStats = VLF.zombieStats

    if speed then sandboxOptions:set("ZombieLore.Speed", speed) end
    if cognition then sandboxOptions:set("ZombieLore.Cognition", cognition) end --1 open door
    if memory then sandboxOptions:set("ZombieLore.Memory", memory) end
    if sight then sandboxOptions:set("ZombieLore.Sight", sight) end
    if hearing then sandboxOptions:set("ZombieLore.Hearing", hearing) end
    if strength then sandboxOptions:set("ZombieLore.Strength", strength) end
    if toughness then sandboxOptions:set("ZombieLore.Toughness", toughness) end
    zombie:makeInactive(true);
    zombie:makeInactive(false);
    if speed then sandboxOptions:set("ZombieLore.Speed",zombieStats.oldSpeed) end
    if cognition then sandboxOptions:set("ZombieLore.Cognition",zombieStats.oldCognition) end
    if memory then sandboxOptions:set("ZombieLore.Memory",zombieStats.oldMemory) end
    if sight then sandboxOptions:set("ZombieLore.Sight",zombieStats.oldSight) end
    if hearing then sandboxOptions:set("ZombieLore.Hearing",zombieStats.oldHearing) end
    if strength then sandboxOptions:set("ZombieLore.Strength", zombieStats.oldStrength) end
    if toughness then sandboxOptions:set("ZombieLore.Toughness", zombieStats.oldToughness) end
end

--Kiểm tra có phải chế độ chơi Multiplayer hay không
function VLF.IsMultiplayerMode()
    local gamemode = getWorld():getGameMode()
    if gamemode == "Multiplayer" then
		return true
	else
		return false
	end
end
local IsMultiplayerMode = VLF.IsMultiplayerMode

--------------------------------------PLAYER--------------------------------------
--lấy ID từ Player
function VLF.GetPlayerID(player)
    if not player then return nil end
    local pID
    if IsMultiplayerMode() then
        pID = player:getOnlineID()
    else
        pID = player:getPlayerNum()
    end
    return pID
end
local GetPlayerID = VLF.GetPlayerID

--lấy Player từ ID player
function VLF.GetPlayerByID(pID)
    if not pID then return nil end
    local player
    if IsMultiplayerMode() then
        player = getPlayerByOnlineID(pID)
        return player
    else
        player = getSpecificPlayer(pID)
        return player
    end
    return nil
end

local function IsCorpseOnSquare(square)
    local objs = square:getStaticMovingObjects()
    for i = 0, objs:size()-1 do
        local obj = objs:get(i)
        if obj and instanceof(obj, "IsoDeadBody") then
            return true
        end
    end
    return false
end

----------------------------------MODDATA-----------------------------------
--Lấy Global ModData
function VLF.GetQuestGlobalModData(args) --local questMD = VLF.GetQuestGlobalModData({qStep = qStep})
    local questMD = ModData.getOrCreate("VL_Quest")
    if not questMD.NPC then questMD.NPC = {} end    --reset khi xong quest
    if not questMD.NPCsQueueToSpawn then questMD.NPCsQueueToSpawn = {} end  --reset khi xong quest
    if not questMD.NPCqStep then questMD.NPCqStep = {} end  --reset khi xong quest
    if not questMD.Object then questMD.Object = {} end  --KHÔNG reset khi xong quest
    
    local qStep = args.qStep
    if qStep then
        if not questMD.NPC[qStep] then questMD.NPC[qStep] = {} end
        --if not questMD.Object[qStep] then questMD.Object[qStep] = {} end --Tạo nhiều rác dữ liệu
    end
    return questMD
end
local GetQuestGlobalModData = VLF.GetQuestGlobalModData

--Lấy Global ModData của Player
function VLF.GetPlayerGlobalModData(player)
    if not player then return nil end
	local pID = GetPlayerID(player)
	local playerGMD = ModData.getOrCreate("VL_Player_"..tostring(pID))
	if not playerGMD.Quest then playerGMD.Quest = {} end
    
    return playerGMD
end

---------------------------------------------------------------------------------- 
function VLF.PlaySound(zNPC, soundName)
	if zNPC:getEmitter():isPlaying(soundName) then return end
    zNPC:getEmitter():playSound(soundName)
end
function VLF.StopSound(zNPC, soundName)
    local emitter = zNPC:getEmitter()
	if not emitter or not emitter:isPlaying(soundName) then return end
	zNPC:getEmitter():stopSoundByName(soundName)
end

--Chia String thành các dòng theo số lượng ký tự. --Tiếng Việt tính cả dấu là 1 ký tự
function VLF.SplitTextByLength(inputText, maxLength, font)
    local lines = {}       -- mảng kết quả (các dòng)
    local currentLine = "" -- dòng hiện tại
    --local count = 0        -- số ký tự hiện tại

    --function utf8_iter(str)
    --    return string.gmatch(str, "[%z\1-\127\194-\244][\128-\191]*")
    --end

    -- Duyệt từng từ trong đoạn văn
    --for word in utf8_iter(inputText) do
--[[
    local lang = getCore():getOptionLanguageName()
    if lang == "CN" or lang == "CH"  then
        for word in string.gmatch(inputText, ".") do
            local testLine = currentLine .. word
            local width = getTextManager():MeasureStringX(font, testLine)

            -- Nếu vượt quá giới hạn -> lưu dòng và bắt đầu dòng mới
            if width > maxLength then
                table.insert(lines, currentLine)
                currentLine = word
            else
                currentLine = testLine
            end
        end

        -- Thêm dòng cuối cùng (nếu còn)
        if currentLine ~= "" then
            table.insert(lines, currentLine)
        end
    else
    ]]
        local lastSpacePos = nil
        for word in string.gmatch(inputText, ".") do
            local testLine = currentLine .. word
            local width = getTextManager():MeasureStringX(font, testLine)
            if word == " " then
                lastSpacePos = #currentLine
            end
            
            -- Nếu vượt quá giới hạn -> lưu dòng và bắt đầu dòng mới
            if width > maxLength then
                --Tìm lại vị trí space để xuống dòng, nếu không có thì xuống dòng luôn
                if lastSpacePos then
                    table.insert(lines, string.sub(currentLine, 1, lastSpacePos))
                    currentLine = string.sub(currentLine, lastSpacePos + 2) .. word
                else
                    table.insert(lines, currentLine)
                    currentLine = word
                end
                lastSpacePos = nil
            else
                currentLine = testLine
            end
        end
        
        -- Thêm dòng cuối cùng (nếu còn)
        if currentLine ~= "" then
            table.insert(lines, currentLine)
        end
    --end
    return lines
end
local SplitTextByLength = VLF.SplitTextByLength

--Chờ x tick rồi mới chạy function
function VLF.DelayFunction(func, delay)
    delay = delay or 1
    local ticks = 0
    local canceled = false

    local function onTick()
        if not canceled and ticks < delay then
            ticks = ticks + 1
            return
        end
        Events.OnTick.Remove(onTick)
        if not canceled then func() end
    end
    Events.OnTick.Add(onTick)
    return function()
        canceled = true
    end
end

--Tính khoảng cách bán kính giữa 2 đối tượng
function VLF.Caldistance(obj1,obj2)
	return (math.abs(obj1:getX()-obj2:getX())^2 + math.abs(obj1:getY()-obj2:getY())^2)^0.5
end
local Caldistance = VLF.Caldistance

--Tính khoảng cách giữa 2 x hoặc giữa 2 y và lấy khoảng cách lớn nhất
function VLF.CalculateDistanceXY(args) --CalculateDistanceXY({x1 = x1, y1 = y1, x2 = x2, y2 = y2})
    local x1, x2, y1, y2 = args.x1, args.x2, args.y1, args.y2
    if not x1 or not x2 or not y1 or not y2 then return end
    local disX = math.abs(x1 - x2)
    local disY = math.abs(y1 - y2)
    --print("Client-----------CalculateDistanceXY= ", disX, disY)
    return disX >= disY and disX or disY
end

----------------------------------zNPC - ZOMBIE-----------------------------------
--Lấy Online ID hoặc UID từ zombie
function VLF.GetZombieOnlineIDorUID(zombie)
    if not zombie then return end
    local id
    if IsMultiplayerMode() then
        id = zombie:getOnlineID()
    else
        id = zombie:getUID()
    end
    return id
end
local GetZombieOnlineIDorUID = VLF.GetZombieOnlineIDorUID

--Lấy Online ID hoặc ID từ zombie --Chỉ dùng khi cần ID là số nguyên INT
function VLF.GetZombieID(zombie)
    if not zombie then return end
    local id
    if IsMultiplayerMode() then
        id = zombie:getOnlineID()
    else
        id = zombie:getID()
    end
    return id
end

--Lấy Zombie từ ID zombie
function VLF.GetZombieByOnlineIDOrUID(zID)
    if not zID then return nil end
    local zombies = getCell():getZombieList()
    for i = 0, zombies:size()-1 do
        local zombie = zombies:get(i)
        if zombie then
            --local gameMode = getWorld():getGameMode()
            if IsMultiplayerMode() then
                if zombie:getOnlineID() == zID then return zombie end
            else
                if zombie:getUID() == zID then return zombie end
            end
        end
    end
    return nil
end

--Client Only --set zombie về mặc định, không còn là NPC -dùng khi xóa npc
function VLF.SetZombieDefault(zombie)
    if not zombie then return end
    local zData = zombie:getModData()
    zData.isNPCQuest = false
    --zData.qStep = nil

    zombie:setNoTeeth(false)
    zombie:setInvulnerable(false)
    zombie:setCanWalk(true)
    zombie:setUseless(false)
    if zombie:isFemale() then
        zombie:getDescriptor():setVoicePrefix("FemaleZombie")
    else
        zombie:getDescriptor():setVoicePrefix("MaleZombie")
    end
    
    local zID = GetZombieOnlineIDorUID(zombie)
    local questMD = GetQuestGlobalModData({qStep = nil})
    local qStep = questMD.NPCqStep[zID]
    if qStep then
        zombie:setVariable(VLF.npcData[qStep].variable, false)
    end
end

--Lấy zombie NPC từ Variable và qStep
function VLF.GetNPCsByVarAndQStep(variable, qStep, args) --VLF.GetNPCsByVarAndQStep(variable, qStep, {_ = nil})
    local zombies = getCell():getZombieList()
    local zNPCList = {}
    
    for i = 0, zombies:size()-1 do
        local zombie = zombies:get(i)
        if zombie then
            if zombie:getVariableBoolean(variable) then
                local zID = GetZombieOnlineIDorUID(zombie)
                local questMD = GetQuestGlobalModData({qStep = qStep})
                local _qStep = questMD.NPCqStep[zID]
                if _qStep and _qStep == qStep then
                    table.insert(zNPCList, zombie)
                end
            end
        end
    end
    
    if #zNPCList > 0 then
        return zNPCList
    else return nil
    end
end

--Lấy tất cả IsoZombie xung quanh 1 square
function VLF.GetZombiesAroundSquare(square, args) --VLF.GetZombiesAroundSquare(square, {radius = 1, sameZ = true})
    local zombies = getCell():getZombieList()
    local zombieList = {}
    local radius, sameZ = args.radius or 1, args.sameZ or false
    for i = 0, zombies:size()-1 do
        local zombie = zombies:get(i)
        if zombie and not zombie:isDead() then
            local zZ, zSq = math.floor(zombie:getZ()), square:getZ()
            if zZ and zSq then
                --nếu cùng tầng z
                if sameZ == true then
                    if zZ == zSq then
                        local dist = Caldistance(zombie, square)
                        if dist <= radius then
                            table.insert(zombieList, zombie)
                        end
                    end
                else
                    --nếu tất cả tầng z
                    local dist = Caldistance(zombie, square)
                    if dist <= radius then
                        table.insert(zombieList, zombie)
                    end
                end
            end
        end
    end
    return zombieList
end

--Thêm NPC vào bảng chờ spawn
function VLF.NPCInQueueSpawnList(args) --VLF.NPCInQueueSpawnList({qStep = "q1s004", active = true})
    local qStep = args.qStep
    local active = args.active
    if not qStep then return end
    
    local player = getPlayer()
    sendClientCommand(player, 'NPCQuestCM', 'ActiveNPCInQueueSpawnCM', {qStep = qStep, active = active})
    --print("Client-----------client send command ActiveNPCInQueueSpawnCM= ", qStep, active)
end

--Lấy tất cả npcID đang spawned
function VLF.GetAllnpcIDSpawning()
    local npcIDTab = {}
    local questMD = GetQuestGlobalModData({qStep = nil})
    for qStep, data in pairs(questMD.NPC) do
        if data["spawned"] == true then
            table.insert(npcIDTab, data["zID"])
        end
    end
    return npcIDTab
end
----------------------------------------------------------------------------------   

----------------------------------ITEM và SQUARE-----------------------------------
--Thêm item vào container
function VLF.AddItemToContainer(container, itemINV, itemType, number) --VLF.AddItemToContainer(getPlayer():getInventory(), itemINV, "Base.Candle", 30)
    if not container then return end
    if not itemINV and not itemType then return end
    
    local itemList = {}
    local num = number or 1
    for i = 1, num do
        local item = itemINV or instanceItem(itemType)
        if item then
            container:AddItem(item)
            sendAddItemToContainer(container, item)
            table.insert(itemList, item)
        end
    end
    return itemList
end
local AddItemToContainer = VLF.AddItemToContainer

--Xóa item khỏi container
function VLF.RemoveItemFromContainer(container, itemINV, itemType, number) --VLF.RemoveItemFromContainer(getPlayer():getInventory(), itemINV, "Base.Candle", 20)
    if not container then return end
    if not itemINV and not itemType then return end
    local num = number or 1
    for i = 1, num do    
        if itemINV then --itemINV là item có vị trí cụ thể trong container, chỉ xóa 1 lần duy nhất
            container:Remove(itemINV)
            sendRemoveItemFromContainer(container, item)
            break
        elseif itemType then 
            local itemInINV = container:getFirstType(itemType)
            if itemInINV then
                container:Remove(itemInINV)
                sendRemoveItemFromContainer(container, item)
            else
                break
            end
        end
    end
end

--Lấy Global ModData của Item
function VLF.GetItemModData(item)
    local iData = item:getModData()
    if not iData.isQuestItem then iData.isQuestItem = {} end
    return iData
end
local GetItemModData = VLF.GetItemModData

--Kiểm tra square có sàn không, nếu không thì kiểm tra tiếp các square xung quanh --cùng tầng
local function GetSquareHasFloorInArea(square, args)
    local x, y, z = square:getX(), square:getY(), square:getZ()
    if not x or not y or not z then return end
    local range = args.range or 1
    --kiểm tra square này có floor hay không
    if square:TreatAsSolidFloor() then return square end
    --nếu không, kiểm tra square xung quanh
    for i = -range, range do
        for j = -range, range do
            local newX, newY = x + i, y + j
            local newSq = getCell():getGridSquare(newX, newY, z)
            if newSq and newSq:TreatAsSolidFloor() then return newSq end
        end
    end
    --print("Shared-----------VLF.GetSquareHasFloorInArea: no square") 
    return nil
end

--Add item vào square, nếu có bàn, ghế, giường,... thì cho item đặt trên chúng --chạy trên Client thì item add sẽ không đồng bộ?
function VLF.AddItemToSquares(square, itemFullType, args) --VLF.AddItemToSquares(square, itemFullType, {qStep = qStep, range = range})
    --if not square and not instanceItem(itemFullType) then return nil end
    local sq = GetSquareHasFloorInArea(square, args)
    local qStep = args.qStep
    
    if sq and qStep then
        local objs = sq:getObjects()
        for i = 0, objs:size() - 1 do
            local obj = objs:get(i)
            if obj then
                local tileName = obj:getTileName() or ""
                --add item lên trên các listFurnitureCanPlace nếu có
                local listFurnitureCanPlace = {"Desk", "Chair", "Couch", "Table", "Armchair", "Bed"} --"Drawers", "Cabinet", "Counter", "Dryer", 
                for _, v in ipairs(listFurnitureCanPlace) do
                    if string.find(tileName, v) then
                        local item = obj:addItemToObjectSurface(itemFullType)
                        if item then
                            return item
                        end
                    end
                end            
            end
        end
        --add item ở mặt đất nếu không có listFurnitureCanPlace
        local item = instanceItem(itemFullType)
        if item then
            sq:AddWorldInventoryItem(item, 0.5, 0.5, 0.0)
            return item
        end
    end
    
    return nil
end
local AddItemToSquares = VLF.AddItemToSquares

--Set quest ModData cho Item
local function SetQuestModDataOnItem(item, args) --SetQuestModDataOnItem(item, {qStep = qStep, iModData = "isKeyQuest1"})
    local iData = GetItemModData(item)
    iData.isQuestItem[args.qStep] = true
    --print("Shared-----------SetQuestModDataOnItem:")

    local iModData = args.iModData
    if iModData then
        if not iData[iModData] then iData[iModData] = true end
    end
    return item
end

--Kiểm tra trong khu vực có item giống itemFullType không
function VLF.HasItemOnSquares(square, itemFullType, args) --VLF.HasItemOnSquares(square, itemFullType, {range = range})
    --if not square and not instanceItem(itemFullType) then return nil end
    local x, y, z = square:getX(), square:getY(), square:getZ()
    if not x or not y or not z then return nil end
    local range = args.range or 0
    local itemList = {}
    for i = -range, range do
        for j = -range, range do
            local newX, newY = x + i, y + j
            local newSq = getCell():getGridSquare(newX, newY, z)
            if newSq and newSq:TreatAsSolidFloor() then
                local items = newSq:getWorldObjects()
                for h = 0, items:size() - 1 do
                    local item = items:get(h):getItem()
                    if item and item:getFullType() == itemFullType then
                        table.insert(itemList, item)
                    end
                end
            end
        end
    end
    return itemList
end
local HasItemOnSquares = VLF.HasItemOnSquares

--Kiểm tra item trên các square có ModData isQuestItem không. Nếu có thì trả về item
local function CheckItemsOnSquareIsQuestItem(square, itemFullType, args)
    local itemList = HasItemOnSquares(square, itemFullType, args)
    if itemList then
        for _, item in ipairs(itemList) do
            local iData = GetItemModData(item)
            if iData.isQuestItem[args.qStep] == true then
                --print("Shared-----------CheckItemsOnSquareIsQuestItem: Da co item")
                return item
            end
        end
    end
    return nil
end

local function IsSpecialLanguage(languageName)
    local specLangs = {"CN", "CH", "JP", "KO", "TH"}
    for i, lang in ipairs(specLangs) do
        if languageName == lang then
            return true
        end
    end
    return false
end

--Sửa item nếu là dạng Notepad
local function EditNotepadItem(item, args) --EditNotepadItem(item, {qID = qID, qStep = qStep, noteStep = noteStep, totalPage = totalPage})
    local qID, qStep, noteStep, totalPage = args.qID, args.qStep, args.noteStep, args.totalPage or 1
	item:setName(getText("IGUI_VLQ_"..tostring(qID).."_NoteTitle"..tostring(noteStep).."_"..tostring(qStep)))
	item:setCanBeWrite(true)
    item:setPageToWrite(totalPage)
    
    local lang = getCore():getOptionLanguageName()
    for i = 1, totalPage do
        local text = getText("IGUI_VLQ_"..tostring(qID).."_NotePage"..tostring(noteStep).."_"..tostring(i).."_"..tostring(qStep))
        --Thay thế tất cả <LINE> thành dấu xuống dòng
        text = text:gsub("<LINE>", "\n")
        --Kiểm tra có thuộc ngôn ngữ đặc biệt không --Có ngôn ngữ không có space
        if IsSpecialLanguage(lang) then
            --Chia dòng theo độ rộng
            local lines = SplitTextByLength(text, 300, UIFont.NewSmall)
            local newText = ""
            for i, l in ipairs(lines) do
                newText = newText .. " " .. l
            end
            text = newText
        end
            
        item:addPage(i, text)
    end
	item:setLockedBy("npcQuest")
end

--[[
local function EditNotepadItem(item, args) --EditNotepadItem(item, {qID = qID, qStep = qStep, noteStep = noteStep, totalPage = totalPage})
    local qID, qStep, noteStep, totalPage = args.qID, args.qStep, args.noteStep, args.totalPage or 1
	item:setName(getText("IGUI_VLQ_"..tostring(qID).."_NoteTitle"..tostring(noteStep).."_"..tostring(qStep)))
	item:setCanBeWrite(true)
    item:setPageToWrite(totalPage)
    
    for i = 1, totalPage do
        local text = getText("IGUI_VLQ_"..tostring(qID).."_NotePage"..tostring(noteStep).."_"..tostring(i).."_"..tostring(qStep))
        text = text:gsub("<LINE>", "\n")
        
        item:addPage(i, text)
    end
	item:setLockedBy("npcQuest")
end
]]

--Kiểm tra khu vực spawn item quest đã có item quest chưa, nếu chưa thì spawn
function VLF.DoSpawnItemQuestOnSquare(args) --VLF.DoSpawnItemQuestOnSquare({qStep = qStep, x = 6760, y = 5391, z = 1, itemFullType = "Base.Notepad", range = 1, isNote = true, noteStep = 1, qID = 1}) --thêm , iModData = "isKeyQuest1" nếu là quest Item
    local square = getCell():getGridSquare(args.x, args.y, args.z)
    local itemFullType, qStep = args.itemFullType, args.qStep
    
    if square and instanceItem(itemFullType) then
        local itemQuest = CheckItemsOnSquareIsQuestItem(square, itemFullType, args)
        if itemQuest then
            return itemQuest
        else
            --khi không có item quest trong các square mới spawn để không bị spawn trùng
            --print("Shared-----------VLF.DoSpawnItemQuestOnSquare: not have itemQuest, Trying add item")
            local item = AddItemToSquares(square, itemFullType, args)
            if item then
                SetQuestModDataOnItem(item, args)
				local isNote = args.isNote
				if isNote and isNote == true then
					local noteStep = args.noteStep or 1
                    EditNotepadItem(item, args)
				end
			end
            return item
        end
    end
    return nil
end
local DoSpawnItemQuestOnSquare = VLF.DoSpawnItemQuestOnSquare

--Lấy ItemContainer từ tọa độ x, y, z
function VLF.GetItemContainerFromCoord(args) --VLF.GetItemContainerFromCoord({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 objs = square:getObjects()
		for i = 0, objs:size() - 1 do
			local obj = objs:get(i)
			if obj then
				local iContainer = obj:getItemContainer()
				if iContainer then return iContainer end
			end
		end
	end
	return nil
end
local GetItemContainerFromCoord = VLF.GetItemContainerFromCoord

--Add item vào ItemContainer ở tọa độ x, y, z.
function VLF.AddQuestItemToContainer(iContainer, args) --VLF.AddQuestItemToContainer(iContainer, {qStep = qStep, x = x, y = y, z = z, itemFullType = itemFullType, isNote = true, noteStep = 1, qID = 1})
	local item = instanceItem(args.itemFullType)
    if item then
        local isNote = args.isNote
        if isNote and isNote == true then
            local noteStep = args.noteStep or 1
            EditNotepadItem(item, args)
        end
        --local itemAdded = iContainer:addItem(item)
        local itemAdded = AddItemToContainer(iContainer, item, nil, 1)
        return itemAdded[1]
    end
end
local AddQuestItemToContainer = VLF.AddQuestItemToContainer

--Kiểm tra item trong ItemContainer có ModData isQuestItem không. Nếu có thì trả về item
local function CheckContainerHasQuestItem(iContainer, args)
    local itemList = iContainer:getItems()
    for i = 0, itemList:size() - 1 do
        local item = itemList:get(i)
        if item and item:getFullType() == args.itemFullType then
            local iData = GetItemModData(item)
            if iData.isQuestItem[args.qStep] == true then
                --print("Shared-----------CheckContainerHasQuestItem: Da co item")
                return item
            end
        end
    end
    return nil
end

--Add item vào ItemContainer nếu trong ItemContainer không có quest Item
function VLF.DoSpawnItemQuestInContainer(args) --VLF.DoSpawnItemQuestInContainer({qStep = qStep, x = 5579, y = 12486, z = 0, itemFullType = "Base.Notepad", range = 0, isNote = true, noteStep = 1, qID = 1, addOnSquare = true}) --thêm , iModData = "isKeyQuest1" nếu là quest Item
    local square = getCell():getGridSquare(args.x, args.y, args.z)
    local itemFullType, range, qStep = args.itemFullType, args.range, args.qStep
    if square and square:TreatAsSolidFloor() then
        local iContainer = GetItemContainerFromCoord(args)
        if iContainer then
            --Kiểm tra itemContainer có item quest chưa
            local itemQuest = CheckContainerHasQuestItem(iContainer, args)
            if not itemQuest then
                --Nếu chưa có thì add item
                local item = AddQuestItemToContainer(iContainer, args)
                if item then
                    --print("Shared-----------DoSpawnItemQuestInContainer= ") 
                    SetQuestModDataOnItem(item, args)
                    return item
                end
            end
        end
        --Nếu không có ItemContainer thì add item trên square
        if args.addOnSquare and args.addOnSquare == true then
            local item = DoSpawnItemQuestOnSquare(args)
            if item then 
                --print("Shared-----------DoSpawnItemQuestInContainer: DoSpawnItemQuestOnSquare") 
                return item 
            end
        end            
    end
    return nil
end

--Kiểm tra có item nào trong các itemFullType có moddata isQuestItem không
function VLF.GetItemQuestInINVPlayer(player, itemFullType, args) --VLF.GetItemQuestInINVPlayer(player, itemFullType, {qStep = qStep, iModData = "isKeyQuest1"})
    local itemList = player:getInventory():FindAll(itemFullType)
    if itemList then
        for i = 0, itemList:size()-1 do
            local item = itemList:get(i)
            if item then
                local iData = GetItemModData(item)
                if iData.isQuestItem[args.qStep] == true and iData[args.iModData] == true then
                    return item
                end
            end
        end
    end
    return nil
end

--Client -- Kiểm tra và lấy Square
function VLF.GetSquare(args) --VLF.GetSquare({x = x, y = y, z = z, hasFloor = true, hasIContainer = false, isFree = false, hasCorpse = false})
    local cell = getCell()
    local x, y, z = args.x, args.y, args.z
    local square = cell:getGridSquare(x, y, z)
    if square then
        local hasFloor, hasIContainer, isFree, hasCorpse = args.hasFloor or false, args.hasIContainer or false, args.isFree or false, args.hasCorpse or false
        if hasIContainer and hasIContainer == true then
            local iContainer = GetItemContainerFromCoord({x = x, y = y, z = z})
            if iContainer then return square end
        end
        if isFree and isFree == true then
            if square:TreatAsSolidFloor() and square:isFree(true) then return square end
        end
        if hasCorpse and hasCorpse == true then
            if square:TreatAsSolidFloor() then
                if IsCorpseOnSquare(square) then return square end
            end
        end                
        if hasFloor and hasFloor == true then
            if square:TreatAsSolidFloor() then return square end
        end
        return square
    end
    return nil
end

function VLF.GetOrCreateSquare(args) --VLF.GetOrCreateSquare({x = x, y = y, z = z})
    local cell = getCell()
    local x, y, z = args.x, args.y, args.z
    local square = cell:getGridSquare(x, y, z)
    if square == nil and getWorld():isValidSquare(x, y, z) then
        square = cell:createNewGridSquare(x, y, z, true)
    end
    return square
end
local GetOrCreateSquare = VLF.GetOrCreateSquare

--Server only? Gia cố - Barricade
function VLF.AddBarricade(object, args) --VLF.AddBarricade(object, {barItem = "Base.Plank", addOpposite = false})
    local barItem = args.barricadeItem or "Base.Plank"
    local addOpposite = args.addOpposite or false
    local barricade = IsoBarricade.AddBarricadeToObject(object, addOpposite)
    if barricade then
        if barItem == "Base.SheetMetal" then
            local item = instanceItem(barItem)
            item:setCondition(100)
            barricade:addMetal(nil, item)
            barricade:transmitCompleteItemToClients()
        elseif barItem == "Base.MetalBar" then
            local item = instanceItem(barItem)
            item:setCondition(100)
            barricade:addMetalBar(nil, item)
            barricade:transmitCompleteItemToClients()
        elseif barItem == "Base.Plank" then
            local item = instanceItem(barItem)
            item:setCondition(100)
            barricade:addPlank(nil, item)
            if barricade:getNumPlanks() == 1 then
                barricade:transmitCompleteItemToClients()
            else
                barricade:sendObjectChange('state')
            end
        end
    end
end

--Kiểm tra square có object đó hay không
function VLF.GetObjectOnSquare(square, args) --GetObjectOnSquare(square, {spriteName = spriteName, tileName = tileName, objName = objName})
    local objs = square:getObjects()
    for i = 0, objs:size() - 1 do
        local obj = objs:get(i)
        if obj then
            if args.spriteName then
                if obj:getSpriteName() == args.spriteName then
                    return obj              
                end
            end
            if args.tileName then
                if obj:getTileName() == args.tileName then
                    return obj              
                end
            end  
            if args.objName then
                if obj:getObjectName() == args.objName then
                    return obj              
                end
            end  
        end
    end
    return nil
end
local GetObjectOnSquare = VLF.GetObjectOnSquare

--Xóa object
function VLF.RemoveObjectOnSquare(square, spriteName, args) --VLF.RemoveObjectOnSquare(square, spriteName, {isTransmit = true, x = -1, y = -1, z = 0, removeAll = true})
    local sq = square or getCell():getGridSquare(args.x, args.y, args.z)
    if not sq then return nil end
    local isTransmit, removeAll = args.isTransmit or false, args.removeAll or false
    local objs = sq:getObjects()
    local objTab = {}
    for i = 0, objs:size() - 1 do
        local obj = objs:get(i)
        if obj then
            table.insert(objTab, obj)
        end
    end
    for _, obj in ipairs(objTab) do
        if removeAll == true then
            sq:RemoveTileObject(obj)
        else
            if spriteName and obj:getSpriteName() == spriteName then
                sq:RemoveTileObject(obj)
            end
        end
        if isTransmit == true then
            if isServer() then
                sq:transmitRemoveItemFromSquareOnClients(obj)
            else
                sq:transmitRemoveItemFromSquare(obj)
            end
        end   
    end
end

--Thêm các loại object lên square
function VLF.AddObjectToSquare(square, spriteName, args) --VLF.AddObjectToSquare(square, "location_business_office_generic_01_32", {isTransmit = true, needSqIsFree = true, needFloor = true, x = -1, y = -1, z = 0, objType = "IsoObject", isNorth = false, isLocked = false})
    local sq = square or GetOrCreateSquare({x = args.x, y = args.y, z = args.z})
    --print("Client----AddObjectToSquare = ", sq:getX(), sq:getY(), sq:getZ())
    if not sq or not instanceof(sq, "IsoGridSquare") then return nil end
    if args.needSqIsFree and args.needSqIsFree == true then
		if not sq:isFree(true) then return nil end
	end
    if args.needFloor and args.needFloor == true then
        if not sq:TreatAsSolidFloor()  then return nil end
    end
    local objType, isNorth, isLocked = args.objType, args.isNorth or false, args.isLocked or false
    local object
    if objType then
        local isTransmit = args.isTransmit or false
        local isObjExists = GetObjectOnSquare(sq, {spriteName = spriteName}) --kiểm tra obj có tồn tại chưa
        if not isObjExists then
            if objType == "IsoObject" then
                object = IsoObject.new(sq, spriteName, "") --(sq, spriteName/tileset sprite ID, name)
                sq:AddSpecialObject(object)
            elseif objType == "IsoThumpable" then
                object = IsoThumpable.new(getCell(), sq, spriteName, isNorth, {}) --(IsoCell cell, IsoGridSquare gridSquare, String sprite, boolean _north, KahluaTable _table)
                sq:AddTileObject(object)
            elseif objType == "IsoDoor" or objType == "IsoWindow" then
                if objType == "IsoDoor" then
                    object = IsoDoor.new(getCell(), sq, getSprite(spriteName), isNorth) --(IsoCell cell, IsoGridSquare gridSquare, IsoSprite gid, boolean _north)
                else
                    object = IsoWindow.new(getCell(), sq, getSprite(spriteName), isNorth) --(IsoCell cell, IsoGridSquare gridSquare, IsoSprite gid, boolean _north)
                end
                object:setIsLocked(isLocked)
                sq:AddSpecialObject(object)
            elseif objType == "IsoCurtain" then    
                object = IsoCurtain.new(getCell(), sq, spriteName, isNorth)
                sq:AddSpecialObject(object)
            elseif objType == "Container" then
                object = IsoThumpable.new(getCell(), sq, spriteName, isNorth, {})
                object:setIsContainer(true)
                sq:AddSpecialObject(object)
            elseif objType == "WaterContainer" then
                object = IsoThumpable.new(getCell(), sq, spriteName, isNorth, {})
                object:setWaterAmount(60 + ZombRand(300))
                object:setTaintedWater(true)
                sq:AddSpecialObject(object)
            elseif objType == "Fireplace" then
                object = IsoObject.new(sq, spriteName, "")
                sq:AddSpecialObject(object)
                if isTransmit == true then object:transmitCompleteItemToServer() end
                --fire anim
                object = IsoFire.new(getCell(), sq)
                object:AttachAnim("Fire", "01", IsoFire.NUM_FRAMES_FIRE, IsoFireManager.FireAnimDelay, 1 * Core.getTileScale(), -1 * Core.getTileScale(), true, 0, false, 0.7, IsoFireManager.FireTintMod)
                sq:AddTileObject(object)
            elseif objType == "Fridge" then
                object = IsoObject.new(sq, spriteName, "")
                local sprite = getSprite(spriteName);
                object:createContainersFromSpriteProperties()
                sq:AddSpecialObject(object)
            elseif objType == "IsoLightSwitch" then
                local sprite = getSprite(spriteName)
                local properties = sprite:getProperties()
                properties:set("lightR", "110")
                properties:set("lightG", "110")
                properties:set("lightB", "90")
                properties:set("LightRadius", "20")
                object = IsoLightSwitch.new(getCell(), sq, sprite, sq:getRoomID())
                object:setUseBattery(false)
                object:addLightSourceFromSprite()
                sq:AddSpecialObject(object)
                object:setActive(true)
                object:setActivated(true)                
            end
            if isTransmit == true then object:transmitCompleteItemToServer() end
            return object
        end
    end
    return nil
end
--local AddObjectToSquare = VLF.AddObjectToSquare
------------------------------------------

-------------------------CORPSE---------------------------
--Add xác chết lên square
local function AddCorpse(square, args)
    --Thêm xác ngẫu nhiên outfit, ngẫu nhiên giới tính
    local corpse = square:addCorpse(false)
    --square:addCorpse(true) --bộ xương
    if args.isSplatBlood then
        --square:splatBlood(2, 1) --máu trên tường
    end
    if args.isBloodFloor then
        --square:haveBloodFloor() --máu dưới sàn
    end
    --print("Shared-----------AddCorpse= ", corpse)
    --Add xác có thể chọn giới tính, NHƯNG luôn naked, và zombie có khả năng ảnh hưởng đến NPC    
        --local desc = SurvivorFactory.CreateSurvivor(SurvivorType.Neutral, true) --isFemale
        --desc:dressInNamedOutfit("Police")
        --local body = IsoDeadBody.new(zombie) --luôn naked
        ------------------------
        --local zombie = createZombie(x, y, z, nil, 0, IsoDirections.S)
        --local body = IsoDeadBody.new(zombie, false)
    return corpse
end

--Add xác chết có tùy chỉnh
function VLF.AddCorpseCustomize(args) --VLF.AddCorpseCustomize({x = x, y = y, z = z, outfitName = "Police", femaleChance = 100, number = 1})
    local x, y, z = args.x, args.y, args.z
    local square = getCell():getGridSquare(x, y, z)
    --print("Shared-----------VLF.AddCorpseCustomize= ")
    if square and square:TreatAsSolidFloor() then
        local outfitName, femaleChance, number = args.outfitName or "", args.femaleChance or 50, args.number or 1
		local zombies = addZombiesInOutfit(x, y, z, number, outfitName, femaleChance)
		for i = 0, zombies:size() - 1 do
			local zombie = zombies:get(i)
			zombie:setAlwaysKnockedDown(true)
			zombie:Kill(nil)
		end
    end
end

--Add 1 xác chết trên các square isFree
function VLF.AddCorpseInArea(args) --VLF.AddCorpseInArea({x = x, y = y, z = z, isSplatBlood = false, isBloodFloor = true, range = 1})
    local x, y, z = args.x, args.y, args.z
    local square = getCell():getGridSquare(x, y, z)
    --print("Shared-----------Trying---VLF.AddCorpseInArea= ")
    if square and square:TreatAsSolidFloor() and square:isFree(true) and IsCorpseOnSquare(square) == false then
        local corpse = AddCorpse(square, args)
        return corpse
    end
    
    local range = args.range or 0
    --nếu không có square trên, kiểm tra square xung quanh
    for i = -range, range do
        for j = -range, range do
            local newX, newY = x + i, y + j
            local newSq = getCell():getGridSquare(newX, newY, z)
            if newSq and newSq:TreatAsSolidFloor() and newSq:isFree(true) and IsCorpseOnSquare(newSq) == false then
                local corpse = AddCorpse(newSq, args)
                return corpse
            end
        end
    end
    return nil
end

--------------------------------------------------------
--Client only --Kiểm tra player đang ở trong khu vực xác định hay không
function VLF.IsPlayerInArea(player, args) --VLF.IsPlayerInArea(player, {x1 = 3333, y1 = 3333, x2 = 3350, y2 = 3350, z = 0})
	if not player then return nil end
	local x1, y1, x2, y2, z = args.x1, args.y1, args.x2, args.y2, args.z
	if not x1 or not y1 or not x2 or not y2 then return nil end
	local pX, pY, pZ = player:getX(), player:getY(), player:getZ()
	if z then --Nếu có tọa độ z thì check phải cùng tầng z
		if (pX >= x1 and pX <= x2) and (pY >= y1 and pY <= y2) and pZ == z then
			return true
		end
	else
		if (pX >= x1 and pX <= x2) and (pY >= y1 and pY <= y2) then
			return true
		end
	end
	return false
end

--------------------------FOOD----------------------------
--Duyệt toàn bộ container để lấy food
function VLF.GetFoodInContainer(container, foodList)
    local items = container:getItems()
    if not items then return end
	
    for i = 0, items:size() - 1 do
        local item = items:get(i)
        -- Nếu là thức ăn thì add vào list
        if item and item:isFood() then
            table.insert(foodList, item)
        end
        -- Nếu là container thì duyệt bên trong
        if item and item:IsInventoryContainer() then
            local inner = item:getInventory()
            if inner then
                VLF.GetFoodInContainer(inner, foodList)
            end
        end
    end
end

-------------------------WATER----------------------------
--Duyệt toàn bộ container để lấy water
function VLF.GetWaterInContainer(container, waterSourceList)
    local items = container:getItems()
    if not items then return end
	
    for i = 0, items:size() - 1 do
        local item = items:get(i)
        -- Nếu là đồ chứa nước thì add vào list
        if item and item:isWaterSource() then
            table.insert(waterSourceList, item)
        end
        -- Nếu là container thì duyệt bên trong
        if item and item:IsInventoryContainer() then
            local inner = item:getInventory()
            if inner then
                VLF.GetWaterInContainer(inner, waterSourceList)
            end
        end
    end
end

--Duyệt toàn bộ container để lấy tất cả item có cùng itemType cụ thể
function VLF.GetItemInContainer(container, itemList, itemType)
    local items = container:getItems()
    if not items then return end
	
    for i = 0, items:size() - 1 do
        local item = items:get(i)
        if item and item:getFullType() == itemType then
            table.insert(itemList, item)
        end
        -- Nếu là container thì duyệt bên trong
        if item and item:IsInventoryContainer() then
            local inner = item:getInventory()
            if inner then
                VLF.GetItemInContainer(inner, itemList, itemType)
            end
        end
    end
end

--Duyệt toàn bộ container để lấy tất cả item có Alcohol > 0
function VLF.GetAlcoholsInContainer(container, itemList)
    local items = container:getItems()
    if not items then return end
	
    for i = 0, items:size() - 1 do
        local item = items:get(i)
        if item and item:getFluidContainer() and item:getFluidContainer():getProperties() and item:getFluidContainer():getProperties():getAlcohol() > 0 then
            table.insert(itemList, item)
        end
        -- Nếu là container thì duyệt bên trong
        if item and item:IsInventoryContainer() then
            local inner = item:getInventory()
            if inner then
                VLF.GetItemInContainer(inner, itemList, itemType)
            end
        end
    end
end

--Chọn 1 square làm điểm check modData cố định cho building
function VLF.GetStaticCoordOfBuilding(buildingDef)
    --square giữa building
	local x = math.floor(((buildingDef:getX() + buildingDef:getX2()) / 2) + 0.5)
	local y = math.floor(((buildingDef:getY() + buildingDef:getY2()) / 2) + 0.5)
	local z = 0
	return x, y, z
end

--Lấy IsoRoom phòng bên ngoài bedroom: livingroom, hall, kitchen
function VLF.GetOutsideRoom(building)
    return building:getRandomRoom("livingroom") or building:getRandomRoom("hall") or building:getRandomRoom("kitchen")
end

--Lấy local ModData của Player
function VLF.GetPlayerModData(args) --VLF.GetPlayerModData({qStep = qStep})
	local pData = getPlayer():getModData()
	if not pData.itemQuest then pData.itemQuest = {} end
	if not pData.time then pData.time = {} end
    if not pData.events then pData.events = {} end
	
	local qStep = args.qStep
	if qStep then
		if not pData.itemQuest[qStep] then pData.itemQuest[qStep] = {} end
		if not pData.time[qStep] then pData.time[qStep] = {} end
		--if not pData.events[qStep] then pData.events[qStep] = {} end
	end
	return pData
end

--Client only --Add máy phát điện
function VLF.AddGenerator(args) --VLF.AddGenerator({genType = "Base.Generator_Yellow", x = x, y = y, z = z})
    local cell = getCell()
    local square = cell:getGridSquare(args.x, args.y, args.z)
    if square then
        --local hasGen = GetObjectOnSquare(square, {objName = "IsoGenerator"})
        --if not hasGen then
            local genType = args.genType or "Base.Generator"
            local gItem = instanceItem(genType)
            if gItem then
                local object = IsoGenerator.new(gItem, cell, square)
                object:setConnected(true)
                object:setFuel(ZombRand(80, 100))
                object:setCondition(99)
                object:setActivated(true)
                object:transmitCompleteItemToServer()
            end
        --end
    end
end




        
