|
|
|
@@ -179,6 +179,16 @@
|
|
|
|
|
return Math.round(clamp(Math.min(stageWidth / (columns + 1), stageHeight / (rows + 1)), 32, 72));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function playerMapOffset(selfIndex, width, minX, minY, columns, rows, cellSize) {
|
|
|
|
|
if (![selfIndex, width, minX, minY, columns, rows, cellSize].every(Number.isFinite) || !Number.isInteger(selfIndex) || selfIndex < 0 || width < 1 || columns < 1 || rows < 1 || cellSize < 1) return {x: 0, y: 0};
|
|
|
|
|
const selfX = selfIndex % width - minX;
|
|
|
|
|
const selfY = Math.floor(selfIndex / width) - minY;
|
|
|
|
|
return {
|
|
|
|
|
x: (columns / 2 - selfX - .5) * cellSize,
|
|
|
|
|
y: (rows / 2 - selfY - .5) * cellSize
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function nextOnboardingStep(currentStep) {
|
|
|
|
|
return currentStep >= ONBOARDING_STEPS.length - 1 ? null : currentStep + 1;
|
|
|
|
|
}
|
|
|
|
@@ -290,7 +300,7 @@
|
|
|
|
|
|
|
|
|
|
function boot(documentObject, windowObject) {
|
|
|
|
|
const byId = id => documentObject.getElementById(id);
|
|
|
|
|
const state = {socket: null, clientSequence: 0, serverSequence: 0, viewSequence: -1, session: null, lobby: null, snapshot: null, reconnectTimer: null, noticeTimer: null, touchStart: null, pending: new Map(), assembler: new SnapshotAssembler(), latency: new LatencyTracker(() => windowObject.performance.now()), audio: null, feedbackReady: false, muted: false, reducedMotion: false, onboardingSeen: false, onboardingStep: 0, avatar: {pointerId: null, direction: null, repeatTimer: null, repeatMs: null}, observer: {scale: 1, x: 0, y: 0, dragging: null}};
|
|
|
|
|
const state = {socket: null, clientSequence: 0, serverSequence: 0, viewSequence: -1, session: null, lobby: null, snapshot: null, reconnectTimer: null, noticeTimer: null, touchStart: null, pending: new Map(), assembler: new SnapshotAssembler(), latency: new LatencyTracker(() => windowObject.performance.now()), audio: null, feedbackReady: false, muted: false, reducedMotion: false, onboardingSeen: false, onboardingStep: 0, avatar: {pointerId: null, startX: null, startY: null, direction: null, repeatTimer: null, repeatMs: null}, observer: {scale: 1, x: 0, y: 0, dragging: null}};
|
|
|
|
|
state.moveFlow = new LatestIntentFlow(direction => sendMoveNow(direction));
|
|
|
|
|
|
|
|
|
|
function showNotice(text, kind) {
|
|
|
|
@@ -448,9 +458,12 @@
|
|
|
|
|
const columns = maxX - minX + 1;
|
|
|
|
|
const rows = maxY - minY + 1;
|
|
|
|
|
const mapShell = byId("map-shell");
|
|
|
|
|
maze.style.setProperty("--cell", `${playerCellSize(mapShell.clientWidth, mapShell.clientHeight, columns, rows)}px`);
|
|
|
|
|
const cellSize = playerCellSize(mapShell.clientWidth, mapShell.clientHeight, columns, rows);
|
|
|
|
|
const offset = playerMapOffset(snapshot.self, snapshot.width, minX, minY, columns, rows, cellSize);
|
|
|
|
|
maze.style.setProperty("--cell", `${cellSize}px`);
|
|
|
|
|
maze.style.gridTemplateColumns = `repeat(${columns}, var(--cell))`;
|
|
|
|
|
maze.style.gridTemplateRows = `repeat(${rows}, var(--cell))`;
|
|
|
|
|
maze.style.transform = `translate(calc(-50% + ${offset.x}px), calc(-50% + ${offset.y}px))`;
|
|
|
|
|
const objects = new Map();
|
|
|
|
|
snapshot.objects.forEach(object => { const list = objects.get(object[3]) || []; list.push(object); objects.set(object[3], list); });
|
|
|
|
|
coordinates.forEach(data => {
|
|
|
|
@@ -669,6 +682,8 @@
|
|
|
|
|
function resetAvatarControl() {
|
|
|
|
|
windowObject.clearTimeout(state.avatar.repeatTimer);
|
|
|
|
|
state.avatar.pointerId = null;
|
|
|
|
|
state.avatar.startX = null;
|
|
|
|
|
state.avatar.startY = null;
|
|
|
|
|
state.avatar.direction = null;
|
|
|
|
|
state.avatar.repeatTimer = null;
|
|
|
|
|
state.avatar.repeatMs = null;
|
|
|
|
@@ -695,7 +710,7 @@
|
|
|
|
|
const control = byId("avatar-control");
|
|
|
|
|
const rect = control.getBoundingClientRect();
|
|
|
|
|
const radius = Math.max(AVATAR_DEAD_ZONE + 1, Math.min(rect.width, rect.height) / 2);
|
|
|
|
|
const vector = avatarVector(event.clientX - (rect.left + rect.width / 2), event.clientY - (rect.top + rect.height / 2), radius, state.avatar.direction);
|
|
|
|
|
const vector = avatarVector(event.clientX - state.avatar.startX, event.clientY - state.avatar.startY, radius, state.avatar.direction);
|
|
|
|
|
const handle = byId("avatar-handle");
|
|
|
|
|
handle.style.transform = `translate(calc(-50% + ${vector.x}px), calc(-50% + ${vector.y}px))`;
|
|
|
|
|
const directionChanged = vector.direction !== state.avatar.direction;
|
|
|
|
@@ -741,6 +756,8 @@
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
beginFeedback();
|
|
|
|
|
state.avatar.pointerId = event.pointerId;
|
|
|
|
|
state.avatar.startX = event.clientX;
|
|
|
|
|
state.avatar.startY = event.clientY;
|
|
|
|
|
byId("avatar-control").classList.add("active");
|
|
|
|
|
if (byId("avatar-control").setPointerCapture) byId("avatar-control").setPointerCapture(event.pointerId);
|
|
|
|
|
updateAvatarControl(event);
|
|
|
|
@@ -797,7 +814,7 @@
|
|
|
|
|
return {state, handleMessage, move, resetAvatarControl, latencySamples: () => state.latency.snapshot(), pointerFeedbackSamples: () => state.latency.pointerSnapshot()};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const api = Object.freeze({TOKEN_KEY, MAX_VIEW_CHUNKS, NAME_COMBINATION_COUNT, LOGIN_HEADLINES, ONBOARDING_STEPS, LATENCY_SAMPLE_LIMIT, AVATAR_TOUCH_TARGET_SIZE, AVATAR_MAX_DEFLECTION, AVATAR_DEAD_ZONE, AVATAR_DIRECTION_HYSTERESIS, AVATAR_SLOW_REPEAT_MS, AVATAR_FAST_REPEAT_MS, LatestIntentFlow, LatencyTracker, directionFromKey, directionFromSwipe, avatarDirection, avatarRepeatMs, avatarVector, playerCellSize, nextOnboardingStep, randomIndex, randomSuggestedName, randomLoginHeadline, createEnvelope, snapshotIsValid, SnapshotAssembler, projectedCellIndices, clampObserverZoom, observerFitScale, boot});
|
|
|
|
|
const api = Object.freeze({TOKEN_KEY, MAX_VIEW_CHUNKS, NAME_COMBINATION_COUNT, LOGIN_HEADLINES, ONBOARDING_STEPS, LATENCY_SAMPLE_LIMIT, AVATAR_TOUCH_TARGET_SIZE, AVATAR_MAX_DEFLECTION, AVATAR_DEAD_ZONE, AVATAR_DIRECTION_HYSTERESIS, AVATAR_SLOW_REPEAT_MS, AVATAR_FAST_REPEAT_MS, LatestIntentFlow, LatencyTracker, directionFromKey, directionFromSwipe, avatarDirection, avatarRepeatMs, avatarVector, playerCellSize, playerMapOffset, nextOnboardingStep, randomIndex, randomSuggestedName, randomLoginHeadline, createEnvelope, snapshotIsValid, SnapshotAssembler, projectedCellIndices, clampObserverZoom, observerFitScale, boot});
|
|
|
|
|
if (typeof module !== "undefined" && module.exports) module.exports = api;
|
|
|
|
|
root.Maze32Player = api;
|
|
|
|
|
if (root.document) root.document.addEventListener("DOMContentLoaded", () => { root.maze32PlayerApp = boot(root.document, root); });
|
|
|
|
|