Enable flashlight at sunset and switch primary night action
This commit is contained in:
@@ -8,5 +8,6 @@ export const FLASHLIGHT = {
|
||||
halfAngle: 28 * Math.PI / 180,
|
||||
penumbra: 0.6,
|
||||
color: '#fff2dc',
|
||||
nightAltitude: 0,
|
||||
// Apparent solar centre below the horizon by roughly one solar radius.
|
||||
nightAltitude: -0.3,
|
||||
} as const;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { FLASHLIGHT } from '../constants/flashlight.ts';
|
||||
|
||||
/** Auto-enable only on night entry; preserve manual choices during the night. */
|
||||
export function flashlightAfterSunUpdate(on: boolean, previousAltitude: number | undefined, altitude: number) {
|
||||
if (altitude > FLASHLIGHT.nightAltitude) return false;
|
||||
return previousAltitude === undefined || previousAltitude > FLASHLIGHT.nightAltitude ? true : on;
|
||||
}
|
||||
+25
-9
@@ -52,6 +52,7 @@ import type { PlayerState, PlayerUpdate } from '../multiplayer/protocol.ts';
|
||||
import { createSelfBadge } from '../ui/selfBadge.ts';
|
||||
import { createFlashlight } from './flashlight.ts';
|
||||
import { FLASHLIGHT } from '../constants/flashlight.ts';
|
||||
import { flashlightAfterSunUpdate } from '../controls/flashlight.ts';
|
||||
|
||||
export function createWorld(canvas: HTMLCanvasElement, terrainData: Awaited<ReturnType<typeof loadTerrain>>, buildingData: BuildingAssets | null,
|
||||
onMove: (location: { latitude: number; longitude: number; elevation: number }) => void, coast?: LoadedCoast, structureAssets?: StructureAssets, reconstructSummit = false, summitGeometry?: BufferGeometry,
|
||||
@@ -182,6 +183,8 @@ export function createWorld(canvas: HTMLCanvasElement, terrainData: Awaited<Retu
|
||||
const emotes = createEmoteState();
|
||||
const emoteEvents = new AbortController();
|
||||
const waveButton = document.querySelector<HTMLButtonElement>('#emote-wave')!;
|
||||
const primaryActionLabel = document.getElementById('primary-action-label')!;
|
||||
const primaryActionIcon = waveButton.querySelector('use')!;
|
||||
const prayButton = document.querySelector<HTMLButtonElement>('#emote-pray')!;
|
||||
const emoteStatus = document.getElementById('emote-status')!;
|
||||
const rainGear = createRainGearState();
|
||||
@@ -233,19 +236,26 @@ export function createWorld(canvas: HTMLCanvasElement, terrainData: Awaited<Retu
|
||||
if (!sheep || overview || networkBlocked || !solarState || solarState.altitude > FLASHLIGHT.nightAltitude) return;
|
||||
flashlightOn = !flashlightOn;
|
||||
sheep.setEquipment(rainGear.umbrella, rainGear.raincoat, rainGear.hood, flashlightOn);
|
||||
refreshGear(); rendering.invalidate();
|
||||
refreshEmotes(); rendering.invalidate();
|
||||
};
|
||||
flashlightButton.addEventListener('click', toggleFlashlight, { signal: emoteEvents.signal });
|
||||
const prayerAvailable = () => Boolean(solarState && canPray(solarState.date, solarState.sunrise));
|
||||
const refreshEmotes = () => {
|
||||
refreshGear();
|
||||
waveButton.disabled = !sheep || overview;
|
||||
const dark = Boolean(solarState && solarState.altitude <= FLASHLIGHT.nightAltitude);
|
||||
waveButton.disabled = !sheep || overview || networkBlocked;
|
||||
primaryActionLabel.textContent = dark ? (flashlightOn ? '끄기' : '켜기') : '인사';
|
||||
primaryActionIcon.setAttribute('href', dark ? '#icon-flashlight' : '#icon-wave');
|
||||
const primaryActionName = dark ? `손전등 ${flashlightOn ? '끄기' : '켜기'}` : '손 흔들기';
|
||||
waveButton.setAttribute('aria-label', primaryActionName);
|
||||
waveButton.title = `${primaryActionName} · 단축키 1`;
|
||||
prayButton.disabled = !sheep || overview;
|
||||
prayButton.setAttribute('aria-disabled', String(prayButton.disabled || !prayerAvailable()));
|
||||
prayButton.dataset.unavailableReason = prayerAvailable() ? '' : PRAYER_NOTICE;
|
||||
waveButton.setAttribute('aria-pressed', String(emotes.current === 'wave'));
|
||||
waveButton.setAttribute('aria-pressed', String(dark ? flashlightOn : emotes.current === 'wave'));
|
||||
prayButton.setAttribute('aria-pressed', String(emotes.current === 'pray'));
|
||||
emoteStatus.textContent = overview ? '조감 보기에서는 감정표현을 사용할 수 없습니다'
|
||||
: dark ? '1 손전등 켜기·끄기 / 2 소원빌기'
|
||||
: emotes.current === 'wave' ? '손 흔드는 중 · 이동하면 종료'
|
||||
: emotes.current === 'pray' ? '일출을 보며 소원 비는 중 · 이동하면 종료'
|
||||
: prayerAvailable() ? '소원빌기 가능 · 1 인사 / 2 소원빌기 · 이동하면 종료'
|
||||
@@ -260,7 +270,11 @@ export function createWorld(canvas: HTMLCanvasElement, terrainData: Awaited<Retu
|
||||
refreshEmotes();
|
||||
rendering.invalidate();
|
||||
};
|
||||
waveButton.addEventListener('click', () => startEmote('wave'), { signal: emoteEvents.signal });
|
||||
const primaryAction = () => {
|
||||
if (solarState && solarState.altitude <= FLASHLIGHT.nightAltitude) toggleFlashlight();
|
||||
else startEmote('wave');
|
||||
};
|
||||
waveButton.addEventListener('click', primaryAction, { signal: emoteEvents.signal });
|
||||
prayButton.addEventListener('click', () => startEmote('pray'), { signal: emoteEvents.signal });
|
||||
window.addEventListener('keydown', event => {
|
||||
if (!controls.active() || event.repeat || !['Digit1', 'Digit2', 'Digit3', 'Digit4', 'Digit5', 'Digit6'].includes(event.code)) return;
|
||||
@@ -269,7 +283,8 @@ export function createWorld(canvas: HTMLCanvasElement, terrainData: Awaited<Retu
|
||||
if (['Digit3', 'Digit4', 'Digit5'].includes(event.code)) {
|
||||
toggleGear(event.code === 'Digit3' ? 'umbrella' : event.code === 'Digit4' ? 'raincoat' : 'hood'); return;
|
||||
}
|
||||
startEmote(event.code === 'Digit1' ? 'wave' : 'pray');
|
||||
if (event.code === 'Digit1') primaryAction();
|
||||
else startEmote('pray');
|
||||
}, { signal: emoteEvents.signal });
|
||||
let eyeHeight = observer.eyeHeight;
|
||||
let walkedDistance = 0;
|
||||
@@ -415,7 +430,7 @@ export function createWorld(canvas: HTMLCanvasElement, terrainData: Awaited<Retu
|
||||
camera.position.x = state.x; camera.position.z = state.z;
|
||||
if (sheep) sheep.group.rotation.y = state.yaw;
|
||||
rainGear.apply(state.equipment);
|
||||
flashlightOn = Boolean(state.equipment.flashlight && solarState && solarState.altitude <= FLASHLIGHT.nightAltitude);
|
||||
flashlightOn = Boolean((state.equipment.flashlight ?? flashlightOn) && solarState && solarState.altitude <= FLASHLIGHT.nightAltitude);
|
||||
sheep?.setEquipment(rainGear.umbrella, rainGear.raincoat, rainGear.hood, flashlightOn);
|
||||
placeObserver(); refreshEmotes(); rendering.invalidate();
|
||||
},
|
||||
@@ -465,10 +480,11 @@ export function createWorld(canvas: HTMLCanvasElement, terrainData: Awaited<Retu
|
||||
placeObserver();
|
||||
},
|
||||
update(state: SunState) {
|
||||
const nextFlashlightOn = flashlightAfterSunUpdate(flashlightOn, solarState?.altitude, state.altitude);
|
||||
solarState = state;
|
||||
if (flashlightOn && state.altitude > FLASHLIGHT.nightAltitude) {
|
||||
flashlightOn = false;
|
||||
sheep?.setEquipment(rainGear.umbrella, rainGear.raincoat, rainGear.hood, false);
|
||||
if (flashlightOn !== nextFlashlightOn) {
|
||||
flashlightOn = nextFlashlightOn;
|
||||
sheep?.setEquipment(rainGear.umbrella, rainGear.raincoat, rainGear.hood, flashlightOn);
|
||||
}
|
||||
emotes.update(0, false, prayerAvailable());
|
||||
refreshEmotes();
|
||||
|
||||
Reference in New Issue
Block a user