mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:30:45 +00:00
Back to typed union
This commit is contained in:
@@ -7,11 +7,7 @@ import {
|
||||
} from "../../core/game/GameUpdates";
|
||||
import { GameView } from "../../core/game/GameView";
|
||||
import { Controller } from "../Controller";
|
||||
import {
|
||||
PlaySoundEffectEvent,
|
||||
SoundEffect,
|
||||
SoundUpdateEvent,
|
||||
} from "../sound/Sounds";
|
||||
import { PlaySoundEffectEvent, SoundUpdateEvent } from "../sound/Sounds";
|
||||
|
||||
export class SoundController implements Controller {
|
||||
constructor(
|
||||
@@ -34,7 +30,7 @@ export class SoundController implements Controller {
|
||||
gu.updates[GameUpdateType.ConquestEvent]?.forEach(
|
||||
(cu: ConquestUpdate) => {
|
||||
if (cu.conquerorId === myPlayer.id()) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.KaChing));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("ka-ching"));
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -56,52 +52,42 @@ export class SoundController implements Controller {
|
||||
private handleNewUnitSounds(unitType: UnitType, isMine: boolean) {
|
||||
switch (unitType) {
|
||||
case UnitType.AtomBomb:
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.AtomLaunch));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("atom-launch"));
|
||||
break;
|
||||
case UnitType.HydrogenBomb:
|
||||
this.eventBus.emit(
|
||||
new PlaySoundEffectEvent(SoundEffect.HydrogenLaunch),
|
||||
);
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("hydrogen-launch"));
|
||||
break;
|
||||
case UnitType.MIRV:
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.MirvLaunch));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("mirv-launch"));
|
||||
break;
|
||||
case UnitType.Warship:
|
||||
if (isMine)
|
||||
this.eventBus.emit(
|
||||
new PlaySoundEffectEvent(SoundEffect.BuildWarship),
|
||||
);
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("build-warship"));
|
||||
break;
|
||||
case UnitType.City:
|
||||
if (isMine)
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.BuildCity));
|
||||
if (isMine) this.eventBus.emit(new PlaySoundEffectEvent("build-city"));
|
||||
break;
|
||||
case UnitType.Port:
|
||||
if (isMine)
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.BuildPort));
|
||||
if (isMine) this.eventBus.emit(new PlaySoundEffectEvent("build-port"));
|
||||
break;
|
||||
case UnitType.DefensePost:
|
||||
if (isMine)
|
||||
this.eventBus.emit(
|
||||
new PlaySoundEffectEvent(SoundEffect.BuildDefensePost),
|
||||
);
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("build-defense-post"));
|
||||
break;
|
||||
case UnitType.SAMLauncher:
|
||||
if (isMine)
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.SamBuilt));
|
||||
if (isMine) this.eventBus.emit(new PlaySoundEffectEvent("sam-built"));
|
||||
break;
|
||||
case UnitType.MissileSilo:
|
||||
if (isMine)
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.SiloBuilt));
|
||||
if (isMine) this.eventBus.emit(new PlaySoundEffectEvent("silo-built"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private handleImpactSounds(unitType: UnitType) {
|
||||
if (unitType === UnitType.HydrogenBomb) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.HydrogenHit));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("hydrogen-hit"));
|
||||
} else if (unitType === UnitType.AtomBomb || unitType === UnitType.MIRV) {
|
||||
this.eventBus.emit(new PlaySoundEffectEvent(SoundEffect.AtomHit));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("atom-hit"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "../../../core/game/GameUpdates";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { Controller } from "../../Controller";
|
||||
import { PlaySoundEffectEvent, SoundEffect } from "../../sound/Sounds";
|
||||
import { PlaySoundEffectEvent } from "../../sound/Sounds";
|
||||
import { GoToPlayerEvent } from "../../TransformHandler";
|
||||
import {
|
||||
SendAllianceExtensionIntentEvent,
|
||||
@@ -203,9 +203,7 @@ export class ActionableEvents extends LitElement implements Controller {
|
||||
) as PlayerView;
|
||||
|
||||
if (!requestor.isAlliedWith(recipient)) {
|
||||
this.eventBus.emit(
|
||||
new PlaySoundEffectEvent(SoundEffect.AllianceSuggested),
|
||||
);
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("alliance-suggested"));
|
||||
}
|
||||
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, SoundEffect } from "../../sound/Sounds";
|
||||
import { PlaySoundEffectEvent } 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(SoundEffect.Message));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("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(SoundEffect.AllianceBroken));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("alliance-broken"));
|
||||
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(SoundEffect.AllianceBroken));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("alliance-broken"));
|
||||
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, SoundEffect } from "../../sound/Sounds";
|
||||
import { PlaySoundEffectEvent } 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(SoundEffect.Click));
|
||||
this.eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
|
||||
if (
|
||||
this.currentLevel > 0 &&
|
||||
|
||||
+33
-40
@@ -2,48 +2,41 @@ import { assetUrl } from "../../core/AssetUrls";
|
||||
import { GameEvent } from "../../core/EventBus";
|
||||
import { GameUpdateViewData } from "../../core/game/GameUpdates";
|
||||
|
||||
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 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 const soundEffectUrls: ReadonlyMap<SoundEffect, string> = new Map([
|
||||
[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")],
|
||||
["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")],
|
||||
]);
|
||||
|
||||
export class PlaySoundEffectEvent implements GameEvent {
|
||||
|
||||
@@ -67,7 +67,6 @@ import {
|
||||
PlaySoundEffectEvent,
|
||||
SetBackgroundMusicVolumeEvent,
|
||||
SetSoundEffectsVolumeEvent,
|
||||
SoundEffect,
|
||||
} from "../../../src/client/sound/Sounds";
|
||||
import { EventBus } from "../../../src/core/EventBus";
|
||||
import { UserSettings } from "../../../src/core/game/UserSettings";
|
||||
@@ -94,14 +93,14 @@ describe("SoundManager", () => {
|
||||
});
|
||||
|
||||
it("lazy-loads a sound effect once and reuses it", () => {
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
eventBus.emit(new PlaySoundEffectEvent("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(SoundEffect.AtomHit));
|
||||
eventBus.emit(new PlaySoundEffectEvent("atom-hit"));
|
||||
const effectHowl = howlInstances[howlInstances.length - 1];
|
||||
expect(effectHowl.play).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -124,7 +123,7 @@ describe("SoundManager", () => {
|
||||
howlCtor.mockClear();
|
||||
howlInstances.length = 0;
|
||||
new SoundManager(bus, settings);
|
||||
bus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
bus.emit(new PlaySoundEffectEvent("click"));
|
||||
expect(howlCtor).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ volume: 0.3 }),
|
||||
);
|
||||
@@ -139,7 +138,7 @@ describe("SoundManager", () => {
|
||||
});
|
||||
|
||||
it("responds to SetSoundEffectsVolumeEvent", () => {
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
clickHowl.volume.mockClear();
|
||||
eventBus.emit(new SetSoundEffectsVolumeEvent(0.4));
|
||||
@@ -161,18 +160,18 @@ describe("SoundManager", () => {
|
||||
});
|
||||
|
||||
it("dispose() unsubscribes from EventBus so events no longer play sounds", () => {
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
expect(clickHowl.play).toHaveBeenCalledTimes(1);
|
||||
|
||||
soundManager.dispose();
|
||||
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
expect(clickHowl.play).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("dispose() stops and unloads all loaded sound effects", () => {
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
|
||||
soundManager.dispose();
|
||||
@@ -193,7 +192,7 @@ describe("SoundManager", () => {
|
||||
});
|
||||
|
||||
it("does not throw when playSoundEffect is called directly", () => {
|
||||
expect(() => soundManager.playSoundEffect(SoundEffect.Click)).not.toThrow();
|
||||
expect(() => soundManager.playSoundEffect("click")).not.toThrow();
|
||||
});
|
||||
|
||||
it("does not throw when playBackgroundMusic and stopBackgroundMusic are called", () => {
|
||||
@@ -213,7 +212,7 @@ describe("SoundManager", () => {
|
||||
throw new Error("audio backend failure");
|
||||
});
|
||||
});
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
clickHowl.play.mockImplementation(() => {
|
||||
throw new Error("audio backend failure");
|
||||
@@ -229,8 +228,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(SoundEffect.Click)).not.toThrow();
|
||||
expect(() => soundManager.stopSoundEffect(SoundEffect.Click)).not.toThrow();
|
||||
expect(() => soundManager.playSoundEffect("click")).not.toThrow();
|
||||
expect(() => soundManager.stopSoundEffect("click")).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -247,28 +246,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(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
}
|
||||
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.AtomHit));
|
||||
eventBus.emit(new PlaySoundEffectEvent("atom-hit"));
|
||||
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(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
}
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
|
||||
// The first play had id=1. Playing one more should stop id=1.
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.AtomHit));
|
||||
eventBus.emit(new PlaySoundEffectEvent("atom-hit"));
|
||||
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(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
}
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
|
||||
@@ -277,13 +276,13 @@ describe("Sound channel management", () => {
|
||||
|
||||
// Next sound should play without stopping anything
|
||||
clickHowl.stop.mockClear();
|
||||
eventBus.emit(new PlaySoundEffectEvent(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent("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(SoundEffect.Click));
|
||||
eventBus.emit(new PlaySoundEffectEvent("click"));
|
||||
}
|
||||
const clickHowl = howlInstances[howlInstances.length - 1];
|
||||
expect(clickHowl.play).toHaveBeenCalledTimes(8);
|
||||
|
||||
Reference in New Issue
Block a user