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

@ -18,6 +18,10 @@ public:
virtual void changeSpeed(string change) = 0;
virtual void changeAltitude(string change) = 0;
virtual double getDestinationReachedThreshold() { return AIR_DEST_DIST_THR; }
virtual void setRacetrackLength(double newValue);
virtual void setRacetrackAnchor(Coords newValue);
virtual void setRacetrackBearing(double newValue);
protected:
virtual void AIloop();

View File

@ -154,9 +154,11 @@ void AirUnit::AIloop()
{
srand(static_cast<unsigned int>(time(NULL)) + ID);
if (state != State::IDLE) {
/* Reset the anchor, but only if the unit is not a tanker or a AWACS */
if (state != State::IDLE && !isActiveTanker && !isActiveAWACS) {
setRacetrackAnchor(Coords(NULL));
setRacetrackBearing(NULL);
setRacetrackLength(NULL);
}
/* State machine */
@ -387,4 +389,43 @@ void AirUnit::AIloop()
default:
break;
}
}
void AirUnit::setRacetrackLength(double newRacetrackLength) {
if (racetrackLength != newRacetrackLength) {
racetrackLength = newRacetrackLength;
/* Apply the change */
setHasTask(false);
resetTaskFailedCounter();
AIloop();
triggerUpdate(DataIndex::racetrackLength);
}
}
void AirUnit::setRacetrackAnchor(Coords newRacetrackAnchor) {
if (racetrackAnchor != newRacetrackAnchor) {
racetrackAnchor = newRacetrackAnchor;
/* Apply the change */
setHasTask(false);
resetTaskFailedCounter();
AIloop();
triggerUpdate(DataIndex::racetrackAnchor);
}
}
void AirUnit::setRacetrackBearing(double newRacetrackBearing) {
if (racetrackBearing != newRacetrackBearing) {
racetrackBearing = newRacetrackBearing;
/* Apply the change */
setHasTask(false);
resetTaskFailedCounter();
AIloop();
triggerUpdate(DataIndex::racetrackBearing);
}
}

View File

@ -354,14 +354,22 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") altitude type: " + to_string(value[L"altitudeType"]), true);
}
}/************************/
else if (key.compare("setRacetrackLength") == 0)
else if (key.compare("setRacetrack") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setRacetrackLength(value[L"racetrackLength"].as_double());
log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") racetrack length: " + to_string(value[L"racetrackLength"].as_double()), true);
unit->setRacetrackLength(value[L"length"].as_double());
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
Coords location; location.lat = lat; location.lng = lng;
unit->setRacetrackAnchor(location);
unit->setRacetrackBearing(value[L"bearing"].as_double());
log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") racetrack length: " + to_string(value[L"length"].as_double()) + " racetrack bearing: " + to_string(value[L"bearing"].as_double()), true);
}
}
/************************/

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() {