forked from ppfun/pixelplanet
add MMORPG style Event
This commit is contained in:
parent
4baadec329
commit
dc6b1f8cbe
|
@ -92,6 +92,7 @@ Configuration takes place in the environment variables that are defined in ecosy
|
||||||
| BACKUP_DIR | mounted directory of backup server | "/mnt/backup/" |
|
| BACKUP_DIR | mounted directory of backup server | "/mnt/backup/" |
|
||||||
| GMAIL_USER | gmail username if used for mails | "ppfun@gmail.com" |
|
| GMAIL_USER | gmail username if used for mails | "ppfun@gmail.com" |
|
||||||
| GMAIL_PW | gmail password if used for mails | "lolrofls" |
|
| GMAIL_PW | gmail password if used for mails | "lolrofls" |
|
||||||
|
| HOURLY_EVENT | run hourly void event on main canvas | 1 |
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
|
@ -238,6 +239,10 @@ Alternatively you can run it with pm2, just like pixelplanet. An example ecosyst
|
||||||
Note:
|
Note:
|
||||||
- You do not have to run backups or historical view, it's optional.
|
- You do not have to run backups or historical view, it's optional.
|
||||||
|
|
||||||
|
### Hourly Event
|
||||||
|
|
||||||
|
Hourly event is an MMORPG style event that launches once in two hours where users have to fight against a growing void that starts at a random position at the main canvas. If they complete it successfully, the whole canvas will have half cooldown for a few minutes.
|
||||||
|
|
||||||
### Historical view
|
### Historical view
|
||||||
|
|
||||||
![historicalview](promotion/historicalview.gif)
|
![historicalview](promotion/historicalview.gif)
|
||||||
|
|
|
@ -36,7 +36,8 @@ function init() {
|
||||||
ProtocolClient.on('pixelUpdate', ({
|
ProtocolClient.on('pixelUpdate', ({
|
||||||
i, j, offset, color,
|
i, j, offset, color,
|
||||||
}) => {
|
}) => {
|
||||||
store.dispatch(receivePixelUpdate(i, j, offset, color));
|
// remove protection
|
||||||
|
store.dispatch(receivePixelUpdate(i, j, offset, color & 0x7F));
|
||||||
});
|
});
|
||||||
ProtocolClient.on('pixelReturn', ({
|
ProtocolClient.on('pixelReturn', ({
|
||||||
retCode, wait, coolDownSeconds,
|
retCode, wait, coolDownSeconds,
|
||||||
|
|
|
@ -20,9 +20,12 @@ function ChatMessage({
|
||||||
}
|
}
|
||||||
|
|
||||||
const isInfo = (name === 'info');
|
const isInfo = (name === 'info');
|
||||||
|
const isEvent = (name === 'event');
|
||||||
let className = 'msg';
|
let className = 'msg';
|
||||||
if (isInfo) {
|
if (isInfo) {
|
||||||
className += ' info';
|
className += ' info';
|
||||||
|
} else if (isEvent) {
|
||||||
|
className += ' event';
|
||||||
} else if (msgArray[0][1].charAt(0) === '>') {
|
} else if (msgArray[0][1].charAt(0) === '>') {
|
||||||
className += ' greentext';
|
className += ' greentext';
|
||||||
}
|
}
|
||||||
|
@ -30,7 +33,7 @@ function ChatMessage({
|
||||||
return (
|
return (
|
||||||
<p className="chatmsg">
|
<p className="chatmsg">
|
||||||
{
|
{
|
||||||
(!isInfo)
|
(!isInfo && !isEvent)
|
||||||
&& (
|
&& (
|
||||||
<span>
|
<span>
|
||||||
<img
|
<img
|
||||||
|
|
|
@ -156,7 +156,7 @@ export async function imagemask2Canvas(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pxlCnt) {
|
if (pxlCnt) {
|
||||||
const ret = await RedisCanvas.setChunk(cx, cy, chunk);
|
const ret = await RedisCanvas.setChunk(cx, cy, chunk, canvasId);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
logger.info(`Loaded ${pxlCnt} pixels into chunk ${cx}, ${cy}.`);
|
logger.info(`Loaded ${pxlCnt} pixels into chunk ${cx}, ${cy}.`);
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,10 @@ export async function protectCanvasArea(
|
||||||
const canvasMinXY = -(canvas.size / 2);
|
const canvasMinXY = -(canvas.size / 2);
|
||||||
|
|
||||||
const [ucx, ucy] = getChunkOfPixel(canvas.size, x, y);
|
const [ucx, ucy] = getChunkOfPixel(canvas.size, x, y);
|
||||||
const [lcx, lcy] = getChunkOfPixel(canvas.size, x + width, y + height);
|
const [lcx, lcy] = getChunkOfPixel(
|
||||||
|
canvas.size, x + width - 1,
|
||||||
|
y + height - 1,
|
||||||
|
);
|
||||||
|
|
||||||
let chunk;
|
let chunk;
|
||||||
for (let cx = ucx; cx <= lcx; cx += 1) {
|
for (let cx = ucx; cx <= lcx; cx += 1) {
|
||||||
|
@ -203,8 +206,8 @@ export async function protectCanvasArea(
|
||||||
}
|
}
|
||||||
chunk = new Uint8Array(chunk);
|
chunk = new Uint8Array(chunk);
|
||||||
// offset of area in chunk
|
// offset of area in chunk
|
||||||
const cOffX = x - cx * TILE_SIZE + canvasMinXY;
|
const cOffX = x - cx * TILE_SIZE - canvasMinXY;
|
||||||
const cOffY = y - cy * TILE_SIZE + canvasMinXY;
|
const cOffY = y - cy * TILE_SIZE - canvasMinXY;
|
||||||
const cOffXE = cOffX + width;
|
const cOffXE = cOffX + width;
|
||||||
const cOffYE = cOffY + height;
|
const cOffYE = cOffY + height;
|
||||||
const startX = (cOffX > 0) ? cOffX : 0;
|
const startX = (cOffX > 0) ? cOffX : 0;
|
||||||
|
@ -212,19 +215,19 @@ export async function protectCanvasArea(
|
||||||
const endX = (cOffXE >= TILE_SIZE) ? TILE_SIZE : cOffXE;
|
const endX = (cOffXE >= TILE_SIZE) ? TILE_SIZE : cOffXE;
|
||||||
const endY = (cOffYE >= TILE_SIZE) ? TILE_SIZE : cOffYE;
|
const endY = (cOffYE >= TILE_SIZE) ? TILE_SIZE : cOffYE;
|
||||||
let pxlCnt = 0;
|
let pxlCnt = 0;
|
||||||
for (let py = startX; py < endX; py += 1) {
|
for (let py = startY; py < endY; py += 1) {
|
||||||
for (let px = startY; px < endY; px += 1) {
|
for (let px = startX; px < endX; px += 1) {
|
||||||
const offset = (px + py * TILE_SIZE) * 3;
|
const offset = px + py * TILE_SIZE;
|
||||||
if (protect) {
|
if (protect) {
|
||||||
chunk[offset] |= 0x80;
|
chunk[offset] |= 0x80;
|
||||||
} else {
|
} else {
|
||||||
chunk[offset] &= 0x07;
|
chunk[offset] &= 0x7F;
|
||||||
}
|
}
|
||||||
pxlCnt += 1;
|
pxlCnt += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pxlCnt) {
|
if (pxlCnt) {
|
||||||
const ret = await RedisCanvas.setChunk(cx, cy, chunk);
|
const ret = await RedisCanvas.setChunk(cx, cy, chunk, canvasId);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
logger.info(`Set protection for ${pxlCnt} pixels in chunk ${cx}, ${cy}.`);
|
logger.info(`Set protection for ${pxlCnt} pixels in chunk ${cx}, ${cy}.`);
|
||||||
|
|
183
src/core/Void.js
Normal file
183
src/core/Void.js
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
import webSockets from '../socket/websockets';
|
||||||
|
import WebSocketEvents from '../socket/WebSocketEvents';
|
||||||
|
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 extends WebSocketEvents {
|
||||||
|
i: number;
|
||||||
|
j: number;
|
||||||
|
maxClr: number;
|
||||||
|
msTimeout: number;
|
||||||
|
pixelStack: Array;
|
||||||
|
area: Object;
|
||||||
|
curRadius: number;
|
||||||
|
curAngle: number;
|
||||||
|
curAngleDelta: number;
|
||||||
|
ended: boolean;
|
||||||
|
|
||||||
|
constructor(centerCell) {
|
||||||
|
super();
|
||||||
|
// 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 = webSockets.onlineCounter;
|
||||||
|
const requiredSpeed = online * 4;
|
||||||
|
const ppm = Math.ceil(area / EVENT_DURATION_MIN + requiredSpeed);
|
||||||
|
// timeout between pixels
|
||||||
|
this.msTimeout = 60 * 1000 / ppm;
|
||||||
|
// area where we log placed pixels
|
||||||
|
this.area = new Uint8Array(TILE_SIZE * 3 * TILE_SIZE * 3);
|
||||||
|
// array of pixels that we place before continue building (instant-defense)
|
||||||
|
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);
|
||||||
|
webSockets.addListener(this);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(this.voidLoop, this.msTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
webSockets.remListener(this);
|
||||||
|
this.ended = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStatus() {
|
||||||
|
if (this.ended) {
|
||||||
|
webSockets.remListener(this);
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
return Math.floor(this.curRadius * 100 / TARGET_RADIUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastPixelBuffer(canvasId, chunkid, buffer) {
|
||||||
|
const {
|
||||||
|
i: pi,
|
||||||
|
j: pj,
|
||||||
|
offset: off,
|
||||||
|
color,
|
||||||
|
} = PixelUpdate.hydrate(buffer);
|
||||||
|
if (color <= 2 || color === 25) {
|
||||||
|
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) {
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Void;
|
|
@ -41,6 +41,9 @@ export const DISCORD_INVITE = process.env.DISCORD_INVITE
|
||||||
// Logging
|
// Logging
|
||||||
export const LOG_MYSQL = parseInt(process.env.LOG_MYSQL, 10) || false;
|
export const LOG_MYSQL = parseInt(process.env.LOG_MYSQL, 10) || false;
|
||||||
|
|
||||||
|
// do hourly event
|
||||||
|
export const HOURLY_EVENT = parseInt(process.env.HOURLY_EVENT, 10) || false;
|
||||||
|
|
||||||
// Accounts
|
// Accounts
|
||||||
export const APISOCKET_KEY = process.env.APISOCKET_KEY || 'changethis';
|
export const APISOCKET_KEY = process.env.APISOCKET_KEY || 'changethis';
|
||||||
// Comma seperated list of user ids of Admins
|
// Comma seperated list of user ids of Admins
|
||||||
|
|
|
@ -5,61 +5,21 @@ import { using } from 'bluebird';
|
||||||
import type { User } from '../data/models';
|
import type { User } from '../data/models';
|
||||||
import { redlock } from '../data/redis';
|
import { redlock } from '../data/redis';
|
||||||
import {
|
import {
|
||||||
getChunkOfPixel,
|
|
||||||
getOffsetOfPixel,
|
|
||||||
getPixelFromChunkOffset,
|
getPixelFromChunkOffset,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import webSockets from '../socket/websockets';
|
|
||||||
import logger, { pixelLogger } from './logger';
|
import logger, { pixelLogger } from './logger';
|
||||||
import RedisCanvas from '../data/models/RedisCanvas';
|
import RedisCanvas from '../data/models/RedisCanvas';
|
||||||
|
import {
|
||||||
|
setPixelByOffset,
|
||||||
|
setPixelByCoords,
|
||||||
|
} from './setPixel';
|
||||||
|
import rpgEvent from './event';
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import canvases from './canvases.json';
|
import canvases from './canvases.json';
|
||||||
|
|
||||||
import { THREE_CANVAS_HEIGHT, THREE_TILE_SIZE, TILE_SIZE } from './constants';
|
import { THREE_CANVAS_HEIGHT, THREE_TILE_SIZE, TILE_SIZE } from './constants';
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param canvasId
|
|
||||||
* @param canvasId
|
|
||||||
* @param color
|
|
||||||
* @param x
|
|
||||||
* @param y
|
|
||||||
* @param z optional, if given its 3d canvas
|
|
||||||
*/
|
|
||||||
export function setPixelByCoords(
|
|
||||||
canvasId: number,
|
|
||||||
color: ColorIndex,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
z: number = null,
|
|
||||||
) {
|
|
||||||
const canvasSize = canvases[canvasId].size;
|
|
||||||
const [i, j] = getChunkOfPixel(canvasSize, x, y, z);
|
|
||||||
const offset = getOffsetOfPixel(canvasSize, x, y, z);
|
|
||||||
RedisCanvas.setPixelInChunk(i, j, offset, color, canvasId);
|
|
||||||
webSockets.broadcastPixel(canvasId, i, j, offset, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* By Offset is prefered on server side
|
|
||||||
* @param canvasId
|
|
||||||
* @param i Chunk coordinates
|
|
||||||
* @param j
|
|
||||||
* @param offset Offset of pixel withing chunk
|
|
||||||
*/
|
|
||||||
export function setPixelByOffset(
|
|
||||||
canvasId: number,
|
|
||||||
color: ColorIndex,
|
|
||||||
i: number,
|
|
||||||
j: number,
|
|
||||||
offset: number,
|
|
||||||
) {
|
|
||||||
RedisCanvas.setPixelInChunk(i, j, offset, color, canvasId);
|
|
||||||
webSockets.broadcastPixel(canvasId, i, j, offset, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* By Offset is prefered on server side
|
* By Offset is prefered on server side
|
||||||
|
@ -143,6 +103,14 @@ export async function drawByOffset(
|
||||||
coolDown = (setColor & 0x3F) < canvas.cli ? canvas.bcd : canvas.pcd;
|
coolDown = (setColor & 0x3F) < canvas.cli ? canvas.bcd : canvas.pcd;
|
||||||
if (user.isAdmin()) {
|
if (user.isAdmin()) {
|
||||||
coolDown = 0.0;
|
coolDown = 0.0;
|
||||||
|
} else if (rpgEvent.success) {
|
||||||
|
if (rpgEvent.success === 1) {
|
||||||
|
// if HOURLY_EVENT got won
|
||||||
|
coolDown /= 2;
|
||||||
|
} else {
|
||||||
|
// if HOURLY_EVENT got lost
|
||||||
|
coolDown *= 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
@ -299,6 +267,14 @@ export async function drawByCoords(
|
||||||
let coolDown = (setColor & 0x3F) < canvas.cli ? canvas.bcd : canvas.pcd;
|
let coolDown = (setColor & 0x3F) < canvas.cli ? canvas.bcd : canvas.pcd;
|
||||||
if (user.isAdmin()) {
|
if (user.isAdmin()) {
|
||||||
coolDown = 0.0;
|
coolDown = 0.0;
|
||||||
|
} else if (rpgEvent.success) {
|
||||||
|
if (rpgEvent.success === 1) {
|
||||||
|
// if HOURLY_EVENT got won
|
||||||
|
coolDown /= 2;
|
||||||
|
} else {
|
||||||
|
// if HOURLY_EVENT got lost
|
||||||
|
coolDown *= 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
354
src/core/event.js
Normal file
354
src/core/event.js
Normal file
|
@ -0,0 +1,354 @@
|
||||||
|
/*
|
||||||
|
* This is an even that happens all 2h,
|
||||||
|
* if the users complete, they will get rewarded by half the cooldown sitewide
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import logger from './logger';
|
||||||
|
import {
|
||||||
|
nextEvent,
|
||||||
|
setNextEvent,
|
||||||
|
getEventArea,
|
||||||
|
clearOldEvent,
|
||||||
|
CANVAS_ID,
|
||||||
|
} from '../data/models/Event';
|
||||||
|
import Void from './Void';
|
||||||
|
import { protectCanvasArea } from './Image';
|
||||||
|
import { setPixelByOffset } from './setPixel';
|
||||||
|
import { TILE_SIZE } from './constants';
|
||||||
|
import chatProvider from './ChatProvider';
|
||||||
|
import { HOURLY_EVENT } from './config';
|
||||||
|
// eslint-disable-next-line import/no-unresolved
|
||||||
|
import canvases from './canvases.json';
|
||||||
|
|
||||||
|
// steps in minutes for event stages
|
||||||
|
const STEPS = [30, 10, 2, 1, 0, -10, -15, -40, -60];
|
||||||
|
// const STEPS = [4, 3, 2, 1, 0, -1, -2, -3, -4];
|
||||||
|
// gap between events in min, starting 1h after last event
|
||||||
|
// so 60 is a 2h gap, has to be higher than first and highest STEP numbers
|
||||||
|
const EVENT_GAP_MIN = 60;
|
||||||
|
// const EVENT_GAP_MIN = 5;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* draw cross in center of chunk
|
||||||
|
* @param centerCell chunk coordinates
|
||||||
|
* @param clr color
|
||||||
|
* @param style 0 solid, 1 dashed, 2 dashed invert
|
||||||
|
* @param radius Radius (total width/height will be radius * 2 + 1)
|
||||||
|
*/
|
||||||
|
function drawCross(centerCell, clr, style, radius) {
|
||||||
|
const [i, j] = centerCell;
|
||||||
|
const center = (TILE_SIZE + 1) * TILE_SIZE / 2;
|
||||||
|
if (style !== 2) {
|
||||||
|
setPixelByOffset(CANVAS_ID, clr, i, j, center);
|
||||||
|
}
|
||||||
|
for (let r = 1; r < radius; r += 1) {
|
||||||
|
if (style) {
|
||||||
|
if (r % 2) {
|
||||||
|
if (style === 1) continue;
|
||||||
|
} else if (style === 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let offset = center - TILE_SIZE * r;
|
||||||
|
setPixelByOffset(CANVAS_ID, clr, i, j, offset);
|
||||||
|
offset = center + TILE_SIZE * r;
|
||||||
|
setPixelByOffset(CANVAS_ID, clr, i, j, offset);
|
||||||
|
offset = center - r;
|
||||||
|
setPixelByOffset(CANVAS_ID, clr, i, j, offset);
|
||||||
|
offset = center + r;
|
||||||
|
setPixelByOffset(CANVAS_ID, clr, i, j, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Event {
|
||||||
|
eventState: number;
|
||||||
|
eventTimestamp: number;
|
||||||
|
eventCenter: Array;
|
||||||
|
eventCenterC: Array;
|
||||||
|
eventArea: Array;
|
||||||
|
success: boolean;
|
||||||
|
void: Object;
|
||||||
|
chatTimeout: number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.enabled = HOURLY_EVENT;
|
||||||
|
this.eventState = -1;
|
||||||
|
this.eventCenterC = null;
|
||||||
|
this.void = null;
|
||||||
|
// 0 if waiting
|
||||||
|
// 1 if won
|
||||||
|
// 2 if lost
|
||||||
|
this.success = 0;
|
||||||
|
this.chatTimeout = 0;
|
||||||
|
this.runEventLoop = this.runEventLoop.bind(this);
|
||||||
|
if (HOURLY_EVENT) {
|
||||||
|
this.initEventLoop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async initEventLoop() {
|
||||||
|
let eventTimestamp = await nextEvent();
|
||||||
|
if (!eventTimestamp) {
|
||||||
|
eventTimestamp = await Event.setNextEvent();
|
||||||
|
await this.calcEventCenter();
|
||||||
|
const [x, y, w, h] = this.eventArea;
|
||||||
|
await protectCanvasArea(CANVAS_ID, x, y, w, h, true);
|
||||||
|
}
|
||||||
|
this.eventTimestamp = eventTimestamp;
|
||||||
|
await this.calcEventCenter();
|
||||||
|
this.runEventLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
eventTimer() {
|
||||||
|
const now = Date.now();
|
||||||
|
return Math.floor((this.eventTimestamp - now) / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async calcEventCenter() {
|
||||||
|
const cCoor = await getEventArea();
|
||||||
|
if (cCoor) {
|
||||||
|
this.eventCenterC = cCoor;
|
||||||
|
const {
|
||||||
|
size: canvasSize,
|
||||||
|
} = canvases[CANVAS_ID];
|
||||||
|
const [ux, uy] = cCoor.map((z) => (z - 1) * TILE_SIZE - canvasSize / 2);
|
||||||
|
this.eventArea = [ux, uy, TILE_SIZE * 3, TILE_SIZE * 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDirection(x, y) {
|
||||||
|
const { size: canvasSize } = canvases[CANVAS_ID];
|
||||||
|
let direction = null;
|
||||||
|
const distSquared = x ** 2 + y ** 2;
|
||||||
|
|
||||||
|
if (distSquared < 1000 ** 2) direction = 'center';
|
||||||
|
else if (x < 0 && y < 0) direction = 'North-West';
|
||||||
|
else if (x >= 0 && y < 0) direction = 'North-East';
|
||||||
|
else if (x < 0 && y >= 0) direction = 'South-West';
|
||||||
|
else if (x >= 0 && y >= 0) direction = 'South-East';
|
||||||
|
if (distSquared > (canvasSize / 2) ** 2) direction = `far ${direction}`;
|
||||||
|
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async setNextEvent() {
|
||||||
|
// define next Event area
|
||||||
|
const { size: canvasSize } = canvases[CANVAS_ID];
|
||||||
|
// make sure that its the center of a 3x3 area
|
||||||
|
const i = Math.floor(Math.random() * (canvasSize / TILE_SIZE - 2)) + 1;
|
||||||
|
const j = Math.floor(Math.random() * (canvasSize / TILE_SIZE - 2)) + 1;
|
||||||
|
// backup it and schedul next event in 1h
|
||||||
|
await setNextEvent(EVENT_GAP_MIN, i, j);
|
||||||
|
const timestamp = await nextEvent();
|
||||||
|
const x = i * TILE_SIZE - canvasSize / 2;
|
||||||
|
const y = j * TILE_SIZE - canvasSize / 2;
|
||||||
|
chatProvider.broadcastChatMessage(
|
||||||
|
'event',
|
||||||
|
`Suspicious activity spotted in ${Event.getDirection(x, y)}`,
|
||||||
|
);
|
||||||
|
drawCross([i, j], 19, 0, 13);
|
||||||
|
logger.info(`Set next Event in 60min at ${x},${y}`);
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
async runEventLoop() {
|
||||||
|
const {
|
||||||
|
eventState,
|
||||||
|
} = this;
|
||||||
|
const eventSeconds = this.eventTimer();
|
||||||
|
const eventMinutes = eventSeconds / 60;
|
||||||
|
|
||||||
|
if (eventMinutes > STEPS[0]) {
|
||||||
|
// 1h to 30min before Event: blinking dotted cross
|
||||||
|
if (eventState !== 1) {
|
||||||
|
this.eventState = 1;
|
||||||
|
// color 15 protected
|
||||||
|
drawCross(this.eventCenterC, 15, 1, 9);
|
||||||
|
drawCross(this.eventCenterC, 0, 2, 9);
|
||||||
|
} else {
|
||||||
|
this.eventState = 2;
|
||||||
|
drawCross(this.eventCenterC, 16, 2, 9);
|
||||||
|
drawCross(this.eventCenterC, 0, 1, 9);
|
||||||
|
}
|
||||||
|
setTimeout(this.runEventLoop, 2000);
|
||||||
|
} else if (eventMinutes > STEPS[1]) {
|
||||||
|
// 10min to 30min before Event: blinking solid cross
|
||||||
|
if (eventState !== 3 && eventState !== 4) {
|
||||||
|
this.eventState = 3;
|
||||||
|
const [x, y] = this.eventArea;
|
||||||
|
chatProvider.broadcastChatMessage(
|
||||||
|
'event',
|
||||||
|
`Unstable area at ${Event.getDirection(x, y)} at concerning level`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (eventState !== 3) {
|
||||||
|
this.eventState = 3;
|
||||||
|
drawCross(this.eventCenterC, 30, 1, 9);
|
||||||
|
drawCross(this.eventCenterC, 0, 2, 9);
|
||||||
|
} else {
|
||||||
|
this.eventState = 4;
|
||||||
|
drawCross(this.eventCenterC, 31, 2, 9);
|
||||||
|
drawCross(this.eventCenterC, 0, 1, 9);
|
||||||
|
}
|
||||||
|
setTimeout(this.runEventLoop, 1500);
|
||||||
|
} else if (eventMinutes > STEPS[2]) {
|
||||||
|
// 2min to 10min before Event: blinking solid cross
|
||||||
|
if (eventState !== 5) {
|
||||||
|
this.eventState = 5;
|
||||||
|
drawCross(this.eventCenterC, 12, 0, 7);
|
||||||
|
} else {
|
||||||
|
this.eventState = 6;
|
||||||
|
drawCross(this.eventCenterC, 13, 0, 7);
|
||||||
|
}
|
||||||
|
setTimeout(this.runEventLoop, 1000);
|
||||||
|
} else if (eventMinutes > STEPS[3]) {
|
||||||
|
// 1min to 2min before Event: blinking solid cross red small
|
||||||
|
if (eventState !== 7 && eventState !== 8) {
|
||||||
|
this.eventState = 7;
|
||||||
|
const [x, y] = this.eventArea;
|
||||||
|
const [xNear, yNear] = [x, y].map((z) => {
|
||||||
|
const rand = Math.random() * 3000 - 500;
|
||||||
|
return Math.floor(z + TILE_SIZE * 1.5 + rand);
|
||||||
|
});
|
||||||
|
chatProvider.broadcastChatMessage(
|
||||||
|
'event',
|
||||||
|
`Alert! Void is rising in 2min near #d,${xNear},${yNear},30`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (eventState !== 7) {
|
||||||
|
drawCross(this.eventCenterC, 11, 0, 5);
|
||||||
|
this.eventState = 7;
|
||||||
|
} else {
|
||||||
|
drawCross(this.eventCenterC, 12, 0, 5);
|
||||||
|
this.eventState = 8;
|
||||||
|
}
|
||||||
|
setTimeout(this.runEventLoop, 1000);
|
||||||
|
} else if (eventMinutes > STEPS[4]) {
|
||||||
|
// 1min till Event: blinking solid cross red small fase
|
||||||
|
if (eventState !== 9 && eventState !== 10) {
|
||||||
|
this.eventState = 9;
|
||||||
|
chatProvider.broadcastChatMessage(
|
||||||
|
'event',
|
||||||
|
'Alert! Threat rising!',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (eventState !== 9) {
|
||||||
|
this.eventState = 9;
|
||||||
|
drawCross(this.eventCenterC, 11, 0, 3);
|
||||||
|
} else {
|
||||||
|
this.eventState = 10;
|
||||||
|
drawCross(this.eventCenterC, 19, 0, 3);
|
||||||
|
}
|
||||||
|
setTimeout(this.runEventLoop, 500);
|
||||||
|
} else if (eventMinutes > STEPS[5]) {
|
||||||
|
if (eventState !== 11) {
|
||||||
|
// start event
|
||||||
|
const [x, y, w, h] = this.eventArea;
|
||||||
|
await protectCanvasArea(CANVAS_ID, x, y, w, h, false);
|
||||||
|
logger.info(`Starting Event at ${x},${y} now`);
|
||||||
|
chatProvider.broadcastChatMessage(
|
||||||
|
'event',
|
||||||
|
'Fight starting!',
|
||||||
|
);
|
||||||
|
this.void = new Void(this.eventCenterC);
|
||||||
|
this.eventState = 11;
|
||||||
|
} else if (this.void) {
|
||||||
|
const percent = this.void.checkStatus();
|
||||||
|
if (percent === 100) {
|
||||||
|
// event lost
|
||||||
|
logger.info(`Event got lost after ${Math.abs(eventMinutes)} min`);
|
||||||
|
chatProvider.broadcastChatMessage(
|
||||||
|
'event',
|
||||||
|
'Threat couldn\'t be contained, abandon area',
|
||||||
|
);
|
||||||
|
this.success = 2;
|
||||||
|
this.void = null;
|
||||||
|
const [x, y, w, h] = this.eventArea;
|
||||||
|
await protectCanvasArea(CANVAS_ID, x, y, w, h, true);
|
||||||
|
} else {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now > this.chatTimeout) {
|
||||||
|
chatProvider.broadcastChatMessage(
|
||||||
|
'event',
|
||||||
|
`Void reached ${percent}% of its max size`,
|
||||||
|
);
|
||||||
|
this.chatTimeout = now + 40000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// run event for 10min
|
||||||
|
setTimeout(this.runEventLoop, 1000);
|
||||||
|
} else if (eventMinutes > STEPS[6]) {
|
||||||
|
if (eventState !== 12) {
|
||||||
|
// after 10min of event
|
||||||
|
// check if won
|
||||||
|
if (this.void) {
|
||||||
|
if (this.void.checkStatus() !== 100) {
|
||||||
|
// event won
|
||||||
|
logger.info('Event got won! Cooldown sitewide now half.');
|
||||||
|
chatProvider.broadcastChatMessage(
|
||||||
|
'event',
|
||||||
|
'Threat successfully defeated. Good work!',
|
||||||
|
);
|
||||||
|
this.success = 1;
|
||||||
|
}
|
||||||
|
this.void.cancel();
|
||||||
|
this.void = null;
|
||||||
|
}
|
||||||
|
this.eventState = 12;
|
||||||
|
}
|
||||||
|
// for 30min after event
|
||||||
|
// do nothing
|
||||||
|
setTimeout(this.runEventLoop, 60000);
|
||||||
|
} else if (eventMinutes > STEPS[7]) {
|
||||||
|
if (eventState !== 13) {
|
||||||
|
// 5min after last Event
|
||||||
|
// end debuff if lost
|
||||||
|
if (this.success === 2) {
|
||||||
|
chatProvider.broadcastChatMessage(
|
||||||
|
'event',
|
||||||
|
'Void seems to leave again.',
|
||||||
|
);
|
||||||
|
this.success = 0;
|
||||||
|
}
|
||||||
|
this.eventState = 13;
|
||||||
|
}
|
||||||
|
setTimeout(this.runEventLoop, 60000);
|
||||||
|
} else if (eventMinutes > STEPS[8]) {
|
||||||
|
if (eventState !== 14) {
|
||||||
|
// 30min after last Event
|
||||||
|
// clear old event area
|
||||||
|
// reset success state
|
||||||
|
logger.info('Restoring old event area');
|
||||||
|
await clearOldEvent();
|
||||||
|
if (this.success === 1) {
|
||||||
|
chatProvider.broadcastChatMessage(
|
||||||
|
'event',
|
||||||
|
'Celebration time over, get back to work.',
|
||||||
|
);
|
||||||
|
this.success = 0;
|
||||||
|
}
|
||||||
|
this.eventState = 14;
|
||||||
|
}
|
||||||
|
// 30min to 50min after last Event
|
||||||
|
// do nothing
|
||||||
|
setTimeout(this.runEventLoop, 60000);
|
||||||
|
} else {
|
||||||
|
// 50min after last Event / 1h before next Event
|
||||||
|
// define and protect it
|
||||||
|
this.eventTimestamp = await Event.setNextEvent();
|
||||||
|
await this.calcEventCenter();
|
||||||
|
const [x, y, w, h] = this.eventArea;
|
||||||
|
await protectCanvasArea(CANVAS_ID, x, y, w, h, true);
|
||||||
|
|
||||||
|
setTimeout(this.runEventLoop, 60000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rpgEvent = new Event();
|
||||||
|
|
||||||
|
export default rpgEvent;
|
52
src/core/setPixel.js
Normal file
52
src/core/setPixel.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/* @flow */
|
||||||
|
import RedisCanvas from '../data/models/RedisCanvas';
|
||||||
|
import webSockets from '../socket/websockets';
|
||||||
|
import {
|
||||||
|
getChunkOfPixel,
|
||||||
|
getOffsetOfPixel,
|
||||||
|
} from './utils';
|
||||||
|
// eslint-disable-next-line import/no-unresolved
|
||||||
|
import canvases from './canvases.json';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param canvasId
|
||||||
|
* @param canvasId
|
||||||
|
* @param color
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
* @param z optional, if given its 3d canvas
|
||||||
|
*/
|
||||||
|
export function setPixelByCoords(
|
||||||
|
canvasId: number,
|
||||||
|
color: ColorIndex,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number = null,
|
||||||
|
) {
|
||||||
|
const canvasSize = canvases[canvasId].size;
|
||||||
|
const [i, j] = getChunkOfPixel(canvasSize, x, y, z);
|
||||||
|
const offset = getOffsetOfPixel(canvasSize, x, y, z);
|
||||||
|
RedisCanvas.setPixelInChunk(i, j, offset, color, canvasId);
|
||||||
|
webSockets.broadcastPixel(canvasId, i, j, offset, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* By Offset is prefered on server side
|
||||||
|
* @param canvasId
|
||||||
|
* @param i Chunk coordinates
|
||||||
|
* @param j
|
||||||
|
* @param offset Offset of pixel withing chunk
|
||||||
|
*/
|
||||||
|
export function setPixelByOffset(
|
||||||
|
canvasId: number,
|
||||||
|
color: ColorIndex,
|
||||||
|
i: number,
|
||||||
|
j: number,
|
||||||
|
offset: number,
|
||||||
|
) {
|
||||||
|
RedisCanvas.setPixelInChunk(i, j, offset, color, canvasId);
|
||||||
|
webSockets.broadcastPixel(canvasId, i, j, offset, color);
|
||||||
|
}
|
106
src/data/models/Event.js
Normal file
106
src/data/models/Event.js
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* data saving for hourly events
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
// its ok if its slow
|
||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
|
||||||
|
import redis from '../redis';
|
||||||
|
import logger from '../../core/logger';
|
||||||
|
import RedisCanvas from './RedisCanvas';
|
||||||
|
|
||||||
|
const EVENT_TIMESTAMP_KEY = 'evt:time';
|
||||||
|
const EVENT_POSITION_KEY = 'evt:pos';
|
||||||
|
const EVENT_BACKUP_PREFIX = 'evt:bck';
|
||||||
|
// Note: Events always happen on canvas 0
|
||||||
|
export const CANVAS_ID = '0';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @return time till next event in seconds
|
||||||
|
*/
|
||||||
|
export async function nextEvent() {
|
||||||
|
const timestamp = await redis.getAsync(EVENT_TIMESTAMP_KEY);
|
||||||
|
if (timestamp) {
|
||||||
|
return Number(timestamp.toString());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @return cell of chunk coordinates of event
|
||||||
|
*/
|
||||||
|
export async function getEventArea() {
|
||||||
|
const pos = await redis.getAsync(EVENT_POSITION_KEY);
|
||||||
|
if (pos) {
|
||||||
|
return pos.toString().split(':').map((z) => Number(z));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* restore area effected by last event
|
||||||
|
*/
|
||||||
|
export async function clearOldEvent() {
|
||||||
|
const pos = await getEventArea();
|
||||||
|
if (pos) {
|
||||||
|
const [i, j] = pos;
|
||||||
|
logger.info(`Restore last event area at ${i}/${j}`);
|
||||||
|
// 3x3 chunk area centered at i,j
|
||||||
|
for (let jc = j - 1; jc <= j + 1; jc += 1) {
|
||||||
|
for (let ic = i - 1; ic <= i + 1; ic += 1) {
|
||||||
|
const chunkKey = `${EVENT_BACKUP_PREFIX}:${ic}:${jc}`;
|
||||||
|
const chunk = await redis.getAsync(chunkKey);
|
||||||
|
if (!chunk) {
|
||||||
|
logger.warn(
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
`Couldn't get chunk event backup for ${ic}/${jc}, which is weird`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (chunk.length <= 256) {
|
||||||
|
logger.info(
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
`Tiny chunk in event backup, not-generated chunk at ${ic}/${jc}`,
|
||||||
|
);
|
||||||
|
await redis.delAsync(`ch:${CANVAS_ID}:${ic}:${jc}`);
|
||||||
|
} else {
|
||||||
|
logger.info(
|
||||||
|
`Restoring chunk ${ic}/${jc} from event`,
|
||||||
|
);
|
||||||
|
await redis.setAsync(`ch:${CANVAS_ID}:${ic}:${jc}`, chunk);
|
||||||
|
}
|
||||||
|
await redis.delAsync(chunkKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await redis.delAsync(EVENT_POSITION_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set time of next event
|
||||||
|
* @param minutes minutes till next event
|
||||||
|
* @param i, j chunk coordinates of center of event
|
||||||
|
*/
|
||||||
|
export async function setNextEvent(minutes: number, i: number, j: number) {
|
||||||
|
await clearOldEvent();
|
||||||
|
for (let jc = j - 1; jc <= j + 1; jc += 1) {
|
||||||
|
for (let ic = i - 1; ic <= i + 1; ic += 1) {
|
||||||
|
let chunk = await redis.getAsync(`ch:${CANVAS_ID}:${ic}:${jc}`);
|
||||||
|
if (!chunk) {
|
||||||
|
// place a dummy Array inside to mark chunk as none-existent
|
||||||
|
const buff = new Uint8Array(3);
|
||||||
|
chunk = Buffer.from(buff);
|
||||||
|
// place dummy pixel to make RedisCanvas create chunk
|
||||||
|
await RedisCanvas.setPixelInChunk(ic, jc, 0, 0, CANVAS_ID);
|
||||||
|
}
|
||||||
|
const chunkKey = `${EVENT_BACKUP_PREFIX}:${ic}:${jc}`;
|
||||||
|
await redis.setAsync(chunkKey, chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await redis.setAsync(EVENT_POSITION_KEY, `${i}:${j}`);
|
||||||
|
const timestamp = Date.now() + minutes * 60 * 1000;
|
||||||
|
await redis.setAsync(EVENT_TIMESTAMP_KEY, timestamp);
|
||||||
|
}
|
|
@ -38,7 +38,9 @@ class RedisCanvas {
|
||||||
i: number,
|
i: number,
|
||||||
j: number,
|
j: number,
|
||||||
): Promise<Buffer> {
|
): Promise<Buffer> {
|
||||||
// this key is also hardcoded into core/tilesBackup.js
|
// this key is also hardcoded into
|
||||||
|
// core/tilesBackup.js
|
||||||
|
// and ./EventData.js
|
||||||
const key = `ch:${canvasId}:${i}:${j}`;
|
const key = `ch:${canvasId}:${i}:${j}`;
|
||||||
return redis.getAsync(key);
|
return redis.getAsync(key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@ import WebSocketEvents from './WebSocketEvents';
|
||||||
import webSockets from './websockets';
|
import webSockets from './websockets';
|
||||||
import { getIPFromRequest } from '../utils/ip';
|
import { getIPFromRequest } from '../utils/ip';
|
||||||
import Minecraft from '../core/minecraft';
|
import Minecraft from '../core/minecraft';
|
||||||
import { drawByCoords, setPixelByCoords } from '../core/draw';
|
import { setPixelByCoords } from '../core/setPixel';
|
||||||
|
import { drawByCoords } from '../core/draw';
|
||||||
import logger from '../core/logger';
|
import logger from '../core/logger';
|
||||||
import { APISOCKET_KEY } from '../core/config';
|
import { APISOCKET_KEY } from '../core/config';
|
||||||
import chatProvider from '../core/ChatProvider';
|
import chatProvider from '../core/ChatProvider';
|
||||||
|
|
|
@ -11,15 +11,24 @@ import PixelUpdate from './packets/PixelUpdateServer';
|
||||||
|
|
||||||
class WebSockets {
|
class WebSockets {
|
||||||
listeners: Array<Object>;
|
listeners: Array<Object>;
|
||||||
|
onlineCounter: number;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.listeners = [];
|
this.listeners = [];
|
||||||
|
this.onlineCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
addListener(listener) {
|
addListener(listener) {
|
||||||
this.listeners.push(listener);
|
this.listeners.push(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remListener(listener) {
|
||||||
|
const index = this.listeners.indexOf(listener);
|
||||||
|
if (index > -1) {
|
||||||
|
this.listeners.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* broadcast message via websocket
|
* broadcast message via websocket
|
||||||
* @param message Message to send
|
* @param message Message to send
|
||||||
|
@ -125,6 +134,7 @@ class WebSockets {
|
||||||
* @param online Number of users online
|
* @param online Number of users online
|
||||||
*/
|
*/
|
||||||
broadcastOnlineCounter(online: number) {
|
broadcastOnlineCounter(online: number) {
|
||||||
|
this.onlineCounter = online;
|
||||||
const buffer = OnlineCounter.dehydrate({ online });
|
const buffer = OnlineCounter.dehydrate({ online });
|
||||||
this.listeners.forEach(
|
this.listeners.forEach(
|
||||||
(listener) => listener.broadcastOnlineCounter(buffer),
|
(listener) => listener.broadcastOnlineCounter(buffer),
|
||||||
|
|
|
@ -95,6 +95,9 @@ tr:nth-child(even) {
|
||||||
.msg.info{
|
.msg.info{
|
||||||
color: #ff91a6;
|
color: #ff91a6;
|
||||||
}
|
}
|
||||||
|
.msg.event{
|
||||||
|
color: #9dc8ff;
|
||||||
|
}
|
||||||
.msg.greentext{
|
.msg.greentext{
|
||||||
color: #94ff94;
|
color: #94ff94;
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,9 @@ tr:nth-child(even) {
|
||||||
.msg.info{
|
.msg.info{
|
||||||
color: #ff91a6;
|
color: #ff91a6;
|
||||||
}
|
}
|
||||||
|
.msg.event{
|
||||||
|
color: #9dc8ff;
|
||||||
|
}
|
||||||
.msg.greentext{
|
.msg.greentext{
|
||||||
color: #94ff94;
|
color: #94ff94;
|
||||||
}
|
}
|
||||||
|
|
|
@ -409,6 +409,9 @@ tr:nth-child(even) {
|
||||||
.msg.info {
|
.msg.info {
|
||||||
color: #cc0000;
|
color: #cc0000;
|
||||||
}
|
}
|
||||||
|
.msg.event {
|
||||||
|
color: #3955c6;
|
||||||
|
}
|
||||||
.msg.greentext{
|
.msg.greentext{
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user