196 lines
4.5 KiB
JavaScript
196 lines
4.5 KiB
JavaScript
/* @flow */
|
|
|
|
import { using } from 'bluebird';
|
|
|
|
import type { User } from '../data/models';
|
|
import { redlock } from '../data/redis';
|
|
import { getChunkOfPixel, getOffsetOfPixel } from './utils';
|
|
import webSockets from '../socket/websockets';
|
|
import logger from './logger';
|
|
import RedisCanvas from '../data/models/RedisCanvas';
|
|
import canvases from '../canvases.json';
|
|
|
|
import { THREE_CANVAS_HEIGHT } from './constants';
|
|
|
|
|
|
/**
|
|
*
|
|
* @param canvasId
|
|
* @param x
|
|
* @param y
|
|
* @param color
|
|
*/
|
|
export function setPixel(
|
|
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);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param user
|
|
* @param canvasId
|
|
* @param x
|
|
* @param y
|
|
* @param color
|
|
* @returns {Promise.<Object>}
|
|
*/
|
|
async function draw(
|
|
user: User,
|
|
canvasId: number,
|
|
color: ColorIndex,
|
|
x: number,
|
|
y: number,
|
|
z: number = null,
|
|
): Promise<Object> {
|
|
if (!({}.hasOwnProperty.call(canvases, canvasId))) {
|
|
return {
|
|
error: 'This canvas does not exist',
|
|
success: false,
|
|
};
|
|
}
|
|
const canvas = canvases[canvasId];
|
|
|
|
const canvasMaxXY = canvas.size / 2;
|
|
const canvasMinXY = -canvasMaxXY;
|
|
if (x < canvasMinXY || y < canvasMinXY
|
|
|| x >= canvasMaxXY || y >= canvasMaxXY) {
|
|
return {
|
|
error: 'Coordinates not within canvas',
|
|
success: false,
|
|
};
|
|
}
|
|
if (z !== null) {
|
|
if (z >= THREE_CANVAS_HEIGHT) {
|
|
return {
|
|
error: 'You reached build limit. Can\'t place higher than 128 blocks.',
|
|
success: false,
|
|
};
|
|
}
|
|
if (!canvas.v) {
|
|
return {
|
|
error: 'This is not a 3D canvas',
|
|
success: false,
|
|
};
|
|
}
|
|
} else if (canvas.v) {
|
|
return {
|
|
error: 'This is a 3D canvas. z is required.',
|
|
success: false,
|
|
};
|
|
}
|
|
|
|
if (canvas.req !== -1) {
|
|
if (user.id === null) {
|
|
return {
|
|
errorTitle: 'Not Logged In',
|
|
error: 'You need to be logged in to use this canvas.',
|
|
success: false,
|
|
};
|
|
}
|
|
// if the canvas has a requirement of totalPixels that the user
|
|
// has to have set
|
|
const totalPixels = await user.getTotalPixels();
|
|
if (totalPixels < canvas.req) {
|
|
return {
|
|
errorTitle: 'Not Yet :(',
|
|
// eslint-disable-next-line max-len
|
|
error: `You need to set ${canvas.req} pixels on another canvas first, before you can use this one.`,
|
|
success: false,
|
|
};
|
|
}
|
|
}
|
|
|
|
const setColor = await RedisCanvas.getPixel(canvasId, x, y, z);
|
|
|
|
let coolDown = !(setColor & 0x1E) ? canvas.bcd : canvas.pcd;
|
|
if (user.isAdmin()) {
|
|
coolDown = 0.0;
|
|
}
|
|
|
|
const now = Date.now();
|
|
let wait = await user.getWait(canvasId);
|
|
if (!wait) wait = now;
|
|
wait += coolDown;
|
|
const waitLeft = wait - now;
|
|
if (waitLeft > canvas.cds) {
|
|
return {
|
|
success: false,
|
|
waitSeconds: (waitLeft - coolDown) / 1000,
|
|
coolDownSeconds: (canvas.cds - waitLeft) / 1000,
|
|
};
|
|
}
|
|
|
|
if (setColor & 0x20) {
|
|
logger.info(`${user.ip} tried to set on protected pixel (${x}, ${y})`);
|
|
return {
|
|
errorTitle: 'Pixel Protection',
|
|
error: 'This pixel is protected',
|
|
success: false,
|
|
waitSeconds: (waitLeft - coolDown) / 1000,
|
|
};
|
|
}
|
|
|
|
setPixel(canvasId, color, x, y, null);
|
|
|
|
user.setWait(waitLeft, canvasId);
|
|
user.incrementPixelcount();
|
|
return {
|
|
success: true,
|
|
waitSeconds: waitLeft / 1000,
|
|
coolDownSeconds: coolDown / 1000,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* This function is a wrapper for draw. It fixes race condition exploits
|
|
* It permits just placing one pixel at a time per user.
|
|
*
|
|
* @param user
|
|
* @param canvasId
|
|
* @param x
|
|
* @param y
|
|
* @param color
|
|
* @returns {Promise.<boolean>}
|
|
*/
|
|
function drawSafe(
|
|
user: User,
|
|
canvasId: number,
|
|
color: ColorIndex,
|
|
x: number,
|
|
y: number,
|
|
z: number = null,
|
|
): Promise<Cell> {
|
|
if (user.isAdmin()) {
|
|
return draw(user, canvasId, color, x, y, z);
|
|
}
|
|
|
|
// can just check for one unique occurence,
|
|
// we use ip, because id for logged out users is
|
|
// always null
|
|
const userId = user.ip;
|
|
|
|
return new Promise((resolve) => {
|
|
using(
|
|
redlock.disposer(`locks:${userId}`, 5000, logger.error),
|
|
async () => {
|
|
const ret = await draw(user, canvasId, color, x, y, z);
|
|
resolve(ret);
|
|
},
|
|
); // <-- unlock is automatically handled by bluebird
|
|
});
|
|
}
|
|
|
|
|
|
export const drawUnsafe = draw;
|
|
|
|
export default drawSafe;
|