Rewards #

The Reward system 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
76
{
  "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,
    "max_repeat_rolls": 0,
    "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:

Reward #

PropertyTypeDescription
guaranteedRewardContentsThe rewards that a user is guaranteed to receive.
weighted[]RewardContentsThe rewards that a user may or may not receive depending on the outcome of a random roll.
max_rollsint64The number of weighted RewardContents to select from among the possibilities.
max_repeat_rollsint64The maximum number of repeats for any given weighted reward.
total_weightint64The 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 this to be higher than the sum of individual weights.

Reward Contents #

PropertyTypeDescription
itemsstring:RewardItemA map of item IDs and quantities to reward the player with.
item_sets[]RewardItemSetThe item sets that are part of the reward.
currenciesstring:RewardRangeInt64A map of currency IDs and quantities to reward the player with.
energiesstring:RewardRangeInt32A map of energy IDs and quantities to reward the player with.
energy_modifiers[]RewardEnergyModifierThe energy modifiers that are part of the reward.
reward_modifiers[]RewardRewardModifierThe reward modifiers that are part of the reward.
weightint64The weight of this reward as it relates to the rest of the weighted rewards.

Reward Energy Modifier #

PropertyTypeDescription
idstringThe ID of the energy that should be modified.
operatorstringThe operation that should be performed on the energy when rewarded (e.g. multiplier, addition).
valueRewardRangeInt64The modifier value range.
duration_secRewardRangeUInt64The duration range for the modifier (in seconds).

Reward Reward Modifier #

PropertyTypeDescription
idstringThe ID of the reward element that should be modified (e.g. coins or potion).
typestringThe type of the reward that is to be modified (e.g. item or currency).
operatorstringThe operation that should be performed on the reward when rewarded (e.g. multiplier, addition).
valueRewardRangeInt64The modifier value range.
duration_secRewardRangeUInt64The duration range for the modifier (in seconds).

Reward Item #

PropertyTypeDescription
minint64The minimum amount to reward.
maxint64The maximum amount to reward.
multipleint64The multiple the amount will be rounded to.
string_propertiesstring:RewardStringPropertyAdditional string properties that will be added to the item instance.
numeric_propertiesstring:RewardRangeFloat64Additional numeric properties that will be added to the item instance.

Reward Item Set #

PropertyTypeDescription
minint64The minimum amount to reward.
maxint64The maximum amount to reward.
multipleint64The multiple the amount will be rounded to.
max_repeatsint64The maximum amount of repeats that can be rewarded from this set.
set[]stringWhich intersection of item sets to reward from.

Reward Ranges #

Below are the types that are used when defining ranges across various properties.

Reward Range Int32 #

PropertyTypeDescription
minint32The minimum amount to reward.
maxint32The maximum amount to reward.
multipleint32The multiple the amount will be rounded to.

Reward Range Int64 #

PropertyTypeDescription
minint64The minimum amount to reward.
maxint64The maximum amount to reward.
multipleint64The multiple the amount will be rounded to.

Reward Range UInt64 #

PropertyTypeDescription
minuint64The minimum amount to reward.
maxuint64The maximum amount to reward.
multipleuint64The multiple the amount will be rounded to.

Reward Range Float64 #

PropertyTypeDescription
minfloat64The minimum amount to reward.
maxfloat64The maximum amount to reward.
multiplefloat64The multiple the amount will be rounded to.

Reward String Property #

PropertyTypeDescription
total_weightint64The total weight of all weighted property options, this can be omitted if you would prefer it to be automatically calculated. If you want a chance for a property not to be added to the item instance, you can manually set this to be higher than the sum of individual weights.
optionsstring:RewardStringPropertyOptionA map of the possible properties and their weightings.

Reward String Property Option #

PropertyTypeDescription
weightint64The weight of this property option as it relates to the rest of the weighted property options.

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
}