pixel burst client side
add client prediction
This commit is contained in:
parent
277568fc9c
commit
1735643b32
|
@ -233,139 +233,6 @@ export function notify(notification: string) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function gotCoolDownDelta(delta: number) {
|
|
||||||
return {
|
|
||||||
type: 'COOLDOWN_DELTA',
|
|
||||||
delta,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let pixelTimeout = null;
|
|
||||||
export function tryPlacePixel(
|
|
||||||
i: number,
|
|
||||||
j: number,
|
|
||||||
offset: number,
|
|
||||||
color: ColorIndex,
|
|
||||||
): ThunkAction {
|
|
||||||
return async (dispatch) => {
|
|
||||||
pixelTimeout = Date.now() + 5000;
|
|
||||||
await dispatch(setPlaceAllowed(false));
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// this is for resending after captcha returned
|
|
||||||
// window is ugly, put it into redux or something
|
|
||||||
window.pixel = {
|
|
||||||
i,
|
|
||||||
j,
|
|
||||||
offset,
|
|
||||||
color,
|
|
||||||
};
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: 'REQUEST_PLACE_PIXEL',
|
|
||||||
i,
|
|
||||||
j,
|
|
||||||
offset,
|
|
||||||
color,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function receivePixelReturn(
|
|
||||||
retCode: number,
|
|
||||||
wait: number,
|
|
||||||
coolDownSeconds: number,
|
|
||||||
): ThunkAction {
|
|
||||||
return (dispatch) => {
|
|
||||||
try {
|
|
||||||
/*
|
|
||||||
* the terms coolDown is used in a different meaning here
|
|
||||||
* coolDown is the delta seconds of the placed pixel
|
|
||||||
*/
|
|
||||||
if (wait) {
|
|
||||||
dispatch(setWait(wait));
|
|
||||||
}
|
|
||||||
if (coolDownSeconds) {
|
|
||||||
dispatch(notify(coolDownSeconds));
|
|
||||||
if (coolDownSeconds < 0) {
|
|
||||||
dispatch(gotCoolDownDelta(coolDownSeconds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let errorTitle = null;
|
|
||||||
let msg = null;
|
|
||||||
switch (retCode) {
|
|
||||||
case 0:
|
|
||||||
dispatch(placedPixel());
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
errorTitle = 'Invalid Canvas';
|
|
||||||
msg = 'This canvas doesn\'t exist';
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
errorTitle = 'Invalid Coordinates';
|
|
||||||
msg = 'x out of bounds';
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
errorTitle = 'Invalid Coordinates';
|
|
||||||
msg = 'y out of bounds';
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
errorTitle = 'Invalid Coordinates';
|
|
||||||
msg = 'z out of bounds';
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
errorTitle = 'Wrong Color';
|
|
||||||
msg = 'Invalid color selected';
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
errorTitle = 'Just for registered Users';
|
|
||||||
msg = 'You have to be logged in to place on this canvas';
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
errorTitle = 'Place more :)';
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
msg = 'You can not access this canvas yet. You need to place more pixels';
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
dispatch(notify('Pixel protected!'));
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
// pixestack used up
|
|
||||||
dispatch(pixelWait());
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
// captcha, reCaptcha or hCaptcha
|
|
||||||
if (typeof window.hcaptcha !== 'undefined') {
|
|
||||||
window.hcaptcha.execute();
|
|
||||||
} else {
|
|
||||||
window.grecaptcha.execute();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
errorTitle = 'No Proxies Allowed :(';
|
|
||||||
msg = 'You are using a Proxy.';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
errorTitle = 'Weird';
|
|
||||||
msg = 'Couldn\'t set Pixel';
|
|
||||||
}
|
|
||||||
if (msg) {
|
|
||||||
dispatch(pixelFailure());
|
|
||||||
dispatch(sweetAlert(
|
|
||||||
(errorTitle || `Error ${retCode}`),
|
|
||||||
msg,
|
|
||||||
'error',
|
|
||||||
'OK',
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
pixelTimeout = null;
|
|
||||||
dispatch(setPlaceAllowed(true));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setViewCoordinates(view: Cell): Action {
|
export function setViewCoordinates(view: Cell): Action {
|
||||||
return {
|
return {
|
||||||
type: 'SET_VIEW_COORDINATES',
|
type: 'SET_VIEW_COORDINATES',
|
||||||
|
@ -415,7 +282,6 @@ export function moveEast(): ThunkAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function setScale(scale: number, zoompoint: Cell): Action {
|
export function setScale(scale: number, zoompoint: Cell): Action {
|
||||||
return {
|
return {
|
||||||
type: 'SET_SCALE',
|
type: 'SET_SCALE',
|
||||||
|
@ -484,7 +350,7 @@ export function receiveCoolDown(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function receivePixelUpdate(
|
export function updatePixel(
|
||||||
i: number,
|
i: number,
|
||||||
j: number,
|
j: number,
|
||||||
offset: number,
|
offset: number,
|
||||||
|
@ -668,18 +534,6 @@ function getPendingActions(state): Array<Action> {
|
||||||
else actions.push(endCoolDown());
|
else actions.push(endCoolDown());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pixelTimeout && now > pixelTimeout) {
|
|
||||||
actions.push(pixelFailure());
|
|
||||||
pixelTimeout = null;
|
|
||||||
actions.push(setPlaceAllowed(true));
|
|
||||||
actions.push(sweetAlert(
|
|
||||||
'Error :(',
|
|
||||||
'Didn\'t get an answer from pixelplanet. Maybe try to refresh?',
|
|
||||||
'error',
|
|
||||||
'OK',
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -849,6 +703,13 @@ export function startDm(query): PromiseAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function gotCoolDownDelta(delta: number) {
|
||||||
|
return {
|
||||||
|
type: 'COOLDOWN_DELTA',
|
||||||
|
delta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function setUserBlock(
|
export function setUserBlock(
|
||||||
userId: number,
|
userId: number,
|
||||||
userName: string,
|
userName: string,
|
||||||
|
|
|
@ -39,12 +39,6 @@ export type Action =
|
||||||
| { type: 'COOLDOWN_DELTA', delta: number }
|
| { type: 'COOLDOWN_DELTA', delta: number }
|
||||||
| { type: 'SELECT_COLOR', color: ColorIndex }
|
| { type: 'SELECT_COLOR', color: ColorIndex }
|
||||||
| { type: 'SELECT_CANVAS', canvasId: number }
|
| { type: 'SELECT_CANVAS', canvasId: number }
|
||||||
| { type: 'REQUEST_PLACE_PIXEL',
|
|
||||||
i: number,
|
|
||||||
j: number,
|
|
||||||
offset: number,
|
|
||||||
color: ColorIndex,
|
|
||||||
}
|
|
||||||
| { type: 'PLACE_PIXEL' }
|
| { type: 'PLACE_PIXEL' }
|
||||||
| { type: 'PIXEL_WAIT' }
|
| { type: 'PIXEL_WAIT' }
|
||||||
| { type: 'PIXEL_FAILURE' }
|
| { type: 'PIXEL_FAILURE' }
|
||||||
|
|
|
@ -7,7 +7,6 @@ import './styles/font.css';
|
||||||
|
|
||||||
import onKeyPress from './controls/keypress';
|
import onKeyPress from './controls/keypress';
|
||||||
import {
|
import {
|
||||||
receivePixelUpdate,
|
|
||||||
fetchMe,
|
fetchMe,
|
||||||
fetchStats,
|
fetchStats,
|
||||||
initTimer,
|
initTimer,
|
||||||
|
@ -15,12 +14,14 @@ import {
|
||||||
receiveOnline,
|
receiveOnline,
|
||||||
receiveCoolDown,
|
receiveCoolDown,
|
||||||
receiveChatMessage,
|
receiveChatMessage,
|
||||||
receivePixelReturn,
|
|
||||||
addChatChannel,
|
addChatChannel,
|
||||||
removeChatChannel,
|
removeChatChannel,
|
||||||
setMobile,
|
setMobile,
|
||||||
tryPlacePixel,
|
|
||||||
} from './actions';
|
} from './actions';
|
||||||
|
import {
|
||||||
|
receivePixelUpdate,
|
||||||
|
receivePixelReturn,
|
||||||
|
} from './ui/placePixel';
|
||||||
import store from './ui/store';
|
import store from './ui/store';
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,15 +34,18 @@ function init() {
|
||||||
initRenderer(store, false);
|
initRenderer(store, false);
|
||||||
|
|
||||||
ProtocolClient.on('pixelUpdate', ({
|
ProtocolClient.on('pixelUpdate', ({
|
||||||
i, j, offset, color,
|
i, j, pixels,
|
||||||
}) => {
|
}) => {
|
||||||
|
pixels.forEach((pxl) => {
|
||||||
|
const [offset, color] = pxl;
|
||||||
// remove protection
|
// remove protection
|
||||||
store.dispatch(receivePixelUpdate(i, j, offset, color & 0x7F));
|
receivePixelUpdate(store, i, j, offset, color & 0x7F);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
ProtocolClient.on('pixelReturn', ({
|
ProtocolClient.on('pixelReturn', ({
|
||||||
retCode, wait, coolDownSeconds,
|
retCode, wait, coolDownSeconds, pxlCnt,
|
||||||
}) => {
|
}) => {
|
||||||
store.dispatch(receivePixelReturn(retCode, wait, coolDownSeconds));
|
receivePixelReturn(store, retCode, wait, coolDownSeconds, pxlCnt);
|
||||||
});
|
});
|
||||||
ProtocolClient.on('cooldownPacket', (coolDown) => {
|
ProtocolClient.on('cooldownPacket', (coolDown) => {
|
||||||
store.dispatch(receiveCoolDown(coolDown));
|
store.dispatch(receiveCoolDown(coolDown));
|
||||||
|
@ -145,16 +149,13 @@ window.onCaptcha = async function onCaptcha(token: string) {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body,
|
body,
|
||||||
// https://github.com/github/fetch/issues/349
|
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (window.pixel) {
|
|
||||||
const {
|
const {
|
||||||
i, j, offset, color,
|
i, j, pixels,
|
||||||
} = window.pixel;
|
} = window.pixel;
|
||||||
store.dispatch(tryPlacePixel(i, j, offset, color));
|
ProtocolClient.requestPlacePixels(i, j, pixels);
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof window.hcaptcha !== 'undefined') {
|
if (typeof window.hcaptcha !== 'undefined') {
|
||||||
window.hcaptcha.reset();
|
window.hcaptcha.reset();
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
import keycode from 'keycode';
|
import keycode from 'keycode';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
tryPlacePixel,
|
|
||||||
setHover,
|
setHover,
|
||||||
unsetHover,
|
unsetHover,
|
||||||
setViewCoordinates,
|
setViewCoordinates,
|
||||||
|
@ -21,6 +20,9 @@ import {
|
||||||
moveEast,
|
moveEast,
|
||||||
onViewFinishChange,
|
onViewFinishChange,
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
|
import {
|
||||||
|
tryPlacePixel,
|
||||||
|
} from '../ui/placePixel';
|
||||||
import {
|
import {
|
||||||
screenToWorld,
|
screenToWorld,
|
||||||
getChunkOfPixel,
|
getChunkOfPixel,
|
||||||
|
@ -162,7 +164,6 @@ class PixelPlainterControls {
|
||||||
static placePixel(store, renderer, cell) {
|
static placePixel(store, renderer, cell) {
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const { autoZoomIn } = state.gui;
|
const { autoZoomIn } = state.gui;
|
||||||
const { placeAllowed } = state.user;
|
|
||||||
const {
|
const {
|
||||||
scale,
|
scale,
|
||||||
isHistoricalView,
|
isHistoricalView,
|
||||||
|
@ -180,16 +181,17 @@ class PixelPlainterControls {
|
||||||
// allow placing of pixel just on low zoomlevels
|
// allow placing of pixel just on low zoomlevels
|
||||||
if (scale < 3) return;
|
if (scale < 3) return;
|
||||||
|
|
||||||
if (!placeAllowed) return;
|
const curColor = renderer.getColorIndexOfPixel(...cell);
|
||||||
|
if (selectedColor !== curColor) {
|
||||||
if (selectedColor !== renderer.getColorIndexOfPixel(...cell)) {
|
|
||||||
const { canvasSize } = state.canvas;
|
const { canvasSize } = state.canvas;
|
||||||
const [i, j] = getChunkOfPixel(canvasSize, ...cell);
|
const [i, j] = getChunkOfPixel(canvasSize, ...cell);
|
||||||
const offset = getOffsetOfPixel(canvasSize, ...cell);
|
const offset = getOffsetOfPixel(canvasSize, ...cell);
|
||||||
store.dispatch(tryPlacePixel(
|
tryPlacePixel(
|
||||||
|
store,
|
||||||
i, j, offset,
|
i, j, offset,
|
||||||
selectedColor,
|
selectedColor,
|
||||||
));
|
curColor,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ class PixelCache {
|
||||||
this.PXL_CACHE = new Map();
|
this.PXL_CACHE = new Map();
|
||||||
|
|
||||||
cache.forEach((pxls, chunkCanvasId) => {
|
cache.forEach((pxls, chunkCanvasId) => {
|
||||||
const canvasId = chunkCanvasId & 0xFF0000 >> 16;
|
const canvasId = (chunkCanvasId & 0xFF0000) >> 16;
|
||||||
const chunkId = chunkCanvasId & 0x00FFFF;
|
const chunkId = chunkCanvasId & 0x00FFFF;
|
||||||
webSockets.broadcastPixels(canvasId, chunkId, pxls);
|
webSockets.broadcastPixels(canvasId, chunkId, pxls);
|
||||||
});
|
});
|
||||||
|
|
|
@ -51,6 +51,7 @@ export async function drawByOffsets(
|
||||||
return {
|
return {
|
||||||
wait,
|
wait,
|
||||||
coolDown,
|
coolDown,
|
||||||
|
pxlCnt,
|
||||||
retCode: 1,
|
retCode: 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -177,6 +178,7 @@ export async function drawByOffsets(
|
||||||
return {
|
return {
|
||||||
wait,
|
wait,
|
||||||
coolDown,
|
coolDown,
|
||||||
|
pxlCnt,
|
||||||
retCode,
|
retCode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,7 +128,7 @@ class ProtocolClient extends EventEmitter {
|
||||||
* @param i, j chunk coordinates
|
* @param i, j chunk coordinates
|
||||||
* @param pixel Array of [[offset, color],...] pixels within chunk
|
* @param pixel Array of [[offset, color],...] pixels within chunk
|
||||||
*/
|
*/
|
||||||
requestPlacePixel(
|
requestPlacePixels(
|
||||||
i: number, j: number,
|
i: number, j: number,
|
||||||
pixels: Array,
|
pixels: Array,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -390,6 +390,7 @@ class SocketServer extends WebSocketEvents {
|
||||||
const {
|
const {
|
||||||
wait,
|
wait,
|
||||||
coolDown,
|
coolDown,
|
||||||
|
pxlCnt,
|
||||||
retCode,
|
retCode,
|
||||||
} = await drawSafeByOffsets(
|
} = await drawSafeByOffsets(
|
||||||
ws.user,
|
ws.user,
|
||||||
|
@ -397,7 +398,7 @@ class SocketServer extends WebSocketEvents {
|
||||||
i, j,
|
i, j,
|
||||||
pixels,
|
pixels,
|
||||||
);
|
);
|
||||||
ws.send(PixelReturn.dehydrate(retCode, wait, coolDown));
|
ws.send(PixelReturn.dehydrate(retCode, wait, coolDown, pxlCnt));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RegisterCanvas.OP_CODE: {
|
case RegisterCanvas.OP_CODE: {
|
||||||
|
|
|
@ -9,19 +9,22 @@ export default {
|
||||||
const retCode = data.getUint8(1);
|
const retCode = data.getUint8(1);
|
||||||
const wait = data.getUint32(2);
|
const wait = data.getUint32(2);
|
||||||
const coolDownSeconds = data.getInt16(6);
|
const coolDownSeconds = data.getInt16(6);
|
||||||
|
const pxlCnt = data.getUint8(8);
|
||||||
return {
|
return {
|
||||||
retCode,
|
retCode,
|
||||||
wait,
|
wait,
|
||||||
coolDownSeconds,
|
coolDownSeconds,
|
||||||
|
pxlCnt,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
dehydrate(retCode, wait, coolDown): Buffer {
|
dehydrate(retCode, wait, coolDown, pxlCnt): Buffer {
|
||||||
const buffer = Buffer.allocUnsafe(1 + 1 + 4 + 1 + 2);
|
const buffer = Buffer.allocUnsafe(1 + 1 + 4 + 2 + 1);
|
||||||
buffer.writeUInt8(OP_CODE, 0);
|
buffer.writeUInt8(OP_CODE, 0);
|
||||||
buffer.writeUInt8(retCode, 1);
|
buffer.writeUInt8(retCode, 1);
|
||||||
buffer.writeUInt32BE(wait, 2);
|
buffer.writeUInt32BE(wait, 2);
|
||||||
const coolDownSeconds = Math.round(coolDown / 1000);
|
const coolDownSeconds = Math.round(coolDown / 1000);
|
||||||
buffer.writeInt16BE(coolDownSeconds, 6);
|
buffer.writeInt16BE(coolDownSeconds, 6);
|
||||||
|
buffer.writeUInt8(pxlCnt, 8);
|
||||||
return buffer;
|
return buffer;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,7 +29,7 @@ export default {
|
||||||
*/
|
*/
|
||||||
const pixels = [];
|
const pixels = [];
|
||||||
let off = data.byteLength;
|
let off = data.byteLength;
|
||||||
while (off >= 3) {
|
while (off > 3) {
|
||||||
const color = data.getUint8(off -= 1);
|
const color = data.getUint8(off -= 1);
|
||||||
const offsetL = data.getUint16(off -= 2);
|
const offsetL = data.getUint16(off -= 2);
|
||||||
const offsetH = data.getUint8(off -= 1) << 16;
|
const offsetH = data.getUint8(off -= 1) << 16;
|
||||||
|
|
|
@ -29,7 +29,7 @@ export default {
|
||||||
* receive to 500
|
* receive to 500
|
||||||
*/
|
*/
|
||||||
let pxlcnt = 0;
|
let pxlcnt = 0;
|
||||||
while (off >= 3 && pxlcnt < 500) {
|
while (off > 3 && pxlcnt < 500) {
|
||||||
const color = data.readUInt8(off -= 1);
|
const color = data.readUInt8(off -= 1);
|
||||||
const offsetL = data.readUInt16BE(off -= 2);
|
const offsetL = data.readUInt16BE(off -= 2);
|
||||||
const offsetH = data.readUInt8(off -= 1) << 16;
|
const offsetH = data.readUInt8(off -= 1) << 16;
|
||||||
|
@ -46,7 +46,7 @@ export default {
|
||||||
* @param pixels Buffer with offset and color of one or more pixels
|
* @param pixels Buffer with offset and color of one or more pixels
|
||||||
*/
|
*/
|
||||||
dehydrate(chunkId, pixels): Buffer {
|
dehydrate(chunkId, pixels): Buffer {
|
||||||
const index = new Uint8Array([OP_CODE, chunkId >> 8, chunkId && 0xFF]);
|
const index = new Uint8Array([OP_CODE, chunkId >> 8, chunkId & 0xFF]);
|
||||||
return Buffer.concat([index, pixels]);
|
return Buffer.concat([index, pixels]);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,17 +25,6 @@ export default (store) => (next) => (action) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'REQUEST_PLACE_PIXEL': {
|
|
||||||
const {
|
|
||||||
i, j, offset, color,
|
|
||||||
} = action;
|
|
||||||
ProtocolClient.requestPlacePixel(
|
|
||||||
i, j, offset,
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,9 @@ import {
|
||||||
} from '../core/constants';
|
} from '../core/constants';
|
||||||
import {
|
import {
|
||||||
setHover,
|
setHover,
|
||||||
tryPlacePixel,
|
|
||||||
selectColor,
|
selectColor,
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
|
import { tryPlacePixel } from './placePixel';
|
||||||
|
|
||||||
|
|
||||||
const renderDistance = 150;
|
const renderDistance = 150;
|
||||||
|
@ -448,6 +448,8 @@ class Renderer {
|
||||||
.add(intersect.face.normal.multiplyScalar(0.5))
|
.add(intersect.face.normal.multiplyScalar(0.5))
|
||||||
.floor()
|
.floor()
|
||||||
.addScalar(0.5);
|
.addScalar(0.5);
|
||||||
|
// TODO make rollOverMesh in a different color while placeAllowed false
|
||||||
|
// instead of hiding it.... we can now queue Voxels
|
||||||
if (!placeAllowed
|
if (!placeAllowed
|
||||||
|| target.clone().sub(camera.position).length() > 50) {
|
|| target.clone().sub(camera.position).length() > 50) {
|
||||||
rollOverMesh.position.y = -10;
|
rollOverMesh.position.y = -10;
|
||||||
|
@ -470,9 +472,16 @@ class Renderer {
|
||||||
selectedColor,
|
selectedColor,
|
||||||
} = state.canvas;
|
} = state.canvas;
|
||||||
const chClr = (color === null) ? selectedColor : color;
|
const chClr = (color === null) ? selectedColor : color;
|
||||||
|
const curColor = (chClr === 0) ? this.chunkLoader.getVoxel(x, y, z) : 0;
|
||||||
const [i, j] = getChunkOfPixel(canvasSize, x, y, z);
|
const [i, j] = getChunkOfPixel(canvasSize, x, y, z);
|
||||||
const offset = getOffsetOfPixel(canvasSize, x, y, z);
|
const offset = getOffsetOfPixel(canvasSize, x, y, z);
|
||||||
store.dispatch(tryPlacePixel(i, j, offset, chClr));
|
tryPlacePixel(
|
||||||
|
store,
|
||||||
|
i, j,
|
||||||
|
offset,
|
||||||
|
chClr,
|
||||||
|
curColor,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
multiTapEnd() {
|
multiTapEnd() {
|
||||||
|
@ -483,12 +492,6 @@ class Renderer {
|
||||||
} = this;
|
} = this;
|
||||||
this.multitap = 0;
|
this.multitap = 0;
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const {
|
|
||||||
placeAllowed,
|
|
||||||
} = state.user;
|
|
||||||
if (!placeAllowed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [px, py, pz] = mouseMoveStart;
|
const [px, py, pz] = mouseMoveStart;
|
||||||
const [qx, qy, qz] = state.gui.hover;
|
const [qx, qy, qz] = state.gui.hover;
|
||||||
|
|
275
src/ui/placePixel.js
Normal file
275
src/ui/placePixel.js
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
/*
|
||||||
|
* Place pixel via Websocket
|
||||||
|
* Always just one pixelrequest, queue additional requests to send later
|
||||||
|
* Pixels get predicted on the client and reset if server refused
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
* */
|
||||||
|
import {
|
||||||
|
notify,
|
||||||
|
setPlaceAllowed,
|
||||||
|
sweetAlert,
|
||||||
|
gotCoolDownDelta,
|
||||||
|
pixelFailure,
|
||||||
|
setWait,
|
||||||
|
placedPixel,
|
||||||
|
pixelWait,
|
||||||
|
updatePixel,
|
||||||
|
} from '../actions';
|
||||||
|
import ProtocolClient from '../socket/ProtocolClient';
|
||||||
|
|
||||||
|
let pixelTimeout = null;
|
||||||
|
/*
|
||||||
|
* cache of pixels that still are to set
|
||||||
|
* [{i: i, j: j, pixels: [[offset, color],...]}, ...]
|
||||||
|
*/
|
||||||
|
let pixelQueue = [];
|
||||||
|
/*
|
||||||
|
* requests that got predicted on client and yet have to be
|
||||||
|
* received from the server
|
||||||
|
* [[i, j, offset, color], ...]
|
||||||
|
*/
|
||||||
|
let clientPredictions = [];
|
||||||
|
/*
|
||||||
|
* values of last request
|
||||||
|
* {i: i, j: j, pixels: [[offset, color], ...}
|
||||||
|
*/
|
||||||
|
let lastRequestValues = {};
|
||||||
|
|
||||||
|
|
||||||
|
function requestFromQueue(store) {
|
||||||
|
if (!pixelQueue.length) {
|
||||||
|
pixelTimeout = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* timeout to warn user when Websocket is dysfunctional */
|
||||||
|
pixelTimeout = setTimeout(() => {
|
||||||
|
pixelQueue = [];
|
||||||
|
pixelTimeout = null;
|
||||||
|
store.dispatch(setPlaceAllowed(true));
|
||||||
|
store.dispatch(sweetAlert(
|
||||||
|
'Error :(',
|
||||||
|
'Didn\'t get an answer from pixelplanet. Maybe try to refresh?',
|
||||||
|
'error',
|
||||||
|
'OK',
|
||||||
|
));
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
lastRequestValues = pixelQueue.shift();
|
||||||
|
const { i, j, pixels } = lastRequestValues;
|
||||||
|
ProtocolClient.requestPlacePixels(i, j, pixels);
|
||||||
|
store.dispatch(setPlaceAllowed(false));
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// this is for resending after captcha returned
|
||||||
|
// window is ugly, put it into redux or something
|
||||||
|
window.pixel = {
|
||||||
|
i,
|
||||||
|
j,
|
||||||
|
pixels,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function receivePixelUpdate(
|
||||||
|
store,
|
||||||
|
i: number,
|
||||||
|
j: number,
|
||||||
|
offset: number,
|
||||||
|
color: ColorIndex,
|
||||||
|
) {
|
||||||
|
for (let p = 0; p < clientPredictions; p += 1) {
|
||||||
|
const predPxl = clientPredictions[p];
|
||||||
|
if (predPxl[0] === i
|
||||||
|
&& predPxl[1] === j
|
||||||
|
&& predPxl[2] === offset
|
||||||
|
) {
|
||||||
|
clientPredictions.splice(i, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
store.dispatch(updatePixel(i, j, offset, color));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Revert predictions starting at given pixel
|
||||||
|
* @param i, j, offset data of the first pixel that got rejected
|
||||||
|
*/
|
||||||
|
function revertPredictionsAt(
|
||||||
|
store,
|
||||||
|
sI: number,
|
||||||
|
sJ: number,
|
||||||
|
sOffset: number,
|
||||||
|
) {
|
||||||
|
let p = 0;
|
||||||
|
while (p < clientPredictions.length) {
|
||||||
|
const predPxl = clientPredictions[p];
|
||||||
|
if (predPxl[0] === sI
|
||||||
|
&& predPxl[1] === sJ
|
||||||
|
&& predPxl[2] === sOffset
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p >= clientPredictions.length) {
|
||||||
|
clientPredictions = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Reverting ${clientPredictions.length - p} client predictions`,
|
||||||
|
);
|
||||||
|
|
||||||
|
while (p < clientPredictions.length) {
|
||||||
|
const [i, j, offset, color] = clientPredictions[p];
|
||||||
|
store.dispatch(updatePixel(i, j, offset, color));
|
||||||
|
p += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientPredictions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tryPlacePixel(
|
||||||
|
store,
|
||||||
|
i: number,
|
||||||
|
j: number,
|
||||||
|
offset: number,
|
||||||
|
color: ColorIndex,
|
||||||
|
curColor: ColorIndex,
|
||||||
|
) {
|
||||||
|
store.dispatch(updatePixel(i, j, offset, color));
|
||||||
|
clientPredictions.push([i, j, offset, curColor]);
|
||||||
|
|
||||||
|
if (pixelQueue.length) {
|
||||||
|
const lastReq = pixelQueue[pixelQueue.length - 1];
|
||||||
|
const { i: lastI, j: lastJ } = lastReq;
|
||||||
|
if (i === lastI && j === lastJ) {
|
||||||
|
/* append to last request in queue if same chunk */
|
||||||
|
lastReq.pixels.push([offset, color]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pixelQueue.push({
|
||||||
|
i,
|
||||||
|
j,
|
||||||
|
pixels: [[offset, color]],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!pixelTimeout) {
|
||||||
|
requestFromQueue(store);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function receivePixelReturn(
|
||||||
|
store,
|
||||||
|
retCode: number,
|
||||||
|
wait: number,
|
||||||
|
coolDownSeconds: number,
|
||||||
|
pxlCnt,
|
||||||
|
) {
|
||||||
|
clearTimeout(pixelTimeout);
|
||||||
|
|
||||||
|
try {
|
||||||
|
/*
|
||||||
|
* the terms coolDown is used in a different meaning here
|
||||||
|
* coolDown is the delta seconds of the placed pixel
|
||||||
|
*/
|
||||||
|
if (wait) {
|
||||||
|
store.dispatch(setWait(wait));
|
||||||
|
}
|
||||||
|
if (coolDownSeconds) {
|
||||||
|
store.dispatch(notify(coolDownSeconds));
|
||||||
|
if (coolDownSeconds < 0) {
|
||||||
|
store.dispatch(gotCoolDownDelta(coolDownSeconds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retCode) {
|
||||||
|
/*
|
||||||
|
* one or more pixels didn't get set,
|
||||||
|
* revert predictions and clean queue
|
||||||
|
*/
|
||||||
|
const { i, j, pixels } = lastRequestValues;
|
||||||
|
const [offset] = pixels[pxlCnt];
|
||||||
|
revertPredictionsAt(store, i, j, offset);
|
||||||
|
pixelQueue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let errorTitle = null;
|
||||||
|
let msg = null;
|
||||||
|
switch (retCode) {
|
||||||
|
case 0:
|
||||||
|
store.dispatch(placedPixel());
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
errorTitle = 'Invalid Canvas';
|
||||||
|
msg = 'This canvas doesn\'t exist';
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
errorTitle = 'Invalid Coordinates';
|
||||||
|
msg = 'x out of bounds';
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
errorTitle = 'Invalid Coordinates';
|
||||||
|
msg = 'y out of bounds';
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
errorTitle = 'Invalid Coordinates';
|
||||||
|
msg = 'z out of bounds';
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
errorTitle = 'Wrong Color';
|
||||||
|
msg = 'Invalid color selected';
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
errorTitle = 'Just for registered Users';
|
||||||
|
msg = 'You have to be logged in to place on this canvas';
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
errorTitle = 'Place more :)';
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
msg = 'You can not access this canvas yet. You need to place more pixels';
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
store.dispatch(notify('Pixel protected!'));
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
// pixestack used up
|
||||||
|
store.dispatch(pixelWait());
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
// captcha, reCaptcha or hCaptcha
|
||||||
|
if (typeof window.hcaptcha !== 'undefined') {
|
||||||
|
window.hcaptcha.execute();
|
||||||
|
} else {
|
||||||
|
window.grecaptcha.execute();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case 11:
|
||||||
|
|
||||||
|
errorTitle = 'No Proxies Allowed :(';
|
||||||
|
msg = 'You are using a Proxy.';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorTitle = 'Weird';
|
||||||
|
msg = 'Couldn\'t set Pixel';
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
store.dispatch(pixelFailure());
|
||||||
|
store.dispatch(sweetAlert(
|
||||||
|
(errorTitle || `Error ${retCode}`),
|
||||||
|
msg,
|
||||||
|
'error',
|
||||||
|
'OK',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
store.dispatch(setPlaceAllowed(true));
|
||||||
|
/* start next request if queue isn't empty */
|
||||||
|
requestFromQueue(store);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user