Shrinking zone
A circular safe zone that closes in by phases, and what happens outside: damage every second or expulsion (battle royale storm, sumo ring).
A shrinking zone in your game (zone.shrink) #
What it is for #
zone.shrink manages a circular safe zone that closes in by phases of data: it holds still, then shrinks to a radius over some seconds, around a centre that may slide. Every entity that carries one of the affects tags (and none of the ignore tags) is placed against the edge, on foot or in a vehicle. Outside, it either takes damage every damageEvery seconds (the storm of a battle royale), is thrown out with an event (a sumo ring, a party round), or nothing happens beyond the events. The kit draws the wall, the edge on the ground and the next circle; deciding who is eliminated is left to other kits.
Turn it on #
Install zone.shrink (it requires game.engine@^1) and give the project a zone_phases collection. Without phases the zone keeps its config radius, a fixed ring. With autoStart: false the zone waits for start().
[
{ "id": "first", "wait": 30, "shrink": 65, "radius": 140, "damage": 2 },
{ "id": "second", "wait": 20, "shrink": 65, "radius": 86, "damage": 4 },
{ "id": "last", "wait": 5, "shrink": 20, "radius": 0, "damage": 10 }
]
First example #
<!-- test: kits/game_engine/host/tests/parity.rs::rust_host_matches_the_typescript_traces (fixture zone.shrink.storm) --> The fixture starts a zone of radius: 8 with damage: 1.5, damageEvery: 0.5 and margin: 0.25, and two phases: first (hold 1 s, shrink 1 s to radius 5, damage 3) then second (shrink 0.5 s to radius 2, centre sliding to [1, 0.5]). A phase whose radius is not a number is skipped. The player (tag player) and an NPC (tag contestant) are watched; a prop without the tags and a contestant tagged dead are not. The trace checks state(), view(), outside(), isOutside, contains, distance, and the events and damage over the following ticks.
Settings and defaults #
| Setting | Type | Default | Meaning |
|---|---|---|---|
center | [x, z] | [0, 0] | Centre of the zone when it starts. |
radius | number | 60 | Radius when it starts, before the first phase. |
outside | string | damage | damage, out or none. |
damage | number | 2 | Damage per second outside, until a phase says otherwise. |
damageEvery | number | 1 | Seconds between two damage ticks. |
margin | number | 0 | Metres past the edge before an entity counts as outside (negative: before the edge). |
affects | list | player, contestant | Tags of watched entities (any of them). |
ignore | list | dead, eliminated, spectator | Tags that take an entity out of the watch. |
autoStart | boolean | true | Start with the game; off: call start(). |
wallHeight | number | 40 | Height of the wall at the edge (0: ground circle only). |
color | string | #a177f5 | Colour of the wall and the edge. |
showNext | boolean | true | Draw the circle the zone is closing in to. |
wallOpacity | number | 0.15 | Opacity of the wall. |
rim | boolean | false | A glowing rim along the edge (a sumo ring). |
pylons | integer | 0 | Lamp pylons around the starting circle (0 to 64). |
halo | boolean | false | A glowing halo far under the starting circle. |
A phase { id, radius, wait?, shrink?, center?, damage? }: wait seconds of hold, then a linear shrink over shrink seconds to radius, sliding to center; damage replaces the outside damage from that phase on.
With outside: damage, a tick of damage x damageEvery goes through combat.damage when the entity has combat health, through vehicle.damage for a vehicle, else it removes hp from the entity's health component. combat.core is optional.
Services, events, variables #
zone.storm(service):state()(started,stage,index,phase,center,radius,target,targetCenter,timer,damage),start(),stop(),contains(x, z, margin?),distance(x, z)(negative inside),outside()(entity ids),isOutside(entity),view()(what the renderer draws).- Events:
zone.started,zone.phase_started,zone.shrinking,zone.closed,zone.finished,zone.stopped,zone.leftandzone.returned({ entity }),zone.expelled({ entity, radius, distance }),zone.damaged({ entity, amount, applied };appliedis false when no health is known for the entity). - Variables:
zone.stage(idle, wait, shrink, done),zone.phase,zone.radius,zone.timer,zone.player_outside.
Hooks and modding #
| Hook | Payload | What a tap does |
|---|---|---|
zone.phase | { phase, index, wait, shrink, radius, damage } | wait, shrink, radius, damage are mutable; cancelled, the phase is skipped. |
zone.expel | { entity, radius, distance } | With outside: out; cancelled, nothing happens until the entity returns and leaves again. |
zone.damage | { entity, amount, index } | With outside: damage; amount is mutable; cancelled, no damage this tick. |
{ "id": "shelter", "hook": "zone.damage", "match": { "entity": "e1" }, "mul": { "amount": 0.5 } }
A mod can override every config key above and change the zone_phases collection.
Engines #
The logic is the same Rust implementation on the web and in the native player, which draw the wall and circles. The kit is delivered for the web and Rust targets.
When it does not do what you expect #
- Nothing is watched: the entity lacks every
affectstag, or has anignoretag. - The zone never moves:
autoStartis off (callstart()), or there are no valid phases. - No damage with
outside: out: that mode only emitszone.expelled; another kit takes the entity out of the game. zone.damagedwithapplied: false: no health is known for that entity.
Limits #
One zone per game, circular only. The kit does not eliminate anyone itself and does not keep score.
Versions #
Kit 1.1.0, service zone.storm 1.0.0. The ring dressing (wallOpacity, rim, pylons, halo) arrived in 1.1.0.