Add moving teleport start audio loop and end cut-off

This commit is contained in:
Jage9
2026-02-22 20:23:38 -05:00
parent ed0fc07728
commit 54cfffc5cf
5 changed files with 55 additions and 2 deletions

Binary file not shown.

View File

@@ -1,5 +1,5 @@
// Maintainer-controlled web client version.
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
window.CHGRID_WEB_VERSION = "2026.02.22 R182";
window.CHGRID_WEB_VERSION = "2026.02.22 R183";
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
window.CHGRID_TIME_ZONE = "America/Detroit";

View File

@@ -394,6 +394,34 @@ export class AudioEngine {
}
}
/** Starts a looping sample and returns a stop callback for explicit teardown. */
async startLoopingSample(url: string, gain = 1): Promise<(() => void) | null> {
await this.ensureContext();
const { audioCtx, sfxGainNode } = this;
if (!audioCtx || !sfxGainNode || gain <= 0) return null;
try {
const buffer = await this.getSampleBuffer(url);
const source = audioCtx.createBufferSource();
source.buffer = buffer;
source.loop = true;
const gainNode = audioCtx.createGain();
gainNode.gain.value = gain;
source.connect(gainNode).connect(sfxGainNode);
source.start();
return () => {
try {
source.stop();
} catch {
// Ignore already-stopped source.
}
source.disconnect();
gainNode.disconnect();
};
} catch {
return null;
}
}
cleanupPeerAudio(peer: SpatialPeerRuntime): void {
if (peer.audioElement) {
peer.audioElement.pause();

View File

@@ -177,6 +177,7 @@ const SYSTEM_SOUND_URLS = {
} as const;
const FOOTSTEP_SOUND_URLS = Array.from({ length: 11 }, (_, index) => withBase(`sounds/step-${index + 1}.ogg`));
const FOOTSTEP_GAIN = 0.7;
const TELEPORT_START_SOUND_URL = withBase('sounds/teleport_start.ogg');
const TELEPORT_SOUND_URL = withBase('sounds/teleport.ogg');
const WALL_SOUND_URL = withBase('sounds/wall.ogg');
@@ -219,6 +220,8 @@ let lastSubscriptionRefreshTileX = Math.round(state.player.x);
let lastSubscriptionRefreshTileY = Math.round(state.player.y);
let subscriptionRefreshInFlight = false;
let subscriptionRefreshPending = false;
let activeTeleportLoopStop: (() => void) | null = null;
let activeTeleportLoopToken = 0;
let activeTeleport:
| {
startX: number;
@@ -1096,6 +1099,13 @@ function randomFootstepUrl(): string {
return FOOTSTEP_SOUND_URLS[Math.floor(Math.random() * FOOTSTEP_SOUND_URLS.length)];
}
/** Stops active teleport loop audio, if one is running. */
function stopTeleportLoopAudio(): void {
if (!activeTeleportLoopStop) return;
activeTeleportLoopStop();
activeTeleportLoopStop = null;
}
/** Starts animated teleport movement toward a target tile at fixed squares-per-second pace. */
function startTeleportTo(targetX: number, targetY: number, completionStatus: string): void {
const startX = state.player.x;
@@ -1115,6 +1125,17 @@ function startTeleportTo(targetX: number, targetY: number, completionStatus: str
lastSentY: Math.round(startY),
completionStatus,
};
stopTeleportLoopAudio();
activeTeleportLoopToken += 1;
const loopToken = activeTeleportLoopToken;
void audio.startLoopingSample(TELEPORT_START_SOUND_URL, FOOTSTEP_GAIN).then((stopLoop) => {
if (!stopLoop) return;
if (activeTeleport && loopToken === activeTeleportLoopToken) {
activeTeleportLoopStop = stopLoop;
return;
}
stopLoop();
});
void refreshAudioSubscriptionsAt({ x: targetX, y: targetY }, true);
state.keysPressed.ArrowUp = false;
state.keysPressed.ArrowDown = false;
@@ -1151,6 +1172,7 @@ function updateTeleport(): void {
state.player.y = activeTeleport.targetY;
signaling.send({ type: 'update_position', x: activeTeleport.targetX, y: activeTeleport.targetY });
activeTeleport = null;
stopTeleportLoopAudio();
persistPlayerPosition();
void refreshAudioSubscriptions(true);
void audio.playSample(TELEPORT_SOUND_URL, FOOTSTEP_GAIN);
@@ -1392,6 +1414,7 @@ function disconnect(): void {
lastSubscriptionRefreshAt = 0;
lastSubscriptionRefreshTileX = Math.round(state.player.x);
lastSubscriptionRefreshTileY = Math.round(state.player.y);
stopTeleportLoopAudio();
activeTeleport = null;
}
@@ -1427,6 +1450,7 @@ const onAppMessage = createOnMessageHandler({
);
},
TELEPORT_SOUND_URL,
TELEPORT_START_SOUND_URL,
audioLayers,
pushChatMessage,
classifySystemMessageSound,

View File

@@ -47,6 +47,7 @@ type MessageHandlerDeps = {
randomFootstepUrl: () => string;
playRemoteSpatialStepOrTeleport: (url: string, peerX: number, peerY: number) => void;
TELEPORT_SOUND_URL: string;
TELEPORT_START_SOUND_URL: string;
audioLayers: { world: boolean };
pushChatMessage: (message: string) => void;
classifySystemMessageSound: (message: string) => 'logon' | 'logout' | 'notify' | null;
@@ -135,7 +136,7 @@ export function createOnMessageHandler(deps: MessageHandlerDeps): (message: Inco
deps.peerManager.setPeerPosition(message.id, message.x, message.y);
if (peer) {
const movementDelta = Math.hypot(message.x - prevX, message.y - prevY);
const soundUrl = movementDelta > 1.5 ? deps.TELEPORT_SOUND_URL : deps.randomFootstepUrl();
const soundUrl = movementDelta > 1.5 ? deps.TELEPORT_START_SOUND_URL : deps.randomFootstepUrl();
if (deps.audioLayers.world) {
deps.playRemoteSpatialStepOrTeleport(soundUrl, peer.x, peer.y);
}