mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge pull request #1043 from Pax1601/features/redgreen-unit
Features/redgreen unit
This commit is contained in:
commit
039c922649
@ -18,6 +18,7 @@ namespace SetCommandType {
|
||||
FORMATION = 5,
|
||||
RTB_ON_BINGO = 6,
|
||||
SILENCE = 7,
|
||||
ALARM_STATE = 9,
|
||||
RTB_ON_OUT_OF_AMMO = 10,
|
||||
ECM_USING = 13,
|
||||
PROHIBIT_AA = 14,
|
||||
@ -45,6 +46,14 @@ namespace ROE {
|
||||
};
|
||||
}
|
||||
|
||||
namespace AlarmState {
|
||||
enum AlarmStates {
|
||||
AUTO = 0,
|
||||
GREEN = 1,
|
||||
RED = 2,
|
||||
};
|
||||
}
|
||||
|
||||
namespace ReactionToThreat {
|
||||
enum ReactionsToThreat {
|
||||
NO_REACTION = 0,
|
||||
|
||||
@ -7,6 +7,8 @@ namespace DataIndex {
|
||||
startOfData = 0,
|
||||
category,
|
||||
alive,
|
||||
alarmState,
|
||||
radarState,
|
||||
human,
|
||||
controlled,
|
||||
coalition,
|
||||
|
||||
@ -67,6 +67,7 @@ public:
|
||||
/********** Setters **********/
|
||||
virtual void setCategory(string newValue) { updateValue(category, newValue, DataIndex::category); }
|
||||
virtual void setAlive(bool newValue) { updateValue(alive, newValue, DataIndex::alive); }
|
||||
virtual void setAlarmState(unsigned char newValue, bool force = false);
|
||||
virtual void setHuman(bool newValue) { updateValue(human, newValue, DataIndex::human); }
|
||||
virtual void setControlled(bool newValue) { updateValue(controlled, newValue, DataIndex::controlled); }
|
||||
virtual void setCoalition(unsigned char newValue) { updateValue(coalition, newValue, DataIndex::coalition); }
|
||||
@ -125,10 +126,12 @@ public:
|
||||
virtual void setTargetingRange(double newValue) { updateValue(targetingRange, newValue, DataIndex::targetingRange); }
|
||||
virtual void setAimMethodRange(double newValue) { updateValue(aimMethodRange, newValue, DataIndex::aimMethodRange); }
|
||||
virtual void setAcquisitionRange(double newValue) { updateValue(acquisitionRange, newValue, DataIndex::acquisitionRange); }
|
||||
virtual void setRadarState(bool newValue) { updateValue(radarState, newValue, DataIndex::radarState); }
|
||||
|
||||
/********** Getters **********/
|
||||
virtual string getCategory() { return category; };
|
||||
virtual bool getAlive() { return alive; }
|
||||
virtual unsigned char getAlarmState() { return alarmState; }
|
||||
virtual bool getHuman() { return human; }
|
||||
virtual bool getControlled() { return controlled; }
|
||||
virtual unsigned char getCoalition() { return coalition; }
|
||||
@ -187,6 +190,7 @@ public:
|
||||
virtual double getTargetingRange() { return targetingRange; }
|
||||
virtual double getAimMethodRange() { return aimMethodRange; }
|
||||
virtual double getAcquisitionRange() { return acquisitionRange; }
|
||||
virtual bool getRadarState() { return radarState; }
|
||||
|
||||
protected:
|
||||
unsigned int ID;
|
||||
@ -202,6 +206,8 @@ protected:
|
||||
string callsign = "";
|
||||
string groupName = "";
|
||||
unsigned char state = State::NONE;
|
||||
unsigned char alarmState = AlarmState::AUTO;
|
||||
bool radarState = false;
|
||||
string task = "";
|
||||
bool hasTask = false;
|
||||
Coords position = Coords(NULL);
|
||||
|
||||
@ -609,7 +609,7 @@ string GroundUnit::aimAtPoint(Coords aimTarget) {
|
||||
Geodesic::WGS84().Direct(position.lat, position.lng, bearing1, r, lat, lng);
|
||||
|
||||
taskString = +"Barrel elevation: " + to_string((int) round(barrelElevation)) + "m, bearing: " + to_string((int) round(bearing1)) + "deg";
|
||||
log(unitName + "(" + name + ")" + " shooting with aim at point method. Barrel elevation: " + to_string(barrelElevation) + "m, bearing: " + to_string(bearing1) + "°");
|
||||
log(unitName + "(" + name + ")" + " shooting with aim at point method. Barrel elevation: " + to_string(barrelElevation) + "m, bearing: " + to_string(bearing1) + "<EFBFBD>");
|
||||
|
||||
std::ostringstream taskSS;
|
||||
taskSS.precision(10);
|
||||
|
||||
@ -406,6 +406,19 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") ROE to " + to_string(ROE), true);
|
||||
}
|
||||
}
|
||||
else if (key.compare("setAlarmState") == 0)
|
||||
{
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr) {
|
||||
unsigned char alarmState = value[L"alarmState"].as_number().to_uint32();
|
||||
unit->setAlarmState(alarmState);
|
||||
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") alarm state to " + to_string(alarmState), true);
|
||||
} else {
|
||||
log("Error while setting setAlarmState. Unit does not exist.");
|
||||
}
|
||||
}
|
||||
/************************/
|
||||
else if (key.compare("setReactionToThreat") == 0)
|
||||
{
|
||||
|
||||
@ -152,7 +152,6 @@ void Server::handle_get(http_request request)
|
||||
}
|
||||
/* Drawings data*/
|
||||
else if (URI.compare(DRAWINGS_URI) == 0 && drawingsByLayer.has_object_field(L"drawings")) {
|
||||
log("Trying to answer with drawings...");
|
||||
answer[L"drawings"] = drawingsByLayer[L"drawings"];
|
||||
}
|
||||
|
||||
|
||||
@ -83,6 +83,9 @@ void Unit::update(json::value json, double dt)
|
||||
if (json.has_boolean_field(L"isAlive"))
|
||||
setAlive(json[L"isAlive"].as_bool());
|
||||
|
||||
if (json.has_boolean_field(L"radarState"))
|
||||
setRadarState(json[L"radarState"].as_bool());
|
||||
|
||||
if (json.has_boolean_field(L"isHuman"))
|
||||
setHuman(json[L"isHuman"].as_bool());
|
||||
|
||||
@ -150,7 +153,7 @@ void Unit::update(json::value json, double dt)
|
||||
|
||||
void Unit::setDefaults(bool force)
|
||||
{
|
||||
|
||||
setAlarmState(AlarmState::AUTO, force);
|
||||
}
|
||||
|
||||
void Unit::runAILoop() {
|
||||
@ -208,6 +211,7 @@ void Unit::refreshLeaderData(unsigned long long time) {
|
||||
case DataIndex::operateAs: updateValue(operateAs, leader->operateAs, datumIndex); break;
|
||||
case DataIndex::shotsScatter: updateValue(shotsScatter, leader->shotsScatter, datumIndex); break;
|
||||
case DataIndex::shotsIntensity: updateValue(shotsIntensity, leader->shotsIntensity, datumIndex); break;
|
||||
case DataIndex::alarmState: updateValue(alarmState, leader->alarmState, datumIndex); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -251,6 +255,8 @@ void Unit::getData(stringstream& ss, unsigned long long time)
|
||||
switch (datumIndex) {
|
||||
case DataIndex::category: appendString(ss, datumIndex, category); break;
|
||||
case DataIndex::alive: appendNumeric(ss, datumIndex, alive); break;
|
||||
case DataIndex::alarmState: appendNumeric(ss, datumIndex, alarmState); break;
|
||||
case DataIndex::radarState: appendNumeric(ss, datumIndex, radarState); break;
|
||||
case DataIndex::human: appendNumeric(ss, datumIndex, human); break;
|
||||
case DataIndex::controlled: appendNumeric(ss, datumIndex, controlled); break;
|
||||
case DataIndex::coalition: appendNumeric(ss, datumIndex, coalition); break;
|
||||
@ -467,6 +473,17 @@ void Unit::setROE(unsigned char newROE, bool force)
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::setAlarmState(unsigned char newAlarmState, bool force)
|
||||
{
|
||||
if (alarmState != newAlarmState || force) {
|
||||
alarmState = newAlarmState;
|
||||
Command* command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ALARM_STATE, static_cast<unsigned int>(newAlarmState)));
|
||||
scheduler->appendCommand(command);
|
||||
|
||||
triggerUpdate(DataIndex::alarmState);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::setReactionToThreat(unsigned char newReactionToThreat, bool force)
|
||||
{
|
||||
if (reactionToThreat != newReactionToThreat || force) {
|
||||
|
||||
45
frontend/react/public/images/buttons/alarmState/auto.svg
Normal file
45
frontend/react/public/images/buttons/alarmState/auto.svg
Normal file
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="designated.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
width="30px"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="5.9428571"
|
||||
inkscape:cx="-29.026442"
|
||||
inkscape:cy="16.237981"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
d="m 7.5000002,0 c 0.498,0 0.9375,0.4395 0.9375,0.9375 V 1.2598 C 11.1621,1.6699 13.3301,3.8379002 13.7402,6.5625002 h 0.3223 c 0.498,0 0.9375,0.4395 0.9375,0.9375 0,0.5273 -0.4395,0.9375 -0.9375,0.9375 H 13.7402 C 13.3301,11.1914 11.1621,13.3594 8.4375002,13.7695 v 0.293 c 0,0.5273 -0.4395,0.9375 -0.9375,0.9375 -0.5273,0 -0.9375,-0.4102 -0.9375,-0.9375 v -0.293 C 3.8086002,13.3594 1.6406,11.1914 1.2305,8.4375002 h -0.293 c -0.5273,0 -0.9375,-0.4102 -0.9375,-0.9375 0,-0.498 0.4102,-0.9375 0.9375,-0.9375 h 0.293 C 1.6406,3.8379002 3.8086002,1.6699 6.5625002,1.2598 V 0.9375 c 0,-0.498 0.4102,-0.9375 0.9375,-0.9375 z m -4.3652,8.4375002 c 0.3515,1.7284998 1.6992,3.0761998 3.4277,3.4276998 V 11.25 c 0,-0.498 0.4102,-0.9375 0.9375,-0.9375 0.498,0 0.9375,0.4395 0.9375,0.9375 v 0.6152 C 10.1367,11.5137 11.4844,10.166 11.8359,8.4375002 H 11.25 c -0.5273,0 -0.9375,-0.4102 -0.9375,-0.9375 0,-0.498 0.4102,-0.9375 0.9375,-0.9375 h 0.5859 c -0.3515,-1.6992 -1.6992,-3.0469 -3.3983998,-3.3984 v 0.5859 c 0,0.5273 -0.4395,0.9375 -0.9375,0.9375 -0.5273,0 -0.9375,-0.4102 -0.9375,-0.9375 v -0.5859 c -1.7285,0.3515 -3.0762,1.6992 -3.4277,3.3984 h 0.6152 c 0.498,0 0.9375,0.4395 0.9375,0.9375 0,0.5273 -0.4395,0.9375 -0.9375,0.9375 z m 4.3652,0 c -0.5273,0 -0.9375,-0.4102 -0.9375,-0.9375 0,-0.498 0.4102,-0.9375 0.9375,-0.9375 0.498,0 0.9375,0.4395 0.9375,0.9375 0,0.5273 -0.4395,0.9375 -0.9375,0.9375 z"
|
||||
fill="#5ca7ff"
|
||||
id="path2"
|
||||
style="stroke: none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
44
frontend/react/public/images/buttons/alarmState/green.svg
Normal file
44
frontend/react/public/images/buttons/alarmState/green.svg
Normal file
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="9.3896503"
|
||||
height="9.3896503"
|
||||
viewBox="0 0 9.3896503 9.3896503"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="hold.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="23.771429"
|
||||
inkscape:cx="1.1989183"
|
||||
inkscape:cy="4.311899"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
d="m 9.103975,1.603975 c 0.3809,-0.3515 0.3809,-0.9668 0,-1.3183 -0.3515,-0.3809 -0.9668,-0.3809 -1.3183,0 l -3.0762,3.0761 -3.1055,-3.0761 c -0.3515,-0.3809 -0.9668,-0.3809 -1.3183,0 -0.3809,0.3515 -0.3809,0.9668 0,1.3183 l 3.0761,3.0762 -3.0761,3.1055 c -0.3809,0.3515 -0.3809,0.9668 0,1.3183 0.3515,0.3809 0.9668,0.3809 1.3183,0 l 3.1055,-3.0761 3.0762,3.0761 c 0.3515,0.3809 0.9668,0.3809 1.3183,0 0.3809,-0.3515 0.3809,-0.9668 0,-1.3183 l -3.0761,-3.1055 z"
|
||||
fill="#5ca7ff"
|
||||
id="path2"
|
||||
style="stroke: none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
44
frontend/react/public/images/buttons/alarmState/red.svg
Normal file
44
frontend/react/public/images/buttons/alarmState/red.svg
Normal file
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="13.126647"
|
||||
height="15.015483"
|
||||
viewBox="0 0 13.126647 15.015483"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="free.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="43.789474"
|
||||
inkscape:cx="4.5330529"
|
||||
inkscape:cy="6.7710337"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
d="m 10.796771,3.75 c 0,1.31836 -0.7618,2.46094 -1.8750496,3.13476 v 0.61525 c 0,0.5273 -0.43945,0.9375 -0.9375,0.9375 h -2.8125 c -0.52734,0 -0.9375,-0.4102 -0.9375,-0.9375 V 6.88476 c -1.14258,-0.67382 -1.875,-1.8164 -1.875,-3.13476 0,-2.05078 1.875,-3.75 4.21875,-3.75 2.31445,0 4.2187996,1.69922 4.2187996,3.75 z M 4.9373514,5.15625 c 0.49804,0 0.9375,-0.41016 0.9375,-0.9375 0,-0.49805 -0.43946,-0.9375 -0.9375,-0.9375 -0.52735,0 -0.9375,0.43945 -0.9375,0.9375 0,0.52734 0.41015,0.9375 0.9375,0.9375 z m 4.21872,-0.9375 c 0,-0.49805 -0.43943,-0.9375 -0.93748,-0.9375 -0.52734,0 -0.9375,0.43945 -0.9375,0.9375 0,0.52734 0.41016,0.9375 0.9375,0.9375 0.49805,0 0.93748,-0.41016 0.93748,-0.9375 z M 0.10336144,8.02731 c 0.23438,-0.4687 0.79102,-0.6445 1.25976996,-0.4101 l 5.21484,2.6074 5.1854996,-2.6074 c 0.4688,-0.2344 1.0254,-0.0586 1.2598,0.4101 0.2344,0.4688 0.0586,1.0254 -0.4101,1.2598 l -3.9551196,1.9629 3.9551196,1.9922 c 0.4687,0.2344 0.6445,0.791 0.4101,1.2597 -0.2344,0.4688 -0.791,0.6446 -1.2598,0.4102 l -5.1854996,-2.6074 -5.21484,2.6074 c -0.46874996,0.2344 -1.02538996,0.0586 -1.25976996,-0.4102 -0.234374,-0.4687 -0.058593,-1.0253 0.41016,-1.2597 L 4.4685914,11.25001 0.51352144,9.28711 c -0.468753,-0.2344 -0.644534,-0.791 -0.41016,-1.2598 z"
|
||||
fill="#5ca7ff"
|
||||
id="path2"
|
||||
style="stroke: none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
35
frontend/react/public/images/states/alarm-state-green.svg
Normal file
35
frontend/react/public/images/states/alarm-state-green.svg
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="alarmStateGreen.svg"
|
||||
width="16"
|
||||
height="16"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1" /><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="25.919883"
|
||||
inkscape:cx="13.831081"
|
||||
inkscape:cy="11.400514"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1369"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" /><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path
|
||||
d="M 3.7422746,4.0810782 C 3.5794387,3.952686 3.3430088,3.9824344 3.2146172,4.1452726 3.0862247,4.3081112 3.1159759,4.5445408 3.2788135,4.6729303 l 9.2692385,7.2650807 c 0.162838,0.128392 0.399268,0.09864 0.527657,-0.0642 0.128392,-0.162837 0.09864,-0.399267 -0.0642,-0.527658 l -1.412307,-1.106987 c 0.0031,-0.0062 0.0062,-0.01409 0.0094,-0.02036 0.08142,-0.180061 0.04853,-0.3914375 -0.08298,-0.5386172 L 11.409754,9.5502434 C 10.921242,8.999099 10.650365,8.2898138 10.650365,7.552346 V 7.257985 c 0,-1.2118908 -0.8611614,-2.2233655 -2.0041593,-2.4550974 v -0.300624 c 0,-0.2771365 -0.2239031,-0.5010395 -0.5010397,-0.5010395 -0.2771384,0 -0.5010389,0.223903 -0.5010389,0.5010395 V 4.8028878 C 6.9771163,4.9375431 6.4071837,5.3383753 6.0470597,5.8879535 Z M 9.4948418,10.514745 5.6399655,7.4787555 v 0.075159 c 0,0.7359021 -0.2708759,1.4467532 -0.7593887,1.9978975 L 4.764711,9.6817695 c -0.1315225,0.1471797 -0.1628385,0.3585555 -0.082989,0.5386165 0.079855,0.180061 0.259915,0.294361 0.4571968,0.294361 z m -0.6403933,1.210324 c 0.1878922,-0.187889 0.292796,-0.443107 0.292796,-0.709284 H 8.145166 7.1430839 c 0,0.266177 0.1049072,0.521395 0.2927959,0.709284 0.1878913,0.18789 0.4431078,0.292796 0.7092862,0.292796 0.2661774,0 0.5213939,-0.104907 0.7092825,-0.292796 z"
|
||||
id="path1-6"
|
||||
style="fill:#8bff63;fill-opacity:1;stroke:#272727;stroke-width:3.50002;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" /></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
39
frontend/react/public/images/states/alarm-state-red.svg
Normal file
39
frontend/react/public/images/states/alarm-state-red.svg
Normal file
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="alarmStateRed.svg"
|
||||
width="16"
|
||||
height="16"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="36.65625"
|
||||
inkscape:cx="17.009378"
|
||||
inkscape:cy="10.980392"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1369"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
|
||||
<path
|
||||
d="m 8.3545299,3.3811696 c -0.300221,0 -0.542773,0.247612 -0.542773,0.554097 v 0.332456 c -1.238204,0.25627 -2.171098,1.374849 -2.171098,2.715068 v 0.32553 c 0,0.813829 -0.293438,1.599952 -0.822642,2.209456 l -0.125517,0.143718 c -0.142479,0.162766 -0.176403,0.3965254 -0.0899,0.5956544 0.0865,0.199127 0.281565,0.327261 0.495283,0.327261 h 6.5132921 c 0.213718,0 0.407082,-0.128134 0.495281,-0.327261 0.0882,-0.199129 0.05258,-0.4328884 -0.0899,-0.5956544 L 11.89104,9.5177766 c -0.529204,-0.609504 -0.822641,-1.393896 -0.822641,-2.209456 v -0.32553 c 0,-1.340219 -0.932895,-2.458798 -2.1710981,-2.715068 v -0.332456 c 0,-0.306485 -0.242553,-0.554097 -0.542776,-0.554097 z m 0.768367,8.5417264 c 0.203539,-0.207784 0.317182,-0.490027 0.317182,-0.78439 h -1.085549 -1.085547 c 0,0.294363 0.113643,0.576606 0.317183,0.78439 0.203541,0.207786 0.480016,0.323801 0.768364,0.323801 0.288349,0 0.564825,-0.116015 0.768367,-0.323801 z"
|
||||
id="path1"
|
||||
style="fill:#ff5858;fill-opacity:1;stroke:#272727;stroke-width:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@ -451,6 +451,8 @@ export enum DataIndexes {
|
||||
startOfData = 0,
|
||||
category,
|
||||
alive,
|
||||
alarmState,
|
||||
radarState,
|
||||
human,
|
||||
controlled,
|
||||
coalition,
|
||||
|
||||
@ -173,6 +173,7 @@ export interface ObjectIconOptions {
|
||||
showCallsign: boolean;
|
||||
rotateToHeading: boolean;
|
||||
showCluster: boolean;
|
||||
showAlarmState: boolean;
|
||||
}
|
||||
|
||||
export interface GeneralSettings {
|
||||
@ -220,6 +221,7 @@ export interface UnitData {
|
||||
markerCategory: string;
|
||||
ID: number;
|
||||
alive: boolean;
|
||||
alarmState: AlarmState;
|
||||
human: boolean;
|
||||
controlled: boolean;
|
||||
coalition: string;
|
||||
@ -414,3 +416,9 @@ export interface Drawing {
|
||||
file?: string;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
export enum AlarmState {
|
||||
RED = 'red',
|
||||
GREEN = 'green',
|
||||
AUTO = 'auto'
|
||||
}
|
||||
|
||||
@ -659,6 +659,33 @@
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Unit Radar State */
|
||||
.unit-alarm-state {
|
||||
background-repeat: no-repeat;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
left: 35px;
|
||||
bottom: 0px;
|
||||
}
|
||||
.unit[data-alarm-state="green"] .unit-alarm-state {
|
||||
background-image: url("/images/states/alarm-state-green.svg");
|
||||
}
|
||||
|
||||
.unit[data-alarm-state="red"] .unit-alarm-state {
|
||||
background-image: url("/images/states/alarm-state-red.svg");
|
||||
}
|
||||
|
||||
.unit[data-alarm-state="auto"] .unit-alarm-state {
|
||||
border: 0px solid transpare;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[todo-data-awacs-mode] [data-object|="unit"] .unit-selected-spotlight,
|
||||
[todo-data-awacs-mode] [data-object|="unit"] .unit-short-label,
|
||||
[todo-data-awacs-mode] [data-object|="unit"] .unit-state,
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { Circle, LatLng, Polygon } from "leaflet";
|
||||
import * as turf from "@turf/turf";
|
||||
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
|
||||
import { DateAndTime } from "../interfaces";
|
||||
import { AlarmState, DateAndTime } from "../interfaces";
|
||||
import { Converter } from "usng";
|
||||
import { MGRS } from "../types/types";
|
||||
import { featureCollection } from "turf";
|
||||
import MagVar from "magvar";
|
||||
import axios from 'axios';
|
||||
import axios from "axios";
|
||||
|
||||
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number, magnetic = true) {
|
||||
const φ1 = deg2rad(lat1); // φ, λ in radians
|
||||
@ -58,19 +58,19 @@ export function midpoint(lat1: number, lon1: number, lat2: number, lon2: number,
|
||||
const λ2 = deg2rad(lon2); // Convert longitude of point 2 from degrees to radians
|
||||
|
||||
// Convert point 1 to Mercator projection coordinates
|
||||
const x1 = 1 / (2 * Math.PI) * Math.pow(2, zoom) * (Math.PI + λ1);
|
||||
const y1 = 1 / (2 * Math.PI) * Math.pow(2, zoom) * (Math.PI - Math.log(Math.tan(Math.PI / 4 + φ1 / 2)));
|
||||
const x1 = (1 / (2 * Math.PI)) * Math.pow(2, zoom) * (Math.PI + λ1);
|
||||
const y1 = (1 / (2 * Math.PI)) * Math.pow(2, zoom) * (Math.PI - Math.log(Math.tan(Math.PI / 4 + φ1 / 2)));
|
||||
|
||||
// Convert point 2 to Mercator projection coordinates
|
||||
const x2 = 1 / (2 * Math.PI) * Math.pow(2, zoom) * (Math.PI + λ2);
|
||||
const y2 = 1 / (2 * Math.PI) * Math.pow(2, zoom) * (Math.PI - Math.log(Math.tan(Math.PI / 4 + φ2 / 2)));
|
||||
const x2 = (1 / (2 * Math.PI)) * Math.pow(2, zoom) * (Math.PI + λ2);
|
||||
const y2 = (1 / (2 * Math.PI)) * Math.pow(2, zoom) * (Math.PI - Math.log(Math.tan(Math.PI / 4 + φ2 / 2)));
|
||||
|
||||
// Calculate the midpoint in Mercator projection coordinates
|
||||
const mx = (x1 + x2) / 2;
|
||||
const my = (y1 + y2) / 2;
|
||||
|
||||
// Convert the midpoint back to latitude and longitude
|
||||
const λ = (2 * Math.PI * mx / Math.pow(2, zoom)) - Math.PI;
|
||||
const λ = (2 * Math.PI * mx) / Math.pow(2, zoom) - Math.PI;
|
||||
const φ = 2 * Math.atan(Math.exp(Math.PI - (2 * Math.PI * my) / Math.pow(2, zoom))) - Math.PI / 2;
|
||||
|
||||
// Return the midpoint as a LatLng object
|
||||
@ -286,6 +286,19 @@ export function enumToROE(ROE: number) {
|
||||
else return ROEs[0];
|
||||
}
|
||||
|
||||
export function enumToAlarmState(alarmState: number) {
|
||||
switch (alarmState) {
|
||||
case 2:
|
||||
return AlarmState.RED;
|
||||
case 1:
|
||||
return AlarmState.GREEN;
|
||||
case 0:
|
||||
return AlarmState.AUTO;
|
||||
default:
|
||||
return AlarmState.AUTO;
|
||||
}
|
||||
}
|
||||
|
||||
export function convertROE(idx: number) {
|
||||
let roe = 0;
|
||||
if (idx === 0) roe = 4;
|
||||
@ -522,7 +535,7 @@ export function computeStandardFormationOffset(formation, idx) {
|
||||
var xl = xr * Math.cos(Math.PI / 4) - yr * Math.sin(Math.PI / 4);
|
||||
var yl = xr * Math.sin(Math.PI / 4) + yr * Math.cos(Math.PI / 4);
|
||||
offset = { x: xl * 50, y: yl * 50, z: 0 };
|
||||
offset.z = -Math.sqrt(offset.x * offset.x + offset.y * offset.y) * 0.1
|
||||
offset.z = -Math.sqrt(offset.x * offset.x + offset.y * offset.y) * 0.1;
|
||||
if (yr == 0) {
|
||||
layer++;
|
||||
xr = 0;
|
||||
@ -558,7 +571,7 @@ export function roundToNearestFive(number) {
|
||||
return Math.round(number / 5) * 5;
|
||||
}
|
||||
|
||||
export function toDCSFormationOffset(offset: {x: number, y: number, z: number}) {
|
||||
export function toDCSFormationOffset(offset: { x: number; y: number; z: number }) {
|
||||
// X: front-rear, positive front
|
||||
// Y: top-bottom, positive top
|
||||
// Z: left-right, positive right
|
||||
@ -566,7 +579,7 @@ export function toDCSFormationOffset(offset: {x: number, y: number, z: number})
|
||||
return { x: -offset.y, y: offset.z, z: offset.x };
|
||||
}
|
||||
|
||||
export function fromDCSFormationOffset(offset: {x: number, y: number, z: number}) {
|
||||
export function fromDCSFormationOffset(offset: { x: number; y: number; z: number }) {
|
||||
return { x: offset.z, y: -offset.x, z: offset.y };
|
||||
}
|
||||
|
||||
@ -579,7 +592,7 @@ export function fromDCSFormationOffset(offset: {x: number, y: number, z: number}
|
||||
export function adjustBrightness(color, percent) {
|
||||
// Ensure the color is in the correct format
|
||||
if (!/^#[0-9A-F]{6}$/i.test(color)) {
|
||||
throw new Error('Invalid color format. Use #RRGGBB.');
|
||||
throw new Error("Invalid color format. Use #RRGGBB.");
|
||||
}
|
||||
|
||||
// Parse the color components
|
||||
@ -605,12 +618,12 @@ export function adjustBrightness(color, percent) {
|
||||
export function setOpacity(color, opacity) {
|
||||
// Ensure the color is in the correct format
|
||||
if (!/^#[0-9A-F]{6}$/i.test(color)) {
|
||||
throw new Error('Invalid color format. Use #RRGGBB.');
|
||||
throw new Error("Invalid color format. Use #RRGGBB.");
|
||||
}
|
||||
|
||||
// Ensure the opacity is within the valid range
|
||||
if (opacity < 0 || opacity > 1) {
|
||||
throw new Error('Opacity must be between 0 and 1.');
|
||||
throw new Error("Opacity must be between 0 and 1.");
|
||||
}
|
||||
|
||||
// Parse the color components
|
||||
@ -630,7 +643,7 @@ export function setOpacity(color, opacity) {
|
||||
export function computeBrightness(color) {
|
||||
// Ensure the color is in the correct format
|
||||
if (!/^#[0-9A-F]{6}$/i.test(color)) {
|
||||
throw new Error('Invalid color format. Use #RRGGBB.');
|
||||
throw new Error("Invalid color format. Use #RRGGBB.");
|
||||
}
|
||||
|
||||
// Parse the color components
|
||||
@ -660,10 +673,10 @@ export function normalizeAngle(angle: number): number {
|
||||
}
|
||||
|
||||
export function decimalToRGBA(decimal: number): string {
|
||||
const r = (decimal >>> 24) & 0xff;
|
||||
const g = (decimal >>> 16) & 0xff;
|
||||
const b = (decimal >>> 8) & 0xff;
|
||||
const a = (decimal & 0xff) / 255;
|
||||
const r = (decimal >>> 24) & 0xff;
|
||||
const g = (decimal >>> 16) & 0xff;
|
||||
const b = (decimal >>> 8) & 0xff;
|
||||
const a = (decimal & 0xff) / 255;
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${a.toFixed(2)})`;
|
||||
}
|
||||
@ -671,18 +684,18 @@ export function decimalToRGBA(decimal: number): string {
|
||||
export async function getWikipediaImage(unitName: string): Promise<string | null> {
|
||||
try {
|
||||
// Search for the unit name on Wikipedia
|
||||
const searchResponse = await axios.get('https://en.wikipedia.org/w/api.php', {
|
||||
const searchResponse = await axios.get("https://en.wikipedia.org/w/api.php", {
|
||||
params: {
|
||||
action: 'query',
|
||||
list: 'search',
|
||||
action: "query",
|
||||
list: "search",
|
||||
srsearch: unitName,
|
||||
format: 'json',
|
||||
origin: '*'
|
||||
}
|
||||
format: "json",
|
||||
origin: "*",
|
||||
},
|
||||
});
|
||||
|
||||
if (searchResponse.data.query.search.length === 0) {
|
||||
console.error('No search results found for the unit name.');
|
||||
console.error("No search results found for the unit name.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -690,15 +703,15 @@ export async function getWikipediaImage(unitName: string): Promise<string | null
|
||||
const pageTitle = searchResponse.data.query.search[0].title;
|
||||
|
||||
// Get the page content to find the image
|
||||
const pageResponse = await axios.get('https://en.wikipedia.org/w/api.php', {
|
||||
const pageResponse = await axios.get("https://en.wikipedia.org/w/api.php", {
|
||||
params: {
|
||||
action: 'query',
|
||||
action: "query",
|
||||
titles: pageTitle,
|
||||
prop: 'pageimages',
|
||||
prop: "pageimages",
|
||||
pithumbsize: 500,
|
||||
format: 'json',
|
||||
origin: '*'
|
||||
}
|
||||
format: "json",
|
||||
origin: "*",
|
||||
},
|
||||
});
|
||||
|
||||
const pages = pageResponse.data.query.pages;
|
||||
@ -708,11 +721,11 @@ export async function getWikipediaImage(unitName: string): Promise<string | null
|
||||
if (page.thumbnail && page.thumbnail.source) {
|
||||
return page.thumbnail.source;
|
||||
} else {
|
||||
console.error('No image found for the unit name.');
|
||||
console.error("No image found for the unit name.");
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data from Wikipedia:', error);
|
||||
console.error("Error fetching data from Wikipedia:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -720,18 +733,18 @@ export async function getWikipediaImage(unitName: string): Promise<string | null
|
||||
export async function getWikipediaSummary(unitName: string): Promise<string | null> {
|
||||
try {
|
||||
// Search for the unit name on Wikipedia
|
||||
const searchResponse = await axios.get('https://en.wikipedia.org/w/api.php', {
|
||||
const searchResponse = await axios.get("https://en.wikipedia.org/w/api.php", {
|
||||
params: {
|
||||
action: 'query',
|
||||
list: 'search',
|
||||
action: "query",
|
||||
list: "search",
|
||||
srsearch: unitName,
|
||||
format: 'json',
|
||||
origin: '*'
|
||||
}
|
||||
format: "json",
|
||||
origin: "*",
|
||||
},
|
||||
});
|
||||
|
||||
if (searchResponse.data.query.search.length === 0) {
|
||||
console.error('No search results found for the unit name.');
|
||||
console.error("No search results found for the unit name.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -739,16 +752,16 @@ export async function getWikipediaSummary(unitName: string): Promise<string | nu
|
||||
const pageTitle = searchResponse.data.query.search[0].title;
|
||||
|
||||
// Get the page content to find the summary
|
||||
const pageResponse = await axios.get('https://en.wikipedia.org/w/api.php', {
|
||||
const pageResponse = await axios.get("https://en.wikipedia.org/w/api.php", {
|
||||
params: {
|
||||
action: 'query',
|
||||
prop: 'extracts',
|
||||
action: "query",
|
||||
prop: "extracts",
|
||||
exintro: true,
|
||||
explaintext: true,
|
||||
titles: pageTitle,
|
||||
format: 'json',
|
||||
origin: '*'
|
||||
}
|
||||
format: "json",
|
||||
origin: "*",
|
||||
},
|
||||
});
|
||||
|
||||
const pages = pageResponse.data.query.pages;
|
||||
@ -758,11 +771,11 @@ export async function getWikipediaSummary(unitName: string): Promise<string | nu
|
||||
if (page.extract) {
|
||||
return page.extract;
|
||||
} else {
|
||||
console.error('No summary found for the unit name.');
|
||||
console.error("No summary found for the unit name.");
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data from Wikipedia:', error);
|
||||
console.error("Error fetching data from Wikipedia:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -777,4 +790,4 @@ export function secondsToTimeString(seconds: number) {
|
||||
|
||||
export function isTrustedEnvironment() {
|
||||
return window.location.protocol === "https:";
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
} from "../constants/constants";
|
||||
import {
|
||||
AirbasesData,
|
||||
AlarmState,
|
||||
BullseyesData,
|
||||
CommandModeOptions,
|
||||
GeneralSettings,
|
||||
@ -419,6 +420,12 @@ export class ServerManager {
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
|
||||
setAlarmState(ID: number, alarmState: number, callback: CallableFunction = () => {}) {
|
||||
var command = { ID: ID, alarmState: alarmState };
|
||||
var data = { setAlarmState: command };
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
|
||||
setReactionToThreat(ID: number, reactionToThreat: string, callback: CallableFunction = () => {}) {
|
||||
var command = {
|
||||
ID: ID,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -19,6 +19,9 @@ import {
|
||||
import { OlToggle } from "../components/oltoggle";
|
||||
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
|
||||
import {
|
||||
olButtonsAlarmstateAuto,
|
||||
olButtonsAlarmstateGreen,
|
||||
olButtonsAlarmstateRed,
|
||||
olButtonsEmissionsAttack,
|
||||
olButtonsEmissionsDefend,
|
||||
olButtonsEmissionsFree,
|
||||
@ -54,7 +57,7 @@ import { OlSearchBar } from "../components/olsearchbar";
|
||||
import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
|
||||
import { FaRadio, FaVolumeHigh } from "react-icons/fa6";
|
||||
import { OlNumberInput } from "../components/olnumberinput";
|
||||
import { GeneralSettings, Radio, TACAN } from "../../interfaces";
|
||||
import { AlarmState, GeneralSettings, Radio, TACAN } from "../../interfaces";
|
||||
import { OlStringInput } from "../components/olstringinput";
|
||||
import { OlFrequencyInput } from "../components/olfrequencyinput";
|
||||
import { UnitSink } from "../../audio/unitsink";
|
||||
@ -87,6 +90,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
radio: undefined as undefined | Radio,
|
||||
TACAN: undefined as undefined | TACAN,
|
||||
generalSettings: undefined as undefined | GeneralSettings,
|
||||
alarmState: undefined as undefined | AlarmState
|
||||
};
|
||||
}
|
||||
|
||||
@ -130,7 +134,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const [activeRadioSettings, setActiveRadioSettings] = useState(null as null | { radio: Radio; TACAN: TACAN });
|
||||
const [activeAdvancedSettings, setActiveAdvancedSettings] = useState(null as null | GeneralSettings);
|
||||
const [lastUpdateTime, setLastUpdateTime] = useState(0);
|
||||
const [showScenicModes, setShowScenicModes] = useState(true);
|
||||
const [showScenicModes, setShowScenicModes] = useState(false);
|
||||
const [showEngagementSettings, setShowEngagementSettings] = useState(false);
|
||||
const [barrelHeight, setBarrelHeight] = useState(0);
|
||||
const [muzzleVelocity, setMuzzleVelocity] = useState(0);
|
||||
@ -178,6 +182,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
onOff: (unit: Unit) => unit.getOnOff(),
|
||||
radio: (unit: Unit) => unit.getRadio(),
|
||||
TACAN: (unit: Unit) => unit.getTACAN(),
|
||||
alarmState: (unit: Unit) => unit.getAlarmState(),
|
||||
generalSettings: (unit: Unit) => unit.getGeneralSettings(),
|
||||
isAudioSink: (unit: Unit) => {
|
||||
return (
|
||||
@ -885,6 +890,82 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</div>
|
||||
)}
|
||||
{/* ============== Rules of Engagement END ============== */}
|
||||
|
||||
{/* ============== Alarm state selector START ============== */}
|
||||
{
|
||||
<div className="flex flex-col gap-2">
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Alarm State
|
||||
</span>
|
||||
<OlButtonGroup
|
||||
tooltip={() => (
|
||||
<OlExpandingTooltip
|
||||
title="Alarm State"
|
||||
content={
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>Sets the alarm state of the unit, in order:</div>
|
||||
<div className="flex flex-col gap-2 px-2">
|
||||
<div className="flex content-center gap-2">
|
||||
{" "}
|
||||
<FontAwesomeIcon icon={olButtonsAlarmstateGreen} className={`
|
||||
my-auto min-w-8 text-white
|
||||
`} /> Green: The unit will not engage with its sensors in any circumstances. The unit will be able to move.
|
||||
</div>
|
||||
<div className="flex content-center gap-2">
|
||||
{" "}
|
||||
<FontAwesomeIcon icon={olButtonsAlarmstateAuto} className={`
|
||||
my-auto min-w-8 text-white
|
||||
`} />{" "}
|
||||
<div>
|
||||
{" "}
|
||||
Auto: The unit will use its sensors to engage based on its ROE.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex content-center gap-2">
|
||||
{" "}
|
||||
<FontAwesomeIcon icon={olButtonsAlarmstateRed} className={`
|
||||
my-auto min-w-8 text-white
|
||||
`} /> Red: The unit will be actively searching for target with its sensors. For some units, this will deploy
|
||||
the radar and make the unit not able to move.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
tooltipRelativeToParent={true}
|
||||
>
|
||||
{[olButtonsAlarmstateGreen, olButtonsAlarmstateAuto, olButtonsAlarmstateRed].map((icon, idx) => {
|
||||
return (
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.setAlarmState([1, 0, 2][idx], null, () =>
|
||||
setForcedUnitsData({
|
||||
...forcedUnitsData,
|
||||
alarmState: [AlarmState.GREEN, AlarmState.AUTO, AlarmState.RED][idx],
|
||||
})
|
||||
);
|
||||
}}
|
||||
active={selectedUnitsData.alarmState === [AlarmState.GREEN, AlarmState.AUTO, AlarmState.RED][idx]}
|
||||
icon={icon}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</OlButtonGroup>
|
||||
</div>
|
||||
}
|
||||
{/* ============== Alarm state selector END ============== */}
|
||||
|
||||
|
||||
{selectedCategories.every((category) => {
|
||||
return ["Aircraft", "Helicopter"].includes(category);
|
||||
}) && (
|
||||
@ -1340,7 +1421,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{/* ============== Miss on purpose toggle END ============== */}
|
||||
<div className="flex gap-4">
|
||||
{/* ============== Shots scatter START ============== */}
|
||||
<div className={`flex flex-col gap-2`}>
|
||||
<div className={`flex w-full justify-between gap-2`}>
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
@ -1373,7 +1454,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</div>
|
||||
{/* ============== Shots scatter END ============== */}
|
||||
{/* ============== Shots intensity START ============== */}
|
||||
<div className="flex flex-col gap-2">
|
||||
{/*<div className="flex flex-col gap-2">
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
@ -1405,12 +1486,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</OlButtonGroup>
|
||||
</div>
|
||||
{/* ============== Shots intensity END ============== */}
|
||||
<OlStateButton
|
||||
{/*<OlStateButton
|
||||
className="mt-auto"
|
||||
checked={showEngagementSettings}
|
||||
onClick={() => setShowEngagementSettings(!showEngagementSettings)}
|
||||
icon={faCog}
|
||||
></OlStateButton>
|
||||
*/}
|
||||
</div>
|
||||
{/* ============== Operate as toggle START ============== */}
|
||||
{selectedUnits.every((unit) => unit.getCoalition() === "neutral") && (
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
computeBearingRangeString,
|
||||
adjustBrightness,
|
||||
bearingAndDistanceToLatLng,
|
||||
enumToAlarmState,
|
||||
} from "../other/utils";
|
||||
import { CustomMarker } from "../map/markers/custommarker";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
@ -52,7 +53,7 @@ import {
|
||||
} from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { Weapon } from "../weapon/weapon";
|
||||
import { Ammo, Contact, GeneralSettings, LoadoutBlueprint, ObjectIconOptions, Offset, Radio, TACAN, UnitBlueprint, UnitData } from "../interfaces";
|
||||
import { AlarmState, Ammo, Contact, GeneralSettings, LoadoutBlueprint, ObjectIconOptions, Offset, Radio, TACAN, UnitBlueprint, UnitData } from "../interfaces";
|
||||
import { RangeCircle } from "../map/rangecircle";
|
||||
import { Group } from "./group";
|
||||
import { ContextActionSet } from "./contextactionset";
|
||||
@ -85,6 +86,8 @@ export abstract class Unit extends CustomMarker {
|
||||
|
||||
/* Data controlled directly by the backend. No setters are provided to avoid misalignments */
|
||||
#alive: boolean = false;
|
||||
#alarmState: AlarmState = AlarmState.AUTO;
|
||||
#radarState: boolean | undefined = undefined;
|
||||
#human: boolean = false;
|
||||
#controlled: boolean = false;
|
||||
#coalition: string = "neutral";
|
||||
@ -349,6 +352,12 @@ export abstract class Unit extends CustomMarker {
|
||||
getRaceTrackBearing() {
|
||||
return this.#racetrackBearing;
|
||||
}
|
||||
getAlarmState() {
|
||||
return this.#alarmState;
|
||||
}
|
||||
getRadarState() {
|
||||
return this.#radarState;
|
||||
}
|
||||
getTimeToNextTasking() {
|
||||
return this.#timeToNextTasking;
|
||||
}
|
||||
@ -538,6 +547,7 @@ export abstract class Unit extends CustomMarker {
|
||||
var datumIndex = 0;
|
||||
while (datumIndex != DataIndexes.endOfData) {
|
||||
datumIndex = dataExtractor.extractUInt8();
|
||||
|
||||
switch (datumIndex) {
|
||||
case DataIndexes.category:
|
||||
dataExtractor.extractString();
|
||||
@ -546,6 +556,10 @@ export abstract class Unit extends CustomMarker {
|
||||
this.setAlive(dataExtractor.extractBool());
|
||||
updateMarker = true;
|
||||
break;
|
||||
case DataIndexes.radarState:
|
||||
this.#radarState = dataExtractor.extractBool();
|
||||
updateMarker = true;
|
||||
break;
|
||||
case DataIndexes.human:
|
||||
this.#human = dataExtractor.extractBool();
|
||||
break;
|
||||
@ -651,6 +665,9 @@ export abstract class Unit extends CustomMarker {
|
||||
case DataIndexes.ROE:
|
||||
this.#ROE = enumToROE(dataExtractor.extractUInt8());
|
||||
break;
|
||||
case DataIndexes.alarmState:
|
||||
this.#alarmState = enumToAlarmState(dataExtractor.extractUInt8());
|
||||
break;
|
||||
case DataIndexes.reactionToThreat:
|
||||
this.#reactionToThreat = enumToReactionToThreat(dataExtractor.extractUInt8());
|
||||
break;
|
||||
@ -845,6 +862,7 @@ export abstract class Unit extends CustomMarker {
|
||||
racetrackLength: this.#racetrackLength,
|
||||
racetrackAnchor: this.#racetrackAnchor,
|
||||
racetrackBearing: this.#racetrackBearing,
|
||||
alarmState: this.#alarmState,
|
||||
timeToNextTasking: this.#timeToNextTasking,
|
||||
barrelHeight: this.#barrelHeight,
|
||||
muzzleVelocity: this.#muzzleVelocity,
|
||||
@ -870,6 +888,13 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
}
|
||||
|
||||
setRadarState(newRadarState: boolean) {
|
||||
if (newRadarState != this.#radarState) {
|
||||
this.#radarState = newRadarState;
|
||||
this.#updateMarker();
|
||||
}
|
||||
}
|
||||
|
||||
/** Set the unit as user-selected
|
||||
*
|
||||
* @param selected (boolean)
|
||||
@ -1166,6 +1191,13 @@ export abstract class Unit extends CustomMarker {
|
||||
el.append(healthIndicator);
|
||||
}
|
||||
|
||||
/* Alarm state indicator */
|
||||
if (iconOptions.showAlarmState) {
|
||||
var alarmStateIcon = document.createElement("div");
|
||||
alarmStateIcon.classList.add("unit-alarm-state");
|
||||
el.append(alarmStateIcon);
|
||||
}
|
||||
|
||||
/* Ammo indicator */
|
||||
if (iconOptions.showAmmo) {
|
||||
var ammoIndicator = document.createElement("div");
|
||||
@ -1401,6 +1433,10 @@ export abstract class Unit extends CustomMarker {
|
||||
if (!this.#human) getApp().getServerManager().setROE(this.ID, ROE);
|
||||
}
|
||||
|
||||
setAlarmState(alarmState: number) {
|
||||
if (!this.#human) getApp().getServerManager().setAlarmState(this.ID, alarmState);
|
||||
}
|
||||
|
||||
setReactionToThreat(reactionToThreat: string) {
|
||||
if (!this.#human) getApp().getServerManager().setReactionToThreat(this.ID, reactionToThreat);
|
||||
}
|
||||
@ -1725,165 +1761,168 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.getHidden()) return; // We won't draw the marker if the unit is hidden
|
||||
|
||||
/* Draw the marker */
|
||||
if (!this.getHidden()) {
|
||||
if (this.getLatLng().lat !== this.#position.lat || this.getLatLng().lng !== this.#position.lng) {
|
||||
this.setLatLng(new LatLng(this.#position.lat, this.#position.lng));
|
||||
}
|
||||
if (this.getLatLng().lat !== this.#position.lat || this.getLatLng().lng !== this.#position.lng) {
|
||||
this.setLatLng(new LatLng(this.#position.lat, this.#position.lng));
|
||||
}
|
||||
|
||||
var element = this.getElement();
|
||||
if (element != null) {
|
||||
/* Draw the velocity vector */
|
||||
element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.#speed / 5}px;`);
|
||||
var element = this.getElement();
|
||||
if (element != null) {
|
||||
/* Draw the velocity vector */
|
||||
element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.#speed / 5}px;`);
|
||||
|
||||
/* Set fuel data */
|
||||
element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.#fuel}%`);
|
||||
element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.#fuel < 20);
|
||||
/* Set fuel data */
|
||||
element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.#fuel}%`);
|
||||
element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.#fuel < 20);
|
||||
|
||||
/* Set health data */
|
||||
element.querySelector(".unit-health-level")?.setAttribute("style", `width: ${this.#health}%`);
|
||||
element.querySelector(".unit")?.toggleAttribute("data-has-low-health", this.#health < 20);
|
||||
/* Set health data */
|
||||
element.querySelector(".unit-health-level")?.setAttribute("style", `width: ${this.#health}%`);
|
||||
element.querySelector(".unit")?.toggleAttribute("data-has-low-health", this.#health < 20);
|
||||
|
||||
/* Set dead/alive flag */
|
||||
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive);
|
||||
/* Set dead/alive flag */
|
||||
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive);
|
||||
|
||||
/* Set current unit state */
|
||||
if (this.#human) {
|
||||
// Unit is human
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "human");
|
||||
} else if (!this.#controlled) {
|
||||
// Unit is under DCS control (not Olympus)
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "dcs");
|
||||
} else if ((this.getCategory() == "Aircraft" || this.getCategory() == "Helicopter") && !this.#hasTask) {
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "no-task");
|
||||
} else {
|
||||
// Unit is under Olympus control
|
||||
if (this.#onOff) {
|
||||
if (this.#isActiveTanker) element.querySelector(".unit")?.setAttribute("data-state", "tanker");
|
||||
else if (this.#isActiveAWACS) element.querySelector(".unit")?.setAttribute("data-state", "AWACS");
|
||||
else element.querySelector(".unit")?.setAttribute("data-state", this.#state.toLowerCase());
|
||||
} else {
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "off");
|
||||
}
|
||||
}
|
||||
/* Set radar state*/
|
||||
element.querySelector(".unit")?.setAttribute("data-alarm-state", this.#alarmState);
|
||||
|
||||
/* Set altitude and speed */
|
||||
if (element.querySelector(".unit-altitude"))
|
||||
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + zeroAppend(Math.floor(mToFt(this.#position.alt as number) / 100), 3);
|
||||
if (element.querySelector(".unit-speed"))
|
||||
(<HTMLElement>element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.#speed))) + "GS";
|
||||
|
||||
/* Rotate elements according to heading */
|
||||
element.querySelectorAll("[data-rotate-to-heading]").forEach((el) => {
|
||||
const headingDeg = rad2deg(this.#track);
|
||||
let currentStyle = el.getAttribute("style") || "";
|
||||
el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`);
|
||||
});
|
||||
|
||||
/* Turn on ammo indicators */
|
||||
var hasFox1 = element.querySelector(".unit")?.hasAttribute("data-has-fox-1");
|
||||
var hasFox2 = element.querySelector(".unit")?.hasAttribute("data-has-fox-2");
|
||||
var hasFox3 = element.querySelector(".unit")?.hasAttribute("data-has-fox-3");
|
||||
var hasOtherAmmo = element.querySelector(".unit")?.hasAttribute("data-has-other-ammo");
|
||||
|
||||
var newHasFox1 = false;
|
||||
var newHasFox2 = false;
|
||||
var newHasFox3 = false;
|
||||
var newHasOtherAmmo = false;
|
||||
Object.values(this.#ammo).forEach((ammo: Ammo) => {
|
||||
if (ammo.category == 1 && ammo.missileCategory == 1) {
|
||||
if (ammo.guidance == 4 || ammo.guidance == 5) newHasFox1 = true;
|
||||
else if (ammo.guidance == 2) newHasFox2 = true;
|
||||
else if (ammo.guidance == 3) newHasFox3 = true;
|
||||
} else newHasOtherAmmo = true;
|
||||
});
|
||||
|
||||
if (hasFox1 != newHasFox1) element.querySelector(".unit")?.toggleAttribute("data-has-fox-1", newHasFox1);
|
||||
if (hasFox2 != newHasFox2) element.querySelector(".unit")?.toggleAttribute("data-has-fox-2", newHasFox2);
|
||||
if (hasFox3 != newHasFox3) element.querySelector(".unit")?.toggleAttribute("data-has-fox-3", newHasFox3);
|
||||
if (hasOtherAmmo != newHasOtherAmmo) element.querySelector(".unit")?.toggleAttribute("data-has-other-ammo", newHasOtherAmmo);
|
||||
|
||||
/* Draw the hotgroup element */
|
||||
element.querySelector(".unit")?.toggleAttribute("data-is-in-hotgroup", this.#hotgroup != null);
|
||||
if (this.#hotgroup) {
|
||||
const hotgroupEl = element.querySelector(".unit-hotgroup-id") as HTMLElement;
|
||||
if (hotgroupEl) hotgroupEl.innerText = String(this.#hotgroup);
|
||||
}
|
||||
|
||||
/* Draw the cluster element */
|
||||
element
|
||||
.querySelector(".unit")
|
||||
?.toggleAttribute(
|
||||
"data-is-cluster-leader",
|
||||
this.#isClusterLeader &&
|
||||
this.#clusterUnits.length > 1 &&
|
||||
getApp().getMap().getOptions().clusterGroundUnits &&
|
||||
getApp().getMap().getZoom() < CLUSTERING_ZOOM_TRANSITION &&
|
||||
!this.getSelected()
|
||||
);
|
||||
if (this.#isClusterLeader && this.#clusterUnits.length > 1) {
|
||||
const clusterEl = element.querySelector(".unit-cluster-id") as HTMLElement;
|
||||
if (clusterEl) clusterEl.innerText = String(this.#clusterUnits.length);
|
||||
}
|
||||
|
||||
/* Set bullseyes positions */
|
||||
const bullseyes = getApp().getMissionManager().getBullseyes();
|
||||
if (Object.keys(bullseyes).length > 0) {
|
||||
const bullseye = `${computeBearingRangeString(bullseyes[coalitionToEnum(getApp().getMap().getOptions().AWACSCoalition)].getLatLng(), this.getPosition())}`;
|
||||
if (element.querySelector(".unit-bullseye")) (<HTMLElement>element.querySelector(".unit-bullseye")).innerText = `${bullseye}`;
|
||||
}
|
||||
|
||||
/* Set BRAA */
|
||||
const reference = getApp().getUnitsManager().getAWACSReference();
|
||||
if (reference && reference !== this && reference.getAlive()) {
|
||||
const BRAA = `${computeBearingRangeString(reference.getPosition(), this.getPosition())}`;
|
||||
if (element.querySelector(".unit-braa")) (<HTMLElement>element.querySelector(".unit-braa")).innerText = `${BRAA}`;
|
||||
} else if (element.querySelector(".unit-braa")) (<HTMLElement>element.querySelector(".unit-braa")).innerText = ``;
|
||||
|
||||
/* Set operate as */
|
||||
element
|
||||
.querySelector(".unit")
|
||||
?.setAttribute(
|
||||
"data-operate-as",
|
||||
this.getState() === UnitState.MISS_ON_PURPOSE || this.getState() === UnitState.SCENIC_AAA || this.getState() === UnitState.SIMULATE_FIRE_FIGHT
|
||||
? this.#operateAs
|
||||
: "neutral"
|
||||
);
|
||||
}
|
||||
|
||||
/* Set vertical offset for altitude stacking */
|
||||
var pos = getApp().getMap().latLngToLayerPoint(this.getLatLng()).round();
|
||||
this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0));
|
||||
|
||||
/* Get the cluster this unit is in to position the label correctly */
|
||||
let cluster = Object.values(getApp().getUnitsManager().getClusters()).find((cluster) => cluster.includes(this));
|
||||
if (cluster && cluster.length > 1) {
|
||||
let clusterMean = turf.centroid(turf.featureCollection(cluster.map((unit) => turf.point([unit.getPosition().lng, unit.getPosition().lat]))));
|
||||
let bearingFromCluster = bearing(
|
||||
clusterMean.geometry.coordinates[1],
|
||||
clusterMean.geometry.coordinates[0],
|
||||
this.getPosition().lat,
|
||||
this.getPosition().lng,
|
||||
false
|
||||
);
|
||||
|
||||
if (bearingFromCluster < 0) bearingFromCluster += 360;
|
||||
let trackIndex = Math.round(bearingFromCluster / 45);
|
||||
|
||||
for (let idx = 0; idx < bearingStrings.length; idx++) element?.querySelector(".unit-summary")?.classList.remove("cluster-" + bearingStrings[idx]);
|
||||
element?.querySelector(".unit-summary")?.classList.add("cluster-" + bearingStrings[trackIndex]);
|
||||
/* Set current unit state */
|
||||
if (this.#human) {
|
||||
// Unit is human
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "human");
|
||||
} else if (!this.#controlled) {
|
||||
// Unit is under DCS control (not Olympus)
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "dcs");
|
||||
} else if ((this.getCategory() == "Aircraft" || this.getCategory() == "Helicopter") && !this.#hasTask) {
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "no-task");
|
||||
} else {
|
||||
for (let idx = 0; idx < bearingStrings.length; idx++) element?.querySelector(".unit-summary")?.classList.remove("cluster-" + bearingStrings[idx]);
|
||||
element?.querySelector(".unit-summary")?.classList.add("cluster-north-east");
|
||||
// Unit is under Olympus control
|
||||
if (this.#onOff) {
|
||||
if (this.#isActiveTanker) element.querySelector(".unit")?.setAttribute("data-state", "tanker");
|
||||
else if (this.#isActiveAWACS) element.querySelector(".unit")?.setAttribute("data-state", "AWACS");
|
||||
else element.querySelector(".unit")?.setAttribute("data-state", this.#state.toLowerCase());
|
||||
} else {
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "off");
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw the contact trail */
|
||||
if (/*TODO getApp().getMap().getOptions().AWACSMode*/ false) {
|
||||
this.#trailPolylines = this.#trailPositions.map(
|
||||
(latlng, idx) => new Polyline([latlng, latlng], { color: colors.WHITE, opacity: 1 - (idx + 1) / TRAIL_LENGTH })
|
||||
);
|
||||
this.#trailPolylines.forEach((polyline) => polyline.addTo(getApp().getMap()));
|
||||
/* Set altitude and speed */
|
||||
if (element.querySelector(".unit-altitude"))
|
||||
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + zeroAppend(Math.floor(mToFt(this.#position.alt as number) / 100), 3);
|
||||
if (element.querySelector(".unit-speed"))
|
||||
(<HTMLElement>element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.#speed))) + "GS";
|
||||
|
||||
/* Rotate elements according to heading */
|
||||
element.querySelectorAll("[data-rotate-to-heading]").forEach((el) => {
|
||||
const headingDeg = rad2deg(this.#track);
|
||||
let currentStyle = el.getAttribute("style") || "";
|
||||
el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`);
|
||||
});
|
||||
|
||||
/* Turn on ammo indicators */
|
||||
var hasFox1 = element.querySelector(".unit")?.hasAttribute("data-has-fox-1");
|
||||
var hasFox2 = element.querySelector(".unit")?.hasAttribute("data-has-fox-2");
|
||||
var hasFox3 = element.querySelector(".unit")?.hasAttribute("data-has-fox-3");
|
||||
var hasOtherAmmo = element.querySelector(".unit")?.hasAttribute("data-has-other-ammo");
|
||||
|
||||
var newHasFox1 = false;
|
||||
var newHasFox2 = false;
|
||||
var newHasFox3 = false;
|
||||
var newHasOtherAmmo = false;
|
||||
Object.values(this.#ammo).forEach((ammo: Ammo) => {
|
||||
if (ammo.category == 1 && ammo.missileCategory == 1) {
|
||||
if (ammo.guidance == 4 || ammo.guidance == 5) newHasFox1 = true;
|
||||
else if (ammo.guidance == 2) newHasFox2 = true;
|
||||
else if (ammo.guidance == 3) newHasFox3 = true;
|
||||
} else newHasOtherAmmo = true;
|
||||
});
|
||||
|
||||
if (hasFox1 != newHasFox1) element.querySelector(".unit")?.toggleAttribute("data-has-fox-1", newHasFox1);
|
||||
if (hasFox2 != newHasFox2) element.querySelector(".unit")?.toggleAttribute("data-has-fox-2", newHasFox2);
|
||||
if (hasFox3 != newHasFox3) element.querySelector(".unit")?.toggleAttribute("data-has-fox-3", newHasFox3);
|
||||
if (hasOtherAmmo != newHasOtherAmmo) element.querySelector(".unit")?.toggleAttribute("data-has-other-ammo", newHasOtherAmmo);
|
||||
|
||||
/* Draw the hotgroup element */
|
||||
element.querySelector(".unit")?.toggleAttribute("data-is-in-hotgroup", this.#hotgroup != null);
|
||||
if (this.#hotgroup) {
|
||||
const hotgroupEl = element.querySelector(".unit-hotgroup-id") as HTMLElement;
|
||||
if (hotgroupEl) hotgroupEl.innerText = String(this.#hotgroup);
|
||||
}
|
||||
|
||||
/* Draw the cluster element */
|
||||
element
|
||||
.querySelector(".unit")
|
||||
?.toggleAttribute(
|
||||
"data-is-cluster-leader",
|
||||
this.#isClusterLeader &&
|
||||
this.#clusterUnits.length > 1 &&
|
||||
getApp().getMap().getOptions().clusterGroundUnits &&
|
||||
getApp().getMap().getZoom() < CLUSTERING_ZOOM_TRANSITION &&
|
||||
!this.getSelected()
|
||||
);
|
||||
if (this.#isClusterLeader && this.#clusterUnits.length > 1) {
|
||||
const clusterEl = element.querySelector(".unit-cluster-id") as HTMLElement;
|
||||
if (clusterEl) clusterEl.innerText = String(this.#clusterUnits.length);
|
||||
}
|
||||
|
||||
/* Set bullseyes positions */
|
||||
const bullseyes = getApp().getMissionManager().getBullseyes();
|
||||
if (Object.keys(bullseyes).length > 0) {
|
||||
const bullseye = `${computeBearingRangeString(bullseyes[coalitionToEnum(getApp().getMap().getOptions().AWACSCoalition)].getLatLng(), this.getPosition())}`;
|
||||
if (element.querySelector(".unit-bullseye")) (<HTMLElement>element.querySelector(".unit-bullseye")).innerText = `${bullseye}`;
|
||||
}
|
||||
|
||||
/* Set BRAA */
|
||||
const reference = getApp().getUnitsManager().getAWACSReference();
|
||||
if (reference && reference !== this && reference.getAlive()) {
|
||||
const BRAA = `${computeBearingRangeString(reference.getPosition(), this.getPosition())}`;
|
||||
if (element.querySelector(".unit-braa")) (<HTMLElement>element.querySelector(".unit-braa")).innerText = `${BRAA}`;
|
||||
} else if (element.querySelector(".unit-braa")) (<HTMLElement>element.querySelector(".unit-braa")).innerText = ``;
|
||||
|
||||
/* Set operate as */
|
||||
element
|
||||
.querySelector(".unit")
|
||||
?.setAttribute(
|
||||
"data-operate-as",
|
||||
this.getState() === UnitState.MISS_ON_PURPOSE || this.getState() === UnitState.SCENIC_AAA || this.getState() === UnitState.SIMULATE_FIRE_FIGHT
|
||||
? this.#operateAs
|
||||
: "neutral"
|
||||
);
|
||||
}
|
||||
|
||||
/* Set vertical offset for altitude stacking */
|
||||
var pos = getApp().getMap().latLngToLayerPoint(this.getLatLng()).round();
|
||||
this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0));
|
||||
|
||||
/* Get the cluster this unit is in to position the label correctly */
|
||||
let cluster = Object.values(getApp().getUnitsManager().getClusters()).find((cluster) => cluster.includes(this));
|
||||
if (cluster && cluster.length > 1) {
|
||||
let clusterMean = turf.centroid(turf.featureCollection(cluster.map((unit) => turf.point([unit.getPosition().lng, unit.getPosition().lat]))));
|
||||
let bearingFromCluster = bearing(
|
||||
clusterMean.geometry.coordinates[1],
|
||||
clusterMean.geometry.coordinates[0],
|
||||
this.getPosition().lat,
|
||||
this.getPosition().lng,
|
||||
false
|
||||
);
|
||||
|
||||
if (bearingFromCluster < 0) bearingFromCluster += 360;
|
||||
let trackIndex = Math.round(bearingFromCluster / 45);
|
||||
|
||||
for (let idx = 0; idx < bearingStrings.length; idx++) element?.querySelector(".unit-summary")?.classList.remove("cluster-" + bearingStrings[idx]);
|
||||
element?.querySelector(".unit-summary")?.classList.add("cluster-" + bearingStrings[trackIndex]);
|
||||
} else {
|
||||
for (let idx = 0; idx < bearingStrings.length; idx++) element?.querySelector(".unit-summary")?.classList.remove("cluster-" + bearingStrings[idx]);
|
||||
element?.querySelector(".unit-summary")?.classList.add("cluster-north-east");
|
||||
}
|
||||
|
||||
/* Draw the contact trail */
|
||||
if (/*TODO getApp().getMap().getOptions().AWACSMode*/ false) {
|
||||
this.#trailPolylines = this.#trailPositions.map(
|
||||
(latlng, idx) => new Polyline([latlng, latlng], { color: colors.WHITE, opacity: 1 - (idx + 1) / TRAIL_LENGTH })
|
||||
);
|
||||
this.#trailPolylines.forEach((polyline) => polyline.addTo(getApp().getMap()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2394,6 +2433,7 @@ export abstract class AirUnit extends Unit {
|
||||
showCallsign: belongsToCommandedCoalition && /*TODO !getApp().getMap().getOptions().AWACSMode || */ this.getHuman(),
|
||||
rotateToHeading: false,
|
||||
showCluster: false,
|
||||
showAlarmState: false
|
||||
} as ObjectIconOptions;
|
||||
}
|
||||
|
||||
@ -2482,6 +2522,7 @@ export class GroundUnit extends Unit {
|
||||
showCallsign: belongsToCommandedCoalition && /*TODO !getApp().getMap().getOptions().AWACSMode || */ this.getHuman(),
|
||||
rotateToHeading: false,
|
||||
showCluster: true,
|
||||
showAlarmState: true,
|
||||
} as ObjectIconOptions;
|
||||
}
|
||||
|
||||
@ -2549,6 +2590,7 @@ export class NavyUnit extends Unit {
|
||||
showCallsign: belongsToCommandedCoalition && /*TODO !getApp().getMap().getOptions().AWACSMode || */ this.getHuman(),
|
||||
rotateToHeading: false,
|
||||
showCluster: false,
|
||||
showAlarmState: true
|
||||
} as ObjectIconOptions;
|
||||
}
|
||||
|
||||
|
||||
@ -1,36 +1,14 @@
|
||||
import { DomEvent, DomUtil, LatLng, LatLngBounds } from "leaflet";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { AirUnit, GroundUnit, NavyUnit, Unit } from "./unit";
|
||||
import {
|
||||
areaContains,
|
||||
bearingAndDistanceToLatLng,
|
||||
deepCopyTable,
|
||||
deg2rad,
|
||||
getGroundElevation,
|
||||
latLngToMercator,
|
||||
mToFt,
|
||||
mercatorToLatLng,
|
||||
msToKnots,
|
||||
} from "../other/utils";
|
||||
import { CoalitionPolygon } from "../map/coalitionarea/coalitionpolygon";
|
||||
import * as turf from "@turf/turf";
|
||||
import { DomEvent, LatLng, LatLngBounds } from "leaflet";
|
||||
import {
|
||||
BLUE_COMMANDER,
|
||||
DELETE_CYCLE_TIME,
|
||||
DELETE_SLOW_THRESHOLD,
|
||||
DataIndexes,
|
||||
GAME_MASTER,
|
||||
IADSDensities,
|
||||
OlympusState,
|
||||
RED_COMMANDER,
|
||||
UnitControlSubState,
|
||||
UnitControlSubState
|
||||
} from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { citiesDatabase } from "./databases/citiesdatabase";
|
||||
import { TemporaryUnitMarker } from "../map/markers/temporaryunitmarker";
|
||||
import { Contact, GeneralSettings, Radio, TACAN, UnitBlueprint, UnitData, UnitSpawnTable } from "../interfaces";
|
||||
import { Group } from "./group";
|
||||
import { CoalitionCircle } from "../map/coalitionarea/coalitioncircle";
|
||||
import { ContextActionSet } from "./contextactionset";
|
||||
import {
|
||||
AWACSReferenceChangedEvent,
|
||||
CommandModeOptionsChangedEvent,
|
||||
@ -46,10 +24,31 @@ import {
|
||||
UnitsRefreshedEvent,
|
||||
UnitsUpdatedEvent,
|
||||
} from "../events";
|
||||
import { UnitDatabase } from "./databases/unitdatabase";
|
||||
import * as turf from "@turf/turf";
|
||||
import { Contact, GeneralSettings, Radio, TACAN, UnitBlueprint, UnitData, UnitSpawnTable } from "../interfaces";
|
||||
import { CoalitionCircle } from "../map/coalitionarea/coalitioncircle";
|
||||
import { CoalitionPolygon } from "../map/coalitionarea/coalitionpolygon";
|
||||
import { PathMarker } from "../map/markers/pathmarker";
|
||||
import { TemporaryUnitMarker } from "../map/markers/temporaryunitmarker";
|
||||
import { getApp } from "../olympusapp";
|
||||
import {
|
||||
areaContains,
|
||||
bearingAndDistanceToLatLng,
|
||||
deepCopyTable,
|
||||
deg2rad,
|
||||
getGroundElevation,
|
||||
latLngToMercator,
|
||||
mToFt,
|
||||
mercatorToLatLng,
|
||||
msToKnots,
|
||||
} from "../other/utils";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { Coalition } from "../types/types";
|
||||
import { ContextActionSet } from "./contextactionset";
|
||||
import { citiesDatabase } from "./databases/citiesdatabase";
|
||||
import { UnitDatabase } from "./databases/unitdatabase";
|
||||
import { Group } from "./group";
|
||||
import { AirUnit, GroundUnit, NavyUnit, Unit } from "./unit";
|
||||
|
||||
|
||||
/** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only
|
||||
* result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows
|
||||
@ -252,6 +251,7 @@ export class UnitsManager {
|
||||
if (datumIndex == DataIndexes.category) {
|
||||
const category = dataExtractor.extractString();
|
||||
this.addUnit(ID, category);
|
||||
|
||||
} else {
|
||||
/* Inconsistent data, we need to wait for a refresh */
|
||||
return updateTime;
|
||||
@ -261,6 +261,7 @@ export class UnitsManager {
|
||||
if (ID in this.#units) {
|
||||
this.#units[ID].setData(dataExtractor);
|
||||
this.#units[ID].getAlive() && updatedUnits.push(this.#units[ID]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -779,6 +780,27 @@ export class UnitsManager {
|
||||
this.#protectionCallback = callback;
|
||||
} else callback(units);
|
||||
}
|
||||
|
||||
/** Set a specific Alarm State to all the selected units
|
||||
*
|
||||
* @param AlarmState Value to set, see constants for acceptable values
|
||||
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
|
||||
*/
|
||||
setAlarmState(alarmState: number, units: Unit[] | null = null, onExecution: () => void = () => {}) {
|
||||
if (units === null) units = this.getSelectedUnits();
|
||||
units = units.filter((unit) => !unit.getHuman());
|
||||
|
||||
let callback = (units) => {
|
||||
onExecution();
|
||||
units.forEach((unit: Unit) => unit.setAlarmState(alarmState));
|
||||
this.#showActionMessage(units, `Alarm State set to ${alarmState.toString()}`);
|
||||
};
|
||||
|
||||
if (getApp().getMap().getOptions().protectDCSUnits && !units.every((unit) => unit.isControlledByOlympus())) {
|
||||
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.PROTECTION);
|
||||
this.#protectionCallback = callback;
|
||||
} else callback(units);
|
||||
}
|
||||
/** Set a specific reaction to threat to all the selected units
|
||||
*
|
||||
* @param reactionToThreat Value to set, see constants for acceptable values
|
||||
|
||||
@ -1263,6 +1263,14 @@ function Olympus.setUnitsData(arg, time)
|
||||
end
|
||||
|
||||
table["isAlive"] = unit:isExist() and unit:isActive() and unit:getLife() >= 1
|
||||
|
||||
if unit:isActive() and unit:hasSensors(Unit.SensorType.RADAR) then
|
||||
if unit:getRadar() then
|
||||
table["radarState"] = true
|
||||
else
|
||||
table["radarState"] = false
|
||||
end
|
||||
end
|
||||
|
||||
local group = unit:getGroup()
|
||||
if group ~= nil then
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user