// // Copyright 2020 Electronic Arts Inc. // // TiberianDawn.DLL and RedAlert.dll and corresponding source code 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. // TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed // in the hope that it will be useful, but with permitted additional restrictions // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT // distributed with this program. You should have received a copy of the // GNU General Public License along with permitted additional restrictions // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection /* $Header: F:\projects\c&c\vcs\code\msglist.cpv 1.4 16 Oct 1995 16:48:20 JOE_BOSTIC $ */ /*************************************************************************** * * * Project Name : Command & Conquer * * * * File Name : MSGLIST.CPP * * * * Programmer : Bill R. Randolph * * * * Start Date : 05/22/95 * * * * Last Update : June 26, 1995 [BRR] * * * *-------------------------------------------------------------------------* * Functions: * * MessageListClass::Add_Edit -- Adds editable string to message list * * MessageListClass::Add_Message -- displays the given message * * MessageListClass::Draw -- Draws the messages * * MessageListClass::Get_Edit_Buf -- gets edit buffer * * MessageListClass::Init -- Inits message system, sets options * * MessageListClass::Input -- Handles input for sending messages * * MessageListClass::Manage -- Manages multiplayer messages * * MessageListClass::MessageListClass -- constructor * * MessageListClass::~MessageListClass -- destructor * * MessageListClass::Num_Messages -- returns # messages in the list * * MessageListClass::Set_Width -- sets allowable width of messages * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "function.h" // ST = 12/17/2018 5:44PM #ifndef TickCount extern TimerClass TickCount; #endif char MessageListClass::MessageBuffers[MAX_NUM_MESSAGES][MAX_MESSAGE_LENGTH + 30]; char MessageListClass::BufferAvail[MAX_NUM_MESSAGES]; /*************************************************************************** * MessageListClass::MessageListClass -- constructor * * * * INPUT: * * x,y coord of upper-left of top message * * max_msg max messages allowed, including edit message * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 05/21/1995 BRR : Created. * *=========================================================================*/ MessageListClass::MessageListClass(void) { int i; MessageList = 0; MessageX = 0; MessageY = 0; MaxMessages = 0; MaxChars = 0; Height = 0; EditLabel = 0; EditBuf = 0; EditCurPos = 0; EditInitPos = 0; for (i = 0; i < MAX_NUM_MESSAGES; i++) { BufferAvail[i] = 1; } } /*************************************************************************** * MessageListClass::~MessageListClass -- destructor * * * * INPUT: * * x,y coord of upper-left of top message * * max_msg max messages allowed, including edit message * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 05/21/1995 BRR : Created. * *=========================================================================*/ MessageListClass::~MessageListClass() { Init(0,0,0,0,0); } /*************************************************************************** * MessageListClass::Init -- Inits message system, sets options * * * * INPUT: * * x,y coord of upper-left of top message * * max_msg max messages allowed, including edit message * * maxchars max # characters allowed per message * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 05/21/1995 BRR : Created. * *=========================================================================*/ void MessageListClass::Init(int x, int y, int max_msg, int maxchars, int height) { TextLabelClass *txtlabel; int i; /*------------------------------------------------------------------------ Remove every entry in the list ------------------------------------------------------------------------*/ txtlabel = MessageList; while (txtlabel) { MessageList = (TextLabelClass *)txtlabel->Remove(); delete txtlabel; txtlabel = MessageList; } /*------------------------------------------------------------------------ Mark all buffers as available ------------------------------------------------------------------------*/ for (i = 0; i < MAX_NUM_MESSAGES; i++) { BufferAvail[i] = 1; } /*------------------------------------------------------------------------ Init variables ------------------------------------------------------------------------*/ MessageList = 0; MessageX = x; MessageY = y; MaxMessages = max_msg; if (MaxMessages > MAX_NUM_MESSAGES) MaxMessages = MAX_NUM_MESSAGES; MaxChars = maxchars; if (MaxChars > MAX_MESSAGE_LENGTH) MaxChars = MAX_MESSAGE_LENGTH; Height = height; EditLabel = 0; EditBuf = 0; EditCurPos = 0; EditInitPos = 0; } /*************************************************************************** * MessageListClass::Add_Message -- displays the given message * * * * INPUT: * * txt text to display * * color color to draw text in * * style style to use * * timeout # of ticks the thing is supposed to last (-1 = forever)* * * * OUTPUT: * * ptr to new TextLabelClass object. * * * * WARNINGS: * * The TextLabelClass's text buffer is free'd when the class is free'd, * * so never pass it a static buffer. * * * * HISTORY: * * 05/05/1995 BRR : Created. * *=========================================================================*/ TextLabelClass * MessageListClass::Add_Message(char *txt, int color, TextPrintType style, int timeout, unsigned short magic_number, unsigned short crc) { int num_msg; TextLabelClass *txtlabel; int x,y; GadgetClass *gadg; int i,j; int found; int position; char *raw_string; char *current_string; char *s1,*s2; bool same; #if (0) #if (GERMAN) static int from_adjust = -1; #else #if (FRENCH) static int from_adjust = -2; #else static int from_adjust = 0; #endif #endif #endif //(0) /*------------------------------------------------------------------------ Prevent a duplicate message. (The IPXManager Global Channel cannot detect a resend of a packet, so sometimes two identical messages appear in a row.) ------------------------------------------------------------------------*/ if (MessageList) { txtlabel = MessageList; while (txtlabel) { /* ** Dont check for duplicates in multi-segment strings */ if (!txtlabel->Segments){ if (!strcmp (txtlabel->Text,txt) && txtlabel->Color == color && txtlabel->Style == style) { return(txtlabel); } } txtlabel = (TextLabelClass *)txtlabel->Get_Next(); } } /* ** If the magic number is a valid message tail then see if we ** can add this message to the tail of an existing message (crc's must also match) */ if (magic_number > MESSAGE_HEAD_MAGIC_NUMBER && magic_number < MESSAGE_HEAD_MAGIC_NUMBER+MAX_MESSAGE_SEGMENTS){ position = magic_number - MESSAGE_HEAD_MAGIC_NUMBER; txtlabel = MessageList; while (txtlabel) { if (txtlabel->Color == color && txtlabel->Style == style && txtlabel->CRC == crc) { same = true; s1 = strchr(txtlabel->Text, ':'); s2 = strchr(txt, ':'); if (s1 && s2){ *s1 = 0; *s2 = 0; same = !strcmp (txtlabel->Text, txt); *s1 = ':'; *s2 = ':'; } if (same){ /* ** If this message segment hasnt already come through then add it to the existing text */ if (! (txtlabel->Segments & (1 << position)) ) { /* ** Search for the ':' to find the actual message after the players name */ raw_string = s2; current_string = s1; if (raw_string++ && current_string++){ memcpy (current_string + (position*(COMPAT_MESSAGE_LENGTH-5))/*+from_adjust*/, raw_string, COMPAT_MESSAGE_LENGTH-4); /* ** Flag this string segment as complete */ txtlabel->Segments |= 1<Get_Next(); } } /*------------------------------------------------------------------------ Count the # of messages; if MaxMessages is going to be exceeded, remove the top-most message. ------------------------------------------------------------------------*/ num_msg = 0; if (MessageList) { gadg = MessageList; while (gadg) { num_msg++; gadg = gadg->Get_Next(); } } /*........................................................................ Remove the top-most message, but don't remove the edit message. ........................................................................*/ if ( (MaxMessages > 0) && ((num_msg + 1) > MaxMessages)) { txtlabel = MessageList; /*..................................................................... If the top label is the edit label, go to the next one; if there is no next one, just return. .....................................................................*/ if (txtlabel == EditLabel) txtlabel = (TextLabelClass *)txtlabel->Get_Next(); if (txtlabel==NULL) return(NULL); /*..................................................................... Remove this message from the list; mark its buffer as being available. .....................................................................*/ MessageList = (TextLabelClass *)txtlabel->Remove(); for (i = 0; i < MAX_NUM_MESSAGES; i++) { if (txtlabel->Text == MessageBuffers[i]) BufferAvail[i] = 1; } delete txtlabel; /*..................................................................... Recompute everyone's y-coordinate .....................................................................*/ y = MessageY; if (MessageList) { gadg = MessageList; while (gadg) { gadg->Y = y; gadg = gadg->Get_Next(); y += Height; } } } /*------------------------------------------------------------------------ Figure out the message's y-coordinate; put it below the other messages ------------------------------------------------------------------------*/ x = MessageX; y = MessageY; if (MessageList) { gadg = MessageList; while (gadg) { gadg = gadg->Get_Next(); y += Height; } } /*------------------------------------------------------------------------ Create the message ------------------------------------------------------------------------*/ txtlabel = new TextLabelClass (txt, x, y, color, style); if (timeout==-1) { txtlabel->UserData = 0; } else { txtlabel->UserData = TickCount.Time() + timeout; } /*------------------------------------------------------------------------ Find a buffer to store our message in; if there are none, don't add the message. ------------------------------------------------------------------------*/ found = 0; txtlabel->Segments = 0; txtlabel->CRC = crc; for (i = 0; i < MAX_NUM_MESSAGES; i++) { if (BufferAvail[i]) { BufferAvail[i] = 0; memset (MessageBuffers[i],0,MAX_MESSAGE_LENGTH + 30); strcpy (MessageBuffers[i],txt); /* ** If this is a segment from a larger message then put it in the right place ** in the buffer and clear out the rest with spaces */ if (magic_number >= MESSAGE_HEAD_MAGIC_NUMBER && magic_number < MESSAGE_HEAD_MAGIC_NUMBER+MAX_MESSAGE_SEGMENTS){ raw_string = strchr(txt, ':'); char *dest_str = strchr(MessageBuffers[i], ':'); if (dest_str){ dest_str++; }else{ dest_str = MessageBuffers[i]; } if (raw_string++){ for (j=0 ; j<3 ; j++){ if (! ((magic_number - j) == MESSAGE_HEAD_MAGIC_NUMBER)){ memset (dest_str + j*(COMPAT_MESSAGE_LENGTH-4)/*+from_adjust*/, 32, COMPAT_MESSAGE_LENGTH-4); }else{ strcpy(dest_str + j*(COMPAT_MESSAGE_LENGTH-4)/*+from_adjust*/, raw_string); } } *(dest_str +((COMPAT_MESSAGE_LENGTH-4)*MAX_MESSAGE_SEGMENTS-1)) = 0; } position = magic_number - MESSAGE_HEAD_MAGIC_NUMBER; txtlabel->Segments = 1<Text = MessageBuffers[i]; found = 1; break; } } if (!found) { delete txtlabel; return (NULL); } /*------------------------------------------------------------------------ Attach the message to our list ------------------------------------------------------------------------*/ if (MessageList) { txtlabel->Add_Tail (*MessageList); } else { MessageList = txtlabel; } return(txtlabel); } /*************************************************************************** * MessageListClass::Add_Edit -- Adds editable string to message list * * * * INPUT: * * color color of edit message * * style style of edit message * * to string: who to send to * * width width of editbox in pixels * * * * OUTPUT: * * ptr to new TextLabelClass * * * * WARNINGS: * * none. * * * * HISTORY: * * 05/22/1995 BRR : Created. * *=========================================================================*/ TextLabelClass * MessageListClass::Add_Edit(int color, TextPrintType style, char *to, int width) { /*------------------------------------------------------------------------ Do nothing if we're already in "edit" mode ------------------------------------------------------------------------*/ if (EditLabel) return(NULL); /*------------------------------------------------------------------------ Initialize the buffer positions; add a new label to the label list. ------------------------------------------------------------------------*/ EditCurPos = EditInitPos = strlen(to); EditLabel = Add_Message (to, color, style, -1, 0, 0); Width = width; /*------------------------------------------------------------------------ Save our edit buffer pointer. ------------------------------------------------------------------------*/ if (EditLabel) EditBuf = EditLabel->Text; else EditBuf = NULL; return(EditLabel); } /*************************************************************************** * MessageListClass::Get_Edit_Buf -- gets edit buffer * * * * INPUT: * * none. * * * * OUTPUT: * * ptr to edit buffer, minus the "To:" header * * * * WARNINGS: * * none. * * * * HISTORY: * * 05/21/1995 BRR : Created. * *=========================================================================*/ char * MessageListClass::Get_Edit_Buf(void) { if (!EditBuf) return(NULL); return(EditBuf + EditInitPos); } /*************************************************************************** * MessageListClass::Manage -- Manages multiplayer messages * * * * If this routine returns TRUE, the caller should update the display. * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * 0 = no change has occurred, 1 = changed * * * * HISTORY: * * 05/05/1995 BRR : Created. * *=========================================================================*/ int MessageListClass::Manage (void) { TextLabelClass *txtlabel; TextLabelClass *next; int changed = 0; int y; GadgetClass *gadg; int i; /*------------------------------------------------------------------------ Loop through all messages ------------------------------------------------------------------------*/ txtlabel = MessageList; while (txtlabel) { /*..................................................................... If this message's time is up, remove it from the list .....................................................................*/ if (txtlabel->UserData != 0 && (unsigned)TickCount.Time() > txtlabel->UserData) { /*.................................................................. If we're about to delete the edit message, clear our edit message values. ..................................................................*/ if (txtlabel == EditLabel) { EditLabel = 0; EditBuf = 0; } /*.................................................................. Save the next ptr in the list; remove this entry ..................................................................*/ next = (TextLabelClass *)txtlabel->Get_Next(); MessageList = (TextLabelClass *)txtlabel->Remove(); for (i = 0; i < MAX_NUM_MESSAGES; i++) { if (txtlabel->Text == MessageBuffers[i]) BufferAvail[i] = 1; } delete txtlabel; changed = 1; txtlabel = next; } else { txtlabel = (TextLabelClass *)txtlabel->Get_Next(); } } /*------------------------------------------------------------------------ If a changed has been made, recompute the y-coord of all messages ------------------------------------------------------------------------*/ if (changed) { y = MessageY; if (MessageList) { gadg = MessageList; while (gadg) { gadg->Y = y; gadg = gadg->Get_Next(); y += Height; } } } return(changed); } /*************************************************************************** * MessageListClass::Input -- Handles input for sending messages * * * * INPUT: * * input key value to process * * * * OUTPUT: * * 1 = caller should redraw the message list (no need to complete * * refresh, though) * * 2 = caller should completely refresh the display. * * 3 = caller should send the edit message. * * (sets 'input' to 0 if it processes it.) * * * * WARNINGS: * * none. * * * * HISTORY: * * 05/05/1995 BRR : Created. * *=========================================================================*/ int MessageListClass::Input(KeyNumType &input) { KeyASCIIType ascii; int retcode = 0; /*------------------------------------------------------------------------ Do nothing if nothing to do. ------------------------------------------------------------------------*/ if (input == KN_NONE) return(0); /*------------------------------------------------------------------------ Leave mouse events alone. ------------------------------------------------------------------------*/ if ( (input & (~KN_RLSE_BIT))==KN_LMOUSE || (input & (~KN_RLSE_BIT))==KN_RMOUSE) return(0); /*------------------------------------------------------------------------ If we're in 'edit mode', handle keys ------------------------------------------------------------------------*/ if (EditLabel) { ascii = (KeyASCIIType)(Keyboard::To_ASCII(input) & 0x00ff); /* ** Allow numeric keypad presses to map to ascii numbers */ if ((input & WWKEY_VK_BIT) && ascii >='0' && ascii <= '9') { input = (KeyNumType)(input & ~WWKEY_VK_BIT); } else { /* ** Filter out all special keys except return, escape and backspace */ if ((!(input & WWKEY_VK_BIT) && !(input & KN_BUTTON) && ascii >= ' ' && ascii <= 127) || (input & 0xff)== (KN_RETURN & 0xff) || (input & 0xff)== (KN_BACKSPACE & 0xff) || (input & 0xff)== (KN_ESC & 0xff) ) { //ascii = (KeyASCIIType)(Keyboard->To_ASCII(input)); } else { input = KN_NONE; return (0); } } switch (ascii) { /*------------------------------------------------------------------ ESC = abort message ------------------------------------------------------------------*/ case KA_ESC & 0xff: EditLabel->UserData = 1; // force it to be removed input = KN_NONE; break; /*------------------------------------------------------------------ RETURN = send the message ------------------------------------------------------------------*/ case KA_RETURN & 0xff: EditLabel->UserData = 1; // force it to be removed retcode = 3; input = KN_NONE; break; /*------------------------------------------------------------------ BACKSPACE = remove a character ------------------------------------------------------------------*/ case KA_BACKSPACE & 0xff: if (EditCurPos > EditInitPos) { EditCurPos--; EditBuf[EditCurPos] = 0; retcode = 2; } input = KN_NONE; break; /*------------------------------------------------------------------ default: add a character. Reserve the last buffer position for null. (EditCurPos - EditInitPos) is the buffer index # of the next character, after the "To:" prefix. ------------------------------------------------------------------*/ default: if ( (EditCurPos - EditInitPos) < (MaxChars - 1) ) { if (!(input & WWKEY_VK_BIT) && ascii >= ' ' && ascii <= 127) { EditBuf[EditCurPos] = ascii; EditCurPos++; retcode = 1; /* ** Verify that the additional character would not overrun the on screen edit box. */ Fancy_Text_Print(TXT_NONE, 0, 0, EditLabel->Color, TBLACK, EditLabel->Style); int width = String_Pixel_Width(EditBuf); if (width >= Width){ EditBuf[EditCurPos--] = 0; retcode = 0; } } } input = KN_NONE; break; } } return(retcode); } /*************************************************************************** * MessageListClass::Draw -- draws messages * * * * INPUT: * * none * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 05/22/1995 BRR : Created. * *=========================================================================*/ void MessageListClass::Draw(void) { if (MessageList) { Hide_Mouse(); MessageList->Draw_All(); Show_Mouse(); } } /*************************************************************************** * MessageListClass::Num_Messages -- returns # messages in the list * * * * INPUT: * * none. * * * * OUTPUT: * * # of messages * * * * WARNINGS: * * none. * * * * HISTORY: * * 06/26/1995 BRR : Created. * *=========================================================================*/ int MessageListClass::Num_Messages(void) { GadgetClass *gadg; int num; num = 0; if (MessageList) { gadg = MessageList; while (gadg) { num++; gadg = gadg->Get_Next(); } } return (num); } /*************************************************************************** * MessageListClass::Set_Width -- sets allowable width of messages * * * * INPUT: * * width pixel width * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 06/26/1995 BRR : Created. * *=========================================================================*/ void MessageListClass::Set_Width(int width) { GadgetClass *gadg; if (MessageList) { gadg = MessageList; while (gadg) { ((TextLabelClass *)gadg)->PixWidth = width; gadg = gadg->Get_Next(); } } }