mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:00:44 +00:00
Create SoundController, also make SoundEffect more type-safe
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Config } from "src/core/configuration/Config";
|
||||
import { translateText } from "../client/Utils";
|
||||
import { EventBus } from "../core/EventBus";
|
||||
import { EventBus, GameEvent } from "../core/EventBus";
|
||||
import {
|
||||
ClientID,
|
||||
GameID,
|
||||
@@ -21,12 +21,10 @@ import {
|
||||
import { TileRef } from "../core/game/GameMap";
|
||||
import { GameMapLoader } from "../core/game/GameMapLoader";
|
||||
import {
|
||||
ConquestUpdate,
|
||||
ErrorUpdate,
|
||||
GameUpdateType,
|
||||
GameUpdateViewData,
|
||||
HashUpdate,
|
||||
UnitUpdate,
|
||||
WinUpdate,
|
||||
} from "../core/game/GameUpdates";
|
||||
import { GameView, PlayerView } from "../core/game/GameView";
|
||||
@@ -71,7 +69,6 @@ import { createRenderer, GameRenderer } from "./hud/GameRenderer";
|
||||
import { GameView as WebGLGameView } from "./render/gl";
|
||||
import { ALL_UNIT_TYPES, UnitState } from "./render/types";
|
||||
import { SoundManager } from "./sound/SoundManager";
|
||||
import { PlaySoundEffectEvent } from "./sound/Sounds";
|
||||
|
||||
export interface LobbyConfig {
|
||||
cosmetics: PlayerCosmeticRefs;
|
||||
@@ -522,6 +519,10 @@ async function createClientGame(
|
||||
}
|
||||
}
|
||||
|
||||
export class SoundUpdateEvent implements GameEvent {
|
||||
constructor(public gu: GameUpdateViewData) {}
|
||||
}
|
||||
|
||||
export class ClientGameRunner {
|
||||
private myPlayer: PlayerView | null = null;
|
||||
private isActive = false;
|
||||
@@ -658,87 +659,7 @@ export class ClientGameRunner {
|
||||
this.eventBus.emit(new SendHashEvent(hu.tick, hu.hash));
|
||||
});
|
||||
|
||||
// Sound FX
|
||||
if ((gu.pendingTurns ?? 0) <= 1 && this.gameView.ticks() > 0) {
|
||||
const myPlayer = this.gameView.myPlayer();
|
||||
if (myPlayer) {
|
||||
gu.updates[GameUpdateType.ConquestEvent]?.forEach(
|
||||
(cu: ConquestUpdate) => {
|
||||
if (cu.conquerorId === myPlayer.id()) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("ka-ching"));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
gu.updates[GameUpdateType.Unit]?.forEach((u: UnitUpdate) => {
|
||||
const existingUnit = this.gameView.unit(u.id);
|
||||
const isMine = u.ownerID === myPlayer.smallID();
|
||||
|
||||
if (!existingUnit) {
|
||||
switch (u.unitType) {
|
||||
case UnitType.AtomBomb:
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("atom-launch"));
|
||||
break;
|
||||
case UnitType.HydrogenBomb:
|
||||
this.eventBus.emit(
|
||||
new PlaySoundEffectEvent("hydrogen-launch"),
|
||||
);
|
||||
break;
|
||||
case UnitType.MIRV:
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("mirv-launch"));
|
||||
break;
|
||||
case UnitType.Warship:
|
||||
if (isMine) {
|
||||
this.eventBus.emit(
|
||||
new PlaySoundEffectEvent("build-warship"),
|
||||
);
|
||||
}
|
||||
break;
|
||||
case UnitType.City:
|
||||
if (isMine) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("build-city"));
|
||||
}
|
||||
break;
|
||||
case UnitType.Port:
|
||||
if (isMine) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("build-port"));
|
||||
}
|
||||
break;
|
||||
case UnitType.DefensePost:
|
||||
if (isMine) {
|
||||
this.eventBus.emit(
|
||||
new PlaySoundEffectEvent("build-defense-post"),
|
||||
);
|
||||
}
|
||||
break;
|
||||
case UnitType.SAMLauncher:
|
||||
if (isMine) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("sam-built"));
|
||||
}
|
||||
break;
|
||||
case UnitType.MissileSilo:
|
||||
if (isMine) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("silo-built"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (
|
||||
existingUnit.isActive() &&
|
||||
!u.isActive &&
|
||||
u.reachedTarget
|
||||
) {
|
||||
if (u.unitType === UnitType.HydrogenBomb) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("hydrogen-hit"));
|
||||
} else if (
|
||||
u.unitType === UnitType.AtomBomb ||
|
||||
u.unitType === UnitType.MIRV
|
||||
) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("atom-hit"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
this.eventBus.emit(new SoundUpdateEvent(gu));
|
||||
|
||||
this.gameView.update(gu);
|
||||
this.webglBuilder?.update(this.gameView);
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { EventBus } from "../../core/EventBus";
|
||||
import { UnitType } from "../../core/game/Game";
|
||||
import {
|
||||
ConquestUpdate,
|
||||
GameUpdateType,
|
||||
UnitUpdate,
|
||||
} from "../../core/game/GameUpdates";
|
||||
import { GameView } from "../../core/game/GameView";
|
||||
import { SoundUpdateEvent } from "../ClientGameRunner";
|
||||
import { Controller } from "../Controller";
|
||||
import { PlaySoundEffectEvent, SoundEffect } from "../sound/Sounds";
|
||||
|
||||
export class SoundController implements Controller {
|
||||
constructor(
|
||||
private eventBus: EventBus,
|
||||
private view: GameView,
|
||||
) {}
|
||||
|
||||
init() {
|
||||
this.eventBus.on(SoundUpdateEvent, (e) => this.handleGameUpdate(e));
|
||||
}
|
||||
|
||||
private handleGameUpdate(e: SoundUpdateEvent) {
|
||||
const gu = e.gu;
|
||||
if ((gu.pendingTurns ?? 0) > 1 || this.view.ticks() <= 0) return;
|
||||
|
||||
const myPlayer = this.view.myPlayer();
|
||||
if (!myPlayer) return;
|
||||
|
||||
// 1. Process Conquests
|
||||
gu.updates[GameUpdateType.ConquestEvent]?.forEach((cu: ConquestUpdate) => {
|
||||
if (cu.conquerorId === myPlayer.id()) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.KaChing));
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Process Units
|
||||
gu.updates[GameUpdateType.Unit]?.forEach((u: UnitUpdate) => {
|
||||
const existingUnit = this.view.unit(u.id);
|
||||
const isMine = u.ownerID === myPlayer.smallID();
|
||||
|
||||
if (!existingUnit) {
|
||||
this.handleNewUnitSounds(u.unitType, isMine);
|
||||
} else if (existingUnit.isActive() && !u.isActive && u.reachedTarget) {
|
||||
this.handleImpactSounds(u.unitType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleNewUnitSounds(unitType: UnitType, isMine: boolean) {
|
||||
switch (unitType) {
|
||||
case UnitType.AtomBomb:
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.AtomLaunch));
|
||||
break;
|
||||
case UnitType.HydrogenBomb:
|
||||
this.eventBus.emit(
|
||||
new PlaySoundEffectEvent(SoundEffect.HydrogenLaunch),
|
||||
);
|
||||
break;
|
||||
case UnitType.MIRV:
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.MirvLaunch));
|
||||
break;
|
||||
case UnitType.Warship:
|
||||
if (isMine)
|
||||
this.eventBus.emit(
|
||||
new PlaySoundEffectEvent(SoundEffect.BuildWarship),
|
||||
);
|
||||
break;
|
||||
case UnitType.City:
|
||||
if (isMine)
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.BuildCity));
|
||||
break;
|
||||
case UnitType.Port:
|
||||
if (isMine)
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.BuildPort));
|
||||
break;
|
||||
case UnitType.DefensePost:
|
||||
if (isMine)
|
||||
this.eventBus.emit(
|
||||
new PlaySoundEffectEvent(SoundEffect.BuildDefensePost),
|
||||
);
|
||||
break;
|
||||
case UnitType.SAMLauncher:
|
||||
if (isMine)
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.SamBuilt));
|
||||
break;
|
||||
case UnitType.MissileSilo:
|
||||
if (isMine)
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.SiloBuilt));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private handleImpactSounds(unitType: UnitType) {
|
||||
if (unitType === UnitType.HydrogenBomb) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.HydrogenHit));
|
||||
} else if (unitType === UnitType.AtomBomb || unitType === UnitType.MIRV) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.AtomHit));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { TransformHandler } from "../TransformHandler";
|
||||
import { UIState } from "../UIState";
|
||||
import { BuildPreviewController } from "../controllers/BuildPreviewController";
|
||||
import { HoverHighlightController } from "../controllers/HoverHighlightController";
|
||||
import { SoundController } from "../controllers/SoundController";
|
||||
import { StructureHighlightController } from "../controllers/StructureHighlightController";
|
||||
import { ViewModeController } from "../controllers/ViewModeController";
|
||||
import { WarshipSelectionController } from "../controllers/WarshipSelectionController";
|
||||
@@ -283,6 +284,7 @@ export function createRenderer(
|
||||
new HoverHighlightController(game, eventBus, transformHandler, view),
|
||||
new StructureHighlightController(eventBus, view),
|
||||
new ViewModeController(eventBus, view),
|
||||
new SoundController(eventBus, game),
|
||||
new AttackingTroopsOverlay(game, transformHandler, eventBus, userSettings),
|
||||
eventsDisplay,
|
||||
actionableEvents,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "../../../core/game/GameUpdates";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { Controller } from "../../Controller";
|
||||
import { PlaySoundEffectEvent } from "../../sound/Sounds";
|
||||
import { PlaySoundEffectEvent, SoundEffect } from "../../sound/Sounds";
|
||||
import { GoToPlayerEvent } from "../../TransformHandler";
|
||||
import {
|
||||
SendAllianceExtensionIntentEvent,
|
||||
@@ -203,7 +203,9 @@ export class ActionableEvents extends LitElement implements Controller {
|
||||
) as PlayerView;
|
||||
|
||||
if (!requestor.isAlliedWith(recipient)) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("alliance-suggested"));
|
||||
this.eventBus.emit(
|
||||
new PlaySoundEffectEvent(SoundEffect.AllianceSuggested),
|
||||
);
|
||||
}
|
||||
this.addEvent({
|
||||
description: translateText("events_display.request_alliance", {
|
||||
|
||||
@@ -23,7 +23,7 @@ import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
|
||||
import { onlyImages } from "../../../core/Util";
|
||||
import { GoToPlayerEvent, GoToUnitEvent } from "../../TransformHandler";
|
||||
|
||||
import { PlaySoundEffectEvent } from "../../sound/Sounds";
|
||||
import { PlaySoundEffectEvent, SoundEffect } from "../../sound/Sounds";
|
||||
import { UIState } from "../../UIState";
|
||||
import {
|
||||
getMessageTypeClasses,
|
||||
@@ -297,7 +297,7 @@ export class EventsDisplay extends LitElement implements Controller {
|
||||
type: MessageType.CHAT,
|
||||
unsafeDescription: false,
|
||||
});
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("message"));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Message));
|
||||
}
|
||||
|
||||
onAllianceRequestReplyEvent(update: AllianceRequestReplyUpdate) {
|
||||
@@ -335,7 +335,7 @@ export class EventsDisplay extends LitElement implements Controller {
|
||||
if (betrayed.isDisconnected()) return; // Do not send the message if betraying a disconnected player
|
||||
|
||||
if (!betrayed.isTraitor() && traitor === myPlayer) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("alliance-broken"));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.AllianceBroken));
|
||||
const malusPercent = Math.round(
|
||||
(1 - this.game.config().traitorDefenseDebuff()) * 100,
|
||||
);
|
||||
@@ -362,7 +362,7 @@ export class EventsDisplay extends LitElement implements Controller {
|
||||
focusID: update.betrayedID,
|
||||
});
|
||||
} else if (betrayed === myPlayer) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("alliance-broken"));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.AllianceBroken));
|
||||
this.addEvent({
|
||||
description: translateText("events_display.betrayed_you", {
|
||||
name: traitor.displayName(),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { assetUrl } from "../../../core/AssetUrls";
|
||||
import { EventBus, GameEvent } from "../../../core/EventBus";
|
||||
import { Controller } from "../../Controller";
|
||||
import { CloseViewEvent } from "../../InputHandler";
|
||||
import { PlaySoundEffectEvent } from "../../sound/Sounds";
|
||||
import { PlaySoundEffectEvent, SoundEffect } from "../../sound/Sounds";
|
||||
import { getSvgAspectRatio, translateText } from "../../Utils";
|
||||
import {
|
||||
CenterButtonElement,
|
||||
@@ -507,7 +507,7 @@ export class RadialMenu implements Controller {
|
||||
this.navigationInProgress
|
||||
)
|
||||
return;
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
|
||||
if (
|
||||
this.currentLevel > 0 &&
|
||||
|
||||
+40
-33
@@ -1,41 +1,48 @@
|
||||
import { assetUrl } from "../../core/AssetUrls";
|
||||
import { GameEvent } from "../../core/EventBus";
|
||||
|
||||
export type SoundEffect =
|
||||
| "ka-ching"
|
||||
| "atom-hit"
|
||||
| "atom-launch"
|
||||
| "hydrogen-hit"
|
||||
| "hydrogen-launch"
|
||||
| "mirv-launch"
|
||||
| "alliance-suggested"
|
||||
| "alliance-broken"
|
||||
| "build-port"
|
||||
| "build-city"
|
||||
| "build-defense-post"
|
||||
| "build-warship"
|
||||
| "sam-built"
|
||||
| "silo-built"
|
||||
| "message"
|
||||
| "click";
|
||||
export enum SoundEffect {
|
||||
KaChing = "ka-ching",
|
||||
AtomHit = "atom-hit",
|
||||
AtomLaunch = "atom-launch",
|
||||
HydrogenHit = "hydrogen-hit",
|
||||
HydrogenLaunch = "hydrogen-launch",
|
||||
MirvLaunch = "mirv-launch",
|
||||
AllianceSuggested = "alliance-suggested",
|
||||
AllianceBroken = "alliance-broken",
|
||||
BuildPort = "build-port",
|
||||
BuildCity = "build-city",
|
||||
BuildDefensePost = "build-defense-post",
|
||||
BuildWarship = "build-warship",
|
||||
SamBuilt = "sam-built",
|
||||
SiloBuilt = "silo-built",
|
||||
Message = "message",
|
||||
Click = "click",
|
||||
}
|
||||
|
||||
export const soundEffectUrls: ReadonlyMap<SoundEffect, string> = new Map([
|
||||
["ka-ching", assetUrl("sounds/effects/ka-ching.mp3")],
|
||||
["atom-hit", assetUrl("sounds/effects/atom-hit.mp3")],
|
||||
["atom-launch", assetUrl("sounds/effects/atom-launch.mp3")],
|
||||
["hydrogen-hit", assetUrl("sounds/effects/hydrogen-hit.mp3")],
|
||||
["hydrogen-launch", assetUrl("sounds/effects/hydrogen-launch.mp3")],
|
||||
["mirv-launch", assetUrl("sounds/effects/mirv-launch.mp3")],
|
||||
["alliance-suggested", assetUrl("sounds/effects/alliance-suggested.mp3")],
|
||||
["alliance-broken", assetUrl("sounds/effects/alliance-broken.mp3")],
|
||||
["build-port", assetUrl("sounds/effects/build-port.mp3")],
|
||||
["build-city", assetUrl("sounds/effects/build-city.mp3")],
|
||||
["build-defense-post", assetUrl("sounds/effects/build-defense-post.mp3")],
|
||||
["build-warship", assetUrl("sounds/effects/build-warship.mp3")],
|
||||
["sam-built", assetUrl("sounds/effects/sam-built.mp3")],
|
||||
["silo-built", assetUrl("sounds/effects/silo-built.mp3")],
|
||||
["message", assetUrl("sounds/effects/message.mp3")],
|
||||
["click", assetUrl("sounds/effects/click.mp3")],
|
||||
[SoundEffect.KaChing, assetUrl("sounds/effects/ka-ching.mp3")],
|
||||
[SoundEffect.AtomHit, assetUrl("sounds/effects/atom-hit.mp3")],
|
||||
[SoundEffect.AtomLaunch, assetUrl("sounds/effects/atom-launch.mp3")],
|
||||
[SoundEffect.HydrogenHit, assetUrl("sounds/effects/hydrogen-hit.mp3")],
|
||||
[SoundEffect.HydrogenLaunch, assetUrl("sounds/effects/hydrogen-launch.mp3")],
|
||||
[SoundEffect.MirvLaunch, assetUrl("sounds/effects/mirv-launch.mp3")],
|
||||
[
|
||||
SoundEffect.AllianceSuggested,
|
||||
assetUrl("sounds/effects/alliance-suggested.mp3"),
|
||||
],
|
||||
[SoundEffect.AllianceBroken, assetUrl("sounds/effects/alliance-broken.mp3")],
|
||||
[SoundEffect.BuildPort, assetUrl("sounds/effects/build-port.mp3")],
|
||||
[SoundEffect.BuildCity, assetUrl("sounds/effects/build-city.mp3")],
|
||||
[
|
||||
SoundEffect.BuildDefensePost,
|
||||
assetUrl("sounds/effects/build-defense-post.mp3"),
|
||||
],
|
||||
[SoundEffect.BuildWarship, assetUrl("sounds/effects/build-warship.mp3")],
|
||||
[SoundEffect.SamBuilt, assetUrl("sounds/effects/sam-built.mp3")],
|
||||
[SoundEffect.SiloBuilt, assetUrl("sounds/effects/silo-built.mp3")],
|
||||
[SoundEffect.Message, assetUrl("sounds/effects/message.mp3")],
|
||||
[SoundEffect.Click, assetUrl("sounds/effects/click.mp3")],
|
||||
]);
|
||||
|
||||
export class PlaySoundEffectEvent implements GameEvent {
|
||||
|
||||
@@ -67,6 +67,7 @@ import {
|
||||
PlaySoundEffectEvent,
|
||||
SetBackgroundMusicVolumeEvent,
|
||||
SetSoundEffectsVolumeEvent,
|
||||
SoundEffect,
|
||||
} from "../../../src/client/sound/Sounds";
|
||||
import { EventBus } from "../../../src/core/EventBus";
|
||||
import { UserSettings } from "../../../src/core/game/UserSettings";
|
||||
@@ -93,14 +94,14 @@ describe("SoundManager", () => {
|
||||
});
|
||||
|
||||
it("lazy-loads a sound effect once and reuses it", () => {
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
// 3 background music Howls + 1 Click Howl = 4
|
||||
expect(howlCtor).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it("plays a sound effect when PlaySoundEffectEvent is emitted", () => {
|
||||
eventBus.emit(new PlaySoundEffectEvent("atom-hit"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.AtomHit));
|
||||
const effectHowl = howlInstances[howlInstances.length - 1];
|
||||
expect(effectHowl.play).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -123,7 +124,7 @@ describe("SoundManager", () => {
|
||||
howlCtor.mockClear();
|
||||
howlInstances.length = 0;
|
||||
new SoundManager(bus, settings);
|
||||
bus.emit(new PlaySoundEffectEvent("click"));
|
||||
bus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
expect(howlCtor).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ volume: 0.3 }),
|
||||
);
|
||||
@@ -138,7 +139,7 @@ describe("SoundManager", () => {
|
||||
});
|
||||
|
||||
it("responds to SetSoundEffectsVolumeEvent", () => {
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
clickHowl.volume.mockClear();
|
||||
eventBus.emit(new SetSoundEffectsVolumeEvent(0.4));
|
||||
@@ -160,18 +161,18 @@ describe("SoundManager", () => {
|
||||
});
|
||||
|
||||
it("dispose() unsubscribes from EventBus so events no longer play sounds", () => {
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
expect(clickHowl.play).toHaveBeenCalledTimes(1);
|
||||
|
||||
soundManager.dispose();
|
||||
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
expect(clickHowl.play).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("dispose() stops and unloads all loaded sound effects", () => {
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
|
||||
soundManager.dispose();
|
||||
@@ -192,7 +193,7 @@ describe("SoundManager", () => {
|
||||
});
|
||||
|
||||
it("does not throw when playSoundEffect is called directly", () => {
|
||||
expect(() => soundManager.playSoundEffect("click")).not.toThrow();
|
||||
expect(() => soundManager.playSoundEffect(SoundEffect.Click)).not.toThrow();
|
||||
});
|
||||
|
||||
it("does not throw when playBackgroundMusic and stopBackgroundMusic are called", () => {
|
||||
@@ -212,7 +213,7 @@ describe("SoundManager", () => {
|
||||
throw new Error("audio backend failure");
|
||||
});
|
||||
});
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
clickHowl.play.mockImplementation(() => {
|
||||
throw new Error("audio backend failure");
|
||||
@@ -228,8 +229,8 @@ describe("SoundManager", () => {
|
||||
expect(() => soundManager.stopBackgroundMusic()).not.toThrow();
|
||||
expect(() => soundManager.setBackgroundMusicVolume(0.5)).not.toThrow();
|
||||
expect(() => soundManager.setSoundEffectsVolume(0.5)).not.toThrow();
|
||||
expect(() => soundManager.playSoundEffect("click")).not.toThrow();
|
||||
expect(() => soundManager.stopSoundEffect("click")).not.toThrow();
|
||||
expect(() => soundManager.playSoundEffect(SoundEffect.Click)).not.toThrow();
|
||||
expect(() => soundManager.stopSoundEffect(SoundEffect.Click)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -246,28 +247,28 @@ describe("Sound channel management", () => {
|
||||
|
||||
it("new sound always plays even when at channel cap", () => {
|
||||
for (let i = 0; i < MAX_CONCURRENT_SOUNDS; i++) {
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
}
|
||||
|
||||
eventBus.emit(new PlaySoundEffectEvent("atom-hit"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.AtomHit));
|
||||
const atomHowl = howlInstances[howlInstances.length - 1];
|
||||
expect(atomHowl.play).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("stops the oldest sound when at channel cap", () => {
|
||||
for (let i = 0; i < MAX_CONCURRENT_SOUNDS; i++) {
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
}
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
|
||||
// The first play had id=1. Playing one more should stop id=1.
|
||||
eventBus.emit(new PlaySoundEffectEvent("atom-hit"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.AtomHit));
|
||||
expect(clickHowl.stop).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("frees a channel when a sound ends naturally", () => {
|
||||
for (let i = 0; i < MAX_CONCURRENT_SOUNDS; i++) {
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
}
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
|
||||
@@ -276,13 +277,13 @@ describe("Sound channel management", () => {
|
||||
|
||||
// Next sound should play without stopping anything
|
||||
clickHowl.stop.mockClear();
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
expect(clickHowl.stop).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows up to MAX_CONCURRENT_SOUNDS without stopping any", () => {
|
||||
for (let i = 0; i < MAX_CONCURRENT_SOUNDS; i++) {
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
}
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
expect(clickHowl.play).toHaveBeenCalledTimes(8);
|
||||
|
||||
Reference in New Issue
Block a user