Merge branch 'main' into 499-spawn-menu-reorganisation

This commit is contained in:
Pax1601
2023-11-06 11:12:46 +01:00
committed by GitHub
58 changed files with 13571 additions and 12498 deletions

View File

@@ -48,6 +48,18 @@ export const emissionsCountermeasuresDescriptions: string[] = [
"Always on (Radar and ECM always on)"
];
export const shotsScatterDescriptions: string[] = [
"When performing scenic shooting tasks like simulated firefights, will shoot with a large scatter",
"When performing scenic shooting tasks like simulated firefights, will shoot with a medium scatter",
"When performing scenic shooting tasks like simulated firefights, will shoot with a small scatter (Radar guided units will track shots when the enemy unit is close)"
];
export const shotsIntensityDescriptions: string[] = [
"When performing scenic shooting tasks like simulated firefights, will shoot with a low rate of fire",
"When performing scenic shooting tasks like simulated firefights, will shoot with a medium rate of fire",
"When performing scenic shooting tasks like simulated firefights, will shoot with a high rate of fire"
];
export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
export const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 };
@@ -221,6 +233,8 @@ export enum DataIndexes {
hasTask,
position,
speed,
horizontalVelocity,
verticalVelocity,
heading,
isActiveTanker,
isActiveAWACS,
@@ -246,6 +260,8 @@ export enum DataIndexes {
activePath,
isLeader,
operateAs,
shotsScatter,
shotsIntensity,
endOfData = 255
};

View File

@@ -1,11 +1,31 @@
export interface ContextInterface {
useSpawnMenu?: boolean;
useUnitControlPanel?: boolean;
useUnitInfoPanel?: boolean;
}
export class Context {
constructor( config:ContextInterface ) {
#useSpawnMenu:boolean;
#useUnitControlPanel:boolean;
#useUnitInfoPanel:boolean;
constructor( config:ContextInterface ) {
this.#useSpawnMenu = ( config.useSpawnMenu !== false );
this.#useUnitControlPanel = ( config.useUnitControlPanel !== false );
this.#useUnitInfoPanel = ( config.useUnitInfoPanel !== false );
}
getUseSpawnMenu() {
return this.#useSpawnMenu;
}
getUseUnitControlPanel() {
return this.#useUnitControlPanel;
}
getUseUnitInfoPanel() {
return this.#useUnitInfoPanel;
}
}

View File

@@ -105,6 +105,9 @@ export class MapContextMenu extends ContextMenu {
* @param latlng Leaflet latlng object of the mouse click
*/
show(x: number, y: number, latlng: LatLng) {
if (!getApp().getCurrentContext().getUseSpawnMenu())
return false;
super.show(x, y, latlng);
this.#aircraftSpawnMenu.setLatLng(latlng);

View File

@@ -152,6 +152,8 @@ export interface UnitData {
hasTask: boolean;
position: LatLng;
speed: number;
horizontalVelocity: number;
verticalVelocity: number;
heading: number;
isActiveTanker: boolean;
isActiveAWACS: boolean;
@@ -177,6 +179,8 @@ export interface UnitData {
activePath: LatLng[];
isLeader: boolean;
operateAs: string;
shotsScatter: number;
shotsIntensity: number;
}
export interface LoadoutItemBlueprint {
@@ -210,12 +214,19 @@ export interface UnitBlueprint {
muzzleVelocity?: number;
aimTime?: number;
shotsToFire?: number;
shotsBaseInterval?: number;
shotsBaseScatter?: number;
description?: string;
abilities?: string;
acquisitionRange?: number;
engagementRange?: number;
targetingRange?: number;
aimMethodRange?: number;
alertnessTimeConstant?: number;
canTargetPoint?: boolean;
canRearm?: boolean;
canAAA?: boolean;
indirectFire?: boolean;
}
export interface UnitSpawnOptions {

View File

@@ -734,7 +734,7 @@ export class Map extends L.Map {
const makeTitle = (isProtected:boolean) => {
return ( isProtected ) ? "Unit type is protected and will ignore orders" : "Unit is NOT protected and will respond to orders";
}
this.#mapMarkerControls.forEach( (control:MapMarkerControl) => {
this.getMapMarkerControls().forEach( (control:MapMarkerControl) => {
const toggles = `["${control.toggles.join('","')}"]`;
const div = document.createElement("div");
div.className = control.protectable === true ? "protectable" : "";
@@ -901,5 +901,9 @@ export class Map extends L.Map {
this.#visibilityOptions[option] = ev.currentTarget.checked;
document.dispatchEvent(new CustomEvent("mapVisibilityOptionsChanged"));
}
getMapMarkerControls() {
return this.#mapMarkerControls;
}
}

View File

@@ -403,19 +403,26 @@ export class OlympusApp {
});
/* Try and connect with the Olympus REST server */
document.addEventListener("tryConnection", () => {
const form = document.querySelector("#splash-content")?.querySelector("#authentication-form");
const username = (form?.querySelector("#username") as HTMLInputElement).value;
const password = (form?.querySelector("#password") as HTMLInputElement).value;
const loginForm = document.getElementById("authentication-form");
if (loginForm instanceof HTMLFormElement) {
loginForm.addEventListener("submit", (ev:SubmitEvent) => {
ev.preventDefault();
ev.stopPropagation();
const username = (loginForm.querySelector("#username") as HTMLInputElement).value;
const password = (loginForm.querySelector("#password") as HTMLInputElement).value;
/* Update the user credentials */
this.getServerManager().setCredentials(username, password);
// Update the user credentials
this.getServerManager().setCredentials(username, password);
/* Start periodically requesting updates */
this.getServerManager().startUpdate();
// Start periodically requesting updates
this.getServerManager().startUpdate();
this.setLoginStatus("connecting");
});
} else {
console.error("Unable to find login form.");
}
this.setLoginStatus("connecting");
})
/* Reload the page, used to mimic a restart of the app */
document.addEventListener("reloadPage", () => {

View File

@@ -6,7 +6,7 @@ import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";
import { Switch } from "../controls/switch";
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants";
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, shotsIntensityDescriptions, shotsScatterDescriptions, speedIncrements } from "../constants/constants";
import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils";
import { GeneralSettings, Radio, TACAN } from "../interfaces";
@@ -56,9 +56,19 @@ export class UnitControlPanel extends Panel {
return this.#createOptionButton(option, `emissions/${option.toLowerCase()}.svg`, emissionsCountermeasuresDescriptions[index],() => { getApp().getUnitsManager().selectedUnitsSetEmissionsCountermeasures(option); });
});
this.#optionButtons["shotsScatter"] = [1, 2, 3].map((option: number, index: number) => {
return this.#createOptionButton(option.toString(), `scatter/${option.toString().toLowerCase()}.svg`, shotsScatterDescriptions[index],() => { getApp().getUnitsManager().selectedUnitsSetShotsScatter(option); });
});
this.#optionButtons["shotsIntensity"] = [1, 2, 3].map((option: number, index: number) => {
return this.#createOptionButton(option.toString(), `intensity/${option.toString().toLowerCase()}.svg`, shotsIntensityDescriptions[index],() => { getApp().getUnitsManager().selectedUnitsSetShotsIntensity(option); });
});
this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]);
this.getElement().querySelector("#reaction-to-threat-buttons-container")?.append(...this.#optionButtons["reactionToThreat"]);
this.getElement().querySelector("#emissions-countermeasures-buttons-container")?.append(...this.#optionButtons["emissionsCountermeasures"]);
this.getElement().querySelector("#shots-scatter-buttons-container")?.append(...this.#optionButtons["shotsScatter"]);
this.getElement().querySelector("#shots-intensity-buttons-container")?.append(...this.#optionButtons["shotsIntensity"]);
/* Tanker */
this.#tankerSwitch = new Switch("tanker-on-switch", (value: boolean) => {
@@ -131,6 +141,10 @@ export class UnitControlPanel extends Panel {
}
show() {
const context = getApp().getCurrentContext();
if ( !context.getUseUnitControlPanel() )
return;
super.show();
this.#speedTypeSwitch.resetExpectedValue();
this.#altitudeTypeSwitch.resetExpectedValue();
@@ -195,6 +209,8 @@ export class UnitControlPanel extends Panel {
element.toggleAttribute("data-show-roe", !isTanker && !isAWACS);
element.toggleAttribute("data-show-threat", (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")) && !(this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")));
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-shots-scatter", this.#selectedUnitsTypes.includes("GroundUnit")); //TODO: more refined
element.toggleAttribute("data-show-shots-intensity", this.#selectedUnitsTypes.includes("GroundUnit")); //TODO: more refined
element.toggleAttribute("data-show-tanker-button", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.isTanker();}) === true);
element.toggleAttribute("data-show-AWACS-button", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.isAWACS();}) === true);
element.toggleAttribute("data-show-on-off", (this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")) && !(this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")));
@@ -256,6 +272,14 @@ export class UnitControlPanel extends Panel {
button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getEmissionsCountermeasures() === button.value))
});
this.#optionButtons["shotsScatter"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getShotsScatter().toString() === button.value))
});
this.#optionButtons["shotsIntensity"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", this.#units.every((unit: Unit) => unit.getShotsIntensity().toString() === button.value))
});
this.#tankerSwitch.setValue(isActiveTanker, false);
this.#AWACSSwitch.setValue(isActiveAWACAS, false);
this.#onOffSwitch.setValue(onOff, false);

View File

@@ -1,3 +1,4 @@
import { getApp } from "..";
import { Ammo } from "../interfaces";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { Unit } from "../unit/unit";
@@ -92,4 +93,12 @@ export class UnitInfoPanel extends Panel {
else
this.hide();
}
show() {
const context = getApp().getCurrentContext();
if ( !context.getUseUnitInfoPanel() )
return;
super.show();
}
}

View File

@@ -357,6 +357,18 @@ export class ServerManager {
this.PUT(data, callback);
}
setShotsScatter(ID: number, shotsScatter: number, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "shotsScatter": shotsScatter }
var data = { "setShotsScatter": command }
this.PUT(data, callback);
}
setShotsIntensity(ID: number, shotsIntensity: number, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "shotsIntensity": shotsIntensity }
var data = { "setShotsIntensity": command }
this.PUT(data, callback);
}
setAdvacedOptions(ID: number, isActiveTanker: boolean, isActiveAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback: CallableFunction = () => {}) {
var command = {
"ID": ID,

View File

@@ -34,6 +34,8 @@ export class Unit extends CustomMarker {
#hasTask: boolean = false;
#position: LatLng = new LatLng(0, 0, 0);
#speed: number = 0;
#horizontalVelocity: number = 0;
#verticalVelocity: number = 0;
#heading: number = 0;
#isActiveTanker: boolean = false;
#isActiveAWACS: boolean = false;
@@ -78,6 +80,8 @@ export class Unit extends CustomMarker {
#activePath: LatLng[] = [];
#isLeader: boolean = false;
#operateAs: string = "blue";
#shotsScatter: number = 2;
#shotsIntensity: number = 2;
#selectable: boolean;
#selected: boolean = false;
@@ -95,47 +99,49 @@ export class Unit extends CustomMarker {
#doubleClickTimer: number = 0;
#hotgroup: number | null = null;
#detectionMethods: number[] = [];
#isProtected:boolean = false;
getActivePath() { return this.#activePath };
getAlive() { return this.#alive };
getAmmo() { return this.#ammo };
getCoalition() { return this.#coalition };
getContacts() { return this.#contacts };
getHuman() { return this.#human };
getControlled() { return this.#controlled };
getCoalition() { return this.#coalition };
getCountry() { return this.#country };
getDesiredAltitude() { return this.#desiredAltitude };
getDesiredAltitudeType() { return this.#desiredAltitudeType };
getName() { return this.#name };
getUnitName() { return this.#unitName };
getGroupName() { return this.#groupName };
getState() { return this.#state };
getTask() { return this.#task };
getHasTask() { return this.#hasTask };
getPosition() { return this.#position };
getSpeed() { return this.#speed };
getHorizontalVelocity() { return this.#horizontalVelocity };
getVerticalVelocity() { return this.#verticalVelocity };
getHeading() { return this.#heading };
getIsActiveTanker() { return this.#isActiveTanker };
getIsActiveAWACS() { return this.#isActiveAWACS };
getOnOff() { return this.#onOff };
getFollowRoads() { return this.#followRoads };
getFuel() { return this.#fuel };
getDesiredSpeed() { return this.#desiredSpeed };
getDesiredSpeedType() { return this.#desiredSpeedType };
getEmissionsCountermeasures() { return this.#emissionsCountermeasures };
getFollowRoads() { return this.#followRoads };
getFormationOffset() { return this.#formationOffset };
getFuel() { return this.#fuel };
getGeneralSettings() { return this.#generalSettings };
getGroupName() { return this.#groupName };
getHasTask() { return this.#hasTask };
getHeading() { return this.#heading };
getHuman() { return this.#human };
getIsActiveAWACS() { return this.#isActiveAWACS };
getIsActiveTanker() { return this.#isActiveTanker };
getIsLeader() { return this.#isLeader };
getDesiredAltitude() { return this.#desiredAltitude };
getDesiredAltitudeType() { return this.#desiredAltitudeType };
getLeaderID() { return this.#leaderID };
getName() { return this.#name };
getOnOff() { return this.#onOff };
getOperateAs() { return this.#operateAs };
getPosition() { return this.#position };
getIsProtected() { return this.#isProtected };
getRadio() { return this.#radio };
getReactionToThreat() { return this.#reactionToThreat };
getROE() { return this.#ROE };
getSpeed() { return this.#speed };
getState() { return this.#state };
getTACAN() { return this.#TACAN };
getFormationOffset() { return this.#formationOffset };
getTargetID() { return this.#targetID };
getTargetPosition() { return this.#targetPosition };
getTask() { return this.#task };
getUnitName() { return this.#unitName };
getROE() { return this.#ROE };
getReactionToThreat() { return this.#reactionToThreat };
getEmissionsCountermeasures() { return this.#emissionsCountermeasures };
getTACAN() { return this.#TACAN };
getRadio() { return this.#radio };
getGeneralSettings() { return this.#generalSettings };
getAmmo() { return this.#ammo };
getContacts() { return this.#contacts };
getActivePath() { return this.#activePath };
getIsLeader() { return this.#isLeader };
getOperateAs() { return this.#operateAs };
getShotsScatter() { return this.#shotsScatter};
getShotsIntensity() { return this.#shotsIntensity};
static getConstructor(type: string) {
if (type === "GroundUnit") return GroundUnit;
@@ -222,6 +228,8 @@ export class Unit extends CustomMarker {
case DataIndexes.hasTask: this.#hasTask = dataExtractor.extractBool(); break;
case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break;
case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break;
case DataIndexes.horizontalVelocity: this.#horizontalVelocity = dataExtractor.extractFloat64(); break;
case DataIndexes.verticalVelocity: this.#verticalVelocity = dataExtractor.extractFloat64(); break;
case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break;
case DataIndexes.isActiveTanker: this.#isActiveTanker = dataExtractor.extractBool(); break;
case DataIndexes.isActiveAWACS: this.#isActiveAWACS = dataExtractor.extractBool(); break;
@@ -247,6 +255,8 @@ export class Unit extends CustomMarker {
case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break;
case DataIndexes.isLeader: this.#isLeader = dataExtractor.extractBool(); updateMarker = true; break;
case DataIndexes.operateAs: this.#operateAs = enumToCoalition(dataExtractor.extractUInt8()); break;
case DataIndexes.shotsScatter: this.#shotsScatter = dataExtractor.extractUInt8(); break;
case DataIndexes.shotsIntensity: this.#shotsIntensity = dataExtractor.extractUInt8(); break;
}
}
@@ -286,6 +296,8 @@ export class Unit extends CustomMarker {
hasTask: this.#hasTask,
position: this.#position,
speed: this.#speed,
horizontalVelocity: this.#horizontalVelocity,
verticalVelocity: this.#verticalVelocity,
heading: this.#heading,
isActiveTanker: this.#isActiveTanker,
isActiveAWACS: this.#isActiveAWACS,
@@ -310,7 +322,9 @@ export class Unit extends CustomMarker {
contacts: this.#contacts,
activePath: this.#activePath,
isLeader: this.#isLeader,
operateAs: this.#operateAs
operateAs: this.#operateAs,
shotsScatter: this.#shotsScatter,
shotsIntensity: this.#shotsIntensity
}
}
@@ -338,10 +352,6 @@ export class Unit extends CustomMarker {
}
}
setIsProtected(isProtected:boolean) {
this.#isProtected = isProtected;
}
setAlive(newAlive: boolean) {
if (newAlive != this.#alive)
document.dispatchEvent(new CustomEvent("unitDeath", { detail: this }));
@@ -437,7 +447,6 @@ export class Unit extends CustomMarker {
return this.getDatabase()?.getSpawnPointsByName(this.getName());
}
/********************** Icon *************************/
createIcon(): void {
/* Set the icon */
@@ -636,10 +645,6 @@ export class Unit extends CustomMarker {
return getApp().getMap().getBounds().contains(this.getPosition());
}
canLandAtPoint() {
return this.getCategory() === "Helicopter"; // Only choppers can do this currently
}
canTargetPoint() {
return this.getDatabase()?.getByName(this.#name)?.canTargetPoint === true;
}
@@ -648,6 +653,18 @@ export class Unit extends CustomMarker {
return this.getDatabase()?.getByName(this.#name)?.canRearm === true;
}
canLandAtPoint() {
return this.getCategory() === "Helicopter";
}
canAAA() {
return this.getDatabase()?.getByName(this.#name)?.canAAA === true;
}
indirectFire() {
return this.getDatabase()?.getByName(this.#name)?.indirectFire === true;
}
isTanker() {
return this.canFulfillRole("Tanker");
}
@@ -823,6 +840,16 @@ export class Unit extends CustomMarker {
getApp().getServerManager().landAtPoint(this.ID, latlng);
}
setShotsScatter(shotsScatter: number) {
if (!this.#human)
getApp().getServerManager().setShotsScatter(this.ID, shotsScatter);
}
setShotsIntensity(shotsIntensity: number) {
if (!this.#human)
getApp().getServerManager().setShotsIntensity(this.ID, shotsIntensity);
}
/***********************************************/
getActions(): { [key: string]: { text: string, tooltip: string, type: string } } {
/* To be implemented by child classes */ // TODO make Unit an abstract class
@@ -1446,7 +1473,7 @@ export class GroundUnit extends Unit {
options["group-ground"] = { text: "Create group", tooltip: "Create a group from the selected units", type: "and" };
}
if (["AAA", "flak"].includes(this.getType())) {
if (this.canAAA()) {
options["scenic-aaa"] = { text: "Scenic AAA", tooltip: "Shoot AAA in the air without aiming at any target, when a enemy unit gets close enough. WARNING: works correctly only on neutral units, blue or red units will aim", type: "and" };
options["miss-aaa"] = { text: "Miss on purpose AAA", tooltip: "Shoot AAA towards the closest enemy unit, but don't aim precisely. WARNING: works correctly only on neutral units, blue or red units will aim", type: "and" };
}

View File

@@ -708,6 +708,30 @@ export class UnitsManager {
this.#showActionMessage(selectedUnits, `unit landing at point`);
}
/** Set a specific shots scatter to all the selected units
*
* @param shotsScatter Value to set
*/
selectedUnitsSetShotsScatter(shotsScatter: number) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
for (let idx in selectedUnits) {
selectedUnits[idx].setShotsScatter(shotsScatter);
}
this.#showActionMessage(selectedUnits, `shots scatter set to ${shotsScatter}`);
}
/** Set a specific shots intensity to all the selected units
*
* @param shotsScatter Value to set
*/
selectedUnitsSetShotsIntensity(shotsIntensity: number) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
for (let idx in selectedUnits) {
selectedUnits[idx].setShotsIntensity(shotsIntensity);
}
this.#showActionMessage(selectedUnits, `shots intensity set to ${shotsIntensity}`);
}
/*********************** Control operations on selected units ************************/
/** See getUnitsCategories for more info
*