GS2-Limit

Number of times limit feature

This is a mechanism for limiting the number of times a player can act. You can centrally manage frequently-occurring count limits in games, such as “you can draw the gacha up to 5 times a day” or “a reward you can receive only once a week”.

graph LR
  Action["Player action"] --> CountUp["Count up (with maxValue specified)"]
  CountUp -- Limit not reached --> Success["Allow processing"]
  CountUp -- Limit reached --> Failure["Reject processing"]
  Reset["Reset cycle"] --> Counter["Reset counter to 0"]

Counter

An entity used to represent the number of times a player has acted. The count limit is achieved by a mechanism that specifies the maximum acceptable value when raising the counter and attempting to count up; if the maximum value would be exceeded, the count-up fails, causing subsequent processing to fail as well. A distinctive feature is that there is no maximum value on the counter itself; rather, the maximum is set on each count-up action.

For example, consider the process of recovering stamina. In many games, there is a limit to the number of times you can recover your stamina in a day. And the more you recover, the higher the cost required for recovery.

Such a specification can be expressed as follows.

Tier Cost required to recover Number of times you can perform
Tier.1 5 10
Tier.2 10 10
Tier.3 20 10
Tier.4 40 10

And the table below treats this as a count-up action of a count limit and the cost required for recovery.

Tier counter name counter increase maximum counter value cost required to recover
Tier.1 RecoveryStaminaCounter 1 10 5
Tier.2 RecoveryStaminaCounter 1 20 10
Tier.3 RecoveryStaminaCounter 1 30 20
Tier.4 RecoveryStaminaCounter 1 40 40

The same counter is used for all tiers, and the lower the cost of recovery, the lower the maximum counter value.

In this way, you can design things so that the cheapest Tier.1 becomes unavailable first, forcing the purchase of Tier.2; eventually Tier.2 also becomes unavailable, forcing the purchase of Tier.3, and so on.

Reset counters

Counters can have a reset cycle. There are the following types of reset cycles.

resetType Description
notReset Does not reset (the count remains permanently unless reset manually)
daily Resets daily at resetHour
weekly Resets every week on resetDayOfWeek at resetHour
monthly Resets every month on resetDayOfMonth at resetHour
days Resets every days days starting from the anchor date (anchorTimestamp)

By using anchorTimestamp, flexible scheduling such as resetting every N days from an event start date is possible.

Adjustment with buffs

By integrating with GS2-Buff, you can use buffs to adjust the maxValue of CountUp/CountUpByUserId, temporarily increasing or decreasing the upper limit. This is useful when implementing event-based easing measures such as “for a limited time, double the number of gacha pulls allowed per day”.

Script Triggers

Setting countUpScript in the namespace allows custom scripts to be executed before and after count-up processing. Triggers support both synchronous and asynchronous execution. Asynchronous processing also supports external integration through GS2-Script or Amazon EventBridge.

The main event triggers and script setting names that can be configured are as follows:

  • countUpScript (completion notification: countUpDone): before and after count-up processing.

Transaction Actions

GS2-Limit provides the following transaction actions:

Type Action Description
Verify Gs2Limit:VerifyCounterByUserId Verify the counter value (equal/less than/greater than, etc.)
Consume Gs2Limit:CountUpByUserId Add to the counter value (count up)
Acquire Gs2Limit:CountDownByUserId Subtract from the counter value (count down)
Acquire Gs2Limit:DeleteCounterByUserId Delete the counter (reset)

By using “Subtract counter value (count down)” as an acquire action, it is possible to recover the limit count (effectively reverting the consumed count) when a specific item is obtained or as a reward for completing a mission. This makes it easy to design rewards that encourage continued play.

Managing master data

By registering master data, you can configure the data and behavior available to the microservice.

The types of master data are as follows:

  • LimitModel: Reset cycles and upper limits

Below is an example JSON of master data.

{
  "version": "2023-09-04",
  "limitModels": [
    {
      "name": "daily",
      "metadata": "Daily limit",
      "resetType": "daily",
      "resetHour": 5
    },
    {
      "name": "weekly",
      "metadata": "Weekly limit",
      "resetType": "weekly",
      "resetDayOfWeek": "monday",
      "resetHour": 5
    }
  ]
}

Master data can be registered from the Management Console, or you can set up a workflow that reflects data from GitHub or registers via CI using GS2-Deploy.

Example implementation

Obtain a list of counters

    var items = await gs2.Limit.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).CountersAsync(
    ).ToListAsync();
    const auto Domain = Gs2->Limit->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    );
    const auto It = Domain->Counters( // limitName
    );
    TArray<Gs2::UE5::Limit::Model::FEzCounterPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

Get counter status

    var item = await gs2.Limit.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Counter(
        limitName: "daily",
        counterName: "counter1"
    ).ModelAsync();
    const auto Domain = Gs2->Limit->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Counter(
        "daily", // limitName
        "counter1" // counterName
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

Raise the counter value

It is not recommended to use this API to count up. It is recommended to set the counter value increase as the cost for executing the process to which you want to apply a count limit, such as GS2-Exchange / GS2-Showcase / GS2-Quest.

Attempting a count-up that would exceed maxValue will throw an OverflowException. You can express the count limit by catching this exception on the client and displaying UI such as “You have reached today’s limit”.

    try {
        var result = await gs2.Limit.Namespace(
            namespaceName: "namespace-0001"
        ).Me(
            gameSession: GameSession
        ).Counter(
            limitName: "daily",
            counterName: "counter1"
        ).CountUpAsync(
            countUpValue: 1,
            maxValue: 100
        );
    } catch (Gs2.Gs2Limit.Exception.CountUpOverflowException e) {
        // The maximum number of times limit has been reached.
    }
    const auto Domain = Gs2->Limit->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Counter(
        "daily", // limitName
        "counter1" // counterName
    );
    const auto Future = Domain->CountUp(
        1, // countUpValue
        100 // maxValue
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        auto e = Future->GetTask().Error();
        if (e->IsChildOf(Gs2::Limit::Error::FOverflowError::Class))
        {
            // The maximum number of times limit has been reached.
        }
        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();

Force counter reset

Forced counter resets cannot be handled by the SDK for game engines. You can reset a counter by calling DeleteCounterByUserId from the game server, or by executing Gs2Limit:DeleteCounterByUserId as an acquire action of a transaction.

Detailed Reference