From ec74b44087805f62672c37f4849330190ccc67da Mon Sep 17 00:00:00 2001 From: past-due <30942300+past-due@users.noreply.github.com> Date: Thu, 28 Sep 2023 16:17:00 -0400 Subject: [PATCH] Refactor in-game chat box --- src/hci.cpp | 106 +--------- src/keybind.cpp | 10 - src/screens/chatscreen.cpp | 415 +++++++++++++++++++++++++++++++++++++ src/screens/chatscreen.h | 34 +++ 4 files changed, 460 insertions(+), 105 deletions(-) create mode 100644 src/screens/chatscreen.cpp create mode 100644 src/screens/chatscreen.h diff --git a/src/hci.cpp b/src/hci.cpp index 60510062433..2951a789b5c 100644 --- a/src/hci.cpp +++ b/src/hci.cpp @@ -81,6 +81,7 @@ #include "hci/commander.h" #include "notifications.h" #include "hci/groups.h" +#include "screens/chatscreen.h" // Empty edit window static bool secondaryWindowUp = false; @@ -278,8 +279,6 @@ static void processProximityButtons(UDWORD id); // count the number of selected droids of a type static SDWORD intNumSelectedDroids(UDWORD droidType); -static void parseChatMessageModifiers(InGameChatMessage &message); - /***************************GAME CODE ****************************/ @@ -992,6 +991,11 @@ void interfaceShutDown() { i = RETBUTSTATS(); } + + shutdownChatScreen(); + ChatDialogUp = false; + + bAllowOtherKeyPresses = true; } static bool IntRefreshPending = false; @@ -1544,33 +1548,6 @@ INT_RETVAL intRunWidgets() quitting = true; break; - // process our chatbox - case CHAT_EDITBOX: - { - auto message = InGameChatMessage(selectedPlayer, widgGetString(psWScreen, CHAT_EDITBOX)); - attemptCheatCode(message.text); // parse the message - - if ((int) widgGetUserData2(psWScreen, CHAT_EDITBOX) == CHAT_TEAM) - { - message.toAllies = true; - } - else - { - parseChatMessageModifiers(message); - } - - if (strlen(message.text)) - { - message.send(); - } - - inputLoseFocus(); - bAllowOtherKeyPresses = true; - widgDelete(psWScreen, CHAT_CONSOLEBOX); - ChatDialogUp = false; - break; - } - /* Default case passes remaining IDs to appropriate function */ default: switch (intMode) @@ -3055,48 +3032,12 @@ void chatDialog(int mode) { if (!ChatDialogUp) { - auto const &parent = psWScreen->psForm; - W_CONTEXT sContext = W_CONTEXT::ZeroContext(); - - auto consoleBox = std::make_shared(); - parent->attach(consoleBox); - consoleBox->id = CHAT_CONSOLEBOX; - consoleBox->setCalcLayout(LAMBDA_CALCLAYOUT_SIMPLE({ - psWidget->setGeometry(CHAT_CONSOLEBOXX, CHAT_CONSOLEBOXY, CHAT_CONSOLEBOXW, CHAT_CONSOLEBOXH); - })); - - auto chatBox = std::make_shared(); - consoleBox->attach(chatBox); - chatBox->id = CHAT_EDITBOX; - chatBox->setGeometry(80, 2, 320, 16); - if (mode == CHAT_GLOB) - { - chatBox->setBoxColours(WZCOL_MENU_BORDER, WZCOL_MENU_BORDER, WZCOL_MENU_BACKGROUND); - widgSetUserData2(psWScreen, CHAT_EDITBOX, CHAT_GLOB); - } - else - { - chatBox->setBoxColours(WZCOL_YELLOW, WZCOL_YELLOW, WZCOL_MENU_BACKGROUND); - widgSetUserData2(psWScreen, CHAT_EDITBOX, CHAT_TEAM); - } - - auto label = std::make_shared(); - consoleBox->attach(label); - label->setGeometry(2, 2,60, 16); - if (mode == CHAT_GLOB) - { - label->setFontColour(WZCOL_TEXT_BRIGHT); - label->setString(_("Chat: All")); - } - else - { - label->setFontColour(WZCOL_YELLOW); - label->setString(_("Chat: Team")); - } - label->setTextAlignment(WLAB_ALIGNTOPLEFT); ChatDialogUp = true; - // Auto-click it - widgGetFromID(psWScreen, CHAT_EDITBOX)->clicked(&sContext); + + WzChatMode initialChatMode = (mode == CHAT_GLOB) ? WzChatMode::Glob : WzChatMode::Team; + createChatScreen([]() { + ChatDialogUp = false; + }, initialChatMode); } else { @@ -3121,31 +3062,6 @@ void setSecondaryWindowUp(bool value) secondaryWindowUp = true; } -/** - * Parse what the player types in the chat box. - * - * Messages prefixed with "." are interpreted as messages to allies. - * Messages prefixed with 1 or more digits (0-9) are interpreted as private messages to players. - * - * Examples: - * - ".hi allies" sends "hi allies" to all allies. - * - "123hi there" sends "hi there" to players 1, 2 and 3. - * - ".123hi there" sends "hi there" to allies and players 1, 2 and 3. - **/ -static void parseChatMessageModifiers(InGameChatMessage &message) -{ - if (*message.text == '.') - { - message.toAllies = true; - message.text++; - } - - for (; *message.text >= '0' && *message.text <= '9'; ++message.text) // for each 0..9 numeric char encountered - { - message.addReceiverByPosition(*message.text - '0'); - } -} - void intSetShouldShowRedundantDesign(bool value) { includeRedundantDesigns = value; diff --git a/src/keybind.cpp b/src/keybind.cpp index ab903f98937..7a36305210a 100644 --- a/src/keybind.cpp +++ b/src/keybind.cpp @@ -2006,15 +2006,10 @@ void kf_SendTeamMessage() if (bAllowOtherKeyPresses && !gamePaused()) // just starting. { - bAllowOtherKeyPresses = false; sstrcpy(sCurrentConsoleText, ""); //for beacons inputClearBuffer(); chatDialog(CHAT_TEAM); // throw up the dialog } - else - { - bAllowOtherKeyPresses = true; - } } // Chat message. NOTE THIS FUNCTION CAN DISABLE ALL OTHER KEYPRESSES @@ -2027,15 +2022,10 @@ void kf_SendGlobalMessage() if (bAllowOtherKeyPresses && !gamePaused()) // just starting. { - bAllowOtherKeyPresses = false; sstrcpy(sCurrentConsoleText, ""); //for beacons inputClearBuffer(); chatDialog(CHAT_GLOB); // throw up the dialog } - else - { - bAllowOtherKeyPresses = true; - } } // -------------------------------------------------------------------------- diff --git a/src/screens/chatscreen.cpp b/src/screens/chatscreen.cpp new file mode 100644 index 00000000000..a8ea8ed7e9f --- /dev/null +++ b/src/screens/chatscreen.cpp @@ -0,0 +1,415 @@ +/* + This file is part of Warzone 2100. + Copyright (C) 2023 Warzone 2100 Project + + Warzone 2100 is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Warzone 2100 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Warzone 2100; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#include "lib/framework/frame.h" +#include "lib/framework/math_ext.h" +#include "chatscreen.h" +#include "lib/widget/widgint.h" +#include "lib/widget/label.h" +#include "lib/widget/editbox.h" +#include "lib/ivis_opengl/pieblitfunc.h" +#include "lib/ivis_opengl/piepalette.h" +#include "lib/netplay/netplay.h" + +#include "../intdisplay.h" +#include "../display.h" +#include "../hci.h" +#include "../chat.h" +#include "../cheat.h" + +class WzInGameChatScreen_CLICKFORM; +struct WzInGameChatScreen; + +// MARK: - Globals + +static std::weak_ptr psCurrentChatScreen; + +// MARK: - WzInGameChatScreen definition + +struct WzInGameChatScreen: public W_SCREEN +{ +protected: + WzInGameChatScreen(): W_SCREEN() {} + ~WzInGameChatScreen() + { + setFocus(nullptr); + } + +public: + typedef std::function OnCloseFunc; + static std::shared_ptr make(const OnCloseFunc& onCloseFunction, WzChatMode initialChatMode); + +public: + void closeScreen(); + +private: + OnCloseFunc onCloseFunc; + +private: + WzInGameChatScreen(WzInGameChatScreen const &) = delete; + WzInGameChatScreen &operator =(WzInGameChatScreen const &) = delete; +}; + +// MARK: - WzInGameChatBoxForm + +class WzInGameChatBoxForm : public IntFormAnimated +{ +protected: + WzInGameChatBoxForm(): IntFormAnimated(true) {} + void initialize(WzChatMode initialChatMode); + +public: + static std::shared_ptr make(WzChatMode initialChatMode) + { + class make_shared_enabler: public WzInGameChatBoxForm {}; + auto widget = std::make_shared(); + widget->initialize(initialChatMode); + return widget; + } + + virtual ~WzInGameChatBoxForm(); + + virtual void focusLost() override; + +public: + void setChatMode(WzChatMode newMode); + void startChatBoxEditing(); + void endChatBoxEditing(); + +protected: + void closeParentScreen() + { + auto parentScreen = std::dynamic_pointer_cast(screenPointer.lock()); + ASSERT_OR_RETURN(, parentScreen != nullptr, "No screen?"); + parentScreen->closeScreen(); + } + +private: + WzChatMode chatMode; + std::shared_ptr chatBox; + std::shared_ptr label; +}; + +WzInGameChatBoxForm::~WzInGameChatBoxForm() +{ + // currently, no-op +} + +void WzInGameChatBoxForm::focusLost() +{ + debug(LOG_INFO, "WzInGameChatBoxForm::focusLost"); +} + +void WzInGameChatBoxForm::startChatBoxEditing() +{ + // Auto-click it + W_CONTEXT sContext = W_CONTEXT::ZeroContext(); + chatBox->clicked(&sContext); +} + +void WzInGameChatBoxForm::endChatBoxEditing() +{ + chatBox->focusLost(); +} + +void WzInGameChatBoxForm::setChatMode(WzChatMode newMode) +{ + chatMode = newMode; + + switch (chatMode) + { + case WzChatMode::Glob: + chatBox->setBoxColours(WZCOL_MENU_BORDER, WZCOL_MENU_BORDER, WZCOL_MENU_BACKGROUND); + label->setFontColour(WZCOL_TEXT_BRIGHT); + label->setString(_("Chat: All")); + break; + case WzChatMode::Team: + chatBox->setBoxColours(WZCOL_YELLOW, WZCOL_YELLOW, WZCOL_MENU_BACKGROUND); + label->setFontColour(WZCOL_YELLOW); + label->setString(_("Chat: Team")); + break; + } +} + +/** + * Parse what the player types in the chat box. + * + * Messages prefixed with "." are interpreted as messages to allies. + * Messages prefixed with 1 or more digits (0-9) are interpreted as private messages to players. + * + * Examples: + * - ".hi allies" sends "hi allies" to all allies. + * - "123hi there" sends "hi there" to players 1, 2 and 3. + * - ".123hi there" sends "hi there" to allies and players 1, 2 and 3. + **/ +static void parseChatMessageModifiers(InGameChatMessage &message) +{ + if (*message.text == '.') + { + message.toAllies = true; + message.text++; + } + + for (; *message.text >= '0' && *message.text <= '9'; ++message.text) // for each 0..9 numeric char encountered + { + message.addReceiverByPosition(*message.text - '0'); + } +} + +void WzInGameChatBoxForm::initialize(WzChatMode initialChatMode) +{ + chatBox = std::make_shared(); + attach(chatBox); + chatBox->id = CHAT_EDITBOX; + chatBox->setGeometry(80, 2, 320, 16); + + chatBox->setOnReturnHandler([](W_EDITBOX& widg) { + + auto strongParent = std::dynamic_pointer_cast(widg.parent()); + ASSERT_OR_RETURN(, strongParent != nullptr, "No parent?"); + + auto enteredText = widg.getString(); + const char* pStr = enteredText.toUtf8().c_str(); + auto message = InGameChatMessage(selectedPlayer, pStr); + attemptCheatCode(message.text); // parse the message + + switch (strongParent->chatMode) + { + case WzChatMode::Glob: + parseChatMessageModifiers(message); + break; + case WzChatMode::Team: + message.toAllies = true; + break; + } + + if (strlen(message.text)) + { + message.send(); + } + + inputLoseFocus(); + + strongParent->endChatBoxEditing(); + strongParent->closeParentScreen(); + }); + + chatBox->setOnEscapeHandler([](W_EDITBOX& widg) { + auto strongParent = std::dynamic_pointer_cast(widg.parent()); + ASSERT_OR_RETURN(, strongParent != nullptr, "No parent?"); + + inputLoseFocus(); + strongParent->closeParentScreen(); + }); + + label = std::make_shared(); + attach(label); + label->setGeometry(2, 2, 60, 16); + label->setTextAlignment(WLAB_ALIGNTOPLEFT); + + setChatMode(initialChatMode); +} + +// MARK: - WzInGameChatScreen_CLICKFORM + +class WzInGameChatScreen_CLICKFORM : public W_CLICKFORM +{ +protected: + static constexpr int32_t INTERNAL_PADDING = 15; +protected: + WzInGameChatScreen_CLICKFORM(W_FORMINIT const *init); + WzInGameChatScreen_CLICKFORM(); + ~WzInGameChatScreen_CLICKFORM(); + void initialize(WzChatMode initialChatMode); + void recalcLayout(); +public: + static std::shared_ptr make(WzChatMode initialChatMode, UDWORD formID = 0); + void clicked(W_CONTEXT *psContext, WIDGET_KEY key) override; + void display(int xOffset, int yOffset) override; + void run(W_CONTEXT *psContext) override; + void geometryChanged() override + { + recalcLayout(); + } + +private: + void clearFocusAndEditing(); + +public: + PIELIGHT backgroundColor = pal_RGBA(0, 0, 0, 80); + std::function onClickedFunc; + std::function onCancelPressed; +private: + std::shared_ptr consoleBox; +}; + +WzInGameChatScreen_CLICKFORM::WzInGameChatScreen_CLICKFORM(W_FORMINIT const *init) : W_CLICKFORM(init) {} +WzInGameChatScreen_CLICKFORM::WzInGameChatScreen_CLICKFORM() : W_CLICKFORM() {} +WzInGameChatScreen_CLICKFORM::~WzInGameChatScreen_CLICKFORM() +{ } + +std::shared_ptr WzInGameChatScreen_CLICKFORM::make(WzChatMode initialChatMode, UDWORD formID) +{ + W_FORMINIT sInit; + sInit.id = formID; + sInit.style = WFORM_PLAIN | WFORM_CLICKABLE; + sInit.x = 0; + sInit.y = 0; + sInit.width = screenWidth - 1; + sInit.height = screenHeight - 1; + sInit.calcLayout = LAMBDA_CALCLAYOUT_SIMPLE({ + psWidget->setGeometry(0, 0, screenWidth, screenHeight); + }); + + class make_shared_enabler: public WzInGameChatScreen_CLICKFORM + { + public: + make_shared_enabler(W_FORMINIT const *init): WzInGameChatScreen_CLICKFORM(init) {} + }; + auto widget = std::make_shared(&sInit); + widget->initialize(initialChatMode); + return widget; +} + +void WzInGameChatScreen_CLICKFORM::initialize(WzChatMode initialChatMode) +{ + consoleBox = WzInGameChatBoxForm::make(initialChatMode); + attach(consoleBox); + consoleBox->id = CHAT_CONSOLEBOX; + consoleBox->setCalcLayout(LAMBDA_CALCLAYOUT_SIMPLE({ + psWidget->setGeometry(CHAT_CONSOLEBOXX, CHAT_CONSOLEBOXY, CHAT_CONSOLEBOXW, CHAT_CONSOLEBOXH); + })); + consoleBox->startChatBoxEditing(); + + recalcLayout(); +} + +void WzInGameChatScreen_CLICKFORM::recalcLayout() +{ + if (consoleBox) + { + consoleBox->callCalcLayout(); + } +} + +void WzInGameChatScreen_CLICKFORM::clearFocusAndEditing() +{ + if (auto lockedScreen = screenPointer.lock()) + { + lockedScreen->setFocus(nullptr); + } + if (consoleBox) + { + consoleBox->endChatBoxEditing(); + } +} + +void WzInGameChatScreen_CLICKFORM::clicked(W_CONTEXT *psContext, WIDGET_KEY key) +{ + clearFocusAndEditing(); + if (onClickedFunc) + { + onClickedFunc(); + } +} + +void WzInGameChatScreen_CLICKFORM::display(int xOffset, int yOffset) +{ + if (!visible()) + { + return; // skip if hidden + } + + if (backgroundColor.rgba == 0) + { + return; + } + + int x0 = x() + xOffset; + int y0 = y() + yOffset; + + // draw background over everything + pie_UniTransBoxFill(x0, y0, x0 + width(), y0 + height(), backgroundColor); +} + +void WzInGameChatScreen_CLICKFORM::run(W_CONTEXT *psContext) +{ + if (keyPressed(KEY_ESC)) + { + clearFocusAndEditing(); + if (onCancelPressed) + { + onCancelPressed(); + } + } + + // while this is displayed, clear the input buffer + // (ensuring that subsequent screens / the main screen do not get the input to handle) + inputLoseFocus(); +} + +// MARK: - WzGameStartOverlayScreen + +std::shared_ptr WzInGameChatScreen::make(const OnCloseFunc& _onCloseFunc, WzChatMode initialChatMode) +{ + class make_shared_enabler: public WzInGameChatScreen {}; + auto newRootFrm = WzInGameChatScreen_CLICKFORM::make(initialChatMode); + auto screen = std::make_shared(); + screen->initialize(newRootFrm); + screen->onCloseFunc = _onCloseFunc; + std::weak_ptr psWeakHelpOverlayScreen(screen); + newRootFrm->onClickedFunc = [psWeakHelpOverlayScreen]() { + auto psOverlayScreen = psWeakHelpOverlayScreen.lock(); + if (psOverlayScreen) + { + psOverlayScreen->closeScreen(); + } + }; + newRootFrm->onCancelPressed = newRootFrm->onClickedFunc; + + return screen; +} + +void WzInGameChatScreen::closeScreen() +{ + shutdownChatScreen(); + if (onCloseFunc) + { + onCloseFunc(); + } +} + +std::shared_ptr createChatScreen(std::function onCloseFunc, WzChatMode initialChatMode) +{ + auto screen = WzInGameChatScreen::make(onCloseFunc, initialChatMode); + widgRegisterOverlayScreenOnTopOfScreen(screen, psWScreen); + psCurrentChatScreen = screen; + return screen; +} + +void shutdownChatScreen() +{ + if (auto strongGameStartScreen = psCurrentChatScreen.lock()) + { + widgRemoveOverlayScreen(strongGameStartScreen); + psCurrentChatScreen.reset(); + } +} diff --git a/src/screens/chatscreen.h b/src/screens/chatscreen.h new file mode 100644 index 00000000000..af9c08de4e9 --- /dev/null +++ b/src/screens/chatscreen.h @@ -0,0 +1,34 @@ +/* + This file is part of Warzone 2100. + Copyright (C) 2023 Warzone 2100 Project + + Warzone 2100 is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Warzone 2100 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Warzone 2100; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +#include "lib/widget/widget.h" +#include "lib/widget/form.h" + +#include + +enum class WzChatMode +{ + Team, + Glob +}; + +std::shared_ptr createChatScreen(std::function onCloseFunc, WzChatMode initialChatMode); +void shutdownChatScreen();