mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 17:52:08 -10:00
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));
|
|
}
|