Merge pull request #13738 from IntelOrca/ride-music-objects-3

Read and use the JSON music objects for ride music implementation. Supports both vanilla and custom music objects. More music objects can be added via object manager if you compile-in the hidden tabs.

Refactors a lot of the ride music logic into a new source file: RideAudio.
This commit is contained in:
Ted John
2021-01-28 17:26:40 +00:00
committed by GitHub
38 changed files with 1511 additions and 714 deletions

View File

@@ -57,6 +57,8 @@
4C8BB68125533D65005C8830 /* StringBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C8BB67D25533D64005C8830 /* StringBuilder.cpp */; };
4C8BB68225533D65005C8830 /* StringReader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C8BB67E25533D64005C8830 /* StringReader.cpp */; };
4C8BB68525533DB9005C8830 /* ZoomLevel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C8BB68425533DB9005C8830 /* ZoomLevel.cpp */; };
4C91FD5F25AE476700CA5DA4 /* MusicObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C91FD5D25AE476700CA5DA4 /* MusicObject.cpp */; };
4C91FD6225AE483700CA5DA4 /* RideAudio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C91FD6025AE483600CA5DA4 /* RideAudio.cpp */; };
4C93F1AD1F8CD9F000A9330D /* Input.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C93F1AC1F8CD9F000A9330D /* Input.cpp */; };
4C93F1AF1F8CD9F600A9330D /* KeyboardShortcut.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C93F1AE1F8CD9F600A9330D /* KeyboardShortcut.cpp */; };
4CA39E512513F8A00094066B /* RTL.ICU.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4CA39E4E2513F8A00094066B /* RTL.ICU.cpp */; };
@@ -458,7 +460,6 @@
C688786F20289A6F0084B384 /* VehicleData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C7B54052005735F00A52E21 /* VehicleData.cpp */; };
C688787020289A6F0084B384 /* VehiclePaint.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C7B54072005736700A52E21 /* VehiclePaint.cpp */; };
C688787120289A780084B384 /* Ride.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C6A66BF1FF9322A00694CB6 /* Ride.cpp */; };
C688787220289A780084B384 /* MusicList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F73E320F2011589F00C4D975 /* MusicList.cpp */; };
C688787320289A780084B384 /* RideRatings.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F73E320B2011589E00C4D975 /* RideRatings.cpp */; };
C688787420289A780084B384 /* TrackDesignSave.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F73E320E2011589F00C4D975 /* TrackDesignSave.cpp */; };
C688787520289A780084B384 /* RideData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C7B541420060D8E00A52E21 /* RideData.cpp */; };
@@ -1033,6 +1034,10 @@
4C8BB68325533DB9005C8830 /* ZoomLevel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZoomLevel.h; sourceTree = "<group>"; };
4C8BB68425533DB9005C8830 /* ZoomLevel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZoomLevel.cpp; sourceTree = "<group>"; };
4C9196ED204FF3E000869A24 /* Location.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Location.hpp; sourceTree = "<group>"; };
4C91FD5D25AE476700CA5DA4 /* MusicObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MusicObject.cpp; sourceTree = "<group>"; };
4C91FD5E25AE476700CA5DA4 /* MusicObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MusicObject.h; sourceTree = "<group>"; };
4C91FD6025AE483600CA5DA4 /* RideAudio.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RideAudio.cpp; sourceTree = "<group>"; };
4C91FD6125AE483600CA5DA4 /* RideAudio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RideAudio.h; sourceTree = "<group>"; };
4C93F1181F8B744400A9330D /* AirPoweredVerticalCoaster.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AirPoweredVerticalCoaster.cpp; sourceTree = "<group>"; };
4C93F1191F8B744400A9330D /* BobsleighCoaster.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BobsleighCoaster.cpp; sourceTree = "<group>"; };
4C93F11A1F8B744400A9330D /* BolligerMabillardTrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BolligerMabillardTrack.h; sourceTree = "<group>"; };
@@ -1913,9 +1918,7 @@
F70839911FFC0AFF002DCEFA /* Scenario.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Scenario.cpp; sourceTree = "<group>"; };
F73E320B2011589E00C4D975 /* RideRatings.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RideRatings.cpp; sourceTree = "<group>"; };
F73E320C2011589F00C4D975 /* RideRatings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RideRatings.h; sourceTree = "<group>"; };
F73E320D2011589F00C4D975 /* MusicList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MusicList.h; sourceTree = "<group>"; };
F73E320E2011589F00C4D975 /* TrackDesignSave.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TrackDesignSave.cpp; sourceTree = "<group>"; };
F73E320F2011589F00C4D975 /* MusicList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MusicList.cpp; sourceTree = "<group>"; };
F74789541EEDEA0D009E50E7 /* Input.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Input.h; sourceTree = "<group>"; };
F76C809A1EC4D9FA00FA49E2 /* libopenrct2.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libopenrct2.a; sourceTree = BUILT_PRODUCTS_DIR; };
F76C83571EC4E7CC00FA49E2 /* Audio.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Audio.cpp; sourceTree = "<group>"; };
@@ -3384,6 +3387,8 @@
F76C841B1EC4E7CC00FA49E2 /* ImageTable.h */,
F76C841C1EC4E7CC00FA49E2 /* LargeSceneryObject.cpp */,
F76C841D1EC4E7CC00FA49E2 /* LargeSceneryObject.h */,
4C91FD5D25AE476700CA5DA4 /* MusicObject.cpp */,
4C91FD5E25AE476700CA5DA4 /* MusicObject.h */,
F76C841E1EC4E7CC00FA49E2 /* Object.cpp */,
F76C841F1EC4E7CC00FA49E2 /* Object.h */,
F76C84201EC4E7CC00FA49E2 /* ObjectFactory.cpp */,
@@ -3559,10 +3564,10 @@
F76C84EA1EC4E7CD00FA49E2 /* water */,
4C6AC2101F9E1CB3004324AA /* CableLift.cpp */,
4C6AC2111F9E1CB3004324AA /* CableLift.h */,
F73E320F2011589F00C4D975 /* MusicList.cpp */,
F73E320D2011589F00C4D975 /* MusicList.h */,
4C6A66BF1FF9322A00694CB6 /* Ride.cpp */,
4C6A66C01FF9322A00694CB6 /* Ride.h */,
4C91FD6025AE483600CA5DA4 /* RideAudio.cpp */,
4C91FD6125AE483600CA5DA4 /* RideAudio.h */,
4C7B541420060D8E00A52E21 /* RideData.cpp */,
4C7B541520060D8E00A52E21 /* RideData.h */,
F73E320B2011589E00C4D975 /* RideRatings.cpp */,
@@ -4508,6 +4513,7 @@
930EEA6A24FC00950070314E /* ScenarioSelect.cpp in Sources */,
C654DF3C1F69C0430040F43D /* TrackDesignManage.cpp in Sources */,
C64645001F3FA4120026AC2D /* ViewClipping.cpp in Sources */,
4C91FD6225AE483700CA5DA4 /* RideAudio.cpp in Sources */,
C68878C020289B710084B384 /* ApplyPaletteShader.cpp in Sources */,
C666EE791F37ACB10061AA04 /* ServerStart.cpp in Sources */,
C61ADB231FBBCB8B0024F2EF /* GameBottomToolbar.cpp in Sources */,
@@ -4587,6 +4593,7 @@
C666EE771F37ACB10061AA04 /* SavePrompt.cpp in Sources */,
C654DF391F69C0430040F43D /* TitleCommandEditor.cpp in Sources */,
C61FB2731FA3E25D0095FB9D /* TextInput.cpp in Sources */,
4C91FD5F25AE476700CA5DA4 /* MusicObject.cpp in Sources */,
C62D83871FCC7D1A008C04F1 /* EditorObjectSelection.cpp in Sources */,
4C3B4236205914F7000C5BB7 /* InGameConsole.cpp in Sources */,
C68878C820289B710084B384 /* SwapFramebuffer.cpp in Sources */,
@@ -4936,7 +4943,6 @@
936F412A24CE030F00E07BCF /* NetworkClient.cpp in Sources */,
F76C86C51EC4E88400FA49E2 /* S6Importer.cpp in Sources */,
C688790A20289B9B0084B384 /* WoodenRollerCoaster.cpp in Sources */,
C688787220289A780084B384 /* MusicList.cpp in Sources */,
93F76F0220BFF77B00D4512C /* Paint.Surface.cpp in Sources */,
66A10ECF257F1DF800DD651A /* ClimateSetAction.cpp in Sources */,
93DFD02F24521BA0001FCBAF /* FileWatcher.cpp in Sources */,

View File

@@ -70,6 +70,11 @@ namespace OpenRCT2::Audio
return AudioSource::CreateStreamFromWAV(path);
}
IAudioSource* CreateStreamFromWAV(std::unique_ptr<IStream> stream) override
{
return AudioSource::CreateStreamFromWAV(std::move(stream));
}
void StartTitleMusic() override
{
}

View File

@@ -66,6 +66,7 @@ namespace OpenRCT2::Audio
IAudioSource* CreateMemoryFromWAV(const std::string& path, const AudioFormat* targetFormat = nullptr);
IAudioSource* CreateStreamFromWAV(const std::string& path);
IAudioSource* CreateStreamFromWAV(SDL_RWops* rw);
IAudioSource* CreateStreamFromWAV(std::unique_ptr<IStream> stream);
} // namespace AudioSource
namespace AudioChannel

View File

@@ -202,4 +202,34 @@ namespace OpenRCT2::Audio
}
return source;
}
IAudioSource* AudioSource::CreateStreamFromWAV(std::unique_ptr<IStream> stream)
{
auto rw = new SDL_RWops();
*rw = {};
rw->type = SDL_RWOPS_UNKNOWN;
rw->hidden.unknown.data1 = stream.release();
rw->seek = [](SDL_RWops* ctx, Sint64 offset, int whence) {
auto ptr = static_cast<IStream*>(ctx->hidden.unknown.data1);
ptr->Seek(offset, whence);
return static_cast<Sint64>(ptr->GetPosition());
};
rw->read = [](SDL_RWops* ctx, void* buf, size_t size, size_t maxnum) {
auto ptr = static_cast<IStream*>(ctx->hidden.unknown.data1);
return static_cast<size_t>(ptr->TryRead(buf, size * maxnum) / size);
};
rw->size = [](SDL_RWops* ctx) {
auto ptr = static_cast<IStream*>(ctx->hidden.unknown.data1);
return static_cast<Sint64>(ptr->GetLength());
};
rw->close = [](SDL_RWops* ctx) {
auto ptr = static_cast<IStream*>(ctx->hidden.unknown.data1);
delete ptr;
ctx->hidden.unknown.data1 = nullptr;
delete ctx;
return 0;
};
return CreateStreamFromWAV(rw);
}
} // namespace OpenRCT2::Audio

View File

@@ -33,6 +33,7 @@
#include <openrct2/localisation/LocalisationService.h>
#include <openrct2/network/network.h>
#include <openrct2/platform/Platform2.h>
#include <openrct2/ride/RideAudio.h>
#include <openrct2/scenario/Scenario.h>
#include <openrct2/sprites.h>
#include <openrct2/title/TitleScreen.h>
@@ -1374,7 +1375,7 @@ static void window_options_audio_mouseup(rct_window* w, rct_widgetindex widgetIn
gConfigSound.ride_music_enabled = !gConfigSound.ride_music_enabled;
if (!gConfigSound.ride_music_enabled)
{
OpenRCT2::Audio::StopRideMusic();
OpenRCT2::RideAudio::StopAllChannels();
}
config_save_default();
w->Invalidate();

View File

@@ -36,6 +36,7 @@
#include <openrct2/localisation/LocalisationService.h>
#include <openrct2/localisation/StringIds.h>
#include <openrct2/network/network.h>
#include <openrct2/object/MusicObject.h>
#include <openrct2/object/ObjectManager.h>
#include <openrct2/object/ObjectRepository.h>
#include <openrct2/object/StationObject.h>
@@ -52,6 +53,8 @@
#include <openrct2/sprites.h>
#include <openrct2/windows/Intent.h>
#include <openrct2/world/Park.h>
#include <vector>
using namespace OpenRCT2;
static constexpr const rct_string_id WINDOW_TITLE = STR_RIDE_WINDOW_TITLE;
@@ -4938,15 +4941,20 @@ static void window_ride_colour_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi
#pragma region Music
static constexpr const uint8_t MusicStyleOrder[] = {
MUSIC_STYLE_GENTLE, MUSIC_STYLE_SUMMER, MUSIC_STYLE_WATER, MUSIC_STYLE_RAGTIME, MUSIC_STYLE_TECHNO,
MUSIC_STYLE_MECHANICAL, MUSIC_STYLE_MODERN, MUSIC_STYLE_WILD_WEST, MUSIC_STYLE_PIRATES, MUSIC_STYLE_ROCK,
MUSIC_STYLE_ROCK_STYLE_2, MUSIC_STYLE_ROCK_STYLE_3, MUSIC_STYLE_FANTASY, MUSIC_STYLE_HORROR, MUSIC_STYLE_TOYLAND,
MUSIC_STYLE_CANDY_STYLE, MUSIC_STYLE_ROMAN_FANFARE, MUSIC_STYLE_ORIENTAL, MUSIC_STYLE_MARTIAN, MUSIC_STYLE_SPACE,
MUSIC_STYLE_JUNGLE_DRUMS, MUSIC_STYLE_JURASSIC, MUSIC_STYLE_EGYPTIAN, MUSIC_STYLE_DODGEMS_BEAT, MUSIC_STYLE_SNOW,
MUSIC_STYLE_ICE, MUSIC_STYLE_MEDIEVAL, MUSIC_STYLE_URBAN, MUSIC_STYLE_ORGAN
MUSIC_STYLE_GENTLE, MUSIC_STYLE_SUMMER, MUSIC_STYLE_WATER,
MUSIC_STYLE_RAGTIME, MUSIC_STYLE_TECHNO, MUSIC_STYLE_MECHANICAL,
MUSIC_STYLE_MODERN, MUSIC_STYLE_WILD_WEST, MUSIC_STYLE_PIRATES,
MUSIC_STYLE_ROCK, MUSIC_STYLE_ROCK_STYLE_2, MUSIC_STYLE_ROCK_STYLE_3,
MUSIC_STYLE_FANTASY, MUSIC_STYLE_HORROR, MUSIC_STYLE_TOYLAND,
MUSIC_STYLE_CANDY_STYLE, MUSIC_STYLE_ROMAN_FANFARE, MUSIC_STYLE_ORIENTAL,
MUSIC_STYLE_MARTIAN, MUSIC_STYLE_SPACE, MUSIC_STYLE_JUNGLE_DRUMS,
MUSIC_STYLE_JURASSIC, MUSIC_STYLE_EGYPTIAN, MUSIC_STYLE_DODGEMS_BEAT,
MUSIC_STYLE_SNOW, MUSIC_STYLE_ICE, MUSIC_STYLE_MEDIEVAL,
MUSIC_STYLE_URBAN, MUSIC_STYLE_ORGAN, MUSIC_STYLE_CUSTOM_MUSIC_1,
MUSIC_STYLE_CUSTOM_MUSIC_2
};
static uint8_t window_ride_current_music_style_order[42];
static std::vector<ObjectEntryIndex> window_ride_current_music_style_order;
/**
*
@@ -5001,6 +5009,25 @@ static void window_ride_music_resize(rct_window* w)
window_set_resize(w, 316, 81, 316, 81);
}
static std::optional<size_t> GetMusicStyleOrder(ObjectEntryIndex musicObjectIndex)
{
auto& objManager = GetContext()->GetObjectManager();
auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, musicObjectIndex));
// Get the index in the order list
auto originalStyleId = musicObj->GetOriginalStyleId();
if (originalStyleId)
{
auto it = std::find(std::begin(MusicStyleOrder), std::end(MusicStyleOrder), *originalStyleId);
if (it != std::end(MusicStyleOrder))
{
return std::distance(std::begin(MusicStyleOrder), it);
}
}
return std::nullopt;
}
/**
*
* rct2: 0x006B1EFC
@@ -5015,37 +5042,67 @@ static void window_ride_music_mousedown(rct_window* w, rct_widgetindex widgetInd
if (ride == nullptr)
return;
int32_t numItems = 0;
if (ride->type == RIDE_TYPE_MERRY_GO_ROUND)
// Construct list of available music
auto& musicOrder = window_ride_current_music_style_order;
musicOrder.clear();
auto& objManager = GetContext()->GetObjectManager();
for (ObjectEntryIndex i = 0; i < MAX_MUSIC_OBJECTS; i++)
{
window_ride_current_music_style_order[numItems++] = MUSIC_STYLE_FAIRGROUND_ORGAN;
}
else
{
for (size_t n = 0; n < std::size(MusicStyleOrder); n++)
window_ride_current_music_style_order[numItems++] = MusicStyleOrder[n];
auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, i));
if (musicObj != nullptr)
{
// Hide custom music if the WAV file does not exist
auto originalStyleId = musicObj->GetOriginalStyleId();
if (originalStyleId == MUSIC_STYLE_CUSTOM_MUSIC_1 || originalStyleId == MUSIC_STYLE_CUSTOM_MUSIC_2)
{
auto numTracks = musicObj->GetTrackCount();
if (numTracks != 0)
{
auto track0 = musicObj->GetTrack(0);
if (!track0->Asset.IsAvailable())
{
continue;
}
}
else
{
continue;
}
}
if (OpenRCT2::Audio::gRideMusicInfoList[36].length != 0)
window_ride_current_music_style_order[numItems++] = MUSIC_STYLE_CUSTOM_MUSIC_1;
if (OpenRCT2::Audio::gRideMusicInfoList[37].length != 0)
window_ride_current_music_style_order[numItems++] = MUSIC_STYLE_CUSTOM_MUSIC_2;
if (musicObj->SupportsRideType(ride->type))
{
musicOrder.push_back(i);
}
}
}
for (auto i = 0; i < numItems; i++)
// Sort available music by the original RCT2 list order
std::stable_sort(musicOrder.begin(), musicOrder.end(), [](const ObjectEntryIndex& a, const ObjectEntryIndex& b) {
auto orderA = GetMusicStyleOrder(a);
auto orderB = GetMusicStyleOrder(b);
return orderA < orderB;
});
// Setup dropdown list
auto numItems = musicOrder.size();
for (size_t i = 0; i < numItems; i++)
{
auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, musicOrder[i]));
gDropdownItemsFormat[i] = STR_DROPDOWN_MENU_LABEL;
gDropdownItemsArgs[i] = MusicStyleNames[window_ride_current_music_style_order[i]];
gDropdownItemsArgs[i] = musicObj->NameStringId;
}
WindowDropdownShowTextCustomWidth(
{ w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
w->colours[1], 0, Dropdown::Flag::StayOpen, numItems, widget->right - dropdownWidget->left);
for (auto i = 0; i < numItems; i++)
// Set currently checked item
for (size_t i = 0; i < numItems; i++)
{
if (window_ride_current_music_style_order[i] == ride->music)
if (musicOrder[i] == ride->music)
{
Dropdown::SetChecked(i, true);
Dropdown::SetChecked(static_cast<int32_t>(i), true);
}
}
}
@@ -5056,13 +5113,12 @@ static void window_ride_music_mousedown(rct_window* w, rct_widgetindex widgetInd
*/
static void window_ride_music_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
{
uint8_t musicStyle;
if (widgetIndex != WIDX_MUSIC_DROPDOWN || dropdownIndex == -1)
return;
musicStyle = window_ride_current_music_style_order[dropdownIndex];
set_operating_setting(w->number, RideSetSetting::MusicType, musicStyle);
if (widgetIndex == WIDX_MUSIC_DROPDOWN && dropdownIndex >= 0
&& static_cast<size_t>(dropdownIndex) < window_ride_current_music_style_order.size())
{
auto musicStyle = window_ride_current_music_style_order[dropdownIndex];
set_operating_setting(w->number, RideSetSetting::MusicType, musicStyle);
}
}
/**
@@ -5099,7 +5155,14 @@ static void window_ride_music_invalidate(rct_window* w)
ride->FormatNameTo(ft);
// Set selected music
window_ride_music_widgets[WIDX_MUSIC].text = MusicStyleNames[ride->music];
rct_string_id musicName = STR_NONE;
auto& objManager = GetContext()->GetObjectManager();
auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, ride->music));
if (musicObj != nullptr)
{
musicName = musicObj->NameStringId;
}
window_ride_music_widgets[WIDX_MUSIC].text = musicName;
// Set music activated
auto isMusicActivated = (ride->lifecycle_flags & RIDE_LIFECYCLE_MUSIC) != 0;

View File

@@ -175,6 +175,7 @@ namespace OpenRCT2
gfx_object_check_all_images_freed();
gfx_unload_g2();
gfx_unload_g1();
Audio::Close();
config_release();
Instance = nullptr;

View File

@@ -9,6 +9,8 @@
#include "RideSetSettingAction.h"
#include "../Context.h"
#include "../object/ObjectManager.h"
#include "../ride/Ride.h"
#include "../ride/RideData.h"
@@ -100,12 +102,16 @@ GameActions::Result::Ptr RideSetSettingAction::Query() const
case RideSetSetting::Music:
break;
case RideSetSetting::MusicType:
if (_value >= MUSIC_STYLE_COUNT)
{
auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
auto musicObj = objManager.GetLoadedObject(ObjectType::Music, _value);
if (musicObj == nullptr)
{
log_warning("Invalid music style: %u", _value);
return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_CHANGE_OPERATING_MODE);
}
break;
}
case RideSetSetting::LiftHillSpeed:
if (!ride_is_valid_lift_hill_speed(ride))
{

View File

@@ -22,6 +22,7 @@
#include "../localisation/StringIds.h"
#include "../peep/Peep.h"
#include "../ride/Ride.h"
#include "../ride/RideAudio.h"
#include "../ui/UiContext.h"
#include "../util/Util.h"
#include "AudioContext.h"
@@ -48,9 +49,6 @@ namespace OpenRCT2::Audio
void* gTitleMusicChannel = nullptr;
void* gWeatherSoundChannel = nullptr;
RideMusic gRideMusicList[MaxRideMusic];
RideMusicParams gRideMusicParamsList[MaxRideMusic];
RideMusicParams* gRideMusicParamsListEnd;
VehicleSound gVehicleSoundList[MaxVehicleSounds];
// clang-format off
@@ -290,26 +288,11 @@ namespace OpenRCT2::Audio
}
}
void StopRideMusic()
{
for (auto& rideMusic : gRideMusicList)
{
if (rideMusic.ride_id != RIDE_ID_NULL)
{
rideMusic.ride_id = RIDE_ID_NULL;
if (rideMusic.sound_channel != nullptr)
{
Mixer_Stop_Channel(rideMusic.sound_channel);
}
}
}
}
void StopAll()
{
StopTitleMusic();
StopVehicleSounds();
StopRideMusic();
RideAudio::StopAllChannels();
peep_stop_crowd_noise();
StopWeatherSound();
}
@@ -355,33 +338,7 @@ namespace OpenRCT2::Audio
void InitRideSoundsAndInfo()
{
int32_t deviceNum = 0;
InitRideSounds(deviceNum);
for (auto& rideMusicInfo : gRideMusicInfoList)
{
const utf8* path = context_get_path_legacy(rideMusicInfo.path_id);
if (File::Exists(path))
{
try
{
auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN);
uint32_t head = fs.ReadValue<uint32_t>();
if (head == 0x78787878)
{
rideMusicInfo.length = 0;
}
// The length used to be hardcoded, but we stopped doing that to allow replacement.
if (rideMusicInfo.length == 0)
{
rideMusicInfo.length = fs.GetLength();
}
}
catch (const std::exception&)
{
}
}
}
InitRideSounds(0);
}
void InitRideSounds(int32_t device)
@@ -394,17 +351,13 @@ namespace OpenRCT2::Audio
_currentAudioDevice = device;
config_save_default();
for (auto& rideMusic : gRideMusicList)
{
rideMusic.ride_id = RIDE_ID_NULL;
}
}
void Close()
{
peep_stop_crowd_noise();
StopTitleMusic();
StopRideMusic();
RideAudio::StopAllChannels();
StopWeatherSound();
_currentAudioDevice = -1;
}
@@ -429,7 +382,7 @@ namespace OpenRCT2::Audio
{
gGameSoundsOff = true;
StopVehicleSounds();
StopRideMusic();
RideAudio::StopAllChannels();
peep_stop_crowd_noise();
StopWeatherSound();
}

View File

@@ -10,6 +10,7 @@
#pragma once
#include "../common.h"
#include "../core/IStream.hpp"
#include <memory>
#include <string>
@@ -34,6 +35,7 @@ namespace OpenRCT2::Audio
virtual void SetOutputDevice(const std::string& deviceName) abstract;
virtual IAudioSource* CreateStreamFromWAV(const std::string& path) abstract;
virtual IAudioSource* CreateStreamFromWAV(std::unique_ptr<IStream> stream) abstract;
virtual void StartTitleMusic() abstract;

View File

@@ -129,6 +129,28 @@ void Mixer_Channel_SetGroup(void* channel, MixerGroup group)
static_cast<IAudioChannel*>(channel)->SetGroup(group);
}
template<typename T> static void* PlayMusic(T&& src, int32_t loop)
{
auto* mixer = GetMixer();
if (mixer == nullptr)
return nullptr;
auto audioContext = GetContext()->GetAudioContext();
auto stream = audioContext->CreateStreamFromWAV(std::forward<T&&>(src));
if (stream == nullptr)
return nullptr;
auto* channel = mixer->Play(stream, loop, false, true);
if (channel == nullptr)
{
delete stream;
return nullptr;
}
channel->SetGroup(MixerGroup::RideMusic);
return channel;
}
void* Mixer_Play_Music(int32_t pathId, int32_t loop, int32_t streaming)
{
IAudioChannel* channel = nullptr;
@@ -166,6 +188,16 @@ void* Mixer_Play_Music(int32_t pathId, int32_t loop, int32_t streaming)
return channel;
}
void* Mixer_Play_Music(const char* path, int32_t loop)
{
return PlayMusic(path, loop);
}
void* Mixer_Play_Music(std::unique_ptr<IStream> stream, int32_t loop)
{
return PlayMusic(std::move(stream), loop);
}
void Mixer_SetVolume(float volume)
{
GetMixer()->SetVolume(volume);

View File

@@ -10,6 +10,9 @@
#pragma once
#include "../common.h"
#include "../core/IStream.hpp"
#include <memory>
#define MIXER_VOLUME_MAX 128
#define MIXER_LOOP_NONE 0
@@ -69,6 +72,8 @@ uint64_t Mixer_Channel_GetOffset(void* channel);
int32_t Mixer_Channel_SetOffset(void* channel, uint64_t offset);
void Mixer_Channel_SetGroup(void* channel, OpenRCT2::Audio::MixerGroup group);
void* Mixer_Play_Music(int32_t pathId, int32_t loop, int32_t streaming);
void* Mixer_Play_Music(const char* path, int32_t loop);
void* Mixer_Play_Music(std::unique_ptr<OpenRCT2::IStream> stream, int32_t loop);
void Mixer_SetVolume(float volume);
int32_t DStoMixerVolume(int32_t volume);

View File

@@ -31,6 +31,11 @@ namespace OpenRCT2::Audio
return nullptr;
}
IAudioSource* CreateStreamFromWAV(std::unique_ptr<IStream>) override
{
return nullptr;
}
void StartTitleMusic() override
{
}

View File

@@ -12,12 +12,13 @@
#include "../common.h"
#include "../ride/RideTypes.h"
#include <vector>
struct CoordsXYZ;
namespace OpenRCT2::Audio
{
constexpr size_t MaxDeviceNameSize = 256;
constexpr size_t MaxRideMusic = 32;
constexpr size_t MaxVehicleSounds = 14;
constexpr size_t MaxDefaultMusic = 46;
constexpr uint16_t SoundIdNull = 0xFFFF;
@@ -26,33 +27,6 @@ namespace OpenRCT2::Audio
enum class SoundId : uint8_t;
struct RideMusic
{
ride_id_t ride_id;
uint8_t tune_id;
int16_t volume;
int16_t pan;
uint16_t frequency;
void* sound_channel;
};
struct RideMusicInfo
{
uint8_t path_id;
uint32_t offset;
uint32_t length;
};
struct RideMusicParams
{
ride_id_t ride_id;
uint8_t tune_id;
int32_t offset;
int16_t volume;
int16_t pan;
uint16_t frequency;
};
struct Sound
{
SoundId Id;
@@ -157,11 +131,6 @@ namespace OpenRCT2::Audio
extern void* gTitleMusicChannel;
extern void* gWeatherSoundChannel;
extern RideMusic gRideMusicList[MaxRideMusic];
extern RideMusicInfo gRideMusicInfoList[MaxDefaultMusic];
extern RideMusicParams gRideMusicParamsList[MaxRideMusic];
extern RideMusicParams* gRideMusicParamsListEnd;
extern VehicleSound gVehicleSoundList[MaxVehicleSounds];
/**
@@ -247,12 +216,6 @@ namespace OpenRCT2::Audio
*/
void StopWeatherSound();
/**
* Stops ride music from playing.
* rct2: 0x006BCA9F
*/
void StopRideMusic();
/**
* Stops the title music from playing.
* rct2: 0x006BD0BD

View File

@@ -51,6 +51,16 @@ namespace OpenRCT2
{
}
MemoryStream::MemoryStream(std::vector<uint8_t>&& v)
{
_access = MEMORY_ACCESS::OWNER;
_dataCapacity = v.size();
_dataSize = v.size();
_data = Memory::Allocate<void>(v.size());
_position = _data;
std::memcpy(_data, v.data(), v.size());
}
MemoryStream::MemoryStream(MemoryStream&& mv) noexcept
: _access(mv._access)
, _dataCapacity(mv._dataCapacity)

View File

@@ -13,6 +13,7 @@
#include "IStream.hpp"
#include <algorithm>
#include <vector>
namespace OpenRCT2
{
@@ -42,6 +43,7 @@ namespace OpenRCT2
explicit MemoryStream(size_t capacity);
MemoryStream(void* data, size_t dataSize, uint8_t access = MEMORY_ACCESS::READ);
MemoryStream(const void* data, size_t dataSize);
MemoryStream(std::vector<uint8_t>&& v);
virtual ~MemoryStream();
MemoryStream& operator=(MemoryStream&& mv) noexcept;

View File

@@ -30,12 +30,45 @@ namespace Path
return safe_strcat_path(buffer, src, bufferSize);
}
std::string Combine(const std::string& a, const std::string& b)
static constexpr bool IsPathSeparator(char c)
{
utf8 buffer[MAX_PATH];
String::Set(buffer, sizeof(buffer), a.c_str());
Path::Append(buffer, sizeof(buffer), b.c_str());
return std::string(buffer);
#ifdef _WIN32
if (c == '\\')
return true;
#endif
return c == '/';
}
std::string Combine(std::string_view a, std::string_view b)
{
if (a.empty())
return std::string(b);
if (b.empty())
return std::string(a);
auto aEnd = a.back();
auto bBegin = b.front();
if (IsPathSeparator(aEnd))
{
if (IsPathSeparator(bBegin))
{
return std::string(a) + std::string(b.substr(1));
}
else
{
return std::string(a) + std::string(b);
}
}
else
{
if (IsPathSeparator(bBegin))
{
return std::string(a) + std::string(b);
}
else
{
return std::string(a) + PATH_SEPARATOR + std::string(b);
}
}
}
std::string GetDirectory(const std::string& path)

View File

@@ -16,9 +16,9 @@
namespace Path
{
utf8* Append(utf8* buffer, size_t bufferSize, const utf8* src);
std::string Combine(const std::string& a, const std::string& b);
std::string Combine(std::string_view a, std::string_view b);
template<typename... Args> static std::string Combine(const std::string& a, const std::string& b, Args... args)
template<typename... Args> static std::string Combine(std::string_view a, std::string_view b, Args... args)
{
return Combine(a, Combine(b, args...));
}

View File

@@ -7,12 +7,65 @@
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "Zip.h"
#include "IStream.hpp"
#include <algorithm>
#ifndef __ANDROID__
# include "Zip.h"
# include "IStream.hpp"
# include <zip.h>
#endif
using namespace OpenRCT2;
static std::string NormalisePath(std::string_view path)
{
std::string result;
if (!path.empty())
{
result.reserve(path.size());
for (auto ch : path)
{
if (ch == '\\')
{
result += '/';
}
else
{
result += ch;
}
}
}
return result;
}
/**
* Normalises both the given path and the stored paths and finds the first match.
*/
std::optional<size_t> IZipArchive::GetIndexFromPath(std::string_view path) const
{
auto normalisedPath = NormalisePath(path);
if (!normalisedPath.empty())
{
auto numFiles = GetNumFiles();
for (size_t i = 0; i < numFiles; i++)
{
auto normalisedZipPath = NormalisePath(GetFileName(i));
if (normalisedZipPath == normalisedPath)
{
return i;
}
}
}
return std::nullopt;
}
bool IZipArchive::Exists(std::string_view path) const
{
return GetIndexFromPath(path).has_value();
}
#ifndef __ANDROID__
class ZipArchive final : public IZipArchive
{
@@ -78,25 +131,37 @@ public:
{
std::vector<uint8_t> result;
auto index = GetIndexFromPath(path);
auto dataSize = GetFileSize(index);
if (dataSize > 0 && dataSize < SIZE_MAX)
if (index)
{
auto zipFile = zip_fopen_index(_zip, index, 0);
if (zipFile != nullptr)
auto dataSize = GetFileSize(*index);
if (dataSize > 0 && dataSize < SIZE_MAX)
{
result.resize(static_cast<size_t>(dataSize));
uint64_t readBytes = zip_fread(zipFile, result.data(), dataSize);
if (readBytes != dataSize)
auto zipFile = zip_fopen_index(_zip, *index, 0);
if (zipFile != nullptr)
{
result.clear();
result.shrink_to_fit();
result.resize(static_cast<size_t>(dataSize));
uint64_t readBytes = zip_fread(zipFile, result.data(), dataSize);
if (readBytes != dataSize)
{
result = {};
}
zip_fclose(zipFile);
}
zip_fclose(zipFile);
}
}
return result;
}
std::unique_ptr<IStream> GetFileStream(std::string_view path) const override
{
auto index = GetIndexFromPath(path);
if (index)
{
return std::make_unique<ZipItemStream>(_zip, *index);
}
return {};
}
void SetFileData(std::string_view path, std::vector<uint8_t>&& data) override
{
// Push buffer to an internal list as libzip requires access to it until the zip
@@ -106,66 +171,203 @@ public:
auto source = zip_source_buffer(_zip, writeBuffer.data(), writeBuffer.size(), 0);
auto index = GetIndexFromPath(path);
if (index == -1)
if (index)
{
zip_add(_zip, path.data(), source);
zip_replace(_zip, *index, source);
}
else
{
zip_replace(_zip, index, source);
zip_add(_zip, path.data(), source);
}
}
void DeleteFile(std::string_view path) override
{
auto index = GetIndexFromPath(path);
zip_delete(_zip, index);
if (index)
{
zip_delete(_zip, *index);
}
else
{
throw std::runtime_error("File does not exist.");
}
}
void RenameFile(std::string_view path, std::string_view newPath) override
{
auto index = GetIndexFromPath(path);
zip_file_rename(_zip, index, newPath.data(), ZIP_FL_ENC_GUESS);
if (index)
{
zip_file_rename(_zip, *index, newPath.data(), ZIP_FL_ENC_GUESS);
}
else
{
throw std::runtime_error("File does not exist.");
}
}
private:
/**
* Normalises both the given path and the stored paths and finds the first match.
*/
zip_int64_t GetIndexFromPath(std::string_view path) const
class ZipItemStream final : public IStream
{
auto normalisedPath = NormalisePath(path);
if (!normalisedPath.empty())
{
auto numFiles = zip_get_num_entries(_zip, 0);
for (zip_int64_t i = 0; i < numFiles; i++)
{
auto normalisedZipPath = NormalisePath(zip_get_name(_zip, i, ZIP_FL_ENC_GUESS));
if (normalisedZipPath == normalisedPath)
{
return i;
}
}
}
return -1;
}
private:
zip* _zip;
zip_int64_t _index;
zip_file_t* _zipFile{};
zip_uint64_t _len{};
zip_uint64_t _pos{};
static std::string NormalisePath(std::string_view path)
{
std::string result(path);
if (!path.empty())
public:
ZipItemStream(zip* zip, zip_int64_t index)
: _zip(zip)
, _index(index)
{
// Convert back slashes to forward slashes
for (auto ch = result.data(); *ch != '\0'; ch++)
}
~ZipItemStream() override
{
Close();
}
bool CanRead() const override
{
return true;
}
bool CanWrite() const override
{
return false;
}
uint64_t GetLength() const override
{
return _len;
}
uint64_t GetPosition() const override
{
return _pos;
}
void SetPosition(uint64_t position) override
{
if (position > _pos)
{
if (*ch == '\\')
{
*ch = '/';
}
// Read to seek forwards
Skip(position - _pos);
}
else if (position < _pos)
{
// Can not seek backwards, start from the beginning
Reset();
Skip(position);
}
}
return result;
}
void Seek(int64_t offset, int32_t origin) override
{
switch (origin)
{
case STREAM_SEEK_BEGIN:
SetPosition(offset);
break;
case STREAM_SEEK_CURRENT:
SetPosition(_pos + offset);
break;
case STREAM_SEEK_END:
SetPosition(_len - offset);
break;
}
}
void Read(void* buffer, uint64_t length) override
{
size_t readBytes = TryRead(buffer, length);
if (readBytes != length)
{
throw IOException("Attempted to read past end of file.");
}
}
void Write(const void* buffer, uint64_t length) override
{
throw IOException("Stream is read-only.");
}
uint64_t TryRead(void* buffer, uint64_t length) override
{
if (_zipFile == nullptr && !Reset())
{
return 0;
}
auto readBytes = zip_fread(_zipFile, buffer, length);
if (readBytes < 0)
{
return 0;
}
else
{
_pos += readBytes;
return static_cast<uint64_t>(readBytes);
}
}
const void* GetData() const override
{
return nullptr;
}
private:
void Close()
{
if (_zipFile != nullptr)
{
zip_fclose(_zipFile);
_zipFile = nullptr;
}
}
bool Reset()
{
Close();
_pos = 0;
_len = 0;
_zipFile = zip_fopen_index(_zip, _index, 0);
if (_zipFile == nullptr)
{
return false;
}
zip_stat_t zipFileStat{};
if (zip_stat_index(_zip, _index, 0, &zipFileStat) != ZIP_ER_OK)
{
return false;
}
_len = zipFileStat.size;
return true;
}
void Skip(zip_int64_t len)
{
// zip_fseek can not be used on compressed data, so skip bytes by
// reading into a temporary buffer
char buffer[2048]{};
while (len > 0)
{
auto readLen = std::min<zip_int64_t>(len, sizeof(buffer));
auto read = zip_fread(_zipFile, buffer, readLen);
if (read <= 0)
{
break;
}
_pos += read;
len -= read;
}
}
};
};
namespace Zip

View File

@@ -11,10 +11,17 @@
#include "../common.h"
#include <istream>
#include <memory>
#include <optional>
#include <string_view>
#include <vector>
namespace OpenRCT2
{
struct IStream;
}
/**
* Represents a zip file.
*/
@@ -28,6 +35,7 @@ struct IZipArchive
virtual std::string GetFileName(size_t index) const abstract;
virtual uint64_t GetFileSize(size_t index) const abstract;
virtual std::vector<uint8_t> GetFileData(std::string_view path) const abstract;
virtual std::unique_ptr<OpenRCT2::IStream> GetFileStream(std::string_view path) const abstract;
/**
* Creates or overwrites a file within the zip archive to the given data buffer.
@@ -38,6 +46,9 @@ struct IZipArchive
virtual void DeleteFile(std::string_view path) abstract;
virtual void RenameFile(std::string_view path, std::string_view newPath) abstract;
std::optional<size_t> GetIndexFromPath(std::string_view path) const;
bool Exists(std::string_view path) const;
};
enum class ZIP_ACCESS

Some files were not shown because too many files have changed in this diff Show More