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의 종류를 정의합니다. 하나의 길드 모델에 최대 10종류의 역할을 정의할 수 있습니다.
권한 설정
역할에는 GS2-Identifier의 정책 문서를 정의할 수 있습니다. 길드 사용자로 전환하기 전의 권한 && 역할의 권한 범위 내에서 API를 호출할 수 있습니다.
즉, 역할에 강력한 권한을 설정하더라도, 원래 길드 사용자로 접근하고 있을 때 호출할 수 없었던 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"
],
"Resources": ["*"]
}
]
}길드 사용자에 해당하는 최소한의 정책
{
"Version": "2016-04-01",
"Statements": [
{
"Effect": "Allow",
"Actions": [
"Gs2Guild:Describe*",
"Gs2Guild:Get*",
"Gs2Guild:PromoteSeniorMember",
"Gs2Gateway:SetUserId"
],
"Resources": ["*"]
}
]
}실전 예시
권한은 GS2-Identifier의 정책 문서 형식을 사용하여 정의합니다. 구체적인 예로, 길드 관리 권한과 길드 사용자의 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": ["*"]
}
]
}앞부분의 ‘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시간 이내에 갱신된 길드만 포함됩니다. 이는 활성 상태가 아닌 길드를 대상에서 제외하면서, 검색에 필요한 비용을 최소화하려는 목적으로 이러한 사양으로 되어 있습니다.
길드 마스터는 특별히 변경 사항이 없더라도 하루에 한 번은 길드 정보를 갱신하도록 구현해 주십시오.
참고로, 갱신에는 멤버의 증감이나 역할 할당도 포함됩니다.
길드 참가 쿨다운
길드 참가 쿨다운 파라미터를 설정하면, 어딘가의 길드에서 탈퇴한 후 쿨다운으로 설정한 시간(분 단위)이 경과할 때까지 다른 길드에 참가 리퀘스트를 보낼 수 없습니다.
여러 길드 참가
플레이어는 여러 길드에 동시에 참가할 수 있습니다. 참가 가능한 길드 수에 제약을 두고 싶은 경우에는 애플리케이션에서 제어해 주십시오.
길드 마스터가 은퇴했을 때의 긴급 조치
유일한 길드 마스터가 아무런 예고 없이 은퇴해 버린 경우, 길드 유지가 어려워집니다. 이러한 상황에 빠졌을 때를 위한 긴급 조치를 마련해 두고 있습니다.
길드 마스터의 최종 활동 시간 조회
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에서 등록하는 워크플로우를 구성하는 것도 가능합니다.
구현 예제
길드 생성
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
TOptional<int32>(), // attribute2
TOptional<int32>(), // attribute3
TOptional<int32>(), // attribute4
TOptional<int32>(), // attribute5
nullptr, // customRoles
TOptional<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();var domain = ez.guild.namespace_(
"namespace-0001"
).me(game_session)
var async_result = await domain.create_guild(
"guild-model-0001", # guild_model_name
"My Guild", # display_name
"anybody", # join_policy
1, # attribute1
null, # attribute2
null, # attribute3
null, # attribute4
null, # attribute5
null, # metadata
null, # member_metadata
null, # custom_roles
null # guild_member_default_role
)
if async_result.error != null:
push_error(str(async_result.error))
return
var result = async_result.result길드 검색
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());
}var domain = ez.guild.namespace_(
"namespace-0001"
).me(game_session)
var async_result = await domain.search_guilds(
"guild-model-0001", # guild_model_name
null, # display_name
null, # attributes1
null, # attributes2
null, # attributes3
null, # attributes4
null, # attributes5
null, # join_policies
null, # include_full_members_guild
null # order_by
).load()
if async_result.error != null:
push_error(str(async_result.error))
return
var result = async_result.result길드에 참가 리퀘스트 전송
길드의 참가 방침이 ‘자유 참가’인 경우에는 즉시 길드 멤버가 됩니다
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;
}var domain = ez.guild.namespace_(
"namespace-0001"
).me(game_session)
var async_result = await domain.send_request(
"guild-0002", # guild_model_name
"guild-0002", # target_guild_name
null # metadata
)
if async_result.error != null:
if async_result.error.type == "MaximumMembersReachedException":
# 멤버 수가 상한에 도달했습니다.
pass
if async_result.error.type == "MaximumJoinedGuildsReachedException":
# 동시에 참가할 수 있는 길드 수가 최대값에 도달했습니다.
pass
if async_result.error.type == "MaximumReceiveRequestsReachedException":
# 길드에 대한 참가 리퀘스트 수가 상한에 도달했습니다.
pass
if async_result.error.type == "MaximumSendRequestsReachedException":
# 자신이 보낸 참가 리퀘스트 수가 상한에 도달했습니다.
pass
if async_result.error.type == "DotMeetJoinRequirementsException":
# 길드 참가 조건을 충족하지 않습니다.
pass
push_error(str(async_result.error))
return
var result = async_result.result전송한 참가 리퀘스트 목록 조회
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());
}var iterator = ez.guild.namespace_(
"namespace-0001"
).me(
game_session
).send_requests(
"guild-0002"
)
var async_result = await iterator.load()
if async_result.error != null:
# 오류 처리
push_error(str(async_result.error))
return
var items = async_result.result참가 리퀘스트 철회
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();var domain = ez.guild.namespace_(
"namespace-0001"
).me(game_session)
var async_result = await domain.cancel_request(
"guild-0002", # guild_model_name
"guild-0002" # target_guild_name
)
if async_result.error != null:
push_error(str(async_result.error))
return
var result = async_result.result길드 사용자로서 접근하기 위한 게임 세션 취득
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();var domain = ez.guild.namespace_(
"namespace-0001"
).me(game_session)
var async_result = await domain.assume(
"gateway-namespace-0001", false, "guild-model-0001", "guild-0001"
)
if async_result.error != null:
push_error(str(async_result.error))
return
var guild_game_session = async_result.result수신한 참가 리퀘스트 목록 조회
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());
}var iterator = ez.guild.namespace_(
"namespace-0001"
).guild_game_session(
"guild-0001",
guild_game_session
).receive_requests(
)
var async_result = await iterator.load()
if async_result.error != null:
# 오류 처리
push_error(str(async_result.error))
return
var items = async_result.result참가 리퀘스트 승인
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();var domain = ez.guild.namespace_(
"namespace-0001"
).guild_game_session(
"guild-0001", guild_game_session
).receive_member_request("user-0001")
var async_result = await domain.accept_request()
if async_result.error != null:
push_error(str(async_result.error))
return참가 리퀘스트 거부
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();var domain = ez.guild.namespace_(
"namespace-0001"
).guild_game_session(
"guild-0001", guild_game_session
).receive_member_request("user-0001")
var async_result = await domain.reject_request()
if async_result.error != null:
push_error(str(async_result.error))
return