mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
178 lines
4.9 KiB
C++
178 lines
4.9 KiB
C++
//
|
|
//
|
|
|
|
#include "SoundPlugin.h"
|
|
#include "gamesnd/gamesnd.h"
|
|
#include "utils/string_utils.h"
|
|
|
|
using namespace Rocket::Core;
|
|
|
|
namespace {
|
|
|
|
struct SoundDefinition {
|
|
interface_snd_id default_id;
|
|
|
|
SCP_unordered_map<SCP_string, interface_snd_id> state_ids;
|
|
};
|
|
|
|
SoundDefinition parse_sound_value(const String& value)
|
|
{
|
|
SoundDefinition def{};
|
|
|
|
bool saw_default = false;
|
|
for (auto& part : util::split_string(SCP_string(value.CString()), ',')) {
|
|
drop_white_space(part);
|
|
if (part.find(')') != SCP_string::npos) {
|
|
auto state_start = part.find('(');
|
|
auto state_end = part.find(')');
|
|
|
|
if (state_start == SCP_string::npos || state_end == SCP_string::npos) {
|
|
throw std::runtime_error("Could not find both '(' and ')' for state sound!");
|
|
}
|
|
|
|
state_start += 1; // skip the (
|
|
|
|
if (state_start > state_end) {
|
|
throw std::runtime_error("')' appeared before '('!");
|
|
}
|
|
SCP_string state_name = part.substr(state_start, state_end - state_start);
|
|
if (state_name.empty()) {
|
|
throw std::runtime_error("State name is empty!");
|
|
}
|
|
|
|
auto sound_name = part.substr(state_end + 1);
|
|
drop_white_space(sound_name);
|
|
|
|
auto snd = gamesnd_get_by_iface_name(sound_name.c_str());
|
|
if (!snd.isValid()) {
|
|
SCP_string error("Sound '");
|
|
error += sound_name;
|
|
error += "' for state '";
|
|
error += state_name;
|
|
error += "' is invalid!";
|
|
throw std::runtime_error(error);
|
|
}
|
|
|
|
def.state_ids.emplace(state_name, snd);
|
|
} else {
|
|
if (saw_default) {
|
|
throw std::runtime_error("Multiple default sounds specified!");
|
|
}
|
|
|
|
def.default_id = gamesnd_get_by_iface_name(part.c_str());
|
|
saw_default = true;
|
|
|
|
if (!def.default_id.isValid()) {
|
|
throw std::runtime_error("Default sound has an invalid name!");
|
|
}
|
|
}
|
|
}
|
|
|
|
return def;
|
|
}
|
|
}
|
|
|
|
namespace scpui {
|
|
|
|
bool SoundPropertyParser::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const
|
|
{
|
|
try {
|
|
parse_sound_value(value);
|
|
} catch (const std::exception& e) {
|
|
mprintf(("libRocket: Failure to parse sound CSS property '%s': %s\n", value.CString(), e.what()));
|
|
return false;
|
|
}
|
|
|
|
property.value = Variant(value);
|
|
property.unit = Property::STRING;
|
|
return true;
|
|
}
|
|
void SoundPropertyParser::Release() { delete this; }
|
|
|
|
int SoundPlugin::GetEventClasses() { return EVT_BASIC | EVT_ELEMENT; }
|
|
|
|
SoundPlugin* SoundPlugin::_instance = nullptr;
|
|
SCP_vector<SCP_string> SoundPlugin::_event_types = {"click", "dblclick", "mouseover", "mouseout",
|
|
"mouseup", "mousedown", "mousescroll", "scrollchange"};
|
|
const SCP_vector<SCP_string>& SoundPlugin::getEventTypes() { return _event_types; }
|
|
|
|
void SoundPlugin::OnShutdown() { delete this; }
|
|
void SoundPlugin::OnElementCreate(Element* element)
|
|
{
|
|
for (auto& type : getEventTypes()) {
|
|
element->AddEventListener(type.c_str(), _event_handler.get());
|
|
}
|
|
}
|
|
void SoundPlugin::OnElementDestroy(Element* /*element*/) {}
|
|
SoundPlugin::SoundPlugin()
|
|
{
|
|
_event_handler.reset(new SoundEventHandler());
|
|
_instance = this;
|
|
}
|
|
SoundPlugin::~SoundPlugin() { _instance = nullptr; }
|
|
SoundPlugin* SoundPlugin::instance() { return _instance; }
|
|
|
|
bool SoundPlugin::PlayElementSound(Element* element, const String& event, const String& state)
|
|
{
|
|
auto property_name = event + "-sound";
|
|
|
|
const Property* sound_prop = element->GetProperty(property_name);
|
|
|
|
if (!(sound_prop->unit & Property::STRING)) {
|
|
// sounds are stored as their original string values which are then parsed whenever necessary
|
|
return false;
|
|
}
|
|
|
|
auto sound = sound_prop->Get<String>();
|
|
|
|
try {
|
|
auto snds = parse_sound_value(sound);
|
|
|
|
interface_snd_id sound_id;
|
|
|
|
if (state.Empty()) {
|
|
sound_id = snds.default_id;
|
|
} else {
|
|
auto snd_iter = snds.state_ids.find(SCP_string(state.CString()));
|
|
|
|
if (snd_iter == snds.state_ids.end()) {
|
|
return false;
|
|
}
|
|
|
|
sound_id = snd_iter->second;
|
|
}
|
|
|
|
// The default sound is optional so this might be invalid
|
|
if (!sound_id.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
if (event == "scrollchange") {
|
|
if (_last_scrollchange_time >= 0) {
|
|
auto diff = Rocket::Core::GetSystemInterface()->GetElapsedTime() - _last_scrollchange_time;
|
|
|
|
if (diff < gamesnd_get_max_duration(gamesnd_get_interface_sound(sound_id)) / 1000.0f * 1.1f) {
|
|
return false;
|
|
}
|
|
}
|
|
_last_scrollchange_time = Rocket::Core::GetSystemInterface()->GetElapsedTime();
|
|
}
|
|
|
|
gamesnd_play_iface(sound_id);
|
|
|
|
return true;
|
|
} catch (const std::exception& e) {
|
|
mprintf(("libRocket: Failure to parse sound CSS property '%s': %s\n", sound.CString(), e.what()));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void SoundPlugin::SoundEventHandler::ProcessEvent(Event& event)
|
|
{
|
|
// Hover events bubble up so ignore any mouse movement events that aren't caused by the target element
|
|
if ((event.GetType() != "mouseover" && event.GetType() != "mouseout") || event.GetPhase() == Event::PHASE_TARGET) {
|
|
SoundPlugin::instance()->PlayElementSound(event.GetCurrentElement(), event.GetType());
|
|
}
|
|
}
|
|
} // namespace scpui
|