EndCore Framework

Groups

Player-founded groups of survivors in EndCore, with ranks, invites, leadership rules, exports, callbacks and events.

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.

KeyDefaultWhat it does
maxMembers20Largest roster allowed
foundingCost0Cash taken from the founder. 0 makes founding free.
inviteTimeout60Seconds before an invite expires
name{ min = 3, max = 24 }Name length limits
tag{ min = 2, max = 4 }Tag length limits
rankssee belowRank names and permissions

Default ranks:

GradeRankPermissions
0Recruitnone
1Membernone
2Officerinvite, kick
3Leaderinvite, 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.
  • foundingCost is taken from the founder's cash and refunded if the group cannot be saved.

Ranks

  • kick and promote only 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 inviteTimeout seconds.
  • 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.

ExportArgumentsReturns
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)citizenidSame summary, or nil
GetGroupMembers(id)idArray of { citizenid, name, grade, rank, online, source?, joined }
GetOnlineGroupMembers(id)idArray of sources
IsInGroup(source, id?)source, idboolean. Without id, true if they are in any group.
AreInSameGroup(a, b)two sourcesboolean
CreateGroup(source, name, tag)source, name, tagtrue, groupId or false, msg
InviteToGroup(source, targetSource)source, targetSourcetrue or false, msg
AcceptGroupInvite(source)sourcetrue or false, msg
DeclineGroupInvite(source)sourcetrue or false, msg
LeaveGroup(source)sourcetrue or false, msg
KickFromGroup(source, citizenid)source, citizenidtrue or false, msg
SetGroupGrade(source, citizenid, grade)source, citizenid, gradetrue or false, msg
TransferGroup(source, citizenid)source, citizenidtrue or false, msg
DisbandGroup(source)sourcetrue 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.

CallbackArgumentsReturns
encore:groups:getnone{ group = snapshot or false, invite = { id, name, tag, from, remaining } or false, rules }
encore:groups:actionaction, data{ ok = true, result } or { ok = false, error }

Actions for encore:groups:action:

ActionData
create{ name, tag }
invite{ target }
accept, decline, leave, disbandnone
kick{ citizenid }
grade{ citizenid, grade }
transfer{ citizenid }

See Callbacks for how to call them from the client.

Commands

CommandWhoUsage
/groupEveryone/group create TAG Name, /group invite ID, /group accept, /group decline, /group leave, /group info
/groupdisbandgroup.admin/groupdisband <id>. The id is shown in /charinfo.

Kicking, changing ranks and transferring leadership are only in the en-party menu.

Events

SideEventPayload
Serverencore:server:onGroupUpdatesource, summary (or nil when they left)
Serverencore:server:groupCreatedgroupId, source
Serverencore:server:groupInvitetargetSource, groupId, fromSource
Serverencore:server:groupJoinedgroupId, source
Serverencore:server:groupLeftgroupId, source
Serverencore:server:groupDisbandedgroupId
Client (net)encore:client:onGroupUpdatesummary or nil
Client (net)encore:client:groupInvite{ id, name, tag, from, timeout }
Client (net)encore:client:groupRosterChangedgroupId
Client (local)encore:client:groupChangedsummary or nil

Examples

Found a group from a custom NPC and show the error message if it fails:

lua
local ok, result = exports['en-core']:CreateGroup(source, 'Dust Walkers', 'DUST')
if not ok then
    encore.notify(source, { description = result, type = 'error' })
end

No friendly fire between group members:

lua
if exports['en-core']:AreInSameGroup(attacker, victim) then
    return
end

Message every online member of a player's group:

lua
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
end

Show an invite prompt on the client:

lua
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.