/* * Copyright (C) Volition, Inc. 1999. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #include #include "network/multi_pmsg.h" #include "network/multi.h" #include "network/multimsgs.h" #include "network/multiutil.h" #include "network/multi_kick.h" #include "gamesnd/gamesnd.h" #include "hud/hudmessage.h" #include "hud/hudsquadmsg.h" #include "io/key.h" #include "io/timer.h" #include "playerman/player.h" #include "ship/ship.h" #include "object/object.h" #include "parse/parselo.h" #include "sound/fsspeech.h" // ---------------------------------------------------------------------------------- // MULTI MESSAGING DEFINES/VARS // // if a key is down less than this time, fire up the test messaging system, otherwise fire up the voice messaging system #define MULTI_MSG_KEYDOWN_WAIT 325 // in ms // sound to play before displaying incoming text messages in-mission #define MULTI_MSG_TEXT_SOUND GameSounds::CUE_VOICE // max length of a string we'll allow players to send #define MULTI_MSG_MAX_LEN 75 // current message processing mode int Multi_msg_mode = MULTI_MSG_NONE; // timestamp for timing keydown static UI_TIMESTAMP Multi_msg_stamp; // flag indicating if there is _still_ a key down for the current message mode int Multi_msg_repeat_flag = 0; // timestamp set when we leave messaging mode, use to keep eating keys for a short period of time static UI_TIMESTAMP Multi_msg_eat_stamp; // text message input vars int Multi_msg_text_enter = 0; char Multi_msg_text[MULTI_MSG_MAX_TEXT_LEN+1]; // command defines - all these commands must be followed by a ":" so that we can easily tokenize and recognize // it as a command instead of a word. they also must be immediately at the beginning of a text string // SO : kick dave would not work // kick: dave would work // Finally, if no command is found but there is a ":", it uses the text before the : as an expression to // lookup players to route the text to #define MULTI_MSG_CMD_COUNT 1 // # of commands #define MULTI_MSG_CMD_KICK 0 // kick command //XSTR:OFF const char *Multi_msg_commands[MULTI_MSG_CMD_COUNT] = { // commands themselves "kick" }; //XSTR:ON // process an entered line of text and maybe perform a command on it (return 1 if an action was performed, 0 if not) int multi_msg_check_command(char *str); // perform the passed command (MULTI_MSG_CMD_* define) with the passed string argument void multi_msg_perform_command(int command,char *param); // ---------------------------------------------------------------------------------- // MULTI MESSAGING FUNCTIONS // // called when a messaging key has been detected as being pressed void multi_msg_key_down(int mode) { // keep eating keys for a short period of time if ( Multi_msg_eat_stamp.isValid() && !ui_timestamp_elapsed(Multi_msg_eat_stamp) ) { return; } // if our player flags are marked as being in msg mode, don't do anything if(Player->flags & PLAYER_FLAGS_MSG_MODE){ return; } // if there already is a keydown if(Multi_msg_mode != MULTI_MSG_NONE){ // if it is the same as the current mode, set the "still down" flag if((mode == Multi_msg_mode) && !Multi_msg_text_enter){ Multi_msg_repeat_flag = 1; } // return here return; } // otherwise set the message mode and set the timestamp Multi_msg_mode = mode; Multi_msg_repeat_flag = 1; Multi_msg_stamp = ui_timestamp(MULTI_MSG_KEYDOWN_WAIT); } // returns true when messaging system has determined that we should be messaging with voice int multi_msg_voice_record() { return ((Multi_msg_mode != MULTI_MSG_NONE) && ui_timestamp_elapsed(Multi_msg_stamp) && Multi_msg_repeat_flag && !Multi_msg_text_enter) ? 1 : 0; } // general processing function to do things like timing keydown, etc. call from multi_do_frame() void multi_msg_process() { // keep eating keys for a short period of time if (ui_timestamp_elapsed(Multi_msg_eat_stamp)) { Multi_msg_eat_stamp = UI_TIMESTAMP::invalid(); return; } // if we don't currently have a valid mode set, don't do anything if(Multi_msg_mode == MULTI_MSG_NONE){ return; } // if the key has been released if(!Multi_msg_repeat_flag && Multi_msg_stamp.isValid() && !Multi_msg_text_enter){ // if the timestamp had not yet elapsed, fire up the text messaging system // this is the equivalent of a (TAP) if(!ui_timestamp_elapsed(Multi_msg_stamp) && !Multi_msg_text_enter){ // fire up text messaging system here Multi_msg_text_enter = 1; memset(Multi_msg_text,0,MULTI_MSG_MAX_TEXT_LEN+1); } else { Multi_msg_mode = MULTI_MSG_NONE; Multi_msg_stamp = UI_TIMESTAMP::invalid(); } } // unset the repeat flag every frame Multi_msg_repeat_flag = 0; } // get the current messaging mode int multi_msg_mode() { return Multi_msg_mode; } // return 0 or 1 if in text chat mode or not int multi_msg_text_mode() { return Multi_msg_text_enter; } // process a text string entered by the local player void multi_msg_eval_text_msg() { int player_index; // if its a 0 length string, don't do anything if(strlen(Multi_msg_text) <= 0){ return; } // evaluate any special commands here if(multi_msg_check_command(Multi_msg_text)){ return; } // get the player if in MSG_TARGET mode if(Multi_msg_mode == MULTI_MSG_TARGET){ if(Player_ai->target_objnum != -1){ player_index = multi_find_player_by_object(&Objects[Player_ai->target_objnum]); if(player_index != -1){ // send the chat packet send_game_chat_packet(Net_player, Multi_msg_text, Multi_msg_mode, &Net_players[player_index]); // echo the message locally multi_msg_display_mission_text(Multi_msg_text, MY_NET_PLAYER_NUM); } } } // all other modes else { // send the chat packet send_game_chat_packet(Net_player, Multi_msg_text, Multi_msg_mode, NULL); // echo the message locally multi_msg_display_mission_text(Multi_msg_text, MY_NET_PLAYER_NUM); } } // maybe process a keypress in text messaging mode, return true if the key was processed int multi_msg_text_process(int k) { char str[2]; int ascii; // keep eating keys for a short period of time if ( Multi_msg_eat_stamp.isValid() && !ui_timestamp_elapsed(Multi_msg_eat_stamp) ) { return 1; } // if we're not in text message mode, return 0 if(!Multi_msg_text_enter){ return 0; } switch(k){ // cancel the message case KEY_ESC: multi_msg_text_flush(); break; // send the message case KEY_ENTER: multi_msg_eval_text_msg(); multi_msg_text_flush(); break; // backspace case KEY_BACKSP: if(Multi_msg_text[0] != '\0'){ Multi_msg_text[strlen(Multi_msg_text)-1] = '\0'; } break; // ignore these individual keys case KEY_LSHIFT + KEY_SHIFTED: case KEY_RSHIFT + KEY_SHIFTED: case KEY_LALT + KEY_SHIFTED: case KEY_RALT + KEY_SHIFTED: case KEY_LCTRL + KEY_SHIFTED: case KEY_RCTRL + KEY_SHIFTED: break; // stick other printable characters onto the text default : ascii = key_to_ascii(k); // make sure it's actually a printable character if (ascii == 255) { break; } // if we're not already at the maximum length if(strlen(Multi_msg_text) < MULTI_MSG_MAX_LEN){ str[0] = static_cast(ascii); str[1] = '\0'; strcat_s(Multi_msg_text,str); } break; } return 1; } // return 0 or 1 if there is multi text to be rendered (filling in txt if necessary) int multi_msg_message_text(char *txt) { // if we're not in text message mode, return 0 if(!Multi_msg_text_enter){ return 0; } // put the target of the message at the front of the string switch(Multi_msg_mode){ // messaging all players case MULTI_MSG_ALL: strcpy(txt, XSTR("ALL : ",694)); break; // messaging friendly players case MULTI_MSG_FRIENDLY: strcpy(txt,XSTR("FRIENDLY : ",695)); break; // messaging hostile players case MULTI_MSG_HOSTILE: strcpy(txt,XSTR("HOSTILE : ",696)); break; // messaging targeted ship case MULTI_MSG_TARGET: strcpy(txt,XSTR("TARGET : ",697)); break; default : Int3(); } strcat(txt,Multi_msg_text); strcat(txt,"_"); return 1; } // display ingame,inmission message text void multi_msg_display_mission_text(const char *msg, int player_index) { // play a cue voice sound and text to speech if not from this player if(Net_players[player_index].player_id != MY_NET_PLAYER_NUM) { snd_play(gamesnd_get_game_sound(MULTI_MSG_TEXT_SOUND)); fsspeech_play(FSSPEECH_FROM_MULTI, msg); } if(MULTI_STANDALONE(Net_players[player_index])){ HUD_sourced_printf(HUD_SOURCE_NETPLAYER,"%s %s",XSTR("", 698), msg); } else { HUD_sourced_printf(HUD_SOURCE_NETPLAYER,"%s: %s", Net_players[player_index].m_player->callsign, msg); } } // if the passed net_player's callsign matches the reg expression of the passed expr int multi_msg_matches_expr(net_player *np, const char *expr) { // some error checking if((np == NULL) || (expr == NULL) || (strlen(expr) <= 0)){ return 0; } return stricmp(expr, np->m_player->callsign) ? 0 : 1 ; } // if text input mode is active, clear it void multi_msg_text_flush() { Multi_msg_text_enter = 0; Multi_msg_mode = MULTI_MSG_NONE; Multi_msg_stamp = UI_TIMESTAMP::invalid(); // keep eating keys for a short period of time and unset any used control bits Multi_msg_eat_stamp = ui_timestamp(350); control_config_clear_used_status(); key_flush(); } // ----------------------------------------------------------------------------------- // MULTI MESSAGE COMMAND FUNCTIONS // // process an entered line of text and maybe perform a command on it (return 1 if an action was performed, 0 if not) int multi_msg_check_command(char *str) { int idx; char *prefix = NULL, *predicate = NULL, param[MULTI_MSG_MAX_TEXT_LEN+1]; char temp_str[MULTI_MSG_MAX_TEXT_LEN+1]; // look for a colon if(strstr(str,":") == NULL){ return 0; } // we don't want to modify the original string, which strtok() does strcpy_s(temp_str, str); // try and find a command prefix prefix = strtok(temp_str, ":"); if (prefix == NULL) return 0; // get all the text after the message predicate = strtok(NULL, NOX("\n\0")); if (predicate == NULL) return 0; // store the text as the actual parameter strcpy_s(param, predicate); drop_leading_white_space(param); // go through all existing commands and see what we can do for(idx=0;idx : ",699),source->m_player->callsign); // get the target obj if possible target_obj = NULL; target_obj = multi_get_network_object(target_sig); should_display = 1; // add the command specific text switch(command){ // attack my target case ATTACK_TARGET_ITEM : if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){ sprintf(temp_string,XSTR("Attack %s",700),Ships[target_obj->instance].get_display_name()); strcat_s(hud_string,temp_string); } else { should_display = 0; } break; // disable my target case DISABLE_TARGET_ITEM: if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){ sprintf(temp_string,XSTR("Disable %s",701),Ships[target_obj->instance].get_display_name()); strcat_s(hud_string,temp_string); } else { should_display = 0; } break; // protect my target case PROTECT_TARGET_ITEM: if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){ sprintf(temp_string,XSTR("Protect %s",702),Ships[target_obj->instance].get_display_name()); strcat_s(hud_string,temp_string); } else { should_display = 0; } break; // ignore my target case IGNORE_TARGET_ITEM: if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){ sprintf(temp_string,XSTR("Ignore %s",703),Ships[target_obj->instance].get_display_name()); strcat_s(hud_string,temp_string); } else { should_display = 0; } break; // disarm my target case DISARM_TARGET_ITEM: if((target_obj != NULL) && (target_obj->type == OBJ_SHIP)){ sprintf(temp_string,XSTR("Disarm %s",704),Ships[target_obj->instance].get_display_name()); strcat_s(hud_string,temp_string); } else { should_display = 0; } break; // disable subsystem on my target case DISABLE_SUBSYSTEM_ITEM: if((target_obj != NULL) && (target_obj->type == OBJ_SHIP) && (subsys_type != -1) && (subsys_type != 0)){ sprintf(temp_string,XSTR("Disable subsystem %s on %s",705),Multi_msg_subsys_name[subsys_type],Ships[target_obj->instance].get_display_name()); strcat_s(hud_string,temp_string); } else { should_display = 0; } break; // form on my wing case FORMATION_ITEM: strcat_s(hud_string,XSTR("Form on my wing",706)); break; // cover me case COVER_ME_ITEM: strcat_s(hud_string,XSTR("Cover me",707)); break; // engage enemy case ENGAGE_ENEMY_ITEM: strcat_s(hud_string,XSTR("Engage enemy!",708)); break; default : should_display =0; break; } // print it out if(should_display){ HUD_printf("%s", hud_string); } } // evaluate if the given netplayer exists in the passed wingnum int multi_msg_player_in_wing(int wingnum,net_player *pl) { int idx; // if this guy doesn't have a valid ship, bail if((pl->m_player->objnum == -1) || (Objects[pl->m_player->objnum].type != OBJ_SHIP)){ return 0; } // look through all ships in the wing for(idx=0;idxm_player->objnum].instance){ return 1; } } return 0; } // evaluate if the given netplayer is flying the passed shipnum int multi_msg_player_in_ship(int shipnum,net_player *pl) { // if we found a matching ship if((pl->m_player->objnum != -1) && (Objects[pl->m_player->objnum].type == OBJ_SHIP) && (shipnum == Objects[pl->m_player->objnum].instance)){ return 1; } // not a matching ship return 0; } // send a squadmsg packet to a player void multi_msg_send_squadmsg_packet(net_player *target,net_player *source,int command,ushort net_sig,int subsys_type) { ubyte data[100]; char s_val; int packet_size; Assert(source != NULL); Assert(target != NULL); if((source == NULL) || (target == NULL)){ return; } // build the header BUILD_HEADER(SQUADMSG_PLAYER); // add the command and targeting data ADD_INT(command); // add the id of the guy sending the order ADD_SHORT(source->player_id); // net signature ADD_USHORT(net_sig); // targeted subsytem (or -1 if none) s_val = (char)subsys_type; ADD_DATA(s_val); // send to the player multi_io_send_reliable(target, data, packet_size); } // evaluate if a wing SQUADMATE MESSAGE command should be sent to a player // return 0 if at least one ai ship got the order, 1 if only players int multi_msg_eval_wing_squadmsg(int wingnum,int command,ai_info *aif, int player_num) { int idx; ushort net_sig; int subsys_type; int sent_count; // get the index of the sender if(player_num == -1) player_num = MY_NET_PLAYER_NUM; // get the target information if(aif->target_objnum == -1){ net_sig = 0; } else { net_sig = Objects[aif->target_objnum].net_signature; } subsys_type = -1; if((aif->targeted_subsys == NULL) || (aif->targeted_subsys->system_info == NULL)){ subsys_type = -1; } else { subsys_type = aif->targeted_subsys->system_info->type; } // go through all netplayers and find all matched sent_count = Wings[wingnum].current_count; for(idx=0;idxtarget_objnum == -1){ net_sig = 0; } else { net_sig = Objects[aif->target_objnum].net_signature; } subsys_type = -1; if((aif->targeted_subsys == NULL) || (aif->targeted_subsys->system_info == NULL)){ subsys_type = -1; } else { subsys_type = aif->targeted_subsys->system_info->type; } // go through all netplayers and find all matched for(idx=0;idx