Rewards #

The Reward systems is a powerful tool that enables you to reward players’ in-game activities with everything from currencies and energies, to items and reward modifiers. The system is designed to be flexible enough to allow for simple guaranteed rewards as well as complex Gacha weighted table rewards seen in popular games today.

The Reward system is integrated into all systems within Hiro, which allows you to create sophisticated reward structures in your game. These concepts are explored in the Reward Types examples section below.

Customization parameters #

The following JSON represents the customization parameters you can use to configure the default user experience for a particular reward. Note, rewards are configured inline where they are used, for example as part of an Unlockables configuration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
{
  "rewards": {
    "guaranteed": {
      "items": {
        "bronze_sword": {
          "min": 1
        },
        "small_potion": {
          "min": 1,
          "max": 5
        }
      },
      "item_sets": [
        {
          "set": ["rare", "weapon"],
          "max_repeats": 0,
          "min": 1
        }
      ],
      "currencies": {
        "coins": {
          "min": 100,
          "max": 500,
          "multiple": 50
        }
      },
      "energies": {
        "power": {
          "min": 100,
          "max": 200
        }
      },
      "energy_modifiers": [
        {
          "id": "power",
          "operator": "multiplier",
          "value": {
            "min": 2
          },
          "duration_sec": 64000
        }
      ],
      "reward_modifiers": [
        {
          "id": "coins",
          "type": "currency",
          "value": {
            "min": 2
          },
          "duration_sec": 32000
        }
      ]
    },
    "weighted": [
      {
        "items": {
          "apple": {
            "min": 1
          }
        },
        "weight": 99
      },
      {
        "items": {
          "legendary_sword": {
            "min": 1
          },
          "weight": 1
        }
      }
    ],
    "max_rolls": 1,
    "total_weight": 100
  }
}

The JSON schema defines a reward object which defines the various items, item sets, currencies, energies, energy modifiers, and reward modifiers that will be available as rewards where this particular reward is defined. You can configure as few or as many of each as needed for your desired gameplay. This object is defined in-line throughout the various data definitions within Hiro, above it is shown as part of a rewards field, but in some systems this field may be named differently (e.g. contributorRewards in the Donations System).

A Reward object consists of the following properties

  • guaranteed: This section defines a RewardContents object that contains all of the rewards a player is guaranteed to receive once a certain activity is completed or unlocked.
  • weighted: This section defines an array of RewardContents that the player may or may not receive depending on the outcome of a random roll.
  • max_rolls: This property determines how many rolls are made against these weighted reward contents. Each weighted reward defines a weight property that determines how likely the user will receive that particular reward content.
  • total_weight: This property defines the total weight of all weighted reward contents, this can be omitted if you would prefer it to be automatically calculated. If you want a chance for the player to receive no reward, you can manually set the total_weight to be higher than the sum of individual weights.

RewardContents #

The RewardContents object is structured as follows:

PropertySubpropertyDescription
itemsA string:object, where the key is the item ID and the value defines the quantity.
minThe minimum amount to reward.
maxThe maximum amount to reward.
multipleThe multiple the amount will be rounded to.
item_setsAn array of object values that define which item sets are part of the reward.
setAn array of string values that define which intersection of item sets to reward from.
minThe minimum amount to reward.
maxThe maximum amount to reward.
multipleThe multiple the amount will be rounded to.
max_repeatsThe maximum amount of repeats that can be rewarded from this set.
currenciesA string:object, where the key is the currency ID and the value defines the quantity.
minThe minimum amount to reward.
maxThe maximum amount to reward.
multipleThe multiple the amount will be rounded to.
energiesA string:object, where the key is the energy ID and the value defines the quantity.
minThe minimum amount to reward.
maxThe maximum amount to reward.
multipleThe multiple the amount will be rounded to.
energy_modifiersAn array of object values that define which energy modifiers are part of the reward.
idThe ID of the energy that should be modified.
operationThe operation that should be performed on the energy when rewarded (e.g. multiplier, addition)
valueAn object which defines the min, max and multiple amounts.
reward_modifiersAn array of object values that define which reward modifiers are part of the reward.
idThe ID of the reward element that should be modified (e.g. coins or potion).
typeThe type of the reward that is to be modified (e.g. item or currency)
operationThe operation that should be performed on the reward when rewarded (e.g. multiplier, addition)
valueAn object which defines the min, max and multiple amounts.
weightThe weight of this reward as it relates to the rest of the weighted rewards.

Reward types #

The reward system in Hiro offers a variety of options to enhance the excitement and progression in your games. With different reward types available, you can create engaging experiences that keep players motivated and eager to continue playing. Hiro supports simple rewards, weighted rewards, gacha-style mechanics, and custom rewards tailored to your game. By understanding how to effectively implement these rewards, you can optimize the gaming experience for your players and provide them with a sense of accomplishment and satisfaction.

Simple rewards #

Simple rewards are a straightforward and easy-to-implement type of reward in Hiro. These rewards typically offer guaranteed items, currency, or energies to players upon achieving specific milestones or completing tasks. Simple rewards are ideal for creating a sense of immediate gratification and progress, providing players with a tangible benefit for their efforts.

Daily login reward

Define a reward as part of a Daily Achievement that will give players a random number of coins for logging in each day.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "guaranteed": {
    "currencies": {
      "coins": {
        "min": 100,
        "max": 500,
        "multiple": 50
      }
    }
  }
}

Health Restore Potion

Define a reward that will restore a player’s health as part of a Consumable Item.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "guaranteed": {
    "energies": {
      "health": {
        "min": 20,
        "max": 80,
        "multiple": 10
      }
    }
  }
}

Boosting Health Energy Rewards

Define a reward that will multiply a player’s health energy rewards for the next hour as part of a Virtual Store Purchase.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "guaranteed": {
    "energy_modifiers": [
      {
        "id": "health",
        "operator": "multiplier",
        "value": {
          "min": 2
        },
        "duration_sec": 3600
      }
    ]
  }
}

Boosting Coin Rewards

Define a reward that will multiply all coin’s received via rewards for the next 2 hours as part of a Donation Contribution reward.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "guaranteed": {
    "reward_modifiers": [
      {
        "id": "coins",
        "type": "currency",
        "value": {
          "min": 2
        },
        "duration_sec": 7200
      }
    ]
  }
}

Weighted rewards #

Weighted rewards introduce an element of chance and rarity to your game’s reward system. With weighted rewards, you can assign different probabilities to various items or outcomes. This allows you to create a sense of anticipation and excitement as players strive to obtain rare and valuable rewards. By carefully balancing the weights assigned to each reward, you can ensure a fair and rewarding experience for your players.

Level completion reward chest

Define a reward as part of a Level Completion Unlockable that guarantees the player a potion as well as a 25% chance to get a rare gem.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "guaranteed": {
    "items": {
      "health_potion": {
        "min": 1
      }
    }
  },
  "weighted": [
    {
      "items": {
        "rare_gem": {
          "min": 1
        }
      },
      "weight": 25
    }
  ],
  "max_rolls": 1,
  "total_weight": 100
}

Gacha rewards #

Gacha-style rewards have become increasingly popular in many games. Inspired by capsule toy vending machines, gacha rewards offer players a chance to obtain a randomized selection of prizes. Players typically use in-game currency or special tokens to “pull” from a pool of possible rewards. Gacha rewards add an element of surprise and collection to your game, enticing players to engage in repeated pulls to obtain rare and coveted items.

Hero reward

Define a reward as part of a Virtual Store Purchase that rewards a player with two random heroes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
  "weighted": [
    {
      "items": {
        "common_hero": {
          "min": 1
        }
      },
      "weight": 70
    },
    {
      "items": {
        "rare_hero": {
          "min": 1
        }
      },
      "weight": 25
    },
    {
      "items": {
        "legendary_hero": {
          "min": 1
        }
      },
      "weight": 5
    }
  ],
  "max_rolls": 2,
  "total_weight": 100
}

Custom rewards #

Custom rewards provide you with the flexibility to design unique and personalized rewards tailored specifically to your game’s mechanics and progression systems. Whether it’s unlocking special abilities, accessing exclusive content, or granting players special privileges, custom rewards allow you to shape the gameplay experience according to your vision. With custom rewards, you have the freedom to create meaningful and memorable moments for your players, ensuring a truly immersive and rewarding gaming experience.

Custom rewards in Hiro are implemented by utilizing a hook function, which allows you to modify the reward behavior and provide a personalized experience for your players. To set up the hook, you can use the appropriate SetOnReward() function and pass in your own implementation of the hiro.OnReward hook.

The following example demonstrates how you can implement a custom reward hook when an inventory item is consumed using the inventorySystem.SetOnConsumeReward() hook.

Define a custom reward hook function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func onConsumeReward(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule, userID string, source *hiro.InventoryConfigItem, rewardConfig *hiro.EconomyConfigReward, reward *hiro.Reward) (*hiro.Reward, error) {
  // Perform custom logic or checks based on the specific item being consumed
  switch source.Name {
  case "New Adventurer Chest":
    // Get the player level (assumes a function exists called getPlayerLevel)
    playerLevel := getPlayerLevel(userID)

    // Do nothing if the player isn't <= level 10
    if playerLevel > 10 {
      return nil, runtime.NewError("player level too high to open chest", 13)
    }
		
    // Define an array of possible items
    possibleItems := []string{"minor_potion", "leather_scraps", "copper_ore"}

    // Choose a random item from the array
    chosenItem := possibleItems[rand.Intn(len(possibleItems))]

    // Reward 2 * playerLevel of the chosen item
    reward.Items = make(map[string]int64)
    reward.Items[chosenItem] = int64(2 * playerLevel)

    // Reward a fixed amount of gold
    reward.Currencies = make(map[string]int64)
    reward.Currencies["gold"] = 500
  case "Home Teleport Stone":
    // Get the player's home location (assumes a function exists called getPlayerHomeLocation)
    playerHomeLocation := getPlayerHomeLocation(userID)

    // Teleport the player home (assumes a function exists called setPlayerLocation)
    setPlayerLocation(userID, playerHomeLocation)
  }

  // Return the modified reward object
  return reward, nil
}

Assign the hook function to the inventory system:

1
inventorySystem.SetOnConsumeReward(onConsumeReward)

Within the onConsumeReward() function, you have the opportunity to modify the reward object and/or perform any necessary actions or checks. In the example above, the context of the consumed item is checked and a specific action is taken and reward given depending on the user’s contextual information.

By utilizing a reward hook function and customizing it to fit your game’s specific needs, you can dynamically adjust rewards, incorporate game mechanics, and create a more personalized and engaging experience for your players.

Here are some examples of custom rewards you could create:

  • Gacha based pity system, rewarding player’s with a legendary after X rewards without one.
  • Unlocking special abilities or powers for the player.
  • Granting access to exclusive areas, levels, or game content.
  • Granting in-game titles, badges, or achievements.
  • Enabling fast travel options within the game world.
  • Offering personalized quests or missions.
  • Granting access to rare or legendary items with personalized attributes.
  • Granting additional character slots.
  • Providing in-game social features or privileges, such as VIP status or access to exclusive chat channels.

Pity system example #

The following code show how you could implement a Pity system using a custom reward hook on a Virtual Store Purchase that guarantees a player receives a legendary hero after a certain number of rewards received without one.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
const (
	maxPityCounter        = 10
	legendaryItem         = "legendary_sword"
	pityCounterCollection = "pity"
	pityCounterKey        = "pity_counter"
)

type pityStorage struct {
	pity int `json:"pity"`
}

func onPurchaseReward(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule, userID string, source *hiro.EconomyConfigStoreItem, rewardConfig *hiro.EconomyConfigReward, reward *hiro.Reward) (*hiro.Reward, error) {
  // Don't modify the reward if it isn't the gacha lootbox
  if source.Name != "Gacha Lootbox" {
    return reward, nil
  }

  // Assume the player's pity is 0 by default
  pityCounter := 0

  // If the reward does not contain the legendary sword, increment the player's pity counter
  if _, ok := reward.Items[legendaryItem]; !ok {
    // Get player's current pity from storage
    records, err := nk.StorageRead(ctx, []*runtime.StorageRead{
      {
        Collection: pityCounterCollection,
        Key:        pityCounterKey,
        UserID:     userID,
      },
    })
    if err != nil {
      return nil, err
    }

    // Deserialize the pity storage object
    if len(records) > 0 {
      var pityStorage *pityStorage
      err = json.Unmarshal([]byte(records[0].Value), &pityStorage)
      if err != nil {
        return nil, err
      }

      pityCounter = pityStorage.pity
    }

    // Increase the pity counter
    pityCounter++

    // If the pity counter is over the threshold, replace the reward with the legendary item and reset the pity counter
    if pityCounter >= maxPityCounter {
      reward.Items = make(map[string]int64)
      reward.Items[legendaryItem] = 1
      pityCounter = 0
    }
  }

  // Write the updated pity counter to storage
  pityStorage := &pityStorage{pity: pityCounter}

  // Serialize the pity data to JSON
  bytes, err := json.Marshal(pityStorage)
  if err != nil {
    return nil, err
  }

  // Write the updated pity counter to storage
  if _, err = nk.StorageWrite(ctx, []*runtime.StorageWrite{
    {
      Collection:      pityCounterCollection,
      Key:             pityCounterKey,
      UserID:          userID,
      Value:           string(bytes),
      PermissionRead:  0,
      PermissionWrite: 0,
    },
  }); err != nil {
    return nil, err
  }

  // Return the modified reward object
  return reward, nil
}