Do you think I could just leave this part blank and it'd be okay? We're just going to replace the whole thing with a header image anyway, right?
You are not logged in.
Pages: 1
BlockHandler is a simple to use library that handles block placements.
It's goal is to allow you to focus purely on the functionality of your bot, rather than needing to deal with details such as max placement speed, missed blocks, and desynchronisations.
It does this by acting as an interface layer between your bot and the connection to EE, making it seem like you're bot has direct control over the world by hiding things like latency and connection problems. This means that when you place a block it is in the local copy of the world instantly, even if it takes a few hundred milliseconds to actually place it, and that when you place a block it is guaranteed to be placed in the world, even if it takes multiple repeated messaged.
For a while I have been meaning to rewrite BlockHandler, and I've finally got around to it.
Version 2 now uses more specialised data structures for the block queues so that it should generally perform better, especially when placing large numbers of blocks all at once.
This also allowed me to add priorities, which lets you prioritise some tasks over others, even if they happened in different orders.
// BlockHandler.js Version 2.1 (by LukeM)
class TreeNode {
constructor(value, parent) {
this.value = value;
this.depth = 0;
this.parent = parent;
this.before = null;
this.after = null;
}
get beforeDepth() {
return (this.before !== null ? this.before.depth + 1 : 0);
}
get afterDepth() {
return (this.after !== null ? this.after.depth + 1 : 0);
}
}
class BalancedTree {
constructor(comparer) {
this.comparer = comparer;
this.root = null;
this.first = null;
}
add(value) {
if (this.root === null) {
this.root = new TreeNode(value, null);
this.first = value;
} else {
if (this.comparer(value, this.first) > 0) this.first = value;
let child = this.root, node, before, depth = 0, difference;
while (child !== null) {
node = child;
before = this.comparer(value, node.value) > 0;
child = before ? node.before : node.after;
}
if (before) node.before = new TreeNode(value, node);
else node.after = new TreeNode(value, node);
while (node !== null && node.depth < ++depth) {
node.depth = depth;
difference = node.beforeDepth - node.afterDepth;
if (difference > 1 || difference < -1) {
let rotate = (root, isLeft) => {
let pivot = isLeft ? root.before : root.after;
if (isLeft) {
root.before = pivot.after;
if (root.before !== null) root.before.parent = root;
pivot.after = root;
} else {
root.after = pivot.before;
if (root.after !== null) root.after.parent = root;
pivot.before = root;
}
pivot.parent = root.parent;
if (root.parent === null) this.root = pivot;
else if (root.parent.before === root) root.parent.before = pivot;
else root.parent.after = pivot;
root.parent = pivot;
root.depth = Math.max(root.beforeDepth, root.afterDepth);
pivot.depth = Math.max(pivot.beforeDepth, pivot.afterDepth);
}
if (difference > 0 && child.beforeDepth < child.afterDepth ||
difference < 0 && child.beforeDepth > child.afterDepth)
rotate(child, difference < 0);
rotate(node, difference > 0);
return;
}
child = node;
node = node.parent;
}
}
}
remove(value) {
let node = this.root, last = null, found, compared, before, direction, moved = false;
while (node !== null && (compared = this.comparer(value, node.value)) !== 0) {
before = compared > 0;
node = before ? node.before : node.after;
}
if (node === null) return false;
found = node;
direction = found.beforeDepth > found.afterDepth;
node = direction ? node.before : node.after;
while (node !== null) {
last = node;
node = direction ? node.after : node.before;
if (node !== null) moved = true;
}
if (last !== null) {
if (this.comparer(found.value, this.first) === 0) this.first = last.value;
node = direction ? last.before : last.after;
found.value = last.value;
if (direction !== moved) last.parent.before = node;
else last.parent.after = node;
if (node !== null) node.parent = last.parent;
node = last;
} else if (found.parent !== null) {
node = found;
if (this.comparer(found.value, this.first) === 0) this.first = found.parent.value;
if (before) found.parent.before = null;
else found.parent.after = null;
} else {
this.root = null;
this.first = null;
}
if (node) {
let oldDepth;
do {
node = node.parent;
oldDepth = node.depth;
node.depth = Math.max(node.beforeDepth, node.afterDepth);
} while (node.parent !== null && node.depth !== oldDepth);
}
return true;
}
find(value) {
let node = this.root, compared;
while (node !== null && (compared = this.comparer(value, node.value)) !== 0)
node = compared > 0 ? node.before : node.after;
return node !== null ? node.value : null;
}
}
class LinkedNode {
constructor(value) {
this.value = value;
this.next = null;
}
}
class LinkedList {
constructor() {
this.first = null;
this.last = null;
}
add(value) {
let last = new LinkedNode(value);
if (this.last === null) this.first = last;
else this.last.next = last;
this.last = last;
}
remove() {
let first = this.first;
this.first = first.next;
if (this.first === null) this.last = null;
return first.value;
}
}
class Block {
constructor(id, ...args) {
this.id = id;
this.args = args;
}
equals(block) {
return this.id === block.id &&
this.args.length === block.args.length &&
this.args.every((a, i) => a === block.args[i]);
}
}
class BlockPlacement {
constructor(l, x, y, block, prev, priority, blockNo) {
this.l = l;
this.x = x;
this.y = y;
this.block = block;
this.prev = prev;
this.priority = priority;
this.blockNo = blockNo;
}
get message() {
return ['b', this.l, this.x, this.y, this.block.id, ...this.block.args];
}
}
export default class BlockHandler {
constructor(con, botID, width, height, BPS = 100) {
this.con = con;
this.botID = botID;
this.width = width;
this.height = height;
this.BPS = BPS;
this.blocks = new Array(this.width);
for (let x = 0; x < this.width; x++) {
this.blocks[x] = new Array(this.height);
}
this.blockNo = 0;
this.nextTick = null;
this.timer = 0;
this.paused = 0;
this.queue = new BalancedTree((a, b) => a.priority - b.priority || b.blockNo - a.blockNo);
this.queueLocs = new BalancedTree((a, b) => a.l - b.l || a.x - b.x || a.y - b.y);
this.sent = new LinkedList();
this.sentLocs = new BalancedTree((a, b) => a.l - b.l || a.x - b.x || a.y - b.y);
}
deserialise(msg) {
msg = msg._internal_('get-objects');
let empty = new Block(0);
for (let x = 0; x < this.width; x++)
for (let y = 0; y < this.height; y++)
this.blocks[x][y] = [empty, empty];
let i = msg.length - 1;
while (msg[i--] !== 'we');
while (msg[i] !== 'ws') {
let args = [ ];
while (!Array.isArray(msg[i]))
args.push(msg[i--]);
let ys = msg[i--],
xs = msg[i--],
l = msg[i--],
id = msg[i--];
let block = new Block(id, ...args);
while (xs.length) this.blocks[xs.pop() + (xs.pop() << 8)][ys.pop() + (ys.pop() << 8)][l] = block;
}
}
clear(fillFG = 0, borderFG = fillFG, fillBG = 0, borderBG = fillBG) {
if (typeof fillFG === 'number') fillFG = new Block(fillFG);
if (typeof borderFG === 'number') borderFG = new Block(borderFG);
if (typeof fillBG === 'number') fillBG = new Block(fillBG);
if (typeof borderBG === 'number') borderBG = new Block(borderBG);
for (let x = 0; x < this.width; x++)
for (let y = 0; y < this.height; y++)
this.blocks[x][y] = x === 0 || y === 0 || x === this.width - 1 || y === this.height - 1 ?
[borderFG, borderBG] : [fillFG, fillBG];
}
place(priority, l, x, y, id, ...args) {
let block = new Block(id, ...args);
let b = this.queueLocs.find({ l: l, x: x, y: y });
if (b === null) {
if (!this.blocks[x][y][l].equals(block)) {
b = new BlockPlacement(l, x, y, block, this.blocks[x][y][l], priority, this.blockNo++);
this.queue.add(b);
this.queueLocs.add(b);
this.startTicking();
}
} else if (b.last.equals(block)) {
this.queue.remove(b);
this.queueLocs.remove(b);
} else {
b.block = block;
if (priority > b.priority) {
this.queue.remove(b);
b.priority = priority;
b.blockNo = this.blockNo++;
this.queue.add(b);
}
}
}
block(msg, lPos) {
msg = msg._internal_('get-objects');
let l = lPos >= 0 ? msg.splice(lPos, 1)[0] : 0,
pid = msg.pop(),
x = msg.shift(),
y = msg.shift(),
block = new Block(msg.shift(), ...msg);
if (pid === this.botID) {
while (true) {
let b = this.sent.remove();
if (b.l === l && b.x === x && b.y === y) {
if (b.block.equals(block)) {
if (this.sentLocs.find(b) === b) {
this.sentLocs.remove(b);
if (this.queueLocs.find(b) === null) this.blocks[x][y][l] = block;
}
break;
}
} else if (this.sentLocs.find(b) === b &&
this.queueLocs.find(b) === null) {
this.sentLocs.remove(b);
this.queue.add(b);
this.queueLocs.add(b);
this.startTicking();
}
}
} else {
let b = this.queueLocs.find({ l: l, x: x, y: y });
if (b !== null) {
this.queue.remove(b);
this.queueLocs.remove(b);
}
this.sentLocs.remove({ l: l, x: x, y: y });
this.blocks[x][y][l] = block;
}
}
pause() {
this.paused++;
}
resume() {
this.paused--;
this.startTicking();
}
clearQueue() {
this.queue = new BalancedTree((a, b) => a.priority - b.priority || b.blockNo - a.blockNo);
this.queueLocs = new BalancedTree((a, b) => a.l - b.l || a.x - b.x || a.y - b.y);
this.sentLocs = new BalancedTree((a, b) => a.l - b.l || a.x - b.x || a.y - b.y);
}
startTicking() {
if (this.paused == 0 && this.nextTick === null) {
this.nextTick = Date.now();
if (this.flusher !== null) {
clearTimeout(this.flusher);
this.flusher = null;
}
this.tick();
}
}
tick() {
if (this.paused > 0) {
this.nextTick = null;
return;
}
while (Date.now() >= this.nextTick) {
let b = this.queue.first;
if (b !== null) {
this.queue.remove(b);
this.queueLocs.remove(b);
this.nextTick += 1000 / this.BPS;
this.con.send(...b.message);
this.sent.add(b);
this.sentLocs.remove(b);
this.sentLocs.add(b);
} else {
this.nextTick = null;
this.flusher = setTimeout(this.flush.bind(this), 1000);
return;
}
}
setTimeout(this.tick.bind(this), 1000 / this.BPS);
}
flush() {
this.flusher = null;
while (this.sent.first !== null) {
let b = this.sent.remove();
if (this.sentLocs.find(b) === b &&
this.queueLocs.find(b) === null) {
this.sentLocs.remove(b);
this.queue.add(b);
this.queueLocs.add(b);
}
}
if (this.nextTick === null) {
this.nextTick = Date.now();
this.tick();
}
}
}
(Cant remember the changes before this)
This uses the new import and export functions added recently, so instead of adding a script tag you just need to save it in your bot directory then reference it in your main javascript file (named Example.js in this example):
import BlockHandler from "./BlockHandler.js";
let cli = null;
let cfg = null;
let con = null;
let BH = null;
function Setup() {
let email = prompt("Email: ");
let password = prompt("Password: ");
let worldID = prompt("World ID: ");
PlayerIO.authenticate("everybody-edits-su9rn58o40itdbnw69plyw", "simpleUsers", { email: email, password: password }, { }, client => {
Log("Authenticated");
cli = client;
cli.bigDB.load("config", "config", config => {
Log("Loaded config");
cfg = config;
cli.multiplayer.createJoinRoom(worldID, "Everybodyedits" + cfg.version, true, null, null, connection => {
Log("Connected");
con = connection;
con.addMessageCallback("*", OnMessage);
con.send("init");
}, CallbackError);
}, CallbackError);
}, CallbackError);
}
function Log(text) { console.log(text) }
function CallbackError(error) { Log("Error: " + error.code + ": " + error.message) }
function OnMessage(m) {
switch (m.type) {
case "init":
Log("Init received");
con.send("init2");
BH = new BlockHandler(con, m.getInt(5), m.getInt(18), m.getInt(19), 100);
BH.deserialise(m)
break;
case "init2":
Log("Init2 received");
OnLoad();
break;
case 'reset':
Log("World reloaded");
BH.clearQueue();
BH.deserialise(m);
break;
case 'clear':
Log("World cleared");
BH.clearQueue();
BH.clear(m.getUInt(2), m.getUInt(3));
break;
case 'b':
BH.block(m, 0);
break;
case 'br':
BH.block(m, 4);
break;
case 'bc': case 'bn': case 'bs': case 'lb': case 'pt': case 'ts': case 'wp':
BH.block(m);
break;
}
}
function OnLoad() {
// Do your own thing here
for (let y = 5; y < 25; y++)
for (let x = 5; x < 25; x++)
BH.place(Math.random(), 0, x, y, 10);
}
Setup();
// You can use BH.place(priority, layer, x, y, id, (optional)arg1, arg2...) to place blocks
// e.g. BH.place(0, 0, 5, 5, 10); would place a blue block in the position (5, 5)
// Priority lets you set some tasks to happen before others, higher priority placements happen first
// You can use BH.blocks[x][y][l] to access the blocks in the world
// e.g. BH.blocks[5][5][0].id would return the id of the fg in the position (5, 5)
// and BH.blocks[5][5][0].args would return the extra arguments of the fg in the position (5, 5) as an array
// pause, resume and clearQueue can be used to pause, resume, and clear block placements respectively
// The constructor function allows for some arguments:
// BlockHandler(con, botID, width, height, (optional)BPS)
// con = the playerIO connection
// botID is the id used by the bot
// width is the width of the world
// height is the height of the world
// BPS is the number of blocks placed per second (defaults to 100)
To get modules to work, you just need to state that your script uses modules by using the type="module" attribute:
<html>
<body>
<script src="PlayerIOClient.development.js"></script>
<script src="Example.js" type="module"></script>
</body>
</html>
If your browser doesn't support modules yet, or if you want to be extra safe, you can just remove the export keyword and use this as normal, just make sure you don't use the same names as any of the variables / functions this uses.
Offline
BPS isn't working for me. There's no delay no matter what I change it to.
Offline
Same one in the OP but with a few placeBlocks when init is sent. I also tested it by making it place a block whenever I sent a chat message.
Offline
Its definately working for me, Ive just tested it using the exact same as the OP, apart from changing the init message to this:
Log("init recieved");
global.BH = new BlockHandler(global.connection, message.getInt(5), message.getInt(22), message.getInt(23), 1, message);
setTimeout(function() {
for(var y = 10; y < 20; y++)
for(var x = 10; x < 20; x++)
global.BH.placeBlock(0, x, y, 10);
}, 2000);
// The set timeout is just to allow for any possible delays while joining, you dont really need it, but it sometimes prevents some problems
Offline
My code is exactly the same and it's still not working. Tested it in Chrome & Firefox. Thanks for trying to help anyway. I must be the only person with this problem.
Offline
Um... Im not sure what could be causing it then...
I guess make sure that everything is exactly the same, but if you already have, then im not sure what could be wrong...
I guess maybe copy / paste the working init code into yours just to see if it is a typo or anything maybe
Also, this is different to other bots because it uses a frequency rather than a delay, so a lower BPS means lower block speed, rather than most other bots where you would increase the delay to decrease the speed, which would probably be quite an easy mistake to make, but other than that, I cant think of any that would make it have little / no delay
Offline
Also, this is different to other bots because it uses a frequency rather than a delay, so a lower BPS means lower block speed, rather than most other bots where you would increase the delay to decrease the speed, which would probably be quite an easy mistake to make, but other than that, I cant think of any that would make it have little / no delay
AAAAAAAAAAAAAH
Yes, I thought it was millisecond delay. Thank you. I can't believe I didn't even bother trying a lower BPS. I always changed it to about 1000.
Offline
PlayerIO message (de)serialisation is fixed now, this means that you should probably download the newest version of playerIOClient.js, and change
global.BH = new BlockHandler(global.connection, message.getInt(5), message.getInt(22), message.getInt(23), 25, message);
to
global.BH = new BlockHandler(global.connection, message.getInt(5), message.getInt(18), message.getInt(19), 25, message);
Offline
Ive just updated the explanations of how to use this library, as I hadnt explained some of the new features
If you are using this, I would suggest adding the code for the reset and clear messages, so it doesnt break if the world is loaded / cleared
Edit: I've also found a bug! To fix this, all you need to do is replace the clearQueue function in BlockHandler to this:
Edit 2: That ^ is still slightly bugged , ill try to fix it soon
Edit 3: Ok, fixed now, (if you've already changed clearQueue, change it back), the new fix is to replace the if else at the bottom of the message function with this:
var pos = l+','+x+','+y;
if (!(pos in this.blockPlaces)) {
if (arguments[3] != undefined) arguments[3](pid, l, x, y, id, args);
this.blocks[x][y][l] = {id:id, args:args};
} else if (pid == this.botID) {
if (--this.blockPlaces[pos][1] == 0 && this.blockPlaces[pos][0] == undefined) {
delete this.blockPlaces[pos];
if (arguments[3] != undefined) arguments[3](pid, l, x, y, id, args);
}
} else {
if (this.blockPlaces[pos][0] != undefined) {
this.blockQueue.splice(this.blockQueue.findIndex(b => b[0] == l && b[1] == x && b[2] == y), 1);
if (this.blockPlaces[pos][1] == 0) {
delete this.blockPlaces[pos];
if (arguments[3] != undefined) arguments[3](pid, l, x, y, id, args);
this.blocks[x][y][l] = {id:id, args:args};
} else this.blockPlaces[pos][0] = undefined;
}
}
Offline
Just released a new update to fix a couple of problems with world synchronisation (im now pretty sure that it works as it should do)
I would advise replacing your old versions of the library with the new one, although this update isnt as critical as the problems only happened in very specific circumstances
Nothing needs to be changed in your actual bot, and no new features have been added, this update is purely for bugfixes
Edit: Wait no, I think there might be one more, im currently trying to fix it, and will update this post if I do, or if its a problem with how im testing it or anything
Edit 2: Never mind, it definately was a problem with what I was doing
Offline
Just updated to 1.4, which fixes a bug with the pre-updated world (the library says blocks are there as soon as you call 'place', rather than waiting for it to reach the top of the placement queue to be sent)
This bug caused the clearQueue function to lead to a lot of artifacts in the world, as it still believed these 'pre-placed' blocks were there, even though they were cancelled, which sometimes caused other blocks not to be placed, and caused a lot of extra cpu power to be used if a lot of blocks were cancelled.
(The only thing changed in this update is the BlockHandler.clearQueue function)
Offline
Update to ES6 using classes
Kek
Offline
Update to ES6 using classes
Kek
I was thinking of doing that maybe, but 1. not everything supports it yet (although I think I might already be using things that wouldn't be), and 2. I don't think they support private functions, so I'd need some weird scope function things, which might make it messy
Offline
For a while I have been meaning to rewrite BlockHandler, and I've finally got around to it
Version 2 was rewritten from scratch, so a few of the features havent been added back yet, but I'm really pleased with the features currently there:
I have made use of more specialised data structures for the block queues, so there should be much better performance, especially when placing large numbers of blocks all at once.
This also allowed me to add new features such as priorities, which lets you prioritise some tasks over others, even if they happened in different orders (which is also very efficient).
Back when the block limit was removed, I removed the missed block checking and correction as I thought it wouldnt be needed anymore, and removing it allowed me to improve some other features. I originally didn't notice that it was still there for people that weren't the owner, and by the time I did I couldn't add it back as it was incompatible with some of the features I had added. In this version I have managed to write it in a way that means its very efficient and easy to do, so I have added it back again
Offline
To update any bots using this library to support NPCs, you will need to add "case 'bn':" after "case 'bc':" in the switch statement in OnMessage in you're bot's javascript file. No changes need to be made to the actual library
Offline
Updated to version 2.1
This version now uses modules, the new recommended way of doing things like this, which are now supported by all major desktop browsers apart from IE (which this didn't work on before, so that doesn't really matter )
Offline
Pages: 1
[ Started around 1732363942.7672 - Generated in 0.213 seconds, 12 queries executed - Memory usage: 1.6 MiB (Peak: 1.83 MiB) ]