local QBCore = exports['qb-core']:GetCoreObject() local placedBowls = {} -- Load placed bowls from server RegisterNetEvent('pet-bowls:client:loadBowls', function(bowls) print("^2[Pet-Bowls]^7 Loading " .. #bowls .. " bowls from server") for bowlId, bowl in pairs(bowls) do local coords = json.decode(bowl.coords) local bowlCoords = vector3(coords.x, coords.y, coords.z) -- Find the closest object to these coordinates local closestObject = nil local closestDistance = 2.0 -- Maximum distance to consider local objects = GetGamePool('CObject') for _, object in pairs(objects) do if DoesEntityExist(object) then local objectCoords = GetEntityCoords(object) local distance = #(objectCoords - bowlCoords) if distance < closestDistance then local model = GetEntityModel(object) -- Check if this is a valid bowl model for _, bowlConfig in pairs(Config.BowlProps) do if GetHashKey(bowlConfig.model) == model then closestObject = object closestDistance = distance break end end end end end if closestObject then -- Store in local table placedBowls[bowlId] = { object = closestObject, id = bowlId, model = bowl.model, type = bowl.type, fillLevel = bowl.fill_level } -- Add target AddTargetToBowl(closestObject, bowlId, bowl.type) print("^2[Pet-Bowls]^7 Added target to bowl: " .. bowlId) else print("^3[Pet-Bowls]^7 Could not find object for bowl: " .. bowlId) end end end) -- Function to add qb-target to a bowl function AddTargetToBowl(bowlObject, bowlId, bowlType) if not DoesEntityExist(bowlObject) then print("^1[Pet-Bowls]^7 Cannot add target to non-existent object") return end -- Debug info print("^2[Pet-Bowls]^7 Adding target to object: " .. bowlObject .. " with ID: " .. bowlId) -- Add target with explicit options exports['qb-target']:AddTargetEntity(bowlObject, { options = { { type = "client", event = "pet-bowls:client:useBowl", icon = "fas fa-hand", label = "Use Bowl", bowlId = bowlId, bowlType = bowlType }, { type = "client", event = "pet-bowls:client:openFillMenu", icon = "fas fa-fill", label = "Fill Bowl", bowlId = bowlId, bowlType = bowlType } }, distance = 2.0 }) end -- Event handlers for target interactions RegisterNetEvent('pet-bowls:client:useBowl', function(data) UseBowl(data.bowlId, data.bowlType) end) RegisterNetEvent('pet-bowls:client:openFillMenu', function(data) OpenFillMenu(data.bowlId, data.bowlType) end) -- Function to use a bowl function UseBowl(bowlId, bowlType) local bowl = placedBowls[bowlId] if not bowl then print("^1[Pet-Bowls]^7 Bowl not found: " .. bowlId) return end -- Check if bowl has content if bowl.fillLevel <= 0 then lib.notify(Config.Notifications.bowlEmpty) return end -- Set animation and progress bar based on bowl type local progressConfig = bowlType == 'food' and Config.ProgressBar.eating or Config.ProgressBar.drinking -- Start progress bar if lib.progressBar(progressConfig) then -- Consume from bowl TriggerServerEvent('pet-bowls:server:consumeBowl', bowlId) end end -- Function to open fill menu function OpenFillMenu(bowlId, bowlType) local bowl = placedBowls[bowlId] if not bowl then return end -- Get fill items for this bowl type local fillItems = Config.FillItems[bowlType] local options = {} for _, item in pairs(fillItems) do table.insert(options, { title = item.label, description = 'Fill Amount: ' .. item.fillAmount .. '%', onSelect = function() FillBowl(bowlId, item.item, item.fillAmount) end }) end lib.registerContext({ id = 'bowl_fill_menu', title = 'Fill ' .. (bowlType == 'food' and 'Food' or 'Water') .. ' Bowl (' .. bowl.fillLevel .. '%)', options = options }) lib.showContext('bowl_fill_menu') end -- Function to fill a bowl function FillBowl(bowlId, itemName, fillAmount) -- Start progress bar if lib.progressBar(Config.ProgressBar.filling) then -- Fill the bowl TriggerServerEvent('pet-bowls:server:fillBowl', bowlId, itemName, fillAmount) end end -- Update bowl fill level RegisterNetEvent('pet-bowls:client:updateBowlLevel', function(bowlId, newLevel) if placedBowls[bowlId] then placedBowls[bowlId].fillLevel = newLevel end end) -- Register a new bowl (for props placed with ProPlacer) RegisterNetEvent('pet-bowls:client:registerNewBowl', function(modelName, bowlType) -- Find the closest valid prop local playerPed = PlayerPedId() local playerCoords = GetEntityCoords(playerPed) local closestObject = nil local closestDistance = 3.0 -- Maximum distance to consider local validModel = GetHashKey(modelName) local objects = GetGamePool('CObject') for _, object in pairs(objects) do if DoesEntityExist(object) and GetEntityModel(object) == validModel then local objectCoords = GetEntityCoords(object) local distance = #(objectCoords - playerCoords) if distance < closestDistance then closestObject = object closestDistance = distance end end end if closestObject then -- Generate a unique ID for this bowl local bowlId = 'bowl_' .. math.random(100000, 999999) .. '_' .. GetGameTimer() -- Save to server local coords = GetEntityCoords(closestObject) local heading = GetEntityHeading(closestObject) TriggerServerEvent('pet-bowls:server:placeBowl', bowlId, modelName, bowlType, { x = coords.x, y = coords.y, z = coords.z, w = heading }) -- Store locally placedBowls[bowlId] = { object = closestObject, id = bowlId, model = modelName, type = bowlType, fillLevel = 0 } -- Add target AddTargetToBowl(closestObject, bowlId, bowlType) lib.notify(Config.Notifications.bowlPlaced) else lib.notify({ title = 'Bowl System', description = 'No valid bowl prop found nearby!', type = 'error' }) end end) -- Command to register a bowl RegisterCommand('registerbowl', function() OpenRegisterBowlMenu() end, false) -- Function to open register bowl menu function OpenRegisterBowlMenu() local options = {} for _, bowl in pairs(Config.BowlProps) do table.insert(options, { title = bowl.label, description = 'Type: ' .. (bowl.type == 'food' and 'Food Bowl' or 'Water Bowl'), onSelect = function() TriggerEvent('pet-bowls:client:registerNewBowl', bowl.model, bowl.type) end }) end lib.registerContext({ id = 'register_bowl_menu', title = 'Register Bowl', options = options }) lib.showContext('register_bowl_menu') end -- Initialize Citizen.CreateThread(function() -- Wait for world to load Citizen.Wait(2000) -- Request all placed bowls from server TriggerServerEvent('pet-bowls:server:requestBowls') -- Add target to all valid bowl props in the world that aren't registered yet Citizen.Wait(5000) -- Wait for bowls to load local validModels = {} for _, bowlConfig in pairs(Config.BowlProps) do validModels[GetHashKey(bowlConfig.model)] = bowlConfig end local objects = GetGamePool('CObject') local addedTargets = 0 for _, object in pairs(objects) do if DoesEntityExist(object) then local model = GetEntityModel(object) if validModels[model] then -- Check if this object is already registered as a bowl local isRegistered = false for _, bowl in pairs(placedBowls) do if bowl.object == object then isRegistered = true break end end if not isRegistered then -- Add a register option to this unregistered bowl prop local bowlConfig = validModels[model] exports['qb-target']:AddTargetEntity(object, { options = { { type = "client", event = "pet-bowls:client:registerNewBowl", icon = "fas fa-plus", label = "Register as " .. bowlConfig.label, modelName = bowlConfig.model, bowlType = bowlConfig.type } }, distance = 2.0 }) addedTargets = addedTargets + 1 end end end end print("^2[Pet-Bowls]^7 Added registration targets to " .. addedTargets .. " unregistered bowl props") end) -- Event handler for registering from target RegisterNetEvent('pet-bowls:client:registerNewBowl', function(data) TriggerEvent('pet-bowls:client:registerNewBowl', data.modelName, data.bowlType) end)