feat: Add turtle auto-naming, state recovery, and multiple action endpoints for inventory management

This commit is contained in:
MayaTheShy
2026-02-20 02:34:13 -05:00
parent 7b63c92434
commit 67b2d7eb2e

View File

@@ -95,6 +95,12 @@ function getOrCreateTurtle(turtleID) {
turtle.homePosition = savedHome;
}
// Auto-name if no label
turtle.autoName();
// Try to recover previous state from DB (e.g., if turtle rebooted)
turtle.recoverState();
// ---- Event handlers ----
// Forward eval commands to webbridge via pending command queue
@@ -1330,6 +1336,250 @@ app.get('/api/player/:id', (req, res) => {
}
});
// ========== TURTLE ACTION ENDPOINTS ==========
// Rename a turtle
app.post('/api/turtle/:id/rename', async (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const { name } = req.body;
if (!name || typeof name !== 'string') {
return res.status(400).json({ error: 'Missing or invalid name' });
}
const turtle = turtles.get(turtleID);
if (!turtle) {
return res.status(404).json({ error: 'Turtle not found' });
}
await turtle.rename(name.trim());
console.log(`📝 Turtle ${turtleID} renamed to "${name.trim()}"`);
res.json({ success: true, label: turtle.label });
} catch (error) {
console.error(`❌ Error renaming turtle ${req.params.id}:`, error.message);
res.json({ success: false, error: error.message });
}
});
// Equip item on left side
app.post('/api/turtle/:id/equip-left', async (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const turtle = turtles.get(turtleID);
if (!turtle) return res.status(404).json({ error: 'Turtle not found' });
const result = await turtle.equipLeft();
res.json({ success: true, result });
} catch (error) {
res.json({ success: false, error: error.message });
}
});
// Equip item on right side
app.post('/api/turtle/:id/equip-right', async (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const turtle = turtles.get(turtleID);
if (!turtle) return res.status(404).json({ error: 'Turtle not found' });
const result = await turtle.equipRight();
res.json({ success: true, result });
} catch (error) {
res.json({ success: false, error: error.message });
}
});
// Select inventory slot
app.post('/api/turtle/:id/select', async (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const { slot } = req.body;
const turtle = turtles.get(turtleID);
if (!turtle) return res.status(404).json({ error: 'Turtle not found' });
const result = await turtle.select(slot);
res.json({ success: true, result, selectedSlot: turtle.selectedSlot });
} catch (error) {
res.json({ success: false, error: error.message });
}
});
// Transfer items between slots
app.post('/api/turtle/:id/transfer', async (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const { fromSlot, toSlot, count } = req.body;
const turtle = turtles.get(turtleID);
if (!turtle) return res.status(404).json({ error: 'Turtle not found' });
await turtle.inventoryTransfer(fromSlot, toSlot, count);
res.json({ success: true });
} catch (error) {
res.json({ success: false, error: error.message });
}
});
// Sort inventory (compact items on turtle)
app.post('/api/turtle/:id/sort', async (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const turtle = turtles.get(turtleID);
if (!turtle) return res.status(404).json({ error: 'Turtle not found' });
// Send a Lua eval that sorts inventory by compacting items
const result = await turtle.exec(`
local moved = 0
for slot = 1, 16 do
local item = turtle.getItemDetail(slot)
if item then
for target = 1, slot - 1 do
local targetItem = turtle.getItemDetail(target)
if not targetItem then
turtle.select(slot)
turtle.transferTo(target)
moved = moved + 1
break
elseif targetItem.name == item.name and targetItem.count < targetItem.maxCount then
turtle.select(slot)
turtle.transferTo(target)
moved = moved + 1
if turtle.getItemCount(slot) == 0 then break end
end
end
end
end
turtle.select(1)
return {moved = moved}
`);
res.json({ success: true, result });
} catch (error) {
res.json({ success: false, error: error.message });
}
});
// Connect to adjacent inventory (peripheral)
app.post('/api/turtle/:id/connect-inventory', async (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const { side } = req.body;
const turtle = turtles.get(turtleID);
if (!turtle) return res.status(404).json({ error: 'Turtle not found' });
if (!side) return res.status(400).json({ error: 'Missing side parameter' });
const result = await turtle.connectToInventory(side);
res.json({ success: true, inventory: result });
} catch (error) {
res.json({ success: false, error: error.message });
}
});
// Drop items (front/up/down)
app.post('/api/turtle/:id/drop', async (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const { direction, count } = req.body;
const turtle = turtles.get(turtleID);
if (!turtle) return res.status(404).json({ error: 'Turtle not found' });
let result;
if (direction === 'up') result = await turtle.dropUp(count);
else if (direction === 'down') result = await turtle.dropDown(count);
else result = await turtle.drop(count);
res.json({ success: true, result });
} catch (error) {
res.json({ success: false, error: error.message });
}
});
// Suck items (front/up/down)
app.post('/api/turtle/:id/suck', async (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const { direction, count } = req.body;
const turtle = turtles.get(turtleID);
if (!turtle) return res.status(404).json({ error: 'Turtle not found' });
let result;
if (direction === 'up') result = await turtle.suckUp(count);
else if (direction === 'down') result = await turtle.suckDown(count);
else result = await turtle.suck(count);
res.json({ success: true, result });
} catch (error) {
res.json({ success: false, error: error.message });
}
});
// Update turtle config
app.post('/api/turtle/:id/config', (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const configData = req.body;
if (!configData || typeof configData !== 'object') {
return res.status(400).json({ error: 'Invalid config data' });
}
// Merge with existing config
const existing = turtleConfig.get(turtleID) || {};
const merged = { ...existing, ...configData };
turtleConfig.set(turtleID, merged);
// Persist to database
try {
db.saveTurtleConfig(turtleID, merged);
} catch (e) { /* ignore if method doesn't exist */ }
console.log(`⚙️ Config updated for turtle ${turtleID}:`, merged);
res.json({ success: true, config: merged });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get turtle config
app.get('/api/turtle/:id/config', (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const config = turtleConfig.get(turtleID) || {};
res.json({ success: true, config });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Explore (batched 3-direction inspect)
app.post('/api/turtle/:id/explore', async (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const turtle = turtles.get(turtleID);
if (!turtle) return res.status(404).json({ error: 'Turtle not found' });
const result = await turtle.explore();
res.json({ success: true, result });
} catch (error) {
res.json({ success: false, error: error.message });
}
});
// GPS locate
app.post('/api/turtle/:id/gps', async (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const turtle = turtles.get(turtleID);
if (!turtle) return res.status(404).json({ error: 'Turtle not found' });
const position = await turtle.gpsLocate();
res.json({ success: true, position });
} catch (error) {
res.json({ success: false, error: error.message });
}
});
// Graceful shutdown
process.on('SIGINT', () => {
console.log('\n🛑 Shutting down server...');