-
Options
+
Options
+
@@ -57,17 +60,51 @@
+
+
+
+
+
Camera
+
+
\ No newline at end of file
diff --git a/frontend/website/src/constants/constants.ts b/frontend/website/src/constants/constants.ts
index fc96f47a..a0fa35cf 100644
--- a/frontend/website/src/constants/constants.ts
+++ b/frontend/website/src/constants/constants.ts
@@ -207,7 +207,7 @@ export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{
"toggles": ["human"],
"tooltip": "Toggle human players' visibility"
}, {
- "image": "visibility/head-side-virus-solid.svg",
+ "image": "visibility/olympus.svg",
"isProtected": false,
"name": "Olympus",
"protectable": false,
@@ -268,6 +268,7 @@ export const FILL_SELECTED_RING = "Fill the threat range rings of selected units
export const SHOW_UNIT_CONTACTS = "Show selected units contact lines";
export const SHOW_UNIT_PATHS = "Show selected unit paths";
export const SHOW_UNIT_TARGETS = "Show selected unit targets";
+export const DCS_LINK_PORT = "DCS Camera link port";
export enum DataIndexes {
startOfData = 0,
diff --git a/frontend/website/src/controls/dropdown.ts b/frontend/website/src/controls/dropdown.ts
index 38014dc9..99694154 100644
--- a/frontend/website/src/controls/dropdown.ts
+++ b/frontend/website/src/controls/dropdown.ts
@@ -117,6 +117,13 @@ export class Dropdown {
this.#options.appendChild(optionElement);
}
+ addHorizontalDivider() {
+ let div = document.createElement("div");
+ let hr = document.createElement('hr');
+ div.appendChild(hr);
+ this.#options.appendChild(div);
+ }
+
/** Select the active value of the dropdown
*
* @param idx The index of the element to select
diff --git a/frontend/website/src/map/map.ts b/frontend/website/src/map/map.ts
index 4c82614c..6629c59c 100644
--- a/frontend/website/src/map/map.ts
+++ b/frontend/website/src/map/map.ts
@@ -7,12 +7,12 @@ import { AirbaseContextMenu } from "../contextmenus/airbasecontextmenu";
import { Dropdown } from "../controls/dropdown";
import { Airbase } from "../mission/airbase";
import { Unit } from "../unit/unit";
-import { bearing, createCheckboxOption, deg2rad, getGroundElevation, polyContains } from "../other/utils";
+import { bearing, createCheckboxOption, createTextInputOption, deg2rad, getGroundElevation, polyContains } from "../other/utils";
import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker";
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector'
-import { defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, MAP_MARKER_CONTROLS } from "../constants/constants";
+import { defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, MAP_MARKER_CONTROLS, DCS_LINK_PORT } from "../constants/constants";
import { CoalitionArea } from "./coalitionarea/coalitionarea";
import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
import { DrawingCursor } from "./coalitionarea/drawingcursor";
@@ -70,6 +70,11 @@ export class Map extends L.Map {
#selecting: boolean = false;
#isZooming: boolean = false;
#previousZoom: number = 0;
+ #slaveDCSCamera: boolean = false;
+ #slaveDCSCameraAvailable: boolean = false;
+ #cameraControlTimer: number = 0;
+ #cameraControlPort: number = 3003;
+ #cameraControlMode: string = 'map';
#destinationGroupRotation: number = 0;
#computeDestinationRotation: boolean = false;
@@ -94,7 +99,7 @@ export class Map extends L.Map {
#mapMarkerVisibilityControls: MapMarkerVisibilityControl[] = MAP_MARKER_CONTROLS;
#mapVisibilityOptionsDropdown: Dropdown;
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
- #visibilityOptions: { [key: string]: boolean } = {}
+ #visibilityOptions: { [key: string]: boolean | string | number } = {}
#hiddenTypes: string[] = [];
/**
@@ -158,7 +163,7 @@ export class Map extends L.Map {
this.on('drag', (e: any) => this.#onMouseMove(e));
this.on('keydown', (e: any) => this.#onKeyDown(e));
this.on('keyup', (e: any) => this.#onKeyUp(e));
- this.on('move', (e: any) => this.#broadcastPosition(e));
+ this.on('move', (e: any) => { if (this.#slaveDCSCamera) this.#broadcastPosition() });
/* Event listeners */
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
@@ -202,6 +207,7 @@ export class Map extends L.Map {
document.addEventListener("mapOptionsChanged", () => {
this.getContainer().toggleAttribute("data-hide-labels", !this.getVisibilityOptions()[SHOW_UNIT_LABELS]);
+ this.#cameraControlPort = this.getVisibilityOptions()[DCS_LINK_PORT] as number;
});
document.addEventListener("configLoaded", () => {
@@ -216,6 +222,18 @@ export class Map extends L.Map {
}
})
+ document.addEventListener("toggleCameraLinkStatus", () => {
+ if (this.#slaveDCSCameraAvailable) {
+ this.setSlaveDCSCamera(!this.#slaveDCSCamera);
+ }
+ })
+
+ document.addEventListener("slewCameraToPosition", () => {
+ if (this.#slaveDCSCameraAvailable) {
+ this.#broadcastPosition();
+ }
+ })
+
/* Pan interval */
this.#panInterval = window.setInterval(() => {
if (this.#panUp || this.#panDown || this.#panRight || this.#panLeft)
@@ -223,10 +241,19 @@ export class Map extends L.Map {
((this.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.#deafultPanDelta * (this.#shiftKey ? 3 : 1)));
}, 20);
+ /* Periodically check if the camera control endpoint is available */
+ this.#cameraControlTimer = window.setInterval(() => {
+ this.#checkCameraPort();
+ }, 1000)
+
/* Option buttons */
this.#createUnitMarkerControlButtons();
/* Create the checkboxes to select the advanced visibility options */
+ this.addVisibilityOption(DCS_LINK_PORT, 3003, { min: 1024, max: 65535 });
+
+ this.#mapVisibilityOptionsDropdown.addHorizontalDivider();
+
this.addVisibilityOption(SHOW_UNIT_CONTACTS, false);
this.addVisibilityOption(HIDE_GROUP_MEMBERS, true);
this.addVisibilityOption(SHOW_UNIT_PATHS, true);
@@ -238,9 +265,14 @@ export class Map extends L.Map {
/* this.addVisibilityOption(FILL_SELECTED_RING, false); Removed since currently broken: TODO fix!*/
}
- addVisibilityOption(option: string, defaultValue: boolean) {
+ addVisibilityOption(option: string, defaultValue: boolean | number | string, options?: { [key: string]: any }) {
this.#visibilityOptions[option] = defaultValue;
- this.#mapVisibilityOptionsDropdown.addOptionElement(createCheckboxOption(option, option, defaultValue, (ev: any) => { this.#setVisibilityOption(option, ev); }));
+ if (typeof defaultValue === 'boolean')
+ this.#mapVisibilityOptionsDropdown.addOptionElement(createCheckboxOption(option, option, defaultValue as boolean, (ev: any) => { this.#setVisibilityOption(option, ev); }, options));
+ else if (typeof defaultValue === 'number')
+ this.#mapVisibilityOptionsDropdown.addOptionElement(createTextInputOption(option, option, defaultValue.toString(), 'number', (ev: any) => { this.#setVisibilityOption(option, ev); }, options));
+ else
+ this.#mapVisibilityOptionsDropdown.addOptionElement(createTextInputOption(option, option, defaultValue, 'text', (ev: any) => { this.#setVisibilityOption(option, ev); }, options));
}
setLayer(layerName: string) {
@@ -538,6 +570,21 @@ export class Map extends L.Map {
return this.#mapMarkerVisibilityControls;
}
+ setSlaveDCSCamera(newSlaveDCSCamera: boolean) {
+ if (this.#slaveDCSCameraAvailable || !newSlaveDCSCamera) {
+ this.#slaveDCSCamera = newSlaveDCSCamera;
+ let button = document.getElementById("camera-link-control");
+ button?.classList.toggle("off", !newSlaveDCSCamera);
+ if (newSlaveDCSCamera)
+ this.#broadcastPosition();
+ }
+ }
+
+ setCameraControlMode(newCameraControlMode: string) {
+ this.#cameraControlMode = newCameraControlMode;
+ this.#broadcastPosition();
+ }
+
/* Event handlers */
#onClick(e: any) {
if (!this.#preventLeftClick) {
@@ -720,27 +767,26 @@ export class Map extends L.Map {
this.#isZooming = false;
}
- #broadcastPosition(e: any) {
+ #broadcastPosition() {
getGroundElevation(this.getCenter(), (response: string) => {
var groundElevation: number | null = null;
try {
groundElevation = parseFloat(response);
var xmlHttp = new XMLHttpRequest();
- xmlHttp.open("PUT", "http://localhost:8080");
+ xmlHttp.open("PUT", `http://localhost:${this.#cameraControlPort}`);
xmlHttp.setRequestHeader("Content-Type", "application/json");
- const C = 40075016.686;
+ const C = 40075016.686;
let mpp = C * Math.cos(deg2rad(this.getCenter().lat)) / Math.pow(2, this.getZoom() + 8);
let d = mpp * 1920;
let alt = d / 2 * 1 / Math.tan(deg2rad(40));
if (alt > 100000)
alt = 100000;
- xmlHttp.send(JSON.stringify({lat: this.getCenter().lat, lng: this.getCenter().lng, alt: alt + groundElevation}));
+ xmlHttp.send(JSON.stringify({ lat: this.getCenter().lat, lng: this.getCenter().lng, alt: alt + groundElevation, mode: this.#cameraControlMode }));
} catch {
console.warn("broadcastPosition: could not retrieve ground elevation")
}
});
-
}
/* */
@@ -909,8 +955,47 @@ export class Map extends L.Map {
}
#setVisibilityOption(option: string, ev: any) {
- this.#visibilityOptions[option] = ev.currentTarget.checked;
+ if (typeof this.#visibilityOptions[option] === 'boolean')
+ this.#visibilityOptions[option] = ev.currentTarget.checked;
+ else if (typeof this.#visibilityOptions[option] === 'number')
+ this.#visibilityOptions[option] = Number(ev.currentTarget.value);
+ else
+ this.#visibilityOptions[option] = ev.currentTarget.value;
document.dispatchEvent(new CustomEvent("mapOptionsChanged"));
}
+
+ #setSlaveDCSCameraAvailable(newSlaveDCSCameraAvailable: boolean) {
+ this.#slaveDCSCameraAvailable = newSlaveDCSCameraAvailable;
+ let linkButton = document.getElementById("camera-link-control");
+ if (linkButton) {
+ if (!newSlaveDCSCameraAvailable) {
+ this.setSlaveDCSCamera(false);
+ linkButton.classList.add("red");
+ linkButton.title = "Camera link to DCS is not available";
+ } else {
+ linkButton.classList.remove("red");
+ linkButton.title = "Link/Unlink DCS camera with Olympus position";
+ }
+ }
+ }
+
+ #checkCameraPort(){
+ var xmlHttp = new XMLHttpRequest();
+ xmlHttp.open("OPTIONS", `http://localhost:${this.#cameraControlPort}`);
+ xmlHttp.onload = (res: any) => {
+ if (xmlHttp.status == 200)
+ this.#setSlaveDCSCameraAvailable(true);
+ else
+ this.#setSlaveDCSCameraAvailable(false);
+ };
+ xmlHttp.onerror = (res: any) => {
+ this.#setSlaveDCSCameraAvailable(false);
+ }
+ xmlHttp.ontimeout = (res: any) => {
+ this.#setSlaveDCSCameraAvailable(false);
+ }
+ xmlHttp.timeout = 500;
+ xmlHttp.send("");
+ }
}
diff --git a/frontend/website/src/mission/missionmanager.ts b/frontend/website/src/mission/missionmanager.ts
index fb557e33..0cbe8232 100644
--- a/frontend/website/src/mission/missionmanager.ts
+++ b/frontend/website/src/mission/missionmanager.ts
@@ -118,8 +118,8 @@ export class MissionManager {
}
commandModePhaseEl.classList.toggle("setup-phase", this.#remainingSetupTime > 0 && this.getCommandModeOptions().restrictSpawns);
- commandModePhaseEl.classList.toggle("game-commenced", this.#remainingSetupTime <= 0 || !this.getCommandModeOptions().restrictSpawns);
- commandModePhaseEl.classList.toggle("no-restrictions", !this.getCommandModeOptions().restrictSpawns);
+ //commandModePhaseEl.classList.toggle("game-commenced", this.#remainingSetupTime <= 0 || !this.getCommandModeOptions().restrictSpawns);
+ //commandModePhaseEl.classList.toggle("no-restrictions", !this.getCommandModeOptions().restrictSpawns);
}
}
}
diff --git a/frontend/website/src/other/utils.ts b/frontend/website/src/other/utils.ts
index 0476a610..f8e6d5ff 100644
--- a/frontend/website/src/other/utils.ts
+++ b/frontend/website/src/other/utils.ts
@@ -463,7 +463,7 @@ export function convertDateAndTimeToDate(dateAndTime: DateAndTime) {
return new Date(year, month, date.Day, time.h, time.m, time.s);
}
-export function createCheckboxOption(value: string, text: string, checked: boolean = true, callback: CallableFunction = (ev: any) => {}, options?:any) {
+export function createCheckboxOption(text: string, description: string, checked: boolean = true, callback: CallableFunction = (ev: any) => {}, options?:any) {
options = {
"disabled": false,
"name": "",
@@ -473,16 +473,15 @@ export function createCheckboxOption(value: string, text: string, checked: boole
var div = document.createElement("div");
div.classList.add("ol-checkbox");
var label = document.createElement("label");
- label.title = text;
+ label.title = description;
var input = document.createElement("input");
input.type = "checkbox";
input.checked = checked;
input.name = options.name;
input.disabled = options.disabled;
input.readOnly = options.readOnly;
- input.value = value;
var span = document.createElement("span");
- span.innerText = value;
+ span.innerText = text;
label.appendChild(input);
label.appendChild(span);
div.appendChild(label);
@@ -503,6 +502,45 @@ export function getCheckboxOptions(dropdown: Dropdown) {
return values;
}
+export function createTextInputOption(text: string, description: string, initialValue: string, type: string, callback: CallableFunction = (ev: any) => {}, options?:any) {
+ options = {
+ "disabled": false,
+ "name": "",
+ "readOnly": false,
+ ...options
+ };
+ var div = document.createElement("div");
+ div.classList.add("ol-text-input", "border");
+ var label = document.createElement("label");
+ label.title = description;
+ var input = document.createElement("input");
+ input.type = type;
+ input.name = options.name;
+ input.disabled = options.disabled;
+ input.readOnly = options.readOnly;
+ if (options.min)
+ input.min = options.min;
+ if (options.max)
+ input.max = options.max;
+ input.value = initialValue;
+ input.style.width = "80px";
+ var span = document.createElement("span");
+ span.innerText = text;
+ label.appendChild(span);
+ label.appendChild(input);
+ div.appendChild(label);
+ input.onchange = (ev: any) => {
+ if (type === 'number') {
+ if (Number(input.max) && Number(ev.srcElement.value) > Number(input.max))
+ input.value = input.max;
+ else if (Number(input.min) && Number(ev.srcElement.value) < Number(input.min))
+ input.value = input.min;
+ }
+ callback(ev);
+ }
+ return div as HTMLElement;
+}
+
export function getGroundElevation(latlng: LatLng, callback: CallableFunction) {
/* Get the ground elevation from the server endpoint */
const xhr = new XMLHttpRequest();
diff --git a/frontend/website/src/toolbars/primarytoolbar.ts b/frontend/website/src/toolbars/primarytoolbar.ts
index ad0aa625..a9d477c1 100644
--- a/frontend/website/src/toolbars/primarytoolbar.ts
+++ b/frontend/website/src/toolbars/primarytoolbar.ts
@@ -1,14 +1,21 @@
+import { getApp } from "..";
import { Dropdown } from "../controls/dropdown";
+import { Switch } from "../controls/switch";
import { Toolbar } from "./toolbar";
export class PrimaryToolbar extends Toolbar {
#mainDropdown: Dropdown;
+ #cameraLinkTypeSwitch: Switch;
constructor(ID: string) {
super(ID);
/* The content of the dropdown is entirely defined in the .ejs file */
this.#mainDropdown = new Dropdown("app-icon", () => { });
+
+ this.#cameraLinkTypeSwitch = new Switch("camera-link-type-switch", (value: boolean) => {
+ getApp().getMap().setCameraControlMode(value? 'map': 'live');
+ })
}
getMainDropdown() {
diff --git a/frontend/website/src/unit/unit.ts b/frontend/website/src/unit/unit.ts
index 6166ce41..ab8f8b9a 100644
--- a/frontend/website/src/unit/unit.ts
+++ b/frontend/website/src/unit/unit.ts
@@ -1654,7 +1654,7 @@ export class GroundUnit extends Unit {
/* When we zoom past the grouping limit, grouping is enabled and the unit is a leader, we redraw the unit to apply any possible grouped marker */
checkZoomRedraw(): boolean {
- return (this.getIsLeader() && getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] &&
+ return (this.getIsLeader() && getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] as boolean &&
(getApp().getMap().getZoom() >= GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() < GROUPING_ZOOM_TRANSITION ||
getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() >= GROUPING_ZOOM_TRANSITION))
}
diff --git a/scripts/python/http_example.py b/scripts/python/http_example.py
new file mode 100644
index 00000000..5b857886
--- /dev/null
+++ b/scripts/python/http_example.py
@@ -0,0 +1,23 @@
+import socket
+from email.utils import formatdate
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.bind(('127.0.0.1', 3003))
+sock.listen(5)
+
+count = 0
+while True:
+ connection, address = sock.accept()
+ buf = connection.recv(1024)
+ print(buf.decode("utf-8"))
+ if "OPTIONS" in buf.decode("utf-8"):
+ resp = (f"""HTTP/1.1 200 OK\r\nDate: {formatdate(timeval=None, localtime=False, usegmt=True)}\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: PUT, GET, OPTIONS\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Max-Age: 86400\r\nVary: Accept-Encoding, Origin\r\nKeep-Alive: timeout=2, max=100\r\nConnection: Keep-Alive\r\n""".encode("utf-8"))
+ connection.send(resp)
+ if not "PUT" in buf.decode("utf-8"):
+ connection.close()
+ else:
+ resp = (f"""HTTP/1.1 200 OK\r\nDate: {formatdate(timeval=None, localtime=False, usegmt=True)}\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: PUT, GET, OPTIONS\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Max-Age: 86400\r\nVary: Accept-Encoding, Origin\r\nKeep-Alive: timeout=2, max=100\r\nConnection: Keep-Alive\r\n\r\n{{"Hi": "Wirts!"}}\r\n""".encode("utf-8"))
+ connection.send(resp)
+ connection.close()
+
+ count += 1
\ No newline at end of file