/** * Handler for the HMI language * $Id$ * $HeadURL$ * $Author$ */ #include "object_explosion_area.h" #include "special_area_provider.h" #include "Utils/polygons.h" #include #include "handler_charge_administration.h" #include #include #include namespace AreaEffectSimulation { ObjectExplosionArea::ObjectExplosionArea(MainHandler *mainHandler, QObject *parent) : ObjectBaseTmpl(mainHandler, QStringLiteral("ExplosionArea"), parent) , m_singleExplosionTriggerTimer(new QTimer(this)) , m_lastSentTriggeredExplosionNumber(-1) , m_chargeCountUntilLastStart(0) { connect(m_singleExplosionTriggerTimer, &QTimer::timeout, this, &ObjectExplosionArea::checkNextSingleExplosion); m_bSupportsPausing = true; } ObjectExplosionArea::~ObjectExplosionArea() { removeTopics(); } Core::FunctionReplyObject ObjectExplosionArea::createObject(const Database::AesExplosionArea &data) { Core::FunctionReplyObject ret; // No need to create object, if there is not enough charge left. if (data.id == 0) // new, not read from db ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, data.cannonCount * data.cannonRounds); if (ret.error()) return ret; ret = ObjectBaseTmpl::createObject(data); if (ret.error()) { if (data.id == 0) m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, (-1) * data.cannonCount * data.cannonRounds); return ret; } if (data.id <= 0) // new, not read from db { m_data.originalEffectDuration = data.effectDuration; m_data.originalEffectStart = data.effectStart; } else checkNextSingleExplosion(); return ret; } Core::FunctionReplyObject ObjectExplosionArea::updateObject(const Database::AesExplosionArea &data) { Core::FunctionReplyObject ret = ObjectBaseTmpl::updateObject(data); if (ret.error() || (isOnlyObjectNameModifiable() && (data.id > 0))) // data.id > 0 -> negative values from oc always get a full update return ret; // if (data.id == 0) // should not happen, maybe an assert here ? // return ret; // only allow some fields auto tmpData(m_data); tmpData.id = std::abs(data.id); tmpData.name = data.name; tmpData.effectStart = data.effectStart; tmpData.effectDuration = data.effectDuration; tmpData.impactPosition = data.impactPosition; tmpData.participantId = data.participantId; tmpData.partyId = data.partyId; tmpData.chargeTypeId = data.chargeTypeId; tmpData.fireRequestTime = data.fireRequestTime; tmpData.altitude = data.altitude; tmpData.firePurpose = data.firePurpose; tmpData.effectMarkingInTa = data.effectMarkingInTa; tmpData.rotationAngle = data.rotationAngle; tmpData.impactWidth = data.impactWidth; tmpData.impactLength = data.impactLength; tmpData.effectStateId = data.effectStateId; // overwrite for oc tmpData.properties = data.properties; if (!isOnlyObjectNameModifiable()) // check again, this time also if oc { tmpData.originalEffectDuration = data.effectDuration; tmpData.originalEffectStart = data.effectStart; tmpData.cannonCount = data.cannonCount; tmpData.cannonRounds = data.cannonRounds; tmpData.properties = data.properties; } if (!m_mainHandler->db()->update(tmpData)) { return Core::FunctionReplyObject(data.id, tr_lts("dds", "Error updating explosion area with id %1 in database.")).arg(m_data.id); } // this is for party change or charge type change if (data.partyId != m_data.partyId || data.chargeTypeId != m_data.chargeTypeId) { ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(m_data.chargeTypeId, m_data.partyId, (-1) * m_data.cannonRounds * m_data.cannonCount); if (ret.error()) return ret; ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, data.cannonRounds * data.cannonCount); if (ret.error()) return ret; } else // this is the case when cannonCount or cannonRounds changed { int diff = data.cannonCount * data.cannonRounds - m_data.cannonRounds * m_data.cannonCount; if (diff != 0) { DEBUG_LOG(m_logger) << "Claiming planned charge " << diff; ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(data.chargeTypeId, data.partyId, diff); if (ret.error()) return ret; } } m_data = std::move(tmpData); //m_lastSentTriggeredExplosionNumber = -1; m_publishDataTimer->start(); checkEnterNewState(); return Core::FunctionReplyObject(data.id); // data not m_data for the possible negative id in the oc-laptop } Core::FunctionReplyObject ObjectExplosionArea::removeObject() { Core::FunctionReplyObject ret = ObjectBaseTmpl::removeObject(); if (ret.error()) return ret; int chargeLeft = m_data.cannonCount * m_data.cannonRounds; DEBUG_LOG(m_logger) << "Impact count: " << chargeLeft; DEBUG_LOG(m_logger) << "Last triggered explosion number: " << m_lastSentTriggeredExplosionNumber; if (isActivationTimeReached()) chargeLeft -= m_lastSentTriggeredExplosionNumber; if (chargeLeft < 0) chargeLeft = 0; DEBUG_LOG(m_logger) << "Charge left: " << chargeLeft; // restore original stuff m_data.effectDuration = m_data.originalEffectDuration; m_data.effectStart = m_data.originalEffectStart; // Negative, as we free the charge ret = m_mainHandler->chargeAdministrationHandler()->claimPlannedCharge(m_data.chargeTypeId, m_data.partyId, 0 - chargeLeft); return ret; } bool ObjectExplosionArea::removeObjectFromDb() { return m_mainHandler->db()->removeAesExplosionArea(m_data.id); } Core::FunctionReplyObject ObjectExplosionArea::resumePause(const ExplosionAreaPauseResumeRequest &req) { if (req.resumeExecution()) { if (req.timeToResume().is_set()) m_data.effectStart = QDateTime::fromMSecsSinceEpoch(req.timeToResume().get()); else m_data.effectStart = partitionTime(); if (req.remainingDuration().is_set()) m_data.effectDuration = req.remainingDuration().get(); // req.resumeSeed() currently not used, as it is calculated internaly. maybe in the future if (!trySwitchToActivated()) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Explosion Area is currently not paused.")); } else if (!trySwitchToPaused()) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Switching explosion area to pause is not possible in the current state.")); //m_lastSentTriggeredExplosionNumber = -1; return Core::FunctionReplyObject(m_data.id); } void ObjectExplosionArea::setEffectState(AesEffectState newState) { if (newState == AesEffectState::EsPaused || newState == AesEffectState::EsExpired || newState == AesEffectState::EsRemoved || newState == AesEffectState::EsUnknown) stopSingleExplosionHandling(newState); ObjectBaseTmpl::setEffectState(newState); if (newState == AesEffectState::EsActivated) startSingleExplosionHandling(); } void ObjectExplosionArea::onPublishData() { ObjectBaseTmpl::onPublishData(); if (!m_mainHandler->db()->update(m_data)) ERROR_LOG(m_logger) << "Error updating AesIndirectFireSingle-data in database"; ExplosionArea ddsData(m_data.id, m_data.name.toStdString(), effectState() == AesEffectState::EsRemoved, false, partitionTimeMSec(), {m_data.chargeTypeId,0,0}, m_data.effectDuration, toOptional(toMSec(m_data.effectStart)), m_data.effectStateId, m_data.effectSourceId, toOptional(m_data.partyId), toOptional(m_data.participantId), m_data.effectMarkingInTa, m_data.externalId, m_data.isCentral, m_data.properties.toStdString(), Area(Core::GeoPosition2D(m_data.impactPosition.x(), m_data.impactPosition.y()), m_data.impactWidth, m_data.impactLength, m_data.rotationAngle), m_data.cannonCount, m_data.cannonRounds, toMSec(m_data.originalEffectStart), m_data.originalEffectDuration, currentExplosionNumber(), toMSec(m_data.fireRequestTime), m_data.altitude, m_data.firePurpose.toStdString()); m_mainHandler->dds()->dts()->publish(ddsData); publishAiv(); } void ObjectExplosionArea::publishAiv() { // Todo: calculate unique id. // Better than aiv.id(1000 * aesItemId + AesType::AtExplosionArea); const QPolygonF poly = Polygons::explosionArea(m_data); const QRectF bounds(poly.boundingRect()); AesItemView aiv(m_aiv); aiv.aesItemId(m_data.id); aiv.chargeStruct().chargeTypeId(m_data.chargeTypeId); aiv.effectStart(toMSec(m_data.effectStart)); ddsVecFromPoly(poly, aiv.form()); aiv.boundingBoxBottomLeft(Core::GeoPosition2D(bounds.topLeft().x(), bounds.topLeft().y())); // QRectF's bigger values are at bottom aiv.boundingBoxTopRight(Core::GeoPosition2D(bounds.bottomRight().x(), bounds.bottomRight().y())); aiv.aesTypeId(AesType::AtExplosionArea); aiv.effectStateId(effectState()); aiv.id(1000 * m_data.id + AesType::AtExplosionArea); if (m_data.partyId != 0) aiv.partyId(m_data.partyId); checkIsCentralAndPublish(aiv.form(), m_aiv.form(), m_data.isCentral); const QVector singleExplosions = Polygons::explosionAreaSingleExplosions(data()); AesItemView aiv2(m_aiv2); aiv2.aesItemId(m_data.id); aiv2.chargeStruct().chargeTypeId(m_data.chargeTypeId); aiv2.effectStart(toMSec(m_data.effectStart)); ddsVecFromPoly(singleExplosions, aiv2.form()); aiv2.boundingBoxBottomLeft(aiv.boundingBoxBottomLeft()); aiv2.boundingBoxTopRight(aiv.boundingBoxTopRight()); aiv2.aesTypeId(AesType::AtExplosionAreaSingleExplosions); aiv2.id(1000 * m_data.id + AesType::AtExplosionAreaSingleExplosions); aiv2.effectStateId(effectState()); if (m_data.partyId != 0) aiv2.partyId(m_data.partyId); if (aiv != m_aiv) { aiv.sampleTime(partitionTimeMSec()); m_aiv = aiv; m_mainHandler->dds()->dts()->publish(aiv); emit newAiv(aiv); } if (aiv2 != m_aiv2) { aiv2.sampleTime(partitionTimeMSec()); m_aiv2 = aiv2; m_mainHandler->dds()->dts()->publish(aiv2); emit newAiv(aiv2); } } void ObjectExplosionArea::removeTopics() { auto dts = m_mainHandler->dds()->dts(); dts->removeExplosionAreaByKey(m_data.id); dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtExplosionArea); dts->removeAesItemViewByKey(1000 * m_data.id + AesType::AtExplosionAreaSingleExplosions); emit removeAiv(m_data.id); } void ObjectExplosionArea::startSingleExplosionHandling() { DEBUG_LOG(m_logger) << "Starting area explosion handling"; if (!m_data.effectStart.isValid()) // 0 -> paused { // trim to second (really? yes since we can only enter seconds) m_data.effectStart = partitionTime(); m_publishDataTimer->start(); } m_singleExplosionTriggerTimer->setInterval(impactDeltaMSec()); m_singleExplosionTriggerTimer->start(); checkNextSingleExplosion(); // for second 0 } void ObjectExplosionArea::stopSingleExplosionHandling(AesEffectState newState) { m_singleExplosionTriggerTimer->stop(); if (m_data.effectStateId != AesEffectState_def::EsActivated) return; // int chargeCountUntilLastStart = lrint((m_data.originalEffectDuration - m_data.effectDuration) / impactDeltaMSec()); int chargeCountUntilLastStart = m_chargeCountUntilLastStart; qint64 msecsSinceLastStart = m_data.effectStart.msecsTo(partitionTime()); int impactCount = m_data.cannonCount * m_data.cannonRounds; int curExplosionNumber = m_lastSentTriggeredExplosionNumber; // keep before modification !? Here -1 because we want to consume the proper amount of charge! if (curExplosionNumber > impactCount) curExplosionNumber = impactCount; DEBUG_LOG(m_logger) << "impactCount: " << impactCount; DEBUG_LOG(m_logger) << "chargeCountUntilLastStart: " << chargeCountUntilLastStart; DEBUG_LOG(m_logger) << "curExplosionNumber: " << curExplosionNumber; m_data.effectDuration -= msecsSinceLastStart; DEBUG_LOG(m_logger) << "stopSingleExplosionHandling: " << m_data.effectDuration; Core::FunctionReplyObject ret = m_mainHandler->chargeAdministrationHandler()->consumeCharge(m_data.chargeTypeId, m_data.partyId, curExplosionNumber - chargeCountUntilLastStart); DEBUG_LOG(m_logger) << "Charge marked used in last lap: " << curExplosionNumber - chargeCountUntilLastStart; if (ret.error()) ERROR_LOG(m_logger) << "Could not consume Charge! " << ret(); m_chargeCountUntilLastStart = curExplosionNumber; if (m_data.effectDuration < 0 || newState == AesEffectState::EsExpired) { DEBUG_LOG(m_logger) << "Expired!"; m_data.effectDuration = m_data.originalEffectDuration; m_data.effectStart = m_data.originalEffectStart; } else // Paused m_data.effectStart = QDateTime(); // Invalid QDateTime to mark it paused m_publishDataTimer->start(); } // called every second void ObjectExplosionArea::checkNextSingleExplosion() { const int curExplosionNumber = currentExplosionNumber(); const int impactCount = m_data.cannonCount * m_data.cannonRounds; if (curExplosionNumber > impactCount) { DEBUG_LOG(m_logger) << "Remaining effect duration is zero - no more single explosions"; m_singleExplosionTriggerTimer->stop(); m_data.effectDuration = m_data.originalEffectDuration; m_data.effectStart = m_data.originalEffectStart; m_publishDataTimer->start(); } else { // check if a new explosion is triggered if (curExplosionNumber == m_lastSentTriggeredExplosionNumber) return; // nothing changed -> do not publish anything m_lastSentTriggeredExplosionNumber = curExplosionNumber; DEBUG_LOG(m_logger) << "checkNextSingleExplosion: curExplosionNumber = " << curExplosionNumber; DEBUG_LOG(m_logger) << "checkNextSingleExplosion: m_lastSentTriggeredExplosionNumber = " << m_lastSentTriggeredExplosionNumber; // publish new triggered explosions data here TriggeredImpacts ti; ti.id(1000 * m_data.id + AesType::AtExplosionAreaSingleExplosions); ti.aesItemId(m_data.id); ti.aesTypeId(AesType::AtExplosionAreaSingleExplosions); ti.sampleTime(partitionTimeMSec()); ti.impactCount(impactCount); ti.bitfield().resize((impactCount + 7) / 8); // not very fast but ok for now for(int i = 0; i < impactCount; ++i) { // Triggered Impact == 0, if impact has taken place LtsUtils::BitArray::setBit(ti.bitfield(), i, i >= curExplosionNumber); // not "<" -> explosion NUMBER } DEBUG_LOG(m_logger) << "Publishing TriggeredExplosions with curExplosionNumber " << curExplosionNumber; m_mainHandler->dds()->dts()->publish(ti); } } qint64 ObjectExplosionArea::impactDeltaMSec() const { const qint64 impactCount = m_data.cannonCount * m_data.cannonRounds; // double for calculation below if (impactCount <= 0) return 24 * 60 * 60 * 1000; // 1day - more or less invalid if (m_data.originalEffectDuration > 0) return m_data.originalEffectDuration / impactCount; // msecs between impacts else return 500; // In case of invalid (0) effect duration time, return 500 msecs. } int ObjectExplosionArea::currentExplosionNumber() const { const qint64 delta_t = impactDeltaMSec(); const double exhausedDuration = m_data.originalEffectDuration - m_data.effectDuration; // exhaused till last start const bool haveEffectStart = m_data.effectStart.isValid(); const double curDuration = haveEffectStart ? partitionTimeMSec() - m_data.effectStart.toMSecsSinceEpoch() : 0; // exhaused between last start and now const double completedEffectDuration = exhausedDuration + curDuration; const int ret = lrint(completedEffectDuration / delta_t); DEBUG_LOG(m_logger) << "Current explosion number is " << ret << ", ic: " << m_data.cannonCount * m_data.cannonRounds << ", dt: " << delta_t << ", cd: " << QString::number(completedEffectDuration,'f', 2) << ", exd: " << QString::number(exhausedDuration,'f', 2) << ", curD: " << QString::number(curDuration,'f', 2) << ", ed: " << m_data.effectDuration ; return ret + 1; // start at 1 for proper bit setting } QPolygonF ObjectExplosionArea::generateForm() { return Polygons::explosionArea(m_data); } } // Namespace AreaEffectSimulation