You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
491 lines
36 KiB
JavaScript
491 lines
36 KiB
JavaScript
// features/rideables.js block <-> rideable-entity vehicles (tube, flamingo, lounger, bike, surfboard, basket bike, sled).
|
|
|
|
// Parks as a block; a ride_* interact spawns a steerable entity; riderless it reverts (color + heading kept).
|
|
// Water craft steer via applyImpulse + no-Y setRotation (engine keeps float); land bikes velocity-drive with gravity.
|
|
import { world, system, BlockPermutation, ItemStack, isCreative, getDir, safeGetBlock } from "../core/mc.js";
|
|
import { RIGHT, FWD, TUBE_DIR_YAW, tubeYawToDir, tubeWaterTop, tubeOverWater, loungerSupportY, fcCanReplace } from "../core/grid.js";
|
|
import { BLOCK_TOP, BLOCK_PASSABLE } from "../core/block_heights.js";
|
|
import { undoPlace, breakAir } from "../core/fx.js";
|
|
import { registerVariants } from "../core/variants.js";
|
|
import { onPlace, onBreak } from "../core/events.js";
|
|
import { LOUNGER, LOUNGER_COLORS } from "./mb_grid.js";
|
|
|
|
const R = (dir) => RIGHT[dir] || RIGHT.south, F = (dir) => FWD[dir] || FWD.south;
|
|
|
|
// ---------- shared 2-cell "grid vehicle" scaffolding (surfboard-lay, basket bike, sled; lounger reuses placeGridBlocks) ----------
|
|
// cellWorld/anchorFrom rotate a cell offset by facing; parse maps a block id -> {cell, color} via the PREFIX map.
|
|
function makeCellHelpers(CELLS, PREFIX) {
|
|
const cellWorld = (cell, ax, ay, az, dir) => { const g = CELLS[cell], r = R(dir), f = F(dir); return { x: ax + g[0] * r[0] + g[2] * f[0], y: ay + g[1], z: az + g[0] * r[2] + g[2] * f[2] }; };
|
|
const anchorFrom = (cell, x, y, z, dir) => { const g = CELLS[cell], r = R(dir), f = F(dir); return { x: x - g[0] * r[0] - g[2] * f[0], y: y - g[1], z: z - g[0] * r[2] - g[2] * f[2] }; };
|
|
const parse = (id) => { if (!id) return null; for (const cell in PREFIX) { const px = PREFIX[cell]; if (id.startsWith(px)) return { cell, color: id.slice(px.length) }; } return null; };
|
|
return { cellWorld, anchorFrom, parse };
|
|
}
|
|
// Place a rig's cells iff every target block is replaceable (surfboard also requires water directly below each). blockFor(cell)->id.
|
|
function placeGridBlocks(dim, ax, ay, az, dir, CELLS, cellWorld, blockFor, requireWaterBelow = false) {
|
|
const cells = Object.keys(CELLS).map((c) => ({ c, p: cellWorld(c, ax, ay, az, dir) }));
|
|
for (const { p } of cells) {
|
|
const b = safeGetBlock(dim, p); if (!b || !(b.isAir || b.isLiquid)) return false;
|
|
if (requireWaterBelow) { const below = safeGetBlock(dim, { x: p.x, y: p.y - 1, z: p.z }); if (!below || !(below.typeId || "").endsWith("water")) return false; }
|
|
}
|
|
for (const { c, p } of cells) { const b = safeGetBlock(dim, p); try { b.setPermutation(BlockPermutation.resolve(blockFor(c), { "minecraft:cardinal_direction": dir })); } catch {} }
|
|
return true;
|
|
}
|
|
// Anchor + one-forward rig (basket bike, sled): placing the anchor auto-builds the front cell; breaking either removes the pair.
|
|
function registerFrontPairRig(BASE, FRONT_PREFIX, CELLS, cellWorld, anchorFrom, parse, label) {
|
|
onPlace((ev) => {
|
|
const b = ev.block; if (!b.typeId.startsWith(BASE)) return;
|
|
const color = b.typeId.slice(BASE.length), dim = b.dimension, dir = getDir(b.permutation), fb = safeGetBlock(dim, cellWorld("0_0_1", b.x, b.y, b.z, dir));
|
|
if (!fb || !fcCanReplace(fb)) { undoPlace(b, ev.player, BASE + color, label); return; }
|
|
try { fb.setPermutation(BlockPermutation.resolve(FRONT_PREFIX + color, { "minecraft:cardinal_direction": dir })); } catch {}
|
|
});
|
|
onBreak((ev) => {
|
|
const sp = parse(ev.brokenBlockPermutation?.type?.id || ""); if (!sp) return;
|
|
const b = ev.block, dim = b.dimension, dir = getDir(ev.brokenBlockPermutation), a = anchorFrom(sp.cell, b.x, b.y, b.z, dir);
|
|
for (const c of Object.keys(CELLS)) { if (c === sp.cell) continue; const p = cellWorld(c, a.x, a.y, a.z, dir); const nb = safeGetBlock(dim, p); if (nb && parse(nb.typeId)?.cell === c) { try { breakAir(nb); } catch {} } }
|
|
});
|
|
}
|
|
// Board a 2-cell rig: air its cells, spawn its entity at the rig midpoint (+half a cell forward), seat the rider. cfg.spawnYaw
|
|
// (dir, player) gives the spawn heading (per vehicle); cfg.waterGate requires water (surfboard). Fallback: re-place the blocks.
|
|
function board2Cell(player, block, cfg) {
|
|
const p = cfg.parse(block.typeId); if (!p) return false;
|
|
if (cfg.waterGate && !tubeOverWater(block)) return false;
|
|
const dim = block.dimension, dir = getDir(block.permutation), color = p.color, a = cfg.anchorFrom(p.cell, block.x, block.y, block.z, dir);
|
|
for (const c of Object.keys(cfg.cells)) { const cp = cfg.cellWorld(c, a.x, a.y, a.z, dir); const bb = safeGetBlock(dim, cp); if (bb && cfg.parse(bb.typeId)) { try { bb.setType("minecraft:air"); } catch {} } }
|
|
const f = F(dir), loc = { x: a.x + 0.5 + f[0] * 0.5, y: a.y, z: a.z + 0.5 + f[2] * 0.5 };
|
|
let ent; try { ent = dim.spawnEntity(cfg.entity, loc); } catch {}
|
|
if (!ent) { cfg.place(dim, a.x, a.y, a.z, dir, color); return true; }
|
|
try { ent.setProperty("rzb_dcs:color", Math.max(0, cfg.colors.indexOf(color))); } catch {}
|
|
try { ent.setDynamicProperty("spawn", system.currentTick); } catch {}
|
|
try { ent.teleport(loc, { dimension: dim, rotation: { x: 0, y: cfg.spawnYaw(dir, player) } }); } catch {}
|
|
try { ent.getComponent("minecraft:rideable")?.addRider(player); } catch {}
|
|
return true;
|
|
}
|
|
// Riderless revert of a 2-cell rig: re-place the parked blocks (or drop the anchor item). cfg.revertDir(yaw) = block facing;
|
|
// cfg.surfaceSnap floats the parked blocks to the water surface (surfboard), else they revert at the entity's floor.
|
|
function revert2Cell(t, dim, cfg) {
|
|
let yaw = 0; try { yaw = t.getRotation().y; } catch {}
|
|
const dir = cfg.revertDir(yaw), f = F(dir);
|
|
let ci = 0; try { ci = t.getProperty("rzb_dcs:color") ?? 0; } catch {}
|
|
const color = cfg.colors[ci] ?? cfg.colors[0];
|
|
const l = t.location, ax = Math.floor(l.x - 0.5 * f[0]), az = Math.floor(l.z - 0.5 * f[2]);
|
|
let my = Math.floor(l.y);
|
|
if (cfg.surfaceSnap) { const topW = tubeWaterTop(dim, Math.floor(l.x), Math.floor(l.z), l.y); my = topW !== null ? topW + 1 : Math.floor(l.y); }
|
|
try { t.remove(); } catch {}
|
|
if (!cfg.place(dim, ax, my, az, dir, color)) { try { dim.spawnItem(new ItemStack(cfg.base + color, 1), { x: l.x, y: my + 1, z: l.z }); } catch {} }
|
|
}
|
|
|
|
const TUBE_ENTITY = "rzb_dcs:pool_tube";
|
|
// Special (non-colour) pool tubes: each has its OWN model/entity, NO colour property, and a facing offset (the directional
|
|
// models render 180 off the round tube). Add one here plus its assets and it's fully wired.
|
|
const TUBE_SPECIAL = { flamingo: { entity: "rzb_dcs:pool_tube_flamingo", yawOff: 180 }, unicorn: { entity: "rzb_dcs:pool_tube_unicorn", yawOff: 180 } };
|
|
const TUBE_SPECIAL_BY_ENT = {}; for (const name in TUBE_SPECIAL) TUBE_SPECIAL_BY_ENT[TUBE_SPECIAL[name].entity] = { name, ...TUBE_SPECIAL[name] };
|
|
const TUBE_COLORS = ["black", "blue", "cyan", "green", "light_blue", "lime", "magenta", "orange", "pink", "purple", "red", "yellow"];
|
|
const TUBE_SPEED = 0.18;
|
|
registerVariants(["rzb_dcs:su02_black", "rzb_dcs:su02_orange", "rzb_dcs:su02_magenta", "rzb_dcs:su02_light_blue", "rzb_dcs:su02_yellow", "rzb_dcs:su02_lime", "rzb_dcs:su02_pink", "rzb_dcs:su02_cyan", "rzb_dcs:su02_purple", "rzb_dcs:su02_blue", "rzb_dcs:su02_green", "rzb_dcs:su02_red"]);
|
|
|
|
function boardTube(player, block) {
|
|
const m = block.typeId.match(/^rzb_dcs:su02_(.+)$/); if (!m) return false;
|
|
const special = TUBE_SPECIAL[m[1]];
|
|
const ci = special ? -1 : TUBE_COLORS.indexOf(m[1]); if (!special && ci < 0) return false;
|
|
if (!tubeOverWater(block)) return false; // on land the tube stays a plain block
|
|
const dim = block.dimension, dir = getDir(block.permutation), loc = { x: block.x + 0.5, y: block.y, z: block.z + 0.5 };
|
|
try { block.setType("minecraft:air"); } catch {}
|
|
let ent; try { ent = dim.spawnEntity(special ? special.entity : TUBE_ENTITY, loc); } catch {}
|
|
if (!ent) { try { block.setPermutation(BlockPermutation.resolve("rzb_dcs:su02_" + m[1], { "minecraft:cardinal_direction": dir })); } catch {} return true; }
|
|
if (!special) { try { ent.setProperty("rzb_dcs:color", ci); } catch {} }
|
|
try { ent.setDynamicProperty("spawn", system.currentTick); } catch {}
|
|
try { ent.teleport(loc, { dimension: dim, rotation: { x: 0, y: (TUBE_DIR_YAW[dir] ?? 0) + (special ? special.yawOff : 0) } }); } catch {}
|
|
try { ent.getComponent("minecraft:rideable")?.addRider(player); } catch {}
|
|
return true;
|
|
}
|
|
|
|
function revertTube(t, dim) { // handles the coloured tube AND the special tubes (flamingo/unicorn: no colour property)
|
|
const sp = TUBE_SPECIAL_BY_ENT[t.typeId];
|
|
let color = sp ? sp.name : "blue", yawOff = sp ? sp.yawOff : 0;
|
|
if (!sp) { let ci = 0; try { ci = t.getProperty("rzb_dcs:color") ?? 0; } catch {} color = TUBE_COLORS[ci] ?? "blue"; }
|
|
let yaw = 0; try { yaw = t.getRotation().y; } catch {}
|
|
const l = t.location, wx = Math.floor(l.x), wz = Math.floor(l.z);
|
|
const topW = tubeWaterTop(dim, wx, wz, l.y);
|
|
const pos = { x: wx, y: topW !== null ? topW + 1 : Math.floor(l.y), z: wz };
|
|
try { t.remove(); } catch {}
|
|
const perm = () => BlockPermutation.resolve("rzb_dcs:su02_" + color, { "minecraft:cardinal_direction": tubeYawToDir(yaw + yawOff) });
|
|
const b = safeGetBlock(dim, pos);
|
|
if (b && (b.isAir || b.isLiquid)) { try { b.setPermutation(perm()); } catch {} }
|
|
else if (b) {
|
|
const up = safeGetBlock(dim, { x: pos.x, y: pos.y + 1, z: pos.z });
|
|
if (up && (up.isAir || up.isLiquid)) { try { up.setPermutation(perm()); } catch {} }
|
|
else { try { dim.spawnItem(new ItemStack("rzb_dcs:su02_" + color, 1), { x: pos.x + 0.5, y: pos.y + 1, z: pos.z + 0.5 }); } catch {} }
|
|
}
|
|
}
|
|
|
|
const LOUNGER_ENTITY = "rzb_dcs:pool_lounger";
|
|
const LOUNGER_SPEED = 0.16;
|
|
const LG_BASE = LOUNGER.prefix, LG_ANCHOR = LOUNGER.anchorKey;
|
|
const placeLoungerBlocks = (dim, ax, ay, az, dir, color) => placeGridBlocks(dim, ax, ay, az, dir, LOUNGER.cells, LOUNGER.cellWorld, (c) => LOUNGER.idFor(c, color));
|
|
|
|
// Shared steering basis for every vehicle. Travel dir = flattened look; near-vertical look jitters (divide by ~0), so
|
|
// require fl > 0.2 else HOLD heading from entity yaw minus yawOff (caller's setRotation re-adds it). `look` gates re-aiming.
|
|
function steerForward(p, ent, yawOff = 0) {
|
|
const v = p.getViewDirection(), fl = Math.hypot(v.x, v.z), look = fl > 0.2;
|
|
if (look) return { fx: v.x / fl, fz: v.z / fl, look: true };
|
|
let y0 = 0; try { y0 = ent.getRotation().y; } catch {}
|
|
const r = (y0 - yawOff) * Math.PI / 180;
|
|
return { fx: -Math.sin(r), fz: Math.cos(r), look: false };
|
|
}
|
|
|
|
// Every buoyant water craft (tube, flamingo, lounger, surfboard) steers identically: impulse horizontal velocity toward
|
|
// look+WASD * speed (coast when idle), face the travel heading. Only the pose anim, speed, yaw offset, and turn style
|
|
// vary smoothTurn>0 lerps the body toward heading (the round tube), 0 snaps instantly (directional lounger/surfboard).
|
|
function driveWaterCraft(p, boat, anim, speed, yawOff = 0, smoothTurn = 0) {
|
|
try { p.playAnimation(anim, { blendOutTime: 0, stopExpression: "!q.is_riding" }); } catch {}
|
|
let cur; try { cur = boat.getVelocity(); } catch { cur = { x: 0, y: 0, z: 0 }; }
|
|
let mv; try { mv = p.inputInfo.getMovementVector(); } catch { mv = null; }
|
|
const { fx, fz, look } = steerForward(p, boat, yawOff);
|
|
const rx = fz, rz = -fx;
|
|
if (mv && (mv.x !== 0 || mv.y !== 0)) { const dx = (fx * mv.y + rx * mv.x) * speed, dz = (fz * mv.y + rz * mv.x) * speed; try { boat.applyImpulse({ x: dx - cur.x, y: 0, z: dz - cur.z }); } catch {} }
|
|
else { try { boat.applyImpulse({ x: -cur.x * 0.25, y: 0, z: -cur.z * 0.25 }); } catch {} }
|
|
if (look) { const target = -Math.atan2(fx, fz) * 180 / Math.PI + yawOff;
|
|
try { if (smoothTurn > 0) { let cy = 0; try { cy = boat.getRotation().y; } catch {} const d = ((target - cy + 540) % 360) - 180; boat.setRotation({ x: 0, y: cy + d * smoothTurn }); } else boat.setRotation({ x: 0, y: target }); } catch {} }
|
|
}
|
|
function driveLounger(p, boat) { driveWaterCraft(p, boat, "animation.rzb_dcs.ride_lounger", LOUNGER_SPEED); }
|
|
function driveTube(p, boat) { driveWaterCraft(p, boat, "animation.rzb_dcs.ride_pool_tube", TUBE_SPEED, 0, 0.12); }
|
|
function boardLounger(player, block) {
|
|
const pr = LOUNGER.parse(block.typeId); if (!pr) return false; const cell = pr.cell, color = pr.color;
|
|
if (!tubeOverWater(block)) return false; // on land the lounger stays furniture (generic sit handles it)
|
|
const dim = block.dimension, dir = getDir(block.permutation), a = LOUNGER.anchorFrom(cell, block.x, block.y, block.z, dir);
|
|
for (const c of Object.keys(LOUNGER.cells)) { const p = LOUNGER.cellWorld(c, a.x, a.y, a.z, dir); const b = safeGetBlock(dim, p); if (b && LOUNGER.parse(b.typeId)) { try { b.setType("minecraft:air"); } catch {} } }
|
|
const mid = LOUNGER.cellWorld("0_0_1", a.x, a.y, a.z, dir), loc = { x: mid.x + 0.5, y: mid.y, z: mid.z + 0.5 };
|
|
let ent; try { ent = dim.spawnEntity(LOUNGER_ENTITY, loc); } catch {}
|
|
if (!ent) { placeLoungerBlocks(dim, a.x, a.y, a.z, dir, color); return true; }
|
|
try { ent.setProperty("rzb_dcs:color", Math.max(0, LOUNGER_COLORS.indexOf(color))); } catch {} // carry the colour onto the entity
|
|
try { ent.setDynamicProperty("spawn", system.currentTick); } catch {}
|
|
try { ent.teleport(loc, { dimension: dim, rotation: { x: 0, y: TUBE_DIR_YAW[dir] ?? 0 } }); } catch {}
|
|
try { ent.getComponent("minecraft:rideable")?.addRider(player); } catch {}
|
|
return true;
|
|
}
|
|
function revertLounger(t, dim) {
|
|
let yaw = 0; try { yaw = t.getRotation().y; } catch {}
|
|
let ci = 0; try { ci = t.getProperty("rzb_dcs:color") ?? 0; } catch {} const color = LOUNGER_COLORS[ci] || "rainbow";
|
|
const dir = tubeYawToDir(yaw + 180);
|
|
const l = t.location, wx = Math.floor(l.x), wz = Math.floor(l.z), topW = tubeWaterTop(dim, wx, wz, l.y);
|
|
const my = topW !== null ? topW + 1 : Math.floor(l.y), a = LOUNGER.anchorFrom("0_0_1", wx, my, wz, dir);
|
|
try { t.remove(); } catch {}
|
|
if (!placeLoungerBlocks(dim, a.x, a.y, a.z, dir, color)) { try { dim.spawnItem(new ItemStack(LOUNGER.idFor(LG_ANCHOR, color), 1), { x: wx + 0.5, y: my + 1, z: wz + 0.5 }); } catch {} }
|
|
}
|
|
|
|
const BIKE_ENTITY = "rzb_dcs:mountain_bike";
|
|
const BIKE_COLORS = ["black", "blue", "green", "purple", "red", "silver"]; // entity color-index map (tied to rzb_dcs:color); do NOT reorder
|
|
registerVariants(["black", "purple", "blue", "green", "red", "silver"].map((c) => "rzb_dcs:su29_" + c)); // brush cycle order (canonical), decoupled from the index map
|
|
const BIKE_MAX = 0.42, BIKE_ACCEL = 0.02, BIKE_DECEL = 0.03, BIKE_STRAFE = 0.14, BIKE_ROLL = 0.12, BIKE_STEP = 1.05, BIKE_WHEEL = 0.62;
|
|
const BIKE_HOP = 0.38, BIKE_IMPULSE_MAX = 1.6; // hop impulse to climb a 1-block step; per-tick impulse clamp
|
|
const BIKE_SNOW_FACTOR = 0.5; // bikes are sluggish on snow/ice (opposite of the sled)
|
|
|
|
function boardBike(player, block) {
|
|
const m = block.typeId.match(/^rzb_dcs:su29_(.+)$/); if (!m) return false;
|
|
const ci = BIKE_COLORS.indexOf(m[1]); if (ci < 0) return false;
|
|
const dim = block.dimension, dir = getDir(block.permutation), loc = { x: block.x + 0.5, y: block.y, z: block.z + 0.5 };
|
|
try { block.setType("minecraft:air"); } catch {}
|
|
let ent; try { ent = dim.spawnEntity(BIKE_ENTITY, loc); } catch {}
|
|
if (!ent) { try { block.setPermutation(BlockPermutation.resolve("rzb_dcs:su29_" + m[1], { "minecraft:cardinal_direction": dir })); } catch {} return true; }
|
|
try { ent.setProperty("rzb_dcs:color", ci); } catch {}
|
|
try { ent.setDynamicProperty("spawn", system.currentTick); } catch {}
|
|
let pyaw = TUBE_DIR_YAW[dir] ?? 0; try { pyaw = player.getRotation().y; } catch {}
|
|
try { ent.teleport(loc, { dimension: dim, rotation: { x: 0, y: pyaw } }); } catch {}
|
|
try { ent.getComponent("minecraft:rideable")?.addRider(player); } catch {}
|
|
return true;
|
|
}
|
|
|
|
function breakBikeInWater(bike, dim, at) {
|
|
let ci = 0; try { ci = bike.getProperty("rzb_dcs:color") ?? 0; } catch {}
|
|
let item = null;
|
|
if (bike.typeId === BIKE_ENTITY) item = "rzb_dcs:su29_" + (BIKE_COLORS[ci] || BIKE_COLORS[0]);
|
|
else if (bike.typeId === BB_ENTITY) item = "rzb_dcs:su31_b_" + (BB_COLORS[ci] || BB_COLORS[0]);
|
|
if (item) { try { dim.spawnItem(new ItemStack(item, 1), at); } catch {} }
|
|
try { bike.remove(); } catch {}
|
|
}
|
|
function bikeInWater(dim, x, y, z) {
|
|
const isW = (b) => { try { return !!b && b.typeId.endsWith("water"); } catch { return false; } };
|
|
return isW(safeGetBlock(dim, { x: Math.floor(x), y: Math.floor(y), z: Math.floor(z) })) || isW(safeGetBlock(dim, { x: Math.floor(x), y: Math.floor(y) + 1, z: Math.floor(z) }));
|
|
}
|
|
|
|
function blockCollisionTop(b) {
|
|
try {
|
|
const id = b.typeId || "";
|
|
if (id.includes("slab") && !id.includes("double")) { let h = ""; try { h = b.permutation.getState("minecraft:vertical_half"); } catch {} return h === "top" ? 1 : 0.5; }
|
|
if (id.endsWith("snow_layer")) { let n = 0; try { n = b.permutation.getState("height"); } catch {} return (typeof n === "number" ? n + 1 : 1) * 0.125; }
|
|
if (id.endsWith("carpet")) return 0.0625;
|
|
} catch {}
|
|
return 1;
|
|
}
|
|
|
|
function groundSupportY(dim, x, z, fromY) {
|
|
const bx = Math.floor(x), bz = Math.floor(z);
|
|
for (let y = Math.floor(fromY) + 1; y >= Math.floor(fromY) - 48; y--) {
|
|
let b = null; try { b = dim.getBlock({ x: bx, y, z: bz }); } catch {}
|
|
if (!b) continue;
|
|
const id = b.typeId || "";
|
|
if (/water|lava/.test(id)) continue;
|
|
let air = id === "minecraft:air"; try { air = b.isAir; } catch {}
|
|
if (air || BLOCK_PASSABLE.has(id)) continue; // air or a no-collision decoration -> ride through
|
|
const top = BLOCK_TOP[id]; // pack partial-collision block -> baked real top
|
|
return y + (top !== undefined ? top : blockCollisionTop(b));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function solidCol(dim, x, y, z) {
|
|
let b = null; try { b = dim.getBlock({ x: Math.floor(x), y: Math.floor(y), z: Math.floor(z) }); } catch {}
|
|
if (!b) return false;
|
|
const id = b.typeId || "";
|
|
if (/water|lava/.test(id)) return false;
|
|
let air = id === "minecraft:air"; try { air = b.isAir; } catch {}
|
|
return !air && !BLOCK_PASSABLE.has(id);
|
|
}
|
|
|
|
function frontStep(dim, loc, dx, dz) {
|
|
const ax = loc.x + dx * 0.6, az = loc.z + dz * 0.6;
|
|
return solidCol(dim, ax, loc.y + 0.1, az) && !solidCol(dim, ax, loc.y + 1.1, az);
|
|
}
|
|
// Shared land-vehicle velocity step (bike + sled are byte-identical here bar the strafe const and the water-break fn):
|
|
// steer horizontal velocity toward (fx,fz)*spd + strafe, hop a 1-block step, break if it entered water. Returns the
|
|
// pre-impulse velocity facts the caller's tail needs (nearGround, gspd) + broke (caller returns early when true).
|
|
function landImpulse(veh, dim, loc, fx, fz, rx, rz, ix, spd, strafe, breakFn) {
|
|
let vel = { x: 0, y: 0, z: 0 }; try { vel = veh.getVelocity(); } catch {}
|
|
const nearGround = vel.y > -0.3 && vel.y < 0.12; // ~resting/rolling
|
|
const desX = fx * spd + rx * ix * strafe, desZ = fz * spd + rz * ix * strafe;
|
|
let jx = desX - vel.x, jz = desZ - vel.z; // impulse to reach target horizontal velocity (compensates ground drag)
|
|
const jm = Math.hypot(jx, jz); if (jm > BIKE_IMPULSE_MAX) { jx = jx / jm * BIKE_IMPULSE_MAX; jz = jz / jm * BIKE_IMPULSE_MAX; }
|
|
const sg = spd < 0 ? -1 : 1;
|
|
const hop = (nearGround && Math.abs(spd) > 0.05 && frontStep(dim, loc, fx * sg, fz * sg)) ? BIKE_HOP : 0;
|
|
try { veh.applyImpulse({ x: jx, y: hop, z: jz }); } catch {}
|
|
let broke = false;
|
|
if (bikeInWater(dim, loc.x, loc.y, loc.z)) { breakFn(veh, dim, { x: loc.x, y: loc.y + 0.5, z: loc.z }); broke = true; }
|
|
return { nearGround, gspd: Math.hypot(vel.x, vel.z) * sg, broke };
|
|
}
|
|
// VELOCITY DRIVE: engine owns Y (gravity), collision, and carries the rider. We steer only horizontal velocity via
|
|
// applyImpulse + face with setRotation NO teleport (teleport pins position and kills gravity).
|
|
function driveBike(p, bike) {
|
|
const dim = bike.dimension, loc = bike.location;
|
|
let spd0 = 0; try { spd0 = bike.getDynamicProperty("spd") ?? 0; } catch {}
|
|
const bikeBase = bike.typeId === BB_ENTITY ? "animation.rzb_dcs.ride_basket_bike" : "animation.rzb_dcs.ride_bike";
|
|
const bikeAnim = Math.abs(spd0) > 0.02 ? bikeBase + "_pedal" : bikeBase;
|
|
try { p.playAnimation(bikeAnim, { blendOutTime: 0, stopExpression: "!q.is_riding" }); } catch {}
|
|
const { fx, fz, look: hasLook } = steerForward(p, bike); // hasLook gates the steering ramp below
|
|
const rx = fz, rz = -fx;
|
|
let mv; try { mv = p.inputInfo.getMovementVector(); } catch { mv = null; }
|
|
const ix = mv ? mv.x : 0, iy = mv ? mv.y : 0;
|
|
let spd = 0; try { spd = bike.getDynamicProperty("spd") ?? 0; } catch {}
|
|
const below = safeGetBlock(dim, { x: Math.floor(loc.x), y: Math.floor(loc.y) - 1, z: Math.floor(loc.z) });
|
|
const slow = isSnow(below?.typeId) || isSand(below?.typeId); // snow/ice + sand/gravel = loose/slick
|
|
const bmax = slow ? BIKE_MAX * BIKE_SNOW_FACTOR : BIKE_MAX;
|
|
if (iy > 0.1) spd = Math.min(spd + BIKE_ACCEL, bmax);
|
|
else if (iy < -0.1) spd = Math.max(spd - BIKE_ACCEL * 1.5, -bmax * 0.35);
|
|
else spd = spd > 0 ? Math.max(spd - BIKE_DECEL, 0) : Math.min(spd + BIKE_DECEL, 0);
|
|
if (spd > bmax) spd = Math.max(spd - BIKE_DECEL, bmax); // ease down when crossing onto slow ground at speed
|
|
try { bike.setDynamicProperty("spd", spd); } catch {}
|
|
const { nearGround, gspd, broke } = landImpulse(bike, dim, loc, fx, fz, rx, rz, ix, spd, BIKE_STRAFE, breakBikeInWater);
|
|
if (broke) return;
|
|
let roll = 0; try { roll = bike.getProperty("rzb_dcs:roll") ?? 0; } catch {}
|
|
roll = (roll + gspd * BIKE_ROLL) % 1; if (roll < 0) roll += 1;
|
|
try { bike.setProperty("rzb_dcs:roll", roll); } catch {}
|
|
let yaw = 0; try { yaw = -Math.atan2(fx, fz) * 180 / Math.PI; } catch {}
|
|
{ let cur = 0; try { cur = bike.getRotation().y; } catch {} const d = hasLook ? ((yaw - cur + 540) % 360) - 180 : 0; const target = Math.max(-1, Math.min(1, d * 0.16));
|
|
let st = 0; try { st = bike.getDynamicProperty("str") ?? 0; } catch {} st += (target - st) * 0.18; if (Math.abs(st) < 0.015) st = 0;
|
|
try { bike.setDynamicProperty("str", st); } catch {} try { bike.setProperty("rzb_dcs:steer", st); } catch {} }
|
|
try { bike.setRotation({ x: 0, y: yaw }); } catch {}
|
|
if (nearGround && Math.abs(spd) >= bmax - 0.05 && Math.abs(gspd) > 0.03 && (system.currentTick & 1) === 0) { // spd = commanded top speed (reliable); gspd>0.03 = actually rolling (no dust vs a wall)
|
|
const gy = loc.y + 0.02;
|
|
try { dim.spawnParticle("rzb_dcs:bike_dust", { x: loc.x + fx * BIKE_WHEEL, y: gy, z: loc.z + fz * BIKE_WHEEL }); } catch {}
|
|
try { dim.spawnParticle("rzb_dcs:bike_dust", { x: loc.x - fx * BIKE_WHEEL, y: gy, z: loc.z - fz * BIKE_WHEEL }); } catch {}
|
|
}
|
|
}
|
|
function revertBike(t, dim) {
|
|
let ci = 0; try { ci = t.getProperty("rzb_dcs:color") ?? 0; } catch {}
|
|
const color = BIKE_COLORS[ci] ?? "black";
|
|
let yaw = 0; try { yaw = t.getRotation().y; } catch {}
|
|
const dir = tubeYawToDir(yaw + 180);
|
|
const l = t.location, wx = Math.floor(l.x), wy = Math.floor(l.y), wz = Math.floor(l.z);
|
|
try { t.remove(); } catch {}
|
|
const perm = () => BlockPermutation.resolve("rzb_dcs:su29_" + color, { "minecraft:cardinal_direction": dir });
|
|
const b = safeGetBlock(dim, { x: wx, y: wy, z: wz });
|
|
if (b && (b.isAir || b.isLiquid)) { try { b.setPermutation(perm()); } catch {} }
|
|
else { const up = safeGetBlock(dim, { x: wx, y: wy + 1, z: wz }); if (up && (up.isAir || up.isLiquid)) { try { up.setPermutation(perm()); } catch {} } else { try { dim.spawnItem(new ItemStack("rzb_dcs:su29_" + color, 1), { x: wx + 0.5, y: wy + 1, z: wz + 0.5 }); } catch {} } }
|
|
}
|
|
|
|
export const SB_BASE = "rzb_dcs:su30_b_", SB_TOP = "rzb_dcs:su30_t_", SB_ENTITY = "rzb_dcs:surfboard";
|
|
export const SB_LB = "rzb_dcs:su30_lb_", SB_LF = "rzb_dcs:su30_lf_";
|
|
export const SB_COLORS = ["blue", "cyan", "green", "light_blue", "lime", "magenta", "orange", "pink", "purple", "red", "yellow"]; // entity color-index map; do NOT reorder
|
|
export const SB_ORDER = ["orange", "magenta", "light_blue", "yellow", "lime", "pink", "cyan", "purple", "blue", "green", "red"]; // brush cycle order (canonical), decoupled from the index map
|
|
const SURF_SPEED = 0.17, SURF_YAW_OFF = 0;
|
|
registerVariants([...SB_COLORS.map((c) => SB_BASE + c), ...SB_COLORS.map((c) => SB_TOP + c)]);
|
|
registerVariants([...SB_COLORS.map((c) => SB_LB + c), ...SB_COLORS.map((c) => SB_LF + c)]);
|
|
export const SB_LAY_CELLS = { "0_0_0": [0, 0, 0], "0_0_1": [0, 0, 1] };
|
|
export const SB_LAY_PREFIX = { "0_0_0": SB_LB, "0_0_1": SB_LF };
|
|
const _sbH = makeCellHelpers(SB_LAY_CELLS, SB_LAY_PREFIX);
|
|
export const sbLayCellWorld = _sbH.cellWorld, sbLayAnchorFrom = _sbH.anchorFrom, sbLayParse = _sbH.parse;
|
|
const placeSurfboardBlocks = (dim, ax, ay, az, dir, color) => placeGridBlocks(dim, ax, ay, az, dir, SB_LAY_CELLS, sbLayCellWorld, (c) => SB_LAY_PREFIX[c] + color, true);
|
|
const _surfRecent = new Set();
|
|
function markSurf(pid) { if (pid) { _surfRecent.add(pid); system.runTimeout(() => _surfRecent.delete(pid), 4); } }
|
|
onPlace((ev) => {
|
|
const b = ev.block; if (!b.typeId.startsWith(SB_BASE)) return;
|
|
const color = b.typeId.slice(SB_BASE.length), dim = b.dimension, dir = getDir(b.permutation);
|
|
if (tubeOverWater(b)) {
|
|
const wx = b.x, wz = b.z, f = F(dir), fx = wx + f[0], fz = wz + f[2];
|
|
const isWater = (x, y, z) => (safeGetBlock(dim, { x, y, z })?.typeId || "").endsWith("water");
|
|
const clear = (bl) => bl && (bl.isAir || bl.isLiquid);
|
|
let ay = b.y;
|
|
for (let i = 0; i < 8 && isWater(fx, ay, fz); i++) ay++;
|
|
const c1 = ay === b.y ? b : safeGetBlock(dim, { x: wx, y: ay, z: wz });
|
|
const c2 = safeGetBlock(dim, { x: fx, y: ay, z: fz });
|
|
if (c1 && (c1 === b || clear(c1)) && clear(c2)) {
|
|
if (ay !== b.y) { try { b.setType(b.y < ay ? "minecraft:water" : "minecraft:air"); } catch {} }
|
|
try { c2.setPermutation(BlockPermutation.resolve(SB_LF + color, { "minecraft:cardinal_direction": dir })); } catch {}
|
|
try { c1.setPermutation(BlockPermutation.resolve(SB_LB + color, { "minecraft:cardinal_direction": dir })); } catch {}
|
|
markSurf(ev.player?.id);
|
|
} else {
|
|
undoPlace(b, ev.player, SB_BASE + color, "surfboard");
|
|
}
|
|
return;
|
|
}
|
|
const ab = safeGetBlock(dim, { x: b.x, y: b.y + 1, z: b.z });
|
|
if (!ab || !fcCanReplace(ab)) { undoPlace(b, ev.player, SB_BASE + color, "surfboard"); return; }
|
|
try { ab.setPermutation(BlockPermutation.resolve(SB_TOP + color, { "minecraft:cardinal_direction": dir })); } catch {}
|
|
});
|
|
|
|
world.afterEvents.itemUse?.subscribe((ev) => {
|
|
const p = ev.source, item = ev.itemStack; if (!item?.typeId?.startsWith(SB_BASE) || !p) return;
|
|
let hit; try { hit = p.getBlockFromViewDirection({ includeLiquidBlocks: true, maxDistance: 6 }); } catch {}
|
|
if (!hit?.block || !/water/.test(hit.block.typeId)) return;
|
|
const color = item.typeId.slice(SB_BASE.length), dim = p.dimension, wx = hit.block.x, wz = hit.block.z, pid = p.id;
|
|
let wy = hit.block.y;
|
|
for (let i = 0; i < 40; i++) { const up = safeGetBlock(dim, { x: wx, y: wy + 1, z: wz }); if (up && /water/.test(up.typeId)) wy++; else break; }
|
|
let yaw = 0; try { yaw = p.getRotation().y; } catch {}
|
|
const dir = tubeYawToDir(yaw);
|
|
system.run(() => {
|
|
if (_surfRecent.has(pid)) return;
|
|
markSurf(pid);
|
|
if (placeSurfboardBlocks(dim, wx, wy + 1, wz, dir, color)) {
|
|
try { if (!isCreative(p)) { const inv = p.getComponent("minecraft:inventory")?.container, i = p.selectedSlotIndex, it = inv?.getItem(i); if (it && it.typeId?.startsWith(SB_BASE)) { if (it.amount > 1) { it.amount -= 1; inv.setItem(i, it); } else inv.setItem(i, undefined); } } } catch {}
|
|
}
|
|
});
|
|
});
|
|
onBreak((ev) => {
|
|
const id = ev.brokenBlockPermutation?.type?.id || "", b = ev.block, dim = b.dimension;
|
|
if (id.startsWith(SB_BASE)) { const nb = dim.getBlock({ x: b.x, y: b.y + 1, z: b.z }); if (nb && nb.typeId === SB_TOP + id.slice(SB_BASE.length)) { try { breakAir(nb); } catch {} } }
|
|
else if (id.startsWith(SB_TOP)) { const nb = dim.getBlock({ x: b.x, y: b.y - 1, z: b.z }); if (nb && nb.typeId === SB_BASE + id.slice(SB_TOP.length)) { try { breakAir(nb); } catch {} } }
|
|
else { const lp = sbLayParse(id); if (lp) { const dir = getDir(ev.brokenBlockPermutation), a = sbLayAnchorFrom(lp.cell, b.x, b.y, b.z, dir); for (const c of Object.keys(SB_LAY_CELLS)) { if (c === lp.cell) continue; const p = sbLayCellWorld(c, a.x, a.y, a.z, dir); const nb = safeGetBlock(dim, p); if (nb && sbLayParse(nb.typeId)?.cell === c) { try { breakAir(nb); } catch {} } } } }
|
|
});
|
|
const SURF_RIG = { parse: sbLayParse, cells: SB_LAY_CELLS, cellWorld: sbLayCellWorld, anchorFrom: sbLayAnchorFrom, entity: SB_ENTITY, colors: SB_COLORS, base: SB_BASE, place: placeSurfboardBlocks, waterGate: true, surfaceSnap: true, spawnYaw: (dir) => (TUBE_DIR_YAW[dir] ?? 0) + SURF_YAW_OFF, revertDir: (yaw) => tubeYawToDir(yaw - SURF_YAW_OFF) };
|
|
const boardSurfboard = (player, block) => board2Cell(player, block, SURF_RIG);
|
|
const revertSurfboard = (t, dim) => revert2Cell(t, dim, SURF_RIG);
|
|
function driveSurfboard(p, boat) { driveWaterCraft(p, boat, "animation.rzb_dcs.ride_surfboard", SURF_SPEED, SURF_YAW_OFF); }
|
|
|
|
const BB_ENTITY = "rzb_dcs:basket_bike";
|
|
export const BB_B = "rzb_dcs:su31_b_", BB_FR = "rzb_dcs:su31_fr_";
|
|
export const BB_COLORS = ["cyan", "green", "lavender", "light_blue", "mint", "orange", "pink", "red", "white", "yellow"]; // entity color-index map; do NOT reorder
|
|
export const BB_ORDER = ["white", "orange", "light_blue", "yellow", "pink", "cyan", "green", "red", "lavender", "mint"]; // brush cycle order (canonical); decoupled from the index map
|
|
const BB_YAW_OFF = 180;
|
|
registerVariants([...BB_COLORS.map((c) => BB_B + c), ...BB_COLORS.map((c) => BB_FR + c)]);
|
|
export const BB_CELLS = { "0_0_0": [0, 0, 0], "0_0_1": [0, 0, -1] };
|
|
export const BB_PREFIX = { "0_0_0": BB_B, "0_0_1": BB_FR };
|
|
const _bbH = makeCellHelpers(BB_CELLS, BB_PREFIX);
|
|
export const bbCellWorld = _bbH.cellWorld, bbAnchorFrom = _bbH.anchorFrom, bbParse = _bbH.parse;
|
|
const placeBasketBikeBlocks = (dim, ax, ay, az, dir, color) => placeGridBlocks(dim, ax, ay, az, dir, BB_CELLS, bbCellWorld, (c) => BB_PREFIX[c] + color);
|
|
registerFrontPairRig(BB_B, BB_FR, BB_CELLS, bbCellWorld, bbAnchorFrom, bbParse, "basket bike");
|
|
const BB_RIG = { parse: bbParse, cells: BB_CELLS, cellWorld: bbCellWorld, anchorFrom: bbAnchorFrom, entity: BB_ENTITY, colors: BB_COLORS, base: BB_B, place: placeBasketBikeBlocks, spawnYaw: (dir, player) => { let y = (TUBE_DIR_YAW[dir] ?? 0) + BB_YAW_OFF; try { y = player.getRotation().y; } catch {} return y; }, revertDir: (yaw) => tubeYawToDir(yaw - BB_YAW_OFF + 180) };
|
|
const boardBasketBike = (player, block) => board2Cell(player, block, BB_RIG);
|
|
const revertBasketBike = (t, dim) => revert2Cell(t, dim, BB_RIG);
|
|
|
|
const SLED_ENTITY = "rzb_dcs:sled";
|
|
export const SLED_B = "rzb_dcs:wi41_b_", SLED_FR = "rzb_dcs:wi41_fr_";
|
|
const SLED_COLORS = ["red", "green", "blue", "yellow"]; // entity color index do NOT reorder
|
|
export const SLED_ORDER = ["yellow", "blue", "green", "red"]; // brush cycle order (canonical); decoupled from the index map
|
|
export const SLED_CELLS = { "0_0_0": [0, 0, 0], "0_0_1": [0, 0, 1] }; // anchor(back) + front, one block forward (+FWD)
|
|
export const SLED_PREFIX = { "0_0_0": SLED_B, "0_0_1": SLED_FR };
|
|
const SLED_MAX_SNOW = 0.5, SLED_MAX_SLOW = 0.16, SLED_ACCEL = 0.03, SLED_DECEL = 0.03, SLED_STRAFE = 0.1, SLED_STEP = 1.05;
|
|
const SLED_YAW_OFF = 180; // sled drives "backwards" -> flip the model 180 so its front points the way of travel
|
|
const isSnow = (id) => !!id && /snow|ice|powder_snow/.test(id);
|
|
const isSand = (id) => !!id && /sand|gravel/.test(id); // loose ground bikes bog down like on snow
|
|
registerVariants([...SLED_COLORS.map((c) => SLED_B + c), ...SLED_COLORS.map((c) => SLED_FR + c)]); // brush gate; cycling uses SLED_ORDER
|
|
const _slH = makeCellHelpers(SLED_CELLS, SLED_PREFIX);
|
|
export const sledCellWorld = _slH.cellWorld, sledAnchorFrom = _slH.anchorFrom, sledParse = _slH.parse;
|
|
const placeSledBlocks = (dim, ax, ay, az, dir, color) => placeGridBlocks(dim, ax, ay, az, dir, SLED_CELLS, sledCellWorld, (c) => SLED_PREFIX[c] + color);
|
|
registerFrontPairRig(SLED_B, SLED_FR, SLED_CELLS, sledCellWorld, sledAnchorFrom, sledParse, "sled");
|
|
const SLED_RIG = { parse: sledParse, cells: SLED_CELLS, cellWorld: sledCellWorld, anchorFrom: sledAnchorFrom, entity: SLED_ENTITY, colors: SLED_COLORS, base: SLED_B, place: placeSledBlocks, spawnYaw: (dir, player) => { let y = (TUBE_DIR_YAW[dir] ?? 0) + SLED_YAW_OFF; try { y = player.getRotation().y + SLED_YAW_OFF; } catch {} return y; }, revertDir: (yaw) => tubeYawToDir(yaw + 180) };
|
|
const boardSled = (player, block) => board2Cell(player, block, SLED_RIG);
|
|
const revertSled = (t, dim) => revert2Cell(t, dim, SLED_RIG);
|
|
function breakSledInWater(sled, dim, at) {
|
|
let ci = 0; try { ci = sled.getProperty("rzb_dcs:color") ?? 0; } catch {}
|
|
try { dim.spawnItem(new ItemStack(SLED_B + (SLED_COLORS[ci] || SLED_COLORS[0]), 1), at); } catch {}
|
|
try { sled.remove(); } catch {}
|
|
}
|
|
// Land drive like the bike, but max speed depends on the block under the sled: fast on snow/ice, sluggish elsewhere.
|
|
function driveSled(p, sled) {
|
|
const dim = sled.dimension, loc = sled.location;
|
|
try { p.playAnimation("animation.rzb_dcs.ride_sled", { blendOutTime: 0, stopExpression: "!q.is_riding" }); } catch {}
|
|
const { fx, fz } = steerForward(p, sled, SLED_YAW_OFF); // holds heading (minus the 180 offset) when look is near-vertical
|
|
const rx = fz, rz = -fx;
|
|
let mv; try { mv = p.inputInfo.getMovementVector(); } catch { mv = null; }
|
|
const ix = mv ? mv.x : 0, iy = mv ? mv.y : 0;
|
|
const below = safeGetBlock(dim, { x: Math.floor(loc.x), y: Math.floor(loc.y) - 1, z: Math.floor(loc.z) });
|
|
const onSnow = isSnow(below?.typeId), maxSpd = onSnow ? SLED_MAX_SNOW : SLED_MAX_SLOW;
|
|
let spd = 0; try { spd = sled.getDynamicProperty("spd") ?? 0; } catch {}
|
|
if (iy > 0.1) spd = Math.min(spd + SLED_ACCEL, maxSpd);
|
|
else if (iy < -0.1) spd = Math.max(spd - SLED_ACCEL * 1.5, -maxSpd * 0.35);
|
|
else spd = spd > 0 ? Math.max(spd - SLED_DECEL, 0) : Math.min(spd + SLED_DECEL, 0);
|
|
if (spd > maxSpd) spd = maxSpd; else if (spd < -maxSpd * 0.35) spd = -maxSpd * 0.35; // clamp when crossing onto slow terrain at speed
|
|
try { sled.setDynamicProperty("spd", spd); } catch {}
|
|
const { nearGround, gspd, broke } = landImpulse(sled, dim, loc, fx, fz, rx, rz, ix, spd, SLED_STRAFE, breakSledInWater);
|
|
if (broke) return;
|
|
let yaw = SLED_YAW_OFF; try { yaw = -Math.atan2(fx, fz) * 180 / Math.PI + SLED_YAW_OFF; } catch {}
|
|
try { sled.setRotation({ x: 0, y: yaw }); } catch {}
|
|
if (nearGround && Math.abs(spd) >= maxSpd - 0.03 && Math.abs(gspd) > 0.03 && onSnow && (system.currentTick & 1) === 0) { try { dim.spawnParticle("rzb_dcs:sled_snow", { x: loc.x, y: loc.y + 0.02, z: loc.z }); } catch {} } // snow spray at top speed (spd = commanded, reliable)
|
|
}
|
|
|
|
// Consolidated riderless-revert loop. Scans only dimensions that currently have players (a vehicle only exists where
|
|
// someone rode it). Each type's body is its revertX() fn; the rider + spawn-age gate is shared.
|
|
const REVERTERS = [
|
|
[TUBE_ENTITY, revertTube], ...Object.values(TUBE_SPECIAL).map((s) => [s.entity, revertTube]), [LOUNGER_ENTITY, revertLounger], [BIKE_ENTITY, revertBike],
|
|
[SB_ENTITY, revertSurfboard], [BB_ENTITY, revertBasketBike], [SLED_ENTITY, revertSled],
|
|
];
|
|
system.runInterval(() => {
|
|
const dims = new Map();
|
|
for (const p of world.getAllPlayers()) { try { dims.set(p.dimension.id, p.dimension); } catch {} }
|
|
for (const dim of dims.values()) {
|
|
for (const [type, revert] of REVERTERS) {
|
|
let ents; try { ents = dim.getEntities({ type }); } catch { continue; }
|
|
for (const t of ents) {
|
|
let riders; try { riders = t.getComponent("minecraft:rideable")?.getRiders() ?? []; } catch { riders = []; }
|
|
if (riders.length > 0) continue;
|
|
let spawn = 0; try { spawn = t.getDynamicProperty("spawn") ?? 0; } catch {}
|
|
if (system.currentTick - spawn < 20) continue;
|
|
try { revert(t, dim); } catch {}
|
|
}
|
|
}
|
|
}
|
|
}, 10);
|
|
|
|
// Master drive loop dispatch pertick steering by the entity a player is riding.
|
|
system.runInterval(() => {
|
|
for (const p of world.getAllPlayers()) {
|
|
let boat; try { boat = p.getComponent("minecraft:riding")?.entityRidingOn; } catch {}
|
|
if (!boat) continue;
|
|
if (boat.typeId === LOUNGER_ENTITY) { driveLounger(p, boat); continue; }
|
|
if (boat.typeId === BIKE_ENTITY) { driveBike(p, boat); continue; }
|
|
if (boat.typeId === SB_ENTITY) { driveSurfboard(p, boat); continue; }
|
|
if (boat.typeId === BB_ENTITY) { driveBike(p, boat); continue; }
|
|
if (boat.typeId === SLED_ENTITY) { driveSled(p, boat); continue; }
|
|
if (boat.typeId === TUBE_ENTITY || TUBE_SPECIAL_BY_ENT[boat.typeId]) { driveTube(p, boat); continue; }
|
|
}
|
|
}, 1);
|
|
|
|
system.beforeEvents.startup.subscribe((init) => {
|
|
const reg = init.blockComponentRegistry;
|
|
reg.registerCustomComponent("rzb_dcs:ride_tube", { onPlayerInteract(e) { boardTube(e.player, e.block); } });
|
|
reg.registerCustomComponent("rzb_dcs:ride_lounger", { onPlayerInteract(e) { boardLounger(e.player, e.block); } });
|
|
reg.registerCustomComponent("rzb_dcs:ride_bike", { onPlayerInteract(e) { boardBike(e.player, e.block); } });
|
|
reg.registerCustomComponent("rzb_dcs:ride_surfboard", { onPlayerInteract(e) { boardSurfboard(e.player, e.block); } });
|
|
reg.registerCustomComponent("rzb_dcs:ride_basket_bike", { onPlayerInteract(e) { boardBasketBike(e.player, e.block); } });
|
|
reg.registerCustomComponent("rzb_dcs:ride_sled", { onPlayerInteract(e) { boardSled(e.player, e.block); } });
|
|
});
|