pixelplanet/src/core/event.js
2020-05-24 23:23:19 +02:00

355 lines
11 KiB
JavaScript

/*
* 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;