diff --git a/LEGAL b/LEGAL index b0059956..ff20b280 100644 --- a/LEGAL +++ b/LEGAL @@ -2,7 +2,8 @@ DCS Olympus A real-time AI unit control mod for DCS World -Copyright (C) 2023 Veltro & Gang +Copyright (C) 2023 Veltro & Gang (the "DCS Olympus Team" or the +"Rightsholders") DCS Olympus (the "MATERIAL" or "Software") is provided completely free to users subject to the it under both the terms of version 3 of the GNU @@ -665,4 +666,13 @@ you, the licensee, and the authors and/or copyright holders of the Software with respect to the subject matter to which it pertains. It supersedes all prior agreements and understandings (if applicable), oral or written, with respect to such matters. - + +3. Unilateral Modification + + The parties agree that the DCS Olympus Team shall have the right to +unilaterally modify these terms (i.e. the agreement between you and the +DCS Olympus Team for the use of the Software), and that parties shall +be bound by such terms as modified from time to time. The DCS Olympus Team +shall not have an obligation to inform you of such modification, save that +such changes will be published on the DCS Olympus Github Repository located at +https://github.com/Pax1601/DCSOlympus. diff --git a/client/public/stylesheets/other/toolbar.css b/client/public/stylesheets/other/toolbar.css index c90df5f8..27550908 100644 --- a/client/public/stylesheets/other/toolbar.css +++ b/client/public/stylesheets/other/toolbar.css @@ -14,6 +14,10 @@ width: fit-content; } +#app-icon>.ol-select-value { + box-shadow: none; +} + #toolbar-summary { background-image: url("/images/icon-round.png"); background-position: 20px 22px; @@ -29,22 +33,56 @@ white-space: nowrap; } -#toolbar-container>*:nth-child(2)>svg { - display: none; - width: 0px; - height: 0px; +.ol-panel-tab { + align-items: center; + display:flex; + flex-direction: row; + margin-right:6px; +} + +.ol-panel-tab svg { + height:24; + width:24px; +} + +.ol-panel-tab svg * { + fill:white; +} + +.ol-panel-tab span { + font-size:13px; + font-weight:400; + padding:0 6px; } #toolbar-container>*:nth-child(3)>svg { display: none; } +#unit-visibility-control > div:nth-child(3), +#coalition-visibility-control { + border-left: 4px solid white; + padding-left: 12px; +} + @media (max-width: 1145px) { #toolbar-container { flex-direction: column; align-items: start; } + #toolbar-container .ol-panel .ol-panel-tab { + margin-right:0; + } + + #toolbar-container .ol-panel:hover .ol-panel-tab { + display:none; + } + + #toolbar-container .ol-panel-tab span { + display:none; + } + #toolbar-container>*:nth-child(1):not(:hover) { width: fit-content; height: fit-content; @@ -62,10 +100,10 @@ } #toolbar-container>*:not(:first-child):not(:hover)>svg { - display: block; - width: 24px; - height: 24px; + display: block; filter: invert(); + height: 24px; + width: 24px; } #toolbar-container>*:not(:first-child):not(:hover)>*:not(:first-child) { diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css index 8a77990d..b55b79a8 100644 --- a/client/public/stylesheets/style/style.css +++ b/client/public/stylesheets/style/style.css @@ -72,6 +72,18 @@ form { padding: 0; } +button svg.fill-coalition * { + fill: var(--primary-neutral) !important; +} + +button svg.fill-coalition[data-coalition="blue"] * { + fill: var(--primary-blue) !important; +} + +button svg.fill-coalition[data-coalition="red"] * { + fill: var(--primary-red) !important; +} + .pill { background-color: var(--background-steel); border-radius: 999px; @@ -376,7 +388,7 @@ button.ol-button-warning>svg:first-child { } nav.ol-panel { - column-gap: 20px; + column-gap: 10px; display: flex; flex-direction: row; height: 58px; @@ -388,8 +400,7 @@ nav.ol-panel> :last-child { .ol-panel .ol-group { align-items: center; - border-radius: var(--border-radius-sm); - column-gap: 10px; + column-gap: 12px; display: flex; flex-direction: row; flex-wrap: nowrap; @@ -656,13 +667,13 @@ nav.ol-panel> :last-child { } .ol-navbar-buttons-group button.off svg * { - fill: white !important; - stroke: white !important; + fill: white; + stroke: white; } .ol-navbar-buttons-group button svg * { - fill: var(--background-steel) !important; - stroke: var(--background-steel) !important; + fill: var(--background-steel); + stroke: var(--background-steel); } .ol-navbar-buttons-group .protectable button:first-of-type { @@ -1594,6 +1605,24 @@ input[type=number]::-webkit-outer-spin-button { fill: lightgray; } +#map-visibility-options .ol-select-options .ol-checkbox { + font-size:13px; + font-weight:400; + padding:6px 15px; +} + +#map-visibility-options .ol-select-options .ol-checkbox:first-of-type { + padding-top:12px; +} + +#map-visibility-options .ol-select-options .ol-checkbox:last-of-type { + padding-bottom:12px; +} + +#map-visibility-options .ol-select-options .ol-checkbox label:hover span { + text-decoration: underline; +} + .ol-log-entry:first-of-type { border-top: 1px solid #FFFFFF44; } @@ -1689,4 +1718,5 @@ input[type=number]::-webkit-outer-spin-button { 50% { opacity: 0; } -} \ No newline at end of file +} + diff --git a/client/public/themes/olympus/images/buttons/visibility/circle-dot.svg b/client/public/themes/olympus/images/buttons/visibility/circle-dot.svg new file mode 100644 index 00000000..f98ede3a --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/circle-dot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/images/buttons/visibility/flag.svg b/client/public/themes/olympus/images/buttons/visibility/flag.svg new file mode 100644 index 00000000..a1efc147 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/flag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/images/buttons/visibility/shield.svg b/client/public/themes/olympus/images/buttons/visibility/shield.svg new file mode 100644 index 00000000..d8ff87ec --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/shield.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/images/icons/eye-solid.svg b/client/public/themes/olympus/images/icons/eye-solid.svg index 3b5d2412..63bc71ba 100644 --- a/client/public/themes/olympus/images/icons/eye-solid.svg +++ b/client/public/themes/olympus/images/icons/eye-solid.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/client/src/contextmenus/coalitionareacontextmenu.ts b/client/src/contextmenus/coalitionareacontextmenu.ts index ad6ab040..ca993f6e 100644 --- a/client/src/contextmenus/coalitionareacontextmenu.ts +++ b/client/src/contextmenus/coalitionareacontextmenu.ts @@ -27,8 +27,8 @@ export class CoalitionAreaContextMenu extends ContextMenu { super(ID); /* Create the coalition switch */ - this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value)); - this.#coalitionSwitch.setValue(false); + this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value), true); + this.#coalitionSwitch.setValue(true); /* Create the controls of the IADS creation submenu */ this.#iadsTypesDropdown = new Dropdown("iads-units-type-options", () => { }); @@ -146,11 +146,11 @@ export class CoalitionAreaContextMenu extends ContextMenu { /** Callback event called when the coalition switch is clicked to change the coalition of the CoalitionArea * - * @param value Switch position (false: blue, true: red) + * @param value Switch position (false: red, true: blue) */ #onSwitchClick(value: boolean) { if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER) { - this.getCoalitionArea()?.setCoalition(value ? "red" : "blue"); + this.getCoalitionArea()?.setCoalition(value ? "blue" : "red"); this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition()) }); diff --git a/client/views/contextmenus/coalitionarea.ejs b/client/views/contextmenus/coalitionarea.ejs index 7341fa99..85ad2873 100644 --- a/client/views/contextmenus/coalitionarea.ejs +++ b/client/views/contextmenus/coalitionarea.ejs @@ -1,7 +1,7 @@
-
+
-
Options
+
Options
@@ -46,25 +46,28 @@
\ No newline at end of file diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 3f186e90..2037c261 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1345,3 +1345,11 @@ Olympus.initializeUnits() Olympus.notify("OlympusCommand script " .. version .. " loaded successfully", 2, true) +-- Load the current instance folder +local lfs = require('lfs') + +Olympus.instancePath = lfs.writedir().."Mods\\Services\\Olympus" + +Olympus.notify("Starting DCS Olympus backend session in "..Olympus.instancePath, 2) +Olympus.OlympusDLL.setInstancePath() + diff --git a/src/core/src/aircraft.cpp b/src/core/src/aircraft.cpp index be3021f9..eb716c01 100644 --- a/src/core/src/aircraft.cpp +++ b/src/core/src/aircraft.cpp @@ -12,24 +12,18 @@ using namespace GeographicLib; extern Scheduler* scheduler; extern UnitsManager* unitsManager; json::value Aircraft::database = json::value(); +extern string instancePath; void Aircraft::loadDatabase(string path) { - char* buf = nullptr; - size_t sz = 0; - if (_dupenv_s(&buf, &sz, "DCSOLYMPUS_PATH") == 0 && buf != nullptr) - { - std::ifstream ifstream(string(buf) + path); - std::stringstream ss; - ss << ifstream.rdbuf(); - std::error_code errorCode; - database = json::value::parse(ss.str(), errorCode); - if (database.is_object()) - log("Aircrafts database loaded correctly"); - else - log("Error reading Aircrafts database file"); - - free(buf); - } + std::ifstream ifstream(instancePath + path); + std::stringstream ss; + ss << ifstream.rdbuf(); + std::error_code errorCode; + database = json::value::parse(ss.str(), errorCode); + if (database.is_object()) + log("Aircrafts database loaded correctly from " + instancePath + path); + else + log("Error reading Aircrafts database file"); } /* Aircraft */ diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index 35822d46..d59432ce 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -25,6 +25,7 @@ json::value missionData = json::value::object(); mutex mutexLock; string sessionHash; +string instancePath; bool initialized = false; @@ -69,6 +70,20 @@ extern "C" DllExport int coreInit(lua_State* L) return(0); } +extern "C" DllExport int coreInstancePath(lua_State * L) +{ + /* Lock for thread safety */ + lock_guard guard(mutexLock); + + lua_getglobal(L, "Olympus"); + lua_getfield(L, -1, "instancePath"); + instancePath = lua_tostring(L, -1); + + log("Setting instance path to " + instancePath); + + return(0); +} + extern "C" DllExport int coreFrame(lua_State* L) { if (!initialized) diff --git a/src/core/src/groundunit.cpp b/src/core/src/groundunit.cpp index 8462a5bd..b9ab0183 100644 --- a/src/core/src/groundunit.cpp +++ b/src/core/src/groundunit.cpp @@ -12,27 +12,21 @@ using namespace GeographicLib; extern Scheduler* scheduler; extern UnitsManager* unitsManager; json::value GroundUnit::database = json::value(); +extern string instancePath; #define RANDOM_ZERO_TO_ONE (double)(rand()) / (double)(RAND_MAX) #define RANDOM_MINUS_ONE_TO_ONE (((double)(rand()) / (double)(RAND_MAX) - 0.5) * 2) void GroundUnit::loadDatabase(string path) { - char* buf = nullptr; - size_t sz = 0; - if (_dupenv_s(&buf, &sz, "DCSOLYMPUS_PATH") == 0 && buf != nullptr) - { - std::ifstream ifstream(string(buf) + path); - std::stringstream ss; - ss << ifstream.rdbuf(); - std::error_code errorCode; - database = json::value::parse(ss.str(), errorCode); - if (database.is_object()) - log("Ground Units database loaded correctly"); - else - log("Error reading Ground Units database file"); - - free(buf); - } + std::ifstream ifstream(instancePath + path); + std::stringstream ss; + ss << ifstream.rdbuf(); + std::error_code errorCode; + database = json::value::parse(ss.str(), errorCode); + if (database.is_object()) + log("GroundUnits database loaded correctly from " + instancePath + path); + else + log("Error reading GroundUnits database file"); } /* Ground unit */ diff --git a/src/core/src/helicopter.cpp b/src/core/src/helicopter.cpp index c452f118..f12a5d3e 100644 --- a/src/core/src/helicopter.cpp +++ b/src/core/src/helicopter.cpp @@ -12,24 +12,18 @@ using namespace GeographicLib; extern Scheduler* scheduler; extern UnitsManager* unitsManager; json::value Helicopter::database = json::value(); +extern string instancePath; void Helicopter::loadDatabase(string path) { - char* buf = nullptr; - size_t sz = 0; - if (_dupenv_s(&buf, &sz, "DCSOLYMPUS_PATH") == 0 && buf != nullptr) - { - std::ifstream ifstream(string(buf) + path); - std::stringstream ss; - ss << ifstream.rdbuf(); - std::error_code errorCode; - database = json::value::parse(ss.str(), errorCode); - if (database.is_object()) - log("Helicopters database loaded correctly"); - else - log("Error reading Helicopters database file"); - - free(buf); - } + std::ifstream ifstream(instancePath + path); + std::stringstream ss; + ss << ifstream.rdbuf(); + std::error_code errorCode; + database = json::value::parse(ss.str(), errorCode); + if (database.is_object()) + log("Helicopters database loaded correctly from " + instancePath + path); + else + log("Error reading Helicopters database file"); } /* Helicopter */ diff --git a/src/core/src/navyunit.cpp b/src/core/src/navyunit.cpp index a8cc7d87..317178e1 100644 --- a/src/core/src/navyunit.cpp +++ b/src/core/src/navyunit.cpp @@ -12,24 +12,18 @@ using namespace GeographicLib; extern Scheduler* scheduler; extern UnitsManager* unitsManager; json::value NavyUnit::database = json::value(); +extern string instancePath; void NavyUnit::loadDatabase(string path) { - char* buf = nullptr; - size_t sz = 0; - if (_dupenv_s(&buf, &sz, "DCSOLYMPUS_PATH") == 0 && buf != nullptr) - { - std::ifstream ifstream(string(buf) + path); - std::stringstream ss; - ss << ifstream.rdbuf(); - std::error_code errorCode; - database = json::value::parse(ss.str(), errorCode); - if (database.is_object()) - log("Navy Units database loaded correctly"); - else - log("Error reading Navy Units database file"); - - free(buf); - } + std::ifstream ifstream(instancePath + path); + std::stringstream ss; + ss << ifstream.rdbuf(); + std::error_code errorCode; + database = json::value::parse(ss.str(), errorCode); + if (database.is_object()) + log("NavyUnits database loaded correctly from " + instancePath + path); + else + log("Error reading NavyUnits database file"); } /* Navy Unit */ diff --git a/src/core/src/server.cpp b/src/core/src/server.cpp index e3be2f2f..b810db13 100644 --- a/src/core/src/server.cpp +++ b/src/core/src/server.cpp @@ -19,6 +19,7 @@ extern Scheduler* scheduler; extern json::value missionData; extern mutex mutexLock; extern string sessionHash; +extern string instancePath; void handle_eptr(std::exception_ptr eptr) { @@ -286,39 +287,32 @@ string Server::extractPassword(http_request& request) { void Server::task() { string address = REST_ADDRESS; - string modLocation; - char* buf = nullptr; - size_t sz = 0; - if (_dupenv_s(&buf, &sz, "DCSOLYMPUS_PATH") == 0 && buf != nullptr) - { - std::ifstream ifstream(string(buf) + OLYMPUS_JSON_PATH); - std::stringstream ss; - ss << ifstream.rdbuf(); - std::error_code errorCode; - json::value config = json::value::parse(ss.str(), errorCode); - if (config.is_object() && config.has_object_field(L"server") && - config[L"server"].has_string_field(L"address") && config[L"server"].has_number_field(L"port")) - { - address = "http://" + to_string(config[L"server"][L"address"]) + ":" + to_string(config[L"server"][L"port"].as_number().to_int32()); - log("Starting server on " + address); - } - else - log("Error reading configuration file. Starting server on " + address); + string jsonLocation = instancePath + OLYMPUS_JSON_PATH; - if (config.is_object() && config.has_object_field(L"authentication")) - { - if (config[L"authentication"].has_string_field(L"gameMasterPassword")) gameMasterPassword = to_string(config[L"authentication"][L"gameMasterPassword"]); - if (config[L"authentication"].has_string_field(L"blueCommanderPassword")) blueCommanderPassword = to_string(config[L"authentication"][L"blueCommanderPassword"]); - if (config[L"authentication"].has_string_field(L"redCommanderPassword")) redCommanderPassword = to_string(config[L"authentication"][L"redCommanderPassword"]); - } - else - log("Error reading configuration file. No password set."); - free(buf); + log("Reading configuration from " + jsonLocation); + + std::ifstream ifstream(jsonLocation); + std::stringstream ss; + ss << ifstream.rdbuf(); + std::error_code errorCode; + json::value config = json::value::parse(ss.str(), errorCode); + if (config.is_object() && config.has_object_field(L"server") && + config[L"server"].has_string_field(L"address") && config[L"server"].has_number_field(L"port")) + { + address = "http://" + to_string(config[L"server"][L"address"]) + ":" + to_string(config[L"server"][L"port"].as_number().to_int32()); + log("Starting server on " + address); } else + log("Error reading configuration file. Starting server on " + address); + + if (config.is_object() && config.has_object_field(L"authentication")) { - log("DCSOLYMPUS_PATH environment variable is missing, starting server on " + address); + if (config[L"authentication"].has_string_field(L"gameMasterPassword")) gameMasterPassword = to_string(config[L"authentication"][L"gameMasterPassword"]); + if (config[L"authentication"].has_string_field(L"blueCommanderPassword")) blueCommanderPassword = to_string(config[L"authentication"][L"blueCommanderPassword"]); + if (config[L"authentication"].has_string_field(L"redCommanderPassword")) redCommanderPassword = to_string(config[L"authentication"][L"redCommanderPassword"]); } + else + log("Error reading configuration file. No password set."); http_listener listener(to_wstring(address + "/" + REST_URI)); diff --git a/src/olympus/src/olympus.cpp b/src/olympus/src/olympus.cpp index 601a3ebb..2417b054 100644 --- a/src/olympus/src/olympus.cpp +++ b/src/olympus/src/olympus.cpp @@ -11,12 +11,14 @@ typedef int(__stdcall* f_coreFrame)(lua_State* L); typedef int(__stdcall* f_coreUnitsData)(lua_State* L); typedef int(__stdcall* f_coreWeaponsData)(lua_State* L); typedef int(__stdcall* f_coreMissionData)(lua_State* L); +typedef int(__stdcall* f_coreInstancePath)(lua_State* L); f_coreInit coreInit = nullptr; f_coreDeinit coreDeinit = nullptr; f_coreFrame coreFrame = nullptr; f_coreUnitsData coreUnitsData = nullptr; f_coreWeaponsData coreWeaponsData = nullptr; f_coreMissionData coreMissionData = nullptr; +f_coreInstancePath coreInstancePath = nullptr; static int onSimulationStart(lua_State* L) { @@ -90,6 +92,13 @@ static int onSimulationStart(lua_State* L) goto error; } + coreInstancePath = (f_coreInstancePath)GetProcAddress(hGetProcIDDLL, "coreInstancePath"); + if (!coreInstancePath) + { + LogError(L, "Error getting coreInstancePath ProcAddress from DLL"); + goto error; + } + coreInit(L); LogInfo(L, "Module loaded and started successfully."); @@ -137,6 +146,7 @@ static int onSimulationStop(lua_State* L) coreUnitsData = nullptr; coreWeaponsData = nullptr; coreMissionData = nullptr; + coreInstancePath = nullptr; } hGetProcIDDLL = NULL; @@ -175,6 +185,15 @@ static int setMissionData(lua_State* L) return 0; } +static int setInstancePath(lua_State* L) +{ + if (coreInstancePath) + { + coreInstancePath(L); + } + return 0; +} + static const luaL_Reg Map[] = { {"onSimulationStart", onSimulationStart}, {"onSimulationFrame", onSimulationFrame}, @@ -182,6 +201,7 @@ static const luaL_Reg Map[] = { {"setUnitsData", setUnitsData }, {"setWeaponsData", setWeaponsData }, {"setMissionData", setMissionData }, + {"setInstancePath", setInstancePath }, {NULL, NULL} };