599 lines
19 KiB
JavaScript
599 lines
19 KiB
JavaScript
window.Client = require('agario-client');
|
|
window.EventEmitter = require('events').EventEmitter;
|
|
|
|
function AnimatedValue(value) {
|
|
this.write(value);
|
|
}
|
|
|
|
AnimatedValue.prototype = {
|
|
get: function () {
|
|
if (this.timeout) {
|
|
var now = performance.now(),
|
|
end = this.frTime + this.timeout;
|
|
if (now >= end) {
|
|
this.timeout = 0;
|
|
return this.toVal;
|
|
} else {
|
|
if (this.following) {
|
|
this.toVal = this.following();
|
|
}
|
|
return this.toVal - (this.toVal - this.frVal) * (end - now) / this.timeout;
|
|
}
|
|
} else {
|
|
return this.toVal;
|
|
}
|
|
},
|
|
set: function (value, timeout) {
|
|
if (value != this.toVal) {
|
|
this.frVal = this.get();
|
|
this.toVal = value;
|
|
this.timeout = timeout;
|
|
this.following = undefined;
|
|
this.frTime = performance.now();
|
|
}
|
|
},
|
|
follow: function (following, timeout) {
|
|
this.frVal = this.get();
|
|
this.following = following;
|
|
this.timeout = timeout;
|
|
this.frTime = performance.now();
|
|
},
|
|
write: function (value) {
|
|
this.frVal = value;
|
|
this.toVal = value;
|
|
this.timeout = 0;
|
|
this.frTime = performance.now(); // so end == now
|
|
}
|
|
};
|
|
|
|
PIXI.Container.prototype.bringToFront = function () {
|
|
if (this.parent) {
|
|
var parent = this.parent;
|
|
parent.removeChild(this);
|
|
parent.addChild(this);
|
|
}
|
|
};
|
|
|
|
function BallView(main, ball) {
|
|
this.main = main;
|
|
this.ball = ball;
|
|
this.container = new PIXI.Container();
|
|
this.graphic = new PIXI.Graphics();
|
|
this.container.addChild(this.graphic);
|
|
|
|
this.x = new AnimatedValue(0);
|
|
this.y = new AnimatedValue(0);
|
|
this.s = new AnimatedValue(0);
|
|
|
|
var _this = this;
|
|
this.appear();
|
|
this.ball.on('appear', function () {
|
|
_this.appear();
|
|
});
|
|
this.ball.on('destroy', function (reason) {
|
|
if (reason.reason == 'eaten') {
|
|
var eater = _this.main.balls[reason.by];
|
|
if (eater && eater.ball.id != _this.ball.id) {
|
|
_this.x.follow(function () {
|
|
return eater.x.get();
|
|
}, 100);
|
|
_this.y.follow(function () {
|
|
return eater.y.get();
|
|
}, 100);
|
|
setTimeout(function () {
|
|
_this.disappear();
|
|
}, 50);
|
|
} else {
|
|
_this.disappear();
|
|
}
|
|
} else {
|
|
_this.disappear();
|
|
}
|
|
});
|
|
this.ball.on('disappear', function () {
|
|
_this.disappear();
|
|
});
|
|
this.ball.on('move', function (old_x, old_y, new_x, new_y) {
|
|
_this.x.set(new_x, 100);
|
|
_this.y.set(new_y, 100);
|
|
});
|
|
this.ball.on('resize', function (old_size, new_size) {
|
|
_this.s.set(new_size, 100);
|
|
_this.main.zSort(new_size);
|
|
});
|
|
}
|
|
|
|
BallView.prototype = {
|
|
appear: function () {
|
|
this.x.write(this.ball.x);
|
|
this.y.write(this.ball.y);
|
|
this.s.set(this.ball.size, 100);
|
|
this.shape();
|
|
this.setName();
|
|
this.setMass();
|
|
this.main.zSort(this.ball.size);
|
|
this.main.stage.addChild(this.container);
|
|
},
|
|
disappear: function () {
|
|
this.s.set(0, 100);
|
|
var _this = this;
|
|
setTimeout(function () {
|
|
_this.main.stage.removeChild(_this.container);
|
|
}, 100);
|
|
},
|
|
shape: function () {
|
|
this.graphic.clear();
|
|
this.graphic.beginFill(this.ball.virus ? 0x005500 : this.ball.color.replace('#', '0x'), 1);
|
|
this.graphic.drawCircle(0, 0, 1);
|
|
this.graphic.endFill();
|
|
},
|
|
setName: function () {
|
|
if (this.ball.name) {
|
|
if (!this.name) {
|
|
this.name = new PIXI.Text(this.ball.name, {
|
|
font: 'bold 20pt Arial',
|
|
fill: 0xFFFFFF,
|
|
stroke: 0x000000,
|
|
strokeThickness: 5
|
|
});
|
|
var _this = this;
|
|
this.ball.on('rename', function () {
|
|
_this.updateName();
|
|
});
|
|
}
|
|
this.updateName();
|
|
this.container.addChild(this.name);
|
|
} else {
|
|
if (this.name) {
|
|
this.container.removeChild(this.text);
|
|
this.ball.removeAllListener('rename');
|
|
delete this.text;
|
|
}
|
|
}
|
|
},
|
|
updateName: function () {
|
|
this.name.resolution = 10;
|
|
this.name.scale.x = this.name.scale.y *= 2 * 0.9 / this.name.width;
|
|
this.name.position.x = -this.name.width / 2;
|
|
this.name.position.y = -this.name.height / 2;
|
|
},
|
|
setMass: function () {
|
|
if (this.ball.mine) {
|
|
if (!this.mass) {
|
|
this.mass = new PIXI.Text(this.ball.size, {
|
|
font: 'bold 20pt Arial',
|
|
fill: 0xFFFFFF,
|
|
stroke: 0x000000,
|
|
strokeThickness: 5
|
|
});
|
|
var _this = this;
|
|
this.ball.on('resize', function () {
|
|
_this.updateMass();
|
|
});
|
|
}
|
|
this.updateMass();
|
|
this.container.addChild(this.mass);
|
|
} else {
|
|
if (this.mass) {
|
|
this.container.removeChild(this.mass);
|
|
this.ball.removeAllListeners('rename');
|
|
delete this.mass;
|
|
}
|
|
}
|
|
},
|
|
updateMass: function () {
|
|
this.mass.text = this.ball.size;
|
|
this.mass.resolution = 10;
|
|
this.mass.scale.x = this.mass.scale.y *= 0.5 / this.mass.width;
|
|
this.mass.position.x = -this.mass.width / 2;
|
|
this.mass.position.y = this.name ? this.name.height / 2 : 0;
|
|
},
|
|
render: function () {
|
|
this.container.position.x = this.x.get();
|
|
this.container.position.y = this.y.get();
|
|
this.container.scale.x = this.container.scale.y = this.s.get();
|
|
}
|
|
};
|
|
|
|
|
|
function Viewer(client, container) {
|
|
this.client = client;
|
|
this.container = container;
|
|
|
|
this.balls = {};
|
|
|
|
this.addRenderer();
|
|
this.addStats();
|
|
var _this = this;
|
|
client.once('mapSizeLoad', function (min_x, min_y, max_x, max_y) {
|
|
_this.gameWidth = max_x;
|
|
_this.gameHeight = max_y;
|
|
_this.initStage();
|
|
_this.addListners();
|
|
_this.addBorders();
|
|
_this.animate();
|
|
_this.homeview = true;
|
|
client.once('myNewBall', function() {
|
|
_this.homeview = false;
|
|
});
|
|
_this.emit('launched');
|
|
});
|
|
window.addEventListener('resize', function () {
|
|
_this.updateSize();
|
|
});
|
|
}
|
|
|
|
Viewer.prototype = {
|
|
getSize: function () {
|
|
this.width = window.innerWidth;
|
|
this.height = window.innerHeight;
|
|
},
|
|
addRenderer: function () {
|
|
this.getSize();
|
|
this.renderer = PIXI.autoDetectRenderer(this.width, this.height, {
|
|
antialias: true
|
|
});
|
|
this.container.appendChild(this.renderer.view);
|
|
},
|
|
updateSize: function () {
|
|
this.getSize();
|
|
this.renderer.resize(this.width, this.height);
|
|
},
|
|
defaultScale: function () {
|
|
return Math.max(this.width / 1920, this.height / 1080)
|
|
},
|
|
initStage: function () {
|
|
this.stage = new PIXI.Container();
|
|
this.cam = {
|
|
x: new AnimatedValue(this.gameWidth / 2),
|
|
y: new AnimatedValue(this.gameHeight / 2),
|
|
s: new AnimatedValue(this.defaultScale())
|
|
};
|
|
this.d = {};
|
|
this.dg = new PIXI.Graphics();
|
|
this.stage.addChild(this.dg);
|
|
},
|
|
addListners: function () {
|
|
var _this = this;
|
|
this.client.on('ballAppear', function (id) {
|
|
if (!_this.balls[id]) {
|
|
_this.balls[id] = new BallView(_this, this.balls[id]);
|
|
} else {}
|
|
});
|
|
this.client.on('ballDestroy', function (id) {
|
|
delete this.balls[id];
|
|
});
|
|
},
|
|
addBorders: function () {
|
|
this.borders = new PIXI.Graphics();
|
|
this.borders.lineStyle(5, 0xFF3300, 1);
|
|
this.borders.drawRect(0, 0, this.gameWidth, this.gameHeight);
|
|
this.stage.addChild(this.borders);
|
|
},
|
|
addStats: function () {
|
|
this.stats = new Stats();
|
|
this.stats.setMode(1);
|
|
this.stats.domElement.style.position = 'absolute';
|
|
this.stats.domElement.style.left = '0px';
|
|
this.stats.domElement.style.top = '0px';
|
|
document.body.appendChild(this.stats.domElement);
|
|
},
|
|
zSort: function (at) {
|
|
if (!at) {
|
|
at = 0;
|
|
}
|
|
var keys = Object.keys(this.balls);
|
|
var _this = this;
|
|
keys.sort(function (a, b) {
|
|
return _this.balls[a].ball.size - _this.balls[b].ball.size;
|
|
});
|
|
for (var key_offset in keys) {
|
|
var ball = this.balls[keys[key_offset]];
|
|
if (ball.ball.size >= at) {
|
|
ball.container.bringToFront();
|
|
}
|
|
}
|
|
},
|
|
posCamera: function () {
|
|
var x = y = p = 0;
|
|
for (var ball_id in this.client.my_balls) {
|
|
var ball = this.client.balls[this.client.my_balls[ball_id]];
|
|
if (!ball.visible) continue;
|
|
x += ball.x * ball.size;
|
|
y += ball.y * ball.size;
|
|
p += ball.size;
|
|
}
|
|
if (p > 0) { // if we have visible ball(s)
|
|
this.cam.x.set(x / p, 100);
|
|
this.cam.y.set(y / p, 100);
|
|
this.cam.s.set(Math.pow(Math.min(64 / p, 1), 0.4) * this.defaultScale(), 500);
|
|
} else if (this.homeview) {
|
|
this.cam.s.write(this.defaultScale());
|
|
} // else: don't move the camera
|
|
},
|
|
render: function () {
|
|
for (var ball_id in this.client.balls) {
|
|
var ball = this.balls[ball_id];
|
|
if (ball) {
|
|
ball.render();
|
|
}
|
|
}
|
|
},
|
|
animate: function () {
|
|
this.stats.begin();
|
|
this.render();
|
|
this.posCamera();
|
|
this.stage.scale.x = this.stage.scale.y = this.cam.s.get();
|
|
this.stage.position.x = -this.cam.x.get() * this.stage.scale.x + this.width / 2;
|
|
this.stage.position.y = -this.cam.y.get() * this.stage.scale.y + this.height / 2;
|
|
this.renderer.render(this.stage);
|
|
this.stats.end();
|
|
this.emit('animate');
|
|
var _this = this;
|
|
requestAnimationFrame(function () {
|
|
_this.animate();
|
|
});
|
|
}
|
|
};
|
|
|
|
// Inherit from EventEmitter
|
|
for (var key in EventEmitter.prototype) {
|
|
Viewer.prototype[key] = EventEmitter.prototype[key];
|
|
}
|
|
|
|
function Pointer(viewer) {
|
|
this.viewer = viewer;
|
|
this.client = this.viewer.client;
|
|
this.dest = { // Destination, relative to camera center
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
var _this = this;
|
|
this.viewer.once('launched', function () {
|
|
_this.viewer.stage.interactive = true;
|
|
_this.viewer.stage.on('mousemove', function (e) {
|
|
_this.pointermove(e);
|
|
});
|
|
_this.viewer.stage.on('touchmove', function (e) {
|
|
_this.pointermove(e);
|
|
});
|
|
_this.viewer.on('animate', function (e) {
|
|
_this.move();
|
|
});
|
|
});
|
|
window.addEventListener('keydown', function (e) {
|
|
if (e.keyCode == 87) {
|
|
_this.client.eject();
|
|
} else if (e.keyCode == 32) {
|
|
_this.client.split();
|
|
}
|
|
});
|
|
}
|
|
|
|
Pointer.prototype = {
|
|
move: function () {
|
|
this.client.moveTo(this.viewer.cam.x.get() + this.dest.x, this.viewer.cam.y.get() + this.dest.y);
|
|
},
|
|
pointermove: function (e) {
|
|
var gamePos = e.data.getLocalPosition(this.viewer.stage);
|
|
this.dest = {
|
|
x: gamePos.x - this.viewer.cam.x.get(),
|
|
y: gamePos.y - this.viewer.cam.y.get()
|
|
};
|
|
if (Math.abs(this.dest.x) < 10 && Math.abs(this.dest.y) < 10) {
|
|
this.dest = {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
}
|
|
this.move();
|
|
}
|
|
};
|
|
|
|
function Controller(client) {
|
|
this.client = client;
|
|
|
|
this.server = {
|
|
region: 'EU-London',
|
|
ip: '127.0.0.1',
|
|
port: 9158
|
|
};
|
|
this.nick = 'agario-client';
|
|
this.autoRespawn = false;
|
|
|
|
this.gui = new dat.GUI();
|
|
|
|
this.servgui = this.gui.addFolder('Server');
|
|
this.servgui.add(this.server, 'region', ['US-Fremont', 'US-Atlanta', 'BR-Brazil', 'EU-London', 'RU-Russia', 'JP-Tokyo', 'CN-China', 'SG-Singapore', 'TK-Turkey']);
|
|
this.servgui.add(this, 'findServer');
|
|
this.servgui.add(this.server, 'ip');
|
|
this.servgui.add(this.server, 'port');
|
|
this.servgui.add(this, 'connect');
|
|
this.servgui.add(this, 'disconnect');
|
|
this.servgui.open();
|
|
|
|
this.cellgui = this.gui.addFolder('Cell');
|
|
this.cellgui.add(this, 'nick');
|
|
this.cellgui.add(this, 'spawn');
|
|
this.cellgui.add(this, 'autoRespawn');
|
|
var scoreGui = this.cellgui.add(this.client, 'score').listen();
|
|
this.client.on('scoreUpdate', function () {
|
|
scoreGui.updateDisplay();
|
|
});
|
|
|
|
this.leadergui = this.gui.addFolder('Leaderboard');
|
|
this.leaders = {};
|
|
this.resetLeader();
|
|
for (var i = 1; i <= 10; i++) {
|
|
this.leadergui.add(this.leaders, i);
|
|
}
|
|
|
|
var _this = this;
|
|
client.on('connected', function () {
|
|
_this.servgui.close();
|
|
_this.cellgui.open();
|
|
_this.leadergui.open();
|
|
if (_this.autoRespawn) {
|
|
_this.spawn();
|
|
}
|
|
});
|
|
client.on('reset', function () {
|
|
_this.servgui.open();
|
|
_this.cellgui.close();
|
|
_this.leadergui.close();
|
|
_this.resetLeader();
|
|
});
|
|
client.on('lostMyBalls', function () {
|
|
if (_this.autoRespawn) {
|
|
_this.spawn();
|
|
}
|
|
});
|
|
client.on('leaderBoardUpdate', function (old, leaders) {
|
|
for (var i in leaders) {
|
|
var rank = parseInt(i) + 1;
|
|
_this.leaders[rank] = this.balls[leaders[i]].name || 'An unnamed cell';
|
|
for (var j in _this.leadergui.__controllers) {
|
|
_this.leadergui.__controllers[j].updateDisplay();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
Controller.prototype = {
|
|
findServer: function () {
|
|
// Because of SOP, this will never work
|
|
x = new XMLHttpRequest();
|
|
x.open('POST', 'http://m.agar.io', false);
|
|
x.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
|
x.setRequestHeader('Content-Length', this.server.region.length);
|
|
// x.setRequestHeader('Origin', 'http://agar.io');
|
|
// x.setRequestHeader('Referer', 'http://agar.io/');
|
|
x.send(this.server.region);
|
|
s = x.responseText.split(':');
|
|
this.server.ip = s[0];
|
|
this.server.port = s[1];
|
|
|
|
for (var i in this.servgui.__controllers) {
|
|
this.servgui.__controllers[i].updateDisplay();
|
|
}
|
|
},
|
|
connect: function () {
|
|
this.client.connect('ws://' + this.server.ip + ':' + this.server.port);
|
|
},
|
|
disconnect: function () {
|
|
this.client.disconnect();
|
|
},
|
|
spawn: function () {
|
|
this.client.spawn(this.nick);
|
|
},
|
|
resetLeader: function () {
|
|
for (var i = 1; i <= 10; i++) {
|
|
this.leaders[i] = '---';
|
|
}
|
|
}
|
|
};
|
|
|
|
function IA(client) {
|
|
this.client = client;
|
|
this.begin();
|
|
}
|
|
|
|
IA.prototype = {
|
|
begin: function () {
|
|
var _this = this;
|
|
this.interval = setInterval(function () {
|
|
_this.food();
|
|
// _this.decide();
|
|
}, 100);
|
|
},
|
|
end: function () {
|
|
clearInterval(this.interval_id);
|
|
},
|
|
getDistanceBetweenBalls: function (ball_1, ball_2) { //this calculates distance between 2 balls
|
|
return Math.sqrt(Math.pow(ball_1.x - ball_2.x, 2) + Math.pow(ball_2.y - ball_1.y, 2));
|
|
},
|
|
getAngleBetweenBalls: function (b1, b2) { // output in rad
|
|
dX = b2.x - b1.x;
|
|
dY = b2.y - b1.y;
|
|
return Math.tan(dY / dX);
|
|
},
|
|
food: function () {
|
|
var candidate_ball = null; //first we don't have candidate to eat
|
|
var candidate_distance = 0;
|
|
var my_ball = this.client.balls[this.client.my_balls[0]]; //we get our first ball. We don't care if there more then one, its just example.
|
|
if (!my_ball) return; //if our ball not spawned yet then we abort. We will come back here in 100ms later
|
|
|
|
for (var ball_id in this.client.balls) { //we go true all balls we know about
|
|
var ball = this.client.balls[ball_id];
|
|
if (ball.virus) continue; //if ball is a virus (green non edible thing) then we skip it
|
|
if (!ball.visible) continue; //if ball is not on our screen (field of view) then we skip it
|
|
if (ball.mine) continue; //if ball is our ball - then we skip it
|
|
if (ball.size / my_ball.size > 0.5) continue; //if ball is bigger than 50% of our size - then we skip it
|
|
var distance = this.getDistanceBetweenBalls(ball, my_ball); //we calculate distances between our ball and candidate
|
|
if (candidate_ball && distance > candidate_distance) continue; //if we do have some candidate and distance to it smaller, than distance to this ball, we skip it
|
|
|
|
candidate_ball = ball; //we found new candidate and we record him
|
|
candidate_distance = this.getDistanceBetweenBalls(ball, my_ball); //we record distance to him to compare it with other balls
|
|
}
|
|
if (!candidate_ball) return; //if we didn't find any candidate, we abort. We will come back here in 100ms later
|
|
|
|
this.client.log('closest ' + candidate_ball + ', distance ' + candidate_distance);
|
|
this.client.moveTo(candidate_ball.x, candidate_ball.y); //we send move command to move to food's coordinates
|
|
},
|
|
decide: function () {
|
|
var my_ball = this.client.balls[this.client.my_balls[0]]; // TODO Handle more balls
|
|
if (!my_ball) return;
|
|
var candidates = [];
|
|
|
|
for (var ball_id in this.client.balls) {
|
|
var ball = this.client.balls[ball_id];
|
|
if (!ball.visible) continue;
|
|
|
|
var score = 0;
|
|
if (ball.mine) {
|
|
score = 1;
|
|
} else {
|
|
if (ball.virus) {
|
|
if (ball.size > my_ball.size) {
|
|
score = -5;
|
|
} else {
|
|
score = 5;
|
|
}
|
|
} else {
|
|
score = Math.max(1000 - this.getDistanceBetweenBalls(ball, my_ball) - ball.size - my_ball.size, 0);
|
|
if (ball.size < my_ball.size) {
|
|
score = score;
|
|
} else {
|
|
score = -score * 5;
|
|
}
|
|
}
|
|
}
|
|
|
|
candidates.push({
|
|
x: ball.x - my_ball.x,
|
|
y: ball.y - my_ball.y,
|
|
score: score
|
|
});
|
|
}
|
|
|
|
var x = y = p = 0;
|
|
for (var candidate_id in candidates) {
|
|
var candidate = candidates[candidate_id];
|
|
// console.log(candidate.x, candidate.y, candidate.score)
|
|
x += candidate.x * candidate.score;
|
|
y += candidate.y * candidate.score;
|
|
p += candidate.score;
|
|
}
|
|
this.client.moveTo(my_ball.x + x / p, my_ball.y + y / p);
|
|
}
|
|
};
|
|
|
|
var d = {}; // DEBUG Allow access from console
|
|
window.onload = function () {
|
|
d.client = new Client('worker');
|
|
d.viewer = new Viewer(d.client, document.getElementById('viewer'));
|
|
d.controller = new Controller(d.client);
|
|
d.pointer = new Pointer(d.viewer);
|
|
// d.ia = new IA(d.client);
|
|
};
|