2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/FRED2/CampaignTreeWnd.cpp $
15 * Campaign display tree window code. Works very closely with the Campaign editor dialog box.
18 * Revision 1.3 2002/06/09 04:41:16 relnev
19 * added copyright header
21 * Revision 1.2 2002/05/07 03:16:44 theoddone33
22 * The Great Newline Fix
24 * Revision 1.1.1.1 2002/05/03 03:28:08 root
28 * 3 1/07/99 1:52p Andsager
29 * Initial check in of Sexp_variables
31 * 2 10/07/98 6:28p Dave
32 * Initial checkin. Renamed all relevant stuff to be Fred2 instead of
33 * Fred. Globalized mission and campaign file extensions. Removed Silent
34 * Threat specific code.
36 * 1 10/07/98 3:00p Dave
38 * 27 9/16/98 3:08p Dave
39 * Upped max sexpression nodes to 1800 (from 1600). Changed FRED to sort
40 * the ship list box. Added code so that tracker stats are not stored with
43 * 26 8/19/98 9:46a Hoffoss
44 * Added some 'save first?' type checks to the campaign editor.
46 * 25 5/26/98 2:32p Hoffoss
47 * Made campaign editor come up with a new campaign instead of loading
50 * 24 5/22/98 1:06a Hoffoss
51 * Made Fred not use OLE.
53 * 23 4/14/98 11:55a Allender
54 * add end-of-campaign sexpression to allow for mission replay at the end
57 * 22 3/31/98 12:23a Allender
58 * changed macro names of campaign types to be more descriptive. Added
59 * "team" to objectives dialog for team v. team missions. Added two
60 * distinct multiplayer campaign types
62 * 21 3/18/98 10:38p Allender
63 * added required "num players" for multiplayer missions. Put in required
64 * "num players" for multiplayer campaigns. Added campaign editor support
65 * to determine "num players"
67 * 20 2/24/98 10:23a Johnson
68 * Fixed up some build bugs caused by a multiplayer struct removal.
70 * 19 12/19/97 10:29a Allender
71 * made initial status windows appear on top of campaign editor window
72 * instead of Fred main window
74 * 18 12/18/97 5:11p Allender
75 * initial support for ship/weapon persistence
77 * 17 12/02/97 5:29p Johnson
78 * Fixed bug in branch sexp error checking. Was using link number instead
81 * 16 11/22/97 3:05p Allender
82 * support for new fields for multiplayer in campaign editor
84 * 15 9/16/97 4:19p Jasen
85 * Fixed a bug in Fred with Campaign mode.
87 * 14 9/01/97 6:31p Hoffoss
88 * Added remaining missing features to campaign editor in Fred.
90 * 13 8/14/97 11:54p Hoffoss
91 * Added more error checking to Campaign editor, and made exit from
92 * Campaign editor reload last mission in Fred (unless specifically
93 * loading another mission).
95 * 12 8/13/97 5:49p Hoffoss
96 * Fixed bugs, made additions.
98 * 11 8/13/97 12:46p Hoffoss
99 * Added campaign error checker, accelerator table, and mission goal data
100 * listings to sexp tree right click menu.
102 * 10 7/09/97 2:28p Hoffoss
103 * Fixed bug with adding new links, and made campaign general info update.
105 * 9 5/15/97 12:45p Hoffoss
106 * Extensive changes to fix many little bugs.
108 * 8 5/14/97 12:54p Hoffoss
109 * Added sexp tree for campaign branches, branch hilighting, and branch
112 * 7 5/13/97 12:46p Hoffoss
113 * Added close campaign editor functions, changed global pointer to have
114 * capped first letter.
116 * 6 5/13/97 11:13a Hoffoss
117 * Added remaining file menu options to campaign editor.
119 * 5 5/13/97 10:52a Hoffoss
120 * Added campaign saving code.
122 * 4 5/09/97 9:50a Hoffoss
123 * Routine code check in.
125 * 3 5/05/97 9:40a Hoffoss
126 * Campaign editor begun.
128 * 2 5/01/97 4:11p Hoffoss
129 * Started on Campaign editor stuff, being sidetracked with fixing bugs
130 * now, though, so checking it for now.
137 #include "campaigntreewnd.h"
138 #include "campaigneditordlg.h"
139 #include "campaigntreeview.h"
140 #include "management.h"
142 #include "fredview.h"
143 #include "missionsave.h"
144 #include "initialships.h"
145 #include "missionparse.h"
148 #define new DEBUG_NEW
150 static char THIS_FILE[] = __FILE__;
153 int Mission_filename_cb_format;
154 int Campaign_modified = 0;
155 int Bypass_clear_mission;
156 campaign_tree_wnd *Campaign_wnd = NULL;
158 IMPLEMENT_DYNCREATE(campaign_tree_wnd, CFrameWnd)
160 /////////////////////////////////////////////////////////////////////////////
163 campaign_tree_wnd::campaign_tree_wnd()
165 Bypass_clear_mission = 0;
168 campaign_tree_wnd::~campaign_tree_wnd()
172 BEGIN_MESSAGE_MAP(campaign_tree_wnd, CFrameWnd)
173 //{{AFX_MSG_MAP(campaign_tree_wnd)
174 ON_UPDATE_COMMAND_UI(ID_CPGN_FILE_OPEN, OnUpdateCpgnFileOpen)
175 ON_COMMAND(ID_CPGN_FILE_OPEN, OnCpgnFileOpen)
177 ON_COMMAND(ID_CPGN_FILE_SAVE, OnCpgnFileSave)
178 ON_COMMAND(ID_CPGN_FILE_SAVE_AS, OnCpgnFileSaveAs)
179 ON_COMMAND(ID_CPGN_FILE_NEW, OnCpgnFileNew)
180 ON_COMMAND(ID_CLOSE, OnClose2)
181 ON_COMMAND(ID_ERROR_CHECKER, OnErrorChecker)
183 ON_COMMAND(ID_INITIAL_SHIPS, OnInitialShips)
184 ON_COMMAND(ID_INITIAL_WEAPONS, OnInitialWeapons)
188 /////////////////////////////////////////////////////////////////////////////
189 // campaign_tree_wnd message handlers
191 BOOL campaign_tree_wnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext)
195 LoadAccelTable("IDR_ACC_CAMPAIGN");
196 Mission_filename_cb_format = RegisterClipboardFormat("Mission Filename");
197 Campaign_modified = 0;
200 // create a splitter with 1 row, 2 columns
201 if (!m_splitter.CreateStatic(this, 1, 2))
203 TRACE0("Failed to CreateStaticSplitter\n");
207 // add the first splitter pane - the campaign input form in column 0
208 if (!m_splitter.CreateView(0, 0, RUNTIME_CLASS(campaign_editor), CSize(0, 0), pContext))
210 TRACE0("Failed to create first pane\n");
214 // add the second splitter pane - the campaign tree view in column 1
215 if (!m_splitter.CreateView(0, 1, RUNTIME_CLASS(campaign_tree_view), CSize(240, 100), pContext))
217 TRACE0("Failed to create second pane\n");
221 Campaign_tree_formp = (campaign_editor *) m_splitter.GetPane(0, 0);
222 Campaign_tree_viewp = (campaign_tree_view *) m_splitter.GetPane(0, 1);
223 s = Campaign_tree_formp->GetTotalSize();
224 m_splitter.SetColumnInfo(0, s.cx, 0);
225 m_splitter.SetColumnInfo(1, 0, 0);
226 m_splitter.RecalcLayout();
228 // activate the input view
229 SetActiveView(Campaign_tree_formp);
231 // Campaign_tree_formp->load_campaign();
232 Fred_main_wnd->EnableWindow(FALSE);
236 void campaign_tree_wnd::OnUpdateCpgnFileOpen(CCmdUI* pCmdUI)
241 void campaign_tree_wnd::OnCpgnFileOpen()
245 if (Campaign_modified)
249 CFileDialog dlg(TRUE, "fc2", NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "FreeSpace Campaign files (*.fc2)|*.fc2||", this);
250 if (dlg.DoModal() == IDOK)
252 name = dlg.GetFileName();
253 if (strlen(name) > MAX_FILENAME_LEN - 1) {
254 MessageBox("Filename is too long", "Error");
261 string_copy(Campaign.filename, name, MAX_FILENAME_LEN);
262 Campaign_tree_formp->load_campaign();
266 void campaign_tree_wnd::OnDestroy()
271 Fred_main_wnd->EnableWindow(TRUE);
272 // if (!Bypass_clear_mission)
273 // create_new_mission();
274 str = FREDDoc_ptr->GetPathName();
276 create_new_mission();
278 FREDDoc_ptr->OnOpenDocument(str);
280 CFrameWnd::OnDestroy();
282 Fred_main_wnd->SetFocus();
285 void campaign_tree_wnd::OnCpgnFileSave()
287 CFred_mission_save save;
289 Campaign_tree_formp->update();
290 if (!Campaign.filename[0]) {
295 // sanity checking for multiplayer
297 if ( Campaign.type == TYPE_MULTI_PLAYER ) {
299 if ( (Campaign.mc_info.min_players < 0) || (Campaign.mc_info.min_players > Campaign.mc_info.max_players) ) {
300 MessageBox("Min players must be > 0 and <= max players", "Error", MB_OK | MB_ICONEXCLAMATION);
303 if ( (Campaign.mc_info.max_players < 0) || (Campaign.mc_info.max_players > MAX_PLAYERS) ) {
306 sprintf(buf, "Max players must be > 0 and <= %d", MAX_PLAYERS );
307 MessageBox(buf, "Error", MB_OK | MB_ICONEXCLAMATION);
310 if ( Campaign.mc_info.max_players < Campaign.mc_info.min_players ) {
311 MessageBox("Max Players must be greater than min players", "Error", MB_OK | MB_ICONEXCLAMATION);
317 if (save.save_campaign_file(Campaign.filename))
319 MessageBox("An error occured while saving!", "Error", MB_OK | MB_ICONEXCLAMATION);
323 Campaign_modified = 0;
327 void campaign_tree_wnd::OnCpgnFileSaveAs()
329 char *old_name = NULL;
331 CFred_mission_save save;
333 Campaign_tree_formp->update();
334 if (Campaign.filename[0])
335 old_name = Campaign.filename;
337 CFileDialog dlg(FALSE, "fc2", old_name, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "FreeSpace Campaign files (*.fc2)|*.fc2||", this);
338 if (dlg.DoModal() == IDOK)
340 name = dlg.GetFileName();
341 if (strlen(name) > MAX_FILENAME_LEN - 1) {
342 MessageBox("Filename is too long", "Error");
350 string_copy(Campaign.filename, name, MAX_FILENAME_LEN);
351 if (save.save_campaign_file(Campaign.filename))
353 MessageBox("An error occured while saving!", "Error", MB_OK | MB_ICONEXCLAMATION);
357 Campaign_modified = 0;
361 void campaign_tree_wnd::OnCpgnFileNew()
363 if (Campaign_modified)
367 Campaign.filename[0] = 0;
368 Campaign.num_missions = 0;
369 Campaign.num_players = 0;
370 strcpy(Campaign.name, "Unnamed");
371 Campaign_tree_viewp->free_links();
372 Campaign_tree_formp->initialize();
373 Campaign_modified = 0;
376 void campaign_tree_wnd::OnClose()
378 if (Campaign_modified)
382 CFrameWnd::OnClose();
385 void campaign_tree_wnd::OnClose2()
387 if (Campaign_modified)
394 // returns 0 for success and 1 for cancel
395 int campaign_tree_wnd::save_modified()
399 r = MessageBox("This campaign has been modified. Save changes first?", "Campaign Modified",
400 MB_YESNOCANCEL | MB_ICONQUESTION);
407 if (Campaign_modified) // error occured in saving.
411 Campaign_modified = 0;
415 void campaign_tree_wnd::OnErrorChecker()
417 Campaign_tree_formp->save_tree(0);
420 MessageBox("No errors detected in campaign", "Woohoo!");
423 int campaign_tree_wnd::error_checker()
426 int mcount[MAX_CAMPAIGN_MISSIONS], true_at[MAX_CAMPAIGN_MISSIONS];
428 for (i=0; i<MAX_CAMPAIGN_MISSIONS; i++) {
434 for (i=0; i<Total_links; i++) {
435 if ( (Links[i].from < 0) || (Links[i].from >= Campaign.num_missions) )
436 return internal_error("Branch #%d has illegal source mission", i);
437 if ( (Links[i].to < -1) || (Links[i].to >= Campaign.num_missions) )
438 return internal_error("Branch #%d has illegal target mission", i);
439 Sexp_useful_number = Links[i].from;
440 if (fred_check_sexp(Links[i].sexp, OPR_BOOL, "formula of branch #%d", i))
445 if (Links[i].sexp == Locked_sexp_false)
446 if (error("Mission \"%s\" branch %d is always false", Campaign.missions[z].name, mcount[z]))
449 if (Links[i].sexp == Locked_sexp_true) {
451 if (error("Mission \"%s\" branch %d is true but is not last branch", Campaign.missions[z].name, true_at[z]))
454 true_at[z] = mcount[z];
458 // check that all missions in a multiplayer game have the same number of players
459 if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
460 for (i = 0; i < Campaign.num_missions; i++ ) {
463 get_mission_info(Campaign.missions[i].name, &a_mission);
464 if ( a_mission.num_players != Campaign.num_players ) {
465 if ( error("Mission \"%s\" has %d players. Multiplayer campaign allows %d", Campaign.missions[i].name, a_mission.num_players, Campaign.num_players) )
471 for (i=0; i<Campaign.num_missions; i++)
472 if (mcount[i] && true_at[i] < mcount[i])
473 if (error("Mission \"%s\" last branch isn't set to true", Campaign.missions[i].name))
476 for (i=z=0; i<Campaign.num_missions; i++) {
477 for (j=0; j<Campaign.num_missions; j++)
478 if ((i != j) && !stricmp(Campaign.missions[i].name, Campaign.missions[j].name))
479 return internal_error("Mission \"%s\" is listed twice in campaign", Campaign.missions[i].name);
481 if (!Campaign.missions[i].level)
486 if (error("No top level mission present in tree"))
490 return internal_error("More than one top level mission present in tree");
495 int campaign_tree_wnd::error(char *msg, ...)
502 vsprintf(buf, msg, args);
505 if (MessageBox(buf, "Error", MB_OKCANCEL | MB_ICONEXCLAMATION) == IDOK)
511 int campaign_tree_wnd::internal_error(char *msg, ...)
513 char buf[2048], buf2[2048];
518 vsprintf(buf, msg, args);
521 sprintf(buf2, "%s\n\nThis is an internal error. Please let Hoffoss\n"
522 "know about this so he can fix it. Click cancel to debug.", buf);
524 if (MessageBox(buf2, "Internal Error", MB_OKCANCEL | MB_ICONEXCLAMATION) == IDCANCEL)
525 Int3(); // drop to debugger so the problem can be analyzed.
530 int campaign_tree_wnd::fred_check_sexp(int sexp, int type, char *msg, ...)
532 char buf[512], buf2[2048], buf3[4096];
533 int err = 0, z, faulty_node;
537 vsprintf(buf, msg, args);
543 z = check_sexp_syntax(sexp, type, 1, &faulty_node, SEXP_MODE_CAMPAIGN);
547 convert_sexp_to_string(sexp, buf2, SEXP_ERROR_CHECK_MODE);
548 sprintf(buf3, "Error in %s: %s\n\nIn sexpression: %s\n\n(Error appears to be: %s)",
549 buf, sexp_error_message(z), buf2, Sexp_nodes[faulty_node].text);
551 if (z < 0 && z > -100)
555 return internal_error(buf3);
563 // code to deal with the initial ships that a player can choose
564 void campaign_tree_wnd::OnInitialShips()
566 InitialShips isd(Campaign_tree_formp);
568 isd.m_initial_items = INITIAL_SHIPS;
572 void campaign_tree_wnd::OnInitialWeapons()
574 InitialShips isd(Campaign_tree_formp);
576 isd.m_initial_items = INITIAL_WEAPONS;