Boons

Offer a choice of boons with rolled rarities, let the player pick one, and plug it into combat as data: damage multipliers, on-hit statuses and chain bolts, dash blasts, health, dash charges, cooldowns and speed.

Source: module-kits:kits/upgrades_boons/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs upgrades-boonsMCP tool: gripforge_docs { "topic": "upgrades-boons" }

Run upgrades granted by gods (upgrades.boons) #

What it is for #

upgrades.boons gives a run its upgrades. When a reward is collected, a script asks for it, or your code calls offer, the kit draws a few eligible boons, rolls the rarity of each, pauses the game and waits for the player to choose. The chosen boon then acts on combat as data: damage multipliers, a status or a chain bolt on hit, a blast at a dash, health, dash charges, cooldowns, speed. A boon is data in two collections (gods and boons); the kit leaves the choice screen to your interface, which reads boons.offer and calls choose. With no documents in the project, the kit plays a built-in set of five gods and their boons.

Turn it on #

gripforge_gamekit_install   { project_id: "gkp_…", id: "upgrades.boons" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "upgrades.boons",
                              config: { "choices": 3, "rarity": { "common": 60, "rare": 30, "epic": 10 } } }

The kit needs combat.damage, combat.status, combat.health and game.engine.

First example #

<!-- test: kits/game_engine/host/tests/upgrades_boons.rs::an_offer_pauses_and_a_choice_resumes_and_acquires --> With two gods' boons in the project, offer draws from the eligible ones (a boon whose requires is not owned is left out), pauses the game, names the offer offer:1 and keeps answering the same offer until it is settled. choose(0) takes the first choice, resumes the game and sets boons.count to 1.

{ "op": "call", "service": "boons.offer", "method": "offer", "args": [{ "source": "altar" }] }
{ "op": "call", "service": "boons.offer", "method": "choose", "args": [0] }

Events, in order: boon.offered, then boon.acquired. A wrong index answers false and the offer keeps waiting.

Settings and defaults #

SettingTypeDefaultMeaning
choicesinteger3boons per offer (1 to 4)
rarityobject{ common: 70, rare: 25, epic: 5 }weights of a rarity roll (0 to 100 each; all zero means common)
pauseOnOfferbooleantruepause the game while an offer waits; choose resumes it
godslist[]restrict offers to these gods; empty = every god

Content: gods and boons #

A god is { id, name, title?, color } (color is #rrggbb). A boon is { id, god, name, description, slot?, requires?, effects[], rarityScale? }:

  • slot is attack, special, dash or passive. Acquiring a boon removes the one you own in the same slot (not for a passive).
  • requires lists boon ids that must be owned for this boon to be offered.
  • rarityScale is { common, rare, epic }, the multiplier of the effects at each rarity (defaults 1, 1.3 and 1.6).
  • effects hold at least one of: damage_mult (on, value), incoming_mult (value), on_hit_status (on, status, stacks, chance), on_hit_bolt (on, damage, radius, targets, chance), dash_blast (damage, radius, status, knockback, at), dash_invulnerable (seconds), max_health (amount), heal (amount), dash_charges (amount), special_cooldown_mult (value), move_speed_mult (value). The trigger on is attack, special, dash or any.
{ "id": "zeus_storm_edge", "god": "zeus", "name": "Storm Edge", "description": "Your attacks arc lightning to a nearby foe.",
  "slot": "attack", "effects": [{ "type": "on_hit_bolt", "on": "attack", "damage": 12, "radius": 5, "targets": 1 }] }

Services, events, variables #

  • boons.offer (service): offer({ god?, count?, source? }) (answers the offer { id, god, source, choices[] }, or null if nothing is eligible or a hook cancelled it), current(), choose(index) (answers whether it worked), grant(id, rarity?), owned(), gods(), boons(). A choice is { boon, god, name, description, rarity, rank, replaces? }.
  • boon.offered (event): { offer, god, choices, source }.
  • boon.acquired (event): { boon, god, rarity, rank, replaced? }.
  • boon.proc (event): an extra hit fired, { boon, effect, from, at, targets } (on_hit_bolt or dash_blast).
  • Variables: boons.offer (the waiting offer, null when none), boons.owned and boons.count (how many boons are owned).
  • Verbs for content: offer_boon { god?, count? } and grant_boon { boon, rarity? } (actions), has_boon { boon } (condition).
  • Listens to: reward.collected (a reward of type boon, with an optional god, starts an offer), combat.damaged, dash.started, dash.ended.

The random draw uses its own fork per offer (offer:<n>), and the offer counter is saved, so a seed gives the same offers.

Hooks and modding #

  • Hook boon.offer: payload { god, count, source }, god and count mutable, cancellable. A cancelled offer draws nothing: offer answers null, no event, no pause.
  • Hook boon.acquire: payload { boon, god, slot, rarity, replaced, via } (via is choose or grant), rarity mutable (it sets the scale of the effects), cancellable (choose and grant answer false, the offer keeps waiting).
  • Hook boon.proc: payload { boon, god, effect, target, amount, radius } with effect on_hit_status, on_hit_bolt or dash_blast; amount and radius mutable, cancellable. It is asked after the chance was rolled.
  • A mod may change choices, rarity, pauseOnOffer, gods and the collections gods and boons.

The damage multipliers of a boon are taps of the combat.damage hook, ordered with yours by priority.

Engines #

The rules are the same Rust implementation on the web, the native player, Godot, Unity and Unreal; effects act through the combat kits of the game. The web player draws the lightning of a bolt or blast on boon.proc as an additive ribbon from the origin to each target.

When it does not do what you expect #

  • offer answers null: no boon is eligible (all owned, a missing requires, a gods restriction), or a boon.offer tap cancelled it.
  • The game stays paused: an offer waits; call choose, or set pauseOnOffer to false.
  • choose answers false: no offer waits, the index is out of range, the boon is already owned or a boon.acquire tap cancelled it.
  • A boon never appears: its god is not in the gods document, or its requires boon is not owned yet.

Limits #

The kit has no choice screen: your interface reads boons.offer. A boon from the same slot replaces the previous one; there is no upgrade of a boon beyond its rank 1.

Versions #

Kit 1.3.0 (hooks boon.offer, boon.acquire, boon.proc; collections moddable). Service boons.offer 1.0.0.