feat: completed orbit management

This commit is contained in:
Davide Passoni
2025-01-28 09:47:44 +01:00
parent c2ea746d48
commit 79f9905413
17 changed files with 241 additions and 60 deletions

View File

@@ -3,12 +3,12 @@
"short_name": "DCS Olympus",
"icons": [
{
"src": "./images/favicons/android-chrome-192x192.png",
"src": "images/favicons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "./images/favicons/android-chrome-512x512.png",
"src": "images/favicons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}

View File

@@ -0,0 +1,34 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker";
import { SVGInjector } from "@tanem/svg-injector";
export class ArrowMarker extends CustomMarker {
#bearing: number = 0;
constructor(latlng: LatLng) {
super(latlng, { interactive: true, draggable: false });
}
createIcon() {
this.setIcon(
new DivIcon({
iconSize: [24, 24],
iconAnchor: [12, 12],
className: "leaflet-arrow-marker",
})
);
var div = document.createElement("div");
var img = document.createElement("img");
img.src = "images/others/arrow.svg";
img.onload = () => SVGInjector(img);
div.classList.add("ol-arrow-icon");
div.append(img);
this.getElement()?.appendChild(div);
}
setBearing(bearing: number) {
this.#bearing = bearing;
let img = this.getElement()?.querySelector("svg");
if (img) img.style.transform = `rotate(${bearing}rad)`;
}
}

View File

@@ -31,7 +31,7 @@ export class ExplosionMarker extends CustomMarker {
var el = document.createElement("div");
el.classList.add("ol-explosion-icon");
var img = document.createElement("img");
img.src = "./images/markers/explosion.svg";
img.src = "images/markers/explosion.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);

View File

@@ -28,7 +28,7 @@ export class SmokeMarker extends CustomMarker {
el.classList.add("ol-smoke-icon");
el.setAttribute("data-color", this.#color);
var img = document.createElement("img");
img.src = "./images/markers/smoke.svg";
img.src = "images/markers/smoke.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);

View File

@@ -60,7 +60,7 @@ export class TemporaryUnitMarker extends CustomMarker {
var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon");
var img = document.createElement("img");
img.src = `./images/units/map/${/*TODO getApp().getMap().getOptions().AWACSMode ? "awacs" :*/ "normal"}/${this.#coalition}/${blueprint.markerFile ?? blueprint.category}.svg`;
img.src = `images/units/map/${/*TODO getApp().getMap().getOptions().AWACSMode ? "awacs" :*/ "normal"}/${this.#coalition}/${blueprint.markerFile ?? blueprint.category}.svg`;
img.onload = () => SVGInjector(img);
unitIcon.appendChild(img);
unitIcon.toggleAttribute("data-rotate-to-heading", false);
@@ -78,7 +78,7 @@ export class TemporaryUnitMarker extends CustomMarker {
if (this.#headingHandle) {
var handle = document.createElement("div");
var handleImg = document.createElement("img");
handleImg.src = "/images/others/arrow.svg";
handleImg.src = "images/others/arrow.svg";
handleImg.onload = () => SVGInjector(handleImg);
handle.classList.add("heading-handle");
el.append(handle);

View File

@@ -253,4 +253,14 @@ path.leaflet-interactive:focus {
.pointer-cursor {
cursor: url("/images/cursors/pointer.svg") 13 5, auto !important;
}
}
.ol-arrow-icon svg {
width: 24px;
height: 24px;
}
.ol-arrow-icon svg path {
fill: #FFFFFFAA;
}

View File

@@ -69,7 +69,7 @@ export class Airbase extends CustomMarker {
el.classList.add("airbase-icon");
el.setAttribute("data-object", "airbase");
this.#img.src = "./images/markers/airbase.svg";
this.#img.src = "images/markers/airbase.svg";
this.#img.onload = () => SVGInjector(this.#img);
el.appendChild(this.#img);
this.getElement()?.appendChild(el);

View File

@@ -23,7 +23,7 @@ export class Bullseye extends CustomMarker {
el.classList.add("bullseye-icon");
el.setAttribute("data-object", "bullseye");
var img = document.createElement("img");
img.src = "./images/markers/bullseye.svg";
img.src = "images/markers/bullseye.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);

View File

@@ -488,6 +488,12 @@ export class ServerManager {
this.PUT(data, callback);
}
setRacetrack(ID: number, length: number, latlng: LatLng, bearing: number, callback: CallableFunction = () => {}) {
var command = { ID: ID, location: latlng, bearing: bearing, length: length };
var data = { setRacetrack: command };
this.PUT(data, callback);
}
setAdvancedOptions(
ID: number,
isActiveTanker: boolean,

View File

@@ -94,7 +94,7 @@ export function LoginModal(props: { open: boolean }) {
max-md:border-none
`}
>
<img src="./images/splash/1.jpg" className={`
<img src="images/splash/1.jpg" className={`
contents-center w-full object-cover opacity-[7%]
`}></img>
<div
@@ -154,7 +154,7 @@ export function LoginModal(props: { open: boolean }) {
`}
>
<span className="size-[80px] min-w-14">
<img src="./images/olympus-500x500.png" className={`
<img src="images/olympus-500x500.png" className={`
flex w-full
`}></img>
</span>
@@ -360,7 +360,7 @@ export function LoginModal(props: { open: boolean }) {
>
<Card className="flex">
<img
src="./images/splash/1.jpg"
src="images/splash/1.jpg"
className={`
h-[40%] max-h-[120px] contents-center w-full rounded-md
object-cover
@@ -385,7 +385,7 @@ export function LoginModal(props: { open: boolean }) {
</Card>
<Card className="flex">
<img
src="./images/splash/1.jpg"
src="images/splash/1.jpg"
className={`
h-[40%] max-h-[120px] contents-center w-full rounded-md
object-cover

View File

@@ -75,7 +75,7 @@ export function Header() {
dark:border-gray-800 dark:bg-olympus-900
`}
>
<img src="./images/icon.png" className={`my-auto h-10 w-10 rounded-md p-0`}></img>
<img src="images/icon.png" className={`my-auto h-10 w-10 rounded-md p-0`}></img>
{!scrolledLeft && (
<FaChevronLeft
className={`

View File

@@ -479,12 +479,12 @@ export function UnitSpawnMenu(props: {
/>
<div className={`relative mr-3 h-[60px] w-[60px]`}>
<img className="absolute" ref={compassRef} onMouseDown={handleMouseDown} src={"/images/others/arrow_background.png"}></img>
<img className="absolute" ref={compassRef} onMouseDown={handleMouseDown} src={"images/others/arrow_background.png"}></img>
<img
className="absolute left-0"
ref={compassRef}
onMouseDown={handleMouseDown}
src={"/images/others/arrow.png"}
src={"images/others/arrow.png"}
style={{
width: "60px",
height: "60px",

View File

@@ -65,7 +65,7 @@ export function ServerOverlay() {
</div>
</div>
<img src="./images/olympus-500x500.png" className={`
<img src="images/olympus-500x500.png" className={`
absolute right-4 top-4 ml-auto flex h-24
`}></img>
</div>

View File

@@ -18,7 +18,6 @@ import {
computeBearingRangeString,
adjustBrightness,
bearingAndDistanceToLatLng,
mToNm,
} from "../other/utils";
import { CustomMarker } from "../map/markers/custommarker";
import { SVGInjector } from "@tanem/svg-injector";
@@ -67,12 +66,14 @@ import {
UnitSelectedEvent,
UnitUpdatedEvent,
} from "../events";
import { CoalitionAreaHandle } from "../map/coalitionarea/coalitionareahandle";
import { ArrowMarker } from "../map/markers/arrowmarker";
const bearingStrings = ["north", "north-east", "east", "south-east", "south", "south-west", "west", "north-west", "north"];
var pathIcon = new Icon({
iconUrl: "./images/markers/marker-icon.png",
shadowUrl: "./images/markers/marker-shadow.png",
iconUrl: "images/markers/marker-icon.png",
shadowUrl: "images/markers/marker-shadow.png",
iconAnchor: [13, 41],
});
@@ -171,9 +172,11 @@ export abstract class Unit extends CustomMarker {
#detectionMethods: number[] = [];
#trailPositions: LatLng[] = [];
#trailPolylines: Polyline[] = [];
#racetrackPolylines: Polyline[] = [new Polyline([]), new Polyline([])];
#racetrackArcs: Polyline[] = [new Polyline([]), new Polyline([])];
#anchorMarkers: Marker[];
#racetrackPolylines: Polyline[] = [];
#racetrackArcs: Polyline[] = [];
#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;
/* Inputs timers */
#debounceTimeout: number | null = null;
@@ -323,7 +326,7 @@ export abstract class Unit extends CustomMarker {
getRaceTrackLength() {
return this.#racetrackLength;
}
getRaceTrackAnchor() {
getRaceTrackAnchor() {
return this.#racetrackAnchor;
}
getRaceTrackBearing() {
@@ -343,7 +346,7 @@ export abstract class Unit extends CustomMarker {
this.ID = ID;
this.#pathPolyline = new Polyline([], {
color: colors.STEEL_BLUE,
color: colors.OLYMPUS_BLUE,
weight: 3,
opacity: 0.5,
smoothFactor: 1,
@@ -375,6 +378,33 @@ export abstract class Unit extends CustomMarker {
bubblingMouseEvents: false,
});
this.#racetrackPolylines = [
new Polyline([], { color: colors.OLYMPUS_BLUE, weight: 3, opacity: 0.5, smoothFactor: 1 }),
new Polyline([], { color: colors.OLYMPUS_BLUE, weight: 3, opacity: 0.5, smoothFactor: 1 }),
];
this.#racetrackArcs = [
new Polyline([], { color: colors.OLYMPUS_BLUE, weight: 3, opacity: 0.5, smoothFactor: 1 }),
new Polyline([], { color: colors.OLYMPUS_BLUE, weight: 3, opacity: 0.5, smoothFactor: 1 }),
];
this.#racetrackAnchorMarkers[0].on("drag", (e: any) => {
this.#inhibitRacetrackDraw = true;
this.#drawRacetrack();
});
this.#racetrackAnchorMarkers[1].on("drag", (e: any) => {
this.#inhibitRacetrackDraw = true;
this.#drawRacetrack();
});
this.#racetrackAnchorMarkers[0].on("dragend", (e: any) => {
this.#applyRacetrackEdit();
});
this.#racetrackAnchorMarkers[1].on("dragend", (e: any) => {
this.#applyRacetrackEdit();
});
/* Leaflet events listeners */
this.on("mouseup", (e: any) => this.#onMouseUp(e));
this.on("mousedown", (e: any) => this.#onMouseDown(e));
@@ -767,6 +797,8 @@ export abstract class Unit extends CustomMarker {
/* Trigger events after all (de-)selecting has been done */
selected ? UnitSelectedEvent.dispatch(this) : UnitDeselectedEvent.dispatch(this);
this.#inhibitRacetrackDraw = false;
}
}
@@ -1339,6 +1371,10 @@ export abstract class Unit extends CustomMarker {
if (!this.#human) getApp().getServerManager().setShotsIntensity(this.ID, shotsIntensity);
}
setRacetrack(length: number, anchor: LatLng, bearing: number, callback: () => void) {
if (!this.#human) getApp().getServerManager().setRacetrack(this.ID, length, anchor, bearing, callback);
}
/***********************************************/
onAdd(map: Map): this {
super.onAdd(map);
@@ -1687,13 +1723,18 @@ export abstract class Unit extends CustomMarker {
for (let WP in this.#activePath) {
var destination = this.#activePath[WP];
var frozen = this.#pathMarkers[parseInt(WP)].options["freeze"];
if (!this.#pathMarkers[parseInt(WP)].options["freeze"]) {
if (!frozen) {
this.#pathMarkers[parseInt(WP)].setLatLng([destination.lat, destination.lng]);
}
points.push(new LatLng(destination.lat, destination.lng));
this.#pathPolyline.setLatLngs(points);
}
/* 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);
this.#pathPolyline.setLatLngs(points);
if (points.length == 1) this.#clearPath();
} else {
this.#clearPath();
@@ -1712,63 +1753,100 @@ export abstract class Unit extends CustomMarker {
#drawRacetrack() {
let groundspeed = this.#speed;
// Determine racetrack length
let racetrackLength = this.#racetrackLength;
if (racetrackLength === 0) {
if (this.getIsActiveTanker())
racetrackLength = nmToM(50);
else
racetrackLength = this.#desiredSpeed * 30;
if (this.getIsActiveTanker())
racetrackLength = nmToM(50); // Default length for active tanker
else racetrackLength = this.#desiredSpeed * 30; // Default length based on desired speed
}
// Calculate the radius of the racetrack turns
const radius = Math.pow(groundspeed, 2) / 9.81 / Math.tan(deg2rad(22.5));
const point1 = this.#racetrackAnchor;
const point2 = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing, racetrackLength);
let point1;
let point2;
// Determine the anchor points of the racetrack
if (!this.#inhibitRacetrackDraw) {
point1 = this.#racetrackAnchor;
point2 = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing, racetrackLength);
} else {
point1 = this.#racetrackAnchorMarkers[0].getLatLng();
point2 = this.#racetrackAnchorMarkers[1].getLatLng();
this.#racetrackBearing = deg2rad(bearing(point1.lat, point1.lng, point2.lat, point2.lng, false));
this.#racetrackLength = point1.distanceTo(point2);
}
// Calculate the other points of the racetrack
const point3 = bearingAndDistanceToLatLng(point2.lat, point2.lng, this.#racetrackBearing - deg2rad(90), radius * 2);
const point4 = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing - deg2rad(90), radius * 2);
const pointArrow = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing, racetrackLength / 2);
// Calculate the centers of the racetrack turns
const center1 = bearingAndDistanceToLatLng(point2.lat, point2.lng, this.#racetrackBearing - deg2rad(90), radius);
const center2 = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing - deg2rad(90), radius);
if (!getApp().getMap().hasLayer(this.#racetrackPolylines[0])) {
this.#racetrackPolylines[0] = new Polyline([point1, point2]);
this.#racetrackPolylines[0].addTo(getApp().getMap());
} else {
this.#racetrackPolylines[0].setLatLngs([point1, point2]);
}
// Draw or update the straight segments of the racetrack
if (!getApp().getMap().hasLayer(this.#racetrackPolylines[0])) this.#racetrackPolylines[0].addTo(getApp().getMap());
this.#racetrackPolylines[0].setLatLngs([point1, point2]);
if (!getApp().getMap().hasLayer(this.#racetrackPolylines[1])) {
this.#racetrackPolylines[1] = new Polyline([point3, point4]);
this.#racetrackPolylines[1].addTo(getApp().getMap());
} else {
this.#racetrackPolylines[1].setLatLngs([point3, point4]);
}
if (!getApp().getMap().hasLayer(this.#racetrackPolylines[1])) this.#racetrackPolylines[1].addTo(getApp().getMap());
this.#racetrackPolylines[1].setLatLngs([point3, point4]);
const arc1Points: LatLng[] = [];
const arc2Points: LatLng[] = [];
// Calculate the points for the racetrack arcs
for (let theta = 0; theta <= 180; theta += 5) {
arc1Points.push(bearingAndDistanceToLatLng(center1.lat, center1.lng, this.#racetrackBearing + deg2rad(theta - 90), radius));
arc2Points.push(bearingAndDistanceToLatLng(center2.lat, center2.lng, this.#racetrackBearing + deg2rad(theta + 90), radius));
}
if (!getApp().getMap().hasLayer(this.#racetrackArcs[0])) {
this.#racetrackArcs[0] = new Polyline(arc1Points);
this.#racetrackArcs[0].addTo(getApp().getMap());
} else {
this.#racetrackArcs[0].setLatLngs(arc1Points);
// Draw or update the racetrack arcs
if (!getApp().getMap().hasLayer(this.#racetrackArcs[0])) this.#racetrackArcs[0].addTo(getApp().getMap());
this.#racetrackArcs[0].setLatLngs(arc1Points);
if (!getApp().getMap().hasLayer(this.#racetrackArcs[1])) this.#racetrackArcs[1].addTo(getApp().getMap());
this.#racetrackArcs[1].setLatLngs(arc2Points);
// Update the positions of the racetrack anchor markers
this.#racetrackAnchorMarkers[0].setLatLng(point1);
this.#racetrackAnchorMarkers[1].setLatLng(point2);
// Add the racetrack anchor markers to the map if they are not already present
if (!getApp().getMap().hasLayer(this.#racetrackAnchorMarkers[0])) {
this.#racetrackAnchorMarkers[0].addTo(getApp().getMap());
}
if (!getApp().getMap().hasLayer(this.#racetrackArcs[1])) {
this.#racetrackArcs[1] = new Polyline(arc2Points);
this.#racetrackArcs[1].addTo(getApp().getMap());
} else {
this.#racetrackArcs[1].setLatLngs(arc2Points);
if (!getApp().getMap().hasLayer(this.#racetrackAnchorMarkers[1])) {
this.#racetrackAnchorMarkers[1].addTo(getApp().getMap());
}
if (!getApp().getMap().hasLayer(this.#racetrackArrow)) {
this.#racetrackArrow.addTo(getApp().getMap());
}
this.#racetrackArrow.setLatLng(pointArrow);
this.#racetrackArrow.setBearing(this.#racetrackBearing);
}
#clearRacetrack() {
this.#racetrackPolylines.forEach((polyline) => getApp().getMap().removeLayer(polyline));
this.#racetrackArcs.forEach((arc) => getApp().getMap().removeLayer(arc));
this.#racetrackAnchorMarkers.forEach((marker) => getApp().getMap().removeLayer(marker));
this.#racetrackArrow.removeFrom(getApp().getMap());
}
#applyRacetrackEdit() {
const point1 = this.#racetrackAnchorMarkers[0].getLatLng();
const point2 = this.#racetrackAnchorMarkers[1].getLatLng();
const racetrackLength = point1.distanceTo(point2);
const racetrackBearing = deg2rad(bearing(point1.lat, point1.lng, point2.lat, point2.lng, false));
this.setRacetrack(racetrackLength, this.#racetrackAnchorMarkers[0].getLatLng(), racetrackBearing, () => {
this.#inhibitRacetrackDraw = false;
});
}
#drawContacts() {