Skip to content

Make ice balls freeze entities instead of slowing them down - #121

Merged
Hugman76 merged 11 commits into
devfrom
claude/issue-118-ikarms
Sep 6, 2026
Merged

Make ice balls freeze entities instead of slowing them down#121
Hugman76 merged 11 commits into
devfrom
claude/issue-118-ikarms

Conversation

@Hugman76

@Hugman76 Hugman76 commented Aug 24, 2026

Copy link
Copy Markdown
Member

Closes #118.

An ice ball no longer hands out a slowness effect: it traps whatever it hits in a block of ice.

What a frozen entity does

  • Cannot moveLivingEntity.isImmobile() returns true, which cuts off the movement input and skips serverAiStep() entirely. The same check runs on the client that moves a player, so a frozen player cannot walk out of their own block of ice either.
  • Holds its last animation — the render state age is wound back to what it was when the ice took hold, so everything driven by it stands still; the walk animation is pinned at zero on top of that, since a shoved block of ice does travel.
  • Turns ice blue — drawn through super_mario:core/frozen_entity, which remaps the entity onto the six colours of the vanilla ice block texture. Built the same way as the Gold power-up's shader, with its own render pipeline and render type.
  • Is filled by a visible ice cube — the vanilla ice block model, scaled to the entity hitbox and handed to the block renderer, so it picks up the ice block texture directly.
  • Is solidcanBeCollidedWith returns true while frozen, so others walk into it and stand on top of it.
  • Slides when shoved — a player walking into its side sends it off along the heading they walked into it at, so coming at it from a corner sends it towards that corner. Ice has very little drag, so a shove carries it several blocks, and one that meets a wall at speed shatters against it.
  • Floats — a block of ice rides the water surface rather than sinking. The lift is what it displaces, scaled so it cancels gravity at FLOAT_SUBMERSION, which is where it comes to rest. That is set well above what real ice would ride at, so the head of whoever is inside stays out of the water.
  • Turns the next ice ball away — one that hits an entity already in the ice bursts against it the way it would against a wall, hurting neither what is inside nor the ice it landed on.

Who resists it

Resistance is judged on hitbox volume, with the bosses named one by one since nothing about a hitbox tells them apart from any other oversized mob:

Behaviour
Regular (a pig, a player) Frozen for 10 s, unharmed once it thaws
Big — hitbox volume ≥ 2 blocks³, i.e. an iron golem and up Cracks the ice open after 3 s, taking 4 damage doing so
Creative player An ice ball leaves them alone; /freeze still takes them
super_mario:freeze_immune (ender dragon, wither), spectators Never trapped, takes 4 damage instead

The last row is what Freezing.isUnfreezable covers — nothing at all can hold those. Everything above it is Freezing.resistanceOf, which is only what an ice ball asks.

The issue asks to copy the "big" conditions from the bubble catching system. Bubbles have since landed on dev (#113) and brought super_mario:bubble_can_trap / super_mario:bubble_cannot_trap with them, so the two systems now judge "big" by different rules — the volume rule here, tags there. Aligning them is left as follow-up rather than folded into this branch.

A frozen player can smash any of their movement keys to melt their way out sooner, 15 ticks per press, sent over a new super_mario:freeze/struggle payload.

/freeze

/freeze set <target> <time> puts an entity in the ice by hand, and /freeze query <target> reads back whether one is in there, answering with the usual 1 or 0.

The time is the whole of it, and it is mandatory — there is no toggle to trip over:

<time>
0 Thaws the target. Fails if it was not frozen, the way /effect clear does with nothing to clear
any other number Freezes for that many ticks, replacing whatever the target was already serving
infinite A freeze that nothing chips away at, and only a command or fire calls off

How it is stored

The freeze lives in a synced, persistent FreezeState attachment rather than on the entities themselves, which is what makes every living entity freezable without any of them knowing about it. It holds the start and the end of the freeze as game times rather than as a countdown, so the whole thing only has to reach the clients once and they work the rest out on their own — a countdown would have been a packet per entity per tick. An endless freeze is that end set to NEVER.

Tests

FreezeGameTest covers the three resistances, the solidity, the sliding, the floating and the struggling; FreezeCommandGameTest covers the command; BallGameTest.iceballSlowsDownWhatItHits became iceballFreezesWhatItHits, alongside a test that an ice ball bursts on what is already frozen.

./gradlew runDatagen && ./gradlew test && ./gradlew runGameTest — all 184 game tests pass, datagen reports no drift, and the unit tests are green.

The ice shader is not covered by those: render pipelines compile lazily on first use, so it wants a look during playtesting. The mixin behind it was checked by booting the client headless with -Dmixin.debug.export=trueFrozenLivingEntityRendererMixin applies to LivingEntityRenderer cleanly and its @ModifyReturnValue wraps all four of getRenderType's vanilla returns.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CYij5pCRAcCecyXMAKSsz2

@Hugman76
Hugman76 changed the base branch from main to dev August 24, 2026 09:34
An ice ball no longer hands out slowness: it traps whatever it hits in a
block of ice. While frozen, an entity cannot move, its animations hold
still, it takes on the pale blue of the ice, its hitbox is filled by an
actual ice block model, and it turns solid — others walk into it and
stand on top of it. A player walking into its side sends it sliding
straight along that axis.

How well an entity holds up depends on its bulk: anything from an iron
golem upwards cracks the ice open well before it melts and hurts itself
doing so, and the bosses listed under `super_mario:freeze_immune` are
never trapped at all, only hurt. A frozen player can smash their
movement keys to melt their way out sooner.

The freeze lives in a synced, persistent `FreezeState` attachment rather
than on the entities themselves, which is what makes every living entity
freezable without any of them knowing about it. It holds the start and
end of the freeze as game times, so the whole thing only has to reach
the clients once and they can work the rest out on their own.

Closes #118

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYij5pCRAcCecyXMAKSsz2
@Hugman76

Copy link
Copy Markdown
Member Author

Feature has been playtested and validated

Hugman76 and others added 2 commits August 29, 2026 10:58
# Conflicts:
#	mubble-super_mario/src/client/java/fr/hugman/mubble/super_mario/client/references/SuperMarioRenderStateDataKeys.java
#	mubble-super_mario/src/client/resources/super_mario.client.mixins.json
#	mubble-super_mario/src/datagen/java/fr/hugman/mubble/super_mario/data/provider/SuperMarioEnglishLangProvider.java
#	mubble-super_mario/src/datagen/java/fr/hugman/mubble/super_mario/data/provider/SuperMarioEntityTypeTagsProvider.java
#	mubble-super_mario/src/main/java/fr/hugman/mubble/super_mario/tags/SuperMarioEntityTypeTags.java
claude and others added 5 commits September 6, 2026 09:05
Tint frozen entities through a shader over the ice block's palette, the way
the Gold power-up does, in place of the flat multiply on `getModelTint`. The
six colours are the whole palette of the vanilla ice block texture, and the
render type is only substituted where vanilla settled on one, so an invisible
mob stays invisible inside the ice.

Rename `super_mario:melts_freeze` to `super_mario:melts_frozen_entities`.

Cut the comments back across the freezing code: drop the ones that restate
what the code says, and shorten those that were carrying more prose than the
point needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAdapm2U1S6rjAc4sCqwrp
`/freeze set` no longer flips the target when the value is left off: the
`<true|false>` is mandatory, and omitting it is now an error rather than a
silent toggle.

It also takes a duration, in ticks, or the literal `infinite` for a freeze
that only a command or fire calls off. `FreezeState` carries that as an
`endsAt` of `NEVER`, which nothing chips away at. A duration alongside
`false` is turned down, since it means nothing when thawing.

Split "cannot be frozen at all" from "an ice ball leaves it alone". A
creative player is only the latter, so the command — an operator's tool —
can now freeze one, while a stray ice ball still cannot. The tick that lets
unfreezable entities out follows the narrower rule, so a commanded freeze
survives it.

An ice ball that hits an already frozen entity now bursts against it the way
it would against a wall, with the block-hit sound: it no longer hurts what is
inside, nor cracks the ice it landed on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAdapm2U1S6rjAc4sCqwrp
A block of ice rides the surface instead of sinking. The lift is what it
displaces, scaled so it cancels gravity at FLOAT_SUBMERSION, so the block
comes to rest with that fraction of its height under the waterline: pushed up
while it rides lower, falling back while it rides higher.

Ice really floats with almost all of itself under. Sitting higher than that
keeps the head of whoever is inside above water, so a frozen mob shoved into
a pond bobs rather than drowns, and the cube stays in view.

Water also drags on a sliding block of ice much harder than air does, so a
shove into a pond runs itself out rather than skating across.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAdapm2U1S6rjAc4sCqwrp
`/freeze set <target> <time>` replaces `<true|false> [<ticks>|infinite]`. The
time is the whole of it: `0` thaws, `infinite` is a freeze that only a command
or fire calls off, and anything else freezes for that many ticks.

Setting a time on an entity that is already frozen now replaces what it was
serving rather than being turned down, the way `/effect give` replaces an
effect. `0` on an entity that is not frozen still fails, the way
`/effect clear` does with nothing to clear.

That leaves `already_frozen` and `thaw_duration` with no caller, so both are
gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAdapm2U1S6rjAc4sCqwrp
@Hugman76
Hugman76 merged commit c9da980 into dev Sep 6, 2026
1 check passed
@Hugman76
Hugman76 deleted the claude/issue-118-ikarms branch September 6, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ice balls should freeze enemies, not slow them down

2 participants