This commit is contained in:
WoodyXP 2023-02-14 01:39:40 +01:00
commit b489659ddd
27 changed files with 852 additions and 102 deletions

View File

@ -5,4 +5,5 @@ explosion
wrong name for ground units
improve map zIndex
fuel is wrong (either 0 or 1, its is casting it to int somewhere)
weapons should not be selectable
weapons should not be selectable
human symbol if user

View File

@ -0,0 +1,47 @@
.slider-container {
width: 100%;
}
.slider {
width: 100%;
-webkit-appearance: none;
appearance: none;
height: 2px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
margin-top: 10px;
margin-bottom: 10px;
}
.slider:hover {
opacity: 1;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: gray;
cursor: pointer;
border-radius: 999px;
}
.active .slider::-webkit-slider-thumb {
background: #5ca7ff;
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
background: gray;
cursor: pointer;
border-radius: 999px;
}
.active .slider::-moz-range-thumb {
background: #5ca7ff;
}

View File

@ -1,4 +1,5 @@
@import url("button.css");
@import url("slider.css");
@import url("dropdown.css");
@import url("selectionwheel.css");

View File

@ -52,7 +52,7 @@
height: 100%;
}
#threat-reaction-buttons-container {
#reaction-to-threat-buttons-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
@ -106,4 +106,25 @@
color: white;
font-size: 13px;
width: 100%;
}
}
.flight-control-slider {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.flight-control-title {
font-size: 13px;
color: white;
}
.flight-control-value {
font-size: 14px;
font-weight: 600;
color: gray;
}
.active .flight-control-value {
color: #5ca7ff;
}

View File

@ -0,0 +1,83 @@
export class Slider {
#container: HTMLElement | null;
#callback: CallableFunction;
#slider: HTMLInputElement | null = null;
#value: HTMLElement | null = null;
#minValue: number;
#maxValue: number;
#minValueDiv: HTMLElement | null = null;
#maxValueDiv: HTMLElement | null = null;
#unit: string;
#display: string = "";
constructor(ID: string, minValue: number, maxValue: number, unit: string, callback: CallableFunction) {
this.#container = document.getElementById(ID);
this.#callback = callback;
this.#minValue = minValue;
this.#maxValue = maxValue;
this.#unit = unit;
if (this.#container != null) {
this.#display = this.#container.style.display;
this.#slider = <HTMLInputElement>this.#container.querySelector("input");
if (this.#slider != null)
{
this.#slider.addEventListener("input", (e: any) => this.#onInput());
this.#slider.addEventListener("mouseup", (e: any) => this.#onFinalize());
}
this.#value = <HTMLElement>this.#container.querySelector("#value");
}
}
#onValue()
{
if (this.#value != null && this.#slider != null)
this.#value.innerHTML = this.#minValue + Math.round(parseFloat(this.#slider.value) / 100 * (this.#maxValue - this.#minValue)) + this.#unit
this.setActive(true);
}
#onInput()
{
this.#onValue();
}
#onFinalize()
{
if (this.#slider != null)
this.#callback(this.#minValue + parseFloat(this.#slider.value) / 100 * (this.#maxValue - this.#minValue));
}
show()
{
if (this.#container != null)
this.#container.style.display = this.#display;
}
hide()
{
if (this.#container != null)
this.#container.style.display = 'none';
}
setActive(newActive: boolean)
{
if (this.#container)
{
this.#container.classList.toggle("active", newActive);
if (!newActive && this.#value != null)
this.#value.innerHTML = "Mixed values"
}
}
setMinMax(newMinValue: number, newMaxValue: number)
{
this.#minValue = newMinValue;
this.#maxValue = newMaxValue;
}
setValue(newValue: number)
{
if (this.#slider != null)
this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * 100);
this.#onValue()
}
}

View File

@ -99,7 +99,7 @@ export function attackUnit(ID: number, targetID: number) {
xhr.send(JSON.stringify(data));
}
export function cloneUnit(ID: number) {
export function cloneUnit(ID: number, latlng: L.LatLng) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
@ -109,12 +109,28 @@ export function cloneUnit(ID: number) {
}
};
var command = { "ID": ID };
var command = { "ID": ID, "location": latlng };
var data = { "cloneUnit": command }
xhr.send(JSON.stringify(data));
}
export function landAt(ID: number, latlng: L.LatLng) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
//console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " cloned");
}
};
var command = { "ID": ID, "location": latlng };
var data = { "landAt": command }
xhr.send(JSON.stringify(data));
}
export function changeSpeed(ID: number, speedChange: string) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
@ -131,6 +147,22 @@ export function changeSpeed(ID: number, speedChange: string) {
xhr.send(JSON.stringify(data));
}
export function setSpeed(ID: number, speed: number) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
//console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange);
}
};
var command = {"ID": ID, "speed": speed}
var data = {"setSpeed": command}
xhr.send(JSON.stringify(data));
}
export function changeAltitude(ID: number, altitudeChange: string) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
@ -147,6 +179,22 @@ export function changeAltitude(ID: number, altitudeChange: string) {
xhr.send(JSON.stringify(data));
}
export function setAltitude(ID: number, altitude: number) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
//console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange);
}
};
var command = {"ID": ID, "altitude": altitude}
var data = {"setAltitude": command}
xhr.send(JSON.stringify(data));
}
export function createFormation(ID: number, isLeader: boolean, wingmenIDs: number[]) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
@ -160,5 +208,37 @@ export function createFormation(ID: number, isLeader: boolean, wingmenIDs: numbe
var command = {"ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader}
var data = {"setLeader": command}
xhr.send(JSON.stringify(data));
}
export function setROE(ID: number, ROE: string) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
//console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange);
}
};
var command = {"ID": ID, "ROE": ROE}
var data = {"setROE": command}
xhr.send(JSON.stringify(data));
}
export function setReactionToThreat(ID: number, reactionToThreat: string) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
//console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange);
}
};
var command = {"ID": ID, "reactionToThreat": reactionToThreat}
var data = {"setReactionToThreat": command}
xhr.send(JSON.stringify(data));
}

View File

@ -10,6 +10,7 @@ import { Button } from "./controls/button";
import { MissionData } from "./missiondata/missiondata";
import { UnitControlPanel } from "./panels/unitcontrolpanel";
import { MouseInfoPanel } from "./panels/mouseInfoPanel";
import { Slider } from "./controls/slider";
/* TODO: should this be a class? */
var map: Map;
@ -36,6 +37,9 @@ var aiVisibilityButton: Button;
var weaponVisibilityButton: Button;
var deadVisibilityButton: Button;
var altitudeSlider: Slider;
var airspeedSlider: Slider;
var connected: boolean;
var activeCoalition: string;
@ -59,6 +63,10 @@ function setup() {
climbButton = new Button("climb-button", ["images/buttons/climb.svg"], () => { getUnitsManager().selectedUnitsChangeAltitude("climb"); });
descendButton = new Button("descend-button", ["images/buttons/descend.svg"], () => { getUnitsManager().selectedUnitsChangeAltitude("descend"); });
/* Unit control sliders */
altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => getUnitsManager().selectedUnitsSetAltitude(value * 0.3048));
airspeedSlider = new Slider("airspeed-slider", 0, 100, "kts", (value: number) => getUnitsManager().selectedUnitsSetSpeed(value / 1.94384));
/* Visibility buttons */
userVisibilityButton = new Button("user-visibility-button", ["images/buttons/user-full.svg", "images/buttons/user-partial.svg", "images/buttons/user-none.svg", "images/buttons/user-hidden.svg"], () => { });
aiVisibilityButton = new Button("ai-visibility-button", ["images/buttons/ai-full.svg", "images/buttons/ai-partial.svg", "images/buttons/ai-none.svg", "images/buttons/ai-hidden.svg"], () => { });
@ -184,4 +192,12 @@ export function getVisibilitySettings() {
return visibility;
}
export function getVisibilityButtons() {
return {user: userVisibilityButton, ai: aiVisibilityButton, weapon: weaponVisibilityButton, dead: deadVisibilityButton}
}
export function getUnitControlSliders() {
return {altitude: altitudeSlider, airspeed: airspeedSlider}
}
window.onload = setup;

View File

@ -154,6 +154,14 @@ export class Map extends L.Map {
getSelectionScroll().hide();
}
getMousePosition() {
return this.#lastMousePosition;
}
getMouseCoordinates() {
return this.containerPointToLatLng(this.#lastMousePosition);
}
/* Event handlers */
#onClick(e: any) {
if (!this.#preventLeftClick) {

View File

@ -1,7 +1,7 @@
import { imageOverlay } from "leaflet";
import { getUnitsManager } from "..";
import { getUnitControlSliders, getUnitsManager } from "..";
import { ConvertDDToDMS, rad2deg } from "../other/utils";
import { AirUnit, Unit } from "../units/unit";
import { Aircraft, AirUnit, GroundUnit, Helicopter, NavyUnit, Unit } from "../units/unit";
export class UnitControlPanel {
#element: HTMLElement
@ -21,6 +21,25 @@ export class UnitControlPanel {
var undoButton = <HTMLElement>formationCreationContainer.querySelector("#undo-formation");
undoButton?.addEventListener("click", () => getUnitsManager().selectedUnitsUndoFormation());
}
var ROEButtonsContainer = <HTMLElement>(this.#element.querySelector("#roe-buttons-container"));
if (ROEButtonsContainer != null)
{
(<HTMLElement>ROEButtonsContainer.querySelector("#free"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Free"));
(<HTMLElement>ROEButtonsContainer.querySelector("#designated-free"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Designated free"));
(<HTMLElement>ROEButtonsContainer.querySelector("#designated"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Designated"));
(<HTMLElement>ROEButtonsContainer.querySelector("#return"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Return"));
(<HTMLElement>ROEButtonsContainer.querySelector("#hold"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Hold"));
}
var reactionToThreatButtonsContainer = <HTMLElement>(this.#element.querySelector("#reaction-to-threat-buttons-container"));
if (reactionToThreatButtonsContainer != null)
{
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#none"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("None"));
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#passive"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("Passive"));
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#evade"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("Evade"));
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#escape"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("Escape"));
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#abort"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("Abort"));
}
this.hide();
}
}
@ -41,8 +60,83 @@ export class UnitControlPanel {
if (selectedUnitsContainer != null && formationCreationContainer != null)
{
this.#addUnitsButtons(units, selectedUnitsContainer);
this.#showFlightControlSliders(units);
this.#showFormationButtons(units, formationCreationContainer);
}
var ROEButtonsContainer = <HTMLElement>(this.#element.querySelector("#roe-buttons-container"));
if (ROEButtonsContainer != null)
{
(<HTMLElement>ROEButtonsContainer.querySelector("#free"))?.classList.toggle("white", this.#getROE(units) === "Free");
(<HTMLElement>ROEButtonsContainer.querySelector("#designated-free"))?.classList.toggle("white", this.#getROE(units) === "Designated free");
(<HTMLElement>ROEButtonsContainer.querySelector("#designated"))?.classList.toggle("white", this.#getROE(units) === "Designated");
(<HTMLElement>ROEButtonsContainer.querySelector("#return"))?.classList.toggle("white", this.#getROE(units) === "Return");
(<HTMLElement>ROEButtonsContainer.querySelector("#hold"))?.classList.toggle("white", this.#getROE(units) === "Hold");
}
var reactionToThreatButtonsContainer = <HTMLElement>(this.#element.querySelector("#reaction-to-threat-buttons-container"));
if (reactionToThreatButtonsContainer != null)
{
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#none"))?.classList.toggle("white", this.#getReactionToThreat(units) === "None");
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#passive"))?.classList.toggle("white", this.#getReactionToThreat(units) === "Passive");
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#evade"))?.classList.toggle("white", this.#getReactionToThreat(units) === "Evade");
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#escape"))?.classList.toggle("white", this.#getReactionToThreat(units) === "Escape");
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#abort"))?.classList.toggle("white", this.#getReactionToThreat(units) === "Abort");
}
}
}
#showFlightControlSliders(units: Unit[])
{
var sliders = getUnitControlSliders();
sliders.airspeed.show();
sliders.altitude.show();
if (this.#checkAllUnitsAircraft(units))
{
sliders.airspeed.setMinMax(100, 600);
sliders.altitude.setMinMax(0, 50000);
}
else if (this.#checkAllUnitsHelicopter(units))
{
sliders.airspeed.setMinMax(0, 200);
sliders.altitude.setMinMax(0, 10000);
}
else if (this.#checkAllUnitsGroundUnit(units))
{
sliders.airspeed.setMinMax(0, 60);
sliders.altitude.hide();
}
else if (this.#checkAllUnitsNavyUnit(units))
{
sliders.airspeed.setMinMax(0, 60);
sliders.altitude.hide();
}
else {
sliders.airspeed.hide();
sliders.altitude.hide();
}
var targetSpeed = this.#getTargetAirspeed(units);
if (targetSpeed != null)
{
sliders.airspeed.setActive(true);
sliders.airspeed.setValue(targetSpeed * 1.94384);
}
else
{
sliders.airspeed.setActive(false);
}
var targetAltitude = this.#getTargetAltitude(units);
if (targetAltitude != null)
{
sliders.altitude.setActive(true);
sliders.altitude.setValue(targetAltitude / 0.3048);
}
else
{
sliders.altitude.setActive(false);
}
}
@ -157,6 +251,38 @@ export class UnitControlPanel {
return true
}
#checkAllUnitsAircraft(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof Aircraft))
return false
return true
}
#checkAllUnitsHelicopter(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof Helicopter))
return false
return true
}
#checkAllUnitsGroundUnit(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof GroundUnit))
return false
return true
}
#checkAllUnitsNavyUnit(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof NavyUnit))
return false
return true
}
#checkAllUnitsSameFormation(units: Unit[])
{
var leaderFound = false;
@ -182,4 +308,56 @@ export class UnitControlPanel {
return true
return false
}
#getTargetAirspeed(units: Unit[])
{
var airspeed = null;
for (let unit of units)
{
if (unit.targetSpeed != airspeed && airspeed != null)
return null
else
airspeed = unit.targetSpeed;
}
return airspeed;
}
#getTargetAltitude(units: Unit[])
{
var altitude = null;
for (let unit of units)
{
if (unit.targetAltitude != altitude && altitude != null)
return null
else
altitude = unit.targetAltitude;
}
return altitude;
}
#getROE(units: Unit[])
{
var ROE = null;
for (let unit of units)
{
if (unit.ROE !== ROE && ROE != null)
return null
else
ROE = unit.ROE;
}
return ROE;
}
#getReactionToThreat(units: Unit[])
{
var reactionToThreat = null;
for (let unit of units)
{
if (unit.reactionToThreat !== reactionToThreat && reactionToThreat != null)
return null
else
reactionToThreat = unit.reactionToThreat;
}
return reactionToThreat;
}
}

View File

@ -2,7 +2,7 @@ import { Marker, LatLng, Polyline, Icon } from 'leaflet';
import { ConvertDDToDMS } from '../other/utils';
import { getMap, getUnitsManager, getVisibilitySettings } from '..';
import { UnitMarker, MarkerOptions, AircraftMarker, HelicopterMarker, GroundUnitMarker, NavyUnitMarker, WeaponMarker } from './unitmarker';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader } from '../dcs/dcs';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, landAt, setAltitude, setReactionToThreat, setROE, setSpeed } from '../dcs/dcs';
var pathIcon = new Icon({
iconUrl: 'images/marker-icon.png',
@ -36,6 +36,10 @@ export class Unit {
leaderID: number = 0;
wingmen: Unit[] = [];
wingmenIDs: number[] = [];
targetSpeed: number = 0;
targetAltitude: number = 0;
ROE: string = "";
reactionToThreat: string = "";
#selectable: boolean;
#selected: boolean = false;
@ -296,7 +300,6 @@ export class Unit {
}
}
attackUnit(targetID: number) {
/* Call DCS attackUnit function */
if (this.ID != targetID) {
@ -307,7 +310,11 @@ export class Unit {
}
}
landAt(latlng: LatLng)
{
landAt(this.ID, latlng);
}
changeSpeed(speedChange: string)
{
changeSpeed(this.ID, speedChange);
@ -318,6 +325,26 @@ export class Unit {
changeAltitude(this.ID, altitudeChange);
}
setSpeed(speed: number)
{
setSpeed(this.ID, speed);
}
setAltitude(altitude: number)
{
setAltitude(this.ID, altitude);
}
setROE(ROE: string)
{
setROE(this.ID, ROE);
}
setReactionToThreat(reactionToThreat: string)
{
setReactionToThreat(this.ID, reactionToThreat);
}
/*
setformation(formation)
{

View File

@ -15,6 +15,17 @@ export class UnitsManager {
document.addEventListener('paste', () => this.pasteUnits());
}
#updateUnitControlPanel() {
/* Update the unit control panel */
if (this.getSelectedUnits().length > 0) {
getUnitControlPanel().show();
getUnitControlPanel().update(this.getSelectedLeaders().concat(this.getSelectedSingletons()));
}
else {
getUnitControlPanel().hide();
}
}
getUnits() {
return this.#units;
}
@ -167,7 +178,11 @@ export class UnitsManager {
selectedUnitsLandAt(latlng: LatLng)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].landAt(latlng);
}
}
selectedUnitsChangeSpeed(speedChange: string)
@ -177,6 +192,8 @@ export class UnitsManager {
{
selectedUnits[idx].changeSpeed(speedChange);
}
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
selectedUnitsChangeAltitude(altitudeChange: string)
@ -186,8 +203,51 @@ export class UnitsManager {
{
selectedUnits[idx].changeAltitude(altitudeChange);
}
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
selectedUnitsSetSpeed(speed: number)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].setSpeed(speed);
}
}
selectedUnitsSetAltitude(altitude: number)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].setAltitude(altitude);
}
}
selectedUnitsSetROE(ROE: string)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].setROE(ROE);
}
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
selectedUnitsSetReactionToThreat(reactionToThreat: string)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].setReactionToThreat(reactionToThreat);
}
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
copyUnits()
{
this.#copiedUnits = this.getSelectedUnits();
@ -198,7 +258,7 @@ export class UnitsManager {
for (let idx in this.#copiedUnits)
{
var unit = this.#copiedUnits[idx];
cloneUnit(unit.ID);
cloneUnit(unit.ID, getMap().getMouseCoordinates());
}
}
@ -259,7 +319,7 @@ export class UnitsManager {
console.log("At least 2 units must be selected to create a formation.");
}
}
setTimeout(() => this.#updateUnitControlPanel(), 1000); // TODO find better method, may fail
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
selectedUnitsUndoFormation(ID: number | null = null)
@ -268,17 +328,6 @@ export class UnitsManager {
{
leader.setLeader(false);
}
setTimeout(() => this.#updateUnitControlPanel(), 1000); // TODO find better method, may fail
}
#updateUnitControlPanel() {
/* Update the unit control panel */
if (this.getSelectedUnits().length > 0) {
getUnitControlPanel().show();
getUnitControlPanel().update(this.getSelectedLeaders().concat(this.getSelectedSingletons()));
}
else {
getUnitControlPanel().hide();
}
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
}

View File

@ -18,7 +18,20 @@
</div>
<div class="hl"></div>
<!--
<div id="section-label">Controls</div>
<div id="flight-controls-buttons-container">
<div class="slider-container flight-control-slider" id="altitude-slider">
<div class="flight-control-title">Altitude</div>
<div class="flight-control-value" id="value"></div>
<input type="range" min="1" max="100" value="50" class="slider">
</div>
<div class="slider-container flight-control-slider" id="airspeed-slider">
<div class="flight-control-title">Speed</div>
<div class="flight-control-value" id="value"></div>
<input type="range" min="1" max="100" value="50" class="slider">
</div>
</div>
<div id="section-label">Formation</div>
<div id="formation-buttons-container">
<div class="rectangular-button">Echelon</div>
@ -31,22 +44,22 @@
<div id="section-label">Rules of engagement</div>
<div id="roe-buttons-container">
<div class="rectangular-button">Free</div>
<div class="rectangular-button">Designated free</div>
<div class="rectangular-button">Designated</div>
<div class="rectangular-button">Return</div>
<div class="rectangular-button">Hold</div>
<div class="rectangular-button" id="free">Free</div>
<div class="rectangular-button" id="designated-free">Designated free</div>
<div class="rectangular-button" id="designated">Designated</div>
<div class="rectangular-button" id="return">Return</div>
<div class="rectangular-button" id="hold">Hold</div>
</div>
<div class="hl"></div>
<div id="section-label">Reaction to threat</div>
<div id="threat-reaction-buttons-container">
<div class="rectangular-button">None</div>
<div class="rectangular-button">Passive</div>
<div class="rectangular-button">Evade</div>
<div class="rectangular-button">Escape</div>
<div class="rectangular-button">Abort</div>
<div id="reaction-to-threat-buttons-container">
<div class="rectangular-button" id="none">None</div>
<div class="rectangular-button" id="passive">Passive</div>
<div class="rectangular-button" id="evade">Evade</div>
<div class="rectangular-button" id="escape">Escape</div>
<div class="rectangular-button" id="abort">Abort</div>
</div>
-->
</div>

View File

@ -2,7 +2,7 @@
[Setup]
AppName=DCS Olympus
AppVerName=DCS Olympus Alpha v0.0.3
AppVerName=DCS Olympus Alpha v0.0.5
DefaultDirName={usersavedgames}\DCS.openbeta
DefaultGroupName=DCSOlympus
OutputBaseFilename=DCSOlympus

View File

@ -82,12 +82,20 @@ function Olympus.move(ID, lat, lng, altitude, speed, category, taskOptions)
if unit then
if category == "Aircraft" then
local startPoint = mist.getLeadPos(unit:getGroup())
local endPoint = coord.LLtoLO(lat, lng, 0)
local endPoint = coord.LLtoLO(lat, lng, 0)
local path = {
[1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
}
local path = {}
if taskOptions and taskOptions['id'] == 'Land' then
path = {
[1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, landing, speed, 0, 'AGL')
}
else
path = {
[1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
}
end
-- If a task exists assign it to the controller
if taskOptions then
@ -273,7 +281,7 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions)
task = 'CAP',
}
mist.dynAdd(vars)
local newGroup = mist.dynAdd(vars)
-- Save the payload to be reused in case the unit is cloned. TODO: save by ID not by name (it works but I like consistency)
Olympus.payloadRegistry[vars.name] = payload
@ -282,22 +290,30 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions)
end
-- Clones a unit by ID. Will clone the unit with the same original payload as the source unit. TODO: only works on Olympus unit not ME units.
function Olympus.clone(ID)
function Olympus.clone(ID, lat, lng)
Olympus.notify("Olympus.clone " .. ID, 2)
local unit = Olympus.getUnitByID(ID)
if unit then
local coalition = Olympus.getCoalitionByCoalitionID(unit:getCoalition())
local lat, lng, alt = coord.LOtoLL(unit:getPoint())
-- TODO: only works on Aircraft
local spawnOptions = {
payload = Olympus.payloadRegistry[unitName]
payload = Olympus.payloadRegistry[unit:getName()]
}
Olympus.spawnAircraft(coalition, unit:getTypeName(), lat + 0.001, lng + 0.001, spawnOptions)
Olympus.spawnAircraft(coalition, unit:getTypeName(), lat, lng, spawnOptions)
end
Olympus.notify("Olympus.clone completed successfully", 2)
end
function Olympus.delete(ID, lat, lng)
Olympus.notify("Olympus.delete " .. ID, 2)
local unit = Olympus.getUnitByID(ID)
if unit then
unit:destroy();
Olympus.notify("Olympus.delete completed successfully", 2)
end
end
function Olympus.setTask(ID, taskOptions)
Olympus.notify("Olympus.setTask " .. ID .. " " .. Olympus.serializeTable(taskOptions), 2)
local unit = Olympus.getUnitByID(ID)
@ -337,7 +353,6 @@ function Olympus.setOption(ID, optionID, optionValue)
end
end
function Olympus.serializeTable(val, name, skipnewlines, depth)
skipnewlines = skipnewlines or false
depth = depth or 0
@ -370,5 +385,4 @@ function Olympus.serializeTable(val, name, skipnewlines, depth)
return tmp
end
Olympus.notify("OlympusCommand script loaded successfully", 2)

View File

@ -11,6 +11,54 @@ namespace CommandType {
enum CommandTypes { NO_TYPE, MOVE, SMOKE, SPAWN_AIR, SPAWN_GROUND, CLONE, FOLLOW, RESET_TASK, SET_OPTION, SET_COMMAND, SET_TASK };
};
namespace SetCommandType {
enum SetCommandTypes {
ROE = 0,
REACTION_ON_THREAT = 1,
RADAR_USING = 3,
FLARE_USING = 4,
Formation = 5,
RTB_ON_BINGO = 6,
SILENCE = 7,
RTB_ON_OUT_OF_AMMO = 10,
ECM_USING = 13,
PROHIBIT_AA = 14,
PROHIBIT_JETT = 15,
PROHIBIT_AB = 16,
PROHIBIT_AG = 17,
MISSILE_ATTACK = 18,
PROHIBIT_WP_PASS_REPORT = 19,
OPTION_RADIO_USAGE_CONTACT = 21,
OPTION_RADIO_USAGE_ENGAGE = 22,
OPTION_RADIO_USAGE_KILL = 23,
JETT_TANKS_IF_EMPTY = 25,
FORCED_ATTACK = 26
};
}
namespace ROE {
enum ROEs {
WEAPON_FREE = 0,
OPEN_FIRE_WEAPON_FREE = 1,
OPEN_FIRE = 2,
RETURN_FIRE = 3,
WEAPON_HOLD = 4,
};
}
namespace ReactionToThreat {
enum ReactionToThreats {
NO_REACTION = 0,
PASSIVE_DEFENCE = 1,
EVADE_FIRE = 2,
BYPASS_AND_ESCAPE = 3,
ALLOW_ABORT_MISSION = 4
};
}
/* Base command class */
class Command
{
@ -117,14 +165,32 @@ private:
class Clone : public Command
{
public:
Clone(int ID) :
ID(ID)
Clone(int ID, Coords location) :
ID(ID),
location(location)
{
priority = CommandPriority::LOW;
type = CommandType::CLONE;
};
virtual wstring getString(lua_State* L);
private:
const int ID;
const Coords location;
};
/* Delete unit command */
class Delete : public Command
{
public:
Delete(int ID) :
ID(ID)
{
priority = CommandPriority::HIGH;
type = CommandType::CLONE;
};
virtual wstring getString(lua_State* L);
private:
const int ID;
};

View File

@ -5,7 +5,7 @@
#include "luatools.h"
namespace State {
enum States { IDLE, REACH_DESTINATION, ATTACK, WINGMAN, FOLLOW, RTB, REFUEL, AWACS, EWR, TANKER, RUN_AWAY };
enum States { IDLE, REACH_DESTINATION, ATTACK, WINGMAN, FOLLOW, LAND, REFUEL, AWACS, EWR, TANKER, RUN_AWAY };
};
class Unit
@ -31,6 +31,9 @@ public:
void setWingmen(vector<Unit*> newWingmen) { wingmen = newWingmen; }
void setFormation(wstring newFormation) { formation = newFormation; }
void setFormationOffset(Offset formationOffset);
void setROE(wstring newROE);
void setReactionToThreat(wstring newReactionToThreat);
void landAt(Coords loc);
int getID() { return ID; }
wstring getName() { return name; }
@ -65,33 +68,35 @@ public:
protected:
int ID;
int state = State::IDLE;
bool hasTask = false;
bool AI = false;
bool alive = true;
wstring name = L"undefined";
wstring unitName = L"undefined";
wstring groupName = L"undefined";
json::value type = json::value::null();
int country = NULL;
int coalitionID = NULL;
double latitude = NULL;
double longitude = NULL;
double altitude = NULL;
double heading = NULL;
double speed = NULL;
json::value flags = json::value::null();
int targetID = NULL;
wstring currentTask = L"";
bool isLeader = false;
bool isWingman = false;
Offset formationOffset = Offset(NULL);
wstring formation = L"";
Unit* leader = nullptr;
int state = State::IDLE;
bool hasTask = false;
bool AI = false;
bool alive = true;
wstring name = L"undefined";
wstring unitName = L"undefined";
wstring groupName = L"undefined";
json::value type = json::value::null();
int country = NULL;
int coalitionID = NULL;
double latitude = NULL;
double longitude = NULL;
double altitude = NULL;
double heading = NULL;
double speed = NULL;
json::value flags = json::value::null();
int targetID = NULL;
wstring currentTask = L"";
bool isLeader = false;
bool isWingman = false;
Offset formationOffset = Offset(NULL);
wstring formation = L"";
Unit* leader = nullptr;
wstring ROE = L"";
wstring reactionToThreat = L"";
vector<Unit*> wingmen;
double targetSpeed = 0;
double targetAltitude = 0;
double fuel = 0;
double targetSpeed = 0;
double targetAltitude = 0;
double fuel = 0;
json::value ammo;
json::value targets;

View File

@ -12,8 +12,10 @@ public:
virtual void changeAltitude(wstring change);
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
virtual void setTargetSpeed(double newTargetSpeed);
virtual void setTargetAltitude(double newTargetAltitude);
protected:
double targetSpeed = 150;
double targetAltitude = 5000;
double targetSpeed = 300 / 1.94384;
double targetAltitude = 20000 * 0.3048;
};

View File

@ -15,8 +15,8 @@ public:
virtual wstring getCategory() = 0;
virtual void changeSpeed(wstring change) {};
virtual void changeAltitude(wstring change) {};
virtual void setTargetSpeed(double newTargetSpeed);
virtual void setTargetAltitude(double newTargetAltitude);
virtual void setTargetSpeed(double newTargetSpeed) {};
virtual void setTargetAltitude(double newTargetAltitude) {};
protected:
virtual void AIloop();

View File

@ -12,8 +12,10 @@ public:
virtual void changeAltitude(wstring change);
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
virtual void setTargetSpeed(double newTargetSpeed);
virtual void setTargetAltitude(double newTargetAltitude);
protected:
double targetSpeed = 50;
double targetAltitude = 1000;
double targetSpeed = 100 / 1.94384;
double targetAltitude = 5000 * 0.3048;
};

View File

@ -14,6 +14,7 @@ public:
void updateExportData(lua_State* L);
void updateMissionData(json::value missionData);
void updateAnswer(json::value& answer);
void deleteUnit(int ID);
private:
map<int, Unit*> units;

View File

@ -69,7 +69,19 @@ wstring Clone::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.clone, "
commandSS << "Olympus.clone, "
<< ID << ", "
<< location.lat << ", "
<< location.lng;
return commandSS.str();
}
/* Delete unit command */
wstring Delete::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.delete, "
<< ID;
return commandSS.str();
}

View File

@ -182,7 +182,10 @@ void Scheduler::handleRequest(wstring key, json::value value)
else if (key.compare(L"cloneUnit") == 0)
{
int ID = value[L"ID"].as_integer();
command = dynamic_cast<Command*>(new Clone(ID));
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
Coords loc; loc.lat = lat; loc.lng = lng;
command = dynamic_cast<Command*>(new Clone(ID, loc));
log(L"Cloning unit " + to_wstring(ID));
}
else if (key.compare(L"setLeader") == 0)
@ -219,6 +222,34 @@ void Scheduler::handleRequest(wstring key, json::value value)
wstring formation = value[L"formation"].as_string();
unit->setFormation(formation);
}
else if (key.compare(L"setROE") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
wstring ROE = value[L"ROE"].as_string();
unit->setROE(ROE);
}
else if (key.compare(L"setReactionToThreat") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
wstring reactionToThreat = value[L"reactionToThreat"].as_string();
unit->setReactionToThreat(reactionToThreat);
}
else if (key.compare(L"landAt") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
Coords loc; loc.lat = lat; loc.lng = lng;
unit->landAt(loc);
}
else if (key.compare(L"deleteUnit") == 0)
{
int ID = value[L"ID"].as_integer();
unitsFactory->deleteUnit(ID);
}
else
{
log(L"Unknown command: " + key);

View File

@ -118,9 +118,11 @@ json::value Unit::json()
json[L"fuel"] = fuel;
json[L"ammo"] = ammo;
json[L"targets"] = targets;
json[L"targetSpeed"] = targetSpeed;
json[L"targetAltitude"] = targetAltitude;
json[L"targetSpeed"] = getTargetSpeed();
json[L"targetAltitude"] = getTargetAltitude();
json[L"hasTask"] = hasTask;
json[L"ROE"] = json::value::string(ROE);
json[L"reactionToThreat"] = json::value::string(reactionToThreat);
int i = 0;
for (auto itr = wingmen.begin(); itr != wingmen.end(); itr++)
@ -221,4 +223,48 @@ void Unit::setFormationOffset(Offset newFormationOffset)
{
formationOffset = newFormationOffset;
resetTask();
}
void Unit::setROE(wstring newROE) {
ROE = newROE;
int ROEEnum;
if (newROE.compare(L"Free") == 0)
ROEEnum = ROE::WEAPON_FREE;
else if (newROE.compare(L"Designated free") == 0)
ROEEnum = ROE::OPEN_FIRE_WEAPON_FREE;
else if (newROE.compare(L"Designated") == 0)
ROEEnum = ROE::OPEN_FIRE;
else if (newROE.compare(L"Return") == 0)
ROEEnum = ROE::RETURN_FIRE;
else if (newROE.compare(L"Hold") == 0)
ROEEnum = ROE::WEAPON_HOLD;
else
return;
Command* command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::ROE, ROEEnum));
scheduler->appendCommand(command);
}
void Unit::setReactionToThreat(wstring newReactionToThreat) {
reactionToThreat = newReactionToThreat;
int reactionToThreatEnum;
if (newReactionToThreat.compare(L"None") == 0)
reactionToThreatEnum = ReactionToThreat::NO_REACTION;
else if (newReactionToThreat.compare(L"Passive") == 0)
reactionToThreatEnum = ReactionToThreat::PASSIVE_DEFENCE;
else if (newReactionToThreat.compare(L"Evade") == 0)
reactionToThreatEnum = ReactionToThreat::EVADE_FIRE;
else if (newReactionToThreat.compare(L"Escape") == 0)
reactionToThreatEnum = ReactionToThreat::BYPASS_AND_ESCAPE;
else if (newReactionToThreat.compare(L"Abort") == 0)
reactionToThreatEnum = ReactionToThreat::ALLOW_ABORT_MISSION;
else
return;
Command* command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum));
scheduler->appendCommand(command);
}
void Unit::landAt(Coords loc) {
activePath.clear();
activePath.push_back(loc);
setState(State::LAND);
}

View File

@ -48,11 +48,21 @@ void Aircraft::changeAltitude(wstring change)
{
if (targetAltitude > 5000)
targetAltitude += 2500 / 3.28084;
else if (targetAltitude > 0)
else if (targetAltitude >= 0)
targetAltitude += 500 / 3.28084;
}
if (targetAltitude < 0)
targetAltitude = 0;
goToDestination(); /* Send the command to reach the destination */
}
void Aircraft::setTargetSpeed(double newTargetSpeed) {
targetSpeed = newTargetSpeed;
goToDestination();
}
void Aircraft::setTargetAltitude(double newTargetAltitude) {
targetAltitude = newTargetAltitude;
goToDestination();
}

View File

@ -41,6 +41,9 @@ void AirUnit::setState(int newState)
return;
break;
}
case State::LAND: {
break;
}
default:
break;
}
@ -72,6 +75,10 @@ void AirUnit::setState(int newState)
resetActiveDestination();
break;
}
case State::LAND: {
resetActiveDestination();
break;
}
default:
break;
}
@ -242,6 +249,22 @@ void AirUnit::AIloop()
break;
}
case State::LAND: {
wstring enrouteTask = L"{" "id = 'land' }";
currentTask = L"Landing";
if (activeDestination == NULL)
{
setActiveDestination();
goToDestination(enrouteTask);
}
if (isLeader)
taskWingmen();
break;
}
case State::ATTACK: {
/* If the target is not alive (either not set or was succesfully destroyed) go back to REACH_DESTINATION */
if (!isTargetAlive()) {
@ -319,13 +342,3 @@ void AirUnit::AIloop()
break;
}
}
void AirUnit::setTargetSpeed(double newTargetSpeed) {
targetSpeed = newTargetSpeed;
goToDestination();
}
void AirUnit::setTargetAltitude(double newTargetAltitude) {
targetAltitude = newTargetAltitude;
goToDestination();
}

View File

@ -48,7 +48,7 @@ void Helicopter::changeAltitude(wstring change)
{
if (targetAltitude > 100)
targetAltitude += 100 / 3.28084;
else if (targetAltitude > 0)
else if (targetAltitude >= 0)
targetAltitude += 10 / 3.28084;
}
if (targetAltitude < 0)
@ -56,3 +56,14 @@ void Helicopter::changeAltitude(wstring change)
goToDestination(); /* Send the command to reach the destination */
}
void Helicopter::setTargetSpeed(double newTargetSpeed) {
targetSpeed = newTargetSpeed;
goToDestination();
}
void Helicopter::setTargetAltitude(double newTargetAltitude) {
targetAltitude = newTargetAltitude;
goToDestination();
}

View File

@ -7,6 +7,10 @@
#include "groundunit.h"
#include "navyunit.h"
#include "weapon.h"
#include "commands.h"
#include "scheduler.h"
extern Scheduler* scheduler;
UnitsFactory::UnitsFactory(lua_State* L)
{
@ -116,3 +120,12 @@ void UnitsFactory::updateAnswer(json::value& answer)
answer[L"units"] = unitsJson;
}
void UnitsFactory::deleteUnit(int ID)
{
if (getUnit(ID) != nullptr)
{
Command* command = dynamic_cast<Command*>(new Delete(ID));
scheduler->appendCommand(command);
}
}