diff --git a/.github/workflows/utests-minimal.yml b/.github/workflows/utests-minimal.yml new file mode 100644 index 00000000..00e02639 --- /dev/null +++ b/.github/workflows/utests-minimal.yml @@ -0,0 +1,28 @@ +name: Unit tests (minimal build) + +on: + push: + branches: '*' + pull_request: + branches: [ "master" ] + +env: + BUILD_TYPE: Debug + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBSCRATCHCPP_BUILD_UNIT_TESTS=ON -DLIBSCRATCHCPP_AUDIO_SUPPORT=OFF -DLIBSCRATCHCPP_NETWORK_SUPPORT=OFF -DLIBSCRATCHCPP_COMPUTED_GOTO=OFF + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j$(nproc --all) + + - name: Run unit tests + run: ctest --test-dir build -V diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d9a8456..66a900c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(libscratchcpp VERSION 0.9.0 LANGUAGES C CXX) +project(libscratchcpp VERSION 0.10.0 LANGUAGES C CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) diff --git a/README.md b/README.md index 78dc3387..f4a9efbf 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ libscratchcpp::Project p("https://scratch.mit.edu/projects/XXXXXX"); - [x] Motion blocks - [x] Looks blocks -- [ ] Sound blocks +- [x] Sound blocks - [x] Event blocks - [x] Control blocks - [ ] Sensing blocks diff --git a/include/scratchcpp/block.h b/include/scratchcpp/block.h index 02b7ddeb..fe6cf382 100644 --- a/include/scratchcpp/block.h +++ b/include/scratchcpp/block.h @@ -56,6 +56,12 @@ class LIBSCRATCHCPP_EXPORT Block : public Entity bool topLevel() const; + int x() const; + void setX(int x); + + int y() const; + void setY(int y); + std::shared_ptr comment() const; const std::string &commentId() const; void setComment(std::shared_ptr comment); diff --git a/include/scratchcpp/compiler.h b/include/scratchcpp/compiler.h index a8b852bb..05ab0c7c 100644 --- a/include/scratchcpp/compiler.h +++ b/include/scratchcpp/compiler.h @@ -4,6 +4,7 @@ #include #include +#include #include "global.h" #include "spimpl.h" @@ -82,6 +83,8 @@ class LIBSCRATCHCPP_EXPORT Compiler BlockPrototype *procedurePrototype() const; void setProcedurePrototype(BlockPrototype *prototype); + const std::unordered_set &unsupportedBlocks() const; + private: spimpl::unique_impl_ptr impl; }; diff --git a/include/scratchcpp/global.h b/include/scratchcpp/global.h index 91500012..c72c14f6 100644 --- a/include/scratchcpp/global.h +++ b/include/scratchcpp/global.h @@ -23,12 +23,6 @@ namespace libscratchcpp { -enum class ScratchVersion -{ - Invalid = 0, /*!< An unsupported version. */ - Scratch3 = 3 /*!< Scratch 3.0 */ -}; - class VirtualMachine; class Compiler; class Block; diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h index 88e95521..aa5aac94 100644 --- a/include/scratchcpp/iengine.h +++ b/include/scratchcpp/iengine.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "global.h" @@ -60,7 +61,7 @@ class LIBSCRATCHCPP_EXPORT IEngine virtual VirtualMachine *startScript(std::shared_ptr topLevelBlock, Target *) = 0; /*! Starts the scripts of the broadcast with the given index. */ - virtual void broadcast(unsigned int index) = 0; + virtual void broadcast(int index) = 0; /*! Starts the scripts of the given broadcast. */ virtual void broadcastByPtr(Broadcast *broadcast) = 0; @@ -87,6 +88,12 @@ class LIBSCRATCHCPP_EXPORT IEngine /*! Stops all currently playing sounds. */ virtual void stopSounds() = 0; + /*! Returns the global volume of all sounds (in %). */ + virtual double globalVolume() const = 0; + + /*! Sets the global volume of all sounds (in %). */ + virtual void setGlobalVolume(double volume) = 0; + /*! Updates the values of stage monitors. */ virtual void updateMonitors() = 0; @@ -116,6 +123,9 @@ class LIBSCRATCHCPP_EXPORT IEngine /*! Emits when a script is about to stop. */ virtual sigslot::signal &threadAboutToStop() = 0; + /*! Emits when the project is stopped by calling stop(). */ + virtual sigslot::signal<> &stopped() = 0; + /*! Returns true if the project is currently running. */ virtual bool isRunning() const = 0; @@ -282,12 +292,15 @@ class LIBSCRATCHCPP_EXPORT IEngine /*! Returns the broadcast at index. */ virtual std::shared_ptr broadcastAt(int index) const = 0; - /*! Returns the index of the broadcast with the given name. */ - virtual int findBroadcast(const std::string &broadcastName) const = 0; + /*! Returns the list of indexes of the broadcasts with the given name (case insensitive). */ + virtual std::vector findBroadcasts(const std::string &broadcastName) const = 0; /*! Returns the index of the broadcast with the given ID. */ virtual int findBroadcastById(const std::string &broadcastId) const = 0; + /* Registers the given "when touching object" script. */ + virtual void addWhenTouchingObjectScript(std::shared_ptr hatBlock) = 0; + /*! Registers the "green flag" script. */ virtual void addGreenFlagScript(std::shared_ptr hatBlock) = 0; @@ -358,6 +371,9 @@ class LIBSCRATCHCPP_EXPORT IEngine /*! Emits when a question is asked, for example using the 'ask and wait' block. */ virtual sigslot::signal &questionAsked() = 0; + /*! Emits when the current question is aborted. */ + virtual sigslot::signal<> &questionAborted() = 0; + /*! Emits when a question is answered. */ virtual sigslot::signal &questionAnswered() = 0; @@ -375,6 +391,9 @@ class LIBSCRATCHCPP_EXPORT IEngine /*! Sets the user agent of the last person to edit the project. */ virtual void setUserAgent(const std::string &agent) = 0; + + /*! Returns the unsupported block opcodes which were found when compiling. */ + virtual const std::unordered_set &unsupportedBlocks() const = 0; }; } // namespace libscratchcpp diff --git a/include/scratchcpp/ispritehandler.h b/include/scratchcpp/ispritehandler.h index 35173c03..9bfb4307 100644 --- a/include/scratchcpp/ispritehandler.h +++ b/include/scratchcpp/ispritehandler.h @@ -65,6 +65,12 @@ class LIBSCRATCHCPP_EXPORT ISpriteHandler /*! Called when the bubble text changes. */ virtual void onBubbleTextChanged(const std::string &text) = 0; + /*! Used to get the current costume width. */ + virtual int costumeWidth() const = 0; + + /*! Used to get the current costume height. */ + virtual int costumeHeight() const = 0; + /*! * Used to get the bounding rectangle of the sprite. * \note The rectangle must be relative to the stage, so make sure to use the sprite's coordinates. diff --git a/include/scratchcpp/istagehandler.h b/include/scratchcpp/istagehandler.h index 31b3c7f3..0b397e17 100644 --- a/include/scratchcpp/istagehandler.h +++ b/include/scratchcpp/istagehandler.h @@ -46,6 +46,12 @@ class LIBSCRATCHCPP_EXPORT IStageHandler /*! Called when the bubble text changes. */ virtual void onBubbleTextChanged(const std::string &text) = 0; + /*! Used to get the current costume width. */ + virtual int costumeWidth() const = 0; + + /*! Used to get the current costume height. */ + virtual int costumeHeight() const = 0; + /*! Used to get the bounding rectangle of the stage. */ virtual Rect boundingRect() const = 0; diff --git a/include/scratchcpp/project.h b/include/scratchcpp/project.h index 414ae47b..6f1c455c 100644 --- a/include/scratchcpp/project.h +++ b/include/scratchcpp/project.h @@ -26,7 +26,6 @@ class LIBSCRATCHCPP_EXPORT Project public: Project(); Project(const std::string &fileName); - Project(const std::string &fileName, ScratchVersion scratchVersion); Project(const Project &) = delete; bool load(); @@ -38,9 +37,6 @@ class LIBSCRATCHCPP_EXPORT Project const std::string &fileName() const; void setFileName(const std::string &newFileName); - ScratchVersion scratchVersion() const; - void setScratchVersion(const ScratchVersion &version); - std::shared_ptr engine() const; sigslot::signal &downloadProgressChanged(); diff --git a/include/scratchcpp/script.h b/include/scratchcpp/script.h index fe3a44ee..b82aaf48 100644 --- a/include/scratchcpp/script.h +++ b/include/scratchcpp/script.h @@ -35,7 +35,7 @@ class LIBSCRATCHCPP_EXPORT Script void setBytecode(const std::vector &code); void setHatPredicateBytecode(const std::vector &code); - bool runHatPredicate(); + bool runHatPredicate(Target *target); void setProcedures(const std::vector &procedures); void setConstValues(const std::vector &values); diff --git a/include/scratchcpp/sound.h b/include/scratchcpp/sound.h index 4185ade0..90f5abc7 100644 --- a/include/scratchcpp/sound.h +++ b/include/scratchcpp/sound.h @@ -9,12 +9,19 @@ namespace libscratchcpp { +class Target; class SoundPrivate; /*! \brief The Sound class represents a Scratch sound. */ class LIBSCRATCHCPP_EXPORT Sound : public Asset { public: + enum class Effect + { + Pitch, + Pan + }; + Sound(const std::string &name, const std::string &id, const std::string &format); Sound(const Sound &) = delete; virtual ~Sound() { } @@ -26,11 +33,15 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset void setSampleCount(int newSampleCount); virtual void setVolume(double volume); + virtual void setEffect(Effect effect, double value); virtual void start(); virtual void stop(); - virtual bool isPlaying(); + virtual bool isPlaying() const; + + Target *target() const; + void setTarget(Target *target); std::shared_ptr clone() const; @@ -38,6 +49,8 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset void processData(unsigned int size, void *data) override; private: + void stopCloneSounds(); + spimpl::unique_impl_ptr impl; }; diff --git a/include/scratchcpp/sprite.h b/include/scratchcpp/sprite.h index 97805772..328c6371 100644 --- a/include/scratchcpp/sprite.h +++ b/include/scratchcpp/sprite.h @@ -59,6 +59,9 @@ class LIBSCRATCHCPP_EXPORT Sprite void setCostumeIndex(int newCostumeIndex) override; + int currentCostumeWidth() const override; + int currentCostumeHeight() const override; + double direction() const; void setDirection(double newDirection); diff --git a/include/scratchcpp/stage.h b/include/scratchcpp/stage.h index 5c6b1564..0d37c862 100644 --- a/include/scratchcpp/stage.h +++ b/include/scratchcpp/stage.h @@ -33,6 +33,9 @@ class LIBSCRATCHCPP_EXPORT Stage : public Target void setCostumeIndex(int newCostumeIndex) override; + int currentCostumeWidth() const override; + int currentCostumeHeight() const override; + int tempo() const; void setTempo(int newTempo); diff --git a/include/scratchcpp/target.h b/include/scratchcpp/target.h index 8007bf31..cb8f7c1b 100644 --- a/include/scratchcpp/target.h +++ b/include/scratchcpp/target.h @@ -7,6 +7,7 @@ #include "global.h" #include "spimpl.h" #include "rect.h" +#include "sound.h" namespace libscratchcpp { @@ -69,6 +70,8 @@ class LIBSCRATCHCPP_EXPORT Target virtual void setCostumeIndex(int newCostumeIndex); std::shared_ptr currentCostume() const; + virtual int currentCostumeWidth() const; + virtual int currentCostumeHeight() const; const std::vector> &costumes() const; int addCostume(std::shared_ptr costume); @@ -86,6 +89,11 @@ class LIBSCRATCHCPP_EXPORT Target double volume() const; void setVolume(double newVolume); + virtual double soundEffectValue(Sound::Effect effect) const; + virtual void setSoundEffectValue(Sound::Effect effect, double value); + + virtual void clearSoundEffects(); + virtual Rect boundingRect() const; virtual Rect fastBoundingRect() const; diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h index fa53716a..94baeb55 100644 --- a/include/scratchcpp/value.h +++ b/include/scratchcpp/value.h @@ -8,8 +8,8 @@ #include #include #include -#include #include +#include #include "global.h" @@ -369,7 +369,7 @@ class LIBSCRATCHCPP_EXPORT Value }; /*! Returns the UTF-16 representation of the value. */ - std::u16string toUtf16() const { return utf8::utf8to16(toString()); }; + std::u16string toUtf16() const; /*! Adds the given value to the value. */ void add(const Value &v) diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 5bf4e0b3..536c266b 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -15,6 +15,7 @@ endif() target_sources(scratchcpp-audio PUBLIC + iaudioengine.h iaudioplayer.h iaudiooutput.h audiooutput.cpp @@ -38,6 +39,8 @@ if (LIBSCRATCHCPP_AUDIO_SUPPORT) else() target_sources(scratchcpp-audio PUBLIC + internal/audioenginestub.cpp + internal/audioenginestub.h internal/audioplayerstub.cpp internal/audioplayerstub.h internal/audioloudnessstub.cpp diff --git a/src/audio/iaudioengine.h b/src/audio/iaudioengine.h new file mode 100644 index 00000000..8100334b --- /dev/null +++ b/src/audio/iaudioengine.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class IAudioEngine +{ + public: + virtual ~IAudioEngine() { } + + static IAudioEngine *instance(); + + virtual float volume() const = 0; + virtual void setVolume(float volume) = 0; +}; + +} // namespace libscratchcpp diff --git a/src/audio/iaudioplayer.h b/src/audio/iaudioplayer.h index 0023f195..0531c8ed 100644 --- a/src/audio/iaudioplayer.h +++ b/src/audio/iaudioplayer.h @@ -16,6 +16,12 @@ class IAudioPlayer virtual float volume() const = 0; virtual void setVolume(float volume) = 0; + virtual float pitch() const = 0; + virtual void setPitch(float pitch) = 0; + + virtual float pan() const = 0; + virtual void setPan(float pan) = 0; + virtual bool isLoaded() const = 0; virtual void start() = 0; diff --git a/src/audio/internal/audioengine.cpp b/src/audio/internal/audioengine.cpp index 710a394e..1af3874d 100644 --- a/src/audio/internal/audioengine.cpp +++ b/src/audio/internal/audioengine.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include "audioengine.h" @@ -8,12 +9,17 @@ using namespace libscratchcpp; AudioEngine AudioEngine::instance; +IAudioEngine *IAudioEngine::instance() +{ + return &AudioEngine::instance; +} + ma_engine *AudioEngine::engine() { if (!instance.m_initialized) instance.init(); - return instance.m_initialized ? &instance.m_engine : nullptr; + return instance.m_initialized ? instance.m_engine : nullptr; } bool AudioEngine::initialized() @@ -24,20 +30,36 @@ bool AudioEngine::initialized() return instance.m_initialized; } +float AudioEngine::volume() const +{ + return m_volume; +} + +void AudioEngine::setVolume(float volume) +{ + m_volume = volume; + + if (m_initialized) + ma_engine_set_volume(m_engine, volume); +} + AudioEngine::AudioEngine() { } AudioEngine::~AudioEngine() { - if (m_initialized) - ma_engine_uninit(&m_engine); + if (m_initialized && m_engine) { + ma_engine_uninit(m_engine); + delete m_engine; + } } void AudioEngine::init() { ma_result result; - result = ma_engine_init(NULL, &m_engine); + m_engine = new ma_engine; + result = ma_engine_init(NULL, m_engine); if (result != MA_SUCCESS) { std::cerr << "Failed to initialize audio engine." << std::endl; diff --git a/src/audio/internal/audioengine.h b/src/audio/internal/audioengine.h index 49097871..02672ff3 100644 --- a/src/audio/internal/audioengine.h +++ b/src/audio/internal/audioengine.h @@ -3,18 +3,26 @@ #pragma once #include -#include + +#include "../iaudioengine.h" + +struct ma_engine; namespace libscratchcpp { // This is a singleton which initializes and uninitializes the miniaudio engine -class AudioEngine +class AudioEngine : public IAudioEngine { public: + friend class IAudioEngine; + static ma_engine *engine(); static bool initialized(); + float volume() const override; + void setVolume(float volume) override; + private: AudioEngine(); ~AudioEngine(); @@ -22,8 +30,10 @@ class AudioEngine void init(); static AudioEngine instance; - ma_engine m_engine; + + ma_engine *m_engine = nullptr; bool m_initialized = false; + float m_volume = 1.0f; }; } // namespace libscratchcpp diff --git a/src/audio/internal/audioenginestub.cpp b/src/audio/internal/audioenginestub.cpp new file mode 100644 index 00000000..83b8bd45 --- /dev/null +++ b/src/audio/internal/audioenginestub.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "audioenginestub.h" + +using namespace libscratchcpp; + +AudioEngineStub AudioEngineStub::instance; + +IAudioEngine *IAudioEngine::instance() +{ + return &AudioEngineStub::instance; +} + +AudioEngineStub::AudioEngineStub() +{ +} + +float AudioEngineStub::volume() const +{ + return m_volume; +} + +void AudioEngineStub::setVolume(float volume) +{ + m_volume = volume; +} diff --git a/src/audio/internal/audioenginestub.h b/src/audio/internal/audioenginestub.h new file mode 100644 index 00000000..e49e076c --- /dev/null +++ b/src/audio/internal/audioenginestub.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../iaudioengine.h" + +namespace libscratchcpp +{ + +class AudioEngineStub : public IAudioEngine +{ + public: + friend class IAudioEngine; + AudioEngineStub(); + + float volume() const override; + void setVolume(float volume) override; + + private: + static AudioEngineStub instance; + float m_volume = 1.0f; +}; + +} // namespace libscratchcpp diff --git a/src/audio/internal/audioplayer.cpp b/src/audio/internal/audioplayer.cpp index 9cab04ba..dce100fb 100644 --- a/src/audio/internal/audioplayer.cpp +++ b/src/audio/internal/audioplayer.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "audioplayer.h" #include "audioengine.h" @@ -18,11 +19,15 @@ AudioPlayer::~AudioPlayer() { if (m_loaded) { ma_sound_uninit(m_sound); - ma_decoder_uninit(m_decoder); + + if (!m_copy) + ma_decoder_uninit(m_decoder); } - delete m_decoder; delete m_sound; + + if (!m_copy) + delete m_decoder; } bool AudioPlayer::load(unsigned int size, const void *data, unsigned long sampleRate) @@ -53,6 +58,8 @@ bool AudioPlayer::load(unsigned int size, const void *data, unsigned long sample m_loaded = true; ma_sound_set_volume(m_sound, m_volume); + ma_sound_set_pitch(m_sound, m_pitch); + ma_sound_set_pan(m_sound, m_pan); return true; } @@ -74,7 +81,10 @@ bool AudioPlayer::loadCopy(IAudioPlayer *player) } m_loaded = true; + m_copy = true; ma_sound_set_volume(m_sound, m_volume); + ma_sound_set_pitch(m_sound, m_pitch); + ma_sound_set_pan(m_sound, m_pan); return true; } @@ -93,6 +103,44 @@ void AudioPlayer::setVolume(float volume) ma_sound_set_volume(m_sound, volume); } +float AudioPlayer::pitch() const +{ + return m_pitch; +} + +void AudioPlayer::setPitch(float pitch) +{ + /* + * 0.5 -> 220 Hz + * 1 -> 440 Hz + * 2 -> 880 Hz + * 4 -> 1760 Hz + * ... + */ + m_pitch = pitch; + + if (!m_loaded) + return; + + ma_sound_set_pitch(m_sound, pitch); +} + +float AudioPlayer::pan() const +{ + return m_pan; +} + +void AudioPlayer::setPan(float pan) +{ + // -1 ... 0 ... 1 + m_pan = pan; + + if (!m_loaded) + return; + + ma_sound_set_pan(m_sound, pan); +} + bool AudioPlayer::isLoaded() const { return m_loaded; diff --git a/src/audio/internal/audioplayer.h b/src/audio/internal/audioplayer.h index 854691ae..ad5a2e0d 100644 --- a/src/audio/internal/audioplayer.h +++ b/src/audio/internal/audioplayer.h @@ -22,6 +22,12 @@ class AudioPlayer : public IAudioPlayer float volume() const override; void setVolume(float volume) override; + float pitch() const override; + void setPitch(float pitch) override; + + float pan() const override; + void setPan(float pan) override; + bool isLoaded() const override; void start() override; @@ -33,8 +39,11 @@ class AudioPlayer : public IAudioPlayer ma_decoder *m_decoder = nullptr; ma_sound *m_sound; bool m_loaded = false; + bool m_copy = false; bool m_started = false; float m_volume = 1; + float m_pitch = 1; + float m_pan = 0; }; } // namespace libscratchcpp diff --git a/src/audio/internal/audioplayerstub.cpp b/src/audio/internal/audioplayerstub.cpp index 82983f1c..0dc39939 100644 --- a/src/audio/internal/audioplayerstub.cpp +++ b/src/audio/internal/audioplayerstub.cpp @@ -28,6 +28,26 @@ void AudioPlayerStub::setVolume(float volume) m_volume = volume; } +float AudioPlayerStub::pitch() const +{ + return m_pitch; +} + +void AudioPlayerStub::setPitch(float pitch) +{ + m_pitch = pitch; +} + +float AudioPlayerStub::pan() const +{ + return m_pan; +} + +void AudioPlayerStub::setPan(float pan) +{ + m_pan = pan; +} + bool AudioPlayerStub::isLoaded() const { return true; diff --git a/src/audio/internal/audioplayerstub.h b/src/audio/internal/audioplayerstub.h index a577ddb2..c8c297cf 100644 --- a/src/audio/internal/audioplayerstub.h +++ b/src/audio/internal/audioplayerstub.h @@ -18,6 +18,12 @@ class AudioPlayerStub : public IAudioPlayer float volume() const override; void setVolume(float volume) override; + float pitch() const override; + void setPitch(float pitch) override; + + float pan() const override; + void setPan(float pan) override; + bool isLoaded() const override; void start() override; @@ -27,6 +33,8 @@ class AudioPlayerStub : public IAudioPlayer private: float m_volume = 1; + float m_pitch = 1; + float m_pan = 0; }; } // namespace libscratchcpp diff --git a/src/blocks/controlblocks.cpp b/src/blocks/controlblocks.cpp index d3f89b07..0f965123 100644 --- a/src/blocks/controlblocks.cpp +++ b/src/blocks/controlblocks.cpp @@ -175,8 +175,7 @@ void ControlBlocks::compileCreateClone(Compiler *compiler) { Input *input = compiler->input(CLONE_OPTION); - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string spriteName = input->selectedMenuItem(); if (spriteName == "_myself_") diff --git a/src/blocks/eventblocks.cpp b/src/blocks/eventblocks.cpp index 282437c6..e4c61902 100644 --- a/src/blocks/eventblocks.cpp +++ b/src/blocks/eventblocks.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,7 @@ std::string EventBlocks::name() const void EventBlocks::registerBlocks(IEngine *engine) { // Blocks + engine->addCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObject); engine->addCompileFunction(this, "event_whenflagclicked", &compileWhenFlagClicked); engine->addCompileFunction(this, "event_whenthisspriteclicked", &compileWhenThisSpriteClicked); engine->addCompileFunction(this, "event_whenstageclicked", &compileWhenStageClicked); @@ -38,9 +40,11 @@ void EventBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "event_whenkeypressed", &compileWhenKeyPressed); // Hat predicates + engine->addHatPredicateCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObjectPredicate); engine->addHatPredicateCompileFunction(this, "event_whengreaterthan", &compileWhenGreaterThanPredicate); // Inputs + engine->addInput(this, "TOUCHINGOBJECTMENU", TOUCHINGOBJECTMENU); engine->addInput(this, "BROADCAST_INPUT", BROADCAST_INPUT); engine->addInput(this, "VALUE", VALUE); @@ -55,6 +59,26 @@ void EventBlocks::registerBlocks(IEngine *engine) engine->addFieldValue(this, "TIMER", Timer); } +void EventBlocks::compileWhenTouchingObjectPredicate(Compiler *compiler) +{ + Input *input = compiler->input(TOUCHINGOBJECTMENU); + + if (input->pointsToDropdownMenu()) { + std::string value = input->selectedMenuItem(); + + compiler->addConstValue(value); + compiler->addFunctionCall(&whenTouchingObjectPredicate); + } else { + compiler->addInput(input); + compiler->addFunctionCall(&whenTouchingObjectPredicate); + } +} + +void EventBlocks::compileWhenTouchingObject(Compiler *compiler) +{ + compiler->engine()->addWhenTouchingObjectScript(compiler->block()); +} + void EventBlocks::compileWhenFlagClicked(Compiler *compiler) { compiler->engine()->addGreenFlagScript(compiler->block()); @@ -75,8 +99,12 @@ void EventBlocks::compileBroadcast(Compiler *compiler) auto input = compiler->input(BROADCAST_INPUT); if (input->type() != Input::Type::ObscuredShadow) { - compiler->addConstValue(compiler->engine()->findBroadcast(input->primaryValue()->value().toString())); - compiler->addFunctionCall(&broadcastByIndex); + std::vector broadcasts = compiler->engine()->findBroadcasts(input->primaryValue()->value().toString()); + + for (int index : broadcasts) { + compiler->addConstValue(index); + compiler->addFunctionCall(&broadcastByIndex); + } } else { compiler->addInput(input); compiler->addFunctionCall(&broadcast); @@ -88,11 +116,17 @@ void EventBlocks::compileBroadcastAndWait(Compiler *compiler) auto input = compiler->input(BROADCAST_INPUT); if (input->type() != Input::Type::ObscuredShadow) { - int index = compiler->engine()->findBroadcast(input->primaryValue()->value().toString()); - compiler->addConstValue(index); - compiler->addFunctionCall(&broadcastByIndexAndWait); - compiler->addConstValue(index); - compiler->addFunctionCall(&checkBroadcastByIndex); + std::vector broadcasts = compiler->engine()->findBroadcasts(input->primaryValue()->value().toString()); + + for (int index : broadcasts) { + compiler->addConstValue(index); + compiler->addFunctionCall(&broadcastByIndexAndWait); + } + + for (int index : broadcasts) { + compiler->addConstValue(index); + compiler->addFunctionCall(&checkBroadcastByIndex); + } } else { compiler->addInput(input); compiler->addFunctionCall(&broadcastAndWait); @@ -151,9 +185,33 @@ void EventBlocks::compileWhenKeyPressed(Compiler *compiler) compiler->engine()->addKeyPressScript(compiler->block(), KEY_OPTION); } +unsigned int EventBlocks::whenTouchingObjectPredicate(VirtualMachine *vm) +{ + std::string value = vm->getInput(0, 1)->toString(); + + if (value == "_mouse_") + vm->replaceReturnValue(vm->target()->touchingPoint(vm->engine()->mouseX(), vm->engine()->mouseY()), 1); + else if (value == "_edge_") + vm->replaceReturnValue(vm->target()->touchingEdge(), 1); + else { + Target *target = vm->engine()->targetAt(vm->engine()->findTarget(value)); + + if (target && !target->isStage()) + vm->replaceReturnValue(vm->target()->touchingSprite(static_cast(target)), 1); + else + vm->replaceReturnValue(false, 1); + } + + return 0; +} + unsigned int EventBlocks::broadcast(VirtualMachine *vm) { - vm->engine()->broadcast(vm->engine()->findBroadcast(vm->getInput(0, 1)->toString())); + std::vector broadcasts = vm->engine()->findBroadcasts(vm->getInput(0, 1)->toString()); + + for (int index : broadcasts) + vm->engine()->broadcast(index); + return 1; } @@ -165,7 +223,11 @@ unsigned int EventBlocks::broadcastByIndex(VirtualMachine *vm) unsigned int EventBlocks::broadcastAndWait(VirtualMachine *vm) { - vm->engine()->broadcast(vm->engine()->findBroadcast(vm->getInput(0, 1)->toString())); + std::vector broadcasts = vm->engine()->findBroadcasts(vm->getInput(0, 1)->toString()); + + for (int index : broadcasts) + vm->engine()->broadcast(index); + return 1; } @@ -177,8 +239,20 @@ unsigned int EventBlocks::broadcastByIndexAndWait(VirtualMachine *vm) unsigned int EventBlocks::checkBroadcast(VirtualMachine *vm) { - if (vm->engine()->broadcastRunning(vm->engine()->findBroadcast(vm->getInput(0, 1)->toString()))) + bool running = false; + + std::vector broadcasts = vm->engine()->findBroadcasts(vm->getInput(0, 1)->toString()); + + for (int index : broadcasts) { + if (vm->engine()->broadcastRunning(index)) { + running = true; + break; + } + } + + if (running) vm->stop(true, true, true); + return 1; } diff --git a/src/blocks/eventblocks.h b/src/blocks/eventblocks.h index 49479da7..0c5e5fd6 100644 --- a/src/blocks/eventblocks.h +++ b/src/blocks/eventblocks.h @@ -17,6 +17,7 @@ class EventBlocks : public IBlockSection public: enum Inputs { + TOUCHINGOBJECTMENU, BROADCAST_INPUT, VALUE }; @@ -39,6 +40,8 @@ class EventBlocks : public IBlockSection void registerBlocks(IEngine *engine) override; + static void compileWhenTouchingObjectPredicate(Compiler *compiler); + static void compileWhenTouchingObject(Compiler *compiler); static void compileWhenFlagClicked(Compiler *compiler); static void compileWhenThisSpriteClicked(Compiler *compiler); static void compileWhenStageClicked(Compiler *compiler); @@ -50,6 +53,8 @@ class EventBlocks : public IBlockSection static void compileWhenGreaterThan(Compiler *compiler); static void compileWhenKeyPressed(Compiler *compiler); + static unsigned int whenTouchingObjectPredicate(VirtualMachine *vm); + static unsigned int broadcast(VirtualMachine *vm); static unsigned int broadcastByIndex(VirtualMachine *vm); static unsigned int broadcastAndWait(VirtualMachine *vm); diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index b10691a4..fb55f39d 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -103,6 +103,15 @@ void LooksBlocks::onInit(IEngine *engine) m_timeMap.erase(vm); erase_if(m_waitingBubbles, [vm](const std::pair &pair) { return pair.second == vm; }); }); + + engine->stopped().connect([engine]() { + const auto &targets = engine->targets(); + + for (auto target : targets) { + target->setBubbleText(""); + target->clearGraphicsEffects(); + } + }); } void LooksBlocks::compileSayForSecs(Compiler *compiler) @@ -340,8 +349,7 @@ void LooksBlocks::compileSwitchCostumeTo(Compiler *compiler) Input *input = compiler->input(COSTUME); - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string value = input->selectedMenuItem(); int index = target->findCostume(value); @@ -385,8 +393,7 @@ void LooksBlocks::compileSwitchBackdropTo(Compiler *compiler) Input *input = compiler->input(BACKDROP); - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string value = input->selectedMenuItem(); int index = stage->findCostume(value); @@ -427,8 +434,7 @@ void LooksBlocks::compileSwitchBackdropToAndWait(Compiler *compiler) Input *input = compiler->input(BACKDROP); - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string value = input->selectedMenuItem(); int index = stage->findCostume(value); diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 012d436d..549cb18d 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -107,8 +107,7 @@ void MotionBlocks::compilePointTowards(Compiler *compiler) { Input *input = compiler->input(TOWARDS); - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string value = input->selectedMenuItem(); if (value == "_mouse_") @@ -137,8 +136,7 @@ void MotionBlocks::compileGoTo(Compiler *compiler) { Input *input = compiler->input(TO); - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string value = input->selectedMenuItem(); if (value == "_mouse_") @@ -171,8 +169,7 @@ void MotionBlocks::compileGlideTo(Compiler *compiler) Input *input = compiler->input(TO); - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string value = input->selectedMenuItem(); if (value == "_mouse_") diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp index 722be78a..fee64bc0 100644 --- a/src/blocks/sensingblocks.cpp +++ b/src/blocks/sensingblocks.cpp @@ -95,12 +95,27 @@ void SensingBlocks::registerBlocks(IEngine *engine) engine->questionAnswered().connect(&onAnswer); } +void SensingBlocks::onInit(IEngine *engine) +{ + engine->threadAboutToStop().connect([engine](VirtualMachine *thread) { + if (!m_questionList.empty()) { + // Abort the question of this thread if it's currently being displayed + if (m_questionList.front()->vm == thread) { + thread->target()->setBubbleText(""); + engine->questionAborted()(); + } + + m_questionList + .erase(std::remove_if(m_questionList.begin(), m_questionList.end(), [thread](const std::unique_ptr &question) { return question->vm == thread; }), m_questionList.end()); + } + }); +} + void SensingBlocks::compileTouchingObject(Compiler *compiler) { Input *input = compiler->input(TOUCHINGOBJECTMENU); - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string value = input->selectedMenuItem(); if (value == "_mouse_") @@ -128,8 +143,7 @@ void SensingBlocks::compileDistanceTo(Compiler *compiler) { Input *input = compiler->input(DISTANCETOMENU); - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string value = input->selectedMenuItem(); if (value == "_mouse_") @@ -224,8 +238,7 @@ void SensingBlocks::compileOf(Compiler *compiler) assert(input); BlockFunc f = nullptr; - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string value = input->selectedMenuItem(); IEngine *engine = compiler->engine(); @@ -276,13 +289,16 @@ void SensingBlocks::compileOf(Compiler *compiler) default: { // Variable + f = &variableOfTargetByIndex; Target *target = engine->targetAt(index); auto varIndex = target->findVariable(property->value().toString()); if (varIndex == -1) compiler->addInstruction(vm::OP_NULL); - else - compiler->addInstruction(vm::OP_READ_VAR, { compiler->variableIndex(target->variableAt(varIndex)) }); + else { + // NOTE: The OP_READ_VAR instruction can't be used for this (see #548) + compiler->addConstValue(varIndex); + } break; } @@ -869,6 +885,19 @@ unsigned int SensingBlocks::variableOfTarget(VirtualMachine *vm) return 1; } +unsigned int SensingBlocks::variableOfTargetByIndex(VirtualMachine *vm) +{ + Target *target = vm->engine()->targetAt(vm->getInput(0, 1)->toInt()); + + if (target) { + const int varIndex = vm->getInput(0, 2)->toInt(); + vm->replaceReturnValue(target->variableAt(varIndex)->value(), 2); + } else + vm->replaceReturnValue(0, 2); + + return 1; +} + unsigned int SensingBlocks::backdropNumberOfStage(VirtualMachine *vm) { Target *target = vm->engine()->targetAt(vm->engine()->findTarget(vm->getInput(0, 1)->toString())); diff --git a/src/blocks/sensingblocks.h b/src/blocks/sensingblocks.h index 032c0561..e281827f 100644 --- a/src/blocks/sensingblocks.h +++ b/src/blocks/sensingblocks.h @@ -60,6 +60,7 @@ class SensingBlocks : public IBlockSection std::string name() const override; void registerBlocks(IEngine *engine) override; + void onInit(IEngine *engine) override; static void compileTouchingObject(Compiler *compiler); static void compileTouchingColor(Compiler *compiler); @@ -131,6 +132,7 @@ class SensingBlocks : public IBlockSection static unsigned int volumeOfTarget(VirtualMachine *vm); static unsigned int volumeOfTargetByIndex(VirtualMachine *vm); static unsigned int variableOfTarget(VirtualMachine *vm); + static unsigned int variableOfTargetByIndex(VirtualMachine *vm); static unsigned int backdropNumberOfStage(VirtualMachine *vm); static unsigned int backdropNumberOfStageByIndex(VirtualMachine *vm); static unsigned int backdropNameOfStage(VirtualMachine *vm); diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index fb999b87..4eb311fa 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "soundblocks.h" @@ -33,7 +34,10 @@ void SoundBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "sound_play", &compilePlay); engine->addCompileFunction(this, "sound_playuntildone", &compilePlayUntilDone); engine->addCompileFunction(this, "sound_stopallsounds", &compileStopAllSounds); + engine->addCompileFunction(this, "sound_seteffectto", &compileSetEffectTo); + engine->addCompileFunction(this, "sound_changeeffectby", &compileChangeEffectBy); engine->addCompileFunction(this, "sound_changevolumeby", &compileChangeVolumeBy); + engine->addCompileFunction(this, "sound_cleareffects", &compileClearEffects); engine->addCompileFunction(this, "sound_setvolumeto", &compileSetVolumeTo); engine->addCompileFunction(this, "sound_volume", &compileVolume); @@ -42,7 +46,15 @@ void SoundBlocks::registerBlocks(IEngine *engine) // Inputs engine->addInput(this, "SOUND_MENU", SOUND_MENU); + engine->addInput(this, "VALUE", VALUE); engine->addInput(this, "VOLUME", VOLUME); + + // Fields + engine->addField(this, "EFFECT", EFFECT); + + // Field values + engine->addFieldValue(this, "PITCH", PITCH); + engine->addFieldValue(this, "PAN", PAN); } void SoundBlocks::onInit(IEngine *engine) @@ -61,8 +73,7 @@ bool SoundBlocks::compilePlayCommon(Compiler *compiler, bool untilDone, bool *by Input *input = compiler->input(SOUND_MENU); - if (input->type() != Input::Type::ObscuredShadow) { - assert(input->pointsToDropdownMenu()); + if (input->pointsToDropdownMenu()) { std::string value = input->selectedMenuItem(); int index = target->findSound(value); @@ -119,6 +130,51 @@ void SoundBlocks::compileStopAllSounds(Compiler *compiler) compiler->addFunctionCall(&stopAllSounds); } +void SoundBlocks::compileSetEffectTo(Compiler *compiler) +{ + compiler->addInput(VALUE); + int option = compiler->field(EFFECT)->specialValueId(); + + switch (option) { + case PITCH: + compiler->addFunctionCall(&setPitchEffectTo); + break; + + case PAN: + compiler->addFunctionCall(&setPanEffectTo); + break; + + default: + assert(false); + break; + } +} + +void SoundBlocks::compileChangeEffectBy(Compiler *compiler) +{ + compiler->addInput(VALUE); + int option = compiler->field(EFFECT)->specialValueId(); + + switch (option) { + case PITCH: + compiler->addFunctionCall(&changePitchEffectBy); + break; + + case PAN: + compiler->addFunctionCall(&changePanEffectBy); + break; + + default: + assert(false); + break; + } +} + +void SoundBlocks::compileClearEffects(Compiler *compiler) +{ + compiler->addFunctionCall(&clearEffects); +} + void SoundBlocks::compileChangeVolumeBy(Compiler *compiler) { compiler->addInput(VOLUME); @@ -297,6 +353,46 @@ unsigned int SoundBlocks::stopAllSounds(VirtualMachine *vm) return 0; } +unsigned int SoundBlocks::setPitchEffectTo(VirtualMachine *vm) +{ + if (Target *target = vm->target()) + target->setSoundEffectValue(Sound::Effect::Pitch, vm->getInput(0, 1)->toDouble()); + + return 1; +} + +unsigned int SoundBlocks::setPanEffectTo(VirtualMachine *vm) +{ + if (Target *target = vm->target()) + target->setSoundEffectValue(Sound::Effect::Pan, vm->getInput(0, 1)->toDouble()); + + return 1; +} + +unsigned int SoundBlocks::changePitchEffectBy(VirtualMachine *vm) +{ + if (Target *target = vm->target()) + target->setSoundEffectValue(Sound::Effect::Pitch, target->soundEffectValue(Sound::Effect::Pitch) + vm->getInput(0, 1)->toDouble()); + + return 1; +} + +unsigned int SoundBlocks::changePanEffectBy(VirtualMachine *vm) +{ + if (Target *target = vm->target()) + target->setSoundEffectValue(Sound::Effect::Pan, target->soundEffectValue(Sound::Effect::Pan) + vm->getInput(0, 1)->toDouble()); + + return 1; +} + +unsigned int SoundBlocks::clearEffects(VirtualMachine *vm) +{ + if (Target *target = vm->target()) + target->clearSoundEffects(); + + return 0; +} + unsigned int SoundBlocks::changeVolumeBy(VirtualMachine *vm) { if (Target *target = vm->target()) diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index 866be118..10e493c5 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -20,17 +20,19 @@ class SoundBlocks : public IBlockSection enum Inputs { SOUND_MENU, + VALUE, VOLUME }; enum Fields { - + EFFECT }; enum FieldValues { - + PITCH, + PAN }; std::string name() const override; @@ -42,6 +44,9 @@ class SoundBlocks : public IBlockSection static void compilePlay(Compiler *compiler); static void compilePlayUntilDone(Compiler *compiler); static void compileStopAllSounds(Compiler *compiler); + static void compileSetEffectTo(Compiler *compiler); + static void compileChangeEffectBy(Compiler *compiler); + static void compileClearEffects(Compiler *compiler); static void compileChangeVolumeBy(Compiler *compiler); static void compileSetVolumeTo(Compiler *compiler); static void compileVolume(Compiler *compiler); @@ -62,6 +67,12 @@ class SoundBlocks : public IBlockSection static unsigned int stopAllSounds(VirtualMachine *vm); + static unsigned int setPitchEffectTo(VirtualMachine *vm); + static unsigned int setPanEffectTo(VirtualMachine *vm); + static unsigned int changePitchEffectBy(VirtualMachine *vm); + static unsigned int changePanEffectBy(VirtualMachine *vm); + static unsigned int clearEffects(VirtualMachine *vm); + static unsigned int changeVolumeBy(VirtualMachine *vm); static unsigned int setVolumeTo(VirtualMachine *vm); static unsigned int volume(VirtualMachine *vm); diff --git a/src/engine/compiler.cpp b/src/engine/compiler.cpp index 9f737b73..2ff823ba 100644 --- a/src/engine/compiler.cpp +++ b/src/engine/compiler.cpp @@ -66,8 +66,10 @@ void Compiler::compile(std::shared_ptr topLevelBlock) if (impl->block->compileFunction()) impl->block->compile(this); - else + else { std::cout << "warning: unsupported block: " << impl->block->opcode() << std::endl; + impl->unsupportedBlocks.insert(impl->block->opcode()); + } if (substacks != impl->substackTree.size()) continue; @@ -174,23 +176,27 @@ void Compiler::addInput(Input *input) } switch (input->type()) { case Input::Type::Shadow: + case Input::Type::NoShadow: { if (input->pointsToDropdownMenu()) addInstruction(OP_CONST, { impl->constIndex(input->primaryValue(), true, input->selectedMenuItem()) }); - else - addInstruction(OP_CONST, { impl->constIndex(input->primaryValue()) }); - break; - - case Input::Type::NoShadow: { - auto previousBlock = impl->block; - impl->block = input->valueBlock(); - assert(impl->block); - if (impl->block->compileFunction()) - impl->block->compile(this); else { - std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl; - addInstruction(OP_NULL); + auto previousBlock = impl->block; + impl->block = input->valueBlock(); + + if (impl->block) { + if (impl->block->compileFunction()) + impl->block->compile(this); + else { + std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl; + impl->unsupportedBlocks.insert(impl->block->opcode()); + addInstruction(OP_NULL); + } + } else + addInstruction(OP_CONST, { impl->constIndex(input->primaryValue()) }); + + impl->block = previousBlock; } - impl->block = previousBlock; + break; } @@ -202,6 +208,7 @@ void Compiler::addInput(Input *input) impl->block->compile(this); else { std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl; + impl->unsupportedBlocks.insert(impl->block->opcode()); addInstruction(OP_NULL); } } else @@ -353,6 +360,12 @@ void Compiler::setProcedurePrototype(BlockPrototype *prototype) impl->procedurePrototype = prototype; } +/*! Returns unsupported block opcodes which were found when compiling. */ +const std::unordered_set &libscratchcpp::Compiler::unsupportedBlocks() const +{ + return impl->unsupportedBlocks; +} + /*! Returns the list of custom block prototypes. */ const std::vector &Compiler::procedures() const { diff --git a/src/engine/compiler_p.h b/src/engine/compiler_p.h index a310eb47..5a396a7a 100644 --- a/src/engine/compiler_p.h +++ b/src/engine/compiler_p.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include @@ -38,6 +39,8 @@ struct CompilerPrivate std::unordered_map> procedureArgs; BlockPrototype *procedurePrototype = nullptr; bool warp = false; + + std::unordered_set unsupportedBlocks; }; } // namespace libscratchcpp diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index d2923948..448103e4 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -26,6 +26,7 @@ #include "blocksectioncontainer.h" #include "timer.h" #include "clock.h" +#include "audio/iaudioengine.h" #include "blocks/standardblocks.h" #include "blocks/variableblocks.h" #include "blocks/listblocks.h" @@ -34,19 +35,20 @@ using namespace libscratchcpp; const std::unordered_map Engine::m_hatRestartExistingThreads = { - { HatType::GreenFlag, true }, { HatType::BroadcastReceived, true }, { HatType::BackdropChanged, true }, { HatType::CloneInit, false }, - { HatType::KeyPressed, false }, { HatType::TargetClicked, true }, { HatType::WhenGreaterThan, false } + { HatType::WhenTouchingObject, false }, { HatType::GreenFlag, true }, { HatType::BroadcastReceived, true }, { HatType::BackdropChanged, true }, + { HatType::CloneInit, false }, { HatType::KeyPressed, false }, { HatType::TargetClicked, true }, { HatType::WhenGreaterThan, false } }; const std::unordered_map Engine::m_hatEdgeActivated = { - { HatType::GreenFlag, false }, { HatType::BroadcastReceived, false }, { HatType::BackdropChanged, false }, { HatType::CloneInit, false }, - { HatType::KeyPressed, false }, { HatType::TargetClicked, false }, { HatType::WhenGreaterThan, true } + { HatType::WhenTouchingObject, true }, { HatType::GreenFlag, false }, { HatType::BroadcastReceived, false }, { HatType::BackdropChanged, false }, + { HatType::CloneInit, false }, { HatType::KeyPressed, false }, { HatType::TargetClicked, false }, { HatType::WhenGreaterThan, true } }; Engine::Engine() : m_defaultTimer(std::make_unique()), m_timer(m_defaultTimer.get()), - m_clock(Clock::instance().get()) + m_clock(Clock::instance().get()), + m_audioEngine(IAudioEngine::instance()) { } @@ -80,6 +82,7 @@ void Engine::clear() m_scripts.clear(); m_functions.clear(); + m_whenTouchingObjectHats.clear(); m_greenFlagHats.clear(); m_backdropChangeHats.clear(); m_broadcastHats.clear(); @@ -92,6 +95,8 @@ void Engine::clear() m_edgeActivatedHatValues.clear(); m_running = false; + + m_unsupportedBlocks.clear(); } // Resolves ID references and sets pointers of entities. @@ -102,8 +107,8 @@ void Engine::resolveIds() const auto &blocks = target->blocks(); for (auto block : blocks) { auto container = blockSectionContainer(block->opcode()); - block->setNext(getBlock(block->nextId())); - block->setParent(getBlock(block->parentId())); + block->setNext(getBlock(block->nextId(), target.get())); + block->setParent(getBlock(block->parentId(), target.get())); if (container) { block->setCompileFunction(container->resolveBlockCompileFunc(block->opcode())); @@ -112,16 +117,16 @@ void Engine::resolveIds() const auto &inputs = block->inputs(); for (const auto &input : inputs) { - input->setValueBlock(getBlock(input->valueBlockId())); + input->setValueBlock(getBlock(input->valueBlockId(), target.get())); if (container) input->setInputId(container->resolveInput(input->name())); InputValue *value = input->primaryValue(); std::string id = value->valueId(); // no reference! - value->setValuePtr(getEntity(id)); + value->setValuePtr(getEntity(id, target.get())); assert(input->secondaryValue()->type() != InputValue::Type::Variable && input->secondaryValue()->type() != InputValue::Type::List); // secondary values never have a variable or list - input->secondaryValue()->setValuePtr(getEntity(input->secondaryValue()->valueId())); + input->secondaryValue()->setValuePtr(getEntity(input->secondaryValue()->valueId(), target.get())); // Add missing variables and lists if (!value->valuePtr()) { @@ -144,7 +149,7 @@ void Engine::resolveIds() const auto &fields = block->fields(); for (auto field : fields) { std::string id = field->valueId(); // no reference! - field->setValuePtr(getEntity(id)); + field->setValuePtr(getEntity(id, target.get())); if (container) { field->setFieldId(container->resolveField(field->name())); @@ -174,7 +179,7 @@ void Engine::resolveIds() block->updateInputMap(); block->updateFieldMap(); - auto comment = getComment(block->commentId()); + auto comment = getComment(block->commentId(), target.get()); block->setComment(comment); if (comment) { @@ -204,7 +209,7 @@ void Engine::resolveIds() assert(target); for (auto field : fields) { - field->setValuePtr(getEntity(field->valueId())); + field->setValuePtr(getEntity(field->valueId(), target)); if (container) { field->setFieldId(container->resolveField(field->name())); @@ -262,7 +267,7 @@ void Engine::compile() Compiler compiler(this, target.get()); const auto &blocks = target->blocks(); for (auto block : blocks) { - if (block->topLevel() && !block->shadow()) { + if (block->topLevel() && !block->isTopLevelReporter() && !block->shadow()) { auto section = blockSection(block->opcode()); if (section) { auto script = std::make_shared