├── public ├── js │ ├── server │ │ ├── webClientTest.js │ │ ├── connect.js │ │ ├── integrationTests.js │ │ ├── runTests.js │ │ ├── inventoryTest.js │ │ ├── webClientTestData.js │ │ ├── SocketToAccountMap.js │ │ ├── robotTest.js │ │ ├── robotTestData.js │ │ └── customizeServer.js │ ├── config │ │ └── config.example.js │ ├── client │ │ ├── Game.mjs │ │ ├── cmd.js │ │ ├── WorldAndScenePoint.mjs │ │ ├── VoxelMap.mjs │ │ ├── CoordForm.mjs │ │ ├── CutawayForm.mjs │ │ ├── Robot.mjs │ │ ├── bannerMessages.mjs │ │ ├── WebClient.mjs │ │ └── InventoryRender.mjs │ ├── shared │ │ ├── recipeSearch.js │ │ ├── MapData.js │ │ ├── fromClientSchemas.js │ │ ├── InventoryData.js │ │ └── fromRobotSchemas.js │ └── lib │ │ └── PointerLockControls.js ├── favicon.ico ├── assets │ ├── tree.gif │ ├── placeholder_icon.ico │ ├── placeholder_icon.png │ ├── placeholder_icon.icns │ ├── cube.svg │ └── squaredcube.svg ├── lua │ └── oc │ │ ├── config.txt │ │ ├── setup.lua │ │ ├── login │ │ └── login.lua │ │ ├── downloadCode.lua │ │ ├── tcp.lua │ │ ├── adjacent.lua │ │ ├── sendScan.lua │ │ ├── scanDirection.lua │ │ ├── commandLoop.lua │ │ ├── trackOrientation.lua │ │ ├── trackPosition.lua │ │ ├── config.lua │ │ ├── moveAndScan.lua │ │ ├── commandMap.lua │ │ ├── doToArea.lua │ │ ├── interact.lua │ │ └── craft.lua └── css │ ├── accounts.css │ ├── style.css │ └── bootstrap-select.min.css ├── documentation ├── test-setup.md ├── web_install.txt ├── local_install.txt ├── standalone-install.md ├── creative-robot-install.md ├── server-install.md ├── faq.md ├── survival-robot-install.md ├── protocol.md └── tips.md ├── views ├── error.ejs ├── login.ejs └── index.ejs ├── .gitignore ├── jsconfig.json ├── release_checklist.md ├── LICENSE ├── package.json ├── bin └── www ├── readme.md ├── electronApp.js ├── routes └── routes.js ├── app.js ├── todo.md └── testplan.md /public/js/server/webClientTest.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunstad/roboserver/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/assets/tree.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunstad/roboserver/HEAD/public/assets/tree.gif -------------------------------------------------------------------------------- /public/lua/oc/config.txt: -------------------------------------------------------------------------------- 1 | {posX=0,serverIP="127.0.0.1",serverPort="8080",posY=0,tcpPort=3001,posZ=0} -------------------------------------------------------------------------------- /public/assets/placeholder_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunstad/roboserver/HEAD/public/assets/placeholder_icon.ico -------------------------------------------------------------------------------- /public/assets/placeholder_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunstad/roboserver/HEAD/public/assets/placeholder_icon.png -------------------------------------------------------------------------------- /public/assets/placeholder_icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dunstad/roboserver/HEAD/public/assets/placeholder_icon.icns -------------------------------------------------------------------------------- /public/js/server/connect.js: -------------------------------------------------------------------------------- 1 | testData = require('./robotTestData'); 2 | testClient = new (require('./TestClient'))(testData); 3 | testClient.connect(); -------------------------------------------------------------------------------- /public/css/accounts.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5px; 3 | } 4 | 5 | .padded { 6 | margin: 5px; 7 | } 8 | 9 | .bigtext { 10 | font-size: 24px; 11 | } -------------------------------------------------------------------------------- /public/js/config/config.example.js: -------------------------------------------------------------------------------- 1 | var config = {}; 2 | 3 | config.webServerPort = '8080'; 4 | config.expressSessionSecret = "testSecret" 5 | 6 | module.exports = config; -------------------------------------------------------------------------------- /documentation/test-setup.md: -------------------------------------------------------------------------------- 1 | ### Test Setup 2 | * download chromedriver 3 | * if you've done ```npm install --dev``` already, the selenium-webdriver module should be available 4 | * run ```public/server/integrationTests.js``` -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |<%= message %>
8 |<%= error %>
9 | 10 | 11 | -------------------------------------------------------------------------------- /documentation/web_install.txt: -------------------------------------------------------------------------------- 1 | mkdir /home/lib; 2 | set ROBOSERVER_CODE=https://raw.githubusercontent.com/dunstad/roboserver/master/public/lua/oc; 3 | wget $ROBOSERVER_CODE/setup.lua /home/lib/setup.lua; 4 | lua /home/lib/setup.lua; -------------------------------------------------------------------------------- /documentation/local_install.txt: -------------------------------------------------------------------------------- 1 | mkdir /home/lib; 2 | -- if you're not on localhost or you changed the port, modify those here 3 | set ROBOSERVER_CODE=http://localhost:8080/lua/oc; 4 | wget $ROBOSERVER_CODE/setup.lua /home/lib/setup.lua; 5 | lua /home/lib/setup.lua; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /public/js/lib/three.js-master/* 2 | /public/js/config/config.js 3 | /recipes/* 4 | /node_modules/* 5 | /.cache/* 6 | /.mocha-puppeteer/* 7 | *.log 8 | *.db 9 | .DS_Store 10 | /lua/serialization.lua 11 | /lua/robot.lua 12 | /lua/internet.lua 13 | /release-builds/* -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6" 4 | }, 5 | "include": [ 6 | "public/js/**/*", 7 | "public/js/lib/*", 8 | "public/js/lib/three.min.js" 9 | ], 10 | "typeAcquisition": { 11 | "include": [ 12 | "three", 13 | "socket.io-client" 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /release_checklist.md: -------------------------------------------------------------------------------- 1 | ## release checklist 2 | * do everything in [testplan.md](testplan.md) 3 | * change the version number in [package.json](package.json) 4 | * change the version number in [creative-robot-install.md](creative-robot-install.md) and (survival-robot-install.md)[survival-robot-install.md] 5 | * push to master branch 6 | * make a develop branch 7 | * add a tag to the release commit -------------------------------------------------------------------------------- /public/lua/oc/setup.lua: -------------------------------------------------------------------------------- 1 | local path = '/home/lib'; 2 | local os = require('os'); 3 | local codeURL = os.getenv('ROBOSERVER_CODE'); 4 | local fileName = '/downloadCode.lua' 5 | os.execute('wget -f ' .. codeURL .. fileName .. ' ' .. path .. fileName); 6 | local dl = require("downloadCode"); 7 | dl.downloadAll(path); 8 | local config = require("config"); 9 | config.easy(config.path); 10 | require('commandLoop'); 11 | -------------------------------------------------------------------------------- /public/assets/cube.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/js/server/integrationTests.js: -------------------------------------------------------------------------------- 1 | var webdriver = require('selenium-webdriver'); 2 | var browser = new webdriver.Builder().usingServer().withCapabilities({'browserName': 'chrome' }).build(); 3 | 4 | // this is just an example 5 | 6 | browser.get('http://en.wikipedia.org/wiki/Wiki'); 7 | browser.findElements(webdriver.By.css('[href^="/wiki/"]')).then(function(links){ 8 | console.log('Found', links.length, 'Wiki links.' ) 9 | browser.quit(); 10 | }); -------------------------------------------------------------------------------- /public/js/client/Game.mjs: -------------------------------------------------------------------------------- 1 | import {GUI} from '/js/client/GUI.mjs'; 2 | import {MapRender} from '/js/client/MapRender.mjs'; 3 | import {WebClient} from '/js/client/WebClient.mjs'; 4 | 5 | export class Game { 6 | 7 | /** 8 | * Used to connect the MapRender, GUI, and other things to each other. 9 | */ 10 | constructor() { 11 | 12 | this.mapRender = new MapRender(this); 13 | this.GUI = new GUI(this); 14 | this.webClient = new WebClient(this); 15 | 16 | } 17 | 18 | }; -------------------------------------------------------------------------------- /public/lua/oc/login/login.lua: -------------------------------------------------------------------------------- 1 | local debug = require("component").debug; 2 | local world = debug.getWorld(); 3 | 4 | function getBlock(x, y, z) 5 | return { 6 | ["id"] = world.getBlockId(x, y, z), 7 | ["metadata"] = world.getMetadata(x, y, z), 8 | ["nbt"] = world.getTileNBT(x, y, z) 9 | }; 10 | end 11 | 12 | function setBlock(x, y, z, block) 13 | world.setBlock(x, y, z, block["id"], 0); -- need to get numeric metadata into block["metadata"] 14 | world.setTileNBT(x, y, z, block["nbt"]); 15 | end -------------------------------------------------------------------------------- /public/assets/squaredcube.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /documentation/standalone-install.md: -------------------------------------------------------------------------------- 1 | ### Standalone 2 | 3 | You can download Roboserver for Windows, OS X, or Linux [here (TODO)](TODO). Unpack and run it when the download finishes. You'll also need to remove "127.0.0.0/8" from the blacklist in your OpenComputers configuration file, otherwise your robot will be unable to connect. 4 | 5 | Congratulations, you're halfway done! Now let's get a robot set up. 6 | 7 | If you just want to try the program out without any fuss, I recommend using a Creative robot. Read how [here](creative-robot-install.md). If you want to use this in a survival world, you'll want to follow the steps [here](survival-robot-install.md). -------------------------------------------------------------------------------- /documentation/creative-robot-install.md: -------------------------------------------------------------------------------- 1 | #### Creative 2 | 3 | Set down the Creatix robot, run ```install```, select OpenOS, and reboot it when the install completes. Make sure to put a Geolyzer in its upgrade slot (not its tool slot!). 4 | 5 | Now that your robot is running and OpenOS is installed, just paste [this](web_install.txt) into it. 6 | (If your Roboserver is running but you don't have internet access, you can use the [local install](local_install.txt) instead.) 7 | 8 | After answering a few questions about your robot, it will connect to the server you started in the previous step. Congratulations, you're done! Next check out [these tips](tips.md) on how to use the Roboserver. -------------------------------------------------------------------------------- /public/js/client/cmd.js: -------------------------------------------------------------------------------- 1 | // For easy use from the dev console 2 | 3 | var commandInput = document.getElementById("commandInput"); 4 | var runInTerminal = document.getElementById("runInTerminal"); 5 | var craftButton = document.getElementById("craftButton"); 6 | var craftSelect = document.getElementById("craftSelect"); 7 | 8 | function sendEnter(elem) { 9 | var e = new Event("keydown"); 10 | e.keyCode = 13; 11 | elem.dispatchEvent(e); 12 | } 13 | 14 | function send(command, asShell) { 15 | if (asShell) {runInTerminal.checked = true;} 16 | else {runInTerminal.checked = false;} 17 | commandInput.value = command; 18 | sendEnter(commandInput); 19 | } 20 | 21 | function craft(itemName) { 22 | $('.selectpicker').selectpicker('val', itemName); 23 | craftButton.click(); 24 | } -------------------------------------------------------------------------------- /public/js/server/runTests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Runs a test suite. Used for unit testing. 3 | * @param {object} tests 4 | * @param {function} setup 5 | * @param {object} testData 6 | */ 7 | function runTests(tests, setup, testData) { 8 | 9 | passedTests = 0; 10 | failedTests = 0; 11 | 12 | for (let testName in tests) { 13 | 14 | try { 15 | tests[testName](setup(testData)); 16 | passedTests++; 17 | // console.log(testName, "passed"); 18 | // console.log(); 19 | } 20 | catch (e) { 21 | console.log(testName, "failed"); 22 | console.log(e); 23 | console.log(); 24 | failedTests++; 25 | } 26 | } 27 | 28 | console.log("passed tests:", passedTests); 29 | console.log("failed tests:", failedTests); 30 | console.log(); 31 | 32 | } 33 | 34 | module.exports = runTests; -------------------------------------------------------------------------------- /public/lua/oc/downloadCode.lua: -------------------------------------------------------------------------------- 1 | local os = require('os'); 2 | 3 | local url = os.getenv('ROBOSERVER_CODE') .. '/'; 4 | local filenames = { 5 | 'commandLoop.lua', 6 | 'commandMap.lua', 7 | 'json.lua', 8 | 'scanDirection.lua', 9 | 'sendScan.lua', 10 | 'tcp.lua', 11 | 'trackPosition.lua', 12 | 'trackOrientation.lua', 13 | 'moveAndScan.lua', 14 | 'adjacent.lua', 15 | 'doToArea.lua', 16 | 'interact.lua', 17 | 'craft.lua', 18 | 'config.lua', 19 | -- 'config.txt', -- overwriting the config when updating isn't nice 20 | }; 21 | 22 | local M = {}; 23 | 24 | function M.downloadAll(location) 25 | for index, name in pairs(filenames) do 26 | M.download(name, location); 27 | end 28 | end 29 | 30 | -- rapid reuse may result in receiving cached pages 31 | function M.download(name, location) 32 | os.execute('wget -f ' .. url .. name .. ' ' .. location .. '/' .. name); 33 | end 34 | 35 | return M; 36 | -------------------------------------------------------------------------------- /public/js/server/inventoryTest.js: -------------------------------------------------------------------------------- 1 | const testData = require('./robotTestData'); 2 | const validators = require('../shared/fromRobotSchemas.js').validators; 3 | const assert = require('assert'); 4 | const InventoryData = require('../shared/InventoryData'); 5 | const runTests = require('./runTests.js'); 6 | 7 | function setup(testData) { 8 | let inventory = new InventoryData(testData.internalInventory.meta); 9 | for (let slot of testData.internalInventory.slots) { 10 | inventory.setSlot(slot); 11 | } 12 | return inventory; 13 | } 14 | 15 | let tests = { 16 | 17 | testSerializeSlot: (inventory)=>{ 18 | 19 | for (slotNum in inventory.slots) { 20 | let slot = inventory.serializeSlot(slotNum); 21 | validators.inventorySlot(slot); 22 | assert(slot.side == inventory.side); 23 | assert(slot.slotNum == slotNum); 24 | assert.deepEqual(slot.contents, inventory.slots[slotNum]); 25 | } 26 | 27 | }, 28 | 29 | } 30 | 31 | runTests(tests, setup, testData); -------------------------------------------------------------------------------- /public/lua/oc/tcp.lua: -------------------------------------------------------------------------------- 1 | local internet = require('internet'); 2 | local JSON = require("json"); 3 | local config = require('config'); 4 | local conf = config.get(config.path); 5 | 6 | local handle = internet.open(conf.serverIP, tonumber(conf.tcpPort)); 7 | handle:setvbuf('line'); 8 | -- handle:setTimeout('10'); 9 | 10 | local delimiter = '\n'; 11 | 12 | local M = {}; 13 | 14 | function M.read() 15 | -- reads delimited by newlines 16 | return JSON:decode(handle:read()); 17 | end 18 | 19 | function M.write(data) 20 | local status, result = pcall(function() 21 | -- without the newline the write will wait in the buffer 22 | handle:write(JSON:encode(data)..delimiter); 23 | end); 24 | if not status then 25 | local errorMessage = {['message']='Failed to serialize result!'}; 26 | handle:write(JSON:encode(errorMessage)..delimiter); 27 | end 28 | return status; 29 | end 30 | 31 | function M.close() 32 | return handle:close(); 33 | end 34 | 35 | M.write({id={account=conf.accountName, robot=conf.robotName}}); 36 | 37 | return M; 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Anthony "dunstad" Blount 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /documentation/server-install.md: -------------------------------------------------------------------------------- 1 | ### Server 2 | 3 | Following these instructions will let you access the Roboserver directly from your web browser. If you want, you can even make it available over a network so your friends can too. 4 | 5 | (If this is too much trouble, you can get the desktop version of the Roboserver [here (TODO)](TODO).) 6 | 7 | 1. Install Node.js and Node Package Manager (npm). 8 | 2. Clone this repository. 9 | 3. Checkout a [released version](https://github.com/dunstad/roboserver/releases). 10 | 3. Run ```npm install``` in the project directory. 11 | 4. Rename ```public/js/config.example.js``` to ```public/js/config.js``` and optionally change the settings inside. 12 | 5. Run ```npm run server``` in the project directory. 13 | 14 | If you're running the Roboserver on the same network as any robots trying to connect to it, you may need to change the blacklist settings in your OpenComputers configuration file. For example, if you're running it on the same computer as the Minecraft world your robots are in, you'll need to remove "127.0.0.0/8" from the blacklist, otherwise your robot will be unable to connect. 15 | 16 | Congratulations, you're halfway done! Now let's get a robot set up. 17 | 18 | If you just want to try the program out without any fuss, I recommend using a Creative robot. Read how [here](creative-robot-install.md). If you want to use this in a survival world, you'll want to follow the steps [here](survival-robot-install.md). -------------------------------------------------------------------------------- /public/js/client/WorldAndScenePoint.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This class allows for easy conversion between Three.js scene coordinates and Minecraft world coordinates. 3 | */ 4 | export class WorldAndScenePoint { 5 | 6 | /** 7 | * This class allows for easy conversion between Three.js scene coordinates and Minecraft world coordinates. 8 | * @param {THREE.Vector3 | object} point 9 | * @param {boolean} isWorldPoint 10 | */ 11 | constructor(point, isWorldPoint) { 12 | 13 | this.voxelSideLength = 50; 14 | 15 | if (isWorldPoint) { 16 | this.worldPoint = new THREE.Vector3(point.x, point.y, point.z); 17 | this.scenePoint = new THREE.Vector3( 18 | point.x * this.voxelSideLength, 19 | point.y * this.voxelSideLength, 20 | point.z * this.voxelSideLength 21 | ); 22 | } 23 | 24 | else { 25 | this.scenePoint = new THREE.Vector3(point.x, point.y, point.z); 26 | this.worldPoint = new THREE.Vector3( 27 | Math.round(point.x / this.voxelSideLength), 28 | Math.round(point.y / this.voxelSideLength), 29 | Math.round(point.z / this.voxelSideLength) 30 | ); 31 | } 32 | 33 | } 34 | 35 | /** 36 | * The world point contains the coordinates of something in the Minecraft world. 37 | * @returns {THREE.Vector3} 38 | */ 39 | world() { 40 | return this.worldPoint; 41 | } 42 | 43 | /** 44 | * The scene point contains coordinates used by three.js to render meshes. 45 | * @returns {THREE.Vector3} 46 | */ 47 | scene() { 48 | return this.scenePoint; 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /documentation/faq.md: -------------------------------------------------------------------------------- 1 | #### Why did you pick such weird colors for the map? Purple water? 2 | 3 | The geolyzer can only tell us the estimated hardness of blocks when scanning from a distance, not exactly what they are. Because of this, water and lava look exactly the same to the robot. There are a few ways to address this problem. I decided to make the colors of blocks halfway between the common things they could possibly be in most cases, which is why the water is purple (a mix of red for lava and blue for water). 4 | 5 | Alternatively, I could have picked the most common block at each level of hardness and used that color, but then you have situations like a desert with lava pools that's colored just like a field with ponds. That being said, I'm not against this approach if the current one turns out to be too hard to get used to. 6 | 7 | Here are all the colors in use currently, and the hardness they represent: 8 | 9 | * Bedrock: Hardness = -1, Color = Pure Black (#000000) 10 | * Leaves: Hardness = 0.2, Color = Green (#00CC00) 11 | * Glowstone: Hardness = 0.3, Color = Yellow (#FFCC00) 12 | * Netherrack: Hardness = 0.4, Color = Maroon (#800000) 13 | * Dirt or Sand: Hardness = 0.5, Color = Tan (#ffc140) 14 | * Grass Block: Hardness = 0.6, Color = Yellow Green (#ddc100) 15 | * Sandstone: Hardness = 0.8, Color = Cream (#ffff99) 16 | * Pumpkins or Melons: Hardness = 1.0, Color = Orange (#fdca00) 17 | * Smooth Stone: Hardness = 1.5, Color = Light Gray (#cfcfcf) 18 | * Cobblestone: Hardness = 2.0, Color = Dark Gray (#959595) 19 | * Ores: Hardness = 3.0, Color = Light Blue (#66ffff) 20 | * Cobwebs: Hardness = 4.0, Color = Off White (#f5f5f5) 21 | * Ore Blocks: Hardness = 5.0, Color = Red (#c60000) 22 | * Obsidian: Hardness = 50, Color = Black (#1f1f1f) 23 | * Water or Lava: Hardness = 100, Color = Purple (#9900cc) -------------------------------------------------------------------------------- /public/js/shared/recipeSearch.js: -------------------------------------------------------------------------------- 1 | function getRecipeNames(recipe) { 2 | var recipeNames = []; 3 | for (var output of recipe.out) { 4 | for (var item of output) { 5 | if (recipeNames.indexOf(item.product) == -1) { 6 | recipeNames.push(item.product); 7 | } 8 | } 9 | } 10 | return recipeNames; 11 | } 12 | 13 | function findRecipeFor(product, recipes) { 14 | var recipesForProduct = []; 15 | for (var recipe of recipes) { 16 | var recipeProducts = getRecipeNames(recipe); 17 | if (recipeProducts.indexOf(product) != -1) { 18 | recipesForProduct.push(recipe); 19 | } 20 | } 21 | return recipesForProduct; 22 | } 23 | 24 | function extractRecipeFor(product, recipe) { 25 | if (recipe.out.length == 1) { 26 | var productRecipe = recipe; 27 | } 28 | else { 29 | var indexToCraftingSlotMap = [1, 2, 3, 5, 6, 7, 9, 10, 11]; 30 | var productRecipe = {"in":{}, "out":[]}; 31 | 32 | var productIndex; 33 | for (var i = 0; i < recipe.out.length; i++) { 34 | var outputName = recipe.out[i][0].product; 35 | if (outputName == product) { 36 | productIndex = i; 37 | } 38 | } 39 | if (productIndex === undefined) {productRecipe = false;} 40 | else { 41 | for (var slot in recipe.in) { 42 | if (recipe.in[slot].length == recipe.out.length) { 43 | productRecipe.in[slot] = recipe.in[slot][productIndex].map(item=>[item]); 44 | } 45 | else { 46 | productRecipe.in[slot] = [recipe.in[slot][0]]; 47 | } 48 | } 49 | } 50 | productRecipe.out.push(recipe.out[productIndex]); 51 | } 52 | 53 | return productRecipe; 54 | } 55 | 56 | try { 57 | module.exports.findRecipeFor = findRecipeFor; 58 | module.exports.extractRecipeFor = extractRecipeFor; 59 | } 60 | catch(e) {;} -------------------------------------------------------------------------------- /public/js/client/VoxelMap.mjs: -------------------------------------------------------------------------------- 1 | import {WorldAndScenePoint} from '/js/client/WorldAndScenePoint.mjs'; 2 | 3 | /** 4 | * An organized way to store the voxels of a terrain map. 5 | */ 6 | export class VoxelMap { 7 | 8 | /** 9 | * An organized way to store the voxels of a terrain map. 10 | */ 11 | constructor() { 12 | this.map = {}; 13 | } 14 | 15 | /** 16 | * Retrieve a voxel from the map if it exists. 17 | * @param {WorldAndScenePoint} point 18 | * @returns {THREE.Mesh | false} 19 | */ 20 | get(point) { 21 | var worldPoint = point.world(); 22 | var x = worldPoint.x; 23 | var y = worldPoint.y; 24 | var z = worldPoint.z; 25 | var result; 26 | if (this.map[x] && this.map[x][y] && this.map[x][y][z]) { 27 | result = this.map[x][y][z]; 28 | } 29 | else {result = false;} 30 | return result; 31 | } 32 | 33 | /** 34 | * Store a voxel in the map or remove one from it. 35 | * @param {WorldAndScenePoint} point 36 | * @param {THREE.Mesh} voxel 37 | * @returns {THREE.Mesh} 38 | */ 39 | set(point, voxel) { 40 | var worldPoint = point.world(); 41 | var x = worldPoint.x; 42 | var y = worldPoint.y; 43 | var z = worldPoint.z; 44 | if (!this.map[x]) {this.map[x] = {};} 45 | if (!this.map[x][y]) {this.map[x][y] = {};} 46 | this.map[x][y][z] = voxel; 47 | return voxel; 48 | } 49 | 50 | /** 51 | * Call this function on every voxel in the map. 52 | * @param {function} func 53 | */ 54 | forEach(func) { 55 | for (var xIndex in this.map) { 56 | for (var yIndex in this.map[xIndex]) { 57 | for (var zIndex in this.map[xIndex][yIndex]) { 58 | var point = new WorldAndScenePoint(new THREE.Vector3(xIndex, yIndex, zIndex), true); 59 | var voxel = this.get(point); 60 | if (voxel) {func(voxel);} 61 | } 62 | } 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /public/lua/oc/adjacent.lua: -------------------------------------------------------------------------------- 1 | local orient = require('trackOrientation'); 2 | local pos = require('trackPosition'); 3 | local mas = require('moveAndScan'); 4 | 5 | local M = {}; 6 | 7 | function M.distance(coord1, coord2) 8 | local xDist = coord2.x - coord1.x; 9 | local yDist = coord2.y - coord1.y; 10 | local zDist = coord2.z - coord1.z; 11 | return math.sqrt(xDist^2 + yDist^2 + zDist^2); 12 | end 13 | 14 | function M.distFromSort(coord1) 15 | return function(coord2, coord3) 16 | local dist1 = M.distance(coord1, coord2); 17 | local dist2 = M.distance(coord1, coord3); 18 | return dist1 < dist2; 19 | end 20 | end 21 | 22 | function M.distanceSort(start, destinations) 23 | table.sort(destinations, M.distFromSort(start)); 24 | end 25 | 26 | function M.getAdjacentPoints(point) 27 | local negXPoint = {x=point.x-1, y=point.y, z=point.z}; 28 | local posXPoint = {x=point.x+1, y=point.y, z=point.z}; 29 | local posZPoint = {x=point.x, y=point.y, z=point.z+1}; 30 | local negZPoint = {x=point.x, y=point.y, z=point.z-1}; 31 | local negYPoint = {x=point.x, y=point.y-1, z=point.z}; 32 | local posYPoint = {x=point.x, y=point.y+1, z=point.z}; 33 | return {negXPoint, posXPoint, negZPoint, posZPoint, negYPoint, posYPoint}; 34 | end 35 | 36 | function M.facePoint(point) 37 | local start = pos.get(); 38 | if point.x ~= start.x then 39 | orient.faceX(point.x - start.x); 40 | elseif point.z ~= start.z then 41 | orient.faceZ(point.z - start.z); 42 | end 43 | return orient.get(); 44 | end 45 | 46 | function M.toAdjacent(point, scanType, times) 47 | local adjacentPoints = M.getAdjacentPoints(point); 48 | M.distanceSort(pos.get(), adjacentPoints); 49 | local success = false; 50 | for index, adjPoint in pairs(adjacentPoints) do 51 | if not success then 52 | success = mas.to(adjPoint.x, adjPoint.y, adjPoint.z, false, scanType, times); 53 | end 54 | end 55 | M.facePoint(point); 56 | orient.save(); 57 | return success; 58 | end 59 | 60 | return M; 61 | -------------------------------------------------------------------------------- /public/lua/oc/sendScan.lua: -------------------------------------------------------------------------------- 1 | local component = require('component'); 2 | if not component.isAvailable("geolyzer") then 3 | error("Geolyzer not found"); 4 | end 5 | local geolyzer = component.geolyzer; 6 | tcp = require('tcp'); -- if this is local, reloading modules fails in commandLoop 7 | local pos = require('trackPosition'); 8 | 9 | local M = {}; 10 | 11 | function M.weightedAverage(n1, w1, n2, w2) 12 | return (n1*w1 + n2*w2)/(w1 + w2); 13 | end 14 | 15 | -- round a number to 2 decimal places 16 | function round(num) return tonumber(string.format("%." .. 2 .. "f", num)) end 17 | 18 | function M.volume(x, z, y, w, d, h, times) 19 | -- default to 0 (which is true in lua) 20 | if times then 21 | times = times - 1; 22 | else 23 | times = 0; 24 | end 25 | 26 | local robotPos = pos.get(); 27 | local result = { 28 | x = x + robotPos.x, 29 | y = y + robotPos.y, 30 | z = z + robotPos.z, 31 | w=w, 32 | d=d, 33 | data=geolyzer.scan(x, z, y, w, d, h)}; 34 | 35 | local weight = 1; 36 | for i = 1, times do 37 | 38 | local newScan = geolyzer.scan(x, z, y, w, d, h); 39 | 40 | -- average all data points using weights 41 | for j = 1, result.data.n do 42 | result.data[j] = M.weightedAverage(result.data[j], weight, newScan[j], 1); 43 | end 44 | 45 | weight = weight + 1; 46 | 47 | end 48 | 49 | -- round the numbers to save space in the json 50 | -- important because robots have a limited write buffer 51 | for i = 1, result.data.n do 52 | result.data[i] = round(result.data[i]); 53 | end 54 | 55 | tcp.write({['map data']=result}); 56 | return result; 57 | end 58 | 59 | function M.plane(y, times) 60 | for x = -32, 32 do 61 | M.volume(x, -32, y, 1, 64, 1, times); 62 | end 63 | -- max shape volume is 64, but we can scan from -32 to 32, inclusive 64 | -- that's 65, so we have one row we miss in the previous loop to scan 65 | -- still missing one cube after this final row, but oh well 66 | M.volume(-32, 32, y, 64, 1, 1, times); 67 | return true; 68 | end 69 | 70 | return M; 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roboserver", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "test": "node ./public/js/server/robotTest.js & node ./public/js/server/inventoryTest.js", 7 | "start": "node ./bin/www", 8 | "server": "node ./bin/www", 9 | "server-dev": "nodemon ./bin/www", 10 | "connect": "nodemon ./public/js/server/connect.js", 11 | "electron": "electron electronApp.js", 12 | "package-mac": "electron-packager . --overwrite --platform=darwin --arch=x64 --icon=public/assets/placeholder_icon.icns --prune=true --out=release-builds", 13 | "package-win": "electron-packager . --overwrite --platform=win32 --arch=x64 --icon=public/assets/placeholder_icon.ico --prune=true --out=release-builds", 14 | "package-all": "electron-packager . --overwrite --all --icon=public/assets/placeholder_icon.icns --prune=true --out=release-builds" 15 | }, 16 | "dependencies": { 17 | "ajv": "^6.10.2", 18 | "bcryptjs": "^2.4.3", 19 | "body-parser": "^1.15.2", 20 | "cookie-parser": "~1.4.3", 21 | "debug": "^4.1.1", 22 | "ejs": "^2.5.2", 23 | "express": "^4.16.4", 24 | "express-session": "^1.15.2", 25 | "minecraft-data": "^2.34.0", 26 | "morgan": "^1.9.1", 27 | "nedb": "^1.8.0", 28 | "nedb-promise": "^2.0.1", 29 | "nedb-session-store": "^1.1.1", 30 | "passport": "^0.3.2", 31 | "passport-local": "^1.0.0", 32 | "passport.socketio": "^3.7.0", 33 | "serve-favicon": "^2.5.0", 34 | "socket.io": "^2.2.0" 35 | }, 36 | "devDependencies": { 37 | "electron": "^3.0.10", 38 | "electron-packager": "^12.2.0", 39 | "nodemon": "^1.18.6" 40 | }, 41 | "main": "electronApp.js", 42 | "description": "This is a HTTP and TCP server which OpenComputers robots can read and execute commands from.", 43 | "repository": { 44 | "type": "git", 45 | "url": "git+https://github.com/dunstad/roboserver.git" 46 | }, 47 | "author": "dunstad", 48 | "license": "ISC", 49 | "bugs": { 50 | "url": "https://github.com/dunstad/roboserver/issues" 51 | }, 52 | "homepage": "https://github.com/dunstad/roboserver#readme" 53 | } 54 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('roboserver:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || require('../public/js/config/config.js').webServerPort); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | // add socket.io handlers and create tcp server 25 | require('../public/js/server/customizeServer.js')(server, app); 26 | 27 | /** 28 | * Listen on provided port, on all network interfaces. 29 | */ 30 | 31 | server.listen(port); 32 | server.on('error', onError); 33 | server.on('listening', onListening); 34 | 35 | /** 36 | * Normalize a port into a number, string, or false. 37 | */ 38 | 39 | function normalizePort(val) { 40 | var port = parseInt(val, 10); 41 | 42 | if (isNaN(port)) { 43 | // named pipe 44 | return val; 45 | } 46 | 47 | if (port >= 0) { 48 | // port number 49 | return port; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | /** 56 | * Event listener for HTTP server "error" event. 57 | */ 58 | 59 | function onError(error) { 60 | if (error.syscall !== 'listen') { 61 | throw error; 62 | } 63 | 64 | var bind = typeof port === 'string' 65 | ? 'Pipe ' + port 66 | : 'Port ' + port; 67 | 68 | // handle specific listen errors with friendly messages 69 | switch (error.code) { 70 | case 'EACCES': 71 | console.error(bind + ' requires elevated privileges'); 72 | process.exit(1); 73 | break; 74 | case 'EADDRINUSE': 75 | console.error(bind + ' is already in use'); 76 | process.exit(1); 77 | break; 78 | default: 79 | throw error; 80 | } 81 | } 82 | 83 | /** 84 | * Event listener for HTTP server "listening" event. 85 | */ 86 | 87 | function onListening() { 88 | var addr = server.address(); 89 | var bind = typeof addr === 'string' 90 | ? 'pipe ' + addr 91 | : 'port ' + addr.port; 92 | debug('Listening on ' + bind); 93 | } 94 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Roboserver 2 | 3 | This project lets you control [OpenComputers](http://ocdoc.cil.li/) robots through a simple GUI. No Lua coding necessary! 4 | 5 |  6 | 7 | Here are a few things the Roboserver can simplify right now: 8 | * Digging large areas 9 | * Crafting complex items 10 | * Repetitive construction 11 | * Locating ore underground 12 | * [Other stuff!](https://www.youtube.com/watch?v=2lbb0-yfSdw) 13 | 14 | ## Getting Started 15 | 16 | (Tested on Minecraft version 1.10.2, OpenComputers version 1.6. If something's broken for your version, see [Reporting Bugs](#reporting-bugs).) 17 | 18 | If you're looking to get set up as fast as possible, you can get the desktop version of the Roboserver [here (TODO)](TODO). See how to start using it [here](documentation/standalone-install.md). 19 | 20 | Alternatively, if you've got a bit of technical know-how and more time on your hands, you can run the Roboserver from the source code by following the instructions [here](documentation/server-install.md). 21 | 22 | ## Reporting Bugs 23 | 24 | Before creating an issue, please read the [usage tips](documentation/tips.md), and check that it hasn't already been reported. 25 | 26 | When reporting a bug in the [issue tracker](https://github.com/dunstad/roboserver/issues?q=is%3Aopen), in order to help me address your issue as quickly as possible, please provide the following information: 27 | 28 | 1. What version you're using of Minecraft and OpenComputers 29 | 2. Whether you're using the Roboserver's desktop application or accessing it from your browser 30 | * If you're using a browser, state which one 31 | 3. Steps to reproduce the problem 32 | 4. The expected behavior 33 | 5. The actual behavior 34 | 35 | Screenshots, video, and error messages are always welcome. 36 | 37 | Feel free to create a pull request if you've resolved an outstanding issue. 38 | 39 | ## License 40 | 41 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 42 | 43 | ## Acknowledgments 44 | 45 | This project is made possible by the continued effort of all the wonderful people who contribute to [OpenComputers](https://github.com/MightyPirates/OpenComputers). -------------------------------------------------------------------------------- /documentation/survival-robot-install.md: -------------------------------------------------------------------------------- 1 | #### Survival 2 | 3 | If you have no previous experience with OpenComputers, you should take a look at [this guide](http://ocdoc.cil.li/tutorial:oc1_basic_computer). 4 | 5 | You need to craft at minimum the following parts for your robot: 6 | * [Computer Case (Tier 2)](http://crafting-guide.com/browse/opencomputers/computer_case_tier_2/) 7 | * [EEPROM (Lua BIOS)](http://crafting-guide.com/browse/opencomputers/eeprom_lua_bios/) 8 | * [CPU (Tier 2)](http://crafting-guide.com/browse/opencomputers/central_processing_unit_cpu_tier_2/) 9 | * [Memory (Tier 1)](http://crafting-guide.com/browse/opencomputers/memory_tier_1/) x2 10 | * [Hard Disk Drive (Tier 1)](http://crafting-guide.com/browse/opencomputers/hard_disk_drive_tier_1/) with [OpenOS](http://crafting-guide.com/browse/opencomputers/floppy_disk_openos/) installed 11 | * [Internet Card](http://crafting-guide.com/browse/opencomputers/internet_card/) 12 | * [Geolyzer](http://crafting-guide.com/browse/opencomputers/geolyzer/) 13 | * [Inventory Upgrade](http://crafting-guide.com/browse/opencomputers/inventory_upgrade/) 14 | * [Inventory Controller Upgrade](http://crafting-guide.com/browse/opencomputers/inventory_controller_upgrade/) 15 | * [Crafting Upgrade](http://crafting-guide.com/browse/opencomputers/crafting_upgrade/) 16 | 17 | Unless you really know what you're doing, you probably need these too: 18 | * [Keyboard](http://crafting-guide.com/browse/opencomputers/keyboard/) 19 | * [Screen](http://crafting-guide.com/browse/opencomputers/screen_tier_1/) 20 | * [Graphics Card (Tier 1)](http://crafting-guide.com/browse/opencomputers/graphics_card_tier_1/) 21 | 22 | Place all these parts in an [Electronics Assembler](http://crafting-guide.com/browse/opencomputers/electronics_assembler/). Power and start the assembler, and when your robot is finished, place it in the world and power it on. 23 | 24 | Now that your robot is running and OpenOS is installed, just paste [this](web_install.txt) into it. 25 | (If your Roboserver is running but you don't have internet access, you can use the [local install](local_install.txt) instead.) 26 | 27 | After answering a few questions about your robot, it will connect to the server you started in the previous step. Congratulations, you're done! Next check out [these tips](tips.md) on how to use the Roboserver. -------------------------------------------------------------------------------- /public/js/client/CoordForm.mjs: -------------------------------------------------------------------------------- 1 | import {WorldAndScenePoint} from '/js/client/WorldAndScenePoint.mjs'; 2 | 3 | /** 4 | * An organized way to access coordinates stored in number inputs. 5 | */ 6 | export class CoordForm { 7 | 8 | /** 9 | * An organized way to access coordinates stored in number inputs. 10 | * @param {HTMLInputElement} xForm 11 | * @param {HTMLInputElement} yForm 12 | * @param {HTMLInputElement} zForm 13 | */ 14 | constructor(xForm, yForm, zForm) { 15 | this.x = xForm; 16 | this.y = yForm; 17 | this.z = zForm; 18 | } 19 | 20 | /** 21 | * Used to tell when no coordinates are entered. 22 | * @returns {boolean} 23 | */ 24 | isEmpty() { 25 | var empty = false; 26 | if (!(this.x.value || this.y.value || this.z.value )) { 27 | empty = true; 28 | } 29 | return empty; 30 | } 31 | 32 | /** 33 | * Used to tell when all coordinates have been entered. 34 | * @returns {boolean} 35 | */ 36 | isComplete() { 37 | var complete = false; 38 | if (this.x.value && this.y.value && this.z.value ) { 39 | complete = true; 40 | } 41 | return complete; 42 | } 43 | 44 | /** 45 | * Creates a Vector3 from the current form input. 46 | * @returns {WorldAndScenePoint} 47 | */ 48 | getPoint() { 49 | return new WorldAndScenePoint( 50 | new THREE.Vector3( 51 | parseInt(this.x.value), 52 | parseInt(this.y.value), 53 | parseInt(this.z.value) 54 | ), 55 | true 56 | ); 57 | } 58 | 59 | /** 60 | * Sets form inputs to the values in the provided point. 61 | * @param {WorldAndScenePoint} point 62 | */ 63 | setFromPoint(point) { 64 | var worldVector = point.world(); 65 | this.x.value = worldVector.x; 66 | this.y.value = worldVector.y; 67 | this.z.value = worldVector.z; 68 | } 69 | 70 | /** 71 | * Removes any values from all fields of the form. 72 | */ 73 | clear() { 74 | this.x.value = ""; 75 | this.y.value = ""; 76 | this.z.value = ""; 77 | } 78 | 79 | /** 80 | * Used to add one event listener to all inputs. 81 | * @param {string} type 82 | * @param {function} listener 83 | */ 84 | addEventListener(type, listener) { 85 | this.x.addEventListener(type, listener); 86 | this.y.addEventListener(type, listener); 87 | this.z.addEventListener(type, listener); 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /public/lua/oc/scanDirection.lua: -------------------------------------------------------------------------------- 1 | local orient = require('trackOrientation'); 2 | local scan = require('sendScan'); 3 | 4 | local M = {}; 5 | 6 | function M.makeBigScanner(x, z, w, d) 7 | return function(y, times) 8 | return scan.volume(x, z, y, w, d, 1, times); 9 | end; 10 | end 11 | 12 | -- functions to scan one row at the end of an axis 13 | local scanZPosBig = M.makeBigScanner(-32, 32, 64, 1); 14 | local scanZNegBig = M.makeBigScanner(-32, -32, 64, 1); 15 | local scanXPosBig = M.makeBigScanner(32, -32, 1, 64); 16 | local scanXNegBig = M.makeBigScanner(-32, -32, 1, 64); 17 | 18 | local scanBigMap = { 19 | [0]=scanZPosBig, 20 | [1]=scanZNegBig, 21 | [2]=scanXPosBig, 22 | [3]=scanXNegBig 23 | }; 24 | 25 | function M.makeSmallScanner(x, z, w, d) 26 | return function(times) 27 | return scan.volume(x, z, -2, w, d, 8, times); 28 | end; 29 | end 30 | 31 | -- scan.volume(-3, -3, 8, 8) 32 | -- functions to scan a small plane in a particular direction 33 | local scanZPosSmall = M.makeSmallScanner(-3, 4, 8, 1); 34 | local scanZNegSmall = M.makeSmallScanner(-3, -3, 8, 1); 35 | local scanXPosSmall = M.makeSmallScanner(4, -3, 1, 8); 36 | local scanXNegSmall = M.makeSmallScanner(-3, -3, 1, 8); 37 | 38 | local scanSmallMap = { 39 | [0]=scanZPosSmall, 40 | [1]=scanZNegSmall, 41 | [2]=scanXPosSmall, 42 | [3]=scanXNegSmall 43 | }; 44 | 45 | 46 | local scanForwardMap = { 47 | [0]=0, 48 | [1]=2, 49 | [2]=1, 50 | [3]=3 51 | }; 52 | 53 | local scanBackMap = { 54 | [0]=1, 55 | [1]=3, 56 | [2]=0, 57 | [3]=2 58 | }; 59 | 60 | -- orientation is from trackOrientation.lua 61 | function M.forwardBig(y, times) 62 | return scanBigMap[scanForwardMap[orient.get()]](y, times); 63 | end 64 | 65 | function M.backBig(y, times) 66 | return scanBigMap[scanBackMap[orient.get()]](y, times); 67 | end 68 | 69 | function M.upBig(times) 70 | return scan.plane(7); 71 | end; 72 | 73 | function M.downBig(times) 74 | return scan.plane(-1); 75 | end; 76 | 77 | function M.forwardSmall(times) 78 | return scanSmallMap[scanForwardMap[orient.get()]](y, times); 79 | end 80 | 81 | function M.backSmall(times) 82 | return scanSmallMap[scanBackMap[orient.get()]](y, times); 83 | end 84 | 85 | function M.upSmall(times) 86 | return scan.volume(-3, -3, 5, 8, 8, 1, times); 87 | end 88 | 89 | function M.downSmall(times) 90 | return scan.volume(-3, -3, -2, 8, 8, 1, times); 91 | end 92 | 93 | return M; 94 | -------------------------------------------------------------------------------- /public/lua/oc/commandLoop.lua: -------------------------------------------------------------------------------- 1 | -- packages that use tcp 2 | function reloadPackages() 3 | tcp = require('tcp'); 4 | commandMap = require('commandMap'); 5 | dta = require('doToArea'); 6 | int = require('interact'); 7 | sendScan = require('sendScan'); 8 | pos = require('trackPosition'); 9 | end 10 | 11 | function reconnect(sleepTime) 12 | tcp.close(); 13 | -- unloading 'computer' breaks stuff, it can't be required again for some reason 14 | -- unload all packages that use tcp so they work after reconnecting 15 | local loadedPackages = {'tcp', 'commandMap', 'doToArea', 'interact', 'sendScan', 'trackPosition'}; 16 | for index, p in pairs(loadedPackages) do 17 | package.loaded[p] = nil; 18 | end 19 | -- wait for server to come back up 20 | os.sleep(sleepTime or 5); 21 | -- reconnect to server 22 | reloadPackages(); 23 | end 24 | 25 | function loadSafely() 26 | commandMap = require('commandMap'); 27 | dta = require('doToArea'); 28 | int = require('interact'); 29 | sendScan = require('sendScan'); 30 | pos = require('trackPosition'); 31 | scanDirection = require('scanDirection'); 32 | orient = require('trackOrientation'); 33 | mas = require('moveAndScan'); 34 | robot = require('robot'); 35 | adj = require('adjacent'); 36 | craft = require('craft'); 37 | computer = require('computer'); 38 | config = require('config'); 39 | raw = config.get(config.path).raw; 40 | end 41 | 42 | -- not much we can do if tcp fails 43 | -- it shouldn't change much though 44 | tcp = require('tcp'); 45 | local success, message = pcall(loadSafely); 46 | if not success then 47 | print(message); 48 | tcp.write({['message']=message}); 49 | reconnect(15); 50 | end 51 | 52 | function unpack (t, i) 53 | i = i or 1; 54 | if t[i] ~= nil then 55 | return t[i], unpack(t, i + 1); 56 | end 57 | end 58 | 59 | -- wait until a command exists, grab it, execute it, and send result back 60 | function executeCommand() 61 | local data = tcp.read(); 62 | local result = commandMap[data['name']](unpack(data['parameters'])); 63 | tcp.write({['command result']={data['name'], result}}); 64 | tcp.write({['power level']=computer.energy()/computer.maxEnergy()}); 65 | end 66 | 67 | continueLoop = true; 68 | while continueLoop do 69 | local success, message = pcall(executeCommand); 70 | if not success then 71 | print(message); 72 | tcp.write({['message']=message}); 73 | reconnect(); 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /public/js/server/webClientTestData.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | 'listen start': { 4 | robot: 'rob', 5 | }, 6 | 7 | 'listen end': { 8 | robot: 'rob', 9 | }, 10 | 11 | 'command result': { 12 | robot: 'rob', 13 | data: [true, 'a test command result'], 14 | }, 15 | 16 | 'map data': { 17 | robot: 'rob', 18 | data: { 19 | x: 0, 20 | z: 0, 21 | y: 0, 22 | w: 3, 23 | d: 3, 24 | data: { 25 | 1: 1, 26 | 2: 1, 27 | 3: 1, 28 | 4: 1, 29 | 5: 1, 30 | 6: 1, 31 | 7: 1, 32 | 8: 1, 33 | 9: 1, 34 | 10: 1, 35 | 11: 1, 36 | 12: 1, 37 | 13: 1, 38 | 14: 0, 39 | 15: 1, 40 | 16: 1, 41 | 17: 1, 42 | 18: 1, 43 | 19: 1, 44 | 20: 1, 45 | 21: 1, 46 | 22: 1, 47 | 23: 0, 48 | 24: 1, 49 | 25: 1, 50 | 26: 1, 51 | 27: 1, 52 | n: 27 53 | }, 54 | }, 55 | }, 56 | 57 | 58 | 'block data': { 59 | robot: 'rob', 60 | data: { 61 | name: 'minecraft:dirt', 62 | hardness: .5, 63 | point: { 64 | x: 2, 65 | y: 2, 66 | z: 2, 67 | }, 68 | }, 69 | }, 70 | 71 | 'robot position': { 72 | robot: 'rob', 73 | data: { 74 | x: 4, 75 | y: 4, 76 | z: 4, 77 | }, 78 | }, 79 | 80 | 'delete selection': { 81 | robot: 'rob', 82 | data: 1, 83 | }, 84 | 85 | 'dig success': { 86 | robot: 'rob', 87 | data: { 88 | x: 2, 89 | y: 2, 90 | z: 2, 91 | }, 92 | }, 93 | 94 | 'inventory data': { 95 | robot: 'rob', 96 | data: { 97 | 'size': 64, 98 | 'side': -1, 99 | 'selected': 1 100 | }, 101 | }, 102 | 103 | 'slot data': { 104 | robot: 'rob', 105 | data: { 106 | side: -1, 107 | slotNum: 1, 108 | contents: { 109 | damage: 0, 110 | hasTag: false, 111 | label: 'Dirt', 112 | maxDamage: 0, 113 | maxSize: 64, 114 | name: 'minecraft:dirt', 115 | size: 64 116 | } 117 | }, 118 | }, 119 | 120 | 'power level': { 121 | robot: 'rob', 122 | data: .5, 123 | }, 124 | 125 | 'available components': { 126 | robot: 'rob', 127 | data: {raw: true}, 128 | }, 129 | 130 | }; -------------------------------------------------------------------------------- /public/lua/oc/trackOrientation.lua: -------------------------------------------------------------------------------- 1 | local robot = require('robot'); 2 | local math = require('math'); 3 | local config = require('config'); 4 | 5 | local orientation = tonumber(config.get(config.path).orient); 6 | -- 0: z+, south 7 | -- 1: x+, east 8 | -- 2: z-, north 9 | -- 3: x-, west 10 | 11 | local axisMap = { 12 | x = { 13 | pos = 1, 14 | neg = 3 15 | }, 16 | z = { 17 | pos = 0, 18 | neg = 2 19 | } 20 | }; 21 | 22 | local M = {}; 23 | 24 | function M.save() 25 | config.set({orient=orientation}, config.path); 26 | end 27 | 28 | function M.load() 29 | orientation = config.get(config.path).orient; 30 | end 31 | 32 | function M.set(orient) 33 | orientation = orient; 34 | end 35 | 36 | function M.get() 37 | return orientation; 38 | end 39 | 40 | -- start using these functions when the robot is facing south. 41 | -- don't revert to using normal turn functions or they'll stop being accurate 42 | function M.turnLeft() 43 | robot.turnLeft(); 44 | orientation = (orientation + 1) % 4; 45 | return orientation; 46 | end 47 | 48 | function M.turnRight() 49 | robot.turnRight(); 50 | orientation = (orientation - 1) % 4; 51 | return orientation; 52 | end 53 | 54 | -- accepts number of times to turn, pos for left, neg for right 55 | function M.turn(num) 56 | local turnFunction; 57 | if num > 0 then 58 | turnFunction = M.turnLeft; 59 | else 60 | turnFunction = M.turnRight; 61 | end 62 | 63 | for i = 1, math.abs(num) do 64 | turnFunction(); 65 | end 66 | return orientation; 67 | end 68 | 69 | -- accepts direction to face 70 | function M.face(num) 71 | assert(num >= 0 and num <= 3, 'facing out of range'); 72 | if num == orientation then return orientation; end 73 | local distR = num - orientation; 74 | -- should never have to turn more than twice 75 | if math.abs(distR) > 2 then 76 | local distRSign = distR / math.abs(distR); 77 | local distL = distR - 4 * distRSign; 78 | return M.turn(distL); 79 | else 80 | return M.turn(distR); 81 | end 82 | end 83 | 84 | -- positive number to face x+, negative to face x- 85 | function M.faceX(num) 86 | local sign = num / math.abs(num); 87 | if sign > 0 then 88 | M.face(axisMap.x.pos); 89 | else 90 | return M.face(axisMap.x.neg); 91 | end 92 | end 93 | 94 | function M.faceZ(num) 95 | local sign = num / math.abs(num); 96 | if sign > 0 then 97 | M.face(axisMap.z.pos); 98 | else 99 | return M.face(axisMap.z.neg); 100 | end 101 | end 102 | 103 | return M; 104 | -------------------------------------------------------------------------------- /public/js/client/CutawayForm.mjs: -------------------------------------------------------------------------------- 1 | import {WorldAndScenePoint} from '/js/client/WorldAndScenePoint.mjs'; 2 | 3 | /** 4 | * An organized way to access how we want to cut away the map. 5 | */ 6 | export class CutawayForm { 7 | 8 | /** 9 | * An organized way to access how we want to cut away the map. 10 | * @param {HTMLButtonElement} axisForm 11 | * @param {HTMLButtonElement} operationForm 12 | * @param {HTMLInputElement} cutawayValueForm 13 | */ 14 | constructor(axisForm, operationForm, cutawayValueForm) { 15 | this.axis = axisForm; 16 | this.operation = operationForm; 17 | this.cutawayValue = cutawayValueForm; 18 | 19 | this.axis.states = ['X', 'Y', 'Z']; 20 | this.operation.states = ['>', '<']; 21 | 22 | var changeState = (e)=>{ 23 | var button = e.target; 24 | var currentState = button.states.indexOf(button.textContent); 25 | var nextState = currentState == button.states.length - 1 ? 0 : currentState + 1; 26 | button.textContent = button.states[nextState]; 27 | }; 28 | 29 | this.axis.addEventListener('click', changeState); 30 | this.operation.addEventListener('click', changeState); 31 | 32 | } 33 | 34 | /** 35 | * Removes any values from the form. 36 | */ 37 | clear() { 38 | this.cutawayValue.value = ""; 39 | } 40 | 41 | /** 42 | * Used to re-render the cutaway when the form is interacted with. 43 | * @param {function} listener 44 | */ 45 | addChangeListener(listener) { 46 | this.axis.addEventListener('click', listener); 47 | this.operation.addEventListener('click', listener); 48 | this.cutawayValue.addEventListener('input', listener); 49 | } 50 | 51 | /** 52 | * Lets us know whether a voxel should be displayed given the entered cutaway point. 53 | * @param {WorldAndScenePoint} point 54 | * @returns {boolean} 55 | */ 56 | shouldBeRendered(point) { 57 | var axisName = this.axis.textContent; 58 | var operationName = this.operation.textContent; 59 | var cutawayValue = parseInt(this.cutawayValue.value); 60 | var result = true; 61 | if (!isNaN(cutawayValue)) { 62 | var axisNameMap = { 63 | 'X': 'x', 64 | 'Y': 'y', 65 | 'Z': 'z' 66 | } 67 | var coord = point.world()[axisNameMap[axisName]]; 68 | if (operationName == '>') { 69 | if (coord > cutawayValue) { 70 | result = false; 71 | } 72 | } 73 | else if (operationName == '<') { 74 | if (coord < cutawayValue) { 75 | result = false; 76 | } 77 | } 78 | } 79 | return result; 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /electronApp.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron') 2 | // Module to control application life. 3 | const electronApp = electron.app 4 | // Module to create native browser window. 5 | const BrowserWindow = electron.BrowserWindow 6 | 7 | // for making the refresh key work 8 | const globalShortcut = electron.globalShortcut 9 | 10 | const path = require('path') 11 | const url = require('url') 12 | 13 | // Keep a global reference of the window object, if you don't, the window will 14 | // be closed automatically when the JavaScript object is garbage collected. 15 | let mainWindow 16 | 17 | function createWindow () { 18 | 19 | // start the web server 20 | require('./bin/www') 21 | 22 | // Create the browser window. 23 | mainWindow = new BrowserWindow({ 24 | width: 800, 25 | height: 600, 26 | webPreferences: { 27 | nodeIntegration: false, 28 | }, 29 | }) 30 | 31 | // and load the index.html of the app. 32 | var webServerPort = require('./public/js/config/config.js').webServerPort; 33 | mainWindow.loadURL('http://127.0.0.1:' + webServerPort + '/login') 34 | 35 | // Open the DevTools. 36 | mainWindow.webContents.openDevTools() 37 | 38 | // make the refresh key work 39 | globalShortcut.register('f5', function() { 40 | console.log('f5 is pressed') 41 | mainWindow.reload() 42 | }) 43 | globalShortcut.register('CommandOrControl+R', function() { 44 | console.log('CommandOrControl+R is pressed') 45 | mainWindow.reload() 46 | }) 47 | 48 | // Emitted when the window is closed. 49 | mainWindow.on('closed', function () { 50 | // Dereference the window object, usually you would store windows 51 | // in an array if your app supports multi windows, this is the time 52 | // when you should delete the corresponding element. 53 | mainWindow = null 54 | }) 55 | } 56 | 57 | // This method will be called when Electron has finished 58 | // initialization and is ready to create browser windows. 59 | // Some APIs can only be used after this event occurs. 60 | electronApp.on('ready', ()=>{setTimeout(createWindow, 1000)}) 61 | 62 | // Quit when all windows are closed. 63 | electronApp.on('window-all-closed', function () { 64 | // On OS X it is common for applications and their menu bar 65 | // to stay active until the user quits explicitly with Cmd + Q 66 | if (process.platform !== 'darwin') { 67 | electronApp.quit() 68 | } 69 | }) 70 | 71 | electronApp.on('activate', function () { 72 | // On OS X it's common to re-create a window in the app when the 73 | // dock icon is clicked and there are no other windows open. 74 | if (mainWindow === null) { 75 | createWindow() 76 | } 77 | }) -------------------------------------------------------------------------------- /public/js/shared/MapData.js: -------------------------------------------------------------------------------- 1 | let validators = require('./fromRobotSchemas.js').validators; 2 | /** 3 | * An organized way to store map data. 4 | */ 5 | class MapData { 6 | 7 | /** 8 | * An organized way to store map data. 9 | */ 10 | constructor() { 11 | this.map = {}; 12 | } 13 | 14 | /** 15 | * Retrieve block data from the map if it exists. 16 | * @param {number} x 17 | * @param {number} y 18 | * @param {number} z 19 | * @returns {object | false} 20 | */ 21 | get(x, y, z) { 22 | let result; 23 | if (this.map[x] && this.map[x][y] && this.map[x][y][z]) { 24 | result = this.map[x][y][z]; 25 | } 26 | else {result = false;} 27 | return result; 28 | } 29 | 30 | /** 31 | * Store block data in or remove it from the map. 32 | * This will only change properties listed in blockData. 33 | * If blockData is falsy, it removes the entry. 34 | * @param {number} x 35 | * @param {number} y 36 | * @param {number} z 37 | * @param {object} blockData 38 | * @returns {object} 39 | */ 40 | set(x, y, z, blockData) { 41 | if (!this.map[x]) {this.map[x] = {};} 42 | if (!this.map[x][y]) {this.map[x][y] = {};} 43 | if (blockData) { 44 | if (!this.map[x][y][z]) {this.map[x][y][z] = {};} 45 | Object.assign(this.map[x][y][z], blockData); 46 | } 47 | else { 48 | this.map[x][y][z] = undefined; 49 | } 50 | return blockData; 51 | } 52 | 53 | /** 54 | * Store block data contained in the geolyzer scan format 55 | * @param {object} geolyzerScan 56 | */ 57 | setFromGeolyzerScan(geolyzerScan) { 58 | validators.geolyzerScan(geolyzerScan); 59 | for (let x = 0; x < geolyzerScan.w; x++) { 60 | for (let z = 0; z < geolyzerScan.d; z++) { 61 | for (let y = 0; y < (geolyzerScan.data.n / (geolyzerScan.w * geolyzerScan.d)); y++) { 62 | 63 | let xWithOffset = x + geolyzerScan.x; 64 | let yWithOffset = y + geolyzerScan.y; 65 | let zWithOffset = z + geolyzerScan.z; 66 | 67 | // this is how the geolyzer reports 3d data in a 1d array 68 | // also lua is indexed from 1 69 | let index = (x + 1) + z*geolyzerScan.w + y*geolyzerScan.w*geolyzerScan.d; 70 | 71 | this.set(xWithOffset, yWithOffset, zWithOffset, {"hardness": geolyzerScan.data[index]}); 72 | 73 | } 74 | } 75 | } 76 | } 77 | 78 | /** 79 | * Store block data contained in the map data format 80 | * @param {object} mapData 81 | */ 82 | setFromMapData(mapData) { 83 | for (var xIndex in mapData) { 84 | for (var yIndex in mapData[xIndex]) { 85 | for (var zIndex in mapData[xIndex][yIndex]) { 86 | let blockData = mapData[xIndex][yIndex][zIndex]; 87 | this.set(xIndex, yIndex, zIndex, blockData); 88 | } 89 | } 90 | } 91 | } 92 | 93 | } 94 | 95 | try {module.exports = MapData;} 96 | catch(e) {;} -------------------------------------------------------------------------------- /public/lua/oc/trackPosition.lua: -------------------------------------------------------------------------------- 1 | local robot = require('robot'); 2 | tcp = require('tcp'); -- if this is local, reloading modules fails in commandLoop 3 | local orient = require('trackOrientation'); 4 | local config = require('config'); 5 | local confOptions = config.get(config.path); 6 | 7 | local position = { 8 | x = tonumber(confOptions.posX), 9 | y = tonumber(confOptions.posY), 10 | z = tonumber(confOptions.posZ), 11 | }; 12 | local M = {}; 13 | 14 | function M.save() 15 | local posConf = { 16 | posX = position.x, 17 | posY = position.y, 18 | posZ = position.z, 19 | }; 20 | config.set(posConf, config.path); 21 | end 22 | 23 | function M.load() 24 | local confOptions = config.get(config.path); 25 | position = { 26 | x = tonumber(confOptions.posX), 27 | y = tonumber(confOptions.posY), 28 | z = tonumber(confOptions.posZ), 29 | }; 30 | end 31 | 32 | function M.set(x, y, z) 33 | position = {x=x, y=y, z=z}; 34 | return position; 35 | end 36 | 37 | function M.get() 38 | return position; 39 | end 40 | 41 | function M.toAbsolute(x, y, z) 42 | return x + position.x, y + position.y, z + position.z; 43 | end 44 | 45 | -- how to change coordinates based on orientation 46 | -- 0: z+, south 47 | -- 1: x+, east 48 | -- 2: z-, north 49 | -- 3: x-, west 50 | local forwardMap = { 51 | [0]={z=1}, 52 | [1]={x=1}, 53 | [2]={z=-1}, 54 | [3]={x=-1} 55 | }; 56 | 57 | local backwardMap = { 58 | [0]={z=-1}, 59 | [1]={x=-1}, 60 | [2]={z=1}, 61 | [3]={x=1} 62 | }; 63 | 64 | 65 | function M.sendLocation() 66 | return tcp.write({['robot position']=position}); 67 | end 68 | 69 | -- don't stop using these functions once you start or they won't be accurate 70 | 71 | -- orientation comes from trackOrientation.lua 72 | function M.forward() 73 | -- the loop will only perform one iteration 74 | -- this is just a way to treat the properties generically 75 | if (robot.forward()) then 76 | for axis, change in pairs(forwardMap[orient.get()]) do 77 | position[axis] = position[axis] + change; 78 | end 79 | M.sendLocation(); 80 | return position; 81 | end 82 | -- if the movement failed 83 | return false; 84 | end 85 | 86 | function M.back() 87 | if (robot.back()) then 88 | for axis, change in pairs(backwardMap[orient.get()]) do 89 | position[axis] = position[axis] + change; 90 | end 91 | M.sendLocation(); 92 | return position; 93 | end 94 | return false; 95 | end 96 | 97 | function M.up() 98 | if (robot.up()) then 99 | position.y = position.y + 1; 100 | M.sendLocation(); 101 | return position; 102 | end 103 | return false; 104 | end 105 | 106 | function M.down() 107 | if (robot.down()) then 108 | position.y = position.y - 1; 109 | M.sendLocation(); 110 | return position; 111 | end 112 | return false; 113 | end 114 | 115 | return M; 116 | -------------------------------------------------------------------------------- /public/js/shared/fromClientSchemas.js: -------------------------------------------------------------------------------- 1 | if (!Ajv) {var Ajv = require("ajv")}; 2 | const ajv = Ajv({allErrors: true, $data: true}); 3 | 4 | /** 5 | * Used to automate adding this outside bit to all command schemas 6 | * and adding their validator to module.exports. 7 | * @param {object} ajv 8 | * @param {object} innerSchema 9 | * @param {string} id 10 | * @return {object} 11 | */ 12 | function makeCommandValidator(ajv, innerSchema, id, validators) { 13 | 14 | ajv.addSchema(innerSchema, id); 15 | 16 | let schema = { 17 | "properties": { 18 | "command": {"$ref": id}, 19 | "robot": {"type": "string"}, 20 | }, 21 | "additionalProperties": false, 22 | "required": ["command", "robot"], 23 | }; 24 | 25 | let result = ajv.compile(schema); 26 | 27 | validators[id] = result; 28 | 29 | return result; 30 | 31 | } 32 | 33 | /** 34 | * Used to determine minimum array size by counting optional arguments. 35 | * @param {Array[]} typeList 36 | */ 37 | function countNull(typeList) { 38 | return typeList.reduce((a, b)=>{return a + (b.indexOf('null')!=-1)}, 0); 39 | } 40 | 41 | /** 42 | * Used to make creating command schemas easier, since they're all very similar. 43 | * @param {string} name 44 | * @param {string[]} parameters 45 | */ 46 | function makeCommandSchema(name, parameters) { 47 | 48 | let schema = { 49 | "properties": { 50 | "name": { 51 | "type": "string", 52 | "pattern": `^${name}$`, 53 | }, 54 | "parameters": { 55 | "type": "array", 56 | "additionalItems": false, 57 | "minItems": 0, 58 | "maxItems": 0, 59 | }, 60 | }, 61 | "additionalProperties": false, 62 | "required": ["name", "parameters"], 63 | }; 64 | 65 | if (parameters.length) { 66 | schema.properties.parameters = { 67 | "type": "array", 68 | "items": parameters.map((s)=>{return {type: s}}), 69 | "additionalItems": false, 70 | "minItems": parameters.length - countNull(parameters), 71 | "maxItems": parameters.length, 72 | }; 73 | } 74 | 75 | return schema; 76 | } 77 | 78 | let doToAreaParams = Array(6).fill('integer').concat([['boolean', 'null']]).concat(Array(2).fill(['integer', 'null'])); 79 | let moveParams = Array(3).fill('integer').concat([['boolean', 'null'], ['integer', 'null']]); 80 | 81 | const commandSchemas = { 82 | scanArea: ['integer', ['integer', 'null']], 83 | viewInventory: [], 84 | equip: [], 85 | dig: doToAreaParams, 86 | place: doToAreaParams, 87 | move: moveParams, 88 | interact: moveParams, 89 | inspect: moveParams, 90 | select: ['integer'], 91 | transfer: Array(5).fill('integer'), 92 | craft: ['string'], 93 | raw: ['string'], 94 | sendPosition: [], 95 | sendComponents: [], 96 | config: [['string', 'null'], ['string', 'integer', 'boolean', 'null']], 97 | } 98 | 99 | const validators = {}; 100 | 101 | for (let id in commandSchemas) { 102 | let subSchema = makeCommandSchema(id, commandSchemas[id]); 103 | makeCommandValidator(ajv, subSchema, id, validators); 104 | } 105 | 106 | try { 107 | module.exports = validators; 108 | } 109 | catch (e) {;} -------------------------------------------------------------------------------- /public/lua/oc/config.lua: -------------------------------------------------------------------------------- 1 | local ser = require("serialization"); 2 | 3 | local configPath = "/home/lib/config.txt"; 4 | 5 | local promptMap = { 6 | robotName = "Enter a name for your robot.", 7 | accountName = "Enter your Roboserver account name.", 8 | serverIP = "Enter the IP address of your Roboserver.", 9 | serverPort = "Enter the port of your Roboserver.", 10 | tcpPort = "Enter the TCP port for your Roboserver.", 11 | posX = "Enter your robot's X coordinate.", 12 | posY = "Enter your robot's Y coordinate.", 13 | posZ = "Enter your robot's Z coordinate.", 14 | orient = "Enter 0 if your robot is facing South, 1 if East, 2 if North, 3 if West.", 15 | raw = "Enter true to allow raw Lua commands to run on this robot, false to ignore them.", 16 | }; 17 | 18 | function readFile(path) 19 | local file = io.open(path, "r"); 20 | if not file then return nil; end 21 | local content = file:read("*all"); 22 | file:close(); 23 | return content; 24 | end 25 | 26 | function getConfig(filePath) 27 | local config = {}; 28 | local content = readFile(filePath); 29 | if content then 30 | config = ser.unserialize(content); 31 | end 32 | return config; 33 | end 34 | 35 | function setConfig(configTable, filePath) 36 | local file = io.open(filePath, "w"); 37 | local configString = ser.serialize(configTable); 38 | file:write(configString); 39 | file:close(); 40 | return configString; 41 | end 42 | 43 | function setConfigOptions(options, path) 44 | local config = getConfig(path); 45 | for key, value in pairs(options) do 46 | config[key] = value; 47 | end 48 | return setConfig(config, path) 49 | end 50 | 51 | function readNotEmpty() 52 | local result = nil; 53 | local value = io.read(); 54 | if value ~= "" then result = value; end 55 | return result; 56 | end 57 | 58 | function readNewConfigOption(prompt, oldValue) 59 | print(prompt); 60 | if oldValue then 61 | print("Current value: " .. oldValue); 62 | end 63 | return readNotEmpty() or oldValue; 64 | end 65 | 66 | function readConfigOptions(options, path) 67 | local oldConfig = getConfig(path); 68 | print("Changing configuration. Just press enter to leave a value unchanged."); 69 | for i, property in pairs(options) do 70 | oldConfig[property] = readNewConfigOption(promptMap[property], oldConfig[property]); 71 | end 72 | return setConfig(oldConfig, path); 73 | end 74 | 75 | function easyConfig(path) 76 | local promptOrder = {"serverIP", "serverPort", "accountName", "robotName", "posX", "posY", "posZ", "orient"}; 77 | local result = readConfigOptions(promptOrder, path); 78 | setAvailableComponents(path); 79 | return result; 80 | end 81 | 82 | local arg = {...}; 83 | if arg[1] and arg[2] and not (arg[1] == 'config') then 84 | setConfigOptions({[arg[1]]=arg[2]}, "/home/lib/config.txt"); 85 | print('Set config option ' .. arg[1] .. ' to ' .. arg[2]); 86 | elseif not (arg[1] == 'config') then 87 | print('Usage: lua /home/lib/config.lua settingName settingValue'); 88 | end 89 | 90 | function setAvailableComponents(path) 91 | local availableComponents = {}; 92 | setConfigOptions({components=availableComponents}, path); 93 | end 94 | 95 | return { 96 | get = getConfig, 97 | set = setConfigOptions, 98 | easy = easyConfig, 99 | path = configPath, 100 | }; -------------------------------------------------------------------------------- /public/lua/oc/moveAndScan.lua: -------------------------------------------------------------------------------- 1 | local scan = require('scanDirection'); 2 | local orient = require('trackOrientation'); 3 | local pos = require('trackPosition'); 4 | local robot = require('robot'); 5 | 6 | local position = pos.get(); 7 | 8 | local M = {}; 9 | 10 | function doNothing() end 11 | 12 | local directionToNoScanMap = { 13 | ["forward"] = doNothing, 14 | ["back"] = doNothing, 15 | ["up"] = doNothing, 16 | ["down"] = doNothing, 17 | }; 18 | 19 | local directionToScanSmallMap = { 20 | ["forward"] = scan.forwardSmall, 21 | ["back"] = scan.backSmall, 22 | ["up"] = scan.upSmall, 23 | ["down"] = scan.downSmall, 24 | }; 25 | 26 | local directionToScanBigMap = { 27 | ["forward"] = function(times) for i=-1,7 do scan.forwardBig(0, times); end end, 28 | ["back"] = function(times) for i=-1,7 do scan.backBig(0, times); end end, 29 | ["up"] = scan.upBig, 30 | ["down"] = scan.downBig, 31 | }; 32 | 33 | local scanTypeMap = { 34 | [0] = directionToNoScanMap, 35 | [1] = directionToScanSmallMap, 36 | [2] = directionToScanBigMap, 37 | }; 38 | 39 | local directionToMoveFunctionMap = { 40 | ["forward"] = pos.forward, 41 | ["back"] = pos.back, 42 | ["up"] = pos.up, 43 | ["down"] = pos.down 44 | }; 45 | 46 | local directionToDetectFunctionMap = { 47 | ["forward"] = robot.detect, 48 | ["back"] = doNothing, 49 | ["up"] = robot.detectUp, 50 | ["down"] = robot.detectDown 51 | }; 52 | 53 | function M.moveAndScan(direction, scanType, times) 54 | local result = not directionToDetectFunctionMap[direction](); 55 | if result then 56 | result = directionToMoveFunctionMap[direction](); 57 | pos.save(); 58 | orient.save(); 59 | scanType = scanType or 0; 60 | scanTypeMap[scanType][direction](times); 61 | end 62 | return result; 63 | end 64 | 65 | -- try to reach the desired X or Z coordinate until it fails 66 | function M.approach(target, current, faceAxis, scanType, times) 67 | if target ~= current then 68 | local dist = target - current; 69 | faceAxis(dist); 70 | for i = 1, math.abs(dist) do 71 | if not M.moveAndScan('forward', scanType, times) then return false; end 72 | end 73 | end 74 | return true; 75 | end 76 | 77 | -- try to reach the desired Y coordinate until it fails 78 | function M.approachY(target, scanType, times) 79 | if target ~= position.y then 80 | 81 | local dist = target - position.y; 82 | 83 | local direction; 84 | if dist > 0 then 85 | direction = 'up'; 86 | else 87 | direction = 'down'; 88 | end 89 | 90 | for i = 1, math.abs(dist) do 91 | if not M.moveAndScan(direction, scanType, times) then return false; end 92 | end 93 | 94 | end 95 | return true; 96 | end 97 | 98 | -- attempt to go to coordinate until we get stuck 99 | function M.to(x, y, z, relative, scanType, times) 100 | if relative then 101 | x, y, z = pos.toAbsolute(x, y, z); 102 | end 103 | local start = { 104 | x = position.x, 105 | y = position.y, 106 | z = position.z, 107 | }; 108 | local xReached = M.approach(x, position.x, orient.faceX, scanType, times); 109 | local zReached = M.approach(z, position.z, orient.faceZ, scanType, times); 110 | local yReached = M.approachY(y, scanType, times); 111 | if xReached and zReached and yReached then 112 | return true; 113 | elseif 114 | start.x == position.x and 115 | start.y == position.y and 116 | start.z == position.z then 117 | return false; 118 | else 119 | return M.to(x, y, z, false, scanType, times); 120 | end 121 | end 122 | 123 | return M; 124 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f0f0f0; 3 | margin: 0px; 4 | overflow: hidden; 5 | font-family: monospace; 6 | } 7 | 8 | .message { 9 | font-size: 14px; 10 | display: inline-block; 11 | padding: 4px; 12 | border-radius: 25px; 13 | margin: 5px 0px; 14 | color: #FFFFFF; 15 | max-width: 100%; 16 | overflow-wrap: break-word; 17 | } 18 | 19 | .input { 20 | background-color: #337ab7; 21 | border: 1px solid #2e6da4; 22 | cursor: pointer; 23 | } 24 | .input:hover { 25 | background-color: #286090; 26 | border: 1px solid #204d74; 27 | } 28 | 29 | .output { 30 | background-color: #eeeeee; 31 | border: 1px solid #cccccc; 32 | color: #555555; 33 | } 34 | 35 | #bottomLeftUI { 36 | position: fixed; 37 | top: 99%; 38 | left: 1%; 39 | transform:translateY(-100%); 40 | } 41 | 42 | #bottomLeftUI > div { 43 | display: inline-block; 44 | } 45 | 46 | #messageContainer { 47 | overflow-y: scroll; 48 | width: 100%; 49 | border: 1px solid #cccccc; 50 | padding: 5px; 51 | } 52 | 53 | #messageContainer:empty { 54 | display: none; 55 | } 56 | 57 | #buttonContainer { 58 | position: absolute; 59 | right: 1%; 60 | top: 1%; 61 | width:270px; 62 | height: 100vh; 63 | } 64 | 65 | #topLeftUI { 66 | position: fixed; 67 | top: 1%; 68 | left: 1%; 69 | } 70 | 71 | .itemStackNumber { 72 | position: absolute; 73 | right: 0px; 74 | bottom: 0px; 75 | background: rgba(0, 0, 0, .5); 76 | border-radius: 50%; 77 | width: 18px; 78 | height: 18px; 79 | } 80 | 81 | .coordinateInput { 82 | max-width: 100%; 83 | text-align: center; 84 | border-width: 0px 1px 1px 0px; 85 | float: left; 86 | } 87 | 88 | .mc-table { 89 | display: inline-block; 90 | background-color: #c6c6c6; 91 | border: 2px solid; 92 | border-color: #dbdbdb #5b5b5b #5b5b5b #dbdbdb; 93 | padding: 0px 6px 6px 6px; 94 | margin: 5px; 95 | } 96 | 97 | .mc-td[data-selected=true] { 98 | border-color: lime; 99 | } 100 | .mc-td:hover { 101 | background-color: #C3C3C3; 102 | } 103 | 104 | .mc-td { 105 | color: white; 106 | display: inline-block; 107 | background-color: #8b8b8b; 108 | border: 2px solid; 109 | border-color: #373737 #ffffff #ffffff #373737; 110 | width: 40px; 111 | height: 40px; 112 | text-align: left; 113 | user-select: none; 114 | overflow: hidden; 115 | } 116 | 117 | .mc-td > div { 118 | cursor: pointer; 119 | width: 100%; 120 | height: 100%; 121 | position: relative; 122 | } 123 | 124 | .hidden { 125 | display: none; 126 | } 127 | 128 | .flex-row { 129 | display: flex; 130 | } 131 | 132 | .flex-col { 133 | display: flex; 134 | flex-direction: column; 135 | align-items: flex-start; 136 | } 137 | 138 | .flex-item { 139 | min-width: 0; 140 | min-height: 0; 141 | } 142 | 143 | .fullWidth { 144 | width: 100%; 145 | } 146 | 147 | .information-panel { 148 | background-color: #f5f5f5; 149 | } 150 | 151 | .input-group-select { 152 | /* rounded corner fix for osx chrome */ 153 | border: 0; 154 | outline: 1px solid #CCC; 155 | outline-offset: -1px; 156 | } 157 | 158 | .inline-block { 159 | display: inline-block; 160 | } 161 | 162 | /* bootstrap modifications */ 163 | 164 | .panel-default { 165 | border-color: #cccccc; 166 | } 167 | 168 | .panel-default > .panel-body { 169 | padding: 5px; 170 | } 171 | 172 | .panel-default > .panel-heading { 173 | padding: 5px; 174 | border-color: #cccccc; 175 | font-weight: bold; 176 | } 177 | 178 | .input-group-addon { 179 | background-color: #f5f5f5; 180 | font-weight: bold; 181 | padding: 5px; 182 | color: #333333; 183 | } -------------------------------------------------------------------------------- /routes/routes.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | var passport = require('passport'); 5 | 6 | var bcrypt = require('bcryptjs'); 7 | 8 | function loggedIn(req, res, next) { 9 | if (req.isAuthenticated()) {next();} 10 | else {res.redirect('/login');} 11 | } 12 | 13 | 14 | // main page where you enter commands 15 | router.get('/', loggedIn, function(req, res) { 16 | res.render('index', {user: req.user}); 17 | }); 18 | 19 | // login and registration page 20 | router.get('/login', function(req, res) { 21 | res.render('login.ejs', {error: false, active: 'login'}); 22 | }); 23 | 24 | router.get('/logout', function(req, res){ 25 | req.logout(); 26 | req.session.destroy(console.error); 27 | res.redirect('/login'); 28 | }); 29 | 30 | function makeLogInOrRedirect(req, res, next) { 31 | return (err, user, info)=>{ 32 | if (err) { return next(err); } 33 | if (!user) { return res.render('login.ejs', {error: 'Login failed.', active: 'login'}); } 34 | req.logIn(user, function(err) { 35 | if (err) { return next(err); } 36 | return res.redirect('/'); 37 | }); 38 | }; 39 | } 40 | 41 | router.post('/login', (req, res, next)=>{ 42 | passport.authenticate('local', makeLogInOrRedirect(req, res, next))(req, res, next); 43 | }); 44 | 45 | const saltRounds = 10; 46 | router.post('/register', (req, res, next)=>{ 47 | var db = req.app.get('db'); 48 | bcrypt.hash(req.body.password, saltRounds).then((hash)=>{ 49 | db.findOne({username: req.body.username}).then((doc)=>{ 50 | console.log(req.body.username, doc) 51 | if (doc) { 52 | return res.render('login.ejs', {error: 'Username unavailable.', active: 'register'}); 53 | } 54 | else { 55 | db.insert({username: req.body.username, passwordHash: hash}).then((newDoc)=>{ 56 | passport.authenticate('local', makeLogInOrRedirect(req, res, next))(req, res, next); 57 | }) 58 | .catch((err)=>{return next(err);}); 59 | } 60 | }) 61 | .catch((err)=>{return next(err);}); 62 | }) 63 | .catch((err)=>{return next(err);}); 64 | }); 65 | 66 | // allows robots to look up crafting recipes 67 | var minecraftRecipes = require('../public/js/recipes/minecraftRecipes.json'); 68 | var OCRecipes = require('../public/js/recipes/OCRecipes.json'); 69 | var allRecipes = minecraftRecipes.concat(OCRecipes); 70 | var recipeSearch = require('../public/js/shared/recipeSearch.js'); 71 | 72 | router.get('/recipe/:recipeName', function(req, res) { 73 | var recipeName = req.params.recipeName; 74 | var recipes = recipeSearch.findRecipeFor(recipeName, allRecipes); 75 | var productRecipes = recipes.map((recipe)=>{return recipeSearch.extractRecipeFor(recipeName, recipe);}); 76 | res.send(productRecipes); 77 | }); 78 | 79 | // allows robots to look up block hardness values 80 | let minecraftData = require('minecraft-data')('1.12.2'); 81 | let namesToHardness = {}; 82 | for (let block of minecraftData.blocksArray) { 83 | namesToHardness[block.name] = block.hardness; 84 | } 85 | 86 | router.get('/namesToHardness', function(req, res) { 87 | res.send(namesToHardness); 88 | }); 89 | 90 | router.get('/blockData/:blockName', function(req, res) { 91 | let blockName = req.params.blockName; 92 | let blockData = minecraftData.findItemOrBlockByName(blockName); 93 | res.send(blockData); 94 | }); 95 | 96 | // let the web client see what version we're using 97 | let version = require('../package').version; 98 | router.get('/version', function(req, res) { 99 | res.send(version); 100 | }); 101 | 102 | module.exports = router; 103 | -------------------------------------------------------------------------------- /public/lua/oc/commandMap.lua: -------------------------------------------------------------------------------- 1 | local sendScan = require('sendScan'); 2 | local int = require('interact'); 3 | local component = require('component'); 4 | local inv = component.inventory_controller; 5 | local robot = require('robot'); 6 | local dta = require('doToArea'); 7 | local mas = require('moveAndScan'); 8 | local craft = require('craft'); 9 | local pos = require('trackPosition'); 10 | 11 | tcp = require('tcp'); -- if this is local, reloading modules fails in commandLoop 12 | local config = require('config'); 13 | 14 | local M = {}; 15 | 16 | M['scanArea'] = function(scanLevel, times) 17 | local result; 18 | if scanLevel == 1 then 19 | for i=-2,5 do 20 | result = sendScan.volume(-3, -3, i, 8, 8, 1, times) 21 | end 22 | elseif scanLevel == 2 then 23 | for i=-1,7 do 24 | result = sendScan.plane(i, times); 25 | end 26 | end 27 | return result; 28 | end; 29 | 30 | M['viewInventory'] = function() 31 | return int.sendInventoryData(-1); 32 | end; 33 | 34 | M['equip'] = function() 35 | inv.equip(); 36 | int.sendInventoryMetadata(-1); 37 | return int.sendSlotData(-1, robot.select()); 38 | end; 39 | 40 | M['dig'] = function(x1, y1, z1, x2, y2, z2, relative, scanLevel, selectionIndex) 41 | return dta.digArea(x1, y1, z1, x2, y2, z2, relative, scanLevel, selectionIndex); 42 | end; 43 | 44 | M['place'] = function(x1, y1, z1, x2, y2, z2, relative, scanLevel, selectionIndex) 45 | return dta.placeArea(x1, y1, z1, x2, y2, z2, relative, scanLevel, selectionIndex); 46 | end; 47 | 48 | M['move'] = function(x, y, z, relative, scanLevel) 49 | return mas.to(x, y, z, relative, scanLevel); 50 | end; 51 | 52 | M['interact'] = function(x, y, z, relative, scanLevel) 53 | return int.interact(x, y, z, relative, scanLevel); 54 | end; 55 | 56 | M['inspect'] = function(x, y, z, relative, scanLevel) 57 | return int.inspect(x, y, z, relative, scanLevel); 58 | end; 59 | 60 | M['select'] = function(slotNum) 61 | return robot.select(slotNum); 62 | end; 63 | 64 | M['transfer'] = function(fromSlot, fromSide, toSlot, toSide, amount) 65 | return int.transfer(fromSlot, fromSide, toSlot, toSide, amount); 66 | end; 67 | 68 | M['craft'] = function(itemName) 69 | return craft.craft(itemName); 70 | end; 71 | 72 | function runInTerminal(commandText) 73 | local file = assert(io.popen(commandText, 'r')); 74 | local output = file:read('*all'); 75 | file:close(); 76 | return output; 77 | end 78 | 79 | M['raw'] = function(commandString) 80 | local raw = config.get(config.path).raw; 81 | local rawBool = (raw == "true" or raw == true) and true or false; 82 | local result; 83 | if rawBool then 84 | local status; 85 | local command = load(commandString, nil, 't', _ENV); 86 | status, result = pcall(command); 87 | else 88 | result = false; 89 | end 90 | return result; 91 | end; 92 | 93 | M['sendPosition'] = function() 94 | return pos.sendLocation(); 95 | end; 96 | 97 | M['sendComponents'] = function() 98 | return tcp.write({['available components']=component.list()}); 99 | end; 100 | 101 | M['config'] = function(optionName, optionValue) 102 | local result; 103 | if optionName and (optionValue ~= nil) then 104 | config.set({[optionName]=optionValue}, config.path); 105 | result = true; 106 | elseif optionName then 107 | local options = {}; 108 | options[optionName] = config.get(config.path)[optionName]; 109 | result = tcp.write({['config']=options}); 110 | else 111 | result = tcp.write({['config']=config.get(config.path)}); 112 | end 113 | return result; 114 | end; 115 | 116 | M['message'] = function(message) 117 | return print(message); 118 | end; 119 | 120 | return M; -------------------------------------------------------------------------------- /public/js/shared/InventoryData.js: -------------------------------------------------------------------------------- 1 | let validators = require('./fromRobotSchemas.js').validators; 2 | /** 3 | * Used to simulate an in-game inventory for the test client 4 | * and to represent the in-game inventory on the web client. 5 | */ 6 | class InventoryData { 7 | 8 | /** 9 | * Used to set the initial state of a simulated inventory from test data. 10 | * @param {object} inventoryMeta 11 | */ 12 | constructor(inventoryMeta) { 13 | validators.inventoryMeta(inventoryMeta); 14 | this.size = inventoryMeta.size; 15 | this.side = inventoryMeta.side; 16 | this.selected = inventoryMeta.selected; 17 | this.slots = {}; 18 | } 19 | 20 | /** 21 | * Used to change the contents of a slot in the inventory. 22 | * @param {object} inventorySlot 23 | */ 24 | setSlot(inventorySlot) { 25 | validators.inventorySlot(inventorySlot); 26 | this.slots[inventorySlot.slotNum] = inventorySlot.contents; 27 | } 28 | 29 | /** 30 | * Used to format slot data in a way the server understands. 31 | * @param {number} slotNum 32 | * @returns {object} 33 | */ 34 | serializeSlot(slotNum) { 35 | let inventorySlot = { 36 | side: this.side, 37 | slotNum: slotNum, 38 | contents: this.slots[slotNum], 39 | }; 40 | return inventorySlot; 41 | } 42 | 43 | /** 44 | * Used to determine whether items can stack. 45 | * Doesn't take into account remaining space in the stacks. 46 | * @param {object} itemStack1 47 | * @param {object} itemStack2 48 | */ 49 | canStack(itemStack1, itemStack2) { 50 | let sameName = itemStack1.name == itemStack2.name; 51 | let noDamage = !itemStack1.damage && !itemStack2.damage; 52 | let noTag = !itemStack1.hasTag && !itemStack2.hasTag; 53 | let result = false; 54 | if (sameName && noDamage && noTag) { 55 | result = true; 56 | } 57 | return result; 58 | } 59 | 60 | /** 61 | * Used to make sure a transfer obeys inventory rules before we execute it. 62 | * @param {object} fromSlot 63 | * @param {object} toSlot 64 | * @param {number} desiredTransferAmount 65 | * @return {number} 66 | */ 67 | validateTransfer(fromSlot, toSlot, desiredTransferAmount) { 68 | if (fromSlot.contents) {validators.inventorySlot(fromSlot);} 69 | if (toSlot.contents) {validators.inventorySlot(toSlot);} 70 | 71 | let finalTransferAmount = 0; 72 | 73 | if (!fromSlot.contents || fromSlot.side !== -1 && toSlot.side !== -1) {;} 74 | else { 75 | let fromItemStack = fromSlot.contents; 76 | if (desiredTransferAmount > fromItemStack.size || desiredTransferAmount < 1) {;} 77 | else if (!toSlot.contents) { 78 | finalTransferAmount = desiredTransferAmount || fromItemStack.size; 79 | } 80 | else { 81 | let toItemStack = toSlot.contents; 82 | if (this.canStack(fromItemStack, toItemStack)) { 83 | var toItemStackSpace = toItemStack.maxSize - toItemStack.size; 84 | if (toItemStackSpace < 1) {;} 85 | else { 86 | let actualTransferAmount; 87 | if (desiredTransferAmount) { 88 | actualTransferAmount = Math.min(desiredTransferAmount, toItemStackSpace); 89 | } 90 | else { 91 | actualTransferAmount = Math.min(fromItemStack.size, toItemStackSpace); 92 | } 93 | finalTransferAmount = actualTransferAmount; 94 | } 95 | } 96 | else { 97 | if (!desiredTransferAmount || desiredTransferAmount == fromItemStack.size) { 98 | finalTransferAmount = fromItemStack.size; 99 | } 100 | } 101 | } 102 | } 103 | return finalTransferAmount; 104 | } 105 | 106 | } 107 | 108 | try {module.exports = InventoryData;} 109 | catch(e) {;} -------------------------------------------------------------------------------- /public/js/client/Robot.mjs: -------------------------------------------------------------------------------- 1 | import {WorldAndScenePoint} from '/js/client/WorldAndScenePoint.mjs'; 2 | 3 | /** 4 | * An organized collection of all important data about a connected robot. 5 | */ 6 | export class Robot { 7 | 8 | /** 9 | * An organized collection of all important data about a connected robot. 10 | */ 11 | constructor() { 12 | this.showInventories = false; 13 | this.inventories = {}; 14 | this.components = []; 15 | } 16 | 17 | /** 18 | * Used to display how much power remains in the GUI. 19 | * @returns {number} 20 | */ 21 | getPower() { 22 | return this.power; 23 | } 24 | 25 | /** 26 | * Receive an updated power value from the robot. 27 | * @param {number} power 28 | */ 29 | setPower(power) { 30 | this.power = power; 31 | } 32 | 33 | /** 34 | * Used to know where to look when this robot is selected, where to put the select highlight, 35 | * and to make sure the robot material isn't overwritten to the stone material by terrain scans. 36 | * @returns {WorldAndScenePoint} 37 | */ 38 | getPosition() { 39 | return this.position; 40 | } 41 | 42 | /** 43 | * Receive an updated location from the robot. 44 | * @param {WorldAndScenePoint} point 45 | */ 46 | setPosition(point) { 47 | this.position = point; 48 | } 49 | 50 | /** 51 | * Needed so we can call methods of the robot's inventories. 52 | * @param {number} side 53 | * @returns {Inventory} 54 | */ 55 | getInventory(side) { 56 | return this.inventories[side]; 57 | } 58 | 59 | /** 60 | * Receive an updated inventory from the robot. Will overwrite existing inventories from the same side. 61 | * @param {number} side 62 | * @param {Inventory} inventory 63 | */ 64 | addInventory(inventory) { 65 | var oldInventory = this.inventories[inventory.inventory.side]; 66 | if (oldInventory) {oldInventory.removeFromDisplay();} 67 | this.inventories[inventory.inventory.side] = inventory; 68 | } 69 | 70 | /** 71 | * Used when we want to toggle the visibility of all a robot's inventories. 72 | * @returns {Inventory[]} 73 | */ 74 | getAllInventories() { 75 | return Object.values(this.inventories); 76 | } 77 | 78 | /** 79 | * Used when we want to remove all a robot's external inventories when it moves. 80 | * @returns {Inventory[]} 81 | */ 82 | getAllExternalInventories() { 83 | return Object.keys(this.inventories) 84 | .filter(side => side != -1) 85 | .map(side => this.inventories[side]); 86 | } 87 | 88 | /** 89 | * Used when we want to remove all a robot's external inventories when it moves. 90 | */ 91 | removeAllExternalInventories() { 92 | var internalInventories = {}; 93 | for (var inventory of Object.values(this.inventories)) { 94 | var side = inventory.getSide() 95 | if (side == -1) {internalInventories[side] = inventory;} 96 | else {inventory.removeFromDisplay();} 97 | } 98 | this.inventories = internalInventories; 99 | } 100 | 101 | /** 102 | * The robot's available components are used to customize the GUI to each robot's abilities. 103 | */ 104 | getComponents() { 105 | return this.components; 106 | } 107 | 108 | /** 109 | * The robot's available components are set when it connects. 110 | * @param {string[]} components 111 | */ 112 | setComponents(components) { 113 | this.components = components; 114 | } 115 | 116 | /** 117 | * Used to tell the UI whether this robot's inventories should be displayed or not. 118 | * @returns {boolean} 119 | */ 120 | getShowInventories() { 121 | return this.showInventories; 122 | } 123 | 124 | /** 125 | * Used when the inventory button is pressed. The UI reads this and changes accordingly. 126 | */ 127 | toggleShowInventories() { 128 | this.showInventories = !this.showInventories; 129 | } 130 | 131 | } -------------------------------------------------------------------------------- /public/lua/oc/doToArea.lua: -------------------------------------------------------------------------------- 1 | local adj = require('adjacent'); 2 | local robot = require('robot'); 3 | tcp = require('tcp'); -- if this is local, reloading modules fails in commandLoop 4 | local pos = require('trackPosition'); 5 | local component = require('component'); 6 | if not component.isAvailable('geolyzer') then 7 | error('Geolyzer not found'); 8 | end 9 | local geolyzer = component.geolyzer; 10 | local int = require('interact'); 11 | 12 | local M = {}; 13 | 14 | function M.getMinPoint(p1, p2) 15 | local minPoint = {x=1e100, y=1e100, z=1e100}; 16 | for axis in pairs(p1) do 17 | minPoint[axis] = math.min(minPoint[axis], p1[axis], p2[axis]) 18 | end 19 | return minPoint; 20 | end 21 | 22 | function M.getMaxPoint(p1, p2) 23 | local maxPoint = {x=-1e100, y=-1e100, z=-1e100}; 24 | for axis in pairs(p1) do 25 | maxPoint[axis] = math.max(maxPoint[axis], p1[axis], p2[axis]) 26 | end 27 | return maxPoint; 28 | end 29 | 30 | function M.generateBoxPoints(corner1, corner2) 31 | local minPoint = M.getMinPoint(corner1, corner2); 32 | local maxPoint = M.getMaxPoint(corner1, corner2); 33 | local points = {}; 34 | for x = minPoint.x, maxPoint.x do 35 | for y = minPoint.y, maxPoint.y do 36 | for z = minPoint.z, maxPoint.z do 37 | table.insert(points, {x=x,y=y,z=z}); 38 | end 39 | end 40 | end 41 | return points; 42 | end 43 | 44 | function M.doToAllPoints(pointList, action) 45 | local success = true; 46 | for i = 1, #pointList do 47 | success = action(pointList[i]) and success; 48 | end 49 | return success; 50 | end 51 | 52 | function M.makeApproachAndDoAction(action, scanType, times) 53 | return function (point) 54 | local moveSuccess = adj.toAdjacent(point, scanType, times); 55 | local actionSuccess = false; 56 | if moveSuccess then 57 | actionSuccess = action(point); 58 | end 59 | return moveSuccess and actionSuccess; 60 | end 61 | end 62 | 63 | function M.makeDoActionToArea(action) 64 | return function (x1, y1, z1, x2, y2, z2, relative, scanType, index, times) 65 | if relative then 66 | x1, y1, z1 = pos.toAbsolute(x1, y1, z1); 67 | x2, y2, z2 = pos.toAbsolute(x2, y2, z2); 68 | end 69 | local p1 = {x=x1, y=y1, z=z1}; 70 | local p2 = {x=x2, y=y2, z=z2}; 71 | local pointList = M.generateBoxPoints(p1, p2); 72 | adj.distanceSort(pos.get(), pointList); 73 | local approachAndDoAction = M.makeApproachAndDoAction(action, scanType, times); 74 | local actionSuccess = M.doToAllPoints(pointList, approachAndDoAction); 75 | if index then -- 0 is true 76 | tcp.write({['delete selection']=index}); 77 | end 78 | return actionSuccess; 79 | end 80 | end 81 | 82 | function M.dig(point) 83 | local robotPos = pos.get(); 84 | local pointSide = 3; -- front 85 | if point.y > robotPos.y then 86 | pointSide = 1; -- top 87 | elseif point.y < robotPos.y then 88 | pointSide = 0; -- bottom 89 | end 90 | local swingSuccess = true; 91 | if component.robot.detect(pointSide) then 92 | swingSuccess = component.robot.swing(pointSide); 93 | if swingSuccess then 94 | tcp.write({['dig success']=point}); 95 | end 96 | end 97 | return swingSuccess; 98 | end 99 | 100 | M.digArea = M.makeDoActionToArea(M.dig); 101 | 102 | function M.place(point) 103 | local robotPos = pos.get(); 104 | local pointSide = 3; -- front 105 | if point.y > robotPos.y then 106 | pointSide = 1; -- top 107 | elseif point.y < robotPos.y then 108 | pointSide = 0; -- bottom 109 | end 110 | local placeSuccess = component.robot.place(pointSide); 111 | if placeSuccess then 112 | local blockData = geolyzer.analyze(pointSide); 113 | blockData.point = point; 114 | tcp.write({['block data']=blockData}); 115 | int.sendSlotData(-1, robot.select()); 116 | end 117 | return placeSuccess; 118 | end 119 | 120 | M.placeArea = M.makeDoActionToArea(M.place); 121 | 122 | return M; -------------------------------------------------------------------------------- /views/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |