diff --git a/src/global/EnumIndexedArray.h b/src/global/EnumIndexedArray.h index 16d41f3d7..0e769d86e 100644 --- a/src/global/EnumIndexedArray.h +++ b/src/global/EnumIndexedArray.h @@ -34,6 +34,7 @@ class NODISCARD EnumIndexedArray : private MMapper::Array public: using base::data; + using base::empty; using base::size; public: diff --git a/src/parser/AbstractParser-Commands.cpp b/src/parser/AbstractParser-Commands.cpp index 44632e9d1..e0f25cdfc 100644 --- a/src/parser/AbstractParser-Commands.cpp +++ b/src/parser/AbstractParser-Commands.cpp @@ -4,7 +4,6 @@ #include "AbstractParser-Commands.h" -#include "../global/AsyncTasks.h" #include "../global/CaseUtils.h" #include "../global/Consts.h" #include "../global/LineUtils.h" @@ -18,11 +17,11 @@ #include "../map/enums.h" #include "../map/infomark.h" #include "../mapdata/mapdata.h" +#include "../observer/gameobserver.h" #include "../syntax/SyntaxArgs.h" #include "../syntax/TreeParser.h" #include "../viewers/AnsiViewWindow.h" #include "../viewers/LaunchAsyncViewer.h" -#include "../viewers/TopLevelWindows.h" #include "Abbrev.h" #include "AbstractParser-Utils.h" #include "DoorAction.h" @@ -38,9 +37,158 @@ #include #include -#include #include +namespace { // anonymous + +struct NODISCARD SendToUserHelper final +{ +private: + std::ostringstream m_oss; + AnsiOstream m_aos{m_oss}; + AbstractParser &m_self; + SendToUserSourceEnum m_source = SendToUserSourceEnum::FromMMapper; + +public: + explicit SendToUserHelper(AbstractParser &self, + SendToUserSourceEnum src = SendToUserSourceEnum::FromMMapper) + : m_self{self} + , m_source{src} + {} + DELETE_CTORS_AND_ASSIGN_OPS(SendToUserHelper); + ~SendToUserHelper() + { + if (!m_aos.hasNewline()) { + m_aos.writeNewline(); + } + m_self.sendToUser(m_source, m_oss.str()); + } + NODISCARD AnsiOstream &getAnsiOstream() { return m_aos; } +}; + +template +NODISCARD auto find_abbrev(const Container &container, + const StringView input, + const size_t required_len) + -> std::optional +{ + using E = typename Container::index_type; + for (const std::string_view w : container) { + if (required_len <= w.size() && isAbbrev(input, w, static_cast(required_len))) { + if (std::optional opt = container.findIndexOf(w)) { + return opt; + } + } + } + return std::nullopt; +}; + +template +void concat_comma_and_or(AnsiOstream &aos, + const Container &container, + // color each word int he container + const RawAnsi &ansi, + // e.g. "and" or "or" + const std::string_view and_or) +{ + using E = typename Container::index_type; + if (container.empty()) { + return; // This is probably an error, so maybe it should throw or print "(none)" ? + } + const size_t size = container.size(); + for (size_t i = 0; i < size; ++i) { + if (i != 0) { + aos << ", "; + if (i + 1 == size && !and_or.empty()) { + aos << and_or << " "; + } + } + const auto e = static_cast(i); + aos << ColoredValue{ansi, container[e]}; + } +} + +// one or more question marks +template +NODISCARD bool is_question_marks(const T word) +{ + if constexpr (std::is_same_v) { + return !word.empty() && std::all_of(word.begin(), word.end(), [](char c) -> bool { + return c == char_consts::C_QUESTION_MARK; + }); + } else if constexpr (std::is_same_v) { + return is_question_marks(word.getStdStringView()); + } else { + static_assert(std::is_same_v, "unsupported word type"); + std::abort(); + } +} + +constexpr auto green = getRawAnsi(AnsiColor16Enum::green); +constexpr auto red = getRawAnsi(AnsiColor16Enum::red); +constexpr auto yellow = getRawAnsi(AnsiColor16Enum::yellow); + +constexpr EnumIndexedArray all_fog_names{ +#define X_CASE(_x) std::string_view{#_x}, + XFOREACH_PROMPT_FOG_ENUM(X_CASE) +#undef X_CASE +}; +constexpr EnumIndexedArray + all_weather_names{ +#define X_CASE(_x) std::string_view{#_x}, + XFOREACH_PROMPT_WEATHER_ENUM(X_CASE) +#undef X_CASE + }; + +template +void trySetEnumValue(AbstractParser &parser, + StringView view, + const std::string_view what, + const Container &enum_names, + const size_t required_len, + Getter &&get, + Setter &&set) +{ + using E = typename Container::index_type; + + if (view.isEmpty()) { + SendToUserHelper helper{parser}; + AnsiOstream &aos = helper.getAnsiOstream(); + aos << "The current " << what << " is: " << ColoredValue{green, get()} << "."; + return; + } + + const auto next = view.takeFirstWord(); + const auto input_sv = next.getStdStringView(); + + if (const std::optional opt = find_abbrev(enum_names, next, required_len)) { + // success + const E e = *opt; + { + SendToUserHelper helper{parser}; + AnsiOstream &aos = helper.getAnsiOstream(); + aos << "Setting " << what << " to " << ColoredValue{green, enum_names[e]} << "..."; + } + set(e); + } else { + // failure + SendToUserHelper helper{parser}; + AnsiOstream &aos = helper.getAnsiOstream(); + + if (!is_question_marks(input_sv)) { + aos << "Error: Unrecognized " << what + << " option: " << ColoredQuotedStringView{red, yellow, input_sv} << "." + << AnsiOstream::endl; + } + + aos << "Valid " << what << " options: "; + concat_comma_and_or(aos, enum_names, green, "or"); + aos << "." << AnsiOstream::endl; + } +} + +} // namespace + const Abbrev cmdBack{"back"}; const Abbrev cmdConfig{"config", 4}; const Abbrev cmdConnect{"connect", 4}; @@ -515,13 +663,36 @@ bool AbstractParser::parseDoorAction(const DoorActionEnum dat, StringView words) void AbstractParser::parseSetCommand(StringView view) { + auto show_set_syntax = [this] { + SendToUserHelper helper{*this}; + AnsiOstream &aos = helper.getAnsiOstream(); + + aos << "Syntax:" << AnsiOstream::endl; + aos << " " << getPrefixChar() << "set ..." << AnsiOstream::endl; + { + aos << " ... fog ["; + concat_comma_and_or(aos, all_fog_names, green, "or"); + aos << "]" << AnsiOstream::endl; + } + aos << " ... prefix [punct-char]" << AnsiOstream::endl; + { + aos << " ... weather ["; + concat_comma_and_or(aos, all_weather_names, green, "or"); + aos << "]" << AnsiOstream::endl; + } + }; + if (view.isEmpty()) { - sendToUser(SendToUserSourceEnum::FromMMapper, - QString("Syntax: %1set prefix [punct-char]\n").arg(getPrefixChar())); + show_set_syntax(); return; } auto first = view.takeFirstWord(); + if (is_question_marks(first)) { + show_set_syntax(); + return; + } + if (Abbrev{"prefix", 3}.matches(first)) { if (view.isEmpty()) { showCommandPrefix(); @@ -550,7 +721,37 @@ void AbstractParser::parseSetCommand(StringView view) return; } - sendToUser(SendToUserSourceEnum::FromMMapper, "That variable is not supported."); + if (Abbrev{"fog", 3}.matches(first)) { + trySetEnumValue( + *this, + view, + "fog", + all_fog_names, + 2, // "no" should match "no_fog" + [this]() -> std::string_view { return to_string_view(m_gameObserver.getFog()); }, + [this](const PromptFogEnum e) { m_gameObserver.observeFog(e); }); + return; + } + + if (Abbrev{"weather", 3}.matches(first)) { + trySetEnumValue( + *this, + view, + "weather", + all_weather_names, + 3, + [this]() -> std::string_view { return to_string_view(m_gameObserver.getWeather()); }, + [this](const PromptWeatherEnum e) { m_gameObserver.observeWeather(e); }); + return; + } + + { + SendToUserHelper helper{*this}; + AnsiOstream &aos = helper.getAnsiOstream(); + aos << "Error: " << ColoredQuotedStringView{red, yellow, first.getStdStringView()} + << " is not a valid variable name." << AnsiOstream::endl; + } + show_set_syntax(); } void AbstractParser::parseSpecialCommand(StringView wholeCommand) diff --git a/src/parser/abstractparser.cpp b/src/parser/abstractparser.cpp index 6894ca725..6e45fd180 100644 --- a/src/parser/abstractparser.cpp +++ b/src/parser/abstractparser.cpp @@ -163,9 +163,11 @@ AbstractParser::AbstractParser(MapData &md, HotkeyManager &hm, QObject *const parent, AbstractParserOutputs &outputs, - ParserCommonData &commonData) + ParserCommonData &commonData, + GameObserver &gameObserver) : ParserCommon{parent, mc, md, group, hm, proxyUserGmcp, outputs, commonData} , m_proxyMudConnection{proxyMudConnection} + , m_gameObserver{gameObserver} { QObject::connect(&m_offlineCommandTimer, &QTimer::timeout, this, [this]() { doOfflineCharacterMove(); diff --git a/src/parser/abstractparser.h b/src/parser/abstractparser.h index f8b5d325b..2cd8b467c 100644 --- a/src/parser/abstractparser.h +++ b/src/parser/abstractparser.h @@ -44,13 +44,14 @@ #include #include +class CTimers; class Coordinate; +class GameObserver; class HotkeyManager; class MapData; class MumeClock; class RoomFieldVariant; class RoomFilter; -class CTimers; namespace syntax { class Sublist; @@ -344,6 +345,7 @@ class NODISCARD_QOBJECT AbstractParser final : public ParserCommon private: QTimer m_offlineCommandTimer; + GameObserver &m_gameObserver; public: explicit AbstractParser(MapData &, @@ -354,7 +356,8 @@ class NODISCARD_QOBJECT AbstractParser final : public ParserCommon HotkeyManager &, QObject *parent, AbstractParserOutputs &outputs, - ParserCommonData &commonData); + ParserCommonData &commonData, + GameObserver &gameObserver); ~AbstractParser() override; void doMove(CommandEnum cmd); diff --git a/src/proxy/proxy.cpp b/src/proxy/proxy.cpp index e8f9611b9..0d269daaa 100644 --- a/src/proxy/proxy.cpp +++ b/src/proxy/proxy.cpp @@ -755,7 +755,8 @@ void Proxy::allocParser() m_mainWindow.getHotkeyManager(), this, deref(out), - deref(parserCommon)); + deref(parserCommon), + m_gameObserver); /* The login credentials are fetched asynchronously because the OS will prompt the user for permission */ pipe.mud.passwordConfig = std::make_unique(this);