From 1383375db637e80e8a6f2bc5e0be1daea19083da Mon Sep 17 00:00:00 2001 From: Nils Schimmelmann Date: Sat, 27 Dec 2025 20:36:26 -0600 Subject: [PATCH] refactor MumeClock to align its second timer with MUME --- src/clock/mumeclock.cpp | 85 ++++++--- src/clock/mumeclock.h | 13 +- src/clock/mumeclockwidget.cpp | 285 ++++++++++++++++-------------- src/clock/mumeclockwidget.h | 29 ++- src/mainwindow/mainwindow.cpp | 5 +- src/observer/gameobserver.cpp | 48 +++++ src/observer/gameobserver.h | 39 +++- src/parser/mumexmlparser-gmcp.cpp | 12 ++ src/parser/mumexmlparser.cpp | 2 + src/parser/mumexmlparser.h | 7 +- src/proxy/proxy.cpp | 2 + tests/CMakeLists.txt | 3 + tests/testclock.cpp | 16 +- 13 files changed, 363 insertions(+), 183 deletions(-) diff --git a/src/clock/mumeclock.cpp b/src/clock/mumeclock.cpp index 10973e7d7..8ca3c20df 100644 --- a/src/clock/mumeclock.cpp +++ b/src/clock/mumeclock.cpp @@ -4,14 +4,12 @@ #include "mumeclock.h" +#include "../configuration/configuration.h" #include "../global/Array.h" #include "../global/JsonObj.h" #include "../proxy/GmcpMessage.h" +#include "../proxy/MudTelnet.h" // FIXME: move MsspTime somewhere more appropriate, or just use MumeMoment. #include "mumemoment.h" -#if 1 -// FIXME: move MsspTime somewhere more appropriate, or just use MumeMoment. -#include "../proxy/MudTelnet.h" -#endif #include @@ -111,12 +109,14 @@ using westronWeekDayNames = mmqt::QME; MumeClock::MumeClock(int64_t mumeEpoch, GameObserver &observer, QObject *const parent) : QObject(parent) + , m_observer{observer} , m_mumeStartEpoch(mumeEpoch) , m_precision(MumeClockPrecisionEnum::UNSET) - , m_observer{observer} { m_observer.sig2_sentToUserGmcp.connect(m_lifetime, [this](const GmcpMessage &gmcp) { onUserGmcp(gmcp); }); + connect(&m_timer, &QTimer::timeout, this, &MumeClock::slot_tick); + m_timer.start(1000); } MumeClock::MumeClock(GameObserver &observer) @@ -125,13 +125,12 @@ MumeClock::MumeClock(GameObserver &observer) MumeMoment MumeClock::getMumeMoment() const { - const int64_t t = QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); + const int64_t t = QDateTime::currentSecsSinceEpoch(); return MumeMoment::sinceMumeEpoch(t - m_mumeStartEpoch); } MumeMoment MumeClock::getMumeMoment(const int64_t secsSinceUnixEpoch) const { - /* This will break on 2038-01-19 if you use 32-bit. */ if (secsSinceUnixEpoch < 0) { assert(secsSinceUnixEpoch == -1); return getMumeMoment(); @@ -141,7 +140,7 @@ MumeMoment MumeClock::getMumeMoment(const int64_t secsSinceUnixEpoch) const void MumeClock::parseMumeTime(const QString &mumeTime) { - const int64_t secsSinceEpoch = QDateTime::QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); + const int64_t secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); parseMumeTime(mumeTime, secsSinceEpoch); } @@ -221,6 +220,7 @@ void MumeClock::parseMumeTime(const QString &mumeTime, const int64_t secsSinceEp qWarning() << "Calculated week day does not match MUME"; } m_mumeStartEpoch = newStartEpoch; + setConfig().mumeClock.startEpoch = newStartEpoch; } void MumeClock::onUserGmcp(const GmcpMessage &msg) @@ -262,12 +262,15 @@ void MumeClock::onUserGmcp(const GmcpMessage &msg) break; } } - const int64_t secsSinceEpoch = QDateTime::QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); + const int64_t secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); parseWeather(time, secsSinceEpoch); } void MumeClock::parseWeather(const MumeTimeEnum time, int64_t secsSinceEpoch) { + // Restart the timer to sync with the game's tick + m_timer.start(1000); + // Update last sync timestamp setLastSyncEpoch(secsSinceEpoch); @@ -312,18 +315,19 @@ void MumeClock::parseWeather(const MumeTimeEnum time, int64_t secsSinceEpoch) log(QString("Unsychronized tick detected using %1 (off by %2 seconds)") .arg(reason) .arg(moment.minute)); - return; + } else { + log(QString("Synchronized tick using %1").arg(reason)); + if (time != MumeTimeEnum::UNKNOWN || m_precision >= MumeClockPrecisionEnum::HOUR) { + m_precision = MumeClockPrecisionEnum::MINUTE; + } } - log(QString("Synchronized tick using %1").arg(reason)); - if (time != MumeTimeEnum::UNKNOWN || m_precision >= MumeClockPrecisionEnum::HOUR) { - m_precision = MumeClockPrecisionEnum::MINUTE; - } + updateObserver(moment); } void MumeClock::parseClockTime(const QString &clockTime) { - const int64_t secsSinceEpoch = QDateTime::QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); + const int64_t secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); parseClockTime(clockTime, secsSinceEpoch); } @@ -366,7 +370,7 @@ void MumeClock::parseMSSP(const MsspTime &msspTime) return; } - const int64_t secsSinceEpoch = QDateTime::QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); + const int64_t secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); auto moment = getMumeMoment(); moment.year = msspTime.year; @@ -390,9 +394,14 @@ void MumeClock::setPrecision(const MumeClockPrecisionEnum precision) m_precision = precision; } +void MumeClock::setLastSyncEpoch(int64_t epoch) +{ + m_lastSyncEpoch = epoch; +} + MumeClockPrecisionEnum MumeClock::getPrecision() { - const int64_t secsSinceEpoch = QDateTime::QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); + const int64_t secsSinceEpoch = QDateTime::QDateTime::currentSecsSinceEpoch(); if (m_precision >= MumeClockPrecisionEnum::HOUR && secsSinceEpoch - m_lastSyncEpoch > ONE_RL_DAY_IN_SECONDS) { m_precision = MumeClockPrecisionEnum::DAY; @@ -421,14 +430,13 @@ QString MumeClock::toMumeTime(const MumeMoment &moment) const QString time; switch (m_precision) { case MumeClockPrecisionEnum::HOUR: - time = QString("%1%2 on %3").arg(hour).arg(period).arg(weekDay); + time = QString("%1%2 on %3").arg(hour).arg(period, weekDay); break; case MumeClockPrecisionEnum::MINUTE: time = QString("%1:%2%3 on %4") .arg(hour) .arg(moment.minute, 2, 10, QChar('0')) - .arg(period) - .arg(weekDay); + .arg(period, weekDay); break; case MumeClockPrecisionEnum::UNSET: case MumeClockPrecisionEnum::DAY: @@ -443,8 +451,7 @@ QString MumeClock::toMumeTime(const MumeMoment &moment) const return QString("%1, the %2%3 of %4, year %5 of the Third Age.") .arg(time) .arg(day) - .arg(QString{getOrdinalSuffix(day)}) - .arg(monthName) + .arg(QString{getOrdinalSuffix(day)}, monthName) .arg(moment.year); } @@ -491,3 +498,37 @@ int MumeClock::getMumeWeekday(const QString &weekdayName) { return mmqt::parseTwoEnums(weekdayName); } + +void MumeClock::slot_tick() +{ + const MumeMoment moment = getMumeMoment(); + m_observer.observeTick(moment); + updateObserver(moment); +} + +void MumeClock::updateObserver(const MumeMoment &moment) +{ + const auto timeOfDay = moment.toTimeOfDay(); + if (timeOfDay != m_timeOfDay) { + m_timeOfDay = timeOfDay; + m_observer.observeTimeOfDay(m_timeOfDay); + } + + const auto moonPhase = moment.moonPhase(); + if (moonPhase != m_moonPhase) { + m_moonPhase = moonPhase; + m_observer.observeMoonPhase(m_moonPhase); + } + + const auto moonVisibility = moment.moonVisibility(); + if (moonVisibility != m_moonVisibility) { + m_moonVisibility = moonVisibility; + m_observer.observeMoonVisibility(m_moonVisibility); + } + + const auto season = moment.toSeason(); + if (season != m_season) { + m_season = season; + m_observer.observeSeason(m_season); + } +} diff --git a/src/clock/mumeclock.h b/src/clock/mumeclock.h index 1268c797b..ad0fdc443 100644 --- a/src/clock/mumeclock.h +++ b/src/clock/mumeclock.h @@ -76,11 +76,16 @@ class NODISCARD_QOBJECT MumeClock final : public QObject friend class TestClock; private: + Signal2Lifetime m_lifetime; + QTimer m_timer; + GameObserver &m_observer; int64_t m_lastSyncEpoch = 0; int64_t m_mumeStartEpoch = 0; MumeClockPrecisionEnum m_precision = MumeClockPrecisionEnum::UNSET; - GameObserver &m_observer; - Signal2Lifetime m_lifetime; + MumeTimeEnum m_timeOfDay = MumeTimeEnum::UNKNOWN; + MumeMoonPhaseEnum m_moonPhase = MumeMoonPhaseEnum::UNKNOWN; + MumeMoonVisibilityEnum m_moonVisibility = MumeMoonVisibilityEnum::UNKNOWN; + MumeSeasonEnum m_season = MumeSeasonEnum::UNKNOWN; public: static inline constexpr const int NUM_MONTHS = 12; @@ -140,7 +145,7 @@ class NODISCARD_QOBJECT MumeClock final : public QObject public: void setPrecision(MumeClockPrecisionEnum state); - void setLastSyncEpoch(int64_t epoch) { m_lastSyncEpoch = epoch; } + void setLastSyncEpoch(int64_t epoch); NODISCARD static int getMumeMonth(const QString &monthName); NODISCARD static int getMumeWeekday(const QString &weekdayName); @@ -153,6 +158,7 @@ class NODISCARD_QOBJECT MumeClock final : public QObject private: void onUserGmcp(const GmcpMessage &msg); + void updateObserver(const MumeMoment &moment); signals: void sig_log(const QString &, const QString &); @@ -160,4 +166,5 @@ class NODISCARD_QOBJECT MumeClock final : public QObject public slots: void parseMumeTime(const QString &mumeTime); void parseClockTime(const QString &clockTime); + void slot_tick(); }; diff --git a/src/clock/mumeclockwidget.cpp b/src/clock/mumeclockwidget.cpp index 3962742ad..725912b1a 100644 --- a/src/clock/mumeclockwidget.cpp +++ b/src/clock/mumeclockwidget.cpp @@ -9,26 +9,43 @@ #include "mumemoment.h" #include -#include #include #include #include #include -MumeClockWidget::MumeClockWidget(MumeClock *const clock, QWidget *const parent) +MumeClockWidget::MumeClockWidget(GameObserver &observer, MumeClock &clock, QWidget *const parent) : QWidget(parent) , m_clock(clock) { setupUi(this); + moonPhaseLabel->setText(""); + seasonLabel->setText(""); setAttribute(Qt::WA_DeleteOnClose); assert(testAttribute(Qt::WA_DeleteOnClose)); - - m_timer = std::make_unique(this); - connect(m_timer.get(), &QTimer::timeout, this, &MumeClockWidget::slot_updateLabel); - m_timer->start(1000); - - slot_updateLabel(); + setAttribute(Qt::WA_Hover, true); + assert(testAttribute(Qt::WA_Hover)); + + observer.sig2_timeOfDayChanged.connect(m_lifetime, + [this](MumeTimeEnum time) { updateTime(time); }); + observer.sig2_moonPhaseChanged.connect(m_lifetime, [this](MumeMoonPhaseEnum phase) { + updateMoonPhase(phase); + }); + observer.sig2_moonVisibilityChanged.connect(m_lifetime, + [this](MumeMoonVisibilityEnum visibility) { + updateMoonVisibility(visibility); + }); + observer.sig2_seasonChanged.connect(m_lifetime, + [this](MumeSeasonEnum season) { updateSeason(season); }); + observer.sig2_tick.connect(m_lifetime, + [this](const MumeMoment &moment) { updateCountdown(moment); }); + + updateTime(observer.getTimeOfDay()); + updateMoonPhase(observer.getMoonPhase()); + updateMoonVisibility(observer.getMoonVisibility()); + updateSeason(observer.getSeason()); + updateCountdown(clock.getMumeMoment()); } MumeClockWidget::~MumeClockWidget() = default; @@ -36,149 +53,145 @@ MumeClockWidget::~MumeClockWidget() = default; void MumeClockWidget::mousePressEvent(QMouseEvent * /*event*/) { // Force precision to minute and reset last sync to current timestamp - m_clock->setPrecision(MumeClockPrecisionEnum::MINUTE); - m_clock->setLastSyncEpoch(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); - - slot_updateLabel(); + m_clock.setPrecision(MumeClockPrecisionEnum::MINUTE); + m_clock.setLastSyncEpoch(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); + auto moment = m_clock.getMumeMoment(); + updateTime(moment.toTimeOfDay()); + updateCountdown(moment); + updateStatusTips(moment); } -void MumeClockWidget::slot_updateLabel() +bool MumeClockWidget::event(QEvent *event) { - // Ensure we have updated the epoch - setConfig().mumeClock.startEpoch = m_clock->getMumeStartEpoch(); - - // Hide or show the widget if necessary - if (!getConfig().mumeClock.display) { - hide(); - // Slow down the interval to a reasonable number - m_timer->setInterval(60 * 1000); - return; + if (event->type() == QEvent::HoverEnter) { + updateStatusTips(m_clock.getMumeMoment()); } - if (m_timer->interval() != 1000) { - show(); - // Speed up the interval again if the display needs to be shown - m_timer->setInterval(1000); + return QWidget::event(event); +} + +void MumeClockWidget::updateTime(MumeTimeEnum time) +{ + // The current time is 12:15 am. + QString styleSheet = ""; + if (m_clock.getPrecision() <= MumeClockPrecisionEnum::UNSET) { + styleSheet = "padding-left:1px;padding-right:1px;color:white;background:grey"; + } else if (time == MumeTimeEnum::DAWN) { + styleSheet = "padding-left:1px;padding-right:1px;color:white;background:red"; + } else if (time >= MumeTimeEnum::DUSK) { + styleSheet = "padding-left:1px;padding-right:1px;color:white;background:blue"; + } else { + styleSheet = "padding-left:1px;padding-right:1px;color:black;background:yellow"; } - const MumeMoment moment = m_clock->getMumeMoment(); - const MumeClockPrecisionEnum precision = m_clock->getPrecision(); - - bool updateMoonText = false; - const MumeMoonPhaseEnum phase = moment.moonPhase(); - if (phase != m_lastPhase) { - m_lastPhase = phase; - switch (phase) { - case MumeMoonPhaseEnum::WAXING_CRESCENT: - moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x92")); - break; - case MumeMoonPhaseEnum::FIRST_QUARTER: - moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x93")); - break; - case MumeMoonPhaseEnum::WAXING_GIBBOUS: - moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x94")); - break; - case MumeMoonPhaseEnum::FULL_MOON: - moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x95")); - break; - case MumeMoonPhaseEnum::WANING_GIBBOUS: - moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x96")); - break; - case MumeMoonPhaseEnum::THIRD_QUARTER: - moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x97")); - break; - case MumeMoonPhaseEnum::WANING_CRESCENT: - moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x98")); - break; - case MumeMoonPhaseEnum::NEW_MOON: - moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x91")); - break; - case MumeMoonPhaseEnum::UNKNOWN: - moonPhaseLabel->setText(""); - break; - } - updateMoonText = true; + timeLabel->setStyleSheet(styleSheet); +} + +void MumeClockWidget::updateMoonPhase(MumeMoonPhaseEnum phase) +{ + switch (phase) { + case MumeMoonPhaseEnum::WAXING_CRESCENT: + moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x92")); + break; + case MumeMoonPhaseEnum::FIRST_QUARTER: + moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x93")); + break; + case MumeMoonPhaseEnum::WAXING_GIBBOUS: + moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x94")); + break; + case MumeMoonPhaseEnum::FULL_MOON: + moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x95")); + break; + case MumeMoonPhaseEnum::WANING_GIBBOUS: + moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x96")); + break; + case MumeMoonPhaseEnum::THIRD_QUARTER: + moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x97")); + break; + case MumeMoonPhaseEnum::WANING_CRESCENT: + moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x98")); + break; + case MumeMoonPhaseEnum::NEW_MOON: + moonPhaseLabel->setText(QString::fromUtf8("\xF0\x9F\x8C\x91")); + break; + case MumeMoonPhaseEnum::UNKNOWN: + moonPhaseLabel->setText(""); + break; } +} - seasonLabel->setStatusTip(m_clock->toMumeTime(moment)); - const MumeSeasonEnum season = moment.toSeason(); - if (season != m_lastSeason) { - m_lastSeason = season; - QString styleSheet = "color:black"; - QString text = "Unknown"; - switch (season) { - case MumeSeasonEnum::WINTER: - styleSheet = "color:black;background:white"; - text = "Winter"; - break; - case MumeSeasonEnum::SPRING: - styleSheet = "color:white;background:teal"; - text = "Spring"; - break; - case MumeSeasonEnum::SUMMER: - styleSheet = "color:white;background:green"; - text = "Summer"; - break; - case MumeSeasonEnum::AUTUMN: - styleSheet = "color:black;background:orange"; - text = "Autumn"; - break; - case MumeSeasonEnum::UNKNOWN: - default: - break; - } - seasonLabel->setStyleSheet(styleSheet); - seasonLabel->setText(text); +void MumeClockWidget::updateMoonVisibility(MumeMoonVisibilityEnum visibility) +{ + const QString moonStyleSheet = (visibility == MumeMoonVisibilityEnum::INVISIBLE + || visibility == MumeMoonVisibilityEnum::UNKNOWN) + ? "color:black;background:grey" + : (visibility == MumeMoonVisibilityEnum::BRIGHT) + ? "color:black;background:yellow" + : "color:black;background:white"; + moonPhaseLabel->setStyleSheet(moonStyleSheet); +} + +void MumeClockWidget::updateSeason(MumeSeasonEnum season) +{ + QString styleSheet = "color:black"; + QString text = ""; + switch (season) { + case MumeSeasonEnum::WINTER: + styleSheet = "color:black;background:white"; + text = "Winter"; + break; + case MumeSeasonEnum::SPRING: + styleSheet = "color:white;background:teal"; + text = "Spring"; + break; + case MumeSeasonEnum::SUMMER: + styleSheet = "color:white;background:green"; + text = "Summer"; + break; + case MumeSeasonEnum::AUTUMN: + styleSheet = "color:black;background:orange"; + text = "Autumn"; + break; + case MumeSeasonEnum::UNKNOWN: + default: + break; } + seasonLabel->setStyleSheet(styleSheet); + seasonLabel->setText(text); +} - bool updateMoonStyleSheet = false; - const MumeTimeEnum time = moment.toTimeOfDay(); - if (time != m_lastTime || precision != m_lastPrecision) { - m_lastTime = time; - m_lastPrecision = precision; - // The current time is 12:15 am. - QString styleSheet = ""; - QString statusTip = ""; - if (precision <= MumeClockPrecisionEnum::UNSET) { - styleSheet = "padding-left:1px;padding-right:1px;color:white;background:grey"; - } else if (time == MumeTimeEnum::DAWN) { - styleSheet = "padding-left:1px;padding-right:1px;color:white;background:red"; - statusTip = "Ticks left until day"; - } else if (time >= MumeTimeEnum::DUSK) { - styleSheet = "padding-left:1px;padding-right:1px;color:white;background:blue"; - statusTip = "Ticks left until day"; - } else { - styleSheet = "padding-left:1px;padding-right:1px;color:black;background:yellow"; - statusTip = "Ticks left until night"; - } - if (precision != MumeClockPrecisionEnum::MINUTE) { - statusTip = "The clock has not synced with MUME! Click to override at your own risk."; - } - - timeLabel->setStyleSheet(styleSheet); - timeLabel->setStatusTip(statusTip); - updateMoonStyleSheet = true; +void MumeClockWidget::updateCountdown(const MumeMoment &moment) +{ + // FIXME: Use ChangeMonitor + setVisible(getConfig().mumeClock.display); + if (!getConfig().mumeClock.display) { + return; } + + const MumeClockPrecisionEnum precision = m_clock.getPrecision(); if (precision <= MumeClockPrecisionEnum::HOUR) { - // Prepend warning emoji to countdown - timeLabel->setText(QString::fromUtf8("\xE2\x9A\xA0").append(m_clock->toCountdown(moment))); + timeLabel->setText(QString::fromUtf8("\xE2\x9A\xA0").append(m_clock.toCountdown(moment))); } else { - timeLabel->setText(m_clock->toCountdown(moment)); + timeLabel->setText(m_clock.toCountdown(moment)); } +} - const MumeMoonVisibilityEnum moonVisibility = moment.moonVisibility(); - if (moonVisibility != m_lastVisibility || updateMoonStyleSheet) { - m_lastVisibility = moonVisibility; - const QString moonStyleSheet = (moonVisibility == MumeMoonVisibilityEnum::INVISIBLE - || moonVisibility == MumeMoonVisibilityEnum::UNKNOWN) - ? "color:black;background:grey" - : (moonVisibility == MumeMoonVisibilityEnum::BRIGHT) - ? "color:black;background:yellow" - : "color:black;background:white"; - moonPhaseLabel->setStyleSheet(moonStyleSheet); - updateMoonText = true; +void MumeClockWidget::updateStatusTips(const MumeMoment &moment) +{ + moonPhaseLabel->setStatusTip(moment.toMumeMoonTime()); + seasonLabel->setStatusTip(m_clock.toMumeTime(moment)); + + QString statusTip = ""; + const MumeClockPrecisionEnum precision = m_clock.getPrecision(); + const auto time = moment.toTimeOfDay(); + if (time == MumeTimeEnum::DAWN) { + statusTip = "Ticks left until day"; + } else if (time >= MumeTimeEnum::DUSK) { + statusTip = "Ticks left until day"; + } else { + statusTip = "Ticks left until night"; } - - if (updateMoonText) { - moonPhaseLabel->setStatusTip(moment.toMumeMoonTime()); + if (precision != MumeClockPrecisionEnum::MINUTE) { + statusTip = "The clock has not synced with MUME! Click to override at your own risk."; } + timeLabel->setStatusTip(statusTip); } diff --git a/src/clock/mumeclockwidget.h b/src/clock/mumeclockwidget.h index b340b0cbb..1b3f75397 100644 --- a/src/clock/mumeclockwidget.h +++ b/src/clock/mumeclockwidget.h @@ -3,41 +3,40 @@ // Copyright (C) 2019 The MMapper Authors // Author: Nils Schimmelmann (Jahara) +#include "../global/Signal2.h" +#include "../observer/gameobserver.h" #include "mumeclock.h" #include "mumemoment.h" #include "ui_mumeclockwidget.h" -#include - #include #include #include +class QEvent; class QMouseEvent; -class QObject; -class QTimer; class NODISCARD_QOBJECT MumeClockWidget final : public QWidget, private Ui::MumeClockWidget { Q_OBJECT private: - MumeClock *m_clock = nullptr; - std::unique_ptr m_timer; - - MumeTimeEnum m_lastTime = MumeTimeEnum::UNKNOWN; - MumeSeasonEnum m_lastSeason = MumeSeasonEnum::UNKNOWN; - MumeMoonPhaseEnum m_lastPhase = MumeMoonPhaseEnum::UNKNOWN; - MumeMoonVisibilityEnum m_lastVisibility = MumeMoonVisibilityEnum::UNKNOWN; - MumeClockPrecisionEnum m_lastPrecision = MumeClockPrecisionEnum::UNSET; + Signal2Lifetime m_lifetime; + MumeClock &m_clock; public: - explicit MumeClockWidget(MumeClock *clock, QWidget *parent); + explicit MumeClockWidget(GameObserver &observer, MumeClock &clock, QWidget *parent); ~MumeClockWidget() final; protected: void mousePressEvent(QMouseEvent *event) override; + bool event(QEvent *event) override; -public slots: - void slot_updateLabel(); +private: + void updateCountdown(const MumeMoment &moment); + void updateTime(MumeTimeEnum time); + void updateMoonPhase(MumeMoonPhaseEnum phase); + void updateMoonVisibility(MumeMoonVisibilityEnum visibility); + void updateSeason(MumeSeasonEnum season); + void updateStatusTips(const MumeMoment &moment); }; diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp index 65ac3ae2f..4f5239d54 100644 --- a/src/mainwindow/mainwindow.cpp +++ b/src/mainwindow/mainwindow.cpp @@ -1402,7 +1402,10 @@ void MainWindow::setupToolBars() void MainWindow::setupStatusBar() { showStatusForever(tr("Say friend and enter...")); - statusBar()->insertPermanentWidget(0, new MumeClockWidget(m_mumeClock, this)); + statusBar()->insertPermanentWidget(0, + new MumeClockWidget(deref(m_gameObserver), + deref(m_mumeClock), + this)); XPStatusWidget *xpStatus = new XPStatusWidget(*m_adventureTracker, statusBar(), this); xpStatus->setToolTip("Click to toggle the Adventure Panel."); diff --git a/src/observer/gameobserver.cpp b/src/observer/gameobserver.cpp index a15d0a8b1..88a17b368 100644 --- a/src/observer/gameobserver.cpp +++ b/src/observer/gameobserver.cpp @@ -34,3 +34,51 @@ void GameObserver::observeToggledEchoMode(const bool echo) { sig2_toggledEchoMode.invoke(echo); } + +void GameObserver::observeWeather(const PromptWeatherEnum weather) +{ + if (m_weather != weather) { + m_weather = weather; + sig2_weatherChanged.invoke(weather); + } +} + +void GameObserver::observeFog(const PromptFogEnum fog) +{ + if (m_fog != fog) { + m_fog = fog; + sig2_fogChanged.invoke(fog); + } +} + +void GameObserver::observeTimeOfDay(MumeTimeEnum timeOfDay) +{ + if (m_timeOfDay != timeOfDay) { + m_timeOfDay = timeOfDay; + sig2_timeOfDayChanged.invoke(m_timeOfDay); + } +} + +void GameObserver::observeMoonPhase(MumeMoonPhaseEnum moonPhase) +{ + if (m_moonPhase != moonPhase) { + m_moonPhase = moonPhase; + sig2_moonPhaseChanged.invoke(m_moonPhase); + } +} + +void GameObserver::observeMoonVisibility(MumeMoonVisibilityEnum moonVisibility) +{ + if (m_moonVisibility != moonVisibility) { + m_moonVisibility = moonVisibility; + sig2_moonVisibilityChanged.invoke(m_moonVisibility); + } +} + +void GameObserver::observeSeason(MumeSeasonEnum season) +{ + if (m_season != season) { + m_season = season; + sig2_seasonChanged.invoke(m_season); + } +} diff --git a/src/observer/gameobserver.h b/src/observer/gameobserver.h index dc3097a3e..84e24a24b 100644 --- a/src/observer/gameobserver.h +++ b/src/observer/gameobserver.h @@ -3,13 +3,12 @@ // Copyright (C) 2023 The MMapper Authors // Author: Mike Repass (Taryn) -#include "../global/ChangeMonitor.h" +#include "../clock/mumemoment.h" #include "../global/Signal2.h" +#include "../map/PromptFlags.h" #include "../proxy/GmcpMessage.h" -#include - -class NODISCARD_QOBJECT GameObserver final +class NODISCARD GameObserver final { public: Signal2<> sig2_connected; @@ -20,10 +19,42 @@ class NODISCARD_QOBJECT GameObserver final Signal2 sig2_sentToUserGmcp; Signal2 sig2_toggledEchoMode; + Signal2 sig2_timeOfDayChanged; + Signal2 sig2_moonPhaseChanged; + Signal2 sig2_moonVisibilityChanged; + Signal2 sig2_seasonChanged; + Signal2 sig2_weatherChanged; + Signal2 sig2_fogChanged; + Signal2 sig2_tick; + +private: + MumeTimeEnum m_timeOfDay = MumeTimeEnum::UNKNOWN; + MumeMoonPhaseEnum m_moonPhase = MumeMoonPhaseEnum::UNKNOWN; + MumeMoonVisibilityEnum m_moonVisibility = MumeMoonVisibilityEnum::UNKNOWN; + MumeSeasonEnum m_season = MumeSeasonEnum::UNKNOWN; + PromptWeatherEnum m_weather = PromptWeatherEnum::NICE; + PromptFogEnum m_fog = PromptFogEnum::NO_FOG; + public: void observeConnected(); void observeSentToMud(const QString &ba); void observeSentToUser(const QString &ba); void observeSentToUserGmcp(const GmcpMessage &m); void observeToggledEchoMode(bool echo); + + void observeTimeOfDay(MumeTimeEnum timeOfDay); + void observeMoonPhase(MumeMoonPhaseEnum moonPhase); + void observeMoonVisibility(MumeMoonVisibilityEnum moonVisibility); + void observeSeason(MumeSeasonEnum season); + void observeWeather(PromptWeatherEnum weather); + void observeFog(PromptFogEnum fog); + void observeTick(const MumeMoment &moment) { sig2_tick.invoke(moment); } + +public: + MumeTimeEnum getTimeOfDay() const { return m_timeOfDay; } + MumeMoonPhaseEnum getMoonPhase() const { return m_moonPhase; } + MumeMoonVisibilityEnum getMoonVisibility() const { return m_moonVisibility; } + MumeSeasonEnum getSeason() const { return m_season; } + PromptWeatherEnum getWeather() const { return m_weather; } + PromptFogEnum getFog() const { return m_fog; } }; diff --git a/src/parser/mumexmlparser-gmcp.cpp b/src/parser/mumexmlparser-gmcp.cpp index 9606d0b3f..f033f8653 100644 --- a/src/parser/mumexmlparser-gmcp.cpp +++ b/src/parser/mumexmlparser-gmcp.cpp @@ -357,6 +357,18 @@ void MumeXmlParser::parseGmcpCharVitals(const JsonObj &obj) qWarning().noquote() << "prompt has unknown weather flag:" << *weather; } } + + const auto fog = promptFlags.getFogType(); + if (fog != m_fog) { + m_fog = fog; + m_observer.observeFog(m_fog); + } + + const auto weather = promptFlags.getWeatherType(); + if (weather != m_weather) { + m_weather = weather; + m_observer.observeWeather(m_weather); + } } void MumeXmlParser::parseGmcpEventMoved(const JsonObj &obj) diff --git a/src/parser/mumexmlparser.cpp b/src/parser/mumexmlparser.cpp index 5011cefdd..4bd2c7b05 100644 --- a/src/parser/mumexmlparser.cpp +++ b/src/parser/mumexmlparser.cpp @@ -65,10 +65,12 @@ MumeXmlParser::MumeXmlParser(MapData &md, ProxyMudConnectionApi & /*proxyMudConnection*/, ProxyUserGmcpApi &proxyGmcp, GroupManagerApi &group, + GameObserver &observer, QObject *parent, AbstractParserOutputs &outputs, ParserCommonData &parserCommonData) : MumeXmlParserBase{parent, mc, md, group, proxyGmcp, outputs, parserCommonData} + , m_observer{observer} {} MumeXmlParser::~MumeXmlParser() = default; diff --git a/src/parser/mumexmlparser.h b/src/parser/mumexmlparser.h index 4f0c82425..62f321749 100644 --- a/src/parser/mumexmlparser.h +++ b/src/parser/mumexmlparser.h @@ -7,6 +7,8 @@ #include "../global/Charset.h" #include "../map/CommandId.h" +#include "../map/PromptFlags.h" +#include "../observer/gameobserver.h" #include "LineFlags.h" #include "abstractparser.h" @@ -18,7 +20,6 @@ #include #include #include - class GmcpMessage; class GroupManagerApi; class JsonObj; @@ -57,6 +58,9 @@ class NODISCARD_QOBJECT MumeXmlParser final : public MumeXmlParserBase bool m_exitsReady = false; bool m_descriptionReady = false; bool m_eventReady = false; + PromptWeatherEnum m_weather = PromptWeatherEnum::NICE; + PromptFogEnum m_fog = PromptFogEnum::NO_FOG; + GameObserver &m_observer; private: enum class NODISCARD XmlAttributeStateEnum : uint8_t { @@ -80,6 +84,7 @@ class NODISCARD_QOBJECT MumeXmlParser final : public MumeXmlParserBase ProxyMudConnectionApi &, ProxyUserGmcpApi &, GroupManagerApi &, + GameObserver &, QObject *parent, AbstractParserOutputs &outputs, ParserCommonData &parserCommonData); diff --git a/src/proxy/proxy.cpp b/src/proxy/proxy.cpp index 883666589..eb212b904 100644 --- a/src/proxy/proxy.cpp +++ b/src/proxy/proxy.cpp @@ -21,6 +21,7 @@ #include "../map/parseevent.h" #include "../mpi/mpifilter.h" #include "../mpi/remoteedit.h" +#include "../observer/gameobserver.h" #include "../parser/abstractparser.h" #include "../parser/mumexmlparser.h" #include "../pathmachine/mmapper2pathmachine.h" @@ -720,6 +721,7 @@ void Proxy::allocParser() deref(conn), deref(gmcp), m_groupManager.getGroupManagerApi(), + m_gameObserver, this, deref(out), deref(parserCommon)); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a8c528313..a041cf1b7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -25,6 +25,8 @@ endif() # Clock set(clock_SRCS + ../src/configuration/configuration.cpp + ../src/configuration/configuration.h ../src/clock/mumeclock.cpp ../src/clock/mumeclock.h ../src/clock/mumemoment.cpp @@ -39,6 +41,7 @@ add_executable(TestClock ${TestClock_SRCS} ${clock_SRCS}) add_dependencies(TestClock mm_global) target_link_libraries(TestClock mm_global + Qt6::Network Qt6::Gui Qt6::Test Qt6::Widgets diff --git a/tests/testclock.cpp b/tests/testclock.cpp index 76f189a9b..06ec3a2ba 100644 --- a/tests/testclock.cpp +++ b/tests/testclock.cpp @@ -4,6 +4,7 @@ #include "testclock.h" #include "../src/clock/mumeclock.h" +#include "../src/configuration/configuration.h" #include "../src/global/HideQDebug.h" #include "../src/observer/gameobserver.h" #include "../src/proxy/GmcpMessage.h" @@ -11,7 +12,10 @@ #include #include -TestClock::TestClock() = default; +TestClock::TestClock() +{ + setEnteredMain(); +} TestClock::~TestClock() = default; @@ -244,6 +248,16 @@ void TestClock::parseWeatherTest() clock.onUserGmcp(GmcpMessage::fromRawBytes(R"(Event.Moon {"what":"rise"})")); QCOMPARE(clock.toMumeTime(clock.getMumeMoment()), expectedTime); + + QCOMPARE(static_cast(observer.getTimeOfDay()), static_cast(MumeTimeEnum::NIGHT)); + + QCOMPARE(static_cast(observer.getMoonPhase()), + static_cast(MumeMoonPhaseEnum::WANING_GIBBOUS)); + + QCOMPARE(static_cast(observer.getMoonVisibility()), + static_cast(MumeMoonVisibilityEnum::BRIGHT)); + + QCOMPARE(static_cast(observer.getSeason()), static_cast(MumeSeasonEnum::SUMMER)); } void TestClock::parseClockTimeTest()