mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
* fix FastDebug QtFRED linking the debug CRT FastDebug builds in the DLL-runtime tree (used whenever qtFRED is enabled) compiled with /MDd and, since #7450, linked Qt's debug libraries. Both import ucrtbased.dll, MSVCP140D.dll and VCRUNTIME140D.dll, which Microsoft does not license for redistribution, so end users could not run the FastDebug qtFRED shipped in nightlies and releases. Static Qt is ruled out by the LGPL, so the only shippable shape is the release CRT plus release Qt. On MSVC, _DEBUG is the CRT's own contract: /MTd and /MDd define it themselves, and the STL headers select msvcprtd.lib and _ITERATOR_DEBUG_LEVEL=2 from it. Stop force-defining it for FastDebug on MSVC; the engine keys its debug behaviour on NDEBUG, and the few _DEBUG guards in code/ are switched to NDEBUG so they keep working. The static tree is unchanged: /MTd still defines _DEBUG there. Fixes #7915 Co-Authored-By: Claude Fable 5.1 <[email protected]> * derive the MSVC runtime choice once The "DLL runtime or not" condition was spelled out in the toolchain and twice in fred2 with "keep in sync" comments, and gtest_force_shared_crt and antlr4's WITH_STATIC_CRT had already drifted: they only looked at MSVC_USE_RUNTIME_DLL, so a qtFRED tree with tests enabled would have asked both for the static CRT. That is harmless today only because our flag strings carry no literal /MD for them to rewrite. Compute FSO_MSVC_RUNTIME_DLL once in toolchain-msvc.cmake and use it everywhere. InstallRequiredSystemLibraries deliberately stays keyed on MSVC_USE_RUNTIME_DLL: the qtFRED deploy ships no compiler runtime. Co-Authored-By: Claude Fable 5.1 <[email protected]> * remove FastDebug runtime leftovers The /MD -> /MDd substitution on the FastDebug flag strings has done nothing since the runtime moved to CMAKE_MSVC_RUNTIME_LIBRARY: the Release flags it copies contain no runtime switch. The commented-out _ITERATOR_DEBUG_LEVEL=0 lines for gtest and the OpenXR loader belong to the scheme #7450 removed from the toolchain. Co-Authored-By: Claude Fable 5.1 <[email protected]> * use the release CRT for all FastDebug builds Microsoft's documentation marks the static debug libraries (libcmtd, libucrtd, libvcruntimed) "Not redistributable" and states that debug versions of an application may only be copied to machines used for testing, and the license grants distribution rights only for code on the REDIST list, which contains no debug library in any form. So the statically linked FastDebug game and FRED2 are no more shippable than the /MDd qtFRED was. Link every FastDebug build against the release runtime. The engine's debug behaviour keys on NDEBUG, which FastDebug still leaves undefined, so asserts, warnings and mprintf are unaffected; only the debug CRT's heap checking goes, and that remains available in Debug builds. FRED2's static MFC library follows the CRT: the debug variant requires the debug runtime. Co-Authored-By: Claude Fable 5.1 <[email protected]> * keep the debug icon, version flag and FRED2 asserts without _DEBUG The resource compiler only ever sees the explicit definitions, so with _DEBUG no longer defined for FastDebug the game lost its debug icon and both editors lost the VS_FF_DEBUG version flag. Key those on NDEBUG, which FastDebug still leaves undefined. FRED2 also used MFC's ASSERT, VERIFY, ASSERT_VALID and TRACE0, which are all compiled out without _DEBUG. Convert them to the FSO equivalents FRED2 already uses nearly everywhere else: Assert, Verify, a null check in place of ASSERT_VALID (AssertValid only exists in debug MFC), and mprintf. The AssertValid/Dump overrides and DEBUG_NEW stay under _DEBUG, as they must. Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]>
1385 lines
36 KiB
C++
1385 lines
36 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
// CampaignTreeView.cpp : implementation file
|
|
//
|
|
|
|
#include "stdafx.h"
|
|
#include "FRED.h"
|
|
#include "CampaignTreeView.h"
|
|
#include "CampaignEditorDlg.h"
|
|
#include "CampaignTreeWnd.h"
|
|
#include "mission/missioncampaign.h"
|
|
#include "mission/missionparse.h"
|
|
|
|
#ifdef _DEBUG
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
LOCAL int Bx, By, Mission_dragging = -1, Mission_dropping = -1, Context_mission;
|
|
int Total_links = 0;
|
|
int CTV_button_down = 0;
|
|
int Level_counts[MAX_LEVELS];
|
|
int Sorted[MAX_CAMPAIGN_MISSIONS];
|
|
campaign_tree_element Elements[MAX_CAMPAIGN_MISSIONS];
|
|
campaign_tree_link Links[MAX_CAMPAIGN_TREE_LINKS];
|
|
LOCAL CRect Dragging_rect;
|
|
LOCAL CSize Rect_offset, Last_draw_size;
|
|
|
|
void init_link(campaign_tree_link &link, int from, int to)
|
|
{
|
|
link.from = from;
|
|
link.to = to;
|
|
link.sexp = Locked_sexp_true;
|
|
link.node = -1;
|
|
link.from_pos = -1;
|
|
link.to_pos = -1;
|
|
link.is_mission_loop = false;
|
|
link.is_mission_fork = false;
|
|
link.mission_branch_txt = nullptr;
|
|
link.mission_branch_brief_anim = nullptr;
|
|
link.mission_branch_brief_sound = nullptr;
|
|
link.p1 = CPoint();
|
|
link.p2 = CPoint();
|
|
}
|
|
|
|
void init_element(campaign_tree_element &element)
|
|
{
|
|
element.from_links = 0;
|
|
element.to_links = 0;
|
|
element.box = CRect();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// campaign_tree_view
|
|
|
|
IMPLEMENT_DYNCREATE(campaign_tree_view, CScrollView)
|
|
|
|
campaign_tree_view *Campaign_tree_viewp;
|
|
|
|
campaign_tree_view::campaign_tree_view()
|
|
{
|
|
total_levels = 1;
|
|
total_width = 1;
|
|
}
|
|
|
|
campaign_tree_view::~campaign_tree_view()
|
|
{
|
|
}
|
|
|
|
BEGIN_MESSAGE_MAP(campaign_tree_view, CScrollView)
|
|
//{{AFX_MSG_MAP(campaign_tree_view)
|
|
ON_WM_LBUTTONDOWN()
|
|
ON_WM_MOUSEMOVE()
|
|
ON_WM_LBUTTONUP()
|
|
ON_WM_CREATE()
|
|
ON_WM_CONTEXTMENU()
|
|
ON_COMMAND(ID_REMOVE_MISSION, OnRemoveMission)
|
|
ON_COMMAND(ID_DELETE_ROW, OnDeleteRow)
|
|
ON_COMMAND(ID_INSERT_ROW, OnInsertRow)
|
|
ON_COMMAND(ID_ADD_REPEAT, OnAddRepeat)
|
|
ON_COMMAND(ID_END_OF_CAMPAIGN, OnEndOfCampaign)
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// campaign_tree_view drawing
|
|
|
|
#define LEVEL_HEIGHT 75
|
|
#define CELL_WIDTH 150
|
|
#define CELL_TEXT_WIDTH 130
|
|
|
|
int campaign_tree_view::OnCreate(LPCREATESTRUCT lpCreateStruct)
|
|
{
|
|
if (CScrollView::OnCreate(lpCreateStruct) == -1)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void campaign_tree_view::OnDraw(CDC* pDC)
|
|
{
|
|
char str[256];
|
|
int i, x, y, f, t;
|
|
BOOL r;
|
|
CSize size;
|
|
CRect rect;
|
|
CPen black_pen, white_pen, red_pen, blue_pen, green_pen;
|
|
CBrush gray_brush;
|
|
TEXTMETRIC tm;
|
|
|
|
// setup text drawing stuff
|
|
pDC->SetTextAlign(TA_TOP | TA_CENTER);
|
|
pDC->SetTextColor(RGB(0, 0, 0));
|
|
pDC->SetBkMode(TRANSPARENT);
|
|
|
|
// figure out text box sizes
|
|
r = pDC->GetTextMetrics(&tm);
|
|
Assert(r);
|
|
Bx = CELL_TEXT_WIDTH + 4;
|
|
By = tm.tmHeight + 4;
|
|
|
|
r = gray_brush.CreateSolidBrush(RGB(192, 192, 192));
|
|
Assert(r);
|
|
pDC->FillRect(CRect(0, 0, total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT), &gray_brush);
|
|
|
|
// create pens
|
|
r = black_pen.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
|
|
Assert(r);
|
|
r = white_pen.CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
|
|
Assert(r);
|
|
r = red_pen.CreatePen(PS_SOLID, 1, RGB(192, 0, 0));
|
|
Assert(r);
|
|
r = blue_pen.CreatePen(PS_SOLID, 1, RGB(0, 0, 192));
|
|
Assert(r);
|
|
r = green_pen.CreatePen(PS_SOLID, 1, RGB(0, 192, 0));
|
|
Assert(r);
|
|
|
|
pDC->SelectObject(&black_pen);
|
|
// draw level separators
|
|
for (i=1; i<total_levels; i++) {
|
|
pDC->MoveTo(0, i * LEVEL_HEIGHT - 1);
|
|
pDC->LineTo(total_width * CELL_WIDTH, i * LEVEL_HEIGHT - 1);
|
|
}
|
|
|
|
pDC->SelectObject(&white_pen);
|
|
for (i=1; i<total_levels; i++) {
|
|
pDC->MoveTo(0, i * LEVEL_HEIGHT);
|
|
pDC->LineTo(total_width * CELL_WIDTH, i * LEVEL_HEIGHT);
|
|
}
|
|
|
|
|
|
// draw edges of the whole tree rectangle
|
|
pDC->SelectObject(&black_pen);
|
|
pDC->MoveTo(0, total_levels * LEVEL_HEIGHT);
|
|
pDC->LineTo(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT);
|
|
pDC->LineTo(total_width * CELL_WIDTH, -1);
|
|
|
|
// draw text boxes and text
|
|
|
|
for (i=0; i<Campaign.num_missions; i++) {
|
|
x = (Campaign.missions[i].pos + 1) * CELL_WIDTH / 2;
|
|
y = Campaign.missions[i].level * LEVEL_HEIGHT + LEVEL_HEIGHT / 2;
|
|
Elements[i].box.left = x - Bx / 2;
|
|
Elements[i].box.right = Elements[i].box.left + Bx;
|
|
Elements[i].box.top = y - By / 2;
|
|
Elements[i].box.bottom = Elements[i].box.top + By;
|
|
|
|
strcpy_s(str, Campaign.missions[i].name);
|
|
str[strlen(str) - 4] = 0; // strip extension from filename
|
|
GetTextExtentPoint32(pDC->m_hDC, str, (int)strlen(str), &size);
|
|
if (size.cx > CELL_TEXT_WIDTH) {
|
|
strcpy(str + strlen(str) - 1, "...");
|
|
GetTextExtentPoint32(pDC->m_hDC, str, (int)strlen(str), &size);
|
|
while (size.cx > CELL_TEXT_WIDTH) {
|
|
strcpy(str + strlen(str) - 4, "...");
|
|
GetTextExtentPoint32(pDC->m_hDC, str, (int)strlen(str), &size);
|
|
}
|
|
}
|
|
|
|
if (i == Cur_campaign_mission) {
|
|
pDC->SetTextColor(RGB(192, 0, 0));
|
|
pDC->Draw3dRect(x - Bx / 2, y - By / 2, Bx, By, RGB(0, 0, 0), RGB(255, 255, 255));
|
|
|
|
} else {
|
|
pDC->SetTextColor(RGB(0, 0, 0));
|
|
pDC->Draw3dRect(x - Bx / 2, y - By / 2, Bx, By, RGB(255, 255, 255), RGB(0, 0, 0));
|
|
}
|
|
|
|
pDC->TextOut(x, y - By / 2 + 2, str, (int)strlen(str));
|
|
}
|
|
|
|
for (i=0; i<Total_links; i++) {
|
|
f = Links[i].from;
|
|
t = Links[i].to;
|
|
|
|
if (f == -1 || t == -1) {
|
|
continue;
|
|
}
|
|
Links[i].p1.x = Elements[f].box.left + Links[i].from_pos * Bx / (Elements[f].from_links + 1);
|
|
Links[i].p1.y = Elements[f].box.bottom;
|
|
Links[i].p2.x = Elements[t].box.left + Links[i].to_pos * Bx / (Elements[t].to_links + 1);
|
|
Links[i].p2.y = Elements[t].box.top;
|
|
|
|
// if special mission link, select blue pen
|
|
if (Links[i].is_mission_loop || Links[i].is_mission_fork) {
|
|
pDC->SelectObject(&blue_pen);
|
|
}
|
|
|
|
// if active link, select highlight pen (red - normal, green - special)
|
|
if (i == Cur_campaign_link) {
|
|
if (Links[i].is_mission_loop || Links[i].is_mission_fork) {
|
|
pDC->SelectObject(&green_pen);
|
|
} else {
|
|
pDC->SelectObject(&red_pen);
|
|
}
|
|
}
|
|
|
|
// draw a line between 'from' and 'to' mission. to might be -1 in the case of end-campaign, so
|
|
// don't draw line if so.
|
|
if ( (f != t) && ( t != -1) ) {
|
|
pDC->MoveTo(Links[i].p1);
|
|
pDC->LineTo(Links[i].p2);
|
|
}
|
|
|
|
// select (defalt) black pen
|
|
pDC->SelectObject(&black_pen);
|
|
}
|
|
|
|
pDC->SelectObject(&black_pen);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// campaign_tree_view diagnostics
|
|
|
|
#ifdef _DEBUG
|
|
void campaign_tree_view::AssertValid() const
|
|
{
|
|
CScrollView::AssertValid();
|
|
}
|
|
|
|
void campaign_tree_view::Dump(CDumpContext& dc) const
|
|
{
|
|
CScrollView::Dump(dc);
|
|
}
|
|
#endif //_DEBUG
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// campaign_tree_view message handlers
|
|
|
|
void campaign_tree_view::OnInitialUpdate()
|
|
{
|
|
CScrollView::OnInitialUpdate();
|
|
SetScrollSizes(MM_TEXT, CSize(320, 320));
|
|
}
|
|
|
|
void stuff_link_with_formula(int *link_idx, int formula, int mission_num)
|
|
{
|
|
int j, node, node2, node3;
|
|
Assert(mission_num >= 0 && mission_num < Campaign.num_missions);
|
|
|
|
if (formula >= 0) {
|
|
if (!stricmp(CTEXT(formula), "cond")) {
|
|
// sexp is valid
|
|
|
|
node = CDR(formula);
|
|
free_one_sexp(formula);
|
|
while (node != -1) {
|
|
init_link(Links[*link_idx], mission_num);
|
|
node2 = CAR(node);
|
|
Links[*link_idx].sexp = CAR(node2);
|
|
sexp_mark_persistent(CAR(node2));
|
|
free_one_sexp(node2);
|
|
node3 = CADR(node2);
|
|
if ( !stricmp( CTEXT(node3), "next-mission") ) {
|
|
node3 = CDR(node3);
|
|
for (j=0; j<Campaign.num_missions; j++)
|
|
if (!stricmp(CTEXT(node3), Campaign.missions[j].name))
|
|
break;
|
|
|
|
if (j < Campaign.num_missions) { // mission is in campaign (you never know..)
|
|
Links[(*link_idx)++].to = j;
|
|
Elements[mission_num].from_links++;
|
|
Elements[j].to_links++;
|
|
}
|
|
|
|
} else if ( !stricmp( CTEXT(node3), "end-of-campaign") ) {
|
|
(*link_idx)++;
|
|
Elements[mission_num].from_links++;
|
|
|
|
} else
|
|
Int3(); // bogus operator in campaign file
|
|
|
|
free_sexp(Sexp_nodes[node2].rest, node2);
|
|
free_one_sexp(node);
|
|
node = CDR(node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// this function should only be called right after a campaign is loaded. Calling it a second
|
|
// time without having loaded a campaign again will result in undefined behavior.
|
|
void campaign_tree_view::construct_tree()
|
|
{
|
|
int i;
|
|
free_links();
|
|
|
|
// initialize mission link counts
|
|
for (i=0; i<Campaign.num_missions; i++) {
|
|
init_element(Elements[i]);
|
|
}
|
|
|
|
// analyze branching sexps and build links from them.
|
|
int link_idx = 0;
|
|
for (i=0; i<Campaign.num_missions; i++) {
|
|
|
|
// do main campaign path
|
|
stuff_link_with_formula(&link_idx, Campaign.missions[i].formula, i);
|
|
|
|
// do special mission path
|
|
if ( Campaign.missions[i].flags & CMISSION_FLAG_HAS_LOOP ) {
|
|
stuff_link_with_formula(&link_idx, Campaign.missions[i].mission_loop_formula, i);
|
|
Links[link_idx-1].mission_branch_txt = Campaign.missions[i].mission_branch_desc;
|
|
Links[link_idx-1].mission_branch_brief_anim = Campaign.missions[i].mission_branch_brief_anim;
|
|
Links[link_idx-1].mission_branch_brief_sound = Campaign.missions[i].mission_branch_brief_sound;
|
|
Links[link_idx-1].is_mission_loop = true;
|
|
}
|
|
else if ( Campaign.missions[i].flags & CMISSION_FLAG_HAS_FORK ) {
|
|
Campaign.missions[i].mission_loop_formula = -1;
|
|
Links[link_idx-1].mission_branch_txt = Campaign.missions[i].mission_branch_desc;
|
|
Links[link_idx-1].mission_branch_brief_anim = Campaign.missions[i].mission_branch_brief_anim;
|
|
Links[link_idx-1].mission_branch_brief_sound = Campaign.missions[i].mission_branch_brief_sound;
|
|
Links[link_idx-1].is_mission_fork = true;
|
|
}
|
|
}
|
|
|
|
for (i=0; i<Campaign.num_missions; i++) {
|
|
Sorted[i] = i;
|
|
}
|
|
|
|
Total_links = link_idx;
|
|
if (Campaign.realign_required) {
|
|
realign_tree();
|
|
Campaign.realign_required = 0;
|
|
}
|
|
}
|
|
|
|
void campaign_tree_view::initialize()
|
|
{
|
|
int i, z;
|
|
|
|
total_levels = total_width = 1;
|
|
for (i=0; i<MAX_LEVELS; i++)
|
|
Level_counts[i] = 0;
|
|
|
|
for (i=0; i<Campaign.num_missions; i++) {
|
|
z = Campaign.missions[i].level;
|
|
if (z + 2 > total_levels)
|
|
total_levels = z + 2;
|
|
|
|
Level_counts[z]++;
|
|
z = (Campaign.missions[i].pos + 3) / 2;
|
|
if (z > total_width)
|
|
total_width = z;
|
|
}
|
|
|
|
sort_links();
|
|
SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
|
|
Invalidate();
|
|
}
|
|
|
|
void campaign_tree_view::free_links()
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<Total_links; i++) {
|
|
sexp_unmark_persistent(Links[i].sexp);
|
|
free_sexp2(Links[i].sexp);
|
|
}
|
|
|
|
Total_links = 0;
|
|
}
|
|
|
|
void campaign_tree_view::realign_tree()
|
|
{
|
|
int i, j, z, offset, level, pos;
|
|
|
|
// figure out what level each mission lies on and an initial position on that level
|
|
level = pos = total_width = 0;
|
|
for (i=0; i<Campaign.num_missions; i++) {
|
|
z = Sorted[i];
|
|
for (j=0; j<Total_links; j++)
|
|
if (Links[j].to == z) {
|
|
Assert(Campaign.missions[Links[j].from].level <= Campaign.missions[z].level); // links can't go up the tree, only down
|
|
if (Campaign.missions[Links[j].from].level == level) {
|
|
level++; // force to next level in tree
|
|
pos = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Campaign.missions[z].level = level;
|
|
Campaign.missions[z].pos = pos++;
|
|
if (pos > total_width)
|
|
total_width = pos;
|
|
|
|
Level_counts[level] = pos;
|
|
if (!z) { // topmost node must always be alone on level
|
|
level++;
|
|
pos = 0;
|
|
}
|
|
}
|
|
|
|
// now calculate the true x position of each mission
|
|
for (i=0; i<Campaign.num_missions; i++) {
|
|
offset = total_width - Level_counts[Campaign.missions[i].level];
|
|
Campaign.missions[i].pos = Campaign.missions[i].pos * 2 + offset;
|
|
}
|
|
}
|
|
|
|
void campaign_tree_view::sort_links()
|
|
{
|
|
int i, j, k, z, to_count, from_count, swap;
|
|
int to_list[MAX_CAMPAIGN_TREE_LINKS];
|
|
int from_list[MAX_CAMPAIGN_TREE_LINKS];
|
|
|
|
for (i=0; i<Campaign.num_missions; i++) {
|
|
// build list of all to and from links for one mission at a time
|
|
to_count = from_count = 0;
|
|
for (j=0; j<Total_links; j++) {
|
|
if ((Links[j].to == i) && (Links[j].from == i))
|
|
continue; // ignore 'repeat mission' links
|
|
if (Links[j].to == i)
|
|
to_list[to_count++] = j;
|
|
if (Links[j].from == i)
|
|
from_list[from_count++] = j;
|
|
}
|
|
|
|
// sort to links, left to right and top to bottom
|
|
for (j=0; j<to_count-1; j++)
|
|
for (k=j+1; k<to_count; k++) {
|
|
swap = 0;
|
|
z = Campaign.missions[Links[to_list[j]].from].pos -
|
|
Campaign.missions[Links[to_list[k]].from].pos;
|
|
|
|
if (z > 0) // sort left to right
|
|
swap = 1;
|
|
|
|
else if (!z) { // both have same position?
|
|
z = Campaign.missions[Links[to_list[j]].from].level -
|
|
Campaign.missions[Links[to_list[k]].from].level;
|
|
|
|
// see where from link position is relative to to link position
|
|
if (Campaign.missions[i].pos < Campaign.missions[Links[to_list[j]].from].pos) {
|
|
if (z > 0) // sort bottom to top
|
|
swap = 1;
|
|
|
|
} else {
|
|
if (z < 0) // sort top to bottom
|
|
swap = 1;
|
|
}
|
|
}
|
|
|
|
if (swap) {
|
|
z = to_list[j];
|
|
to_list[j] = to_list[k];
|
|
to_list[k] = z;
|
|
}
|
|
}
|
|
|
|
// set all links to positions
|
|
for (j=0; j<to_count; j++)
|
|
Links[to_list[j]].to_pos = j + 1;
|
|
|
|
// sort from links, left to right and bottom to top
|
|
for (j=0; j<from_count-1; j++)
|
|
for (k=j+1; k<from_count; k++) {
|
|
swap = 0;
|
|
z = Campaign.missions[Links[from_list[j]].to].pos -
|
|
Campaign.missions[Links[from_list[k]].to].pos;
|
|
|
|
if (z > 0)
|
|
swap = 1;
|
|
|
|
else if (!z) {
|
|
z = Campaign.missions[Links[from_list[j]].to].level -
|
|
Campaign.missions[Links[from_list[k]].to].level;
|
|
|
|
if (Campaign.missions[i].pos < Campaign.missions[Links[from_list[j]].to].pos) {
|
|
if (z < 0)
|
|
swap = 1;
|
|
|
|
} else {
|
|
if (z > 0)
|
|
swap = 1;
|
|
}
|
|
}
|
|
|
|
if (swap) {
|
|
z = from_list[j];
|
|
from_list[j] = from_list[k];
|
|
from_list[k] = z;
|
|
}
|
|
}
|
|
|
|
// set all links from positions
|
|
for (j=0; j<from_count; j++)
|
|
Links[from_list[j]].from_pos = j + 1;
|
|
}
|
|
}
|
|
|
|
void campaign_tree_view::OnLButtonDown(UINT nFlags, CPoint point)
|
|
{
|
|
int i;
|
|
CString str;
|
|
CEdit *box;
|
|
CListBox *listbox;
|
|
CClientDC dc(this);
|
|
|
|
OnPrepareDC(&dc);
|
|
dc.DPtoLP(&point);
|
|
if (nFlags & MK_CONTROL) {
|
|
listbox = (CListBox *) &Campaign_tree_formp->m_filelist;
|
|
i = listbox->GetCurSel();
|
|
|
|
Mission_dropping = -1;
|
|
if (i != LB_ERR) {
|
|
Mission_dropping = i;
|
|
SetCapture();
|
|
}
|
|
|
|
Last_draw_size = CSize(0, 0);
|
|
Dragging_rect.SetRect(0, 0, 1, 1);
|
|
dc.DrawDragRect(Dragging_rect, Last_draw_size, NULL, CSize(0, 0));
|
|
|
|
} else {
|
|
if ( (Cur_campaign_link >= 0) && (Links[Cur_campaign_link].is_mission_loop || Links[Cur_campaign_link].is_mission_fork)) {
|
|
// HACK!! UPDATE mission loop/fork desc before changing selections
|
|
// save mission loop/fork desc
|
|
char buffer[MISSION_DESC_LENGTH];
|
|
box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_MISSION_LOOP_DESC);
|
|
box->GetWindowText(buffer, MISSION_DESC_LENGTH);
|
|
if (strlen(buffer)) {
|
|
if (Links[Cur_campaign_link].mission_branch_txt) {
|
|
free(Links[Cur_campaign_link].mission_branch_txt);
|
|
}
|
|
Links[Cur_campaign_link].mission_branch_txt = strdup(buffer);
|
|
} else {
|
|
Links[Cur_campaign_link].mission_branch_txt = NULL;
|
|
}
|
|
|
|
// HACK!! UPDATE mission loop/fork desc before changing selections
|
|
// save mission loop/fork desc
|
|
box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_LOOP_BRIEF_ANIM);
|
|
box->GetWindowText(buffer, MISSION_DESC_LENGTH);
|
|
if (strlen(buffer)) {
|
|
if (Links[Cur_campaign_link].mission_branch_brief_anim) {
|
|
free(Links[Cur_campaign_link].mission_branch_brief_anim);
|
|
}
|
|
Links[Cur_campaign_link].mission_branch_brief_anim = strdup(buffer);
|
|
} else {
|
|
Links[Cur_campaign_link].mission_branch_brief_anim = NULL;
|
|
}
|
|
|
|
// HACK!! UPDATE mission loop/fork desc before changing selections
|
|
// save mission loop/fork desc
|
|
box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_LOOP_BRIEF_SOUND);
|
|
box->GetWindowText(buffer, MISSION_DESC_LENGTH);
|
|
if (strlen(buffer)) {
|
|
if (Links[Cur_campaign_link].mission_branch_brief_sound) {
|
|
free(Links[Cur_campaign_link].mission_branch_brief_sound);
|
|
}
|
|
Links[Cur_campaign_link].mission_branch_brief_sound = strdup(buffer);
|
|
} else {
|
|
Links[Cur_campaign_link].mission_branch_brief_sound = NULL;
|
|
}
|
|
}
|
|
Mission_dragging = Cur_campaign_mission = Cur_campaign_link = -1;
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
if (Elements[i].box.PtInRect(point)) {
|
|
SetCapture();
|
|
|
|
Mission_dragging = Cur_campaign_mission = i;
|
|
Dragging_rect = Elements[i].box;
|
|
Rect_offset = Dragging_rect.TopLeft() - point;
|
|
Last_draw_size = CSize(4, 4);
|
|
if (Campaign.missions[Cur_campaign_mission].flags & CMISSION_FLAG_FRED_LOAD_PENDING) // haven't loaded goal names yet (or notes)
|
|
{
|
|
read_mission_goal_list(Cur_campaign_mission);
|
|
Campaign.missions[Cur_campaign_mission].flags &= ~CMISSION_FLAG_FRED_LOAD_PENDING;
|
|
}
|
|
|
|
if (Campaign.missions[Cur_campaign_mission].notes) {
|
|
convert_multiline_string(str, Campaign.missions[Cur_campaign_mission].notes);
|
|
box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_HELP_BOX);
|
|
if (box)
|
|
box->SetWindowText(str);
|
|
}
|
|
|
|
Campaign_tree_formp->mission_selected(Cur_campaign_mission);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Invalidate();
|
|
UpdateWindow();
|
|
Campaign_tree_formp->load_tree();
|
|
if (Mission_dragging != -1) {
|
|
CRect rect = Dragging_rect;
|
|
|
|
dc.LPtoDP(&rect);
|
|
dc.DrawDragRect(rect, Last_draw_size, NULL, CSize(0, 0));
|
|
}
|
|
|
|
CScrollView::OnLButtonDown(nFlags, point);
|
|
}
|
|
|
|
void campaign_tree_view::OnMouseMove(UINT nFlags, CPoint point)
|
|
{
|
|
int i, level, pos, x, y;
|
|
CSize draw_size;
|
|
CRect rect, r1;
|
|
CClientDC dc(this);
|
|
|
|
OnPrepareDC(&dc);
|
|
dc.DPtoLP(&point);
|
|
if ((Mission_dragging >= 0) || (Mission_dropping >= 0)) {
|
|
if (GetCapture() != this) {
|
|
rect = Dragging_rect;
|
|
dc.LPtoDP(&rect);
|
|
dc.DrawDragRect(rect, CSize(0, 0), rect, Last_draw_size);
|
|
Mission_dragging = Mission_dropping = -1;
|
|
|
|
} else {
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
if (Elements[i].box.PtInRect(point))
|
|
break;
|
|
|
|
if ((i < Campaign.num_missions) && (Mission_dropping < 0)) { // on a mission box?
|
|
draw_size = CSize(4, 4);
|
|
rect = Elements[i].box;
|
|
|
|
} else {
|
|
level = query_level(point);
|
|
pos = query_pos(point);
|
|
if ((level < 0) || (pos < 0)) { // off table?
|
|
draw_size = CSize(0, 0);
|
|
rect = Dragging_rect;
|
|
|
|
} else {
|
|
draw_size = CSize(2, 2);
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
|
|
pos = query_alternate_pos(point);
|
|
break;
|
|
}
|
|
|
|
x = pos * CELL_WIDTH / 2 - Bx / 2;
|
|
y = level * LEVEL_HEIGHT + LEVEL_HEIGHT / 2 - By / 2;
|
|
rect.SetRect(x, y, x + Bx, y + By);
|
|
}
|
|
}
|
|
|
|
r1 = rect;
|
|
dc.LPtoDP(&r1);
|
|
dc.LPtoDP(&Dragging_rect);
|
|
dc.DrawDragRect(r1, draw_size, Dragging_rect, Last_draw_size);
|
|
Dragging_rect = rect;
|
|
Last_draw_size = draw_size;
|
|
}
|
|
}
|
|
|
|
CScrollView::OnMouseMove(nFlags, point);
|
|
}
|
|
|
|
void campaign_tree_view::OnLButtonUp(UINT nFlags, CPoint point)
|
|
{
|
|
int i, j, z, level, pos;
|
|
CClientDC dc(this);
|
|
|
|
OnPrepareDC(&dc);
|
|
dc.DPtoLP(&point);
|
|
if (Mission_dropping >= 0) { // dropping a new mission into campaign?
|
|
z = Mission_dropping;
|
|
Mission_dropping = -1;
|
|
if (GetCapture() == this) {
|
|
ReleaseCapture();
|
|
dc.LPtoDP(&Dragging_rect);
|
|
dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
|
|
|
|
drop_mission(z, point);
|
|
return;
|
|
}
|
|
|
|
} else if (Mission_dragging >= 0) { // moving position of a mission?
|
|
z = Mission_dragging;
|
|
Mission_dragging = -1;
|
|
if (GetCapture() == this) {
|
|
ReleaseCapture();
|
|
dc.LPtoDP(&Dragging_rect);
|
|
dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
if (Elements[i].box.PtInRect(point)) { // see if released on another mission
|
|
if (i == z) // released on the same mission?
|
|
return;
|
|
|
|
for (j=0; j<Total_links; j++)
|
|
if ((Links[j].from == z) && (Links[j].to == i))
|
|
return; // already linked
|
|
|
|
if (Total_links >= MAX_CAMPAIGN_TREE_LINKS) {
|
|
MessageBox("Too many links exist. Can't add any more.");
|
|
return;
|
|
}
|
|
|
|
if (Campaign.missions[z].level >= Campaign.missions[i].level) {
|
|
MessageBox("A branch can only be set to a mission on a lower level");
|
|
return;
|
|
}
|
|
|
|
add_link(z, i);
|
|
return;
|
|
}
|
|
|
|
// at this point, we are dragging a mission to a new place
|
|
level = query_level(point);
|
|
pos = query_pos(point);
|
|
if ((level < 0) || (pos < 0))
|
|
return;
|
|
|
|
if (!level && (get_root_mission() >= 0)) {
|
|
MessageBox("Can't move mission to this level. There is already a top level mission");
|
|
return;
|
|
}
|
|
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
|
|
pos = query_alternate_pos(point);
|
|
break;
|
|
}
|
|
|
|
for (i=0; i<Total_links; i++)
|
|
if (Links[i].to == z)
|
|
if (level <= Campaign.missions[Links[i].from].level) {
|
|
MessageBox("Can't move mission to that level, as it would be\n"
|
|
"above a parent mission", "Error");
|
|
|
|
return;
|
|
}
|
|
|
|
Campaign.missions[z].level = level;
|
|
Campaign.missions[z].pos = pos - 1;
|
|
correct_position(z);
|
|
sort_links();
|
|
SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
|
|
Invalidate();
|
|
Campaign_modified = 1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
CScrollView::OnLButtonUp(nFlags, point);
|
|
}
|
|
|
|
int campaign_tree_view::add_link(int from, int to)
|
|
{
|
|
if (Total_links >= MAX_CAMPAIGN_TREE_LINKS)
|
|
return -1;
|
|
|
|
Campaign_tree_formp->load_tree(1);
|
|
init_link(Links[Total_links], from, to);
|
|
Total_links++;
|
|
if (from != to) {
|
|
if (from >= 0)
|
|
Elements[from].from_links++;
|
|
if (to >= 0)
|
|
Elements[to].to_links++;
|
|
}
|
|
|
|
sort_links();
|
|
Campaign_tree_formp->load_tree(0);
|
|
Invalidate();
|
|
Campaign_modified = 1;
|
|
return 0;
|
|
}
|
|
|
|
int campaign_tree_view::query_level(const CPoint& p)
|
|
{
|
|
int level;
|
|
|
|
if ((p.y < 0) || (p.y >= total_levels * LEVEL_HEIGHT))
|
|
return -1;
|
|
|
|
level = p.y / LEVEL_HEIGHT;
|
|
Assert((level >= 0) && (level < total_levels));
|
|
return level;
|
|
}
|
|
|
|
int campaign_tree_view::query_pos(const CPoint& p)
|
|
{
|
|
int pos;
|
|
|
|
if ((p.x < 0) || (p.x >= total_width * CELL_WIDTH))
|
|
return -1;
|
|
|
|
pos = ((p.x * 4 / CELL_WIDTH) + 1) / 2;
|
|
Assert((pos >= 0) && (pos <= total_width * 2));
|
|
return pos;
|
|
}
|
|
|
|
int campaign_tree_view::query_alternate_pos(const CPoint& p)
|
|
{
|
|
int x, pos;
|
|
|
|
if ((p.x < 0) || (p.x >= total_width * CELL_WIDTH))
|
|
return -1;
|
|
|
|
x = p.x * 4 / CELL_WIDTH;
|
|
pos = (x + 1) / 2;
|
|
if (x & 1) // odd number
|
|
pos--;
|
|
else // even number
|
|
pos++;
|
|
|
|
Assert((pos >= 0) && (pos <= total_width * 2));
|
|
return pos;
|
|
}
|
|
/*
|
|
DROPEFFECT campaign_tree_view::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
|
|
{
|
|
CClientDC dc(this);
|
|
OnPrepareDC(&dc);
|
|
|
|
if (!pDataObject->IsDataAvailable((unsigned short)Mission_filename_cb_format))
|
|
return DROPEFFECT_NONE; // data isn't a mission filename, the only valid data to drop here
|
|
|
|
Mission_dropping = 1;
|
|
Last_draw_size = CSize(0, 0);
|
|
Dragging_rect.SetRect(0, 0, 1, 1);
|
|
dc.DrawDragRect(Dragging_rect, Last_draw_size, NULL, CSize(0, 0));
|
|
return DROPEFFECT_MOVE;
|
|
}
|
|
|
|
void campaign_tree_view::OnDragLeave()
|
|
{
|
|
CScrollView::OnDragLeave();
|
|
if (Mission_dropping >= 0) {
|
|
CClientDC dc(this);
|
|
OnPrepareDC(&dc);
|
|
|
|
dc.LPtoDP(&Dragging_rect);
|
|
dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
|
|
Mission_dropping = -1;
|
|
}
|
|
}
|
|
|
|
DROPEFFECT campaign_tree_view::OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
|
|
{
|
|
int i, level, pos, x, y;
|
|
CSize draw_size;
|
|
CRect rect, r1;
|
|
DROPEFFECT r = DROPEFFECT_MOVE;
|
|
|
|
if (Mission_dropping < 0)
|
|
return DROPEFFECT_NONE;
|
|
|
|
CClientDC dc(this);
|
|
OnPrepareDC(&dc);
|
|
dc.DPtoLP(&point);
|
|
|
|
level = query_level(point);
|
|
pos = query_pos(point);
|
|
if ((level < 0) || (pos < 0)) { // off table?
|
|
draw_size = CSize(0, 0);
|
|
rect = Dragging_rect;
|
|
r = DROPEFFECT_NONE;
|
|
|
|
} else {
|
|
draw_size = CSize(2, 2);
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
|
|
pos = query_alternate_pos(point);
|
|
break;
|
|
}
|
|
|
|
x = pos * CELL_WIDTH / 2 - Dragging_rect.Width() / 2;
|
|
y = level * LEVEL_HEIGHT + LEVEL_HEIGHT / 2 - Dragging_rect.Height() / 2;
|
|
rect.SetRect(x, y, x + Bx, y + By);
|
|
}
|
|
|
|
r1 = rect;
|
|
dc.LPtoDP(&r1);
|
|
dc.LPtoDP(&Dragging_rect);
|
|
dc.DrawDragRect(r1, draw_size, Dragging_rect, Last_draw_size);
|
|
Dragging_rect = rect;
|
|
Last_draw_size = draw_size;
|
|
|
|
return r;
|
|
}
|
|
|
|
BOOL campaign_tree_view::OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
|
|
{
|
|
int i, level, pos;
|
|
cmission *cm;
|
|
HGLOBAL hGlobal;
|
|
LPCSTR pData;
|
|
mission a_mission;
|
|
|
|
if (Mission_dropping < 0)
|
|
return FALSE;
|
|
|
|
// If the dropEffect requested is not a MOVE, return FALSE to
|
|
// signal no drop. (No COPY into trashcan)
|
|
if ((dropEffect & DROPEFFECT_MOVE) != DROPEFFECT_MOVE)
|
|
return FALSE;
|
|
|
|
CClientDC dc(this);
|
|
OnPrepareDC(&dc);
|
|
dc.DPtoLP(&point);
|
|
|
|
dc.LPtoDP(&Dragging_rect);
|
|
dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
|
|
Mission_dropping = -1;
|
|
|
|
if (!pDataObject->IsDataAvailable((unsigned short)Mission_filename_cb_format))
|
|
return FALSE; // data isn't a mission filename, the only valid data to drop here
|
|
|
|
// Get text data from COleDataObject
|
|
hGlobal = pDataObject->GetGlobalData((unsigned short)Mission_filename_cb_format);
|
|
|
|
// Get pointer to data
|
|
pData = (LPCSTR) GlobalLock(hGlobal);
|
|
Assert(pData);
|
|
|
|
if (Campaign.num_missions >= MAX_CAMPAIGN_MISSIONS) { // Can't add any more
|
|
GlobalUnlock(hGlobal);
|
|
MessageBox("Too many missions. Can't add more to Campaign.", "Error");
|
|
return FALSE;
|
|
}
|
|
|
|
level = query_level(point);
|
|
pos = query_pos(point);
|
|
Assert((level >= 0) && (pos >= 0)); // this should be impossible
|
|
if (!level && (get_root_mission() >= 0)) {
|
|
GlobalUnlock(hGlobal);
|
|
MessageBox("Only 1 mission may be in the top level");
|
|
return FALSE;
|
|
}
|
|
|
|
// check the number of players in a multiplayer campaign against the number of players
|
|
// in the mission that was just dropped
|
|
if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
|
|
get_mission_info((char *)pData, &a_mission);
|
|
if ( !(a_mission.game_type & MISSION_TYPE_MULTI) ) {
|
|
char buf[256];
|
|
|
|
sprintf( buf, "Mission \"%s\" is not a multiplayer mission", pData );
|
|
MessageBox(buf, "Error");
|
|
GlobalUnlock(hGlobal);
|
|
return FALSE;
|
|
}
|
|
|
|
if ( Campaign.num_players != 0 ) {
|
|
if ( Campaign.num_players != a_mission.num_players ) {
|
|
char buf[512];
|
|
|
|
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 );
|
|
MessageBox(buf, "Warning");
|
|
}
|
|
} else {
|
|
Campaign.num_players = a_mission.num_players;
|
|
}
|
|
}
|
|
|
|
init_element(Elements[Campaign.num_missions]);
|
|
cm = &(Campaign.missions[Campaign.num_missions++]);
|
|
cm->name = strdup(pData);
|
|
cm->formula = Locked_sexp_true;
|
|
cm->flags |= CMISSION_FLAG_FRED_LOAD_PENDING;
|
|
cm->notes = NULL;
|
|
cm->briefing_cutscene[0] = 0;
|
|
for (i=0; i<Campaign.num_missions - 1; i++)
|
|
if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
|
|
pos = query_alternate_pos(point);
|
|
break;
|
|
}
|
|
|
|
cm->level = level;
|
|
cm->pos = pos - 1;
|
|
correct_position(Campaign.num_missions - 1);
|
|
sort_links();
|
|
SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
|
|
Invalidate();
|
|
|
|
// update and reinitialize dialog items
|
|
if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
|
|
Campaign_tree_formp->update();
|
|
Campaign_tree_formp->initialize(0);
|
|
}
|
|
|
|
// Unlock memory - Send dropped text into the "bit-bucket"
|
|
GlobalUnlock(hGlobal);
|
|
Campaign_modified = 1;
|
|
return TRUE;
|
|
}
|
|
*/
|
|
|
|
void campaign_tree_view::drop_mission(int m, CPoint point)
|
|
{
|
|
char name[MAX_FILENAME_LEN + 1];
|
|
int i, item, level, pos;
|
|
cmission *cm;
|
|
mission a_mission;
|
|
CListBox *listbox;
|
|
|
|
level = query_level(point);
|
|
pos = query_pos(point);
|
|
Assert((level >= 0) && (pos >= 0)); // this should be impossible
|
|
|
|
listbox = (CListBox *) &Campaign_tree_formp->m_filelist;
|
|
item = listbox->GetCurSel();
|
|
if (item == LB_ERR) {
|
|
MessageBox("Select a mission from listbox to add.", "Error");
|
|
return;
|
|
}
|
|
|
|
if (listbox->GetTextLen(item) > MAX_FILENAME_LEN) {
|
|
char buf[256];
|
|
|
|
sprintf(buf, "Filename is too long. Must be %d or less characters.", MAX_FILENAME_LEN);
|
|
MessageBox(buf, "Error");
|
|
return; // filename is too long. Would overflow our buffer
|
|
}
|
|
|
|
// grab the filename selected from the listbox
|
|
listbox->GetText(item, name);
|
|
|
|
if (Campaign.num_missions >= MAX_CAMPAIGN_MISSIONS) { // Can't add any more
|
|
MessageBox("Too many missions. Can't add more to Campaign.", "Error");
|
|
return;
|
|
}
|
|
|
|
if (!level && (get_root_mission() >= 0)) {
|
|
MessageBox("Only 1 mission may be in the top level");
|
|
return;
|
|
}
|
|
|
|
// check the number of players in a multiplayer campaign against the number of players
|
|
// in the mission that was just dropped
|
|
if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
|
|
get_mission_info(name, &a_mission);
|
|
if ( !(a_mission.game_type & MISSION_TYPE_MULTI) ) {
|
|
char buf[256];
|
|
|
|
sprintf(buf, "Mission \"%s\" is not a multiplayer mission", name);
|
|
MessageBox(buf, "Error");
|
|
return;
|
|
}
|
|
|
|
if (Campaign.num_players != 0) {
|
|
if (Campaign.num_players != a_mission.num_players) {
|
|
char buf[512];
|
|
|
|
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 );
|
|
MessageBox(buf, "Warning");
|
|
}
|
|
|
|
} else {
|
|
Campaign.num_players = a_mission.num_players;
|
|
}
|
|
}
|
|
|
|
init_element(Elements[Campaign.num_missions]);
|
|
cm = &(Campaign.missions[Campaign.num_missions++]);
|
|
cm->name = strdup(name);
|
|
cm->formula = Locked_sexp_true;
|
|
cm->flags |= CMISSION_FLAG_FRED_LOAD_PENDING;
|
|
cm->notes = NULL;
|
|
cm->briefing_cutscene[0] = 0;
|
|
for (i=0; i<Campaign.num_missions - 1; i++)
|
|
if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
|
|
pos = query_alternate_pos(point);
|
|
break;
|
|
}
|
|
|
|
cm->level = level;
|
|
cm->pos = pos - 1;
|
|
correct_position(Campaign.num_missions - 1);
|
|
sort_links();
|
|
SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
|
|
Invalidate();
|
|
|
|
// update and reinitialize dialog items
|
|
if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
|
|
Campaign_tree_formp->update();
|
|
Campaign_tree_formp->initialize(false, false);
|
|
}
|
|
|
|
listbox->DeleteString(item);
|
|
Campaign_modified = 1;
|
|
return;
|
|
}
|
|
|
|
void campaign_tree_view::sort_elements()
|
|
{
|
|
int i, j, s1, s2;
|
|
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
Sorted[i] = i;
|
|
|
|
// sort the tree, so realignment will work property
|
|
for (i=1; i<Campaign.num_missions; i++) {
|
|
s1 = Sorted[i];
|
|
for (j=i-1; j>=0; j--) {
|
|
s2 = Sorted[j];
|
|
if ((Campaign.missions[s1].level > Campaign.missions[s2].level) ||
|
|
((Campaign.missions[s1].level == Campaign.missions[s2].level) &&
|
|
(Campaign.missions[s1].pos > Campaign.missions[s2].pos)))
|
|
break;
|
|
|
|
Sorted[j + 1] = s2;
|
|
}
|
|
|
|
Sorted[j + 1] = s1;
|
|
}
|
|
}
|
|
|
|
void campaign_tree_view::correct_position(int num)
|
|
{
|
|
int i, z;
|
|
|
|
// move missions down if required
|
|
if (Campaign.missions[num].level + 2 > total_levels)
|
|
total_levels = Campaign.missions[num].level + 2;
|
|
|
|
for (i=0; i<Total_links; i++)
|
|
if (Links[i].from == num) {
|
|
z = Links[i].to;
|
|
if ( (num != z) && (Campaign.missions[num].level >= Campaign.missions[z].level) ) {
|
|
Campaign.missions[z].level = Campaign.missions[num].level + 1;
|
|
correct_position(z);
|
|
}
|
|
}
|
|
|
|
// space out horizontally to avoid overlap of missions
|
|
horizontally_align_mission(num, -1);
|
|
horizontally_align_mission(num, 1);
|
|
}
|
|
|
|
void campaign_tree_view::horizontally_align_mission(int num, int dir)
|
|
{
|
|
int i, z;
|
|
|
|
if ((Campaign.missions[num].pos == -1) || (Campaign.missions[num].pos + 1 == total_width * 2)) { // need to expand total_width
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
Campaign.missions[i].pos++;
|
|
|
|
total_width++;
|
|
}
|
|
|
|
for (i=0; i<Campaign.num_missions; i++) {
|
|
if (i == num)
|
|
continue;
|
|
|
|
if (Campaign.missions[i].level == Campaign.missions[num].level) {
|
|
z = Campaign.missions[i].pos - Campaign.missions[num].pos;
|
|
if (dir < 0) {
|
|
if (!z || (z == -1)) {
|
|
Campaign.missions[i].pos = Campaign.missions[num].pos - 2;
|
|
horizontally_align_mission(i, -1);
|
|
}
|
|
|
|
} else {
|
|
if (!z || (z == 1)) {
|
|
Campaign.missions[i].pos = Campaign.missions[num].pos + 2;
|
|
horizontally_align_mission(i, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void campaign_tree_view::delete_link(int num)
|
|
{
|
|
Assert((num >= 0) && (num < Total_links));
|
|
|
|
int from = Links[num].from;
|
|
int to = Links[num].to;
|
|
if (from != to) {
|
|
if (from >= 0)
|
|
Elements[from].from_links--;
|
|
if (to >= 0)
|
|
Elements[to].to_links--;
|
|
}
|
|
|
|
sexp_unmark_persistent(Links[num].sexp);
|
|
free_sexp2(Links[num].sexp);
|
|
while (num < Total_links - 1) {
|
|
Links[num] = Links[num + 1];
|
|
num++;
|
|
}
|
|
|
|
Total_links--;
|
|
sort_links();
|
|
Invalidate();
|
|
Campaign_modified = 1;
|
|
return;
|
|
}
|
|
|
|
int campaign_tree_view::get_root_mission()
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
if (!Campaign.missions[i].level)
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
void campaign_tree_view::OnContextMenu(CWnd* pWnd, CPoint point)
|
|
{
|
|
int i;
|
|
CMenu menu, *popup;
|
|
CPoint p = point;
|
|
CClientDC dc(this);
|
|
|
|
OnPrepareDC(&dc);
|
|
dc.DPtoLP(&p);
|
|
|
|
ScreenToClient(&p);
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
if (Elements[i].box.PtInRect(p))
|
|
break;
|
|
|
|
if (i < Campaign.num_missions) { // clicked on a mission
|
|
Context_mission = i;
|
|
if (menu.LoadMenu(IDR_CPGN_VIEW_ON)) {
|
|
popup = menu.GetSubMenu(0);
|
|
Assert(popup);
|
|
popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
|
|
}
|
|
|
|
} else {
|
|
Context_mission = query_level(p);
|
|
if ((Context_mission >= 0) && (Context_mission < total_levels))
|
|
if (menu.LoadMenu(IDR_CPGN_VIEW_OFF)) {
|
|
popup = menu.GetSubMenu(0);
|
|
Assert(popup);
|
|
popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void campaign_tree_view::OnRemoveMission()
|
|
{
|
|
remove_mission(Context_mission);
|
|
Invalidate();
|
|
UpdateWindow();
|
|
|
|
// for multiplayer missions, update the data and reiniailize the dialog -- the number of player
|
|
// in the mission might have changed because of deletion of the first mission
|
|
if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
|
|
Campaign_tree_formp->update();
|
|
Campaign_tree_formp->initialize(true, false);
|
|
}
|
|
}
|
|
|
|
void campaign_tree_view::remove_mission(int m)
|
|
{
|
|
int i, z;
|
|
CEdit *box;
|
|
|
|
Assert(m >= 0);
|
|
Campaign_tree_formp->m_filelist.AddString(Campaign.missions[m].name);
|
|
|
|
z = --Campaign.num_missions;
|
|
i = Total_links;
|
|
while (i--) {
|
|
if ((Links[i].from == m) || (Links[i].to == m))
|
|
delete_link(i);
|
|
if (Links[i].from == z)
|
|
Links[i].from = m;
|
|
if (Links[i].to == z)
|
|
Links[i].to = m;
|
|
}
|
|
|
|
Elements[m] = Elements[z];
|
|
|
|
// free the removed mission's strings before its slot is overwritten, then
|
|
// null the vacated slot's pointers, which now alias slot m's strings. (If
|
|
// m == z, the assignment is a self-assign of freed pointers, and the
|
|
// nulling below leaves the slot clean.)
|
|
mission_campaign_free_mission_strings(Campaign.missions[m]);
|
|
Campaign.missions[m] = Campaign.missions[z];
|
|
Campaign.missions[z].name = nullptr;
|
|
Campaign.missions[z].notes = nullptr;
|
|
Campaign.missions[z].mission_branch_desc = nullptr;
|
|
Campaign.missions[z].mission_branch_brief_anim = nullptr;
|
|
Campaign.missions[z].mission_branch_brief_sound = nullptr;
|
|
|
|
if (m == Cur_campaign_mission) {
|
|
Cur_campaign_mission = -1;
|
|
box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_HELP_BOX);
|
|
if (box)
|
|
box->SetWindowText("");
|
|
|
|
Campaign_tree_formp->load_tree(0);
|
|
}
|
|
|
|
Campaign_modified = 1;
|
|
}
|
|
|
|
void campaign_tree_view::OnDeleteRow()
|
|
{
|
|
int i, z;
|
|
|
|
if (!Context_mission) {
|
|
MessageBox("Can't delete the top level");
|
|
return;
|
|
}
|
|
|
|
for (i=z=0; i<Campaign.num_missions; i++)
|
|
if (Campaign.missions[i].level == Context_mission)
|
|
z++;
|
|
|
|
if (z) {
|
|
z = MessageBox("Deleting row will remove all missions on this row", "Notice", MB_ICONEXCLAMATION | MB_OKCANCEL);
|
|
if (z == IDCANCEL)
|
|
return;
|
|
}
|
|
|
|
while (i--)
|
|
if (Campaign.missions[i].level == Context_mission)
|
|
remove_mission(i);
|
|
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
if (Campaign.missions[i].level > Context_mission)
|
|
Campaign.missions[i].level--;
|
|
|
|
total_levels--;
|
|
SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
|
|
Invalidate();
|
|
UpdateWindow();
|
|
Campaign_modified = 1;
|
|
}
|
|
|
|
void campaign_tree_view::OnInsertRow()
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<Campaign.num_missions; i++)
|
|
if (Campaign.missions[i].level >= Context_mission)
|
|
Campaign.missions[i].level++;
|
|
|
|
total_levels++;
|
|
SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
|
|
Invalidate();
|
|
UpdateWindow();
|
|
Campaign_modified = 1;
|
|
}
|
|
|
|
void campaign_tree_view::OnAddRepeat()
|
|
{
|
|
if (add_link(Context_mission, Context_mission)) {
|
|
MessageBox("Too many links exist. Can't add any more.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void campaign_tree_view::OnEndOfCampaign()
|
|
{
|
|
if ( add_link(Context_mission, -1) ) {
|
|
MessageBox("Too many links exist. Cannot add any more.");
|
|
return;
|
|
}
|
|
}
|