From 5a4a2028059aeb356cfd73347101ec8e856daf6b Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 29 Jan 2025 08:03:32 +0100 Subject: [PATCH] feat: started implementing infrared and laser --- backend/core/include/commands.h | 40 ++++++++++++++ backend/core/src/commands.cpp | 25 +++++++++ backend/core/src/scheduler.cpp | 31 +++++++++++ frontend/react/src/constants/constants.ts | 27 +++++++++ .../src/map/markers/temporaryunitmarker.ts | 6 +- frontend/react/src/olympusapp.ts | 3 + frontend/react/src/server/servermanager.ts | 12 ++++ frontend/react/src/unit/unit.ts | 10 ++++ frontend/react/src/unit/unitsmanager.ts | 55 +++++++++++++++++++ scripts/lua/backend/OlympusCommand.lua | 24 ++++++++ 10 files changed, 231 insertions(+), 2 deletions(-) diff --git a/backend/core/include/commands.h b/backend/core/include/commands.h index 2badd091..638ad877 100644 --- a/backend/core/include/commands.h +++ b/backend/core/include/commands.h @@ -430,3 +430,43 @@ private: const unsigned int intensity; const string explosionType; }; + +/* Shine a laser with a specific code */ +class Laser : public Command +{ +public: + Laser(unsigned int ID, unsigned int code, Coords destination, function callback = []() {}) : + Command(callback), + ID(ID), + destination(destination), + code(code) + { + priority = CommandPriority::LOW; + }; + virtual string getString(); + virtual unsigned int getLoad() { return 5; } + +private: + const unsigned int ID; + const unsigned int code; + const Coords destination; +}; + +/* Shine a infrared light */ +class Infrared : public Command +{ +public: + Infrared(unsigned int ID, Coords destination, function callback = []() {}) : + Command(callback), + ID(ID), + destination(destination) + { + priority = CommandPriority::LOW; + }; + virtual string getString(); + virtual unsigned int getLoad() { return 5; } + +private: + const unsigned int ID; + const Coords destination; +}; diff --git a/backend/core/src/commands.cpp b/backend/core/src/commands.cpp index dde084be..467c43c8 100644 --- a/backend/core/src/commands.cpp +++ b/backend/core/src/commands.cpp @@ -257,4 +257,29 @@ string Explosion::getString() << location.lat << ", " << location.lng; return commandSS.str(); +} + +/* Laser command */ +string Laser::getString() +{ + std::ostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.laser, " + << ID << ", " + << code << ", " + << destination.lat << ", " + << destination.lng; + return commandSS.str(); +} + +/* Infrared command */ +string Infrared::getString() +{ + std::ostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.infrared, " + << ID << ", " + << destination.lat << ", " + << destination.lng; + return commandSS.str(); } \ No newline at end of file diff --git a/backend/core/src/scheduler.cpp b/backend/core/src/scheduler.cpp index 811cdcdc..b45fb9eb 100644 --- a/backend/core/src/scheduler.cpp +++ b/backend/core/src/scheduler.cpp @@ -695,6 +695,37 @@ void Scheduler::handleRequest(string key, json::value value, string username, js } } /************************/ + else if (key.compare("fireLaser") == 0) + { + unsigned int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + if (unit != nullptr) { + 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; + unsigned int code = value[L"code"].as_integer(); + + log("Adding laser with code " + to_string(code) + " from unit " + unit->getUnitName() + " to (" + to_string(lat) + ", " + to_string(lng) + ")"); + + command = dynamic_cast(new Laser(ID, code, loc)); + } + } + /************************/ + else if (key.compare("fireInfrared") == 0) + { + unsigned int ID = value[L"ID"].as_integer(); + Unit* unit = unitsManager->getUnit(ID); + if (unit != nullptr) { + 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; + + log("Adding infrared from unit " + unit->getUnitName() + " to (" + to_string(lat) + ", " + to_string(lng) + ")"); + + command = dynamic_cast(new Infrared(ID, loc)); + } + } + /************************/ else if (key.compare("setCommandModeOptions") == 0) { setCommandModeOptions(value); diff --git a/frontend/react/src/constants/constants.ts b/frontend/react/src/constants/constants.ts index cb919638..f9f3b4aa 100644 --- a/frontend/react/src/constants/constants.ts +++ b/frontend/react/src/constants/constants.ts @@ -6,6 +6,7 @@ import { faClone, faExplosion, faHand, + faLightbulb, faLocationCrosshairs, faLocationDot, faMapLocation, @@ -887,6 +888,32 @@ export namespace ContextActions { { type: ContextActionType.ENGAGE, code: "KeyV", ctrlKey: false, shiftKey: false } ); + export const FIRE_LASER = new ContextAction( + "fire-laser", + "Shine laser at point", + "Click on a point to shine a laser with the given code from the unit to the ground.", + faLightbulb, + ContextActionTarget.POINT, + (units: Unit[], _, targetPosition: LatLng | null) => { + if (targetPosition) + getApp().getUnitsManager().fireLaser(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units); + }, + { type: ContextActionType.ENGAGE, code: "KeyL", ctrlKey: true, shiftKey: false } + ); + + export const FIRE_INFRARED = new ContextAction( + "fire-infrared", + "Shine infrared at point", + "Click on a point to shine a infrared beam from the unit to the ground.", + faLightbulb, + ContextActionTarget.POINT, + (units: Unit[], _, targetPosition: LatLng | null) => { + if (targetPosition) + getApp().getUnitsManager().fireInfrared(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units); + }, + { type: ContextActionType.ENGAGE, code: "KeyL", ctrlKey: true, shiftKey: false } + ); + export const SIMULATE_FIRE_FIGHT = new ContextAction( "simulate-fire-fight", "Simulate fire fight", diff --git a/frontend/react/src/map/markers/temporaryunitmarker.ts b/frontend/react/src/map/markers/temporaryunitmarker.ts index ff09d38e..7d093645 100644 --- a/frontend/react/src/map/markers/temporaryunitmarker.ts +++ b/frontend/react/src/map/markers/temporaryunitmarker.ts @@ -67,8 +67,9 @@ export class TemporaryUnitMarker extends CustomMarker { el.append(unitIcon); // Short label + let shortLabel: null | HTMLDivElement = null; if (blueprint.category == "aircraft" || blueprint.category == "helicopter") { - var shortLabel = document.createElement("div"); + shortLabel = document.createElement("div"); shortLabel.classList.add("unit-short-label"); shortLabel.innerText = blueprint?.shortLabel || ""; el.append(shortLabel); @@ -88,7 +89,8 @@ export class TemporaryUnitMarker extends CustomMarker { const rotateHandle = (heading) => { el.style.transform = `rotate(${heading}deg)`; unitIcon.style.transform = `rotate(-${heading}deg)`; - shortLabel.style.transform = `rotate(-${heading}deg)`; + if (shortLabel) + shortLabel.style.transform = `rotate(-${heading}deg)`; }; SpawnHeadingChangedEvent.on((heading) => rotateHandle(heading)); diff --git a/frontend/react/src/olympusapp.ts b/frontend/react/src/olympusapp.ts index a6f13647..d395f3ff 100644 --- a/frontend/react/src/olympusapp.ts +++ b/frontend/react/src/olympusapp.ts @@ -192,6 +192,9 @@ export class OlympusApp { this.getServerManager().setActiveCommandMode(GAME_MASTER); } + else if (this.getState() !== OlympusState.LOGIN) { + this.setState(OlympusState.LOGIN, LoginSubState.CREDENTIALS); + } } else if (this.getState() !== OlympusState.LOGIN) { this.setState(OlympusState.LOGIN, LoginSubState.CREDENTIALS); } diff --git a/frontend/react/src/server/servermanager.ts b/frontend/react/src/server/servermanager.ts index c2b4acda..ca74781e 100644 --- a/frontend/react/src/server/servermanager.ts +++ b/frontend/react/src/server/servermanager.ts @@ -450,6 +450,18 @@ export class ServerManager { this.PUT(data, callback); } + fireLaser(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) { + var command = { ID: ID, location: latlng, code: 1688 }; + var data = { fireLaser: command }; + this.PUT(data, callback); + } + + fireInfrared(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) { + var command = { ID: ID, location: latlng }; + var data = { fireInfrared: command }; + this.PUT(data, callback); + } + simulateFireFight(ID: number, latlng: LatLng, altitude: number, callback: CallableFunction = () => {}) { var command = { ID: ID, location: latlng, altitude: altitude }; var data = { simulateFireFight: command }; diff --git a/frontend/react/src/unit/unit.ts b/frontend/react/src/unit/unit.ts index 6b8d8540..4a3ff44a 100644 --- a/frontend/react/src/unit/unit.ts +++ b/frontend/react/src/unit/unit.ts @@ -915,6 +915,8 @@ export abstract class Unit extends CustomMarker { contextActionSet.addContextAction(this, ContextActions.CENTER_MAP); contextActionSet.addContextAction(this, ContextActions.CLONE); contextActionSet.addContextAction(this, ContextActions.ATTACK); + contextActionSet.addContextAction(this, ContextActions.FIRE_LASER); + contextActionSet.addContextAction(this, ContextActions.FIRE_INFRARED); contextActionSet.addDefaultContextAction(this, ContextActions.MOVE); } @@ -1325,6 +1327,14 @@ export abstract class Unit extends CustomMarker { getApp().getServerManager().fireAtArea(this.ID, latlng); } + fireLaser(latlng: LatLng) { + getApp().getServerManager().fireLaser(this.ID, latlng); + } + + fireInfrared(latlng: LatLng) { + getApp().getServerManager().fireInfrared(this.ID, latlng); + } + simulateFireFight(latlng: LatLng, targetGroundElevation: number | null) { getGroundElevation(this.getPosition(), (response: string) => { var unitGroundElevation: number | null = null; diff --git a/frontend/react/src/unit/unitsmanager.ts b/frontend/react/src/unit/unitsmanager.ts index 648f7ec3..8af9e940 100644 --- a/frontend/react/src/unit/unitsmanager.ts +++ b/frontend/react/src/unit/unitsmanager.ts @@ -895,6 +895,61 @@ export class UnitsManager { this.#protectionCallback = callback; } else callback(units); } + + /** Instruct the selected units to fire at specific coordinates + * + * @param latlng Location to fire at + * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. + */ + fireLaser(latlng: LatLng, mantainRelativePosition: boolean, rotation: number = 0, units: Unit[] | null = null) { + if (units === null) units = this.getSelectedUnits(); + units = units.filter((unit) => !unit.getHuman()); + + let callback = (units) => { + /* Compute the target for each unit. If mantainRelativePosition is true, compute the target so to hold the relative positions */ + var unitTargets: { [key: number]: LatLng } = {}; + if (mantainRelativePosition) unitTargets = this.computeGroupDestination(latlng, rotation); + else + units.forEach((unit: Unit) => { + unitTargets[unit.ID] = latlng; + }); + units.forEach((unit: Unit) => unit.fireLaser(unitTargets[unit.ID])); + this.#showActionMessage(units, `unit shining laser at point`); + }; + + if (getApp().getMap().getOptions().protectDCSUnits && !units.every((unit) => unit.isControlledByOlympus())) { + getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.PROTECTION); + this.#protectionCallback = callback; + } else callback(units); + } + + /** Instruct the selected units to fire at specific coordinates + * + * @param latlng Location to fire at + * @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units. + */ + fireInfrared(latlng: LatLng, mantainRelativePosition: boolean, rotation: number = 0, units: Unit[] | null = null) { + if (units === null) units = this.getSelectedUnits(); + units = units.filter((unit) => !unit.getHuman()); + + let callback = (units) => { + /* Compute the target for each unit. If mantainRelativePosition is true, compute the target so to hold the relative positions */ + var unitTargets: { [key: number]: LatLng } = {}; + if (mantainRelativePosition) unitTargets = this.computeGroupDestination(latlng, rotation); + else + units.forEach((unit: Unit) => { + unitTargets[unit.ID] = latlng; + }); + units.forEach((unit: Unit) => unit.fireInfrared(unitTargets[unit.ID])); + this.#showActionMessage(units, `unit shining infrared at point`); + }; + + if (getApp().getMap().getOptions().protectDCSUnits && !units.every((unit) => unit.isControlledByOlympus())) { + getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.PROTECTION); + this.#protectionCallback = callback; + } else callback(units); + } + /** Instruct the selected units to simulate a fire fight at specific coordinates * * @param latlng Location to fire at diff --git a/scripts/lua/backend/OlympusCommand.lua b/scripts/lua/backend/OlympusCommand.lua index a8940fb8..b30e0197 100644 --- a/scripts/lua/backend/OlympusCommand.lua +++ b/scripts/lua/backend/OlympusCommand.lua @@ -566,6 +566,30 @@ function Olympus.randomDebries(vec3) end end +-- Shines a laser from a unit to a point +function Olympus.fireLaser(ID, code, lat, lng) + Olympus.debug("Olympus.fireLaser " .. ID .. " -> (" .. lat .. ", " .. lng .. ") code " .. code, 2) + + local vec3 = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng)) + + local unit = Olympus.getUnitByID(ID) + if unit ~= nil and unit:isExist() then + local ray = Spot.createLaser(unit, {x = 0, y = 1, z = 0}, vec3, code) + end +end + +-- Shines a infrared light from a unit to a point +function Olympus.fireInfrared(ID, lat, lng) + Olympus.debug("Olympus.fireInfrared " .. ID .. " -> (" .. lat .. ", " .. lng .. ")", 2) + + local vec3 = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng)) + + local unit = Olympus.getUnitByID(ID) + if unit ~= nil and unit:isExist() then + local ray = Spot.createInfraRed(unit, {x = 0, y = 1, z = 0}, vec3) + end +end + -- Spawns a new unit or group -- Spawn table contains the following parameters -- category: (string), either Aircraft, Helicopter, GroundUnit or NavyUnit