//----------------------------------------------------------------------------- // // 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/ // //----------------------------------------------------------------------------- // #include #include #include "doomdef.h" #include "m_swap.h" #include "hu_stuff.h" #include "s_sound.h" #include "doomstat.h" #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" #include "d_net.h" #include "d_event.h" #include "sbar.h" #define QUEUESIZE 128 #define MESSAGESIZE 128 #define MESSAGELEN 265 #define HU_INPUTX 0 #define HU_INPUTY (0 + (screen->Font->GetHeight () + 1)) void CT_PasteChat(const char *clip); 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(); // Public data void CT_Init (); void CT_Drawer (); bool CT_Responder (event_t *ev); int chatmodeon; // Private data static void CT_ClearChatMessage (); static void CT_AddChar (char c); static void CT_BackSpace (); static void ShoveChatStr (const char *str, uint8_t who); static bool DoSubstitution (FString &out, const char *in); static int len; static uint8_t ChatQueue[QUEUESIZE]; 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 () { len = 0; // initialize the queue index chatmodeon = 0; ChatQueue[0] = 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, chatmodeon - 1); 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); 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 (char(ev->data1)); } 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 != NULL && *clip != '\0') { // Only paste the first line. while (*clip != '\0') { if (*clip == '\n' || *clip == '\r' || *clip == '\b') { break; } CT_AddChar (*clip++); } } } //=========================================================================== // // CT_Drawer // //=========================================================================== void CT_Drawer (void) { if (chatmodeon) { static const char *prompt = "Say: "; int i, x, scalex, y, promptwidth; 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; y += ((SCREENHEIGHT == viewheight && viewactive) || gamestate != GS_LEVEL) ? screen_height : st_y; promptwidth = SmallFont->StringWidth (prompt) * scalex; x = SmallFont->GetCharWidth ('_') * scalex * 2 + promptwidth; // figure out if the text is wider than the screen-> // if so, only draw the right-most portion of it. for (i = len - 1; i >= 0 && x < screen_width; i--) { x += SmallFont->GetCharWidth (ChatQueue[i] & 0x7f) * scalex; } if (i >= 0) { i++; } else { i = 0; } // draw the prompt, text, and cursor ChatQueue[len] = SmallFont->GetCursor(); ChatQueue[len+1] = '\0'; screen->DrawText (SmallFont, CR_GREEN, 0, y, prompt, DTA_VirtualWidth, screen_width, DTA_VirtualHeight, screen_height, DTA_KeepRatio, true, TAG_DONE); screen->DrawText (SmallFont, CR_GREY, promptwidth, y, (char *)(ChatQueue + i), DTA_VirtualWidth, screen_width, DTA_VirtualHeight, screen_height, DTA_KeepRatio, true, TAG_DONE); ChatQueue[len] = '\0'; } 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 (char c) { if (len < QUEUESIZE-2) { ChatQueue[len++] = c; ChatQueue[len] = 0; } } //=========================================================================== // // CT_BackSpace // // Backs up a space, when the user hits (obviously) backspace //=========================================================================== static void CT_BackSpace () { if (len) { ChatQueue[--len] = 0; } } //=========================================================================== // // CT_ClearChatMessage // // Clears out the data for the chat message. //=========================================================================== static void CT_ClearChatMessage () { ChatQueue[0] = 0; len = 0; } //=========================================================================== // // ShoveChatStr // // Sends the chat message across the network // //=========================================================================== static void ShoveChatStr (const char *str, uint8_t who) { // 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 (str); } else { Net_WriteString (substBuff); } } //=========================================================================== // // 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(NAME_Ammo1) : nullptr; auto ammo2 = weapon ? weapon->PointerVar(NAME_Ammo2) : nullptr; 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 len = b - a; if (len == 6) { 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 (len == 5) { if (strnicmp(a, "armor", 5) == 0) { auto armor = player->mo->FindInventory(NAME_BasicArmor); out.AppendFormat("%d", armor != NULL ? armor->IntVar(NAME_Amount) : 0); } } else if (len == 9) { 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) { out.AppendFormat("/%d", ammo2->IntVar(NAME_Amount)); } } } } else if (len == 4) { if (strnicmp(a, "ammo", 4) == 0) { if (ammo1 == NULL) { out += "no ammo"; } else { out.AppendFormat("%s", ammo1->GetClass()->TypeName.GetChars()); if (ammo2 != NULL) { out.AppendFormat("/%s", ammo2->GetClass()->TypeName.GetChars()); } } } } else if (len == 0) { out += '$'; if (*b == '$') { b++; } } else { out += '$'; out.AppendCStrPart(a, len); } 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 \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 \n"); } else { ShoveChatStr (argv[1], 1); } }