feat: Enhance turtle model and visualization with block surroundings and world block management
This commit is contained in:
@@ -1,49 +1,95 @@
|
|||||||
import React, { useRef, useMemo } from 'react';
|
import React, { useRef, useMemo, useEffect } from 'react';
|
||||||
import { Canvas, useFrame } from '@react-three/fiber';
|
import { Canvas, useFrame } from '@react-three/fiber';
|
||||||
import { OrbitControls, Grid, Text, Line } from '@react-three/drei';
|
import { OrbitControls, Grid, Text, Line } from '@react-three/drei';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { useTurtleStore } from '../store/turtleStore';
|
import { useTurtleStore } from '../store/turtleStore';
|
||||||
|
|
||||||
// Turtle marker component
|
// Minecraft-style turtle model
|
||||||
function TurtleMarker({ turtle, isSelected, onClick }) {
|
function TurtleModel({ turtle, isSelected, onClick }) {
|
||||||
const meshRef = useRef();
|
const groupRef = useRef();
|
||||||
const { position, mode } = turtle;
|
const { position, facing, mode } = turtle;
|
||||||
|
|
||||||
useFrame((state) => {
|
useFrame((state) => {
|
||||||
if (meshRef.current && isSelected) {
|
if (groupRef.current && isSelected) {
|
||||||
meshRef.current.rotation.y += 0.02;
|
groupRef.current.children[0].position.y = Math.sin(state.clock.elapsedTime * 2) * 0.05;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!position) return null;
|
if (!position) return null;
|
||||||
|
|
||||||
|
// Calculate rotation based on facing (0=North(-Z), 1=East(+X), 2=South(+Z), 3=West(-X))
|
||||||
|
const rotation = facing !== undefined ? [0, (facing * Math.PI / 2), 0] : [0, 0, 0];
|
||||||
|
|
||||||
const color = mode === 'mining' ? '#4ade80' :
|
const color = mode === 'mining' ? '#4ade80' :
|
||||||
mode === 'exploring' ? '#60a5fa' :
|
mode === 'exploring' ? '#60a5fa' :
|
||||||
mode === 'returning' ? '#f59e0b' :
|
mode === 'returning' ? '#f59e0b' :
|
||||||
'#9ca3af';
|
'#9ca3af';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group position={[position.x, position.y, position.z]} onClick={onClick}>
|
<group ref={groupRef} position={[position.x, position.y, position.z]} rotation={rotation} onClick={onClick}>
|
||||||
{/* Turtle body */}
|
<group>
|
||||||
<mesh ref={meshRef}>
|
{/* Shell (main body) */}
|
||||||
<boxGeometry args={[0.8, 0.6, 0.8]} />
|
<mesh position={[0, 0.2, 0]}>
|
||||||
<meshStandardMaterial
|
<boxGeometry args={[0.8, 0.5, 0.9]} />
|
||||||
color={color}
|
<meshStandardMaterial
|
||||||
emissive={color}
|
color={color}
|
||||||
emissiveIntensity={isSelected ? 0.5 : 0.2}
|
emissive={color}
|
||||||
/>
|
emissiveIntensity={isSelected ? 0.4 : 0.2}
|
||||||
</mesh>
|
roughness={0.4}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Head */}
|
||||||
|
<mesh position={[0, 0.15, 0.55]}>
|
||||||
|
<boxGeometry args={[0.5, 0.35, 0.3]} />
|
||||||
|
<meshStandardMaterial color={color} roughness={0.3} />
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Eyes */}
|
||||||
|
<mesh position={[-0.12, 0.2, 0.7]}>
|
||||||
|
<boxGeometry args={[0.1, 0.1, 0.05]} />
|
||||||
|
<meshStandardMaterial color="#ffffff" emissive="#ffff00" emissiveIntensity={0.5} />
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[0.12, 0.2, 0.7]}>
|
||||||
|
<boxGeometry args={[0.1, 0.1, 0.05]} />
|
||||||
|
<meshStandardMaterial color="#ffffff" emissive="#ffff00" emissiveIntensity={0.5} />
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Legs */}
|
||||||
|
<mesh position={[-0.3, -0.15, 0.25]}>
|
||||||
|
<boxGeometry args={[0.2, 0.2, 0.2]} />
|
||||||
|
<meshStandardMaterial color="#666" />
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[0.3, -0.15, 0.25]}>
|
||||||
|
<boxGeometry args={[0.2, 0.2, 0.2]} />
|
||||||
|
<meshStandardMaterial color="#666" />
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[-0.3, -0.15, -0.25]}>
|
||||||
|
<boxGeometry args={[0.2, 0.2, 0.2]} />
|
||||||
|
<meshStandardMaterial color="#666" />
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[0.3, -0.15, -0.25]}>
|
||||||
|
<boxGeometry args={[0.2, 0.2, 0.2]} />
|
||||||
|
<meshStandardMaterial color="#666" />
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* Tail */}
|
||||||
|
<mesh position={[0, 0.1, -0.55]}>
|
||||||
|
<boxGeometry args={[0.15, 0.15, 0.2]} />
|
||||||
|
<meshStandardMaterial color={color} />
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
|
||||||
{/* Selection indicator */}
|
{/* Selection indicator */}
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<>
|
<>
|
||||||
<mesh position={[0, 1, 0]}>
|
<mesh position={[0, 1.2, 0]}>
|
||||||
<coneGeometry args={[0.3, 0.5, 4]} />
|
<coneGeometry args={[0.3, 0.5, 4]} />
|
||||||
<meshStandardMaterial color="#ffffff" emissive="#ffffff" emissiveIntensity={0.8} />
|
<meshStandardMaterial color="#ffffff" emissive="#ffffff" emissiveIntensity={0.8} />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
{/* Selection ring */}
|
{/* Selection ring */}
|
||||||
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.4, 0]}>
|
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.5, 0]}>
|
||||||
<ringGeometry args={[1, 1.2, 32]} />
|
<ringGeometry args={[1, 1.2, 32]} />
|
||||||
<meshBasicMaterial color="#ffffff" side={THREE.DoubleSide} transparent opacity={0.5} />
|
<meshBasicMaterial color="#ffffff" side={THREE.DoubleSide} transparent opacity={0.5} />
|
||||||
</mesh>
|
</mesh>
|
||||||
@@ -58,7 +104,7 @@ function TurtleMarker({ turtle, isSelected, onClick }) {
|
|||||||
anchorX="center"
|
anchorX="center"
|
||||||
anchorY="middle"
|
anchorY="middle"
|
||||||
>
|
>
|
||||||
T-{turtle.turtleID}
|
🐢 {turtle.turtleID}
|
||||||
</Text>
|
</Text>
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
@@ -75,6 +121,99 @@ function PathTrail({ turtle }) {
|
|||||||
new THREE.Vector3(homePosition.x, homePosition.y, homePosition.z)
|
new THREE.Vector3(homePosition.x, homePosition.y, homePosition.z)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Line
|
||||||
|
points={points}
|
||||||
|
color="#60a5fa"
|
||||||
|
lineWidth={2}
|
||||||
|
transparent
|
||||||
|
opacity={0.3}
|
||||||
|
dashed
|
||||||
|
dashScale={2}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get color for block type
|
||||||
|
function getBlockColor(blockName) {
|
||||||
|
if (!blockName) return '#888';
|
||||||
|
|
||||||
|
const name = blockName.toLowerCase();
|
||||||
|
|
||||||
|
// Ores
|
||||||
|
if (name.includes('diamond')) return '#00ffff';
|
||||||
|
if (name.includes('emerald')) return '#00ff00';
|
||||||
|
if (name.includes('gold')) return '#ffd700';
|
||||||
|
if (name.includes('iron')) return '#d4d4d4';
|
||||||
|
if (name.includes('coal')) return '#1a1a1a';
|
||||||
|
if (name.includes('redstone')) return '#ff0000';
|
||||||
|
if (name.includes('lapis')) return '#0000ff';
|
||||||
|
if (name.includes('copper')) return '#ff8c00';
|
||||||
|
|
||||||
|
// Common blocks
|
||||||
|
if (name.includes('stone')) return '#7f7f7f';
|
||||||
|
if (name.includes('dirt')) return '#8b4513';
|
||||||
|
if (name.includes('grass')) return '#228b22';
|
||||||
|
if (name.includes('sand')) return '#f4a460';
|
||||||
|
if (name.includes('gravel')) return '#808080';
|
||||||
|
if (name.includes('bedrock')) return '#222';
|
||||||
|
if (name.includes('obsidian')) return '#1a0033';
|
||||||
|
if (name.includes('netherrack')) return '#8b0000';
|
||||||
|
|
||||||
|
return '#666666'; // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorldBlocks component to render discovered blocks
|
||||||
|
function WorldBlocks({ blocks }) {
|
||||||
|
const meshes = useMemo(() => {
|
||||||
|
const instances = new Map();
|
||||||
|
|
||||||
|
blocks.forEach(block => {
|
||||||
|
const color = getBlockColor(block.name);
|
||||||
|
if (!instances.has(color)) {
|
||||||
|
instances.set(color, []);
|
||||||
|
}
|
||||||
|
instances.get(color).push(block);
|
||||||
|
});
|
||||||
|
|
||||||
|
return instances;
|
||||||
|
}, [blocks]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group>
|
||||||
|
{Array.from(meshes.entries()).map(([color, blockList]) => (
|
||||||
|
<group key={color}>
|
||||||
|
{blockList.map((block, idx) => (
|
||||||
|
<mesh
|
||||||
|
key={`${block.x},${block.y},${block.z}`}
|
||||||
|
position={[block.x, block.y, block.z]}
|
||||||
|
>
|
||||||
|
<boxGeometry args={[0.9, 0.9, 0.9]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color={color}
|
||||||
|
transparent
|
||||||
|
opacity={0.6}
|
||||||
|
roughness={0.8}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
))}
|
||||||
|
</group>
|
||||||
|
))}
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path trail component (old version removed above)
|
||||||
|
function OldPathTrail({ turtle }) {
|
||||||
|
const { position, homePosition } = turtle;
|
||||||
|
|
||||||
|
if (!position || !homePosition) return null;
|
||||||
|
|
||||||
|
const points = [
|
||||||
|
new THREE.Vector3(position.x, position.y, position.z),
|
||||||
|
new THREE.Vector3(homePosition.x, homePosition.y, homePosition.z)
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Line
|
<Line
|
||||||
points={points}
|
points={points}
|
||||||
@@ -116,6 +255,7 @@ function Scene() {
|
|||||||
const turtles = useTurtleStore((state) => state.getTurtleArray());
|
const turtles = useTurtleStore((state) => state.getTurtleArray());
|
||||||
const selectedTurtleId = useTurtleStore((state) => state.selectedTurtleId);
|
const selectedTurtleId = useTurtleStore((state) => state.selectedTurtleId);
|
||||||
const selectTurtle = useTurtleStore((state) => state.selectTurtle);
|
const selectTurtle = useTurtleStore((state) => state.selectTurtle);
|
||||||
|
const worldBlocks = useTurtleStore((state) => state.worldBlocks || []);
|
||||||
|
|
||||||
// Calculate center point for camera focus
|
// Calculate center point for camera focus
|
||||||
const centerPoint = useMemo(() => {
|
const centerPoint = useMemo(() => {
|
||||||
@@ -142,9 +282,10 @@ function Scene() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ambientLight intensity={0.5} />
|
<ambientLight intensity={0.6} />
|
||||||
<pointLight position={[10, 10, 10]} intensity={1} />
|
<pointLight position={[10, 10, 10]} intensity={1.2} />
|
||||||
<pointLight position={[-10, -10, -10]} intensity={0.5} />
|
<pointLight position={[-10, -10, -10]} intensity={0.6} />
|
||||||
|
<pointLight position={[0, 20, 0]} intensity={0.8} color="#ffffff" />
|
||||||
|
|
||||||
{/* Grid */}
|
{/* Grid */}
|
||||||
<Grid
|
<Grid
|
||||||
@@ -161,6 +302,9 @@ function Scene() {
|
|||||||
infiniteGrid
|
infiniteGrid
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Render discovered blocks */}
|
||||||
|
<WorldBlocks blocks={worldBlocks} />
|
||||||
|
|
||||||
{/* Home marker */}
|
{/* Home marker */}
|
||||||
{homePosition && <HomeMarker position={homePosition} />}
|
{homePosition && <HomeMarker position={homePosition} />}
|
||||||
|
|
||||||
@@ -168,7 +312,7 @@ function Scene() {
|
|||||||
{turtles.map((turtle) => (
|
{turtles.map((turtle) => (
|
||||||
<React.Fragment key={turtle.turtleID}>
|
<React.Fragment key={turtle.turtleID}>
|
||||||
<PathTrail turtle={turtle} />
|
<PathTrail turtle={turtle} />
|
||||||
<TurtleMarker
|
<TurtleModel
|
||||||
turtle={turtle}
|
turtle={turtle}
|
||||||
isSelected={selectedTurtleId === turtle.turtleID}
|
isSelected={selectedTurtleId === turtle.turtleID}
|
||||||
onClick={() => selectTurtle(turtle.turtleID)}
|
onClick={() => selectTurtle(turtle.turtleID)}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ console.log('📡 API URL:', API_URL);
|
|||||||
export const useTurtleStore = create((set, get) => ({
|
export const useTurtleStore = create((set, get) => ({
|
||||||
// State
|
// State
|
||||||
turtles: {},
|
turtles: {},
|
||||||
|
worldBlocks: [],
|
||||||
selectedTurtleId: null,
|
selectedTurtleId: null,
|
||||||
connected: false,
|
connected: false,
|
||||||
ws: null,
|
ws: null,
|
||||||
@@ -32,7 +33,10 @@ export const useTurtleStore = create((set, get) => ({
|
|||||||
data.turtles.forEach(turtle => {
|
data.turtles.forEach(turtle => {
|
||||||
turtlesMap[turtle.turtleID] = turtle;
|
turtlesMap[turtle.turtleID] = turtle;
|
||||||
});
|
});
|
||||||
set({ turtles: turtlesMap });
|
set({
|
||||||
|
turtles: turtlesMap,
|
||||||
|
worldBlocks: data.blocks || []
|
||||||
|
});
|
||||||
} else if (data.type === 'turtle_update') {
|
} else if (data.type === 'turtle_update') {
|
||||||
set(state => ({
|
set(state => ({
|
||||||
turtles: {
|
turtles: {
|
||||||
@@ -40,6 +44,11 @@ export const useTurtleStore = create((set, get) => ({
|
|||||||
[data.turtle.turtleID]: data.turtle
|
[data.turtle.turtleID]: data.turtle
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Update world blocks from turtle surroundings
|
||||||
|
if (data.turtle.surroundings && data.turtle.position && data.turtle.facing !== undefined) {
|
||||||
|
get().updateBlocksFromSurroundings(data.turtle);
|
||||||
|
}
|
||||||
} else if (data.type === 'turtle_disconnected') {
|
} else if (data.type === 'turtle_disconnected') {
|
||||||
set(state => {
|
set(state => {
|
||||||
const newTurtles = { ...state.turtles };
|
const newTurtles = { ...state.turtles };
|
||||||
@@ -71,6 +80,52 @@ export const useTurtleStore = create((set, get) => ({
|
|||||||
selectTurtle: (turtleId) => {
|
selectTurtle: (turtleId) => {
|
||||||
set({ selectedTurtleId: turtleId });
|
set({ selectedTurtleId: turtleId });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateBlocksFromSurroundings: (turtle) => {
|
||||||
|
const { surroundings, position, facing } = turtle;
|
||||||
|
if (!surroundings || !position || facing === undefined) return;
|
||||||
|
|
||||||
|
const newBlocks = [];
|
||||||
|
const blockMap = new Map(get().worldBlocks.map(b => [`${b.x},${b.y},${b.z}`, b]));
|
||||||
|
|
||||||
|
// Calculate block positions based on turtle position and facing
|
||||||
|
const directions = {
|
||||||
|
forward: { x: 0, y: 0, z: 0 },
|
||||||
|
up: { x: 0, y: 1, z: 0 },
|
||||||
|
down: { x: 0, y: -1, z: 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Facing: 0=North(-Z), 1=East(+X), 2=South(+Z), 3=West(-X)
|
||||||
|
if (surroundings.forward) {
|
||||||
|
if (facing === 0) directions.forward = { x: 0, y: 0, z: -1 };
|
||||||
|
else if (facing === 1) directions.forward = { x: 1, y: 0, z: 0 };
|
||||||
|
else if (facing === 2) directions.forward = { x: 0, y: 0, z: 1 };
|
||||||
|
else if (facing === 3) directions.forward = { x: -1, y: 0, z: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(surroundings).forEach(([direction, blockData]) => {
|
||||||
|
const offset = directions[direction];
|
||||||
|
if (!offset) return;
|
||||||
|
|
||||||
|
const blockPos = {
|
||||||
|
x: position.x + offset.x,
|
||||||
|
y: position.y + offset.y,
|
||||||
|
z: position.z + offset.z
|
||||||
|
};
|
||||||
|
|
||||||
|
const key = `${blockPos.x},${blockPos.y},${blockPos.z}`;
|
||||||
|
if (!blockMap.has(key)) {
|
||||||
|
blockMap.set(key, {
|
||||||
|
...blockPos,
|
||||||
|
...blockData,
|
||||||
|
discoveredBy: turtle.turtleID,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
set({ worldBlocks: Array.from(blockMap.values()) });
|
||||||
|
},
|
||||||
|
|
||||||
sendCommand: async (turtleId, command, param = null) => {
|
sendCommand: async (turtleId, command, param = null) => {
|
||||||
const { ws } = get();
|
const { ws } = get();
|
||||||
|
|||||||
@@ -13,6 +13,38 @@ app.use(express.json());
|
|||||||
// Store connected web clients and turtle data
|
// Store connected web clients and turtle data
|
||||||
const webClients = new Set();
|
const webClients = new Set();
|
||||||
const turtleData = new Map(); // turtleID -> turtle state
|
const turtleData = new Map(); // turtleID -> turtle state
|
||||||
|
const worldBlocks = new Map(); // "x,y,z" -> {name, metadata, discoveredBy, timestamp}
|
||||||
|
|
||||||
|
// Helper to store discovered blocks
|
||||||
|
function storeBlock(x, y, z, blockData, turtleID) {
|
||||||
|
const key = `${x},${y},${z}`;
|
||||||
|
worldBlocks.set(key, {
|
||||||
|
...blockData,
|
||||||
|
discoveredBy: turtleID,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to calculate block position based on turtle position and facing
|
||||||
|
function getBlockPosition(turtlePos, facing, direction) {
|
||||||
|
if (!turtlePos) return null;
|
||||||
|
|
||||||
|
const pos = { ...turtlePos };
|
||||||
|
|
||||||
|
if (direction === 'up') {
|
||||||
|
pos.y += 1;
|
||||||
|
} else if (direction === 'down') {
|
||||||
|
pos.y -= 1;
|
||||||
|
} else if (direction === 'forward') {
|
||||||
|
// Calculate based on facing direction
|
||||||
|
if (facing === 0) pos.z -= 1; // North
|
||||||
|
else if (facing === 1) pos.x += 1; // East
|
||||||
|
else if (facing === 2) pos.z += 1; // South
|
||||||
|
else if (facing === 3) pos.x -= 1; // West
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
// Create HTTP server
|
// Create HTTP server
|
||||||
const server = createServer(app);
|
const server = createServer(app);
|
||||||
@@ -29,10 +61,17 @@ wss.on('connection', (ws) => {
|
|||||||
console.log('🌐 New web client connected');
|
console.log('🌐 New web client connected');
|
||||||
webClients.add(ws);
|
webClients.add(ws);
|
||||||
|
|
||||||
// Send current turtle data to new client
|
// Send current turtle data and world blocks to new client
|
||||||
|
const blocks = [];
|
||||||
|
for (const [key, blockData] of worldBlocks.entries()) {
|
||||||
|
const [x, y, z] = key.split(',').map(Number);
|
||||||
|
blocks.push({ x, y, z, ...blockData });
|
||||||
|
}
|
||||||
|
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
type: 'initial_state',
|
type: 'initial_state',
|
||||||
turtles: Array.from(turtleData.values())
|
turtles: Array.from(turtleData.values()),
|
||||||
|
blocks: blocks
|
||||||
}));
|
}));
|
||||||
|
|
||||||
ws.on('message', (message) => {
|
ws.on('message', (message) => {
|
||||||
@@ -101,6 +140,16 @@ app.post('/api/turtle/update', (req, res) => {
|
|||||||
...turtleUpdate,
|
...turtleUpdate,
|
||||||
lastUpdate: Date.now()
|
lastUpdate: Date.now()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Store discovered blocks in world map
|
||||||
|
if (turtleUpdate.surroundings && turtleUpdate.position && turtleUpdate.facing !== undefined) {
|
||||||
|
for (const [direction, blockData] of Object.entries(turtleUpdate.surroundings)) {
|
||||||
|
const blockPos = getBlockPosition(turtleUpdate.position, turtleUpdate.facing, direction);
|
||||||
|
if (blockPos) {
|
||||||
|
storeBlock(blockPos.x, blockPos.y, blockPos.z, blockData, turtleID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Broadcast to web clients
|
// Broadcast to web clients
|
||||||
broadcastToClients({
|
broadcastToClients({
|
||||||
@@ -149,6 +198,19 @@ app.get('/api/turtles', (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get world blocks for map visualization
|
||||||
|
app.get('/api/world/blocks', (req, res) => {
|
||||||
|
const blocks = [];
|
||||||
|
for (const [key, blockData] of worldBlocks.entries()) {
|
||||||
|
const [x, y, z] = key.split(',').map(Number);
|
||||||
|
blocks.push({
|
||||||
|
x, y, z,
|
||||||
|
...blockData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res.json({ blocks });
|
||||||
|
});
|
||||||
|
|
||||||
// Send command to turtle
|
// Send command to turtle
|
||||||
app.post('/api/turtle/:id/command', (req, res) => {
|
app.post('/api/turtle/:id/command', (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
25
turtle.lua
25
turtle.lua
@@ -358,6 +358,28 @@ function broadcastStatus()
|
|||||||
-- Don't update position on every broadcast to avoid GPS delays
|
-- Don't update position on every broadcast to avoid GPS delays
|
||||||
-- Position will be updated by movement functions
|
-- Position will be updated by movement functions
|
||||||
|
|
||||||
|
-- Scan surrounding blocks for map visualization
|
||||||
|
local surroundings = {}
|
||||||
|
local hasBlock, data
|
||||||
|
|
||||||
|
-- Check forward
|
||||||
|
hasBlock, data = turtle.inspect()
|
||||||
|
if hasBlock and data then
|
||||||
|
surroundings.forward = {name = data.name, metadata = data.metadata or 0}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check up
|
||||||
|
hasBlock, data = turtle.inspectUp()
|
||||||
|
if hasBlock and data then
|
||||||
|
surroundings.up = {name = data.name, metadata = data.metadata or 0}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check down
|
||||||
|
hasBlock, data = turtle.inspectDown()
|
||||||
|
if hasBlock and data then
|
||||||
|
surroundings.down = {name = data.name, metadata = data.metadata or 0}
|
||||||
|
end
|
||||||
|
|
||||||
modem.transmit(STATUS_CHANNEL, CHANNEL_RECEIVE, {
|
modem.transmit(STATUS_CHANNEL, CHANNEL_RECEIVE, {
|
||||||
type = "status",
|
type = "status",
|
||||||
turtleID = os.getComputerID(),
|
turtleID = os.getComputerID(),
|
||||||
@@ -367,7 +389,8 @@ function broadcastStatus()
|
|||||||
fuel = state.fuel,
|
fuel = state.fuel,
|
||||||
inventoryCount = #state.inventory,
|
inventoryCount = #state.inventory,
|
||||||
inventory = state.inventory,
|
inventory = state.inventory,
|
||||||
facing = facing
|
facing = facing,
|
||||||
|
surroundings = surroundings
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user