Skip to content

Commit 6ff8050

Browse files
committed
add _set weather/fog
Use the existing isAbbrev()/Abbrev class for the _set fog/weather matching instead of hand-rolled find_abbrev()/compareUtf8NoCase() case-insensitive comparison, which duplicated logic that isAbbrev() already provides and that the rest of this file relies on pervasively.
1 parent 95ac16e commit 6ff8050

5 files changed

Lines changed: 218 additions & 10 deletions

File tree

src/global/EnumIndexedArray.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class NODISCARD EnumIndexedArray : private MMapper::Array<T, SIZE_>
3434

3535
public:
3636
using base::data;
37+
using base::empty;
3738
using base::size;
3839

3940
public:

src/parser/AbstractParser-Commands.cpp

Lines changed: 207 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
#include "AbstractParser-Commands.h"
66

7-
#include "../global/AsyncTasks.h"
87
#include "../global/CaseUtils.h"
98
#include "../global/Consts.h"
109
#include "../global/LineUtils.h"
@@ -18,11 +17,11 @@
1817
#include "../map/enums.h"
1918
#include "../map/infomark.h"
2019
#include "../mapdata/mapdata.h"
20+
#include "../observer/gameobserver.h"
2121
#include "../syntax/SyntaxArgs.h"
2222
#include "../syntax/TreeParser.h"
2323
#include "../viewers/AnsiViewWindow.h"
2424
#include "../viewers/LaunchAsyncViewer.h"
25-
#include "../viewers/TopLevelWindows.h"
2625
#include "Abbrev.h"
2726
#include "AbstractParser-Utils.h"
2827
#include "DoorAction.h"
@@ -38,9 +37,158 @@
3837
#include <utility>
3938
#include <vector>
4039

41-
#include <QMessageLogContext>
4240
#include <QtCore>
4341

42+
namespace { // anonymous
43+
44+
struct NODISCARD SendToUserHelper final
45+
{
46+
private:
47+
std::ostringstream m_oss;
48+
AnsiOstream m_aos{m_oss};
49+
AbstractParser &m_self;
50+
SendToUserSourceEnum m_source = SendToUserSourceEnum::FromMMapper;
51+
52+
public:
53+
explicit SendToUserHelper(AbstractParser &self,
54+
SendToUserSourceEnum src = SendToUserSourceEnum::FromMMapper)
55+
: m_self{self}
56+
, m_source{src}
57+
{}
58+
DELETE_CTORS_AND_ASSIGN_OPS(SendToUserHelper);
59+
~SendToUserHelper()
60+
{
61+
if (!m_aos.hasNewline()) {
62+
m_aos.writeNewline();
63+
}
64+
m_self.sendToUser(m_source, m_oss.str());
65+
}
66+
NODISCARD AnsiOstream &getAnsiOstream() { return m_aos; }
67+
};
68+
69+
template<typename Container>
70+
NODISCARD auto find_abbrev(const Container &container,
71+
const StringView input,
72+
const size_t required_len)
73+
-> std::optional<typename Container::index_type>
74+
{
75+
using E = typename Container::index_type;
76+
for (const std::string_view w : container) {
77+
if (required_len <= w.size() && isAbbrev(input, w, static_cast<int>(required_len))) {
78+
if (std::optional<E> opt = container.findIndexOf(w)) {
79+
return opt;
80+
}
81+
}
82+
}
83+
return std::nullopt;
84+
};
85+
86+
template<typename Container>
87+
void concat_comma_and_or(AnsiOstream &aos,
88+
const Container &container,
89+
// color each word int he container
90+
const RawAnsi &ansi,
91+
// e.g. "and" or "or"
92+
const std::string_view and_or)
93+
{
94+
using E = typename Container::index_type;
95+
if (container.empty()) {
96+
return; // This is probably an error, so maybe it should throw or print "(none)" ?
97+
}
98+
const size_t size = container.size();
99+
for (size_t i = 0; i < size; ++i) {
100+
if (i != 0) {
101+
aos << ", ";
102+
if (i + 1 == size && !and_or.empty()) {
103+
aos << and_or << " ";
104+
}
105+
}
106+
const auto e = static_cast<E>(i);
107+
aos << ColoredValue{ansi, container[e]};
108+
}
109+
}
110+
111+
// one or more question marks
112+
template<typename T>
113+
NODISCARD bool is_question_marks(const T word)
114+
{
115+
if constexpr (std::is_same_v<T, std::string_view>) {
116+
return !word.empty() && std::all_of(word.begin(), word.end(), [](char c) -> bool {
117+
return c == char_consts::C_QUESTION_MARK;
118+
});
119+
} else if constexpr (std::is_same_v<T, StringView>) {
120+
return is_question_marks<std::string_view>(word.getStdStringView());
121+
} else {
122+
static_assert(std::is_same_v<T, void>, "unsupported word type");
123+
std::abort();
124+
}
125+
}
126+
127+
constexpr auto green = getRawAnsi(AnsiColor16Enum::green);
128+
constexpr auto red = getRawAnsi(AnsiColor16Enum::red);
129+
constexpr auto yellow = getRawAnsi(AnsiColor16Enum::yellow);
130+
131+
constexpr EnumIndexedArray<std::string_view, PromptFogEnum, NUM_PROMPT_FOG_TYPES> all_fog_names{
132+
#define X_CASE(_x) std::string_view{#_x},
133+
XFOREACH_PROMPT_FOG_ENUM(X_CASE)
134+
#undef X_CASE
135+
};
136+
constexpr EnumIndexedArray<std::string_view, PromptWeatherEnum, NUM_PROMPT_WEATHER_TYPES>
137+
all_weather_names{
138+
#define X_CASE(_x) std::string_view{#_x},
139+
XFOREACH_PROMPT_WEATHER_ENUM(X_CASE)
140+
#undef X_CASE
141+
};
142+
143+
template<typename Container, typename Getter, typename Setter>
144+
void trySetEnumValue(AbstractParser &parser,
145+
StringView view,
146+
const std::string_view what,
147+
const Container &enum_names,
148+
const size_t required_len,
149+
Getter &&get,
150+
Setter &&set)
151+
{
152+
using E = typename Container::index_type;
153+
154+
if (view.isEmpty()) {
155+
SendToUserHelper helper{parser};
156+
AnsiOstream &aos = helper.getAnsiOstream();
157+
aos << "The current " << what << " is: " << ColoredValue{green, get()} << ".";
158+
return;
159+
}
160+
161+
const auto next = view.takeFirstWord();
162+
const auto input_sv = next.getStdStringView();
163+
164+
if (const std::optional<E> opt = find_abbrev(enum_names, next, required_len)) {
165+
// success
166+
const E e = *opt;
167+
{
168+
SendToUserHelper helper{parser};
169+
AnsiOstream &aos = helper.getAnsiOstream();
170+
aos << "Setting " << what << " to " << ColoredValue{green, enum_names[e]} << "...";
171+
}
172+
set(e);
173+
} else {
174+
// failure
175+
SendToUserHelper helper{parser};
176+
AnsiOstream &aos = helper.getAnsiOstream();
177+
178+
if (!is_question_marks(input_sv)) {
179+
aos << "Error: Unrecognized " << what
180+
<< " option: " << ColoredQuotedStringView{red, yellow, input_sv} << "."
181+
<< AnsiOstream::endl;
182+
}
183+
184+
aos << "Valid " << what << " options: ";
185+
concat_comma_and_or(aos, enum_names, green, "or");
186+
aos << "." << AnsiOstream::endl;
187+
}
188+
}
189+
190+
} // namespace
191+
44192
const Abbrev cmdBack{"back"};
45193
const Abbrev cmdConfig{"config", 4};
46194
const Abbrev cmdConnect{"connect", 4};
@@ -515,13 +663,36 @@ bool AbstractParser::parseDoorAction(const DoorActionEnum dat, StringView words)
515663

516664
void AbstractParser::parseSetCommand(StringView view)
517665
{
666+
auto show_set_syntax = [this] {
667+
SendToUserHelper helper{*this};
668+
AnsiOstream &aos = helper.getAnsiOstream();
669+
670+
aos << "Syntax:" << AnsiOstream::endl;
671+
aos << " " << getPrefixChar() << "set ..." << AnsiOstream::endl;
672+
{
673+
aos << " ... fog [";
674+
concat_comma_and_or(aos, all_fog_names, green, "or");
675+
aos << "]" << AnsiOstream::endl;
676+
}
677+
aos << " ... prefix [punct-char]" << AnsiOstream::endl;
678+
{
679+
aos << " ... weather [";
680+
concat_comma_and_or(aos, all_weather_names, green, "or");
681+
aos << "]" << AnsiOstream::endl;
682+
}
683+
};
684+
518685
if (view.isEmpty()) {
519-
sendToUser(SendToUserSourceEnum::FromMMapper,
520-
QString("Syntax: %1set prefix [punct-char]\n").arg(getPrefixChar()));
686+
show_set_syntax();
521687
return;
522688
}
523689

524690
auto first = view.takeFirstWord();
691+
if (is_question_marks(first)) {
692+
show_set_syntax();
693+
return;
694+
}
695+
525696
if (Abbrev{"prefix", 3}.matches(first)) {
526697
if (view.isEmpty()) {
527698
showCommandPrefix();
@@ -550,7 +721,37 @@ void AbstractParser::parseSetCommand(StringView view)
550721
return;
551722
}
552723

553-
sendToUser(SendToUserSourceEnum::FromMMapper, "That variable is not supported.");
724+
if (Abbrev{"fog", 3}.matches(first)) {
725+
trySetEnumValue(
726+
*this,
727+
view,
728+
"fog",
729+
all_fog_names,
730+
2, // "no" should match "no_fog"
731+
[this]() -> std::string_view { return to_string_view(m_gameObserver.getFog()); },
732+
[this](const PromptFogEnum e) { m_gameObserver.observeFog(e); });
733+
return;
734+
}
735+
736+
if (Abbrev{"weather", 3}.matches(first)) {
737+
trySetEnumValue(
738+
*this,
739+
view,
740+
"weather",
741+
all_weather_names,
742+
3,
743+
[this]() -> std::string_view { return to_string_view(m_gameObserver.getWeather()); },
744+
[this](const PromptWeatherEnum e) { m_gameObserver.observeWeather(e); });
745+
return;
746+
}
747+
748+
{
749+
SendToUserHelper helper{*this};
750+
AnsiOstream &aos = helper.getAnsiOstream();
751+
aos << "Error: " << ColoredQuotedStringView{red, yellow, first.getStdStringView()}
752+
<< " is not a valid variable name." << AnsiOstream::endl;
753+
}
754+
show_set_syntax();
554755
}
555756

556757
void AbstractParser::parseSpecialCommand(StringView wholeCommand)

src/parser/abstractparser.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,11 @@ AbstractParser::AbstractParser(MapData &md,
163163
HotkeyManager &hm,
164164
QObject *const parent,
165165
AbstractParserOutputs &outputs,
166-
ParserCommonData &commonData)
166+
ParserCommonData &commonData,
167+
GameObserver &gameObserver)
167168
: ParserCommon{parent, mc, md, group, hm, proxyUserGmcp, outputs, commonData}
168169
, m_proxyMudConnection{proxyMudConnection}
170+
, m_gameObserver{gameObserver}
169171
{
170172
QObject::connect(&m_offlineCommandTimer, &QTimer::timeout, this, [this]() {
171173
doOfflineCharacterMove();

src/parser/abstractparser.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,14 @@
4444
#include <QTimer>
4545
#include <QVariant>
4646

47+
class CTimers;
4748
class Coordinate;
49+
class GameObserver;
4850
class HotkeyManager;
4951
class MapData;
5052
class MumeClock;
5153
class RoomFieldVariant;
5254
class RoomFilter;
53-
class CTimers;
5455

5556
namespace syntax {
5657
class Sublist;
@@ -344,6 +345,7 @@ class NODISCARD_QOBJECT AbstractParser final : public ParserCommon
344345

345346
private:
346347
QTimer m_offlineCommandTimer;
348+
GameObserver &m_gameObserver;
347349

348350
public:
349351
explicit AbstractParser(MapData &,
@@ -354,7 +356,8 @@ class NODISCARD_QOBJECT AbstractParser final : public ParserCommon
354356
HotkeyManager &,
355357
QObject *parent,
356358
AbstractParserOutputs &outputs,
357-
ParserCommonData &commonData);
359+
ParserCommonData &commonData,
360+
GameObserver &gameObserver);
358361
~AbstractParser() override;
359362

360363
void doMove(CommandEnum cmd);

src/proxy/proxy.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,8 @@ void Proxy::allocParser()
755755
m_mainWindow.getHotkeyManager(),
756756
this,
757757
deref(out),
758-
deref(parserCommon));
758+
deref(parserCommon),
759+
m_gameObserver);
759760

760761
/* The login credentials are fetched asynchronously because the OS will prompt the user for permission */
761762
pipe.mud.passwordConfig = std::make_unique<PasswordConfig>(this);

0 commit comments

Comments
 (0)