twinded_lgw_society
A comprehensive society/company management system for RedM. Manage cash registers, employees, salaries, safes (armory inventories), boss offices, and permissions — all driven by database-stored jobs and grades.
Dependencies
| Resource | Required | Notes |
|---|---|---|
| vorp_core | Yes | Framework |
| vorp_inventory | Yes | Inventory system |
| twinded_libs | Yes | Free shared library |
| oxmysql | Yes | Database |
Compatibility
VORP Framework only — this script uses VORP Core API and vorp_inventory directly.
Installation
bash
ensure twinded_libs
ensure twinded_lgw_societyDatabase
This script reads from existing job tables. The default table names are:
| Table | Description |
|---|---|
twinded_jobs | Jobs (name, label, balance, bosslocation, armorylocation, payCurrency, payInterval, ...) |
twinded_jobs_grades | Grades (job_name, grade, grade_label, salary, permissions JSON) |
twinded_jobs_salary_tracker | Salary tracker (charidentifier, job_name, elapsed_minutes) |
You can change these names in Config.Tables (see settings.lua).
Config Files
| File | Description |
|---|---|
settings.lua | Debug, database tables, prompt ranges, inventory, placement, salary, unemployment, markers, webhook |
lang.lua | All translation strings (prompts, menus, notifications, webhook titles) |
webhook.lua | Custom webhook handler (when Config.Webhook.type = 'custom') |
See the Configuration guide for how to override these files.
Configuration Reference
settings.lua
| Option | Type | Default | Description |
|---|---|---|---|
Config.Debug | boolean | false | Enable debug logging |
Config.Tables.Jobs | string | "twinded_jobs" | Jobs database table |
Config.Tables.Grades | string | "twinded_jobs_grades" | Grades database table |
Config.Tables.SalaryTracker | string | "twinded_jobs_salary_tracker" | Salary tracker table |
Config.BossPromptRange | number | 2.0 | Distance (meters) to interact with boss office |
Config.ArmoryPromptRange | number | 2.0 | Distance (meters) to interact with safe/armory |
Config.MarkerRange | number | 10.0 | Distance (meters) at which markers become visible |
Config.HireRange | number | 5.0 | Distance (meters) to detect nearby players for hiring |
Config.InventoryLimit | number | 0 | Max slots in armory inventory (0 = unlimited) |
Config.AcceptWeapons | boolean | true | Allow weapons in armory |
Config.PositionTimerDays | number | 1 | Cooldown (days) between office/safe relocations |
Config.PlacementTimeout | number | 60 | Timeout (seconds) for placement mode |
Config.StaffGroups | table | {"admin", "dev", "superadmin"} | Groups that bypass placement cooldown |
Config.SalaryTrackerSaveInterval | number | 2 | How often (minutes) salary progress is saved to DB |
Config.UnemployedMoney | number | 2 | Unemployment allowance amount |
Config.UnemployedPayInterval | number | 60 | Unemployment payment interval (minutes) |
Config.BossMarker | table | — | Boss office marker color (default: gold) |
Config.ArmoryMarker | table | — | Armory marker color (default: blue) |
Config.Webhook.type | string | "discord" | 'discord' or 'custom' |
Config.Webhook.url | string | "" | Discord webhook URL |
Permission Fields (grades table JSON)
Each grade's permissions JSON column in the database:
| Permission | Type | Description |
|---|---|---|
isBoss | boolean | Full management access |
armoryAccess | boolean | Can open the armory/safe |
depositBalance | boolean | Can deposit money to cash register |
withDrawBalance | boolean | Can withdraw money from cash register |
storeDepositItems | boolean | Can deposit items in armory |
storeWithdrawItems | boolean | Can withdraw items from armory |
webhook.lua — Custom Data Fields
When using Config.Webhook.type = 'custom', the data._meta object contains:
| Field | Type | Description |
|---|---|---|
action | string | "deposit", "withdraw", "hire", "fire", "grade_change", "salary", "unemployment" |
job | string | Job name |
amount | number | Money amount (when applicable) |
currency | number | Currency type (0 = dollars, 1 = gold) |
newBalance | number | Company balance after operation |
targetCharId | number | Target character identifier |
newGrade | number | New grade number (for grade changes) |
gradeLabel | string | New grade label |
srcName | string | Action initiator name |
targetName | string | Action target name |
Features
- Boss office — Proximity-based prompt to open management menu
- Cash register — Deposit and withdraw money (permission-based)
- Employee management — View, hire, promote, demote, fire
- Safe (armory) — Shared inventory accessible by authorized employees
- Salary system — Automatic payments at configurable intervals, persisted across reconnects
- Unemployment allowance — Periodic payments to unemployed/offduty players
- Office/safe relocation — Boss can move the office and safe (with cooldown)
- Permission system — Grade-based permissions stored as JSON
- Real-time sync — Markers and prompts update instantly when job or permissions change
- Multi-job support — Full multijob integration
- Hot restart support — Re-pushes all player data on resource restart
- RCON sync events — External panel integration
- Webhook logging — Discord or custom handler
- Rate limiting — Per-source and per-job locking
Command
| Command | Description |
|---|---|
/jobmenu | Open placement menu (boss only, no proximity required) |
Server Exports
Money
lua
-- Get company balance
local balance = exports['twinded_lgw_society']:getSocietyMoney(jobName)
-- Add money (atomic SQL)
exports['twinded_lgw_society']:addSocietyMoney(jobName, amount)
-- Remove money (floor at 0)
exports['twinded_lgw_society']:removeSocietyMoney(jobName, amount)Player Status
lua
local isBoss = exports['twinded_lgw_society']:getPlayerBossStatus(src)
local hasArmory = exports['twinded_lgw_society']:getPlayerArmoryStatus(src)
local canDraw = exports['twinded_lgw_society']:getPlayerWithDrawStatus(src)
local perms = exports['twinded_lgw_society']:getPlayerStorePermissions(src)Job Data
lua
local grades = exports['twinded_lgw_society']:getJobGrades(jobName)
local jobData = exports['twinded_lgw_society']:getJobData(jobName)
local allJobs = exports['twinded_lgw_society']:getAllJobs()
local labels = exports['twinded_lgw_society']:getGradesForClient(jobName)
-- Async
exports['twinded_lgw_society']:getJobEmployees(jobName, function(employees)
-- ...
end)Salary
lua
-- Delete salary tracker entry
exports['twinded_lgw_society']:deleteSalaryTrackerForJob(charId, jobName)
-- Get remaining time per job (async)
exports['twinded_lgw_society']:getSalaryInfoForJobs(charId, jobNames, function(info)
-- ...
end)Server Events
| Event | Description |
|---|---|
twinded_lgw_society:cacheReady | Fired when the cache is fully loaded |
twinded_lgw_society:employeeHire | Fired after a hire (src, jobName, charId) |
twinded_lgw_society:employeeFire | Fired after a fire (src, jobName, charId) |
twinded_lgw_society:rcon:syncJob | RCON: reload a specific job (jobName) |
twinded_lgw_society:rcon:syncAllJobs | RCON: reload all jobs |
Troubleshooting
| Problem | Solution |
|---|---|
| Script doesn't start | Make sure twinded_libs is started before |
| No markers/prompts | Check the job exists in DB with bosslocation/armorylocation set |
| Can't access office | Verify grade permissions in twinded_jobs_grades.permissions JSON |
| Salary not paying | Check payInterval in jobs table and salary in grades table |
| Safe not opening | Verify armoryAccess or isBoss permission for the grade |
| Wrong table names | Update Config.Tables in settings.lua |

