diff --git a/client/src/@types/server.d.ts b/client/src/@types/server.d.ts index e1a9c3e9..23920339 100644 --- a/client/src/@types/server.d.ts +++ b/client/src/@types/server.d.ts @@ -1,5 +1,5 @@ interface UnitsData { - units: {[key: string]: UnitData}, + units: string, sessionHash: string } diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index a38e0f26..6043539d 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -4,6 +4,7 @@ import { UnitDatabase } from "../units/unitdatabase"; import { aircraftDatabase } from "../units/aircraftdatabase"; import { helicopterDatabase } from "../units/helicopterdatabase"; import { groundUnitsDatabase } from "../units/groundunitsdatabase"; +import { Buffer } from "buffer"; export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) { const φ1 = deg2rad(lat1); // φ, λ in radians @@ -247,4 +248,8 @@ export function getUnitDatabaseByCategory(category: string) { return groundUnitsDatabase; else return null; -} \ No newline at end of file +} + +export function base64ToBytes(base64: string) { + return Buffer.from(base64, 'base64').buffer; +} diff --git a/client/src/server/server.ts b/client/src/server/server.ts index f3ea1afd..f6249184 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -300,7 +300,7 @@ export function startUpdate() { getAirbases((data: AirbasesData) => getMissionData()?.update(data)); getBullseye((data: BullseyesData) => getMissionData()?.update(data)); getMission((data: any) => { getMissionData()?.update(data) }); - getUnits((data: UnitsData) => getUnitsManager()?.update(data), true /* Does a full refresh */); + getUnits((data: UnitsData) => getUnitsManager()?.update(data.units), true /* Does a full refresh */); requestUpdate(); requestRefresh(); @@ -310,7 +310,7 @@ export function requestUpdate() { /* Main update rate = 250ms is minimum time, equal to server update time. */ getUnits((data: UnitsData) => { if (!getPaused()) { - getUnitsManager()?.update(data); + getUnitsManager()?.update(data.units); checkSessionHash(data.sessionHash); } }, false); diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 35a7afcb..2bb85f3c 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -1,8 +1,8 @@ import { LatLng, LatLngBounds } from "leaflet"; -import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitDataTable } from ".."; +import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler } from ".."; import { Unit } from "./unit"; import { cloneUnit, spawnGroundUnit } from "../server/server"; -import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polygonArea, randomPointInPoly, randomUnitBlueprintByRole } from "../other/utils"; +import { base64ToBytes, deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polygonArea, randomPointInPoly, randomUnitBlueprintByRole } from "../other/utils"; import { CoalitionArea } from "../map/coalitionarea"; import { Airbase } from "../missionhandler/airbase"; import { groundUnitsDatabase } from "./groundunitsdatabase"; @@ -68,9 +68,61 @@ export class UnitsManager { } - update(data: UnitsData) { + update(data: string) { var updatedUnits: Unit[] = []; - Object.keys(data.units) + var buffer = base64ToBytes(data); + + /*Coords position; + double speed; + double heading; + unsigned short fuel; + double desiredSpeed; + double desiredAltitude; + unsigned int targetID; + Coords targetPosition; + unsigned char state; + unsigned char ROE; + unsigned char reactionToThreat; + unsigned char emissionsCountermeasures; + Options::TACAN TACAN; + Options::Radio Radio; + unsigned short pathLength; + unsigned char nameLength; + unsigned char unitNameLength; + unsigned char groupNameLength; + unsigned char categoryLength; + unsigned char coalitionLength;*/ + + var offset = 0; + var dataview = new DataView(buffer); + const ID = dataview.getUint32(offset, true); offset += 4; + const bitmask = dataview.getUint32(offset , true); offset += 4; + const alive = bitmask & (1 << 0); + const human = bitmask >> 1 & 1; + const controlled = bitmask >> 2 & 1; + const hasTask = bitmask >> 3 & 1; + const desiredAltitudeType = bitmask >> 16 & 1; + const desiredSpeedType = bitmask >> 17 & 1; + const isTanker = bitmask >> 18 & 1; + const isAWACS = bitmask >> 19 & 1; + const onOff = bitmask >> 20 & 1; + const followRoads = bitmask >> 21 & 1; + const EPLRS = bitmask >> 22 & 1; + const prohibitAA = bitmask >> 23 & 1; + const prohibitAfterburner = bitmask >> 24 & 1; + const prohibitAG = bitmask >> 25 & 1; + const prohibitAirWpn = bitmask >> 26 & 1; + const prohibitJettison = bitmask >> 27 & 1; + + const latitude = dataview.getFloat64(offset , true); offset += 8; + const longitude = dataview.getFloat64(offset , true); offset += 8; + const altitude = dataview.getFloat64(offset , true); offset += 8; + const speed = dataview.getFloat64(offset , true); offset += 8; + const heading = dataview.getFloat64(offset , true); offset += 8; + + + var foo = 12; + /*Object.keys(data.units) .filter((ID: string) => !(ID in this.#units)) .reduce((timeout: number, ID: string) => { window.setTimeout(() => { @@ -91,7 +143,7 @@ export class UnitsManager { this.getSelectedUnits().forEach((unit: Unit) => { if (!updatedUnits.includes(unit)) unit.setData({}) - }); + });*/ } setHiddenType(key: string, value: boolean) { diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 2fc60171..f8f5241d 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -29,6 +29,7 @@ namespace State }; }; +#pragma pack(push, 1) namespace Options { struct TACAN { @@ -86,8 +87,15 @@ namespace DataTypes { unsigned char emissionsCountermeasures; Options::TACAN TACAN; Options::Radio Radio; + unsigned short pathLength; + unsigned char nameLength; + unsigned char unitNameLength; + unsigned char groupNameLength; + unsigned char categoryLength; + unsigned char coalitionLength; }; } +#pragma pack(pop) class Unit { @@ -102,8 +110,8 @@ public: void runAILoop(); void updateExportData(json::value json, double dt = 0); void updateMissionData(json::value json); - DataTypes::DataPacket getDataPacket(); - string getData(bool refresh); + unsigned int getUpdateData(char* &data); + void getData(stringstream &ss, bool refresh); virtual string getCategory() { return "No category"; }; /********** Base data **********/ diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index 375a996d..fe0b9977 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -68,7 +68,7 @@ void AirUnit::setState(unsigned char newState) case State::ATTACK: { if (isTargetAlive()) { Unit* target = unitsManager->getUnit(targetID); - Coords targetPosition = Coords(target->getLatitude(), target->getLongitude(), 0); + Coords targetPosition = Coords(target->getPosition().lat, target->getPosition().lng, 0); clearActivePath(); pushActivePathFront(targetPosition); resetActiveDestination(); diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index 7a17691b..4f75b5ec 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -79,18 +79,19 @@ extern "C" DllExport int coreFrame(lua_State* L) lock_guard guard(mutexLock); milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); - frameRate = frameCounter / duration.count(); + if (duration.count() > 0) + frameRate = frameCounter / duration.count(); frameCounter = 0; if (unitsManager != nullptr) { unitsManager->updateExportData(L, duration.count()); - unitsManager->runAILoop(); + //unitsManager->runAILoop(); } before = std::chrono::system_clock::now(); } if (scheduler != nullptr) - scheduler->execute(L); + //scheduler->execute(L); return(0); } diff --git a/src/core/src/server.cpp b/src/core/src/server.cpp index e8c71e5d..086882d7 100644 --- a/src/core/src/server.cpp +++ b/src/core/src/server.cpp @@ -71,6 +71,7 @@ void Server::handle_get(http_request request) http_response response(status_codes::OK); string authorization = to_base64("admin:" + password); + log(authorization); if (password == "" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization))) { std::exception_ptr eptr; diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 1fdda23a..bc99edbd 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -6,9 +6,6 @@ #include "defines.h" #include "unitsmanager.h" -#include "base64.hpp" -using namespace base64; - #include using namespace std::chrono; @@ -54,30 +51,26 @@ void Unit::initialize(json::value json) void Unit::setDefaults(bool force) { - const bool isUnitControlledByOlympus = getControlled(); - const bool isUnitAlive = getAlive(); - const bool isUnitLeader = unitsManager->isUnitGroupLeader(this); - const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); + if (!getControlled()) return; + if (!unitsManager->isUnitGroupLeader(this)) return; + if (!(getAlive() || unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this))) return; + if (getHuman()) return; - if (isUnitControlledByOlympus && (isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits) && isUnitLeader && !human) { - /* Set the default IDLE state */ - setState(State::IDLE); + /* Set the default IDLE state */ + setState(State::IDLE); - /* Set desired altitude to be equal to current altitude so the unit does not climb/descend after spawn */ - setDesiredAltitude(position.alt); + /* Set desired altitude to be equal to current altitude so the unit does not climb/descend after spawn */ + setDesiredAltitude(position.alt); - /* Set the default options (these are all defaults so will only affect the export data, no DCS command will be sent) */ - setROE(ROE::OPEN_FIRE_WEAPON_FREE, force); - setReactionToThreat(ReactionToThreat::EVADE_FIRE, force); - setEmissionsCountermeasures(EmissionCountermeasure::DEFEND, force); - strcpy_s(TACAN.callsign, 4, "TKR"); - setTACAN(TACAN, force); - setRadio(radio, force); - setEPLRS(EPLRS, force); - setGeneralSettings(generalSettings, force); - setOnOff(onOff); - setFollowRoads(followRoads); - } + /* Set the default options */ + setROE(ROE::OPEN_FIRE_WEAPON_FREE, force); + setReactionToThreat(ReactionToThreat::EVADE_FIRE, force); + setEmissionsCountermeasures(EmissionCountermeasure::DEFEND, force); + strcpy_s(TACAN.callsign, 4, "TKR"); + setTACAN(TACAN, force); + setRadio(radio, force); + setEPLRS(EPLRS, force); + setGeneralSettings(generalSettings, force); } void Unit::runAILoop() { @@ -107,8 +100,7 @@ void Unit::updateExportData(json::value json, double dt) if (dt > 0) setSpeed(getSpeed() * 0.95 + (dist / dt) * 0.05); } - oldPosition = position; - + if (json.has_string_field(L"Name")) setName(to_string(json[L"Name"])); if (json.has_string_field(L"UnitName")) @@ -136,53 +128,63 @@ void Unit::updateExportData(json::value json, double dt) /* All units which contain the name "Olympus" are automatically under AI control */ if (getUnitName().find("Olympus") != string::npos) setControlled(true); + + oldPosition = position; } void Unit::updateMissionData(json::value json) { - if (json.has_number_field(L"fuel")) - setFuel(short(json[L"fuel"].as_number().to_double() * 100)); - - if (json.has_object_field(L"ammo")) { - vector ammo; - for (auto const& el : json[L"ammo"].as_object()) { - DataTypes::Ammo ammoItem; - auto ammoJson = el.second; - ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32(); - ammoItem.name = to_string(ammoJson[L"desc"][L"displayName"]); - ammoItem.guidance = ammoJson[L"desc"][L"guidance"].as_number().to_uint32(); - ammoItem.category = ammoJson[L"desc"][L"category"].as_number().to_uint32(); - ammoItem.missileCategory = ammoJson[L"desc"][L"missileCategory"].as_number().to_uint32(); - ammo.push_back(ammoItem); - } - setAmmo(ammo); - } - - if (json.has_object_field(L"contacts")) { - vector contacts; - for (auto const& el : json[L"ammo"].as_object()) { - DataTypes::Contact contactItem; - auto contactJson = el.second; - contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32(); - - string detectionMethod = to_string(contactJson[L"detectionMethod"]); - if (detectionMethod.compare("VISUAL")) contactItem.detectionMethod = 1; - else if (detectionMethod.compare("OPTIC")) contactItem.detectionMethod = 2; - else if (detectionMethod.compare("RADAR")) contactItem.detectionMethod = 4; - else if (detectionMethod.compare("IRST")) contactItem.detectionMethod = 8; - else if (detectionMethod.compare("RWR")) contactItem.detectionMethod = 16; - else if (detectionMethod.compare("DLINK")) contactItem.detectionMethod = 32; - contacts.push_back(contactItem); - } - setContacts(contacts); - } + //if (json.has_number_field(L"fuel")) + // setFuel(short(json[L"fuel"].as_number().to_double() * 100)); + // + //if (json.has_object_field(L"ammo")) { + // vector ammo; + // for (auto const& el : json[L"ammo"].as_object()) { + // DataTypes::Ammo ammoItem; + // auto ammoJson = el.second; + // ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32(); + // ammoItem.name = to_string(ammoJson[L"desc"][L"displayName"]); + // ammoItem.guidance = ammoJson[L"desc"][L"guidance"].as_number().to_uint32(); + // ammoItem.category = ammoJson[L"desc"][L"category"].as_number().to_uint32(); + // ammoItem.missileCategory = ammoJson[L"desc"][L"missileCategory"].as_number().to_uint32(); + // ammo.push_back(ammoItem); + // } + // setAmmo(ammo); + //} + // + //if (json.has_object_field(L"contacts")) { + // vector contacts; + // for (auto const& el : json[L"ammo"].as_object()) { + // DataTypes::Contact contactItem; + // auto contactJson = el.second; + // contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32(); + // + // string detectionMethod = to_string(contactJson[L"detectionMethod"]); + // if (detectionMethod.compare("VISUAL")) contactItem.detectionMethod = 1; + // else if (detectionMethod.compare("OPTIC")) contactItem.detectionMethod = 2; + // else if (detectionMethod.compare("RADAR")) contactItem.detectionMethod = 4; + // else if (detectionMethod.compare("IRST")) contactItem.detectionMethod = 8; + // else if (detectionMethod.compare("RWR")) contactItem.detectionMethod = 16; + // else if (detectionMethod.compare("DLINK")) contactItem.detectionMethod = 32; + // contacts.push_back(contactItem); + // } + // setContacts(contacts); + //} if (json.has_boolean_field(L"hasTask")) setHasTask(json[L"hasTask"].as_bool()); } -DataTypes::DataPacket Unit::getDataPacket() +unsigned int Unit::getUpdateData(char* &data) { + /* Reserve data for: + 1) DataPacket; + 2) Active path; + */ + data = (char*)malloc(sizeof(DataTypes::DataPacket) + activePath.size() * sizeof(Coords)); + unsigned int offset = 0; + + /* Prepare the data packet and copy it to memory */ unsigned int bitmask = 0; bitmask |= alive << 0; bitmask |= human << 1; @@ -192,16 +194,16 @@ DataTypes::DataPacket Unit::getDataPacket() bitmask |= desiredSpeedType << 17; bitmask |= isTanker << 18; bitmask |= isAWACS << 19; - bitmask |= onOff << 19; - bitmask |= followRoads << 19; - bitmask |= EPLRS << 20; - bitmask |= generalSettings.prohibitAA << 21; - bitmask |= generalSettings.prohibitAfterburner << 22; - bitmask |= generalSettings.prohibitAG << 23; - bitmask |= generalSettings.prohibitAirWpn << 24; - bitmask |= generalSettings.prohibitJettison << 25; + bitmask |= onOff << 20; + bitmask |= followRoads << 21; + bitmask |= EPLRS << 22; + bitmask |= generalSettings.prohibitAA << 23; + bitmask |= generalSettings.prohibitAfterburner << 24; + bitmask |= generalSettings.prohibitAG << 25; + bitmask |= generalSettings.prohibitAirWpn << 26; + bitmask |= generalSettings.prohibitJettison << 27; - DataTypes::DataPacket datapacket{ + DataTypes::DataPacket dataPacket{ ID, bitmask, position, @@ -217,37 +219,15 @@ DataTypes::DataPacket Unit::getDataPacket() reactionToThreat, emissionsCountermeasures, TACAN, - radio + radio, + activePath.size(), + name.size(), + unitName.size(), + groupName.size(), + getCategory().size(), + coalition.size() }; - return datapacket; -} - -string Unit::getData(bool refresh) -{ - /* Prepare the data in a stringstream */ - stringstream ss; - - /* Reserve data for: - 1) DataPacket; - 2) Length of active path; - 3) Active path; - */ - char* data = (char*)malloc(sizeof(DataTypes::DataPacket) + sizeof(unsigned short) + activePath.size() * sizeof(Coords)); - unsigned int offset = 0; - - /* Prepare the data packet and copy it to memory */ - DataTypes::DataPacket dataPacket; - /* If the unit is in a group, get the datapacket from the group leader and only replace the position, speed and heading */ - if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) { - dataPacket = unitsManager->getGroupLeader(this)->getDataPacket(); - dataPacket.position = position; - dataPacket.speed = speed; - dataPacket.heading = heading; - } - else - dataPacket = getDataPacket(); - memcpy(data + offset, &dataPacket, sizeof(dataPacket)); offset += sizeof(dataPacket); @@ -255,14 +235,29 @@ string Unit::getData(bool refresh) std::vector path; for (const Coords& c : activePath) path.push_back(c); - unsigned short pathLength = activePath.size(); - memcpy(data + offset, &pathLength, sizeof(unsigned short)); - offset += sizeof(unsigned short); memcpy(data + offset, &path, activePath.size() * sizeof(Coords)); - offset += sizeof(unsigned short); + offset += activePath.size() * sizeof(Coords); - ss << to_base64(data, offset); + return offset; +} + +void Unit::getData(stringstream &ss, bool refresh) +{ + char* data; + unsigned int size = getUpdateData(data); + + /* Prepare the data packet and copy it to memory */ + /* If the unit is in a group, get the update data from the group leader and only replace the position, speed and heading */ + if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) { + DataTypes::DataPacket* p = (DataTypes::DataPacket*)data; + p->position = position; + p->speed = speed; + p->heading = heading; + } + + ss.write(data, size); + delete data; if (refresh) { ss << name; @@ -271,8 +266,6 @@ string Unit::getData(bool refresh) ss << getCategory(); ss << coalition; } - - return ss.str(); } void Unit::setActivePath(list newPath) @@ -397,7 +390,7 @@ void Unit::setROE(unsigned char newROE, bool force) { if (ROE != newROE || force) { ROE = newROE; - Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::ROE, ROE)); + Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::ROE, static_cast(ROE))); scheduler->appendCommand(command); } } @@ -407,7 +400,7 @@ void Unit::setReactionToThreat(unsigned char newReactionToThreat, bool force) if (reactionToThreat != newReactionToThreat || force) { reactionToThreat = newReactionToThreat; - Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, reactionToThreat)); + Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, static_cast(reactionToThreat))); scheduler->appendCommand(command); } } diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index 1c6b33aa..66a976b5 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -10,6 +10,9 @@ #include "commands.h" #include "scheduler.h" +#include "base64.hpp" +using namespace base64; + extern Scheduler* scheduler; UnitsManager::UnitsManager(lua_State* L) @@ -155,8 +158,8 @@ string UnitsManager::getUnitData(bool refresh) { stringstream ss; for (auto const& p : units) - ss << p.second->getData(refresh); - return ss.str(); + p.second->getData(ss, refresh); + return to_base64(ss.str()); } void UnitsManager::deleteUnit(unsigned int ID, bool explosion) diff --git a/src/utils/src/utils.cpp b/src/utils/src/utils.cpp index 62299d2d..f6b0a556 100644 --- a/src/utils/src/utils.cpp +++ b/src/utils/src/utils.cpp @@ -20,7 +20,7 @@ std::wstring to_wstring(const std::string& str) return wstrTo; } -std::string to_string(json::value value) { +std::string to_string(json::value& value) { return to_string(value.as_string()); } diff --git a/third-party/base64/include/base64.hpp b/third-party/base64/include/base64.hpp index 5590e7a8..9e3d1565 100644 --- a/third-party/base64/include/base64.hpp +++ b/third-party/base64/include/base64.hpp @@ -12,10 +12,7 @@ namespace base64 { "0123456789+/"; return base64_chars; } - inline std::string to_base64(std::string const& data) { - return to_base64(data.c_str(), data.length()); - } - + inline std::string to_base64(const char* data, size_t size) { int counter = 0; uint32_t bit_stream = 0; @@ -52,6 +49,10 @@ namespace base64 { return encoded; } + inline std::string to_base64(std::string const& data) { + return to_base64(data.c_str(), data.length()); + } + inline std::string from_base64(std::string const& data) { int counter = 0; uint32_t bit_stream = 0;