/** * @RDE_HEADER@ * Handler for all Effect Cluster * $Id$ * $HeadURL$ * $Author$ * Created by Norbert Thoden, SLSE, 2023-10 */ #include "handler_effect_cluster.h" #include "handler_charge_administration.h" #include "object_effect_cluster.h" #include "settings.h" #include #include #include #include #include #include // for TrajectoryGenerator #include #include #include namespace AreaEffectSimulation { HandlerEffectCluster::HandlerEffectCluster(MainHandler *mainHandler) : HandlerBaseTmpl(mainHandler, QStringLiteral("EffectCluster"), s_areaCount[AesType::AtEffectCluster], s_areaCountCentral[AesType::AtEffectCluster]) , Interface::Fts::EffectClusterModifyRequestHandler(mainHandler->dds()->fts()) , Interface::Fts::EffectClusterModifyActorRequestHandler(mainHandler->dds()->fts()) , Interface::Fts::EffectClusterSingleEffectOnlyModifyRequestHandler(mainHandler->dds()->fts()) , Interface::Fts::EffectClusterOcModifyRequestHandler(mainHandler->dds()->fts()) , Interface::Fts::EffectClusterRemoveRequestHandler(mainHandler->dds()->fts()) , Interface::Fts::EffectClusterPauseResumeRequestHandler(mainHandler->dds()->fts()) , Interface::Fts::EffectClusterFireHaltRequestHandler(mainHandler->dds()->fts()) , Interface::Fts::TrajectoryAdlerModifyRequestHandler(m_mainHandler->dds()->fts()) , Interface::Fts::EffectClusterReleaseReceivedRequestHandler(mainHandler->dds()->fts()) , Interface::Fts::AcknowledgeExternalAesItemRequestHandler(m_mainHandler->dds()->fts()) , Interface::Fts::SetAesItemEffectorStateRequestHandler(m_mainHandler->dds()->fts()) { } bool HandlerEffectCluster::init() { // create explosion cluster objects QVector effectorVec; bool bRet = true; if (!m_mainHandler->db()->select(effectorVec)) { ERROR_LOG(m_logger) << "Error Reading Effector Data from Database"; return false; } auto cb = [this, &bRet, effectorVec](const Database::AesEffectCluster &data) { DEBUG_LOG(m_logger) << "my vector has:" << effectorVec.size() << " my aesData " << data.id; // Add EffectorData to Object QVector effectorDataVec; for (const auto &effectorData : effectorVec) { DEBUG_LOG(m_logger) << "INIT TRY ADD effector with entity ID " << effectorData.entityId << " with aesData " << data.id; if (effectorData.entityId == data.id) effectorDataVec.append(effectorData); } ObjectEffectCluster *ifs(new ObjectEffectCluster(m_mainHandler, this)); connect(ifs, &ObjectEffectCluster::effectStateChanged, this, &HandlerEffectCluster::onEffectStateChanged); auto data_copy = data; if (data.impactCount == 0) { for (const auto &effector : effectorDataVec) { data_copy.impactCount += effector.chargeAmount; } } Core::FunctionReplyObject ret = ifs->createObject(data_copy, effectorDataVec); if (ret.error()) { delete ifs; ERROR_LOG(m_logger) << ret(); bRet = false; return true; } // Context NASAT: check via chargeTypeId: const bool bChargeIsAirSupport = m_mainHandler->dds()->pdmDtc()->gearClassFromChargeTypeId(ifs->data().chargeTypeId) == ProjectDataManagement::GearClass::AirSupport; ifs->setAirSupportSource(bChargeIsAirSupport); // Charge Effects are alwazs Central! if (bChargeIsAirSupport) { // Note: s_currentAirSupportCount is updated via overloaded recountActiveAreas if (s_currentAirSupportCount >= s_maxAirSupportCount) { ERROR_LOG(m_logger) << "More active air supports in database than allowed - ignoring the rest!"; ifs->removeObject(); delete ifs; return true; } } if (ifs->data().isCentral) ++m_areaCountCentral; m_objectMap.insert(ifs->id(), ifs); connect(ifs, &ObjectEffectCluster::newAiv, this, &HandlerEffectCluster::newAiv); connect(ifs, &ObjectEffectCluster::removeAiv, this, &HandlerEffectCluster::removeAiv); const QJsonObject propertiesObject = QJsonDocument::fromJson(QByteArray::fromStdString(ifs->data().properties.toStdString())).object(); m_referenceToClusterId.insert(propertiesObject[AdlerJson::JsonKeys::FEUERKAMPFREFERENZ()].toString(), ifs->id()); return true; }; if (!m_mainHandler->db()->select(cb) || !bRet) { ERROR_LOG(m_logger) << "Error reading data from database!"; return false; } DEBUG_LOG(m_logger) << "m_exeObjMap.size() = " << m_objectMap.size(); DEBUG_LOG(m_logger) << "m_areaCountCentral = " << m_areaCountCentral; // init TAB auto dtcTab = m_mainHandler->dds().get()->tabDtc().get(); if (!dtcTab->isEX_CeaseFiringEnabled()) dtcTab->enableEX_CeaseFiring(); if (!dtcTab->isEX_FireEnabled()) dtcTab->enableEX_Fire(); connect(dtcTab, &TrainingAreaBase::Interface::Dtc::eXFireChanged, this, &HandlerEffectCluster::onFireCommandReceived); connect(dtcTab, &TrainingAreaBase::Interface::Dtc::eXCeaseFiringChanged, this, &HandlerEffectCluster::onFireHoldReceived); return true; } const ObjectEffectCluster *HandlerEffectCluster::getEffectClusterObj(int id) { return m_objectMap.value(id, nullptr); } void HandlerEffectCluster::onFireCommandReceived(const TrainingAreaBase::EX_Fire &fire, DataModification modtype) { // get fireReference if (modtype == DataModification::eRemoved) return; DEBUG_LOG(m_logger) << "got EX_Fire for ref: " << fire.reference() << " internal mapsize " << m_referenceToClusterId.size(); auto *ifs = m_objectMap.value(m_referenceToClusterId.value(QString::fromStdString(fire.reference()))); if (!ifs) return; ifs->sendFireCommand(AdlerJson::waffensystemkennerMap.key(fire.effectorId())); } void HandlerEffectCluster::onFireHoldReceived(const TrainingAreaBase::EX_CeaseFiring &ceaseFire, DataModification modType) { Q_UNUSED(ceaseFire); if (modType == DataModification::eRemoved) return; } int HandlerEffectCluster::totalAreasCentral() const { return s_areaCountCentral[AesType::AtEffectCluster]; } // Appends /* static void updateProperties(Database::AesEffectCluster &dst, QVector &dstEffectorVector) { QJsonObject propertiesObject = QJsonDocument::fromJson(QByteArray::fromStdString(dst.properties.toStdString())).object(); propertiesObject[QStringLiteral("EffectorData")] = dstEffectorVector.size(); QJsonDocument doc(propertiesObject); dst.properties = QString::fromStdString(doc.toJson(QJsonDocument::Compact).toStdString()); } */ static void fillData(const EffectClusterModifyRequest &src, Database::AesEffectCluster &dst, QVector &dstEffectorVector, MainHandler *mainHandler) { if (src.id() < 0) // is the id given as negative? set id -> used by OC-Laptop dst.id = src.id(); dst.name = QString::fromStdString(src.name()); dst.element = QString::fromStdString(src.element()); dst.impactPosition.rx() = src.area().cornerBottomLeft().lon(); dst.impactPosition.ry() = src.area().cornerBottomLeft().lat(); dst.impactLength = src.area().height(); dst.impactWidth = src.area().width(); dst.rotationAngle = src.area().rotation(); const bool bIsPointSimilar = (src.explosionGeometry() == AreaEffectSimulation::AesExplosionGeometry_def::EgAimPoint) || (src.explosionGeometry() == AreaEffectSimulation::AesExplosionGeometry_def::EgPoint); dstEffectorVector.reserve(src.effectors().size()); Database::AesEffectorData dstEffector; int localImpactCount = 0; int firstChargeTypeId = 0; for (const auto &effector : src.effectors()) { // copy from Source Request. dstEffector.angleOfFall = effector.angleOfFall(); dstEffector.chargeAmount = effector.chargeAmount(); dstEffector.chargeTypeId = effector.chargeStruct().chargeTypeId(); dstEffector.entityId = src.id(); dstEffector.flightTime = effector.flightTime(); dstEffector.igniterTypeId = effector.chargeStruct().igniterTypeId(); dstEffector.igniterFunctionId = effector.chargeStruct().igniterFunctionId(); dstEffector.interval = effector.interval(); dstEffector.mhpPosition.rx() = effector.mhpPosition().lon(); dstEffector.mhpPosition.ry() = effector.mhpPosition().lat(); dstEffector.mhpPosition.rz() = effector.mhpPosition().z(); // [E01562-399] dstEffector.properties = QString::fromStdString(effector.properties()); // FillScatterData if (effector.mhpScatterLenght() > 0.0) dstEffector.mhpScatterLenght = effector.mhpScatterLenght(); else dstEffector.mhpScatterLenght = Settings::getScatterLenghtDefault(); dstEffector.mhpScatterLengthDetonationHeight = effector.mhpScatterLengthDetonationHeight(); if (effector.mhpScatterWidth() > 0.0) dstEffector.mhpScatterWidth = effector.mhpScatterWidth(); else dstEffector.mhpScatterWidth = Settings::getScatterWidthDefault(); dstEffector.mhpScatterWidthDetonationHeight = effector.mhpScatterWidthDetonationHeight(); // Fill rest dstEffector.participantId = effector.participantId(); dstEffector.peakHeight = effector.peakHeight(); dstEffector.trajectory = QString::fromStdString(TrajectoryRelative::toString(effector.trajectoryRelative())); // sanity stuff localImpactCount = localImpactCount + dstEffector.chargeAmount; if (firstChargeTypeId <= 0) firstChargeTypeId = effector.chargeStruct().chargeTypeId(); // check if it's a single fire if (bIsPointSimilar && ((src.area().width() <= 0) || (src.area().height() <= 0))) { const Core::GeoPosition2D &cornerBottomLeft = src.area().cornerBottomLeft(); const double impactDiameter = mainHandler->dds()->pdmDtc()->impactRadius(effector.chargeStruct().chargeTypeId(), effector.chargeStruct().igniterTypeId(), effector.chargeStruct().igniterFunctionId()) * 2.0; // GUEZE-2634 const QPointF bottomLeft(Polygons::centerPoint2BottomLeft(QPointF(cornerBottomLeft.lon(), cornerBottomLeft.lat()), impactDiameter, impactDiameter, 0)); dstEffector.mhpPosition.rx() = bottomLeft.x(); dstEffector.mhpPosition.ry() = bottomLeft.y(); dstEffector.mhpScatterLenght = impactDiameter; dstEffector.mhpScatterWidth = impactDiameter; } dstEffectorVector.append(dstEffector); } // Save the mhpCountRequest for later dst.mhpCountRequest = dstEffectorVector.size(); dst.explosionGeometryId = src.explosionGeometry(); dst.tla = src.tla(); dst.effectStart = src.effectStart().is_set() && src.effectStart().get() > 0 ? QDateTime::fromMSecsSinceEpoch(src.effectStart().get()) : QDateTime(); // Effect Duration might be set and will be used for the AutoFire // -- Module Duration if (src.effectDuration()) dst.effectDuration = src.effectDuration(); else { // Sometimes there is no effectDuration // We use a more or less uniqe duration for now dst.effectDuration = DEFAULT_EFFECT_DURATION; } dst.effectSourceId = src.effectSourceId(); dst.firePurposeId = src.firePurposeId(); dst.partyId = src.partyId().is_set() ? src.partyId().get() : 0; dst.participantId = src.participantId().is_set() ? src.participantId().get() : -1; // -1 -> avoid trajectory dst.altitude = -1; dst.effectMarkingInTa = src.effectMarkingInTa(); dst.externalId = src.externalId().is_set() ? src.externalId().get() : 0; dst.properties = QString::fromStdString(src.properties()); dst.esmParticipantIds = LtsUtils::IntArray(src.esmParticipantIds()); // some modules: // if (dst.impactCount == 0 && localImpactCount > 0) dst.impactCount = localImpactCount; if (dst.chargeTypeId == 0 && firstChargeTypeId > 0) dst.chargeTypeId = firstChargeTypeId; // update Properties // updateProperties(dst, dstEffectorVector); } void HandlerEffectCluster::onEffectClusterModifyRequest(const rti::core::SampleIdentity &cookie, const EffectClusterModifyRequest &request) { // DEBUG_LOG(m_logger) << "************************************ start onEffectClusterModifyRequest: " << request; Core::FunctionReplyObject ret = effectClusterModifyRequest(cookie, request); if (ret.error()) WARNING_LOG(m_logger) << ret() << ", effectStart :" << request.effectStart(); ret.setAdditionalInfo(request.properties().c_str()); // DEBUG_LOG(m_logger) << "************************************ SEND REPLY FOR onEffectClusterModifyRequest: " << request; m_mainHandler->dds()->fts()->effectClusterModifyReply(cookie, ret); } void HandlerEffectCluster::onEffectClusterModifyActorRequest(const rti::core::SampleIdentity &cookie, const EffectClusterModifyActorRequest &request) { // DEBUG_LOG(m_logger) << "************************************ start onEffectClusterModifyActorRequest: " << request; Core::FunctionReplyObject ret; ObjectEffectCluster *ifs = getObjectFromMap(m_objectMap, request.aesId(), ret); if (!ifs) { m_mainHandler->dds()->fts()->effectClusterModifyActorReply(cookie, ret); return; } ret = ifs->effectClusterModifyActor(request); if (ret.error()) ERROR_LOG(m_logger) << ret(); DEBUG_LOG(m_logger) << "************************************ SEND REPLY FOR onEffectClusterModifyActorRequest: " << request; m_mainHandler->dds()->fts()->effectClusterModifyActorReply(cookie, ret); } void HandlerEffectCluster::onEffectClusterSingleEffectOnlyModifyRequest(const rti::core::SampleIdentity &cookie, const EffectClusterSingleEffectOnlyRequest &request) { DEBUG_LOG(m_logger) << "************************************ onEffectClusterSingleEffectModifyRequest: " << request; // generalize the specific request to the common one. (similar an 'upcast') // in other words: EffectClusterSingleEffectOnlyRequest -> EffectClusterModifyRequest: EffectClusterModifyRequest ecCommonModifyRequest; /* ecCommonModifyRequest.id(request.id()); ecCommonModifyRequest.externalId(request.externalId()); ecCommonModifyRequest.effectStart(request.effectStart()); ecCommonModifyRequest.effectDuration(1000); // charge and geometry: ecCommonModifyRequest.explosionGeometry(AreaEffectSimulation::AesExplosionGeometry_def::EgPoint); // ecCommonModifyRequest.zog(0); ecCommonModifyRequest.fuseType(ProjectDataManagement::FuseType_def::FtImpact); ecCommonModifyRequest.chargeTypeId(request.chargeTypeId()); // height, width are <=0 -> area will be adjusted using impactRadius of charge: ecCommonModifyRequest.area().cornerBottomLeft(request.impactPosition()); ecCommonModifyRequest.area().height(0); ecCommonModifyRequest.area().width(0); ecCommonModifyRequest.mhpCount(1); ecCommonModifyRequest.impactCount(1); ecCommonModifyRequest.name(request.name()); // ZpNummer ecCommonModifyRequest.element(request.element()); // element ecCommonModifyRequest.partyId(request.partyId()); ecCommonModifyRequest.participantId(-1); // -1 -> avoid trajectory */ Core::FunctionReplyObject ret = effectClusterModifyRequest(cookie, ecCommonModifyRequest); if (ret.error()) WARNING_LOG(m_logger) << ret(); m_mainHandler->dds()->fts()->effectClusterSingleEffectOnlyModifyReply(cookie, ret); } void HandlerEffectCluster::onEffectClusterOcModifyRequest(const rti::core::SampleIdentity &cookie, const EffectClusterOcModifyRequest &request) { DEBUG_LOG(m_logger) << "************************************ onEffectClusterOcModifyRequest: " << request; // generalize the specific request to the common one. (similar an 'upcast') // in other words: EffectClusterSingleEffectOnlyRequest -> EffectClusterModifyRequest: EffectClusterModifyRequest ecCommonModifyRequest; ecCommonModifyRequest.id(request.id()); // ecCommonModifyRequest.externalId(request.externalId()); ecCommonModifyRequest.effectStart(request.effectStart()); ecCommonModifyRequest.effectDuration(request.effectDuration()); // charge and geometry: ecCommonModifyRequest.area(request.area()); int impactCount = 0; for (const auto &effector : request.effectors()) effector.chargeAmount(); if (impactCount == 1) ecCommonModifyRequest.explosionGeometry(AreaEffectSimulation::AesExplosionGeometry_def::EgPoint); else ecCommonModifyRequest.explosionGeometry(AreaEffectSimulation::AesExplosionGeometry_def::EgArea); // ecCommonModifyRequest.zog(0); ecCommonModifyRequest.effectors(request.effectors()); ecCommonModifyRequest.name(request.name()); // ZpNummer ecCommonModifyRequest.element(ecCommonModifyRequest.element()); // element ecCommonModifyRequest.partyId(request.partyId()); // ecCommonModifyRequest.participantId(-1); // -1 -> avoid trajectory // ToDo: no trajectory needed, but how can we transfer the rotation of MhpAreas? // ToDo: no trajectory needed, but how can we transfer the rotation of MhpAreas? // ToDo: no trajectory needed, but how can we transfer the rotation of MhpAreas? // ToDo: how to transfer request.mhpRotation?? Core::FunctionReplyObject ret = effectClusterModifyRequest(cookie, ecCommonModifyRequest); if (ret.error()) WARNING_LOG(m_logger) << ret(); m_mainHandler->dds()->fts()->effectClusterOcModifyReply(cookie, ret); } Core::FunctionReplyObject HandlerEffectCluster::effectClusterCreateRequest(const EffectClusterModifyRequest &request) { // HLA Interface is using externalId... // external (externalId>0) -> no partyId required // external (externalId==0) -> partyId required // We keep the old charge behavior // and also ignore it from ADLER? const int externalIdValue = request.externalId().is_set() ? request.externalId().get() : 0; if (request.effectors().size() == 0) return Core::FunctionReplyObject(tr_lts("dds", "Invalid Effector Count")); // We assume that there is no mixture of GeraClasses between charges const ProjectDataManagement::GearClass gearClass = m_mainHandler->gearClassOfChargeTypeId(request.effectors().at(0).chargeStruct().chargeTypeId()); const bool bDoClaimPlannedCharge = (request.id() == 0) && (externalIdValue == 0) && (gearClass != ProjectDataManagement::GearClass_def::AirSupport //&& (m_mainHandler->dds()->pdmDtc()->isAdlerChargeType(request.chargeTypeId())) ) && 1 == 2 && (request.effectSourceId() == ProjectDataManagement::EffectSource::ES_Internal); if (bDoClaimPlannedCharge) { // get full charge amount per charge QMap mapChargeTypeAmount; // QMp for (const auto &effector : request.effectors()) mapChargeTypeAmount.insert(effector.chargeStruct().chargeTypeId(), mapChargeTypeAmount.value(effector.chargeStruct().chargeTypeId()) + effector.chargeAmount()); QMapIterator it(mapChargeTypeAmount); while (it.hasNext()) { it.next(); DEBUG_LOG(m_logger) << "Got new charges" << it.key() << " " << it.value(); int chargeNeeded = it.value(); if (chargeNeeded <= 0) return Core::FunctionReplyObject(tr_lts("dds", "Invalid impact count (<= 0).")); if (request.id() == 0) { int chargeLeft = m_mainHandler->chargeAdministrationHandler()->availableCharge(it.key(), request.partyId().get()); QString chargeName = QString::fromStdString(m_mainHandler->dds()->pdmDtc()->chargeType(it.key()).name()); QString fireUnitName; if (request.participantId().is_set()) fireUnitName = QString::fromStdString(m_mainHandler->dds()->ppmDtc()->participant(request.participantId().get()).designator()); if (chargeNeeded > chargeLeft) return Core::FunctionReplyObject(tr_lts("dds", "Invalid impact count. Fire Unit '%1' has %2 charges (%3), but for this cluster %4 are needed.")) .arg(fireUnitName) .arg((chargeLeft)) .arg(chargeName) .arg(chargeNeeded); } } } Database::AesEffectCluster newData; QVector newEffectorDataVec; const QJsonObject propertiesObject = QJsonDocument::fromJson(QByteArray::fromStdString(request.properties())).object(); fillData(request, newData, newEffectorDataVec, m_mainHandler); DEBUG_LOG(m_logger) << "AUTO newData " << newData.impactCount; if (!request.properties().empty() && (!request.effectStart().is_set() || request.effectStart().get() == 0)) { newData.isUsingEffectStateReceived = propertiesObject.value(QStringLiteral("useReceived")).toBool(); } ObjectEffectCluster *ifs(new ObjectEffectCluster(m_mainHandler, this)); connect(ifs, &ObjectEffectCluster::effectStateChanged, this, &HandlerEffectCluster::onEffectStateChanged); // Context NASAT: check via chargeTypeId: const bool bChargeIsAirSupport = gearClass == ProjectDataManagement::GearClass::AirSupport; ifs->setAirSupportSource(bChargeIsAirSupport); // Now we always need to send fire ready, never without newData.effectorState = AesEffectorState::EsNotReadyToFire; const Core::FunctionReplyObject ret = ifs->createObject(newData, newEffectorDataVec); if (ret.error()) { delete ifs; return ret; } DEBUG_LOG(m_logger) << "New EffectCluster"; if (gearClass == ProjectDataManagement::GearClass::AirSupport) { if (s_currentAirSupportCount >= s_maxAirSupportCount) { ifs->removeObject(); // Not finally deleted from db after this,Fallback if DB deletion is not working correct ifs->removeObjectFromDb(); delete ifs; return Core::FunctionReplyObject(1, tr_lts("dds", "Too many active air supports.")); } } else if (totalAreasCentral() >= (s_maxActiveAreasCentral[AesType::AtEffectCluster]) + (s_currentAirSupportCount) && request.effectStart().is_set()) { ifs->removeObject(); // Not finally deleted from db after this,Fallback if DB deletion is not working correct ifs->removeObjectFromDb(); delete ifs; return Core::FunctionReplyObject(1, tr_lts("dds", "Too many active central effect cluster.")); } ++m_areaCountCentral; DEBUG_LOG(m_logger) << "m_areaCountCentral " << m_areaCountCentral; if (ifs->data().name == propertiesObject[AdlerJson::JsonKeys::FEUERKAMPFREFERENZ()].toString()) { // Update name ifs->updateName(QStringLiteral("ZH-Nr. %1").arg(ifs->data().id)); } m_objectMap.insert(ifs->id(), ifs); connect(ifs, &ObjectEffectCluster::newAiv, this, &HandlerEffectCluster::newAiv); connect(ifs, &ObjectEffectCluster::removeAiv, this, &HandlerEffectCluster::removeAiv); m_referenceToClusterId.insert(propertiesObject[AdlerJson::JsonKeys::FEUERKAMPFREFERENZ()].toString(), ifs->id()); return ret; } Core::FunctionReplyObject HandlerEffectCluster::effectClusterModifyRequest(const rti::core::SampleIdentity &cookie, const EffectClusterModifyRequest &request) { // // externalId maybe set be HLA interface: // // from external (externalIdValue>0) -> no partyId required // // from internal (externalIdValue==0) -> partyId required // const int externalIdValue = request.externalId().is_set() ? request.externalId().get() : 0; // if (externalIdValue == 0 && !request.partyId().is_set()) // return Core::FunctionReplyObject(tr_lts("dds", "Explosion areas need a party id.")); DEBUG_LOG(m_logger) << "request.effectStart :" << request.effectStart() << ", effectDuration :" << request.effectDuration() // << ", flightDuration :" << request.flightDuration() // << ", tapTime :" << request.tapTime() << ", properties: " << request.properties(); Core::FunctionReplyObject ret; if (request.id() == 0 || ((request.id() < 0) && !getObjectFromMap(m_objectMap, std::abs(request.id()), ret))) return effectClusterCreateRequest(request); ObjectEffectCluster *ifs = getObjectFromMap(m_objectMap, std::abs(request.id()), ret); if (!ifs) return ret; ifs->setCookie(cookie); AreaEffectSimulation::Database::AesEffectCluster aea(ifs->data()); QVector effectorDataVec; fillData(request, aea, effectorDataVec, m_mainHandler); const QJsonObject propertiesObject = QJsonDocument::fromJson(QByteArray::fromStdString(aea.properties.toStdString())).object(); // Context NASAT: check via chargeTypeId: const ProjectDataManagement::GearClass gc = m_mainHandler->gearClassOfChargeTypeId(request.effectors().at(0).chargeStruct().chargeTypeId()); const bool bChargeIsAirSupport = (gc == ProjectDataManagement::GearClass::AirSupport); ifs->setAirSupportSource(bChargeIsAirSupport); // For counting, only Elements being at least prepared are important: if (request.effectStart().is_set() && ifs->isObjectModifiable()) // ToDo: check { // Calculate new polygon here. Not nice, though auto poly = Polygons::effectClusterArea(aea); std::vector _form; ObjectBase::ddsVecFromPoly(poly, _form); bool wasCentral = ifs->data().isCentral; if (bChargeIsAirSupport) { if (s_currentAirSupportCount >= s_maxAirSupportCount) return Core::FunctionReplyObject(1, tr_lts("dds", "Too many active air supports.")); } else { if (totalAreasCentral() >= (s_maxActiveAreasCentral[AesType::AtEffectCluster] + (s_currentAirSupportCount)) && request.effectStart().is_set() && ((wasCentral && !ifs->isActivationTimeSet()) || !wasCentral)) return Core::FunctionReplyObject(1, tr_lts("dds", "Too many active central effect clusters.")); } // For counting, only Elements being at least prepared are important: } if (request.id() < 0 && checkStateForSfm(ifs)) { DEBUG_LOG(m_logger) << "Overwrite Effect State for OC"; aea.effectStateId = AesEffectState::EsPrepared; } DEBUG_LOG(m_logger) << "request.properties()" << request.properties() << " aea.properties=" << aea.properties; // ifs->setEffectors(effectorDataVec); // updateProperties(aea, effectorDataVec); <- done in fillData // update internalMap QString referenceToDelete; for (const auto &key : m_referenceToClusterId.keys()) { if (m_referenceToClusterId.value(key) == ifs->id() && key != ifs->name()) referenceToDelete = key; } if (!referenceToDelete.isEmpty()) { m_referenceToClusterId.remove(referenceToDelete); m_referenceToClusterId.insert(propertiesObject[AdlerJson::JsonKeys::FEUERKAMPFREFERENZ()].toString(), ifs->id()); } if (aea.impactCount == 0) { for (const auto &effector : effectorDataVec) { aea.impactCount += effector.chargeAmount; } } return ifs->updateObject(aea, effectorDataVec); } void HandlerEffectCluster::recountActiveAreas() { s_currentAirSupportCount = 0; for (const auto *area : qAsConst(m_objectMap)) { if (area->isObjectInTrainingArea()) // meanwhile an ugly name, means EsPrepared, EsActivated, EsPaused { if (m_mainHandler->gearClassOfChargeTypeId(area->data().chargeTypeId) == ProjectDataManagement::GearClass::AirSupport) ++s_currentAirSupportCount; } } HandlerBaseTmpl::recountActiveAreas(); DEBUG_LOG(m_logger) << "m_areaCountCentral after Recount = " << m_areaCountCentral; DEBUG_LOG(m_logger) << "m_areaCount s_currentAirSupportCount after Recount = " << s_currentAirSupportCount; } void HandlerEffectCluster::onEffectClusterRemoveRequest(const rti::core::SampleIdentity &cookie, const Core::RemoveElementRequest &request) { DEBUG_LOG(m_logger) << "************************************ onEffectClusterRemoveRequest: " << request; m_mainHandler->dds()->fts()->effectClusterRemoveReply(cookie, removeReply(&DatabaseInterface::removeAesEffectCluster, request)); } void HandlerEffectCluster::onEffectClusterPauseResumeRequest(const rti::core::SampleIdentity &cookie, const EffectClusterPauseResumeRequest &request) { DEBUG_LOG(m_logger) << "onEffectClusterPauseResumeRequest" << request; Core::FunctionReplyObject ret; ObjectEffectCluster *ifs = getObjectFromMap(m_objectMap, request.id(), ret); if (!ifs) { m_mainHandler->dds()->fts()->effectClusterPauseResumeReply(cookie, ret); return; } ret = ifs->resumePause(request); if (ret.error()) ERROR_LOG(m_logger) << ret(); m_mainHandler->dds()->fts()->effectClusterPauseResumeReply(cookie, ret); } void HandlerEffectCluster::onEffectClusterFireHaltRequest(const rti::core::SampleIdentity &cookie, const EffectClusterFireHaltRequest &request) { DEBUG_LOG(m_logger) << "onEffectClusterFireHaltRequest" << request; Core::FunctionReplyObject ret; ProjectDataManagement::L22_FireHaltCategory categorie = request.fireHaltCategory(); QMap effectClusterPartyMap; for (const auto *area : qAsConst(m_objectMap)) { if (area->data().partyId == request.partyId()) { ObjectEffectCluster *ifs = getObjectFromMap(m_objectMap, area->data().id, ret); if (!ifs) { m_mainHandler->dds()->fts()->effectClusterFireHaltReply(cookie, ret); return; } effectClusterPartyMap.insert(area->data().id, ifs); } } switch (categorie.underlying()) { case ProjectDataManagement::L22_FireHaltCategory::A: // Feuer Halt Allgemein { // all control operations are halted for (auto *ifs : qAsConst(effectClusterPartyMap)) { EffectClusterPauseResumeRequest req; req.id(ifs->data().id); req.remainingDuration(ifs->data().effectDuration); req.resumeExecution(false); // false -> pause aes ret = ifs->resumePause(req); } break; } case ProjectDataManagement::L22_FireHaltCategory::R: // Feuer Halt Referenziert { // Ongoing combat operations are halted after comparing the extent of // the fire hold with the corresponding target coordinate and circular area. for (auto *ifs : qAsConst(effectClusterPartyMap)) { const double lonRad = request.longitude().is_set() ? request.longitude().get() * LtsCoordTrans::deg2rad() : 0.0; const double latRad = request.latitude().is_set() ? request.latitude().get() * LtsCoordTrans::deg2rad() : 0.0; const int radius = request.radius().is_set() ? request.radius().get() : 0; DEBUG_LOG(m_logger) << "[Feuer Halt Referenziert] " << "lonRad : " << lonRad << " " << "latRad : " << latRad << " " << "radius : " << radius; if (ifs->intersectWithCircle(lonRad, latRad, radius)) { DEBUG_LOG(m_logger) << "[Feuer Halt Referenziert ----- intersectWithCircle] " << "effectClusterId: " << ifs->data().id; EffectClusterPauseResumeRequest req; req.id(ifs->data().id); req.remainingDuration(ifs->data().effectDuration); req.resumeExecution(false); // false -> pause aes ifs->resumePause(req); } } break; } case ProjectDataManagement::L22_FireHaltCategory::E: // Feuer Einstellen { // Cease Fire (Category E) messages are issued when continuing an engagement is no longer viable. // Unlike "Check Fire" (Feuer Halt), any rounds already loaded in a Panzerhaubitze (PzH) are still fired, // followed by an EX-FQ(FN) report. Once the weapon system confirms execution, // the engagement is closed and the target area status is set to "expired." for (auto *ifs : qAsConst(effectClusterPartyMap)) { DEBUG_LOG(m_logger) << "[Feuer Einstellen ----- markAsExpired] " << "effectClusterId: " << ifs->data().id; ifs->markAsExpired(request.entladenSchuss().is_set() ? request.entladenSchuss().get() : false); } break; } case ProjectDataManagement::L22_FireHaltCategory::C: // Aufheben Feuer Halt Allgemein for (auto *ifs : qAsConst(effectClusterPartyMap)) { DEBUG_LOG(m_logger) << "[Aufheben Feuer Halt Allgemein] " << "effectClusterId: " << ifs->data().id; EffectClusterPauseResumeRequest req; req.remainingDuration(ifs->data().effectDuration); req.resumeExecution(true); // true -> resume aes ifs->resumePause(req); } break; case ProjectDataManagement::L22_FireHaltCategory::F: // Aufheben Feuer Halt Referenziert { for (auto *ifs : qAsConst(effectClusterPartyMap)) { const double lonRad = request.longitude().is_set() ? request.longitude().get() * LtsCoordTrans::deg2rad() : 0.0; // optional const double latRad = request.latitude().is_set() ? request.latitude().get() * LtsCoordTrans::deg2rad() : 0.0; // optional const int radius = request.radius().is_set() ? request.radius().get() : 0; DEBUG_LOG(m_logger) << "[Aufheben Feuer Halt Referenziert] " << "lonRad : " << lonRad << " " << "latRad : " << latRad << " " << "radius : " << radius; if (ifs->intersectWithCircle(lonRad, latRad, radius)) { DEBUG_LOG(m_logger) << "[Aufheben Feuer Halt Referenziert ----- intersectWithCircle] " << "effectClusterId: " << ifs->data().id; EffectClusterPauseResumeRequest req; req.id(ifs->data().id); req.remainingDuration(ifs->data().effectDuration); req.resumeExecution(true); // true -> resume aes ifs->resumePause(req); } } break; } default: INFO_LOG(m_logger) << "EX-FH missing or wrong FireHaltCategory" << categorie.underlying(); break; } if (ret.error()) ERROR_LOG(m_logger) << ret(); m_mainHandler->dds()->fts()->effectClusterFireHaltReply(cookie, ret); } void HandlerEffectCluster::onEffectClusterReleaseReceivedRequest(const rti::core::SampleIdentity &cookie, const EffectClusterReleaseReceivedRequest &request) { DEBUG_LOG(m_logger) << "onEffectClusterReleaseReceivedRequest" << request; Core::FunctionReplyObject ret(request.id()); ObjectEffectCluster *ifs = getObjectFromMap(m_objectMap, request.id(), ret); if (!ifs) { ERROR_LOG(m_logger) << ret(); m_mainHandler->dds()->fts()->effectClusterReleaseReceivedReply(cookie, ret); return; } if (!ifs->isUsingEffectStateReceived()) { ret = ret.setStr(tr_lts("dds", "EffectCluster with id %1 is not using EffectState Received")).arg(request.id()); ERROR_LOG(m_logger) << ret(); m_mainHandler->dds()->fts()->effectClusterReleaseReceivedReply(cookie, ret); return; } ifs->setUseEffectStateReceived(false); ifs->checkEnterNewState(); if (ret.error()) ERROR_LOG(m_logger) << ret(); m_mainHandler->dds()->fts()->effectClusterReleaseReceivedReply(cookie, ret); } void HandlerEffectCluster::onAcknowledgeExternalAesItemRequest(const rti::core::SampleIdentity &cookie, const AcknowledgeExternalAesItemRequest &request) { DEBUG_LOG(m_logger) << "HandlerEffectCluster::onAcknowledgeExternalAesItemRequest for aesItem" << request.aesId(); Core::FunctionReplyObject ret(request.aesId()); ObjectEffectCluster *ifs = getObjectFromMap(m_objectMap, request.aesId(), ret); if (!ifs) { ERROR_LOG(m_logger) << ret(); m_mainHandler->dds()->fts()->acknowledgeExternalAesItemReply(cookie, ret); return; } ifs->setUseEffectStateReceived(false); ifs->checkEnterNewState(); if (ret.error()) ERROR_LOG(m_logger) << ret(); m_mainHandler->dds()->fts()->acknowledgeExternalAesItemReply(cookie, ret); } void HandlerEffectCluster::onSetAesItemEffectorStateRequest(const rti::core::SampleIdentity &cookie, const SetAesItemEffectorStateRequest &request) { DEBUG_LOG(m_logger) << "HandlerEffectCluster::onSetAesItemEffectorStateRequest for aesItem" << request.aesId() << " to State: " << request.newEffectorState(); Core::FunctionReplyObject ret(request.aesId()); ObjectEffectCluster *ifs = getObjectFromMap(m_objectMap, request.aesId(), ret); if (!ifs) { ERROR_LOG(m_logger) << ret(); m_mainHandler->dds()->fts()->setAesItemEffectorStateReply(cookie, ret); return; } if (!ifs->overrideEffectorStates(request.newEffectorState())) { ret.setStr(tr_lts("dds", "EffectCluster with id %1 can't change to new State")).arg(request.aesId()); } m_mainHandler->dds()->fts()->setAesItemEffectorStateReply(cookie, ret); } void HandlerEffectCluster::onAdlerFqFbReply(const Core::FunctionReplyObject &replyObject) { DEBUG_LOG(m_logger) << "HandlerEffectCluster::onAdlerFqFbReply " << replyObject.id(); QJsonObject property; property[QStringLiteral("readyToFire")] = true; m_objectMap[replyObject.id()]->setAesProperty(property); } /* void HandlerEffectCluster::onTargetPositionsModifyRequest(const rti::core::SampleIdentity &cookie, const TargetPositionsModifyRequest &request) { DEBUG_LOG(m_logger) << "onTargetPositionsModifyRequest" << request; Core::FunctionReplyObject ret; ObjectEffectCluster *ifs = getObjectFromMap(m_objectMap, request.aesItemId(), ret); if (!ifs) { m_mainHandler->dds()->fts()->targetPositionsModifyReply(cookie, ret); return; } ret = ifs->targetPositionsModify(request); if (ret.error()) ERROR_LOG(m_logger) << ret(); m_mainHandler->dds()->fts()->targetPositionsModifyReply(cookie, ret); } */ void HandlerEffectCluster::onTrajectoryAdlerModifyRequest(const rti::core::SampleIdentity &cookie, const TrajectoryAdlerModifyRequest &request) { DEBUG_LOG(m_logger) << "onTrajectoryAdlerModifyRequest" << request; Core::FunctionReplyObject ret; ObjectEffectCluster *ifs = getObjectFromMap(m_objectMap, request.aesItemId(), ret); if (!ifs) { m_mainHandler->dds()->fts()->trajectoryAdlerModifyReply(cookie, ret); return; } ret = ifs->trajectoryAdlerModify(request); DEBUG_LOG(m_logger) << "onTrajectoryAdlerModifyRequest 1"; if (ret.error()) ERROR_LOG(m_logger) << ret(); DEBUG_LOG(m_logger) << "onTrajectoryAdlerModifyRequest 2"; m_mainHandler->dds()->fts()->trajectoryAdlerModifyReply(cookie, ret); DEBUG_LOG(m_logger) << "onTrajectoryAdlerModifyRequest 3"; } } // Namespace AreaEffectSimulation