Clans — In-Game Menu
Last updated
May be stale
This page was last updated ago, on 25 August 2026. Check what it says against the code before relying on it.
The in-game menu is the only clan management surface. Anything it cannot do,
nobody can do — the companion web app is out of scope and its repository is
disabled, and the legacy in-game panel is a "Coming soon..." stub.
Status: Recommended route, not an implementation spec. Exact dimensions, colours and Derma control choices belong to whoever builds it. Companion to core.md and bank.md.
Integration
core/derma/cl_main.lua already defines the F1 shell as a top tab bar
(f1_categoriesToBeMade), where each entry is a Dock(FILL) panel resolved by
class name. cityrp_Clan is already registered — it is the stub. Adding "Clan"
to that array is the whole shell-level integration cost.
Everything below happens inside that one panel. For sub-navigation the nearest
precedent is the Settings tab (core/plugins/player_settings/cl_panel.lua): a
persistent left sidebar, content on the right.
Sections: Roster / Ranks / Bank / Invites / Identity, landing on Roster.
Do not copy Settings' opt-out of cityrp.ui scaling. That panel uses fixed
sizes because it configures the scale slider itself — a bootstrapping problem
Clans does not have. Every font here goes through cityrp.ui.CreateScaledFont
and every dimension through cityrp.ui.Scale. The roster, rank list and
permission editor rebuild child controls when data changes, so they must also
hook OnScaleChanged to relayout — not merely rebuild fonts.
No-clan state
A player without a clan still needs somewhere to land:
"You're not in a clan."- Pending invites — header
"You have X invites"and a table, accept and decline per row. Omitted entirely when there are none. - A Create a clan button, stating the $50,000 cost before confirmation.
This is the only route to clan creation once chat commands are replaced, and the only place invites are visible. The legacy implementation surfaced invites once as a spawn notification — easy to miss, impossible to recall.
Roster
┌─ ROSTER ── 24 / 100 members ──────────[ Search ]─┐
│ ⬤ Nova Leader │
│ Online · Joined 12 Jan 2025 │
│ ⬤ Rusk Officer [Kick] [Rank ▾] │
│ Online · Joined 03 Mar 2025 │
│ ⚪ Priya Lieutenant [Kick] [Rank ▾] │
│ Offline · Joined 02 Jul 2025 │
│ … │
└────────────────────────────────────────────────────┘
- Sorted by rank, then online-first, then alphabetically.
- Online status is icon and word, never colour alone.
[Kick]and[Rank ▾]render only on rows the viewer's threshold actually reaches — omitted entirely elsewhere, not shown disabled. At 100 members a greyed-out button on every unreachable row is noise.
That last point does real teaching work: the presence or absence of a Kick button next to a specific person explains a threshold permission more clearly than any abstract configuration screen.
Ranks
Ranks are a strict ladder — one rank per position, most senior first. A plain ordered list is correct.
Master–detail: the ladder on the left, the selected rank's identity and permissions on the right. Member count is shown inline per rank, because deleting a rank that members hold is rejected by default and the leader needs that number before trying.
Rank 1 is not editable here beyond renaming. Reassigning who holds it is a membership action tied to a person — Transfer Leadership, in Roster — not a structural rank edit.
Reordering is drag-and-drop, but every drag needs a non-drag equivalent for keyboard use: a per-row context menu with Move Up / Move Down / Rename / Delete / Duplicate permissions from…
Duplication is a scale mitigation, not a convenience. Live clans reach 28 ranks and the cap is 50; configuring each rank's permissions from blank is the real "wall of controls" risk. Cloning an existing rank as a starting point is what keeps it usable.
Permission editor
Built by iterating the permission registry and dispatching on kind, grouped
into a section per owning module. Nothing here is a hardcoded form — that is
what makes module-declared permissions work in practice, and a new module's
permissions must appear with no UI change.
| Kind | Control | States |
|---|---|---|
BOOLEAN |
Toggle | On / Off |
THRESHOLD |
Rank picker plus a resolved sentence | None, or a specific rank |
NUMERIC_LIMIT |
Grant toggle → number field or Unlimited | Off / Limited / Unlimited |
Always render the module-declared label ("Withdraw from clan bank"), never the
raw id (bank.withdraw). Never show -1 or NULL to a player — those are
storage sentinels, not vocabulary.
Threshold permissions need a plain-language caption, not just a dropdown. "Kick = Lieutenant" does not self-evidently mean "may kick Lieutenant and everyone below it". Pair the picker with a permanent sentence:
Grants authority over Lieutenant and everyone junior to it.
The sentence is the floor — cheap, accessible, complete on its own. A clickable mini-ladder that shades the covered range is a worthwhile enhancement on top, but it needs a non-colour indicator per row, and it degrades to the sentence cleanly if it does not fit the build.
Filter the options to what the server would accept. A rank's threshold picker offers only ranks strictly junior to it (invariant 4). The same principle applies throughout: never render a control whose action would be rejected.
⚠
core.manage_ranksalso gates editing a rank's own permissions, includingcore.delete_clan— granting it to a non-leader rank is effectively clan co-ownership. Show a persistent inline warning under that toggle whenever it is enabled for any rank other than rank 1. Persistent, not a one-time toast: the consequence is standing, not momentary.
Change Rank is the mirror case
Assigning a person to a rank resolves to one specific rank, and its options are
filtered to those satisfying the actor's core.setrank threshold — both the
target's current rank and the new rank must satisfy it.
core/derma/cl_market.lua's category picker (dropDown.CategoryTree, built with
AddSubMenu) is the existing precedent for a grouped dropdown.
Progressive disclosure
Rule: if a section has legitimate read-only value for every member, show it read-only and gate the controls. If it is operationally private with no read-only value, hide it entirely.
| Section | Visible to | Reasoning |
|---|---|---|
| Roster | Everyone | Baseline membership info |
| Ranks | Everyone, read-only without core.manage_ranks |
A member knowing what an Officer can do — and therefore who to ask — costs nothing |
| Invites | core.invite or core.manage_invites holders |
Who is being recruited is operationally sensitive with no read-only value to someone who cannot act |
| Bank — balance, deposit, own reclaimable deposits | Everyone | bank.deposit is universal; a player must see their own deposits to reclaim them |
| Bank — full ledger | bank.withdraw holders |
Financial oversight rather than baseline transparency |
| Identity | Everyone views; field-level edit split — name, abbreviation, motto and description need core.edit_identity, MOTD needs core.motd_edit |
Two permissions gate different fields in one tab, so control-level adaptation applies within a section, not only at the sidebar |
Clan deletion
Deletion distributes the bank balance pro-rata to all historical net contributors and cannot be undone.
[Delete Clan]sits in a visually separated danger zone, never beside frequent actions.- The confirmation states, in plain language rather than formulas:
- "This will permanently delete [Clan Name]."
- "Current bank balance: $X."
- "This will be split among everyone who has ever contributed, in proportion to what they put in. Anyone who withdrew more than they deposited receives nothing."
- "Your $25,000 creation deposit is refunded to you separately."
- "This happens immediately and cannot be undone."
- Type the clan name to confirm. The button stays disabled until it matches. This mirrors the existing confirmation-code convention while using something the leader already knows, so typing it is itself part of registering what is about to happen.
Deletion is immediate — there is no hold, countdown or cancellation window. The typed confirmation is the only safeguard, which is the standard pattern for irreversible destructive actions. Members learn of the dissolution from their payout notification. See bank.md for why a hold was rejected rather than merely omitted.
Deposit consent prompt
Shown before a first deposit to a clan, and again after consent_reprompt_days
of no deposit or withdrawal. Four short lines, not a wall of text:
┌─ Depositing to [Clan Name] ──────────────────┐
│ ⚠ You do not currently hold withdraw │
│ permission in this clan. │
│ Only Leader and Officer can take money out. │
│ │
│ • This money becomes clan property. │
│ • You can reclaim THIS deposit yourself for │
│ the next 10 minutes. │
│ • If the clan disbands, remaining funds are │
│ split by how much each person put in. │
│ │
│ [ Cancel ] [ Deposit $500 ] │
└─────────────────────────────────────────────────┘
- The withdraw-status line is personalised and always in the same position,
tonally adaptive — warning if they lack
bank.withdraw, neutral if they hold it. Consistent placement matters because the prompt reappears later, possibly under different leadership. - No "don't show again" checkbox. The re-prompt interval is a deliberate server-side safety mechanism; a client-side snooze would either do nothing or actively defeat it.
- The amount is entered before the prompt and carried into it.
- Escape maps to Cancel. The default action must never be the one that commits money.
Accessibility
| Requirement | How |
|---|---|
| Keyboard only | Every drag interaction has a context-menu equivalent |
| Scales at all resolutions | Everything through cityrp.ui; OnScaleChanged relayout on data-driven panels |
| Not colour-only | Online/offline is icon + word; threshold range uses a per-row marker, not tint |
| No unwarned flashing | The deletion countdown is static or slow — never strobing |
Long-form fields have no per-line caps, so the renderer must hard-wrap on character boundaries — a single unbroken 2000-byte MOTD is valid input. Panels clip or scroll; they never expand.
Outside this design's scope but worth raising: the outer F1 frame is currently fixed at 1008×825 and is not itself scaled. A correctly-scaled Clan panel inside an unscaled frame still will not grow with the player's HUD scale preference.
Open decisions
| # | Decision | Recommendation |
|---|---|---|
| Moot — the hold was dropped. Deletion is immediate; the typed confirmation is the safeguard | ||
core.manage_ranks from their own rank? |
Resolved in the model, not the UI. Rank 1 permanently holds core.manage_ranks; see core.md. The editor renders it locked-on with an explanation rather than as an editable toggle that would be rejected |
|
| 3 | Should a core.invite-only holder see the pending list for invites they sent? |
Default no for v1 — keep the Send control, hide the list |