Sound engine enhancements (#1141)

* Finalize features

* Add descriptions

* Amplify loudness output

* fix compile error

* Add subtitle parser for voice track

* Update bass.lib

* Return nil instead of empty string if no subtitle is found

* Allow to use newlines in subtitles

* Additionally try to load subtitles from /subtitles subdirectory

* Don't stop ambience when Lara dies

* Add option for turning subtitles on or off

* Update TombEngine.vcxproj

* Parse newlines correctly in subtitles

* Add millisecond constant

* Align menu

* Minor formatting; remove newlines preventing tooltips

---------

Co-authored-by: Kubsy <kubadd475@gmail.com>
Co-authored-by: Sezz <sezzary@outlook.com>
This commit is contained in:
Lwmte 2023-06-17 14:02:51 +03:00 committed by GitHub
parent 35304ce84a
commit 95ff7091a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 2511 additions and 1603 deletions

View file

@ -4,6 +4,7 @@ Version 1.1.0
* Fix enemies shooting Lara through static meshes and moveables.
* Fix skeletons and mummies not affected by explosive weapons.
* Fix crash on loading if static meshes with IDs above maximum are present.
* Fix random crashes when playing audio tracks with names longer than 15 symbols.
* Fix sprint value going below zero.
* Fix fog bulb density formula.
* Fix electricity effect crashing 64-bit version of the engine.
@ -12,6 +13,8 @@ Version 1.1.0
* Fix default ambience overlapping current one when loading a savegame.
* Fix doppelganger being limited to be in a single room.
* Add multiple doppelgangers by using the same OCB for the origin nullmesh and doppelganger.
* Implement separate audio track channel for playing voiceovers with subtitles.
* Don't stop ambience when Lara dies.
* Pause all sounds when entering inventory or pause menu.
* Improve deflection against slopes.
* Move and rotate Lara together with dynamic bridge objects.
@ -19,6 +22,12 @@ Version 1.1.0
* Add TR1 skateboard kid.
* Add TR1 Kold.
Lua API changes:
* Add soundtrack functions:
- Misc::GetAudioTrackLoudness() for getting current loudness of a given track type.
- Misc::IsAudioTrackPlaying() for checking if a given track type is playing.
- Misc::GetCurrentSubtitle() for getting current subtitle string for the voice track.
Version 1.0.9
=============

Binary file not shown.

657
Libs/srtparser/srtparser.h Normal file
View file

@ -0,0 +1,657 @@
/*
* Author : Saurabh Shrivastava
* Email : saurabh.shrivastava54@gmail.com
* Link : https://github.com/saurabhshri
*
* Based on subtitle-parser by Oleksii Maryshchenko.
* Email : young_developer@mail.ru
* Link : https://github.com/young-developer/subtitle-parser
*/
#ifndef SRTPARSER_H
#define SRTPARSER_H
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <iterator>
//function for splitting sentences based on supplied delimiter
inline std::vector<std::string> &split(const std::string &s, char delim, std::vector<std::string> &elems) {
std::stringstream ss(s);
std::string item;
while (getline(ss, item, delim)) {
elems.push_back(item);
}
return elems;
}
/**** Class definitions ****/
class SubtitleWord
{
private:
std::string _text;
public:
SubtitleWord(void);
SubtitleWord(std::string text);
virtual std::string getText() const;
~SubtitleWord(void);
};
class SubtitleItem
{
private:
long int _startTime; //in milliseconds
long int _endTime;
std::string _text; //actual line, as present in subtitle file
long int timeMSec(std::string value); //converts time string into ms
int _subNo; //subtitle number
std::string _startTimeString; //time as in srt format
std::string _endTimeString;
bool _ignore; //should subtitle be ignore; used when the subtitle is empty after processing
std::string _justDialogue; //contains processed subtitle - stripped style, non dialogue text removal etc.
int _speakerCount; //count of number of speakers
std::vector<std::string> _speaker; //list of speakers in a single subtitle
int _nonDialogueCount; //count of non spoken words in a subtitle
std::vector<std::string> _nonDialogue; //list of non dialogue words, e.g. (applause)
int _wordCount; //number of words in _justDialogue
std::vector<std::string> _word; //list of words in dialogue
std::vector<long int> _wordStartTime; //start time of each word in dialogue
std::vector<long int> _wordEndTime; //end time of each word in dialogue
std::vector<long int> _wordDuration; //actual duration of each word without silence
int _styleTagCount; //count of style tags in a single subtitle
std::vector<std::string> _styleTag; //list of style tags in that subtitle
void extractInfo(bool keepHTML = 0, bool doNotIgnoreNonDialogues = 0, bool doNotRemoveSpeakerNames = 0); //process subtitle
public:
long int getStartTime() const; //returns starting time in ms
long int getEndTime() const; //returns ending time in ms
std::string getText() const; //returns subtitle text as present in .srt file
int getSubNo() const; //returns subtitle number
std::string getStartTimeString() const; //returns sarting time as present in .srt file
std::string getEndTimeString() const; //returns ending time as present in .srt file
bool getIgnoreStatus() const; //returns status, whether the subtitle is ignorable or not after processing
std::string getDialogue(bool keepHTML = 0, bool doNotIgnoreNonDialogues = 0, bool doNotRemoveSpeakerNames = 0); //returns processed subtitle
int getSpeakerCount() const; //return speaker count
int getNonDialogueCount() const; //return non dialogue words count
int getStyleTagCount() const; //return style tags count
int getWordCount() const; //return words count
std::vector<std::string> getIndividualWords(); //return string vector of individual words
std::string getWordByIndex(int index); //return word stored at 'index'
std::vector<long int> getWordStartTimes(); //return long int vector of start time of individual words
std::vector<long int> getWordEndTimes(); //return long int vector of end time of individual words
long int getWordStartTimeByIndex(int index); //return the start time of a word based on index
long int getWordEndTimeByIndex (int index); //return the end time of a word based on index
std::vector<std::string> getSpeakerNames(); //return string vector of speaker names
std::vector<std::string> getNonDialogueWords(); //return string vector of non dialogue words
std::vector<std::string> getStyleTags(); //return string vector of style tags
void setStartTime(long int startTime); //set starting time
void setEndTime(long int endTime); //set ending time
void setText(std::string text); //set subtitle text
void setWordTimes(std::vector<long int> wordStartTime, std::vector<long int> wordEndTime, std::vector<long int> wordDuration); //assign time to individual words
SubtitleItem(void);
SubtitleItem(int subNo, std::string startTime,std::string endTime, std::string text, bool ignore = false,
std::string justDialogue = "" , int speakerCount = 0, int nonDialogueCount = 0,
int styleTagCount = 0, int wordCount = 0, std::vector<std::string> speaker = std::vector<std::string>(),
std::vector<std::string> nonDialogue = std::vector<std::string>(),
std::vector<std::string> styleTags = std::vector<std::string>(),
std::vector<std::string> word = std::vector<std::string>()); //default constructor
~SubtitleItem(void);
};
class SubtitleParser
{
protected:
std::vector<SubtitleItem*> _subtitles; //stores subtitles
std::string _fileName; //supplied filename
virtual void parse(std::string fileName) = 0;
public:
virtual std::vector<SubtitleItem*> getSubtitles(); //returns subtitles
std::string getFileData();
SubtitleParser(void);
virtual ~SubtitleParser(void);
};
class SubtitleParserFactory
{
private:
std::string _fileName;
public:
SubtitleParser* getParser();
SubtitleParserFactory(std::string fileName);
~SubtitleParserFactory(void);
};
class SubRipParser : public SubtitleParser
{
void parse(std::string fileName);
public:
SubRipParser(void);
SubRipParser(std::string fileName);
~SubRipParser(void);
};
/**** Function definitions ****/
//1. SubtitleParserFactory class
inline SubtitleParserFactory::SubtitleParserFactory(std::string fileName)
{
_fileName = fileName;
}
inline SubtitleParser* SubtitleParserFactory::getParser()
{
return new SubRipParser(_fileName); //creates and returns SubRipParser obj
}
inline SubtitleParserFactory::~SubtitleParserFactory(void)
{
}
//2. SubtitleParser class
inline std::vector<SubtitleItem*> SubtitleParser::getSubtitles()
{
return _subtitles;
}
inline std::string SubtitleParser::getFileData() //returns whole read file i.e. contents of input.srt
{
std::ifstream infile(_fileName);
std::string allData = "";
std::string line;
while (std::getline(infile, line))
{
std::istringstream iss(line);
allData += line + "\n";
}
return allData;
}
inline SubtitleParser::SubtitleParser(void)
{
}
inline SubtitleParser::~SubtitleParser(void)
{
}
//3. SubRipParser class
inline SubRipParser::SubRipParser(void)
{
}
inline void SubRipParser::parse(std::string fileName) //srt parser
{
std::ifstream infile(fileName);
std::string line, start, end, completeLine = "", timeLine = "";
int subNo, turn = 0;
/*
* turn = 0 -> Add subtitle number
* turn = 1 -> Add string to timeLine
* turn > 1 -> Add string to completeLine
*/
while (std::getline(infile, line))
{
line.erase(remove(line.begin(), line.end(), '\r'), line.end());
if (line.compare(""))
{
if(!turn)
{
subNo=atoi(line.c_str());
turn++;
continue;
}
if (line.find("-->") != std::string::npos)
{
timeLine += line;
std::vector<std::string> srtTime;
srtTime = split(timeLine, ' ', srtTime);
start = srtTime[0];
end = srtTime[2];
}
else
{
if (completeLine != "")
completeLine += "\n";
completeLine += line;
}
turn++;
}
else
{
turn = 0;
_subtitles.push_back(new SubtitleItem(subNo,start,end,completeLine));
completeLine = timeLine = "";
}
if(infile.eof()) //insert last remaining subtitle
{
_subtitles.push_back(new SubtitleItem(subNo,start,end,completeLine));
}
}
}
inline SubRipParser::SubRipParser(std::string fileName)
{
_fileName = fileName;
parse(fileName);
}
inline SubRipParser::~SubRipParser(void)
{
for(int i=0;i != _subtitles.size();++i)
{
if(_subtitles[i])
delete _subtitles[i];
}
}
//4. SubtitleItem class
inline SubtitleItem::SubtitleItem(void)
{
}
inline SubtitleItem::SubtitleItem(int subNo, std::string startTime,std::string endTime, std::string text, bool ignore,
std::string justDialogue, int speakerCount, int nonDialogueCount,
int styleTagCount, int wordCount, std::vector<std::string> speaker, std::vector<std::string> nonDialogue,
std::vector<std::string> styleTags, std::vector<std::string> word)
{
_startTime = timeMSec(startTime);
_endTime = timeMSec(endTime);
_text = text;
_subNo = subNo;
_startTimeString = startTime;
_endTimeString = endTime;
_ignore = ignore;
_justDialogue = justDialogue;
_speakerCount = speakerCount;
_nonDialogueCount = nonDialogueCount;
_wordCount = wordCount;
_speaker = speaker;
_styleTagCount = styleTagCount;
_styleTag = styleTags;
_nonDialogue = nonDialogue;
_word = word;
extractInfo();
}
inline long int SubtitleItem::timeMSec(std::string value)
{
std::vector<std::string> t, secs;
int hours, mins, seconds, milliseconds;
t = split(value, ':', t);
hours = atoi(t[0].c_str());
mins = atoi(t[1].c_str());
secs = split(t[2], ',', secs);
seconds = atoi(secs[0].c_str());
milliseconds = atoi(secs[1].c_str());
return hours * 3600000 + mins * 60000 + seconds * 1000 + milliseconds;
}
inline long int SubtitleItem::getStartTime() const
{
return _startTime;
}
inline long int SubtitleItem::getEndTime() const
{
return _endTime;
}
inline std::string SubtitleItem::getText() const
{
return _text;
}
inline void SubtitleItem::setStartTime(long int startTime)
{
_startTime = startTime;
}
inline void SubtitleItem::setEndTime(long int endTime)
{
_endTime = endTime;
}
inline void SubtitleItem::setText(std::string text)
{
_text = text;
}
inline void SubtitleItem::setWordTimes(std::vector<long int> wordStartTime, std::vector<long int> wordEndTime, std::vector<long int> wordDuration)
{
_wordStartTime = wordStartTime;
_wordEndTime = wordEndTime;
_wordDuration = wordDuration;
}
inline int SubtitleItem::getSubNo() const
{
return _subNo;
}
inline std::string SubtitleItem::getStartTimeString() const
{
return _startTimeString;
}
inline std::string SubtitleItem::getEndTimeString() const
{
return _endTimeString;
}
inline bool SubtitleItem::getIgnoreStatus() const
{
if(_ignore)
return true;
else
return false;
}
inline void SubtitleItem::extractInfo(bool keepHTML, bool doNotIgnoreNonDialogues, bool doNotRemoveSpeakerNames) //process subtitle
{
std::string output = _text;
//stripping HTML tags
if(!keepHTML)
{
/*
* TODO : Before erasing, extract the words.
* std::vector<std::string> getStyleTags();
* int getStyleTagCount() const;
* std::vector<std::string> _styleTag;
* int _styleTagCount;
*/
int countP = 0;
for(char& c : output) // replacing <...> with ~~~~
{
if(c=='<')
{
countP++;
c = '~';
}
else
{
if(countP!=0)
{
if(c != '>')
c = '~';
else if(c == '>')
{
c = '~';
countP--;
}
}
}
}
}
//stripping non dialogue data e.g. (applause)
if(!doNotIgnoreNonDialogues)
{
/*
* TODO : Before erasing, extract the words.
* std::vector<std::string> getNonDialogueWords();
* int getNonDialogueCount() const;
* std::vector<std::string> _nonDialogue;
* int _nonDialogueCount;
*/
int countP = 0;
for(char& c : output) // replacing (...) with ~~~~
{
if(c=='(')
{
countP++;
c = '~';
}
else
{
if(countP!=0)
{
if(c != ')')
c = '~';
else if(c == ')')
{
c = '~';
countP--;
}
}
}
}
}
output.erase(std::remove(output.begin(), output.end(), '~'), output.end()); // deleting all ~
//Extracting speaker names
if(!doNotRemoveSpeakerNames)
{
for(int i=0; output[i]!='\0';i++)
{
int colonIndex = 0, nameBeginIndex = 0;
if(output[i]==':') //speaker found; travel back
{
_speakerCount++;
colonIndex = i;
int tempIndex = 0, foundEvilColon = 0, continueFlag = 0, spaceBeforeColon = 0;
if(output[i-1] == ' ')
spaceBeforeColon = 2;
/*
Possible Cases :
Elon Musk: Hey Saurabh, you are pretty smart. // First and Last Name
Saurabh: *_* What? Elon Musk: Yes! // Two names in single line
Saurabh : OMG OMG! // Space before colon
Elon: LOL World: LAMAO
Saurabh: ._. // normal
*/
for(int j=i - spaceBeforeColon; j>=0;j--)
{
if(output[j] == '.' || output[j] == '!' || output[j] == ',' || output[j] == '?' || output[j] == '\n'
|| output[j] == ' ' || j== 0)
{
if(output[j] == '.' || output[j] == '!' || output[j] == ',' || output[j] == '?' || j == 0)
{
if((continueFlag && j == 0))
{
if(!isupper(output[j]))
{
nameBeginIndex = tempIndex;
break;
}
else
tempIndex = j;
}
else if(j!=0)
tempIndex = j + 1;
}
else if(output[j] == ' ' && isupper(output[j+1]))
{
tempIndex = j;
continueFlag = 1;
continue;
}
else if(output[j] == ' ' && !isupper(output[j+1] && tempIndex == 0))
{
_speakerCount--;
foundEvilColon = 1;
break;
}
nameBeginIndex = tempIndex;
break;
}
}
if(foundEvilColon)
continue;
i = nameBeginIndex; //compensating the removal and changes in index
//check if there's a space after colon i.e. A: Hello vs A:Hello
int removeSpace = 0;
if(output[colonIndex + 1]==' ')
removeSpace = 1;
_speaker.push_back(output.substr(nameBeginIndex, colonIndex - nameBeginIndex));
output.erase(nameBeginIndex, colonIndex - nameBeginIndex + removeSpace);
}
}
}
// removing more than one whitespaces with one space
unique_copy (output.begin(), output.end(), std::back_insert_iterator<std::string>(_justDialogue),
[](char a,char b)
{
return isspace(a) && isspace(b);
});
// trimming whitespaces
const char* whiteSpaces = " \t\n\r\f\v";
_justDialogue.erase(0, _justDialogue.find_first_not_of(whiteSpaces));
_justDialogue.erase(_justDialogue.find_last_not_of(whiteSpaces) + 1);
if(_justDialogue.empty() || _justDialogue == " ")
_ignore = true;
else
{
_word = split(_justDialogue, ' ', _word); //extracting individual words
_wordCount = (int)_word.size();
}
}
inline std::string SubtitleItem::getDialogue(bool keepHTML, bool doNotIgnoreNonDialogues, bool doNotRemoveSpeakerNames)
{
if(_justDialogue.empty())
extractInfo(keepHTML, doNotIgnoreNonDialogues, doNotRemoveSpeakerNames);
return _justDialogue;
}
inline int SubtitleItem::getSpeakerCount() const
{
return _speakerCount;
}
inline int SubtitleItem::getNonDialogueCount() const
{
return _nonDialogueCount;
}
inline int SubtitleItem::getStyleTagCount() const
{
return _styleTagCount;
}
inline int SubtitleItem::getWordCount() const
{
return _wordCount;
}
inline std::vector<std::string> SubtitleItem::getSpeakerNames()
{
return _speaker;
}
inline std::vector<std::string> SubtitleItem::getNonDialogueWords()
{
return _nonDialogue;
}
inline std::vector<std::string> SubtitleItem::getIndividualWords()
{
return _word;
}
inline std::string SubtitleItem::getWordByIndex(int index)
{
return _word[index];
}
inline std::vector<long int> SubtitleItem::getWordStartTimes()
{
return _wordStartTime;
}
inline std::vector<long int> SubtitleItem::getWordEndTimes()
{
return _wordEndTime;
}
inline long int SubtitleItem::getWordStartTimeByIndex(int index)
{
return _wordStartTime[index];
}
inline long int SubtitleItem::getWordEndTimeByIndex(int index)
{
return _wordEndTime[index];
}
inline std::vector<std::string> SubtitleItem::getStyleTags()
{
return _styleTag;
}
inline SubtitleItem::~SubtitleItem(void)
{
}
//5. SubtitleWordclass
inline SubtitleWord::SubtitleWord(void)
{
_text = "";
}
inline SubtitleWord::SubtitleWord(std::string text)
{
_text = text;
}
inline std::string SubtitleWord::getText() const
{
return _text;
}
inline SubtitleWord::~SubtitleWord(void)
{
}
#endif //SRTPARSER_H

View file

@ -90,6 +90,7 @@ local strings =
rocket_launcher_ammo = { "Rocket Launcher Ammo" },
rocket_launcher = { "Rocket Launcher" },
rumble = { "Rumble" },
subtitles = { "Subtitles" },
save_game = { "Save Game" },
savegame_timestamp = { "%02d Days %02d:%02d:%02d" },
screen_resolution = { "Screen Resolution" },

View file

@ -716,7 +716,7 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
item->HitPoints = -1;
if (lara->Control.Count.Death == 0)
StopSoundTracks();
StopSoundTracks(true);
lara->Control.Count.Death++;
if ((item->Flags & IFLAG_INVISIBLE))

View file

@ -726,6 +726,7 @@ namespace TEN::Gui
Reverb,
MusicVolume,
SfxVolume,
Subtitles,
AutoTarget,
ToggleRumble,
ThumbstickCameraControl,
@ -733,7 +734,7 @@ namespace TEN::Gui
Cancel
};
static const int numOtherSettingsOptions = 7;
static const int numOtherSettingsOptions = 8;
OptionCount = numOtherSettingsOptions;
@ -772,6 +773,11 @@ namespace TEN::Gui
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
CurrentSettings.Configuration.EnableThumbstickCameraControl = !CurrentSettings.Configuration.EnableThumbstickCameraControl;
break;
case OtherSettingsOption::Subtitles:
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
CurrentSettings.Configuration.EnableSubtitles = !CurrentSettings.Configuration.EnableSubtitles;
break;
}
}

View file

@ -806,8 +806,10 @@ bool SaveGame::Save(int slot)
// Soundtrack playheads
auto bgmTrackData = GetSoundTrackNameAndPosition(SoundTrackType::BGM);
auto oneshotTrackData = GetSoundTrackNameAndPosition(SoundTrackType::OneShot);
auto voiceTrackData = GetSoundTrackNameAndPosition(SoundTrackType::Voice);
auto bgmTrackOffset = fbb.CreateString(bgmTrackData.first);
auto oneshotTrackOffset = fbb.CreateString(oneshotTrackData.first);
auto voiceTrackOffset = fbb.CreateString(voiceTrackData.first);
// Legacy soundtrack map
std::vector<int> soundTrackMap;
@ -1272,6 +1274,8 @@ bool SaveGame::Save(int slot)
sgb.add_ambient_position(bgmTrackData.second);
sgb.add_oneshot_track(oneshotTrackOffset);
sgb.add_oneshot_position(oneshotTrackData.second);
sgb.add_voice_track(voiceTrackOffset);
sgb.add_voice_position(voiceTrackData.second);
sgb.add_cd_flags(soundtrackMapOffset);
sgb.add_action_queue(actionQueueOffset);
sgb.add_flip_maps(flipMapsOffset);
@ -1456,6 +1460,7 @@ bool SaveGame::Load(int slot)
// Restore soundtracks
PlaySoundTrack(s->ambient_track()->str(), SoundTrackType::BGM, s->ambient_position());
PlaySoundTrack(s->oneshot_track()->str(), SoundTrackType::OneShot, s->oneshot_position());
PlaySoundTrack(s->voice_track()->str(), SoundTrackType::Voice, s->voice_position());
// Legacy soundtrack map
for (int i = 0; i < s->cd_flags()->size(); i++)

View file

@ -42,7 +42,7 @@ namespace TEN::Renderer
// Vertical menu positioning templates
constexpr auto MenuVerticalTop = 11;
constexpr auto MenuVerticalDisplaySettings = 200;
constexpr auto MenuVerticalOtherSettings = 150;
constexpr auto MenuVerticalOtherSettings = 130;
constexpr auto MenuVerticalBottomCenter = 400;
constexpr auto MenuVerticalStatisticsTitle = 150;
constexpr auto MenuVerticalOptionsTitle = 350;
@ -219,29 +219,32 @@ namespace TEN::Renderer
DrawBar(g_Gui.GetCurrentSettings().Configuration.SfxVolume / 100.0f, *g_SFXVolumeBar, ID_SFX_BAR_TEXTURE, 0, false);
GetNextBlockPosition(&y);
// Subtitles
AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_SUBTITLES), PRINTSTRING_COLOR_ORANGE, SF(title_option == 3));
AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableSubtitles), PRINTSTRING_COLOR_WHITE, SF(title_option == 3));
GetNextLinePosition(&y);
// Auto targeting
AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_AUTO_TARGET), PRINTSTRING_COLOR_ORANGE, SF(title_option == 3));
AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.AutoTarget), PRINTSTRING_COLOR_WHITE, SF(title_option == 3));
AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_AUTO_TARGET), PRINTSTRING_COLOR_ORANGE, SF(title_option == 4));
AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.AutoTarget), PRINTSTRING_COLOR_WHITE, SF(title_option == 4));
GetNextLinePosition(&y);
// Vibration
AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_RUMBLE), PRINTSTRING_COLOR_ORANGE, SF(title_option == 4));
AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableRumble), PRINTSTRING_COLOR_WHITE, SF(title_option == 4));
AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_RUMBLE), PRINTSTRING_COLOR_ORANGE, SF(title_option == 5));
AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableRumble), PRINTSTRING_COLOR_WHITE, SF(title_option == 5));
GetNextLinePosition(&y);
// Thumbstick camera
AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_THUMBSTICK_CAMERA), PRINTSTRING_COLOR_ORANGE, SF(title_option == 5));
AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableThumbstickCameraControl), PRINTSTRING_COLOR_WHITE, SF(title_option == 5));
AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_THUMBSTICK_CAMERA), PRINTSTRING_COLOR_ORANGE, SF(title_option == 6));
AddString(MenuRightSideEntry, y, Str_Enabled(g_Gui.GetCurrentSettings().Configuration.EnableThumbstickCameraControl), PRINTSTRING_COLOR_WHITE, SF(title_option == 6));
GetNextBlockPosition(&y);
// Apply
AddString(MenuCenterEntry, y, g_GameFlow->GetString(STRING_APPLY), PRINTSTRING_COLOR_ORANGE, SF_Center(title_option == 6));
AddString(MenuCenterEntry, y, g_GameFlow->GetString(STRING_APPLY), PRINTSTRING_COLOR_ORANGE, SF_Center(title_option == 7));
GetNextLinePosition(&y);
// Cancel
AddString(MenuCenterEntry, y, g_GameFlow->GetString(STRING_CANCEL), PRINTSTRING_COLOR_ORANGE, SF_Center(title_option == 7));
AddString(MenuCenterEntry, y, g_GameFlow->GetString(STRING_CANCEL), PRINTSTRING_COLOR_ORANGE, SF_Center(title_option == 8));
break;
case Menu::Controls:

View file

@ -81,6 +81,7 @@
#define STRING_AUTO_TARGET "auto_target"
#define STRING_RUMBLE "rumble"
#define STRING_THUMBSTICK_CAMERA "thumbstick_camera"
#define STRING_SUBTITLES "subtitles"
#define STRING_CONTROLS_MOVE_FORWARD "controls_move_forward"
#define STRING_CONTROLS_MOVE_BACKWARD "controls_move_backward"
#define STRING_CONTROLS_MOVE_LEFT "controls_move_left"

View file

@ -196,8 +196,11 @@ static constexpr char ScriptReserved_SetAmbientTrack[] = "SetAmbientTrack";
static constexpr char ScriptReserved_PlayAudioTrack[] = "PlayAudioTrack";
static constexpr char ScriptReserved_StopAudioTrack[] = "StopAudioTrack";
static constexpr char ScriptReserved_StopAudioTracks[] = "StopAudioTracks";
static constexpr char ScriptReserved_GetAudioTrackLoudness[] = "GetAudioTrackLoudness";
static constexpr char ScriptReserved_GetCurrentSubtitle[] = "GetCurrentSubtitle";
static constexpr char ScriptReserved_PlaySound[] = "PlaySound";
static constexpr char ScriptReserved_IsSoundPlaying[] = "IsSoundPlaying";
static constexpr char ScriptReserved_IsAudioTrackPlaying[] = "IsAudioTrackPlaying";
static constexpr char ScriptReserved_GiveInvItem[] = "GiveItem";
static constexpr char ScriptReserved_TakeInvItem[] = "TakeItem";
static constexpr char ScriptReserved_GetInvItemCount[] = "GetItemCount";
@ -264,6 +267,7 @@ static constexpr char ScriptReserved_BlendID[] = "BlendID";
static constexpr char ScriptReserved_EffectID[] = "EffectID";
static constexpr char ScriptReserved_ActionID[] = "ActionID";
static constexpr char ScriptReserved_CameraType[] = "CameraType";
static constexpr char ScriptReserved_SoundTrackType[] = "SoundTrackType";
static constexpr char ScriptReserved_LogLevel[] = "LogLevel";
static constexpr char ScriptReserved_RoomFlagID[] = "RoomFlagID";
static constexpr char ScriptReserved_RoomReverb[] = "RoomReverb";

View file

@ -27,10 +27,10 @@ The following constants are inside CameraType.
static const std::unordered_map<std::string, CameraType> CAMERA_TYPE
{
{ "Chase", CameraType::Chase },
{ "Fixed", CameraType::Fixed },
{ "Look", CameraType::Look },
{ "Combat", CameraType::Combat },
{ "Heavy", CameraType::Heavy },
{ "Object", CameraType::Object }
{ "CHASE", CameraType::Chase },
{ "FIXED", CameraType::Fixed },
{ "LOOK", CameraType::Look },
{ "COMBAT", CameraType::Combat },
{ "HEAVY", CameraType::Heavy },
{ "OBJECT", CameraType::Object }
};

View file

@ -17,6 +17,7 @@
#include "Scripting/Internal/TEN/Misc/ActionIDs.h"
#include "Scripting/Internal/TEN/Misc/CameraTypes.h"
#include "Scripting/Internal/TEN/Misc/LevelLog.h"
#include "Scripting/Internal/TEN/Misc/SoundTrackTypes.h"
#include "Scripting/Internal/TEN/Vec3/Vec3.h"
#include "Sound/sound.h"
#include "Specific/clock.h"
@ -144,11 +145,11 @@ namespace Misc
/// Play an audio track
//@function PlayAudioTrack
//@tparam string name of track (without file extension) to play
//@tparam bool loop if true, the track will loop; if false, it won't (default: false)
static void PlayAudioTrack(const std::string& trackName, TypeOrNil<bool> looped)
//@tparam Misc.SoundTrackType type of the audio track to play
static void PlayAudioTrack(const std::string& trackName, TypeOrNil<SoundTrackType> mode)
{
auto mode = USE_IF_HAVE(bool, looped, false) ? SoundTrackType::BGM : SoundTrackType::OneShot;
PlaySoundTrack(trackName, mode);
auto playMode = USE_IF_HAVE(SoundTrackType, mode, SoundTrackType::OneShot);
PlaySoundTrack(trackName, playMode);
}
///Set and play an ambient track
@ -168,11 +169,40 @@ namespace Misc
///Stop audio track that is currently playing
//@function StopAudioTrack
//@tparam bool looped if set, stop looped audio track, if not, stop one-shot audio track
static void StopAudioTrack(TypeOrNil<bool> looped)
//@tparam Misc.SoundTrackType type of the audio track
static void StopAudioTrack(TypeOrNil<SoundTrackType> mode)
{
auto mode = USE_IF_HAVE(bool, looped, false) ? SoundTrackType::BGM : SoundTrackType::OneShot;
StopSoundTrack(mode, SOUND_XFADETIME_ONESHOT);
auto playMode = USE_IF_HAVE(SoundTrackType, mode, SoundTrackType::OneShot);
StopSoundTrack(playMode, SOUND_XFADETIME_ONESHOT);
}
///Get current loudness level for specified track type
//@function GetAudioTrackLoudness
//@tparam Misc.SoundTrackType type of the audio track
//@treturn float current loudness of a specified audio track
static float GetAudioTrackLoudness(TypeOrNil<SoundTrackType> mode)
{
auto playMode = USE_IF_HAVE(SoundTrackType, mode, SoundTrackType::OneShot);
return GetSoundTrackLoudness(playMode);
}
///Get current subtitle string for a voice track currently playing.
//Subtitle file must be in .srt format, have same filename as voice track, and be placed in same directory as voice track.
//Returns nil if no voice track is playing or no subtitle present.
//@function GetCurrentSubtitle
//@treturn string current subtitle string
static TypeOrNil<std::string> GetCurrentVoiceTrackSubtitle()
{
auto& result = GetCurrentSubtitle();
if (result.has_value())
{
return result.value();
}
else
{
return sol::nil;
}
}
/// Play sound effect
@ -189,7 +219,15 @@ namespace Misc
//@tparam int Sound ID to check. Corresponds to the value in the sound XML file or Tomb Editor's "Sound Infos" window.
static bool IsSoundPlaying(int effectID)
{
return IsSoundEffectPlaying(effectID);
return (Sound_EffectIsPlaying(effectID, nullptr) != SOUND_NO_CHANNEL);
}
/// Check if the audio track is playing
//@function IsAudioTrackPlaying
//@tparam string Track filename to check. Should be without extension and without full directory path.
static bool IsAudioTrackPlaying(const std::string& trackName)
{
return Sound_TrackIsPlaying(trackName);
}
static bool CheckInput(int actionIndex)
@ -382,9 +420,12 @@ namespace Misc
tableMisc.set_function(ScriptReserved_PlayAudioTrack, &PlayAudioTrack);
tableMisc.set_function(ScriptReserved_StopAudioTrack, &StopAudioTrack);
tableMisc.set_function(ScriptReserved_StopAudioTracks, &StopAudioTracks);
tableMisc.set_function(ScriptReserved_GetAudioTrackLoudness, &GetAudioTrackLoudness);
tableMisc.set_function(ScriptReserved_GetCurrentSubtitle, &GetCurrentVoiceTrackSubtitle);
tableMisc.set_function(ScriptReserved_PlaySound, &PlaySoundEffect);
tableMisc.set_function(ScriptReserved_IsSoundPlaying, &IsSoundPlaying);
tableMisc.set_function(ScriptReserved_IsAudioTrackPlaying, &IsAudioTrackPlaying);
/// Check if particular action key is held
//@function KeyIsHeld
@ -421,6 +462,7 @@ namespace Misc
LuaHandler handler{ state };
handler.MakeReadOnlyTable(tableMisc, ScriptReserved_ActionID, ACTION_IDS);
handler.MakeReadOnlyTable(tableMisc, ScriptReserved_CameraType, CAMERA_TYPE);
handler.MakeReadOnlyTable(tableMisc, ScriptReserved_SoundTrackType, SOUNDTRACK_TYPE);
handler.MakeReadOnlyTable(tableMisc, ScriptReserved_LogLevel, LOG_LEVEL);
}
}

View file

@ -0,0 +1,30 @@
#pragma once
#include "Sound/sound.h"
/***
Constants for the type of the audio tracks.
@enum Misc.SoundTrackType
@pragma nostrip
*/
/*** Misc.SoundTrackType constants.
The following constants are inside SoundTrackType.
ONESHOT
LOOPED
VOICE
@section Misc.SoundTrackType
*/
/*** Table of sound track type constants (for use with sound track functions).
@table CONSTANT_STRING_HERE
*/
static const std::unordered_map<std::string, SoundTrackType> SOUNDTRACK_TYPE
{
{ "ONESHOT", SoundTrackType::OneShot },
{ "LOOPED", SoundTrackType::BGM },
{ "VOICE", SoundTrackType::Voice }
};

View file

@ -3,6 +3,7 @@
#include <filesystem>
#include <regex>
#include <srtparser.h>
#include "Game/camera.h"
#include "Game/collision/collide_room.h"
@ -11,6 +12,7 @@
#include "Game/Setup.h"
#include "Specific/configuration.h"
#include "Specific/level.h"
#include "Specific/trutils.h"
#include "Specific/winmain.h"
HSAMPLE BASS_SamplePointer[SOUND_MAX_SAMPLES];
@ -35,7 +37,9 @@ static std::string FullAudioDirectory;
std::map<std::string, int> SoundTrackMap;
std::unordered_map<int, SoundTrackInfo> SoundTracks;
int SecretSoundIndex = 5;
std::vector<SubtitleItem*> Subtitles;
static int SecretSoundIndex = 5;
constexpr int LegacyLoopingTrackMin = 98;
constexpr int LegacyLoopingTrackMax = 111;
@ -231,17 +235,17 @@ bool SoundEffect(int effectID, Pose* position, SoundEnvironment condition, float
break;
case SoundPlayMode::Wait:
if (existingChannel != -1) // Don't play until stopped
if (existingChannel != SOUND_NO_CHANNEL) // Don't play until stopped
return false;
break;
case SoundPlayMode::Restart:
if (existingChannel != -1) // Stop existing and continue
if (existingChannel != SOUND_NO_CHANNEL) // Stop existing and continue
Sound_FreeSlot(existingChannel, SOUND_XFADETIME_CUTSOUND);
break;
case SoundPlayMode::Looped:
if (existingChannel != -1) // Just update parameters and return, if already playing
if (existingChannel != SOUND_NO_CHANNEL) // Just update parameters and return, if already playing
{
Sound_UpdateEffectPosition(existingChannel, position);
Sound_UpdateEffectAttributes(existingChannel, pitch, volume);
@ -261,7 +265,7 @@ bool SoundEffect(int effectID, Pose* position, SoundEnvironment condition, float
// Get free channel to play sample
int freeSlot = Sound_GetFreeSlot();
if (freeSlot == -1)
if (freeSlot == SOUND_NO_CHANNEL)
{
TENLog("No free channel slot available!", LogLevel::Warning);
return false;
@ -312,17 +316,18 @@ void PauseAllSounds(SoundPauseMode mode)
return;
}
for (auto& slot : SoundSlot)
for (const auto& slot : SoundSlot)
{
if ((slot.Channel != NULL) && (BASS_ChannelIsActive(slot.Channel) == BASS_ACTIVE_PLAYING))
BASS_ChannelPause(slot.Channel);
}
if (mode == SoundPauseMode::Inventory)
return;
for (auto& slot : SoundtrackSlot)
for (int i = 0; i < (int)SoundTrackType::Count; i++)
{
if (mode == SoundPauseMode::Inventory && (SoundTrackType)i != SoundTrackType::Voice)
continue;
const auto& slot = SoundtrackSlot[i];
if ((slot.Channel != NULL) && (BASS_ChannelIsActive(slot.Channel) == BASS_ACTIVE_PLAYING))
BASS_ChannelPause(slot.Channel);
}
@ -333,7 +338,7 @@ void ResumeAllSounds(SoundPauseMode mode)
if (mode == SoundPauseMode::Global)
BASS_Start();
for (auto& slot : SoundtrackSlot)
for (const auto& slot : SoundtrackSlot)
{
if ((slot.Channel != NULL) && (BASS_ChannelIsActive(slot.Channel) == BASS_ACTIVE_PAUSED))
BASS_ChannelStart(slot.Channel);
@ -342,7 +347,7 @@ void ResumeAllSounds(SoundPauseMode mode)
if (mode == SoundPauseMode::Global)
return;
for (auto& slot : SoundSlot)
for (const auto& slot : SoundSlot)
{
if ((slot.Channel != NULL) && (BASS_ChannelIsActive(slot.Channel) == BASS_ACTIVE_PAUSED))
BASS_ChannelStart(slot.Channel);
@ -418,7 +423,45 @@ void EnumerateLegacyTracks()
}
void PlaySoundTrack(std::string track, SoundTrackType mode, QWORD position)
float GetSoundTrackLoudness(SoundTrackType mode)
{
float result = 0.0f;
if (!g_Configuration.EnableSound)
return result;
if (!BASS_ChannelIsActive(SoundtrackSlot[(int)mode].Channel))
return result;
BASS_ChannelGetLevelEx(SoundtrackSlot[(int)mode].Channel, &result, 0.1f, BASS_LEVEL_MONO | BASS_LEVEL_RMS);
return std::clamp(result * 2.0f, 0.0f, 1.0f);
}
std::optional<std::string> GetCurrentSubtitle()
{
if (!g_Configuration.EnableSound || !g_Configuration.EnableSubtitles)
return std::nullopt;
auto channel = SoundtrackSlot[(int)SoundTrackType::Voice].Channel;
if (!BASS_ChannelIsActive(channel))
return std::nullopt;
if (Subtitles.empty())
return std::nullopt;
long time = long(BASS_ChannelBytes2Seconds(channel, BASS_ChannelGetPosition(channel, BASS_POS_BYTE)) * SOUND_MILLISECONDS_IN_SECOND);
for (auto* stringPtr : Subtitles)
{
if (time >= stringPtr->getStartTime() && time <= stringPtr->getEndTime())
return stringPtr->getText();
}
return std::nullopt;
}
void PlaySoundTrack(const std::string& track, SoundTrackType mode, QWORD position)
{
if (!g_Configuration.EnableSound)
return;
@ -443,6 +486,7 @@ void PlaySoundTrack(std::string track, SoundTrackType mode, QWORD position)
switch (mode)
{
case SoundTrackType::OneShot:
case SoundTrackType::Voice:
crossfadeTime = SOUND_XFADETIME_ONESHOT;
break;
@ -518,9 +562,34 @@ void PlaySoundTrack(std::string track, SoundTrackType mode, QWORD position)
SoundtrackSlot[(int)mode].Channel = stream;
SoundtrackSlot[(int)mode].Track = track;
// Additionally attempt to load subtitle file, if exists.
if (mode == SoundTrackType::Voice)
LoadSubtitles(track);
}
void PlaySoundTrack(std::string track, short mask)
void LoadSubtitles(const std::string& name)
{
Subtitles.clear();
auto subtitleName = FullAudioDirectory + name + ".srt";
if (!std::filesystem::is_regular_file(subtitleName))
subtitleName = FullAudioDirectory + "/subtitles/" + name + ".srt";
if (!std::filesystem::is_regular_file(subtitleName))
return;
auto factory = new SubtitleParserFactory(subtitleName);
auto parser = factory->getParser();
Subtitles = parser->getSubtitles();
delete factory;
for (auto& sub : Subtitles)
sub->setText(ReplaceNewLineSymbols(sub->getText()));
}
void PlaySoundTrack(const std::string& track, short mask)
{
// If track name was included in script, play it as registered track and take mask into account.
// Otherwise, play it once without registering anywhere.
@ -559,10 +628,16 @@ void PlaySoundTrack(int index, short mask)
PlaySoundTrack(SoundTracks[index].Name, SoundTracks[index].Mode);
}
void StopSoundTracks()
void StopSoundTracks(bool excludeAmbience)
{
StopSoundTrack(SoundTrackType::OneShot, SOUND_XFADETIME_ONESHOT);
StopSoundTrack(SoundTrackType::BGM, SOUND_XFADETIME_ONESHOT);
for (int i = 0; i < (int)SoundTrackType::Count; i++)
{
auto mode = (SoundTrackType)i;
if (excludeAmbience && mode == SoundTrackType::BGM)
continue;
StopSoundTrack((SoundTrackType)i, SOUND_XFADETIME_ONESHOT);
}
}
void StopSoundTrack(SoundTrackType mode, int fadeoutTime)
@ -581,7 +656,6 @@ void ClearSoundTrackMasks()
// Returns specified soundtrack type's stem name and playhead position.
// To be used with savegames. To restore soundtrack, use PlaySoundtrack function with playhead position passed as 3rd argument.
std::pair<std::string, QWORD> GetSoundTrackNameAndPosition(SoundTrackType type)
{
auto track = SoundtrackSlot[(int)type];
@ -610,7 +684,6 @@ void Sound_FreeSample(int index)
// Get first free (non-playing) sound slot.
// If no free slots found, now try to hijack slot which is as far from listener as possible
int Sound_GetFreeSlot()
{
for (int i = 0; i < SOUND_MAX_CHANNELS; i++)
@ -622,7 +695,7 @@ int Sound_GetFreeSlot()
// No free slots, hijack now.
float minDistance = 0;
int farSlot = -1;
int farSlot = SOUND_NO_CHANNEL;
for (int i = 0; i < SOUND_MAX_CHANNELS; i++)
{
@ -639,6 +712,25 @@ int Sound_GetFreeSlot()
return farSlot;
}
int Sound_TrackIsPlaying(const std::string& fileName)
{
for (int i = 0; i < (int)SoundTrackType::Count; i++)
{
const auto& slot = SoundtrackSlot[i];
if (!BASS_ChannelIsActive(slot.Channel))
continue;
auto name1 = TEN::Utils::ToLower(slot.Track);
auto name2 = TEN::Utils::ToLower(fileName);
if (name1.compare(name2) == 0)
return true;
}
return false;
}
// Returns slot ID in which effect is playing, if found. If not found, returns -1.
// We use origin position as a reference, because in original TRs it's not possible to clearly
// identify what's the source of the producing effect.
@ -649,7 +741,8 @@ int Sound_EffectIsPlaying(int effectID, Pose *position)
{
if (SoundSlot[i].EffectID == effectID)
{
if (SoundSlot[i].Channel == NULL) // Free channel
// Free channel.
if (SoundSlot[i].Channel == NULL)
continue;
if (BASS_ChannelIsActive(SoundSlot[i].Channel))
@ -663,29 +756,21 @@ int Sound_EffectIsPlaying(int effectID, Pose *position)
// Check if effect origin is equal OR in nearest possible hearing range.
Vector3 origin = Vector3(position->Position.x, position->Position.y, position->Position.z);
auto origin = Vector3(position->Position.x, position->Position.y, position->Position.z);
if (Vector3::Distance(origin, SoundSlot[i].Origin) < SOUND_MAXVOL_RADIUS)
return i;
}
else
{
SoundSlot[i].Channel = NULL; // WTF, let's clean this up
}
}
return -1;
}
}
bool IsSoundEffectPlaying(int effectID)
{
int channelIndex = Sound_EffectIsPlaying(effectID, nullptr);
if (channelIndex == -1)
return false;
return (SoundSlot[channelIndex].EffectID == effectID);
return SOUND_NO_CHANNEL;
}
// Gets the distance to the source.
float Sound_DistanceToListener(Pose *position)
{
// Assume sound is 2D menu sound.
@ -700,7 +785,6 @@ float Sound_DistanceToListener(Vector3 position)
}
// Calculate attenuated volume.
float Sound_Attenuate(float gain, float distance, float radius)
{
float result = gain * (1.0f - (distance / radius));
@ -709,7 +793,6 @@ float Sound_Attenuate(float gain, float distance, float radius)
}
// Stop and free desired sound slot.
void Sound_FreeSlot(int index, unsigned int fadeout)
{
if (index >= SOUND_MAX_CHANNELS || index < 0)
@ -725,11 +808,10 @@ void Sound_FreeSlot(int index, unsigned int fadeout)
SoundSlot[index].Channel = NULL;
SoundSlot[index].State = SoundState::Idle;
SoundSlot[index].EffectID = -1;
SoundSlot[index].EffectID = SOUND_NO_CHANNEL;
}
// Update sound position in a level.
bool Sound_UpdateEffectPosition(int index, Pose *position, bool force)
{
if (index >= SOUND_MAX_CHANNELS || index < 0)
@ -773,7 +855,6 @@ bool Sound_UpdateEffectAttributes(int index, float pitch, float gain)
// Update whole sound scene in a level.
// Must be called every frame to update camera position and 3D parameters.
void Sound_UpdateScene()
{
if (!g_Configuration.EnableSound)
@ -851,7 +932,6 @@ void Sound_UpdateScene()
// Initialize BASS engine and also prepare all sound data.
// Called once on engine start-up.
void Sound_Init(const std::string& gameDirectory)
{
// Initialize and collect soundtrack paths.
@ -893,7 +973,6 @@ void Sound_Init(const std::string& gameDirectory)
return;
// Initialize channels and tracks array
ZeroMemory(SoundtrackSlot, (sizeof(HSTREAM) * (int)SoundTrackType::Count));
ZeroMemory(SoundSlot, (sizeof(SoundEffectSlot) * SOUND_MAX_CHANNELS));
// Attach reverb effect to 3D channel
@ -916,7 +995,6 @@ void Sound_Init(const std::string& gameDirectory)
// Stop all sounds and streams, if any, unplug all channels from the mixer and unload BASS engine.
// Must be called on engine quit.
void Sound_DeInit()
{
if (g_Configuration.EnableSound)

View file

@ -5,13 +5,12 @@
#include "Game/control/control.h"
#include "Sound/sound_effects.h"
using std::string;
constexpr auto SOUND_NO_CHANNEL = -1;
constexpr auto SOUND_BASS_UNITS = 1.0f / 1024.0f; // TR->BASS distance unit coefficient
constexpr auto SOUND_MAXVOL_RADIUS = 1024.0f; // Max. volume hearing distance
constexpr auto SOUND_OMNIPRESENT_ORIGIN = Vector3(1.17549e-038f, 1.17549e-038f, 1.17549e-038f);
constexpr auto SOUND_MAX_SAMPLES = 8192; // Original was 1024, reallocate original 3-dword DX handle struct to just 1-dword memory pointer
constexpr auto SOUND_MAX_CHANNELS = 32; // Original was 24, reallocate original 36-byte struct with 24-byte SoundEffectSlot struct
constexpr auto SOUND_MAX_SAMPLES = 8192;
constexpr auto SOUND_MAX_CHANNELS = 32;
constexpr auto SOUND_LEGACY_SOUNDMAP_SIZE = 450;
constexpr auto SOUND_NEW_SOUNDMAP_MAX_SIZE = 4096;
constexpr auto SOUND_LEGACY_TRACKTABLE_SIZE = 136;
@ -22,6 +21,7 @@ constexpr auto SOUND_MAX_PITCH_CHANGE = 0.09f;
constexpr auto SOUND_MAX_GAIN_CHANGE = 0.0625f;
constexpr auto SOUND_32BIT_SILENCE_LEVEL = 4.9e-04f;
constexpr auto SOUND_SAMPLE_FLAGS = (BASS_SAMPLE_MONO | BASS_SAMPLE_FLOAT);
constexpr auto SOUND_MILLISECONDS_IN_SECOND = 1000.0f;
constexpr auto SOUND_XFADETIME_BGM = 5000;
constexpr auto SOUND_XFADETIME_BGM_START = 1500;
constexpr auto SOUND_XFADETIME_ONESHOT = 200;
@ -42,6 +42,7 @@ enum class SoundTrackType
{
OneShot,
BGM,
Voice,
Count
};
@ -96,8 +97,8 @@ struct SoundEffectSlot
struct SoundTrackSlot
{
HSTREAM Channel;
std::string Track;
HSTREAM Channel { 0 };
std::string Track {};
};
struct SampleInfo
@ -112,9 +113,9 @@ struct SampleInfo
struct SoundTrackInfo
{
std::string Name{};
SoundTrackType Mode{ SoundTrackType::OneShot };
int Mask{ 0 };
std::string Name {};
SoundTrackType Mode { SoundTrackType::OneShot };
int Mask { 0 };
};
struct SoundSourceInfo
@ -122,7 +123,7 @@ struct SoundSourceInfo
Vector3i Position = Vector3i::Zero;
int SoundID = 0;
int Flags = 0;
string Name = "";
std::string Name {};
SoundSourceInfo()
{
@ -158,19 +159,21 @@ void FreeSamples();
void StopAllSounds();
void PauseAllSounds(SoundPauseMode mode);
void ResumeAllSounds(SoundPauseMode mode);
void PlaySoundTrack(std::string trackName, SoundTrackType mode, QWORD position = 0);
void PlaySoundTrack(std::string trackName, short mask = 0);
void PlaySoundTrack(int index, short mask = 0);
void StopSoundTrack(SoundTrackType mode, int fadeoutTime);
void StopSoundTracks();
void ClearSoundTrackMasks();
void PlaySecretTrack();
void SayNo();
void PlaySoundSources();
int GetShatterSound(int shatterID);
void EnumerateLegacyTracks();
void PlaySoundTrack(const std::string& trackName, SoundTrackType mode, QWORD position = 0);
void PlaySoundTrack(const std::string& trackName, short mask = 0);
void PlaySoundTrack(int index, short mask = 0);
void StopSoundTrack(SoundTrackType mode, int fadeoutTime);
void StopSoundTracks(bool excludeAmbience = false);
void ClearSoundTrackMasks();
void PlaySecretTrack();
void EnumerateLegacyTracks();
void LoadSubtitles(const std::string& path);
float GetSoundTrackLoudness(SoundTrackType mode);
std::optional<std::string> GetCurrentSubtitle();
std::pair<std::string, QWORD> GetSoundTrackNameAndPosition(SoundTrackType type);
static void CALLBACK Sound_FinishOneshotTrack(HSYNC handle, DWORD channel, DWORD data, void* userData);
@ -186,10 +189,9 @@ void Sound_FreeSample(int index);
int Sound_GetFreeSlot();
void Sound_FreeSlot(int index, unsigned int fadeout = 0);
int Sound_EffectIsPlaying(int effectID, Pose *position);
int Sound_TrackIsPlaying(const std::string& fileName);
float Sound_DistanceToListener(Pose *position);
float Sound_DistanceToListener(Vector3 position);
float Sound_Attenuate(float gain, float distance, float radius);
bool Sound_UpdateEffectPosition(int index, Pose *position, bool force = false);
bool Sound_UpdateEffectAttributes(int index, float pitch, float gain);
bool IsSoundEffectPlaying(int effectID);

View file

@ -777,7 +777,8 @@ namespace TEN::Input
}
auto vendor = TEN::Utils::ToLower(OisGamepad->vendor());
if (vendor.find("xbox") != string::npos || vendor.find("xinput") != string::npos)
if (vendor.find("xbox") != std::string::npos || vendor.find("xinput") != std::string::npos)
{
ApplyBindings(XInputBindings);

View file

@ -259,6 +259,12 @@ bool SaveConfiguration()
return false;
}
if (SetBoolRegKey(rootKey, REGKEY_ENABLE_SUBTITLES, g_Configuration.EnableSubtitles) != ERROR_SUCCESS)
{
RegCloseKey(rootKey);
return false;
}
if (SetBoolRegKey(rootKey, REGKEY_AUTOTARGET, g_Configuration.AutoTarget) != ERROR_SUCCESS)
{
RegCloseKey(rootKey);
@ -293,6 +299,7 @@ void InitDefaultConfiguration()
auto currentScreenResolution = GetScreenResolution();
g_Configuration.EnableSubtitles = true;
g_Configuration.AutoTarget = true;
g_Configuration.SoundDevice = 1;
g_Configuration.EnableReverb = true;
@ -422,13 +429,20 @@ bool LoadConfiguration()
return false;
}
bool autoTarget = false;
bool autoTarget = true;
if (GetBoolRegKey(rootKey, REGKEY_AUTOTARGET, &autoTarget, true) != ERROR_SUCCESS)
{
RegCloseKey(rootKey);
return false;
}
bool enableSubtitles = true;
if (GetBoolRegKey(rootKey, REGKEY_ENABLE_SUBTITLES, &enableSubtitles, true) != ERROR_SUCCESS)
{
RegCloseKey(rootKey);
return false;
}
for (int i = 0; i < KEY_COUNT; i++)
{
DWORD tempKey;
@ -464,6 +478,7 @@ bool LoadConfiguration()
g_Configuration.AutoTarget = autoTarget;
g_Configuration.EnableRumble = enableRumble;
g_Configuration.EnableThumbstickCameraControl = enableThumbstickCamera;
g_Configuration.EnableSubtitles = enableSubtitles;
// Set legacy variables
SetVolumeMusic(musicVolume);

View file

@ -24,6 +24,7 @@ using namespace TEN::Math;
#define REGKEY_ENABLE_RUMBLE "EnableRumble"
#define REGKEY_ENABLE_THUMBSTICK_CAMERA "EnableThumbstickCamera"
#define REGKEY_ENABLE_SUBTITLES "EnableSubtitles"
#define REGKEY_AUTOTARGET "AutoTarget"
@ -45,6 +46,7 @@ struct GameConfiguration
int ShadowMapSize = 1024;
int ShadowMaxBlobs = 16;
bool EnableSubtitles;
bool AutoTarget;
bool EnableRumble;
bool EnableThumbstickCameraControl;

View file

@ -6775,6 +6775,8 @@ struct SaveGameT : public flatbuffers::NativeTable {
uint64_t ambient_position = 0;
std::string oneshot_track{};
uint64_t oneshot_position = 0;
std::string voice_track{};
uint64_t voice_position = 0;
std::vector<int32_t> cd_flags{};
std::unique_ptr<TEN::Save::RopeT> rope{};
std::unique_ptr<TEN::Save::PendulumT> pendulum{};
@ -6831,23 +6833,25 @@ struct SaveGame FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
VT_AMBIENT_POSITION = 62,
VT_ONESHOT_TRACK = 64,
VT_ONESHOT_POSITION = 66,
VT_CD_FLAGS = 68,
VT_ROPE = 70,
VT_PENDULUM = 72,
VT_ALTERNATE_PENDULUM = 74,
VT_VOLUMES = 76,
VT_CALL_COUNTERS = 78,
VT_SCRIPT_VARS = 80,
VT_CALLBACKS_PRE_START = 82,
VT_CALLBACKS_POST_START = 84,
VT_CALLBACKS_PRE_END = 86,
VT_CALLBACKS_POST_END = 88,
VT_CALLBACKS_PRE_SAVE = 90,
VT_CALLBACKS_POST_SAVE = 92,
VT_CALLBACKS_PRE_LOAD = 94,
VT_CALLBACKS_POST_LOAD = 96,
VT_CALLBACKS_PRE_CONTROL = 98,
VT_CALLBACKS_POST_CONTROL = 100
VT_VOICE_TRACK = 68,
VT_VOICE_POSITION = 70,
VT_CD_FLAGS = 72,
VT_ROPE = 74,
VT_PENDULUM = 76,
VT_ALTERNATE_PENDULUM = 78,
VT_VOLUMES = 80,
VT_CALL_COUNTERS = 82,
VT_SCRIPT_VARS = 84,
VT_CALLBACKS_PRE_START = 86,
VT_CALLBACKS_POST_START = 88,
VT_CALLBACKS_PRE_END = 90,
VT_CALLBACKS_POST_END = 92,
VT_CALLBACKS_PRE_SAVE = 94,
VT_CALLBACKS_POST_SAVE = 96,
VT_CALLBACKS_PRE_LOAD = 98,
VT_CALLBACKS_POST_LOAD = 100,
VT_CALLBACKS_PRE_CONTROL = 102,
VT_CALLBACKS_POST_CONTROL = 104
};
const TEN::Save::SaveGameHeader *header() const {
return GetPointer<const TEN::Save::SaveGameHeader *>(VT_HEADER);
@ -6945,6 +6949,12 @@ struct SaveGame FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
uint64_t oneshot_position() const {
return GetField<uint64_t>(VT_ONESHOT_POSITION, 0);
}
const flatbuffers::String *voice_track() const {
return GetPointer<const flatbuffers::String *>(VT_VOICE_TRACK);
}
uint64_t voice_position() const {
return GetField<uint64_t>(VT_VOICE_POSITION, 0);
}
const flatbuffers::Vector<int32_t> *cd_flags() const {
return GetPointer<const flatbuffers::Vector<int32_t> *>(VT_CD_FLAGS);
}
@ -7064,6 +7074,9 @@ struct SaveGame FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
VerifyOffset(verifier, VT_ONESHOT_TRACK) &&
verifier.VerifyString(oneshot_track()) &&
VerifyField<uint64_t>(verifier, VT_ONESHOT_POSITION) &&
VerifyOffset(verifier, VT_VOICE_TRACK) &&
verifier.VerifyString(voice_track()) &&
VerifyField<uint64_t>(verifier, VT_VOICE_POSITION) &&
VerifyOffset(verifier, VT_CD_FLAGS) &&
verifier.VerifyVector(cd_flags()) &&
VerifyOffset(verifier, VT_ROPE) &&
@ -7217,6 +7230,12 @@ struct SaveGameBuilder {
void add_oneshot_position(uint64_t oneshot_position) {
fbb_.AddElement<uint64_t>(SaveGame::VT_ONESHOT_POSITION, oneshot_position, 0);
}
void add_voice_track(flatbuffers::Offset<flatbuffers::String> voice_track) {
fbb_.AddOffset(SaveGame::VT_VOICE_TRACK, voice_track);
}
void add_voice_position(uint64_t voice_position) {
fbb_.AddElement<uint64_t>(SaveGame::VT_VOICE_POSITION, voice_position, 0);
}
void add_cd_flags(flatbuffers::Offset<flatbuffers::Vector<int32_t>> cd_flags) {
fbb_.AddOffset(SaveGame::VT_CD_FLAGS, cd_flags);
}
@ -7313,6 +7332,8 @@ inline flatbuffers::Offset<SaveGame> CreateSaveGame(
uint64_t ambient_position = 0,
flatbuffers::Offset<flatbuffers::String> oneshot_track = 0,
uint64_t oneshot_position = 0,
flatbuffers::Offset<flatbuffers::String> voice_track = 0,
uint64_t voice_position = 0,
flatbuffers::Offset<flatbuffers::Vector<int32_t>> cd_flags = 0,
flatbuffers::Offset<TEN::Save::Rope> rope = 0,
flatbuffers::Offset<TEN::Save::Pendulum> pendulum = 0,
@ -7331,6 +7352,7 @@ inline flatbuffers::Offset<SaveGame> CreateSaveGame(
flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>> callbacks_pre_control = 0,
flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>> callbacks_post_control = 0) {
SaveGameBuilder builder_(_fbb);
builder_.add_voice_position(voice_position);
builder_.add_oneshot_position(oneshot_position);
builder_.add_ambient_position(ambient_position);
builder_.add_callbacks_post_control(callbacks_post_control);
@ -7350,6 +7372,7 @@ inline flatbuffers::Offset<SaveGame> CreateSaveGame(
builder_.add_pendulum(pendulum);
builder_.add_rope(rope);
builder_.add_cd_flags(cd_flags);
builder_.add_voice_track(voice_track);
builder_.add_oneshot_track(oneshot_track);
builder_.add_ambient_track(ambient_track);
builder_.add_action_queue(action_queue);
@ -7422,6 +7445,8 @@ inline flatbuffers::Offset<SaveGame> CreateSaveGameDirect(
uint64_t ambient_position = 0,
const char *oneshot_track = nullptr,
uint64_t oneshot_position = 0,
const char *voice_track = nullptr,
uint64_t voice_position = 0,
const std::vector<int32_t> *cd_flags = nullptr,
flatbuffers::Offset<TEN::Save::Rope> rope = 0,
flatbuffers::Offset<TEN::Save::Pendulum> pendulum = 0,
@ -7457,6 +7482,7 @@ inline flatbuffers::Offset<SaveGame> CreateSaveGameDirect(
auto action_queue__ = action_queue ? _fbb.CreateVector<int32_t>(*action_queue) : 0;
auto ambient_track__ = ambient_track ? _fbb.CreateString(ambient_track) : 0;
auto oneshot_track__ = oneshot_track ? _fbb.CreateString(oneshot_track) : 0;
auto voice_track__ = voice_track ? _fbb.CreateString(voice_track) : 0;
auto cd_flags__ = cd_flags ? _fbb.CreateVector<int32_t>(*cd_flags) : 0;
auto volumes__ = volumes ? _fbb.CreateVector<flatbuffers::Offset<TEN::Save::Volume>>(*volumes) : 0;
auto call_counters__ = call_counters ? _fbb.CreateVector<flatbuffers::Offset<TEN::Save::EventSetCallCounters>>(*call_counters) : 0;
@ -7504,6 +7530,8 @@ inline flatbuffers::Offset<SaveGame> CreateSaveGameDirect(
ambient_position,
oneshot_track__,
oneshot_position,
voice_track__,
voice_position,
cd_flags__,
rope,
pendulum,
@ -9532,6 +9560,8 @@ inline void SaveGame::UnPackTo(SaveGameT *_o, const flatbuffers::resolver_functi
{ auto _e = ambient_position(); _o->ambient_position = _e; }
{ auto _e = oneshot_track(); if (_e) _o->oneshot_track = _e->str(); }
{ auto _e = oneshot_position(); _o->oneshot_position = _e; }
{ auto _e = voice_track(); if (_e) _o->voice_track = _e->str(); }
{ auto _e = voice_position(); _o->voice_position = _e; }
{ auto _e = cd_flags(); if (_e) { _o->cd_flags.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->cd_flags[_i] = _e->Get(_i); } } }
{ auto _e = rope(); if (_e) _o->rope = std::unique_ptr<TEN::Save::RopeT>(_e->UnPack(_resolver)); }
{ auto _e = pendulum(); if (_e) _o->pendulum = std::unique_ptr<TEN::Save::PendulumT>(_e->UnPack(_resolver)); }
@ -9591,6 +9621,8 @@ inline flatbuffers::Offset<SaveGame> CreateSaveGame(flatbuffers::FlatBufferBuild
auto _ambient_position = _o->ambient_position;
auto _oneshot_track = _o->oneshot_track.empty() ? _fbb.CreateSharedString("") : _fbb.CreateString(_o->oneshot_track);
auto _oneshot_position = _o->oneshot_position;
auto _voice_track = _o->voice_track.empty() ? _fbb.CreateSharedString("") : _fbb.CreateString(_o->voice_track);
auto _voice_position = _o->voice_position;
auto _cd_flags = _fbb.CreateVector(_o->cd_flags);
auto _rope = _o->rope ? CreateRope(_fbb, _o->rope.get(), _rehasher) : 0;
auto _pendulum = _o->pendulum ? CreatePendulum(_fbb, _o->pendulum.get(), _rehasher) : 0;
@ -9642,6 +9674,8 @@ inline flatbuffers::Offset<SaveGame> CreateSaveGame(flatbuffers::FlatBufferBuild
_ambient_position,
_oneshot_track,
_oneshot_position,
_voice_track,
_voice_position,
_cd_flags,
_rope,
_pendulum,

View file

@ -515,6 +515,8 @@ table SaveGame {
ambient_position: uint64;
oneshot_track: string;
oneshot_position: uint64;
voice_track: string;
voice_position: uint64;
cd_flags: [int32];
rope: Rope;
pendulum: Pendulum;

View file

@ -1,11 +1,11 @@
#include "framework.h"
#include "Specific/trutils.h"
#include <codecvt>
#include <filesystem>
#include "Renderer/Renderer11.h"
#include "Renderer/Renderer11Enums.h"
#include "Specific/trutils.h"
using TEN::Renderer::g_Renderer;
@ -118,6 +118,20 @@ namespace TEN::Utils
return std::wstring(buffer);
}
std::string ReplaceNewLineSymbols(const std::string& string)
{
auto result = string;
std::string::size_type index = 0;
while ((index = result.find("\\n", index)) != std::string::npos)
{
result.replace(index, 2, "\n");
++index;
}
return result;
}
std::vector<std::string> SplitString(const std::string& string)
{
auto strings = std::vector<std::string>{};

View file

@ -4,6 +4,7 @@ namespace TEN::Utils
{
// String utilities
std::string ConstructAssetDirectory(std::string customDirectory);
std::string ReplaceNewLineSymbols(const std::string& string);
std::string ToUpper(std::string string);
std::string ToLower(std::string string);
std::string ToString(const std::wstring& wString);

View file

@ -73,13 +73,13 @@
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)Build\$(Configuration)\Bin\x86\</OutDir>
<ExecutablePath>$(ExecutablePath);$(DXSDK_DIR)Utilities\bin\x86</ExecutablePath>
<IncludePath>$(SolutionDir)Libs;$(SolutionDir)Libs\lua;$(SolutionDir)Libs\sol;$(SolutionDir)Libs\zlib;$(SolutionDir)Libs\spdlog;$(SolutionDir)Libs\ois;$(SolutionDir)Libs\bass;$(IncludePath)</IncludePath>
<IncludePath>$(SolutionDir)Libs;$(SolutionDir)Libs\lua;$(SolutionDir)Libs\sol;$(SolutionDir)Libs\zlib;$(SolutionDir)Libs\spdlog;$(SolutionDir)Libs\ois;$(SolutionDir)Libs\bass;$(SolutionDir)Libs\srtparser;$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath);$(DXSDK_DIR)Lib\x86;$(SolutionDir)Libs\spdlog\x86;$(SolutionDir)Libs\lua\x86;$(SolutionDir)Libs\zlib\x86;$(SolutionDir)Libs\bass\x86;$(SolutionDir)Libs\ois\x86</LibraryPath>
<TargetExt>.exe</TargetExt>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ExecutablePath>$(ExecutablePath);$(DXSDK_DIR)Utilities\bin\x64</ExecutablePath>
<IncludePath>$(SolutionDir)Libs;$(SolutionDir)Libs\lua;$(SolutionDir)Libs\sol;$(SolutionDir)Libs\zlib;$(SolutionDir)Libs\spdlog;$(SolutionDir)Libs\ois;$(SolutionDir)Libs\bass;$(IncludePath)</IncludePath>
<IncludePath>$(SolutionDir)Libs;$(SolutionDir)Libs\lua;$(SolutionDir)Libs\sol;$(SolutionDir)Libs\zlib;$(SolutionDir)Libs\spdlog;$(SolutionDir)Libs\ois;$(SolutionDir)Libs\bass;$(SolutionDir)Libs\srtparser;$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath);$(DXSDK_DIR)Lib\x64;$(SolutionDir)Libs\spdlog\x64;$(SolutionDir)Libs\lua\x64;$(SolutionDir)Libs\zlib\x64;$(SolutionDir)Libs\bass\x64;$(SolutionDir)Libs\ois\x64</LibraryPath>
<TargetExt>.exe</TargetExt>
<LinkIncremental>true</LinkIncremental>
@ -91,13 +91,13 @@
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)Build\$(Configuration)\Bin\x86\</OutDir>
<ExecutablePath>$(ExecutablePath);$(DXSDK_DIR)Utilities\bin\x86</ExecutablePath>
<IncludePath>$(SolutionDir)Libs;$(SolutionDir)Libs\lua;$(SolutionDir)Libs\sol;$(SolutionDir)Libs\ois;$(SolutionDir)Libs\spdlog;$(SolutionDir)Libs\zlib;$(SolutionDir)Libs\bass;$(IncludePath)</IncludePath>
<IncludePath>$(SolutionDir)Libs;$(SolutionDir)Libs\lua;$(SolutionDir)Libs\sol;$(SolutionDir)Libs\zlib;$(SolutionDir)Libs\spdlog;$(SolutionDir)Libs\ois;$(SolutionDir)Libs\bass;$(SolutionDir)Libs\srtparser;$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath);$(DXSDK_DIR)Lib\x86;$(SolutionDir)Libs\spdlog\x86;$(SolutionDir)Libs\lua\x86;$(SolutionDir)Libs\zlib\x86;$(SolutionDir)Libs\bass\x86;$(SolutionDir)Libs\ois\x86</LibraryPath>
<TargetExt>.exe</TargetExt>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ExecutablePath>$(ExecutablePath);$(DXSDK_DIR)Utilities\bin\x64</ExecutablePath>
<IncludePath>$(SolutionDir)Libs;$(SolutionDir)Libs\lua;$(SolutionDir)Libs\sol;$(SolutionDir)Libs\zlib;$(SolutionDir)Libs\spdlog;$(SolutionDir)Libs\ois;$(SolutionDir)Libs\bass;$(IncludePath)</IncludePath>
<IncludePath>$(SolutionDir)Libs;$(SolutionDir)Libs\lua;$(SolutionDir)Libs\sol;$(SolutionDir)Libs\zlib;$(SolutionDir)Libs\spdlog;$(SolutionDir)Libs\ois;$(SolutionDir)Libs\bass;$(SolutionDir)Libs\srtparser;$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath);$(DXSDK_DIR)Lib\x64;$(SolutionDir)Libs\spdlog\x64;$(SolutionDir)Libs\lua\x64;$(SolutionDir)Libs\zlib\x64;$(SolutionDir)Libs\bass\x64;$(SolutionDir)Libs\ois\x64</LibraryPath>
<TargetExt>.exe</TargetExt>
<LinkIncremental>true</LinkIncremental>
@ -726,6 +726,7 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)"</Command>
<ClInclude Include="Scripting\Internal\TEN\Misc\CameraTypes.h" />
<ClInclude Include="Scripting\Internal\TEN\Misc\LevelLog.h" />
<ClInclude Include="Scripting\Internal\TEN\Misc\Miscellaneous.h" />
<ClInclude Include="Scripting\Internal\TEN\Misc\SoundTrackTypes.h" />
<ClInclude Include="Scripting\Internal\TEN\Objects\AIObject\AIObject.h" />
<ClInclude Include="Scripting\Internal\TEN\Objects\Camera\CameraObject.h" />
<ClInclude Include="Scripting\Internal\TEN\Objects\Lara\LaraObject.h" />