Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion code/mission/missionparse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7015,7 +7015,11 @@ bool post_process_mission(mission *pm)
return false;
}
Player_start_shipnum = player_start_entry->shipnum;
Assert( Player_start_shipnum != -1 );
if (Player_start_shipnum < 0) {
// the ship exists in the mission file but its first wave was never created
Warning(LOCATION, "Player start ship '%s' was not created at mission start! If the wing containing the player has a custom arrival cue, the cue must evaluate to true at the beginning of the mission. Similarly, if the wing arrives from a hangar bay, the mothership must be present at the beginning of the mission.", Player_start_shipname);
return false;
}
Player_start_pobject = player_start_entry->p_objp();
Assert( Player_start_pobject != NULL );

Expand Down
29 changes: 26 additions & 3 deletions fred2/fredview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3301,9 +3301,32 @@ int CFREDView::global_error_check_player_wings(int multi)
}
}

// // player's wing must have a true arrival
// free_sexp2(Wings[z].arrival_cue);
// Wings[z].arrival_cue = Locked_sexp_true;
// The wing containing the player start must be present at mission start, so a custom arrival cue
// is only useful for controlling the arrival of subsequent waves. Flag a single-wave wing with a
// custom cue as a potential issue, but never change the cue itself.
if (Error_checker_checks_potential_issues || Error_checker_checks_potential_issues_once)
{
for (i=0; i<MAX_WINGS; i++)
{
if (!Wings[i].wave_count || Wings[i].num_waves > 1)
continue;
if (Wings[i].arrival_cue == Locked_sexp_true)
continue;

bool contains_player_start = false;
for (z=0; z<Wings[i].wave_count; z++)
if (Objects[Ships[Wings[i].ship_index[z]].objnum].type == OBJ_START)
contains_player_start = true;

if (contains_player_start)
{
SCP_string issue_buf;
sprintf(issue_buf, "Potential issue detected in wing %s:\n\nThis wing contains a player start and a custom arrival cue, but only one wave. The arrival cue must evaluate to true at mission start, or the player will not be created.", Wings[i].name);
if (Fred_main_wnd->MessageBox(issue_buf.c_str(), "Warning", MB_OKCANCEL | MB_ICONINFORMATION) != IDOK)
return 1;
}
}
}

// Check to be sure that any player wing doesn't have > 1 wave for multiplayer
if ( multi )
Expand Down
55 changes: 37 additions & 18 deletions fred2/wing_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ BEGIN_MESSAGE_MAP(wing_editor, CDialog)
ON_BN_CLICKED(IDC_CUSTOM_WARPOUT_PARAMS, OnBnClickedCustomWarpoutParams)
ON_BN_CLICKED(IDC_WING_FORMATION_ALIGN, OnWingFormationAlign)
ON_EN_CHANGE(IDC_WING_NAME, OnChangeWingName)
ON_EN_CHANGE(IDC_WING_WAVES, OnChangeWingWaves)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

Expand Down Expand Up @@ -273,7 +274,7 @@ void wing_editor::OnClose()
// initialize everything that update_data_safe() saves.
void wing_editor::initialize_data_safe(int full_update)
{
int i, enable = TRUE, player_wing = 0, player_enabled = 1;
int i, enable = TRUE, player_wing = 0, player_enabled = 1, waves_enabled = 1;
CComboBox *arrival_box, *departure_box;

nprintf(("Fred routing", "Wing dialog load safe\n"));
Expand Down Expand Up @@ -314,40 +315,41 @@ void wing_editor::initialize_data_safe(int full_update)
m_same_arrival_warp_when_docked = FALSE;
m_same_departure_warp_when_docked = FALSE;
m_no_dynamic = 0;
player_enabled = enable = FALSE;
waves_enabled = player_enabled = enable = FALSE;

} else {
CComboBox *ptr;

if ((Player_start_shipnum >= 0) && (Player_start_shipnum < MAX_SHIPS) && (Ships[Player_start_shipnum].objnum >= 0))
if (Ships[Player_start_shipnum].wingnum == cur_wing)
player_wing = 1;

if (The_mission.game_type & MISSION_TYPE_MULTI)
{
if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS)
{
for (i=0; i<MAX_TVT_WINGS; i++)
{
if (cur_wing == TVT_wings[i])
player_enabled = 0;
waves_enabled = player_enabled = 0;
}
}
else
{
for (i=0; i<MAX_STARTING_WINGS; i++)
{
if (cur_wing == Starting_wings[i])
player_enabled = 0;
waves_enabled = player_enabled = 0;
}
}
}
else
{
if (cur_wing == Ships[Player_start_shipnum].wingnum)
// single-player wings, including the player wing, can have multiple waves
if (player_wing)
player_enabled = 0;
}

if ((Player_start_shipnum >= 0) && (Player_start_shipnum < MAX_SHIPS) && (Ships[Player_start_shipnum].objnum >= 0))
if (Ships[Player_start_shipnum].wingnum == cur_wing)
player_wing = 1;

m_wing_squad_filename = _T(Wings[cur_wing].wing_squad_filename);
m_wing_display_name = Wings[cur_wing].has_display_name() ? Wings[cur_wing].get_display_name() : "<none>";
m_special_ship = Wings[cur_wing].special_ship;
Expand Down Expand Up @@ -410,10 +412,7 @@ void wing_editor::initialize_data_safe(int full_update)
m_departure_target = departure_box->FindStringExact(-1, Ships[m_departure_target].ship_name);

m_departure_delay = Wings[cur_wing].departure_delay;
if (player_wing)
m_arrival_tree.load_tree(Locked_sexp_true);
else
m_arrival_tree.load_tree(Wings[cur_wing].arrival_cue);
m_arrival_tree.load_tree(Wings[cur_wing].arrival_cue);

m_departure_tree.load_tree(Wings[cur_wing].departure_cue, "false");
m_hotkey = Wings[cur_wing].hotkey+1;
Expand Down Expand Up @@ -452,11 +451,11 @@ void wing_editor::initialize_data_safe(int full_update)
GetDlgItem(IDC_WING_NAME)->EnableWindow(enable);
GetDlgItem(IDC_WING_SQUAD_LOGO_BUTTON)->EnableWindow(enable);
GetDlgItem(IDC_WING_SPECIAL_SHIP)->EnableWindow(enable);
GetDlgItem(IDC_WING_WAVES)->EnableWindow(player_enabled);
GetDlgItem(IDC_WING_WAVE_THRESHOLD)->EnableWindow(player_enabled);
GetDlgItem(IDC_WING_WAVES)->EnableWindow(waves_enabled);
GetDlgItem(IDC_WING_WAVE_THRESHOLD)->EnableWindow(waves_enabled);
GetDlgItem(IDC_DISBAND_WING)->EnableWindow(enable);
GetDlgItem(IDC_SPIN_WAVES)->EnableWindow(player_enabled);
GetDlgItem(IDC_SPIN_WAVE_THRESHOLD)->EnableWindow(player_enabled);
GetDlgItem(IDC_SPIN_WAVES)->EnableWindow(waves_enabled);
GetDlgItem(IDC_SPIN_WAVE_THRESHOLD)->EnableWindow(waves_enabled);

GetDlgItem(IDC_WING_FORMATION)->EnableWindow(enable);
GetDlgItem(IDC_WING_FORMATION_ALIGN)->EnableWindow(enable);
Expand Down Expand Up @@ -496,8 +495,10 @@ void wing_editor::initialize_data_safe(int full_update)
GetDlgItem(IDC_CUSTOM_WARPOUT_PARAMS)->EnableWindow(enable);
}

// the player wing must be present at mission start, so its arrival cue is only editable
// if it has multiple waves (where the cue governs the arrival of subsequent waves)
if (player_wing)
GetDlgItem(IDC_ARRIVAL_TREE)->EnableWindow(0);
GetDlgItem(IDC_ARRIVAL_TREE)->EnableWindow(m_waves > 1);
else
GetDlgItem(IDC_ARRIVAL_TREE)->EnableWindow(enable);

Expand Down Expand Up @@ -1403,3 +1404,21 @@ void wing_editor::OnChangeWingName()
// sync the variable to the edit box
UpdateData(FALSE);
}

void wing_editor::OnChangeWingWaves()
{
if (!GetSafeHwnd() || cur_wing < 0)
return;

// only the player wing's arrival cue is gated on the number of waves
if ((Player_start_shipnum < 0) || (Player_start_shipnum >= MAX_SHIPS) || (Ships[Player_start_shipnum].objnum < 0))
return;
if (Ships[Player_start_shipnum].wingnum != cur_wing)
return;

// read the control directly; this notification can arrive in the middle of a DDX update,
// so calling UpdateData() here would not be safe
CString str;
GetDlgItem(IDC_WING_WAVES)->GetWindowText(str);
GetDlgItem(IDC_ARRIVAL_TREE)->EnableWindow(atoi(str) > 1);
}
1 change: 1 addition & 0 deletions fred2/wing_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class wing_editor : public CDialog
afx_msg void OnBnClickedCustomWarpoutParams();
afx_msg void OnWingFormationAlign();
afx_msg void OnChangeWingName();
afx_msg void OnChangeWingWaves();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

Expand Down
2 changes: 1 addition & 1 deletion qtfred/src/mission/EditorWing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ bool Editor::wing_is_player_wing(int wing)
}
// Single player wing check
} else {
if (Player_start_shipnum >= 0 && Player_start_shipnum < MAX_SHIPS) {
if (Player_start_shipnum >= 0 && Player_start_shipnum < MAX_SHIPS && Ships[Player_start_shipnum].objnum >= 0) {
const int pw = Ships[Player_start_shipnum].wingnum;
return pw >= 0 && pw == wing;
}
Expand Down
46 changes: 25 additions & 21 deletions qtfred/src/ui/dialogs/WingEditorDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "ui_WingEditorDialog.h"

#include <globalincs/globals.h>
#include <mission/missionparse.h>
#include <ship/ship.h>
#include <ui/util/SignalBlockers.h>
#include <ui/util/ImageRenderer.h>
Expand Down Expand Up @@ -218,36 +219,39 @@ void WingEditorDialog::enableOrDisableControls()
const bool containsPlayerStart = _model->containsPlayerStart();
const bool allFighterBombers = _model->wingAllFighterBombers();

// Waves / Threshold: enabled only if NOT a player wing and all members are fighter/bombers
const bool wavesEnabled = (!isPlayerWing) && allFighterBombers;
// Waves / Threshold: locked for multiplayer starting/TVT wings, which must have exactly one
// wave; single-player wings, including the player wing, can have multiple waves. Also
// requires all members to be fighter/bombers.
const bool wavesEnabled = !(isPlayerWing && (The_mission.game_type & MISSION_TYPE_MULTI)) && allFighterBombers;
ui->numWavesSpinBox->setEnabled(wavesEnabled);
ui->waveThresholdSpinBox->setEnabled(wavesEnabled);

// Arrival section: disabled for starting wings (SP player wing or MP starting wing)
const bool arrivalEditable = !isPlayerWing;
ui->arrivalLocationCombo->setEnabled(arrivalEditable);
ui->arrivalDelaySpinBox->setEnabled(arrivalEditable);
ui->minDelaySpinBox->setEnabled(arrivalEditable);
ui->maxDelaySpinBox->setEnabled(arrivalEditable);
if (!arrivalEditable) {
clearArrivalFields();
}
// Arrival delay is locked for starting wings (SP player wing or MP starting wing), since
// they must be present at mission start; the arrival location remains editable because it
// governs where subsequent waves arrive from
const bool arrivalDelayEditable = !isPlayerWing;
ui->arrivalDelaySpinBox->setEnabled(arrivalDelayEditable);
ui->minDelaySpinBox->setEnabled(arrivalDelayEditable);
ui->maxDelaySpinBox->setEnabled(arrivalDelayEditable);

// Arrival target/distance and path/custom buttons
// Arrival location, target/distance, and path/custom buttons
const bool arrivalIsDockBay = _model->arrivalIsDockBay();
const bool arrivalNeedsTarget = _model->arrivalNeedsTarget();

ui->arrivalTargetCombo->setEnabled(arrivalEditable && arrivalNeedsTarget);
ui->arrivalDistanceSpinBox->setEnabled(arrivalEditable && _model->arrivalNeedsDistance());
ui->restrictArrivalPathsButton->setEnabled(arrivalEditable && arrivalIsDockBay);
ui->customWarpinButton->setEnabled(arrivalEditable && !arrivalIsDockBay);
ui->arrivalLocationCombo->setEnabled(true);
ui->arrivalTargetCombo->setEnabled(arrivalNeedsTarget);
ui->arrivalDistanceSpinBox->setEnabled(_model->arrivalNeedsDistance());
ui->restrictArrivalPathsButton->setEnabled(arrivalIsDockBay);
ui->customWarpinButton->setEnabled(!arrivalIsDockBay);

// Arrival cue tree: lock when the wing actually contains Player-1 start (retail behavior)
ui->arrivalTree->setEnabled(!containsPlayerStart);
// Arrival cue tree: the wing containing the Player-1 start must be present at mission
// start, so its cue is only editable if it has multiple waves (where the cue governs
// the arrival of subsequent waves)
ui->arrivalTree->setEnabled(!containsPlayerStart || _model->getNumberOfWaves() > 1);

// Also tie the "no arrival warp" checkboxes to whether arrival is editable
ui->noArrivalWarpCheckBox->setEnabled(arrivalEditable);
ui->noArrivalWarpAdjustCheckbox->setEnabled(arrivalEditable);
// The "no arrival warp" checkboxes are always editable
ui->noArrivalWarpCheckBox->setEnabled(true);
ui->noArrivalWarpAdjustCheckbox->setEnabled(true);

// Departure side: never gated by starting-wing rule
ui->departureLocationCombo->setEnabled(true);
Expand Down
20 changes: 20 additions & 0 deletions qtfred/src/ui/util/ErrorChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,26 @@ int ErrorChecker::checkPlayerWings() {
Starting_wing_names[0], TVT_wing_names[0]);
}

// The wing containing the player start must be present at mission start, so a custom arrival cue
// is only useful for controlling the arrival of subsequent waves. Flag a single-wave wing with a
// custom cue as a potential issue, but never change the cue itself.
for (auto &w : Wings) {
if (!w.wave_count || w.num_waves > 1)
continue;
if (w.arrival_cue == Locked_sexp_true)
continue;

bool contains_player_start = false;
for (int j = 0; j < w.wave_count; j++)
if (Objects[Ships[w.ship_index[j]].objnum].type == OBJ_START)
contains_player_start = true;

if (contains_player_start) {
potential("Wing \"%s\" contains a player start and a custom arrival cue, but only one wave. The arrival cue must evaluate to true at mission start, or the player will not be created.",
w.name);
}
}

auto checkMixedSpecies = [this](int w) {
int species = -1;
bool mixed = false;
Expand Down
Loading