2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Network/multi_pmsg.cpp $
16 * Revision 1.6 2005/10/02 09:30:10 taylor
17 * sync up rest of big-endian network changes. it should at least be as good as what's in FS2_Open now, only better :)
19 * Revision 1.5 2004/06/11 01:38:28 tigital
20 * byte-swapping changes for bigendian systems
22 * Revision 1.4 2002/06/09 04:41:23 relnev
23 * added copyright header
25 * Revision 1.3 2002/05/26 20:22:48 theoddone33
26 * Most of network/ works
28 * Revision 1.2 2002/05/07 03:16:47 theoddone33
29 * The Great Newline Fix
31 * Revision 1.1.1.1 2002/05/03 03:28:10 root
35 * 7 3/10/99 6:50p Dave
36 * Changed the way we buffer packets for all clients. Optimized turret
37 * fired packets. Did some weapon firing optimizations.
39 * 6 3/09/99 6:24p Dave
40 * More work on object update revamping. Identified several sources of
41 * unnecessary bandwidth.
43 * 5 2/24/99 2:25p Dave
44 * Fixed up chatbox bugs. Made squad war reporting better. Fixed a respawn
45 * bug for dogfight more.
47 * 4 11/19/98 8:03a Dave
48 * Full support for D3-style reliable sockets. Revamped packet lag/loss
49 * system, made it receiver side and at the lowest possible level.
51 * 3 11/17/98 11:12a Dave
52 * Removed player identification by address. Now assign explicit id #'s.
54 * 2 10/07/98 10:53a Dave
57 * 1 10/07/98 10:50a Dave
59 * 18 6/13/98 9:32p Mike
60 * Kill last character in file which caused "Find in Files" to report the
61 * file as "not a text file."
63 * 17 6/13/98 6:01p Hoffoss
64 * Externalized all new (or forgot to be added) strings to all the code.
66 * 16 6/13/98 3:19p Hoffoss
67 * NOX()ed out a bunch of strings that shouldn't be translated.
69 * 15 5/13/98 6:54p Dave
70 * More sophistication to PXO interface. Changed respawn checking so
71 * there's no window for desynchronization between the server and the
74 * 14 5/08/98 5:05p Dave
75 * Go to the join game screen when quitting multiplayer. Fixed mission
76 * text chat bugs. Put mission type symbols on the create game list.
77 * Started updating standalone gui controls.
79 * 13 5/02/98 5:38p Dave
80 * Put in new tracker API code. Put in ship information on mp team select
81 * screen. Make standalone server name permanent. Fixed standalone server
84 * 12 4/23/98 6:18p Dave
85 * Store ETS values between respawns. Put kick feature in the text
86 * messaging system. Fixed text messaging system so that it doesn't
87 * process or trigger ship controls. Other UI fixes.
89 * 11 4/22/98 4:59p Allender
90 * new multiplayer dead popup. big changes to the comm menu system for
91 * team vs. team. Start of debriefing stuff for team vs. team Make form
92 * on my wing work with individual ships who have high priority orders
94 * 10 4/16/98 6:35p Dave
95 * Display more informative prompt when typing in a text message.
97 * 9 4/06/98 10:24p Dave
98 * Fixed up Netgame.respawn for the standalone case.
100 * 8 4/05/98 3:30p Dave
101 * Print netplayer messages in brighter green on the hud, with
102 * accompanying sound. Echo netplayer messages on sending machine. Fixed
103 * standalone sequencing bug where host never get the "enter mission"
106 * 7 4/04/98 4:22p Dave
107 * First rev of UDP reliable sockets is done. Seems to work well if not
110 * 6 4/03/98 1:03a Dave
111 * First pass at unreliable guaranteed delivery packets.
113 * 5 4/02/98 5:50p Dave
114 * Put in support for standard comm messages to get sent to netplayers as
115 * well as ai ships. Make critical button presses not get evaluated on the
118 * 4 4/01/98 5:56p Dave
119 * Fixed a messaging bug which caused msg_all mode in multiplayer not to
120 * work. Compile out a host of multiplayer options not available in the
123 * 3 3/27/98 11:57a Dave
124 * Put in expression checking for text messages.
126 * 2 3/25/98 2:16p Dave
127 * Select random default image for newly created pilots. Fixed several
128 * multi-pause messaging bugs. Begin work on online help for multiplayer
131 * 1 3/19/98 5:04p Dave
139 #include "multimsgs.h"
140 #include "multiutil.h"
141 #include "multi_pmsg.h"
142 #include "multi_kick.h"
145 #include "hudmessage.h"
146 #include "hudsquadmsg.h"
151 // ----------------------------------------------------------------------------------
152 // MULTI MESSAGING DEFINES/VARS
155 // if a key is down less than this time, fire up the test messaging system, otherwise fire up the voice messaging system
156 #define MULTI_MSG_KEYDOWN_WAIT 325 // in ms
158 // sound to play before displaying incoming text messages in-mission
159 #define MULTI_MSG_TEXT_SOUND SND_CUE_VOICE
161 // max length of a string we'll allow players to send
162 #define MULTI_MSG_MAX_LEN 75
164 // current message processing mode
165 int Multi_msg_mode = MULTI_MSG_NONE;
167 // timestamp for timing keydown
168 int Multi_msg_stamp = -1;
170 // flag indicating if there is _still_ a key down for the current message mode
171 int Multi_msg_repeat_flag = 0;
173 // timestamp set when we leave messaging mode, use to keep eating keys for a short period of time
174 int Multi_msg_eat_stamp = -1;
176 // text message input vars
177 int Multi_msg_text_enter = 0;
178 char Multi_msg_text[MULTI_MSG_MAX_TEXT_LEN+1];
180 // command defines - all these commands must be followed by a ":" so that we can easily tokenize and recognize
181 // it as a command instead of a word. they also must be immediately at the beginning of a text string
182 // SO : kick dave would not work
183 // kick: dave would work
184 // Finally, if no command is found but there is a ":", it uses the text before the : as an expression to
185 // lookup players to route the text to
186 #define MULTI_MSG_CMD_COUNT 1 // # of commands
187 #define MULTI_MSG_CMD_KICK 0 // kick command
190 const char *Multi_msg_commands[MULTI_MSG_CMD_COUNT] = { // commands themselves
195 // process an entered line of text and maybe perform a command on it (return 1 if an action was performed, 0 if not)
196 int multi_msg_check_command(char *str);
198 // perform the passed command (MULTI_MSG_CMD_* define) with the passed string argument
199 void multi_msg_perform_command(int command,char *param);
202 // ----------------------------------------------------------------------------------
203 // MULTI MESSAGING FUNCTIONS
206 // called when a messaging key has been detected as being pressed
207 void multi_msg_key_down(int mode)
209 // keep eating keys for a short period of time
210 if((Multi_msg_eat_stamp != -1) && !timestamp_elapsed(Multi_msg_eat_stamp)){
214 // if our player flags are marked as being in msg mode, don't do anything
215 if(Player->flags & PLAYER_FLAGS_MSG_MODE){
219 // if there already is a keydown
220 if(Multi_msg_mode != MULTI_MSG_NONE){
221 // if it is the same as the current mode, set the "still down" flag
222 if((mode == Multi_msg_mode) && !Multi_msg_text_enter){
223 Multi_msg_repeat_flag = 1;
230 // otherwise set the message mode and set the timestamp
231 Multi_msg_mode = mode;
232 Multi_msg_repeat_flag = 1;
233 Multi_msg_stamp = timestamp(MULTI_MSG_KEYDOWN_WAIT);
236 // returns true when messaging system has determined that we should be messaging with voice
237 int multi_msg_voice_record()
239 return ((Multi_msg_mode != MULTI_MSG_NONE) && timestamp_elapsed(Multi_msg_stamp) && Multi_msg_repeat_flag && !Multi_msg_text_enter) ? 1 : 0;
242 // general processing function to do things like timing keydown, etc. call from multi_do_frame()
243 void multi_msg_process()
245 // keep eating keys for a short period of time
246 if((Multi_msg_eat_stamp != -1) && timestamp_elapsed(Multi_msg_eat_stamp)){
247 Multi_msg_eat_stamp = -1;
251 // if we don't currently have a valid mode set, don't do anything
252 if(Multi_msg_mode == MULTI_MSG_NONE){
256 // if the key has been released
257 if(!Multi_msg_repeat_flag && (Multi_msg_stamp != -1) && !Multi_msg_text_enter){
258 // if the timestamp had not yet elapsed, fire up the text messaging system
259 // this is the equivalent of a (TAP)
260 if(!timestamp_elapsed(Multi_msg_stamp) && !Multi_msg_text_enter){
261 // fire up text messaging system here
262 Multi_msg_text_enter = 1;
263 memset(Multi_msg_text,0,MULTI_MSG_MAX_TEXT_LEN+1);
265 Multi_msg_mode = MULTI_MSG_NONE;
266 Multi_msg_stamp = -1;
270 // unset the repeat flag every frame
271 Multi_msg_repeat_flag = 0;
274 // get the current messaging mode
277 return Multi_msg_mode;
280 // return 0 or 1 if in text chat mode or not
281 int multi_msg_text_mode()
283 return Multi_msg_text_enter;
286 // process a text string entered by the local player
287 void multi_msg_eval_text_msg()
291 // if its a 0 length string, don't do anything
292 if(strlen(Multi_msg_text) <= 0){
296 // evaluate any special commands here
297 if(multi_msg_check_command(Multi_msg_text)){
301 // get the player if in MSG_TARGET mode
302 if(Multi_msg_mode == MULTI_MSG_TARGET){
303 if(Player_ai->target_objnum != -1){
304 player_index = multi_find_player_by_object(&Objects[Player_ai->target_objnum]);
305 if(player_index != -1){
306 // send the chat packet
307 send_game_chat_packet(Net_player, Multi_msg_text, Multi_msg_mode, &Net_players[player_index]);
309 // echo the message locally
310 multi_msg_display_mission_text(Multi_msg_text, MY_NET_PLAYER_NUM);
316 // send the chat packet
317 send_game_chat_packet(Net_player, Multi_msg_text, Multi_msg_mode, NULL);
319 // echo the message locally
320 multi_msg_display_mission_text(Multi_msg_text, MY_NET_PLAYER_NUM);
324 // maybe process a keypress in text messaging mode, return true if the key was processed
325 int multi_msg_text_process(int k)
329 // keep eating keys for a short period of time
330 if((Multi_msg_eat_stamp != -1) && !timestamp_elapsed(Multi_msg_eat_stamp)){
334 // if we're not in text message mode, return 0
335 if(!Multi_msg_text_enter){
340 // cancel the message
342 multi_msg_text_flush();
347 multi_msg_eval_text_msg();
348 multi_msg_text_flush();
353 if(strlen(Multi_msg_text) > 0){
354 Multi_msg_text[strlen(Multi_msg_text)-1] = '\0';
358 // ignore these individual keys
359 case SDLK_LSHIFT + KEY_SHIFTED:
360 case SDLK_RSHIFT + KEY_SHIFTED:
361 case SDLK_LALT + KEY_SHIFTED:
362 case SDLK_RALT + KEY_SHIFTED:
363 case SDLK_LCTRL + KEY_SHIFTED:
364 case SDLK_RCTRL + KEY_SHIFTED:
367 // stick other printable characters onto the text
369 // if we're not already at the maximum length
370 if(strlen(Multi_msg_text) < MULTI_MSG_MAX_LEN){
371 int key_text = key_get_text_input();
377 str[0] = (char)key_text;
379 SDL_strlcat(Multi_msg_text, str, SDL_arraysize(Multi_msg_text));
387 // return 0 or 1 if there is multi text to be rendered (filling in txt if necessary)
388 int multi_msg_message_text(char *txt, const int txt_len)
390 // if we're not in text message mode, return 0
391 if(!Multi_msg_text_enter){
395 // put the target of the message at the front of the string
396 switch(Multi_msg_mode){
397 // messaging all players
399 SDL_strlcpy(txt, XSTR("ALL : ", 694), txt_len);
402 // messaging friendly players
403 case MULTI_MSG_FRIENDLY:
404 SDL_strlcpy(txt, XSTR("FRIENDLY : ", 695), txt_len);
407 // messaging hostile players
408 case MULTI_MSG_HOSTILE:
409 SDL_strlcpy(txt, XSTR("HOSTILE : ", 696), txt_len);
412 // messaging targeted ship
413 case MULTI_MSG_TARGET:
414 SDL_strlcpy(txt, XSTR("TARGET : ", 697), txt_len);
420 SDL_strlcat(txt, Multi_msg_text, txt_len);
421 SDL_strlcat(txt, "_", txt_len);
425 // display ingame,inmission message text
426 void multi_msg_display_mission_text(const char *msg, int player_index)
428 // play a cue voice sound
429 snd_play(&Snds[MULTI_MSG_TEXT_SOUND]);
431 if(MULTI_STANDALONE(Net_players[player_index])){
432 HUD_sourced_printf(HUD_SOURCE_NETPLAYER,"%s %s",XSTR("<SERVER>",698),msg);
434 HUD_sourced_printf(HUD_SOURCE_NETPLAYER,"%s : %s",Net_players[player_index].player->callsign,msg);
438 // if the passed net_player's callsign matches the reg expression of the passed expr
439 int multi_msg_matches_expr(net_player *player, const char *expr)
441 char callsign[CALLSIGN_LEN+1];
444 // some error checking
445 if((player == NULL) || (expr == NULL) || (strlen(expr) <= 0)){
449 // get the completely lowercase callsign
450 memset(callsign,0,CALLSIGN_LEN+1);
451 len = strlen(player->player->callsign);
452 for(idx=0;idx<len;idx++){
453 callsign[idx] = (char)tolower(player->player->callsign[idx]);
456 // see if this guy's callsign matches the expr
458 for(idx=0;idx<len;idx++){
459 // look for non-matching characters
460 if(callsign[idx] != expr[idx]){
469 // if text input mode is active, clear it
470 void multi_msg_text_flush()
472 Multi_msg_text_enter = 0;
473 Multi_msg_mode = MULTI_MSG_NONE;
474 Multi_msg_stamp = -1;
476 // keep eating keys for a short period of time and unset any used control bits
477 Multi_msg_eat_stamp = timestamp(350);
478 control_config_clear_used_status();
483 // -----------------------------------------------------------------------------------
484 // MULTI MESSAGE COMMAND FUNCTIONS
487 // process an entered line of text and maybe perform a command on it (return 1 if an action was performed, 0 if not)
488 int multi_msg_check_command(char *str)
491 char *prefix,*predicate,param[MULTI_MSG_MAX_TEXT_LEN+1];
494 if(strstr(str,":") == NULL){
498 // try and find a command prefix
500 prefix = strtok(str,":");
505 // get all the text after the message
507 predicate = strtok(NULL, NOX("\n\0"));
508 if(predicate == NULL){
512 // store the text as the actual parameter
513 SDL_strlcpy(param, predicate, SDL_arraysize(param));
514 drop_leading_white_space(param);
516 // go through all existing commands and see what we can do
517 for(idx=0;idx<MULTI_MSG_CMD_COUNT;idx++){
518 if(!SDL_strcasecmp(prefix,Multi_msg_commands[idx])){
519 // perform the command
520 multi_msg_perform_command(idx,param);
527 // apply the results as a general expression, if we're in message all mode
528 if(Multi_msg_mode == MULTI_MSG_ALL){
529 SDL_strlcpy(Multi_msg_text, param, SDL_arraysize(Multi_msg_text));
531 // send the chat packet
532 send_game_chat_packet(Net_player, Multi_msg_text, MULTI_MSG_EXPR,NULL, prefix);
534 // echo the message locally
535 multi_msg_display_mission_text(Multi_msg_text, MY_NET_PLAYER_NUM);
541 // no commands performed
545 // perform the passed command (MULTI_MSG_CMD_* define) with the passed string argument
546 void multi_msg_perform_command(int command,char *param)
548 // we may eventually want to split each of these cases into its own function to make things neater
551 case MULTI_MSG_CMD_KICK:
552 int np_index = multi_find_player_by_callsign(param);
554 multi_kick_player(np_index);
561 // -----------------------------------------------------------------------------------
562 // MULTI SQUADMATE MESSAGING FUNCTIONS
567 const char *Multi_msg_subsys_name[SUBSYSTEM_MAX] = {
585 // display a squadmsg order directed towards _me_
586 void multi_msg_show_squadmsg(net_player *source,int command,ushort target_sig,int subsys_type)
588 char hud_string[255];
589 char temp_string[100];
594 memset(hud_string,0,255);
595 memset(temp_string,0,100);
597 // add the message header
598 SDL_snprintf(hud_string,SDL_arraysize(hud_string),XSTR("ORDER FROM <%s> : ",699),source->player->callsign);
600 // get the target obj if possible
602 target_obj = multi_get_network_object(target_sig);
606 // add the command specific text
609 case ATTACK_TARGET_ITEM :
610 if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){
611 SDL_snprintf(temp_string,SDL_arraysize(temp_string),XSTR("Attack %s",700),Ships[target_obj->instance].ship_name);
612 SDL_strlcat(hud_string, temp_string, SDL_arraysize(hud_string));
619 case DISABLE_TARGET_ITEM:
620 if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){
621 SDL_snprintf(temp_string,SDL_arraysize(temp_string),XSTR("Disable %s",701),Ships[target_obj->instance].ship_name);
622 SDL_strlcat(hud_string, temp_string, SDL_arraysize(hud_string));
629 case PROTECT_TARGET_ITEM:
630 if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){
631 SDL_snprintf(temp_string,SDL_arraysize(temp_string),XSTR("Protect %s",702),Ships[target_obj->instance].ship_name);
632 SDL_strlcat(hud_string, temp_string, SDL_arraysize(hud_string));
639 case IGNORE_TARGET_ITEM:
640 if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){
641 SDL_snprintf(temp_string,SDL_arraysize(temp_string),XSTR("Ignore %s",703),Ships[target_obj->instance].ship_name);
642 SDL_strlcat(hud_string, temp_string, SDL_arraysize(hud_string));
649 case DISARM_TARGET_ITEM:
650 if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){
651 SDL_snprintf(temp_string,SDL_arraysize(temp_string),XSTR("Disarm %s",704),Ships[target_obj->instance].ship_name);
652 SDL_strlcat(hud_string, temp_string, SDL_arraysize(hud_string));
658 // disable subsystem on my target
659 case DISABLE_SUBSYSTEM_ITEM:
660 if((target_obj != NULL) && (target_obj->type == OBJ_SHIP) && (subsys_type != -1) && (subsys_type != 0)){
661 SDL_snprintf(temp_string,SDL_arraysize(temp_string),XSTR("Disable subsystem %s on %s",705),Multi_msg_subsys_name[subsys_type],Ships[target_obj->instance].ship_name);
662 SDL_strlcat(hud_string, temp_string, SDL_arraysize(hud_string));
670 SDL_strlcat(hud_string, XSTR("Form on my wing",706), SDL_arraysize(hud_string));
675 SDL_strlcat(hud_string, XSTR("Cover me",707), SDL_arraysize(hud_string));
679 case ENGAGE_ENEMY_ITEM:
680 SDL_strlcat(hud_string, XSTR("Engage enemy!",708), SDL_arraysize(hud_string));
690 HUD_printf(hud_string);
694 // evaluate if the given netplayer exists in the passed wingnum
695 int multi_msg_player_in_wing(int wingnum,net_player *pl)
699 // if this guy doesn't have a valid ship, bail
700 if((pl->player->objnum == -1) || (Objects[pl->player->objnum].type != OBJ_SHIP)){
704 // look through all ships in the wing
705 for(idx=0;idx<Wings[wingnum].current_count;idx++){
706 // if we found a match
707 if(Wings[wingnum].ship_index[idx] == Objects[pl->player->objnum].instance){
715 // evaluate if the given netplayer is flying the passed shipnum
716 int multi_msg_player_in_ship(int shipnum,net_player *pl)
718 // if we found a matching ship
719 if((pl->player->objnum != -1) && (Objects[pl->player->objnum].type == OBJ_SHIP) && (shipnum == Objects[pl->player->objnum].instance)){
723 // not a matching ship
727 // send a squadmsg packet to a player
728 void multi_msg_send_squadmsg_packet(net_player *target,net_player *source,int command,ushort net_sig,int subsys_type)
734 SDL_assert(source != NULL);
735 SDL_assert(target != NULL);
736 if((source == NULL) || (target == NULL)){
741 BUILD_HEADER(SQUADMSG_PLAYER);
743 // add the command and targeting data
746 // add the id of the guy sending the order
747 ADD_SHORT(source->player_id);
752 // targeted subsytem (or -1 if none)
753 s_val = (char)subsys_type;
756 // send to the player
757 multi_io_send_reliable(target, data, packet_size);
760 // evaluate if a wing SQUADMATE MESSAGE command should be sent to a player
761 // return 0 if at least one ai ship got the order, 1 if only players
762 int multi_msg_eval_wing_squadmsg(int wingnum,int command,ai_info *aif, int player_num)
769 // get the index of the sender
771 player_num = MY_NET_PLAYER_NUM;
773 // get the target information
774 if(aif->target_objnum == -1){
777 net_sig = Objects[aif->target_objnum].net_signature;
780 if((aif->targeted_subsys == NULL) || (aif->targeted_subsys->system_info == NULL)){
783 subsys_type = aif->targeted_subsys->system_info->type;
786 // go through all netplayers and find all matched
787 sent_count = Wings[wingnum].current_count;
788 for(idx=0;idx<MAX_PLAYERS;idx++){
789 if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx])){
790 // if he is in the wing, send him the message
791 if(multi_msg_player_in_wing(wingnum,&Net_players[idx])){
792 // if this was the sender himself, just decrement the count
793 if(idx == player_num){
798 // if its me, just display locally
799 if(&Net_players[idx] == Net_player){
800 multi_msg_show_squadmsg(&Net_players[player_num],command,net_sig,subsys_type);
803 // otherwise send it to who is supposed to get it
805 multi_msg_send_squadmsg_packet(&Net_players[idx],&Net_players[player_num],command,net_sig,subsys_type);
812 // if all the ships which got the message were players, return 1
816 // evaluate if a ship SQUADMATE MESSAGE command should be sent to a player
817 // return 0 if not sent to a netplayer, 1 if it was
818 int multi_msg_eval_ship_squadmsg(int shipnum,int command,ai_info *aif, int player_num)
824 // get the index of the sender
825 if ( player_num == -1 )
826 player_num = MY_NET_PLAYER_NUM;
828 // get the target information
829 if(aif->target_objnum == -1){
832 net_sig = Objects[aif->target_objnum].net_signature;
835 if((aif->targeted_subsys == NULL) || (aif->targeted_subsys->system_info == NULL)){
838 subsys_type = aif->targeted_subsys->system_info->type;
841 // go through all netplayers and find all matched
842 for(idx=0;idx<MAX_PLAYERS;idx++){
843 if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && (idx != player_num)){
844 // if he is in the ship, send him the message
845 if(multi_msg_player_in_ship(shipnum,&Net_players[idx])){
846 // if its me, just display locall
847 if(&Net_players[idx] == Net_player){
848 multi_msg_show_squadmsg(&Net_players[player_num],command,net_sig,subsys_type);
852 // otherwise send it to who is supposed to get it
854 multi_msg_send_squadmsg_packet(&Net_players[idx],&Net_players[player_num],command,net_sig,subsys_type);
861 // this will let the messaging system show a response to the sender of the packet
865 // process incoming squadmate messaging info
866 void multi_msg_process_squadmsg_packet(unsigned char *data, header *hinfo)
873 int offset = HEADER_LENGTH;
875 // get all packet data
877 GET_SHORT(source_id);
882 // determine who the order is from
883 source_index = find_player_id(source_id);
884 if(source_index == -1){
885 nprintf(("Network","Received squadmsg order packet from unknown player!!\n"));
889 // display the squadmessage somehow
890 multi_msg_show_squadmsg(&Net_players[source_index],command,net_sig,(int)s_val);