GS2-Guild
The guild function is a feature that allows multiple players to create a team and work together toward some goal. GS2-Guild is a microservice that manages guild members and the privileges that guild members have.
In GS2-Guild, a “guild” is treated as a single player and referred to as a guild user. Guild members are federated to be able to retrieve and update information as guild users, so that guild members can manipulate common properties.
For example, suppose there is a guild A with player A player B as members.
It is easy to imagine that common information such as player level and stamina would be managed in conjunction with player A and player B.
In the same way, it is easy to imagine that properties such as guild level could be managed by associating them with guild A.
One of the characteristics of the GS2-Guild philosophy is that there is no clear distinction between Player A Player B and Guild A, and they are all treated as users.
Therefore, the properties that a guild has can be managed using microservices that handle all user data in GS2.
For example, a guild can have GS2-Experience ranks/experience, or GS2-Stamina stamina, or GS2-SkillTree skill trees.
And GS2-Guild has the ability to manage guild members and issue access tokens that allow guild members to call GS2’s API as guild users.
In this case, there is an access privilege control function to control more precisely which APIs can be called as a guild user.
By using this function, only players with the position of guild master can purchase GS2-Showcase products as a guild user.
Guild Model
Guild settings are managed via the GuildModel. Set the initial and maximum member counts with defaultMaximumMemberCount and maximumMemberCount. Specify the guild master’s inactivity period with inactivityPeriodDays to enable automatic succession. rejoinCoolTimeMinutes sets the cooldown period for rejoining after leaving. maxConcurrentJoinGuilds and maxConcurrentGuildMasterCount also allow setting limits on the number of guilds a member can join simultaneously and the number of guilds a member can lead.
Guild Members
Refers to players who belong to a guild
Roles
An entity that corresponds to the position of a player belonging to a guild, and defines the type of API that can be executed. Up to 10 types of roles can be defined in one guild model.
Permission Settings
Roles can define policy document of GS2-Identifier. You can call the API within the scope of privileges && privileges of the role before it turns into a guild user.
In other words, even if you set strong privileges on a role, it will not allow you to call APIs that you could not call when accessing as the original guild user.
Example policy document
The minimum policy equivalent to a guild master.
{
"Version": "2016-04-01",
"Statements": [
{
"Effect": "Allow",
"Actions": [
"Gs2Guild:Describe*",
"Gs2Guild:Get*",
"Gs2Guild:AcceptRequest",
"Gs2Guild:RejectRequest",
"Gs2Guild:DeleteMember",
"Gs2Guild:DescribeIgnoreUsers",
"Gs2Guild:AddIgnoreUser",
"Gs2Guild:GetIgnoreUser",
"Gs2Guild:DeleteIgnoreUser",
"Gs2Gateway:SetUserId"
],
"Resources": ["*"]
}
]
}Minimum policy equivalent to guild users
{
"Version": "2016-04-01",
"Statements": [
{
"Effect": "Allow",
"Actions": [
"Gs2Guild:Describe*",
"Gs2Guild:Get*",
"Gs2Guild:PromoteSeniorMember",
"Gs2Gateway:SetUserId"
],
"Resources": ["*"]
}
]
}Practical Examples
Authorizations are defined using the GS2-Identifier policy document format. As a concrete example, let’s say you want to grant administrative privileges for a guild and privileges for guild users to call the GS2-Experience API.
{
"Version": "2016-04-01",
"Statements": [
{
"Effect": "Allow",
"Actions": [
"Gs2Guild:Describe*",
"Gs2Guild:Get*",
"Gs2Guild:AcceptRequest",
"Gs2Guild:RejectRequest",
"Gs2Guild:DeleteMember",
"Gs2Guild:DescribeIgnoreUsers",
"Gs2Guild:AddIgnoreUser",
"Gs2Guild:GetIgnoreUser",
"Gs2Guild:DeleteIgnoreUser",
"Gs2Gateway:SetUserId",
"Gs2Experience:*"
],
"Resources": ["*"]
}
]
}The first half, beginning with “Gs2Guild”, declares the APIs that can be executed as a guild user and specifies the APIs necessary for member management. “Gs2Gateway:SetUserId” is the permission setting to receive notifications about guild users. Without this declaration, notifications cannot be received and the application will not be able to know that a guild membership application has been received or that properties related to guild users have changed. The last part “Gs2Experience:*” is the part that grants the authority to call all APIs of GS2-Experience.
Now, if we write “Gs2Experience:*” here, you may wonder if it will be possible to call APIs that grant arbitrary experience values to arbitrary players, such as “Gs2Experience:AddExperienceByUserId”. You may be concerned. However, when performing user federation, you cannot gain privileges stronger than those held by the access token prior to federation. Therefore, the “ApplicationAccess” privilege that a typical game player would have originally cannot call “Gs2Experience:AddExperienceByUserId,” so even if the role’s policy document is written so that all APIs can be called, it will not be possible to call “Gs2Experience:AddExperienceByUserId”.
If you want to allow only the guild master to buy products as a guild user in GS2-Showcase, you can manage permissions in detail by adding “Gs2Showcase:Buy” only to the role of the guild master.
Custom Roles
Guild-specific roles. Can be used if you want to let players define any combination of privileges.
Number of participants
Guilds can set a limit on the number of participants. The maximum number of participants can be raised on a guild-by-guild basis, and can be implemented in such a way that the maximum number of participants is raised as the guild level increases.
Participation Policy
Each guild can set its own participation policy. You can set “Free Participation” or “Approval Only”.
Free Participation
You can request to join a guild and if there is a vacancy in the guild, you will be able to join immediately.
Approval System
You cannot become a guild member immediately after submitting a join request. You can only become a guild member after you are approved by a guild member who has the approval authority.
Search for guilds
Guild Display Name
You can search for guilds by matching the string registered as the guild’s display name.
Attribute values
Guilds can have up to 5 different attribute values. Attribute values can only be integer values and can be used for guild search conditions.
When searching for guilds, the search criteria can be narrowed down to a maximum of 10 different values for each attribute value. Attribute values cannot be narrowed down by a large or small value.
If multiple attribute values are specified as search criteria, the search will be performed using an AND decision; there is no way to search using an OR decision.
Whether to include guilds that have reached their maximum member capacity in search results
If true, guilds with the maximum number of members will be included in the search results. Note that it is still possible to submit a request to join a guild that has reached its maximum number of members.
Search Eligibility
Only guilds that have been updated in the past 24 hours are included in the search. This is done to minimize the cost of the search while excluding inactive guilds.
Guildmasters are encouraged to update their guild information once a day, even if there are no changes.
Updates may include member increases/decreases and role assignments.
Cooldown for joining a guild
If you set a guild join cooldown parameter, you will not be able to request to join another guild until the cooldown time (in minutes) has elapsed after you leave a guild.
Joining multiple guilds
A player can join multiple guilds simultaneously. If you wish to limit the number of guilds a player can join, please control this in your application.
Emergency measures when guild master retires
If the sole guildmaster retires without warning, it will be difficult to maintain the guild. We have emergency measures in place in the event of such a situation.
Obtain Guild Master’s last active time
The GetLastGuildMasterActivity function can be used to obtain the date and time when the player who owns the guild master role last performed an Assume.
By checking this date and time, it is possible to determine “who” among guild master role holders last accessed and “when” that last access was.
Days to Activate Emergency Action
GuildModel allows you to define the number of days you need to wait for an emergency action to take effect. The number of days defined here must elapse from the guild master’s last active time before the emergency action can be taken.
Emergency Action Execution
Emergency actions can be executed by calling PromoteSeniorMember. Calling this API will promote the oldest guild member with the oldest active time to the new guild master. At this time, the last active time is also updated as if the promoted player was active. This means that this old member will also have to wait an additional period of time if he/she has been inactive for a long time.
Guild Master Change Notification
GS2-Guild itself does not have a mechanism for notifying about guild master changes. However, a script can be defined to be executed when a guild member’s role is updated, so that guild members can be notified of guild master changes by writing to guild chat or by other additional processing from the script.
Script Triggers
Setting createGuildScript, updateGuildScript, joinGuildScript, receiveJoinRequestScript, leaveGuildScript, changeRoleScript, and deleteGuildScript in the namespace allows custom scripts to be executed before and after operations such as guild creation, updates, joining, leaving, and role changes. Scripts support both synchronous execution and asynchronous execution via Amazon EventBridge.
Main event triggers and script setting names are:
createGuildScript(completion notification:createGuildDone): before and after guild creationupdateGuildScript(completion notification:updateGuildDone): before and after guild information updatesjoinGuildScript(completion notification:joinGuildDone): before and after joining a guildreceiveJoinRequestScript(completion notification:receiveJoinRequestDone): before and after receiving join requestsleaveGuildScript(completion notification:leaveGuildDone): before and after leaving a guildchangeRoleScript(completion notification:changeRoleDone): before and after role changesdeleteGuildScript(completion notification:deleteGuildDone): before and after guild deletion
Push Notifications
The main push notifications and their configuration names are as follows:
changeNotification: notifies when guild information changesjoinNotification: notifies when a member joinsleaveNotification: notifies when a member leaveschangeMemberNotification: notifies when member information changesreceiveRequestNotification: notifies when a join request is receivedremoveRequestNotification: notifies when a join request is removed
All notifications can be sent in real time via GS2-Gateway, and mobile push forwarding to offline devices can also be configured.
Transaction Actions
GS2-Guild provides the following transaction actions:
- Verify Actions: Verify member inclusion (whether a specific user is a member), verify maximum member count
- Consume Actions: Subtract maximum member count
- Acquire Actions: Add maximum member count, set maximum member count
By using “Verify member inclusion” as a verify action, it is possible to integrate restrictions into transactions, such as rewards that only members of a specific guild can receive, or processes that can only be executed if a user is not a member of a specific guild. This allows for safe implementation of guild competition rewards or recruitment campaigns restricted to non-members.
Master Data Management
Registering master data allows you to configure data and behavior that can be used by the microservice.
Available master data types:
GuildModel: settings such as member limits and rejoin cooldownRoleModel: roles defining in-guild permissions
Master data can be registered through the management console, imported from GitHub, or registered from CI using GS2-Deploy.
Example implementation
Create a guild
var result = await gs2.Guild.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).CreateGuildAsync(
guildModelName: "guild-model-0001",
displayName: "My Guild",
joinPolicy: "anybody",
attribute1: 1,
attribute2: null,
attribute3: null,
attribute4: null,
attribute5: null,
customRoles: null,
guildMemberDefaultRole: null
);
var item = await result.ModelAsync(); const auto Future = Gs2->Guild->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->CreateGuild(
"guild-model-0001", // guildModelName
"My Guild", // displayName
"anybody", // joinPolicy
1, // attribute1
TOptinal<int32>(), // attribute2
TOptinal<int32>(), // attribute3
TOptinal<int32>(), // attribute4
TOptinal<int32>(), // attribute5
nullptr, // customRoles
TOptinal<FString>() // guildMemberDefaultRole
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
return false;
}
// obtain changed values / result values
const auto Future2 = Future->GetTask().Result()->Model();
Future2->StartSynchronousTask();
if (Future2->GetTask().IsError())
{
return Future2->GetTask().Error();
}
const auto Result = Future2->GetTask().Result();Search guilds
var items = await gs2.Guild.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).SearchGuildsAsync(
guildModelName: "guild-model-0001",
displayName: "My Guild",
attributes1: new int[] { 0, 1 },
attributes2: null,
attributes3: null,
attributes4: null,
attributes5: null,
joinPolicies: null,
includeFullMembersGuild = null
).ToListAsync(); const auto It = Gs2->Guild->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->SearchGuilds(
"guild-model-0001", // guildModelName
"My Guild", // displayName
MakeShared<TArray<int32>>([]{
TArray<int32> v;
v.Add(0);
v.Add(1);
return v;
}()), // attributes1
TSharedPtr<TArray<int32>>(), // attributes2
TSharedPtr<TArray<int32>>(), // attributes3
TSharedPtr<TArray<int32>>(), // attributes4
TSharedPtr<TArray<int32>>(), // attributes5
TSharedPtr<TArray<FString>>(), // joinPolicies
TOptional<bool>(true) // includeFullMembersGuild
);
TArray<Gs2::UE5::Guild::Model::FEzGuildPtr> Result;
for (auto Item : *It)
{
if (Item.IsError())
{
return false;
}
Result.Add(Item.Current());
}Send a join request to the guild
If the guild’s participation policy is “free to join”, you will immediately become a guild member
var result = await gs2.Guild.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).SendRequestAsync(
guildModelName: "guild-0002",
targetGuildName: "guild-0002"
); const auto Future = Gs2->Guild->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->SendRequest(
"guild-0002", // guildModelName
"guild-0002" // targetGuildName
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
return false;
}Get list of join requests sent
var items = await gs2.Guild.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).SendRequestsAsync(
guildModelName: "guild-0002"
).ToListAsync(); const auto It = Gs2->Guild->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->SendRequests(
"guild-0002" // guildModelName
);
TArray<Gs2::UE5::Guild::Model::FEzSendMemberRequestPtr> Result;
for (auto Item : *It)
{
if (Item.IsError())
{
return false;
}
Result.Add(Item.Current());
}Withdraw participation request
var result = await gs2.Guild.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).CancelRequestAsync(
guildModelName: "guild-0002",
targetGuildName: "guild-0002"
); const auto Future = Gs2->Guild->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->CancelRequest(
"guild-0002", // guildModelName
"guild-0002" // targetGuildName
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
return false;
}
const auto Result = Future->GetTask().Result();Get game session to access as guild user
var guildGameSession = await gs2.Guild.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).AssumeAsync(
guildModelName: "guild-model-0001",
guildName: "guild-0001"
); const auto Future = Gs2->Guild->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->Assume(
"guild-model-0001", // guildModelName
"guild-0001" // guildName
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
return false;
}
const auto GuildGameSession = Future->GetTask().Result();Get list of received join requests
var domain = gs2.Guild.Namespace(
namespaceName: "namespace-0001"
).GuildGameSession(
guildModelName: "guild-0001",
guildGameSession: guildGameSession
);
var items = await domain.ReceiveRequestsAsync(
).ToListAsync(); const auto It = Gs2->Guild->Namespace(
"namespace-0001" // namespaceName
)->GuildGameSession(
"guild-0001", // guildModelName
GuildGameSession // guildGameSession
)->ReceiveRequests(
);
TArray<Gs2::UE5::Guild::Model::FEzReceiveMemberRequestPtr> Result;
for (auto Item : *It)
{
if (Item.IsError())
{
return false;
}
Result.Add(Item.Current());
}Approve join request
var result = await gs2.Guild.Namespace(
namespaceName: "namespace-0001"
)->GuildGameSession(
"guild-0001", // guildModelName
GuildGameSession // guildGameSession
).ReceiveMemberRequest(
fromUserId: "user-0001"
).AcceptRequestAsync(
); const auto Future = Gs2->Guild->Namespace(
"namespace-0001" // namespaceName
)->GuildGameSession(
"guild-0001", // guildModelName
GuildGameSession // guildGameSession
)->ReceiveMemberRequest(
"user-0001" // fromUserId
)->AcceptRequest(
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
return false;
}
const auto Result = Future->GetTask().Result();Deny join request
var result = await gs2.Guild.Namespace(
namespaceName: "namespace-0001"
)->GuildGameSession(
"guild-0001", // guildModelName
GuildGameSession // guildGameSession
).ReceiveMemberRequest(
fromUserId: "user-0001"
).RejectRequestAsync(
); const auto Future = Gs2->Guild->Namespace(
"namespace-0001" // namespaceName
)->GuildGameSession(
"guild-0001", // guildModelName
GuildGameSession // guildGameSession
)->ReceiveMemberRequest(
"user-0001" // fromUserId
)->RejectRequest(
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError())
{
return false;
}
const auto Result = Future->GetTask().Result();