# Waves of minions (minion.waves) AI units that spawn in waves, walk a route toward the other end, fight what they meet, die and pay a reward. One kit serves a MOBA, a tower defense, an RTS skirmish or a siege, with a different config. Readable offline: `gripforge docs minion-waves`, or the MCP tool `gripforge_docs` (topic `minion-waves`). ## What it is for Every step is a named state and an event, and every rule is a config key. The kit holds no wallet and draws nothing itself: it sends units down routes, runs their state machine and announces rewards; health, damage and attacks come from the combat kits, the model comes from a slot of your game. | Step | What happens | Rule (config) | Event or state | | --- | --- | --- | --- | | SpawnWave | each team sends the composition down every route | `firstWave`, `interval`, `composition`, `maxActive`, `rowSize`, `spacing` | `minion.wave` | | FollowPath | walk the route toward the other end | type `speed` | state `follow` | | SelectTarget | nearest enemy of the first wanted category within `aggro` | `targetPriority`, `assistHeroes` | | | EnterCombat | chase the target into attack range | `engage` | state `combat` | | Attack | one hit every `cooldown` | type `damage`, `range`, `cooldown` | `minion.attack` | | LoseAggro | target dead, out of `aggro` x 1.3, or too far off the route | `chaseDistance` | state `return` | | ReturnToPath | walk back to the route, or resume from the nearest point | `returnToLane` | `return` then `follow` | | Die | death animation, then despawn | `despawnDelay` | state `dead` | | GiveReward | gold to the last hit, experience split between enemy heroes nearby | `rewards.*` | `minion.killed` | Every state change is `minion.state { minion, from, to, target }`; each minion carries the component `minion.ai { state, target, type, route, aggro, range }`. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "minion.waves" } gripforge_gamekit_configure { project_id: "gkp_…", id: "minion.waves", config: { "interval": 45, "targetPriority": ["structure"] } } ``` It requires `combat.attacks`, `combat.health`, `combat.damage` and `game.engine`. The full reference is `gripforge kit api minion.waves`. ## Routes and teams - A kit that provides `lanes.routes` (`moba.lanes` does) gives the routes and the two teams. - Otherwise the collection `minion_routes` holds `{ id, points: [[x, z]…] }` (ground points from the first team's end to the second's) and `teams` names the two teams. - A scene may carry its own routes in `data.minion_routes`. Entering the scene starts its waves over (`firstWave` after the entry); leaving it removes its minions. The first team walks a route forward, the second backward, so opposite waves leave together and meet midway. ## Settings and defaults | Key | Default | Meaning | | --- | --- | --- | | `teams` | `["blue", "red"]` | the two teams (ignored when a kit provides `lanes.routes`) | | `firstWave`, `interval` | 8, 30 | seconds before the first wave, between waves | | `composition` | melee x3, siege x1 every 3 waves, ranged x3 | front row first; `{ type, count, every? }` | | `maxActive` | 80 | no wave is sent while that many minions are alive | | `rowSize`, `spacing` | 3, 2 | minions side by side; metres between minions | | `scale` | 1 | size factor over a 1.8 m standard character | | `engage` | `true` | `false`: they only walk (a tower defense) | | `chaseDistance` | 6 | metres from the route a chase may take a minion | | `returnToLane` | `true` | walk back to the route after a fight | | `targetPriority` | `["minion", "hero", "structure"]` | most wanted first; a category left out is ignored (also `monster`, `other`) | | `assistHeroes`, `assistRadius` | `true`, 9 | turn on an enemy hero who hits an allied hero nearby | | `avoidance` | `true` | keep `spacing` x 0.7 apart instead of stacking | | `formation` | `false` | a wave walks as a group at the pace of its slowest minion | | `pool` | `false` | the look of a dead minion is handed to the next minion of the same model | | `scaling` | `{ mode: "time", every: 180, hp: 0.05, damage: 0.04, armor: 2 }` | growth per step; `mode` is `time` (seconds) or `wave` | | `rewards` | `{ lastHitOnly: true, shareXp: true, shareRadius: 14, goldFactor: 1, xpFactor: 1 }` | | | `despawnDelay` | 1.5 | seconds a dead minion stays | | `entityKind`, `slotPrefix`, `tags` | `minion`, `minion_`, `[]` | entity kind, model slot prefix, extra tags | | `debug` | `false` | draw routes, detection and attack ranges, the current target | ## Types The collection `minion_types` holds `{ id, name?, hp, damage, armor?, range, cooldown, speed, aggro, scale?, slot?, windup?, projectileSpeed?, color?, reward?: { gold?, xp? } }`. Built in: `melee`, `ranged`, `siege`, `super`. A `range` above 3 m is a ranged attack (a projectile is drawn); `armor` divides damage by (100 + armor) / 100. The model of a type is the slot `slotPrefix + id` with the team as variant (`minion_melee:blue`); the slot declares the animation clips it needs (`idle`, `run`, `attack1`, `death`). ## Recipes - Minions ignore heroes and only attack towers: `targetPriority: ["structure"]`. - A tower defense (creeps walk, never fight): `engage: false`, with one empty team. - Bigger, slower waves: `composition: [{ "type": "melee", "count": 6 }, { "type": "siege", "count": 2 }]`, `interval: 45`. - Late-game pressure: `scaling: { "mode": "wave", "every": 2, "hp": 0.1, "damage": 0.08, "armor": 3 }`. - A boss every fifth wave: a `minion_types` entry `brute` and `{ "type": "brute", "count": 1, "every": 5 }`. ## Hooks, mods, services | What | Name | Detail | | --- | --- | --- | | Hook | `minion.spawn` | `{ wave, route, team, count, reinforced }`, asked once per route and team of a wave. A tap changes `count` (fewer: the rear stays home; more: the composition is repeated from its head; 48 at most) or cancels (nobody leaves for that team on that route; the wave still counts) | | Hook | `minion.reward` | `{ minion, type, team, route, by, gold, xp }`, once per death, before `minion.killed`; a tap changes `gold` and `xp`, a cancel pays nothing (`minion.killed` is still sent with 0) | | Registries | `minion_types`, `minion_routes` | a mod adds, patches or removes documents | | Moddable config | all keys but `teams`, `entityKind` and `tags` | wave, chase, priority, scaling and reward keys | | Events | `minion.wave { wave, routes, count }`, `minion.state`, `minion.attack { source, target, ranged, synchronized, type }`, `minion.killed { minion, type, team, route, by, gold, xp, sharers }` | an economy or match kit pays `gold` to `by` and splits `xp` between `sharers` | | Listens | `combat.killed`, `combat.damaged` | | | Service | `minion.waves` | `spawnWave()`, `spawn(type, team, route, t?, side?)`, `list()`, `get(id)`, `front(route, team)`, `count(team?)`, `wave()`, `routes()`, `teams()`, `types()`, `priority()`, `setPriority(list)`, `setReinforcements(route, team, entries)` (super minions after an inhibitor), `setDebug(on)`, `debug()` | ```json { "id": "thin_red_waves", "hook": "minion.spawn", "match": { "team": "red" }, "add": { "count": -2 } } ``` Which enemy a minion may hit is a service another kit provides, `combat.targeting { canTarget?(attacker, target), radius?(id), category?(id) }` (a tower that cannot be hit yet, a neutral camp, a wide building). Without it the category is read from tags (`minion`, `hero` / `player`, `structure` / `tower`, `monster`). A kit that provides `world.navigation { next(key, from, to) }` lets minions bend around walls when a chase takes them off their route. Variables: `minion.wave`, `minion.count`, `minion.debug`. ## Save The wave count and clock, every minion (type, team, route, progress, state, target, cooldown), the reinforcements and the target priority. After a load the minions walk and fight on and the next wave leaves on time; a minion whose type, route or team is gone (a mod removed it) is dropped. ## Engines Web and native player run the same rules (waves, rows, scaling, the state machine, rewards, hooks, save). Health, damage, targeting, attacks, navigation, water and ground are called from the modules that serve them.