mirror of https://github.com/OpenRCT2/OpenRCT2.git
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:
commit
900a30507c
|
@ -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 */,
|
||||
|
|
|
@ -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
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -175,6 +175,7 @@ namespace OpenRCT2
|
|||
gfx_object_check_all_images_freed();
|
||||
gfx_unload_g2();
|
||||
gfx_unload_g1();
|
||||
Audio::Close();
|
||||
config_release();
|
||||
|
||||
Instance = nullptr;
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -31,6 +31,11 @@ namespace OpenRCT2::Audio
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
IAudioSource* CreateStreamFromWAV(std::unique_ptr<IStream>) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void StartTitleMusic() override
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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...));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -11,11 +11,14 @@
|
|||
|
||||
# include "../platform/platform.h"
|
||||
# include "IStream.hpp"
|
||||
# include "MemoryStream.h"
|
||||
# include "Zip.h"
|
||||
|
||||
# include <SDL.h>
|
||||
# include <jni.h>
|
||||
|
||||
using namespace OpenRCT2;
|
||||
|
||||
class ZipArchive final : public IZipArchive
|
||||
{
|
||||
private:
|
||||
|
@ -113,6 +116,12 @@ public:
|
|||
return std::vector<uint8_t>(dataPtr, dataPtr + dataSize);
|
||||
}
|
||||
|
||||
std::unique_ptr<IStream> GetFileStream(std::string_view path) const override
|
||||
{
|
||||
auto data = GetFileData(path);
|
||||
return std::make_unique<MemoryStream>(std::move(data));
|
||||
}
|
||||
|
||||
void SetFileData(std::string_view path, std::vector<uint8_t>&& data) override
|
||||
{
|
||||
STUB();
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "../localisation/Localisation.h"
|
||||
#include "../localisation/StringIds.h"
|
||||
#include "../platform/platform.h"
|
||||
#include "../ride/RideAudio.h"
|
||||
#include "../scenario/Scenario.h"
|
||||
#include "../sprites.h"
|
||||
#include "../ui/UiContext.h"
|
||||
|
@ -1784,7 +1785,7 @@ void window_close_construction_windows()
|
|||
*/
|
||||
void window_update_viewport_ride_music()
|
||||
{
|
||||
OpenRCT2::Audio::gRideMusicParamsListEnd = &OpenRCT2::Audio::gRideMusicParamsList[0];
|
||||
OpenRCT2::RideAudio::ClearAllViewportInstances();
|
||||
g_music_tracking_viewport = nullptr;
|
||||
|
||||
for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++)
|
||||
|
|
|
@ -255,6 +255,7 @@
|
|||
<ClInclude Include="object\FootpathObject.h" />
|
||||
<ClInclude Include="object\ImageTable.h" />
|
||||
<ClInclude Include="object\LargeSceneryObject.h" />
|
||||
<ClInclude Include="object\MusicObject.h" />
|
||||
<ClInclude Include="object\Object.h" />
|
||||
<ClInclude Include="object\ObjectFactory.h" />
|
||||
<ClInclude Include="object\ObjectLimits.h" />
|
||||
|
@ -352,8 +353,8 @@
|
|||
<ClInclude Include="ride\gentle\meta\ObservationTower.h" />
|
||||
<ClInclude Include="ride\gentle\meta\SpaceRings.h" />
|
||||
<ClInclude Include="ride\gentle\meta\SpiralSlide.h" />
|
||||
<ClInclude Include="ride\MusicList.h" />
|
||||
<ClInclude Include="ride\Ride.h" />
|
||||
<ClInclude Include="ride\RideAudio.h" />
|
||||
<ClInclude Include="ride\RideData.h" />
|
||||
<ClInclude Include="ride\RideRatings.h" />
|
||||
<ClInclude Include="ride\RideTypes.h" />
|
||||
|
@ -671,6 +672,7 @@
|
|||
<ClCompile Include="object\FootpathObject.cpp" />
|
||||
<ClCompile Include="object\ImageTable.cpp" />
|
||||
<ClCompile Include="object\LargeSceneryObject.cpp" />
|
||||
<ClCompile Include="object\MusicObject.cpp" />
|
||||
<ClCompile Include="object\Object.cpp" />
|
||||
<ClCompile Include="object\ObjectFactory.cpp" />
|
||||
<ClCompile Include="object\ObjectList.cpp" />
|
||||
|
@ -787,8 +789,8 @@
|
|||
<ClCompile Include="ride\gentle\ObservationTower.cpp" />
|
||||
<ClCompile Include="ride\gentle\SpaceRings.cpp" />
|
||||
<ClCompile Include="ride\gentle\SpiralSlide.cpp" />
|
||||
<ClCompile Include="ride\MusicList.cpp" />
|
||||
<ClCompile Include="ride\Ride.cpp" />
|
||||
<ClCompile Include="ride\RideAudio.cpp" />
|
||||
<ClCompile Include="ride\RideData.cpp" />
|
||||
<ClCompile Include="ride\RideRatings.cpp" />
|
||||
<ClCompile Include="ride\ShopItem.cpp" />
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/*****************************************************************************
|
||||
* Copyright (c) 2014-2021 OpenRCT2 developers
|
||||
*
|
||||
* For a complete list of all authors, please refer to contributors.md
|
||||
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||||
*
|
||||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||
*****************************************************************************/
|
||||
|
||||
#include "MusicObject.h"
|
||||
|
||||
#include "../Context.h"
|
||||
#include "../OpenRCT2.h"
|
||||
#include "../PlatformEnvironment.h"
|
||||
#include "../core/IStream.hpp"
|
||||
#include "../core/Json.hpp"
|
||||
#include "../core/Path.hpp"
|
||||
#include "../localisation/StringIds.h"
|
||||
#include "../ride/Ride.h"
|
||||
#include "RideObject.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
using namespace OpenRCT2;
|
||||
|
||||
constexpr size_t DEFAULT_BYTES_PER_TICK = 1378;
|
||||
|
||||
void MusicObject::Load()
|
||||
{
|
||||
GetStringTable().Sort();
|
||||
NameStringId = language_allocate_object_string(GetName());
|
||||
|
||||
for (auto& track : _tracks)
|
||||
{
|
||||
track.BytesPerTick = DEFAULT_BYTES_PER_TICK;
|
||||
track.Size = track.Asset.GetSize();
|
||||
}
|
||||
}
|
||||
|
||||
void MusicObject::Unload()
|
||||
{
|
||||
language_free_object_string(NameStringId);
|
||||
NameStringId = 0;
|
||||
}
|
||||
|
||||
void MusicObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t height) const
|
||||
{
|
||||
// Write (no image)
|
||||
int32_t x = width / 2;
|
||||
int32_t y = height / 2;
|
||||
gfx_draw_string_centred(dpi, STR_WINDOW_NO_IMAGE, { x, y }, COLOUR_BLACK, nullptr);
|
||||
}
|
||||
|
||||
void MusicObject::ReadJson(IReadObjectContext* context, json_t& root)
|
||||
{
|
||||
_originalStyleId = {};
|
||||
_rideTypes.clear();
|
||||
_tracks.clear();
|
||||
|
||||
auto& properties = root["properties"];
|
||||
if (properties != nullptr)
|
||||
{
|
||||
const auto& originalStyleId = properties["originalStyleId"];
|
||||
if (originalStyleId.is_number_integer())
|
||||
{
|
||||
_originalStyleId = originalStyleId.get<uint8_t>();
|
||||
}
|
||||
|
||||
const auto& jRideTypes = properties["rideTypes"];
|
||||
if (jRideTypes.is_array())
|
||||
{
|
||||
ParseRideTypes(jRideTypes);
|
||||
}
|
||||
|
||||
auto& jTracks = properties["tracks"];
|
||||
if (jTracks.is_array())
|
||||
{
|
||||
ParseTracks(*context, jTracks);
|
||||
}
|
||||
}
|
||||
|
||||
PopulateTablesFromJson(context, root);
|
||||
}
|
||||
|
||||
void MusicObject::ParseRideTypes(const json_t& jRideTypes)
|
||||
{
|
||||
for (const auto& jRideType : jRideTypes)
|
||||
{
|
||||
auto szRideType = Json::GetString(jRideType);
|
||||
if (!szRideType.empty())
|
||||
{
|
||||
auto rideType = RideObject::ParseRideType(szRideType);
|
||||
if (rideType != RIDE_TYPE_NULL)
|
||||
{
|
||||
_rideTypes.push_back(rideType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MusicObject::ParseTracks(IReadObjectContext& context, json_t& jTracks)
|
||||
{
|
||||
for (auto& jTrack : jTracks)
|
||||
{
|
||||
if (jTrack.is_object())
|
||||
{
|
||||
MusicObjectTrack track;
|
||||
track.Name = Json::GetString(jTrack["name"]);
|
||||
auto source = Json::GetString(jTrack["source"]);
|
||||
if (source.empty())
|
||||
{
|
||||
context.LogError(ObjectError::InvalidProperty, "Invalid audio track definition.");
|
||||
}
|
||||
else
|
||||
{
|
||||
track.Asset = GetAsset(context, source);
|
||||
_tracks.push_back(std::move(track));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<uint8_t> MusicObject::GetOriginalStyleId() const
|
||||
{
|
||||
return _originalStyleId;
|
||||
}
|
||||
|
||||
bool MusicObject::SupportsRideType(uint8_t rideType)
|
||||
{
|
||||
if (_rideTypes.size() == 0)
|
||||
{
|
||||
// Default behaviour for music is to only exclude from merry-go-round
|
||||
return rideType != RIDE_TYPE_MERRY_GO_ROUND;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it = std::find(_rideTypes.begin(), _rideTypes.end(), rideType);
|
||||
return it != _rideTypes.end();
|
||||
}
|
||||
}
|
||||
|
||||
size_t MusicObject::GetTrackCount() const
|
||||
{
|
||||
return _tracks.size();
|
||||
}
|
||||
|
||||
const MusicObjectTrack* MusicObject::GetTrack(size_t trackIndex) const
|
||||
{
|
||||
if (_tracks.size() > trackIndex)
|
||||
{
|
||||
return &_tracks[trackIndex];
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ObjectAsset MusicObject::GetAsset(IReadObjectContext& context, std::string_view path)
|
||||
{
|
||||
if (path.find("$RCT2:DATA/") == 0)
|
||||
{
|
||||
auto platformEnvironment = GetContext()->GetPlatformEnvironment();
|
||||
auto dir = platformEnvironment->GetDirectoryPath(DIRBASE::RCT2, DIRID::DATA);
|
||||
auto path2 = Path::Combine(dir, std::string(path.substr(11)));
|
||||
return ObjectAsset(path2);
|
||||
}
|
||||
else
|
||||
{
|
||||
return context.GetAsset(path);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*****************************************************************************
|
||||
* Copyright (c) 2014-2019 OpenRCT2 developers
|
||||
*
|
||||
* For a complete list of all authors, please refer to contributors.md
|
||||
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||||
*
|
||||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Object.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class MusicObjectTrack
|
||||
{
|
||||
public:
|
||||
std::string Name;
|
||||
ObjectAsset Asset;
|
||||
|
||||
/**
|
||||
* The number of PCM bytes to seek per game tick when the music is playing offscreen.
|
||||
*/
|
||||
size_t BytesPerTick;
|
||||
|
||||
/**
|
||||
* The length of the PCM track in bytes.
|
||||
*/
|
||||
size_t Size;
|
||||
};
|
||||
|
||||
class MusicObject final : public Object
|
||||
{
|
||||
private:
|
||||
std::vector<uint8_t> _rideTypes;
|
||||
std::vector<MusicObjectTrack> _tracks;
|
||||
std::optional<uint8_t> _originalStyleId;
|
||||
|
||||
public:
|
||||
rct_string_id NameStringId{};
|
||||
|
||||
explicit MusicObject(const rct_object_entry& entry)
|
||||
: Object(entry)
|
||||
{
|
||||
}
|
||||
|
||||
void ReadJson(IReadObjectContext* context, json_t& root) override;
|
||||
void Load() override;
|
||||
void Unload() override;
|
||||
|
||||
void DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t height) const override;
|
||||
|
||||
std::optional<uint8_t> GetOriginalStyleId() const;
|
||||
bool SupportsRideType(uint8_t rideType);
|
||||
size_t GetTrackCount() const;
|
||||
const MusicObjectTrack* GetTrack(size_t trackIndex) const;
|
||||
|
||||
private:
|
||||
void ParseRideTypes(const json_t& jRideTypes);
|
||||
void ParseTracks(IReadObjectContext& context, json_t& jTracks);
|
||||
static ObjectAsset GetAsset(IReadObjectContext& context, std::string_view path);
|
||||
};
|
|
@ -10,8 +10,11 @@
|
|||
#include "Object.h"
|
||||
|
||||
#include "../Context.h"
|
||||
#include "../core/File.h"
|
||||
#include "../core/FileStream.h"
|
||||
#include "../core/Memory.hpp"
|
||||
#include "../core/String.hpp"
|
||||
#include "../core/Zip.h"
|
||||
#include "../localisation/Language.h"
|
||||
#include "../localisation/LocalisationService.h"
|
||||
#include "../localisation/StringIds.h"
|
||||
|
@ -22,6 +25,8 @@
|
|||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace OpenRCT2;
|
||||
|
||||
ObjectType& operator++(ObjectType& d, int)
|
||||
{
|
||||
return d = (d == ObjectType::Count) ? ObjectType::Ride : static_cast<ObjectType>(static_cast<uint8_t>(d) + 1);
|
||||
|
@ -165,6 +170,137 @@ std::optional<uint8_t> rct_object_entry::GetSceneryType() const
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Couples a zip archive and a zip item stream to ensure the lifetime of the zip archive is maintained
|
||||
* for the lifetime of the stream.
|
||||
*/
|
||||
class ZipStreamWrapper final : public IStream
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<IZipArchive> _zipArchive;
|
||||
std::unique_ptr<IStream> _base;
|
||||
|
||||
public:
|
||||
ZipStreamWrapper(std::unique_ptr<IZipArchive> zipArchive, std::unique_ptr<IStream> base)
|
||||
: _zipArchive(std::move(zipArchive))
|
||||
, _base(std::move(base))
|
||||
{
|
||||
}
|
||||
|
||||
bool CanRead() const override
|
||||
{
|
||||
return _base->CanRead();
|
||||
}
|
||||
|
||||
bool CanWrite() const override
|
||||
{
|
||||
return _base->CanWrite();
|
||||
}
|
||||
|
||||
uint64_t GetLength() const override
|
||||
{
|
||||
return _base->GetLength();
|
||||
}
|
||||
|
||||
uint64_t GetPosition() const override
|
||||
{
|
||||
return _base->GetPosition();
|
||||
}
|
||||
|
||||
void SetPosition(uint64_t position) override
|
||||
{
|
||||
_base->SetPosition(position);
|
||||
}
|
||||
|
||||
void Seek(int64_t offset, int32_t origin) override
|
||||
{
|
||||
_base->Seek(offset, origin);
|
||||
}
|
||||
|
||||
void Read(void* buffer, uint64_t length) override
|
||||
{
|
||||
_base->Read(buffer, length);
|
||||
}
|
||||
|
||||
void Write(const void* buffer, uint64_t length) override
|
||||
{
|
||||
_base->Write(buffer, length);
|
||||
}
|
||||
|
||||
uint64_t TryRead(void* buffer, uint64_t length) override
|
||||
{
|
||||
return _base->TryRead(buffer, length);
|
||||
}
|
||||
|
||||
const void* GetData() const override
|
||||
{
|
||||
return _base->GetData();
|
||||
}
|
||||
};
|
||||
|
||||
bool ObjectAsset::IsAvailable() const
|
||||
{
|
||||
if (_zipPath.empty())
|
||||
{
|
||||
return File::Exists(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto zipArchive = Zip::TryOpen(_zipPath, ZIP_ACCESS::READ);
|
||||
return zipArchive != nullptr && zipArchive->Exists(_path);
|
||||
}
|
||||
}
|
||||
|
||||
size_t ObjectAsset::GetSize() const
|
||||
{
|
||||
if (_zipPath.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
return File::ReadAllBytes(_path).size();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto zipArchive = Zip::TryOpen(_zipPath, ZIP_ACCESS::READ);
|
||||
if (zipArchive != nullptr)
|
||||
{
|
||||
auto index = zipArchive->GetIndexFromPath(_path);
|
||||
if (index)
|
||||
{
|
||||
auto size = zipArchive->GetFileSize(*index);
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<IStream> ObjectAsset::GetStream() const
|
||||
{
|
||||
if (_zipPath.empty())
|
||||
{
|
||||
return std::make_unique<FileStream>(_path, FILE_MODE_OPEN);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto zipArchive = Zip::TryOpen(_zipPath, ZIP_ACCESS::READ);
|
||||
if (zipArchive != nullptr)
|
||||
{
|
||||
auto stream = zipArchive->GetFileStream(_path);
|
||||
if (stream != nullptr)
|
||||
{
|
||||
return std::make_unique<ZipStreamWrapper>(std::move(zipArchive), std::move(stream));
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifdef __WARN_SUGGEST_FINAL_METHODS__
|
||||
# pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "ImageTable.h"
|
||||
#include "StringTable.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
@ -200,6 +201,29 @@ enum class ObjectError : uint32_t
|
|||
UnexpectedEOF,
|
||||
};
|
||||
|
||||
class ObjectAsset
|
||||
{
|
||||
private:
|
||||
std::string _zipPath;
|
||||
std::string _path;
|
||||
|
||||
public:
|
||||
ObjectAsset() = default;
|
||||
ObjectAsset(std::string_view path)
|
||||
: _path(path)
|
||||
{
|
||||
}
|
||||
ObjectAsset(std::string_view zipPath, std::string_view path)
|
||||
: _zipPath(zipPath)
|
||||
, _path(path)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsAvailable() const;
|
||||
size_t GetSize() const;
|
||||
std::unique_ptr<OpenRCT2::IStream> GetStream() const;
|
||||
};
|
||||
|
||||
struct IReadObjectContext
|
||||
{
|
||||
virtual ~IReadObjectContext() = default;
|
||||
|
@ -208,6 +232,7 @@ struct IReadObjectContext
|
|||
virtual IObjectRepository& GetObjectRepository() abstract;
|
||||
virtual bool ShouldLoadImages() abstract;
|
||||
virtual std::vector<uint8_t> GetData(std::string_view path) abstract;
|
||||
virtual ObjectAsset GetAsset(std::string_view path) abstract;
|
||||
|
||||
virtual void LogWarning(ObjectError code, const utf8* text) abstract;
|
||||
virtual void LogError(ObjectError code, const utf8* text) abstract;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "FootpathItemObject.h"
|
||||
#include "FootpathObject.h"
|
||||
#include "LargeSceneryObject.h"
|
||||
#include "MusicObject.h"
|
||||
#include "Object.h"
|
||||
#include "ObjectLimits.h"
|
||||
#include "ObjectList.h"
|
||||
|
@ -44,6 +45,7 @@ struct IFileDataRetriever
|
|||
{
|
||||
virtual ~IFileDataRetriever() = default;
|
||||
virtual std::vector<uint8_t> GetData(std::string_view path) const abstract;
|
||||
virtual ObjectAsset GetAsset(std::string_view path) const abstract;
|
||||
};
|
||||
|
||||
class FileSystemDataRetriever : public IFileDataRetriever
|
||||
|
@ -59,19 +61,27 @@ public:
|
|||
|
||||
std::vector<uint8_t> GetData(std::string_view path) const override
|
||||
{
|
||||
auto absolutePath = Path::Combine(_basePath, std::string(path).c_str());
|
||||
auto absolutePath = Path::Combine(_basePath, path);
|
||||
return File::ReadAllBytes(absolutePath);
|
||||
}
|
||||
|
||||
ObjectAsset GetAsset(std::string_view path) const override
|
||||
{
|
||||
auto absolutePath = Path::Combine(_basePath, path);
|
||||
return ObjectAsset(absolutePath);
|
||||
}
|
||||
};
|
||||
|
||||
class ZipDataRetriever : public IFileDataRetriever
|
||||
{
|
||||
private:
|
||||
const std::string _path;
|
||||
const IZipArchive& _zipArchive;
|
||||
|
||||
public:
|
||||
ZipDataRetriever(const IZipArchive& zipArchive)
|
||||
: _zipArchive(zipArchive)
|
||||
ZipDataRetriever(std::string_view path, const IZipArchive& zipArchive)
|
||||
: _path(path)
|
||||
, _zipArchive(zipArchive)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -79,6 +89,11 @@ public:
|
|||
{
|
||||
return _zipArchive.GetFileData(path);
|
||||
}
|
||||
|
||||
ObjectAsset GetAsset(std::string_view path) const override
|
||||
{
|
||||
return ObjectAsset(_path, path);
|
||||
}
|
||||
};
|
||||
|
||||
class ReadObjectContext : public IReadObjectContext
|
||||
|
@ -137,6 +152,15 @@ public:
|
|||
return {};
|
||||
}
|
||||
|
||||
ObjectAsset GetAsset(std::string_view path) override
|
||||
{
|
||||
if (_fileDataRetriever != nullptr)
|
||||
{
|
||||
return _fileDataRetriever->GetAsset(path);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void LogWarning(ObjectError code, const utf8* text) override
|
||||
{
|
||||
_wasWarning = true;
|
||||
|
@ -314,6 +338,9 @@ namespace ObjectFactory
|
|||
case ObjectType::Station:
|
||||
result = std::make_unique<StationObject>(entry);
|
||||
break;
|
||||
case ObjectType::Music:
|
||||
result = std::make_unique<MusicObject>(entry);
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Invalid object type");
|
||||
}
|
||||
|
@ -348,6 +375,8 @@ namespace ObjectFactory
|
|||
return ObjectType::TerrainEdge;
|
||||
if (s == "station")
|
||||
return ObjectType::Station;
|
||||
if (s == "music")
|
||||
return ObjectType::Music;
|
||||
return ObjectType::None;
|
||||
}
|
||||
|
||||
|
@ -366,7 +395,7 @@ namespace ObjectFactory
|
|||
|
||||
if (jRoot.is_object())
|
||||
{
|
||||
auto fileDataRetriever = ZipDataRetriever(*archive);
|
||||
auto fileDataRetriever = ZipDataRetriever(path, *archive);
|
||||
return CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ constexpr const uint16_t MAX_SCENARIO_TEXT_OBJECTS = 1;
|
|||
constexpr const uint16_t MAX_TERRAIN_SURFACE_OBJECTS = 18;
|
||||
constexpr const uint16_t MAX_TERRAIN_EDGE_OBJECTS = 255;
|
||||
constexpr const uint16_t MAX_STATION_OBJECTS = 255;
|
||||
constexpr const uint16_t MAX_MUSIC_OBJECTS = 0;
|
||||
constexpr const uint16_t MAX_MUSIC_OBJECTS = 255;
|
||||
|
||||
// clang-format off
|
||||
constexpr const uint16_t OBJECT_ENTRY_COUNT =
|
||||
|
|
|
@ -102,36 +102,6 @@ public:
|
|||
return result;
|
||||
}
|
||||
|
||||
Object* RepositoryItemToObject(const ObjectRepositoryItem* ori)
|
||||
{
|
||||
Object* loadedObject = nullptr;
|
||||
if (ori != nullptr)
|
||||
{
|
||||
loadedObject = ori->LoadedObject;
|
||||
if (loadedObject == nullptr)
|
||||
{
|
||||
ObjectType objectType = ori->ObjectEntry.GetType();
|
||||
int32_t slot = FindSpareSlot(objectType);
|
||||
if (slot != -1)
|
||||
{
|
||||
auto object = GetOrLoadObject(ori);
|
||||
if (object != nullptr)
|
||||
{
|
||||
if (_loadedObjects.size() <= static_cast<size_t>(slot))
|
||||
{
|
||||
_loadedObjects.resize(slot + 1);
|
||||
}
|
||||
loadedObject = object.get();
|
||||
_loadedObjects[slot] = std::move(object);
|
||||
UpdateSceneryGroupIndexes();
|
||||
ResetTypeToRideEntryIndexMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return loadedObject;
|
||||
}
|
||||
|
||||
Object* LoadObject(std::string_view identifier) override
|
||||
{
|
||||
const ObjectRepositoryItem* ori = _objectRepository.FindObject(identifier);
|
||||
|
@ -284,6 +254,41 @@ public:
|
|||
LoadObject("rct2.station.pagoda");
|
||||
LoadObject("rct2.station.space");
|
||||
LoadObject("openrct2.station.noentrance");
|
||||
|
||||
// Music
|
||||
auto baseIndex = GetIndexFromTypeEntry(ObjectType::Music, 0);
|
||||
LoadObject(baseIndex + MUSIC_STYLE_DODGEMS_BEAT, "rct2.music.dodgems");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_FAIRGROUND_ORGAN, "rct2.music.fairground");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_ROMAN_FANFARE, "rct2.music.roman");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_ORIENTAL, "rct2.music.oriental");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_MARTIAN, "rct2.music.martian");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_JUNGLE_DRUMS, "rct2.music.jungle");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_EGYPTIAN, "rct2.music.egyptian");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_TOYLAND, "rct2.music.toyland");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_SPACE, "rct2.music.space");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_HORROR, "rct2.music.horror");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_TECHNO, "rct2.music.techno");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_GENTLE, "rct2.music.gentle");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_SUMMER, "rct2.music.summer");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_WATER, "rct2.music.water");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_WILD_WEST, "rct2.music.wildwest");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_JURASSIC, "rct2.music.jurassic");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_ROCK, "rct2.music.rock1");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_RAGTIME, "rct2.music.ragtime");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_FANTASY, "rct2.music.fantasy");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_ROCK_STYLE_2, "rct2.music.rock2");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_ICE, "rct2.music.ice");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_SNOW, "rct2.music.snow");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_CUSTOM_MUSIC_1, "rct2.music.custom1");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_CUSTOM_MUSIC_2, "rct2.music.custom2");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_MEDIEVAL, "rct2.music.medieval");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_URBAN, "rct2.music.urban");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_ORGAN, "rct2.music.organ");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_MECHANICAL, "rct2.music.mechanical");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_MODERN, "rct2.music.modern");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_PIRATES, "rct2.music.pirate");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_ROCK_STYLE_3, "rct2.music.rock3");
|
||||
LoadObject(baseIndex + MUSIC_STYLE_CANDY_STYLE, "rct2.music.candy");
|
||||
}
|
||||
|
||||
static rct_string_id GetObjectSourceGameString(const ObjectSourceGame sourceGame)
|
||||
|
@ -320,7 +325,54 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
int32_t FindSpareSlot(ObjectType objectType)
|
||||
Object* LoadObject(int32_t slot, std::string_view identifier)
|
||||
{
|
||||
const ObjectRepositoryItem* ori = _objectRepository.FindObject(identifier);
|
||||
return RepositoryItemToObject(ori, slot);
|
||||
}
|
||||
|
||||
Object* RepositoryItemToObject(const ObjectRepositoryItem* ori, std::optional<int32_t> slot = {})
|
||||
{
|
||||
Object* loadedObject = nullptr;
|
||||
if (ori != nullptr)
|
||||
{
|
||||
loadedObject = ori->LoadedObject;
|
||||
if (loadedObject == nullptr)
|
||||
{
|
||||
ObjectType objectType = ori->ObjectEntry.GetType();
|
||||
if (slot)
|
||||
{
|
||||
if (_loadedObjects.size() > static_cast<size_t>(*slot) && _loadedObjects[*slot] != nullptr)
|
||||
{
|
||||
// Slot already taken
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
slot = FindSpareSlot(objectType);
|
||||
}
|
||||
if (slot)
|
||||
{
|
||||
auto object = GetOrLoadObject(ori);
|
||||
if (object != nullptr)
|
||||
{
|
||||
if (_loadedObjects.size() <= static_cast<size_t>(*slot))
|
||||
{
|
||||
_loadedObjects.resize(*slot + 1);
|
||||
}
|
||||
loadedObject = object.get();
|
||||
_loadedObjects[*slot] = std::move(object);
|
||||
UpdateSceneryGroupIndexes();
|
||||
ResetTypeToRideEntryIndexMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return loadedObject;
|
||||
}
|
||||
|
||||
std::optional<int32_t> FindSpareSlot(ObjectType objectType)
|
||||
{
|
||||
size_t firstIndex = GetIndexFromTypeEntry(objectType, 0);
|
||||
size_t endIndex = firstIndex + object_entry_group_counts[EnumValue(objectType)];
|
||||
|
@ -336,7 +388,7 @@ private:
|
|||
return static_cast<int32_t>(i);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t GetLoadedObjectIndex(const Object* object)
|
||||
|
|
|
@ -76,7 +76,7 @@ class ObjectFileIndex final : public FileIndex<ObjectRepositoryItem>
|
|||
{
|
||||
private:
|
||||
static constexpr uint32_t MAGIC_NUMBER = 0x5844494F; // OIDX
|
||||
static constexpr uint16_t VERSION = 26;
|
||||
static constexpr uint16_t VERSION = 27;
|
||||
static constexpr auto PATTERN = "*.dat;*.pob;*.json;*.parkobj";
|
||||
|
||||
IObjectRepository& _objectRepository;
|
||||
|
|
|
@ -46,6 +46,8 @@ public:
|
|||
|
||||
void SetRepositoryItem(ObjectRepositoryItem* item) const override;
|
||||
|
||||
static uint8_t ParseRideType(const std::string& s);
|
||||
|
||||
private:
|
||||
void ReadLegacyVehicle(IReadObjectContext* context, OpenRCT2::IStream* stream, rct_ride_entry_vehicle* vehicle);
|
||||
|
||||
|
@ -59,7 +61,6 @@ private:
|
|||
static uint8_t CalculateNumHorizontalFrames(const rct_ride_entry_vehicle* vehicleEntry);
|
||||
|
||||
static bool IsRideTypeShopOrFacility(uint8_t rideType);
|
||||
static uint8_t ParseRideType(const std::string& s);
|
||||
static uint8_t ParseRideCategory(const std::string& s);
|
||||
static ShopItem ParseShopItem(const std::string& s);
|
||||
static colour_t ParseColour(const std::string& s);
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
/*****************************************************************************
|
||||
* Copyright (c) 2014-2020 OpenRCT2 developers
|
||||
*
|
||||
* For a complete list of all authors, please refer to contributors.md
|
||||
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||||
*
|
||||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||
*****************************************************************************/
|
||||
|
||||
#include "MusicList.h"
|
||||
|
||||
#include "../Context.h"
|
||||
#include "../audio/audio.h"
|
||||
#include "../common.h"
|
||||
|
||||
namespace OpenRCT2::Audio
|
||||
{
|
||||
#define MAKE_TUNEID_LIST(...) std::vector<uint8_t>({ __VA_ARGS__ })
|
||||
|
||||
// 0x009AEF28
|
||||
std::vector<uint8_t> gRideMusicStyleTuneIds[] = {
|
||||
MAKE_TUNEID_LIST(TUNE_DODGEMS_BEAT), // MUSIC_STYLE_DODGEMS_BEAT
|
||||
MAKE_TUNEID_LIST( // MUSIC_STYLE_FAIRGROUND_ORGAN
|
||||
TUNE_CHILDREN_OF_THE_REGIMENT, TUNE_SERENADE_OP_21, TUNE_IN_CONTINENTAL_MOOD, TUNE_WEDDING_JOURNEY,
|
||||
TUNE_TALES_FROM_THE_VIENNA_WOODS, TUNE_SLAVONIC_DANCE, TUNE_CSS_10, TUNE_DAS_ALPENHORN, TUNE_BELLA_BELLA_BIMBA,
|
||||
TUNE_THE_BLOND_SAILOR, TUNE_POET_AND_PEASANT_OVERTURE, TUNE_WALTZ_MEDLEY, TUNE_CSS_16),
|
||||
MAKE_TUNEID_LIST(TUNE_CAESARS_MARCH), // MUSIC_STYLE_ROMAN_FANFARE
|
||||
MAKE_TUNEID_LIST(TUNE_NINJAS_NOODLES), // MUSIC_STYLE_ORIENTAL
|
||||
MAKE_TUNEID_LIST(TUNE_INVADERS), // MUSIC_STYLE_MARTIAN
|
||||
MAKE_TUNEID_LIST(TUNE_JUNGLE_JUICE), // MUSIC_STYLE_JUNGLE_DRUMS
|
||||
MAKE_TUNEID_LIST(TUNE_PHARAOHS_TOMB), // MUSIC_STYLE_EGYPTIAN
|
||||
MAKE_TUNEID_LIST(TUNE_ETERNAL_TOYBOX), // MUSIC_STYLE_TOYLAND
|
||||
MAKE_TUNEID_LIST(TUNE_CIRCUS_SHOW), // MUSIC_STYLE_CIRCUS_SHOW
|
||||
MAKE_TUNEID_LIST(TUNE_VOYAGE_TO_ANDROMEDA), // MUSIC_STYLE_SPACE
|
||||
MAKE_TUNEID_LIST(TUNE_VAMPIRES_LAIR), // MUSIC_STYLE_HORROR
|
||||
MAKE_TUNEID_LIST(TUNE_BRIMBLES_BEAT), // MUSIC_STYLE_TECHNO
|
||||
MAKE_TUNEID_LIST(TUNE_DRIFTING_TO_HEAVEN), // MUSIC_STYLE_GENTLE
|
||||
MAKE_TUNEID_LIST(TUNE_MID_SUMMERS_HEAT), // MUSIC_STYLE_SUMMER
|
||||
MAKE_TUNEID_LIST(TUNE_ATLANTIS), // MUSIC_STYLE_WATER
|
||||
MAKE_TUNEID_LIST(TUNE_WILD_WEST_KID), // MUSIC_STYLE_WILD_WEST
|
||||
MAKE_TUNEID_LIST(TUNE_BLOCKBUSTER), // MUSIC_STYLE_JURASSIC
|
||||
MAKE_TUNEID_LIST(TUNE_AIRTIME_ROCK), // MUSIC_STYLE_ROCK
|
||||
MAKE_TUNEID_LIST(TUNE_SEARCHLIGHT_RAG), // MUSIC_STYLE_RAGTIME
|
||||
MAKE_TUNEID_LIST(TUNE_FLIGHT_OF_FANTASY), // MUSIC_STYLE_FANTASY
|
||||
MAKE_TUNEID_LIST(TUNE_BIG_ROCK), // MUSIC_STYLE_ROCK_STYLE_2
|
||||
MAKE_TUNEID_LIST(TUNE_HYPOTHERMIA), // MUSIC_STYLE_ICE
|
||||
MAKE_TUNEID_LIST(TUNE_LAST_SLEIGH_RIDE), // MUSIC_STYLE_SNOW
|
||||
MAKE_TUNEID_LIST(TUNE_CUSTOM_1), // MUSIC_STYLE_CUSTOM_MUSIC_1
|
||||
MAKE_TUNEID_LIST(TUNE_CUSTOM_2), // MUSIC_STYLE_CUSTOM_MUSIC_2
|
||||
MAKE_TUNEID_LIST(TUNE_PIPES_OF_GLENCAIRN), // MUSIC_STYLE_MEDIEVAL
|
||||
MAKE_TUNEID_LIST(TUNE_TRAFFIC_JAM), // MUSIC_STYLE_URBAN
|
||||
MAKE_TUNEID_LIST(TUNE_TOCCATA), // MUSIC_STYLE_ORGAN
|
||||
MAKE_TUNEID_LIST(TUNE_MANIC_MECHANIC), // MUSIC_STYLE_MECHANICAL
|
||||
MAKE_TUNEID_LIST(TUNE_TECHNO_TORTURE), // MUSIC_STYLE_MODERN
|
||||
MAKE_TUNEID_LIST(TUNE_WHAT_SHALL_WE_DO_WITH_THE_DRUNKEN_SAILOR), // MUSIC_STYLE_PIRATES
|
||||
MAKE_TUNEID_LIST(TUNE_SPACE_ROCK), // MUSIC_STYLE_ROCK_STYLE_3
|
||||
MAKE_TUNEID_LIST(TUNE_SWEAT_DREAMS), // MUSIC_STYLE_CANDY_STYLE
|
||||
};
|
||||
|
||||
#define INIT_MUSIC_INFO(path_id, offset) \
|
||||
{ \
|
||||
path_id, offset, 0 \
|
||||
}
|
||||
|
||||
// 0x009AF1C8
|
||||
RideMusicInfo gRideMusicInfoList[MaxDefaultMusic] = {
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS4, 1378),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS5, 1378),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS6, 1378),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS7, 1378),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS8, 1378),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS9, 1378),
|
||||
INIT_MUSIC_INFO(0, 1378), // Referred to the nearly empty CSS10.DAT file
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS11, 1378),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS12, 1378),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS13, 1378),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS14, 1378),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS15, 1378),
|
||||
INIT_MUSIC_INFO(0, 1378), // Referred to the nearly empty CSS16.DAT file
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS3, 689),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS17, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS18, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS19, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS20, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS21, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS22, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS23, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS24, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS25, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS26, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS27, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS28, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS29, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS30, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS31, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS32, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS33, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS34, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS35, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS36, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS37, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS38, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CUSTOM1, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CUSTOM2, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS39, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS40, 1378),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS41, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS42, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS43, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS44, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS45, 2756),
|
||||
INIT_MUSIC_INFO(PATH_ID_CSS46, 2756),
|
||||
};
|
||||
|
||||
} // namespace OpenRCT2::Audio
|
|
@ -1,71 +0,0 @@
|
|||
/*****************************************************************************
|
||||
* Copyright (c) 2014-2020 OpenRCT2 developers
|
||||
*
|
||||
* For a complete list of all authors, please refer to contributors.md
|
||||
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||||
*
|
||||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../audio/audio.h"
|
||||
#include "../common.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace OpenRCT2::Audio
|
||||
{
|
||||
enum
|
||||
{
|
||||
TUNE_CHILDREN_OF_THE_REGIMENT, // 00
|
||||
TUNE_SERENADE_OP_21, // 01
|
||||
TUNE_IN_CONTINENTAL_MOOD, // 02
|
||||
TUNE_WEDDING_JOURNEY, // 03
|
||||
TUNE_TALES_FROM_THE_VIENNA_WOODS, // 04
|
||||
TUNE_SLAVONIC_DANCE, // 05
|
||||
TUNE_CSS_10, // 06, empty
|
||||
TUNE_DAS_ALPENHORN, // 07
|
||||
TUNE_BELLA_BELLA_BIMBA, // 08
|
||||
TUNE_THE_BLOND_SAILOR, // 09
|
||||
TUNE_POET_AND_PEASANT_OVERTURE, // 10
|
||||
TUNE_WALTZ_MEDLEY, // 11
|
||||
TUNE_CSS_16, // 12, empty
|
||||
TUNE_DODGEMS_BEAT, // 13
|
||||
TUNE_RCT2_THEME_MUSIC, // 14
|
||||
TUNE_CAESARS_MARCH, // 15
|
||||
TUNE_NINJAS_NOODLES, // 16
|
||||
TUNE_INVADERS, // 17
|
||||
TUNE_JUNGLE_JUICE, // 18
|
||||
TUNE_PHARAOHS_TOMB, // 19
|
||||
TUNE_ETERNAL_TOYBOX, // 20
|
||||
TUNE_CIRCUS_SHOW, // 21
|
||||
TUNE_VOYAGE_TO_ANDROMEDA, // 22
|
||||
TUNE_VAMPIRES_LAIR, // 23
|
||||
TUNE_BRIMBLES_BEAT, // 24
|
||||
TUNE_DRIFTING_TO_HEAVEN, // 25
|
||||
TUNE_MID_SUMMERS_HEAT, // 26
|
||||
TUNE_ATLANTIS, // 27
|
||||
TUNE_WILD_WEST_KID, // 28
|
||||
TUNE_BLOCKBUSTER, // 29
|
||||
TUNE_AIRTIME_ROCK, // 30
|
||||
TUNE_SEARCHLIGHT_RAG, // 31
|
||||
TUNE_FLIGHT_OF_FANTASY, // 32
|
||||
TUNE_BIG_ROCK, // 33
|
||||
TUNE_HYPOTHERMIA, // 34
|
||||
TUNE_LAST_SLEIGH_RIDE, // 35
|
||||
TUNE_CUSTOM_1, // 36
|
||||
TUNE_CUSTOM_2, // 37
|
||||
TUNE_PIPES_OF_GLENCAIRN, // 38
|
||||
TUNE_TRAFFIC_JAM, // 39
|
||||
TUNE_TOCCATA, // 40
|
||||
TUNE_MANIC_MECHANIC, // 41
|
||||
TUNE_TECHNO_TORTURE, // 42
|
||||
TUNE_WHAT_SHALL_WE_DO_WITH_THE_DRUNKEN_SAILOR, // 43
|
||||
TUNE_SPACE_ROCK, // 44
|
||||
TUNE_SWEAT_DREAMS, // 45, (sic)
|
||||
};
|
||||
|
||||
extern std::vector<uint8_t> gRideMusicStyleTuneIds[];
|
||||
|
||||
} // namespace OpenRCT2::Audio
|
|
@ -33,6 +33,7 @@
|
|||
#include "../management/Marketing.h"
|
||||
#include "../management/NewsItem.h"
|
||||
#include "../network/network.h"
|
||||
#include "../object/MusicObject.h"
|
||||
#include "../object/ObjectList.h"
|
||||
#include "../object/ObjectManager.h"
|
||||
#include "../object/StationObject.h"
|
||||
|
@ -55,7 +56,7 @@
|
|||
#include "../world/Scenery.h"
|
||||
#include "../world/Sprite.h"
|
||||
#include "CableLift.h"
|
||||
#include "MusicList.h"
|
||||
#include "RideAudio.h"
|
||||
#include "RideData.h"
|
||||
#include "ShopItem.h"
|
||||
#include "Station.h"
|
||||
|
@ -2037,7 +2038,7 @@ void Ride::UpdateAll()
|
|||
for (auto& ride : GetRideManager())
|
||||
ride.Update();
|
||||
|
||||
ride_music_update_final();
|
||||
OpenRCT2::RideAudio::UpdateMusicChannels();
|
||||
}
|
||||
|
||||
std::unique_ptr<TrackDesign> Ride::SaveToTrackDesign() const
|
||||
|
@ -2898,10 +2899,14 @@ static void ride_music_update(Ride* ride)
|
|||
// Select random tune from available tunes for a music style (of course only merry-go-rounds have more than one tune)
|
||||
if (ride->music_tune_id == 255)
|
||||
{
|
||||
const auto& musicStyleTunes = OpenRCT2::Audio::gRideMusicStyleTuneIds[ride->music];
|
||||
auto numTunes = musicStyleTunes.size();
|
||||
ride->music_tune_id = musicStyleTunes[util_rand() % numTunes];
|
||||
ride->music_position = 0;
|
||||
auto& objManager = GetContext()->GetObjectManager();
|
||||
auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, ride->music));
|
||||
if (musicObj != nullptr)
|
||||
{
|
||||
auto numTracks = musicObj->GetTrackCount();
|
||||
ride->music_tune_id = static_cast<uint8_t>(util_rand() % numTracks);
|
||||
ride->music_position = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2918,7 +2923,7 @@ static void ride_music_update(Ride* ride)
|
|||
sampleRate += 22050;
|
||||
}
|
||||
|
||||
ride->music_position = ride_music_params_update(rideCoords, ride, sampleRate, ride->music_position, &ride->music_tune_id);
|
||||
OpenRCT2::RideAudio::UpdateMusicInstance(*ride, rideCoords, sampleRate);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
@ -3514,294 +3519,6 @@ void ride_set_map_tooltip(TileElement* tileElement)
|
|||
}
|
||||
}
|
||||
|
||||
static int32_t ride_music_params_update_label_51(
|
||||
uint32_t a1, uint8_t* tuneId, Ride* ride, int32_t v32, int32_t pan_x, uint16_t sampleRate)
|
||||
{
|
||||
if (a1 < OpenRCT2::Audio::gRideMusicInfoList[*tuneId].length)
|
||||
{
|
||||
OpenRCT2::Audio::RideMusicParams* ride_music_params = OpenRCT2::Audio::gRideMusicParamsListEnd;
|
||||
if (ride_music_params < &OpenRCT2::Audio::gRideMusicParamsList[std::size(OpenRCT2::Audio::gRideMusicParamsList)])
|
||||
{
|
||||
ride_music_params->ride_id = ride->id;
|
||||
ride_music_params->tune_id = *tuneId;
|
||||
ride_music_params->offset = a1;
|
||||
ride_music_params->volume = v32;
|
||||
ride_music_params->pan = pan_x;
|
||||
ride_music_params->frequency = sampleRate;
|
||||
OpenRCT2::Audio::gRideMusicParamsListEnd++;
|
||||
}
|
||||
|
||||
return a1;
|
||||
}
|
||||
else
|
||||
{
|
||||
*tuneId = 0xFF;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t ride_music_params_update_label_58(uint32_t position, uint8_t* tuneId)
|
||||
{
|
||||
OpenRCT2::Audio::RideMusicInfo* ride_music_info = &OpenRCT2::Audio::gRideMusicInfoList[*tuneId];
|
||||
position += ride_music_info->offset;
|
||||
if (position < ride_music_info->length)
|
||||
{
|
||||
return position;
|
||||
}
|
||||
else
|
||||
{
|
||||
*tuneId = 0xFF;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* rct2: 0x006BC3AC
|
||||
* Update ride music parameters
|
||||
* @param x (ax)
|
||||
* @param y (cx)
|
||||
* @param z (dx)
|
||||
* @param sampleRate (di)
|
||||
* @param rideIndex (bl)
|
||||
* @param position (ebp)
|
||||
* @param tuneId (bh)
|
||||
* @returns new position (ebp)
|
||||
*/
|
||||
int32_t ride_music_params_update(
|
||||
const CoordsXYZ& rideCoords, Ride* ride, uint16_t sampleRate, uint32_t position, uint8_t* tuneId)
|
||||
{
|
||||
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !OpenRCT2::Audio::gGameSoundsOff
|
||||
&& g_music_tracking_viewport != nullptr)
|
||||
{
|
||||
const ScreenCoordsXY rotatedCoords = translate_3d_to_2d_with_z(get_current_rotation(), rideCoords);
|
||||
rct_viewport* viewport = g_music_tracking_viewport;
|
||||
int16_t view_width = viewport->view_width;
|
||||
int16_t view_width2 = view_width * 2;
|
||||
int16_t view_x = viewport->viewPos.x - view_width2;
|
||||
int16_t view_y = viewport->viewPos.y - view_width;
|
||||
int16_t view_x2 = view_width2 + view_width2 + viewport->view_width + view_x;
|
||||
int16_t view_y2 = view_width + view_width + viewport->view_height + view_y;
|
||||
|
||||
if (view_x >= rotatedCoords.x || view_y >= rotatedCoords.y || view_x2 < rotatedCoords.x || view_y2 < rotatedCoords.y)
|
||||
{
|
||||
return ride_music_params_update_label_58(position, tuneId);
|
||||
}
|
||||
|
||||
int32_t x2 = viewport->pos.x + ((rotatedCoords.x - viewport->viewPos.x) / viewport->zoom);
|
||||
x2 *= 0x10000;
|
||||
uint16_t screenwidth = context_get_width();
|
||||
if (screenwidth < 64)
|
||||
{
|
||||
screenwidth = 64;
|
||||
}
|
||||
int32_t pan_x = ((x2 / screenwidth) - 0x8000) >> 4;
|
||||
|
||||
int32_t y2 = viewport->pos.y + ((rotatedCoords.y - viewport->viewPos.y) / viewport->zoom);
|
||||
y2 *= 0x10000;
|
||||
uint16_t screenheight = context_get_height();
|
||||
if (screenheight < 64)
|
||||
{
|
||||
screenheight = 64;
|
||||
}
|
||||
int32_t pan_y = ((y2 / screenheight) - 0x8000) >> 4;
|
||||
|
||||
uint8_t vol1 = 255;
|
||||
uint8_t vol2 = 255;
|
||||
int32_t panx2 = pan_x;
|
||||
int32_t pany2 = pan_y;
|
||||
if (pany2 < 0)
|
||||
{
|
||||
pany2 = -pany2;
|
||||
}
|
||||
if (pany2 > 6143)
|
||||
{
|
||||
pany2 = 6143;
|
||||
}
|
||||
pany2 -= 2048;
|
||||
if (pany2 > 0)
|
||||
{
|
||||
pany2 = -((pany2 / 4) - 1024) / 4;
|
||||
vol1 = static_cast<uint8_t>(pany2);
|
||||
if (pany2 >= 256)
|
||||
{
|
||||
vol1 = 255;
|
||||
}
|
||||
}
|
||||
|
||||
if (panx2 < 0)
|
||||
{
|
||||
panx2 = -panx2;
|
||||
}
|
||||
if (panx2 > 6143)
|
||||
{
|
||||
panx2 = 6143;
|
||||
}
|
||||
panx2 -= 2048;
|
||||
if (panx2 > 0)
|
||||
{
|
||||
panx2 = -((panx2 / 4) - 1024) / 4;
|
||||
vol2 = static_cast<uint8_t>(panx2);
|
||||
if (panx2 >= 256)
|
||||
{
|
||||
vol2 = 255;
|
||||
}
|
||||
}
|
||||
if (vol1 >= vol2)
|
||||
{
|
||||
vol1 = vol2;
|
||||
}
|
||||
if (vol1 < OpenRCT2::Audio::gVolumeAdjustZoom * 3)
|
||||
{
|
||||
vol1 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
vol1 = vol1 - (OpenRCT2::Audio::gVolumeAdjustZoom * 3);
|
||||
}
|
||||
int32_t v32 = -((static_cast<uint8_t>(-vol1 - 1) * static_cast<uint8_t>(-vol1 - 1)) / 16) - 700;
|
||||
if (vol1 && v32 >= -4000)
|
||||
{
|
||||
if (pan_x > 10000)
|
||||
{
|
||||
pan_x = 10000;
|
||||
}
|
||||
if (pan_x < -10000)
|
||||
{
|
||||
pan_x = -10000;
|
||||
}
|
||||
OpenRCT2::Audio::RideMusic* ride_music = &OpenRCT2::Audio::gRideMusicList[0];
|
||||
int32_t channel = 0;
|
||||
while (ride_music->ride_id != ride->id || ride_music->tune_id != *tuneId)
|
||||
{
|
||||
ride_music++;
|
||||
channel++;
|
||||
if (static_cast<size_t>(channel) >= OpenRCT2::Audio::MaxRideMusic)
|
||||
{
|
||||
OpenRCT2::Audio::RideMusicInfo* ride_music_info = &OpenRCT2::Audio::gRideMusicInfoList[*tuneId];
|
||||
const uint32_t offset = position + ride_music_info->offset;
|
||||
return ride_music_params_update_label_51(offset, tuneId, ride, v32, pan_x, sampleRate);
|
||||
}
|
||||
}
|
||||
int32_t playing = Mixer_Channel_IsPlaying(OpenRCT2::Audio::gRideMusicList[channel].sound_channel);
|
||||
if (!playing)
|
||||
{
|
||||
*tuneId = 0xFF;
|
||||
return 0;
|
||||
}
|
||||
const uint32_t offset = static_cast<uint32_t>(
|
||||
Mixer_Channel_GetOffset(OpenRCT2::Audio::gRideMusicList[channel].sound_channel));
|
||||
return ride_music_params_update_label_51(offset, tuneId, ride, v32, pan_x, sampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ride_music_params_update_label_58(position, tuneId);
|
||||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play/update ride music based on structs updated in 0x006BC3AC
|
||||
* rct2: 0x006BC6D8
|
||||
*/
|
||||
void ride_music_update_final()
|
||||
{
|
||||
if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) != 0 || (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) != 0)
|
||||
return;
|
||||
|
||||
// TODO Allow circus music (CSS24) to play if ride music is disabled (that should be sound)
|
||||
if (OpenRCT2::Audio::gGameSoundsOff || !gConfigSound.ride_music_enabled)
|
||||
return;
|
||||
|
||||
// Stop currently playing music that is not in music params list or not playing?
|
||||
for (auto& rideMusic : OpenRCT2::Audio::gRideMusicList)
|
||||
{
|
||||
if (rideMusic.ride_id != RIDE_ID_NULL)
|
||||
{
|
||||
OpenRCT2::Audio::RideMusicParams* rideMusicParams = &OpenRCT2::Audio::gRideMusicParamsList[0];
|
||||
int32_t isPlaying = 0;
|
||||
while (rideMusicParams < OpenRCT2::Audio::gRideMusicParamsListEnd && !isPlaying)
|
||||
{
|
||||
if (rideMusicParams->ride_id == rideMusic.ride_id && rideMusicParams->tune_id == rideMusic.tune_id)
|
||||
{
|
||||
isPlaying = Mixer_Channel_IsPlaying(rideMusic.sound_channel);
|
||||
break;
|
||||
}
|
||||
rideMusicParams++;
|
||||
}
|
||||
if (!isPlaying)
|
||||
{
|
||||
Mixer_Stop_Channel(rideMusic.sound_channel);
|
||||
rideMusic.ride_id = RIDE_ID_NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t freeChannelIndex = 0;
|
||||
for (auto* rideMusicParams = &OpenRCT2::Audio::gRideMusicParamsList[0];
|
||||
rideMusicParams < OpenRCT2::Audio::gRideMusicParamsListEnd; rideMusicParams++)
|
||||
{
|
||||
if (rideMusicParams->ride_id != RIDE_ID_NULL)
|
||||
{
|
||||
auto* rideMusic = &OpenRCT2::Audio::gRideMusicList[0];
|
||||
int32_t channelIndex = 0;
|
||||
// Look for existing entry, if not found start playing the sound, otherwise update parameters.
|
||||
while (rideMusicParams->ride_id != rideMusic->ride_id || rideMusicParams->tune_id != rideMusic->tune_id)
|
||||
{
|
||||
if (rideMusic->ride_id == RIDE_ID_NULL)
|
||||
{
|
||||
freeChannelIndex = channelIndex;
|
||||
}
|
||||
rideMusic++;
|
||||
channelIndex++;
|
||||
if (static_cast<size_t>(channelIndex) >= OpenRCT2::Audio::MaxRideMusic)
|
||||
{
|
||||
auto* ride_music_info = &OpenRCT2::Audio::gRideMusicInfoList[rideMusicParams->tune_id];
|
||||
auto* ride_music_3 = &OpenRCT2::Audio::gRideMusicList[freeChannelIndex];
|
||||
ride_music_3->sound_channel = Mixer_Play_Music(ride_music_info->path_id, MIXER_LOOP_NONE, true);
|
||||
if (ride_music_3->sound_channel)
|
||||
{
|
||||
ride_music_3->volume = rideMusicParams->volume;
|
||||
ride_music_3->pan = rideMusicParams->pan;
|
||||
ride_music_3->frequency = rideMusicParams->frequency;
|
||||
ride_music_3->ride_id = rideMusicParams->ride_id;
|
||||
ride_music_3->tune_id = rideMusicParams->tune_id;
|
||||
Mixer_Channel_Volume(ride_music_3->sound_channel, DStoMixerVolume(ride_music_3->volume));
|
||||
Mixer_Channel_Pan(ride_music_3->sound_channel, DStoMixerPan(ride_music_3->pan));
|
||||
Mixer_Channel_Rate(ride_music_3->sound_channel, DStoMixerRate(ride_music_3->frequency));
|
||||
int32_t offset = std::max(0, rideMusicParams->offset - 10000);
|
||||
Mixer_Channel_SetOffset(ride_music_3->sound_channel, offset);
|
||||
|
||||
// Move circus music to the sound mixer group
|
||||
if (ride_music_info->path_id == PATH_ID_CSS24)
|
||||
{
|
||||
Mixer_Channel_SetGroup(ride_music_3->sound_channel, Audio::MixerGroup::Sound);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (rideMusicParams->volume != rideMusic->volume)
|
||||
{
|
||||
rideMusic->volume = rideMusicParams->volume;
|
||||
Mixer_Channel_Volume(rideMusic->sound_channel, DStoMixerVolume(rideMusic->volume));
|
||||
}
|
||||
if (rideMusicParams->pan != rideMusic->pan)
|
||||
{
|
||||
rideMusic->pan = rideMusicParams->pan;
|
||||
Mixer_Channel_Pan(rideMusic->sound_channel, DStoMixerPan(rideMusic->pan));
|
||||
}
|
||||
if (rideMusicParams->frequency != rideMusic->frequency)
|
||||
{
|
||||
rideMusic->frequency = rideMusicParams->frequency;
|
||||
Mixer_Channel_Rate(rideMusic->sound_channel, DStoMixerRate(rideMusic->frequency));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
money32 set_operating_setting(ride_id_t rideId, RideSetSetting setting, uint8_t value)
|
||||
|
|
|
@ -1137,9 +1137,6 @@ void ride_construction_invalidate_current_track();
|
|||
std::optional<CoordsXYZ> sub_6C683D(
|
||||
const CoordsXYZD& location, track_type_t type, uint16_t extra_params, TileElement** output_element, uint16_t flags);
|
||||
void ride_set_map_tooltip(TileElement* tileElement);
|
||||
int32_t ride_music_params_update(
|
||||
const CoordsXYZ& rideCoords, Ride* ride, uint16_t sampleRate, uint32_t position, uint8_t* tuneId);
|
||||
void ride_music_update_final();
|
||||
void ride_prepare_breakdown(Ride* ride, int32_t breakdownReason);
|
||||
TileElement* ride_get_station_start_track_element(Ride* ride, StationIndex stationIndex);
|
||||
TileElement* ride_get_station_exit_element(const CoordsXYZ& elementPos);
|
||||
|
|
|
@ -0,0 +1,427 @@
|
|||
/*****************************************************************************
|
||||
* Copyright (c) 2014-2020 OpenRCT2 developers
|
||||
*
|
||||
* For a complete list of all authors, please refer to contributors.md
|
||||
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||||
*
|
||||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||
*****************************************************************************/
|
||||
|
||||
#include "RideAudio.h"
|
||||
|
||||
#include "../Context.h"
|
||||
#include "../OpenRCT2.h"
|
||||
#include "../audio/AudioMixer.h"
|
||||
#include "../audio/audio.h"
|
||||
#include "../config/Config.h"
|
||||
#include "../object/MusicObject.h"
|
||||
#include "../object/ObjectManager.h"
|
||||
#include "Ride.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
using namespace OpenRCT2;
|
||||
using namespace OpenRCT2::Audio;
|
||||
|
||||
namespace OpenRCT2::RideAudio
|
||||
{
|
||||
constexpr uint8_t TUNE_ID_NULL = 0xFF;
|
||||
constexpr size_t MAX_RIDE_MUSIC_CHANNELS = 32;
|
||||
|
||||
/**
|
||||
* Represents a particular instance of ride music that can be heard in a viewport.
|
||||
* These are created each frame via enumerating each ride / viewport.
|
||||
*/
|
||||
struct ViewportRideMusicInstance
|
||||
{
|
||||
ride_id_t RideId;
|
||||
uint8_t TrackIndex{};
|
||||
|
||||
size_t Offset{};
|
||||
int16_t Volume{};
|
||||
int16_t Pan{};
|
||||
uint16_t Frequency{};
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents an audio channel to play a particular ride's music track.
|
||||
*/
|
||||
struct RideMusicChannel
|
||||
{
|
||||
ride_id_t RideId{};
|
||||
uint8_t TrackIndex{};
|
||||
|
||||
size_t Offset{};
|
||||
int16_t Volume{};
|
||||
int16_t Pan{};
|
||||
uint16_t Frequency{};
|
||||
|
||||
void* Channel{};
|
||||
|
||||
RideMusicChannel(const ViewportRideMusicInstance& instance, void* channel)
|
||||
{
|
||||
RideId = instance.RideId;
|
||||
TrackIndex = instance.TrackIndex;
|
||||
|
||||
Offset = std::max<size_t>(0, instance.Offset - 10000);
|
||||
Volume = instance.Volume;
|
||||
Pan = instance.Pan;
|
||||
Frequency = instance.Frequency;
|
||||
|
||||
Channel = channel;
|
||||
|
||||
Mixer_Channel_SetOffset(channel, Offset);
|
||||
Mixer_Channel_Volume(channel, DStoMixerVolume(Volume));
|
||||
Mixer_Channel_Pan(channel, DStoMixerPan(Pan));
|
||||
Mixer_Channel_Rate(channel, DStoMixerRate(Frequency));
|
||||
}
|
||||
|
||||
RideMusicChannel(const RideMusicChannel&) = delete;
|
||||
|
||||
RideMusicChannel(RideMusicChannel&& src) noexcept
|
||||
{
|
||||
*this = std::move(src);
|
||||
}
|
||||
|
||||
RideMusicChannel& operator=(RideMusicChannel&& src) noexcept
|
||||
{
|
||||
RideId = src.RideId;
|
||||
TrackIndex = src.TrackIndex;
|
||||
|
||||
Offset = src.Offset;
|
||||
Volume = src.Volume;
|
||||
Pan = src.Pan;
|
||||
Frequency = src.Frequency;
|
||||
|
||||
if (Channel != nullptr)
|
||||
{
|
||||
Mixer_Stop_Channel(Channel);
|
||||
}
|
||||
Channel = src.Channel;
|
||||
src.Channel = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
~RideMusicChannel()
|
||||
{
|
||||
if (Channel != nullptr)
|
||||
{
|
||||
Mixer_Stop_Channel(Channel);
|
||||
Channel = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPlaying() const
|
||||
{
|
||||
if (Channel != nullptr)
|
||||
{
|
||||
return Mixer_Channel_IsPlaying(Channel);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t GetOffset() const
|
||||
{
|
||||
if (Channel != nullptr)
|
||||
{
|
||||
return Mixer_Channel_GetOffset(Channel);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Update(const ViewportRideMusicInstance& instance)
|
||||
{
|
||||
if (Volume != instance.Volume)
|
||||
{
|
||||
Volume = instance.Volume;
|
||||
if (Channel != nullptr)
|
||||
{
|
||||
Mixer_Channel_Volume(Channel, DStoMixerVolume(Volume));
|
||||
}
|
||||
}
|
||||
if (Pan != instance.Pan)
|
||||
{
|
||||
Pan = instance.Pan;
|
||||
if (Channel != nullptr)
|
||||
{
|
||||
Mixer_Channel_Pan(Channel, DStoMixerPan(Pan));
|
||||
}
|
||||
}
|
||||
if (Frequency != instance.Frequency)
|
||||
{
|
||||
Frequency = instance.Frequency;
|
||||
if (Channel != nullptr)
|
||||
{
|
||||
Mixer_Channel_Rate(Channel, DStoMixerRate(Frequency));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<ViewportRideMusicInstance> _musicInstances;
|
||||
static std::vector<RideMusicChannel> _musicChannels;
|
||||
|
||||
void StopAllChannels()
|
||||
{
|
||||
_musicChannels.clear();
|
||||
}
|
||||
|
||||
void ClearAllViewportInstances()
|
||||
{
|
||||
_musicInstances.clear();
|
||||
}
|
||||
|
||||
static void StartRideMusicChannel(const ViewportRideMusicInstance& instance)
|
||||
{
|
||||
// Create new music channel
|
||||
auto ride = get_ride(instance.RideId);
|
||||
if (ride->type == RIDE_TYPE_CIRCUS)
|
||||
{
|
||||
auto channel = Mixer_Play_Music(PATH_ID_CSS24, MIXER_LOOP_NONE, true);
|
||||
if (channel != nullptr)
|
||||
{
|
||||
// Move circus music to the sound mixer group
|
||||
Mixer_Channel_SetGroup(channel, Audio::MixerGroup::Sound);
|
||||
|
||||
_musicChannels.emplace_back(instance, channel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& objManager = GetContext()->GetObjectManager();
|
||||
auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, ride->music));
|
||||
if (musicObj != nullptr)
|
||||
{
|
||||
auto track = musicObj->GetTrack(instance.TrackIndex);
|
||||
if (track != nullptr)
|
||||
{
|
||||
auto stream = track->Asset.GetStream();
|
||||
auto channel = Mixer_Play_Music(std::move(stream), MIXER_LOOP_NONE);
|
||||
if (channel != nullptr)
|
||||
{
|
||||
_musicChannels.emplace_back(instance, channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void StopInactiveRideMusicChannels()
|
||||
{
|
||||
_musicChannels.erase(
|
||||
std::remove_if(
|
||||
_musicChannels.begin(), _musicChannels.end(),
|
||||
[](const auto& channel) {
|
||||
auto found = std::any_of(_musicInstances.begin(), _musicInstances.end(), [&channel](const auto& instance) {
|
||||
return instance.RideId == channel.RideId && instance.TrackIndex == channel.TrackIndex;
|
||||
});
|
||||
if (!found || !channel.IsPlaying())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
_musicChannels.end());
|
||||
}
|
||||
|
||||
static void UpdateRideMusicChannelForMusicParams(const ViewportRideMusicInstance& instance)
|
||||
{
|
||||
// Find existing music channel
|
||||
auto foundChannel = std::find_if(
|
||||
_musicChannels.begin(), _musicChannels.end(), [&instance](const RideMusicChannel& channel) {
|
||||
return channel.RideId == instance.RideId && channel.TrackIndex == instance.TrackIndex;
|
||||
});
|
||||
|
||||
if (foundChannel != _musicChannels.end())
|
||||
{
|
||||
foundChannel->Update(instance);
|
||||
}
|
||||
else if (_musicChannels.size() < MAX_RIDE_MUSIC_CHANNELS)
|
||||
{
|
||||
StartRideMusicChannel(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start, update and stop audio channels for each ride music instance that can be heard across all viewports.
|
||||
*/
|
||||
void UpdateMusicChannels()
|
||||
{
|
||||
if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) != 0 || (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) != 0)
|
||||
return;
|
||||
|
||||
// TODO Allow circus music (CSS24) to play if ride music is disabled (that should be sound)
|
||||
if (gGameSoundsOff || !gConfigSound.ride_music_enabled)
|
||||
return;
|
||||
|
||||
StopInactiveRideMusicChannels();
|
||||
for (const auto& instance : _musicInstances)
|
||||
{
|
||||
UpdateRideMusicChannelForMusicParams(instance);
|
||||
}
|
||||
}
|
||||
|
||||
static std::pair<size_t, size_t> RideMusicGetTrackOffsetLength(const Ride& ride)
|
||||
{
|
||||
if (ride.type == RIDE_TYPE_CIRCUS)
|
||||
{
|
||||
return { 1378, 12427456 };
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& objManager = GetContext()->GetObjectManager();
|
||||
auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, ride.music));
|
||||
if (musicObj != nullptr)
|
||||
{
|
||||
auto numTracks = musicObj->GetTrackCount();
|
||||
if (ride.music_tune_id < numTracks)
|
||||
{
|
||||
auto track = musicObj->GetTrack(ride.music_tune_id);
|
||||
return { track->BytesPerTick, track->Size };
|
||||
}
|
||||
}
|
||||
}
|
||||
return { 0, 0 };
|
||||
}
|
||||
|
||||
static void RideUpdateMusicPosition(Ride& ride)
|
||||
{
|
||||
auto [trackOffset, trackLength] = RideMusicGetTrackOffsetLength(ride);
|
||||
auto position = ride.music_position + trackOffset;
|
||||
if (position < trackLength)
|
||||
{
|
||||
ride.music_position = position;
|
||||
}
|
||||
else
|
||||
{
|
||||
ride.music_tune_id = TUNE_ID_NULL;
|
||||
ride.music_position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void RideUpdateMusicPosition(
|
||||
Ride& ride, size_t offset, size_t length, int16_t volume, int16_t pan, uint16_t sampleRate)
|
||||
{
|
||||
if (offset < length)
|
||||
{
|
||||
if (_musicInstances.size() < MAX_RIDE_MUSIC_CHANNELS)
|
||||
{
|
||||
auto& instance = _musicInstances.emplace_back();
|
||||
instance.RideId = ride.id;
|
||||
instance.TrackIndex = ride.music_tune_id;
|
||||
instance.Offset = offset;
|
||||
instance.Volume = volume;
|
||||
instance.Pan = pan;
|
||||
instance.Frequency = sampleRate;
|
||||
}
|
||||
ride.music_position = static_cast<uint32_t>(offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
ride.music_tune_id = TUNE_ID_NULL;
|
||||
ride.music_position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void RideUpdateMusicPosition(Ride& ride, int16_t volume, int16_t pan, uint16_t sampleRate)
|
||||
{
|
||||
auto foundChannel = std::find_if(_musicChannels.begin(), _musicChannels.end(), [&ride](const auto& channel) {
|
||||
return channel.RideId == ride.id && channel.TrackIndex == ride.music_tune_id;
|
||||
});
|
||||
|
||||
auto [trackOffset, trackLength] = RideMusicGetTrackOffsetLength(ride);
|
||||
if (foundChannel != _musicChannels.end())
|
||||
{
|
||||
if (foundChannel->IsPlaying())
|
||||
{
|
||||
// Since we have a real music channel, use the offset from that
|
||||
auto newOffset = foundChannel->GetOffset();
|
||||
RideUpdateMusicPosition(ride, newOffset, trackLength, volume, pan, sampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We had a real music channel, but it isn't playing anymore, so stop the track
|
||||
ride.music_position = 0;
|
||||
ride.music_tune_id = TUNE_ID_NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We do not have a real music channel, so simulate the playing of the music track
|
||||
auto newOffset = ride.music_position + trackOffset;
|
||||
RideUpdateMusicPosition(ride, newOffset, trackLength, volume, pan, sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t CalculateVolume(int32_t pan)
|
||||
{
|
||||
uint8_t result = 255;
|
||||
int32_t v = std::min(std::abs(pan), 6143) - 2048;
|
||||
if (v > 0)
|
||||
{
|
||||
v = -((v / 4) - 1024) / 4;
|
||||
result = static_cast<uint8_t>(std::clamp(v, 0, 255));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an instance of audible ride music for this frame at the given coordinates.
|
||||
*/
|
||||
void UpdateMusicInstance(Ride& ride, const CoordsXYZ& rideCoords, uint16_t sampleRate)
|
||||
{
|
||||
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gGameSoundsOff && g_music_tracking_viewport != nullptr)
|
||||
{
|
||||
auto rotatedCoords = translate_3d_to_2d_with_z(get_current_rotation(), rideCoords);
|
||||
auto viewport = g_music_tracking_viewport;
|
||||
auto viewWidth = viewport->view_width;
|
||||
auto viewWidth2 = viewWidth * 2;
|
||||
auto viewX = viewport->viewPos.x - viewWidth2;
|
||||
auto viewY = viewport->viewPos.y - viewWidth;
|
||||
auto viewX2 = viewWidth2 + viewWidth2 + viewport->view_width + viewX;
|
||||
auto viewY2 = viewWidth + viewWidth + viewport->view_height + viewY;
|
||||
if (viewX >= rotatedCoords.x || viewY >= rotatedCoords.y || viewX2 < rotatedCoords.x || viewY2 < rotatedCoords.y)
|
||||
{
|
||||
RideUpdateMusicPosition(ride);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto x2 = (viewport->pos.x + ((rotatedCoords.x - viewport->viewPos.x) / viewport->zoom)) * 0x10000;
|
||||
auto screenWidth = std::max(context_get_width(), 64);
|
||||
auto panX = ((x2 / screenWidth) - 0x8000) >> 4;
|
||||
|
||||
auto y2 = (viewport->pos.y + ((rotatedCoords.y - viewport->viewPos.y) / viewport->zoom)) * 0x10000;
|
||||
auto screenHeight = std::max(context_get_height(), 64);
|
||||
auto panY = ((y2 / screenHeight) - 0x8000) >> 4;
|
||||
|
||||
auto volX = CalculateVolume(panX);
|
||||
auto volY = CalculateVolume(panY);
|
||||
auto volXY = std::min(volX, volY);
|
||||
if (volXY < gVolumeAdjustZoom * 3)
|
||||
{
|
||||
volXY = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
volXY = volXY - (gVolumeAdjustZoom * 3);
|
||||
}
|
||||
|
||||
int16_t newVolume = -((static_cast<uint8_t>(-volXY - 1) * static_cast<uint8_t>(-volXY - 1)) / 16) - 700;
|
||||
if (volXY != 0 && newVolume >= -4000)
|
||||
{
|
||||
auto newPan = std::clamp(panX, -10000, 10000);
|
||||
RideUpdateMusicPosition(ride, newVolume, newPan, sampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
RideUpdateMusicPosition(ride);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace OpenRCT2::RideAudio
|
|
@ -0,0 +1,23 @@
|
|||
/*****************************************************************************
|
||||
* Copyright (c) 2014-2021 OpenRCT2 developers
|
||||
*
|
||||
* For a complete list of all authors, please refer to contributors.md
|
||||
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||||
*
|
||||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct CoordsXYZ;
|
||||
struct Ride;
|
||||
|
||||
namespace OpenRCT2::RideAudio
|
||||
{
|
||||
void ClearAllViewportInstances();
|
||||
void StopAllChannels();
|
||||
void UpdateMusicChannels();
|
||||
void UpdateMusicInstance(Ride& ride, const CoordsXYZ& rideCoords, uint16_t sampleRate);
|
||||
} // namespace OpenRCT2::RideAudio
|
Loading…
Reference in New Issue