206 lines
5.7 KiB
JavaScript
206 lines
5.7 KiB
JavaScript
/*
|
|
* this is the actual event
|
|
* A ever growing circle of random pixels starts at event area
|
|
* users fight it with background pixels
|
|
* if it reaches the TARGET_RADIUS size, the event is lost
|
|
*
|
|
*/
|
|
import socketEvents from '../socket/SocketEvents';
|
|
import PixelUpdate from '../socket/packets/PixelUpdateServer';
|
|
import { setPixelByOffset } from './setPixel';
|
|
import { TILE_SIZE } from './constants';
|
|
import { CANVAS_ID } from '../data/models/Event';
|
|
// eslint-disable-next-line import/no-unresolved
|
|
import canvases from './canvases.json';
|
|
|
|
const TARGET_RADIUS = 62;
|
|
const EVENT_DURATION_MIN = 10;
|
|
// const EVENT_DURATION_MIN = 1;
|
|
|
|
class Void {
|
|
// chunk coords
|
|
i;
|
|
j;
|
|
// number highest possible colorIndex
|
|
maxClr;
|
|
// timeout between pixels in ms
|
|
msTimeout;
|
|
// array of pixels that we place before continue building (instant-defense)
|
|
pixelStack;
|
|
// Uint8Array to log pixels in area
|
|
area;
|
|
userArea;
|
|
// current numberical data
|
|
curRadius;
|
|
curAngle;
|
|
curAngleDelta;
|
|
// boolean if ended
|
|
ended;
|
|
|
|
constructor(centerCell) {
|
|
// chunk coordinates
|
|
const [i, j] = centerCell;
|
|
this.i = i;
|
|
this.j = j;
|
|
this.ended = false;
|
|
this.maxClr = canvases[CANVAS_ID].colors.length;
|
|
const area = TARGET_RADIUS ** 2 * Math.PI;
|
|
const online = socketEvents.onlineCounter.total || 0;
|
|
// require an average of 0.25 px / min / user
|
|
const requiredSpeed = Math.floor(online / 1.8);
|
|
const ppm = Math.ceil(area / EVENT_DURATION_MIN + requiredSpeed);
|
|
this.msTimeout = 60 * 1000 / ppm;
|
|
this.area = new Uint8Array(TILE_SIZE * 3 * TILE_SIZE * 3);
|
|
this.userArea = new Uint8Array(TILE_SIZE * 3 * TILE_SIZE * 3);
|
|
this.pixelStack = [];
|
|
this.curRadius = 0;
|
|
this.curAngle = 0;
|
|
this.curAngleDelta = Math.PI;
|
|
|
|
this.voidLoop = this.voidLoop.bind(this);
|
|
this.cancel = this.cancel.bind(this);
|
|
this.checkStatus = this.checkStatus.bind(this);
|
|
this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this);
|
|
socketEvents.addListener('pixelUpdate', this.broadcastPixelBuffer);
|
|
this.voidLoop();
|
|
}
|
|
|
|
/*
|
|
* send pixel relative to 3x3 tile area
|
|
*/
|
|
sendPixel(x, y, clr) {
|
|
const [u, v, off] = Void.coordsToOffset(x, y);
|
|
const i = this.i + u;
|
|
const j = this.j + v;
|
|
this.area[x + y * TILE_SIZE * 3] = clr;
|
|
setPixelByOffset(CANVAS_ID, clr, i, j, off);
|
|
}
|
|
|
|
/*
|
|
* check if pixel is set by us
|
|
* x, y relative to 3x3 tiles area
|
|
*/
|
|
isSet(x, y, resetIfSet = false) {
|
|
const off = x + y * TILE_SIZE * 3;
|
|
const clr = this.area[off];
|
|
if (clr) {
|
|
if (resetIfSet) this.area[off] = 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* check if pixel is set by user during event
|
|
*/
|
|
isUserSet(x, y) {
|
|
const off = x + y * TILE_SIZE * 3;
|
|
const clr = this.userArea[off];
|
|
return clr || false;
|
|
}
|
|
|
|
static coordsToOffset(x, y) {
|
|
const ox = x % TILE_SIZE;
|
|
const oy = y % TILE_SIZE;
|
|
const off = ox + oy * TILE_SIZE;
|
|
const u = (x - ox) / TILE_SIZE - 1;
|
|
const v = (y - oy) / TILE_SIZE - 1;
|
|
return [u, v, off];
|
|
}
|
|
|
|
voidLoop() {
|
|
if (this.ended) {
|
|
return;
|
|
}
|
|
let clr = 0;
|
|
while (clr <= 2 || clr === 25) {
|
|
// choose random color
|
|
clr = Math.floor(Math.random() * this.maxClr);
|
|
}
|
|
const pxl = this.pixelStack.pop();
|
|
if (pxl) {
|
|
// use stack pixel if available
|
|
const [x, y] = pxl;
|
|
this.sendPixel(x, y, clr);
|
|
} else {
|
|
// build in a circle
|
|
/* that really is the best here */
|
|
// eslint-disable-next-line no-constant-condition
|
|
while (true) {
|
|
this.curAngle += this.curAngleDelta;
|
|
if (this.curAngle > 2 * Math.PI) {
|
|
// it does skip some pixel, but thats ok
|
|
this.curRadius += 1;
|
|
if (this.curRadius > TARGET_RADIUS) {
|
|
this.ended = true;
|
|
return;
|
|
}
|
|
this.curAngleDelta = 2 * Math.PI / (2 * this.curRadius * Math.PI);
|
|
this.curAngle = 0;
|
|
}
|
|
const { curAngle, curRadius } = this;
|
|
let gk = Math.sin(curAngle) * curRadius;
|
|
let ak = Math.cos(curAngle) * curRadius;
|
|
if (gk > 0) gk = Math.floor(gk);
|
|
else gk = Math.ceil(gk);
|
|
if (ak > 0) ak = Math.floor(ak);
|
|
else ak = Math.ceil(ak);
|
|
const x = ak + TILE_SIZE * 1.5;
|
|
const y = gk + TILE_SIZE * 1.5;
|
|
if (this.isSet(x, y)) {
|
|
continue;
|
|
}
|
|
this.sendPixel(x, y, clr);
|
|
if (this.isUserSet(x, y)) {
|
|
// if drawing on a user set pixel, wait longer
|
|
setTimeout(this.voidLoop, this.msTimeout * 4);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
setTimeout(this.voidLoop, this.msTimeout);
|
|
}
|
|
|
|
cancel() {
|
|
socketEvents.removeListener('pixelUpdate', this.broadcastPixelBuffer);
|
|
this.ended = true;
|
|
}
|
|
|
|
checkStatus() {
|
|
if (this.ended) {
|
|
socketEvents.removeListener('pixelUpdate', this.broadcastPixelBuffer);
|
|
return 100;
|
|
}
|
|
return Math.floor(this.curRadius * 100 / TARGET_RADIUS);
|
|
}
|
|
|
|
broadcastPixelBuffer(canvasId, chunkId, buffer) {
|
|
const {
|
|
i: pi,
|
|
j: pj,
|
|
pixels,
|
|
} = PixelUpdate.hydrate(buffer);
|
|
const { i, j } = this;
|
|
// 3x3 chunk area (this is hardcoded on multiple places)
|
|
if (pi >= i - 1 && pi <= i + 1 && pj >= j - 1 && pj <= j + 1) {
|
|
pixels.forEach((pxl) => {
|
|
const [off, color] = pxl;
|
|
if (color <= 2 || color === 25) {
|
|
const uOff = (pi - i + 1) * TILE_SIZE;
|
|
const vOff = (pj - j + 1) * TILE_SIZE;
|
|
const x = uOff + off % TILE_SIZE;
|
|
const y = vOff + Math.floor(off / TILE_SIZE);
|
|
if (this.isSet(x, y, true)) {
|
|
this.pixelStack.push([x, y]);
|
|
} else {
|
|
this.userArea[x + y * TILE_SIZE * 3] = color;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
export default Void;
|