mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
* fix FastDebug QtFRED linking the debug CRT FastDebug builds in the DLL-runtime tree (used whenever qtFRED is enabled) compiled with /MDd and, since #7450, linked Qt's debug libraries. Both import ucrtbased.dll, MSVCP140D.dll and VCRUNTIME140D.dll, which Microsoft does not license for redistribution, so end users could not run the FastDebug qtFRED shipped in nightlies and releases. Static Qt is ruled out by the LGPL, so the only shippable shape is the release CRT plus release Qt. On MSVC, _DEBUG is the CRT's own contract: /MTd and /MDd define it themselves, and the STL headers select msvcprtd.lib and _ITERATOR_DEBUG_LEVEL=2 from it. Stop force-defining it for FastDebug on MSVC; the engine keys its debug behaviour on NDEBUG, and the few _DEBUG guards in code/ are switched to NDEBUG so they keep working. The static tree is unchanged: /MTd still defines _DEBUG there. Fixes #7915 Co-Authored-By: Claude Fable 5.1 <[email protected]> * derive the MSVC runtime choice once The "DLL runtime or not" condition was spelled out in the toolchain and twice in fred2 with "keep in sync" comments, and gtest_force_shared_crt and antlr4's WITH_STATIC_CRT had already drifted: they only looked at MSVC_USE_RUNTIME_DLL, so a qtFRED tree with tests enabled would have asked both for the static CRT. That is harmless today only because our flag strings carry no literal /MD for them to rewrite. Compute FSO_MSVC_RUNTIME_DLL once in toolchain-msvc.cmake and use it everywhere. InstallRequiredSystemLibraries deliberately stays keyed on MSVC_USE_RUNTIME_DLL: the qtFRED deploy ships no compiler runtime. Co-Authored-By: Claude Fable 5.1 <[email protected]> * remove FastDebug runtime leftovers The /MD -> /MDd substitution on the FastDebug flag strings has done nothing since the runtime moved to CMAKE_MSVC_RUNTIME_LIBRARY: the Release flags it copies contain no runtime switch. The commented-out _ITERATOR_DEBUG_LEVEL=0 lines for gtest and the OpenXR loader belong to the scheme #7450 removed from the toolchain. Co-Authored-By: Claude Fable 5.1 <[email protected]> * use the release CRT for all FastDebug builds Microsoft's documentation marks the static debug libraries (libcmtd, libucrtd, libvcruntimed) "Not redistributable" and states that debug versions of an application may only be copied to machines used for testing, and the license grants distribution rights only for code on the REDIST list, which contains no debug library in any form. So the statically linked FastDebug game and FRED2 are no more shippable than the /MDd qtFRED was. Link every FastDebug build against the release runtime. The engine's debug behaviour keys on NDEBUG, which FastDebug still leaves undefined, so asserts, warnings and mprintf are unaffected; only the debug CRT's heap checking goes, and that remains available in Debug builds. FRED2's static MFC library follows the CRT: the debug variant requires the debug runtime. Co-Authored-By: Claude Fable 5.1 <[email protected]> * keep the debug icon, version flag and FRED2 asserts without _DEBUG The resource compiler only ever sees the explicit definitions, so with _DEBUG no longer defined for FastDebug the game lost its debug icon and both editors lost the VS_FF_DEBUG version flag. Key those on NDEBUG, which FastDebug still leaves undefined. FRED2 also used MFC's ASSERT, VERIFY, ASSERT_VALID and TRACE0, which are all compiled out without _DEBUG. Convert them to the FSO equivalents FRED2 already uses nearly everywhere else: Assert, Verify, a null check in place of ASSERT_VALID (AssertValid only exists in debug MFC), and mprintf. The AssertValid/Dump overrides and DEBUG_NEW stay under _DEBUG, as they must. Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]>
561 lines
14 KiB
C++
561 lines
14 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 "stdafx.h"
|
|
#include "FRED.h"
|
|
#include "CampaignTreeWnd.h"
|
|
#include "CampaignEditorDlg.h"
|
|
#include "CampaignTreeView.h"
|
|
#include "Management.h"
|
|
#include "MainFrm.h"
|
|
#include "FREDView.h"
|
|
#include "missioneditor/campaignsave.h"
|
|
#include "InitialShips.h"
|
|
#include "mission/missionparse.h"
|
|
#include "parse/parselo.h"
|
|
|
|
#ifdef _DEBUG
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
int Mission_filename_cb_format;
|
|
int Campaign_modified = 0;
|
|
campaign_tree_wnd *Campaign_wnd = NULL;
|
|
|
|
IMPLEMENT_DYNCREATE(campaign_tree_wnd, CFrameWnd)
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// campaign_tree_wnd
|
|
|
|
campaign_tree_wnd::campaign_tree_wnd()
|
|
: g_err(0)
|
|
{
|
|
}
|
|
|
|
campaign_tree_wnd::~campaign_tree_wnd()
|
|
{
|
|
}
|
|
|
|
BEGIN_MESSAGE_MAP(campaign_tree_wnd, CFrameWnd)
|
|
//{{AFX_MSG_MAP(campaign_tree_wnd)
|
|
ON_UPDATE_COMMAND_UI(ID_CPGN_FILE_OPEN, OnUpdateCpgnFileOpen)
|
|
ON_COMMAND(ID_CPGN_FILE_OPEN, OnCpgnFileOpen)
|
|
ON_WM_DESTROY()
|
|
ON_COMMAND(ID_CPGN_FILE_SAVE, OnCpgnFileSave)
|
|
ON_COMMAND(ID_CPGN_FILE_SAVE_AS, OnCpgnFileSaveAs)
|
|
ON_COMMAND(ID_CPGN_FILE_NEW, OnCpgnFileNew)
|
|
ON_COMMAND(ID_CLOSE, OnClose2)
|
|
ON_COMMAND(ID_ERROR_CHECKER, OnErrorChecker)
|
|
ON_WM_CLOSE()
|
|
ON_COMMAND(ID_INITIAL_SHIPS, OnInitialShips)
|
|
ON_COMMAND(ID_INITIAL_WEAPONS, OnInitialWeapons)
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// campaign_tree_wnd message handlers
|
|
|
|
BOOL campaign_tree_wnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext)
|
|
{
|
|
CSize s;
|
|
|
|
LoadAccelTable("IDR_ACC_CAMPAIGN");
|
|
Mission_filename_cb_format = RegisterClipboardFormat("Mission Filename");
|
|
Campaign_modified = 0;
|
|
clear_mission(true);
|
|
|
|
// create a splitter with 1 row, 2 columns
|
|
if (!m_splitter.CreateStatic(this, 1, 2))
|
|
{
|
|
mprintf(("Failed to CreateStaticSplitter\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
// add the first splitter pane - the campaign input form in column 0
|
|
if (!m_splitter.CreateView(0, 0, RUNTIME_CLASS(campaign_editor), CSize(0, 0), pContext))
|
|
{
|
|
mprintf(("Failed to create first pane\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
// add the second splitter pane - the campaign tree view in column 1
|
|
if (!m_splitter.CreateView(0, 1, RUNTIME_CLASS(campaign_tree_view), CSize(240, 100), pContext))
|
|
{
|
|
mprintf(("Failed to create second pane\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
Campaign_tree_formp = (campaign_editor *) m_splitter.GetPane(0, 0);
|
|
Campaign_tree_viewp = (campaign_tree_view *) m_splitter.GetPane(0, 1);
|
|
s = Campaign_tree_formp->GetTotalSize();
|
|
m_splitter.SetColumnInfo(0, s.cx, 0);
|
|
m_splitter.SetColumnInfo(1, 0, 0);
|
|
m_splitter.RecalcLayout();
|
|
|
|
// activate the input view
|
|
SetActiveView(Campaign_tree_formp);
|
|
OnCpgnFileNew();
|
|
Fred_main_wnd->EnableWindow(FALSE);
|
|
return TRUE;
|
|
}
|
|
|
|
void campaign_tree_wnd::OnUpdateCpgnFileOpen(CCmdUI* pCmdUI)
|
|
{
|
|
pCmdUI->Enable();
|
|
}
|
|
|
|
void campaign_tree_wnd::OnCpgnFileOpen()
|
|
{
|
|
CString name, full_path;
|
|
|
|
if (Campaign_modified)
|
|
if (save_modified())
|
|
return;
|
|
|
|
CFileDialog dlg(TRUE, "fc2", NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "FreeSpace Campaign files (*.fc2)|*.fc2||", this);
|
|
if (dlg.DoModal() == IDOK)
|
|
{
|
|
name = dlg.GetFileName();
|
|
if (strlen(name) > MAX_FILENAME_LEN - 1) {
|
|
MessageBox("Filename is too long", "Error");
|
|
return;
|
|
}
|
|
|
|
if (!strlen(name))
|
|
return;
|
|
|
|
full_path = dlg.GetPathName();
|
|
|
|
Campaign_tree_formp->load_campaign((LPCTSTR)name, (LPCTSTR)full_path);
|
|
}
|
|
}
|
|
|
|
void campaign_tree_wnd::OnDestroy()
|
|
{
|
|
CString str;
|
|
|
|
OnCpgnFileNew();
|
|
Fred_main_wnd->EnableWindow(TRUE);
|
|
|
|
str = FREDDoc_ptr->GetPathName();
|
|
if (str.IsEmpty())
|
|
create_new_mission();
|
|
else
|
|
FREDDoc_ptr->OnOpenDocument(str);
|
|
|
|
CFrameWnd::OnDestroy();
|
|
Campaign_wnd = NULL;
|
|
Fred_main_wnd->SetFocus();
|
|
}
|
|
|
|
void campaign_tree_wnd::OnCpgnFileSave()
|
|
{
|
|
Fred_campaign_save save;
|
|
|
|
// This if/else is not strictly necessary as the underlying enum values match
|
|
// the Mission_save_format values but it is clearer to read and more robust against
|
|
// future changes.
|
|
if (Mission_save_format == FSO_FORMAT_RETAIL) {
|
|
save.set_save_format(MissionFormat::RETAIL);
|
|
} else if (Mission_save_format == FSO_FORMAT_COMPATIBILITY_MODE) {
|
|
save.set_save_format(MissionFormat::COMPATIBILITY_MODE);
|
|
} else {
|
|
save.set_save_format(MissionFormat::STANDARD);
|
|
}
|
|
|
|
|
|
Campaign_tree_formp->update();
|
|
if (!Campaign.filename[0]) {
|
|
OnCpgnFileSaveAs();
|
|
return;
|
|
}
|
|
|
|
// sanity checking for multiplayer
|
|
/*
|
|
if ( Campaign.type == TYPE_MULTI_PLAYER ) {
|
|
|
|
if ( (Campaign.mc_info.min_players < 0) || (Campaign.mc_info.min_players > Campaign.mc_info.max_players) ) {
|
|
MessageBox("Min players must be > 0 and <= max players", "Error", MB_OK | MB_ICONEXCLAMATION);
|
|
return;
|
|
}
|
|
if ( (Campaign.mc_info.max_players < 0) || (Campaign.mc_info.max_players > MAX_PLAYERS) ) {
|
|
char buf[256];
|
|
|
|
sprintf(buf, "Max players must be > 0 and <= %d", MAX_PLAYERS );
|
|
MessageBox(buf, "Error", MB_OK | MB_ICONEXCLAMATION);
|
|
return;
|
|
}
|
|
if ( Campaign.mc_info.max_players < Campaign.mc_info.min_players ) {
|
|
MessageBox("Max Players must be greater than min players", "Error", MB_OK | MB_ICONEXCLAMATION);
|
|
return;
|
|
}
|
|
}
|
|
*/
|
|
|
|
auto full_path = (LPCSTR)Campaign_tree_formp->GetCurrentCampaignPath();
|
|
|
|
Campaign_tree_formp->save_tree(); // flush all changes so they get saved.
|
|
Campaign_tree_viewp->sort_elements();
|
|
|
|
SCP_vector<campaign_link> links;
|
|
for (int i = 0; i < Total_links; i++) {
|
|
campaign_link link{
|
|
Links[i].from,
|
|
Links[i].to,
|
|
Links[i].sexp,
|
|
Links[i].node,
|
|
Links[i].is_mission_loop,
|
|
Links[i].is_mission_fork,
|
|
Links[i].mission_branch_txt,
|
|
Links[i].mission_branch_brief_anim,
|
|
Links[i].mission_branch_brief_sound
|
|
};
|
|
|
|
links.emplace_back(link);
|
|
}
|
|
|
|
if (save.save_campaign_file(full_path, links))
|
|
{
|
|
MessageBox("An error occured while saving!", "Error", MB_OK | MB_ICONEXCLAMATION);
|
|
return;
|
|
}
|
|
|
|
Campaign_modified = 0;
|
|
return;
|
|
}
|
|
|
|
void campaign_tree_wnd::OnCpgnFileSaveAs()
|
|
{
|
|
const char *old_name = nullptr;
|
|
Fred_campaign_save save;
|
|
|
|
// This if/else is not strictly necessary as the underlying enum values match
|
|
// the Mission_save_format values but it is clearer to read and more robust against
|
|
// future changes.
|
|
if (Mission_save_format == FSO_FORMAT_RETAIL) {
|
|
save.set_save_format(MissionFormat::RETAIL);
|
|
} else if (Mission_save_format == FSO_FORMAT_COMPATIBILITY_MODE) {
|
|
save.set_save_format(MissionFormat::COMPATIBILITY_MODE);
|
|
} else {
|
|
save.set_save_format(MissionFormat::STANDARD);
|
|
}
|
|
|
|
Campaign_tree_formp->update();
|
|
if (Campaign.filename[0])
|
|
old_name = Campaign.filename;
|
|
|
|
CFileDialog dlg(FALSE, "fc2", old_name, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "FreeSpace Campaign files (*.fc2)|*.fc2||", this);
|
|
auto campaign_path = Campaign_tree_formp->GetCurrentCampaignPath();
|
|
if (!campaign_path.IsEmpty())
|
|
dlg.m_ofn.lpstrInitialDir = (LPCSTR)campaign_path;
|
|
|
|
if (dlg.DoModal() == IDOK)
|
|
{
|
|
auto name = dlg.GetFileName();
|
|
if (strlen(name) > MAX_FILENAME_LEN - 1) {
|
|
MessageBox("Filename is too long", "Error");
|
|
return;
|
|
}
|
|
|
|
if (!strlen(name)){
|
|
return;
|
|
}
|
|
|
|
string_copy(Campaign.filename, name, MAX_FILENAME_LEN - 1);
|
|
|
|
Campaign_tree_formp->save_tree(); // flush all changes so they get saved.
|
|
Campaign_tree_viewp->sort_elements();
|
|
|
|
SCP_vector<campaign_link> links;
|
|
for (int i = 0; i < Total_links; i++) {
|
|
campaign_link link{Links[i].from,
|
|
Links[i].to,
|
|
Links[i].sexp,
|
|
Links[i].node,
|
|
Links[i].is_mission_loop,
|
|
Links[i].is_mission_fork,
|
|
Links[i].mission_branch_txt,
|
|
Links[i].mission_branch_brief_anim,
|
|
Links[i].mission_branch_brief_sound};
|
|
|
|
links.emplace_back(link);
|
|
}
|
|
|
|
if (save.save_campaign_file((LPCSTR)dlg.GetPathName(), links))
|
|
{
|
|
MessageBox("An error occured while saving!", "Error", MB_OK | MB_ICONEXCLAMATION);
|
|
return;
|
|
}
|
|
|
|
Campaign_tree_formp->SetCurrentCampaignPath(dlg.GetPathName());
|
|
Campaign_modified = 0;
|
|
}
|
|
}
|
|
|
|
void campaign_tree_wnd::OnCpgnFileNew()
|
|
{
|
|
if (Campaign_modified)
|
|
if (save_modified())
|
|
return;
|
|
|
|
Campaign.filename[0] = 0;
|
|
Campaign.num_missions = 0;
|
|
Campaign.num_players = 0;
|
|
strcpy_s(Campaign.name, "Unnamed");
|
|
Campaign_tree_viewp->free_links();
|
|
Campaign_tree_formp->initialize(true, true);
|
|
Campaign_modified = 0;
|
|
Campaign.flags = CF_DEFAULT_VALUE;
|
|
((CButton *) (Campaign_tree_formp->GetDlgItem(IDC_CUSTOM_TECH_DB)))->SetCheck(0);
|
|
}
|
|
|
|
void campaign_tree_wnd::OnClose()
|
|
{
|
|
if (Campaign_modified)
|
|
if (save_modified())
|
|
return;
|
|
|
|
CFrameWnd::OnClose();
|
|
}
|
|
|
|
void campaign_tree_wnd::OnClose2()
|
|
{
|
|
if (Campaign_modified)
|
|
if (save_modified())
|
|
return;
|
|
|
|
DestroyWindow();
|
|
}
|
|
|
|
// returns 0 for success and 1 for cancel
|
|
int campaign_tree_wnd::save_modified()
|
|
{
|
|
int r;
|
|
|
|
r = MessageBox("This campaign has been modified. Save changes first?", "Campaign Modified",
|
|
MB_YESNOCANCEL | MB_ICONQUESTION);
|
|
|
|
if (r == IDCANCEL)
|
|
return 1;
|
|
|
|
if (r == IDYES) {
|
|
OnCpgnFileSave();
|
|
if (Campaign_modified) // error occured in saving.
|
|
return 1;
|
|
}
|
|
|
|
Campaign_modified = 0;
|
|
return 0;
|
|
}
|
|
|
|
void campaign_tree_wnd::OnErrorChecker()
|
|
{
|
|
Campaign_tree_formp->save_tree(0);
|
|
error_checker();
|
|
if (!g_err)
|
|
MessageBox("No errors detected in campaign", "Woohoo!");
|
|
}
|
|
|
|
int campaign_tree_wnd::error_checker()
|
|
{
|
|
int i, j, z;
|
|
int mcount[MAX_CAMPAIGN_MISSIONS], true_at[MAX_CAMPAIGN_MISSIONS];
|
|
|
|
for (i=0; i<MAX_CAMPAIGN_MISSIONS; i++) {
|
|
mcount[i] = 0;
|
|
true_at[i] = -1;
|
|
}
|
|
|
|
g_err = 0;
|
|
for (i=0; i<Total_links; i++) {
|
|
// #1 check: illegal source mission
|
|
if ( (Links[i].from < 0) || (Links[i].from >= Campaign.num_missions) )
|
|
return internal_error("Branch #%d has illegal source mission", i);
|
|
// #2 check: illegal target mission
|
|
if ( (Links[i].to < -1) || (Links[i].to >= Campaign.num_missions) )
|
|
return internal_error("Branch #%d has illegal target mission", i);
|
|
// #3 check: formula syntax
|
|
Sexp_useful_number = Links[i].from;
|
|
if (fred_check_sexp(Links[i].sexp, OPR_BOOL, "formula of branch #%d", i))
|
|
return -1;
|
|
|
|
z = Links[i].from;
|
|
|
|
// #4 check: always true loop
|
|
if (Links[i].is_mission_loop || Links[i].is_mission_fork) {
|
|
if (Links[i].sexp == Locked_sexp_true) {
|
|
if (error("Mission \"%s\" has a loop branch that is always true", Campaign.missions[z].name))
|
|
return 1;
|
|
}
|
|
// no further checking for loop links - in particular, a loop link isn't a regular link
|
|
continue;
|
|
}
|
|
|
|
// total number of regular links
|
|
mcount[z]++;
|
|
|
|
// #5 check: always false branch
|
|
if (Links[i].sexp == Locked_sexp_false) {
|
|
if (error("Mission \"%s\" branch %d is always false", Campaign.missions[z].name, mcount[z]))
|
|
return 1;
|
|
}
|
|
|
|
// #6 check: true middle branch
|
|
if (Links[i].sexp == Locked_sexp_true) {
|
|
if (true_at[z] >= 0)
|
|
if (error("Mission \"%s\" branch %d is true but is not last branch", Campaign.missions[z].name, true_at[z]))
|
|
return 1;
|
|
|
|
true_at[z] = mcount[z];
|
|
}
|
|
}
|
|
|
|
// #7 check: not always true last branch
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
if (mcount[i] && true_at[i] < mcount[i])
|
|
if (error("Mission \"%s\" last branch isn't set to true", Campaign.missions[i].name))
|
|
return 1;
|
|
|
|
// #8 check: duplicate mission
|
|
for (i=z=0; i<Campaign.num_missions; i++) {
|
|
for (j=0; j<Campaign.num_missions; j++)
|
|
if ((i != j) && !stricmp(Campaign.missions[i].name, Campaign.missions[j].name))
|
|
return internal_error("Mission \"%s\" is listed twice in campaign", Campaign.missions[i].name);
|
|
|
|
if (!Campaign.missions[i].level)
|
|
z++;
|
|
}
|
|
|
|
// #9 check: no first mission
|
|
if (!z)
|
|
if (error("No top level mission present in tree"))
|
|
return 1;
|
|
|
|
// #10 check: duplicate first mission
|
|
if (z > 1)
|
|
return internal_error("More than one top level mission present in tree");
|
|
|
|
// #11 check: Multi player number
|
|
// check that all missions in a multiplayer game have the same number of players
|
|
if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
|
|
for (i = 0; i < Campaign.num_missions; i++ ) {
|
|
mission a_mission;
|
|
|
|
get_mission_info(Campaign.missions[i].name, &a_mission);
|
|
if ( a_mission.num_players != Campaign.num_players ) {
|
|
if ( error("Mission \"%s\" has %d players. Multiplayer campaign allows %d", Campaign.missions[i].name, a_mission.num_players, Campaign.num_players) )
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int campaign_tree_wnd::error(const char *msg, ...)
|
|
{
|
|
SCP_string buf;
|
|
va_list args;
|
|
|
|
g_err++;
|
|
va_start(args, msg);
|
|
vsprintf(buf, msg, args);
|
|
va_end(args);
|
|
|
|
nprintf(("Error", buf.c_str()));
|
|
if (MessageBox(buf.c_str(), "Error", MB_OKCANCEL | MB_ICONEXCLAMATION) == IDOK)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int campaign_tree_wnd::internal_error(const char *msg, ...)
|
|
{
|
|
SCP_string buf;
|
|
va_list args;
|
|
|
|
va_start(args, msg);
|
|
vsprintf(buf, msg, args);
|
|
va_end(args);
|
|
|
|
g_err++;
|
|
|
|
#ifndef NDEBUG
|
|
nprintf(("Internal Error", buf.c_str()));
|
|
|
|
buf += "\n\nThis is an internal error. Please notify a coder about this. Click cancel to debug.";
|
|
|
|
if (MessageBox(buf.c_str(), "Internal Error", MB_OKCANCEL | MB_ICONEXCLAMATION) == IDCANCEL)
|
|
Int3(); // drop to debugger so the problem can be analyzed.
|
|
#else
|
|
MessageBox(buf.c_str(), "Error", MB_OK | MB_ICONEXCLAMATION);
|
|
#endif
|
|
|
|
return -1;
|
|
}
|
|
|
|
int campaign_tree_wnd::fred_check_sexp(int sexp, int type, char *msg, ...)
|
|
{
|
|
SCP_string buf, sexp_buf, error_buf, bad_node_str;
|
|
int err = 0, z, faulty_node;
|
|
va_list args;
|
|
|
|
va_start(args, msg);
|
|
vsprintf(buf, msg, args);
|
|
va_end(args);
|
|
|
|
if (sexp == -1)
|
|
return 0;
|
|
|
|
z = check_sexp_syntax(sexp, type, 1, &faulty_node, sexp_mode::CAMPAIGN);
|
|
if (!z)
|
|
return 0;
|
|
|
|
convert_sexp_to_string(sexp_buf, sexp, SEXP_ERROR_CHECK_MODE);
|
|
truncate_message_lines(sexp_buf, 30);
|
|
|
|
stuff_sexp_text_string(bad_node_str, faulty_node, SEXP_ERROR_CHECK_MODE);
|
|
if (!bad_node_str.empty()) { // the previous function adds a space at the end
|
|
bad_node_str.pop_back();
|
|
}
|
|
|
|
sprintf(error_buf, "Error in %s: %s\n\nIn sexpression: %s\n\n(Bad node appears to be: %s)", buf.c_str(), sexp_error_message(z), sexp_buf.c_str(), bad_node_str.c_str());
|
|
|
|
if (z < 0 && z > -100)
|
|
err = 1;
|
|
|
|
if (err)
|
|
return internal_error("%s", error_buf.c_str());
|
|
|
|
if (error("%s", error_buf.c_str()))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// code to deal with the initial ships that a player can choose
|
|
void campaign_tree_wnd::OnInitialShips()
|
|
{
|
|
InitialShips isd(Campaign_tree_formp);
|
|
|
|
isd.m_initial_items = INITIAL_SHIPS;
|
|
isd.DoModal();
|
|
}
|
|
|
|
void campaign_tree_wnd::OnInitialWeapons()
|
|
{
|
|
InitialShips isd(Campaign_tree_formp);
|
|
|
|
isd.m_initial_items = INITIAL_WEAPONS;
|
|
isd.DoModal();
|
|
}
|