diff --git a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx index 038e655ff3b..d706b48f2e4 100644 --- a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx +++ b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx @@ -27,6 +27,7 @@ const EWDMessages = { '000002010': ' \x1b<3mFLAPS FULL', '000002011': ' \x1b<3mFLAPS\x1b<5m.....CONF 3', '000002012': ' \x1b<3mFLAPS CONF 3', + '315100001': '\x1b<7mEMERGENCY CANCEL ON', '320000001': '\x1b<4mAUTO BRK OFF', '000002201': '\x1b<3mAUTO BRK LO', '000002202': '\x1b<3mAUTO BRK MED', diff --git a/fbw-a32nx/src/systems/systems-host/systems/FWC/FwsSoundManager.ts b/fbw-a32nx/src/systems/systems-host/systems/FWC/FwsSoundManager.ts index 180b42a7b4b..72b87ca099a 100644 --- a/fbw-a32nx/src/systems/systems-host/systems/FWC/FwsSoundManager.ts +++ b/fbw-a32nx/src/systems/systems-host/systems/FWC/FwsSoundManager.ts @@ -239,6 +239,8 @@ export const FwsAuralsList: Record = { }, }; +const MutableSounds = [FwsAuralsList.cavalryChargeOnce, FwsAuralsList.cavalryChargeCont]; + // FIXME Not all sounds are added to this yet (e.g. CIDS chimes), consider adding them in the future // Also, single chimes are not filtered (in RL only once every two seconds) export class FwsSoundManager { @@ -251,6 +253,12 @@ export class FwsSoundManager { /** in seconds */ private currentSoundPlayTimeRemaining = 0; + private manualAudioInhibition = false; + + private requestedVolume = FwsAuralVolume.Full; + + private appliedVolume: FwsAuralVolume | null = null; + constructor( private bus: EventBus, private startupCompleted: Subscribable, @@ -272,6 +280,15 @@ export class FwsSoundManager { return this.currentSoundPlaying; } + /** Inhibit starting any new broadcasts (MAI). */ + setManualAudioInhibition(inhibit: boolean) { + if (this.manualAudioInhibition === inhibit) { + return; + } + this.manualAudioInhibition = inhibit; + this.applyVolume(); + } + /** Add sound to queue. Don't add if already playing */ enqueueSound(soundKey: keyof typeof FwsAuralsList) { const sound = FwsAuralsList[soundKey]; @@ -321,7 +338,17 @@ export class FwsSoundManager { /** This only has an effect on sounds defining WwiseRTPC behavior/var for volume */ setVolume(volume: FwsAuralVolume) { - SimVar.SetSimVarValue('L:A32NX_FWS_AUDIO_VOLUME', SimVarValueType.Enum, volume); + this.requestedVolume = volume; + this.applyVolume(); + } + + private applyVolume() { + const effectiveVolume = this.manualAudioInhibition ? FwsAuralVolume.Silent : this.requestedVolume; + if (this.appliedVolume !== null && effectiveVolume === this.appliedVolume) { + return; + } + SimVar.SetSimVarValue('L:A32NX_FWS_AUDIO_VOLUME', SimVarValueType.Enum, effectiveVolume); + this.appliedVolume = effectiveVolume; } /** Play now, not to be called from the outside */ @@ -360,6 +387,11 @@ export class FwsSoundManager { } }); + // TODO: Once all sounds are mutable, this.manualAudioInhibition shouldn't be needed anymore + if (this.manualAudioInhibition && !MutableSounds.includes(FwsAuralsList[selectedSoundKey])) { + return; + } + if (selectedSoundKey) { this.playSound(selectedSoundKey); return selectedSoundKey; @@ -367,6 +399,9 @@ export class FwsSoundManager { // See if single chimes are left if (this.singleChimesPending) { + if (this.manualAudioInhibition) { + return null; + } this.playSound('singleChime'); this.singleChimesPending--; return 'singleChime'; @@ -398,17 +433,18 @@ export class FwsSoundManager { // Interrupt if sound with higher category is present in queue and current sound is continuous let shouldInterrupt = false; let rescheduleSound: keyof typeof FwsAuralsList | null = null; - this.soundQueue.forEach((sk) => { - const s = FwsAuralsList[sk]; - if ( - s && - this.currentSoundPlaying && - FwsAuralsList[this.currentSoundPlaying]?.continuous && - s.type > FwsAuralsList[this.currentSoundPlaying].type - ) { - shouldInterrupt = true; - } - }); + const currentSound = this.currentSoundPlaying ? FwsAuralsList[this.currentSoundPlaying] : undefined; + if (currentSound?.continuous) { + this.soundQueue.forEach((sk) => { + const s = FwsAuralsList[sk]; + if ( + s && + (s.type > currentSound.type || (s.type === currentSound.type && s.priority > currentSound.priority)) + ) { + shouldInterrupt = true; + } + }); + } if (shouldInterrupt) { if (this.currentSoundPlaying && FwsAuralsList[this.currentSoundPlaying]?.continuous) { diff --git a/fbw-a32nx/src/systems/systems-host/systems/FWC/PseudoFWC.ts b/fbw-a32nx/src/systems/systems-host/systems/FWC/PseudoFWC.ts index 87d5196982d..9aaecf9b2c9 100644 --- a/fbw-a32nx/src/systems/systems-host/systems/FWC/PseudoFWC.ts +++ b/fbw-a32nx/src/systems/systems-host/systems/FWC/PseudoFWC.ts @@ -165,6 +165,16 @@ export class PseudoFWC { private auralScKeys: string[] = []; + private auralCavchargeKeys: string[] = []; + + private auralCChordKeys: string[] = []; + + // Catch-all to handle extinguishing the master warning light for level 3 warnings we don't yet handle the sound here for, e.g the STALL warning + // This does mean that that sound is not currently cancelled by the EMER CANC button + private activeWarningKeys: string[] = []; + + private readonly emergencyCancelledWarnings = new Set(); + private readonly auralCrcActive = Subject.create(false); private auralSingleChimePending = false; @@ -1528,7 +1538,12 @@ export class PseudoFWC { private readonly ecpEmergencyCancelPulseDownTrigger = new NXLogicTriggeredMonostableNode(0.5, true); private ecpEmergencyCancelPulseUp = false; private ecpEmergencyCancelPulseDown = false; - private ecpEmergencyCancelLevel = false; + private ecpEmergencyCancelLevel = Subject.create(false); + private wasEcpEmergencyCancelLevel = false; + private emergencyCancelReady = true; + + // TODO: Change this to disable the caution for the entire flight once status page is implemented + private emergencyCancelClearCaution = false; private processEcpButtons(deltaTime: number): void { const warningButtons = this.ecpWarningButtonStatus.get(); @@ -1577,7 +1592,7 @@ export class PseudoFWC { this.ecpEmergencyCancelWirePulseDown.write(this.ecpEmergencyCancelButtonHardwired.get(), deltaTime), deltaTime, ) && !warningButtons.isFailureWarning(); - this.ecpEmergencyCancelLevel = warningButtons.bitValue(17) || this.ecpEmergencyCancelButtonHardwired.get(); + this.ecpEmergencyCancelLevel.set(warningButtons.bitValue(17) || this.ecpEmergencyCancelButtonHardwired.get()); } private readonly ir1AlignDiscreteVar = RegisteredSimVar.createBoolean('L:A32NX_ADIRS_IR_1_ALIGN_DISCRETE'); @@ -1613,12 +1628,18 @@ export class PseudoFWC { return; } - // Play sounds - this.soundManager.onUpdate(deltaTime); - // Inputs update this.processEcpButtons(deltaTime); + // Manual audio inhibition (MAI) + this.soundManager.setManualAudioInhibition(this.ecpEmergencyCancelLevel.get()); + if (this.ecpEmergencyCancelLevel.get() !== this.wasEcpEmergencyCancelLevel) { + this.wasEcpEmergencyCancelLevel = this.ecpEmergencyCancelLevel.get(); + } + + // Play sounds + this.soundManager.onUpdate(deltaTime); + this.flightPhaseEndedPulseNode.write(false, deltaTime); this.fwcFlightPhase.set(SimVar.GetSimVarValue('L:A32NX_FWC_FLIGHT_PHASE', 'Enum')); @@ -3245,6 +3266,33 @@ export class PseudoFWC { this.auralCrcActive.set(this.nonCancellableWarningCount > 0); this.cChordActive.set(this.nonCancellableWarningCount > 0); } + // Emergency audio cancel (EAC) + // Listening to the pulse down ensures we only cancel one signal at a time + if (this.ecpEmergencyCancelPulseDown) { + this.emergencyCancelReady = true; + } + if (this.ecpEmergencyCancelLevel.get() && this.emergencyCancelReady) { + // Get the highest priority signal from the sound manager + const currentSound = this.soundManager.getCurrentSoundPlaying(); + const soundToKeys: Record = { + continuousRepetitiveChime: this.auralCrcKeys, + cavalryChargeCont: this.auralCavchargeKeys, + cavalryChargeOnce: this.auralCavchargeKeys, + cChordCont: this.auralCChordKeys, + singleChime: this.auralScKeys, + }; + const keys = currentSound ? soundToKeys[currentSound] : undefined; + const cancelKey = + keys?.find((key) => !this.emergencyCancelledWarnings.has(key)) ?? + this.activeWarningKeys.find((key) => !this.emergencyCancelledWarnings.has(key)) ?? + null; + if (cancelKey) { + this.emergencyCancelledWarnings.add(cancelKey); + } else if (this.masterCaution.get()) { + this.emergencyCancelClearCaution = true; + } + this.emergencyCancelReady = false; + } /* T.O. CONFIG CHECK */ // TODO Note that fuel tank low pressure and gravity feed warnings are not included @@ -3292,10 +3340,11 @@ export class PseudoFWC { this.bat2Off.set(this.bat2PbOff.get() && (this.phase6For60Seconds.read() || this.fwcFlightPhase.get() === 2)); /* CLEAR AND RECALL */ - if (this.ecpClearPulseUp) { + if (this.ecpClearPulseUp || this.emergencyCancelClearCaution) { // delete the first failure this.failuresLeft.splice(0, 1); this.recallFailures = this.allCurrentFailures.filter((item) => !this.failuresLeft.includes(item)); + this.emergencyCancelClearCaution = false; } if (this.ecpRecallPulseUp) { @@ -3338,6 +3387,9 @@ export class PseudoFWC { const failureKeysRight: string[] = this.failuresRight; let leftFailureSystemCount = 0; let rightFailureSystemCount = 0; + let activeWarningCount = 0; + let activeCautionCount = 0; + const activeWarningKeys: string[] = []; const auralCrcKeys: string[] = []; const auralScKeys: string[] = []; const auralCavchargeKeys: string[] = []; @@ -3348,6 +3400,7 @@ export class PseudoFWC { if (!value.simVarIsActive.get()) { failureKeysLeft = failureKeysLeft.filter((e) => e !== key); recallFailureKeys = recallFailureKeys.filter((e) => e !== key); + this.emergencyCancelledWarnings.delete(key as keyof EWDMessageDict); } } @@ -3359,6 +3412,7 @@ export class PseudoFWC { // Failures first for (const [key, value] of Object.entries(this.ewdMessageFailures)) { + const isCancelled = this.emergencyCancelledWarnings.has(key as keyof EWDMessageDict); // new warning? const newWarning = (value.side === 'LEFT' && !this.failuresLeft.includes(key) && !recallFailureKeys.includes(key)) || @@ -3373,6 +3427,16 @@ export class PseudoFWC { // consider monitor input confirm time (0.3 sec by default) simTime >= (this.ewdFailureActivationTime.get(key) ?? 0) + (value.monitorConfirmTime ?? 0.3) ) { + if (!isCancelled) { + if (value.failure === 3) { + activeWarningCount++; + activeWarningKeys.push(key); + } + if (value.failure === 2) { + activeCautionCount++; + } + } + if (newWarning) { if (value.side === 'LEFT') { failureKeysLeft.push(key); @@ -3380,29 +3444,31 @@ export class PseudoFWC { failureKeysRight.push(key); } - if (value.failure === 3) { - this.requestMasterWarningFromFaults = true; - } - if (value.failure === 2) { - this.requestMasterCautionFromFaults = true; - } - if (value.auralWarning?.get() === FwcAuralWarning.CChord) { - this.cChordActive.set(true); + if (!isCancelled) { + if (value.failure === 3) { + this.requestMasterWarningFromFaults = true; + } + if (value.failure === 2) { + this.requestMasterCautionFromFaults = true; + } + if (value.auralWarning?.get() === FwcAuralWarning.CChord) { + this.cChordActive.set(true); + } } } - if (value.cancel === false && value.failure === 3) { + if (!isCancelled && value.cancel === false && value.failure === 3) { this.nonCancellableWarningCount++; } // if the warning is the same as the aural - if (value.auralWarning === undefined && value.failure === 3) { + if (!isCancelled && value.auralWarning === undefined && value.failure === 3) { if (newWarning) { this.auralCrcActive.set(true); } auralCrcKeys.push(key); } - if (value.auralWarning === undefined && value.failure === 2) { + if (!isCancelled && value.auralWarning === undefined && value.failure === 2) { if (newWarning) { this.auralSingleChimePending = true; } @@ -3444,36 +3510,39 @@ export class PseudoFWC { } } - if (value.auralWarning?.get() === FwcAuralWarning.Crc) { + if (!isCancelled && value.auralWarning?.get() === FwcAuralWarning.Crc) { if (!this.auralCrcKeys.includes(key)) { this.auralCrcActive.set(true); } auralCrcKeys.push(key); } - if (value.auralWarning?.get() === FwcAuralWarning.SingleChime) { + if (!isCancelled && value.auralWarning?.get() === FwcAuralWarning.SingleChime) { if (!this.auralScKeys.includes(key)) { this.auralSingleChimePending = true; } auralScKeys.push(key); } - if (value.auralWarning?.get() === FwcAuralWarning.CavalryCharge) { + if (!isCancelled && value.auralWarning?.get() === FwcAuralWarning.CavalryCharge) { auralCavchargeKeys.push(key); } - if (newWarning && value.auralWarning?.get() === FwcAuralWarning.TripleClick) { + if (!isCancelled && newWarning && value.auralWarning?.get() === FwcAuralWarning.TripleClick) { this.soundManager.enqueueSound('pause0p8s'); this.soundManager.enqueueSound('tripleClick'); } - if (value.auralWarning?.get() === FwcAuralWarning.CChord) { + if (!isCancelled && value.auralWarning?.get() === FwcAuralWarning.CChord) { auralCChordKeys.push(key); } } this.auralCrcKeys = auralCrcKeys; this.auralScKeys = auralScKeys; + this.auralCavchargeKeys = auralCavchargeKeys; + this.auralCChordKeys = auralCChordKeys; + this.activeWarningKeys = activeWarningKeys; if (this.auralCrcKeys.length === 0) { this.auralCrcActive.set(false); @@ -3490,6 +3559,7 @@ export class PseudoFWC { } const failLeft = tempFailureArrayLeft.length > 0; + const specialLinesLeft = ['315100001']; const mesgFailOrderLeft: string[] = []; const mesgFailOrderRight: string[] = []; @@ -3514,10 +3584,6 @@ export class PseudoFWC { this.failuresRight.length = 0; this.failuresRight.push(...failureKeysRight); - if (tempFailureArrayLeft.length > 0) { - this.ewdMessageLinesLeft.forEach((l, i) => l.set(orderedFailureArrayLeft[i])); - } - for (const [, value] of Object.entries(this.ewdMessageMemos)) { if ( value.simVarIsActive.get() && @@ -3535,7 +3601,10 @@ export class PseudoFWC { }); } - if (value.side === 'LEFT' && !failLeft) { + if ( + value.side === 'LEFT' && + (!failLeft || value.codesToReturn.some((code) => specialLinesLeft.includes(code))) + ) { tempMemoArrayLeft = tempMemoArrayLeft.concat(newCode); } if (value.side === 'RIGHT') { @@ -3560,12 +3629,24 @@ export class PseudoFWC { } } - const orderedMemoArrayLeft = this.mapOrder(tempMemoArrayLeft, mesgOrderLeft); + let orderedMemoArrayLeft: string[] = this.mapOrder(tempMemoArrayLeft, mesgOrderLeft); let orderedMemoArrayRight: string[] = this.mapOrder(tempMemoArrayRight, mesgOrderRight); - if (!failLeft) { - this.ewdMessageLinesLeft.forEach((l, i) => l.set(orderedMemoArrayLeft[i])); + const filteredMemoLeft = orderedMemoArrayLeft.filter((e) => !specialLinesLeft.includes(e)); + const specLinesInMemoLeft = orderedMemoArrayLeft.filter((e) => specialLinesLeft.includes(e)); + + if (orderedFailureArrayLeft.length > 0) { + // Left side failures need to be inserted below special lines (currently just EMERGENCY CANCEL ON) + if (specLinesInMemoLeft.length > 0) { + orderedMemoArrayLeft = [...specLinesInMemoLeft, ...orderedFailureArrayLeft, ...filteredMemoLeft]; + } else { + orderedMemoArrayLeft = [...orderedFailureArrayLeft, ...orderedMemoArrayLeft]; + } + } else if (specLinesInMemoLeft.length > 0) { + orderedMemoArrayLeft = [...specLinesInMemoLeft, ...filteredMemoLeft]; + } + if (!failLeft) { if (orderedFailureArrayRight.length === 0) { this.requestMasterCautionFromFaults = false; if (this.nonCancellableWarningCount === 0) { @@ -3574,6 +3655,13 @@ export class PseudoFWC { } } + if (activeWarningCount === 0) { + this.requestMasterWarningFromFaults = false; + } + if (activeCautionCount === 0) { + this.requestMasterCautionFromFaults = false; + } + this.masterCaution.set(this.requestMasterCautionFromFaults || this.requestMasterCautionFromABrkOff); this.masterWarning.set(this.requestMasterWarningFromFaults); @@ -3595,6 +3683,7 @@ export class PseudoFWC { } } + this.ewdMessageLinesLeft.forEach((l, i) => l.set(orderedMemoArrayLeft[i])); this.ewdMessageLinesRight.forEach((l, i) => l.set(orderedMemoArrayRight[i])); const chimeRequested = @@ -5770,6 +5859,17 @@ export class PseudoFWC { sysPage: -1, side: 'RIGHT', }, + '3151000': { + // EMERGENCY CANCEL ON + flightPhaseInhib: [], + simVarIsActive: this.ecpEmergencyCancelLevel, + whichCodeToReturn: () => [0], + codesToReturn: ['315100001'], + memoInhibit: () => false, + failure: 0, + sysPage: -1, + side: 'LEFT', + }, // 32 LANDING GEAR 320000001: { // AUTO BRK OFF diff --git a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/behaviour/ecam-cp.xml b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/behaviour/ecam-cp.xml index d36af0dbd14..fe47e733745 100644 --- a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/behaviour/ecam-cp.xml +++ b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/behaviour/ecam-cp.xml @@ -179,7 +179,7 @@ 0 (>L:#SIMVAR#) - (L:A32NX_SD_MORE_SHOWN, Bool) (L:A32NX_OVHD_INTLT_ANN) 0 == or #SEQ2_POWERED# and + (L:A32NX_ECAM_SD_MORE_SHOWN, Bool) (L:A32NX_OVHD_INTLT_ANN) 0 == or #SEQ2_POWERED# and diff --git a/fbw-a380x/src/systems/instruments/src/EWD/elements/WdAbnormalSensedProcedures.tsx b/fbw-a380x/src/systems/instruments/src/EWD/elements/WdAbnormalSensedProcedures.tsx index 6b8f005c963..794b743bc91 100644 --- a/fbw-a380x/src/systems/instruments/src/EWD/elements/WdAbnormalSensedProcedures.tsx +++ b/fbw-a380x/src/systems/instruments/src/EWD/elements/WdAbnormalSensedProcedures.tsx @@ -8,6 +8,8 @@ import { import { WdAbstractChecklistComponent } from 'instruments/src/EWD/elements/WdAbstractChecklistComponent'; import { + ChecklistLineStyle, + EcamMemos, EcamAbnormalProcedures, EcamAbnormalSensedProcedures, isChecklistAction, @@ -17,10 +19,16 @@ import { import { ChecklistState } from 'instruments/src/MsfsAvionicsCommon/providers/FwsPublisher'; import { Arinc429LocalVarConsumerSubject } from '@flybywiresim/fbw-sdk'; +const EMERGENCY_CANCEL_ON_MEMO = '315100001'; +const CANCELLED_CAUTION_MEMO = '315100002'; + export class WdAbnormalSensedProcedures extends WdAbstractChecklistComponent { private readonly procedures = ConsumerSubject.create(this.sub.on('fws_abn_sensed_procedures'), []); private readonly activeProcedureId = ConsumerSubject.create(this.sub.on('fws_active_procedure'), '0'); + private readonly memoLeftLines = Array.from(Array(10), (_, idx) => + ConsumerSubject.create(this.sub.on(`memo_left_${idx + 1}`).whenChanged(), 0), + ); private readonly fcdc1DiscreteWord1 = Arinc429LocalVarConsumerSubject.create(this.sub.on('fcdc_discrete_word_1_1')); private readonly fcdc2DiscreteWord1 = Arinc429LocalVarConsumerSubject.create(this.sub.on('fcdc_discrete_word_1_2')); @@ -57,6 +65,7 @@ export class WdAbnormalSensedProcedures extends WdAbstractChecklistComponent { this.lastProcUpdate = Date.now(); if (!this.props.fwsAvail || this.props.fwsAvail.get()) { + this.addEmergencyCancelTopLines(); this.procedures.get().forEach((procState, index) => { const procGen = new ProcedureLinesGenerator( procState.id, @@ -127,6 +136,7 @@ export class WdAbnormalSensedProcedures extends WdAbstractChecklistComponent { this.subscriptions.push( this.procedures.sub(() => this.updateChecklists(), true), this.activeProcedureId.sub(() => this.updateChecklists(), true), + ...this.memoLeftLines.map((line) => line.sub(() => this.updateChecklists(), true)), this.fcdc12Failed.sub(() => this.updateChecklists(), true), this.fcdc1DiscreteWord1, this.fcdc2DiscreteWord1, @@ -138,6 +148,7 @@ export class WdAbnormalSensedProcedures extends WdAbstractChecklistComponent { this.onGround1, this.onGround2, this.onGround, + ...this.memoLeftLines, ); if (this.props.cpiomAvailChecker) { @@ -215,4 +226,56 @@ export class WdAbnormalSensedProcedures extends WdAbstractChecklistComponent { } } } + + private addEmergencyCancelTopLines(): void { + const leftMemoCodes = this.memoLeftLines + .map((line) => line.get()) + .filter((code): code is number => !!code) + .map((code) => code.toString().padStart(9, '0')); + + if (leftMemoCodes.includes(CANCELLED_CAUTION_MEMO)) { + this.lineData.push(this.createTopLine(EcamMemos[CANCELLED_CAUTION_MEMO] ?? '')); + + const cancelledCautionMemoIndex = leftMemoCodes.indexOf(CANCELLED_CAUTION_MEMO); + const cancelledCautionCode = leftMemoCodes[cancelledCautionMemoIndex + 1]; + const cancelledCautionText = cancelledCautionCode ? this.resolveMemoMessage(cancelledCautionCode) : undefined; + if (cancelledCautionText) { + this.lineData.push(this.createTopLine(cancelledCautionText)); + } + + return; + } + + if (leftMemoCodes.includes(EMERGENCY_CANCEL_ON_MEMO)) { + this.lineData.push(this.createTopLine(EcamMemos[EMERGENCY_CANCEL_ON_MEMO] ?? '')); + } + } + + private resolveMemoMessage(codeString: string): string | undefined { + const memo = EcamMemos[codeString]; + if (memo) { + return memo; + } + + const abnormalTitle = EcamAbnormalProcedures[codeString]?.title; + if (abnormalTitle) { + // eslint-disable-next-line no-control-regex + return abnormalTitle.replace(/\x1b<[1-6]m/g, '\x1b<7m'); + } + + return undefined; + } + + private createTopLine(text: string) { + return { + activeProcedure: false, + sensed: true, + checked: false, + text, + style: ChecklistLineStyle.Headline, + firstLine: true, + lastLine: true, + abnormalProcedure: true, + }; + } } diff --git a/fbw-a380x/src/systems/instruments/src/EWD/elements/WdMemos.tsx b/fbw-a380x/src/systems/instruments/src/EWD/elements/WdMemos.tsx index 6461d2c812e..5545922a4f1 100644 --- a/fbw-a380x/src/systems/instruments/src/EWD/elements/WdMemos.tsx +++ b/fbw-a380x/src/systems/instruments/src/EWD/elements/WdMemos.tsx @@ -10,7 +10,7 @@ import { } from '@microsoft/msfs-sdk'; import { FormattedFwcText } from 'instruments/src/EWD/elements/FormattedFwcText'; import { EwdSimvars } from 'instruments/src/EWD/shared/EwdSimvarPublisher'; -import { EcamMemos } from '../../MsfsAvionicsCommon/EcamMessages'; +import { EcamAbnormalProcedures, EcamMemos } from '../../MsfsAvionicsCommon/EcamMessages'; interface WdMemosProps { bus: EventBus; @@ -18,6 +18,8 @@ interface WdMemosProps { } const padEWDCode = (code: number) => code.toString().padStart(9, '0'); +const EMERGENCY_CANCEL_MEMO = '315100001'; +const CANCELLED_CAUTION_MEMO = '315100002'; export class WdMemos extends DisplayComponent { private readonly sub = this.props.bus.getSubscriber(); @@ -38,17 +40,48 @@ export class WdMemos extends DisplayComponent { private readonly memosRightFormatString = Subject.create(''); + private readonly emergencyCancelMemoActive = Subject.create(false); + private readonly memosDividedAreaClass = this.emergencyCancelMemoActive.map( + (active) => `MemosDividedArea${active ? ' EmergencyCancelFullWidth' : ''}`, + ); + + private resolveMemoMessage(code: number): string | undefined { + const codeString = padEWDCode(code); + const memo = EcamMemos[codeString]; + if (memo) { + return memo; + } + + const abnormalTitle = EcamAbnormalProcedures[codeString]?.title; + if (abnormalTitle) { + // eslint-disable-next-line no-control-regex + return abnormalTitle.replace(/\x1b<[1-6]m/g, '\x1b<7m'); + } + + return undefined; + } + private update() { + const leftMemoCodes = this.memosLeft + .map((v) => v.get()) + .filter((v): v is number => !!v) + .map((code) => padEWDCode(code)); + + this.emergencyCancelMemoActive.set( + leftMemoCodes.includes(EMERGENCY_CANCEL_MEMO) || leftMemoCodes.includes(CANCELLED_CAUTION_MEMO), + ); + this.memosLeftFormatString.set( - this.memosLeft - .filter((v) => !!v.get()) - .map((val) => EcamMemos[padEWDCode(val.get())]) + leftMemoCodes + .map((code) => this.resolveMemoMessage(Number(code))) + .filter((memo): memo is string => !!memo) .join('\r'), ); this.memosRightFormatString.set( this.memosRight .filter((v) => !!v.get()) - .map((val) => EcamMemos[padEWDCode(val.get())]) + .map((val) => this.resolveMemoMessage(val.get())) + .filter((memo): memo is string => !!memo) .join('\r'), ); @@ -72,7 +105,7 @@ export class WdMemos extends DisplayComponent { return ( <>
(it ? 'flex' : 'none')) }}> -
+
diff --git a/fbw-a380x/src/systems/instruments/src/EWD/style.scss b/fbw-a380x/src/systems/instruments/src/EWD/style.scss index af997a2aa43..22f63f68012 100644 --- a/fbw-a380x/src/systems/instruments/src/EWD/style.scss +++ b/fbw-a380x/src/systems/instruments/src/EWD/style.scss @@ -350,6 +350,14 @@ text, tspan, span { padding-bottom: 4px; } +.MemosDividedArea.EmergencyCancelFullWidth .MemosLeft { + border-right: none; +} + +.MemosDividedArea.EmergencyCancelFullWidth .MemosRight { + display: none; +} + .MemosLeft { display: flex; flex-direction: column; diff --git a/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/EcamMessages/index.ts b/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/EcamMessages/index.ts index f7d3cc1d16b..f91eca1c0dd 100644 --- a/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/EcamMessages/index.ts +++ b/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/EcamMessages/index.ts @@ -116,6 +116,8 @@ export const EcamMemos: { [n: string]: string } = { '310000001': '\x1b<4mMEMO NOT AVAIL', '314000001': '\x1b<6mT.O INHIBIT', '314000002': '\x1b<6mLDG INHIBIT', + '315100001': '\x1b<7mEMERGENCY CANCEL ON', + '315100002': '\x1b<7mCANCELLED CAUTION :', '317000001': '\x1b<3mCLOCK INT', '320000001': '\x1b<4mAUTO BRK OFF', '320000002': '\x1b<3mPARK BRK ON', diff --git a/fbw-a380x/src/systems/instruments/src/SDv2/Pages/Status/StatusPage.tsx b/fbw-a380x/src/systems/instruments/src/SDv2/Pages/Status/StatusPage.tsx index b8d742cc632..d556fd07a0b 100644 --- a/fbw-a380x/src/systems/instruments/src/SDv2/Pages/Status/StatusPage.tsx +++ b/fbw-a380x/src/systems/instruments/src/SDv2/Pages/Status/StatusPage.tsx @@ -5,6 +5,7 @@ import { EventBus, FSComponent, MappedSubject, + Subscription, SimVarValueType, Subject, Subscribable, @@ -15,6 +16,7 @@ import { DEFERRED_PROCEDURE_TYPE_TO_STRING, DeferredProcedureType, EcamDeferredProcedures, + EcamAbnormalProcedures, EcamInfos, EcamInopSys, EcamLimitations, @@ -40,6 +42,8 @@ enum StatusPageSectionDisplayStatus { export class StatusPage extends DestroyableComponent { private readonly sub = this.props.bus.getSubscriber(); + private readonly persistentSubscriptions: Subscription[] = []; + private readonly availChecker = new FwsSdAvailabilityChecker(this.props.bus); private readonly topSvgVisibility = this.props.visible.map((v) => (v ? 'visible' : 'hidden')); @@ -204,8 +208,47 @@ export class StatusPage extends DestroyableComponent { private readonly inopSysRedundHeight = this.inopSysRedundLines.map((lines) => `${lines * 30 + 3}px`); + /* CANCELLED CAUTION */ + private readonly cancelledCaution = ConsumerSubject.create(this.sub.on('fws_cancelled_caution'), []); + private readonly cancelledCautionCurrentPage = MappedSubject.create( + ([cancelledCaution, pageToShow]) => + cancelledCaution.slice( + pageToShow * SD_STS_LINES_PER_PAGE_USABLE, + (pageToShow + 1) * SD_STS_LINES_PER_PAGE_USABLE, + ), + this.cancelledCaution, + this.stsPageToShow, + ); + + private readonly cancelledCautionFormatString = this.cancelledCautionCurrentPage.map((cancelledCautionCurrentPage) => + cancelledCautionCurrentPage + // eslint-disable-next-line no-control-regex + .map((val) => EcamAbnormalProcedures[val].title.replace(/\x1b<[1-6]m/g, '\x1b<7m')) + .join('\r'), + ); + + private readonly cancelledCautionLines = MappedSubject.create( + ([cancelledCaution]) => (cancelledCaution.length > 0 ? cancelledCaution.length : 0), + this.cancelledCautionCurrentPage, + ); + private readonly cancelledCautionDisplay = MappedSubject.create( + ([lines]) => (lines > 0 ? 'flex' : 'none'), + this.cancelledCautionLines, + ); + + private readonly cancelledCautionHeight = this.cancelledCautionLines.map((lines) => `${lines * 30 + 3}px`); + private readonly cancelledCautionHasGap = MappedSubject.create( + ([cancelledCautionLines, inopSysRedundLines]) => cancelledCautionLines > 0 && inopSysRedundLines > 0, + this.cancelledCautionLines, + this.inopSysRedundLines, + ); + private readonly moreActive = ConsumerSubject.create(this.sub.on('moreActive'), false); - private readonly moreAvailable = this.inopSysRedund.map((lines) => lines.length > 0); + private readonly moreAvailable = MappedSubject.create( + ([inopSysRedund, cancelledCaution]) => inopSysRedund.length > 0 || cancelledCaution.length > 0, + this.inopSysRedund, + this.cancelledCaution, + ); private readonly moreAvailableVisibility = this.moreAvailable.map((v) => (v ? 'inherit' : 'hidden')); private readonly moreActiveVisibility = this.moreActive.map((v) => (v ? 'inherit' : 'hidden')); @@ -246,8 +289,11 @@ export class StatusPage extends DestroyableComponent { ); private readonly morePageOverflow = MappedSubject.create( - ([inopSysRedund, pageToShow]) => inopSysRedund.length > (pageToShow + 1) * SD_STS_LINES_PER_PAGE_USABLE, + ([inopSysRedund, cancelledCaution, pageToShow]) => + inopSysRedund.length > (pageToShow + 1) * SD_STS_LINES_PER_PAGE_USABLE || + cancelledCaution.length > (pageToShow + 1) * SD_STS_LINES_PER_PAGE_USABLE, this.inopSysRedund, + this.cancelledCaution, this.stsPageToShow, ); private readonly pressMoreForNextMorePageVisibility = this.morePageOverflow.map((v) => (v ? 'inherit' : 'hidden')); @@ -282,12 +328,34 @@ export class StatusPage extends DestroyableComponent { this.inopSysLines, this.inopSysDisplay, this.inopSysHeight, + this.inopSysRedund, + this.inopSysRedundFormatString, + this.inopSysRedundLines, + this.inopSysRedundDisplay, + this.inopSysRedundHeight, + this.cancelledCaution, + this.cancelledCautionFormatString, + this.cancelledCautionLines, + this.cancelledCautionDisplay, + this.cancelledCautionHeight, + this.cancelledCautionHasGap, this.pressStsForNextStatusPageVisibility, ); this.subscriptions.push( MappedSubject.create( - ([pageToShow, limAll, limAppr, deferredProcedures, info, inopApp, inopAppr, moreActive, inopSysRedund]) => { + ([ + pageToShow, + limAll, + limAppr, + deferredProcedures, + info, + inopApp, + inopAppr, + moreActive, + inopSysRedund, + cancelledCaution, + ]) => { // Calculate number of pages required and where to display "ON NEXT PAGE" messages // 6 lines reserved for heading and white PRESS STS... line if ( @@ -296,7 +364,9 @@ export class StatusPage extends DestroyableComponent { deferredProcedures.length === 0 && info.length === 0 && inopApp.length === 0 && - inopAppr.length === 0 + inopAppr.length === 0 && + inopSysRedund.length === 0 && + cancelledCaution.length === 0 ) { this.statusNormal.set(true); } else { @@ -304,9 +374,8 @@ export class StatusPage extends DestroyableComponent { } if (moreActive) { - this.stsNumberOfPagesSimvar.set( - inopSysRedund.length > 0 ? Math.ceil(inopSysRedund.length / SD_STS_LINES_PER_PAGE_USABLE) : 1, - ); + const moreLines = Math.max(inopSysRedund.length, cancelledCaution.length); + this.stsNumberOfPagesSimvar.set(moreLines > 0 ? Math.ceil(moreLines / SD_STS_LINES_PER_PAGE_USABLE) : 1); return; } @@ -424,7 +493,11 @@ export class StatusPage extends DestroyableComponent { this.inopSysApprLdg, this.moreActive, this.inopSysRedund, + this.cancelledCaution, ), + ); + + this.persistentSubscriptions.push( this.moreAvailable.sub((v) => { this.stsMoreAvailableSimvar.set(v ? 1 : 0); }, true), @@ -432,6 +505,9 @@ export class StatusPage extends DestroyableComponent { } destroy(): void { + for (const s of this.persistentSubscriptions) { + s.destroy(); + } super.destroy(); } @@ -599,6 +675,21 @@ export class StatusPage extends DestroyableComponent {
+ {/* CANCELLED CAUTION */} +
+ + + + +
PRESS MORE FOR NEXT MORE PAGE diff --git a/fbw-a380x/src/systems/instruments/src/SDv2/Pages/Status/style.scss b/fbw-a380x/src/systems/instruments/src/SDv2/Pages/Status/style.scss index 9bd21d9a545..64d9abd3a34 100644 --- a/fbw-a380x/src/systems/instruments/src/SDv2/Pages/Status/style.scss +++ b/fbw-a380x/src/systems/instruments/src/SDv2/Pages/Status/style.scss @@ -69,6 +69,10 @@ padding: 0px 15px 0px 15px; } +.sd-sts-more-section-gap { + margin-top: 30px; +} + .sd-sts-section-heading-group { display: flex; flex-direction: row; @@ -99,6 +103,12 @@ text-decoration: underline; } +.WhiteLine { + stroke: $display-white !important; + stroke-width: 2; + fill: none; +} + .sd-sts-section-divided-area-heading { display: flex; justify-content: center; diff --git a/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsAbnormalSensed.ts b/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsAbnormalSensed.ts index 6fa2ae94918..237c215d937 100644 --- a/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsAbnormalSensed.ts +++ b/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsAbnormalSensed.ts @@ -3022,6 +3022,7 @@ export class FwsAbnormalSensed { failure: 2, sysPage: SdPages.None, inopSysAllPhases: () => [], + cancel: false, }, 271800070: { // LOAD ANALYSIS REQUIRED diff --git a/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsCore.ts b/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsCore.ts index a30cd211edb..9ab55d7358e 100644 --- a/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsCore.ts +++ b/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsCore.ts @@ -231,11 +231,14 @@ export class FwsCore { private readonly inopSysApprLdgKeys = Subject.create([]); private readonly alertsImpactingLdgPerfKeys = Subject.create([]); private readonly inopSysRedundLossKeys = Subject.create([]); + private readonly cancelledCautionKeys = Subject.create([]); + public readonly cancelledCautionMemoKey = Subject.create(''); // Input buffering public readonly toConfigInputBuffer = new NXLogicMemoryNode(false); public readonly clearButtonInputBuffer = new NXLogicMemoryNode(false); public readonly recallButtonInputBuffer = new NXLogicMemoryNode(false); + public readonly emergencyCancelInputBuffer = new NXLogicMemoryNode(false); public readonly clInputBuffer = new NXLogicMemoryNode(false); public readonly clCheckInputBuffer = new NXLogicMemoryNode(false); public readonly clUpInputBuffer = new NXLogicMemoryNode(false); @@ -287,8 +290,17 @@ export class FwsCore { private auralScKeys: string[] = []; + private auralCavchargeKeys: string[] = []; + + private activeWarningKeys: string[] = []; + + private readonly emergencyCancelledWarnings = new Set(); + private readonly cancelledCautions = new Set(); + public readonly auralCrcActive = Subject.create(false); + private readonly cavalryChargeActive = Subject.create(false); + private auralSingleChimePending = false; public readonly auralSingleChimeInhibitTimer = new DebounceTimer(); @@ -301,6 +313,16 @@ export class FwsCore { private nonCancellableWarningCount = 0; + private emergencyCancelReady = true; + + private emergencyCancelUsed = false; + + private emergencyCancelClearCautionKey: string | null = null; + + public readonly ecpEmergencyCancelLevel = Subject.create(false); + + private wasEcpEmergencyCancelLevel = false; + public readonly stallWarning = Subject.create(false); public readonly masterCautionOutput = MappedSubject.create( @@ -1443,6 +1465,10 @@ export class FwsCore { public readonly clrPulseNode = new NXLogicPulseNode(); public readonly rclUpPulseNode = new NXLogicPulseNode(); + private readonly rclHeld3sConfNode = new NXLogicConfirmNode(3); + private readonly rclHeld3sPulseNode = new NXLogicPulseNode(); + private readonly emerCancResetIrsAlignConfNode = new NXLogicConfirmNode(30); + private readonly emerCancResetIrsAlignPulseNode = new NXLogicPulseNode(); public readonly clPulseNode = new NXLogicPulseNode(); @@ -2435,6 +2461,7 @@ export class FwsCore { this.inopSysApprLdgKeys.sub((v) => this.publisher.pub('fws_inop_sys_appr_ldg', v, true)), this.alertsImpactingLdgPerfKeys.sub((v) => this.publisher.pub('fws_alerts_impacting_ldg_perf', v, true)), this.inopSysRedundLossKeys.sub((v) => this.publisher.pub('fws_inop_sys_redundancy_loss', v, true)), + this.cancelledCautionKeys.sub((v) => this.publisher.pub('fws_cancelled_caution', v, true)), ); this.subs.push( @@ -2451,6 +2478,13 @@ export class FwsCore { this.auralCrcActive.sub((crc) => this.soundManager.handleSoundCondition('continuousRepetitiveChime', crc), true), ); + this.subs.push( + this.cavalryChargeActive.sub( + (cavcharge) => this.soundManager.handleSoundCondition('cavalryChargeCont', cavcharge), + true, + ), + ); + this.subs.push( this.masterCautionOutput.sub((caution) => { // Inhibit master warning/cautions until FWC startup has been completed @@ -2614,10 +2648,19 @@ export class FwsCore { mapOrder(array: string[], order: string[]): string[] { array.sort((a, b) => { - if (order.indexOf(a) > order.indexOf(b)) { + const orderA = order.indexOf(a); + const orderB = order.indexOf(b); + const normalizedOrderA = orderA === -1 ? Number.POSITIVE_INFINITY : orderA; + const normalizedOrderB = orderB === -1 ? Number.POSITIVE_INFINITY : orderB; + + if (normalizedOrderA > normalizedOrderB) { return 1; } - return -1; + if (normalizedOrderA < normalizedOrderB) { + return -1; + } + + return 0; }); return array; } @@ -2718,6 +2761,13 @@ export class FwsCore { this.recallButtonInputBuffer.write(true, false); } + // EMER CANC button + const emergencyCancelButton = SimVar.GetSimVarValue('L:A32NX_BTN_EMERCANC', 'bool'); + if (emergencyCancelButton) { + this.emergencyCancelInputBuffer.write(true, false); + } + this.ecpEmergencyCancelLevel.set(emergencyCancelButton); + // C/L buttons if (SimVar.GetSimVarValue('L:A32NX_BTN_CL', 'bool')) { this.clInputBuffer.write(true, false); @@ -2775,6 +2825,13 @@ export class FwsCore { // Update flight phases this.flightPhases.update(deltaTime); + // Manual audio inhibition (MAI) + this.soundManager.setManualAudioInhibition(this.ecpEmergencyCancelLevel.get()); + if (this.ecpEmergencyCancelLevel.get() !== this.wasEcpEmergencyCancelLevel) { + this.cancelledCautionMemoKey.set(''); + this.wasEcpEmergencyCancelLevel = this.ecpEmergencyCancelLevel.get(); + } + // Play sounds this.soundManager.onUpdate(deltaTime); @@ -3323,6 +3380,21 @@ export class FwsCore { this.adiru1ModeSelector.set(SimVar.GetSimVarValue('L:A32NX_OVHD_ADIRS_IR_1_MODE_SELECTOR_KNOB', 'enum')); this.adiru2ModeSelector.set(SimVar.GetSimVarValue('L:A32NX_OVHD_ADIRS_IR_2_MODE_SELECTOR_KNOB', 'enum')); this.adiru3ModeSelector.set(SimVar.GetSimVarValue('L:A32NX_OVHD_ADIRS_IR_3_MODE_SELECTOR_KNOB', 'enum')); + + const rclHeld3s = this.rclHeld3sConfNode.write(recallButton && !this.fwsEcpFailed.get(), deltaTime); + const rclHeld3sPulse = this.rclHeld3sPulseNode.write(rclHeld3s, deltaTime); + const irsAlignConfirmed = this.emerCancResetIrsAlignConfNode.write( + this.ir1Align.get() && this.ir2Align.get(), + deltaTime, + ); + const irsAlignPulse = this.emerCancResetIrsAlignPulseNode.write(irsAlignConfirmed, deltaTime); + if (rclHeld3sPulse || (irsAlignPulse && this.flightPhase1Or2.get())) { + if (this.cancelledCautions.size > 0) { + this.cancelledCautions.clear(); + this.cancelledCautionMemoKey.set(''); + } + } + // RA acquisition this.radioHeight1.setFromSimVar('L:A32NX_RA_1_RADIO_ALTITUDE'); this.radioHeight2.setFromSimVar('L:A32NX_RA_2_RADIO_ALTITUDE'); @@ -5246,6 +5318,58 @@ export class FwsCore { this.auralCrcActive.set(this.nonCancellableWarningCount > 0); } + // Emergency audio cancel (EAC) + const emergencyCancelInput = this.emergencyCancelInputBuffer.read(); + if (emergencyCancelInput && this.emergencyCancelReady) { + const currentSound = this.soundManager.getCurrentSoundPlaying(); + const soundToKeys: Record = { + continuousRepetitiveChime: this.auralCrcKeys, + cavalryChargeCont: this.auralCavchargeKeys, + cavalryChargeOnce: this.auralCavchargeKeys, + singleChime: this.auralScKeys, + }; + const keys = currentSound ? soundToKeys[currentSound] : undefined; + const isKeyCancelled = (key: string) => + this.emergencyCancelledWarnings.has(key) || this.cancelledCautions.has(key); + const cancelKey = + keys?.find((key) => !isKeyCancelled(key)) ?? this.activeWarningKeys.find((key) => !isKeyCancelled(key)) ?? null; + if (cancelKey) { + if (this.ewdAbnormal[cancelKey]?.failure === 2) { + if (!this.cancelledCautions.has(cancelKey)) { + this.cancelledCautions.add(cancelKey); + } + this.cancelledCautionMemoKey.set(cancelKey); + this.emergencyCancelClearCautionKey = cancelKey; + this.emergencyCancelUsed = true; + } else { + this.emergencyCancelledWarnings.add(cancelKey); + } + } else { + let nonSensedDeactivated = false; + const activeProcedureId = this.abnormalSensed.activeProcedureId.get(); + if (activeProcedureId && !EcamAbnormalProcedures[activeProcedureId]?.sensed) { + nonSensedDeactivated = this.activeAbnormalNonSensedKeys.delete(Number(activeProcedureId)); + } + + if (!nonSensedDeactivated && this.masterCaution.get()) { + const cautionKey = this.presentedFailures.find( + (key) => this.ewdAbnormal[key]?.failure === 2 && !this.cancelledCautions.has(key), + ); + if (cautionKey) { + this.cancelledCautions.add(cautionKey); + this.cancelledCautionMemoKey.set(cautionKey); + this.emergencyCancelClearCautionKey = cautionKey; + this.emergencyCancelUsed = true; + } + } + } + this.emergencyCancelReady = false; + } + if (!this.ecpEmergencyCancelLevel.get() && !emergencyCancelInput) { + this.emergencyCancelReady = true; + this.emergencyCancelUsed = false; + } + /* T.O. CONFIG CHECK */ if (this.toMemo.get() && this.toConfigTestRaw) { @@ -5300,6 +5424,25 @@ export class FwsCore { } } + if (this.emergencyCancelClearCautionKey) { + const clearKey = this.emergencyCancelClearCautionKey; + if (this.abnormalSensed.abnormalShown.get() && this.abnormalSensed.activeProcedureId.get() === clearKey) { + this.abnormalSensed.clearActiveProcedure(); + } else { + const index = this.presentedFailures.indexOf(clearKey); + if (index !== -1) { + this.presentedFailures.splice(index, 1); + const clearedState = this.presentedAbnormalProceduresList.getValue(clearKey); + if (clearedState) { + this.clearedAbnormalProceduresList.setValue(clearKey, clearedState); + this.presentedAbnormalProceduresList.delete(clearKey); + } + this.recallFailures = this.allCurrentFailures.filter((item) => !this.presentedFailures.includes(item)); + } + } + this.emergencyCancelClearCautionKey = null; + } + // Output logic this.landAsap.set( @@ -5327,8 +5470,16 @@ export class FwsCore { let failureKeys: string[] = this.presentedFailures; let recallFailureKeys: string[] = this.recallFailures; let failureSystemCount = 0; + let activeWarningCount = 0; + let activeCautionCount = 0; + const activeWarningKeys: string[] = []; const auralCrcKeys: string[] = []; const auralScKeys: string[] = []; + const auralCavchargeKeys: string[] = []; + if (this.cancelledCautions.size > 0) { + failureKeys = failureKeys.filter((key) => !this.cancelledCautions.has(key)); + recallFailureKeys = recallFailureKeys.filter((key) => !this.cancelledCautions.has(key)); + } const itemIsActiveConsideringFaultSuppression = ( item: FwsSuppressableItem, @@ -5370,6 +5521,9 @@ export class FwsCore { if (!itemIsActiveConsideringFaultSuppression(value, key, 0.6)) { failureKeys = failureKeys.filter((e) => e !== key); recallFailureKeys = recallFailureKeys.filter((e) => e !== key); + if (value.failure === 3) { + this.emergencyCancelledWarnings.delete(key); + } } } @@ -5396,136 +5550,172 @@ export class FwsCore { continue; } - if (itemIsActiveConsideringFaultSuppression(value, key, 0.6)) { + const isActive = itemIsActiveConsideringFaultSuppression(value, key, 0.6); + + if ( + this.ecpEmergencyCancelLevel.get() && + !this.emergencyCancelUsed && + isActive && + newWarning && + value.failure === 2 && + !this.cancelledCautions.has(key) + ) { + // TODO: Add cancelled caution ordering when we implement an ordering system within priority 2 groups + this.cancelledCautions.add(key); + this.cancelledCautionMemoKey.set(key); + this.emergencyCancelUsed = true; + } + + const isCancelledCaution = value.failure === 2 && this.cancelledCautions.has(key); + const isCancelledWarning = value.failure === 3 && this.emergencyCancelledWarnings.has(key); + const isCancelled = isCancelledWarning || isCancelledCaution; + + if (isActive) { const itemsChecked = value.whichItemsChecked().map((v, i) => (!proc.items[i]?.sensed ? false : !!v)); const itemsToShow = value.whichItemsToShow ? value.whichItemsToShow() : Array(itemsChecked.length).fill(true); const itemsActive = value.whichItemsActive ? value.whichItemsActive() : Array(itemsChecked.length).fill(true); const itemsTimer = value.whichItemsTimer ? value.whichItemsTimer() : undefined; ProcedureLinesGenerator.conditionalActiveItems(proc, itemsChecked, itemsActive, itemsTimer); - if (newWarning) { - failureKeys.push(key); - + if (!isCancelled) { if (value.failure === 3) { - this.requestMasterWarningFromFaults = true; + activeWarningCount++; + activeWarningKeys.push(key); } if (value.failure === 2) { - this.requestMasterCautionFromFaults = true; + activeCautionCount++; } } - const previousPresentedState = this.presentedAbnormalProceduresList.getValue(key); - const previousClearedState = this.clearedAbnormalProceduresList.getValue(key); - if (!previousPresentedState && !previousClearedState) { - // Insert into internal map - if (value.whichItemsActive) { - if (proc.items.length !== value.whichItemsActive().length) { - console.warn( - proc.title, - 'ECAM alert definition error: whichItemsActive() not the same size as number of procedure items', - ); + if (newWarning) { + if (!isCancelledCaution) { + failureKeys.push(key); + } + + if (!isCancelled) { + if (value.failure === 3) { + this.requestMasterWarningFromFaults = true; + } + if (value.failure === 2) { + this.requestMasterCautionFromFaults = true; } } - if (value.whichItemsToShow) { - if (proc.items.length !== value.whichItemsToShow().length) { + } + + if (!isCancelledCaution) { + const previousPresentedState = this.presentedAbnormalProceduresList.getValue(key); + const previousClearedState = this.clearedAbnormalProceduresList.getValue(key); + if (!previousPresentedState && !previousClearedState) { + // Insert into internal map + if (value.whichItemsActive) { + if (proc.items.length !== value.whichItemsActive().length) { + console.warn( + proc.title, + 'ECAM alert definition error: whichItemsActive() not the same size as number of procedure items', + ); + } + } + if (value.whichItemsToShow) { + if (proc.items.length !== value.whichItemsToShow().length) { + console.warn( + proc.title, + 'ECAM alert definition error: whichItemsToShow() not the same size as number of procedure items', + ); + } + } + if (proc.items.length !== value.whichItemsChecked().length) { console.warn( proc.title, - 'ECAM alert definition error: whichItemsToShow() not the same size as number of procedure items', - ); - } - } - if (proc.items.length !== value.whichItemsChecked().length) { - console.warn( - proc.title, - 'ECAM alert definition error: whichItemsChecked() not the same size as number of procedure items', - ); - } - this.presentedAbnormalProceduresList.setValue(key, { - id: key, - procedureActivated: true, - procedureCompleted: false, - itemsActive: itemsActive, - itemsChecked: itemsChecked, - itemsToShow: itemsToShow, - itemsTimeStamp: itemsTimer, - }); - - for (const [deferredKey, deferredValue] of ewdDeferredEntries) { - if ( - EcamDeferredProcedures[deferredKey].fromAbnormalProcs.includes(key) && - this.abnormalSensed.ewdDeferredProcs[deferredKey] - ) { - const deferredItemsActive = Array(deferredValue.whichItemsChecked().length).fill(false); // not activated, hence all false - const deferredItemsChecked = deferredValue.whichItemsChecked - ? deferredValue.whichItemsChecked() - : Array(deferredItemsActive.length).fill(true); - ProcedureLinesGenerator.conditionalActiveItems( - EcamDeferredProcedures[deferredKey], - deferredItemsChecked, - deferredItemsActive, + 'ECAM alert definition error: whichItemsChecked() not the same size as number of procedure items', ); - this.activeDeferredProceduresList.setValue(deferredKey, { - id: deferredKey, - procedureCompleted: false, - procedureActivated: false, - itemsChecked: deferredItemsChecked, - itemsActive: deferredItemsActive, - itemsToShow: deferredValue.whichItemsToShow - ? deferredValue.whichItemsToShow() - : Array(deferredValue.whichItemsChecked().length).fill(true), - }); } - } - } else if (previousPresentedState) { - // Update internal map - const fusedChecked = [...previousPresentedState.itemsChecked].map((val, index) => - proc.items[index].sensed ? itemsChecked[index] : !!val, - ); - ProcedureLinesGenerator.conditionalActiveItems(proc, fusedChecked, itemsActive, itemsTimer); - this.abnormalUpdatedItems.set(key, []); - proc.items.forEach((item, idx) => { - if ( - previousPresentedState.itemsToShow[idx] !== itemsToShow[idx] || - previousPresentedState.itemsActive[idx] !== itemsActive[idx] || - (previousPresentedState.itemsChecked[idx] !== fusedChecked[idx] && item.sensed) || - (isTimedItem(item) !== undefined && - itemsTimer !== undefined && - previousPresentedState.itemsTimeStamp !== undefined && - previousPresentedState.itemsTimeStamp[idx] !== itemsTimer[idx]) - ) { - this.abnormalUpdatedItems.get(key)?.push(idx); - } - }); - - if ((this.abnormalUpdatedItems.has(key) && this.abnormalUpdatedItems.get(key)?.length) ?? 0 > 0) { this.presentedAbnormalProceduresList.setValue(key, { id: key, - procedureActivated: previousPresentedState.procedureActivated, - procedureCompleted: previousPresentedState.procedureCompleted, - itemsChecked: fusedChecked, - itemsActive: [...previousPresentedState.itemsActive].map((_, index) => itemsActive[index]), - itemsToShow: [...previousPresentedState.itemsToShow].map((_, index) => itemsToShow[index]), - itemsTimeStamp: previousPresentedState.itemsTimeStamp - ? [...previousPresentedState.itemsTimeStamp].map((_, index) => - itemsTimer ? itemsTimer[index] : undefined, - ) - : undefined, + procedureActivated: true, + procedureCompleted: false, + itemsActive: itemsActive, + itemsChecked: itemsChecked, + itemsToShow: itemsToShow, + itemsTimeStamp: itemsTimer, + }); + + for (const [deferredKey, deferredValue] of ewdDeferredEntries) { + if ( + EcamDeferredProcedures[deferredKey].fromAbnormalProcs.includes(key) && + this.abnormalSensed.ewdDeferredProcs[deferredKey] + ) { + const deferredItemsActive = Array(deferredValue.whichItemsChecked().length).fill(false); // not activated, hence all false + const deferredItemsChecked = deferredValue.whichItemsChecked + ? deferredValue.whichItemsChecked() + : Array(deferredItemsActive.length).fill(true); + ProcedureLinesGenerator.conditionalActiveItems( + EcamDeferredProcedures[deferredKey], + deferredItemsChecked, + deferredItemsActive, + ); + this.activeDeferredProceduresList.setValue(deferredKey, { + id: deferredKey, + procedureCompleted: false, + procedureActivated: false, + itemsChecked: deferredItemsChecked, + itemsActive: deferredItemsActive, + itemsToShow: deferredValue.whichItemsToShow + ? deferredValue.whichItemsToShow() + : Array(deferredValue.whichItemsChecked().length).fill(true), + }); + } + } + } else if (previousPresentedState) { + // Update internal map + const fusedChecked = [...previousPresentedState.itemsChecked].map((val, index) => + proc.items[index].sensed ? itemsChecked[index] : !!val, + ); + ProcedureLinesGenerator.conditionalActiveItems(proc, fusedChecked, itemsActive, itemsTimer); + this.abnormalUpdatedItems.set(key, []); + proc.items.forEach((item, idx) => { + if ( + previousPresentedState.itemsToShow[idx] !== itemsToShow[idx] || + previousPresentedState.itemsActive[idx] !== itemsActive[idx] || + (previousPresentedState.itemsChecked[idx] !== fusedChecked[idx] && item.sensed) || + (isTimedItem(item) !== undefined && + itemsTimer !== undefined && + previousPresentedState.itemsTimeStamp !== undefined && + previousPresentedState.itemsTimeStamp[idx] !== itemsTimer[idx]) + ) { + this.abnormalUpdatedItems.get(key)?.push(idx); + } }); + + if ((this.abnormalUpdatedItems.has(key) && this.abnormalUpdatedItems.get(key)?.length) ?? 0 > 0) { + this.presentedAbnormalProceduresList.setValue(key, { + id: key, + procedureActivated: previousPresentedState.procedureActivated, + procedureCompleted: previousPresentedState.procedureCompleted, + itemsChecked: fusedChecked, + itemsActive: [...previousPresentedState.itemsActive].map((_, index) => itemsActive[index]), + itemsToShow: [...previousPresentedState.itemsToShow].map((_, index) => itemsToShow[index]), + itemsTimeStamp: previousPresentedState.itemsTimeStamp + ? [...previousPresentedState.itemsTimeStamp].map((_, index) => + itemsTimer ? itemsTimer[index] : undefined, + ) + : undefined, + }); + } } } - if (value.cancel === false && value.failure === 3) { + if (!isCancelled && value.cancel === false && value.failure === 3) { this.nonCancellableWarningCount++; } // if the warning is the same as the aural - if (value.auralWarning === undefined && value.failure === 3) { + if (!isCancelled && value.auralWarning === undefined && value.failure === 3) { if (newWarning) { this.auralCrcActive.set(true); } auralCrcKeys.push(key); } - if (value.auralWarning === undefined && value.failure === 2) { + if (!isCancelled && value.auralWarning === undefined && value.failure === 2) { if (newWarning) { this.auralSingleChimePending = true; console.log('single chime pending'); @@ -5559,29 +5749,29 @@ export class FwsCore { return []; }, ewdLimitationsAllPhasesKeys); - if (!recallFailureKeys.includes(key)) { + if (!recallFailureKeys.includes(key) && !isCancelledCaution) { if (value.sysPage > -1) { failureSystemCount++; } } } - if (value.auralWarning?.get() === FwcAuralWarning.Crc) { + if (!isCancelled && value.auralWarning?.get() === FwcAuralWarning.Crc) { if (!this.auralCrcKeys.includes(key)) { this.auralCrcActive.set(true); } auralCrcKeys.push(key); } - if (value.auralWarning?.get() === FwcAuralWarning.SingleChime) { + if (!isCancelled && value.auralWarning?.get() === FwcAuralWarning.SingleChime) { if (!this.auralScKeys.includes(key)) { this.auralSingleChimePending = true; } auralScKeys.push(key); } - if (value.auralWarning?.get() === FwcAuralWarning.CavalryCharge) { - this.soundManager.enqueueSound('cavalryChargeCont'); + if (!isCancelled && value.auralWarning?.get() === FwcAuralWarning.CavalryCharge) { + auralCavchargeKeys.push(key); } } @@ -5641,6 +5831,19 @@ export class FwsCore { } }); + if (this.cancelledCautions.size > 0) { + this.presentedAbnormalProceduresList.get().forEach((_, key) => { + if (this.cancelledCautions.has(key)) { + this.presentedAbnormalProceduresList.delete(key); + } + }); + this.clearedAbnormalProceduresList.get().forEach((_, key) => { + if (this.cancelledCautions.has(key)) { + this.clearedAbnormalProceduresList.delete(key); + } + }); + } + // Delete inactive failures from internal map this.presentedAbnormalProceduresList.get().forEach((procedure, key) => { if (!allFailureKeys.includes(key) || this.recallFailures.includes(key)) { @@ -5661,6 +5864,8 @@ export class FwsCore { this.auralCrcKeys = auralCrcKeys; this.auralScKeys = auralScKeys; + this.auralCavchargeKeys = auralCavchargeKeys; + this.activeWarningKeys = activeWarningKeys; if (this.auralCrcKeys.length === 0) { this.auralCrcActive.set(false); @@ -5670,12 +5875,38 @@ export class FwsCore { this.auralSingleChimePending = false; } + this.cavalryChargeActive.set(auralCavchargeKeys.length !== 0); + this.allCurrentFailures.length = 0; this.allCurrentFailures.push(...allFailureKeys); this.presentedFailures.length = 0; this.presentedFailures.push(...failureKeys); + // Resolve dynamic codes for CANCELLED CAUTION memos + const resolveMemoCodesToReturn = ( + memoCodeSelection: Array, + memoCodesToReturn: string[], + ): string[] => { + const resolvedCodes: string[] = []; + for (const code of memoCodeSelection) { + if (code === null) { + continue; + } + + if (typeof code === 'string') { + resolvedCodes.push(code); + continue; + } + + if (typeof code === 'number' && memoCodesToReturn[code] !== undefined) { + resolvedCodes.push(memoCodesToReturn[code]); + } + } + + return resolvedCodes; + }; + // MEMOs (except T.O and LDG) for (const [, value] of Object.entries(this.memos.ewdMemos)) { if ( @@ -5683,12 +5914,7 @@ export class FwsCore { !value.memoInhibit() && !value.flightPhaseInhib.some((e) => e === flightPhase) ) { - const newCode: string[] = []; - - const codeIndex = value.whichCodeToReturn().filter((e) => e !== null); - codeIndex.forEach((e: number) => { - newCode.push(value.codesToReturn[e]); - }); + const newCode = resolveMemoCodesToReturn(value.whichCodeToReturn(), value.codesToReturn); const tempArrayRight = tempMemoArrayRight.filter((e) => !value.codesToReturn.includes(e)); tempMemoArrayRight = tempArrayRight.concat(newCode); } @@ -5701,12 +5927,7 @@ export class FwsCore { !value.memoInhibit() && !value.flightPhaseInhib.some((e) => e === flightPhase) ) { - const newCode: string[] = []; - - const codeIndex = value.whichCodeToReturn().filter((e) => e !== null); - codeIndex.forEach((e: number) => { - newCode.push(value.codesToReturn[e]); - }); + const newCode = resolveMemoCodesToReturn(value.whichCodeToReturn(), value.codesToReturn); tempMemoArrayLeft = tempMemoArrayLeft.concat(newCode); } @@ -5772,15 +5993,12 @@ export class FwsCore { } // Reset master caution light if appropriate - if (allFailureKeys.filter((key) => this.ewdAbnormal[key].failure === 2).length === 0) { + if (activeCautionCount === 0) { this.requestMasterCautionFromFaults = false; } // Reset master warning light if appropriate - if ( - allFailureKeys.filter((key) => this.ewdAbnormal[key].failure === 3).length === 0 && - this.nonCancellableWarningCount === 0 - ) { + if (activeWarningCount === 0) { this.requestMasterWarningFromFaults = false; } @@ -5854,6 +6072,13 @@ export class FwsCore { ) { this.inopSysRedundLossKeys.set(stsInopRedundLossKeys); } + const cancelledCautionKeysList = [...this.cancelledCautions]; + if ( + this.cancelledCautionKeys.get().length !== cancelledCautionKeysList.length || + !this.cancelledCautionKeys.get().every((value, index) => value === cancelledCautionKeysList[index]) + ) { + this.cancelledCautionKeys.set(cancelledCautionKeysList); + } // TODO order by decreasing importance, only color-based for now // LAND ASAP overrides/replaces LAND ANSA @@ -5977,6 +6202,7 @@ export class FwsCore { this.toConfigInputBuffer.write(false, true); this.clearButtonInputBuffer.write(false, true); this.recallButtonInputBuffer.write(false, true); + this.emergencyCancelInputBuffer.write(false, true); this.clInputBuffer.write(false, true); this.clCheckInputBuffer.write(false, true); this.clUpInputBuffer.write(false, true); @@ -6050,14 +6276,16 @@ export class FwsCore { } // Highest importance: priority 0 switch (message.trim().substring(0, 3)) { - case '\x1b<6': + case '\x1b<7': return 0; - case '\x1b<2': + case '\x1b<6': return 1; - case '\x1b<4': + case '\x1b<2': return 2; - case '\x1b<3': + case '\x1b<4': return 3; + case '\x1b<3': + return 4; default: return 10; } diff --git a/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsFlightPhases.ts b/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsFlightPhases.ts index 9d372efd3be..93e7bdfa024 100644 --- a/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsFlightPhases.ts +++ b/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsFlightPhases.ts @@ -418,7 +418,11 @@ export class FwsFlightPhases { const warningPressed = !!SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERAWARN_L', 'Bool') || !!SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERAWARN_R', 'Bool'); - if (warningPressed === true) { + const emergencyCancelPressed = this.fws.emergencyCancelInputBuffer.read(); + const currentSound = this.fws.soundManager.getCurrentSoundPlaying(); + const cChordPlaying = currentSound === 'cChordOnce' || currentSound === 'cChordCont'; + // Check cChordPlaying, otherwise the warning / EMER CANC press could be trying to cancel something else + if ((warningPressed || emergencyCancelPressed) && cChordPlaying) { this._wasBelowThreshold = false; this._wasAboveThreshold = false; this._wasInRange = false; diff --git a/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsMemos.ts b/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsMemos.ts index b5826448f91..8f8c2662084 100644 --- a/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsMemos.ts +++ b/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsMemos.ts @@ -16,7 +16,7 @@ interface EwdMemoItem { flightPhaseInhib: number[]; /** warning is active */ simVarIsActive: MappedSubscribable | Subscribable; - whichCodeToReturn: () => any[]; + whichCodeToReturn: () => Array; codesToReturn: string[]; memoInhibit: () => boolean; leftSide?: boolean; @@ -662,6 +662,23 @@ export class FwsMemos { memoInhibit: () => false, leftSide: true, }, + '3151000': { + // EMERGENCY CANCEL ON + flightPhaseInhib: [], + simVarIsActive: this.fws.ecpEmergencyCancelLevel, + whichCodeToReturn: () => { + const cancelledCautionKey = this.fws.cancelledCautionMemoKey.get(); + + if (cancelledCautionKey) { + return [1, cancelledCautionKey]; + } + + return [0]; + }, + codesToReturn: ['315100001', '315100002'], + memoInhibit: () => false, + leftSide: true, + }, }; public destroy(): void { diff --git a/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsSoundManager.ts b/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsSoundManager.ts index 2185f15c487..dafb688f553 100644 --- a/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsSoundManager.ts +++ b/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsSoundManager.ts @@ -269,6 +269,8 @@ export const FwsAuralsList: Record = { }, }; +const MutableSounds = [FwsAuralsList.cavalryChargeOnce, FwsAuralsList.cavalryChargeCont]; + // FIXME Not all sounds are added to this yet (e.g. CIDS chimes), consider adding them in the future // Also, single chimes are not filtered (in RL only once every two seconds) export class FwsSoundManager { @@ -281,6 +283,12 @@ export class FwsSoundManager { /** in seconds */ private currentSoundPlayTimeRemaining = 0; + private manualAudioInhibition = false; + + private requestedVolume = FwsAuralVolume.Full; + + private appliedVolume: FwsAuralVolume | null = null; + constructor( private readonly bus: EventBus, private readonly startupCompleted: Subscribable, @@ -298,6 +306,19 @@ export class FwsSoundManager { sub.on('dequeueSound').handle((s) => this.dequeueSound(s)); } + getCurrentSoundPlaying(): keyof typeof FwsAuralsList | null { + return this.currentSoundPlaying; + } + + /** Inhibit starting any new broadcasts (MAI). */ + setManualAudioInhibition(inhibit: boolean) { + if (this.manualAudioInhibition === inhibit) { + return; + } + this.manualAudioInhibition = inhibit; + this.applyVolume(); + } + /** Add sound to queue. Don't add if already playing */ enqueueSound(soundKey: keyof typeof FwsAuralsList) { const sound = FwsAuralsList[soundKey]; @@ -347,7 +368,17 @@ export class FwsSoundManager { /** This only has an effect on sounds defining WwiseRTPC behavior/var for volume */ setVolume(volume: FwsAuralVolume) { - SimVar.SetSimVarValue('L:A32NX_FWS_AUDIO_VOLUME', SimVarValueType.Enum, volume); + this.requestedVolume = volume; + this.applyVolume(); + } + + private applyVolume() { + const effectiveVolume = this.manualAudioInhibition ? FwsAuralVolume.Silent : this.requestedVolume; + if (this.appliedVolume !== null && effectiveVolume === this.appliedVolume) { + return; + } + SimVar.SetSimVarValue('L:A32NX_FWS_AUDIO_VOLUME', SimVarValueType.Enum, effectiveVolume); + this.appliedVolume = effectiveVolume; } /** Play now, not to be called from the outside */ @@ -387,6 +418,11 @@ export class FwsSoundManager { } }); + // TODO: Once all sounds are mutable, this.manualAudioInhibition shouldn't be needed anymore + if (this.manualAudioInhibition && selectedSoundKey && !MutableSounds.includes(FwsAuralsList[selectedSoundKey])) { + return null; + } + if (selectedSoundKey) { this.playSound(selectedSoundKey); return selectedSoundKey; @@ -394,6 +430,9 @@ export class FwsSoundManager { // See if single chimes are left if (this.singleChimesPending) { + if (this.manualAudioInhibition) { + return null; + } this.playSound('singleChime'); this.singleChimesPending--; return 'singleChime'; @@ -432,17 +471,18 @@ export class FwsSoundManager { // Interrupt if sound with higher category is present in queue and current sound is continuous let shouldInterrupt = false; let rescheduleSound: keyof typeof FwsAuralsList | null = null; - this.soundQueue.forEach((sk) => { - const s = FwsAuralsList[sk]; - if ( - s && - this.currentSoundPlaying && - FwsAuralsList[this.currentSoundPlaying]?.continuous && - s.type > FwsAuralsList[this.currentSoundPlaying].type - ) { - shouldInterrupt = true; - } - }); + const currentSound = this.currentSoundPlaying ? FwsAuralsList[this.currentSoundPlaying] : undefined; + if (currentSound?.continuous) { + this.soundQueue.forEach((sk) => { + const s = FwsAuralsList[sk]; + if ( + s && + (s.type > currentSound.type || (s.type === currentSound.type && s.priority > currentSound.priority)) + ) { + shouldInterrupt = true; + } + }); + } if (shouldInterrupt) { if (this.currentSoundPlaying && FwsAuralsList[this.currentSoundPlaying]?.continuous) { diff --git a/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsSystemDisplayLogic.ts b/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsSystemDisplayLogic.ts index 46968551b80..cbbeac693a8 100644 --- a/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsSystemDisplayLogic.ts +++ b/fbw-a380x/src/systems/systems-host/CpiomC/FlightWarningSystem/FwsSystemDisplayLogic.ts @@ -291,13 +291,19 @@ export class FwsSystemDisplayLogic { private checkStsPage = (deltaTime: number) => { const isStatusPageEmpty = this.fws.ecamStatusNormal.get(); + const isStatusMorePageAvailable = this.stsMoreAvailableSimvar.get() === 1; + + if (!isStatusMorePageAvailable && this.sdMoreShownSimvar.get() !== 0) { + this.sdMoreShownSimvar.set(0); + this.stsPageToShowSimvar.set(0); + } if (this.currentPage.get() !== SdPages.Status) { this.stsPressedTimer.set(STS_DISPLAY_TIMER_DURATION); return; } - if (isStatusPageEmpty) { + if (isStatusPageEmpty && !isStatusMorePageAvailable) { if (this.stsPressedTimer.get() > 0) { const prev = this.stsPressedTimer.get(); this.stsPressedTimer.set(prev - deltaTime / 1000);