From b8a0f7c40c49a4cca5b85603b5159076342be189 Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Mon, 4 May 2026 22:14:12 +0200 Subject: [PATCH 01/10] Implement enumerateCameraDefinitions --- src/SimConnectConnection.ts | 15 ++++++++++++++- src/datastructures/CameraDefinitionItem.ts | 9 +++++++++ src/datastructures/index.ts | 1 + src/recv/RecvCameraDefinitionList.ts | 16 ++++++++++++++++ src/recv/index.ts | 1 + 5 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/datastructures/CameraDefinitionItem.ts create mode 100644 src/recv/RecvCameraDefinitionList.ts diff --git a/src/SimConnectConnection.ts b/src/SimConnectConnection.ts index ccc3b74..5f0fb7a 100644 --- a/src/SimConnectConnection.ts +++ b/src/SimConnectConnection.ts @@ -22,6 +22,7 @@ import { RecvAirportList, RecvAssignedObjectID, RecvCloudState, + RecvCameraDefinitionList, RecvControllersList, RecvCustomAction, RecvEnumerateInputEventParams, @@ -161,6 +162,7 @@ interface SimConnectRecvEvents { recvEnumerateSimobjectAndLiveryList: RecvEnumerateSimobjectAndLiveryList ) => void; flowEvent: (recvFlowEvent: RecvFlowEvent) => void; + cameraDefinitionList: (recvCameraDefinitionList: RecvCameraDefinitionList) => void; commBusEvent: (recvCommBus: RecvCommBus) => void; } @@ -1865,6 +1867,17 @@ class SimConnectConnection extends EventEmitter { * SimConnect_CameraSetUsingCameraDefinition: 0x69 */ + /** + * + * @returns sendId of packet (can be used to identify packet when exception event occurs) + */ + enumerateCameraDefinitions(): number { + if (this._ourProtocol < Protocol.SunRise) throw Error(SimConnectError.BadVersion); + + const packet = this._beginPacket(0x68); + return this._buildAndSend(packet); + } + /** * * @returns sendId of packet (can be used to identify packet when exception event occurs) @@ -2069,7 +2082,7 @@ class SimConnectConnection extends EventEmitter { // TODO break; case RecvID.ID_CAMERA_DEFINITION_LIST: - // TODO + this.emit('cameraDefinitionList', new RecvCameraDefinitionList(data)); break; case RecvID.ID_COMM_BUS: this.emit('commBusEvent', new RecvCommBus(data)); diff --git a/src/datastructures/CameraDefinitionItem.ts b/src/datastructures/CameraDefinitionItem.ts new file mode 100644 index 0000000..c24acd0 --- /dev/null +++ b/src/datastructures/CameraDefinitionItem.ts @@ -0,0 +1,9 @@ +import { RawBuffer } from '../RawBuffer'; + +export class CameraDefinitionItem { + name: string; + + constructor(data: RawBuffer) { + this.name = data.readString256(); + } +} diff --git a/src/datastructures/index.ts b/src/datastructures/index.ts index 640de6e..cd5ee01 100644 --- a/src/datastructures/index.ts +++ b/src/datastructures/index.ts @@ -8,3 +8,4 @@ export * from './InputEventDescriptor'; export * from './JetwayData'; export * from './EnumerateSimobjectLivery'; export * from './VersionBaseType'; +export * from './CameraDefinitionItem'; diff --git a/src/recv/RecvCameraDefinitionList.ts b/src/recv/RecvCameraDefinitionList.ts new file mode 100644 index 0000000..4269a2b --- /dev/null +++ b/src/recv/RecvCameraDefinitionList.ts @@ -0,0 +1,16 @@ +import { CameraDefinitionItem } from '../datastructures/CameraDefinitionItem'; +import { RawBuffer } from '../RawBuffer'; +import { RecvListTemplate } from './RecvListTemplate'; + +export class RecvCameraDefinitionList extends RecvListTemplate { + cameraDefinitions: CameraDefinitionItem[] = []; + + constructor(data: RawBuffer) { + super(data); + + this.cameraDefinitions = []; + for (let i = 0; i < this.arraySize; i++) { + this.cameraDefinitions.push(new CameraDefinitionItem(data)); + } + } +} diff --git a/src/recv/index.ts b/src/recv/index.ts index 195cb45..27c5625 100644 --- a/src/recv/index.ts +++ b/src/recv/index.ts @@ -34,3 +34,4 @@ export * from './RecvListTemplate'; export * from './RecvFlowEvent'; export * from './RecvEnumerateSimobjectAndLiveryList'; export * from './RecvCommBus'; +export * from './RecvCameraDefinitionList'; From 54bbdc6d0abeed2cbcba2a19c50864409af86563 Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Wed, 6 May 2026 20:47:27 +0200 Subject: [PATCH 02/10] Rename prompt -> skill which is a more common term --- .github/copilot-instructions.md | 4 ++-- .../add-api-method.skill.md} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename .github/{prompts/add-api-method.prompt.md => skills/add-api-method.skill.md} (100%) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 32f6d31..607811a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -32,6 +32,6 @@ npm run lint # ESLint + Prettier - Pre-commit hook runs `lint-staged` (ESLint fix → Prettier → `tsc --noEmit`). -## Skills / prompt files +## Skills -See [`.github/prompts/`](.github/prompts/) for reusable Copilot prompt files (skills). +See [`.github/skills/`](.github/skills/) for reusable Copilot skills. diff --git a/.github/prompts/add-api-method.prompt.md b/.github/skills/add-api-method.skill.md similarity index 100% rename from .github/prompts/add-api-method.prompt.md rename to .github/skills/add-api-method.skill.md From cfbec52e83d372c7de6f59b7db00ca5c18b3e27c Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Wed, 6 May 2026 21:59:22 +0200 Subject: [PATCH 03/10] Implement cameraAcquire and cameraRelease --- src/SimConnectConnection.ts | 26 +++++++++++++++++++++++++- src/enums/CameraAvailability.ts | 6 ++++++ src/enums/index.ts | 1 + src/recv/RecvCameraStatus.ts | 13 +++++++++++++ src/recv/index.ts | 1 + 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/enums/CameraAvailability.ts create mode 100644 src/recv/RecvCameraStatus.ts diff --git a/src/SimConnectConnection.ts b/src/SimConnectConnection.ts index 5f0fb7a..8989457 100644 --- a/src/SimConnectConnection.ts +++ b/src/SimConnectConnection.ts @@ -23,6 +23,7 @@ import { RecvAssignedObjectID, RecvCloudState, RecvCameraDefinitionList, + RecvCameraStatus, RecvControllersList, RecvCustomAction, RecvEnumerateInputEventParams, @@ -163,6 +164,7 @@ interface SimConnectRecvEvents { ) => void; flowEvent: (recvFlowEvent: RecvFlowEvent) => void; cameraDefinitionList: (recvCameraDefinitionList: RecvCameraDefinitionList) => void; + cameraStatus: (recvCameraStatus: RecvCameraStatus) => void; commBusEvent: (recvCommBus: RecvCommBus) => void; } @@ -1851,6 +1853,28 @@ class SimConnectConnection extends EventEmitter { return this._buildAndSend(packet); } + /** + * + * @returns sendId of packet (can be used to identify packet when exception event occurs) + */ + cameraAcquire(clientId: string): number { + if (this._ourProtocol < Protocol.SunRise) throw Error(SimConnectError.BadVersion); + + const packet = this._beginPacket(0x5f).putString(clientId, 2048); + return this._buildAndSend(packet); + } + + /** + * + * @returns sendId of packet (can be used to identify packet when exception event occurs) + */ + cameraRelease(cameraDefName: string): number { + if (this._ourProtocol < Protocol.SunRise) throw Error(SimConnectError.BadVersion); + + const packet = this._beginPacket(0x60).putString(cameraDefName, 2048); + return this._buildAndSend(packet); + } + /** * TODO: implement new camera APIs here * @@ -2079,7 +2103,7 @@ class SimConnectConnection extends EventEmitter { // TODO break; case RecvID.ID_CAMERA_STATUS: - // TODO + this.emit('cameraStatus', new RecvCameraStatus(data)); break; case RecvID.ID_CAMERA_DEFINITION_LIST: this.emit('cameraDefinitionList', new RecvCameraDefinitionList(data)); diff --git a/src/enums/CameraAvailability.ts b/src/enums/CameraAvailability.ts new file mode 100644 index 0000000..ada3999 --- /dev/null +++ b/src/enums/CameraAvailability.ts @@ -0,0 +1,6 @@ +export enum CameraAvailability { + NOT_ACQUIRED = 0, + ACQUIRED = 1, + ACQUIRED_BY_OTHER = 2, + USER_DISABLED = 3, +} diff --git a/src/enums/index.ts b/src/enums/index.ts index 2364467..fe08724 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -14,3 +14,4 @@ export * from './FacilityDataType'; export * from './FlowEvent'; export * from './JetwayStatus'; export * from './CommBusBroadcastTo'; +export * from './CameraAvailability'; diff --git a/src/recv/RecvCameraStatus.ts b/src/recv/RecvCameraStatus.ts new file mode 100644 index 0000000..3186b0e --- /dev/null +++ b/src/recv/RecvCameraStatus.ts @@ -0,0 +1,13 @@ +import { CameraAvailability } from '../enums/CameraAvailability'; +import { RawBuffer } from '../RawBuffer'; + +export class RecvCameraStatus { + acquiredState: CameraAvailability; + + gameControlled: boolean; + + constructor(data: RawBuffer) { + this.acquiredState = data.readUint32() as CameraAvailability; + this.gameControlled = data.readInt32() !== 0; + } +} diff --git a/src/recv/index.ts b/src/recv/index.ts index 27c5625..b980a11 100644 --- a/src/recv/index.ts +++ b/src/recv/index.ts @@ -35,3 +35,4 @@ export * from './RecvFlowEvent'; export * from './RecvEnumerateSimobjectAndLiveryList'; export * from './RecvCommBus'; export * from './RecvCameraDefinitionList'; +export * from './RecvCameraStatus'; From 39a22bef0c45fa8543c12a7d64f79c45d2b9a44c Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Wed, 6 May 2026 22:02:08 +0200 Subject: [PATCH 04/10] Implement cameraSetUsingCameraDefinition --- src/SimConnectConnection.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/SimConnectConnection.ts b/src/SimConnectConnection.ts index 8989457..7463de1 100644 --- a/src/SimConnectConnection.ts +++ b/src/SimConnectConnection.ts @@ -1902,6 +1902,17 @@ class SimConnectConnection extends EventEmitter { return this._buildAndSend(packet); } + /** + * + * @returns sendId of packet (can be used to identify packet when exception event occurs) + */ + cameraSetUsingCameraDefinition(cameraDefinition: string): number { + if (this._ourProtocol < Protocol.SunRise) throw Error(SimConnectError.BadVersion); + + const packet = this._beginPacket(0x69).putString(cameraDefinition, 2048); + return this._buildAndSend(packet); + } + /** * * @returns sendId of packet (can be used to identify packet when exception event occurs) From 67cb1c0567cea173932338ff01b250f0042b7443 Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Wed, 6 May 2026 22:05:14 +0200 Subject: [PATCH 05/10] Implement cameraGetStatus --- src/SimConnectConnection.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/SimConnectConnection.ts b/src/SimConnectConnection.ts index 7463de1..d5e5275 100644 --- a/src/SimConnectConnection.ts +++ b/src/SimConnectConnection.ts @@ -1875,20 +1875,26 @@ class SimConnectConnection extends EventEmitter { return this._buildAndSend(packet); } + /** + * + * @returns sendId of packet (can be used to identify packet when exception event occurs) + */ + cameraGetStatus(): number { + if (this._ourProtocol < Protocol.SunRise) throw Error(SimConnectError.BadVersion); + + const packet = this._beginPacket(0x61); + return this._buildAndSend(packet); + } + /** * TODO: implement new camera APIs here * - * SimConnect_CameraAcquire: 0x5f - * SimConnect_CameraRelease: 0x60 - * SimConnect_CameraGetStatus: 0x61 * SimConnect_CameraSet: 0x62 * SimConnect_CameraGet: 0x63 * SimConnect_CameraEnableFlag: 0x64 * SimConnect_CameraDisableFlag: 0x65 * SimConnect_SubscribeToCameraStatusUpdate: 0x66 * SimConnect_UnsubscribeToCameraStatusUpdate: 0x67 - * SimConnect_EnumerateCameraDefinitions: 0x68 - * SimConnect_CameraSetUsingCameraDefinition: 0x69 */ /** From 663d391e54ca95347309df5c2d4276bc81d7269b Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Wed, 6 May 2026 22:13:24 +0200 Subject: [PATCH 06/10] Implement subscribeToCameraStatusUpdate and unsubscribeToCameraStatusUpdate --- src/SimConnectConnection.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/SimConnectConnection.ts b/src/SimConnectConnection.ts index d5e5275..86de99b 100644 --- a/src/SimConnectConnection.ts +++ b/src/SimConnectConnection.ts @@ -1893,10 +1893,30 @@ class SimConnectConnection extends EventEmitter { * SimConnect_CameraGet: 0x63 * SimConnect_CameraEnableFlag: 0x64 * SimConnect_CameraDisableFlag: 0x65 - * SimConnect_SubscribeToCameraStatusUpdate: 0x66 - * SimConnect_UnsubscribeToCameraStatusUpdate: 0x67 */ + /** + * + * @returns sendId of packet (can be used to identify packet when exception event occurs) + */ + subscribeToCameraStatusUpdate(): number { + if (this._ourProtocol < Protocol.SunRise) throw Error(SimConnectError.BadVersion); + + const packet = this._beginPacket(0x66); + return this._buildAndSend(packet); + } + + /** + * + * @returns sendId of packet (can be used to identify packet when exception event occurs) + */ + unsubscribeToCameraStatusUpdate(): number { + if (this._ourProtocol < Protocol.SunRise) throw Error(SimConnectError.BadVersion); + + const packet = this._beginPacket(0x67); + return this._buildAndSend(packet); + } + /** * * @returns sendId of packet (can be used to identify packet when exception event occurs) From b50124edab00a444b6ce6ecf0252e34575259638 Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Wed, 6 May 2026 22:24:11 +0200 Subject: [PATCH 07/10] Implement cameraSet and cameraGet --- .github/copilot-instructions.md | 2 +- src/SimConnectConnection.ts | 33 ++++++++++++++++++++-- src/dto/CameraData.ts | 47 ++++++++++++++++++++++++++++++++ src/dto/index.ts | 1 + src/enums/CameraDataMask.ts | 10 +++++++ src/enums/PositionReferential.ts | 7 +++++ src/enums/index.ts | 2 ++ src/recv/RecvCameraData.ts | 11 ++++++++ src/recv/index.ts | 1 + 9 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 src/dto/CameraData.ts create mode 100644 src/enums/CameraDataMask.ts create mode 100644 src/enums/PositionReferential.ts create mode 100644 src/recv/RecvCameraData.ts diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 607811a..53af8e4 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -18,7 +18,7 @@ Official MSFS docs home: ## Enum conventions - File names: `PascalCase.ts` (e.g. `CommBusBroadcastTo.ts`), exported from `src/enums/index.ts`. -- Member names: short `PascalCase` stripping the repetitive C++ prefix (e.g. `SIMCONNECT_COMM_BUS_BROADCAST_TO_JS` → `JS`). +- Member names: `SCREAMING_SNAKE_CASE` stripping the repetitive C++ prefix (e.g. `SIMCONNECT_COMM_BUS_BROADCAST_TO_JS` → `JS`, `SIMCONNECT_CAMERA_AVAILABILITY_NOT_ACQUIRED` → `NOT_ACQUIRED`). - Values must exactly match the SDK C++ definitions — use bit-shift literals (`1 << 0`) for flags and composite expressions for combined values. ## Build & test diff --git a/src/SimConnectConnection.ts b/src/SimConnectConnection.ts index 86de99b..e5d7752 100644 --- a/src/SimConnectConnection.ts +++ b/src/SimConnectConnection.ts @@ -22,6 +22,7 @@ import { RecvAirportList, RecvAssignedObjectID, RecvCloudState, + RecvCameraData, RecvCameraDefinitionList, RecvCameraStatus, RecvControllersList, @@ -70,6 +71,9 @@ import { RecvEnumerateSimobjectAndLiveryList } from './recv/RecvEnumerateSimobje import { RecvFlowEvent } from './recv/RecvFlowEvent'; import { RecvCommBus } from './recv/RecvCommBus'; import { CommBusBroadcastTo } from './enums/CommBusBroadcastTo'; +import { CameraData } from './dto/CameraData'; +import { CameraDataMask } from './enums/CameraDataMask'; +import { PositionReferential } from './enums'; type OpenPacketData = { major: number; @@ -163,6 +167,7 @@ interface SimConnectRecvEvents { recvEnumerateSimobjectAndLiveryList: RecvEnumerateSimobjectAndLiveryList ) => void; flowEvent: (recvFlowEvent: RecvFlowEvent) => void; + cameraData: (recvCameraData: RecvCameraData) => void; cameraDefinitionList: (recvCameraDefinitionList: RecvCameraDefinitionList) => void; cameraStatus: (recvCameraStatus: RecvCameraStatus) => void; commBusEvent: (recvCommBus: RecvCommBus) => void; @@ -1889,12 +1894,34 @@ class SimConnectConnection extends EventEmitter { /** * TODO: implement new camera APIs here * - * SimConnect_CameraSet: 0x62 - * SimConnect_CameraGet: 0x63 * SimConnect_CameraEnableFlag: 0x64 * SimConnect_CameraDisableFlag: 0x65 */ + /** + * + * @returns sendId of packet (can be used to identify packet when exception event occurs) + */ + cameraSet(cameraData: CameraData, dataMask: CameraDataMask): number { + if (this._ourProtocol < Protocol.SunRise) throw Error(SimConnectError.BadVersion); + + const packet = this._beginPacket(0x62); + cameraData.writeTo(packet); + packet.putUint32(dataMask); + return this._buildAndSend(packet); + } + + /** + * + * @returns sendId of packet (can be used to identify packet when exception event occurs) + */ + cameraGet(positionReferential: PositionReferential): number { + if (this._ourProtocol < Protocol.SunRise) throw Error(SimConnectError.BadVersion); + + const packet = this._beginPacket(0x63).putUint32(positionReferential); + return this._buildAndSend(packet); + } + /** * * @returns sendId of packet (can be used to identify packet when exception event occurs) @@ -2137,7 +2164,7 @@ class SimConnectConnection extends EventEmitter { this.emit('flowEvent', new RecvFlowEvent(data)); break; case RecvID.ID_CAMERA_DATA: - // TODO + this.emit('cameraData', new RecvCameraData(data)); break; case RecvID.ID_CAMERA_STATUS: this.emit('cameraStatus', new RecvCameraStatus(data)); diff --git a/src/dto/CameraData.ts b/src/dto/CameraData.ts new file mode 100644 index 0000000..894ff3d --- /dev/null +++ b/src/dto/CameraData.ts @@ -0,0 +1,47 @@ +import { RawBuffer } from '../RawBuffer'; +import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder'; +import { PositionReferential } from '../enums/PositionReferential'; +import { PBH } from './PBH'; +import { XYZ } from './XYZ'; + +export class CameraData { + position: XYZ = new XYZ(); + + positionReferential: PositionReferential = PositionReferential.NONE; + + positionReferentialObjectId = 0; + + targetedPos: XYZ = new XYZ(); + + pbh: PBH = new PBH(); + + rotationReferential: PositionReferential = PositionReferential.NONE; + + rotationReferentialObjectId = 0; + + fov = 0; + + readFrom(buffer: RawBuffer) { + this.position.readFrom(buffer); + this.positionReferential = buffer.readUint32() as PositionReferential; + this.positionReferentialObjectId = buffer.readUint32(); + this.targetedPos.readFrom(buffer); + this.pbh.readFrom(buffer); + this.rotationReferential = buffer.readUint32() as PositionReferential; + this.rotationReferentialObjectId = buffer.readUint32(); + this.fov = buffer.readFloat64(); + } + + writeTo(packetBuilder: SimConnectPacketBuilder) { + this.position.writeTo(packetBuilder); + packetBuilder + .putUint32(this.positionReferential) + .putUint32(this.positionReferentialObjectId); + this.targetedPos.writeTo(packetBuilder); + this.pbh.writeTo(packetBuilder); + packetBuilder + .putUint32(this.rotationReferential) + .putUint32(this.rotationReferentialObjectId) + .putFloat64(this.fov); + } +} diff --git a/src/dto/index.ts b/src/dto/index.ts index 158c4d9..1a250b6 100644 --- a/src/dto/index.ts +++ b/src/dto/index.ts @@ -7,3 +7,4 @@ export * from './PBH'; export * from './SimConnectData'; export * from './Waypoint'; export * from './XYZ'; +export * from './CameraData'; diff --git a/src/enums/CameraDataMask.ts b/src/enums/CameraDataMask.ts new file mode 100644 index 0000000..c1ffcc4 --- /dev/null +++ b/src/enums/CameraDataMask.ts @@ -0,0 +1,10 @@ +export enum CameraDataMask { + NONE = 0, + POSITION = 1 << 0, + ROTATION = 1 << 1, + TARGETED = 1 << 2, + FOV = 1 << 3, + REFERENTIAL = 1 << 4, + ALL_ROTATION = POSITION | ROTATION | FOV, + ALL_TARGETED = POSITION | TARGETED | FOV, +} diff --git a/src/enums/PositionReferential.ts b/src/enums/PositionReferential.ts new file mode 100644 index 0000000..9b0b2fe --- /dev/null +++ b/src/enums/PositionReferential.ts @@ -0,0 +1,7 @@ +export enum PositionReferential { + NONE = 0, + SIMOBJECT = 1, + WORLD = 2, + EYEPOINT = 3, + SIMOBJECT_DATUM = 4, +} diff --git a/src/enums/index.ts b/src/enums/index.ts index fe08724..b00c8d6 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -15,3 +15,5 @@ export * from './FlowEvent'; export * from './JetwayStatus'; export * from './CommBusBroadcastTo'; export * from './CameraAvailability'; +export * from './PositionReferential'; +export * from './CameraDataMask'; diff --git a/src/recv/RecvCameraData.ts b/src/recv/RecvCameraData.ts new file mode 100644 index 0000000..69de2fb --- /dev/null +++ b/src/recv/RecvCameraData.ts @@ -0,0 +1,11 @@ +import { CameraData } from '../dto/CameraData'; +import { RawBuffer } from '../RawBuffer'; + +export class RecvCameraData { + cameraData: CameraData; + + constructor(data: RawBuffer) { + this.cameraData = new CameraData(); + this.cameraData.readFrom(data); + } +} diff --git a/src/recv/index.ts b/src/recv/index.ts index b980a11..d7ead0a 100644 --- a/src/recv/index.ts +++ b/src/recv/index.ts @@ -36,3 +36,4 @@ export * from './RecvEnumerateSimobjectAndLiveryList'; export * from './RecvCommBus'; export * from './RecvCameraDefinitionList'; export * from './RecvCameraStatus'; +export * from './RecvCameraData'; From b8fe3a94a4d636fd68b9d3006e34a7488ae424df Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Wed, 6 May 2026 22:41:43 +0200 Subject: [PATCH 08/10] Implement cameraEnableFlag and cameraDisableFlag --- src/SimConnectConnection.ts | 22 +++++++++++++++++++--- src/enums/CameraFlag.ts | 5 +++++ src/enums/index.ts | 1 + 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 src/enums/CameraFlag.ts diff --git a/src/SimConnectConnection.ts b/src/SimConnectConnection.ts index e5d7752..db5f43a 100644 --- a/src/SimConnectConnection.ts +++ b/src/SimConnectConnection.ts @@ -73,6 +73,7 @@ import { RecvCommBus } from './recv/RecvCommBus'; import { CommBusBroadcastTo } from './enums/CommBusBroadcastTo'; import { CameraData } from './dto/CameraData'; import { CameraDataMask } from './enums/CameraDataMask'; +import { CameraFlag } from './enums/CameraFlag'; import { PositionReferential } from './enums'; type OpenPacketData = { @@ -1892,11 +1893,26 @@ class SimConnectConnection extends EventEmitter { } /** - * TODO: implement new camera APIs here * - * SimConnect_CameraEnableFlag: 0x64 - * SimConnect_CameraDisableFlag: 0x65 + * @returns sendId of packet (can be used to identify packet when exception event occurs) + */ + cameraEnableFlag(flag: CameraFlag): number { + if (this._ourProtocol < Protocol.SunRise) throw Error(SimConnectError.BadVersion); + + const packet = this._beginPacket(0x64).putUint32(flag); + return this._buildAndSend(packet); + } + + /** + * + * @returns sendId of packet (can be used to identify packet when exception event occurs) */ + cameraDisableFlag(flag: CameraFlag): number { + if (this._ourProtocol < Protocol.SunRise) throw Error(SimConnectError.BadVersion); + + const packet = this._beginPacket(0x65).putUint32(flag); + return this._buildAndSend(packet); + } /** * diff --git a/src/enums/CameraFlag.ts b/src/enums/CameraFlag.ts new file mode 100644 index 0000000..97bf9d8 --- /dev/null +++ b/src/enums/CameraFlag.ts @@ -0,0 +1,5 @@ +export enum CameraFlag { + NONE = 0x00, + INTERACTION = 0x01, + ABOVE_GROUND = 0x02, +} diff --git a/src/enums/index.ts b/src/enums/index.ts index b00c8d6..ba2240a 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -17,3 +17,4 @@ export * from './CommBusBroadcastTo'; export * from './CameraAvailability'; export * from './PositionReferential'; export * from './CameraDataMask'; +export * from './CameraFlag'; From e92a7ac804ffc539ca48dfe398c455b682f0e351 Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Wed, 6 May 2026 22:51:05 +0200 Subject: [PATCH 09/10] Add sample for camera APIs --- samples/typescript/cameraControl.ts | 152 ++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 samples/typescript/cameraControl.ts diff --git a/samples/typescript/cameraControl.ts b/samples/typescript/cameraControl.ts new file mode 100644 index 0000000..e58e3d0 --- /dev/null +++ b/samples/typescript/cameraControl.ts @@ -0,0 +1,152 @@ +import { + CameraAvailability, + CameraData, + CameraDataMask, + CameraFlag, + open, + PositionReferential, + Protocol, + SimConnectConnection, + XYZ, +} from '../../dist'; + +const APP_NAME = 'SimConnect Camera Sample'; + +type Mode = 'cycleCameraDefinitions' | 'moveCamera' | 'updateFov' | null; +let activeMode: Mode = null; + +let cameraAcquired = false; +let gameControlled = false; +const cameraDefinitions: string[] = []; +let currentCameraDef = 0; +let frame = 0; + +// moveCamera state +let moveDeg = -180; +let moveAxis = 0; + +// updateFov state +let currentFov = 45.0; +let fovIncrement = true; + +function printStatus() { + console.log( + `\nActive mode: ${ + activeMode ?? 'none' + } [1] cycleCameraDefinitions [2] moveCamera [3] updateFov [q] quit` + ); +} + +open(APP_NAME, Protocol.SunRise) + .then(({ recvOpen, handle }) => { + console.log('Connected:', recvOpen.applicationName); + console.log('Press 1/2/3 to toggle modes, q to quit.'); + printStatus(); + + handle.on('exception', recvException => { + console.log('SimConnect exception:', recvException); + }); + + handle.on('cameraStatus', recvCameraStatus => { + gameControlled = recvCameraStatus.gameControlled; + console.log('Game Controlled =', gameControlled ? 1 : 0); + console.log( + 'Camera Availability =', + CameraAvailability[recvCameraStatus.acquiredState] + ); + cameraAcquired = recvCameraStatus.acquiredState === CameraAvailability.ACQUIRED; + }); + + handle.on('cameraDefinitionList', recvCameraDefinitionList => { + for (const cd of recvCameraDefinitionList.cameraDefinitions) { + cameraDefinitions.push(cd.name); + } + }); + + handle.subscribeToCameraStatusUpdate(); + handle.enumerateCameraDefinitions(); + handle.cameraAcquire(APP_NAME); + handle.cameraDisableFlag(CameraFlag.INTERACTION | CameraFlag.ABOVE_GROUND); + + const loop = setInterval(() => { + if (cameraAcquired && !gameControlled) { + if (activeMode === 'cycleCameraDefinitions') switchToNextCameraDefinition(handle); + if (activeMode === 'updateFov') doUpdateFov(handle); + if (activeMode === 'moveCamera') doMoveCamera(handle); + } + }, 1); + + // Keyboard input + process.stdin.setRawMode(true); + process.stdin.resume(); + process.stdin.setEncoding('utf8'); + process.stdin.on('data', (key: string) => { + if (key === '1') { + activeMode = + activeMode === 'cycleCameraDefinitions' ? null : 'cycleCameraDefinitions'; + printStatus(); + } else if (key === '2') { + activeMode = activeMode === 'moveCamera' ? null : 'moveCamera'; + printStatus(); + } else if (key === '3') { + activeMode = activeMode === 'updateFov' ? null : 'updateFov'; + printStatus(); + } else if (key === 'q' || key === '\u0003') { + clearInterval(loop); + handle.unsubscribeToCameraStatusUpdate(); + handle.close(); + process.exit(0); + } + }); + }) + .catch(error => { + console.log('Failed to connect:', error); + }); + +function doMoveCamera(handle: SimConnectConnection) { + if (moveDeg === 180) { + moveAxis = (moveAxis + 1) % 3; + moveDeg = -180; + } + + const data = new CameraData(); + data.position = new XYZ(); + data.pbh.pitch = moveAxis === 0 ? moveDeg : 0; + data.pbh.bank = moveAxis === 1 ? moveDeg : 0; + data.pbh.heading = moveAxis === 2 ? moveDeg : 0; + data.rotationReferential = PositionReferential.EYEPOINT; + + handle.cameraSet( + data, + CameraDataMask.POSITION | CameraDataMask.REFERENTIAL | CameraDataMask.ROTATION + ); + + moveDeg++; +} + +function doUpdateFov(handle: SimConnectConnection) { + currentFov += fovIncrement ? 0.5 : -0.5; + if (currentFov <= 1.0 || currentFov >= 160.0) { + fovIncrement = !fovIncrement; + } + + const data = new CameraData(); + data.rotationReferential = PositionReferential.EYEPOINT; + data.fov = currentFov * (Math.PI / 180); + + handle.cameraSet(data, CameraDataMask.ALL_ROTATION); +} + +function switchToNextCameraDefinition(handle: SimConnectConnection) { + if (cameraDefinitions.length === 0) return; + + frame++; + if (frame < 100) return; + + frame = 0; + currentCameraDef = (currentCameraDef + 1) % cameraDefinitions.length; + + const name = cameraDefinitions[currentCameraDef]!; + console.log('Switch to CameraDefinition', name); + handle.cameraSetUsingCameraDefinition(name); +} From 40536c8f78d32a579806e2b0004a6a9eef97f16b Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Thu, 7 May 2026 19:30:07 +0200 Subject: [PATCH 10/10] Fix VS Code complaining about line endings and sample code --- .gitattributes | 1 + tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/tsconfig.json b/tsconfig.json index 84534b8..91884f5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,6 @@ "isolatedModules": true, "types": ["node", "jest"] }, - "include": ["src/**/*", "tests/**/*", "utils/**/*"], + "include": ["src/**/*", "tests/**/*", "utils/**/*", "samples/**/*"], "exclude": ["node_modules", "dist"] }