diff --git a/backend/core/src/groundunit.cpp b/backend/core/src/groundunit.cpp index 5aa11bd6..e15bcb15 100644 --- a/backend/core/src/groundunit.cpp +++ b/backend/core/src/groundunit.cpp @@ -242,7 +242,12 @@ void GroundUnit::AIloop() if (!getHasTask()) { std::ostringstream taskSS; taskSS.precision(10); - taskSS << "{id = 'FireAtPoint', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << ", radius = 100}"; + if (targetPosition.alt == NULL) { + taskSS << "{id = 'FireAtPoint', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << ", radius = 100}"; + } + else { + taskSS << "{id = 'FireAtPoint', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << ", alt = " << targetPosition.alt << ", radius = 100}"; + } Command* command = dynamic_cast(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); })); scheduler->appendCommand(command); setHasTask(true); @@ -437,9 +442,22 @@ void GroundUnit::AIloop() taskString += "Missing on purpose. Valid target at range: " + to_string((int) round(distance)) + "m"; double correctedAimTime = aimTime; + double dstep = 0; + double vstep = muzzleVelocity; + double dt = 0.1; + double k = 0.0086; + double gdelta = 9.81; + /* Approximate the flight time */ - if (muzzleVelocity != 0) - correctedAimTime += distance / muzzleVelocity; + unsigned int stepCount = 0; + if (muzzleVelocity != 0) { + while (dstep < distance && stepCount < 1000) { + dstep += vstep * dt; + vstep -= (k * vstep + gdelta) * dt; + stepCount++; + } + correctedAimTime += stepCount * dt; + } /* If the target is in targeting range and we are in highest precision mode, target it */ if (distance < targetingRange && shotsScatter == ShotsScatter::LOW) { diff --git a/backend/core/src/scheduler.cpp b/backend/core/src/scheduler.cpp index 30243911..663600d6 100644 --- a/backend/core/src/scheduler.cpp +++ b/backend/core/src/scheduler.cpp @@ -619,6 +619,11 @@ void Scheduler::handleRequest(string key, json::value value, string username, js 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; + + if (value[L"location"].has_number_field(L"alt")) { + loc.alt = value[L"location"][L"alt"].as_double(); + } + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) { unit->setTargetPosition(loc); diff --git a/frontend/react/src/interfaces.ts b/frontend/react/src/interfaces.ts index f99e9f2f..869237cd 100644 --- a/frontend/react/src/interfaces.ts +++ b/frontend/react/src/interfaces.ts @@ -89,7 +89,7 @@ export interface BullseyesData { export interface SpotsData { spots: { - [key: string]: { type: string; targetPosition: { lat: number; lng: number }; sourceUnitID: number; code?: number }; + [key: string]: { active: boolean; type: string; targetPosition: { lat: number; lng: number }; sourceUnitID: number; code?: number }; }; sessionHash: string; time: number; diff --git a/frontend/react/src/mission/missionmanager.ts b/frontend/react/src/mission/missionmanager.ts index 2900dc06..d61247dd 100644 --- a/frontend/react/src/mission/missionmanager.ts +++ b/frontend/react/src/mission/missionmanager.ts @@ -6,13 +6,20 @@ import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/c import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionData, SpotsData } from "../interfaces"; import { Coalition } from "../types/types"; import { Carrier } from "./carrier"; -import { AirbaseSelectedEvent, AppStateChangedEvent, BullseyesDataChangedEvent, CommandModeOptionsChangedEvent, EnabledCommandModesChangedEvent, MissionDataChangedEvent } from "../events"; +import { + AirbaseSelectedEvent, + AppStateChangedEvent, + BullseyesDataChangedEvent, + CommandModeOptionsChangedEvent, + EnabledCommandModesChangedEvent, + MissionDataChangedEvent, +} from "../events"; import { Spot } from "./spot"; /** The MissionManager */ export class MissionManager { #bullseyes: { [name: string]: Bullseye } = {}; - #spots: {[key: string]: Spot} = {}; + #spots: { [key: string]: Spot } = {}; #airbases: { [name: string]: Airbase | Carrier } = {}; #theatre: string = ""; #dateAndTime: DateAndTime = { @@ -39,7 +46,7 @@ export class MissionManager { constructor() { AppStateChangedEvent.on((state, subState) => { if (this.getSelectedAirbase() !== null) AirbaseSelectedEvent.dispatch(null); - }) + }); } /** Update location of bullseyes @@ -63,7 +70,7 @@ export class MissionManager { this.#bullseyes[idx].setCoalition(bullseye.coalition); } - BullseyesDataChangedEvent.dispatch(this.#bullseyes) + BullseyesDataChangedEvent.dispatch(this.#bullseyes); } } @@ -72,18 +79,18 @@ export class MissionManager { const spotID = Number(idx); const spot = data.spots[idx]; if (this.#spots[spotID] === undefined) { - this.#spots[spotID] = new Spot(spotID, spot.type, new LatLng(spot.targetPosition.lat, spot.targetPosition.lng), spot.sourceUnitID, spot.code); + this.#spots[spotID] = new Spot( + spotID, + spot.type, + new LatLng(spot.targetPosition.lat, spot.targetPosition.lng), + spot.sourceUnitID, + spot.active, + spot.code + ); } else { - if (spot.type === "laser") - this.#spots[spotID].setCode(spot.code ?? 0) - this.#spots[spotID].setTargetPosition( new LatLng(spot.targetPosition.lat, spot.targetPosition.lng)); - } - } - - /* Iterate the existing spots and remove all spots that where deleted */ - for (let idx in this.#spots) { - if (data.spots[idx] === undefined) { - delete this.#spots[idx]; + if (spot.type === "laser") this.#spots[spotID].setCode(spot.code ?? 0); + this.#spots[spotID].setActive(spot.active); + this.#spots[spotID].setTargetPosition(new LatLng(spot.targetPosition.lat, spot.targetPosition.lng)); } } } @@ -99,7 +106,7 @@ export class MissionManager { updateAirbases(data: AirbasesData) { for (let idx in data.airbases) { var airbase = data.airbases[idx]; - var airbaseCallsign = airbase.callsign !== ""? airbase.callsign: `carrier-${airbase.unitId}` + var airbaseCallsign = airbase.callsign !== "" ? airbase.callsign : `carrier-${airbase.unitId}`; if (this.#airbases[airbaseCallsign] === undefined) { if (airbase.callsign != "") { this.#airbases[airbaseCallsign] = new Airbase({ @@ -161,7 +168,7 @@ export class MissionManager { return this.#airbases; } - getSpots() { + getSpots() { return this.#spots; } @@ -279,7 +286,7 @@ export class MissionManager { commandModeOptions.spawnPoints.red !== this.getCommandModeOptions().spawnPoints.red || commandModeOptions.spawnPoints.blue !== this.getCommandModeOptions().spawnPoints.blue || commandModeOptions.restrictSpawns !== this.getCommandModeOptions().restrictSpawns || - commandModeOptions.restrictToCoalition !== this.getCommandModeOptions().restrictToCoalition || + commandModeOptions.restrictToCoalition !== this.getCommandModeOptions().restrictToCoalition || commandModeOptions.setupTime !== this.getCommandModeOptions().setupTime; this.#commandModeOptions = commandModeOptions; diff --git a/frontend/react/src/mission/spot.ts b/frontend/react/src/mission/spot.ts index 2bd72624..95f10735 100644 --- a/frontend/react/src/mission/spot.ts +++ b/frontend/react/src/mission/spot.ts @@ -2,47 +2,57 @@ import { LatLng } from "leaflet"; import { getApp } from "../olympusapp"; export class Spot { - private ID: number; - private type: string; - private targetPosition: LatLng; - private sourceUnitID: number; - private code?: number; + #ID: number; + #type: string; + #targetPosition: LatLng; + #sourceUnitID: number; + #active: boolean; + #code?: number; - constructor(ID: number, type: string, targetPosition: LatLng, sourceUnitID: number, code?: number) { - this.ID = ID; - this.type = type; - this.targetPosition = targetPosition; - this.sourceUnitID = sourceUnitID; - this.code = code; + constructor(ID: number, type: string, targetPosition: LatLng, sourceUnitID: number, active: boolean, code?: number) { + this.#ID = ID; + this.#type = type; + this.#targetPosition = targetPosition; + this.#sourceUnitID = sourceUnitID; + this.#code = code; + this.#active = active; } // Getter methods getID() { - return this.ID; + return this.#ID; } getType() { - return this.type; + return this.#type; } getTargetPosition() { - return this.targetPosition; + return this.#targetPosition; } getSourceUnitID() { - return this.sourceUnitID; + return this.#sourceUnitID; } getCode() { - return this.code; + return this.#code; + } + + getActive() { + return this.#active; } // Setter methods setTargetPosition(position: LatLng) { - this.targetPosition = position; + this.#targetPosition = position; } setCode(code: number) { - this.code = code; + this.#code = code; + } + + setActive(active: boolean) { + this.#active = active; } } \ No newline at end of file diff --git a/frontend/react/src/ui/panels/header.tsx b/frontend/react/src/ui/panels/header.tsx index e3891a3a..9da74fcf 100644 --- a/frontend/react/src/ui/panels/header.tsx +++ b/frontend/react/src/ui/panels/header.tsx @@ -50,6 +50,8 @@ import { import { OlympusConfig } from "../../interfaces"; import { FaCheck, FaQuestionCircle, FaSave, FaSpinner } from "react-icons/fa"; import { OlExpandingTooltip } from "../components/olexpandingtooltip"; +import { ftToM } from "../../other/utils"; +import { LatLng } from "leaflet"; export function Header() { const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS); @@ -219,6 +221,18 @@ export function Header() { />
+ { + getApp().getUnitsManager().getSelectedUnits().forEach((unit) => { + let position = new LatLng(unit.getPosition().lat, unit.getPosition().lng); + position.lat += 0.01; + position.alt = ftToM(15000); + unit.fireAtArea(position); + }) + }} + checked={false} + icon={faFlag} + />
{Object.entries({ human: olButtonsVisibilityHuman, diff --git a/frontend/react/src/unit/unit.ts b/frontend/react/src/unit/unit.ts index 917e33bc..ed44267d 100644 --- a/frontend/react/src/unit/unit.ts +++ b/frontend/react/src/unit/unit.ts @@ -1953,21 +1953,29 @@ export abstract class Unit extends CustomMarker { // Iterate over all spots and draw lines, edit markers, and markers Object.values(getApp().getMissionManager().getSpots()).forEach((spot: Spot) => { if (spot.getSourceUnitID() === this.ID) { - const spotBearing = deg2rad(bearing(this.getPosition().lat, this.getPosition().lng, spot.getTargetPosition().lat, spot.getTargetPosition().lng, false)); - const spotDistance = this.getPosition().distanceTo(spot.getTargetPosition()); - const midPosition = bearingAndDistanceToLatLng(this.getPosition().lat, this.getPosition().lng, spotBearing, spotDistance / 2); + if (spot.getActive()) { + const spotBearing = deg2rad( + bearing(this.getPosition().lat, this.getPosition().lng, spot.getTargetPosition().lat, spot.getTargetPosition().lng, false) + ); + const spotDistance = this.getPosition().distanceTo(spot.getTargetPosition()); + const midPosition = bearingAndDistanceToLatLng(this.getPosition().lat, this.getPosition().lng, spotBearing, spotDistance / 2); - // Draw the spot line - this.#drawSpotLine(spot, spotBearing); + // Draw the spot line + this.#drawSpotLine(spot, spotBearing); - // Draw the spot edit marker if the map is zoomed in enough - if (getApp().getMap().getZoom() >= SPOTS_EDIT_ZOOM_TRANSITION) { - // Draw the spot edit marker - this.#drawSpotEditMarker(spot, midPosition, spotBearing); + // Draw the spot edit marker if the map is zoomed in enough + if (getApp().getMap().getZoom() >= SPOTS_EDIT_ZOOM_TRANSITION) { + // Draw the spot edit marker + this.#drawSpotEditMarker(spot, midPosition, spotBearing); + } + + // Draw the spot marker + this.#drawSpotMarker(spot); + } else { + this.#spotLines[spot.getID()]?.removeFrom(getApp().getMap()); + this.#spotEditMarkers[spot.getID()]?.removeFrom(getApp().getMap()); + this.#spotMarkers[spot.getID()]?.removeFrom(getApp().getMap()); } - - // Draw the spot marker - this.#drawSpotMarker(spot); } }); } diff --git a/frontend/react/src/weapon/weapon.ts b/frontend/react/src/weapon/weapon.ts index 6b6f4b02..2d448b99 100644 --- a/frontend/react/src/weapon/weapon.ts +++ b/frontend/react/src/weapon/weapon.ts @@ -20,7 +20,9 @@ export abstract class Weapon extends CustomMarker { #hidden: boolean = false; #detectionMethods: number[] = []; - + #speedVector: number[] = []; + #altitude: number[] = []; + getAlive() { return this.#alive; } @@ -86,6 +88,7 @@ export abstract class Weapon extends CustomMarker { break; case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); + this.#speedVector.push(this.#speed); updateMarker = true; break; case DataIndexes.heading: @@ -116,6 +119,9 @@ export abstract class Weapon extends CustomMarker { getApp().getMap().addFlakMarker(this.getLatLng()); } this.#alive = newAlive; + if (this.#speedVector.length > 0 && newAlive === false) { + let asd = 1; + } } belongsToCommandedCoalition() { diff --git a/scripts/lua/backend/OlympusCommand.lua b/scripts/lua/backend/OlympusCommand.lua index 4a0d2385..44f27268 100644 --- a/scripts/lua/backend/OlympusCommand.lua +++ b/scripts/lua/backend/OlympusCommand.lua @@ -589,6 +589,7 @@ function Olympus.fireLaser(ID, code, lat, lng) lat = lat, lng = lng }, + active = true, code = code } end @@ -611,13 +612,15 @@ function Olympus.fireInfrared(ID, lat, lng) targetPosition = { lat = lat, lng = lng - } + }, + active = true } end end -- Set new laser code function Olympus.setLaserCode(spotID, code) + Olympus.debug("Olympus.setLaserCode " .. spotID .. " -> " .. code, 2) local spot = Olympus.spots[spotID] if spot ~= nil and spot.type == "laser" then spot.object:setCode(code) @@ -627,19 +630,21 @@ end -- Move spot to a new location function Olympus.moveSpot(spotID, lat, lng) + Olympus.debug("Olympus.moveSpot " .. spotID .. " -> (" .. lat .. ", " .. lng .. ")", 2) local spot = Olympus.spots[spotID] if spot ~= nil then - spot.object:setPoint(coord.LLtoLO(lat, lng, 0)) + spot.object:setPoint(mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0))) spot.targetPosition = {lat = lat, lng = lng} end end -- Remove the spot function Olympus.deleteSpot(spotID) + Olympus.debug("Olympus.deleteSpot " .. spotID, 2) local spot = Olympus.spots[spotID] if spot ~= nil then spot.object:destroy() - Olympus.spots[spotID] = nil + Olympus.spots[spotID]["active"] = false end end @@ -1415,8 +1420,8 @@ function Olympus.setWeaponsData(arg, time) table["category"] = "Missile" elseif weapon:getDesc().category == Weapon.Category.BOMB then table["category"] = "Bomb" - elseif weapon:getDesc().category == Weapon.Category.SHELL then - table["category"] = "Shell" + --elseif weapon:getDesc().category == Weapon.Category.SHELL then + -- table["category"] = "Shell" -- Useful for debugging but has no real use and has big impact on performance end else weapons[ID] = {isAlive = false} @@ -1527,6 +1532,7 @@ function Olympus.setMissionData(arg, time) type = spot.type, sourceUnitID = spot.sourceUnitID, targetPosition = spot.targetPosition, + active = spot.active, } -- If the spot type is "laser", add the code to the spot entry @@ -1542,7 +1548,7 @@ function Olympus.setMissionData(arg, time) Olympus.missionData["spots"] = spots Olympus.OlympusDLL.setMissionData() - return time + 1 -- For perfomance reasons weapons are updated once every second + return time + 1 -- For perfomance reasons mission data is updated once every second end -- Initializes the units table with all the existing ME units