/** * @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[static_cast(AesType::AtEffectCluster)], s_areaCountCentral[static_cast(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::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[static_cast(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::EgAimPoint) || (src.explosionGeometry == AreaEffectSimulation::AesExplosionGeometry::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::EgPoint; else ecCommonModifyRequest.explosionGeometry = AreaEffectSimulation::AesExplosionGeometry::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::GearClass_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::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::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[static_cast(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::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[static_cast(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::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) { case ProjectDataManagement::L22_FireHaltCategory::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::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::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::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::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; 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