Files
Goober5000andClaude Opus 4.7 77186c72fc refactor loadout pools for vectorized ship and weapon classes
Convert ship/weapon loadout pools from naive per-class arrays to ordered maps that list loadout entries.  Each entry represents a ship/weapon class and its count.  An absent entry means the class is not in this mission's loadout.  Since these are ordered maps, iteration of the map keys occurs in the same sequence as iteration of `Ship_info` or `Weapon_info`.

Includes several bugfixes:
- `csg_read_loadout` now clears both pools before reading.  Previously the pools were only zeroed in `mission_campaign_init`
- `restore_wss_data`: non-transmitted classes are now absent rather than memset to 0
- a copy-paste bug where the weapon pool entries were bounds-checked against MAX_SHIP_CLASSES
- `wss_maybe_restore_loadout`: the pool write-back now preserves loadout membership instead of writing a count for every class
- `ss_dump_to_list`/`ss_swap_list_slot`: returning a slot ship whose class is not in the pool now makes it available instead of silently losing it.  Unreachable in retail but reachable via script
- off-by-one errors in the Lua `Ship_Pool`/`Weapon_Pool` indexers

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-08-03 01:28:28 -04:00

3015 lines
96 KiB
C++

/*
* 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 "network/multiteamselect.h"
#include "gamesequence/gamesequence.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/alphacolors.h"
#include "globalincs/linklist.h"
#include "graphics/font.h"
#include "io/key.h"
#include "io/mouse.h"
#include "menuui/snazzyui.h"
#include "mission/missionparse.h"
#include "missionui/chatbox.h"
#include "missionui/missionscreencommon.h"
#include "missionui/missionshipchoice.h"
#include "missionui/missionweaponchoice.h"
#include "missionui/missionbrief.h"
#include "network/multi.h"
#include "network/multi_endgame.h"
#include "network/multimsgs.h"
#include "network/multiui.h"
#include "network/multiutil.h"
#include "object/object.h"
#include "parse/parselo.h"
#include "playerman/player.h"
#include "popup/popup.h"
#include "ship/ship.h"
#include "utils/string_utils.h"
#include "weapon/weapon.h"
// ------------------------------------------------------------------------------------------------------
// TEAM SELECT DEFINES/VARS
//
// mission screen common data
extern int Next_screen;
//XSTR:OFF
// bitmap defines
#define MULTI_TS_PALETTE "InterfacePalette"
const char *Multi_ts_bitmap_fname[GR_NUM_RESOLUTIONS] = {
"TeamSelect", // GR_640
"2_TeamSelect" // GR_1024
};
const char *Multi_ts_bitmap_mask_fname[GR_NUM_RESOLUTIONS] = {
"TeamSelect-M", // GR_640
"2_TeamSelect-M" // GR_1024
};
// constants for coordinate lookup
#define MULTI_TS_X_COORD 0
#define MULTI_TS_Y_COORD 1
#define MULTI_TS_W_COORD 2
#define MULTI_TS_H_COORD 3
#define MULTI_TS_NUM_BUTTONS 7
#define MULTI_TS_BRIEFING 0 // go to the briefing
#define MULTI_TS_SHIP_SELECT 1 // this screen
#define MULTI_TS_WEAPON_SELECT 2 // go to the weapon select screen
#define MULTI_TS_SHIPS_UP 3 // scroll the ships list up
#define MULTI_TS_SHIPS_DOWN 4 // scroll the ships list down
#define MULTI_TS_COMMIT 5 // commit
#define MULTI_TS_LOCK 6 // lock (free) ship/weapon select
ui_button_info Multi_ts_buttons[GR_NUM_RESOLUTIONS][MULTI_TS_NUM_BUTTONS] = {
{ // GR_640
ui_button_info("CB_00", 7, 3, 37, 7, 0),
ui_button_info("CB_01", 7, 19, 37, 23, 1),
ui_button_info("CB_02", 7, 35, 37, 39, 2),
ui_button_info("TSB_03", 5, 303, -1, -1, 3),
ui_button_info("TSB_04", 5, 454, -1, -1, 4),
ui_button_info("TSB_09", 571, 425, 572, 413, 9),
ui_button_info("TSB_34", 603, 374, 602, 364, 34)
},
{ // GR_1024
ui_button_info("2_CB_00", 12, 5, 59, 12, 0),
ui_button_info("2_CB_01", 12, 31, 59, 37, 1),
ui_button_info("2_CB_02", 12, 56, 59, 62, 2),
ui_button_info("2_TSB_03", 8, 485, -1, -1, 3),
ui_button_info("2_TSB_04", 8, 727, -1, -1, 4),
ui_button_info("2_TSB_09", 914, 681, 937, 660, 9),
ui_button_info("2_TSB_34", 966, 599, 964, 584, 34)
},
};
// players locked ani graphic
#define MULTI_TS_NUM_LOCKED_BITMAPS 3
const char *Multi_ts_bmap_names[GR_NUM_RESOLUTIONS][3] = {
{ // GR_640
"TSB_340000",
"TSB_340001",
"TSB_340002"
},
{ // GR_1024
"2_TSB_340000",
"2_TSB_340001",
"2_TSB_340002"
}
};
int Multi_ts_locked_bitmaps[MULTI_TS_NUM_LOCKED_BITMAPS];
// snazzy menu regions
#define TSWING_0_SHIP_0 10
#define TSWING_0_SHIP_1 12
#define TSWING_0_SHIP_2 14
#define TSWING_0_SHIP_3 16
#define TSWING_1_SHIP_0 18
#define TSWING_1_SHIP_1 20
#define TSWING_1_SHIP_2 22
#define TSWING_1_SHIP_3 24
#define TSWING_2_SHIP_0 26
#define TSWING_2_SHIP_1 28
#define TSWING_2_SHIP_2 30
#define TSWING_2_SHIP_3 32
#define TSWING_0_NAME_0 11
#define TSWING_0_NAME_1 13
#define TSWING_0_NAME_2 15
#define TSWING_0_NAME_3 17
#define TSWING_1_NAME_0 19
#define TSWING_1_NAME_1 21
#define TSWING_1_NAME_2 23
#define TSWING_1_NAME_3 25
#define TSWING_2_NAME_0 27
#define TSWING_2_NAME_1 29
#define TSWING_2_NAME_2 31
#define TSWING_2_NAME_3 33
#define TSWING_LIST_0 5
#define TSWING_LIST_1 6
#define TSWING_LIST_2 7
#define TSWING_LIST_3 8
#define MULTI_TS_SLOT_LIST 0
#define MULTI_TS_PLAYER_LIST 1
#define MULTI_TS_AVAIL_LIST 2
// interface data
#define MULTI_TS_NUM_SNAZZY_REGIONS 28
int Multi_ts_bitmap;
int Multi_ts_mask;
int Multi_ts_inited = 0;
int Multi_ts_snazzy_regions;
ubyte *Multi_ts_mask_data;
int Multi_ts_mask_w, Multi_ts_mask_h;
std::array<MENU_REGION, MULTI_TS_NUM_SNAZZY_REGIONS> Multi_ts_region;
UI_WINDOW Multi_ts_window;
// ship slot data
#define MULTI_TS_NUM_SHIP_SLOTS_TEAM 4 // # of ship slots in team v team
#define MULTI_TS_FLAG_NONE -2 // never has any ships
#define MULTI_TS_FLAG_EMPTY -1 // currently empty
static int Multi_ts_slot_icon_coords[MULTI_TS_NUM_SHIP_SLOTS][GR_NUM_RESOLUTIONS][2] = { // x,y
{ //
{128,301}, // GR_640
{205,482} // GR_1024
},
{ //
{91,347}, // GR_640
{146,555} // GR_1024
},
{ //
{166,347}, // GR_640
{266,555} // GR_1024
},
{ // alpha
{128,395}, // GR_640
{205,632} // GR_1024
},
{ //
{290,301}, // GR_640
{464,482} // GR_1024
},
{ //
{253,347}, // GR_640
{405,555} // GR_1024
},
{ //
{328,347}, // GR_640
{525,555} // GR_1024
},
{ // beta
{290,395}, // GR_640
{464,632} // GR_1024
},
{ //
{453,301}, // GR_640
{725,482} // GR_1024
},
{ //
{416,347}, // GR_640
{666,555} // GR_1024
},
{ //
{491,347}, // GR_640
{786,555} // GR_1024
},
{ // gamma
{453,395}, // GR_640
{725,632} // GR_1024
}
};
static int Multi_ts_slot_text_coords[MULTI_TS_NUM_SHIP_SLOTS][GR_NUM_RESOLUTIONS][3] = { // x,y,width
{ // alpha
{112,330,181-112}, // GR_640
{187,517,181-112} // GR_1024
},
{ // alpha
{74,377,143-74}, // GR_640
{126,592,143-74} // GR_1024
},
{ // alpha
{149,377,218-149},// GR_640
{248,592,218-149} // GR_1024
},
{ // alpha
{112,424,181-112},// GR_640
{187,667,181-112} // GR_1024
},
{ // beta
{274,330,343-274},// GR_640
{446,517,343-274} // GR_1024
},
{ // beta
{236,377,305-236},// GR_640
{385,592,305-236} // GR_1024
},
{ // beta
{311,377,380-311},// GR_640
{507,592,380-311} // GR_1024
},
{ // beta
{274,424,343-274},// GR_640
{446,667,343-274} // GR_1024
},
{ // gamma
{437,330,506-437},// GR_640
{707,517,506-437} // GR_1024
},
{ // gamma
{399,377,468-399},// GR_640
{646,592,468-399} // GR_1024
},
{ // gamma
{474,377,543-474},// GR_640
{768,592,543-474} // GR_1024
},
{ // gamma
{437,424,506-437},// GR_640
{707,667,506-437} // GR_1024
}
};
// avail ship list data
#define MULTI_TS_AVAIL_MAX_DISPLAY 4
static int Multi_ts_avail_coords[MULTI_TS_AVAIL_MAX_DISPLAY][GR_NUM_RESOLUTIONS][2] = { // x,y coords
{ //
{23,331}, // GR_640
{37,530} // GR_1024
},
{ //
{23,361}, // GR_640
{37,578} // GR_1024
},
{ //
{23,391}, // GR_640
{37,626} // GR_1024
},
{ //
{23,421}, // GR_640
{37,674} // GR_1024
}
};
int Multi_ts_avail_start = 0; // starting index of where we will display the available ships
int Multi_ts_avail_count = 0; // the # of available ship classes
// ship information stuff
#define MULTI_TS_SHIP_INFO_MAX_LINE_LEN 150
#define MULTI_TS_SHIP_INFO_MAX_LINES 10
#define MULTI_TS_SHIP_INFO_MAX_TEXT (MULTI_TS_SHIP_INFO_MAX_LINE_LEN * MULTI_TS_SHIP_INFO_MAX_LINES)
static int Multi_ts_ship_info_coords[GR_NUM_RESOLUTIONS][3] = {
{ // GR_640
33, 150, 387
},
{ // GR_1024
53, 240, 618
}
};
char Multi_ts_ship_info_lines[MULTI_TS_SHIP_INFO_MAX_LINES][MULTI_TS_SHIP_INFO_MAX_LINE_LEN];
char Multi_ts_ship_info_text[MULTI_TS_SHIP_INFO_MAX_TEXT];
int Multi_ts_ship_info_line_count;
// status bar mode
static int Multi_ts_status_coords[GR_NUM_RESOLUTIONS][3] = {
{ // GR_640
95, 467, 426
},
{ // GR_1024
152, 747, 688
}
};
int Multi_ts_status_bar_mode = 0;
// carried icon information
int Multi_ts_carried_flag = 0;
int Multi_ts_clicked_flag = 0;
int Multi_ts_clicked_x,Multi_ts_clicked_y;
int Multi_ts_carried_ship_class;
int Multi_ts_carried_from_type = 0;
int Multi_ts_carried_from_index = 0;
// selected ship types (for informational purposes)
int Multi_ts_select_type = -1;
int Multi_ts_select_index = -1;
int Multi_ts_select_ship_class = -1;
// per-frame mouse hotspot vars
int Multi_ts_hotspot_type = -1;
int Multi_ts_hotspot_index = -1;
// operation types
#define TS_GRAB_FROM_LIST 0
#define TS_SWAP_LIST_SLOT 1
#define TS_SWAP_SLOT_SLOT 2
#define TS_DUMP_TO_LIST 3
#define TS_SWAP_PLAYER_PLAYER 4
#define TS_MOVE_PLAYER 5
// team data
#define MULTI_TS_FLAG_NONE -2 // slot is _always_ empty
#define MULTI_TS_FLAG_EMPTY -1 // flag is temporarily empty
typedef struct ts_team_data {
int multi_ts_objnum[MULTI_TS_NUM_SHIP_SLOTS]; // objnums for all slots in this team
net_player *multi_ts_player[MULTI_TS_NUM_SHIP_SLOTS]; // net players corresponding to the same slots
int multi_ts_flag[MULTI_TS_NUM_SHIP_SLOTS]; // flags indicating the "status" of a slot
int multi_players_locked; // are the players locked into place
} ts_team_data;
ts_team_data Multi_ts_team[MULTI_TS_MAX_TVT_TEAMS]; // data for all teams
// deleted ship objnums
int Multi_ts_deleted_objnums[MULTI_TS_MAX_TVT_TEAMS * MULTI_TS_NUM_SHIP_SLOTS];
int Multi_ts_num_deleted;
//XSTR:ON
// ------------------------------------------------------------------------------------------------------
// TEAM SELECT FORWARD DECLARATIONS
//
// check for button presses
void multi_ts_check_buttons();
// act on a button press
void multi_ts_button_pressed(int n);
// initialize all screen data, etc
void multi_ts_init_graphics();
// blit all of the icons representing all wings
void multi_ts_blit_wings();
// blit all of the player callsigns under the correct ships
void multi_ts_blit_wing_callsigns();
// blit the ships on the avail list
void multi_ts_blit_avail_ships();
// initialize the snazzy menu stuff for dragging ships,players around
void multi_ts_init_snazzy();
// what type of region the index is (0 == ship avail list, 1 == ship slots, 2 == player slot)
int multi_ts_region_type(int region);
// convert the region num to a ship slot index
int multi_ts_slot_index(int region);
// convert the region num to an avail list index
int multi_ts_avail_index(int region);
// convert the region num to a player slot index
int multi_ts_player_index(int region);
// blit the status bar
void multi_ts_blit_status_bar();
// assign the correct players to the correct slots
void multi_ts_init_players();
// assign the correct objnums to the correct slots
void multi_ts_init_objnums();
// assign the correct flags to the correct slots
void multi_ts_init_flags();
// handle an available ship scroll down button press
void multi_ts_avail_scroll_down();
// handle an available ship scroll up button press
void multi_ts_avail_scroll_up();
// handle all mouse events (clicking, dragging, and dropping)
void multi_ts_handle_mouse();
// can the specified player perform the action he is attempting
int multi_ts_can_perform(int from_type,int from_index,int to_type,int to_index,int ship_class,int player_index = -1);
// determine the kind of drag and drop operation this is
int multi_ts_get_dnd_type(int from_type,int from_index,int to_type,int to_index,int player_index = -1);
// swap two player positions
int multi_ts_swap_player_player(int from_index,int to_index,interface_snd_id *sound,int player_index = -1);
// move a player
int multi_ts_move_player(int from_index,int to_index,interface_snd_id *sound,int player_index = -1);
// get the ship class of the current index in the avail list or -1 if none exists
int multi_ts_get_avail_ship_class(int index);
// blit the currently carried icon (if any)
void multi_ts_blit_carried_icon();
// if the (console) player is allowed to grab a player slot at this point
int multi_ts_can_grab_player(int slot_index,int player_index = -1);
// return the bitmap index into the ships icon array (in ship select) which should be displayed for the given slot
int multi_ts_slot_bmap_num(int slot_index);
// blit any active ship information text
void multi_ts_blit_ship_info();
// select the given slot and setup any information, etc
void multi_ts_select_ship();
// is it ok for this player to commit
int multi_ts_ok_to_commit();
// return the bitmap index into the ships icon array (in ship select) which should be displayed for the given slot
int multi_ts_avail_bmap_num(int slot_index);
// set the status bar to reflect the status of wing slots (free or not free). 0 or 1 are valid values for now
void multi_ts_set_status_bar_mode(int m);
// check to see that no illegal ship settings have occurred
void multi_ts_check_errors();
// ------------------------------------------------------------------------------------------------------
// TEAM SELECT FUNCTIONS
//
// initialize the team select screen (always call, even when switching between weapon select, etc)
void multi_ts_init()
{
// if we haven't initialized at all yet, then do it
if(!Multi_ts_inited){
multi_ts_init_graphics();
Multi_ts_inited = 1;
}
// use the common interface palette
multi_common_set_palette();
Net_player->state = NETPLAYER_STATE_SHIP_SELECT;
Current_screen = ON_SHIP_SELECT;
}
// initialize all critical internal data structures
void multi_ts_common_init()
{
int idx;
// reset timestamps here. they seem to get hosed by the loadinh of the mission file
multi_reset_timestamps();
// saying "not allowed to mess with ships"
Multi_ts_status_bar_mode = 0;
// intialize ship info stuff
memset(Multi_ts_ship_info_text,0,MULTI_TS_SHIP_INFO_MAX_TEXT);
memset(Multi_ts_ship_info_lines,0,MULTI_TS_SHIP_INFO_MAX_TEXT);
Multi_ts_ship_info_line_count = 0;
// initialize carried icon information
Multi_ts_carried_flag = 0;
Multi_ts_clicked_flag = 0;
Multi_ts_clicked_x = 0;
Multi_ts_clicked_y = 0;
Multi_ts_carried_ship_class = -1;
Multi_ts_carried_from_type = 0;
Multi_ts_carried_from_index = 0;
// selected slot information (should be default player ship)
if(!MULTI_PERM_OBSERVER(Net_players[MY_NET_PLAYER_NUM])){
Multi_ts_select_type = MULTI_TS_SLOT_LIST;
Multi_ts_select_index = Net_player->p_info.ship_index;
// select this ship and setup his info
Multi_ts_select_ship_class = Wss_slots[Multi_ts_select_index].ship_class;
multi_ts_select_ship();
} else {
Multi_ts_select_type = -1;
Multi_ts_select_index = -1;
// no ship class selected for information purposes
Multi_ts_select_ship_class = -1;
}
// deleted ship information
memset(Multi_ts_deleted_objnums,0,sizeof(int) * MULTI_TS_MAX_TVT_TEAMS * MULTI_TS_NUM_SHIP_SLOTS);
Multi_ts_num_deleted = 0;
// mouse hotspot information
Multi_ts_hotspot_type = -1;
Multi_ts_hotspot_index = -1;
// initialize avail ship list data
Multi_ts_avail_start = 0;
// load the locked button bitmaps bitmaps
for(idx=0;idx<MULTI_TS_NUM_LOCKED_BITMAPS;idx++){
Multi_ts_locked_bitmaps[idx] = -1;
Multi_ts_locked_bitmaps[idx] = bm_load(Multi_ts_bmap_names[gr_screen.res][idx]);
}
// assign the correct players to the correct slots
multi_ts_init_players();
// assign the correct objnums to the correct slots
multi_ts_init_objnums();
// sync the interface as normal
multi_ts_sync_interface();
}
void multi_ts_common_level_init()
{
// blast the team data clean
memset(Multi_ts_team,0,sizeof(ts_team_data) * MULTI_TS_MAX_TVT_TEAMS);
}
// do frame for team select
void multi_ts_do()
{
int k = chatbox_process();
k = Multi_ts_window.process(k);
// process any keypresses
switch(k){
case KEY_ESC :
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
multi_quit_game(PROMPT_ALL);
break;
// cycle to the weapon select screen
case KEY_TAB :
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
Next_screen = ON_WEAPON_SELECT;
gameseq_post_event(GS_EVENT_WEAPON_SELECTION);
break;
case KEY_ENTER|KEY_CTRLED:
// multi_ts_commit_pressed();
Commit_pressed = 1;
break;
}
// check any button presses
multi_ts_check_buttons();
// handle all mouse related events
multi_ts_handle_mouse();
// check for errors
multi_ts_check_errors();
// draw the background, etc
gr_reset_clip();
GR_MAYBE_CLEAR_RES(Multi_ts_bitmap);
if(Multi_ts_bitmap != -1){
gr_set_bitmap(Multi_ts_bitmap);
gr_bitmap(0,0,GR_RESIZE_MENU);
}
Multi_ts_window.draw();
// render all wings
multi_ts_blit_wings();
// blit all callsigns
multi_ts_blit_wing_callsigns();
// blit the ships on the available list
multi_ts_blit_avail_ships();
// force draw the ship select button
Multi_ts_buttons[gr_screen.res][MULTI_TS_SHIP_SELECT].button.draw_forced(2);
// force draw the "locked" button if necessary
if(multi_ts_is_locked()){
Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.draw_forced(2);
} else {
if( ((Netgame.type_flags & NG_TYPE_TEAM) && !(Net_player->flags & NETINFO_FLAG_TEAM_CAPTAIN)) ||
((Netgame.type_flags & NG_TYPE_TEAM) && !(Net_player->flags & NETINFO_FLAG_GAME_HOST)) ){
Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.draw_forced(0);
} else {
Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.draw();
}
}
// blit any active ship information
multi_ts_blit_ship_info();
// blit the status bar
multi_ts_blit_status_bar();
// render the chatbox
chatbox_render();
// render tooltips
Multi_ts_window.draw_tooltip();
// display the status of the voice system
multi_common_voice_display_status();
// blit any carried icons
multi_ts_blit_carried_icon();
// flip the buffer
gr_flip();
// If the commit button was pressed, do the commit button actions. Done at the end of the
// loop since we are a bit special and have to close out in the proper order.
if ( Commit_pressed ) {
multi_ts_commit_pressed();
Commit_pressed = 0;
}
}
// close the team select screen (always call, even when switching between weapon select, etc)
void multi_ts_close()
{
int idx;
if(!Multi_ts_inited){
return;
}
Multi_ts_inited = 0;
// shut down the snazzy menu
snazzy_menu_close();
// unload any bitmaps
if(!bm_unload(Multi_ts_bitmap)){
nprintf(("General","WARNING : could not unload background bitmap %s\n",Multi_ts_bitmap_fname[gr_screen.res]));
}
for(idx=0;idx<MULTI_TS_NUM_LOCKED_BITMAPS;idx++){
if(Multi_ts_locked_bitmaps[idx] != -1){
bm_release(Multi_ts_locked_bitmaps[idx]);
Multi_ts_locked_bitmaps[idx] = -1;
}
}
// destroy the UI_WINDOW
Multi_ts_window.destroy();
}
// is the given slot disabled for the specified player
int multi_ts_disabled_slot(int slot_num, int player_index)
{
net_player *pl;
// get the appropriate net player
if(player_index == -1){
pl = Net_player;
} else {
pl = &Net_players[player_index];
}
// if the player is an observer, its _always_ disabled
if(pl->flags & NETINFO_FLAG_OBSERVER){
return 1;
}
// if the flag for this team isn't set to "free" we can't do anything
if(!Multi_ts_team[pl->p_info.team].multi_players_locked){
return 1;
}
// if the "leaders" only flag is set
if(Netgame.options.flags & MSO_FLAG_SS_LEADERS){
// in a team vs. team situation
if(Netgame.type_flags & NG_TYPE_TEAM){
if(pl->flags & NETINFO_FLAG_TEAM_CAPTAIN){
return 0;
}
}
// in a non team vs. team situation
else {
if(pl->flags & NETINFO_FLAG_GAME_HOST){
return 0;
}
}
} else {
// in a team vs. team situation
if(Netgame.type_flags & NG_TYPE_TEAM){
// if i'm the team captain I can mess with my own ships as well as those of the ai ships on my team
if(pl->flags & NETINFO_FLAG_TEAM_CAPTAIN){
if((Multi_ts_team[pl->p_info.team].multi_ts_player[slot_num] != NULL) && (Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[slot_num]].flags[Object::Object_Flags::Player_ship]) && (slot_num != pl->p_info.ship_index)){
return 1;
}
return 0;
}
}
// in a non team vs. team situation
else {
// if we're the host, we can our own ship and ai ships
if(pl->flags & NETINFO_FLAG_GAME_HOST){
// can't grab player ships
if((Multi_ts_team[pl->p_info.team].multi_ts_player[slot_num] != NULL) && (Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[slot_num]].flags[Object::Object_Flags::Player_ship]) && (slot_num != pl->p_info.ship_index)){
return 1;
}
return 0;
}
}
// if this is our slot, then we can grab it
if(slot_num == pl->p_info.ship_index){
return 0;
}
}
return 1;
}
// is the given slot disabled for the specified player, _and_ it is his ship as well
int multi_ts_disabled_high_slot(int slot_index,int player_index)
{
net_player *pl;
// get the appropriate net player
if(player_index == -1){
pl = Net_player;
} else {
pl = &Net_players[player_index];
}
// if this is disabled for him and its also _his_ slot
if(multi_ts_disabled_slot(slot_index,player_index) && !Multi_ts_team[pl->p_info.team].multi_players_locked && (slot_index == pl->p_info.ship_index)){
return 1;
}
return 0;
}
// resynch all display/interface elements based upon all the ship/weapon pool values
void multi_ts_sync_interface()
{
int idx;
// item 1 - determine how many ship types are available in the ship pool
Multi_ts_avail_count = 0;
for(const auto &[ship_class, count] : *Ss_pool) {
if(count > 0){
Multi_ts_avail_count++;
}
}
// item 2 - make sure our local Multi_ts_slot_flag array is up to date
multi_ts_init_flags();
// item 3 - set/unset any necessary flags in underlying ship select data structures
for(idx=0;idx<MAX_WSS_SLOTS;idx++){
switch(Multi_ts_team[Net_player->p_info.team].multi_ts_flag[idx]){
case MULTI_TS_FLAG_EMPTY :
ss_make_slot_empty(idx);
break;
case MULTI_TS_FLAG_NONE :
break;
default :
ss_make_slot_full(idx);
break;
}
}
// item 4 - reset the locked/unlocked status of all ships in the weapon select screen
ss_recalc_multiplayer_slots();
}
void multi_ts_assign_players_all()
{
int idx,team_index,slot_index,found,player_count,shipnum;
char name_lookup[NAME_LENGTH];
object *objp;
// set all player ship indices to -1
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx])){
Net_players[idx].p_info.ship_index = -1;
}
}
// merge the created object list with the actual object list so we have all available ships
obj_merge_created_list();
// get the # of players currently in the game
player_count = multi_num_players();
// always assign the host to the wing leader of one of the TVT wings
// this is valid for coop games as well because the first starting wing
// and the first tvt wing must have the same name
if(Netgame.type_flags & NG_TYPE_TEAM) {
wing_bash_ship_name(name_lookup, TVT_wing_names[Netgame.host->p_info.team], 1);
} else {
// To account for cases where <Wingname> 1 is not a player ship
for (int i = 0; i < MAX_SHIPS_PER_WING; i++) {
wing_bash_ship_name(name_lookup, TVT_wing_names[0], i + 1);
if (!stricmp(name_lookup, Player_start_shipname))
break;
}
}
shipnum = ship_name_lookup(name_lookup);
// if we couldn't find the ship for the host
if(shipnum == -1){
// Netgame.flags |= NG_FLAG_QUITTING;
multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_NONE, MULTI_END_ERROR_SHIP_ASSIGN);
return;
}
multi_ts_get_team_and_slot(Ships[shipnum].ship_name,&team_index,&slot_index);
multi_assign_player_ship(NET_PLAYER_INDEX(Netgame.host),&Objects[Ships[shipnum].objnum],Ships[shipnum].ship_info_index);
Netgame.host->p_info.ship_index = slot_index;
Assert(Netgame.host->p_info.ship_index >= 0);
Netgame.host->p_info.ship_class = Ships[shipnum].ship_info_index;
Netgame.host->m_player->objnum = Ships[shipnum].objnum;
// for each netplayer, try and find a ship
objp = GET_FIRST(&obj_used_list);
while(objp != END_OF_LIST(&obj_used_list)){
// find a valid player ship - ignoring the ship which was assigned to the host
if((objp->flags[Object::Object_Flags::Player_ship]) && stricmp(Ships[objp->instance].ship_name,name_lookup) != 0){
// determine what team and slot this ship is
multi_ts_get_team_and_slot(Ships[objp->instance].ship_name,&team_index,&slot_index);
Assert((team_index != -1) && (slot_index != -1));
// in a team vs. team situation
if(Netgame.type_flags & NG_TYPE_TEAM){
// find a player on this team who needs a ship
found = 0;
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && !MULTI_OBSERVER(Net_players[idx]) && (Net_players[idx].p_info.ship_index == -1) && (Net_players[idx].p_info.team == team_index)){
found = 1;
break;
}
}
}
// in a non team vs. team situation
else {
// find any player on this who needs a ship
found = 0;
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && !MULTI_OBSERVER(Net_players[idx]) && (Net_players[idx].p_info.ship_index == -1)){
found = 1;
break;
}
}
}
// if we found a player
if(found){
multi_assign_player_ship(idx,objp,Ships[objp->instance].ship_info_index);
Net_players[idx].p_info.ship_index = slot_index;
Assert(Net_players[idx].p_info.ship_index >= 0);
Net_players[idx].p_info.ship_class = Ships[objp->instance].ship_info_index;
Net_players[idx].m_player->objnum = OBJ_INDEX(objp);
// decrement the player count
player_count--;
} else {
objp->flags.remove(Object::Object_Flags::Player_ship);
obj_set_flags( objp, objp->flags + Object::Object_Flags::Could_be_player);
}
// if we've assigned all players, we're done
if(player_count <= 0){
break;
}
}
// move to the next item
objp = GET_NEXT(objp);
}
// go through and change any ships marked as player ships to be COULD_BE_PLAYER
if ( objp != END_OF_LIST(&obj_used_list) ) {
for ( objp = GET_NEXT(objp); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
if ( objp->flags[Object::Object_Flags::Player_ship] ){
objp->flags.remove(Object::Object_Flags::Player_ship);
obj_set_flags( objp, objp->flags + Object::Object_Flags::Could_be_player);
}
}
}
if(Game_mode & GM_STANDALONE_SERVER){
Player_obj = NULL;
//Net_player->m_player->objnum = -1;
}
// check to make sure all players were assigned correctly
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && !MULTI_OBSERVER(Net_players[idx])){
// if this guy never got assigned a player ship, there's a mission problem
if(Net_players[idx].p_info.ship_index == -1){
// Netgame.flags |= NG_FLAG_QUITTING;
multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_NONE, MULTI_END_ERROR_SHIP_ASSIGN);
return;
}
}
}
}
// delete ships which have been removed from the game, tidy things
void multi_ts_create_wings()
{
int idx,s_idx;
// the standalone never went through this screen so he should never call this function!
// the standalone and all other clients will have this equivalent function performed whey they receive
// the post_sync_data_packet!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Assert(!(Game_mode & GM_STANDALONE_SERVER));
// check status of all ships and delete or change ship type as necessary
Multi_ts_num_deleted = 0;
for(idx=0;idx<MULTI_TS_MAX_TVT_TEAMS;idx++){
for(s_idx=0;s_idx<MULTI_TS_NUM_SHIP_SLOTS;s_idx++){
// otherwise if there's a valid ship in this spot
if(Multi_ts_team[idx].multi_ts_flag[s_idx] >= 0){
int objnum;
// set the ship type appropriately
Assert(Wss_slots_teams[idx][s_idx].ship_class >= 0);
objnum = Multi_ts_team[idx].multi_ts_objnum[s_idx];
change_ship_type(Objects[objnum].instance,Wss_slots_teams[idx][s_idx].ship_class);
// set the ship weapons correctly
wl_update_ship_weapons(objnum,&Wss_slots_teams[idx][s_idx]);
// assign ts_index of the ship to point to the proper Wss_slots slot
Ships[Objects[objnum].instance].ts_index = s_idx;
} else if(Multi_ts_team[idx].multi_ts_flag[s_idx] == MULTI_TS_FLAG_EMPTY){
Assert(Multi_ts_team[idx].multi_ts_objnum[s_idx] >= 0);
// mark the object as having been deleted
Multi_ts_deleted_objnums[Multi_ts_num_deleted] = Multi_ts_team[idx].multi_ts_objnum[s_idx];
// delete the ship
ship_add_exited_ship( &Ships[Objects[Multi_ts_deleted_objnums[Multi_ts_num_deleted]].instance], Ship::Exit_Flags::Player_deleted);
obj_delete(Multi_ts_deleted_objnums[Multi_ts_num_deleted]);
ship_wing_cleanup(Objects[Multi_ts_deleted_objnums[Multi_ts_num_deleted]].instance,&Wings[Ships[Objects[Multi_ts_team[idx].multi_ts_objnum[s_idx]].instance].wingnum]);
// increment the # of ships deleted
Multi_ts_num_deleted++;
}
}
}
}
// do any necessary processing for players who have left the game
void multi_ts_handle_player_drop()
{
int idx,s_idx;
// find the player
for(idx=0;idx<MULTI_TS_MAX_TVT_TEAMS;idx++){
for(s_idx=0;s_idx<MULTI_TS_NUM_SHIP_SLOTS;s_idx++){
// if we found him, clear his player slot and set his object back to being OF_COULD_BE_PLAYER
if((Multi_ts_team[idx].multi_ts_player[s_idx] != NULL) && !MULTI_CONNECTED((*Multi_ts_team[idx].multi_ts_player[s_idx]))){
Assert(Multi_ts_team[idx].multi_ts_objnum[s_idx] != -1);
Multi_ts_team[idx].multi_ts_player[s_idx] = NULL;
Objects[Multi_ts_team[idx].multi_ts_objnum[s_idx]].flags.remove(Object::Object_Flags::Player_ship);
obj_set_flags( &Objects[Multi_ts_team[idx].multi_ts_objnum[s_idx]], Objects[Multi_ts_team[idx].multi_ts_objnum[s_idx]].flags + Object::Object_Flags::Could_be_player);
}
}
}
}
// set the status bar to reflect the status of wing slots (free or not free). 0 or 1 are valid values for now
void multi_ts_set_status_bar_mode(int m)
{
Multi_ts_status_bar_mode = m;
}
// the "lock" button has been pressed
void multi_ts_lock_pressed()
{
// do nothing if the button has already been pressed
if(multi_ts_is_locked()){
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
return;
}
if(Netgame.type_flags & NG_TYPE_TEAM){
if (!(Net_player->flags & NETINFO_FLAG_TEAM_CAPTAIN)) {
return;
}
} else {
if (!(Net_player->flags & NETINFO_FLAG_GAME_HOST)) {
return;
}
}
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
// send a final player slot update packet
send_pslot_update_packet(Net_player->p_info.team,TS_CODE_LOCK_TEAM, interface_snd_id());
Multi_ts_team[Net_player->p_info.team].multi_players_locked = 1;
// sync interface stuff
multi_ts_set_status_bar_mode(1);
multi_ts_sync_interface();
ss_recalc_multiplayer_slots();
// disable this button now
Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.disable();
}
// if i'm "locked"
int multi_ts_is_locked()
{
return Multi_ts_team[Net_player->p_info.team].multi_players_locked;
}
// show a popup saying "only host and team captains can modify, etc, etc"
void multi_ts_maybe_host_only_popup()
{
/*
// if this is because the "host modifies" option is set
if((Netgame.options.flags & MSO_FLAG_SS_LEADERS) && !(Net_player->flags & NETINFO_FLAG_GAME_HOST) && !(Net_player->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
multi_ts_host_only_popup();
}
if(Netgame.type == NG_TYPE_TEAM){
popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,"Only team captains may modify ships and weapons in this game");
} else {
popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,"Only the host may modify ships and weapons in this game");
}
*/
}
// ------------------------------------------------------------------------------------------------------
// TEAM SELECT FORWARD DEFINITIONS
//
// check for button presses
void multi_ts_check_buttons()
{
int idx;
for(idx=0;idx<MULTI_TS_NUM_BUTTONS;idx++){
// we only really need to check for one button pressed at a time, so we can break after
// finding one.
if(Multi_ts_buttons[gr_screen.res][idx].button.pressed()){
multi_ts_button_pressed(idx);
break;
}
}
}
// act on a button press
void multi_ts_button_pressed(int n)
{
switch(n){
// back to the briefing screen
case MULTI_TS_BRIEFING :
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
Next_screen = ON_BRIEFING_SELECT;
gameseq_post_event( GS_EVENT_START_BRIEFING );
break;
// already on this screen
case MULTI_TS_SHIP_SELECT:
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
break;
// back to the weapon select screen
case MULTI_TS_WEAPON_SELECT:
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
Next_screen = ON_WEAPON_SELECT;
gameseq_post_event(GS_EVENT_WEAPON_SELECTION);
break;
// scroll the available ships list down
case MULTI_TS_SHIPS_DOWN:
multi_ts_avail_scroll_down();
break;
// scroll the available ships list up
case MULTI_TS_SHIPS_UP:
multi_ts_avail_scroll_up();
break;
// free ship/weapon select
case MULTI_TS_LOCK:
Assert(Game_mode & GM_MULTIPLAYER);
// the "lock" button has been pressed
multi_ts_lock_pressed();
// disable the button if it is now locked
if(multi_ts_is_locked()){
Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.disable();
}
break;
// commit button
case MULTI_TS_COMMIT :
// multi_ts_commit_pressed();
Commit_pressed = 1;
break;
default :
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
break;
}
}
// initialize all screen data, etc
void multi_ts_init_graphics()
{
int idx;
// create the interface window
Multi_ts_window.create(0,0,gr_screen.max_w_unscaled,gr_screen.max_h_unscaled,0);
Multi_ts_window.set_mask_bmap(Multi_ts_bitmap_mask_fname[gr_screen.res]);
// load the background bitmap
Multi_ts_bitmap = mission_ui_background_load(Briefing->ship_select_background[gr_screen.res], Multi_ts_bitmap_fname[gr_screen.res]);
if(Multi_ts_bitmap < 0){
// we failed to load the bitmap - this is very bad
Int3();
}
// create the interface buttons
for(idx=0;idx<MULTI_TS_NUM_BUTTONS;idx++){
// create the object
if((idx == MULTI_TS_SHIPS_UP) || (idx == MULTI_TS_SHIPS_DOWN)){
Multi_ts_buttons[gr_screen.res][idx].button.create(&Multi_ts_window, "", Multi_ts_buttons[gr_screen.res][idx].x, Multi_ts_buttons[gr_screen.res][idx].y, 1, 1, 1, 1);
} else {
Multi_ts_buttons[gr_screen.res][idx].button.create(&Multi_ts_window, "", Multi_ts_buttons[gr_screen.res][idx].x, Multi_ts_buttons[gr_screen.res][idx].y, 1, 1, 0, 1);
}
// set the sound to play when highlighted
Multi_ts_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
// set the ani for the button
Multi_ts_buttons[gr_screen.res][idx].button.set_bmaps(Multi_ts_buttons[gr_screen.res][idx].filename);
// set the hotspot
Multi_ts_buttons[gr_screen.res][idx].button.link_hotspot(Multi_ts_buttons[gr_screen.res][idx].hotspot);
}
// add some text
Multi_ts_window.add_XSTR("Briefing", 765, Multi_ts_buttons[gr_screen.res][MULTI_TS_BRIEFING].xt, Multi_ts_buttons[gr_screen.res][MULTI_TS_BRIEFING].yt, &Multi_ts_buttons[gr_screen.res][MULTI_TS_BRIEFING].button, UI_XSTR_COLOR_GREEN);
Multi_ts_window.add_XSTR("Ship Selection", 1067, Multi_ts_buttons[gr_screen.res][MULTI_TS_SHIP_SELECT].xt, Multi_ts_buttons[gr_screen.res][MULTI_TS_SHIP_SELECT].yt, &Multi_ts_buttons[gr_screen.res][MULTI_TS_SHIP_SELECT].button, UI_XSTR_COLOR_GREEN);
Multi_ts_window.add_XSTR("Weapon Loadout", 1068, Multi_ts_buttons[gr_screen.res][MULTI_TS_WEAPON_SELECT].xt, Multi_ts_buttons[gr_screen.res][MULTI_TS_WEAPON_SELECT].yt, &Multi_ts_buttons[gr_screen.res][MULTI_TS_WEAPON_SELECT].button, UI_XSTR_COLOR_GREEN);
Multi_ts_window.add_XSTR("Commit", 1062, Multi_ts_buttons[gr_screen.res][MULTI_TS_COMMIT].xt, Multi_ts_buttons[gr_screen.res][MULTI_TS_COMMIT].yt, &Multi_ts_buttons[gr_screen.res][MULTI_TS_COMMIT].button, UI_XSTR_COLOR_PINK);
Multi_ts_window.add_XSTR("Lock", 1270, Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].xt, Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].yt, &Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button, UI_XSTR_COLOR_GREEN);
// Multi_ts_window.add_XSTR("Help", 928, Multi_ts_buttons[Current_screen-1][gr_screen.res][COMMON_HELP_BUTTON].xt, Multi_ts_buttons[Current_screen-1][gr_screen.res][COMMON_HELP_BUTTON].yt, &Multi_ts_buttons[Current_screen-1][gr_screen.res][COMMON_HELP_BUTTON].button, UI_XSTR_COLOR_GREEN);
// Multi_ts_window.add_XSTR("Options", 1036, Multi_ts_buttons[Current_screen-1][gr_screen.res][COMMON_OPTIONS_BUTTON].xt, Multi_ts_buttons[Current_screen-1][gr_screen.res][COMMON_OPTIONS_BUTTON].yt, &Multi_ts_buttons[Current_screen-1][gr_screen.res][COMMON_OPTIONS_BUTTON].button, UI_XSTR_COLOR_GREEN);
// make the ship scrolling lists
// if we're not the host of the game (or a tema captain in team vs. team mode), disable the lock button
if (Netgame.type_flags & NG_TYPE_TEAM) {
if(!(Net_player->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.disable();
}
} else {
if(!(Net_player->flags & NETINFO_FLAG_GAME_HOST)){
Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.disable();
}
}
// initialize the snazzy menu stuff (for grabbing ships, names, etc)
multi_ts_init_snazzy();
// create the chatbox (again, should not be necessary at this point)
chatbox_create();
// sync the interface as normal
multi_ts_sync_interface();
}
// blit all of the icons representing all wings
void multi_ts_blit_wings()
{
int idx;
// blit them all blindly for now
for(idx=0;idx<MAX_WSS_SLOTS;idx++){
// if this ship doesn't exist, then continue
if(Multi_ts_team[Net_player->p_info.team].multi_ts_flag[idx] == MULTI_TS_FLAG_NONE){
continue;
}
// otherwise blit the ship icon or the "empty" icon
if(Multi_ts_team[Net_player->p_info.team].multi_ts_flag[idx] == MULTI_TS_FLAG_EMPTY){
ss_blit_ship_icon(Multi_ts_slot_icon_coords[idx][gr_screen.res][MULTI_TS_X_COORD],Multi_ts_slot_icon_coords[idx][gr_screen.res][MULTI_TS_Y_COORD],-1,0);
} else {
ss_blit_ship_icon(Multi_ts_slot_icon_coords[idx][gr_screen.res][MULTI_TS_X_COORD],Multi_ts_slot_icon_coords[idx][gr_screen.res][MULTI_TS_Y_COORD],Wss_slots[idx].ship_class,multi_ts_slot_bmap_num(idx));
// if this is a team vs team game, and the slot is occupised by a team captain, put a c there
if((Netgame.type_flags & NG_TYPE_TEAM) && (Multi_ts_team[Net_player->p_info.team].multi_ts_player[idx] != NULL) && (Multi_ts_team[Net_player->p_info.team].multi_ts_player[idx]->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
gr_set_color_fast(&Color_bright);
gr_string(Multi_ts_slot_icon_coords[idx][gr_screen.res][MULTI_TS_X_COORD] - 5,Multi_ts_slot_icon_coords[idx][gr_screen.res][MULTI_TS_Y_COORD] - 5, XSTR("C",737), GR_RESIZE_MENU); // [[ Team captain ]]
}
}
}
}
bool multi_ts_is_player_in_slot(int slot_idx)
{
Assertion(slot_idx >= 0 && slot_idx < MAX_WSS_SLOTS, "Slot index is out of bounds!");
Assertion(Net_player != nullptr, "Cannot check the slot for the player because the player is null!");
return (Multi_ts_team[Net_player->p_info.team].multi_ts_player[slot_idx] != nullptr);
}
bool multi_ts_is_player_allowed_in_slot(int slot_idx)
{
Assertion(slot_idx >= 0 && slot_idx < MAX_WSS_SLOTS, "Slot index is out of bounds!");
Assertion(Net_player != nullptr, "Cannot check the slot for the player because the player is null!");
p_object * pobj = mission_parse_get_arrival_ship(Ships[Objects[Multi_ts_team[Net_player->p_info.team].multi_ts_objnum[slot_idx]].instance].ship_name);
if ((pobj == NULL) || !(pobj->flags[Mission::Parse_Object_Flags::OF_Player_start])) {
return false;
}
return true;
}
// blit all of the player callsigns under the correct ships
void multi_ts_blit_wing_callsigns()
{
int idx,callsign_w;
char callsign[CALLSIGN_LEN+2];
// blit them all blindly for now
for(idx=0;idx<MAX_WSS_SLOTS;idx++){
// if this ship doesn't exist, then continue
if(Multi_ts_team[Net_player->p_info.team].multi_ts_flag[idx] == MULTI_TS_FLAG_NONE){
continue;
}
// if there is a player in the slot
if (multi_ts_is_player_in_slot(idx)) {
// make sure the string fits
strcpy_s(callsign,Multi_ts_team[Net_player->p_info.team].multi_ts_player[idx]->m_player->callsign);
} else {
// determine if this is a locked AI ship
if (!multi_ts_is_player_allowed_in_slot(idx)) {
strcpy_s(callsign, NOX("<"));
strcat_s(callsign, XSTR("AI",738)); // [[ Artificial Intellegence ]]
strcat_s(callsign, NOX(">"));
} else {
strcpy_s(callsign, XSTR("AI",738)); // [[ Artificial Intellegence ]]
}
}
font::force_fit_string(callsign, CALLSIGN_LEN, Multi_ts_slot_text_coords[idx][gr_screen.res][MULTI_TS_W_COORD]);
// get the final length
gr_get_string_size(&callsign_w, NULL, callsign);
// blit the string
if((Multi_ts_hotspot_type == MULTI_TS_PLAYER_LIST) && (Multi_ts_hotspot_index == idx) && (Multi_ts_team[Net_player->p_info.team].multi_ts_player[idx] != NULL)){
gr_set_color_fast(&Color_text_active_hi);
} else {
gr_set_color_fast(&Color_normal);
}
gr_string(Multi_ts_slot_text_coords[idx][gr_screen.res][MULTI_TS_X_COORD] + ((Multi_ts_slot_text_coords[idx][gr_screen.res][MULTI_TS_W_COORD] - callsign_w)/2),Multi_ts_slot_text_coords[idx][gr_screen.res][MULTI_TS_Y_COORD],callsign,GR_RESIZE_MENU);
}
}
// blit the ships on the avail list
void multi_ts_blit_avail_ships()
{
int display_count,ship_count;
char count[6];
// blit the availability of all ship counts
display_count = 0;
ship_count = 0;
for(const auto &[ship_class, pool_count] : *Ss_pool) {
if(pool_count > 0){
// if our starting display index is after this, then skip it
if(ship_count < Multi_ts_avail_start){
ship_count++;
} else {
// blit the icon
ss_blit_ship_icon(Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_X_COORD],Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_Y_COORD],ship_class,multi_ts_avail_bmap_num(display_count));
// blit the ship count available
sprintf(count,"%d",pool_count);
gr_set_color_fast(&Color_normal);
gr_string(Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_X_COORD] - 20,Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_Y_COORD],count,GR_RESIZE_MENU);
// increment the counts
display_count++;
ship_count++;
}
}
// if we've reached the max amount we can display, then stop
if(display_count >= MULTI_TS_AVAIL_MAX_DISPLAY){
return;
}
}
}
// initialize the snazzy menu stuff for dragging ships,players around
void multi_ts_init_snazzy()
{
// initialize the snazzy menu
snazzy_menu_init();
// blast the data
Multi_ts_snazzy_regions = 0;
Multi_ts_region.fill({});
// get a pointer to the mask bitmap data
Multi_ts_mask_data = Multi_ts_window.get_mask_data(&Multi_ts_mask_w, &Multi_ts_mask_h);
// add the wing slots information
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_0_SHIP_0, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_0_SHIP_1, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_0_SHIP_2, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_0_SHIP_3, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_1_SHIP_0, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_1_SHIP_1, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_1_SHIP_2, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_1_SHIP_3, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_2_SHIP_0, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_2_SHIP_1, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_2_SHIP_2, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_2_SHIP_3, 0);
// add the name slots information
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_0_NAME_0, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_0_NAME_1, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_0_NAME_2, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_0_NAME_3, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_1_NAME_0, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_1_NAME_1, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_1_NAME_2, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_1_NAME_3, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_2_NAME_0, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_2_NAME_1, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_2_NAME_2, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_2_NAME_3, 0);
// add the available ships region
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_LIST_0, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_LIST_1, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_LIST_2, 0);
snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "", TSWING_LIST_3, 0);
}
// what type of region the index is (0 == ship avail list, 1 == ship slots, 2 == player slot)
int multi_ts_region_type(int region)
{
if((region == TSWING_0_SHIP_0) || (region == TSWING_0_SHIP_1) || (region == TSWING_0_SHIP_2) || (region == TSWING_0_SHIP_3) ||
(region == TSWING_1_SHIP_0) || (region == TSWING_1_SHIP_1) || (region == TSWING_1_SHIP_2) || (region == TSWING_1_SHIP_3) ||
(region == TSWING_2_SHIP_0) || (region == TSWING_2_SHIP_1) || (region == TSWING_2_SHIP_2) || (region == TSWING_2_SHIP_3) ){
return MULTI_TS_SLOT_LIST;
}
if((region == TSWING_0_NAME_0) || (region == TSWING_0_NAME_1) || (region == TSWING_0_NAME_2) || (region == TSWING_0_NAME_3) ||
(region == TSWING_1_NAME_0) || (region == TSWING_1_NAME_1) || (region == TSWING_1_NAME_2) || (region == TSWING_1_NAME_3) ||
(region == TSWING_2_NAME_0) || (region == TSWING_2_NAME_1) || (region == TSWING_2_NAME_2) || (region == TSWING_2_NAME_3) ){
return MULTI_TS_PLAYER_LIST;
}
if((region == TSWING_LIST_0) || (region == TSWING_LIST_1) || (region == TSWING_LIST_2) || (region == TSWING_LIST_3)){
return MULTI_TS_AVAIL_LIST;
}
return -1;
}
// convert the region num to a ship slot index
int multi_ts_slot_index(int region)
{
switch(region){
case TSWING_0_SHIP_0:
return 0;
case TSWING_0_SHIP_1:
return 1;
case TSWING_0_SHIP_2:
return 2;
case TSWING_0_SHIP_3:
return 3;
case TSWING_1_SHIP_0:
return 4;
case TSWING_1_SHIP_1:
return 5;
case TSWING_1_SHIP_2:
return 6;
case TSWING_1_SHIP_3:
return 7;
case TSWING_2_SHIP_0:
return 8;
case TSWING_2_SHIP_1:
return 9;
case TSWING_2_SHIP_2:
return 10;
case TSWING_2_SHIP_3:
return 11;
}
return -1;
}
// convert the region num to an avail list index (starting from absolute 0)
int multi_ts_avail_index(int region)
{
switch(region){
case TSWING_LIST_0:
return 0;
case TSWING_LIST_1:
return 1;
case TSWING_LIST_2:
return 2;
case TSWING_LIST_3:
return 3;
}
return -1;
}
// convert the region num to a player slot index
int multi_ts_player_index(int region)
{
switch(region){
case TSWING_0_NAME_0:
return 0;
case TSWING_0_NAME_1:
return 1;
case TSWING_0_NAME_2:
return 2;
case TSWING_0_NAME_3:
return 3;
case TSWING_1_NAME_0:
return 4;
case TSWING_1_NAME_1:
return 5;
case TSWING_1_NAME_2:
return 6;
case TSWING_1_NAME_3:
return 7;
case TSWING_2_NAME_0:
return 8;
case TSWING_2_NAME_1:
return 9;
case TSWING_2_NAME_2:
return 10;
case TSWING_2_NAME_3:
return 11;
}
return -1;
}
// blit any active ship information text
void multi_ts_blit_ship_info()
{
int y_start, line_height;
ship_info *sip;
char str[100];
// if we don't have a valid ship selected, do nothing
if(Multi_ts_select_ship_class == -1){
return;
}
// get the ship class
sip = &Ship_info[Multi_ts_select_ship_class];
// starting line
y_start = Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_Y_COORD];
line_height = gr_get_font_height() + 1;
memset(str,0,100);
// blit the ship class (name)
gr_set_color_fast(&Color_normal);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Class",739), GR_RESIZE_MENU);
if(strlen(sip->get_display_name())){
gr_set_color_fast(&Color_bright);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->get_display_name(), GR_RESIZE_MENU);
}
y_start += line_height;
// blit the ship type
gr_set_color_fast(&Color_normal);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Type",740), GR_RESIZE_MENU);
if(sip->type_str){
gr_set_color_fast(&Color_bright);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->type_str.get(), GR_RESIZE_MENU);
}
y_start += line_height;
// blit the ship length
gr_set_color_fast(&Color_normal);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Length",741), GR_RESIZE_MENU);
if(sip->ship_length){
gr_set_color_fast(&Color_bright);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->ship_length.get(), GR_RESIZE_MENU);
}
y_start += line_height;
// blit the max velocity
gr_set_color_fast(&Color_normal);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Max Velocity",742), GR_RESIZE_MENU);
sprintf(str, XSTR("%d m/s",743),(int)sip->max_vel.xyz.z);
gr_set_color_fast(&Color_bright);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start,str,GR_RESIZE_MENU);
y_start += line_height;
// blit the maneuverability
gr_set_color_fast(&Color_normal);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Maneuverability",744), GR_RESIZE_MENU);
if(sip->maneuverability_str){
gr_set_color_fast(&Color_bright);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->maneuverability_str.get(), GR_RESIZE_MENU);
}
y_start += line_height;
// blit the armor
gr_set_color_fast(&Color_normal);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Armor",745), GR_RESIZE_MENU);
if(sip->armor_str){
gr_set_color_fast(&Color_bright);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->armor_str.get(), GR_RESIZE_MENU);
}
y_start += line_height;
// blit the gun mounts
gr_set_color_fast(&Color_normal);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Gun Mounts",746), GR_RESIZE_MENU);
if(sip->gun_mounts){
gr_set_color_fast(&Color_bright);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->gun_mounts.get(), GR_RESIZE_MENU);
}
y_start += line_height;
// blit the missile banke
gr_set_color_fast(&Color_normal);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Missile Banks",747), GR_RESIZE_MENU);
if(sip->missile_banks){
gr_set_color_fast(&Color_bright);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->missile_banks.get(), GR_RESIZE_MENU);
}
y_start += line_height;
// blit the manufacturer
gr_set_color_fast(&Color_normal);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Manufacturer",748), GR_RESIZE_MENU);
if(sip->manufacturer_str){
gr_set_color_fast(&Color_bright);
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->manufacturer_str.get(), GR_RESIZE_MENU);
}
y_start += line_height;
// blit the _short_ text description
Assert(Multi_ts_ship_info_line_count < 3);
gr_set_color_fast(&Color_normal);
for(int idx=0;idx<Multi_ts_ship_info_line_count;idx++){
gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, Multi_ts_ship_info_lines[idx], GR_RESIZE_MENU);
y_start += line_height;
}
}
// blit the status bar
void multi_ts_blit_status_bar()
{
char text[50];
int text_w;
int blit = 0;
// mode specific text
switch(Multi_ts_status_bar_mode){
case 0 :
strcpy_s(text, XSTR("Ships/Weapons Locked",749));
blit = 1;
break;
case 1 :
strcpy_s(text, XSTR("Ships/Weapons Are Now Free",750));
blit = 1;
break;
}
// if we should be blitting
if(blit){
gr_get_string_size(&text_w,NULL,text);
gr_set_color_fast(&Color_bright_blue);
gr_string(Multi_ts_status_coords[gr_screen.res][MULTI_TS_X_COORD] + ((Multi_ts_status_coords[gr_screen.res][MULTI_TS_W_COORD] - text_w)/2),Multi_ts_status_coords[gr_screen.res][MULTI_TS_Y_COORD],text,GR_RESIZE_MENU);
}
}
// assign the correct players to the correct slots
void multi_ts_init_players()
{
int idx;
// if i'm an observer, i have no ship
if(Net_player->flags & NETINFO_FLAG_OBSERVER){
Net_player->p_info.ship_index = -1;
}
// initialize all players and observer
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx])){
if(MULTI_OBSERVER(Net_players[idx])){
Net_players[idx].p_info.ship_index = -1;
} else {
Multi_ts_team[Net_players[idx].p_info.team].multi_ts_player[Net_players[idx].p_info.ship_index] = &Net_players[idx];
}
}
}
}
// assign the correct objnums to the correct slots
void multi_ts_init_objnums()
{
int idx,s_idx,team_index,slot_index;
// zero out the indices
for(idx=0;idx<MULTI_TS_MAX_TVT_TEAMS;idx++){
for(s_idx=0;s_idx<MULTI_TS_NUM_SHIP_SLOTS;s_idx++){
Multi_ts_team[idx].multi_ts_objnum[s_idx] = -1;
}
}
// set all the objnums
for (auto so: list_range(&Ship_obj_list)) {
auto objp = &Objects[so->objnum];
if (objp->flags[Object::Object_Flags::Should_be_dead])
continue;
// if its a ship, get its slot index (if any)
multi_ts_get_team_and_slot(Ships[objp->instance].ship_name,&team_index,&slot_index);
if((slot_index != -1) && (team_index != -1)){
Multi_ts_team[team_index].multi_ts_objnum[slot_index] = Ships[objp->instance].objnum;
}
}
}
// get the proper team and slot index for the given ship name
void multi_ts_get_team_and_slot(char* ship_name, int* team_index, int* slot_index)
{
// set the return values to default values
*team_index = -1;
*slot_index = -1;
// set up the ship registry pointer
const ship_registry_entry* ship_regp = ship_registry_get(ship_name);
// For a player usable ship, there has to be a parse object, a team and a position within a wing.
Assert(ship_regp != nullptr && ship_regp->has_p_objp());
if (ship_regp == nullptr || !ship_regp->has_p_objp()) {
return;
}
// this should send the original team, in case the team changes via sexp or scripting
*team_index = ship_regp->p_objp()->loadout_team;
// if we're in team vs. team mode
if(Netgame.type_flags & NG_TYPE_TEAM){
// get the slot within the wing, since there's only one wing each in team vs. team.
*slot_index = ship_regp->p_objp()->pos_in_wing;
}
// if we're _not_ in team vs. team mode
else {
// get the wing index first.
int wing_index = ship_regp->p_objp()->wing_status_wing_index;
// only if the wing index is valid do we set a slot index.
if (wing_index >= 0 && wing_index < MAX_STARTING_WINGS) {
*slot_index = wing_index * MULTI_TS_NUM_SHIP_SLOTS_TEAM + ship_regp->p_objp()->pos_in_wing;
}
}
}
// function to return the shipname of the ship in the slot designated by the team and slot
// parameters
void multi_ts_get_shipname( char *ship_name, int team, int slot_index )
{
if ( Netgame.type_flags & NG_TYPE_TEAM ) {
Assert( (team >= 0) && (team < MULTI_TS_MAX_TVT_TEAMS) );
wing_bash_ship_name(ship_name, TVT_wing_names[team], slot_index + 1);
} else {
Assert( team == 0 );
wing_bash_ship_name(ship_name, Starting_wing_names[slot_index / MULTI_TS_NUM_SHIP_SLOTS_TEAM], (slot_index % MULTI_TS_NUM_SHIP_SLOTS_TEAM) + 1);
}
}
// assign the correct flags to the correct slots
void multi_ts_init_flags()
{
int idx,s_idx;
// zero out the flags
for(idx=0;idx<MULTI_TS_MAX_TVT_TEAMS;idx++){
for(s_idx=0;s_idx<MULTI_TS_NUM_SHIP_SLOTS;s_idx++){
Multi_ts_team[idx].multi_ts_flag[s_idx] = MULTI_TS_FLAG_NONE;
}
}
// in a team vs. team situation
if(Netgame.type_flags & NG_TYPE_TEAM){
for(idx=0;idx<MULTI_TS_MAX_TVT_TEAMS;idx++){
for(s_idx=0;s_idx<MULTI_TS_NUM_SHIP_SLOTS_TEAM;s_idx++){
// if the there is an objnum here but no ship class, we know its currently empty
if((Multi_ts_team[idx].multi_ts_objnum[s_idx] != -1) && (Wss_slots_teams[idx][s_idx].ship_class == -1)){
Multi_ts_team[idx].multi_ts_flag[s_idx] = MULTI_TS_FLAG_EMPTY;
} else if((Multi_ts_team[idx].multi_ts_objnum[s_idx] != -1) && (Wss_slots_teams[idx][s_idx].ship_class != -1)){
Multi_ts_team[idx].multi_ts_flag[s_idx] = Wss_slots_teams[idx][s_idx].ship_class;
}
}
}
}
// in a non team vs. team situation
else {
for(idx=0;idx<MULTI_TS_NUM_SHIP_SLOTS;idx++){
// if the there is an objnum here but no ship class, we know its currently empty
if((Multi_ts_team[0].multi_ts_objnum[idx] != -1) && (Wss_slots[idx].ship_class == -1)){
Multi_ts_team[0].multi_ts_flag[idx] = MULTI_TS_FLAG_EMPTY;
} else if((Multi_ts_team[0].multi_ts_objnum[idx] != -1) && (Wss_slots[idx].ship_class != -1)){
Multi_ts_team[0].multi_ts_flag[idx] = Wss_slots[idx].ship_class;
}
}
}
}
// handle an available ship scroll down button press
void multi_ts_avail_scroll_down()
{
if((Multi_ts_avail_count - Multi_ts_avail_start) > MULTI_TS_AVAIL_MAX_DISPLAY){
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
Multi_ts_avail_start++;
} else {
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
}
}
// handle an available ship scroll up button press
void multi_ts_avail_scroll_up()
{
if(Multi_ts_avail_start > 0){
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
Multi_ts_avail_start--;
} else {
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
}
}
// handle all mouse events (clicking, dragging, and dropping)
void multi_ts_handle_mouse()
{
int snazzy_region,snazzy_action;
int region_type,region_index,region_empty;
int mouse_x,mouse_y,ship_class;
// get the mouse coords
mouse_get_pos_unscaled(&mouse_x,&mouse_y);
// do frame for the snazzy menu
snazzy_region = snazzy_menu_do(Multi_ts_mask_data, Multi_ts_mask_w, Multi_ts_mask_h, Multi_ts_snazzy_regions, Multi_ts_region.data(), &snazzy_action, 0);
region_type = -1;
region_index = -1;
region_empty = 1;
ship_class = -1;
if(snazzy_region != -1){
region_type = multi_ts_region_type(snazzy_region);
Assert(region_type != -1);
// determine what type of region the mouse is over and the appropriate index
switch(region_type){
case MULTI_TS_AVAIL_LIST:
region_index = multi_ts_avail_index(snazzy_region);
ship_class = multi_ts_get_avail_ship_class(region_index);
if(ship_class == -1){
region_empty = 1;
} else {
region_empty = (Ss_pool->value_or(ship_class, -1) > 0) ? 0 : 1;
}
break;
case MULTI_TS_SLOT_LIST:
region_index = multi_ts_slot_index(snazzy_region);
if(region_index >= 0 ) {
region_empty = (Multi_ts_team[Net_player->p_info.team].multi_ts_flag[region_index] >= 0) ? 0 : 1;
if(!region_empty){
ship_class = Wss_slots[region_index].ship_class;
}
}
break;
case MULTI_TS_PLAYER_LIST:
region_index = multi_ts_player_index(snazzy_region);
if(region_index >= 0 ) {
region_empty = (Multi_ts_team[Net_player->p_info.team].multi_ts_player[region_index] != NULL) ? 0 : 1;
}
break;
}
}
// maybe play a "highlight" sound
switch(region_type){
case MULTI_TS_PLAYER_LIST:
if((Multi_ts_hotspot_index != region_index) && (region_index >= 0) && (Multi_ts_team[Net_player->p_info.team].multi_ts_player[region_index] != NULL)){
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
}
break;
}
// set the current frame mouse hotspot vars
Multi_ts_hotspot_type = region_type;
Multi_ts_hotspot_index = region_index;
// if we currently have clicked on something and have just released it
if(!Multi_ts_carried_flag && Multi_ts_clicked_flag && !mouse_down(MOUSE_LEFT_BUTTON)){
Multi_ts_clicked_flag = 0;
}
// if we're currently not carrying anything and the user has clicked
if(!Multi_ts_carried_flag && !Multi_ts_clicked_flag && mouse_down(MOUSE_LEFT_BUTTON) && !region_empty){
// set the "clicked" flag
Multi_ts_clicked_flag = 1;
// check to see if he clicked on a ship type and highlight if necessary
switch(region_type){
// selected a ship in the wing slots
case MULTI_TS_SLOT_LIST:
Multi_ts_select_type = MULTI_TS_SLOT_LIST;
Multi_ts_select_index = region_index;
multi_ts_select_ship();
break;
// selected a ship on the avail list
case MULTI_TS_AVAIL_LIST:
Multi_ts_select_type = MULTI_TS_AVAIL_LIST;
Multi_ts_select_index = region_index;
multi_ts_select_ship();
break;
// selected something else - unselect
default :
Multi_ts_select_type = -1;
Multi_ts_select_index = -1;
Multi_ts_select_ship_class = -1;
break;
}
Multi_ts_clicked_x = mouse_x;
Multi_ts_clicked_y = mouse_y;
}
// if we had something clicked and have started dragging it
if(!Multi_ts_carried_flag && Multi_ts_clicked_flag && mouse_down(MOUSE_LEFT_BUTTON) && ((Multi_ts_clicked_x != mouse_x) || (Multi_ts_clicked_y != mouse_y))){
// if this player is an observer, he shouldn't be able to do jack
if(Net_player->flags & NETINFO_FLAG_OBSERVER){
return;
}
// first we check for illegal conditions (any case where he cannot grab what he is attempting to grab)
switch(region_type){
case MULTI_TS_AVAIL_LIST :
// if players are not yet locked, can't grab ships
if(!Multi_ts_team[Net_player->p_info.team].multi_players_locked){
return;
}
if(region_empty){
return;
}
break;
case MULTI_TS_SLOT_LIST:
// if players are not yet locked, can't grab ships
if(!Multi_ts_team[Net_player->p_info.team].multi_players_locked){
return;
}
if(multi_ts_disabled_slot(region_index)){
multi_ts_maybe_host_only_popup();
return;
}
if(region_index >= 0 ) {
if(Multi_ts_team[Net_player->p_info.team].multi_ts_flag[region_index] < 0){
return;
}
}
break;
case MULTI_TS_PLAYER_LIST:
if(!multi_ts_can_grab_player(region_index)){
return;
}
break;
}
Multi_ts_clicked_flag = 0;
Multi_ts_carried_flag = 1;
// set up the carried icon here
Multi_ts_carried_from_type = region_type;
Multi_ts_carried_from_index = region_index;
Multi_ts_carried_ship_class = ship_class;
}
// if we were carrying something but have dropped it
if(Multi_ts_carried_flag && !mouse_down(MOUSE_LEFT_BUTTON)){
Multi_ts_carried_flag = 0;
Multi_ts_clicked_flag = 0;
// if we're not allowed to drop onto this slot
if((region_type == MULTI_TS_SLOT_LIST) && multi_ts_disabled_slot(region_index)){
multi_ts_maybe_host_only_popup();
}
// if we're over some kind of valid region, apply
multi_ts_drop(Multi_ts_carried_from_type,Multi_ts_carried_from_index,region_type,region_index,Multi_ts_carried_ship_class);
}
}
// can the specified player perform the action he is attempting
int multi_ts_can_perform(int from_type,int from_index,int to_type,int to_index,int ship_class,int player_index)
{
net_player *pl;
int op_type;
p_object *pobj;
// get the appropriate player
if(player_index == -1){
pl = Net_player;
} else {
pl = &Net_players[player_index];
}
// get the operation type
op_type = multi_ts_get_dnd_type(from_type,from_index,to_type,to_index,player_index);
// if either of the indices are bogus, then bail
if((from_index == -1) || (to_index == -1)){
return 0;
}
switch(op_type){
case TS_GRAB_FROM_LIST:
// if there are no more of this ship class, its no go
if(Ss_pool_teams[pl->p_info.team].value_or(ship_class, -1) <= 0){
return 0;
}
// if he's not allowed to touch the wing slot
if(multi_ts_disabled_slot(to_index,player_index)){
return 0;
}
// if the slot he's trying to drop it on is "permanently" empty
if(Multi_ts_team[pl->p_info.team].multi_ts_flag[to_index] == MULTI_TS_FLAG_NONE){
return 0;
}
break;
case TS_SWAP_LIST_SLOT:
// if there are no more of this ship class, its no go
if(Ss_pool_teams[pl->p_info.team].value_or(ship_class, -1) <= 0){
return 0;
}
// if he's not allowed to touch the wing slot
if(multi_ts_disabled_slot(to_index,player_index)){
return 0;
}
// if the slot we're trying to move to is invalid, then do nothing
if(Multi_ts_team[pl->p_info.team].multi_ts_flag[to_index] == MULTI_TS_FLAG_NONE){
return 0;
}
break;
case TS_SWAP_SLOT_SLOT:
// if he's not allowed to touch one of the slots, its no go
if(multi_ts_disabled_slot(from_index,player_index) || multi_ts_disabled_slot(to_index,player_index)){
return 0;
}
// if the slot we're taking from is invalid
if(Multi_ts_team[pl->p_info.team].multi_ts_flag[to_index] == MULTI_TS_FLAG_NONE){
return 0;
}
break;
case TS_DUMP_TO_LIST:
// if he's not allowed to be touching the slot to begin with, it no go
if(multi_ts_disabled_slot(from_index,player_index)){
return 0;
}
// if the slot we're trying to move to is invalid, then do nothing
if(Multi_ts_team[pl->p_info.team].multi_ts_flag[to_index] == MULTI_TS_FLAG_NONE){
return 0;
}
break;
case TS_SWAP_PLAYER_PLAYER:
// if his team is already locked, he cannot do this
if(Multi_ts_team[pl->p_info.team].multi_players_locked){
return 0;
}
// if there isn't a player at one of the positions
if((Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] == NULL) || (Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] == NULL)){
return 0;
}
// if this is not a player ship type object
if(Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index] != -1){
pobj = mission_parse_get_arrival_ship(Ships[Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].instance].ship_name);
if((pobj == NULL) || !(pobj->flags[Mission::Parse_Object_Flags::OF_Player_start])){
return 0;
}
}
if(Netgame.type_flags & NG_TYPE_TEAM){
// if he's not the team captain, he cannot do this
if(!(pl->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
return 0;
}
} else {
// if he's not the host, he cannot do this
if(!(pl->flags & NETINFO_FLAG_GAME_HOST)){
return 0;
}
}
break;
case TS_MOVE_PLAYER:
// if his team is already locked, he cannot do this
if(Multi_ts_team[pl->p_info.team].multi_players_locked){
return 0;
}
// if there isn't a player at the _from_
if(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] == NULL){
return 0;
}
// if there is no ship at the _to_ location
if(Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index] < 0){
return 0;
}
// if this is not a player ship type object
if(Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index] != -1){
pobj = mission_parse_get_arrival_ship(Ships[Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].instance].ship_name);
if((pobj == NULL) || !(pobj->flags[Mission::Parse_Object_Flags::OF_Player_start])){
return 0;
}
}
if(Netgame.type_flags & NG_TYPE_TEAM){
// if he's not the team captain, he cannot do this
if(!(pl->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
return 0;
}
} else {
// if he's not the host, he cannot do this
if(!(pl->flags & NETINFO_FLAG_GAME_HOST)){
return 0;
}
}
break;
default :
return 0;
break;
}
return 1;
}
// determine the kind of drag and drop operation this is
int multi_ts_get_dnd_type(int from_type,int /*from_index*/,int to_type,int to_index,int player_index)
{
net_player *pl;
// get the appropriate player
if(player_index == -1){
pl = Net_player;
} else {
pl = &Net_players[player_index];
}
switch(from_type){
// came from the ship avail list
case MULTI_TS_AVAIL_LIST :
// do nothing
if(to_type == MULTI_TS_AVAIL_LIST){
return -1;
}
// if placing it on a slot
if(to_type == MULTI_TS_SLOT_LIST){
if(Wss_slots_teams[pl->p_info.team][to_index].ship_class == -1){
return TS_GRAB_FROM_LIST;
} else {
return TS_SWAP_LIST_SLOT;
}
}
break;
// came from the ship slots
case MULTI_TS_SLOT_LIST :
if(to_type == MULTI_TS_SLOT_LIST){
return TS_SWAP_SLOT_SLOT;
}
if(to_type == MULTI_TS_AVAIL_LIST){
return TS_DUMP_TO_LIST;
}
break;
// came from the player lists
case MULTI_TS_PLAYER_LIST :
if(to_type == MULTI_TS_PLAYER_LIST){
if(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] == NULL){
return TS_MOVE_PLAYER;
} else {
return TS_SWAP_PLAYER_PLAYER;
}
}
break;
}
return -1;
}
void multi_ts_apply(int from_type,int from_index,int to_type,int to_index,int ship_class,int player_index)
{
int size,update;
ubyte wss_data[MAX_PACKET_SIZE];
net_player *pl;
// determine what kind of operation this is
int type = multi_ts_get_dnd_type(from_type,from_index,to_type,to_index,player_index);
// get the proper net player
if(player_index == -1){
pl = Net_player;
} else {
pl = &Net_players[player_index];
}
// set the proper pool pointers
common_set_team_pointers(pl->p_info.team);
interface_snd_id sound;
switch(type){
case TS_SWAP_SLOT_SLOT :
nprintf(("Network","Apply swap slot slot %d %d\n",from_index,to_index));
update = ss_swap_slot_slot(from_index,to_index,&sound);
break;
case TS_DUMP_TO_LIST :
nprintf(("Network","Apply dump to list %d %d\n",from_index,to_index));
update = ss_dump_to_list(from_index,ship_class,&sound);
break;
case TS_SWAP_LIST_SLOT :
nprintf(("Network","Apply swap list slot %d %d\n",from_index,to_index));
update = ss_swap_list_slot(ship_class,to_index,&sound);
break;
case TS_GRAB_FROM_LIST :
nprintf(("Network","Apply grab from list %d %d\n",from_index,to_index));
update = ss_grab_from_list(ship_class,to_index,&sound);
break;
case TS_SWAP_PLAYER_PLAYER :
nprintf(("Network","Apply swap player player %d %d\n",from_index,to_index));
update = multi_ts_swap_player_player(from_index,to_index,&sound,player_index);
break;
case TS_MOVE_PLAYER :
nprintf(("Network","Apply move player %d %d\n",from_index,to_index));
update = multi_ts_move_player(from_index,to_index,&sound,player_index);
break;
default :
update = 0;
break;
}
if(update){
// if we're the host, send an update to all players
if ( MULTIPLAYER_HOST ) {
// send the correct type of update
if(type == TS_SWAP_PLAYER_PLAYER){
} else {
size = store_wss_data(wss_data, MAX_PACKET_SIZE-20, sound, player_index);
send_wss_update_packet(pl->p_info.team,wss_data, size);
// send a player slot update packet as well, so ship class information, etc is kept correct
send_pslot_update_packet(pl->p_info.team,TS_CODE_PLAYER_UPDATE, interface_snd_id());
}
// if the player index == -1, it means the action was done locally - so play a sound
if((player_index == -1) && (sound.isValid())){
gamesnd_play_iface(sound);
}
}
// sync the interface screen up ourselves, if necessary
if(Net_player->p_info.team == pl->p_info.team){
multi_ts_sync_interface();
}
// make sure all flags are set properly for all teams
multi_ts_init_flags();
}
// set the proper pool pointers
common_set_team_pointers(Net_player->p_info.team);
}
// drop a carried icon
void multi_ts_drop(int from_type,int from_index,int to_type,int to_index,int ship_class,int player_index)
{
// if I'm the host, apply immediately
if(Net_player->flags & NETINFO_FLAG_GAME_HOST){
// if this is a legal operation
if(multi_ts_can_perform(from_type,from_index,to_type,to_index,ship_class,player_index)){
multi_ts_apply(from_type,from_index,to_type,to_index,ship_class,player_index);
} else {
nprintf(("Network","Could not apply operation!\n"));
}
}
// otherwise send a request to the host
else {
send_wss_request_packet(Net_player->player_id, from_type, from_index, to_type, to_index, -1, ship_class, WSS_SHIP_SELECT);
}
}
// swap two player positions
int multi_ts_swap_player_player(int from_index,int to_index,interface_snd_id *sound,int player_index)
{
net_player *pl,*temp;
// get the proper player pointer
if(player_index == -1){
pl = Net_player;
} else {
pl = &Net_players[player_index];
}
// swap the players
temp = Multi_ts_team[pl->p_info.team].multi_ts_player[to_index];
Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] = Multi_ts_team[pl->p_info.team].multi_ts_player[from_index];
Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] = temp;
// update netplayer information if necessary
if(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] != NULL){
Multi_ts_team[pl->p_info.team].multi_ts_player[from_index]->p_info.ship_index = from_index;
Multi_ts_team[pl->p_info.team].multi_ts_player[from_index]->p_info.ship_class = Wss_slots_teams[pl->p_info.team][from_index].ship_class;
multi_assign_player_ship(NET_PLAYER_INDEX(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index]),&Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]],Wss_slots_teams[pl->p_info.team][from_index].ship_class);
}
if(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] != NULL){
Multi_ts_team[pl->p_info.team].multi_ts_player[to_index]->p_info.ship_index = to_index;
Multi_ts_team[pl->p_info.team].multi_ts_player[to_index]->p_info.ship_class = Wss_slots_teams[pl->p_info.team][to_index].ship_class;
multi_assign_player_ship(NET_PLAYER_INDEX(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index]),&Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]],Wss_slots_teams[pl->p_info.team][to_index].ship_class);
}
// update ship flags
Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].flags.remove(Object::Object_Flags::Could_be_player);
Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].flags.remove(Object::Object_Flags::Player_ship);
if(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] != NULL){
Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].flags.set(Object::Object_Flags::Player_ship);
}
Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]].flags.remove(Object::Object_Flags::Could_be_player);
Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]].flags.remove(Object::Object_Flags::Player_ship);
if(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] != NULL){
Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]].flags.set(Object::Object_Flags::Player_ship);
}
// recalcalate which slots are locked/unlocked, etc
ss_recalc_multiplayer_slots();
// send an update packet to all players
if(Net_player->flags & NETINFO_FLAG_GAME_HOST){
send_pslot_update_packet(pl->p_info.team,TS_CODE_PLAYER_UPDATE,InterfaceSounds::ICON_DROP_ON_WING);
gamesnd_play_iface(InterfaceSounds::ICON_DROP_ON_WING);
}
*sound = InterfaceSounds::ICON_DROP;
return 1;
}
// move a player
int multi_ts_move_player(int from_index,int to_index,interface_snd_id *sound,int player_index)
{
net_player *pl;
// get the proper player pointer
if(player_index == -1){
pl = Net_player;
} else {
pl = &Net_players[player_index];
}
// swap the players
Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] = Multi_ts_team[pl->p_info.team].multi_ts_player[from_index];
Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] = NULL;
// update netplayer information if necessary
if(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] != NULL){
Multi_ts_team[pl->p_info.team].multi_ts_player[from_index]->p_info.ship_index = from_index;
Multi_ts_team[pl->p_info.team].multi_ts_player[from_index]->p_info.ship_class = Wss_slots_teams[pl->p_info.team][from_index].ship_class;
multi_assign_player_ship(NET_PLAYER_INDEX(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index]),&Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]],Wss_slots_teams[pl->p_info.team][from_index].ship_class);
}
if(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] != NULL){
Multi_ts_team[pl->p_info.team].multi_ts_player[to_index]->p_info.ship_index = to_index;
Multi_ts_team[pl->p_info.team].multi_ts_player[to_index]->p_info.ship_class = Wss_slots_teams[pl->p_info.team][to_index].ship_class;
multi_assign_player_ship(NET_PLAYER_INDEX(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index]),&Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]],Wss_slots_teams[pl->p_info.team][to_index].ship_class);
}
// update ship flags
Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].flags.remove(Object::Object_Flags::Could_be_player);
Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].flags.remove(Object::Object_Flags::Player_ship);
if(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] != NULL){
Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].flags.set(Object::Object_Flags::Player_ship);
}
Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]].flags.remove(Object::Object_Flags::Could_be_player);
Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]].flags.remove(Object::Object_Flags::Player_ship);
if(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] != NULL){
Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]].flags.set(Object::Object_Flags::Player_ship);
}
// recalcalate which slots are locked/unlocked, etc
ss_recalc_multiplayer_slots();
// send an update packet to all players
if(Net_player->flags & NETINFO_FLAG_GAME_HOST){
send_pslot_update_packet(pl->p_info.team,TS_CODE_PLAYER_UPDATE,InterfaceSounds::ICON_DROP_ON_WING);
gamesnd_play_iface(InterfaceSounds::ICON_DROP_ON_WING);
}
*sound = InterfaceSounds::ICON_DROP;
return 1;
}
// get the ship class of the current index in the avail list or -1 if none exists
int multi_ts_get_avail_ship_class(int index)
{
int ship_count = index + Multi_ts_avail_start;
// find the Nth class with ships still available (the map iterates in ascending class
// order, which is the same order the avail list is rendered in)
for(const auto &[ship_class, count] : *Ss_pool){
if(count > 0){
if(ship_count == 0){
return ship_class;
}
ship_count--;
}
}
return -1;
}
// blit the currently carried icon (if any)
void multi_ts_blit_carried_icon()
{
int x,y;
int offset_x,offset_y,callsign_w;
char callsign[CALLSIGN_LEN+2];
// if we're not carrying anything, then return
if(!Multi_ts_carried_flag){
return;
}
// get the mouse position
mouse_get_pos_unscaled(&x,&y);
// if we're carrying an icon of some kind
switch(Multi_ts_carried_from_type){
case MULTI_TS_SLOT_LIST:
offset_x = Multi_ts_slot_icon_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_X_COORD] - Multi_ts_clicked_x;
offset_y = Multi_ts_slot_icon_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_Y_COORD] - Multi_ts_clicked_y;
// blit the icon
ss_blit_ship_icon(x + offset_x,y + offset_y,Multi_ts_carried_ship_class,0);
break;
case MULTI_TS_AVAIL_LIST:
offset_x = Multi_ts_avail_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_X_COORD] - Multi_ts_clicked_x;
offset_y = Multi_ts_avail_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_Y_COORD] - Multi_ts_clicked_y;
// blit the icon
ss_blit_ship_icon(x + offset_x,y + offset_y,Multi_ts_carried_ship_class,0);
break;
case MULTI_TS_PLAYER_LIST:
// get the final length of the string so we can calculate a valid offset
strcpy_s(callsign,Multi_ts_team[Net_player->p_info.team].multi_ts_player[Multi_ts_carried_from_index]->m_player->callsign);
font::force_fit_string(callsign,CALLSIGN_LEN,Multi_ts_slot_text_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_W_COORD]);
gr_get_string_size(&callsign_w,NULL,callsign);
// calculate the offsets
offset_x = (Multi_ts_slot_text_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_X_COORD] - Multi_ts_clicked_x) + ((Multi_ts_slot_text_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_W_COORD] - callsign_w)/2);
offset_y = Multi_ts_slot_text_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_Y_COORD] - Multi_ts_clicked_y;
gr_set_color_fast(&Color_normal);
gr_string(x + offset_x,y + offset_y,Multi_ts_team[Net_player->p_info.team].multi_ts_player[Multi_ts_carried_from_index]->m_player->callsign,GR_RESIZE_MENU);
break;
default :
break;
}
}
// if the (console) player is allowed to grab a player slot at this point
int multi_ts_can_grab_player(int slot_index, int player_index)
{
net_player *pl;
// get a pointe rto the proper net player
if(player_index == -1){
pl = Net_player;
} else {
pl = &Net_players[player_index];
}
// if the players are locked in any case, he annot grab it
if(Multi_ts_team[pl->p_info.team].multi_players_locked){
return 0;
}
if(Netgame.type_flags & NG_TYPE_TEAM){
// if he's not the team captain, he cannot do this
if(!(pl->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
return 0;
}
} else {
// if he's not the host, he cannot do this
if(!(pl->flags & NETINFO_FLAG_GAME_HOST)){
return 0;
}
}
// if the slot is empty
if(Multi_ts_team[pl->p_info.team].multi_ts_player[slot_index] == NULL){
return 0;
}
return 1;
}
// get the team # of the given ship
int multi_ts_get_team(char *ship_name)
{
int idx;//,s_idx;
// lookup through all team ship names
Assert(MAX_TVT_WINGS == MULTI_TS_MAX_TVT_TEAMS);
for(idx=0;idx<MAX_TVT_WINGS;idx++)
{
if (!strnicmp(ship_name, TVT_wing_names[idx], strlen(TVT_wing_names[idx])))
return idx;
}
// always on team 0 if not found otherwise
return 0;
}
// return the bitmap index into the ships icon array (in ship select) which should be displayed for the given slot
int multi_ts_avail_bmap_num(int slot_index)
{
// if this slot has been highlighted for informational purposes
if((Multi_ts_select_type == MULTI_TS_AVAIL_LIST) && (Multi_ts_select_index == slot_index)){
return ICON_FRAME_SELECTED;
}
// if its otherwise being lit by the mouse
if((Multi_ts_hotspot_type == MULTI_TS_AVAIL_LIST) && (Multi_ts_hotspot_index == slot_index)){
return ICON_FRAME_HOT;
}
return ICON_FRAME_NORMAL;
}
// return the bitmap index into the ships icon array (in ship select) which should be displayed for the given slot
int multi_ts_slot_bmap_num(int slot_index)
{
// special case - slot is disabled, its my ship and the host hasn't locked the ships yet
if(multi_ts_disabled_high_slot(slot_index)){
return ICON_FRAME_DISABLED_HIGH;
}
// if this slot is disabled for us, then show it as such
if(multi_ts_disabled_slot(slot_index)){
return ICON_FRAME_DISABLED;
}
// if this slot has been highlighted for informational purposes
if((Multi_ts_select_type == MULTI_TS_SLOT_LIST) && (Multi_ts_select_index == slot_index)){
return ICON_FRAME_SELECTED;
}
// if this is our ship, then highlight it as so
if(Net_player->p_info.ship_index == slot_index){
return ICON_FRAME_PLAYER;
}
// if its otherwise being lit by the mouse
if((Multi_ts_hotspot_type == MULTI_TS_SLOT_LIST) && (Multi_ts_hotspot_index == slot_index)){
return ICON_FRAME_HOT;
}
// normal unhighlighted frame
return ICON_FRAME_NORMAL;
}
// select the given slot and setup any information, etc
void multi_ts_select_ship()
{
int n_lines;
int n_chars[MAX_BRIEF_LINES];
const char *p_str[MAX_BRIEF_LINES];
char *token;
// blast all current text
memset(Multi_ts_ship_info_lines,0,MULTI_TS_SHIP_INFO_MAX_TEXT);
memset(Multi_ts_ship_info_text,0,MULTI_TS_SHIP_INFO_MAX_TEXT);
// get the selected ship class
Assert(Multi_ts_select_index >= 0);
Multi_ts_select_ship_class = -1;
switch(Multi_ts_select_type){
case MULTI_TS_SLOT_LIST:
Multi_ts_select_ship_class = Wss_slots[Multi_ts_select_index].ship_class;
break;
case MULTI_TS_AVAIL_LIST:
Multi_ts_select_ship_class = multi_ts_get_avail_ship_class(Multi_ts_select_index);
// if he has selected an empty slot, don't do anything
if(Multi_ts_select_ship_class < 0){
return;
}
break;
default :
// should always have one of the 2 above types selected
Int3();
break;
}
// split the text info up
Assert(Multi_ts_select_ship_class >= 0);
if (Ship_info[Multi_ts_select_ship_class].desc)
{
// strip out newlines
auto ship_desc = util::unique_copy(Ship_info[Multi_ts_select_ship_class].desc.get(), true);
token = strtok(ship_desc.get(), "\n");
if(token != NULL){
strcpy_s(Multi_ts_ship_info_text,token);
while(token != NULL){
token = strtok(NULL,"\n");
if(token != NULL){
strcat_s(Multi_ts_ship_info_text," ");
strcat_s(Multi_ts_ship_info_text,token);
}
}
}
if(Multi_ts_ship_info_text[0] != '\0'){
// split the string into multiple lines
n_lines = split_str(Multi_ts_ship_info_text, Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_W_COORD], n_chars, p_str, MULTI_TS_SHIP_INFO_MAX_LINES, MULTI_TS_SHIP_INFO_MAX_LINE_LEN,0);
// copy the split up lines into the text lines array
for (int idx = 0;idx<n_lines;idx++ ) {
Assert(n_chars[idx] < MULTI_TS_SHIP_INFO_MAX_LINE_LEN);
strncpy(Multi_ts_ship_info_lines[idx], p_str[idx], n_chars[idx]);
Multi_ts_ship_info_lines[idx][n_chars[idx]] = 0;
drop_leading_white_space(Multi_ts_ship_info_lines[idx]);
}
// get the line count
Multi_ts_ship_info_line_count = n_lines;
} else {
// set the line count to
Multi_ts_ship_info_line_count = 0;
}
}
}
// handle all details when the commit button is pressed (including possibly reporting errors/popups)
commit_pressed_status multi_ts_commit_pressed()
{
// if my team's slots are still not "locked", we cannot commit unless we're the only player in the game
if(!Multi_ts_team[Net_player->p_info.team].multi_players_locked){
extern int red_alert_mission();
// skip this for red-alert missions too since nothing is selectable
if ( !red_alert_mission() && (multi_num_players() != 1) ) {
popup(PF_USE_AFFIRMATIVE_ICON | PF_BODY_BIG,1,POPUP_OK, XSTR("Players have not yet been assigned to their ships",751));
return commit_pressed_status::MULTI_PLAYERS_NO_SHIPS;
} else {
Multi_ts_team[Net_player->p_info.team].multi_players_locked = 1;
}
}
commit_pressed_status rc = commit_pressed_status::GENERAL_FAIL;
// check to see if its not ok for this player to commit
switch(multi_ts_ok_to_commit()){
// yes, it _is_ ok to commit
case 0:
rc = commit_pressed();
break;
// player has not assigned all necessary ships
case 1:
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
rc = commit_pressed_status::MULTI_NOT_ALL_ASSIGNED;
popup(PF_USE_AFFIRMATIVE_ICON | PF_BODY_BIG,1,POPUP_OK, XSTR("You have not yet assigned all necessary ships",752));
break;
// there are ships without primary weapons
case 2:
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
rc = commit_pressed_status::MULTI_NO_PRIMARY;
popup(PF_USE_AFFIRMATIVE_ICON | PF_BODY_BIG,1,POPUP_OK, XSTR("There are ships without primary weapons!",753));
break;
// there are ships without secondary weapons
case 3:
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
rc = commit_pressed_status::MULTI_NO_SECONDARY;
popup(PF_USE_AFFIRMATIVE_ICON | PF_BODY_BIG,1,POPUP_OK, XSTR("There are ships without secondary weapons!",754));
break;
}
return rc;
}
// is it ok for this player to commit
int multi_ts_ok_to_commit()
{
int idx,s_idx;
int primary_ok,secondary_ok;
// if this player is an observer, he can always commit
if(Net_player->flags & NETINFO_FLAG_OBSERVER){
return 0;
}
for(idx=0;idx<MULTI_TS_NUM_SHIP_SLOTS;idx++){
// if this is a player slot this player can modify and it is empty, then he cannot continue
// implies there is never an object in this slot
if((Multi_ts_team[Net_player->p_info.team].multi_ts_objnum[idx] != -1) &&
// implies player can't touch this slot anyway
!multi_ts_disabled_slot(idx) &&
// implies that there should be a player ship here but there isn't
((Multi_ts_team[Net_player->p_info.team].multi_ts_player[idx] != NULL) && (Multi_ts_team[Net_player->p_info.team].multi_ts_flag[idx] == MULTI_TS_FLAG_EMPTY)) ){
return 1;
}
// if the ship in this slot has a ship which can be a player but has 0 primary or secondary weapons, then he cannot continue
if( (Multi_ts_team[Net_player->p_info.team].multi_ts_objnum[idx] != -1) &&
((Objects[Multi_ts_team[Net_player->p_info.team].multi_ts_objnum[idx]].flags[Object::Object_Flags::Could_be_player]) ||
(Objects[Multi_ts_team[Net_player->p_info.team].multi_ts_objnum[idx]].flags[Object::Object_Flags::Player_ship])) &&
!multi_ts_disabled_slot(idx)){
primary_ok = 0;
secondary_ok = 0;
// go through all weapons in the list
for(s_idx=0;s_idx<MAX_SHIP_WEAPONS;s_idx++){
// if this slot has a weapon with a greater than 0 count, check
if((Wss_slots_teams[Net_player->p_info.team][idx].wep[s_idx] >= 0) && (Wss_slots_teams[Net_player->p_info.team][idx].wep_count[s_idx] > 0)){
switch(Weapon_info[Wss_slots_teams[Net_player->p_info.team][idx].wep[s_idx]].subtype){
case WP_LASER:
primary_ok = 1;
break;
case WP_MISSILE:
secondary_ok = 1;
break;
default :
Int3();
}
}
// if we've got both primary and secondary weapons
if(primary_ok && secondary_ok){
break;
}
}
// if the ship doesn't have primary weapons
if (!primary_ok && !(The_mission.ai_profile->flags[AI::Profile_Flags::Multi_allow_empty_secondaries])) {
return 2;
}
// if the ship doesn't have secondary weapons
if (!secondary_ok && !(The_mission.ai_profile->flags[AI::Profile_Flags::Multi_allow_empty_secondaries])) {
return 3;
}
}
}
return 0;
}
// check to see that no illegal ship settings have occurred
void multi_ts_check_errors()
{
/*
int idx;
ship *shipp;
for(idx=0;idx<MULTI_TS_NUM_SHIP_SLOTS;idx++){
if(Multi_ts_team[0].multi_ts_objnum[idx] == -1){
continue;
}
shipp = &Ships[Objects[Multi_ts_team[0].multi_ts_objnum[idx]].instance];
Assert((shipp->weapons.current_primary_bank != -1) && (shipp->weapons.current_secondary_bank != -1));
}
*/
}
// ------------------------------------------------------------------------------------------------------
// TEAM SELECT PACKET HANDLERS
//
// send a player slot position update
void send_pslot_update_packet(int team,int code, interface_snd_id sound)
{
ubyte data[MAX_PACKET_SIZE],stop,val;
short s_sound;
int idx;
int packet_size = 0;
int i_tmp;
// build the header and add the data
BUILD_HEADER(SLOT_UPDATE);
// add the opcode
val = (ubyte)code;
ADD_DATA(val);
// add the team
val = (ubyte)team;
ADD_DATA(val);
// add the sound to play
s_sound = (short)sound.value();
ADD_SHORT(s_sound);
// add data based upon the packet code
switch(code){
case TS_CODE_LOCK_TEAM:
// don't have to do anything
break;
case TS_CODE_PLAYER_UPDATE:
// only the host should ever be doing this
Assert(Net_player->flags & NETINFO_FLAG_GAME_HOST);
// add individual slot data
for(idx=0;idx<MAX_WSS_SLOTS;idx++){
if(Multi_ts_team[team].multi_ts_flag[idx] != MULTI_TS_FLAG_NONE){
// add a stop byte
stop = 0x0;
ADD_DATA(stop);
// add the slot #
val = (ubyte)idx;
ADD_DATA(val);
// add the ship class
ADD_SHORT(static_cast<short>(Wss_slots_teams[team][idx].ship_class));
// add the objnum we're working with
i_tmp = Multi_ts_team[team].multi_ts_objnum[idx];
ADD_INT(i_tmp);
// add a byte indicating if a player is here or not
if(Multi_ts_team[team].multi_ts_player[idx] == NULL){
val = 0;
} else {
val = 1;
}
ADD_DATA(val);
// if there's a player, add his address
if(val){
ADD_SHORT(Multi_ts_team[team].multi_ts_player[idx]->player_id);
// should also update his p_info settings locally
Multi_ts_team[team].multi_ts_player[idx]->p_info.ship_class = Wss_slots_teams[team][idx].ship_class;
Multi_ts_team[team].multi_ts_player[idx]->p_info.ship_index = idx;
}
// add a byte indicating what object flag should be set (0 == ~(OF_COULD_BE_PLAYER | OF_PLAYER_SHIP), 1 == player ship, 2 == could be player ship)
if(Objects[Multi_ts_team[team].multi_ts_objnum[idx]].flags[Object::Object_Flags::Could_be_player]){
val = 2;
} else if(Objects[Multi_ts_team[team].multi_ts_objnum[idx]].flags[Object::Object_Flags::Player_ship]){
val = 1;
} else {
val = 0;
}
ADD_DATA(val);
}
}
// add a final stop byte
val = 0xff;
ADD_DATA(val);
break;
default :
Int3();
break;
}
// send the packet to the standalone
if(Net_player->flags & NETINFO_FLAG_AM_MASTER) {
multi_io_send_to_all_reliable(data, packet_size);
} else {
multi_io_send_reliable(Net_player, data, packet_size);
}
}
// process a player slot position update
void process_pslot_update_packet(ubyte *data, header *hinfo)
{
int offset = HEADER_LENGTH;
int my_index;
int player_index,idx,team,code,objnum;
short sound_id;
short player_id;
ubyte stop, val, slot_num;
short ship_class;
my_index = Net_player->p_info.ship_index;
// if we're the standalone, then we should be routing this data to all the other clients
player_index = -1;
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
// fill in the address information of where this came from
player_index = find_player_index(hinfo->id);
Assert(player_index != -1);
}
// get the opcode
GET_DATA(val);
code = (int)val;
// get the team
GET_DATA(val);
team = (int)val;
// get the sound to play
GET_SHORT(sound_id);
auto sound = interface_snd_id(sound_id);
// process the different opcodes
switch(code){
case TS_CODE_LOCK_TEAM:
// lock the team
Multi_ts_team[team].multi_players_locked = 1;
// if this was my team, sync stuff up
if((team == Net_player->p_info.team) && !(Game_mode & GM_STANDALONE_SERVER)){
multi_ts_set_status_bar_mode(1);
multi_ts_sync_interface();
ss_recalc_multiplayer_slots();
}
// if this is the standalone server, we need to re-route the packet here and there
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
// in team vs team mode, only a team captain should ever be sending this
if(Netgame.type_flags & NG_TYPE_TEAM){
Assert(Net_players[player_index].flags & NETINFO_FLAG_TEAM_CAPTAIN);
}
// in any other mode, it better be coming from the game host
else {
Assert(Net_players[player_index].flags & NETINFO_FLAG_GAME_HOST);
}
// re-route to all other players
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && (&Net_players[idx] != Net_player) && (&Net_players[idx] != &Net_players[player_index]) ){
multi_io_send_reliable(&Net_players[idx], data, offset);
}
}
}
break;
case TS_CODE_PLAYER_UPDATE:
// get the first stop byte
GET_DATA(stop);
while(stop != 0xff){
// get the slot #
GET_DATA(slot_num);
// get the ship class
GET_SHORT(ship_class);
// get the objnum
GET_INT(objnum);
// flag indicating if a player is in this slot
GET_DATA(val);
if(val){
// look the player up
GET_SHORT(player_id);
player_index = find_player_index(player_id);
// if we couldn't find him
if(player_index == -1){
nprintf(("Network","Couldn't find player for pslot update!\n"));
Multi_ts_team[team].multi_ts_player[slot_num] = NULL;
}
// if we found him, assign him to this ship
else {
Net_players[player_index].p_info.ship_class = ship_class;
Net_players[player_index].p_info.ship_index = (int)slot_num;
multi_assign_player_ship(player_index, &Objects[objnum], ship_class);
// ui stuff
Multi_ts_team[team].multi_ts_player[slot_num] = &Net_players[player_index];
// if this was me and my ship index changed, update the weapon select screen
if(my_index != Net_player->p_info.ship_index){
wl_reset_selected_slot();
my_index = Net_player->p_info.ship_index;
}
}
} else {
Multi_ts_team[team].multi_ts_player[slot_num] = NULL;
}
// get the ship flag byte
GET_DATA(val);
Objects[objnum].flags.remove(Object::Object_Flags::Player_ship);
Objects[objnum].flags.remove(Object::Object_Flags::Could_be_player);
switch(val){
case 1 :
Objects[objnum].flags.set(Object::Object_Flags::Player_ship);
break;
case 2 :
obj_set_flags( &Objects[objnum], Objects[objnum].flags + Object::Object_Flags::Could_be_player);
break;
}
// get the next stop byte
GET_DATA(stop);
}
// if we have a sound we're supposed to play
if((sound.isValid()) && !(Game_mode & GM_STANDALONE_SERVER) && (gameseq_get_state() == GS_STATE_TEAM_SELECT)){
gamesnd_play_iface(sound);
}
// if i'm the standalone server, I should rebroadcast this packet
if(Game_mode & GM_STANDALONE_SERVER){
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_HOST(Net_players[idx]) && (Net_player != &Net_players[idx])){
multi_io_send_reliable(&Net_players[idx], data, offset);
}
}
}
break;
}
PACKET_SET_SIZE();
// recalculate stuff
if(!(Game_mode & GM_STANDALONE_SERVER)){
ss_recalc_multiplayer_slots();
}
}