]> icculus.org git repositories - taylor/freespace2.git/blob - src/fred2/campaigntreewnd.cpp
Initial revision
[taylor/freespace2.git] / src / fred2 / campaigntreewnd.cpp
1 /*
2  * $Logfile: /Freespace2/code/FRED2/CampaignTreeWnd.cpp $
3  * $Revision$
4  * $Date$
5  * $Author$
6  *
7  * Campaign display tree window code.  Works very closely with the Campaign editor dialog box.
8  *
9  * $Log$
10  * Revision 1.1  2002/05/03 03:28:08  root
11  * Initial revision
12  *
13  * 
14  * 3     1/07/99 1:52p Andsager
15  * Initial check in of Sexp_variables
16  * 
17  * 2     10/07/98 6:28p Dave
18  * Initial checkin. Renamed all relevant stuff to be Fred2 instead of
19  * Fred. Globalized mission and campaign file extensions. Removed Silent
20  * Threat specific code.
21  * 
22  * 1     10/07/98 3:00p Dave
23  * 
24  * 27    9/16/98 3:08p Dave
25  * Upped  max sexpression nodes to 1800 (from 1600). Changed FRED to sort
26  * the ship list box. Added code so that tracker stats are not stored with
27  * only 1 player.
28  * 
29  * 26    8/19/98 9:46a Hoffoss
30  * Added some 'save first?' type checks to the campaign editor.
31  * 
32  * 25    5/26/98 2:32p Hoffoss
33  * Made campaign editor come up with a new campaign instead of loading
34  * built-in one.
35  * 
36  * 24    5/22/98 1:06a Hoffoss
37  * Made Fred not use OLE.
38  * 
39  * 23    4/14/98 11:55a Allender
40  * add end-of-campaign sexpression to allow for mission replay at the end
41  * of campaigns
42  * 
43  * 22    3/31/98 12:23a Allender
44  * changed macro names of campaign types to be more descriptive.  Added
45  * "team" to objectives dialog for team v. team missions.  Added two
46  * distinct multiplayer campaign types
47  * 
48  * 21    3/18/98 10:38p Allender
49  * added required "num players" for multiplayer missions.  Put in required
50  * "num players" for multiplayer campaigns.  Added campaign editor support
51  * to determine "num players"
52  * 
53  * 20    2/24/98 10:23a Johnson
54  * Fixed up some build bugs caused by a multiplayer struct removal.
55  * 
56  * 19    12/19/97 10:29a Allender
57  * made initial status windows appear on top of campaign editor window
58  * instead of Fred main window
59  * 
60  * 18    12/18/97 5:11p Allender
61  * initial support for ship/weapon persistence
62  * 
63  * 17    12/02/97 5:29p Johnson
64  * Fixed bug in branch sexp error checking.  Was using link number instead
65  * of mission number.
66  * 
67  * 16    11/22/97 3:05p Allender
68  * support for new fields for multiplayer in campaign editor
69  * 
70  * 15    9/16/97 4:19p Jasen
71  * Fixed a bug in Fred with Campaign mode.
72  * 
73  * 14    9/01/97 6:31p Hoffoss
74  * Added remaining missing features to campaign editor in Fred.
75  * 
76  * 13    8/14/97 11:54p Hoffoss
77  * Added more error checking to Campaign editor, and made exit from
78  * Campaign editor reload last mission in Fred (unless specifically
79  * loading another mission).
80  * 
81  * 12    8/13/97 5:49p Hoffoss
82  * Fixed bugs, made additions.
83  * 
84  * 11    8/13/97 12:46p Hoffoss
85  * Added campaign error checker, accelerator table, and mission goal data
86  * listings to sexp tree right click menu.
87  * 
88  * 10    7/09/97 2:28p Hoffoss
89  * Fixed bug with adding new links, and made campaign general info update.
90  * 
91  * 9     5/15/97 12:45p Hoffoss
92  * Extensive changes to fix many little bugs.
93  * 
94  * 8     5/14/97 12:54p Hoffoss
95  * Added sexp tree for campaign branches, branch hilighting, and branch
96  * reordering.
97  * 
98  * 7     5/13/97 12:46p Hoffoss
99  * Added close campaign editor functions, changed global pointer to have
100  * capped first letter.
101  * 
102  * 6     5/13/97 11:13a Hoffoss
103  * Added remaining file menu options to campaign editor.
104  * 
105  * 5     5/13/97 10:52a Hoffoss
106  * Added campaign saving code.
107  * 
108  * 4     5/09/97 9:50a Hoffoss
109  * Routine code check in.
110  * 
111  * 3     5/05/97 9:40a Hoffoss
112  * Campaign editor begun.
113  * 
114  * 2     5/01/97 4:11p Hoffoss
115  * Started on Campaign editor stuff, being sidetracked with fixing bugs
116  * now, though, so checking it for now.
117  *
118  * $NoKeywords: $
119  */
120
121 #include "stdafx.h"
122 #include "fred.h"
123 #include "campaigntreewnd.h"
124 #include "campaigneditordlg.h"
125 #include "campaigntreeview.h"
126 #include "management.h"
127 #include "mainfrm.h"
128 #include "fredview.h"
129 #include "missionsave.h"
130 #include "initialships.h"
131 #include "missionparse.h"
132
133 #ifdef _DEBUG
134 #define new DEBUG_NEW
135 #undef THIS_FILE
136 static char THIS_FILE[] = __FILE__;
137 #endif
138
139 int Mission_filename_cb_format;
140 int Campaign_modified = 0;
141 int Bypass_clear_mission;
142 campaign_tree_wnd *Campaign_wnd = NULL;
143
144 IMPLEMENT_DYNCREATE(campaign_tree_wnd, CFrameWnd)
145
146 /////////////////////////////////////////////////////////////////////////////
147 // campaign_tree_wnd
148
149 campaign_tree_wnd::campaign_tree_wnd()
150 {
151         Bypass_clear_mission = 0;
152 }
153
154 campaign_tree_wnd::~campaign_tree_wnd()
155 {
156 }
157
158 BEGIN_MESSAGE_MAP(campaign_tree_wnd, CFrameWnd)
159         //{{AFX_MSG_MAP(campaign_tree_wnd)
160         ON_UPDATE_COMMAND_UI(ID_CPGN_FILE_OPEN, OnUpdateCpgnFileOpen)
161         ON_COMMAND(ID_CPGN_FILE_OPEN, OnCpgnFileOpen)
162         ON_WM_DESTROY()
163         ON_COMMAND(ID_CPGN_FILE_SAVE, OnCpgnFileSave)
164         ON_COMMAND(ID_CPGN_FILE_SAVE_AS, OnCpgnFileSaveAs)
165         ON_COMMAND(ID_CPGN_FILE_NEW, OnCpgnFileNew)
166         ON_COMMAND(ID_CLOSE, OnClose2)
167         ON_COMMAND(ID_ERROR_CHECKER, OnErrorChecker)
168         ON_WM_CLOSE()
169         ON_COMMAND(ID_INITIAL_SHIPS, OnInitialShips)
170         ON_COMMAND(ID_INITIAL_WEAPONS, OnInitialWeapons)
171         //}}AFX_MSG_MAP
172 END_MESSAGE_MAP()
173
174 /////////////////////////////////////////////////////////////////////////////
175 // campaign_tree_wnd message handlers
176
177 BOOL campaign_tree_wnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext)
178 {
179         CSize s;
180
181         LoadAccelTable("IDR_ACC_CAMPAIGN");
182         Mission_filename_cb_format = RegisterClipboardFormat("Mission Filename");
183         Campaign_modified = 0;
184         clear_mission();
185
186         // create a splitter with 1 row, 2 columns
187         if (!m_splitter.CreateStatic(this, 1, 2))
188         {
189                 TRACE0("Failed to CreateStaticSplitter\n");
190                 return FALSE;
191         }
192
193         // add the first splitter pane - the campaign input form in column 0
194         if (!m_splitter.CreateView(0, 0, RUNTIME_CLASS(campaign_editor), CSize(0, 0), pContext))
195         {
196                 TRACE0("Failed to create first pane\n");
197                 return FALSE;
198         }
199
200         // add the second splitter pane - the campaign tree view in column 1
201         if (!m_splitter.CreateView(0, 1, RUNTIME_CLASS(campaign_tree_view), CSize(240, 100), pContext))
202         {
203                 TRACE0("Failed to create second pane\n");
204                 return FALSE;
205         }
206
207         Campaign_tree_formp = (campaign_editor *) m_splitter.GetPane(0, 0);
208         Campaign_tree_viewp = (campaign_tree_view *) m_splitter.GetPane(0, 1);
209         s = Campaign_tree_formp->GetTotalSize();
210         m_splitter.SetColumnInfo(0, s.cx, 0);
211         m_splitter.SetColumnInfo(1, 0, 0);
212         m_splitter.RecalcLayout();
213
214         // activate the input view
215         SetActiveView(Campaign_tree_formp);
216         OnCpgnFileNew();
217 //      Campaign_tree_formp->load_campaign();
218         Fred_main_wnd->EnableWindow(FALSE);
219         return TRUE;
220 }
221
222 void campaign_tree_wnd::OnUpdateCpgnFileOpen(CCmdUI* pCmdUI) 
223 {
224         pCmdUI->Enable();
225 }
226
227 void campaign_tree_wnd::OnCpgnFileOpen() 
228 {
229         CString name;
230
231         if (Campaign_modified)
232                 if (save_modified())
233                         return;
234
235         CFileDialog dlg(TRUE, "fc2", NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "FreeSpace Campaign files (*.fc2)|*.fc2||", this);
236         if (dlg.DoModal() == IDOK)
237         {
238                 name = dlg.GetFileName();
239                 if (strlen(name) > MAX_FILENAME_LEN - 1) {
240                         MessageBox("Filename is too long", "Error");
241                         return;
242                 }
243
244                 if (!strlen(name))
245                         return;
246
247                 string_copy(Campaign.filename, name, MAX_FILENAME_LEN);
248                 Campaign_tree_formp->load_campaign();
249         }
250 }
251
252 void campaign_tree_wnd::OnDestroy()
253 {
254         CString str;
255
256         OnCpgnFileNew();
257         Fred_main_wnd->EnableWindow(TRUE);
258 //      if (!Bypass_clear_mission)
259 //              create_new_mission();
260         str = FREDDoc_ptr->GetPathName();
261         if (str.IsEmpty())
262                 create_new_mission();
263         else
264                 FREDDoc_ptr->OnOpenDocument(str);
265
266         CFrameWnd::OnDestroy();
267         Campaign_wnd = NULL;
268         Fred_main_wnd->SetFocus();
269 }
270
271 void campaign_tree_wnd::OnCpgnFileSave() 
272 {
273         CFred_mission_save save;
274
275         Campaign_tree_formp->update();
276         if (!Campaign.filename[0]) {
277                 OnCpgnFileSaveAs();
278                 return;
279         }
280
281         // sanity checking for multiplayer      
282         /*
283         if ( Campaign.type == TYPE_MULTI_PLAYER ) {
284         
285                 if ( (Campaign.mc_info.min_players < 0) || (Campaign.mc_info.min_players > Campaign.mc_info.max_players) ) {
286                         MessageBox("Min players must be > 0 and <= max players", "Error", MB_OK | MB_ICONEXCLAMATION);
287                         return;
288                 }
289                 if ( (Campaign.mc_info.max_players < 0) || (Campaign.mc_info.max_players > MAX_PLAYERS) ) {
290                         char buf[256];
291
292                         sprintf(buf, "Max players must be > 0 and <= %d", MAX_PLAYERS );
293                         MessageBox(buf, "Error", MB_OK | MB_ICONEXCLAMATION);
294                         return;
295                 }
296                 if ( Campaign.mc_info.max_players < Campaign.mc_info.min_players ) {
297                         MessageBox("Max Players must be greater than min players", "Error", MB_OK | MB_ICONEXCLAMATION);
298                         return;
299                 }
300         }
301         */      
302
303         if (save.save_campaign_file(Campaign.filename))
304         {
305                 MessageBox("An error occured while saving!", "Error", MB_OK | MB_ICONEXCLAMATION);
306                 return;
307         }
308
309         Campaign_modified = 0;
310         return;
311 }
312
313 void campaign_tree_wnd::OnCpgnFileSaveAs() 
314 {
315         char *old_name = NULL;
316         CString name;
317         CFred_mission_save save;
318
319         Campaign_tree_formp->update();
320         if (Campaign.filename[0])
321                 old_name = Campaign.filename;
322
323         CFileDialog dlg(FALSE, "fc2", old_name, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "FreeSpace Campaign files (*.fc2)|*.fc2||", this);
324         if (dlg.DoModal() == IDOK)
325         {
326                 name = dlg.GetFileName();
327                 if (strlen(name) > MAX_FILENAME_LEN - 1) {
328                         MessageBox("Filename is too long", "Error");
329                         return;
330                 }
331
332                 if (!strlen(name)){
333                         return;         
334                 }
335
336                 string_copy(Campaign.filename, name, MAX_FILENAME_LEN);
337                 if (save.save_campaign_file(Campaign.filename))
338                 {
339                         MessageBox("An error occured while saving!", "Error", MB_OK | MB_ICONEXCLAMATION);
340                         return;
341                 }
342
343                 Campaign_modified = 0;
344         }
345 }
346
347 void campaign_tree_wnd::OnCpgnFileNew()
348 {
349         if (Campaign_modified)
350                 if (save_modified())
351                         return;
352
353         Campaign.filename[0] = 0;
354         Campaign.num_missions = 0;
355         Campaign.num_players = 0;
356         strcpy(Campaign.name, "Unnamed");
357         Campaign_tree_viewp->free_links();
358         Campaign_tree_formp->initialize();
359         Campaign_modified = 0;
360 }
361
362 void campaign_tree_wnd::OnClose() 
363 {
364         if (Campaign_modified)
365                 if (save_modified())
366                         return;
367
368         CFrameWnd::OnClose();
369 }
370
371 void campaign_tree_wnd::OnClose2() 
372 {
373         if (Campaign_modified)
374                 if (save_modified())
375                         return;
376
377         DestroyWindow();
378 }
379
380 // returns 0 for success and 1 for cancel
381 int campaign_tree_wnd::save_modified()
382 {
383         int r;
384
385         r = MessageBox("This campaign has been modified.  Save changes first?", "Campaign Modified",
386                 MB_YESNOCANCEL | MB_ICONQUESTION);
387
388         if (r == IDCANCEL)
389                 return 1;
390
391         if (r == IDYES) {
392                 OnCpgnFileSave();
393                 if (Campaign_modified)  // error occured in saving.
394                         return 1;
395         }
396
397         Campaign_modified = 0;
398         return 0;
399 }
400
401 void campaign_tree_wnd::OnErrorChecker() 
402 {
403         Campaign_tree_formp->save_tree(0);
404         error_checker();
405         if (!g_err)
406                 MessageBox("No errors detected in campaign", "Woohoo!");
407 }
408
409 int campaign_tree_wnd::error_checker()
410 {
411         int i, j, z;
412         int mcount[MAX_CAMPAIGN_MISSIONS], true_at[MAX_CAMPAIGN_MISSIONS];
413
414         for (i=0; i<MAX_CAMPAIGN_MISSIONS; i++) {
415                 mcount[i] = 0;
416                 true_at[i] = -1;
417         }
418
419         g_err = 0;
420         for (i=0; i<Total_links; i++) {
421                 if ( (Links[i].from < 0) || (Links[i].from >= Campaign.num_missions) )
422                         return internal_error("Branch #%d has illegal source mission", i);
423                 if ( (Links[i].to < -1) || (Links[i].to >= Campaign.num_missions) )
424                         return internal_error("Branch #%d has illegal target mission", i);
425                 Sexp_useful_number = Links[i].from;
426                 if (fred_check_sexp(Links[i].sexp, OPR_BOOL, "formula of branch #%d", i))
427                         return -1;
428
429                 z = Links[i].from;
430                 mcount[z]++;
431                 if (Links[i].sexp == Locked_sexp_false)
432                         if (error("Mission \"%s\" branch %d is always false", Campaign.missions[z].name, mcount[z]))
433                                 return 1;
434
435                 if (Links[i].sexp == Locked_sexp_true) {
436                         if (true_at[z] >= 0)
437                                 if (error("Mission \"%s\" branch %d is true but is not last branch", Campaign.missions[z].name, true_at[z]))
438                                         return 1;
439
440                         true_at[z] = mcount[z];
441                 }
442         }
443
444         // check that all missions in a multiplayer game have the same number of players
445         if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
446                 for (i = 0; i < Campaign.num_missions; i++ ) {
447                         mission a_mission;
448
449                         get_mission_info(Campaign.missions[i].name, &a_mission);
450                         if ( a_mission.num_players != Campaign.num_players ) {
451                                 if ( error("Mission \"%s\" has %d players.  Multiplayer campaign allows %d", Campaign.missions[i].name, a_mission.num_players, Campaign.num_players) )
452                                         return 1;
453                         }
454                 }
455         }
456
457         for (i=0; i<Campaign.num_missions; i++)
458                 if (mcount[i] && true_at[i] < mcount[i])
459                         if (error("Mission \"%s\" last branch isn't set to true", Campaign.missions[i].name))
460                                 return 1;
461
462         for (i=z=0; i<Campaign.num_missions; i++) {
463                 for (j=0; j<Campaign.num_missions; j++)
464                         if ((i != j) && !stricmp(Campaign.missions[i].name, Campaign.missions[j].name))
465                                 return internal_error("Mission \"%s\" is listed twice in campaign", Campaign.missions[i].name);
466
467                 if (!Campaign.missions[i].level)
468                         z++;
469         }
470
471         if (!z)
472                 if (error("No top level mission present in tree"))
473                         return 1;
474
475         if (z > 1)
476                 return internal_error("More than one top level mission present in tree");
477
478         return 0;
479 }
480
481 int campaign_tree_wnd::error(char *msg, ...)
482 {
483         char buf[2048];
484         va_list args;
485
486         g_err++;
487         va_start(args, msg);
488         vsprintf(buf, msg, args);
489         va_end(args);
490
491         if (MessageBox(buf, "Error", MB_OKCANCEL | MB_ICONEXCLAMATION) == IDOK)
492                 return 0;
493
494         return 1;
495 }
496
497 int campaign_tree_wnd::internal_error(char *msg, ...)
498 {
499         char buf[2048], buf2[2048];
500         va_list args;
501
502         g_err++;
503         va_start(args, msg);
504         vsprintf(buf, msg, args);
505         va_end(args);
506
507         sprintf(buf2, "%s\n\nThis is an internal error.  Please let Hoffoss\n"
508                 "know about this so he can fix it.  Click cancel to debug.", buf);
509
510         if (MessageBox(buf2, "Internal Error", MB_OKCANCEL | MB_ICONEXCLAMATION) == IDCANCEL)
511                 Int3();  // drop to debugger so the problem can be analyzed.
512
513         return -1;
514 }
515
516 int campaign_tree_wnd::fred_check_sexp(int sexp, int type, char *msg, ...)
517 {
518         char buf[512], buf2[2048], buf3[4096];
519         int err = 0, z, faulty_node;
520         va_list args;
521
522         va_start(args, msg);
523         vsprintf(buf, msg, args);
524         va_end(args);
525
526         if (sexp == -1)
527                 return 0;
528
529         z = check_sexp_syntax(sexp, type, 1, &faulty_node, SEXP_MODE_CAMPAIGN);
530         if (!z)
531                 return 0;
532
533         convert_sexp_to_string(sexp, buf2, SEXP_ERROR_CHECK_MODE);
534         sprintf(buf3, "Error in %s: %s\n\nIn sexpression: %s\n\n(Error appears to be: %s)",
535                 buf, sexp_error_message(z), buf2, Sexp_nodes[faulty_node].text);
536
537         if (z < 0 && z > -100)
538                 err = 1;
539
540         if (err)
541                 return internal_error(buf3);
542
543         if (error(buf3))
544                 return 1;
545
546         return 0;
547 }
548
549 // code to deal with the initial ships that a player can choose
550 void campaign_tree_wnd::OnInitialShips() 
551 {
552         InitialShips isd(Campaign_tree_formp);
553
554         isd.m_initial_items = INITIAL_SHIPS;
555         isd.DoModal();
556 }
557
558 void campaign_tree_wnd::OnInitialWeapons() 
559 {
560         InitialShips isd(Campaign_tree_formp);
561
562         isd.m_initial_items = INITIAL_WEAPONS;
563         isd.DoModal();
564 }