mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 18:31:26 -10:00
Some code refactoring to allow mission titles to be strings of any length. Titles that are too long to actually display (based on string width) will be visibly truncated using `font::force_fit_string`, but the underlying title will not be affected. Multiplayer is still subject to the 31-character length limit, so titles sent over the network will still have characters truncated. Also removes some redundant calls to `gr_get_string_size`, since `font::force_fit_string` returns the fitted string width.
2018 lines
59 KiB
C++
2018 lines
59 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 <cctype>
|
|
|
|
#include "cfile/cfile.h"
|
|
#include "freespace.h"
|
|
#include "gamehelp/contexthelp.h"
|
|
#include "gamesequence/gamesequence.h"
|
|
#include "gamesnd/gamesnd.h"
|
|
#include "globalincs/alphacolors.h"
|
|
#include "graphics/font.h"
|
|
#include "io/key.h"
|
|
#include "menuui/mainhallmenu.h"
|
|
#include "menuui/readyroom.h"
|
|
#include "menuui/techmenu.h" // for tech menu reset stuff
|
|
#include "mission/missionload.h"
|
|
#include "mission/missionparse.h"
|
|
#include "mission/missioncampaign.h"
|
|
#include "missionui/missionscreencommon.h"
|
|
#include "parse/parselo.h"
|
|
#include "pilotfile/pilotfile.h"
|
|
#include "playerman/managepilot.h"
|
|
#include "playerman/player.h"
|
|
#include "popup/popup.h"
|
|
#include "ui/ui.h"
|
|
#include "ui/uidefs.h"
|
|
|
|
|
|
|
|
#define MAX_MISSIONS 1024
|
|
|
|
int Mission_list_coords[GR_NUM_RESOLUTIONS][4] = {
|
|
{ // GR_640
|
|
33, 108, 402, 279
|
|
},
|
|
{ // GR_1024
|
|
43, 175, 402, 459
|
|
}
|
|
};
|
|
|
|
int Campaign_list_coords[GR_NUM_RESOLUTIONS][4] = {
|
|
{ // GR_640
|
|
491, 108, 115, 279
|
|
},
|
|
{ // GR_1024
|
|
491, 175, 115, 459
|
|
}
|
|
};
|
|
|
|
|
|
#define C_SUBTEXT_X 19
|
|
// x coordinate offsets for data when campaign tab active
|
|
#define C_TEXT_X 0
|
|
|
|
// x coordinate offsets for data when mission tab active
|
|
#define M_TEXT_X C_SUBTEXT_X //0
|
|
|
|
#define MODE_CAMPAIGNS 0
|
|
#define MODE_MISSIONS 1
|
|
|
|
#define MAX_LINES 200 // NOLINT(modernize-macro-to-enum)
|
|
#define NUM_BUTTONS 11
|
|
#define LIST_BUTTONS_MAX 42
|
|
|
|
#define SCROLL_UP_BUTTON 0
|
|
#define SCROLL_DOWN_BUTTON 1
|
|
#define MISSION_TAB 2
|
|
#define CAMPAIGN_TAB 3
|
|
#define HELP_BUTTON 4
|
|
#define COMMIT_BUTTON 5
|
|
#define OPTIONS_BUTTON 6
|
|
#define TECH_DATABASE_BUTTON 7
|
|
#define SIMULATOR_BUTTON 8
|
|
#define CUTSCENES_BUTTON 9
|
|
#define CREDITS_BUTTON 10
|
|
|
|
#define X_COORD 0
|
|
#define Y_COORD 1
|
|
#define W_COORD 2
|
|
#define H_COORD 3
|
|
|
|
#define CAMPAIGN_MISSION_HASH_SIZE 307
|
|
|
|
struct sim_room_buttons {
|
|
const char *filename;
|
|
int x, y, xt, yt;
|
|
int hotspot;
|
|
UI_BUTTON button; // because we have a class inside this struct, we need the constructor below..
|
|
|
|
sim_room_buttons(const char *name, int x1, int y1, int xt1, int yt1, int h) : filename(name), x(x1), y(y1), xt(xt1), yt(yt1), hotspot(h) {}
|
|
};
|
|
|
|
static sim_room_buttons Buttons[GR_NUM_RESOLUTIONS][NUM_BUTTONS] = {
|
|
//XSTR:OFF
|
|
{ // GR_640
|
|
sim_room_buttons("LMB_04", 1, 99, -1, -1, 4),
|
|
sim_room_buttons("LMB_05", 1, 381, -1, -1, 5),
|
|
sim_room_buttons("LMB_06", 6, 438, 40, 445, 6),
|
|
sim_room_buttons("LMB_07", 6, 457, 40, 462, 7),
|
|
sim_room_buttons("LMB_08", 534, 426, 500, 440, 8),
|
|
sim_room_buttons("LMB_09", 571, 426, 572, 413, 9),
|
|
sim_room_buttons("LMB_10", 534, 455, 480, 462, 10),
|
|
|
|
sim_room_buttons("TDB_00", 7, 3, 37, 7, 0),
|
|
sim_room_buttons("TDB_01", 7, 18, 37, 23, 1),
|
|
sim_room_buttons("TDB_02", 7, 34, 37, 38, 2),
|
|
sim_room_buttons("TDB_03", 7, 49, 37, 54, 3),
|
|
},
|
|
{ // GR_1024
|
|
sim_room_buttons("2_LMB_04", 2, 159, -1, -1, 4),
|
|
sim_room_buttons("2_LMB_05", 2, 609, -1, -1, 5),
|
|
sim_room_buttons("2_LMB_06", 10, 701, 64, 712, 6),
|
|
sim_room_buttons("2_LMB_07", 10, 732, 64, 739, 7),
|
|
sim_room_buttons("2_LMB_08", 854, 681, 800, 704, 8),
|
|
sim_room_buttons("2_LMB_09", 914, 681, 915, 660, 9),
|
|
sim_room_buttons("2_LMB_10", 854, 728, 780, 736, 10),
|
|
|
|
sim_room_buttons("2_TDB_00", 12, 5, 59, 12, 0),
|
|
sim_room_buttons("2_TDB_01", 12, 31, 59, 37, 1),
|
|
sim_room_buttons("2_TDB_02", 12, 56, 59, 62, 2),
|
|
sim_room_buttons("2_TDB_03", 12, 81, 59, 88, 3),
|
|
}
|
|
//XSTR:ON
|
|
};
|
|
|
|
SCP_vector<sim_mission> Sim_Missions;
|
|
SCP_vector<sim_mission> Sim_CMissions;
|
|
|
|
const char* Sim_filename[GR_NUM_RESOLUTIONS] = {
|
|
"LoadMission",
|
|
"2_LoadMission"
|
|
};
|
|
const char* Sim_mask_filename[GR_NUM_RESOLUTIONS] = {
|
|
"LoadMission-m",
|
|
"2_LoadMission-m"
|
|
};
|
|
|
|
const char* Campaign_filename[GR_NUM_RESOLUTIONS] = {
|
|
"Campaign",
|
|
"2_Campaign"
|
|
};
|
|
const char* Campaign_mask_filename[GR_NUM_RESOLUTIONS] = {
|
|
"Campaign-m",
|
|
"2_Campaign-m"
|
|
};
|
|
|
|
const char* Sim_room_slider_filename[GR_NUM_RESOLUTIONS] = {
|
|
"slider",
|
|
"2_slider"
|
|
};
|
|
|
|
// misc text. ("Mission" and "Filename"
|
|
#define NUM_SIM_MISC_TEXT 2
|
|
#define SIM_MISC_TEXT_MISSION 0
|
|
#define SIM_MISC_TEXT_FILENAME 1
|
|
int Sim_misc_text_coords[GR_NUM_RESOLUTIONS][NUM_SIM_MISC_TEXT][2] = {
|
|
{ // GR_640
|
|
{33, 95},
|
|
{491, 95}
|
|
},
|
|
{ // GR_1024
|
|
{43, 155},
|
|
{491, 155}
|
|
}
|
|
};
|
|
|
|
int Sim_room_slider_coords[GR_NUM_RESOLUTIONS][4] = {
|
|
{ // GR_640
|
|
4, 131, 20, 245
|
|
},
|
|
{ // GR_1024
|
|
5, 209, 32, 392
|
|
}
|
|
};
|
|
|
|
// readyroom text line stuff
|
|
#define READYROOM_LINE_CAMPAIGN 1
|
|
#define READYROOM_LINE_CMISSION 2
|
|
#define READYROOM_LINE_MISSION 3
|
|
|
|
#define READYROOM_FLAG_FROM_VOLITION (1<<0) // volition made
|
|
static struct {
|
|
int type; // see READYROOM_LINE_* defines above
|
|
const char *name;
|
|
const char *filename;
|
|
int x; // X coordinate of line
|
|
int y; // Y coordinate of line
|
|
int flags; // special flags, see READYROOM_FLAG_* defines above
|
|
} sim_room_lines[MAX_LINES];
|
|
|
|
static char Cur_campaign[MAX_FILENAME_LEN];
|
|
static char *Mission_filenames[MAX_MISSIONS] = { NULL };
|
|
static char *Standalone_mission_names[MAX_MISSIONS] = { NULL };
|
|
static int Standalone_mission_flags[MAX_MISSIONS];
|
|
static char *Campaign_missions[MAX_MISSIONS] = { NULL };
|
|
static char *Campaign_mission_names[MAX_CAMPAIGN_MISSIONS] = { NULL };
|
|
static int Campaign_mission_flags[MAX_MISSIONS];
|
|
static int Simroom_show_all = 0;
|
|
static int Standalone_mission_names_inited = 0;
|
|
static int Campaign_mission_names_inited = 0;
|
|
static int Num_standalone_missions;
|
|
static int Num_campaign_missions;
|
|
//static int Num_player_missions;
|
|
static int Scroll_offset;
|
|
static int Selected_line;
|
|
static int Num_lines;
|
|
static int Num_campaign_missions_with_info = 0;
|
|
static int Num_standalone_missions_with_info = 0;
|
|
static int list_x1;
|
|
static int list_x2;
|
|
static int list_y;
|
|
static int list_w1;
|
|
static int list_w2;
|
|
static int list_h;
|
|
static int Background_bitmap;
|
|
static int Campaign_background_bitmap_mask;
|
|
static UI_WINDOW Ui_window;
|
|
static UI_BUTTON List_buttons[LIST_BUTTONS_MAX]; // buttons for each line of text in list
|
|
|
|
int Sim_room_overlay_id;
|
|
int Campaign_room_overlay_id;
|
|
|
|
// globals
|
|
bool Campaign_room_no_campaigns = false;
|
|
|
|
// slider stuff
|
|
static UI_SLIDER2 Sim_room_slider;
|
|
|
|
typedef struct hash_node {
|
|
hash_node *next;
|
|
const char *filename;
|
|
} hash_node;
|
|
|
|
static hash_node *Campaign_mission_hash_table[CAMPAIGN_MISSION_HASH_SIZE];
|
|
static int Hash_table_inited;
|
|
|
|
// special icons (1.04 + stuff)
|
|
#define NUM_MISSION_ICONS 1
|
|
#define MISSION_ICON_VOLITION 0 // mini volition death's head :)
|
|
|
|
// icon offsets (see LIST_ defines above
|
|
//#define MISSION_ICON_VOLITION_X (46)
|
|
#define MISSION_ICON_VOLITION_Y_OFFSET (-1)
|
|
|
|
// icon offsets
|
|
static int Sim_volition_icon_x[GR_NUM_RESOLUTIONS] = {
|
|
38,
|
|
49
|
|
};
|
|
|
|
// special icons themselves
|
|
int Mission_icon_bitmaps[NUM_MISSION_ICONS];
|
|
//XSTR:OFF
|
|
const char *Mission_icon_bitmap_filenames[NUM_MISSION_ICONS] = {
|
|
"icon-volition"
|
|
};
|
|
//XSTR:ON
|
|
void sim_room_load_mission_icons();
|
|
void sim_room_unload_mission_icons();
|
|
void sim_room_blit_icons(int line_index, int y_start, fs_builtin_mission *fb = NULL, int is_md = 0);
|
|
|
|
const std::shared_ptr<scripting::Hook<>> OnCampaignBeginHook = scripting::Hook<>::Factory(
|
|
"On Campaign Begin", "Called when a campaign is started from the beginning or is reset",
|
|
{
|
|
{ "Campaign", "string", "The campaign filename (without the extension)" },
|
|
});
|
|
|
|
|
|
// Finds a hash value for mission filename
|
|
//
|
|
// returns hash value
|
|
int hash_filename(const char *filename) {
|
|
ulonglong hash_val = 0;
|
|
const char *ptr = filename;
|
|
|
|
// Don't hash .fsm extension, convert all to upper case
|
|
for (int i=0; i < ((signed int)(strlen(filename)) - 4); i++) {
|
|
hash_val = (hash_val << 4) + SCP_toupper(*ptr++);
|
|
}
|
|
|
|
return int(hash_val % CAMPAIGN_MISSION_HASH_SIZE);
|
|
}
|
|
|
|
// insert filename into Campaign_mission_hash_table
|
|
//
|
|
// returns 1 if successful, 0 if could not allocate memory
|
|
int hash_insert(const char *filename) {
|
|
int hash_val = hash_filename(filename);
|
|
hash_node *cur_node;
|
|
|
|
// Check if table empty
|
|
if (Campaign_mission_hash_table[hash_val] == NULL) {
|
|
Campaign_mission_hash_table[hash_val] = new hash_node;
|
|
|
|
cur_node = Campaign_mission_hash_table[hash_val];
|
|
|
|
if (cur_node == NULL) {
|
|
// Unable to allocate memory
|
|
return 0;
|
|
}
|
|
} else {
|
|
// Walk down list to first empty node
|
|
cur_node = Campaign_mission_hash_table[hash_val];
|
|
while (cur_node->next != NULL) {
|
|
cur_node = cur_node->next;
|
|
}
|
|
|
|
// Create new node
|
|
cur_node->next = new hash_node;
|
|
|
|
if (cur_node->next == NULL) {
|
|
// unable to allocate memory
|
|
return 0;
|
|
} else {
|
|
cur_node = cur_node->next;
|
|
}
|
|
}
|
|
|
|
// Initialize data
|
|
cur_node->next = NULL;
|
|
cur_node->filename = filename;
|
|
|
|
// Return successs
|
|
return 1;
|
|
}
|
|
|
|
// Checks if a filename already exitst in the hash table
|
|
//
|
|
// returns 1 if found (collision), 0 if no collision
|
|
int campaign_mission_hash_collision(const char *filename)
|
|
{
|
|
int hash_val = hash_filename(filename);
|
|
hash_node *cur_node = Campaign_mission_hash_table[hash_val];
|
|
|
|
if (cur_node == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
do {
|
|
if (!stricmp(filename, cur_node->filename)) {
|
|
return 1;
|
|
}
|
|
|
|
cur_node = cur_node->next;
|
|
} while (cur_node != NULL);
|
|
|
|
// Ran out of stuff to check
|
|
return 0;
|
|
}
|
|
|
|
// builds hash table of campaign mission filenames
|
|
//
|
|
// returns 1 if successful, 0 if not successful
|
|
int build_campaign_mission_filename_hash_table()
|
|
{
|
|
int rval;
|
|
// Go through all campaign missions
|
|
for (int i=0; i<Num_campaign_missions; i++) {
|
|
rval = hash_insert(Campaign_missions[i]);
|
|
if (rval == 0) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// successful
|
|
return 1;
|
|
}
|
|
|
|
// deletes hash table nodes
|
|
//
|
|
void campaign_mission_hash_table_delete()
|
|
{
|
|
hash_node *cur_node;
|
|
|
|
for (int i=0; i<CAMPAIGN_MISSION_HASH_SIZE; i++) {
|
|
// Look for entries into array
|
|
if (Campaign_mission_hash_table[i] != NULL) {
|
|
cur_node = Campaign_mission_hash_table[i];
|
|
|
|
// Walk down the list deleting self
|
|
while (cur_node->next != NULL) {
|
|
hash_node *temp = cur_node->next;
|
|
delete cur_node;
|
|
cur_node = temp;
|
|
}
|
|
|
|
// Delete last node
|
|
delete cur_node;
|
|
Campaign_mission_hash_table[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// add a line of sim_room smuck to end of list
|
|
int sim_room_line_add(int type, const char *name, const char *filename, int x, int y, int flags)
|
|
{
|
|
if (Num_lines >= MAX_LINES)
|
|
return 0;
|
|
|
|
sim_room_lines[Num_lines].type = type;
|
|
sim_room_lines[Num_lines].name = name;
|
|
sim_room_lines[Num_lines].filename = filename;
|
|
sim_room_lines[Num_lines].x = x;
|
|
sim_room_lines[Num_lines].y = y;
|
|
sim_room_lines[Num_lines].flags = flags;
|
|
return Num_lines++;
|
|
}
|
|
|
|
// build up a list of all missions in all campaigns.
|
|
int sim_room_campaign_mission_filter(const char *filename)
|
|
{
|
|
int num;
|
|
|
|
num = mission_campaign_get_mission_list(filename, &Campaign_missions[Num_campaign_missions], MAX_MISSIONS - Num_campaign_missions);
|
|
if (num < 0)
|
|
return 0;
|
|
|
|
Num_campaigns++;
|
|
Num_campaign_missions += num;
|
|
return 1;
|
|
}
|
|
|
|
// filter out all missions already used in existing campaigns
|
|
int sim_room_standalone_mission_filter(const char *filename)
|
|
{
|
|
int type;
|
|
char mission_name[255];
|
|
|
|
// Check if a campaign mission (single and multi)
|
|
if (campaign_mission_hash_collision(filename)) {
|
|
return 0;
|
|
}
|
|
|
|
// Check if a standalone multi mission OR Mdisk mission with data
|
|
type = mission_parse_is_multi(filename, mission_name);
|
|
if (type && !(type & MISSION_TYPE_SINGLE))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
// builds up list of standalone missions and adds them to missions simulator
|
|
// processes one mission per frame
|
|
//
|
|
// returns 1 if finished with all missions, 0 otherwise
|
|
//
|
|
int build_standalone_mission_list_do_frame(bool API_Access)
|
|
{
|
|
int font_height = gr_get_font_height();
|
|
SCP_string popup_str;
|
|
bool lcl_weirdness = false;
|
|
|
|
// When no standalone missions in data directory
|
|
if (Num_standalone_missions == 0) {
|
|
Standalone_mission_names_inited = 1;
|
|
return 1;
|
|
}
|
|
|
|
// Set global variable so we we'll have list available next time
|
|
Standalone_mission_names[Num_standalone_missions_with_info] = NULL;
|
|
Standalone_mission_flags[Num_standalone_missions_with_info] = 0;
|
|
|
|
if (Num_standalone_missions > 0) { // sanity check
|
|
if (strlen(Mission_filenames[Num_standalone_missions_with_info]) < MAX_FILENAME_LEN - 4) { // sanity check?
|
|
// tack on an extension
|
|
auto filename = cf_add_ext(Mission_filenames[Num_standalone_missions_with_info], FS_MISSION_FILE_EXT);
|
|
|
|
// update popup
|
|
sprintf(popup_str, XSTR("Single Mission\n\n%s", 989), filename);
|
|
popup_change_text(popup_str.c_str());
|
|
|
|
// activate tstrings check
|
|
Lcl_unexpected_tstring_check = &lcl_weirdness;
|
|
|
|
// check if we can list the mission, if loading basic info didn't return an error code, and if we didn't find an XSTR mismatch
|
|
bool condition = !mission_is_ignored(filename) && !get_mission_info(filename) && !lcl_weirdness;
|
|
|
|
// maybe log
|
|
if (lcl_weirdness)
|
|
mprintf(("Skipping %s due to XSTR mismatch\n", filename));
|
|
|
|
// deactivate tstrings check
|
|
Lcl_unexpected_tstring_check = nullptr;
|
|
|
|
if (condition) {
|
|
Standalone_mission_names[Num_standalone_missions_with_info] = vm_strdup(The_mission.name.c_str());
|
|
Standalone_mission_flags[Num_standalone_missions_with_info] = The_mission.game_type;
|
|
int y = Num_lines * (font_height + 2);
|
|
|
|
//Add mission data to the API
|
|
if (API_Access) {
|
|
sim_mission api_mission;
|
|
api_mission.name = The_mission.name;
|
|
api_mission.filename = filename;
|
|
api_mission.mission_desc = The_mission.mission_desc;
|
|
api_mission.author = The_mission.author;
|
|
api_mission.visible = 1;
|
|
|
|
Sim_Missions.push_back(std::move(api_mission));
|
|
}
|
|
|
|
// determine some extra information
|
|
int flags = 0;
|
|
auto fb = game_find_builtin_mission(filename);
|
|
if((fb != NULL) && (fb->flags & FSB_FROM_VOLITION)){
|
|
flags |= READYROOM_FLAG_FROM_VOLITION;
|
|
}
|
|
|
|
// add the line
|
|
sim_room_line_add(READYROOM_LINE_MISSION, Standalone_mission_names[Num_standalone_missions_with_info], Mission_filenames[Num_standalone_missions_with_info], list_x1 + M_TEXT_X, y, flags);
|
|
}
|
|
}
|
|
|
|
Num_standalone_missions_with_info++;
|
|
}
|
|
|
|
if (Num_standalone_missions_with_info == Num_standalone_missions) {
|
|
Standalone_mission_names_inited = 1;
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// builds up list of already played missions in a campaign and adds them to missions simulator
|
|
// processes one mission per frame
|
|
//
|
|
// returns 1 if finished with all missions, 0 otherwise
|
|
//
|
|
int build_campaign_mission_list_do_frame(bool API_Access)
|
|
{
|
|
int font_height = gr_get_font_height();
|
|
SCP_string popup_str;
|
|
static int valid_missions_with_info = 0; // we use this to avoid blank entries in the mission list
|
|
|
|
// When no campaign files in data directory
|
|
if (Campaign.num_missions == 0) {
|
|
Campaign_mission_names_inited = 1;
|
|
return 1;
|
|
}
|
|
|
|
// change popup
|
|
sprintf(popup_str, XSTR("Campaign Mission\n\n%s", 990), Campaign.missions[Num_campaign_missions_with_info].name);
|
|
popup_change_text(popup_str.c_str());
|
|
|
|
// Set global variable so we we'll have list available next time
|
|
Campaign_mission_names[Num_campaign_missions_with_info] = NULL;
|
|
Campaign_mission_flags[Num_campaign_missions_with_info] = 0;
|
|
|
|
// Only allow missions already completed
|
|
if (Campaign.missions[Num_campaign_missions_with_info].completed || (Simroom_show_all || API_Access))
|
|
{
|
|
if (!get_mission_info(Campaign.missions[Num_campaign_missions_with_info].name))
|
|
{
|
|
auto filename = Campaign.missions[Num_campaign_missions_with_info].name;
|
|
|
|
// add to list
|
|
Campaign_mission_names[Num_campaign_missions_with_info] = vm_strdup(The_mission.name.c_str());
|
|
Campaign_mission_flags[Num_campaign_missions_with_info] = The_mission.game_type;
|
|
int y = valid_missions_with_info * (font_height + 2);
|
|
|
|
// Add mission data to the API
|
|
if (API_Access) {
|
|
sim_mission api_mission;
|
|
api_mission.name = The_mission.name;
|
|
api_mission.filename = filename;
|
|
api_mission.mission_desc = The_mission.mission_desc;
|
|
api_mission.author = The_mission.author;
|
|
api_mission.visible = Campaign.missions[Num_campaign_missions_with_info].completed;
|
|
|
|
Sim_CMissions.push_back(std::move(api_mission));
|
|
}
|
|
|
|
// determine some extra information
|
|
int flags = 0;
|
|
auto fb = game_find_builtin_mission(Campaign.missions[Num_campaign_missions_with_info].name);
|
|
if((fb != NULL) && (fb->flags & FSB_FROM_VOLITION))
|
|
{
|
|
flags |= READYROOM_FLAG_FROM_VOLITION;
|
|
}
|
|
|
|
sim_room_line_add(READYROOM_LINE_CMISSION, Campaign_mission_names[Num_campaign_missions_with_info], Campaign.missions[Num_campaign_missions_with_info].name, list_x1 + C_SUBTEXT_X, y, flags);
|
|
valid_missions_with_info++;
|
|
}
|
|
}
|
|
|
|
Num_campaign_missions_with_info++;
|
|
|
|
if (Num_campaign_missions_with_info == Campaign.num_missions) {
|
|
valid_missions_with_info = 0;
|
|
Campaign_mission_names_inited = 1;
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Builds list of either (1) standalone missions or (2) already played missions in current campaign
|
|
// First time through, it builds list, next time it uses precompiled list
|
|
void sim_room_build_listing()
|
|
{
|
|
int i, y, max_num_entries_viewable;
|
|
int font_height = gr_get_font_height();
|
|
|
|
Num_lines = y = 0;
|
|
list_y = Mission_list_coords[gr_screen.res][1];
|
|
list_h = Mission_list_coords[gr_screen.res][3];
|
|
|
|
// Stand alone single player missions.
|
|
if (Player->readyroom_listing_mode == MODE_MISSIONS) {
|
|
if (Hash_table_inited) {
|
|
if (!Standalone_mission_names_inited) { // Is this the first time through
|
|
// build_list_do_frame builds list and adds sim room line and sets Standalone_mission_names_inited
|
|
popup_till_condition([]() -> int { return build_standalone_mission_list_do_frame(false); },
|
|
POPUP_CANCEL,
|
|
XSTR("Loading missions", 991));
|
|
} else {
|
|
for (i=0; i<Num_standalone_missions_with_info; i++) {
|
|
if (Standalone_mission_names[i]) {
|
|
// determine some extra information
|
|
int flags = 0;
|
|
auto full_filename = cf_add_ext(Mission_filenames[i], FS_MISSION_FILE_EXT);
|
|
auto fb = game_find_builtin_mission(full_filename);
|
|
if((fb != NULL) && (fb->flags & FSB_FROM_VOLITION)){
|
|
flags |= READYROOM_FLAG_FROM_VOLITION;
|
|
}
|
|
|
|
sim_room_line_add(READYROOM_LINE_MISSION, Standalone_mission_names[i], Mission_filenames[i], list_x1 + M_TEXT_X, y, flags);
|
|
y += font_height + 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Campaign missions
|
|
list_y += font_height + 2;
|
|
list_h -= font_height - 2;
|
|
|
|
if (!Campaign_mission_names_inited) { // Is this the first time through
|
|
// builds list, adds sim room line and sets Campaign_mission_names_inited
|
|
popup_till_condition([]() -> int { return build_campaign_mission_list_do_frame(false); },
|
|
POPUP_CANCEL,
|
|
XSTR("Loading campaign missions", 992));
|
|
} else {
|
|
for (i=0; i<Num_campaign_missions_with_info; i++) {
|
|
if (Campaign_mission_names[i]) {
|
|
// determine some extra information
|
|
int flags = 0;
|
|
auto full_filename = cf_add_ext(Campaign.missions[i].name, FS_MISSION_FILE_EXT);
|
|
auto fb = game_find_builtin_mission(full_filename);
|
|
if((fb != NULL) && (fb->flags & FSB_FROM_VOLITION)){
|
|
flags |= READYROOM_FLAG_FROM_VOLITION;
|
|
}
|
|
|
|
sim_room_line_add(READYROOM_LINE_CMISSION, Campaign_mission_names[i], Campaign.missions[i].name, list_x1 + C_SUBTEXT_X, y, flags);
|
|
y += font_height + 2; // Goober5000 - added +2 to conform with above
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// free up memory from parsing all of those missions
|
|
stop_parse();
|
|
|
|
// set appropriate slider size
|
|
max_num_entries_viewable = list_h / (font_height + 2);
|
|
Sim_room_slider.set_numberItems((Num_lines > max_num_entries_viewable) ? (Num_lines - max_num_entries_viewable) : 0);
|
|
}
|
|
|
|
void sim_room_reset_campaign_listing()
|
|
{
|
|
// only reset if we got fully inited in the first place
|
|
if (!Campaign_mission_names_inited)
|
|
return;
|
|
|
|
|
|
for (int i=0; i<Campaign.num_missions; i++) {
|
|
if (Campaign_mission_names[i]) {
|
|
vm_free(Campaign_mission_names[i]);
|
|
Campaign_mission_names[i] = NULL;
|
|
}
|
|
}
|
|
|
|
Campaign_mission_names_inited = 0;
|
|
Num_campaign_missions_with_info = 0;
|
|
|
|
sim_room_build_listing();
|
|
}
|
|
|
|
int sim_room_line_query_visible(int n)
|
|
{
|
|
int y;
|
|
|
|
if ((n < 0) || (n >= Num_lines))
|
|
return 0;
|
|
|
|
y = sim_room_lines[n].y - sim_room_lines[Scroll_offset].y;
|
|
if ((y < 0) || (y + gr_get_font_height() > list_h))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void sim_room_scroll_screen_up()
|
|
{
|
|
if (Scroll_offset > 0) {
|
|
Scroll_offset--;
|
|
|
|
if (Player->readyroom_listing_mode == MODE_MISSIONS)
|
|
{
|
|
Assert(Selected_line > Scroll_offset);
|
|
while (!sim_room_line_query_visible(Selected_line))
|
|
{
|
|
Selected_line--;
|
|
}
|
|
}
|
|
|
|
gamesnd_play_iface(InterfaceSounds::SCROLL);
|
|
|
|
} else
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
}
|
|
|
|
void sim_room_scroll_line_up()
|
|
{
|
|
if (Selected_line) {
|
|
Selected_line--;
|
|
if (Selected_line < Scroll_offset)
|
|
{
|
|
Scroll_offset = Selected_line;
|
|
Sim_room_slider.forceUp();
|
|
}
|
|
|
|
gamesnd_play_iface(InterfaceSounds::SCROLL);
|
|
|
|
} else
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
}
|
|
|
|
void sim_room_scroll_screen_down()
|
|
{
|
|
if (sim_room_lines[Num_lines - 1].y + gr_get_font_height() > sim_room_lines[Scroll_offset].y + list_h) {
|
|
Scroll_offset++;
|
|
|
|
if (Player->readyroom_listing_mode == MODE_MISSIONS)
|
|
{
|
|
while (!sim_room_line_query_visible(Selected_line))
|
|
{
|
|
Selected_line++;
|
|
Assert(Selected_line < Num_lines);
|
|
}
|
|
}
|
|
|
|
gamesnd_play_iface(InterfaceSounds::SCROLL);
|
|
|
|
} else
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
}
|
|
|
|
void sim_room_scroll_line_down()
|
|
{
|
|
if (Selected_line < Num_lines - 1) {
|
|
Selected_line++;
|
|
|
|
Assert(Selected_line > Scroll_offset);
|
|
while (!sim_room_line_query_visible(Selected_line))
|
|
{
|
|
Scroll_offset++;
|
|
Sim_room_slider.forceDown();
|
|
}
|
|
|
|
gamesnd_play_iface(InterfaceSounds::SCROLL);
|
|
|
|
} else
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
}
|
|
|
|
/* Goober5000 - why are there two nearly identical functions?
|
|
(campaign_room_reset_campaign and sim_room_reset_campaign)
|
|
Looks like this function should be deprecated...
|
|
|
|
// returns: 0 = success, !0 = aborted or failed
|
|
int sim_room_reset_campaign()
|
|
{
|
|
int z, rval = 1;
|
|
|
|
z = popup(PF_TITLE_BIG | PF_TITLE_RED, 2, POPUP_CANCEL, POPUP_OK, XSTR( "Warning\nThis will cause all progress in your\ncurrent campaign to be lost", 110) );
|
|
|
|
if (z) {
|
|
mission_campaign_savefile_delete(Campaign.filename);
|
|
mission_campaign_load(Campaign.filename);
|
|
|
|
rval = 0;
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
*/
|
|
|
|
// Decide if we should offer choice to resume this savegame
|
|
int sim_room_can_resume_savegame(char * /*savegame_filename*/)
|
|
{
|
|
#ifdef FREESPACE_SAVERESTORE_SYSTEM
|
|
char savegame_mission[MAX_FILENAME_LEN];
|
|
|
|
if (state_read_description(savegame_filename, NULL, savegame_mission)) {
|
|
return 0;
|
|
}
|
|
|
|
if (stricmp(Game_current_mission_filename, savegame_mission)) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int readyroom_continue_campaign()
|
|
{
|
|
// check for possible mission loop, need to do this before calling mission_campaign_next_mission()!
|
|
if ( !(Game_mode & GM_MULTIPLAYER) && !Campaign.loop_enabled && (Campaign.current_mission == -1) &&
|
|
(Campaign.prev_mission != -1) && (Campaign.next_mission != -1) )
|
|
{
|
|
if (Campaign.missions[Campaign.prev_mission].flags & CMISSION_FLAG_HAS_LOOP) {
|
|
// NOTE: the order of these calls is *very* important
|
|
|
|
// we must manually set the current mission to the *previous* mission and the mission name.
|
|
// this isn't the same thing that is done in mission_campaign_next_mission(), but it's
|
|
// cleaner to do this here than it is to hack that function to do the same thing
|
|
Campaign.current_mission = Campaign.prev_mission;
|
|
strcpy_s( Game_current_mission_filename, Campaign.missions[Campaign.current_mission].name );
|
|
|
|
// set the bit for campaign mode
|
|
Game_mode |= GM_CAMPAIGN_MODE;
|
|
|
|
// eval the mission (may be needed to setup loop mission stuff)
|
|
if (Campaign.loop_mission == CAMPAIGN_LOOP_MISSION_UNINITIALIZED)
|
|
mission_campaign_eval_next_mission();
|
|
|
|
// only proceed to loop if we have a loop
|
|
if (Campaign.loop_mission > 0) {
|
|
gameseq_post_event(GS_EVENT_LOOP_BRIEF);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
int mc_rval = mission_campaign_next_mission();
|
|
if (mc_rval == -1)
|
|
{ // is campaign and next mission valid?
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR( "The campaign is over. To replay the campaign, either create a new pilot or restart the campaign in the campaign room.", 112) );
|
|
return -1;
|
|
}
|
|
else if(mc_rval == -2)
|
|
{
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, NOX("The current campaign has no missions") );
|
|
return -1;
|
|
}
|
|
|
|
// set the bit for campaign mode
|
|
Game_mode |= GM_CAMPAIGN_MODE;
|
|
gameseq_post_event( GS_EVENT_START_GAME );
|
|
|
|
return 0;
|
|
}
|
|
|
|
void sim_room_commit()
|
|
{
|
|
if ((Selected_line >= Num_lines) || !sim_room_lines[Selected_line].filename) {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
return;
|
|
}
|
|
|
|
strcpy_s(Game_current_mission_filename, sim_room_lines[Selected_line].filename);
|
|
|
|
Game_mode &= ~(GM_CAMPAIGN_MODE); // be sure this bit is clear
|
|
|
|
gameseq_post_event(GS_EVENT_START_GAME);
|
|
gamesnd_play_iface(InterfaceSounds::COMMIT_PRESSED);
|
|
}
|
|
|
|
int sim_room_button_pressed(int n)
|
|
{
|
|
switch (n) {
|
|
case SCROLL_UP_BUTTON:
|
|
sim_room_scroll_screen_up();
|
|
Sim_room_slider.forceUp();
|
|
break;
|
|
|
|
case SCROLL_DOWN_BUTTON:
|
|
sim_room_scroll_screen_down();
|
|
Sim_room_slider.forceDown();
|
|
break;
|
|
|
|
case MISSION_TAB:
|
|
Simroom_show_all = 0;
|
|
Player->readyroom_listing_mode = MODE_MISSIONS;
|
|
Selected_line = Scroll_offset = 0;
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
sim_room_build_listing();
|
|
break;
|
|
|
|
case CAMPAIGN_TAB:
|
|
if ( !strlen(Campaign.filename) ) {
|
|
if (Campaign_load_failure == CAMPAIGN_ERROR_MISSING) {
|
|
popup(PF_USE_AFFIRMATIVE_ICON | PF_NO_NETWORKING, 1, POPUP_OK, XSTR("The currently active campaign cannot be found. Unable to switch to campaign mode.", 1612));
|
|
} else {
|
|
popup(PF_USE_AFFIRMATIVE_ICON | PF_NO_NETWORKING, 1, POPUP_OK, XSTR("The currently active campaign cannot be loaded. Unable to switch to campaign mode.", 1816));
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (Player->readyroom_listing_mode != MODE_CAMPAIGNS)
|
|
Simroom_show_all = 0;
|
|
|
|
Player->readyroom_listing_mode = MODE_CAMPAIGNS;
|
|
Scroll_offset = 0;
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
sim_room_build_listing();
|
|
break;
|
|
|
|
case COMMIT_BUTTON:
|
|
sim_room_commit();
|
|
break;
|
|
|
|
case HELP_BUTTON:
|
|
launch_context_help();
|
|
gamesnd_play_iface(InterfaceSounds::HELP_PRESSED);
|
|
break;
|
|
|
|
case OPTIONS_BUTTON:
|
|
gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS);
|
|
gameseq_post_event(GS_EVENT_OPTIONS_MENU);
|
|
return 1;
|
|
|
|
case TECH_DATABASE_BUTTON:
|
|
gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS);
|
|
gameseq_post_event(GS_EVENT_TECH_MENU);
|
|
return 1;
|
|
|
|
case CUTSCENES_BUTTON:
|
|
gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS);
|
|
gameseq_post_event(GS_EVENT_GOTO_VIEW_CUTSCENES_SCREEN);
|
|
return 1;
|
|
|
|
case CREDITS_BUTTON:
|
|
gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS);
|
|
gameseq_post_event(GS_EVENT_CREDITS);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// do nothing
|
|
void sim_room_scroll_capture()
|
|
{
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// mission_sim_room_init()
|
|
//
|
|
// Initialize the sim_room assignment screen system. Called when GS_STATE_sim_room_SCREEN
|
|
// is entered.
|
|
//
|
|
|
|
void sim_room_init()
|
|
{
|
|
int i;
|
|
sim_room_buttons *b;
|
|
char wild_card[8];
|
|
|
|
list_x1 = Mission_list_coords[gr_screen.res][0];
|
|
list_x2 = Campaign_list_coords[gr_screen.res][0];
|
|
list_y = Mission_list_coords[gr_screen.res][1];
|
|
list_w1 = Mission_list_coords[gr_screen.res][2];
|
|
list_w2 = Campaign_list_coords[gr_screen.res][2];
|
|
list_h = Mission_list_coords[gr_screen.res][3];
|
|
|
|
// force mode to valid range. I once had a bogus value here. Don't know how that happened, though.
|
|
if (Player->readyroom_listing_mode != MODE_MISSIONS)
|
|
Player->readyroom_listing_mode = MODE_CAMPAIGNS;
|
|
|
|
*Game_current_mission_filename = 0;
|
|
common_set_interface_palette("InterfacePalette"); // set the interface palette
|
|
Ui_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0);
|
|
Ui_window.set_mask_bmap(Sim_mask_filename[gr_screen.res]);
|
|
|
|
for (i=0; i<NUM_BUTTONS; i++) {
|
|
b = &Buttons[gr_screen.res][i];
|
|
|
|
b->button.create(&Ui_window, "", b->x, b->y, 60, 30, (i < 2), 1);
|
|
// set up callback for when a mouse first goes over a button
|
|
b->button.set_highlight_action(common_play_highlight_sound);
|
|
b->button.set_bmaps(b->filename);
|
|
b->button.link_hotspot(b->hotspot);
|
|
}
|
|
|
|
// screen/button specific text
|
|
Ui_window.add_XSTR("Single Missions", 1060, Buttons[gr_screen.res][MISSION_TAB].xt, Buttons[gr_screen.res][MISSION_TAB].yt, &Buttons[gr_screen.res][MISSION_TAB].button, UI_XSTR_COLOR_GREEN);
|
|
Ui_window.add_XSTR("Campaign Missions", 1061, Buttons[gr_screen.res][CAMPAIGN_TAB].xt, Buttons[gr_screen.res][CAMPAIGN_TAB].yt, &Buttons[gr_screen.res][CAMPAIGN_TAB].button, UI_XSTR_COLOR_GREEN);
|
|
Ui_window.add_XSTR("Help", 928, Buttons[gr_screen.res][HELP_BUTTON].xt, Buttons[gr_screen.res][HELP_BUTTON].yt, &Buttons[gr_screen.res][HELP_BUTTON].button, UI_XSTR_COLOR_GREEN);
|
|
Ui_window.add_XSTR("Commit", 1062, Buttons[gr_screen.res][COMMIT_BUTTON].xt, Buttons[gr_screen.res][COMMIT_BUTTON].yt, &Buttons[gr_screen.res][COMMIT_BUTTON].button, UI_XSTR_COLOR_PINK);
|
|
Ui_window.add_XSTR("Options", 1036, Buttons[gr_screen.res][OPTIONS_BUTTON].xt, Buttons[gr_screen.res][OPTIONS_BUTTON].yt, &Buttons[gr_screen.res][OPTIONS_BUTTON].button, UI_XSTR_COLOR_GREEN);
|
|
|
|
// common tab button text
|
|
Ui_window.add_XSTR("Technical Database", 1055, Buttons[gr_screen.res][TECH_DATABASE_BUTTON].xt, Buttons[gr_screen.res][TECH_DATABASE_BUTTON].yt, &Buttons[gr_screen.res][TECH_DATABASE_BUTTON].button, UI_XSTR_COLOR_GREEN);
|
|
Ui_window.add_XSTR("Mission Simulator", 1056, Buttons[gr_screen.res][SIMULATOR_BUTTON].xt, Buttons[gr_screen.res][SIMULATOR_BUTTON].yt, &Buttons[gr_screen.res][SIMULATOR_BUTTON].button, UI_XSTR_COLOR_GREEN);
|
|
Ui_window.add_XSTR("Cutscenes", 1057, Buttons[gr_screen.res][CUTSCENES_BUTTON].xt, Buttons[gr_screen.res][CUTSCENES_BUTTON].yt, &Buttons[gr_screen.res][CUTSCENES_BUTTON].button, UI_XSTR_COLOR_GREEN);
|
|
Ui_window.add_XSTR("Credits", 1058, Buttons[gr_screen.res][CREDITS_BUTTON].xt, Buttons[gr_screen.res][CREDITS_BUTTON].yt, &Buttons[gr_screen.res][CREDITS_BUTTON].button, UI_XSTR_COLOR_GREEN);
|
|
|
|
// misc text - not associated with any buttons
|
|
Ui_window.add_XSTR("Mission", 1063, Sim_misc_text_coords[gr_screen.res][SIM_MISC_TEXT_MISSION][0], Sim_misc_text_coords[gr_screen.res][SIM_MISC_TEXT_MISSION][1], NULL, UI_XSTR_COLOR_GREEN);
|
|
Ui_window.add_XSTR("Filename", 1064, Sim_misc_text_coords[gr_screen.res][SIM_MISC_TEXT_FILENAME][0], Sim_misc_text_coords[gr_screen.res][SIM_MISC_TEXT_FILENAME][1], NULL, UI_XSTR_COLOR_GREEN);
|
|
|
|
for (i=0; i<LIST_BUTTONS_MAX; i++) {
|
|
List_buttons[i].create(&Ui_window, "", 0, 0, 60, 30, 0, 1);
|
|
List_buttons[i].hide();
|
|
List_buttons[i].disable();
|
|
}
|
|
|
|
// set up sim_rooms for buttons so we draw the correct animation frame when a key is pressed
|
|
Buttons[gr_screen.res][SCROLL_UP_BUTTON].button.set_hotkey(KEY_PAGEUP);
|
|
Buttons[gr_screen.res][SCROLL_DOWN_BUTTON].button.set_hotkey(KEY_PAGEDOWN);
|
|
Buttons[gr_screen.res][COMMIT_BUTTON].button.set_hotkey(KEY_CTRLED | KEY_ENTER);
|
|
|
|
Background_bitmap = bm_load(Sim_filename[gr_screen.res]);
|
|
|
|
// load in help overlay bitmap
|
|
Sim_room_overlay_id = help_overlay_get_index(SIM_ROOM_OVERLAY);
|
|
help_overlay_set_state(Sim_room_overlay_id,gr_screen.res,0);
|
|
|
|
Scroll_offset = Selected_line = 0;
|
|
|
|
strcpy_s(Cur_campaign, Player->current_campaign);
|
|
if ( !mission_load_up_campaign() ) {
|
|
mission_campaign_next_mission();
|
|
} else {
|
|
Campaign.filename[0] = 0;
|
|
Campaign.num_missions = 0;
|
|
|
|
// don't display the popup in the sim room - first because there is already logic to prevent listing campaign missions,
|
|
// second because there's not much the player can do about it in the sim room, and third because displaying the popup
|
|
// clears the error code
|
|
}
|
|
|
|
Num_campaign_missions = 0;
|
|
Get_file_list_filter = sim_room_campaign_mission_filter;
|
|
|
|
mission_campaign_build_list(false, false); // no descs, no sorting
|
|
|
|
Hash_table_inited = 0;
|
|
if (build_campaign_mission_filename_hash_table()) {
|
|
Hash_table_inited = 1;
|
|
}
|
|
|
|
// if there is no campaign available then force the use of mode_missions only
|
|
if ( !strlen(Campaign.filename) )
|
|
Player->readyroom_listing_mode = MODE_MISSIONS;
|
|
|
|
// HACK
|
|
GR_MAYBE_CLEAR_RES(Background_bitmap);
|
|
if(Background_bitmap != -1){
|
|
gr_set_bitmap(Background_bitmap);
|
|
gr_bitmap(0, 0, GR_RESIZE_MENU);
|
|
}
|
|
Ui_window.draw();
|
|
gr_flip();
|
|
|
|
Get_file_list_filter = sim_room_standalone_mission_filter;
|
|
strcpy_s(wild_card, NOX("*"));
|
|
strcat_s(wild_card, FS_MISSION_FILE_EXT);
|
|
|
|
#ifndef NDEBUG
|
|
// Activate the check while we fill the mission list so that we don't potentially end up with dozens of string length warnings.
|
|
// We don't actually remove mismatched missions from the list here; that will happen in build_standalone_mission_list_do_frame().
|
|
bool dummy_buffer;
|
|
Lcl_unexpected_tstring_check = &dummy_buffer;
|
|
#endif
|
|
Num_standalone_missions = cf_get_file_list(MAX_MISSIONS, Mission_filenames, CF_TYPE_MISSIONS, wild_card, CF_SORT_NAME);
|
|
#ifndef NDEBUG
|
|
Lcl_unexpected_tstring_check = nullptr;
|
|
#endif
|
|
|
|
// set up slider with 0 items to start
|
|
Sim_room_slider.create(&Ui_window, Sim_room_slider_coords[gr_screen.res][X_COORD], Sim_room_slider_coords[gr_screen.res][Y_COORD], Sim_room_slider_coords[gr_screen.res][W_COORD], Sim_room_slider_coords[gr_screen.res][H_COORD], 0, Sim_room_slider_filename[gr_screen.res], &sim_room_scroll_screen_up, &sim_room_scroll_screen_down, &sim_room_scroll_capture);
|
|
|
|
Num_campaign_missions_with_info = Num_standalone_missions_with_info = Standalone_mission_names_inited = Campaign_mission_names_inited = 0;
|
|
Simroom_show_all = 0;
|
|
sim_room_build_listing();
|
|
|
|
// load special mission icons
|
|
sim_room_load_mission_icons();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// sim_room_close()
|
|
//
|
|
// Cleanup the sim_room assignment screen system. Called when GS_STATE_sim_room_SCREEN
|
|
// is left.
|
|
//
|
|
void sim_room_close()
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<Num_campaign_missions; i++) {
|
|
if (Campaign_missions[i]) {
|
|
vm_free(Campaign_missions[i]);
|
|
Campaign_missions[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if (Background_bitmap >= 0)
|
|
bm_release(Background_bitmap);
|
|
|
|
if (Standalone_mission_names_inited){
|
|
for (i=0; i<Num_standalone_missions; i++){
|
|
if (Standalone_mission_names[i] != NULL){
|
|
vm_free(Standalone_mission_names[i]);
|
|
Standalone_mission_names[i] = NULL;
|
|
}
|
|
Standalone_mission_flags[i] = 0;
|
|
}
|
|
}
|
|
|
|
if (Campaign_mission_names_inited) {
|
|
for (i=0; i<Campaign.num_missions; i++) {
|
|
if (Campaign_mission_names[i]) {
|
|
vm_free(Campaign_mission_names[i]);
|
|
Campaign_mission_names[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i=0; i<Num_standalone_missions; i++) {
|
|
vm_free(Mission_filenames[i]);
|
|
Mission_filenames[i] = NULL;
|
|
}
|
|
|
|
// free global Campaign_* list stuff
|
|
mission_campaign_free_list();
|
|
|
|
campaign_mission_hash_table_delete();
|
|
|
|
Ui_window.destroy();
|
|
common_free_interface_palette(); // restore game palette
|
|
Pilot.save_player();
|
|
|
|
// unload special mission icons
|
|
sim_room_unload_mission_icons();
|
|
}
|
|
|
|
//The mission list for the tech room is built each time the game state is entered
|
|
//so this method provides a way to do the exact same thing from scripting.
|
|
//It has some duplication of code but seemed the easiest way to provide similar
|
|
//functionality without a complete refactor.
|
|
void api_sim_room_build_mission_list(bool API_Access)
|
|
{
|
|
char wild_card[8];
|
|
|
|
Num_campaign_missions = 0;
|
|
Get_file_list_filter = sim_room_campaign_mission_filter;
|
|
|
|
mission_campaign_build_list(false, false);
|
|
|
|
Hash_table_inited = 0;
|
|
if (build_campaign_mission_filename_hash_table()) {
|
|
Hash_table_inited = 1;
|
|
}
|
|
|
|
Get_file_list_filter = sim_room_standalone_mission_filter;
|
|
strcpy_s(wild_card, NOX("*"));
|
|
strcat_s(wild_card, FS_MISSION_FILE_EXT);
|
|
|
|
Num_standalone_missions =
|
|
cf_get_file_list(MAX_MISSIONS, Mission_filenames, CF_TYPE_MISSIONS, wild_card, CF_SORT_NAME);
|
|
|
|
while (!build_standalone_mission_list_do_frame(API_Access)) {
|
|
}
|
|
while (!build_campaign_mission_list_do_frame(API_Access)) {
|
|
}
|
|
|
|
Num_campaign_missions_with_info = Num_standalone_missions_with_info = Standalone_mission_names_inited =
|
|
Campaign_mission_names_inited = 0;
|
|
|
|
int i;
|
|
for (i = 0; i < Num_campaign_missions; i++) {
|
|
if (Campaign_missions[i]) {
|
|
vm_free(Campaign_missions[i]);
|
|
Campaign_missions[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if (Standalone_mission_names_inited) {
|
|
for (i = 0; i < Num_standalone_missions; i++) {
|
|
if (Standalone_mission_names[i] != NULL) {
|
|
vm_free(Standalone_mission_names[i]);
|
|
Standalone_mission_names[i] = NULL;
|
|
}
|
|
Standalone_mission_flags[i] = 0;
|
|
}
|
|
}
|
|
|
|
if (Campaign_mission_names_inited) {
|
|
for (i = 0; i < Campaign.num_missions; i++) {
|
|
if (Campaign_mission_names[i]) {
|
|
vm_free(Campaign_mission_names[i]);
|
|
Campaign_mission_names[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < Num_standalone_missions; i++) {
|
|
vm_free(Mission_filenames[i]);
|
|
Mission_filenames[i] = NULL;
|
|
}
|
|
|
|
// free global Campaign_* list stuff
|
|
mission_campaign_free_list();
|
|
|
|
campaign_mission_hash_table_delete();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// sim_room_do_frame()
|
|
//
|
|
// Called once per frame to process user input for the sim_room Assignment Screen
|
|
//
|
|
void sim_room_do_frame(float /*frametime*/)
|
|
{
|
|
char buf[CF_MAX_PATHNAME_LENGTH + 4]; // must hold at most the Campaign.name and a file extension (4 chars)
|
|
int i, k, y, line;
|
|
int font_height = gr_get_font_height();
|
|
int select_tease_line = -1; // line mouse is down on, but won't be selected until button released
|
|
|
|
if ( help_overlay_active(Sim_room_overlay_id) ) {
|
|
Buttons[gr_screen.res][HELP_BUTTON].button.reset_status();
|
|
Ui_window.set_ignore_gadgets(1);
|
|
}
|
|
|
|
k = Ui_window.process() & ~KEY_DEBUGGED;
|
|
|
|
if ( (k > 0) || B1_JUST_RELEASED ) {
|
|
if ( help_overlay_active(Sim_room_overlay_id) ) {
|
|
help_overlay_set_state(Sim_room_overlay_id, gr_screen.res, 0);
|
|
Ui_window.set_ignore_gadgets(0);
|
|
k = 0;
|
|
}
|
|
}
|
|
|
|
if ( !help_overlay_active(Sim_room_overlay_id) ) {
|
|
Ui_window.set_ignore_gadgets(0);
|
|
}
|
|
|
|
switch (k) {
|
|
case KEY_DOWN: // scroll list down
|
|
sim_room_scroll_line_down();
|
|
break;
|
|
|
|
case KEY_UP: // scroll list up
|
|
sim_room_scroll_line_up();
|
|
break;
|
|
|
|
case KEY_ESC:
|
|
gameseq_post_event(GS_EVENT_MAIN_MENU);
|
|
break;
|
|
|
|
case KEY_CTRLED | KEY_UP:
|
|
sim_room_button_pressed(TECH_DATABASE_BUTTON);
|
|
break;
|
|
|
|
case KEY_CTRLED | KEY_DOWN:
|
|
sim_room_button_pressed(CUTSCENES_BUTTON);
|
|
break;
|
|
|
|
case KEY_TAB:
|
|
if (Player->readyroom_listing_mode == MODE_CAMPAIGNS)
|
|
Player->readyroom_listing_mode = MODE_MISSIONS;
|
|
else if ( strlen(Campaign.filename) )
|
|
Player->readyroom_listing_mode = MODE_CAMPAIGNS;
|
|
else
|
|
Player->readyroom_listing_mode = MODE_MISSIONS;
|
|
|
|
Selected_line = Scroll_offset = Simroom_show_all = 0;
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
sim_room_build_listing();
|
|
break;
|
|
|
|
case KEY_F2:
|
|
gamesnd_play_iface(InterfaceSounds::SWITCH_SCREENS);
|
|
gameseq_post_event(GS_EVENT_OPTIONS_MENU);
|
|
break;
|
|
|
|
case KEY_CTRLED | KEY_SHIFTED | KEY_S:
|
|
if (Player->readyroom_listing_mode == MODE_CAMPAIGNS) {
|
|
Simroom_show_all = !Simroom_show_all;
|
|
sim_room_reset_campaign_listing();
|
|
}
|
|
break;
|
|
} // end switch
|
|
|
|
for (i=0; i<NUM_BUTTONS; i++){
|
|
if (Buttons[gr_screen.res][i].button.pressed()){
|
|
if (sim_room_button_pressed(i)){
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i=0; i<LIST_BUTTONS_MAX; i++) {
|
|
if (List_buttons[i].is_mouse_on())
|
|
select_tease_line = i + Scroll_offset;
|
|
|
|
if (List_buttons[i].pressed()) {
|
|
Selected_line = i + Scroll_offset;
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
}
|
|
}
|
|
|
|
GR_MAYBE_CLEAR_RES(Background_bitmap);
|
|
if (Background_bitmap >= 0) {
|
|
gr_set_bitmap(Background_bitmap);
|
|
gr_bitmap(0, 0, GR_RESIZE_MENU);
|
|
}
|
|
|
|
Ui_window.draw();
|
|
|
|
for (i=TECH_DATABASE_BUTTON; i<=CREDITS_BUTTON; i++){
|
|
if (Buttons[gr_screen.res][i].button.button_down()){
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i > CREDITS_BUTTON){
|
|
Buttons[gr_screen.res][SIMULATOR_BUTTON].button.draw_forced(2);
|
|
}
|
|
|
|
if (!Buttons[gr_screen.res][CAMPAIGN_TAB].button.button_down() && !Buttons[gr_screen.res][MISSION_TAB].button.button_down()) {
|
|
if (Player->readyroom_listing_mode == MODE_CAMPAIGNS){
|
|
Buttons[gr_screen.res][CAMPAIGN_TAB].button.draw_forced(2);
|
|
} else if (Player->readyroom_listing_mode == MODE_MISSIONS){
|
|
Buttons[gr_screen.res][MISSION_TAB].button.draw_forced(2);
|
|
}
|
|
}
|
|
|
|
font::set_font(font::FONT1);
|
|
if (Player->readyroom_listing_mode == MODE_CAMPAIGNS) {
|
|
gr_set_color_fast(&Color_text_heading);
|
|
strcpy_s(buf, Campaign.name);
|
|
font::force_fit_string(buf, 255, list_w1);
|
|
gr_printf_menu(list_x1, Mission_list_coords[gr_screen.res][1], "%s", buf);
|
|
|
|
if (Campaign.filename[0] != '\0') {
|
|
sprintf(buf, NOX("%s%s"), Campaign.filename, FS_CAMPAIGN_FILE_EXT);
|
|
font::force_fit_string(buf, 255, list_w2);
|
|
gr_printf_menu(list_x2, Mission_list_coords[gr_screen.res][1], "%s", buf);
|
|
|
|
// blit the proper icons if necessary
|
|
auto full_name = cf_add_ext(Campaign.filename,FS_CAMPAIGN_FILE_EXT);
|
|
auto fb = game_find_builtin_mission(full_name);
|
|
if(fb != NULL){
|
|
// sim_room_blit_icons(0, Mission_list_coords[gr_screen.res][1], fb, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
line = Scroll_offset;
|
|
while (sim_room_line_query_visible(line) && (line - Scroll_offset < LIST_BUTTONS_MAX)) {
|
|
y = list_y + sim_room_lines[line].y - sim_room_lines[Scroll_offset].y;
|
|
|
|
if (sim_room_lines[line].type != READYROOM_LINE_CAMPAIGN) {
|
|
List_buttons[line - Scroll_offset].update_dimensions(list_x1, y, list_x2 + list_w2 - list_x1, font_height);
|
|
List_buttons[line - Scroll_offset].enable();
|
|
|
|
} else
|
|
List_buttons[line - Scroll_offset].disable();
|
|
|
|
if (line == Selected_line)
|
|
gr_set_color_fast(&Color_text_selected);
|
|
else if (line == select_tease_line)
|
|
gr_set_color_fast(&Color_text_subselected);
|
|
else
|
|
gr_set_color_fast(&Color_text_normal);
|
|
|
|
strcpy_s(buf, sim_room_lines[line].name);
|
|
font::force_fit_string(buf, 255, list_x1 + list_w1 - sim_room_lines[line].x);
|
|
gr_printf_menu(sim_room_lines[line].x, y, "%s", buf);
|
|
|
|
if (sim_room_lines[line].filename) {
|
|
strcpy_s(buf, sim_room_lines[line].filename);
|
|
font::force_fit_string(buf, 255, list_w2);
|
|
gr_printf_menu(list_x2, y, "%s", buf);
|
|
}
|
|
|
|
// blit additional icon information
|
|
sim_room_blit_icons(line, y);
|
|
|
|
line++;
|
|
}
|
|
|
|
i = line - Scroll_offset;
|
|
while (i < LIST_BUTTONS_MAX)
|
|
List_buttons[i++].disable();
|
|
|
|
// blit help overlay if active
|
|
help_overlay_maybe_blit(Sim_room_overlay_id, gr_screen.res);
|
|
|
|
gr_flip();
|
|
}
|
|
|
|
void sim_room_blit_icons(int line_index, int y_start, fs_builtin_mission *fb, int /*is_md*/)
|
|
{
|
|
int is_from_volition = 0;
|
|
|
|
// determine icon status
|
|
if(fb == NULL){
|
|
is_from_volition = (sim_room_lines[line_index].flags & READYROOM_FLAG_FROM_VOLITION) ? 1 : 0;
|
|
} else {
|
|
is_from_volition = (fb->flags & FSB_FROM_VOLITION) ? 1 : 0;
|
|
}
|
|
|
|
// if the line is flagged as a volition file
|
|
if(is_from_volition && (Mission_icon_bitmaps[MISSION_ICON_VOLITION] >= 0)){
|
|
gr_set_bitmap(Mission_icon_bitmaps[MISSION_ICON_VOLITION]);
|
|
gr_bitmap(Sim_volition_icon_x[gr_screen.res], y_start + MISSION_ICON_VOLITION_Y_OFFSET, GR_RESIZE_MENU);
|
|
}
|
|
}
|
|
|
|
/// Campaign room stuff below
|
|
int Cr_list_coords[GR_NUM_RESOLUTIONS][4] = {
|
|
{ // GR_640
|
|
47, 21, 565, 233
|
|
},
|
|
{ // GR_1024
|
|
64, 34, 916, 459
|
|
}
|
|
};
|
|
|
|
int Cr_info_coords[GR_NUM_RESOLUTIONS][4] = {
|
|
{ // GR_640
|
|
28, 267, 476, 103
|
|
},
|
|
{ // GR_1024
|
|
45, 427, 761, 165
|
|
},
|
|
};
|
|
|
|
#define CR_NUM_BUTTONS 6
|
|
|
|
#define CR_SCROLL_UP_BUTTON 0
|
|
#define CR_SCROLL_DOWN_BUTTON 1
|
|
#define CR_SCROLL_INFO_UP_BUTTON 2
|
|
#define CR_SCROLL_INFO_DOWN_BUTTON 3
|
|
#define CR_RESET_BUTTON 4
|
|
#define CR_COMMIT_BUTTON 5
|
|
|
|
#define MAX_INFO_LINES 20
|
|
|
|
#define MAX_INFO_LINE_LEN 256
|
|
|
|
ui_button_info Cr_buttons[GR_NUM_RESOLUTIONS][CR_NUM_BUTTONS] = {
|
|
{ // GR_640
|
|
ui_button_info("CAB_00", 2, 42, -1, -1, 0),
|
|
ui_button_info("CAB_01", 2, 89, -1, -1, 1),
|
|
ui_button_info("CAB_02", 2, 279, -1, -1, 2),
|
|
ui_button_info("CAB_03", 2, 325, -1, -1, 3),
|
|
ui_button_info("CAB_04", 579, 353, -1, -1, 4),
|
|
ui_button_info("CAB_05", 575, 434, -1, -1, 5),
|
|
},
|
|
{ // GR_1024
|
|
ui_button_info("2_CAB_00", 3, 68, -1, -1, 0),
|
|
ui_button_info("2_CAB_01", 3, 142, -1, -1, 1),
|
|
ui_button_info("2_CAB_02", 3, 446, -1, -1, 2),
|
|
ui_button_info("2_CAB_03", 3, 520, -1, -1, 3),
|
|
ui_button_info("2_CAB_04", 927, 565, -1, -1, 4),
|
|
ui_button_info("2_CAB_05", 920, 694, -1, -1, 5),
|
|
}
|
|
};
|
|
|
|
// text
|
|
#define CR_NUM_TEXT 3
|
|
UI_XSTR Cr_text[GR_NUM_RESOLUTIONS][CR_NUM_TEXT] = {
|
|
{ // GR_640
|
|
{ "Restart", 1403, 569, 326, UI_XSTR_COLOR_GREEN, -1, &Cr_buttons[0][CR_RESET_BUTTON].button },
|
|
{ "Campaign", 1404, 569, 337, UI_XSTR_COLOR_GREEN, -1, &Cr_buttons[0][CR_RESET_BUTTON].button },
|
|
{ "Select", 1409, 568, 413, UI_XSTR_COLOR_PINK, -1, &Cr_buttons[0][CR_COMMIT_BUTTON].button },
|
|
},
|
|
{ // GR_1024
|
|
{ "Restart", 1403, 922, 523, UI_XSTR_COLOR_GREEN, -1, &Cr_buttons[1][CR_RESET_BUTTON].button },
|
|
{ "Campaign", 1404, 922, 538, UI_XSTR_COLOR_GREEN, -1, &Cr_buttons[1][CR_RESET_BUTTON].button },
|
|
{ "Select", 1409, 921, 665, UI_XSTR_COLOR_PINK, -1, &Cr_buttons[1][CR_COMMIT_BUTTON].button },
|
|
}
|
|
};
|
|
|
|
static int Desc_scroll_offset;
|
|
static int Selected_campaign_index;
|
|
static int Active_campaign_index;
|
|
|
|
const char *Info_text_ptrs[MAX_INFO_LINES];
|
|
int Num_info_lines, Info_text_line_size[MAX_INFO_LINES];
|
|
|
|
void campaign_room_build_listing()
|
|
{
|
|
font::set_font(font::FONT1);
|
|
int font_height = gr_get_font_height();
|
|
int i, y;
|
|
|
|
Num_lines = y = 0;
|
|
|
|
for (i = 0; i < Num_campaigns; i++) {
|
|
if (Campaign_names[i] != NULL) {
|
|
// determine some extra information
|
|
int flags = 0;
|
|
auto fb = game_find_builtin_mission(Campaign_file_names[i]);
|
|
if (fb != NULL) {
|
|
if (fb->flags & FSB_FROM_VOLITION) {
|
|
flags |= READYROOM_FLAG_FROM_VOLITION;
|
|
}
|
|
}
|
|
|
|
sim_room_line_add(READYROOM_LINE_CAMPAIGN, Campaign_names[i], Campaign_file_names[i], Cr_list_coords[gr_screen.res][0], y, flags);
|
|
y += font_height + 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
void set_new_campaign_line(int n)
|
|
{
|
|
char *str;
|
|
|
|
Selected_campaign_index = n;
|
|
str = Campaign_descs[Selected_campaign_index];
|
|
Num_info_lines = 0;
|
|
if (str) {
|
|
Num_info_lines = split_str(str, Cr_info_coords[gr_screen.res][2], Info_text_line_size, Info_text_ptrs, MAX_INFO_LINES, MAX_INFO_LINE_LEN);
|
|
Assert(Num_info_lines >= 0);
|
|
}
|
|
|
|
Desc_scroll_offset = 0;
|
|
}
|
|
|
|
void campaign_room_scroll_info_up()
|
|
{
|
|
if (Desc_scroll_offset) {
|
|
Desc_scroll_offset--;
|
|
gamesnd_play_iface(InterfaceSounds::SCROLL);
|
|
|
|
} else
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
}
|
|
|
|
void campaign_room_scroll_info_down()
|
|
{
|
|
if ( (Num_info_lines - Desc_scroll_offset) * gr_get_font_height() > Cr_info_coords[gr_screen.res][3]) {
|
|
Desc_scroll_offset++;
|
|
gamesnd_play_iface(InterfaceSounds::SCROLL);
|
|
|
|
} else
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
}
|
|
|
|
void campaign_reset(const SCP_string& campaign_file)
|
|
{
|
|
// note: we do not toss all-time stats from player's performance in campaign up till now
|
|
mission_campaign_savefile_delete(campaign_file.c_str());
|
|
|
|
const int load_status = mission_campaign_load(campaign_file.c_str(), nullptr, nullptr, 1);
|
|
|
|
// see if we successfully loaded this campaign
|
|
if (load_status == 0) {
|
|
// Goober5000 - reinitialize tech database if needed
|
|
if ((Campaign.flags & CF_CUSTOM_TECH_DATABASE) || !stricmp(Campaign.filename, "freespace2")) {
|
|
// reset tech database to what's in the tables
|
|
tech_reset_to_default();
|
|
|
|
// write the savefile so that we don't later load a stale techroom
|
|
Pilot.save_savefile();
|
|
}
|
|
|
|
if (OnCampaignBeginHook->isActive()) {
|
|
OnCampaignBeginHook->run(scripting::hook_param_list(scripting::hook_param("Campaign", 's', Campaign.filename)));
|
|
}
|
|
}
|
|
|
|
mission_campaign_next_mission();
|
|
}
|
|
|
|
// returns: 0 = success, !0 = aborted or failed
|
|
int campaign_room_reset_campaign(int n)
|
|
{
|
|
int z = popup(PF_TITLE_BIG | PF_TITLE_RED, 2, POPUP_CANCEL, POPUP_OK, XSTR( "Warning\nThis will cause all progress in your\ncurrent campaign to be lost", 110));
|
|
if (z == 1) {
|
|
campaign_reset(Campaign_file_names[n]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void campaign_room_commit()
|
|
{
|
|
if (Selected_campaign_index < 0) {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
return;
|
|
}
|
|
|
|
campaign_select_campaign(Campaign_file_names[Selected_campaign_index]);
|
|
|
|
gameseq_post_event(GS_EVENT_MAIN_MENU);
|
|
gamesnd_play_iface(InterfaceSounds::COMMIT_PRESSED);
|
|
}
|
|
|
|
int campaign_room_button_pressed(int n)
|
|
{
|
|
switch (n) {
|
|
case CR_SCROLL_UP_BUTTON:
|
|
sim_room_scroll_screen_up();
|
|
break;
|
|
|
|
case CR_SCROLL_DOWN_BUTTON:
|
|
sim_room_scroll_screen_down();
|
|
break;
|
|
|
|
case CR_SCROLL_INFO_UP_BUTTON:
|
|
campaign_room_scroll_info_up();
|
|
break;
|
|
|
|
case CR_SCROLL_INFO_DOWN_BUTTON:
|
|
campaign_room_scroll_info_down();
|
|
break;
|
|
|
|
case CR_COMMIT_BUTTON:
|
|
campaign_room_commit();
|
|
break;
|
|
|
|
/*
|
|
case CR_HELP_BUTTON:
|
|
launch_context_help();
|
|
gamesnd_play_iface(SND_HELP_PRESSED);
|
|
break;
|
|
|
|
case CR_OPTIONS_BUTTON:
|
|
gamesnd_play_iface(SND_SWITCH_SCREENS);
|
|
gameseq_post_event(GS_EVENT_OPTIONS_MENU);
|
|
return 1;
|
|
*/
|
|
|
|
case CR_RESET_BUTTON:
|
|
if ( (Active_campaign_index < 0) || (Active_campaign_index >= Num_campaigns) )
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
else if (campaign_room_reset_campaign(Active_campaign_index))
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
else
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool campaign_build_campaign_list() {
|
|
const auto load_failed = mission_load_up_campaign();
|
|
if (!load_failed) {
|
|
mission_campaign_next_mission();
|
|
} else {
|
|
Campaign.filename[0] = 0;
|
|
Campaign.num_missions = 0;
|
|
|
|
mission_campaign_load_failure_popup();
|
|
}
|
|
|
|
// we need descriptions too, so "true" it
|
|
mission_campaign_build_list(true);
|
|
|
|
// convert to proper bool
|
|
return load_failed != 0;
|
|
}
|
|
|
|
void campaign_select_campaign(const SCP_string& campaign_file)
|
|
{
|
|
// new campaign selected?
|
|
if (stricmp(campaign_file.c_str(), Campaign.filename) != 0) {
|
|
strcpy_s(Player->current_campaign, campaign_file.c_str()); // track new campaign for player
|
|
|
|
// attempt to load the campaign
|
|
const int load_status = mission_campaign_load(campaign_file.c_str());
|
|
|
|
// see if we successfully loaded this campaign and it's at the beginning
|
|
if (load_status == 0 && Campaign.prev_mission < 0) {
|
|
// Goober5000 - reinitialize tech database if needed
|
|
if ((Campaign.flags & CF_CUSTOM_TECH_DATABASE) || !stricmp(Campaign.filename, "freespace2")) {
|
|
// reset tech database to what's in the tables
|
|
tech_reset_to_default();
|
|
|
|
// write the savefile so that we don't later load a stale techroom
|
|
Pilot.save_savefile();
|
|
}
|
|
|
|
if (OnCampaignBeginHook->isActive()) {
|
|
OnCampaignBeginHook->run(scripting::hook_param_list(scripting::hook_param("Campaign", 's', Campaign.filename)));
|
|
}
|
|
}
|
|
|
|
// that's all we need to do for now; the campaign loading status will be checked again when we try to load the
|
|
// campaign in the ready room
|
|
}
|
|
}
|
|
|
|
void campaign_room_init()
|
|
{
|
|
int i;
|
|
ui_button_info *b;
|
|
|
|
list_h = Mission_list_coords[gr_screen.res][3];
|
|
|
|
Ui_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0);
|
|
|
|
Campaign_background_bitmap_mask = bm_load(Campaign_mask_filename[gr_screen.res]);
|
|
if (Campaign_background_bitmap_mask < 0) {
|
|
Warning(LOCATION, "Error loading campaign room mask %s", Campaign_mask_filename[gr_screen.res]);
|
|
} else {
|
|
Ui_window.set_mask_bmap(Campaign_mask_filename[gr_screen.res]);
|
|
}
|
|
|
|
for (i=0; i<CR_NUM_BUTTONS; i++) {
|
|
b = &Cr_buttons[gr_screen.res][i];
|
|
|
|
b->button.create(&Ui_window, "", b->x, b->y, 60, 30, (i < 2), 1);
|
|
// set up callback for when a mouse first goes over a button
|
|
b->button.set_highlight_action(common_play_highlight_sound);
|
|
b->button.set_bmaps(b->filename);
|
|
b->button.link_hotspot(b->hotspot);
|
|
}
|
|
|
|
for (i=0; i<LIST_BUTTONS_MAX; i++) {
|
|
List_buttons[i].create(&Ui_window, "", 0, 0, 60, 30, 0, 1);
|
|
List_buttons[i].hide();
|
|
List_buttons[i].disable();
|
|
}
|
|
|
|
// add xstrs
|
|
for(i=0; i<CR_NUM_TEXT; i++){
|
|
Ui_window.add_XSTR(&Cr_text[gr_screen.res][i]);
|
|
}
|
|
|
|
// set up sim_rooms for buttons so we draw the correct animation frame when a key is pressed
|
|
Cr_buttons[gr_screen.res][CR_SCROLL_UP_BUTTON].button.set_hotkey(KEY_PAGEUP);
|
|
Cr_buttons[gr_screen.res][CR_SCROLL_DOWN_BUTTON].button.set_hotkey(KEY_PAGEDOWN);
|
|
Cr_buttons[gr_screen.res][CR_RESET_BUTTON].button.set_hotkey(KEY_DELETE);
|
|
Cr_buttons[gr_screen.res][CR_COMMIT_BUTTON].button.set_hotkey(KEY_CTRLED | KEY_ENTER);
|
|
// Cr_buttons[gr_screen.res][CR_HELP_BUTTON].button.set_hotkey(KEY_F2);
|
|
|
|
Background_bitmap = bm_load(Campaign_filename[gr_screen.res]);
|
|
if (Background_bitmap < 0) {
|
|
Warning(LOCATION, "Error loading campaign room background %s", Campaign_filename[gr_screen.res]);
|
|
}
|
|
|
|
// load in help overlay bitmap
|
|
Campaign_room_overlay_id = help_overlay_get_index(CAMPAIGN_ROOM_OVERLAY);
|
|
help_overlay_set_state(Campaign_room_overlay_id,gr_screen.res,0);
|
|
|
|
Desc_scroll_offset = Scroll_offset = 0;
|
|
|
|
// this stuff needs to happen before the mission_campaign_build_list() call
|
|
const auto load_failed = campaign_build_campaign_list();
|
|
|
|
campaign_room_build_listing();
|
|
|
|
Selected_campaign_index = Active_campaign_index = -1;
|
|
if (!load_failed) {
|
|
for (i=0; i<Num_campaigns; i++)
|
|
if (!stricmp(Campaign_file_names[i], Campaign.filename)) {
|
|
set_new_campaign_line(i);
|
|
Active_campaign_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Campaign_room_no_campaigns = false;
|
|
}
|
|
|
|
void campaign_room_close()
|
|
{
|
|
if (bm_is_valid(Background_bitmap))
|
|
bm_release(Background_bitmap);
|
|
|
|
// Reset info text pointers and size since they may contain pointers into the campaign description which will be
|
|
// freed soon.
|
|
memset(Info_text_line_size, 0, sizeof(Info_text_line_size));
|
|
memset(Info_text_ptrs, 0, sizeof(Info_text_ptrs));
|
|
|
|
// free the global Campaign_* list stuff
|
|
mission_campaign_free_list();
|
|
|
|
Ui_window.destroy();
|
|
|
|
if (bm_is_valid(Campaign_background_bitmap_mask)) {
|
|
bm_release(Campaign_background_bitmap_mask);
|
|
}
|
|
|
|
common_free_interface_palette(); // restore game palette
|
|
Pilot.save_player();
|
|
}
|
|
|
|
void campaign_room_do_frame(float /*frametime*/)
|
|
{
|
|
char buf[256];
|
|
char line_text[MAX_INFO_LINE_LEN];
|
|
int i, k, y, line;
|
|
int select_tease_line = -1; // line mouse is down on, but won't be selected until button released
|
|
|
|
// If we don't have a mask, we don't have enough data to do anything with this screen.
|
|
if (Campaign_background_bitmap_mask == -1) {
|
|
if ((Active_campaign_index < 0) || (Active_campaign_index >= Num_campaigns)) {
|
|
// Player is trying to use a regular pilot in the demo.
|
|
if (Num_campaigns < 1) {
|
|
// If there are no campaigns loaded, there's really nothing left to do.
|
|
popup_game_feature_not_in_demo();
|
|
return;
|
|
}
|
|
int select_default = popup(PF_USE_AFFIRMATIVE_ICON|PF_BODY_BIG, 2, "Exit", "Select first campaign", "Campaign Room only available in full version. However, you may select the first campaign found.");
|
|
if (select_default == 1) {
|
|
campaign_select_campaign(Campaign_file_names[0]);
|
|
}
|
|
} else {
|
|
int reset_campaign = popup(PF_USE_AFFIRMATIVE_ICON|PF_BODY_BIG, 2, "Exit", "Restart Campaign", "Campaign Room only available in full version. However, you may restart the campaign.");
|
|
if (reset_campaign == 1) {
|
|
// Rather than hardcoding the reset, let's reuse this.
|
|
campaign_room_reset_campaign(Active_campaign_index);
|
|
}
|
|
}
|
|
gameseq_post_event(GS_EVENT_MAIN_MENU);
|
|
return;
|
|
}
|
|
|
|
if ( help_overlay_active(Campaign_room_overlay_id) ) {
|
|
// Cr_buttons[gr_screen.res][CR_HELP_BUTTON].button.reset_status();
|
|
Ui_window.set_ignore_gadgets(1);
|
|
}
|
|
|
|
k = Ui_window.process() & ~KEY_DEBUGGED;
|
|
|
|
if ( (k > 0) || B1_JUST_RELEASED ) {
|
|
if ( help_overlay_active(Campaign_room_overlay_id) ) {
|
|
help_overlay_set_state(Campaign_room_overlay_id, gr_screen.res, 0);
|
|
Ui_window.set_ignore_gadgets(0);
|
|
k = 0;
|
|
}
|
|
}
|
|
|
|
if ( !help_overlay_active(Campaign_room_overlay_id) ) {
|
|
Ui_window.set_ignore_gadgets(0);
|
|
}
|
|
|
|
switch (k) {
|
|
case KEY_DOWN: // scroll list down
|
|
if (Selected_campaign_index < Num_campaigns - 1) {
|
|
set_new_campaign_line(Selected_campaign_index + 1);
|
|
gamesnd_play_iface(InterfaceSounds::SCROLL);
|
|
|
|
} else
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
|
|
break;
|
|
|
|
case KEY_UP: // scroll list up
|
|
if (Selected_campaign_index < 0)
|
|
Selected_campaign_index = 1;
|
|
|
|
if (Selected_campaign_index) {
|
|
set_new_campaign_line(Selected_campaign_index - 1);
|
|
gamesnd_play_iface(InterfaceSounds::SCROLL);
|
|
|
|
} else
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
|
|
break;
|
|
|
|
case KEY_ESC:
|
|
gameseq_post_event(GS_EVENT_MAIN_MENU);
|
|
break;
|
|
} // end switch
|
|
|
|
for (i=0; i<CR_NUM_BUTTONS; i++){
|
|
if (Cr_buttons[gr_screen.res][i].button.pressed()){
|
|
if (campaign_room_button_pressed(i)){
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i=0; i<LIST_BUTTONS_MAX; i++) {
|
|
if (List_buttons[i].is_mouse_on()){
|
|
select_tease_line = i + Scroll_offset;
|
|
}
|
|
|
|
if (List_buttons[i].pressed()) {
|
|
set_new_campaign_line(i + Scroll_offset);
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
}
|
|
}
|
|
|
|
GR_MAYBE_CLEAR_RES(Background_bitmap);
|
|
if (Background_bitmap >= 0) {
|
|
gr_set_bitmap(Background_bitmap);
|
|
gr_bitmap(0, 0, GR_RESIZE_MENU);
|
|
}
|
|
|
|
Ui_window.draw();
|
|
|
|
font::set_font(font::FONT1);
|
|
int font_height = gr_get_font_height();
|
|
line = Scroll_offset;
|
|
while (sim_room_line_query_visible(line) && (line - Scroll_offset < LIST_BUTTONS_MAX)) {
|
|
y = Cr_list_coords[gr_screen.res][1] + sim_room_lines[line].y - sim_room_lines[Scroll_offset].y;
|
|
|
|
List_buttons[line - Scroll_offset].update_dimensions(Cr_list_coords[gr_screen.res][0], y, Cr_list_coords[gr_screen.res][2], font_height);
|
|
List_buttons[line - Scroll_offset].enable();
|
|
|
|
if (!stricmp(sim_room_lines[line].filename, Campaign.filename)) {
|
|
gr_set_color_fast(&Color_white);
|
|
i = y + font_height / 2 - 1;
|
|
gr_circle(Cr_list_coords[gr_screen.res][0] - 6, i, 5, GR_RESIZE_MENU);
|
|
|
|
gr_set_color_fast(&Color_bright_white);
|
|
gr_line(Cr_list_coords[gr_screen.res][0] - 10, i, Cr_list_coords[gr_screen.res][0] - 8, i, GR_RESIZE_MENU);
|
|
gr_line(Cr_list_coords[gr_screen.res][0] - 6, i - 4, Cr_list_coords[gr_screen.res][0] - 6, i - 2, GR_RESIZE_MENU);
|
|
gr_line(Cr_list_coords[gr_screen.res][0] - 4, i, Cr_list_coords[gr_screen.res][0] - 2, i, GR_RESIZE_MENU);
|
|
gr_line(Cr_list_coords[gr_screen.res][0] - 6, i + 2, Cr_list_coords[gr_screen.res][0] - 6, i + 4, GR_RESIZE_MENU);
|
|
}
|
|
|
|
if (line == Selected_campaign_index)
|
|
gr_set_color_fast(&Color_text_selected);
|
|
else if (line == select_tease_line)
|
|
gr_set_color_fast(&Color_text_subselected);
|
|
else
|
|
gr_set_color_fast(&Color_text_normal);
|
|
|
|
strcpy_s(buf, sim_room_lines[line].name);
|
|
font::force_fit_string(buf, 255, Cr_list_coords[gr_screen.res][0] + Cr_list_coords[gr_screen.res][2] - sim_room_lines[line].x);
|
|
gr_printf_menu(sim_room_lines[line].x, y, "%s", buf);
|
|
line++;
|
|
}
|
|
|
|
i = line - Scroll_offset;
|
|
while (i < LIST_BUTTONS_MAX)
|
|
List_buttons[i++].disable();
|
|
|
|
y = 0;
|
|
i = Desc_scroll_offset;
|
|
gr_set_color_fast(&Color_text_normal);
|
|
|
|
while (y + font_height <= Cr_info_coords[gr_screen.res][3]) {
|
|
if (i >= Num_info_lines)
|
|
break;
|
|
|
|
Assert(Info_text_line_size[i] < MAX_INFO_LINE_LEN);
|
|
strncpy(line_text, Info_text_ptrs[i], Info_text_line_size[i]);
|
|
line_text[Info_text_line_size[i]] = 0;
|
|
drop_white_space(line_text);
|
|
gr_string(Cr_info_coords[gr_screen.res][0], Cr_info_coords[gr_screen.res][1] + y, line_text, GR_RESIZE_MENU);
|
|
y += font_height;
|
|
i++;
|
|
}
|
|
|
|
if (Num_campaigns < 1) {
|
|
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR( "No campaigns are available!", 1613));
|
|
Campaign_room_no_campaigns = true;
|
|
gameseq_post_event(GS_EVENT_MAIN_MENU);
|
|
}
|
|
|
|
// blit help overlay if active
|
|
help_overlay_maybe_blit(Campaign_room_overlay_id, gr_screen.res);
|
|
|
|
gr_flip();
|
|
}
|
|
|
|
void sim_room_load_mission_icons()
|
|
{
|
|
int idx;
|
|
|
|
// load all bitmaps
|
|
for(idx=0; idx<NUM_MISSION_ICONS; idx++){
|
|
Mission_icon_bitmaps[idx] = -1;
|
|
Mission_icon_bitmaps[idx] = bm_load(Mission_icon_bitmap_filenames[idx]);
|
|
}
|
|
}
|
|
|
|
void sim_room_unload_mission_icons()
|
|
{
|
|
int idx;
|
|
|
|
// unload all bitmaps
|
|
for(idx=0; idx<NUM_MISSION_ICONS; idx++){
|
|
if(Mission_icon_bitmaps[idx] >= 0){
|
|
// don't bm_release() here, used in multiple places - taylor
|
|
bm_unload(Mission_icon_bitmaps[idx]);
|
|
Mission_icon_bitmaps[idx] = -1;
|
|
}
|
|
}
|
|
}
|