feat: Added material to quick reference guide

This commit is contained in:
Davide Passoni 2025-03-28 17:07:38 +01:00
parent bdf9c83053
commit 0452a8081b
23 changed files with 489 additions and 145 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 KiB

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

View File

@ -1,6 +1,6 @@
import { LatLngExpression, Map, Circle, DivIcon, Marker, CircleOptions, LatLng } from "leaflet";
import { getApp } from "../../olympusapp";
import { DraggableHandle } from "./coalitionareahandle";
import { DraggableHandle } from "../markers/draggablehandle";
import { BLUE_COMMANDER, colors, RED_COMMANDER } from "../../constants/constants";
import { Coalition } from "../../types/types";
import * as turf from "@turf/turf";

View File

@ -1,6 +1,6 @@
import { LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions, DivIcon, Marker } from "leaflet";
import { getApp } from "../../olympusapp";
import { DraggableHandle } from "./coalitionareahandle";
import { DraggableHandle } from "../markers/draggablehandle";
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
import { BLUE_COMMANDER, colors, RED_COMMANDER } from "../../constants/constants";
import { Coalition } from "../../types/types";

View File

@ -46,8 +46,8 @@ export class MapMouseHandler {
/* Events for touchscreen and mouse */
if ("ontouchstart" in window) {
DomEvent.on(this.#map.getContainer(), "touchstart", (e: any) => this.#onTouchDown(e), this);
DomEvent.on(this.#map.getContainer(), "touchend", (e: any) => this.#onTouchUp(e), this);
DomEvent.on(this.#map.getContainer(), "touchstart", (e: any) => this.#onTouchStart(e), this);
DomEvent.on(this.#map.getContainer(), "touchend", (e: any) => this.#onTouchEnd(e), this);
DomEvent.on(this.#map.getContainer(), "touchmove", (e: any) => this.#onTouchMove(e), this);
} else {
this.#map.on("mouseup", (e: any) => this.#onMouseUp(e));
@ -162,7 +162,7 @@ export class MapMouseHandler {
this.mouseWheel(e);
};
#onTouchDown = (e: TouchEvent) => {
#onTouchStart = (e: TouchEvent) => {
let newEvent = {
latlng: this.#map.containerPointToLatLng(this.#map.mouseEventToContainerPoint(e.changedTouches[0] as unknown as MouseEvent)),
originalEvent: e,
@ -177,7 +177,7 @@ export class MapMouseHandler {
}, 300);
};
#onTouchUp = (e: TouchEvent) => {
#onTouchEnd = (e: TouchEvent) => {
let newEvent = {
latlng: this.#map.containerPointToLatLng(this.#map.mouseEventToContainerPoint(e.changedTouches[0] as unknown as MouseEvent)),
originalEvent: e,

View File

@ -1,5 +1,5 @@
import { DivIcon, DomEvent, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker";
import { CustomMarker } from "./custommarker";
export class DraggableHandle extends CustomMarker {
constructor(latlng: LatLng) {
@ -9,21 +9,10 @@ export class DraggableHandle extends CustomMarker {
this.getElement()?.addEventListener("touchstart", (e) => e.stopPropagation());
});
this.on("mousedown", (e) => {
DomEvent.stopPropagation(e);
});
this.on("mouseup", (e) => {
DomEvent.stopPropagation(e);
});
this.on("dragstart", (e) => {
DomEvent.stopPropagation(e);
});
this.on("dragend", (e) => {
DomEvent.stopPropagation(e);
});
this.on("mousedown", (e) => DomEvent.stopPropagation(e));
this.on("mouseup", (e) => DomEvent.stopPropagation(e));
this.on("dragstart", (e) => DomEvent.stopPropagation(e));
this.on("dragend", (e) => DomEvent.stopPropagation(e));
}
createIcon() {

View File

@ -1,15 +1,32 @@
import { DivIcon, LatLngExpression, Map, MarkerOptions } from "leaflet";
import { DivIcon, DomEvent, LatLngExpression, Map, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
import { SVGInjector } from "@tanem/svg-injector";
export class MeasureEndMarker extends CustomMarker {
#rotationAngle: number = 0;
#moving: boolean = true;
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.options.interactive = true;
this.options.draggable = true;
this.setZIndexOffset(9999);
this.on("mousedown", (e) => {
if (!this.#moving) DomEvent.stopPropagation(e);
});
this.on("mouseup", (e) => {
if (!this.#moving) DomEvent.stopPropagation(e);
});
this.on("dragstart", (e) => {
DomEvent.stopPropagation(e);
});
this.on("dragend", (e) => {
DomEvent.stopPropagation(e);
});
}
createIcon() {
@ -27,7 +44,7 @@ export class MeasureEndMarker extends CustomMarker {
img.onload = () => {
SVGInjector(img);
this.#applyRotation();
}
};
el.appendChild(img);
this.getElement()?.appendChild(el);
}
@ -47,6 +64,14 @@ export class MeasureEndMarker extends CustomMarker {
return this;
}
setMoving(moving: boolean) {
this.#moving = moving;
}
getMoving() {
return this.#moving;
}
#applyRotation() {
const element = this.getElement();
if (element) {

View File

@ -1,14 +1,29 @@
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { DivIcon, DomEvent, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
import { SVGInjector } from "@tanem/svg-injector";
export class MeasureStartMarker extends CustomMarker {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.options.interactive = true;
this.options.draggable = true;
this.setZIndexOffset(9999);
this.on("mousedown", (e) => {
DomEvent.stopPropagation(e);
});
this.on("mouseup", (e) => {
DomEvent.stopPropagation(e);
});
this.on("dragstart", (e) => {
DomEvent.stopPropagation(e);
});
this.on("dragend", (e) => {
DomEvent.stopPropagation(e);
});
}
createIcon() {

View File

@ -1,4 +1,4 @@
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { DivIcon, DomEvent, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
export class SpotMarker extends CustomMarker {
@ -7,6 +7,22 @@ export class SpotMarker extends CustomMarker {
this.options.interactive = true;
this.options.draggable = true;
this.setZIndexOffset(9999);
this.on("mousedown", (e) => {
DomEvent.stopPropagation(e);
});
this.on("mouseup", (e) => {
DomEvent.stopPropagation(e);
});
this.on("dragstart", (e) => {
DomEvent.stopPropagation(e);
});
this.on("dragend", (e) => {
DomEvent.stopPropagation(e);
});
}
createIcon() {

View File

@ -1,11 +1,9 @@
import { LatLng, LeafletMouseEvent, Polyline } from "leaflet";
import { LatLng, Polyline } from "leaflet";
import { Map } from "./map";
import { MeasureMarker } from "./markers/measuremarker";
import { MeasureStartMarker } from "./markers/measurestartmarker";
import { MeasureEndMarker } from "./markers/measureendmarker";
import { bearing, deg2rad, midpoint, mToFt, mToNm, nmToM, rad2deg } from "../other/utils";
import { AppStateChangedEvent } from "../events";
import { OlympusState } from "../constants/constants";
export class Measure {
#active: boolean = false;
@ -76,6 +74,7 @@ export class Measure {
finish() {
this.#active = false;
this.#endMarker.setMoving(false);
}
isActive() {

View File

@ -40,17 +40,15 @@ export class Airbase extends CustomMarker {
(this.getElement()?.querySelector(".airbase-icon") as HTMLElement).dataset.selected = `${this.#selected}`;
});
this.addEventListener("mousedown", (ev) => {
this.on("mousedown", (ev) => {
if (getApp().getState() === OlympusState.IDLE || getApp().getState() === OlympusState.AIRBASE) {
DomEvent.stop(ev);
ev.originalEvent.stopImmediatePropagation();
}
});
this.addEventListener("mouseup", (ev) => {
this.on("mouseup", (ev) => {
if (getApp().getState() === OlympusState.IDLE || getApp().getState() === OlympusState.AIRBASE) {
DomEvent.stop(ev);
ev.originalEvent.stopImmediatePropagation();
getApp().setState(OlympusState.AIRBASE);
AirbaseSelectedEvent.dispatch(this);
}

View File

@ -2,70 +2,116 @@ import React, { useState } from "react";
import { Modal } from "./components/modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons";
import { FaCaretRight, FaLink } from "react-icons/fa6";
import { FaLink } from "react-icons/fa6";
import { FaQuestionCircle } from "react-icons/fa";
const MAX_STEPS = 10;
const MAX_STEPS = 15;
export function TrainingModal(props: { open: boolean }) {
const [step, setStep] = useState(0);
return (
<Modal open={props.open}>
<div>
<h1 className={`text-2xl font-semibold text-white`}>DCS Olympus guided tour</h1>
</div>
<Modal open={props.open} size="lg">
<>
{step === 0 && (
<div>
<h1 className={`text-3xl font-semibold text-gray-200`}>DCS Olympus quick reference guide</h1>
</div>
)}
</>
<>
{step === 0 && (
<div className="flex flex-col sm:flex-row gap-4 sm:gap-16">
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<img
src="images/olympus-500x500.png"
className={`my-auto h-40 w-40 rounded-xl`}
className={`
pointer-events-none absolute left-[50%] top-[50%] my-auto
aspect-square h-[80%] translate-x-[-50%] translate-y-[-50%]
rounded-xl opacity-[2%]
`}
/>
<div className="flex flex-col gap-4 text-gray-400">
<div className="flex flex-col gap-4 gap-x-10 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Home</h2>
<p>
Welcome to the Olympus quick start guide! This tour will guide you through the basics of DCS Olympus. You can navigate through the steps using
the "Next" and "Previous" buttons at the bottom of the screen, or select a topic from the list below.
</p>
<div className="flex flex-col flex-wrap gap-2">
<div className={`
flex w-fit flex-col flex-wrap gap-2
md:h-32
`}>
<div className="flex gap-2">
<FaLink className="my-auto" />
<a href="#" className={`text-blue-400`} onClick={() => setStep(1)}>
<div className={`cursor-pointer text-blue-400`} onClick={() => setStep(1)}>
Main navbar
</a>
</div>
</div>
<div className="flex gap-2">
<FaLink className="my-auto" />
<a href="#" className={`text-blue-400`} onClick={() => setStep(2)}>
<div className={`cursor-pointer text-blue-400`} onClick={() => setStep(2)}>
Spawning units
</a>
</div>
</div>
<div className="flex gap-2">
<FaLink className="my-auto" />
<a href="#" className={`text-blue-400`} onClick={() => setStep(5)}>
<div className={`cursor-pointer text-blue-400`} onClick={() => setStep(5)}>
Controlling units
</a>
</div>
</div>
<div className="flex gap-2">
<FaLink className="my-auto" />
<a href="#" className={`text-blue-400`} onClick={() => setStep(9)}>
<div className={`cursor-pointer text-blue-400`} onClick={() => setStep(9)}>
The unit marker
</a>
</div>
</div>
<div>
Every panel has a dedicated integrated wiki. Click on the{" "}
<span
className={`
mt-[-7px] inline-block translate-y-2 rounded-full p-1
`}
>
<FaQuestionCircle />
</span>{" "}
symbol to access it. Moreover, most clickable content has tooltips providing info about their function.
<div className="flex gap-2">
<FaLink className="my-auto" />
<div className={`cursor-pointer text-blue-400`} onClick={() => setStep(11)}>
Interacting with the map
</div>
</div>
<div className="flex gap-2">
<FaLink className="my-auto" />
<div className={`cursor-pointer text-blue-400`} onClick={() => setStep(13)}>
Mission drawings
</div>
</div>
<div className="flex gap-2">
<FaLink className="my-auto" />
<div className={`cursor-pointer text-blue-400`} onClick={() => setStep(14)}>
The audio backend
</div>
</div>
<div className="flex gap-2">
<FaLink className="my-auto" />
<div className={`cursor-pointer text-blue-400`} onClick={() => setStep(15)}>
Game master mode
</div>
</div>
<div className="flex gap-2">
<FaLink className="my-auto" />
<div className={`cursor-pointer text-blue-400`} onClick={() => {}}>
Advanced topics
</div>
</div>
</div>
<div>
Every panel has a dedicated integrated wiki. Click on the{" "}
<span
className={`
mt-[-7px] inline-block translate-y-2 rounded-full p-1
`}
>
<FaQuestionCircle />
</span>{" "}
symbol to access it. Moreover, most clickable content has tooltips providing info about their function.
</div>
</div>
</div>
@ -74,18 +120,23 @@ export function TrainingModal(props: { open: boolean }) {
<>
{step === 1 && (
<div className="flex flex-col sm:flex-row gap-4 sm:gap-16">
<img
src="images/training/step1.gif"
className={`h-72 w-72 rounded-xl`}
/>
<div
className={`
flex flex-col content-center gap-4
md:flex-row
sm:gap-16
`}
>
<img src="images/training/step1.gif" className={`
h-96 w-96 rounded-xl
`} />
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Main navbar</h2>
<p>
The main functions of DCS Olympus are accessible from the main navbar. You can access the spawn tool, the unit selection and control tool, the
drawings tool, the audio/radio tool, and the game master options from here.
</p>
<p>On the bottom left corner, you can find the DCS Olympus options tool.</p>
<p>On the bottom left corner, you can find the DCS Olympus options tool and the button to access this quick reference guide.</p>
</div>
</div>
)}
@ -93,11 +144,16 @@ export function TrainingModal(props: { open: boolean }) {
<>
{step === 2 && (
<div className="flex flex-col sm:flex-row gap-4 sm:gap-16">
<img
src="images/training/step2.gif"
className={`h-72 w-72 rounded-xl`}
/>
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<img src="images/training/step2.gif" className={`
h-96 w-96 rounded-xl
`} />
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Spawning units (1 of 3)</h2>
<p>
@ -116,18 +172,29 @@ export function TrainingModal(props: { open: boolean }) {
<>
{step === 3 && (
<div className="flex flex-col sm:flex-row gap-4 sm:gap-16">
<img
src="images/training/step3.gif"
className={`h-72 w-72 rounded-xl`}
/>
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<img src="images/training/step3.gif" className={`
h-96 w-96 rounded-xl
`} />
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Spawning units (2 of 3)</h2>
<p>
You can also spawn units directly on the map by right clicking on it and selecting the unit you want to spawn. This will spawn the unit at the
clicked location.
</p>
<p>You can edit the unit properties like in the previous method.</p>
<p>You can edit the unit properties like in the previous method. Remember you can open the unit summary section to get more info on the unit.</p>
<div className="flex gap-4">
<img src="images/training/step3.1.gif" className={`
h-32 w-32 rounded-xl
`} />
You can change the spawn heading of the unit by dragging the arrow on the map. This will also change the spawn heading in the unit properties.
</div>
</div>
</div>
)}
@ -135,11 +202,16 @@ export function TrainingModal(props: { open: boolean }) {
<>
{step === 4 && (
<div className="flex flex-col sm:flex-row gap-4 sm:gap-16">
<img
src="images/training/step4.gif"
className={`h-72 w-72 rounded-xl`}
/>
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<img src="images/training/step4.gif" className={`
h-96 w-96 rounded-xl
`} />
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Spawning units (3 of 3)</h2>
<p>
@ -154,11 +226,16 @@ export function TrainingModal(props: { open: boolean }) {
<>
{step === 5 && (
<div className="flex flex-col sm:flex-row gap-4 sm:gap-16">
<img
src="images/training/step5.gif"
className={`h-72 w-72 rounded-xl`}
/>
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<img src="images/training/step5.gif" className={`
h-96 w-96 rounded-xl
`} />
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Controlling units (1 of 4)</h2>
<p>
@ -170,6 +247,13 @@ export function TrainingModal(props: { open: boolean }) {
Previously created destinations can be moved by dragging the marker on the map. If multiple units are selected when creating the path,
destinations will be shared between them.
</p>
<div className="flex gap-4">
<img src="images/training/step4.1.gif" className={`
h-40 w-40 rounded-xl
`} />
Holding down the right mouse button enters "group movement" mode. The units will hold their relative positions and move as a formation. Move the
mouse to choose the formation heading. Ctrl can be pressed to create a path.
</div>
</div>
</div>
)}
@ -177,16 +261,31 @@ export function TrainingModal(props: { open: boolean }) {
<>
{step === 6 && (
<div className="flex flex-col sm:flex-row gap-4 sm:gap-16">
<img
src="images/training/step6.gif"
className={`h-72 w-72 rounded-xl`}
/>
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<img src="images/training/step6.gif" className={`
h-96 w-96 rounded-xl
`} />
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Controlling units (2 of 4)</h2>
<p>
To issue an instruction to a unit, long press the right mouse button on the map. This will allow you to select an action, depending on what you
clicked on.
To issue an instruction to a unit, long press the left mouse button on the map. This will open a context menu allowing you to select an action,
depending on what you clicked on. The same can be done by clicking a unit marker, which will show the unit's context menu.{" "}
</p>
<p>
Actions are color coded:
<ul className="list-inside list-disc">
<li className="text-white">White: Movement</li>
<li className="text-green-400">Green: Miscellaneous (group, center on map, etc)</li>
<li className="text-purple-400">Purple: Admin (AAR, land, etc)</li>
<li className="cursor-pointer text-blue-400">Blue: Attack or engage</li>
<li className="text-red-400">Red: Delete or destroy</li>
</ul>
</p>
<p></p>
</div>
@ -196,11 +295,16 @@ export function TrainingModal(props: { open: boolean }) {
<>
{step === 7 && (
<div className="flex flex-col sm:flex-row gap-4 sm:gap-16">
<img
src="images/training/step7.gif"
className={`h-72 w-72 rounded-xl`}
/>
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<img src="images/training/step7.gif" className={`
h-96 w-96 rounded-xl
`} />
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Controlling units (3 of 4)</h2>
<p>
@ -211,6 +315,7 @@ export function TrainingModal(props: { open: boolean }) {
Tools can be enabled using keyboard shortcuts. To learn what a tool does and what shortcut enables it, place your cursor over the corresponding
button.{" "}
</p>
<p>You can hold the left mouse button down to enter "group" mode just like for movement. Moving the mouse allows to rotate the tool patterns.</p>
</div>
</div>
)}
@ -218,11 +323,16 @@ export function TrainingModal(props: { open: boolean }) {
<>
{step === 8 && (
<div className="flex flex-col sm:flex-row gap-4 sm:gap-16">
<img
src="images/training/step8.gif"
className={`h-72 w-72 rounded-xl`}
/>
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<img src="images/training/step8.gif" className={`
h-96 w-96 rounded-xl
`} />
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Controlling units (4 of 4)</h2>
<p>
@ -230,7 +340,16 @@ export function TrainingModal(props: { open: boolean }) {
Unit properties are set using the unit control menu, which opens automatically when a unit is selected. Here, depending on the selected unit,
you can set altitude and speed, Rules Of Engagement, reaction to threat, as well as advanced settings like AWACS frequencies and so on.{" "}
</p>
<p> </p>
<p>
{" "}
Available options will change depending on selected unit type and number. If different types of units are selected at the same time, only common
properties will be editable. Use the units list on the upper part of the panel to refine your choice.{" "}
</p>
<p>
{" "}
If the selected units have properties set differently, the relevant inputs will show a "Different value" state, or show no state at all until
the same value is set for all units.
</p>
</div>
</div>
)}
@ -238,11 +357,16 @@ export function TrainingModal(props: { open: boolean }) {
<>
{step === 9 && (
<div className="flex flex-col sm:flex-row gap-4 sm:gap-16">
<img
src="images/training/unitmarker.png"
className={`max-h-34 max-w-34 my-auto rounded-xl`}
/>
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<img src="images/training/unitmarker.png" className={`
max-h-34 max-w-34 my-auto rounded-xl
`} />
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>The unit marker (1 of 2)</h2>
<p>
@ -353,11 +477,22 @@ export function TrainingModal(props: { open: boolean }) {
<>
{step === 10 && (
<div className="flex flex-col sm:flex-row gap-4 sm:gap-16">
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>The unit marker (2 of 2)</h2>
<p>The unit marker has a symbol showing the unit state, i.e. what instruction it is performing. These are all the possible values:</p>
<div className="flex sm:max-h-64 flex-col flex-wrap gap-4">
<div
className={`
flex flex-col flex-wrap gap-4
sm:max-h-64
`}
>
<div className="flex flex-col">
<p className="flex gap-2">
<img src="images/states/attack.svg" />
@ -454,6 +589,131 @@ export function TrainingModal(props: { open: boolean }) {
)}
</>
<>
{step === 11 && (
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Interacting with the map (1 of 2)</h2>
<p>
Moving the map is done in a similar way to other map applications. You can use the mouse wheel to zoom in and out, and click and drag to move
the map. On mobile, you can use two fingers to zoom in and out, and one finger to move the map.
</p>
<p>
You have multiple options for selecting units. Clicking on a unit will select it. If you want to select multiple units, you can hold the ctrl
key and click on them. A double click on the unit will perform the same operations, but on all currently visible units of the same type. This is
useful for selecting all units of a certain type, like all F-16s.
</p>
<p>
Box selection mode can be accessed in three different ways: by long pressing on the map with left mouse button (but only if no unit is currently
selected), by holding the alt key and clicking on the map, or by using the box selection tool. This will select all units inside the box.
</p>
<p>
Ctrl + A will select all the visible units on the map. Toggling the visibility of unwanted units and using this shortcut is a good way to select
all units of a certain coalition or type. A more advanced selection tool can be accessed by clicking on the unit selection tool on the main
navbar.
</p>
</div>
</div>
)}
</>
<>
{step === 12 && (
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<img src="images/training/step12.gif" className={`
h-96 w-96 rounded-xl
`} />
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Interacting with the map (2 of 2)</h2>
<p>
Measurements can be perfomed on the map. To enter measure mode you can either use the measure tool, or click the mouse wheel. Subsequent clicks
on the map will add additional points. You will be able to read the leg length and bearing, as well as the overall length of the line.
</p>
<div
className={`
flex w-full flex-col content-center justify-center gap-4
`}
>
<img src="images/training/step12.1.png" className={`
mx-auto w-40 rounded-xl
`} />
On the bottom right corner of the map, you can find the coordinates panel, providing the coordinates of the mouse cursor, as well as its
bullseye position and the ground elevation. Click on the coordinates to rotate format.
</div>
</div>
</div>
)}
</>
<>
{step === 13 && (
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<img src="images/training/step13.png" className={`h-96 rounded-xl`} />
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Mission drawings</h2>
<p>Mission drawings are useful to provide information from the mission creator. They can define borders, Areas of Operation, navigational points and more. </p>
<p>Mission drawings are automatically imported from the mission and can be shown using the drawing menu. You can enable or disable different sections, change the opacity, and look for specific drawings or navpoint using the shearch bar.</p>
<p>You can also define your own drawings, which can be useful as reference, or as a way to automatically create IADS.</p>
<p>Use the <FaQuestionCircle className="inline"/> button on the upper right of the drawings panel for more info. </p>
</div>
</div>
)}
</>
<>
{step === 14 && (
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>The audio backend</h2>
<p></p>
<p></p>
</div>
</div>
)}
</>
<>
{step === 15 && (
<div
className={`
flex flex-col gap-4
md:flex-row
sm:gap-16
`}
>
<div className="flex flex-col gap-4 text-gray-400">
<h2 className={`text-xl font-semibold text-white`}>Game master mode</h2>
<p></p>
<p></p>
</div>
</div>
)}
</>
<div className="mt-auto flex justify-between">
{step > 0 ? (
<button
@ -475,15 +735,22 @@ export function TrainingModal(props: { open: boolean }) {
)}
{step > 0 && (
<div className="my-auto gap-2 hidden sm:flex">
<div
className={`
my-auto hidden gap-2
sm:flex
`}
>
{[...Array(MAX_STEPS).keys()].map((i) => (
<div
key={i + 1}
className={`
h-4 w-4 rounded-full
${i + 1 === step ? "bg-blue-700 shadow-white" : `
bg-gray-300/10
`}
${
i + 1 === step
? "bg-blue-700 shadow-white"
: `bg-gray-300/10`
}
`}
/>
))}

View File

@ -164,7 +164,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
<FontAwesomeIcon
icon={faMapLocation}
className={`
ml-auto cusor-pointer transition-transform
ml-auto cursor-pointer transition-transform
hover:scale-125
`}
onClick={() => {

View File

@ -22,6 +22,13 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { FaQuestionCircle } from "react-icons/fa";
import { OlExpandingTooltip } from "../components/olexpandingtooltip";
enum OpenAccordion {
NONE,
LOADOUT,
UNIT_SUMMARY,
ADVANCED_OPTIONS,
}
export function UnitSpawnMenu(props: {
visible: boolean;
compact: boolean;
@ -49,13 +56,13 @@ export function UnitSpawnMenu(props: {
const [spawnAltitudeType, setSpawnAltitudeType] = useState(false);
const [spawnLiveryID, setSpawnLiveryID] = useState("");
const [spawnSkill, setSpawnSkill] = useState("High");
const [showLoadout, setShowLoadout] = useState(false);
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
const [showUnitSummary, setShowUnitSummary] = useState(false);
const [quickAccessName, setQuickAccessName] = useState("Preset 1");
const [key, setKey] = useState("");
const [spawnRequestTable, setSpawnRequestTable] = useState(null as null | SpawnRequestTable);
const [openAccordion, setOpenAccordion] = useState(OpenAccordion.NONE);
const [showLoadout, setShowLoadout] = useState(false);
useEffect(() => {
setAppState(getApp()?.getState());
AppStateChangedEvent.on((state, subState) => setAppState(state));
@ -391,9 +398,9 @@ export function UnitSpawnMenu(props: {
)}
<OlAccordion
onClick={() => {
setShowAdvancedOptions(!showAdvancedOptions);
setOpenAccordion(openAccordion === OpenAccordion.ADVANCED_OPTIONS ? OpenAccordion.NONE : OpenAccordion.ADVANCED_OPTIONS);
}}
open={showAdvancedOptions}
open={openAccordion === OpenAccordion.ADVANCED_OPTIONS}
title="Advanced options"
>
<div className="flex flex-col gap-2">
@ -441,9 +448,10 @@ export function UnitSpawnMenu(props: {
`}
>
{props.blueprint?.liveries && props.blueprint?.liveries[id].countries.length == 1 && (
<img src={`images/countries/${country?.flagCode.toLowerCase()}.svg`} className={`
h-6
`} />
<img
src={`images/countries/${country?.flagCode.toLowerCase()}.svg`}
className={`h-6`}
/>
)}
<div className="my-auto truncate">
@ -509,9 +517,12 @@ export function UnitSpawnMenu(props: {
<div className="my-auto flex flex-col gap-2">
<span>Spawn heading</span>
<div className="flex gap-1 text-sm text-gray-400">
<FaQuestionCircle className={`my-auto`} /> <div className={`
my-auto
`}>Drag to change</div>
<FaQuestionCircle className={`my-auto`} />{" "}
<div
className={`my-auto`}
>
Drag to change
</div>
</div>
</div>
@ -558,9 +569,9 @@ export function UnitSpawnMenu(props: {
</div>
<OlAccordion
onClick={() => {
setShowUnitSummary(!showUnitSummary);
setOpenAccordion(openAccordion === OpenAccordion.UNIT_SUMMARY ? OpenAccordion.NONE : OpenAccordion.UNIT_SUMMARY);
}}
open={showUnitSummary}
open={openAccordion === OpenAccordion.UNIT_SUMMARY}
title="Unit summary"
>
{props.blueprint ? <OlUnitSummary blueprint={props.blueprint} coalition={spawnCoalition} /> : <span></span>}
@ -568,9 +579,9 @@ export function UnitSpawnMenu(props: {
{spawnLoadout && spawnLoadout.items.length > 0 && (
<OlAccordion
onClick={() => {
setShowLoadout(!showLoadout);
setOpenAccordion(openAccordion === OpenAccordion.LOADOUT ? OpenAccordion.NONE : OpenAccordion.LOADOUT);
}}
open={showLoadout}
open={openAccordion === OpenAccordion.LOADOUT}
title="Loadout"
>
{spawnLoadout.items.map((item) => {
@ -897,10 +908,9 @@ export function UnitSpawnMenu(props: {
`}
>
{props.blueprint?.liveries && props.blueprint?.liveries[id].countries.length == 1 && (
<img
src={`images/countries/${country?.flagCode.toLowerCase()}.svg`}
className={`h-6`}
/>
<img src={`images/countries/${country?.flagCode.toLowerCase()}.svg`} className={`
h-6
`} />
)}
<div className="my-auto truncate">
@ -966,9 +976,12 @@ export function UnitSpawnMenu(props: {
<div className="my-auto flex flex-col gap-2">
<span className="text-white">Spawn heading</span>
<div className="flex gap-1 text-sm text-gray-400">
<FaQuestionCircle className={`my-auto`} /> <div className={`
my-auto
`}>Drag to change</div>
<FaQuestionCircle className={`my-auto`} />{" "}
<div
className={`my-auto`}
>
Drag to change
</div>
</div>
</div>

View File

@ -70,7 +70,7 @@ import {
UnitSelectedEvent,
UnitUpdatedEvent,
} from "../events";
import { DraggableHandle } from "../map/coalitionarea/coalitionareahandle";
import { DraggableHandle } from "../map/markers/draggablehandle";
import { ArrowMarker } from "../map/markers/arrowmarker";
import { Spot } from "../mission/spot";
import { SpotEditMarker } from "../map/markers/spoteditmarker";

View File

@ -1,3 +1,26 @@
v2.0.1 ====================
Changes:
feat: Added starred spawns to spawn menu and ability to remove starred spawns
feat: Added selection tools to unit list in unit control menu
fix: Removed accidental development feature in unit spawn men
feat: Added nightly link to manager to update without github account
feat: Multiple improvements to audio backend
feat: Added ability to change command mode, improved local connection detection
fix: Drawings filtering utility made case insensitive
feat: Added drawings filtering by string and go to drawing feature
feat: Navpoints settings saved in session data
feat: Navpoints separated from Drawings
refactor: Changed styles for visible / hidden drawing layers
feat: Added + and - buttons to control map zoom level
fix: "Don't show again" tickbox not being respected
fix: Manager not respecting autoconnectwhentrue and srsport settings
fix: Aligned airbase spawn menu to other menus
fix: Context menu shown when dragging handles
fix: Aligned tanker orbit behaviour
fix: Quick box selection causes units to be immediately deselected
fix: Unable to clone units if game master with spawn restrictions on
feat: Added support for callsigns
v2.0.0 ====================
Changes:
1) completely redone UI using React
@ -5,7 +28,6 @@ Changes:
3) added Mission Editor drawings in DCS
4) multiple enhancements to control scheme
v1.0.4 ====================
Changes:
1) Added Olympus Manager for unified configuration management;