Merge branch 'main' into 602-attack-mode-not-working-on-ground-and-navy-units

This commit is contained in:
Pax1601 2023-12-02 10:49:28 +01:00
commit 7184bc1eb2
9 changed files with 211 additions and 98 deletions

View File

@ -1,4 +1,4 @@
# Important note: DCS Olympus is in alpha state. No official release has been produced yet. The first public version is planned for Q4 2023.
# Important note: DCS Olympus is in alpha state. No official release has been produced yet. The first public version is planned for mid december 2023.
# DCS Olympus
*A real-time web interface to spawn and control units in DCS World*
@ -6,32 +6,19 @@
![alt text](https://github.com/Pax1601/DCSOlympus/blob/main/client/sample.png?raw=true)
### What is this?
DCS Olympus is a mod for DCS World. It allows users to spawn, control, task, group, and remove units from a DCS World server using a real-time map interface, similarly to Real Time Strategy games. The user interface also provides useful informations units, like loadouts, fuel, tasking, and so on. In the future, more features for DCS World GCI and JTAC will be available.
DCS: Olympus is a free and open-source mod for DCS that enables dynamic real-time control through a map interface. The user is able to spawn units/groups, deploy a variety of effects such as smoke, flares, or explosions, and waypoints/tasks can be given to AI units in real-time in a way similar to a classic RTS game.
### Features and how to use it
- Spawn air and ground units, with preset loadouts
- Double click on the map to spawn a blue and red units, both in the air and in the ground, with preset loadouts for air-to-air or air-to-ground tasks;
- Control units
- Select one ore more units to move them around. Hold down ctrl and click to create a route for the unit to follow;
- Attack other units
- After selecting one ore more units, double click on another unit and select "Attack" to attack it, depending on the available weapons.
Additionally Olympus is able to run several effects and unit behaviours beyond the core DCS offerings. This includes such things as napalm and white phosphosous explosions, or setting up AA units to fire at players and miss, and more.
It even includes Red and Blue modes which limit your view and powers to just seeing what your coalition sees, with a spawning budget you could play against your friends even with no-one in the game piloting, or have a Red commander working against a squadron of blue pilots, and/or a blue commander working with them.
Even better it requires no client mods be installed if used on a server
The full feature list is simply too long to enumerate in a short summary but needless to say Olympus offers up a lot of unique gameplay that has previously not existed, and enhances many other elements of DCS in exciting ways
### Installing DCS Olympus
A prebuilt installer will soon be released and available here
### Building DCS Olympus
DCS Olympus is comprised of two modules:
A "core" c++ .dll module, which is run by DCS and exposes all the necessary data, and provides endpoints for commands from a REST server. A Visual Studio 2017/2019/2022 solution is provided, and requires no additional configuration. The core dll solution has two dependencies, both can be installed using vcpkg (https://vcpkg.io/en/getting-started.html):
- cpprestsdk: `vcpkg install cpprestsdk:x64-windows`
- geographiclib: `vcpkg install geographiclib:x64-windows`
A "client" node.js typescript web app, which can be hosted on the server using express.js. A Visual Studio Code configuration is provided for debugging. The client requires node.js to be installed for building (https://nodejs.org/en/). After installing node.js, move in the client folder and run the following commands:
- `npm install`
- `npm -g install`
After installing all the necessary dependencies you can start a development server executing the *client/debug.bat* batch file, and visiting http:\\localhost:3000 with any modern browser (tested with updated Chrome, Firefox and Edge). However, it is highly suggested to simply run the `Launch Chrome against localhost` debug configuration in Visual Studio Code.

View File

@ -55,7 +55,7 @@ class DemoDataGenerator {
// UNCOMMENT TO TEST ALL UNITS ****************
/*
var databases = Object.assign({}, aircraftDatabase, helicopterDatabase, groundUnitDatabase, navyUnitDatabase);
var t = Object.keys(databases).length;
var l = Math.floor(Math.sqrt(t));

View File

@ -45,6 +45,7 @@
justify-content: space-between;
row-gap: 5px;
width: 100%;
padding: 5px;
}
.contextmenu-advanced-options-toggle,
@ -61,7 +62,13 @@
.contextmenu-advanced-options-toggle:after,
.contextmenu-metadata-toggle:after {
content: url(/resources/theme/images/icons/chevron-down.svg);
margin: auto;
margin-left: auto;
margin-top: auto;
}
.contextmenu-advanced-options-toggle.is-open:after,
.contextmenu-metadata-toggle.is-open:after {
transform: rotate(180deg);
}
.contextmenu-advanced-options-toggle div:first-child,
@ -580,6 +587,7 @@
#iads-menu {
row-gap: 10px;
padding: 10px;
}
#coalition-area-contextmenu>div:nth-child(2) {
@ -596,6 +604,7 @@
flex-direction: column;
justify-content: space-between;
row-gap: 5px;
padding: 20px;
}
.create-iads-button {

View File

@ -199,6 +199,10 @@ form {
right: 10px;
}
.ol-select.is-open:not(.ol-select-image)>.ol-select-value:after {
transform: rotate(180deg);
}
.ol-select>.ol-select-options {
border-radius: var(--border-radius-md);
max-height: 0;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 7.7 MiB

View File

@ -220,8 +220,8 @@ export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{
"tooltip": "Toggle airbase' visibility"
}];
export const IADSTypes = ["AAA", "MANPADS", "SAM Site", "Radar"];
export const IADSDensities: { [key: string]: number } = { "AAA": 0.8, "MANPADS": 0.3, "SAM Site": 0.1, "Radar": 0.05 };
export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"];
export const IADSDensities: { [key: string]: number } = { "AAA": 0.8, "SAM Site": 0.1, "Radar (EWR)": 0.05 };
export const GROUND_UNIT_AIR_DEFENCE_REGEX:RegExp = /(\b(AAA|SAM|MANPADS?|[mM]anpads?)|[sS]tinger\b)/;
export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out";
export const SHOW_UNIT_LABELS = "Show unit labels (L)";

View File

@ -12,8 +12,14 @@ import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
import { UnitBlueprint, UnitSpawnOptions, UnitSpawnTable } from "../interfaces";
export class UnitSpawnMenu {
/** This is the common code for all the unit spawn menus. It is shown both when right clicking on the map and when spawning from airbase.
*
*/
export abstract class UnitSpawnMenu {
protected showRangeCircles: boolean = false;
protected unitTypeFilter = (unit:any) => { return true; };
/* Default options */
protected spawnOptions: UnitSpawnOptions = {
roleType: "",
name: "",
@ -31,8 +37,9 @@ export class UnitSpawnMenu {
#unitDatabase: UnitDatabase;
#countryCodes: any;
#orderByRole: boolean;
protected unitTypeFilter = (unit:any) => { return true; };
#showLoadout: boolean = true;
#showAltitudeSlider: boolean = true;
/* Controls */
#unitRoleTypeDropdown: Dropdown;
#unitLabelDropdown: Dropdown;
@ -44,11 +51,18 @@ export class UnitSpawnMenu {
/* HTML Elements */
#deployUnitButtonEl: HTMLButtonElement;
#unitCountDivider: HTMLDivElement;
#unitLoadoutPreviewEl: HTMLDivElement;
#unitImageEl: HTMLImageElement;
#unitLoadoutListEl: HTMLDivElement;
#descriptionDiv: HTMLDivElement;
#abilitiesDiv: HTMLDivElement;
#advancedOptionsDiv: HTMLDivElement;
#unitInfoDiv: HTMLDivElement;
#advancedOptionsToggle: HTMLDivElement;
#advancedOptionsText: HTMLDivElement;
#unitInfoToggle: HTMLDivElement;
#unitInfoText: HTMLDivElement;
/* Range circle previews */
#engagementCircle: Circle;
@ -60,20 +74,20 @@ export class UnitSpawnMenu {
this.#orderByRole = orderByRole;
/* Create the dropdowns and the altitude slider */
this.#unitRoleTypeDropdown = new Dropdown(null, (roleType: string) => this.#setUnitRoleType(roleType), undefined, "Unit type");
this.#unitLabelDropdown = new Dropdown(null, (name: string) => this.#setUnitName(name), undefined, "Unit label");
this.#unitLoadoutDropdown = new Dropdown(null, (loadout: string) => this.#setUnitLoadout(loadout), undefined, "Unit loadout");
this.#unitCountDropdown = new Dropdown(null, (count: string) => this.#setUnitCount(count), undefined, "Unit count");
this.#unitCountryDropdown = new Dropdown(null, () => { /* Custom button implementation */ }, undefined, "Unit country");
this.#unitLiveryDropdown = new Dropdown(null, (livery: string) => this.#setUnitLivery(livery), undefined, "Unit livery");
this.#unitRoleTypeDropdown = new Dropdown(null, (roleType: string) => this.#setUnitRoleType(roleType), undefined, "Role");
this.#unitLabelDropdown = new Dropdown(null, (name: string) => this.#setUnitName(name), undefined, "Type");
this.#unitLoadoutDropdown = new Dropdown(null, (loadout: string) => this.#setUnitLoadout(loadout), undefined, "Loadout");
this.#unitCountDropdown = new Dropdown(null, (count: string) => this.#setUnitCount(count), undefined, "Count");
this.#unitCountryDropdown = new Dropdown(null, () => { /* Custom button implementation */ }, undefined, "Country");
this.#unitLiveryDropdown = new Dropdown(null, (livery: string) => this.#setUnitLivery(livery), undefined, "Livery");
this.#unitSpawnAltitudeSlider = new Slider(null, 0, 1000, "ft", (value: number) => { this.spawnOptions.altitude = ftToM(value); }, { title: "Spawn altitude" });
/* The unit label and unit count are in the same "row" for clarity and compactness */
var unitLabelCountContainerEl = document.createElement("div");
unitLabelCountContainerEl.classList.add("unit-label-count-container");
var divider = document.createElement("div");
divider.innerText = "x";
unitLabelCountContainerEl.append(this.#unitLabelDropdown.getContainer(), divider, this.#unitCountDropdown.getContainer());
this.#unitCountDivider = document.createElement("div");
this.#unitCountDivider.innerText = "x";
unitLabelCountContainerEl.append(this.#unitLabelDropdown.getContainer(), this.#unitCountDivider, this.#unitCountDropdown.getContainer());
/* Create the unit image and loadout elements */
this.#unitImageEl = document.createElement("img");
@ -84,38 +98,37 @@ export class UnitSpawnMenu {
this.#unitLoadoutListEl.classList.add("unit-loadout-list");
this.#unitLoadoutPreviewEl.append(this.#unitImageEl, this.#unitLoadoutListEl);
/* Create the divider and the advanced options collapsible div */
var advancedOptionsDiv = document.createElement("div");
advancedOptionsDiv.classList.add("contextmenu-advanced-options", "hide");
var advancedOptionsToggle = document.createElement("div");
advancedOptionsToggle.classList.add("contextmenu-advanced-options-toggle");
var advancedOptionsText = document.createElement("div");
advancedOptionsText.innerText = "Advanced options";
var advancedOptionsHr = document.createElement("hr");
advancedOptionsToggle.append(advancedOptionsText, advancedOptionsHr);
advancedOptionsToggle.addEventListener("click", () => {
advancedOptionsDiv.classList.toggle("hide");
/* Create the advanced options collapsible div */
this.#advancedOptionsDiv = document.createElement("div");
this.#advancedOptionsDiv.classList.add("contextmenu-advanced-options", "hide");
this.#advancedOptionsToggle = document.createElement("div");
this.#advancedOptionsToggle.classList.add("contextmenu-advanced-options-toggle");
this.#advancedOptionsText = document.createElement("div");
this.#advancedOptionsText.innerText = "Faction / Liveries";
this.#advancedOptionsToggle.append(this.#advancedOptionsText);
this.#advancedOptionsToggle.addEventListener("click", () => {
this.#advancedOptionsToggle.classList.toggle("is-open");
this.#advancedOptionsDiv.classList.toggle("hide");
this.#container.dispatchEvent(new Event("resize"));
});
advancedOptionsDiv.append(this.#unitCountryDropdown.getContainer(), this.#unitLiveryDropdown.getContainer(),
this.#unitSpawnAltitudeSlider.getContainer() as HTMLElement);
this.#advancedOptionsDiv.append(this.#unitCountryDropdown.getContainer(), this.#unitLiveryDropdown.getContainer());
/* Create the divider and the metadata collapsible div */
var metadataDiv = document.createElement("div");
metadataDiv.classList.add("contextmenu-metadata", "hide");
var metadataToggle = document.createElement("div");
metadataToggle.classList.add("contextmenu-metadata-toggle");
var metadataText = document.createElement("div");
metadataText.innerText = "Info";
var metadataHr = document.createElement("hr");
metadataToggle.append(metadataText, metadataHr);
metadataToggle.addEventListener("click", () => {
metadataDiv.classList.toggle("hide");
/* Create the unit info collapsible div */
this.#unitInfoDiv = document.createElement("div");
this.#unitInfoDiv.classList.add("contextmenu-metadata", "hide");
this.#unitInfoToggle = document.createElement("div");
this.#unitInfoToggle.classList.add("contextmenu-metadata-toggle");
this.#unitInfoText = document.createElement("div");
this.#unitInfoText.innerText = "Unit information";
this.#unitInfoToggle.append(this.#unitInfoText);
this.#unitInfoToggle.addEventListener("click", () => {
this.#unitInfoToggle.classList.toggle("is-open");
this.#unitInfoDiv.classList.toggle("hide");
this.#container.dispatchEvent(new Event("resize"));
});
this.#descriptionDiv = document.createElement("div");
this.#abilitiesDiv = document.createElement("div");
metadataDiv.append(this.#descriptionDiv, this.#abilitiesDiv);
this.#unitInfoDiv.append(this.#descriptionDiv, this.#abilitiesDiv);
/* Create the unit deploy button */
this.#deployUnitButtonEl = document.createElement("button");
@ -128,8 +141,8 @@ export class UnitSpawnMenu {
});
/* Assemble all components */
this.#container.append(this.#unitRoleTypeDropdown.getContainer(), unitLabelCountContainerEl, this.#unitLoadoutDropdown.getContainer(),
this.#unitLoadoutPreviewEl, advancedOptionsToggle, advancedOptionsDiv, metadataToggle, metadataDiv, this.#deployUnitButtonEl);
this.#container.append(this.#unitRoleTypeDropdown.getContainer(), unitLabelCountContainerEl, this.#unitLoadoutDropdown.getContainer(), this.#unitSpawnAltitudeSlider.getContainer() as HTMLElement,
this.#unitLoadoutPreviewEl, this.#advancedOptionsToggle, this.#advancedOptionsDiv, this.#unitInfoToggle, this.#unitInfoDiv, this.#deployUnitButtonEl);
/* Load the country codes from the public folder */
var xhr = new XMLHttpRequest();
@ -144,8 +157,29 @@ export class UnitSpawnMenu {
};
xhr.send();
/* Create the range circle previews */
this.#engagementCircle = new Circle(this.spawnOptions.latlng, { radius: 0, weight: 4, opacity: 0.8, fillOpacity: 0, dashArray: "4 8", interactive: false, bubblingMouseEvents: false });
this.#acquisitionCircle = new Circle(this.spawnOptions.latlng, { radius: 0, weight: 2, opacity: 0.8, fillOpacity: 0, dashArray: "8 12", interactive: false, bubblingMouseEvents: false });
/* Event listeners */
this.#container.addEventListener("unitRoleTypeChanged", () => {
/* Shown the unit label and the unit count dropdowns */
this.#unitLabelDropdown.show();
this.#unitCountDivider.classList.remove("hide");
this.#unitCountDropdown.show();
/* Hide all the other components */
this.#unitLoadoutDropdown.hide();
this.#unitSpawnAltitudeSlider.hide();
this.#unitLoadoutPreviewEl.classList.add("hide");
this.#advancedOptionsDiv.classList.add("hide");
this.#unitInfoDiv.classList.add("hide");
this.#advancedOptionsText.classList.add("hide");
this.#advancedOptionsToggle.classList.add("hide");
this.#unitInfoText.classList.add("hide");
this.#unitInfoToggle.classList.add("hide");
/* Disable the spawn button */
this.#deployUnitButtonEl.disabled = true;
this.#unitLabelDropdown.reset();
this.#unitLoadoutListEl.replaceChildren();
@ -153,6 +187,7 @@ export class UnitSpawnMenu {
this.#unitImageEl.classList.toggle("hide", true);
this.#unitLiveryDropdown.reset();
/* Populate the labels dropdown from the database */
var blueprints: UnitBlueprint[] = [];
if (this.#orderByRole)
blueprints = this.#unitDatabase.getByRole(this.spawnOptions.roleType);
@ -188,8 +223,10 @@ export class UnitSpawnMenu {
}
}
/* Request resizing */
this.#container.dispatchEvent(new Event("resize"));
/* Reset the spawn options */
this.spawnOptions.name = "";
this.spawnOptions.loadout = undefined;
this.spawnOptions.liveryID = undefined;
@ -198,23 +235,43 @@ export class UnitSpawnMenu {
})
this.#container.addEventListener("unitLabelChanged", () => {
/* If enabled, show the altitude slideer and loadouts section */
if (this.#showAltitudeSlider)
this.#unitSpawnAltitudeSlider.show();
if (this.#showLoadout) {
this.#unitLoadoutDropdown.show();
this.#unitLoadoutPreviewEl.classList.remove("hide");
}
/* Show the advanced options and unit info sections */
this.#advancedOptionsText.classList.remove("hide");
this.#advancedOptionsToggle.classList.remove("hide");
this.#advancedOptionsToggle.classList.remove("is-open");
this.#unitInfoText.classList.remove("hide");
this.#unitInfoToggle.classList.remove("hide");
this.#unitInfoToggle.classList.remove("is-open");
/* Enable the spawn button */
this.#deployUnitButtonEl.disabled = false;
/* If enabled, populate the loadout dropdown */
if (!this.#unitLoadoutDropdown.isHidden()) {
this.#unitLoadoutDropdown.setOptions(this.#unitDatabase.getLoadoutNamesByRole(this.spawnOptions.name, this.spawnOptions.roleType));
this.#unitLoadoutDropdown.selectValue(0);
}
/* Get the unit data from the db */
var blueprint = this.#unitDatabase.getByName(this.spawnOptions.name);
/* Shown the unit silhouette */
this.#unitImageEl.src = `images/units/${blueprint?.filename}`;
this.#unitImageEl.classList.toggle("hide", !(blueprint?.filename !== undefined));
this.#unitImageEl.classList.toggle("hide", !(blueprint?.filename !== undefined && blueprint?.filename !== ''));
/* Set the livery options */
this.#setUnitLiveryOptions();
this.#container.dispatchEvent(new Event("resize"));
this.#computeSpawnPoints();
/* Populate the description and abilities sections */
this.#descriptionDiv.replaceChildren();
this.#abilitiesDiv.replaceChildren();
@ -235,10 +292,16 @@ export class UnitSpawnMenu {
}
}
/* Show the range circles */
this.showCirclesPreviews();
/* Request resizing */
this.#container.dispatchEvent(new Event("resize"));
this.#computeSpawnPoints();
})
this.#container.addEventListener("unitLoadoutChanged", () => {
/* Update the loadout information */
var items = this.spawnOptions.loadout?.items.map((item: any) => { return `${item.quantity}x ${item.name}`; });
if (items != undefined) {
items.length == 0 ? items.push("Empty loadout") : "";
@ -255,10 +318,12 @@ export class UnitSpawnMenu {
})
this.#container.addEventListener("unitCountChanged", () => {
/* Recompute the spawn points */
this.#computeSpawnPoints();
})
this.#container.addEventListener("unitCountryChanged", () => {
/* Get the unit liveries by country */
this.#setUnitLiveryOptions();
})
@ -267,13 +332,13 @@ export class UnitSpawnMenu {
})
document.addEventListener('activeCoalitionChanged', () => {
/* If the coalition changed, update the circle previews to set the colours */
this.showCirclesPreviews();
});
this.#engagementCircle = new Circle(this.spawnOptions.latlng, { radius: 0, weight: 4, opacity: 0.8, fillOpacity: 0, dashArray: "4 8", interactive: false, bubblingMouseEvents: false });
this.#acquisitionCircle = new Circle(this.spawnOptions.latlng, { radius: 0, weight: 2, opacity: 0.8, fillOpacity: 0, dashArray: "8 12", interactive: false, bubblingMouseEvents: false });
}
abstract deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number): void;
getContainer() {
return this.#container;
}
@ -283,7 +348,10 @@ export class UnitSpawnMenu {
}
reset() {
/* Disable the spawn button */
this.#deployUnitButtonEl.disabled = true;
/* Reset all the dropdowns */
this.#unitRoleTypeDropdown.reset();
this.#unitLabelDropdown.reset();
this.#unitLiveryDropdown.reset();
@ -292,19 +360,37 @@ export class UnitSpawnMenu {
else
this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getTypes(this.unitTypeFilter));
/* Reset the contents of the div elements */
this.#unitLoadoutListEl.replaceChildren();
this.#unitLoadoutDropdown.reset();
this.#unitImageEl.classList.toggle("hide", true);
this.#descriptionDiv.replaceChildren();
this.#abilitiesDiv.replaceChildren();
this.setCountries();
this.#container.dispatchEvent(new Event("resize"));
/* Hide everything but the unit type dropdown */
this.#unitLabelDropdown.hide();
this.#unitCountDivider.classList.add("hide");
this.#unitCountDropdown.hide();
this.#unitLoadoutDropdown.hide();
this.#unitSpawnAltitudeSlider.hide();
this.#unitLoadoutPreviewEl.classList.add("hide");
this.#advancedOptionsDiv.classList.add("hide");
this.#unitInfoDiv.classList.add("hide");
this.#advancedOptionsText.classList.add("hide");
this.#advancedOptionsToggle.classList.add("hide");
this.#unitInfoText.classList.add("hide");
this.#unitInfoToggle.classList.add("hide");
/* Get the countries and clear the circle previews */
this.setCountries();
this.clearCirclesPreviews();
/* Request resizing */
this.#container.dispatchEvent(new Event("resize"));
}
setCountries() {
/* Create the countries dropdown elements (with the little flags) */
var coalitions = getApp().getMissionManager().getCoalitions();
var countries = Object.values(coalitions[getApp().getActiveCoalition() as keyof typeof coalitions]);
this.#unitCountryDropdown.setOptionsElements(this.#createCountryButtons(this.#unitCountryDropdown, countries, (country: string) => { this.#setUnitCountry(country) }));
@ -315,13 +401,6 @@ export class UnitSpawnMenu {
}
}
refreshOptions() {
//if (!this.#unitDatabase.getTypes().includes(this.#unitTypeDropdown.getValue()))
// this.reset();
//if (!this.#unitDatabase.getByType(this.#unitTypeDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#unitLabelDropdown.getValue()))
// this.resetUnitLabel();
}
showCirclesPreviews() {
this.clearCirclesPreviews();
@ -425,6 +504,14 @@ export class UnitSpawnMenu {
return this.#unitSpawnAltitudeSlider;
}
setShowLoadout(showLoadout: boolean) {
this.#showLoadout = showLoadout;
}
setShowAltitudeSlider(showAltitudeSlider: boolean) {
this.#showAltitudeSlider = showAltitudeSlider;
}
#setUnitRoleType(roleType: string) {
this.spawnOptions.roleType = roleType;
this.#container.dispatchEvent(new Event("unitRoleTypeChanged"));
@ -482,10 +569,6 @@ export class UnitSpawnMenu {
}
}
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
/* Virtual function must be overloaded by inheriting classes */
}
#createCountryButtons(parent: Dropdown, countries: string[], callback: CallableFunction) {
return Object.values(countries).map((country: string) => {
var el = document.createElement("div");
@ -614,7 +697,6 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu {
}
export class GroundUnitSpawnMenu extends UnitSpawnMenu {
protected showRangeCircles: boolean = true;
protected unitTypeFilter = (unit:any) => {return !(GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.type))};
@ -625,8 +707,8 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu {
constructor(ID: string){
super(ID, groundUnitDatabase, false);
this.setMaxUnitCount(20);
this.getAltitudeSlider().hide();
this.getLoadoutDropdown().hide();
this.setShowAltitudeSlider(false);
this.setShowLoadout(false);
this.getLoadoutPreview().classList.add("hide");
}
@ -656,7 +738,6 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu {
}
export class AirDefenceUnitSpawnMenu extends GroundUnitSpawnMenu {
protected unitTypeFilter = (unit:any) => {return GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.type)};
/**
@ -677,9 +758,8 @@ export class NavyUnitSpawnMenu extends UnitSpawnMenu {
constructor(ID: string){
super(ID, navyUnitDatabase, false);
this.setMaxUnitCount(4);
this.getAltitudeSlider().hide();
this.getLoadoutDropdown().hide();
this.getLoadoutPreview().classList.add("hide");
this.setShowAltitudeSlider(false);
this.setShowLoadout(false);
}
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {

View File

@ -7,7 +7,7 @@ import { AirbaseContextMenu } from "../contextmenus/airbasecontextmenu";
import { Dropdown } from "../controls/dropdown";
import { Airbase } from "../mission/airbase";
import { Unit } from "../unit/unit";
import { bearing, createCheckboxOption } from "../other/utils";
import { bearing, createCheckboxOption, polyContains } from "../other/utils";
import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker";
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
@ -559,7 +559,7 @@ export class Map extends L.Map {
/* Coalition areas are ordered in the #coalitionAreas array according to their zindex. Select the upper one */
for (let coalitionArea of this.#coalitionAreas) {
if (coalitionArea.getBounds().contains(e.latlng)) {
if (polyContains(e.latlng, coalitionArea)) {
if (coalitionArea.getSelected())
clickedCoalitionArea = coalitionArea;
else
@ -662,7 +662,7 @@ export class Map extends L.Map {
this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng);
this.#updateDestinationCursors();
}
else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
else if (this.#state === COALITIONAREA_DRAW_POLYGON && e.latlng !== undefined) {
this.#drawingCursor.setLatLng(e.latlng);
/* Update the polygon being drawn with the current position of the mouse cursor */
this.getSelectedCoalitionArea()?.moveActiveVertex(e.latlng);

View File

@ -1108,6 +1108,36 @@ export class UnitsManager {
const activeEras = Object.keys(eras).filter((key: string) => { return eras[key]; });
const activeRanges = Object.keys(ranges).filter((key: string) => { return ranges[key]; });
var airbases = getApp().getMissionManager().getAirbases();
Object.keys(airbases).forEach((airbaseName: string) => {
var airbase = airbases[airbaseName];
/* Check if the city is inside the coalition area */
if (polyContains(new LatLng(airbase.getLatLng().lat, airbase.getLatLng().lng), coalitionArea)) {
/* Arbitrary formula to obtain a number of units */
var pointsNumber = 2 + 40 * density / 100;
for (let i = 0; i < pointsNumber; i++) {
/* Place the unit nearby the airbase, depending on the distribution parameter */
var bearing = Math.random() * 360;
var distance = Math.random() * distribution * 100;
const latlng = bearingAndDistanceToLatLng(airbase.getLatLng().lat, airbase.getLatLng().lng, bearing, distance);
/* Make sure the unit is still inside the coalition area */
if (polyContains(latlng, coalitionArea)) {
const type = activeTypes[Math.floor(Math.random() * activeTypes.length)];
if (Math.random() < IADSDensities[type]) {
/* Get a random blueprint depending on the selected parameters and spawn the unit */
const unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { type: type, eras: activeEras, ranges: activeRanges });
if (unitBlueprint) {
this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "" }], coalitionArea.getCoalition(), true, "", "", (res: any) =>{
getApp().getMap().addTemporaryMarker(latlng, unitBlueprint.name, getApp().getActiveCoalition(), res.commandHash);
});
}
}
}
}
}
})
citiesDatabase.forEach((city: { lat: number, lng: number, pop: number }) => {
/* Check if the city is inside the coalition area */
if (polyContains(new LatLng(city.lat, city.lng), coalitionArea)) {
@ -1125,8 +1155,11 @@ export class UnitsManager {
if (Math.random() < IADSDensities[type]) {
/* Get a random blueprint depending on the selected parameters and spawn the unit */
const unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { type: type, eras: activeEras, ranges: activeRanges });
if (unitBlueprint)
this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "" }], coalitionArea.getCoalition(), true);
if (unitBlueprint) {
this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "" }], coalitionArea.getCoalition(), true, "", "", (res: any) =>{
getApp().getMap().addTemporaryMarker(latlng, unitBlueprint.name, getApp().getActiveCoalition(), res.commandHash);
});
}
}
}
}