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