2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
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
10 * $Logfile: /Freespace2/code/fred2/MissionGoalsDlg.cpp $
15 * Mission goals editor dialog box handling code
18 * Revision 1.3 2002/06/09 04:41:16 relnev
19 * added copyright header
21 * Revision 1.2 2002/05/07 03:16:44 theoddone33
22 * The Great Newline Fix
24 * Revision 1.1.1.1 2002/05/03 03:28:08 root
28 * 4 2/17/99 2:11p Dave
29 * First full run of squad war. All freespace and tracker side stuff
32 * 3 1/19/99 3:57p Andsager
33 * Round 2 of variables
35 * 2 10/07/98 6:28p Dave
36 * Initial checkin. Renamed all relevant stuff to be Fred2 instead of
37 * Fred. Globalized mission and campaign file extensions. Removed Silent
38 * Threat specific code.
40 * 1 10/07/98 3:01p Dave
42 * 1 10/07/98 3:00p Dave
44 * 47 5/23/98 12:20p Sandeep
46 * 46 5/22/98 1:06a Hoffoss
47 * Made Fred not use OLE.
49 * 45 5/20/98 1:04p Hoffoss
50 * Made credits screen use new artwork and removed rating field usage from
51 * Fred (a goal struct member).
53 * 44 3/31/98 12:23a Allender
54 * changed macro names of campaign types to be more descriptive. Added
55 * "team" to objectives dialog for team v. team missions. Added two
56 * distinct multiplayer campaign types
58 * 43 12/08/97 2:03p Hoffoss
59 * Added Fred support for MGF_NO_MUSIC flag in objectives.
61 * 42 11/11/97 4:13p Duncan
62 * changed assert to abort operation instead.
64 * 41 10/10/97 6:21p Hoffoss
65 * Put in Fred support for training object list editing.
67 * 40 10/10/97 2:53p Johnson
68 * Fixed bug with new items being selected before they are fully
69 * registered as added.
71 * 39 10/09/97 1:03p Hoffoss
72 * Renaming events or goals now updates sexp references as well.
74 * 38 9/30/97 12:30p Hoffoss
75 * Drag and drop reordering of sexp tree roots now does an insert after
76 * rather than a swap operation.
78 * 37 9/09/97 3:39p Sandeep
79 * warning level 4 bugs
81 * 36 8/12/97 3:33p Hoffoss
82 * Fixed the "press cancel to go to reference" code to work properly.
84 * 35 8/12/97 2:39p Johnson
85 * fixed bug with sexp tree goal name problem.
87 * 34 8/01/97 3:10p Hoffoss
88 * Made Sexp help hidable.
90 * 33 7/30/97 5:23p Hoffoss
91 * Removed Sexp tree verification code, since it duplicates normal sexp
92 * verification, and is just another set of code to keep maintained.
94 * 32 7/25/97 3:05p Allender
95 * added score field to goals and events editor
97 * 31 7/25/97 2:40p Hoffoss
98 * Fixed bug in sexp tree selection updating handling.
100 * 30 7/24/97 12:45p Hoffoss
101 * Added sexp help system to sexp trees and some dialog boxes.
103 * 29 7/17/97 4:10p Hoffoss
104 * Added drag and drop to sexp trees for reordering root items.
106 * 28 7/16/97 6:30p Hoffoss
107 * Added icons to sexp trees, mainly because I think they will be required
110 * 27 7/07/97 12:04p Allender
111 * mission goal validation.
113 * 26 6/02/97 8:47p Hoffoss
114 * Fixed bug with inserting an operator at root position, but under a
117 * 25 5/20/97 2:28p Hoffoss
118 * Added message box queries for close window operation on all modal
121 * 24 5/01/97 4:12p Hoffoss
122 * Added return handling to dialogs.
124 * 23 4/25/97 12:50p Allender
125 * change globals to new naming conventions
127 * 22 4/23/97 11:55a Hoffoss
128 * Fixed many bugs uncovered while trying to create Mission 6.
130 * 21 4/17/97 2:01p Hoffoss
131 * All dialog box window states are saved between sessions now.
133 * 20 4/11/97 4:22p Hoffoss
134 * Fixed bug in Sexp trees, moved Show starfield option to view menu and
135 * removed preferences dialog box.
137 * 19 4/11/97 10:11a Hoffoss
138 * Name fields supported by Fred for Events and Mission Goals.
140 * 18 4/10/97 3:20p Mike
141 * Change hull damage to be like shields.
143 * 17 4/01/97 5:15p Hoffoss
144 * Fixed errors in max length checks, renaming a wing now renames the
145 * ships in the wing as well, as it should.
147 * 16 2/21/97 5:34p Hoffoss
148 * Added extensive modification detection and fixed a bug in initial
151 * 15 2/17/97 5:28p Hoffoss
152 * Checked RCS headers, added them were missing, changing description to
153 * something better, etc where needed.
161 #include "fredview.h"
162 #include "linklist.h"
164 #include "missiongoalsdlg.h"
165 #include "management.h"
166 #include "operatorargtypeselect.h"
168 #define ID_ADD_SHIPS 9000
169 #define ID_REPLACE_SHIPS 11000
170 #define ID_ADD_WINGS 13000
171 #define ID_REPLACE_WINGS 15000
174 #define new DEBUG_NEW
176 static char THIS_FILE[] = __FILE__;
179 CMissionGoalsDlg *Goal_editor_dlg; // global reference needed by sexp_tree class
181 /////////////////////////////////////////////////////////////////////////////
182 // sexp_goal_tree class member functions
184 // determine the node number that would be allocated without actually allocating it yet.
185 int sexp_goal_tree::get_new_node_position()
189 for (i=0; i<MAX_SEXP_TREE_SIZE; i++)
190 if (nodes[i].type == SEXPT_UNUSED)
196 // construct tree nodes for an sexp, adding them to the list and returning first node
197 int sexp_goal_tree::load_sub_tree(int index)
202 cur = allocate_node(-1);
203 set_node(cur, (SEXPT_OPERATOR | SEXPT_VALID), "true"); // setup a default tree if none
207 // assumption: first token is an operator. I require this because it would cause problems
208 // with child/parent relations otherwise, and it should be this way anyway, since the
209 // return type of the whole sexp is boolean, and only operators can satisfy this.
210 Assert(Sexp_nodes[index].subtype == SEXP_ATOM_OPERATOR);
211 cur = get_new_node_position();
212 load_branch(index, -1);
216 /////////////////////////////////////////////////////////////////////////////
217 // CMissionGoalsDlg dialog class member functions
219 CMissionGoalsDlg::CMissionGoalsDlg(CWnd* pParent /*=NULL*/)
220 : CDialog(CMissionGoalsDlg::IDD, pParent)
222 //{{AFX_DATA_INIT(CMissionGoalsDlg)
223 m_goal_desc = _T("");
225 m_display_goal_types = 0;
227 m_goal_invalid = FALSE;
232 m_goals_tree.m_mode = MODE_GOALS;
234 m_goals_tree.link_modified(&modified);
236 select_sexp_node = -1;
239 BOOL CMissionGoalsDlg::OnInitDialog()
243 CDialog::OnInitDialog(); // let the base class do the default work
245 adjust = -SEXP_HELP_BOX_SIZE;
247 theApp.init_window(&Mission_goals_wnd_data, this, adjust);
248 m_goals_tree.setup((CEdit *) GetDlgItem(IDC_HELP_BOX));
251 if (m_num_goals >= MAX_GOALS)
252 GetDlgItem(IDC_BUTTON_NEW_GOAL)->EnableWindow(FALSE);
254 Goal_editor_dlg = this;
255 i = m_goals_tree.select_sexp_node;
257 GetDlgItem(IDC_GOALS_TREE) -> SetFocus();
258 m_goals_tree.hilite_item(i);
265 void CMissionGoalsDlg::DoDataExchange(CDataExchange* pDX)
267 CDialog::DoDataExchange(pDX);
268 //{{AFX_DATA_MAP(CMissionGoalsDlg)
269 DDX_Control(pDX, IDC_GOALS_TREE, m_goals_tree);
270 DDX_Text(pDX, IDC_GOAL_DESC, m_goal_desc);
271 DDX_CBIndex(pDX, IDC_GOAL_TYPE_DROP, m_goal_type);
272 DDX_CBIndex(pDX, IDC_DISPLAY_GOAL_TYPES_DROP, m_display_goal_types);
273 DDX_Text(pDX, IDC_GOAL_NAME, m_name);
274 DDX_Check(pDX, IDC_GOAL_INVALID, m_goal_invalid);
275 DDX_Text(pDX, IDC_GOAL_SCORE, m_goal_score);
276 DDX_Check(pDX, IDC_NO_MUSIC, m_no_music);
277 DDX_CBIndex(pDX, IDC_OBJ_TEAM, m_team);
279 DDV_MaxChars(pDX, m_goal_desc, MAX_GOAL_TEXT - 1);
280 DDV_MaxChars(pDX, m_name, NAME_LENGTH - 1);
283 BEGIN_MESSAGE_MAP(CMissionGoalsDlg, CDialog)
284 //{{AFX_MSG_MAP(CMissionGoalsDlg)
285 ON_CBN_SELCHANGE(IDC_DISPLAY_GOAL_TYPES_DROP, OnSelchangeDisplayGoalTypesDrop)
286 ON_NOTIFY(TVN_SELCHANGED, IDC_GOALS_TREE, OnSelchangedGoalsTree)
287 ON_NOTIFY(NM_RCLICK, IDC_GOALS_TREE, OnRclickGoalsTree)
288 ON_NOTIFY(TVN_ENDLABELEDIT, IDC_GOALS_TREE, OnEndlabeleditGoalsTree)
289 ON_NOTIFY(TVN_BEGINLABELEDIT, IDC_GOALS_TREE, OnBeginlabeleditGoalsTree)
290 ON_BN_CLICKED(IDC_BUTTON_NEW_GOAL, OnButtonNewGoal)
291 ON_EN_CHANGE(IDC_GOAL_DESC, OnChangeGoalDesc)
292 ON_EN_CHANGE(IDC_GOAL_RATING, OnChangeGoalRating)
293 ON_CBN_SELCHANGE(IDC_GOAL_TYPE_DROP, OnSelchangeGoalTypeDrop)
294 ON_EN_CHANGE(IDC_GOAL_NAME, OnChangeGoalName)
295 ON_BN_CLICKED(ID_OK, OnOk)
297 ON_BN_CLICKED(IDC_GOAL_INVALID, OnGoalInvalid)
298 ON_EN_CHANGE(IDC_GOAL_SCORE, OnChangeGoalScore)
299 ON_BN_CLICKED(IDC_NO_MUSIC, OnNoMusic)
300 ON_CBN_SELCHANGE(IDC_OBJ_TEAM, OnSelchangeTeam)
304 /////////////////////////////////////////////////////////////////////////////
305 // CMissionGoalsDlg message handlers
307 // Initialization: sets up internal working copy of mission goals and goal trees.
308 void CMissionGoalsDlg::load_tree()
312 m_goals_tree.select_sexp_node = select_sexp_node;
313 select_sexp_node = -1;
315 m_goals_tree.clear_tree();
316 m_num_goals = Num_goals;
317 for (i=0; i<Num_goals; i++) {
318 m_goals[i] = Mission_goals[i];
320 if (!(*m_goals[i].name))
321 strcpy(m_goals[i].name, "<unnamed>");
323 m_goals[i].formula = m_goals_tree.load_sub_tree(Mission_goals[i].formula);
326 m_goals_tree.post_load();
331 // create the CTreeCtrl tree from the goal tree, filtering based on m_display_goal_types
332 void CMissionGoalsDlg::create_tree()
337 m_goals_tree.DeleteAllItems();
338 m_goals_tree.reset_handles();
339 for (i=0; i<m_num_goals; i++) {
340 if ( (m_goals[i].type & GOAL_TYPE_MASK) != m_display_goal_types)
343 h = m_goals_tree.insert(m_goals[i].name);
344 m_goals_tree.SetItemData(h, m_goals[i].formula);
345 m_goals_tree.add_sub_tree(m_goals[i].formula, h);
352 // Display goal types selection changed, so update the display
353 void CMissionGoalsDlg::OnSelchangeDisplayGoalTypesDrop()
359 // New tree item selected. Because goal info is displayed for the selected tree item,
360 // we need to update the display when this occurs.
361 void CMissionGoalsDlg::OnSelchangedGoalsTree(NMHDR* pNMHDR, LRESULT* pResult)
364 NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
367 h = pNMTreeView->itemNew.hItem;
371 m_goals_tree.update_help(h);
372 while ((h2 = m_goals_tree.GetParentItem(h)) != 0)
375 z = m_goals_tree.GetItemData(h);
376 for (i=0; i<m_num_goals; i++)
377 if (m_goals[i].formula == z)
380 Assert(i < m_num_goals);
386 // update display info to reflect the currently selected goal.
387 void CMissionGoalsDlg::update_cur_goal()
391 m_goal_desc = _T("");
395 GetDlgItem(IDC_GOAL_TYPE_DROP) -> EnableWindow(FALSE);
396 GetDlgItem(IDC_GOAL_NAME) -> EnableWindow(FALSE);
397 GetDlgItem(IDC_GOAL_DESC) -> EnableWindow(FALSE);
398 GetDlgItem(IDC_GOAL_INVALID)->EnableWindow(FALSE);
399 GetDlgItem(IDC_GOAL_SCORE)->EnableWindow(FALSE);
400 GetDlgItem(IDC_NO_MUSIC)->EnableWindow(FALSE);
401 GetDlgItem(IDC_OBJ_TEAM)->EnableWindow(FALSE);
405 m_name = _T(m_goals[cur_goal].name);
406 m_goal_desc = _T(m_goals[cur_goal].message);
407 m_goal_type = m_goals[cur_goal].type & GOAL_TYPE_MASK;
408 if ( m_goals[cur_goal].type & INVALID_GOAL ){
414 if ( m_goals[cur_goal].flags & MGF_NO_MUSIC ){
420 m_goal_score = m_goals[cur_goal].score;
422 m_team = m_goals[cur_goal].team;
425 GetDlgItem(IDC_GOAL_TYPE_DROP) -> EnableWindow(TRUE);
426 GetDlgItem(IDC_GOAL_NAME) -> EnableWindow(TRUE);
427 GetDlgItem(IDC_GOAL_DESC) -> EnableWindow(TRUE);
428 // GetDlgItem(IDC_GOAL_RATING) -> EnableWindow(TRUE);
429 GetDlgItem(IDC_GOAL_INVALID)->EnableWindow(TRUE);
430 GetDlgItem(IDC_GOAL_SCORE)->EnableWindow(TRUE);
431 GetDlgItem(IDC_NO_MUSIC)->EnableWindow(TRUE);
432 GetDlgItem(IDC_OBJ_TEAM)->EnableWindow(FALSE);
433 if ( The_mission.game_type & MISSION_TYPE_MULTI_TEAMS ){
434 GetDlgItem(IDC_OBJ_TEAM)->EnableWindow(TRUE);
438 // handler for context menu (i.e. a right mouse button click).
439 void CMissionGoalsDlg::OnRclickGoalsTree(NMHDR* pNMHDR, LRESULT* pResult)
441 m_goals_tree.right_clicked(MODE_GOALS);
445 // goal tree item label editing is requested. Determine if it should be allowed.
446 void CMissionGoalsDlg::OnBeginlabeleditGoalsTree(NMHDR* pNMHDR, LRESULT* pResult)
448 TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;
450 if (m_goals_tree.edit_label(pTVDispInfo->item.hItem) == 1) {
458 // Once we finish editing, we need to clean up, which we do here.
459 void CMissionGoalsDlg::OnEndlabeleditGoalsTree(NMHDR* pNMHDR, LRESULT* pResult)
461 TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;
463 *pResult = m_goals_tree.end_label_edit(pTVDispInfo->item.hItem, pTVDispInfo->item.pszText);
466 void CMissionGoalsDlg::OnOK()
474 GetDlgItem(IDC_GOALS_TREE)->SetFocus();
479 int CMissionGoalsDlg::query_modified()
486 if (Num_goals != m_num_goals)
489 for (i=0; i<Num_goals; i++) {
490 if (stricmp(Mission_goals[i].name, m_goals[i].name))
492 if (stricmp(Mission_goals[i].message, m_goals[i].message))
494 if (Mission_goals[i].type != m_goals[i].type)
496 if ( Mission_goals[i].score != m_goals[i].score )
498 if ( Mission_goals[i].team != m_goals[i].team )
505 void CMissionGoalsDlg::OnOk()
507 char buf[256], names[2][MAX_GOALS][NAME_LENGTH];
510 for (i=0; i<Num_goals; i++)
511 free_sexp2(Mission_goals[i].formula);
514 if (query_modified())
518 for (i=0; i<Num_goals; i++)
519 Mission_goals[i].satisfied = 0; // use this as a processed flag
521 // rename all sexp references to old events
522 for (i=0; i<m_num_goals; i++)
524 strcpy(names[0][count], Mission_goals[m_sig[i]].name);
525 strcpy(names[1][count], m_goals[i].name);
527 Mission_goals[m_sig[i]].satisfied = 1;
530 // invalidate all sexp references to deleted events.
531 for (i=0; i<Num_goals; i++)
532 if (!Mission_goals[i].satisfied) {
533 sprintf(buf, "<%s>", Mission_goals[i].name);
534 strcpy(buf + NAME_LENGTH - 2, ">"); // force it to be not too long
535 strcpy(names[0][count], Mission_goals[i].name);
536 strcpy(names[1][count], buf);
540 Num_goals = m_num_goals;
541 for (i=0; i<Num_goals; i++) {
542 Mission_goals[i] = m_goals[i];
543 Mission_goals[i].formula = m_goals_tree.save_tree(Mission_goals[i].formula);
544 if ( The_mission.game_type & MISSION_TYPE_MULTI_TEAMS ) {
545 Assert( Mission_goals[i].team != -1 );
549 // now update all sexp references
551 update_sexp_references(names[0][count], names[1][count], OPF_GOAL_NAME);
553 theApp.record_window_data(&Mission_goals_wnd_data, this);
557 void CMissionGoalsDlg::OnButtonNewGoal()
562 Assert(m_num_goals < MAX_GOALS);
563 m_goals[m_num_goals].type = m_display_goal_types; // this also marks the goal as valid since bit not set
564 m_sig[m_num_goals] = -1;
565 strcpy(m_goals[m_num_goals].name, "Goal name");
566 strcpy(m_goals[m_num_goals].message, "Mission goal text");
567 h = m_goals_tree.insert(m_goals[m_num_goals].name);
569 m_goals_tree.item_index = -1;
570 m_goals_tree.add_operator("true", h);
571 m_goals[m_num_goals].score = 0;
572 index = m_goals[m_num_goals].formula = m_goals_tree.item_index;
573 m_goals_tree.SetItemData(h, index);
575 // team defaults to the first team.
576 m_goals[m_num_goals].team = 0;
580 if (m_num_goals >= MAX_GOALS){
581 GetDlgItem(IDC_BUTTON_NEW_GOAL)->EnableWindow(FALSE);
584 m_goals_tree.SelectItem(h);
587 int CMissionGoalsDlg::handler(int code, int node)
593 for (goal=0; goal<m_num_goals; goal++){
594 if (m_goals[goal].formula == node){
599 Assert(goal < m_num_goals);
600 while (goal < m_num_goals - 1) {
601 m_goals[goal] = m_goals[goal + 1];
602 m_sig[goal] = m_sig[goal + 1];
606 GetDlgItem(IDC_BUTTON_NEW_GOAL)->EnableWindow(TRUE);
616 void CMissionGoalsDlg::OnChangeGoalDesc()
623 string_copy(m_goals[cur_goal].message, m_goal_desc, MAX_GOAL_TEXT);
626 void CMissionGoalsDlg::OnChangeGoalRating()
635 void CMissionGoalsDlg::OnSelchangeGoalTypeDrop()
645 UpdateData(TRUE); // doesn't seem to update unless we do it twice..
647 // change the type being sure to keep the invalid bit if set
648 otype = m_goals[cur_goal].type;
649 m_goals[cur_goal].type = m_goal_type;
650 if ( otype & INVALID_GOAL ){
651 m_goals[cur_goal].type |= INVALID_GOAL;
654 h = m_goals_tree.GetSelectedItem();
656 while ((h2 = m_goals_tree.GetParentItem(h)) != 0){
660 m_goals_tree.DeleteItem(h);
665 void CMissionGoalsDlg::OnChangeGoalName()
674 h = m_goals_tree.GetSelectedItem();
679 while ((h2 = m_goals_tree.GetParentItem(h)) != 0){
683 m_goals_tree.SetItemText(h, m_name);
684 string_copy(m_goals[cur_goal].name, m_name, NAME_LENGTH);
687 void CMissionGoalsDlg::OnCancel()
689 theApp.record_window_data(&Messages_wnd_data, this);
693 void CMissionGoalsDlg::OnClose()
697 if (query_modified()) {
698 z = MessageBox("Do you want to keep your changes?", "Close", MB_ICONQUESTION | MB_YESNOCANCEL);
711 void CMissionGoalsDlg::insert_handler(int old, int node)
715 for (i=0; i<m_num_goals; i++){
716 if (m_goals[i].formula == old){
721 Assert(i < m_num_goals);
722 m_goals[i].formula = node;
726 void CMissionGoalsDlg::OnGoalInvalid()
732 m_goal_invalid = !m_goal_invalid;
733 m_goals[cur_goal].type ^= INVALID_GOAL;
737 void CMissionGoalsDlg::OnNoMusic()
743 m_no_music = !m_no_music;
744 m_goals[cur_goal].flags ^= MGF_NO_MUSIC;
748 void CMissionGoalsDlg::swap_handler(int node1, int node2)
753 for (index1=0; index1<m_num_goals; index1++){
754 if (m_goals[index1].formula == node1){
759 Assert(index1 < m_num_goals);
760 for (index2=0; index2<m_num_goals; index2++){
761 if (m_goals[index2].formula == node2){
766 Assert(index2 < m_num_goals);
768 // m_goals[index1] = m_goals[index2];
769 while (index1 < index2) {
770 m_goals[index1] = m_goals[index1 + 1];
771 m_sig[index1] = m_sig[index1 + 1];
775 while (index1 > index2 + 1) {
776 m_goals[index1] = m_goals[index1 - 1];
777 m_sig[index1] = m_sig[index1 - 1];
784 void CMissionGoalsDlg::OnChangeGoalScore()
791 m_goals[cur_goal].score = m_goal_score;
795 // code when the "team" selection in the combo box changes
796 void CMissionGoalsDlg::OnSelchangeTeam()
803 m_goals[cur_goal].team = m_team;