Diablo2D — Shadows of Tristram: complete browser ARPG
- Isometric canvas renderer (depth-sorted, FOV/fog, additive lighting) - 3 classes x 20 skills, 4 acts x 4 floors + boss lairs, torment I-X - Diablo-style loot: rarities, affix tiers, 14 legendaries, vendor, stash - Rogue camp with 6 NPCs: Charsi/Akara/Kashya/Cain/Gheed/storage - NPC quest chain (accept -> hunt -> turn in) with rewards & gating - Procedural WebAudio SFX + generative music, EN/VI localization - Saves, settings, waypoints, hardcore mode, PWA manifest - 93-assertion headless suite + browser E2E via CDP
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
/* Verifies the stuck-mouse fix inside the live game page.
|
||||
* usage: node tools/cdp_mouse_test.mjs [port] */
|
||||
const port = process.argv[2] || '9366';
|
||||
const targets = await (await fetch(`http://127.0.0.1:${port}/json`)).json();
|
||||
const page = targets.find(t => t.type === 'page');
|
||||
const ws = new WebSocket(page.webSocketDebuggerUrl);
|
||||
let id = 0;
|
||||
const pending = new Map();
|
||||
const send = (m, p = {}) => new Promise((res, rej) => {
|
||||
const i = ++id; pending.set(i, { res, rej });
|
||||
ws.send(JSON.stringify({ id: i, method: m, params: p }));
|
||||
});
|
||||
ws.onmessage = ev => {
|
||||
const msg = JSON.parse(ev.data);
|
||||
if (msg.id && pending.has(msg.id)) {
|
||||
const { res, rej } = pending.get(msg.id); pending.delete(msg.id);
|
||||
msg.error ? rej(new Error(msg.error.message)) : res(msg.result);
|
||||
}
|
||||
};
|
||||
await new Promise(r => { ws.onopen = r; });
|
||||
const ev = async expr => (await send('Runtime.evaluate', { expression: expr, returnByValue: true })).result.value;
|
||||
|
||||
/* wait for game ready */
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const s = await ev(`(document.getElementById('status')||{textContent:''}).textContent`);
|
||||
if (/DONE|DRIVE_ERROR/.test(s)) break;
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
}
|
||||
|
||||
console.log(await ev(`(() => {
|
||||
const w = document.getElementById('g').contentWindow;
|
||||
const D = w.D2, cv = w.document.getElementById('game-canvas');
|
||||
if (!D || !D.game.player) return 'FAIL no game';
|
||||
const r = cv.getBoundingClientRect();
|
||||
const opts = { bubbles: true, button: 0, clientX: r.left + 500, clientY: r.top + 400 };
|
||||
/* 1. press LMB and NEVER send mouseup (missed-release simulation) */
|
||||
cv.dispatchEvent(new w.MouseEvent('mousedown', opts));
|
||||
return 'pressed';
|
||||
})()`));
|
||||
|
||||
await new Promise(r => setTimeout(r, 700));
|
||||
const during = await ev(`(() => {
|
||||
const D = document.getElementById('g').contentWindow.D2;
|
||||
return JSON.stringify({ left: D.input.state.left, mt: !!D.game.player.moveTarget });
|
||||
})()`);
|
||||
console.log('while held (no mouseup):', during);
|
||||
|
||||
/* 2. user then just MOVES the mouse normally — real browsers attach
|
||||
e.buttons=0 once released; our resync must heal the stuck flag */
|
||||
console.log(await ev(`(() => {
|
||||
const w = document.getElementById('g').contentWindow;
|
||||
const cv = w.document.getElementById('game-canvas');
|
||||
const r = cv.getBoundingClientRect();
|
||||
cv.dispatchEvent(new w.MouseEvent('mousemove', {
|
||||
bubbles: true, clientX: r.left + 520, clientY: r.top + 420, buttons: 0 }));
|
||||
return 'moved';
|
||||
})()`));
|
||||
await new Promise(r => setTimeout(r, 250));
|
||||
const healed = await ev(`(() => {
|
||||
const D = document.getElementById('g').contentWindow.D2;
|
||||
return JSON.stringify({ left: D.input.state.left });
|
||||
})()`);
|
||||
console.log('after normal mousemove:', healed);
|
||||
|
||||
/* 3. full click still works afterwards */
|
||||
console.log(await ev(`(() => {
|
||||
const w = document.getElementById('g').contentWindow;
|
||||
const D = w.D2, cv = w.document.getElementById('game-canvas');
|
||||
const r = cv.getBoundingClientRect();
|
||||
const o = { bubbles: true, button: 0, clientX: r.left + 640, clientY: r.top + 450 };
|
||||
cv.dispatchEvent(new w.MouseEvent('mousemove', o));
|
||||
cv.dispatchEvent(new w.MouseEvent('mousedown', o));
|
||||
w.dispatchEvent(new w.MouseEvent('mouseup', { bubbles: true, button: 0 }));
|
||||
return 'clicked';
|
||||
})()`));
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
console.log(await ev(`(() => {
|
||||
const D = document.getElementById('g').contentWindow.D2;
|
||||
const p = D.game.player;
|
||||
return JSON.stringify({ newTargetAccepted: !!p.moveTarget, left: D.input.state.left });
|
||||
})()`));
|
||||
ws.close();
|
||||
Reference in New Issue
Block a user