Skip to content

Add the Bubble Flower power-up - #113

Merged
Hugman76 merged 14 commits into
devfrom
copilot/add-bubble-flower-power-up
Aug 31, 2026
Merged

Add the Bubble Flower power-up#113
Hugman76 merged 14 commits into
devfrom
copilot/add-bubble-flower-power-up

Conversation

Copilot AI commented Mar 4, 2026

Copy link
Copy Markdown

Adds the Bubble Flower and the Bubble entity it shoots, as described in #111.

In Bubble form the player shoots bubbles that drift forward, catch what they run into, and can be bounced
on. Shooting one opens a 1.2 second window that fits two bubbles, counting the one that opened it; once
the window runs out both come back at once, and the next shot opens a fresh one.

Bubble entity

  • Travels forward with air friction and drifts upwards as its horizontal speed drops.
  • Cube hitbox, billboard sprite, one of four looks picked at random on spawn.
  • Rebounds off blocks with a squish animation, and never passes through them. Rebounds notify the block,
    so target blocks and the like react.
  • Holds a single entity as a regular passenger, so vanilla handles syncing and saving it. The bubble
    resizes to a slightly larger cube around whatever it holds; the drawn size eases into place while the
    hitbox changes in one step.
  • A trapped entity has its AI disabled, tumbles as if thrown in space, and is released unharmed when the
    bubble goes. The tumble leans along the bubble's heading at the moment of capture.
  • Catching a Super Mario entity swallows it: it spins, shrinks away, and is replaced by loot rolled from
    super_mario:gameplay/bubble_capture — a coin by default, and a real collectible entity rather than a
    stored stack, so it keeps a coin's size and spin.
  • No health, like a painting: any real hit pops it. Wind charges deal no damage, so they only blow it
    around. Explosions and portals work because the bubble runs the usual "inside block" effects.
  • Can be stomped for a vertical boost that keeps the stomper's horizontal momentum. The owner cannot stomp
    their own bubble for a short grace period, otherwise shooting one while airborne is a free flight.
  • Pops on its own after 5 seconds, or 5 seconds after catching something.

What can be trapped

Entities under 2 blocks in both directions with a base max health of 20 or less, or anything in
super_mario:bubble_can_trap. Health is read from the base attribute, so a leader zombie still counts as
an ordinary zombie. super_mario:bubble_cannot_trap holds the exceptions — bosses, and players.

Empty bubbles steer slightly towards a nearby trappable entity, as an aim assist rather than as homing.

Core module

The power-up charge system needed work to support firing in bursts:

  • New BURST_RECHARGE charge counting: the first use opens a window of a set length, the uses that follow
    run out the same window, and every charge comes back at once when it is over. The Bubble Flower runs on
    2 charges per 24 tick window.
  • New TIMED_RECHARGE charge counting, one charge back at a fixed interval, built on the previously
    unused cooldown field. Both countdowns are now started from inside the properties, so an action never
    has to drive the cooldown itself.
  • How a power-up counts its charges moved into a PowerUpCharges record (counting, max, interval), so it
    is configured from data instead of being guessed from whichever fields the action happens to set:
    shoot_projectile takes a single charges field, and any counting can be paired with any max and
    interval.
  • Power-up properties are only synced when the charge count actually changes, instead of on every tick of
    a running cooldown.
  • addEntity() split into useCharge() and trackEntity(), so the tracked entity list no longer grows
    forever outside FROM_ACTIVE_ENTITIES.
  • Projectiles are shot centered on the eye line instead of resting their bottom on it.

Notes

  • All the new content lives in the super_mario module; the charge system changes are in mubble-core.
  • The bubble is customisable from NBT: lifetime, filled_lifetime, texture, capture_motion, and
    vanilla Passengers for what it holds.
  • Run ./gradlew runDatagen after checking out: the generated sounds.json still lists the rebound sound
    that this branch removes.

Closes #111

Copilot AI and others added 2 commits March 4, 2026 11:49
…nd supporting registrations

Co-authored-by: Hugman76 <36963654+Hugman76@users.noreply.github.com>
…tBubblePowerUpAction

Co-authored-by: Hugman76 <36963654+Hugman76@users.noreply.github.com>
Copilot AI changed the title [WIP] Add Bubble Flower power-up item Add Bubble Flower power-up with Bubble entity and timed charge recharge Mar 4, 2026
Copilot stopped work on behalf of Hugman76 due to an error March 4, 2026 13:19
Co-authored-by: Hugman76 <36963654+Hugman76@users.noreply.github.com>
Copilot AI changed the title Add Bubble Flower power-up with Bubble entity and timed charge recharge Fix bubble billboard rendering and consolidate bubble shooting into ShootProjectilePowerUpAction Mar 4, 2026
Co-authored-by: Hugman76 <36963654+Hugman76@users.noreply.github.com>
Copilot AI changed the title Fix bubble billboard rendering and consolidate bubble shooting into ShootProjectilePowerUpAction Fix Bubble: billboard rendering, physics, entity trapping, and stomping Mar 4, 2026
Hugman76 and others added 4 commits August 8, 2026 22:59
…ging

The entity did not compile (startRiding(Entity, boolean) does not exist in
this version) and several behaviours were inverted or missing compared to the
design in issue #111.

Bubble entity, rewritten:
- float upwards as horizontal speed drops, instead of the other way around
- hold whatever is inside as a regular passenger, so vanilla syncs and saves
  it, and the client bounding box (and sprite scale) is finally correct
- rebound off blocks from the actual movement rather than from a pre-emptive
  ray cast, with a squish animation and no rebound spam while resting
- persist lifetimes as saved counters instead of comparing to tickCount, which
  is not persisted and made reloaded bubbles immortal
- swallow Super Mario entities into their capture loot, hold items as
  collectibles, and pop for anything that touches a filled bubble
- restore a trapped mob's AI from removePassenger, the single point every way
  out of a bubble goes through
- keep the stomper's horizontal momentum by reading getKnownMovement()
- steer towards nearby targets like an aim assist rather than homing in

Power-up recharging:
- only sync power-up properties when the charge count actually changes, rather
  than on every tick of a running cooldown
- split addEntity() into useCharge() and trackEntity(), so the tracked entity
  list no longer grows forever outside FROM_ACTIVE_ENTITIES
- shoot projectiles centered on the eye line instead of resting their bottom
  on it

Adds the capture loot table, the trap blacklist and collectible item tags, a
placeholder bubble texture, and the client-side tumble for trapped entities.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings in the port to 26.2-snapshot-3 and the *Keys -> *Ids reference rename.

Conflict resolutions:
- registries and datagen providers keep dev's *Ids references, with the bubble
  entries added back on top
- tag providers move to dev's builder(...) form taking ids; the module's
  FIREBALL id is qualified because vanilla now exposes one under that name too
- CameraRenderState moved to the state.level package, so BubbleRenderer follows

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- run applyEffectsFromBlocks() after move(). Entity.move() only records the
  blocks that were crossed; without this call nothing runs their "entity
  inside" behaviour, which is why bubbles went through Nether portals without
  teleporting and never pressed pressure plates
- notify the block a bubble rebounds against through onProjectileHit, so
  target blocks (and anything else keyed on projectile hits) react
- pop on any damage above zero instead of only on projectile and explosion
  sources, so a bare fist or a sword pops a bubble like it would a painting.
  Wind charges still deal no damage, so they only blow it around
- pop on contact with a foreign projectile straight from the bubble's own
  collision scan, rather than relying on the projectile choosing to deal
  damage to a target that has no health

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Hugman76 Hugman76 changed the title Fix Bubble: billboard rendering, physics, entity trapping, and stomping Add the Bubble Flower power-up Aug 19, 2026
@Hugman76
Hugman76 self-requested a review August 21, 2026 17:11
@Hugman76

Copy link
Copy Markdown
Member

The cooldown timers and refill conditions need to be checked up against Mario Wonder again, I'm not sure about them.

@Hugman76

Copy link
Copy Markdown
Member

After trying in wonder, firing a bubble opens a window of roughly 1,2 seconds where you can fire at most 2 bubbles (counting the initial bubble). Then that window rests until the next time you fire a bubble.
We might need a new refill type for this, and make it highly configurable

claude and others added 2 commits August 29, 2026 15:33
In Wonder, shooting a bubble opens a window of about 1.2 seconds that fits two
bubbles, counting the one that opened it. The window then runs out and resets,
waiting for the next shot.

- New BURST_RECHARGE charge counting: the first use opens a window of `interval`
  ticks, and every charge comes back at once once it runs out.
- Charge setup moves into a PowerUpCharges record, so the counting, the number of
  charges and the interval all come from data instead of being guessed from
  whichever fields happen to be set on the action.
- The countdown is now started from inside the properties, so an action no longer
  has to set the cooldown itself.
- The Bubble Flower shoots 2 bubbles per 24 tick window.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NyTB2gv3uaiYrdtDdRTbor
Brings in the port to 26.2 release, the koopa shell rework, the cloud platform
placement search, ball trail particles and the new test and testmod module.

Conflict resolutions:
- EntityMixin keeps dev's cheaper move() hook, which reads the resolved
  movement out of a local instead of running collide() a second time, along
  with the fall grace this branch adds
- CollectibleItem keeps the shared cosmetics helper over the inlined copy, with
  dev's renamed stack config call
- SpawnCloudPlatformPowerUpAction keeps dev's placement search, calling
  useCharge() since addEntity() was split in two on this branch

Test fallout from the API changes this branch makes:
- the game tests and their data pack fixtures move to PowerUpCharges and to
  useCharge(), the charge count having moved into a record
- the PowerUpProperties unit tests drive the cooldown through useCharge() and
  the constructor, setCooldown() no longer being public, and cover the interval
  that the codecs gained
- super_mario:bubble_can_trap is now written out empty rather than left
  undeclared, and is exempt from the "a declared tag must not be empty" check:
  it exists so data packs can make something trappable that the automatic size
  and health criteria turn down

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Hugman76
Hugman76 marked this pull request as ready for review August 31, 2026 18:12
Comment on lines +34 to +36
// Shipped empty on purpose: the opposite exception, for data packs to make something trappable that the
// automatic size and health criteria turn down. Still written out, so the tag is there to be added to.
builder(BUBBLE_CAN_TRAP);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we don't HAVE to put the tag in the datagen to make it available for other datapacks

SuperMarioEntityTypes.BUBBLE,
Optional.empty(), // the bubble plays its own "appear" sound as it spawns
0.4f,
// Wonder gives a 1.2s window per burst, and two bubbles to spend in it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary comment

Comment on lines +13 to +14
/** Items that come back to the world as a {@code mubble:collectible} rather than as a plain item entity. */
public static final TagKey<Item> SPAWNS_AS_COLLECTIBLE = bind("spawns_as_collectible");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tag should be renamed as bubble/catch_as_collectible. Comment is unnecessary.

Comment on lines +81 to +129
/** Ticks before an empty bubble pops on its own. */
public static final int DEFAULT_LIFETIME = 100;
/** Ticks before a bubble pops after it caught something. */
public static final int DEFAULT_FILLED_LIFETIME = 100;
/** Grace period during which the bubble ignores its owner, so it does not pop right where it spawned. */
public static final int OWNER_POP_DELAY = 20;
/**
* Grace period during which the owner cannot stomp their own bubble. Without it, shooting one while airborne
* hands out a free bounce straight away, which is enough to fly forever.
*/
public static final int OWNER_STOMP_DELAY = 10;
/** Ticks the capture animation lasts before the caught entity turns into its loot. */
public static final int ABSORB_DURATION = 12;
/** Ticks the squish animation lasts after rebounding against a block. */
public static final int SQUISH_DURATION = 6;
/** Ticks the wobble lasts after the bubble closes around something. */
public static final int CAPTURE_WOBBLE_DURATION = 10;

public static final double ATTRACT_RADIUS = 2.0;
public static final float MAX_TRAPPABLE_SIZE = 2.0f;
/** Zombies and skeletons sit exactly at 20 HP, and the issue lists them as trappable. */
public static final float MAX_TRAPPABLE_HEALTH = 20.0f;
public static final float BASE_SIZE = 0.75f;
public static final float TRAPPED_PADDING = 0.25f;
public static final float MAX_SIZE = MAX_TRAPPABLE_SIZE + TRAPPED_PADDING;

private static final double AIR_FRICTION = 0.97;
/** Horizontal speed above which the bubble does not rise at all. */
private static final double FLOAT_SPEED_THRESHOLD = 0.25;
private static final double FLOAT_MAX_UP = 0.04;
private static final double FLOAT_LERP = 0.12;
private static final double REBOUND_RESTITUTION = 0.6;
/** Below this, a blocked component is just a bubble resting against a block, not a rebound worth animating. */
private static final double MIN_REBOUND_SPEED = 0.01;
/** How much of the way towards the target the heading turns each tick. */
private static final double AIM_ASSIST_STRENGTH = 0.09;
/** Cosine of the half-angle of the cone the target has to be in. Roughly 55 degrees. */
private static final double AIM_ASSIST_MIN_DOT = 0.57;
private static final float STOMP_BOOST = 0.7f;
/**
* Blocks of free fall handed out by a bounce. Vanilla clamps accumulated fall to one block when a
* wind charge throws you up; a bubble is kinder than that and starts the next fall in credit, so that
* chaining bounces does not end in a broken ankle.
*/
private static final double BOUNCE_FALL_GRACE = 2.0;
/** How fast the drawn size catches up with the real one when the bubble swallows or releases something. */
private static final float SIZE_LERP = 0.4F;
/** Peak of the squash-and-stretch that runs when the bubble closes around something. */
private static final float CAPTURE_WOBBLE_AMOUNT = 0.3F;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are too many comments here. No need to clear them all, but some constants are very easy to understand without any comment.

builder.define(DATA_FILLED_LIFETIME, DEFAULT_FILLED_LIFETIME);
}

// ---------------------------------------------------------------- state

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a weird comment


. It should be replaced with capitalized Intellij region/endregion


@Override
public EntityDimensions getDimensions(Pose pose) {
// Always a cube, whatever is inside.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnessecary comment.

- Drop the empty `bubble_can_trap` entry from the entity type tag datagen:
  data packs can define the tag themselves, the mod does not have to ship
  the file for it to be usable. The dynamic content game test now skips
  data pack hooks entirely instead of only skipping the emptiness check.
- Rename `mubble:spawns_as_collectible` to `mubble:bubble/catch_as_collectible`.
- Replace the horizontal rule section comments in `Bubble` with capitalized
  IntelliJ region/endregion markers.
- Trim comments that only restate what they sit on: the Wonder burst note in
  the power-up datagen, the constants in `Bubble` whose names already say it,
  and the one in `getDimensions`.
@Hugman76
Hugman76 merged commit cfd6145 into dev Aug 31, 2026
1 check passed
@Hugman76
Hugman76 deleted the copilot/add-bubble-flower-power-up branch August 31, 2026 21:16
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.

3 participants