You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
320 lines
8.7 KiB
320 lines
8.7 KiB
|
3 months ago
|
#include "control_chat.hpp"
|
||
|
|
#include "control.hpp"
|
||
|
|
#include "control_panel.hpp"
|
||
|
|
|
||
|
|
#include "control/control_chat_commands.hpp"
|
||
|
|
#include "engine/backbuffer_state.hpp"
|
||
|
|
#include "engine/render/clx_render.hpp"
|
||
|
|
#include "options.h"
|
||
|
|
#include "panels/console.hpp"
|
||
|
|
#include "panels/mainpanel.hpp"
|
||
|
|
#include "quick_messages.hpp"
|
||
|
|
#include "utils/display.h"
|
||
|
|
#include "utils/sdl_compat.h"
|
||
|
|
#include "utils/str_cat.hpp"
|
||
|
|
|
||
|
|
namespace devilution {
|
||
|
|
|
||
|
|
std::optional<TextInputState> ChatInputState;
|
||
|
|
char TalkMessage[MAX_SEND_STR_LEN];
|
||
|
|
bool TalkButtonsDown[3];
|
||
|
|
int sgbPlrTalkTbl;
|
||
|
|
bool WhisperList[MAX_PLRS];
|
||
|
|
OptionalOwnedClxSpriteList talkButtons;
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
char TalkSave[8][MAX_SEND_STR_LEN];
|
||
|
|
uint8_t TalkSaveIndex;
|
||
|
|
uint8_t NextTalkSave;
|
||
|
|
TextInputCursorState ChatCursor;
|
||
|
|
|
||
|
|
int MuteButtons = 3;
|
||
|
|
int MuteButtonPadding = 2;
|
||
|
|
Rectangle MuteButtonRect { { 172, 69 }, { 61, 16 } };
|
||
|
|
|
||
|
|
void ResetChatMessage()
|
||
|
|
{
|
||
|
|
if (CheckChatCommand(TalkMessage))
|
||
|
|
return;
|
||
|
|
|
||
|
|
uint32_t pmask = 0;
|
||
|
|
|
||
|
|
for (size_t i = 0; i < Players.size(); i++) {
|
||
|
|
if (WhisperList[i])
|
||
|
|
pmask |= 1 << i;
|
||
|
|
}
|
||
|
|
|
||
|
|
NetSendCmdString(pmask, TalkMessage);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ControlPressEnter()
|
||
|
|
{
|
||
|
|
if (TalkMessage[0] != 0) {
|
||
|
|
ResetChatMessage();
|
||
|
|
uint8_t i = 0;
|
||
|
|
for (; i < 8; i++) {
|
||
|
|
if (strcmp(TalkSave[i], TalkMessage) == 0)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (i >= 8) {
|
||
|
|
strcpy(TalkSave[NextTalkSave], TalkMessage);
|
||
|
|
NextTalkSave++;
|
||
|
|
NextTalkSave &= 7;
|
||
|
|
} else {
|
||
|
|
uint8_t talkSave = NextTalkSave - 1;
|
||
|
|
talkSave &= 7;
|
||
|
|
if (i != talkSave) {
|
||
|
|
strcpy(TalkSave[i], TalkSave[talkSave]);
|
||
|
|
*BufCopy(TalkSave[talkSave], ChatInputState->value()) = '\0';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
TalkMessage[0] = '\0';
|
||
|
|
TalkSaveIndex = NextTalkSave;
|
||
|
|
}
|
||
|
|
ResetChat();
|
||
|
|
}
|
||
|
|
|
||
|
|
void ControlUpDown(int v)
|
||
|
|
{
|
||
|
|
for (int i = 0; i < 8; i++) {
|
||
|
|
TalkSaveIndex = (v + TalkSaveIndex) & 7;
|
||
|
|
if (TalkSave[TalkSaveIndex][0] != 0) {
|
||
|
|
ChatInputState->assign(TalkSave[TalkSaveIndex]);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
void DrawChatBox(const Surface &out)
|
||
|
|
{
|
||
|
|
if (!ChatFlag)
|
||
|
|
return;
|
||
|
|
|
||
|
|
const Point mainPanelPosition = GetMainPanel().position;
|
||
|
|
|
||
|
|
DrawPanelBox(out, MakeSdlRect(175, sgbPlrTalkTbl + 20, 294, 5), mainPanelPosition + Displacement { 175, 4 });
|
||
|
|
int off = 0;
|
||
|
|
for (int i = 293; i > 283; off++, i--) {
|
||
|
|
DrawPanelBox(out, MakeSdlRect((off / 2) + 175, sgbPlrTalkTbl + off + 25, i, 1), mainPanelPosition + Displacement { (off / 2) + 175, off + 9 });
|
||
|
|
}
|
||
|
|
DrawPanelBox(out, MakeSdlRect(185, sgbPlrTalkTbl + 35, 274, 30), mainPanelPosition + Displacement { 185, 19 });
|
||
|
|
DrawPanelBox(out, MakeSdlRect(180, sgbPlrTalkTbl + 65, 284, 5), mainPanelPosition + Displacement { 180, 49 });
|
||
|
|
for (int i = 0; i < 10; i++) {
|
||
|
|
DrawPanelBox(out, MakeSdlRect(180, sgbPlrTalkTbl + i + 70, i + 284, 1), mainPanelPosition + Displacement { 180, i + 54 });
|
||
|
|
}
|
||
|
|
DrawPanelBox(out, MakeSdlRect(170, sgbPlrTalkTbl + 80, 310, 55), mainPanelPosition + Displacement { 170, 64 });
|
||
|
|
|
||
|
|
int x = mainPanelPosition.x + 200;
|
||
|
|
const int y = mainPanelPosition.y + 10;
|
||
|
|
|
||
|
|
const uint32_t len = DrawString(out, TalkMessage, { { x, y }, { 250, 39 } },
|
||
|
|
{
|
||
|
|
.flags = UiFlags::ColorWhite | UiFlags::PentaCursor,
|
||
|
|
.lineHeight = 13,
|
||
|
|
.cursorPosition = static_cast<int>(ChatCursor.position),
|
||
|
|
.highlightRange = { static_cast<int>(ChatCursor.selection.begin), static_cast<int>(ChatCursor.selection.end) },
|
||
|
|
});
|
||
|
|
ChatInputState->truncate(len);
|
||
|
|
|
||
|
|
x += 46;
|
||
|
|
int talkBtn = 0;
|
||
|
|
for (size_t i = 0; i < Players.size(); i++) {
|
||
|
|
Player &player = Players[i];
|
||
|
|
if (&player == MyPlayer)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
const UiFlags color = player.friendlyMode ? UiFlags::ColorWhitegold : UiFlags::ColorRed;
|
||
|
|
const Point talkPanPosition = mainPanelPosition + Displacement { 172, 84 + 18 * talkBtn };
|
||
|
|
if (WhisperList[i]) {
|
||
|
|
// the normal (unpressed) voice button is pre-rendered on the panel, only need to draw over it when the button is held
|
||
|
|
if (TalkButtonsDown[talkBtn]) {
|
||
|
|
const unsigned spriteIndex = talkBtn == 0 ? 2 : 3; // the first button sprite includes a tip from the devils wing so is different to the rest.
|
||
|
|
ClxDraw(out, talkPanPosition, (*talkButtons)[spriteIndex]);
|
||
|
|
|
||
|
|
// Draw the translated string over the top of the default (english) button. This graphic is inset to avoid overlapping the wingtip, letting
|
||
|
|
// the first button be treated the same as the other two further down the panel.
|
||
|
|
RenderClxSprite(out, (*TalkButton)[2], talkPanPosition + Displacement { 4, -15 });
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
unsigned spriteIndex = talkBtn == 0 ? 0 : 1; // the first button sprite includes a tip from the devils wing so is different to the rest.
|
||
|
|
if (TalkButtonsDown[talkBtn])
|
||
|
|
spriteIndex += 4; // held button sprites are at index 4 and 5 (with and without wingtip respectively)
|
||
|
|
ClxDraw(out, talkPanPosition, (*talkButtons)[spriteIndex]);
|
||
|
|
|
||
|
|
// Draw the translated string over the top of the default (english) button. This graphic is inset to avoid overlapping the wingtip, letting
|
||
|
|
// the first button be treated the same as the other two further down the panel.
|
||
|
|
RenderClxSprite(out, (*TalkButton)[TalkButtonsDown[talkBtn] ? 1 : 0], talkPanPosition + Displacement { 4, -15 });
|
||
|
|
}
|
||
|
|
if (player.plractive) {
|
||
|
|
DrawString(out, player._pName, { { x, y + 60 + talkBtn * 18 }, { 204, 0 } }, { .flags = color });
|
||
|
|
}
|
||
|
|
|
||
|
|
talkBtn++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool CheckMuteButton()
|
||
|
|
{
|
||
|
|
if (!ChatFlag)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
Rectangle buttons = MuteButtonRect;
|
||
|
|
|
||
|
|
SetPanelObjectPosition(UiPanels::Main, buttons);
|
||
|
|
|
||
|
|
buttons.size.height = (MuteButtons * buttons.size.height) + ((MuteButtons - 1) * MuteButtonPadding);
|
||
|
|
|
||
|
|
if (!buttons.contains(MousePosition))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
for (bool &talkButtonDown : TalkButtonsDown) {
|
||
|
|
talkButtonDown = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const Point mainPanelPosition = GetMainPanel().position;
|
||
|
|
|
||
|
|
TalkButtonsDown[(MousePosition.y - (69 + mainPanelPosition.y)) / 18] = true;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void CheckMuteButtonUp()
|
||
|
|
{
|
||
|
|
if (!ChatFlag)
|
||
|
|
return;
|
||
|
|
|
||
|
|
for (bool &talkButtonDown : TalkButtonsDown)
|
||
|
|
talkButtonDown = false;
|
||
|
|
|
||
|
|
Rectangle buttons = MuteButtonRect;
|
||
|
|
|
||
|
|
SetPanelObjectPosition(UiPanels::Main, buttons);
|
||
|
|
|
||
|
|
buttons.size.height = (MuteButtons * buttons.size.height) + ((MuteButtons - 1) * MuteButtonPadding);
|
||
|
|
|
||
|
|
if (!buttons.contains(MousePosition))
|
||
|
|
return;
|
||
|
|
|
||
|
|
int off = (MousePosition.y - buttons.position.y) / (MuteButtonRect.size.height + MuteButtonPadding);
|
||
|
|
|
||
|
|
size_t playerId = 0;
|
||
|
|
for (; playerId < Players.size() && off != -1; ++playerId) {
|
||
|
|
if (playerId != MyPlayerId)
|
||
|
|
off--;
|
||
|
|
}
|
||
|
|
if (playerId > 0 && playerId <= Players.size())
|
||
|
|
WhisperList[playerId - 1] = !WhisperList[playerId - 1];
|
||
|
|
}
|
||
|
|
|
||
|
|
void TypeChatMessage()
|
||
|
|
{
|
||
|
|
if (!IsChatAvailable())
|
||
|
|
return;
|
||
|
|
|
||
|
|
ChatFlag = true;
|
||
|
|
TalkMessage[0] = '\0';
|
||
|
|
ChatInputState.emplace(TextInputState::Options {
|
||
|
|
.value = TalkMessage,
|
||
|
|
.cursor = &ChatCursor,
|
||
|
|
.maxLength = sizeof(TalkMessage) - 1 });
|
||
|
|
for (bool &talkButtonDown : TalkButtonsDown) {
|
||
|
|
talkButtonDown = false;
|
||
|
|
}
|
||
|
|
sgbPlrTalkTbl = GetMainPanel().size.height + PanelPaddingHeight;
|
||
|
|
RedrawEverything();
|
||
|
|
TalkSaveIndex = NextTalkSave;
|
||
|
|
|
||
|
|
SDL_Rect rect = MakeSdlRect(GetMainPanel().position.x + 200, GetMainPanel().position.y + 22, 0, 27);
|
||
|
|
SDL_SetTextInputArea(ghMainWnd, &rect, /*cursor=*/0);
|
||
|
|
SDLC_StartTextInput(ghMainWnd);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ResetChat()
|
||
|
|
{
|
||
|
|
ChatFlag = false;
|
||
|
|
SDLC_StopTextInput(ghMainWnd);
|
||
|
|
ChatCursor = {};
|
||
|
|
ChatInputState = std::nullopt;
|
||
|
|
sgbPlrTalkTbl = 0;
|
||
|
|
RedrawEverything();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool IsChatActive()
|
||
|
|
{
|
||
|
|
if (!IsChatAvailable())
|
||
|
|
return false;
|
||
|
|
|
||
|
|
if (!ChatFlag)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool CheckKeypress(SDL_Keycode vkey)
|
||
|
|
{
|
||
|
|
if (!IsChatAvailable())
|
||
|
|
return false;
|
||
|
|
if (!ChatFlag)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
switch (vkey) {
|
||
|
|
case SDLK_ESCAPE:
|
||
|
|
ResetChat();
|
||
|
|
return true;
|
||
|
|
case SDLK_RETURN:
|
||
|
|
case SDLK_KP_ENTER:
|
||
|
|
ControlPressEnter();
|
||
|
|
return true;
|
||
|
|
case SDLK_DOWN:
|
||
|
|
ControlUpDown(1);
|
||
|
|
return true;
|
||
|
|
case SDLK_UP:
|
||
|
|
ControlUpDown(-1);
|
||
|
|
return true;
|
||
|
|
default:
|
||
|
|
return vkey >= SDLK_SPACE && vkey <= SDLK_Z;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void DiabloHotkeyMsg(uint32_t dwMsg)
|
||
|
|
{
|
||
|
|
assert(dwMsg < QuickMessages.size());
|
||
|
|
|
||
|
|
#ifdef _DEBUG
|
||
|
|
constexpr std::string_view LuaPrefix = "/lua ";
|
||
|
|
for (const std::string &msg : GetOptions().Chat.szHotKeyMsgs[dwMsg]) {
|
||
|
|
if (!msg.starts_with(LuaPrefix)) continue;
|
||
|
|
InitConsole();
|
||
|
|
RunInConsole(std::string_view(msg).substr(LuaPrefix.size()));
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
if (!IsChatAvailable()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const std::string &msg : GetOptions().Chat.szHotKeyMsgs[dwMsg]) {
|
||
|
|
#ifdef _DEBUG
|
||
|
|
if (msg.starts_with(LuaPrefix)) continue;
|
||
|
|
#endif
|
||
|
|
char charMsg[MAX_SEND_STR_LEN];
|
||
|
|
CopyUtf8(charMsg, msg, sizeof(charMsg));
|
||
|
|
NetSendCmdString(0xFFFFFF, charMsg);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool IsChatAvailable()
|
||
|
|
{
|
||
|
|
return gbIsMultiplayer;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool HandleTalkTextInputEvent(const SDL_Event &event)
|
||
|
|
{
|
||
|
|
return HandleInputEvent(event, ChatInputState);
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace devilution
|