Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 38 additions & 20 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1862,6 +1862,7 @@
<div id="quake-damage-overlay" aria-hidden="true"></div>
<div id="quake-notify" role="log" aria-live="polite" hidden></div>
<div id="quake-centerprint" role="status" aria-live="polite" hidden></div>
<div id="quake-intermission" role="status" aria-live="polite" hidden></div>
<div id="quake-main-menu" role="button" tabindex="-1" aria-label="cssQuake menu">
<span id="quake-main-menu-art">
<img id="quake-main-menu-plaque" src="/q/main-menu-plaque.png" width="32" height="144" alt="" />
Expand Down Expand Up @@ -2148,72 +2149,74 @@ <h1 id="quake-debug-menu-title" class="quake-menu-panel-title quake-bm-title qua
<div id="quake-debug-menu-sections" class="quake-menu-panel-sections">
<div class="quake-options-list">
<label class="quake-option-toggle" tabindex="0">
<input id="quake-debug-show-menu" type="checkbox" checked />
<input id="quake-debug-enabled" type="checkbox" />
<span class="quake-option-checkbox" aria-hidden="true"></span>
<span class="quake-option-text quake-bm-label">Show menu</span>
<span class="quake-option-text quake-bm-label">Show stats panel</span>
<span class="quake-option-value quake-option-value-off quake-bm-label quake-bm-alt">off</span>
<span class="quake-option-value quake-option-value-on quake-bm-label quake-bm-alt">on</span>
</label>
<label class="quake-option-toggle" tabindex="0">
<input id="quake-debug-enabled" type="checkbox" />
<input id="quake-debug-show-menu" type="checkbox" checked />
<span class="quake-option-checkbox" aria-hidden="true"></span>
<span class="quake-option-text quake-bm-label">Show stats</span>
<span class="quake-option-text quake-bm-label">Show main menu</span>
<span class="quake-option-value quake-option-value-off quake-bm-label quake-bm-alt">off</span>
<span class="quake-option-value quake-option-value-on quake-bm-label quake-bm-alt">on</span>
</label>
<div class="quake-debug-menu-divider" role="separator" aria-hidden="true"></div>
<label class="quake-option-toggle" tabindex="0">
<input id="quake-debug-show-fps" type="checkbox" checked />
<input id="quake-debug-show-textures" type="checkbox" checked />
<span class="quake-option-checkbox" aria-hidden="true"></span>
<span class="quake-option-text quake-bm-label">Show FPS</span>
<span class="quake-option-text quake-bm-label">Show textures</span>
<span class="quake-option-value quake-option-value-off quake-bm-label quake-bm-alt">off</span>
<span class="quake-option-value quake-option-value-on quake-bm-label quake-bm-alt">on</span>
</label>
<label class="quake-option-toggle" tabindex="0">
<input id="quake-debug-enable-animations" type="checkbox" checked />
<input id="quake-debug-show-labels" type="checkbox" />
<span class="quake-option-checkbox" aria-hidden="true"></span>
<span class="quake-option-text quake-bm-label">Enable animations</span>
<span class="quake-option-text quake-bm-label">Show IDs</span>
<span class="quake-option-value quake-option-value-off quake-bm-label quake-bm-alt">off</span>
<span class="quake-option-value quake-option-value-on quake-bm-label quake-bm-alt">on</span>
</label>
<label class="quake-option-toggle" tabindex="0">
<input id="quake-debug-freeze-enemies" type="checkbox" />
<input id="quake-debug-show-outlines" type="checkbox" />
<span class="quake-option-checkbox" aria-hidden="true"></span>
<span class="quake-option-text quake-bm-label">Freeze enemies</span>
<span class="quake-option-text quake-bm-label">Show outlines</span>
<span class="quake-option-value quake-option-value-off quake-bm-label quake-bm-alt">off</span>
<span class="quake-option-value quake-option-value-on quake-bm-label quake-bm-alt">on</span>
</label>
<div class="quake-debug-menu-divider" role="separator" aria-hidden="true"></div>
<label class="quake-option-toggle" tabindex="0">
<input id="quake-debug-disable-attacks" type="checkbox" />
<input id="quake-debug-show-fps" type="checkbox" checked />
<span class="quake-option-checkbox" aria-hidden="true"></span>
<span class="quake-option-text quake-bm-label">Disable attacks</span>
<span class="quake-option-text quake-bm-label">Show FPS</span>
<span class="quake-option-value quake-option-value-off quake-bm-label quake-bm-alt">off</span>
<span class="quake-option-value quake-option-value-on quake-bm-label quake-bm-alt">on</span>
</label>
<label class="quake-option-toggle" tabindex="0">
<input id="quake-debug-show-textures" type="checkbox" checked />
<input id="quake-debug-enable-animations" type="checkbox" checked />
<span class="quake-option-checkbox" aria-hidden="true"></span>
<span class="quake-option-text quake-bm-label">Show textures</span>
<span class="quake-option-text quake-bm-label">Enable animations</span>
<span class="quake-option-value quake-option-value-off quake-bm-label quake-bm-alt">off</span>
<span class="quake-option-value quake-option-value-on quake-bm-label quake-bm-alt">on</span>
</label>
<label class="quake-option-toggle" tabindex="0">
<input id="quake-debug-fly-mode" type="checkbox" />
<input id="quake-debug-freeze-enemies" type="checkbox" />
<span class="quake-option-checkbox" aria-hidden="true"></span>
<span class="quake-option-text quake-bm-label">Fly controls</span>
<span class="quake-option-text quake-bm-label">Freeze enemies</span>
<span class="quake-option-value quake-option-value-off quake-bm-label quake-bm-alt">off</span>
<span class="quake-option-value quake-option-value-on quake-bm-label quake-bm-alt">on</span>
</label>
<label class="quake-option-toggle" tabindex="0">
<input id="quake-debug-show-labels" type="checkbox" />
<input id="quake-debug-disable-attacks" type="checkbox" />
<span class="quake-option-checkbox" aria-hidden="true"></span>
<span class="quake-option-text quake-bm-label">Show IDs</span>
<span class="quake-option-text quake-bm-label">Disable attacks</span>
<span class="quake-option-value quake-option-value-off quake-bm-label quake-bm-alt">off</span>
<span class="quake-option-value quake-option-value-on quake-bm-label quake-bm-alt">on</span>
</label>
<label class="quake-option-toggle" tabindex="0">
<input id="quake-debug-show-outlines" type="checkbox" />
<input id="quake-debug-fly-mode" type="checkbox" />
<span class="quake-option-checkbox" aria-hidden="true"></span>
<span class="quake-option-text quake-bm-label">Show outlines</span>
<span class="quake-option-text quake-bm-label">Fly controls</span>
<span class="quake-option-value quake-option-value-off quake-bm-label quake-bm-alt">off</span>
<span class="quake-option-value quake-option-value-on quake-bm-label quake-bm-alt">on</span>
</label>
Expand Down Expand Up @@ -2326,6 +2329,21 @@ <h2 id="quake-debug-title" class="quake-bm-label quake-bm-alt">Debug</h2>
/>
</svg>
</a>
<a
class="quake-id-software-link"
rel="noopener noreferrer"
target="_blank"
aria-label="View Quake source on GitHub"
href="https://github.com/id-Software/Quake"
>
<img
class="quake-id-software-logo"
src="/src/assets/id-software-logo.svg"
width="40"
height="40"
alt=""
/>
</a>
<a
class="quake-css-logo"
rel="noopener noreferrer"
Expand Down
97 changes: 96 additions & 1 deletion src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
import { QUAKE_RENDER_SUPERSAMPLE } from "./prepare/scene";
import type {
QuakeEntity,
QuakeEntityManifestPoint,
QuakeScene,
QuakeVertex,
} from "./types/quake";
import { QUAKE_PLAYER_WEAPON_FIRE_FACTS } from "./generated/quakeProgramFacts";
import { createQuakeSoundController, type QuakeSoundEvent } from "./runtime/audio";
Expand Down Expand Up @@ -78,6 +80,11 @@ import { createQuakeDebugPanelFlow } from "./runtime/app/debugPanelFlow";
import { createQuakeDebugRecordingSnapshotFlow } from "./runtime/app/debugRecordingSnapshotFlow";
import { createQuakeEntityMeshMountFlow } from "./runtime/app/entityMeshMountFlow";
import { createQuakeHudFlow } from "./runtime/app/hudFlow";
import { createQuakeIntermissionFlow } from "./runtime/app/intermissionFlow";
import {
createQuakeLevelStatsFlow,
quakeLevelStatsTotalsForEntities,
} from "./runtime/app/levelStatsFlow";
import { createQuakeAppInputController } from "./runtime/app/input";
import { createQuakeGameplayInputFlow } from "./runtime/app/gameplayInputFlow";
import { createQuakeDamageableBrushFlow } from "./runtime/app/damageableBrushFlow";
Expand Down Expand Up @@ -288,11 +295,17 @@ const {
damageOverlay,
notify: quakeNotify,
centerPrint: quakeCenterPrint,
intermission: quakeIntermissionRoot,
} = quakeDom;
const quakeText = createQuakeTextController({
centerPrintRoot: quakeCenterPrint,
notifyRoot: quakeNotify,
});
const quakeIntermission = createQuakeIntermissionFlow({
renderBitmapText: mountQuakeBitmapText,
root: quakeIntermissionRoot,
});
const quakeLevelStats = createQuakeLevelStatsFlow();
const hudElements = createQuakeHudElements({
root: classicHud,
armor: hudArmorValue,
Expand Down Expand Up @@ -1687,6 +1700,9 @@ const shootables = createQuakeShootablesController({
isInPlayerView: (point) => quakeSceneMount.isPointInPlayerView(point, QUAKE_MONSTER_MOUNT_VIEW_DOT_MIN),
leafIndexAt: world.leafIndexAt,
monsterRuntimeEnabled: () => QUAKE_MONSTER_RUNTIME_ENABLED && !QUAKE_MULTIPLAYER_ENABLED && !quakeEnemiesDisabled,
onDestroyed: (entity) => {
if (entity.classname.startsWith("monster_")) quakeLevelStats.markMonsterKilled(entity.index);
},
pointToPoly: quakeCameraView.pointToPoly,
shouldSpawn: shouldSpawnQuakeShootableForCurrentMode,
pixelate: world.pixelate,
Expand Down Expand Up @@ -1862,6 +1878,7 @@ quakePointerGameplay = createQuakePointerGameplayFlow({
pointerLockElement: () => document.pointerLockElement,
queueCrosshairTargetSync: queueQuakeCrosshairTargetSync,
renderSupersample: QUAKE_RENDER_SUPERSAMPLE,
requestIntermissionAdvance: requestQuakeIntermissionAdvance,
respawnPlayerFromDeath: respawnQuakePlayerFromDeath,
rotation: () => ({
rotX: scene.camera.state.rotX ?? 88,
Expand Down Expand Up @@ -2052,7 +2069,10 @@ const quakeLoading = createQuakeLoadingFlow({
trace: markQuakeTrace,
});
const quakeSceneMount = createQuakeSceneMountFlow({
afterMountScene: startQuakeMultiplayerScene,
afterMountScene: () => {
resetQuakeLevelStatsForCurrentScene();
startQuakeMultiplayerScene();
},
audio,
beforeDisposeScene: () => stopQuakeMultiplayerScene("scene-dispose"),
clearPlayerDeath: clearQuakePlayerDeath,
Expand Down Expand Up @@ -2115,9 +2135,14 @@ quakeEntityActivation = createQuakeEntityActivationFlow({
currentGameLogic: () => currentResult?.gameLogic,
entities: () => entityByIndex,
getOrigin: () => controls.getOrigin(),
intermission: {
show: () => quakeIntermission.show(quakeLevelStats.freeze()),
syncCamera: syncQuakeIntermissionCamera,
},
loadMap: loadQuakeMap,
mapExists: quakeAssetCatalog.mapExists,
movers,
onSecretActivated: (entity) => quakeLevelStats.markSecret(entity.index),
pickups: getPickups(),
player: getPlayer,
pointToPoly: quakeCameraView.pointToPoly,
Expand All @@ -2129,6 +2154,7 @@ quakeEntityActivation = createQuakeEntityActivationFlow({
targets: targetSystem,
text: {
centerPrint: (message) => quakeTextPresentation.centerPrint(message),
clearCenterPrint: () => quakeTextPresentation.clearCenterPrint(),
hasUseTargetsMessageText: (entity) => quakeTextPresentation.hasUseTargetsMessageText(entity),
setCenterPrint: (message) => quakeTextPresentation.setCenterPrint(message),
showDirectCenterPrintMessageText: (entity) => quakeTextPresentation.showDirectCenterPrintMessageText(entity),
Expand Down Expand Up @@ -2433,9 +2459,19 @@ function clearQuakeLevelLoadTimer(): void {
}

function clearQuakeLevelComplete(): void {
quakeIntermission.clear();
quakePlayerLifecycle.clearLevelComplete();
}

function requestQuakeIntermissionAdvance(): boolean {
return quakeEntityActivation.requestIntermissionAdvance();
}

function requestQuakeIntermissionAdvanceFromKey(event: KeyboardEvent): boolean {
if (event.code !== "Space" || quakeGameplayInput.isEditableTarget(event.target)) return false;
return requestQuakeIntermissionAdvance();
}

function isQuakeDeathUnlockControlsEndTraceSuppressed(now = performance.now()): boolean {
return quakePlayerLifecycle.isDeathUnlockControlsEndTraceSuppressed(now);
}
Expand Down Expand Up @@ -3903,6 +3939,64 @@ function completeQuakeLevel(entity: QuakeEntity): void {
quakeEntityActivation.completeLevel(entity);
}

function resetQuakeLevelStatsForCurrentScene(): void {
quakeLevelStats.reset(currentMapName, quakeLevelStatsTotalsForEntities(currentResult?.entities ?? []));
}

function syncQuakeIntermissionCamera(): void {
const point = quakeIntermissionPointForCurrentScene();
if (!point) return;
const origin = quakeCameraView.pointToPoly(point.origin);
const { rotX, rotY } = quakeIntermissionCameraRotation(point);
quakeCameraView.syncSceneCameraAt(origin, rotX, rotY);
shootables.syncVisibility(origin as [number, number, number], true);
world.syncVisibilityAt(origin as [number, number, number], true);
syncQuakeCrosshairTarget();
}

function quakeIntermissionPointForCurrentScene(): QuakeEntityManifestPoint | null {
const manifestPoint = currentResult?.entityManifest.intermissions?.[0];
if (manifestPoint) return manifestPoint;
const entity = currentResult?.entities
.filter((candidate) => candidate.classname.startsWith("info_intermission") && candidate.origin)
.sort((a, b) => a.index - b.index)[0];
if (!entity?.origin) return null;
return {
entityIndex: entity.index,
classname: entity.classname,
origin: entity.origin,
spawnflags: 0,
...(entity.angle !== undefined ? { angle: entity.angle } : {}),
...quakeEntityMangleProperties(entity),
...(entity.properties.targetname ? { targetname: entity.properties.targetname } : {}),
};
}

function quakeIntermissionCameraRotation(point: QuakeEntityManifestPoint): { rotX: number; rotY: number } {
if (point.mangle) {
return {
rotX: 90 - point.mangle.x,
rotY: (180 + point.mangle.y + 360) % 360,
};
}
return {
rotX: 90,
rotY: (180 + (point.angle ?? 0) + 360) % 360,
};
}

function quakeEntityMangleProperties(entity: QuakeEntity): { mangle?: QuakeVertex } {
const mangle = quakeParseEntityVector(entity.properties.mangle);
return mangle ? { mangle } : {};
}

function quakeParseEntityVector(value: string | undefined): QuakeVertex | null {
if (!value) return null;
const parts = value.trim().split(/\s+/).map((part) => Number.parseFloat(part));
if (parts.length < 3 || parts.some((part) => !Number.isFinite(part))) return null;
return { x: parts[0], y: parts[1], z: parts[2] };
}

function activateSolidTouch(touch: QuakeTouchedTrigger): void {
const entity = entityByIndex.get(touch.entityIndex);
if (entity?.classname === "func_button" && requestQuakeMultiplayerTouchIntent(entity.index, "touch")) return;
Expand Down Expand Up @@ -4288,6 +4382,7 @@ const quakeInput = createQuakeAppInputController({
menuIsMainOpen: () => menu.isMainMenuOpen(),
menuIsPanelOpen: () => menu.isMenuPanelOpen(),
parentKeyRelay: quakeGameplayInput.parentKeyRelay,
requestIntermissionAdvance: requestQuakeIntermissionAdvanceFromKey,
shouldOpenMainMenuOnEscape: shouldOpenQuakeMainMenuOnControlsEnd,
shouldPreventGameplayKeyDefault: quakeGameplayInput.shouldPreventGameplayKeyDefault,
showMainMenu: () => menu.showMainMenu(),
Expand Down
1 change: 1 addition & 0 deletions src/assets/id-software-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading