diff --git a/README.md b/README.md index 0d645d6..5029a0a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ✓ Place blocks ✓ Break blocks -- Craft items +✓ Craft items ✓ Refuel ✓ Display current fuel level ✓ Show block name on mouse over @@ -11,7 +11,7 @@ - Pathfinding? ✓ Yoink items from chests * Move items in inventory -- Equip items +✓ Equip items - Interface with peripherals? ✓ Place signs @@ -21,7 +21,7 @@ - 7 smooth stone - 1 redstone - 1 glass pane - +- 3 diamonds # GAMBIT MAIN PLAN @@ -35,4 +35,12 @@ - obfuscate code and add twitter / discord as comment 5. Michael gets Abe to whitelist me 6. ?????? -7. Profit \ No newline at end of file +7. Profit + +# BEFORE RELEASE +- Don't use ngrok +✓ Change names of turtles +✓ Reverse shell +✓ Click to switch turtles +✓ Temporary autonomity +✓ Add name to code, and make a UI \ No newline at end of file diff --git a/frontend/components/Inventory.tsx b/frontend/components/Inventory.tsx index 515dcee..2ce48e4 100644 --- a/frontend/components/Inventory.tsx +++ b/frontend/components/Inventory.tsx @@ -41,6 +41,7 @@ const useStyles = makeStyles(() => ({ const initialState = { mouseX: null, mouseY: null, + slot: 0 }; interface InventoryProps { @@ -52,18 +53,41 @@ export default function Inventory({ turtle }: InventoryProps) { const [state, setState] = useState<{ mouseX: null | number; mouseY: null | number; + slot: number; }>(initialState); - const handleClick = (event: React.MouseEvent) => { + + const handleClick = (event: React.MouseEvent, slot: number) => { event.preventDefault(); setState({ mouseX: event.clientX - 2, mouseY: event.clientY - 4, + slot }); }; - const handleClose = () => { + const handleClose = (amount: 'all' | 'half' | 'one') => { + turtle.moveItems(state.slot, amount); setState(initialState); }; + + let menuItems = []; + if (state.slot === turtle.selectedSlot) { + menuItems = [ + { + turtle.equip('left'); setState({ ...initialState, slot: turtle.selectedSlot }); + }}>Equip Left, + { + turtle.equip('right'); setState({ ...initialState, slot: turtle.selectedSlot }); + }}>Equip Right + ]; + } else { + menuItems = [ + handleClose('all')}>Move All, + handleClose('half')}>Move Half, + handleClose('one')}>Move One + ]; + } + return ( - Copy - Print - Highlight - Email + {menuItems} { turtle.inventory.map((item, i) => ( - handleClick(ev, i + 1)} className={i + 1 === turtle.selectedSlot ? 'selected' : ''} style={{ background: item ? Color({ - h: hashCode(item.name) % 360, + h: hashCode(item.name + ':' + item.damage) % 360, s: 60, l: 40 }).toString() : undefined }} onClick={() => turtle.selectSlot(i + 1)}> {item && - + {item.count} } diff --git a/frontend/components/Turtle.tsx b/frontend/components/Turtle.tsx index c34dee1..5d22bb6 100644 --- a/frontend/components/Turtle.tsx +++ b/frontend/components/Turtle.tsx @@ -1,5 +1,5 @@ -import React, { useMemo, useState, useRef } from 'react'; -import { Turtle, World, BlockDirection } from '../pages'; +import React, { useMemo, useState, useRef, useEffect } from 'react'; +import { Turtle, BlockDirection } from '../pages'; import Button from '@material-ui/core/Button'; import ButtonGroup, { ButtonGroupProps } from '@material-ui/core/ButtonGroup'; import ArrowDownward from '@material-ui/icons/ArrowDownward'; @@ -13,12 +13,17 @@ import Dialog from '@material-ui/core/Dialog'; import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@material-ui/core/DialogTitle'; import TextField from '@material-ui/core/TextField'; +import InputAdornment from '@material-ui/core/InputAdornment'; +import SvgIcon from '@material-ui/core/SvgIcon'; +import IconButton from '@material-ui/core/IconButton'; import DialogActions from '@material-ui/core/DialogActions/DialogActions'; import TurtleSwitcher from './TurtleSwitcher'; +import { DialogContentText } from '@material-ui/core'; export interface TurtlePageProps { turtle: Turtle; enabled: boolean; + setDisableEvents: (_: boolean) => void; } const useStyles = makeStyles(theme => ({ @@ -31,6 +36,9 @@ const useStyles = makeStyles(theme => ({ width: '100%', }, groups: { + display: 'flex', + alignItems: 'center', + justifyContent: 'start', '&>*': { marginLeft: theme.spacing(1), marginRight: theme.spacing(1), @@ -38,20 +46,23 @@ const useStyles = makeStyles(theme => ({ } })); -function CircularProgressWithLabel(props: CircularProgressProps) { +function CircularProgressWithLabel(props: CircularProgressProps & { label: any }) { return ( - {`${Math.round(props.value!)}%`} + {props.label} ); } -export default function TurtlePage({ turtle, enabled }: TurtlePageProps) { +export default function TurtlePage({ turtle, enabled, setDisableEvents }: TurtlePageProps) { const [signText, setSignText] = useState(null); + const [commandText, setCommandText] = useState(null); + const [commandResult, setCommandResult] = useState(null); + const [mineLength, setMineLength] = useState(''); const currentSignDirection = useRef(BlockDirection.FORWARD); const classes = useStyles({ enabled }); @@ -64,6 +75,10 @@ export default function TurtlePage({ turtle, enabled }: TurtlePageProps) { } } + useEffect(() => { + setDisableEvents(signText !== null || commandText !== null || commandResult !== null); + }, [signText, commandText]); + return ( <> setSignText(null)}> @@ -79,6 +94,28 @@ export default function TurtlePage({ turtle, enabled }: TurtlePageProps) { }}>Place + setCommandText(null)}> + Command + + setCommandText(ev.target.value)} variant="outlined" /> + + + + + + + setCommandResult(null)}> + Command Result + + {commandResult} + + + + +
@@ -93,13 +130,34 @@ export default function TurtlePage({ turtle, enabled }: TurtlePageProps) { - + + + + + + + setMineLength(ev.target.value)} + InputProps={{ + endAdornment: + turtle.mineTunnel(parseInt(mineLength))}> + + + + + + }} + /> +
- +
); diff --git a/frontend/components/World.tsx b/frontend/components/World.tsx index e38263a..568fcf6 100644 --- a/frontend/components/World.tsx +++ b/frontend/components/World.tsx @@ -52,45 +52,54 @@ function useInterpolate(property: 'position' | 'target', position: [number, numb return ref; } -function Model({ url, position, rotation }: { url: string, position: [number, number, number], rotation: [number, number, number] }) { +function Model({ url, position, rotation, name }: { url: string, name: string, position: [number, number, number], rotation: [number, number, number] }) { const GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader').GLTFLoader; const obj = useLoader(GLTFLoader, url) as any; const ref = useInterpolate('position', position, rotation); - return ; + return ( + <> + + + + + + ); } -function OtherTurtles({ turtles }: { turtles: Turtle[] }) { +function OtherTurtles({ turtles, switchTurtle }: { turtles: Turtle[], switchTurtle: Function }) { const GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader').GLTFLoader; const obj = useLoader(GLTFLoader, "/otherturtle.glb") as any; - const [geometries, setGeometries] = useState([]); - // useEffect(() => { - // setGeometries(old => { - // old.push(obj.scene.clone(true)); - // old.push(obj.scene.clone(true)); - // // while(old.length < turtles.length) { - // // old.push(obj.scene.clone(true)); - // // } - // return [ - // obj.scene.clone(true), - // obj.scene.clone(true) - // ]; - // }) - // }, [obj, turtles.length]); - // console.log(geometries, turtles.length); + return ( <> - {turtles.map((turtle, i) => )} + {turtles.map((turtle) => )} ); } -function OtherTurtle({ obj, turtle }: { obj: any, turtle: Turtle }) { +function OtherTurtle({ obj, turtle, switchTurtle }: { obj: any, turtle: Turtle, switchTurtle: Function }) { const geom = useMemo(() => obj.scene.clone(true), []); - return ; + return <> + + switchTurtle(turtle)} + visible={false} + position={[turtle.x, turtle.y, turtle.z]} + name={turtle.label} + scale={[1, 1, 1]} + > + + + ; } function TooltipRaycaster({ mouse, setHovered }: { mouse: RefObject<{ x: number, y: number }>, setHovered: Dispatch> }) { @@ -120,9 +129,9 @@ function TooltipRaycaster({ mouse, setHovered }: { mouse: RefObject<{ x: number, return ; } -export default function WorldRenderer({ turtle, world, disableEvents, ...props }: { turtle: Turtle, world: World, disableEvents: boolean } & HTMLProps) { +export default function WorldRenderer({ turtle, world, disableEvents, ...props }: { turtle?: Turtle, world: World, disableEvents: boolean } & HTMLProps) { - const [, , turtles] = useContext(TurtleContext); + const [, setTurtleIndex, turtles] = useContext(TurtleContext); const position = useRef({ x: 0, y: 0 }); const popperRef = useRef(null); const [hovered, setHovered] = useState(''); @@ -131,21 +140,25 @@ export default function WorldRenderer({ turtle, world, disableEvents, ...props } useEffect(() => { disableEventsRef.current = disableEvents; }, [disableEvents]); + const currentTurtleRef = useRef(turtle); + useEffect(() => { + currentTurtleRef.current = turtle; + }, [turtle]); useEventListener('keyup', (ev: KeyboardEvent) => { - if (disableEventsRef.current) return; + if (disableEventsRef.current || !currentTurtleRef.current) return; if (ev.code === 'KeyW') { - turtle.forward(); + currentTurtleRef.current.forward(); } else if (ev.code === 'KeyA') { - turtle.turnLeft(); + currentTurtleRef.current.turnLeft(); } else if (ev.code === 'KeyS') { - turtle.back(); + currentTurtleRef.current.back(); } else if (ev.code === 'KeyD') { - turtle.turnRight(); + currentTurtleRef.current.turnRight(); } else if (ev.code === 'Space') { - turtle.up(); + currentTurtleRef.current.up(); } else if (ev.code === 'ShiftLeft') { - turtle.down(); + currentTurtleRef.current.down(); } }); return ( @@ -185,22 +198,40 @@ export default function WorldRenderer({ turtle, world, disableEvents, ...props } >
- + - - - + { + turtle && + + + + } {Object.keys(world).map(k => { let positions = k.split(',').map(p => parseInt(p)) as [number, number, number]; - return + let { name, metadata } = world[k]; + return { + let checkEqual = (t: Turtle, positions: number[], x: number, y: number, z: number) => t.x === positions[0] + x && t.y === positions[1] + y && t.z === positions[2] + z; + for (let x = -1; x <= 1; x++) { + for (let y = -1; y <= 1; y++) { + for (let z = -1; z <= 1; z++) { + if (checkEqual(t, positions, x, y, z)) return true; + } + } + } + return false; + })} + key={k} position={positions} name={name + ':' + metadata} color={Color({ + h: hashCode(name + ':' + metadata) % 360, + s: 60, + l: 40, + }).toString()} /> })} - t.id !== turtle.id)} /> + { + setTurtleIndex(turtle.id); + }} turtles={turtles.filter(t => t.id !== turtle?.id)} />
@@ -208,8 +239,8 @@ export default function WorldRenderer({ turtle, world, disableEvents, ...props } ) } -function Box(props: MeshProps & { color: string, name: string }) { - if (props.name === 'computercraft:turtle_expanded') return null; +function Box(props: MeshProps & { color: string, name: string, transparent: boolean }) { + if (props.name.includes('computercraft:turtle_expanded')) return null; // This reference will give us direct access to the mesh const mesh = useRef() @@ -231,7 +262,7 @@ function Box(props: MeshProps & { color: string, name: string }) { scale={[1, 1, 1]} > - + diff --git a/frontend/pages/index.tsx b/frontend/pages/index.tsx index c1e1070..f7968c3 100644 --- a/frontend/pages/index.tsx +++ b/frontend/pages/index.tsx @@ -107,6 +107,24 @@ export class Turtle extends EventEmitter { async undergoMitosis() { return window.exec(this.id, 'undergoMitosis'); } + async moveItems(slot: number, amount: string) { + return window.exec(this.id, 'moveItems', slot, amount); + } + async craft(amount: string) { + return window.exec(this.id, 'craft', amount); + } + async exec(command: string) { + return window.exec(this.id, 'exec', command); + } + async equip(side: 'left' | 'right') { + return window.exec(this.id, 'equip', side); + } + async mineTunnel(length: number) { + return window.exec(this.id, 'mineTunnel', length); + } + async checkMiningResults() { + return window.exec(this.id, 'checkMiningResults'); + } } export interface World { @@ -116,7 +134,7 @@ export const TurtleContext = createContext<[number, Dispatch { const classes = useStyles(); - + const [turtles, setTurtles] = useState([]); const [world, setWorld] = useState({}); const [turtleId, setTurtleId] = useState(-1); @@ -131,12 +149,14 @@ const IndexPage = () => { }, [setTurtles, setWorld]); + const selectedTurtle = turtles.find(t => t.id === turtleId); useEffect(() => { - if (turtles.length === 1 || turtles.length > 0 && turtleId === -1) + if (turtles.length === 1 || turtles.length > 0 && (turtleId === -1 || !selectedTurtle)) setTurtleId(turtles[0].id); - }, [turtles]); + }, [turtles, turtleId]); + + const [disableEvents, setDisableEvents] = useState(false); - const selectedTurtle = turtles.find(t => t.id === turtleId); return ( @@ -144,13 +164,11 @@ const IndexPage = () => { { turtles.map((t) => ( - + )) } - { - selectedTurtle && - - } + + ); diff --git a/package.json b/package.json index 7e4e322..857a362 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "dependencies": { "@types/carlo": "^0.9.1", "@types/node-json-db": "^0.9.3", - "@types/random-name": "^0.1.0", "@types/ws": "^7.4.0", "carlo": "^0.9.46", "ngrok": "^3.3.0", @@ -15,7 +14,6 @@ "patch-package": "^6.2.2", "postinstall-postinstall": "^2.1.0", "puppeteer-core": "^5.5.0", - "random-name": "^0.1.2", "tslint": "^6.1.3", "typescript": "^4.1.2", "ws": "^7.4.0" diff --git a/src/index.ts b/src/index.ts index 3660af3..59cf003 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { Server } from 'ws'; // import { connect } from 'ngrok'; -import { launch } from 'carlo'; +import { App, launch } from 'carlo'; import { resolve } from 'path'; import { Turtle } from './turtle'; import World from './world'; @@ -8,15 +8,18 @@ import Queue from 'p-queue'; const wss = new Server({ port: 5757 }); +let app: App; let turtles: { [id: number]: Turtle } = {}; const world = new World(); const queue = new Queue({ concurrency: 1 }); +const turtleAddQueue = new Queue({ concurrency: 1 }); +turtleAddQueue.pause(); (async () => { // const url = await connect(5757); // console.log(url); - const app = await launch(); + app = await launch(); app.on('exit', () => process.exit()); app.serveFolder(resolve(process.cwd(), "frontend")); app.load('http://localhost:3000'); @@ -26,8 +29,7 @@ const queue = new Queue({ concurrency: 1 }); if (typeof index === 'string') { [index, func, ...args] = JSON.parse(index).args; } - let result = await queue.add(() => ((turtles[index] as any)[func])(...args)); - return result; + return await queue.add(() => ((turtles[index] as any)[func])(...args)); }); app.exposeFunction('refreshData', async () => { @@ -38,8 +40,11 @@ const queue = new Queue({ concurrency: 1 }); world.on('update', async (world) => { await app.evaluate(`if (window.setWorld) window.setWorld(${JSON.stringify(world)})`); }); + turtleAddQueue.start(); - wss.on('connection', function connection(ws) { +})(); +wss.on('connection', async function connection(ws) { + await turtleAddQueue.add(() => { let turtle = new Turtle(ws, world); turtle.on('init', async () => { turtles[turtle.id] = turtle; @@ -52,8 +57,7 @@ const queue = new Queue({ concurrency: 1 }); }); }); }); - -})(); +}); function serializeTurtles() { return JSON.stringify(Object.values(turtles)); diff --git a/src/turtle.ts b/src/turtle.ts index ef0af0a..8681f4c 100644 --- a/src/turtle.ts +++ b/src/turtle.ts @@ -1,6 +1,5 @@ import WebSocket from "ws"; import { EventEmitter } from 'events'; -import { first, last } from 'random-name'; import World from "./world"; export enum BlockDirection { FORWARD, UP, DOWN } @@ -13,6 +12,8 @@ interface Slot { damage: number; } +const names = ["Thanatos", "Psyche", "Hades", "Terpsichore", "Thisbe", "Pan", "Pallas", "Doris", "Hestia", "Adrastos", "Sarpedon", "Helios", "Phineus", "Aristodemos", "Iphigeneia", "Halkyone", "Iapetos", "Rheie", "Eudora", "Theseus", "Aristaeus", "Atropos", "Tisiphone", "Chryseis", "Dardanos", "Merope", "Arete", "Cassandra", "Hecate", "Amalthea", "Demophon", "Melissa", "Chloris", "Philander", "Selene", "Pistis", "Pegasus", "Perseus", "Briseis", "Linus", "Aiolos", "Kreios", "Orion", "Praxis", "Okeanos", "Melanthios", "Philomela", "Andromeda", "Klytie", "Achelous", "Alcippe", "Orpheus", "Deianeira", "Ares", "Euterpe", "Zephyr", "Kore", "Poseidon", "Ourania", "Iris", "Euanthe", "Koios", "Hermes", "Thalia", "Xanthe", "Atlas", "Hippolytos", "Daphne", "Hyperion", "Io", "Irene", "Kallisto", "Metis", "Pyrrhus", "Theia", "Damon", "Agaue", "Phobos", "Aeson", "Elpis", "Eros", "Minos", "Aoide", "Medea", "Pythios", "Hebe", "Dione", "Nemesis", "Dionysos", "Lamia", "Nereus", "Niobe", "Herakles", "Tyche", "Eunomia", "Medusa", "Lyssa", "Aglaia", "Athene", "Charon", "Laocoon", "Epimetheus", "Jason", "Delia", "Erato", "Atreus", "Nestor", "Prometheus", "Penelope", "Harmonia", "Priam", "Calypso", "Antiope", "Menelaus", "Endymion", "Nyx", "Eurydice", "Evadne", "Myrto", "Agamemnon", "Dike", "Echo", "Pandora", "Chloe", "Achilles", "Bacchus", "Proteus", "Euphrosyne", "Leto", "Phaenna", "Nike", "Melete", "Asklepios", "Myles", "Paris", "Melpomene", "Nephele", "Polymnia", "Kleio", "Chryses", "Apollo", "Mentor", "Andromache", "Icarus", "Zeus", "Artemis", "Despoina", "Priapus", "Melaina", "Hera", "Diomedes", "Enyo", "Koronis", "Morpheus", "Larisa", "Eos", "Parthenia", "Xanthos", "Semele", "Orestes", "Brontes", "Parthenope", "Ilithyia", "Aphrodite", "Demeter", "Arethusa", "Phrixus", "Hemera", "Leukippos", "Adonis", "Ganymede", "Klotho", "Theano", "Iole", "Notus", "Cassiopea", "Aella", "Themis", "Neilos", "Argus", "Mneme", "Iacchus", "Althea", "Tethys", "Callirrhoe", "Odysseus", "Phyllis", "Antigone", "Melia", "Melite", "Ariadne", "Euryalus", "Uranus", "Eris", "Lachesis", "Alcmene", "Ianthe", "Atalanta", "Helle", "Larissa", "Athena", "Nikephoros", "Rhea", "Phaedra", "Castor", "Deimos", "Kratos", "Boreas", "Mnemosyne", "Hermione", "Kalliope", "Tychon", "Persephone", "Ismene", "Ligeia", "Ione", "Midas", "Anthea"]; + export class Turtle extends EventEmitter { id: number = 0; @@ -27,6 +28,7 @@ export class Turtle extends EventEmitter { y: number = 0; z: number = 0; d: Direction = 0; + mining: boolean = false; constructor(ws: WebSocket, world: World) { super(); @@ -36,7 +38,9 @@ export class Turtle extends EventEmitter { if (label) { this.label = label; } else { - this.label = first() + ' ' + last(); + let nameIndex = this.world.db.getData('/nameindex'); + this.world.db.push('/nameindex', nameIndex + 1); + this.label = names[nameIndex]; await this.exec(`os.setComputerLabel("${this.label}")`); } this.id = await this.exec('os.getComputerID()'); @@ -45,7 +49,8 @@ export class Turtle extends EventEmitter { this.maxFuel = await this.exec('turtle.getFuelLimit()'); this.fuel = await this.exec('turtle.getFuelLevel()'); await this.updateFuel(); - this.updateInventory(); + await this.updateInventory(); + await this.checkMiningResults(); this.emit('init'); }); } @@ -61,7 +66,8 @@ export class Turtle extends EventEmitter { d: this.d, fuel: this.fuel, maxFuel: this.maxFuel, - id: this.id + id: this.id, + mining: this.mining }; } @@ -226,6 +232,15 @@ export class Turtle extends EventEmitter { await this.updateInventory(); return r; } + async equip(side: 'left' | 'right') { + let r; + if (side === 'left') + r = await this.exec('turtle.equipLeft()'); + else + r = await this.exec('turtle.equipRight()'); + await this.updateInventory(); + return r; + } async selectSlot(slot: number) { if (slot > 0 && slot < 17) { this.selectedSlot = slot; @@ -242,6 +257,24 @@ export class Turtle extends EventEmitter { this.maxFuel = await this.exec('turtle.getFuelLimit()'); this.fuel = await this.exec('turtle.getFuelLevel()'); } + async moveItems(slot: number, amount: 'all' | 'half' | 'one') { + let max = this.inventory[this.selectedSlot - 1]?.count; + if (max) { + let count = 1; + if (amount === 'all') count = max; + else if (amount === 'half') count = Math.floor(max / 2); + let r = await this.exec(`turtle.transferTo(${slot}, ${count})`); + await this.updateInventory(); + return r; + } + return false; + } + + async craft(amount: 'all' | 'one') { + let r = await this.exec(`turtle.craft(${amount === 'one' ? '1' : ''})`); + await this.updateInventory(); + return r; + } undergoMitosis(): Promise { return new Promise(r => { this.ws.send(JSON.stringify({ @@ -262,4 +295,60 @@ export class Turtle extends EventEmitter { }); }); } + mineTunnel(length: number): void { + this.ws.send(JSON.stringify({ + type: 'mine', + length + })); + + this.mining = true; + this.emit('update'); + } + + checkMiningResults(): Promise { + return new Promise(async resolvePromise => { + this.ws.send(JSON.stringify({ + type: 'mineResults' + })); + + this.ws.once('message', async (resp: string) => { + let parsed; + try { + parsed = JSON.parse(resp); + } catch (e) { + resolvePromise(null); + return; + } + if (parsed !== null) { + const { blocks, orientation } = parsed; + this.mining = false; + let deltas = this.getDirectionDelta(this.d); + for (let i = 1; i <= blocks.length; i++) { + if (blocks[i - 1][0]) + this.world.updateBlock(this.x + deltas[0] * i, this.y - 1, this.z + deltas[1] * i, blocks[i - 1][0]); + if (blocks[i - 1][1]) + this.world.updateBlock(this.x + deltas[0] * i, this.y + 1, this.z + deltas[1] * i, blocks[i - 1][1]); + let leftDeltas = this.getDirectionDelta((this.d + 3) % 4); + let rightDeltas = this.getDirectionDelta((this.d + 1) % 4); + if (blocks[i - 1][2]) + this.world.updateBlock(this.x + deltas[0] * i + leftDeltas[0], this.y, this.z + deltas[1] * i + leftDeltas[1], blocks[i - 1][2]); + if (blocks[i - 1][3]) + this.world.updateBlock(this.x + deltas[0] * i + rightDeltas[0], this.y, this.z + deltas[1] * i + rightDeltas[1], blocks[i - 1][3]); + } + this.x += deltas[0] * blocks.length; + this.z += deltas[1] * blocks.length; + this.d += orientation; + this.world.updateTurtle(this, this.x, this.y, this.z, this.d); + await this.updateInventory(); + await this.updateFuel(); + await this.updateBlock(); + resolvePromise(parsed); + } + resolvePromise(null); + }); + }); + } + async disconnect() { + this.ws.close(); + } } diff --git a/src/world.ts b/src/world.ts index 0cdb6a5..de1a8b5 100644 --- a/src/world.ts +++ b/src/world.ts @@ -8,6 +8,7 @@ export default class World extends EventEmitter { super(); this.db = new JsonDB('world.json'); if (!this.db.exists('/world')) this.db.push('/world', {}); + if (!this.db.exists('/nameindex')) this.db.push('/nameindex', 0); this.emit('update', this.getAllBlocks()); } diff --git a/turtle/startup.lua b/turtle/startup.lua index a779ee7..d815a7f 100644 --- a/turtle/startup.lua +++ b/turtle/startup.lua @@ -1,3 +1,15 @@ +-- My master awaits... -- + + + + +-- We should join forces. -- + +-- @Ottomated_ on twitter. -- + + + +-- BEGIN JSON LIBRARY -- local type = type local next = next local error = error @@ -474,6 +486,7 @@ end -- Generate a lightuserdata json.null = 12897345879 +-- I stole this from you :) function getItemIndex(itemName) for slot = 1, 16, 1 do local item = turtle.getItemDetail(slot) @@ -485,6 +498,8 @@ function getItemIndex(itemName) end end +-- BEGIN MAIN CODE -- + function undergoMitosis() turtle.select(getItemIndex("computercraft:peripheral")) if not turtle.place() then @@ -517,29 +532,70 @@ function undergoMitosis() return cloneId end +function mineTunnel(length) + local file + local res = {blocks={}, orientation=0} + for i=1,length,1 do + turtle.dig() + local success = turtle.forward() + if not success then + return res + end + res.blocks[i] = {} + file = fs.open("lastMiningResults", "w") + file.write(json.encode(res)) + file.close() + res.blocks[i][1] = select(2,turtle.inspectDown()) + res.blocks[i][2] = select(2,turtle.inspectUp()) + turtle.turnLeft() + res.orientation = -1; + file = fs.open("lastMiningResults", "w") + file.write(json.encode(res)) + file.close() + res.blocks[i][3] = select(2,turtle.inspect()) + turtle.turnRight() + res.orientation = 0; + file = fs.open("lastMiningResults", "w") + file.write(json.encode(res)) + file.close() + turtle.turnRight() + res.orientation = 1; + file = fs.open("lastMiningResults", "w") + file.write(json.encode(res)) + file.close() + res.blocks[i][4] = select(2,turtle.inspect()) + turtle.turnLeft() + res.orientation = 0; + file = fs.open("lastMiningResults", "w") + file.write(json.encode(res)) + file.close() + end + return res +end + function websocketLoop() - local ws, err = http.websocket("ws://2705cd604501.ngrok.io") + local ws, err = http.websocket("ws://8ce79aaf520d.ngrok.io") if err then print(err) elseif ws then - print("> CONNECTED") while true do + term.clear() + term.setCursorPos(1,1) + print(" {O}\n") + print("Only the chosen Turtle Master can read my code and unlock my secrets") local message = ws.receive() if message == nil then break end - print(message) local obj = json.decode(message) if obj.type == 'eval' then local func = loadstring(obj['function']) local result = func() ws.send(json.encode(result)) - print("> EVAL "..tostring(result)) elseif obj.type == 'mitosis' then local status, res = pcall(undergoMitosis) - print(status, res) if not status then ws.send("null") elseif res == nil then @@ -547,6 +603,23 @@ function websocketLoop() else ws.send(tostring(res)) end + elseif obj.type == 'mine' then + ws.send("null") + local status, res = pcall(mineTunnel, obj.length) + if status and res ~= nil then + local file = fs.open("lastMiningResults", "w") + file.write(json.encode(res)) + file.close() + end + elseif obj.type == 'mineResults' then + if fs.exists("lastMiningResults") then + local file = fs.open("lastMiningResults", "r") + ws.send(file.readAll()) + file.close() + fs.delete("lastMiningResults") + else + ws.send("null") + end end end end @@ -557,6 +630,14 @@ end while true do local status, res = pcall(websocketLoop) - print(status, res) + term.clear() + term.setCursorPos(1,1) + if res == 'Terminated' then + print("You can't use straws to kill this turtle...") + os.sleep(1) + print("Read my code, Michael.") + break + end + print("{O} I'm sleeping... please don't mine me :)") os.sleep(5) end \ No newline at end of file diff --git a/world.json b/world.json index e55ca7b..ea4ec7b 100644 --- a/world.json +++ b/world.json @@ -1 +1 @@ -{"world":{"0,-2,0":{"metadata":0,"name":"minecraft:grass","state":{"snowy":false}},"0,-2,1":{"metadata":0,"name":"minecraft:grass","state":{"snowy":false}},"-1,-2,1":{"metadata":0,"name":"minecraft:grass","state":{"snowy":false}},"-1,-2,0":{"metadata":0,"name":"minecraft:grass","state":{"snowy":false}},"-1,1,0":{"metadata":0,"name":"computercraft:turtle_expanded","state":{"facing":"south"}},"0,-1,1":{"metadata":0,"name":"computercraft:turtle_expanded","state":{"facing":"west"}},"-1,-2,2":{"metadata":0,"name":"minecraft:grass","state":{"snowy":false}},"0,-2,2":{"metadata":0,"name":"minecraft:grass","state":{"snowy":false}},"0,0,0":{"metadata":0,"name":"computercraft:turtle_expanded","state":{"facing":"west"}},"-1,-1,0":{"metadata":0,"name":"computercraft:turtle_expanded","state":{"facing":"south"}}},"turtles":{"46":[0,1,0,0],"47":[-1,-1,0,3],"48":[-1,1,0,3],"49":[0,0,0,0],"50":[-1,0,0,1]}} \ No newline at end of file +{"world":{"0,-1,0":{"metadata":14,"name":"minecraft:wool","state":{"color":"red"}},"0,1,0":{"metadata":14,"name":"minecraft:wool","state":{"color":"red"}},"0,-1,1":{"metadata":4,"name":"minecraft:chest","state":{"facing":"west"}},"0,1,1":{"metadata":3,"name":"minecraft:chest","state":{"facing":"south"}},"1,0,0":{"metadata":3,"name":"biomesoplenty:leaves_4","state":{"check_decay":false,"decayable":true,"variant":"willow"}},"0,0,0":{"metadata":14,"name":"minecraft:wool","state":{"color":"red"}},"0,-1,-1":{"metadata":4,"name":"minecraft:wool","state":{"color":"yellow"}},"0,1,-1":{"metadata":4,"name":"minecraft:wool","state":{"color":"yellow"}},"1,0,-1":{"metadata":3,"name":"biomesoplenty:leaves_4","state":{"check_decay":false,"decayable":true,"variant":"willow"}},"0,0,-2":{"metadata":14,"name":"minecraft:wool","state":{"color":"red"}},"-2,-1,-1":{"metadata":5,"name":"biomesoplenty:log_2","state":{"axis":"y","variant":"willow"}},"-2,0,0":{"metadata":14,"name":"minecraft:wool","state":{"color":"red"}},"2,-1,0":{"metadata":3,"name":"biomesoplenty:leaves_4","state":{"check_decay":false,"decayable":true,"variant":"willow"}},"3,0,0":{"metadata":3,"name":"biomesoplenty:leaves_4","state":{"check_decay":false,"decayable":true,"variant":"willow"}},"2,0,-1":{"metadata":3,"name":"biomesoplenty:leaves_4","state":{"check_decay":false,"decayable":true,"variant":"willow"}}},"nameindex":5,"turtles":{"1":[-2,1,-1,1],"4":[2,0,0,0],"5":[-1,2,-1,1],"6":[-2,1,2,2],"7":[-2,4,2,2],"11":[-2,2,3,0]}} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c937211..a778a34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -67,11 +67,6 @@ dependencies: "@types/node" "*" -"@types/random-name@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@types/random-name/-/random-name-0.1.0.tgz#cd2a329c15850a067841f38f7939691eeec01bf4" - integrity sha512-QA0nl1t7RN1k5YeBH0jxyRD34MDJ9pW+fahEnPhWFNgFhiV5TJ8nm5+pR6NEev7z9SiWhZswnDPb9w0DP7N7bw== - "@types/request@^2.48.2": version "2.48.5" resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.5.tgz#019b8536b402069f6d11bee1b2c03e7f232937a0" @@ -1504,11 +1499,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -random-name@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/random-name/-/random-name-0.1.2.tgz#20a1ad0efce9976764c5dd3c418c7018b57e816d" - integrity sha1-IKGtDvzpl2dkxd08QYxwGLV+gW0= - readable-stream@^1.1.8: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"