gzdoom/src/ct_chat.cpp

544 lines
12 KiB
C++
Raw Normal View History

//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1994-1996 Raven Software
// Copyright 1999-2016 Randy Heit
//
// This program 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 3 of the License, or
// (at your option) any later version.
//
// This program 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 this program. If not, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//
2016-03-01 15:47:10 +00:00
#include <string.h>
#include <ctype.h>
#include "doomdef.h"
#include "m_swap.h"
#include "hu_stuff.h"
#include "s_sound.h"
#include "g_game.h"
2016-03-01 15:47:10 +00:00
#include "st_stuff.h"
#include "c_console.h"
#include "c_dispatch.h"
#include "d_player.h"
#include "v_text.h"
#include "d_gui.h"
#include "g_input.h"
2016-03-01 15:47:10 +00:00
#include "d_net.h"
#include "d_event.h"
#include "sbar.h"
#include "v_video.h"
#include "utf8.h"
2016-03-01 15:47:10 +00:00
enum
{
QUEUESIZE = 128
};
2016-03-01 15:47:10 +00:00
EXTERN_CVAR (Int, con_scaletext)
EXTERN_CVAR (Bool, sb_cooperative_enable)
EXTERN_CVAR (Bool, sb_deathmatch_enable)
EXTERN_CVAR (Bool, sb_teamdeathmatch_enable)
int active_con_scaletext();
2016-03-01 15:47:10 +00:00
// Public data
void CT_Init ();
void CT_Drawer ();
bool CT_Responder (event_t *ev);
void CT_PasteChat(const char *clip);
2016-03-01 15:47:10 +00:00
int chatmodeon;
// Private data
static void CT_ClearChatMessage ();
static void CT_AddChar (int c);
2016-03-01 15:47:10 +00:00
static void CT_BackSpace ();
2017-03-08 17:47:52 +00:00
static void ShoveChatStr (const char *str, uint8_t who);
2016-03-01 15:47:10 +00:00
static bool DoSubstitution (FString &out, const char *in);
static int CharLen;
static TArray<uint8_t> ChatQueue;
2016-03-01 15:47:10 +00:00
CVAR (String, chatmacro1, "I'm ready to kick butt!", CVAR_ARCHIVE)
CVAR (String, chatmacro2, "I'm OK.", CVAR_ARCHIVE)
CVAR (String, chatmacro3, "I'm not looking too good!", CVAR_ARCHIVE)
CVAR (String, chatmacro4, "Help!", CVAR_ARCHIVE)
CVAR (String, chatmacro5, "You suck!", CVAR_ARCHIVE)
CVAR (String, chatmacro6, "Next time, scumbag...", CVAR_ARCHIVE)
CVAR (String, chatmacro7, "Come here!", CVAR_ARCHIVE)
CVAR (String, chatmacro8, "I'll take care of it.", CVAR_ARCHIVE)
CVAR (String, chatmacro9, "Yes", CVAR_ARCHIVE)
CVAR (String, chatmacro0, "No", CVAR_ARCHIVE)
FStringCVar *chat_macros[10] =
{
&chatmacro0,
&chatmacro1,
&chatmacro2,
&chatmacro3,
&chatmacro4,
&chatmacro5,
&chatmacro6,
&chatmacro7,
&chatmacro8,
&chatmacro9
};
CVAR (Bool, chat_substitution, false, CVAR_ARCHIVE)
//===========================================================================
//
// CT_Init
//
// Initialize chat mode data
//===========================================================================
void CT_Init ()
{
ChatQueue.Clear();
ChatQueue.Push(0);
CharLen = 0;
2016-03-01 15:47:10 +00:00
chatmodeon = 0;
}
//===========================================================================
//
// CT_Stop
//
//===========================================================================
void CT_Stop ()
{
chatmodeon = 0;
}
//===========================================================================
//
// CT_Responder
//
//===========================================================================
bool CT_Responder (event_t *ev)
{
if (chatmodeon && ev->type == EV_GUI_Event)
{
if (ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat)
{
if (ev->data1 == '\r')
{
ShoveChatStr ((char *)ChatQueue.Data(), chatmodeon - 1);
2016-03-01 15:47:10 +00:00
CT_Stop ();
return true;
}
else if (ev->data1 == GK_ESCAPE)
{
CT_Stop ();
return true;
}
else if (ev->data1 == '\b')
{
CT_BackSpace ();
return true;
}
#ifdef __APPLE__
else if (ev->data1 == 'C' && (ev->data3 & GKM_META))
#else // !__APPLE__
else if (ev->data1 == 'C' && (ev->data3 & GKM_CTRL))
#endif // __APPLE__
{
I_PutInClipboard ((char *)ChatQueue.Data());
2016-03-01 15:47:10 +00:00
return true;
}
#ifdef __APPLE__
else if (ev->data1 == 'V' && (ev->data3 & GKM_META))
#else // !__APPLE__
else if (ev->data1 == 'V' && (ev->data3 & GKM_CTRL))
#endif // __APPLE__
{
CT_PasteChat(I_GetFromClipboard(false));
}
}
else if (ev->subtype == EV_GUI_Char)
{
// send a macro
if (ev->data2 && (ev->data1 >= '0' && ev->data1 <= '9'))
{
ShoveChatStr (*chat_macros[ev->data1 - '0'], chatmodeon - 1);
CT_Stop ();
}
else
{
CT_AddChar (ev->data1);
2016-03-01 15:47:10 +00:00
}
return true;
}
#ifdef __unix__
else if (ev->subtype == EV_GUI_MButtonDown)
{
CT_PasteChat(I_GetFromClipboard(true));
}
#endif
}
return false;
}
//===========================================================================
//
// CT_PasteChat
//
//===========================================================================
void CT_PasteChat(const char *clip)
{
if (clip != nullptr && *clip != '\0')
2016-03-01 15:47:10 +00:00
{
auto p = (const uint8_t *)clip;
2016-03-01 15:47:10 +00:00
// Only paste the first line.
while (auto chr = GetCharFromString(p))
2016-03-01 15:47:10 +00:00
{
if (chr == '\n' || chr == '\r' || chr == '\b')
2016-03-01 15:47:10 +00:00
{
break;
}
CT_AddChar (chr);
2016-03-01 15:47:10 +00:00
}
}
}
//===========================================================================
//
// CT_Drawer
//
//===========================================================================
void CT_Drawer (void)
{
FFont *displayfont = ConFont;
2016-03-01 15:47:10 +00:00
if (chatmodeon)
{
static const char *prompt = "Say: ";
int x, scalex, y, promptwidth;
2016-03-01 15:47:10 +00:00
y = (viewactive || gamestate != GS_LEVEL) ? -10 : -30;
scalex = 1;
int scale = active_con_scaletext();
int screen_width = SCREENWIDTH / scale;
int screen_height= SCREENHEIGHT / scale;
int st_y = StatusBar->GetTopOfStatusbar() / scale;
2016-03-01 15:47:10 +00:00
y += ((SCREENHEIGHT == viewheight && viewactive) || gamestate != GS_LEVEL) ? screen_height : st_y;
promptwidth = displayfont->StringWidth (prompt) * scalex;
x = displayfont->GetCharWidth (displayfont->GetCursor()) * scalex * 2 + promptwidth;
2016-03-01 15:47:10 +00:00
// figure out if the text is wider than the screen
2016-03-01 15:47:10 +00:00
// if so, only draw the right-most portion of it.
const uint8_t *textp = ChatQueue.Data();
while(*textp)
2016-03-01 15:47:10 +00:00
{
auto textw = displayfont->StringWidth(textp);
if (x + textw * scalex < screen_width) break;
GetCharFromString(textp);
2016-03-01 15:47:10 +00:00
}
// draw the prompt, text, and cursor
ChatQueue.Last() = displayfont->GetCursor();
ChatQueue.Push(0);
screen->DrawText (displayfont, CR_GREEN, 0, y, prompt,
DTA_VirtualWidth, screen_width, DTA_VirtualHeight, screen_height, DTA_KeepRatio, true, TAG_DONE);
screen->DrawText (displayfont, CR_GREY, promptwidth, y, (const char *)textp,
DTA_VirtualWidth, screen_width, DTA_VirtualHeight, screen_height, DTA_KeepRatio, true, TAG_DONE);
ChatQueue.Pop();
ChatQueue.Last() = 0;
2016-03-01 15:47:10 +00:00
}
if (players[consoleplayer].camera != NULL &&
(Button_ShowScores.bDown ||
players[consoleplayer].camera->health <= 0 ||
SB_ForceActive) &&
// Don't draw during intermission, since it has its own scoreboard in wi_stuff.cpp.
gamestate != GS_INTERMISSION)
{
HU_DrawScores (&players[consoleplayer]);
}
}
//===========================================================================
//
// CT_AddChar
//
//===========================================================================
static void CT_AddChar (int c)
2016-03-01 15:47:10 +00:00
{
if (CharLen < QUEUESIZE-2)
2016-03-01 15:47:10 +00:00
{
int size;
uint8_t encode[4];
ChatQueue.Pop();
if (utf8_encode(c, encode, &size) == 0)
{
for (int i = 0; i < size; i++)
{
ChatQueue.Push(encode[i]);
}
CharLen++;
}
ChatQueue.Push(0);
2016-03-01 15:47:10 +00:00
}
}
//===========================================================================
//
// CT_BackSpace
//
// Backs up a space, when the user hits (obviously) backspace
//===========================================================================
static void CT_BackSpace ()
{
if (CharLen)
2016-03-01 15:47:10 +00:00
{
int endpos = ChatQueue.Size() - 2;
while (endpos > 0 && ChatQueue[endpos] >= 0x80 && ChatQueue[endpos] < 0xc0) endpos--;
ChatQueue[endpos] = 0;
ChatQueue.Clamp(endpos + 1);
CharLen--;
2016-03-01 15:47:10 +00:00
}
}
//===========================================================================
//
// CT_ClearChatMessage
//
// Clears out the data for the chat message.
//===========================================================================
static void CT_ClearChatMessage ()
{
if (ChatQueue.Size() > 1)
{
ChatQueue.Clamp(1);
ChatQueue[0] = 0;
CharLen = 0;
}
2016-03-01 15:47:10 +00:00
}
//===========================================================================
//
// ShoveChatStr
//
// Sends the chat message across the network
//
//===========================================================================
2017-03-08 17:47:52 +00:00
static void ShoveChatStr (const char *str, uint8_t who)
2016-03-01 15:47:10 +00:00
{
// Don't send empty messages
if (str == NULL || str[0] == '\0')
return;
FString substBuff;
if (str[0] == '/' &&
(str[1] == 'm' || str[1] == 'M') &&
(str[2] == 'e' || str[2] == 'E'))
{ // This is a /me message
str += 3;
who |= 2;
}
Net_WriteByte (DEM_SAY);
Net_WriteByte (who);
if (!chat_substitution || !DoSubstitution (substBuff, str))
{
Net_WriteString(MakeUTF8(str));
2016-03-01 15:47:10 +00:00
}
else
{
Net_WriteString(MakeUTF8(substBuff));
2016-03-01 15:47:10 +00:00
}
}
//===========================================================================
//
// DoSubstitution
//
// Replace certain special substrings with different values to reflect
// the player's current state.
//
//===========================================================================
static bool DoSubstitution (FString &out, const char *in)
{
player_t *player = &players[consoleplayer];
auto weapon = player->ReadyWeapon;
auto ammo1 = weapon ? weapon->PointerVar<AActor>(NAME_Ammo1) : nullptr;
auto ammo2 = weapon ? weapon->PointerVar<AActor>(NAME_Ammo2) : nullptr;
2016-03-01 15:47:10 +00:00
const char *a, *b;
a = in;
out = "";
while ( (b = strchr(a, '$')) )
{
out.AppendCStrPart(a, b - a);
a = ++b;
while (*b && isalpha(*b))
{
++b;
}
ptrdiff_t ByteLen = b - a;
2016-03-01 15:47:10 +00:00
if (ByteLen == 6)
2016-03-01 15:47:10 +00:00
{
if (strnicmp(a, "health", 6) == 0)
{
out.AppendFormat("%d", player->health);
}
else if (strnicmp(a, "weapon", 6) == 0)
{
if (weapon == NULL)
{
out += "no weapon";
}
else
{
out += weapon->GetClass()->TypeName;
}
}
}
else if (ByteLen == 5)
2016-03-01 15:47:10 +00:00
{
if (strnicmp(a, "armor", 5) == 0)
{
auto armor = player->mo->FindInventory(NAME_BasicArmor);
out.AppendFormat("%d", armor != NULL ? armor->IntVar(NAME_Amount) : 0);
2016-03-01 15:47:10 +00:00
}
}
else if (ByteLen == 9)
2016-03-01 15:47:10 +00:00
{
if (strnicmp(a, "ammocount", 9) == 0)
{
if (weapon == NULL)
{
out += '0';
}
else
{
out.AppendFormat("%d", ammo1 != NULL ? ammo1->IntVar(NAME_Amount) : 0);
if (ammo2 != NULL)
2016-03-01 15:47:10 +00:00
{
out.AppendFormat("/%d", ammo2->IntVar(NAME_Amount));
2016-03-01 15:47:10 +00:00
}
}
}
}
else if (ByteLen == 4)
2016-03-01 15:47:10 +00:00
{
if (strnicmp(a, "ammo", 4) == 0)
{
if (ammo1 == NULL)
2016-03-01 15:47:10 +00:00
{
out += "no ammo";
}
else
{
out.AppendFormat("%s", ammo1->GetClass()->TypeName.GetChars());
if (ammo2 != NULL)
2016-03-01 15:47:10 +00:00
{
out.AppendFormat("/%s", ammo2->GetClass()->TypeName.GetChars());
2016-03-01 15:47:10 +00:00
}
}
}
}
else if (ByteLen == 0)
2016-03-01 15:47:10 +00:00
{
out += '$';
if (*b == '$')
{
b++;
}
}
else
{
out += '$';
out.AppendCStrPart(a, ByteLen);
2016-03-01 15:47:10 +00:00
}
a = b;
}
// Return false if no substitution was performed
if (a == in)
{
return false;
}
out += a;
return true;
}
CCMD (messagemode)
{
if (menuactive == MENU_Off)
{
chatmodeon = 1;
C_HideConsole ();
CT_ClearChatMessage ();
}
}
CCMD (say)
{
if (argv.argc() == 1)
{
Printf ("Usage: say <message>\n");
}
else
{
ShoveChatStr (argv[1], 0);
}
}
CCMD (messagemode2)
{
if (menuactive == MENU_Off)
{
chatmodeon = 2;
C_HideConsole ();
CT_ClearChatMessage ();
}
}
CCMD (say_team)
{
if (argv.argc() == 1)
{
Printf ("Usage: say_team <message>\n");
}
else
{
ShoveChatStr (argv[1], 1);
}
}