feat: A-frame tent + portal walk-through field + texture polish
All checks were successful
Deploy Addons / deploy (push) Successful in 14s
All checks were successful
Deploy Addons / deploy (push) Successful in 14s
- camping: replace cube tent with A-frame slope panels (tent_panel_l/r) + cardinal_direction permutations; vote-skip sleep that mixes player.isSleeping bed sleepers with tent occupants and respects the playersSleepingPercentage gamerule; new weathered-canvas texture. - lobby: walk-through silverlabs:portal_field block (no collision, translucent swirl, cross-plane geo) auto-placed above each portal frame; invisible silverlabs:portal_label entity floats above each portal with the destination world name; transfer detection now scans down through the field to find the destination frame. - postal: regenerate post_office and mailbox block textures so they fill the full block face (brick + POST plaque, full red panel with slot/latch /flag/rivets) instead of small sprites floating on transparent. - dynamite + tow-boat: ship the addons (volumes wired into all four worlds; enabled_packs registers them into Mya's world). - art: build-textures.py extended; build-art-catalog.py added to project. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
33
lobby-addon/lobby_transfer_BP/blocks/portal_field.json
Normal file
33
lobby-addon/lobby_transfer_BP/blocks/portal_field.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"format_version": "1.21.0",
|
||||
"minecraft:block": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:portal_field",
|
||||
"menu_category": {
|
||||
"category": "items",
|
||||
"group": "itemGroup.name.decorations"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"minecraft:destructible_by_mining": { "seconds_to_destroy": 0 },
|
||||
"minecraft:destructible_by_explosion": { "explosion_resistance": 1200.0 },
|
||||
"minecraft:light_emission": 11,
|
||||
"minecraft:light_dampening": 0,
|
||||
"minecraft:collision_box": false,
|
||||
"minecraft:selection_box": {
|
||||
"origin": [-7, 0, -7],
|
||||
"size": [14, 16, 14]
|
||||
},
|
||||
"minecraft:map_color": "#7B27FF",
|
||||
"minecraft:material_instances": {
|
||||
"*": {
|
||||
"texture": "portal_field",
|
||||
"render_method": "blend",
|
||||
"ambient_occlusion": false,
|
||||
"face_dimming": false
|
||||
}
|
||||
},
|
||||
"minecraft:geometry": "geometry.silverlabs.portal_field"
|
||||
}
|
||||
}
|
||||
}
|
||||
26
lobby-addon/lobby_transfer_BP/entities/portal_label.json
Normal file
26
lobby-addon/lobby_transfer_BP/entities/portal_label.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"format_version": "1.21.0",
|
||||
"minecraft:entity": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:portal_label",
|
||||
"is_spawnable": false,
|
||||
"is_summonable": true,
|
||||
"is_experimental": false
|
||||
},
|
||||
"components": {
|
||||
"minecraft:nameable": {},
|
||||
"minecraft:type_family": { "family": ["portal_label", "inanimate"] },
|
||||
"minecraft:health": { "value": 1, "max": 1 },
|
||||
"minecraft:physics": { "has_collision": false, "has_gravity": false },
|
||||
"minecraft:pushable": { "is_pushable": false, "is_pushable_by_piston": false },
|
||||
"minecraft:damage_sensor": {
|
||||
"triggers": [{ "deals_damage": false }]
|
||||
},
|
||||
"minecraft:fire_immune": {},
|
||||
"minecraft:knockback_resistance": { "value": 1.0, "max": 1.0 },
|
||||
"minecraft:persistent": {},
|
||||
"minecraft:movement": { "value": 0 },
|
||||
"minecraft:collision_box": { "width": 0.01, "height": 0.01 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,26 @@
|
||||
import { world, system } from "@minecraft/server";
|
||||
import { transferPlayer } from "@minecraft/server-admin";
|
||||
|
||||
// Portal block → transfer target mapping (custom blocks — priority detection)
|
||||
// Portal frame block → transfer target mapping. The frame is the floor block;
|
||||
// the walk-through field column above it inherits the destination by looking down.
|
||||
const PORTAL_BLOCKS = {
|
||||
"silverlabs:portal_jamie": { name: "Jamie's World", host: "10.0.0.247", port: 19133, color: "§a" },
|
||||
"silverlabs:portal_lyla": { name: "Lyla's World", host: "10.0.0.247", port: 19134, color: "§d" },
|
||||
"silverlabs:portal_mya": { name: "Mya's World", host: "10.0.0.247", port: 19135, color: "§b" },
|
||||
};
|
||||
|
||||
// Coordinate-based portal zones (fallback detection)
|
||||
const PORTAL_FIELD_BLOCK = "silverlabs:portal_field";
|
||||
const PORTAL_LABEL_ENTITY = "silverlabs:portal_label";
|
||||
|
||||
// Coordinate-based portal zones (fallback detection + auto-spawn anchors).
|
||||
// `frame_y` is the floor block where the destination frame sits; the field
|
||||
// blocks are placed at frame_y+1 and frame_y+2 (2-tall walk-through column),
|
||||
// and the floating label entity sits at frame_y+3.5.
|
||||
const PORTAL_ZONES = [
|
||||
{ name: "Jamie's World", x: 436, y: 66, z: -296, host: "10.0.0.247", port: 19133, color: "§a" },
|
||||
{ name: "Lyla's World", x: 462, y: 65, z: -322, host: "10.0.0.247", port: 19134, color: "§d" },
|
||||
{ name: "Lyla's World", x: 474, y: 65, z: -281, host: "10.0.0.247", port: 19134, color: "§d" }, // Super Kitties portal
|
||||
{ name: "Mya's World", x: 488, y: 66, z: -296, host: "10.0.0.247", port: 19135, color: "§b" },
|
||||
{ name: "Jamie's World", x: 436, y: 66, z: -296, frame_y: 66, host: "10.0.0.247", port: 19133, color: "§a" },
|
||||
{ name: "Lyla's World", x: 462, y: 65, z: -322, frame_y: 65, host: "10.0.0.247", port: 19134, color: "§d" },
|
||||
{ name: "Lyla's World", x: 474, y: 65, z: -281, frame_y: 65, host: "10.0.0.247", port: 19134, color: "§d", label_suffix: " (Super Kitties)" },
|
||||
{ name: "Mya's World", x: 488, y: 66, z: -296, frame_y: 66, host: "10.0.0.247", port: 19135, color: "§b" },
|
||||
];
|
||||
|
||||
const PORTAL_RADIUS_X = 2.5;
|
||||
@@ -38,13 +45,26 @@ world.afterEvents.playerSpawn.subscribe((event) => {
|
||||
function checkBlockPortal(player) {
|
||||
const pos = player.location;
|
||||
const dimension = player.dimension;
|
||||
const blockAtFeet = dimension.getBlock({ x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) });
|
||||
const blockBelow = dimension.getBlock({ x: Math.floor(pos.x), y: Math.floor(pos.y) - 1, z: Math.floor(pos.z) });
|
||||
const fx = Math.floor(pos.x);
|
||||
const fy = Math.floor(pos.y);
|
||||
const fz = Math.floor(pos.z);
|
||||
const blockAtFeet = dimension.getBlock({ x: fx, y: fy, z: fz });
|
||||
const blockHead = dimension.getBlock({ x: fx, y: fy + 1, z: fz });
|
||||
const blockBelow = dimension.getBlock({ x: fx, y: fy - 1, z: fz });
|
||||
|
||||
const feetId = blockAtFeet?.typeId;
|
||||
const belowId = blockBelow?.typeId;
|
||||
// Walk-through portal field: scan downward for the destination frame block.
|
||||
for (const b of [blockAtFeet, blockHead]) {
|
||||
if (b?.typeId === PORTAL_FIELD_BLOCK) {
|
||||
for (let dy = 1; dy <= 4; dy++) {
|
||||
const probe = dimension.getBlock({ x: fx, y: fy - dy, z: fz });
|
||||
const dest = probe && PORTAL_BLOCKS[probe.typeId];
|
||||
if (dest) return dest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PORTAL_BLOCKS[feetId] || PORTAL_BLOCKS[belowId] || null;
|
||||
// Legacy: standing on a frame block directly.
|
||||
return PORTAL_BLOCKS[blockAtFeet?.typeId] || PORTAL_BLOCKS[blockBelow?.typeId] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,43 +131,65 @@ world.afterEvents.playerLeave.subscribe((event) => {
|
||||
spawnTicks.delete(event.playerId);
|
||||
});
|
||||
|
||||
// ─── Place signs above each portal on load ──────────────────────
|
||||
// ─── Walk-through portal fields + floating destination labels ──
|
||||
// For each known portal zone, place a 2-block-tall column of `portal_field`
|
||||
// blocks (no collision, translucent, light-emitting) directly above the frame
|
||||
// block, and summon an invisible `portal_label` entity above with the
|
||||
// destination world's name visible as a floating tag.
|
||||
|
||||
function placePortalSigns() {
|
||||
const overworld = world.getDimension("overworld");
|
||||
const signs = [
|
||||
{ x: 438, y: 71, z: -296, name: "Jamie's", color: "§a", facing: 5 }, // east-facing
|
||||
{ x: 462, y: 71, z: -320, name: "Lyla's", color: "§d", facing: 3 }, // south-facing
|
||||
{ x: 486, y: 71, z: -296, name: "Mya's", color: "§b", facing: 4 }, // west-facing
|
||||
{ x: 474, y: 74, z: -281, name: "Super Kitties\n§fLyla's", color: "§d", facing: 2 }, // north-facing (Super Kitties portal)
|
||||
];
|
||||
|
||||
for (const sign of signs) {
|
||||
function ensurePortalField(overworld, x, fy, z) {
|
||||
for (const dy of [1, 2]) {
|
||||
try {
|
||||
overworld.runCommand(`setblock ${sign.x} ${sign.y} ${sign.z} oak_wall_sign ["facing_direction":${sign.facing}]`);
|
||||
} catch (e) {
|
||||
// Non-fatal — chunks may not be loaded
|
||||
}
|
||||
}
|
||||
|
||||
system.runTimeout(() => {
|
||||
for (const sign of signs) {
|
||||
try {
|
||||
const block = overworld.getBlock({ x: sign.x, y: sign.y, z: sign.z });
|
||||
if (!block) continue;
|
||||
const signComponent = block.getComponent("minecraft:sign");
|
||||
if (!signComponent) continue;
|
||||
signComponent.setText(`${sign.color}${sign.name}\n${sign.color}World\n§7▼ Step in ▼`);
|
||||
} catch (e) {
|
||||
// Non-fatal
|
||||
const b = overworld.getBlock({ x, y: fy + dy, z });
|
||||
if (!b) continue;
|
||||
if (b.typeId !== PORTAL_FIELD_BLOCK) {
|
||||
overworld.runCommand(`setblock ${x} ${fy + dy} ${z} ${PORTAL_FIELD_BLOCK}`);
|
||||
}
|
||||
}
|
||||
}, 10);
|
||||
} catch (_) { /* chunks may be unloaded; retried next boot */ }
|
||||
}
|
||||
}
|
||||
|
||||
system.runTimeout(() => {
|
||||
placePortalSigns();
|
||||
}, 40);
|
||||
function ensurePortalLabel(overworld, zone) {
|
||||
const labelY = zone.frame_y + 3.5;
|
||||
// Check if a label already exists nearby; replace its tag if outdated.
|
||||
let existing = null;
|
||||
try {
|
||||
const matches = overworld.getEntities({
|
||||
type: PORTAL_LABEL_ENTITY,
|
||||
location: { x: zone.x + 0.5, y: labelY, z: zone.z + 0.5 },
|
||||
maxDistance: 1.5,
|
||||
});
|
||||
existing = matches[0] || null;
|
||||
} catch (_) {}
|
||||
|
||||
const tag = `${zone.color}${zone.name}${zone.label_suffix || ""}`;
|
||||
if (existing) {
|
||||
try { existing.nameTag = tag; } catch (_) {}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const entity = overworld.spawnEntity(
|
||||
PORTAL_LABEL_ENTITY,
|
||||
{ x: zone.x + 0.5, y: labelY, z: zone.z + 0.5 }
|
||||
);
|
||||
if (entity) {
|
||||
try { entity.nameTag = tag; } catch (_) {}
|
||||
}
|
||||
} catch (_) { /* chunks may be unloaded */ }
|
||||
}
|
||||
|
||||
function placePortalDressing() {
|
||||
const overworld = world.getDimension("overworld");
|
||||
for (const zone of PORTAL_ZONES) {
|
||||
if (zone.frame_y === undefined) continue;
|
||||
ensurePortalField(overworld, zone.x, zone.frame_y, zone.z);
|
||||
ensurePortalLabel(overworld, zone);
|
||||
}
|
||||
}
|
||||
|
||||
// Initial place + periodic re-ensure (handles chunk loading after first attempt).
|
||||
system.runTimeout(() => placePortalDressing(), 40);
|
||||
system.runInterval(() => placePortalDressing(), 600); // every 30s
|
||||
|
||||
system.run(() => {
|
||||
world.sendMessage("§6[Hub] §7Portal transfer system loaded!");
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"silverlabs:portal_frame": { "sound": "stone" },
|
||||
"silverlabs:portal_jamie": { "sound": "stone" },
|
||||
"silverlabs:portal_lyla": { "sound": "stone" },
|
||||
"silverlabs:portal_mya": { "sound": "stone" }
|
||||
"silverlabs:portal_mya": { "sound": "stone" },
|
||||
"silverlabs:portal_field": { "sound": "glass" }
|
||||
}
|
||||
|
||||
12
lobby-addon/lobby_transfer_RP/entity/portal_label.json
Normal file
12
lobby-addon/lobby_transfer_RP/entity/portal_label.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"format_version": "1.10.0",
|
||||
"minecraft:client_entity": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:portal_label",
|
||||
"materials": { "default": "entity_alphatest" },
|
||||
"textures": { "default": "textures/entity/portal_label" },
|
||||
"geometry": { "default": "geometry.silverlabs.portal_label" },
|
||||
"render_controllers": ["controller.render.default"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"format_version": "1.12.0",
|
||||
"minecraft:geometry": [
|
||||
{
|
||||
"description": {
|
||||
"identifier": "geometry.silverlabs.portal_field",
|
||||
"texture_width": 16,
|
||||
"texture_height": 16,
|
||||
"visible_bounds_width": 1.5,
|
||||
"visible_bounds_height": 2,
|
||||
"visible_bounds_offset": [0, 0.5, 0]
|
||||
},
|
||||
"bones": [
|
||||
{
|
||||
"name": "field",
|
||||
"pivot": [0, 0, 0],
|
||||
"cubes": [
|
||||
{ "origin": [-7, 0, -1], "size": [14, 16, 2], "uv": [0, 0] },
|
||||
{ "origin": [-1, 0, -7], "size": [2, 16, 14], "uv": [0, 0] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"format_version": "1.12.0",
|
||||
"minecraft:geometry": [
|
||||
{
|
||||
"description": {
|
||||
"identifier": "geometry.silverlabs.portal_label",
|
||||
"texture_width": 1,
|
||||
"texture_height": 1,
|
||||
"visible_bounds_width": 0,
|
||||
"visible_bounds_height": 0,
|
||||
"visible_bounds_offset": [0, 0, 0]
|
||||
},
|
||||
"bones": [
|
||||
{
|
||||
"name": "root",
|
||||
"pivot": [0, 0, 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
lobby-addon/lobby_transfer_RP/textures/blocks/portal_field.png
Normal file
BIN
lobby-addon/lobby_transfer_RP/textures/blocks/portal_field.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 941 B |
BIN
lobby-addon/lobby_transfer_RP/textures/entity/portal_label.png
Normal file
BIN
lobby-addon/lobby_transfer_RP/textures/entity/portal_label.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 B |
@@ -15,6 +15,9 @@
|
||||
},
|
||||
"portal_mya": {
|
||||
"textures": "textures/blocks/prismarine_bricks"
|
||||
},
|
||||
"portal_field": {
|
||||
"textures": "textures/blocks/portal_field"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user