From b5f3137fffe4ce37e7aae6dce4e3dfec20b6f55a Mon Sep 17 00:00:00 2001 From: "hamed@B" Date: Wed, 21 Oct 2026 10:58:14 +0330 Subject: [PATCH] update --- README.md | 8 ++++---- src/controls.js | 6 +++--- src/main.js | 15 ++++++++++++--- tests/controls.test.js | 5 +++-- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e5770d2..26c571b 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ node --test tests/*.test.js - E یا دکمهٔ محصولات: باز کردن دستگاه در نزدیکی آن - شروع دوباره: بازنشانی داستان، موجودی و زوم -گفت‌وگوها کوتاه‌اند و در کادر سفید پایین صفحه خودکار عوض می‌شوند. در طول تمام گفت‌وگوها، از جمله پل، حرکت آزاد است. هنگام خرید، تحویل محصول یا باز بودن دفترچه حرکت موقتاً متوقف می‌شود. +گفت‌وگوها کوتاه‌اند و در کادر سفید پایین صفحه خودکار عوض می‌شوند. هنگام گفت‌وگوها، خرید، تحویل محصول یا باز بودن دفترچه حرکت موقتاً متوقف می‌شود. ## دستگاه ۶۰ کانال و خرید با هتومان @@ -32,7 +32,7 @@ node --test tests/*.test.js ## ساختار - `src/main.js`: صحنه، دوربین، دستگاه، رابط و حلقهٔ بازی -- `src/controls.js`: جهت حرکت و شرایط مجاز بودن حرکت؛ دیالوگ مانع حرکت نیست +- `src/controls.js`: جهت حرکت و شرایط مجاز بودن حرکت؛ دیالوگ حرکت را موقتاً متوقف می‌کند - `src/characters.js`: شخصیت‌های مفصلی، تناسب بدن، لباس، چشم‌ها، قدم زدن و تماس دست‌ها - `src/payment.js`: مراحل پرداخت، انصراف و جلوگیری از برداشت تکراری - `src/game-state.js`: قیمت‌های هتومان، موجودی، اعتبارسنجی کد و خرید @@ -50,12 +50,12 @@ node --test tests/*.test.js ## تعامل تک‌دکمه‌ای -گزینه‌های چندانتخابی داستان حذف شده‌اند. هر رویداد یک کار مشخص دارد: تماشای خرگوش، برداشتن نقاشی، ادامه در مسیر آفتابی و کامل کردن نقاشی با آسمان طلایی. برای انجام آن، کلید **F** یا همان دکمهٔ روی صفحه را بزنید. متن‌های معمولی خودکار عوض می‌شوند و حرکت آزاد می‌ماند. **E** تعامل با دستگاه و **C** کشیدن کارت است. انتخاب شمارهٔ محصول و دکمهٔ پرداخت در پنجرهٔ دستگاه باقی است. +گزینه‌های چندانتخابی داستان حذف شده‌اند. هر رویداد یک کار مشخص دارد: تماشای خرگوش، برداشتن نقاشی، ادامه در مسیر آفتابی و کامل کردن نقاشی با آسمان طلایی. برای انجام آن، کلید **F** یا همان دکمهٔ روی صفحه را بزنید. متن‌های معمولی خودکار عوض می‌شوند و پس از پایان گفت‌وگو حرکت دوباره آزاد می‌شود. **E** تعامل با دستگاه و **C** کشیدن کارت است. انتخاب شمارهٔ محصول و دکمهٔ پرداخت در پنجرهٔ دستگاه باقی است. ## سفر از خانه به کوه بازی با خروج خانواده از خانه شروع می‌شود. با F یا دکمهٔ روی صفحه سوار ماشین شوید؛ با W یا ↑ گاز بدهید، با S یا ↓ ترمز کنید و با A/D فرمان بدهید؛ پس از رسیدن به ورودی جنگل، در ورودی جنگل دوباره F را بزنید تا پیاده شوید. در مسیر جنگل، جمعی از دخترها و پسرها کنار آتش می‌رقصند و خانواده آدرس دستگاه وندینگ را از آن‌ها می‌پرسد. پس از خرید نوشیدنی و خوراکی، مسیر را تا پیک‌نیک بالای کوه ادامه دهید. -دوربین پیش‌فرض سوم‌شخص است؛ با V یا دکمهٔ تغییر دوربین بین نمای اول‌شخص و سوم‌شخص جابه‌جا شوید. برای نگاه به اطراف روی صحنه بکشید. W/S یا چپ/راست حرکت در مسیر و Shift دویدن است. این نسخه از مدل‌های هندسی استفاده می‌کند و کیفیت گرافیکی یک بازی AAA ندارد. +دوربین پیش‌فرض سوم‌شخص است؛ با V یا دکمهٔ تغییر دوربین بین نمای اول‌شخص و سوم‌شخص جابه‌جا شوید. برای نگاه به اطراف در نمای اول‌شخص یا چرخاندن دوربین در نمای سوم‌شخص روی صحنه بکشید؛ هنگام تمرکز خودکار روی رویدادها و دستگاه، نمای دوربین ثابت است. W/S یا چپ/راست حرکت در مسیر و Shift دویدن است. این نسخه از مدل‌های هندسی استفاده می‌کند و کیفیت گرافیکی یک بازی AAA ندارد. گفت‌وگوی هر رویداد هنگام رسیدن به همان نقطه نمایش داده می‌شود و تا پایان آن حرکت متوقف می‌ماند. دوربین نزدیک دستگاه خودکار روی آن تمرکز می‌کند. خانواده پس از رسیدن به قله روی فرش می‌نشینند. diff --git a/src/controls.js b/src/controls.js index 5f3a564..1d204be 100644 --- a/src/controls.js +++ b/src/controls.js @@ -1,9 +1,9 @@ -// Dialogue deliberately never locks walking. Only transactional UI and delivery do. +// Dialogue, transactional UI and delivery pause walking. export function movementDirection(pressed, held) { return Number(pressed.has('arrowright') || pressed.has('d') || held.has(1)) - Number(pressed.has('arrowleft') || pressed.has('a') || held.has(-1)); } -export function canWalk({shopOpen = false, notebookOpen = false, endingOpen = false, busy = false, finished = false}) { - return !shopOpen && !notebookOpen && !endingOpen && !busy && !finished; +export function canWalk({shopOpen = false, notebookOpen = false, endingOpen = false, busy = false, finished = false, chapterId = null}) { + return !shopOpen && !notebookOpen && !endingOpen && !busy && !finished && !chapterId; } diff --git a/src/main.js b/src/main.js index a88a38a..854f276 100644 --- a/src/main.js +++ b/src/main.js @@ -835,7 +835,8 @@ function frame(now) { shopOpen: shop.open, notebookOpen: notebook.open, endingOpen: ending.open, - busy: state.busy || Boolean(chapterId), + busy: state.busy, + chapterId, finished: state.finished }) ? direction : 0; x = THREE.MathUtils.clamp(x + moving * (pressed.has('shift') ? 3.7 : 2.2) * dt, -9, 22); @@ -881,7 +882,15 @@ function frame(now) { zoomDistance = THREE.MathUtils.clamp(zoomDistance + zoomInput * dt * .45, .6, 1.6); } camTarget.sub(lookTarget).multiplyScalar(zoomDistance).add(lookTarget); - if (walking && Math.abs(x - stationX) >= 3.2 && firstPerson && !shopping && !state.finished && !encounterActive) { + const firstPersonActive = walking && Math.abs(x - stationX) >= 3.2 && firstPerson && !shopping && !state.finished && !encounterActive && chapterId !== 'camp'; + if (walking && !firstPersonActive && !shopping && !state.finished && !encounterActive && chapterId !== 'camp' && Math.abs(x - stationX) >= 3.2) { + camTarget.sub(lookTarget); + const orbit = new THREE.Spherical().setFromVector3(camTarget); + orbit.theta += viewYaw; + orbit.phi = THREE.MathUtils.clamp(orbit.phi - viewPitch, .2, Math.PI / 2 - .05); + camTarget.setFromSpherical(orbit).add(lookTarget); + } + if (firstPersonActive) { const bob = moving ? Math.sin(gait * 2) * .035 : 0; camTarget.set(x - .35, 1.7 + pathHeight(x) + bob, pathZ(x) - .7); lookTarget.set(x + 5 * Math.cos(viewYaw), 1.65 + pathHeight(x) + viewPitch * 4, pathZ(x) + 5 * Math.sin(viewYaw)); @@ -903,7 +912,7 @@ function frame(now) { lookTarget.set(cx + 3, 1, scenery.car.position.z); } } - family.visible = journey.phase !== 'driving' && !(journey.phase === 'boarding' && journey.time > 1.8) && !(walking && firstPerson && !shopping && !state.finished && !encounterActive); + family.visible = journey.phase !== 'driving' && !(journey.phase === 'boarding' && journey.time > 1.8) && !firstPersonActive; if (chapterId === 'camp' && walking) { camTarget.set(1, 2.4, -1); lookTarget.set(4, 1, 4.4); diff --git a/tests/controls.test.js b/tests/controls.test.js index 8d3008c..b3cdc38 100644 --- a/tests/controls.test.js +++ b/tests/controls.test.js @@ -3,8 +3,9 @@ import assert from 'node:assert/strict'; import {canWalk,movementDirection} from '../src/controls.js'; import {products,createState,purchase,INITIAL_BALANCE,formatHetoman} from '../src/game-state.js'; import {createFamily} from '../src/characters.js'; -test('Walking remains enabled during dialogue; arrows up/down only zoom',()=>{ - assert.ok(canWalk({chapterId:'bridge'}));assert.ok(canWalk({chapterId:'fish'})); +test('Dialogue pauses walking; arrows up/down only zoom',()=>{ + assert.equal(canWalk({chapterId:'bridge'}),false);assert.equal(canWalk({chapterId:'fish'}),false); + assert.ok(canWalk({chapterId:null})); assert.equal(canWalk({busy:true}),false);assert.equal(canWalk({shopOpen:true}),false); assert.equal(movementDirection(new Set(['arrowup']),new Set()),0); assert.equal(movementDirection(new Set(['arrowright']),new Set()),1);