/* * 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 // this is need even when not building debug!! #include "gamesequence/gamesequence.h" #include "network/multi.h" #include "network/multiui.h" #include "freespace.h" #include "network/multimsgs.h" #include "network/multiutil.h" #include "popup/popup.h" #include "network/multi_campaign.h" #include "network/multi_endgame.h" #include "network/stand_gui.h" #include "mission/missiongoals.h" #include "mission/missioncampaign.h" #include "mission/missionparse.h" #include "ship/ship.h" // Needed for Ship_info.size() -MageKing17 #include "weapon/weapon.h" // Likewise, needed for Weapon_info.size() -Goober5000 // ------------------------------------------------------------------------------------ // MULTIPLAYER CAMPAIGN DEFINES/VARS // // packet codes #define MC_CODE_POOL 0 // pool/weapons update #define MC_CODE_DONE 1 // campaign is "done" #define MC_CODE_DEBRIEF 2 // debrief info #define MC_CODE_START 3 // start campaign information // packet code for ingame joining #define MC_JIP_INITIAL_PACKET 1 // initial data #define MC_JIP_GE_STATUS 2 // goal event status for all missions #define MC_JIP_GOAL_NAMES 3 // goal names #define MC_JIP_EVENT_NAMES 4 // event names #define MC_JIP_END_DATA 5 // this is the end of the data #define MC_INGAME_DATA_SLOP 50 // flags indicating the "accept" status of all players involved in a campaign int Multi_campaign_accept_flags[MAX_PLAYERS]; // ------------------------------------------------------------------------------------ // MULTIPLAYER CAMPAIGN FUNCTIONS // // load a new campaign file or notify the standalone if we're not the server void multi_campaign_start(char *filename) { char str[255]; // set the netgame mode Netgame.campaign_mode = MP_CAMPAIGN; // set the campaign filename strcpy_s(Netgame.campaign_name,filename); // add the campaign mode flag Game_mode |= GM_CAMPAIGN_MODE; // if we're the server of the game we should also be loading the campaign up. otherwise, we let the standalone do it if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ // start the campaign, passing 0 so we do _not_ load the savefile. this is only for starting // new campaigns mission_campaign_load(filename); mission_campaign_next_mission(); // setup various filenames and mission names strcpy_s(Netgame.mission_name,Campaign.missions[Campaign.current_mission].name); strcpy_s(Netgame.campaign_name,filename); strcpy_s(Game_current_mission_filename,Netgame.mission_name); // if we're the standalone server, set the mission and campaign names if(Game_mode & GM_STANDALONE_SERVER){ memset(str,0,255); strcpy_s(str,Netgame.mission_name); strcat_s(str," ("); strcat_s(str,Netgame.campaign_name); strcat_s(str,")"); // set the control on the stand_gui std_multi_set_standalone_mission_name(str); } // maybe override the Netgame.respawn setting Netgame.respawn = The_mission.num_respawns; nprintf(("Network","MULTI CAMPAIGN : overriding respawn setting with mission max %d\n",The_mission.num_respawns)); // send a "start campaign" packet multi_campaign_send_start(); } } // client-side start of a campaign void multi_campaign_client_start() { mission_campaign_clear(); // set campaign mode. why not. Game_mode |= GM_CAMPAIGN_MODE; } // move everything and eveyrone into the next mission state void multi_campaign_next_mission() { char str[255]; // flush the important data multi_campaign_flush_data(); // call the campaign over function mission_campaign_mission_over(); // now we should be sequencing through the next stage (mission load, etc) // this will eventually be replaced with the real filename of the next mission if(Campaign.current_mission != -1){ strcpy_s(Game_current_mission_filename, Campaign.missions[Campaign.current_mission].name); strcpy_s(Netgame.mission_name,Game_current_mission_filename); // if we're the standalone server, set the mission and campaign names if(Game_mode & GM_STANDALONE_SERVER){ memset(str,0,255); strcpy_s(str,Netgame.mission_name); strcat_s(str," ("); strcat_s(str,Netgame.campaign_name); strcat_s(str,")"); // set the control on the stand_gui std_multi_set_standalone_mission_name(str); } } } // flush all important data between missions void multi_campaign_flush_data() { // blast the accept flags memset(Multi_campaign_accept_flags,0,sizeof(int) * MAX_PLAYERS); // flush mission stuff multi_flush_mission_stuff(); } // call in the debriefing stage to evaluate what we should be doing in regards to the campaign // if player_status == 0, nothing should be done // == 1, players want to continue to the next mission // == 2, players want to repeat the previous mission void multi_campaign_do_debrief(int player_status) { // the server (standalone or no) if((Net_player->flags & NETINFO_FLAG_AM_MASTER) && (Campaign.current_mission != -1) && player_status){ // if players want to go to the next mission if(player_status == 1){ // move the multiplayer campaign along multi_campaign_next_mission(); // if we're at the end of the campaign if(Campaign.current_mission == -1){ // set the netgame state to be forming and continue Netgame.game_state = NETGAME_STATE_FORMING; send_netgame_update_packet(); if(Game_mode & GM_STANDALONE_SERVER){ gameseq_post_event(GS_EVENT_STANDALONE_MAIN); } else { gameseq_post_event(GS_EVENT_MULTI_HOST_SETUP); } multi_reset_timestamps(); } // if we're still in the campaign else { Netgame.game_state = NETGAME_STATE_MISSION_SYNC; send_netgame_update_packet(); Multi_sync_mode = MULTI_SYNC_PRE_BRIEFING; gameseq_post_event(GS_EVENT_MULTI_MISSION_SYNC); multi_reset_timestamps(); } } // if the players want to replay the current mission else if(player_status == 2){ Netgame.game_state = NETGAME_STATE_MISSION_SYNC; send_netgame_update_packet(); Multi_sync_mode = MULTI_SYNC_PRE_BRIEFING; gameseq_post_event(GS_EVENT_MULTI_MISSION_SYNC); multi_reset_timestamps(); } else { Int3(); } } } // display the done popup void multi_campaign_done_popup() { popup(PF_BODY_BIG | PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("The Campaign Is Complete. Thank You For Playing",643)); multi_quit_game(PROMPT_NONE); } // evaluate post mission goal stuff for the campaign and send all relevant stuff to clients void multi_campaign_eval_debrief() { // evaluate mission stuff (fills in structures, etc). // DKA 12/12/98 already done // mission_campaign_eval_next_mission(); // send the campaign debriefing packet multi_campaign_send_debrief_info(); } // clients should store mission goal/event names in the campaign now void multi_campaign_client_store_goals(int mission_num) { int idx; // copy mission goals into the campaign goals for(idx=0;idxstate = NETPLAYER_STATE_CPOOL_ACK; send_netplayer_update_packet(); break; case MC_CODE_DEBRIEF: GET_DATA(cur_mission); GET_DATA(next_mission); // add the filename GET_STRING(fname); Campaign.missions[cur_mission].name = vm_strdup(fname); // add the # of goals and events GET_DATA(val); Campaign.missions[cur_mission].num_goals = val; Campaign.missions[cur_mission].goals = (mgoal*)vm_malloc(sizeof(mgoal) * val); GET_DATA(val); Campaign.missions[cur_mission].num_events = val; Campaign.missions[cur_mission].events = (mevent*)vm_malloc(sizeof(mevent) * val); // add the goals for(idx=0;idxflags & NETINFO_FLAG_AM_MASTER); multi_io_send_to_all_reliable(data, packet_size); // notification message multi_common_add_text(XSTR("Campaign ship/weapon pool\n",644),1); } // send a "start campaign" packet void multi_campaign_send_start(net_player *pl) { ubyte data[MAX_PACKET_SIZE],val; int idx; int packet_size = 0; // build the header BUILD_HEADER(CAMPAIGN_UPDATE); // add the code val = MC_CODE_START; ADD_DATA(val); // add the # of missions, and their filenames ADD_DATA(Campaign.num_missions); for(idx=0;idx MAX_PACKET_SIZE ) { *ptr = goal_count; multi_io_send_reliable(pl, data, packet_size); BUILD_HEADER(CAMPAIGN_UPDATE_INGAME); packet_type = MC_JIP_GOAL_NAMES; ADD_DATA( packet_type ); ADD_INT(i); ptr = &data[packet_size]; goal_count = 0; ADD_DATA( goal_count ); starting_goal_num = (ubyte)j; ADD_DATA( starting_goal_num ); } } *ptr = goal_count; multi_io_send_reliable(pl, data, packet_size); } // send the goal/event names. for ( i = 0; i < Campaign.num_missions; i++ ) { ubyte event_count, starting_event_num; // first the goal names Assert( Campaign.missions[i].num_events < UCHAR_MAX ); num_events = (ubyte)Campaign.missions[i].num_events; // don't do anything if mission hasn't been completed if ( !Campaign.missions[i].completed ) continue; BUILD_HEADER(CAMPAIGN_UPDATE_INGAME); packet_type = MC_JIP_EVENT_NAMES; ADD_DATA(packet_type); ADD_INT(i); // save a pointer so we can put the number of goals written here. ptr = &data[packet_size]; event_count = 0; ADD_DATA( event_count ); starting_event_num = 0; ADD_DATA( starting_event_num ); for ( j = 0; j < num_events; j++ ) { ADD_STRING( Campaign.missions[i].events[j].name ); event_count++; // if packet will get too big, send it off. if ( (packet_size + MC_INGAME_DATA_SLOP) > MAX_PACKET_SIZE ) { *ptr = event_count; multi_io_send_reliable(pl, data, packet_size); BUILD_HEADER(CAMPAIGN_UPDATE_INGAME); packet_type = MC_JIP_EVENT_NAMES; ADD_DATA( packet_type ); ADD_INT(i); ptr = &data[packet_size]; event_count = 0; ADD_DATA( event_count ); starting_event_num = (ubyte)j; ADD_DATA( starting_event_num ); } } *ptr = event_count; multi_io_send_reliable(pl, data, packet_size); } } // add the stop byte BUILD_HEADER(CAMPAIGN_UPDATE_INGAME); packet_type = MC_JIP_END_DATA; ADD_DATA(packet_type); multi_io_send_reliable(pl, data, packet_size); } void multi_campaign_process_ingame_start( ubyte *data, header *hinfo ) { int offset, mission_num, i; ubyte packet_type, num_goals, num_events, status, starting_num; char fname[255]; offset = HEADER_LENGTH; GET_DATA( packet_type ); switch( packet_type ) { case MC_JIP_INITIAL_PACKET: // clear out the names of the missions mission_campaign_clear(); // should free all data structures which need to be freed // get the number of campaigns and their names. GET_INT(Campaign.num_missions); for( i = 0; i < Campaign.num_missions ; i++) { GET_STRING(fname); Campaign.missions[i].name = vm_strdup(fname); } break; case MC_JIP_GE_STATUS: GET_INT( mission_num ); GET_DATA( num_goals ); // need to malloc out the data Assert( Campaign.missions[mission_num].num_goals == 0 ); Campaign.missions[mission_num].num_goals = num_goals; if ( num_goals > 0 ){ Campaign.missions[mission_num].goals = (mgoal *)vm_malloc( sizeof(mgoal) * num_goals ); } for ( i = 0; i < num_goals; i++ ) { GET_DATA(status); // AL: .goals was a NULL pointer here! I have no idea why. Putting // in a check to avoid the unhandled exception if ( Campaign.missions[mission_num].goals ) { Campaign.missions[mission_num].goals[i].status = status; } } // now the events GET_DATA( num_events ); // need to malloc out the data Assert( Campaign.missions[mission_num].num_events == 0 ); Campaign.missions[mission_num].num_events = num_events; if ( num_events > 0 ){ Campaign.missions[mission_num].events = (mevent *)vm_malloc( sizeof(mevent) * num_events ); } for ( i = 0; i < num_events; i++ ) { GET_DATA(status); Campaign.missions[mission_num].events[i].status = status; } break; case MC_JIP_GOAL_NAMES: GET_INT( mission_num ); GET_DATA( num_goals ); GET_DATA( starting_num ); for ( i = starting_num; i < (starting_num + num_goals); i++ ) { GET_STRING(Campaign.missions[mission_num].goals[i].name); } break; case MC_JIP_EVENT_NAMES: GET_INT( mission_num ); GET_DATA( num_events ); GET_DATA( starting_num ); for ( i = starting_num; i < (starting_num + num_events); i++ ) { GET_STRING(Campaign.missions[mission_num].events[i].name); } break; case MC_JIP_END_DATA: Net_player->state = NETPLAYER_STATE_INGAME_CINFO; send_netplayer_update_packet(); break; } PACKET_SET_SIZE(); }