FS2_Open
Open source remastering of the Freespace 2 engine
campaigntreeview.cpp
Go to the documentation of this file.
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 the
6  * source.
7  *
8 */
9 
10 // CampaignTreeView.cpp : implementation file
11 //
12 
13 #include "stdafx.h"
14 #include "FRED.h"
15 #include "CampaignTreeView.h"
16 #include "CampaignEditorDlg.h"
17 #include "CampaignTreeWnd.h"
18 #include "mission/missionparse.h"
19 
20 #ifdef _DEBUG
21 #undef THIS_FILE
22 static char THIS_FILE[] = __FILE__;
23 #endif
24 
26 int Total_links = 0;
34 
36 // campaign_tree_view
37 
38 IMPLEMENT_DYNCREATE(campaign_tree_view, CScrollView)
39 
41 
43 {
44  total_levels = 1;
45  total_width = 1;
46 }
47 
49 {
50 }
51 
52 BEGIN_MESSAGE_MAP(campaign_tree_view, CScrollView)
53  //{{AFX_MSG_MAP(campaign_tree_view)
54  ON_WM_LBUTTONDOWN()
55  ON_WM_MOUSEMOVE()
56  ON_WM_LBUTTONUP()
57  ON_WM_CREATE()
58  ON_WM_CONTEXTMENU()
59  ON_COMMAND(ID_REMOVE_MISSION, OnRemoveMission)
60  ON_COMMAND(ID_DELETE_ROW, OnDeleteRow)
61  ON_COMMAND(ID_INSERT_ROW, OnInsertRow)
62  ON_COMMAND(ID_ADD_REPEAT, OnAddRepeat)
63  ON_COMMAND(ID_END_OF_CAMPAIGN, OnEndOfCampaign)
64  //}}AFX_MSG_MAP
65 END_MESSAGE_MAP()
66 
68 // campaign_tree_view drawing
69 
70 #define LEVEL_HEIGHT 75
71 #define CELL_WIDTH 150
72 #define CELL_TEXT_WIDTH 130
73 
74 int campaign_tree_view::OnCreate(LPCREATESTRUCT lpCreateStruct)
75 {
76  if (CScrollView::OnCreate(lpCreateStruct) == -1)
77  return -1;
78 
79  return 0;
80 }
81 
83 {
84  char str[256];
85  int i, x, y, f, t;
86  BOOL r;
87  CSize size;
88  CRect rect;
89  CPen black_pen, white_pen, red_pen, blue_pen, green_pen;
90  CBrush gray_brush;
91  TEXTMETRIC tm;
92 
93  // setup text drawing stuff
94  pDC->SetTextAlign(TA_TOP | TA_CENTER);
95  pDC->SetTextColor(RGB(0, 0, 0));
96  pDC->SetBkMode(TRANSPARENT);
97 
98  // figure out text box sizes
99  r = pDC->GetTextMetrics(&tm);
100  Assert(r);
101  Bx = CELL_TEXT_WIDTH + 4;
102  By = tm.tmHeight + 4;
103 
104  r = gray_brush.CreateSolidBrush(RGB(192, 192, 192));
105  Assert(r);
106  pDC->FillRect(CRect(0, 0, total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT), &gray_brush);
107 
108  // create pens
109  r = black_pen.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
110  Assert(r);
111  r = white_pen.CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
112  Assert(r);
113  r = red_pen.CreatePen(PS_SOLID, 1, RGB(192, 0, 0));
114  Assert(r);
115  r = blue_pen.CreatePen(PS_SOLID, 1, RGB(0, 0, 192));
116  Assert(r);
117  r = green_pen.CreatePen(PS_SOLID, 1, RGB(0, 192, 0));
118  Assert(r);
119 
120  pDC->SelectObject(&black_pen);
121  // draw level seperators
122  for (i=1; i<total_levels; i++) {
123  pDC->MoveTo(0, i * LEVEL_HEIGHT - 1);
124  pDC->LineTo(total_width * CELL_WIDTH, i * LEVEL_HEIGHT - 1);
125  }
126 
127  pDC->SelectObject(&white_pen);
128  for (i=1; i<total_levels; i++) {
129  pDC->MoveTo(0, i * LEVEL_HEIGHT);
130  pDC->LineTo(total_width * CELL_WIDTH, i * LEVEL_HEIGHT);
131  }
132 
133 
134  // draw edges of the whole tree rectangle
135  pDC->SelectObject(&black_pen);
136  pDC->MoveTo(0, total_levels * LEVEL_HEIGHT);
137  pDC->LineTo(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT);
138  pDC->LineTo(total_width * CELL_WIDTH, -1);
139 
140  // draw text boxes and text
141 
142  for (i=0; i<Campaign.num_missions; i++) {
143  x = (Campaign.missions[i].pos + 1) * CELL_WIDTH / 2;
144  y = Campaign.missions[i].level * LEVEL_HEIGHT + LEVEL_HEIGHT / 2;
145  Elements[i].box.left = x - Bx / 2;
146  Elements[i].box.right = Elements[i].box.left + Bx;
147  Elements[i].box.top = y - By / 2;
148  Elements[i].box.bottom = Elements[i].box.top + By;
149 
150  strcpy_s(str, Campaign.missions[i].name);
151  str[strlen(str) - 4] = 0; // strip extension from filename
152  GetTextExtentPoint32(pDC->m_hDC, str, strlen(str), &size);
153  if (size.cx > CELL_TEXT_WIDTH) {
154  strcpy(str + strlen(str) - 1, "...");
155  GetTextExtentPoint32(pDC->m_hDC, str, strlen(str), &size);
156  while (size.cx > CELL_TEXT_WIDTH) {
157  strcpy(str + strlen(str) - 4, "...");
158  GetTextExtentPoint32(pDC->m_hDC, str, strlen(str), &size);
159  }
160  }
161 
162  if (i == Cur_campaign_mission) {
163  pDC->SetTextColor(RGB(192, 0, 0));
164  pDC->Draw3dRect(x - Bx / 2, y - By / 2, Bx, By, RGB(0, 0, 0), RGB(255, 255, 255));
165 
166  } else {
167  pDC->SetTextColor(RGB(0, 0, 0));
168  pDC->Draw3dRect(x - Bx / 2, y - By / 2, Bx, By, RGB(255, 255, 255), RGB(0, 0, 0));
169  }
170 
171  pDC->TextOut(x, y - By / 2 + 2, str, strlen(str));
172  }
173 
174  for (i=0; i<Total_links; i++) {
175  f = Links[i].from;
176  t = Links[i].to;
177 
178  if (t == -1) {
179  continue;
180  }
181  Links[i].p1.x = Elements[f].box.left + Links[i].from_pos * Bx / (Elements[f].from_links + 1);
182  Links[i].p1.y = Elements[f].box.bottom;
183  Links[i].p2.x = Elements[t].box.left + Links[i].to_pos * Bx / (Elements[t].to_links + 1);
184  Links[i].p2.y = Elements[t].box.top;
185 
186  // if special mission link, select blue pen
187  if (Links[i].is_mission_loop || Links[i].is_mission_fork) {
188  pDC->SelectObject(&blue_pen);
189  }
190 
191  // if active link, select highlight pen (red - normal, green - special)
192  if (i == Cur_campaign_link) {
193  if (Links[i].is_mission_loop || Links[i].is_mission_fork) {
194  pDC->SelectObject(&green_pen);
195  } else {
196  pDC->SelectObject(&red_pen);
197  }
198  }
199 
200  // draw a line between 'from' and 'to' mission. to might be -1 in the case of end-campaign, so
201  // don't draw line if so.
202  if ( (f != t) && ( t != -1) ) {
203  pDC->MoveTo(Links[i].p1);
204  pDC->LineTo(Links[i].p2);
205  }
206 
207  // select (defalt) black pen
208  pDC->SelectObject(&black_pen);
209  }
210 
211  pDC->SelectObject(&black_pen);
212 }
213 
215 // campaign_tree_view diagnostics
216 
217 #ifdef _DEBUG
218 void campaign_tree_view::AssertValid() const
219 {
220  CScrollView::AssertValid();
221 }
222 
223 void campaign_tree_view::Dump(CDumpContext& dc) const
224 {
225  CScrollView::Dump(dc);
226 }
227 #endif //_DEBUG
228 
230 // campaign_tree_view message handlers
231 
233 {
234  CScrollView::OnInitialUpdate();
235  SetScrollSizes(MM_TEXT, CSize(320, 320));
236 }
237 
238 void stuff_link_with_formula(int *link_idx, int formula, int mission_num)
239 {
240  int j, node, node2, node3;
241  if (formula >= 0) {
242  if (!stricmp(CTEXT(formula), "cond")) {
243  // sexp is valid
244 
245  node = CDR(formula);
246  free_one_sexp(formula);
247  while (node != -1) {
248  node2 = CAR(node);
249  Links[*link_idx].from = mission_num;
250  Links[*link_idx].sexp = CAR(node2);
251  Links[*link_idx].mission_branch_txt = NULL;
252  Links[*link_idx].mission_branch_brief_anim = NULL;
253  Links[*link_idx].mission_branch_brief_sound = NULL;
254  sexp_mark_persistent(CAR(node2));
255  free_one_sexp(node2);
256  node3 = CADR(node2);
257  if ( !stricmp( CTEXT(node3), "next-mission") ) {
258  node3 = CDR(node3);
259  for (j=0; j<Campaign.num_missions; j++)
260  if (!stricmp(CTEXT(node3), Campaign.missions[j].name))
261  break;
262 
263  if (j < Campaign.num_missions) { // mission is in campaign (you never know..)
264  Links[(*link_idx)++].to = j;
265  Elements[mission_num].from_links++;
266  Elements[j].to_links++;
267  }
268 
269  } else if ( !stricmp( CTEXT(node3), "end-of-campaign") ) {
270  Links[(*link_idx)++].to = -1;
271  Elements[mission_num].from_links++;
272 
273  } else
274  Int3(); // bogus operator in campaign file
275 
276  free_sexp(CDR(node2));
277  free_one_sexp(node);
278  node = CDR(node);
279  }
280  }
281  }
282 }
283 
284 // this function should only be called right after a campaign is loaded. Calling it a second
285 // time without having loaded a campaign again will result in undefined behavior.
287 {
288  int i;
289  free_links();
290 
291  // initialize mission link counts
292  for (i=0; i<Campaign.num_missions; i++) {
293  Elements[i].from_links = Elements[i].to_links = 0;
294  }
295 
296  // analyze branching sexps and build links from them.
297  int link_idx = 0;
298  for (i=0; i<Campaign.num_missions; i++) {
299 
300  // do main campaign path
302 
303  // do special mission path
309  Links[link_idx-1].is_mission_loop = true;
310  }
311  else if ( Campaign.missions[i].flags & CMISSION_FLAG_HAS_FORK ) {
316  Links[link_idx-1].is_mission_fork = true;
317  }
318  }
319 
320  for (i=0; i<Campaign.num_missions; i++) {
321  Sorted[i] = i;
322  }
323 
324  Total_links = link_idx;
326  realign_tree();
328  }
329 }
330 
332 {
333  int i, z;
334 
336  for (i=0; i<MAX_LEVELS; i++)
337  Level_counts[i] = 0;
338 
339  for (i=0; i<Campaign.num_missions; i++) {
340  z = Campaign.missions[i].level;
341  if (z + 2 > total_levels)
342  total_levels = z + 2;
343 
344  Level_counts[z]++;
345  z = (Campaign.missions[i].pos + 3) / 2;
346  if (z > total_width)
347  total_width = z;
348  }
349 
350  sort_links();
351  SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
352  Invalidate();
353 }
354 
356 {
357  int i;
358 
359  for (i=0; i<Total_links; i++) {
360  sexp_unmark_persistent(Links[i].sexp);
361  free_sexp2(Links[i].sexp);
362  }
363 
364  Total_links = 0;
365 }
366 
368 {
369  int i, j, z, offset, level, pos;
370 
371  // figure out what level each mission lies on and an initial position on that level
372  level = pos = total_width = 0;
373  for (i=0; i<Campaign.num_missions; i++) {
374  z = Sorted[i];
375  for (j=0; j<Total_links; j++)
376  if (Links[j].to == z) {
377  Assert(Campaign.missions[Links[j].from].level <= Campaign.missions[z].level); // links can't go up the tree, only down
378  if (Campaign.missions[Links[j].from].level == level) {
379  level++; // force to next level in tree
380  pos = 0;
381  break;
382  }
383  }
384 
386  Campaign.missions[z].pos = pos++;
387  if (pos > total_width)
388  total_width = pos;
389 
391  if (!z) { // topmost node must always be alone on level
392  level++;
393  pos = 0;
394  }
395  }
396 
397  // now calculate the true x position of each mission
398  for (i=0; i<Campaign.num_missions; i++) {
401  }
402 }
403 
405 {
406  int i, j, k, z, to_count, from_count, swap;
407  int to_list[MAX_CAMPAIGN_TREE_LINKS];
408  int from_list[MAX_CAMPAIGN_TREE_LINKS];
409 
410  for (i=0; i<Campaign.num_missions; i++) {
411  // build list of all to and from links for one mission at a time
412  to_count = from_count = 0;
413  for (j=0; j<Total_links; j++) {
414  if ((Links[j].to == i) && (Links[j].from == i))
415  continue; // ignore 'repeat mission' links
416  if (Links[j].to == i)
417  to_list[to_count++] = j;
418  if (Links[j].from == i)
419  from_list[from_count++] = j;
420  }
421 
422  // sort to links, left to right and top to bottom
423  for (j=0; j<to_count-1; j++)
424  for (k=j+1; k<to_count; k++) {
425  swap = 0;
426  z = Campaign.missions[Links[to_list[j]].from].pos -
427  Campaign.missions[Links[to_list[k]].from].pos;
428 
429  if (z > 0) // sort left to right
430  swap = 1;
431 
432  else if (!z) { // both have same position?
433  z = Campaign.missions[Links[to_list[j]].from].level -
434  Campaign.missions[Links[to_list[k]].from].level;
435 
436  // see where from link position is relative to to link position
437  if (Campaign.missions[i].pos < Campaign.missions[Links[to_list[j]].from].pos) {
438  if (z > 0) // sort bottom to top
439  swap = 1;
440 
441  } else {
442  if (z < 0) // sort top to bottom
443  swap = 1;
444  }
445  }
446 
447  if (swap) {
448  z = to_list[j];
449  to_list[j] = to_list[k];
450  to_list[k] = z;
451  }
452  }
453 
454  // set all links to positions
455  for (j=0; j<to_count; j++)
456  Links[to_list[j]].to_pos = j + 1;
457 
458  // sort from links, left to right and bottom to top
459  for (j=0; j<from_count-1; j++)
460  for (k=j+1; k<from_count; k++) {
461  swap = 0;
462  z = Campaign.missions[Links[from_list[j]].to].pos -
463  Campaign.missions[Links[from_list[k]].to].pos;
464 
465  if (z > 0)
466  swap = 1;
467 
468  else if (!z) {
469  z = Campaign.missions[Links[from_list[j]].to].level -
470  Campaign.missions[Links[from_list[k]].to].level;
471 
472  if (Campaign.missions[i].pos < Campaign.missions[Links[from_list[j]].to].pos) {
473  if (z < 0)
474  swap = 1;
475 
476  } else {
477  if (z > 0)
478  swap = 1;
479  }
480  }
481 
482  if (swap) {
483  z = from_list[j];
484  from_list[j] = from_list[k];
485  from_list[k] = z;
486  }
487  }
488 
489  // set all links from positions
490  for (j=0; j<from_count; j++)
491  Links[from_list[j]].from_pos = j + 1;
492  }
493 }
494 
495 void campaign_tree_view::OnLButtonDown(UINT nFlags, CPoint point)
496 {
497  int i;
498  CString str;
499  CEdit *box;
500  CListBox *listbox;
501  CClientDC dc(this);
502 
503  OnPrepareDC(&dc);
504  dc.DPtoLP(&point);
505  if (nFlags & MK_CONTROL) {
506  listbox = (CListBox *) &Campaign_tree_formp->m_filelist;
507  i = listbox->GetCurSel();
508 
509  Mission_dropping = -1;
510  if (i != LB_ERR) {
512  SetCapture();
513  }
514 
515  Last_draw_size = CSize(0, 0);
516  Dragging_rect.SetRect(0, 0, 1, 1);
517  dc.DrawDragRect(Dragging_rect, Last_draw_size, NULL, CSize(0, 0));
518 
519  } else {
520  if ( (Cur_campaign_link >= 0) && (Links[Cur_campaign_link].is_mission_loop || Links[Cur_campaign_link].is_mission_fork)) {
521  // HACK!! UPDATE mission loop/fork desc before changing selections
522  // save mission loop/fork desc
524  box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_MISSISON_LOOP_DESC);
525  box->GetWindowText(buffer, MISSION_DESC_LENGTH);
526  if (strlen(buffer)) {
527  if (Links[Cur_campaign_link].mission_branch_txt) {
528  free(Links[Cur_campaign_link].mission_branch_txt);
529  }
530  Links[Cur_campaign_link].mission_branch_txt = strdup(buffer);
531  } else {
533  }
534 
535  // HACK!! UPDATE mission loop/fork desc before changing selections
536  // save mission loop/fork desc
537  box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_LOOP_BRIEF_ANIM);
538  box->GetWindowText(buffer, MISSION_DESC_LENGTH);
539  if (strlen(buffer)) {
540  if (Links[Cur_campaign_link].mission_branch_brief_anim) {
541  free(Links[Cur_campaign_link].mission_branch_brief_anim);
542  }
543  Links[Cur_campaign_link].mission_branch_brief_anim = strdup(buffer);
544  } else {
546  }
547 
548  // HACK!! UPDATE mission loop/fork desc before changing selections
549  // save mission loop/fork desc
550  box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_LOOP_BRIEF_SOUND);
551  box->GetWindowText(buffer, MISSION_DESC_LENGTH);
552  if (strlen(buffer)) {
553  if (Links[Cur_campaign_link].mission_branch_brief_sound) {
554  free(Links[Cur_campaign_link].mission_branch_brief_sound);
555  }
556  Links[Cur_campaign_link].mission_branch_brief_sound = strdup(buffer);
557  } else {
559  }
560  }
562  for (i=0; i<Campaign.num_missions; i++)
563  if (Elements[i].box.PtInRect(point)) {
564  SetCapture();
565 
567  Dragging_rect = Elements[i].box;
568  Rect_offset = Dragging_rect.TopLeft() - point;
569  Last_draw_size = CSize(4, 4);
570  if (Campaign.missions[Cur_campaign_mission].num_goals < 0) // haven't loaded goal names yet (or notes)
572 
575  box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_HELP_BOX);
576  if (box)
577  box->SetWindowText(str);
578  }
579 
581  break;
582  }
583  }
584 
585  Invalidate();
586  UpdateWindow();
588  if (Mission_dragging != -1) {
589  CRect rect = Dragging_rect;
590 
591  dc.LPtoDP(&rect);
592  dc.DrawDragRect(rect, Last_draw_size, NULL, CSize(0, 0));
593  }
594 
595  CScrollView::OnLButtonDown(nFlags, point);
596 }
597 
598 void campaign_tree_view::OnMouseMove(UINT nFlags, CPoint point)
599 {
600  int i, level, pos, x, y;
601  CSize draw_size;
602  CRect rect, r1;
603  CClientDC dc(this);
604 
605  OnPrepareDC(&dc);
606  dc.DPtoLP(&point);
607  if ((Mission_dragging >= 0) || (Mission_dropping >= 0)) {
608  if (GetCapture() != this) {
609  rect = Dragging_rect;
610  dc.LPtoDP(&rect);
611  dc.DrawDragRect(rect, CSize(0, 0), rect, Last_draw_size);
613 
614  } else {
615  for (i=0; i<Campaign.num_missions; i++)
616  if (Elements[i].box.PtInRect(point))
617  break;
618 
619  if ((i < Campaign.num_missions) && (Mission_dropping < 0)) { // on a mission box?
620  draw_size = CSize(4, 4);
621  rect = Elements[i].box;
622 
623  } else {
624  level = query_level(point);
625  pos = query_pos(point);
626  if ((level < 0) || (pos < 0)) { // off table?
627  draw_size = CSize(0, 0);
628  rect = Dragging_rect;
629 
630  } else {
631  draw_size = CSize(2, 2);
632  for (i=0; i<Campaign.num_missions; i++)
633  if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
634  pos = query_alternate_pos(point);
635  break;
636  }
637 
638  x = pos * CELL_WIDTH / 2 - Bx / 2;
639  y = level * LEVEL_HEIGHT + LEVEL_HEIGHT / 2 - By / 2;
640  rect.SetRect(x, y, x + Bx, y + By);
641  }
642  }
643 
644  r1 = rect;
645  dc.LPtoDP(&r1);
646  dc.LPtoDP(&Dragging_rect);
647  dc.DrawDragRect(r1, draw_size, Dragging_rect, Last_draw_size);
648  Dragging_rect = rect;
649  Last_draw_size = draw_size;
650  }
651  }
652 
653  CScrollView::OnMouseMove(nFlags, point);
654 }
655 
656 void campaign_tree_view::OnLButtonUp(UINT nFlags, CPoint point)
657 {
658  int i, j, z, level, pos;
659  CClientDC dc(this);
660 
661  OnPrepareDC(&dc);
662  dc.DPtoLP(&point);
663  if (Mission_dropping >= 0) { // dropping a new mission into campaign?
664  z = Mission_dropping;
665  Mission_dropping = -1;
666  if (GetCapture() == this) {
667  ReleaseCapture();
668  dc.LPtoDP(&Dragging_rect);
669  dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
670 
671  drop_mission(z, point);
672  return;
673  }
674 
675  } else if (Mission_dragging >= 0) { // moving position of a mission?
676  z = Mission_dragging;
677  Mission_dragging = -1;
678  if (GetCapture() == this) {
679  ReleaseCapture();
680  dc.LPtoDP(&Dragging_rect);
681  dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
682  for (i=0; i<Campaign.num_missions; i++)
683  if (Elements[i].box.PtInRect(point)) { // see if released on another mission
684  if (i == z) // released on the same mission?
685  return;
686 
687  for (j=0; j<Total_links; j++)
688  if ((Links[j].from == z) && (Links[j].to == i))
689  return; // already linked
690 
691  if (Total_links >= MAX_CAMPAIGN_TREE_LINKS) {
692  MessageBox("Too many links exist. Can't add any more.");
693  return;
694  }
695 
696  if (Campaign.missions[z].level >= Campaign.missions[i].level) {
697  MessageBox("A branch can only be set to a mission on a lower level");
698  return;
699  }
700 
701  add_link(z, i);
702  return;
703  }
704 
705  // at this point, we are dragging a mission to a new place
706  level = query_level(point);
707  pos = query_pos(point);
708  if ((level < 0) || (pos < 0))
709  return;
710 
711  if (!level && (get_root_mission() >= 0)) {
712  MessageBox("Can't move mission to this level. There is already a top level mission");
713  return;
714  }
715 
716  for (i=0; i<Campaign.num_missions; i++)
717  if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
718  pos = query_alternate_pos(point);
719  break;
720  }
721 
722  for (i=0; i<Total_links; i++)
723  if (Links[i].to == z)
724  if (level <= Campaign.missions[Links[i].from].level) {
725  MessageBox("Can't move mission to that level, as it would be\n"
726  "above a parent mission", "Error");
727 
728  return;
729  }
730 
732  Campaign.missions[z].pos = pos - 1;
733  correct_position(z);
734  sort_links();
735  SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
736  Invalidate();
737  Campaign_modified = 1;
738  return;
739  }
740  }
741 
742  CScrollView::OnLButtonUp(nFlags, point);
743 }
744 
745 int campaign_tree_view::add_link(int from, int to)
746 {
748  return -1;
749 
751  Links[Total_links].from = from;
752  Links[Total_links].to = to;
754  Links[Total_links].is_mission_loop = false;
755  Links[Total_links].is_mission_fork = false;
756  Links[Total_links].mission_branch_txt = NULL;
759  Total_links++;
760  if (from != to) {
761  Elements[from].from_links++;
762  Elements[to].to_links++;
763  }
764 
765  sort_links();
767  Invalidate();
768  Campaign_modified = 1;
769  return 0;
770 }
771 
773 {
774  int level;
775 
776  if ((p.y < 0) || (p.y >= total_levels * LEVEL_HEIGHT))
777  return -1;
778 
779  level = p.y / LEVEL_HEIGHT;
780  Assert((level >= 0) && (level < total_levels));
781  return level;
782 }
783 
785 {
786  int pos;
787 
788  if ((p.x < 0) || (p.x >= total_width * CELL_WIDTH))
789  return -1;
790 
791  pos = ((p.x * 4 / CELL_WIDTH) + 1) / 2;
792  Assert((pos >= 0) && (pos <= total_width * 2));
793  return pos;
794 }
795 
797 {
798  int x, pos;
799 
800  if ((p.x < 0) || (p.x >= total_width * CELL_WIDTH))
801  return -1;
802 
803  x = p.x * 4 / CELL_WIDTH;
804  pos = (x + 1) / 2;
805  if (x & 1) // odd number
806  pos--;
807  else // even number
808  pos++;
809 
810  Assert((pos >= 0) && (pos <= total_width * 2));
811  return pos;
812 }
813 /*
814 DROPEFFECT campaign_tree_view::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
815 {
816  CClientDC dc(this);
817  OnPrepareDC(&dc);
818 
819  if (!pDataObject->IsDataAvailable((unsigned short)Mission_filename_cb_format))
820  return DROPEFFECT_NONE; // data isn't a mission filename, the only valid data to drop here
821 
822  Mission_dropping = 1;
823  Last_draw_size = CSize(0, 0);
824  Dragging_rect.SetRect(0, 0, 1, 1);
825  dc.DrawDragRect(Dragging_rect, Last_draw_size, NULL, CSize(0, 0));
826  return DROPEFFECT_MOVE;
827 }
828 
829 void campaign_tree_view::OnDragLeave()
830 {
831  CScrollView::OnDragLeave();
832  if (Mission_dropping >= 0) {
833  CClientDC dc(this);
834  OnPrepareDC(&dc);
835 
836  dc.LPtoDP(&Dragging_rect);
837  dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
838  Mission_dropping = -1;
839  }
840 }
841 
842 DROPEFFECT campaign_tree_view::OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
843 {
844  int i, level, pos, x, y;
845  CSize draw_size;
846  CRect rect, r1;
847  DROPEFFECT r = DROPEFFECT_MOVE;
848 
849  if (Mission_dropping < 0)
850  return DROPEFFECT_NONE;
851 
852  CClientDC dc(this);
853  OnPrepareDC(&dc);
854  dc.DPtoLP(&point);
855 
856  level = query_level(point);
857  pos = query_pos(point);
858  if ((level < 0) || (pos < 0)) { // off table?
859  draw_size = CSize(0, 0);
860  rect = Dragging_rect;
861  r = DROPEFFECT_NONE;
862 
863  } else {
864  draw_size = CSize(2, 2);
865  for (i=0; i<Campaign.num_missions; i++)
866  if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
867  pos = query_alternate_pos(point);
868  break;
869  }
870 
871  x = pos * CELL_WIDTH / 2 - Dragging_rect.Width() / 2;
872  y = level * LEVEL_HEIGHT + LEVEL_HEIGHT / 2 - Dragging_rect.Height() / 2;
873  rect.SetRect(x, y, x + Bx, y + By);
874  }
875 
876  r1 = rect;
877  dc.LPtoDP(&r1);
878  dc.LPtoDP(&Dragging_rect);
879  dc.DrawDragRect(r1, draw_size, Dragging_rect, Last_draw_size);
880  Dragging_rect = rect;
881  Last_draw_size = draw_size;
882 
883  return r;
884 }
885 
886 BOOL campaign_tree_view::OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
887 {
888  int i, level, pos;
889  cmission *cm;
890  HGLOBAL hGlobal;
891  LPCSTR pData;
892  mission a_mission;
893 
894  if (Mission_dropping < 0)
895  return FALSE;
896 
897  // If the dropEffect requested is not a MOVE, return FALSE to
898  // signal no drop. (No COPY into trashcan)
899  if ((dropEffect & DROPEFFECT_MOVE) != DROPEFFECT_MOVE)
900  return FALSE;
901 
902  CClientDC dc(this);
903  OnPrepareDC(&dc);
904  dc.DPtoLP(&point);
905 
906  dc.LPtoDP(&Dragging_rect);
907  dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
908  Mission_dropping = -1;
909 
910  if (!pDataObject->IsDataAvailable((unsigned short)Mission_filename_cb_format))
911  return FALSE; // data isn't a mission filename, the only valid data to drop here
912 
913  // Get text data from COleDataObject
914  hGlobal = pDataObject->GetGlobalData((unsigned short)Mission_filename_cb_format);
915 
916  // Get pointer to data
917  pData = (LPCSTR) GlobalLock(hGlobal);
918  ASSERT(pData);
919 
920  if (Campaign.num_missions >= MAX_CAMPAIGN_MISSIONS) { // Can't add any more
921  GlobalUnlock(hGlobal);
922  MessageBox("Too many missions. Can't add more to Campaign.", "Error");
923  return FALSE;
924  }
925 
926  level = query_level(point);
927  pos = query_pos(point);
928  Assert((level >= 0) && (pos >= 0)); // this should be impossible
929  if (!level && (get_root_mission() >= 0)) {
930  GlobalUnlock(hGlobal);
931  MessageBox("Only 1 mission may be in the top level");
932  return FALSE;
933  }
934 
935  // check the number of players in a multiplayer campaign against the number of players
936  // in the mission that was just dropped
937  if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
938  get_mission_info((char *)pData, &a_mission);
939  if ( !(a_mission.game_type & MISSION_TYPE_MULTI) ) {
940  char buf[256];
941 
942  sprintf( buf, "Mission \"%s\" is not a multiplayer mission", pData );
943  MessageBox(buf, "Error");
944  GlobalUnlock(hGlobal);
945  return FALSE;
946  }
947 
948  if ( Campaign.num_players != 0 ) {
949  if ( Campaign.num_players != a_mission.num_players ) {
950  char buf[512];
951 
952  sprintf(buf, "Mission \"%s\" has %d players. Campaign has %d players. Campaign will not play properly in FreeSpace", pData, a_mission.num_players, Campaign.num_players );
953  MessageBox(buf, "Warning");
954  }
955  } else {
956  Campaign.num_players = a_mission.num_players;
957  }
958  }
959 
960  Elements[Campaign.num_missions].from_links = Elements[Campaign.num_missions].to_links = 0;
961  cm = &(Campaign.missions[Campaign.num_missions++]);
962  cm->name = strdup(pData);
963  cm->formula = Locked_sexp_true;
964  cm->num_goals = -1;
965  cm->notes = NULL;
966  cm->briefing_cutscene[0] = 0;
967  for (i=0; i<Campaign.num_missions - 1; i++)
968  if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
969  pos = query_alternate_pos(point);
970  break;
971  }
972 
973  cm->level = level;
974  cm->pos = pos - 1;
975  correct_position(Campaign.num_missions - 1);
976  sort_links();
977  SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
978  Invalidate();
979 
980  // update and reinitialize dialog items
981  if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
982  Campaign_tree_formp->update();
983  Campaign_tree_formp->initialize(0);
984  }
985 
986  // Unlock memory - Send dropped text into the "bit-bucket"
987  GlobalUnlock(hGlobal);
988  Campaign_modified = 1;
989  return TRUE;
990 }
991 */
992 
993 void campaign_tree_view::drop_mission(int m, CPoint point)
994 {
995  char name[MAX_FILENAME_LEN + 1];
996  int i, item, level, pos;
997  cmission *cm;
998  mission a_mission;
999  CListBox *listbox;
1000 
1001  level = query_level(point);
1002  pos = query_pos(point);
1003  Assert((level >= 0) && (pos >= 0)); // this should be impossible
1004 
1005  listbox = (CListBox *) &Campaign_tree_formp->m_filelist;
1006  item = listbox->GetCurSel();
1007  if (item == LB_ERR) {
1008  MessageBox("Select a mission from listbox to add.", "Error");
1009  return;
1010  }
1011 
1012  if (listbox->GetTextLen(item) > MAX_FILENAME_LEN) {
1013  char buf[256];
1014 
1015  sprintf(buf, "Filename is too long. Must be %d or less characters.", MAX_FILENAME_LEN);
1016  MessageBox(buf, "Error");
1017  return; // filename is too long. Would overflow our buffer
1018  }
1019 
1020  // grab the filename selected from the listbox
1021  listbox->GetText(item, name);
1022 
1023  if (Campaign.num_missions >= MAX_CAMPAIGN_MISSIONS) { // Can't add any more
1024  MessageBox("Too many missions. Can't add more to Campaign.", "Error");
1025  return;
1026  }
1027 
1028  if (!level && (get_root_mission() >= 0)) {
1029  MessageBox("Only 1 mission may be in the top level");
1030  return;
1031  }
1032 
1033  // check the number of players in a multiplayer campaign against the number of players
1034  // in the mission that was just dropped
1035  if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
1036  get_mission_info(name, &a_mission);
1037  if ( !(a_mission.game_type & MISSION_TYPE_MULTI) ) {
1038  char buf[256];
1039 
1040  sprintf(buf, "Mission \"%s\" is not a multiplayer mission", name);
1041  MessageBox(buf, "Error");
1042  return;
1043  }
1044 
1045  if (Campaign.num_players != 0) {
1046  if (Campaign.num_players != a_mission.num_players) {
1047  char buf[512];
1048 
1049  sprintf(buf, "Mission \"%s\" has %d players. Campaign has %d players. Campaign will not play properly in FreeSpace", name, a_mission.num_players, Campaign.num_players );
1050  MessageBox(buf, "Warning");
1051  }
1052 
1053  } else {
1054  Campaign.num_players = a_mission.num_players;
1055  }
1056  }
1057 
1058  Elements[Campaign.num_missions].from_links = Elements[Campaign.num_missions].to_links = 0;
1060  cm->name = strdup(name);
1061  cm->formula = Locked_sexp_true;
1062  cm->num_goals = -1;
1063  cm->notes = NULL;
1064  cm->briefing_cutscene[0] = 0;
1065  for (i=0; i<Campaign.num_missions - 1; i++)
1066  if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
1067  pos = query_alternate_pos(point);
1068  break;
1069  }
1070 
1071  cm->level = level;
1072  cm->pos = pos - 1;
1074  sort_links();
1075  SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
1076  Invalidate();
1077 
1078  // update and reinitialize dialog items
1079  if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
1082  }
1083 
1084  listbox->DeleteString(item);
1085  Campaign_modified = 1;
1086  return;
1087 }
1088 
1090 {
1091  int i, j, s1, s2;
1092 
1093  for (i=0; i<Campaign.num_missions; i++)
1094  Sorted[i] = i;
1095 
1096  // sort the tree, so realignment will work property
1097  for (i=1; i<Campaign.num_missions; i++) {
1098  s1 = Sorted[i];
1099  for (j=i-1; j>=0; j--) {
1100  s2 = Sorted[j];
1101  if ((Campaign.missions[s1].level > Campaign.missions[s2].level) ||
1102  ((Campaign.missions[s1].level == Campaign.missions[s2].level) &&
1103  (Campaign.missions[s1].pos > Campaign.missions[s2].pos)))
1104  break;
1105 
1106  Sorted[j + 1] = s2;
1107  }
1108 
1109  Sorted[j + 1] = s1;
1110  }
1111 }
1112 
1114 {
1115  int i, z;
1116 
1117  // move missions down if required
1118  if (Campaign.missions[num].level + 2 > total_levels)
1120 
1121  for (i=0; i<Total_links; i++)
1122  if (Links[i].from == num) {
1123  z = Links[i].to;
1124  if ( (num != z) && (Campaign.missions[num].level >= Campaign.missions[z].level) ) {
1126  correct_position(z);
1127  }
1128  }
1129 
1130  // space out horizontally to avoid overlap of missions
1131  horizontally_align_mission(num, -1);
1133 }
1134 
1136 {
1137  int i, z;
1138 
1139  if ((Campaign.missions[num].pos == -1) || (Campaign.missions[num].pos + 1 == total_width * 2)) { // need to expand total_width
1140  for (i=0; i<Campaign.num_missions; i++)
1141  Campaign.missions[i].pos++;
1142 
1143  total_width++;
1144  }
1145 
1146  for (i=0; i<Campaign.num_missions; i++) {
1147  if (i == num)
1148  continue;
1149 
1150  if (Campaign.missions[i].level == Campaign.missions[num].level) {
1152  if (dir < 0) {
1153  if (!z || (z == -1)) {
1156  }
1157 
1158  } else {
1159  if (!z || (z == 1)) {
1162  }
1163  }
1164  }
1165  }
1166 }
1167 
1169 {
1170  Assert((num >= 0) && (num < Total_links));
1171  if (Links[num].from != Links[num].to) {
1172  Elements[Links[num].from].from_links--;
1173  Elements[Links[num].to].to_links--;
1174  }
1175 
1176  sexp_unmark_persistent(Links[num].sexp);
1177  free_sexp2(Links[num].sexp);
1178  while (num < Total_links - 1) {
1179  Links[num] = Links[num + 1];
1180  num++;
1181  }
1182 
1183  Total_links--;
1184  sort_links();
1185  Invalidate();
1186  Campaign_modified = 1;
1187  return;
1188 }
1189 
1191 {
1192  int i;
1193 
1194  for (i=0; i<Campaign.num_missions; i++)
1195  if (!Campaign.missions[i].level)
1196  return i;
1197 
1198  return -1;
1199 }
1200 
1201 void campaign_tree_view::OnContextMenu(CWnd* pWnd, CPoint point)
1202 {
1203  int i;
1204  CMenu menu, *popup;
1205  CPoint p = point;
1206  CClientDC dc(this);
1207 
1208  OnPrepareDC(&dc);
1209  dc.DPtoLP(&p);
1210 
1211  ScreenToClient(&p);
1212  for (i=0; i<Campaign.num_missions; i++)
1213  if (Elements[i].box.PtInRect(p))
1214  break;
1215 
1216  if (i < Campaign.num_missions) { // clicked on a mission
1217  Context_mission = i;
1218  if (menu.LoadMenu(IDR_CPGN_VIEW_ON)) {
1219  popup = menu.GetSubMenu(0);
1220  ASSERT(popup);
1221  popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
1222  }
1223 
1224  } else {
1226  if ((Context_mission >= 0) && (Context_mission < total_levels))
1227  if (menu.LoadMenu(IDR_CPGN_VIEW_OFF)) {
1228  popup = menu.GetSubMenu(0);
1229  ASSERT(popup);
1230  popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
1231  }
1232  }
1233 }
1234 
1236 {
1238  Invalidate();
1239  UpdateWindow();
1240 
1241  // for multiplayer missions, update the data and reiniailize the dialog -- the number of player
1242  // in the mission might have changed because of deletion of the first mission
1243  if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
1246  }
1247 }
1248 
1250 {
1251  int i, z;
1252  CEdit *box;
1253 
1254  Assert(m >= 0);
1256 
1257  z = --Campaign.num_missions;
1258  i = Total_links;
1259  while (i--) {
1260  if ((Links[i].from == m) || (Links[i].to == m))
1261  delete_link(i);
1262  if (Links[i].from == z)
1263  Links[i].from = m;
1264  if (Links[i].to == z)
1265  Links[i].to = m;
1266  }
1267 
1268  Elements[m] = Elements[z];
1270  if (m == Cur_campaign_mission) {
1271  Cur_campaign_mission = -1;
1272  box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_HELP_BOX);
1273  if (box)
1274  box->SetWindowText("");
1275 
1277  }
1278 
1279  Campaign_modified = 1;
1280 }
1281 
1283 {
1284  int i, z;
1285 
1286  if (!Context_mission) {
1287  MessageBox("Can't delete the top level");
1288  return;
1289  }
1290 
1291  for (i=z=0; i<Campaign.num_missions; i++)
1293  z++;
1294 
1295  if (z) {
1296  z = MessageBox("Deleting row will remove all missions on this row", "Notice", MB_ICONEXCLAMATION | MB_OKCANCEL);
1297  if (z == IDCANCEL)
1298  return;
1299  }
1300 
1301  while (i--)
1303  remove_mission(i);
1304 
1305  for (i=0; i<Campaign.num_missions; i++)
1307  Campaign.missions[i].level--;
1308 
1309  total_levels--;
1310  SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
1311  Invalidate();
1312  UpdateWindow();
1313  Campaign_modified = 1;
1314 }
1315 
1317 {
1318  int i;
1319 
1320  for (i=0; i<Campaign.num_missions; i++)
1322  Campaign.missions[i].level++;
1323 
1324  total_levels++;
1325  SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
1326  Invalidate();
1327  UpdateWindow();
1328  Campaign_modified = 1;
1329 }
1330 
1332 {
1334  MessageBox("Too many links exist. Can't add any more.");
1335  return;
1336  }
1337 }
1338 
1340 {
1341  if ( add_link(Context_mission, -1) ) {
1342  MessageBox("Too many links exist. Cannot add any more.");
1343  return;
1344  }
1345 }
SCP_string sexp
Definition: sexp.cpp:25556
#define MAX_FILENAME_LEN
Definition: pstypes.h:324
#define MB_OKCANCEL
Definition: config.h:180
LOCAL CSize Rect_offset
int i
Definition: multi_pxo.cpp:466
int Total_links
#define IDR_CPGN_VIEW_ON
Definition: resource.h:82
#define CAMPAIGN_TYPE_SINGLE
int Cur_campaign_link
campaign_tree_element Elements[MAX_CAMPAIGN_MISSIONS]
LOCAL CRect Dragging_rect
int free_sexp(int num)
Definition: sexp.cpp:1281
afx_msg void OnRemoveMission()
int add_link(int from, int to)
void correct_position(int num)
afx_msg void OnDeleteRow()
int game_type
Definition: missionparse.h:138
int query_alternate_pos(const CPoint &p)
int realign_required
#define CAR(n)
Definition: sexp.h:820
int free_one_sexp(int num)
Definition: sexp.cpp:1262
campaign_editor * Campaign_tree_formp
#define IDC_HELP_BOX
Definition: resource.h:872
char * mission_branch_desc
afx_msg void OnLButtonDown(UINT nFlags, CPoint point)
int mission_loop_formula
LOCAL int Mission_dropping
Assert(pm!=NULL)
#define IDC_LOOP_BRIEF_SOUND
Definition: resource.h:1041
char * CTEXT(int n)
Definition: sexp.cpp:28821
void initialize(int init_files=1)
GLclampf f
Definition: Glext.h:7097
char briefing_cutscene[NAME_LENGTH]
#define MISSION_TYPE_MULTI
Definition: missionparse.h:62
virtual void OnInitialUpdate()
LOCAL int Bx
unsigned int UINT
Definition: config.h:82
#define IDC_MISSISON_LOOP_DESC
Definition: resource.h:972
GLsizeiptr size
Definition: Glext.h:5496
#define Int3()
Definition: pstypes.h:292
#define CADR(n)
Definition: sexp.h:822
GLenum GLuint GLenum GLsizei const GLchar * buf
Definition: Glext.h:7308
int query_level(const CPoint &p)
#define ID_ADD_REPEAT
Definition: resource.h:1424
char * name
afx_msg void OnContextMenu(CWnd *pWnd, CPoint point)
int Sorted[MAX_CAMPAIGN_MISSIONS]
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct)
#define CDR(n)
Definition: sexp.h:821
campaign_tree_view * Campaign_tree_viewp
void mission_selected(int num)
void sexp_mark_persistent(int n)
Definition: sexp.cpp:1220
int Locked_sexp_true
Definition: sexp.cpp:828
void convert_multiline_string(CString &dest, const SCP_string &src)
Definition: management.cpp:169
GLintptr offset
Definition: Glext.h:5497
#define LEVEL_HEIGHT
GLdouble GLdouble GLdouble r
Definition: Glext.h:5337
char * notes
sprintf(buf,"(%f,%f,%f)", v3->xyz.x, v3->xyz.y, v3->xyz.z)
GLdouble GLdouble z
Definition: Glext.h:5451
void sexp_unmark_persistent(int n)
Definition: sexp.cpp:1242
int free_sexp2(int num)
Definition: sexp.cpp:1321
#define IDR_CPGN_VIEW_OFF
Definition: resource.h:81
int Level_counts[MAX_LEVELS]
hull_check p1
Definition: lua.cpp:5052
int Cur_campaign_mission
GLuint buffer
Definition: Glext.h:5492
virtual void OnDraw(CDC *pDC)
#define CELL_TEXT_WIDTH
#define MISSION_DESC_LENGTH
Definition: globals.h:28
char * mission_branch_brief_anim
GLdouble GLdouble t
Definition: Glext.h:5329
GLint GLint GLint GLint GLint x
Definition: Glext.h:5182
int get_mission_info(const char *filename, mission *mission_p, bool basic)
int CTV_button_down
#define IDC_LOOP_BRIEF_ANIM
Definition: resource.h:1039
#define CMISSION_FLAG_HAS_FORK
#define ID_END_OF_CAMPAIGN
Definition: resource.h:1433
GLuint const GLchar * name
Definition: Glext.h:5608
LOCAL int Context_mission
char * mission_branch_brief_sound
int BOOL
Definition: config.h:80
LOCAL CSize Last_draw_size
GLuint GLuint num
Definition: Glext.h:9089
#define MB_ICONEXCLAMATION
Definition: config.h:184
campaign Campaign
void read_mission_goal_list(int num)
#define MAX_LEVELS
int Campaign_modified
GLfloat GLfloat p
Definition: Glext.h:8373
int MessageBox(HWND h, const char *s1, const char *s2, int i)
#define CELL_WIDTH
#define ID_REMOVE_MISSION
Definition: resource.h:1420
afx_msg void OnInsertRow()
campaign_filelist_box m_filelist
LOCAL int Mission_dragging
hull_check pos
Definition: lua.cpp:5050
const GLfloat * m
Definition: Glext.h:10319
#define ID_DELETE_ROW
Definition: resource.h:1419
afx_msg void OnAddRepeat()
#define MAX_CAMPAIGN_MISSIONS
int num_players
Definition: missionparse.h:140
afx_msg void OnEndOfCampaign()
afx_msg void OnLButtonUp(UINT nFlags, CPoint point)
GLint level
Definition: Glext.h:5180
void horizontally_align_mission(int num, int dir)
#define LOCAL
Definition: pstypes.h:37
void drop_mission(int m, CPoint point)
#define CMISSION_FLAG_HAS_LOOP
#define stricmp(s1, s2)
Definition: config.h:271
#define ID_INSERT_ROW
Definition: resource.h:1421
#define MAX_CAMPAIGN_TREE_LINKS
cmission missions[MAX_CAMPAIGN_MISSIONS]
GLint y
Definition: Gl.h:1505
LOCAL int By
void stuff_link_with_formula(int *link_idx, int formula, int mission_num)
afx_msg void OnMouseMove(UINT nFlags, CPoint point)
void load_tree(int save=1)
#define strcpy_s(...)
Definition: safe_strings.h:67
campaign_tree_link Links[MAX_CAMPAIGN_TREE_LINKS]
int query_pos(const CPoint &p)