# 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 ```text 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 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. ```json { "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 | Setting | Type | Default | Meaning | |---|---|---|---| | `choices` | integer | `3` | boons per offer (1 to 4) | | `rarity` | object | `{ common: 70, rare: 25, epic: 5 }` | weights of a rarity roll (0 to 100 each; all zero means common) | | `pauseOnOffer` | boolean | `true` | pause the game while an offer waits; `choose` resumes it | | `gods` | list | `[]` | 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`. ```json { "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:`), 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.