Groups are persistent bands of survivors that players found themselves. They replace gangs: EndCore has no gang system. A group has a name, a short tag, a roster and ranks, and it survives restarts. Players manage their group from the en-party menu or with the /group command.
How groups work
- Membership belongs to a character (citizenid), not an account. A character can be in one group at a time.
- Groups are cached in memory at start and every change is written through to the database.
- The founder becomes the leader. The leader holds the highest rank and every permission.
- When a leader leaves, the highest-ranked member takes over (the earliest joiner wins a tie).
- A group with no members left is deleted.
- Deleting a character removes them from their group first, so leadership passes on properly.
- The player's state bag carries their group id:
Player(source).state.group. It is replicated, so clients can read it too.
Settings
Settings live in shared/groups.lua.
| Key | Default | What it does |
|---|---|---|
maxMembers | 20 | Largest roster allowed |
foundingCost | 0 | Cash taken from the founder. 0 makes founding free. |
inviteTimeout | 60 | Seconds before an invite expires |
name | { min = 3, max = 24 } | Name length limits |
tag | { min = 2, max = 4 } | Tag length limits |
ranks | see below | Rank names and permissions |
Default ranks:
| Grade | Rank | Permissions |
|---|---|---|
| 0 | Recruit | none |
| 1 | Member | none |
| 2 | Officer | invite, kick |
| 3 | Leader | invite, kick, promote (and everything else) |
Rules
Founding a group
- The name must be 3–24 characters, start with a letter or digit, and contain only letters, digits, spaces, apostrophes and hyphens.
- The tag is uppercased and must be 2–4 letters or digits.
- Name and tag must both be unique.
foundingCostis taken from the founder's cash and refunded if the group cannot be saved.
Ranks
kickandpromoteonly work on members ranked below you.- You can only assign grades below your own.
- Only the leader can transfer leadership. The old leader drops one rank.
Invites
- Invites expire after
inviteTimeoutseconds. - Pending invites are cleared when either player disconnects.
Exports
Actions return true (sometimes with a result) or false, message. The message is written to be shown straight to the player.
| Export | Arguments | Returns |
|---|---|---|
GetGroup(id) | id | { id, name, tag, created, members, maxMembers, ranks, leaderGrade } or nil |
GetPlayerGroup(source) | source | { id, name, tag, grade, rank, isleader } or nil |
GetGroupByCitizenId(citizenid) | citizenid | Same summary, or nil |
GetGroupMembers(id) | id | Array of { citizenid, name, grade, rank, online, source?, joined } |
GetOnlineGroupMembers(id) | id | Array of sources |
IsInGroup(source, id?) | source, id | boolean. Without id, true if they are in any group. |
AreInSameGroup(a, b) | two sources | boolean |
CreateGroup(source, name, tag) | source, name, tag | true, groupId or false, msg |
InviteToGroup(source, targetSource) | source, targetSource | true or false, msg |
AcceptGroupInvite(source) | source | true or false, msg |
DeclineGroupInvite(source) | source | true or false, msg |
LeaveGroup(source) | source | true or false, msg |
KickFromGroup(source, citizenid) | source, citizenid | true or false, msg |
SetGroupGrade(source, citizenid, grade) | source, citizenid, grade | true or false, msg |
TransferGroup(source, citizenid) | source, citizenid | true or false, msg |
DisbandGroup(source) | source | true or false, msg |
GetGroupMembers is sorted by grade (highest first), then online members first, then by name.
The group summary is also attached to PlayerData.group at login. It is not stored on the character row.
Callbacks
en-core registers two callbacks that the en-party group menu uses. You can use them to build your own group UI.
| Callback | Arguments | Returns |
|---|---|---|
encore:groups:get | none | { group = snapshot or false, invite = { id, name, tag, from, remaining } or false, rules } |
encore:groups:action | action, data | { ok = true, result } or { ok = false, error } |
Actions for encore:groups:action:
| Action | Data |
|---|---|
create | { name, tag } |
invite | { target } |
accept, decline, leave, disband | none |
kick | { citizenid } |
grade | { citizenid, grade } |
transfer | { citizenid } |
See Callbacks for how to call them from the client.
Commands
| Command | Who | Usage |
|---|---|---|
/group | Everyone | /group create TAG Name, /group invite ID, /group accept, /group decline, /group leave, /group info |
/groupdisband | group.admin | /groupdisband <id>. The id is shown in /charinfo. |
Kicking, changing ranks and transferring leadership are only in the en-party menu.
Events
| Side | Event | Payload |
|---|---|---|
| Server | encore:server:onGroupUpdate | source, summary (or nil when they left) |
| Server | encore:server:groupCreated | groupId, source |
| Server | encore:server:groupInvite | targetSource, groupId, fromSource |
| Server | encore:server:groupJoined | groupId, source |
| Server | encore:server:groupLeft | groupId, source |
| Server | encore:server:groupDisbanded | groupId |
| Client (net) | encore:client:onGroupUpdate | summary or nil |
| Client (net) | encore:client:groupInvite | { id, name, tag, from, timeout } |
| Client (net) | encore:client:groupRosterChanged | groupId |
| Client (local) | encore:client:groupChanged | summary or nil |
Examples
Found a group from a custom NPC and show the error message if it fails:
local ok, result = exports['en-core']:CreateGroup(source, 'Dust Walkers', 'DUST')
if not ok then
encore.notify(source, { description = result, type = 'error' })
endNo friendly fire between group members:
if exports['en-core']:AreInSameGroup(attacker, victim) then
return
endMessage every online member of a player's group:
local group = exports['en-core']:GetPlayerGroup(source)
if group then
for _, member in ipairs(exports['en-core']:GetOnlineGroupMembers(group.id)) do
encore.notify(member, { description = ('[%s] Supply drop marked on your map.'):format(group.tag) })
end
endShow an invite prompt on the client:
RegisterNetEvent('encore:client:groupInvite', function(invite)
print(('%s invited you to [%s] %s. Expires in %ds.'):format(invite.from, invite.tag, invite.name, invite.timeout))
end)Compatibility
QB scripts that read PlayerData.gang see the player's group presented as a gang when the QB bridge is active. See Compatibility bridges.