diff --git a/Sources/Jazz2/LevelHandler.cpp b/Sources/Jazz2/LevelHandler.cpp index e44a1802..937ba114 100644 --- a/Sources/Jazz2/LevelHandler.cpp +++ b/Sources/Jazz2/LevelHandler.cpp @@ -48,6 +48,56 @@ namespace Jazz2 using namespace Jazz2::Resources; +#if defined(WITH_AUDIO) + class AudioBufferPlayerForSplitscreen : public AudioBufferPlayer + { + DEATH_RUNTIME_OBJECT(AudioBufferPlayer); + + public: + explicit AudioBufferPlayerForSplitscreen(AudioBuffer* audioBuffer); + + void setPosition(const Vector3f& position) override; + + void updatePosition(ArrayView> viewports); + }; + + AudioBufferPlayerForSplitscreen::AudioBufferPlayerForSplitscreen(AudioBuffer* audioBuffer) + : AudioBufferPlayer(audioBuffer) + { + } + + void AudioBufferPlayerForSplitscreen::setPosition(const Vector3f& position) + { + position_ = position; + if (state_ == PlayerState::Playing && (GetFlags(PlayerFlags::SourceRelative) || GetFlags(PlayerFlags::As2D))) { + IAudioDevice& device = theServiceLocator().GetAudioDevice(); + setPositionInternal(getAdjustedPosition(device, position_, GetFlags(PlayerFlags::SourceRelative), GetFlags(PlayerFlags::As2D))); + } + } + + void AudioBufferPlayerForSplitscreen::updatePosition(ArrayView> viewports) + { + if (state_ != PlayerState::Playing || GetFlags(PlayerFlags::SourceRelative) || GetFlags(PlayerFlags::As2D)) { + return; + } + + std::size_t minIndex = 0; + float minDistance = FLT_MAX; + + for (std::size_t i = 0; i < viewports.size(); i++) { + float distance = (position_.ToVector2() - viewports[i]->_cameraPos).SqrLength(); + if (minDistance > distance) { + minDistance = distance; + minIndex = i; + } + } + + IAudioDevice& device = theServiceLocator().GetAudioDevice(); + Vector3f relativePos = (position_ - Vector3f(viewports[minIndex]->_cameraPos, 0.0f)); + setPositionInternal(getAdjustedPosition(device, relativePos, false, false)); + } +#endif + LevelHandler::LevelHandler(IRootController* root) : _root(root), _eventSpawner(this), _difficulty(GameDifficulty::Default), _isReforged(false), _cheatsUsed(false), _checkpointCreated(false), _cheatsBufferLength(0), _nextLevelType(ExitType::None), _nextLevelTime(0.0f), _elapsedFrames(0.0f), _checkpointFrames(0.0f), @@ -413,6 +463,25 @@ namespace Jazz2 viewport->UpdateCamera(timeMult); } +#if defined(WITH_AUDIO) + if (!_assignedViewports.empty()) { + // Update audio listener position + IAudioDevice& audioDevice = theServiceLocator().GetAudioDevice(); + if (_assignedViewports.size() == 1) { + audioDevice.updateListener(Vector3f(_assignedViewports[0]->_cameraPos, 0.0f), + Vector3f(_assignedViewports[0]->_targetPlayer->GetSpeed(), 0.0f)); + } else { + audioDevice.updateListener(Vector3f::Zero, Vector3f::Zero); + + for (auto& player : _playingSounds) { + if (auto* splitscreenPlayer = runtime_cast(player)) { + splitscreenPlayer->updatePosition(_assignedViewports); + } + } + } + } +#endif + _elapsedFrames += timeMult; } @@ -588,14 +657,18 @@ namespace Jazz2 if (_cheatsBuffer[2] == (char)KeySym::K) { _cheatsBufferLength = 0; _cheatsUsed = true; - _players[0]->TakeDamage(INT32_MAX); + for (auto* player : _players) { + player->TakeDamage(INT32_MAX); + } } break; case 5: if (_cheatsBuffer[2] == (char)KeySym::G && _cheatsBuffer[3] == (char)KeySym::O && _cheatsBuffer[4] == (char)KeySym::D) { _cheatsBufferLength = 0; _cheatsUsed = true; - _players[0]->SetInvulnerability(36000.0f, true); + for (auto* player : _players) { + player->SetInvulnerability(36000.0f, true); + } } break; case 6: @@ -607,33 +680,44 @@ namespace Jazz2 (_cheatsBuffer[2] == (char)KeySym::A && _cheatsBuffer[3] == (char)KeySym::M && _cheatsBuffer[4] == (char)KeySym::M && _cheatsBuffer[5] == (char)KeySym::O)) { _cheatsBufferLength = 0; _cheatsUsed = true; - for (int32_t i = 0; i < (int32_t)WeaponType::Count; i++) { - _players[0]->AddAmmo((WeaponType)i, 99); + for (auto* player : _players) { + for (std::int32_t i = 0; i < (std::int32_t)WeaponType::Count; i++) { + player->AddAmmo((WeaponType)i, 99); + } } } else if (_cheatsBuffer[2] == (char)KeySym::R && _cheatsBuffer[3] == (char)KeySym::U && _cheatsBuffer[4] == (char)KeySym::S && _cheatsBuffer[5] == (char)KeySym::H) { _cheatsBufferLength = 0; _cheatsUsed = true; - _players[0]->ActivateSugarRush(1300.0f); + for (auto* player : _players) { + player->ActivateSugarRush(1300.0f); + } } else if (_cheatsBuffer[2] == (char)KeySym::G && _cheatsBuffer[3] == (char)KeySym::E && _cheatsBuffer[4] == (char)KeySym::M && _cheatsBuffer[5] == (char)KeySym::S) { _cheatsBufferLength = 0; _cheatsUsed = true; - _players[0]->AddGems(5); + for (auto* player : _players) { + player->AddGems(5); + } } else if (_cheatsBuffer[2] == (char)KeySym::B && _cheatsBuffer[3] == (char)KeySym::I && _cheatsBuffer[4] == (char)KeySym::R && _cheatsBuffer[5] == (char)KeySym::D) { _cheatsBufferLength = 0; _cheatsUsed = true; - _players[0]->SpawnBird(0, _players[0]->GetPos()); + for (auto* player : _players) { + player->SpawnBird(0, player->GetPos()); + } } break; case 7: if (_cheatsBuffer[2] == (char)KeySym::P && _cheatsBuffer[3] == (char)KeySym::O && _cheatsBuffer[4] == (char)KeySym::W && _cheatsBuffer[5] == (char)KeySym::E && _cheatsBuffer[6] == (char)KeySym::R) { _cheatsBufferLength = 0; _cheatsUsed = true; - for (std::int32_t i = 0; i < (std::int32_t)WeaponType::Count; i++) { - _players[0]->AddWeaponUpgrade((WeaponType)i, 0x01); + for (auto* player : _players) { + for (std::int32_t i = 0; i < (std::int32_t)WeaponType::Count; i++) { + player->AddWeaponUpgrade((WeaponType)i, 0x01); + } } } else if (_cheatsBuffer[2] == (char)KeySym::C && _cheatsBuffer[3] == (char)KeySym::O && _cheatsBuffer[4] == (char)KeySym::I && _cheatsBuffer[5] == (char)KeySym::N && _cheatsBuffer[6] == (char)KeySym::S) { _cheatsBufferLength = 0; _cheatsUsed = true; + // Coins are synchronized automatically _players[0]->AddCoins(5); } break; @@ -641,8 +725,10 @@ namespace Jazz2 if (_cheatsBuffer[2] == (char)KeySym::S && _cheatsBuffer[3] == (char)KeySym::H && _cheatsBuffer[4] == (char)KeySym::I && _cheatsBuffer[5] == (char)KeySym::E && _cheatsBuffer[6] == (char)KeySym::L && _cheatsBuffer[7] == (char)KeySym::D) { _cheatsBufferLength = 0; _cheatsUsed = true; - ShieldType shieldType = (ShieldType)(((std::int32_t)_players[0]->GetActiveShield() + 1) % (std::int32_t)ShieldType::Count); - _players[0]->SetShield(shieldType, 600.0f * FrameTimer::FramesPerSecond); + for (auto* player : _players) { + ShieldType shieldType = (ShieldType)(((std::int32_t)player->GetActiveShield() + 1) % (std::int32_t)ShieldType::Count); + player->SetShield(shieldType, 600.0f * FrameTimer::FramesPerSecond); + } } break; } @@ -681,7 +767,9 @@ namespace Jazz2 std::shared_ptr LevelHandler::PlaySfx(Actors::ActorBase* self, const StringView identifier, AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain, float pitch) { #if defined(WITH_AUDIO) - auto& player = _playingSounds.emplace_back(std::make_shared(buffer)); + auto& player = _playingSounds.emplace_back(_assignedViewports.size() > 1 + ? std::make_shared(buffer) + : std::make_shared(buffer)); player->setPosition(Vector3f(pos.X, pos.Y, 100.0f)); player->setGain(gain * PreferencesCache::MasterVolume * PreferencesCache::SfxVolume); player->setSourceRelative(sourceRelative); @@ -708,12 +796,15 @@ namespace Jazz2 return nullptr; } std::int32_t idx = (it->second.Buffers.size() > 1 ? Random().Next(0, (std::int32_t)it->second.Buffers.size()) : 0); - auto& player = _playingSounds.emplace_back(std::make_shared(&it->second.Buffers[idx]->Buffer)); + auto* buffer = &it->second.Buffers[idx]->Buffer; + auto& player = _playingSounds.emplace_back(_assignedViewports.size() > 1 + ? std::make_shared(buffer) + : std::make_shared(buffer)); player->setPosition(Vector3f(pos.X, pos.Y, 100.0f)); player->setGain(gain * PreferencesCache::MasterVolume * PreferencesCache::SfxVolume); if (pos.Y >= _waterLevel) { - player->setLowPass(/*0.2f*/0.05f); + player->setLowPass(0.05f); player->setPitch(pitch * 0.7f); } else { player->setPitch(pitch); diff --git a/Sources/Jazz2/PlayerViewport.cpp b/Sources/Jazz2/PlayerViewport.cpp index b4be3584..b04178dd 100644 --- a/Sources/Jazz2/PlayerViewport.cpp +++ b/Sources/Jazz2/PlayerViewport.cpp @@ -345,13 +345,6 @@ namespace Jazz2 } _camera->setView(_cameraPos - halfView.As(), 0.0f, 1.0f); - - // TODO: Viewports - Audio listener - if (_targetPlayer->GetPlayerIndex() == 0) { - // Update audio listener position - IAudioDevice& device = theServiceLocator().GetAudioDevice(); - device.updateListener(Vector3f(_cameraPos, 0.0f), Vector3f(focusSpeed, 0.0f)); - } } void PlayerViewport::ShakeCameraView(float duration) diff --git a/Sources/Shared/Containers/StringView.h b/Sources/Shared/Containers/StringView.h index f5b5bdcc..c3717aa0 100644 --- a/Sources/Shared/Containers/StringView.h +++ b/Sources/Shared/Containers/StringView.h @@ -146,7 +146,7 @@ namespace Death { namespace Containers { It's also explicitly disallowing T[] arguments (which are implicitly convertible to an ArrayView), because those should be picking the T* overload and rely on strlen(), consistently with how C string literals work; and disallowing construction from a StringView because it'd get preferred over the implicit copy constructor. */ - template::type>::value && !std::is_same::type, BasicStringView>::value && !std::is_same::type, std::nullptr_t>::value, decltype(ArrayView{std::declval()})>::type> constexpr /*implicit*/ BasicStringView(U&& data, StringViewFlags flags = {}) noexcept: BasicStringView{flags, ArrayView(data)} {} + template::type>::value && !std::is_same::type, BasicStringView>::value && !std::is_same::type, std::nullptr_t>::value, decltype(ArrayView{std::declval()})>::type> constexpr /*implicit*/ BasicStringView(U&& data, StringViewFlags flags = {}) noexcept : BasicStringView{flags, ArrayView(data)} {} /** @brief Construct a @ref StringView from a @ref MutableStringView */ template::value>::type> constexpr /*implicit*/ BasicStringView(BasicStringView mutable_) noexcept : _data{mutable_._data}, _sizePlusFlags{mutable_._sizePlusFlags} {} diff --git a/Sources/nCine/Audio/AudioBufferPlayer.cpp b/Sources/nCine/Audio/AudioBufferPlayer.cpp index 8b44e45e..611c817a 100644 --- a/Sources/nCine/Audio/AudioBufferPlayer.cpp +++ b/Sources/nCine/Audio/AudioBufferPlayer.cpp @@ -95,12 +95,11 @@ namespace nCine bool isSourceRelative = GetFlags(PlayerFlags::SourceRelative); bool isAs2D = GetFlags(PlayerFlags::As2D); - Vector3f adjustedPos = getAdjustedPosition(device, position_, isSourceRelative, isAs2D); alSourcei(sourceId_, AL_SOURCE_RELATIVE, isSourceRelative || isAs2D ? AL_TRUE : AL_FALSE); - alSource3f(sourceId_, AL_POSITION, adjustedPos.X, adjustedPos.Y, adjustedPos.Z); alSourcef(sourceId_, AL_REFERENCE_DISTANCE, IAudioDevice::ReferenceDistance); alSourcef(sourceId_, AL_MAX_DISTANCE, IAudioDevice::MaxDistance); + setPositionInternal(getAdjustedPosition(device, position_, isSourceRelative, isAs2D)); alSourcePlay(sourceId_); state_ = PlayerState::Playing; diff --git a/Sources/nCine/Audio/AudioBufferPlayer.h b/Sources/nCine/Audio/AudioBufferPlayer.h index 67469eae..7e9585ad 100644 --- a/Sources/nCine/Audio/AudioBufferPlayer.h +++ b/Sources/nCine/Audio/AudioBufferPlayer.h @@ -9,6 +9,8 @@ namespace nCine /// Audio buffer player class class AudioBufferPlayer : public IAudioPlayer { + DEATH_RUNTIME_OBJECT(IAudioPlayer); + public: /// Default constructor AudioBufferPlayer(); diff --git a/Sources/nCine/Audio/AudioStreamPlayer.cpp b/Sources/nCine/Audio/AudioStreamPlayer.cpp index 31ffc704..0fda388f 100644 --- a/Sources/nCine/Audio/AudioStreamPlayer.cpp +++ b/Sources/nCine/Audio/AudioStreamPlayer.cpp @@ -11,11 +11,6 @@ namespace nCine { } - /*AudioStreamPlayer::AudioStreamPlayer(const unsigned char* bufferPtr, unsigned long int bufferSize) - : IAudioPlayer(ObjectType::AudioStreamPlayer), audioStream_(bufferPtr, bufferSize) - { - }*/ - AudioStreamPlayer::AudioStreamPlayer(const StringView& filename) : IAudioPlayer(ObjectType::AudioStreamPlayer), audioStream_(filename) { @@ -26,15 +21,6 @@ namespace nCine stop(); } - /*bool AudioStreamPlayer::loadFromMemory(const unsigned char* bufferPtr, unsigned long int bufferSize) - { - if (state_ != PlayerState::Stopped) { - audioStream_.stop(sourceId_); - } - - return audioStream_.loadFromMemory(bufferPtr, bufferSize); - }*/ - bool AudioStreamPlayer::loadFromFile(const char* filename) { if (state_ != PlayerState::Stopped) { @@ -70,12 +56,11 @@ namespace nCine bool isSourceRelative = GetFlags(PlayerFlags::SourceRelative); bool isAs2D = GetFlags(PlayerFlags::As2D); - Vector3f adjustedPos = getAdjustedPosition(device, position_, isSourceRelative, isAs2D); alSourcei(sourceId_, AL_SOURCE_RELATIVE, isSourceRelative || isAs2D ? AL_TRUE : AL_FALSE); - alSource3f(sourceId_, AL_POSITION, adjustedPos.X, adjustedPos.Y, adjustedPos.Z); alSourcef(sourceId_, AL_REFERENCE_DISTANCE, IAudioDevice::ReferenceDistance); alSourcef(sourceId_, AL_MAX_DISTANCE, IAudioDevice::MaxDistance); + setPositionInternal(getAdjustedPosition(device, position_, isSourceRelative, isAs2D)); alSourcePlay(sourceId_); state_ = PlayerState::Playing; diff --git a/Sources/nCine/Audio/AudioStreamPlayer.h b/Sources/nCine/Audio/AudioStreamPlayer.h index 8abae7e1..5dfccb24 100644 --- a/Sources/nCine/Audio/AudioStreamPlayer.h +++ b/Sources/nCine/Audio/AudioStreamPlayer.h @@ -8,11 +8,11 @@ namespace nCine /// Audio stream player class class AudioStreamPlayer : public IAudioPlayer { + DEATH_RUNTIME_OBJECT(IAudioPlayer); + public: /// Default constructor AudioStreamPlayer(); - /// A constructor creating a player from a named memory buffer - //AudioStreamPlayer(const unsigned char* bufferPtr, unsigned long int bufferSize); /// A constructor creating a player from a file explicit AudioStreamPlayer(const StringView& filename); ~AudioStreamPlayer() override; diff --git a/Sources/nCine/Audio/IAudioPlayer.cpp b/Sources/nCine/Audio/IAudioPlayer.cpp index b3abb279..36914658 100644 --- a/Sources/nCine/Audio/IAudioPlayer.cpp +++ b/Sources/nCine/Audio/IAudioPlayer.cpp @@ -82,8 +82,7 @@ namespace nCine position_ = position; if (state_ == PlayerState::Playing) { IAudioDevice& device = theServiceLocator().GetAudioDevice(); - Vector3f adjustedPos = getAdjustedPosition(device, position_, GetFlags(PlayerFlags::SourceRelative), GetFlags(PlayerFlags::As2D)); - alSource3f(sourceId_, AL_POSITION, adjustedPos.X, adjustedPos.Y, adjustedPos.Z); + setPositionInternal(getAdjustedPosition(device, position_, GetFlags(PlayerFlags::SourceRelative), GetFlags(PlayerFlags::As2D))); } } @@ -109,6 +108,11 @@ namespace nCine #endif } + void IAudioPlayer::setPositionInternal(const Vector3f& position) + { + alSource3f(sourceId_, AL_POSITION, position.X, position.Y, position.Z); + } + Vector3f IAudioPlayer::getAdjustedPosition(IAudioDevice& device, const Vector3f& pos, bool isSourceRelative, bool isAs2D) { if (isAs2D) { diff --git a/Sources/nCine/Audio/IAudioPlayer.h b/Sources/nCine/Audio/IAudioPlayer.h index 819392dd..82f2709d 100644 --- a/Sources/nCine/Audio/IAudioPlayer.h +++ b/Sources/nCine/Audio/IAudioPlayer.h @@ -9,6 +9,8 @@ namespace nCine /// Audio player interface class class IAudioPlayer : public Object { + DEATH_RUNTIME_OBJECT(); + public: /// Player state enum class PlayerState { @@ -30,6 +32,7 @@ namespace nCine inline unsigned int sourceId() const { return sourceId_; } + /// Returns the OpenAL id of the currently playing buffer virtual unsigned int bufferId() const = 0; @@ -49,9 +52,9 @@ namespace nCine virtual unsigned long bufferSize() const = 0; /// Returns the playback position expressed in samples - int sampleOffset() const; + virtual int sampleOffset() const; /// Sets the playback position expressed in samples - void setSampleOffset(int offset); + virtual void setSampleOffset(int offset); /// Starts playing virtual void play() = 0; @@ -90,12 +93,12 @@ namespace nCine return GetFlags(PlayerFlags::SourceRelative); } /// Sets player source relative property - void setSourceRelative(bool value); + virtual void setSourceRelative(bool value); inline bool isAs2D() const { return GetFlags(PlayerFlags::As2D); } - void setAs2D(bool value) { + virtual void setAs2D(bool value) { SetFlags(PlayerFlags::As2D, value); } @@ -104,25 +107,28 @@ namespace nCine return gain_; } /// Sets player gain value - void setGain(float gain); + virtual void setGain(float gain); + /// Returns player pitch value inline float pitch() const { return pitch_; } /// Sets player pitch value - void setPitch(float pitch); + virtual void setPitch(float pitch); + /// Returns player low-pass value inline float lowPass() const { return lowPass_; } /// Sets player low-pass value - void setLowPass(float value); + virtual void setLowPass(float value); + /// Returns player position value inline Vector3f position() const { return position_; } /// Sets player position value through vector - void setPosition(const Vector3f& position); + virtual void setPosition(const Vector3f& position); protected: enum class PlayerFlags { @@ -170,6 +176,8 @@ namespace nCine virtual void updateFilters(); + void setPositionInternal(const Vector3f& position); + static Vector3f getAdjustedPosition(IAudioDevice& device, const Vector3f& pos, bool isSourceRelative, bool isAs2D); friend class ALAudioDevice; diff --git a/Sources/nCine/Base/Object.h b/Sources/nCine/Base/Object.h index 015731f4..d6230c7f 100644 --- a/Sources/nCine/Base/Object.h +++ b/Sources/nCine/Base/Object.h @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace nCine { diff --git a/Sources/nCine/MainApplication.cpp b/Sources/nCine/MainApplication.cpp index bed623f5..17f26b2e 100644 --- a/Sources/nCine/MainApplication.cpp +++ b/Sources/nCine/MainApplication.cpp @@ -357,8 +357,8 @@ namespace nCine } } #else - const char* envGameControllerConfig = ::getenv("SDL_GAMECONTROLLERCONFIG"); - if (envGameControllerConfig != nullptr && envGameControllerConfig[0] != '\0') { + StringView envGameControllerConfig = ::getenv("SDL_GAMECONTROLLERCONFIG"); + if (envGameControllerConfig != nullptr) { inputManager_->addJoyMappingsFromString(envGameControllerConfig, "SDL_GAMECONTROLLERCONFIG variable"_s); } #endif