be able to send uid null
This commit is contained in:
parent
5ebf16aa08
commit
7344945a43
BIN
avatars/flags.png
Normal file
BIN
avatars/flags.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
127
ppfun-bridge/src/pixelplanet/Palette.js
Normal file
127
ppfun-bridge/src/pixelplanet/Palette.js
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
|
||||||
|
class Palette {
|
||||||
|
|
||||||
|
constructor(colors) {
|
||||||
|
this.length = colors.length;
|
||||||
|
this.rgb = new Uint8Array(this.length * 3);
|
||||||
|
this.colors = new Array(this.length);
|
||||||
|
this.abgr = new Uint32Array(this.length);
|
||||||
|
this.fl = new Array(this.length);
|
||||||
|
|
||||||
|
let cnt = 0;
|
||||||
|
for (let index = 0; index < colors.length; index++) {
|
||||||
|
const r = colors[index][0];
|
||||||
|
const g = colors[index][1];
|
||||||
|
const b = colors[index][2];
|
||||||
|
this.rgb[cnt++] = r;
|
||||||
|
this.rgb[cnt++] = g;
|
||||||
|
this.rgb[cnt++] = b;
|
||||||
|
this.colors[index] = `rgb(${r}, ${g}, ${b})`;
|
||||||
|
this.abgr[index] = (0xFF000000) | (b << 16) | (g << 8) | (r);
|
||||||
|
this.fl[index] = [r / 256, g / 256, b / 256];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if a color is light (closer to white) or dark (closer to black)
|
||||||
|
* @param color Index of color in palette
|
||||||
|
* @return dark True if color is dark
|
||||||
|
*/
|
||||||
|
isDark(color) {
|
||||||
|
color *= 3;
|
||||||
|
const r = this.rgb[color++];
|
||||||
|
const g = this.rgb[color++];
|
||||||
|
const b = this.rgb[color];
|
||||||
|
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||||
|
return (luminance < 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get last matching color index of RGB color
|
||||||
|
* @param r r
|
||||||
|
* @param g g
|
||||||
|
* @param b b
|
||||||
|
* @return index of color
|
||||||
|
*/
|
||||||
|
getIndexOfColor(r, g, b) {
|
||||||
|
const { rgb } = this;
|
||||||
|
let i = rgb.length / 3;
|
||||||
|
while (i > 0) {
|
||||||
|
i -= 1;
|
||||||
|
const off = i * 3;
|
||||||
|
if (rgb[off] === r
|
||||||
|
&& rgb[off + 1] === g
|
||||||
|
&& rgb[off + 2] === b
|
||||||
|
) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Take a buffer of indexed pixels and output it as ABGR Array
|
||||||
|
* @param chunkBuffer Buffer of indexed pixels
|
||||||
|
* @return ABRG Buffer
|
||||||
|
*/
|
||||||
|
buffer2ABGR(chunkBuffer) {
|
||||||
|
const { length } = chunkBuffer;
|
||||||
|
const colors = new Uint32Array(length);
|
||||||
|
let value;
|
||||||
|
const buffer = chunkBuffer;
|
||||||
|
|
||||||
|
let pos = 0;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
value = (buffer[i] & 0x3F);
|
||||||
|
colors[pos++] = this.abgr[value];
|
||||||
|
}
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Take a buffer of indexed pixels and output it as RGB Array
|
||||||
|
* @param chunkBuffer Buffer of indexed pixels
|
||||||
|
* @return RGB Buffer
|
||||||
|
*/
|
||||||
|
buffer2RGB(chunkBuffer) {
|
||||||
|
const { length } = chunkBuffer;
|
||||||
|
const colors = new Uint8Array(length * 3);
|
||||||
|
let color;
|
||||||
|
let value;
|
||||||
|
const buffer = chunkBuffer;
|
||||||
|
|
||||||
|
let c = 0;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
value = buffer[i];
|
||||||
|
|
||||||
|
color = (value & 0x3F) * 3;
|
||||||
|
colors[c++] = this.rgb[color++];
|
||||||
|
colors[c++] = this.rgb[color++];
|
||||||
|
colors[c++] = this.rgb[color];
|
||||||
|
}
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a RGB Buffer of a specific size with just one color
|
||||||
|
* @param color Color Index of color to use
|
||||||
|
* @param length Length of needed Buffer
|
||||||
|
* @return RGB Buffer of wanted size with just one color
|
||||||
|
*/
|
||||||
|
oneColorBuffer(color, length) {
|
||||||
|
const buffer = new Uint8Array(length * 3);
|
||||||
|
const r = this.rgb[color * 3];
|
||||||
|
const g = this.rgb[color * 3 + 1];
|
||||||
|
const b = this.rgb[color * 3 + 2];
|
||||||
|
let pos = 0;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
buffer[pos++] = r;
|
||||||
|
buffer[pos++] = g;
|
||||||
|
buffer[pos++] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Palette;
|
5
ppfun-bridge/src/pixelplanet/constants.js
Normal file
5
ppfun-bridge/src/pixelplanet/constants.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export const WIDTH = 1024;
|
||||||
|
export const HEIGHT = 768;
|
||||||
|
export const MAX_SCALE = 40; // 52 in log2
|
||||||
|
export const TILE_SIZE = 256;
|
||||||
|
export const TILE_ZOOM_LEVEL = 4;
|
73
ppfun-bridge/src/pixelplanet/index.js
Normal file
73
ppfun-bridge/src/pixelplanet/index.js
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
|
import renderCanvas from './renderCanvas';
|
||||||
|
import Palette from './Palette';
|
||||||
|
import {
|
||||||
|
TILE_SIZE,
|
||||||
|
TILE_ZOOM_LEVEL,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
const linkRegExp = /(#[a-z]*,-?[0-9]*,-?[0-9]*(,-?[0-9]+)?)/gi;
|
||||||
|
const linkRegExpFilter = (val, ind) => ((ind % 3) !== 2);
|
||||||
|
|
||||||
|
let canvases = null;
|
||||||
|
|
||||||
|
async function fetchCanvasData() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://pixelplanet.fun/api/me');
|
||||||
|
if (response.status >= 300) {
|
||||||
|
throw new Error('Can not connect to pixelplanet!');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
const canvasMap = new Map();
|
||||||
|
|
||||||
|
const ids = Object.keys(data.canvases);
|
||||||
|
for (let i = 0; i < ids.length; i += 1) {
|
||||||
|
const id = ids[i];
|
||||||
|
const canvas = data.canvases[id];
|
||||||
|
canvas.id = id;
|
||||||
|
canvas.palette = new Palette(canvas.colors);
|
||||||
|
canvas.maxTiledZoom = Math.log2(canvas.size / TILE_SIZE) / TILE_ZOOM_LEVEL * 2;
|
||||||
|
canvasMap.set(canvas.ident, canvas);
|
||||||
|
}
|
||||||
|
canvases = canvasMap;
|
||||||
|
console.log('Successfully fetched canvas data from pixelplanet');
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Couldn\'t connect to pixelplanet, trying to connect again in 60s');
|
||||||
|
setTimeout(fetchCanvasData, 60000);
|
||||||
|
throw(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchCanvasData();
|
||||||
|
|
||||||
|
export async function parseCanvasLinks(msg) {
|
||||||
|
const {
|
||||||
|
text,
|
||||||
|
} = msg;
|
||||||
|
|
||||||
|
if (!text || !canvases) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgArray = text.split(linkRegExp).filter(linkRegExpFilter);
|
||||||
|
if (msgArray.length > 1) {
|
||||||
|
const coordsParts = msgArray[1].substr(1).split(',');
|
||||||
|
const [ canvasIdent, x, y, z ] = coordsParts;
|
||||||
|
|
||||||
|
const canvas = canvases.get(canvasIdent);
|
||||||
|
if (!canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`Fetch canvas ${canvas.title} on ${x}/${y} with zoom ${z}`);
|
||||||
|
|
||||||
|
const image = await renderCanvas(canvas, x, y, z);
|
||||||
|
msg.reply({
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
image,
|
||||||
|
text: `${canvas.title} on ${x}/${y} with zoom ${z}`,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
62
ppfun-bridge/src/pixelplanet/loadChunk.js
Normal file
62
ppfun-bridge/src/pixelplanet/loadChunk.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createCanvas,
|
||||||
|
loadImage,
|
||||||
|
createImageData,
|
||||||
|
} from 'canvas';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TILE_SIZE,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
export async function getChunk(
|
||||||
|
canvas,
|
||||||
|
zoom,
|
||||||
|
cx,
|
||||||
|
cy,
|
||||||
|
) {
|
||||||
|
const tile = createCanvas(TILE_SIZE, TILE_SIZE);
|
||||||
|
|
||||||
|
const canvasId = canvas.id;
|
||||||
|
if (canvas.maxTiledZoom === zoom) {
|
||||||
|
await fetchBaseChunk(canvasId, canvas.palette, zoom, cx, cy, tile);
|
||||||
|
} else {
|
||||||
|
await fetchTile(canvasId, zoom, cx, cy, tile);
|
||||||
|
}
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchBaseChunk(
|
||||||
|
canvasId,
|
||||||
|
palette,
|
||||||
|
zoom,
|
||||||
|
cx, cy,
|
||||||
|
tile,
|
||||||
|
) {
|
||||||
|
const url = `https://pixelplanet.fun/chunks/${canvasId}/${cx}/${cy}.bmp`;
|
||||||
|
console.log(`Fetching ${url}`);
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (response.ok) {
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
if (arrayBuffer.byteLength) {
|
||||||
|
const chunkArray = new Uint8Array(arrayBuffer);
|
||||||
|
const abgr = palette.buffer2ABGR(chunkArray);
|
||||||
|
const imageData = createImageData(
|
||||||
|
new Uint8ClampedArray(abgr.buffer),
|
||||||
|
TILE_SIZE,
|
||||||
|
TILE_SIZE,
|
||||||
|
);
|
||||||
|
const ctx = tile.getContext('2d');
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchTile(canvasId, zoom, cx, cy, tile) {
|
||||||
|
const url = `https://pixelplanet.fun/tiles/${canvasId}/${zoom}/${cx}/${cy}.png`;
|
||||||
|
console.log(`Fetching ${url}`);
|
||||||
|
const image = await loadImage(url);
|
||||||
|
const ctx = tile.getContext('2d');
|
||||||
|
ctx.drawImage(image, 0, 0);
|
||||||
|
}
|
93
ppfun-bridge/src/pixelplanet/renderCanvas.js
Normal file
93
ppfun-bridge/src/pixelplanet/renderCanvas.js
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import { createCanvas } from 'canvas';
|
||||||
|
|
||||||
|
import { getChunk } from './loadChunk';
|
||||||
|
|
||||||
|
import {
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
MAX_SCALE,
|
||||||
|
TILE_SIZE,
|
||||||
|
TILE_ZOOM_LEVEL,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
function coordToChunk(z, canvasSize, tiledScale) {
|
||||||
|
return Math.floor((z + canvasSize / 2) / TILE_SIZE * tiledScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
function chunkToCoord(z, canvasSize, tiledScale) {
|
||||||
|
return Math.round(z * TILE_SIZE / tiledScale - canvasSize / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function drawChunk(
|
||||||
|
ctx, xOff, yOff,
|
||||||
|
canvas, tiledZoom, xc, yc,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const chunk = await getChunk(canvas, tiledZoom, xc, yc);
|
||||||
|
ctx.drawImage(chunk, xOff,yOff);
|
||||||
|
} catch(e) {
|
||||||
|
console.log(`Chunk ${xc} / ${yc} - ${tiledZoom} is empty`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function renderCanvas(
|
||||||
|
canvas,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
z,
|
||||||
|
) {
|
||||||
|
const can = createCanvas(WIDTH, HEIGHT);
|
||||||
|
const ctx = can.getContext('2d');
|
||||||
|
const canvasSize = canvas.size;
|
||||||
|
|
||||||
|
let scale = 2 ** (z / 10);
|
||||||
|
scale = Math.max(scale, TILE_SIZE / canvas.size);
|
||||||
|
scale = Math.min(scale, MAX_SCALE);
|
||||||
|
|
||||||
|
let tiledScale = (scale > 0.5)
|
||||||
|
? 0
|
||||||
|
: Math.round(Math.log2(scale) / 2);
|
||||||
|
tiledScale = TILE_ZOOM_LEVEL ** tiledScale;
|
||||||
|
const tiledZoom = canvas.maxTiledZoom + Math.log2(tiledScale) / 2;
|
||||||
|
|
||||||
|
const relScale = scale / tiledScale;
|
||||||
|
ctx.scale(relScale, relScale);
|
||||||
|
ctx.fillStyle = canvas.palette.colors[0];
|
||||||
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
ctx.patternQuality = "nearest";
|
||||||
|
ctx.antialias = 'none';
|
||||||
|
ctx.fillRect(0, 0, WIDTH, HEIGHT);
|
||||||
|
|
||||||
|
const tlX = Math.floor(x - WIDTH / 2 / scale);
|
||||||
|
const tlY = Math.floor(y - HEIGHT / 2 / scale);
|
||||||
|
const brX = Math.floor(x - 1 + WIDTH / 2 / scale);
|
||||||
|
const brY = Math.floor(y - 1 + HEIGHT / 2 / scale);
|
||||||
|
|
||||||
|
const tlCX = coordToChunk(tlX, canvasSize, tiledScale);
|
||||||
|
const tlCY = coordToChunk(tlY, canvasSize, tiledScale);
|
||||||
|
const brCX = coordToChunk(brX, canvasSize, tiledScale);
|
||||||
|
const brCY = coordToChunk(brY, canvasSize, tiledScale);
|
||||||
|
|
||||||
|
console.log(`Load chunks from ${tlCX} / ${tlCY} to ${brCX} / ${brCY}`);
|
||||||
|
const chunkMax = canvasSize / TILE_SIZE * tiledScale;
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
for (let xc = tlCX; xc <= brCX; xc += 1) {
|
||||||
|
for (let yc = tlCY; yc <= brCY; yc += 1) {
|
||||||
|
const xOff = Math.round((chunkToCoord(xc, canvasSize, tiledScale) - tlX) * tiledScale);
|
||||||
|
const yOff = Math.round((chunkToCoord(yc, canvasSize, tiledScale) - tlY) * tiledScale);
|
||||||
|
|
||||||
|
if (xc < 0 || xc >= chunkMax || yc < 0 || yc >= chunkMax) {
|
||||||
|
ctx.clearRect(xOff, yOff, TILE_SIZE, TILE_SIZE);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
promises.push(
|
||||||
|
drawChunk(ctx, xOff, yOff, canvas, tiledZoom, xc, yc),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
return can.toBuffer('image/png');
|
||||||
|
}
|
|
@ -9,7 +9,6 @@ class PPfunMatrixBridge {
|
||||||
const {
|
const {
|
||||||
apiSocketKey,
|
apiSocketKey,
|
||||||
apiSocketUrl,
|
apiSocketUrl,
|
||||||
ppfunId,
|
|
||||||
homeserverUrl,
|
homeserverUrl,
|
||||||
domain,
|
domain,
|
||||||
registration,
|
registration,
|
||||||
|
@ -27,7 +26,6 @@ class PPfunMatrixBridge {
|
||||||
this.ppfunSocket = new PPfunSocket(
|
this.ppfunSocket = new PPfunSocket(
|
||||||
apiSocketUrl,
|
apiSocketUrl,
|
||||||
apiSocketKey,
|
apiSocketKey,
|
||||||
ppfunId,
|
|
||||||
);
|
);
|
||||||
this.matrixBridge = new MatrixBridge({
|
this.matrixBridge = new MatrixBridge({
|
||||||
homeserverUrl,
|
homeserverUrl,
|
||||||
|
@ -39,7 +37,6 @@ class PPfunMatrixBridge {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.ppfunId = ppfunId;
|
|
||||||
this.domain = domain;
|
this.domain = domain;
|
||||||
this.prefix = 'pp';
|
this.prefix = 'pp';
|
||||||
|
|
||||||
|
@ -153,7 +150,7 @@ class PPfunMatrixBridge {
|
||||||
this.ppfunSocket.emit(
|
this.ppfunSocket.emit(
|
||||||
'sendChatMessage',
|
'sendChatMessage',
|
||||||
(uid) ? name : `[mx] ${name}`,
|
(uid) ? name : `[mx] ${name}`,
|
||||||
uid || this.ppfunId,
|
uid || null,
|
||||||
msg,
|
msg,
|
||||||
cid,
|
cid,
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,15 +4,14 @@ const EventEmitter = require('events');
|
||||||
const WebSocket = require('ws')
|
const WebSocket = require('ws')
|
||||||
|
|
||||||
class PPfunSocket extends EventEmitter {
|
class PPfunSocket extends EventEmitter {
|
||||||
constructor(url, apisocketkey, ppfunId) {
|
constructor(url, apisocketkey) {
|
||||||
super();
|
super();
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.apisocketkey = apisocketkey;
|
this.apisocketkey = apisocketkey;
|
||||||
this.ppfunId = ppfunId;
|
|
||||||
this.timeConnected = null;
|
this.timeConnected = null;
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
this.flagMap = new Map();
|
this.flagMap = new Map();
|
||||||
this.flagMap.set(ppfunId, 'yy');
|
this.flagMap.set(null , 'yy');
|
||||||
this.ws;
|
this.ws;
|
||||||
this.onWsClose = this.onWsClose.bind(this);
|
this.onWsClose = this.onWsClose.bind(this);
|
||||||
console.log('PPfunSocket: Connecting to WebSocket')
|
console.log('PPfunSocket: Connecting to WebSocket')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user