feat: added coalition selection for audio manager

This commit is contained in:
Davide Passoni 2025-01-22 12:53:48 +01:00
parent cde33fdd76
commit d31aa30da8
6 changed files with 104 additions and 31 deletions

View File

@ -1,4 +1,4 @@
import { AudioMessageType, OlympusState } from "../constants/constants";
import { AudioMessageType, BLUE_COMMANDER, GAME_MASTER, OlympusState, RED_COMMANDER } from "../constants/constants";
import { MicrophoneSource } from "./microphonesource";
import { RadioSink } from "./radiosink";
import { getApp } from "../olympusapp";
@ -11,17 +11,20 @@ import { Unit } from "../unit/unit";
import { UnitSink } from "./unitsink";
import { AudioPacket, MessageType } from "./audiopacket";
import {
AudioManagerCoalitionChangedEvent,
AudioManagerDevicesChangedEvent,
AudioManagerInputChangedEvent,
AudioManagerOutputChangedEvent,
AudioManagerStateChangedEvent,
AudioSinksChangedEvent,
AudioSourcesChangedEvent,
CommandModeOptionsChangedEvent,
ConfigLoadedEvent,
SRSClientsChangedEvent,
} from "../events";
import { OlympusConfig } from "../interfaces";
import { CommandModeOptions, OlympusConfig } from "../interfaces";
import { TextToSpeechSource } from "./texttospeechsource";
import { Coalition } from "../types/types";
export class AudioManager {
#audioContext: AudioContext;
@ -46,12 +49,23 @@ export class AudioManager {
#syncInterval: number;
#speechRecognition: boolean = true;
#internalTextToSpeechSource: TextToSpeechSource;
#coalition: Coalition = "blue";
#commandMode: string = BLUE_COMMANDER;
constructor() {
ConfigLoadedEvent.on((config: OlympusConfig) => {
config.audio.WSPort ? this.setPort(config.audio.WSPort) : this.setEndpoint(config.audio.WSEndpoint);
});
CommandModeOptionsChangedEvent.on((options: CommandModeOptions) => {
if (options.commandMode === BLUE_COMMANDER) {
this.setCoalition("blue");
} else if (options.commandMode === RED_COMMANDER) {
this.setCoalition("red");
}
this.#commandMode = options.commandMode;
});
let PTTKeys = ["KeyZ", "KeyX", "KeyC", "KeyV", "KeyB", "KeyN", "KeyM", "KeyComma", "KeyDot"];
PTTKeys.forEach((key, idx) => {
getApp()
@ -117,7 +131,7 @@ export class AudioManager {
if (sink.getFrequency() === frequencyInfo.frequency && sink.getModulation() === frequencyInfo.modulation && sink.getTuned()) {
sink.setReceiving(true);
sink.setTransmittingUnit(getApp().getUnitsManager().getUnitByID(audioPacket.getUnitID()) ?? undefined)
sink.setTransmittingUnit(getApp().getUnitsManager().getUnitByID(audioPacket.getUnitID()) ?? undefined);
/* Make a copy of the array buffer for the playback pipeline to use */
var dst = new ArrayBuffer(audioPacket.getAudioData().buffer.byteLength);
@ -150,7 +164,7 @@ export class AudioManager {
let newRadio = this.addRadio();
newRadio?.setFrequency(options.frequency);
newRadio?.setModulation(options.modulation);
newRadio?.setPan(options.pan)
newRadio?.setPan(options.pan);
});
} else {
/* Add two default radios and connect to the microphone*/
@ -346,12 +360,23 @@ export class AudioManager {
return this.#internalTextToSpeechSource;
}
setCoalition(coalition: Coalition) {
if (this.#commandMode === GAME_MASTER) {
this.#coalition = coalition;
AudioManagerCoalitionChangedEvent.dispatch(coalition);
}
}
getCoalition() {
return this.#coalition;
}
#syncRadioSettings() {
/* Send the radio settings of each radio to the SRS backend */
let message = {
type: "Settings update",
guid: this.#guid,
coalition: 2, // TODO
coalition: this.#coalition,
settings: this.#sinks
.filter((sink) => sink instanceof RadioSink)
.map((radio) => {

View File

@ -7,7 +7,7 @@ import { CoalitionPolygon } from "./map/coalitionarea/coalitionpolygon";
import { Airbase } from "./mission/airbase";
import { Bullseye } from "./mission/bullseye";
import { Shortcut } from "./shortcut/shortcut";
import { MapHiddenTypes, MapOptions } from "./types/types";
import { Coalition, MapHiddenTypes, MapOptions } from "./types/types";
import { ContextAction } from "./unit/contextaction";
import { ContextActionSet } from "./unit/contextactionset";
import { Unit } from "./unit/unit";
@ -749,6 +749,23 @@ export class AudioManagerOutputChangedEvent {
}
}
export class AudioManagerCoalitionChangedEvent {
static on(callback: (coalition: Coalition) => void, singleShot = false) {
document.addEventListener(
this.name,
(ev: CustomEventInit) => {
callback(ev.detail.coalition);
},
{ once: singleShot }
);
}
static dispatch(coalition: Coalition) {
document.dispatchEvent(new CustomEvent(this.name, { detail: { coalition } }));
console.log(`Event ${this.name} dispatched`);
}
}
/************** Mission data events ***************/
export class BullseyesDataChangedEvent {
static on(callback: (bullseyes: { [name: string]: Bullseye }) => void, singleShot = false) {

View File

@ -6,7 +6,7 @@ import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/c
import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionData } from "../interfaces";
import { Coalition } from "../types/types";
import { Carrier } from "./carrier";
import { AirbaseSelectedEvent, AppStateChangedEvent, BullseyesDataChangedEvent, CommandModeOptionsChangedEvent, InfoPopupEvent, MissionDataChangedEvent } from "../events";
import { AirbaseSelectedEvent, AppStateChangedEvent, BullseyesDataChangedEvent, CommandModeOptionsChangedEvent, MissionDataChangedEvent } from "../events";
/** The MissionManager */
export class MissionManager {

View File

@ -28,7 +28,6 @@ import { OlDropdownItem } from "../components/oldropdown";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
import { Coalition } from "../../types/types";
import { CompactEffectSpawnMenu } from "../panels/compacteffectspawnmenu";
enum CategoryGroup {
NONE,

View File

@ -12,15 +12,20 @@ import { UnitSink } from "../../audio/unitsink";
import { FaMinus, FaVolumeHigh } from "react-icons/fa6";
import { getRandomColor } from "../../other/utils";
import {
AudioManagerCoalitionChangedEvent,
AudioManagerDevicesChangedEvent,
AudioManagerInputChangedEvent,
AudioManagerOutputChangedEvent,
AudioManagerStateChangedEvent,
AudioSinksChangedEvent,
AudioSourcesChangedEvent,
CommandModeOptionsChangedEvent,
ShortcutsChangedEvent,
} from "../../events";
import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
import { Coalition } from "../../types/types";
import { GAME_MASTER, NONE } from "../../constants/constants";
export function AudioMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
const [devices, setDevices] = useState([] as MediaDeviceInfo[]);
@ -32,6 +37,8 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
const [shortcuts, setShortcuts] = useState({});
const [input, setInput] = useState(undefined as undefined | MediaDeviceInfo);
const [output, setOutput] = useState(undefined as undefined | MediaDeviceInfo);
const [coalition, setCoalition] = useState("blue" as Coalition);
const [commandMode, setCommandMode] = useState(NONE as string);
/* Preallocate 128 references for the source and sink panels. If the number of references changes, React will give an error */
const sourceRefs = Array(128)
@ -77,6 +84,8 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
AudioManagerDevicesChangedEvent.on((devices) => setDevices([...devices]));
AudioManagerInputChangedEvent.on((input) => setInput(input));
AudioManagerOutputChangedEvent.on((output) => setOutput(output));
AudioManagerCoalitionChangedEvent.on((coalition) => setCoalition(coalition));
CommandModeOptionsChangedEvent.on((options) => setCommandMode(options.commandMode));
}, []);
/* When the sinks or sources change, use the count state to force a rerender to update the connection lines */
@ -135,6 +144,7 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
</div>
)}
</>
<div className="flex flex-col gap-3">
<div
className={`
@ -145,6 +155,24 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
>
{audioManagerEnabled && (
<>
{commandMode === GAME_MASTER && (
<div className="flex justify-between">
<div>Radio coalition </div>
<OlCoalitionToggle
coalition={coalition}
onClick={() => {
let newCoalition = "";
if (coalition === "blue") newCoalition = "neutral";
else if (coalition === "neutral") newCoalition = "red";
else if (coalition === "red") newCoalition = "blue";
getApp()
.getAudioManager()
.setCoalition(newCoalition as Coalition);
}}
/>
</div>
)}
<span>Input</span>
<OlDropdown label={input ? input.label : "Default"}>

View File

@ -21,7 +21,7 @@ export function CoordinatesPanel(props: {}) {
BullseyesDataChangedEvent.on((bullseyes) => setBullseyes(bullseyes));
SelectedUnitsChangedEvent.on((selectedUnits) => setSelectedUnits(selectedUnits));
SelectionClearedEvent.on(() => setSelectedUnits([]))
SelectionClearedEvent.on(() => setSelectedUnits([]));
}, []);
return (
@ -41,28 +41,32 @@ export function CoordinatesPanel(props: {}) {
gap-2
`}
>
<div className="flex justify-start gap-2">
<span
className={`
rounded-sm bg-blue-500 px-1 py-1 text-center font-bold
text-olympus-700
`}
>
<FaBullseye />
</span>{" "}
{computeBearingRangeString(bullseyes[2].getLatLng(), latlng)}
</div>
<div className="flex w-[50%] justify-start gap-2">
<span
className={`
rounded-sm bg-red-500 px-1 py-1 text-center font-bold
text-olympus-700
`}
>
<FaBullseye />
</span>
{computeBearingRangeString(bullseyes[1].getLatLng(), latlng)}
</div>
{bullseyes[2] && (
<div className="flex justify-start gap-2">
<span
className={`
rounded-sm bg-blue-500 px-1 py-1 text-center font-bold
text-olympus-700
`}
>
<FaBullseye />
</span>{" "}
{computeBearingRangeString(bullseyes[2].getLatLng(), latlng)}
</div>
)}
{bullseyes[1] && (
<div className="flex w-[50%] justify-start gap-2">
<span
className={`
rounded-sm bg-red-500 px-1 py-1 text-center font-bold
text-olympus-700
`}
>
<FaBullseye />
</span>
{computeBearingRangeString(bullseyes[1].getLatLng(), latlng)}
</div>
)}
</div>
{selectedUnits.length == 1 && (
<div className="flex justify-start gap-2">