diff --git a/frontend/react/public/images/training/step12.1.png b/frontend/react/public/images/training/step12.1.png new file mode 100644 index 00000000..0629dd1a Binary files /dev/null and b/frontend/react/public/images/training/step12.1.png differ diff --git a/frontend/react/public/images/training/step12.gif b/frontend/react/public/images/training/step12.gif new file mode 100644 index 00000000..67fb7544 Binary files /dev/null and b/frontend/react/public/images/training/step12.gif differ diff --git a/frontend/react/public/images/training/step13.png b/frontend/react/public/images/training/step13.png new file mode 100644 index 00000000..93c674f3 Binary files /dev/null and b/frontend/react/public/images/training/step13.png differ diff --git a/frontend/react/public/images/training/step2.gif b/frontend/react/public/images/training/step2.gif index 855fce9c..bae15035 100644 Binary files a/frontend/react/public/images/training/step2.gif and b/frontend/react/public/images/training/step2.gif differ diff --git a/frontend/react/public/images/training/step3.1.gif b/frontend/react/public/images/training/step3.1.gif new file mode 100644 index 00000000..c0147a99 Binary files /dev/null and b/frontend/react/public/images/training/step3.1.gif differ diff --git a/frontend/react/public/images/training/step3.gif b/frontend/react/public/images/training/step3.gif index 86544b5d..b594ffa5 100644 Binary files a/frontend/react/public/images/training/step3.gif and b/frontend/react/public/images/training/step3.gif differ diff --git a/frontend/react/public/images/training/step4.1.gif b/frontend/react/public/images/training/step4.1.gif new file mode 100644 index 00000000..071ffaa3 Binary files /dev/null and b/frontend/react/public/images/training/step4.1.gif differ diff --git a/frontend/react/public/images/training/step6.gif b/frontend/react/public/images/training/step6.gif index 4377fa2e..b5c73002 100644 Binary files a/frontend/react/public/images/training/step6.gif and b/frontend/react/public/images/training/step6.gif differ diff --git a/frontend/react/public/images/training/step8.gif b/frontend/react/public/images/training/step8.gif new file mode 100644 index 00000000..3be96e2d Binary files /dev/null and b/frontend/react/public/images/training/step8.gif differ diff --git a/frontend/react/src/map/coalitionarea/coalitioncircle.ts b/frontend/react/src/map/coalitionarea/coalitioncircle.ts index 1a6f7f9e..4273a761 100644 --- a/frontend/react/src/map/coalitionarea/coalitioncircle.ts +++ b/frontend/react/src/map/coalitionarea/coalitioncircle.ts @@ -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"; diff --git a/frontend/react/src/map/coalitionarea/coalitionpolygon.ts b/frontend/react/src/map/coalitionarea/coalitionpolygon.ts index 1a2ead5f..7cb64d65 100644 --- a/frontend/react/src/map/coalitionarea/coalitionpolygon.ts +++ b/frontend/react/src/map/coalitionarea/coalitionpolygon.ts @@ -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"; diff --git a/frontend/react/src/map/mapmousehandler.ts b/frontend/react/src/map/mapmousehandler.ts index 0b7246d3..799ef483 100644 --- a/frontend/react/src/map/mapmousehandler.ts +++ b/frontend/react/src/map/mapmousehandler.ts @@ -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, diff --git a/frontend/react/src/map/coalitionarea/coalitionareahandle.ts b/frontend/react/src/map/markers/draggablehandle.ts similarity index 64% rename from frontend/react/src/map/coalitionarea/coalitionareahandle.ts rename to frontend/react/src/map/markers/draggablehandle.ts index aa3b77c9..24c15b16 100644 --- a/frontend/react/src/map/coalitionarea/coalitionareahandle.ts +++ b/frontend/react/src/map/markers/draggablehandle.ts @@ -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() { diff --git a/frontend/react/src/map/markers/measureendmarker.ts b/frontend/react/src/map/markers/measureendmarker.ts index 125d493a..4eaeed54 100644 --- a/frontend/react/src/map/markers/measureendmarker.ts +++ b/frontend/react/src/map/markers/measureendmarker.ts @@ -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) { diff --git a/frontend/react/src/map/markers/measurestartmarker.ts b/frontend/react/src/map/markers/measurestartmarker.ts index d1b1f0fc..0ebe35cd 100644 --- a/frontend/react/src/map/markers/measurestartmarker.ts +++ b/frontend/react/src/map/markers/measurestartmarker.ts @@ -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() { diff --git a/frontend/react/src/map/markers/spotmarker.ts b/frontend/react/src/map/markers/spotmarker.ts index a13175ab..bbf1cd33 100644 --- a/frontend/react/src/map/markers/spotmarker.ts +++ b/frontend/react/src/map/markers/spotmarker.ts @@ -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() { diff --git a/frontend/react/src/map/measure.ts b/frontend/react/src/map/measure.ts index 66d00c16..97fc9568 100644 --- a/frontend/react/src/map/measure.ts +++ b/frontend/react/src/map/measure.ts @@ -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() { diff --git a/frontend/react/src/mission/airbase.ts b/frontend/react/src/mission/airbase.ts index 95a67d2e..943e5e5f 100644 --- a/frontend/react/src/mission/airbase.ts +++ b/frontend/react/src/mission/airbase.ts @@ -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); } diff --git a/frontend/react/src/ui/modals/trainingmodal.tsx b/frontend/react/src/ui/modals/trainingmodal.tsx index 2cfe79c1..ebdfbff9 100644 --- a/frontend/react/src/ui/modals/trainingmodal.tsx +++ b/frontend/react/src/ui/modals/trainingmodal.tsx @@ -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 ( - -
-

DCS Olympus guided tour

- -
+ + <> + {step === 0 && ( +
+

DCS Olympus quick reference guide

+
+ )} + <> {step === 0 && ( -
+
-
+

Home

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.

-
+
-
- Every panel has a dedicated integrated wiki. Click on the{" "} - - - {" "} - symbol to access it. Moreover, most clickable content has tooltips providing info about their function. +
+ +
setStep(11)}> + Interacting with the map +
+
+ +
setStep(13)}> + Mission drawings +
+
+
+ +
setStep(14)}> + The audio backend +
+
+
+ +
setStep(15)}> + Game master mode +
+
+
+ +
{}}> + Advanced topics +
+
+
+
+ Every panel has a dedicated integrated wiki. Click on the{" "} + + + {" "} + symbol to access it. Moreover, most clickable content has tooltips providing info about their function.
@@ -74,18 +120,23 @@ export function TrainingModal(props: { open: boolean }) { <> {step === 1 && ( -
- +
+

Main navbar

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.

-

On the bottom left corner, you can find the DCS Olympus options tool.

+

On the bottom left corner, you can find the DCS Olympus options tool and the button to access this quick reference guide.

)} @@ -93,11 +144,16 @@ export function TrainingModal(props: { open: boolean }) { <> {step === 2 && ( -
- +
+

Spawning units (1 of 3)

@@ -116,18 +172,29 @@ export function TrainingModal(props: { open: boolean }) { <> {step === 3 && ( -

- +
+

Spawning units (2 of 3)

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.

-

You can edit the unit properties like in the previous method.

+

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.

+
+ + 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. +
)} @@ -135,11 +202,16 @@ export function TrainingModal(props: { open: boolean }) { <> {step === 4 && ( -
- +
+

Spawning units (3 of 3)

@@ -154,11 +226,16 @@ export function TrainingModal(props: { open: boolean }) { <> {step === 5 && ( -

- +
+

Controlling units (1 of 4)

@@ -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.

+
+ + 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. +
)} @@ -177,16 +261,31 @@ export function TrainingModal(props: { open: boolean }) { <> {step === 6 && ( -
- +
+

Controlling units (2 of 4)

- 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.{" "} +

+

+ Actions are color coded: +

    +
  • White: Movement
  • +
  • Green: Miscellaneous (group, center on map, etc)
  • +
  • Purple: Admin (AAR, land, etc)
  • +
  • Blue: Attack or engage
  • +
  • Red: Delete or destroy
  • +

@@ -196,11 +295,16 @@ export function TrainingModal(props: { open: boolean }) { <> {step === 7 && ( -
- +
+

Controlling units (3 of 4)

@@ -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.{" "}

+

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.

)} @@ -218,11 +323,16 @@ export function TrainingModal(props: { open: boolean }) { <> {step === 8 && ( -
- +
+

Controlling units (4 of 4)

@@ -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.{" "}

-

+

+ {" "} + 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.{" "} +

+

+ {" "} + 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. +

)} @@ -238,11 +357,16 @@ export function TrainingModal(props: { open: boolean }) { <> {step === 9 && ( -
- +
+

The unit marker (1 of 2)

@@ -353,11 +477,22 @@ export function TrainingModal(props: { open: boolean }) { <> {step === 10 && ( -

+

The unit marker (2 of 2)

The unit marker has a symbol showing the unit state, i.e. what instruction it is performing. These are all the possible values:

-
+

@@ -454,6 +589,131 @@ export function TrainingModal(props: { open: boolean }) { )} + <> + {step === 11 && ( +

+
+

Interacting with the map (1 of 2)

+

+ 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. +

+

+ 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. +

+

+ 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. +

+

+ 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. +

+
+
+ )} + + + <> + {step === 12 && ( +
+ +
+

Interacting with the map (2 of 2)

+

+ 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. +

+
+ + 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. +
+
+
+ )} + + + <> + {step === 13 && ( +
+ +
+

Mission drawings

+

Mission drawings are useful to provide information from the mission creator. They can define borders, Areas of Operation, navigational points and more.

+

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.

+

You can also define your own drawings, which can be useful as reference, or as a way to automatically create IADS.

+

Use the button on the upper right of the drawings panel for more info.

+
+
+ )} + + + <> + {step === 14 && ( +
+
+

The audio backend

+

+

+
+
+ )} + + + <> + {step === 15 && ( +
+
+

Game master mode

+

+

+
+
+ )} + +
{step > 0 ? (