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]>
2110 lines
56 KiB
C++
2110 lines
56 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.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
#include "stdafx.h"
|
|
#include "FRED.h"
|
|
#include "FREDDoc.h"
|
|
#include "EventEditor.h"
|
|
#include "FREDView.h"
|
|
#include "Management.h"
|
|
#include "sexp_tree_view.h"
|
|
#include "textviewdlg.h"
|
|
#include "mission/missionmessage.h"
|
|
#include "cfile/cfile.h"
|
|
#include "sound/audiostr.h"
|
|
#include "localization/localize.h"
|
|
#include "mod_table/mod_table.h"
|
|
|
|
#ifdef _DEBUG
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
|
|
static int annotation_key_for_item(const event_sexp_tree *tree, HTREEITEM h);
|
|
|
|
BEGIN_MESSAGE_MAP(event_sexp_tree, sexp_tree_view)
|
|
//{{AFX_MSG_MAP(event_sexp_tree)
|
|
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
|
|
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
|
|
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
event_editor *Event_editor_dlg = NULL; // global reference needed by event tree class
|
|
|
|
// this is just useful for comparing modified EAs to unmodified ones
|
|
|
|
int safe_stricmp(const char* one, const char* two)
|
|
{
|
|
if (!one && !two)
|
|
return 0;
|
|
|
|
if (!one)
|
|
return -1;
|
|
|
|
if (!two)
|
|
return 1;
|
|
|
|
return stricmp(one, two);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// event_editor dialog
|
|
|
|
int event_editor::getRootReturnType() const
|
|
{
|
|
return OPR_NULL;
|
|
}
|
|
|
|
int event_editor::onRootDeleted(int formula_node)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < (int)m_events.size(); i++) {
|
|
if (m_events[i].formula == formula_node) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert(i < (int)m_events.size());
|
|
m_events.erase(m_events.begin() + i);
|
|
m_sig.erase(m_sig.begin() + i);
|
|
|
|
if (i >= (int)m_events.size()) {
|
|
// if we have deleted the last event, i will be set to -1 which is what we want
|
|
i--;
|
|
}
|
|
|
|
cur_event = i;
|
|
update_cur_event();
|
|
|
|
return formula_node;
|
|
}
|
|
|
|
void event_editor::onRootRenamed(int formula_node, const char* new_name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < (int)m_events.size(); i++) {
|
|
if (m_events[i].formula == formula_node) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert(i < (int)m_events.size());
|
|
m_events[i].name = new_name;
|
|
}
|
|
|
|
void event_editor::onRootInserted(int old_formula, int new_formula)
|
|
{
|
|
insert_handler(old_formula, new_formula);
|
|
}
|
|
|
|
void event_editor::onRootMoved(int node1, int node2, bool insert_before)
|
|
{
|
|
move_handler(node1, node2, insert_before);
|
|
}
|
|
|
|
event_editor::event_editor(CWnd* pParent /*=NULL*/)
|
|
: CDialog(event_editor::IDD, pParent),
|
|
SexpTreeEditorInterface({ TreeFlags::LabeledRoot, TreeFlags::RootDeletable, TreeFlags::RootEditable, TreeFlags::AnnotationsAllowed })
|
|
{
|
|
//{{AFX_DATA_INIT(event_editor)
|
|
m_repeat_count = 0;
|
|
m_trigger_count = 0;
|
|
m_interval = 0;
|
|
m_event_score = 0;
|
|
m_chain_delay = 0;
|
|
m_chained = FALSE;
|
|
m_use_msecs = FALSE;
|
|
m_obj_text = _T("");
|
|
m_obj_key_text = _T("");
|
|
m_avi_filename = _T("");
|
|
m_message_name = _T("");
|
|
m_message_note = _T("");
|
|
m_message_text = _T("");
|
|
m_persona = -1;
|
|
m_wave_filename = _T("");
|
|
m_cur_msg = -1;
|
|
m_cur_msg_old = -1;
|
|
m_team = -1;
|
|
m_message_team = -1;
|
|
m_last_message_node = -1;
|
|
//}}AFX_DATA_INIT
|
|
m_event_tree._model._interface = this;
|
|
m_event_tree._model.modified = &modified;
|
|
modified = 0;
|
|
select_sexp_node = -1;
|
|
m_wave_id = -1;
|
|
m_log_true = 0;
|
|
m_log_false = 0;
|
|
m_log_always_false = 0;
|
|
m_log_1st_repeat = 0;
|
|
m_log_last_repeat = 0;
|
|
m_log_1st_trigger = 0;
|
|
m_log_last_trigger = 0;
|
|
m_log_state_change = 0;
|
|
m_play_icon = nullptr;
|
|
m_move_to_top_icon = nullptr;
|
|
m_move_up_icon = nullptr;
|
|
m_move_down_icon = nullptr;
|
|
m_move_to_bottom_icon = nullptr;
|
|
}
|
|
|
|
void event_editor::DoDataExchange(CDataExchange* pDX)
|
|
{
|
|
if (m_cur_msg >= 0)
|
|
m_cur_msg_old = m_cur_msg;
|
|
|
|
CDialog::DoDataExchange(pDX);
|
|
//{{AFX_DATA_MAP(event_editor)
|
|
DDX_Control(pDX, IDC_EVENT_TREE, m_event_tree);
|
|
DDX_Text(pDX, IDC_REPEAT_COUNT, m_repeat_count);
|
|
DDX_Text(pDX, IDC_TRIGGER_COUNT, m_trigger_count);
|
|
DDX_Text(pDX, IDC_INTERVAL_TIME, m_interval);
|
|
DDX_Text(pDX, IDC_EVENT_SCORE, m_event_score);
|
|
DDX_Text(pDX, IDC_CHAIN_DELAY, m_chain_delay);
|
|
DDX_Check(pDX, IDC_CHAINED, m_chained);
|
|
DDX_Check(pDX, IDC_USE_MSECS, m_use_msecs);
|
|
DDX_Text(pDX, IDC_OBJ_TEXT, m_obj_text);
|
|
DDV_MaxChars(pDX, m_obj_text, NAME_LENGTH - 1);
|
|
DDX_Text(pDX, IDC_OBJ_KEY_TEXT, m_obj_key_text);
|
|
DDV_MaxChars(pDX, m_obj_key_text, NAME_LENGTH - 1);
|
|
DDX_CBString(pDX, IDC_AVI_FILENAME, m_avi_filename);
|
|
DDV_MaxChars(pDX, m_avi_filename, MAX_FILENAME_LEN - 1);
|
|
DDX_Text(pDX, IDC_MESSAGE_NAME, m_message_name);
|
|
DDV_MaxChars(pDX, m_message_name, NAME_LENGTH - 1);
|
|
DDX_Text(pDX, IDC_MESSAGE_TEXT, m_message_text);
|
|
DDV_MaxChars(pDX, m_message_text, MESSAGE_LENGTH - 1);
|
|
DDX_CBIndex(pDX, IDC_PERSONA_NAME, m_persona);
|
|
DDX_CBString(pDX, IDC_WAVE_FILENAME, m_wave_filename);
|
|
DDV_MaxChars(pDX, m_wave_filename, MAX_FILENAME_LEN - 1);
|
|
DDX_LBIndex(pDX, IDC_MESSAGE_LIST, m_cur_msg);
|
|
DDX_Check(pDX, IDC_MISSION_LOG_TRUE, m_log_true);
|
|
DDX_Check(pDX, IDC_MISSION_LOG_FALSE, m_log_false);
|
|
DDX_Check(pDX, IDC_MISSION_LOG_ALWAYS_FALSE, m_log_always_false);
|
|
DDX_Check(pDX, IDC_MISSION_LOG_1ST_REPEAT, m_log_1st_repeat);
|
|
DDX_Check(pDX, IDC_MISSION_LOG_LAST_REPEAT, m_log_last_repeat);
|
|
DDX_Check(pDX, IDC_MISSION_LOG_1ST_TRIGGER, m_log_1st_trigger);
|
|
DDX_Check(pDX, IDC_MISSION_LOG_LAST_TRIGGER, m_log_last_trigger);
|
|
DDX_Check(pDX, IDC_MISSION_LOG_STATE_CHANGE, m_log_state_change);
|
|
|
|
// m_team == -1 maps to 2
|
|
if(m_team == -1){
|
|
m_team = MAX_TVT_TEAMS;
|
|
}
|
|
|
|
DDX_CBIndex(pDX, IDC_EVENT_TEAM, m_team);
|
|
|
|
// m_message_team == -1 maps to 2
|
|
if(m_message_team == -1){
|
|
m_message_team = MAX_TVT_TEAMS;
|
|
}
|
|
DDX_CBIndex(pDX, IDC_MESSAGE_TEAM, m_message_team);
|
|
//}}AFX_DATA_MAP
|
|
}
|
|
|
|
BEGIN_MESSAGE_MAP(event_editor, CDialog)
|
|
//{{AFX_MSG_MAP(event_editor)
|
|
ON_NOTIFY(NM_RCLICK, IDC_EVENT_TREE, OnRclickEventTree)
|
|
ON_NOTIFY(TVN_BEGINLABELEDIT, IDC_EVENT_TREE, OnBeginlabeleditEventTree)
|
|
ON_NOTIFY(TVN_ENDLABELEDIT, IDC_EVENT_TREE, OnEndlabeleditEventTree)
|
|
ON_BN_CLICKED(IDC_BUTTON_NEW_EVENT, OnButtonNewEvent)
|
|
ON_BN_CLICKED(IDC_DELETE, OnDelete)
|
|
ON_BN_CLICKED(ID_OK, OnButtonOk)
|
|
ON_WM_CLOSE()
|
|
ON_NOTIFY(TVN_SELCHANGED, IDC_EVENT_TREE, OnSelchangedEventTree)
|
|
ON_EN_UPDATE(IDC_REPEAT_COUNT, OnUpdateRepeatCount)
|
|
ON_EN_UPDATE(IDC_TRIGGER_COUNT, OnUpdateTriggerCount)
|
|
ON_BN_CLICKED(IDC_CHAINED, OnChained)
|
|
ON_BN_CLICKED(IDC_INSERT, OnInsert)
|
|
ON_LBN_SELCHANGE(IDC_MESSAGE_LIST, OnSelchangeMessageList)
|
|
ON_BN_CLICKED(IDC_NEW_MSG, OnNewMsg)
|
|
ON_BN_CLICKED(IDC_INSERT_MSG, OnInsertMsg)
|
|
ON_BN_CLICKED(IDC_DELETE_MSG, OnDeleteMsg)
|
|
ON_BN_CLICKED(IDC_NEW_NOTE, OnMsgNote)
|
|
ON_BN_CLICKED(IDC_BROWSE_AVI, OnBrowseAvi)
|
|
ON_BN_CLICKED(IDC_BROWSE_WAVE, OnBrowseWave)
|
|
ON_CBN_SELCHANGE(IDC_WAVE_FILENAME, OnSelchangeWaveFilename)
|
|
ON_BN_CLICKED(IDC_PLAY, OnPlay)
|
|
ON_BN_CLICKED(IDC_UPDATE, OnUpdateStuff)
|
|
ON_BN_CLICKED(ID_CANCEL, OnButtonCancel)
|
|
ON_CBN_SELCHANGE(IDC_EVENT_TEAM, OnSelchangeTeam)
|
|
ON_CBN_SELCHANGE(IDC_MESSAGE_TEAM, OnSelchangeMessageTeam)
|
|
ON_LBN_DBLCLK(IDC_MESSAGE_LIST, OnDblclkMessageList)
|
|
ON_BN_CLICKED(IDC_EVENT_MOVE_TO_TOP, OnEventMoveToTop)
|
|
ON_BN_CLICKED(IDC_EVENT_MOVE_UP, OnEventMoveUp)
|
|
ON_BN_CLICKED(IDC_EVENT_MOVE_DOWN, OnEventMoveDown)
|
|
ON_BN_CLICKED(IDC_EVENT_MOVE_TO_BOTTOM, OnEventMoveToBottom)
|
|
ON_BN_CLICKED(IDC_MESSAGE_MOVE_TO_TOP, OnMessageMoveToTop)
|
|
ON_BN_CLICKED(IDC_MESSAGE_MOVE_UP, OnMessageMoveUp)
|
|
ON_BN_CLICKED(IDC_MESSAGE_MOVE_DOWN, OnMessageMoveDown)
|
|
ON_BN_CLICKED(IDC_MESSAGE_MOVE_TO_BOTTOM, OnMessageMoveToBottom)
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// event_editor message handlers
|
|
|
|
void maybe_add_head(CComboBox *box, const char* name)
|
|
{
|
|
if (box->FindStringExact(-1, name) == CB_ERR) {
|
|
box->AddString(name);
|
|
}
|
|
}
|
|
|
|
BOOL event_editor::OnInitDialog()
|
|
{
|
|
int i;
|
|
BOOL r = TRUE;
|
|
CListBox *list;
|
|
CComboBox *box;
|
|
MMessage msg;
|
|
|
|
CDialog::OnInitDialog(); // let the base class do the default work
|
|
m_play_icon = load_button_icon(IDB_PLAY, RGB(192, 192, 192));
|
|
((CButton *) GetDlgItem(IDC_PLAY)) -> SetIcon(m_play_icon);
|
|
|
|
// reorder arrows; one icon is shared between the event and message buttons.
|
|
// Icons carry a transparency mask, so they blend with the button face and
|
|
// gray out correctly when disabled (see load_button_icon).
|
|
m_move_to_top_icon = load_button_icon(IDB_MOVE_TO_TOP, RGB(255, 0, 255));
|
|
m_move_up_icon = load_button_icon(IDB_MOVE_UP, RGB(255, 0, 255));
|
|
m_move_down_icon = load_button_icon(IDB_MOVE_DOWN, RGB(255, 0, 255));
|
|
m_move_to_bottom_icon = load_button_icon(IDB_MOVE_TO_BOTTOM, RGB(255, 0, 255));
|
|
((CButton *) GetDlgItem(IDC_EVENT_MOVE_TO_TOP)) -> SetIcon(m_move_to_top_icon);
|
|
((CButton *) GetDlgItem(IDC_EVENT_MOVE_UP)) -> SetIcon(m_move_up_icon);
|
|
((CButton *) GetDlgItem(IDC_EVENT_MOVE_DOWN)) -> SetIcon(m_move_down_icon);
|
|
((CButton *) GetDlgItem(IDC_EVENT_MOVE_TO_BOTTOM)) -> SetIcon(m_move_to_bottom_icon);
|
|
((CButton *) GetDlgItem(IDC_MESSAGE_MOVE_TO_TOP)) -> SetIcon(m_move_to_top_icon);
|
|
((CButton *) GetDlgItem(IDC_MESSAGE_MOVE_UP)) -> SetIcon(m_move_up_icon);
|
|
((CButton *) GetDlgItem(IDC_MESSAGE_MOVE_DOWN)) -> SetIcon(m_move_down_icon);
|
|
((CButton *) GetDlgItem(IDC_MESSAGE_MOVE_TO_BOTTOM)) -> SetIcon(m_move_to_bottom_icon);
|
|
|
|
theApp.init_window(&Events_wnd_data, this, 0);
|
|
m_event_tree.setup((CEdit *) GetDlgItem(IDC_HELP_BOX));
|
|
load_tree();
|
|
create_tree();
|
|
|
|
update_cur_event();
|
|
i = m_event_tree.select_sexp_node;
|
|
if (i != -1) {
|
|
GetDlgItem(IDC_EVENT_TREE) -> SetFocus();
|
|
m_event_tree.hilite_item(i);
|
|
r = FALSE;
|
|
}
|
|
|
|
// load event annotations into local model and apply comment icons
|
|
m_annotation_model.loadFromGlobal(m_event_tree._model.tree_nodes, m_events, m_sig);
|
|
m_event_tree.m_annotations = &m_annotation_model;
|
|
m_event_tree._model.annotation_model = &m_annotation_model;
|
|
for (auto &ea : m_annotation_model.annotations())
|
|
{
|
|
if (ea.node_index >= 0 && !ea.comment.empty())
|
|
{
|
|
HTREEITEM h = m_event_tree.handle(ea.node_index);
|
|
if (h)
|
|
event_annotation_swap_image(&m_event_tree, h, ea);
|
|
}
|
|
}
|
|
|
|
// ---------- end of event initialization ----------
|
|
|
|
int num_messages = Num_messages - Num_builtin_messages;
|
|
m_messages.clear();
|
|
m_messages.reserve(num_messages);
|
|
for (i=0; i<num_messages; i++) {
|
|
msg = Messages[i + Num_builtin_messages];
|
|
m_messages.push_back(msg);
|
|
if (m_messages[i].avi_info.name){
|
|
m_messages[i].avi_info.name = strdup(m_messages[i].avi_info.name);
|
|
}
|
|
if (m_messages[i].wave_info.name){
|
|
m_messages[i].wave_info.name = strdup(m_messages[i].wave_info.name);
|
|
}
|
|
}
|
|
|
|
((CEdit *) GetDlgItem(IDC_MESSAGE_NAME))->LimitText(NAME_LENGTH - 1);
|
|
((CEdit *) GetDlgItem(IDC_MESSAGE_TEXT))->LimitText(MESSAGE_LENGTH - 1);
|
|
((CComboBox *) GetDlgItem(IDC_AVI_FILENAME))->LimitText(MAX_FILENAME_LEN - 1);
|
|
((CComboBox *) GetDlgItem(IDC_WAVE_FILENAME))->LimitText(MAX_FILENAME_LEN - 1);
|
|
|
|
list = (CListBox *) GetDlgItem(IDC_MESSAGE_LIST);
|
|
list->ResetContent();
|
|
for (const auto &message: m_messages) {
|
|
list->AddString(message.name);
|
|
}
|
|
|
|
box = (CComboBox *) GetDlgItem(IDC_AVI_FILENAME);
|
|
box->ResetContent();
|
|
box->AddString("<None>");
|
|
for (i=0; i<Num_messages; i++) {
|
|
if (Messages[i].avi_info.name) {
|
|
maybe_add_head(box, Messages[i].avi_info.name);
|
|
}
|
|
}
|
|
|
|
if (!Disable_hc_message_ani) {
|
|
maybe_add_head(box, "Head-TP2");
|
|
maybe_add_head(box, "Head-VC2");
|
|
maybe_add_head(box, "Head-TP4");
|
|
maybe_add_head(box, "Head-TP5");
|
|
maybe_add_head(box, "Head-TP6");
|
|
maybe_add_head(box, "Head-TP7");
|
|
maybe_add_head(box, "Head-TP8");
|
|
maybe_add_head(box, "Head-VP2");
|
|
maybe_add_head(box, "Head-VP2");
|
|
maybe_add_head(box, "Head-CM2");
|
|
maybe_add_head(box, "Head-CM3");
|
|
maybe_add_head(box, "Head-CM4");
|
|
maybe_add_head(box, "Head-CM5");
|
|
maybe_add_head(box, "Head-BSH");
|
|
|
|
}
|
|
|
|
for (auto &thisHead : Custom_head_anis) {
|
|
maybe_add_head(box, thisHead.c_str());
|
|
}
|
|
|
|
/*
|
|
box->AddString("Head-VC"); // force it in, since Sandeep wants it and it's not used in built-in messages
|
|
box->AddString("Head-VC2");
|
|
|
|
// add terran pilot heads
|
|
box->AddString("Head-TP4");
|
|
box->AddString("Head-TP5");
|
|
box->AddString("Head-TP6");
|
|
box->AddString("Head-TP7");
|
|
box->AddString("Head-TP8");
|
|
|
|
// add vasudan pilot heads
|
|
box->AddString("Head-VP2");
|
|
|
|
// BSH and CM2
|
|
box->AddString("Head-CM2");
|
|
box->AddString("Head-BSH");
|
|
*/
|
|
|
|
box = (CComboBox *) GetDlgItem(IDC_WAVE_FILENAME);
|
|
box->ResetContent();
|
|
box->AddString("<None>");
|
|
for (i=0; i<Num_messages; i++){
|
|
if (Messages[i].wave_info.name){
|
|
if (box->FindStringExact(i, Messages[i].wave_info.name) == CB_ERR){
|
|
box->AddString(Messages[i].wave_info.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add the persona names into the combo box
|
|
box = (CComboBox *) GetDlgItem(IDC_PERSONA_NAME);
|
|
box->ResetContent();
|
|
box->AddString("<None>");
|
|
for (const auto &persona: Personas) {
|
|
box->AddString(persona.name);
|
|
}
|
|
|
|
// set the first message to be the first non-builtin message (if it exists)
|
|
if ( Num_messages > Num_builtin_messages ){
|
|
m_cur_msg = 0;
|
|
} else {
|
|
m_cur_msg = -1;
|
|
}
|
|
|
|
update_cur_message();
|
|
return r;
|
|
}
|
|
|
|
void event_editor::load_tree()
|
|
{
|
|
int i;
|
|
|
|
m_event_tree.select_sexp_node = select_sexp_node;
|
|
select_sexp_node = -1;
|
|
|
|
m_event_tree.clear_tree();
|
|
m_events.clear();
|
|
m_sig.clear();
|
|
for (i=0; i<(int)Mission_events.size(); i++) {
|
|
m_events.push_back(Mission_events[i]);
|
|
m_sig.push_back(i);
|
|
|
|
if (m_events[i].name.empty())
|
|
m_events[i].name = "<Unnamed>";
|
|
|
|
m_events[i].formula = m_event_tree._model.load_sub_tree(Mission_events[i].formula, false, "do-nothing");
|
|
|
|
// we must check for the case of the repeat count being 0. This would happen if the repeat
|
|
// count is not specified in a mission
|
|
if ( m_events[i].repeat_count == 0 ){
|
|
m_events[i].repeat_count = 1;
|
|
}
|
|
}
|
|
|
|
m_event_tree._model.post_load();
|
|
cur_event = -1;
|
|
}
|
|
|
|
void event_editor::create_tree()
|
|
{
|
|
int i;
|
|
HTREEITEM h;
|
|
|
|
m_event_tree.DeleteAllItems();
|
|
for (i=0; i<(int)m_events.size(); i++) {
|
|
|
|
// set the proper bitmap
|
|
int image;
|
|
if (m_events[i].chain_delay >= 0) {
|
|
image = BITMAP_CHAIN;
|
|
if (!m_events[i].objective_text.empty()) {
|
|
image = BITMAP_CHAIN_DIRECTIVE;
|
|
}
|
|
} else {
|
|
image = BITMAP_ROOT;
|
|
if (!m_events[i].objective_text.empty()) {
|
|
image = BITMAP_ROOT_DIRECTIVE;
|
|
}
|
|
}
|
|
|
|
h = m_event_tree.insert(m_events[i].name.c_str(), image, image);
|
|
|
|
m_event_tree.SetItemData(h, m_events[i].formula);
|
|
m_event_tree.add_sub_tree(m_events[i].formula, h);
|
|
}
|
|
|
|
cur_event = -1;
|
|
}
|
|
|
|
void event_editor::OnRclickEventTree(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
save();
|
|
m_event_tree.right_clicked();
|
|
*pResult = 0;
|
|
}
|
|
|
|
void event_editor::OnBeginlabeleditEventTree(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;
|
|
CEdit *edit;
|
|
bool is_operator;
|
|
|
|
if (m_event_tree.edit_label(pTVDispInfo->item.hItem, &is_operator) == 1) {
|
|
// when editing operators, do not use the built-in CEdit control, but overlay the special CComboBox
|
|
if (is_operator) {
|
|
*pResult = 1;
|
|
modified = 1;
|
|
m_event_tree.start_operator_edit(pTVDispInfo->item.hItem);
|
|
}
|
|
else {
|
|
*pResult = 0;
|
|
modified = 1;
|
|
edit = m_event_tree.GetEditControl();
|
|
Assert(edit);
|
|
edit->SetLimitText(NAME_LENGTH - 1);
|
|
}
|
|
|
|
} else
|
|
*pResult = 1;
|
|
}
|
|
|
|
void event_editor::OnEndlabeleditEventTree(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;
|
|
|
|
*pResult = m_event_tree.end_label_edit(pTVDispInfo->item);
|
|
}
|
|
|
|
// This is needed as a HACK around default MFC standard
|
|
// It is not required, but overrides default MFC and links no errors without.
|
|
// (Specifically, this overrides the MFC behavior so that pressing Enter doesn't close the dialog)
|
|
void event_editor::OnOK()
|
|
{
|
|
HWND h;
|
|
CWnd *w;
|
|
|
|
save();
|
|
w = GetFocus();
|
|
if (w) {
|
|
h = w->m_hWnd;
|
|
GetDlgItem(IDC_EVENT_TREE)->SetFocus();
|
|
::SetFocus(h);
|
|
}
|
|
((CListBox *)GetDlgItem(IDC_MESSAGE_LIST))->SetCurSel(m_cur_msg);
|
|
}
|
|
|
|
int event_editor::query_modified()
|
|
{
|
|
UpdateData(TRUE);
|
|
|
|
if (modified)
|
|
return 1;
|
|
|
|
if (Mission_events.size() != m_events.size())
|
|
return 1;
|
|
|
|
for (size_t i = 0; i < m_events.size(); ++i) {
|
|
if (!lcase_equal(m_events[i].name, Mission_events[i].name))
|
|
return 1;
|
|
if (m_events[i].repeat_count != Mission_events[i].repeat_count)
|
|
return 1;
|
|
if (m_events[i].trigger_count != Mission_events[i].trigger_count)
|
|
return 1;
|
|
if (m_events[i].interval != Mission_events[i].interval)
|
|
return 1;
|
|
if (m_events[i].score != Mission_events[i].score)
|
|
return 1;
|
|
if (m_events[i].chain_delay != Mission_events[i].chain_delay)
|
|
return 1;
|
|
if (!lcase_equal(m_events[i].objective_text, Mission_events[i].objective_text))
|
|
return 1;
|
|
if (!lcase_equal(m_events[i].objective_key_text, Mission_events[i].objective_key_text))
|
|
return 1;
|
|
if (m_events[i].flags != Mission_events[i].flags)
|
|
return 1;
|
|
if (m_events[i].mission_log_flags != Mission_events[i].mission_log_flags)
|
|
return 1;
|
|
}
|
|
|
|
if (static_cast<int>(m_messages.size()) != Num_messages - Num_builtin_messages) {
|
|
return 1;
|
|
}
|
|
|
|
for (size_t i = 0; i < m_messages.size(); ++i) {
|
|
auto& local = m_messages[i];
|
|
auto& ref = Messages[Num_builtin_messages + i];
|
|
|
|
if (stricmp(local.name, ref.name) != 0)
|
|
return 1;
|
|
if (stricmp(local.message, ref.message) != 0)
|
|
return 1;
|
|
if (!lcase_equal(local.note, ref.note))
|
|
return 1;
|
|
if (local.persona_index != ref.persona_index)
|
|
return 1;
|
|
if (local.multi_team != ref.multi_team)
|
|
return 1;
|
|
if (safe_stricmp(local.avi_info.name, ref.avi_info.name) != 0)
|
|
return 1;
|
|
if (safe_stricmp(local.wave_info.name, ref.wave_info.name) != 0)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void event_editor::OnButtonOk()
|
|
{
|
|
SCP_vector<std::pair<SCP_string, SCP_string>> names;
|
|
int i;
|
|
|
|
audiostream_close_file(m_wave_id, 0);
|
|
m_wave_id = -1;
|
|
|
|
save();
|
|
if (query_modified())
|
|
set_modified();
|
|
|
|
for (auto &event: Mission_events) {
|
|
free_sexp2(event.formula);
|
|
event.result = 0; // use this as a processed flag
|
|
}
|
|
|
|
// rename all sexp references to old events
|
|
for (i=0; i<(int)m_events.size(); i++) {
|
|
if (m_sig[i] >= 0) {
|
|
names.emplace_back(Mission_events[m_sig[i]].name, m_events[i].name);
|
|
Mission_events[m_sig[i]].result = 1;
|
|
}
|
|
}
|
|
|
|
// invalidate all sexp references to deleted events.
|
|
for (const auto &event: Mission_events) {
|
|
if (!event.result) {
|
|
SCP_string buf = "<" + event.name + ">";
|
|
|
|
// force it to not be too long
|
|
if (SCP_truncate(buf, NAME_LENGTH - 1))
|
|
buf.back() = '>';
|
|
|
|
names.emplace_back(event.name, buf);
|
|
}
|
|
}
|
|
|
|
// copy all dialog events to the mission
|
|
Mission_events.clear();
|
|
for (const auto &dialog_event: m_events) {
|
|
Mission_events.push_back(dialog_event);
|
|
Mission_events.back().formula = m_event_tree._model.save_tree(dialog_event.formula);
|
|
}
|
|
|
|
// now update all sexp references
|
|
for (const auto &name_pair: names)
|
|
update_sexp_references(name_pair.first.c_str(), name_pair.second.c_str(), OPF_EVENT_NAME);
|
|
|
|
for (i=Num_builtin_messages; i<Num_messages; i++)
|
|
message_free_media_names(Messages[i]);
|
|
|
|
Num_messages = (int)m_messages.size() + Num_builtin_messages;
|
|
Messages.resize(Num_messages);
|
|
for (i=0; i<(int)m_messages.size(); i++)
|
|
Messages[i + Num_builtin_messages] = m_messages[i];
|
|
|
|
m_annotation_model.saveToGlobal(m_event_tree._model.tree_nodes, m_events, m_sig);
|
|
|
|
theApp.record_window_data(&Events_wnd_data, this);
|
|
delete Event_editor_dlg;
|
|
Event_editor_dlg = NULL;
|
|
|
|
FREDDoc_ptr->autosave("event editor");
|
|
}
|
|
|
|
// load controls with structure data
|
|
void event_editor::update_cur_message()
|
|
{
|
|
int enable = TRUE;
|
|
|
|
audiostream_close_file(m_wave_id, 0);
|
|
m_wave_id = -1;
|
|
|
|
if (m_cur_msg < 0) {
|
|
enable = FALSE;
|
|
m_message_name = _T("");
|
|
m_message_note = _T("");
|
|
m_message_text = _T("");
|
|
m_avi_filename = _T("");
|
|
m_wave_filename = _T("");
|
|
m_persona = 0;
|
|
m_message_team = -1;
|
|
} else {
|
|
m_message_name = m_messages[m_cur_msg].name;
|
|
m_message_note = (CString)m_messages[m_cur_msg].note.c_str();
|
|
m_message_text = m_messages[m_cur_msg].message;
|
|
if (m_messages[m_cur_msg].avi_info.name){
|
|
m_avi_filename = _T(m_messages[m_cur_msg].avi_info.name);
|
|
} else {
|
|
m_avi_filename = _T("<None>");
|
|
}
|
|
|
|
if (m_messages[m_cur_msg].wave_info.name){
|
|
m_wave_filename = _T(m_messages[m_cur_msg].wave_info.name);
|
|
} else {
|
|
m_wave_filename = _T("<None>");
|
|
}
|
|
|
|
// add persona id
|
|
if ( m_messages[m_cur_msg].persona_index != -1 ){
|
|
m_persona = m_messages[m_cur_msg].persona_index + 1; // add one for the "none" at the beginning of the list
|
|
} else {
|
|
m_persona = 0;
|
|
}
|
|
|
|
if(m_messages[m_cur_msg].multi_team >= MAX_TVT_TEAMS){
|
|
m_message_team = -1;
|
|
m_messages[m_cur_msg].multi_team = -1;
|
|
} else {
|
|
m_message_team = m_messages[m_cur_msg].multi_team;
|
|
}
|
|
/*
|
|
m_event_num = find_event();
|
|
if (m_event_num < 0) {
|
|
node = -1;
|
|
m_sender = m_priority = 0;
|
|
|
|
} else
|
|
node = CADR(Mission_events[m_event_num].formula);
|
|
*/ }
|
|
|
|
GetDlgItem(IDC_MESSAGE_NAME)->EnableWindow(enable);
|
|
GetDlgItem(IDC_MESSAGE_TEXT)->EnableWindow(enable);
|
|
GetDlgItem(IDC_AVI_FILENAME)->EnableWindow(enable);
|
|
GetDlgItem(IDC_BROWSE_AVI)->EnableWindow(enable);
|
|
GetDlgItem(IDC_BROWSE_WAVE)->EnableWindow(enable);
|
|
GetDlgItem(IDC_WAVE_FILENAME)->EnableWindow(enable);
|
|
GetDlgItem(IDC_DELETE_MSG)->EnableWindow(enable);
|
|
GetDlgItem(IDC_PERSONA_NAME)->EnableWindow(enable);
|
|
GetDlgItem(IDC_MESSAGE_TEAM)->EnableWindow(enable);
|
|
update_move_buttons();
|
|
UpdateData(FALSE);
|
|
}
|
|
|
|
void event_editor::OnButtonNewEvent()
|
|
{
|
|
// before we do anything, we must check and save off any data from the current event (e.g
|
|
// the repeat count and interval count)
|
|
save();
|
|
|
|
m_events.emplace_back();
|
|
m_sig.push_back(-1);
|
|
reset_event((int)m_events.size() - 1, TVI_LAST);
|
|
}
|
|
|
|
void event_editor::OnInsert()
|
|
{
|
|
if(cur_event < 0 || m_events.empty())
|
|
{
|
|
//There are no events yet, so just create one
|
|
OnButtonNewEvent();
|
|
}
|
|
else
|
|
{
|
|
// before we do anything, we must check and save off any data from the current event (e.g
|
|
// the repeat count and interval count)
|
|
save();
|
|
|
|
m_events.insert(m_events.begin() + cur_event, mission_event());
|
|
m_sig.insert(m_sig.begin() + cur_event, -1);
|
|
|
|
if (cur_event){
|
|
reset_event(cur_event, get_event_handle(cur_event - 1));
|
|
} else {
|
|
reset_event(cur_event, TVI_FIRST);
|
|
}
|
|
}
|
|
}
|
|
|
|
HTREEITEM event_editor::get_event_handle(int num)
|
|
{
|
|
HTREEITEM h;
|
|
|
|
h = m_event_tree.GetRootItem();
|
|
while (h) {
|
|
if ((int) m_event_tree.GetItemData(h) == m_events[num].formula){
|
|
return h;
|
|
}
|
|
|
|
h = m_event_tree.GetNextSiblingItem(h);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int event_editor::get_event_num(HTREEITEM handle)
|
|
{
|
|
int formula = (int)m_event_tree.GetItemData(handle);
|
|
|
|
for (int i = 0; i < (int)m_events.size(); ++i)
|
|
if (formula == m_events[i].formula)
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
void event_editor::reset_event(int num, HTREEITEM after)
|
|
{
|
|
int index;
|
|
HTREEITEM h;
|
|
|
|
// this is always called for a freshly constructed event, so all we have to do is set the name
|
|
m_events[num].name = "Event name";
|
|
h = m_event_tree.insert(m_events[num].name.c_str(), BITMAP_ROOT, BITMAP_ROOT, TVI_ROOT, after);
|
|
|
|
m_event_tree.item_index = -1;
|
|
m_event_tree.add_operator("when", h);
|
|
index = m_events[num].formula = m_event_tree.item_index;
|
|
m_event_tree.SetItemData(h, index);
|
|
m_event_tree.add_operator("true");
|
|
m_event_tree.item_index = index;
|
|
m_event_tree.add_operator("do-nothing");
|
|
|
|
update_cur_event();
|
|
|
|
m_event_tree.SelectItem(h);
|
|
}
|
|
|
|
void event_editor::OnDelete()
|
|
{
|
|
HTREEITEM h;
|
|
|
|
// call update_cur_event to clean up local class variables so that we can correctly
|
|
// set up the newly selected item.
|
|
cur_event = -1;
|
|
update_cur_event();
|
|
|
|
h = m_event_tree.GetSelectedItem();
|
|
if (h) {
|
|
while (m_event_tree.GetParentItem(h))
|
|
h = m_event_tree.GetParentItem(h);
|
|
|
|
m_event_tree.setup_selected(h);
|
|
m_event_tree.OnCommand(ID_DELETE, 0);
|
|
}
|
|
}
|
|
|
|
// this is called when you hit the escape key..
|
|
void event_editor::OnCancel()
|
|
{
|
|
// override MFC default behavior and do nothing
|
|
// the Esc key is used for certain actions inside the events editor
|
|
// so pressing Esc shouldn't close the window
|
|
}
|
|
|
|
// this is called when you click the ID_CANCEL button
|
|
void event_editor::OnButtonCancel()
|
|
{
|
|
audiostream_close_file(m_wave_id, 0);
|
|
m_wave_id = -1;
|
|
|
|
if (query_modified()) {
|
|
int z = MessageBox("Do you want to keep your changes?", "Close", MB_ICONQUESTION | MB_YESNOCANCEL);
|
|
if (z == IDCANCEL){
|
|
return;
|
|
}
|
|
|
|
if (z == IDYES) {
|
|
OnButtonOk();
|
|
return;
|
|
}
|
|
}
|
|
|
|
theApp.record_window_data(&Events_wnd_data, this);
|
|
delete Event_editor_dlg;
|
|
Event_editor_dlg = NULL;
|
|
}
|
|
|
|
void event_editor::OnClose()
|
|
{
|
|
OnButtonCancel();
|
|
}
|
|
|
|
void event_editor::insert_handler(int old, int node)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<(int)m_events.size(); i++){
|
|
if (m_events[i].formula == old){
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert(i < (int)m_events.size());
|
|
m_events[i].formula = node;
|
|
return;
|
|
}
|
|
|
|
void event_editor::save()
|
|
{
|
|
int m;
|
|
|
|
if (m_cur_msg >= 0) {
|
|
m = m_cur_msg;
|
|
} else {
|
|
// the current message could be -1 because the message list
|
|
// lost focus, so remember the last focused message
|
|
// (but make sure it's valid)
|
|
if (m_cur_msg_old >= 0 && m_cur_msg_old < (int)m_messages.size()) {
|
|
m = m_cur_msg_old;
|
|
} else {
|
|
m = -1;
|
|
}
|
|
}
|
|
|
|
save_event(cur_event);
|
|
save_message(m);
|
|
}
|
|
|
|
void event_editor::save_event(int e)
|
|
{
|
|
if (e < 0) {
|
|
return;
|
|
}
|
|
|
|
UpdateData(TRUE);
|
|
|
|
// there is currently no usage of a number < -1, so cap it at that
|
|
if (m_repeat_count < -1)
|
|
m_repeat_count = -1;
|
|
if (m_trigger_count < -1)
|
|
m_trigger_count = -1;
|
|
|
|
m_events[e].repeat_count = m_repeat_count;
|
|
m_events[e].trigger_count = m_trigger_count;
|
|
m_events[e].interval = m_interval;
|
|
m_events[e].score = m_event_score;
|
|
|
|
// handle chain
|
|
if (m_chained) {
|
|
m_events[e].chain_delay = m_chain_delay;
|
|
} else {
|
|
m_events[e].chain_delay = -1;
|
|
}
|
|
|
|
// handle objective text
|
|
lcl_fred_replace_stuff(m_obj_text);
|
|
lcl_fred_replace_stuff(m_obj_key_text);
|
|
m_events[e].objective_text = (LPCTSTR)m_obj_text;
|
|
m_events[e].objective_key_text = (LPCTSTR)m_obj_key_text;
|
|
|
|
// update bitmap
|
|
int bitmap;
|
|
if (m_chained) {
|
|
if (m_obj_text.IsEmpty()) {
|
|
bitmap = BITMAP_CHAIN;
|
|
} else {
|
|
bitmap = BITMAP_CHAIN_DIRECTIVE;
|
|
}
|
|
} else {
|
|
// not chained
|
|
if (m_obj_text.IsEmpty()) {
|
|
bitmap = BITMAP_ROOT;
|
|
} else {
|
|
bitmap = BITMAP_ROOT_DIRECTIVE;
|
|
}
|
|
}
|
|
|
|
// handle event flags
|
|
m_events[e].flags = 0;
|
|
if (m_use_msecs)
|
|
m_events[e].flags |= MEF_USE_MSECS;
|
|
|
|
// handle event log flags
|
|
m_events[e].mission_log_flags = 0;
|
|
if (m_log_true)
|
|
m_events[e].mission_log_flags |= MLF_SEXP_TRUE;
|
|
if (m_log_false)
|
|
m_events[e].mission_log_flags |= MLF_SEXP_FALSE;
|
|
if (m_log_always_false)
|
|
m_events[e].mission_log_flags |= MLF_SEXP_KNOWN_FALSE;
|
|
if (m_log_1st_repeat)
|
|
m_events[e].mission_log_flags |= MLF_FIRST_REPEAT_ONLY;
|
|
if (m_log_last_repeat)
|
|
m_events[e].mission_log_flags |= MLF_LAST_REPEAT_ONLY;
|
|
if (m_log_1st_trigger)
|
|
m_events[e].mission_log_flags |= MLF_FIRST_TRIGGER_ONLY;
|
|
if (m_log_last_trigger)
|
|
m_events[e].mission_log_flags |= MLF_LAST_TRIGGER_ONLY;
|
|
if (m_log_state_change)
|
|
m_events[e].mission_log_flags |= MLF_STATE_CHANGE;
|
|
|
|
|
|
// Search for item to update
|
|
HTREEITEM h = m_event_tree.GetRootItem();
|
|
while (h) {
|
|
if ((int) m_event_tree.GetItemData(h) == m_events[e].formula) {
|
|
m_event_tree.SetItemImage(h, bitmap, bitmap);
|
|
return;
|
|
}
|
|
|
|
h = m_event_tree.GetNextSiblingItem(h);
|
|
}
|
|
|
|
}
|
|
|
|
// this function deals with the left click on an event in the tree view. We get into this
|
|
// function so that we may update the other data on the screen (i.e repeat count and interval
|
|
// count)
|
|
void event_editor::OnSelchangedEventTree(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
int i, z;
|
|
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
|
|
HTREEITEM h, h2;
|
|
|
|
// before we do anything, we must check and save off any data from the current event (i.e.
|
|
// the repeat count and interval count)
|
|
save();
|
|
h = pNMTreeView->itemNew.hItem;
|
|
if (!h){
|
|
return;
|
|
}
|
|
|
|
m_event_tree.update_help(h);
|
|
while ((h2 = m_event_tree.GetParentItem(h))>0){
|
|
h = h2;
|
|
}
|
|
|
|
z = (int)m_event_tree.GetItemData(h);
|
|
for (i=0; i<(int)m_events.size(); i++){
|
|
if (m_events[i].formula == z){
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert(i < (int)m_events.size());
|
|
cur_event = i;
|
|
update_cur_event();
|
|
|
|
*pResult = 0;
|
|
}
|
|
|
|
void event_editor::update_cur_event()
|
|
{
|
|
if (cur_event < 0) {
|
|
m_repeat_count = 1;
|
|
m_trigger_count = 1;
|
|
m_interval = 1;
|
|
m_chained = FALSE;
|
|
m_chain_delay = 0;
|
|
m_event_score = 0;
|
|
m_team = -1;
|
|
m_obj_text.Empty();
|
|
m_obj_key_text.Empty();
|
|
m_use_msecs = FALSE;
|
|
|
|
m_log_true = FALSE;
|
|
m_log_false = FALSE;
|
|
m_log_always_false = FALSE;
|
|
m_log_1st_repeat = FALSE;
|
|
m_log_last_repeat = FALSE;
|
|
m_log_1st_trigger = FALSE;
|
|
m_log_last_trigger = FALSE;
|
|
m_log_state_change = FALSE;
|
|
|
|
GetDlgItem(IDC_REPEAT_COUNT)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_TRIGGER_COUNT)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_INTERVAL_TIME)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_CHAINED)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_CHAIN_DELAY)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_EVENT_SCORE)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_EVENT_TEAM)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_OBJ_TEXT)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_OBJ_KEY_TEXT)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_USE_MSECS)->EnableWindow(FALSE);
|
|
|
|
GetDlgItem(IDC_MISSION_LOG_TRUE)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_MISSION_LOG_FALSE)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_MISSION_LOG_ALWAYS_FALSE)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_MISSION_LOG_1ST_REPEAT)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_MISSION_LOG_LAST_REPEAT)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_MISSION_LOG_1ST_TRIGGER)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_MISSION_LOG_LAST_TRIGGER)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_MISSION_LOG_STATE_CHANGE)->EnableWindow(FALSE);
|
|
|
|
update_move_buttons();
|
|
UpdateData(FALSE);
|
|
return;
|
|
}
|
|
|
|
m_team = m_events[cur_event].team;
|
|
|
|
m_repeat_count = m_events[cur_event].repeat_count;
|
|
m_trigger_count = m_events[cur_event].trigger_count;
|
|
m_interval = m_events[cur_event].interval;
|
|
m_event_score = m_events[cur_event].score;
|
|
if (m_events[cur_event].chain_delay >= 0) {
|
|
m_chained = TRUE;
|
|
m_chain_delay = m_events[cur_event].chain_delay;
|
|
GetDlgItem(IDC_CHAIN_DELAY) -> EnableWindow(TRUE);
|
|
|
|
} else {
|
|
m_chained = FALSE;
|
|
m_chain_delay = 0;
|
|
GetDlgItem(IDC_CHAIN_DELAY) -> EnableWindow(FALSE);
|
|
}
|
|
|
|
m_obj_text = m_events[cur_event].objective_text.c_str();
|
|
m_obj_key_text = m_events[cur_event].objective_key_text.c_str();
|
|
|
|
GetDlgItem(IDC_REPEAT_COUNT)->EnableWindow(TRUE);
|
|
GetDlgItem(IDC_TRIGGER_COUNT)->EnableWindow(TRUE);
|
|
|
|
GetDlgItem(IDC_USE_MSECS)->EnableWindow(TRUE);
|
|
GetDlgItem(IDC_MISSION_LOG_TRUE)->EnableWindow(TRUE);
|
|
GetDlgItem(IDC_MISSION_LOG_FALSE)->EnableWindow(TRUE);
|
|
GetDlgItem(IDC_MISSION_LOG_ALWAYS_FALSE)->EnableWindow(TRUE);
|
|
GetDlgItem(IDC_MISSION_LOG_1ST_REPEAT)->EnableWindow(TRUE);
|
|
GetDlgItem(IDC_MISSION_LOG_LAST_REPEAT)->EnableWindow(TRUE);
|
|
GetDlgItem(IDC_MISSION_LOG_1ST_TRIGGER)->EnableWindow(TRUE);
|
|
GetDlgItem(IDC_MISSION_LOG_LAST_TRIGGER)->EnableWindow(TRUE);
|
|
GetDlgItem(IDC_MISSION_LOG_STATE_CHANGE)->EnableWindow(TRUE);
|
|
|
|
if ((m_repeat_count < 0) || (m_repeat_count > 1) || (m_trigger_count < 0) || (m_trigger_count > 1)) {
|
|
GetDlgItem(IDC_INTERVAL_TIME) -> EnableWindow(TRUE);
|
|
} else {
|
|
m_interval = 1;
|
|
GetDlgItem(IDC_INTERVAL_TIME) -> EnableWindow(FALSE);
|
|
}
|
|
|
|
GetDlgItem(IDC_EVENT_SCORE) -> EnableWindow(TRUE);
|
|
GetDlgItem(IDC_CHAINED) -> EnableWindow(TRUE);
|
|
GetDlgItem(IDC_OBJ_TEXT) -> EnableWindow(TRUE);
|
|
GetDlgItem(IDC_OBJ_KEY_TEXT) -> EnableWindow(TRUE);
|
|
GetDlgItem(IDC_EVENT_TEAM)->EnableWindow(FALSE);
|
|
if ( The_mission.game_type & MISSION_TYPE_MULTI_TEAMS ){
|
|
GetDlgItem(IDC_EVENT_TEAM)->EnableWindow(TRUE);
|
|
}
|
|
|
|
// handle event flags
|
|
m_use_msecs = (m_events[cur_event].flags & MEF_USE_MSECS) ? TRUE : FALSE;
|
|
|
|
// handle event log flags
|
|
m_log_true = (m_events[cur_event].mission_log_flags & MLF_SEXP_TRUE) ? TRUE : FALSE;
|
|
m_log_false = (m_events[cur_event].mission_log_flags & MLF_SEXP_FALSE) ? TRUE : FALSE;
|
|
m_log_always_false = (m_events[cur_event].mission_log_flags & MLF_SEXP_KNOWN_FALSE) ? TRUE : FALSE;
|
|
m_log_1st_repeat = (m_events[cur_event].mission_log_flags & MLF_FIRST_REPEAT_ONLY) ? TRUE : FALSE;
|
|
m_log_last_repeat = (m_events[cur_event].mission_log_flags & MLF_LAST_REPEAT_ONLY) ? TRUE : FALSE;
|
|
m_log_1st_trigger = (m_events[cur_event].mission_log_flags & MLF_FIRST_TRIGGER_ONLY) ? TRUE : FALSE;
|
|
m_log_last_trigger = (m_events[cur_event].mission_log_flags & MLF_LAST_TRIGGER_ONLY) ? TRUE : FALSE;
|
|
m_log_state_change = (m_events[cur_event].mission_log_flags & MLF_STATE_CHANGE) ? TRUE : FALSE;
|
|
|
|
update_move_buttons();
|
|
UpdateData(FALSE);
|
|
}
|
|
|
|
void event_editor::OnUpdateRepeatCount()
|
|
{
|
|
char buf[128];
|
|
int count = 128;
|
|
GetDlgItem(IDC_REPEAT_COUNT)->GetWindowText(buf, count);
|
|
m_repeat_count = atoi(buf);
|
|
|
|
if ((m_repeat_count < 0) || (m_repeat_count > 1) || (m_trigger_count < 0) || (m_trigger_count > 1)) {
|
|
GetDlgItem(IDC_INTERVAL_TIME)->EnableWindow(TRUE);
|
|
} else {
|
|
GetDlgItem(IDC_INTERVAL_TIME)->EnableWindow(FALSE);
|
|
}
|
|
}
|
|
|
|
void event_editor::OnUpdateTriggerCount()
|
|
{
|
|
char buf[128];
|
|
int count = 128;
|
|
|
|
GetDlgItem(IDC_TRIGGER_COUNT)->GetWindowText(buf, count);
|
|
m_trigger_count = atoi(buf);
|
|
|
|
if ((m_repeat_count < 0) || (m_repeat_count > 1) || (m_trigger_count < 0) || (m_trigger_count > 1)) {
|
|
GetDlgItem(IDC_INTERVAL_TIME)->EnableWindow(TRUE);
|
|
} else {
|
|
GetDlgItem(IDC_INTERVAL_TIME)->EnableWindow(FALSE);
|
|
}
|
|
}
|
|
void event_editor::move_handler(int node1, int node2, bool insert_before)
|
|
{
|
|
int index1, index2, s;
|
|
mission_event e;
|
|
|
|
save();
|
|
|
|
for (index1=0; index1<(int)m_events.size(); index1++){
|
|
if (m_events[index1].formula == node1){
|
|
break;
|
|
}
|
|
}
|
|
Assert(index1 < (int)m_events.size());
|
|
|
|
for (index2=0; index2<(int)m_events.size(); index2++){
|
|
if (m_events[index2].formula == node2){
|
|
break;
|
|
}
|
|
}
|
|
Assert(index2 < (int)m_events.size());
|
|
|
|
e = m_events[index1];
|
|
s = m_sig[index1];
|
|
|
|
int offset = insert_before ? -1 : 0;
|
|
|
|
while (index1 < index2 + offset) {
|
|
m_events[index1] = m_events[index1 + 1];
|
|
m_sig[index1] = m_sig[index1 + 1];
|
|
index1++;
|
|
}
|
|
while (index1 > index2 + offset + 1) {
|
|
m_events[index1] = m_events[index1 - 1];
|
|
m_sig[index1] = m_sig[index1 - 1];
|
|
index1--;
|
|
}
|
|
|
|
m_events[index1] = e;
|
|
m_sig[index1] = s;
|
|
|
|
cur_event = index2;
|
|
update_cur_event();
|
|
}
|
|
|
|
// Enable/disable the event and message reorder arrows based on the current
|
|
// selection and its position in the list.
|
|
void event_editor::update_move_buttons()
|
|
{
|
|
int ecount = (int)m_events.size();
|
|
BOOL e_up = (cur_event > 0 && cur_event < ecount) ? TRUE : FALSE;
|
|
BOOL e_down = (cur_event >= 0 && cur_event < ecount - 1) ? TRUE : FALSE;
|
|
GetDlgItem(IDC_EVENT_MOVE_TO_TOP)->EnableWindow(e_up);
|
|
GetDlgItem(IDC_EVENT_MOVE_UP)->EnableWindow(e_up);
|
|
GetDlgItem(IDC_EVENT_MOVE_DOWN)->EnableWindow(e_down);
|
|
GetDlgItem(IDC_EVENT_MOVE_TO_BOTTOM)->EnableWindow(e_down);
|
|
|
|
int mcount = (int)m_messages.size();
|
|
BOOL m_up = (m_cur_msg > 0 && m_cur_msg < mcount) ? TRUE : FALSE;
|
|
BOOL m_down = (m_cur_msg >= 0 && m_cur_msg < mcount - 1) ? TRUE : FALSE;
|
|
GetDlgItem(IDC_MESSAGE_MOVE_TO_TOP)->EnableWindow(m_up);
|
|
GetDlgItem(IDC_MESSAGE_MOVE_UP)->EnableWindow(m_up);
|
|
GetDlgItem(IDC_MESSAGE_MOVE_DOWN)->EnableWindow(m_down);
|
|
GetDlgItem(IDC_MESSAGE_MOVE_TO_BOTTOM)->EnableWindow(m_down);
|
|
}
|
|
|
|
// Reorder the currently-selected event. 'up'/'all_the_way' are interpreted as:
|
|
// move up one, move to top, move down one, move to bottom.
|
|
void event_editor::move_event(bool up, bool all_the_way)
|
|
{
|
|
int count = (int)m_events.size();
|
|
if (cur_event < 0 || cur_event >= count)
|
|
return;
|
|
|
|
int from = cur_event;
|
|
int to;
|
|
if (up)
|
|
to = all_the_way ? 0 : from - 1;
|
|
else
|
|
to = all_the_way ? count - 1 : from + 1;
|
|
|
|
if (to < 0 || to >= count || to == from)
|
|
return;
|
|
|
|
// Perform the move exactly as a drag-and-drop would: move_root shifts the
|
|
// tree item in place (the on_node_handle_changed hook keeps annotations
|
|
// attached, and the expanded state is preserved), and move_handler reorders
|
|
// m_events/m_sig to match (it also calls save() and update_cur_event()).
|
|
HTREEITEM source = get_event_handle(from);
|
|
HTREEITEM dest = get_event_handle(to);
|
|
int node1 = m_events[from].formula;
|
|
int node2 = m_events[to].formula;
|
|
bool insert_before = (to < from);
|
|
|
|
m_event_tree.move_root(source, dest, insert_before);
|
|
move_handler(node1, node2, insert_before);
|
|
|
|
update_move_buttons();
|
|
}
|
|
|
|
// Reorder the currently-selected message. 'up'/'all_the_way' are interpreted
|
|
// as: move up one, move to top, move down one, move to bottom.
|
|
void event_editor::move_message(bool up, bool all_the_way)
|
|
{
|
|
// commit any pending edits first
|
|
save();
|
|
|
|
int count = (int)m_messages.size();
|
|
if (m_cur_msg < 0 || m_cur_msg >= count)
|
|
return;
|
|
|
|
int from = m_cur_msg;
|
|
int to;
|
|
if (up)
|
|
to = all_the_way ? 0 : from - 1;
|
|
else
|
|
to = all_the_way ? count - 1 : from + 1;
|
|
|
|
if (to < 0 || to >= count || to == from)
|
|
return;
|
|
|
|
// Rotate m_messages so the item at 'from' lands at 'to'. MMessage has move
|
|
// semantics, so this transfers the media-name pointers without copying.
|
|
MMessage m = std::move(m_messages[from]);
|
|
if (from < to)
|
|
{
|
|
for (int i = from; i < to; ++i)
|
|
m_messages[i] = std::move(m_messages[i + 1]);
|
|
}
|
|
else
|
|
{
|
|
for (int i = from; i > to; --i)
|
|
m_messages[i] = std::move(m_messages[i - 1]);
|
|
}
|
|
m_messages[to] = std::move(m);
|
|
|
|
// Rebuild the listbox to match the new order.
|
|
CListBox *list = (CListBox *) GetDlgItem(IDC_MESSAGE_LIST);
|
|
list->ResetContent();
|
|
for (const auto &msg : m_messages)
|
|
list->AddString(msg.name);
|
|
|
|
m_cur_msg = to;
|
|
list->SetCurSel(to);
|
|
|
|
update_cur_message();
|
|
update_move_buttons();
|
|
modified = 1;
|
|
}
|
|
|
|
void event_editor::OnEventMoveToTop() { move_event(true, true); }
|
|
void event_editor::OnEventMoveUp() { move_event(true, false); }
|
|
void event_editor::OnEventMoveDown() { move_event(false, false); }
|
|
void event_editor::OnEventMoveToBottom() { move_event(false, true); }
|
|
void event_editor::OnMessageMoveToTop() { move_message(true, true); }
|
|
void event_editor::OnMessageMoveUp() { move_message(true, false); }
|
|
void event_editor::OnMessageMoveDown() { move_message(false, false); }
|
|
void event_editor::OnMessageMoveToBottom() { move_message(false, true); }
|
|
|
|
void event_editor::OnChained()
|
|
{
|
|
int image;
|
|
HTREEITEM h;
|
|
|
|
UpdateData(TRUE);
|
|
if (m_chained) {
|
|
GetDlgItem(IDC_CHAIN_DELAY) -> EnableWindow(TRUE);
|
|
if (m_obj_text.IsEmpty()) {
|
|
image = BITMAP_CHAIN;
|
|
} else {
|
|
image = BITMAP_CHAIN_DIRECTIVE;
|
|
}
|
|
} else {
|
|
GetDlgItem(IDC_CHAIN_DELAY) -> EnableWindow(FALSE);
|
|
if (m_obj_text.IsEmpty()) {
|
|
image = BITMAP_ROOT;
|
|
} else {
|
|
image = BITMAP_ROOT_DIRECTIVE;
|
|
}
|
|
}
|
|
|
|
h = m_event_tree.GetRootItem();
|
|
while (h) {
|
|
if ((int) m_event_tree.GetItemData(h) == m_events[cur_event].formula) {
|
|
m_event_tree.SetItemImage(h, image, image);
|
|
return;
|
|
}
|
|
|
|
h = m_event_tree.GetNextSiblingItem(h);
|
|
}
|
|
}
|
|
|
|
void event_editor::OnSelchangeMessageList()
|
|
{
|
|
static int flag = 0;
|
|
|
|
if (flag)
|
|
return;
|
|
/*
|
|
if (save_message(m_cur_msg)) {
|
|
flag = 1;
|
|
((CListBox *) GetDlgItem(IDC_MESSAGE_LIST)) -> SetCurSel(old);
|
|
m_cur_msg = old;
|
|
flag = 0;
|
|
return;
|
|
}*/
|
|
|
|
save();
|
|
update_cur_message();
|
|
}
|
|
|
|
int event_editor::save_message(int num)
|
|
{
|
|
char *ptr;
|
|
int i, conflict = 0;
|
|
CListBox *list;
|
|
|
|
UpdateData(TRUE);
|
|
if (num >= 0) {
|
|
ptr = (char *) (LPCTSTR) m_message_name;
|
|
for (i=0; i<Num_builtin_messages; i++){
|
|
if (!stricmp(m_message_name, Messages[i].name)) {
|
|
conflict = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i=0; i<(int)m_messages.size(); i++){
|
|
if ((i != num) && (!stricmp(m_message_name, m_messages[i].name))) {
|
|
conflict = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!conflict) { // update name if no conflicts, otherwise keep old name
|
|
string_copy(m_messages[num].name, m_message_name, NAME_LENGTH - 1);
|
|
|
|
list = (CListBox *) GetDlgItem(IDC_MESSAGE_LIST);
|
|
list->DeleteString(num);
|
|
list->InsertString(num, m_message_name);
|
|
}
|
|
|
|
string_copy(m_messages[num].message, m_message_text, MESSAGE_LENGTH - 1);
|
|
m_messages[num].note = m_message_note;
|
|
lcl_fred_replace_stuff(m_messages[num].message, MESSAGE_LENGTH - 1);
|
|
if (m_messages[num].avi_info.name){
|
|
free(m_messages[num].avi_info.name);
|
|
}
|
|
|
|
ptr = (char *) (LPCTSTR) m_avi_filename;
|
|
if ( !ptr || !VALID_FNAME(ptr) ) {
|
|
m_messages[num].avi_info.name = NULL;
|
|
} else {
|
|
m_messages[num].avi_info.name = strdup(ptr);
|
|
}
|
|
|
|
if (m_messages[num].wave_info.name){
|
|
free(m_messages[num].wave_info.name);
|
|
}
|
|
|
|
ptr = (char *) (LPCTSTR) m_wave_filename;
|
|
if ( !ptr || !VALID_FNAME(ptr) ) {
|
|
m_messages[num].wave_info.name = NULL;
|
|
} else {
|
|
m_messages[num].wave_info.name = strdup(ptr);
|
|
}
|
|
|
|
// update the persona to the message. We subtract 1 for the "None" at the beginning of the combo
|
|
// box list.
|
|
m_messages[num].persona_index = m_persona - 1;
|
|
|
|
if(m_message_team >= MAX_TVT_TEAMS){
|
|
m_messages[num].multi_team = -1;
|
|
m_message_team = -1;
|
|
} else {
|
|
m_messages[num].multi_team = m_message_team;
|
|
}
|
|
|
|
// possible TODO: auto-update event tree references to this message if we renamed it.
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void event_editor::OnNewMsg()
|
|
{
|
|
MMessage msg;
|
|
|
|
save();
|
|
strcpy_s(msg.name, "<new message>");
|
|
((CListBox *) GetDlgItem(IDC_MESSAGE_LIST))->AddString("<new message>");
|
|
|
|
strcpy_s(msg.message, "<put description here>");
|
|
msg.avi_info.name = NULL;
|
|
msg.wave_info.name = NULL;
|
|
msg.persona_index = -1;
|
|
msg.multi_team = -1;
|
|
m_messages.push_back(msg);
|
|
m_cur_msg = (int)m_messages.size() - 1;
|
|
|
|
modified = 1;
|
|
update_cur_message();
|
|
}
|
|
|
|
void event_editor::OnInsertMsg()
|
|
{
|
|
// with no current message there's nothing to insert ahead of, so just append
|
|
if (m_cur_msg < 0 || m_messages.empty()) {
|
|
OnNewMsg();
|
|
return;
|
|
}
|
|
|
|
save();
|
|
|
|
MMessage msg;
|
|
strcpy_s(msg.name, "<new message>");
|
|
strcpy_s(msg.message, "<put description here>");
|
|
msg.avi_info.name = nullptr;
|
|
msg.wave_info.name = nullptr;
|
|
msg.persona_index = -1;
|
|
msg.multi_team = -1;
|
|
|
|
// insert ahead of the current message; the new one takes its slot
|
|
m_messages.insert(m_messages.begin() + m_cur_msg, std::move(msg));
|
|
|
|
// rebuild the listbox to match the new order
|
|
CListBox *list = (CListBox *) GetDlgItem(IDC_MESSAGE_LIST);
|
|
list->ResetContent();
|
|
for (const auto &m : m_messages)
|
|
list->AddString(m.name);
|
|
list->SetCurSel(m_cur_msg);
|
|
|
|
modified = 1;
|
|
update_cur_message();
|
|
}
|
|
|
|
void event_editor::OnDeleteMsg()
|
|
{
|
|
char buf[256];
|
|
|
|
// handle this case somewhat gracefully
|
|
Assertion((m_cur_msg >= -1) && (m_cur_msg < (int)m_messages.size()), "Unexpected m_cur_msg value (%d); expected either -1, or between 0-%d. Get a coder!\n", m_cur_msg, (int)m_messages.size() - 1);
|
|
if((m_cur_msg < 0) || (m_cur_msg >= (int)m_messages.size())){
|
|
return;
|
|
}
|
|
|
|
if (m_messages[m_cur_msg].avi_info.name){
|
|
free(m_messages[m_cur_msg].avi_info.name);
|
|
}
|
|
if (m_messages[m_cur_msg].wave_info.name){
|
|
free(m_messages[m_cur_msg].wave_info.name);
|
|
}
|
|
|
|
((CListBox *) GetDlgItem(IDC_MESSAGE_LIST))->DeleteString(m_cur_msg);
|
|
sprintf(buf, "<%s>", m_messages[m_cur_msg].name);
|
|
update_sexp_references(m_messages[m_cur_msg].name, buf, OPF_MESSAGE);
|
|
update_sexp_references(m_messages[m_cur_msg].name, buf, OPF_MESSAGE_OR_STRING);
|
|
|
|
m_messages.erase(m_messages.begin() + m_cur_msg);
|
|
|
|
if (m_cur_msg >= (int)m_messages.size()){
|
|
m_cur_msg = (int)m_messages.size() - 1;
|
|
}
|
|
|
|
GetDlgItem(IDC_NEW_MSG)->EnableWindow(TRUE);
|
|
modified = 1;
|
|
update_cur_message();
|
|
}
|
|
|
|
void event_editor::OnMsgNote()
|
|
{
|
|
// handle this case somewhat gracefully
|
|
Assertion((m_cur_msg >= -1) && (m_cur_msg < (int)m_messages.size()), "Unexpected m_cur_msg value (%d); expected either -1, or between 0-%d. Get a coder!\n", m_cur_msg, (int)m_messages.size() - 1);
|
|
if((m_cur_msg < 0) || (m_cur_msg >= (int)m_messages.size())){
|
|
return;
|
|
}
|
|
|
|
CString old_text = m_message_note;
|
|
CString new_text;
|
|
|
|
TextViewDlg dlg;
|
|
dlg.SetText(old_text);
|
|
dlg.SetCaption("Enter a note");
|
|
dlg.SetEditable(true);
|
|
dlg.DoModal();
|
|
dlg.GetText(new_text);
|
|
|
|
// comment is unchanged
|
|
if (new_text == old_text)
|
|
return;
|
|
|
|
m_message_note = new_text;
|
|
}
|
|
|
|
void event_editor::OnBrowseAvi()
|
|
{
|
|
int z;
|
|
CString name;
|
|
|
|
UpdateData(TRUE);
|
|
if (!stricmp(m_avi_filename, "<None>"))
|
|
m_avi_filename = _T("");
|
|
|
|
z = cfile_push_chdir(CF_TYPE_INTERFACE);
|
|
CFileDialog dlg(TRUE, "png", m_avi_filename, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR,
|
|
"APNG Files (*.png)|*.png|Ani Files (*.ani)|*.ani|Eff Files (*.eff)|*.eff|All Anims (*.ani, *.eff, *.png)|*.ani;*.eff;*.png|");
|
|
|
|
if (dlg.DoModal() == IDOK) {
|
|
m_avi_filename = dlg.GetFileName();
|
|
UpdateData(FALSE);
|
|
modified = 1;
|
|
}
|
|
|
|
if (!z)
|
|
cfile_pop_dir();
|
|
}
|
|
|
|
void event_editor::OnBrowseWave()
|
|
{
|
|
int z;
|
|
CString name;
|
|
|
|
audiostream_close_file(m_wave_id, 0);
|
|
m_wave_id = -1;
|
|
|
|
UpdateData(TRUE);
|
|
if (!stricmp(m_wave_filename, "<None>"))
|
|
m_wave_filename = _T("");
|
|
|
|
if (The_mission.game_type & MISSION_TYPE_TRAINING)
|
|
z = cfile_push_chdir(CF_TYPE_VOICE_TRAINING);
|
|
else
|
|
z = cfile_push_chdir(CF_TYPE_VOICE_SPECIAL);
|
|
|
|
CFileDialog dlg(TRUE, "wav", m_wave_filename, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR,
|
|
"Voice Files (*.ogg, *.wav)|*.ogg;*.wav|Ogg Vorbis Files (*.ogg)|*.ogg|Wave Files (*.wav)|*.wav||");
|
|
|
|
if (dlg.DoModal() == IDOK) {
|
|
m_wave_filename = dlg.GetFileName();
|
|
update_persona();
|
|
}
|
|
|
|
if (!z){
|
|
cfile_pop_dir();
|
|
}
|
|
}
|
|
|
|
char *event_editor::current_message_name(int i)
|
|
{
|
|
if ( (i < 0) || (i >= (int)m_messages.size()) ){
|
|
return NULL;
|
|
}
|
|
|
|
return m_messages[i].name;
|
|
}
|
|
|
|
char *event_editor::get_message_list_item(int i)
|
|
{
|
|
return m_messages[i].name;
|
|
}
|
|
|
|
void event_editor::update_persona()
|
|
{
|
|
int i, mask;
|
|
|
|
if ((m_wave_filename[0] >= '1') && (m_wave_filename[0] <= '9') && (m_wave_filename[1] == '_') ) {
|
|
i = m_wave_filename[0] - '1';
|
|
if ((i < (int)Personas.size()) && (Personas[i].flags & PERSONA_FLAG_WINGMAN)) {
|
|
m_persona = i + 1;
|
|
if ((m_persona==1) || (m_persona==2))
|
|
m_avi_filename = "HEAD-TP1";
|
|
else if ((m_persona==3) || (m_persona==4))
|
|
m_avi_filename = "HEAD-TP2";
|
|
else if ((m_persona==5))
|
|
m_avi_filename = "HEAD-TP3";
|
|
else if ((m_persona==6))
|
|
m_avi_filename = "HEAD-VP1";
|
|
}
|
|
} else {
|
|
mask = 0;
|
|
if (!strnicmp(m_wave_filename, "S_", 2)) {
|
|
mask = PERSONA_FLAG_SUPPORT;
|
|
m_avi_filename = "HEAD-CM1";
|
|
}
|
|
else if (!strnicmp(m_wave_filename, "L_", 2)) {
|
|
mask = PERSONA_FLAG_LARGE;
|
|
m_avi_filename = "HEAD-CM1";
|
|
}
|
|
else if (!strnicmp(m_wave_filename, "TC_", 3)) {
|
|
mask = PERSONA_FLAG_COMMAND;
|
|
m_avi_filename = "HEAD-CM1";
|
|
}
|
|
|
|
for (i = 0; i < (int)Personas.size(); i++)
|
|
if (Personas[i].flags & mask)
|
|
m_persona = i + 1;
|
|
}
|
|
//GetDlgItem(IDC_ANI_FILENAME)->SetWindowText(m_avi_filename);
|
|
UpdateData(FALSE);
|
|
modified = 1;
|
|
}
|
|
|
|
void event_editor::OnSelchangeWaveFilename()
|
|
{
|
|
int z;
|
|
CComboBox *box;
|
|
|
|
audiostream_close_file(m_wave_id, 0);
|
|
m_wave_id = -1;
|
|
|
|
box = (CComboBox *) GetDlgItem(IDC_WAVE_FILENAME);
|
|
z = box -> GetCurSel();
|
|
UpdateData(TRUE);
|
|
UpdateData(TRUE);
|
|
|
|
box -> GetLBText(z, m_wave_filename);
|
|
UpdateData(FALSE);
|
|
update_persona();
|
|
}
|
|
|
|
BOOL event_editor::DestroyWindow()
|
|
{
|
|
Event_editor_dlg = nullptr;
|
|
|
|
audiostream_close_file(m_wave_id, 0);
|
|
m_wave_id = -1;
|
|
|
|
if (m_play_icon) DestroyIcon(m_play_icon);
|
|
if (m_move_to_top_icon) DestroyIcon(m_move_to_top_icon);
|
|
if (m_move_up_icon) DestroyIcon(m_move_up_icon);
|
|
if (m_move_down_icon) DestroyIcon(m_move_down_icon);
|
|
if (m_move_to_bottom_icon) DestroyIcon(m_move_to_bottom_icon);
|
|
return CDialog::DestroyWindow();
|
|
}
|
|
|
|
void event_editor::OnPlay()
|
|
{
|
|
GetDlgItem(IDC_WAVE_FILENAME)->GetWindowText(m_wave_filename);
|
|
|
|
if (m_wave_id >= 0) {
|
|
audiostream_close_file(m_wave_id, 0);
|
|
m_wave_id = -1;
|
|
return;
|
|
}
|
|
|
|
// we use ASF_EVENTMUSIC here so that it will keep the extension in place
|
|
m_wave_id = audiostream_open((char *)(LPCSTR) m_wave_filename, ASF_EVENTMUSIC);
|
|
|
|
if (m_wave_id >= 0) {
|
|
audiostream_play(m_wave_id, 1.0f, 0);
|
|
}
|
|
}
|
|
|
|
void event_editor::OnUpdateStuff()
|
|
{
|
|
int z = MessageBox("This will set the head ANIs according to the FS1 personas. Do you want to proceed?\n\n(Consider using the 'Sync Personas' buttons in the Voice Acting Manager instead.)", "Update Stuff", MB_ICONQUESTION | MB_YESNO);
|
|
if (z != IDYES) {
|
|
return;
|
|
}
|
|
|
|
// GetDlgItem(IDC_WAVE_FILENAME)->GetWindowText(m_wave_filename);
|
|
UpdateData(TRUE);
|
|
update_persona();
|
|
}
|
|
|
|
// code when the "team" selection in the combo box changes
|
|
void event_editor::OnSelchangeTeam()
|
|
{
|
|
if ( cur_event < 0 ){
|
|
return;
|
|
}
|
|
|
|
UpdateData(TRUE);
|
|
|
|
// If the team isn't valid mark it as such.
|
|
if((m_team >= MAX_TVT_TEAMS) || (m_team < -1) ){
|
|
m_team = -1;
|
|
}
|
|
|
|
m_events[cur_event].team = m_team;
|
|
}
|
|
|
|
// code when the "team" selection in the combo box changes
|
|
void event_editor::OnSelchangeMessageTeam()
|
|
{
|
|
if ( m_cur_msg < 0 ){
|
|
return;
|
|
}
|
|
|
|
UpdateData(TRUE);
|
|
|
|
// If the team isn't valid mark it as such.
|
|
if((m_message_team>= MAX_TVT_TEAMS) || (m_message_team < -1) ) {
|
|
m_message_team = -1;
|
|
}
|
|
|
|
m_messages[m_cur_msg].multi_team = m_message_team;
|
|
}
|
|
|
|
// Cycles among sexp nodes with message text
|
|
void event_editor::OnDblclkMessageList()
|
|
{
|
|
CListBox *list = (CListBox*) GetDlgItem(IDC_MESSAGE_LIST);
|
|
int num_messages;
|
|
int message_nodes[MAX_SEARCH_MESSAGE_DEPTH];
|
|
|
|
// get current message index and message name
|
|
int cur_index = list->GetCurSel();
|
|
|
|
// check if message name is in event tree
|
|
char buffer[256];
|
|
list->GetText(cur_index, buffer);
|
|
|
|
|
|
num_messages = m_event_tree._model.find_text(buffer, message_nodes, MAX_SEARCH_MESSAGE_DEPTH);
|
|
|
|
if (num_messages == 0) {
|
|
char message[256];
|
|
sprintf(message, "No events using message '%s'", buffer);
|
|
MessageBox(message);
|
|
} else {
|
|
// find last message_node
|
|
if (m_last_message_node == -1) {
|
|
m_last_message_node = message_nodes[0];
|
|
} else {
|
|
|
|
if (num_messages == 1) {
|
|
// only 1 message
|
|
m_last_message_node = message_nodes[0];
|
|
} else {
|
|
// find which message and go to next message
|
|
int found_pos = -1;
|
|
for (int i=0; i<num_messages; i++) {
|
|
if (message_nodes[i] == m_last_message_node) {
|
|
found_pos = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found_pos == -1) {
|
|
// no previous message
|
|
m_last_message_node = message_nodes[0];
|
|
} else if (found_pos == num_messages-1) {
|
|
// cycle back to start
|
|
m_last_message_node = message_nodes[0];
|
|
} else {
|
|
// go to next
|
|
m_last_message_node = message_nodes[found_pos+1];
|
|
}
|
|
}
|
|
}
|
|
|
|
// highlight next
|
|
m_event_tree.hilite_item(m_last_message_node);
|
|
}
|
|
}
|
|
|
|
// Returns a unique annotation key for the given tree item.
|
|
// For regular nodes: their tree_nodes[] index (>= 0).
|
|
// For root label nodes (event names): -(formula + 2), which is always <= -2,
|
|
// avoiding collision with -1 (the default/unresolved sentinel).
|
|
static int annotation_key_for_item(const event_sexp_tree *tree, HTREEITEM h)
|
|
{
|
|
int node = tree->get_node(h);
|
|
if (node >= 0)
|
|
return node;
|
|
|
|
// Root label: encode the formula stored in item data
|
|
if (!tree->GetParentItem(h)) {
|
|
int formula = (int)tree->GetItemData(h);
|
|
if (formula >= 0)
|
|
return -(formula + 2);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void event_annotation_swap_image(event_sexp_tree *tree, HTREEITEM handle, event_annotation &ea)
|
|
{
|
|
// see if this node is a top-level event - if so, don't mess with the image
|
|
if (!tree->GetParentItem(handle))
|
|
return;
|
|
|
|
int nImage, nSelectedImage;
|
|
tree->GetItemImage(handle, nImage, nSelectedImage);
|
|
|
|
// if tree node already has the comment image, replace it with the old one
|
|
if (nImage == BITMAP_COMMENT)
|
|
{
|
|
nImage = ea.item_image;
|
|
}
|
|
// if tree has its normal image, store it and use the comment image
|
|
else
|
|
{
|
|
ea.item_image = nImage;
|
|
nImage = BITMAP_COMMENT;
|
|
}
|
|
|
|
// all tree nodes use the same image for both selected and unselected
|
|
tree->SetItemImage(handle, nImage, nImage);
|
|
}
|
|
|
|
// tooltip stuff is based on example at
|
|
// https://www.codeproject.com/articles/1761/ctreectrl-clistctrl-clistbox-with-tooltip-based-on
|
|
|
|
void event_sexp_tree::PreSubclassWindow()
|
|
{
|
|
CTreeCtrl::PreSubclassWindow();
|
|
EnableToolTips(TRUE);
|
|
}
|
|
|
|
INT_PTR event_sexp_tree::OnToolHitTest(CPoint point, TOOLINFO *pTI) const
|
|
{
|
|
RECT rect;
|
|
|
|
UINT nFlags;
|
|
HTREEITEM hitem = HitTest(point, &nFlags);
|
|
if (nFlags & TVHT_ONITEMLABEL)
|
|
{
|
|
GetItemRect(hitem, &rect, TRUE);
|
|
pTI->hwnd = m_hWnd;
|
|
pTI->uId = (UINT)hitem;
|
|
pTI->lpszText = LPSTR_TEXTCALLBACK;
|
|
pTI->rect = rect;
|
|
return pTI->uId;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
//here we supply the text for the item
|
|
BOOL event_sexp_tree::OnToolTipText(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
|
|
{
|
|
// need to handle both ANSI and UNICODE versions of the message
|
|
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
|
|
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
|
|
|
|
UINT_PTR nID = pNMHDR->idFrom;
|
|
|
|
// Do not process the message from built in tooltip
|
|
if (nID == (UINT_PTR)m_hWnd &&
|
|
((pNMHDR->code == TTN_NEEDTEXTA && pTTTA->uFlags & TTF_IDISHWND) ||
|
|
(pNMHDR->code == TTN_NEEDTEXTW && pTTTW->uFlags & TTF_IDISHWND)))
|
|
return FALSE;
|
|
|
|
// Get the mouse position
|
|
auto pMessage = GetCurrentMessage(); // get mouse pos
|
|
Assert(pMessage);
|
|
auto pt = pMessage->pt;
|
|
ScreenToClient(&pt);
|
|
|
|
UINT nFlags;
|
|
HTREEITEM h = HitTest(pt, &nFlags); //Get item pointed by mouse
|
|
|
|
if (h && m_annotations)
|
|
{
|
|
int key = annotation_key_for_item(this, h);
|
|
auto ea = m_annotations->getByKey(key);
|
|
if (ea && !ea->comment.empty())
|
|
{
|
|
m_tooltiptextA = ea->comment.c_str();
|
|
m_tooltiptextW = ea->comment.c_str();
|
|
|
|
if (pNMHDR->code == TTN_NEEDTEXTA)
|
|
{
|
|
pTTTA->lpszText = (LPSTR)(LPCSTR)m_tooltiptextA;
|
|
::SendMessage(pTTTA->hdr.hwndFrom, TTM_SETMAXTIPWIDTH, 0, 400);
|
|
}
|
|
else
|
|
{
|
|
pTTTW->lpszText = (LPWSTR)(LPCWSTR)m_tooltiptextW;
|
|
::SendMessage(pTTTW->hdr.hwndFrom, TTM_SETMAXTIPWIDTH, 0, 400);
|
|
}
|
|
}
|
|
}
|
|
|
|
*pResult = 0;
|
|
|
|
return TRUE; // message was handled
|
|
}
|
|
|
|
// color stuff is based on example at
|
|
// https://stackoverflow.com/questions/2119717/changing-the-color-of-a-selected-ctreectrl-item
|
|
|
|
void event_sexp_tree::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
static CRect rect;
|
|
static bool drawBorder = false;
|
|
|
|
NMTVCUSTOMDRAW *pcd = (NMTVCUSTOMDRAW *)pNMHDR;
|
|
switch (pcd->nmcd.dwDrawStage)
|
|
{
|
|
case CDDS_PREPAINT:
|
|
*pResult = CDRF_NOTIFYITEMDRAW;
|
|
break;
|
|
|
|
case CDDS_ITEMPREPAINT:
|
|
{
|
|
drawBorder = false;
|
|
*pResult = CDRF_DODEFAULT;
|
|
|
|
HTREEITEM hItem = (HTREEITEM)pcd->nmcd.dwItemSpec;
|
|
if (hItem && m_annotations)
|
|
{
|
|
int key = annotation_key_for_item(this, hItem);
|
|
auto ea = m_annotations->getByKey(key);
|
|
if (ea)
|
|
{
|
|
if (ea->r != 255 || ea->g != 255 || ea->b != 255)
|
|
{
|
|
// contrast color calculation taken from here:
|
|
// https://stackoverflow.com/questions/3942878/how-to-decide-font-color-in-white-or-black-depending-on-background-color
|
|
if ((ea->r*0.299 + ea->g*0.587 + ea->b*0.114) > 149)
|
|
pcd->clrText = RGB(0, 0, 0);
|
|
else
|
|
pcd->clrText = RGB(255, 255, 255);
|
|
|
|
pcd->clrTextBk = RGB(ea->r, ea->g, ea->b);
|
|
|
|
// if this is selected, save some information for drawing a border later
|
|
if (pcd->nmcd.uItemState & CDIS_SELECTED)
|
|
{
|
|
drawBorder = true;
|
|
GetItemRect((HTREEITEM)pcd->nmcd.dwItemSpec, &rect, TRUE);
|
|
// we want to get the CDDS_ITEMPOSTPAINT notification
|
|
*pResult = CDRF_NOTIFYPOSTPAINT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CDDS_ITEMPOSTPAINT:
|
|
{
|
|
// see Solution 2 here: https://www.codeproject.com/Questions/685172/CTreeCtrl-with-Item-Border
|
|
if (drawBorder)
|
|
{
|
|
SelectObject(pcd->nmcd.hdc, CreatePen(PS_INSIDEFRAME, 2, RGB(0, 0, 0)));
|
|
SelectObject(pcd->nmcd.hdc, GetStockObject(HOLLOW_BRUSH));
|
|
Rectangle(pcd->nmcd.hdc, rect.left, rect.top, rect.right, rect.bottom);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void event_sexp_tree::edit_comment(HTREEITEM h)
|
|
{
|
|
if (!m_annotations)
|
|
return;
|
|
|
|
CString old_text = _T("");
|
|
CString new_text;
|
|
|
|
int key = annotation_key_for_item(this, h);
|
|
auto* ea = m_annotations->getByKey(key);
|
|
if (ea)
|
|
old_text = (CString)ea->comment.c_str();
|
|
|
|
TextViewDlg dlg;
|
|
dlg.SetText(old_text);
|
|
dlg.SetCaption("Enter a comment");
|
|
dlg.SetEditable(true);
|
|
dlg.DoModal();
|
|
dlg.GetText(new_text);
|
|
|
|
// comment is unchanged
|
|
if (new_text == old_text)
|
|
return;
|
|
|
|
auto& ann = m_annotations->ensureByKey(key);
|
|
ann.comment = (LPCSTR)new_text;
|
|
|
|
// see if we are either adding a new comment or removing an existing comment
|
|
// if so, change the icon
|
|
if (old_text.IsEmpty() || new_text.IsEmpty())
|
|
event_annotation_swap_image(this, h, ann);
|
|
}
|
|
|
|
void event_sexp_tree::edit_bg_color(HTREEITEM h)
|
|
{
|
|
if (!m_annotations)
|
|
return;
|
|
|
|
COLORREF old_color = RGB(255, 255, 255);
|
|
COLORREF new_color;
|
|
|
|
int key = annotation_key_for_item(this, h);
|
|
auto* ea = m_annotations->getByKey(key);
|
|
if (ea)
|
|
old_color = RGB(ea->r, ea->g, ea->b);
|
|
|
|
CColorDialog dlg(old_color);
|
|
if (dlg.DoModal() != IDOK)
|
|
return;
|
|
new_color = dlg.GetColor();
|
|
|
|
// color is unchanged
|
|
if (new_color == old_color)
|
|
return;
|
|
|
|
auto& ann = m_annotations->ensureByKey(key);
|
|
ann.r = GetRValue(new_color);
|
|
ann.g = GetGValue(new_color);
|
|
ann.b = GetBValue(new_color);
|
|
|
|
// This is needed otherwise the color won't change until the user clicks something
|
|
RedrawWindow();
|
|
}
|
|
|
|
SCP_string event_sexp_tree::get_node_comment(int node_index) const
|
|
{
|
|
if (!m_annotations)
|
|
return "";
|
|
|
|
auto* ea = m_annotations->getByKey(node_index);
|
|
if (ea && !ea->comment.empty())
|
|
return ea->comment;
|
|
|
|
return "";
|
|
}
|
|
|
|
SCP_string event_sexp_tree::get_item_comment(HTREEITEM h, int /*node_index*/) const
|
|
{
|
|
// resolves both regular nodes and root labels (which have no model node,
|
|
// so the base class's node_index would be -1 for them)
|
|
return get_node_comment(annotation_key_for_item(this, h));
|
|
}
|