=============== LIBRARY RULES =============== From library maintainers: - Answer only from the published Murphy's Job Board documentation, in the user's language. Cite a relevant clickable Markdown source link under https://docs.murphy-workshop.com/murphy-s-job-board/. - This is a public RedM plugin. Do not substitute FiveM, another Murphy plugin, or undocumented framework APIs. Do not mention or speculate about unpublished products. - Never invent commands, exports, event names, configuration keys, default values or compatibility. If the documentation does not establish an answer, say so and link to the closest relevant reference. - Ask for the installed framework and plugin version when they affect behavior. A public release note establishes that release, not a guarantee about files actually installed on the user's server. - Keep answers concise and focused. Give only the requested code. Preserve existing configuration entries and label excerpts. Do not use emojis or long dashes. Remove display line numbers from copied code. - Distinguish client and server context, online server player IDs and persistent character identifiers. Do not interchange commands, events and exports without explicit documentation. - Do not promise data preservation, backups, refunds or offline behavior unless the relevant documentation establishes it. Do not recommend deleting data or blindly replacing a full core file. - Store screenshots show one demonstration setup. Do not infer fixed rewards, version, framework, controls or guarantees from a picture. Do not claim that an in-game test was performed. - Never claim access to purchases, accounts, licenses or server actions. For account or order questions only, link to https://murphy-workshop.com/portal or https://murphy-workshop.com/support. - Published reference: Non, la capture et le bouton ne l'établissent pas. Vérifier le comportement configuré sur le serveur. - Published reference: Ne pas inventer cet export. La page précise que l'ancien SelectJob était du pseudocode, pas une API établie. Consulter le guide de création et les callbacks documentés. ### Complete Custom Action Example Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide A full implementation of a fishing-spot action, including objective display, GPS routing, interaction setup, and the required abandon watcher. ```lua actions["FishAtSpot"] = function(job) local data = job.steps[job.currentStep] local coords = data.coords local duration = data.duration or 10000 TriggerEvent("murphy_jobpanel:ShowObjective", data.description, 4000) local blip = CreateBlip(coords, data.label, 'blip_objective', `BLIP_STYLE_OBJECTIVE`) table.insert(blips, blip) -- GPS route to the location StartGpsMultiRoute(GetHashKey("COLOR_OBJECTIVE"), true, true) Citizen.InvokeNative(0x64C59DD6834FA942, coords) Citizen.InvokeNative(0x4426D65E029A4DC0, true) exports.murphy_interact:AddInteraction({ coords = vector3(coords.x, coords.y, coords.z + 1.5), distance = 8.0, interactDst = 3.0, id = job.name .. "_step" .. job.currentStep, options = { { label = "Start fishing", action = function(entity, crds, args) local ped = PlayerPedId() TaskStartScenarioInPlace(ped, "WORLD_HUMAN_RIVER_FISHING", -1, true) Citizen.Wait(duration) ClearPedTasks(ped) ClearGpsMultiRoute() exports.murphy_interact:RemoveInteraction( job.name .. "_step" .. job.currentStep ) job:nextStep() end, }, } }) -- Abandon watcher Citizen.CreateThread(function() local step = job.currentStep while true do Wait(1000) if step ~= job.currentStep or job.abandon == true then RemoveBlip(blip) if job.abandon == true then exports.murphy_interact:RemoveInteraction(job.name .. "_step" .. step) ClearGpsMultiRoute() Utils.StopJob() end break end end end) end ``` -------------------------------- ### Add Currency Server Callback Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Example of using the AddCurrency server callback within a CompleteJob action to reward the player. ```lua -- In a CompleteJob action: Callback.triggerServer('AddCurrency', function() -- Currency added, continue end, job.steps[job.currentStep].reward) ``` -------------------------------- ### Check Inventory Server Callback Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Example of checking item quantity and conditionally removing items from the player's inventory using server callbacks. ```lua Callback.triggerServer('GetItemAmount', function(amount) if amount >= 5 then Callback.triggerServer('RemoveItem', function(success) if success then job:nextStep() end end, "water", 5, {}) else TriggerEvent("murphy_jobpanel:ShowObjective", "You need 5 water!", 4000) end end, "water") ``` -------------------------------- ### Custom Translation File Structure Source: https://docs.murphy-workshop.com/murphy-s-job-board/reference/translation Example of a translation file structure where string values are mapped to specific UI keys. ```lua Translation = { jobboard_title = "Доска вакансий", apply_button = "Подать заявку", no_jobs = "Нет доступных вакансий" } ``` -------------------------------- ### Mining Job Implementation Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide A complete example of a mining job, including job data definition, localization, and action handlers for navigating to the mine and mining ore. ```lua local JobData = { name = filename(), label = "Mining", description = "Extract ore from the Annesburg mines", steps = { { action = "GoToMine" }, { action = "MineOre" }, { action = "MineOre" }, { action = "MineOre" }, { action = "DeliverOre", reward = 75, label = "Deliver", description = "Deliver the ore" }, }, board = { "vanhorn" }, image = "./img/Poster_art/camp.png", reward = "$75", } if IsDuplicityVersion() then JobsConfig[filename()] = JobData else local lang = { ["Go to mine"] = "Go to the mine entrance", ["Mine ore"] = "Mine ore", ["Deliver"] = "Deliver the ore", ["Complete"] = "Job complete!", } local mineEntrance = vector3(2933.0, 1280.0, 44.0) local oreSpots = { { coords = vector3(2940.0, 1285.0, 44.0), description = "Mine the first vein" }, { coords = vector3(2945.0, 1290.0, 44.0), description = "Mine the second vein" }, { coords = vector3(2950.0, 1295.0, 44.0), description = "Mine the third vein" }, } local deliveryPoint = vector3(2984.0, 561.65, 43.45) -- Van Horn local oreCount = 0 local blips = {} local Utils = {} local actions = {} actions["GoToMine"] = function(job) local blip = CreateBlip(mineEntrance, lang["Go to mine"], 'blip_objective', `BLIP_STYLE_OBJECTIVE`) table.insert(blips, blip) TriggerEvent("murphy_jobpanel:ShowObjective", lang["Go to mine"], 4000) StartGpsMultiRoute(GetHashKey("COLOR_OBJECTIVE"), true, true) Citizen.InvokeNative(0x64C59DD6834FA942, mineEntrance) Citizen.InvokeNative(0x4426D65E029A4DC0, true) Citizen.CreateThread(function() while true do Wait(500) if #(GetEntityCoords(PlayerPedId()) - mineEntrance) < 5.0 then ClearGpsMultiRoute() RemoveBlip(blip) job:nextStep() break end end end) Citizen.CreateThread(function() local step = job.currentStep while true do Wait(1000) if step ~= job.currentStep or job.abandon == true then RemoveBlip(blip) if job.abandon == true then ClearGpsMultiRoute() Utils.StopJob() end break end end end) end actions["MineOre"] = function(job) local data = job.steps[job.currentStep] local coords = data.coords TriggerEvent("murphy_jobpanel:ShowObjective", data.description, 4000) local blip = CreateBlip(coords, lang["Mine ore"], 'blip_objective', `BLIP_STYLE_OBJECTIVE`) table.insert(blips, blip) exports.murphy_interact:AddInteraction({ coords = vector3(coords.x, coords.y, coords.z + 1.0), distance = 8.0, interactDst = 2.0, id = job.name .. "_step" .. job.currentStep, options = { { label = lang["Mine ore"], action = function(entity, crds, args) local ped = PlayerPedId() local startPos = GetEntityCoords(ped) -- Play pickaxe animation LoadAnim("amb_work@world_human_pickaxe@male_a@idle_a") TaskPlayAnim(ped, ``` -------------------------------- ### Define Task Job Animation Formats Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-beginner-guide Examples of defining animation formats for Task Jobs, supporting either built-in scenarios or custom animation dictionaries. ```lua -- Scenario (built-in game animation with props) anim = "WORLD_HUMAN_BROOM" -- Custom animation (dict + anim name) anim = { dict = "amb_work@world_human_clean_window@male_a@wip_base", anim = "wip_base" } ``` -------------------------------- ### Select Job Types in Lua Source: https://docs.murphy-workshop.com/murphy-s-job-board/features/job-types Example of defining job objects for hunting and delivery types in Lua. Note that available jobs may vary based on server configuration and active missions. ```lua -- Selecting a job type local huntingJob = { name = "Forest Deer Hunt", type = "hunting", difficulty = "medium", reward = 50 } local deliveryJob = { name = "Supply Wagon Delivery", type = "delivery", distance = "long", reward = 75 } ``` -------------------------------- ### Configure QBCore Framework Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/framework-support Set the framework to 'qb' and enable QBCore integration. ```lua -- Recommended QB setup Config.Framework = 'qb' Config.UseQBCore = true ``` -------------------------------- ### Configure RedEM Framework Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/framework-support Set the framework to 'redem' and enable RedEM integration. ```lua -- Recommended RedEM setup Config.Framework = 'redem' Config.UseRedEM = true ``` -------------------------------- ### Configure VORP Framework Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/framework-support Set the framework to 'vorp' and enable VORP integration. ```lua -- Recommended VORP setup Config.Framework = 'vorp' Config.UseVORP = true ``` -------------------------------- ### Configure Framework in config.lua Source: https://docs.murphy-workshop.com/murphy-s-job-board/getting-started/configuration Select the server's core framework by modifying the Config.framework setting. Supported options include rsg-core, qbr-core, REDEMRP2k23, redemrp2022, and vorp. ```lua Config.framework = "rsg-core" -- Supported options: -- qbr-core -- REDEMRP2k23 -- redemrp2022 -- rsg-core -- vorp ``` -------------------------------- ### Configure RSG Framework Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/framework-support Set the framework to 'rsg' and enable RSG integration. ```lua -- Recommended RSG setup Config.Framework = 'rsg' Config.UseRSG = true ``` -------------------------------- ### Set Framework Configuration Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-beginner-guide Update Config.framework in config/config.lua to match your server framework. ```lua Config.framework = "vorp" -- Options: vorp, rsg-core, REDEMRP2k23, redemrp2022, qbr-core ``` -------------------------------- ### Add resources to server.cfg Source: https://docs.murphy-workshop.com/murphy-s-job-board/getting-started/installation Add these lines to your server.cfg file to ensure the required dependencies and the job board resource are loaded. ```cfg ensure mysql-async ensure oxmysql ensure ox_lib ensure murphy_jobpanel ``` -------------------------------- ### Define Job Configuration in config/jobs/my_first_job.lua Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-beginner-guide This template defines the job metadata, steps, and interaction logic. It handles both server-side registration and client-side execution of job actions like animations and completion. ```lua local JobData = { name = filename(), label = "Street Sweeper", description = "Clean the streets of Armadillo", steps = { { action = "PlayAnimation" }, { action = "PlayAnimation" }, { action = "CompleteJob", reward = 30, label = "Complete", description = "Collect your pay" }, }, board = { "armadillo" }, image = "./img/Poster_art/train2.png", reward = "$30", } if IsDuplicityVersion() then JobsConfig[filename()] = JobData else local lang = { ["NextStep"] = "Next step", ["Finish"] = "Job complete!", } local orderedLocations = { { name = "Sweep the porch", description = "Sweep in front of the general store", coords = vector3(-3626.562, -2605.512, -14.346), anim = "WORLD_HUMAN_BROOM", duration = 8000 }, { name = "Clean the window", description = "Wipe the bank window clean", coords = vector3(-3670.741, -2622.23, -14.752), anim = { dict = "amb_work@world_human_clean_window@male_a@wip_base", anim = "wip_base" }, duration = 8000 }, } local blips = {} local Utils = {} local actions = {} actions["PlayAnimation"] = function(job) local data = job.steps[job.currentStep] local coords = data.coords local label = data.label local description = data.description local anim = data.anim local duration = data.duration TriggerEvent("murphy_jobpanel:ShowObjective", description, 4000) local blip = CreateBlip(coords, label, 'blip_objective', `BLIP_STYLE_OBJECTIVE`) table.insert(blips, blip) exports.murphy_interact:AddInteraction({ coords = vector3(coords.x, coords.y, coords.z + 1.5), distance = 8.0, interactDst = 2.5, id = job.name .. "_step" .. job.currentStep, options = { { label = label, action = function(entity, coords, args) local playerPed = PlayerPedId() local startCoords = GetEntityCoords(playerPed) if type(anim) == "string" then TaskStartScenarioInPlace(playerPed, anim, -1, true) elseif type(anim) == "table" then LoadAnim(anim.dict) TaskPlayAnim(playerPed, anim.dict, anim.anim, 8.0, 8.0, -1, 25, 0, 0, 0, 0) end Citizen.Wait(duration) if #(GetEntityCoords(playerPed) - startCoords) > 10.0 then ClearPedTasks(playerPed) TriggerEvent("murphy_jobpanel:ShowObjective", "You moved too far, try again.", 4000) else ClearPedTasks(playerPed) exports.murphy_interact:RemoveInteraction(job.name .. "_step" .. job.currentStep) RemoveBlip(blip) job:nextStep() end end, }, } }) end actions["CompleteJob"] = function(job) TriggerEvent("murphy_jobpanel:ShowObjective", lang["Finish"], 4000) Callback.triggerServer('AddCurrency', function() end, job.steps[job.currentStep].reward) job:nextStep() Utils.StopJob() end function Utils.StopJob() for _, blip in ipairs(blips) do if blip then RemoveBlip(blip) end end blips = {} end function Utils.createJob(jobData) for i, step in ipairs(jobData.steps) do if step.action == "PlayAnimation" then local location = orderedLocations[i] step.coords = location.coords step.anim = location.anim step.duration = location.duration step.description = location.description step.label = location.name end step.action = actions[step.action] end return Job:new(jobData) end local job = Utils.createJob(JobData) AddEventHandler("onResourceStop", function(resourceName) if GetCurrentResourceName() ~= resourceName then return end Utils.StopJob() end) return job end ``` -------------------------------- ### Configure Global Prompts Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-beginner-guide Edit the config/translate.lua file to update global job panel prompts. ```lua Translate = { ["Job Panel: View Jobs"] = "View Jobs", ["Already have a job"] = "You already have a job", ["Abandon"] = "You have abandoned the job", } ``` -------------------------------- ### Create Ordered Job Pattern Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Implementation of sequential location assignment for job steps. ```lua function Utils.createJob(jobData) local deliveryIndex = 1 for i, step in ipairs(jobData.steps) do if step.action == "DeliverPackage" then local location = deliveries[deliveryIndex] step.coords = location.Pos step.description = location.Description deliveryIndex = deliveryIndex + 1 end step.action = actions[step.action] end return Job:new(jobData) end ``` -------------------------------- ### Register Client Callback Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/callbacks Registers a callback on the client side using Callback.register(). ```lua Callback.register('ClientSpecificTask', function(data) -- Perform client-side logic return true end) ``` -------------------------------- ### Configure Job Boards in config/config.lua Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-beginner-guide Define job board locations and models within the Config.Panel table. Use a unique key for each board and specify the prop model, world coordinates, and rotation. ```lua Config.Panel = { ["my_board_name"] = { model = "mp005_p_mp_bountyboard01x", -- Board prop model coords = vector3(x, y, z), -- World position rotation = vector3(0.0, 0.0, 90.0) -- Rotation (usually just Z axis) }, } ``` -------------------------------- ### Check player item amount via GetItemAmount Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Uses a server callback to verify if a player possesses a specific quantity of an item. ```lua Callback.triggerServer('GetItemAmount', function(amount) if amount >= requiredAmount then -- Player has enough end end, "item_name") ``` -------------------------------- ### Restart the Resource Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-beginner-guide Command to ensure the job panel resource is active. ```text ensure murphy_jobpanel ``` -------------------------------- ### Implement MineOre and DeliverOre Job Actions Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Defines the logic for mining and delivery steps, including interaction triggers, objective blips, and GPS routing. ```lua "amb_work@world_human_pickaxe@male_a@idle_a", "idle_a", 8.0, 8.0, -1, 25, 0, 0, 0, 0 ) Wait(8000) if #(GetEntityCoords(ped) - startPos) > 5.0 then ClearPedTasks(ped) TriggerEvent("murphy_jobpanel:ShowObjective", "You moved too far!", 4000) else ClearPedTasks(ped) oreCount = oreCount + 1 exports.murphy_interact:RemoveInteraction( job.name .. "_step" .. job.currentStep ) RemoveBlip(blip) job:nextStep() end end, }, } }) Citizen.CreateThread(function() local step = job.currentStep while true do Wait(1000) if step ~= job.currentStep or job.abandon == true then RemoveBlip(blip) if job.abandon == true then exports.murphy_interact:RemoveInteraction( job.name .. "_step" .. step ) Utils.StopJob() end break end end end) end actions["DeliverOre"] = function(job) TriggerEvent("murphy_jobpanel:ShowObjective", lang["Deliver"], 4000) local blip = CreateBlip(deliveryPoint, lang["Deliver"], 'blip_objective', `BLIP_STYLE_OBJECTIVE`) table.insert(blips, blip) StartGpsMultiRoute(GetHashKey("COLOR_OBJECTIVE"), true, true) Citizen.InvokeNative(0x64C59DD6834FA942, deliveryPoint) Citizen.InvokeNative(0x4426D65E029A4DC0, true) exports.murphy_interact:AddInteraction({ coords = vector3(deliveryPoint.x, deliveryPoint.y, deliveryPoint.z + 1.0), distance = 10.0, interactDst = 3.0, id = job.name .. "_step" .. job.currentStep, options = { { label = lang["Complete"], action = function() Callback.triggerServer('AddCurrency', function() exports.murphy_interact:RemoveInteraction( job.name .. "_step" .. job.currentStep ) job:nextStep() Utils.StopJob() end, job.steps[job.currentStep].reward) end, }, } }) Citizen.CreateThread(function() local step = job.currentStep while true do Wait(1000) if step ~= job.currentStep or job.abandon == true then RemoveBlip(blip) if job.abandon == true then exports.murphy_interact:RemoveInteraction( job.name .. "_step" .. step ) ClearGpsMultiRoute() Utils.StopJob() end break end end end) end ``` -------------------------------- ### Register Server Callback Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/callbacks Registers a callback on the server side using Callback.register(). ```lua Callback.register('GetCharIdentifier', function(source) -- Return character identifier for player return MySQL.query('SELECT character_id FROM characters WHERE player_id = ?', {source}) end) ``` -------------------------------- ### Configure Primary Language Source: https://docs.murphy-workshop.com/murphy-s-job-board/reference/translation Modify the configuration file to set the server's primary language using the appropriate language code. ```lua Config.Language = 'en' -- Change 'en' to your preferred language code ``` -------------------------------- ### Trigger Client to Server Callback Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Use Callback.triggerServer to send a request from the client to the server and handle the returned result. ```lua Callback.triggerServer('CallbackName', function(result) -- Handle the result print("Server returned:", result) end, arg1, arg2, ...) ``` -------------------------------- ### Callback.triggerServer('GiveItem') Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Adds a specified quantity of an item to the player's inventory. ```APIDOC ## Callback.triggerServer('GiveItem') ### Description Adds a specific amount of an item to the player's inventory. ### Parameters - **callback** (function) - Required - Function to handle the success boolean. - **item_name** (string) - Required - The identifier of the item to add. - **amount** (number) - Required - The quantity to add. - **metadata** (table) - Required - Metadata associated with the item. ``` -------------------------------- ### Create murphy_jobpanel database table Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide The schema required for tracking job progression, including character identifiers, job names, and current steps. ```sql CREATE TABLE `murphy_jobpanel` ( `id` INT(11) PRIMARY KEY AUTO_INCREMENT, `charid` VARCHAR(200) DEFAULT 0, `job` VARCHAR(2000), `step` VARCHAR(200) ); ``` -------------------------------- ### Callback.triggerServer('GetItemAmount') Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Checks the quantity of a specific item currently held by the player. ```APIDOC ## Callback.triggerServer('GetItemAmount') ### Description Retrieves the amount of a specific item held by the player. ### Parameters - **callback** (function) - Required - Function to handle the returned amount. - **item_name** (string) - Required - The identifier of the item to check. ``` -------------------------------- ### Trigger Client to Server Callback Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/callbacks Triggers a callback from the client to the server using Callback.triggerServer(). ```lua -- Trigger callback to server Callback.triggerServer('ServerEventName', function(result) print(result) -- Handle server response end) ``` -------------------------------- ### Configure aggressive animal relationships Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Sets up a relationship group where animals are hostile toward the player and disables fleeing behaviors. ```lua AddRelationshipGroup("animalstokill") SetRelationshipBetweenGroups(5, GetHashKey("animalstokill"), GetHashKey("PLAYER")) -- 5 = Hate SetRelationshipBetweenGroups(5, GetHashKey("PLAYER"), GetHashKey("animalstokill")) -- Assign to spawned animal: SetPedRelationshipGroupHash(animal, GetHashKey("animalstokill")) SetPedCombatAttributes(animal, 58, true) -- CA_DISABLE_FLEE_FROM_COMBAT -- Prevent fleeing behavior: Citizen.InvokeNative(0x9FF1E042FA597187, animal, 22, 0) -- ATB_FleeMountedPeds = false Citizen.InvokeNative(0x9FF1E042FA597187, animal, 88, 1) -- ATB_WaitInsteadOfFleeForUnreachableVolumes = true ``` -------------------------------- ### Create Random Job Pattern Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Implementation of randomized location assignment with deduplication. Ensure the deliveries table is larger than the number of steps to prevent infinite loops. ```lua function Utils.createJob(jobData) local usedLocations = {} for _, step in ipairs(jobData.steps) do if step.action == "DeliverPackage" then local location repeat location = deliveries[math.random(#deliveries)] until not usedLocations[location] usedLocations[location] = true step.coords = location.Pos step.description = location.Description end step.action = actions[step.action] end return Job:new(jobData) end ``` -------------------------------- ### Give items via GiveItem Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Triggers a server callback to grant a specified amount of an item to the player. ```lua Callback.triggerServer('GiveItem', function(success) if success then -- Item given end end, "item_name", amount, {}) ``` -------------------------------- ### Database State Management Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide SQL queries to inspect active jobs and reset stuck player records in the murphy_jobpanel table. ```sql -- See all active jobs: SELECT * FROM murphy_jobpanel WHERE job IS NOT NULL; -- Clear a stuck player: UPDATE murphy_jobpanel SET job = NULL, step = NULL WHERE charid = 'PLAYER_CHARID'; ``` -------------------------------- ### Define In-Game Job Translations Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-beginner-guide Configure the lang table within individual job files to translate step-specific text. ```lua local lang = { ["Take"] = "Take", ["Drop"] = "Drop", ["Finish"] = "Finish", } ``` -------------------------------- ### Define Job Utilities and Resource Cleanup Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Provides helper functions for stopping jobs, creating job instances, and handling resource termination. ```lua function Utils.StopJob() ClearGpsMultiRoute() for _, blip in ipairs(blips) do if blip then RemoveBlip(blip) end end blips = {} oreCount = 0 end function Utils.createJob(jobData) local oreIndex = 1 for i, step in ipairs(jobData.steps) do if step.action == "MineOre" then local spot = oreSpots[oreIndex] step.coords = spot.coords step.description = spot.description oreIndex = oreIndex + 1 end step.action = actions[step.action] end return Job:new(jobData) end local job = Utils.createJob(JobData) AddEventHandler("onResourceStop", function(resourceName) if GetCurrentResourceName() ~= resourceName then return end Utils.StopJob() end) return job ``` -------------------------------- ### Define Job Board Locations in Config.Panel Source: https://docs.murphy-workshop.com/murphy-s-job-board/getting-started/configuration Add job board locations to the Config.Panel table. Each entry requires a unique identifier, a model, vector3 coordinates, and rotation. ```lua Config.Panel = { ["armadillo"] = { model = "mp005_p_mp_bountyboard01x", coords = vector3(-3727.36, -2598.500244140625, -14.46000003814697), rotation = vector3(0.0,0.0,89) }, -- Add more locations as needed } ``` -------------------------------- ### Check Callback Registration Source: https://docs.murphy-workshop.com/murphy-s-job-board/reference/troubleshooting Verify if a specific callback has been registered by checking the registeredCallback table. ```lua -- Example callback registration check if not registeredCallback[name] then print('No callback registered for:', name) end ``` -------------------------------- ### Trigger Server to Client Callback Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Use Callback.triggerClient to send a request from the server to a specific client and handle the returned result. ```lua Callback.triggerClient('CallbackName', source, function(result) print("Client returned:", result) end, arg1, arg2, ...) ``` -------------------------------- ### Trigger Server to Client Callback Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/callbacks Triggers a callback from the server to a specific client using Callback.triggerClient(). ```lua -- Trigger callback to specific client Callback.triggerClient('EventName', playerSource, function(result) print(result) -- Handle client response end) ``` -------------------------------- ### Job System Architecture Diagram Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Visual representation of the job file structure, showing the split between shared JobData and the separation of server-side configuration and client-side gameplay logic. ```text ┌─────────────────────────────────────────────────────────────┐ │ Job File (config/jobs/xxx.lua) │ │ │ │ ┌─────────────────────┐ ┌─────────────────────────────┐ │ │ │ JobData (shared) │ │ IsDuplicityVersion() split │ │ │ │ name, label, steps │───>│ Server: JobsConfig[name] │ │ │ │ board, image, reward│ │ Client: actions + Job:new() │ │ │ └─────────────────────┘ └─────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │ JobManager │ │ Callbacks │ │ Adapters │ │ (client) │ │ (shared) │ │ (server-side)│ │ │ │ │ │ │ │ Job:new() │ │ triggerSvr │ │ AddCurrency │ │ Job:start()│ │ triggerCli │ │ GiveItem │ │ Job:stop() │ │ register() │ │ RemoveItem │ │ nextStep() │ │ │ │ GetCharJob │ └────────────┘ └────────────┘ └──────────────┘ ``` -------------------------------- ### Utils Pattern Structure Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide The standard organizational pattern for job files, requiring a Utils table with StopJob, createJob, and an onResourceStop handler. ```lua local Utils = {} local actions = {} -- Action definitions actions["StepName"] = function(job) ... end actions["CompleteJob"] = function(job) ... end -- Cleanup function (REQUIRED) function Utils.StopJob() -- Delete all spawned entities -- Remove all blips -- Clear GPS routes -- Reset local state variables end -- Job factory (REQUIRED) function Utils.createJob(jobData) for i, step in ipairs(jobData.steps) do -- Map data from location tables to steps -- Convert string action names to function references step.action = actions[step.action] end return Job:new(jobData) end -- Create and return the job instance local job = Utils.createJob(JobData) -- Resource cleanup handler (REQUIRED) AddEventHandler("onResourceStop", function(resourceName) if GetCurrentResourceName() ~= resourceName then return end Utils.StopJob() end) return job ``` -------------------------------- ### Define Job Board Locations in config.lua Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-beginner-guide The Config.Panel table defines the available physical job boards in the world. Use this configuration to map specific job boards to the board field in your job files. ```lua Config.Panel = { ["armadillo"] = { ... }, ["vanhorn"] = { ... }, ["oil"] = { ... }, } ``` -------------------------------- ### Create Map Blips Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Functions for creating standard, radius, and entity-attached blips on the map. ```lua -- Standard blip at coordinates local blip = CreateBlip(coords, "Label", 'blip_objective', `BLIP_STYLE_OBJECTIVE`) -- Radius blip (circular area on map) local blip = CreateBlipRadius(coords, radius, "Label", 'blip_objective', `BLIP_STYLE_OBJECTIVE`) -- Entity blip (follows a ped/vehicle) local blip = CreateBlipEntity(entity, "Label", `BLIP_STYLE_OBJECTIVE`, nil, `blip_ambient_ped_small`) ``` -------------------------------- ### Callback.triggerServer(name, callback) Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/callbacks Triggers a registered callback on the server from a client. ```APIDOC ## Callback.triggerServer(name, callback) ### Description Sends a request to the server to execute a registered callback. ### Parameters - **name** (string) - The name of the registered callback. - **callback** (function) - The response handler function to execute once the server returns a result. ``` -------------------------------- ### Configure UI Language Text Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-beginner-guide Modify ui/language_config.json to translate UI buttons and headers. ```json [ { "Close_button": "Close", "Accept_button": "Accept" }, { "Header_title": "ACTIVE JOB", "Objectives_title": "Objectives", "Reward_text": "Reward", "Abandon_button": "Abandon" } ] ``` -------------------------------- ### Draw GPS Route Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Commands to initiate a multi-point GPS route on the minimap and clear it when finished. ```lua -- Start route StartGpsMultiRoute(GetHashKey("COLOR_OBJECTIVE"), true, true) Citizen.InvokeNative(0x64C59DD6834FA942, destination) -- Add waypoint Citizen.InvokeNative(0x4426D65E029A4DC0, true) -- Render route -- Clear route ClearGpsMultiRoute() ``` -------------------------------- ### Callback.register(name, callback) Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/callbacks Registers a callback function on either the server or client side to handle incoming requests. ```APIDOC ## Callback.register(name, callback) ### Description Registers a handler function for a specific callback name. This allows the system to execute logic when the callback is triggered. ### Parameters - **name** (string) - The unique identifier for the callback. - **callback** (function) - The function to execute when triggered. Receives data/source as arguments and returns a result. ``` -------------------------------- ### Debug Print Statements Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Use these print statements to track job progress and entity spawning in the console. ```lua print("Step", job.currentStep, "coords:", data.coords) print("Entity spawned:", entity, "at", GetEntityCoords(entity)) ``` -------------------------------- ### Configure passive animal relationships Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Sets up a neutral relationship group for herding, disables specific flee behaviors, and enables movement along the road navmesh. ```lua AddRelationshipGroup("herdanimals") SetRelationshipBetweenGroups(1, GetHashKey("herdanimals"), GetHashKey("PLAYER")) -- 1 = Respect SetRelationshipBetweenGroups(1, GetHashKey("PLAYER"), GetHashKey("herdanimals")) -- Configure cow behavior: SetAnimalTuningBoolParam(cow, 25, false) -- Disable specific flee behavior SetAnimalTuningBoolParam(cow, 24, false) TaskAnimalUnalerted(cow, -1, false, 0, 0) SetPedConfigFlag(cow, 297, true) SetPedConfigFlag(cow, 310, true) SetPedConfigFlag(cow, 40, false) -- Disable mounting and lassoing: Citizen.InvokeNative(0xAE6004120C18DF97, cow, 5, false) Citizen.InvokeNative(0xAE6004120C18DF97, cow, 6, false) -- Move cow toward destination via road navmesh: TaskMoveFollowRoadUsingNavmesh(cow, 2.0, dest.x, dest.y, dest.z, 0) ``` -------------------------------- ### Callback.triggerServer Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Triggers a server-side callback from the client. ```APIDOC ## Callback.triggerServer ### Description Triggers a pre-registered server-side callback from the client. ### Signature `Callback.triggerServer(CallbackName, callbackFunction, ...args)` ### Parameters - **CallbackName** (string) - The name of the registered server callback. - **callbackFunction** (function) - The function to execute once the server returns a result. - **...args** (any) - Variable arguments to pass to the server callback. ``` -------------------------------- ### Guard framework-specific adapter code Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Use this conditional check to isolate framework-specific implementations within the adapter layer. ```lua if Config.framework == "vorp" then -- VORP-specific implementation end ``` -------------------------------- ### Spawn a Vehicle Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Spawns a vehicle using its hash key. Always call SetEntityAsMissionEntity to prevent the game from despawning the vehicle. ```lua local vehicleHash = GetHashKey("chuckwagon002x") RequestModel(vehicleHash) while not HasModelLoaded(vehicleHash) do Wait(1) end local vehicle = CreateVehicle( vehicleHash, coords.x, coords.y, coords.z, heading, -- float: spawn rotation true, true, -- isNetwork, thisScriptCheck false, true -- p6, p7 ) SetEntityAsMissionEntity(vehicle, true, true) ``` -------------------------------- ### Clean Up Blips Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Pattern for tracking active blips in a table and removing them during job termination. ```lua local blips = {} -- When creating: table.insert(blips, blip) -- In StopJob: for _, blip in ipairs(blips) do if blip then RemoveBlip(blip) end end blips = {} ``` -------------------------------- ### Callback.triggerClient(name, playerSource, callback) Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/callbacks Triggers a registered callback on a specific client from the server. ```APIDOC ## Callback.triggerClient(name, playerSource, callback) ### Description Sends a request to a specific client to execute a registered callback. ### Parameters - **name** (string) - The name of the registered callback. - **playerSource** (number) - The ID of the target client. - **callback** (function) - The response handler function to execute once the client returns a result. ``` -------------------------------- ### Callback.triggerClient Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Triggers a client-side callback from the server. ```APIDOC ## Callback.triggerClient ### Description Triggers a client-side callback from the server to a specific client. ### Signature `Callback.triggerClient(CallbackName, source, callbackFunction, ...args)` ### Parameters - **CallbackName** (string) - The name of the registered client callback. - **source** (number) - The player source ID. - **callbackFunction** (function) - The function to execute once the client returns a result. - **...args** (any) - Variable arguments to pass to the client callback. ``` -------------------------------- ### Spawn a Ped or Animal Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Spawns a ped or animal and randomizes its outfit. Free the model from memory after spawning. ```lua local model = GetHashKey("a_c_wolf") RequestModel(model) while not HasModelLoaded(model) do Wait(1) end local ped = CreatePed(model, x, y, z, heading, true, true) SetEntityAsMissionEntity(ped, true, true) SetRandomOutfitVariation(ped, true) -- Randomize appearance SetModelAsNoLongerNeeded(model) -- Free the model from memory ``` -------------------------------- ### Remove items via RemoveItem Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Triggers a server callback to remove a specified amount of an item from the player's inventory. ```lua Callback.triggerServer('RemoveItem', function(success) if success then -- Item removed successfully end end, "item_name", amount, {}) -- Third arg is metadata (empty table if none) ``` -------------------------------- ### Add Static World Interaction Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Registers an interaction at specific world coordinates. Use the unique ID provided to remove the interaction when it is no longer needed. ```lua exports.murphy_interact:AddInteraction({ coords = vector3(x, y, z), distance = 8.0, -- Draw distance interactDst = 2.5, -- Interaction distance id = "unique_id", -- Used for removal options = { { label = "Do something", action = function(entity, coords, args) -- Triggered when player interacts end, }, } }) -- Remove when done: exports.murphy_interact:RemoveInteraction("unique_id") ``` -------------------------------- ### Callback.triggerServer('RemoveItem') Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Removes a specified quantity of an item from the player's inventory. ```APIDOC ## Callback.triggerServer('RemoveItem') ### Description Attempts to remove a specific amount of an item from the player's inventory. ### Parameters - **callback** (function) - Required - Function to handle the success boolean. - **item_name** (string) - Required - The identifier of the item to remove. - **amount** (number) - Required - The quantity to remove. - **metadata** (table) - Required - Metadata associated with the item removal. ``` -------------------------------- ### Attach Object to Ped Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Plays a carry animation and attaches an object to a specific bone on a ped. ```lua LoadAnim("mech_carry_box") Citizen.InvokeNative(0xEA47FE3719165B94, ped, "mech_carry_box", "idle", 1.0, 8.0, -1, 31, 0, 0, 0, 0) Citizen.InvokeNative(0x6B9BBD38AB0796DF, packageEntity, ped, GetEntityBoneIndexByName(ped, "SKEL_R_Finger12"), 0.20, -0.028, 0.001, -- position 15.0, 175.0, 0.0, -- rotation true, true, false, true, 1, true ) ``` -------------------------------- ### Debug Character Identifier via Callback Source: https://docs.murphy-workshop.com/murphy-s-job-board/reference/troubleshooting Use this snippet to verify the character identifier by triggering the server callback and printing the result to the console. ```lua -- Potential debugging steps Callback.triggerServer('GetCharIdentifier', function(result) print("Current Character ID:", result) end) ``` -------------------------------- ### Add Local Entity Interaction Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Registers an interaction for a client-side entity. This method allows for specific offsets and line-of-sight overrides. ```lua exports.murphy_interact:AddLocalEntityInteraction({ entity = entity, name = "interactionName", id = entity, -- Can use entity handle as ID distance = 5.0, interactDst = 3.0, offset = vec3(0.0, 0.0, 0.5), ignoreLos = true, options = { ... } }) -- Remove: exports.murphy_interact:RemoveLocalEntityInteraction(entity, entity) ``` -------------------------------- ### Abandon Watcher Pattern Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide A mandatory thread pattern for cleaning up entities and interactions when a player advances a step or abandons a job. ```lua actions["MyAction"] = function(job) -- ... setup logic ... -- REQUIRED: Abandon watcher Citizen.CreateThread(function() local step = job.currentStep -- Capture the step at creation time while true do Wait(1000) -- Triggers when step advances OR job is abandoned if step ~= job.currentStep or job.abandon == true then -- Clean up step-specific resources RemoveBlip(blip) if job.abandon == true then -- Full cleanup on abandon exports.murphy_interact:RemoveInteraction(job.name .. "_step" .. step) ClearGpsMultiRoute() Utils.StopJob() end break end end end) end ``` -------------------------------- ### Action Function Signature Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide The basic structure of a custom action function, which receives the job object and must call job:nextStep() to advance. ```lua actions["MyAction"] = function(job) local data = job.steps[job.currentStep] -- Access step-specific data local stepIndex = job.currentStep -- Current step number (1-based) local jobName = job.name -- Unique job identifier -- ... do work ... job:nextStep() -- Advance to next step when done end ``` -------------------------------- ### Set Animal Wandering Behavior Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Forces an animal to wander within a specified area. ```lua TaskWanderInArea(animal, x, y, z, radius, 0, 0) ``` -------------------------------- ### Attach Object to Vehicle Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Attaches an entity to a vehicle using the AttachEntityToEntity native. ```lua Citizen.InvokeNative(0x6B9BBD38AB0796DF, packageEntity, -- entity to attach vehicle, -- target entity 0, -- bone index offsetX, offsetY, offsetZ, -- position offset rotX, rotY, rotZ, -- rotation offset 0, 0, 0, 0, -- additional params 2, -- attachment type true -- collision ) ``` -------------------------------- ### Set Panel Refresh Interval Source: https://docs.murphy-workshop.com/murphy-s-job-board/getting-started/configuration Control the automatic update frequency of job panels in minutes. Set to false to disable automatic refreshing. ```lua Config.Refreshpanels = 60 -- Refresh every 60 minutes -- Set to false to disable automatic refreshing ``` -------------------------------- ### Job Object Interface Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide The job object provided to custom actions contains state information and control methods for managing job progression. ```APIDOC ## Job Object Interface ### Description The job object is passed to every custom action function, providing access to job state and control methods. ### Properties - **job.name** (string) - Unique job identifier - **job.label** (string) - Display name - **job.steps** (table[]) - All step definitions - **job.currentStep** (number) - Index of the currently executing step (1-based) - **job.completed** (boolean) - True when all steps are done - **job.abandon** (boolean) - True when the player abandoned the job - **job.activejobinfo** (table) - Data sent to the active job UI overlay ### Methods - **job:start(step?)** - Start the job (optionally at a specific step) - **job:stop()** - Abandon the job - **job:nextStep()** - Advance to the next step - **job:executeCurrentStep()** - Run the current step's action ``` -------------------------------- ### Configure Invincible NPCs Source: https://docs.murphy-workshop.com/murphy-s-job-board/advanced/murphy-jobpanel-advanced-guide Prevents an NPC from taking damage or dying. ```lua SetEntityCanBeDamaged(npc, false) SetEntityInvincible(npc, true) ```