Skip to content

API Reference ​

← BLN Society overview

Everything a resource can call β€” server-side mutations and reads, plus client-side helpers.

Related: Statebags (raw client data) Β· Events Β· Hooks

Before mutating: wait for bln_society:ready. Mutating exports return not_ready until boot finishes.

Money primitives: AddMoney / RemoveMoney have no permission check (revenue and costs). Deposit / Withdraw check permissions and move player money.


Response shape ​

Every mutating function answers with the same table:

lua
{
    success = true,
    code    = 'member_hired',
    message = 'Arthur Morgan has been hired as Deputy.',
    args    = { 'Arthur Morgan', 'Deputy' },
    data    = { ... },
}

code is stable across languages β€” branch on it and show message. Pure reads (GetBalance, HasPermission, IsOnDuty, …) return their value directly.

The actor argument ​

Most functions take a trailing actor:

You passTreated as
nothing, or nilSystem call β€” permission checks skipped, audit still written
player source (number)That player β€” permissions enforced
character id (string)Same, resolved from character
{ source = ..., charId = ... }When you have both
{ system = true, charId = ... }System call attributed to a character in the audit

Pass source when a player clicked something. Omit it when your resource acts on its own (e.g. booking a sale into the till).


Server exports ​

Meta ​

ExportArgumentsReturns
IsReady()–boolean
GetConstants()–Const table: statuses, ledger types, actions, hook names
GetPermissionRegistry()–permission groups from config
GetPresets()–society presets from config
GetSettings()–merged config settings

Session ​

ExportArgumentsReturns
GetCharacterId(source)player sourcestring|nil
GetSource(characterId)character idinteger|nil
GetCharacterName(characterId)character idstring

Societies ​

ExportArgumentsReturns
CreateSociety(data, actor?)see belowresponse, data = society
EnsureSociety(data)requires ownerResource + externalKeyresponse, creates only if missing
GetSociety(societyId)response
GetSocietyByKey(ownerResource, externalKey)response
GetSocieties(filter?){ status?, fundingSource? }response, data = array
UpdateSociety(societyId, data, actor?){ name?, status?, fundingSource?, metadata?, chargeFees? }response
UpdateSettings(societyId, settings, actor?)merged into settings bagresponse
GetSetting(societyId, key, fallback?)value
SetFundingSource(societyId, 'account'|'state', actor?)system onlyresponse
IsStateFunded(societyId)boolean
SetSocietyPoint(societyId, point, actor?)see belowresponse
ClearSocietyPoint(societyId, actor?)response
GetSocietyPoint(societyId)response, data = point or nil
OpenSociety(societyId, source)opens menu for playerresponse
TransferOwnership(societyId, characterId, actor?)owner or systemresponse
IsOwner(societyId, characterId)boolean
GetOwner(societyId)response, data = { characterId, name }
DeleteSociety(societyId, actor?)soft deleteresponse
PurgeSociety(societyId)permanent, system onlyresponse

CreateSociety data:

lua
{
    name = 'Valentine Sheriff',
    preset = 'law',
    fundingSource = 'state',
    owner = characterId,
    ownerName = 'Arthur Morgan',
    status = 'active',
    point = { ... },
    ownerResource = 'bln_stores',
    externalKey = 'valentine_store',
    ranks = { ... },
    balances = { [0] = 500.0 },
    settings = { require_no_other_society = true },
    metadata = { ... },
    createStorages = true,
}

Point shape (fields optional except coords; defaults from Config.points.defaults):

lua
{
    enabled = true,
    coords = { x = -324.1, y = 776.5, z = 117.8, h = 90.0 },
    blip = { enabled = true, sprite = 'blip_shop_store', name = 'Valentine Store' },
    marker = { type = 'default', color = { r = 255, g = 255, b = 255, a = 180 }, scale = 1.0, distance = 15.0 },
    prompt = { enabled = true, name = 'Open', key = Keys.E, distance = 2.0 },
}

Society settings (UpdateSettings):

KeyTypePurpose
require_no_other_societybooleanBlock hiring anyone already in another society
inactive_daysintegerDays idle before auto fire/suspend; 0 disables
inactive_warn_daysintegerWarn window before threshold
inactive_action'fire' | 'suspend'What the inactivity sweep does
discord_webhookstringDiscord webhook URL for audit lines

Ranks ​

ExportArgumentsReturns
GetRanks(societyId)response, ranks sorted by level
GetRank(societyId, rankId)response
CreateRank(societyId, data, actor?){ name, level, salary?, salaryCurrency?, permissions? }response
UpdateRank(societyId, rankId, data, actor?)partial updateresponse
DeleteRank(societyId, rankId, actor?)fails while members hold itresponse
SetRankSalary(societyId, rankId, amount, actor?)response

Members ​

ExportArgumentsReturns
AddMember(societyId, characterId, rankId?, actor?)rank defaults to lowestresponse
RemoveMember(societyId, characterId, actor?)response
SetMemberRank(societyId, characterId, rankId, actor?)response
SetMemberStatus(societyId, characterId, 'active'|'suspended', actor?)response
GetMember(societyId, characterId)response, includes permissions
GetMembers(societyId)response, array with rank and duty flags
IsMember(societyId, characterId)boolean
GetMemberships(characterId)response, all societies for character
GetPlayerMemberships(source)online playerstatebag payload
CanHire(societyId, characterId)dry runresponse
CountMembers(societyId)integer

Permissions ​

ExportArgumentsReturns
HasPermission(societyId, characterId, permission)boolean
GetPermissions(societyId, characterId)map, wildcards expanded
GetPermissionOverrides(societyId, characterId)member overrides only
GrantPermission(societyId, characterId, permission, actor?)response
RevokePermission(societyId, characterId, permission, actor?)explicit denyresponse
ClearPermission(societyId, characterId, permission, actor?)inherit from rankresponse
GetRankLevel(societyId, characterId)integer

Duty ​

ExportArgumentsReturns
SetDuty(societyId, characterId, onDuty, actor?)response
ToggleDuty(societyId, characterId, actor?)response
IsOnDuty(characterId, societyId?)omit society = anywhereboolean
GetDuty(characterId){ societyId, rankId, since }|nil
GetPlayerDuty(source)statebag payload
GetOnDutyMembers(societyId)array of character ids
CountOnDuty(societyId)integer
ForceOffDuty(societyId, characterId, reason?)systemresponse
GetActivity(societyId)response, per-member activity
GetLastSeen(societyId)response, last seen timestamps
GetInactivityReport(societyId)read onlyresponse
SweepInactive(societyId?)run sweep nowresponse

Finance ​

Currencies: 0 = cash, 1 = gold, 2 = rol (Const.CASH, Const.GOLD, Const.ROL).

ExportArgumentsReturns
GetBalance(societyId, currency?)number
GetBalances(societyId){ [currency] = number }
CanAfford(societyId, amount, currency?)boolean
AddMoney(societyId, amount, opts?)no permission checkresponse
RemoveMoney(societyId, amount, opts?)no permission checkresponse
Deposit(societyId, amount, opts?, actor?)from player pocketresponse
Withdraw(societyId, amount, opts?, actor?)to player pocketresponse
Transfer(fromId, toId, amount, opts?, actor?)response
GetLedger(societyId, filters?, actor?)pagedresponse
GetLedgerSummary(societyId, currency?, days?){ income, expenses, net, days }
GetStateSpending(societyId?, days?)state-funded wagesnumber

Finance opts:

lua
{
    currency = 0,
    type = 'sale',
    reason = 'Cart sold',
    ref = 'store:sale:8821',  -- idempotency key
    actorId = characterId,
    targetId = characterId,
    metadata = { ... },
}

Pass a unique ref to make retries safe β€” duplicate ref returns the original entry.

Payroll and paychecks ​

ExportArgumentsReturns
RunPayroll(societyId, actor?)response with totals
GetPayrollHistory(societyId, filters?, actor?)pagedresponse
GetSalary(societyId, characterId)number
GetPayrollEligible(societyId)array
GetPaychecks(societyId, characterId)response
GetPendingPay(characterId)all societiesmap
CollectPaychecks(societyId, actor)response
CollectAllPaychecks(actor)response
IssuePaycheck(societyId, characterId, amount, opts?)bonus, etc.response

Storage ​

ExportArgumentsReturns
GetStorages(societyId, charId?)response
GetStorage(societyId, key)response
OpenStorage(societyId, key, actor)opens inventoryresponse
BuyStorage(societyId, actor, payload)paid from accountresponse
SetStorageAccess(societyId, key, minRankLevel, actor?)response
CalculateStorageCost(slots, maxWeight, acceptWeapons)response
CreateStorage(societyId, key, opts?)response

Fines ​

ExportArgumentsReturns
IssueFine(societyId, characterId, amount, reason, actor?)response
PayFine(fineId, actor)response
CancelFine(fineId, actor?)response
GetFines(societyId, filters?, actor?)pagedresponse
GetCharacterFines(characterId)response

Action fees ​

ExportArgumentsReturns
GetFees(societyId, actor?)response
CalculateFee(societyId, action, baseAmount?)previewresponse
ChargeFee(societyId, action, baseAmount?, opts?)manual chargeresponse

Fee actions: payroll_run, member_hire, finance_withdraw, finance_transfer, society_rename, storage_buy.

Logs ​

ExportArgumentsReturns
Log(societyId, action, opts?)custom audit lineresponse
GetLogs(societyId, filters?, actor?)pagedresponse

Directory and hooks registry ​

ExportArgumentsReturns
GetDirectory()public society directory
GetRevision()integer, bumped on structural change
RegisterHook(name, handler)hook id
RemoveHook(name, id)–
ListHooks()hook names

Society handle ​

For code that works with one society at a time:

lua
local society = exports.bln_society:Society(societyId)
local society = exports.bln_society:SocietyByKey('bln_stores', 'valentine_store')
local society = exports.bln_society:EnsureSocietyHandle({ ... })

Methods (same as exports, without societyId):

Exists Get Data Update Delete Settings Setting UpdateSettingsIsStateFunded SetFunding Owner IsOwner TransferOwnershipPoint SetPoint ClearPoint OpenRanks Rank CreateRank UpdateRank DeleteRank SetRankSalaryMembers Member IsMember AddMember RemoveMember SetMemberRank SetMemberStatus CanHireHasPermission Permissions GrantPermission RevokePermission LevelSetDuty ToggleDuty IsOnDuty OnDuty CountOnDutyBalance Balances CanAfford AddMoney RemoveMoney Deposit Withdraw TransferTo Ledger LedgerSummaryRunPayroll PayrollHistory Salary Paychecks CollectPaychecks IssuePaycheckStorages OpenStorage BuyStorage SetStorageAccess CreateStorageFines IssueFineFees CalculateFee ChargeFeeLogs Log

lua
if society:HasPermission(charId, 'finances.withdraw') then
    society:Withdraw(100, { reason = 'Restock' }, source)
end

Client exports ​

Convenience wrappers around statebags β€” permission checks, wildcards, and filtering handled for you. No server round trip.

ExportReturns
IsReady()boolean
GetMemberships()every society you belong to, keyed by id as string
GetMembership(societyId)one entry, or nil
IsMember(societyId)boolean
HasPermission(societyId, permission)boolean, wildcards resolved
IsOnDuty(societyId?)boolean
GetDuty(){ id, name, type, rankId, since } or false
GetActiveSociety()membership you are on duty for
GetPendingPay(societyId?){ count, amounts }
GetBalance(societyId, currency?)number|nil, requires finances.view
GetStorage(societyId, key?)tier info, requires storage.use
GetDirectory()every society: name, member count, on duty count
GetRevision()integer β€” compare to invalidate cache
GetPlayerMemberships(serverId)another player's memberships
GetPlayerDuty(serverId)another player's duty
OpenUi(societyId) / OpenSociety(societyId) / CloseUi()management menu
IsUiOpen()boolean
ToggleDuty(societyId?)asks server
OpenStorage(societyId, key)asks server
GetStorageInfo(societyId, key)tier info from bag

Error codes ​

Branch on response.code. Every code has a line in config/locale.cfg.lua.

General β€” generic_error, db_error, rate_limited, invalid_request, invalid_amount, player_offline, character_not_found, feature_disabled, blocked_by_hook, not_ready

Society β€” society_not_found, not_member, no_permission, admin_only, point_invalid

Members β€” already_member, target_not_member, cannot_target_self, target_outranks_you, cannot_remove_owner

Hiring β€” target_in_other_society, too_many_societies

Ranks β€” rank_not_found, rank_in_use, rank_level_taken, rank_above_you, cannot_delete_owner_rank, invalid_rank_name

Duty β€” already_on_duty, not_on_duty, duty_elsewhere

Finance β€” insufficient_funds, insufficient_player_funds, transfers_disabled, negative_balance_blocked

Payroll β€” payroll_no_members, payroll_unaffordable, no_paychecks, paycheck_too_far

Storage β€” storage_not_found, storage_max_tier, storage_no_access

Fines β€” fine_not_found, fine_not_pending

Success codes β€” society_created, member_hired, member_promoted, deposited, withdrawn, payroll_done, paycheck_collected, storage_upgraded, fine_issued, point_updated, and more.