CityRP Documentation Repository

Clans — In-Game Menu

Last updated

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:

  1. "You're not in a clan."
  2. Pending invites — header "You have X invites" and a table, accept and decline per row. Omitted entirely when there are none.
  3. 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_ranks also gates editing a rank's own permissions, including core.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.

  1. [Delete Clan] sits in a visually separated danger zone, never beside frequent actions.
  2. 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."
  3. 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.

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
1 Is the deletion hold cancellable? Moot — the hold was dropped. Deletion is immediate; the typed confirmation is the safeguard
2 Should the UI block a leader removing 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