2 * $Logfile: /Freespace2/code/Network/multi_pmsg.cpp $
8 * Revision 1.2 2002/05/07 03:16:47 theoddone33
9 * The Great Newline Fix
11 * Revision 1.1.1.1 2002/05/03 03:28:10 root
15 * 7 3/10/99 6:50p Dave
16 * Changed the way we buffer packets for all clients. Optimized turret
17 * fired packets. Did some weapon firing optimizations.
19 * 6 3/09/99 6:24p Dave
20 * More work on object update revamping. Identified several sources of
21 * unnecessary bandwidth.
23 * 5 2/24/99 2:25p Dave
24 * Fixed up chatbox bugs. Made squad war reporting better. Fixed a respawn
25 * bug for dogfight more.
27 * 4 11/19/98 8:03a Dave
28 * Full support for D3-style reliable sockets. Revamped packet lag/loss
29 * system, made it receiver side and at the lowest possible level.
31 * 3 11/17/98 11:12a Dave
32 * Removed player identification by address. Now assign explicit id #'s.
34 * 2 10/07/98 10:53a Dave
37 * 1 10/07/98 10:50a Dave
39 * 18 6/13/98 9:32p Mike
40 * Kill last character in file which caused "Find in Files" to report the
41 * file as "not a text file."
43 * 17 6/13/98 6:01p Hoffoss
44 * Externalized all new (or forgot to be added) strings to all the code.
46 * 16 6/13/98 3:19p Hoffoss
47 * NOX()ed out a bunch of strings that shouldn't be translated.
49 * 15 5/13/98 6:54p Dave
50 * More sophistication to PXO interface. Changed respawn checking so
51 * there's no window for desynchronization between the server and the
54 * 14 5/08/98 5:05p Dave
55 * Go to the join game screen when quitting multiplayer. Fixed mission
56 * text chat bugs. Put mission type symbols on the create game list.
57 * Started updating standalone gui controls.
59 * 13 5/02/98 5:38p Dave
60 * Put in new tracker API code. Put in ship information on mp team select
61 * screen. Make standalone server name permanent. Fixed standalone server
64 * 12 4/23/98 6:18p Dave
65 * Store ETS values between respawns. Put kick feature in the text
66 * messaging system. Fixed text messaging system so that it doesn't
67 * process or trigger ship controls. Other UI fixes.
69 * 11 4/22/98 4:59p Allender
70 * new multiplayer dead popup. big changes to the comm menu system for
71 * team vs. team. Start of debriefing stuff for team vs. team Make form
72 * on my wing work with individual ships who have high priority orders
74 * 10 4/16/98 6:35p Dave
75 * Display more informative prompt when typing in a text message.
77 * 9 4/06/98 10:24p Dave
78 * Fixed up Netgame.respawn for the standalone case.
80 * 8 4/05/98 3:30p Dave
81 * Print netplayer messages in brighter green on the hud, with
82 * accompanying sound. Echo netplayer messages on sending machine. Fixed
83 * standalone sequencing bug where host never get the "enter mission"
86 * 7 4/04/98 4:22p Dave
87 * First rev of UDP reliable sockets is done. Seems to work well if not
90 * 6 4/03/98 1:03a Dave
91 * First pass at unreliable guaranteed delivery packets.
93 * 5 4/02/98 5:50p Dave
94 * Put in support for standard comm messages to get sent to netplayers as
95 * well as ai ships. Make critical button presses not get evaluated on the
98 * 4 4/01/98 5:56p Dave
99 * Fixed a messaging bug which caused msg_all mode in multiplayer not to
100 * work. Compile out a host of multiplayer options not available in the
103 * 3 3/27/98 11:57a Dave
104 * Put in expression checking for text messages.
106 * 2 3/25/98 2:16p Dave
107 * Select random default image for newly created pilots. Fixed several
108 * multi-pause messaging bugs. Begin work on online help for multiplayer
111 * 1 3/19/98 5:04p Dave
118 #include "multimsgs.h"
119 #include "multiutil.h"
120 #include "multi_pmsg.h"
121 #include "multi_kick.h"
124 #include "hudmessage.h"
125 #include "hudsquadmsg.h"
130 // ----------------------------------------------------------------------------------
131 // MULTI MESSAGING DEFINES/VARS
134 // if a key is down less than this time, fire up the test messaging system, otherwise fire up the voice messaging system
135 #define MULTI_MSG_KEYDOWN_WAIT 325 // in ms
137 // sound to play before displaying incoming text messages in-mission
138 #define MULTI_MSG_TEXT_SOUND SND_CUE_VOICE
140 // max length of a string we'll allow players to send
141 #define MULTI_MSG_MAX_LEN 75
143 // current message processing mode
144 int Multi_msg_mode = MULTI_MSG_NONE;
146 // timestamp for timing keydown
147 int Multi_msg_stamp = -1;
149 // flag indicating if there is _still_ a key down for the current message mode
150 int Multi_msg_repeat_flag = 0;
152 // timestamp set when we leave messaging mode, use to keep eating keys for a short period of time
153 int Multi_msg_eat_stamp = -1;
155 // text message input vars
156 int Multi_msg_text_enter = 0;
157 char Multi_msg_text[MULTI_MSG_MAX_TEXT_LEN+1];
159 // command defines - all these commands must be followed by a ":" so that we can easily tokenize and recognize
160 // it as a command instead of a word. they also must be immediately at the beginning of a text string
161 // SO : kick dave would not work
162 // kick: dave would work
163 // Finally, if no command is found but there is a ":", it uses the text before the : as an expression to
164 // lookup players to route the text to
165 #define MULTI_MSG_CMD_COUNT 1 // # of commands
166 #define MULTI_MSG_CMD_KICK 0 // kick command
169 char *Multi_msg_commands[MULTI_MSG_CMD_COUNT] = { // commands themselves
174 // process an entered line of text and maybe perform a command on it (return 1 if an action was performed, 0 if not)
175 int multi_msg_check_command(char *str);
177 // perform the passed command (MULTI_MSG_CMD_* define) with the passed string argument
178 void multi_msg_perform_command(int command,char *param);
181 // ----------------------------------------------------------------------------------
182 // MULTI MESSAGING FUNCTIONS
185 // called when a messaging key has been detected as being pressed
186 void multi_msg_key_down(int mode)
188 // keep eating keys for a short period of time
189 if((Multi_msg_eat_stamp != -1) && !timestamp_elapsed(Multi_msg_eat_stamp)){
193 // if our player flags are marked as being in msg mode, don't do anything
194 if(Player->flags & PLAYER_FLAGS_MSG_MODE){
198 // if there already is a keydown
199 if(Multi_msg_mode != MULTI_MSG_NONE){
200 // if it is the same as the current mode, set the "still down" flag
201 if((mode == Multi_msg_mode) && !Multi_msg_text_enter){
202 Multi_msg_repeat_flag = 1;
209 // otherwise set the message mode and set the timestamp
210 Multi_msg_mode = mode;
211 Multi_msg_repeat_flag = 1;
212 Multi_msg_stamp = timestamp(MULTI_MSG_KEYDOWN_WAIT);
215 // returns true when messaging system has determined that we should be messaging with voice
216 int multi_msg_voice_record()
218 return ((Multi_msg_mode != MULTI_MSG_NONE) && timestamp_elapsed(Multi_msg_stamp) && Multi_msg_repeat_flag && !Multi_msg_text_enter) ? 1 : 0;
221 // general processing function to do things like timing keydown, etc. call from multi_do_frame()
222 void multi_msg_process()
224 // keep eating keys for a short period of time
225 if((Multi_msg_eat_stamp != -1) && timestamp_elapsed(Multi_msg_eat_stamp)){
226 Multi_msg_eat_stamp = -1;
230 // if we don't currently have a valid mode set, don't do anything
231 if(Multi_msg_mode == MULTI_MSG_NONE){
235 // if the key has been released
236 if(!Multi_msg_repeat_flag && (Multi_msg_stamp != -1) && !Multi_msg_text_enter){
237 // if the timestamp had not yet elapsed, fire up the text messaging system
238 // this is the equivalent of a (TAP)
239 if(!timestamp_elapsed(Multi_msg_stamp) && !Multi_msg_text_enter){
240 // fire up text messaging system here
241 Multi_msg_text_enter = 1;
242 memset(Multi_msg_text,0,MULTI_MSG_MAX_TEXT_LEN+1);
244 Multi_msg_mode = MULTI_MSG_NONE;
245 Multi_msg_stamp = -1;
249 // unset the repeat flag every frame
250 Multi_msg_repeat_flag = 0;
253 // get the current messaging mode
256 return Multi_msg_mode;
259 // return 0 or 1 if in text chat mode or not
260 int multi_msg_text_mode()
262 return Multi_msg_text_enter;
265 // process a text string entered by the local player
266 void multi_msg_eval_text_msg()
270 // if its a 0 length string, don't do anything
271 if(strlen(Multi_msg_text) <= 0){
275 // evaluate any special commands here
276 if(multi_msg_check_command(Multi_msg_text)){
280 // get the player if in MSG_TARGET mode
281 if(Multi_msg_mode == MULTI_MSG_TARGET){
282 if(Player_ai->target_objnum != -1){
283 player_index = multi_find_player_by_object(&Objects[Player_ai->target_objnum]);
284 if(player_index != -1){
285 // send the chat packet
286 send_game_chat_packet(Net_player, Multi_msg_text, Multi_msg_mode, &Net_players[player_index]);
288 // echo the message locally
289 multi_msg_display_mission_text(Multi_msg_text, MY_NET_PLAYER_NUM);
295 // send the chat packet
296 send_game_chat_packet(Net_player, Multi_msg_text, Multi_msg_mode, NULL);
298 // echo the message locally
299 multi_msg_display_mission_text(Multi_msg_text, MY_NET_PLAYER_NUM);
303 // maybe process a keypress in text messaging mode, return true if the key was processed
304 int multi_msg_text_process(int k)
308 // keep eating keys for a short period of time
309 if((Multi_msg_eat_stamp != -1) && !timestamp_elapsed(Multi_msg_eat_stamp)){
313 // if we're not in text message mode, return 0
314 if(!Multi_msg_text_enter){
319 // cancel the message
321 multi_msg_text_flush();
326 multi_msg_eval_text_msg();
327 multi_msg_text_flush();
332 if(strlen(Multi_msg_text) > 0){
333 Multi_msg_text[strlen(Multi_msg_text)-1] = '\0';
337 // ignore these individual keys
338 case KEY_LSHIFT + KEY_SHIFTED:
339 case KEY_RSHIFT + KEY_SHIFTED:
340 case KEY_LALT + KEY_SHIFTED:
341 case KEY_RALT + KEY_SHIFTED:
342 case KEY_LCTRL + KEY_SHIFTED:
343 case KEY_RCTRL + KEY_SHIFTED:
346 // stick other printable characters onto the text
348 // if we're not already at the maximum length
349 if(strlen(Multi_msg_text) < MULTI_MSG_MAX_LEN){
350 str[0] = (char)key_to_ascii(k);
352 strcat(Multi_msg_text,str);
360 // return 0 or 1 if there is multi text to be rendered (filling in txt if necessary)
361 int multi_msg_message_text(char *txt)
363 // if we're not in text message mode, return 0
364 if(!Multi_msg_text_enter){
368 // put the target of the message at the front of the string
369 switch(Multi_msg_mode){
370 // messaging all players
372 strcpy(txt,XSTR("ALL : ",694));
375 // messaging friendly players
376 case MULTI_MSG_FRIENDLY:
377 strcpy(txt,XSTR("FRIENDLY : ",695));
380 // messaging hostile players
381 case MULTI_MSG_HOSTILE:
382 strcpy(txt,XSTR("HOSTILE : ",696));
385 // messaging targeted ship
386 case MULTI_MSG_TARGET:
387 strcpy(txt,XSTR("TARGET : ",697));
393 strcat(txt,Multi_msg_text);
398 // display ingame,inmission message text
399 void multi_msg_display_mission_text(char *msg,int player_index)
401 // play a cue voice sound
402 snd_play(&Snds[MULTI_MSG_TEXT_SOUND]);
404 if(MULTI_STANDALONE(Net_players[player_index])){
405 HUD_sourced_printf(HUD_SOURCE_NETPLAYER,"%s %s",XSTR("<SERVER>",698),msg);
407 HUD_sourced_printf(HUD_SOURCE_NETPLAYER,"%s : %s",Net_players[player_index].player->callsign,msg);
411 // if the passed net_player's callsign matches the reg expression of the passed expr
412 int multi_msg_matches_expr(net_player *player,char *expr)
414 char callsign[CALLSIGN_LEN+1];
417 // some error checking
418 if((player == NULL) || (expr == NULL) || (strlen(expr) <= 0)){
422 // get the completely lowercase callsign
423 memset(callsign,0,CALLSIGN_LEN+1);
424 len = strlen(player->player->callsign);
425 for(idx=0;idx<len;idx++){
426 callsign[idx] = (char)tolower(player->player->callsign[idx]);
429 // see if this guy's callsign matches the expr
431 for(idx=0;idx<len;idx++){
432 // look for non-matching characters
433 if(callsign[idx] != expr[idx]){
442 // if text input mode is active, clear it
443 void multi_msg_text_flush()
445 Multi_msg_text_enter = 0;
446 Multi_msg_mode = MULTI_MSG_NONE;
447 Multi_msg_stamp = -1;
449 // keep eating keys for a short period of time and unset any used control bits
450 Multi_msg_eat_stamp = timestamp(350);
451 control_config_clear_used_status();
456 // -----------------------------------------------------------------------------------
457 // MULTI MESSAGE COMMAND FUNCTIONS
460 // process an entered line of text and maybe perform a command on it (return 1 if an action was performed, 0 if not)
461 int multi_msg_check_command(char *str)
464 char *prefix,*predicate,param[MULTI_MSG_MAX_TEXT_LEN+1];
467 if(strstr(str,":") == NULL){
471 // try and find a command prefix
473 prefix = strtok(str,":");
478 // get all the text after the message
480 predicate = strtok(NULL, NOX("\n\0"));
481 if(predicate == NULL){
485 // store the text as the actual parameter
486 strcpy(param,predicate);
487 drop_leading_white_space(param);
489 // go through all existing commands and see what we can do
490 for(idx=0;idx<MULTI_MSG_CMD_COUNT;idx++){
491 if(!stricmp(prefix,Multi_msg_commands[idx])){
492 // perform the command
493 multi_msg_perform_command(idx,param);
500 // apply the results as a general expression, if we're in message all mode
501 if(Multi_msg_mode == MULTI_MSG_ALL){
502 strcpy(Multi_msg_text,param);
504 // send the chat packet
505 send_game_chat_packet(Net_player, Multi_msg_text, MULTI_MSG_EXPR,NULL, prefix);
507 // echo the message locally
508 multi_msg_display_mission_text(Multi_msg_text, MY_NET_PLAYER_NUM);
514 // no commands performed
518 // perform the passed command (MULTI_MSG_CMD_* define) with the passed string argument
519 void multi_msg_perform_command(int command,char *param)
521 // we may eventually want to split each of these cases into its own function to make things neater
524 case MULTI_MSG_CMD_KICK:
525 int np_index = multi_find_player_by_callsign(param);
527 multi_kick_player(np_index);
534 // -----------------------------------------------------------------------------------
535 // MULTI SQUADMATE MESSAGING FUNCTIONS
540 char *Multi_msg_subsys_name[SUBSYSTEM_MAX] = {
556 // display a squadmsg order directed towards _me_
557 void multi_msg_show_squadmsg(net_player *source,int command,ushort target_sig,int subsys_type)
559 char hud_string[255];
560 char temp_string[100];
565 memset(hud_string,0,255);
566 memset(temp_string,0,100);
568 // add the message header
569 sprintf(hud_string,XSTR("ORDER FROM <%s> : ",699),source->player->callsign);
571 // get the target obj if possible
573 target_obj = multi_get_network_object(target_sig);
577 // add the command specific text
580 case ATTACK_TARGET_ITEM :
581 if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){
582 sprintf(temp_string,XSTR("Attack %s",700),Ships[target_obj->instance].ship_name);
583 strcat(hud_string,temp_string);
590 case DISABLE_TARGET_ITEM:
591 if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){
592 sprintf(temp_string,XSTR("Disable %s",701),Ships[target_obj->instance].ship_name);
593 strcat(hud_string,temp_string);
600 case PROTECT_TARGET_ITEM:
601 if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){
602 sprintf(temp_string,XSTR("Protect %s",702),Ships[target_obj->instance].ship_name);
603 strcat(hud_string,temp_string);
610 case IGNORE_TARGET_ITEM:
611 if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){
612 sprintf(temp_string,XSTR("Ignore %s",703),Ships[target_obj->instance].ship_name);
613 strcat(hud_string,temp_string);
620 case DISARM_TARGET_ITEM:
621 if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){
622 sprintf(temp_string,XSTR("Disarm %s",704),Ships[target_obj->instance].ship_name);
623 strcat(hud_string,temp_string);
629 // disable subsystem on my target
630 case DISABLE_SUBSYSTEM_ITEM:
631 if((target_obj != NULL) && (target_obj->type == OBJ_SHIP) && (subsys_type != -1) && (subsys_type != 0)){
632 sprintf(temp_string,XSTR("Disable subsystem %s on %s",705),Multi_msg_subsys_name[subsys_type],Ships[target_obj->instance].ship_name);
633 strcat(hud_string,temp_string);
641 strcat(hud_string,XSTR("Form on my wing",706));
646 strcat(hud_string,XSTR("Cover me",707));
650 case ENGAGE_ENEMY_ITEM:
651 strcat(hud_string,XSTR("Engage enemy!",708));
661 HUD_printf(hud_string);
665 // evaluate if the given netplayer exists in the passed wingnum
666 int multi_msg_player_in_wing(int wingnum,net_player *pl)
670 // if this guy doesn't have a valid ship, bail
671 if((pl->player->objnum == -1) || (Objects[pl->player->objnum].type != OBJ_SHIP)){
675 // look through all ships in the wing
676 for(idx=0;idx<Wings[wingnum].current_count;idx++){
677 // if we found a match
678 if(Wings[wingnum].ship_index[idx] == Objects[pl->player->objnum].instance){
686 // evaluate if the given netplayer is flying the passed shipnum
687 int multi_msg_player_in_ship(int shipnum,net_player *pl)
689 // if we found a matching ship
690 if((pl->player->objnum != -1) && (Objects[pl->player->objnum].type == OBJ_SHIP) && (shipnum == Objects[pl->player->objnum].instance)){
694 // not a matching ship
698 // send a squadmsg packet to a player
699 void multi_msg_send_squadmsg_packet(net_player *target,net_player *source,int command,ushort net_sig,int subsys_type)
705 Assert(source != NULL);
706 Assert(target != NULL);
707 if((source == NULL) || (target == NULL)){
712 BUILD_HEADER(SQUADMSG_PLAYER);
714 // add the command and targeting data
717 // add the id of the guy sending the order
718 ADD_DATA(source->player_id);
723 // targeted subsytem (or -1 if none)
724 s_val = (char)subsys_type;
727 // send to the player
728 multi_io_send_reliable(target, data, packet_size);
731 // evaluate if a wing SQUADMATE MESSAGE command should be sent to a player
732 // return 0 if at least one ai ship got the order, 1 if only players
733 int multi_msg_eval_wing_squadmsg(int wingnum,int command,ai_info *aif, int player_num)
740 // get the index of the sender
742 player_num = MY_NET_PLAYER_NUM;
744 // get the target information
745 if(aif->target_objnum == -1){
748 net_sig = Objects[aif->target_objnum].net_signature;
751 if((aif->targeted_subsys == NULL) || (aif->targeted_subsys->system_info == NULL)){
754 subsys_type = aif->targeted_subsys->system_info->type;
757 // go through all netplayers and find all matched
758 sent_count = Wings[wingnum].current_count;
759 for(idx=0;idx<MAX_PLAYERS;idx++){
760 if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx])){
761 // if he is in the wing, send him the message
762 if(multi_msg_player_in_wing(wingnum,&Net_players[idx])){
763 // if this was the sender himself, just decrement the count
764 if(idx == player_num){
769 // if its me, just display locally
770 if(&Net_players[idx] == Net_player){
771 multi_msg_show_squadmsg(&Net_players[player_num],command,net_sig,subsys_type);
774 // otherwise send it to who is supposed to get it
776 multi_msg_send_squadmsg_packet(&Net_players[idx],&Net_players[player_num],command,net_sig,subsys_type);
783 // if all the ships which got the message were players, return 1
787 // evaluate if a ship SQUADMATE MESSAGE command should be sent to a player
788 // return 0 if not sent to a netplayer, 1 if it was
789 int multi_msg_eval_ship_squadmsg(int shipnum,int command,ai_info *aif, int player_num)
795 // get the index of the sender
796 if ( player_num == -1 )
797 player_num = MY_NET_PLAYER_NUM;
799 // get the target information
800 if(aif->target_objnum == -1){
803 net_sig = Objects[aif->target_objnum].net_signature;
806 if((aif->targeted_subsys == NULL) || (aif->targeted_subsys->system_info == NULL)){
809 subsys_type = aif->targeted_subsys->system_info->type;
812 // go through all netplayers and find all matched
813 for(idx=0;idx<MAX_PLAYERS;idx++){
814 if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && (idx != player_num)){
815 // if he is in the ship, send him the message
816 if(multi_msg_player_in_ship(shipnum,&Net_players[idx])){
817 // if its me, just display locall
818 if(&Net_players[idx] == Net_player){
819 multi_msg_show_squadmsg(&Net_players[player_num],command,net_sig,subsys_type);
823 // otherwise send it to who is supposed to get it
825 multi_msg_send_squadmsg_packet(&Net_players[idx],&Net_players[player_num],command,net_sig,subsys_type);
832 // this will let the messaging system show a response to the sender of the packet
836 // process incoming squadmate messaging info
837 void multi_msg_process_squadmsg_packet(unsigned char *data, header *hinfo)
844 int offset = HEADER_LENGTH;
846 // get all packet data
853 // determine who the order is from
854 source_index = find_player_id(source_id);
855 if(source_index == -1){
856 nprintf(("Network","Received squadmsg order packet from unknown player!!\n"));
860 // display the squadmessage somehow
861 multi_msg_show_squadmsg(&Net_players[source_index],command,net_sig,(int)s_val);