Files
fs2open.github.com/fred2/voiceactingmanager.cpp
Goober5000 ef73e2572d unlimited mission titles
Some code refactoring to allow mission titles to be strings of any length.  Titles that are too long to actually display (based on string width) will be visibly truncated using `font::force_fit_string`, but the underlying title will not be affected.  Multiplayer is still subject to the 31-character length limit, so titles sent over the network will still have characters truncated.

Also removes some redundant calls to `gr_get_string_size`, since `font::force_fit_string` returns the fitted string width.
2026-05-12 11:31:20 -04:00

1251 lines
33 KiB
C++

// VoiceActingManager.cpp : implementation file
//
#include "stdafx.h"
#include <string.h>
#include "fred.h"
#include "freddoc.h"
#include "VoiceActingManager.h"
#include "globalincs/vmallocator.h"
#include "missioneditor/common.h"
#include "missionui/missioncmdbrief.h"
#include "mission/missionbriefcommon.h"
#include "mission/missionmessage.h"
#include "mission/missiongoals.h"
#include "hud/hudtarget.h"
#include "parse/sexp.h"
#include "iff_defs/iff_defs.h"
#include "mission/missiongoals.h"
#include <math.h>
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// VoiceActingManager dialog
VoiceActingManager::VoiceActingManager(CWnd* pParent /*=NULL*/)
: CDialog(VoiceActingManager::IDD, pParent)
{
//{{AFX_DATA_INIT(VoiceActingManager)
m_abbrev_briefing = _T("");
m_abbrev_campaign = _T("");
m_abbrev_command_briefing = _T("");
m_abbrev_debriefing = _T("");
m_abbrev_message = _T("");
m_abbrev_mission = _T("");
m_use_sender_in_filename = FALSE;
m_example = _T("");
m_no_replace = FALSE;
m_script_entry_format = _T("");
m_export_everything = FALSE;
m_export_command_briefings = FALSE;
m_export_briefings = FALSE;
m_export_debriefings = FALSE;
m_export_messages = FALSE;
m_group_messages = FALSE;
m_which_persona_to_sync = 0;
//}}AFX_DATA_INIT
}
void VoiceActingManager::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(VoiceActingManager)
DDX_Text(pDX, IDC_ABBREV_BRIEFING, m_abbrev_briefing);
DDX_Text(pDX, IDC_ABBREV_CAMPAIGN, m_abbrev_campaign);
DDX_Text(pDX, IDC_ABBREV_COMMAND_BRIEFING, m_abbrev_command_briefing);
DDX_Text(pDX, IDC_ABBREV_DEBRIEFING, m_abbrev_debriefing);
DDX_Text(pDX, IDC_ABBREV_MESSAGE, m_abbrev_message);
DDX_Text(pDX, IDC_ABBREV_MISSION, m_abbrev_mission);
DDX_Text(pDX, IDC_EXAMPLE, m_example);
DDX_Check(pDX, IDC_NO_REPLACE, m_no_replace);
DDX_Check(pDX, IDC_INCLUDE_SENDER, m_use_sender_in_filename);
DDX_Text(pDX, IDC_ENTRY_FORMAT, m_script_entry_format);
DDX_Check(pDX, IDC_EXPORT_EVERYTHING, m_export_everything);
DDX_Check(pDX, IDC_EXPORT_COMMAND_BRIEFINGS, m_export_command_briefings);
DDX_Check(pDX, IDC_EXPORT_BRIEFINGS, m_export_briefings);
DDX_Check(pDX, IDC_EXPORT_DEBRIEFINGS, m_export_debriefings);
DDX_Check(pDX, IDC_EXPORT_MESSAGES, m_export_messages);
DDX_Check(pDX, IDC_GROUP_MESSAGES, m_group_messages);
DDX_CBIndex(pDX, IDC_WHICH_TO_SYNC, m_which_persona_to_sync);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(VoiceActingManager, CDialog)
//{{AFX_MSG_MAP(VoiceActingManager)
ON_WM_CLOSE()
ON_EN_SETFOCUS(IDC_ABBREV_BRIEFING, OnSetfocusAbbrevBriefing)
ON_EN_SETFOCUS(IDC_ABBREV_CAMPAIGN, OnSetfocusAbbrevCampaign)
ON_EN_SETFOCUS(IDC_ABBREV_COMMAND_BRIEFING, OnSetfocusAbbrevCommandBriefing)
ON_EN_SETFOCUS(IDC_ABBREV_DEBRIEFING, OnSetfocusAbbrevDebriefing)
ON_EN_SETFOCUS(IDC_ABBREV_MESSAGE, OnSetfocusAbbrevMessage)
ON_EN_SETFOCUS(IDC_ABBREV_MISSION, OnSetfocusAbbrevMission)
ON_CBN_SETFOCUS(IDC_SUFFIX, OnSetfocusSuffix)
ON_EN_CHANGE(IDC_ABBREV_BRIEFING, OnChangeAbbrevBriefing)
ON_EN_CHANGE(IDC_ABBREV_CAMPAIGN, OnChangeAbbrevCampaign)
ON_EN_CHANGE(IDC_ABBREV_COMMAND_BRIEFING, OnChangeAbbrevCommandBriefing)
ON_EN_CHANGE(IDC_ABBREV_DEBRIEFING, OnChangeAbbrevDebriefing)
ON_EN_CHANGE(IDC_ABBREV_MESSAGE, OnChangeAbbrevMessage)
ON_EN_CHANGE(IDC_ABBREV_MISSION, OnChangeAbbrevMission)
ON_CBN_SELCHANGE(IDC_SUFFIX, OnChangeOtherSuffix)
ON_BN_CLICKED(IDC_NO_REPLACE, OnChangeNoReplace)
ON_BN_CLICKED(IDC_GENERATE_FILE_NAMES, OnGenerateFileNames)
ON_BN_CLICKED(IDC_GENERATE_SCRIPT, OnGenerateScript)
ON_BN_CLICKED(IDC_EXPORT_EVERYTHING, OnExportEverything)
ON_BN_CLICKED(IDC_EXPORT_COMMAND_BRIEFINGS, OnExportCommandBriefings)
ON_BN_CLICKED(IDC_EXPORT_BRIEFINGS, OnExportBriefings)
ON_BN_CLICKED(IDC_EXPORT_DEBRIEFINGS, OnExportDebriefings)
ON_BN_CLICKED(IDC_EXPORT_MESSAGES, OnExportMessages)
ON_BN_CLICKED(IDC_INCLUDE_SENDER, OnBnClickedIncludeSender)
ON_CBN_SELCHANGE(IDC_WHICH_TO_SYNC, OnChangeWhichToSync)
ON_BN_CLICKED(IDC_MESSAGE_PERSONAS_TO_SHIPS, OnCopyMessagePersonasToShips)
ON_BN_CLICKED(IDC_SHIP_PERSONAS_TO_MESSAGES, OnCopyShipPersonasToMessages)
ON_BN_CLICKED(IDC_SET_HEAD_ANIS_USING_MESSAGES_TBL, OnSetHeadANIsUsingMessagesTbl)
ON_BN_CLICKED(IDC_CLEAR_PERSONAS_FROM_NON_SENDERS, OnClearPersonasFromNonSenders)
ON_BN_CLICKED(IDC_CHECK_ANY_WINGMAN_PERSONAS, OnCheckAnyWingmanPersonas)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// VoiceActingManager message handlers
BOOL VoiceActingManager::OnInitDialog()
{
CComboBox *box = ((CComboBox *) GetDlgItem(IDC_SUFFIX));
box->AddString(".WAV");
box->AddString(".OGG");
box->SetCurSel(0);
// this text is too long for the .rc file, so set it here
GetDlgItem(IDC_ENTRY_FORMAT_DESC)->SetWindowText(Voice_script_instructions_string.c_str());
// load saved data for file names
m_abbrev_briefing = _T(Voice_abbrev_briefing);
m_abbrev_campaign = _T(Voice_abbrev_campaign);
m_abbrev_command_briefing = _T(Voice_abbrev_command_briefing);
m_abbrev_debriefing = _T(Voice_abbrev_debriefing);
m_abbrev_message = _T(Voice_abbrev_message);
m_abbrev_mission = _T(Voice_abbrev_mission);
m_no_replace = Voice_no_replace_filenames;
// load saved data for script
m_script_entry_format = _T(Voice_script_entry_format);
m_export_everything = m_export_command_briefings = m_export_briefings = m_export_debriefings = m_export_messages = FALSE;
if (Voice_export_selection == 1)
m_export_command_briefings = TRUE;
else if (Voice_export_selection == 2)
m_export_briefings = TRUE;
else if (Voice_export_selection == 3)
m_export_debriefings = TRUE;
else if (Voice_export_selection == 4)
m_export_messages = TRUE;
else
m_export_everything = TRUE;
m_group_messages = Voice_group_messages;
CButton *button = ((CButton *) GetDlgItem(IDC_GROUP_MESSAGES));
button->EnableWindow(m_export_everything || m_export_messages);
// populate persona dropdown
box = ((CComboBox *) GetDlgItem(IDC_WHICH_TO_SYNC));
box->AddString("<Wingman Personas>");
box->AddString("<Non-Wingman Personas>");
for (const auto& persona : Personas)
box->AddString(persona.name);
UpdateData(FALSE);
return TRUE;
}
void VoiceActingManager::OnClose()
{
UpdateData(TRUE);
// save data for file names
strcpy_s(Voice_abbrev_briefing, m_abbrev_briefing);
strcpy_s(Voice_abbrev_campaign, m_abbrev_campaign);
strcpy_s(Voice_abbrev_command_briefing, m_abbrev_command_briefing);
strcpy_s(Voice_abbrev_debriefing, m_abbrev_debriefing);
strcpy_s(Voice_abbrev_message, m_abbrev_message);
strcpy_s(Voice_abbrev_mission, m_abbrev_mission);
Voice_no_replace_filenames = (m_no_replace == TRUE) ? true : false;
// save data for script
strcpy_s(Voice_script_entry_format, m_script_entry_format);
if (m_export_command_briefings)
Voice_export_selection = 1;
else if (m_export_briefings)
Voice_export_selection = 2;
else if (m_export_debriefings)
Voice_export_selection = 3;
else if (m_export_messages)
Voice_export_selection = 4;
else
Voice_export_selection = 0;
Voice_group_messages = (m_group_messages == TRUE) ? true : false;
CDialog::OnClose();
}
CString VoiceActingManager::get_suffix()
{
// assign suffix
if (((CComboBox *) GetDlgItem(IDC_SUFFIX))->GetCurSel() == 1)
return ".ogg";
else
return ".wav";
}
int VoiceActingManager::calc_digits(int size)
{
if (size >= 10000)
return 5;
else if (size >= 1000) // I hope we never hit this!
return 4;
else if (size >= 100)
return 3;
else
return 2;
}
void VoiceActingManager::build_example()
{
// pick a default
if (m_abbrev_command_briefing != "")
build_example(m_abbrev_command_briefing);
else if (m_abbrev_briefing != "")
build_example(m_abbrev_briefing);
else if (m_abbrev_debriefing != "")
build_example(m_abbrev_debriefing);
else if (m_abbrev_message != "")
build_example(m_abbrev_message);
else
build_example("");
}
void VoiceActingManager::build_example(CString section)
{
if (section == "")
{
m_example = "";
return;
}
m_example = generate_filename(section, 1, 2, INVALID_MESSAGE);
}
CString VoiceActingManager::generate_filename(CString section, int number, int digits, const MMessage *message)
{
if (section == "")
return "none.wav";
int i;
CString str = "";
CString num;
// build prefix
if (m_abbrev_campaign != "")
str = str + m_abbrev_campaign;
if (m_abbrev_mission != "")
str = str + m_abbrev_mission;
str = str + section;
// build number
num.Format("%d", number);;
digits -= num.GetLength();
for (i = 0; i < digits; i++)
{
num = "0" + num;
}
str = str + num;
const CString suffix = get_suffix();
// append sender name if supposed to and I have been passed a message
// to get the sender from
if ( message != NULL && m_use_sender_in_filename ) {
size_t allow_to_copy = NAME_LENGTH - suffix.GetLength() - str.GetLength();
char sender[NAME_LENGTH];
if ( message == INVALID_MESSAGE ) {
strcpy_s(sender, "Alpha 1");
} else {
get_valid_sender(sender, sizeof(sender), message);
}
// truncate sender to that we don't overflow filename
sender[allow_to_copy] = '\0';
size_t j;
for( j = 0; sender[j] != '\0'; j++ ) {
// lower case letter
sender[j] = SCP_tolower(sender[j]);
// replace any non alpha numeric with a underscore
if ( !isalnum( sender[j] ) )
sender[j] = '_';
}
// flatten multiple underscores
j = 1;
while( sender[j] != '\0' ) {
if ( sender[j-1] == '_' && sender[j] == '_' ) {
size_t k;
for (k = j + 1; sender[k] != '\0'; k++ )
sender[k - 1] = sender[k];
sender[k - 1] = '\0';
} else {
// only increment on rounds when I am not moving the string down
j++;
}
}
str = str + sender;
}
// suffix
str = str + get_suffix();
Assert( str.GetLength() < NAME_LENGTH );
return str;
}
#define STRCPY_IF_MODIFIED(dest, src) { auto temp = src; if (stricmp(dest, temp)) { strcpy(dest, temp); num_modified++; } }
#define COPY_IF_MODIFIED(dest, src) { auto temp = src; if (dest != temp) { dest = temp; num_modified++; } }
#define STRDUP_IF_MODIFIED(dest, src) { auto temp = src; if (dest == nullptr || stricmp(dest, temp)) { if (dest != nullptr) { free(dest); } dest = strdup(temp); num_modified++; } }
void VoiceActingManager::OnGenerateFileNames()
{
int i;
int digits;
size_t num_modified = 0;
// stuff data to variables
UpdateData(TRUE);
// command briefings
digits = calc_digits(Cmd_briefs[0].num_stages);
for (i = 0; i < Cmd_briefs[0].num_stages; i++)
{
char *filename = Cmd_briefs[0].stage[i].wave_filename;
// generate only if we're replacing or if it has a replaceable name
if (!m_no_replace || !strlen(filename) || message_filename_is_generic(filename))
STRCPY_IF_MODIFIED(filename, generate_filename(m_abbrev_command_briefing, i + 1, digits));
}
// briefings
digits = calc_digits(Briefings[0].num_stages);
for (i = 0; i < Briefings[0].num_stages; i++)
{
char *filename = Briefings[0].stages[i].voice;
// generate only if we're replacing or if it has a replaceable name
if (!m_no_replace || !strlen(filename) || message_filename_is_generic(filename))
STRCPY_IF_MODIFIED(filename, generate_filename(m_abbrev_briefing, i + 1, digits));
}
// debriefings
digits = calc_digits(Debriefings[0].num_stages);
for (i = 0; i < Debriefings[0].num_stages; i++)
{
char *filename = Debriefings[0].stages[i].voice;
// generate only if we're replacing or if it has a replaceable name
if (!m_no_replace || !strlen(filename) || message_filename_is_generic(filename))
STRCPY_IF_MODIFIED(filename, generate_filename(m_abbrev_debriefing, i + 1, digits));
}
// messages
digits = calc_digits(Num_messages - Num_builtin_messages);
for (i = 0; i < Num_messages - Num_builtin_messages; i++)
{
MMessage *message = &Messages[i + Num_builtin_messages];
const char *filename = message->wave_info.name;
// generate only if we're replacing or if it has a replaceable name
if (!m_no_replace || !strlen(filename) || message_filename_is_generic(filename))
STRDUP_IF_MODIFIED(message->wave_info.name, generate_filename(m_abbrev_message, i + 1, digits, message));
}
if ( num_modified > 0 ) {
// Tell FRED that we actually modified something
set_modified(TRUE);
}
// notify user that we are done and how many filenames were changed
SCP_string message;
sprintf(message, "File name generation complete. " SIZE_T_ARG " file %s modified.", num_modified, (num_modified == 1) ? "name was" : "names were");
MessageBox(message.c_str(), "Voice Acting Manager");
}
void VoiceActingManager::OnGenerateScript()
{
char pathname[256];
// stuff data to variables
UpdateData(TRUE);
// prompt to save script
CFileDialog dlg(FALSE, "txt", NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "Text files (*.txt)|*.txt||");
if (dlg.DoModal() != IDOK)
return;
CString dlgPathName = dlg.GetPathName( );
string_copy(pathname, dlgPathName, 256 - 1);
fp = cfopen(pathname, "wt");
if (!fp)
{
MessageBox("Can't open file to save.", "Error!");
return;
}
fout("%s\n", Mission_filename);
fout("%s\n\n", The_mission.name.c_str());
if (m_export_everything || m_export_command_briefings)
{
fout("\n\nCommand Briefings\n-----------------\n\n");
for (int i = 0; i < Cmd_briefs[0].num_stages; i++)
{
CString entry = m_script_entry_format;
entry.Replace("\r\n", "\n");
cmd_brief_stage *stage = &Cmd_briefs[0].stage[i];
entry.Replace("$filename", stage->wave_filename);
entry.Replace("$message", stage->text.c_str());
entry.Replace("$persona", "<no persona specified>");
entry.Replace("$sender", "<no sender specified>");
entry.Replace("$name", "<no name specified>");
entry.Replace("$note", "<no note specified>");
fout("%s\n\n\n", (char *) (LPCTSTR) entry);
}
}
if (m_export_everything || m_export_briefings)
{
fout("\n\nBriefings\n---------\n\n");
for (int i = 0; i < Briefings[0].num_stages; i++)
{
CString entry = m_script_entry_format;
entry.Replace("\r\n", "\n");
brief_stage *stage = &Briefings[0].stages[i];
entry.Replace("$filename", stage->voice);
entry.Replace("$message", stage->text.c_str());
entry.Replace("$persona", "<no persona specified>");
entry.Replace("$sender", "<no sender specified>");
entry.Replace("$name", "<no name specified>");
entry.Replace("$note", "<no note specified>");
fout("%s\n\n\n", (char *) (LPCTSTR) entry);
}
}
if (m_export_everything || m_export_debriefings)
{
fout("\n\nDebriefings\n-----------\n\n");
for (int i = 0; i < Debriefings[0].num_stages; i++)
{
CString entry = m_script_entry_format;
entry.Replace("\r\n", "\n");
debrief_stage *stage = &Debriefings[0].stages[i];
entry.Replace("$filename", stage->voice);
entry.Replace("$message", stage->text.c_str());
entry.Replace("$persona", "<no persona specified>");
entry.Replace("$sender", "<no sender specified>");
entry.Replace("$name", "<no name specified>");
entry.Replace("$note", "<no note specified>");
fout("%s\n\n\n", (char *) (LPCTSTR) entry);
}
}
if (m_export_everything || m_export_messages)
{
fout("\n\nMessages\n--------\n\n");
if (m_group_messages)
{
SCP_vector<int> message_indexes;
for (int i = 0; i < Num_messages - Num_builtin_messages; i++)
message_indexes.push_back(i + Num_builtin_messages);
group_message_indexes(message_indexes);
for (size_t index = 0; index < message_indexes.size(); index++)
{
MMessage *message = &Messages[message_indexes[index]];
export_one_message(message);
}
}
else
{
for (int i = 0; i < Num_messages - Num_builtin_messages; i++)
{
MMessage *message = &Messages[i + Num_builtin_messages];
export_one_message(message);
}
}
}
cfclose(fp);
// notify
MessageBox("Script generation complete.", "Woohoo!");
}
void VoiceActingManager::export_one_message(const MMessage *message)
{
CString entry = m_script_entry_format;
entry.Replace("\r\n", "\n");
entry.Replace("$name", message->name);
// replace file name
entry.Replace("$filename", message->wave_info.name);
// replace message
entry.Replace("$message", message->message);
entry.Replace("$note", message->note.c_str());
// determine and replace persona
if (message->persona_index >= 0)
entry.Replace("$persona", Personas[message->persona_index].name);
else
entry.Replace("$persona", "<none>");
// determine sender
char sender[NAME_LENGTH+1];
get_valid_sender(sender, sizeof(sender), message);
// replace sender (but print #Command as Command)
if (*sender == '#')
entry.Replace("$sender", &sender[1]);
else
entry.Replace("$sender", sender);
fout("%s\n\n\n", (char *) (LPCTSTR) entry);
}
/** Passed sender string will have either have the senders name
or '\<none\>'*/
void VoiceActingManager::get_valid_sender(char *sender, size_t sender_size, const MMessage *message, int *sender_shipnum, bool *is_command)
{
Assert( sender != NULL );
Assert( message != NULL );
memset(sender, 0, sender_size);
strncpy(sender, get_message_sender(message), sender_size - 1);
if (!strcmp("#Command", sender))
{
if (is_command)
*is_command = true;
// check if we're overriding #Command
if (The_mission.flags[Mission::Mission_Flags::Override_hashcommand])
{
memset(sender, 0, sender_size);
strncpy(sender, The_mission.command_sender, sender_size - 1);
}
}
else
{
if (is_command)
*is_command = false;
}
// strip hash if present
if ( sender[0] == '#' )
{
size_t i = 1;
for(; sender[i] != '\0'; i++ ) {
sender[i-1] = sender[i];
}
sender[i-1] = '\0';
}
int shipnum = ship_name_lookup(sender, 1); // The player's ship is valid for this search.
if (sender_shipnum != nullptr)
*sender_shipnum = shipnum;
if (shipnum >= 0)
{
ship *shipp = &Ships[shipnum];
// we may have to use the callsign
if (*Fred_callsigns[shipnum])
{
hud_stuff_ship_callsign(sender, shipp);
}
// account for hidden ship names
else if ( ((Iff_info[shipp->team].flags & IFFF_WING_NAME_HIDDEN) && (shipp->wingnum != -1)) || (shipp->flags[Ship::Ship_Flags::Hide_ship_name]) )
{
hud_stuff_ship_class(sender, shipp);
}
// use the regular sender display name
else
{
memset(sender, 0, sender_size);
strncpy(sender, shipp->get_display_name(), sender_size - 1);
}
}
}
void VoiceActingManager::OnSetfocusAbbrevBriefing()
{
UpdateData(TRUE);
build_example(m_abbrev_briefing);
UpdateData(FALSE);
}
void VoiceActingManager::OnSetfocusAbbrevCampaign()
{
UpdateData(TRUE);
build_example();
UpdateData(FALSE);
}
void VoiceActingManager::OnSetfocusAbbrevCommandBriefing()
{
UpdateData(TRUE);
build_example(m_abbrev_command_briefing);
UpdateData(FALSE);
}
void VoiceActingManager::OnSetfocusAbbrevDebriefing()
{
UpdateData(TRUE);
build_example(m_abbrev_debriefing);
UpdateData(FALSE);
}
void VoiceActingManager::OnSetfocusAbbrevMessage()
{
UpdateData(TRUE);
build_example(m_abbrev_message);
UpdateData(FALSE);
}
void VoiceActingManager::OnSetfocusAbbrevMission()
{
UpdateData(TRUE);
build_example();
UpdateData(FALSE);
}
void VoiceActingManager::OnSetfocusSuffix()
{
UpdateData(TRUE);
build_example();
UpdateData(FALSE);
}
void VoiceActingManager::OnChangeAbbrevBriefing()
{
UpdateData(TRUE);
build_example(m_abbrev_briefing);
UpdateData(FALSE);
}
void VoiceActingManager::OnChangeAbbrevCampaign()
{
UpdateData(TRUE);
build_example();
UpdateData(FALSE);
}
void VoiceActingManager::OnChangeAbbrevCommandBriefing()
{
UpdateData(TRUE);
build_example(m_abbrev_command_briefing);
UpdateData(FALSE);
}
void VoiceActingManager::OnChangeAbbrevDebriefing()
{
UpdateData(TRUE);
build_example(m_abbrev_debriefing);
UpdateData(FALSE);
}
void VoiceActingManager::OnChangeAbbrevMessage()
{
UpdateData(TRUE);
build_example(m_abbrev_message);
UpdateData(FALSE);
}
void VoiceActingManager::OnChangeAbbrevMission()
{
UpdateData(TRUE);
build_example();
UpdateData(FALSE);
}
void VoiceActingManager::OnChangeOtherSuffix()
{
UpdateData(TRUE);
build_example();
UpdateData(FALSE);
}
void VoiceActingManager::OnChangeNoReplace()
{
UpdateData(TRUE);
}
int VoiceActingManager::fout(const char *format, ...)
{
SCP_string str;
va_list args;
va_start(args, format);
vsprintf(str, format, args);
va_end(args);
cfputs(str.c_str(), fp);
return 0;
}
// Loops through all the sexps and finds the sender of the specified message. This assumes there is only one possible
// sender of the message, which is probably nearly always true (especially for voice-acted missions).
const char *VoiceActingManager::get_message_sender(const MMessage *message)
{
int i;
for (i = 0; i < Num_sexp_nodes; i++)
{
if (Sexp_nodes[i].type == SEXP_NOT_USED)
continue;
// stuff
int op = get_operator_const(i);
int n = CDR(i);
// find the message sexps
if (op == OP_SEND_MESSAGE)
{
// the first argument is the sender; the third is the message
if (!strcmp(message->name, Sexp_nodes[CDDR(n)].text))
return Sexp_nodes[n].text;
}
else if (op == OP_SEND_MESSAGE_LIST || op == OP_SEND_MESSAGE_CHAIN)
{
// skip the event argument
if (op == OP_SEND_MESSAGE_CHAIN)
n = CDR(n);
// check the argument list
while (n != -1)
{
// as before
if (!strcmp(message->name, Sexp_nodes[CDDR(n)].text))
return Sexp_nodes[n].text;
// iterate along the list
n = CDDDDR(n);
}
}
else if (op == OP_SEND_RANDOM_MESSAGE)
{
// as before, sort of
char *sender = Sexp_nodes[n].text;
// check the argument list
n = CDDR(n);
while (n != -1)
{
if (!strcmp(message->name, Sexp_nodes[n].text))
return sender;
// iterate along the list
n = CDR(n);
}
}
else if (op == OP_TRAINING_MSG)
{
// just check the message
if (!strcmp(message->name, Sexp_nodes[n].text))
return "Training Message";
}
}
return "<none>";
}
void VoiceActingManager::group_message_indexes(SCP_vector<int> &message_indexes)
{
#ifndef NDEBUG
size_t initial_size = message_indexes.size();
#endif
SCP_vector<int> temp_message_indexes = message_indexes;
message_indexes.clear();
// add all messages found in send-message-list or send-random-message node trees
for (const auto &event: Mission_events)
group_message_indexes_in_tree(event.formula, temp_message_indexes, message_indexes);
// add remaining messages
for (size_t index = 0; index < temp_message_indexes.size(); index++)
message_indexes.push_back(temp_message_indexes[index]);
#ifndef NDEBUG
if (initial_size > message_indexes.size())
{
Warning(LOCATION, "Initial size is greater than size after sorting!");
}
else if (initial_size < message_indexes.size())
{
Warning(LOCATION, "Initial size is less than size after sorting!");
}
#endif
}
void VoiceActingManager::group_message_indexes_in_tree(int node, SCP_vector<int> &source_list, SCP_vector<int> &destination_list)
{
int op, n;
if (node < 0)
return;
if (Sexp_nodes[node].type == SEXP_NOT_USED)
return;
// stuff
op = get_operator_const(node);
n = CDR(node);
if (op == OP_SEND_MESSAGE_LIST || op == OP_SEND_MESSAGE_CHAIN)
{
// skip the event argument
if (op == OP_SEND_MESSAGE_CHAIN)
n = CDR(n);
// check the argument list
while (n != -1)
{
// the third argument is a message
char *message_name = Sexp_nodes[CDDR(n)].text;
// check source messages
for (size_t i = 0; i < source_list.size(); i++)
{
if (!strcmp(message_name, Messages[source_list[i]].name))
{
// move it from source to destination
destination_list.push_back(source_list[i]);
source_list.erase(source_list.begin() + i);
break;
}
}
// iterate along the list
n = CDDDDR(n);
}
}
else if (op == OP_SEND_RANDOM_MESSAGE)
{
// check the argument list
n = CDDR(n);
while (n != -1)
{
// each argument from this point on is a message
char *message_name = Sexp_nodes[n].text;
// check source messages
for (size_t i = 0; i < source_list.size(); i++)
{
if (!strcmp(message_name, Messages[source_list[i]].name))
{
// move it from source to destination
destination_list.push_back(source_list[i]);
source_list.erase(source_list.begin() + i);
break;
}
}
// iterate along the list
n = CDR(n);
}
}
// iterate on first element
group_message_indexes_in_tree(CAR(node), source_list, destination_list);
// iterate on rest of elements
group_message_indexes_in_tree(CDR(node), source_list, destination_list);
}
void VoiceActingManager::OnExportEverything()
{
CButton *button = ((CButton *) GetDlgItem(IDC_GROUP_MESSAGES));
button->EnableWindow(TRUE);
}
void VoiceActingManager::OnExportCommandBriefings()
{
CButton *button = ((CButton *) GetDlgItem(IDC_GROUP_MESSAGES));
button->EnableWindow(FALSE);
}
void VoiceActingManager::OnExportBriefings()
{
CButton *button = ((CButton *) GetDlgItem(IDC_GROUP_MESSAGES));
button->EnableWindow(FALSE);
}
void VoiceActingManager::OnExportDebriefings()
{
CButton *button = ((CButton *) GetDlgItem(IDC_GROUP_MESSAGES));
button->EnableWindow(FALSE);
}
void VoiceActingManager::OnExportMessages()
{
CButton *button = ((CButton *) GetDlgItem(IDC_GROUP_MESSAGES));
button->EnableWindow(TRUE);
}
void VoiceActingManager::OnBnClickedIncludeSender()
{
UpdateData(TRUE);
build_example();
UpdateData(FALSE);
}
void VoiceActingManager::OnChangeWhichToSync()
{
UpdateData(TRUE);
}
bool VoiceActingManager::check_persona(int persona)
{
Assertion(SCP_vector_inbounds(Personas, persona), "The persona index provided to check_persona() is not in range!");
if (m_which_persona_to_sync == static_cast<int>(PersonaSyncIndex::Wingman))
{
return (Personas[persona].flags & PERSONA_FLAG_WINGMAN) != 0;
}
else if (m_which_persona_to_sync == static_cast<int>(PersonaSyncIndex::NonWingman))
{
return (Personas[persona].flags & PERSONA_FLAG_WINGMAN) == 0;
}
else
{
int real_persona_to_sync = m_which_persona_to_sync - static_cast<int>(PersonaSyncIndex::PersonasStart);
Assertion(SCP_vector_inbounds(Personas, real_persona_to_sync), "The m_which_persona_to_sync dropdown index is not in range!");
return real_persona_to_sync == persona;
}
}
void VoiceActingManager::OnCopyPersonas(bool messages_to_ships)
{
char sender_buf[NAME_LENGTH];
int sender_shipnum;
bool is_command;
size_t num_modified = 0;
SCP_unordered_set<int> already_assigned;
SCP_string inconsistent_copy_msg;
// go through all messages in the mission
for (int i = 0; i < Num_messages - Num_builtin_messages; i++)
{
auto message = &Messages[i + Num_builtin_messages];
// find whoever sent this message
get_valid_sender(sender_buf, NAME_LENGTH, message, &sender_shipnum, &is_command);
auto sender_shipp = (sender_shipnum < 0) ? nullptr : &Ships[sender_shipnum];
// NOTE: It is possible for a ship that is behaving as a "command source" to have a different persona than the mission's "command persona".
// If this is so, any messages sent by #Command will have the mission's "command persona", and any messages sent by the ship will have the ship's persona.
int persona_to_copy;
if (messages_to_ships)
persona_to_copy = message->persona_index;
else if (is_command)
persona_to_copy = The_mission.command_persona;
else if (sender_shipp)
persona_to_copy = sender_shipp->persona_index;
else
persona_to_copy = -1;
// don't copy None, and only copy the personas we want
if (persona_to_copy >= 0 && check_persona(persona_to_copy))
{
if (messages_to_ships && sender_shipp)
{
if (already_assigned.count(sender_shipnum) > 0 && sender_shipp->persona_index != persona_to_copy)
{
inconsistent_copy_msg += "\n\u2022 ";
inconsistent_copy_msg += sender_shipp->ship_name;
}
already_assigned.insert(sender_shipnum);
COPY_IF_MODIFIED(sender_shipp->persona_index, persona_to_copy);
}
else if (!messages_to_ships)
{
if (already_assigned.count(i) > 0 && message->persona_index != persona_to_copy)
{
inconsistent_copy_msg += "\n\u2022 ";
inconsistent_copy_msg += message->name;
}
already_assigned.insert(i);
COPY_IF_MODIFIED(message->persona_index, persona_to_copy);
}
}
}
if (num_modified > 0)
set_modified(TRUE);
SCP_string message;
if (messages_to_ships)
sprintf(message, "Personas have been copied from messages to ships. " SIZE_T_ARG " %s modified.", num_modified, (num_modified == 1) ? "ship was" : "ships were");
else
sprintf(message, "Personas have been copied from ships to messages. " SIZE_T_ARG " %s modified.", num_modified, (num_modified == 1) ? "message was" : "messages were");
if (!inconsistent_copy_msg.empty())
{
if (messages_to_ships)
message += "\n\nThe following ships send messages with inconsistent personas. You may want to review them.\n";
else
message += "\n\nThe following messages are sent by ships with inconsistent personas. You may want to review them.\n";
message += inconsistent_copy_msg;
}
MessageBox(message.c_str(), "Voice Acting Manager");
}
void VoiceActingManager::OnClearPersonasFromNonSenders()
{
SCP_unordered_set<int> all_senders;
char sender_buf[NAME_LENGTH];
int sender_shipnum;
size_t num_modified = 0;
// go through all messages in the mission
for (int i = 0; i < Num_messages - Num_builtin_messages; i++)
{
auto message = &Messages[i + Num_builtin_messages];
// find whoever sent this message
get_valid_sender(sender_buf, NAME_LENGTH, message, &sender_shipnum);
// if it's a ship, save the shipnum
if (sender_shipnum >= 0)
all_senders.insert(sender_shipnum);
}
// go through all ships in the mission
for (auto objp : list_range(&obj_used_list))
{
if ((objp->type == OBJ_START) || (objp->type == OBJ_SHIP))
{
// for ships that aren't message senders
if (all_senders.count(objp->instance) == 0)
{
// only for the personas we want
if ((Ships[objp->instance].persona_index >= 0) && check_persona(Ships[objp->instance].persona_index))
{
// clear the persona
COPY_IF_MODIFIED(Ships[objp->instance].persona_index, -1);
}
}
}
}
if (num_modified > 0)
set_modified(TRUE);
SCP_string message;
sprintf(message, "Personas have been cleared from all ships that do not send messages. " SIZE_T_ARG " %s modified.", num_modified, (num_modified == 1) ? "ship was" : "ships were");
MessageBox(message.c_str(), "Voice Acting Manager");
}
void VoiceActingManager::OnCopyMessagePersonasToShips()
{
OnCopyPersonas(true);
}
void VoiceActingManager::OnCopyShipPersonasToMessages()
{
OnCopyPersonas(false);
}
void VoiceActingManager::OnSetHeadANIsUsingMessagesTbl()
{
size_t num_modified = 0;
// go through all messages in the mission
for (int i = 0; i < Num_messages - Num_builtin_messages; i++)
{
auto message = &Messages[i + Num_builtin_messages];
// only messages with personas
if (message->persona_index < 0)
continue;
// only the personas we want
if (!check_persona(message->persona_index))
continue;
// find the corresponding head for this persona
bool found = false;
for (int j = 0; j < Num_builtin_messages; j++)
{
auto builtin_message = &Messages[j];
if (message->persona_index == builtin_message->persona_index)
{
// either assign the correct head from scratch, or change the head to the correct one
STRDUP_IF_MODIFIED(message->avi_info.name, builtin_message->avi_info.name);
// done searching
found = true;
break;
}
}
if (!found)
{
Warning(LOCATION, "Persona index %d was not found in list of messages.tbl personas!", message->persona_index);
}
}
if (num_modified > 0)
set_modified(TRUE);
SCP_string message;
sprintf(message, "Message head ANIs have been assigned from builtin messages. " SIZE_T_ARG " %s modified.", num_modified, (num_modified == 1) ? "message was" : "messages were");
MessageBox(message.c_str(), "Voice Acting Manager");
}
void VoiceActingManager::OnCheckAnyWingmanPersonas()
{
char sender_buf[NAME_LENGTH];
SCP_string output_msg;
bool any_any_wingman = false;
int issue_count = 0;
// go through all messages in the mission
for (int i = 0; i < Num_messages - Num_builtin_messages; i++)
{
auto message = &Messages[i + Num_builtin_messages];
// find whoever sent this message
get_valid_sender(sender_buf, NAME_LENGTH, message);
// only check <any wingman>
if (stricmp(sender_buf, "<any wingman>") != 0)
continue;
any_any_wingman = true;
// check the message itself
if (message->persona_index < 0)
{
issue_count++;
output_msg += "\n\"";
output_msg += message->name;
output_msg += "\" - does not have a persona";
continue;
}
if (!(Personas[message->persona_index].flags & PERSONA_FLAG_WINGMAN))
{
issue_count++;
output_msg += "\n\"";
output_msg += message->name;
output_msg += "\" - does not have a wingman persona";
continue;
}
bool found_potential_sender = false;
// go through all ships in the mission
for (auto objp : list_range(&obj_used_list))
{
if ((objp->type == OBJ_START) || (objp->type == OBJ_SHIP))
{
if (Ships[objp->instance].persona_index == message->persona_index)
{
found_potential_sender = true;
break;
}
}
}
if (!found_potential_sender)
{
issue_count++;
output_msg += "\n\"";
output_msg += message->name;
output_msg += "\" - no ship with persona \"";
output_msg += Personas[message->persona_index].name;
output_msg += "\" was found";
}
}
if (!output_msg.empty())
{
if (issue_count == 1)
output_msg = "The following issue was found for messages sent by <any wingman>:\n" + output_msg;
else
output_msg = "The following issues were found for messages sent by <any wingman>:\n" + output_msg;
MessageBox(output_msg.c_str(), "Voice Acting Manager");
}
else if (!any_any_wingman)
MessageBox("All messages have been checked. There are no messages sent by <any wingman>.", "Voice Acting Manager");
else
MessageBox("All messages have been checked. All messages sent by <any wingman> have at least one candidate sender.", "Voice Acting Manager");
}