Update Healer Man server
This commit is contained in:
1 parent
6cc1b9217f
commit
6114a2295f
67 files changed
+6241
-347
No files matched your search
@@ -1,10 +1,11 @@
|
||||
const { DEFAULT_TEST_DUNGEON_TITLE, openSelectedDungeonRoles } = require('./playtest-defaults.cjs');
|
||||
const { chromium } = require('C:/Users/phenom/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
|
||||
const output = path.resolve('playtest-artifacts/party-equipment');
|
||||
const output = path.resolve('playtest-artifacts/party-equipment', process.argv.includes('--basic-only') ? 'basic' : '.');
|
||||
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'healer-man-equipment-'));
|
||||
fs.mkdirSync(output, { recursive: true });
|
||||
|
||||
@@ -23,8 +24,19 @@ async function capture(page, name) {
|
||||
for (const category of process.argv.includes('--rom-only') ? ['RoM'] : ['WoW', 'RoM']) {
|
||||
const context = await browser.newContext({ viewport: { width: 1600, height: 1000 } });
|
||||
const page = await context.newPage();
|
||||
// Test-only observation of the existing canvas; no alternate character renderer.
|
||||
await page.route(/\/assets\/SkeletonUtils-.*\.js$/, async (route) => {
|
||||
const response = await route.fetch();
|
||||
const source = await response.text();
|
||||
const instrumented = source.replace(/onCreated:([\w$]+)=>\{(?=\1\.events\.connect)/,
|
||||
'onCreated:$1=>{(window.__equipmentQaScenes??=[]).push($1);');
|
||||
assert.notEqual(instrumented, source, 'The built canvas creation hook must be observable.');
|
||||
await route.fulfill({ response, body: instrumented });
|
||||
});
|
||||
page.setDefaultTimeout(60000);
|
||||
const errors = [];
|
||||
let inspectedParty = [];
|
||||
let bowFacing = null;
|
||||
page.on('pageerror', (error) => errors.push(error.message));
|
||||
page.on('console', (message) => {
|
||||
if (message.type() === 'error' && !message.text().startsWith('Failed to load resource:')) errors.push(message.text());
|
||||
@@ -43,27 +55,33 @@ async function capture(page, name) {
|
||||
await page.getByRole('button', { name: /Create character/ }).click();
|
||||
await page.locator('.character-select-screen').waitFor();
|
||||
await page.waitForTimeout(4000);
|
||||
await capture(page, `${category}-roster`);
|
||||
await page.getByRole('button', { name: /Continue as/ }).click({ position: { x: 24, y: 30 } });
|
||||
await page.locator('.main-menu-screen').waitFor();
|
||||
await page.getByRole('button', { name: /Downloads/ }).click();
|
||||
const pack = page.locator('.content-pack-row').filter({ hasText: category === 'WoW'
|
||||
? 'Extra Wrath Character Models & Skins' : 'RuneWaker Character Models & Skins' });
|
||||
await pack.getByRole('button', { name: 'Download', exact: true }).click();
|
||||
await pack.getByText('Installed — offline ready', { exact: true }).waitFor({ timeout: 180000 });
|
||||
await page.getByRole('button', { name: 'Back', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Change character', exact: true }).click();
|
||||
await page.locator('.character-select-screen').waitFor();
|
||||
await page.waitForTimeout(5000);
|
||||
await capture(page, `${category}-roster`);
|
||||
await capture(page, `${category}-roster-basic`);
|
||||
await page.getByRole('button', { name: /Continue as/ }).click({ position: { x: 24, y: 30 } });
|
||||
await page.locator('.main-menu-screen').waitFor();
|
||||
if (!process.argv.includes('--basic-only')) {
|
||||
await page.getByRole('button', { name: /Downloads/ }).click();
|
||||
const pack = page.locator('.content-pack-row').filter({ hasText: category === 'WoW'
|
||||
? 'Extra Wrath Character Models & Skins' : 'RuneWaker Character Models & Skins' });
|
||||
await pack.getByRole('button', { name: 'Download', exact: true }).click();
|
||||
await pack.getByText('Installed — offline ready', { exact: true }).waitFor({ timeout: 180000 });
|
||||
await page.getByRole('button', { name: 'Back', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Change character', exact: true }).click();
|
||||
await page.locator('.character-select-screen').waitFor();
|
||||
await page.waitForTimeout(5000);
|
||||
await capture(page, `${category}-roster`);
|
||||
await page.getByRole('button', { name: /Continue as/ }).click({ position: { x: 24, y: 30 } });
|
||||
await page.locator('.main-menu-screen').waitFor();
|
||||
}
|
||||
await page.getByRole('button', { name: /^Dungeons/ }).click();
|
||||
await page.locator('.dungeon-category-tab').filter({ hasText: category }).click();
|
||||
await page.locator('.dungeon-select-row').filter({ hasText: category === 'WoW' ? 'Wailing Caverns' : 'Forsaken Abbey' }).first().click();
|
||||
const download = page.getByRole('button', { name: 'Download and continue' });
|
||||
if (await download.isVisible()) await download.click();
|
||||
else await page.getByRole('button', { name: 'Choose role' }).click();
|
||||
await page.locator('.dungeon-category-tab').filter({ hasText: 'WoW' }).click();
|
||||
await page.locator('.dungeon-select-row').filter({ hasText: DEFAULT_TEST_DUNGEON_TITLE }).first().click();
|
||||
const download = page.getByRole('button', { name: 'Download and continue', exact: true });
|
||||
if (await download.isVisible()) {
|
||||
await download.click();
|
||||
await page.locator('.dungeon-role-option').first().waitFor({ timeout: 180000 });
|
||||
} else {
|
||||
await openSelectedDungeonRoles(page);
|
||||
}
|
||||
await page.locator('.dungeon-role-option--healer').click();
|
||||
await page.locator('.scene-shell').waitFor();
|
||||
await page.locator('.loading-screen').waitFor({ state: 'hidden' });
|
||||
@@ -71,6 +89,107 @@ async function capture(page, name) {
|
||||
await page.waitForTimeout(12000);
|
||||
await page.locator('.dungeon-boss-intro').waitFor({ state: 'hidden' });
|
||||
await capture(page, `${category}-dungeon`);
|
||||
if (category === 'RoM') {
|
||||
inspectedParty = await page.evaluate(() => {
|
||||
const canvas = document.querySelector('.scene-shell canvas');
|
||||
const state = window.__equipmentQaScenes?.find((entry) => entry.gl.domElement === canvas);
|
||||
if (!state) throw new Error('Could not inspect the live shared scene.');
|
||||
window.equipmentQaScene = state;
|
||||
const actors = state.scene.getObjectByName('party-population').children;
|
||||
return actors.map((actor) => {
|
||||
const weapons = [];
|
||||
const sourceMeshes = [];
|
||||
actor.traverse((node) => {
|
||||
if (node.name.startsWith('equipped-')) weapons.push(node.name);
|
||||
if (/^(blade01|hammer01|shield01|staff00|bow03|dagger01)_/.test(node.name)) sourceMeshes.push(node.name);
|
||||
});
|
||||
if (!sourceMeshes.length) throw new Error(`${actor.name} must carry original RoM weapon meshes.`);
|
||||
return { name: actor.name, position: actor.position.toArray(), weapons, sourceMeshes };
|
||||
});
|
||||
});
|
||||
console.log(JSON.stringify({ inspection: inspectedParty }, null, 2));
|
||||
await page.evaluate(() => {
|
||||
const scene = window.equipmentQaScene.scene;
|
||||
const actors = scene.getObjectByName('party-population').children;
|
||||
const scout = actors.find((actor) => {
|
||||
let found = false;
|
||||
actor.traverse((node) => { if (node.name.startsWith('bow03_')) found = true; });
|
||||
return found;
|
||||
});
|
||||
if (!scout) throw new Error('Caelan must carry the original bow03 RoM mesh.');
|
||||
const state = scout.__r3f.root.getState();
|
||||
window.equipmentQaScene = state;
|
||||
let bow;
|
||||
scout.traverse((node) => { if (node.name.startsWith('bow03_')) bow = node; });
|
||||
let prop = bow;
|
||||
while (prop && !prop.name.startsWith('equipped-')) prop = prop.parent;
|
||||
window.equipmentQaBow = { bow, prop };
|
||||
const vector = scout.position.clone();
|
||||
const eye = state.camera.position.clone();
|
||||
window.equipmentQaOriginalFov = state.camera.fov;
|
||||
const view = {
|
||||
ref: { current: () => {
|
||||
const center = scout.getWorldPosition(vector.clone());
|
||||
center.y += 0.95;
|
||||
// Keep the normal camera's collision-safe location in the corridor.
|
||||
state.camera.position.copy(eye);
|
||||
state.camera.fov = 40;
|
||||
state.camera.updateProjectionMatrix();
|
||||
state.camera.lookAt(center);
|
||||
} }, priority: 0, store: scout.__r3f.root,
|
||||
};
|
||||
state.internal.subscribers.push(view);
|
||||
state.internal.subscribers.sort((left, right) => left.priority - right.priority);
|
||||
window.equipmentQaCameraView = view;
|
||||
});
|
||||
await page.waitForTimeout(1200);
|
||||
bowFacing = await page.evaluate(() => {
|
||||
window.equipmentQaBowFacing = () => {
|
||||
const { bow, prop } = window.equipmentQaBow;
|
||||
bow.updateWorldMatrix(true, true);
|
||||
const center = bow.position.clone().set(0, 0, 0);
|
||||
const tips = center.clone();
|
||||
const upper = center.clone();
|
||||
const lower = center.clone();
|
||||
let upperCount = 0;
|
||||
let lowerCount = 0;
|
||||
let centerCount = 0;
|
||||
let tipCount = 0;
|
||||
const positions = bow.geometry.attributes.position;
|
||||
for (let index = 0; index < positions.count; index++) {
|
||||
const y = Math.abs(positions.getY(index));
|
||||
const point = bow.localToWorld(bow.getVertexPosition(index, center.clone().set(0, 0, 0)));
|
||||
if (y < 0.2) { center.add(point); centerCount++; }
|
||||
if (y > 0.8) { tips.add(point); tipCount++; }
|
||||
if (positions.getY(index) > 0.8) { upper.add(point); upperCount++; }
|
||||
if (positions.getY(index) < -0.8) { lower.add(point); lowerCount++; }
|
||||
}
|
||||
center.divideScalar(centerCount);
|
||||
tips.divideScalar(tipCount);
|
||||
upper.divideScalar(upperCount);
|
||||
lower.divideScalar(lowerCount);
|
||||
const actor = window.equipmentQaScene.scene.getObjectByName('party-population').children.find((node) => {
|
||||
let found = false;
|
||||
node.traverse((child) => { if (child === bow) found = true; });
|
||||
return found;
|
||||
});
|
||||
const chest = actor.localToWorld(center.clone().set(0, 1.1, 0));
|
||||
const facing = tips.sub(center).normalize().dot(chest.sub(prop.getWorldPosition(center.clone())).normalize());
|
||||
return { stringSideTowardTorso: facing, longAxisUp: Math.abs(upper.sub(lower).normalize().y), grip: prop.getWorldPosition(center.clone()).toArray() };
|
||||
};
|
||||
return window.equipmentQaBowFacing();
|
||||
});
|
||||
console.log(JSON.stringify({ bowFacing }));
|
||||
assert.ok(bowFacing.stringSideTowardTorso > 0.1, 'The original bow string side must face Caelan’s torso.');
|
||||
await capture(page, process.argv.includes('--basic-only') ? 'RoM-Caelan-basic' : 'RoM-Caelan-native');
|
||||
await page.evaluate(() => {
|
||||
const subscribers = window.equipmentQaScene.internal.subscribers;
|
||||
const index = subscribers.indexOf(window.equipmentQaCameraView);
|
||||
if (index >= 0) subscribers.splice(index, 1);
|
||||
window.equipmentQaScene.camera.fov = window.equipmentQaOriginalFov;
|
||||
window.equipmentQaScene.camera.updateProjectionMatrix();
|
||||
});
|
||||
}
|
||||
// Rotate the camera with the normal held-right-mouse gesture to show the party fronts.
|
||||
const bounds = await page.locator('.scene-shell canvas').boundingBox();
|
||||
await page.mouse.move(bounds.x + bounds.width * 0.5, bounds.y + bounds.height * 0.55);
|
||||
@@ -90,9 +209,14 @@ async function capture(page, name) {
|
||||
}
|
||||
assert.deepEqual(errors, []);
|
||||
results[category] = {
|
||||
installedNativeCharacterPack: true,
|
||||
screenshots: [`${category}-roster.png`, `${category}-dungeon.png`, `${category}-party-front.png`,
|
||||
...(category === 'RoM' ? [`${category}-party-open.png`] : [])],
|
||||
dungeon: DEFAULT_TEST_DUNGEON_TITLE,
|
||||
installedNativeCharacterPack: !process.argv.includes('--basic-only'),
|
||||
screenshots: [`${category}-roster-basic.png`,
|
||||
...(!process.argv.includes('--basic-only') ? [`${category}-roster.png`] : []),
|
||||
`${category}-dungeon.png`, `${category}-party-front.png`,
|
||||
...(category === 'RoM' ? [`${category}-party-open.png`, process.argv.includes('--basic-only') ? 'RoM-Caelan-basic.png' : 'RoM-Caelan-native.png'] : [])],
|
||||
inspectedParty,
|
||||
bowFacing,
|
||||
errors,
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
Reference in new issue
Block a user