All posts

Tick targets, and expressions in every particle

Three things this time, and the first one is a command rather than a power.

/tick entity and /tick chunk

Minecraft has had /tick since 1.20.3, and it is the right place for this: it already knows how to slow the world down, and it already tells every client about it. Apoli now hangs two more branches off it.

tick entity @e[type=creeper] freeze
tick entity @e[type=arrow] rate 4
tick chunk ~ ~ radius 64 rate 5
tick chunk 0 0 512 512 query

Every operation the server-wide command has — query, rate, freeze, unfreeze, step, sprint — works on a selector full of entities or on a patch of chunks. The full syntax is on the Tick command page.

The vanilla branches sit under permission level 3, which is more than a data-pack function gets, so the same tree is also registered as /apoli:tick entity … and /apoli:tick chunk … at Apoli’s own level 2. Use that one inside .mcfunction files.

A rate above the server’s own still does nothing on its own — a server cannot tick one entity more often than it ticks at all. Raise the whole server with /tick rate 40 and hold everything else down with tick chunk, tick entity or apoli:modify_tick_rate on a dimension target.

Every number on a particle is an expression

count, speed, spread, offset_x/y/z, velocity_x/y/z and frequency on apoli:particle and both apoli:spawn_particles actions now take an expression wherever they took a number, and spread takes a single number as shorthand for the same figure on all three axes.

So does every number inside a custom particlesize, lifetime, gravity, friction and the variation fields. Those are evaluated on the server, once, at the moment the burst is spawned, against the entity that spawned it:

{
    "type": "apoli:particle",
    "particle": {
        "type": "apoli:custom",
        "texture": "example:textures/particle/spark.png",
        "size": "0.1 + resource('example:charge') * 0.02",
        "lifetime": "10 + resource('example:charge')"
    },
    "count": "resource('example:charge')",
    "spread": "0.2 + resource('example:charge') * 0.05",
    "frequency": 2
}

That is one power whose particles grow, multiply and live longer as a resource fills, instead of five powers with a condition each.

roll and roll_speed stay as they were: they run on the client, once per particle, which is what makes "roll": "rUni(0, 360)" give every particle its own angle.

apoli:action_on_mouse_movement on NeoForge

The power reads the camera through a wrapper on Entity.turn. On NeoForge that wrapper was still the older cursor-speed-only version, so the client never accumulated anything and the power never fired. It fires now.