--[[ -- Author: Tim Plate -- Project: Advanced Roleplay Environment -- Copyright (c) 2022 Tim Plate Solutions --]] --- Server extension module for Advanced Roleplay Environment. ---Returns true if the player has the item with the specified amount ---@param source any ---@param item any ---@param amount any ---@return boolean function HasItem(source, item, amount) source = tonumber(source) if not ServerConfig.m_itemsNeeded or ShouldIgnoreItems(source) then return true end if not amount then amount = 1 end LogDebug(TableContains(ServerConfig.m_debugModeModules, "items"), "HasItem on player ", GetPlayerName(source), " and item", item) if FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.ES_EXTENDED then local frameworkPlayer = FRAMEWORK_DATA.GetPlayerFromId(source) if not frameworkPlayer then return false end if not ServerConfig.m_customInventory.enabled then -- Default inventory handling return frameworkPlayer.getInventoryItem(item) and frameworkPlayer.getInventoryItem(item).count >= amount end if ServerConfig.m_customInventory.inventory_type == "ox_inventory" then return exports.ox_inventory:Search(source, 'count', item) >= amount elseif ServerConfig.m_customInventory.inventory_type == "qs-inventory" then return exports['qs-inventory']:GetItemTotalAmount(source, item) >= amount elseif ServerConfig.m_customInventory.inventory_type == "mf-inventory" then return frameworkPlayer.getInventoryItem(item) and frameworkPlayer.getInventoryItem(item).count >= amount elseif ServerConfig.m_customInventory.inventory_type == "core_inventory" then local inventory = 'content-' .. frameworkPlayer.identifier:gsub(":", "") return exports["core_inventory"]:getItem(inventory, item) and exports["core_inventory"]:getItem(inventory, item).count >= amount end elseif FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.QB_CORE then local frameworkPlayer = FRAMEWORK_DATA.Functions.GetPlayer(source) if not frameworkPlayer then return false end return frameworkPlayer.Functions.GetItemByName(item).amount >= amount end -- Implement your own logic (standalone) return true end ---Adds an item to the player ---@param source any ---@param item any ---@param amount any ---@return boolean function AddItem(source, item, amount) source = tonumber(source) if not amount then amount = 1 end LogDebug(TableContains(ServerConfig.m_debugModeModules, "items"), "AddItem on player ", GetPlayerName(source), " and item", item) if FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.ES_EXTENDED then local frameworkPlayer = FRAMEWORK_DATA.GetPlayerFromId(source) if not frameworkPlayer then return false end if not ServerConfig.m_customInventory.enabled then -- Default inventory handling if frameworkPlayer.canCarryItem(item, amount) then frameworkPlayer.addInventoryItem(item, amount) return true end return false end if ServerConfig.m_customInventory.inventory_type == "ox_inventory" then if exports.ox_inventory:CanCarryItem(source, item, amount) then exports.ox_inventory:AddItem(source, item, amount) return true end elseif ServerConfig.m_customInventory.inventory_type == "qs-inventory" then if frameworkPlayer.canCarryItem(item, amount) then frameworkPlayer.addInventoryItem(item, amount) return true end elseif ServerConfig.m_customInventory.inventory_type == "mf-inventory" then if frameworkPlayer.canCarryItem(item, amount) then frameworkPlayer.addInventoryItem(item, amount) return true end elseif ServerConfig.m_customInventory.inventory_type == "core_inventory" then return exports["core_inventory"]:addItem('content-' .. frameworkPlayer.identifier:gsub(":", ""), item, amount, nil) end return false elseif FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.QB_CORE then local frameworkPlayer = FRAMEWORK_DATA.Functions.GetPlayer(source) if not frameworkPlayer then return false end return frameworkPlayer.Functions.AddItem(item, amount) end return true -- Implement your own logic (standalone) end ---Removes an item from the player ---@param source any ---@param item any ---@param amount any function RemoveItem(source, item, amount) source = tonumber(source) if not amount then amount = 1 end LogDebug(TableContains(ServerConfig.m_debugModeModules, "items"), "RemoveItem on player ", GetPlayerName(source), " and item", item) if FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.ES_EXTENDED then local frameworkPlayer = FRAMEWORK_DATA.GetPlayerFromId(source) if not frameworkPlayer then return end if not ServerConfig.m_customInventory.enabled then -- Default inventory handling if HasItem(source, item, amount) then frameworkPlayer.removeInventoryItem(item, amount) end return end if ServerConfig.m_customInventory.inventory_type == "ox_inventory" then exports.ox_inventory:RemoveItem(source, item, amount) elseif ServerConfig.m_customInventory.inventory_type == "qs-inventory" then frameworkPlayer.removeInventoryItem(item, amount) elseif ServerConfig.m_customInventory.inventory_type == "mf-inventory" then frameworkPlayer.removeInventoryItem(item, amount) elseif ServerConfig.m_customInventory.inventory_type == "core_inventory" then exports["core_inventory"]:removeItem('content-' .. frameworkPlayer.identifier:gsub(":", ""), item, amount) end elseif FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.QB_CORE then local frameworkPlayer = FRAMEWORK_DATA.Functions.GetPlayer(source) if not frameworkPlayer then return end frameworkPlayer.Functions.RemoveItem(item, amount) end -- Implement your own logic (standalone) end ---Gets all items from the player ---@param source any ---@return table function GetItems(source) source = tonumber(source) if not ServerConfig.m_itemsNeeded or ShouldIgnoreItems(source) then return GetAvailableItems() end if FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.ES_EXTENDED then local frameworkPlayer = FRAMEWORK_DATA.GetPlayerFromId(source) if not frameworkPlayer then return {} end local items = frameworkPlayer.getInventory(true) if ServerConfig.m_customInventory.enabled and ServerConfig.m_customInventory.inventory_type == "core_inventory" then items = exports["core_inventory"]:getInventory('content-' .. frameworkPlayer.identifier:gsub(":", "")) end for _, v in pairs(items) do if type(v) == "table" and v.count > 0 then items[v.name] = v.count end end return items elseif FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.QB_CORE then local frameworkPlayer = FRAMEWORK_DATA.Functions.GetPlayer(source) if not frameworkPlayer then return {} end local items = {} for _, v in pairs(frameworkPlayer.PlayerData.items) do items[v.name] = v.amount end return items end -- Implement your own logic (standalone) return {} end ---Returns true if the player is allowed to perform a action ---@param source any ---@param category any ---@param action any ---@return boolean function IsAllowedToPerformAction(source, category, action) source = tonumber(source) if not AVAILABLE_ACTIONS[category] then return false end local actionData = nil for _, v in pairs(AVAILABLE_ACTIONS[category]) do if v.name == action then actionData = v break end end if not actionData then return false end if not actionData.requiredJobs then return true end return TableContains(actionData.requiredJobs, GetPlayerJob(source)) end ---Returns true if the player is allowed to open the menu ---@param source any ---@return boolean function IsAllowedToOpenMenu(source) source = tonumber(source) if not ServerConfig.m_limitMenuToJobs.enabled then return true end local playerJob = GetPlayerJob(source) if not playerJob then return false end return TableContains(ServerConfig.m_limitMenuToJobs.jobs, playerJob) end ---Saves the player ---@param source any function SavePlayer(source) source = tonumber(source) if not ServerConfig.m_stateSaving.enabled then return end if not ValidateSource(source) then return end local healthBuffer = Player(source).state.healthBuffer if not healthBuffer then return end -- Remove logs and clientside properties healthBuffer.logs = nil healthBuffer.lastHeartRateChange = nil healthBuffer.lastDamageTimestamp = nil healthBuffer.lastReceivedPain = nil healthBuffer.triageSelection = nil healthBuffer.lastDamage = nil healthBuffer._lastHealth = nil healthBuffer.lastHandledDamageTimestamp = nil healthBuffer.unconscious_timestamp = nil healthBuffer.unconsciousTimeUntilDeath = nil local playerIdentifier = GetPlayerCustomIdentifier(source) if not playerIdentifier then return end if ServerConfig.m_stateSaving.method == 'oxmysql' then local databaseConfig = ServerConfig.m_stateSaving.database exports["oxmysql"]:update("UPDATE " .. databaseConfig.table .. " SET " .. databaseConfig.column .. " = ? WHERE " .. databaseConfig.identifierColumn .. " = ?", { json.encode(healthBuffer), playerIdentifier }) elseif ServerConfig.m_stateSaving.method == 'mysql-async' then local databaseConfig = ServerConfig.m_stateSaving.database exports["mysql-async"]:mysql_execute("UPDATE " .. databaseConfig.table .. " SET " .. databaseConfig.column .. " = @value WHERE " .. databaseConfig.identifierColumn .. " = @identifier", { ['value'] = json.encode(healthBuffer), ['identifier'] = playerIdentifier }) elseif ServerConfig.m_stateSaving.method == 'file' then local filePath = ServerConfig.m_stateSaving.file.path local fileEnding local encodedData if ServerConfig.m_stateSaving.file.type == 'message_pack' then encodedData = msgpack.pack(healthBuffer) fileEnding = ".bin" elseif ServerConfig.m_stateSaving.file.type == 'json' then encodedData = json.encode(healthBuffer) fileEnding = ".json" else LogError("Invalid file type for state saving") return end if not fileEnding or not encodedData then return end local fileName = (filePath .. playerIdentifier .. fileEnding):gsub("%:", "_") local file, err = io.open(GetResourcePath(GetCurrentResourceName()) .. fileName,'wb') if err then LogError("Player state saving (" .. playerIdentifier .. ") failed: " .. err) return end if file then file:write(encodedData) file:close() end else LogWarning("Unknown state saving method: " .. ServerConfig.m_stateSaving.method) return end end ---Loads the player ---@param source any function LoadPlayer(source) source = tonumber(source) if not ServerConfig.m_stateSaving.enabled then return end if not ValidateSource(source) then return end local playerIdentifier = GetPlayerCustomIdentifier(source) or "invalid_identifier" if not playerIdentifier then return end if ServerConfig.m_stateSaving.method == 'oxmysql' then local databaseConfig = ServerConfig.m_stateSaving.database exports["oxmysql"]:query("SELECT " .. databaseConfig.column .. " FROM " .. databaseConfig.table .. " WHERE " .. databaseConfig.identifierColumn .. " = ?", { playerIdentifier }, function(result) if not result or not result[1] or not result[1][databaseConfig.column] then return end local healthBuffer = json.decode(result[1][databaseConfig.column]) if not healthBuffer then return end LogDebug(true, "Loaded health buffer for " .. playerIdentifier .. " (sql) | unconscious-state: ", healthBuffer.unconscious) healthBuffer.unconscious = healthBuffer.unconscious == true healthBuffer.medications = {} Player(source).state:set("healthBuffer", healthBuffer, true) TriggerClientEvent(ENUM_EVENT_TYPES.EVENT_SET_HEALTH_BUFFER, source, AuthToken, healthBuffer) end) elseif ServerConfig.m_stateSaving.method == 'mysql-async' then local databaseConfig = ServerConfig.m_stateSaving.database exports["mysql-async"]:mysql_fetch_all("SELECT " .. databaseConfig.column .. " FROM " .. databaseConfig.table .. " WHERE " .. databaseConfig.identifierColumn .. "=@identifier", { ['identifier'] = playerIdentifier }, function(result) if not result or not result[1] or not result[1][databaseConfig.column] then return end local healthBuffer = json.decode(result[1][databaseConfig.column]) if not healthBuffer then return end LogDebug(true, "Loaded health buffer for " .. playerIdentifier .. " (sql) | unconscious-state: ", healthBuffer.unconscious) healthBuffer.unconscious = healthBuffer.unconscious == true healthBuffer.medications = {} Player(source).state:set("healthBuffer", healthBuffer, true) TriggerClientEvent(ENUM_EVENT_TYPES.EVENT_SET_HEALTH_BUFFER, source, AuthToken, healthBuffer) end) elseif ServerConfig.m_stateSaving.method == 'file' then local filePath = ServerConfig.m_stateSaving.file.path local healthBuffer local fileName = (filePath .. playerIdentifier):gsub("%:", "_") if ServerConfig.m_stateSaving.file.type == 'message_pack' then local data = LoadResourceFile(GetCurrentResourceName(), fileName .. ".bin") if not data then return end healthBuffer = msgpack.unpack(data) elseif ServerConfig.m_stateSaving.file.type == 'json' then local data = LoadResourceFile(GetCurrentResourceName(), fileName .. ".json") if not data then return end healthBuffer = json.decode(data) else LogError("Invalid file type for state saving") return end if not healthBuffer then return end LogDebug(true, "Loaded health buffer for " .. playerIdentifier .. " (" .. fileName .. ") | unconscious-state: ", healthBuffer.unconscious or false) healthBuffer.unconscious = (healthBuffer.unconscious or false) == true healthBuffer.medications = {} Player(source).state:set("healthBuffer", healthBuffer, true) TriggerClientEvent(ENUM_EVENT_TYPES.EVENT_SET_HEALTH_BUFFER, source, AuthToken, healthBuffer) else LogWarning("Unknown state saving method: " .. ServerConfig.m_stateSaving.method) return end end ---Gets the menu title name from the player ---@param source any ---@return unknown function GetPlayerMenuTitleName(source) source = tonumber(source) if FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.ES_EXTENDED then return FRAMEWORK_DATA.GetPlayerFromId(source).getName() elseif FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.QB_CORE then return FRAMEWORK_DATA.Functions.GetPlayer(source).PlayerData.charinfo.firstname .. ' ' .. FRAMEWORK_DATA.Functions.GetPlayer(source).PlayerData.charinfo.lastname end -- Implement your own logic (standalone) return GetPlayerName(source) end ---Gets the player job ---@param source any ---@return unknown function GetPlayerJob(source) source = tonumber(source) if FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.ES_EXTENDED then local frameworkPlayer = FRAMEWORK_DATA.GetPlayerFromId(source) if not frameworkPlayer then LogWarning("Tried to get player job but player doesnt have a framework player?", source) return "" end return frameworkPlayer.getJob().name elseif FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.QB_CORE then local frameworkPlayer = FRAMEWORK_DATA.Functions.GetPlayer(source) if not frameworkPlayer then LogWarning("Tried to get player job but player doesnt have a framework player?", source) return "" end return frameworkPlayer.PlayerData.job.name end -- Implement your own logic (standalone) return "" end ---Gets the player identifier (framework related) ---@param source any ---@return any function GetPlayerCustomIdentifier(source) source = tonumber(source) if FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.ES_EXTENDED then local frameworkPlayer = FRAMEWORK_DATA.GetPlayerFromId(source) if not frameworkPlayer then LogWarning("Tried to get player identifier but player doesnt have a framework player?" , source) return end return frameworkPlayer.getIdentifier() elseif FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.QB_CORE then local frameworkPlayer = FRAMEWORK_DATA.Functions.GetPlayer(source) if not frameworkPlayer then LogWarning("Tried to get player identifier but player doesnt have a framework player?" , source) return end return frameworkPlayer.PlayerData.citizenid end return GetRealIdentifier(source) end ---Returns true if the player should return item amounts ---@param source any ---@return boolean function ShouldIgnoreItems(source) source = tonumber(source) if not ServerConfig.m_itemsNeeded then return true end return TableContains(ServerConfig.m_ignoreItemsNeededJobs, GetPlayerJob(source)) end ---Gets the medics on duty ---@return integer function GetMedicsOnDutyCount() local medicsOnDuty = 0 if FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.ES_EXTENDED then local xPlayers = ESX.GetExtendedPlayers() for _, v in pairs(xPlayers) do if TableContains(ServerConfig.m_dependUnconsciousTimeOnMedicCount.jobs, v.job.name) then medicsOnDuty = medicsOnDuty + 1 end end elseif FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.QB_CORE then for _, player in pairs(FRAMEWORK_DATA.Functions.GetPlayers()) do if TableContains(ServerConfig.m_dependUnconsciousTimeOnMedicCount.jobs, player.PlayerData.job.name) then medicsOnDuty = medicsOnDuty + 1 end end else -- Implement your own logic (standalone) end return medicsOnDuty end ---Returns true if the player is allowed to use the triage system ---@param source any ---@return boolean function IsAllowedToUseTriageSystem(source) source = tonumber(source) if not ServerConfig.m_triageSystem.enabled then return false end if not ServerConfig.m_triageSystem.jobRestriction then return true end local job = GetPlayerJob(source) return TableContains(ServerConfig.m_triageSystem.jobs, job) end ---Returns true if the player is allowed to use the revive command ---@param source any function IsAllowedToUseReviveCommand(source) source = tonumber(source) if not ServerConfig.m_reviveCommand then return false end if FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.ES_EXTENDED then local frameworkPlayer = FRAMEWORK_DATA.GetPlayerFromId(source) if not frameworkPlayer then return false end return frameworkPlayer.getGroup() == "admin" or frameworkPlayer.getGroup() == "superadmin" elseif FRAMEWORK == ENUM_DEFINED_FRAMEWORKS.QB_CORE then return FRAMEWORK_DATA.Functions.HasPermission(source, 'admin') end -- Your own logic (standalone) return IsPlayerAceAllowed(source, "command.revive") end ---Gets all available items configured in the actions.lua ---@return table function GetAvailableItems() local items = {} for _, v in pairs(AVAILABLE_ACTIONS) do for _, v2 in pairs(v) do if v2.requiredItem then items[v2.requiredItem.name] = v2.requiredItem end end end return items end ---Sends a discord log to the discord api ---@param message any ---@param description any function SendDiscordLog(message, description) if not ServerConfig.m_discordLogging.enabled then return end if not ServerConfig.m_discordLogging.webhook or ServerConfig.m_discordLogging.webhook == "" then return end if not message or message == "" then return end local discordEmbed = { ["title"] = message, ["description"] = description or "", ["type"] = "rich", ["color"] = "16754688", ["footer"] = { ["text"] = "Advanced Roleplay Environment (" .. CURRENT_BUILD .. ")", }, ["timestamp"] = os.date("!%Y-%m-%dT%H:%M:%SZ"), } PerformHttpRequest(ServerConfig.m_discordLogging.webhook, function(err, text, headers) end, 'POST', json.encode({ "Advanced Roleplay Environment", embeds = { discordEmbed }}), { ['Content-Type'] = 'application/json' }) end