GS2-Guild

guild function

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 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 describes all API calls Therefore, 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.

Include guilds with the maximum number of members in the 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 of the last Assume by the player who owns the guild master role. By checking this date and time, it is possible to determine “who” who has the guild master role and “when” they last accessed the site.

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.

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();

Advanced Reference.