diff --git a/resources/[inventory]/nordi_petbowl/client.lua b/resources/[inventory]/nordi_petbowl/client.lua index f8fd9f2a6..76ed863dc 100644 --- a/resources/[inventory]/nordi_petbowl/client.lua +++ b/resources/[inventory]/nordi_petbowl/client.lua @@ -1,72 +1,107 @@ local QBCore = exports['qb-core']:GetCoreObject() local placedBowls = {} -local currentBowl = nil -- Load placed bowls from server RegisterNetEvent('pet-bowls:client:loadBowls', function(bowls) - for _, bowl in pairs(bowls) do + 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) - -- Add target to existing bowl objects - local bowlObjects = GetGamePool('CObject') - for _, object in pairs(bowlObjects) do - local objectCoords = GetEntityCoords(object) - local distance = #(objectCoords - bowlCoords) - - if distance < 0.5 and GetEntityModel(object) == GetHashKey(bowl.model) then - -- Found the bowl object - placedBowls[bowl.bowl_id] = { - object = object, - id = bowl.bowl_id, - model = bowl.model, - type = bowl.type, - fillLevel = bowl.fill_level - } + -- 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) - -- Add target - AddTargetToBowl(object, bowl.bowl_id, bowl.type) - break + 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", - action = function() - UseBowl(bowlId, bowlType) - end, - canInteract = function() - return true - end, + bowlId = bowlId, + bowlType = bowlType }, { type = "client", + event = "pet-bowls:client:openFillMenu", icon = "fas fa-fill", label = "Fill Bowl", - action = function() - OpenFillMenu(bowlId, bowlType) - end, - canInteract = function() - return true - end, + 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 return end + 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 @@ -128,99 +163,155 @@ RegisterNetEvent('pet-bowls:client:updateBowlLevel', function(bowlId, newLevel) end end) --- Register a new bowl (called from ProPlacer) -RegisterNetEvent('pet-bowls:client:registerBowl', function(objectNetId, bowlType) - local bowlObject = NetworkGetEntityFromNetworkId(objectNetId) - if not DoesEntityExist(bowlObject) then return 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 model = GetEntityModel(bowlObject) - local modelName = nil - - -- Find the model name from the hash - for _, bowlConfig in pairs(Config.BowlProps) do - if GetHashKey(bowlConfig.model) == model then - modelName = bowlConfig.model - break - end - end - - if not modelName then return end - - -- Generate a unique ID for this bowl - local bowlId = 'bowl_' .. math.random(100000, 999999) .. '_' .. GetGameTimer() - - -- Save to server - local coords = GetEntityCoords(bowlObject) - local heading = GetEntityHeading(bowlObject) - - TriggerServerEvent('pet-bowls:server:placeBowl', bowlId, modelName, bowlType, { - x = coords.x, - y = coords.y, - z = coords.z, - w = heading - }) - - -- Store locally - placedBowls[bowlId] = { - object = bowlObject, - id = bowlId, - model = modelName, - type = bowlType, - fillLevel = 0 - } - - -- Add target - AddTargetToBowl(bowlObject, bowlId, bowlType) - - lib.notify(Config.Notifications.bowlPlaced) -end) - --- Initialize -Citizen.CreateThread(function() - -- Request all placed bowls from server - TriggerServerEvent('pet-bowls:server:requestBowls') - - -- Add target to all valid bowl props in the world - Citizen.Wait(2000) -- Wait for world to load - - local validModels = {} - for _, bowlConfig in pairs(Config.BowlProps) do - validModels[GetHashKey(bowlConfig.model)] = bowlConfig.type - end + 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 - local model = GetEntityModel(object) - if validModels[model] then - -- This is a valid bowl model, check if it's already registered - local isRegistered = false - for id, bowl in pairs(placedBowls) do - if bowl.object == object then - isRegistered = true - break - end - end + if DoesEntityExist(object) and GetEntityModel(object) == validModel then + local objectCoords = GetEntityCoords(object) + local distance = #(objectCoords - playerCoords) - if not isRegistered then - -- This bowl isn't registered yet, add interaction without database entry - exports['qb-target']:AddTargetEntity(object, { - options = { - { - type = "client", - icon = "fas fa-plus", - label = "Register as Bowl", - action = function() - local bowlType = validModels[model] - TriggerEvent('pet-bowls:client:registerBowl', NetworkGetNetworkIdFromEntity(object), bowlType) - end, - canInteract = function() - return true - end, - } - }, - distance = 2.0 - }) + 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) diff --git a/resources/[inventory]/nordi_petbowl/server.lua b/resources/[inventory]/nordi_petbowl/server.lua index cb3d17537..c8ec8fa7d 100644 --- a/resources/[inventory]/nordi_petbowl/server.lua +++ b/resources/[inventory]/nordi_petbowl/server.lua @@ -16,7 +16,7 @@ function LoadBowls() id = bowl.bowl_id, model = bowl.model, type = bowl.type, - fillLevel = bowl.fill_level, + fill_level = bowl.fill_level, coords = bowl.coords } end @@ -51,12 +51,13 @@ RegisterNetEvent('pet-bowls:server:placeBowl', function(bowlId, model, bowlType, id = bowlId, model = model, type = bowlType, - fillLevel = 0, + fill_level = 0, coords = coordsJson } -- Notify all clients about the new bowl TriggerClientEvent('pet-bowls:client:updateBowlLevel', -1, bowlId, 0) + print('^2[Pet-Bowls]^7 New bowl registered: ' .. bowlId) end end ) @@ -84,10 +85,10 @@ RegisterNetEvent('pet-bowls:server:fillBowl', function(bowlId, itemName, fillAmo -- Update bowl fill level local bowl = bowls[bowlId] if bowl then - local newLevel = math.min(100, bowl.fillLevel + fillAmount) + local newLevel = math.min(100, bowl.fill_level + fillAmount) -- Update in memory - bowl.fillLevel = newLevel + bowl.fill_level = newLevel -- Update in database MySQL.update('UPDATE pet_bowls SET fill_level = ?, last_refill = CURRENT_TIMESTAMP WHERE bowl_id = ?', @@ -97,6 +98,8 @@ RegisterNetEvent('pet-bowls:server:fillBowl', function(bowlId, itemName, fillAmo -- Update all clients TriggerClientEvent('pet-bowls:client:updateBowlLevel', -1, bowlId, newLevel) TriggerClientEvent('ox_lib:notify', src, Config.Notifications.bowlFilled) + + print('^2[Pet-Bowls]^7 Bowl ' .. bowlId .. ' filled to ' .. newLevel .. '%') end end) @@ -123,17 +126,17 @@ RegisterNetEvent('pet-bowls:server:consumeBowl', function(bowlId) if not bowlConfig then return end -- Check if bowl has content - if bowl.fillLevel <= 0 then + if bowl.fill_level <= 0 then TriggerClientEvent('ox_lib:notify', src, Config.Notifications.bowlEmpty) return end -- Consume from bowl local consumeAmount = bowlConfig.consumeAmount - local newLevel = math.max(0, bowl.fillLevel - consumeAmount) + local newLevel = math.max(0, bowl.fill_level - consumeAmount) -- Update in memory - bowl.fillLevel = newLevel + bowl.fill_level = newLevel -- Update in database MySQL.update('UPDATE pet_bowls SET fill_level = ? WHERE bowl_id = ?', @@ -162,6 +165,8 @@ RegisterNetEvent('pet-bowls:server:consumeBowl', function(bowlId) -- Update all clients TriggerClientEvent('pet-bowls:client:updateBowlLevel', -1, bowlId, newLevel) TriggerClientEvent('ox_lib:notify', src, Config.Notifications.consumed) + + print('^2[Pet-Bowls]^7 Bowl ' .. bowlId .. ' consumed, new level: ' .. newLevel .. '%') end) -- Remove a bowl from database (if needed) @@ -179,4 +184,11 @@ RegisterNetEvent('pet-bowls:server:removeBowl', function(bowlId) -- Notify all clients TriggerClientEvent('pet-bowls:client:bowlRemoved', -1, bowlId) + + print('^2[Pet-Bowls]^7 Bowl removed: ' .. bowlId) +end) + +-- Register command to register bowls +QBCore.Commands.Add('registerbowl', 'Register a nearby bowl prop', {}, false, function(source, args) + TriggerClientEvent('pet-bowls:client:openRegisterBowlMenu', source) end)