Skip to content

Commit

Permalink
add Mud Server Protocol (Amend) to handle game time
Browse files Browse the repository at this point in the history
  • Loading branch information
Mattias-Viklund authored and nschimme committed Feb 11, 2024
1 parent a53f3d6 commit 43e3e9f
Show file tree
Hide file tree
Showing 14 changed files with 279 additions and 3 deletions.
34 changes: 34 additions & 0 deletions src/clock/mumeclock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,31 @@ void MumeClock::parseClockTime(const QString &clockTime, const int64_t secsSince
m_mumeStartEpoch = newStartEpoch;
}

void MumeClock::parseMSSP(const int year, const int month, const int day, const int hour)
{
// We should not parse the fuzzy MSSP time if we already have a greater precision.
if (getPrecision() > MumeClockPrecisionEnum::DAY)
return;

const int64_t secsSinceEpoch = QDateTime::QDateTime::currentDateTimeUtc().toSecsSinceEpoch();

auto moment = getMumeMoment();
moment.year = year;
moment.month = month;
moment.day = day;
moment.hour = hour;
// Don't override minute, since we don't get the from the MSSP time.

const int64_t newStartEpoch = secsSinceEpoch - moment.toSeconds();
m_mumeStartEpoch = newStartEpoch;

// Update last sync timestamp
setLastSyncEpoch(secsSinceEpoch);

setPrecision(MumeClockPrecisionEnum::HOUR);
log("Synchronized clock using MSSP");
}

// TODO: move this somewhere useful?
NODISCARD static const char *getOrdinalSuffix(const int day)
{
Expand Down Expand Up @@ -410,3 +435,12 @@ MumeClock::DawnDusk MumeClock::getDawnDusk(const int month)
const auto m = static_cast<uint32_t>(month);
return DawnDusk{s_dawnHour.at(m), s_duskHour.at(m)};
}

int MumeClock::getMumeMonth(const QString &monthName)
{
const int month = s_westronMonthNames.keyToValue(monthName.toLatin1().data());
if (month == static_cast<int>(WestronMonthNamesEnum::UnknownWestronMonth)) {
return s_sindarinMonthNames.keysToValue(monthName.toLatin1().data());
}
return month;
}
4 changes: 3 additions & 1 deletion src/clock/mumeclock.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,11 @@ public slots:

public:
void setPrecision(const MumeClockPrecisionEnum state);

void setLastSyncEpoch(int64_t epoch) { m_lastSyncEpoch = epoch; }

int getMumeMonth(const QString &monthName);
void parseMSSP(const int year, const int month, const int day, const int hour);

protected:
void parseMumeTime(const QString &mumeTime, int64_t secsSinceEpoch);

Expand Down
5 changes: 3 additions & 2 deletions src/clock/mumeclockwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ void MumeClockWidget::slot_updateLabel()
QString statusTip = "";
if (precision <= MumeClockPrecisionEnum::UNSET) {
styleSheet = "padding-left:1px;padding-right:1px;color:white;background:grey";
statusTip = "The clock has not synced with MUME! Click to override at your own risk.";
} else if (time == MumeTimeEnum::DAWN) {
styleSheet = "padding-left:1px;padding-right:1px;color:white;background:red";
statusTip = "Ticks left until day";
Expand All @@ -150,11 +149,13 @@ void MumeClockWidget::slot_updateLabel()
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;
}
if (precision <= MumeClockPrecisionEnum::DAY) {
if (precision <= MumeClockPrecisionEnum::HOUR) {
// Prepend warning emoji to countdown
timeLabel->setText(QString::fromUtf8("\xe2\x9a\xa0").append(m_clock->toCountdown(moment)));
} else
Expand Down
2 changes: 2 additions & 0 deletions src/parser/abstractparser.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ protected slots:

void showHeader(const QString &s);

void receiveMudServerStatus(const QByteArray &);

NODISCARD ExitDirEnum tryGetDir(StringView &words);

void parseSetCommand(StringView view);
Expand Down
13 changes: 13 additions & 0 deletions src/proxy/AbstractTelnet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,17 @@ void AbstractTelnet::sendGmcpMessage(const GmcpMessage &msg)
s.addSubnegEnd();
}

void AbstractTelnet::sendMudServerStatus(const QByteArray &data)
{
if (debug)
qDebug() << "Sending MSSP:" << data;

TelnetFormatter s{*this};
s.addSubnegBegin(OPT_MSSP);
s.addEscapedBytes(data);
s.addSubnegEnd();
}

void AbstractTelnet::sendLineModeEdit()
{
if (debug)
Expand Down Expand Up @@ -704,6 +715,8 @@ void AbstractTelnet::processTelnetSubnegotiation(const AppendBuffer &payload)
if (hisOptionState[OPT_MSSP]) {
if (debug)
qDebug() << "Received MSSP message" << payload;

receiveMudServerStatus(payload);
}
break;

Expand Down
5 changes: 5 additions & 0 deletions src/proxy/AbstractTelnet.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ static constexpr const uint8_t TNSB_SEND = 1;
static constexpr const uint8_t TNSB_REQUEST = 1;
static constexpr const uint8_t TNSB_MODE = 1;
static constexpr const uint8_t TNSB_EDIT = 1;
static constexpr const uint8_t TNSB_MSSP_VAR = 1;
static constexpr const uint8_t TNSB_MSSP_VAL = 2;
static constexpr const uint8_t TNSB_ACCEPTED = 2;
static constexpr const uint8_t TNSB_REJECTED = 3;
static constexpr const uint8_t TNSB_TTABLE_IS = 4;
Expand Down Expand Up @@ -122,6 +124,7 @@ class AbstractTelnet : public QObject
void sendWindowSizeChanged(int, int);
void sendTerminalTypeRequest();
void sendGmcpMessage(const GmcpMessage &msg);
void sendMudServerStatus(const QByteArray &);
void sendLineModeEdit();
void requestTelnetOption(unsigned char type, unsigned char subnegBuffer);

Expand All @@ -134,6 +137,7 @@ class AbstractTelnet : public QObject
virtual void virt_receiveEchoMode(bool) {}
virtual void virt_receiveGmcpMessage(const GmcpMessage &) {}
virtual void virt_receiveTerminalType(const QByteArray &) {}
virtual void virt_receiveMudServerStatus(const QByteArray &) {}
virtual void virt_receiveWindowSize(int, int) {}
/// Send out the data. Does not double IACs, this must be done
/// by caller if needed. This function is suitable for sending
Expand All @@ -146,6 +150,7 @@ class AbstractTelnet : public QObject
void receiveEchoMode(bool b) { virt_receiveEchoMode(b); }
void receiveGmcpMessage(const GmcpMessage &msg) { virt_receiveGmcpMessage(msg); }
void receiveTerminalType(const QByteArray &ba) { virt_receiveTerminalType(ba); }
void receiveMudServerStatus(const QByteArray &ba) { virt_receiveMudServerStatus(ba); }
void receiveWindowSize(int x, int y) { virt_receiveWindowSize(x, y); }

/// Send out the data. Does not double IACs, this must be done
Expand Down
127 changes: 127 additions & 0 deletions src/proxy/MudTelnet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ void MudTelnet::virt_receiveGmcpMessage(const GmcpMessage &msg)
emit sig_relayGmcp(msg);
}

void MudTelnet::virt_receiveMudServerStatus(const QByteArray &ba)
{
parseMudServerStatus(ba);
emit sig_sendMSSPToUser(ba);
}

void MudTelnet::virt_onGmcpEnabled()
{
if (debug)
Expand Down Expand Up @@ -215,3 +221,124 @@ void MudTelnet::sendCoreSupports()

sendGmcpMessage(GmcpMessage(GmcpMessageTypeEnum::CORE_SUPPORTS_SET, set));
}

void MudTelnet::parseMudServerStatus(const QByteArray &data)
{
std::map<std::string, std::list<std::string>> map;

std::optional<std::string> varName = std::nullopt;
std::list<std::string> vals;

enum class NODISCARD MSSPStateEnum {
///
BEGIN,
/// VAR
IN_VAR,
/// VAL
IN_VAL
} state
= MSSPStateEnum::BEGIN;

AppendBuffer buffer;

const auto addValue([&map, &vals, &varName, &buffer, this]() {
// Put it into the map.
if (debug)
qDebug() << "MSSP received value" << ::toQByteArrayLatin1(buffer.toStdString())
<< "for variable" << ::toQByteArrayLatin1(varName.value());

vals.push_back(buffer.toStdString());
map[varName.value()] = vals;

buffer.clear();
});

for (int i = 0; i < data.size(); i++) {
switch (state) {
case MSSPStateEnum::BEGIN:
if (data.at(i) != TNSB_MSSP_VAR)
continue;
state = MSSPStateEnum::IN_VAR;
break;

case MSSPStateEnum::IN_VAR:
switch (data.at(i)) {
case TNSB_MSSP_VAR:
case TN_IAC:
case 0:
continue;

case TNSB_MSSP_VAL: {
if (buffer.isEmpty()) {
if (debug)
qDebug() << "MSSP received variable without any name; ignoring it";
continue;
}

if (debug)
qDebug() << "MSSP received variable"
<< ::toQByteArrayLatin1(buffer.toStdString());

varName = buffer.toStdString();
state = MSSPStateEnum::IN_VAL;

vals.clear(); // Which means this is a new value, so clear the list.
buffer.clear();
} break;

default:
buffer.append(static_cast<uint8_t>(data.at(i)));
}
break;

case MSSPStateEnum::IN_VAL: {
assert(varName.has_value());

switch (data.at(i)) {
case TN_IAC:
case 0:
continue;

case TNSB_MSSP_VAR:
state = MSSPStateEnum::IN_VAR;
FALLTHROUGH;
case TNSB_MSSP_VAL:
addValue();
break;

default:
buffer.append(static_cast<uint8_t>(data.at(i)));
break;
}
break;

Check notice on line 314 in src/proxy/MudTelnet.cpp

View check run for this annotation

codefactor.io / CodeFactor

src/proxy/MudTelnet.cpp#L314

Redundant blank line at the end of a code block should be deleted. (whitespace/blank_line)
} break;
}
}

if (varName.has_value() && !buffer.isEmpty())
addValue();

// Parse game time from MSSP
const auto firstElement(
[](const std::list<std::string> &elements) -> std::optional<std::string> {
return elements.empty() ? std::nullopt : std::optional<std::string>{elements.front()};
});
const auto yearStr = firstElement(map["GAME YEAR"]);
const auto monthStr = firstElement(map["GAME MONTH"]);
const auto dayStr = firstElement(map["GAME DAY"]);
const auto hourStr = firstElement(map["GAME HOUR"]);

qInfo() << "MSSP game time received with"
<< "year:" << ::toQByteArrayLatin1(yearStr.value_or("unknown"))
<< "month:" << ::toQByteArrayLatin1(monthStr.value_or("unknown"))
<< "day:" << ::toQByteArrayLatin1(dayStr.value_or("unknown"))
<< "hour:" << ::toQByteArrayLatin1(hourStr.value_or("unknown"));

if (yearStr.has_value() && monthStr.has_value() && dayStr.has_value() && hourStr.has_value()) {
const int year = stoi(yearStr.value());
const int day = stoi(dayStr.value());
const int hour = stoi(hourStr.value());
emit sig_sendGameTimeToClock(year, monthStr.value(), day, hour);
}

Check notice on line 343 in src/proxy/MudTelnet.cpp

View check run for this annotation

codefactor.io / CodeFactor

src/proxy/MudTelnet.cpp#L225-L343

Complex Method
}
7 changes: 7 additions & 0 deletions src/proxy/MudTelnet.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,24 @@ public slots:
void sig_sendToSocket(const QByteArray &);
void sig_relayEchoMode(bool);
void sig_relayGmcp(const GmcpMessage &);
void sig_sendMSSPToUser(const QByteArray &);
void sig_sendGameTimeToClock(const int year,
const std::string &month,
const int day,
const int hour);

private:
void virt_sendToMapper(const QByteArray &data, bool goAhead) final;
void virt_receiveEchoMode(bool toggle) final;
void virt_receiveGmcpMessage(const GmcpMessage &) final;
void virt_receiveMudServerStatus(const QByteArray &) final;
void virt_onGmcpEnabled() final;
void virt_sendRawData(const std::string_view data) final;

private:
void receiveGmcpModule(const GmcpModule &, bool);
void resetGmcpModules();
void parseMudServerStatus(const QByteArray &);

private:
void sendCoreSupports();
Expand Down
5 changes: 5 additions & 0 deletions src/proxy/UserTelnet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ void UserTelnet::slot_onGmcpToUser(const GmcpMessage &msg)
}
}

void UserTelnet::slot_onSendMSSPToUser(const QByteArray &data)
{
sendMudServerStatus(data);
}

void UserTelnet::virt_sendToMapper(const QByteArray &data, const bool goAhead)
{
// MMapper requires all data to be Latin-1 internally
Expand Down
1 change: 1 addition & 0 deletions src/proxy/UserTelnet.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public slots:
void slot_onConnected();
void slot_onRelayEchoMode(bool);
void slot_onGmcpToUser(const GmcpMessage &);
void slot_onSendMSSPToUser(const QByteArray &);

signals:
void sig_analyzeUserStream(const QByteArray &, bool goAhead);
Expand Down
19 changes: 19 additions & 0 deletions src/proxy/proxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ void Proxy::slot_start()
connect(mudTelnet, &MudTelnet::sig_sendToSocket, this, &Proxy::slot_onSendToMudSocket);
connect(mudTelnet, &MudTelnet::sig_relayEchoMode, userTelnet, &UserTelnet::slot_onRelayEchoMode);
connect(mudTelnet, &MudTelnet::sig_relayGmcp, userTelnet, &UserTelnet::slot_onGmcpToUser);
connect(mudTelnet,
&MudTelnet::sig_sendGameTimeToClock,
this,
&Proxy::slot_onSendGameTimeToClock);
connect(mudTelnet,
&MudTelnet::sig_sendMSSPToUser,
userTelnet,
&UserTelnet::slot_onSendMSSPToUser);

connect(mudTelnet,
&MudTelnet::sig_relayGmcp,
&m_groupManager,
Expand Down Expand Up @@ -565,3 +574,13 @@ bool Proxy::isGmcpModuleEnabled(const GmcpModuleTypeEnum &module) const
{
return m_userTelnet->isGmcpModuleEnabled(module);
}

void Proxy::slot_onSendGameTimeToClock(const int year,
const std::string &monthStr,
const int day,
const int hour)
{
// Month from MSSP comes as a string, so fetch the month index.
const int month = m_mumeClock.getMumeMonth(::toQStringLatin1(monthStr));
m_mumeClock.parseMSSP(year, month, day, hour);
}
5 changes: 5 additions & 0 deletions src/proxy/proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ public slots:
void slot_onMudError(const QString &);
void slot_onMudConnected();

void slot_onSendGameTimeToClock(const int year,
const std::string &month,
const int day,
const int hour);

signals:
void sig_log(const QString &, const QString &);

Expand Down
Loading

0 comments on commit 43e3e9f

Please sign in to comment.