codeconnection
Version:
Code minecraft education edition using nodeJS. An attempt to improve on Microsoft Code Connection.
1,480 lines (1,164 loc) • 47.5 kB
JavaScript
///////////////////////////////////////////////////////////
//// This send the commands to the web server /////////////
///////////////////////////////////////////////////////////
const fetch = require('sync-fetch')
const uuid = require('uuid')
const db = require('siennasql')
const { blockNames } = require('./crafting/blockListNames')
const { recipeList } = require('./crafting/masterRecipeList')
const { blocks } = require('./crafting/blockList')
const prompt = require('prompt-sync')()
db.connect(__dirname+'/mc2.db')
////////////////////////////////////////////////////////////
/// This function transmits the command that was generated as a JSON object to the server and awaits the response
/////////////////////////////////////////////////////////////
function transmit(cmd, pieces = 1) {
let commandToSend = command(cmd)
commandToSend.pieces = pieces
let response
try {
response = fetch('http://localhost:3001',
{
method: 'POST',
body: JSON.stringify(commandToSend),
headers: {
'Content-type': 'application/json; charset=UTF-8'
}
}
).json()
} catch (error) {
if (error.code === "ECONNREFUSED") {
console.log("\nThe server has not been started.\n1. Run 'npx codeserver-start' to launch.\n2. Then in Minecraft, execute the '/connect localhost:3000' command.\n3. Then run your code again.\n")
} else {
console.log(error)
}
process.exit(0)
}
//this is where handle the issue of agent being out of range
let canCheck = true
while (response.body && canCheck) {
if (response.body.statusMessage) {
if (response.body.statusMessage === "Minecraft not connected.") {
console.log("\nMINECRAFT IS NOT CONNECTED TO THE SERVER.\n\n1. Run '/connect localhost:3000' on minecraft\n2. Then re-run your code.\n ")
process.exit(0)
}
if (response.body.statusMessage.startsWith("Cannot issue command")) {
if (agent.autocorrect) {
let position = agent.getPosition()
transmit(`/tp @s ${position.x + 5} ${position.y + 7} ${position.z + 5} facing @c`)
response = fetch('http://localhost:3001',
{
method: 'POST',
body: JSON.stringify(commandToSend),
headers: {
'Content-type': 'application/json; charset=UTF-8'
}
}
).json()
} else {
let input = prompt("\n\n----- Agent Error Message -----\nProgram paused, agent is out of range. Reposition the player closer and then hit any key to resume.\nEntering 'c' will terminate the program.\n")
if (input.toLowerCase() === 'c') {
process.exit(0)
}
response = fetch('http://localhost:3001',
{
method: 'POST',
body: JSON.stringify(commandToSend),
headers: {
'Content-type': 'application/json; charset=UTF-8'
}
}
).json()
}
} else {
canCheck = false
}
} else {
canCheck = false
}
}
return response
}
//creates the JSON for minecraft commands to control things
function command(cmd) {
const msg = {
"header": {
"version": 1,
"requestId": uuid.v4(), // Send unique ID each time
"messagePurpose": "commandRequest",
"messageType": "commandRequest"
},
"body": {
"version": 1,
"commandLine": cmd, // Define the command
"origin": {
"type": "player" // Message comes from player
}
}
}
return msg
}
//////////////////////////////////////////////////////////////////////
////these functions are about setting directions and stuff
//////////////////////////////////////////////////////////////////////
const SOUTH = -180 //-180 z decreases
const WEST = -90 //-90 x decreases
const NORTH = 0 //0 z increases
const EAST = 90 //90 x increases
function getDirection(value) {
if (value === SOUTH) {
return 'SOUTH'
} else if (value === NORTH) {
return 'NORTH'
} else if (value === EAST) {
return 'EAST'
} else if (value === WEST) {
return 'WEST'
}
}
/////////////////////////////////////////////////////////////////////////
////// THIS IS THE INVENTORY CONTROLLER /////////////////////////////////
////// and one day crafting machine /////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
class INVENTORY {
//0th element is empty
//this is because the game runs things as slots 1 to 27
constructor() {
//this store the actual items
this.items = []
this.empty()
}
//empties inventory
empty() {
this.items.length = 0
for (let i = 0; i <= 27; i++) {
let item = {}
item.itemName = ''
item.slot = i
item.count = 0
this.items.push(item)
}
}
//match inventory with game inventory
//this needs updating to a better method
//it returns the mineracft name of the items from the game e.g. stone, but not detail for the actual type e.g. granite, diorite etc
sync(slot) {
let start = 1; //inventory slots are numbered 1 to 27 in minecraft
let end = 27;
if (slot !== 'all') {
start = slot
end = slot + 1
}
let inventory = []
if (slot === 'all') {
console.log("Checking the entire inventory takes some time...")
inventory = transmit("inventory") //it is 30% faster to do this way then check individually
} else {
let position = agent.getPosition()
for (let i = start; i < end; i++) {
agent.drop(i, 1, d.UP)
let data = transmit(`/testfor @e[type=item,x=${position.x},y=${position.y},z=${position.z},dx=${2},dy=${2},dz=${2}]`)
let items = data.body.victim
if (items === undefined) {
break
}
let itemName = items[0]
agent.collect()
// let response = transmit('agent getitemdetail ' + i, 2)
// let itemName = JSON.parse(response.body.properties.Result).itemName
let count = 0, space = 64;
if (itemName !== "") {
let response = transmit('agent getitemcount ' + i, 2)
count = JSON.parse(response.body.properties.Result).stackCount
space = 64 - count
}
inventory.push(
{ slot: i, name: itemName, stackCount: count }
)
if (inventory.length % 3 === 0 && slot === 'all') {
console.log(Math.round(inventory.length / 27 * 100) + "% done...")
}
}
}
if (slot === 'all') {
console.log('Inventory complete.')
this.items = inventory
} else {
this.items[slot] = inventory[0]
}
for (let i = 0; i < inventory.length; i++) {
if (inventory[i].itemName !== undefined) {
if (inventory[i].itemName !== '') {
let id = db.run("SELECT NUMERICAL_ID FROM BLOCKS WHERE SCAN_NAMES=?", [inventory[i].itemName])
inventory[i].id = id[0].NUMERICAL_ID
} else {
inventory[i].id = -1
}
}
}
return inventory
}
//reveal stored inventory
list() {
return this.items
}
//move items between slots
transfer() {
}
//pick up items from the ground and file them in the inventory
collect(type) {
// if (type==='all'){
this.sync('all') //anything could have happened
/* } else {
//need to tie the blocks list to the database
//do we have item already
//check that slot and then the next empty slot (hopefully it fills the inventory in a logical order)
for (let i=1; i<this.items.length; i++){
}
}*/
}
//drop from a single slot up to but not exceeding the limit
drop(slot, quantity) {
let count = this.items[slot].count - quantity
if (count < 0) { count = 0 }
this.items[slot].count = count
}
//craft new items from existing ones
//this is a very hacky way of giving the agent the ability to craft things
//it
// gets the desired item
// flattens the inventory
// sees if the inventory matches the requirements for the desired item recipe
// drops the ingredients of the desired item
// clears the dropped ingredients from the area
// inserts the desired object above the agent
// agent breaks the item
// agent collects the broken object and stores it in the inventory
//
//this is return a list of missing items or an empty list if the transaction is successful
craft(desiredItem = 1, quantity = 1) {
quantity = Math.round(quantity)
if (quantity < 1 || isNaN(quantity)) {
console.log('Must craft at least one item.')
return false
}
//this is where we work out if the crafting is possible
//1. is desired item real
if (!blockNames[desiredItem]) {
return false
}
//2. what are the requirements
let itemNumber = blockNames[desiredItem]
// console.log(desiredItem)
let potentialRecipes = recipeList[desiredItem].recipe
// console.log(potentialRecipes)
//3. check inventory
console.log('Checking Inventory')
let stackedInventory = this.direct('inventory')
// console.log(stackedInventory)
//flatten the inventory
let flatInventory = []
for (let i = 1; i < stackedInventory.length; i++) {
let slot = stackedInventory[i]
for (let count = 0; count < slot.count; count++) {
flatInventory.push(blockNames[slot.itemName])
}
}
//4. validate requirements
//setup a null variable to store the recipe that could be made with the current items in the inventory
let successfulRecipe = null;
//iterate over the potential recipes for the desired object and choose the first successful one
for (let i = 0; i < potentialRecipes.length; i++) {
let recipe = potentialRecipes[i]
// console.log(flatInventory)
let hasEnoughMaterials = recipe.every(val => flatInventory.includes(val)
&& flatInventory.filter(el => el === val).length
>=
recipe.filter(el => el === val).length
);
//if found a good recipe, then save it and move on
if (hasEnoughMaterials) {
successfulRecipe = recipe
break //choose the first successful recipe
}
}
//print results
if (successfulRecipe !== null) {
// console.log('good')
// console.log(successfulRecipe)
} else {
console.log('Cannot craft the desired item')
//need to print list of missing items
let missingItems = []
let missingItemsText = []
let sampleRecipe = potentialRecipes[0]
for (let i = 0; i < sampleRecipe.length; i++) {
if (flatInventory.indexOf(sampleRecipe[i]) !== -1) {
flatInventory[flatInventory.indexOf(sampleRecipe[i])] = undefined
} else {
missingItems.push(sampleRecipe[i])
missingItemsText.push(blockNames[sampleRecipe[i]])
}
}
console.log(`You are missing ${missingItemsText} (${missingItems})`)
return missingItems
}
//5. work out most of requested item that can be created (up to the value requested)
//and subsequently adjust (if required) the quantity limit
let canCreate = 0
for (let i = 0; i < quantity; i++) {
let made = true
for (let j = 0; j < successfulRecipe.length; j++) {
if (flatInventory.indexOf(successfulRecipe[j]) !== -1) {
flatInventory[flatInventory.indexOf(successfulRecipe[j])] = undefined
} else {
made = false
}
}
if (made) {
canCreate++
} else {
break
}
}
console.log(`Can create ${canCreate}/${quantity} of the requested item`)
quantity = canCreate
//5.5 work out the list of drops
//generate list of required items
let allIngredients = []
for (let i = 0; i < quantity; i++) {
allIngredients = allIngredients.concat(successfulRecipe)
}
allIngredients.sort()
let droppingList = []
droppingList.length = 28
droppingList.fill(0)
for (let i = 0; i < allIngredients.length; i++) {
//find right list
let slot = 0
while (stackedInventory[slot].itemName !== blockNames[allIngredients[i]]) {
slot++
}
droppingList[slot]++
stackedInventory[slot].count--
if (stackedInventory[slot].count === 0) {
stackedInventory[slot].itemName = ''
}
}
// console.log(droppingList)
//6. drop the items
//this is where the items to be removed are dropped/lost
// this.dropAll()
for (let i = 0; i < droppingList.length; i++) {
if (droppingList[i] !== 0) {
let drop = transmit(`agent drop ${i} ${droppingList[i]} up`)
}
}
//6.5 adjust the cached inventory
//7. delete dropped items
//this is how all the ejected items are removed
let p = this.getPosition()
let response = transmit(`/kill @e[type=item,x=${p.x - 2},y=${p.y - 2},z=${p.z - 2},dx=5,dy=5,dz=5]`)
//8. spawn the requested item
for (let i = 0; i < quantity; i++) {
//this is how the entity is given an object in exchange
//create the object above, destory it and collect it
// console.log(`/setblock ${p.x} ${p.y + 1} ${p.z} ${blockNames[desiredItem+'']}`)
response = transmit(`/setblock ${p.x} ${p.y + 1} ${p.z} ${blockNames[desiredItem + '']}`)
this.destroy('up')
//need to run collect twice for some reason (I think it is a timing thing in Minecraft)
this.collect()
this.collect()
}
return true
}
}
let agentInventory = new INVENTORY()
/////////////////////////////////////////////////////////////////////////
//////// AGENT STATS MACHINE ////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
let timer = new Date()
let agentStats = {
move: 0,
turn: 0,
inventory: 0,
place: 0,
destroy: 0,
collect: 0,
getPosition: 0,
inspect: 0,
till: 0,
attack: 0,
drop: 0,
detect: 0,
detectRedstone: 0,
transfer: 0,
face: 0,
storePosition: 0,
restorePosition: 0,
radar: 0
}
/////////////////////////////////////////////////
////// THIS IS THE AGENT ////////////////////////
/////////////////////////////////////////////////
class AGENT {
constructor() {
this.x;
this.y;
this.z;
this.direction;
this.autocorrect = true
}
//move the player to be close to the player if distance too far to execute command
followed(tf) {
if (tf) {
this.autocorrect = true
} else {
this.autocorrect = false
}
}
//this can run any slash command sent through
direct(cmd, count = 1) {
let response = transmit(cmd, count)
return response
}
//get agent position in world and store the coordinates
getPosition() {
agentStats.getPosition++
let response = transmit('/querytarget @c', 1)
// console.log(response)
let data = JSON.parse(response.body.details)[0]
let position = data.position
//return p
/* let response = transmit('agent getposition')
if (response.body.properties !== undefined) {
if (response.body.properties.Result === '{"success":false}') {
return false
}
}
*/
let x = position.x
let y = position.y
let z = position.z
let direction = response.body["y-rot"]
return { x: x, y: y, z: z, direction: getDirection(direction) }
}
getPlayerPosition() {
let response = transmit('/querytarget @s', 1)
let data = JSON.parse(response.body.details)[0]
let p = data.position
return p
}
//get direction faced
orientation() {
let response = transmit('agent getposition')
this.direction = response.body["y-rot"]
return getDirection(this.direction)
}
//move forward
move(direction = 'forward', distance = 1) {
agentStats.move++
if (!validateDirection(direction)) {
if (!isNaN(direction)) {
distance = Math.round(direction)
if (distance < 1) { distance = 1 }
direction = 'forward'
} else {
console.log("Invalid move command.")
return false
}
}
let success = true;
for (let i = 0; i < distance; i++) {
let response = transmit('agent move ' + direction, 2)
success = JSON.parse(response.body.properties.Result).success
if (!success) {
break; //take take QCAA
}
}
return success
}
//turn left or right
turn(direction = 'left', times = 1) {
agentStats.turn++
if (direction !== 'left' && direction !== 'right') {
if (!isNaN(direction)) {
times = Math.round(direction)
if (times < 1) { times = 1 }
direction = 'left'
} else {
console.log("Invalid turn command.")
return false
}
}
let success = true
for (let i = 0; i < times; i++) {
let response = transmit('agent turn ' + direction)
// success = JSON.parse(response.body.properties.Result).success
// if (!success) {
// break; //take take QCAA
// }
}
return success
}
//detect object
inspect(direction = 'forward') {
agentStats.inspect++
if (!validateDirection(direction)) {
console.log("Invalid inspect command")
return false
}
let response = transmit('agent inspect ' + direction, 2)
let blockName = JSON.parse(response.body.properties.Result).blockName
return blockName
}
//destroy object in direction
destroy(direction = "forward") {
agentStats.destroy++
if (!validateDirection(direction)) {
console.log("Invalid destroy command")
return false
}
let response = transmit('agent destroy ' + direction, 2)
let success = JSON.parse(response.body.properties.Result).success
return success
}
//collect 'all' objects or object of specific name that is near the agent
collect(type = 'all') {
agentStats.collect++
let response;
if (type === 'all') {
response = transmit('agent collect ' + type, 2)
} else {
response = transmit('agent collect ' + type, 2)
}
let success = JSON.parse(response.body.properties.Result).success
return success
}
//returns full inventory
//or that for a single slot
//takes 5-6 seconds
inventory(slot = 'all') {
agentStats.inventory++
let inventory;
if (slot === 'all') {
inventory = agentInventory.sync('all')
} else {
if (isNaN(slot)) {
slot = 1
} else {
slot = Math.round(slot)
if (slot < 1) {
slot = 1
}
if (slot > 27) {
slot = 27
}
}
inventory = agentInventory.sync(slot)
}
return inventory
}
//till the soil (agent has an automatic hoe-ing ability)
till(direction = "forward") {
agentStats.till++
if (direction !== 'forward' && direction !== 'back' && direction !== 'left' && direction !== 'right') {
console.log("Invalid till command")
return false
}
let response = transmit('agent till ' + direction, 2)
let success = JSON.parse(response.body.properties.Result).success
return success
}
//attack
attack(direction = "forward") {
agentStats.attack++
if (!validateDirection(direction)) {
console.log("Invalid attack command")
return false
}
let response = transmit('agent attack ' + direction, 2)
let success = JSON.parse(response.body.properties.Result).success
return success
}
//drops all items in the direction specified
dropAll(direction = 'forward') {
agentStats.drop++
if (!validateDirection(direction)) {
console.log("Invalid dropall command")
return false
}
let response = transmit('agent dropall ' + direction, 2)
let success = JSON.parse(response.body.properties.Result).success
return success
}
//this works ok
//seems to want to drop from every slot containing items of the same type
drop(slot = 1, quantity = 1, direction = 'forward') {
agentStats.drop++
if (isNaN(slot) || isNaN(quantity) || !validateDirection(direction)) {
console.log("Invalid drop command")
return false
}
slot = Math.round(slot)
if (slot < 1) { slot = 1 }
if (slot > 27) { slot = 27 }
quantity = Math.round(quantity)
if (quantity < 1) { quantity = 1 }
let response = transmit('agent drop ' + slot + " " + quantity + " " + direction, 2)
let success = JSON.parse(response.body.properties.Result).success
return success
}
//places a block
place(direction = 'forward', slot = 1) {
agentStats.place++
if (!validateDirection(direction)) {
if (!isNaN(slot)) {
slot = Math.round(slot)
if (slot < 1) { slot = 1 }
if (slot > 27) { slot = 27 }
direction = 'forward'
} else {
console.log("Invalid place command.")
return false
}
}
let response = transmit('agent place ' + slot + " " + direction, 2)
let success = JSON.parse(response.body.properties.Result).success
return success
}
//use is a synonym for 'place' because it makes more sense sometimes
use(direction = 'forward', slot = 1) {
place(direction, slot)
}
//detects if there is a colliable (blocking) block in the direction
detect(direction = "forward") {
agentStats.detect++
if (!validateDirection(direction)) {
console.log('Invalid detect command')
return false
}
let response = transmit("agent detect " + direction, 2)
let result = JSON.parse(response.body.properties.Result).result
return result
}
//return the block name
inspect(direction = "forward") {
agentStats.inspect++
if (!validateDirection(direction)) {
console.log('Invalid inspect command')
return false
}
let response = transmit("agent inspect " + direction, 2)
let success = JSON.parse(response.body.properties.Result).success
if (success) {
let blockName = JSON.parse(response.body.properties.Result).blockName
// console.log(blockName)
let fullName;
let query = "SELECT * FROM BLOCKS WHERE AGENT_INSPECT = ? OR LEGACY_ITEM_ID=? AND LEGACY_ITEM_ID IS NOT NULL ORDER BY DATA_VALUE ASC"
let list = db.run(query, [blockName, blockName])
if (list.length > 1) {
let position = agent.getPosition()
// console.log(position.direction)
if (position.direction === 'SOUTH') {
if (direction === d.FORWARD) { position.z-- }
if (direction === d.BACK) { position.z++ }
if (direction === d.LEFT) { position.x-- }
if (direction === d.RIGHT) { position.x++ }
if (direction === d.UP) { position.y++ }
if (direction === d.DOWN) { position.y-- }
}
if (position.direction === 'NORTH') {
if (direction === d.FORWARD) { position.z++ }
if (direction === d.BACK) { position.z-- }
if (direction === d.LEFT) { position.x++ }
if (direction === d.RIGHT) { position.x-- }
if (direction === d.UP) { position.y++ }
if (direction === d.DOWN) { position.y-- }
}
if (position.direction === 'EAST') {
if (direction === d.FORWARD) { position.x-- }
if (direction === d.BACK) { position.x++ }
if (direction === d.LEFT) { position.z++ }
if (direction === d.RIGHT) { position.z-- }
if (direction === d.UP) { position.y++ }
if (direction === d.DOWN) { position.y-- }
}
if (position.direction === 'WEST') {
if (direction === d.FORWARD) { position.x++ }
if (direction === d.BACK) { position.x-- }
if (direction === d.LEFT) { position.z-- }
if (direction === d.RIGHT) { position.z++ }
if (direction === d.UP) { position.y++ }
if (direction === d.DOWN) { position.y-- }
}
for (let i = 0; i < list.length; i++) {
// console.log(i, list[i])
let outcome = transmit(`/testforblock ${position.x} ${position.y} ${position.z} ${blockName} ${list[i].DATA_VALUE}`)
// console.log(outcome)
if (outcome.body.statusMessage.startsWith("Success")) {
blockName = list[i].NUMERICAL_ID
fullName = list[i].NAME
i = list.length
// break
}
}
} else if (list.length === 1) {
blockName = list[0].NUMERICAL_ID
fullName = list[0].NAME
} else {
return false3
}
let name = blockNames[blockName]
let id = blockName
if (name === undefined) {
id = JSON.parse(response.body.properties.Result).blockName
name = blockNames[name]
if (name === undefined) {
name = ''
}
}
if (isNaN(name)) {
return { fullName: fullName, name: name, id: id }
} else {
return { fullName: fullName, name: id, id: name }
}
}
return success
}
//is block recieving redstone power in the specified direction
detectRedstone(direction = 'forward') {
agentStats.detectRedstone++
if (!validateDirection(direction)) {
console.log('Invalid inspect command')
return false
}
let response = transmit("agent detectredstone " + direction, 2)
let result = JSON.parse(response.body.properties.Result).result
return result
}
//transfer inventory from one slot to another
transfer(from = 1, to = 1, quantity = 0) {
agentStats.transfer++
if (isNaN(from) || isNaN(to) || isNaN(quantity)) {
console.log("Invalid transfer command")
return false
}
from = Math.round(from)
to = Math.round(to)
quantity = Math.round(quantity)
if (from < 1 || from > 27) { return false }
if (to < 1 || to > 27) { return false }
let response = transmit("agent transfer " + from + " " + quantity + " " + to, 2)
let success = JSON.parse(response.body.properties.Result).success
return success
}
// tp to player
tpToPlayer() {
let position = agent.getPosition()
let response = transmit("/tp @c @s", 1)
let newPosition = agent.getPosition()
let dx = Math.abs(position.x - newPosition.x)
let dy = Math.abs(position.y - newPosition.y)
let dz = Math.abs(position.z - newPosition.z)
agentStats.move = agentStats.move + dx + dy + dz - 2
return true;
}
//custom destroy, collect and move command (these often happen in this sequence)
destroyCollectMove() {
this.destroy()
this.collect()
this.move()
}
//face in a certain direction
face(direction = NORTH) {
agent.face++
if (direction !== NORTH && direction !== SOUTH && direction !== EAST && direction !== WEST) {
console.log("Invalid face command")
return false
}
let response = transmit('agent getposition')
while (response.body["y-rot"] !== direction) {
this.turn('left')
response = transmit('agent getposition')
}
}
//same as position() but makes more sense to a person
storePosition() {
agentStats.storePosition++
let response = transmit('agent getposition')
this.x = response.body.position.x
this.y = response.body.position.y
this.z = response.body.position.z
this.direction = response.body["y-rot"]
return { x: this.x, y: this.y, z: this.z, direction: getDirection(this.direction) }
}
//this puts the agent back in the last stored position
restorePosition() {
agentStats.restorePosition++
let position = agent.getPosition()
let response = transmit(`/tp @c ${this.x} ${this.y} ${this.z}`)
let dx = Math.abs(position.x - this.x)
let dy = Math.abs(position.y - this.y)
let dz = Math.abs(position.z - this.z)
agentStats.move = agentStats.move + dx + dy + dz - 1
response = transmit('agent getposition')
while (response.body["y-rot"] !== this.direction) {
this.turn('left')
response = transmit('agent getposition')
}
}
//have the agent craft things // experimental
/*craft(desiredItem = 1, quantity = 1) {
return agentInventory.craft(desiredItem, quantity)
}*/
radar(target) {
agentStats.radar++
if (isNaN(target)) {
return []
}
let position = agent.getPosition()
let px = position.x
let y = position.y
let pz = position.z
let sx = Math.floor(px / 16)
let sz = Math.floor(pz / 16)
let dx = 1, dz = 1;
if (px - sx * 16 >= 8) {
dx = 0
}
if (pz - sz * 16 >= 8) {
dz = 0
}
let colourCode = db.run('SELECT RADAR FROM BLOCKS WHERE NUMERICAL_ID=?', [target])[0]
if (colourCode === null) { return false }
colourCode = colourCode.RADAR
let height;
let potentials = []
//run the queries
for (let x = sx - dx; x <= sx + 1 - dx; x++) {
for (let z = sz - dz; z <= sz + 1 - dz; z++) {
let data = agent.direct(`/getchunkdata overworld ${x} ${z} ${y + 1}`, 1)
let input = data.body.data.replaceAll("\"", '')
let line = input.split(',')
//unzip stage
let line2 = []
for (let i = 0; i < line.length; i++) {
let code = line[i]
if (code.indexOf('*') !== -1) {
let values = code.split('*')
for (let j = 0; j <= values[1]; j++) {
line2.push(values[0])
}
} else {
line2.push(code)
}
}
//un-dictionary stage
for (let i = 0; i < line2.length; i++) {
if (!isNaN(line2[i])) {
line2[i] = line2[line2[i]]
}
}
//filter results to match search
for (let i = 0; i < line2.length; i++) {
if (height === undefined) {
let test = line2[i].substring(4, 6)
const buffer = Buffer.from(test, 'base64');
const bufString = buffer.toString('hex');
let value = parseInt(bufString, 16)
if (value === y) {
height = test
}
}
if (height === undefined) {
//not found correct hheight yet so must be an air block
line2[i] = ''
} else {
//delete record if not a match
if (!line2[i].endsWith(height)) {
line2[i] = ''
} else {
if (!line2[i].startsWith(colourCode)) {
line2[i] = ''
} else {
//potential match
let detail = {}
detail.x = i % 16 + x * 16
detail.y = y
detail.z = Math.floor(i / 16) + z * 16
potentials.push(detail)
}
}
}
}
}
}
//remove any potentials that are more that 15 blocks away from the co-ordinates in any direction
for (let i = potentials.length - 1; i >= 0; i--) {
let c = potentials[i]
if (Math.abs(c.x - px) > 15 || Math.abs(c.z - pz) > 15) {
potentials.splice(i, 1)
}
}
return potentials
}
getStats() {
let total = 0
Object.keys(agentStats).forEach((key) => {
total += agentStats[key]
})
agentStats.total = total
let time = new Date()
agentStats.time = (time.getTime() - timer.getTime()) / 1000
return agentStats
}
give(block, slot = 1, quantity = 1, index=0) {
let info = db.run("SELECT GIVE FROM BLOCKS WHERE NUMERICAL_ID=?", [block])
if (info.length > 1) {
console.log("\nUh oh, duplicate id found in database.\nTrying to give you the first returned item and hoping it is correct.\nThe developer sknow these problems exist and will try and fix them over time.\nSorry for the inconvenience.\n")
console.log("\nIf the delivered block isn't right, add a fourth parameter to the 'give' as an integer greater than or equal to 1 and less than "+info.length+" for this item and that will attempt to place a different item in the inventory.\n\n")
//console.log(block)
// return false
}
if (isNaN(slot)) { slot = 1 }
if (isNaN(quantity)) { quantity = 1 }
slot = Math.round(slot)
quantity = Math.round(quantity)
if (slot < 1) { slot = 1 }
if (slot > 27) { slot = 27 }
if (quantity < 1) { quantity = 1 }
if (quantity > 64) { quantity = 64 }
//game names slots 1 to 27 but the command expect 0 to 26
slot--
let GIVE = info[index].GIVE
let itemName = GIVE
let data = ''
if (itemName.indexOf(',') !== -1) {
let list = itemName.split(",")
itemName = list[0]
data = list[1]
}
let response = transmit(`/replaceitem entity @c slot.inventory ${slot} ${itemName} ${quantity} ${data}`, 1)
//console.log(response)
return true
}
}
//creates the agent
let agent = new AGENT()
//exports the agent so it can be required elsewhere
exports.agent = agent;
/////////////////////////////////////////
/// Directions //////////////////////////
/////////////////////////////////////////
//this class makes it easier for the user to put in valid directions
class Directions {
constructor() {
this.FORWARD = 'forward'
this.BACK = 'back'
this.LEFT = 'left'
this.RIGHT = 'right'
this.UP = 'up'
this.DOWN = 'down'
}
}
let d = new Directions()
let directionList = [
'forward',
'back',
'left',
'right',
'up',
'down'
]
function validateDirection(direction) {
if (directionList.includes(direction)) {
return true
}
return false
}
//////////////////////////////////////////
//// SPECIAL FUNCTIONS ///////////////////
//////////////////////////////////////////
class Special {
constructor() {
}
about() {
console.log(`
createHeightMap()\nThis centers the player on a 256 x 256 world (old world).
The function a heightmap of the world as an object containing and array of arrays of objects.
It contains the heights, a colour code that matches different items in the database for the
highest block in the world at that location in a 256 x 256 array of objects.
`)
}
createHeightMap() {
const fs = require('fs')
//move player to center of world
let step = 128
do {
agent.tpToPlayer()
let playerpos = agent.getPlayerPosition()
agent.direct(`/tp @s ${playerpos.x - step} ${127} ${playerpos.z}`)
console.log(`Stepping player east ${step}`)
step = step / 2
} while (step >= 1)
step = 128
do {
agent.tpToPlayer()
let playerpos = agent.getPlayerPosition()
agent.direct(`/tp @s ${playerpos.x} ${127} ${playerpos.z - step}`)
console.log(`Stepping player south ${step}`)
step = step / 2
} while (step >= 1)
console.log("Centering player")
agent.tpToPlayer()
let playerpos = agent.getPlayerPosition()
agent.direct(`/tp @s ${playerpos.x + 128} ${127} ${playerpos.z + 128}`)
agent.tpToPlayer()
//spin around
console.log("Visual 360 degree scan")
playerpos = agent.getPlayerPosition()
for (let y = 0; y < 360; y += 5) {
agent.direct(`/tp @s ${playerpos.x} ${120} ${playerpos.z} facing ${playerpos.x + Math.sin(y / 360 * Math.PI) * 10} ${110} ${playerpos.z + Math.cos(y / 360 * Math.PI) * 10}`)
}
let maxX = -1000000
let minX = 1000000
let maxZ = -10000000
let minZ = 10000000
let chunkQuery = agent.direct("/getchunks overworld")
let chunks = JSON.parse(chunkQuery.body.data)
for (let i = 0; i < chunks.length; i++) {
let coord = chunks[i]
if (coord[0] > maxX) { maxX = coord[0] }
if (coord[0] < minX) { minX = coord[0] }
if (coord[1] > maxZ) { maxZ = coord[1] }
if (coord[1] < minZ) { minZ = coord[1] }
}
console.log("Please wait. Getting ready to collect world heightmap. Please do not move the player until writing data is complete.")
// console.log(minX, minZ)
// console.log(maxX, maxZ)
let pos = agent.getPlayerPosition()
// console.log(pos)
let avX = Math.round((minX + maxX) / 2)
let avZ = Math.round((minZ + maxZ) / 2)
minX = avX
maxX = avX
minZ = avZ
maxZ = avZ
let input = '';//"AAAAAQ*255"
while (input !== "AAAAAQ*255") {
let data = agent.direct(`/getchunkdata overworld ${minX} ${avZ} 127`, 1)
input = data.body.data.replaceAll("\"", '')
if (input !== "AAAAAQ*255") {
minX--
} else {
minX++
}
}
input = '';//"AAAAAQ*255"
while (input !== "AAAAAQ*255") {
let data = agent.direct(`/getchunkdata overworld ${maxX} ${avZ} 127`, 1)
input = data.body.data.replaceAll("\"", '')
if (input !== "AAAAAQ*255") {
maxX++
} else {
maxX--
}
}
input = '';//"AAAAAQ*255"
while (input !== "AAAAAQ*255") {
let data = agent.direct(`/getchunkdata overworld ${avX} ${minZ} 127`, 1)
input = data.body.data.replaceAll("\"", '')
if (input !== "AAAAAQ*255") {
minZ--
} else {
minZ++
}
}
input = '';//"AAAAAQ*255"
while (input !== "AAAAAQ*255") {
let data = agent.direct(`/getchunkdata overworld ${avX} ${maxZ} 127`, 1)
input = data.body.data.replaceAll("\"", '')
if (input !== "AAAAAQ*255") {
maxZ++
} else {
maxZ--
}
}
let map = []
for (let i = 0; i < 256; i++) {
let lmap = [].fill(0)
map.push(lmap)
}
let offsetX = 0
let offsetZ = 0
if (minX < 0) { offsetX = minX * -1 * 16 }
if (minZ < 0) { offsetZ = minZ * -1 * 16 }
if (minX > 0) { offsetX = -minX * 16 }
if (maxZ > 0) { offsetZ = -minZ * 16 }
let output = ''
for (let x = minX; x <= maxX; x++) {
for (let z = minZ; z <= maxZ; z++) {
let data = agent.direct(`/getchunkdata overworld ${x} ${z} 127`, 1)
let input = data.body.data.replaceAll("\"", '')
if (input !== "AAAAAQ*255") {
// console.log(`/getchunkdata overworld ${x} ${z} 127`)
console.log(`Getting chunk at ${x} ${z}`)
} else {
console.log('error', x, z)
}
let line = input.split(',')
//unzip stage
let line2 = []
for (let i = 0; i < line.length; i++) {
let code = line[i]
if (code.indexOf('*') !== -1) {
let values = code.split('*')
for (let j = 0; j <= values[1]; j++) {
line2.push(values[0])
}
} else {
line2.push(code)
}
}
//un-dictionary stage
for (let i = 0; i < line2.length; i++) {
if (!isNaN(line2[i])) {
line2[i] = line2[line2[i]]
}
}
let px = x * 16
let pz = z * 16
//filter results to match search
for (let i = 0; i < line2.length; i++) {
let test = line2[i].substring(4, 6)
const buffer = Buffer.from(test, 'base64');
const bufString = buffer.toString('hex');
let value = parseInt(bufString, 16)
let dx = i % 16
let dz = Math.floor(i / 16)
let posx = pz + dz + offsetZ
let posz = px + dx + offsetX
let posy = value
let colorcode = line2[i].substring(0, 4)
map[posx][posz] = { height: posy, colorcode }
}
}
}
let world = {}
world.offsetX = -offsetZ
world.offsetZ = -offsetX
world.map = map
console.log("Writing data to map.json")
fs.writeFileSync(__dirname+"/map.json", JSON.stringify(world))
console.log("Done")
return world;
}
}
////////////////////////////////////////
/// ALL THE EXPORTS ////////////////////
////////////////////////////////////////
exports.d = d
exports.blocks = blocks
exports.bn = blockNames
exports.special = new Special()