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