pixelplanet/src/core/Void.js
2022-01-04 21:09:35 +01:00

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;