]> icculus.org git repositories - taylor/freespace2.git/blob - src/fred2/campaigntreewnd.cpp
fix issue with looping audio streams
[taylor/freespace2.git] / src / fred2 / campaigntreewnd.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
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
6  * the source.
7  */
8
9 /*
10  * $Logfile: /Freespace2/code/FRED2/CampaignTreeWnd.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * Campaign display tree window code.  Works very closely with the Campaign editor dialog box.
16  *
17  * $Log$
18  * Revision 1.3  2002/06/09 04:41:16  relnev
19  * added copyright header
20  *
21  * Revision 1.2  2002/05/07 03:16:44  theoddone33
22  * The Great Newline Fix
23  *
24  * Revision 1.1.1.1  2002/05/03 03:28:08  root
25  * Initial import.
26  *
27  * 
28  * 3     1/07/99 1:52p Andsager
29  * Initial check in of Sexp_variables
30  * 
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.
35  * 
36  * 1     10/07/98 3:00p Dave
37  * 
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
41  * only 1 player.
42  * 
43  * 26    8/19/98 9:46a Hoffoss
44  * Added some 'save first?' type checks to the campaign editor.
45  * 
46  * 25    5/26/98 2:32p Hoffoss
47  * Made campaign editor come up with a new campaign instead of loading
48  * built-in one.
49  * 
50  * 24    5/22/98 1:06a Hoffoss
51  * Made Fred not use OLE.
52  * 
53  * 23    4/14/98 11:55a Allender
54  * add end-of-campaign sexpression to allow for mission replay at the end
55  * of campaigns
56  * 
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
61  * 
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"
66  * 
67  * 20    2/24/98 10:23a Johnson
68  * Fixed up some build bugs caused by a multiplayer struct removal.
69  * 
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
73  * 
74  * 18    12/18/97 5:11p Allender
75  * initial support for ship/weapon persistence
76  * 
77  * 17    12/02/97 5:29p Johnson
78  * Fixed bug in branch sexp error checking.  Was using link number instead
79  * of mission number.
80  * 
81  * 16    11/22/97 3:05p Allender
82  * support for new fields for multiplayer in campaign editor
83  * 
84  * 15    9/16/97 4:19p Jasen
85  * Fixed a bug in Fred with Campaign mode.
86  * 
87  * 14    9/01/97 6:31p Hoffoss
88  * Added remaining missing features to campaign editor in Fred.
89  * 
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).
94  * 
95  * 12    8/13/97 5:49p Hoffoss
96  * Fixed bugs, made additions.
97  * 
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.
101  * 
102  * 10    7/09/97 2:28p Hoffoss
103  * Fixed bug with adding new links, and made campaign general info update.
104  * 
105  * 9     5/15/97 12:45p Hoffoss
106  * Extensive changes to fix many little bugs.
107  * 
108  * 8     5/14/97 12:54p Hoffoss
109  * Added sexp tree for campaign branches, branch hilighting, and branch
110  * reordering.
111  * 
112  * 7     5/13/97 12:46p Hoffoss
113  * Added close campaign editor functions, changed global pointer to have
114  * capped first letter.
115  * 
116  * 6     5/13/97 11:13a Hoffoss
117  * Added remaining file menu options to campaign editor.
118  * 
119  * 5     5/13/97 10:52a Hoffoss
120  * Added campaign saving code.
121  * 
122  * 4     5/09/97 9:50a Hoffoss
123  * Routine code check in.
124  * 
125  * 3     5/05/97 9:40a Hoffoss
126  * Campaign editor begun.
127  * 
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.
131  *
132  * $NoKeywords: $
133  */
134
135 #include "stdafx.h"
136 #include "fred.h"
137 #include "campaigntreewnd.h"
138 #include "campaigneditordlg.h"
139 #include "campaigntreeview.h"
140 #include "management.h"
141 #include "mainfrm.h"
142 #include "fredview.h"
143 #include "missionsave.h"
144 #include "initialships.h"
145 #include "missionparse.h"
146
147 #ifdef _DEBUG
148 #define new DEBUG_NEW
149 #undef THIS_FILE
150 static char THIS_FILE[] = __FILE__;
151 #endif
152
153 int Mission_filename_cb_format;
154 int Campaign_modified = 0;
155 int Bypass_clear_mission;
156 campaign_tree_wnd *Campaign_wnd = NULL;
157
158 IMPLEMENT_DYNCREATE(campaign_tree_wnd, CFrameWnd)
159
160 /////////////////////////////////////////////////////////////////////////////
161 // campaign_tree_wnd
162
163 campaign_tree_wnd::campaign_tree_wnd()
164 {
165         Bypass_clear_mission = 0;
166 }
167
168 campaign_tree_wnd::~campaign_tree_wnd()
169 {
170 }
171
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)
176         ON_WM_DESTROY()
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)
182         ON_WM_CLOSE()
183         ON_COMMAND(ID_INITIAL_SHIPS, OnInitialShips)
184         ON_COMMAND(ID_INITIAL_WEAPONS, OnInitialWeapons)
185         //}}AFX_MSG_MAP
186 END_MESSAGE_MAP()
187
188 /////////////////////////////////////////////////////////////////////////////
189 // campaign_tree_wnd message handlers
190
191 BOOL campaign_tree_wnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext)
192 {
193         CSize s;
194
195         LoadAccelTable("IDR_ACC_CAMPAIGN");
196         Mission_filename_cb_format = RegisterClipboardFormat("Mission Filename");
197         Campaign_modified = 0;
198         clear_mission();
199
200         // create a splitter with 1 row, 2 columns
201         if (!m_splitter.CreateStatic(this, 1, 2))
202         {
203                 TRACE0("Failed to CreateStaticSplitter\n");
204                 return FALSE;
205         }
206
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))
209         {
210                 TRACE0("Failed to create first pane\n");
211                 return FALSE;
212         }
213
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))
216         {
217                 TRACE0("Failed to create second pane\n");
218                 return FALSE;
219         }
220
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();
227
228         // activate the input view
229         SetActiveView(Campaign_tree_formp);
230         OnCpgnFileNew();
231 //      Campaign_tree_formp->load_campaign();
232         Fred_main_wnd->EnableWindow(FALSE);
233         return TRUE;
234 }
235
236 void campaign_tree_wnd::OnUpdateCpgnFileOpen(CCmdUI* pCmdUI) 
237 {
238         pCmdUI->Enable();
239 }
240
241 void campaign_tree_wnd::OnCpgnFileOpen() 
242 {
243         CString name;
244
245         if (Campaign_modified)
246                 if (save_modified())
247                         return;
248
249         CFileDialog dlg(TRUE, "fc2", NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "FreeSpace Campaign files (*.fc2)|*.fc2||", this);
250         if (dlg.DoModal() == IDOK)
251         {
252                 name = dlg.GetFileName();
253                 if (strlen(name) > MAX_FILENAME_LEN - 1) {
254                         MessageBox("Filename is too long", "Error");
255                         return;
256                 }
257
258                 if (!strlen(name))
259                         return;
260
261                 string_copy(Campaign.filename, name, MAX_FILENAME_LEN);
262                 Campaign_tree_formp->load_campaign();
263         }
264 }
265
266 void campaign_tree_wnd::OnDestroy()
267 {
268         CString str;
269
270         OnCpgnFileNew();
271         Fred_main_wnd->EnableWindow(TRUE);
272 //      if (!Bypass_clear_mission)
273 //              create_new_mission();
274         str = FREDDoc_ptr->GetPathName();
275         if (str.IsEmpty())
276                 create_new_mission();
277         else
278                 FREDDoc_ptr->OnOpenDocument(str);
279
280         CFrameWnd::OnDestroy();
281         Campaign_wnd = NULL;
282         Fred_main_wnd->SetFocus();
283 }
284
285 void campaign_tree_wnd::OnCpgnFileSave() 
286 {
287         CFred_mission_save save;
288
289         Campaign_tree_formp->update();
290         if (!Campaign.filename[0]) {
291                 OnCpgnFileSaveAs();
292                 return;
293         }
294
295         // sanity checking for multiplayer      
296         /*
297         if ( Campaign.type == TYPE_MULTI_PLAYER ) {
298         
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);
301                         return;
302                 }
303                 if ( (Campaign.mc_info.max_players < 0) || (Campaign.mc_info.max_players > MAX_PLAYERS) ) {
304                         char buf[256];
305
306                         sprintf(buf, "Max players must be > 0 and <= %d", MAX_PLAYERS );
307                         MessageBox(buf, "Error", MB_OK | MB_ICONEXCLAMATION);
308                         return;
309                 }
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);
312                         return;
313                 }
314         }
315         */      
316
317         if (save.save_campaign_file(Campaign.filename))
318         {
319                 MessageBox("An error occured while saving!", "Error", MB_OK | MB_ICONEXCLAMATION);
320                 return;
321         }
322
323         Campaign_modified = 0;
324         return;
325 }
326
327 void campaign_tree_wnd::OnCpgnFileSaveAs() 
328 {
329         char *old_name = NULL;
330         CString name;
331         CFred_mission_save save;
332
333         Campaign_tree_formp->update();
334         if (Campaign.filename[0])
335                 old_name = Campaign.filename;
336
337         CFileDialog dlg(FALSE, "fc2", old_name, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "FreeSpace Campaign files (*.fc2)|*.fc2||", this);
338         if (dlg.DoModal() == IDOK)
339         {
340                 name = dlg.GetFileName();
341                 if (strlen(name) > MAX_FILENAME_LEN - 1) {
342                         MessageBox("Filename is too long", "Error");
343                         return;
344                 }
345
346                 if (!strlen(name)){
347                         return;         
348                 }
349
350                 string_copy(Campaign.filename, name, MAX_FILENAME_LEN);
351                 if (save.save_campaign_file(Campaign.filename))
352                 {
353                         MessageBox("An error occured while saving!", "Error", MB_OK | MB_ICONEXCLAMATION);
354                         return;
355                 }
356
357                 Campaign_modified = 0;
358         }
359 }
360
361 void campaign_tree_wnd::OnCpgnFileNew()
362 {
363         if (Campaign_modified)
364                 if (save_modified())
365                         return;
366
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;
374 }
375
376 void campaign_tree_wnd::OnClose() 
377 {
378         if (Campaign_modified)
379                 if (save_modified())
380                         return;
381
382         CFrameWnd::OnClose();
383 }
384
385 void campaign_tree_wnd::OnClose2() 
386 {
387         if (Campaign_modified)
388                 if (save_modified())
389                         return;
390
391         DestroyWindow();
392 }
393
394 // returns 0 for success and 1 for cancel
395 int campaign_tree_wnd::save_modified()
396 {
397         int r;
398
399         r = MessageBox("This campaign has been modified.  Save changes first?", "Campaign Modified",
400                 MB_YESNOCANCEL | MB_ICONQUESTION);
401
402         if (r == IDCANCEL)
403                 return 1;
404
405         if (r == IDYES) {
406                 OnCpgnFileSave();
407                 if (Campaign_modified)  // error occured in saving.
408                         return 1;
409         }
410
411         Campaign_modified = 0;
412         return 0;
413 }
414
415 void campaign_tree_wnd::OnErrorChecker() 
416 {
417         Campaign_tree_formp->save_tree(0);
418         error_checker();
419         if (!g_err)
420                 MessageBox("No errors detected in campaign", "Woohoo!");
421 }
422
423 int campaign_tree_wnd::error_checker()
424 {
425         int i, j, z;
426         int mcount[MAX_CAMPAIGN_MISSIONS], true_at[MAX_CAMPAIGN_MISSIONS];
427
428         for (i=0; i<MAX_CAMPAIGN_MISSIONS; i++) {
429                 mcount[i] = 0;
430                 true_at[i] = -1;
431         }
432
433         g_err = 0;
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))
441                         return -1;
442
443                 z = Links[i].from;
444                 mcount[z]++;
445                 if (Links[i].sexp == Locked_sexp_false)
446                         if (error("Mission \"%s\" branch %d is always false", Campaign.missions[z].name, mcount[z]))
447                                 return 1;
448
449                 if (Links[i].sexp == Locked_sexp_true) {
450                         if (true_at[z] >= 0)
451                                 if (error("Mission \"%s\" branch %d is true but is not last branch", Campaign.missions[z].name, true_at[z]))
452                                         return 1;
453
454                         true_at[z] = mcount[z];
455                 }
456         }
457
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++ ) {
461                         mission a_mission;
462
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) )
466                                         return 1;
467                         }
468                 }
469         }
470
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))
474                                 return 1;
475
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);
480
481                 if (!Campaign.missions[i].level)
482                         z++;
483         }
484
485         if (!z)
486                 if (error("No top level mission present in tree"))
487                         return 1;
488
489         if (z > 1)
490                 return internal_error("More than one top level mission present in tree");
491
492         return 0;
493 }
494
495 int campaign_tree_wnd::error(char *msg, ...)
496 {
497         char buf[2048];
498         va_list args;
499
500         g_err++;
501         va_start(args, msg);
502         vsprintf(buf, msg, args);
503         va_end(args);
504
505         if (MessageBox(buf, "Error", MB_OKCANCEL | MB_ICONEXCLAMATION) == IDOK)
506                 return 0;
507
508         return 1;
509 }
510
511 int campaign_tree_wnd::internal_error(char *msg, ...)
512 {
513         char buf[2048], buf2[2048];
514         va_list args;
515
516         g_err++;
517         va_start(args, msg);
518         vsprintf(buf, msg, args);
519         va_end(args);
520
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);
523
524         if (MessageBox(buf2, "Internal Error", MB_OKCANCEL | MB_ICONEXCLAMATION) == IDCANCEL)
525                 Int3();  // drop to debugger so the problem can be analyzed.
526
527         return -1;
528 }
529
530 int campaign_tree_wnd::fred_check_sexp(int sexp, int type, char *msg, ...)
531 {
532         char buf[512], buf2[2048], buf3[4096];
533         int err = 0, z, faulty_node;
534         va_list args;
535
536         va_start(args, msg);
537         vsprintf(buf, msg, args);
538         va_end(args);
539
540         if (sexp == -1)
541                 return 0;
542
543         z = check_sexp_syntax(sexp, type, 1, &faulty_node, SEXP_MODE_CAMPAIGN);
544         if (!z)
545                 return 0;
546
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);
550
551         if (z < 0 && z > -100)
552                 err = 1;
553
554         if (err)
555                 return internal_error(buf3);
556
557         if (error(buf3))
558                 return 1;
559
560         return 0;
561 }
562
563 // code to deal with the initial ships that a player can choose
564 void campaign_tree_wnd::OnInitialShips() 
565 {
566         InitialShips isd(Campaign_tree_formp);
567
568         isd.m_initial_items = INITIAL_SHIPS;
569         isd.DoModal();
570 }
571
572 void campaign_tree_wnd::OnInitialWeapons() 
573 {
574         InitialShips isd(Campaign_tree_formp);
575
576         isd.m_initial_items = INITIAL_WEAPONS;
577         isd.DoModal();
578 }
579