feat(portals): add craftable portal blocks replacing hardcoded coordinates
Portal frame crafted from obsidian + ender pearl, combined with crystals (emerald/amethyst/prismarine) to create world-specific portal blocks. Stepping on a portal block triggers transfer. Includes resource pack with vanilla textures, block definitions, crafting recipes, and updated script. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- lobby-data:/data
|
- lobby-data:/data
|
||||||
- ./lobby-addon/lobby_transfer_BP:/data/behavior_packs/lobby_transfer_BP
|
- ./lobby-addon/lobby_transfer_BP:/data/behavior_packs/lobby_transfer_BP
|
||||||
|
- ./lobby-addon/lobby_transfer_RP:/data/resource_packs/lobby_transfer_RP
|
||||||
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
||||||
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
28
lobby-addon/lobby_transfer_BP/blocks/portal_frame.json
Normal file
28
lobby-addon/lobby_transfer_BP/blocks/portal_frame.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.40",
|
||||||
|
"minecraft:block": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:portal_frame",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "items",
|
||||||
|
"group": "itemGroup.name.decorations"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:destructible_by_mining": {
|
||||||
|
"seconds_to_destroy": 5.0
|
||||||
|
},
|
||||||
|
"minecraft:destructible_by_explosion": {
|
||||||
|
"explosion_resistance": 1200.0
|
||||||
|
},
|
||||||
|
"minecraft:light_emission": 10,
|
||||||
|
"minecraft:map_color": "#5C6D74",
|
||||||
|
"minecraft:material_instances": {
|
||||||
|
"*": {
|
||||||
|
"texture": "portal_frame",
|
||||||
|
"render_method": "opaque"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
lobby-addon/lobby_transfer_BP/blocks/portal_jamie.json
Normal file
28
lobby-addon/lobby_transfer_BP/blocks/portal_jamie.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.40",
|
||||||
|
"minecraft:block": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:portal_jamie",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "items",
|
||||||
|
"group": "itemGroup.name.decorations"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:destructible_by_mining": {
|
||||||
|
"seconds_to_destroy": 5.0
|
||||||
|
},
|
||||||
|
"minecraft:destructible_by_explosion": {
|
||||||
|
"explosion_resistance": 1200.0
|
||||||
|
},
|
||||||
|
"minecraft:light_emission": 10,
|
||||||
|
"minecraft:map_color": "#2D9C2D",
|
||||||
|
"minecraft:material_instances": {
|
||||||
|
"*": {
|
||||||
|
"texture": "portal_jamie",
|
||||||
|
"render_method": "opaque"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
lobby-addon/lobby_transfer_BP/blocks/portal_lyla.json
Normal file
28
lobby-addon/lobby_transfer_BP/blocks/portal_lyla.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.40",
|
||||||
|
"minecraft:block": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:portal_lyla",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "items",
|
||||||
|
"group": "itemGroup.name.decorations"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:destructible_by_mining": {
|
||||||
|
"seconds_to_destroy": 5.0
|
||||||
|
},
|
||||||
|
"minecraft:destructible_by_explosion": {
|
||||||
|
"explosion_resistance": 1200.0
|
||||||
|
},
|
||||||
|
"minecraft:light_emission": 10,
|
||||||
|
"minecraft:map_color": "#9B59B6",
|
||||||
|
"minecraft:material_instances": {
|
||||||
|
"*": {
|
||||||
|
"texture": "portal_lyla",
|
||||||
|
"render_method": "opaque"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
lobby-addon/lobby_transfer_BP/blocks/portal_mya.json
Normal file
28
lobby-addon/lobby_transfer_BP/blocks/portal_mya.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.40",
|
||||||
|
"minecraft:block": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:portal_mya",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "items",
|
||||||
|
"group": "itemGroup.name.decorations"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:destructible_by_mining": {
|
||||||
|
"seconds_to_destroy": 5.0
|
||||||
|
},
|
||||||
|
"minecraft:destructible_by_explosion": {
|
||||||
|
"explosion_resistance": 1200.0
|
||||||
|
},
|
||||||
|
"minecraft:light_emission": 10,
|
||||||
|
"minecraft:map_color": "#4FC1E9",
|
||||||
|
"minecraft:material_instances": {
|
||||||
|
"*": {
|
||||||
|
"texture": "portal_mya",
|
||||||
|
"render_method": "opaque"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"name": "Lobby Portal Transfer",
|
"name": "Lobby Portal Transfer",
|
||||||
"description": "Auto-transfers players when they step into portal areas",
|
"description": "Auto-transfers players when they step into portal areas",
|
||||||
"uuid": "a1b2c3d4-1111-2222-3333-abcdef123456",
|
"uuid": "a1b2c3d4-1111-2222-3333-abcdef123456",
|
||||||
"version": [1, 0, 4],
|
"version": [1, 1, 0],
|
||||||
"min_engine_version": [1, 21, 0]
|
"min_engine_version": [1, 21, 0]
|
||||||
},
|
},
|
||||||
"modules": [
|
"modules": [
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"type": "script",
|
"type": "script",
|
||||||
"language": "javascript",
|
"language": "javascript",
|
||||||
"uuid": "a1b2c3d4-4444-5555-6666-abcdef789012",
|
"uuid": "a1b2c3d4-4444-5555-6666-abcdef789012",
|
||||||
"version": [1, 0, 4],
|
"version": [1, 1, 0],
|
||||||
"entry": "scripts/main.js"
|
"entry": "scripts/main.js"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -24,6 +24,10 @@
|
|||||||
{
|
{
|
||||||
"module_name": "@minecraft/server-admin",
|
"module_name": "@minecraft/server-admin",
|
||||||
"version": "1.0.0-beta"
|
"version": "1.0.0-beta"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "b2c3d4e5-1111-2222-3333-fedcba654321",
|
||||||
|
"version": [1, 0, 0]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
25
lobby-addon/lobby_transfer_BP/recipes/portal_frame.json
Normal file
25
lobby-addon/lobby_transfer_BP/recipes/portal_frame.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.40",
|
||||||
|
"minecraft:recipe_shaped": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:portal_frame"
|
||||||
|
},
|
||||||
|
"tags": ["crafting_table"],
|
||||||
|
"pattern": [
|
||||||
|
" O ",
|
||||||
|
"OEO",
|
||||||
|
" O "
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"O": { "item": "minecraft:obsidian" },
|
||||||
|
"E": { "item": "minecraft:ender_pearl" }
|
||||||
|
},
|
||||||
|
"unlock": [
|
||||||
|
{ "item": "minecraft:obsidian" }
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:portal_frame",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
lobby-addon/lobby_transfer_BP/recipes/portal_jamie.json
Normal file
20
lobby-addon/lobby_transfer_BP/recipes/portal_jamie.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.40",
|
||||||
|
"minecraft:recipe_shapeless": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:portal_jamie"
|
||||||
|
},
|
||||||
|
"tags": ["crafting_table"],
|
||||||
|
"ingredients": [
|
||||||
|
{ "item": "silverlabs:portal_frame" },
|
||||||
|
{ "item": "minecraft:emerald" }
|
||||||
|
],
|
||||||
|
"unlock": [
|
||||||
|
{ "item": "silverlabs:portal_frame" }
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:portal_jamie",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
lobby-addon/lobby_transfer_BP/recipes/portal_lyla.json
Normal file
20
lobby-addon/lobby_transfer_BP/recipes/portal_lyla.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.40",
|
||||||
|
"minecraft:recipe_shapeless": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:portal_lyla"
|
||||||
|
},
|
||||||
|
"tags": ["crafting_table"],
|
||||||
|
"ingredients": [
|
||||||
|
{ "item": "silverlabs:portal_frame" },
|
||||||
|
{ "item": "minecraft:amethyst_shard" }
|
||||||
|
],
|
||||||
|
"unlock": [
|
||||||
|
{ "item": "silverlabs:portal_frame" }
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:portal_lyla",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
lobby-addon/lobby_transfer_BP/recipes/portal_mya.json
Normal file
20
lobby-addon/lobby_transfer_BP/recipes/portal_mya.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.40",
|
||||||
|
"minecraft:recipe_shapeless": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:portal_mya"
|
||||||
|
},
|
||||||
|
"tags": ["crafting_table"],
|
||||||
|
"ingredients": [
|
||||||
|
{ "item": "silverlabs:portal_frame" },
|
||||||
|
{ "item": "minecraft:prismarine_crystals" }
|
||||||
|
],
|
||||||
|
"unlock": [
|
||||||
|
{ "item": "silverlabs:portal_frame" }
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:portal_mya",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
import { world, system } from "@minecraft/server";
|
import { world, system } from "@minecraft/server";
|
||||||
import { transferPlayer } from "@minecraft/server-admin";
|
import { transferPlayer } from "@minecraft/server-admin";
|
||||||
|
|
||||||
// Portal definitions: name, center position, color, and direct transfer target
|
// Portal block → transfer target mapping
|
||||||
const portals = [
|
const PORTAL_BLOCKS = {
|
||||||
{ name: "Jamie's World", x: -15, y: 65, z: -24, host: "10.0.0.247", port: 19133, color: "§a" },
|
"silverlabs:portal_jamie": { name: "Jamie's World", host: "10.0.0.247", port: 19133, color: "§a" },
|
||||||
{ name: "Lyla's World", x: 0, y: 65, z: -24, host: "10.0.0.247", port: 19134, color: "§d" },
|
"silverlabs:portal_lyla": { name: "Lyla's World", host: "10.0.0.247", port: 19134, color: "§d" },
|
||||||
{ name: "Mya's World", x: 15, y: 65, z: -24, host: "10.0.0.247", port: 19135, color: "§b" },
|
"silverlabs:portal_mya": { name: "Mya's World", host: "10.0.0.247", port: 19135, color: "§b" },
|
||||||
];
|
};
|
||||||
|
|
||||||
const PORTAL_RADIUS_X = 2.5;
|
|
||||||
const PORTAL_RADIUS_Z = 2.0;
|
|
||||||
const PORTAL_RADIUS_Y = 2.0;
|
|
||||||
const COOLDOWN_TICKS = 100; // 5 seconds cooldown
|
const COOLDOWN_TICKS = 100; // 5 seconds cooldown
|
||||||
const SPAWN_PROTECTION_TICKS = 200; // 10 seconds — ignore portal detection after spawn
|
const SPAWN_PROTECTION_TICKS = 200; // 10 seconds — ignore portal detection after spawn
|
||||||
|
|
||||||
@@ -36,24 +33,28 @@ system.runInterval(() => {
|
|||||||
const spawnedAt = spawnTicks.get(playerId) || 0;
|
const spawnedAt = spawnTicks.get(playerId) || 0;
|
||||||
if (system.currentTick - spawnedAt < SPAWN_PROTECTION_TICKS) continue;
|
if (system.currentTick - spawnedAt < SPAWN_PROTECTION_TICKS) continue;
|
||||||
|
|
||||||
for (const portal of portals) {
|
// Check block at player's feet and one block below
|
||||||
const dx = Math.abs(pos.x - portal.x);
|
const dimension = player.dimension;
|
||||||
const dy = Math.abs(pos.y - portal.y);
|
const blockAtFeet = dimension.getBlock({ x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) });
|
||||||
const dz = Math.abs(pos.z - portal.z);
|
const blockBelow = dimension.getBlock({ x: Math.floor(pos.x), y: Math.floor(pos.y) - 1, z: Math.floor(pos.z) });
|
||||||
|
|
||||||
if (dx < PORTAL_RADIUS_X && dy < PORTAL_RADIUS_Y && dz < PORTAL_RADIUS_Z) {
|
const feetId = blockAtFeet?.typeId;
|
||||||
cooldowns.set(playerId, system.currentTick);
|
const belowId = blockBelow?.typeId;
|
||||||
// Show title notification
|
|
||||||
player.runCommand(`titleraw @s title {"rawtext":[{"text":"${portal.color}${portal.name}"}]}`);
|
const portal = PORTAL_BLOCKS[feetId] || PORTAL_BLOCKS[belowId];
|
||||||
player.runCommand(`titleraw @s subtitle {"rawtext":[{"text":"§7Transferring..."}]}`);
|
if (!portal) continue;
|
||||||
player.sendMessage(`§6Transferring to ${portal.name}...`);
|
|
||||||
try {
|
cooldowns.set(playerId, system.currentTick);
|
||||||
transferPlayer(player, { hostname: portal.host, port: portal.port });
|
// Teleport player away from the portal block so they don't land on it on return
|
||||||
} catch (e) {
|
player.teleport({ x: pos.x, y: pos.y, z: pos.z + 3 });
|
||||||
player.sendMessage(`§cTransfer failed: ${e.message}`);
|
// Show title notification
|
||||||
}
|
player.runCommand(`titleraw @s title {"rawtext":[{"text":"${portal.color}${portal.name}"}]}`);
|
||||||
break;
|
player.runCommand(`titleraw @s subtitle {"rawtext":[{"text":"§7Transferring..."}]}`);
|
||||||
}
|
player.sendMessage(`§6Transferring to ${portal.name}...`);
|
||||||
|
try {
|
||||||
|
transferPlayer(player, { hostname: portal.host, port: portal.port });
|
||||||
|
} catch (e) {
|
||||||
|
player.sendMessage(`§cTransfer failed: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 10); // Check every half second
|
}, 10); // Check every half second
|
||||||
@@ -64,45 +65,6 @@ world.afterEvents.playerLeave.subscribe((event) => {
|
|||||||
spawnTicks.delete(event.playerId);
|
spawnTicks.delete(event.playerId);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── Place signs above each portal on load ──────────────────────
|
|
||||||
|
|
||||||
function placePortalSigns() {
|
|
||||||
const overworld = world.getDimension("overworld");
|
|
||||||
const signs = [
|
|
||||||
{ x: -15, y: 70, z: -23, name: "Jamie's", color: "§a" },
|
|
||||||
{ x: 0, y: 70, z: -23, name: "Lyla's", color: "§d" },
|
|
||||||
{ x: 15, y: 70, z: -23, name: "Mya's", color: "§b" },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const sign of signs) {
|
|
||||||
try {
|
|
||||||
// Place wall sign facing south (toward players approaching from +Z)
|
|
||||||
overworld.runCommand(`setblock ${sign.x} ${sign.y} ${sign.z} oak_wall_sign ["facing_direction":3]`);
|
|
||||||
} catch (e) {
|
|
||||||
// Non-fatal — chunks may not be loaded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delay to let sign blocks register, then write text
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
system.runTimeout(() => {
|
|
||||||
placePortalSigns();
|
|
||||||
}, 40);
|
|
||||||
|
|
||||||
system.run(() => {
|
system.run(() => {
|
||||||
world.sendMessage("§6[Hub] §7Portal transfer system loaded!");
|
world.sendMessage("§6[Hub] §7Portal transfer system loaded! Place portal blocks to create portals.");
|
||||||
});
|
});
|
||||||
|
|||||||
17
lobby-addon/lobby_transfer_RP/manifest.json
Normal file
17
lobby-addon/lobby_transfer_RP/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"name": "Lobby Portal Transfer Resources",
|
||||||
|
"description": "Textures and lang for craftable portal blocks",
|
||||||
|
"uuid": "b2c3d4e5-1111-2222-3333-fedcba654321",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"min_engine_version": [1, 21, 0]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "resources",
|
||||||
|
"uuid": "b2c3d4e5-4444-5555-6666-fedcba987654",
|
||||||
|
"version": [1, 0, 0]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
lobby-addon/lobby_transfer_RP/pack_icon.png
Normal file
BIN
lobby-addon/lobby_transfer_RP/pack_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 971 B |
4
lobby-addon/lobby_transfer_RP/texts/en_US.lang
Normal file
4
lobby-addon/lobby_transfer_RP/texts/en_US.lang
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
tile.silverlabs:portal_frame.name=Portal Frame
|
||||||
|
tile.silverlabs:portal_jamie.name=Jamie's World Portal
|
||||||
|
tile.silverlabs:portal_lyla.name=Lyla's World Portal
|
||||||
|
tile.silverlabs:portal_mya.name=Mya's World Portal
|
||||||
20
lobby-addon/lobby_transfer_RP/textures/terrain_texture.json
Normal file
20
lobby-addon/lobby_transfer_RP/textures/terrain_texture.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"resource_pack_name": "lobby_transfer_RP",
|
||||||
|
"texture_name": "atlas.terrain",
|
||||||
|
"padding": 8,
|
||||||
|
"num_mip_levels": 4,
|
||||||
|
"texture_data": {
|
||||||
|
"portal_frame": {
|
||||||
|
"textures": "textures/blocks/end_portal_frame_top"
|
||||||
|
},
|
||||||
|
"portal_jamie": {
|
||||||
|
"textures": "textures/blocks/emerald_block"
|
||||||
|
},
|
||||||
|
"portal_lyla": {
|
||||||
|
"textures": "textures/blocks/amethyst_block"
|
||||||
|
},
|
||||||
|
"portal_mya": {
|
||||||
|
"textures": "textures/blocks/prismarine_bricks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user