// features/bounce.js; springy blocks that launch a player who lands on them. // Trampoline builds energy while you keep jumping (higher the longer you pump / hold jump) and decays it when you don't. import { world, system, safeGetBlock, InputButton, ButtonState } from "../core/mc.js"; const BOUNCE_BLOCKS = new Set(["rzb_dcs:dv_mid", "rzb_dcs:dv_end"]); // diving board planks (front/stairs is a stepup, not springy) const TRAMP_BLOCKS = new Set(["rzb_dcs:tr_fl", "rzb_dcs:tr_fr", "rzb_dcs:tr_bl", "rzb_dcs:tr_br"]); // trampoline mat cells (full-cube: stood ON TOP) const FALL_MIN = 0.12; // ignore near-zero impact (walking) so standing never bounces const SURFACE_MAX = 0.72; // act only when feet are AT the plank surface, not the band above (avoids mid-air re-bounces) const REST = 0.96, MAX = 1.5; // diving board: strength = min(impact*REST, MAX) // trampoline energy model (vertical launch speed, persists across bounces) const TRAMP_MAX = 1.7; // cap on launch const PUMP = 0.3; // added per bounce while jumping; also the floor of an active pump const DAMP = 0.82; // passive bounces shrink by this each time, so you settle when you stop const SEED = 0.85; // fresh landing starts from this fraction of your fall speed... const SEED_MIN = 0.62; // ...but never less than this, so the first bounce always pops const SETTLE = 0.15; // below this a decaying bounce stops, so you can stand const JUMP_GRACE = 8; // treat jump as "held" if pressed within this many ticks of contact const _last = new Map(); // player.id -> tick of last bounce (debounce) const _lastVy = new Map(); // player.id -> previous tick's vy (pre-collision impact; contact-tick vy is clamped to ~0) const _energy = new Map(); // player.id -> current trampoline energy (absent = not in a bounce cycle) const _jumpTick = new Map();// player.id -> last tick the jump button was down const jumpDown = (p) => { try { return p.inputInfo.getButtonState(InputButton.Jump) === ButtonState.Pressed; } catch { return false; } }; system.runInterval(() => { const t = system.currentTick; for (const p of world.getAllPlayers()) { if (jumpDown(p)) _jumpTick.set(p.id, t); let v; try { v = p.getVelocity(); } catch { continue; } const vy = v ? v.y : 0, prevVy = _lastVy.get(p.id) ?? 0; _lastVy.set(p.id, vy); const l = p.location, by = Math.floor(l.y), x = Math.floor(l.x), z = Math.floor(l.z); const feet = safeGetBlock(p.dimension, { x, y: by, z }); let tramp = false, ok = false; if (feet && BOUNCE_BLOCKS.has(feet.typeId)) { ok = (l.y - by) <= SURFACE_MAX; } // plank: feet AT surface else { const below = safeGetBlock(p.dimension, { x, y: by - 1, z }); if (below && TRAMP_BLOCKS.has(below.typeId)) { tramp = true; ok = true; } } let grounded = false; try { grounded = p.isOnGround; } catch {} if (!ok) { if (grounded) _energy.delete(p.id); continue; } // landed off the trampoline -> reset its energy if (grounded) { try { p.addEffect("jump_boost", 8, { amplifier: tramp ? 1 : 0, showParticles: false }); } catch {} } const impact = Math.max(-vy, -prevVy); // more-negative of this/last tick (contact vy is clamped by collision) if (impact < FALL_MIN) continue; if (t - (_last.get(p.id) ?? -99) < 4) continue; // one bounce per landing _last.set(p.id, t); let strength; if (tramp) { const held = t - (_jumpTick.get(p.id) ?? -99) <= JUMP_GRACE; let e = _energy.get(p.id); if (e === undefined) e = Math.min(Math.max(impact * SEED, SEED_MIN), TRAMP_MAX); // fresh landing: seed from the fall, floored else if (held) e = Math.min(Math.max(e, 0) + PUMP, TRAMP_MAX); // pumping/holding jump: build up else e = e * DAMP; // passive: shrink and settle if (held) e = Math.max(e, PUMP); // a jumped bounce always pops if (e < SETTLE) { _energy.delete(p.id); continue; } // fully settled: let them stand _energy.set(p.id, e); strength = e; } else { strength = Math.min(impact * REST, MAX); } try { p.applyKnockback({ x: 0, z: 0 }, strength); } catch { try { p.applyKnockback(0, 0, 0, strength); } catch {} } // 2.0.0 vs older signature } }, 1);