mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
feat: started adding editable laser/infrared
This commit is contained in:
parent
74e2332b17
commit
cc902aec04
@ -264,7 +264,7 @@ string Laser::getString()
|
||||
{
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.laser, "
|
||||
commandSS << "Olympus.fireLaser, "
|
||||
<< ID << ", "
|
||||
<< code << ", "
|
||||
<< destination.lat << ", "
|
||||
@ -277,7 +277,7 @@ string Infrared::getString()
|
||||
{
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.infrared, "
|
||||
commandSS << "Olympus.fireInfrared, "
|
||||
<< ID << ", "
|
||||
<< destination.lat << ", "
|
||||
<< destination.lng;
|
||||
|
||||
@ -128,6 +128,9 @@ void Server::handle_get(http_request request)
|
||||
/* Bullseyes data */
|
||||
else if (URI.compare(BULLSEYE_URI) == 0 && missionData.has_object_field(L"bullseyes"))
|
||||
answer[L"bullseyes"] = missionData[L"bullseyes"];
|
||||
/* Spots (laser/IR) data */
|
||||
else if (URI.compare(SPOTS_URI) == 0 && missionData.has_object_field(L"spots"))
|
||||
answer[L"spots"] = missionData[L"spots"];
|
||||
/* Mission data */
|
||||
else if (URI.compare(MISSION_URI) == 0 && missionData.has_object_field(L"mission")) {
|
||||
answer[L"mission"] = missionData[L"mission"];
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#define LOGS_URI "logs"
|
||||
#define AIRBASES_URI "airbases"
|
||||
#define BULLSEYE_URI "bullseyes"
|
||||
#define SPOTS_URI "spots"
|
||||
#define MISSION_URI "mission"
|
||||
#define COMMANDS_URI "commands"
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ export const WEAPONS_URI = "weapons";
|
||||
export const LOGS_URI = "logs";
|
||||
export const AIRBASES_URI = "airbases";
|
||||
export const BULLSEYE_URI = "bullseyes";
|
||||
export const SPOTS_URI = "spots";
|
||||
export const MISSION_URI = "mission";
|
||||
export const COMMANDS_URI = "commands";
|
||||
|
||||
|
||||
@ -80,6 +80,14 @@ export interface BullseyesData {
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface SpotsData {
|
||||
spots: {
|
||||
[key: string]: { type: string, targetPosition: {lat: number; lng: number}; sourceUnitID: number; code?: number };
|
||||
};
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface MissionData {
|
||||
mission: {
|
||||
theatre: string;
|
||||
|
||||
@ -35,6 +35,7 @@ import { ContextAction } from "../unit/contextaction";
|
||||
import "./markers/stylesheets/airbase.css";
|
||||
import "./markers/stylesheets/bullseye.css";
|
||||
import "./markers/stylesheets/units.css";
|
||||
import "./markers/stylesheets/spot.css";
|
||||
import "./stylesheets/map.css";
|
||||
|
||||
import { initDraggablePath } from "./coalitionarea/draggablepath";
|
||||
|
||||
216
frontend/react/src/map/markers/spoteditmarker.ts
Normal file
216
frontend/react/src/map/markers/spoteditmarker.ts
Normal file
@ -0,0 +1,216 @@
|
||||
import { Marker, LatLng, DivIcon, DomEvent, Map } from "leaflet";
|
||||
|
||||
export class SpotEditMarker extends Marker {
|
||||
#textValue: string;
|
||||
#isEditable: boolean = false;
|
||||
#rotationAngle: number; // Rotation angle in radians
|
||||
#previousValue: string;
|
||||
|
||||
constructor(latlng: LatLng, textValue: string, rotationAngle: number = 0) {
|
||||
super(latlng, {
|
||||
icon: new DivIcon({
|
||||
className: "leaflet-spot-input-marker",
|
||||
html: `<div class="container">
|
||||
<input class="input"/>
|
||||
<div class="text">${textValue}</div>
|
||||
<div class="delete">X</div>
|
||||
</div>`,
|
||||
}),
|
||||
});
|
||||
|
||||
this.#textValue = textValue;
|
||||
this.#rotationAngle = rotationAngle;
|
||||
this.#previousValue = textValue;
|
||||
}
|
||||
|
||||
onAdd(map: Map): this {
|
||||
super.onAdd(map);
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const text = element.querySelector(".text");
|
||||
const button = element.querySelector(".delete");
|
||||
const input = element.querySelector(".input") as HTMLInputElement;
|
||||
|
||||
// Add click event listener to toggle edit mode
|
||||
text?.addEventListener("mousedown", (ev) => this.#toggleEditMode(ev));
|
||||
text?.addEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
|
||||
text?.addEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
|
||||
|
||||
// Add click event listener to delete spot
|
||||
button?.addEventListener("mousedown", (ev) => this.#stopEventPropagation(ev));
|
||||
button?.addEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
|
||||
button?.addEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
|
||||
|
||||
// Add click event listener to input spot
|
||||
input?.addEventListener("mousedown", (ev) => this.#stopEventPropagation(ev));
|
||||
input?.addEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
|
||||
input?.addEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
|
||||
input?.addEventListener("blur", (ev) => this.#toggleEditMode(ev));
|
||||
input?.addEventListener("keydown", (ev) => this.#acceptInput(ev));
|
||||
input?.addEventListener("input", (ev) => this.#validateInput(ev));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
onRemove(map: Map): this {
|
||||
super.onRemove(map);
|
||||
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const text = element.querySelector(".text");
|
||||
const button = element.querySelector(".delete");
|
||||
const input = element.querySelector(".input");
|
||||
|
||||
// Add click event listener to toggle edit mode
|
||||
text?.removeEventListener("mousedown", (ev) => this.#toggleEditMode(ev));
|
||||
text?.removeEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
|
||||
text?.removeEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
|
||||
|
||||
// Add click event listener to delete spot
|
||||
button?.removeEventListener("mousedown", (ev) => this.#stopEventPropagation(ev));
|
||||
button?.removeEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
|
||||
button?.removeEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
|
||||
|
||||
// Add click event listener to input spot
|
||||
input?.removeEventListener("mousedown", (ev) => this.#stopEventPropagation(ev));
|
||||
input?.removeEventListener("mouseup", (ev) => this.#stopEventPropagation(ev));
|
||||
input?.removeEventListener("dblclick", (ev) => this.#stopEventPropagation(ev));
|
||||
input?.removeEventListener("blur", (ev) => this.#toggleEditMode(ev));
|
||||
input?.removeEventListener("keydown", (ev) => this.#acceptInput(ev));
|
||||
input?.removeEventListener("input", (ev) => this.#validateInput(ev));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Method to set the text value
|
||||
setTextValue(textValue: string) {
|
||||
this.#textValue = textValue;
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const text = element.querySelector(".text");
|
||||
if (text) text.textContent = textValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Method to get the text value
|
||||
getTextValue() {
|
||||
return this.#textValue;
|
||||
}
|
||||
|
||||
// Method to set the rotation angle in radians
|
||||
setRotationAngle(angle: number) {
|
||||
this.#rotationAngle = angle;
|
||||
if (!this.#isEditable) {
|
||||
this.#updateRotation();
|
||||
}
|
||||
}
|
||||
|
||||
// Method to get the rotation angle in radians
|
||||
getRotationAngle() {
|
||||
return this.#rotationAngle;
|
||||
}
|
||||
|
||||
// Method to update the rotation angle to ensure the text is always readable
|
||||
#updateRotation() {
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const container = element.querySelector(".container") as HTMLDivElement;
|
||||
if (container) {
|
||||
let angle = this.#rotationAngle % (2 * Math.PI);
|
||||
if (angle < 0) angle += 2 * Math.PI;
|
||||
if (angle > Math.PI / 2 && angle < (3 * Math.PI) / 2) {
|
||||
angle = (angle + Math.PI) % (2 * Math.PI); // Flip the text to be upright
|
||||
}
|
||||
const angleInDegrees = angle * (180 / Math.PI);
|
||||
container.style.transform = `rotate(${angleInDegrees}deg)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#toggleEditMode(ev) {
|
||||
this.#isEditable = !this.#isEditable;
|
||||
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const textElement = element.querySelector(".text");
|
||||
const inputElement = element.querySelector(".input") as HTMLInputElement;
|
||||
const buttonElement = element.querySelector(".delete");
|
||||
const container = element.querySelector(".container") as HTMLDivElement;
|
||||
if (this.#isEditable) {
|
||||
// Rotate to horizontal and make text editable
|
||||
if (textElement && inputElement && buttonElement) {
|
||||
textElement.classList.add("edit-mode");
|
||||
inputElement.style.display = "block";
|
||||
inputElement.value = this.#textValue;
|
||||
inputElement.focus();
|
||||
buttonElement.textContent = "✔"; // Change to check mark
|
||||
buttonElement.classList.add("edit-mode");
|
||||
}
|
||||
container.style.transform = `rotate(0deg)`;
|
||||
} else {
|
||||
// Save the edited text and revert to normal mode
|
||||
if (textElement && inputElement && buttonElement) {
|
||||
const newText = inputElement.value || this.#textValue;
|
||||
this.#textValue = newText;
|
||||
textElement.classList.remove("edit-mode");
|
||||
inputElement.style.display = "none";
|
||||
buttonElement.textContent = "X"; // Change to delete mark
|
||||
buttonElement.classList.remove("edit-mode");
|
||||
}
|
||||
this.#updateRotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#stopEventPropagation(ev) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
#validateInput(ev) {
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const input = ev.target as HTMLInputElement;
|
||||
const text = element.querySelector(".text");
|
||||
|
||||
// Validate the input value
|
||||
const value = input.value;
|
||||
|
||||
// Check if the value is a partial valid input
|
||||
const isPartialValid = /^[1]$|^[1][1-7]$|^[1][1-7][1-8]$|^[1][1-7][1-8][1-8]$/.test(value);
|
||||
|
||||
// Check if the value is a complete valid input
|
||||
const isValid = /^[1][1-7][1-8][1-8]$/.test(value) && Number(value) <= 1788;
|
||||
|
||||
if (isPartialValid || isValid) {
|
||||
if (text) text.textContent = value;
|
||||
this.#previousValue = value;
|
||||
} else {
|
||||
this.#errorFunction();
|
||||
input.value = this.#previousValue;
|
||||
if (text) text.textContent = this.#previousValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#errorFunction() {
|
||||
const element = this.getElement();
|
||||
if (element) {
|
||||
const input = element.querySelector(".input") as HTMLInputElement;
|
||||
if (input) {
|
||||
input.classList.add("error-flash");
|
||||
setTimeout(() => {
|
||||
input.classList.remove("error-flash");
|
||||
}, 300); // Duration of the flash effect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#acceptInput(ev) {
|
||||
if (ev.key === "Enter") this.#toggleEditMode(ev);
|
||||
}
|
||||
}
|
||||
23
frontend/react/src/map/markers/spotmarker.ts
Normal file
23
frontend/react/src/map/markers/spotmarker.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
|
||||
export class SpotMarker extends CustomMarker {
|
||||
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
|
||||
super(latlng, options);
|
||||
this.options.interactive = false;
|
||||
this.setZIndexOffset(9999);
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
this.setIcon(
|
||||
new DivIcon({
|
||||
iconSize: [52, 52],
|
||||
iconAnchor: [26, 26],
|
||||
className: "leaflet-spot-marker",
|
||||
})
|
||||
);
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("ol-spot-icon");
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
}
|
||||
90
frontend/react/src/map/markers/stylesheets/spot.css
Normal file
90
frontend/react/src/map/markers/stylesheets/spot.css
Normal file
@ -0,0 +1,90 @@
|
||||
.leaflet-spot-input-marker {
|
||||
text-align: center;
|
||||
display: flex !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .delete {
|
||||
background-color: darkred;
|
||||
color: #fffd;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
font-weight: bold;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .delete:hover {
|
||||
background-color: lightcoral;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .container {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
transform-origin: center;
|
||||
background-color: var(--background-steel);
|
||||
color: white;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
column-gap: 6px;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .text {
|
||||
margin-left: 12px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .input {
|
||||
display: none;
|
||||
color: white;
|
||||
appearance: none; /* Remove default appearance */
|
||||
background-color: #333; /* Dark background */
|
||||
border-radius: 8px; /* Rounded borders */
|
||||
border: 1px solid #555; /* Border color */
|
||||
padding: 4px 8px; /* Padding for better appearance */
|
||||
outline: none; /* Remove default outline */
|
||||
font-size: 13px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .input:focus {
|
||||
border-color: #777; /* Border color on focus */
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); /* Shadow on focus */
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .container .input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .container .text.edit-mode {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .input.error-flash {
|
||||
animation: error-flash 0.3s;
|
||||
}
|
||||
|
||||
@keyframes error-flash {
|
||||
0% {
|
||||
border-color: #555;
|
||||
}
|
||||
50% {
|
||||
border-color: red;
|
||||
}
|
||||
100% {
|
||||
border-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .delete.edit-mode {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.leaflet-spot-input-marker .delete.edit-mode:hover {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
@ -141,6 +141,12 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ol-spot-icon {
|
||||
background-image: url("/images/markers/target.svg");
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ol-text-icon {
|
||||
color: #111111;
|
||||
text-align: center;
|
||||
|
||||
@ -3,14 +3,16 @@ import { getApp } from "../olympusapp";
|
||||
import { Airbase } from "./airbase";
|
||||
import { Bullseye } from "./bullseye";
|
||||
import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
|
||||
import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionData } from "../interfaces";
|
||||
import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionData, SpotsData } from "../interfaces";
|
||||
import { Coalition } from "../types/types";
|
||||
import { Carrier } from "./carrier";
|
||||
import { AirbaseSelectedEvent, AppStateChangedEvent, BullseyesDataChangedEvent, CommandModeOptionsChangedEvent, EnabledCommandModesChangedEvent, MissionDataChangedEvent } from "../events";
|
||||
import { Spot } from "./spot";
|
||||
|
||||
/** The MissionManager */
|
||||
export class MissionManager {
|
||||
#bullseyes: { [name: string]: Bullseye } = {};
|
||||
#spots: {[key: string]: Spot} = {};
|
||||
#airbases: { [name: string]: Airbase | Carrier } = {};
|
||||
#theatre: string = "";
|
||||
#dateAndTime: DateAndTime = {
|
||||
@ -65,6 +67,16 @@ export class MissionManager {
|
||||
}
|
||||
}
|
||||
|
||||
updateSpots(data: SpotsData) {
|
||||
for (let idx in data.spots) {
|
||||
const spotID = Number(idx);
|
||||
if (this.#spots[spotID] === undefined) {
|
||||
const spot = data.spots[idx];
|
||||
this.#spots[spotID] = new Spot(spotID, spot.type, new LatLng(spot.targetPosition.lat, spot.targetPosition.lng), spot.sourceUnitID, spot.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Update airbase information
|
||||
*
|
||||
* @param object <AirbasesData>
|
||||
@ -134,6 +146,10 @@ export class MissionManager {
|
||||
return this.#airbases;
|
||||
}
|
||||
|
||||
getSpots() {
|
||||
return this.#spots;
|
||||
}
|
||||
|
||||
/** Get the options/settings as set in the command mode
|
||||
*
|
||||
* @returns object
|
||||
|
||||
48
frontend/react/src/mission/spot.ts
Normal file
48
frontend/react/src/mission/spot.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getApp } from "../olympusapp";
|
||||
|
||||
export class Spot {
|
||||
private ID: number;
|
||||
private type: string;
|
||||
private targetPosition: LatLng;
|
||||
private sourceUnitID: number;
|
||||
private code?: number;
|
||||
|
||||
constructor(ID: number, type: string, targetPosition: LatLng, sourceUnitID: number, code?: number) {
|
||||
this.ID = ID;
|
||||
this.type = type;
|
||||
this.targetPosition = targetPosition;
|
||||
this.sourceUnitID = sourceUnitID;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
// Getter methods
|
||||
getID() {
|
||||
return this.ID;
|
||||
}
|
||||
|
||||
getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
getTargetPosition() {
|
||||
return this.targetPosition;
|
||||
}
|
||||
|
||||
getSourceUnitID() {
|
||||
return this.sourceUnitID;
|
||||
}
|
||||
|
||||
getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
// Setter methods
|
||||
setTargetPosition(position: LatLng) {
|
||||
this.targetPosition = position;
|
||||
}
|
||||
|
||||
setCode(code: number) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
@ -8,12 +8,13 @@ import {
|
||||
MISSION_URI,
|
||||
NONE,
|
||||
ROEs,
|
||||
SPOTS_URI,
|
||||
UNITS_URI,
|
||||
WEAPONS_URI,
|
||||
emissionsCountermeasures,
|
||||
reactionsToThreat,
|
||||
} from "../constants/constants";
|
||||
import { AirbasesData, BullseyesData, CommandModeOptions, GeneralSettings, MissionData, Radio, ServerRequestOptions, ServerStatus, TACAN } from "../interfaces";
|
||||
import { AirbasesData, BullseyesData, CommandModeOptions, GeneralSettings, MissionData, Radio, ServerRequestOptions, ServerStatus, SpotsData, TACAN } from "../interfaces";
|
||||
import { MapOptionsChangedEvent, ServerStatusUpdatedEvent, WrongCredentialsEvent } from "../events";
|
||||
|
||||
export class ServerManager {
|
||||
@ -180,10 +181,14 @@ export class ServerManager {
|
||||
this.GET(callback, errorCallback, AIRBASES_URI);
|
||||
}
|
||||
|
||||
getBullseye(callback: CallableFunction, errorCallback: CallableFunction = () => {}) {
|
||||
getBullseyes(callback: CallableFunction, errorCallback: CallableFunction = () => {}) {
|
||||
this.GET(callback, errorCallback, BULLSEYE_URI);
|
||||
}
|
||||
|
||||
getSpots(callback: CallableFunction, errorCallback: CallableFunction = () => {}) {
|
||||
this.GET(callback, errorCallback, SPOTS_URI);
|
||||
}
|
||||
|
||||
getLogs(callback: CallableFunction, refresh: boolean = false, errorCallback: CallableFunction = () => {}) {
|
||||
this.GET(callback, errorCallback, LOGS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[LOGS_URI] }, "text", refresh);
|
||||
}
|
||||
@ -576,7 +581,7 @@ export class ServerManager {
|
||||
this.#intervals.push(
|
||||
window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
|
||||
this.getBullseye((data: BullseyesData) => {
|
||||
this.getBullseyes((data: BullseyesData) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
getApp().getMissionManager()?.updateBullseyes(data);
|
||||
return data.time;
|
||||
@ -585,6 +590,18 @@ export class ServerManager {
|
||||
}, 10000)
|
||||
);
|
||||
|
||||
this.#intervals.push(
|
||||
window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
|
||||
this.getSpots((data: SpotsData) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
getApp().getMissionManager()?.updateSpots(data);
|
||||
return data.time;
|
||||
});
|
||||
}
|
||||
}, 2000)
|
||||
);
|
||||
|
||||
this.#intervals.push(
|
||||
window.setInterval(() => {
|
||||
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
|
||||
@ -670,7 +687,7 @@ export class ServerManager {
|
||||
return data.time;
|
||||
});
|
||||
|
||||
this.getBullseye((data: BullseyesData) => {
|
||||
this.getBullseyes((data: BullseyesData) => {
|
||||
this.checkSessionHash(data.sessionHash);
|
||||
getApp().getMissionManager()?.updateBullseyes(data);
|
||||
return data.time;
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
} from "../components/olicons";
|
||||
import { FaChevronLeft, FaChevronRight, FaFloppyDisk } from "react-icons/fa6";
|
||||
import { CommandModeOptionsChangedEvent, ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent, SessionDataChangedEvent, SessionDataSavedEvent } from "../../events";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, ImportExportSubstate, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, OlympusState } from "../../constants/constants";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, ImportExportSubstate, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, OlympusState, RED_COMMANDER } from "../../constants/constants";
|
||||
import { OlympusConfig } from "../../interfaces";
|
||||
import { FaCheck, FaSave, FaSpinner } from "react-icons/fa";
|
||||
|
||||
@ -132,6 +132,11 @@ export function Header() {
|
||||
<span className="my-auto font-bold">BLUE Commander ({commandModeOptions.spawnPoints.blue} points)</span>
|
||||
</div>
|
||||
)}
|
||||
{commandModeOptions.commandMode === RED_COMMANDER && (
|
||||
<div className={`flex h-full rounded-md bg-red-600 px-4 text-white`}>
|
||||
<span className="my-auto font-bold">BLUE Commander ({commandModeOptions.spawnPoints.blue} points)</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={`flex h-fit flex-row items-center justify-start gap-1`}>
|
||||
<OlLockStateButton
|
||||
checked={!mapOptions.protectDCSUnits}
|
||||
|
||||
@ -68,6 +68,9 @@ import {
|
||||
} from "../events";
|
||||
import { CoalitionAreaHandle } from "../map/coalitionarea/coalitionareahandle";
|
||||
import { ArrowMarker } from "../map/markers/arrowmarker";
|
||||
import { Spot } from "../mission/spot";
|
||||
import { SpotEditMarker } from "../map/markers/spoteditmarker";
|
||||
import { SpotMarker } from "../map/markers/spotmarker";
|
||||
|
||||
const bearingStrings = ["north", "north-east", "east", "south-east", "south", "south-west", "west", "north-west", "north"];
|
||||
|
||||
@ -177,6 +180,9 @@ export abstract class Unit extends CustomMarker {
|
||||
#racetrackAnchorMarkers: CoalitionAreaHandle[] = [new CoalitionAreaHandle(new LatLng(0, 0)), new CoalitionAreaHandle(new LatLng(0, 0))];
|
||||
#racetrackArrow: ArrowMarker = new ArrowMarker(new LatLng(0, 0));
|
||||
#inhibitRacetrackDraw: boolean = false;
|
||||
#spots: { [key: number]: Polyline } = {};
|
||||
#spotEditMarkers: { [key: number]: SpotEditMarker } = {};
|
||||
#spotMarkers: { [key: number]: SpotMarker } = {};
|
||||
|
||||
/* Inputs timers */
|
||||
#debounceTimeout: number | null = null;
|
||||
@ -779,6 +785,7 @@ export abstract class Unit extends CustomMarker {
|
||||
this.#clearPath();
|
||||
this.#clearTargetPosition();
|
||||
this.#clearRacetrack();
|
||||
this.#clearSpots();
|
||||
}
|
||||
|
||||
/* When the group leader is selected, if grouping is active, all the other group members are also selected */
|
||||
@ -928,6 +935,7 @@ export abstract class Unit extends CustomMarker {
|
||||
this.#drawRacetrack();
|
||||
this.#drawContacts();
|
||||
this.#drawTarget();
|
||||
this.#drawSpots();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1740,8 +1748,7 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
/* Add racetrack anchor to the path, but only if we are an active tanker or AWACS */
|
||||
if (this.getState() !== UnitState.IDLE && (this.getIsActiveAWACS() || this.getIsActiveTanker()))
|
||||
points.push(this.#racetrackAnchor);
|
||||
if (this.getState() !== UnitState.IDLE && (this.getIsActiveAWACS() || this.getIsActiveTanker())) points.push(this.#racetrackAnchor);
|
||||
|
||||
this.#pathPolyline.setLatLngs(points);
|
||||
|
||||
@ -1859,6 +1866,43 @@ export abstract class Unit extends CustomMarker {
|
||||
});
|
||||
}
|
||||
|
||||
#drawSpots() {
|
||||
Object.values(getApp().getMissionManager().getSpots()).forEach((spot: Spot) => {
|
||||
if (spot.getSourceUnitID() === this.ID) {
|
||||
const spotBearing = deg2rad(bearing(this.getPosition().lat, this.getPosition().lng, spot.getTargetPosition().lat, spot.getTargetPosition().lng, false));
|
||||
const spotDistance = this.getPosition().distanceTo(spot.getTargetPosition());
|
||||
const midPosition = bearingAndDistanceToLatLng(this.getPosition().lat, this.getPosition().lng, spotBearing, spotDistance / 2);
|
||||
if (this.#spots[spot.getID()] === undefined) {
|
||||
this.#spots[spot.getID()] = new Polyline([this.getPosition(), spot.getTargetPosition()], {
|
||||
color: spot.getType() === "laser" ? colors.BLUE_VIOLET : colors.DARK_RED,
|
||||
dashArray: "1, 8",
|
||||
});
|
||||
this.#spots[spot.getID()].addTo(getApp().getMap());
|
||||
this.#spotEditMarkers[spot.getID()] = new SpotEditMarker(midPosition, `${spot.getCode() ?? ""}`);
|
||||
this.#spotEditMarkers[spot.getID()].addTo(getApp().getMap());
|
||||
this.#spotEditMarkers[spot.getID()].setRotationAngle(spotBearing + Math.PI / 2);
|
||||
this.#spotMarkers[spot.getID()] = new SpotMarker(spot.getTargetPosition());
|
||||
} else {
|
||||
if (!getApp().getMap().hasLayer(this.#spots[spot.getID()])) this.#spots[spot.getID()].addTo(getApp().getMap());
|
||||
if (!getApp().getMap().hasLayer(this.#spotEditMarkers[spot.getID()])) this.#spotEditMarkers[spot.getID()].addTo(getApp().getMap());
|
||||
if (!getApp().getMap().hasLayer(this.#spotMarkers[spot.getID()])) this.#spotMarkers[spot.getID()].addTo(getApp().getMap());
|
||||
|
||||
this.#spots[spot.getID()].setLatLngs([this.getPosition(), spot.getTargetPosition()]);
|
||||
this.#spotEditMarkers[spot.getID()].setLatLng(midPosition);
|
||||
this.#spotMarkers[spot.getID()].setLatLng(spot.getTargetPosition());
|
||||
}
|
||||
this.#spotEditMarkers[spot.getID()].setRotationAngle(spotBearing + Math.PI / 2);
|
||||
this.#spotEditMarkers[spot.getID()].setTextValue(`${spot.getCode() ?? ""}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#clearSpots() {
|
||||
Object.values(this.#spots).forEach((spot) => getApp().getMap().removeLayer(spot));
|
||||
Object.values(this.#spotEditMarkers).forEach((spotEditMarker) => getApp().getMap().removeLayer(spotEditMarker));
|
||||
Object.values(this.#spotMarkers).forEach((spotMarker) => getApp().getMap().removeLayer(spotMarker));
|
||||
}
|
||||
|
||||
#drawContacts() {
|
||||
this.#clearContacts();
|
||||
if (getApp().getMap().getOptions().showUnitContacts) {
|
||||
|
||||
@ -602,6 +602,7 @@ function Olympus.fireInfrared(ID, lat, lng)
|
||||
local unit = Olympus.getUnitByID(ID)
|
||||
if unit ~= nil and unit:isExist() then
|
||||
local spot = Spot.createInfraRed(unit, {x = 0, y = 1, z = 0}, vec3)
|
||||
Olympus.spotsCounter = Olympus.spotsCounter + 1
|
||||
Olympus.spots[Olympus.spotsCounter] = {
|
||||
type = "infrared",
|
||||
object = spot,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user