/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** 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 .
*/
/***********************************************************************************************
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : Combat *
* *
* $Archive:: /Commando/Code/commando/dlgmpingamechat.cpp $*
* *
* Author:: Tom SS *
* *
* $Modtime:: 2/19/02 2:40p $*
* *
* $Revision:: 20 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "dlgmpingamechat.h"
#include "cnetwork.h"
#include "dialogcontrol.h"
#include "dialogmgr.h"
#include "gamedata.h"
#include "cstextobj.h"
#include "sctextobj.h"
#include "comboboxctrl.h"
#include "playermanager.h"
#include "player.h"
#include "listctrl.h"
#include "messagewindow.h"
#include "wolgmode.h"
#include "translatedb.h"
#include "string_ids.h"
static const WCHAR* Get_Parameter_From_String(const WCHAR* command, WideStringClass& param);
////////////////////////////////////////////////////////////////
//
// MPChatChildDialogClass
//
////////////////////////////////////////////////////////////////
MPChatChildDialogClass::MPChatChildDialogClass (void) :
MessageType (TEXT_MESSAGE_PUBLIC),
TestForAutoCompletion (false),
EndDialogOnSend (true),
ChildDialogClass (IDD_CHAT_MODULE)
{
return ;
}
////////////////////////////////////////////////////////////////
//
// On_Init_Dialog
//
////////////////////////////////////////////////////////////////
void
MPChatChildDialogClass::On_Init_Dialog (void)
{
WWASSERT (The_Game () != NULL);
//
// Allow the base class to process the message
//
ChildDialogClass::On_Init_Dialog ();
//
// Set the text of the type control accordingly
//
if (MessageType == TEXT_MESSAGE_TEAM) {
Set_Dlg_Item_Text (IDC_TYPE_STATIC, TRANSLATE (IDS_MENU_TEAM_MESSAGE));
} else if (MessageType == TEXT_MESSAGE_PUBLIC) {
Set_Dlg_Item_Text (IDC_TYPE_STATIC, TRANSLATE (IDS_MENU_PUBLIC_MESSAGE));
}
//
// Put the focus into the text control
//
((EditCtrlClass *)Get_Dlg_Item (IDC_MESSAGE_EDIT))->Set_Text_Limit (100);
Get_Dlg_Item (IDC_MESSAGE_EDIT)->Set_Focus ();
return ;
}
////////////////////////////////////////////////////////////////
//
// Send_Message
//
////////////////////////////////////////////////////////////////
void
MPChatChildDialogClass::Process_Message (void)
{
// Get the text to send...
WideStringClass message(0, true);
message = Get_Dlg_Item_Text(IDC_MESSAGE_EDIT);
message.Trim();
if (message.Is_Empty() == false) {
if (Process_Commands(message) == false) {
TextMessageEnum message_type = MessageType;
int recipient_id = -1;
bool is_ok_to_send = true;
//
// Determine who to send the message to...
//
if (RecipientName.Is_Empty() == false) {
//
// Lookup the player we're sending the message to
//
cPlayer *recipient = cPlayerManager::Find_Player (RecipientName);
if (recipient != NULL) {
message_type = TEXT_MESSAGE_PRIVATE;
recipient_id = recipient->Get_Id ();
} else {
is_ok_to_send = false;
}
}
//
// Transmit the message
//
if (is_ok_to_send) {
Send_Message(message, message_type, recipient_id);
}
}
//
// Clear the edit control
//
Set_Dlg_Item_Text (IDC_MESSAGE_EDIT, L"");
}
return ;
}
void MPChatChildDialogClass::Send_Message(WideStringClass& message, TextMessageEnum type, int recipientID)
{
//
// Send the message
//
if (cNetwork::I_Am_Client ()) {
cCsTextObj *event_obj = new cCsTextObj;
event_obj->Init(message, type, cNetwork::Get_My_Id(), recipientID);
} else {
cScTextObj *event_obj = new cScTextObj;
event_obj->Init(message, type, false, HOST_TEXT_SENDER, recipientID);
}
}
////////////////////////////////////////////////////////////////
//
// Process Commands
//
////////////////////////////////////////////////////////////////
bool MPChatChildDialogClass::Process_Commands(const WCHAR* message)
{
// Does this look like a command?
if (message && message[0] == L'/') {
// Separate the parameters into individual strings
WideStringClass command(255, true);
const WCHAR* curr_pos = Get_Parameter_From_String(&message[1], command);
if (command.Get_Length() > 0 && curr_pos[0] != 0) {
// Kick a player from the game
if (command.Compare_No_Case(L"kick") == 0) {
GameModeClass* gameMode = GameModeManager::Find("WOL");
if (gameMode && gameMode->Is_Active()) {
// Get the name parameter from the string
WideStringClass user_name(64, true);
curr_pos = Get_Parameter_From_String(curr_pos, user_name);
if (user_name.Get_Length() > 0) {
WolGameModeClass* wolGame = reinterpret_cast(gameMode);
wolGame->Kick_Player(user_name);
}
}
return true;
}
// Page users outside of this game.
if (command.Compare_No_Case(L"page") == 0) {
GameModeClass* gameMode = GameModeManager::Find("WOL");
if (gameMode && gameMode->Is_Active()) {
// Get the name parameter from the string
WideStringClass user_name(64, true);
curr_pos = Get_Parameter_From_String(curr_pos, user_name);
if (user_name.Get_Length() > 0) {
WideStringClass message(0, true);
message = curr_pos;
message.Trim();
// If the user is in this game then just send them a private message.
cPlayer* recipient = cPlayerManager::Find_Player(user_name);
if (recipient) {
int recipient_id = recipient->Get_Id();
Send_Message(message, TEXT_MESSAGE_PRIVATE, recipient_id);
} else {
// Page external users.
WolGameModeClass* wolGame = reinterpret_cast(gameMode);
wolGame->Page_WOL_User(user_name, message);
}
}
}
return true;
}
// Reply to the last page
if (command.Compare_No_Case(L"r") == 0) {
GameModeClass* gameMode = GameModeManager::Find("WOL");
if (gameMode && gameMode->Is_Active()) {
WolGameModeClass* wolGame = reinterpret_cast(gameMode);
WideStringClass message(0, true);
message = curr_pos;
message.Trim();
wolGame->Reply_Last_Page(message);
}
return true;
}
// Locate a WOL user
if (command.Compare_No_Case(L"locate") == 0) {
GameModeClass* gameMode = GameModeManager::Find("WOL");
if (gameMode && gameMode->Is_Active()) {
// Get the name parameter from the string
WideStringClass user_name(64, true);
curr_pos = Get_Parameter_From_String(curr_pos, user_name);
if (user_name.Get_Length() > 0) {
WolGameModeClass* wolGame = reinterpret_cast(gameMode);
wolGame->Locate_WOL_User(user_name);
}
}
return true;
}
// Invite another WOL user to this game
if (command.Compare_No_Case(L"invite") == 0) {
GameModeClass* gameMode = GameModeManager::Find("WOL");
if (gameMode && gameMode->Is_Active()) {
// Get the name parameter from the string
WideStringClass user_name(64, true);
curr_pos = Get_Parameter_From_String(curr_pos, user_name);
if (user_name.Get_Length() > 0) {
WolGameModeClass* wolGame = reinterpret_cast(gameMode);
WideStringClass message(0, true);
message = curr_pos;
message.Trim();
wolGame->Invite_WOL_User(user_name, message);
}
}
return true;
}
// Join another WOL user at their location
if (command.Compare_No_Case(L"join") == 0) {
GameModeClass* gameMode = GameModeManager::Find("WOL");
if (gameMode && gameMode->Is_Active()) {
// Get the name parameter from the string
WideStringClass user_name(64, true);
curr_pos = Get_Parameter_From_String(curr_pos, user_name);
if (user_name.Get_Length() > 0) {
WolGameModeClass* wolGame = reinterpret_cast(gameMode);
wolGame->Join_WOL_User(user_name);
}
}
return true;
}
}
}
return false;
}
////////////////////////////////////////////////////////////////
//
// Get_Parameter_From_String
//
////////////////////////////////////////////////////////////////
const WCHAR* Get_Parameter_From_String(const WCHAR* command, WideStringClass& param)
{
#define LOCAL_STRIP_WHITESPACE(str) \
while (str[0] != 0 && str[0] == L' ') {++str;}
// Strip off whitespace
LOCAL_STRIP_WHITESPACE(command);
const WCHAR* curr_pos = command;
// Look for the first whitespace break
while (curr_pos[0] != 0 && curr_pos[0] != L' ') {
++curr_pos;
}
// Return the string contents to the caller
int length = ((curr_pos + 1) - command);
if (length > 0) {
WCHAR* buffer = param.Get_Buffer(length + 1);
wcsncpy(buffer, command, length);
buffer[length - 1] = 0;
}
// Strip off whitespace
LOCAL_STRIP_WHITESPACE(curr_pos);
return curr_pos;
}
//////////////////////////////////////////////////////////////////////
//
// Auto_Complete_Name
//
//////////////////////////////////////////////////////////////////////
void
MPChatChildDialogClass::Auto_Complete_Name (void)
{
EditCtrlClass *edit_ctrl = (EditCtrlClass *)Get_Dlg_Item (IDC_MESSAGE_EDIT);
if (edit_ctrl == NULL) {
return ;
}
//
// Examine everything left of the caret
//
int caret_pos = edit_ctrl->Get_Caret_Pos ();
if (caret_pos > 0) {
//
// Get the message
//
WideStringClass message = Get_Dlg_Item_Text (IDC_MESSAGE_EDIT);
int message_len = message.Get_Length ();
if (message_len > 0) {
//
// Check to see if we are typing in a command
//
int cmd_start_index = 0;
int cmd_end_index = 0;
if (Find_Current_Command (message, cmd_start_index, cmd_end_index)) {
cmd_start_index ++;
//
// Try to find the start of the name
//
const WCHAR *name_start = message.Peek_Buffer () + cmd_start_index;
//
// Make a copy of the first part of the message before the command
//
WideStringClass first_part (cmd_start_index + 1, true);
::wcsncpy (first_part.Peek_Buffer (), message, cmd_start_index);
first_part.Peek_Buffer ()[cmd_start_index] = 0;
//
// Make a copy of the remainder of the message after the command
//
WideStringClass last_part (message_len - cmd_end_index, true);
::wcscpy (last_part.Peek_Buffer (), message.Peek_Buffer () + cmd_end_index);
//
// Copy the typed characters into their own buffer
//
int typed_len = caret_pos - cmd_start_index;
WideStringClass typed_name (typed_len + 1, true);
::wcsncpy (typed_name.Peek_Buffer (), name_start, typed_len);
typed_name.Peek_Buffer ()[typed_len] = 0;
//
// Try to complete the name that the user typed in
//
WideStringClass completed_name (0, true);
Complete_Player_Name (typed_name, completed_name);
//
// Did we find a valid completed name?
//
int completed_name_len = completed_name.Get_Length ();
if (completed_name_len >= typed_len) {
CurrRecipientName = completed_name;
//
// Rebuild the message...
//
WideStringClass new_message (first_part);
new_message += completed_name;
new_message += last_part;
//
// Put the new text into the edit control and reset the caret
//
edit_ctrl->Set_Text (new_message);
edit_ctrl->Set_Caret_Pos (caret_pos);
//
// Hilight the auto-completed characters
//
int hilight_end = caret_pos + (completed_name_len - typed_len);
edit_ctrl->Set_Sel (caret_pos, hilight_end);
} else {
CurrRecipientName = L"";
}
}
}
}
return ;
}
//////////////////////////////////////////////////////////////////////
//
// Complete_Player_Name
//
//////////////////////////////////////////////////////////////////////
void
MPChatChildDialogClass::Complete_Player_Name (const WCHAR *typed_name, WideStringClass &completed_name)
{
int typed_len = ::wcslen (typed_name);
//
// Require more then one character for any name starting with "R'. This is
// so that Denzil's "reply to last page" code will work...
//
if (typed_len == 1 && (typed_name[0] == L'r' || typed_name[0] == L'R')) {
return ;
}
//
// Find the player's name that most closely matches the typed name
//
for ( SLNode *player_node = cPlayerManager::Get_Player_Object_List ()->Head ();
player_node != NULL;
player_node = player_node->Next ())
{
cPlayer *player = player_node->Data ();
WWASSERT (player != NULL);
if (player->Get_Is_Active().Is_False()) {
continue;
}
const WCHAR *player_name = player->Get_Name ();
//
// Is this the best match so far?
//
if (::wcsnicmp (player_name, typed_name, typed_len) == 0) {
if ( completed_name.Get_Length () == 0 ||
::wcsicmp (player_name, completed_name) < 0)
{
completed_name = player_name;
}
}
}
return ;
}
//////////////////////////////////////////////////////////////////////
//
// On_EditCtrl_Enter_Pressed
//
//////////////////////////////////////////////////////////////////////
void
MPChatChildDialogClass::On_EditCtrl_Enter_Pressed (EditCtrlClass *edit_ctrl, int ctrl_id)
{
//
// Send the message
//
Process_Message ();
//
// Close the dialog
//
if (EndDialogOnSend) {
Get_Parent_Dialog ()->End_Dialog ();
}
return ;
}
//////////////////////////////////////////////////////////////////////
//
// On_EditCtrl_Change
//
//////////////////////////////////////////////////////////////////////
void
MPChatChildDialogClass::On_EditCtrl_Change (EditCtrlClass *edit_ctrl, int ctrl_id)
{
if (TestForAutoCompletion == false) {
return ;
}
//
// Examine everything left of the caret
//
int caret_pos = edit_ctrl->Get_Caret_Pos ();
if (caret_pos > 0) {
//
// Get the message
//
WideStringClass message = Get_Dlg_Item_Text (IDC_MESSAGE_EDIT);
if (message.Get_Length () > 0) {
//
// Check to see if we are typing in a command
//
int cmd_start_index = 0;
int cmd_end_index = 0;
if (Find_Current_Command (message, cmd_start_index, cmd_end_index)) {
//
// Fill in the rest of the name for the user
//
Auto_Complete_Name ();
}
}
}
return ;
}
//////////////////////////////////////////////////////////////////////
//
// Find_Current_Command
//
//////////////////////////////////////////////////////////////////////
bool
MPChatChildDialogClass::Find_Current_Command(const WCHAR* message, int& start_index, int& end_index)
{
EditCtrlClass *edit_ctrl = (EditCtrlClass *)Get_Dlg_Item (IDC_MESSAGE_EDIT);
if (edit_ctrl == NULL) {
return false;
}
bool retval = false;
//
// Get the caret position so we can determine where to
// start looking for the command
//
int caret_pos = edit_ctrl->Get_Caret_Pos ();
if (caret_pos > 0) {
//
// Look to see if there is a command designator preceding the caret.
//
const WCHAR *command_start = ::wcsrchr (message, L'/');
if (command_start != NULL) {
start_index = command_start - message;
command_start ++;
//
// Check to ensure there isn't a space between the designator
// and the caret
//
const WCHAR *first_space = ::wcschr (command_start, L' ');
if (first_space == NULL) {
end_index = ::wcslen (message);
retval = true;
} else if (caret_pos <= (first_space - message)) {
end_index = (first_space - message);
retval = true;
}
}
}
return retval;
}
//////////////////////////////////////////////////////////////////////
//
// On_EditCtrl_Key_Down
//
//////////////////////////////////////////////////////////////////////
bool
MPChatChildDialogClass::On_EditCtrl_Key_Down
(
EditCtrlClass * edit_ctrl,
uint32 key_id,
uint32 key_data
)
{
bool retval = false;
TestForAutoCompletion = false;
//
// Examine everything left of the caret
//
int caret_pos = edit_ctrl->Get_Caret_Pos ();
if (caret_pos > 0) {
//
// Get the message
//
WideStringClass message = Get_Dlg_Item_Text (IDC_MESSAGE_EDIT);
if (message.Get_Length () > 0) {
//
// Check to see if we are typing in a command
//
int cmd_start_index = 0;
int cmd_end_index = 0;
if (Find_Current_Command (message, cmd_start_index, cmd_end_index)) {
//
// Special-case the space key
//
if (key_id == VK_SPACE && CurrRecipientName.Get_Length () > 0) {
RecipientName = CurrRecipientName;
//
// Erase the user's name from the string
//
message.Erase (cmd_start_index, cmd_end_index - cmd_start_index);
edit_ctrl->Set_Text (message);
edit_ctrl->Set_Caret_Pos (cmd_start_index);
//
// Update the dialog with the user's name...
//
WideStringClass heading_text(0, true);
heading_text.Format (L"%s:", CurrRecipientName.Peek_Buffer ());
Set_Dlg_Item_Text (IDC_TYPE_STATIC, heading_text);
//
// Eat the spacebar...
//
retval = true;
} else if (key_id != VK_BACK && key_id != VK_DELETE) {
TestForAutoCompletion = true;
}
}
}
}
return retval;
}
////////////////////////////////////////////////////////////////
//
// On_Command
//
////////////////////////////////////////////////////////////////
void
MPChatChildDialogClass::On_Command (int ctrl_id, int message_id, DWORD param)
{
switch (ctrl_id)
{
case IDC_SEND:
Process_Message ();
if (EndDialogOnSend) {
Get_Parent_Dialog ()->End_Dialog ();
}
break;
}
ChildDialogClass::On_Command (ctrl_id, message_id, param);
return ;
}
////////////////////////////////////////////////////////////////
//
// MPIngameChatPopupClass
//
////////////////////////////////////////////////////////////////
MPIngameChatPopupClass::MPIngameChatPopupClass (void) :
DefaultType (TEXT_MESSAGE_PUBLIC),
ChatModule (NULL),
PopupDialogClass (IDD_MULTIPLAY_INGAME_CHAT)
{
//
// Configure the background renderer
//
StyleMgrClass::Configure_Renderer (&WindowBackgroundRenderer);
WindowBackgroundRenderer.Get_Shader ()->Set_Depth_Compare (ShaderClass::PASS_ALWAYS);
return ;
}
////////////////////////////////////////////////////////////////
//
// ~MPIngameChatPopupClass
//
////////////////////////////////////////////////////////////////
MPIngameChatPopupClass::~MPIngameChatPopupClass (void)
{
REF_PTR_RELEASE (ChatModule);
return ;
}
////////////////////////////////////////////////////////////////
//
// On_Init_Dialog
//
////////////////////////////////////////////////////////////////
void
MPIngameChatPopupClass::On_Init_Dialog (void)
{
WWASSERT (The_Game () != NULL);
//
// Align the window with the bottom of the screen
//
const RectClass &screen_rect = Render2DClass::Get_Screen_Resolution ();
RectClass window_rect = Get_Rect ();
float height = window_rect.Height ();
window_rect.Bottom = int(screen_rect.Bottom - 40.0F);
window_rect.Top = int(window_rect.Bottom - height);
Set_Rect (window_rect);
//
// Insert the chat module dialog into our window area
//
ChatModule = new MPChatChildDialogClass;
ChatModule->Set_Default_Type (DefaultType);
ChatModule->Start_Dialog ();
Add_Child_Dialog (ChatModule);
ChatModule->Set_Rect (window_rect);
//
// Darken the background behind this window
//
WindowBackgroundRenderer.Add_Quad (window_rect, RGBA_TO_INT32 (0, 0, 0, 200));
WindowBackgroundRenderer.Enable_Texturing (false);
//
// Let the base class process...
//
Set_Background_Darkened (false);
PopupDialogClass::On_Init_Dialog ();
return ;
}
////////////////////////////////////////////////////////////////
//
// Render
//
////////////////////////////////////////////////////////////////
void
MPIngameChatPopupClass::Render (void)
{
//
// Render the black backdrop
//
WindowBackgroundRenderer.Render ();
//
// Allow the base class to render
//
PopupDialogClass::Render ();
return ;
}
////////////////////////////////////////////////////////////////
//
// On_Command
//
////////////////////////////////////////////////////////////////
void
MPIngameChatPopupClass::On_Command (int ctrl_id, int message_id, DWORD param)
{
/*switch (ctrl_id)
{
case IDOK:
ChatModule->Send_Message ();
End_Dialog ();
break;
}*/
PopupDialogClass::On_Command (ctrl_id, message_id, param);
return ;
}