Compare commits

...

6 Commits

5 changed files with 244 additions and 0 deletions

View File

@@ -20,6 +20,10 @@ import {
RefuelingState,
DumpInventoryState,
FarmingState,
ScanState,
ExtractionState,
BuildingState,
AutocraftState,
} from './states/index.js';
const STATE_MAP = {
@@ -33,6 +37,14 @@ const STATE_MAP = {
dumpInventory: DumpInventoryState,
dumping: DumpInventoryState,
farming: FarmingState,
scanning: ScanState,
scan: ScanState,
extracting: ExtractionState,
extraction: ExtractionState,
building: BuildingState,
build: BuildingState,
autocrafting: AutocraftState,
autocraft: AutocraftState,
};
// Timeout for exec() commands (ms)
@@ -486,6 +498,8 @@ export class Turtle extends EventEmitter {
homePosition: this._homePosition,
facing: this._facing,
fuel: this._fuel,
fuelLimit: this._fuelLimit,
selectedSlot: this._selectedSlot,
inventory: this._inventory,
inventoryCount: Array.isArray(this._inventory)
? this._inventory.length
@@ -496,6 +510,9 @@ export class Turtle extends EventEmitter {
connected: this.connected,
lastUpdate: this.lastUpdate,
label: this._label,
peripherals: this._peripherals,
error: this._error,
warning: this._warning,
};
}

View File

@@ -384,6 +384,28 @@ app.post('/api/turtle/:id/commands/ack', (req, res) => {
}
});
// Receive real-time events from turtles (inventory, peripherals)
app.post('/api/turtle/event', (req, res) => {
try {
const { turtleID, type, message } = req.body;
if (!turtleID || !type) {
return res.status(400).json({ error: 'Missing turtleID or type' });
}
const turtle = turtles.get(turtleID);
if (turtle) {
turtle.handleEvent(type, message);
console.log(`📡 Event from T#${turtleID}: ${type}`);
}
res.json({ success: true });
} catch (error) {
console.error('❌ Error processing turtle event:', error);
res.status(500).json({ error: error.message });
}
});
// Get all turtles
app.get('/api/turtles', (req, res) => {
res.json({
@@ -970,6 +992,72 @@ app.post('/api/mining-areas/:areaId/close', (req, res) => {
}
});
// ========== CHUNK ANALYSIS ENDPOINTS ==========
// Get all chunk analyses
app.get('/api/chunks', (req, res) => {
try {
const chunks = db.getAllChunkAnalyses();
res.json(chunks);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get specific chunk analysis
app.get('/api/chunks/:x/:z', (req, res) => {
try {
const x = parseInt(req.params.x);
const z = parseInt(req.params.z);
const chunk = db.getChunkAnalysis(x, z);
res.json(chunk || { x, z, analysis: {} });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Save chunk analysis
app.post('/api/chunks', (req, res) => {
try {
const { x, z, analysis } = req.body;
db.saveChunkAnalysis(x, z, analysis || {});
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Search blocks by name pattern in area
app.get('/api/world/blocks/search', (req, res) => {
try {
const { fromX, fromY, fromZ, toX, toY, toZ, pattern } = req.query;
if (!pattern) {
return res.status(400).json({ error: 'Missing pattern parameter' });
}
const blocks = db.getBlocksWithNameLike(
parseInt(fromX) || -1000, parseInt(fromY) || -64, parseInt(fromZ) || -1000,
parseInt(toX) || 1000, parseInt(toY) || 320, parseInt(toZ) || 1000,
pattern
);
res.json({ blocks });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get single block info
app.get('/api/world/block/:x/:y/:z', (req, res) => {
try {
const x = parseInt(req.params.x);
const y = parseInt(req.params.y);
const z = parseInt(req.params.z);
const block = db.getBlock(x, y, z);
res.json(block || null);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ========== STATISTICS ENDPOINTS ==========
// Get server statistics

View File

@@ -7,3 +7,7 @@ export { GoHomeState } from './GoHomeState.js';
export { RefuelingState } from './RefuelingState.js';
export { DumpInventoryState } from './DumpInventoryState.js';
export { FarmingState } from './FarmingState.js';
export { ScanState } from './ScanState.js';
export { ExtractionState } from './ExtractionState.js';
export { BuildingState } from './BuildingState.js';
export { AutocraftState } from './AutocraftState.js';

View File

@@ -639,6 +639,66 @@ parallel.waitForAny(
end
end,
function()
-- Inventory change events (real-time)
while true do
os.pullEvent("turtle_inventory")
local inventory = {}
local fns = {}
for i = 1, 16 do
fns[i] = function()
local item = turtle.getItemDetail(i, true)
if item then
inventory[tostring(i)] = item
end
end
end
parallel.waitForAll(table.unpack(fns))
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
type = "inventory_update",
turtleID = os.getComputerID(),
inventory = inventory,
timestamp = os.epoch("utc")
})
end
end,
function()
-- Peripheral attached events (real-time)
while true do
os.pullEvent("peripheral")
sleep(0.1) -- Small delay to let CC register
local peripherals = {}
local names = peripheral.getNames()
for _, name in ipairs(names) do
peripherals[name] = {types = {peripheral.getType(name)}}
end
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
type = "peripheral_attached",
turtleID = os.getComputerID(),
peripherals = peripherals,
timestamp = os.epoch("utc")
})
end
end,
function()
-- Peripheral detached events (real-time)
while true do
local _, side = os.pullEvent("peripheral_detach")
if not peripheral.isPresent(side) then
modem.transmit(CHANNEL_SEND, CHANNEL_RECEIVE, {
type = "peripheral_detached",
turtleID = os.getComputerID(),
side = side,
timestamp = os.epoch("utc")
})
end
end
end,
function()
-- Local autonomous fallback
while true do

View File

@@ -573,6 +573,81 @@ while true do
addLog(" -> Sent " .. #blocks .. " blocks to server", colors.lime)
end
elseif message.type == "inventory_update" then
-- Turtle inventory changed in real-time
local turtleID = message.turtleID
addLog("Turtle #" .. turtleID .. " inventory update", colors.cyan)
local success = pcall(function()
local response = http.post(
SERVER_URL .. "/api/turtle/event",
textutils.serializeJSON({
turtleID = turtleID,
type = "INVENTORY_UPDATE",
message = message.inventory
}),
{["Content-Type"] = "application/json"}
)
if response then
response.close()
end
end)
if not success then
stats.errors = stats.errors + 1
addLog(" -> Failed to forward inventory update", colors.red)
end
elseif message.type == "peripheral_attached" then
-- Turtle peripheral attached in real-time
local turtleID = message.turtleID
addLog("Turtle #" .. turtleID .. " peripheral attached", colors.cyan)
local success = pcall(function()
local response = http.post(
SERVER_URL .. "/api/turtle/event",
textutils.serializeJSON({
turtleID = turtleID,
type = "PERIPHERAL_ATTACHED",
message = message.peripherals
}),
{["Content-Type"] = "application/json"}
)
if response then
response.close()
end
end)
if not success then
stats.errors = stats.errors + 1
addLog(" -> Failed to forward peripheral attach", colors.red)
end
elseif message.type == "peripheral_detached" then
-- Turtle peripheral detached in real-time
local turtleID = message.turtleID
addLog("Turtle #" .. turtleID .. " peripheral detached: " .. (message.side or "?"), colors.cyan)
local success = pcall(function()
local response = http.post(
SERVER_URL .. "/api/turtle/event",
textutils.serializeJSON({
turtleID = turtleID,
type = "PERIPHERAL_DETACHED",
message = message.side
}),
{["Content-Type"] = "application/json"}
)
if response then
response.close()
end
end)
if not success then
stats.errors = stats.errors + 1
addLog(" -> Failed to forward peripheral detach", colors.red)
end
end
end
elseif channel == POCKET_CHANNEL then