Added options for miss on purpose and scenic AAA

This commit is contained in:
Pax1601
2023-10-03 15:25:45 +02:00
parent 4da407008e
commit ce5f00e075
23 changed files with 9430 additions and 7539 deletions

View File

@@ -688,7 +688,6 @@ export class Map extends L.Map {
this.#isZooming = false;
}
#panToUnit(unit: Unit) {
var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng);
this.setView(unitPosition, this.getZoom(), { animate: false });

View File

@@ -17,6 +17,7 @@ export class UnitControlPanel extends Panel {
#speedTypeSwitch: Switch;
#onOffSwitch: Switch;
#followRoadsSwitch: Switch;
#operateAs: Switch;
#TACANXYDropdown: Dropdown;
#radioDecimalsDropdown: Dropdown;
#radioCallsignDropdown: Dropdown;
@@ -67,6 +68,11 @@ export class UnitControlPanel extends Panel {
getApp().getUnitsManager().selectedUnitsSetFollowRoads(value);
});
/* Operate as */
this.#operateAs = new Switch("operate-as-switch", (value: boolean) => {
//getApp().getUnitsManager().selectedUnitsSetFollowRoads(value);
});
/* Advanced settings dialog */
this.#advancedSettingsDialog = <HTMLElement> document.querySelector("#advanced-settings-dialog");
@@ -80,21 +86,28 @@ export class UnitControlPanel extends Panel {
/* Events and timer */
window.setInterval(() => {this.update();}, 25);
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => { this.show(); this.addButtons();});
document.addEventListener("clearSelection", () => { this.hide() });
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => {
this.show();
this.addButtons();
this.#updateRapidControls();
});
document.addEventListener("clearSelection", () => {
this.hide();
this.#updateRapidControls();
});
document.addEventListener("applyAdvancedSettings", () => {this.#applyAdvancedSettings();})
document.addEventListener("showAdvancedSettings", () => {
this.#updateAdvancedSettingsDialog(getApp().getUnitsManager().getSelectedUnits());
this.#advancedSettingsDialog.classList.remove("hide");
});
this.hide();
// This is for when a ctrl-click happens on the map for deselection and we need to remove the selected unit from the panel
/* This is for when a ctrl-click happens on the map for deselection and we need to remove the selected unit from the panel */
document.addEventListener( "unitDeselection", ( ev:CustomEventInit ) => {
this.getElement().querySelector( `button[data-unit-id="${ev.detail.ID}"]` )?.remove();
this.#updateRapidControls();
});
this.hide();
}
show() {
@@ -157,6 +170,7 @@ export class UnitControlPanel extends Panel {
element.toggleAttribute("data-show-emissions-countermeasures", (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")) && !(this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-on-off", (this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")) && !(this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")));
element.toggleAttribute("data-show-follow-roads", (this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit")));
element.toggleAttribute("data-show-operate-as", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) === "neutral");
element.toggleAttribute("data-show-advanced-settings-button", this.#units.length == 1);
if (this.#selectedUnitsTypes.length == 1) {
@@ -208,6 +222,59 @@ export class UnitControlPanel extends Panel {
}
}
#updateRapidControls() {
var options: { [key: string]: { text: string, tooltip: string, type: string } } | null = null;
var selectedUnits = getApp().getUnitsManager().getSelectedUnits();
var showAltitudeChange = selectedUnits.some((unit: Unit) => {return ["Aircraft", "Helicopter"].includes(unit.getCategory());});
this.getElement().querySelector("#climb")?.classList.toggle("hide", !showAltitudeChange);
this.getElement().querySelector("#descend")?.classList.toggle("hide", !showAltitudeChange);
/* Keep only the common "and" options, unless a single unit is selected */
selectedUnits.forEach((unit: Unit) => {
var unitOptions = unit.getActions();
if (options === null) {
options = unitOptions;
} else {
/* Delete all the "or" type options */
for (let optionKey in options) {
if (options[optionKey].type == "or") {
delete options[optionKey];
}
}
/* Options of "and" type get shown if ALL units have it */
for (let optionKey in options) {
if (!(optionKey in unitOptions)) {
delete options[optionKey];
}
}
}
});
options = options ?? {};
const rapidControlsContainer = this.getElement().querySelector("#rapid-controls") as HTMLElement;
const unitActionButtons = rapidControlsContainer.querySelectorAll(".unit-action-button");
for (let button of unitActionButtons) {
rapidControlsContainer.removeChild(button);
}
for (let option in options) {
let button = document.createElement("button");
button.title = options[option].tooltip;
button.classList.add("ol-button", "unit-action-button");
button.id = option;
rapidControlsContainer.appendChild(button);
button.onclick = () => {
/* Since only common actions are shown in the rapid controls, we execute it only on the first unit */
if (selectedUnits.length > 0)
selectedUnits[0].executeAction(null, option);
}
}
}
#updateAdvancedSettingsDialog(units: Unit[])
{
if (units.length == 1)

View File

@@ -329,14 +329,14 @@ export class ServerManager {
this.PUT(data, callback);
}
scenicAAA(ID: number, callback: CallableFunction = () => {}) {
var command = { "ID": ID }
scenicAAA(ID: number, coalition: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "coalition": coalition }
var data = { "scenicAAA": command }
this.PUT(data, callback);
}
missOnPurpose(ID: number, callback: CallableFunction = () => {}) {
var command = { "ID": ID }
missOnPurpose(ID: number, coalition: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "coalition": coalition }
var data = { "missOnPurpose": command }
this.PUT(data, callback);
}

View File

@@ -588,15 +588,7 @@ export class Unit extends CustomMarker {
}
isInViewport() {
const mapBounds = getApp().getMap().getBounds();
const unitPos = this.getPosition();
return (unitPos.lng > mapBounds.getWest()
&& unitPos.lng < mapBounds.getEast()
&& unitPos.lat > mapBounds.getSouth()
&& unitPos.lat < mapBounds.getNorth());
return getApp().getMap().getBounds().contains(this.getPosition());
}
/********************** Unit commands *************************/
@@ -739,14 +731,40 @@ export class Unit extends CustomMarker {
});
}
scenicAAA() {
getApp().getServerManager().scenicAAA(this.ID);
scenicAAA(coalition: string) {
getApp().getServerManager().scenicAAA(this.ID, coalition);
}
missOnPurpose() {
getApp().getServerManager().missOnPurpose(this.ID);
missOnPurpose(coalition: string) {
getApp().getServerManager().missOnPurpose(this.ID, coalition);
}
/***********************************************/
getActions(): { [key: string]: { text: string, tooltip: string, type: string } } {
/* To be implemented by child classes */ // TODO make Unit an abstract class
return {};
}
executeAction(e: any, action: string) {
if (action === "center-map")
getApp().getMap().centerOnUnit(this.ID);
if (action === "attack")
getApp().getUnitsManager().selectedUnitsAttackUnit(this.ID);
else if (action === "refuel")
getApp().getUnitsManager().selectedUnitsRefuel();
else if (action === "group-ground" || action === "group-navy")
getApp().getUnitsManager().selectedUnitsCreateGroup();
else if (action === "scenic-aaa-red")
getApp().getUnitsManager().selectedUnitsScenicAAA("red");
else if (action === "scenic-aaa-blue")
getApp().getUnitsManager().selectedUnitsScenicAAA("blue");
else if (action === "miss-aaa-red")
getApp().getUnitsManager().selectedUnitsMissOnPurpose("red");
else if (action === "miss-aaa-blue")
getApp().getUnitsManager().selectedUnitsMissOnPurpose("blue");
else if (action === "follow")
this.#showFollowOptions(e);
}
/***********************************************/
onAdd(map: Map): this {
@@ -761,18 +779,18 @@ export class Unit extends CustomMarker {
if (this.#waitingForDoubleClick) {
return;
}
// We'll wait for a doubleclick
this.#waitingForDoubleClick = true;
this.#doubleClickTimer = window.setTimeout(() => {
this.#doubleClickTimer = window.setTimeout(() => {
// Still waiting so no doubleclick; do the click action
if (this.#waitingForDoubleClick) {
if (getApp().getMap().getState() === IDLE || getApp().getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) {
if (!e.originalEvent.ctrlKey)
getApp().getUnitsManager().deselectAllUnits();
this.setSelected(!this.getSelected());
const detail = { "detail": { "unit": this } };
if (this.getSelected())
@@ -788,7 +806,6 @@ export class Unit extends CustomMarker {
}
#onDoubleClick(e: any) {
// Let single clicks work again
this.#waitingForDoubleClick = false;
clearTimeout(this.#doubleClickTimer);
@@ -801,58 +818,49 @@ export class Unit extends CustomMarker {
});
}
#onContextMenu(e: any) {
var options: { [key: string]: { text: string, tooltip: string } } = {};
const selectedUnits = getApp().getUnitsManager().getSelectedUnits();
const selectedUnitTypes = getApp().getUnitsManager().getSelectedUnitsCategories();
getActionOptions() {
var options: { [key: string]: { text: string, tooltip: string, type: string } } | null = null;
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it" };
var units = getApp().getUnitsManager().getSelectedUnits();
units.push(this);
if (selectedUnits.length > 0 && !(selectedUnits.length == 1 && (selectedUnits.includes(this)))) {
options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons" };
if (getApp().getUnitsManager().getSelectedUnitsCategories().length == 1 && getApp().getUnitsManager().getSelectedUnitsCategories()[0] === "Aircraft")
options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position" };
}
else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) {
if (this.getCategory() == "Aircraft") {
options["refuel"] = { text: "Air to air refuel", tooltip: "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB." }; // TODO Add some way of knowing which aircraft can AAR
/* Keep only the common "or" options or any "and" option */
units.forEach((unit: Unit) => {
var unitOptions = unit.getActions();
if (options === null) {
options = unitOptions;
} else {
/* Options of "or" type get shown if any one unit has it*/
for (let optionKey in unitOptions) {
if (unitOptions[optionKey].type == "or") {
options[optionKey] = unitOptions[optionKey];
}
}
/* Options of "and" type get shown if ALL units have it */
for (let optionKey in options) {
if (!(optionKey in unitOptions)) {
delete options[optionKey];
}
}
}
}
});
if (selectedUnitTypes.length === 1 && ["NavyUnit", "GroundUnit"].includes(selectedUnitTypes[0]) && getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) !== undefined)
options["group"] = { text: "Create group", tooltip: "Create a group from the selected units." };
return options ?? {};
}
if (selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0]) && selectedUnits.every((unit: Unit) => { return ["AAA"].includes(unit.getType())})) {
options["scenic-aaa"] = { text: "Scenic AAA", tooltip: "Shoot AAA in the air without aiming at any target" };
options["miss-aaa"] = { text: "Miss on purpose AAA", tooltip: "Shoot AAA towards the closes enemy, but don't aim precisely" };
}
#onContextMenu(e: any) {
var options = this.getActionOptions();
if (Object.keys(options).length > 0) {
getApp().getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
getApp().getMap().getUnitContextMenu().setOptions(options, (option: string) => {
getApp().getMap().hideUnitContextMenu();
this.#executeAction(e, option);
this.executeAction(e, option);
});
}
}
#executeAction(e: any, action: string) {
if (action === "center-map")
getApp().getMap().centerOnUnit(this.ID);
if (action === "attack")
getApp().getUnitsManager().selectedUnitsAttackUnit(this.ID);
else if (action === "refuel")
getApp().getUnitsManager().selectedUnitsRefuel();
else if (action === "group")
getApp().getUnitsManager().selectedUnitsCreateGroup();
else if (action === "scenic-aaa")
getApp().getUnitsManager().selectedUnitsScenicAAA();
else if (action === "miss-aaa")
getApp().getUnitsManager().selectedUnitsMissOnPurpose();
else if (action === "follow")
this.#showFollowOptions(e);
}
#showFollowOptions(e: any) {
var options: { [key: string]: { text: string, tooltip: string } } = {};
@@ -892,14 +900,14 @@ export class Unit extends CustomMarker {
var angleRad = deg2rad(angleDeg);
var distance = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#distance`)?.querySelector("input")).value));
var upDown = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#up-down`)?.querySelector("input")).value));
// X: front-rear, positive front
// Y: top-bottom, positive top
// Z: left-right, positive right
var x = distance * Math.cos(angleRad);
var y = upDown;
var z = distance * Math.sin(angleRad);
getApp().getUnitsManager().selectedUnitsFollowUnit(this.ID, { "x": x, "y": y, "z": z });
}
});
@@ -1166,6 +1174,37 @@ export class AirUnit extends Unit {
rotateToHeading: false
};
}
getActions() {
var options: { [key: string]: { text: string, tooltip: string, type: string } } = {};
/* Options if this unit is not selected */
if (!this.getSelected()) {
/* Someone else is selected */
if (getApp().getUnitsManager().getSelectedUnits().length > 0) {
options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons", type: "or" };
options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position", type: "or" };
} else {
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" };
}
}
/* Options if this unit is selected*/
else if (this.getSelected()) {
/* This is the only selected unit */
if (getApp().getUnitsManager().getSelectedUnits().length == 1) {
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" };
} else {
/* Provision */
}
options["refuel"] = { text: "Air to air refuel", tooltip: "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB.", type: "and" }; // TODO Add some way of knowing which aircraft can AAR
}
/* All other options */
else {
/* Provision */
}
return options;
}
}
export class Aircraft extends AirUnit {
@@ -1209,6 +1248,41 @@ export class GroundUnit extends Unit {
};
}
getActions() {
var options: { [key: string]: { text: string, tooltip: string, type: string } } = {};
/* Options if this unit is not selected */
if (!this.getSelected()) {
/* Someone else is selected */
if (getApp().getUnitsManager().getSelectedUnits().length > 0) {
options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons", type: "or" };
} else {
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" };
}
}
/* Options if this unit is selected*/
else if (this.getSelected()) {
/* This is the only selected unit */
if (getApp().getUnitsManager().getSelectedUnits().length == 1) {
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" };
} else {
options["group-ground"] = { text: "Create group", tooltip: "Create a group from the selected units", type: "and" };
}
if (["AAA", "flak"].includes(this.getType())) {
options["scenic-aaa-red"] = { text: "Scenic AAA (red)", tooltip: "Shoot AAA in the air without aiming at any target, when a red air unit gets close enough", type: "and" };
options["scenic-aaa-blue"] = { text: "Scenic AAA (blue)", tooltip: "Shoot AAA in the air without aiming at any target, when a blue air unit gets close enough", type: "and" };
options["miss-aaa-red"] = { text: "Miss on purpose AAA (red)", tooltip: "Shoot AAA towards the closest red air unit, but don't aim precisely", type: "and" };
options["miss-aaa-blue"] = { text: "Miss on purpose AAA (blue)", tooltip: "Shoot AAA towards the closest blue air unit, but don't aim precisely", type: "and" };
}
}
/* All other options */
else {
/* Provision */
}
return options;
}
getCategory() {
return "GroundUnit";
}
@@ -1240,6 +1314,34 @@ export class NavyUnit extends Unit {
};
}
getActions() {
var options: { [key: string]: { text: string, tooltip: string, type: string } } = {};
/* Options if this unit is not selected */
if (!this.getSelected()) {
/* Someone else is selected */
if (getApp().getUnitsManager().getSelectedUnits().length > 0) {
options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons", type: "or" };
} else {
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" };
}
}
/* Options if this unit is selected */
else if (this.getSelected()) {
/* This is the only selected unit */
if (getApp().getUnitsManager().getSelectedUnits().length == 1) {
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it", type: "and" };
} else {
options["group-navy"] = { text: "Create group", tooltip: "Create a group from the selected units", type: "and" };
}
}
/* All other options */
else {
/* Provision */
}
return options;
}
getMarkerCategory() {
return "navyunit";
}

View File

@@ -629,21 +629,23 @@ export class UnitsManager {
/** Instruct units to enter into scenic AAA mode. Units will shoot in the air without aiming
*
*/
selectedUnitsScenicAAA() {
selectedUnitsScenicAAA(coalition: string) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
for (let idx in selectedUnits) {
selectedUnits[idx].scenicAAA();
selectedUnits[idx].scenicAAA(coalition);
}
this.#showActionMessage(selectedUnits, `unit set to perform scenic AAA against ${coalition} units`);
}
/** Instruct units to enter into miss on purpose mode. Units will aim to the nearest enemy unit but not precisely.
*
*/
selectedUnitsMissOnPurpose() {
selectedUnitsMissOnPurpose(coalition: string) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
for (let idx in selectedUnits) {
selectedUnits[idx].missOnPurpose();
selectedUnits[idx].missOnPurpose(coalition);
}
this.#showActionMessage(selectedUnits, `unit set to perform miss on purpose AAA against ${coalition} units`);
}
/*********************** Control operations on selected units ************************/