From de072904a5d4250510f5dd7a27afaea935d109bd Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 5 Feb 2024 18:07:32 +0100
Subject: [PATCH 1/4] Add target click hat

---
 include/scratchcpp/iengine.h   |  3 +++
 src/engine/internal/engine.cpp | 17 +++++++++++------
 src/engine/internal/engine.h   |  5 ++++-
 test/mocks/enginemock.h        |  1 +
 4 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h
index 1b347fff..b6b43004 100644
--- a/include/scratchcpp/iengine.h
+++ b/include/scratchcpp/iengine.h
@@ -283,6 +283,9 @@ class LIBSCRATCHCPP_EXPORT IEngine
         /* Registers the given "when key pressed" script. */
         virtual void addKeyPressScript(std::shared_ptr<Block> hatBlock, int fieldId) = 0;
 
+        /* Registers the given "when this sprite/stage clicked" script. */
+        virtual void addTargetClickScript(std::shared_ptr<Block> hatBlock) = 0;
+
         /*! Returns the list of targets. */
         virtual const std::vector<std::shared_ptr<Target>> &targets() const = 0;
 
diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index a1ee378b..bd6410a2 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -34,11 +34,8 @@
 using namespace libscratchcpp;
 
 const std::unordered_map<Engine::HatType, bool> Engine::m_hatRestartExistingThreads = {
-    { HatType::GreenFlag, true },
-    { HatType::BroadcastReceived, true },
-    { HatType::BackdropChanged, true },
-    { HatType::CloneInit, false },
-    { HatType::KeyPressed, false }
+    { HatType::GreenFlag, true },  { HatType::BroadcastReceived, true }, { HatType::BackdropChanged, true },
+    { HatType::CloneInit, false }, { HatType::KeyPressed, false },       { HatType::TargetClicked, true }
 };
 
 Engine::Engine() :
@@ -686,7 +683,7 @@ void Engine::setMousePressed(bool pressed)
 
 void Engine::clickTarget(Target *target)
 {
-    // TODO: Implement this (#92, #93)
+    startHats(HatType::TargetClicked, {}, target);
 }
 
 unsigned int Engine::stageWidth() const
@@ -969,6 +966,11 @@ void Engine::addKeyPressScript(std::shared_ptr<Block> hatBlock, int fieldId)
     addHatField(script, HatField::KeyOption, fieldId);
 }
 
+void Engine::addTargetClickScript(std::shared_ptr<Block> hatBlock)
+{
+    addHatToMap(m_whenTargetClickedHats, m_scripts[hatBlock].get());
+}
+
 const std::vector<std::shared_ptr<Target>> &Engine::targets() const
 {
     return m_targets;
@@ -1354,6 +1356,9 @@ const std::vector<Script *> &Engine::getHats(Target *target, HatType type)
         case HatType::KeyPressed:
             return m_whenKeyPressedHats[target];
 
+        case HatType::TargetClicked:
+            return m_whenTargetClickedHats[target];
+
         default: {
             static const std::vector<Script *> empty = {};
             return empty;
diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h
index fc945e9a..2bb5bf58 100644
--- a/src/engine/internal/engine.h
+++ b/src/engine/internal/engine.h
@@ -120,6 +120,7 @@ class Engine : public IEngine
         void addBackdropChangeScript(std::shared_ptr<Block> hatBlock, int fieldId) override;
         void addCloneInitScript(std::shared_ptr<Block> hatBlock) override;
         void addKeyPressScript(std::shared_ptr<Block> hatBlock, int fieldId) override;
+        void addTargetClickScript(std::shared_ptr<Block> hatBlock) override;
 
         const std::vector<std::shared_ptr<Target>> &targets() const override;
         void setTargets(const std::vector<std::shared_ptr<Target>> &newTargets) override;
@@ -156,7 +157,8 @@ class Engine : public IEngine
             BroadcastReceived,
             BackdropChanged,
             CloneInit,
-            KeyPressed
+            KeyPressed,
+            TargetClicked
         };
 
         enum class HatField
@@ -222,6 +224,7 @@ class Engine : public IEngine
         std::unordered_map<Target *, std::vector<Script *>> m_broadcastHats;
         std::unordered_map<Target *, std::vector<Script *>> m_cloneInitHats;
         std::unordered_map<Target *, std::vector<Script *>> m_whenKeyPressedHats;
+        std::unordered_map<Target *, std::vector<Script *>> m_whenTargetClickedHats;
 
         std::unordered_map<Script *, std::unordered_map<HatField, int>> m_scriptHatFields; // HatField, field ID from the block implementation
 
diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h
index feda73db..0ed2260f 100644
--- a/test/mocks/enginemock.h
+++ b/test/mocks/enginemock.h
@@ -101,6 +101,7 @@ class EngineMock : public IEngine
         MOCK_METHOD(void, addBackdropChangeScript, (std::shared_ptr<Block>, int), (override));
         MOCK_METHOD(void, addCloneInitScript, (std::shared_ptr<Block>), (override));
         MOCK_METHOD(void, addKeyPressScript, (std::shared_ptr<Block>, int), (override));
+        MOCK_METHOD(void, addTargetClickScript, (std::shared_ptr<Block>), (override));
 
         MOCK_METHOD(const std::vector<std::shared_ptr<Target>> &, targets, (), (const, override));
         MOCK_METHOD(void, setTargets, (const std::vector<std::shared_ptr<Target>> &), (override));

From d6b9fe604e8f1063f7885b85570f9e0503317c8f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 5 Feb 2024 18:22:07 +0100
Subject: [PATCH 2/4] Implement event_whenthisspriteclicked block

---
 src/blocks/eventblocks.cpp        |  6 ++++++
 src/blocks/eventblocks.h          |  1 +
 test/blocks/event_blocks_test.cpp | 21 +++++++++++++++++++++
 3 files changed, 28 insertions(+)

diff --git a/src/blocks/eventblocks.cpp b/src/blocks/eventblocks.cpp
index 4edd07b7..040dac6c 100644
--- a/src/blocks/eventblocks.cpp
+++ b/src/blocks/eventblocks.cpp
@@ -22,6 +22,7 @@ void EventBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
     engine->addCompileFunction(this, "event_whenflagclicked", &compileWhenFlagClicked);
+    engine->addCompileFunction(this, "event_whenthisspriteclicked", &compileWhenThisSpriteClicked);
     engine->addCompileFunction(this, "event_broadcast", &compileBroadcast);
     engine->addCompileFunction(this, "event_broadcastandwait", &compileBroadcastAndWait);
     engine->addCompileFunction(this, "event_whenbroadcastreceived", &compileWhenBroadcastReceived);
@@ -42,6 +43,11 @@ void EventBlocks::compileWhenFlagClicked(Compiler *compiler)
     compiler->engine()->addGreenFlagScript(compiler->block());
 }
 
+void EventBlocks::compileWhenThisSpriteClicked(Compiler *compiler)
+{
+    compiler->engine()->addTargetClickScript(compiler->block());
+}
+
 void EventBlocks::compileBroadcast(Compiler *compiler)
 {
     auto input = compiler->input(BROADCAST_INPUT);
diff --git a/src/blocks/eventblocks.h b/src/blocks/eventblocks.h
index 21f7e812..80004101 100644
--- a/src/blocks/eventblocks.h
+++ b/src/blocks/eventblocks.h
@@ -31,6 +31,7 @@ class EventBlocks : public IBlockSection
         void registerBlocks(IEngine *engine) override;
 
         static void compileWhenFlagClicked(Compiler *compiler);
+        static void compileWhenThisSpriteClicked(Compiler *compiler);
         static void compileBroadcast(Compiler *compiler);
         static void compileBroadcastAndWait(Compiler *compiler);
         static void compileWhenBroadcastReceived(Compiler *compiler);
diff --git a/test/blocks/event_blocks_test.cpp b/test/blocks/event_blocks_test.cpp
index 60cbb47b..39860da6 100644
--- a/test/blocks/event_blocks_test.cpp
+++ b/test/blocks/event_blocks_test.cpp
@@ -84,6 +84,7 @@ TEST_F(EventBlocksTest, RegisterBlocks)
 {
     // Blocks
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_whenflagclicked", &EventBlocks::compileWhenFlagClicked));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_whenthisspriteclicked", &EventBlocks::compileWhenThisSpriteClicked));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_broadcast", &EventBlocks::compileBroadcast));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_broadcastandwait", &EventBlocks::compileBroadcastAndWait));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_whenbroadcastreceived", &EventBlocks::compileWhenBroadcastReceived));
@@ -121,6 +122,26 @@ TEST_F(EventBlocksTest, WhenFlagClicked)
     ASSERT_TRUE(compiler.lists().empty());
 }
 
+TEST_F(EventBlocksTest, WhenThisSpriteClicked)
+{
+    Compiler compiler(&m_engineMock);
+
+    auto block = createEventBlock("a", "event_whenthisspriteclicked");
+
+    compiler.init();
+
+    EXPECT_CALL(m_engineMock, addTargetClickScript(block));
+    compiler.setBlock(block);
+    EventBlocks::compileWhenThisSpriteClicked(&compiler);
+
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_HALT }));
+    ASSERT_TRUE(compiler.constValues().empty());
+    ASSERT_TRUE(compiler.variables().empty());
+    ASSERT_TRUE(compiler.lists().empty());
+}
+
 TEST_F(EventBlocksTest, Broadcast)
 {
     Compiler compiler(&m_engineMock);

From 115bcb5154d8878387462ac8890f1a5aa4016522 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 5 Feb 2024 18:26:57 +0100
Subject: [PATCH 3/4] Implement event_whenstageclicked block

---
 src/blocks/eventblocks.cpp        |  6 ++++++
 src/blocks/eventblocks.h          |  1 +
 test/blocks/event_blocks_test.cpp | 21 +++++++++++++++++++++
 3 files changed, 28 insertions(+)

diff --git a/src/blocks/eventblocks.cpp b/src/blocks/eventblocks.cpp
index 040dac6c..81f925ef 100644
--- a/src/blocks/eventblocks.cpp
+++ b/src/blocks/eventblocks.cpp
@@ -23,6 +23,7 @@ void EventBlocks::registerBlocks(IEngine *engine)
     // Blocks
     engine->addCompileFunction(this, "event_whenflagclicked", &compileWhenFlagClicked);
     engine->addCompileFunction(this, "event_whenthisspriteclicked", &compileWhenThisSpriteClicked);
+    engine->addCompileFunction(this, "event_whenstageclicked", &compileWhenStageClicked);
     engine->addCompileFunction(this, "event_broadcast", &compileBroadcast);
     engine->addCompileFunction(this, "event_broadcastandwait", &compileBroadcastAndWait);
     engine->addCompileFunction(this, "event_whenbroadcastreceived", &compileWhenBroadcastReceived);
@@ -48,6 +49,11 @@ void EventBlocks::compileWhenThisSpriteClicked(Compiler *compiler)
     compiler->engine()->addTargetClickScript(compiler->block());
 }
 
+void EventBlocks::compileWhenStageClicked(Compiler *compiler)
+{
+    compiler->engine()->addTargetClickScript(compiler->block());
+}
+
 void EventBlocks::compileBroadcast(Compiler *compiler)
 {
     auto input = compiler->input(BROADCAST_INPUT);
diff --git a/src/blocks/eventblocks.h b/src/blocks/eventblocks.h
index 80004101..ec63bdf8 100644
--- a/src/blocks/eventblocks.h
+++ b/src/blocks/eventblocks.h
@@ -32,6 +32,7 @@ class EventBlocks : public IBlockSection
 
         static void compileWhenFlagClicked(Compiler *compiler);
         static void compileWhenThisSpriteClicked(Compiler *compiler);
+        static void compileWhenStageClicked(Compiler *compiler);
         static void compileBroadcast(Compiler *compiler);
         static void compileBroadcastAndWait(Compiler *compiler);
         static void compileWhenBroadcastReceived(Compiler *compiler);
diff --git a/test/blocks/event_blocks_test.cpp b/test/blocks/event_blocks_test.cpp
index 39860da6..717930c8 100644
--- a/test/blocks/event_blocks_test.cpp
+++ b/test/blocks/event_blocks_test.cpp
@@ -85,6 +85,7 @@ TEST_F(EventBlocksTest, RegisterBlocks)
     // Blocks
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_whenflagclicked", &EventBlocks::compileWhenFlagClicked));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_whenthisspriteclicked", &EventBlocks::compileWhenThisSpriteClicked));
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_whenstageclicked", &EventBlocks::compileWhenStageClicked));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_broadcast", &EventBlocks::compileBroadcast));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_broadcastandwait", &EventBlocks::compileBroadcastAndWait));
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "event_whenbroadcastreceived", &EventBlocks::compileWhenBroadcastReceived));
@@ -142,6 +143,26 @@ TEST_F(EventBlocksTest, WhenThisSpriteClicked)
     ASSERT_TRUE(compiler.lists().empty());
 }
 
+TEST_F(EventBlocksTest, WhenStageClicked)
+{
+    Compiler compiler(&m_engineMock);
+
+    auto block = createEventBlock("a", "event_whenstageclicked");
+
+    compiler.init();
+
+    EXPECT_CALL(m_engineMock, addTargetClickScript(block));
+    compiler.setBlock(block);
+    EventBlocks::compileWhenStageClicked(&compiler);
+
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_HALT }));
+    ASSERT_TRUE(compiler.constValues().empty());
+    ASSERT_TRUE(compiler.variables().empty());
+    ASSERT_TRUE(compiler.lists().empty());
+}
+
 TEST_F(EventBlocksTest, Broadcast)
 {
     Compiler compiler(&m_engineMock);

From cd81439ed55df9b2b32e124afe035e8ddc1f3c71 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 5 Feb 2024 18:54:08 +0100
Subject: [PATCH 4/4] Add test for target click scripts

---
 test/engine/engine_test.cpp   |  81 ++++++++++++++++++++++++++++++++++
 test/target_click_scripts.sb3 | Bin 0 -> 1566 bytes
 2 files changed, 81 insertions(+)
 create mode 100644 test/target_click_scripts.sb3

diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index 9bb9378b..3a07ecfc 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -994,6 +994,87 @@ TEST(EngineTest, Broadcasts)
     ASSERT_EQ(engine.findBroadcastById("c"), 2);
 }
 
+TEST(EngineTest, TargetClickScripts)
+{
+    Project p("target_click_scripts.sb3");
+    ASSERT_TRUE(p.load());
+    p.run();
+
+    auto engine = p.engine();
+
+    Stage *stage = engine->stage();
+    ASSERT_TRUE(stage);
+
+    // Initial state
+    ASSERT_VAR(stage, "1");
+    ASSERT_EQ(GET_VAR(stage, "1")->value().toInt(), 0);
+    ASSERT_VAR(stage, "2");
+    ASSERT_EQ(GET_VAR(stage, "2")->value().toInt(), 0);
+    ASSERT_VAR(stage, "stage");
+    ASSERT_EQ(GET_VAR(stage, "stage")->value().toInt(), 0);
+
+    // Sprite1
+    Target *sprite = engine->targetAt(engine->findTarget("Sprite1"));
+    ASSERT_TRUE(sprite);
+    engine->clickTarget(sprite);
+    engine->step();
+    ASSERT_VAR(stage, "1");
+    ASSERT_EQ(GET_VAR(stage, "1")->value().toInt(), 1);
+    ASSERT_VAR(stage, "2");
+    ASSERT_EQ(GET_VAR(stage, "2")->value().toInt(), 0);
+    ASSERT_VAR(stage, "stage");
+    ASSERT_EQ(GET_VAR(stage, "stage")->value().toInt(), 0);
+
+    engine->clickTarget(sprite);
+    engine->step();
+    ASSERT_VAR(stage, "1");
+    ASSERT_EQ(GET_VAR(stage, "1")->value().toInt(), 2);
+    ASSERT_VAR(stage, "2");
+    ASSERT_EQ(GET_VAR(stage, "2")->value().toInt(), 0);
+    ASSERT_VAR(stage, "stage");
+    ASSERT_EQ(GET_VAR(stage, "stage")->value().toInt(), 0);
+
+    // Sprite2
+    sprite = engine->targetAt(engine->findTarget("Sprite2"));
+    ASSERT_TRUE(sprite);
+    engine->clickTarget(sprite);
+    engine->step();
+    ASSERT_VAR(stage, "1");
+    ASSERT_EQ(GET_VAR(stage, "1")->value().toInt(), 2);
+    ASSERT_VAR(stage, "2");
+    ASSERT_EQ(GET_VAR(stage, "2")->value().toInt(), 1);
+    ASSERT_VAR(stage, "stage");
+    ASSERT_EQ(GET_VAR(stage, "stage")->value().toInt(), 0);
+
+    engine->clickTarget(sprite);
+    engine->step();
+    ASSERT_VAR(stage, "1");
+    ASSERT_EQ(GET_VAR(stage, "1")->value().toInt(), 2);
+    ASSERT_VAR(stage, "2");
+    ASSERT_EQ(GET_VAR(stage, "2")->value().toInt(), 2);
+    ASSERT_VAR(stage, "stage");
+    ASSERT_EQ(GET_VAR(stage, "stage")->value().toInt(), 0);
+
+    // Stage
+    engine->clickTarget(stage);
+    engine->step();
+    ASSERT_VAR(stage, "1");
+    ASSERT_EQ(GET_VAR(stage, "1")->value().toInt(), 2);
+    ASSERT_VAR(stage, "2");
+    ASSERT_EQ(GET_VAR(stage, "2")->value().toInt(), 2);
+    ASSERT_VAR(stage, "stage");
+    ASSERT_EQ(GET_VAR(stage, "stage")->value().toInt(), 1);
+
+    engine->clickTarget(stage);
+    engine->step();
+    ASSERT_VAR(stage, "1");
+    ASSERT_EQ(GET_VAR(stage, "1")->value().toInt(), 2);
+    ASSERT_VAR(stage, "2");
+    ASSERT_EQ(GET_VAR(stage, "2")->value().toInt(), 2);
+    ASSERT_VAR(stage, "stage");
+    ASSERT_EQ(GET_VAR(stage, "stage")->value().toInt(), 2);
+}
+
 TEST(EngineTest, Targets)
 {
     Engine engine;
diff --git a/test/target_click_scripts.sb3 b/test/target_click_scripts.sb3
new file mode 100644
index 0000000000000000000000000000000000000000..ba8a0233c14ce53c3225443fa66875a67fa5e540
GIT binary patch
