From a1872781a2f31b18a7d33626e7426a56161f686e Mon Sep 17 00:00:00 2001 From: Ianis Catalin Date: Thu, 29 May 2025 11:53:29 +0300 Subject: [PATCH 1/2] Faction Group --- vrp/cfg/groups.lua | 110 +++++---- vrp/modules/group.lua | 532 +++++++++++++++++++++++++++++++++--------- 2 files changed, 486 insertions(+), 156 deletions(-) diff --git a/vrp/cfg/groups.lua b/vrp/cfg/groups.lua index ead475f..1840b89 100644 --- a/vrp/cfg/groups.lua +++ b/vrp/cfg/groups.lua @@ -1,4 +1,3 @@ - local cfg = {} -- define each group with a set of permissions @@ -43,7 +42,7 @@ cfg.groups = { "player.group.remove", "player.givemoney", "player.giveitem", - "player.giveweapon", + "player.giveweapon", "profiler.server", "profiler.client" }, @@ -62,8 +61,8 @@ cfg.groups = { "player.custom_sound", "player.display_custom", "player.coords", - "player.revive", - "player.spectate", + "player.revive", + "player.spectate", "player.tptome", "player.tpto" }, @@ -80,7 +79,56 @@ cfg.groups = { ["police"] = { _config = { title = "Police", - gtype = "job", + gtype = "faction", + paycheck_interval = 3, --(minutes) Apply to whole faction + grades = { + [1] = { + name = "Recruit", + payment = 50, + permissions = { + "police.recruit.menu", + "police.recruit.callbackup" + } + }, + [2] = { + name = "Officer", + payment = 75, + permissions = { + "police.officer.menu", + "police.officer.handcuff", + "police.officer.putinvehicle" + } + }, + [3] = { + name = "Sergeant", + payment = 100, + permissions = { + "police.sergeant.menu", + "police.sergeant.seizeweapons", + "police.sergeant.drag" + } + }, + [4] = { + name = "Lieutenant", + payment = 125, + Co_Lider = true, + permissions = { + "police.lieutenant.menu", + "police.lieutenant.manage", + "police.lieutenant.checkbank" + } + }, + [5] = { + name = "Chief", + Lider = true, + payment = 150, + permissions = { + "police.chief.menu", + "police.chief.promote", + "police.chief.managebudget" + } + } + }, onjoin = police_onjoin, onspawn = police_onspawn, onleave = police_onleave @@ -104,7 +152,6 @@ cfg.groups = { "police.chest_seized", "-player.store_weapons", "-police.seizable" -- negative permission, police can't seize itself, even if another group add the permission --- "mission.paycheck.police" -- basic mission }, ["emergency"] = { _config = { @@ -171,13 +218,14 @@ cfg.selectors = { "repair", "citizen" }, - ["Police job"] = { + ["police"] = { _config = {x = 437.924987792969,y = -987.974182128906, z = 30.6896076202393, map_entity = {"PoI", {blip_id = 351, blip_color = 38, marker_id = 1}}}, - "police", + -- Job group entries can now be a string or a table with group name + config + { group = "police", grade = 1 }, "citizen" }, ["Emergency job"] = { - _config = {x=-498.959716796875,y=-335.715148925781,z=34.5017547607422, map_entity = {"PoI", {blip_id = 351, blip_color = 1, marker_id = 1}}}, + _config = {x = -498.959716796875,y = -335.715148925781,z = 34.5017547607422, map_entity = {"PoI", {blip_id = 351, blip_color = 1, marker_id = 1}}}, "emergency", "citizen" } @@ -186,49 +234,9 @@ cfg.selectors = { -- identity display gtypes -- used to display gtype groups in the identity -- map of gtype => title +--[[ cfg.identity_gtypes = { job = "Job" -} - --- count display - -cfg.count_display_interval = 15 -- seconds - --- toggle display -cfg.display = false - -cfg.count_display_css = [[ -.div_group_count_display{ - position: absolute; - right: 0; - bottom: 0; - display: flex; - flex-direction: row; - padding: 2px; - padding-right: 5px; -} - -.div_group_count_display > div{ - padding-left: 7px; - color: white; - font-weight: bold; - line-height: 22px; -} - -.div_group_count_display > div > img{ - margin-right: 2px; - vertical-align: bottom; -} -]] - --- list of {permission, img_src} -cfg.count_display_permissions = { - {"!group.user", "https://i.imgur.com/tQ2VHAi.png"}, - {"!group.admin", "https://i.imgur.com/cpSYyN0.png"}, - {"!group.police", "https://i.imgur.com/dygLDfC.png"}, - {"!group.emergency", "https://i.imgur.com/K5lXutO.png"}, - {"!group.repair", "https://i.imgur.com/QEjFgzM.png"}, - {"!group.taxi", "https://i.imgur.com/yY4yrZN.png"} -} +} ]] return cfg diff --git a/vrp/modules/group.lua b/vrp/modules/group.lua index 79fc688..ed97ae3 100644 --- a/vrp/modules/group.lua +++ b/vrp/modules/group.lua @@ -1,95 +1,119 @@ -- https://github.com/ImagicTheCat/vRP --- MIT license (see LICENSE or vrp/vRPShared.lua) +-- MIT license (see LICENSE or vRP/vRPShared.lua) if not vRP.modules.group then return end local lang = vRP.lang --- this module define the group/permission system (per character) - --- multiple groups can be set to the same player, but the gtype config option can be used to set some groups as unique - +-- This module defines the group/permission system (per character) +-- Multiple groups can be set to the same player, but the gtype config option can be used to set some groups as unique local Group = class("Group", vRP.Extension) -- SUBCLASS - Group.User = class("User") +-- Check if user has a specific group function Group.User:hasGroup(name) return self.cdata.groups[name] ~= nil end --- return map of groups +-- Return map of groups function Group.User:getGroups() return self.cdata.groups end +-- Get the user's faction grade +function Group.User:getFactionGrade() + return self.cdata.faction_grade +end + +-- Get the user's current faction group +function Group.User:getFactionGroup() + for group, _ in pairs(self.cdata.groups) do + local cfg = vRP.EXT.Group.cfg.groups[group] + if cfg and cfg._config and cfg._config.gtype == "faction" then + return group + end + end + return nil +end + +-- Check if user is on faction duty +function Group.User:isOnFactionDuty() + return self.cdata.faction_duty == 1 +end + +-- Set faction duty +function Group.User:setFactionDuty(state) + local faction = self:getFactionGroup() + if faction then + self.cdata.faction_duty = state and 1 or 0 + if self.cdata.faction_duty == 1 then + vRP.EXT.Base.remote._notify(self.source, "You are now ~g~On Duty~s~.") + else + vRP.EXT.Base.remote._notify(self.source, "You are now ~r~Off Duty~s~.") + end + end +end + function Group.User:addGroup(name) - if not self:hasGroup(name) then - local groups = self:getGroups() - local cfg = vRP.EXT.Group.cfg + if self:hasGroup(name) then return end - local ngroup = cfg.groups[name] - if ngroup then - if ngroup._config and ngroup._config.gtype ~= nil then - -- copy group list to prevent iteration while removing - local _groups = {} - for k,v in pairs(groups) do - _groups[k] = v - end + local groups = self:getGroups() + local cfg = vRP.EXT.Group.cfg + local ngroup = cfg.groups[name] + if not ngroup then return end - for k,v in pairs(_groups) do -- remove all groups with the same gtype - local kgroup = cfg.groups[k] - if kgroup and kgroup._config and ngroup._config and kgroup._config.gtype == ngroup._config.gtype then - self:removeGroup(k) - end - end - end + local gtype = ngroup._config and ngroup._config.gtype or nil - -- add group - groups[name] = true - if ngroup._config and ngroup._config.onjoin then - ngroup._config.onjoin(self) -- call join callback + -- Remove existing group with same gtype + if gtype then + for k in pairs(groups) do + local kgroup = cfg.groups[k] + if kgroup and kgroup._config and kgroup._config.gtype == gtype then + self:removeGroup(k) end + end + end - -- trigger join event - local gtype = nil - if ngroup._config then - gtype = ngroup._config.gtype - end + groups[name] = true - vRP:triggerEvent("playerJoinGroup", self, name, gtype) - end + if ngroup._config and ngroup._config.onjoin then + ngroup._config.onjoin(self) end + + vRP:triggerEvent("playerJoinGroup", self, name, gtype) end function Group.User:removeGroup(name) local groups = self:getGroups() - local cfg = vRP.EXT.Group.cfg local group = cfg.groups[name] + + local gtype = group and group._config and group._config.gtype + if group and group._config and group._config.onleave then - group._config.onleave(self) -- call leave callback + group._config.onleave(self) end - -- trigger leave event - local gtype = nil - if group and group._config then - gtype = group._config.gtype - end + groups[name] = nil - groups[name] = nil -- remove reference + -- Clear faction grade if it's a faction group + if gtype == "faction" then + self.cdata.faction_grade = nil + self.cdata.faction_duty = 0 + end vRP:triggerEvent("playerLeaveGroup", self, name, gtype) end - --- get user group by type --- return group name or nil + +-- Get user group by type +-- Return group name or nil function Group.User:getGroupByType(gtype) local groups = self:getGroups() local cfg = vRP.EXT.Group.cfg - for k,v in pairs(groups) do + for k, v in pairs(groups) do local kgroup = cfg.groups[k] if kgroup then if kgroup._config and kgroup._config.gtype and kgroup._config.gtype == gtype then @@ -99,13 +123,13 @@ function Group.User:getGroupByType(gtype) end end --- check if the user has a specific permission +-- Check if the user has a specific permission function Group.User:hasPermission(perm) - local fchar = string.sub(perm,1,1) + local fchar = string.sub(perm, 1, 1) - if fchar == "!" then -- special function permission - local _perm = string.sub(perm,2,string.len(perm)) - local params = splitString(_perm,".") + if fchar == "!" then -- Special function permission + local _perm = string.sub(perm, 2, string.len(perm)) + local params = splitString(_perm, ".") if #params > 0 then local fperm = vRP.EXT.Group.func_perms[params[1]] if fperm then @@ -114,26 +138,26 @@ function Group.User:hasPermission(perm) return false end end - else -- regular plain permission + else -- Regular plain permission local cfg = vRP.EXT.Group.cfg local groups = self:getGroups() - -- precheck negative permission + -- Precheck negative permission local nperm = "-"..perm for name in pairs(groups) do local group = cfg.groups[name] if group then - for l,w in pairs(group) do -- for each group permission + for l, w in pairs(group) do -- For each group permission if l ~= "_config" and w == nperm then return false end end end end - -- check if the permission exists + -- Check if the permission exists for name in pairs(groups) do local group = cfg.groups[name] if group then - for l,w in pairs(group) do -- for each group permission + for l, w in pairs(group) do -- For each group permission if l ~= "_config" and w == perm then return true end end end @@ -143,25 +167,41 @@ function Group.User:hasPermission(perm) return false end --- check if the user has a specific list of permissions (all of them) +-- Check if the user has a specific list of permissions (all of them) function Group.User:hasPermissions(perms) - for _,perm in pairs(perms) do + for _, perm in pairs(perms) do if not self:hasPermission(perm) then return false end end - return true end -- PRIVATE METHODS --- menu: group_selector +-- Menu: group_selector local function menu_group_selector(self) - local function m_select(menu, group_name) + local function m_select(menu, entry) local user = menu.user - user:addGroup(group_name) + local group_name, grade + if type(entry) == "table" and entry.group then + group_name = entry.group + grade = tonumber(entry.grade) -- Force to number + else + group_name = entry + end + + if group_name then + user:addGroup(group_name) + + -- If it's a faction, set default grade if provided + local gcfg = vRP.EXT.Group.cfg.groups[group_name] + if gcfg and gcfg._config and gcfg._config.gtype == "faction" and grade then + user.cdata.faction_grade = grade -- Store as number + end + end + user:closeMenu(menu) end @@ -169,31 +209,46 @@ local function menu_group_selector(self) menu.title = menu.data.name menu.css.header_color = "rgba(255,154,24,0.75)" - for k,group_name in pairs(menu.data.groups) do - if k ~= "_config" then - local title = self:getGroupTitle(group_name) - if title then - menu:addOption(title, m_select, nil, group_name) - end + for _, entry in ipairs(menu.data.groups or {}) do + local group_name = type(entry) == "table" and entry.group or entry + local title = self:getGroupTitle(group_name) + + if group_name and title then + menu:addOption(title, m_select, nil, entry) end end end) end --- menu: admin users user +-- Menu: admin users user local function menu_user_groups(self) local function m_groups(menu, index) local user = menu.user local tuser = vRP.users[menu.data.id] - local groups = "" + local groups_str = "" + local cfg = vRP.EXT.Group.cfg + if tuser and tuser:isReady() then for group in pairs(tuser.cdata.groups) do - groups = groups..group.." " + local group_name = group + local grade_str = "" + + -- Check if group is a faction to add grade info + local gcfg = cfg.groups[group] + if gcfg and gcfg._config and gcfg._config.gtype == "faction" then + local grade = tuser.cdata.faction_grade + local grade_info = gcfg._config.grades[grade] + if grade_info then + grade_str = " ( " .. grade_info.name .. " )" + end + end + + groups_str = groups_str .. group_name .. grade_str .. " " end end - menu:updateOption(index, nil, lang.admin.users.user.groups.description({groups})) + menu:updateOption(index, nil, lang.admin.users.user.groups.description({groups_str})) end local function m_addgroup(menu) @@ -201,7 +256,33 @@ local function menu_user_groups(self) local tuser = vRP.users[menu.data.id] if tuser then - local group = user:prompt(lang.admin.users.user.group_add.prompt(),"") + local group = user:prompt("Group name to add:", "") + local cfg = vRP.EXT.Group.cfg + local gcfg = cfg.groups[group] + + if gcfg and gcfg._config and gcfg._config.gtype == "faction" then + local grades = gcfg._config.grades or {} + local grade_list = {} + + for k, v in pairs(grades) do + table.insert(grade_list, string.format("%d - %s", k, v.name or "Unnamed")) + end + + table.sort(grade_list) + + local grade_prompt = "Available grades:\n" .. table.concat(grade_list, "\n") + local grade = tonumber(user:prompt("Faction grade (number):", grade_prompt)) or 1 + tuser.cdata.faction_grade = grade + end + + -- Remove existing faction if needed + for k, v in pairs(tuser:getGroups()) do + local g = cfg.groups[k] + if g and g._config and g._config.gtype == "faction" then + tuser:removeGroup(k) + end + end + tuser:addGroup(group) end end @@ -211,7 +292,7 @@ local function menu_user_groups(self) local tuser = vRP.users[menu.data.id] if tuser then - local group = user:prompt(lang.admin.users.user.group_remove.prompt(),"") + local group = user:prompt(lang.admin.users.user.group_remove.prompt(), "") tuser:removeGroup(group) end end @@ -223,7 +304,6 @@ local function menu_user_groups(self) if tuser then menu:addOption(lang.admin.users.user.groups.title(), m_groups, lang.admin.users.user.groups.description()) - if user:hasPermission("player.group.add") then menu:addOption(lang.admin.users.user.group_add.title(), m_addgroup) end @@ -234,6 +314,254 @@ local function menu_user_groups(self) end) end +-- Menu: manage_faction +local function menu_manage_faction(self) + -- Check if user has Lider or Co_Lider + local function has_leader_permissions(user, faction_cfg, grade) + local grade_cfg = faction_cfg._config.grades[grade] + return grade_cfg and (grade_cfg.Lider or grade_cfg.Co_Lider) + end + + -- Get grade config + local function get_grade_cfg(faction_cfg, grade) + return faction_cfg._config.grades[grade] + end + + -- Add a nearby player to faction + local function m_add_player_in_faction(menu) + local user = menu.user + local near_player = vRP.EXT.Base.remote.getNearestPlayer(user.source, 10) + + if not near_player then + vRP.EXT.Base.remote._notify(user.source, "No player nearby") + return + end + + local nuser = vRP.users_by_source[near_player] + if not nuser or nuser:getFactionGroup() then + vRP.EXT.Base.remote._notify(user.source, "Player is already in a faction") + return + end + + local faction = user:getFactionGroup() + local grade = user:getFactionGrade() + local faction_cfg = vRP.EXT.Group.cfg.groups[faction] + + if has_leader_permissions(user, faction_cfg, grade) then + local confirm = user:request("Do you want to add " .. nuser.name .. " to your faction?", 30) + if confirm then + nuser:addGroup(faction) + vRP.EXT.Base.remote._notify(user.source, "Added " .. nuser.name .. " to the faction") + vRP.EXT.Base.remote._notify(nuser.source, "You were added to the faction") + end + else + vRP.EXT.Base.remote._notify(user.source, "You don't have permission to add members") + end + end + + -- Remove a faction member + local function m_remove_player_in_faction(menu) + local user = menu.user + local faction = user:getFactionGroup() + local grade = user:getFactionGrade() + local faction_cfg = vRP.EXT.Group.cfg.groups[faction] + + if not has_leader_permissions(user, faction_cfg, grade) then + vRP.EXT.Base.remote._notify(user.source, "You don't have permission to remove members") + return + end + + vRP.EXT.GUI:registerMenuBuilder("faction_members", function(submenu) + submenu.title = "Faction Members" + submenu.css.header_color = "rgba(0,125,255,0.75)" + + for _, target in pairs(vRP.users) do + if target:isReady() and target:getFactionGroup() == faction then + if target.id ~= user.id then -- Can't remove yourself + if not get_grade_cfg(faction_cfg, grade).Co_Lider or target:getFactionGrade() < grade then + submenu:addOption(target.name, function() + local confirm = user:request("Do you want to remove " .. target.name .. " from the faction?", 30) + if confirm then + target:removeGroup(faction) + vRP.EXT.Base.remote._notify(user.source, "Removed " .. target.name) + vRP.EXT.Base.remote._notify(target.source, "You were removed from the faction") + user:closeMenu(submenu) + end + end) + end + end + end + end + end) + + user:openMenu("faction_members") + end + + -- Check if user can modify target's grade + local function can_modify_grade(user_grade_cfg, user_grade, target_grade, is_self) + -- Lider can't modify other Liders or themselves + if user_grade_cfg.Lider then + if is_self then return false end + return target_grade < user_grade + end + + -- Co-Lider can modify lower grades + if user_grade_cfg.Co_Lider then + return target_grade < user_grade + end + + return false + end + + -- Grades management menu + vRP.EXT.GUI:registerMenuBuilder("faction_grades", function(menu) + local user = menu.user + local faction = user:getFactionGroup() + local user_grade = user:getFactionGrade() + local faction_cfg = vRP.EXT.Group.cfg.groups[faction] + + menu.title = "Manage Grades" + menu.css.header_color = "rgba(0,125,255,0.75)" + + local user_grade_cfg = get_grade_cfg(faction_cfg, user_grade) + if not user_grade_cfg.Lider and not user_grade_cfg.Co_Lider then + vRP.EXT.Base.remote._notify(user.source, "You don't have permission to manage grades") + return + end + + -- Populate member list + for _, target in pairs(vRP.users) do + if target:isReady() and target:getFactionGroup() == faction then + local target_grade = target:getFactionGrade() + local is_self = (target == user) + + if can_modify_grade(user_grade_cfg, user_grade, target_grade, is_self) then + local target_grade_name = get_grade_cfg(faction_cfg, target_grade).name + + menu:addOption(target.name .. " - " .. target_grade_name, function() + vRP.EXT.GUI:registerMenuBuilder("faction_grade_selector", function(grade_menu) + grade_menu.title = "Grade " .. target.name + grade_menu.css.header_color = "rgba(0,125,255,0.75)" + + local max_grade = user_grade_cfg.Lider + and #faction_cfg._config.grades + or (user_grade - 1) + + for g = 0, max_grade do + local ginfo = faction_cfg._config.grades[g] + if ginfo then + grade_menu:addOption(ginfo.name, function() + local confirm = user:request("Set " .. target.name .. " to " .. ginfo.name .. "?", 30) + if confirm then + target.cdata.faction_grade = g + vRP.EXT.Base.remote._notify(user.source, "Set " .. target.name .. " to " .. ginfo.name) + vRP.EXT.Base.remote._notify(target.source, "Your rank changed to " .. ginfo.name) + user:closeMenu(grade_menu) + end + end) + end + end + end) + user:openMenu("faction_grade_selector") + end) + end + end + end + end) + + -- Open grades management menu + local function m_manage_grades(menu) + local user = menu.user + local faction = user:getFactionGroup() + local grade = user:getFactionGrade() + local faction_cfg = vRP.EXT.Group.cfg.groups[faction] + + local user_grade_cfg = get_grade_cfg(faction_cfg, grade) + if not user_grade_cfg.Lider and not user_grade_cfg.Co_Lider then + vRP.EXT.Base.remote._notify(user.source, "You don't have permission to manage grades") + return + end + + user:openMenu("faction_grades") + end + + -- Exit faction + local function m_exit_faction(menu) + local user = menu.user + local faction = user:getFactionGroup() + local grade = user:getFactionGrade() + local faction_cfg = vRP.EXT.Group.cfg.groups[faction] + local grade_cfg = get_grade_cfg(faction_cfg, grade) + + if not grade_cfg.Lider then + local confirm = user:request("Do you want to leave the faction?", 30) + if confirm then + user:removeGroup(faction) + user.cdata.faction_grade = nil + vRP.EXT.Base.remote._notify(user.source, "You left the faction") + user:closeMenu(menu) + end + end + end + + -- Toggle duty status + local function m_toggle_duty(menu) + local user = menu.user + user:setFactionDuty(not user:isOnFactionDuty()) + end + + -- Main faction menu + vRP.EXT.GUI:registerMenuBuilder("manage_faction", function(menu) + local user = menu.user + local faction = user:getFactionGroup() + local grade = user:getFactionGrade() + local faction_cfg = vRP.EXT.Group.cfg.groups[faction] + local grade_cfg = get_grade_cfg(faction_cfg, grade) + + menu.title = "Faction" + menu.css.header_color = "rgba(0,125,255,0.75)" + + if grade_cfg.Lider or grade_cfg.Co_Lider then + menu:addOption("Add Member", m_add_player_in_faction) + menu:addOption("Remove Member", m_remove_player_in_faction) + menu:addOption("Grades", m_manage_grades) + end + + menu:addOption("Toggle Duty", m_toggle_duty) + + if not grade_cfg.Lider then + menu:addOption("Exit Faction", m_exit_faction) + end + end) +end + +function Group:taskPaycheck() + for faction_name, group_cfg in pairs(self.cfg.groups) do + if group_cfg._config and group_cfg._config.gtype == "faction" then + local interval = group_cfg._config.paycheck_interval or 60 + self.paychecks_elapsed[faction_name] = (self.paychecks_elapsed[faction_name] or 0) + 1 + + if self.paychecks_elapsed[faction_name] >= interval then + self.paychecks_elapsed[faction_name] = 0 + + local users = self:getUsersByGroup(faction_name) + for _, user in pairs(users) do + if user:isReady() and user.spawns > 0 and user:isOnFactionDuty() then + local grade = user:getFactionGrade() + local grade_cfg = group_cfg._config.grades and group_cfg._config.grades[grade] + + if grade_cfg and grade_cfg.payment and grade_cfg.payment > 0 then + user:giveWallet(grade_cfg.payment) + vRP.EXT.Base.remote._notify(user.source, "Faction salary: ~g~$" .. grade_cfg.payment) + end + end + end + end + end + end +end + + -- METHODS function Group:__construct() @@ -241,6 +569,7 @@ function Group:__construct() self.cfg = module("cfg/groups") self.func_perms = {} + self.paychecks_elapsed = {} -- faction name => elapsed minutes -- register not fperm (negate another fperm) self:registerPermissionFunction("not", function(user, params) @@ -253,42 +582,42 @@ function Group:__construct() if group then return user:hasGroup(group) end - return false end) + -- Faction menu access + vRP.EXT.GUI:registerMenuBuilder("main", function(menu) + local user = menu.user + if user:getFactionGroup() then + menu:addOption("Faction", function() + user:openMenu("manage_faction") + end) + end + end) + -- menu menu_group_selector(self) menu_user_groups(self) - + menu_manage_faction(self) + -- main menu vRP.EXT.GUI:registerMenuBuilder("admin.users.user", function(menu) menu:addOption("Groups", function(menu) menu.user:openMenu("user.groups", menu.data) end) end) - - -- task: group count display - if next(self.cfg.count_display_permissions) then - Citizen.CreateThread(function() - while true do - Citizen.Wait(self.cfg.count_display_interval*1000) - - -- display - local content = "" - for _, dperm in ipairs(self.cfg.count_display_permissions) do - local count = #self:getUsersByPermission(dperm[1]) - local img = dperm[2] - - content = content.."
"..count.."
" - end - vRP.EXT.GUI.remote.setDivContent(-1, "group_count_display", content) - end - end) + local function task_paycheck() + SetTimeout(60000, task_paycheck) + self:taskPaycheck() end + + async(function() + task_paycheck() + end) end + -- return users list function Group:getUsersByGroup(name) local users = {} @@ -371,13 +700,6 @@ function Group.event:playerSpawn(user, first_spawn) user:setArea("vRP:gselector:"..k,x,y,z,1,1.5,enter,leave) end end - - -- group count display - if next(self.cfg.count_display_permissions) then - if self.cfg.display then - vRP.EXT.GUI.remote.setDiv(user.source, "group_count_display", self.cfg.count_display_css, "") - end - end end -- call group onspawn callback at spawn @@ -411,4 +733,4 @@ function Group.event:characterLoad(user) end end -vRP:registerExtension(Group) +vRP:registerExtension(Group) \ No newline at end of file From 267f1b62dd35927d06560dbdfa65247ccbfae335 Mon Sep 17 00:00:00 2001 From: Ianis Catalin Date: Thu, 29 May 2025 12:03:48 +0300 Subject: [PATCH 2/2] Hours --- vrp/cfg/groups.lua | 3 ++- vrp/cfg/modules.lua | 3 ++- vrp/client/hours.lua | 20 +++++++++++++++ vrp/modules/group.lua | 51 +++++++++++++++++++++++++------------ vrp/modules/hours.lua | 58 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 vrp/client/hours.lua create mode 100644 vrp/modules/hours.lua diff --git a/vrp/cfg/groups.lua b/vrp/cfg/groups.lua index 1840b89..6ff536b 100644 --- a/vrp/cfg/groups.lua +++ b/vrp/cfg/groups.lua @@ -156,7 +156,8 @@ cfg.groups = { ["emergency"] = { _config = { title = "Emergency", - gtype = "job" + gtype = "job", + required_hours = 2 -- Requires 2 hours to access }, "emergency.revive", "emergency.shop", diff --git a/vrp/cfg/modules.lua b/vrp/cfg/modules.lua index 412c8f8..09cccb5 100644 --- a/vrp/cfg/modules.lua +++ b/vrp/cfg/modules.lua @@ -16,7 +16,8 @@ local modules = { user = true, identity = true, money = true, - logs = true -- discord logs + logs = true, -- discord logs + hours = true, -- hours module } return modules diff --git a/vrp/client/hours.lua b/vrp/client/hours.lua new file mode 100644 index 0000000..9600f75 --- /dev/null +++ b/vrp/client/hours.lua @@ -0,0 +1,20 @@ +-- https://github.com/ImagicTheCat/vRP +-- MIT license (see LICENSE or vrp/vRPShared.lua) +if not vRP.modules.hours then return end + +local hours = class("hours", vRP.Extension) + +function hours:__construct() + vRP.Extension.__construct(self) + + Citizen.CreateThread(function() + while true do + Wait(15 * 60000) + print("Hours: 15 minutes have passed.") + self.remote._updateHours(0.25) + end + end) +end + + +vRP:registerExtension(hours) \ No newline at end of file diff --git a/vrp/modules/group.lua b/vrp/modules/group.lua index ed97ae3..ac67ca2 100644 --- a/vrp/modules/group.lua +++ b/vrp/modules/group.lua @@ -670,19 +670,34 @@ Group.event = {} function Group.event:playerSpawn(user, first_spawn) if first_spawn then - -- init group selectors - for k,v in pairs(self.cfg.selectors) do + -- Initialize group selectors + for k, v in pairs(self.cfg.selectors or {}) do local gcfg = v._config if gcfg then - local x = gcfg.x - local y = gcfg.y - local z = gcfg.z - + local x, y, z = gcfg.x, gcfg.y, gcfg.z local menu + local function enter(user) if user:hasPermissions(gcfg.permissions or {}) then - menu = user:openMenu("group_selector", {name = k, groups = v}) + local player_hours = user.cdata.hours or 0 + local can_access = true + + -- Check required hours for each group + for _, group_name in ipairs(v) do + local group_cfg = self.cfg.groups and self.cfg.groups[group_name] + local required_hours = group_cfg and group_cfg._config and group_cfg._config.required_hours or 0 + + if vRP.modules.hours and required_hours > 0 and player_hours < required_hours then + vRP.EXT.Base.remote._notify(user.source, "You need ~r~" .. (required_hours - player_hours) .. " ~w~ more hours to access " .. group_name .. "!") + can_access = false + end + end + + -- Open menu if all groups pass the check + if can_access then + menu = user:openMenu("group_selector", { name = k, groups = v }) + end end end @@ -692,22 +707,26 @@ function Group.event:playerSpawn(user, first_spawn) end end - local ment = clone(gcfg.map_entity) + local ment = clone(gcfg.map_entity or {}) + ment[2] = ment[2] or {} ment[2].title = k - ment[2].pos = {x,y,z-1} - vRP.EXT.Map.remote._addEntity(user.source, ment[1], ment[2]) + ment[2].pos = { x, y, z - 1 } + vRP.EXT.Map.remote._addEntity(user.source, ment[1] or "default", ment[2]) - user:setArea("vRP:gselector:"..k,x,y,z,1,1.5,enter,leave) + user:setArea("vRP:gselector:" .. k, x, y, z, 1, 1.5, enter, leave) end end - end - -- call group onspawn callback at spawn - - local groups = user:getGroups() + -- Optional: Setup group count display + if next(self.cfg.count_display_permissions or {}) and self.cfg.display then + vRP.EXT.GUI.remote.setDiv(user.source, "group_count_display", self.cfg.count_display_css or "", "") + end + end + -- Call group onspawn callbacks + local groups = user:getGroups() or {} for name in pairs(groups) do - local group = self.cfg.groups[name] + local group = self.cfg.groups and self.cfg.groups[name] if group and group._config and group._config.onspawn then group._config.onspawn(user) end diff --git a/vrp/modules/hours.lua b/vrp/modules/hours.lua new file mode 100644 index 0000000..0270ac2 --- /dev/null +++ b/vrp/modules/hours.lua @@ -0,0 +1,58 @@ +if not vRP.modules.hours then return end + +local Hours = class("Hours", vRP.Extension) + +Hours.event = {} +Hours.tunnel = {} +Hours.User = class("User") + +function Hours:__construct() + vRP.Extension.__construct(self) + RegisterCommand("timer", function(source) + vRP:triggerEvent("ShowHours", source) + end, false) +end + +function Hours.User:getHours() + return self.cdata.hours +end + +function Hours.event:ShowHours(source) + local user = vRP.users_by_source[source] + local hours = user.cdata.hours + local minutes = hours - math.floor(hours) + + vRP.EXT.Base.remote._notify(user.source, "You have ~b~" .. math.floor(hours) .. " ~w~ hours and ~b~" .. math.floor(minutes * 100 * 0.60) .. " ~w~ minutes played!") +end + +function Hours.event:updateHours(hoursToAdd) + for _, user in pairs(vRP.users) do + if user.cdata then + local currentHours = user.cdata.hours or 0 + local newHour = currentHours + hoursToAdd + user.cdata.hours = newHour + + user:save() + + vRP.EXT.Base.remote._notify(user.source, "You now have ~b~" .. math.floor(newHour) .. " ~w~ hours and ~b~" .. math.floor((newHour - math.floor(newHour)) * 100 * 0.60) .. " ~w~ minutes played.") + end + end +end + +function Hours.tunnel:updateHours(hours) + local user = vRP.users_by_source[source] + if user then + vRP:triggerEvent("updateHours", hours) + end +end + +function Hours.event:characterLoad(user) + if user.cdata then + if user.cdata.hours == nil then + user.cdata.hours = 0 + end + end +end + + +vRP:registerExtension(Hours) \ No newline at end of file