]> icculus.org git repositories - taylor/freespace2.git/blob - src/fred2/campaigneditordlg.cpp
Initial revision
[taylor/freespace2.git] / src / fred2 / campaigneditordlg.cpp
1 // CampaignEditorDlg.cpp : implementation file
2 //
3
4 #include <setjmp.h>
5 #include "stdafx.h"
6 #include "fred.h"
7 #include "campaigneditordlg.h"
8 #include "campaigntreeview.h"
9 #include "campaigntreewnd.h"
10 #include "management.h"
11 #include "freddoc.h"
12 #include "parselo.h"
13 #include "missiongoals.h"
14
15 #ifdef _DEBUG
16 #define new DEBUG_NEW
17 #undef THIS_FILE
18 static char THIS_FILE[] = __FILE__;
19 #endif
20
21 int Cur_campaign_mission = -1;
22 int Cur_campaign_link = -1;
23
24 // determine the node number that would be allocated without actually allocating it yet.
25 int campaign_sexp_tree::get_new_node_position()
26 {
27         int i;
28
29         for (i=0; i<MAX_SEXP_TREE_SIZE; i++){
30                 if (nodes[i].type == SEXPT_UNUSED){
31                         return i;
32                 }
33         }
34
35         return -1;
36 }
37
38 // construct tree nodes for an sexp, adding them to the list and returning first node
39 int campaign_sexp_tree::load_sub_tree(int index)
40 {
41         int cur;
42
43         if (index < 0) {
44                 cur = allocate_node(-1);
45                 set_node(cur, (SEXPT_OPERATOR  | SEXPT_VALID), "do-nothing");  // setup a default tree if none
46                 return cur;
47         }
48
49         // assumption: first token is an operator.  I require this because it would cause problems
50         // with child/parent relations otherwise, and it should be this way anyway, since the
51         // return type of the whole sexp is boolean, and only operators can satisfy this.
52         Assert(Sexp_nodes[index].subtype == SEXP_ATOM_OPERATOR);
53         cur = get_new_node_position();
54         load_branch(index, -1);
55         return cur;
56 }
57
58 /////////////////////////////////////////////////////////////////////////////
59 // campaign_editor
60
61 IMPLEMENT_DYNCREATE(campaign_editor, CFormView)
62
63 campaign_editor *Campaign_tree_formp;
64
65 campaign_editor::campaign_editor()
66         : CFormView(campaign_editor::IDD)
67 {
68         //{{AFX_DATA_INIT(campaign_editor)
69         m_name = _T("");
70         m_type = -1;
71         m_num_players = _T("");
72         m_desc = _T("");
73         m_loop_desc = _T("");
74         m_loop_brief_anim = _T("");
75         m_loop_brief_sound = _T("");
76         //}}AFX_DATA_INIT
77
78         m_tree.m_mode = MODE_CAMPAIGN;
79         m_num_links = 0;
80         m_tree.link_modified(&Campaign_modified);
81         m_last_mission = -1;
82 }
83
84 campaign_editor::~campaign_editor()
85 {
86 }
87
88 void campaign_editor::DoDataExchange(CDataExchange* pDX)
89 {
90         CFormView::DoDataExchange(pDX);
91         //{{AFX_DATA_MAP(campaign_editor)
92         DDX_Control(pDX, IDC_SEXP_TREE, m_tree);
93         DDX_Control(pDX, IDC_FILELIST, m_filelist);
94         DDX_Text(pDX, IDC_NAME, m_name);
95         DDX_CBIndex(pDX, IDC_CAMPAIGN_TYPE, m_type);
96         DDX_Text(pDX, IDC_NUM_PLAYERS, m_num_players);
97         DDX_Text(pDX, IDC_DESC2, m_desc);
98         DDX_Text(pDX, IDC_MISSISON_LOOP_DESC, m_loop_desc);
99         DDX_Text(pDX, IDC_LOOP_BRIEF_ANIM, m_loop_brief_anim);
100         DDX_Text(pDX, IDC_LOOP_BRIEF_SOUND, m_loop_brief_sound);
101         //}}AFX_DATA_MAP
102
103         DDV_MaxChars(pDX, m_desc, MISSION_DESC_LENGTH - 1);
104         DDV_MaxChars(pDX, m_loop_desc, MISSION_DESC_LENGTH - 1);        
105 }
106
107 BEGIN_MESSAGE_MAP(campaign_editor, CFormView)
108         //{{AFX_MSG_MAP(campaign_editor)
109         ON_BN_CLICKED(ID_LOAD, OnLoad)
110         ON_BN_CLICKED(IDC_ALIGN, OnAlign)
111         ON_BN_CLICKED(ID_CPGN_CLOSE, OnCpgnClose)
112         ON_NOTIFY(NM_RCLICK, IDC_SEXP_TREE, OnRclickTree)
113         ON_NOTIFY(TVN_BEGINLABELEDIT, IDC_SEXP_TREE, OnBeginlabeleditSexpTree)
114         ON_NOTIFY(TVN_ENDLABELEDIT, IDC_SEXP_TREE, OnEndlabeleditSexpTree)
115         ON_NOTIFY(TVN_SELCHANGED, IDC_SEXP_TREE, OnSelchangedSexpTree)
116         ON_BN_CLICKED(IDC_MOVE_UP, OnMoveUp)
117         ON_BN_CLICKED(IDC_MOVE_DOWN, OnMoveDown)
118         ON_COMMAND(ID_END_EDIT, OnEndEdit)
119         ON_EN_CHANGE(IDC_BRIEFING_CUTSCENE, OnChangeBriefingCutscene)
120         ON_CBN_SELCHANGE(IDC_CAMPAIGN_TYPE, OnSelchangeType)
121         ON_BN_CLICKED(IDC_GALATEA, OnGalatea)
122         ON_BN_CLICKED(IDC_BASTION, OnBastion)
123         ON_BN_CLICKED(IDC_TOGGLE_LOOP, OnToggleLoop)
124         ON_BN_CLICKED(IDC_LOOP_BRIEF_BROWSE, OnBrowseLoopAni)
125         ON_BN_CLICKED(IDC_LOOP_BRIEF_SOUND_BROWSE, OnBrowseLoopSound)
126         //}}AFX_MSG_MAP
127 END_MESSAGE_MAP()
128
129 /////////////////////////////////////////////////////////////////////////////
130 // campaign_editor diagnostics
131
132 #ifdef _DEBUG
133 void campaign_editor::AssertValid() const
134 {
135         CFormView::AssertValid();
136 }
137
138 void campaign_editor::Dump(CDumpContext& dc) const
139 {
140         CFormView::Dump(dc);
141 }
142 #endif //_DEBUG
143
144 /////////////////////////////////////////////////////////////////////////////
145 // campaign_editor message handlers
146
147 void campaign_editor::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
148 {
149         UpdateData(FALSE);
150 }
151
152 void campaign_editor::OnLoad() 
153 {
154         char buf[512];
155
156         if (Cur_campaign_mission < 0){
157                 return;
158         }
159
160         if (Campaign_modified){
161                 if (Campaign_wnd->save_modified()){
162                         return;
163                 }
164         }
165
166         GetCurrentDirectory(512, buf);
167         strcat(buf, "\\");
168         strcat(buf, Campaign.missions[Cur_campaign_mission].name);
169         FREDDoc_ptr->SetPathName(buf);
170         Campaign_wnd->DestroyWindow();
171
172 //              if (FREDDoc_ptr->OnOpenDocument(Campaign.missions[Cur_campaign_mission].name)) {
173 //                      Bypass_clear_mission = 1;
174 //                      Campaign_wnd->DestroyWindow();
175 //
176 //              } else {
177 //                      MessageBox("Failed to load mission!", "Error");
178 //              }
179 }
180
181 BOOL campaign_editor::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext) 
182 {
183         int i;
184         BOOL r;
185         CComboBox *box;
186
187         r = CFormView::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext);
188         if (r) {
189                 box = (CComboBox *) GetDlgItem(IDC_CAMPAIGN_TYPE);
190                 box->ResetContent();
191                 for (i=0; i<MAX_CAMPAIGN_TYPES; i++){
192                         box->AddString(campaign_types[i]);
193                 }
194         }
195
196         return r;
197 }
198
199 void campaign_editor::load_campaign()
200 {
201         Cur_campaign_mission = Cur_campaign_link = -1;
202         load_tree(0);
203
204         if (!strlen(Campaign.filename))
205                 strcpy(Campaign.filename, BUILTIN_CAMPAIGN);
206
207         if (mission_campaign_load(Campaign.filename, 0)) {
208                 MessageBox("Couldn't open Campaign file!", "Error");
209                 Campaign_wnd->OnCpgnFileNew();
210                 return;
211         }
212
213         Campaign_modified = 0;
214         Campaign_tree_viewp->construct_tree();
215         initialize();
216 }
217
218 void campaign_editor::OnAlign() 
219 {
220         Campaign_tree_viewp->sort_elements();
221         Campaign_tree_viewp->realign_tree();
222         Campaign_tree_viewp->Invalidate();
223 }
224
225 void campaign_editor::initialize( int init_files )
226 {
227         Cur_campaign_mission = Cur_campaign_link = -1;
228         m_tree.setup((CEdit *) GetDlgItem(IDC_HELP_BOX));
229         load_tree(0);
230         Campaign_tree_viewp->initialize();
231
232         // only initialize the file dialog box when the parameter says to.  This should
233         // only happen when a campaign type changes
234         if ( init_files ){
235                 m_filelist.initialize();
236         }
237
238         m_name = Campaign.name;
239         m_type = Campaign.type;
240         m_num_players.Format("%d", Campaign.num_players);
241
242         if (Campaign.desc) {
243                 m_desc = convert_multiline_string(Campaign.desc);
244         } else {
245                 m_desc = _T("");
246         }
247
248         m_loop_desc = _T("");
249
250         m_loop_brief_anim = _T("");     
251         m_loop_brief_sound = _T("");    
252
253         // single player should hide the two dialog items about number of players allowed
254         if ( m_type == CAMPAIGN_TYPE_SINGLE ) {
255                 GetDlgItem(IDC_NUM_PLAYERS)->ShowWindow( SW_HIDE );
256                 GetDlgItem(IDC_PLAYERS_LABEL)->ShowWindow( SW_HIDE );
257         } else {
258                 GetDlgItem(IDC_NUM_PLAYERS)->ShowWindow( SW_SHOW );
259                 GetDlgItem(IDC_PLAYERS_LABEL)->ShowWindow( SW_SHOW );
260         }
261
262         UpdateData(FALSE);
263 }
264
265 void campaign_editor::mission_selected(int num)
266 {
267         CEdit *bc_dialog;
268
269         bc_dialog = (CEdit *) GetDlgItem(IDC_BRIEFING_CUTSCENE);
270
271         // clear out the text for the briefing cutscene, and put in new text if specified
272         bc_dialog->SetWindowText("");
273         if ( strlen(Campaign.missions[num].briefing_cutscene) )
274                 bc_dialog->SetWindowText(Campaign.missions[num].briefing_cutscene);
275
276         if (Campaign.missions[num].flags & CMISSION_FLAG_BASTION) {
277                 ((CButton *) GetDlgItem(IDC_GALATEA)) -> SetCheck(0);
278                 ((CButton *) GetDlgItem(IDC_BASTION)) -> SetCheck(1);
279
280         } else {
281                 ((CButton *) GetDlgItem(IDC_GALATEA)) -> SetCheck(1);
282                 ((CButton *) GetDlgItem(IDC_BASTION)) -> SetCheck(0);
283         }
284 }
285
286 void campaign_editor::update()
287 {
288         char buf[MISSION_DESC_LENGTH];
289
290         // get data from dlog box
291         UpdateData(TRUE);
292
293         // update campaign name
294         string_copy(Campaign.name, m_name, NAME_LENGTH);
295         Campaign.type = m_type;
296
297         // update campaign desc
298         deconvert_multiline_string(buf, m_desc, MISSION_DESC_LENGTH);
299         if (Campaign.desc) {
300                 free(Campaign.desc);
301         }
302
303         Campaign.desc = NULL;
304         if (strlen(buf)) {
305                 Campaign.desc = strdup(buf);
306         }
307
308         // maybe update mission loop text
309         save_loop_desc_window();
310
311         // set the number of players in a multiplayer mission equal to the number of players in the first mission
312         if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
313                 if ( Campaign.num_missions == 0 ) {
314                         Campaign.num_players = 0;
315                 } else {
316                         mission a_mission;
317
318                         get_mission_info(Campaign.missions[0].name, &a_mission);
319                         Campaign.num_players = a_mission.num_players;
320                 }
321         }
322 }
323
324 void campaign_editor::OnCpgnClose() 
325 {
326         Campaign_wnd->OnClose();
327 }
328
329 void campaign_editor::load_tree(int save_first)
330 {
331         char text[80];
332         int i;
333         HTREEITEM h;
334
335         if (save_first)
336                 save_tree();
337
338         m_tree.clear_tree();
339         m_tree.DeleteAllItems();
340         m_num_links = 0;
341         UpdateData(TRUE);
342         update_loop_desc_window();
343
344         m_last_mission = Cur_campaign_mission;
345         if (Cur_campaign_mission < 0) {
346                 GetDlgItem(IDC_SEXP_TREE)->EnableWindow(FALSE);
347                 GetDlgItem(IDC_BRIEFING_CUTSCENE)->EnableWindow(FALSE);
348                 return;
349         }
350
351         GetDlgItem(IDC_SEXP_TREE)->EnableWindow(TRUE);
352         GetDlgItem(IDC_BRIEFING_CUTSCENE)->EnableWindow(TRUE);
353
354         GetDlgItem(IDC_MISSISON_LOOP_DESC)->EnableWindow(FALSE);        
355         GetDlgItem(IDC_LOOP_BRIEF_ANIM)->EnableWindow(FALSE);
356         GetDlgItem(IDC_LOOP_BRIEF_SOUND)->EnableWindow(FALSE);
357         GetDlgItem(IDC_LOOP_BRIEF_BROWSE)->EnableWindow(FALSE);
358         GetDlgItem(IDC_LOOP_BRIEF_SOUND_BROWSE)->EnableWindow(FALSE);
359
360         for (i=0; i<Total_links; i++) {
361                 if (Links[i].from == Cur_campaign_mission) {
362                         Links[i].node = m_tree.load_sub_tree(Links[i].sexp);
363                         m_num_links++;
364
365                         if (Links[i].from == Links[i].to) {
366                                 strcpy(text, "Repeat mission");
367                         } else if ( (Links[i].to == -1) && (Links[i].from != -1) ) {
368                                 strcpy(text, "End of Campaign");
369                         } else {
370                                 sprintf(text, "Branch to %s", Campaign.missions[Links[i].to].name);
371                         }
372
373                         // insert item into tree
374                         int image, sel_image;
375                         if (Links[i].mission_loop) {
376                                 image = BITMAP_BLUE_DOT;
377                                 sel_image = BITMAP_GREEN_DOT;
378                         } else {
379                                 image = BITMAP_BLACK_DOT;
380                                 sel_image = BITMAP_RED_DOT;
381                         }
382
383                         h = m_tree.insert(text, image, sel_image);
384
385                         m_tree.SetItemData(h, Links[i].node);
386                         m_tree.add_sub_tree(Links[i].node, h);
387                 }
388         }
389 }
390
391 void campaign_editor::OnRclickTree(NMHDR* pNMHDR, LRESULT* pResult) 
392 {
393         m_tree.right_clicked(MODE_CAMPAIGN);
394         *pResult = 0;
395 }
396
397 void campaign_editor::OnBeginlabeleditSexpTree(NMHDR* pNMHDR, LRESULT* pResult) 
398 {
399         TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;
400
401         if (m_tree.edit_label(pTVDispInfo->item.hItem) == 1)    {
402                 *pResult = 0;
403                 Campaign_modified = 1;
404         } else {
405                 *pResult = 1;
406         }
407 }
408
409 void campaign_editor::OnEndlabeleditSexpTree(NMHDR* pNMHDR, LRESULT* pResult) 
410 {
411         TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;
412
413         *pResult = m_tree.end_label_edit(pTVDispInfo->item.hItem, pTVDispInfo->item.pszText);
414 }
415
416 int campaign_editor::handler(int code, int node, char *str)
417 {
418         int i;
419
420         switch (code) {
421         case ROOT_DELETED:
422                 for (i=0; i<Total_links; i++){
423                         if ((Links[i].from == Cur_campaign_mission) && (Links[i].node == node)){
424                                 break;
425                         }
426                 }
427
428                 Campaign_tree_viewp->delete_link(i);
429                 m_num_links--;
430                 return node;
431
432         default:
433                 Int3();
434         }
435
436         return -1;
437 }
438
439 void campaign_editor::save_tree(int clear)
440 {
441         int i;
442
443         if (m_last_mission < 0){
444                 return;  // nothing to save
445         }
446
447         for (i=0; i<Total_links; i++){
448                 if (Links[i].from == m_last_mission) {
449                         sexp_unmark_persistent(Links[i].sexp);
450                         free_sexp2(Links[i].sexp);
451                         Links[i].sexp = m_tree.save_tree(Links[i].node);
452                         sexp_mark_persistent(Links[i].sexp);
453                 }
454         }
455
456         if (clear){
457                 m_last_mission = -1;
458         }
459 }
460
461 void campaign_editor::OnSelchangedSexpTree(NMHDR* pNMHDR, LRESULT* pResult) 
462 {
463         int i, node;
464         HTREEITEM h, h2;
465
466         // get handle of selected item
467         NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
468         h = pNMTreeView->itemNew.hItem;
469         Assert(h);
470
471         // update help on sexp
472         m_tree.update_help(h);
473
474         // get handle of parent
475         while ((h2 = m_tree.GetParentItem(h))>0){
476                 h = h2;
477         }
478
479         // get identifier of parent
480         node = m_tree.GetItemData(h);
481         for (i=0; i<Total_links; i++){
482                 if ((Links[i].from == Cur_campaign_mission) && (Links[i].node == node)){
483                         break;
484                 }
485         }
486
487         if (i == Total_links) {
488                 Cur_campaign_link = -1;
489                 return;
490         }
491
492         // update mission loop text
493         UpdateData(TRUE);
494         save_loop_desc_window();
495
496         Cur_campaign_link = i;
497         update_loop_desc_window();
498         Campaign_tree_viewp->Invalidate();
499         *pResult = 0;
500 }
501
502 void campaign_editor::OnMoveUp() 
503 {
504         int i, last = -1;
505         campaign_tree_link temp;
506         HTREEITEM h1, h2;
507
508         if (Cur_campaign_link >= 0) {
509                 save_tree();
510                 for (i=0; i<Total_links; i++){
511                         if (Links[i].from == Cur_campaign_mission) {
512                                 if (i == Cur_campaign_link){
513                                         break;
514                                 }
515
516                                 last = i;
517                         }
518                 }
519
520                 if ((last != -1) && (i < Total_links)) {
521                         h1 = m_tree.GetParentItem(m_tree.handle(Links[i].node));
522                         h2 = m_tree.GetParentItem(m_tree.handle(Links[last].node));
523                         m_tree.swap_roots(h1, h2);
524                         m_tree.SelectItem(m_tree.GetParentItem(m_tree.handle(Links[i].node)));
525
526                         temp = Links[last];
527                         Links[last] = Links[i];
528                         Links[i] = temp;
529                         Cur_campaign_link = last;
530                 }
531         }
532
533         GetDlgItem(IDC_SEXP_TREE)->SetFocus();
534 }
535
536 void campaign_editor::OnMoveDown() 
537 {
538         int i, j;
539         campaign_tree_link temp;
540         HTREEITEM h1, h2;
541
542         if (Cur_campaign_link >= 0) {
543                 save_tree();
544                 for (i=0; i<Total_links; i++)
545                         if (Links[i].from == Cur_campaign_mission)
546                                 if (i == Cur_campaign_link)
547                                         break;
548
549                 for (j=i+1; j<Total_links; j++)
550                         if (Links[j].from == Cur_campaign_mission)
551                                 break;
552
553                 if (j < Total_links) {
554                         h1 = m_tree.GetParentItem(m_tree.handle(Links[i].node));
555                         h2 = m_tree.GetParentItem(m_tree.handle(Links[j].node));
556                         m_tree.swap_roots(h1, h2);
557                         m_tree.SelectItem(m_tree.GetParentItem(m_tree.handle(Links[i].node)));
558
559                         temp = Links[j];
560                         Links[j] = Links[i];
561                         Links[i] = temp;
562                         Cur_campaign_link = j;
563                 }
564         }
565
566         GetDlgItem(IDC_SEXP_TREE)->SetFocus();
567 }
568
569 void campaign_editor::swap_handler(int node1, int node2)
570 {
571         int index1, index2;
572         campaign_tree_link temp;
573
574         for (index1=0; index1<Total_links; index1++){
575                 if ((Links[index1].from == Cur_campaign_mission) && (Links[index1].node == node1)){
576                         break;
577                 }
578         }
579
580         Assert(index1 < Total_links);
581         for (index2=0; index2<Total_links; index2++){
582                 if ((Links[index2].from == Cur_campaign_mission) && (Links[index2].node == node2)){
583                         break;
584                 }
585         }
586
587         Assert(index2 < Total_links);
588         temp = Links[index1];
589 //      Links[index1] = Links[index2];
590         while (index1 < index2) {
591                 Links[index1] = Links[index1 + 1];
592                 index1++;
593         }
594
595         while (index1 > index2 + 1) {
596                 Links[index1] = Links[index1 - 1];
597                 index1--;
598         }
599
600         // update Cur_campaign_link
601         Cur_campaign_link = index1;
602
603         Links[index1] = temp;
604 }
605
606 void campaign_editor::insert_handler(int old, int node)
607 {
608         int i;
609
610         for (i=0; i<Total_links; i++){
611                 if ((Links[i].from == Cur_campaign_mission) && (Links[i].node == old)){
612                         break;
613                 }
614         }
615
616         Assert(i < Total_links);
617         Links[i].node = node;
618         return;
619 }
620
621 void campaign_editor::OnEndEdit() 
622 {
623         HWND h;
624         CWnd *w;
625
626         w = GetFocus();
627         if (w) {
628                 h = w->m_hWnd;
629                 GetDlgItem(IDC_SEXP_TREE)->SetFocus();
630                 ::SetFocus(h);
631         }
632 }
633
634 void campaign_editor::OnChangeBriefingCutscene() 
635 {
636         CEdit *bc_dialog;
637
638         bc_dialog = (CEdit *) GetDlgItem(IDC_BRIEFING_CUTSCENE);
639
640         // maybe save off the current cutscene names.
641         if ( Cur_campaign_mission != -1 ) {
642
643                 // see if the contents of the edit box have changed.  Luckily, this returns 0 when the edit box is
644                 // cleared.
645                 if ( bc_dialog->GetModify() ) {
646                         bc_dialog->GetWindowText( Campaign.missions[Cur_campaign_mission].briefing_cutscene, NAME_LENGTH );
647                         Campaign_modified = 1;
648                 }
649         }
650
651 }
652
653 // what to do when changing campaign type
654 void campaign_editor::OnSelchangeType() 
655 {
656         // if campaign type is single player, then disable the multiplayer items
657         update();
658         initialize();
659         Campaign_modified = 1;
660 }
661
662
663 void campaign_editor::OnGalatea() 
664 {
665         if (Cur_campaign_mission >= 0){
666                 Campaign.missions[Cur_campaign_mission].flags &= ~CMISSION_FLAG_BASTION;
667         }
668 }
669
670 void campaign_editor::OnBastion() 
671 {
672         if (Cur_campaign_mission >= 0){
673                 Campaign.missions[Cur_campaign_mission].flags |= CMISSION_FLAG_BASTION;
674         }
675 }
676
677 // update the loop mission text
678 void campaign_editor::save_loop_desc_window()
679 {
680         char buffer[MISSION_DESC_LENGTH];
681
682         // update only if active link and there is a mission has mission loop
683         if ( (Cur_campaign_link >= 0) && Links[Cur_campaign_link].mission_loop ) {
684                 deconvert_multiline_string(buffer, m_loop_desc, MISSION_DESC_LENGTH);
685                 if (Links[Cur_campaign_link].mission_loop_txt) {
686                         free(Links[Cur_campaign_link].mission_loop_txt);
687                 }
688                 if (Links[Cur_campaign_link].mission_loop_brief_anim) {
689                         free(Links[Cur_campaign_link].mission_loop_brief_anim);
690                 }
691                 if (Links[Cur_campaign_link].mission_loop_brief_sound) {
692                         free(Links[Cur_campaign_link].mission_loop_brief_sound);
693                 }
694
695                 if (strlen(buffer)) {
696                         Links[Cur_campaign_link].mission_loop_txt = strdup(buffer);
697                 } else {
698                         Links[Cur_campaign_link].mission_loop_txt = NULL;
699                 }
700
701                 deconvert_multiline_string(buffer, m_loop_brief_anim, MAX_FILENAME_LEN);
702                 if(strlen(buffer)){
703                         Links[Cur_campaign_link].mission_loop_brief_anim = strdup(buffer);
704                 } else {
705                         Links[Cur_campaign_link].mission_loop_brief_anim = NULL;
706                 }
707
708                 deconvert_multiline_string(buffer, m_loop_brief_sound, MAX_FILENAME_LEN);
709                 if(strlen(buffer)){
710                         Links[Cur_campaign_link].mission_loop_brief_sound = strdup(buffer);
711                 } else {
712                         Links[Cur_campaign_link].mission_loop_brief_sound = NULL;
713                 }
714         }
715 }
716
717 void campaign_editor::update_loop_desc_window()
718 {
719         bool enable_loop_desc_window;
720
721         enable_loop_desc_window = false;
722         if ((Cur_campaign_link >= 0) && Links[Cur_campaign_link].mission_loop) {
723                 enable_loop_desc_window = true;
724         }
725
726         // maybe enable description window
727         GetDlgItem(IDC_MISSISON_LOOP_DESC)->EnableWindow(enable_loop_desc_window);
728         GetDlgItem(IDC_LOOP_BRIEF_ANIM)->EnableWindow(enable_loop_desc_window);
729         GetDlgItem(IDC_LOOP_BRIEF_SOUND)->EnableWindow(enable_loop_desc_window);
730         GetDlgItem(IDC_LOOP_BRIEF_BROWSE)->EnableWindow(enable_loop_desc_window);
731         GetDlgItem(IDC_LOOP_BRIEF_SOUND_BROWSE)->EnableWindow(enable_loop_desc_window);
732
733         // set new text
734         if ((Cur_campaign_link >= 0) && Links[Cur_campaign_link].mission_loop_txt && enable_loop_desc_window) {
735                 m_loop_desc = convert_multiline_string(Links[Cur_campaign_link].mission_loop_txt);              
736         } else {
737                 m_loop_desc = _T("");
738         }
739
740         // set new text
741         if ((Cur_campaign_link >= 0) && Links[Cur_campaign_link].mission_loop_brief_anim && enable_loop_desc_window) {
742                 m_loop_brief_anim = convert_multiline_string(Links[Cur_campaign_link].mission_loop_brief_anim);         
743         } else {
744                 m_loop_brief_anim = _T("");
745         }
746
747         // set new text
748         if ((Cur_campaign_link >= 0) && Links[Cur_campaign_link].mission_loop_brief_sound && enable_loop_desc_window) {
749                 m_loop_brief_sound = convert_multiline_string(Links[Cur_campaign_link].mission_loop_brief_sound);
750         } else {
751                 m_loop_brief_sound = _T("");
752         }
753
754         // reset text
755         UpdateData(FALSE);
756 }
757
758 void campaign_editor::OnToggleLoop() 
759 {
760         // check if branch selected
761         if (Cur_campaign_link == -1) {
762                 return;
763         }
764
765         // store mission looop text
766         UpdateData(TRUE);
767
768         if ( (Cur_campaign_link >= 0) && Links[Cur_campaign_link].mission_loop ) {
769                 if (Links[Cur_campaign_link].mission_loop_txt) {
770                         free(Links[Cur_campaign_link].mission_loop_txt);
771                 }
772
773                 if (Links[Cur_campaign_link].mission_loop_brief_anim) {
774                         free(Links[Cur_campaign_link].mission_loop_brief_anim);
775                 }
776
777                 if (Links[Cur_campaign_link].mission_loop_brief_sound) {
778                         free(Links[Cur_campaign_link].mission_loop_brief_sound);
779                 }
780
781                 char buffer[MISSION_DESC_LENGTH];
782                 
783                 deconvert_multiline_string(buffer, m_loop_desc, MISSION_DESC_LENGTH);
784                 if (m_loop_desc && strlen(buffer)) {
785                         Links[Cur_campaign_link].mission_loop_txt = strdup(buffer);
786                 } else {
787                         Links[Cur_campaign_link].mission_loop_txt = NULL;
788                 }
789
790                 deconvert_multiline_string(buffer, m_loop_brief_anim, MISSION_DESC_LENGTH);
791                 if (m_loop_brief_anim && strlen(buffer)) {
792                         Links[Cur_campaign_link].mission_loop_brief_anim = strdup(buffer);
793                 } else {
794                         Links[Cur_campaign_link].mission_loop_brief_anim = NULL;
795                 }
796
797                 deconvert_multiline_string(buffer, m_loop_brief_sound, MISSION_DESC_LENGTH);
798                 if (m_loop_brief_sound && strlen(buffer)) {
799                         Links[Cur_campaign_link].mission_loop_brief_sound = strdup(buffer);
800                 } else {
801                         Links[Cur_campaign_link].mission_loop_brief_sound = NULL;
802                 }
803         }
804
805         // toggle to mission_loop setting
806         Links[Cur_campaign_link].mission_loop = !Links[Cur_campaign_link].mission_loop;
807
808         // reset loop desc window (gray if inactive)
809         update_loop_desc_window();
810
811         // set root icon
812         int bitmap1, bitmap2;
813         if (Links[Cur_campaign_link].mission_loop) {
814                 bitmap2 = BITMAP_GREEN_DOT;
815                 bitmap1 = BITMAP_BLUE_DOT;
816         } else {
817                 bitmap1 = BITMAP_BLACK_DOT;
818                 bitmap2 = BITMAP_RED_DOT;
819         }
820
821         // Search for item and update bitmap
822         HTREEITEM h = m_tree.GetRootItem();
823         while (h) {
824                 if ((int) m_tree.GetItemData(h) == Links[Cur_campaign_link].node) {
825                         m_tree.SetItemImage(h, bitmap1, bitmap2);
826                         break;
827                 }
828
829                 h = m_tree.GetNextSiblingItem(h);
830         }
831
832         // set to redraw
833         Campaign_tree_viewp->Invalidate();
834 }
835
836 void campaign_editor::OnBrowseLoopAni()
837 {
838         int pushed_dir;
839         
840         UpdateData(TRUE);
841
842         // switch directories
843         pushed_dir = cfile_push_chdir(CF_TYPE_INTERFACE);
844         CFileDialog dlg(TRUE, "ani", NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR, "Ani Files (*.ani)|*.ani");
845
846         if (dlg.DoModal() == IDOK) {
847                 m_loop_brief_anim = dlg.GetFileName();
848                 UpdateData(FALSE);
849         }
850
851         // move back to the proper directory
852         if (!pushed_dir){
853                 cfile_pop_dir();        
854         }
855 }
856
857 void campaign_editor::OnBrowseLoopSound()
858 {
859         int pushed_dir;
860         
861         UpdateData(TRUE);
862
863         // switch directories
864         pushed_dir = cfile_push_chdir(CF_TYPE_VOICE_CMD_BRIEF);
865         CFileDialog dlg(TRUE, "wav", NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR, "Wave Files (*.wav)|*.wav||");
866
867         if (dlg.DoModal() == IDOK) {
868                 m_loop_brief_sound = dlg.GetFileName();
869                 UpdateData(FALSE);
870         }
871
872         // move back to the proper directory
873         if (!pushed_dir){
874                 cfile_pop_dir();        
875         }
876 }