> For the complete documentation index, see [llms.txt](/llms.txt)

# GS2-Guild

ギルド機能



複数のプレイヤーでチームを作成し、一緒になんらかの目標に向かって活動するための機能がギルド機能です。
GS2-Guild はギルドメンバーの管理と、ギルドメンバーが実行できる権限管理を行うためのマイクロサービスです。

GS2-Guild において「ギルド」は一人のプレイヤーとして処理され、ギルドユーザーと呼びます。
ギルドメンバーはギルドユーザーとして情報を取得・更新可能なフェデレーションを行うことで、ギルドメンバーが共通のプロパティを操作可能な状態を実現します。

例えば `プレイヤーA` `プレイヤーB` が所属している `ギルドA` があったと仮定します。
`プレイヤーレベル` や `スタミナ` といった一般的な情報が `プレイヤーA` や `プレイヤーB` に紐づいて管理されることは容易に想像できるでしょう。
これと同じように `ギルドA` に紐づいて `ギルドレベル` のようなプロパティを紐づけて管理することも容易に想像できるでしょう。

GS2-Guild の思想の特徴的な部分として `プレイヤーA` `プレイヤーB` と `ギルドA` の明確な区別はなく一律 `ユーザー` と捉えて処理されることにあります。
そのため、ギルドが持つプロパティには GS2 のあらゆるユーザーデータを扱うマイクロサービスを使用して管理できます。
たとえば、ギルドに GS2-Experience のランク・経験値を持たせることもできますし、GS2-Stamina のスタミナを持たせることもできますし、GS2-SkillTree のスキルツリーを持たせることもできます。

そして、GS2-Guild ではギルドメンバーを管理するとともに、ギルドメンバーが `ギルドユーザー` としてGS2のAPIを呼び出せるアクセストークンを発行する機能があります。
その際にギルドユーザーとしてどのAPIを呼び出せるのかをより細かく制御するためのアクセス権限制御機能があります。
この機能を利用すれば `ギルドマスター` の役職をもつプレイヤーのみが、ギルドユーザーとして GS2-Showcase の商品を購入できるような機能を実現できます。

## ギルドモデル

ギルド単位の設定は GuildModel で管理します。`defaultMaximumMemberCount` と `maximumMemberCount` でメンバーの初期値と上限を定め、`inactivityPeriodDays` にギルドマスターの無活動期間を指定すると自動的に後継者を選出できます。`rejoinCoolTimeMinutes` で脱退後の再参加クールタイム、`maxConcurrentJoinGuilds` や `maxConcurrentGuildMasterCount` で同時参加ギルド数やギルドマスターの人数制限も設定可能です。

## ギルドメンバー

ギルドに所属しているプレイヤーを指します

## ロール

ギルドに所属しているプレイヤーの役職に相当するエンティティで、実行可能なAPIの種類を定義します。
1つのギルドモデルに最大10種類のロールを定義できます。

### 権限設定

ロールには GS2-Identifier の[ポリシードキュメント]()を定義できます。
ギルドユーザーに変わる前の権限 && ロールの権限 の範囲でAPIを呼び出すことができます。

つまり、ロールに強力な権限を設定したとしても、元々のギルドユーザーとしてアクセスしている時に呼び出せなかったAPIを呼び出せるようにはなりません。

#### ポリシードキュメントの例

*ギルドマスターに相当する最小のポリシー*

```json
{
  "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": ["*"]
    }
  ]
}
```

*ギルドユーザーに相当する最小のポリシー*

```json
{
  "Version": "2016-04-01",
  "Statements": [
    {
      "Effect": "Allow",
      "Actions": [
        "Gs2Guild:Describe*",
        "Gs2Guild:Get*",
        "Gs2Guild:PromoteSeniorMember",
        "Gs2Gateway:SetUserId"
      ],
      "Resources": ["*"]
    }
  ]
}
```

#### 実践的な例

権限は GS2-Identifier のポリシードキュメントフォーマットを用いて定義します。
具体的な例として、ギルドの管理権限とギルドユーザーの GS2-Experience のAPIを呼び出す権限を付与するとしましょう。

```json
{
  "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": ["*"]
    }
  ]
}
```

前半の 「Gs2Guild」で始まる部分がギルドユーザーとして実行可能なAPIを宣言する部分で、メンバー管理に必要なAPIが指定されています。
「Gs2Gateway:SetUserId」にはギルドユーザーに関する通知を受け取れるようにする権限設定です。
この宣言がないと通知を受け取ることができず、ギルドへの参加申請が届いたことや、ギルドユーザーに関するプロパティの変化をアプリが知ることができなくなります。
最後の「Gs2Experience:*」が GS2-Experience の全てのAPIを呼び出す権限を付与する部分です。

さて、ここで 「Gs2Experience:*」と書いてしまうと、「Gs2Experience:AddExperienceByUserId」 のような任意のプレイヤーに任意の経験値を付与するAPIを呼び出せるようになるのでは？と不安に思うかもしれません。
しかし、ユーザーフェデレーションをする場合、フェデレーションする前のアクセストークンが持つ権限より強い権限を得ることはできません。
そのため、一般的なゲームプレイヤーが持つであろう「ApplicationAccess」権限では元々「Gs2Experience:AddExperienceByUserId」を呼び出すことはできませんので、ロールのポリシードキュメントに全てのAPIを呼び出せるように記述したとしても、「Gs2Experience:AddExperienceByUserId」を呼び出せるようにはなりません。

GS2-Showcase でギルドマスターだけがギルドユーザーとして商品を買えるようにしたければ、ギルドマスターのロールにだけ「Gs2Showcase:Buy」をつけるといった対応をすることで細やかな権限管理ができます。

## カスタムロール

ギルド固有のロールです。
プレイヤーに権限の組み合わせを自由に定義させたい場合に使用できます。

## 参加人数

ギルドには参加人数の制限を設定することができます。
参加人数の上限はギルド単位で引き上げることができ、ギルドレベルの上昇とともに参加人数の上限を引き上げるような実装が可能です。

## 参加方針

ギルドごとに参加方針を設定できます。
「自由参加」と「承認制」を設定できます。

### 自由参加

参加リクエストを出して、ギルドメンバーに空きがある場合は即時参加が可能です。

### 承認制

参加リクエストを出してもすぐにはギルドメンバーにはなれません。
ギルドメンバーの中で承認権限をもつプレイヤーに承認してもらうことでギルドメンバーになることができます。

## ギルドの検索

### ギルドの表示名

ギルドの表示名として登録した文字列の部分一致で検索することができます。

### 属性値

ギルドには最大5種類の属性値を設定可能です。
属性値は整数値のみで、ギルドの検索条件に使用することができます。

ギルドを検索する際には検索条件として、各属性値に対して最大10種の値で絞り込むことができます。
属性値を大小判定で絞り込むことはできません。

検索条件に複数の属性値を指定した場合は、AND 判定で検索されます。OR で検索する方法はありません。

### メンバーが最大に達しているギルドを検索結果に含むか

真偽値で指定できます。true を指定するとメンバーが最大値に達しているギルドも検索結果に含みます。
ちなみに、メンバーが最大値に達しているギルドに対しても参加リクエストを出すことは可能です。

### 検索対象

検索対象には過去24時間以内に更新のあったギルドのみが含まれます。
これはアクティブではないギルドを対象から除外しつつ、検索に必要なコストを最小にする目的でこのような仕様になっています。

ギルドマスターは特に変更点がなくても1日一回はギルド情報を更新するような実装にしてください。

なお、更新にはメンバーの増減やロールの割り当ても含まれます。

## ギルド参加のクールダウン

ギルド参加のクールダウンのパラメーターを設定すると、どこかのギルドから離脱した後クールダウンに設定した時間（分単位）が経過するまで他のギルドへの参加リクエストを出すことができません。

## 複数ギルドへの参加

プレイヤーは複数のギルドに同時に参加できます。
参加できるギルドの数に制約を設けたい場合は、アプリケーションで制御してください。

## ギルドマスターが引退した時の緊急措置

唯一のギルドマスターがなんの前触れもなく引退してしまった場合、ギルドの維持が困難になります。
このような状態に陥った時の緊急措置を用意しています。

### ギルドマスターの最終アクティブ時間の取得

GetLastGuildMasterActivity 関数を利用すると、ギルドマスターロールを所有しているプレイヤーが最後に Assume した日時を取得できます。
この日時を確認することでギルドマスターロールを持つ「誰」が「いつ」アクセスしたのが最後のアクセスかを判断することができます。

### 緊急措置の有効化日数

GuildModel には緊急措置を有効化するために待つ必要がある日数を定義できます。
ギルドマスターの最終アクティブ時間から、ここで定めた日数が経過することで緊急措置を実行できるようになります。

### 緊急措置の実行

緊急措置の実行は PromoteSeniorMember を呼び出すことで実行できます。
このAPIを呼び出すと、ギルドメンバーの中で最も参加日時が古いプレイヤーを新しいギルドマスターに昇格させます。
この時、最終アクティブ時間も昇格したプレイヤーがアクティブになったとして更新されます。つまり、この古参メンバーも長期間アクティブではない場合は追加で一定期間を待つ必要があります。

### ギルドマスター交代の通知

GS2-Guild 自体にはギルドマスター交代に関する通知を行う仕組みはありません。
ただし、ギルドメンバーのロールが更新された時に実行されるスクリプト は定義可能ですので、スクリプトからギルドチャットに書き込むなどの追加の処理をすることで、ギルドメンバーがギルドマスター交代に関する情報を知ることができます。

## スクリプトトリガー

ネームスペースに `createGuildScript` ・ `updateGuildScript` ・ `joinGuildScript` ・ `receiveJoinRequestScript` ・ `leaveGuildScript` ・ `changeRoleScript` ・ `deleteGuildScript` を設定すると、ギルド作成や更新、参加・離脱、ロール変更など各処理の前後でカスタムスクリプトを実行できます。スクリプトは同期実行に加えて Amazon EventBridge を利用した非同期実行も選択できます。

設定できる主なイベントトリガーとスクリプト設定名は以下の通りです。

- `createGuildScript`（完了通知: `createGuildDone`）: ギルド作成の前後
- `updateGuildScript`（完了通知: `updateGuildDone`）: ギルド情報更新の前後
- `joinGuildScript`（完了通知: `joinGuildDone`）: ギルド参加の前後
- `receiveJoinRequestScript`（完了通知: `receiveJoinRequestDone`）: 参加リクエスト受付の前後
- `leaveGuildScript`（完了通知: `leaveGuildDone`）: ギルド離脱の前後
- `changeRoleScript`（完了通知: `changeRoleDone`）: ロール変更の前後
- `deleteGuildScript`（完了通知: `deleteGuildDone`）: ギルド削除の前後

## プッシュ通知
設定できる主なプッシュ通知と設定名は以下の通りです。

- `changeNotification`: ギルド情報変更時に通知
- `joinNotification`: メンバー参加時に通知
- `leaveNotification`: メンバー離脱時に通知
- `changeMemberNotification`: メンバー情報変更時に通知
- `receiveRequestNotification`: 参加リクエスト受付時に通知
- `removeRequestNotification`: 参加リクエスト削除時に通知

いずれも GS2-Gateway 経由でリアルタイムに通知でき、オフライン端末へのモバイルプッシュ転送も指定できます。

## トランザクションアクション

GS2-Guild では以下のトランザクションアクションを提供しています。

- 検証アクション: ギルドメンバーの包含確認（特定のユーザーがメンバーか）、最大参加可能人数の検証
- 消費アクション: 最大参加可能人数の減算
- 入手アクション: 最大参加可能人数の加算、最大参加可能人数の設定

「ギルドメンバーの包含確認」を検証アクションとして利用することで、特定のギルドに所属しているメンバーのみが受け取れる報酬や、特定のギルドメンバーではない場合のみ実行可能な処理、といった制限をトランザクション内に組み込むことが可能になります。これにより、ギルド対抗イベントの報酬配布や、ギルド未所属者限定の勧誘キャンペーンなどを安全に実装できます。

## マスターデータ運用
マスターデータを登録することでマイクロサービスで利用可能なデータや振る舞いを設定できます。

マスターデータの種類には以下があります。

- `GuildModel`: メンバー上限や再参加クールタイムなどの設定
- `RoleModel`: ギルド内権限を定義するロール

マスターデータの登録はマネージメントコンソールから登録する他、GitHubからデータを反映したり、GS2-Deployを使ってCIから登録するようなワークフローを組むことが可能です。

## 実装例

### ギルドを作成



**Unity**
```csharp

    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();
```
**Unreal Engine**
```cpp

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


### ギルドを検索



**Unity**
```csharp

    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();
```
**Unreal Engine**
```cpp

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


### ギルドに参加リクエストを送信

ギルドの参加方針が「自由参加」の場合は直ちにギルドメンバーになります



**Unity**
```csharp

    var result = await gs2.Guild.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SendRequestAsync(
        guildModelName: "guild-0002",
        targetGuildName: "guild-0002"
    );
```
**Unreal Engine**
```cpp

    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;
    }
```


### 送信した参加リクエストの一覧を取得



**Unity**
```csharp

    var items = await gs2.Guild.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).SendRequestsAsync(
        guildModelName: "guild-0002"
    ).ToListAsync();
```
**Unreal Engine**
```cpp

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


### 参加リクエストを取り下げ



**Unity**
```csharp

    var result = await gs2.Guild.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).CancelRequestAsync(
        guildModelName: "guild-0002",
        targetGuildName: "guild-0002"
    );
```
**Unreal Engine**
```cpp

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


### ギルドユーザーとしてアクセスするためのゲームセッションを取得



**Unity**
```csharp

    var guildGameSession = await gs2.Guild.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).AssumeAsync(
        guildModelName: "guild-model-0001",
        guildName: "guild-0001"
    );
```
**Unreal Engine**
```cpp

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


### 受信した参加リクエストの一覧を取得



**Unity**
```csharp

    var domain = gs2.Guild.Namespace(
        namespaceName: "namespace-0001"
    ).GuildGameSession(
        guildModelName: "guild-0001",
        guildGameSession: guildGameSession
    );
    var items = await domain.ReceiveRequestsAsync(
    ).ToListAsync();
```
**Unreal Engine**
```cpp

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


### 参加リクエストを承認



**Unity**
```csharp

    var result = await gs2.Guild.Namespace(
        namespaceName: "namespace-0001"
    )->GuildGameSession(
        "guild-0001", // guildModelName
        GuildGameSession // guildGameSession
    ).ReceiveMemberRequest(
        fromUserId: "user-0001"
    ).AcceptAsync(
    );
```
**Unreal Engine**
```cpp

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


### 参加リクエストを否認



**Unity**
```csharp

    var result = await gs2.Guild.Namespace(
        namespaceName: "namespace-0001"
    )->GuildGameSession(
        "guild-0001", // guildModelName
        GuildGameSession // guildGameSession
    ).ReceiveMemberRequest(
        fromUserId: "user-0001"
    ).RejectAsync(
    );
```
**Unreal Engine**
```cpp

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


## 詳細なリファレンス

[GS2-Guild API リファレンス](../../api_reference/guild)



