/* * file chat.c - manage chat data for both client and server * * $Id: chat.c,v 1.20 2006/03/28 11:41:19 fzago Exp $ * * Program XBLAST * (C) by Oliver Vogel (e-mail: m.vogel@ndh.net) * * 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 2; or (at your option) * any later version * * This program is distributed in the hope that it will be entertaining, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILTY 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, write to the Free Software Foundation, Inc. * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "xblast.h" /* max size for names */ #define MAX_CHAT_NAME_DISP 4 /* chat description size, at least 5 */ #define CHAT_DESCR_SIZE 5 #if CHAT_DESCR_SIZE<=MAX_CHAT_NAME_SIZE #error "CHAT_DESCR_SIZE must be at least as large as MAX_CHAT_NAME_SIZE!" #endif #if CHAT_DESCR_SIZE<5 #error "CHAT_DESCR_SIZE must be at least 5!" #endif /* chat structure */ struct _xb_chat { XBChat *next; unsigned char fh; /* sending host */ unsigned char fp; /* sending local player */ unsigned char th; /* receiving host */ unsigned char tp; /* receiving local player */ XBChatMode how; /* 0..numofplayers-1 (private), public, team */ char txt[CHAT_LINE_SIZE]; size_t len; XBChatStatus status; }; /* input lines for local players */ static XBChat *input[NUM_LOCAL_PLAYER + 1]; static XBEventCode codes[NUM_LOCAL_PLAYER + 1]; static XBBool initialized = XBFalse; static XBBool listening = XBFalse; static int active = -1; /* list of all chats */ static XBChat *listFirst = NULL; static XBChat *listLast = NULL; /**************************** * initialization, creation * ****************************/ /* * clear all chat data */ void Chat_Clear (void) { XBChat *next; memset (input, 0, sizeof (input)); memset (codes, 0, sizeof (codes)); Dbg_Chat ("clearing\n"); while (listFirst != NULL) { next = listFirst->next; free (listFirst); listFirst = next; } listLast = NULL; initialized = XBTrue; listening = XBFalse; active = -1; } /* Chat_Clear */ /* * listen to chat events */ void Chat_Listen (XBBool flag) { if (!initialized) { Chat_Clear (); } listening = flag; Dbg_Chat ("%s\n", listening ? "active" : "inactive"); } /* Chat_Listen */ /* * return if listening */ XBBool Chat_isListening (void) { if (!initialized) { return XBFalse; } return listening; } /* Chat_isListening */ /* * create a chat structure */ XBChat * Chat_Create (void) { XBChat *dat; Dbg_Chat ("creating\n"); /* get memory */ dat = calloc (1, sizeof (XBChat)); assert (dat != NULL); /* append to list */ if (listLast == NULL) { listFirst = dat; } else { assert (listLast->next == NULL); listLast->next = dat; } listLast = dat; /* set successor */ dat->next = NULL; /* mark as created */ dat->status = XBCS_Created; return dat; } /* Chat_Create */ /* * create a chat structure */ XBChat * Chat_CreateSys (void) { XBChat *chat = Chat_Create (); Chat_Set (chat, Network_LocalHostId (), NUM_LOCAL_PLAYER, 0x00, 0x00, XBCM_System, ""); return chat; } /* Chat_CreateSys */ /* * set chat parameters */ void Chat_Set (XBChat * chat, unsigned char fh, unsigned char fp, unsigned char th, unsigned char tp, unsigned char how, const char *txt) { assert (chat != NULL); /* store sender */ chat->fh = fh; chat->fp = fp; /* store target */ chat->th = th; chat->tp = tp; /* store mode */ chat->how = how; /* mark as set */ chat->status = XBCS_Inactive; /* set text */ Chat_SetText (chat, txt); } /* Chat_Set */ /* * set chat text */ void Chat_SetText (XBChat * chat, const char *txt) { assert (chat != NULL); /* store length of message, truncate if necessary */ chat->len = strlen (txt); if (chat->len > CHAT_LINE_SIZE - 1) { chat->len = CHAT_LINE_SIZE - 1; } /* copy message */ memcpy (chat->txt, txt, chat->len); chat->txt[chat->len] = (char)'\0'; } /* Chat_SetText */ /* * make a chat line visible */ void Chat_Receive (XBChat * chat) { static char buf[CHAT_LINE_SIZE + 2 * CHAT_DESCR_SIZE + 6]; static char snd[20]; static char snd0[CHAT_DESCR_SIZE + 1]; static char sep[3]; static char trg[20]; XBBool own; XBBool any; XBAtom from; XBAtom to; assert (NULL != chat); /* clear strings */ memset (snd, 0, sizeof (snd)); memset (snd0, 0, sizeof (snd0)); memset (trg, 0, sizeof (trg)); /* chat originated on local machine? */ own = (Network_LocalHostId () == chat->fh); /* chat didn't come from a specific player? */ any = (chat->fp == NUM_LOCAL_PLAYER); /* get name atom from sending player */ from = any ? ATOM_INVALID : Network_GetPlayer2 (chat->fh, chat->fp); /* determine sender string */ if (from == ATOM_INVALID) { /* sender is host */ sprintf (snd, "#%u", chat->fh); /* length<=5 */ } else { /* sender is player, truncate full name if necessary */ strncpy (snd, GUI_AtomToString (from), sizeof (snd) - 1); } /* truncate sender for display */ strncpy (snd0, snd, MAX_CHAT_NAME_DISP); /* set separator */ sprintf (sep, "->"); /* now check mode for target string */ switch (chat->how) { case XBCM_Public: sprintf (trg, "%s", "all"); /* length<=5 */ break; case XBCM_Team: sprintf (trg, "%s", "team"); /* length<=5 */ break; case XBCM_Private: to = Network_GetPlayer2 (chat->th, chat->tp); if (to == ATOM_INVALID) { /* target is host */ sprintf (trg, "#%u", chat->th); /* length<=5 */ } else { /* target is player */ strncpy (trg, GUI_AtomToString (to), sizeof (trg) - 1); } break; case XBCM_System: sprintf (snd, "SYS"); sprintf (snd0, "SYS"); sprintf (sep, "-"); sprintf (trg, "#%u", chat->fh); break; } /* full print to stdout */ fprintf (stdout, "CHAT %s%s%s: %s\n", snd, sep, trg, chat->txt); /* truncated display in GUI */ if (chat->how != XBCM_System) { sprintf (buf, "%s:%s", snd0, chat->txt); SetChat (buf, XBTrue); } /* mark as received */ chat->status = XBCS_Received; } /* Chat_Receive */ /* * flush out all messages with status 0 */ static void Chat_Flush (void) { XBChat *next; while (listFirst != NULL) { if (listFirst->status != 0) { return; } next = listFirst->next; free (listFirst); listFirst = next; } if (listFirst == NULL) { listLast = NULL; } } /* Chat_Flush */ /* * remove and return first line */ XBChat * Chat_Pop (void) { XBChat *ret = NULL; Chat_Flush (); if (listFirst != NULL) { ret = listFirst; listFirst = listFirst->next; if (listFirst == NULL) { listLast = NULL; } } return (ret); } /* Chat_Pop */ /********************* * packing/unpacking * *********************/ /* * pack chat data for transmission */ size_t Chat_PackData (XBChat * chat, char **data, unsigned *iob) { static char buf[CHAT_LINE_SIZE + 2]; unsigned char from; assert (chat != NULL); Dbg_Chat ("packing (%u,%u)->(%u,%u)-%u-%s(%lu)\n", chat->fh, chat->fp, chat->th, chat->tp, chat->how, chat->txt, (unsigned long)chat->len); /* redefine local sender */ from = (chat->fp == NUM_LOCAL_PLAYER) ? 0xFF : chat->fp; /* build buffer */ buf[0] = 0xFF & ((chat->fh << 4) + (from & 0x0F)); buf[1] = 0xFF & ((chat->th << 4) + (chat->tp & 0x0F)); memcpy (buf + 2, chat->txt, chat->len); buf[chat->len + 2] = (char)'\0'; /* return data */ *data = buf; *iob = chat->how & 0xFF; return (chat->len + 3); } /* Chat_PackData */ /* * unpack chat data */ XBChat * Chat_UnpackData (const char *data, size_t len, unsigned iob) { XBChat *chat = NULL; unsigned char from; if (len > 2) { chat = Chat_Create (); from = data[0] & 0x0F; if (from == 0x0F) { from = NUM_LOCAL_PLAYER; } Chat_Set (chat, data[0] >> 4, from, data[1] >> 4, data[1] & 0x0F, iob, data + 2); Dbg_Chat ("unpacking (%u,%u)->(%u,%u)-%u-%s(%lu)\n", chat->fh, chat->fp, chat->th, chat->tp, chat->how, chat->txt, (unsigned long)chat->len); } return chat; } /* Chat_UnpackData */ /********* * input * *********/ /* * deactivate all input */ static void Chat_Deactivate (void) { int p; assert (initialized); /* deactivating all input */ for (p = 0; p <= NUM_LOCAL_PLAYER; p++) { if (input[p] != NULL) { input[p]->status = XBCS_Inactive; } } Dbg_Chat ("deactivating all current input\n"); active = -1; } /* Chat_Deactivate */ /* * activate chat input for a player */ static void Chat_ActivateInput (unsigned int local) { assert (local >= 0); assert (local <= NUM_LOCAL_PLAYER); assert (initialized); assert (input[local] != NULL); Chat_Deactivate (); /* activate */ input[local]->status = XBCS_Input; active = local; } /* Chat_Activate */ /* * start chat input for a local player */ static void Chat_StartInput (unsigned int local) { unsigned char id = Network_LocalHostId (); if (id >= MAX_HOSTS) { Dbg_Chat ("cannot start input, no host id\n"); return; } assert (local <= NUM_LOCAL_PLAYER); assert (initialized); /* activating input for local */ if (input[local] == NULL) { input[local] = Chat_Create (); Chat_Set (input[local], id, local, 0, 0, XBCM_Public, ""); Dbg_Chat ("initializing chat input for player %u\n", local); } SetGet ("Start Chatting", XBTrue); Chat_ActivateInput (local); } /* Chat_StartInput */ /* * send input for a local player */ static void Chat_SendInput (unsigned int local) { assert (local <= NUM_LOCAL_PLAYER); assert (initialized); if (input[local] == NULL) { Dbg_Chat ("no chat input for player %u, cannot send\n", local); return; } switch (input[local]->status) { case XBCS_Inactive: Dbg_Chat ("activating chat input for player %u before send\n", local); SetGet (input[local]->txt, XBTrue); Chat_ActivateInput (local); break; case XBCS_Input: Dbg_Chat ("sending chat input #%u\n", local); switch (Network_GetType ()) { case XBNT_Server: Server_ReceiveChat (input[local]); break; case XBNT_Client: Client_SendChat (input[local]); break; default: Dbg_Chat ("failed to send, invalid net type\n"); return; } input[local]->status = XBCS_Sent; SetGet (NULL, XBTrue); input[local] = NULL; active = -1; break; default: Dbg_Chat ("failed to send chat input #%u, invalid status %u\n", local, input[local]->status); break; } } /* Chat_SendInput */ /* * cancel input for local player */ static void Chat_CancelInput (unsigned int local) { assert (local <= NUM_LOCAL_PLAYER); assert (initialized); if (input[local] == NULL) { Dbg_Chat ("no chat input for player %u, cannot cancel\n", local); return; } switch (input[local]->status) { case XBCS_Inactive: Dbg_Chat ("activating chat input for player %u before cancel\n", local); SetGet (input[local]->txt, XBTrue); Chat_ActivateInput (local); break; case XBCS_Input: Dbg_Chat ("canceling chat input #%u\n", local); memset (input[local]->txt, 0, sizeof (input[local]->txt)); input[local]->len = 0; SetGet ("Chat canceled", XBTrue); input[local]->status = XBCS_Inactive; active = -1; break; default: Dbg_Chat ("failed to send chat input #%u, invalid status %u\n", local, input[local]->status); break; } } /* Chat_CancelInput */ /* * choose next target */ static void Chat_NextTarget (unsigned int local) { XBAtom atom; XBChat *chat; assert (local <= NUM_LOCAL_PLAYER); assert (initialized); if (input[local] == NULL) { Chat_StartInput (local); } chat = input[local]; switch (chat->status) { case XBCS_Inactive: Dbg_Chat ("activating chat input #%u\n", local); SetGet (input[local]->txt, XBTrue); Chat_ActivateInput (local); case XBCS_Input: switch (chat->how) { case XBCM_Public: chat->how = XBCM_Team; SetGet ("Team message", XBTrue); return; case XBCM_Team: chat->how = XBCM_Private; if (Network_GetFirstOtherPlayer (chat->fh, local, &chat->th, &chat->tp)) { atom = Network_GetPlayer2 (chat->th, chat->tp); SetGet (GUI_AtomToString (atom), XBTrue); Dbg_Chat ("first other player = %s (%u,%u)\n", GUI_AtomToString (atom), chat->th, chat->tp); } else { Dbg_Chat ("no other players found, skipping private target\n"); Chat_NextTarget (local); } break; case XBCM_Private: if (Network_GetNextOtherPlayer (chat->fh, local, &chat->th, &chat->tp)) { atom = Network_GetPlayer2 (chat->th, chat->tp); SetGet (GUI_AtomToString (atom), XBTrue); Dbg_Chat ("next other player = %s (%u,%u)\n", GUI_AtomToString (atom), chat->th, chat->tp); } else { chat->how = XBCM_Public; SetGet ("Public message", XBTrue); } break; default: break; } Dbg_Chat ("how = %u\n", chat->how); break; default: Dbg_Chat ("failed to change target for input #%u, invalid status %u\n", local, input[local]->status); break; } } /* Chat_NextTarget */ /* * handle backspace for active input */ static void Chat_Backspace (void) { assert (initialized); if (active < 0) { Dbg_Chat ("no active input, backspace failed\n"); return; } assert (active <= NUM_LOCAL_PLAYER); if (input[active] != NULL && input[active]->status == XBCS_Input) { size_t len = input[active]->len; if (len > 0) { input[active]->txt[--len] = (char)0; input[active]->len = len; Dbg_Chat ("backspacing chat input #%u\n", active); SetGet (input[active]->txt, XBTrue); } else { Dbg_Chat ("chat input #%u empty, backspace failed\n", active); } return; } } /* Chat_Backspace */ /* * add a character for active input, return if overflow */ static XBBool Chat_AddAscii (char ascii) { size_t len; assert (initialized); if (active < 0) { Dbg_Chat ("no active input, failed to add char\n"); return XBFalse; } assert (active <= NUM_LOCAL_PLAYER); /* find active input */ len = input[active]->len; if (len < CHAT_LINE_SIZE - 1) { input[active]->txt[len++] = ascii; input[active]->txt[len] = (char)0; input[active]->len = len; SetGet (input[active]->txt, XBTrue); Dbg_Chat ("adding character to chat input #%u, length %lu = %s\n", active, (unsigned long)strlen (input[active]->txt), input[active]->txt); return XBTrue; } else { Dbg_Chat ("ignoring character, line for display #%u too long\n", active); return XBFalse; } } /* Chat_AddAscii */ /**************************** * event checking for menus * ****************************/ /* * add event code */ void Chat_AddEventCode (unsigned local, XBEventCode ev) { assert (initialized); assert (local <= NUM_LOCAL_PLAYER); codes[local] = ev; Dbg_Chat ("assigning event type %u to local player %u\n", ev, local); } /* Chat_AddEventCode */ /* * find display to which event code corresponds to */ unsigned char Chat_FindCode (XBEventCode ev) { unsigned id; if (!initialized || !listening) { return 0xFF; } for (id = 0; id <= NUM_LOCAL_PLAYER; id++) { if (codes[id] == ev) { return id; } } return 0xFF; } /* Chat_FindCode */ /* * get current event code of active chatter */ XBEventCode Chat_GetCurrentCode (void) { if (!initialized || !listening || active < 0) { return XBE_NONE; } return codes[active]; } /* Chat_GetCurrentCode */ /* * chat keys */ XBBool Chat_Event (XBEventCode event, XBEventData data) { unsigned int local; /* check if chat mode active */ if (!listening) { return XBFalse; } /* if so, grab the chat events */ switch (event) { /* a fixed chat key has been entered */ case XBE_CHAT: /* redefine event to currently active */ event = Chat_GetCurrentCode (); /* restart event routine */ assert (event != XBE_CHAT); return Chat_Event (event, data); /* ascii character for current input, if any */ case XBE_ASCII: Chat_AddAscii ((char)data.value); break; /* control characters */ case XBE_CTRL: return XBFalse; /* chat keys */ default: /* TODO: find better solution than +-1000 */ if (data.value < 1000) { return XBFalse; } local = Chat_FindCode (event); if (local > NUM_LOCAL_PLAYER) { return XBFalse; } Dbg_Chat ("chat event %u for display %u\n", data.value, local); switch (data.value - 1000) { case XBCE_START: Chat_StartInput (local); break; case XBCE_ESCAPE: case XBCE_CANCEL: Chat_CancelInput (local); break; case XBCE_CHANGE: Chat_NextTarget (local); break; case XBCE_SEND: case XBCE_ENTER: Chat_SendInput (local); break; case XBCE_BACK: Chat_Backspace (); break; default: return XBFalse; } break; } return XBTrue; } /* * end of file chat.c */