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

# GS2-Idle

방치 보상 기능




게임을 플레이하지 않은 기간에 따라 보상을 지급하는 구조를 구현합니다.
스마트폰 대상 게임에서는 "오랜만에 앱을 실행했더니 대량의 보상을 획득할 수 있었다"는 디자인이 플레이어의 재방문 촉진에 크게 기여합니다.
GS2-Idle은 이러한 방치 보상을 간단하게 도입하기 위한 마이크로서비스입니다.

```mermaid
graph LR
  Start["대기 시작"] --> Idle["방치 시간 경과"]
  Idle --> Prediction["획득 예정 보상 조회"]
  Prediction --> Receive["보상 수령"]
  Receive --> Start
```

## 카테고리

방치 보상은 여러 개를 준비할 수 있습니다.
플레이어는 카테고리별로 1개의 대기 시간을 가질 수 있습니다.

카테고리는 "캐릭터의 스태미나 자연 회복", "채굴장의 자동 채굴", "소재의 자동 생성" 등, 각각 독립된 방치 보상의 단위로 활용할 수 있습니다.

### 대기 시간

카테고리에는 대기 시간(몇 분마다 보상을 얻을 수 있는지)과 대기 시간 최댓값의 초깃값을 설정할 수 있습니다.
대기 시간의 최댓값은 플레이어별로 늘릴 수 있습니다. 보상 수령 후에 미소진된 대기 시간을 초기화할지 다음으로 이월할지는 `rewardResetMode`로 선택할 수 있습니다.

| `rewardResetMode` | 동작 |
| --- | --- |
| `Reset` | 보상 수령 시 경과 시간이 0으로 초기화됩니다. 획득 타이밍의 나머지 시간은 폐기됩니다. |
| `CarryOver` | 보상 수령 시 획득한 인터벌만큼 경과 시간에서 차감됩니다. 나머지 시간은 다음 대기에도 이어집니다. |

### 방치 보상

방치 보상에는 대기 시간이 일정 시간 경과하면 얻을 수 있는 아이템 목록을 정의합니다.
지급하는 보상은 "경험치+아이템"처럼 여러 개(최대 10개)를 설정할 수 있습니다.

또한, 대기 시간 내에서 보상 내용에 변화를 줄 수 있도록 여러 개의 보상 목록을 설정할 수 있습니다.

예를 들어, 10분 대기할 때마다 "경험치+10"과 "강화 소재 Lv.1 x 1"을 얻을 수 있습니다.
단, 60분마다인 타이밍에는 위 보상 대신 "경험치+20"과 "강화 소재 Lv.2 x 1"을 얻을 수 있습니다.
위와 같은 예시를 생각해 봅시다.

이 경우, 방치 보상에는 다음과 같은 테이블을 설정합니다.

-| 보상1    | 보상2
----|--------|------
1 | 경험치+10 | 강화 소재 Lv.1 x 1
2 | 경험치+10 | 강화 소재 Lv.1 x 1
3 | 경험치+10 | 강화 소재 Lv.1 x 1
4 | 경험치+10 | 강화 소재 Lv.1 x 1
5 | 경험치+10 | 강화 소재 Lv.1 x 1
6 | 경험치+20 | 강화 소재 Lv.2 x 1

이렇게 하면 경과 시간에 따라 보상 아이템 내용을 변경할 수 있습니다.
보상 내용은 순환하므로, 2시간 경과 후에는 "1,2,3,4,5,6,1,2,3,4,5,6" 순서의 아이템을 얻게 됩니다.

### 방치 보상의 랜덤 추첨

보상 내용에 좀 더 무작위성을 부여하고 싶은 경우가 있습니다.
그러한 경우에는 보상에 GS2-Lottery의 추첨 처리를 설정해 주세요.

기존의 GS2-Lottery에서는 추첨을 수행할 때까지 결과가 정해지지 않지만, GS2-Idle을 사용하는 경우에는 대기 시작 시점에 난수 시드를 생성하고
보상 계산 시 그 난수 시드를 이용해 추첨을 수행함으로써, 대기 도중에도 무작위로 추첨된 아이템 내용을 플레이어에게 제시할 수 있으며, 내용은 불변이 됩니다.
(GS2-Lottery의 경품 테이블을 변경하면 이 전제는 무너집니다)

### 대기 시간의 스케줄 관리

이벤트와 연동한 방치 보상을 구현할 수 있도록, 카테고리별로 GS2-Schedule의 이벤트와 연결할 수 있습니다.

이벤트 개최 기간을 2023-01-01 00:00 ~ 2023-02-01 00:00으로 하고, 2023-01-31 23:00부터 대기를 시작했다고 가정합시다.
이 경우, 2023-02-01 00:00이 되는 시점에 방치 시간 카운트가 정지합니다.

따라서 2023-02-01 01:00에 보상을 수령하는 경우도, 2023-02-01 09:00에 보상을 수령하는 경우도 내용은 동일합니다.

이벤트에 반복 설정이 있는 경우, 반복 횟수가 바뀌는 시점에 방치 시간이 초기화됩니다.
예를 들어 매주 월요일 00:00 ~ 화요일 00:00에 반복되는 이벤트의 경우, 다음 주 월요일 00:00이 되는 순간 전주의 보상을 수령하지 않았더라도 대기 시간은 초기화됩니다.

대기 시간의 스케줄과는 별도로, 보상 수령 가능 기간을 설정할 수 있습니다.
수령 가능 기간 외에도 Prediction으로 예정 보상을 가져올 수는 있지만, 수령 API를 호출하면 오류가 발생합니다.

## 상태

플레이어는 카테고리별로 1개의 `Status`를 보유합니다.
`Status`에는 다음과 같은 정보가 포함됩니다.

| 필드 | 설명 |
| --- | --- |
| `idleStartedAt` | 대기 시작 시각 |
| `idleMinutes` | 누적 방치 시간(분) |
| `nextRewardsAt` | 다음 보상 획득 타이밍 |
| `maximumIdleMinutes` | 이 플레이어에게 적용되는 방치 시간의 상한 |
| `randomSeed` | GS2-Lottery와 결합한 추첨용 고정 시드 |

## 구현 예제

### 대기 시작

처음으로 대기 시간 정보를 가져올 때 대기가 시작됩니다.



**Unity**
```csharp

    var item = await gs2.Idle.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        category: "category-0001"
    ).ModelAsync();
```
**Unreal Engine**
```cpp

    const auto Future = Gs2->Idle->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
        "category-0001" // categoryName
    )->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```
**Godot**
```gdscript

var domain = ez.idle.namespace_(
        "namespace-0001"
    ).me(game_session).status(
        "category-0001"
    )

var async_result = await domain.model()
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

```


### 상태(대기 중 정보) 목록 가져오기



**Unity**
```csharp

    var items = await gs2.Idle.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).StatusesAsync(
    ).ToListAsync();
```
**Unreal Engine**
```cpp

    const auto It = Gs2->Idle->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Statuses(
    );
    TArray<Gs2::UE5::Idle::Model::FEzStatusPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
```
**Godot**
```gdscript

var iterator = ez.idle.namespace_(
        "namespace-0001"
    ).me(
        game_session
    ).statuses(
    )

var async_result = await iterator.load()
if async_result.error != null:
    # 오류 처리
    push_error(str(async_result.error))
    return

var items = async_result.result

```


### 보상 내용 확인

획득 예정 보상을 수령 전에 확인하기 위해 `Prediction` API를 사용합니다.
플레이어에게 "앞으로 N분 기다리면 이만큼의 아이템을 얻을 수 있다"와 같은 사전 안내를 하고 싶은 경우에 활용할 수 있습니다.



**Unity**
```csharp

    var items = await gs2.Idle.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        category: "category-0001"
    ).PredictionAsync();
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Idle->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
        "category-0001" // categoryName
    );
    const auto Future = Domain->Prediction(
    );
    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 false;
    const auto Result = Future2->GetTask().Result();

```
**Godot**
```gdscript

var domain = ez.idle.namespace_(
        "namespace-0001"
    ).me(game_session).status(
        "category-0001"
    )

var async_result = await domain.prediction(
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

```


### 보상 수령

보상을 수령하면 대기 시간이 초기화됩니다.
또한, 대기 시간에 보상 획득 타이밍까지의 나머지가 존재하는 경우도 0으로 초기화됩니다(`rewardResetMode`에 `CarryOver`를 지정한 경우에는 나머지 시간이 다음으로 이월됩니다).



**Unity**
```csharp

    await gs2.Idle.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        category: "category-0001"
    ).ReceiveAsync();
```
**Unreal Engine**
```cpp

    const auto Future = Gs2->Idle->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
        "category-0001" // categoryName
    )->Receive();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
```
**Godot**
```gdscript

var domain = ez.idle.namespace_(
        "namespace-0001"
    ).me(game_session).status(
        "category-0001"
    )

var async_result = await domain.receive(
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

```


## 스크립트 트리거

네임스페이스에 `overrideAcquireActionsScriptId`나 `receiveScript`를 설정하면, 보상 산출이나 수령 처리 전후에 커스텀 스크립트를 실행할 수 있습니다.
트리거는 동기·비동기 실행 방식을 선택할 수 있으며, 비동기 처리에서는 GS2-Script나 Amazon EventBridge를 이용한 외부 연계도 가능합니다.

설정할 수 있는 주요 이벤트 트리거와 스크립트 설정명은 다음과 같습니다.

- `overrideAcquireActionsScriptId`: 보상 산출 시 실행되는 동기 스크립트. 동적으로 보상 목록을 덮어쓸 수 있습니다. 플레이어 상태에 따른 보상 교체 등에 활용할 수 있습니다.
- `receiveScript`(완료 통지: `receiveDone`): 보상 수령 전후. 동기 실행으로 수령 허가·거부나 배율 보정, 비동기 실행으로 Amazon EventBridge를 통한 외부 연계가 가능합니다.

## 트랜잭션 액션

GS2-Idle에서는 다음과 같은 트랜잭션 액션을 제공하고 있습니다.

| 유형 | 액션 | 설명 |
| --- | --- | --- |
| 소비 | `Gs2Idle:DecreaseMaximumIdleMinutesByUserId` | 최대 방치 시간 감소 |
| 입수 | `Gs2Idle:IncreaseMaximumIdleMinutesByUserId` | 최대 방치 시간 증가 |
| 입수 | `Gs2Idle:SetMaximumIdleMinutesByUserId` | 최대 방치 시간 설정 |
| 입수 | `Gs2Idle:ReceiveByUserId` | 보상 수령 |

"최대 방치 시간 증가"를 입수 액션으로 이용하면, 특정 아이템을 입수했을 때나 플레이어 랭크가 상승했을 때 등, 자동으로 방치 보상을 쌓을 수 있는 상한 시간을 확장하는 처리가 가능해집니다. 이를 통해 플레이어의 성장에 맞춰 더 많은 방치 보상을 축적할 수 있게 되어, 플레이 경험 향상으로 이어질 수 있습니다.

## 버프에 의한 보정

GS2-Buff를 이용하면, 카테고리 모델의 `acquireActions`나 플레이어별 `maximumIdleMinutes`를 버프로 보정하여, 이벤트나 캠페인에 따라 대기 중 보상 내용이나 상한 시간을 동적으로 조정할 수 있습니다.

예를 들어 "주말에는 방치 보상 1.5배", "특정 칭호를 가진 플레이어는 최대 방치 시간 +60분"과 같은 운영이 가능합니다.

## 마스터 데이터 운용
마스터 데이터를 등록하면 마이크로서비스에서 사용 가능한 데이터나 동작을 설정할 수 있습니다.

마스터 데이터의 종류에는 다음과 같은 것이 있습니다.

- `CategoryModel`: 대기 시간이나 보상 테이블 설정

다음은 마스터 데이터의 JSON 예시입니다.

```json
{
  "version": "2024-04-25",
  "categoryModels": [
    {
      "name": "category-0001",
      "metadata": "Stamina",
      "rewardIntervalMinutes": 10,
      "defaultMaximumIdleMinutes": 360,
      "rewardResetMode": "CarryOver",
      "acquireActions": [
        {
          "acquireActions": [
            {
              "action": "Gs2Experience:AddExperienceByUserId",
              "request": "{\"experienceValue\": 10}"
            }
          ]
        }
      ]
    }
  ]
}
```

마스터 데이터 등록은 관리 콘솔에서 등록하는 방법 외에도, GitHub에서 데이터를 반영하거나, GS2-Deploy를 사용해 CI에서 등록하는 등의 워크플로우를 구성할 수 있습니다.

## 상세 레퍼런스

[GS2-Idle API 레퍼런스](../../api_reference/idle)



